diff --git a/extensions/cornerstone-dicom-seg/package.json b/extensions/cornerstone-dicom-seg/package.json
index 450769f9513..a054f16d998 100644
--- a/extensions/cornerstone-dicom-seg/package.json
+++ b/extensions/cornerstone-dicom-seg/package.json
@@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/adapters": "^1.70.9",
- "@cornerstonejs/core": "^1.70.9",
+ "@cornerstonejs/adapters": "^1.70.10",
+ "@cornerstonejs/core": "^1.70.10",
"@kitware/vtk.js": "30.3.3",
"react-color": "^2.19.3"
}
diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json
index a30cc98df8a..e7d6df5d17b 100644
--- a/extensions/cornerstone-dicom-sr/package.json
+++ b/extensions/cornerstone-dicom-sr/package.json
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/adapters": "^1.70.9",
- "@cornerstonejs/core": "^1.70.9",
- "@cornerstonejs/tools": "^1.70.9",
+ "@cornerstonejs/adapters": "^1.70.10",
+ "@cornerstonejs/core": "^1.70.10",
+ "@cornerstonejs/tools": "^1.70.10",
"classnames": "^2.3.2"
}
}
diff --git a/extensions/cornerstone-dynamic-volume/package.json b/extensions/cornerstone-dynamic-volume/package.json
index d8100e68c81..449b8e2d5d6 100644
--- a/extensions/cornerstone-dynamic-volume/package.json
+++ b/extensions/cornerstone-dynamic-volume/package.json
@@ -42,9 +42,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/core": "^1.70.9",
- "@cornerstonejs/streaming-image-volume-loader": "^1.70.9",
- "@cornerstonejs/tools": "^1.70.9",
+ "@cornerstonejs/core": "^1.70.10",
+ "@cornerstonejs/streaming-image-volume-loader": "^1.70.10",
+ "@cornerstonejs/tools": "^1.70.10",
"classnames": "^2.3.2"
}
}
diff --git a/extensions/cornerstone-dynamic-volume/src/getPanelModule.tsx b/extensions/cornerstone-dynamic-volume/src/getPanelModule.tsx
index a32b5243d57..41f45384797 100644
--- a/extensions/cornerstone-dynamic-volume/src/getPanelModule.tsx
+++ b/extensions/cornerstone-dynamic-volume/src/getPanelModule.tsx
@@ -43,21 +43,21 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
return [
{
name: 'dynamic-volume',
- iconName: 'group-layers',
+ iconName: 'tab-4d',
iconLabel: '4D Workflow',
label: '4D Workflow',
component: wrappedDynamicDataPanel,
},
{
name: 'dynamic-toolbox',
- iconName: 'group-layers',
+ iconName: 'tab-4d',
iconLabel: '4D Workflow',
label: 'Dynamic Toolbox',
component: wrappedDynamicToolbox,
},
{
name: 'dynamic-export',
- iconName: 'group-layers',
+ iconName: 'tab-4d',
iconLabel: '4D Workflow',
label: '4D Workflow',
component: wrappedDynamicExport,
diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json
index e48602a993a..6ff3f70faec 100644
--- a/extensions/cornerstone/package.json
+++ b/extensions/cornerstone/package.json
@@ -38,7 +38,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
"@cornerstonejs/codec-openjph": "^2.4.2",
- "@cornerstonejs/dicom-image-loader": "^1.70.9",
+ "@cornerstonejs/dicom-image-loader": "^1.70.10",
"@icr/polyseg-wasm": "^0.4.0",
"@ohif/core": "3.8.0-beta.86",
"@ohif/ui": "3.8.0-beta.86",
@@ -55,10 +55,10 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
- "@cornerstonejs/adapters": "^1.70.9",
- "@cornerstonejs/core": "^1.70.9",
- "@cornerstonejs/streaming-image-volume-loader": "^1.70.9",
- "@cornerstonejs/tools": "^1.70.9",
+ "@cornerstonejs/adapters": "^1.70.10",
+ "@cornerstonejs/core": "^1.70.10",
+ "@cornerstonejs/streaming-image-volume-loader": "^1.70.10",
+ "@cornerstonejs/tools": "^1.70.10",
"@icr/polyseg-wasm": "^0.4.0",
"@kitware/vtk.js": "30.3.3",
"html2canvas": "^1.4.1",
diff --git a/extensions/cornerstone/src/commandsModule.ts b/extensions/cornerstone/src/commandsModule.ts
index f72a710510d..ecabf31fb64 100644
--- a/extensions/cornerstone/src/commandsModule.ts
+++ b/extensions/cornerstone/src/commandsModule.ts
@@ -346,9 +346,9 @@ function commandsModule({
}
}
},
- setToolActiveToolbar: ({ value, itemId, toolGroupIds = [] }) => {
+ setToolActiveToolbar: ({ value, itemId, toolName, toolGroupIds = [] }) => {
// Sometimes it is passed as value (tools with options), sometimes as itemId (toolbar buttons)
- const toolName = itemId || value;
+ toolName = toolName || itemId || value;
toolGroupIds = toolGroupIds.length ? toolGroupIds : toolGroupService.getToolGroupIds();
diff --git a/extensions/cornerstone/src/components/ViewportWindowLevel/getViewportVolumeHistogram.ts b/extensions/cornerstone/src/components/ViewportWindowLevel/getViewportVolumeHistogram.ts
index 2d72229fc95..167d49f8a02 100644
--- a/extensions/cornerstone/src/components/ViewportWindowLevel/getViewportVolumeHistogram.ts
+++ b/extensions/cornerstone/src/components/ViewportWindowLevel/getViewportVolumeHistogram.ts
@@ -2,10 +2,12 @@ import { getWebWorkerManager } from '@cornerstonejs/core';
const workerManager = getWebWorkerManager();
-const options = {
- // maxWorkerInstances: 1,
- // overwrite: false
- autoTerminationOnIdle: 1000,
+const WorkerOptions = {
+ maxWorkerInstances: 1,
+ autoTerminateOnIdle: {
+ enabled: true,
+ idleTimeThreshold: 1000,
+ },
};
// Register the task
@@ -15,9 +17,9 @@ const workerFn = () => {
});
};
-workerManager.registerWorker('histogram-worker', workerFn, options);
-
const getViewportVolumeHistogram = async (viewport, volume, options?) => {
+ workerManager.registerWorker('histogram-worker', workerFn, WorkerOptions);
+
if (!volume?.loadStatus.loaded) {
return undefined;
}
diff --git a/extensions/cornerstone/src/init.tsx b/extensions/cornerstone/src/init.tsx
index 9e5f2c38c49..52993059b87 100644
--- a/extensions/cornerstone/src/init.tsx
+++ b/extensions/cornerstone/src/init.tsx
@@ -279,19 +279,18 @@ export default async function init({
eventTarget.addEventListener(EVENTS.ELEMENT_DISABLED, elementDisabledHandler.bind(null));
colormaps.forEach(registerColormap);
- // Create a debounced function that shows the notification
- const debouncedShowNotification = debounce(detail => {
- uiNotificationService.show({
- title: detail.type,
- message: detail.message,
- type: 'error',
- });
- }, 300);
-
// Event listener
- eventTarget.addEventListener(EVENTS.ERROR_EVENT, ({ detail }) => {
- debouncedShowNotification(detail);
- });
+ eventTarget.addEventListenerDebounced(
+ EVENTS.ERROR_EVENT,
+ ({ detail }) => {
+ uiNotificationService.show({
+ title: detail.type,
+ message: detail.message,
+ type: 'error',
+ });
+ },
+ 1000
+ );
}
function CPUModal() {
diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json
index 1bf2091b71e..7371e05e0bb 100644
--- a/extensions/measurement-tracking/package.json
+++ b/extensions/measurement-tracking/package.json
@@ -32,8 +32,8 @@
"start": "yarn run dev"
},
"peerDependencies": {
- "@cornerstonejs/core": "^1.70.9",
- "@cornerstonejs/tools": "^1.70.9",
+ "@cornerstonejs/core": "^1.70.10",
+ "@cornerstonejs/tools": "^1.70.10",
"@ohif/core": "3.8.0-beta.86",
"@ohif/extension-cornerstone-dicom-sr": "3.8.0-beta.86",
"@ohif/ui": "3.8.0-beta.86",
diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdExport.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdExport.tsx
index 44582ed968b..bcbe0a4a7f3 100644
--- a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdExport.tsx
+++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdExport.tsx
@@ -2,11 +2,16 @@ import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Icon, ActionButtons } from '@ohif/ui';
import { useTranslation } from 'react-i18next';
+import { eventTarget } from '@cornerstonejs/core';
+import { Enums } from '@cornerstonejs/tools';
+import { handleROIThresholding } from '../../utils/handleROIThresholding';
+
export default function PanelRoiThresholdSegmentation({ servicesManager, commandsManager }) {
const { segmentationService, uiNotificationService } = servicesManager.services;
const { t } = useTranslation('PanelSUVExport');
const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations());
+ const [activeSegmentation, setActiveSegmentation] = useState(null);
/**
* Update UI based on segmentation changes (added, removed, updated)
@@ -22,7 +27,11 @@ export default function PanelRoiThresholdSegmentation({ servicesManager, command
const { unsubscribe } = segmentationService.subscribe(evt, () => {
const segmentations = segmentationService.getSegmentations();
setSegmentations(segmentations);
+
+ const activeSegmentation = segmentations.filter(seg => seg.isActive);
+ setActiveSegmentation(activeSegmentation[0]);
});
+
subscriptions.push(unsubscribe);
});
@@ -33,26 +42,53 @@ export default function PanelRoiThresholdSegmentation({ servicesManager, command
};
}, []);
- const tmtvValue = segmentations?.[0]?.cachedStats?.tmtv?.value || null;
- const config = segmentations?.[0]?.cachedStats?.tmtv?.config || {};
-
- segmentations.forEach(segmentation => {
- const { cachedStats } = segmentation;
- if (!cachedStats) {
- return;
- }
+ useEffect(() => {
+ const callback = async evt => {
+ const { detail } = evt;
+ const { segmentationId } = detail;
- // segment 1
- const suvPeak = cachedStats?.['1']?.suvPeak?.suvPeak;
+ if (!segmentationId) {
+ return;
+ }
- if (Number.isNaN(suvPeak)) {
- uiNotificationService.show({
- title: 'SUV Peak',
- message: 'Segmented volume does not allow SUV Peak calculation',
- type: 'warning',
+ await handleROIThresholding({
+ segmentationId,
+ commandsManager,
+ segmentationService,
});
- }
- });
+
+ const segmentation = segmentationService.getSegmentation(segmentationId);
+
+ const { cachedStats } = segmentation;
+ if (!cachedStats) {
+ return;
+ }
+
+ // segment 1
+ const suvPeak = cachedStats?.['1']?.suvPeak?.suvPeak;
+
+ if (Number.isNaN(suvPeak)) {
+ uiNotificationService.show({
+ title: 'SUV Peak',
+ message: 'Segmented volume does not allow SUV Peak calculation',
+ type: 'warning',
+ });
+ }
+ };
+
+ eventTarget.addEventListenerDebounced(Enums.Events.SEGMENTATION_DATA_MODIFIED, callback, 300);
+
+ return () => {
+ eventTarget.removeEventListenerDebounced(Enums.Events.SEGMENTATION_DATA_MODIFIED, callback);
+ };
+ }, []);
+
+ if (!activeSegmentation) {
+ return null;
+ }
+
+ const tmtvValue = activeSegmentation.cachedStats?.tmtv?.value || null;
+ const config = activeSegmentation.cachedStats?.tmtv?.config || {};
const actions = [
{
@@ -67,7 +103,7 @@ export default function PanelRoiThresholdSegmentation({ servicesManager, command
disabled: tmtvValue === null,
},
{
- label: 'Create RT Report',
+ label: 'Export RT Report',
onClick: () => {
commandsManager.runCommand('createTMTVRTReport');
},
@@ -80,7 +116,7 @@ export default function PanelRoiThresholdSegmentation({ servicesManager, command
{tmtvValue !== null ? (
-
+
{'TMTV:'}
diff --git a/extensions/tmtv/src/Panels/RectangleROIOptions.tsx b/extensions/tmtv/src/Panels/RectangleROIOptions.tsx
index 602bb5531df..5a227b652d1 100644
--- a/extensions/tmtv/src/Panels/RectangleROIOptions.tsx
+++ b/extensions/tmtv/src/Panels/RectangleROIOptions.tsx
@@ -62,72 +62,16 @@ function RectangleROIOptions({ servicesManager, commandsManager }) {
const handleROIThresholding = useCallback(() => {
const segmentationId = selectedSegmentationId;
-
- const segmentation = segmentationService.getSegmentation(segmentationId);
const activeSegmentIndex =
cs3dTools.segmentation.segmentIndex.getActiveSegmentIndex(segmentationId);
// run the threshold based on the active segment index
// Todo: later find a way to associate each rectangle with a segment (e.g., maybe with color?)
- const labelmap = runCommand('thresholdSegmentationByRectangleROITool', {
+ runCommand('thresholdSegmentationByRectangleROITool', {
segmentationId,
config,
segmentIndex: activeSegmentIndex,
});
-
- // re-calculating the cached stats for the active segmentation
- const updatedPerSegmentCachedStats = {};
- segmentation.segments = segmentation.segments.map(segment => {
- if (!segment || !segment.segmentIndex) {
- return segment;
- }
-
- const segmentIndex = segment.segmentIndex;
-
- const lesionStats = runCommand('getLesionStats', { labelmap, segmentIndex });
- const suvPeak = runCommand('calculateSuvPeak', { labelmap, segmentIndex });
- const lesionGlyoclysisStats = lesionStats.volume * lesionStats.meanValue;
-
- // update segDetails with the suv peak for the active segmentation
- const cachedStats = {
- lesionStats,
- suvPeak,
- lesionGlyoclysisStats,
- };
-
- segment.cachedStats = cachedStats;
- segment.displayText = [
- `SUV Peak: ${suvPeak.suvPeak.toFixed(2)}`,
- `Volume: ${lesionStats.volume.toFixed(2)} mm3`,
- ];
- updatedPerSegmentCachedStats[segmentIndex] = cachedStats;
-
- return segment;
- });
-
- const notYetUpdatedAtSource = true;
-
- const segmentations = segmentationService.getSegmentations();
- const tmtv = runCommand('calculateTMTV', { segmentations });
-
- segmentation.cachedStats = Object.assign(
- segmentation.cachedStats,
- updatedPerSegmentCachedStats,
- {
- tmtv: {
- value: tmtv.toFixed(3),
- config: { ...config },
- },
- }
- );
-
- segmentationService.addOrUpdateSegmentation(
- {
- ...segmentation,
- },
- false, // don't suppress events
- notYetUpdatedAtSource
- );
}, [selectedSegmentationId, config]);
useEffect(() => {
@@ -171,27 +115,6 @@ function RectangleROIOptions({ servicesManager, commandsManager }) {
};
}, []);
- useEffect(() => {
- const { unsubscribe } = segmentationService.subscribe(
- segmentationService.EVENTS.SEGMENTATION_REMOVED,
- () => {
- const segmentations = segmentationService.getSegmentations();
-
- if (segmentations.length > 0) {
- setSelectedSegmentationId(segmentations[0].id);
- handleROIThresholding();
- } else {
- setSelectedSegmentationId(null);
- handleROIThresholding();
- }
- }
- );
-
- return () => {
- unsubscribe();
- };
- }, []);
-
return (
{
+ return new Worker(new URL('./utils/calculateSUVPeakWorker.js', import.meta.url), {
+ name: 'suv-peak-worker', // name used by the browser to name the worker
+ });
+};
+
const commandsModule = ({ servicesManager, commandsManager, extensionManager }) => {
const {
viewportGridService,
@@ -265,7 +283,10 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager })
{ overwrite: true, segmentIndex }
);
},
- calculateSuvPeak: ({ labelmap, segmentIndex }) => {
+ calculateSuvPeak: async ({ labelmap, segmentIndex }) => {
+ // if we put it in the top, it will appear in other modes
+ workerManager.registerWorker('suv-peak-worker', workerFn, options);
+
const { referencedVolumeId } = labelmap;
const referencedVolume = cs.cache.getVolume(referencedVolumeId);
@@ -277,7 +298,31 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager })
csTools.annotation.state.getAnnotation(annotationUID)
);
- const suvPeak = calculateSuvPeak(labelmap, referencedVolume, annotations, segmentIndex);
+ const labelmapProps = {
+ dimensions: labelmap.dimensions,
+ origin: labelmap.origin,
+ direction: labelmap.direction,
+ spacing: labelmap.spacing,
+ scalarData: labelmap.scalarData,
+ metadata: labelmap.metadata,
+ };
+
+ const referenceVolumeProps = {
+ dimensions: referencedVolume.dimensions,
+ origin: referencedVolume.origin,
+ direction: referencedVolume.direction,
+ spacing: referencedVolume.spacing,
+ scalarData: referencedVolume.scalarData,
+ metadata: referencedVolume.metadata,
+ };
+
+ const suvPeak = await workerManager.executeTask('suv-peak-worker', 'calculateSuvPeak', {
+ labelmapProps,
+ referenceVolumeProps,
+ annotations,
+ segmentIndex,
+ });
+
return {
suvPeak: suvPeak.mean,
suvMax: suvPeak.max,
diff --git a/extensions/tmtv/src/utils/calculateSUVPeak.ts b/extensions/tmtv/src/utils/calculateSUVPeakWorker.js
similarity index 74%
rename from extensions/tmtv/src/utils/calculateSUVPeak.ts
rename to extensions/tmtv/src/utils/calculateSUVPeakWorker.js
index 6627d2e2b05..638ec9d4f82 100644
--- a/extensions/tmtv/src/utils/calculateSUVPeak.ts
+++ b/extensions/tmtv/src/utils/calculateSUVPeakWorker.js
@@ -1,18 +1,32 @@
-import { Types } from '@cornerstonejs/core';
import { utilities } from '@cornerstonejs/tools';
import { vec3 } from 'gl-matrix';
+import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
+import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
+import { expose } from 'comlink';
-type AnnotationsForThresholding = {
- data: {
- handles: {
- points: Types.Point3[];
- };
- cachedStats?: {
- projectionPoints?: Types.Point3[][];
- };
+const createVolume = ({ dimensions, origin, direction, spacing, scalarData, metadata }) => {
+ const imageData = vtkImageData.newInstance();
+ imageData.setDimensions(dimensions);
+ imageData.setOrigin(origin);
+ imageData.setDirection(direction);
+ imageData.setSpacing(spacing);
+
+ const scalarArray = vtkDataArray.newInstance({
+ name: 'Pixels',
+ numberOfComponents: 1,
+ values: scalarData,
+ });
+
+ imageData.getPointData().setScalars(scalarArray);
+
+ imageData.modified();
+
+ return {
+ imageData,
+ metadata,
+ getScalarData: () => scalarData,
};
};
-
/**
* This method calculates the SUV peak on a segmented ROI from a reference PET
* volume. If a rectangle annotation is provided, the peak is calculated within that
@@ -25,17 +39,10 @@ type AnnotationsForThresholding = {
* @param segmentIndex The index of the segment to use for masking
* @returns
*/
-function calculateSuvPeak(
- labelmap: Types.IImageVolume,
- referenceVolume: Types.IImageVolume,
- annotations?: AnnotationsForThresholding[],
- segmentIndex = 1
-): {
- max: number;
- maxIJK: Types.Point3;
- maxLPS: Types.Point3;
- mean: number;
-} {
+function calculateSuvPeak({ labelmapProps, referenceVolumeProps, annotations, segmentIndex = 1 }) {
+ const labelmap = createVolume(labelmapProps);
+ const referenceVolume = createVolume(referenceVolumeProps);
+
if (referenceVolume.metadata.Modality !== 'PT') {
return;
}
@@ -59,7 +66,7 @@ function calculateSuvPeak(
const rectangleCornersIJK = pointsToUse.map(world => {
const ijk = vec3.fromValues(0, 0, 0);
referenceVolumeImageData.worldToIndex(world, ijk);
- return ijk as Types.Point3;
+ return ijk;
});
boundsIJK = utilities.boundingBox.getBoundingBoxAroundShape(rectangleCornersIJK, dimensions);
@@ -88,7 +95,7 @@ function calculateSuvPeak(
utilities.pointInShapeCallback(labelmapImageData, () => true, callback, boundsIJK);
- const direction = labelmapImageData.getDirection().slice(0, 3) as Types.Point3;
+ const direction = labelmapImageData.getDirection().slice(0, 3);
/**
* 2. Find the bottom and top of the great circle for the second sphere (1cc sphere)
@@ -100,10 +107,10 @@ function calculateSuvPeak(
const secondaryCircleWorld = vec3.create();
const bottomWorld = vec3.create();
const topWorld = vec3.create();
- referenceVolumeImageData.indexToWorld(maxIJK as vec3, secondaryCircleWorld);
+ referenceVolumeImageData.indexToWorld(maxIJK, secondaryCircleWorld);
vec3.scaleAndAdd(bottomWorld, secondaryCircleWorld, direction, -diameter / 2);
vec3.scaleAndAdd(topWorld, secondaryCircleWorld, direction, diameter / 2);
- const suvPeakCirclePoints = [bottomWorld, topWorld] as [Types.Point3, Types.Point3];
+ const suvPeakCirclePoints = [bottomWorld, topWorld];
/**
* 3. Find the Mean and Max of the 1cc sphere centered on the suv Max of the previous
@@ -132,4 +139,8 @@ function calculateSuvPeak(
};
}
-export default calculateSuvPeak;
+const obj = {
+ calculateSuvPeak,
+};
+
+expose(obj);
diff --git a/extensions/tmtv/src/utils/handleROIThresholding.ts b/extensions/tmtv/src/utils/handleROIThresholding.ts
new file mode 100644
index 00000000000..ab47fa0326d
--- /dev/null
+++ b/extensions/tmtv/src/utils/handleROIThresholding.ts
@@ -0,0 +1,64 @@
+import { cache } from '@cornerstonejs/core';
+
+export const handleROIThresholding = async ({
+ segmentationId,
+ commandsManager,
+ segmentationService,
+ config = {},
+}) => {
+ const segmentation = segmentationService.getSegmentation(segmentationId);
+
+ // re-calculating the cached stats for the active segmentation
+ const updatedPerSegmentCachedStats = {};
+ segmentation.segments = await Promise.all(
+ segmentation.segments.map(async segment => {
+ if (!segment || !segment.segmentIndex) {
+ return segment;
+ }
+
+ const labelmap = cache.getVolume(segmentationId);
+
+ const segmentIndex = segment.segmentIndex;
+
+ const lesionStats = commandsManager.run('getLesionStats', { labelmap, segmentIndex });
+ const suvPeak = await commandsManager.run('calculateSuvPeak', { labelmap, segmentIndex });
+ const lesionGlyoclysisStats = lesionStats.volume * lesionStats.meanValue;
+
+ // update segDetails with the suv peak for the active segmentation
+ const cachedStats = {
+ lesionStats,
+ suvPeak,
+ lesionGlyoclysisStats,
+ };
+
+ segment.cachedStats = cachedStats;
+ segment.displayText = [
+ `SUV Peak: ${suvPeak.suvPeak.toFixed(2)}`,
+ `Volume: ${lesionStats.volume.toFixed(2)} mm3`,
+ ];
+ updatedPerSegmentCachedStats[segmentIndex] = cachedStats;
+
+ return segment;
+ })
+ );
+
+ const notYetUpdatedAtSource = true;
+
+ const segmentations = segmentationService.getSegmentations();
+ const tmtv = commandsManager.run('calculateTMTV', { segmentations });
+
+ segmentation.cachedStats = Object.assign(segmentation.cachedStats, updatedPerSegmentCachedStats, {
+ tmtv: {
+ value: tmtv.toFixed(3),
+ config: { ...config },
+ },
+ });
+
+ segmentationService.addOrUpdateSegmentation(
+ {
+ ...segmentation,
+ },
+ false, // don't suppress events
+ notYetUpdatedAtSource
+ );
+};
diff --git a/modes/preclinical-4d/src/segmentationButtons.ts b/modes/preclinical-4d/src/segmentationButtons.ts
index 330cdcf48b8..ecc028a1f8a 100644
--- a/modes/preclinical-4d/src/segmentationButtons.ts
+++ b/modes/preclinical-4d/src/segmentationButtons.ts
@@ -29,7 +29,7 @@ const toolbarButtons: Button[] = [
commands: _createSetToolActiveCommands('CircularBrush'),
options: [
{
- name: 'Radius (mm)',
+ name: 'Size (mm)',
id: 'brush-radius',
type: 'range',
min: 0.5,
@@ -131,9 +131,9 @@ const toolbarButtons: Button[] = [
type: 'double-range',
id: 'threshold-range',
min: 0,
- max: 10,
- step: 1,
- values: [2, 5],
+ max: 100,
+ step: 0.5,
+ values: [2, 50],
commands: {
commandName: 'setThresholdRange',
commandOptions: {
diff --git a/modes/tmtv/src/index.js b/modes/tmtv/src/index.js
index da318a5278d..03c73baff02 100644
--- a/modes/tmtv/src/index.js
+++ b/modes/tmtv/src/index.js
@@ -99,7 +99,10 @@ function modeFactory({ modeConfiguration }) {
'Pan',
'SyncToggle',
]);
- toolbarService.createButtonSection('ROIThresholdToolbox', ['RectangleROIStartEndThreshold']);
+ toolbarService.createButtonSection('ROIThresholdToolbox', [
+ 'RectangleROIStartEndThreshold',
+ 'BrushTools',
+ ]);
customizationService.addModeCustomizations([
{
diff --git a/modes/tmtv/src/initToolGroups.js b/modes/tmtv/src/initToolGroups.js
index 965668ff4a5..0a972f28990 100644
--- a/modes/tmtv/src/initToolGroups.js
+++ b/modes/tmtv/src/initToolGroups.js
@@ -4,7 +4,6 @@ export const toolGroupIds = {
Fusion: 'fusionToolGroup',
MIP: 'mipToolGroup',
default: 'default',
- // MPR: 'mpr',
};
function _initToolGroups(toolNames, Enums, toolGroupService, commandsManager, modeLabelConfig) {
@@ -59,6 +58,67 @@ function _initToolGroups(toolNames, Enums, toolGroupService, commandsManager, mo
{ toolName: toolNames.Angle },
{ toolName: toolNames.CobbAngle },
{ toolName: toolNames.Magnify },
+ {
+ toolName: 'CircularBrush',
+ parentTool: 'Brush',
+ configuration: {
+ activeStrategy: 'FILL_INSIDE_CIRCLE',
+ },
+ },
+ {
+ toolName: 'CircularEraser',
+ parentTool: 'Brush',
+ configuration: {
+ activeStrategy: 'ERASE_INSIDE_CIRCLE',
+ },
+ },
+ {
+ toolName: 'SphereBrush',
+ parentTool: 'Brush',
+ configuration: {
+ activeStrategy: 'FILL_INSIDE_SPHERE',
+ },
+ },
+ {
+ toolName: 'SphereEraser',
+ parentTool: 'Brush',
+ configuration: {
+ activeStrategy: 'ERASE_INSIDE_SPHERE',
+ },
+ },
+ {
+ toolName: 'ThresholdCircularBrush',
+ parentTool: 'Brush',
+ configuration: {
+ activeStrategy: 'THRESHOLD_INSIDE_CIRCLE',
+ },
+ },
+ {
+ toolName: 'ThresholdSphereBrush',
+ parentTool: 'Brush',
+ configuration: {
+ activeStrategy: 'THRESHOLD_INSIDE_SPHERE',
+ },
+ },
+ {
+ toolName: 'ThresholdCircularBrushDynamic',
+ parentTool: 'Brush',
+ configuration: {
+ activeStrategy: 'THRESHOLD_INSIDE_CIRCLE',
+ // preview: {
+ // enabled: true,
+ // },
+ strategySpecificConfiguration: {
+ // to use the use the center segment index to determine
+ // if inside -> same segment, if outside -> eraser
+ // useCenterSegmentIndex: true,
+ THRESHOLD: {
+ isDynamic: true,
+ dynamicRadius: 3,
+ },
+ },
+ },
+ },
],
enabled: [{ toolName: toolNames.SegmentationDisplay }],
disabled: [
diff --git a/modes/tmtv/src/toolbarButtons.js b/modes/tmtv/src/toolbarButtons.js
index 042a54b5963..b6a14fc6461 100644
--- a/modes/tmtv/src/toolbarButtons.js
+++ b/modes/tmtv/src/toolbarButtons.js
@@ -1,8 +1,6 @@
-import { defaults, ToolbarService } from '@ohif/core';
+import { ToolbarService } from '@ohif/core';
import { toolGroupIds } from './initToolGroups';
-const { windowLevelPresets } = defaults;
-
const setToolActiveToolbar = {
commandName: 'setToolActiveToolbar',
commandOptions: {
@@ -10,6 +8,18 @@ const setToolActiveToolbar = {
},
};
+function _createSetToolActiveCommands(toolName) {
+ return [
+ {
+ commandName: 'setToolActiveToolbar',
+ commandOptions: {
+ toolName,
+ toolGroupIds: [toolGroupIds.CT, toolGroupIds.PT, toolGroupIds.Fusion],
+ },
+ },
+ ];
+}
+
const toolbarButtons = [
{
id: 'MeasurementTools',
@@ -107,13 +117,183 @@ const toolbarButtons = [
icon: 'tool-create-threshold',
label: 'Rectangle ROI Threshold',
commands: setToolActiveToolbar,
- evaluate: {
- name: 'evaluate.cornerstoneTool',
- disabledText: 'Select the PT Axial to enable this tool',
- },
+ evaluate: [
+ 'evaluate.cornerstone.segmentation',
+ // need to put the disabled text last, since each evaluator will
+ // merge the result text into the final result
+ {
+ name: 'evaluate.cornerstoneTool',
+ disabledText: 'Select the PT Axial to enable this tool',
+ },
+ ],
options: 'tmtv.RectangleROIThresholdOptions',
},
},
+ {
+ id: 'BrushTools',
+ uiType: 'ohif.buttonGroup',
+ props: {
+ groupId: 'BrushTools',
+ items: [
+ {
+ id: 'Brush',
+ icon: 'icon-tool-brush',
+ label: 'Brush',
+ evaluate: {
+ name: 'evaluate.cornerstone.segmentation',
+ toolNames: ['CircularBrush', 'SphereBrush'],
+ disabledText: 'Create new segmentation to enable this tool.',
+ },
+ commands: _createSetToolActiveCommands('CircularBrush'),
+ options: [
+ {
+ name: 'Radius (mm)',
+ id: 'brush-radius',
+ type: 'range',
+ min: 0.5,
+ max: 99.5,
+ step: 0.5,
+ value: 25,
+ commands: {
+ commandName: 'setBrushSize',
+ commandOptions: { toolNames: ['CircularBrush', 'SphereBrush'] },
+ },
+ },
+ {
+ name: 'Shape',
+ type: 'radio',
+ id: 'brush-mode',
+ value: 'CircularBrush',
+ values: [
+ { value: 'CircularBrush', label: 'Circle' },
+ { value: 'SphereBrush', label: 'Sphere' },
+ ],
+ commands: 'setToolActiveToolbar',
+ },
+ ],
+ },
+ {
+ id: 'Eraser',
+ icon: 'icon-tool-eraser',
+ label: 'Eraser',
+ evaluate: {
+ name: 'evaluate.cornerstone.segmentation',
+ toolNames: ['CircularEraser', 'SphereEraser'],
+ },
+ commands: _createSetToolActiveCommands('CircularEraser'),
+ options: [
+ {
+ name: 'Radius (mm)',
+ id: 'eraser-radius',
+ type: 'range',
+ min: 0.5,
+ max: 99.5,
+ step: 0.5,
+ value: 25,
+ commands: {
+ commandName: 'setBrushSize',
+ commandOptions: { toolNames: ['CircularEraser', 'SphereEraser'] },
+ },
+ },
+ {
+ name: 'Shape',
+ type: 'radio',
+ id: 'eraser-mode',
+ value: 'CircularEraser',
+ values: [
+ { value: 'CircularEraser', label: 'Circle' },
+ { value: 'SphereEraser', label: 'Sphere' },
+ ],
+ commands: 'setToolActiveToolbar',
+ },
+ ],
+ },
+ {
+ id: 'Threshold',
+ icon: 'icon-tool-threshold',
+ label: 'Threshold Tool',
+ evaluate: {
+ name: 'evaluate.cornerstone.segmentation',
+ toolNames: ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
+ },
+ commands: _createSetToolActiveCommands('ThresholdCircularBrush'),
+ options: [
+ {
+ name: 'Radius (mm)',
+ id: 'threshold-radius',
+ type: 'range',
+ min: 0.5,
+ max: 99.5,
+ step: 0.5,
+ value: 25,
+ commands: {
+ commandName: 'setBrushSize',
+ commandOptions: {
+ toolNames: [
+ 'ThresholdCircularBrush',
+ 'ThresholdSphereBrush',
+ 'ThresholdCircularBrushDynamic',
+ ],
+ },
+ },
+ },
+
+ {
+ name: 'Threshold',
+ type: 'radio',
+ id: 'dynamic-mode',
+ value: 'ThresholdRange',
+ values: [
+ { value: 'ThresholdDynamic', label: 'Dynamic' },
+ { value: 'ThresholdRange', label: 'Range' },
+ ],
+ commands: ({ value, commandsManager }) => {
+ if (value === 'ThresholdDynamic') {
+ commandsManager.run('setToolActive', {
+ toolName: 'ThresholdCircularBrushDynamic',
+ });
+ } else {
+ commandsManager.run('setToolActive', {
+ toolName: 'ThresholdCircularBrush',
+ });
+ }
+ },
+ },
+ {
+ name: 'Shape',
+ type: 'radio',
+ id: 'eraser-mode',
+ value: 'ThresholdCircularBrush',
+ values: [
+ { value: 'ThresholdCircularBrush', label: 'Circle' },
+ { value: 'ThresholdSphereBrush', label: 'Sphere' },
+ ],
+ condition: ({ options }) =>
+ options.find(option => option.id === 'dynamic-mode').value === 'ThresholdRange',
+ commands: 'setToolActiveToolbar',
+ },
+ {
+ name: 'ThresholdRange',
+ type: 'double-range',
+ id: 'threshold-range',
+ min: 0,
+ max: 50,
+ step: 0.5,
+ values: [2.5, 50],
+ condition: ({ options }) =>
+ options.find(option => option.id === 'dynamic-mode').value === 'ThresholdRange',
+ commands: {
+ commandName: 'setThresholdRange',
+ commandOptions: {
+ toolNames: ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ },
];
export default toolbarButtons;
diff --git a/platform/app/package.json b/platform/app/package.json
index d233d1c7a57..0e55855b9bb 100644
--- a/platform/app/package.json
+++ b/platform/app/package.json
@@ -54,7 +54,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
"@cornerstonejs/codec-openjph": "^2.4.5",
- "@cornerstonejs/dicom-image-loader": "^1.70.9",
+ "@cornerstonejs/dicom-image-loader": "^1.70.10",
"@emotion/serialize": "^1.1.3",
"@ohif/core": "3.8.0-beta.86",
"@ohif/extension-cornerstone": "3.8.0-beta.86",
diff --git a/platform/core/package.json b/platform/core/package.json
index edb65f7759f..0dccb0580f2 100644
--- a/platform/core/package.json
+++ b/platform/core/package.json
@@ -37,7 +37,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
"@cornerstonejs/codec-openjph": "^2.4.2",
- "@cornerstonejs/dicom-image-loader": "^1.70.9",
+ "@cornerstonejs/dicom-image-loader": "^1.70.10",
"@ohif/ui": "3.8.0-beta.86",
"cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21"
diff --git a/platform/core/src/services/PanelService/PanelService.tsx b/platform/core/src/services/PanelService/PanelService.tsx
index 06795f4f207..0368b1a6cac 100644
--- a/platform/core/src/services/PanelService/PanelService.tsx
+++ b/platform/core/src/services/PanelService/PanelService.tsx
@@ -107,8 +107,8 @@ export default class PanelService extends PubSubService {
// stack the content of the panels in one react component
content = () => (
<>
- {panelsData.map(({ content: PanelContent }) => (
-
+ {panelsData.map(({ content: PanelContent }, index) => (
+
))}
>
);
diff --git a/platform/core/src/services/ToolBarService/ToolbarService.ts b/platform/core/src/services/ToolBarService/ToolbarService.ts
index b9dfea404b3..d5a0b28283b 100644
--- a/platform/core/src/services/ToolBarService/ToolbarService.ts
+++ b/platform/core/src/services/ToolBarService/ToolbarService.ts
@@ -438,7 +438,11 @@ export default class ToolbarService extends PubSubService {
}
if (Array.isArray(evaluate)) {
- const evaluators = evaluate.map(evaluatorName => {
+ const evaluators = evaluate.map(evaluator => {
+ const isObject = typeof evaluator === 'object';
+
+ const evaluatorName = isObject ? evaluator.name : evaluator;
+
const evaluateFunction = this._evaluateFunction[evaluatorName];
if (!evaluateFunction) {
@@ -447,6 +451,10 @@ export default class ToolbarService extends PubSubService {
);
}
+ if (isObject) {
+ return args => evaluateFunction({ ...args, ...evaluator });
+ }
+
return evaluateFunction;
});
diff --git a/platform/docs/docs/platform/extensions/modules/toolbar.md b/platform/docs/docs/platform/extensions/modules/toolbar.md
index 4ef1fe3ec06..e10fe36ec32 100644
--- a/platform/docs/docs/platform/extensions/modules/toolbar.md
+++ b/platform/docs/docs/platform/extensions/modules/toolbar.md
@@ -137,6 +137,36 @@ this pattern, where multiple toolbar buttons are using the same evaluator but wi
},
```
+#### Composing evaluators
+
+You can choose to set up multiple evaluators for a single button. This comes in handy when you need to assess the button according to various conditions. For example, we aim to prevent the Cine player from showing up on the 3D viewport, so we have:
+
+```js
+evaluate: ['evaluate.cine', 'evaluate.not3D'],
+```
+
+You can even come up with advanced evaluators such as:
+
+```js
+evaluate: [
+ 'evaluate.cornerstone.segmentation',
+ // need to put the disabled text last, since each evaluator will
+ // merge the result text into the final result
+ {
+ name: 'evaluate.cornerstoneTool',
+ disabledText: 'Select the PT Axial to enable this tool',
+ },
+],
+```
+
+that we use for our RectangleROIStartEndThreshold tool in tmtv mode.
+
+As you see this evaluator is composed of two evaluators, one is `evaluate.cornerstone.segmentation` which makes sure (in the implementation), that
+there is a segmentation created, and the second one is `evaluate.cornerstoneTool` which makes sure that the tool is available in the viewport.
+
+Since we are using multiple evaluators, the `disabledText` of each evaluator will be merged into the final result, so you need to
+put the `disabledText` in the last evaluator.
+
#### Group evaluators
Split buttons (see in [ToolbarService](../../services/data/ToolbarService.md) on how to define one) may feature a group evaluator, we provide two of them and you can write your own.
diff --git a/platform/ui/src/assets/icons/tab-4d.svg b/platform/ui/src/assets/icons/tab-4d.svg
new file mode 100644
index 00000000000..e8eb101c216
--- /dev/null
+++ b/platform/ui/src/assets/icons/tab-4d.svg
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/platform/ui/src/components/Icon/getIcon.js b/platform/ui/src/components/Icon/getIcon.js
index 494b8ce3bfe..3ffa7069371 100644
--- a/platform/ui/src/components/Icon/getIcon.js
+++ b/platform/ui/src/components/Icon/getIcon.js
@@ -216,6 +216,9 @@ import layoutCommon2x2 from './../../assets/icons/layout-common-2x2.svg';
import layoutCommon2x3 from './../../assets/icons/layout-common-2x3.svg';
import iconToolRotate from './../../assets/icons/tool-3d-rotate.svg';
+//
+import tab4D from './../../assets/icons/tab-4d.svg';
+
/** New investigational use */
import investigationalUse from './../../assets/icons/illustration-investigational-use.svg';
@@ -426,6 +429,7 @@ const ICONS = {
'layout-common-1x2': layoutCommon1x2,
'layout-common-2x2': layoutCommon2x2,
'layout-common-2x3': layoutCommon2x3,
+ 'tab-4d': tab4D,
/** New investigational use */
'illustration-investigational-use': investigationalUse,
diff --git a/platform/ui/src/components/SegmentationGroupTable/SegmentationGroupSegment.tsx b/platform/ui/src/components/SegmentationGroupTable/SegmentationGroupSegment.tsx
index 11773a8b7fd..0e9e0b0a26e 100644
--- a/platform/ui/src/components/SegmentationGroupTable/SegmentationGroupSegment.tsx
+++ b/platform/ui/src/components/SegmentationGroupTable/SegmentationGroupSegment.tsx
@@ -28,9 +28,12 @@ const SegmentItem = ({
return (
{
e.stopPropagation();
onClick(segmentationId, segmentIndex);
@@ -151,11 +154,11 @@ const SegmentItem = ({
{Array.isArray(displayText) ? (
-
+
{displayText.map(text => (
{text}
@@ -163,7 +166,7 @@ const SegmentItem = ({
) : (
displayText && (
-
+
{displayText}
)
diff --git a/yarn.lock b/yarn.lock
index a0b48fbf21a..e42d9336fe6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1497,13 +1497,13 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
-"@cornerstonejs/adapters@^1.70.9":
- version "1.70.9"
- resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.70.9.tgz#77f96dbdd2d53225ad8d13d4912afa3538c84baa"
- integrity sha512-eGJu9Skb2WUYvXn1JNO/5NB3EMEHj9rGAelrTozFclqPjnfHbxXlMBeScdE8YTmwR6wHtwiB/lXgu6hhrbYOMQ==
+"@cornerstonejs/adapters@^1.70.10":
+ version "1.70.10"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.70.10.tgz#5b8113a6a7c0d8677d72d97761530e3e7f91e118"
+ integrity sha512-OTYDg64SjrmCGEt1VDM27iaUOXi+v7WZaoJI5CfwXOA3cHG9AAv8lRaCtvUhIMKXce7HZuYdgKh/zizvrnVPow==
dependencies:
"@babel/runtime-corejs2" "^7.17.8"
- "@cornerstonejs/tools" "^1.70.9"
+ "@cornerstonejs/tools" "^1.70.10"
buffer "^6.0.3"
dcmjs "^0.29.8"
gl-matrix "^3.4.3"
@@ -1550,10 +1550,10 @@
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0"
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
-"@cornerstonejs/core@^1.70.9":
- version "1.70.9"
- resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.70.9.tgz#c08aa055a786aac79c457e744f18c5fe3f3203f5"
- integrity sha512-2Gu6SXRKuWFoP0aD0dC5et5pcWExxJOE2DWXUKuVdXollbIC+uq3I5FSWOwM8Zzu/Z3JzWWY5IYYPKdJqb8ogQ==
+"@cornerstonejs/core@^1.70.10":
+ version "1.70.10"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.70.10.tgz#8485ad15cd07e784a07892c2d2246bc6e2d5d5fe"
+ integrity sha512-c9wkgDGKpGt0VEdILLAIn5ZRTqoofsGrWAzj+8h0zogdVY9yQ1PIP2WW7xEbuUcJ/m1RFpapiwqMFU6UEXNNBA==
dependencies:
"@kitware/vtk.js" "30.3.3"
comlink "^4.4.1"
@@ -1561,34 +1561,34 @@
gl-matrix "^3.4.3"
lodash.clonedeep "4.5.0"
-"@cornerstonejs/dicom-image-loader@^1.70.9":
- version "1.70.9"
- resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.70.9.tgz#e329eb86343651f3d6148e03eb1e9d4390b79a6a"
- integrity sha512-M4qd88NA+z62d8w6eaDUbYfFCn3dmDqGkF0iI2s67LlySmeDo7pUmSG8gQbuJMOUPQMxD1Hy8EXBexaTAsBiaQ==
+"@cornerstonejs/dicom-image-loader@^1.70.10":
+ version "1.70.10"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.70.10.tgz#29de339e969e370b07fbb229724094b924cf5469"
+ integrity sha512-4/J4xU3C1Qk8KAQaQdF0duaehfGFX5z47ucU3rXqvN44+qHZrK/iPeGVORhJmErlB9qTY++K8uAZbqo/E65Aiw==
dependencies:
"@cornerstonejs/codec-charls" "^1.2.3"
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
"@cornerstonejs/codec-openjpeg" "^1.2.2"
"@cornerstonejs/codec-openjph" "^2.4.5"
- "@cornerstonejs/core" "^1.70.9"
+ "@cornerstonejs/core" "^1.70.10"
dicom-parser "^1.8.9"
pako "^2.0.4"
uuid "^9.0.0"
-"@cornerstonejs/streaming-image-volume-loader@^1.70.9":
- version "1.70.9"
- resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-1.70.9.tgz#3f3b7c04b3db6c303cb278e5ab8d1916cd91a3b1"
- integrity sha512-Uay1IIRJMge0Tp7DrgisaqIXowh5IdROXbexmbGVT++0qeyzIQIzXdaUrJpuXDpaB2bYppWEY9moKja0mtlBHw==
+"@cornerstonejs/streaming-image-volume-loader@^1.70.10":
+ version "1.70.10"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-1.70.10.tgz#e548e82b1014517efd686c294e161a15b66b6e1e"
+ integrity sha512-Rg7v0WDjGNjV+DPXLj1LLSXV/6kBq+RxRBxLVfKnA3onEFRuPxqnMUYY9uxG7Edr5RLaGlnBCr4V4tvFzzYYmA==
dependencies:
- "@cornerstonejs/core" "^1.70.9"
+ "@cornerstonejs/core" "^1.70.10"
comlink "^4.4.1"
-"@cornerstonejs/tools@^1.70.9":
- version "1.70.9"
- resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.70.9.tgz#e71b709ba5652918a9c92ad344638d3188d8af2b"
- integrity sha512-/Pbbb/hAeYd0bgrXtAEwQOksdL5qy+zwosk3HMsUCa5S7FnvEOSnSQbs0h8UZwKIkW09LtzAkSiPgJpP7A3cwg==
+"@cornerstonejs/tools@^1.70.10":
+ version "1.70.10"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.70.10.tgz#9b24b17dfb5ba48556df7643bee01f79b6378672"
+ integrity sha512-GmwhfB/DzEc86cur2pHvlLp/FCvhG7WlNe0dJWLNRPA/fgPt+BPVBwrMf4a6934r4N7WCzwOJjcnUpiwDjT+MQ==
dependencies:
- "@cornerstonejs/core" "^1.70.9"
+ "@cornerstonejs/core" "^1.70.10"
"@icr/polyseg-wasm" "0.4.0"
"@types/offscreencanvas" "2019.7.3"
comlink "^4.4.1"