All files / tools/src/utilities getSphereBoundsInfo.ts

78.94% Statements 30/38
50% Branches 1/2
80% Functions 4/5
78.94% Lines 30/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214                428x                                         279x     279x             279x   279x               279x                                                                                                                       279x           279x         279x         279x         279x   279x   279x           279x                     279x         279x               279x 279x         279x 279x   279x 279x               279x 279x           279x       279x         279x 558x       279x         279x          
import { utilities as csUtils } from '@cornerstonejs/core';
import type { Types } from '@cornerstonejs/core';
 
import type { vtkImageData } from '@kitware/vtk.js/Common/DataModel/ImageData';
import { vec3 } from 'gl-matrix';
import type { BoundsIJK } from '../types';
import { getBoundingBoxAroundShapeIJK } from './boundingBox';
 
const { transformWorldToIndex } = csUtils;
 
type SphereBoundsInfo = {
  boundsIJK: BoundsIJK;
  centerWorld: Types.Point3;
  radiusWorld: number;
  topLeftWorld: Types.Point3;
  bottomRightWorld: Types.Point3;
};
 
function _getSphereBoundsInfo(
  circlePoints: [Types.Point3, Types.Point3],
  imageData: vtkImageData,
  directionVectors
): {
  boundsIJK: BoundsIJK;
  centerWorld: Types.Point3;
  radiusWorld: number;
  topLeftWorld: Types.Point3;
  bottomRightWorld: Types.Point3;
} {
  const [bottom, top] = circlePoints;
 
  // Sphere center in world
  const centerWorld = vec3.fromValues(
    (bottom[0] + top[0]) / 2,
    (bottom[1] + top[1]) / 2,
    (bottom[2] + top[2]) / 2
  );
 
  // sphere radius in world
  const radiusWorld = vec3.distance(bottom, top) / 2;
 
  const { boundsIJK, topLeftWorld, bottomRightWorld } = _computeBoundsIJK(
    imageData,
    directionVectors,
    circlePoints,
    centerWorld,
    radiusWorld
  );
 
  return {
    boundsIJK,
    centerWorld: centerWorld as Types.Point3,
    radiusWorld,
    topLeftWorld: topLeftWorld as Types.Point3,
    bottomRightWorld: bottomRightWorld as Types.Point3,
  };
}
 
/**
 * Returns the bounds, center, radius and top-left/bottom-right coordinates of a
 * sphere for a given circle and imageData. The region of interest will be an
 * accurate approximation of the sphere using the direction vectors from imageData.
 *
 * @privateRemarks great circle also known as orthodrome is the intersection of
 * the sphere and the plane that passes through the center of the sphere
 *
 * @param imageData - Volume imageData
 * @param circlePoints - Bottom and top points of the great circle in world coordinates
 */
function getSphereBoundsInfo(
  circlePoints: [Types.Point3, Types.Point3],
  imageData: vtkImageData
): SphereBoundsInfo {
  const direction = imageData.getDirection();
  const rowCosine = vec3.fromValues(direction[0], direction[1], direction[2]);
  const columnCosine = vec3.fromValues(
    direction[3],
    direction[4],
    direction[5]
  );
  const scanAxis = vec3.fromValues(direction[6], direction[7], direction[8]);
  const viewPlaneNormal = vec3.negate(vec3.create(), scanAxis);
 
  const directionVectors = {
    row: rowCosine as Types.Point3,
    column: columnCosine as Types.Point3,
    normal: viewPlaneNormal as Types.Point3,
  };
 
  return _getSphereBoundsInfo(circlePoints, imageData, directionVectors);
}
 
/**
 * Returns the bounds, center, radius and top-left/bottom-right coordinates of a
 * sphere for a given circle, imageData and a viewport. The region of interest
 * will be an accurate approximation of the sphere (using viewport camera)
 *
 * @privateRemarks great circle also known as orthodrome is the intersection of
 * the sphere and the plane that passes through the center of the sphere
 *
 * @param imageData - Volume imageData
 * @param circlePoints - Bottom and top points of the great circle in world coordinates
 * @param viewport - Viewport
 */
function getSphereBoundsInfoFromViewport(
  circlePoints: [Types.Point3, Types.Point3],
  imageData: vtkImageData,
  viewport
): SphereBoundsInfo {
  Iif (!viewport) {
    throw new Error(
      'viewport is required in order to calculate the sphere bounds'
    );
  }
 
  const camera = viewport.getCamera();
 
  // Calculate viewRight from the camera, this will get used in order to
  // calculate circles topLeft and bottomRight on different planes of intersection
  // between sphere and viewPlane
  const viewUp = vec3.fromValues(
    camera.viewUp[0],
    camera.viewUp[1],
    camera.viewUp[2]
  );
  const viewPlaneNormal = vec3.fromValues(
    camera.viewPlaneNormal[0],
    camera.viewPlaneNormal[1],
    camera.viewPlaneNormal[2]
  );
  const viewRight = vec3.create();
 
  vec3.cross(viewRight, viewUp, viewPlaneNormal);
 
  const directionVectors = {
    row: viewRight as Types.Point3,
    normal: viewPlaneNormal as Types.Point3,
    column: vec3.negate(vec3.create(), viewUp) as Types.Point3,
  };
 
  return _getSphereBoundsInfo(circlePoints, imageData, directionVectors);
}
 
function _computeBoundsIJK(
  imageData,
  directionVectors,
  circlePoints,
  centerWorld,
  radiusWorld
) {
  // const [bottom, top] = circlePoints;
  const dimensions = imageData.getDimensions() as Types.Point3;
  const {
    row: rowCosine,
    column: columnCosine,
    normal: vecNormal,
  } = directionVectors;
 
  // we need to find the bounding box of the sphere in the image, e.g., the
  // topLeftWorld and bottomRightWorld points of the bounding box.
  // We go from the sphereCenter in the normal direction of amount radius, and
  // we go left to find the topLeftWorld point of the bounding box. Next we go
  // in the opposite direction and go right to find the bottomRightWorld point
  // of the bounding box.
  const topLeftWorld = vec3.create();
  const bottomRightWorld = vec3.create();
 
  // vec3.scaleAndAdd(topLeftWorld, top, viewPlaneNormal, radiusWorld);
  // vec3.scaleAndAdd(bottomRightWorld, bottom, viewPlaneNormal, -radiusWorld);
 
  vec3.scaleAndAdd(topLeftWorld, centerWorld, vecNormal, radiusWorld);
  vec3.scaleAndAdd(bottomRightWorld, centerWorld, vecNormal, -radiusWorld);
 
  vec3.scaleAndAdd(topLeftWorld, topLeftWorld, columnCosine, -radiusWorld);
  vec3.scaleAndAdd(
    bottomRightWorld,
    bottomRightWorld,
    columnCosine,
    radiusWorld
  );
 
  // go in the direction of viewRight with the value of radius
  vec3.scaleAndAdd(topLeftWorld, topLeftWorld, rowCosine, -radiusWorld);
  vec3.scaleAndAdd(bottomRightWorld, bottomRightWorld, rowCosine, radiusWorld);
 
  // In order to correctly come up with the boundsIJK, we need to consider
  // all the points IJK to get the bounds, since the viewport might have
  // rotate views and we cannot guarantee that the topLeft and bottomRight in the
  // world, are the ones that will define the bounds in IJK
  const topLeftIJK = transformWorldToIndex(
    imageData,
    topLeftWorld as Types.Point3
  );
  const bottomRightIJK = transformWorldToIndex(
    imageData,
    bottomRightWorld as Types.Point3
  );
 
  const pointsIJK = circlePoints.map((p) =>
    transformWorldToIndex(imageData, p)
  );
 
  // get the bounding box of the sphere in the image
  const boundsIJK = getBoundingBoxAroundShapeIJK(
    [topLeftIJK, bottomRightIJK, ...pointsIJK],
    dimensions
  );
 
  return { boundsIJK, topLeftWorld, bottomRightWorld };
}
 
export { getSphereBoundsInfo, getSphereBoundsInfoFromViewport };
export type { SphereBoundsInfo };