fix(seg): jump to the first slice in SEG and RT that has data (#4605)

This commit is contained in:
Alireza 2024-12-17 12:34:13 -05:00 committed by GitHub
parent 7586006e7f
commit 9bf24d6dc5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 123 additions and 60 deletions

View File

@ -97,6 +97,7 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) {
viewportType: 'volume',
orientation: viewportOptions.orientation,
viewportId: viewportOptions.viewportId,
presentationIds: viewportOptions.presentationIds,
}}
displaySetOptions={[{}, pmapDisplaySetOptions]}
></Component>

View File

@ -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(

View File

@ -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);

View File

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

View File

@ -16,22 +16,13 @@ const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking'
const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup';
function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
const {
children,
dataSource,
displaySets,
viewportOptions,
servicesManager,
extensionManager,
} = props;
const { children, dataSource, displaySets, viewportOptions, servicesManager, extensionManager } =
props;
const [appConfig] = useAppConfig();
const {
displaySetService,
measurementService,
viewportActionCornersService,
} = servicesManager.services;
const { displaySetService, measurementService, viewportActionCornersService } =
servicesManager.services;
const viewportId = viewportOptions.viewportId;
@ -44,7 +35,6 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
const { setPositionPresentation } = usePositionPresentationStore();
const [viewportGrid, viewportGridService] = useViewportGrid();
const [measurementSelected, setMeasurementSelected] = useState(0);
const [measurementCount, setMeasurementCount] = useState(1);
@ -160,13 +150,13 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
setActiveImageDisplaySetData(referencedDisplaySet);
setReferencedDisplaySetMetadata(referencedDisplaySetMetadata);
const { presentationIds } = viewportOptions
const measurement = srDisplaySet.measurements[newMeasurementSelected]
setPositionPresentation(presentationIds.positionPresentationId, {
const { presentationIds } = viewportOptions;
const measurement = srDisplaySet.measurements[newMeasurementSelected];
setPositionPresentation(presentationIds.positionPresentationId, {
viewReference: {
referencedImageId: measurement.imageId
}
})
referencedImageId: measurement.imageId,
},
});
});
},
[dataSource, srDisplaySet, activeImageDisplaySetData, viewportId]
@ -188,8 +178,6 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
return null;
}
return (
<Component
{...props}

View File

@ -20,7 +20,7 @@ const frameView: Types.HangingProtocol.Protocol = {
},
{
attribute: 'isDisplaySetFromUrl',
weight: 10,
weight: 20,
constraint: {
equals: true,
},

View File

@ -479,14 +479,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<number>;
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,
@ -543,7 +553,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);

View File

@ -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(

View File

@ -63,7 +63,7 @@ const defaultProtocol = {
// It has no affect if nothing is specified in the URL.
{
attribute: 'isDisplaySetFromUrl',
weight: 10,
weight: 20,
constraint: {
equals: true,
},

View File

@ -24,7 +24,7 @@ const defaultDisplaySetSelector = {
// It has no affect if nothing is specified in the URL.
{
attribute: 'isDisplaySetFromUrl',
weight: 10,
weight: 20,
constraint: {
equals: true,
},
@ -56,7 +56,7 @@ const priorDisplaySetSelector = {
// It has no affect if nothing is specified in the URL.
{
attribute: 'isDisplaySetFromUrl',
weight: 10,
weight: 20,
constraint: {
equals: true,
},

View File

@ -50,7 +50,7 @@ const hpMN: Types.HangingProtocol.Protocol = {
// It has no affect if nothing is specified in the URL.
{
attribute: 'isDisplaySetFromUrl',
weight: 10,
weight: 20,
constraint: {
equals: true,
},

View File

@ -43,7 +43,7 @@ const hpScale: Types.HangingProtocol.Protocol = {
// It has no affect if nothing is specified in the URL.
{
attribute: 'isDisplaySetFromUrl',
weight: 10,
weight: 20,
constraint: {
equals: true,
},

View File

@ -188,7 +188,7 @@ const hpTestSwitch: Types.HangingProtocol.Protocol = {
// It has no affect if nothing is specified in the URL.
{
attribute: 'isDisplaySetFromUrl',
weight: 10,
weight: 20,
constraint: {
equals: true,
},

View File

@ -23,6 +23,7 @@ window.config = {
wadoRoot: 'http://localhost/dicom-web',
qidoSupportsIncludeField: true,
supportsReject: true,
dicomUploadEnabled: true,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
enableStudyLazyLoad: true,

View File

@ -2580,10 +2580,10 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
"@cornerstonejs/adapters@^2.7.3":
version "2.7.3"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-2.7.3.tgz#96d2ab568bfc987a6c7bb44f849ad2f94be9014a"
integrity sha512-hEJusn3DynPrgyaAidOoYboFJ/t5dd1TDaIiIr2FQQbfkIhun6EE5UkLOcdP8dqiKujpsHm9LcdCFh/b3/7K8g==
"@cornerstonejs/adapters@^2.11.2":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-2.11.5.tgz#21b799917e09a002465e84412ab123a778d3d2a0"
integrity sha512-nLtbCXqyjtQH59KthjnlB3I8MgNo9bZt/nYPWdm4K3FTMXRafJ8sp4oc1VWpeHJlAAgmnyq0PEKaWY2K0Y+S3w==
dependencies:
"@babel/runtime-corejs2" "^7.17.8"
buffer "^6.0.3"
@ -2616,19 +2616,19 @@
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0"
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
"@cornerstonejs/core@^2.7.3":
version "2.7.3"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-2.7.3.tgz#0e580e3052b84530f0da4fb4b94e23f580415f47"
integrity sha512-q3Sc1eiRlC6kAzgqkJ/cGFGpJe62YwinS2nbZceqDKoAno3C4W81h5jrolENKH4HEOS6qvuz3sPP/sh6ryg/zA==
"@cornerstonejs/core@^2.11.2":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-2.11.5.tgz#2c45ca7b3f8032752f3d9ed787fa14e7a77cb739"
integrity sha512-RB3zkk88sVdVwtWGbG2V9rTXhZHNwxGOiQrkftRZqMF17bFTLqPEHkpf/PZveNUcZmjaqn0oPlKXD8QmedJonA==
dependencies:
"@kitware/vtk.js" "32.1.1"
comlink "^4.4.1"
gl-matrix "^3.4.3"
"@cornerstonejs/dicom-image-loader@^2.7.3":
version "2.7.3"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-2.7.3.tgz#04466643bd3802933378460d60a731d9cc3a34e3"
integrity sha512-P5wZovDo5onMIMILwNRrlv75GYnDFIFVQfnvepJKTfNTeQbHhTaNRfx8MHOHNYCa8JBtrR6JSeD2FkMoZE/vsg==
"@cornerstonejs/dicom-image-loader@^2.11.2":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-2.11.5.tgz#b3acf689b23c9db61f5b0c8fba26e7ec2144deb4"
integrity sha512-xdYBHMAMlilziXYUYvMh9nrfTl0O26kvFowWRMeIjP2SwTtsYQqCri9CsRxf6giJRcsw3gVn0PhUdZpfzX6vSQ==
dependencies:
"@cornerstonejs/codec-charls" "^1.2.3"
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
@ -2639,10 +2639,10 @@
pako "^2.0.4"
uuid "^9.0.0"
"@cornerstonejs/tools@^2.7.3":
version "2.7.3"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-2.7.3.tgz#9e2cbbb4d5d6e3b6b6f42f631d51a433a337b201"
integrity sha512-03WRQ5AX5RQmaqc7uSkgv9npruCBbRExjp4FOlOHU7Tq16q7CJlqIPF1wWEqecoxn7T1IXL3bokXOTPvh8zSFQ==
"@cornerstonejs/tools@^2.11.2":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-2.11.5.tgz#f8a50fd636172e5895781a429d85b22a49a1021c"
integrity sha512-n9ScPe0d73dXR5RgblhRPkrQ+hmzzx6/3ykH2jaf/kr52tzfNO3qxyZURPDAusC/sOJ2Rk2yVqQBv0dMhKYi0Q==
dependencies:
"@types/offscreencanvas" "2019.7.3"
comlink "^4.4.1"