fix: OHIF 3.9 Recovery 3 (#4697)
Co-authored-by: Ibrahim <93064150+IbrahimCSAE@users.noreply.github.com> Co-authored-by: Bill Wallace <wayfarer3130@gmail.com> Co-authored-by: Chao Sui <chao.sui@curvebeamai.com> Co-authored-by: Jaseel-trenser <108331536+Jaseel-trenser@users.noreply.github.com>
This commit is contained in:
parent
b19d7af9ff
commit
5d4f2e2a8e
@ -68,7 +68,7 @@
|
|||||||
"prettier-plugin-tailwindcss": "^0.5.4",
|
"prettier-plugin-tailwindcss": "^0.5.4",
|
||||||
"react-refresh": "^0.14.2",
|
"react-refresh": "^0.14.2",
|
||||||
"semver": "^7.5.1",
|
"semver": "^7.5.1",
|
||||||
"serve": "^14.2.0",
|
"serve": "^14.2.4",
|
||||||
"shader-loader": "^1.3.1",
|
"shader-loader": "^1.3.1",
|
||||||
"shx": "^0.3.3",
|
"shx": "^0.3.3",
|
||||||
"source-map-loader": "^4.0.1",
|
"source-map-loader": "^4.0.1",
|
||||||
|
|||||||
@ -97,6 +97,7 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) {
|
|||||||
viewportType: 'volume',
|
viewportType: 'volume',
|
||||||
orientation: viewportOptions.orientation,
|
orientation: viewportOptions.orientation,
|
||||||
viewportId: viewportOptions.viewportId,
|
viewportId: viewportOptions.viewportId,
|
||||||
|
presentationIds: viewportOptions.presentationIds,
|
||||||
}}
|
}}
|
||||||
displaySetOptions={[{}, pmapDisplaySetOptions]}
|
displaySetOptions={[{}, pmapDisplaySetOptions]}
|
||||||
></Component>
|
></Component>
|
||||||
|
|||||||
@ -126,6 +126,7 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet,
|
|||||||
SeriesInstanceUID: instance.SeriesInstanceUID,
|
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||||
ROIContours: [],
|
ROIContours: [],
|
||||||
visible: true,
|
visible: true,
|
||||||
|
ReferencedSOPInstanceUIDsSet: new Set(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let i = 0; i < ROIContourSequence.length; i++) {
|
for (let i = 0; i < ROIContourSequence.length; i++) {
|
||||||
@ -142,7 +143,8 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet,
|
|||||||
|
|
||||||
const contourPoints = [];
|
const contourPoints = [];
|
||||||
for (let c = 0; c < ContourSequenceArray.length; c++) {
|
for (let c = 0; c < ContourSequenceArray.length; c++) {
|
||||||
const { ContourData, NumberOfContourPoints, ContourGeometricType } = ContourSequenceArray[c];
|
const { ContourData, NumberOfContourPoints, ContourGeometricType, ContourImageSequence } =
|
||||||
|
ContourSequenceArray[c];
|
||||||
|
|
||||||
let isSupported = false;
|
let isSupported = false;
|
||||||
|
|
||||||
@ -172,6 +174,12 @@ export default async function loadRTStruct(extensionManager, rtStructDisplaySet,
|
|||||||
type: ContourGeometricType,
|
type: ContourGeometricType,
|
||||||
isSupported,
|
isSupported,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (ContourImageSequence?.ReferencedSOPInstanceUID) {
|
||||||
|
structureSet.ReferencedSOPInstanceUIDsSet.add(
|
||||||
|
ContourImageSequence?.ReferencedSOPInstanceUID
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_setROIContourMetadata(
|
_setROIContourMetadata(
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import promptHydrateRT from '../utils/promptHydrateRT';
|
|||||||
import _getStatusComponent from './_getStatusComponent';
|
import _getStatusComponent from './_getStatusComponent';
|
||||||
|
|
||||||
import createRTToolGroupAndAddTools from '../utils/initRTToolGroup';
|
import createRTToolGroupAndAddTools from '../utils/initRTToolGroup';
|
||||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
|
||||||
|
|
||||||
const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup';
|
const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup';
|
||||||
|
|
||||||
@ -43,8 +43,8 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
|||||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||||
|
|
||||||
// States
|
// States
|
||||||
const [isToolGroupCreated, setToolGroupCreated] = useState(false);
|
|
||||||
const [selectedSegment, setSelectedSegment] = useState(1);
|
const [selectedSegment, setSelectedSegment] = useState(1);
|
||||||
|
const { setPositionPresentation } = usePositionPresentationStore();
|
||||||
|
|
||||||
// Hydration means that the RT is opened and segments are loaded into the
|
// 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
|
// segmentation panel, and RT is also rendered on any viewport that is in the
|
||||||
@ -123,6 +123,7 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
|||||||
toolGroupId: toolGroupId,
|
toolGroupId: toolGroupId,
|
||||||
orientation: viewportOptions.orientation,
|
orientation: viewportOptions.orientation,
|
||||||
viewportId: viewportOptions.viewportId,
|
viewportId: viewportOptions.viewportId,
|
||||||
|
presentationIds: viewportOptions.presentationIds,
|
||||||
}}
|
}}
|
||||||
onElementEnabled={evt => {
|
onElementEnabled={evt => {
|
||||||
props.onElementEnabled?.(evt);
|
props.onElementEnabled?.(evt);
|
||||||
@ -185,6 +186,19 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
|||||||
setRtIsLoading(false);
|
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) {
|
if (evt.overlappingSegments) {
|
||||||
uiNotificationService.show({
|
uiNotificationService.show({
|
||||||
title: 'Overlapping Segments',
|
title: 'Overlapping Segments',
|
||||||
@ -247,8 +261,6 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
|||||||
|
|
||||||
toolGroup = createRTToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId);
|
toolGroup = createRTToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId);
|
||||||
|
|
||||||
setToolGroupCreated(true);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// remove the segmentation representations if seg displayset changed
|
// remove the segmentation representations if seg displayset changed
|
||||||
segmentationService.removeSegmentationRepresentations(viewportId);
|
segmentationService.removeSegmentationRepresentations(viewportId);
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionArrows } f
|
|||||||
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
|
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
|
||||||
import promptHydrateSEG from '../utils/promptHydrateSEG';
|
import promptHydrateSEG from '../utils/promptHydrateSEG';
|
||||||
import _getStatusComponent from './_getStatusComponent';
|
import _getStatusComponent from './_getStatusComponent';
|
||||||
|
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
|
||||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||||
|
|
||||||
const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup';
|
const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup';
|
||||||
@ -41,6 +42,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
|||||||
|
|
||||||
// States
|
// States
|
||||||
const [selectedSegment, setSelectedSegment] = useState(1);
|
const [selectedSegment, setSelectedSegment] = useState(1);
|
||||||
|
const { setPositionPresentation } = usePositionPresentationStore();
|
||||||
|
|
||||||
// Hydration means that the SEG is opened and segments are loaded into the
|
// 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
|
// 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) {
|
if (evt.segDisplaySet.displaySetInstanceUID === segDisplaySet.displaySetInstanceUID) {
|
||||||
setSegIsLoading(false);
|
setSegIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (segDisplaySet?.firstSegmentedSliceImageId && viewportOptions?.presentationIds) {
|
||||||
|
const { firstSegmentedSliceImageId } = segDisplaySet;
|
||||||
|
const { presentationIds } = viewportOptions;
|
||||||
|
|
||||||
|
setPositionPresentation(presentationIds.positionPresentationId, {
|
||||||
|
viewReference: {
|
||||||
|
referencedImageId: firstSegmentedSliceImageId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -9,24 +9,20 @@ import { Icon, Tooltip, useViewportGrid, ViewportActionArrows } from '@ohif/ui';
|
|||||||
import hydrateStructuredReport from '../utils/hydrateStructuredReport';
|
import hydrateStructuredReport from '../utils/hydrateStructuredReport';
|
||||||
import { useAppConfig } from '@state';
|
import { useAppConfig } from '@state';
|
||||||
import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet';
|
import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet';
|
||||||
|
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
|
||||||
|
|
||||||
const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking';
|
const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking';
|
||||||
|
|
||||||
const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup';
|
const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup';
|
||||||
|
|
||||||
function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
||||||
const {
|
const { children, dataSource, displaySets, viewportOptions, servicesManager, extensionManager } =
|
||||||
commandsManager,
|
props;
|
||||||
children,
|
|
||||||
dataSource,
|
|
||||||
displaySets,
|
|
||||||
viewportOptions,
|
|
||||||
servicesManager,
|
|
||||||
extensionManager,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [appConfig] = useAppConfig();
|
const [appConfig] = useAppConfig();
|
||||||
|
|
||||||
|
const { setPositionPresentation } = usePositionPresentationStore();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
displaySetService,
|
displaySetService,
|
||||||
cornerstoneViewportService,
|
cornerstoneViewportService,
|
||||||
@ -157,30 +153,13 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
|||||||
setActiveImageDisplaySetData(referencedDisplaySet);
|
setActiveImageDisplaySetData(referencedDisplaySet);
|
||||||
setReferencedDisplaySetMetadata(referencedDisplaySetMetadata);
|
setReferencedDisplaySetMetadata(referencedDisplaySetMetadata);
|
||||||
|
|
||||||
if (
|
const { presentationIds } = viewportOptions;
|
||||||
referencedDisplaySet.displaySetInstanceUID ===
|
const measurement = srDisplaySet.measurements[newMeasurementSelected];
|
||||||
activeImageDisplaySetData?.displaySetInstanceUID
|
setPositionPresentation(presentationIds.positionPresentationId, {
|
||||||
) {
|
viewReference: {
|
||||||
const { measurements } = srDisplaySet;
|
referencedImageId: measurement.imageId,
|
||||||
|
},
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[dataSource, srDisplaySet, activeImageDisplaySetData, viewportId]
|
[dataSource, srDisplaySet, activeImageDisplaySetData, viewportId]
|
||||||
@ -202,10 +181,6 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialImageIndex = activeImageDisplaySetData.images.findIndex(
|
|
||||||
image => image.imageId === measurement.imageId
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
{...props}
|
{...props}
|
||||||
@ -230,7 +205,6 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
|||||||
props.onElementEnabled?.(evt);
|
props.onElementEnabled?.(evt);
|
||||||
onElementEnabled(evt);
|
onElementEnabled(evt);
|
||||||
}}
|
}}
|
||||||
initialImageIndex={initialImageIndex}
|
|
||||||
isJumpToMeasurementDisabled={true}
|
isJumpToMeasurementDisabled={true}
|
||||||
></Component>
|
></Component>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -25,7 +25,7 @@ const contentItemFormatters = {
|
|||||||
return `${NumericValue} ${CodeValue}`;
|
return `${NumericValue} ${CodeValue}`;
|
||||||
},
|
},
|
||||||
PNAME: contentItem => {
|
PNAME: contentItem => {
|
||||||
const personName = contentItem.PersonName?.[0]?.Alphabetic;
|
const personName = contentItem.PersonName?.[0];
|
||||||
return personName ? utils.formatPN(personName) : undefined;
|
return personName ? utils.formatPN(personName) : undefined;
|
||||||
},
|
},
|
||||||
DATE: contentItem => {
|
DATE: contentItem => {
|
||||||
|
|||||||
@ -262,7 +262,6 @@ function _getInstanceFromSegmentations(segmentations, { servicesManager }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateSegmentationsChartDisplaySet({ servicesManager }: withAppTypes): void {
|
function updateSegmentationsChartDisplaySet({ servicesManager }: withAppTypes): void {
|
||||||
debugger;
|
|
||||||
const { segmentationService } = servicesManager.services;
|
const { segmentationService } = servicesManager.services;
|
||||||
const segmentations = segmentationService.getSegmentations();
|
const segmentations = segmentationService.getSegmentations();
|
||||||
const { seriesMetadata, instance } =
|
const { seriesMetadata, instance } =
|
||||||
|
|||||||
@ -5,12 +5,14 @@ import { metaData, Enums, utilities } from '@cornerstonejs/core';
|
|||||||
import type { ImageSliceData } from '@cornerstonejs/core/types';
|
import type { ImageSliceData } from '@cornerstonejs/core/types';
|
||||||
import { ViewportOverlay } from '@ohif/ui';
|
import { ViewportOverlay } from '@ohif/ui';
|
||||||
import type { InstanceMetadata } from '@ohif/core/src/types';
|
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 { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||||
|
|
||||||
import './CustomizableViewportOverlay.css';
|
import './CustomizableViewportOverlay.css';
|
||||||
|
|
||||||
const EPSILON = 1e-4;
|
const EPSILON = 1e-4;
|
||||||
|
const { formatPN } = utils;
|
||||||
|
|
||||||
type ViewportData = StackViewportData | VolumeViewportData;
|
type ViewportData = StackViewportData | VolumeViewportData;
|
||||||
|
|
||||||
|
|||||||
@ -52,31 +52,6 @@ export function formatDICOMTime(time, strFormat = 'HH:mm:ss') {
|
|||||||
return moment(time, 'HH:mm:ss').format(strFormat);
|
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
|
* Gets compression type
|
||||||
*
|
*
|
||||||
|
|||||||
@ -20,7 +20,7 @@ const frameView: Types.HangingProtocol.Protocol = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
attribute: 'isDisplaySetFromUrl',
|
attribute: 'isDisplaySetFromUrl',
|
||||||
weight: 10,
|
weight: 20,
|
||||||
constraint: {
|
constraint: {
|
||||||
equals: true,
|
equals: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -162,6 +162,14 @@ export default function PanelSegmentation({
|
|||||||
const firstImageId = referencedImageIds[0];
|
const firstImageId = referencedImageIds[0];
|
||||||
|
|
||||||
const instance = metaData.get('instance', firstImageId);
|
const instance = metaData.get('instance', firstImageId);
|
||||||
|
|
||||||
|
if (!instance) {
|
||||||
|
return {
|
||||||
|
segmentationId,
|
||||||
|
isExportable: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const { SOPInstanceUID, SeriesInstanceUID } = instance;
|
const { SOPInstanceUID, SeriesInstanceUID } = instance;
|
||||||
|
|
||||||
const displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
|
const displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
|
||||||
|
|||||||
@ -478,14 +478,24 @@ class SegmentationService extends PubSubService {
|
|||||||
// We should parse the segmentation as separate slices to support overlapping segments.
|
// We should parse the segmentation as separate slices to support overlapping segments.
|
||||||
// This parsing should occur in the CornerstoneJS library adapters.
|
// This parsing should occur in the CornerstoneJS library adapters.
|
||||||
// For now, we use the volume returned from the library and chop it here.
|
// 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++) {
|
for (let i = 0; i < derivedSegmentationImages.length; i++) {
|
||||||
const voxelManager = derivedSegmentationImages[i]
|
const voxelManager = derivedSegmentationImages[i]
|
||||||
.voxelManager as csTypes.IVoxelManager<number>;
|
.voxelManager as csTypes.IVoxelManager<number>;
|
||||||
const scalarData = voxelManager.getScalarData();
|
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);
|
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, {
|
this._broadcastEvent(EVENTS.SEGMENTATION_LOADING_COMPLETE, {
|
||||||
segmentationId,
|
segmentationId,
|
||||||
segDisplaySet,
|
segDisplaySet,
|
||||||
@ -542,7 +552,19 @@ class SegmentationService extends PubSubService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rtDisplaySetUID = rtDisplaySet.displaySetInstanceUID;
|
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
|
// Map ROI contours to RT Struct Data
|
||||||
const allRTStructData = mapROIContoursToRTStructData(structureSet, rtDisplaySetUID);
|
const allRTStructData = mapROIContoursToRTStructData(structureSet, rtDisplaySetUID);
|
||||||
|
|
||||||
|
|||||||
@ -605,10 +605,6 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
|||||||
let initialImageIndexToUse =
|
let initialImageIndexToUse =
|
||||||
presentations?.positionPresentation?.initialImageIndex ?? initialImageIndex;
|
presentations?.positionPresentation?.initialImageIndex ?? initialImageIndex;
|
||||||
|
|
||||||
if (initialImageIndexToUse === undefined || initialImageIndexToUse === null) {
|
|
||||||
initialImageIndexToUse = this._getInitialImageIndexForViewport(viewportInfo, imageIds) || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { rotation, flipHorizontal, displayArea } = viewportInfo.getViewportOptions();
|
const { rotation, flipHorizontal, displayArea } = viewportInfo.getViewportOptions();
|
||||||
|
|
||||||
const properties = { ...presentations.lutPresentation?.properties };
|
const properties = { ...presentations.lutPresentation?.properties };
|
||||||
@ -637,12 +633,26 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
|||||||
});
|
});
|
||||||
|
|
||||||
let imageIdsToSet = imageIds;
|
let imageIdsToSet = imageIds;
|
||||||
const res = this._processExtraDisplaySetsForViewport(viewport);
|
const overlayProcessingResult = this._processExtraDisplaySetsForViewport(viewport);
|
||||||
imageIdsToSet = res?.imageIds ?? imageIdsToSet;
|
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(() => {
|
return viewport.setStack(imageIdsToSet, initialImageIndexToUse).then(() => {
|
||||||
viewport.setProperties({ ...properties });
|
viewport.setProperties({ ...properties });
|
||||||
this.setPresentations(viewport.id, presentations, viewportInfo);
|
this.setPresentations(viewport.id, presentations, viewportInfo);
|
||||||
|
|
||||||
|
if (overlayProcessingResult?.addOverlayFn) {
|
||||||
|
overlayProcessingResult.addOverlayFn();
|
||||||
|
}
|
||||||
|
|
||||||
if (displayArea) {
|
if (displayArea) {
|
||||||
viewport.setDisplayArea(displayArea);
|
viewport.setDisplayArea(displayArea);
|
||||||
}
|
}
|
||||||
@ -827,10 +837,14 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
|||||||
});
|
});
|
||||||
|
|
||||||
// For SEG and RT viewports
|
// For SEG and RT viewports
|
||||||
this._processExtraDisplaySetsForViewport(viewport);
|
const { addOverlayFn } = this._processExtraDisplaySetsForViewport(viewport) || {};
|
||||||
|
|
||||||
await viewport.setVolumes(volumeInputArray);
|
await viewport.setVolumes(volumeInputArray);
|
||||||
|
|
||||||
|
if (addOverlayFn) {
|
||||||
|
addOverlayFn();
|
||||||
|
}
|
||||||
|
|
||||||
volumesProperties.forEach(({ properties, volumeId }) => {
|
volumesProperties.forEach(({ properties, volumeId }) => {
|
||||||
viewport.setProperties(properties, volumeId);
|
viewport.setProperties(properties, volumeId);
|
||||||
});
|
});
|
||||||
@ -880,8 +894,12 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
|||||||
segOrRTSOverlayDisplaySet.referencedDisplaySetInstanceUID
|
segOrRTSOverlayDisplaySet.referencedDisplaySetInstanceUID
|
||||||
);
|
);
|
||||||
const imageIds = referenceDisplaySet.images.map(image => image.imageId);
|
const imageIds = referenceDisplaySet.images.map(image => image.imageId);
|
||||||
this.addOverlayRepresentationForDisplaySet(segOrRTSOverlayDisplaySet, viewport);
|
|
||||||
return { imageIds };
|
return {
|
||||||
|
imageIds,
|
||||||
|
addOverlayFn: () =>
|
||||||
|
this.addOverlayRepresentationForDisplaySet(segOrRTSOverlayDisplaySet, viewport),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private addOverlayRepresentationForDisplaySet(
|
private addOverlayRepresentationForDisplaySet(
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { PositionPresentation } from '../types/Presentation';
|
|||||||
import { addUniqueIndex, JOIN_STR } from './presentationUtils';
|
import { addUniqueIndex, JOIN_STR } from './presentationUtils';
|
||||||
|
|
||||||
const PRESENTATION_TYPE_ID = 'positionPresentationId';
|
const PRESENTATION_TYPE_ID = 'positionPresentationId';
|
||||||
const DEBUG_STORE = true;
|
const DEBUG_STORE = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the state and actions for managing position presentations.
|
* Represents the state and actions for managing position presentations.
|
||||||
|
|||||||
@ -142,7 +142,9 @@ function createDicomLocalApi(dicomLocalConfig) {
|
|||||||
study.series.forEach(aSeries => {
|
study.series.forEach(aSeries => {
|
||||||
const { SeriesInstanceUID } = aSeries;
|
const { SeriesInstanceUID } = aSeries;
|
||||||
|
|
||||||
aSeries.instances.forEach(instance => {
|
const isMultiframe = aSeries.instances[0].NumberOfFrames > 1;
|
||||||
|
|
||||||
|
aSeries.instances.forEach((instance, index) => {
|
||||||
const {
|
const {
|
||||||
url: imageId,
|
url: imageId,
|
||||||
StudyInstanceUID,
|
StudyInstanceUID,
|
||||||
@ -151,22 +153,14 @@ function createDicomLocalApi(dicomLocalConfig) {
|
|||||||
} = instance;
|
} = instance;
|
||||||
|
|
||||||
instance.imageId = imageId;
|
instance.imageId = imageId;
|
||||||
const numberOfFrames = instance.NumberOfFrames || 1;
|
|
||||||
// Process all frames consistently, whether single or multiframe
|
// Add imageId specific mapping to this data as the URL isn't necessarily WADO-URI.
|
||||||
for (let i = 0; i < numberOfFrames; i++) {
|
metadataProvider.addImageIdToUIDs(imageId, {
|
||||||
const frameNumber = i + 1;
|
StudyInstanceUID,
|
||||||
const frameImageId = implementation.getImageIdsForInstance({
|
SeriesInstanceUID,
|
||||||
instance,
|
SOPInstanceUID,
|
||||||
frame: frameNumber,
|
frameIndex: isMultiframe ? index : 1,
|
||||||
});
|
});
|
||||||
// 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
DicomMetadataStore._broadcastEvent(EVENTS.INSTANCES_ADDED, {
|
DicomMetadataStore._broadcastEvent(EVENTS.INSTANCES_ADDED, {
|
||||||
|
|||||||
@ -30,6 +30,7 @@ function WrappedPanelStudyBrowser({ commandsManager, extensionManager, servicesM
|
|||||||
return (
|
return (
|
||||||
<PanelStudyBrowser
|
<PanelStudyBrowser
|
||||||
servicesManager={servicesManager}
|
servicesManager={servicesManager}
|
||||||
|
commandsManager={commandsManager}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
getImageSrc={_getImageSrcFromImageId}
|
getImageSrc={_getImageSrcFromImageId}
|
||||||
getStudiesForPatientByMRN={_getStudiesForPatientByMRN}
|
getStudiesForPatientByMRN={_getStudiesForPatientByMRN}
|
||||||
|
|||||||
@ -63,7 +63,7 @@ const defaultProtocol = {
|
|||||||
// It has no affect if nothing is specified in the URL.
|
// It has no affect if nothing is specified in the URL.
|
||||||
{
|
{
|
||||||
attribute: 'isDisplaySetFromUrl',
|
attribute: 'isDisplaySetFromUrl',
|
||||||
weight: 10,
|
weight: 20,
|
||||||
constraint: {
|
constraint: {
|
||||||
equals: true,
|
equals: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -24,7 +24,7 @@ const defaultDisplaySetSelector = {
|
|||||||
// It has no affect if nothing is specified in the URL.
|
// It has no affect if nothing is specified in the URL.
|
||||||
{
|
{
|
||||||
attribute: 'isDisplaySetFromUrl',
|
attribute: 'isDisplaySetFromUrl',
|
||||||
weight: 10,
|
weight: 20,
|
||||||
constraint: {
|
constraint: {
|
||||||
equals: true,
|
equals: true,
|
||||||
},
|
},
|
||||||
@ -56,7 +56,7 @@ const priorDisplaySetSelector = {
|
|||||||
// It has no affect if nothing is specified in the URL.
|
// It has no affect if nothing is specified in the URL.
|
||||||
{
|
{
|
||||||
attribute: 'isDisplaySetFromUrl',
|
attribute: 'isDisplaySetFromUrl',
|
||||||
weight: 10,
|
weight: 20,
|
||||||
constraint: {
|
constraint: {
|
||||||
equals: true,
|
equals: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -50,7 +50,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
|
|||||||
// It has no affect if nothing is specified in the URL.
|
// It has no affect if nothing is specified in the URL.
|
||||||
{
|
{
|
||||||
attribute: 'isDisplaySetFromUrl',
|
attribute: 'isDisplaySetFromUrl',
|
||||||
weight: 10,
|
weight: 20,
|
||||||
constraint: {
|
constraint: {
|
||||||
equals: true,
|
equals: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -43,7 +43,7 @@ const hpScale: Types.HangingProtocol.Protocol = {
|
|||||||
// It has no affect if nothing is specified in the URL.
|
// It has no affect if nothing is specified in the URL.
|
||||||
{
|
{
|
||||||
attribute: 'isDisplaySetFromUrl',
|
attribute: 'isDisplaySetFromUrl',
|
||||||
weight: 10,
|
weight: 20,
|
||||||
constraint: {
|
constraint: {
|
||||||
equals: true,
|
equals: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -38,7 +38,7 @@ function usePatientInfo(servicesManager: AppTypes.ServicesManager) {
|
|||||||
}
|
}
|
||||||
setPatientInfo({
|
setPatientInfo({
|
||||||
PatientID: instance.PatientID || null,
|
PatientID: instance.PatientID || null,
|
||||||
PatientName: instance.PatientName ? formatPN(instance.PatientName.Alphabetic) : null,
|
PatientName: instance.PatientName ? formatPN(instance.PatientName) : null,
|
||||||
PatientSex: instance.PatientSex || null,
|
PatientSex: instance.PatientSex || null,
|
||||||
PatientDOB: formatDate(instance.PatientBirthDate) || null,
|
PatientDOB: formatDate(instance.PatientBirthDate) || null,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { create } from 'zustand';
|
|||||||
import { devtools } from 'zustand/middleware';
|
import { devtools } from 'zustand/middleware';
|
||||||
|
|
||||||
const PRESENTATION_TYPE_ID = 'viewportsByPositionId';
|
const PRESENTATION_TYPE_ID = 'viewportsByPositionId';
|
||||||
const DEBUG_STORE = true;
|
const DEBUG_STORE = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the state and actions for managing viewports by position.
|
* Represents the state and actions for managing viewports by position.
|
||||||
|
|||||||
@ -3,7 +3,10 @@ import classnames from 'classnames';
|
|||||||
|
|
||||||
import listComponentGenerator from './listComponentGenerator';
|
import listComponentGenerator from './listComponentGenerator';
|
||||||
import './ViewportOverlay.css';
|
import './ViewportOverlay.css';
|
||||||
import { formatDICOMDate, formatDICOMTime, formatNumberPrecision, formatPN } from './utils';
|
import { formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
|
||||||
|
import { utils } from '@ohif/core';
|
||||||
|
|
||||||
|
const { formatPN } = utils;
|
||||||
|
|
||||||
interface OverlayItem {
|
interface OverlayItem {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@ -52,28 +52,6 @@ export function formatDICOMTime(time, strFormat = 'HH:mm:ss') {
|
|||||||
return moment(time, 'HH:mm:ss').format(strFormat);
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the first ^ to a ', '. String.replace() only affects
|
|
||||||
// the first appearance of the character.
|
|
||||||
const commaBetweenFirstAndLast = name.replace('^', ', ');
|
|
||||||
|
|
||||||
// Replace any remaining '^' characters with spaces
|
|
||||||
const cleaned = commaBetweenFirstAndLast.replace(/\^/g, ' ');
|
|
||||||
|
|
||||||
// Trim any extraneous whitespace
|
|
||||||
return cleaned.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets compression type
|
* Gets compression type
|
||||||
*
|
*
|
||||||
|
|||||||
@ -188,7 +188,7 @@ const hpTestSwitch: Types.HangingProtocol.Protocol = {
|
|||||||
// It has no affect if nothing is specified in the URL.
|
// It has no affect if nothing is specified in the URL.
|
||||||
{
|
{
|
||||||
attribute: 'isDisplaySetFromUrl',
|
attribute: 'isDisplaySetFromUrl',
|
||||||
weight: 10,
|
weight: 20,
|
||||||
constraint: {
|
constraint: {
|
||||||
equals: true,
|
equals: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -9,8 +9,10 @@ import dicomRTAnnotationExport from './utils/dicomRTAnnotationExport/RTStructure
|
|||||||
|
|
||||||
import { getWebWorkerManager } from '@cornerstonejs/core';
|
import { getWebWorkerManager } from '@cornerstonejs/core';
|
||||||
import { Enums } from '@cornerstonejs/tools';
|
import { Enums } from '@cornerstonejs/tools';
|
||||||
|
import { utils } from '@ohif/core';
|
||||||
|
|
||||||
const { SegmentationRepresentations } = Enums;
|
const { SegmentationRepresentations } = Enums;
|
||||||
|
const { formatPN } = utils;
|
||||||
|
|
||||||
const metadataProvider = classes.MetadataProvider;
|
const metadataProvider = classes.MetadataProvider;
|
||||||
const ROI_THRESHOLD_MANUAL_TOOL_IDS = [
|
const ROI_THRESHOLD_MANUAL_TOOL_IDS = [
|
||||||
@ -595,7 +597,7 @@ const commandsModule = ({ servicesManager, commandsManager, extensionManager }:
|
|||||||
report[id] = {
|
report[id] = {
|
||||||
...segReport,
|
...segReport,
|
||||||
PatientID: instance.PatientID ?? '000000',
|
PatientID: instance.PatientID ?? '000000',
|
||||||
PatientName: instance.PatientName.Alphabetic,
|
PatientName: formatPN(instance.PatientName),
|
||||||
StudyInstanceUID: instance.StudyInstanceUID,
|
StudyInstanceUID: instance.StudyInstanceUID,
|
||||||
SeriesInstanceUID: instance.SeriesInstanceUID,
|
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||||
StudyDate: instance.StudyDate,
|
StudyDate: instance.StudyDate,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ window.config = {
|
|||||||
wadoRoot: 'http://localhost/dicom-web',
|
wadoRoot: 'http://localhost/dicom-web',
|
||||||
qidoSupportsIncludeField: true,
|
qidoSupportsIncludeField: true,
|
||||||
supportsReject: true,
|
supportsReject: true,
|
||||||
|
dicomUploadEnabled: true,
|
||||||
imageRendering: 'wadors',
|
imageRendering: 'wadors',
|
||||||
thumbnailRendering: 'wadors',
|
thumbnailRendering: 'wadors',
|
||||||
enableStudyLazyLoad: true,
|
enableStudyLazyLoad: true,
|
||||||
|
|||||||
@ -6,10 +6,10 @@ import DicomMetadataStore from '../services/DicomMetadataStore';
|
|||||||
import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';
|
import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';
|
||||||
import toNumber from '../utils/toNumber';
|
import toNumber from '../utils/toNumber';
|
||||||
import combineFrameInstance from '../utils/combineFrameInstance';
|
import combineFrameInstance from '../utils/combineFrameInstance';
|
||||||
|
import formatPN from '../utils/formatPN';
|
||||||
|
|
||||||
class MetadataProvider {
|
class MetadataProvider {
|
||||||
private readonly imageURIToUIDs: Map<string, any> = new Map();
|
private readonly imageURIToUIDs: Map<string, any> = new Map();
|
||||||
private readonly imageUIDsByImageId: Map<string, any> = new Map();
|
|
||||||
// Can be used to store custom metadata for a specific type.
|
// Can be used to store custom metadata for a specific type.
|
||||||
// For instance, the scaling metadata for PET can be stored here
|
// For instance, the scaling metadata for PET can be stored here
|
||||||
// as type "scalingModule"
|
// as type "scalingModule"
|
||||||
@ -25,7 +25,6 @@ class MetadataProvider {
|
|||||||
// An example would be dicom hosted at some random site.
|
// An example would be dicom hosted at some random site.
|
||||||
const imageURI = imageIdToURI(imageId);
|
const imageURI = imageIdToURI(imageId);
|
||||||
this.imageURIToUIDs.set(imageURI, uids);
|
this.imageURIToUIDs.set(imageURI, uids);
|
||||||
this.imageUIDsByImageId.set(imageId, uids);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addCustomMetadata(imageId, type, metadata) {
|
addCustomMetadata(imageId, type, metadata) {
|
||||||
@ -347,7 +346,7 @@ class MetadataProvider {
|
|||||||
|
|
||||||
let patientName;
|
let patientName;
|
||||||
if (PatientName) {
|
if (PatientName) {
|
||||||
patientName = PatientName.Alphabetic;
|
patientName = formatPN(PatientName);
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
@ -462,11 +461,6 @@ class MetadataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUIDsFromImageID(imageId) {
|
getUIDsFromImageID(imageId) {
|
||||||
const cachedUIDs = this.imageUIDsByImageId.get(imageId);
|
|
||||||
if (cachedUIDs) {
|
|
||||||
return cachedUIDs;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageId.startsWith('wadors:')) {
|
if (imageId.startsWith('wadors:')) {
|
||||||
const strippedImageId = imageId.split('/studies/')[1];
|
const strippedImageId = imageId.split('/studies/')[1];
|
||||||
const splitImageId = strippedImageId.split('/');
|
const splitImageId = strippedImageId.split('/');
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { vec3 } from 'gl-matrix';
|
import { vec3 } from 'gl-matrix';
|
||||||
|
import { dicomSplit } from './dicomSplit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combine the Per instance frame data, the shared frame data
|
* Combine the Per instance frame data, the shared frame data
|
||||||
@ -15,24 +16,13 @@ const combineFrameInstance = (frame, instance) => {
|
|||||||
PerFrameFunctionalGroupsSequence,
|
PerFrameFunctionalGroupsSequence,
|
||||||
SharedFunctionalGroupsSequence,
|
SharedFunctionalGroupsSequence,
|
||||||
NumberOfFrames,
|
NumberOfFrames,
|
||||||
SpacingBetweenSlices,
|
ImageType,
|
||||||
} = instance;
|
} = instance;
|
||||||
|
|
||||||
|
instance.ImageType = dicomSplit(ImageType);
|
||||||
|
|
||||||
if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) {
|
if (PerFrameFunctionalGroupsSequence || NumberOfFrames > 1) {
|
||||||
const frameNumber = Number.parseInt(frame || 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
|
// this is to fix NM multiframe datasets with position and orientation
|
||||||
// information inside DetectorInformationSequence
|
// information inside DetectorInformationSequence
|
||||||
@ -44,8 +34,12 @@ const combineFrameInstance = (frame, instance) => {
|
|||||||
let ImagePositionPatientToUse = instance.ImagePositionPatient;
|
let ImagePositionPatientToUse = instance.ImagePositionPatient;
|
||||||
|
|
||||||
if (!instance.ImagePositionPatient && instance.DetectorInformationSequence) {
|
if (!instance.ImagePositionPatient && instance.DetectorInformationSequence) {
|
||||||
const imagePositionPatient = instance.DetectorInformationSequence[0].ImagePositionPatient;
|
let imagePositionPatient = instance.DetectorInformationSequence[0].ImagePositionPatient;
|
||||||
const imageOrientationPatient = instance.ImageOrientationPatient;
|
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
|
// Calculate the position for the current frame
|
||||||
if (imageOrientationPatient && SpacingBetweenSlices) {
|
if (imageOrientationPatient && SpacingBetweenSlices) {
|
||||||
@ -73,27 +67,77 @@ const combineFrameInstance = (frame, instance) => {
|
|||||||
ImagePositionPatientToUse = [position[0], position[1], position[2]];
|
ImagePositionPatientToUse = [position[0], position[1], position[2]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.debug('🚀 ~ ImagePositionPatientToUse:', ImagePositionPatientToUse);
|
|
||||||
|
|
||||||
const newInstance = Object.assign(instance, { frameNumber: frameNumber });
|
// Cache the _parentInstance at the top level as a full copy to prevent
|
||||||
|
// setting values hard.
|
||||||
// merge the shared first then the per frame to override
|
if (!instance._parentInstance) {
|
||||||
[...shared, ...perFrame].forEach(item => {
|
Object.defineProperty(instance, '_parentInstance', {
|
||||||
Object.entries(item).forEach(([key, value]) => {
|
value: { ...instance },
|
||||||
newInstance[key] = value;
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
const sharedInstance = createCombinedValue(
|
||||||
|
instance._parentInstance,
|
||||||
|
SharedFunctionalGroupsSequence?.[0],
|
||||||
|
'_shared'
|
||||||
|
);
|
||||||
|
const newInstance = createCombinedValue(
|
||||||
|
sharedInstance,
|
||||||
|
PerFrameFunctionalGroupsSequence?.[frameNumber - 1],
|
||||||
|
frameNumber
|
||||||
|
);
|
||||||
|
|
||||||
// Todo: we should cache this combined instance somewhere, maybe add it
|
newInstance.ImagePositionPatient = ImagePositionPatientToUse ??
|
||||||
// back to the dicomMetaStore so we don't have to do this again.
|
newInstance.ImagePositionPatient ?? [0, 0, frameNumber];
|
||||||
return {
|
|
||||||
...newInstance,
|
Object.defineProperty(newInstance, 'frameNumber', {
|
||||||
ImagePositionPatient: ImagePositionPatientToUse ??
|
value: frameNumber,
|
||||||
newInstance.ImagePositionPatient ?? [0, 0, frameNumber],
|
writable: true,
|
||||||
};
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
return newInstance;
|
||||||
} else {
|
} else {
|
||||||
return instance;
|
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;
|
export default combineFrameInstance;
|
||||||
|
|||||||
5
platform/core/src/utils/dicomSplit.ts
Normal file
5
platform/core/src/utils/dicomSplit.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export function dicomSplit(value) {
|
||||||
|
return (
|
||||||
|
(Array.isArray(value) && value) || (typeof value === 'string' && value.split('\\')) || value
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { DicomMetadataStore } from '../services/DicomMetadataStore/DicomMetadataStore';
|
import { DicomMetadataStore } from '../services/DicomMetadataStore/DicomMetadataStore';
|
||||||
|
import formatPN from './formatPN';
|
||||||
|
|
||||||
export default function downloadCSVReport(measurementData) {
|
export default function downloadCSVReport(measurementData) {
|
||||||
if (measurementData.length === 0) {
|
if (measurementData.length === 0) {
|
||||||
@ -86,7 +87,7 @@ function _getCommonRowItems(measurement, seriesMetadata) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'Patient ID': firstInstance.PatientID, // Patient ID
|
'Patient ID': firstInstance.PatientID, // Patient ID
|
||||||
'Patient Name': firstInstance.PatientName?.Alphabetic || '', // Patient Name
|
'Patient Name': formatPN(firstInstance.PatientName) || '', // Patient Name
|
||||||
StudyInstanceUID: measurement.referenceStudyUID, // StudyInstanceUID
|
StudyInstanceUID: measurement.referenceStudyUID, // StudyInstanceUID
|
||||||
SeriesInstanceUID: measurement.referenceSeriesUID, // SeriesInstanceUID
|
SeriesInstanceUID: measurement.referenceSeriesUID, // SeriesInstanceUID
|
||||||
SOPInstanceUID: measurement.SOPInstanceUID, // SOPInstanceUID
|
SOPInstanceUID: measurement.SOPInstanceUID, // SOPInstanceUID
|
||||||
|
|||||||
@ -48,7 +48,7 @@ function usePatientInfo(servicesManager: AppTypes.ServicesManager) {
|
|||||||
}
|
}
|
||||||
setPatientInfo({
|
setPatientInfo({
|
||||||
PatientID: instance.PatientID || null,
|
PatientID: instance.PatientID || null,
|
||||||
PatientName: instance.PatientName ? formatPN(instance.PatientName.Alphabetic) : null,
|
PatientName: instance.PatientName ? formatPN(instance.PatientName) : null,
|
||||||
PatientSex: instance.PatientSex || null,
|
PatientSex: instance.PatientSex || null,
|
||||||
PatientDOB: formatDate(instance.PatientBirthDate) || null,
|
PatientDOB: formatDate(instance.PatientBirthDate) || null,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
3.9.2
|
3.9.3
|
||||||
37
yarn.lock
37
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"
|
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.2.tgz#d78b298cf70fd3b752fd951175a3da6a7b48f024"
|
||||||
integrity sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==
|
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:
|
fastest-levenshtein@^1.0.12:
|
||||||
version "1.0.16"
|
version "1.0.16"
|
||||||
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
|
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:
|
dependencies:
|
||||||
isarray "0.0.1"
|
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"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b"
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b"
|
||||||
integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==
|
integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==
|
||||||
@ -20438,7 +20431,7 @@ pumpify@^1.3.3:
|
|||||||
inherits "^2.0.3"
|
inherits "^2.0.3"
|
||||||
pump "^2.0.0"
|
pump "^2.0.0"
|
||||||
|
|
||||||
punycode@^1.3.2, punycode@^1.4.1:
|
punycode@^1.4.1:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||||
integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
|
integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
|
||||||
@ -22034,21 +22027,7 @@ serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
randombytes "^2.1.0"
|
randombytes "^2.1.0"
|
||||||
|
|
||||||
serve-handler@6.1.5:
|
serve-handler@6.1.6, serve-handler@^6.1.6:
|
||||||
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:
|
|
||||||
version "6.1.6"
|
version "6.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.6.tgz#50803c1d3e947cd4a341d617f8209b22bd76cfa1"
|
resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.6.tgz#50803c1d3e947cd4a341d617f8209b22bd76cfa1"
|
||||||
integrity sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==
|
integrity sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==
|
||||||
@ -22084,10 +22063,10 @@ serve-static@1.16.2:
|
|||||||
parseurl "~1.3.3"
|
parseurl "~1.3.3"
|
||||||
send "0.19.0"
|
send "0.19.0"
|
||||||
|
|
||||||
serve@^14.2.0:
|
serve@^14.2.4:
|
||||||
version "14.2.3"
|
version "14.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/serve/-/serve-14.2.3.tgz#047ba2b349354255bc09e0332cd41a92787836c9"
|
resolved "https://registry.yarnpkg.com/serve/-/serve-14.2.4.tgz#ba4c425c3c965f496703762e808f34b913f42fb0"
|
||||||
integrity sha512-VqUFMC7K3LDGeGnJM9h56D3XGKb6KGgOw0cVNtA26yYXHCcpxf3xwCTUaQoWlVS7i8Jdh3GjQkOB23qsXyjoyQ==
|
integrity sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@zeit/schemas" "2.36.0"
|
"@zeit/schemas" "2.36.0"
|
||||||
ajv "8.12.0"
|
ajv "8.12.0"
|
||||||
@ -22098,7 +22077,7 @@ serve@^14.2.0:
|
|||||||
clipboardy "3.0.0"
|
clipboardy "3.0.0"
|
||||||
compression "1.7.4"
|
compression "1.7.4"
|
||||||
is-port-reachable "4.0.0"
|
is-port-reachable "4.0.0"
|
||||||
serve-handler "6.1.5"
|
serve-handler "6.1.6"
|
||||||
update-check "1.5.4"
|
update-check "1.5.4"
|
||||||
|
|
||||||
set-blocking@^2.0.0:
|
set-blocking@^2.0.0:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user