feat: cornerstone3D stack and volume viewports (#2787)
* feat: cs3d working stack viewport and tools (#19) * Squashed everything * fix weird react issue * fix eslint / prettier stuff * thumbnails work now with cpu rendering * feat: use new loadImageToCanvas in cs3d * make jump to measurement work * fix active thumbnail * fix measurement delete * fix window level presets * remove segmentation and sync groups for now * fix the dicom pdf and dicom video * fix cornerstone window assignment for cypress * apply review comments Co-authored-by: Erik <erik.sweed@gmail.com> * feat: cs3d tools and toolGroups (#20) * add more tools to work with cs3d mode * fix hotkeys * add stack manager usage for stach viewports * add image scrollbar * wip viewport overlay * fix toAnnotation schema for tools * fix the unnecessary size change that triggered resize * hanging protocol improvement to allow unmatched errors * study description matching for hanging protocol * fix the displaysetOptions to work * fix handle the active tool when a new viewport is added * fix separate toolGroups for mode * apply review comments * apply review comments * yarn lock * feat: overlay component (#21) * fix default displayset options * add viewport overlay * apply review comments * feat: loading and orientation indicators (#22) * add loading indicator * add orientation marker initial work * apply review comments * fix: orientation markers (#23) * finished the orientation markers * fix various broken cypress tests * apply review comments * update yarn lock * feat: re-working measurement tracking mode with cornerstone3d (#2805) * feat: cs3d working stack viewport and tools (#19) * Squashed everything * fix weird react issue * fix eslint / prettier stuff * thumbnails work now with cpu rendering * feat: use new loadImageToCanvas in cs3d * make jump to measurement work * fix active thumbnail * fix measurement delete * fix window level presets * remove segmentation and sync groups for now * fix the dicom pdf and dicom video * fix cornerstone window assignment for cypress * apply review comments Co-authored-by: Erik <erik.sweed@gmail.com> * feat: Add Measurement tracking mode with cs3D (#2789) * feat: first render for cornerstone3d tracked viewport * make tool active work * wip for SR extension * renamed dicom sr to cornerstone dicom sr * remove cornerstone from dicom pdf and video * move dicom sr logic to sr extension * feat: Add hydration for the length tool * fix SR display tool for length using cs3d * fix default config * fix: various bugs with sr viewport and tracking * fix promptying to continue tracking for when SR is created * feat: add keep trackign of unique identifiers * fix hydration for same imageIds * feat: Add SR toolGroup creation on modeEnter * feat: remove the need for separate mapper for SR hydration * add SR display for ellipse * handle hydration of elliptical ROI tool * remove cornerstone extension * add arrow mapping * feat: Add ArrowAnnotate SR display and hydration * apply review comments * apply review comments * move viewport labels to the viewportData * apply review comments * fix: integration cypress tests with cornerstone3D and add CINE tool (#2795) * fix: integration cypress tests with cornerstone3D * revert to addOrUpdate as it makes more sense * fix local drag and drop * fix tests * move dicomLoaderService to cornerstone extension * fix various import bugs * fix bug for local PT series * fix various unit tests * bump cs3d versions * add angle and magnify tool * bump dependencies to avoid broken peerDeps * feat: add initial work for capture using cs3d * feat: show annotations on the image capture * feat: add svg layer export * feat: Add CINE Tool * feat: remove unnecessary viewport rendering for cine state changes Co-authored-by: Erik Ziegler <erik.sweed@gmail.com> * docs: modify and improve documentation (#2800) * cleanup docs versionings * feat: Add all version explanations * version docs for 3.0 * wip for changing docs * wip for updated docs * add utility module documentation * fix demo with nohoisting of history * add slides and video to resources * apply review comments * fix: drag and drop SR into SR viewport (#2803) * update yarn lock Co-authored-by: Erik <erik.sweed@gmail.com> * update pathnames to match v3-stable * update the e2e pathname * fix: various bugs with regard to tracking workflow (#2811) * fix: various issues with measurement panel * fix: update default tool style for annotations * fix: annotatoin label getting removed * feat: Add backward compatibility for SR hydration with legacy cornerstone * fix: cursors and ellipse ROI max style * fix: ArrowAnnotate SRDisplay * apply review comments * bump package versions * fix: bugin rehydration of SR * fix: e2e tests * fix active viewport thickness and arrowTool ui * add readme for measurement tracking * use uploaded image for readme * add back images * try to fix e2e test * fix: window level presets hotkeys * Update README.md * update yarn lock * feat: volume api and TMTV mode (#2817) * feat: volumeAPI and TMTV mode * feat: use cs3d cache service to obtain viewportData * wip for volume api * wip: fusion viewport * feat: add blend mode option * wip for image scrollbar * fix drag and drop thumbnail * wip for image scrollbar for voluems * fix: element mismatch bug for scroll * feat: Add image scrollbar to volumes * feat: add syncGroups to volume api * feat: add tmtv mode initial setup * feat: add initial image options for the stack viewports * feat: Add custom load strategy for volume viewports via HP * apply review comments fix: Jump presets cs3d (#2812) * feat: Add JumpPreset to OHIF for Cornerstone3D * fix: accessing viewport service from servicesManager feat: volume API and TMTV mode (#2814) * fix: do not display overlays on mip viewports * feat: add optional disableCommands for toggle buttons * fix: toggleCrossharis * feat: config the crosshairs * feat: add PetSUV Panel for changing metadata * feat: initial work for the rectangleROIThreshold panel * wip: measurement service * roi threshold working * feat: Add displayText to segmentations * feat: add remove segmentation * feat: add csv export * feat: add RT export for annotations in tmtv mode * fix: fusion to use pt in tmtv mode and measuremet mappings * fix: various bugs * apply review comments * apply review comments * feat: add fusion color maps * add readme to tmtv mode * Update README.md * fix: try to fix build * fix unit tests * Update README.md * feat: add about to the panel * fix: changing strategy in roi panel * apply review comments * update package versions * wip for stackPrefetch * fix: cornerstone3d hydration and renaming (#2818) * update readme * renamed cornerstone extension * wip for renaming cornerstone3D variables * wip for renaming cornerstone3D variables * wip for fixing bugs for SR viewport * fix: jumpToMeasurement and initial label after hydration * fix: fileName capitalization * feat: use the new prefetch stack in the cs3d (#2820) * feat: use the new prefetch stack in the cs3d * use viewport scroll api for stack viewport * fix cine stop when scrollbar changes * feat: use new prefetch events * fix loading state to not show repeatedly * fix: various bugs for tmtv mode thresholding and new icons (#2823) * feat: make tmtv mode available in worklist * feat: add new icons for tmtv mode * feat: add fusion color icon * fix: parallel scale calculation * fix: bump Cornerstone to get large image support working * fix: Fix issues with CPU viewport flipping, including config files * fix: bump cornerstone to fix magnify tool * fix: Bump Cornerstone version to fix resetCamera issue in StackViewport * ci: Add _headers file to enable CORS headers for Netlify Drag/Drop deploys * fix: WADO-URI was not working. PET Metadata was coming from the wrong place. Fixed some minor React errors * feat(OHIF):Allow modes and extensions to be added after commpile time. (#2838) Also works with the compile time add that the existing cli uses, so that both build types work. Eric and I agreed this doesn't change existing functionality, but is almost entirely build issues/fixes. * bump: dependency versions to fix hydration bugs (#2848) * bump: dcmjs version to fix hydration bugs * try to fix tests * bump dependency versions Co-authored-by: Alireza <ar.sedghi@gmail.com> Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
668 files changed
+41669
-13410
No files matched your search
+18
-16
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useContext, useEffect } from 'react';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Machine } from 'xstate';
|
||||
import { useMachine } from '@xstate/react';
|
||||
@@ -18,14 +18,14 @@ TrackedMeasurementsContext.displayName = 'TrackedMeasurementsContext';
|
||||
const useTrackedMeasurements = () => useContext(TrackedMeasurementsContext);
|
||||
|
||||
const SR_SOPCLASSHANDLERID =
|
||||
'@ohif/extension-dicom-sr.sopClassHandlerModule.dicom-sr';
|
||||
'@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
*/
|
||||
function TrackedMeasurementsContextProvider(
|
||||
{ servicesManager, extensionManager }, // Bound by consumer
|
||||
{ servicesManager, commandsManager, extensionManager }, // Bound by consumer
|
||||
{ children } // Component props
|
||||
) {
|
||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||
@@ -34,10 +34,8 @@ function TrackedMeasurementsContextProvider(
|
||||
const machineOptions = Object.assign({}, defaultOptions);
|
||||
machineOptions.actions = Object.assign({}, machineOptions.actions, {
|
||||
jumpToFirstMeasurementInActiveViewport: (ctx, evt) => {
|
||||
const {
|
||||
DisplaySetService,
|
||||
MeasurementService,
|
||||
} = servicesManager.services;
|
||||
const { MeasurementService } = servicesManager.services;
|
||||
|
||||
const { trackedStudy, trackedSeries } = ctx;
|
||||
const measurements = MeasurementService.getMeasurements();
|
||||
const trackedMeasurements = measurements.filter(
|
||||
@@ -46,11 +44,11 @@ function TrackedMeasurementsContextProvider(
|
||||
trackedSeries.includes(m.referenceSeriesUID)
|
||||
);
|
||||
|
||||
const id = trackedMeasurements[0].id;
|
||||
const uid = trackedMeasurements[0].uid;
|
||||
|
||||
MeasurementService.jumpToMeasurement(
|
||||
viewportGrid.activeViewportIndex,
|
||||
id
|
||||
uid
|
||||
);
|
||||
},
|
||||
showStructuredReportDisplaySetInActiveViewport: (ctx, evt) => {
|
||||
@@ -58,9 +56,9 @@ function TrackedMeasurementsContextProvider(
|
||||
const StructuredReportDisplaySetInstanceUID =
|
||||
evt.data.createdDisplaySetInstanceUIDs[0].displaySetInstanceUID;
|
||||
|
||||
viewportGridService.setDisplaysetForViewport({
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportIndex: evt.data.viewportIndex,
|
||||
displaySetInstanceUID: StructuredReportDisplaySetInstanceUID,
|
||||
displaySetInstanceUIDs: [StructuredReportDisplaySetInstanceUID],
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -73,16 +71,16 @@ function TrackedMeasurementsContextProvider(
|
||||
const measurementIds = filteredMeasurements.map(fm => fm.id);
|
||||
|
||||
for (let i = 0; i < measurementIds.length; i++) {
|
||||
MeasurementService.remove(measurementIds[i], 'app-source');
|
||||
MeasurementService.remove(measurementIds[i]);
|
||||
}
|
||||
},
|
||||
clearAllMeasurements: (ctx, evt) => {
|
||||
const { MeasurementService } = servicesManager.services;
|
||||
const measurements = MeasurementService.getMeasurements();
|
||||
const measurementIds = measurements.map(fm => fm.id);
|
||||
const measurementIds = measurements.map(fm => fm.uid);
|
||||
|
||||
for (let i = 0; i < measurementIds.length; i++) {
|
||||
MeasurementService.remove(measurementIds[i], 'app-source');
|
||||
MeasurementService.remove(measurementIds[i]);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -101,6 +99,7 @@ function TrackedMeasurementsContextProvider(
|
||||
}),
|
||||
promptSaveReport: promptSaveReport.bind(null, {
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}),
|
||||
promptHydrateStructuredReport: promptHydrateStructuredReport.bind(null, {
|
||||
@@ -134,13 +133,15 @@ function TrackedMeasurementsContextProvider(
|
||||
if (viewports.length > 0) {
|
||||
const activeViewport = viewports[activeViewportIndex];
|
||||
|
||||
if (!activeViewport || !activeViewport.displaySetInstanceUID) {
|
||||
if (!activeViewport || !activeViewport?.displaySetInstanceUIDs?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Todo: Getting the first displaySetInstanceUID is wrong, but we don't have
|
||||
// tracking fusion viewports yet. This should change when we do.
|
||||
const { DisplaySetService } = servicesManager.services;
|
||||
const displaySet = DisplaySetService.getDisplaySetByUID(
|
||||
activeViewport.displaySetInstanceUID
|
||||
activeViewport.displaySetInstanceUIDs[0]
|
||||
);
|
||||
|
||||
// If this is an SR produced by our SR SOPClassHandler,
|
||||
@@ -197,6 +198,7 @@ function TrackedMeasurementsContextProvider(
|
||||
TrackedMeasurementsContextProvider.propTypes = {
|
||||
children: PropTypes.oneOf([PropTypes.func, PropTypes.node]),
|
||||
servicesManager: PropTypes.object.isRequired,
|
||||
commandsManager: PropTypes.object.isRequired,
|
||||
extensionManager: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
+124
-42
@@ -1,12 +1,15 @@
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import { utilities, metaData } from '@cornerstonejs/core';
|
||||
import OHIF, { DicomMetadataStore } from '@ohif/core';
|
||||
import getLabelFromDCMJSImportedToolData from './utils/getLabelFromDCMJSImportedToolData';
|
||||
import getCornerstoneToolStateToMeasurementSchema from './getCornerstoneToolStateToMeasurementSchema';
|
||||
import { adapters } from 'dcmjs';
|
||||
|
||||
const { guid } = OHIF.utils;
|
||||
const globalImageIdSpecificToolStateManager =
|
||||
cornerstoneTools.globalImageIdSpecificToolStateManager;
|
||||
const { MeasurementReport, CORNERSTONE_3D_TAG } = adapters.Cornerstone3D;
|
||||
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
||||
|
||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -24,8 +27,8 @@ export default function _hydrateStructuredReport(
|
||||
|
||||
// TODO -> We should define a strict versioning somewhere.
|
||||
const mappings = MeasurementService.getSourceMappings(
|
||||
'CornerstoneTools',
|
||||
'4'
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
||||
);
|
||||
|
||||
if (!mappings || !mappings.length) {
|
||||
@@ -40,29 +43,38 @@ export default function _hydrateStructuredReport(
|
||||
displaySet.SOPInstanceUID
|
||||
);
|
||||
|
||||
const { MeasurementReport } = adapters.Cornerstone;
|
||||
|
||||
const sopInstanceUIDToImageId = {};
|
||||
let imageIdsForToolState = [];
|
||||
|
||||
displaySet.measurements.forEach(measurement => {
|
||||
const { ReferencedSOPInstanceUID, imageId } = measurement;
|
||||
imageIdsForToolState.push(imageId);
|
||||
if (!sopInstanceUIDToImageId[ReferencedSOPInstanceUID]) {
|
||||
sopInstanceUIDToImageId[ReferencedSOPInstanceUID] = imageId;
|
||||
}
|
||||
});
|
||||
|
||||
const datasetToUse = _mapLegacyDataSet(instance);
|
||||
|
||||
// Use dcmjs to generate toolState.
|
||||
const storedMeasurementByToolType = MeasurementReport.generateToolState(
|
||||
instance
|
||||
const storedMeasurementByAnnotationType = MeasurementReport.generateToolState(
|
||||
datasetToUse,
|
||||
// NOTE: we need to pass in the imageIds to dcmjs since the we use them
|
||||
// for the imageToWorld transformation. The following assumes that the order
|
||||
// that measurements were added to the display set are the same order as
|
||||
// the measurementGroups in the instance.
|
||||
sopInstanceUIDToImageId,
|
||||
utilities.imageToWorldCoords,
|
||||
metaData
|
||||
);
|
||||
|
||||
// Filter what is found by DICOM SR to measurements we support.
|
||||
const mappingDefinitions = mappings.map(m => m.definition);
|
||||
const mappingDefinitions = mappings.map(m => m.annotationType);
|
||||
const hydratableMeasurementsInSR = {};
|
||||
|
||||
Object.keys(storedMeasurementByToolType).forEach(key => {
|
||||
Object.keys(storedMeasurementByAnnotationType).forEach(key => {
|
||||
if (mappingDefinitions.includes(key)) {
|
||||
hydratableMeasurementsInSR[key] = storedMeasurementByToolType[key];
|
||||
hydratableMeasurementsInSR[key] = storedMeasurementByAnnotationType[key];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -70,12 +82,13 @@ export default function _hydrateStructuredReport(
|
||||
const imageIds = [];
|
||||
|
||||
// TODO: notification if no hydratable?
|
||||
Object.keys(hydratableMeasurementsInSR).forEach(toolType => {
|
||||
const toolDataForToolType = hydratableMeasurementsInSR[toolType];
|
||||
Object.keys(hydratableMeasurementsInSR).forEach(annotationType => {
|
||||
const toolDataForAnnotationType =
|
||||
hydratableMeasurementsInSR[annotationType];
|
||||
|
||||
toolDataForToolType.forEach(data => {
|
||||
toolDataForAnnotationType.forEach(toolData => {
|
||||
// Add the measurement to toolState
|
||||
const imageId = sopInstanceUIDToImageId[data.sopInstanceUid];
|
||||
const imageId = sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
||||
|
||||
if (!imageIds.includes(imageId)) {
|
||||
imageIds.push(imageId);
|
||||
@@ -88,7 +101,7 @@ export default function _hydrateStructuredReport(
|
||||
|
||||
for (let i = 0; i < imageIds.length; i++) {
|
||||
const imageId = imageIds[i];
|
||||
const { SeriesInstanceUID, StudyInstanceUID } = cornerstone.metaData.get(
|
||||
const { SeriesInstanceUID, StudyInstanceUID } = metaData.get(
|
||||
'instance',
|
||||
imageId
|
||||
);
|
||||
@@ -106,43 +119,49 @@ export default function _hydrateStructuredReport(
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(hydratableMeasurementsInSR).forEach(toolType => {
|
||||
const toolDataForToolType = hydratableMeasurementsInSR[toolType];
|
||||
Object.keys(hydratableMeasurementsInSR).forEach(annotationType => {
|
||||
const toolDataForAnnotationType =
|
||||
hydratableMeasurementsInSR[annotationType];
|
||||
|
||||
toolDataForToolType.forEach(data => {
|
||||
toolDataForAnnotationType.forEach(toolData => {
|
||||
// Add the measurement to toolState
|
||||
const imageId = sopInstanceUIDToImageId[data.sopInstanceUid];
|
||||
const imageId = sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
||||
|
||||
data.id = guid();
|
||||
toolData.uid = guid();
|
||||
|
||||
const instance = cornerstone.metaData.get('instance', imageId);
|
||||
const instance = metaData.get('instance', imageId);
|
||||
const {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
// SOPInstanceUID,
|
||||
// SeriesInstanceUID,
|
||||
// StudyInstanceUID,
|
||||
} = instance;
|
||||
|
||||
// Let the measurement service know we added to toolState
|
||||
const toMeasurementSchema = getCornerstoneToolStateToMeasurementSchema(
|
||||
toolType,
|
||||
MeasurementService,
|
||||
DisplaySetService,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID
|
||||
const annotation = {
|
||||
annotationUID: toolData.annotation.annotationUID,
|
||||
data: toolData.annotation.data,
|
||||
metadata: {
|
||||
toolName: annotationType,
|
||||
referencedImageId: imageId,
|
||||
FrameOfReferenceUID,
|
||||
},
|
||||
};
|
||||
|
||||
const source = MeasurementService.getSource(
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
||||
);
|
||||
annotation.data.label = getLabelFromDCMJSImportedToolData(toolData);
|
||||
|
||||
const source = MeasurementService.getSource('CornerstoneTools', '4');
|
||||
|
||||
data.label = getLabelFromDCMJSImportedToolData(data);
|
||||
const matchingMapping = mappings.find(
|
||||
m => m.annotationType === annotationType
|
||||
);
|
||||
|
||||
MeasurementService.addRawMeasurement(
|
||||
source,
|
||||
toolType,
|
||||
data,
|
||||
toMeasurementSchema,
|
||||
annotationType,
|
||||
{ annotation },
|
||||
matchingMapping.toMeasurementSchema,
|
||||
dataSource
|
||||
);
|
||||
|
||||
@@ -159,3 +178,66 @@ export default function _hydrateStructuredReport(
|
||||
SeriesInstanceUIDs,
|
||||
};
|
||||
}
|
||||
|
||||
function _mapLegacyDataSet(dataset) {
|
||||
const REPORT = 'Imaging Measurements';
|
||||
const GROUP = 'Measurement Group';
|
||||
const TRACKING_IDENTIFIER = 'Tracking Identifier';
|
||||
|
||||
// Identify the Imaging Measurements
|
||||
const imagingMeasurementContent = toArray(dataset.ContentSequence).find(
|
||||
codeMeaningEquals(REPORT)
|
||||
);
|
||||
|
||||
// Retrieve the Measurements themselves
|
||||
const measurementGroups = toArray(
|
||||
imagingMeasurementContent.ContentSequence
|
||||
).filter(codeMeaningEquals(GROUP));
|
||||
|
||||
// For each of the supported measurement types, compute the measurement data
|
||||
const measurementData = {};
|
||||
|
||||
const cornerstoneToolClasses =
|
||||
MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
||||
|
||||
const registeredToolClasses = [];
|
||||
|
||||
Object.keys(cornerstoneToolClasses).forEach(key => {
|
||||
registeredToolClasses.push(cornerstoneToolClasses[key]);
|
||||
measurementData[key] = [];
|
||||
});
|
||||
|
||||
measurementGroups.forEach((measurementGroup, index) => {
|
||||
const measurementGroupContentSequence = toArray(
|
||||
measurementGroup.ContentSequence
|
||||
);
|
||||
|
||||
const TrackingIdentifierGroup = measurementGroupContentSequence.find(
|
||||
contentItem =>
|
||||
contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER
|
||||
);
|
||||
|
||||
const TrackingIdentifier = TrackingIdentifierGroup.TextValue;
|
||||
|
||||
let [cornerstoneTag, toolName] = TrackingIdentifier.split(':');
|
||||
if (supportedLegacyCornerstoneTags.includes(cornerstoneTag)) {
|
||||
cornerstoneTag = CORNERSTONE_3D_TAG;
|
||||
}
|
||||
|
||||
const mappedTrackingIdentifier = `${cornerstoneTag}:${toolName}`;
|
||||
|
||||
TrackingIdentifierGroup.TextValue = mappedTrackingIdentifier;
|
||||
});
|
||||
|
||||
return dataset;
|
||||
}
|
||||
|
||||
const toArray = function(x) {
|
||||
return Array.isArray(x) ? x : [x];
|
||||
};
|
||||
|
||||
const codeMeaningEquals = codeMeaningName => {
|
||||
return contentItem => {
|
||||
return contentItem.ConceptNameCodeSequence.CodeMeaning === codeMeaningName;
|
||||
};
|
||||
};
|
||||
-283
@@ -1,283 +0,0 @@
|
||||
export default function getCornerstoneToolStateToMeasurementSchema(
|
||||
toolType,
|
||||
MeasurementService,
|
||||
DisplaySetService,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID
|
||||
) {
|
||||
const _getValueTypeFromToolType = toolType => {
|
||||
const {
|
||||
POLYLINE,
|
||||
ELLIPSE,
|
||||
POINT,
|
||||
BIDIRECTIONAL,
|
||||
} = MeasurementService.VALUE_TYPES;
|
||||
|
||||
// TODO -> I get why this was attemped, but its not nearly flexible enough.
|
||||
// A single measurement may have an ellipse + a bidirectional measurement, for instances.
|
||||
// You can't define a bidirectional tool as a single type..
|
||||
// OHIF-230
|
||||
const TOOL_TYPE_TO_VALUE_TYPE = {
|
||||
Length: POLYLINE,
|
||||
EllipticalRoi: ELLIPSE,
|
||||
Bidirectional: BIDIRECTIONAL,
|
||||
ArrowAnnotate: POINT,
|
||||
};
|
||||
|
||||
return TOOL_TYPE_TO_VALUE_TYPE[toolType];
|
||||
};
|
||||
|
||||
switch (toolType) {
|
||||
case 'Length':
|
||||
return measurementData =>
|
||||
Length(
|
||||
measurementData,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
DisplaySetService,
|
||||
_getValueTypeFromToolType
|
||||
);
|
||||
case 'Bidirectional':
|
||||
return measurementData =>
|
||||
Bidirectional(
|
||||
measurementData,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
DisplaySetService,
|
||||
_getValueTypeFromToolType
|
||||
);
|
||||
case 'EllipticalRoi':
|
||||
return measurementData =>
|
||||
EllipticalRoi(
|
||||
measurementData,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
DisplaySetService,
|
||||
_getValueTypeFromToolType
|
||||
);
|
||||
case 'ArrowAnnotate':
|
||||
return measurementData =>
|
||||
ArrowAnnotate(
|
||||
measurementData,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
DisplaySetService,
|
||||
_getValueTypeFromToolType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Length(
|
||||
measurementData,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
DisplaySetService,
|
||||
_getValueTypeFromToolType
|
||||
) {
|
||||
const tool = measurementData.toolType || measurementData.toolName;
|
||||
|
||||
const displaySetInstanceUID = _getDisplaySetInstanceUID(
|
||||
DisplaySetService,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
);
|
||||
|
||||
const { handles, label } = measurementData;
|
||||
|
||||
const points = [];
|
||||
Object.keys(handles).map(handle => {
|
||||
if (['start', 'end'].includes(handle)) {
|
||||
let point = {};
|
||||
if (handles[handle].x) point.x = handles[handle].x;
|
||||
if (handles[handle].y) point.y = handles[handle].y;
|
||||
points.push(point);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
id: measurementData.id,
|
||||
SOPInstanceUID: SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
description: measurementData.description,
|
||||
unit: measurementData.unit,
|
||||
length: measurementData.length,
|
||||
type: _getValueTypeFromToolType(tool),
|
||||
points,
|
||||
label,
|
||||
};
|
||||
}
|
||||
|
||||
function Bidirectional(
|
||||
measurementData,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
DisplaySetService,
|
||||
_getValueTypeFromToolType
|
||||
) {
|
||||
const tool = measurementData.toolType || measurementData.toolName;
|
||||
|
||||
const displaySetInstanceUID = _getDisplaySetInstanceUID(
|
||||
DisplaySetService,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
);
|
||||
|
||||
const { handles, label } = measurementData;
|
||||
|
||||
const longAxis = [handles.start, handles.end];
|
||||
const shortAxis = [handles.perpendicularStart, handles.perpendicularEnd];
|
||||
|
||||
return {
|
||||
id: measurementData.id,
|
||||
SOPInstanceUID: SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
description: measurementData.description,
|
||||
unit: measurementData.unit,
|
||||
shortestDiameter: measurementData.shortestDiameter,
|
||||
longestDiameter: measurementData.longestDiameter,
|
||||
type: _getValueTypeFromToolType(tool),
|
||||
points: { longAxis, shortAxis },
|
||||
label,
|
||||
};
|
||||
}
|
||||
|
||||
function EllipticalRoi(
|
||||
measurementData,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
DisplaySetService,
|
||||
_getValueTypeFromToolType
|
||||
) {
|
||||
const tool = measurementData.toolType || measurementData.toolName;
|
||||
|
||||
const displaySetInstanceUID = _getDisplaySetInstanceUID(
|
||||
DisplaySetService,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
);
|
||||
const { handles, label } = measurementData;
|
||||
const { start, end } = handles;
|
||||
|
||||
const halfXLength = Math.abs(start.x - end.x) / 2;
|
||||
const halfYLength = Math.abs(start.y - end.y) / 2;
|
||||
|
||||
const points = [];
|
||||
const center = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
|
||||
|
||||
// To store similar to SR.
|
||||
if (halfXLength > halfYLength) {
|
||||
// X-axis major
|
||||
// Major axis
|
||||
points.push({ x: center.x - halfXLength, y: center.y });
|
||||
points.push({ x: center.x + halfXLength, y: center.y });
|
||||
// Minor axis
|
||||
points.push({ x: center.x, y: center.y - halfYLength });
|
||||
points.push({ x: center.x, y: center.y + halfYLength });
|
||||
} else {
|
||||
// Y-axis major
|
||||
// Major axis
|
||||
points.push({ x: center.x, y: center.y - halfYLength });
|
||||
points.push({ x: center.x, y: center.y + halfYLength });
|
||||
// Minor axis
|
||||
points.push({ x: center.x - halfXLength, y: center.y });
|
||||
points.push({ x: center.x + halfXLength, y: center.y });
|
||||
}
|
||||
|
||||
return {
|
||||
id: measurementData.id,
|
||||
SOPInstanceUID: SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
description: measurementData.description,
|
||||
unit: measurementData.unit,
|
||||
area:
|
||||
measurementData.cachedStats &&
|
||||
measurementData.cachedStats
|
||||
.area /* TODO: Add concept names instead (descriptor) */,
|
||||
type: _getValueTypeFromToolType(tool),
|
||||
points,
|
||||
label,
|
||||
};
|
||||
}
|
||||
|
||||
function ArrowAnnotate(
|
||||
measurementData,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
DisplaySetService,
|
||||
_getValueTypeFromToolType
|
||||
) {
|
||||
const tool = measurementData.toolType || measurementData.toolName;
|
||||
|
||||
const displaySetInstanceUID = _getDisplaySetInstanceUID(
|
||||
DisplaySetService,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
);
|
||||
|
||||
const { handles } = measurementData;
|
||||
|
||||
const points = [];
|
||||
Object.keys(handles).map(handle => {
|
||||
if (['start', 'end'].includes(handle)) {
|
||||
let point = {};
|
||||
if (handles[handle].x) point.x = handles[handle].x;
|
||||
if (handles[handle].y) point.y = handles[handle].y;
|
||||
points.push(point);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
id: measurementData.id,
|
||||
SOPInstanceUID: SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
label: measurementData.text,
|
||||
description: measurementData.description,
|
||||
unit: measurementData.unit,
|
||||
text: measurementData.text,
|
||||
type: _getValueTypeFromToolType(tool),
|
||||
points,
|
||||
};
|
||||
}
|
||||
|
||||
function _getDisplaySetInstanceUID(
|
||||
DisplaySetService,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
) {
|
||||
const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID
|
||||
);
|
||||
|
||||
return displaySet.displaySetInstanceUID;
|
||||
}
|
||||
@@ -2,4 +2,4 @@ export {
|
||||
TrackedMeasurementsContext,
|
||||
TrackedMeasurementsContextProvider,
|
||||
useTrackedMeasurements,
|
||||
} from './TrackedMeasurementsContext.jsx';
|
||||
} from './TrackedMeasurementsContext.tsx';
|
||||
+7
-4
@@ -44,7 +44,7 @@ const machineConfiguration = {
|
||||
target: 'promptHydrateStructuredReport',
|
||||
cond: 'hasNotIgnoredSRSeriesForHydration',
|
||||
},
|
||||
RESTORE_PROMPT_HYDRATE_SR: 'promptHydrateStructuredReport'
|
||||
RESTORE_PROMPT_HYDRATE_SR: 'promptHydrateStructuredReport',
|
||||
},
|
||||
},
|
||||
promptBeginTracking: {
|
||||
@@ -224,7 +224,7 @@ const machineConfiguration = {
|
||||
{
|
||||
target: 'idle',
|
||||
actions: ['ignoreHydrationForSRSeries'],
|
||||
cond: 'shouldIgnoreHydrationForSR'
|
||||
cond: 'shouldIgnoreHydrationForSR',
|
||||
},
|
||||
],
|
||||
onError: {
|
||||
@@ -308,7 +308,10 @@ const defaultOptions = {
|
||||
ignoredSeries: [...ctx.ignoredSeries, evt.data.SeriesInstanceUID],
|
||||
})),
|
||||
ignoreHydrationForSRSeries: assign((ctx, evt) => ({
|
||||
ignoredSRSeriesForHydration: [...ctx.ignoredSRSeriesForHydration, evt.data.srSeriesInstanceUID],
|
||||
ignoredSRSeriesForHydration: [
|
||||
...ctx.ignoredSRSeriesForHydration,
|
||||
evt.data.srSeriesInstanceUID,
|
||||
],
|
||||
})),
|
||||
addTrackedSeries: assign((ctx, evt) => ({
|
||||
prevTrackedSeries: [...ctx.trackedSeries],
|
||||
@@ -376,7 +379,7 @@ const defaultOptions = {
|
||||
ctx.trackedSeries.length > 1 ||
|
||||
!ctx.trackedSeries.includes(evt.SeriesInstanceUID),
|
||||
hasNotIgnoredSRSeriesForHydration: (ctx, evt) => {
|
||||
return !ctx.ignoredSRSeriesForHydration.includes(evt.SeriesInstanceUID)
|
||||
return !ctx.ignoredSRSeriesForHydration.includes(evt.SeriesInstanceUID);
|
||||
},
|
||||
isNewStudy: (ctx, evt) =>
|
||||
!ctx.ignoredSeries.includes(evt.SeriesInstanceUID) &&
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ const RESPONSE = {
|
||||
SET_STUDY_AND_SERIES: 3,
|
||||
};
|
||||
|
||||
function promptUser({ servicesManager, extensionManager }, ctx, evt) {
|
||||
function promptBeginTracking({ servicesManager, extensionManager }, ctx, evt) {
|
||||
const { UIViewportDialogService } = servicesManager.services;
|
||||
const { viewportIndex, StudyInstanceUID, SeriesInstanceUID } = evt;
|
||||
|
||||
@@ -33,7 +33,7 @@ function _askTrackMeasurements(UIViewportDialogService, viewportIndex) {
|
||||
id: 'prompt-begin-tracking-cancel',
|
||||
type: 'cancel',
|
||||
text: 'No',
|
||||
value: RESPONSE.CANCEL
|
||||
value: RESPONSE.CANCEL,
|
||||
},
|
||||
{
|
||||
id: 'prompt-begin-tracking-no-do-not-ask-again',
|
||||
@@ -68,4 +68,4 @@ function _askTrackMeasurements(UIViewportDialogService, viewportIndex) {
|
||||
});
|
||||
}
|
||||
|
||||
export default promptUser;
|
||||
export default promptBeginTracking;
|
||||
+13
-4
@@ -10,10 +10,19 @@ const RESPONSE = {
|
||||
HYDRATE_REPORT: 5,
|
||||
};
|
||||
|
||||
function promptUser({ servicesManager, extensionManager }, ctx, evt) {
|
||||
const { UIViewportDialogService, DisplaySetService } = servicesManager.services;
|
||||
function promptHydrateStructuredReport(
|
||||
{ servicesManager, extensionManager },
|
||||
ctx,
|
||||
evt
|
||||
) {
|
||||
const {
|
||||
UIViewportDialogService,
|
||||
DisplaySetService,
|
||||
} = servicesManager.services;
|
||||
const { viewportIndex, displaySetInstanceUID } = evt;
|
||||
const srDisplaySet = DisplaySetService.getDisplaySetByUID(displaySetInstanceUID)
|
||||
const srDisplaySet = DisplaySetService.getDisplaySetByUID(
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
return new Promise(async function(resolve, reject) {
|
||||
const promptResult = await _askTrackMeasurements(
|
||||
@@ -81,4 +90,4 @@ function _askTrackMeasurements(UIViewportDialogService, viewportIndex) {
|
||||
});
|
||||
}
|
||||
|
||||
export default promptUser;
|
||||
export default promptHydrateStructuredReport;
|
||||
+8
-3
@@ -1,9 +1,13 @@
|
||||
import createReportAsync from './../../_shared/createReportAsync.js';
|
||||
import createReportAsync from './../../_shared/createReportAsync';
|
||||
import createReportDialogPrompt from '../../_shared/createReportDialogPrompt';
|
||||
import getNextSRSeriesNumber from '../../_shared/getNextSRSeriesNumber';
|
||||
import RESPONSE from '../../_shared/PROMPT_RESPONSES';
|
||||
|
||||
function promptUser({ servicesManager, extensionManager }, ctx, evt) {
|
||||
function promptSaveReport(
|
||||
{ servicesManager, commandsManager, extensionManager },
|
||||
ctx,
|
||||
evt
|
||||
) {
|
||||
const {
|
||||
UIDialogService,
|
||||
MeasurementService,
|
||||
@@ -45,6 +49,7 @@ function promptUser({ servicesManager, extensionManager }, ctx, evt) {
|
||||
|
||||
displaySetInstanceUIDs = await createReportAsync(
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
dataSource,
|
||||
trackedMeasurements,
|
||||
{
|
||||
@@ -67,4 +72,4 @@ function promptUser({ servicesManager, extensionManager }, ctx, evt) {
|
||||
});
|
||||
}
|
||||
|
||||
export default promptUser;
|
||||
export default promptSaveReport;
|
||||
+2
-2
@@ -7,7 +7,7 @@ const RESPONSE = {
|
||||
NO_NOT_FOR_SERIES: 4,
|
||||
};
|
||||
|
||||
function promptUser({ servicesManager, extensionManager }, ctx, evt) {
|
||||
function promptTrackNewSeries({ servicesManager, extensionManager }, ctx, evt) {
|
||||
const { UIViewportDialogService } = servicesManager.services;
|
||||
const { viewportIndex, StudyInstanceUID, SeriesInstanceUID } = evt;
|
||||
|
||||
@@ -105,4 +105,4 @@ function _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) {
|
||||
});
|
||||
}
|
||||
|
||||
export default promptUser;
|
||||
export default promptTrackNewSeries;
|
||||
+2
-2
@@ -7,7 +7,7 @@ const RESPONSE = {
|
||||
NO_NOT_FOR_SERIES: 4,
|
||||
};
|
||||
|
||||
function promptUser({ servicesManager, extensionManager }, ctx, evt) {
|
||||
function promptTrackNewStudy({ servicesManager, extensionManager }, ctx, evt) {
|
||||
const { UIViewportDialogService } = servicesManager.services;
|
||||
const { viewportIndex, StudyInstanceUID, SeriesInstanceUID } = evt;
|
||||
|
||||
@@ -104,4 +104,4 @@ function _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) {
|
||||
});
|
||||
}
|
||||
|
||||
export default promptUser;
|
||||
export default promptTrackNewStudy;
|
||||
Reference in new issue
Block a user