Compare commits

..
Author SHA1 Message Date
ohif-bot d282f72d75 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.10.0
 - @ohif/extension-dicom-html@1.3.0
 - @ohif/extension-dicom-rt@0.6.0
 - @ohif/extension-dicom-segmentation@0.5.0
 - @ohif/extension-dicom-tag-browser@0.2.0
 - @ohif/extension-vtk@1.10.0
 - @ohif/core@2.12.0
 - @ohif/ui@1.8.0
 - @ohif/viewer@4.8.0
2020-12-10 10:09:50 +00:00
Davide PunzoandIgor Octaviano 29fceacee9 feat: visualize overlapping segments in cornerstone (#2185)
* feat: visualize overlapping segments

load multiple labelMaps (splitted by dcmjs because overlapping) in a segmentations [IDC2164]

Dynamically update active labelmap [IDC2164]

Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
Co-authored-by: Igor Octaviano <igoroctaviano@gmail.com>
2020-12-10 11:04:27 +01:00
Igor Octaviano a7a5a359dc Use runtime values for version (#2204) 2020-12-10 09:56:30 +01:00
ohif-bot e362e895d4 chore(release): publish [skip ci]
- @ohif/viewer@4.7.2
2020-12-03 20:45:12 +00:00
Nikola Mijajlović 45a07dfa19 refactor: Update SidePanel.js to use a functional component (#2179) 2020-12-03 21:39:14 +01:00
ohif-bot 7323e63608 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.9.4
 - @ohif/viewer@4.7.1
2020-12-03 17:20:07 +00:00
Igor OctavianoandDavide Punzo 0baf18dcb2 fix: reset VOI on mpr reset (#2115)
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2020-12-03 18:11:14 +01:00
ohif-bot cca3d6bff2 chore(release): publish [skip ci]
- @ohif/extension-dicom-rt@0.5.0
 - @ohif/viewer@4.7.0
2020-12-03 16:47:50 +00:00
Igor OctavianoandDavide Punzo f3cecf129b feat: Update approach to better handle rtdose instead of disabling panel
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2020-12-03 17:36:02 +01:00
ohif-bot fe03fc6f55 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.9.3
 - @ohif/viewer@4.6.3
2020-12-03 16:11:30 +00:00
Davide Punzo b59187c74d Re #2113: disable 2D MPR when datasets have multiple slices for the same ImagePositionPatient (#2160)
fix: disable 2D MPR when datasets are 4D
2020-12-03 17:08:38 +01:00
ohif-bot 5210c80369 chore(release): publish [skip ci]
- @ohif/extension-dicom-tag-browser@0.1.1
 - @ohif/extension-vtk@1.9.2
 - @ohif/core@2.11.1
 - @ohif/ui@1.7.1
 - @ohif/viewer@4.6.2
2020-12-03 15:19:00 +00:00
Igor OctavianoandDavide Punzo f57725ac8b fix: fallback to original dataset VR for XS
Co-authored-by: Davide Punzo <punzodavide@hotmail.it>
2020-12-03 16:15:27 +01:00
35 changed files with 662 additions and 310 deletions

No files matched your search

+4 -2
View File
@@ -3,7 +3,6 @@ const dotenv = require('dotenv');
// //
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const PACKAGE = require('../platform/viewer/package.json');
// ~~ RULES // ~~ RULES
const loadShadersRule = require('./rules/loadShaders.js'); const loadShadersRule = require('./rules/loadShaders.js');
const loadWebWorkersRule = require('./rules/loadWebWorkers.js'); const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
@@ -77,7 +76,10 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
'process.env.DEBUG': JSON.stringify(process.env.DEBUG), 'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''), 'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''),
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'), 'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
'process.env.VERSION_NUMBER': JSON.stringify(PACKAGE.version || ''), 'process.env.VERSION_NUMBER': webpack.DefinePlugin.runtimeValue(() => {
const package = require('../platform/viewer/package.json');
return JSON.stringify(package.version || '');
}, ['../platform/viewer/package.json']),
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM), 'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
/* i18n */ /* i18n */
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''), 'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.10.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.6...@ohif/extension-cornerstone@2.10.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.5...@ohif/extension-cornerstone@2.9.6) (2020-10-07) ## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.5...@ohif/extension-cornerstone@2.9.6) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-cornerstone **Note:** Version bump only for package @ohif/extension-cornerstone
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-cornerstone", "name": "@ohif/extension-cornerstone",
"version": "2.9.6", "version": "2.10.0",
"description": "OHIF extension for Cornerstone", "description": "OHIF extension for Cornerstone",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -36,7 +36,7 @@
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.9...@ohif/extension-dicom-html@1.3.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [1.2.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.8...@ohif/extension-dicom-html@1.2.9) (2020-10-07) ## [1.2.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.8...@ohif/extension-dicom-html@1.2.9) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-dicom-html **Note:** Version bump only for package @ohif/extension-dicom-html
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-html", "name": "@ohif/extension-dicom-html",
"version": "1.2.9", "version": "1.3.0",
"description": "OHIF extension for rendering structured reports to HTML", "description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -29,7 +29,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6" "react-dom": "^16.8.6"
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.6.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.5.0...@ohif/extension-dicom-rt@0.6.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
# [0.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.9...@ohif/extension-dicom-rt@0.5.0) (2020-12-03)
### Features
* Update approach to better handle rtdose instead of disabling panel ([f3cecf1](https://github.com/OHIF/Viewers/commit/f3cecf129b16f8d24d239aa44fdff7f46e6ba683))
## [0.4.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.8...@ohif/extension-dicom-rt@0.4.9) (2020-10-12) ## [0.4.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.8...@ohif/extension-dicom-rt@0.4.9) (2020-10-12)
**Note:** Version bump only for package @ohif/extension-dicom-rt **Note:** Version bump only for package @ohif/extension-dicom-rt
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-rt", "name": "@ohif/extension-dicom-rt",
"version": "0.4.9", "version": "0.6.0",
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.", "description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -31,7 +31,7 @@
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"gl-matrix": "^3.3.0", "gl-matrix": "^3.3.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cornerstoneTools from 'cornerstone-tools'; import cornerstoneTools from 'cornerstone-tools';
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
@@ -31,19 +31,20 @@ const refreshViewport = () => {
* *
* @param {Object} props * @param {Object} props
* @param {Array} props.studies * @param {Array} props.studies
* @param {Array} props.viewports - viewportSpecificData * @param {Array} props.getActiveViewport - get active viewport data
* @param {number} props.activeIndex - activeViewportIndex * @param {number} props.activeIndex - activeViewportIndex
* @param {number} props.isOpen - isOpen * @param {number} props.isOpen - isOpen
* @returns component * @returns component
*/ */
const RTPanel = ({ const RTPanel = ({
studies, studies,
viewports,
activeIndex, activeIndex,
isOpen, isOpen,
onContourItemClick, onContourItemClick,
activeContexts = [], activeContexts = [],
contexts = {}, contexts = {},
activeViewport,
getActiveViewport
}) => { }) => {
const isVTK = () => activeContexts.includes(contexts.VTK); const isVTK = () => activeContexts.includes(contexts.VTK);
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE); const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
@@ -54,11 +55,11 @@ const RTPanel = ({
referencedDisplaysets: [], referencedDisplaysets: [],
sets: [], sets: [],
selectedSet: null, selectedSet: null,
isLocked: false
}; };
const [state, setState] = useState(DEFAULT_STATE); const [state, setState] = useState(DEFAULT_STATE);
const [showSettings, setShowSettings] = useState(false); const [showSettings, setShowSettings] = useState(false);
const activeViewport = viewports[activeIndex];
/* /*
* TODO: Improve the way we notify parts of the app that depends on rts to be loaded. * TODO: Improve the way we notify parts of the app that depends on rts to be loaded.
@@ -81,27 +82,32 @@ const RTPanel = ({
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets); document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
return () => { return () => {
document.removeEventListener( document.removeEventListener('extensiondicomrtrtloaded', updateStructureSets);
'extensiondicomrtrtloaded',
updateStructureSets
);
}; };
}, []); }, []);
useEffect(() => {
const studyMetadata = studyMetadataManager.get(activeViewport.StudyInstanceUID);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
Modality: 'RTSTRUCT',
});
setState(state => ({ ...state, isLocked: !referencedDisplaysets.length }));
}, [activeViewport]);
const updateStructureSets = () => { const updateStructureSets = () => {
const viewport = getActiveViewport();
const module = cornerstoneTools.getModule('rtstruct'); const module = cornerstoneTools.getModule('rtstruct');
const StructureSets = module.state.StructureSets; const StructureSets = module.state.StructureSets;
if (StructureSets && StructureSets.length) { if (StructureSets && StructureSets.length) {
const viewportSets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid( const viewportSets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
activeViewport.SeriesInstanceUID viewport.SeriesInstanceUID
); );
const studyMetadata = studyMetadataManager.get( const studyMetadata = studyMetadataManager.get(viewport.StudyInstanceUID);
activeViewport.StudyInstanceUID
);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({ const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID, referencedSeriesInstanceUID: viewport.SeriesInstanceUID,
Modality: 'RTSTRUCT', Modality: 'RTSTRUCT',
}); });
@@ -118,10 +124,6 @@ const RTPanel = ({
} }
}; };
useEffect(() => {
updateStructureSets();
}, [studies, viewports, activeIndex]);
useEffect(() => { useEffect(() => {
setShowSettings(showSettings && !isOpen); setShowSettings(showSettings && !isOpen);
}, [isOpen]); }, [isOpen]);
@@ -232,78 +234,80 @@ const RTPanel = ({
<div className="dcmrt-panel"> <div className="dcmrt-panel">
<div className="dcmrt-panel-header"> <div className="dcmrt-panel-header">
<h3>RT Structure Sets</h3> <h3>RT Structure Sets</h3>
<Icon {!state.isLocked && (
className="cog-icon" <Icon
name="cog" className="cog-icon"
width="25px" name="cog"
height="25px" width="25px"
onClick={() => setShowSettings(true)} height="25px"
/> onClick={() => setShowSettings(true)}
/>
)}
</div> </div>
{!state.referencedDisplaysets.length && ( {!state.isLocked && !state.referencedDisplaysets.length && <LoadingIndicator expand height="70px" width="70px" />}
<LoadingIndicator expand height="70px" width="70px" /> {state.sets && state.referencedDisplaysets.map(displaySet => {
)} const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
{state.sets &&
state.referencedDisplaysets.map(displaySet => {
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
const module = cornerstoneTools.getModule('rtstruct'); const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid( const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
viewports[activeIndex].SeriesInstanceUID activeViewport.SeriesInstanceUID
); );
const loadedSet = sets.find( const loadedSet = sets.find(
set => set.SeriesInstanceUID === SeriesInstanceUID set => set.SeriesInstanceUID === SeriesInstanceUID
); );
return (
<PanelSection if (!loadedSet) return null;
key={SeriesInstanceUID}
title={metadata.StructureSetLabel} return (
loading={!isLoaded || !loadedSet} <PanelSection
visible={isLoaded && loadedSet && loadedSet.visible} key={SeriesInstanceUID}
hideVisibleButton={!isLoaded} title={metadata.StructureSetLabel}
expanded={ loading={!isLoaded || !loadedSet}
isLoaded && visible={isLoaded && loadedSet && loadedSet.visible}
loadedSet.SeriesInstanceUID === hideVisibleButton={!isLoaded}
state.selectedSet.SeriesInstanceUID expanded={
isLoaded &&
loadedSet.SeriesInstanceUID ===
state.selectedSet.SeriesInstanceUID
}
onVisibilityChange={newVisibility => {
const module = cornerstoneTools.getModule('rtstruct');
if (newVisibility) {
module.setters.showStructureSet(loadedSet.SeriesInstanceUID);
} else {
module.setters.hideStructureSet(loadedSet.SeriesInstanceUID);
} }
onVisibilityChange={newVisibility => { const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
activeViewport.SeriesInstanceUID
);
setState(state => ({ ...state, sets }));
refreshViewport();
}}
onExpandChange={async () => {
if (!isLoaded) {
await displaySet.load(activeViewport, studies);
const module = cornerstoneTools.getModule('rtstruct'); const module = cornerstoneTools.getModule('rtstruct');
if (newVisibility) {
module.setters.showStructureSet(loadedSet.SeriesInstanceUID);
} else {
module.setters.hideStructureSet(loadedSet.SeriesInstanceUID);
}
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid( const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
viewports[activeIndex].SeriesInstanceUID activeViewport.SeriesInstanceUID
); );
setState(state => ({ ...state, sets })); const selectedSet = sets.find(
refreshViewport(); set => set.SeriesInstanceUID === SeriesInstanceUID
}} );
onExpandChange={async () => { setState(state => ({ ...state, selectedSet, sets }));
if (!isLoaded) { }
await displaySet.load(viewports[activeIndex], studies); }}
const module = cornerstoneTools.getModule('rtstruct'); >
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid( <ScrollableArea>
viewports[activeIndex].SeriesInstanceUID <TableList headless>
); {isLoaded &&
const selectedSet = sets.find( loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
set => set.SeriesInstanceUID === SeriesInstanceUID </TableList>
); </ScrollableArea>
setState(state => ({ ...state, selectedSet, sets })); </PanelSection>
} );
}} })}
>
<ScrollableArea>
<TableList headless>
{isLoaded &&
loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
</TableList>
</ScrollableArea>
</PanelSection>
);
})}
</div> </div>
); );
}; };
+5 -5
View File
@@ -90,11 +90,11 @@ export default async function loadRTStruct(
const sopInstanceUID = ContourImageSequence const sopInstanceUID = ContourImageSequence
? ContourImageSequence.ReferencedSOPInstanceUID ? ContourImageSequence.ReferencedSOPInstanceUID
: _getClosestSOPInstanceUID( : _getClosestSOPInstanceUID(
ContourData, ContourData,
ContourGeometricType, ContourGeometricType,
NumberOfContourPoints, NumberOfContourPoints,
imageIdSopInstanceUidPairs imageIdSopInstanceUidPairs
); );
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID); const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
const imageIdSpecificToolData = _getOrCreateImageIdSpecificToolData( const imageIdSpecificToolData = _getOrCreateImageIdSpecificToolData(
toolState, toolState,
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.7...@ohif/extension-dicom-segmentation@0.5.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [0.4.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.6...@ohif/extension-dicom-segmentation@0.4.7) (2020-10-07) ## [0.4.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.6...@ohif/extension-dicom-segmentation@0.4.7) (2020-10-07)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation **Note:** Version bump only for package @ohif/extension-dicom-segmentation
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-segmentation", "name": "@ohif/extension-dicom-segmentation",
"version": "0.4.7", "version": "0.5.0",
"description": "OHIF extension for viewing segmentations in the 2D MPR view", "description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -31,7 +31,7 @@
"@ohif/core": "^0.50.0", "@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6" "react-dom": "^16.8.6"
@@ -159,12 +159,12 @@ const SegmentationPanel = ({
const labelmapList = getLabelmapList( const labelmapList = getLabelmapList(
brushStackState, brushStackState,
firstImageId, firstImageId,
activeViewport activeViewport,
); );
const segmentList = getSegmentList( const segmentList = getSegmentList(
labelmap3D, labelmap3D,
firstImageId, firstImageId,
brushStackState brushStackState,
); );
setState(state => ({ setState(state => ({
...state, ...state,
@@ -292,10 +292,13 @@ const SegmentationPanel = ({
const sameSegment = state.selectedSegment === segmentNumber; const sameSegment = state.selectedSegment === segmentNumber;
const setCurrentSelectedSegment = () => { const setCurrentSelectedSegment = () => {
const activeViewport = viewports[activeIndex];
_setActiveSegment( _setActiveSegment(
firstImageId, firstImageId,
segmentNumber, segmentNumber,
labelmap3D.activeSegmentIndex labelmap3D.activeSegmentIndex,
activeViewport
); );
updateState('selectedSegment', sameSegment ? null : segmentNumber); updateState('selectedSegment', sameSegment ? null : segmentNumber);
@@ -640,7 +643,7 @@ const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
* @param {*} activeSegmentIndex * @param {*} activeSegmentIndex
* @returns * @returns
*/ */
const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => { const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex, activeViewport) => {
if (segmentIndex === activeSegmentIndex) { if (segmentIndex === activeSegmentIndex) {
log.info(`${activeSegmentIndex} is already the active segment`); log.info(`${activeSegmentIndex} is already the active segment`);
return; return;
@@ -653,8 +656,27 @@ const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => {
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex]; brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
labelmap3D.activeSegmentIndex = segmentIndex; labelmap3D.activeSegmentIndex = segmentIndex;
refreshViewports(); /**
* Activates the correct label map if clicked segment
* does not belong to the active labelmap
*/
const { StudyInstanceUID } = activeViewport;
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
const allDisplaySets = studyMetadata.getDisplaySets();
let newLabelmapIndex = brushStackState.activeLabelmapIndex;
allDisplaySets.forEach(displaySet => {
if (displaySet.labelmapSegments) {
Object.keys(displaySet.labelmapSegments).forEach(labelmapIndex => {
if (displaySet.labelmapSegments[labelmapIndex].includes(segmentIndex)) {
newLabelmapIndex = labelmapIndex;
}
});
}
});
brushStackState.activeLabelmapIndex = newLabelmapIndex;
refreshViewports();
return segmentIndex; return segmentIndex;
}; };
@@ -1,6 +1,11 @@
import { MODULE_TYPES, utils } from '@ohif/core'; import { MODULE_TYPES, utils } from '@ohif/core';
import loadSegmentation from './loadSegmentation'; import loadSegmentation from './loadSegmentation';
import getSourceDisplaySet from './getSourceDisplaySet'; import getSourceDisplaySet from './getSourceDisplaySet';
import OHIF from '@ohif/core';
import dcmjs from 'dcmjs';
const { DicomLoaderService } = OHIF.utils;
const { DicomMessage, DicomMetaDictionary } = dcmjs.data;
// TODO: Should probably use dcmjs for this // TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = { const SOP_CLASS_UIDS = {
@@ -16,7 +21,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
id: 'OHIFDicomSegSopClassHandler', id: 'OHIFDicomSegSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER, type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs, sopClassUIDs,
getDisplaySetFromSeries: function( getDisplaySetFromSeries: function (
series, series,
study, study,
dicomWebClient, dicomWebClient,
@@ -46,7 +51,6 @@ export default function getSopClassHandlerModule({ servicesManager }) {
StudyInstanceUID, StudyInstanceUID,
FrameOfReferenceUID, FrameOfReferenceUID,
authorizationHeaders, authorizationHeaders,
metadata,
isDerived: true, isDerived: true,
referencedDisplaySetUID: null, // Assigned when loaded. referencedDisplaySetUID: null, // Assigned when loaded.
labelmapIndex: null, // Assigned when loaded. labelmapIndex: null, // Assigned when loaded.
@@ -58,15 +62,85 @@ export default function getSopClassHandlerModule({ servicesManager }) {
metadata, metadata,
}; };
segDisplaySet.getSourceDisplaySet = function(studies) { segDisplaySet.getSourceDisplaySet = function (studies) {
return getSourceDisplaySet(studies, segDisplaySet); return getSourceDisplaySet(studies, segDisplaySet);
}; };
segDisplaySet.load = function(referencedDisplaySet, studies) { segDisplaySet.load = async function (referencedDisplaySet, studies) {
return loadSegmentation(segDisplaySet, referencedDisplaySet, studies); segDisplaySet.isLoaded = true;
const { StudyInstanceUID } = referencedDisplaySet;
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise(
segDisplaySet,
studies
);
const dicomData = DicomMessage.readFile(segArrayBuffer);
const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
const imageIds = _getImageIdsForDisplaySet(
studies,
StudyInstanceUID,
referencedDisplaySet.SeriesInstanceUID
);
return new Promise((resolve, reject) => {
let results;
try {
results = _parseSeg(segArrayBuffer, imageIds);
} catch (error) {
segDisplaySet.isLoaded = false;
segDisplaySet.loadError = true;
reject(error);
}
const { labelmapBufferArray, segMetadata, segmentsOnFrame, segmentsOnFrameArray } = results;
if (labelmapBufferArray.length > 1) {
for (let i = 0; i < labelmapBufferArray.length; ++i) {
loadSegmentation(imageIds, segDisplaySet, labelmapBufferArray[i], segMetadata, segmentsOnFrame, segmentsOnFrameArray[i]);
}
} else {
loadSegmentation(imageIds, segDisplaySet, labelmapBufferArray[0], segMetadata, segmentsOnFrame, []);
}
segDisplaySet.labelmapIndex = 0;
resolve();
});
}; };
return segDisplaySet; return segDisplaySet;
}, },
}; };
} }
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());
}
@@ -1,92 +1,62 @@
import OHIF from '@ohif/core';
import dcmjs from 'dcmjs'; import dcmjs from 'dcmjs';
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools'; import cornerstoneTools from 'cornerstone-tools';
const { DicomLoaderService } = OHIF.utils;
export default async function loadSegmentation( export default async function loadSegmentation(
imageIds,
segDisplaySet, segDisplaySet,
referencedDisplaySet, labelmapBuffer,
studies segMetadata,
segmentsOnFrame,
labelmapSegments
) { ) {
const { StudyInstanceUID } = referencedDisplaySet; const { setters } = cornerstoneTools.getModule('segmentation');
// Set here is loading is asynchronous. // TODO: Could define a color LUT based on colors in the SEG.
// If this function throws its set back to false. const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
segDisplaySet.isLoaded = true; const colorLUTIndex = _makeColorLUTAndGetIndex(segMetadata);
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise( setters.labelmap3DByFirstImageId(
segDisplaySet, imageIds[0],
studies labelmapBuffer,
labelmapIndex,
segMetadata,
imageIds.length,
segmentsOnFrame,
colorLUTIndex
); );
const dicomData = dcmjs.data.DicomMessage.readFile(segArrayBuffer); if (!segDisplaySet.labelmapSegments) {
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset( segDisplaySet.labelmapSegments = {};
dicomData.dict }
);
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta); /**
* Cache each labelmap segments.
* This data is used to determine the active label map when a given segment is activated/clicked.
*/
segDisplaySet.labelmapSegments[labelmapIndex] = labelmapSegments.length ? Array.from(new Set(labelmapSegments.filter(a => !!a).reduce((a, b) => a.concat(b)))) : [];
segDisplaySet.labelmapIndex = labelmapIndex;
const imageIds = _getImageIdsForDisplaySet( /*
studies, * TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
StudyInstanceUID, *
referencedDisplaySet.SeriesInstanceUID * Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
); * or other components that could rely on loaded segmentations that
* the segments were loaded so that e.g. when the user opens the panel
return new Promise((resolve, reject) => { * before the segments are fully loaded, the panel can subscribe to this custom event
let results; * and update itself with the new segments.
*
try { * This limitation is due to the fact that the cs segmentation module is an object (which will be
results = _parseSeg(segArrayBuffer, imageIds); * updated after the segments are loaded) that React its not aware of its changes
} catch (error) { * because the module object its not passed in to the panel component as prop but accessed externally.
segDisplaySet.isLoaded = false; *
segDisplaySet.loadError = true; * Improving this event approach to something reactive that can be tracked inside the react lifecycle,
reject(error); * allows us to easily watch the module or the segmentations loading process in any other component
} * without subscribing to external events.
*/
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results; console.log('Segmentation loaded.');
const { setters } = cornerstoneTools.getModule('segmentation'); const event = new CustomEvent('extensiondicomsegmentationsegloaded');
document.dispatchEvent(event);
// TODO: Could define a color LUT based on colors in the SEG.
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
const colorLUTIndex = _makeColorLUTAndGetIndex(segMetadata);
setters.labelmap3DByFirstImageId(
imageIds[0],
labelmapBuffer,
labelmapIndex,
segMetadata,
imageIds.length,
segmentsOnFrame,
colorLUTIndex
);
segDisplaySet.labelmapIndex = labelmapIndex;
/*
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
*
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
* or other components that could rely on loaded segmentations that
* the segments were loaded so that e.g. when the user opens the panel
* before the segments are fully loaded, the panel can subscribe to this custom event
* and update itself with the new segments.
*
* This limitation is due to the fact that the cs segmentation module is an object (which will be
* updated after the segments are loaded) that React its not aware of its changes
* because the module object its not passed in to the panel component as prop but accessed externally.
*
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
* allows us to easily watch the module or the segmentations loading process in any other component
* without subscribing to external events.
*/
console.log('Segmentation loaded.');
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
document.dispatchEvent(event);
resolve(labelmapIndex);
});
} }
function _getNextLabelmapIndex(firstImageId) { function _getNextLabelmapIndex(firstImageId) {
@@ -172,38 +142,3 @@ function _getNextColorLUTIndex() {
return colorLUTIndex; return colorLUTIndex;
} }
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());
}
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.1.1...@ohif/extension-dicom-tag-browser@0.2.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.1.0...@ohif/extension-dicom-tag-browser@0.1.1) (2020-12-03)
### Bug Fixes
* fallback to original dataset VR for XS ([f57725a](https://github.com/OHIF/Viewers/commit/f57725ac8bfd59ea46ab334a1823882afadefba1))
# [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.5...@ohif/extension-dicom-tag-browser@0.1.0) (2020-12-03) # [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.5...@ohif/extension-dicom-tag-browser@0.1.0) (2020-12-03)
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-tag-browser", "name": "@ohif/extension-dicom-tag-browser",
"version": "0.1.0", "version": "0.2.0",
"description": "OHIF extension for checking DICOM headers.", "description": "OHIF extension for checking DICOM headers.",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -28,7 +28,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^2.6.0", "@ohif/core": "^2.6.0",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"react": "^16.8.6" "react": "^16.8.6"
}, },
"dependencies": { "dependencies": {
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { classes } from '@ohif/core'; import { classes, cornerstone as OHIFCornerstone } from '@ohif/core';
import dcmjs from 'dcmjs'; import dcmjs from 'dcmjs';
import DicomBrowserSelect from './DicomBrowserSelect'; import DicomBrowserSelect from './DicomBrowserSelect';
import moment from 'moment'; import moment from 'moment';
@@ -10,6 +10,8 @@ const { ImageSet } = classes;
const { DicomMetaDictionary } = dcmjs.data; const { DicomMetaDictionary } = dcmjs.data;
const { nameMap } = DicomMetaDictionary; const { nameMap } = DicomMetaDictionary;
const { metadataProvider } = OHIFCornerstone;
const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
const [ const [
activeDisplaySetInstanceUID, activeDisplaySetInstanceUID,
@@ -17,6 +19,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
] = useState(displaySetInstanceUID); ] = useState(displaySetInstanceUID);
const [activeInstance, setActiveInstance] = useState(0); const [activeInstance, setActiveInstance] = useState(0);
const [tags, setTags] = useState([]); const [tags, setTags] = useState([]);
const [meta, setMeta] = useState('');
const [instanceList, setInstanceList] = useState([]); const [instanceList, setInstanceList] = useState([]);
const [displaySetList, setDisplaySetList] = useState([]); const [displaySetList, setDisplaySetList] = useState([]);
const [isImageStack, setIsImageStack] = useState(false); const [isImageStack, setIsImageStack] = useState(false);
@@ -83,6 +86,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
} }
setTags(getSortedTags(metadata)); setTags(getSortedTags(metadata));
setMeta(metadata);
setInstanceList(instanceList); setInstanceList(instanceList);
setDisplaySetList(newDisplaySetList); setDisplaySetList(newDisplaySetList);
setIsImageStack(isImageStack); setIsImageStack(isImageStack);
@@ -114,41 +118,43 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
options={displaySetList} options={displaySetList}
/> />
{instanceSelectList} {instanceSelectList}
<DicomTagTable tags={tags}></DicomTagTable> <DicomTagTable tags={tags} meta={meta}></DicomTagTable>
</div> </div>
); );
}; };
function DicomTagTable({ tags }) { function DicomTagTable({ tags, meta }) {
const rows = getFormattedRowsFromTags(tags); const rows = getFormattedRowsFromTags(tags, meta);
return ( return (
<div> <div>
<table className="dicom-tag-browser-table"> <table className="dicom-tag-browser-table">
<tr> <tbody>
<th className="dicom-tag-browser-table-left">Tag</th> <tr>
<th className="dicom-tag-browser-table-left">Value Representation</th> <th className="dicom-tag-browser-table-left">Tag</th>
<th className="dicom-tag-browser-table-left">Keyword</th> <th className="dicom-tag-browser-table-left">Value Representation</th>
<th className="dicom-tag-browser-table-left">Value</th> <th className="dicom-tag-browser-table-left">Keyword</th>
</tr> <th className="dicom-tag-browser-table-left">Value</th>
{rows.map(row => { </tr>
const className = row.className ? row.className : null; {rows.map((row, index) => {
const className = row.className ? row.className : null;
return ( return (
<tr className={className}> <tr className={className} key={`DICOMTagRow-${index}`}>
<td>{row[0]}</td> <td>{row[0]}</td>
<td className="dicom-tag-browser-table-center">{row[1]}</td> <td className="dicom-tag-browser-table-center">{row[1]}</td>
<td>{row[2]}</td> <td>{row[2]}</td>
<td>{row[3]}</td> <td>{row[3]}</td>
</tr> </tr>
); );
})} })}
</tbody>
</table> </table>
</div> </div>
); );
} }
function getFormattedRowsFromTags(tags) { function getFormattedRowsFromTags(tags, meta) {
const rows = []; const rows = [];
tags.forEach(tagInfo => { tags.forEach(tagInfo => {
@@ -175,6 +181,17 @@ function getFormattedRowsFromTags(tags) {
rows.push(...formatedRowsFromTags); rows.push(...formatedRowsFromTags);
}); });
} else { } else {
if (tagInfo.vr === 'xs') {
try {
const dataset = metadataProvider.getStudyDataset(meta.StudyInstanceUID);
const tag = dcmjs.data.Tag.fromPString(tagInfo.tag).toCleanString();
const originalTagInfo = dataset[tag];
tagInfo.vr = originalTagInfo.vr;
} catch (error) {
console.error(`Failed to parse value representation for tag '${tagInfo.keyword}'`);
}
}
rows.push([ rows.push([
`${tagInfo.tagIndent}${tagInfo.tag}`, `${tagInfo.tagIndent}${tagInfo.tag}`,
tagInfo.vr, tagInfo.vr,
+38
View File
@@ -3,6 +3,44 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.10.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.4...@ohif/extension-vtk@1.10.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [1.9.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.3...@ohif/extension-vtk@1.9.4) (2020-12-03)
### Bug Fixes
* reset VOI on mpr reset ([#2115](https://github.com/OHIF/Viewers/issues/2115)) ([0baf18d](https://github.com/OHIF/Viewers/commit/0baf18dcb2985378016236b735329346a851e14f))
## [1.9.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.2...@ohif/extension-vtk@1.9.3) (2020-12-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.9.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.1...@ohif/extension-vtk@1.9.2) (2020-12-03)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.9.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.0...@ohif/extension-vtk@1.9.1) (2020-12-03) ## [1.9.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.0...@ohif/extension-vtk@1.9.1) (2020-12-03)
**Note:** Version bump only for package @ohif/extension-vtk **Note:** Version bump only for package @ohif/extension-vtk
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-vtk", "name": "@ohif/extension-vtk",
"version": "1.9.1", "version": "1.10.0",
"description": "OHIF extension for VTK.js", "description": "OHIF extension for VTK.js",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -35,7 +35,7 @@
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"i18next": "^17.0.3", "i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",
@@ -53,8 +53,8 @@
"react-vtkjs-viewport": "^0.14.2" "react-vtkjs-viewport": "^0.14.2"
}, },
"devDependencies": { "devDependencies": {
"@ohif/core": "^2.11.0", "@ohif/core": "^2.12.0",
"@ohif/ui": "^1.7.0", "@ohif/ui": "^1.8.0",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
+11 -2
View File
@@ -72,7 +72,7 @@ class OHIFVTKViewport extends Component {
}; };
static defaultProps = { static defaultProps = {
onScroll: () => {}, onScroll: () => { },
}; };
static id = 'OHIFVTKViewport'; static id = 'OHIFVTKViewport';
@@ -159,6 +159,15 @@ class OHIFVTKViewport extends Component {
const { activeLabelmapIndex } = brushStackState; const { activeLabelmapIndex } = brushStackState;
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex]; const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
if (brushStackState.labelmaps3D.length > 1 && this.props.viewportIndex === 0) {
const { UINotificationService } = this.props.servicesManager.services;
UINotificationService.show({
title: 'Overlapping Segmentation Found',
message: 'Overlapping segmentations cannot be displayed when in MPR mode',
type: 'info',
});
}
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map( this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(
isHidden => { isHidden => {
return { visible: !isHidden }; return { visible: !isHidden };
@@ -354,7 +363,7 @@ class OHIFVTKViewport extends Component {
if ( if (
displaySet.displaySetInstanceUID !== displaySet.displaySetInstanceUID !==
prevDisplaySet.displaySetInstanceUID || prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID || displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex displaySet.frameIndex !== prevDisplaySet.frameIndex
) { ) {
+8 -4
View File
@@ -17,6 +17,7 @@ const { BlendMode } = Constants;
const commandsModule = ({ commandsManager, UINotificationService }) => { const commandsModule = ({ commandsManager, UINotificationService }) => {
// TODO: Put this somewhere else // TODO: Put this somewhere else
let apis = {}; let apis = {};
let defaultVOI;
async function _getActiveViewportVTKApi(viewports) { async function _getActiveViewportVTKApi(viewports) {
const { const {
@@ -119,9 +120,11 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
return apis[index]; return apis[index];
}, },
resetMPRView() { resetMPRView() {
apis.forEach(api => { // Reset orientation
api.resetOrientation(); apis.forEach(api => api.resetOrientation());
});
// Reset VOI
if (defaultVOI) setVOI(defaultVOI);
// Reset the crosshairs // Reset the crosshairs
apis[0].svgWidgets.rotatableCrosshairsWidget.resetCrosshairs(apis, 0); apis[0].svgWidgets.rotatableCrosshairsWidget.resetCrosshairs(apis, 0);
@@ -172,7 +175,7 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
segmentNumber, segmentNumber,
frameIndex, frameIndex,
frame, frame,
done = () => {}, done = () => { },
}) => { }) => {
let api = apis[viewports.activeViewportIndex]; let api = apis[viewports.activeViewportIndex];
@@ -387,6 +390,7 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
// Get current VOI if cornerstone viewport. // Get current VOI if cornerstone viewport.
const cornerstoneVOI = getVOIFromCornerstoneViewport(); const cornerstoneVOI = getVOIFromCornerstoneViewport();
defaultVOI = cornerstoneVOI;
const viewportProps = [ const viewportProps = [
{ {
@@ -31,7 +31,57 @@ const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportI
if (!displaySet) { if (!displaySet) {
return false; return false;
}; }
// 2D MPR is not currently available for 4D datasets.
// Assuming that slices at different time have the same position, here we just check if
// there are multiple slices for the same ImagePositionPatient and disable MPR.
// A better heuristic would be checking 4D tags, e.g. the presence of multiple TemporalPositionIdentifier values.
// However, some studies (e.g. https://github.com/OHIF/Viewers/issues/2113) do not have such tags.
for (let ii = 0; ii < displaySet.numImageFrames; ++ii){
const imageIdControl = displaySet.images[ii].getImageId()
const instanceMetadataControl = cornerstone.metaData.get('instance', imageIdControl)
if (!instanceMetadataControl ||
instanceMetadataControl === undefined ||
!instanceMetadataControl.ImagePositionPatient ||
instanceMetadataControl.ImagePositionPatient === undefined ) {
// if ImagePositionPatient is missing, skip the 4D datasets check.
// do not return false, because it could be a 3D dataset.
continue;
}
let xImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[0];
let yImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[1];
let zImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[2];
for (let jj = ii + 1; jj < displaySet.numImageFrames; ++jj){
const imageId = displaySet.images[jj].getImageId()
const instanceMetadata = cornerstone.metaData.get('instance', imageId)
if (!instanceMetadata ||
instanceMetadata === undefined ||
!instanceMetadata.ImagePositionPatient ||
instanceMetadata.ImagePositionPatient === undefined) {
// if ImagePositionPatient is missing, skip the 4D datasets check.
// do not return false, because it could be a 3D dataset.
continue;
}
let xImagePositionPatient = instanceMetadata.ImagePositionPatient[0];
let yImagePositionPatient = instanceMetadata.ImagePositionPatient[1];
let zImagePositionPatient = instanceMetadata.ImagePositionPatient[2];
if (xImagePositionPatientControl === xImagePositionPatient &&
yImagePositionPatientControl === yImagePositionPatient &&
zImagePositionPatientControl === zImagePositionPatient){
return false;
}
}
}
return displaySet.isReconstructable; return displaySet.isReconstructable;
}; };
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.12.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.11.1...@ohif/core@2.12.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [2.11.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.11.0...@ohif/core@2.11.1) (2020-12-03)
### Bug Fixes
* fallback to original dataset VR for XS ([f57725a](https://github.com/OHIF/Viewers/commit/f57725ac8bfd59ea46ab334a1823882afadefba1))
# [2.11.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.7...@ohif/core@2.11.0) (2020-12-03) # [2.11.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.7...@ohif/core@2.11.0) (2020-12-03)
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/core", "name": "@ohif/core",
"version": "2.11.0", "version": "2.12.0",
"description": "Generic business logic for web-based medical imaging applications", "description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team", "author": "OHIF Core Team",
"license": "MIT", "license": "MIT",
@@ -39,7 +39,7 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"dicomweb-client": "^0.6.0", "dicomweb-client": "^0.6.0",
"immer": "6.0.2", "immer": "6.0.2",
"isomorphic-base64": "^1.0.2", "isomorphic-base64": "^1.0.2",
@@ -20,6 +20,7 @@ class MetadataProvider {
writable: false, writable: false,
value: new Map(), value: new Map(),
}); });
this.datasets = {};
} }
async addInstance(dicomJSONDatasetOrP10ArrayBuffer, options = {}) { async addInstance(dicomJSONDatasetOrP10ArrayBuffer, options = {}) {
@@ -52,6 +53,7 @@ class MetadataProvider {
SOPInstanceUID, SOPInstanceUID,
} = naturalizedDataset; } = naturalizedDataset;
this._getAndCacheStudyDataset(StudyInstanceUID, dicomJSONDataset);
const study = this._getAndCacheStudy(StudyInstanceUID); const study = this._getAndCacheStudy(StudyInstanceUID);
const series = this._getAndCacheSeriesFromStudy(study, SeriesInstanceUID); const series = this._getAndCacheSeriesFromStudy(study, SeriesInstanceUID);
const instance = this._getAndCacheInstanceFromStudy(series, SOPInstanceUID); const instance = this._getAndCacheInstanceFromStudy(series, SOPInstanceUID);
@@ -71,6 +73,16 @@ class MetadataProvider {
this.imageIdToUIDs.set(imageId, uids); this.imageIdToUIDs.set(imageId, uids);
} }
_getAndCacheStudyDataset(StudyInstanceUID, dataset) {
if (!this.datasets[StudyInstanceUID]) {
this.datasets[StudyInstanceUID] = dataset;
}
}
getStudyDataset(StudyInstanceUID) {
return this.datasets[StudyInstanceUID];
}
_getAndCacheStudy(StudyInstanceUID) { _getAndCacheStudy(StudyInstanceUID) {
const studies = this.studies; const studies = this.studies;
@@ -83,6 +95,7 @@ class MetadataProvider {
return study; return study;
} }
_getAndCacheSeriesFromStudy(study, SeriesInstanceUID) { _getAndCacheSeriesFromStudy(study, SeriesInstanceUID) {
let series = study.series.get(SeriesInstanceUID); let series = study.series.get(SeriesInstanceUID);
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.7.1...@ohif/ui@1.8.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.7.0...@ohif/ui@1.7.1) (2020-12-03)
### Bug Fixes
* fallback to original dataset VR for XS ([f57725a](https://github.com/OHIF/Viewers/commit/f57725ac8bfd59ea46ab334a1823882afadefba1))
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.6.0...@ohif/ui@1.7.0) (2020-12-03) # [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.6.0...@ohif/ui@1.7.0) (2020-12-03)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "1.7.0", "version": "1.8.0",
"description": "A set of React components for Medical Imaging Viewers", "description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -15,8 +15,8 @@ const ErrorFallback = ({ error, componentStack, resetErrorBoundary }) => {
const OHIFErrorBoundary = ({ const OHIFErrorBoundary = ({
context = 'OHIF', context = 'OHIF',
onReset = () => {}, onReset = () => { },
onError = () => {}, onError = () => { },
fallbackComponent, fallbackComponent,
children, children,
}) => { }) => {
@@ -45,7 +45,7 @@ OHIFErrorBoundary.propTypes = {
onReset: PropTypes.func, onReset: PropTypes.func,
onError: PropTypes.func, onError: PropTypes.func,
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
fallbackComponent: PropTypes.element, fallbackComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func, PropTypes.element]),
}; };
export default OHIFErrorBoundary; export default OHIFErrorBoundary;
@@ -37,6 +37,10 @@ const SnackbarProvider = ({ children, service }) => {
return null; return null;
} }
if (options.type === 'error') {
console.error(options.error);
}
const newItem = { const newItem = {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
...options, ...options,
+54
View File
@@ -3,6 +3,60 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [4.8.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.7.2...@ohif/viewer@4.8.0) (2020-12-10)
### Features
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
## [4.7.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.7.1...@ohif/viewer@4.7.2) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
## [4.7.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.7.0...@ohif/viewer@4.7.1) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
# [4.7.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.3...@ohif/viewer@4.7.0) (2020-12-03)
### Features
* Update approach to better handle rtdose instead of disabling panel ([f3cecf1](https://github.com/OHIF/Viewers/commit/f3cecf129b16f8d24d239aa44fdff7f46e6ba683))
## [4.6.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.2...@ohif/viewer@4.6.3) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
## [4.6.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.1...@ohif/viewer@4.6.2) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer
## [4.6.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.0...@ohif/viewer@4.6.1) (2020-12-03) ## [4.6.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.0...@ohif/viewer@4.6.1) (2020-12-03)
**Note:** Version bump only for package @ohif/viewer **Note:** Version bump only for package @ohif/viewer
+9 -9
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/viewer", "name": "@ohif/viewer",
"version": "4.6.1", "version": "4.8.0",
"description": "OHIF Viewer", "description": "OHIF Viewer",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -49,18 +49,18 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@ohif/core": "^2.11.0", "@ohif/core": "^2.12.0",
"@ohif/extension-cornerstone": "^2.9.6", "@ohif/extension-cornerstone": "^2.10.0",
"@ohif/extension-debugging": "^0.1.6", "@ohif/extension-debugging": "^0.1.6",
"@ohif/extension-dicom-html": "^1.2.9", "@ohif/extension-dicom-html": "^1.3.0",
"@ohif/extension-dicom-microscopy": "^0.51.3", "@ohif/extension-dicom-microscopy": "^0.51.3",
"@ohif/extension-dicom-pdf": "^1.0.6", "@ohif/extension-dicom-pdf": "^1.0.6",
"@ohif/extension-dicom-rt": "^0.4.9", "@ohif/extension-dicom-rt": "^0.6.0",
"@ohif/extension-dicom-segmentation": "^0.4.7", "@ohif/extension-dicom-segmentation": "^0.5.0",
"@ohif/extension-lesion-tracker": "^0.2.1", "@ohif/extension-lesion-tracker": "^0.2.1",
"@ohif/extension-vtk": "^1.9.1", "@ohif/extension-vtk": "^1.10.0",
"@ohif/i18n": "^0.53.0", "@ohif/i18n": "^0.53.0",
"@ohif/ui": "^1.7.0", "@ohif/ui": "^1.8.0",
"@tanem/react-nprogress": "^1.1.25", "@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"core-js": "^3.2.1", "core-js": "^3.2.1",
@@ -68,7 +68,7 @@
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.16.5", "dcmjs": "0.17.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"dicomweb-client": "^0.4.4", "dicomweb-client": "^0.4.4",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
+26 -31
View File
@@ -1,41 +1,36 @@
import './SidePanel.css'; import './SidePanel.css';
import React, { Component } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
class SidePanel extends Component { const SidePanel = ({ from, isOpen, children, width }) => {
static propTypes = { const fromSideClass = from === 'right' ? 'from-right' : 'from-left';
from: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
children: PropTypes.node,
width: PropTypes.string,
};
render() { const styles = width
const fromSideClass = ? {
this.props.from === 'right' ? 'from-right' : 'from-left'; maxWidth: width,
marginRight: isOpen ? '0' : Number.parseInt(width) * -1,
}
: {};
const styles = this.props.width return (
? { <section
maxWidth: this.props.width, style={styles}
marginRight: this.props.isOpen className={classNames('sidepanel', fromSideClass, {
? '0' 'is-open': isOpen,
: Number.parseInt(this.props.width) * -1, })}
} >
: {}; {children}
</section>
);
};
return ( SidePanel.propTypes = {
<section from: PropTypes.string.isRequired,
style={styles} isOpen: PropTypes.bool.isRequired,
className={classNames('sidepanel', fromSideClass, { children: PropTypes.node,
'is-open': this.props.isOpen, width: PropTypes.string,
})} };
>
{this.props.children}
</section>
);
}
}
export default SidePanel; export default SidePanel;
@@ -12,7 +12,7 @@ import EmptyViewport from './EmptyViewport.js';
const { loadAndCacheDerivedDisplaySets } = utils; const { loadAndCacheDerivedDisplaySets } = utils;
const ViewportGrid = function(props) { const ViewportGrid = function (props) {
const { const {
activeViewportIndex, activeViewportIndex,
availablePlugins, availablePlugins,
@@ -48,6 +48,7 @@ const ViewportGrid = function(props) {
title: 'Error loading derived display set:', title: 'Error loading derived display set:',
message: error.message, message: error.message,
type: 'error', type: 'error',
error,
autoClose: false, autoClose: false,
}); });
}); });
@@ -83,6 +83,8 @@ class Viewer extends Component {
disassociate: this.disassociateStudy, disassociate: this.disassociateStudy,
}, },
}); });
this._getActiveViewport = this._getActiveViewport.bind(this);
} }
state = { state = {
@@ -214,6 +216,10 @@ class Viewer extends Component {
} }
} }
_getActiveViewport() {
return this.props.viewports[this.props.activeViewportIndex];
}
render() { render() {
let VisiblePanelLeft, VisiblePanelRight; let VisiblePanelLeft, VisiblePanelRight;
const panelExtensions = extensionManager.modules[MODULE_TYPES.PANEL]; const panelExtensions = extensionManager.modules[MODULE_TYPES.PANEL];
@@ -314,11 +320,11 @@ class Viewer extends Component {
activeIndex={this.props.activeViewportIndex} activeIndex={this.props.activeViewportIndex}
/> />
) : ( ) : (
<ConnectedStudyBrowser <ConnectedStudyBrowser
studies={this.state.thumbnails} studies={this.state.thumbnails}
studyMetadata={this.props.studies} studyMetadata={this.props.studies}
/> />
)} )}
</SidePanel> </SidePanel>
</ErrorBoundaryDialog> </ErrorBoundaryDialog>
@@ -341,6 +347,8 @@ class Viewer extends Component {
viewports={this.props.viewports} viewports={this.props.viewports}
studies={this.props.studies} studies={this.props.studies}
activeIndex={this.props.activeViewportIndex} activeIndex={this.props.activeViewportIndex}
activeViewport={this.props.viewports[this.props.activeViewportIndex]}
getActiveViewport={this._getActiveViewport}
/> />
)} )}
</SidePanel> </SidePanel>
@@ -363,7 +371,7 @@ export default withDialog(Viewer);
* @param {Study[]} studies * @param {Study[]} studies
* @param {DisplaySet[]} studies[].displaySets * @param {DisplaySet[]} studies[].displaySets
*/ */
const _mapStudiesToThumbnails = function(studies) { const _mapStudiesToThumbnails = function (studies) {
return studies.map(study => { return studies.map(study => {
const { StudyInstanceUID } = study; const { StudyInstanceUID } = study;
+5 -4
View File
@@ -6619,14 +6619,15 @@ dateformat@^3.0.0:
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
dcmjs@0.16.5: dcmjs@0.17.0:
version "0.16.5" version "0.17.0"
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.16.5.tgz#05aa0c8d539a4d5777f996b9b796932d1f848993" resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.17.0.tgz#dbfd6db3efd2936f9bead9c902b7db396f64e9f5"
integrity sha512-Y92KAx4JSfev79xjYhaukQZjWgyJlaHnXSTdbtkGkJAHERjIakgHcCIPmACuVmtW/nHViTu04zIGAB0pmZ4GaA== integrity sha512-7JnNqNWXqjGyspLRwKM4nn+TU/+w3iMl9boU4eHrr831M4crMWWIiSQDejpOnfDT9+opqyeaYcNkiekr7p6TFg==
dependencies: dependencies:
"@babel/polyfill" "^7.8.3" "@babel/polyfill" "^7.8.3"
"@babel/runtime" "^7.8.4" "@babel/runtime" "^7.8.4"
gl-matrix "^3.1.0" gl-matrix "^3.1.0"
lodash.clonedeep "^4.5.0"
loglevelnext "^3.0.1" loglevelnext "^3.0.1"
ndarray "^1.0.19" ndarray "^1.0.19"