feat: Annotation and Measurements support on multi-frame DICOM (#2973)
* added utilities to get frameNumber from imageId and add frameNumber to per-frame instance copy * Fix measurements' display texts on the Measurements Panel to have the correct instance number and frame number * fixed minor React bugs (errors on console) * fixed React's console bugs that are logged for some series that doesn't have "description" * bug fix - multi-frame files was not loading frames correctly. It was loading Frame 1 twice, and not loading the last frame. Due to wrong frame number indexing (frame number begins with 1, not 0) * metadata parser fix - providing default values of imagePlaneModule * measurement SR support on multi-frame DICOM * bug fix - jumping to the selected measurment on multi-frame DICOM * upgrade dcmjs dependency to 2.8.1 * StudySummary component - allow "description" to be null * make getUIDsFromImageID() method public from MetadataProvider * imageId usage fixes to be more stable * change the variable name to be more meaningful * fix metaProvider importing * for(...of) instead of for(i=0;i<length;..) as that doesn't assume anything about the layout/design and just gets next until done. * use Array.findIndex instead of plain for loop * use ReferencedSOPSequence[0] - because the ReferencedSOPSequence is an array that happens to have attributes of child zero when of length 1, but you shouldn't count on that. * DisplaySetService.getDisplaySetForSOPInstanceUID() - added optional frameNumber parameter for future usage : now they are just ignored as we are not supporting multiframe splits * simple code refactoring * refactoring for checking undefined values - mappedAnnotations * remove unreachable code * code refactoring - prefer conditional chaining * fix how we access imageIds of viewport (StackViewport)
This commit is contained in:
parent
af09578544
commit
b93067b6a2
@ -34,7 +34,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^3.0.0",
|
"@ohif/core": "^3.0.0",
|
||||||
"@ohif/ui": "^2.0.0",
|
"@ohif/ui": "^2.0.0",
|
||||||
"dcmjs": "^0.24.5",
|
"dcmjs": "^0.28.1",
|
||||||
"dicom-parser": "^1.8.9",
|
"dicom-parser": "^1.8.9",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { adapters } from 'dcmjs';
|
|||||||
|
|
||||||
const { CodeScheme: Cornerstone3DCodeScheme } = adapters.Cornerstone3D;
|
const { CodeScheme: Cornerstone3DCodeScheme } = adapters.Cornerstone3D;
|
||||||
|
|
||||||
const { ImageSet } = classes;
|
const { ImageSet, MetadataProvider: metadataProvider } = classes;
|
||||||
// TODO ->
|
// TODO ->
|
||||||
// Add SR thumbnail
|
// Add SR thumbnail
|
||||||
// Make viewport
|
// Make viewport
|
||||||
@ -214,20 +214,26 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
|||||||
newDisplaySet
|
newDisplaySet
|
||||||
);
|
);
|
||||||
|
|
||||||
for (let i = 0; i < images.length; i++) {
|
for (const imageId of imageIdsForDisplaySet) {
|
||||||
if (!unloadedMeasurements.length) {
|
if (!unloadedMeasurements.length) {
|
||||||
// All measurements loaded.
|
// All measurements loaded.
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const image = images[i];
|
const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID(
|
||||||
const { SOPInstanceUID } = image;
|
imageId
|
||||||
if (SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
);
|
||||||
const imageId = imageIdsForDisplaySet[i];
|
|
||||||
|
|
||||||
|
if (SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
||||||
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
||||||
const measurement = unloadedMeasurements[j];
|
const measurement = unloadedMeasurements[j];
|
||||||
if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID)) {
|
if (
|
||||||
|
_measurementReferencesSOPInstanceUID(
|
||||||
|
measurement,
|
||||||
|
SOPInstanceUID,
|
||||||
|
frameNumber
|
||||||
|
)
|
||||||
|
) {
|
||||||
addMeasurement(
|
addMeasurement(
|
||||||
measurement,
|
measurement,
|
||||||
imageId,
|
imageId,
|
||||||
@ -241,9 +247,23 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID) {
|
function _measurementReferencesSOPInstanceUID(
|
||||||
|
measurement,
|
||||||
|
SOPInstanceUID,
|
||||||
|
frameNumber
|
||||||
|
) {
|
||||||
const { coords } = measurement;
|
const { coords } = measurement;
|
||||||
|
|
||||||
|
// NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM
|
||||||
|
// Standard. But for now, we will support only one ReferenceFrameNumber.
|
||||||
|
const ReferencedFrameNumber =
|
||||||
|
(measurement.coords[0].ReferencedSOPSequence &&
|
||||||
|
measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber) ||
|
||||||
|
1;
|
||||||
|
|
||||||
|
if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber))
|
||||||
|
return false;
|
||||||
|
|
||||||
for (let j = 0; j < coords.length; j++) {
|
for (let j = 0; j < coords.length; j++) {
|
||||||
const coord = coords[j];
|
const coord = coords[j];
|
||||||
const { ReferencedSOPInstanceUID } = coord.ReferencedSOPSequence;
|
const { ReferencedSOPInstanceUID } = coord.ReferencedSOPSequence;
|
||||||
|
|||||||
@ -45,6 +45,12 @@ export default function addMeasurement(
|
|||||||
|
|
||||||
const annotationManager = annotation.state.getDefaultAnnotationManager();
|
const annotationManager = annotation.state.getDefaultAnnotationManager();
|
||||||
|
|
||||||
|
// Create Cornerstone3D Annotation from measurement
|
||||||
|
const frameNumber =
|
||||||
|
(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,
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -61,6 +67,7 @@ export default function addMeasurement(
|
|||||||
TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier,
|
TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier,
|
||||||
renderableData: measurementData.renderableData,
|
renderableData: measurementData.renderableData,
|
||||||
},
|
},
|
||||||
|
frameNumber: frameNumber,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,6 +82,7 @@ export default function addMeasurement(
|
|||||||
// It'd be super weird if it didn't anyway as a SCOORD.
|
// It'd be super weird if it didn't anyway as a SCOORD.
|
||||||
measurement.ReferencedSOPInstanceUID =
|
measurement.ReferencedSOPInstanceUID =
|
||||||
measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
||||||
|
measurement.frameNumber = frameNumber;
|
||||||
delete measurement.coords;
|
delete measurement.coords;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
"@ohif/core": "^3.0.0",
|
"@ohif/core": "^3.0.0",
|
||||||
"@ohif/ui": "^2.0.0",
|
"@ohif/ui": "^2.0.0",
|
||||||
"cornerstone-wado-image-loader": "^4.2.1",
|
"cornerstone-wado-image-loader": "^4.2.1",
|
||||||
"dcmjs": "^0.24.5",
|
"dcmjs": "^0.28.1",
|
||||||
"dicom-parser": "^1.8.9",
|
"dicom-parser": "^1.8.9",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
|
|||||||
@ -3,13 +3,23 @@ import ReactResizeDetector from 'react-resize-detector';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useViewportGrid } from '@ohif/ui';
|
import { useViewportGrid } from '@ohif/ui';
|
||||||
import * as cs3DTools from '@cornerstonejs/tools';
|
import * as cs3DTools from '@cornerstonejs/tools';
|
||||||
import { Enums, eventTarget, getEnabledElement } from '@cornerstonejs/core';
|
import {
|
||||||
|
Enums,
|
||||||
|
eventTarget,
|
||||||
|
getEnabledElement,
|
||||||
|
StackViewport,
|
||||||
|
} from '@cornerstonejs/core';
|
||||||
|
|
||||||
import { setEnabledElement } from '../state';
|
import { setEnabledElement } from '../state';
|
||||||
import CornerstoneCacheService from '../services/ViewportService/CornerstoneCacheService';
|
import CornerstoneCacheService from '../services/ViewportService/CornerstoneCacheService';
|
||||||
|
|
||||||
import './OHIFCornerstoneViewport.css';
|
import './OHIFCornerstoneViewport.css';
|
||||||
import CornerstoneOverlays from './Overlays/CornerstoneOverlays';
|
import CornerstoneOverlays from './Overlays/CornerstoneOverlays';
|
||||||
|
import {
|
||||||
|
IStackViewport,
|
||||||
|
IVolumeViewport,
|
||||||
|
} from '@cornerstonejs/core/dist/esm/types';
|
||||||
|
import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
||||||
|
|
||||||
const STACK = 'stack';
|
const STACK = 'stack';
|
||||||
|
|
||||||
@ -237,7 +247,7 @@ const OHIFCornerstoneViewport = React.memo(props => {
|
|||||||
displaySets,
|
displaySets,
|
||||||
viewportOptions.viewportType,
|
viewportOptions.viewportType,
|
||||||
dataSource,
|
dataSource,
|
||||||
(viewportDataLoaded) => {
|
viewportDataLoaded => {
|
||||||
CornerstoneViewportService.setViewportDisplaySets(
|
CornerstoneViewportService.setViewportDisplaySets(
|
||||||
viewportIndex,
|
viewportIndex,
|
||||||
viewportDataLoaded,
|
viewportDataLoaded,
|
||||||
@ -394,7 +404,7 @@ function _jumpToMeasurement(
|
|||||||
viewportGridService
|
viewportGridService
|
||||||
) {
|
) {
|
||||||
const targetElement = targetElementRef.current;
|
const targetElement = targetElementRef.current;
|
||||||
const { displaySetInstanceUID, SOPInstanceUID } = measurement;
|
const { displaySetInstanceUID, SOPInstanceUID, frameNumber } = measurement;
|
||||||
|
|
||||||
if (!SOPInstanceUID) {
|
if (!SOPInstanceUID) {
|
||||||
console.warn('cannot jump in a non-acquisition plane measurements yet');
|
console.warn('cannot jump in a non-acquisition plane measurements yet');
|
||||||
@ -404,17 +414,37 @@ function _jumpToMeasurement(
|
|||||||
displaySetInstanceUID
|
displaySetInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const imageIdIndex = referencedDisplaySet.images.findIndex(
|
|
||||||
i => i.SOPInstanceUID === SOPInstanceUID
|
|
||||||
);
|
|
||||||
|
|
||||||
// Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager
|
// Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager
|
||||||
// to set it properly
|
// to set it properly
|
||||||
// setCornerstoneMeasurementActive(measurement);
|
// setCornerstoneMeasurementActive(measurement);
|
||||||
|
|
||||||
viewportGridService.setActiveViewportIndex(viewportIndex);
|
viewportGridService.setActiveViewportIndex(viewportIndex);
|
||||||
|
|
||||||
if (getEnabledElement(targetElement)) {
|
const enableElement = getEnabledElement(targetElement);
|
||||||
|
if (enableElement) {
|
||||||
|
// See how the jumpToSlice() of Cornerstone3D deals with imageIdx param.
|
||||||
|
const viewport = enableElement.viewport as IStackViewport | IVolumeViewport;
|
||||||
|
|
||||||
|
let imageIdIndex = 0;
|
||||||
|
|
||||||
|
if (viewport instanceof StackViewport) {
|
||||||
|
const imageIds = viewport.getImageIds();
|
||||||
|
imageIdIndex = imageIds.findIndex(imageId => {
|
||||||
|
const {
|
||||||
|
SOPInstanceUID: aSOPInstanceUID,
|
||||||
|
frameNumber: aFrameNumber,
|
||||||
|
} = getSOPInstanceAttributes(imageId);
|
||||||
|
return (
|
||||||
|
aSOPInstanceUID === SOPInstanceUID &&
|
||||||
|
(!frameNumber || frameNumber === aFrameNumber)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
imageIdIndex = referencedDisplaySet.images.findIndex(
|
||||||
|
i => i.SOPInstanceUID === SOPInstanceUID
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
cs3DTools.utilities.jumpToSlice(targetElement, {
|
cs3DTools.utilities.jumpToSlice(targetElement, {
|
||||||
imageIndex: imageIdIndex,
|
imageIndex: imageIdIndex,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Enums, annotation } from '@cornerstonejs/tools';
|
|||||||
import { DicomMetadataStore } from '@ohif/core';
|
import { DicomMetadataStore } from '@ohif/core';
|
||||||
|
|
||||||
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory';
|
||||||
|
import getSOPInstanceAttributes from './utils/measurementServiceMappings/utils/getSOPInstanceAttributes';
|
||||||
|
|
||||||
const { removeAnnotation } = annotation.state;
|
const { removeAnnotation } = annotation.state;
|
||||||
|
|
||||||
@ -252,7 +253,18 @@ const connectMeasurementServiceToTools = (
|
|||||||
SOPInstanceUID
|
SOPInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const imageId = dataSource.getImageIdsForInstance({ instance });
|
let imageId;
|
||||||
|
let frameNumber = 1;
|
||||||
|
|
||||||
|
if (measurement?.metadata?.referencedImageId) {
|
||||||
|
imageId = measurement.metadata.referencedImageId;
|
||||||
|
frameNumber = getSOPInstanceAttributes(
|
||||||
|
measurement.metadata.referencedImageId
|
||||||
|
).frameNumber;
|
||||||
|
} else {
|
||||||
|
imageId = dataSource.getImageIdsForInstance({ instance });
|
||||||
|
}
|
||||||
|
|
||||||
const annotationManager = annotation.state.getDefaultAnnotationManager();
|
const annotationManager = annotation.state.getDefaultAnnotationManager();
|
||||||
annotationManager.addAnnotation({
|
annotationManager.addAnnotation({
|
||||||
annotationUID: measurement.uid,
|
annotationUID: measurement.uid,
|
||||||
@ -269,6 +281,7 @@ const connectMeasurementServiceToTools = (
|
|||||||
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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,6 +69,7 @@ const Length = {
|
|||||||
metadata,
|
metadata,
|
||||||
referenceSeriesUID: SeriesInstanceUID,
|
referenceSeriesUID: SeriesInstanceUID,
|
||||||
referenceStudyUID: StudyInstanceUID,
|
referenceStudyUID: StudyInstanceUID,
|
||||||
|
frameNumber: mappedAnnotations[0]?.frameNumber || 1,
|
||||||
toolName: metadata.toolName,
|
toolName: metadata.toolName,
|
||||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||||
label: data.text,
|
label: data.text,
|
||||||
@ -90,13 +91,16 @@ function getMappedAnnotations(annotation, DisplaySetService) {
|
|||||||
|
|
||||||
const annotations = [];
|
const annotations = [];
|
||||||
|
|
||||||
const { SOPInstanceUID, SeriesInstanceUID } = getSOPInstanceAttributes(
|
const {
|
||||||
referencedImageId
|
SOPInstanceUID,
|
||||||
);
|
SeriesInstanceUID,
|
||||||
|
frameNumber,
|
||||||
|
} = getSOPInstanceAttributes(referencedImageId);
|
||||||
|
|
||||||
const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
|
const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
|
||||||
SOPInstanceUID,
|
SOPInstanceUID,
|
||||||
SeriesInstanceUID
|
SeriesInstanceUID,
|
||||||
|
frameNumber
|
||||||
);
|
);
|
||||||
|
|
||||||
const { SeriesNumber } = displaySet;
|
const { SeriesNumber } = displaySet;
|
||||||
@ -105,6 +109,7 @@ function getMappedAnnotations(annotation, DisplaySetService) {
|
|||||||
SeriesInstanceUID,
|
SeriesInstanceUID,
|
||||||
SOPInstanceUID,
|
SOPInstanceUID,
|
||||||
SeriesNumber,
|
SeriesNumber,
|
||||||
|
frameNumber,
|
||||||
text,
|
text,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -119,7 +124,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
|
|||||||
const displayText = [];
|
const displayText = [];
|
||||||
|
|
||||||
// Area is the same for all series
|
// Area is the same for all series
|
||||||
const { SeriesNumber, SOPInstanceUID } = mappedAnnotations[0];
|
const { SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0];
|
||||||
|
|
||||||
const instance = displaySet.images.find(
|
const instance = displaySet.images.find(
|
||||||
image => image.SOPInstanceUID === SOPInstanceUID
|
image => image.SOPInstanceUID === SOPInstanceUID
|
||||||
@ -130,11 +135,10 @@ function getDisplayText(mappedAnnotations, displaySet) {
|
|||||||
InstanceNumber = instance.InstanceNumber;
|
InstanceNumber = instance.InstanceNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayText.push(
|
const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : '';
|
||||||
InstanceNumber
|
const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : '';
|
||||||
? `(S: ${SeriesNumber} I: ${InstanceNumber})`
|
|
||||||
: `(S: ${SeriesNumber})`
|
displayText.push(`(S: ${SeriesNumber}${instanceText}${frameText})`);
|
||||||
);
|
|
||||||
|
|
||||||
return displayText;
|
return displayText;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,6 +67,7 @@ const Bidirectional = {
|
|||||||
metadata,
|
metadata,
|
||||||
referenceSeriesUID: SeriesInstanceUID,
|
referenceSeriesUID: SeriesInstanceUID,
|
||||||
referenceStudyUID: StudyInstanceUID,
|
referenceStudyUID: StudyInstanceUID,
|
||||||
|
frameNumber: mappedAnnotations[0]?.frameNumber || 1,
|
||||||
toolName: metadata.toolName,
|
toolName: metadata.toolName,
|
||||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||||
label: data.label,
|
label: data.label,
|
||||||
@ -92,30 +93,33 @@ function getMappedAnnotations(annotation, DisplaySetService) {
|
|||||||
Object.keys(cachedStats).forEach(targetId => {
|
Object.keys(cachedStats).forEach(targetId => {
|
||||||
const targetStats = cachedStats[targetId];
|
const targetStats = cachedStats[targetId];
|
||||||
|
|
||||||
let displaySet;
|
if (!referencedImageId) {
|
||||||
|
|
||||||
if (referencedImageId) {
|
|
||||||
const { SOPInstanceUID, SeriesInstanceUID } = getSOPInstanceAttributes(
|
|
||||||
referencedImageId
|
|
||||||
);
|
|
||||||
|
|
||||||
displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
|
|
||||||
SOPInstanceUID,
|
|
||||||
SeriesInstanceUID
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Non-acquisition plane measurement mapping not supported'
|
'Non-acquisition plane measurement mapping not supported'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { SeriesNumber, SeriesInstanceUID } = displaySet;
|
const {
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
frameNumber,
|
||||||
|
} = getSOPInstanceAttributes(referencedImageId);
|
||||||
|
|
||||||
|
const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
frameNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
const { SeriesNumber } = displaySet;
|
||||||
const { length, width } = targetStats;
|
const { length, width } = targetStats;
|
||||||
const unit = 'mm';
|
const unit = 'mm';
|
||||||
|
|
||||||
annotations.push({
|
annotations.push({
|
||||||
SeriesInstanceUID,
|
SeriesInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
SeriesNumber,
|
SeriesNumber,
|
||||||
|
frameNumber,
|
||||||
unit,
|
unit,
|
||||||
length,
|
length,
|
||||||
width,
|
width,
|
||||||
@ -171,7 +175,13 @@ function getDisplayText(mappedAnnotations, displaySet) {
|
|||||||
const displayText = [];
|
const displayText = [];
|
||||||
|
|
||||||
// Area is the same for all series
|
// Area is the same for all series
|
||||||
const { length, width, SeriesNumber, SOPInstanceUID } = mappedAnnotations[0];
|
const {
|
||||||
|
length,
|
||||||
|
width,
|
||||||
|
SeriesNumber,
|
||||||
|
SOPInstanceUID,
|
||||||
|
frameNumber,
|
||||||
|
} = mappedAnnotations[0];
|
||||||
const roundedLength = utils.roundNumber(length, 2);
|
const roundedLength = utils.roundNumber(length, 2);
|
||||||
const roundedWidth = utils.roundNumber(width, 2);
|
const roundedWidth = utils.roundNumber(width, 2);
|
||||||
|
|
||||||
@ -184,10 +194,11 @@ function getDisplayText(mappedAnnotations, displaySet) {
|
|||||||
InstanceNumber = instance.InstanceNumber;
|
InstanceNumber = instance.InstanceNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : '';
|
||||||
|
const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : '';
|
||||||
|
|
||||||
displayText.push(
|
displayText.push(
|
||||||
InstanceNumber
|
`L: ${roundedLength} mm (S: ${SeriesNumber}${instanceText}${frameText})`
|
||||||
? `L: ${roundedLength} mm (S: ${SeriesNumber} I: ${InstanceNumber})`
|
|
||||||
: `L: ${roundedLength} mm (S: ${SeriesNumber})`
|
|
||||||
);
|
);
|
||||||
displayText.push(`W: ${roundedWidth} mm`);
|
displayText.push(`W: ${roundedWidth} mm`);
|
||||||
|
|
||||||
|
|||||||
@ -66,6 +66,7 @@ const EllipticalROI = {
|
|||||||
metadata,
|
metadata,
|
||||||
referenceSeriesUID: SeriesInstanceUID,
|
referenceSeriesUID: SeriesInstanceUID,
|
||||||
referenceStudyUID: StudyInstanceUID,
|
referenceStudyUID: StudyInstanceUID,
|
||||||
|
frameNumber: mappedAnnotations[0]?.frameNumber || 1,
|
||||||
toolName: metadata.toolName,
|
toolName: metadata.toolName,
|
||||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||||
label: data.label,
|
label: data.label,
|
||||||
@ -91,31 +92,34 @@ function getMappedAnnotations(annotation, DisplaySetService) {
|
|||||||
Object.keys(cachedStats).forEach(targetId => {
|
Object.keys(cachedStats).forEach(targetId => {
|
||||||
const targetStats = cachedStats[targetId];
|
const targetStats = cachedStats[targetId];
|
||||||
|
|
||||||
let displaySet;
|
if (!referencedImageId) {
|
||||||
|
|
||||||
if (referencedImageId) {
|
|
||||||
const { SOPInstanceUID, SeriesInstanceUID } = getSOPInstanceAttributes(
|
|
||||||
referencedImageId
|
|
||||||
);
|
|
||||||
|
|
||||||
displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
|
|
||||||
SOPInstanceUID,
|
|
||||||
SeriesInstanceUID
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Todo: Non-acquisition plane measurement mapping not supported yet
|
// Todo: Non-acquisition plane measurement mapping not supported yet
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Non-acquisition plane measurement mapping not supported'
|
'Non-acquisition plane measurement mapping not supported'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { SeriesNumber, SeriesInstanceUID } = displaySet;
|
const {
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
frameNumber,
|
||||||
|
} = getSOPInstanceAttributes(referencedImageId);
|
||||||
|
|
||||||
|
const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
frameNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
const { SeriesNumber } = displaySet;
|
||||||
const { mean, stdDev, max, area, Modality } = targetStats;
|
const { mean, stdDev, max, area, Modality } = targetStats;
|
||||||
const unit = getModalityUnit(Modality);
|
const unit = getModalityUnit(Modality);
|
||||||
|
|
||||||
annotations.push({
|
annotations.push({
|
||||||
SeriesInstanceUID,
|
SeriesInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
SeriesNumber,
|
SeriesNumber,
|
||||||
|
frameNumber,
|
||||||
Modality,
|
Modality,
|
||||||
unit,
|
unit,
|
||||||
mean,
|
mean,
|
||||||
@ -184,7 +188,7 @@ function getDisplayText(mappedAnnotations, displaySet) {
|
|||||||
const displayText = [];
|
const displayText = [];
|
||||||
|
|
||||||
// Area is the same for all series
|
// Area is the same for all series
|
||||||
const { area, SOPInstanceUID } = mappedAnnotations[0];
|
const { area, SOPInstanceUID, frameNumber } = mappedAnnotations[0];
|
||||||
|
|
||||||
const instance = displaySet.images.find(
|
const instance = displaySet.images.find(
|
||||||
image => image.SOPInstanceUID === SOPInstanceUID
|
image => image.SOPInstanceUID === SOPInstanceUID
|
||||||
@ -195,6 +199,9 @@ function getDisplayText(mappedAnnotations, displaySet) {
|
|||||||
InstanceNumber = instance.InstanceNumber;
|
InstanceNumber = instance.InstanceNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : '';
|
||||||
|
const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : '';
|
||||||
|
|
||||||
const roundedArea = utils.roundNumber(area, 2);
|
const roundedArea = utils.roundNumber(area, 2);
|
||||||
displayText.push(`${roundedArea} mm<sup>2</sup>`);
|
displayText.push(`${roundedArea} mm<sup>2</sup>`);
|
||||||
|
|
||||||
@ -202,14 +209,15 @@ function getDisplayText(mappedAnnotations, displaySet) {
|
|||||||
mappedAnnotations.forEach(mappedAnnotation => {
|
mappedAnnotations.forEach(mappedAnnotation => {
|
||||||
const { unit, max, SeriesNumber } = mappedAnnotation;
|
const { unit, max, SeriesNumber } = mappedAnnotation;
|
||||||
|
|
||||||
|
let maxStr = '';
|
||||||
if (max) {
|
if (max) {
|
||||||
const roundedMax = utils.roundNumber(max, 2);
|
const roundedMax = utils.roundNumber(max, 2);
|
||||||
|
maxStr = `Max: ${roundedMax} <small>${unit}</small> `;
|
||||||
|
}
|
||||||
|
|
||||||
displayText.push(
|
const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`;
|
||||||
InstanceNumber
|
if (!displayText.includes(str)) {
|
||||||
? `Max: ${roundedMax} <small>${unit}</small> (S:${SeriesNumber} I:${InstanceNumber})`
|
displayText.push(str);
|
||||||
: `Max: ${roundedMax} <small>${unit}</small> (S:${SeriesNumber})`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -72,6 +72,7 @@ const Length = {
|
|||||||
metadata,
|
metadata,
|
||||||
referenceSeriesUID: SeriesInstanceUID,
|
referenceSeriesUID: SeriesInstanceUID,
|
||||||
referenceStudyUID: StudyInstanceUID,
|
referenceStudyUID: StudyInstanceUID,
|
||||||
|
frameNumber: mappedAnnotations[0]?.frameNumber || 1,
|
||||||
toolName: metadata.toolName,
|
toolName: metadata.toolName,
|
||||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||||
label: data.label,
|
label: data.label,
|
||||||
@ -97,30 +98,33 @@ function getMappedAnnotations(annotation, DisplaySetService) {
|
|||||||
Object.keys(cachedStats).forEach(targetId => {
|
Object.keys(cachedStats).forEach(targetId => {
|
||||||
const targetStats = cachedStats[targetId];
|
const targetStats = cachedStats[targetId];
|
||||||
|
|
||||||
let displaySet;
|
if (!referencedImageId) {
|
||||||
|
|
||||||
if (referencedImageId) {
|
|
||||||
const { SOPInstanceUID, SeriesInstanceUID } = getSOPInstanceAttributes(
|
|
||||||
referencedImageId
|
|
||||||
);
|
|
||||||
|
|
||||||
displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
|
|
||||||
SOPInstanceUID,
|
|
||||||
SeriesInstanceUID
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Non-acquisition plane measurement mapping not supported'
|
'Non-acquisition plane measurement mapping not supported'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { SeriesNumber, SeriesInstanceUID } = displaySet;
|
const {
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
frameNumber,
|
||||||
|
} = getSOPInstanceAttributes(referencedImageId);
|
||||||
|
|
||||||
|
const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID(
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
frameNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
const { SeriesNumber } = displaySet;
|
||||||
const { length } = targetStats;
|
const { length } = targetStats;
|
||||||
const unit = 'mm';
|
const unit = 'mm';
|
||||||
|
|
||||||
annotations.push({
|
annotations.push({
|
||||||
SeriesInstanceUID,
|
SeriesInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
SeriesNumber,
|
SeriesNumber,
|
||||||
|
frameNumber,
|
||||||
unit,
|
unit,
|
||||||
length,
|
length,
|
||||||
});
|
});
|
||||||
@ -175,7 +179,12 @@ function getDisplayText(mappedAnnotations, displaySet) {
|
|||||||
const displayText = [];
|
const displayText = [];
|
||||||
|
|
||||||
// Area is the same for all series
|
// Area is the same for all series
|
||||||
const { length, SeriesNumber, SOPInstanceUID } = mappedAnnotations[0];
|
const {
|
||||||
|
length,
|
||||||
|
SeriesNumber,
|
||||||
|
SOPInstanceUID,
|
||||||
|
frameNumber,
|
||||||
|
} = mappedAnnotations[0];
|
||||||
|
|
||||||
const instance = displaySet.images.find(
|
const instance = displaySet.images.find(
|
||||||
image => image.SOPInstanceUID === SOPInstanceUID
|
image => image.SOPInstanceUID === SOPInstanceUID
|
||||||
@ -186,11 +195,12 @@ function getDisplayText(mappedAnnotations, displaySet) {
|
|||||||
InstanceNumber = instance.InstanceNumber;
|
InstanceNumber = instance.InstanceNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : '';
|
||||||
|
const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : '';
|
||||||
|
|
||||||
const roundedLength = utils.roundNumber(length, 2);
|
const roundedLength = utils.roundNumber(length, 2);
|
||||||
displayText.push(
|
displayText.push(
|
||||||
InstanceNumber
|
`${roundedLength} mm (S: ${SeriesNumber}${instanceText}${frameText})`
|
||||||
? `${roundedLength} mm (S: ${SeriesNumber} I: ${InstanceNumber})`
|
|
||||||
: `${roundedLength} mm (S: ${SeriesNumber})`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return displayText;
|
return displayText;
|
||||||
|
|||||||
@ -45,6 +45,7 @@ function _getUIDFromImageID(imageId) {
|
|||||||
SOPInstanceUID: instance.SOPInstanceUID,
|
SOPInstanceUID: instance.SOPInstanceUID,
|
||||||
SeriesInstanceUID: instance.SeriesInstanceUID,
|
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||||
StudyInstanceUID: instance.StudyInstanceUID,
|
StudyInstanceUID: instance.StudyInstanceUID,
|
||||||
|
frameNumber: instance.frameNumber || 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^3.0.0",
|
"@ohif/core": "^3.0.0",
|
||||||
"@ohif/i18n": "^1.0.0",
|
"@ohif/i18n": "^1.0.0",
|
||||||
"dcmjs": "^0.24.5",
|
"dcmjs": "^0.28.1",
|
||||||
"dicomweb-client": "^0.6.0",
|
"dicomweb-client": "^0.6.0",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
|||||||
@ -494,10 +494,10 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
|
|||||||
const NumberOfFrames = instance.NumberOfFrames;
|
const NumberOfFrames = instance.NumberOfFrames;
|
||||||
|
|
||||||
if (NumberOfFrames > 1) {
|
if (NumberOfFrames > 1) {
|
||||||
for (let i = 0; i < NumberOfFrames; i++) {
|
for (let frame = 1; frame <= NumberOfFrames; frame++) {
|
||||||
const imageId = this.getImageIdsForInstance({
|
const imageId = this.getImageIdsForInstance({
|
||||||
instance,
|
instance,
|
||||||
frame: i,
|
frame,
|
||||||
});
|
});
|
||||||
imageIds.push(imageId);
|
imageIds.push(imageId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^3.0.0",
|
"@ohif/core": "^3.0.0",
|
||||||
"@ohif/ui": "^2.0.0",
|
"@ohif/ui": "^2.0.0",
|
||||||
"dcmjs": "^0.24.5",
|
"dcmjs": "^0.28.1",
|
||||||
"dicom-parser": "^1.8.9",
|
"dicom-parser": "^1.8.9",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^3.0.0",
|
"@ohif/core": "^3.0.0",
|
||||||
"@ohif/ui": "^2.0.0",
|
"@ohif/ui": "^2.0.0",
|
||||||
"dcmjs": "^0.24.5",
|
"dcmjs": "^0.28.1",
|
||||||
"dicom-parser": "^1.8.9",
|
"dicom-parser": "^1.8.9",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
|
|||||||
@ -35,7 +35,7 @@
|
|||||||
"@cornerstonejs/core": "^0.16.1",
|
"@cornerstonejs/core": "^0.16.1",
|
||||||
"@cornerstonejs/tools": "^0.24.1",
|
"@cornerstonejs/tools": "^0.24.1",
|
||||||
"@ohif/extension-cornerstone-dicom-sr": "^3.0.0",
|
"@ohif/extension-cornerstone-dicom-sr": "^3.0.0",
|
||||||
"dcmjs": "^0.24.5",
|
"dcmjs": "^0.28.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
|||||||
@ -44,13 +44,17 @@ export default function _hydrateStructuredReport(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const sopInstanceUIDToImageId = {};
|
const sopInstanceUIDToImageId = {};
|
||||||
let imageIdsForToolState = [];
|
const imageIdsForToolState = {};
|
||||||
|
|
||||||
displaySet.measurements.forEach(measurement => {
|
displaySet.measurements.forEach(measurement => {
|
||||||
const { ReferencedSOPInstanceUID, imageId } = measurement;
|
const { ReferencedSOPInstanceUID, imageId, frameNumber } = measurement;
|
||||||
imageIdsForToolState.push(imageId);
|
|
||||||
if (!sopInstanceUIDToImageId[ReferencedSOPInstanceUID]) {
|
if (!sopInstanceUIDToImageId[ReferencedSOPInstanceUID]) {
|
||||||
sopInstanceUIDToImageId[ReferencedSOPInstanceUID] = imageId;
|
sopInstanceUIDToImageId[ReferencedSOPInstanceUID] = imageId;
|
||||||
|
imageIdsForToolState[ReferencedSOPInstanceUID] = [];
|
||||||
|
}
|
||||||
|
if (!imageIdsForToolState[ReferencedSOPInstanceUID][frameNumber]) {
|
||||||
|
imageIdsForToolState[ReferencedSOPInstanceUID][frameNumber] = imageId;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -88,7 +92,14 @@ export default function _hydrateStructuredReport(
|
|||||||
|
|
||||||
toolDataForAnnotationType.forEach(toolData => {
|
toolDataForAnnotationType.forEach(toolData => {
|
||||||
// Add the measurement to toolState
|
// Add the measurement to toolState
|
||||||
const imageId = sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
// dcmjs and Cornerstone3D has structural defect in supporting multi-frame
|
||||||
|
// files, and looking up the imageId from sopInstanceUIDToImageId results
|
||||||
|
// in the wrong value.
|
||||||
|
const frameNumber =
|
||||||
|
(toolData.annotation.data && toolData.annotation.data.frameNumber) || 1;
|
||||||
|
const imageId =
|
||||||
|
imageIdsForToolState[toolData.sopInstanceUid][frameNumber] ||
|
||||||
|
sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
||||||
|
|
||||||
if (!imageIds.includes(imageId)) {
|
if (!imageIds.includes(imageId)) {
|
||||||
imageIds.push(imageId);
|
imageIds.push(imageId);
|
||||||
@ -125,7 +136,14 @@ export default function _hydrateStructuredReport(
|
|||||||
|
|
||||||
toolDataForAnnotationType.forEach(toolData => {
|
toolDataForAnnotationType.forEach(toolData => {
|
||||||
// Add the measurement to toolState
|
// Add the measurement to toolState
|
||||||
const imageId = sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
// dcmjs and Cornerstone3D has structural defect in supporting multi-frame
|
||||||
|
// files, and looking up the imageId from sopInstanceUIDToImageId results
|
||||||
|
// in the wrong value.
|
||||||
|
const frameNumber =
|
||||||
|
(toolData.annotation.data && toolData.annotation.data.frameNumber) || 1;
|
||||||
|
const imageId =
|
||||||
|
imageIdsForToolState[toolData.sopInstanceUid][frameNumber] ||
|
||||||
|
sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
||||||
|
|
||||||
toolData.uid = guid();
|
toolData.uid = guid();
|
||||||
|
|
||||||
|
|||||||
@ -17,9 +17,9 @@ const { formatDate } = utils;
|
|||||||
|
|
||||||
const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = {
|
const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = {
|
||||||
key: undefined, //
|
key: undefined, //
|
||||||
date: undefined, // '07-Sep-2010',
|
date: '', // '07-Sep-2010',
|
||||||
modality: undefined, // 'CT',
|
modality: '', // 'CT',
|
||||||
description: undefined, // 'CHEST/ABD/PELVIS W CONTRAST',
|
description: '', // 'CHEST/ABD/PELVIS W CONTRAST',
|
||||||
};
|
};
|
||||||
|
|
||||||
function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
|
function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ohif/core": "^3.0.0",
|
"@ohif/core": "^3.0.0",
|
||||||
"@ohif/ui": "^2.0.0",
|
"@ohif/ui": "^2.0.0",
|
||||||
"dcmjs": "0.22.0",
|
"dcmjs": "0.28.0",
|
||||||
"dicom-parser": "^1.8.9",
|
"dicom-parser": "^1.8.9",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
|
|||||||
@ -13,5 +13,6 @@ function _getUIDFromImageID(imageId) {
|
|||||||
SOPInstanceUID: instance.SOPInstanceUID,
|
SOPInstanceUID: instance.SOPInstanceUID,
|
||||||
SeriesInstanceUID: instance.SeriesInstanceUID,
|
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||||
StudyInstanceUID: instance.StudyInstanceUID,
|
StudyInstanceUID: instance.StudyInstanceUID,
|
||||||
|
frameNumber: instance.frameNumber || 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.16.3",
|
"@babel/runtime": "7.16.3",
|
||||||
"dcmjs": "^0.24.5",
|
"dcmjs": "^0.28.1",
|
||||||
"dicomweb-client": "^0.6.0",
|
"dicomweb-client": "^0.6.0",
|
||||||
"isomorphic-base64": "^1.0.2",
|
"isomorphic-base64": "^1.0.2",
|
||||||
"lodash.merge": "^4.6.1",
|
"lodash.merge": "^4.6.1",
|
||||||
|
|||||||
@ -52,7 +52,7 @@ class MetadataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getInstance(imageId) {
|
_getInstance(imageId) {
|
||||||
const uids = this._getUIDsFromImageID(imageId);
|
const uids = this.getUIDsFromImageID(imageId);
|
||||||
|
|
||||||
if (!uids) {
|
if (!uids) {
|
||||||
return;
|
return;
|
||||||
@ -191,14 +191,16 @@ class MetadataProvider {
|
|||||||
rows: toNumber(instance.Rows),
|
rows: toNumber(instance.Rows),
|
||||||
columns: toNumber(instance.Columns),
|
columns: toNumber(instance.Columns),
|
||||||
imageOrientationPatient: toNumber(ImageOrientationPatient),
|
imageOrientationPatient: toNumber(ImageOrientationPatient),
|
||||||
rowCosines: toNumber(rowCosines),
|
rowCosines: toNumber(rowCosines || [0, 1, 0]),
|
||||||
columnCosines: toNumber(columnCosines),
|
columnCosines: toNumber(columnCosines || [0, 0, -1]),
|
||||||
imagePositionPatient: toNumber(instance.ImagePositionPatient),
|
imagePositionPatient: toNumber(
|
||||||
|
instance.ImagePositionPatient || [0, 0, 0]
|
||||||
|
),
|
||||||
sliceThickness: toNumber(instance.SliceThickness),
|
sliceThickness: toNumber(instance.SliceThickness),
|
||||||
sliceLocation: toNumber(instance.SliceLocation),
|
sliceLocation: toNumber(instance.SliceLocation),
|
||||||
pixelSpacing: toNumber(PixelSpacing),
|
pixelSpacing: toNumber(PixelSpacing || 1),
|
||||||
rowPixelSpacing: toNumber(rowPixelSpacing),
|
rowPixelSpacing: toNumber(rowPixelSpacing || 1),
|
||||||
columnPixelSpacing: toNumber(columnPixelSpacing),
|
columnPixelSpacing: toNumber(columnPixelSpacing || 1),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case WADO_IMAGE_LOADER_TAGS.IMAGE_PIXEL_MODULE:
|
case WADO_IMAGE_LOADER_TAGS.IMAGE_PIXEL_MODULE:
|
||||||
@ -409,7 +411,7 @@ class MetadataProvider {
|
|||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getUIDsFromImageID(imageId) {
|
getUIDsFromImageID(imageId) {
|
||||||
// TODO: adding csiv here is not really correct. Probably need to use
|
// TODO: adding csiv here is not really correct. Probably need to use
|
||||||
// metadataProvider.addImageIdToUIDs(imageId, {
|
// metadataProvider.addImageIdToUIDs(imageId, {
|
||||||
// StudyInstanceUID,
|
// StudyInstanceUID,
|
||||||
|
|||||||
@ -75,7 +75,11 @@ export default class DisplaySetService {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
getDisplaySetForSOPInstanceUID(SOPInstanceUID, SeriesInstanceUID) {
|
getDisplaySetForSOPInstanceUID(
|
||||||
|
SOPInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
frameNumber
|
||||||
|
) {
|
||||||
const displaySets = SeriesInstanceUID
|
const displaySets = SeriesInstanceUID
|
||||||
? this.getDisplaySetsForSeries(SeriesInstanceUID)
|
? this.getDisplaySetsForSeries(SeriesInstanceUID)
|
||||||
: this.getDisplaySetCache();
|
: this.getDisplaySetCache();
|
||||||
|
|||||||
@ -38,6 +38,7 @@ const MEASUREMENT_SCHEMA_KEYS = [
|
|||||||
'FrameOfReferenceUID',
|
'FrameOfReferenceUID',
|
||||||
'referenceStudyUID',
|
'referenceStudyUID',
|
||||||
'referenceSeriesUID',
|
'referenceSeriesUID',
|
||||||
|
'frameNumber',
|
||||||
'displaySetInstanceUID',
|
'displaySetInstanceUID',
|
||||||
'label',
|
'label',
|
||||||
'description',
|
'description',
|
||||||
|
|||||||
@ -12,22 +12,33 @@ const combineFrameInstance = (frame, instance) => {
|
|||||||
const {
|
const {
|
||||||
PerFrameFunctionalGroupsSequence,
|
PerFrameFunctionalGroupsSequence,
|
||||||
SharedFunctionalGroupsSequence,
|
SharedFunctionalGroupsSequence,
|
||||||
|
NumberOfFrames,
|
||||||
} = instance;
|
} = instance;
|
||||||
if (!PerFrameFunctionalGroupsSequence) return instance;
|
|
||||||
const shared = Object.values(SharedFunctionalGroupsSequence[0])
|
if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) {
|
||||||
.map(it => it[0])
|
const frameNumber = Number.parseInt(frame || 1);
|
||||||
.filter(it => it !== undefined && typeof it === 'object');
|
const shared = (SharedFunctionalGroupsSequence
|
||||||
const perFrame = Object.values(
|
? Object.values(SharedFunctionalGroupsSequence[0])
|
||||||
PerFrameFunctionalGroupsSequence[(frame || 1) - 1]
|
: []
|
||||||
)
|
)
|
||||||
.map(it => it[0])
|
.map(it => it[0])
|
||||||
.filter(it => it !== undefined && typeof it === 'object');
|
.filter(it => it !== undefined && typeof it === 'object');
|
||||||
|
const perFrame = (PerFrameFunctionalGroupsSequence
|
||||||
|
? Object.values(PerFrameFunctionalGroupsSequence[frameNumber - 1])
|
||||||
|
: []
|
||||||
|
)
|
||||||
|
.map(it => it[0])
|
||||||
|
.filter(it => it !== undefined && typeof it === 'object');
|
||||||
|
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
{},
|
{ frameNumber: frameNumber },
|
||||||
instance,
|
instance,
|
||||||
...Object.values(shared),
|
...Object.values(shared),
|
||||||
...Object.values(perFrame)
|
...Object.values(perFrame)
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default combineFrameInstance;
|
export default combineFrameInstance;
|
||||||
|
|||||||
@ -235,6 +235,7 @@ Button.propTypes = {
|
|||||||
]),
|
]),
|
||||||
border: PropTypes.oneOf([
|
border: PropTypes.oneOf([
|
||||||
'none',
|
'none',
|
||||||
|
'light',
|
||||||
'default',
|
'default',
|
||||||
'primary',
|
'primary',
|
||||||
'secondary',
|
'secondary',
|
||||||
|
|||||||
@ -52,9 +52,9 @@ const MeasurementItem = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="relative flex flex-col flex-1 px-2 py-1">
|
<div className="relative flex flex-col flex-1 px-2 py-1">
|
||||||
<span className="mb-1 text-base text-primary-light">{label}</span>
|
<span className="mb-1 text-base text-primary-light">{label}</span>
|
||||||
{displayText.map(line => (
|
{displayText.map((line, i) => (
|
||||||
<span
|
<span
|
||||||
key={line}
|
key={i}
|
||||||
className="pl-2 text-base text-white border-l border-primary-light"
|
className="pl-2 text-base text-white border-l border-primary-light"
|
||||||
dangerouslySetInnerHTML={{ __html: line }}
|
dangerouslySetInnerHTML={{ __html: line }}
|
||||||
></span>
|
></span>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const StudySummary = ({ date, modality, description }) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-2 text-base leading-none truncate text-primary-light ellipse">
|
<div className="pt-2 text-base leading-none truncate text-primary-light ellipse">
|
||||||
{description}
|
{description || ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -20,7 +20,7 @@ const StudySummary = ({ date, modality, description }) => {
|
|||||||
StudySummary.propTypes = {
|
StudySummary.propTypes = {
|
||||||
date: PropTypes.string.isRequired,
|
date: PropTypes.string.isRequired,
|
||||||
modality: PropTypes.string.isRequired,
|
modality: PropTypes.string.isRequired,
|
||||||
description: PropTypes.string.isRequired,
|
description: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StudySummary;
|
export default StudySummary;
|
||||||
|
|||||||
@ -63,7 +63,7 @@
|
|||||||
"core-js": "^3.16.1",
|
"core-js": "^3.16.1",
|
||||||
"cornerstone-math": "^0.1.9",
|
"cornerstone-math": "^0.1.9",
|
||||||
"cornerstone-wado-image-loader": "^4.2.1",
|
"cornerstone-wado-image-loader": "^4.2.1",
|
||||||
"dcmjs": "^0.24.5",
|
"dcmjs": "^0.28.1",
|
||||||
"detect-gpu": "^4.0.16",
|
"detect-gpu": "^4.0.16",
|
||||||
"dicom-parser": "^1.8.9",
|
"dicom-parser": "^1.8.9",
|
||||||
"dotenv-webpack": "^1.7.0",
|
"dotenv-webpack": "^1.7.0",
|
||||||
|
|||||||
@ -10033,16 +10033,17 @@ dayjs@^1.10.4:
|
|||||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.2.tgz#fa0f5223ef0d6724b3d8327134890cfe3d72fbe5"
|
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.2.tgz#fa0f5223ef0d6724b3d8327134890cfe3d72fbe5"
|
||||||
integrity sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==
|
integrity sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==
|
||||||
|
|
||||||
dcmjs@^0.24.5:
|
dcmjs@^0.28.1:
|
||||||
version "0.24.6"
|
version "0.28.1"
|
||||||
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.24.6.tgz#436d00361fb8d4286e68e2b0cab15939fa3acafb"
|
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.28.1.tgz#e9b20dddef41fbdbf87c96d0e31531dd6ac93087"
|
||||||
integrity sha512-ts/DigszrYXMOmYLRVlik4Z6Oq0fb4ykrveFkcIPclTk/uoND0uwbhO4IPMNnuhGGwaVFF6uXQISY+kgbpjn2g==
|
integrity sha512-PzlxfdZazOkv9OCFYtp9jWzMt2GnfSvBOQ2SCqnFlDbzZWAiT6kvrfVHx5ux65Lct0P0IGLGoqRdQChtsjzL5A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime-corejs2" "^7.17.8"
|
"@babel/runtime-corejs2" "^7.17.8"
|
||||||
gl-matrix "^3.1.0"
|
gl-matrix "^3.1.0"
|
||||||
lodash.clonedeep "^4.5.0"
|
lodash.clonedeep "^4.5.0"
|
||||||
loglevelnext "^3.0.1"
|
loglevelnext "^3.0.1"
|
||||||
ndarray "^1.0.19"
|
ndarray "^1.0.19"
|
||||||
|
pako "^2.0.4"
|
||||||
|
|
||||||
debug-log@^1.0.0:
|
debug-log@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user