diff --git a/addOns/externals/devDependencies/package.json b/addOns/externals/devDependencies/package.json
index 5533fb4f873..f64d2d77184 100644
--- a/addOns/externals/devDependencies/package.json
+++ b/addOns/externals/devDependencies/package.json
@@ -68,7 +68,7 @@
"prettier-plugin-tailwindcss": "^0.5.4",
"react-refresh": "^0.14.2",
"semver": "^7.5.1",
- "serve": "^14.2.0",
+ "serve": "^14.2.4",
"shader-loader": "^1.3.1",
"shx": "^0.3.3",
"source-map-loader": "^4.0.1",
diff --git a/extensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsx b/extensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsx
index 2133e96ddfd..959d8dbfe37 100644
--- a/extensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsx
+++ b/extensions/cornerstone-dicom-pmap/src/viewports/OHIFCornerstonePMAPViewport.tsx
@@ -97,6 +97,7 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) {
viewportType: 'volume',
orientation: viewportOptions.orientation,
viewportId: viewportOptions.viewportId,
+ presentationIds: viewportOptions.presentationIds,
}}
displaySetOptions={[{}, pmapDisplaySetOptions]}
>
diff --git a/extensions/cornerstone-dicom-rt/src/loadRTStruct.js b/extensions/cornerstone-dicom-rt/src/loadRTStruct.js
index 10d9cc99f5b..0e9ab38417e 100644
--- a/extensions/cornerstone-dicom-rt/src/loadRTStruct.js
+++ b/extensions/cornerstone-dicom-rt/src/loadRTStruct.js
@@ -126,6 +126,7 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet,
SeriesInstanceUID: instance.SeriesInstanceUID,
ROIContours: [],
visible: true,
+ ReferencedSOPInstanceUIDsSet: new Set(),
};
for (let i = 0; i < ROIContourSequence.length; i++) {
@@ -142,7 +143,8 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet,
const contourPoints = [];
for (let c = 0; c < ContourSequenceArray.length; c++) {
- const { ContourData, NumberOfContourPoints, ContourGeometricType } = ContourSequenceArray[c];
+ const { ContourData, NumberOfContourPoints, ContourGeometricType, ContourImageSequence } =
+ ContourSequenceArray[c];
let isSupported = false;
@@ -172,6 +174,12 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet,
type: ContourGeometricType,
isSupported,
});
+
+ if (ContourImageSequence?.ReferencedSOPInstanceUID) {
+ structureSet.ReferencedSOPInstanceUIDsSet.add(
+ ContourImageSequence?.ReferencedSOPInstanceUID
+ );
+ }
}
_setROIContourMetadata(
diff --git a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx
index 1c4a9e0e9b3..662541ff1d9 100644
--- a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx
+++ b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx
@@ -6,7 +6,7 @@ import promptHydrateRT from '../utils/promptHydrateRT';
import _getStatusComponent from './_getStatusComponent';
import createRTToolGroupAndAddTools from '../utils/initRTToolGroup';
-import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
+import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup';
@@ -43,8 +43,8 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
const [viewportGrid, viewportGridService] = useViewportGrid();
// States
- const [isToolGroupCreated, setToolGroupCreated] = useState(false);
const [selectedSegment, setSelectedSegment] = useState(1);
+ const { setPositionPresentation } = usePositionPresentationStore();
// Hydration means that the RT is opened and segments are loaded into the
// segmentation panel, and RT is also rendered on any viewport that is in the
@@ -123,6 +123,7 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
toolGroupId: toolGroupId,
orientation: viewportOptions.orientation,
viewportId: viewportOptions.viewportId,
+ presentationIds: viewportOptions.presentationIds,
}}
onElementEnabled={evt => {
props.onElementEnabled?.(evt);
@@ -185,6 +186,19 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
setRtIsLoading(false);
}
+ if (rtDisplaySet?.firstSegmentedSliceImageId && viewportOptions?.presentationIds) {
+ const { firstSegmentedSliceImageId } = rtDisplaySet;
+ const { presentationIds } = viewportOptions;
+
+ setPositionPresentation(presentationIds.positionPresentationId, {
+ viewportType: 'stack',
+ viewReference: {
+ referencedImageId: firstSegmentedSliceImageId,
+ },
+ viewPresentation: {},
+ });
+ }
+
if (evt.overlappingSegments) {
uiNotificationService.show({
title: 'Overlapping Segments',
@@ -247,8 +261,6 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
toolGroup = createRTToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId);
- setToolGroupCreated(true);
-
return () => {
// remove the segmentation representations if seg displayset changed
segmentationService.removeSegmentationRepresentations(viewportId);
diff --git a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx
index 21dff244fb9..340afad1662 100644
--- a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx
+++ b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx
@@ -4,6 +4,7 @@ import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionArrows } f
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
import promptHydrateSEG from '../utils/promptHydrateSEG';
import _getStatusComponent from './_getStatusComponent';
+import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup';
@@ -41,6 +42,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
// States
const [selectedSegment, setSelectedSegment] = useState(1);
+ const { setPositionPresentation } = usePositionPresentationStore();
// Hydration means that the SEG is opened and segments are loaded into the
// segmentation panel, and SEG is also rendered on any viewport that is in the
@@ -198,6 +200,17 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
if (evt.segDisplaySet.displaySetInstanceUID === segDisplaySet.displaySetInstanceUID) {
setSegIsLoading(false);
}
+
+ if (segDisplaySet?.firstSegmentedSliceImageId && viewportOptions?.presentationIds) {
+ const { firstSegmentedSliceImageId } = segDisplaySet;
+ const { presentationIds } = viewportOptions;
+
+ setPositionPresentation(presentationIds.positionPresentationId, {
+ viewReference: {
+ referencedImageId: firstSegmentedSliceImageId,
+ },
+ });
+ }
}
);
diff --git a/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx
index 7c63f346c7c..592ee486324 100644
--- a/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx
+++ b/extensions/cornerstone-dicom-sr/src/components/OHIFCornerstoneSRMeasurementViewport.tsx
@@ -9,24 +9,20 @@ import { Icon, Tooltip, useViewportGrid, ViewportActionArrows } from '@ohif/ui';
import hydrateStructuredReport from '../utils/hydrateStructuredReport';
import { useAppConfig } from '@state';
import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet';
+import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking';
const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup';
function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
- const {
- commandsManager,
- children,
- dataSource,
- displaySets,
- viewportOptions,
- servicesManager,
- extensionManager,
- } = props;
+ const { children, dataSource, displaySets, viewportOptions, servicesManager, extensionManager } =
+ props;
const [appConfig] = useAppConfig();
+ const { setPositionPresentation } = usePositionPresentationStore();
+
const {
displaySetService,
cornerstoneViewportService,
@@ -157,30 +153,13 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
setActiveImageDisplaySetData(referencedDisplaySet);
setReferencedDisplaySetMetadata(referencedDisplaySetMetadata);
- if (
- referencedDisplaySet.displaySetInstanceUID ===
- activeImageDisplaySetData?.displaySetInstanceUID
- ) {
- const { measurements } = srDisplaySet;
-
- // it means that we have a new referenced display set, and the
- // imageIdIndex will handle it by updating the viewport, but if they
- // are the same we just need to use measurementService to jump to the
- // new measurement
- const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
-
- if (!csViewport) {
- return;
- }
-
- const imageIds = csViewport.getImageIds();
-
- const imageIdIndex = imageIds.indexOf(measurements[newMeasurementSelected].imageId);
-
- if (imageIdIndex !== -1) {
- csViewport.setImageIdIndex(imageIdIndex);
- }
- }
+ const { presentationIds } = viewportOptions;
+ const measurement = srDisplaySet.measurements[newMeasurementSelected];
+ setPositionPresentation(presentationIds.positionPresentationId, {
+ viewReference: {
+ referencedImageId: measurement.imageId,
+ },
+ });
});
},
[dataSource, srDisplaySet, activeImageDisplaySetData, viewportId]
@@ -202,10 +181,6 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
return null;
}
- const initialImageIndex = activeImageDisplaySetData.images.findIndex(
- image => image.imageId === measurement.imageId
- );
-
return (
);
diff --git a/extensions/cornerstone-dicom-sr/src/utils/formatContentItem.ts b/extensions/cornerstone-dicom-sr/src/utils/formatContentItem.ts
index 1446077552a..064a8a2cc78 100644
--- a/extensions/cornerstone-dicom-sr/src/utils/formatContentItem.ts
+++ b/extensions/cornerstone-dicom-sr/src/utils/formatContentItem.ts
@@ -25,7 +25,7 @@ const contentItemFormatters = {
return `${NumericValue} ${CodeValue}`;
},
PNAME: contentItem => {
- const personName = contentItem.PersonName?.[0]?.Alphabetic;
+ const personName = contentItem.PersonName?.[0];
return personName ? utils.formatPN(personName) : undefined;
},
DATE: contentItem => {
diff --git a/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts b/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts
index 78df25a66b6..7e901804bc9 100644
--- a/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts
+++ b/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts
@@ -262,7 +262,6 @@ function _getInstanceFromSegmentations(segmentations, { servicesManager }) {
}
function updateSegmentationsChartDisplaySet({ servicesManager }: withAppTypes): void {
- debugger;
const { segmentationService } = servicesManager.services;
const segmentations = segmentationService.getSegmentations();
const { seriesMetadata, instance } =
diff --git a/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx b/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx
index b459c69cddd..769494891c7 100644
--- a/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx
+++ b/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx
@@ -5,12 +5,14 @@ import { metaData, Enums, utilities } from '@cornerstonejs/core';
import type { ImageSliceData } from '@cornerstonejs/core/types';
import { ViewportOverlay } from '@ohif/ui';
import type { InstanceMetadata } from '@ohif/core/src/types';
-import { formatPN, formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
+import { formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
+import { utils } from '@ohif/core';
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
import './CustomizableViewportOverlay.css';
const EPSILON = 1e-4;
+const { formatPN } = utils;
type ViewportData = StackViewportData | VolumeViewportData;
diff --git a/extensions/cornerstone/src/Viewport/Overlays/utils.ts b/extensions/cornerstone/src/Viewport/Overlays/utils.ts
index d8795f3b3b5..cd90a095eb8 100644
--- a/extensions/cornerstone/src/Viewport/Overlays/utils.ts
+++ b/extensions/cornerstone/src/Viewport/Overlays/utils.ts
@@ -52,31 +52,6 @@ export function formatDICOMTime(time, strFormat = 'HH:mm:ss') {
return moment(time, 'HH:mm:ss').format(strFormat);
}
-/**
- * Formats a patient name for display purposes
- *
- * @param {string} name
- * @returns {string} formatted name.
- */
-export function formatPN(name) {
- if (!name) {
- return '';
- }
- if (typeof name === 'object') {
- name = name.Alphabetic;
- if (!name) {
- return '';
- }
- }
-
- const cleaned = name
- .split('^')
- .filter(s => !!s)
- .join(', ')
- .trim();
- return cleaned === ',' || cleaned === '' ? '' : cleaned;
-}
-
/**
* Gets compression type
*
diff --git a/extensions/cornerstone/src/hps/frameView.ts b/extensions/cornerstone/src/hps/frameView.ts
index cf80aa2a168..7990cd0599b 100644
--- a/extensions/cornerstone/src/hps/frameView.ts
+++ b/extensions/cornerstone/src/hps/frameView.ts
@@ -20,7 +20,7 @@ const frameView: Types.HangingProtocol.Protocol = {
},
{
attribute: 'isDisplaySetFromUrl',
- weight: 10,
+ weight: 20,
constraint: {
equals: true,
},
diff --git a/extensions/cornerstone/src/panels/PanelSegmentation.tsx b/extensions/cornerstone/src/panels/PanelSegmentation.tsx
index 03109fc5c73..83bbc8bfb0a 100644
--- a/extensions/cornerstone/src/panels/PanelSegmentation.tsx
+++ b/extensions/cornerstone/src/panels/PanelSegmentation.tsx
@@ -162,6 +162,14 @@ export default function PanelSegmentation({
const firstImageId = referencedImageIds[0];
const instance = metaData.get('instance', firstImageId);
+
+ if (!instance) {
+ return {
+ segmentationId,
+ isExportable: false,
+ };
+ }
+
const { SOPInstanceUID, SeriesInstanceUID } = instance;
const displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
diff --git a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts
index 45b1dbd1691..c298591d695 100644
--- a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts
+++ b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts
@@ -478,14 +478,24 @@ class SegmentationService extends PubSubService {
// We should parse the segmentation as separate slices to support overlapping segments.
// This parsing should occur in the CornerstoneJS library adapters.
// For now, we use the volume returned from the library and chop it here.
+ let firstSegmentedSliceImageId = null;
for (let i = 0; i < derivedSegmentationImages.length; i++) {
const voxelManager = derivedSegmentationImages[i]
.voxelManager as csTypes.IVoxelManager;
const scalarData = voxelManager.getScalarData();
- scalarData.set(volumeScalarData.slice(i * scalarData.length, (i + 1) * scalarData.length));
+ const sliceData = volumeScalarData.slice(i * scalarData.length, (i + 1) * scalarData.length);
+ scalarData.set(sliceData);
voxelManager.setScalarData(scalarData);
+
+ // Check if this slice has any non-zero voxels and we haven't found one yet
+ if (!firstSegmentedSliceImageId && sliceData.some(value => value !== 0)) {
+ firstSegmentedSliceImageId = derivedSegmentationImages[i].referencedImageId;
+ }
}
+ // assign the first non zero voxel image id to the segDisplaySet
+ segDisplaySet.firstSegmentedSliceImageId = firstSegmentedSliceImageId;
+
this._broadcastEvent(EVENTS.SEGMENTATION_LOADING_COMPLETE, {
segmentationId,
segDisplaySet,
@@ -542,7 +552,19 @@ class SegmentationService extends PubSubService {
}
const rtDisplaySetUID = rtDisplaySet.displaySetInstanceUID;
+ const referencedDisplaySet = this.servicesManager.services.displaySetService.getDisplaySetByUID(
+ rtDisplaySet.referencedDisplaySetInstanceUID
+ );
+
+ const referencedImageIdsWithGeometry = Array.from(structureSet.ReferencedSOPInstanceUIDsSet);
+
+ const referencedImageIds = referencedDisplaySet.instances.map(image => image.imageId);
+ // find the first image id that contains a referenced SOP instance UID
+ const firstSegmentedSliceImageId = referencedImageIds.find(imageId =>
+ referencedImageIdsWithGeometry.some(referencedId => imageId.includes(referencedId))
+ );
+ rtDisplaySet.firstSegmentedSliceImageId = firstSegmentedSliceImageId;
// Map ROI contours to RT Struct Data
const allRTStructData = mapROIContoursToRTStructData(structureSet, rtDisplaySetUID);
diff --git a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts
index c07a4de01e2..56d338c48d8 100644
--- a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts
+++ b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts
@@ -605,10 +605,6 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
let initialImageIndexToUse =
presentations?.positionPresentation?.initialImageIndex ?? initialImageIndex;
- if (initialImageIndexToUse === undefined || initialImageIndexToUse === null) {
- initialImageIndexToUse = this._getInitialImageIndexForViewport(viewportInfo, imageIds) || 0;
- }
-
const { rotation, flipHorizontal, displayArea } = viewportInfo.getViewportOptions();
const properties = { ...presentations.lutPresentation?.properties };
@@ -637,12 +633,26 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
});
let imageIdsToSet = imageIds;
- const res = this._processExtraDisplaySetsForViewport(viewport);
- imageIdsToSet = res?.imageIds ?? imageIdsToSet;
+ const overlayProcessingResult = this._processExtraDisplaySetsForViewport(viewport);
+ imageIdsToSet = overlayProcessingResult?.imageIds ?? imageIdsToSet;
+
+ const referencedImageId = presentations?.positionPresentation?.viewReference?.referencedImageId;
+ if (referencedImageId) {
+ initialImageIndexToUse = imageIdsToSet.indexOf(referencedImageId);
+ }
+
+ if (initialImageIndexToUse === undefined || initialImageIndexToUse === null) {
+ initialImageIndexToUse = this._getInitialImageIndexForViewport(viewportInfo, imageIds) || 0;
+ }
return viewport.setStack(imageIdsToSet, initialImageIndexToUse).then(() => {
viewport.setProperties({ ...properties });
this.setPresentations(viewport.id, presentations, viewportInfo);
+
+ if (overlayProcessingResult?.addOverlayFn) {
+ overlayProcessingResult.addOverlayFn();
+ }
+
if (displayArea) {
viewport.setDisplayArea(displayArea);
}
@@ -827,10 +837,14 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
});
// For SEG and RT viewports
- this._processExtraDisplaySetsForViewport(viewport);
+ const { addOverlayFn } = this._processExtraDisplaySetsForViewport(viewport) || {};
await viewport.setVolumes(volumeInputArray);
+ if (addOverlayFn) {
+ addOverlayFn();
+ }
+
volumesProperties.forEach(({ properties, volumeId }) => {
viewport.setProperties(properties, volumeId);
});
@@ -880,8 +894,12 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
segOrRTSOverlayDisplaySet.referencedDisplaySetInstanceUID
);
const imageIds = referenceDisplaySet.images.map(image => image.imageId);
- this.addOverlayRepresentationForDisplaySet(segOrRTSOverlayDisplaySet, viewport);
- return { imageIds };
+
+ return {
+ imageIds,
+ addOverlayFn: () =>
+ this.addOverlayRepresentationForDisplaySet(segOrRTSOverlayDisplaySet, viewport),
+ };
}
private addOverlayRepresentationForDisplaySet(
diff --git a/extensions/cornerstone/src/stores/usePositionPresentationStore.ts b/extensions/cornerstone/src/stores/usePositionPresentationStore.ts
index 1b06bd8e52d..ac6d94c9f84 100644
--- a/extensions/cornerstone/src/stores/usePositionPresentationStore.ts
+++ b/extensions/cornerstone/src/stores/usePositionPresentationStore.ts
@@ -4,7 +4,7 @@ import { PositionPresentation } from '../types/Presentation';
import { addUniqueIndex, JOIN_STR } from './presentationUtils';
const PRESENTATION_TYPE_ID = 'positionPresentationId';
-const DEBUG_STORE = true;
+const DEBUG_STORE = false;
/**
* Represents the state and actions for managing position presentations.
diff --git a/extensions/default/src/DicomLocalDataSource/index.js b/extensions/default/src/DicomLocalDataSource/index.js
index a0ea9fb4ffa..57badc06c68 100644
--- a/extensions/default/src/DicomLocalDataSource/index.js
+++ b/extensions/default/src/DicomLocalDataSource/index.js
@@ -142,7 +142,9 @@ function createDicomLocalApi(dicomLocalConfig) {
study.series.forEach(aSeries => {
const { SeriesInstanceUID } = aSeries;
- aSeries.instances.forEach(instance => {
+ const isMultiframe = aSeries.instances[0].NumberOfFrames > 1;
+
+ aSeries.instances.forEach((instance, index) => {
const {
url: imageId,
StudyInstanceUID,
@@ -151,22 +153,14 @@ function createDicomLocalApi(dicomLocalConfig) {
} = instance;
instance.imageId = imageId;
- const numberOfFrames = instance.NumberOfFrames || 1;
- // Process all frames consistently, whether single or multiframe
- for (let i = 0; i < numberOfFrames; i++) {
- const frameNumber = i + 1;
- const frameImageId = implementation.getImageIdsForInstance({
- instance,
- frame: frameNumber,
- });
- // Add imageId specific mapping to this data as the URL isn't necessarily WADO-URI.
- metadataProvider.addImageIdToUIDs(frameImageId, {
- StudyInstanceUID,
- SeriesInstanceUID,
- SOPInstanceUID,
- frameNumber: numberOfFrames > 1 ? frameNumber : undefined,
- });
- }
+
+ // Add imageId specific mapping to this data as the URL isn't necessarily WADO-URI.
+ metadataProvider.addImageIdToUIDs(imageId, {
+ StudyInstanceUID,
+ SeriesInstanceUID,
+ SOPInstanceUID,
+ frameIndex: isMultiframe ? index : 1,
+ });
});
DicomMetadataStore._broadcastEvent(EVENTS.INSTANCES_ADDED, {
diff --git a/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx b/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx
index 80480f18114..f3c2e247398 100644
--- a/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx
+++ b/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx
@@ -30,6 +30,7 @@ function WrappedPanelStudyBrowser({ commandsManager, extensionManager, servicesM
return (
= new Map();
- private readonly imageUIDsByImageId: Map = new Map();
// Can be used to store custom metadata for a specific type.
// For instance, the scaling metadata for PET can be stored here
// as type "scalingModule"
@@ -25,7 +25,6 @@ class MetadataProvider {
// An example would be dicom hosted at some random site.
const imageURI = imageIdToURI(imageId);
this.imageURIToUIDs.set(imageURI, uids);
- this.imageUIDsByImageId.set(imageId, uids);
}
addCustomMetadata(imageId, type, metadata) {
@@ -347,7 +346,7 @@ class MetadataProvider {
let patientName;
if (PatientName) {
- patientName = PatientName.Alphabetic;
+ patientName = formatPN(PatientName);
}
metadata = {
@@ -462,11 +461,6 @@ class MetadataProvider {
}
getUIDsFromImageID(imageId) {
- const cachedUIDs = this.imageUIDsByImageId.get(imageId);
- if (cachedUIDs) {
- return cachedUIDs;
- }
-
if (imageId.startsWith('wadors:')) {
const strippedImageId = imageId.split('/studies/')[1];
const splitImageId = strippedImageId.split('/');
diff --git a/platform/core/src/utils/combineFrameInstance.ts b/platform/core/src/utils/combineFrameInstance.ts
index cf70232cf5d..4202c5b0fd7 100644
--- a/platform/core/src/utils/combineFrameInstance.ts
+++ b/platform/core/src/utils/combineFrameInstance.ts
@@ -1,4 +1,5 @@
import { vec3 } from 'gl-matrix';
+import { dicomSplit } from './dicomSplit';
/**
* Combine the Per instance frame data, the shared frame data
@@ -15,24 +16,13 @@ const combineFrameInstance = (frame, instance) => {
PerFrameFunctionalGroupsSequence,
SharedFunctionalGroupsSequence,
NumberOfFrames,
- SpacingBetweenSlices,
+ ImageType,
} = instance;
+ instance.ImageType = dicomSplit(ImageType);
+
if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) {
const frameNumber = Number.parseInt(frame || 1);
- const shared = SharedFunctionalGroupsSequence
- ? Object.values(SharedFunctionalGroupsSequence[0])
- .filter(Boolean)
- .map(it => it[0])
- .filter(it => typeof it === 'object')
- : [];
-
- const perFrame = PerFrameFunctionalGroupsSequence
- ? Object.values(PerFrameFunctionalGroupsSequence[frameNumber - 1])
- .filter(Boolean)
- .map(it => it[0])
- .filter(it => typeof it === 'object')
- : [];
// this is to fix NM multiframe datasets with position and orientation
// information inside DetectorInformationSequence
@@ -44,8 +34,12 @@ const combineFrameInstance = (frame, instance) => {
let ImagePositionPatientToUse = instance.ImagePositionPatient;
if (!instance.ImagePositionPatient && instance.DetectorInformationSequence) {
- const imagePositionPatient = instance.DetectorInformationSequence[0].ImagePositionPatient;
- const imageOrientationPatient = instance.ImageOrientationPatient;
+ let imagePositionPatient = instance.DetectorInformationSequence[0].ImagePositionPatient;
+ let imageOrientationPatient = instance.ImageOrientationPatient;
+
+ imagePositionPatient = imagePositionPatient.map(it => Number(it));
+ imageOrientationPatient = imageOrientationPatient.map(it => Number(it));
+ const SpacingBetweenSlices = Number(instance.SpacingBetweenSlices);
// Calculate the position for the current frame
if (imageOrientationPatient && SpacingBetweenSlices) {
@@ -73,27 +67,77 @@ const combineFrameInstance = (frame, instance) => {
ImagePositionPatientToUse = [position[0], position[1], position[2]];
}
}
- console.debug('🚀 ~ ImagePositionPatientToUse:', ImagePositionPatientToUse);
- const newInstance = Object.assign(instance, { frameNumber: frameNumber });
-
- // merge the shared first then the per frame to override
- [...shared, ...perFrame].forEach(item => {
- Object.entries(item).forEach(([key, value]) => {
- newInstance[key] = value;
+ // Cache the _parentInstance at the top level as a full copy to prevent
+ // setting values hard.
+ if (!instance._parentInstance) {
+ Object.defineProperty(instance, '_parentInstance', {
+ value: { ...instance },
});
- });
+ }
+ const sharedInstance = createCombinedValue(
+ instance._parentInstance,
+ SharedFunctionalGroupsSequence?.[0],
+ '_shared'
+ );
+ const newInstance = createCombinedValue(
+ sharedInstance,
+ PerFrameFunctionalGroupsSequence?.[frameNumber - 1],
+ frameNumber
+ );
+
+ newInstance.ImagePositionPatient = ImagePositionPatientToUse ??
+ newInstance.ImagePositionPatient ?? [0, 0, frameNumber];
- // Todo: we should cache this combined instance somewhere, maybe add it
- // back to the dicomMetaStore so we don't have to do this again.
- return {
- ...newInstance,
- ImagePositionPatient: ImagePositionPatientToUse ??
- newInstance.ImagePositionPatient ?? [0, 0, frameNumber],
- };
+ Object.defineProperty(newInstance, 'frameNumber', {
+ value: frameNumber,
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ });
+ return newInstance;
} else {
return instance;
}
};
+/**
+ * Creates a combined instance stored in the parent object which
+ * inherits from the parent instance the attributes in the functional groups.
+ * The storage key in the parent is in key
+ */
+function createCombinedValue(parent, functionalGroups, key) {
+ if (parent[key]) {
+ return parent[key];
+ }
+ // Exclude any proxying values
+ const newInstance = Object.create(parent);
+ Object.defineProperty(parent, key, {
+ value: newInstance,
+ writable: false,
+ enumerable: false,
+ });
+ if (!functionalGroups) {
+ return newInstance;
+ }
+ const shared = functionalGroups
+ ? Object.values(functionalGroups)
+ .filter(Boolean)
+ .map(it => it[0])
+ .filter(it => typeof it === 'object')
+ : [];
+
+ // merge the shared first then the per frame to override
+ [...shared].forEach(item => {
+ if (item.SOPInstanceUID) {
+ // This sub-item is a previous value information item, so don't merge it
+ return;
+ }
+ Object.entries(item).forEach(([key, value]) => {
+ newInstance[key] = value;
+ });
+ });
+ return newInstance;
+}
+
export default combineFrameInstance;
diff --git a/platform/core/src/utils/dicomSplit.ts b/platform/core/src/utils/dicomSplit.ts
new file mode 100644
index 00000000000..54b20d38f0d
--- /dev/null
+++ b/platform/core/src/utils/dicomSplit.ts
@@ -0,0 +1,5 @@
+export function dicomSplit(value) {
+ return (
+ (Array.isArray(value) && value) || (typeof value === 'string' && value.split('\\')) || value
+ );
+}
diff --git a/platform/core/src/utils/downloadCSVReport.js b/platform/core/src/utils/downloadCSVReport.js
index 4185a791986..70adf9f3b21 100644
--- a/platform/core/src/utils/downloadCSVReport.js
+++ b/platform/core/src/utils/downloadCSVReport.js
@@ -1,4 +1,5 @@
import { DicomMetadataStore } from '../services/DicomMetadataStore/DicomMetadataStore';
+import formatPN from './formatPN';
export default function downloadCSVReport(measurementData) {
if (measurementData.length === 0) {
@@ -86,7 +87,7 @@ function _getCommonRowItems(measurement, seriesMetadata) {
return {
'Patient ID': firstInstance.PatientID, // Patient ID
- 'Patient Name': firstInstance.PatientName?.Alphabetic || '', // Patient Name
+ 'Patient Name': formatPN(firstInstance.PatientName) || '', // Patient Name
StudyInstanceUID: measurement.referenceStudyUID, // StudyInstanceUID
SeriesInstanceUID: measurement.referenceSeriesUID, // SeriesInstanceUID
SOPInstanceUID: measurement.SOPInstanceUID, // SOPInstanceUID
diff --git a/platform/ui/src/components/HeaderPatientInfo/HeaderPatientInfo.tsx b/platform/ui/src/components/HeaderPatientInfo/HeaderPatientInfo.tsx
index 7e7de4d2383..e62f07793ab 100644
--- a/platform/ui/src/components/HeaderPatientInfo/HeaderPatientInfo.tsx
+++ b/platform/ui/src/components/HeaderPatientInfo/HeaderPatientInfo.tsx
@@ -48,7 +48,7 @@ function usePatientInfo(servicesManager: AppTypes.ServicesManager) {
}
setPatientInfo({
PatientID: instance.PatientID || null,
- PatientName: instance.PatientName ? formatPN(instance.PatientName.Alphabetic) : null,
+ PatientName: instance.PatientName ? formatPN(instance.PatientName) : null,
PatientSex: instance.PatientSex || null,
PatientDOB: formatDate(instance.PatientBirthDate) || null,
});
diff --git a/version.txt b/version.txt
index 4764627f925..03d70eaebc4 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-3.9.2
\ No newline at end of file
+3.9.3
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 737d8ac837a..85a2a60ae2f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12594,13 +12594,6 @@ fast-uri@^3.0.1:
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.2.tgz#d78b298cf70fd3b752fd951175a3da6a7b48f024"
integrity sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==
-fast-url-parser@1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d"
- integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==
- dependencies:
- punycode "^1.3.2"
-
fastest-levenshtein@^1.0.12:
version "1.0.16"
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
@@ -19130,7 +19123,7 @@ path-to-regexp@1.9.0, path-to-regexp@^1.7.0:
dependencies:
isarray "0.0.1"
-path-to-regexp@2.2.1, path-to-regexp@3.3.0:
+path-to-regexp@3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b"
integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==
@@ -20438,7 +20431,7 @@ pumpify@^1.3.3:
inherits "^2.0.3"
pump "^2.0.0"
-punycode@^1.3.2, punycode@^1.4.1:
+punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
@@ -22034,21 +22027,7 @@ serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
dependencies:
randombytes "^2.1.0"
-serve-handler@6.1.5:
- version "6.1.5"
- resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.5.tgz#a4a0964f5c55c7e37a02a633232b6f0d6f068375"
- integrity sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==
- dependencies:
- bytes "3.0.0"
- content-disposition "0.5.2"
- fast-url-parser "1.1.3"
- mime-types "2.1.18"
- minimatch "3.1.2"
- path-is-inside "1.0.2"
- path-to-regexp "2.2.1"
- range-parser "1.2.0"
-
-serve-handler@^6.1.6:
+serve-handler@6.1.6, serve-handler@^6.1.6:
version "6.1.6"
resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.6.tgz#50803c1d3e947cd4a341d617f8209b22bd76cfa1"
integrity sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==
@@ -22084,10 +22063,10 @@ serve-static@1.16.2:
parseurl "~1.3.3"
send "0.19.0"
-serve@^14.2.0:
- version "14.2.3"
- resolved "https://registry.yarnpkg.com/serve/-/serve-14.2.3.tgz#047ba2b349354255bc09e0332cd41a92787836c9"
- integrity sha512-VqUFMC7K3LDGeGnJM9h56D3XGKb6KGgOw0cVNtA26yYXHCcpxf3xwCTUaQoWlVS7i8Jdh3GjQkOB23qsXyjoyQ==
+serve@^14.2.4:
+ version "14.2.4"
+ resolved "https://registry.yarnpkg.com/serve/-/serve-14.2.4.tgz#ba4c425c3c965f496703762e808f34b913f42fb0"
+ integrity sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==
dependencies:
"@zeit/schemas" "2.36.0"
ajv "8.12.0"
@@ -22098,7 +22077,7 @@ serve@^14.2.0:
clipboardy "3.0.0"
compression "1.7.4"
is-port-reachable "4.0.0"
- serve-handler "6.1.5"
+ serve-handler "6.1.6"
update-check "1.5.4"
set-blocking@^2.0.0: