Compare commits

..
Author SHA1 Message Date
ohif-bot d57158b59d chore(release): publish [skip ci]
- @ohif/extension-dicom-segmentation@0.1.1
 - @ohif/extension-vtk@1.3.6
 - @ohif/core@2.6.5
 - @ohif/viewer@3.8.13
2020-04-01 03:55:23 +00:00
Igor Octaviano 4a7ce1c093 fix: segmentation not loading (#1566)
* Fix bug

* Refactor the state of the component RTPanel
2020-03-31 23:54:02 -04:00
ohif-bot 333677c40e chore(release): publish [skip ci]
- @ohif/extension-dicom-microscopy@0.50.8
 - @ohif/viewer@3.8.12
2020-03-31 13:30:26 +00:00
James Petts b459b894bd Sm fix (#1585)
* Fix broken SM viewport.

* fix: 🐛 Fix broken SM viewport
2020-03-31 15:28:11 +02:00
Andrey Fedorov c5a4860209 update stale.yml to not close IDC related issues (#1573) 2020-03-29 16:10:11 -04:00
ohif-bot 4f79bda7c9 chore(release): publish [skip ci]
- @ohif/viewer@3.8.11
2020-03-26 03:28:31 +00:00
7d22bb7d5a fix: #1312 Cine dialog remains on screen (#1540)
Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com>
Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-03-25 23:25:04 -04:00
ohif-bot 36822cc6ce chore(release): publish [skip ci]
- @ohif/viewer@3.8.10
2020-03-26 01:47:51 +00:00
Mirna Silva e676f0b5b5 test: E2E tests for W/L Presets (#1557)
* E2E tests for W/L Presets

* CI Fix
2020-03-25 21:46:28 -04:00
ohif-bot 57de66a65f chore(release): publish [skip ci]
- @ohif/viewer@3.8.9
2020-03-25 17:55:57 +00:00
Kedarandkedar.netelixir 99022f2bac fix: Load measurement in active viewport. (#1558)
Possible solution for request https://github.com/OHIF/Viewers/issues/1522 ?

Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com>
2020-03-25 13:53:47 -04:00
ohif-bot 47b1e2837f chore(release): publish [skip ci]
- @ohif/extension-vtk@1.3.5
 - @ohif/core@2.6.4
 - @ohif/viewer@3.8.8
2020-03-25 17:04:29 +00:00
Kedarandkedar.netelixir d604ebaffd fix: disable autoFreeze of immer, even in dev mode (#1560)
Co-authored-by: kedar.netelixir <kedar.choudhary@netelixir.com>
2020-03-25 12:56:46 -04:00
Erik ZieglerandDanny Brown c1a0d3c662 fix: Add support for single entries in SequenceOfUltrasoundRegions. M… (#1559)
* fix: Add support for single entries in SequenceOfUltrasoundRegions. More than one region will need lower-level changes

* Update platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-03-25 12:55:39 -04:00
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
36 changed files with 1042 additions and 377 deletions

No files matched your search

+3
View File
@@ -18,6 +18,9 @@ exemptLabels:
- 'Triage :white_flag:'
- 'Extension: Discussion'
- 'Announcement 🎉'
- 'IDC:priority'
- 'IDC:candidate'
- 'IDC:collaboration'
# Label to use when marking an issue as stale
staleLabel: 'Stale :baguette_bread:'
# Comment to post when marking an issue as stale. Set to `false` to disable
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.50.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.7...@ohif/extension-dicom-microscopy@0.50.8) (2020-03-31)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
## [0.50.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.6...@ohif/extension-dicom-microscopy@0.50.7) (2020-03-09)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-microscopy",
"version": "0.50.7",
"version": "0.50.8",
"description": "OHIF extension for Dicom Microscopy",
"author": "OHIF",
"license": "MIT",
@@ -34,11 +34,12 @@ class DicomMicroscopyViewport extends Component {
.then(instances => {
const promises = [];
for (let i = 0; i < instances.length; i++) {
const sopInstanceUid = instances[i]['00080018']['Value'][0];
const sopInstanceUID = instances[i]['00080018']['Value'][0];
const retrieveInstanceOptions = {
studyInstanceUID: displaySet.StudyInstanceUID,
seriesInstanceUID: displaySet.SeriesInstanceUID,
sopInstanceUid,
sopInstanceUID,
};
const promise = dicomWebClient
@@ -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.
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.1.0...@ohif/extension-dicom-segmentation@0.1.1) (2020-04-01)
### Bug Fixes
* segmentation not loading ([#1566](https://github.com/OHIF/Viewers/issues/1566)) ([4a7ce1c](https://github.com/OHIF/Viewers/commit/4a7ce1c09324d74c61048393e3a2427757e4001a))
# 0.1.0 (2020-03-13)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-segmentation",
"version": "0.1.0",
"version": "0.1.1",
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF",
"license": "MIT",
@@ -14,7 +14,7 @@ const OHIFDicomSegSopClassHandler = {
id: 'OHIFDicomSegSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs,
getDisplaySetFromSeries: function(
getDisplaySetFromSeries: function (
series,
study,
dicomWebClient,
@@ -53,7 +53,7 @@ const OHIFDicomSegSopClassHandler = {
SeriesDescription,
};
segDisplaySet.load = function(referencedDisplaySet, studies) {
segDisplaySet.load = function (referencedDisplaySet, studies) {
return loadSegmentation(
segDisplaySet,
referencedDisplaySet,
@@ -39,50 +39,26 @@ 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 { configuration } = cornerstoneTools.getModule('segmentation');
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]
);
const [state, setState] = useState({
brushRadius: DEFAULT_BRUSH_RADIUS,
brushColor: 'rgba(221, 85, 85, 1)', /* TODO: We shouldn't hardcode this color, in the future the SEG may set the colorLUT to whatever it wants. */
selectedSegment: null,
selectedSegmentation: null,
showSegSettings: false,
brushStackState: null,
labelmapList: [],
segmentList: []
});
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]);
log.warn('Segmentation Panel: labelmap modified', event);
const module = cornerstoneTools.getModule('segmentation');
const activeViewport = viewports[activeIndex];
const firstImageId = studyMetadata.getFirstImageId(activeViewport.displaySetInstanceUID);
updateState('brushStackState', module.state.series[firstImageId]);
};
/*
@@ -107,27 +83,43 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
};
});
if (!brushStackState) {
return null;
}
useEffect(() => {
const module = cornerstoneTools.getModule('segmentation');
const activeViewport = viewports[activeIndex];
const studyMetadata = studyMetadataManager.get(activeViewport.StudyInstanceUID);
const firstImageId = studyMetadata.getFirstImageId(activeViewport.displaySetInstanceUID);
const brushStackState = module.state.series[firstImageId];
const labelmap3D =
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
if (brushStackState) {
const labelmap3D = brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
const labelmapList = getLabelmapList(brushStackState, firstImageId, activeViewport);
const segmentList = getSegmentList(labelmap3D, firstImageId);
setState(state => ({
...state,
brushStackState,
selectedSegmentation: brushStackState.activeLabelmapIndex,
labelmapList,
segmentList
}));
} else {
setState(state => ({
...state,
labelmapList: [],
segmentList: [],
}));
}
}, [studies, viewports, activeIndex]);
/*
* 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
*/
/* Handle open/closed panel behaviour */
useEffect(() => {
updateState('showSegSettings', state.showSegSettings && !isOpen);
}, [isOpen]);
const getLabelmapList = () => {
const getLabelmapList = (brushStackState, firstImageId, activeViewport) => {
/* Get list of SEG labelmaps specific to active viewport (reference series) */
const referencedSegDisplaysets = _getReferencedSegDisplaysets(
StudyInstanceUID,
SeriesInstanceUID
activeViewport.StudyInstanceUID,
activeViewport.SeriesInstanceUID
);
return referencedSegDisplaysets.map((displaySet, index) => {
@@ -148,23 +140,19 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
description: displayDate,
onClick: async () => {
const activatedLabelmapIndex = await _setActiveLabelmap(
viewport,
activeViewport,
studies,
displaySet,
firstImageId,
brushStackState.activeLabelmapIndex
);
setSelectedSegmentation(activatedLabelmapIndex);
updateState('selectedSegmentation', activatedLabelmapIndex);
},
};
});
};
const labelmapList = getLabelmapList();
const segmentList = [];
if (labelmap3D) {
const getSegmentList = (labelmap3D, firstImageId) => {
/*
* Newly created segments have no `meta`
* So we instead build a list of all segment indexes in use
@@ -186,10 +174,12 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
}, [])
.sort((a, b) => a - b);
const module = cornerstoneTools.getModule('segmentation');
const colorLutTable =
segmentationModule.state.colorLutTables[labelmap3D.colorLUTIndex];
module.state.colorLutTables[labelmap3D.colorLUTIndex];
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
const segmentList = [];
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
const segmentIndex = uniqueSegmentIndexes[i];
@@ -207,14 +197,14 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
}
}
const sameSegment = selectedSegment === segmentNumber;
const sameSegment = state.selectedSegment === segmentNumber;
const setCurrentSelectedSegment = () => {
_setActiveSegment(
firstImageId,
segmentNumber,
labelmap3D.activeSegmentIndex
);
setSelectedSegment(sameSegment ? null : segmentNumber);
updateState('selectedSegment', sameSegment ? null : segmentNumber);
};
segmentList.push(
@@ -229,20 +219,27 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
);
}
return segmentList;
/*
* 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 updateState = (field, value) => {
setState(state => ({ ...state, [field]: value }));
};
const updateBrushSize = evt => {
const updatedRadius = Number(evt.target.value);
if (updatedRadius !== brushRadius) {
setBrushRadius(updatedRadius);
segmentationModule.setters.radius(updatedRadius);
updateState('brushRadius', updatedRadius);
const module = cornerstoneTools.getModule('segmentation');
module.setters.radius(updatedRadius);
}
};
@@ -251,29 +248,30 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
if (labelmap3D.activeSegmentIndex > 1) {
labelmap3D.activeSegmentIndex--;
}
setSelectedSegment(labelmap3D.activeSegmentIndex);
updateState('selectedSegment', labelmap3D.activeSegmentIndex);
updateActiveSegmentColor();
};
const incrementSegment = event => {
event.preventDefault();
labelmap3D.activeSegmentIndex++;
setSelectedSegment(labelmap3D.activeSegmentIndex);
updateState('selectedSegment', labelmap3D.activeSegmentIndex);
updateActiveSegmentColor();
};
const updateActiveSegmentColor = () => {
const color = getActiveSegmentColor();
setBrushColor(color);
updateState('brushColor', color);
};
const getActiveSegmentColor = () => {
if (!brushStackState) {
if (!state.brushStackState) {
return 'rgba(255, 255, 255, 1)';
}
const module = cornerstoneTools.getModule('segmentation');
const colorLutTable =
segmentationModule.state.colorLutTables[labelmap3D.colorLUTIndex];
module.state.colorLutTables[labelmap3D.colorLUTIndex];
const color = colorLutTable[labelmap3D.activeSegmentIndex];
return `rgba(${color.join(',')})`;
@@ -293,11 +291,11 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
refreshViewport();
};
if (showSegSettings) {
if (state.showSegSettings) {
return (
<SegmentationSettings
configuration={configuration}
onBack={() => setShowSegSettings(false)}
onBack={() => updateState('showSegSettings', false)}
onChange={updateConfiguration}
/>
);
@@ -309,13 +307,13 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
name="cog"
width="25px"
height="25px"
onClick={() => setShowSegSettings(true)}
onClick={() => updateState('showSegSettings', true)}
/>
{false && (
<form className="selector-form">
<BrushColorSelector
defaultColor={brushColor}
index={labelmap3D.activeSegmentIndex}
index={state.selectedSegment}
onNext={incrementSegment}
onPrev={decrementSegment}
/>
@@ -331,17 +329,17 @@ const SegmentationPanel = ({ studies, viewports, activeIndex, isOpen }) => {
<div className="segmentations">
<SegmentationSelect
value={
labelmapList.find(i => i.value === selectedSegmentation) || null
state.labelmapList.find(i => i.value === state.selectedSegmentation) || null
}
formatOptionLabel={SegmentationItem}
options={labelmapList}
options={state.labelmapList}
/>
</div>
<ScrollableArea>
<TableList
customHeader={<SegmentsHeader count={segmentList.length} />}
customHeader={<SegmentsHeader count={state.segmentList.length} />}
>
{segmentList}
{state.segmentList}
</TableList>
</ScrollableArea>
</div>
@@ -420,7 +418,7 @@ const _setActiveLabelmap = async (
) => {
if (displaySet.labelmapIndex === activeLabelmapIndex) {
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
return;
return displaySet.labelmapIndex;
}
if (!displaySet.isLoaded) {
@@ -431,7 +429,6 @@ const _setActiveLabelmap = async (
const { state } = cornerstoneTools.getModule('segmentation');
const brushStackState = state.series[firstImageId];
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
refreshViewport();
+40
View File
@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.5...@ohif/extension-vtk@1.3.6) (2020-04-01)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.3.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.3.4...@ohif/extension-vtk@1.3.5) (2020-03-25)
**Note:** Version bump only for package @ohif/extension-vtk
## [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
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "1.3.1",
"version": "1.3.6",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@@ -53,7 +53,7 @@
"react-vtkjs-viewport": "^0.8.3"
},
"devDependencies": {
"@ohif/core": "^2.6.0",
"@ohif/core": "^2.6.5",
"@ohif/ui": "^1.4.1",
"cornerstone-tools": "4.12.5",
"cornerstone-wado-image-loader": "^3.0.0",
+56
View File
@@ -3,6 +3,62 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.6.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.6.4...@ohif/core@2.6.5) (2020-04-01)
### Bug Fixes
* segmentation not loading ([#1566](https://github.com/OHIF/Viewers/issues/1566)) ([4a7ce1c](https://github.com/OHIF/Viewers/commit/4a7ce1c09324d74c61048393e3a2427757e4001a))
## [2.6.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.6.3...@ohif/core@2.6.4) (2020-03-25)
### Bug Fixes
* Add support for single entries in SequenceOfUltrasoundRegions. M… ([#1559](https://github.com/OHIF/Viewers/issues/1559)) ([c1a0d3c](https://github.com/OHIF/Viewers/commit/c1a0d3c662d143b62dfbf1c01f6ce394af3756ca))
* disable autoFreeze of immer, even in dev mode ([#1560](https://github.com/OHIF/Viewers/issues/1560)) ([d604eba](https://github.com/OHIF/Viewers/commit/d604ebaffd93f688eadd0081e402f27074dd226b))
## [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)
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "2.6.0",
"version": "2.6.5",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
@@ -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",
+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:
@@ -78,7 +78,7 @@ export class StudyMetadata extends Metadata {
Object.defineProperty(this, 'studyInstanceUID', {
configurable: false,
enumerable: false,
get: function() {
get: function () {
return this.getStudyInstanceUID();
},
});
@@ -208,20 +208,21 @@ export class StudyMetadata extends Metadata {
/**
* 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.Modality
* @param {object} filter.referencedSeriesInstanceUID
* @param {object} filter.referencedFrameOfReferenceUID
* @return {Array} filtered derived display sets
*/
getDerivedDatasets(filter) {
const {
modality,
Modality,
referencedSeriesInstanceUID,
referencedFrameOfReferenceUID,
} = filter;
let filteredDerivedDisplaySets = this._derivedDisplaySets;
if (modality) {
if (Modality) {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet => displaySet.Modality === Modality
);
+22 -20
View File
@@ -1,5 +1,5 @@
import cloneDeep from 'lodash.clonedeep';
import merge from 'lodash.merge';
import produce, { setAutoFreeze } from 'immer';
import {
CLEAR_VIEWPORT,
@@ -11,6 +11,8 @@ import {
SET_VIEWPORT_LAYOUT_AND_DATA,
} from './../constants/ActionTypes.js';
setAutoFreeze(false);
export const DEFAULT_STATE = {
numRows: 1,
numColumns: 1,
@@ -93,12 +95,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 +166,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;
}
});
}
/**
@@ -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,130 @@
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,
SequenceOfUltrasoundRegions,
} = instance;
const isProjection = projectionRadiographSOPClassUIDs.includes(SOPClassUID);
const TYPES = {
NOT_APPLICABLE: 'NOT_APPLICABLE',
UNKNOWN: 'UNKNOWN',
CALIBRATED: 'CALIBRATED',
DETECTOR: 'DETECTOR',
};
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,
};
} else if (
SequenceOfUltrasoundRegions &&
typeof SequenceOfUltrasoundRegions === 'object'
) {
const { PhysicalDeltaX, PhysicalDeltaY } = SequenceOfUltrasoundRegions;
const USPixelSpacing = [PhysicalDeltaX * 10, PhysicalDeltaY * 10];
return {
PixelSpacing: USPixelSpacing,
};
} else if (
SequenceOfUltrasoundRegions &&
Array.isArray(SequenceOfUltrasoundRegions) &&
SequenceOfUltrasoundRegions.length > 1
) {
log.warn(
'Sequence of Ultrasound Regions > one entry. This is not yet implemented, all measurements will be shown in pixels.'
);
} else 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,
};
}
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;
}
+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;
}
+95
View File
@@ -3,6 +3,101 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.8.13](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.12...@ohif/viewer@3.8.13) (2020-04-01)
### Bug Fixes
* segmentation not loading ([#1566](https://github.com/OHIF/Viewers/issues/1566)) ([4a7ce1c](https://github.com/OHIF/Viewers/commit/4a7ce1c09324d74c61048393e3a2427757e4001a))
## [3.8.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.11...@ohif/viewer@3.8.12) (2020-03-31)
**Note:** Version bump only for package @ohif/viewer
## [3.8.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.10...@ohif/viewer@3.8.11) (2020-03-26)
### Bug Fixes
* [#1312](https://github.com/OHIF/Viewers/issues/1312) Cine dialog remains on screen ([#1540](https://github.com/OHIF/Viewers/issues/1540)) ([7d22bb7](https://github.com/OHIF/Viewers/commit/7d22bb7d5a8590cffc169725c93942f758fe13a0))
## [3.8.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.9...@ohif/viewer@3.8.10) (2020-03-26)
**Note:** Version bump only for package @ohif/viewer
## [3.8.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.8...@ohif/viewer@3.8.9) (2020-03-25)
### Bug Fixes
* Load measurement in active viewport. ([#1558](https://github.com/OHIF/Viewers/issues/1558)) ([99022f2](https://github.com/OHIF/Viewers/commit/99022f2bac752f3cd1cedb61e222b8d411e158c8))
## [3.8.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.7...@ohif/viewer@3.8.8) (2020-03-25)
**Note:** Version bump only for package @ohif/viewer
## [3.8.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.6...@ohif/viewer@3.8.7) (2020-03-24)
**Note:** Version bump only for package @ohif/viewer
## [3.8.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.5...@ohif/viewer@3.8.6) (2020-03-24)
**Note:** Version bump only for package @ohif/viewer
## [3.8.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.4...@ohif/viewer@3.8.5) (2020-03-23)
### Bug Fixes
* avoid-wasteful-renders ([#1544](https://github.com/OHIF/Viewers/issues/1544)) ([e41d339](https://github.com/OHIF/Viewers/commit/e41d339f5faef6b93700bc860f37f29f32ad5ed6))
## [3.8.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.3...@ohif/viewer@3.8.4) (2020-03-19)
### Bug Fixes
* Only permit web workers to be initialized once. ([#1535](https://github.com/OHIF/Viewers/issues/1535)) ([9feadd3](https://github.com/OHIF/Viewers/commit/9feadd3c6d71c1c48f7825d024ccf95d5d82606d))
## [3.8.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.8.2...@ohif/viewer@3.8.3) (2020-03-17)
**Note:** Version bump only for package @ohif/viewer
@@ -7,15 +7,22 @@ describe('OHIF User Preferences', () => {
beforeEach(() => {
// Open User Preferences modal
cy.openPreferences();
cy.initPreferencesModalAliases();
});
it('checks displayed information on User Preferences modal', function() {
cy.initPreferencesModalAliases();
//Check Title
cy.get('@preferencesModal').should('contain.text', 'User Preferences');
//Check tabs
cy.get('@userPreferencesHotkeysTab')
.should('have.text', 'Hotkeys')
.and('have.class', 'active');
cy.get('@userPreferencesGeneralTab').should('have.text', 'General');
cy.get('@userPreferencesWindowLevelTab').should(
'have.text',
'Window Level'
);
//Check buttons
cy.get('@restoreBtn')
.scrollIntoView()
.should('have.text', 'Reset to Defaults');
@@ -26,8 +33,7 @@ describe('OHIF User Preferences', () => {
});
it('checks translation by selecting Spanish language', function() {
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.get('@userPreferencesGeneralTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Language dropdown should be displayed
cy.get('#language-select').should('be.visible');
@@ -92,16 +98,22 @@ describe('OHIF User Preferences', () => {
cy.openPreferences();
// Go to general tab
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
cy.get('@restoreBtn')
.scrollIntoView()
.click();
// Save
cy.get('@saveBtn')
.scrollIntoView()
.click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon').click({ force: true });
}
// click on save button
cy.get('@saveBtn')
.scrollIntoView()
.click();
});
// Header should be in "English (USA)"
cy.get('.research-use')
@@ -124,30 +136,54 @@ describe('OHIF User Preferences', () => {
cy.get('[data-cy="options-menu"]').click();
});
it('checks if W/L Preferences table is being displayed in the Window Level tab', function() {
//Navigate to Window Level tab
cy.selectPreferencesTab('@userPreferencesWindowLevelTab');
//Check table header
cy.get('.wlRow.header')
.should('contains.text', 'Preset')
.and('contains.text', 'Description')
.and('contains.text', 'Window')
.and('contains.text', 'Level');
//Check table has more than 1 row (more than header)
cy.get('.wlRow')
.its('length')
.should('be.greaterThan', 1);
});
it('checks if Preferences set in Study List Page will be consistent on Viewer Page', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.get('@userPreferencesHotkeysTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set new hotkey for 'Rotate Right' function
cy.setNewHotkeyShortcutOnUserPreferencesModal('Rotate Right', '{shift}Q');
// Save new hotkey
cy.get('@saveBtn')
.scrollIntoView()
.click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon').click({ force: true });
}
// click on save button
cy.get('@saveBtn')
.scrollIntoView()
.click();
});
// Open User Preferences modal again
cy.openPreferences();
// Go to General tab
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Set language to Spanish
cy.setLanguage('Spanish');
// Go to Study Viewer page
cy.openStudyInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
cy.waitDicomImage();
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
cy.initCommonElementsAliases();
@@ -178,7 +214,9 @@ describe('OHIF User Preferences', () => {
context('Study Viewer Page', function() {
before(() => {
cy.openStudyInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
@@ -186,29 +224,40 @@ describe('OHIF User Preferences', () => {
cy.initCommonElementsAliases();
cy.resetViewport();
cy.resetUserHoktkeyPreferences();
cy.resetUserHotkeyPreferences();
cy.resetUserGeneralPreferences();
// Open User Preferences modal
cy.openPreferences();
});
afterEach(() => {
// Close User Preferences Modal (if displayed)
cy.get('body').then(body => {
if (body.find('.OHIFModal__header').length > 0) {
cy.get('[data-cy="close-button"]').click({ force: true });
}
});
});
it('checks displayed information on User Preferences modal', function() {
cy.get('@preferencesModal').should('contain.text', 'User Preferences');
cy.get('@userPreferencesHotkeysTab')
.should('have.text', 'Hotkeys')
.and('have.class', 'active');
cy.get('@userPreferencesGeneralTab').should('have.text', 'General');
cy.get('@userPreferencesWindowLevelTab').should(
'have.text',
'Window Level'
);
cy.get('@restoreBtn')
.scrollIntoView()
.should('have.text', 'Reset to Defaults');
cy.get('@cancelBtn').should('have.text', 'Cancel');
cy.get('@saveBtn').should('have.text', 'Save');
cy.get('[data-cy="close-button"]').click(); //close User Preferences modal
});
it('checks translation by selecting Spanish language', function() {
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.get('@userPreferencesGeneralTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Language dropdown should be displayed
cy.get('#language-select').should('be.visible');
@@ -232,9 +281,6 @@ describe('OHIF User Preferences', () => {
cy.get('[data-cy="dd-item-menu"]')
.last()
.should('contain.text', 'Preferencias');
// Close Options menu
cy.get('[data-cy="options-menu"]').click();
});
it('checks if user can cancel the language selection and application will be in "English (USA)"', function() {
@@ -260,13 +306,10 @@ describe('OHIF User Preferences', () => {
cy.get('[data-cy="dd-item-menu"]')
.last()
.should('contain.text', 'Preferences');
// Close Options menu
cy.get('[data-cy="options-menu"]').click();
});
it('checks if user can restore to default the language selection and application will be in "English (USA)', function() {
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.get('@userPreferencesGeneralTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Language dropdown should be displayed
cy.get('#language-select').should('be.visible');
@@ -278,12 +321,14 @@ describe('OHIF User Preferences', () => {
cy.openPreferences();
// Go to general tab
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// click on restore button
cy.get('@restoreBtn')
.scrollIntoView()
.click();
// click on save button
cy.get('@saveBtn')
.scrollIntoView()
.click();
@@ -303,15 +348,11 @@ describe('OHIF User Preferences', () => {
cy.get('[data-cy="dd-item-menu"]')
.last()
.should('contain.text', 'Preferences');
// Close Options menu
cy.get('[data-cy="options-menu"]').click();
});
it('checks new hotkeys for "Rotate Right" and "Rotate Left"', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.get('@userPreferencesHotkeysTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set new hotkey for 'Rotate Right' function
cy.setNewHotkeyShortcutOnUserPreferencesModal(
@@ -324,10 +365,16 @@ describe('OHIF User Preferences', () => {
'{shift}{leftarrow}'
);
//Save new hotkeys
cy.get('@saveBtn')
.scrollIntoView()
.click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon').click({ force: true });
}
// click on save button
cy.get('@saveBtn')
.scrollIntoView()
.click();
});
//Rotate Right with new Hotkey
cy.get('body').type('{shift}{rightarrow}');
@@ -340,8 +387,8 @@ describe('OHIF User Preferences', () => {
it('checks new hotkeys for "Next" and "Previous" Image on Viewport', function() {
// Update hotkeys for 'Next/Previous Viewport'
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.get('@userPreferencesHotkeysTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
cy.setNewHotkeyShortcutOnUserPreferencesModal(
'Next Viewport',
'{shift}{rightarrow}'
@@ -350,9 +397,16 @@ describe('OHIF User Preferences', () => {
'Previous Viewport',
'{shift}{leftarrow}'
);
cy.get('@saveBtn')
.scrollIntoView()
.click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon').click({ force: true });
}
// click on save button
cy.get('@saveBtn')
.scrollIntoView()
.click();
});
// Set 3 viewports layout
cy.setLayout(3, 1);
@@ -391,7 +445,7 @@ describe('OHIF User Preferences', () => {
it('checks error message when duplicated hotkeys are inserted', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set duplicated hotkey for 'Rotate Right' function
cy.setNewHotkeyShortcutOnUserPreferencesModal('Rotate Right', '{i}');
@@ -404,15 +458,11 @@ describe('OHIF User Preferences', () => {
.as('errorMsg')
.should('have.text', '"Invert" is already using the "i" shortcut.');
});
//Cancel hotkeys
cy.get('@cancelBtn')
.scrollIntoView()
.click();
});
it('checks error message when invalid hotkey is inserted', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set invalid hotkey for 'Rotate Right' function
cy.setNewHotkeyShortcutOnUserPreferencesModal('Rotate Right', '{ctrl}Z');
@@ -425,16 +475,11 @@ describe('OHIF User Preferences', () => {
.as('errorMsg')
.should('have.text', '"ctrl+z" shortcut combination is not allowed');
});
//Cancel hotkeys
cy.get('@cancelBtn')
.scrollIntoView()
.click();
});
it('checks error message when only modifier keys are inserted', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set invalid modifier key: ctrl
cy.setNewHotkeyShortcutOnUserPreferencesModal('Zoom Out', '{ctrl}');
@@ -465,16 +510,11 @@ describe('OHIF User Preferences', () => {
'have.text',
"It's not possible to define only modifier keys (ctrl, alt and shift) as a shortcut"
);
//Cancel hotkeys
cy.get('@cancelBtn')
.scrollIntoView()
.click();
});
it('checks if user can cancel changes made on User Preferences Hotkeys tab', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set new hotkey for 'Rotate Right' function
cy.setNewHotkeyShortcutOnUserPreferencesModal(
@@ -482,10 +522,16 @@ describe('OHIF User Preferences', () => {
'{ctrl}{shift}S'
);
//Cancel hotkeys
cy.get('@cancelBtn')
.scrollIntoView()
.click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon').click({ force: true });
}
//Cancel hotkeys
cy.get('@cancelBtn')
.scrollIntoView()
.click();
});
// Open User Preferences modal again
cy.openPreferences();
@@ -497,12 +543,11 @@ describe('OHIF User Preferences', () => {
.find('input')
.should('have.value', 'r');
});
cy.get('[data-cy="close-button"]').click();
});
it('checks if user can reset to default values on User Preferences Hotkeys tab', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set new hotkey for 'Rotate Right' function
cy.setNewHotkeyShortcutOnUserPreferencesModal(
@@ -510,7 +555,7 @@ describe('OHIF User Preferences', () => {
'{ctrl}{shift}S'
);
//Save hotkeys
// click on save button
cy.get('@saveBtn')
.scrollIntoView()
.click();
@@ -523,9 +568,6 @@ describe('OHIF User Preferences', () => {
.scrollIntoView()
.click();
// Open User Preferences modal again
cy.openPreferences();
//Check that hotkey for 'Rotate Right' function was not changed
cy.get('.HotkeysPreferences').within(() => {
cy.contains('Rotate Right') // label we're looking for
@@ -533,7 +575,183 @@ describe('OHIF User Preferences', () => {
.find('input')
.should('have.value', 'r');
});
});
});
context('W/L Preset Preferences', function() {
before(() => {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
cy.expectMinimumThumbnails(5);
});
beforeEach(() => {
cy.initCommonElementsAliases();
// Open User Preferences modal
cy.openPreferences();
// Navigate to Window Level tab
cy.selectPreferencesTab('@userPreferencesWindowLevelTab');
});
it('checks if W/L Preferences table is being displayed in the Window Level tab', function() {
//Check table header
cy.get('.wlRow.header')
.should('contains.text', 'Preset')
.and('contains.text', 'Description')
.and('contains.text', 'Window')
.and('contains.text', 'Level');
//Check table has more than 1 row (more than header)
cy.get('.wlRow')
.its('length')
.should('be.greaterThan', 1);
});
// //TODO: Test blocked by issue #1551: https://github.com/OHIF/Viewers/issues/1551
// it('checks if user can add a new W/L preset', function() {
// let description = ':nth-child(8) > .description > .preferencesInput';
// let window = ':nth-child(8) > .window > .preferencesInput';
// let level = ':nth-child(8) > .level > .preferencesInput';
// let new_window_value = 150;
// let new_level_value = -600;
// // Check existing preset values
// cy.get(description).should('have.value', '');
// cy.get(window).should('have.value', '');
// cy.get(level).should('have.value', '');
// // Set new preset value
// cy.setWindowLevelPreset(
// 7,
// 'New Description',
// new_window_value,
// new_level_value
// );
// cy.get('@saveBtn').click();
// // Open User Preferences modal
// cy.openPreferences();
// // Navigate to Window Level tab
// cy.selectPreferencesTab('@userPreferencesWindowLevelTab');
// // Check recently added preset values
// cy.get(description).should('have.value', 'New Description');
// cy.get(window).should('have.value', new_window_value);
// cy.get(level).should('have.value', new_level_value);
// // Close User Preferences modal
// cy.get('[data-cy="close-button"]').click();
// // Check if new hotkey preset is working on viewport
// cy.get('body').type('8');
// cy.get('@viewportInfoBottomRight').should(
// 'contains.text',
// 'W: ' + new_window_value + ' L: ' + new_level_value
// );
// });
it('checks if user can remove an existing W/L preset', function() {
let description = ':nth-child(3) > .description > .preferencesInput';
let window = ':nth-child(3) > .window > .preferencesInput';
let level = ':nth-child(3) > .level > .preferencesInput';
// Check existing preset values
cy.get(description)
.should('not.have.value', '')
.clear();
cy.get(window)
.should('not.have.value', '')
.clear();
cy.get(level)
.should('not.have.value', '')
.clear();
// Save changes
cy.get('@saveBtn').click();
// Open User Preferences modal
cy.openPreferences();
// Navigate to Window Level tab
cy.selectPreferencesTab('@userPreferencesWindowLevelTab');
// Check recently added preset values
cy.get(description).should('have.value', '');
cy.get(window).should('have.value', '');
cy.get(level).should('have.value', '');
// Close User Preferences modal
cy.get('[data-cy="close-button"]').click();
});
// //TODO: Test blocked by issue #1551: https://github.com/OHIF/Viewers/issues/1551
// it('checks if user can edit an existing W/L preset', function() {
// let description = ':nth-child(2) > .description > .preferencesInput';
// let window = ':nth-child(2) > .window > .preferencesInput';
// let level = ':nth-child(2) > .level > .preferencesInput';
// // Check existing preset values
// cy.get(description).should('have.value', 'Soft tissue');
// cy.get(window).should('have.value', '550');
// cy.get(level).should('have.value', '40');
// // Set new preset value
// cy.setWindowLevelPreset(1, 'Soft tissue New Description', 1220, 333);
// cy.get('@saveBtn').click();
// // Open User Preferences modal
// cy.openPreferences();
// // Navigate to Window Level tab
// cy.selectPreferencesTab('@userPreferencesWindowLevelTab');
// // Check recently added preset values
// cy.get(description).should('have.value', 'Soft tissue New Description');
// cy.get(window).should('have.value', '1220');
// cy.get(level).should('have.value', '333');
// });
it('checks if user can change the W/L by triggering different hotkeys with W/L presets', function() {
// Close User Preferences modal
cy.get('[data-cy="close-button"]').click();
// Check if hotkey preset is working on viewport
cy.get('body').type('3');
cy.get('@viewportInfoBottomRight').should(
'contains.text',
'W: 150 L: 90'
);
// Check if hotkey preset is working on viewport
cy.get('body').type('4');
cy.get('@viewportInfoBottomRight').should(
'contains.text',
'W: 2500 L: 480'
);
});
it('checks if user can change the W/L by triggering different hotkeys with W/L presets on multiple viewports', function() {
// Close User Preferences modal
cy.get('[data-cy="close-button"]').click();
// Set 3 viewports layout
cy.setLayout(3, 1);
cy.waitViewportImageLoading();
// Check if hotkey preset is working on viewport
cy.get('body').type('3');
cy.get('@viewportInfoBottomRight').should(
'contains.text',
'W: 150 L: 90'
);
// Overlay information from 2nd viewport
let second_viewport_overlay =
'div:nth-child(2) > div > div.viewport-element > div.ViewportOverlay > div.bottom-right.overlay-element > div';
// Shift active viewport to Viewport #2
cy.get('body').type('{rightarrow}');
// Check if hotkey preset is working on viewport #2
cy.get('body').type('4');
cy.get(second_viewport_overlay).should('contains.text', 'W: 2500 L: 480');
// Set 1 viewport layout
cy.setLayout(1, 1);
});
});
});
@@ -4,7 +4,7 @@ describe('Visual Regression - OHIF PDF Extension', () => {
'1.2.826.0.13854362241694438965858641723883466450351448'
);
cy.expectMinimumThumbnails(5);
});v
});
it('drags and drop a PDF thumbnail into viewport', () => {
cy.get('[data-cy="thumbnail-list"]')
@@ -7,10 +7,11 @@ describe('Visual Regression - OHIF User Preferences', () => {
beforeEach(() => {
// Open User Preferences modal
cy.openPreferences();
cy.initPreferencesModalAliases();
});
it('checks displayed information on User Preferences modal', function() {
// Go go hotkeys tab
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
cy.get('@restoreBtn').scrollIntoView();
// Visual comparison
@@ -21,8 +22,7 @@ describe('Visual Regression - OHIF User Preferences', () => {
});
it('checks translation by selecting Spanish language', function() {
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.get('@userPreferencesGeneralTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Language dropdown should be displayed
cy.get('#language-select').should('be.visible');
@@ -59,12 +59,14 @@ describe('Visual Regression - OHIF User Preferences', () => {
cy.initCommonElementsAliases();
cy.resetViewport();
cy.resetUserHoktkeyPreferences();
cy.resetUserHotkeyPreferences();
// Open User Preferences modal
cy.openPreferences();
});
it('checks displayed information on User Preferences modal', function() {
// Go go hotkeys tab
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
cy.get('@restoreBtn').scrollIntoView();
// Visual comparison
@@ -74,9 +76,18 @@ describe('Visual Regression - OHIF User Preferences', () => {
cy.get('[data-cy="close-button"]').click(); //close User Preferences modal
});
it('checks if W/L Preferences table is being displayed in the Window Level tab', function() {
//Navigate to Window Level tab
cy.selectPreferencesTab('@userPreferencesWindowLevelTab');
// Visual comparison
cy.percyCanvasSnapshot(
'User Preferences Modal - Window Level Presets Tab'
);
});
it('checks translation by selecting Spanish language', function() {
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.get('@userPreferencesGeneralTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Visual comparison
cy.percyCanvasSnapshot(
@@ -100,8 +111,7 @@ describe('Visual Regression - OHIF User Preferences', () => {
});
it('checks if user can restore to default the language selection and application will be in English', function() {
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.get('@userPreferencesGeneralTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Set language to Spanish
cy.setLanguage('Spanish');
@@ -110,7 +120,7 @@ describe('Visual Regression - OHIF User Preferences', () => {
cy.openPreferences();
// Go to general tab
cy.changePreferencesTab('@userPreferencesGeneralTab');
cy.selectPreferencesTab('@userPreferencesGeneralTab');
cy.get('@restoreBtn')
.scrollIntoView()
@@ -136,8 +146,7 @@ describe('Visual Regression - OHIF User Preferences', () => {
it('checks new hotkeys for "Next" and "Previous" Image on Viewport', function() {
// Go go hotkeys tab
cy.changePreferencesTab('@userPreferencesHotkeysTab');
cy.get('@userPreferencesHotkeysTab').should('have.class', 'active');
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
// Set new hotkey for 'Next Image Viewport' function
cy.setNewHotkeyShortcutOnUserPreferencesModal(
@@ -84,6 +84,7 @@ export function initPreferencesModalAliases() {
cy.get('.OHIFModal').as('preferencesModal');
cy.get('[data-cy="hotkeys"]').as('userPreferencesHotkeysTab');
cy.get('[data-cy="general"]').as('userPreferencesGeneralTab');
cy.get('[data-cy="window-level"]').as('userPreferencesWindowLevelTab');
initPreferencesModalFooterBtnAliases();
}
+88 -18
View File
@@ -121,7 +121,7 @@ Cypress.Commands.add('waitVTKReformatting', () => {
Cypress.Commands.add('waitViewportImageLoading', () => {
// Wait for finish loading
cy.get('[data-cy="viewprt-grid"]', { timeout: 20000 }).should($grid => {
cy.get('[data-cy="viewprt-grid"]', { timeout: 30000 }).should($grid => {
expect($grid).not.to.contain.text('Load');
});
});
@@ -466,21 +466,54 @@ Cypress.Commands.add('openPreferences', () => {
});
});
Cypress.Commands.add('changePreferencesTab', tabAlias => {
Cypress.Commands.add('selectPreferencesTab', tabAlias => {
cy.initPreferencesModalAliases();
cy.get(tabAlias).click();
cy.get(tabAlias)
.click()
.should('have.class', 'active');
initPreferencesModalFooterBtnAliases();
});
Cypress.Commands.add('resetUserHoktkeyPreferences', () => {
Cypress.Commands.add('resetUserHotkeyPreferences', () => {
// Open User Preferences modal
cy.openPreferences();
cy.initPreferencesModalAliases();
cy.selectPreferencesTab('@userPreferencesHotkeysTab').then(() => {
cy.log('Reset Hotkeys to Default Preferences');
cy.get('@restoreBtn').click();
});
cy.log('Reset to Default Preferences');
cy.get('@restoreBtn').click();
cy.get('@saveBtn').click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon')
.first()
.click({ force: true });
}
// Click on Save Button
cy.get('@saveBtn').click();
});
});
Cypress.Commands.add('resetUserGeneralPreferences', () => {
// Open User Preferences modal
cy.openPreferences();
cy.selectPreferencesTab('@userPreferencesGeneralTab').then(() => {
cy.log('Reset Language to Default Preferences');
cy.get('@restoreBtn').click();
});
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon')
.first()
.click({ force: true });
}
// Click on Save Button
cy.get('@saveBtn').click();
});
});
Cypress.Commands.add(
@@ -499,6 +532,37 @@ Cypress.Commands.add(
}
);
Cypress.Commands.add(
'setWindowLevelPreset',
(preset_index, description_value, window_value, level_value) => {
let index = parseInt(preset_index) + 1;
// Set new Description value
cy.get(':nth-child(' + index + ') > .description > .preferencesInput')
.clear()
.type(description_value, {
force: true,
})
.blur();
// Set new Window value
cy.get(':nth-child(' + index + ') > .window > .preferencesInput')
.clear()
.type(window_value, {
force: true,
})
.blur();
// Set new Level value
cy.get(':nth-child(' + index + ') > .level > .preferencesInput')
.clear()
.type(level_value, {
force: true,
})
.blur();
}
);
Cypress.Commands.add('openDownloadImageModal', () => {
// Click on More button
cy.get('[data-cy="more"]')
@@ -513,12 +577,8 @@ Cypress.Commands.add('openDownloadImageModal', () => {
Cypress.Commands.add('setLanguage', (language, save = true) => {
cy.openPreferences();
cy.get('@userPreferencesGeneralTab')
.click()
.should('have.class', 'active');
initPreferencesModalFooterBtnAliases();
cy.initPreferencesModalAliases();
cy.selectPreferencesTab('@userPreferencesGeneralTab');
// Language dropdown should be displayed
cy.get('#language-select').should('be.visible');
@@ -526,8 +586,18 @@ Cypress.Commands.add('setLanguage', (language, save = true) => {
// Select Language and Save/Cancel
cy.get('#language-select').select(language);
const toClick = save ? '@saveBtn' : '@cancelBtn';
cy.get(toClick)
.scrollIntoView()
.click();
// Close Success Message overlay (if displayed)
cy.get('body').then(body => {
if (body.find('.sb-closeIcon').length > 0) {
cy.get('.sb-closeIcon')
.first()
.click({ force: true });
}
//Click on Save/Cancel button
const toClick = save ? '@saveBtn' : '@cancelBtn';
cy.get(toClick)
.scrollIntoView()
.click();
});
});
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/viewer",
"version": "3.8.3",
"version": "3.8.13",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
"license": "MIT",
@@ -48,14 +48,14 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@ohif/core": "^2.6.0",
"@ohif/core": "^2.6.5",
"@ohif/extension-cornerstone": "^2.5.0",
"@ohif/extension-dicom-html": "^1.1.1",
"@ohif/extension-dicom-microscopy": "^0.50.7",
"@ohif/extension-dicom-microscopy": "^0.50.8",
"@ohif/extension-dicom-pdf": "^1.0.2",
"@ohif/extension-dicom-segmentation": "^0.1.0",
"@ohif/extension-dicom-segmentation": "^0.1.1",
"@ohif/extension-lesion-tracker": "^0.2.0",
"@ohif/extension-vtk": "^1.3.1",
"@ohif/extension-vtk": "^1.3.6",
"@ohif/i18n": "^0.52.7",
"@ohif/ui": "^1.4.1",
"@tanem/react-nprogress": "^1.1.25",
@@ -103,6 +103,8 @@ export default function jumpToRowItem(
displaySet.frameIndex = data.frameIndex;
}
viewportIndex =
(viewportIndex + viewportsState.activeViewportIndex) % numViewports;
viewportSpecificData.push({
viewportIndex,
displaySet,
@@ -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;
@@ -11,7 +11,7 @@ import EmptyViewport from './EmptyViewport.js';
const { loadAndCacheDerivedDisplaySets } = utils;
const ViewportGrid = function(props) {
const ViewportGrid = function (props) {
const {
activeViewportIndex,
availablePlugins,
@@ -37,68 +37,70 @@ const ViewportGrid = function(props) {
viewportData.forEach(displaySet => {
loadAndCacheDerivedDisplaySets(displaySet, studies);
});
}, [viewportData]);
}, [studies, viewportData]);
const ViewportPanes = layout.viewports.map((layout, viewportIndex) => {
const displaySet = viewportData[viewportIndex];
const getViewportPanes = () =>
layout.viewports.map((layout, viewportIndex) => {
const displaySet = viewportData[viewportIndex];
if (!displaySet) {
return null;
}
if (!displaySet) {
return null;
}
const data = {
displaySet,
studies,
};
const data = {
displaySet,
studies,
};
// JAMES TODO:
// JAMES TODO:
// 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;
// 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
);
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={({
viewportIndex,
StudyInstanceUID,
displaySetInstanceUID,
}) => {
setViewportData({
viewportIndex,
StudyInstanceUID,
displaySetInstanceUID,
});
}}
viewportIndex={viewportIndex} // Needed by `setViewportData`
className={classNames('viewport-container', {
active: activeViewportIndex === viewportIndex,
})}
key={viewportIndex}
>
{ViewportComponent}
</ViewportPane>
);
});
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
@@ -136,12 +136,32 @@ class ToolbarRow extends Component {
}
if (activeContextsChanged) {
this.setState({
toolbarButtons: _getVisibleToolbarButtons.call(this),
});
this.setState(
{
toolbarButtons: _getVisibleToolbarButtons.call(this),
},
this.closeCineDialogIfNotApplicable
);
}
}
closeCineDialogIfNotApplicable = () => {
const { dialog } = this.props;
let { dialogId, activeButtons, toolbarButtons } = this.state;
if (dialogId) {
const cineButtonPresent = toolbarButtons.find(
button => button.options && button.options.behavior === 'CINE'
);
if (!cineButtonPresent) {
dialog.dismiss({ id: dialogId });
activeButtons = activeButtons.filter(
button => button.options && button.options.behavior !== 'CINE'
);
this.setState({ dialogId: null, activeButtons });
}
}
};
render() {
const buttonComponents = _getButtonComponents.call(
this,
@@ -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 = {
@@ -140,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">
+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);
});
});
+5
View File
@@ -9922,6 +9922,11 @@ immer@1.10.0:
resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d"
integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==
immer@6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/immer/-/immer-6.0.2.tgz#5bc08dc4930c756d0749533a2afbd88c8de0cd19"
integrity sha512-56CMvUMZl4kkWJFFUe1TjBgGbyb9ibzpLyHD+RSKSVdytuDXgT/HXO1S+GJVywMVl5neGTdAogoR15eRVEd10Q==
immutable@>=3.6.0:
version "3.8.2"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"