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:
authored and GitHub committed 2025-08-22 10:30:18 -04:00
1 parent 16759506e5
commit 6fe9df6ec9
6 files changed
+148 -44

No files matched your search

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