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:
authored and GitHub committed 2022-07-27 12:39:04 -04:00
1 parent 86dcaf6293
commit bd1c38c00c
668 files changed
+41669 -13410

No files matched your search

@@ -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,
};
@@ -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;
};
};
@@ -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';
@@ -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) &&
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;