Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d282f72d75 | ||
|
|
29fceacee9 | ||
|
|
a7a5a359dc | ||
|
|
e362e895d4 | ||
|
|
45a07dfa19 | ||
|
|
7323e63608 | ||
|
|
0baf18dcb2 | ||
|
|
cca3d6bff2 | ||
|
|
f3cecf129b | ||
|
|
fe03fc6f55 | ||
|
|
b59187c74d | ||
|
|
5210c80369 | ||
|
|
f57725ac8b |
No files matched your search
@@ -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 || ''),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
+27
-5
@@ -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());
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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,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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user