Skip to content

Commit 658994c

Browse files
Belbin-GKtrenser-belbinwayfarer3130
authored
fix: prevent annotation from appearing in active viewport when switching series with different Frame of Reference UID (#5630)
* fix: prevent annotation from appearing in active viewport when switching series with different Frame of Reference UID * refactor: extract viewport update strategies * Update version and use nearest orientation viewport --------- Co-authored-by: trenser-belbin <belbin.kunjumon@prenuvo.com> Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
1 parent 0af4c2a commit 658994c

13 files changed

Lines changed: 321 additions & 260 deletions

File tree

bun.lock

Lines changed: 165 additions & 165 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/cornerstone-dicom-pmap/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@
4646
},
4747
"dependencies": {
4848
"@babel/runtime": "7.28.2",
49-
"@cornerstonejs/adapters": "4.14.4",
50-
"@cornerstonejs/core": "4.14.4",
49+
"@cornerstonejs/adapters": "4.14.5",
50+
"@cornerstonejs/core": "4.14.5",
5151
"@kitware/vtk.js": "34.15.1",
5252
"react-color": "2.19.3"
5353
}

extensions/cornerstone-dicom-seg/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@
4646
},
4747
"dependencies": {
4848
"@babel/runtime": "7.28.2",
49-
"@cornerstonejs/adapters": "4.14.4",
50-
"@cornerstonejs/core": "4.14.4",
49+
"@cornerstonejs/adapters": "4.14.5",
50+
"@cornerstonejs/core": "4.14.5",
5151
"@kitware/vtk.js": "34.15.1",
5252
"react-color": "2.19.3"
5353
}

extensions/cornerstone-dicom-sr/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@
4646
},
4747
"dependencies": {
4848
"@babel/runtime": "7.28.2",
49-
"@cornerstonejs/adapters": "4.14.4",
50-
"@cornerstonejs/core": "4.14.4",
51-
"@cornerstonejs/tools": "4.14.4",
49+
"@cornerstonejs/adapters": "4.14.5",
50+
"@cornerstonejs/core": "4.14.5",
51+
"@cornerstonejs/tools": "4.14.5",
5252
"classnames": "2.5.1"
5353
}
5454
}

extensions/cornerstone-dynamic-volume/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
},
4343
"dependencies": {
4444
"@babel/runtime": "7.28.2",
45-
"@cornerstonejs/core": "4.14.4",
46-
"@cornerstonejs/tools": "4.14.4",
45+
"@cornerstonejs/core": "4.14.5",
46+
"@cornerstonejs/tools": "4.14.5",
4747
"classnames": "2.5.1"
4848
}
4949
}

extensions/cornerstone/package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
4141
"@cornerstonejs/codec-openjpeg": "1.3.0",
4242
"@cornerstonejs/codec-openjph": "2.4.7",
43-
"@cornerstonejs/dicom-image-loader": "4.14.4",
43+
"@cornerstonejs/dicom-image-loader": "4.14.5",
4444
"@ohif/core": "3.12.0-beta.109",
4545
"@ohif/ui": "3.12.0-beta.109",
4646
"dcmjs": "0.45.0",
@@ -53,12 +53,12 @@
5353
},
5454
"dependencies": {
5555
"@babel/runtime": "7.28.2",
56-
"@cornerstonejs/adapters": "4.14.4",
57-
"@cornerstonejs/ai": "4.14.4",
58-
"@cornerstonejs/core": "4.14.4",
59-
"@cornerstonejs/labelmap-interpolation": "4.14.4",
60-
"@cornerstonejs/polymorphic-segmentation": "4.14.4",
61-
"@cornerstonejs/tools": "4.14.4",
56+
"@cornerstonejs/adapters": "4.14.5",
57+
"@cornerstonejs/ai": "4.14.5",
58+
"@cornerstonejs/core": "4.14.5",
59+
"@cornerstonejs/labelmap-interpolation": "4.14.5",
60+
"@cornerstonejs/polymorphic-segmentation": "4.14.5",
61+
"@cornerstonejs/tools": "4.14.5",
6262
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
6363
"@kitware/vtk.js": "34.15.1",
6464
"html2canvas": "1.4.1",

extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts

Lines changed: 103 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { useLutPresentationStore } from '../../stores/useLutPresentationStore';
3737
import { usePositionPresentationStore } from '../../stores/usePositionPresentationStore';
3838
import { useSynchronizersStore } from '../../stores/useSynchronizersStore';
3939
import { useSegmentationPresentationStore } from '../../stores/useSegmentationPresentationStore';
40+
import getClosestOrientationFromIOP from '../../utils/isReferenceViewable';
4041

4142
const EVENTS = {
4243
VIEWPORT_DATA_CHANGED: 'event::cornerstoneViewportService:viewportDataChanged',
@@ -634,67 +635,118 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
634635

635636
/**
636637
* Figures out which viewport to update when the viewport type needs to change.
637-
* This may not be the active viewport if there is already a viewport showing
638-
* the display set, but in the wrong orientation.
639-
*
640-
* The viewport will need to update the viewport type and/or display set to
641-
* display the resulting data.
642-
*
643-
* The first choice will be a viewport already showing the correct display set,
644-
* but showing it as a stack.
645-
*
646-
* Second choice is to see if there is a viewport already showing the right
647-
* orientation for the image, but the wrong display set. This fixes the
648-
* case where the user is in MPR and a viewport other than active should be
649-
* the one to change to display the iamge.
650-
*
651-
* Final choice is to use the provide activeViewportId. This will cover
652-
* changes to/from video and wsi viewports and other cases where no
653-
* viewport is really even close to being able to display the measurement.
638+
* Orchestrates the search strategies in order of preference.
654639
*/
655640
public findUpdateableViewportConfiguration(activeViewportId: string, measurement) {
656641
const { metadata, displaySetInstanceUID } = measurement;
657-
const { volumeId, referencedImageId } = metadata;
658-
const { displaySetService, viewportGridService } = this.servicesManager.services;
642+
const { displaySetService } = this.servicesManager.services;
659643
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
660644

645+
// 1. Determine the target Viewport Type (Stack vs Volume)
646+
const viewportType = this.determineTargetViewportType(displaySet, metadata);
647+
648+
// 2. Strategy: Find viewport already showing this volume
649+
const volumeMatch = this.findViewportShowingVolume(
650+
metadata,
651+
displaySetInstanceUID,
652+
viewportType
653+
);
654+
if (volumeMatch) {
655+
return volumeMatch;
656+
}
657+
658+
// 3. Strategy: Find viewport with compatible orientation (even if different display set)
659+
const compatibleMatch = this.findViewportConvertibleToVolume(
660+
metadata,
661+
displaySetInstanceUID,
662+
viewportType
663+
);
664+
if (compatibleMatch) {
665+
return compatibleMatch;
666+
}
667+
668+
// 4. Strategy: Find viewport with matching orientation via IOP
669+
const orientationMatch = this.findViewportWithMatchingOrientation(
670+
displaySetInstanceUID,
671+
viewportType
672+
);
673+
if (orientationMatch) {
674+
return orientationMatch;
675+
}
676+
677+
// 5. Fallback: Use the active viewport
678+
return {
679+
viewportId: activeViewportId,
680+
displaySetInstanceUID,
681+
viewportOptions: { viewportType },
682+
};
683+
}
684+
685+
/**
686+
* Determines if the viewport should be what is specified in
687+
* the viewportType of the display set, or stack if the display
688+
* set isn't reconstructable and there is a referenced image id, otherwise
689+
* volume.
690+
*
691+
* Expect there to be more rules in the future for different types of annotations/settings
692+
* such as 3d annotations.
693+
*/
694+
public determineTargetViewportType(displaySet, metadata): string {
661695
let { viewportType } = displaySet;
696+
662697
if (!viewportType) {
663-
if (referencedImageId && !displaySet.isReconstructable) {
698+
if (metadata.referencedImageId && !displaySet.isReconstructable) {
664699
viewportType = csEnums.ViewportType.STACK;
665-
} else if (volumeId) {
700+
} else if (metadata.volumeId) {
666701
viewportType = 'volume';
667702
}
668703
}
704+
return viewportType;
705+
}
669706

670-
// Find viewports that could be updated to be volumes to show this view
671-
// That prefers a viewport already showing the right display set.
672-
if (volumeId) {
673-
for (const id of this.viewportsById.keys()) {
674-
const viewport = this.getCornerstoneViewport(id);
675-
if (viewport?.isReferenceViewable(metadata, { asVolume: true, withNavigation: true })) {
676-
return {
677-
viewportId: id,
678-
displaySetInstanceUID,
679-
viewportOptions: { viewportType },
680-
};
681-
}
707+
/**
708+
* Find viewports that could be updated to be volumes to show this view.
709+
* Prefers a viewport already showing the right display set.
710+
*/
711+
public findViewportShowingVolume(metadata, displaySetInstanceUID, viewportType) {
712+
if (!metadata.volumeId) {
713+
return null;
714+
}
715+
716+
for (const id of this.viewportsById.keys()) {
717+
const viewport = this.getCornerstoneViewport(id);
718+
if (viewport?.isReferenceViewable(metadata, { asVolume: true, withNavigation: true })) {
719+
return {
720+
viewportId: id,
721+
displaySetInstanceUID,
722+
viewportOptions: { viewportType },
723+
};
682724
}
683725
}
726+
return null;
727+
}
684728

685-
// Find a viewport in the correct orientation showing a different display set
686-
// which could be used to display the annotation.
729+
/**
730+
* Find a viewport that could be converted to a volume to show this annotation,
731+
* already showing the right display set.
732+
*/
733+
public findViewportConvertibleToVolume(metadata, displaySetInstanceUID, viewportType) {
734+
const { viewportGridService } = this.servicesManager.services;
687735
const altMetadata = { ...metadata, volumeId: null, referencedImageId: null };
736+
688737
for (const id of this.viewportsById.keys()) {
689738
const viewport = this.getCornerstoneViewport(id);
690739
const viewportDisplaySetUID = viewportGridService.getDisplaySetsUIDsForViewport(id)?.[0];
740+
691741
if (!viewportDisplaySetUID || !viewport) {
692742
continue;
693743
}
694-
if (volumeId) {
744+
745+
if (metadata.volumeId) {
695746
altMetadata.volumeId = viewportDisplaySetUID;
696747
}
697748
altMetadata.FrameOfReferenceUID = this._getFrameOfReferenceUID(viewportDisplaySetUID);
749+
698750
if (viewport.isReferenceViewable(altMetadata, { asVolume: true, withNavigation: true })) {
699751
return {
700752
viewportId: id,
@@ -703,13 +755,22 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
703755
};
704756
}
705757
}
758+
return null;
759+
}
706760

707-
// Just display in the active viewport
708-
return {
709-
viewportId: activeViewportId,
710-
displaySetInstanceUID,
711-
viewportOptions: { viewportType },
712-
};
761+
/**
762+
* Find a viewport with the closest orientation but on a different display set.
763+
*/
764+
public findViewportWithMatchingOrientation(metadata, displaySetInstanceUID, viewportType) {
765+
const viewportAlignmentData = this.getViewportAlignmentData(metadata);
766+
if (viewportAlignmentData?.length) {
767+
return {
768+
...viewportAlignmentData[0],
769+
displaySetInstanceUID,
770+
viewportOptions: { viewportType },
771+
};
772+
}
773+
return null;
713774
}
714775

715776
/**

extensions/measurement-tracking/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
"start": "yarn run dev"
3333
},
3434
"peerDependencies": {
35-
"@cornerstonejs/core": "4.14.4",
36-
"@cornerstonejs/tools": "4.14.4",
35+
"@cornerstonejs/core": "4.14.5",
36+
"@cornerstonejs/tools": "4.14.5",
3737
"@ohif/core": "3.12.0-beta.109",
3838
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.109",
3939
"@ohif/extension-default": "3.12.0-beta.109",

extensions/usAnnotation/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
},
4343
"dependencies": {
4444
"@babel/runtime": "7.28.2",
45-
"@cornerstonejs/core": "4.14.4",
46-
"@cornerstonejs/tools": "4.14.4",
45+
"@cornerstonejs/core": "4.14.5",
46+
"@cornerstonejs/tools": "4.14.5",
4747
"@ohif/core": "3.12.0-beta.109",
4848
"@ohif/extension-cornerstone": "3.12.0-beta.109",
4949
"@ohif/extension-default": "3.12.0-beta.109",

modes/usAnnotation/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
},
3535
"dependencies": {
3636
"@babel/runtime": "7.28.2",
37-
"@cornerstonejs/core": "4.14.4",
38-
"@cornerstonejs/tools": "4.14.4",
37+
"@cornerstonejs/core": "4.14.5",
38+
"@cornerstonejs/tools": "4.14.5",
3939
"@ohif/core": "3.12.0-beta.109",
4040
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.109",
4141
"@ohif/extension-ultrasound-pleura-bline": "3.12.0-beta.109",

0 commit comments

Comments
 (0)