fix(cornerstone-dicom-sr): Freehand SR hydration support (#3996)
Co-authored-by: Ibrahim <93064150+IbrahimCSAE@users.noreply.github.com> Co-authored-by: IbrahimCSAE <ibrahim.mdev@gmail.com>
This commit is contained in:
parent
6e5892d202
commit
5645ac1b27
@ -46,8 +46,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
"@cornerstonejs/adapters": "^1.67.0",
|
"@cornerstonejs/adapters": "^1.68.1",
|
||||||
"@cornerstonejs/core": "^1.67.0",
|
"@cornerstonejs/core": "^1.68.1",
|
||||||
"@kitware/vtk.js": "29.7.0",
|
"@kitware/vtk.js": "29.7.0",
|
||||||
"react-color": "^2.19.3"
|
"react-color": "^2.19.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,9 +46,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
"@cornerstonejs/adapters": "^1.67.0",
|
"@cornerstonejs/adapters": "^1.68.1",
|
||||||
"@cornerstonejs/core": "^1.67.0",
|
"@cornerstonejs/core": "^1.68.1",
|
||||||
"@cornerstonejs/tools": "^1.67.0",
|
"@cornerstonejs/tools": "^1.68.1",
|
||||||
"classnames": "^2.3.2"
|
"classnames": "^2.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { SOPClassHandlerName, SOPClassHandlerId } from './id';
|
import { SOPClassHandlerName, SOPClassHandlerId } from './id';
|
||||||
import { utils, classes, DisplaySetService, Types } from '@ohif/core';
|
import { utils, classes, DisplaySetService, Types } from '@ohif/core';
|
||||||
import addMeasurement from './utils/addMeasurement';
|
import addDICOMSRDisplayAnnotation from './utils/addDICOMSRDisplayAnnotation';
|
||||||
import isRehydratable from './utils/isRehydratable';
|
import isRehydratable from './utils/isRehydratable';
|
||||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||||
|
|
||||||
@ -150,13 +150,37 @@ function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager)
|
|||||||
return [displaySet];
|
return [displaySet];
|
||||||
}
|
}
|
||||||
|
|
||||||
function _load(displaySet, servicesManager, extensionManager) {
|
async function _load(displaySet, servicesManager, extensionManager) {
|
||||||
const { displaySetService, measurementService } = servicesManager.services;
|
const { displaySetService, measurementService } = servicesManager.services;
|
||||||
const dataSources = extensionManager.getDataSources();
|
const dataSources = extensionManager.getDataSources();
|
||||||
const dataSource = dataSources[0];
|
const dataSource = dataSources[0];
|
||||||
|
|
||||||
const { ContentSequence } = displaySet.instance;
|
const { ContentSequence } = displaySet.instance;
|
||||||
|
|
||||||
|
async function retrieveBulkData(obj, parentObj = null, key = null) {
|
||||||
|
for (const prop in obj) {
|
||||||
|
if (typeof obj[prop] === 'object' && obj[prop] !== null) {
|
||||||
|
await retrieveBulkData(obj[prop], obj, prop);
|
||||||
|
} else if (Array.isArray(obj[prop])) {
|
||||||
|
await Promise.all(obj[prop].map(item => retrieveBulkData(item, obj, prop)));
|
||||||
|
} else if (prop === 'BulkDataURI') {
|
||||||
|
const value = await dataSource.retrieve.bulkDataURI({
|
||||||
|
BulkDataURI: obj[prop],
|
||||||
|
StudyInstanceUID: displaySet.instance.StudyInstanceUID,
|
||||||
|
SeriesInstanceUID: displaySet.instance.SeriesInstanceUID,
|
||||||
|
SOPInstanceUID: displaySet.instance.SOPInstanceUID,
|
||||||
|
});
|
||||||
|
if (parentObj && key) {
|
||||||
|
parentObj[key] = new Float32Array(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displaySet.isLoaded !== true) {
|
||||||
|
await retrieveBulkData(ContentSequence);
|
||||||
|
}
|
||||||
|
|
||||||
displaySet.referencedImages = _getReferencedImagesList(ContentSequence);
|
displaySet.referencedImages = _getReferencedImagesList(ContentSequence);
|
||||||
displaySet.measurements = _getMeasurements(ContentSequence);
|
displaySet.measurements = _getMeasurements(ContentSequence);
|
||||||
|
|
||||||
@ -211,7 +235,7 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newDisplaySet instanceof ImageSet) {
|
if ((!newDisplaySet) instanceof ImageSet) {
|
||||||
// This also filters out _this_ displaySet, as it is not an ImageSet.
|
// This also filters out _this_ displaySet, as it is not an ImageSet.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -275,7 +299,22 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) {
|
if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) {
|
||||||
addMeasurement(measurement, imageId, newDisplaySet.displaySetInstanceUID);
|
const frame =
|
||||||
|
(measurement.coords[0].ReferencedSOPSequence &&
|
||||||
|
measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber) ||
|
||||||
|
1;
|
||||||
|
|
||||||
|
/** Add DICOMSRDisplay annotation for the SR viewport (only) */
|
||||||
|
addDICOMSRDisplayAnnotation(measurement, imageId, frame);
|
||||||
|
|
||||||
|
/** Update measurement properties */
|
||||||
|
measurement.loaded = true;
|
||||||
|
measurement.imageId = imageId;
|
||||||
|
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
|
||||||
|
measurement.ReferencedSOPInstanceUID =
|
||||||
|
measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
||||||
|
measurement.frameNumber = frame;
|
||||||
|
delete measurement.coords;
|
||||||
|
|
||||||
unloadedMeasurements.splice(j, 1);
|
unloadedMeasurements.splice(j, 1);
|
||||||
}
|
}
|
||||||
@ -291,7 +330,7 @@ function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frame
|
|||||||
// Standard. But for now, we will support only one ReferenceFrameNumber.
|
// Standard. But for now, we will support only one ReferenceFrameNumber.
|
||||||
const ReferencedFrameNumber =
|
const ReferencedFrameNumber =
|
||||||
(measurement.coords[0].ReferencedSOPSequence &&
|
(measurement.coords[0].ReferencedSOPSequence &&
|
||||||
measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber) ||
|
measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber) ||
|
||||||
1;
|
1;
|
||||||
|
|
||||||
if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber)) {
|
if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber)) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||||
import getHangingProtocolModule, { srProtocol } from './getHangingProtocolModule';
|
import { srProtocol } from './getHangingProtocolModule';
|
||||||
import onModeEnter from './onModeEnter';
|
import onModeEnter from './onModeEnter';
|
||||||
import getCommandsModule from './commandsModule';
|
import getCommandsModule from './commandsModule';
|
||||||
import preRegistration from './init';
|
import preRegistration from './init';
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
addTool,
|
|
||||||
AngleTool,
|
AngleTool,
|
||||||
annotation,
|
annotation,
|
||||||
ArrowAnnotateTool,
|
ArrowAnnotateTool,
|
||||||
@ -9,6 +8,7 @@ import {
|
|||||||
CircleROITool,
|
CircleROITool,
|
||||||
LengthTool,
|
LengthTool,
|
||||||
PlanarFreehandROITool,
|
PlanarFreehandROITool,
|
||||||
|
RectangleROITool,
|
||||||
} from '@cornerstonejs/tools';
|
} from '@cornerstonejs/tools';
|
||||||
import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
|
import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
|
||||||
import addToolInstance from './utils/addToolInstance';
|
import addToolInstance from './utils/addToolInstance';
|
||||||
@ -19,24 +19,25 @@ import toolNames from './tools/toolNames';
|
|||||||
* @param {object} configuration
|
* @param {object} configuration
|
||||||
*/
|
*/
|
||||||
export default function init({ configuration = {} }: Types.Extensions.ExtensionParams): void {
|
export default function init({ configuration = {} }: Types.Extensions.ExtensionParams): void {
|
||||||
addTool(DICOMSRDisplayTool);
|
addToolInstance(toolNames.DICOMSRDisplay, DICOMSRDisplayTool);
|
||||||
addToolInstance(toolNames.SRLength, LengthTool, {});
|
addToolInstance(toolNames.SRLength, LengthTool);
|
||||||
addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
|
addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
|
||||||
addToolInstance(toolNames.SREllipticalROI, EllipticalROITool);
|
addToolInstance(toolNames.SREllipticalROI, EllipticalROITool);
|
||||||
addToolInstance(toolNames.SRCircleROI, CircleROITool);
|
addToolInstance(toolNames.SRCircleROI, CircleROITool);
|
||||||
addToolInstance(toolNames.SRArrowAnnotate, ArrowAnnotateTool);
|
addToolInstance(toolNames.SRArrowAnnotate, ArrowAnnotateTool);
|
||||||
addToolInstance(toolNames.SRAngle, AngleTool);
|
addToolInstance(toolNames.SRAngle, AngleTool);
|
||||||
|
addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool);
|
||||||
|
addToolInstance(toolNames.SRRectangleROI, RectangleROITool);
|
||||||
|
|
||||||
// TODO - fix the SR display of Cobb Angle, as it joins the two lines
|
// TODO - fix the SR display of Cobb Angle, as it joins the two lines
|
||||||
addToolInstance(toolNames.SRCobbAngle, CobbAngleTool);
|
addToolInstance(toolNames.SRCobbAngle, CobbAngleTool);
|
||||||
// TODO - fix the rehydration of Freehand, as it throws an exception
|
|
||||||
// on a missing polyline. The fix is probably in CS3D
|
|
||||||
addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool);
|
|
||||||
|
|
||||||
// Modify annotation tools to use dashed lines on SR
|
// Modify annotation tools to use dashed lines on SR
|
||||||
const dashedLine = {
|
const dashedLine = {
|
||||||
lineDash: '4,4',
|
lineDash: '4,4',
|
||||||
};
|
};
|
||||||
annotation.config.style.setToolGroupToolStyles('SRToolGroup', {
|
annotation.config.style.setToolGroupToolStyles('SRToolGroup', {
|
||||||
|
[toolNames.DICOMSRDisplay]: dashedLine,
|
||||||
SRLength: dashedLine,
|
SRLength: dashedLine,
|
||||||
SRBidirectional: dashedLine,
|
SRBidirectional: dashedLine,
|
||||||
SREllipticalROI: dashedLine,
|
SREllipticalROI: dashedLine,
|
||||||
@ -45,6 +46,7 @@ export default function init({ configuration = {} }: Types.Extensions.ExtensionP
|
|||||||
SRCobbAngle: dashedLine,
|
SRCobbAngle: dashedLine,
|
||||||
SRAngle: dashedLine,
|
SRAngle: dashedLine,
|
||||||
SRPlanarFreehandROI: dashedLine,
|
SRPlanarFreehandROI: dashedLine,
|
||||||
|
SRRectangleROI: dashedLine,
|
||||||
global: {},
|
global: {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,14 +22,14 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getTextBoxLinesFromLabels(labels) {
|
_getTextBoxLinesFromLabels(labels) {
|
||||||
// TODO -> max 3 for now (label + shortAxis + longAxis), need a generic solution for this!
|
// TODO -> max 5 for now (label + shortAxis + longAxis), need a generic solution for this!
|
||||||
|
|
||||||
const labelLength = Math.min(labels.length, 3);
|
const labelLength = Math.min(labels.length, 5);
|
||||||
const lines = [];
|
const lines = [];
|
||||||
|
|
||||||
for (let i = 0; i < labelLength; i++) {
|
for (let i = 0; i < labelLength; i++) {
|
||||||
const labelEntry = labels[i];
|
const labelEntry = labels[i];
|
||||||
lines.push(`${_labelToShorthand(labelEntry.label)}${labelEntry.value}`);
|
lines.push(`${_labelToShorthand(labelEntry.label)}: ${labelEntry.value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
@ -77,6 +77,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
toolName: this.getToolName(),
|
toolName: this.getToolName(),
|
||||||
viewportId: enabledElement.viewport.id,
|
viewportId: enabledElement.viewport.id,
|
||||||
};
|
};
|
||||||
|
const { style: annotationStyle } = annotation.config;
|
||||||
|
|
||||||
for (let i = 0; i < filteredAnnotations.length; i++) {
|
for (let i = 0; i < filteredAnnotations.length; i++) {
|
||||||
const annotation = filteredAnnotations[i];
|
const annotation = filteredAnnotations[i];
|
||||||
@ -87,6 +88,10 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
|
|
||||||
styleSpecifier.annotationUID = annotationUID;
|
styleSpecifier.annotationUID = annotationUID;
|
||||||
|
|
||||||
|
const groupStyle = annotationStyle.getToolGroupToolStyles(this.toolGroupId)[
|
||||||
|
this.getToolName()
|
||||||
|
];
|
||||||
|
|
||||||
const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
|
const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
|
||||||
const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
|
const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
|
||||||
const color =
|
const color =
|
||||||
@ -98,6 +103,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
color,
|
color,
|
||||||
lineDash,
|
lineDash,
|
||||||
lineWidth,
|
lineWidth,
|
||||||
|
...groupStyle,
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.keys(renderableData).forEach(GraphicType => {
|
Object.keys(renderableData).forEach(GraphicType => {
|
||||||
@ -160,6 +166,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
const drawingOptions = {
|
const drawingOptions = {
|
||||||
color: options.color,
|
color: options.color,
|
||||||
width: options.lineWidth,
|
width: options.lineWidth,
|
||||||
|
lineDash: options.lineDash,
|
||||||
};
|
};
|
||||||
let allCanvasCoordinates = [];
|
let allCanvasCoordinates = [];
|
||||||
renderableData.map((data, index) => {
|
renderableData.map((data, index) => {
|
||||||
@ -307,6 +314,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
|||||||
{
|
{
|
||||||
color: options.color,
|
color: options.color,
|
||||||
width: options.lineWidth,
|
width: options.lineWidth,
|
||||||
|
lineDash: options.lineDash,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,15 +1,13 @@
|
|||||||
import { vec3 } from 'gl-matrix';
|
import { vec3 } from 'gl-matrix';
|
||||||
import { Types, annotation } from '@cornerstonejs/tools';
|
import { Types, annotation } from '@cornerstonejs/tools';
|
||||||
import { metaData, utilities, Types as csTypes } from '@cornerstonejs/core';
|
import { metaData, utilities, Types as csTypes } from '@cornerstonejs/core';
|
||||||
|
|
||||||
import toolNames from '../tools/toolNames';
|
import toolNames from '../tools/toolNames';
|
||||||
import SCOORD_TYPES from '../constants/scoordTypes';
|
import SCOORD_TYPES from '../constants/scoordTypes';
|
||||||
|
|
||||||
const EPSILON = 1e-4;
|
const EPSILON = 1e-4;
|
||||||
|
|
||||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
export default function addDICOMSRDisplayAnnotation(measurement, imageId, frameNumber) {
|
||||||
|
|
||||||
export default function addMeasurement(measurement, imageId, displaySetInstanceUID) {
|
|
||||||
// TODO -> Render rotated ellipse .
|
|
||||||
const toolName = toolNames.DICOMSRDisplay;
|
const toolName = toolNames.DICOMSRDisplay;
|
||||||
|
|
||||||
const measurementData = {
|
const measurementData = {
|
||||||
@ -27,26 +25,25 @@ export default function addMeasurement(measurement, imageId, displaySetInstanceU
|
|||||||
}
|
}
|
||||||
|
|
||||||
measurementData.renderableData[GraphicType].push(
|
measurementData.renderableData[GraphicType].push(
|
||||||
_getRenderableData(GraphicType, GraphicData, imageId, measurement.TrackingIdentifier)
|
_getRenderableData(GraphicType, GraphicData, imageId)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use the metadata provider to grab its imagePlaneModule metadata
|
|
||||||
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
|
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
|
||||||
|
|
||||||
const annotationManager = annotation.state.getAnnotationManager();
|
/**
|
||||||
|
* This annotation (DICOMSRDisplay) is only used by the SR viewport.
|
||||||
// Create Cornerstone3D Annotation from measurement
|
* This is used before the annotation is hydrated. If hydrated the measurement will be added
|
||||||
const frameNumber =
|
* to the measurement service and will be available for the other viewports.
|
||||||
(measurement.coords[0].ReferencedSOPSequence &&
|
*/
|
||||||
measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber) ||
|
|
||||||
1;
|
|
||||||
|
|
||||||
const SRAnnotation: Types.Annotation = {
|
const SRAnnotation: Types.Annotation = {
|
||||||
annotationUID: measurement.TrackingUniqueIdentifier,
|
annotationUID: measurement.TrackingUniqueIdentifier,
|
||||||
|
highlighted: false,
|
||||||
|
isLocked: false,
|
||||||
|
invalidated: false,
|
||||||
metadata: {
|
metadata: {
|
||||||
FrameOfReferenceUID: imagePlaneModule.frameOfReferenceUID,
|
|
||||||
toolName: toolName,
|
toolName: toolName,
|
||||||
|
FrameOfReferenceUID: imagePlaneModule.frameOfReferenceUID,
|
||||||
referencedImageId: imageId,
|
referencedImageId: imageId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
@ -58,28 +55,14 @@ export default function addMeasurement(measurement, imageId, displaySetInstanceU
|
|||||||
TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier,
|
TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier,
|
||||||
renderableData: measurementData.renderableData,
|
renderableData: measurementData.renderableData,
|
||||||
},
|
},
|
||||||
frameNumber: frameNumber,
|
frameNumber,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const annotationManager = annotation.state.getAnnotationManager();
|
||||||
annotationManager.addAnnotation(SRAnnotation);
|
annotationManager.addAnnotation(SRAnnotation);
|
||||||
|
|
||||||
measurement.loaded = true;
|
|
||||||
measurement.imageId = imageId;
|
|
||||||
measurement.displaySetInstanceUID = displaySetInstanceUID;
|
|
||||||
|
|
||||||
// Remove the unneeded coord now its processed, but keep the SOPInstanceUID.
|
|
||||||
// NOTE: We assume that each SCOORD in the MeasurementGroup maps onto one frame,
|
|
||||||
// It'd be super weird if it didn't anyway as a SCOORD.
|
|
||||||
measurement.ReferencedSOPInstanceUID =
|
|
||||||
measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
|
||||||
measurement.frameNumber = frameNumber;
|
|
||||||
delete measurement.coords;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getRenderableData(GraphicType, GraphicData, imageId, TrackingIdentifier) {
|
function _getRenderableData(GraphicType, GraphicData, imageId) {
|
||||||
const [cornerstoneTag, toolName] = TrackingIdentifier.split(':');
|
|
||||||
|
|
||||||
let renderableData: csTypes.Point3[];
|
let renderableData: csTypes.Point3[];
|
||||||
|
|
||||||
switch (GraphicType) {
|
switch (GraphicType) {
|
||||||
@ -125,6 +125,10 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
console.warn('More than one SOPClassUID in the same series is not yet supported.');
|
console.warn('More than one SOPClassUID in the same series is not yet supported.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if (!srDisplaySet.measurements || !srDisplaySet.measurements.length) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
_getViewportReferencedDisplaySetData(
|
_getViewportReferencedDisplaySetData(
|
||||||
srDisplaySet,
|
srDisplaySet,
|
||||||
newMeasurementSelected,
|
newMeasurementSelected,
|
||||||
@ -253,12 +257,16 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
* if it is hydrated we don't even use the SR viewport.
|
* if it is hydrated we don't even use the SR viewport.
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const loadSR = async () => {
|
||||||
if (!srDisplaySet.isLoaded) {
|
if (!srDisplaySet.isLoaded) {
|
||||||
srDisplaySet.load();
|
await srDisplaySet.load();
|
||||||
}
|
}
|
||||||
const numMeasurements = srDisplaySet.measurements.length;
|
const numMeasurements = srDisplaySet.measurements.length;
|
||||||
setMeasurementCount(numMeasurements);
|
setMeasurementCount(numMeasurements);
|
||||||
}, [srDisplaySet]);
|
updateViewport(measurementSelected);
|
||||||
|
};
|
||||||
|
loadSR();
|
||||||
|
}, [dataSource, srDisplaySet]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to update the tracking identifiers when the selected measurement changes or
|
* Hook to update the tracking identifiers when the selected measurement changes or
|
||||||
@ -275,19 +283,11 @@ function OHIFCornerstoneSRViewport(props) {
|
|||||||
* Todo: what is this, not sure what it does regarding the react aspect,
|
* Todo: what is this, not sure what it does regarding the react aspect,
|
||||||
* it is updating a local variable? which is not state.
|
* it is updating a local variable? which is not state.
|
||||||
*/
|
*/
|
||||||
let isLocked = trackedMeasurements?.context?.trackedSeries?.length > 0;
|
const [isLocked, setIsLocked] = useState(trackedMeasurements?.context?.trackedSeries?.length > 0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isLocked = trackedMeasurements?.context?.trackedSeries?.length > 0;
|
setIsLocked(trackedMeasurements?.context?.trackedSeries?.length > 0);
|
||||||
}, [trackedMeasurements]);
|
}, [trackedMeasurements]);
|
||||||
|
|
||||||
/**
|
|
||||||
* Data fetching for the SR displaySet, which updates the measurements and
|
|
||||||
* also gets the referenced image displaySet that SR is based on.
|
|
||||||
*/
|
|
||||||
useEffect(() => {
|
|
||||||
updateViewport(measurementSelected);
|
|
||||||
}, [dataSource, srDisplaySet]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
viewportActionCornersService.setComponents([
|
viewportActionCornersService.setComponents([
|
||||||
{
|
{
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||||
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
||||||
"@cornerstonejs/codec-openjph": "^2.4.2",
|
"@cornerstonejs/codec-openjph": "^2.4.2",
|
||||||
"@cornerstonejs/dicom-image-loader": "^1.67.0",
|
"@cornerstonejs/dicom-image-loader": "^1.68.1",
|
||||||
"@icr/polyseg-wasm": "^0.4.0",
|
"@icr/polyseg-wasm": "^0.4.0",
|
||||||
"@ohif/core": "3.8.0-beta.71",
|
"@ohif/core": "3.8.0-beta.71",
|
||||||
"@ohif/ui": "3.8.0-beta.71",
|
"@ohif/ui": "3.8.0-beta.71",
|
||||||
@ -55,10 +55,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
"@cornerstonejs/adapters": "^1.67.0",
|
"@cornerstonejs/adapters": "^1.68.1",
|
||||||
"@cornerstonejs/core": "^1.67.0",
|
"@cornerstonejs/core": "^1.68.1",
|
||||||
"@cornerstonejs/streaming-image-volume-loader": "^1.67.0",
|
"@cornerstonejs/streaming-image-volume-loader": "^1.68.1",
|
||||||
"@cornerstonejs/tools": "^1.67.0",
|
"@cornerstonejs/tools": "^1.68.1",
|
||||||
"@kitware/vtk.js": "29.7.0",
|
"@kitware/vtk.js": "29.7.0",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"lodash.debounce": "4.0.8",
|
"lodash.debounce": "4.0.8",
|
||||||
|
|||||||
@ -357,6 +357,10 @@ const connectMeasurementServiceToTools = (
|
|||||||
imageId = dataSource.getImageIdsForInstance({ instance });
|
imageId = dataSource.getImageIdsForInstance({ instance });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This annotation is used by the cornerstone viewport.
|
||||||
|
* This is not the read-only annotation rendered by the SR viewport.
|
||||||
|
*/
|
||||||
const annotationManager = annotation.state.getAnnotationManager();
|
const annotationManager = annotation.state.getAnnotationManager();
|
||||||
annotationManager.addAnnotation({
|
annotationManager.addAnnotation({
|
||||||
annotationUID: measurement.uid,
|
annotationUID: measurement.uid,
|
||||||
@ -369,11 +373,16 @@ const connectMeasurementServiceToTools = (
|
|||||||
referencedImageId: imageId,
|
referencedImageId: imageId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
|
/**
|
||||||
|
* Don't remove this destructuring of data here.
|
||||||
|
* This is used to pass annotation specific data forward e.g. contour
|
||||||
|
*/
|
||||||
|
...(data.annotation.data || {}),
|
||||||
text: data.annotation.data.text,
|
text: data.annotation.data.text,
|
||||||
handles: { ...data.annotation.data.handles },
|
handles: { ...data.annotation.data.handles },
|
||||||
cachedStats: { ...data.annotation.data.cachedStats },
|
cachedStats: { ...data.annotation.data.cachedStats },
|
||||||
label: data.annotation.data.label,
|
label: data.annotation.data.label,
|
||||||
frameNumber: frameNumber,
|
frameNumber,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,22 @@
|
|||||||
import SUPPORTED_TOOLS from './constants/supportedTools';
|
import SUPPORTED_TOOLS from './constants/supportedTools';
|
||||||
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes';
|
||||||
|
import { getDisplayUnit } from './utils';
|
||||||
|
import { utils } from '@ohif/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a mapping utility for Planar Freehand ROI measurements.
|
||||||
|
*/
|
||||||
const PlanarFreehandROI = {
|
const PlanarFreehandROI = {
|
||||||
toAnnotation: measurement => {},
|
toAnnotation: measurement => {},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps cornerstone annotation event data to measurement service format.
|
* Maps cornerstone annotation event data to measurement service format.
|
||||||
*
|
*
|
||||||
* @param {Object} cornerstone Cornerstone event data
|
* @param {Object} csToolsEventDetail Cornerstone event data
|
||||||
* @return {Measurement} Measurement instance
|
* @param {DisplaySetService} DisplaySetService Service for managing display sets
|
||||||
|
* @param {CornerstoneViewportService} CornerstoneViewportService Service for managing viewports
|
||||||
|
* @param {Function} getValueTypeFromToolType Function to get value type from tool type
|
||||||
|
* @returns {Measurement} Measurement instance
|
||||||
*/
|
*/
|
||||||
toMeasurement: (
|
toMeasurement: (
|
||||||
csToolsEventDetail,
|
csToolsEventDetail,
|
||||||
@ -16,7 +24,7 @@ const PlanarFreehandROI = {
|
|||||||
CornerstoneViewportService,
|
CornerstoneViewportService,
|
||||||
getValueTypeFromToolType
|
getValueTypeFromToolType
|
||||||
) => {
|
) => {
|
||||||
const { annotation, viewportId } = csToolsEventDetail;
|
const { annotation } = csToolsEventDetail;
|
||||||
const { metadata, data, annotationUID } = annotation;
|
const { metadata, data, annotationUID } = annotation;
|
||||||
|
|
||||||
if (!metadata || !data) {
|
if (!metadata || !data) {
|
||||||
@ -26,19 +34,14 @@ const PlanarFreehandROI = {
|
|||||||
|
|
||||||
const { toolName, referencedImageId, FrameOfReferenceUID } = metadata;
|
const { toolName, referencedImageId, FrameOfReferenceUID } = metadata;
|
||||||
const validToolType = SUPPORTED_TOOLS.includes(toolName);
|
const validToolType = SUPPORTED_TOOLS.includes(toolName);
|
||||||
|
|
||||||
if (!validToolType) {
|
if (!validToolType) {
|
||||||
throw new Error('Tool not supported');
|
throw new Error(`Tool ${toolName} not supported`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes(
|
const { SOPInstanceUID, SeriesInstanceUID, frameNumber, StudyInstanceUID } =
|
||||||
referencedImageId,
|
getSOPInstanceAttributes(referencedImageId);
|
||||||
CornerstoneViewportService,
|
|
||||||
viewportId
|
|
||||||
);
|
|
||||||
|
|
||||||
let displaySet;
|
let displaySet;
|
||||||
|
|
||||||
if (SOPInstanceUID) {
|
if (SOPInstanceUID) {
|
||||||
displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
|
displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
|
||||||
SOPInstanceUID,
|
SOPInstanceUID,
|
||||||
@ -48,92 +51,151 @@ const PlanarFreehandROI = {
|
|||||||
displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { points, textBox } = data.handles;
|
|
||||||
|
|
||||||
const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService);
|
|
||||||
|
|
||||||
const displayText = getDisplayText(mappedAnnotations);
|
|
||||||
const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid: annotationUID,
|
uid: annotationUID,
|
||||||
SOPInstanceUID,
|
SOPInstanceUID,
|
||||||
FrameOfReferenceUID,
|
FrameOfReferenceUID,
|
||||||
points,
|
points: data.contour.polyline,
|
||||||
textBox,
|
textBox: data.handles.textBox,
|
||||||
metadata,
|
metadata,
|
||||||
|
frameNumber,
|
||||||
referenceSeriesUID: SeriesInstanceUID,
|
referenceSeriesUID: SeriesInstanceUID,
|
||||||
referenceStudyUID: StudyInstanceUID,
|
referenceStudyUID: StudyInstanceUID,
|
||||||
toolName: metadata.toolName,
|
toolName: metadata.toolName,
|
||||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||||
label: data.label,
|
label: data.label,
|
||||||
displayText: displayText,
|
displayText: getDisplayText(annotation, displaySet),
|
||||||
data: { ...data, ...data.cachedStats },
|
data: data.cachedStats,
|
||||||
type: getValueTypeFromToolType(toolName),
|
type: getValueTypeFromToolType(toolName),
|
||||||
getReport,
|
getReport: () => getColumnValueReport(annotation),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It maps an imaging library annotation to a list of simplified annotation properties.
|
* This function is used to convert the measurement data to a
|
||||||
*
|
* format that is suitable for report generation (e.g. for the csv report).
|
||||||
* @param {Object} annotationData
|
|
||||||
* @param {Object} DisplaySetService
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function getMappedAnnotations(annotationData, DisplaySetService) {
|
|
||||||
const { metadata, data } = annotationData;
|
|
||||||
const { label } = data;
|
|
||||||
const { referencedImageId } = metadata;
|
|
||||||
|
|
||||||
const annotations = [];
|
|
||||||
|
|
||||||
const { SOPInstanceUID: _SOPInstanceUID, SeriesInstanceUID: _SeriesInstanceUID } =
|
|
||||||
getSOPInstanceAttributes(referencedImageId) || {};
|
|
||||||
|
|
||||||
if (!_SOPInstanceUID || !_SeriesInstanceUID) {
|
|
||||||
return annotations;
|
|
||||||
}
|
|
||||||
|
|
||||||
const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
|
|
||||||
_SOPInstanceUID,
|
|
||||||
_SeriesInstanceUID
|
|
||||||
);
|
|
||||||
|
|
||||||
const { SeriesNumber, SeriesInstanceUID } = displaySet;
|
|
||||||
|
|
||||||
annotations.push({
|
|
||||||
SeriesInstanceUID,
|
|
||||||
SeriesNumber,
|
|
||||||
label,
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
|
|
||||||
return annotations;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TBD
|
|
||||||
* This function is used to convert the measurement data to a format that is suitable for the report generation (e.g. for the csv report).
|
|
||||||
* The report returns a list of columns and corresponding values.
|
* The report returns a list of columns and corresponding values.
|
||||||
* @param {*} mappedAnnotations
|
*
|
||||||
* @param {*} points
|
* @param {object} annotation
|
||||||
* @param {*} FrameOfReferenceUID
|
* @returns {object} Report's content from this tool
|
||||||
* @returns Object representing the report's content for this tool.
|
|
||||||
*/
|
*/
|
||||||
function _getReport(mappedAnnotations, points, FrameOfReferenceUID) {
|
function getColumnValueReport(annotation) {
|
||||||
const columns = [];
|
const columns = [];
|
||||||
const values = [];
|
const values = [];
|
||||||
|
|
||||||
return {
|
/** Add type */
|
||||||
columns,
|
columns.push('AnnotationType');
|
||||||
values,
|
values.push('Cornerstone:PlanarFreehandROI');
|
||||||
};
|
|
||||||
|
/** Add cachedStats */
|
||||||
|
const { metadata, data } = annotation;
|
||||||
|
const { mean, stdDev, max, area, unit, areaUnit, perimeter } =
|
||||||
|
data.cachedStats[`imageId:${metadata.referencedImageId}`];
|
||||||
|
columns.push(`Maximum`, `Mean`, `Std Dev`, 'Pixel Unit', `Area`, 'Unit', 'Perimeter');
|
||||||
|
values.push(max, mean, stdDev, unit, area, areaUnit, perimeter);
|
||||||
|
|
||||||
|
/** Add FOR */
|
||||||
|
if (metadata.FrameOfReferenceUID) {
|
||||||
|
columns.push('FrameOfReferenceUID');
|
||||||
|
values.push(metadata.FrameOfReferenceUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add points */
|
||||||
|
if (data.contour.polyline) {
|
||||||
|
/**
|
||||||
|
* Points has the form of [[x1, y1, z1], [x2, y2, z2], ...]
|
||||||
|
* convert it to string of [[x1 y1 z1];[x2 y2 z2];...]
|
||||||
|
* so that it can be used in the CSV report
|
||||||
|
*/
|
||||||
|
columns.push('points');
|
||||||
|
values.push(data.contour.polyline.map(p => p.join(' ')).join(';'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { columns, values };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDisplayText(mappedAnnotations) {
|
/**
|
||||||
return '';
|
* Retrieves the display text for an annotation in a display set.
|
||||||
|
*
|
||||||
|
* @param {Object} annotation - The annotation object.
|
||||||
|
* @param {Object} displaySet - The display set object.
|
||||||
|
* @returns {string[]} - An array of display text.
|
||||||
|
*/
|
||||||
|
function getDisplayText(annotation, displaySet) {
|
||||||
|
const { metadata, data } = annotation;
|
||||||
|
|
||||||
|
if (!data.cachedStats || !data.cachedStats[`imageId:${metadata.referencedImageId}`]) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mean, stdDev, max, area, modalityUnit, areaUnit, perimeter } =
|
||||||
|
data.cachedStats[`imageId:${metadata.referencedImageId}`];
|
||||||
|
|
||||||
|
const { SOPInstanceUID, frameNumber } = getSOPInstanceAttributes(metadata.referencedImageId);
|
||||||
|
|
||||||
|
const displayText = [];
|
||||||
|
|
||||||
|
const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID);
|
||||||
|
let InstanceNumber;
|
||||||
|
if (instance) {
|
||||||
|
InstanceNumber = instance.InstanceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : '';
|
||||||
|
const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : '';
|
||||||
|
|
||||||
|
const { SeriesNumber } = displaySet;
|
||||||
|
if (SeriesNumber) {
|
||||||
|
displayText.push(`S: ${SeriesNumber}${instanceText}${frameText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (area) {
|
||||||
|
/**
|
||||||
|
* Add Area
|
||||||
|
* Area sometimes becomes undefined if `preventHandleOutsideImage` is off
|
||||||
|
*/
|
||||||
|
const roundedArea = utils.roundNumber(area || 0, 2);
|
||||||
|
displayText.push(`Area: ${roundedArea} ${getDisplayUnit(areaUnit)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mean) {
|
||||||
|
if (Array.isArray(mean)) {
|
||||||
|
const meanValues = mean.map(value => utils.roundNumber(value));
|
||||||
|
displayText.push(`Mean: ${meanValues.join(', ')} ${modalityUnit}`);
|
||||||
|
} else {
|
||||||
|
displayText.push(`Mean: ${utils.roundNumber(mean)} ${modalityUnit}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max) {
|
||||||
|
if (Array.isArray(max)) {
|
||||||
|
const maxValues = max.map(value => utils.roundNumber(value, 2));
|
||||||
|
displayText.push(`Max: ${maxValues.join(', ')} ${modalityUnit}`);
|
||||||
|
} else {
|
||||||
|
displayText.push(`Max: ${utils.roundNumber(max, 2)} ${modalityUnit}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stdDev) {
|
||||||
|
if (Array.isArray(stdDev)) {
|
||||||
|
const stdDevValues = stdDev.map(value => utils.roundNumber(value));
|
||||||
|
displayText.push(`Std Dev: ${stdDevValues.join(', ')} ${modalityUnit}`);
|
||||||
|
} else {
|
||||||
|
displayText.push(`Std Dev: ${utils.roundNumber(stdDev)} ${modalityUnit}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (perimeter) {
|
||||||
|
if (Array.isArray(perimeter)) {
|
||||||
|
const perimeterValues = perimeter.map(value => utils.roundNumber(value));
|
||||||
|
displayText.push(`Perimeter: ${perimeterValues.join(', ')} ${modalityUnit}`);
|
||||||
|
} else {
|
||||||
|
displayText.push(`Perimeter: ${utils.roundNumber(perimeter)} ${modalityUnit}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return displayText;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PlanarFreehandROI;
|
export default PlanarFreehandROI;
|
||||||
|
|||||||
@ -32,8 +32,8 @@
|
|||||||
"start": "yarn run dev"
|
"start": "yarn run dev"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@cornerstonejs/core": "^1.67.0",
|
"@cornerstonejs/core": "^1.68.1",
|
||||||
"@cornerstonejs/tools": "^1.67.0",
|
"@cornerstonejs/tools": "^1.68.1",
|
||||||
"@ohif/core": "3.8.0-beta.71",
|
"@ohif/core": "3.8.0-beta.71",
|
||||||
"@ohif/extension-cornerstone-dicom-sr": "3.8.0-beta.71",
|
"@ohif/extension-cornerstone-dicom-sr": "3.8.0-beta.71",
|
||||||
"@ohif/ui": "3.8.0-beta.71",
|
"@ohif/ui": "3.8.0-beta.71",
|
||||||
|
|||||||
@ -183,6 +183,7 @@ function TrackedMeasurementsContextProvider(
|
|||||||
|
|
||||||
// ~~ Listen for changes to ViewportGrid for potential SRs hung in panes when idle
|
// ~~ Listen for changes to ViewportGrid for potential SRs hung in panes when idle
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const triggerPromptHydrateFlow = async () => {
|
||||||
if (viewports.size > 0) {
|
if (viewports.size > 0) {
|
||||||
const activeViewport = viewports.get(activeViewportId);
|
const activeViewport = viewports.get(activeViewportId);
|
||||||
|
|
||||||
@ -219,7 +220,7 @@ function TrackedMeasurementsContextProvider(
|
|||||||
!displaySet.isLoaded &&
|
!displaySet.isLoaded &&
|
||||||
displaySet.load
|
displaySet.load
|
||||||
) {
|
) {
|
||||||
displaySet.load();
|
await displaySet.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Magic string
|
// Magic string
|
||||||
@ -236,7 +237,15 @@ function TrackedMeasurementsContextProvider(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [activeViewportId, sendTrackedMeasurementsEvent, servicesManager.services, viewports]);
|
};
|
||||||
|
triggerPromptHydrateFlow();
|
||||||
|
}, [
|
||||||
|
trackedMeasurements,
|
||||||
|
activeViewportId,
|
||||||
|
sendTrackedMeasurementsEvent,
|
||||||
|
servicesManager.services,
|
||||||
|
viewports,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TrackedMeasurementsContext.Provider
|
<TrackedMeasurementsContext.Provider
|
||||||
|
|||||||
@ -19,7 +19,7 @@ function PanelStudyBrowserTracking({
|
|||||||
requestDisplaySetCreationForStudy,
|
requestDisplaySetCreationForStudy,
|
||||||
dataSource,
|
dataSource,
|
||||||
}) {
|
}) {
|
||||||
const { displaySetService, uiDialogService, hangingProtocolService, uiNotificationService } =
|
const { displaySetService, uiDialogService, hangingProtocolService, uiNotificationService, measurementService } =
|
||||||
servicesManager.services;
|
servicesManager.services;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@ -340,6 +340,12 @@ function PanelStudyBrowserTracking({
|
|||||||
sendTrackedMeasurementsEvent('UNTRACK_SERIES', {
|
sendTrackedMeasurementsEvent('UNTRACK_SERIES', {
|
||||||
SeriesInstanceUID: displaySet.SeriesInstanceUID,
|
SeriesInstanceUID: displaySet.SeriesInstanceUID,
|
||||||
});
|
});
|
||||||
|
const measurements = measurementService.getMeasurements();
|
||||||
|
measurements.forEach(m => {
|
||||||
|
if (m.referenceSeriesUID === displaySet.SeriesInstanceUID) {
|
||||||
|
measurementService.remove(m.uid);
|
||||||
|
}
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
onClickThumbnail={() => {}}
|
onClickThumbnail={() => {}}
|
||||||
onDoubleClickThumbnail={onDoubleClickThumbnailHandler}
|
onDoubleClickThumbnail={onDoubleClickThumbnailHandler}
|
||||||
|
|||||||
@ -129,6 +129,8 @@ function initSRToolGroup(extensionManager, toolGroupService) {
|
|||||||
{ toolName: SRToolNames.SRBidirectional },
|
{ toolName: SRToolNames.SRBidirectional },
|
||||||
{ toolName: SRToolNames.SREllipticalROI },
|
{ toolName: SRToolNames.SREllipticalROI },
|
||||||
{ toolName: SRToolNames.SRCircleROI },
|
{ toolName: SRToolNames.SRCircleROI },
|
||||||
|
{ toolName: SRToolNames.SRPlanarFreehandROI },
|
||||||
|
{ toolName: SRToolNames.SRRectangleROI },
|
||||||
],
|
],
|
||||||
enabled: [
|
enabled: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -155,14 +155,6 @@ const moreTools = [
|
|||||||
commands: setToolActiveToolbar,
|
commands: setToolActiveToolbar,
|
||||||
evaluate: 'evaluate.cornerstoneTool',
|
evaluate: 'evaluate.cornerstoneTool',
|
||||||
}),
|
}),
|
||||||
createButton({
|
|
||||||
id: 'RectangleROI',
|
|
||||||
icon: 'tool-rectangle',
|
|
||||||
label: 'Rectangle',
|
|
||||||
tooltip: 'Rectangle',
|
|
||||||
commands: setToolActiveToolbar,
|
|
||||||
evaluate: 'evaluate.cornerstoneTool',
|
|
||||||
}),
|
|
||||||
createButton({
|
createButton({
|
||||||
id: 'CalibrationLine',
|
id: 'CalibrationLine',
|
||||||
icon: 'tool-calibration',
|
icon: 'tool-calibration',
|
||||||
|
|||||||
@ -67,6 +67,22 @@ const toolbarButtons: Button[] = [
|
|||||||
commands: setToolActiveToolbar,
|
commands: setToolActiveToolbar,
|
||||||
evaluate: 'evaluate.cornerstoneTool',
|
evaluate: 'evaluate.cornerstoneTool',
|
||||||
}),
|
}),
|
||||||
|
createButton({
|
||||||
|
id: 'PlanarFreehandROI',
|
||||||
|
icon: 'tool-freehand-polygon',
|
||||||
|
label: 'Freehand',
|
||||||
|
tooltip: 'Freehand ROI',
|
||||||
|
commands: setToolActiveToolbar,
|
||||||
|
evaluate: 'evaluate.cornerstoneTool',
|
||||||
|
}),
|
||||||
|
createButton({
|
||||||
|
id: 'RectangleROI',
|
||||||
|
icon: 'tool-rectangle',
|
||||||
|
label: 'Rectangle',
|
||||||
|
tooltip: 'Rectangle ROI',
|
||||||
|
commands: setToolActiveToolbar,
|
||||||
|
evaluate: 'evaluate.cornerstoneTool',
|
||||||
|
}),
|
||||||
createButton({
|
createButton({
|
||||||
id: 'CircleROI',
|
id: 'CircleROI',
|
||||||
icon: 'tool-circle',
|
icon: 'tool-circle',
|
||||||
|
|||||||
@ -53,7 +53,7 @@
|
|||||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||||
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
||||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||||
"@cornerstonejs/dicom-image-loader": "^1.67.0",
|
"@cornerstonejs/dicom-image-loader": "^1.68.1",
|
||||||
"@emotion/serialize": "^1.1.3",
|
"@emotion/serialize": "^1.1.3",
|
||||||
"@ohif/core": "3.8.0-beta.71",
|
"@ohif/core": "3.8.0-beta.71",
|
||||||
"@ohif/extension-cornerstone": "3.8.0-beta.71",
|
"@ohif/extension-cornerstone": "3.8.0-beta.71",
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||||
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
||||||
"@cornerstonejs/codec-openjph": "^2.4.2",
|
"@cornerstonejs/codec-openjph": "^2.4.2",
|
||||||
"@cornerstonejs/dicom-image-loader": "^1.67.0",
|
"@cornerstonejs/dicom-image-loader": "^1.68.1",
|
||||||
"@ohif/ui": "3.8.0-beta.71",
|
"@ohif/ui": "3.8.0-beta.71",
|
||||||
"cornerstone-math": "0.1.9",
|
"cornerstone-math": "0.1.9",
|
||||||
"dicom-parser": "^1.8.21"
|
"dicom-parser": "^1.8.21"
|
||||||
|
|||||||
@ -53,6 +53,7 @@ const MEASUREMENT_SCHEMA_KEYS = [
|
|||||||
'area', // TODO: Add concept names instead (descriptor)
|
'area', // TODO: Add concept names instead (descriptor)
|
||||||
'mean',
|
'mean',
|
||||||
'stdDev',
|
'stdDev',
|
||||||
|
'perimeter',
|
||||||
'length',
|
'length',
|
||||||
'shortestDiameter',
|
'shortestDiameter',
|
||||||
'longestDiameter',
|
'longestDiameter',
|
||||||
|
|||||||
48
yarn.lock
48
yarn.lock
@ -1582,13 +1582,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
||||||
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
|
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
|
||||||
|
|
||||||
"@cornerstonejs/adapters@^1.67.0":
|
"@cornerstonejs/adapters@^1.68.1":
|
||||||
version "1.67.0"
|
version "1.68.1"
|
||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.67.0.tgz#84c881a2b1c7bc3cec2ae55df869d82740cc6049"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-1.68.1.tgz#9f4fa768f85759bb49e8f252c9a4e5b4acd09b1f"
|
||||||
integrity sha512-EnIpdMD7kx42eJYGHsqjahWTZj/ZTkP+5XCWXpLp0BY0p+O53oSV9aYZXvyjd/kWQPr+30L+010zPuE5oQzIxA==
|
integrity sha512-p4pKMX70/COLf1dSIDUonEBHhSPYklH2OkS9yOmdjOVYzvWxyaQWuczd1V+2cWYanD5WGqRzarl4KpJAK9W+zQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime-corejs2" "^7.17.8"
|
"@babel/runtime-corejs2" "^7.17.8"
|
||||||
"@cornerstonejs/tools" "^1.67.0"
|
"@cornerstonejs/tools" "^1.68.1"
|
||||||
buffer "^6.0.3"
|
buffer "^6.0.3"
|
||||||
dcmjs "^0.29.8"
|
dcmjs "^0.29.8"
|
||||||
gl-matrix "^3.4.3"
|
gl-matrix "^3.4.3"
|
||||||
@ -1635,10 +1635,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0"
|
||||||
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
|
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
|
||||||
|
|
||||||
"@cornerstonejs/core@^1.67.0":
|
"@cornerstonejs/core@^1.68.1":
|
||||||
version "1.67.0"
|
version "1.68.1"
|
||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.67.0.tgz#711a915b70b5273d8838b59c8c06c553079b0ee7"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.68.1.tgz#f5d366924b791b117d77e2fe83cee82dced0f3fa"
|
||||||
integrity sha512-t8VI5acGGu0wN51UzHyEXOjlBjoAJjIVcbBOQejC0yOWfGoy+I9y/h5de41pZyhfbXYNNXA1dHZDDtLi9sauBg==
|
integrity sha512-F+ptRQ4kYPLS1buDvpQhy3ckdJa4K4B7uz3Ujq3BaEw1AZPB01S6OFw6HtbYs8i+kIi8ZF1fdWvDheO0bpefBQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@kitware/vtk.js" "29.7.0"
|
"@kitware/vtk.js" "29.7.0"
|
||||||
comlink "^4.4.1"
|
comlink "^4.4.1"
|
||||||
@ -1646,34 +1646,34 @@
|
|||||||
gl-matrix "^3.4.3"
|
gl-matrix "^3.4.3"
|
||||||
lodash.clonedeep "4.5.0"
|
lodash.clonedeep "4.5.0"
|
||||||
|
|
||||||
"@cornerstonejs/dicom-image-loader@^1.67.0":
|
"@cornerstonejs/dicom-image-loader@^1.68.1":
|
||||||
version "1.67.0"
|
version "1.68.1"
|
||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.67.0.tgz#9e70858100821682105f88a927aa4dff96e75cda"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.68.1.tgz#fd2cda6de4060ade8092a0fc887f32d4ce979684"
|
||||||
integrity sha512-ZhziYYKcci1X11iWA4qOzEVxtVUbDUKqx9ustsAD9vv9P7WfDOI4EHflUhW+X1hIAG5vl7xv6LAG0aRkVnwrrw==
|
integrity sha512-xjmp2Izy4KMPUTZVutdnx0TXhFrLmlJP3ZZ54ypjJyKGdv4EIGyDvGmCHkD7G37dYROTbZJVBNtjukCrfvJ1Zg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@cornerstonejs/codec-charls" "^1.2.3"
|
"@cornerstonejs/codec-charls" "^1.2.3"
|
||||||
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
|
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
|
||||||
"@cornerstonejs/codec-openjpeg" "^1.2.2"
|
"@cornerstonejs/codec-openjpeg" "^1.2.2"
|
||||||
"@cornerstonejs/codec-openjph" "^2.4.5"
|
"@cornerstonejs/codec-openjph" "^2.4.5"
|
||||||
"@cornerstonejs/core" "^1.67.0"
|
"@cornerstonejs/core" "^1.68.1"
|
||||||
dicom-parser "^1.8.9"
|
dicom-parser "^1.8.9"
|
||||||
pako "^2.0.4"
|
pako "^2.0.4"
|
||||||
uuid "^9.0.0"
|
uuid "^9.0.0"
|
||||||
|
|
||||||
"@cornerstonejs/streaming-image-volume-loader@^1.67.0":
|
"@cornerstonejs/streaming-image-volume-loader@^1.68.1":
|
||||||
version "1.67.0"
|
version "1.68.1"
|
||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-1.67.0.tgz#5e22c828e5a5c9932e728314edbfa95abf81982e"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-1.68.1.tgz#93ab87a0b77717200c7ea35ddb832651f02882de"
|
||||||
integrity sha512-W5+CKSxkLI4BbcdXYVb/M+yeN6Z+G8AWOQISR8qX9NGnK3GR/Id7+9OH+HP6FmIZ4YBXew9/DOqRgEtTtyDZGQ==
|
integrity sha512-4n0oNuf+SNxVfZ6gr5CwSgKpu5f3S3+uFxzRcvUH5DMXwVGxwU1XF8CTFfvEnw9xBn3x6Dg4ckR1a682CyuwXA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@cornerstonejs/core" "^1.67.0"
|
"@cornerstonejs/core" "^1.68.1"
|
||||||
comlink "^4.4.1"
|
comlink "^4.4.1"
|
||||||
|
|
||||||
"@cornerstonejs/tools@^1.67.0":
|
"@cornerstonejs/tools@^1.68.1":
|
||||||
version "1.67.0"
|
version "1.68.1"
|
||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.67.0.tgz#47c6459859cc5122f780a7ec67c2b9d41b067b38"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.68.1.tgz#17e177be628bd633391566d8110b2799be7d5068"
|
||||||
integrity sha512-CPFWRF4IePxHV8KoXE6PBY0Q3jw8riJ7ZaJajAzXLpgvTSvIYu0m7tNuFG8iKVppq6KTgSywO4r9VbfcvZXOyw==
|
integrity sha512-fbXIqcgJKa2RUmwLvxbbISSvwkfwZBoKCjtI/j0AMowpV/gpm86Br6/MomNmPQvdfuF5HfQRdBHjZS71Vid1pQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@cornerstonejs/core" "^1.67.0"
|
"@cornerstonejs/core" "^1.68.1"
|
||||||
"@icr/polyseg-wasm" "0.4.0"
|
"@icr/polyseg-wasm" "0.4.0"
|
||||||
"@types/offscreencanvas" "2019.7.3"
|
"@types/offscreencanvas" "2019.7.3"
|
||||||
comlink "^4.4.1"
|
comlink "^4.4.1"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user