Compare commits

..
Author SHA1 Message Date
ohif-bot 16866a47a0 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.4
 - @ohif/core@2.6.3
 - @ohif/viewer@3.8.7
2020-03-24 17:13:15 +00:00
Erik Ziegler 77ab0ad9a1 fix: Ensure we take into account pixel spacing fields properly (#1555)
* fix: Ensure we take into account pixel spacing fields properly

* Rename to getPixelSpacingInformation
2020-03-24 18:10:58 +01:00
ohif-bot 1c306dd243 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.3
 - @ohif/core@2.6.2
 - @ohif/viewer@3.8.6
2020-03-24 15:19:55 +00:00
Erik Ziegler 404d52fe5c fix: OverlayPlane module usage for ArrayBuffer, BulkDataURI, and InlineBinary cases, as well as PaletteColor LUTs for ArrayBuffer (i.e. local drag/drop) case (#1546) 2020-03-24 16:18:13 +01:00
ohif-bot d9a50b6b08 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.2
 - @ohif/core@2.6.1
 - @ohif/viewer@3.8.5
2020-03-23 18:34:20 +00:00
Kedarandkedar.netelixir e41d339f5f fix: avoid-wasteful-renders (#1544)
* Avoid un-necessary redux state update for frequent actions

* Avoid un-necessary re-render when no change in props

Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com>
2020-03-23 14:31:39 -04:00
ohif-bot e374fe2118 chore(release): publish [skip ci]
- @ohif/viewer@3.8.4
2020-03-19 12:26:24 +00:00
Jack Cosgrove 9feadd3c6d fix: Only permit web workers to be initialized once. (#1535) 2020-03-19 13:21:20 +01:00
ohif-bot 3b5f71b0be chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.1
 - @ohif/ui@1.4.1
 - @ohif/viewer@3.8.3
2020-03-17 22:49:26 +00:00
Darren b269415048 fix: rendering delete button when text is too wide for parent div (#1526) 2020-03-17 18:48:06 -04:00
ohif-bot c9e421cff3 chore(release): publish [skip ci]
- @ohif/viewer@3.8.2
2020-03-17 18:19:35 +00:00
Mirna SilvaandDanny Brown d9999c1ae4 ci: Added verification on Before hook to avoid navigating again to viewer page (#1505)
* Added verification on Before hook to avoid navigating again to viewer page

* Removed unecessary screenshots and duplicated test cases

* Navigation Fix for HTML extension and improved Flip button verification

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-03-17 14:14:04 -04:00
ohif-bot 31ba042121 chore(release): publish [skip ci]
- @ohif/viewer@3.8.1
2020-03-17 13:08:24 +00:00
Anth Lubic 2747effd9e fix: resolves #1483 (#1527) 2020-03-17 14:05:56 +01:00
ohif-bot 6ed1e47a3d chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.5.0
 - @ohif/extension-dicom-segmentation@0.1.0
 - @ohif/extension-vtk@1.3.0
 - @ohif/core@2.6.0
 - @ohif/ui@1.4.0
 - @ohif/viewer@3.8.0
2020-03-13 19:47:28 +00:00
e8842cf8ae feat: Segmentations Settings UI - Phase 1 #1391 (#1392)
* Filter segs and fetch correct metadata.

* Initiate seg loading logic on series load into viewport. Need to write.

* Automatic consumption of SEG functional.

* Pull in master.

* Fix issues after pulling in master.

* Clean up old segmentation handlers.

* WIP

* WIP

* Poppulate viewport with metadata.

* Show hide/segmentation in react-vtkjs-viewport.

* feat: 🎸 View segmentations in vtkjs viewport

* Filter segs and fetch correct metadata.

* Initiate seg loading logic on series load into viewport. Need to write.

* Automatic consumption of SEG functional.

* Pull in master.

* Fix issues after pulling in master.

* Clean up old segmentation handlers.

* WIP

* WIP

* Poppulate viewport with metadata.

* Show hide/segmentation in react-vtkjs-viewport.

* feat: 🎸 View segmentations in vtkjs viewport

* Rebase on master.

* format format format

* add basic extension parts

* Updated yarn lock

* Fetch derived/referenced displaysets in sidepanel

* Add buttons, expose seriesDescription, write notes for Danny.

* Add growcut tool extention. -> Move to a seperate repo.

* Change some text fields.

* expose dialog provider

* hide draggable area

* No pointer events for now; need to revisit

* Don't filter seg from the outset for now

* fiddling about

* fix path to series data

* fix logic to determine next available labelmapindex

* Ability to swtich between labelmaps

* Return a promise so we can make sure our labelmap is loaded before pulling metadata

* Better UI to toggle labelmaps

* Fix accidental untoggle of active labelmap for ui

* Remove brush tool from cornerstone extension

* Add cstools in preInit for seg extension

* Add buttons for seg magic

* Hide the measurements panel for now

* Slightly better seg/scissor icons

* Add eraser

* ability to switch segments

* seg color / switcher

* Fix vtk labelmap cache.

* sidepanel should scroll if there's overflow

* Use a fancier range slider

* Slightly prettier buttons

* Push in progress

* A bit more reactive; handle no-meta

* Update to vtk.js 11.14.0 and react-vtkjs-viewport 0.6.0

* chore: kickstart preview

* Fix conflict issues

* Refactor panel

* Isolate increment functions

* Wip segment table

* Hide selector and add scroll to segmentation

* Increase width of segment description

* Hide not used items

* Add new select for segmentations

* Align ui

* Fix default segmentation

* Fix broken import test

* Get measurements panel back and add spacing to grouped buttons

* Hide brush and selector and refresh when switching segmentations

* Add pointer events back and fix broken tests for measurement table

* Update segment list when switching humbnails

* CR Update: Minor UI adjustments

* CR Update: Set active segment

* Move components to components folder

* Add new settings ui

* Integrate configuration

* Improve styles

* Float numbers

* Improve css

* Refresh settings menu if side panel changed

* Fix settings rendering logic and remove comments

* Extract components

* Remove not used tools

* Remove non used extension

* Use style variables and remove comments

* Remove non used dep from lock

* Remove tools

* Remove segmentation from toolbar

* Add todo and brushradius from config

* Fix packagejson growcut import

* Remove webpack config already defined in parent

* Update package json description

* Change component name to british english to us

* Improve prop types

* Rename cornerstone label map event

* Scope css

* Extract and add doc to helper

* Update jsdocs for getDerivedDatasets

* Add proptypes and cr updates

* Update tablistitem proptypes

* config is readonly, rollback destructuring

* Remove event for side panel

* Add jsdocs for util

* Extract getimageid to studymetada

* Use ohif log instead of console

* Remove specific style from side panel

* Fix default props for brush radius

* Descructure configuration props from settings component to avoid invalid props

* Update proptypes to be required (settings)

* Update readme

* Remove commandsmodule

* Get configuration module inside the component

* Set selected segment

* Rename extension and use modality to filter

* Fix for failing tests (#1423)

* hello world

* Finish rebase for Igor.

* Re-fix cornerstoneTools version that got messed up in merge.

* Fix reactivity of isDisabled, write implementation for the SEG panel.

* Nuke yarn.lock to stack CST to 4.12.0

* Remove debugger statements.

* fix: bump cornerstone-tools version to address toFixed issue

* test: force tests for MISTER^MR to navigate to study directly

* test: remove eraser tool test

* test: count is off by one

* test: don't save SR state for the time being

* test: disable more tests

Co-authored-by: James A. Petts <jamesapetts@gmail.com>
Co-authored-by: dannyrb <danny.ri.brown@gmail.com>
Co-authored-by: igoroctaviano] <igoroctaviano@users.noreply.github.com>
Co-authored-by: Mirna Silva <mirna.silva@radicalimaging.com>
2020-03-13 15:45:26 -04:00
101 changed files with 3768 additions and 2310 deletions

No files matched your search

+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.4.3...@ohif/extension-cornerstone@2.5.0) (2020-03-13)
### Features
* Segmentations Settings UI - Phase 1 [#1391](https://github.com/OHIF/Viewers/issues/1391) ([#1392](https://github.com/OHIF/Viewers/issues/1392)) ([e8842cf](https://github.com/OHIF/Viewers/commit/e8842cf8aebde98db7fc123e4867c8288552331f)), closes [#1423](https://github.com/OHIF/Viewers/issues/1423)
## [2.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.4.2...@ohif/extension-cornerstone@2.4.3) (2020-03-09)
**Note:** Version bump only for package @ohif/extension-cornerstone
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "2.4.3",
"version": "2.5.0",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -34,7 +34,7 @@
"@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.12.0",
"cornerstone-tools": "4.12.5",
"cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.10.1",
"dicom-parser": "^1.8.3",
@@ -4,19 +4,9 @@ import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
import OHIF from '@ohif/core';
import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core';
import handleSegmentationStorage from './handleSegmentationStorage.js';
const { StackManager } = OHIF.utils;
const SOP_CLASSES = {
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
};
const specialCaseHandlers = {};
specialCaseHandlers[
SOP_CLASSES.SEGMENTATION_STORAGE
] = handleSegmentationStorage;
class OHIFCornerstoneViewport extends Component {
state = {
viewportData: null,
@@ -124,42 +114,24 @@ class OHIFCornerstoneViewport extends Component {
studies,
StudyInstanceUID,
displaySetInstanceUID,
SOPClassUID,
SOPInstanceUID,
frameIndex
) => {
let viewportData;
switch (SOPClassUID) {
case SOP_CLASSES.SEGMENTATION_STORAGE:
const specialCaseHandler =
specialCaseHandlers[SOP_CLASSES.SEGMENTATION_STORAGE];
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
studies,
StudyInstanceUID,
displaySetInstanceUID,
SOPInstanceUID,
frameIndex
);
viewportData = await specialCaseHandler(
studies,
StudyInstanceUID,
displaySetInstanceUID,
SOPInstanceUID,
frameIndex
);
break;
default:
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
studies,
StudyInstanceUID,
displaySetInstanceUID,
SOPInstanceUID,
frameIndex
);
viewportData = {
StudyInstanceUID,
displaySetInstanceUID,
stack,
};
break;
}
viewportData = {
StudyInstanceUID,
displaySetInstanceUID,
stack,
};
return viewportData;
};
@@ -184,13 +156,10 @@ class OHIFCornerstoneViewport extends Component {
);
}
const SOPClassUID = sopClassUIDs && sopClassUIDs[0];
this.getViewportData(
studies,
StudyInstanceUID,
displaySetInstanceUID,
SOPClassUID,
SOPInstanceUID,
frameIndex
).then(viewportData => {
@@ -205,7 +174,7 @@ class OHIFCornerstoneViewport extends Component {
}
componentDidUpdate(prevProps) {
const { studies, displaySet } = this.props.viewportData;
const { displaySet } = this.props.viewportData;
const prevDisplaySet = prevProps.viewportData.displaySet;
if (
@@ -1,128 +0,0 @@
import * as dcmjs from 'dcmjs';
import OHIF from '@ohif/core';
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
const { StackManager, DicomLoaderService } = OHIF.utils;
function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const displaySet = study.displaySets.find(set => {
return set.displaySetInstanceUID === displaySetInstanceUID;
});
return displaySet;
}
function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
return study.displaySets.filter(set => {
return set.SeriesInstanceUID === SeriesInstanceUID;
});
}
function parseSeg(arrayBuffer, imageIds) {
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
imageIds,
arrayBuffer,
cornerstone.metaData
);
}
function addSegMetadataToCornerstoneToolState(
segMetadata,
toolState,
displaySetInstanceUID
) {
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
toolState
);
const brushModule = cornerstoneTools.store.modules.brush;
for (let i = 0; i < segMetadata.length; i++) {
brushModule.setters.metadata(displaySetInstanceUID, i, segMetadata[i]);
}
}
async function handleSegmentationStorage(
studies,
StudyInstanceUID,
displaySetInstanceUID
) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const displaySet = getDisplaySet(
studies,
StudyInstanceUID,
displaySetInstanceUID
);
const arrayBuffer = await DicomLoaderService.findDicomDataPromise(
displaySet,
studies
);
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
);
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
const displaySets = getDisplaySetsBySeries(
studies,
StudyInstanceUID,
SeriesInstanceUID
);
if (displaySets.length > 1) {
console.warn(
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
);
}
const referenceDisplaySet = displaySets[0];
const imageIds = referenceDisplaySet.images.map(image => image.getImageId());
const results = parseSeg(arrayBuffer, imageIds);
if (!results) {
throw new Error('Fractional segmentations are not supported');
}
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
const { setters } = cornerstoneTools.getModule('segmentation');
setters.labelmap3DByFirstImageId(
imageIds[0],
labelmapBuffer,
0, // TODO -> Can define a color LUT based on colors in the SEG later.
segMetadata,
imageIds.length,
segmentsOnFrame
);
const cachedStack = StackManager.findOrCreateStack(
study,
referenceDisplaySet
);
const stack = Object.assign({}, cachedStack);
stack.currentImageIdIndex = 0;
return {
StudyInstanceUID,
displaySetInstanceUID,
stack,
};
}
export default handleSegmentationStorage;
-1
View File
@@ -66,7 +66,6 @@ export default function init({ servicesManager, configuration }) {
csTools.DragProbeTool,
csTools.RectangleRoiTool,
],
segmentation: [csTools.BrushTool],
other: [
csTools.PanTool,
csTools.ZoomTool,
@@ -0,0 +1,8 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
};
@@ -0,0 +1,33 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const pkg = require('./../package.json');
const ROOT_DIR = path.join(__dirname, './..');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
return merge(commonConfig, {
devtool: 'source-map',
optimization: {
minimize: true,
sideEffects: true,
},
output: {
path: ROOT_DIR,
library: 'OHIFExtDicomSeg',
libraryTarget: 'umd',
libraryExport: 'default',
filename: pkg.main,
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
],
});
};
@@ -0,0 +1,11 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# 0.1.0 (2020-03-13)
### Features
* Segmentations Settings UI - Phase 1 [#1391](https://github.com/OHIF/Viewers/issues/1391) ([#1392](https://github.com/OHIF/Viewers/issues/1392)) ([e8842cf](https://github.com/OHIF/Viewers/commit/e8842cf8aebde98db7fc123e4867c8288552331f)), closes [#1423](https://github.com/OHIF/Viewers/issues/1423)
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+65
View File
@@ -0,0 +1,65 @@
# @ohif/dicom-segmentation
![npm (scoped)](https://img.shields.io/npm/v/@ohif/dicom-segmentation.svg?style=flat-square)
This extension adds support to view and navigate the segments available in the DICOM SEG,
allowing control over visibility of a given segment and providing metadata about the segment(s).
<!-- TODO: Simple image or GIF? -->
#### Index
Extension Id: `com.ohif.dicom-segmentation`
## Tool Configuration
Tools can be configured through extension configuration using the tools key:
```js
...
cornerstoneExtensionConfig: {
tools: {
ArrowAnnotate: {
configuration: {
getTextCallback: (callback, eventDetails) => callback(prompt('Enter your custom annotation')),
},
},
},
},
...
```
## Annotate Tools Configuration
*We currently support one property for annotation tools.*
### Hide handles
This extension configuration allows you to toggle on/off handle rendering for all annotate tools:
```js
...
cornerstoneExtensionConfig: {
hideHandles: true,
},
...
## Resources
### Repositories
- [cornerstonejs/react-cornerstone-viewport][react-viewport]
- [cornerstonejs/cornerstoneTools][cornerstone-tools]
- [cornerstonejs/cornerstone][cornerstone]
<!--
Links
-->
<!-- prettier-ignore-start -->
[docs-commands]: https://www.com
[docs-hotkeys]: https://www.com
[docs-userprefs]: htt
[react-viewport]: https://github.com/cornerstonejs/react-cornerstone-viewport
[cornerstone-tools]: https://github.com/cornerstonejs/cornerstoneTools
[cornerstone]: https://github.com/cornerstonejs/cornerstone
<!-- prettier-ignore-end -->
@@ -0,0 +1,43 @@
{
"name": "@ohif/extension-dicom-segmentation",
"version": "0.1.0",
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "src/index.js",
"engines": {
"node": ">=10",
"npm": ">=6",
"yarn": ">=1.16.0"
},
"files": [
"dist",
"README.md"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
"dev:dicom-html": "yarn run dev",
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
"build:package": "yarn run build",
"prepublishOnly": "yarn run build",
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.12.5",
"dcmjs": "^0.6.1",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"react-select": "^3.0.8"
}
}
@@ -0,0 +1,71 @@
import { MODULE_TYPES, utils } from '@ohif/core';
import loadSegmentation from './loadSegmentation';
// TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = {
DICOM_SEG: '1.2.840.10008.5.1.4.1.1.66.4',
};
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
// TODO: Handle the case where there is more than one SOP Class Handler for the
// same SOP Class.
const OHIFDicomSegSopClassHandler = {
id: 'OHIFDicomSegSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs,
getDisplaySetFromSeries: function(
series,
study,
dicomWebClient,
authorizationHeaders
) {
const instance = series.getFirstInstance();
const metadata = instance.getData().metadata;
const {
SeriesDate,
SeriesTime,
SeriesDescription,
FrameOfReferenceUID,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
} = metadata;
const segDisplaySet = {
Modality: 'SEG',
displaySetInstanceUID: utils.guid(),
wadoRoot: study.getData().wadoRoot,
wadoUri: instance.getData().wadouri,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
FrameOfReferenceUID,
authorizationHeaders,
metadata,
isDerived: true,
referencedDisplaySetUID: null, // Assigned when loaded.
labelmapIndex: null, // Assigned when loaded.
isLoaded: false,
SeriesDate,
SeriesTime,
SeriesDescription,
};
segDisplaySet.load = function(referencedDisplaySet, studies) {
return loadSegmentation(
segDisplaySet,
referencedDisplaySet,
studies
).catch(error => {
segDisplaySet.isLoaded = false;
throw new Error(error);
});
};
return segDisplaySet;
},
};
export default OHIFDicomSegSopClassHandler;
@@ -0,0 +1,41 @@
.dcmseg-brush-color-selector .selector-buttons button {
color: #333;
background-color: #fff;
padding: 4px 12px;
line-height: 1.4;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
user-select: none;
border: 1px solid #ccc;
border-radius: 4px;
transition: all 0.15s ease;
}
.dcmseg-brush-color-selector .selector-buttons button:hover,
.dcmseg-brush-color-selector .selector-buttons button:active,
.dcmseg-brush-color-selector .selector-buttons button.active {
background-color: var(--active-color);
border-color: var(--ui-border-color-active);
color: #fff;
}
.dcmseg-brush-color-selector .selector-buttons {
margin-left: 10px;
display: flex;
flex-direction: row;
align-items: center;
}
.dcmseg-brush-color-selector .selector-buttons button:last-of-type {
margin-left: 5px;
}
.dcmseg-brush-color-selector .selector-active-segment {
border-radius: 100%;
width: 32px;
height: 32px;
text-align: center;
line-height: 32px;
}
@@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import './BrushColorSelector.css';
const BrushColorSelector = ({ defaultColor, index, onNext, onPrev }) => (
<div className="dcmseg-brush-color-selector">
<div
className="selector-active-segment"
style={{ backgroundColor: defaultColor }}
>
{index}
</div>
<div className="selector-buttons">
<button onClick={onPrev}>
Previous
</button>
<button onClick={onNext}>
Next
</button>
</div>
</div>
);
BrushColorSelector.propTypes = {
defaultColor: PropTypes.string.isRequired,
index: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
onNext: PropTypes.func.isRequired,
onPrev: PropTypes.func.isRequired,
};
export default BrushColorSelector;
@@ -0,0 +1,4 @@
.dcmseg-brush-radius label {
display: block;
margin-bottom: 8px;
}
@@ -0,0 +1,36 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Range } from '@ohif/ui';
import './BrushRadius.css';
const BrushRadius = ({ value, onChange, min, max, step }) => (
<div className="dcmseg-brush-radius">
<label htmlFor="dcmseg-brush-radius">Brush Radius</label>
<Range
value={value}
min={min}
max={max}
step={step}
onChange={onChange}
id="dcmseg-brush-radius"
/>
</div>
);
BrushRadius.propTypes = {
value: PropTypes.number.isRequired,
onChange: PropTypes.number,
min: PropTypes.number,
max: PropTypes.number,
step: PropTypes.number,
};
BrushRadius.defaultProps = {
onChange: () => { },
min: 1,
max: 50,
step: 1,
};
export default BrushRadius;
@@ -0,0 +1,64 @@
.dcmseg-segment-item .segment-color-section {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.dcmseg-segment-item .segment-color {
margin-top: 8px;
border-radius: 100%;
width: 20px;
height: 20px;
}
.dcmseg-segment-item .segment-label {
overflow: hidden;
max-width: calc(100% - 50px);
text-overflow: ellipsis;
overflow-wrap: normal;
white-space: nowrap;
}
.dcmseg-segment-item .segment-info {
display: inline-block;
margin-top: 9px;
padding-left: 9px;
width: 90px;
border-left: 1px solid var(--text-secondary-color);
color: var(--text-primary-color);
width: 100%;
}
.dcmseg-segment-item.selected .segment-actions {
height: 35px;
visibility: visible;
}
.dcmseg-segment-item .segment-actions {
margin-left: -1px;
background-color: var(--ui-gray-darker);
height: 0;
overflow: hidden;
transition: all 0.3s ease;
visibility: hidden;
padding-left: 14px;
}
.dcmseg-segment-item .segment-actions .btnAction {
background-color: transparent;
border: none;
color: var(--default-color);
cursor: pointer;
line-height: 35px;
height: 35px;
transition: all 0.3s ease;
}
.dcmseg-segment-item .segment-actions .btnAction:hover, .dcmseg-segment-item .segment-actions .btnAction:active {
color: var(--text-primary-color);
}
.dcmseg-segment-item .segment-actions .btnAction i {
margin-right: 4px;
}
@@ -0,0 +1,76 @@
import React from 'react';
import PropTypes from 'prop-types';
import { TableListItem, Icon } from '@ohif/ui';
import './SegmentItem.css';
const ColoredCircle = ({ color }) => {
return (
<div
className="segment-color"
style={{ backgroundColor: `rgba(${color.join(',')})` }}
></div>
);
};
ColoredCircle.propTypes = {
color: PropTypes.array.isRequired,
};
const SegmentItem = ({ index, label, onClick, itemClass, color }) => (
<div className="dcmseg-segment-item">
<TableListItem
key={index}
itemKey={index}
itemIndex={index}
itemClass={itemClass}
itemMeta={<ColoredCircle color={color} />}
itemMetaClass="segment-color-section"
onItemClick={onClick}
>
<div>
<div className="segment-label" style={{ marginBottom: 4 }}>
{label}
</div>
{false && <div className="segment-info">{'...'}</div>}
{false && (
<div className="segment-actions">
<button
className="btnAction"
onClick={() => console.log('Relabelling...')}
>
<span style={{ marginRight: '4px' }}>
<Icon name="edit" width="14px" height="14px" />
</span>
Relabel
</button>
<button
className="btnAction"
onClick={() => console.log('Editing description...')}
>
<span style={{ marginRight: '4px' }}>
<Icon name="edit" width="14px" height="14px" />
</span>
Description
</button>
</div>
)}
</div>
</TableListItem>
</div>
);
SegmentItem.propTypes = {
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
label: PropTypes.string.isRequired,
onClick: PropTypes.func,
itemClass: PropTypes.string,
color: PropTypes.array.isRequired,
};
SegmentItem.defaultProps = {
itemClass: '',
onClick: () => { }
};
export default SegmentItem;
@@ -0,0 +1,25 @@
.dcmseg-segmentation-item {
display: flex;
justify-content: start;
margin: 0;
}
.dcmseg-segmentation-item .segmentation-meta {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
}
.dcmseg-segmentation-item .segmentation-meta-title {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
color: white;
max-width: calc(100% - 30px);
}
.dcmseg-segmentation-item .segmentation-meta-description {
font-size: 12px;
color: var(--text-secondary-color);
}
@@ -0,0 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
import './SegmentationItem.css';
const SegmentationItem = ({ onClick, title, description }) => {
return (
<li className="dcmseg-segmentation-item" onClick={onClick}>
<div className="segmentation-meta">
<div className="segmentation-meta-title">{title}</div>
<div className="segmentation-meta-description">{description}</div>
</div>
</li>
);
};
SegmentationItem.propTypes = {
onClick: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
};
SegmentationItem.defaultProps = {
description: ''
};
export default SegmentationItem;
@@ -0,0 +1,61 @@
.dcmseg-segmentation-panel {
color: white;
min-height: 200px;
background-color: var(--primary-background-color);
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
}
.dcmseg-segmentation-panel h3 {
padding-left: 20px;
margin-top: 0;
}
.dcmseg-segmentation-panel .segmentations {
padding: 0 20px 20px 20px;
}
.dcmseg-segmentation-panel .cog-icon {
margin: 20px 20px 0 0;
align-self: flex-end;
color: var(--default-color);
cursor: pointer;
}
.dcmseg-segmentation-panel .cog-icon:hover {
color: var(--hover-color);
}
.dcmseg-segmentation-panel .segmentation-icon {
margin-right: 8px;
margin-top: 12px;
min-width: 14px;
}
.dcmseg-segmentation-panel .selector-form {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding: 20px;
height: 150px;
}
.dcmseg-segmentation-panel .selector-form>div {
width: 100%;
}
.dcmseg-segmentation-panel .selector-form>div:first-of-type {
display: flex;
flex-direction: row;
justify-content: center;
}
.dcmseg-segmentation-panel .tableList .tableListHeader .numberOfItems {
float: unset;
max-width: unset;
flex: unset;
margin-right: 16px;
}
@@ -0,0 +1,475 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import cornerstoneTools from 'cornerstone-tools';
import cornerstone from 'cornerstone-core';
import moment from 'moment';
import { utils, log } from '@ohif/core';
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
import {
BrushColorSelector,
BrushRadius,
SegmentationItem,
SegmentItem,
SegmentationSelect,
} from '../index';
import './SegmentationPanel.css';
import SegmentationSettings from '../SegmentationSettings/SegmentationSettings';
const { studyMetadataManager } = utils;
const refreshViewport = () => {
cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element);
});
};
/**
* SegmentationPanel component
*
* @param {Object} props
* @param {Array} props.studies
* @param {Array} props.viewports - viewportSpecificData
* @param {number} props.activeIndex - activeViewportIndex
* @returns component
*/
const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
/*
* TODO: wrap get/set interactions with the cornerstoneTools
* store with context to make these kind of things less blurry.
*/
const segmentationModule = cornerstoneTools.getModule('segmentation');
const { configuration } = segmentationModule;
const DEFAULT_BRUSH_RADIUS = configuration.radius || 10;
const [brushRadius, setBrushRadius] = useState(DEFAULT_BRUSH_RADIUS);
/* TODO: We shouldn't hardcode this color, in the future the SEG may set the colorLUT to whatever it wants. */
const [brushColor, setBrushColor] = useState('rgba(221, 85, 85, 1)');
const [selectedSegment, setSelectedSegment] = useState();
const [showSegSettings, setShowSegSettings] = useState(false);
const [selectedSegmentation, setSelectedSegmentation] = useState();
const viewport = viewports[activeIndex];
const {
StudyInstanceUID,
SeriesInstanceUID,
displaySetInstanceUID,
} = viewport;
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
const firstImageId = studyMetadata.getFirstImageId(displaySetInstanceUID);
/* CornerstoneTools */
const [brushStackState, setBrushStackState] = useState(
segmentationModule.state.series[firstImageId]
);
useEffect(() => {
setShowSegSettings(showSegSettings && !isOpen);
}, [isOpen]);
useEffect(() => {
setBrushStackState(segmentationModule.state.series[firstImageId]);
}, [studies, viewports, activeIndex, firstImageId]);
useEffect(() => {
if (brushStackState) {
setSelectedSegmentation(brushStackState.activeLabelmapIndex);
}
const labelmapModifiedHandler = event => {
log.warn('labelmap modified', event);
setBrushStackState(segmentationModule.state.series[firstImageId]);
};
/*
* These are specific to each element;
* Need to iterate cornerstone-tools tracked enabled elements?
* Then only care about the one tied to active viewport?
*/
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
enabledElement.addEventListener(
'cornerstonetoolslabelmapmodified',
labelmapModifiedHandler
)
);
return () => {
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
enabledElement.removeEventListener(
'cornerstonetoolslabelmapmodified',
labelmapModifiedHandler
)
);
};
});
if (!brushStackState) {
return null;
}
const labelmap3D =
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
/*
* 2. UseEffect to update state? or to a least trigger a re-render
* 4. Toggle visibility of labelmap?
* 5. Toggle visibility of seg?
*
* If the port is cornerstone, just need to call a re-render.
* If the port is vtkjs, its a bit more tricky as we now need to create a new
*/
const getLabelmapList = () => {
/* Get list of SEG labelmaps specific to active viewport (reference series) */
const referencedSegDisplaysets = _getReferencedSegDisplaysets(
StudyInstanceUID,
SeriesInstanceUID
);
return referencedSegDisplaysets.map((displaySet, index) => {
const { labelmapIndex, SeriesDate, SeriesTime } = displaySet;
/* Map to display representation */
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
const isActiveLabelmap =
labelmapIndex === brushStackState.activeLabelmapIndex;
const displayDate = date.format('ddd, MMM Do YYYY');
const displayTime = date.format('h:mm:ss a');
const displayDescription = displaySet.SeriesDescription;
return {
value: labelmapIndex,
title: displayDescription,
description: displayDate,
onClick: async () => {
const activatedLabelmapIndex = await _setActiveLabelmap(
viewport,
studies,
displaySet,
firstImageId,
brushStackState.activeLabelmapIndex
);
setSelectedSegmentation(activatedLabelmapIndex);
},
};
});
};
const labelmapList = getLabelmapList();
const segmentList = [];
if (labelmap3D) {
/*
* Newly created segments have no `meta`
* So we instead build a list of all segment indexes in use
* Then find any associated metadata
*/
const uniqueSegmentIndexes = labelmap3D.labelmaps2D
.reduce((acc, labelmap2D) => {
if (labelmap2D) {
const segmentIndexes = labelmap2D.segmentsOnLabelmap;
for (let i = 0; i < segmentIndexes.length; i++) {
if (!acc.includes(segmentIndexes[i]) && segmentIndexes[i] !== 0) {
acc.push(segmentIndexes[i]);
}
}
}
return acc;
}, [])
.sort((a, b) => a - b);
const colorLutTable =
segmentationModule.state.colorLutTables[labelmap3D.colorLUTIndex];
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
const segmentIndex = uniqueSegmentIndexes[i];
const color = colorLutTable[segmentIndex];
let segmentLabel = '(unlabeled)';
let segmentNumber = segmentIndex;
/* Meta */
if (hasLabelmapMeta) {
const segmentMeta = labelmap3D.metadata.data[segmentIndex];
if (segmentMeta) {
segmentNumber = segmentMeta.SegmentNumber;
segmentLabel = segmentMeta.SegmentLabel;
}
}
const sameSegment = selectedSegment === segmentNumber;
const setCurrentSelectedSegment = () => {
_setActiveSegment(
firstImageId,
segmentNumber,
labelmap3D.activeSegmentIndex
);
setSelectedSegment(sameSegment ? null : segmentNumber);
};
segmentList.push(
<SegmentItem
key={segmentNumber}
itemClass={`segment-item ${sameSegment && 'selected'}`}
onClick={setCurrentSelectedSegment}
label={segmentLabel}
index={segmentNumber}
color={color}
/>
);
}
/*
* Let's iterate over segmentIndexes ^ above
* If meta has a match, use it to show info
* If now, add "no-meta" class
* Show default name
*/
}
const updateBrushSize = evt => {
const updatedRadius = Number(evt.target.value);
if (updatedRadius !== brushRadius) {
setBrushRadius(updatedRadius);
segmentationModule.setters.radius(updatedRadius);
}
};
const decrementSegment = event => {
event.preventDefault();
if (labelmap3D.activeSegmentIndex > 1) {
labelmap3D.activeSegmentIndex--;
}
setSelectedSegment(labelmap3D.activeSegmentIndex);
updateActiveSegmentColor();
};
const incrementSegment = event => {
event.preventDefault();
labelmap3D.activeSegmentIndex++;
setSelectedSegment(labelmap3D.activeSegmentIndex);
updateActiveSegmentColor();
};
const updateActiveSegmentColor = () => {
const color = getActiveSegmentColor();
setBrushColor(color);
};
const getActiveSegmentColor = () => {
if (!brushStackState) {
return 'rgba(255, 255, 255, 1)';
}
const colorLutTable =
segmentationModule.state.colorLutTables[labelmap3D.colorLUTIndex];
const color = colorLutTable[labelmap3D.activeSegmentIndex];
return `rgba(${color.join(',')})`;
};
const updateConfiguration = newConfiguration => {
/* Supported configuration */
configuration.renderFill = newConfiguration.renderFill;
configuration.renderOutline = newConfiguration.renderOutline;
configuration.shouldRenderInactiveLabelmaps =
newConfiguration.shouldRenderInactiveLabelmaps;
configuration.fillAlpha = newConfiguration.fillAlpha;
configuration.outlineAlpha = newConfiguration.outlineAlpha;
configuration.outlineWidth = newConfiguration.outlineWidth;
configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive;
configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive;
refreshViewport();
};
if (showSegSettings) {
return (
<SegmentationSettings
configuration={configuration}
onBack={() => setShowSegSettings(false)}
onChange={updateConfiguration}
/>
);
} else {
return (
<div className="dcmseg-segmentation-panel">
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => setShowSegSettings(true)}
/>
{false && (
<form className="selector-form">
<BrushColorSelector
defaultColor={brushColor}
index={labelmap3D.activeSegmentIndex}
onNext={incrementSegment}
onPrev={decrementSegment}
/>
<BrushRadius
value={brushRadius}
onChange={updateBrushSize}
min={configuration.minRadius}
max={configuration.maxRadius}
/>
</form>
)}
<h3>Segmentations</h3>
<div className="segmentations">
<SegmentationSelect
value={
labelmapList.find(i => i.value === selectedSegmentation) || null
}
formatOptionLabel={SegmentationItem}
options={labelmapList}
/>
</div>
<ScrollableArea>
<TableList
customHeader={<SegmentsHeader count={segmentList.length} />}
>
{segmentList}
</TableList>
</ScrollableArea>
</div>
);
}
};
SegmentationPanel.propTypes = {
/*
* An object, with int index keys?
* Maps to: state.viewports.viewportSpecificData, in `viewer`
* Passed in MODULE_TYPES.PANEL when specifying component in viewer
*/
viewports: PropTypes.shape({
displaySetInstanceUID: PropTypes.string,
frameRate: PropTypes.any,
InstanceNumber: PropTypes.number,
isMultiFrame: PropTypes.bool,
isReconstructable: PropTypes.bool,
Modality: PropTypes.string,
plugin: PropTypes.string,
SeriesDate: PropTypes.string,
SeriesDescription: PropTypes.string,
SeriesInstanceUID: PropTypes.string,
SeriesNumber: PropTypes.any,
SeriesTime: PropTypes.string,
sopClassUIDs: PropTypes.arrayOf(PropTypes.string),
StudyInstanceUID: PropTypes.string,
}),
activeIndex: PropTypes.number.isRequired,
studies: PropTypes.array.isRequired,
};
SegmentationPanel.defaultProps = {};
/**
* Returns SEG Displaysets that reference the target series, sorted by dateTime
*
* @param {string} StudyInstanceUID
* @param {string} SeriesInstanceUID
* @returns Array
*/
const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
/* Referenced DisplaySets */
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: SeriesInstanceUID,
Modality: 'SEG',
});
/* Sort */
referencedDisplaysets.sort((a, b) => {
const aNumber = Number(`${a.SeriesDate}${a.SeriesTime}`);
const bNumber = Number(`${b.SeriesDate}${b.SeriesTime}`);
return aNumber - bNumber;
});
return referencedDisplaysets;
};
/**
*
*
* @param {*} viewportSpecificData
* @param {*} studies
* @param {*} displaySet
* @param {*} firstImageId
* @param {*} activeLabelmapIndex
* @returns
*/
const _setActiveLabelmap = async (
viewportSpecificData,
studies,
displaySet,
firstImageId,
activeLabelmapIndex
) => {
if (displaySet.labelmapIndex === activeLabelmapIndex) {
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
return;
}
if (!displaySet.isLoaded) {
// What props does this expect `viewportSpecificData` to have?
// TODO: Should this return the `labelmapIndex`?
await displaySet.load(viewportSpecificData, studies);
}
const { state } = cornerstoneTools.getModule('segmentation');
const brushStackState = state.series[firstImageId];
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
refreshViewport();
return displaySet.labelmapIndex;
};
/**
*
* @param {*} firstImageId
* @param {*} activeSegmentIndex
* @returns
*/
const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => {
if (segmentIndex === activeSegmentIndex) {
log.info(`${activeSegmentIndex} is already the active segment`);
return;
}
const { state } = cornerstoneTools.getModule('segmentation');
const brushStackState = state.series[firstImageId];
const labelmap3D =
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
labelmap3D.activeSegmentIndex = segmentIndex;
refreshViewport();
return segmentIndex;
};
const SegmentsHeader = ({ count }) => {
return (
<React.Fragment>
<div className="tableListHeaderTitle">Segments</div>
<div className="numberOfItems">{count}</div>
</React.Fragment>
);
};
export default SegmentationPanel;
@@ -0,0 +1,55 @@
import React from 'react';
import Select from 'react-select';
const SegmentationSelect = ({ value, formatOptionLabel, options }) => (
<Select
value={value}
formatOptionLabel={formatOptionLabel}
styles={segmentationSelectStyles}
options={options}
/>
);
const computedstyle = getComputedStyle(document.body);
const uiGrayDarker = computedstyle.getPropertyValue('--ui-gray-darker');
const activeColor = computedstyle.getPropertyValue('--active-color');
const defaultColor = computedstyle.getPropertyValue('--default-color');
const uiGrayDark = computedstyle.getPropertyValue('--ui-gray-dark');
const segmentationSelectStyles = {
control: (base, state) => ({
...base,
cursor: 'pointer',
background: uiGrayDarker,
borderRadius: state.isFocused ? '5px 5px 5px 5px' : 5,
borderColor: state.isFocused ? activeColor : defaultColor,
boxShadow: state.isFocused ? null : null,
minHeight: '50px',
'&:hover': {
borderColor: activeColor,
},
}),
menu: base => ({
...base,
borderRadius: 5,
background: uiGrayDarker,
}),
option: (base, state) => ({
...base,
cursor: 'pointer',
'&:first-of-type': {
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
},
'&:last-of-type': {
borderBottomLeftRadius: 5,
borderBottomRightRadius: 5,
},
background: state.isSelected ? uiGrayDark : uiGrayDarker,
'&:hover': {
background: uiGrayDark,
},
}),
};
export default SegmentationSelect;
@@ -0,0 +1,95 @@
.dcmseg-segmentation-settings {
display: flex;
flex-direction: column;
padding: 20px;
}
.dcmseg-segmentation-settings .range label {
display: block;
margin-bottom: 8px;
}
.dcmseg-segmentation-settings .settings-title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding-bottom: 20px;
}
.dcmseg-segmentation-settings .settings-title h3 {
color: var(--text-primary-color);
margin: 0;
padding: 0;
}
.dcmseg-segmentation-settings .settings-group {
display: flex;
flex-direction: column;
color: var(--text-primary-color);
font-size: 12px;
}
.dcmseg-segmentation-settings .settings-group .range {
display: flex;
padding: 20px 15px;
}
.dcmseg-segmentation-settings .settings-group .range:not(:last-of-type) {
padding-bottom: 0;
}
.dcmseg-segmentation-settings .settings-group .range label {
margin: 0;
display: unset;
min-width: 40px;
max-width: 40px;
}
.dcmseg-segmentation-settings .settings-group .range input {
width: 100%;
padding: 0 20px 0 20px;
}
.dcmseg-segmentation-settings .custom-check label {
font-weight: bold;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-bottom: 2px solid black;
padding: 10px 15px;
}
.dcmseg-segmentation-settings .custom-check input {
width: 15px;
height: 15px;
}
.dcmseg-segmentation-settings .settings-group {
background-color: var(--ui-gray-dark);
border-radius: 5px;
}
.dcmseg-segmentation-settings .return-button {
color: #333;
background-color: #fff;
padding: 4px 12px;
line-height: 1.4;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
user-select: none;
border: 1px solid #ccc;
border-radius: 4px;
transition: all 0.15s ease;
}
.dcmseg-segmentation-settings .return-button:hover,
.dcmseg-segmentation-settings .return-button:active,
.dcmseg-segmentation-settings .return-button.active {
background-color: var(--active-color);
border-color: var(--ui-border-color-active);
color: #fff;
}
@@ -0,0 +1,178 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Range } from '@ohif/ui';
import './SegmentationSettings.css';
const SegmentationSettings = ({ configuration, onBack, onChange }) => {
const [state, setState] = useState({
renderFill: configuration.renderFill,
renderOutline: configuration.renderOutline,
shouldRenderInactiveLabelmaps: configuration.shouldRenderInactiveLabelmaps,
fillAlpha: configuration.fillAlpha,
outlineAlpha: configuration.outlineAlpha,
outlineWidth: configuration.outlineWidth,
fillAlphaInactive: configuration.fillAlphaInactive,
outlineAlphaInactive: configuration.outlineAlphaInactive
});
useEffect(() => {
onChange(state);
}, [state]);
const check = field => {
setState(state => ({ ...state, [field]: !state[field] }));
};
const save = (field, value) => {
setState(state => ({ ...state, [field]: value }));
};
const toFloat = value => parseFloat(value / 100).toFixed(2);
return (
<div className="dcmseg-segmentation-settings">
<div className="settings-title">
<h3>Segmentations Settings</h3>
<button className="return-button" onClick={onBack}>
Back
</button>
</div>
<div
className="settings-group"
style={{ marginBottom: state.renderFill ? 15 : 0 }}
>
<CustomCheck
label="Segment Fill"
checked={state.renderFill}
onChange={() => check('renderFill')}
/>
{state.renderFill && (
<CustomRange
label="Opacity"
step={1}
min={0}
max={100}
value={state.fillAlpha * 100}
onChange={event => save('fillAlpha', toFloat(event.target.value))}
showPercentage
/>
)}
</div>
<div
className="settings-group"
style={{ marginBottom: state.renderOutline ? 15 : 0 }}
>
<CustomCheck
label="Segment Outline"
checked={state.renderOutline}
onChange={() => check('renderOutline')}
/>
{state.renderOutline && (
<>
<CustomRange
value={state.outlineAlpha * 100}
label="Opacity"
showPercentage
step={1}
min={0}
max={100}
onChange={event => save('outlineAlpha', toFloat(event.target.value))}
/>
<CustomRange
value={state.outlineWidth}
label="Width"
showValue
step={1}
min={0}
max={5}
onChange={event => save('outlineWidth', parseInt(event.target.value))}
/>
</>
)}
</div>
{(state.renderFill || state.renderOutline) && (
<div
className="settings-group"
style={{ marginBottom: state.shouldRenderInactiveLabelmaps ? 15 : 0 }}
>
<CustomCheck
label="Render inactive segmentations"
checked={state.shouldRenderInactiveLabelmaps}
onChange={() => check('shouldRenderInactiveLabelmaps')}
/>
{state.shouldRenderInactiveLabelmaps && (
<>
{state.renderFill && (
<CustomRange
label="Fill Opacity"
showPercentage
step={1}
min={0}
max={100}
value={state.fillAlphaInactive * 100}
onChange={event => save('fillAlphaInactive', toFloat(event.target.value))}
/>
)}
{state.renderOutline && (
<CustomRange
label="Outline Opacity"
showPercentage
step={1}
min={0}
max={100}
value={state.outlineAlphaInactive * 100}
onChange={event => save('outlineAlphaInactive', toFloat(event.target.value))}
/>
)}
</>
)}
</div>
)}
</div>
);
};
const CustomCheck = ({ label, checked, onChange }) => {
return (
<div className="custom-check">
<label>
<span>{label}</span>
<input type="checkbox" checked={checked} onChange={onChange} />
</label>
</div>
);
};
const CustomRange = props => {
const { label, onChange } = props;
return (
<div className="range">
<label htmlFor="range">{label}</label>
<Range
{...props}
onChange={event => {
event.persist();
onChange(event);
}}
/>
</div>
);
};
SegmentationSettings.propTypes = {
configuration: PropTypes.shape({
renderFill: PropTypes.bool.isRequired,
renderOutline: PropTypes.bool.isRequired,
shouldRenderInactiveLabelmaps: PropTypes.bool.isRequired,
fillAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, /* TODO: why fillAlpha is string? */
outlineAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, /* TODO: why fillAlpha is string? */
outlineWidth: PropTypes.number.isRequired,
fillAlphaInactive: PropTypes.number.isRequired,
outlineAlphaInactive: PropTypes.number.isRequired,
}).isRequired,
onBack: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
};
export default SegmentationSettings;
@@ -0,0 +1,7 @@
export { default as BrushColorSelector } from './BrushColorSelector/BrushColorSelector';
export { default as BrushRadius } from './BrushRadius/BrushRadius';
export { default as SegmentationItem } from './SegmentationItem/SegmentationItem';
export { default as SegmentationSettings } from './SegmentationSettings/SegmentationSettings';
export { default as SegmentationPanel } from './SegmentationPanel/SegmentationPanel';
export { default as SegmentItem } from './SegmentItem/SegmentItem';
export { default as SegmentationSelect } from './SegmentationSelect';
@@ -0,0 +1,30 @@
import init from './init.js';
import toolbarModule from './toolbarModule.js';
import panelModule from './panelModule.js';
import sopClassHandlerModule from './OHIFDicomSegSopClassHandler.js';
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'com.ohif.dicom-segmentation',
/**
*
*
* @param {object} [configuration={}]
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
*/
preRegistration({ servicesManager, configuration = {} }) {
init({ servicesManager, configuration });
},
getToolbarModule({ servicesManager }) {
return toolbarModule;
},
getPanelModule({ servicesManager }) {
return panelModule;
},
getSopClassHandlerModule({ servicesManager }) {
return sopClassHandlerModule;
},
};
+20
View File
@@ -0,0 +1,20 @@
import csTools from 'cornerstone-tools';
/**
*
* @param {object} configuration
* @param {Object|Array} configuration.csToolsConfig
*/
export default function init({ servicesManager, configuration = {} }) {
const { BrushTool, SphericalBrushTool, CorrectionScissorsTool } = csTools;
const tools = [BrushTool, SphericalBrushTool, CorrectionScissorsTool];
tools.forEach(tool => csTools.addTool(tool));
csTools.addTool(BrushTool, {
name: 'BrushEraser',
configuration: {
alwaysEraseOnClick: true,
},
});
}
@@ -0,0 +1,115 @@
import OHIF from '@ohif/core';
import * as dcmjs from 'dcmjs';
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
const { DicomLoaderService } = OHIF.utils;
export default async function loadSegmentation(
segDisplaySet,
referencedDisplaySet,
studies
) {
const { StudyInstanceUID } = referencedDisplaySet;
// Set here is loading is asynchronous.
// If this function throws its set back to false.
segDisplaySet.isLoaded = true;
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise(
segDisplaySet,
studies
);
const dicomData = dcmjs.data.DicomMessage.readFile(segArrayBuffer);
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
);
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
const imageIds = _getImageIdsForDisplaySet(
studies,
StudyInstanceUID,
referencedDisplaySet.SeriesInstanceUID
);
const results = _parseSeg(segArrayBuffer, imageIds);
if (!results) {
throw new Error('Fractional segmentations are not yet supported');
}
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
const { setters } = cornerstoneTools.getModule('segmentation');
// TODO: Could define a color LUT based on colors in the SEG.
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
setters.labelmap3DByFirstImageId(
imageIds[0],
labelmapBuffer,
labelmapIndex,
segMetadata,
imageIds.length,
segmentsOnFrame
);
segDisplaySet.labelmapIndex = labelmapIndex;
}
function _getNextLabelmapIndex(firstImageId) {
const { state } = cornerstoneTools.getModule('segmentation');
const brushStackState = state.series[firstImageId];
let labelmapIndex = 0;
if (brushStackState) {
const { labelmaps3D } = brushStackState;
labelmapIndex = labelmaps3D.length;
for (let i = 0; i < labelmaps3D.length; i++) {
if (!labelmaps3D[i]) {
labelmapIndex = i;
break;
}
}
}
return labelmapIndex;
}
function _parseSeg(arrayBuffer, imageIds) {
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
imageIds,
arrayBuffer,
cornerstone.metaData
);
}
function _getImageIdsForDisplaySet(
studies,
StudyInstanceUID,
SeriesInstanceUID
) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const displaySets = study.displaySets.filter(displaySet => {
return displaySet.SeriesInstanceUID === SeriesInstanceUID;
});
if (displaySets.length > 1) {
console.warn(
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
);
// TODO -> We could make check the instance list and see if any match?
// Do we split the segmentation into two cornerstoneTools segmentations if there are images in both series?
// ^ Will that even happen?
}
const referencedDisplaySet = displaySets[0];
return referencedDisplaySet.images.map(image => image.getImageId());
}
@@ -0,0 +1,39 @@
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
export default {
menuOptions: [
{
icon: 'list',
label: 'Segmentations',
target: 'segmentation-panel',
isDisabled: studies => {
if (!studies) {
return true;
}
for (let i = 0; i < studies.length; i++) {
const study = studies[i];
if (study && study.series) {
for (let j = 0; j < study.series.length; j++) {
const series = study.series[j];
if (series.Modality === 'SEG') {
return false;
}
}
}
}
return true;
},
},
],
components: [
{
id: 'segmentation-panel',
component: SegmentationPanel,
},
],
defaultContext: ['VIEWER'],
};
@@ -0,0 +1,60 @@
/* TODO: Creation tools (future release).
const TOOLBAR_BUTTON_TYPES = {
COMMAND: 'command',
SET_TOOL_ACTIVE: 'setToolActive',
BUILT_IN: 'builtIn',
};
*/
const definitions = []; /* TODO: Creation tools (future release). [
{
id: 'SegDropdown',
label: 'Segmentation',
icon: 'ellipse-circle',
buttons: [
{
id: 'Brush',
label: 'Brush',
icon: 'brush',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Brush' },
},
{
id: 'SphericalBrush',
label: 'Spherical',
icon: 'sphere',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'SphericalBrush' },
},
{
id: 'CorrectionScissors',
label: 'Correction Scissors',
icon: 'scissors',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'CorrectionScissors' },
},
{
id: 'BrushEraser',
label: 'Eraser',
icon: 'trash',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'BrushEraser' },
},
],
},
]; */
export default {
definitions,
defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE',
};
+114 -319
View File
@@ -3,6 +3,54 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.3...@ohif/extension-vtk@1.3.4) (2020-03-24)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.3.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.2...@ohif/extension-vtk@1.3.3) (2020-03-24)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.3.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.1...@ohif/extension-vtk@1.3.2) (2020-03-23)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.0...@ohif/extension-vtk@1.3.1) (2020-03-17)
**Note:** Version bump only for package @ohif/extension-vtk
# [1.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.24...@ohif/extension-vtk@1.3.0) (2020-03-13)
### Features
* Segmentations Settings UI - Phase 1 [#1391](https://github.com/OHIF/Viewers/issues/1391) ([#1392](https://github.com/OHIF/Viewers/issues/1392)) ([e8842cf](https://github.com/OHIF/Viewers/commit/e8842cf8aebde98db7fc123e4867c8288552331f)), closes [#1423](https://github.com/OHIF/Viewers/issues/1423)
# Change Log
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.24](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.23...@ohif/extension-vtk@1.2.24) (2020-03-09)
**Note:** Version bump only for package @ohif/extension-vtk
@@ -428,621 +476,368 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
**Note:** Version bump only for package @ohif/extension-vtk
## [0.53.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.4...@ohif/extension-vtk@0.53.5) (2019-11-13)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.53.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.3...@ohif/extension-vtk@0.53.4) (2019-11-12)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.53.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.2...@ohif/extension-vtk@0.53.3) (2019-11-11)
### Bug Fixes
* mpr2d vtkjs viewport does not render if range is set to `NaN` values ([#1157](https://github.com/OHIF/Viewers/issues/1157)) ([84a0972](https://github.com/OHIF/Viewers/commit/84a097212babc0f98198b9ced1def9743973a5a8))
- mpr2d vtkjs viewport does not render if range is set to `NaN` values
([#1157](https://github.com/OHIF/Viewers/issues/1157))
([84a0972](https://github.com/OHIF/Viewers/commit/84a097212babc0f98198b9ced1def9743973a5a8))
## [0.53.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.1...@ohif/extension-vtk@0.53.2) (2019-11-08)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.53.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.0...@ohif/extension-vtk@0.53.1) (2019-11-08)
**Note:** Version bump only for package @ohif/extension-vtk
# [0.53.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.32...@ohif/extension-vtk@0.53.0) (2019-11-06)
### Features
* modal provider ([#1151](https://github.com/OHIF/Viewers/issues/1151)) ([75d88bc](https://github.com/OHIF/Viewers/commit/75d88bc454710d2dcdbc7d68c4d9df041159c840)), closes [#1086](https://github.com/OHIF/Viewers/issues/1086) [#1116](https://github.com/OHIF/Viewers/issues/1116) [#1116](https://github.com/OHIF/Viewers/issues/1116) [#1146](https://github.com/OHIF/Viewers/issues/1146) [#1142](https://github.com/OHIF/Viewers/issues/1142) [#1143](https://github.com/OHIF/Viewers/issues/1143) [#1110](https://github.com/OHIF/Viewers/issues/1110) [#1086](https://github.com/OHIF/Viewers/issues/1086) [#1116](https://github.com/OHIF/Viewers/issues/1116) [#1119](https://github.com/OHIF/Viewers/issues/1119)
- modal provider ([#1151](https://github.com/OHIF/Viewers/issues/1151))
([75d88bc](https://github.com/OHIF/Viewers/commit/75d88bc454710d2dcdbc7d68c4d9df041159c840)),
closes [#1086](https://github.com/OHIF/Viewers/issues/1086)
[#1116](https://github.com/OHIF/Viewers/issues/1116)
[#1116](https://github.com/OHIF/Viewers/issues/1116)
[#1146](https://github.com/OHIF/Viewers/issues/1146)
[#1142](https://github.com/OHIF/Viewers/issues/1142)
[#1143](https://github.com/OHIF/Viewers/issues/1143)
[#1110](https://github.com/OHIF/Viewers/issues/1110)
[#1086](https://github.com/OHIF/Viewers/issues/1086)
[#1116](https://github.com/OHIF/Viewers/issues/1116)
[#1119](https://github.com/OHIF/Viewers/issues/1119)
## [0.52.32](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.31...@ohif/extension-vtk@0.52.32) (2019-11-05)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.31](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.30...@ohif/extension-vtk@0.52.31) (2019-11-05)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.30](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.29...@ohif/extension-vtk@0.52.30) (2019-11-04)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.29](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.28...@ohif/extension-vtk@0.52.29) (2019-11-04)
### Bug Fixes
* 🐛 Upgrade react-vtkjs-viewport to fix volume orientation ([#1143](https://github.com/OHIF/Viewers/issues/1143)) ([1d86b1a](https://github.com/OHIF/Viewers/commit/1d86b1a102fc422053cff103de8c12ca6f437354))
- 🐛 Upgrade react-vtkjs-viewport to fix volume orientation
([#1143](https://github.com/OHIF/Viewers/issues/1143))
([1d86b1a](https://github.com/OHIF/Viewers/commit/1d86b1a102fc422053cff103de8c12ca6f437354))
## [0.52.28](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.27...@ohif/extension-vtk@0.52.28) (2019-11-04)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.27](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.26...@ohif/extension-vtk@0.52.27) (2019-10-31)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.26](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.25...@ohif/extension-vtk@0.52.26) (2019-10-30)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.25](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.24...@ohif/extension-vtk@0.52.25) (2019-10-29)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.24](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.23...@ohif/extension-vtk@0.52.24) (2019-10-29)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.23](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.22...@ohif/extension-vtk@0.52.23) (2019-10-28)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.22](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.21...@ohif/extension-vtk@0.52.22) (2019-10-28)
### Bug Fixes
* MIP styling ([#1109](https://github.com/OHIF/Viewers/issues/1109)) ([0d21cc6](https://github.com/OHIF/Viewers/commit/0d21cc6ad0c47706b9e62e05fe2a0f1d86339760))
- MIP styling ([#1109](https://github.com/OHIF/Viewers/issues/1109))
([0d21cc6](https://github.com/OHIF/Viewers/commit/0d21cc6ad0c47706b9e62e05fe2a0f1d86339760))
## [0.52.21](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.20...@ohif/extension-vtk@0.52.21) (2019-10-26)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.20](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.19...@ohif/extension-vtk@0.52.20) (2019-10-26)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.19](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.18...@ohif/extension-vtk@0.52.19) (2019-10-25)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.18](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.17...@ohif/extension-vtk@0.52.18) (2019-10-25)
### Bug Fixes
* 🐛 Orthographic MPR fix ([#1092](https://github.com/OHIF/Viewers/issues/1092)) ([460e375](https://github.com/OHIF/Viewers/commit/460e375f0aa75d35f7a46b4d48e6cc706019956d))
- 🐛 Orthographic MPR fix ([#1092](https://github.com/OHIF/Viewers/issues/1092))
([460e375](https://github.com/OHIF/Viewers/commit/460e375f0aa75d35f7a46b4d48e6cc706019956d))
## [0.52.17](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.16...@ohif/extension-vtk@0.52.17) (2019-10-25)
### Bug Fixes
* 🐛 Fix crosshairs location when using MIP ([#1080](https://github.com/OHIF/Viewers/issues/1080)) ([b451ce4](https://github.com/OHIF/Viewers/commit/b451ce440796f13b0891739d2130e8ee71993d3d))
- 🐛 Fix crosshairs location when using MIP
([#1080](https://github.com/OHIF/Viewers/issues/1080))
([b451ce4](https://github.com/OHIF/Viewers/commit/b451ce440796f13b0891739d2130e8ee71993d3d))
## [0.52.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.15...@ohif/extension-vtk@0.52.16) (2019-10-23)
### Bug Fixes
* 🐛 Switch to orhtographic view for 2D MPR ([#1074](https://github.com/OHIF/Viewers/issues/1074)) ([13d337a](https://github.com/OHIF/Viewers/commit/13d337aaabb8dadf6366c6262c5e47e7781edd08))
- 🐛 Switch to orhtographic view for 2D MPR
([#1074](https://github.com/OHIF/Viewers/issues/1074))
([13d337a](https://github.com/OHIF/Viewers/commit/13d337aaabb8dadf6366c6262c5e47e7781edd08))
## [0.52.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.14...@ohif/extension-vtk@0.52.15) (2019-10-23)
### Performance Improvements
* ⚡️ Throttle UI refresh of synced WL value in vtk port ([#1070](https://github.com/OHIF/Viewers/issues/1070)) ([9d08e81](https://github.com/OHIF/Viewers/commit/9d08e81aa5def63a5e34f464aae099f7c29f0ac5))
- ⚡️ Throttle UI refresh of synced WL value in vtk port
([#1070](https://github.com/OHIF/Viewers/issues/1070))
([9d08e81](https://github.com/OHIF/Viewers/commit/9d08e81aa5def63a5e34f464aae099f7c29f0ac5))
## [0.52.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.13...@ohif/extension-vtk@0.52.14) (2019-10-22)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.12...@ohif/extension-vtk@0.52.13) (2019-10-18)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.11...@ohif/extension-vtk@0.52.12) (2019-10-18)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.10...@ohif/extension-vtk@0.52.11) (2019-10-15)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.9...@ohif/extension-vtk@0.52.10) (2019-10-15)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.8...@ohif/extension-vtk@0.52.9) (2019-10-14)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.7...@ohif/extension-vtk@0.52.8) (2019-10-14)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.6...@ohif/extension-vtk@0.52.7) (2019-10-11)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.5...@ohif/extension-vtk@0.52.6) (2019-10-11)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.4...@ohif/extension-vtk@0.52.5) (2019-10-10)
### Performance Improvements
* ⚡️ Update react-vtkjs-viewport for performance improvement ([6ee5094](https://github.com/OHIF/Viewers/commit/6ee5094))
- ⚡️ Update react-vtkjs-viewport for performance improvement
([6ee5094](https://github.com/OHIF/Viewers/commit/6ee5094))
## [0.52.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.3...@ohif/extension-vtk@0.52.4) (2019-10-10)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.2...@ohif/extension-vtk@0.52.3) (2019-10-09)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.52.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.1...@ohif/extension-vtk@0.52.2) (2019-10-09)
### Performance Improvements
* 🎸 Update vtk viewport for faster image reconstruction ([#1016](https://github.com/OHIF/Viewers/issues/1016)) ([8513ad9](https://github.com/OHIF/Viewers/commit/8513ad9))
- 🎸 Update vtk viewport for faster image reconstruction
([#1016](https://github.com/OHIF/Viewers/issues/1016))
([8513ad9](https://github.com/OHIF/Viewers/commit/8513ad9))
## [0.52.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.52.0...@ohif/extension-vtk@0.52.1) (2019-10-04)
**Note:** Version bump only for package @ohif/extension-vtk
# [0.52.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.51.3...@ohif/extension-vtk@0.52.0) (2019-10-03)
### Features
* 🎸 Synced Window Leveling ([559b637](https://github.com/OHIF/Viewers/commit/559b637)), closes [#558](https://github.com/OHIF/Viewers/issues/558)
- 🎸 Synced Window Leveling
([559b637](https://github.com/OHIF/Viewers/commit/559b637)), closes
[#558](https://github.com/OHIF/Viewers/issues/558)
## [0.51.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.51.2...@ohif/extension-vtk@0.51.3) (2019-10-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.51.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.51.1...@ohif/extension-vtk@0.51.2) (2019-10-02)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.51.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.51.0...@ohif/extension-vtk@0.51.1) (2019-10-01)
### Bug Fixes
* **rotate:** Use new MPR rotate in react-vtkjs-viewport 0.1.2 ([#964](https://github.com/OHIF/Viewers/issues/964)) ([8503cbc](https://github.com/OHIF/Viewers/commit/8503cbc))
- **rotate:** Use new MPR rotate in react-vtkjs-viewport 0.1.2
([#964](https://github.com/OHIF/Viewers/issues/964))
([8503cbc](https://github.com/OHIF/Viewers/commit/8503cbc))
# [0.51.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.23...@ohif/extension-vtk@0.51.0) (2019-10-01)
### Features
* 🎸 MPR UI improvements. Added MinIP, AvgIP, slab thickness slider and mode toggle ([#947](https://github.com/OHIF/Viewers/issues/947)) ([c79c0c3](https://github.com/OHIF/Viewers/commit/c79c0c3))
- 🎸 MPR UI improvements. Added MinIP, AvgIP, slab thickness slider and mode
toggle ([#947](https://github.com/OHIF/Viewers/issues/947))
([c79c0c3](https://github.com/OHIF/Viewers/commit/c79c0c3))
## [0.50.23](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.22...@ohif/extension-vtk@0.50.23) (2019-10-01)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.22](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.21...@ohif/extension-vtk@0.50.22) (2019-09-27)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.21](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.20...@ohif/extension-vtk@0.50.21) (2019-09-27)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.20](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.19...@ohif/extension-vtk@0.50.20) (2019-09-26)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.19](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.18...@ohif/extension-vtk@0.50.19) (2019-09-26)
### Bug Fixes
* Add some code splitting for PWA build ([#937](https://github.com/OHIF/Viewers/issues/937)) ([8938035](https://github.com/OHIF/Viewers/commit/8938035))
- Add some code splitting for PWA build
([#937](https://github.com/OHIF/Viewers/issues/937))
([8938035](https://github.com/OHIF/Viewers/commit/8938035))
## [0.50.18](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.17...@ohif/extension-vtk@0.50.18) (2019-09-23)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.17](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.16...@ohif/extension-vtk@0.50.17) (2019-09-19)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.15...@ohif/extension-vtk@0.50.16) (2019-09-19)
### Bug Fixes
* Set transfer function range to data range when loading volumes in VTK ([#930](https://github.com/OHIF/Viewers/issues/930)) ([2341b32](https://github.com/OHIF/Viewers/commit/2341b32))
- Set transfer function range to data range when loading volumes in VTK
([#930](https://github.com/OHIF/Viewers/issues/930))
([2341b32](https://github.com/OHIF/Viewers/commit/2341b32))
## [0.50.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.14...@ohif/extension-vtk@0.50.15) (2019-09-19)
### Bug Fixes
* Bump VTK viewport dependency ([#929](https://github.com/OHIF/Viewers/issues/929)) ([66a0aad](https://github.com/OHIF/Viewers/commit/66a0aad))
* Stop adding empty labelmap to VTK Viewports for now. Wait until paint tools are implemented ([#926](https://github.com/OHIF/Viewers/issues/926)) ([7f642a0](https://github.com/OHIF/Viewers/commit/7f642a0))
- Bump VTK viewport dependency
([#929](https://github.com/OHIF/Viewers/issues/929))
([66a0aad](https://github.com/OHIF/Viewers/commit/66a0aad))
- Stop adding empty labelmap to VTK Viewports for now. Wait until paint tools
are implemented ([#926](https://github.com/OHIF/Viewers/issues/926))
([7f642a0](https://github.com/OHIF/Viewers/commit/7f642a0))
## [0.50.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.13...@ohif/extension-vtk@0.50.14) (2019-09-17)
### Bug Fixes
* bump cornerstone-tools version in peerDeps ([4afc88c](https://github.com/OHIF/Viewers/commit/4afc88c))
- bump cornerstone-tools version in peerDeps
([4afc88c](https://github.com/OHIF/Viewers/commit/4afc88c))
## [0.50.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.12...@ohif/extension-vtk@0.50.13) (2019-09-12)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.11...@ohif/extension-vtk@0.50.12) (2019-09-12)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.10...@ohif/extension-vtk@0.50.11) (2019-09-10)
### Bug Fixes
* simplify runtime-extension usage ([ac5dbda](https://github.com/OHIF/Viewers/commit/ac5dbda))
- simplify runtime-extension usage
([ac5dbda](https://github.com/OHIF/Viewers/commit/ac5dbda))
## [0.50.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.9...@ohif/extension-vtk@0.50.10) (2019-09-10)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.8...@ohif/extension-vtk@0.50.9) (2019-09-10)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.7...@ohif/extension-vtk@0.50.8) (2019-09-09)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.6...@ohif/extension-vtk@0.50.7) (2019-09-06)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.5...@ohif/extension-vtk@0.50.6) (2019-09-04)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.4...@ohif/extension-vtk@0.50.5) (2019-09-04)
### Bug Fixes
* measurementsAPI issue caused by production build ([#842](https://github.com/OHIF/Viewers/issues/842)) ([49d3439](https://github.com/OHIF/Viewers/commit/49d3439))
- measurementsAPI issue caused by production build
([#842](https://github.com/OHIF/Viewers/issues/842))
([49d3439](https://github.com/OHIF/Viewers/commit/49d3439))
## [0.50.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.3...@ohif/extension-vtk@0.50.4) (2019-08-26)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.2...@ohif/extension-vtk@0.50.3) (2019-08-22)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.1...@ohif/extension-vtk@0.50.2) (2019-08-15)
### Bug Fixes
* Update vtk and viewport dependencies ([98c40bc](https://github.com/OHIF/Viewers/commit/98c40bc))
- Update vtk and viewport dependencies
([98c40bc](https://github.com/OHIF/Viewers/commit/98c40bc))
## [0.50.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.0-alpha.10...@ohif/extension-vtk@0.50.1) (2019-08-14)
**Note:** Version bump only for package @ohif/extension-vtk
# [0.50.0-alpha.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.1.4-alpha.9...@ohif/extension-vtk@0.50.0-alpha.10) (2019-08-14)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.1.4-alpha.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.1.4-alpha.8...@ohif/extension-vtk@0.1.4-alpha.9) (2019-08-14)
**Note:** Version bump only for package @ohif/extension-vtk
## 0.1.4-alpha.8 (2019-08-14)
**Note:** Version bump only for package @ohif/extension-vtk
# Change Log
All notable changes to this project will be documented in this file. See
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "1.2.24",
"version": "1.3.4",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@@ -33,6 +33,7 @@
"@ohif/i18n": "^0.50.0",
"@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.12.5",
"cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.10.1",
"dicom-parser": "^1.8.3",
@@ -52,9 +53,9 @@
"react-vtkjs-viewport": "^0.8.3"
},
"devDependencies": {
"@ohif/core": "^2.5.3",
"@ohif/ui": "^1.3.3",
"cornerstone-tools": "4.12.0",
"@ohif/core": "^2.6.3",
"@ohif/ui": "^1.4.1",
"cornerstone-tools": "4.12.5",
"cornerstone-wado-image-loader": "^3.0.0",
"dicom-parser": "^1.8.3",
"gh-pages": "^2.0.1",
+100 -50
View File
@@ -5,33 +5,30 @@ import LoadingIndicator from './LoadingIndicator.js';
import OHIF from '@ohif/core';
import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core';
import handleSegmentationStorage from './handleSegmentationStorage.js';
import cornerstoneTools from 'cornerstone-tools';
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume';
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
const segmentationModule = cornerstoneTools.getModule('segmentation');
const { StackManager } = OHIF.utils;
const style = { width: '100%', height: '100%', position: 'relative' };
const SOP_CLASSES = {
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
};
const specialCaseHandlers = {};
specialCaseHandlers[
SOP_CLASSES.SEGMENTATION_STORAGE
] = handleSegmentationStorage;
// TODO: Figure out where we plan to put this long term
const volumeCache = {};
const labelmapCache = {};
/**
* Create a labelmap image with the same dimensions as our background volume.
*
* @param backgroundImageData vtkImageData
*/
/* TODO: Not currently used until we have drawing tools in vtkjs.
function createLabelMapImageData(backgroundImageData) {
// TODO => Need to do something like this if we start drawing a new segmentation
// On a vtkjs viewport.
const labelMapData = vtkImageData.newInstance(
backgroundImageData.get('spacing', 'origin', 'direction')
);
@@ -46,7 +43,7 @@ function createLabelMapImageData(backgroundImageData) {
labelMapData.getPointData().setScalars(dataArray);
return labelMapData;
}
} */
class OHIFVTKViewport extends Component {
state = {
@@ -147,33 +144,58 @@ class OHIFVTKViewport extends Component {
frameIndex
);
let imageDataObject;
const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
let labelmapDataObject;
let labelmapColorLUT;
switch (SOPClassUID) {
case SOP_CLASSES.SEGMENTATION_STORAGE:
throw new Error('Not yet implemented');
/*
const data = handleSegmentationStorage(
stack.imageIds,
displaySetInstanceUID
const firstImageId = stack.imageIds[0];
const { state } = segmentationModule;
const brushStackState = state.series[firstImageId];
if (brushStackState) {
const { activeLabelmapIndex } = brushStackState;
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
if (labelmapCache[vtkLabelmapID]) {
labelmapDataObject = labelmapCache[vtkLabelmapID];
} else {
// TODO -> We need an imageId based getter in cornerstoneTools
const labelmapBuffer = labelmap3D.buffer;
// Create VTK Image Data with buffer as input
labelmapDataObject = vtkImageData.newInstance();
const dataArray = vtkDataArray.newInstance({
numberOfComponents: 1, // labelmap with single component
values: new Uint16Array(labelmapBuffer),
});
labelmapDataObject.getPointData().setScalars(dataArray);
labelmapDataObject.setDimensions(...imageDataObject.dimensions);
labelmapDataObject.setSpacing(
...imageDataObject.vtkImageData.getSpacing()
);
labelmapDataObject.setOrigin(
...imageDataObject.vtkImageData.getOrigin()
);
labelmapDataObject.setDirection(
...imageDataObject.vtkImageData.getDirection()
);
imageDataObject = data.referenceDataObject;
labelmapDataObject = data.labelmapDataObject;
// Cache the labelmap volume.
labelmapCache[vtkLabelmapID] = labelmapDataObject;
}
return loadImageData(imageDataObject).then(() => {
return {
data: imageDataObject.vtkImageData,
labelmap: labelmapDataObject,
};
});
*/
default:
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
return imageDataObject;
labelmapColorLUT = state.colorLutTables[labelmap3D.colorLUTIndex];
}
return {
imageDataObject,
labelmapDataObject,
labelmapColorLUT,
};
};
/**
@@ -251,40 +273,48 @@ class OHIFVTKViewport extends Component {
);
}
const SOPClassUID = sopClassUIDs[0];
const imageDataObject = this.getViewportData(
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const dataDetails = {
studyDate: study.studyDate,
studyTime: study.studyTime,
studyDescription: study.studyDescription,
patientName: study.patientName,
patientId: study.patientId,
seriesNumber: String(displaySet.seriesNumber),
seriesDescription: displaySet.seriesDescription,
};
const {
imageDataObject,
labelmapDataObject,
labelmapColorLUT,
} = this.getViewportData(
studies,
StudyInstanceUID,
displaySetInstanceUID,
SOPClassUID,
SOPInstanceUID,
frameIndex
);
this.imageDataObject = imageDataObject;
// TODO: Temporarily disabling this since it is not yet
// being used and hurts performance significantly.
/*if (!labelmap) {
/* TODO: Not currently used until we have drawing tools in vtkjs.
if (!labelmap) {
labelmap = createLabelMapImageData(data);
}*/
} */
const volumeActor = this.getOrCreateVolume(
imageDataObject,
displaySetInstanceUID
);
this.setState({
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
paintFilterLabelMapImageData: null, // TODO
percentComplete: 0,
});
this.setState(
{
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
paintFilterLabelMapImageData: null, // TODO
percentComplete: 0,
dataDetails,
},
() => {
this.loadProgressively(imageDataObject);
@@ -296,6 +326,9 @@ class OHIFVTKViewport extends Component {
setTimeout(() => {
this.setState({
volumes: [volumeActor],
paintFilterLabelMapImageData: labelmapDataObject,
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
labelmapColorLUT,
});
}, 200);
}
@@ -307,7 +340,7 @@ class OHIFVTKViewport extends Component {
}
componentDidUpdate(prevProps) {
const { studies, displaySet } = this.props.viewportData;
const { displaySet } = this.props.viewportData;
const prevDisplaySet = prevProps.viewportData.displaySet;
if (
@@ -347,12 +380,15 @@ class OHIFVTKViewport extends Component {
});
Promise.all(insertPixelDataPromises).then(() => {
this.setState({ isLoaded: true });
this.setState({
isLoaded: true,
});
});
}
render() {
let childrenWithProps = null;
const { configuration } = segmentationModule;
// TODO: Does it make more sense to use Context?
if (this.props.children && this.props.children.length) {
@@ -367,6 +403,12 @@ class OHIFVTKViewport extends Component {
});
}
const style = { width: '100%', height: '100%', position: 'relative' };
const visible = configuration.renderFill || configuration.renderOutline;
const opacity = configuration.fillAlpha;
const outlineThickness = configuration.outlineThickness;
return (
<>
<div style={style}>
@@ -383,6 +425,14 @@ class OHIFVTKViewport extends Component {
this.state.paintFilterBackgroundImageData
}
viewportIndex={this.props.viewportIndex}
dataDetails={this.state.dataDetails}
labelmapRenderingOptions={{
colorLUT: this.state.labelmapColorLUT,
globalOpacity: opacity,
visible,
outlineThickness,
renderOutline: true,
}}
onScroll={this.props.onScroll}
/>
)}
@@ -1,146 +0,0 @@
import * as dcmjs from 'dcmjs';
import OHIF from '@ohif/core';
import { api } from 'dicomweb-client';
const { StackManager } = OHIF.utils;
function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const displaySet = study.displaySets.find(set => {
return set.displaySetInstanceUID === displaySetInstanceUID;
});
return displaySet;
}
function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
return study.displaySets.filter(set => {
return set.SeriesInstanceUID === SeriesInstanceUID;
});
}
function getCornerstoneStack(studies, StudyInstanceUID, displaySetInstanceUID) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
// Create shortcut to displaySet
const displaySet = getDisplaySet(
studies,
StudyInstanceUID,
displaySetInstanceUID
);
// Get stack from Stack Manager
const stack = StackManager.findOrCreateStack(study, displaySet);
// Clone the stack here so we don't mutate it later
const stackClone = Object.assign({}, stack);
stackClone.currentImageIdIndex = 0;
return stackClone;
}
function retrieveDicomData(
studyInstanceUID,
seriesInstanceUID,
sopInstanceUid,
wadoRoot
) {
const config = {
url: wadoRoot,
headers: DICOMWeb.getAuthorizationHeader(),
};
const dicomWeb = new api.DICOMwebClient(config);
const options = {
studyInstanceUID,
seriesInstanceUID,
sopInstanceUid,
};
return dicomWeb.retrieveInstance(options);
}
async function handleSegmentationStorage(
studies,
StudyInstanceUID,
displaySetInstanceUID
) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const displaySet = getDisplaySet(
studies,
StudyInstanceUID,
displaySetInstanceUID
);
// TODO: This is terrible but we need to use WADO-RS or we can't retrieve the SEG
// from google cloud
const wadoRoot = displaySet.images[0].getData().wadoRoot;
const arrayBuffer = await retrieveDicomData(
displaySet.images[0].getStudyInstanceUID(),
displaySet.images[0].getSeriesInstanceUID(),
displaySet.images[0].getSOPInstanceUID(),
wadoRoot
);
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
);
const segments = dcmjs.adapters.VTKjs.Segmentation.generateSegments(dataset);
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
const displaySets = getDisplaySetsBySeries(
studies,
StudyInstanceUID,
SeriesInstanceUID
);
if (displaySets.length > 1) {
console.warn(
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
);
}
const referenceDisplaySet = displaySets[0];
const imageIds = referenceDisplaySet.images.map(image => image.getImageId());
if (!results) {
throw new Error('Fractional segmentations are not supported');
}
const cachedStack = StackManager.findOrCreateStack(
study,
referenceDisplaySet
);
const stack = Object.assign({}, cachedStack);
stack.currentImageIdIndex = 0;
return {
referenceDataObject,
labelmapDataObject,
};
return {
StudyInstanceUID,
displaySetInstanceUID,
stack,
};
}
export default handleSegmentationStorage;
+127 -178
View File
@@ -3,6 +3,55 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.6.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.6.2...@ohif/core@2.6.3) (2020-03-24)
### Bug Fixes
* Ensure we take into account pixel spacing fields properly ([#1555](https://github.com/OHIF/Viewers/issues/1555)) ([77ab0ad](https://github.com/OHIF/Viewers/commit/77ab0ad9a14a135b5560741fc1600704df08c141))
## [2.6.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.6.1...@ohif/core@2.6.2) (2020-03-24)
### Bug Fixes
* OverlayPlane module usage for ArrayBuffer, BulkDataURI, and InlineBinary cases, as well as PaletteColor LUTs for ArrayBuffer (i.e. local drag/drop) case ([#1546](https://github.com/OHIF/Viewers/issues/1546)) ([404d52f](https://github.com/OHIF/Viewers/commit/404d52fe5c0442dd13e4d407bb0687d72fa5f32c))
## [2.6.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.6.0...@ohif/core@2.6.1) (2020-03-23)
### Bug Fixes
* avoid-wasteful-renders ([#1544](https://github.com/OHIF/Viewers/issues/1544)) ([e41d339](https://github.com/OHIF/Viewers/commit/e41d339f5faef6b93700bc860f37f29f32ad5ed6))
# [2.6.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.5.3...@ohif/core@2.6.0) (2020-03-13)
### Features
* Segmentations Settings UI - Phase 1 [#1391](https://github.com/OHIF/Viewers/issues/1391) ([#1392](https://github.com/OHIF/Viewers/issues/1392)) ([e8842cf](https://github.com/OHIF/Viewers/commit/e8842cf8aebde98db7fc123e4867c8288552331f)), closes [#1423](https://github.com/OHIF/Viewers/issues/1423)
# Change Log
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.5.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.5.2...@ohif/core@2.5.3) (2020-03-09)
**Note:** Version bump only for package @ohif/core
@@ -304,339 +353,239 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
# [1.9.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.8.0...@ohif/core@1.9.0) (2019-11-13)
### Features
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
- expose UiNotifications service
([#1172](https://github.com/OHIF/Viewers/issues/1172))
([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.7.1...@ohif/core@1.8.0) (2019-11-12)
### Features
* 🎸 Update hotkeys and user preferences modal ([#1135](https://github.com/OHIF/Viewers/issues/1135)) ([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)), closes [#923](https://github.com/OHIF/Viewers/issues/923)
- 🎸 Update hotkeys and user preferences modal
([#1135](https://github.com/OHIF/Viewers/issues/1135))
([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)),
closes [#923](https://github.com/OHIF/Viewers/issues/923)
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.7.0...@ohif/core@1.7.1) (2019-11-08)
### Bug Fixes
* Add a fallback metadata provider which pulls metadata from WADO-… ([#1158](https://github.com/OHIF/Viewers/issues/1158)) ([31b1adf](https://github.com/OHIF/Viewers/commit/31b1adfa5993d6c8e3e9c8b03fa9856f2621b037))
- Add a fallback metadata provider which pulls metadata from WADO-…
([#1158](https://github.com/OHIF/Viewers/issues/1158))
([31b1adf](https://github.com/OHIF/Viewers/commit/31b1adfa5993d6c8e3e9c8b03fa9856f2621b037))
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.6.2...@ohif/core@1.7.0) (2019-11-05)
### Features
* 🎸 Filter by url query param for seriesInstnaceUID ([#1117](https://github.com/OHIF/Viewers/issues/1117)) ([e208f2e](https://github.com/OHIF/Viewers/commit/e208f2e6a9c49b16dadead0a917f657cf023929a)), closes [#1118](https://github.com/OHIF/Viewers/issues/1118)
- 🎸 Filter by url query param for seriesInstnaceUID
([#1117](https://github.com/OHIF/Viewers/issues/1117))
([e208f2e](https://github.com/OHIF/Viewers/commit/e208f2e6a9c49b16dadead0a917f657cf023929a)),
closes [#1118](https://github.com/OHIF/Viewers/issues/1118)
## [1.6.2](https://github.com/OHIF/Viewers/compare/@ohif/core@1.6.1...@ohif/core@1.6.2) (2019-11-05)
### Bug Fixes
* [#1075](https://github.com/OHIF/Viewers/issues/1075) Returning to the Study List before all series have finishe… ([#1090](https://github.com/OHIF/Viewers/issues/1090)) ([ecaf578](https://github.com/OHIF/Viewers/commit/ecaf578f92dc40294cec7ff9b272fb432dec4125))
- [#1075](https://github.com/OHIF/Viewers/issues/1075) Returning to the Study
List before all series have finishe…
([#1090](https://github.com/OHIF/Viewers/issues/1090))
([ecaf578](https://github.com/OHIF/Viewers/commit/ecaf578f92dc40294cec7ff9b272fb432dec4125))
## [1.6.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.6.0...@ohif/core@1.6.1) (2019-10-31)
### Bug Fixes
* application crash if patientName is an object ([#1138](https://github.com/OHIF/Viewers/issues/1138)) ([64cf3b3](https://github.com/OHIF/Viewers/commit/64cf3b324da2383a927af1df2d46db2fca5318aa))
- application crash if patientName is an object
([#1138](https://github.com/OHIF/Viewers/issues/1138))
([64cf3b3](https://github.com/OHIF/Viewers/commit/64cf3b324da2383a927af1df2d46db2fca5318aa))
# [1.6.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.5.2...@ohif/core@1.6.0) (2019-10-26)
### Features
* Snapshot Download Tool ([#840](https://github.com/OHIF/Viewers/issues/840)) ([450e098](https://github.com/OHIF/Viewers/commit/450e0981a5ba054fcfcb85eeaeb18371af9088f8))
- Snapshot Download Tool ([#840](https://github.com/OHIF/Viewers/issues/840))
([450e098](https://github.com/OHIF/Viewers/commit/450e0981a5ba054fcfcb85eeaeb18371af9088f8))
## [1.5.2](https://github.com/OHIF/Viewers/compare/@ohif/core@1.5.1...@ohif/core@1.5.2) (2019-10-25)
### Bug Fixes
* set SR in ActiveViewport by clicking thumb ([#1091](https://github.com/OHIF/Viewers/issues/1091)) ([986b7ae](https://github.com/OHIF/Viewers/commit/986b7ae2bf4f7d27f326e62f93285ce20eaf0a79))
- set SR in ActiveViewport by clicking thumb
([#1091](https://github.com/OHIF/Viewers/issues/1091))
([986b7ae](https://github.com/OHIF/Viewers/commit/986b7ae2bf4f7d27f326e62f93285ce20eaf0a79))
## [1.5.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.5.0...@ohif/core@1.5.1) (2019-10-25)
### Bug Fixes
* 🐛 Orthographic MPR fix ([#1092](https://github.com/OHIF/Viewers/issues/1092)) ([460e375](https://github.com/OHIF/Viewers/commit/460e375f0aa75d35f7a46b4d48e6cc706019956d))
- 🐛 Orthographic MPR fix ([#1092](https://github.com/OHIF/Viewers/issues/1092))
([460e375](https://github.com/OHIF/Viewers/commit/460e375f0aa75d35f7a46b4d48e6cc706019956d))
# [1.5.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.4.0...@ohif/core@1.5.0) (2019-10-25)
### Features
* 🎸 Allow routes to load Google Cloud DICOM Stores in the Study List ([#1069](https://github.com/OHIF/Viewers/issues/1069)) ([21b586b](https://github.com/OHIF/Viewers/commit/21b586b08f3dde6613859712a9e0577dece564db))
- 🎸 Allow routes to load Google Cloud DICOM Stores in the Study List
([#1069](https://github.com/OHIF/Viewers/issues/1069))
([21b586b](https://github.com/OHIF/Viewers/commit/21b586b08f3dde6613859712a9e0577dece564db))
# [1.4.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.3.2...@ohif/core@1.4.0) (2019-10-15)
### Features
* 🎸 Only allow reconstruction of datasets that make sense ([#1010](https://github.com/OHIF/Viewers/issues/1010)) ([2d75e01](https://github.com/OHIF/Viewers/commit/2d75e01)), closes [#561](https://github.com/OHIF/Viewers/issues/561)
- 🎸 Only allow reconstruction of datasets that make sense
([#1010](https://github.com/OHIF/Viewers/issues/1010))
([2d75e01](https://github.com/OHIF/Viewers/commit/2d75e01)), closes
[#561](https://github.com/OHIF/Viewers/issues/561)
## [1.3.2](https://github.com/OHIF/Viewers/compare/@ohif/core@1.3.1...@ohif/core@1.3.2) (2019-10-14)
### Bug Fixes
* Return display sets in StudyMetadata._createDisplaySetsForSeries ([#1042](https://github.com/OHIF/Viewers/issues/1042)) ([fc01532](https://github.com/OHIF/Viewers/commit/fc01532))
- Return display sets in StudyMetadata.\_createDisplaySetsForSeries
([#1042](https://github.com/OHIF/Viewers/issues/1042))
([fc01532](https://github.com/OHIF/Viewers/commit/fc01532))
## [1.3.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.3.0...@ohif/core@1.3.1) (2019-10-11)
**Note:** Version bump only for package @ohif/core
# [1.3.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.2.0...@ohif/core@1.3.0) (2019-10-11)
### Features
* 🎸 Improve usability of Google Cloud adapter, including direct routes to studies ([#989](https://github.com/OHIF/Viewers/issues/989)) ([2bc361c](https://github.com/OHIF/Viewers/commit/2bc361c))
- 🎸 Improve usability of Google Cloud adapter, including direct routes to
studies ([#989](https://github.com/OHIF/Viewers/issues/989))
([2bc361c](https://github.com/OHIF/Viewers/commit/2bc361c))
# [1.2.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.1.0...@ohif/core@1.2.0) (2019-10-09)
### Bug Fixes
* OHIF-1002 Study lazy load should be true by default ([#1004](https://github.com/OHIF/Viewers/issues/1004)) ([66d8bc6](https://github.com/OHIF/Viewers/commit/66d8bc6))
- OHIF-1002 Study lazy load should be true by default
([#1004](https://github.com/OHIF/Viewers/issues/1004))
([66d8bc6](https://github.com/OHIF/Viewers/commit/66d8bc6))
### Features
* Allow a server requestOptions.auth to be a function that returns the Authorization header. ([#928](https://github.com/OHIF/Viewers/issues/928)) ([0064a4b](https://github.com/OHIF/Viewers/commit/0064a4b))
- Allow a server requestOptions.auth to be a function that returns the
Authorization header. ([#928](https://github.com/OHIF/Viewers/issues/928))
([0064a4b](https://github.com/OHIF/Viewers/commit/0064a4b))
# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.0.2...@ohif/core@1.1.0) (2019-10-03)
### Features
* Use QIDO + WADO to load series metadata individually rather than the entire study metadata at once ([#953](https://github.com/OHIF/Viewers/issues/953)) ([9e10c2b](https://github.com/OHIF/Viewers/commit/9e10c2b))
- Use QIDO + WADO to load series metadata individually rather than the entire
study metadata at once ([#953](https://github.com/OHIF/Viewers/issues/953))
([9e10c2b](https://github.com/OHIF/Viewers/commit/9e10c2b))
## [1.0.2](https://github.com/OHIF/Viewers/compare/@ohif/core@1.0.1...@ohif/core@1.0.2) (2019-10-02)
### Bug Fixes
* Temporarily sort SEG files to the end of the display set list as a workaround for several metadata issues ([#987](https://github.com/OHIF/Viewers/issues/987)) ([b3b4c10](https://github.com/OHIF/Viewers/commit/b3b4c10))
- Temporarily sort SEG files to the end of the display set list as a workaround
for several metadata issues
([#987](https://github.com/OHIF/Viewers/issues/987))
([b3b4c10](https://github.com/OHIF/Viewers/commit/b3b4c10))
## [1.0.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.0.0...@ohif/core@1.0.1) (2019-09-27)
### Bug Fixes
* Check for Value in 00081155 sequence (Few patient protocol images doesn't have this value) and removed a duplicate declaration ([#921](https://github.com/OHIF/Viewers/issues/921)) ([d0ec9cf](https://github.com/OHIF/Viewers/commit/d0ec9cf))
- Check for Value in 00081155 sequence (Few patient protocol images doesn't have
this value) and removed a duplicate declaration
([#921](https://github.com/OHIF/Viewers/issues/921))
([d0ec9cf](https://github.com/OHIF/Viewers/commit/d0ec9cf))
# [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.10...@ohif/core@1.0.0) (2019-09-27)
### Bug Fixes
* 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases ([#862](https://github.com/OHIF/Viewers/issues/862)) ([e7e1a8a](https://github.com/OHIF/Viewers/commit/e7e1a8a)), closes [#838](https://github.com/OHIF/Viewers/issues/838)
- 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support
in local file and WADO-RS-only use cases
([#862](https://github.com/OHIF/Viewers/issues/862))
([e7e1a8a](https://github.com/OHIF/Viewers/commit/e7e1a8a)), closes
[#838](https://github.com/OHIF/Viewers/issues/838)
### BREAKING CHANGES
* DICOM Seg
- DICOM Seg
## [0.50.10](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.9...@ohif/core@0.50.10) (2019-09-23)
### Bug Fixes
* Avoid using variable name "module" ([#942](https://github.com/OHIF/Viewers/issues/942)) ([72427fe](https://github.com/OHIF/Viewers/commit/72427fe)), closes [#940](https://github.com/OHIF/Viewers/issues/940)
- Avoid using variable name "module"
([#942](https://github.com/OHIF/Viewers/issues/942))
([72427fe](https://github.com/OHIF/Viewers/commit/72427fe)), closes
[#940](https://github.com/OHIF/Viewers/issues/940)
## [0.50.9](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.8...@ohif/core@0.50.9) (2019-09-17)
### Bug Fixes
* bump cornerstone-tools version in peerDeps ([4afc88c](https://github.com/OHIF/Viewers/commit/4afc88c))
- bump cornerstone-tools version in peerDeps
([4afc88c](https://github.com/OHIF/Viewers/commit/4afc88c))
## [0.50.8](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.7...@ohif/core@0.50.8) (2019-09-10)
**Note:** Version bump only for package @ohif/core
## [0.50.7](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.6...@ohif/core@0.50.7) (2019-09-10)
### Bug Fixes
* remove requestOptions when key is not needed ([32bc47d](https://github.com/OHIF/Viewers/commit/32bc47d))
- remove requestOptions when key is not needed
([32bc47d](https://github.com/OHIF/Viewers/commit/32bc47d))
## [0.50.6](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.5...@ohif/core@0.50.6) (2019-09-09)
**Note:** Version bump only for package @ohif/core
## [0.50.5](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.4...@ohif/core@0.50.5) (2019-09-04)
**Note:** Version bump only for package @ohif/core
## [0.50.4](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.3...@ohif/core@0.50.4) (2019-09-04)
### Bug Fixes
* measurementsAPI issue caused by production build ([#842](https://github.com/OHIF/Viewers/issues/842)) ([49d3439](https://github.com/OHIF/Viewers/commit/49d3439))
- measurementsAPI issue caused by production build
([#842](https://github.com/OHIF/Viewers/issues/842))
([49d3439](https://github.com/OHIF/Viewers/commit/49d3439))
## [0.50.3](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.2...@ohif/core@0.50.3) (2019-08-26)
### Bug Fixes
* **Studies:** Qidosupportsincludefield should be true by default ([#801](https://github.com/OHIF/Viewers/issues/801)) ([a88d865](https://github.com/OHIF/Viewers/commit/a88d865))
- **Studies:** Qidosupportsincludefield should be true by default
([#801](https://github.com/OHIF/Viewers/issues/801))
([a88d865](https://github.com/OHIF/Viewers/commit/a88d865))
## [0.50.2](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.1...@ohif/core@0.50.2) (2019-08-22)
**Note:** Version bump only for package @ohif/core
## [0.50.1](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.0-alpha.10...@ohif/core@0.50.1) (2019-08-14)
**Note:** Version bump only for package @ohif/core
# [0.50.0-alpha.10](https://github.com/OHIF/Viewers/compare/@ohif/core@0.11.1-alpha.9...@ohif/core@0.50.0-alpha.10) (2019-08-14)
**Note:** Version bump only for package @ohif/core
## [0.11.1-alpha.9](https://github.com/OHIF/Viewers/compare/@ohif/core@0.11.1-alpha.8...@ohif/core@0.11.1-alpha.9) (2019-08-14)
**Note:** Version bump only for package @ohif/core
## 0.11.1-alpha.8 (2019-08-14)
**Note:** Version bump only for package @ohif/core
# Change Log
All notable changes to this project will be documented in this file. See
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "2.5.3",
"version": "2.6.3",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
@@ -32,7 +32,7 @@
},
"peerDependencies": {
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "4.12.0",
"cornerstone-tools": "4.12.5",
"cornerstone-wado-image-loader": "^3.0.0",
"dicom-parser": "^1.8.3"
},
@@ -41,6 +41,7 @@
"ajv": "^6.10.0",
"dcmjs": "^0.8.3",
"dicomweb-client": "^0.5.2",
"immer": "6.0.2",
"isomorphic-base64": "^1.0.2",
"lodash.clonedeep": "^4.5.0",
"lodash.merge": "^4.6.1",
+55
View File
@@ -1,5 +1,6 @@
import guid from '../utils/guid.js';
import OHIFError from './OHIFError';
import { Vector3 } from 'cornerstone-math';
const OBJECT = 'object';
@@ -63,6 +64,60 @@ class ImageSet {
sortBy(sortingCallback) {
return this.images.sort(sortingCallback);
}
sortByImagePositionPatient() {
const images = this.images;
const referenceImagePositionPatient = _getImagePositionPatient(images[0]);
const refIppVec = new Vector3(
referenceImagePositionPatient[0],
referenceImagePositionPatient[1],
referenceImagePositionPatient[2]
);
const ImageOrientationPatient = _getImageOrientationPatient(images[0]);
const scanAxisNormal = new Vector3(
ImageOrientationPatient[0],
ImageOrientationPatient[1],
ImageOrientationPatient[2]
).cross(
new Vector3(
ImageOrientationPatient[3],
ImageOrientationPatient[4],
ImageOrientationPatient[5]
)
);
const distanceImagePairs = images.map(function(image) {
const ippVec = new Vector3(..._getImagePositionPatient(image));
const positionVector = refIppVec.clone().sub(ippVec);
const distance = positionVector.dot(scanAxisNormal);
return {
distance,
image,
};
});
distanceImagePairs.sort(function(a, b) {
return b.distance - a.distance;
});
const sortedImages = distanceImagePairs.map(a => a.image);
images.sort(function(a, b) {
return sortedImages.indexOf(a) - sortedImages.indexOf(b);
});
}
}
function _getImagePositionPatient(image) {
return image.getData().metadata.ImagePositionPatient;
}
function _getImageOrientationPatient(image) {
return image.getData().metadata.ImageOrientationPatient;
}
export default ImageSet;
+11 -22
View File
@@ -1,7 +1,7 @@
import * as dcmjs from 'dcmjs';
import queryString from 'query-string';
import dicomParser from 'dicom-parser';
import { getFallbackTagFromInstance } from '../utils/metadataProvider/metadataProviderFallbackTags';
import getPixelSpacingInformation from '../utils/metadataProvider/getPixelSpacingInformation';
import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';
import fetchOverlayData from '../utils/metadataProvider/fetchOverlayData';
@@ -58,9 +58,7 @@ class MetadataProvider {
Object.assign(instance, naturalizedDataset);
if (options.server) {
await this._checkBulkDataAndInlineBinaries(instance, options.server);
}
await this._checkBulkDataAndInlineBinaries(instance, options.server);
return instance;
}
@@ -163,18 +161,6 @@ class MetadataProvider {
return instance[naturalizedTagOrWADOImageLoaderTag];
}
if (options.fallback) {
// Perhaps the tag has fallbacks?
const fallbackTag = getFallbackTagFromInstance(
naturalizedTagOrWADOImageLoaderTag,
instance
);
if (fallbackTag) {
return fallbackTag;
}
}
// Maybe its a legacy CornerstoneWADOImageLoader tag then:
return this._getCornerstoneWADOImageLoaderTag(
naturalizedTagOrWADOImageLoaderTag,
@@ -220,10 +206,9 @@ class MetadataProvider {
const { ImageOrientationPatient } = instance;
// Fallback for DX images.
const PixelSpacing = getFallbackTagFromInstance(
'PixelSpacing',
instance
);
// TODO: We should use the rest of the results of this function
// to update the UI somehow
const { PixelSpacing } = getPixelSpacingInformation(instance);
let rowPixelSpacing;
let columnPixelSpacing;
@@ -344,7 +329,7 @@ class MetadataProvider {
break;
case WADO_IMAGE_LOADER_TAGS.OVERLAY_PLANE_MODULE:
metadata = [];
const overlays = [];
for (
let overlayGroup = 0x00;
@@ -389,9 +374,13 @@ class MetadataProvider {
roiStandardDeviation: instance[ROIStandardDeviationTag],
};
metadata.push(overlay);
overlays.push(overlay);
}
metadata = {
overlays
};
break;
case WADO_IMAGE_LOADER_TAGS.PATIENT_MODULE:
@@ -35,6 +35,12 @@ export class StudyMetadata extends Metadata {
writable: false,
value: [],
},
_derivedDisplaySets: {
configurable: false,
enumerable: false,
writable: false,
value: [],
},
_firstSeries: {
configurable: false,
enumerable: false,
@@ -121,7 +127,11 @@ export class StudyMetadata extends Metadata {
);
if (displaySet) {
displaySet.sopClassModule = true;
displaySets.push(displaySet);
displaySet.isDerived
? this._addDerivedDisplaySet(displaySet)
: displaySets.push(displaySet);
return displaySets;
}
}
@@ -186,9 +196,73 @@ export class StudyMetadata extends Metadata {
}
/**
* Creates a set of series to be placed in the Study Metadata
* The series that appear in the Study Metadata must represent
* imaging modalities.
* Adds the displaySets to the studies list of derived displaySets.
* @param {object} displaySet The displaySet to append to the derived displaysets list.
*/
_addDerivedDisplaySet(displaySet) {
this._derivedDisplaySets.push(displaySet);
// --> Perhaps that logic should exist in the extension sop class handler and this be a dumb list.
// TODO -> Get x Modality by referencedSeriesInstanceUid, FoR, etc.
}
/**
* Returns a list of derived datasets in the study, filtered by the given filter.
* @param {object} filter An object containing search filters
* @param {object} filter.modality
* @param {object} filter.referencedSeriesInstanceUID
* @param {object} filter.referencedFrameOfReferenceUID
* @return {Array} filtered derived display sets
*/
getDerivedDatasets(filter) {
const {
modality,
referencedSeriesInstanceUID,
referencedFrameOfReferenceUID,
} = filter;
let filteredDerivedDisplaySets = this._derivedDisplaySets;
if (modality) {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet => displaySet.Modality === Modality
);
}
if (referencedSeriesInstanceUID) {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet => {
if (!displaySet.metadata.ReferencedSeriesSequence) {
return false;
}
const ReferencedSeriesSequence = Array.isArray(
displaySet.metadata.ReferencedSeriesSequence
)
? displaySet.metadata.ReferencedSeriesSequence
: [displaySet.metadata.ReferencedSeriesSequence];
return ReferencedSeriesSequence.some(
ReferencedSeries =>
ReferencedSeries.SeriesInstanceUID === referencedSeriesInstanceUID
);
}
);
}
if (referencedFrameOfReferenceUID) {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet =>
displaySet.ReferencedFrameOfReferenceUID ===
ReferencedFrameOfReferenceUID
);
}
return filteredDerivedDisplaySets;
}
/**
* Creates a set of displaySets to be placed in the Study Metadata
* The displaySets that appear in the Study Metadata must represent
* imaging modalities. A series may be split into one or more displaySets.
*
* Furthermore, for drag/drop functionality,
* it is easiest if the stack objects also contain information about
@@ -482,6 +556,22 @@ export class StudyMetadata extends Metadata {
return series;
}
/**
* Get the first image id given display instance uid.
* @return {string} The image id.
*/
getFirstImageId(displaySetInstanceUID) {
try {
const displaySet = this.findDisplaySet(
displaySet => displaySet.displaySetInstanceUID === displaySetInstanceUID
);
return displaySet.images[0].getImageId();
} catch (error) {
console.error('Failed to retrieve image metadata');
return null;
}
}
/**
* Get the first instance of the current study retaining a consistent result across multiple calls.
* @return {InstanceMetadata} An instance of the InstanceMetadata class or null if it does not exist.
@@ -625,6 +715,10 @@ const makeDisplaySet = (series, instances) => {
imageSet.isReconstructable = isReconstructable.value;
if (shallSort && imageSet.isReconstructable) {
imageSet.sortByImagePositionPatient();
}
if (isReconstructable.missingFrames) {
// TODO -> This is currently unused, but may be used for reconstructing
// Volumes with gaps later on.
+20 -20
View File
@@ -1,5 +1,5 @@
import cloneDeep from 'lodash.clonedeep';
import merge from 'lodash.merge';
import produce from 'immer';
import {
CLEAR_VIEWPORT,
@@ -93,12 +93,13 @@ const viewports = (state = DEFAULT_STATE, action) => {
* @return {Object} New state.
*/
case SET_VIEWPORT_ACTIVE: {
const activeViewportIndex = getActiveViewportIndex(
state.numRows,
state.numColumns,
action.viewportIndex
);
return { ...state, activeViewportIndex };
return produce(state, draftState => {
draftState.activeViewportIndex = getActiveViewportIndex(
draftState.numRows,
draftState.numColumns,
action.viewportIndex
);
});
}
/**
@@ -163,21 +164,20 @@ const viewports = (state = DEFAULT_STATE, action) => {
* @return {Object} New state.
*/
case SET_VIEWPORT: {
const layout = cloneDeep(state.layout);
return produce(state, draftState => {
draftState.viewportSpecificData[action.viewportIndex] =
draftState.viewportSpecificData[action.viewportIndex] || {};
let viewportSpecificData = cloneDeep(state.viewportSpecificData);
viewportSpecificData[action.viewportIndex] = merge(
{},
viewportSpecificData[action.viewportIndex],
action.viewportSpecificData
);
Object.keys(action.viewportSpecificData).forEach(key => {
draftState.viewportSpecificData[action.viewportIndex][key] =
action.viewportSpecificData[key];
});
if (action.viewportSpecificData && action.viewportSpecificData.plugin) {
layout.viewports[action.viewportIndex].plugin =
action.viewportSpecificData.plugin;
}
return { ...state, layout, viewportSpecificData };
if (action.viewportSpecificData && action.viewportSpecificData.plugin) {
draftState.layout.viewports[action.viewportIndex].plugin =
action.viewportSpecificData.plugin;
}
});
}
/**
@@ -0,0 +1,45 @@
import DICOMWeb from '../../../DICOMWeb';
/**
* Function to get series sequence (sequence of pepeating items where each
* item includes the attributes of one or more series) based on a given sopInstance.
*
* @param {Object} instance The sop instance
* @returns {Promise} Referenced series sequence
*/
const getReferencedSeriesSequence = instance => {
const referencedSeriesSequenceRaw = instance['00081115'];
const referencedSeriesSequence = [];
if (referencedSeriesSequenceRaw && referencedSeriesSequenceRaw.Value) {
referencedSeriesSequenceRaw.Value.forEach(referencedSeries => {
const referencedSeriesInstanceUID = DICOMWeb.getString(
referencedSeries['0020000E']
);
const referencedInstanceSequenceRaw = referencedSeries['0008114A'];
const referencedInstanceSequence = [];
referencedInstanceSequenceRaw.Value.forEach(referencedInstance => {
referencedInstanceSequence.push({
referencedSOPClassUID: DICOMWeb.getString(
referencedInstance['00081150']
),
referencedSOPInstanceUID: DICOMWeb.getString(
referencedInstance['00081155']
),
});
});
referencedSeriesSequence.push({
referencedSeriesInstanceUID,
referencedInstanceSequence,
});
});
}
return referencedSeriesSequence;
};
export default getReferencedSeriesSequence;
@@ -2,6 +2,7 @@ import DICOMWeb from '../../../DICOMWeb';
import metadataProvider from '../../../classes/MetadataProvider';
import getWADORSImageId from '../../../utils/getWADORSImageId';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import getReferencedSeriesSequence from './getReferencedSeriesSequence';
/**
* Create a plain JS object that describes a study (a study descriptor object)
@@ -23,7 +24,10 @@ function createStudy(server, aSopInstance) {
PatientSize: DICOMWeb.getNumber(aSopInstance['00101020']),
PatientWeight: DICOMWeb.getNumber(aSopInstance['00101030']),
AccessionNumber: DICOMWeb.getString(aSopInstance['00080050']),
StudyTime: DICOMWeb.getString(aSopInstance['00080030']),
StudyDate: DICOMWeb.getString(aSopInstance['00080020']),
FrameOfReferenceUID: DICOMWeb.getString(aSopInstance['00200052']),
ReferencedSeriesSequence: getReferencedSeriesSequence(aSopInstance),
modalities: DICOMWeb.getString(aSopInstance['00080061']), // TODO -> Rename this.. it'll take a while to not mess this one up.
StudyDescription: DICOMWeb.getString(aSopInstance['00081030']),
NumberOfStudyRelatedInstances: DICOMWeb.getString(aSopInstance['00201208']),
@@ -186,7 +190,7 @@ async function makeSOPInstance(server, study, instance) {
*/
async function addInstancesToStudy(server, study, sopInstanceList) {
return Promise.all(
sopInstanceList.map(function(sopInstance) {
sopInstanceList.map(function (sopInstance) {
return makeSOPInstance(server, study, sopInstance);
})
);
+3
View File
@@ -8,6 +8,7 @@ import studyMetadataManager from './studyMetadataManager';
import writeScript from './writeScript.js';
import DicomLoaderService from './dicomLoaderService.js';
import b64toBlob from './b64toBlob.js';
import loadAndCacheDerivedDisplaySets from './loadAndCacheDerivedDisplaySets.js';
import * as urlUtil from './urlUtil';
import makeCancelable from './makeCancelable';
import hotkeys from './hotkeys';
@@ -24,6 +25,7 @@ const utils = {
studyMetadataManager,
DicomLoaderService,
urlUtil,
loadAndCacheDerivedDisplaySets,
makeCancelable,
hotkeys,
};
@@ -40,6 +42,7 @@ export {
studyMetadataManager,
DicomLoaderService,
urlUtil,
loadAndCacheDerivedDisplaySets,
makeCancelable,
hotkeys,
};
+1
View File
@@ -12,6 +12,7 @@ describe('Top level exports', () => {
'b64toBlob',
'StackManager',
'studyMetadataManager',
'loadAndCacheDerivedDisplaySets',
'DicomLoaderService',
'urlUtil',
'makeCancelable',
@@ -167,8 +167,8 @@ function _getSpacingIssue(spacing, averageSpacing) {
function _getPerpendicularDistance(a, b) {
return Math.sqrt(
Math.pow(a[0] - b[0], 2) +
Math.pow(a[1] - b[1], 2) +
Math.pow(a[2] - b[2], 2)
Math.pow(a[1] - b[1], 2) +
Math.pow(a[2] - b[2], 2)
);
}
@@ -0,0 +1,107 @@
import studyMetadataManager from './studyMetadataManager';
/**
* Study schema
*
* @typedef {Object} Study
* @property {Array} seriesList -
* @property {Object} seriesMap -
* @property {Object} seriesLoader -
* @property {string} wadoUriRoot -
* @property {string} wadoRoot -
* @property {string} qidoRoot -
* @property {string} patientName -
* @property {string} patientId -
* @property {number} patientAge -
* @property {number} patientSize -
* @property {number} patientWeight -
* @property {string} accessionNumber -
* @property {string} studyDate -
* @property {string} studyTime -
* @property {string} modalities -
* @property {string} studyDescription -
* @property {string} imageCount -
* @property {string} studyInstanceUid -
* @property {string} institutionName -
* @property {Array} displaySets -
*/
/**
* Factory function to load and cache derived display sets.
*
* @param {object} referencedDisplaySet Display set
* @param {string} referencedDisplaySet.displaySetInstanceUid Display set instance uid
* @param {string} referencedDisplaySet.seriesDate
* @param {string} referencedDisplaySet.seriesTime
* @param {string} referencedDisplaySet.seriesInstanceUid
* @param {string} referencedDisplaySet.seriesNumber
* @param {string} referencedDisplaySet.seriesDescription
* @param {number} referencedDisplaySet.numImageFrames
* @param {string} referencedDisplaySet.frameRate
* @param {string} referencedDisplaySet.modality
* @param {boolean} referencedDisplaySet.isMultiFrame
* @param {number} referencedDisplaySet.instanceNumber
* @param {boolean} referencedDisplaySet.isReconstructable
* @param {string} referencedDisplaySet.studyInstanceUid
* @param {Array} referencedDisplaySet.sopClassUids
* @param {Study[]} studies Collection of studies
* @returns void
*/
const loadAndCacheDerivedDisplaySets = (referencedDisplaySet, studies) => {
const { StudyInstanceUID, SeriesInstanceUID } = referencedDisplaySet;
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
if (!studyMetadata) {
return;
}
const derivedDisplaySets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: SeriesInstanceUID,
});
if (!derivedDisplaySets.length) {
return;
}
// Filter by type
const displaySetsPerModality = {};
derivedDisplaySets.forEach(displaySet => {
const Modality = displaySet.Modality;
if (displaySetsPerModality[Modality] === undefined) {
displaySetsPerModality[Modality] = [];
}
displaySetsPerModality[Modality].push(displaySet);
});
// For each type, see if any are loaded, if not load the most recent.
Object.keys(displaySetsPerModality).forEach(key => {
const displaySets = displaySetsPerModality[key];
const isLoaded = displaySets.some(displaySet => displaySet.isLoaded);
if (isLoaded) {
return;
}
// find most recent and load it.
let recentDateTime = 0;
let recentDisplaySet;
displaySets.forEach(displaySet => {
const dateTime = Number(
`${displaySet.SeriesDate}${displaySet.SeriesTime}`
);
if (dateTime > recentDateTime) {
recentDateTime = dateTime;
recentDisplaySet = displaySet;
}
});
recentDisplaySet.load(referencedDisplaySet, studies);
});
};
export default loadAndCacheDerivedDisplaySets;
@@ -1,5 +1,7 @@
import { api } from 'dicomweb-client';
import DICOMWeb from '../../DICOMWeb';
import str2ab from '../str2ab';
import unpackOverlay from './unpackOverlay';
export default async function fetchOverlayData(instance, server) {
const OverlayDataPromises = [];
@@ -15,11 +17,18 @@ export default async function fetchOverlayData(instance, server) {
const OverlayDataTag = `${groupStr}3000`;
if (instance[OverlayDataTag] && instance[OverlayDataTag].BulkDataURI) {
if (instance[OverlayDataTag] && instance[OverlayDataTag].InlineBinary) {
const inlineBinaryData = atob(instance[OverlayDataTag].InlineBinary);
const arraybuffer = str2ab(inlineBinaryData);
instance[OverlayDataTag] = unpackOverlay(arraybuffer)
} else if (instance[OverlayDataTag] && instance[OverlayDataTag].BulkDataURI) {
OverlayDataPromises.push(
_getOverlayData(instance[OverlayDataTag], server)
);
OverlayDataTags.push(OverlayDataTag);
} else if (instance[OverlayDataTag] && instance[OverlayDataTag] instanceof ArrayBuffer) {
instance[OverlayDataTag] = unpackOverlay(instance[OverlayDataTag])
}
}
@@ -60,19 +69,7 @@ async function _getOverlayData(tag, server) {
return dicomWeb
.retrieveBulkData(options)
.then(result => result[0])
.then(_unpackOverlay);
.then(unpackOverlay);
}
function _unpackOverlay(arrayBuffer) {
const bitArray = new Uint8Array(arrayBuffer);
const byteArray = new Uint8Array(8 * bitArray.length);
for (let byteIndex = 0; byteIndex < byteArray.length; byteIndex++) {
const bitIndex = byteIndex % 8;
const bitByteIndex = Math.floor(byteIndex / 8);
byteArray[byteIndex] =
1 * ((bitArray[bitByteIndex] & (1 << bitIndex)) >> bitIndex);
}
return byteArray;
}
@@ -1,5 +1,6 @@
import { api } from 'dicomweb-client';
import DICOMWeb from '../../DICOMWeb';
import str2ab from '../str2ab';
export default async function fetchPaletteColorLookupTableData(
instance,
@@ -157,31 +158,12 @@ function _getPaletteColor(server, paletteColorLookupTableData, lutDescriptor) {
.then(arrayBufferToPaletteColorLUT);
} else if (paletteColorLookupTableData.InlineBinary) {
const inlineBinaryData = atob(paletteColorLookupTableData.InlineBinary);
const arraybuffer = _str2ab(inlineBinaryData);
const arraybuffer = str2ab(inlineBinaryData);
return new Promise(resolve => {
resolve(arrayBufferToPaletteColorLUT(arraybuffer));
});
} else {
return Promise.resolve(arrayBufferToPaletteColorLUT(paletteColorLookupTableData));
}
throw new Error(
'Palette Color LUT was not provided as InlineBinary or BulkDataURI'
);
}
/**
* Convert String to ArrayBuffer
*
* @param {String} str Input String
* @return {ArrayBuffer} Output converted ArrayBuffer
*/
function _str2ab(str) {
const strLen = str.length;
const bytes = new Uint8Array(strLen);
for (let i = 0; i < strLen; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes.buffer;
}
@@ -0,0 +1,90 @@
import log from '../../log';
export default function getPixelSpacingInformation(instance) {
// See http://gdcm.sourceforge.net/wiki/index.php/Imager_Pixel_Spacing
// TODO: Add Ultrasound region spacing
// TODO: Add manual calibration
// TODO: Use ENUMS from dcmjs
const projectionRadiographSOPClassUIDs = [
'1.2.840.10008.5.1.4.1.1.1', // CR Image Storage
'1.2.840.10008.5.1.4.1.1.1.1', // Digital X-Ray Image Storage – for Presentation
'1.2.840.10008.5.1.4.1.1.1.1.1', // Digital X-Ray Image Storage – for Processing
'1.2.840.10008.5.1.4.1.1.1.2', // Digital Mammography X-Ray Image Storage – for Presentation
'1.2.840.10008.5.1.4.1.1.1.2.1', // Digital Mammography X-Ray Image Storage – for Processing
'1.2.840.10008.5.1.4.1.1.1.3', // Digital Intra – oral X-Ray Image Storage – for Presentation
'1.2.840.10008.5.1.4.1.1.1.3.1', // Digital Intra – oral X-Ray Image Storage – for Processing
'1.2.840.10008.5.1.4.1.1.12.1', // X-Ray Angiographic Image Storage
'1.2.840.10008.5.1.4.1.1.12.1.1', // Enhanced XA Image Storage
'1.2.840.10008.5.1.4.1.1.12.2', // X-Ray Radiofluoroscopic Image Storage
'1.2.840.10008.5.1.4.1.1.12.2.1', // Enhanced XRF Image Storage
'1.2.840.10008.5.1.4.1.1.12.3' // X-Ray Angiographic Bi-plane Image Storage Retired
];
const { PixelSpacing, ImagerPixelSpacing, SOPClassUID, PixelSpacingCalibrationType, PixelSpacingCalibrationDescription, EstimatedRadiographicMagnificationFactor } = instance;
const isProjection = projectionRadiographSOPClassUIDs.includes(SOPClassUID);
const TYPES = {
NOT_APPLICABLE: 'NOT_APPLICABLE',
UNKNOWN: 'UNKNOWN',
CALIBRATED: 'CALIBRATED',
DETECTOR: 'DETECTOR'
};
if (isProjection === false && !ImagerPixelSpacing) {
// If only Pixel Spacing is present, and this is not a projection radiograph,
// we can stop here
return {
PixelSpacing,
type: TYPES.NOT_APPLICABLE,
isProjection
};
} else if (isProjection && !ImagerPixelSpacing) {
// If only Pixel Spacing is present, and this is a projection radiograph,
// PixelSpacing should be used, but the user should be informed that
// what it means is unknown
return {
PixelSpacing,
type: TYPES.UNKNOWN,
isProjection
};
} else if (PixelSpacing && ImagerPixelSpacing && PixelSpacing === ImagerPixelSpacing) {
// If Imager Pixel Spacing and Pixel Spacing are present and they have the same values,
// then the user should be informed that the measurements are at the detector plane
return {
PixelSpacing,
type: TYPES.DETECTOR,
isProjection
};
} else if (PixelSpacing && ImagerPixelSpacing && PixelSpacing !== ImagerPixelSpacing) {
// If Imager Pixel Spacing and Pixel Spacing are present and they have different values,
// then the user should be informed that these are "calibrated"
// (in some unknown manner if Pixel Spacing Calibration Type and/or
// Pixel Spacing Calibration Description are absent)
return {
PixelSpacing,
type: TYPES.CALIBRATED,
isProjection,
PixelSpacingCalibrationType,
PixelSpacingCalibrationDescription
};
} else if (!PixelSpacing && ImagerPixelSpacing) {
let CorrectedImagerPixelSpacing = ImagerPixelSpacing;
if (EstimatedRadiographicMagnificationFactor) {
// Note that in IHE Mammo profile compliant displays, the value of Imager Pixel Spacing is required to be corrected by
// Estimated Radiographic Magnification Factor and the user informed of that.
// TODO: should this correction be done before all of this logic?
CorrectedImagerPixelSpacing = ImagerPixelSpacing.map(pixelSpacing => pixelSpacing / EstimatedRadiographicMagnificationFactor);
} else {
log.info('EstimatedRadiographicMagnificationFactor was not present. Unable to correct ImagerPixelSpacing.');
}
return {
PixelSpacing: CorrectedImagerPixelSpacing,
isProjection,
};
}
log.info('Unknown combination of PixelSpacing and ImagerPixelSpacing identified. Unable to determine spacing.');
}
@@ -1,27 +0,0 @@
import log from '../../log';
function getFallbackTagFromInstance(tag, instance) {
if (instance[tag]) {
return instance[tag];
}
const fallbackTags = fallbackTagsMap[tag];
if (fallbackTags) {
for (let i = 0; i < fallbackTags.length; i++) {
const fallbackTag = fallbackTags[i];
if (instance[fallbackTag]) {
log.info(`metadata provider fallback tag ${tag} to ${fallbackTag}`);
return instance[fallbackTag];
}
}
}
}
const fallbackTagsMap = {
PixelSpacing: ['ImagerPixelSpacing'],
};
export { fallbackTagsMap, getFallbackTagFromInstance };
@@ -0,0 +1,13 @@
export default function unpackOverlay(arrayBuffer) {
const bitArray = new Uint8Array(arrayBuffer);
const byteArray = new Uint8Array(8 * bitArray.length);
for (let byteIndex = 0; byteIndex < byteArray.length; byteIndex++) {
const bitIndex = byteIndex % 8;
const bitByteIndex = Math.floor(byteIndex / 8);
byteArray[byteIndex] =
1 * ((bitArray[bitByteIndex] & (1 << bitIndex)) >> bitIndex);
}
return byteArray;
}
@@ -0,0 +1,3 @@
const reconstructableModalities = ['MR', 'CT', 'PT', 'NM'];
export default reconstructableModalities;
+16
View File
@@ -0,0 +1,16 @@
/**
* Convert String to ArrayBuffer
*
* @param {String} str Input String
* @return {ArrayBuffer} Output converted ArrayBuffer
*/
export default function str2ab(str) {
const strLen = str.length;
const bytes = new Uint8Array(strLen);
for (let i = 0; i < strLen; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes.buffer;
}
+117 -196
View File
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.4.0...@ohif/ui@1.4.1) (2020-03-17)
### Bug Fixes
* rendering delete button when text is too wide for parent div ([#1526](https://github.com/OHIF/Viewers/issues/1526)) ([b269415](https://github.com/OHIF/Viewers/commit/b269415048dfec50e2abb3dd4f4355a23d6ad75a))
# [1.4.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.3.3...@ohif/ui@1.4.0) (2020-03-13)
### Features
* Segmentations Settings UI - Phase 1 [#1391](https://github.com/OHIF/Viewers/issues/1391) ([#1392](https://github.com/OHIF/Viewers/issues/1392)) ([e8842cf](https://github.com/OHIF/Viewers/commit/e8842cf8aebde98db7fc123e4867c8288552331f)), closes [#1423](https://github.com/OHIF/Viewers/issues/1423)
# Change Log
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.3.2...@ohif/ui@1.3.3) (2020-03-09)
**Note:** Version bump only for package @ohif/ui
@@ -304,368 +331,262 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
**Note:** Version bump only for package @ohif/ui
# [0.62.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.61.0...@ohif/ui@0.62.0) (2019-11-13)
### Features
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
- expose UiNotifications service
([#1172](https://github.com/OHIF/Viewers/issues/1172))
([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
# [0.61.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.60.1...@ohif/ui@0.61.0) (2019-11-12)
### Features
* 🎸 Update hotkeys and user preferences modal ([#1135](https://github.com/OHIF/Viewers/issues/1135)) ([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)), closes [#923](https://github.com/OHIF/Viewers/issues/923)
- 🎸 Update hotkeys and user preferences modal
([#1135](https://github.com/OHIF/Viewers/issues/1135))
([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)),
closes [#923](https://github.com/OHIF/Viewers/issues/923)
## [0.60.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.60.0...@ohif/ui@0.60.1) (2019-11-08)
### Bug Fixes
* Fix display issues with incorrect thumbnails. Change ImageThumb to functional component. ([#1148](https://github.com/OHIF/Viewers/issues/1148)) ([d70eae3](https://github.com/OHIF/Viewers/commit/d70eae3eb04fe854464f3e62316df8869bba6f11))
- Fix display issues with incorrect thumbnails. Change ImageThumb to functional
component. ([#1148](https://github.com/OHIF/Viewers/issues/1148))
([d70eae3](https://github.com/OHIF/Viewers/commit/d70eae3eb04fe854464f3e62316df8869bba6f11))
# [0.60.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.59.1...@ohif/ui@0.60.0) (2019-11-06)
### Features
* modal provider ([#1151](https://github.com/OHIF/Viewers/issues/1151)) ([75d88bc](https://github.com/OHIF/Viewers/commit/75d88bc454710d2dcdbc7d68c4d9df041159c840)), closes [#1086](https://github.com/OHIF/Viewers/issues/1086) [#1116](https://github.com/OHIF/Viewers/issues/1116) [#1116](https://github.com/OHIF/Viewers/issues/1116) [#1146](https://github.com/OHIF/Viewers/issues/1146) [#1142](https://github.com/OHIF/Viewers/issues/1142) [#1143](https://github.com/OHIF/Viewers/issues/1143) [#1110](https://github.com/OHIF/Viewers/issues/1110) [#1086](https://github.com/OHIF/Viewers/issues/1086) [#1116](https://github.com/OHIF/Viewers/issues/1116) [#1119](https://github.com/OHIF/Viewers/issues/1119)
- modal provider ([#1151](https://github.com/OHIF/Viewers/issues/1151))
([75d88bc](https://github.com/OHIF/Viewers/commit/75d88bc454710d2dcdbc7d68c4d9df041159c840)),
closes [#1086](https://github.com/OHIF/Viewers/issues/1086)
[#1116](https://github.com/OHIF/Viewers/issues/1116)
[#1116](https://github.com/OHIF/Viewers/issues/1116)
[#1146](https://github.com/OHIF/Viewers/issues/1146)
[#1142](https://github.com/OHIF/Viewers/issues/1142)
[#1143](https://github.com/OHIF/Viewers/issues/1143)
[#1110](https://github.com/OHIF/Viewers/issues/1110)
[#1086](https://github.com/OHIF/Viewers/issues/1086)
[#1116](https://github.com/OHIF/Viewers/issues/1116)
[#1119](https://github.com/OHIF/Viewers/issues/1119)
## [0.59.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.59.0...@ohif/ui@0.59.1) (2019-11-05)
### Bug Fixes
* [#1075](https://github.com/OHIF/Viewers/issues/1075) Returning to the Study List before all series have finishe… ([#1090](https://github.com/OHIF/Viewers/issues/1090)) ([ecaf578](https://github.com/OHIF/Viewers/commit/ecaf578f92dc40294cec7ff9b272fb432dec4125))
- [#1075](https://github.com/OHIF/Viewers/issues/1075) Returning to the Study
List before all series have finishe…
([#1090](https://github.com/OHIF/Viewers/issues/1090))
([ecaf578](https://github.com/OHIF/Viewers/commit/ecaf578f92dc40294cec7ff9b272fb432dec4125))
# [0.59.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.58.5...@ohif/ui@0.59.0) (2019-11-04)
### Features
* 🎸 New modal provider ([#1110](https://github.com/OHIF/Viewers/issues/1110)) ([5ee832b](https://github.com/OHIF/Viewers/commit/5ee832b19505a4e8e5756660ce6ed03a7f18dec3)), closes [#1086](https://github.com/OHIF/Viewers/issues/1086) [#1116](https://github.com/OHIF/Viewers/issues/1116)
- 🎸 New modal provider ([#1110](https://github.com/OHIF/Viewers/issues/1110))
([5ee832b](https://github.com/OHIF/Viewers/commit/5ee832b19505a4e8e5756660ce6ed03a7f18dec3)),
closes [#1086](https://github.com/OHIF/Viewers/issues/1086)
[#1116](https://github.com/OHIF/Viewers/issues/1116)
## [0.58.5](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.58.4...@ohif/ui@0.58.5) (2019-11-04)
### Bug Fixes
* 🐛 Minor issues measurement panel related to description ([#1142](https://github.com/OHIF/Viewers/issues/1142)) ([681384b](https://github.com/OHIF/Viewers/commit/681384b7425c83b02a0ed83371ca92d78ca7838c))
- 🐛 Minor issues measurement panel related to description
([#1142](https://github.com/OHIF/Viewers/issues/1142))
([681384b](https://github.com/OHIF/Viewers/commit/681384b7425c83b02a0ed83371ca92d78ca7838c))
## [0.58.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.58.3...@ohif/ui@0.58.4) (2019-10-31)
### Bug Fixes
* application crash if patientName is an object ([#1138](https://github.com/OHIF/Viewers/issues/1138)) ([64cf3b3](https://github.com/OHIF/Viewers/commit/64cf3b324da2383a927af1df2d46db2fca5318aa))
- application crash if patientName is an object
([#1138](https://github.com/OHIF/Viewers/issues/1138))
([64cf3b3](https://github.com/OHIF/Viewers/commit/64cf3b324da2383a927af1df2d46db2fca5318aa))
## [0.58.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.58.2...@ohif/ui@0.58.3) (2019-10-30)
### Bug Fixes
* 🐛 Fix ghost shadow on thumb ([#1113](https://github.com/OHIF/Viewers/issues/1113)) ([caaa032](https://github.com/OHIF/Viewers/commit/caaa032c4bc24fd69fdb01a15a8feb2721c321db))
- 🐛 Fix ghost shadow on thumb
([#1113](https://github.com/OHIF/Viewers/issues/1113))
([caaa032](https://github.com/OHIF/Viewers/commit/caaa032c4bc24fd69fdb01a15a8feb2721c321db))
## [0.58.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.58.1...@ohif/ui@0.58.2) (2019-10-29)
### Bug Fixes
* 🐛 Limit image download size to avoid browser issues ([#1112](https://github.com/OHIF/Viewers/issues/1112)) ([5716b71](https://github.com/OHIF/Viewers/commit/5716b71d409ee1c6f13393c8cb7f50222415e198)), closes [#1099](https://github.com/OHIF/Viewers/issues/1099)
- 🐛 Limit image download size to avoid browser issues
([#1112](https://github.com/OHIF/Viewers/issues/1112))
([5716b71](https://github.com/OHIF/Viewers/commit/5716b71d409ee1c6f13393c8cb7f50222415e198)),
closes [#1099](https://github.com/OHIF/Viewers/issues/1099)
## [0.58.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.58.0...@ohif/ui@0.58.1) (2019-10-29)
### Bug Fixes
* **StudyList:** camel case colSpan ([#1123](https://github.com/OHIF/Viewers/issues/1123)) ([0d498ba](https://github.com/OHIF/Viewers/commit/0d498ba17ddde8d8f0c51d742770e1574041eec0))
- **StudyList:** camel case colSpan
([#1123](https://github.com/OHIF/Viewers/issues/1123))
([0d498ba](https://github.com/OHIF/Viewers/commit/0d498ba17ddde8d8f0c51d742770e1574041eec0))
# [0.58.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.57.1...@ohif/ui@0.58.0) (2019-10-28)
### Features
* responsive study list ([#1068](https://github.com/OHIF/Viewers/issues/1068)) ([2cdef4b](https://github.com/OHIF/Viewers/commit/2cdef4b9844cc2ce61e9ce76b5a942ba7051fe16))
- responsive study list ([#1068](https://github.com/OHIF/Viewers/issues/1068))
([2cdef4b](https://github.com/OHIF/Viewers/commit/2cdef4b9844cc2ce61e9ce76b5a942ba7051fe16))
## [0.57.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.57.0...@ohif/ui@0.57.1) (2019-10-28)
### Bug Fixes
* MIP styling ([#1109](https://github.com/OHIF/Viewers/issues/1109)) ([0d21cc6](https://github.com/OHIF/Viewers/commit/0d21cc6ad0c47706b9e62e05fe2a0f1d86339760))
- MIP styling ([#1109](https://github.com/OHIF/Viewers/issues/1109))
([0d21cc6](https://github.com/OHIF/Viewers/commit/0d21cc6ad0c47706b9e62e05fe2a0f1d86339760))
# [0.57.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.56.1...@ohif/ui@0.57.0) (2019-10-26)
### Features
* Snapshot Download Tool ([#840](https://github.com/OHIF/Viewers/issues/840)) ([450e098](https://github.com/OHIF/Viewers/commit/450e0981a5ba054fcfcb85eeaeb18371af9088f8))
- Snapshot Download Tool ([#840](https://github.com/OHIF/Viewers/issues/840))
([450e098](https://github.com/OHIF/Viewers/commit/450e0981a5ba054fcfcb85eeaeb18371af9088f8))
## [0.56.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.56.0...@ohif/ui@0.56.1) (2019-10-26)
**Note:** Version bump only for package @ohif/ui
# [0.56.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.55.0...@ohif/ui@0.56.0) (2019-10-22)
### Features
* 🎸 Load spinner when selecting gcloud store. Add key on td ([#1034](https://github.com/OHIF/Viewers/issues/1034)) ([e62f403](https://github.com/OHIF/Viewers/commit/e62f403fe9e3df56713128e3d59045824b086d8d)), closes [#1057](https://github.com/OHIF/Viewers/issues/1057)
- 🎸 Load spinner when selecting gcloud store. Add key on td
([#1034](https://github.com/OHIF/Viewers/issues/1034))
([e62f403](https://github.com/OHIF/Viewers/commit/e62f403fe9e3df56713128e3d59045824b086d8d)),
closes [#1057](https://github.com/OHIF/Viewers/issues/1057)
# [0.55.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.54.0...@ohif/ui@0.55.0) (2019-10-15)
### Features
* Add browser info and app version ([#1046](https://github.com/OHIF/Viewers/issues/1046)) ([c217b8b](https://github.com/OHIF/Viewers/commit/c217b8b))
- Add browser info and app version
([#1046](https://github.com/OHIF/Viewers/issues/1046))
([c217b8b](https://github.com/OHIF/Viewers/commit/c217b8b))
# [0.54.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.4...@ohif/ui@0.54.0) (2019-10-14)
### Features
* Notification Service ([#1011](https://github.com/OHIF/Viewers/issues/1011)) ([92c8996](https://github.com/OHIF/Viewers/commit/92c8996))
- Notification Service ([#1011](https://github.com/OHIF/Viewers/issues/1011))
([92c8996](https://github.com/OHIF/Viewers/commit/92c8996))
## [0.53.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.3...@ohif/ui@0.53.4) (2019-10-11)
**Note:** Version bump only for package @ohif/ui
## [0.53.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.2...@ohif/ui@0.53.3) (2019-10-10)
### Bug Fixes
* 🎸 switch ohif logo from text + font to SVG ([#1021](https://github.com/OHIF/Viewers/issues/1021)) ([e7de8be](https://github.com/OHIF/Viewers/commit/e7de8be))
- 🎸 switch ohif logo from text + font to SVG
([#1021](https://github.com/OHIF/Viewers/issues/1021))
([e7de8be](https://github.com/OHIF/Viewers/commit/e7de8be))
## [0.53.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.1...@ohif/ui@0.53.2) (2019-10-09)
**Note:** Version bump only for package @ohif/ui
## [0.53.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.53.0...@ohif/ui@0.53.1) (2019-10-04)
### Bug Fixes
* Move Series Information to Separate Row ([#990](https://github.com/OHIF/Viewers/issues/990)) ([458d310](https://github.com/OHIF/Viewers/commit/458d310))
- Move Series Information to Separate Row
([#990](https://github.com/OHIF/Viewers/issues/990))
([458d310](https://github.com/OHIF/Viewers/commit/458d310))
# [0.53.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.52.0...@ohif/ui@0.53.0) (2019-10-03)
### Features
* Use QIDO + WADO to load series metadata individually rather than the entire study metadata at once ([#953](https://github.com/OHIF/Viewers/issues/953)) ([9e10c2b](https://github.com/OHIF/Viewers/commit/9e10c2b))
- Use QIDO + WADO to load series metadata individually rather than the entire
study metadata at once ([#953](https://github.com/OHIF/Viewers/issues/953))
([9e10c2b](https://github.com/OHIF/Viewers/commit/9e10c2b))
# [0.52.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.51.3...@ohif/ui@0.52.0) (2019-10-01)
### Features
* 🎸 MPR UI improvements. Added MinIP, AvgIP, slab thickness slider and mode toggle ([#947](https://github.com/OHIF/Viewers/issues/947)) ([c79c0c3](https://github.com/OHIF/Viewers/commit/c79c0c3))
- 🎸 MPR UI improvements. Added MinIP, AvgIP, slab thickness slider and mode
toggle ([#947](https://github.com/OHIF/Viewers/issues/947))
([c79c0c3](https://github.com/OHIF/Viewers/commit/c79c0c3))
## [0.51.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.51.2...@ohif/ui@0.51.3) (2019-10-01)
### Bug Fixes
* Address issues with touch devices and drag/drop causing crashes ([#982](https://github.com/OHIF/Viewers/issues/982)) ([cf40a83](https://github.com/OHIF/Viewers/commit/cf40a83))
- Address issues with touch devices and drag/drop causing crashes
([#982](https://github.com/OHIF/Viewers/issues/982))
([cf40a83](https://github.com/OHIF/Viewers/commit/cf40a83))
## [0.51.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.51.1...@ohif/ui@0.51.2) (2019-09-26)
### Bug Fixes
* 🐛 Set series into active viewport by clicking on thumbnail ([#945](https://github.com/OHIF/Viewers/issues/945)) ([5551f81](https://github.com/OHIF/Viewers/commit/5551f81)), closes [#895](https://github.com/OHIF/Viewers/issues/895) [#895](https://github.com/OHIF/Viewers/issues/895)
- 🐛 Set series into active viewport by clicking on thumbnail
([#945](https://github.com/OHIF/Viewers/issues/945))
([5551f81](https://github.com/OHIF/Viewers/commit/5551f81)), closes
[#895](https://github.com/OHIF/Viewers/issues/895)
[#895](https://github.com/OHIF/Viewers/issues/895)
## [0.51.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.51.0...@ohif/ui@0.51.1) (2019-09-19)
### Bug Fixes
* Use HTML5Backend for drag-drop if the device does not support touch ([#927](https://github.com/OHIF/Viewers/issues/927)) ([6fdac4d](https://github.com/OHIF/Viewers/commit/6fdac4d))
- Use HTML5Backend for drag-drop if the device does not support touch
([#927](https://github.com/OHIF/Viewers/issues/927))
([6fdac4d](https://github.com/OHIF/Viewers/commit/6fdac4d))
# [0.51.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.50.4...@ohif/ui@0.51.0) (2019-09-12)
### Features
* **EraserTool:** add eraserTool to @ohif/extension-cornerstone ([#912](https://github.com/OHIF/Viewers/issues/912)) ([698d274](https://github.com/OHIF/Viewers/commit/698d274))
- **EraserTool:** add eraserTool to @ohif/extension-cornerstone
([#912](https://github.com/OHIF/Viewers/issues/912))
([698d274](https://github.com/OHIF/Viewers/commit/698d274))
## [0.50.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.50.3...@ohif/ui@0.50.4) (2019-09-06)
**Note:** Version bump only for package @ohif/ui
## [0.50.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.50.2...@ohif/ui@0.50.3) (2019-09-04)
**Note:** Version bump only for package @ohif/ui
## [0.50.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.50.1...@ohif/ui@0.50.2) (2019-09-04)
### Bug Fixes
* measurementsAPI issue caused by production build ([#842](https://github.com/OHIF/Viewers/issues/842)) ([49d3439](https://github.com/OHIF/Viewers/commit/49d3439))
- measurementsAPI issue caused by production build
([#842](https://github.com/OHIF/Viewers/issues/842))
([49d3439](https://github.com/OHIF/Viewers/commit/49d3439))
## [0.50.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.50.0-alpha.10...@ohif/ui@0.50.1) (2019-08-14)
**Note:** Version bump only for package @ohif/ui
# [0.50.0-alpha.10](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.2.18-alpha.9...@ohif/ui@0.50.0-alpha.10) (2019-08-14)
**Note:** Version bump only for package @ohif/ui
## [0.2.18-alpha.9](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.2.18-alpha.8...@ohif/ui@0.2.18-alpha.9) (2019-08-14)
**Note:** Version bump only for package @ohif/ui
## 0.2.18-alpha.8 (2019-08-14)
**Note:** Version bump only for package @ohif/ui
# Change Log
All notable changes to this project will be documented in this file. See
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/ui",
"version": "1.3.3",
"version": "1.4.1",
"description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors",
"license": "MIT",
@@ -25,7 +25,7 @@
&.selected
.rowActions
height: 35px
height: auto;
visibility: visible
.measurementLocation
@@ -1,6 +1,5 @@
.RoundedButtonGroup {
--height: 25px;
position: relative;
z-index: 0;
}
@@ -37,12 +36,7 @@
z-index: 1;
}
.roundedButtonWrapper
.roundedButton
svg
.roundedButtonWrapper
.roundedButton
span {
.roundedButtonWrapper .roundedButton svg .roundedButtonWrapper .roundedButton span {
margin: 0 2px;
}
@@ -51,6 +45,10 @@
font-size: 15px;
}
.roundedButtonWrapper .bottomLabel {
padding: 0 10px 0 10px;
}
.bottomLabel {
color: var(--text-secondary-color);
font-size: 12px;
@@ -79,14 +79,14 @@ function ImageThumbnail(props) {
return () => {
purgeCancelablePromise();
};
}, []);
}, [purgeCancelablePromise]);
useEffect(() => {
if (image.imageId) {
cornerstone.renderToCanvas(canvasRef.current, image);
setLoading(false);
}
}, [image.imageId]);
}, [canvasRef, image, image.imageId]);
useEffect(() => {
if (!image.imageId || image.imageId !== imageId) {
@@ -94,7 +94,7 @@ function ImageThumbnail(props) {
setImagePromise();
fetchImagePromise();
}
}, [imageId]);
}, [fetchImagePromise, image.imageId, imageId, purgeCancelablePromise, setImagePromise]);
return (
<div className="ImageThumbnail">
@@ -10,6 +10,8 @@ export class TableListItem extends Component {
children: PropTypes.node,
itemClass: PropTypes.string,
itemIndex: PropTypes.number,
itemMeta: PropTypes.node,
itemMetaClass: PropTypes.string,
itemKey: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
@@ -17,14 +19,20 @@ export class TableListItem extends Component {
onItemClick: PropTypes.func.isRequired,
};
static defaultProps = {
itemMeta: null,
itemMetaClass: ''
};
render() {
return (
<div
className={`tableListItem ${this.props.itemClass}`}
onClick={this.onItemClick}
>
<div className="itemIndex">
<div className={`itemIndex ${this.props.itemMetaClass}`}>
{this.props.itemIndex}
{this.props.itemMeta}
<span className="warning-icon">
<Icon name="exclamation-triangle" />
</span>
+6
View File
@@ -9,6 +9,7 @@ import arrowsAltH from './icons/arrows-alt-h.svg';
import arrowsAltV from './icons/arrows-alt-v.svg';
import bars from './icons/bars.svg';
import brain from './icons/brain.svg';
import brush from './icons/brush.svg';
import caretDown from './icons/caret-down.svg';
import caretUp from './icons/caret-up.svg';
import check from './icons/check.svg';
@@ -63,12 +64,14 @@ import reset from './icons/reset.svg';
import rotate from './icons/rotate.svg';
import rotateRight from './icons/rotate-right.svg';
import saveRegular from './icons/save-regular.svg';
import scissors from './icons/scissors.svg';
import search from './icons/search.svg';
import searchPlus from './icons/search-plus.svg';
import softTissue from './icons/soft-tissue.svg';
import sort from './icons/sort.svg';
import sortDown from './icons/sort-down.svg';
import sortUp from './icons/sort-up.svg';
import sphere from './icons/sphere.svg';
import squareO from './icons/square-o.svg';
import star from './icons/star.svg';
import stepBackward from './icons/step-backward.svg';
@@ -84,11 +87,14 @@ import user from './icons/user.svg';
import youtube from './icons/youtube.svg';
const ICONS = {
brush,
scissors,
user,
sort,
th,
star,
'sort-up': sortUp,
sphere,
'sort-down': sortDown,
info,
cube,
@@ -0,0 +1,11 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
aria-labelledby="title"
width="1em"
height="1em"
fill="currentColor"
>
<title id="title">Brush</title>
<path d="M167.02 309.34c-40.12 2.58-76.53 17.86-97.19 72.3-2.35 6.21-8 9.98-14.59 9.98-11.11 0-45.46-27.67-55.25-34.35C0 439.62 37.93 512 128 512c75.86 0 128-43.77 128-120.19 0-3.11-.65-6.08-.97-9.13l-88.01-73.34zM457.89 0c-15.16 0-29.37 6.71-40.21 16.45C213.27 199.05 192 203.34 192 257.09c0 13.7 3.25 26.76 8.73 38.7l63.82 53.18c7.21 1.8 14.64 3.03 22.39 3.03 62.11 0 98.11-45.47 211.16-256.46 7.38-14.35 13.9-29.85 13.9-45.99C512 20.64 486 0 457.89 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 646 B

@@ -0,0 +1,11 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
aria-labelby="title"
width="1em"
height="1em"
fill="currentColor"
>
<title id="title">Scissors</title>
<path d="M278.06 256L444.48 89.57c4.69-4.69 4.69-12.29 0-16.97-32.8-32.8-85.99-32.8-118.79 0L210.18 188.12l-24.86-24.86c4.31-10.92 6.68-22.81 6.68-35.26 0-53.02-42.98-96-96-96S0 74.98 0 128s42.98 96 96 96c4.54 0 8.99-.32 13.36-.93L142.29 256l-32.93 32.93c-4.37-.61-8.83-.93-13.36-.93-53.02 0-96 42.98-96 96s42.98 96 96 96 96-42.98 96-96c0-12.45-2.37-24.34-6.68-35.26l24.86-24.86L325.69 439.4c32.8 32.8 85.99 32.8 118.79 0 4.69-4.68 4.69-12.28 0-16.97L278.06 256zM96 160c-17.64 0-32-14.36-32-32s14.36-32 32-32 32 14.36 32 32-14.36 32-32 32zm0 256c-17.64 0-32-14.36-32-32s14.36-32 32-32 32 14.36 32 32-14.36 32-32 32z"/>
</svg>

After

Width:  |  Height:  |  Size: 807 B

@@ -0,0 +1,21 @@
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512"
aria-labelby="title"
width="1em"
height="1em"
fill="currentColor"
>
<title id="title">Sphere</title>
<path d="M256,0C114.837,0,0,114.837,0,256s114.837,256,256,256s256-114.837,256-256S397.163,0,256,0z M256,21.333
c129.387,0,234.667,105.28,234.667,234.667c0,40.363-96.384,85.333-234.667,85.333c-28.629,0-55.339-2.005-79.893-5.44
c-3.435-24.555-5.44-51.264-5.44-79.893C170.667,117.717,215.637,21.333,256,21.333z M208.512,26.176
C173.184,67.435,149.333,153.707,149.333,256c0,26.688,1.621,52.309,4.651,76.309C72.683,317.205,21.333,285.419,21.333,256
C21.333,142.869,101.824,48.192,208.512,26.176z M26.176,303.488c27.072,23.168,73.429,41.429,131.136,51.2
c9.792,57.707,28.032,104.064,51.2,131.136C117.163,466.965,45.035,394.837,26.176,303.488z M256,490.667
c-29.419,0-61.205-51.349-76.309-132.651c24,3.029,49.621,4.651,76.309,4.651c102.293,0,188.565-23.851,229.824-59.179
C463.808,410.176,369.131,490.667,256,490.667z"/>
</svg>
<!-- style="enable-background:new 0 0 512 512;" xml:space="preserve" -->

After

Width:  |  Height:  |  Size: 1.1 KiB

+24 -12
View File
@@ -3,10 +3,10 @@ import './Range.css';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Range extends React.Component {
class Range extends Component {
constructor(props) {
super(props);
this.state = { value: props.value };
this.state = { value: props.value || 0 };
}
handleChange = event => {
@@ -16,16 +16,20 @@ class Range extends React.Component {
render() {
return (
<input
type="range"
value={this.state.value}
min={this.props.min}
max={this.props.max}
step={this.props.step || 1}
onChange={this.handleChange}
id={this.props.id}
className="range"
/>
<>
<input
type="range"
value={this.state.value}
min={this.props.min}
max={this.props.max}
step={this.props.step || 1}
onChange={this.handleChange}
id={this.props.id}
className="range"
/>
{this.props.showPercentage && <span>{`${this.state.value}%`}</span>}
{this.props.showValue && <span>{this.state.value}</span>}
</>
);
}
}
@@ -34,8 +38,16 @@ Range.propTypes = {
value: PropTypes.number,
min: PropTypes.number.isRequired,
max: PropTypes.number.isRequired,
step: PropTypes.number,
id: PropTypes.string,
onChange: PropTypes.func,
showPercentage: PropTypes.bool,
showValue: PropTypes.bool,
};
Range.defaultProps = {
showPercentage: false,
showValue: false,
};
export { Range };
File diff suppressed because it is too large. Load diff
@@ -1,8 +1,9 @@
describe('OHIF Cornerstone Hotkeys', () => {
before(() => {
cy.openStudy('MISTER^MR');
cy.waitDicomImage();
cy.expectMinimumThumbnails(3);
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
beforeEach(() => {
@@ -42,13 +43,6 @@ describe('OHIF Cornerstone Hotkeys', () => {
cy.get('@viewportInfoMidTop').should('contains.text', 'P');
});
it('checks if hotkey "I" can invert the image', () => {
// Hotkey I
cy.get('body').type('I');
// Visual comparison
cy.screenshot('Hotkey I - Should Invert Image');
});
it('checks if hotkeys "+", "-" and "=" can zoom in, out and fit to viewport', () => {
// Hotkey +
cy.get('body').type('+++'); // Press hotkey 3 times
@@ -73,9 +67,6 @@ describe('OHIF Cornerstone Hotkeys', () => {
cy.get('@viewportInfoMidLeft').should('contains.text', 'R');
cy.get('@viewportInfoMidTop').should('contains.text', 'A');
cy.get('@viewportInfoBottomRight').should('contains.text', 'Zoom: 211%');
// Visual comparison to make sure the 'inverted' image was reset
cy.screenshot('Hotkey SPACEBAR - Should Reset Image');
});
it('uses hotkeys "RightArrow" and "LeftArrow" to navigate between multiple viewports', () => {
@@ -126,6 +117,9 @@ describe('OHIF Cornerstone Hotkeys', () => {
cy.get('@viewport2InfoMidLeft').should('contains.text', 'A');
cy.get('@viewport2InfoMidTop').should('contains.text', 'H');
cy.get('@viewport2InfoBottomRight').should('contains.text', 'Zoom: 45%');
//Select viewport layout (1,1)
cy.setLayout(1, 1);
});
//TO-DO: This test is blocked by issue #1095 (https://github.com/OHIF/Viewers/issues/1095)
@@ -1,7 +1,8 @@
describe('OHIF Cornerstone Toolbar', () => {
before(() => {
cy.openStudy('MISTER^MR');
cy.waitDicomImage();
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
@@ -116,9 +117,6 @@ describe('OHIF Cornerstone Toolbar', () => {
.trigger('mousedown', 'center', { which: 1 })
.trigger('mousemove', 'bottom', { which: 1 })
.trigger('mouseup', 'bottom');
// Visual comparison
cy.screenshot('Pan tool moved the image inside the viewport');
});
it('checks if Length annotation can be added on viewport and on measurements panel', () => {
@@ -142,7 +140,7 @@ describe('OHIF Cornerstone Toolbar', () => {
cy.get('.measurementItem')
.its('length')
.should('be.eq', 1);
.should('be.at.least', 1);
cy.wrap($measurementsBtn).click();
});
@@ -170,7 +168,7 @@ describe('OHIF Cornerstone Toolbar', () => {
cy.get('.measurementItem')
.its('length')
.should('be.eq', 1);
.should('be.at.least', 1);
cy.wrap($measurementsBtn).click();
});
@@ -200,10 +198,11 @@ describe('OHIF Cornerstone Toolbar', () => {
.should('be.visible');
//Test PLAY button
cy.get('[title="Play / Stop"]')
.click()
.wait(100)
.click();
cy.get('[title="Play / Stop"]').then($btn => {
$btn.click();
cy.wait(100);
$btn.click();
});
let expectedText = 'Img: 1 1/26';
cy.get('@viewportInfoBottomLeft', { timeout: 15000 }).should(
@@ -251,9 +250,12 @@ describe('OHIF Cornerstone Toolbar', () => {
);
//Click on Cine button
cy.get('@cineBtn').click();
//Vefiry if cine control overlay is hidden
cy.get('@cineControls').should('not.be.visible');
cy.get('@cineBtn')
.click()
.then(() => {
//Vefiry if cine control overlay is hidden
cy.get('@cineControls').should('not.be.visible');
});
});
it('checks if More button will prompt a modal with secondary tools', () => {
@@ -397,7 +399,7 @@ describe('OHIF Cornerstone Toolbar', () => {
cy.get('@measurementsBtn').click();
cy.get('.measurementItem')
.its('length')
.should('be.eq', 2);
.should('be.at.least', 2);
//Click on More button
cy.get('@moreBtn').click();
@@ -421,19 +423,6 @@ describe('OHIF Cornerstone Toolbar', () => {
});
});
it('check if Invert tool will change the colors of the image in the viewport', () => {
// Click on More button
cy.get('@moreBtn').click();
// Verify if overlay is displayed
cy.get('.tooltip-toolbar-overlay').should('be.visible');
// Click on Invert button
cy.get('[data-cy="invert"]').click();
// Visual comparison
cy.screenshot('Invert tool - Should Invert Canvas');
});
it('check if Rotate tool will change the image orientation in the viewport', () => {
//Click on More button
cy.get('@moreBtn').click();
@@ -443,10 +432,9 @@ describe('OHIF Cornerstone Toolbar', () => {
.then(() => {
//Click on Rotate button
cy.get('[data-cy="rotate right"]').click({ force: true });
cy.get('@viewportInfoMidLeft').should('contains.text', 'F');
cy.get('@viewportInfoMidTop').should('contains.text', 'R');
});
// Visual comparison
cy.screenshot('Rotate tool - Should Rotate Image to Right');
});
it('check if Flip H tool will flip the image horizontally in the viewport', () => {
@@ -457,9 +445,8 @@ describe('OHIF Cornerstone Toolbar', () => {
//Click on Flip H button
cy.get('[data-cy="flip h"]').click();
// Visual comparison
cy.screenshot('Flip H tool - Should Flip Image on Y axis');
cy.get('@viewportInfoMidLeft').should('contains.text', 'L');
cy.get('@viewportInfoMidTop').should('contains.text', 'H');
});
it('check if Flip V tool will flip the image vertically in the viewport', () => {
@@ -470,8 +457,7 @@ describe('OHIF Cornerstone Toolbar', () => {
//Click on Flip V button
cy.get('[data-cy="flip v"]').click();
// Visual comparison
cy.screenshot('Flip V tool - Should Flip Image on X axis');
cy.get('@viewportInfoMidLeft').should('contains.text', 'R');
cy.get('@viewportInfoMidTop').should('contains.text', 'F');
});
});
@@ -1,7 +1,8 @@
describe('OHIF Download Snapshot File', () => {
before(() => {
cy.openStudy('MISTER^MR');
cy.waitDicomImage();
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
@@ -16,14 +17,14 @@ describe('OHIF Download Snapshot File', () => {
.click();
});
it('checks displayed information for Tablet experience', function () {
it('checks displayed information for Tablet experience', function() {
// Set Tablet resolution
cy.viewport(1000, 660);
// Visual comparison
cy.screenshot('Download Image Modal - Tablet experience');
});
it('checks displayed information for Desktop experience', function () {
it('checks displayed information for Desktop experience', function() {
// Set Desktop resolution
cy.viewport(1750, 720);
// Visual comparison
@@ -52,7 +53,7 @@ describe('OHIF Download Snapshot File', () => {
.should('be.visible');
});
it('cancel changes on download modal', function () {
it('cancel changes on download modal', function() {
//Change Image Width, Filename and File Type
cy.get('[data-cy="image-width"]')
.clear()
@@ -91,7 +92,7 @@ describe('OHIF Download Snapshot File', () => {
// //Check error message
// });
it('checks if "Show Annotations" checkbox will display annotations', function () {
it('checks if "Show Annotations" checkbox will display annotations', function() {
// Close modal that is initially opened
cy.get('[data-cy="close-button"]').click();
@@ -105,8 +106,6 @@ describe('OHIF Download Snapshot File', () => {
cy.get('[data-cy="show-annotations"]').check();
// Check image preview
cy.get('[data-cy="image-preview"]').scrollIntoView();
// Visual comparison
cy.screenshot('Download Image Modal - Show Annotations checked');
//Compare classes that exists on Image Preview with Annotations and Without Annotation
cy.get('[data-cy="modal-content"]')
.find('canvas')
@@ -2,10 +2,19 @@
//this is intended to be running in a controled docker environment with test data.
describe('OHIF Study List', function() {
context('Desktop resolution', function() {
before(function() {
cy.openStudyList();
});
beforeEach(function() {
cy.viewport(1750, 720);
cy.openStudyList();
cy.initStudyListAliasesOnDesktop();
//Clear all text fields
cy.get('@PatientName').clear();
cy.get('@MRN').clear();
cy.get('@AccessionNumber').clear();
cy.get('@StudyDescription').clear();
cy.get('@modalities').clear();
});
it('searches Patient Name with exact string', function() {
@@ -43,7 +52,7 @@ describe('OHIF Study List', function() {
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(16);
expect($list.length).to.be.eq(15); // TODO: Where are you hiding MISTER^MR?
expect($list).to.contain('MR');
});
});
@@ -101,10 +110,16 @@ describe('OHIF Study List', function() {
});
context('Tablet resolution', function() {
before(function() {
cy.openStudyList();
});
beforeEach(function() {
cy.viewport(1000, 660);
cy.openStudyList();
cy.initStudyListAliasesOnTablet();
//Clear all text fields
cy.get('@patientNameOrMRN').clear();
cy.get('@accessionModalityDescription').clear();
});
it('searches Patient Name with exact string', function() {
@@ -132,7 +147,7 @@ describe('OHIF Study List', function() {
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(16);
expect($list.length).to.be.eq(15); // TODO: Where are you hiding MISTER^MR?
expect($list).to.contain('MR');
});
});
@@ -1,8 +1,9 @@
describe('OHIF Study Viewer Page', function() {
before(function() {
cy.openStudy('MISTER^MR');
cy.waitDicomImage();
cy.expectMinimumThumbnails(6);
before(() => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
beforeEach(function() {
@@ -11,8 +12,6 @@ describe('OHIF Study Viewer Page', function() {
});
it('checks if series thumbnails are being displayed', function() {
cy.screenshot('Series Thumbnails - Should Display Thumbnails');
cy.get('[data-cy="thumbnail-list"]')
.its('length')
.should('be.gt', 1);
@@ -103,11 +102,12 @@ describe('OHIF Study Viewer Page', function() {
})
.trigger('mouseup', x1, y1, {
which: 3,
})
.then(() => {
//Contextmenu is visible
cy.get('.ToolContextMenu').should('be.visible');
});
//Contextmenu is visible
cy.get('.ToolContextMenu').should('be.visible');
//Click "Delete measurement"
cy.get('.form-action')
.contains('Delete measurement')
@@ -290,9 +290,6 @@ describe('OHIF Study Viewer Page', function() {
});
});
// Visual comparison
cy.screenshot('About modal - Should display modal');
//close modal
cy.get('[data-cy="close-button"]').click();
cy.get('@aboutOverlay').should('not.be.enabled');
@@ -22,10 +22,6 @@ describe('OHIF User Preferences', () => {
cy.get('@cancelBtn').should('have.text', 'Cancel');
cy.get('@saveBtn').should('have.text', 'Save');
// Visual comparison
cy.screenshot(
'User Preferences Modal - Hotkeys tab initial state in Study List page'
);
cy.get('[data-cy="close-button"]').click();
});
@@ -36,10 +32,6 @@ describe('OHIF User Preferences', () => {
// Language dropdown should be displayed
cy.get('#language-select').should('be.visible');
// Visual comparison
cy.screenshot(
'User Preferences Modal - General tab initial state in Study List page'
);
// Set language to Spanish and save
cy.setLanguage('Spanish');
@@ -154,7 +146,7 @@ describe('OHIF User Preferences', () => {
cy.setLanguage('Spanish');
// Go to Study Viewer page
cy.openStudy('MISTER^MR');
cy.openStudyInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
cy.waitDicomImage();
cy.expectMinimumThumbnails(5);
cy.initCommonElementsAliases();
@@ -186,7 +178,7 @@ describe('OHIF User Preferences', () => {
context('Study Viewer Page', function() {
before(() => {
cy.openStudy('MISTER^MR');
cy.openStudyInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
cy.expectMinimumThumbnails(5);
});
@@ -211,10 +203,6 @@ describe('OHIF User Preferences', () => {
cy.get('@cancelBtn').should('have.text', 'Cancel');
cy.get('@saveBtn').should('have.text', 'Save');
// Visual comparison
cy.screenshot(
'User Preferences Modal - Hotkeys tab initial state in Study Viewer page'
);
cy.get('[data-cy="close-button"]').click(); //close User Preferences modal
});
@@ -225,10 +213,6 @@ describe('OHIF User Preferences', () => {
// Language dropdown should be displayed
cy.get('#language-select').should('be.visible');
// Visual comparison
cy.screenshot(
'User Preferences Modal - General tab initial state in Study Viewer page'
);
// Set language to Spanish
cy.setLanguage('Spanish');
@@ -401,9 +385,6 @@ describe('OHIF User Preferences', () => {
cy.get('body').type(' ');
cy.get('@viewportInfoMidTop').should('contains.text', 'A');
// Visual comparison
cy.screenshot('Viewport Navigation - 2nd viewport inverted and rotated');
// Set 1 viewport layout
cy.setLayout(1, 1);
});
@@ -1,6 +1,8 @@
describe('OHIF HTML Extension', () => {
before(() => {
cy.openStudy('Dummy');
cy.openStudyInViewer(
'1.2.826.0.13854362241694438965858641723883466450351448'
);
cy.expectMinimumThumbnails(5);
});
@@ -40,5 +42,37 @@ describe('OHIF HTML Extension', () => {
.scrollTo('bottom');
cy.wrap($viewport).should('have.class', 'active');
});
cy.setLayout('1', '1');
});
});
describe('OHIF PDF Extension', () => {
before(() => {
cy.checkStudyRouteInViewer(
'1.2.826.0.13854362241694438965858641723883466450351448'
);
cy.expectMinimumThumbnails(5);
});
it('checks if series thumbnails are being displayed', () => {
cy.get('[data-cy="thumbnail-list"]')
.contains('DOC')
.its('length')
.should('to.be.at.least', 1);
});
it('drags and drop a PDF thumbnail into viewport', () => {
cy.get('[data-cy="thumbnail-list"]')
.contains('DOC')
.scrollIntoView()
.drag('.viewport-drop-target');
cy.get('.DicomPDFViewport')
.its('length')
.should('be.eq', 1);
//Take Screenshot
cy.screenshot('PDF Extension - Should load PDF file');
});
});
@@ -1,27 +0,0 @@
describe('OHIF PDF Extension', () => {
before(() => {
cy.openStudy('Dummy');
cy.expectMinimumThumbnails(6);
});
it('checks if series thumbnails are being displayed', () => {
cy.get('[data-cy="thumbnail-list"]')
.contains('DOC')
.its('length')
.should('to.be.at.least', 1);
});
it('drags and drop a PDF thumbnail into viewport', () => {
cy.get('[data-cy="thumbnail-list"]')
.contains('DOC')
.drag('.viewport-drop-target');
cy.get('.DicomPDFViewport')
.its('length')
.should('be.eq', 1);
// This won't work unless we switch to an extension that renders using `canvas`
// Currently, we rely on the browser's built-in implementation
cy.screenshot('PDF Extension - Should load PDF file');
});
});
@@ -1,16 +1,9 @@
describe('OHIF VTK Extension', () => {
before(() => {
cy.openStudy('Juno');
cy.waitDicomImage();
cy.checkStudyRouteInViewer(
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1'
);
cy.expectMinimumThumbnails(7);
});
beforeEach(() => {
//TODO: Due to percy snapshot, the images inside the viewports are being resized
//and once the browser continues the test execution, the images are not centered in the viewport.
//To fix that, we need to reload the page before each test.
//https://github.com/OHIF/Viewers/issues/1168
cy.reload();
//Waiting for the desired thumbnail content to be displayed
cy.get('[data-cy="thumbnail-list"]').should($list => {
@@ -23,14 +16,16 @@ describe('OHIF VTK Extension', () => {
// Drag and drop third thumbnail into first viewport
cy.get('[data-cy="thumbnail-list"]')
.contains('CT WB 5.0 B35f')
.drag('.viewport-drop-target')
.drag('.viewport-drop-target');
//Select 2D MPR button
cy.get('[data-cy="2d mpr"]').click();
//Wait Reformatting Images
cy.waitVTKReformatting();
});
beforeEach(() => {
cy.initVTKToolsAliases();
});
@@ -54,9 +49,6 @@ describe('OHIF VTK Extension', () => {
cy.get('@layoutBtn')
.should('be.visible')
.contains('Layout');
// Visual comparison
cy.screenshot('VTK initial state - Should display toolbar and 3 viewports');
});
it('checks Crosshairs tool', () => {
@@ -64,20 +56,26 @@ describe('OHIF VTK Extension', () => {
// Click and Move the mouse inside the viewport
cy.get('[data-cy="viewport-container-0"]')
.trigger('mousedown', 'center', { which: 1 })
.trigger('mousemove', 'top', { which: 1 })
.trigger('mousedown', 'center', {
which: 1,
})
.trigger('mousemove', 'top', {
which: 1,
})
.trigger('mouseup');
// Visual comparison
//Take Screenshot
cy.screenshot(
"VTK Crosshairs tool - Should display crosshairs' green lines"
);
});
/* TODO: Non-deterministic behavior (const expectedText = 'W: 350 L: -1044';)
it('checks WWWC tool', () => {
cy.get('@wwwcBtn').click();
//Initial label in the viewport
const initialLabelText = 'W: 350 L: 40';
// Click and Move the mouse inside the viewport
cy.get('[data-cy="viewport-container-0"]')
.trigger('mousedown', 'center', { which: 1 })
@@ -86,29 +84,29 @@ describe('OHIF VTK Extension', () => {
.trigger('mousemove', 'top', { which: 1 })
.trigger('mouseup', { which: 1 })
.then(() => {
const expectedText = 'W: 350 L: 40';
cy.get('.ViewportOverlay > div.bottom-right.overlay-element').should(
'contains.text',
expectedText
'not.have.text',
initialLabelText
);
});
// Visual comparison
cy.screenshot('VTK WWWC tool - Canvas should be bright');
}); */
});
it('checks Rotate tool', () => {
cy.get('@rotateBtn').click();
// Click and Move the mouse inside the viewport
cy.get('[data-cy="viewport-container-0"]')
.trigger('mousedown', 'center', { which: 1 })
.trigger('mousedown', 'center', {
which: 1,
})
.trigger('mousemove', 'top', { which: 1 })
.trigger('mousedown', 'center', { which: 1 })
.trigger('mousedown', 'center', {
which: 1,
})
.trigger('mousemove', 'top', { which: 1 })
.trigger('mouseup', { which: 1 });
// Visual comparison
//Take Screenshot
cy.screenshot('VTK Rotate tool - Should rotate image');
});
});
@@ -1,14 +1,14 @@
describe('OHIF Save Measurements', function() {
before(() => {
cy.openStudy('MISTER^MR');
cy.waitDicomImage();
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
beforeEach(() => {
// Wait image to load on viewport
cy.wait(2000);
cy.resetViewport();
cy.initCommonElementsAliases();
});
@@ -23,65 +23,67 @@ describe('OHIF Save Measurements', function() {
.its('length')
.should('be.at.least', 1);
// TODO: Don't save until we're using in-memory data store
// Save new measurement
cy.get('[data-cy="save-measurements-btn"]').click();
// cy.get('[data-cy="save-measurements-btn"]').click();
// Verify that success message overlay is displayed
cy.get('.sb-success')
.should('be.visible')
.and('contains.text', 'Measurements saved successfully');
// cy.get('.sb-success')
// .should('be.visible')
// .and('contains.text', 'Measurements saved successfully');
// Visual test comparison
cy.screenshot('Save Measurements - new measurement added');
cy.percyCanvasSnapshot('Save Measurements - new measurement added');
});
it('retrieves saved measurements', function() {
// Add measurement in the viewport
cy.addLengthMeasurement();
// it('retrieves saved measurements', function() {
// // Add measurement in the viewport
// cy.addLengthMeasurement();
// Verify if measurement annotation was added into the measurements panel
cy.get('@measurementsBtn').click();
cy.get('.measurementDisplayText') // Get label size of the recently added measurement
.last()
.then($measurementSizeLabel => {
// Save new measurement
cy.get('[data-cy="save-measurements-btn"]').click();
// // Verify if measurement annotation was added into the measurements panel
// cy.get('@measurementsBtn').click();
// cy.get('.measurementDisplayText') // Get label size of the recently added measurement
// .last()
// .then($measurementSizeLabel => {
// // Save new measurement
// // TODO: Do not save
// cy.get('[data-cy="save-measurements-btn"]')
// .click()
// .then(() => {
// // Verify that success message overlay is displayed
// cy.get('.sb-success').should('be.visible');
// });
// // Reload the page
// cy.reload()
// .wait(1000) //Wait page to load
// .expectMinimumThumbnails(2); //wait all thumbnails to load
// // Verify that recently added measurement was retrieved
// cy.get('@measurementsBtn').click();
// cy.get('.measurementDisplayText') // Get label size of the recently added measurement
// .last()
// .then($retrivedMeasurementSizeLabel => {
// expect($retrivedMeasurementSizeLabel.textContent).to.eq(
// $measurementSizeLabel.textContent
// );
// });
// });
// });
// Verify that success message overlay is displayed
cy.get('.sb-success').should('be.visible');
// it('checks error message when saving without any measurement', function() {
// // Checks that measurement list is empty
// cy.get('.numberOfItems').should('have.text', '0');
// Reload the page
cy.reload()
.wait(1000) //Wait page to load
.expectMinimumThumbnails(2); //wait all thumbnails to load
// // Click on Save Measurement button
// cy.get('[data-cy="save-measurements-btn"]').click();
//Verify that recently added measurement was retrieved
cy.get('@measurementsBtn').click();
cy.get('.measurementDisplayText') // Get label size of the recently added measurement
.last()
.then($retrivedMeasurementSizeLabel => {
expect($retrivedMeasurementSizeLabel.textContent).to.eq(
$measurementSizeLabel.textContent
);
});
});
});
it('checks error message when saving without any measurement', function() {
// Checks that measurement list is empty
cy.get('.numberOfItems').should('have.text', '0');
// Click on Save Measurement button
cy.get('[data-cy="save-measurements-btn"]').click();
// Verify that error message overlay is displayed
cy.get('.sb-error')
.should('be.visible')
.and('contains.text', 'Error while saving the measurements');
// Close message overlay
cy.get('.sb-closeIcon').click();
});
// // Verify that error message overlay is displayed
// cy.get('.sb-error')
// .should('be.visible')
// .and('contains.text', 'Error while saving the measurements');
// // Close message overlay
// cy.get('.sb-closeIcon').click();
// });
it('checks if warning message is displayed on measurements of unsupported tools', function() {
// Add measurement for unsupported tool in the viewport
@@ -96,13 +98,13 @@ describe('OHIF Save Measurements', function() {
// Check that warning is displayed for unsupported tool
cy.get('.hasWarnings').should('be.visible');
// Save new measurement
cy.get('[data-cy="save-measurements-btn"]').click();
// // Save new measurement
// cy.get('[data-cy="save-measurements-btn"]').click();
// Verify that error message overlay is displayed
cy.get('.sb-error')
.should('be.visible')
.and('contains.text', 'Error while saving the measurements');
// // Verify that error message overlay is displayed
// cy.get('.sb-error')
// .should('be.visible')
// .and('contains.text', 'Error while saving the measurements');
// Close Measurements panel
cy.get('@measurementsBtn').click();
@@ -1,8 +1,9 @@
describe('Visual Regression - OHIF Cornerstone Hotkeys', () => {
before(() => {
cy.openStudy('MISTER^MR');
cy.waitDicomImage();
cy.expectMinimumThumbnails(3);
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
beforeEach(() => {
@@ -1,7 +1,8 @@
describe('Visual Regression - OHIF Cornerstone Toolbar', () => {
before(() => {
cy.openStudy('MISTER^MR');
cy.waitDicomImage();
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
@@ -1,7 +1,8 @@
describe('Visual Regression - OHIF Download Snapshot File', () => {
before(() => {
cy.openStudy('MISTER^MR');
cy.waitDicomImage();
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
@@ -1,8 +1,10 @@
describe('Visual Regression - OHIF PDF Extension', () => {
before(() => {
cy.openStudy('Dummy');
cy.expectMinimumThumbnails(6);
});
cy.checkStudyRouteInViewer(
'1.2.826.0.13854362241694438965858641723883466450351448'
);
cy.expectMinimumThumbnails(5);
});v
it('drags and drop a PDF thumbnail into viewport', () => {
cy.get('[data-cy="thumbnail-list"]')
@@ -1,8 +1,9 @@
describe('Visual Regression - OHIF VTK Extension', () => {
before(() => {
cy.openStudy('Bellona');
cy.waitDicomImage();
cy.expectMinimumThumbnails(5);
cy.checkStudyRouteInViewer(
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1'
);
cy.expectMinimumThumbnails(7);
//Waiting for the desired thumbnail content to be displayed
cy.get('[data-cy="thumbnail-list"]').should($list => {
@@ -1,8 +1,9 @@
describe('Visual Regression - OHIF Study Viewer Page', function() {
before(function() {
cy.openStudy('MISTER^MR');
cy.waitDicomImage();
cy.expectMinimumThumbnails(6);
before(() => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
beforeEach(function() {
@@ -51,7 +51,7 @@ describe('Visual Regression - OHIF User Preferences', () => {
context('Study Viewer Page', function() {
before(() => {
cy.openStudy('MISTER^MR');
cy.openStudyInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
cy.expectMinimumThumbnails(5);
});
+1 -1
View File
@@ -17,7 +17,7 @@ export function initCornerstoneToolsAliases() {
//Creating aliases for Common page elements
export function initCommonElementsAliases() {
cy.get(
'.pull-right > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton'
'.pull-right > .RoundedButtonGroup > .roundedButtonWrapper:first-of-type > .roundedButton'
).as('measurementsBtn');
cy.get('.viewport-element').as('viewport');
cy.get('section.sidepanel.from-right').as('measurementsPanel');
@@ -53,6 +53,20 @@ Cypress.Commands.add('openStudy', PatientName => {
.click({ force: true });
});
Cypress.Commands.add('checkStudyRouteInViewer', StudyInstanceUID => {
cy.location('pathname').then($url => {
cy.log($url);
if ($url == 'blank' || !$url.includes(`/viewer/${StudyInstanceUID}`)) {
cy.openStudyInViewer(StudyInstanceUID);
cy.waitDicomImage();
}
});
});
Cypress.Commands.add('openStudyInViewer', StudyInstanceUID => {
cy.visit(`/viewer/${StudyInstanceUID}`);
});
/**
* Command to search for a Modality and open the study.
*
@@ -282,6 +296,16 @@ Cypress.Commands.add('initStudyListAliasesOnTablet', () => {
initStudyListAliasesOnTablet();
});
//Initialize aliases for Study List page elements
Cypress.Commands.add('initStudyListAliasesOnDesktop', () => {
initStudyListAliasesOnDesktop();
});
//Initialize aliases for Study List page elements
Cypress.Commands.add('initStudyListAliasesOnTablet', () => {
initStudyListAliasesOnTablet();
});
//Add measurements in the viewport
Cypress.Commands.add(
'addLengthMeasurement',
+9 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/viewer",
"version": "3.7.8",
"version": "3.8.7",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
"license": "MIT",
@@ -30,6 +30,7 @@
"start": "yarn run dev",
"test:e2e": "cypress open",
"test:e2e:ci": "percy exec -- cypress run --config video=false --record --browser chrome --spec 'cypress/integration/visual-regression/**/*'",
"test:e2e:local": "cypress run --config video=false --browser chrome --spec 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'",
"test:e2e:dist": "start-server-and-test test:e2e:serve http://localhost:3000 test:e2e:ci",
"test:e2e:serve": "serve -n -l 3000 -s dist",
"test:unit": "jest --watchAll",
@@ -47,21 +48,22 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@ohif/core": "^2.5.3",
"@ohif/extension-cornerstone": "^2.4.3",
"@ohif/core": "^2.6.3",
"@ohif/extension-cornerstone": "^2.5.0",
"@ohif/extension-dicom-html": "^1.1.1",
"@ohif/extension-dicom-microscopy": "^0.50.7",
"@ohif/extension-dicom-pdf": "^1.0.2",
"@ohif/extension-dicom-segmentation": "^0.1.0",
"@ohif/extension-lesion-tracker": "^0.2.0",
"@ohif/extension-vtk": "^1.2.24",
"@ohif/extension-vtk": "^1.3.4",
"@ohif/i18n": "^0.52.7",
"@ohif/ui": "^1.3.3",
"@ohif/ui": "^1.4.1",
"@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6",
"core-js": "^3.2.1",
"cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "4.12.0",
"cornerstone-tools": "4.12.5",
"cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.10.1",
"dicom-parser": "^1.8.3",
@@ -85,7 +87,7 @@
"redux-oidc": "3.1.x",
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0",
"vtk.js": "^11.7.2"
"vtk.js": "^11.14.0"
},
"devDependencies": {
"cypress": "^3.8.0",
@@ -12,6 +12,7 @@
flex: 1;
height: 100%;
transition: var(--sidepanel-transition);
overflow-y: auto;
}
.from-left {
@@ -2,15 +2,20 @@ import ViewportGrid from './ViewportGrid.js';
import { MODULE_TYPES } from '@ohif/core';
import { connect } from 'react-redux';
import { extensionManager } from './../../App.js';
import memoize from 'lodash/memoize';
const mapStateToProps = state => {
const getAvailableViewportModules = memoize(viewportModules => {
const availableViewportModules = {};
const viewportModules = extensionManager.modules[MODULE_TYPES.VIEWPORT];
viewportModules.forEach(moduleDefinition => {
availableViewportModules[moduleDefinition.extensionId] =
moduleDefinition.module;
});
return availableViewportModules;
});
const mapStateToProps = state => {
const viewportModules = extensionManager.modules[MODULE_TYPES.VIEWPORT];
const availableViewportModules = getAvailableViewportModules(viewportModules);
// TODO: Use something like state.plugins.defaultPlugin[MODULE_TYPES.VIEWPORT]
let defaultPlugin;
@@ -1,13 +1,16 @@
import './ViewportGrid.css';
import React from 'react';
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { utils } from '@ohif/core';
//
import ViewportPane from './ViewportPane.js';
import DefaultViewport from './DefaultViewport.js';
import EmptyViewport from './EmptyViewport.js';
const { loadAndCacheDerivedDisplaySets } = utils;
const ViewportGrid = function(props) {
const {
activeViewportIndex,
@@ -30,64 +33,74 @@ const ViewportGrid = function(props) {
return null;
}
const ViewportPanes = layout.viewports.map((layout, viewportIndex) => {
const displaySet = viewportData[viewportIndex];
useEffect(() => {
viewportData.forEach(displaySet => {
loadAndCacheDerivedDisplaySets(displaySet, studies);
});
}, [viewportData]);
if (!displaySet) {
return null;
}
const getViewportPanes = () =>
layout.viewports.map((layout, viewportIndex) => {
const displaySet = viewportData[viewportIndex];
const data = {
displaySet,
studies,
};
if (!displaySet) {
return null;
}
// Use whichever plugin is currently in use in the panel
// unless nothing is specified. If nothing is specified
// and the display set has a plugin specified, use that.
//
// TODO: Change this logic to:
// - Plugins define how capable they are of displaying a SopClass
// - When updating a panel, ensure that the currently enabled plugin
// in the viewport is capable of rendering this display set. If not
// then use the most capable available plugin
const pluginName =
!layout.plugin && displaySet && displaySet.plugin
? displaySet.plugin
: layout.plugin;
const data = {
displaySet,
studies,
};
const ViewportComponent = _getViewportComponent(
data, // Why do we pass this as `ViewportData`, when that's not really what it is?
viewportIndex,
children,
availablePlugins,
pluginName,
defaultPluginName
);
// JAMES TODO:
return (
<ViewportPane
onDrop={({
viewportIndex,
StudyInstanceUID,
displaySetInstanceUID,
}) => {
setViewportData({
viewportIndex,
StudyInstanceUID,
displaySetInstanceUID,
});
}}
viewportIndex={viewportIndex} // Needed by `setViewportData`
className={classNames('viewport-container', {
active: activeViewportIndex === viewportIndex,
})}
key={viewportIndex}
>
{ViewportComponent}
</ViewportPane>
);
});
// Use whichever plugin is currently in use in the panel
// unless nothing is specified. If nothing is specified
// and the display set has a plugin specified, use that.
//
// TODO: Change this logic to:
// - Plugins define how capable they are of displaying a SopClass
// - When updating a panel, ensure that the currently enabled plugin
// in the viewport is capable of rendering this display set. If not
// then use the most capable available plugin
const pluginName =
!layout.plugin && displaySet && displaySet.plugin
? displaySet.plugin
: layout.plugin;
const ViewportComponent = _getViewportComponent(
data, // Why do we pass this as `ViewportData`, when that's not really what it is?
viewportIndex,
children,
availablePlugins,
pluginName,
defaultPluginName
);
return (
<ViewportPane
onDrop={setViewportData}
viewportIndex={viewportIndex} // Needed by `setViewportData`
className={classNames('viewport-container', {
active: activeViewportIndex === viewportIndex,
})}
key={viewportIndex}
>
{ViewportComponent}
</ViewportPane>
);
});
const ViewportPanes = React.useMemo(getViewportPanes, [
layout,
viewportData,
studies,
children,
availablePlugins,
defaultPluginName,
setViewportData,
activeViewportIndex,
]);
return (
<div
@@ -2,6 +2,7 @@ import OHIF from '@ohif/core';
import { connect } from 'react-redux';
import { StudyBrowser } from '@ohif/ui';
import cloneDeep from 'lodash.clonedeep';
import findDisplaySetByUID from './findDisplaySetByUID';
const { setActiveViewportSpecificData } = OHIF.redux.actions;
@@ -38,8 +39,9 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onThumbnailClick: displaySetInstanceUID => {
const displaySet = ownProps.studyMetadata[0].displaySets.find(
ds => ds.displaySetInstanceUID === displaySetInstanceUID
const displaySet = findDisplaySetByUID(
ownProps.studyMetadata,
displaySetInstanceUID
);
dispatch(setActiveViewportSpecificData(displaySet));
@@ -1,10 +1,10 @@
import OHIF from "@ohif/core";
import ViewerMain from "./ViewerMain";
import { connect } from "react-redux";
import OHIF from '@ohif/core';
import ViewerMain from './ViewerMain';
import { connect } from 'react-redux';
const {
setViewportSpecificData,
clearViewportSpecificData
clearViewportSpecificData,
} = OHIF.redux.actions;
const mapStateToProps = state => {
@@ -14,7 +14,7 @@ const mapStateToProps = state => {
activeViewportIndex,
layout,
viewportSpecificData,
viewports: state.viewports
viewports: state.viewports,
};
};
@@ -25,7 +25,7 @@ const mapDispatchToProps = dispatch => {
},
clearViewportSpecificData: () => {
dispatch(clearViewportSpecificData());
}
},
};
};
@@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import ViewerRetrieveStudyData from './ViewerRetrieveStudyData.js';
import OHIF from "@ohif/core";
import OHIF from '@ohif/core';
const { clearViewportSpecificData } = OHIF.redux.actions;
const isActive = a => a.active === true;
@@ -55,8 +55,14 @@ class ToolbarRow extends Component {
activeButtons: [],
};
this.seriesPerStudyCount = [];
this._handleBuiltIn = _handleBuiltIn.bind(this);
this.updateButtonGroups();
}
updateButtonGroups() {
const panelModules = extensionManager.modules[MODULE_TYPES.PANEL];
this.buttonGroups = {
@@ -106,6 +112,29 @@ class ToolbarRow extends Component {
const activeContextsChanged =
prevProps.activeContexts !== this.props.activeContexts;
const prevStudies = prevProps.studies;
const studies = this.props.studies;
const seriesPerStudyCount = this.seriesPerStudyCount;
let studiesUpdated = false;
if (prevStudies.length !== studies.length) {
studiesUpdated = true;
} else {
for (let i = 0; i < studies.length; i++) {
if (studies[i].series.length !== seriesPerStudyCount[i]) {
seriesPerStudyCount[i] = studies[i].series.length;
studiesUpdated = true;
break;
}
}
}
if (studiesUpdated) {
this.updateButtonGroups();
}
if (activeContextsChanged) {
this.setState({
toolbarButtons: _getVisibleToolbarButtons.call(this),
@@ -286,14 +286,15 @@ class Viewer extends Component {
{VisiblePanelLeft ? (
<VisiblePanelLeft
viewports={this.props.viewports}
studies={this.props.studies}
activeIndex={this.props.activeViewportIndex}
/>
) : (
<ConnectedStudyBrowser
studies={this.state.thumbnails}
studyMetadata={this.props.studies}
/>
)}
<ConnectedStudyBrowser
studies={this.state.thumbnails}
studyMetadata={this.props.studies}
/>
)}
</SidePanel>
{/* MAIN */}
@@ -305,7 +306,9 @@ class Viewer extends Component {
<SidePanel from="right" isOpen={this.state.isRightSidePanelOpen}>
{VisiblePanelRight && (
<VisiblePanelRight
isOpen={this.state.isRightSidePanelOpen}
viewports={this.props.viewports}
studies={this.props.studies}
activeIndex={this.props.activeViewportIndex}
/>
)}
@@ -329,7 +332,7 @@ export default withDialog(Viewer);
* @param {Study[]} studies
* @param {DisplaySet[]} studies[].displaySets
*/
const _mapStudiesToThumbnails = function(studies) {
const _mapStudiesToThumbnails = function (studies) {
return studies.map(study => {
const { StudyInstanceUID } = study;
@@ -4,6 +4,10 @@ import { Component } from 'react';
import { ConnectedViewportGrid } from './../components/ViewportGrid/index.js';
import PropTypes from 'prop-types';
import React from 'react';
import memoize from 'lodash/memoize';
import _values from 'lodash/values';
var values = memoize(_values);
class ViewerMain extends Component {
static propTypes = {
@@ -77,6 +81,7 @@ class ViewerMain extends Component {
}
fillEmptyViewportPanes = () => {
// TODO: Here is the entry point for filling viewports on load.
const dirtyViewportPanes = [];
const { layout, viewportSpecificData } = this.props;
const { displaySets } = this.state;
@@ -139,9 +144,7 @@ class ViewerMain extends Component {
render() {
const { viewportSpecificData } = this.props;
const viewportData = viewportSpecificData
? Object.values(viewportSpecificData)
: [];
const viewportData = values(viewportSpecificData);
return (
<div className="ViewerMain">
@@ -0,0 +1,25 @@
/**
* Finds displaySet by UID across all displaySets inside studyMetadata
* @param {Array} studyMetadata
* @param {string} displaySetInstanceUID
*/
export default function findDisplaySetByUID(
studyMetadata,
displaySetInstanceUID
) {
if (!Array.isArray(studyMetadata)) return null;
const allDisplaySets = studyMetadata.reduce((all, current) => {
let currentDisplaySet = [];
if (current && Array.isArray(current.displaySets)) {
currentDisplaySet = current.displaySets;
}
return all.concat(currentDisplaySet);
}, []);
const bySetInstanceUID = ds =>
ds.displaySetInstanceUID === displaySetInstanceUID;
const displaySet = allDisplaySets.find(bySetInstanceUID);
return displaySet || null;
}
@@ -0,0 +1,43 @@
import findDisplaySetByUID from './findDisplaySetByUID';
describe('findDisplaySetByUID', () => {
test('returns null when studyMetadata isnt an array', () => {
const result = findDisplaySetByUID(undefined, 'hello');
expect(result).toBeNull();
});
test('returns null when no match found', () => {
const result = findDisplaySetByUID([], 'no-match');
expect(result).toBeNull();
});
test('it handles missing displaySet arrays', () => {
const expected = '9388-2291-a8fe';
const studyMetadata = [
{ displaySets: null },
{
displaySets: [{ displaySetInstanceUID: expected }],
},
null,
7,
];
const result = findDisplaySetByUID(studyMetadata, expected);
expect(result.displaySetInstanceUID).toBe(expected);
});
test('returns correct displaySet by UID', () => {
const expected = '1234-5678';
const studyMetadata = [
{ displaySets: [{ displaySetInstanceUID: '0011-2239' }] },
{
displaySets: [
{ displaySetInstanceUID: '0392-2211' },
{ displaySetInstanceUID: expected },
],
},
{ displaySets: [{ displaySetInstanceUID: '3384-9933' }] },
];
const result = findDisplaySetByUID(studyMetadata, expected);
expect(result.displaySetInstanceUID).toBe(expected);
});
});
+3
View File
@@ -8,6 +8,7 @@ import 'regenerator-runtime/runtime';
import App from './App.js';
import React from 'react';
import ReactDOM from 'react-dom';
// test
/**
* EXTENSIONS
@@ -24,6 +25,7 @@ import ReactDOM from 'react-dom';
*/
import OHIFVTKExtension from '@ohif/extension-vtk';
import OHIFDicomHtmlExtension from '@ohif/extension-dicom-html';
import OHIFDicomSegmentationExtension from '@ohif/extension-dicom-segmentation';
import OHIFDicomMicroscopyExtension from '@ohif/extension-dicom-microscopy';
import OHIFDicomPDFExtension from '@ohif/extension-dicom-pdf';
@@ -43,6 +45,7 @@ const appProps = {
OHIFDicomHtmlExtension,
OHIFDicomMicroscopyExtension,
OHIFDicomPDFExtension,
OHIFDicomSegmentationExtension,
],
};
+7 -2
View File
@@ -1,6 +1,8 @@
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
export default function initWebWorkers() {
let initialized = false;
export default function initWebWorkers() {
const config = {
maxWebWorkers: Math.max(navigator.hardwareConcurrency - 1, 1),
startWebWorkersOnDemand: true,
@@ -13,5 +15,8 @@ export default function initWebWorkers() {
},
};
cornerstoneWADOImageLoader.webWorkerManager.initialize(config);
if (!initialized) {
cornerstoneWADOImageLoader.webWorkerManager.initialize(config);
initialized = true;
}
}
@@ -10,3 +10,14 @@ describe('initWebWorkers', () => {
).toHaveBeenCalled();
});
});
describe('initWebWorkers', () => {
it("initializes cornerstoneWADOImageLoader's web workers only once", () => {
initWebWorkers();
initWebWorkers();
expect(
cornerstoneWADOImageLoader.webWorkerManager.initialize
).toHaveBeenCalledTimes(1);
});
});
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff. Show more