fix(measurement): fix for measurement jump is not switching series in 2DMPR (#4779)
This commit is contained in:
parent
951f95ef0a
commit
3f06c966eb
@ -264,11 +264,10 @@ const OHIFCornerstoneViewport = React.memo(
|
|||||||
const presentations = getViewportPresentations(viewportId, viewportOptions);
|
const presentations = getViewportPresentations(viewportId, viewportOptions);
|
||||||
|
|
||||||
let measurement;
|
let measurement;
|
||||||
if (cacheJumpToMeasurementEvent?.viewportId === viewportId) {
|
if (cacheJumpToMeasurementEvent?.cornerstoneViewport === viewportId) {
|
||||||
measurement = cacheJumpToMeasurementEvent.measurement;
|
measurement = cacheJumpToMeasurementEvent.measurement;
|
||||||
// Delete the position presentation so that viewport navigates direct
|
// Delete the position presentation so that viewport navigates direct
|
||||||
presentations.positionPresentation = null;
|
presentations.positionPresentation = null;
|
||||||
cacheJumpToMeasurementEvent = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component
|
// 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;
|
viewportOptions.needsRerendering = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
cornerstoneViewportService.setViewportData(
|
await cornerstoneViewportService.setViewportData(
|
||||||
viewportId,
|
viewportId,
|
||||||
viewportData,
|
viewportData,
|
||||||
viewportOptions,
|
viewportOptions,
|
||||||
@ -290,7 +289,13 @@ const OHIFCornerstoneViewport = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (measurement) {
|
if (measurement) {
|
||||||
cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
|
_checkForCachedJumpToMeasurementEvents(
|
||||||
|
elementRef,
|
||||||
|
viewportId,
|
||||||
|
displaySets,
|
||||||
|
servicesManager
|
||||||
|
);
|
||||||
|
cacheJumpToMeasurementEvent = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -318,8 +323,6 @@ const OHIFCornerstoneViewport = React.memo(
|
|||||||
servicesManager
|
servicesManager
|
||||||
);
|
);
|
||||||
|
|
||||||
_checkForCachedJumpToMeasurementEvents(elementRef, viewportId, displaySets, servicesManager);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribeFromJumpToMeasurementEvents();
|
unsubscribeFromJumpToMeasurementEvents();
|
||||||
};
|
};
|
||||||
@ -451,6 +454,16 @@ function _subscribeToJumpToMeasurementEvents(elementRef, viewportId, servicesMan
|
|||||||
if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) {
|
if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) {
|
||||||
return;
|
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);
|
_jumpToMeasurement(measurement, elementRef, viewportId, servicesManager);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -372,7 +372,7 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
|||||||
* @param publicDisplaySetOptions - The public display set options.
|
* @param publicDisplaySetOptions - The public display set options.
|
||||||
* @param presentations - The presentations to set.
|
* @param presentations - The presentations to set.
|
||||||
*/
|
*/
|
||||||
public setViewportData(
|
public async setViewportData(
|
||||||
viewportId: string,
|
viewportId: string,
|
||||||
viewportData: StackViewportData | VolumeViewportData,
|
viewportData: StackViewportData | VolumeViewportData,
|
||||||
publicViewportOptions: PublicViewportOptions,
|
publicViewportOptions: PublicViewportOptions,
|
||||||
@ -458,21 +458,14 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
|||||||
this.viewportsById.set(viewportId, viewportInfo);
|
this.viewportsById.set(viewportId, viewportInfo);
|
||||||
|
|
||||||
const viewport = renderingEngine.getViewport(viewportId);
|
const viewport = renderingEngine.getViewport(viewportId);
|
||||||
const displaySetPromise = this._setDisplaySets(
|
await this._setDisplaySets(viewport, viewportData, viewportInfo, presentations);
|
||||||
viewport,
|
|
||||||
viewportData,
|
|
||||||
viewportInfo,
|
|
||||||
presentations
|
|
||||||
);
|
|
||||||
|
|
||||||
// The broadcast event here ensures that listeners have a valid, up to date
|
// 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
|
// viewport to access. Doing it too early can result in exceptions or
|
||||||
// invalid data.
|
// invalid data.
|
||||||
displaySetPromise.then(() => {
|
this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
|
||||||
this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, {
|
viewportData,
|
||||||
viewportData,
|
viewportId,
|
||||||
viewportId,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useViewportGrid } from '@ohif/ui-next';
|
|||||||
import EmptyViewport from './EmptyViewport';
|
import EmptyViewport from './EmptyViewport';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useAppConfig } from '@state';
|
import { useAppConfig } from '@state';
|
||||||
|
import getClosestOrientationFromIOP from '../utils/getClosestOrientationFromIOP';
|
||||||
|
|
||||||
function ViewerViewportGrid(props: withAppTypes) {
|
function ViewerViewportGrid(props: withAppTypes) {
|
||||||
const { servicesManager, viewportComponents = [], dataSource } = props;
|
const { servicesManager, viewportComponents = [], dataSource } = props;
|
||||||
@ -177,7 +178,20 @@ function ViewerViewportGrid(props: withAppTypes) {
|
|||||||
const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = measurement;
|
const { displaySetInstanceUID: referencedDisplaySetInstanceUID } = measurement;
|
||||||
|
|
||||||
const updatedViewports = _getUpdatedViewports(viewportId, referencedDisplaySetInstanceUID);
|
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(
|
console.warn(
|
||||||
'ViewportGrid::Unable to navigate to viewport containing',
|
'ViewportGrid::Unable to navigate to viewport containing',
|
||||||
referencedDisplaySetInstanceUID
|
referencedDisplaySetInstanceUID
|
||||||
@ -185,14 +199,9 @@ function ViewerViewportGrid(props: withAppTypes) {
|
|||||||
return;
|
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
|
// Copy the viewport options to prevent modifying the internal data
|
||||||
viewport.viewportOptions = {
|
viewportToUpdate.viewportOptions = {
|
||||||
...viewport.viewportOptions,
|
...viewportToUpdate.viewportOptions,
|
||||||
orientation: 'acquisition',
|
orientation: 'acquisition',
|
||||||
// The preferred way to jump to the measurement view is to set the
|
// The preferred way to jump to the measurement view is to set the
|
||||||
// view reference, as this can hold information such as the orientation
|
// view reference, as this can hold information such as the orientation
|
||||||
|
|||||||
59
platform/app/src/utils/getClosestOrientationFromIOP.ts
Normal file
59
platform/app/src/utils/getClosestOrientationFromIOP.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user