-
Notifications
You must be signed in to change notification settings - Fork 285
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add referenceCursors tool (#275)
* added basic cursorCrosshairSync tool with example, TODO: for now cursorSync is displayed regardless of distance, create configurable distance and also sync the position of all viewports over which the mouse is not to scroll to a slice that is close to the currentMousePosition in 3d space * addde stack syncing for StackViewport and syncing for volumeViewport on imageChange events, added configuration for max display distance * refactored tool functions * added comment to possible bug * added configuration options to example * changed look of crosshair to 4 lines with central space * undid local tsconfig change * undid yarn.lock changes * added tool to example-info.json * removed from example-runner because it broke build * readded example and fixed typo * readded example-info and changed example to trigger rebuild * added cleanup for mouseoverElement when tool is disabled * added cleanup when tool gets disabled, this does not get called when toolGroup gets destroyed, might cause remaining listeners * applied naming changes, reworked adding annotation logic * removed event listeners and moved logic to check for stack scrolling into rendering logic * added planeDistanceToPoint to planar utilities * added getClosestStackImageIndexForPoint * rewrote logic to use onCameraModified * updated example-info * fixed bug with 0 being falsey * added logic to remove cursor if wanted * modified toolGroup so that setting a tool active only changes the cursor to default if there is no primary mouse cursor * fixed bug not updating disable cursor * fixed missing parentheses from merge * readded scrollWheel scrolling and api changes * fixed typos
- Loading branch information
Showing
12 changed files
with
1,004 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
packages/core/src/utilities/getClosestStackImageIndexForPoint.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { vec3 } from 'gl-matrix'; | ||
import { planar } from '.'; | ||
import { metaData } from '..'; | ||
import { IStackViewport, Point3 } from '../types'; | ||
|
||
/** | ||
* Given a point in 3D space and a viewport it returns the index of the closest imageId, it assumes that stack images are sorted according to their sliceLocation | ||
* @param point - [A, B, C] coordinates of the point in 3D space | ||
* @param viewport - The StackViewport to search for the closest imageId | ||
* | ||
* @returns The imageId index of the closest imageId or null if no imageId is found | ||
*/ | ||
export default function getClosestStackImageIndexForPoint( | ||
point: Point3, | ||
viewport: IStackViewport | ||
): number | null { | ||
const minimalDistance = calculateMinimalDistanceForStackViewport( | ||
point, | ||
viewport | ||
); | ||
return minimalDistance ? minimalDistance.index : null; | ||
} | ||
|
||
//assumes that imageIds are sorted by slice location | ||
export function calculateMinimalDistanceForStackViewport( | ||
point: Point3, | ||
viewport: IStackViewport | ||
): { distance: number; index: number } | null { | ||
const imageIds = viewport.getImageIds(); | ||
const currentImageIdIndex = viewport.getCurrentImageIdIndex(); | ||
|
||
if (imageIds.length === 0) return null; | ||
|
||
const getDistance = (imageId: string): null | number => { | ||
const planeMetadata = getPlaneMetadata(imageId); | ||
if (!planeMetadata) return null; | ||
const plane = planar.planeEquation( | ||
planeMetadata.planeNormal, | ||
planeMetadata.imagePositionPatient | ||
); | ||
const distance = planar.planeDistanceToPoint(plane, point); | ||
return distance; | ||
}; | ||
|
||
const closestStack = { | ||
distance: getDistance(imageIds[currentImageIdIndex]) ?? Infinity, | ||
index: currentImageIdIndex, | ||
}; | ||
|
||
//check higher indices | ||
const higherImageIds = imageIds.slice(currentImageIdIndex + 1); | ||
|
||
for (let i = 0; i < higherImageIds.length; i++) { | ||
const id = higherImageIds[i]; | ||
const distance = getDistance(id); | ||
if (distance === null) continue; | ||
if (distance <= closestStack.distance) { | ||
closestStack.distance = distance; | ||
closestStack.index = i + currentImageIdIndex + 1; | ||
} else break; | ||
} | ||
//check lower indices | ||
const lowerImageIds = imageIds.slice(0, currentImageIdIndex); | ||
for (let i = lowerImageIds.length - 1; i >= 0; i--) { | ||
const id = lowerImageIds[i]; | ||
const distance = getDistance(id); | ||
if (distance === null || distance === closestStack.distance) continue; | ||
if (distance < closestStack.distance) { | ||
closestStack.distance = distance; | ||
closestStack.index = i; | ||
} else break; | ||
} | ||
return closestStack.distance === Infinity ? null : closestStack; | ||
} | ||
|
||
function getPlaneMetadata(imageId: string): null | { | ||
rowCosines: Point3; | ||
columnCosines: Point3; | ||
imagePositionPatient: Point3; | ||
planeNormal: Point3; | ||
} { | ||
const targetImagePlane = metaData.get('imagePlaneModule', imageId); | ||
|
||
if ( | ||
!targetImagePlane || | ||
!( | ||
targetImagePlane.rowCosines instanceof Array && | ||
targetImagePlane.rowCosines.length === 3 | ||
) || | ||
!( | ||
targetImagePlane.columnCosines instanceof Array && | ||
targetImagePlane.columnCosines.length === 3 | ||
) || | ||
!( | ||
targetImagePlane.imagePositionPatient instanceof Array && | ||
targetImagePlane.imagePositionPatient.length === 3 | ||
) | ||
) { | ||
return null; | ||
} | ||
const { | ||
rowCosines, | ||
columnCosines, | ||
imagePositionPatient, | ||
}: { | ||
rowCosines: Point3; | ||
columnCosines: Point3; | ||
imagePositionPatient: Point3; | ||
} = targetImagePlane; | ||
|
||
const rowVec = vec3.set(vec3.create(), ...rowCosines); | ||
const colVec = vec3.set(vec3.create(), ...columnCosines); | ||
const planeNormal = vec3.cross(vec3.create(), rowVec, colVec) as Point3; | ||
|
||
return { rowCosines, columnCosines, imagePositionPatient, planeNormal }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.