feat: Jump to measurement moves the camera if it is not in the viewport (#5023)
Co-authored-by: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Co-authored-by: Bill Wallace <wayfarer3130@gmail.com> Co-authored-by: Igor Octaviano <igoroctaviano@gmail.com>
This commit is contained in:
parent
16759506e5
commit
6fe9df6ec9
88
bun.lock
88
bun.lock
@ -41,7 +41,7 @@
|
||||
},
|
||||
"addOns/externals/devDependencies": {
|
||||
"name": "@externals/devDependencies",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
@ -126,14 +126,14 @@
|
||||
},
|
||||
"addOns/externals/dicom-microscopy-viewer": {
|
||||
"name": "@externals/dicom-microscopy-viewer",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"dicom-microscopy-viewer": "^0.48.6",
|
||||
},
|
||||
},
|
||||
"extensions/cornerstone": {
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.32.12",
|
||||
@ -159,8 +159,8 @@
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.32.12",
|
||||
"@ohif/core": "3.12.0-beta.7",
|
||||
"@ohif/ui": "3.12.0-beta.7",
|
||||
"@ohif/core": "3.12.0-beta.8",
|
||||
"@ohif/ui": "3.12.0-beta.8",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -172,7 +172,7 @@
|
||||
},
|
||||
"extensions/cornerstone-dicom-pmap": {
|
||||
"name": "@ohif/extension-cornerstone-dicom-pmap",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.32.12",
|
||||
@ -195,7 +195,7 @@
|
||||
},
|
||||
"extensions/cornerstone-dicom-rt": {
|
||||
"name": "@ohif/extension-cornerstone-dicom-rt",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"react-color": "^2.19.3",
|
||||
@ -215,7 +215,7 @@
|
||||
},
|
||||
"extensions/cornerstone-dicom-seg": {
|
||||
"name": "@ohif/extension-cornerstone-dicom-seg",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.32.12",
|
||||
@ -238,7 +238,7 @@
|
||||
},
|
||||
"extensions/cornerstone-dicom-sr": {
|
||||
"name": "@ohif/extension-cornerstone-dicom-sr",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.32.12",
|
||||
@ -247,10 +247,10 @@
|
||||
"classnames": "^2.3.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.12.0-beta.7",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.7",
|
||||
"@ohif/extension-measurement-tracking": "3.12.0-beta.7",
|
||||
"@ohif/ui": "3.12.0-beta.7",
|
||||
"@ohif/core": "3.12.0-beta.8",
|
||||
"@ohif/extension-cornerstone": "3.12.0-beta.8",
|
||||
"@ohif/extension-measurement-tracking": "3.12.0-beta.8",
|
||||
"@ohif/ui": "3.12.0-beta.8",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -260,7 +260,7 @@
|
||||
},
|
||||
"extensions/cornerstone-dynamic-volume": {
|
||||
"name": "@ohif/extension-cornerstone-dynamic-volume",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^3.32.12",
|
||||
@ -282,7 +282,7 @@
|
||||
},
|
||||
"extensions/default": {
|
||||
"name": "@ohif/extension-default",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/calculate-suv": "^1.1.0",
|
||||
@ -290,8 +290,8 @@
|
||||
"lodash.uniqby": "^4.7.0",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.12.0-beta.7",
|
||||
"@ohif/i18n": "3.12.0-beta.7",
|
||||
"@ohif/core": "3.12.0-beta.8",
|
||||
"@ohif/i18n": "3.12.0-beta.8",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicomweb-client": "^0.10.4",
|
||||
"prop-types": "^15.6.2",
|
||||
@ -305,7 +305,7 @@
|
||||
},
|
||||
"extensions/dicom-microscopy": {
|
||||
"name": "@ohif/extension-dicom-microscopy",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||
@ -330,7 +330,7 @@
|
||||
},
|
||||
"extensions/dicom-pdf": {
|
||||
"name": "@ohif/extension-dicom-pdf",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
@ -347,7 +347,7 @@
|
||||
},
|
||||
"extensions/dicom-video": {
|
||||
"name": "@ohif/extension-dicom-video",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
@ -364,20 +364,20 @@
|
||||
},
|
||||
"extensions/measurement-tracking": {
|
||||
"name": "@ohif/extension-measurement-tracking",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@ohif/ui": "3.12.0-beta.7",
|
||||
"@ohif/ui": "3.12.0-beta.8",
|
||||
"@xstate/react": "^3.2.2",
|
||||
"xstate": "^4.10.0",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/core": "^3.32.12",
|
||||
"@cornerstonejs/tools": "^3.32.12",
|
||||
"@ohif/core": "3.12.0-beta.7",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.7",
|
||||
"@ohif/extension-default": "3.12.0-beta.7",
|
||||
"@ohif/ui": "3.12.0-beta.7",
|
||||
"@ohif/core": "3.12.0-beta.8",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.12.0-beta.8",
|
||||
"@ohif/extension-default": "3.12.0-beta.8",
|
||||
"@ohif/ui": "3.12.0-beta.8",
|
||||
"classnames": "^2.3.2",
|
||||
"dcmjs": "0.43.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
@ -390,7 +390,7 @@
|
||||
},
|
||||
"extensions/test-extension": {
|
||||
"name": "@ohif/extension-test",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
@ -407,7 +407,7 @@
|
||||
},
|
||||
"extensions/tmtv": {
|
||||
"name": "@ohif/extension-tmtv",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
@ -424,7 +424,7 @@
|
||||
},
|
||||
"extensions/usAnnotation": {
|
||||
"name": "@ohif/extension-ultrasound-pleura-bline",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^3.32.12",
|
||||
@ -476,7 +476,7 @@
|
||||
},
|
||||
"modes/basic-dev-mode": {
|
||||
"name": "@ohif/mode-basic-dev-mode",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -496,7 +496,7 @@
|
||||
},
|
||||
"modes/basic-test-mode": {
|
||||
"name": "@ohif/mode-test",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -518,7 +518,7 @@
|
||||
},
|
||||
"modes/longitudinal": {
|
||||
"name": "@ohif/mode-longitudinal",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -541,7 +541,7 @@
|
||||
},
|
||||
"modes/microscopy": {
|
||||
"name": "@ohif/mode-microscopy",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -553,7 +553,7 @@
|
||||
},
|
||||
"modes/preclinical-4d": {
|
||||
"name": "@ohif/mode-preclinical-4d",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
},
|
||||
@ -572,7 +572,7 @@
|
||||
},
|
||||
"modes/segmentation": {
|
||||
"name": "@ohif/mode-segmentation",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -616,7 +616,7 @@
|
||||
},
|
||||
"modes/tmtv": {
|
||||
"name": "@ohif/mode-tmtv",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -637,7 +637,7 @@
|
||||
},
|
||||
"modes/usAnnotation": {
|
||||
"name": "@ohif/mode-ultrasound-pleura-bline",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^3.32.12",
|
||||
@ -676,7 +676,7 @@
|
||||
},
|
||||
"platform/app": {
|
||||
"name": "@ohif/app",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||
@ -744,7 +744,7 @@
|
||||
},
|
||||
"platform/cli": {
|
||||
"name": "@ohif/cli",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"bin": {
|
||||
"ohif-cli": "src/index.js",
|
||||
},
|
||||
@ -768,7 +768,7 @@
|
||||
},
|
||||
"platform/core": {
|
||||
"name": "@ohif/core",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"dcmjs": "0.43.1",
|
||||
@ -795,14 +795,14 @@
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/core": "^3.32.12",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.32.12",
|
||||
"@ohif/ui": "3.12.0-beta.7",
|
||||
"@ohif/ui": "3.12.0-beta.8",
|
||||
"cornerstone-math": "0.1.9",
|
||||
"dicom-parser": "^1.8.21",
|
||||
},
|
||||
},
|
||||
"platform/i18n": {
|
||||
"name": "@ohif/i18n",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next-locize-backend": "^2.0.0",
|
||||
@ -827,7 +827,7 @@
|
||||
},
|
||||
"platform/ui": {
|
||||
"name": "@ohif/ui",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@testing-library/react": "^13.1.0",
|
||||
"browser-detect": "^0.2.28",
|
||||
@ -887,7 +887,7 @@
|
||||
},
|
||||
"platform/ui-next": {
|
||||
"name": "@ohif/ui-next",
|
||||
"version": "3.12.0-beta.7",
|
||||
"version": "3.12.0-beta.8",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
|
||||
@ -40,6 +40,8 @@ import { updateSegmentBidirectionalStats } from './utils/updateSegmentationStats
|
||||
import { generateSegmentationCSVReport } from './utils/generateSegmentationCSVReport';
|
||||
import { getUpdatedViewportsForSegmentation } from './utils/hydrationUtils';
|
||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||
import { isMeasurementWithinViewport } from './utils/isMeasurementWithinViewport';
|
||||
import { getCenterExtent } from './utils/getCenterExtent';
|
||||
|
||||
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
|
||||
const toggleSyncFunctions = {
|
||||
@ -161,6 +163,27 @@ function commandsModule({
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
viewport.setViewReference(metadata);
|
||||
viewport.render();
|
||||
|
||||
/**
|
||||
* If the measurement is not visible inside the current viewport,
|
||||
* we need to move the camera to the measurement.
|
||||
*/
|
||||
if (!isMeasurementWithinViewport(viewport, measurement)) {
|
||||
const camera = viewport.getCamera();
|
||||
const { focalPoint: cameraFocalPoint, position: cameraPosition } = camera;
|
||||
const { center, extent } = getCenterExtent(measurement);
|
||||
const position = vec3.sub(vec3.create(), cameraPosition, cameraFocalPoint);
|
||||
vec3.add(position, position, center);
|
||||
viewport.setCamera({ focalPoint: center, position: position as any });
|
||||
/** Zoom out if the measurement is too large */
|
||||
const measurementSize = vec3.dist(extent.min, extent.max);
|
||||
if (measurementSize > camera.parallelScale) {
|
||||
const scaleFactor = measurementSize / camera.parallelScale;
|
||||
viewport.setZoom(viewport.getZoom() / scaleFactor);
|
||||
}
|
||||
viewport.render();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
46
extensions/cornerstone/src/utils/getCenterExtent.ts
Normal file
46
extensions/cornerstone/src/utils/getCenterExtent.ts
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Calculates the center point and bounding box extent of a measurement based on its points.
|
||||
* @param {Object} measurement - The measurement object containing points
|
||||
* @param {Array<Array<number>>} measurement.points - Array of 3D points [x, y, z]
|
||||
* @returns {Object} Object containing center and extent
|
||||
* @returns {Array<number>} returns.center - The center point [x, y, z]
|
||||
* @returns {Object} returns.extent - The bounding box extent with min and max points
|
||||
*/
|
||||
export const getCenterExtent = (measurement: { points?: number[][] }) => {
|
||||
const { points } = measurement;
|
||||
|
||||
if (!points || !Array.isArray(points) || points.length === 0) {
|
||||
// Return default values if no points are available
|
||||
const defaultCenter: [number, number, number] = [0, 0, 0];
|
||||
const defaultExtent = {
|
||||
min: [0, 0, 0] as [number, number, number],
|
||||
max: [0, 0, 0] as [number, number, number],
|
||||
};
|
||||
return { center: defaultCenter, extent: defaultExtent };
|
||||
}
|
||||
|
||||
// Initialize min and max with the first point
|
||||
const min: [number, number, number] = [...points[0]] as [number, number, number];
|
||||
const max: [number, number, number] = [...points[0]] as [number, number, number];
|
||||
|
||||
// Find the bounding box by iterating through all points
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
const point = points[i];
|
||||
for (let j = 0; j < 3; j++) {
|
||||
min[j] = Math.min(min[j], point[j]);
|
||||
max[j] = Math.max(max[j], point[j]);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the center point
|
||||
const center: [number, number, number] = [
|
||||
(min[0] + max[0]) / 2,
|
||||
(min[1] + max[1]) / 2,
|
||||
(min[2] + max[2]) / 2,
|
||||
];
|
||||
|
||||
return {
|
||||
center,
|
||||
extent: { min, max },
|
||||
};
|
||||
};
|
||||
@ -9,6 +9,7 @@ import promptHydrationDialog, {
|
||||
HydrationCallback,
|
||||
HydrationSRResult,
|
||||
} from './promptHydrationDialog';
|
||||
import { getCenterExtent } from './getCenterExtent';
|
||||
|
||||
const utils = {
|
||||
handleSegmentChange,
|
||||
@ -16,6 +17,7 @@ const utils = {
|
||||
setupSegmentationDataModifiedHandler,
|
||||
setupSegmentationModifiedHandler,
|
||||
promptHydrationDialog,
|
||||
getCenterExtent,
|
||||
};
|
||||
|
||||
export type { HydrationDialogProps, HydrationCallback, HydrationSRResult };
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
import { getCenterExtent } from './getCenterExtent';
|
||||
|
||||
/**
|
||||
* Determines if a measurement is within the current viewport extent.
|
||||
* Uses the measurement's bounding box extent to efficiently check if the entire
|
||||
* measurement fits within the viewport's visible area.
|
||||
*
|
||||
* @param {Object} viewport - The viewport object containing camera information
|
||||
* @param {Object} measurement - The measurement object containing points to check
|
||||
* @returns {boolean} True if the measurement extent is within the viewport, false otherwise
|
||||
*/
|
||||
export const isMeasurementWithinViewport = (viewport, measurement) => {
|
||||
const camera = viewport.getCamera();
|
||||
const { focalPoint, parallelScale } = camera;
|
||||
|
||||
// Get the measurement's bounding box extent
|
||||
const { extent } = getCenterExtent(measurement);
|
||||
const { min, max } = extent;
|
||||
|
||||
// Check if the entire bounding box fits within the viewport extent
|
||||
// We need to check both the min and max corners of the bounding box
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const minDistance = Math.abs(min[i] - focalPoint[i]);
|
||||
const maxDistance = Math.abs(max[i] - focalPoint[i]);
|
||||
|
||||
// If either the min or max point is outside the viewport extent, return false
|
||||
if (minDistance > parallelScale || maxDistance > parallelScale) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true; // The entire measurement extent is within the viewport
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 281 KiB After Width: | Height: | Size: 305 KiB |
Loading…
Reference in New Issue
Block a user