Repository: OHIF/Viewers
Pull request #4779: fix(measurement): fix for measurement jump is not switching series in 2DMPR
Author: login: arul-trenser
Created: 2025-02-12T09:18:34Z | Merged: 2025-03-28T14:14:19Z

<!-- Do Not Delete This! pr_template -->
<!-- Please read our Rules of Conduct: https://github.com/OHIF/Viewers/blob/master/CODE_OF_CONDUCT.md -->
<!-- 🕮 Read our guide about our Contributing Guide here https://docs.ohif.org/development/contributing -->
<!-- :hand: Thank you for starting this amazing contribution! -->

<!--
⚠️⚠️ Please make sure the checklist section below is complete before submitting your PR.
To complete the checklist, add an 'x' to each item: [] -> [x]
(PRs that do not have all the checkboxes marked will not be approved)
-->

### Context

<!--
Provide a clear explanation of the reasoning behind this change, such as:
- A link to the issue being addressed, using the format "Fixes #ISSUE_NUMBER"
- An image showing the issue or problem being addressed (if not already in the issue)
- Error logs or callStacks to help with the understanding of the problem (if not already in the issue)
-->

- Fixes #4751 
- It also fix the issue that, on common layout while clicking on tracked measurement of different series, the viewport will update the series for the measurement but the jump to slice is not working.
- This PR has been incorporated by [FlyWheel.io](https://flywheel.io/).

https://github.com/user-attachments/assets/1833f35f-1a2a-4477-9e22-fedcb940e3ea


### Changes & Results

<!--
List all the changes that have been done, such as:
- Add new components
- Remove old components
- Update dependencies

What are the effects of this change?
- Before vs After
- Screenshots / GIFs / Videos
-->

- To fix issue #4751, Added checking whether the found viewport have measurement displaySetInstanceUID and referencedImageId in JUMP_TO_MEASUREMENT_VIEWPORT event.
- To fix issue 

 > on common layout while clicking on tracked measurement of different series, the viewport will update the series for the measurement but the jump to slice is not working
 
 _checkForCachedJumpToMeasurementEvents function has been called, if we have measurement during loadViewportData.

https://github.com/user-attachments/assets/046c062f-e174-49c0-8996-dbde717a1bcc


### Testing

<!--
Describe how we can test your changes.
- open a URL
- visit a page
- click on a button
- etc.
-->

### Checklist

#### PR

<!--
https://semantic-release.gitbook.io/semantic-release/#how-does-it-work

Examples:
Please note the letter casing in the provided examples (upper or lower).

- feat(MeasurementService): add ...
- fix(Toolbar): fix ...
- docs(Readme): update ...
- style(Whitespace): fix ...
- refactor(ExtensionManager): ...
- test(HangingProtocol): Add test ...
- chore(git): update ...
- perf(VolumeLoader): ...

You don't need to have each commit within the Pull Request follow the rule,
but the PR title must comply with it, as it will be used as the commit message
after the commits are squashed.
-->

- [x] My Pull Request title is descriptive, accurate and follows the
  semantic-release format and guidelines.

#### Code

- [x] My code has been well-documented (function documentation, inline comments,
  etc.)

#### Public Documentation Updates

<!-- https://docs.ohif.org/ -->

- [] The documentation page has been updated as necessary for any public API
  additions or removals.

#### Tested Environment

- [x] OS: <!--[e.g. Windows 10, macOS 10.15.4]-->Windows 11
- [x] Node version: <!--[e.g. 18.16.1]-->18.19.0
- [x] Browser:
  <!--[e.g. Chrome 83.0.4103.116, Firefox 77.0.1, Safari 13.1.1]--> Chrome 131.0.6778.265

<!-- prettier-ignore-start -->
[blog]: https://circleci.com/blog/triggering-trusted-ci-jobs-on-untrusted-forks/
[script]: https://github.com/jklukas/git-push-fork-to-upstream-branch
<!-- prettier-ignore-end -->


Linked issue #4751: [Bug]  Measurement Selection Not Switching Series in 2D MPR View
### Describe the Bug

When an annotation is created in the first series, then the second series is loaded, clicking on the measurement of the first series in the measurement panel does not switch back to the corresponding series in the 2D MPR view.
It is also noted that if two series in a session have different FrameOfReferenceUID, the series switches as expected when clicking a measurement from the measurement panel

https://github.com/user-attachments/assets/f11806aa-8816-43c1-9108-0cb7d74b894a

### Steps to Reproduce

1. Launch a multi-series study with the same FrameOfReferenceUID.
2. Draw an annotation on the first series.
3. Navigate to MPR mode.
4. Drag and drop the second series into MPR mode.
5. Click on the annotation of the first series from the measurement panel.
6. Observe the viewport - the series does not switch back to the first series.

### The current behavior

The series is not switching; instead, the annotation appears as a dashed line in the currently loaded series

### The expected behavior

When clicking on a measurement, the corresponding series should load and navigate to the slice containing the annotation

### OS

Windows 11

### Node version

20

### Browser

Chrome

Diff:
diff --git a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
index d7570200d56..8045793c18a 100644
--- a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
+++ b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
@@ -264,11 +264,10 @@ const OHIFCornerstoneViewport = React.memo(
         const presentations = getViewportPresentations(viewportId, viewportOptions);
 
         let measurement;
-        if (cacheJumpToMeasurementEvent?.viewportId === viewportId) {
+        if (cacheJumpToMeasurementEvent?.cornerstoneViewport === viewportId) {
           measurement = cacheJumpToMeasurementEvent.measurement;
           // Delete the position presentation so that viewport navigates direct
           presentations.positionPresentation = null;
-          cacheJumpToMeasurementEvent = null;
         }
 
         // Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component
@@ -281,7 +280,7 @@ const OHIFCornerstoneViewport = React.memo(
           viewportOptions.needsRerendering = false;
         }
 
-        cornerstoneViewportService.setViewportData(
+        await cornerstoneViewportService.setViewportData(
           viewportId,
           viewportData,
           viewportOptions,
@@ -290,7 +289,13 @@ const OHIFCornerstoneViewport = React.memo(
         );
 
         if (measurement) {
-          cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
+          _checkForCachedJumpToMeasurementEvents(
+            elementRef,
+            viewportId,
+            displaySets,
+            servicesManager
+          );
+          cacheJumpToMeasurementEvent = null;
         }
       };
 
@@ -318,8 +323,6 @@ const OHIFCornerstoneViewport = React.memo(
         servicesManager
       );
 
-      _checkForCachedJumpToMeasurementEvents(elementRef, viewportId, displaySets, servicesManager);
-
       return () => {
         unsubscribeFromJumpToMeasurementEvents();
       };
@@ -451,6 +454,16 @@ function _subscribeToJumpToMeasurementEvents(elementRef, viewportId, servicesMan
       if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) {
         return;
       }
+      const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
+      // Confirm viewportInfo matches measurement displaySetInstanceUID and referencedImageId
+      if (
+        !viewportInfo?.contains(
+          measurement.displaySetInstanceUID,
+          measurement.referencedImageId || measurement.metadata?.referencedImageId
+        )
+      ) {
+        return;
+      }
       _jumpToMeasurement(measurement, elementRef, viewportId, servicesManager);
     }
   );
diff --git a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts
index 82f7fbb037e..bc3800928a6 100644
--- a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts
+++ b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts
@@ -372,7 +372,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
    * @param publicDisplaySetOptions - The public display set options.
    * @param presentations - The presentations to set.
    */
-  public setViewportData(
+  public async setViewportData(
     viewportId: string,
     viewportData: StackViewportData | VolumeViewportData,
     publicViewportOptions: PublicViewportOptions,
@@ -458,21 +458,14 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
     this.viewportsById.set(viewportId, viewportInfo);
 
     const viewport = renderingEngine.getViewport(viewportId);
-    const displaySetPromise = this._setDisplaySets(
-      viewport,
-      viewportData,
-      viewportInfo,
-      presentations
-    );
+    await this._setDisplaySets(viewport, viewportData, viewportInfo, presentations);
 
     // The broadcast event here ensures that listeners have a valid, up to date
     // viewport to access.  Doing it too early can result in exceptions or
     // invalid data.
-    displaySetPromise.then(() => {
-      this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
-        viewportData,
-        viewportId,
-      });
+    this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
+      viewportData,
+      viewportId,
     });
   }
 
diff --git a/platform/app/src/components/ViewportGrid.tsx b/platform/app/src/components/ViewportGrid.tsx
index 53f8b224253..b00974fdc96 100644
--- a/platform/app/src/components/ViewportGrid.tsx
+++ b/platform/app/src/components/ViewportGrid.tsx
@@ -6,6 +6,7 @@ import { useViewportGrid } from '@ohif/ui-next';
 import EmptyViewport from './EmptyViewport';
 import classNames from 'classnames';
 import { useAppConfig } from '@state';
+import getClosestOrientationFromIOP from '../utils/getClosestOrientationFromIOP';
 
 function ViewerViewportGrid(props: withAppTypes) {
   const { servicesManager, viewportComponents = [], dataSource } = props;
@@ -177,7 +178,20 @@ function ViewerViewportGrid(props: withAppTypes) {
         const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = measurement;
 
         const updatedViewports = _getUpdatedViewports(viewportId, referencedDisplaySetInstanceUID);
-        if (!updatedViewports[0]) {
+        let viewportToUpdate;
+        if (updatedViewports.length > 1) {
+          // To get the viewport which orientation is closest to IOP
+          const closestOrientation = getClosestOrientationFromIOP(
+            displaySetService,
+            referencedDisplaySetInstanceUID
+          );
+          viewportToUpdate = updatedViewports.find(
+            viewport => viewport.viewportOptions?.orientation == closestOrientation
+          );
+        }
+        // If closest orientation viewport is not find then choose 0
+        viewportToUpdate = updatedViewports[0];
+        if (!viewportToUpdate) {
           console.warn(
             'ViewportGrid::Unable to navigate to viewport containing',
             referencedDisplaySetInstanceUID
@@ -185,14 +199,9 @@ function ViewerViewportGrid(props: withAppTypes) {
           return;
         }
 
-        // Arbitrarily assign the viewport to element 0
-        // TODO - this should perform a search to find the most suitable viewport.
-        updatedViewports[0] = { ...updatedViewports[0] };
-        const [viewport] = updatedViewports;
-
         // Copy the viewport options to prevent modifying the internal data
-        viewport.viewportOptions = {
-          ...viewport.viewportOptions,
+        viewportToUpdate.viewportOptions = {
+          ...viewportToUpdate.viewportOptions,
           orientation: 'acquisition',
           // The preferred way to jump to the measurement view is to set the
           // view reference, as this can hold information such as the orientation
diff --git a/platform/app/src/utils/getClosestOrientationFromIOP.ts b/platform/app/src/utils/getClosestOrientationFromIOP.ts
new file mode 100644
index 00000000000..735b9ba5767
--- /dev/null
+++ b/platform/app/src/utils/getClosestOrientationFromIOP.ts
@@ -0,0 +1,59 @@
+import { vec3 } from 'gl-matrix';
+import { Enums } from '@cornerstonejs/core';
+import OrientationAxis = Enums.OrientationAxis;
+
+/**
+ * Get the plane (orientation) to which the ImageOrientationPatient is most closely aligned
+ *
+ * @param displaySetService
+ * @param displaySetInstanceUID
+ * @returns orientation
+ */
+export default function getClosestOrientationFromIOP(
+  displaySetService,
+  displaySetInstanceUID
+): OrientationAxis {
+  const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
+  const imageOrientationPatient = displaySet.instances[0].ImageOrientationPatient as Array<number>;
+  // ImageOrientationPatient must be an array of length 6.
+  if (imageOrientationPatient?.length !== 6) {
+    return;
+  }
+
+  // Take cross product to get vector coming "out" of image plane
+  const rowCosineVec = vec3.fromValues(
+    imageOrientationPatient[0],
+    imageOrientationPatient[1],
+    imageOrientationPatient[2]
+  );
+  const colCosineVec = vec3.fromValues(
+    imageOrientationPatient[3],
+    imageOrientationPatient[4],
+    imageOrientationPatient[5]
+  );
+  const scanAxisNormal = vec3.cross(vec3.create(), rowCosineVec, colCosineVec);
+
+  // Define the reference vectors for axial, coronal, and sagittal planes
+  const unitVectors = {
+    [OrientationAxis.AXIAL]: vec3.fromValues(0, 0, 1),
+    [OrientationAxis.CORONAL]: vec3.fromValues(0, 1, 0),
+    [OrientationAxis.SAGITTAL]: vec3.fromValues(1, 0, 0),
+  };
+
+  // Compute dot products for each reference plane
+  // Because all vectors are normalized, dot product is bounded between -1 and 1
+  let maxDot = 0;
+  let maxOrientation: string = '';
+  for (const [k, v] of Object.entries(unitVectors)) {
+    // Absolute value of dot product because we only care about alignment with the axis
+    // For example, dot product of -1 for a given axis means perfect alignment
+    // but the image is pointing in the "opposite" direction
+    const res = Math.abs(vec3.dot(scanAxisNormal, v));
+    if (res > maxDot) {
+      maxDot = res;
+      maxOrientation = k;
+    }
+  }
+
+  return maxOrientation as OrientationAxis;
+}


Reviews:
user: login: sedghi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-21T15:27:18Z
user: login: arul-trenser
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-25T07:51:30Z
user: login: sedghi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-25T16:51:01Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This pull request addresses issue #4751 by modifying the measurement jump functionality in 2DMPR, ensuring that the viewport switches series correctly when a tracked measurement is selected. Key changes include:
- A new utility function to determine the closest orientation based on the ImageOrientationPatient data.
- Updates in the ViewportGrid component to select the most suitable viewport using the closest orientation.
- Adjustments in the Cornerstone viewport service and component for asynchronous handling of viewport data and jump-to-measurement events.

### Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

| File                                                                               | Description                                                                     |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| platform/app/src/utils/getClosestOrientationFromIOP.ts                           | New utility function to compute closest orientation from IOP.                   |
| platform/app/src/components/ViewportGrid.tsx                                       | Updated viewport selection logic to use the computed orientation.               |
| extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts   | Changed setViewportData to an async function.                                   |
| extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx                     | Modified measurement jump functionality and adjusted async handling.            |




state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T14:11:36Z

Review comments:
diff_hunk: @@ -286,7 +285,13 @@ const OHIFCornerstoneViewport = React.memo(
         );
 
         if (measurement) {
-          cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
+          _checkForCachedJumpToMeasurementEvents(
path: extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
user: login: sedghi
body: Where did `setAnnotationSelected` go?

created_at: 2025-02-21T15:27:18Z
author_association: MEMBER
diff_hunk: @@ -286,7 +285,13 @@ const OHIFCornerstoneViewport = React.memo(
         );
 
         if (measurement) {
-          cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
+          _checkForCachedJumpToMeasurementEvents(
path: extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
user: login: arul-trenser
body: @sedghi  The `setAnnotationSelected` function is invoked by `_jumpToMeasurement` https://github.com/OHIF/Viewers/blob/72d6e8d36e4720ff61a66acb857ff640f5714815/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx#L527, which itself is called by `_checkForCachedJumpToMeasurementEvents` https://github.com/OHIF/Viewers/blob/72d6e8d36e4720ff61a66acb857ff640f5714815/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx#L497.
created_at: 2025-02-25T07:51:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -452,6 +455,15 @@ function _subscribeToJumpToMeasurementEvents(elementRef, viewportId, servicesMan
       if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) {
         return;
       }
+      const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
+      if (
+        !viewportInfo?.contains(
path: extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
user: login: sedghi
body: can you add a comment on top this to explain what is this contains doing
created_at: 2025-02-25T16:51:01Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,59 @@
+import { vec3 } from 'gl-matrix';
+import { Enums } from '@cornerstonejs/core';
+import OrientationAxis = Enums.OrientationAxis;
+
+/**
+ * Get the plane (orientation) to which the ImageOrientationPatient is most closely aligned
+ *
+ * @param displaySetService
+ * @param displaySetInstanceUID
+ * @returns orientation
+ */
+export default function getClosestOrientationFromIOP(
+  displaySetService,
+  displaySetInstanceUID
+): OrientationAxis {
+  const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
+  const imageOrientationPatient = displaySet.instances[0].ImageOrientationPatient as Array<number>;
+  // ImageOrientationPatient must be an array of length 6.
+  if (imageOrientationPatient?.length !== 6) {
+    return;
path: platform/app/src/utils/getClosestOrientationFromIOP.ts
user: login: Copilot
body: The function returns undefined when imageOrientationPatient is not of length 6, which is inconsistent with the declared return type OrientationAxis. Consider returning a default OrientationAxis value or throwing an error to avoid potential undefined behavior.
```suggestion
    throw new Error('ImageOrientationPatient must be an array of length 6.');
```
created_at: 2025-03-28T14:11:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,22 +178,30 @@ function ViewerViewportGrid(props: withAppTypes) {
         const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = measurement;
 
         const updatedViewports = _getUpdatedViewports(viewportId, referencedDisplaySetInstanceUID);
-        if (!updatedViewports[0]) {
+        let viewportToUpdate;
+        if (updatedViewports.length > 1) {
+          // To get the viewport which orientation is closest to IOP
+          const closestOrientation = getClosestOrientationFromIOP(
+            displaySetService,
+            referencedDisplaySetInstanceUID
+          );
+          viewportToUpdate = updatedViewports.find(
+            viewport => viewport.viewportOptions?.orientation == closestOrientation
+          );
+        }
+        // If closest orientation viewport is not find then choose 0
+        viewportToUpdate = updatedViewports[0];
path: platform/app/src/components/ViewportGrid.tsx
user: login: Copilot
body: This assignment overwrites the result of the closest orientation search, nullifying the conditional selection. Consider assigning updatedViewports[0] only if no matching viewport is found.
```suggestion
        // If closest orientation viewport is not found then choose 0
        if (!viewportToUpdate) {
          viewportToUpdate = updatedViewports[0];
        }
```
created_at: 2025-03-28T14:11:36Z
author_association: CONTRIBUTOR

PR comments:
user: login: netlify[bot]
created_at: 2025-02-12T09:18:55Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *ohif-dev* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit |  |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/ohif-dev/deploys/67e6932148374c0e856967ee |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-4779--ohif-dev.netlify.app](https://deploy-preview-4779--ohif-dev.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTQ3NzktLW9oaWYtZGV2Lm5ldGxpZnkuYXBwIn0.4xwIjhrRxFiUfStoClBHTjFAdX_c4KR36sfhTqcyBUM)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [ohif-dev Preview](https://deploy-preview-4779--ohif-dev.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/ohif-dev/configuration/notifications#deploy-webhooks)._
author_association: NONE
user: login: netlify[bot]
created_at: 2025-02-12T09:18:57Z
body: ### <span aria-hidden="true">❌</span> Deploy Preview for *ohif-platform-docs* failed. **[Why did it fail? →](https://app.netlify.com/sites/ohif-platform-docs/deploys/67dbf75b61c4810008e5a2b1?deploy_diagnostics=true)**


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 4937006a71e69c796e540297ed8503c953f66fe1 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/ohif-platform-docs/deploys/67dbf75b61c4810008e5a2b1 |
author_association: NONE
user: login: sedghi
created_at: 2025-02-18T23:03:58Z
body: I'm not able to reproduce the bug in our master branch, are you sure it is happening here https://viewer-dev.ohif.org/viewer?StudyInstanceUIDs=1.2.840.113619.2.290.3.3767434740.226.1600859119.501
author_association: MEMBER
user: login: arul-trenser
created_at: 2025-02-19T05:29:43Z
body: @sedghi We are still able to reproduce the issues in [https://viewer-dev.ohif.org/ ](https://viewer-dev.ohif.org/ ). 

> on common layout while clicking on tracked measurement of different series, the viewport will update the series for the measurement but the jump to slice is not working.

Previous in v3.8, while clicking on measurement in measurement panel, the viewport will load corresponding series and navigates to corresponding slice.
Currently in master and in v3.9, while clicking on measurement in panel, on first click the viewport is loaded with corresponding series and on second click the navigation to corresponding slice is happening.

https://github.com/user-attachments/assets/dd2daf6e-76a7-42df-af46-be12e91aef08

Issue 2: https://github.com/OHIF/Viewers/issues/4751 is also reproduceable in current main.

https://github.com/user-attachments/assets/91959f36-ec6c-4e09-b8d6-4c3f3fcc1da5


author_association: CONTRIBUTOR
user: login: sedghi
created_at: 2025-03-03T14:49:26Z
body: I tried this, this has a strange side effect 
Try on [here](https://deploy-preview-4779--ohif-dev.netlify.app/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5)

The hanging protocol when mounting the measurement series does NOT correctly place orientations for some reason

https://github.com/user-attachments/assets/55de71c0-85c3-41ad-a7b0-bc3535a49c02


author_association: MEMBER
user: login: arul-trenser
created_at: 2025-03-07T06:11:07Z
body: @sedghi  It appears that the root cause for the above scenario is https://github.com/OHIF/Viewers/blob/master/platform/app/src/components/ViewportGrid.tsx#L189-L190.

In JUMP_TO_MEASUREMENT_LAYOUT, the first viewport is selected to jump to that measurement. Instead, we should select the viewport with the to which the `ImageOrientationPatient` is most closely aligned.
author_association: CONTRIBUTOR
user: login: sedghi
created_at: 2025-03-07T13:42:58Z
body: can you try it to fix it?
author_association: MEMBER
user: login: arul-trenser
created_at: 2025-03-13T08:30:29Z
body: @sedghi I have fixed the issue mentioned in [above comment](https://github.com/OHIF/Viewers/pull/4779#issuecomment-2694653106). Could you please look into it?
author_association: CONTRIBUTOR
user: login: sedghi
created_at: 2025-03-18T04:04:55Z
body: I have created this PR https://github.com/OHIF/Viewers/pull/4867 which solves the issue, please double check
author_association: MEMBER
user: login: arul-trenser
created_at: 2025-03-18T10:07:08Z
body: > I have created this PR #4867 which solves the issue, please double check

@sedghi 
In PR #4779 we have resolved 3 issues
Issue 1: When an annotation is created in the first series, then the second series is loaded, clicking on the measurement of the first series in the measurement panel, the viewport will to switch corresponding series; however, the transition to the corresponding slice does not occur. 
Issue 2: #4751 
Issue 3: After resolving issue 2, we encountered a subsequent issue as noted in the [comment](https://github.com/OHIF/Viewers/pull/4779#issuecomment-2694653106).

In PR #4867, Issue 1 is partially fixed and other two are not fixed.
Issue 1: As part of PR #4867, When an annotation is created in the first series, then the second series is loaded, clicking on the measurement of the first series in the measurement panel, the viewport will to switch corresponding series and jump to the corresponding slice; however the specific annotation is not highlighted.

https://github.com/user-attachments/assets/fa856ae0-68bb-4966-a841-29b4b1075720

Issue 2: This issue is not fixed

https://github.com/user-attachments/assets/c46171de-f00f-4429-9c23-a20d5d39758d

Issue 3: As this is subsequent issue that occurs once the issue 2 (#4751) is resolved, we are not able to reproduce it in the PR #4867; however the root cause of this issue is while updating the series in the series; we consistently choose the first viewport instead of selecting the viewport that has the most closest alignment with the `ImageOrientationPatient`. This problem has been addressed in this PR; however it still persists in PR #4867.
author_association: CONTRIBUTOR
user: login: arul-trenser
created_at: 2025-03-18T15:08:32Z
body: @sedghi  While checking this we found one more issue which is not handled in this PR also not resolved in #4867.
**Issue: The slice switch for annotation is not happening in MPR mode.** 
Steps to Reproduce:
1. Launch the study in viewer
2. Drag and drop the SR report
3. Switch to MPR mode
4. Now click on the measurement

https://github.com/user-attachments/assets/d2742247-0f5c-4155-adda-817ea1e6e276



author_association: CONTRIBUTOR
user: login: nithin-trenser
created_at: 2025-03-18T15:51:13Z
body: > @sedghi While checking this we found one more issue which is not handled in this PR also not resolved in #4867. **Issue: With `disableConfirmationPrompts: true` config, the slice switch for annotation is not happening in MPR mode.** Steps to Reproduce:
> 
> 1. Launch the study in viewer
> 2. Drag and drop the SR report
> 3. Switch to MPR mode
> 4. Now click on the measurement
>    We observe that slice jumping is not happening in MPR mode with `disableConfirmationPrompts: true` config.
> 
>  MPR_SR_Jump_issue.mp4

@sedghi 

This issue occurs regardless of the `disableConfirmationPrompts` config. The issue arises when a measurement is added from a Structured Report, the `cameraFocalPoint` is absent from the measurement data. For a volume viewport, this value is used to set the camera position (Jump to slice) from [here](https://github.com/nithin-trenser/cornerstone3D/blob/main/packages/core/src/RenderingEngine/BaseVolumeViewport.ts#L751-L765).

When an annotation is drawn in viewport, the view reference is saved along with the metadata https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.ts#L186-L211. However, when an annotation is added via the SR report, the view reference is not included with the annotation metadata (the view reference holds the `cameraFocalPoint`.   In this scenario, the annotation data is generated from [here](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.ts#L186-L194).

Could you please suggest what would be the potential fix for this issue?
author_association: CONTRIBUTOR
user: login: sedghi
created_at: 2025-03-25T14:31:17Z
body: @nithin-trenser I've updated https://github.com/OHIF/Viewers/pull/4867 and addressed the issue using the `referenceViewable` APIs. Could you please check it out and see if any of the previously fixed bugs still persist?

author_association: MEMBER
user: login: Devu-trenser
created_at: 2025-03-26T08:15:07Z
body: > @nithin-trenser I've updated #4867 and addressed the issue using the `referenceViewable` APIs. Could you please check it out and see if any of the previously fixed bugs still persist?

@sedghi The previously reported bugs are not reproducible in [#4867](https://github.com/OHIF/Viewers/pull/4867). 

However, I have **identified a minor issue**: when clicking a measurement from another series in the measurement panel, the selected measurement is not initially highlighted. The series and slice transition as expected. A video is attached below
**Also the issue mentioned in [this comment](https://github.com/OHIF/Viewers/pull/4779#issuecomment-2733602616) is not resolved yet**. (This issue occurs regardless of the disableConfirmationPrompts config. )


Notes: 

1. The highlight appears after the second click
2. Reproducible in the MPR mode also

Steps to reproduce:

1. Launch a multi-series study
3. Draw an annotation on the first series.
4. Open the measurement panel.
5. Switch to any other series
6. Click on a measurement that drawn in the first series. 
7. The series and slice will switch correctly, but the measurement will not be highlighted in the viewport.

https://github.com/user-attachments/assets/d39c1e4b-b116-448e-80a4-18d8df63786d







author_association: CONTRIBUTOR
user: login: sedghi
created_at: 2025-03-27T22:31:14Z
body: Ok i fixed everything, check it out
author_association: MEMBER
user: login: arul-trenser
created_at: 2025-03-28T11:49:06Z
body: @sedghi  The issue mentioned in the [comment](https://github.com/OHIF/Viewers/pull/4779#issuecomment-2753553505) is still reproducible in [in PR 4867](https://deploy-preview-4867--ohif-dev.netlify.app/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5). Also, the issue mention in the [comment](https://github.com/OHIF/Viewers/pull/4779#issuecomment-2733602616) is not fixed. I have attached the video for reference.

**Issue 1: Highlight is not working on layout switch** - This is fixed as part of #4779 PR
Steps to reproduce:
1. Launch a multi series study
2. Draw annotation for two different series and track them
3. On the measurement panel, click on the annotation that is not drawn for the series that is currently loaded in the viewport
4. Observe the viewport, Series is changed and slice switch also happening; however the annotation is not selected both in viewport and in measurement panel
5. For 2D MPR mode only Series is changed and slice switch is not happening.

Root cause: [Annotation selection](https://github.com/OHIF/Viewers/pull/4867/files#diff-f646a4a0577bed86941659617be4626dc01a64c4ceb204b6bed0e2e1baad59d8R454) has been handled only for `JUMP_TO_MEASUREMENT_VIEWPORT` and not handled for event `JUMP_TO_MEASUREMENT_LAYOUT`


https://github.com/user-attachments/assets/6aa895ca-432d-4a33-b8f8-1c116d0773eb


**Issue 2: The slice switch for annotation is not happening in MPR mode.** - This is not fixed as part of #4779 PR
Steps to Reproduce:
1. Launch the study in viewer
2. Drag and drop the SR report
3. Switch to MPR mode
4. Now click on the measurement

Root cause: when a measurement is added from a Structured Report, the `cameraFocalPoint` is absent from the measurement data. For a volume viewport, this value is used to set the camera position (Jump to slice) from [here](https://github.com/nithin-trenser/cornerstone3D/blob/main/packages/core/src/RenderingEngine/BaseVolumeViewport.ts#L751-L765).

When an annotation is drawn in viewport, the view reference is saved along with the metadata https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.ts#L186-L211. However, when an annotation is added via the SR report, the view reference is not included with the annotation metadata (the view reference holds the `cameraFocalPoint`. In this scenario, the annotation data is generated from [here](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.ts#L186-L194).


https://github.com/user-attachments/assets/8e047066-fc85-4dcb-bdbf-2a033b267deb


author_association: CONTRIBUTOR
user: login: sedghi
created_at: 2025-03-28T12:20:26Z
body: I fixed the camera focal point yesterday in cornerstone https://github.com/cornerstonejs/cornerstone3D/pull/1950 let me see what is going on
author_association: MEMBER
user: login: sedghi
created_at: 2025-03-28T12:23:10Z
body: It works locally for me! maybe it is some weird netlify cache. Try locally
@arul-trenser 
author_association: MEMBER
user: login: arul-trenser
created_at: 2025-03-28T14:09:42Z
body: @sedghi  Checked locally, issues got fixed
Thanks!
author_association: CONTRIBUTOR
user: login: sedghi
created_at: 2025-03-28T14:19:05Z
body: I merged by mistake and reverted after FYI
author_association: MEMBER