feat: new Total Metabolic Tumor Volume tmtv (#2866)
* fix: ptDisplaySet metadata problem * fix: typo in xhrHeader field * fix: temporary fix for crosshairs on reload data * fix hotkey bug for input fields * upgrade docusarusu to stable release * circle ci node upgrade * cypress node upgrade * bump node in netlify * bump node version * remove versioned docs * update resources * fix: webpack injection of plugins * fix meaningful label for panels * add reviews and fix cli for creation * copy over chunks if exists in the build * edit templates * edit templates * fix yarn lock * fix(plugin):Deploy on windows is broken Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
1 parent
ae897c47e0
commit
10b4ed5f8c
41 files changed
+2020
-811
No files matched your search
@@ -45,7 +45,7 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.3",
|
||||
"classnames": "^2.2.6",
|
||||
"@cornerstonejs/core": "^0.13.11",
|
||||
"@cornerstonejs/tools": "^0.20.15"
|
||||
"@cornerstonejs/core": "^0.14.4",
|
||||
"@cornerstonejs/tools": "^0.21.7"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^3.0.0",
|
||||
"@ohif/ui": "^2.0.0",
|
||||
"cornerstone-wado-image-loader": "^4.2.0",
|
||||
"cornerstone-wado-image-loader": "^4.2.1",
|
||||
"dcmjs": "^0.24.5",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -43,9 +43,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.17.9",
|
||||
"@cornerstonejs/core": "^0.13.11",
|
||||
"@cornerstonejs/streaming-image-volume-loader": "^0.4.10",
|
||||
"@cornerstonejs/tools": "^0.20.15",
|
||||
"@cornerstonejs/core": "^0.14.4",
|
||||
"@cornerstonejs/streaming-image-volume-loader": "^0.4.16",
|
||||
"@cornerstonejs/tools": "^0.21.7",
|
||||
"@kitware/vtk.js": "^24.18.7",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"lodash.debounce": "4.0.8",
|
||||
|
||||
@@ -195,9 +195,11 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
||||
DisplaySetService
|
||||
);
|
||||
|
||||
const keepCamera = true;
|
||||
CornerstoneViewportService.updateViewport(
|
||||
viewportIndex,
|
||||
newViewportData
|
||||
newViewportData,
|
||||
keepCamera
|
||||
);
|
||||
|
||||
setViewportData(newViewportData);
|
||||
|
||||
@@ -41,7 +41,7 @@ function CornerstoneOverlays(props) {
|
||||
viewportIndex
|
||||
);
|
||||
|
||||
if (viewportInfo?.viewportOptions?.customViewportOptions?.hideOverlays) {
|
||||
if (viewportInfo?.viewportOptions?.customViewportProps?.hideOverlays) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,19 +115,18 @@ const commandsModule = ({ servicesManager }) => {
|
||||
const renderingEngine = CornerstoneViewportService.getRenderingEngine();
|
||||
const viewport = renderingEngine.getViewport(viewportId);
|
||||
|
||||
const lower = windowCenterNum - windowWidthNum / 2.0;
|
||||
const upper = windowCenterNum + windowWidthNum / 2.0;
|
||||
const { lower, upper } = csUtils.windowLevel.toLowHighRange(
|
||||
windowWidthNum,
|
||||
windowCenterNum
|
||||
);
|
||||
|
||||
if (viewport instanceof StackViewport) {
|
||||
viewport.setProperties({
|
||||
voiRange: {
|
||||
upper,
|
||||
lower,
|
||||
},
|
||||
});
|
||||
|
||||
viewport.render();
|
||||
}
|
||||
viewport.setProperties({
|
||||
voiRange: {
|
||||
upper,
|
||||
lower,
|
||||
},
|
||||
});
|
||||
viewport.render();
|
||||
},
|
||||
toggleCrosshairs({ toolGroupId, toggledState }) {
|
||||
const toolName = 'Crosshairs';
|
||||
|
||||
@@ -166,14 +166,12 @@ class CornerstoneViewportService implements IViewportService {
|
||||
const viewportInfo = this.viewportsInfo.get(viewportIndex);
|
||||
viewportInfo.setRenderingEngineId(renderingEngine.id);
|
||||
|
||||
const {
|
||||
viewportOptions,
|
||||
displaySetOptions,
|
||||
} = this._getViewportAndDisplaySetOptions(
|
||||
publicViewportOptions,
|
||||
publicDisplaySetOptions,
|
||||
viewportInfo
|
||||
);
|
||||
const { viewportOptions, displaySetOptions } =
|
||||
this._getViewportAndDisplaySetOptions(
|
||||
publicViewportOptions,
|
||||
publicDisplaySetOptions,
|
||||
viewportInfo
|
||||
);
|
||||
|
||||
viewportInfo.setViewportOptions(viewportOptions);
|
||||
viewportInfo.setDisplaySetOptions(displaySetOptions);
|
||||
@@ -400,48 +398,57 @@ class CornerstoneViewportService implements IViewportService {
|
||||
volume.load();
|
||||
});
|
||||
|
||||
this.setVolumesForViewport(viewport, volumeInputArray);
|
||||
// This returns the async continuation only
|
||||
return this.setVolumesForViewport(viewport, volumeInputArray);
|
||||
}
|
||||
|
||||
public setVolumesForViewport(viewport, volumeInputArray) {
|
||||
viewport.setVolumes(volumeInputArray).then(() => {
|
||||
const viewportInfo = this.getViewportInfo(viewport.id);
|
||||
const initialImageOptions = viewportInfo.getInitialImageOptions();
|
||||
public async setVolumesForViewport(viewport, volumeInputArray) {
|
||||
await viewport.setVolumes(volumeInputArray);
|
||||
|
||||
if (
|
||||
initialImageOptions &&
|
||||
(initialImageOptions.preset !== undefined ||
|
||||
initialImageOptions.index !== undefined)
|
||||
) {
|
||||
const { index, preset } = initialImageOptions;
|
||||
const viewportInfo = this.getViewportInfo(viewport.id);
|
||||
const initialImageOptions = viewportInfo.getInitialImageOptions();
|
||||
|
||||
const { numberOfSlices } = csUtils.getImageSliceDataForVolumeViewport(
|
||||
viewport
|
||||
);
|
||||
if (
|
||||
initialImageOptions &&
|
||||
(initialImageOptions.preset !== undefined ||
|
||||
initialImageOptions.index !== undefined)
|
||||
) {
|
||||
const { index, preset } = initialImageOptions;
|
||||
|
||||
const imageIndex = this._getInitialImageIndex(
|
||||
numberOfSlices,
|
||||
index,
|
||||
preset
|
||||
);
|
||||
const { numberOfSlices } =
|
||||
csUtils.getImageSliceDataForVolumeViewport(viewport);
|
||||
|
||||
csToolsUtils.jumpToSlice(viewport.element, {
|
||||
imageIndex,
|
||||
});
|
||||
}
|
||||
const imageIndex = this._getInitialImageIndex(
|
||||
numberOfSlices,
|
||||
index,
|
||||
preset
|
||||
);
|
||||
|
||||
viewport.render();
|
||||
});
|
||||
csToolsUtils.jumpToSlice(viewport.element, {
|
||||
imageIndex,
|
||||
});
|
||||
}
|
||||
|
||||
viewport.render();
|
||||
}
|
||||
|
||||
public updateViewport(viewportIndex, viewportData) {
|
||||
// Todo: keepCamera is an interim solution until we have a better solution for
|
||||
// keeping the camera position when the viewport data is changed
|
||||
public updateViewport(viewportIndex, viewportData, keepCamera = false) {
|
||||
const viewportInfo = this.getViewportInfoByIndex(viewportIndex);
|
||||
|
||||
const viewportId = viewportInfo.getViewportId();
|
||||
const viewport = this.getCornerstoneViewport(viewportId);
|
||||
const viewportCamera = viewport.getCamera();
|
||||
|
||||
if (viewport instanceof VolumeViewport) {
|
||||
this._setVolumeViewport(viewport, viewportData, viewportInfo);
|
||||
this._setVolumeViewport(viewport, viewportData, viewportInfo).then(() => {
|
||||
if (keepCamera) {
|
||||
viewport.setCamera(viewportCamera);
|
||||
viewport.render();
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export type ViewportOptions = {
|
||||
initialView?: string;
|
||||
syncGroups?: SyncGroup[];
|
||||
initialImageOptions?: InitialImageOptions;
|
||||
customViewportOptions?: Record<string, unknown>;
|
||||
customViewportProps?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type PublicViewportOptions = {
|
||||
@@ -31,7 +31,7 @@ export type PublicViewportOptions = {
|
||||
initialView?: string;
|
||||
syncGroups?: SyncGroup[];
|
||||
initialImageOptions?: InitialImageOptions;
|
||||
customViewportOptions?: Record<string, unknown>;
|
||||
customViewportProps?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type PublicDisplaySetOptions = {
|
||||
|
||||
@@ -172,8 +172,8 @@ function getDisplayText(mappedAnnotations, displaySet) {
|
||||
|
||||
// Area is the same for all series
|
||||
const { length, width, SeriesNumber, SOPInstanceUID } = mappedAnnotations[0];
|
||||
const roundedLength = utils.roundNumber(length, 1);
|
||||
const roundedWidth = utils.roundNumber(width, 1);
|
||||
const roundedLength = utils.roundNumber(length, 2);
|
||||
const roundedWidth = utils.roundNumber(width, 2);
|
||||
|
||||
const instance = displaySet.images.find(
|
||||
image => image.SOPInstanceUID === SOPInstanceUID
|
||||
|
||||
@@ -23,7 +23,6 @@ export default function getPTImageIdInstanceMetadata(
|
||||
dicomMetaData.CorrectedImage === undefined ||
|
||||
dicomMetaData.Units === undefined ||
|
||||
!dicomMetaData.RadiopharmaceuticalInformationSequence ||
|
||||
!dicomMetaData.RadiopharmaceuticalInformationSequence.length ||
|
||||
dicomMetaData.RadiopharmaceuticalInformationSequence[0]
|
||||
.RadionuclideHalfLife === undefined ||
|
||||
dicomMetaData.RadiopharmaceuticalInformationSequence[0]
|
||||
|
||||
@@ -36,22 +36,22 @@ const handlePETImageMetadata = ({ SeriesInstanceUID, StudyInstanceUID }) => {
|
||||
return;
|
||||
}
|
||||
const imageIds = instances.map(instance => instance.imageId);
|
||||
const InstanceMetadataArray = [];
|
||||
const instanceMetadataArray = [];
|
||||
imageIds.forEach(imageId => {
|
||||
const instanceMetadata = getPTImageIdInstanceMetadata(imageId);
|
||||
if (instanceMetadata) {
|
||||
InstanceMetadataArray.push(instanceMetadata);
|
||||
instanceMetadataArray.push(instanceMetadata);
|
||||
}
|
||||
});
|
||||
|
||||
if (!InstanceMetadataArray.length) {
|
||||
if (!instanceMetadataArray.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// try except block to prevent errors when the metadata is not correct
|
||||
let suvScalingFactors;
|
||||
try {
|
||||
suvScalingFactors = calculateSUVScalingFactors(InstanceMetadataArray);
|
||||
suvScalingFactors = calculateSUVScalingFactors(instanceMetadataArray);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
@@ -60,7 +60,7 @@ const handlePETImageMetadata = ({ SeriesInstanceUID, StudyInstanceUID }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
InstanceMetadataArray.forEach((instanceMetadata, index) => {
|
||||
instanceMetadataArray.forEach((instanceMetadata, index) => {
|
||||
metadataProvider.addCustomMetadata(
|
||||
imageIds[index],
|
||||
'scalingModule',
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^3.0.0",
|
||||
"classnames": "^2.2.6",
|
||||
"@cornerstonejs/core": "^0.13.11",
|
||||
"@cornerstonejs/tools": "^0.20.15",
|
||||
"@cornerstonejs/core": "^0.14.4",
|
||||
"@cornerstonejs/tools": "^0.21.7",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "^3.0.0",
|
||||
"dcmjs": "^0.24.5",
|
||||
"prop-types": "^15.6.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Input, Button } from '@ohif/ui';
|
||||
import { classes, DicomMetadataStore } from '@ohif/core';
|
||||
import { DicomMetadataStore } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DEFAULT_MEATADATA = {
|
||||
@@ -24,7 +24,8 @@ const DEFAULT_MEATADATA = {
|
||||
*/
|
||||
export default function PanelPetSUV({ servicesManager, commandsManager }) {
|
||||
const { t } = useTranslation('PanelSUV');
|
||||
const { DisplaySetService } = servicesManager.services;
|
||||
const { DisplaySetService, ToolGroupService, ToolBarService } =
|
||||
servicesManager.services;
|
||||
const [metadata, setMetadata] = useState(DEFAULT_MEATADATA);
|
||||
const [ptDisplaySet, setPtDisplaySet] = useState(null);
|
||||
|
||||
@@ -108,6 +109,26 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
|
||||
if (!ptDisplaySet) {
|
||||
throw new Error('No ptDisplaySet found');
|
||||
}
|
||||
|
||||
const toolGroupIds = ToolGroupService.getToolGroupIds();
|
||||
|
||||
// Todo: we don't have a proper way to perform a toggle command and update the
|
||||
// state for the toolbar, so here, we manually toggle the toolbar
|
||||
|
||||
// Todo: Crosshairs have bugs for the camera reset currently, so we need to
|
||||
// force turn it off before we update the metadata
|
||||
toolGroupIds.forEach(toolGroupId => {
|
||||
commandsManager.runCommand('toggleCrosshairs', {
|
||||
toolGroupId,
|
||||
toggledState: false,
|
||||
});
|
||||
});
|
||||
|
||||
ToolBarService.state.toggles['Crosshairs'] = false;
|
||||
ToolBarService._broadcastEvent(
|
||||
ToolBarService.EVENTS.TOOL_BAR_STATE_MODIFIED
|
||||
);
|
||||
|
||||
// metadata should be dcmjs naturalized
|
||||
DicomMetadataStore.updateMetadataForSeries(
|
||||
ptDisplaySet.StudyInstanceUID,
|
||||
@@ -140,7 +161,7 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
label={t('Patient Weight')}
|
||||
label={t('Patient Weight (kg)')}
|
||||
labelClassName="text-white"
|
||||
className="mt-1 mb-2 bg-black border-primary-main"
|
||||
type="text"
|
||||
@@ -153,7 +174,7 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
label={t('Total Dose')}
|
||||
label={t('Total Dose (Mbq)')}
|
||||
labelClassName="text-white"
|
||||
className="mt-1 mb-2 bg-black border-primary-main"
|
||||
type="text"
|
||||
@@ -171,7 +192,7 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
label={t('Half Life')}
|
||||
label={t('Half Life (s)')}
|
||||
labelClassName="text-white"
|
||||
className="mt-1 mb-2 bg-black border-primary-main"
|
||||
type="text"
|
||||
@@ -189,7 +210,7 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
label={t('Injection Time')}
|
||||
label={t('Injection Time (Seconds)')}
|
||||
labelClassName="text-white"
|
||||
className="mt-1 mb-2 bg-black border-primary-main"
|
||||
type="text"
|
||||
@@ -207,7 +228,7 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
label={t('Acquisition Time')}
|
||||
label={t('Acquisition Time (Seconds)')}
|
||||
labelClassName="text-white"
|
||||
className="mt-1 mb-2 bg-black border-primary-main"
|
||||
type="text"
|
||||
|
||||
+1
-1
@@ -297,7 +297,7 @@ export default function PanelRoiThresholdSegmentation({
|
||||
name={'info'}
|
||||
className={'ml-4 mr-3 text-primary-active'}
|
||||
/>
|
||||
<span>{'About'}</span>
|
||||
<span>{'User Guide'}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -325,6 +325,12 @@ const ptCT = {
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'ptFusionWLSync',
|
||||
source: true,
|
||||
target: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
@@ -360,6 +366,12 @@ const ptCT = {
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'ptFusionWLSync',
|
||||
source: true,
|
||||
target: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
@@ -395,6 +407,12 @@ const ptCT = {
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'ptFusionWLSync',
|
||||
source: true,
|
||||
target: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
@@ -439,6 +457,12 @@ const ptCT = {
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'ptFusionWLSync',
|
||||
source: false,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
@@ -486,6 +510,12 @@ const ptCT = {
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'ptFusionWLSync',
|
||||
source: false,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
@@ -533,6 +563,12 @@ const ptCT = {
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'ptFusionWLSync',
|
||||
source: false,
|
||||
target: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
@@ -558,9 +594,24 @@ const ptCT = {
|
||||
orientation: 'sagittal',
|
||||
background: [1, 1, 1],
|
||||
toolGroupId: 'mipToolGroup',
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'ptWLSync',
|
||||
source: true,
|
||||
target: true,
|
||||
},
|
||||
{
|
||||
type: 'voi',
|
||||
id: 'ptFusionWLSync',
|
||||
source: true,
|
||||
target: false,
|
||||
},
|
||||
],
|
||||
|
||||
// Custom props can be used to set custom properties which extensions
|
||||
// can react on.
|
||||
customViewportOptions: {
|
||||
customViewportProps: {
|
||||
// We use viewportDisplay to filter the viewports which are displayed
|
||||
// in mip and we set the scrollbar according to their rotation index
|
||||
// in the cornerstone extension.
|
||||
|
||||
@@ -36,14 +36,14 @@ function getPanelModule({
|
||||
name: 'petSUV',
|
||||
iconName: 'edit-patient',
|
||||
iconLabel: 'PET SUV',
|
||||
label: 'PET-SUV',
|
||||
label: 'PET SUV',
|
||||
component: wrappedPanelPetSuv,
|
||||
},
|
||||
{
|
||||
name: 'ROIThresholdSeg',
|
||||
iconName: 'tool-create-threshold',
|
||||
iconLabel: 'Threshold Seg',
|
||||
label: 'Threshold-Seg',
|
||||
iconLabel: 'ROI Threshold',
|
||||
label: 'ROI Threshold',
|
||||
component: wrappedROIThresholdSeg,
|
||||
},
|
||||
];
|
||||
|
||||
Reference in new issue
Block a user