All files / packages/tools/src/tools/segmentation/strategies fillRectangle.ts

81.39% Statements 35/43
33.33% Branches 6/18
77.77% Functions 7/9
81.39% Lines 35/43

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                          1x                                       8x   8x 8x         8x         8x   8x 32x       8x 32x 96x       8x         8x       8x   8x 8x 8x         8x               8x 8x     8x 8x 8x 8x 8x 8x   8x 13286x                   8x 13286x 121x     13165x     8x             8x                         8x                              
import { utilities as csUtils, StackViewport } from '@cornerstonejs/core';
import type { Types } from '@cornerstonejs/core';
 
import {
  getBoundingBoxAroundShapeIJK,
  getBoundingBoxAroundShapeWorld,
} from '../../../utilities/boundingBox';
import { pointInShapeCallback } from '../../../utilities';
import { triggerSegmentationDataModified } from '../../../stateManagement/segmentation/triggerSegmentationEvents';
import { LabelmapToolOperationData } from '../../../types';
import { getStrategyData } from './utils/getStrategyData';
import { isAxisAlignedRectangle } from '../../../utilities/rectangleROITool/isAxisAlignedRectangle';
 
const { transformWorldToIndex } = csUtils;
 
type OperationData = LabelmapToolOperationData & {
  points: [Types.Point3, Types.Point3, Types.Point3, Types.Point3];
};
 
/**
 * For each point in the bounding box around the rectangle, if the point is inside
 * the rectangle, set the scalar value to the segmentIndex
 * @param toolGroupId - string
 * @param operationData - OperationData
 * @param inside - boolean
 */
// Todo: why we have another constraintFn? in addition to the one in the operationData?
function fillRectangle(
  enabledElement: Types.IEnabledElement,
  operationData: OperationData,
  inside = true
): void {
  const { points, segmentsLocked, segmentIndex, segmentationId } =
    operationData;
 
  const { viewport } = enabledElement;
  const strategyData = getStrategyData({
    operationData,
    viewport: enabledElement.viewport,
  });
 
  Iif (!strategyData) {
    console.warn('No data found for fillRectangle');
    return;
  }
 
  const { segmentationImageData, segmentationScalarData } = strategyData;
 
  let rectangleCornersIJK = points.map((world) => {
    return transformWorldToIndex(segmentationImageData, world);
  });
 
  // math round
  rectangleCornersIJK = rectangleCornersIJK.map((point) => {
    return point.map((coord) => {
      return Math.round(coord);
    });
  });
 
  const boundsIJK = getBoundingBoxAroundShapeIJK(
    rectangleCornersIJK,
    segmentationImageData.getDimensions()
  );
 
  const isStackViewport = viewport instanceof StackViewport;
 
  // Are we working with 2D rectangle in axis aligned viewport view or not
  const isAligned =
    isStackViewport || isAxisAlignedRectangle(rectangleCornersIJK);
 
  const direction = segmentationImageData.getDirection();
  const spacing = segmentationImageData.getSpacing();
  const { viewPlaneNormal } = viewport.getCamera();
 
  // In case that we are working on oblique, our EPS is really the spacing in the
  // normal direction, since we can't really test each voxel against a 2D rectangle
  // we need some tolerance in the normal direction.
  const EPS = csUtils.getSpacingInNormalDirection(
    {
      direction,
      spacing,
    },
    viewPlaneNormal
  );
 
  const pointsBoundsLPS = getBoundingBoxAroundShapeWorld(points);
  let [[xMin, xMax], [yMin, yMax], [zMin, zMax]] = pointsBoundsLPS;
 
  // Update the bounds with +/- EPS
  xMin -= EPS;
  xMax += EPS;
  yMin -= EPS;
  yMax += EPS;
  zMin -= EPS;
  zMax += EPS;
 
  const pointInShapeFn = isAligned
    ? () => true
    : (pointLPS) => {
        const [x, y, z] = pointLPS;
        const xInside = x >= xMin && x <= xMax;
        const yInside = y >= yMin && y <= yMax;
        const zInside = z >= zMin && z <= zMax;
 
        return xInside && yInside && zInside;
      };
 
  const callback = ({ value, index }) => {
    if (segmentsLocked.includes(value)) {
      return;
    }
 
    segmentationScalarData[index] = segmentIndex;
  };
 
  pointInShapeCallback(
    segmentationImageData,
    pointInShapeFn,
    callback,
    boundsIJK
  );
 
  triggerSegmentationDataModified(segmentationId);
}
 
/**
 * Fill the inside of a rectangle
 * @param toolGroupId - The unique identifier of the tool group.
 * @param operationData - The data that will be used to create the
 * new rectangle.
 */
export function fillInsideRectangle(
  enabledElement: Types.IEnabledElement,
  operationData: OperationData
): void {
  fillRectangle(enabledElement, operationData, true);
}
 
/**
 * Fill the area outside of a rectangle for the toolGroupId and segmentationRepresentationUID.
 * @param toolGroupId - The unique identifier of the tool group.
 * @param operationData - The data that will be used to create the
 * new rectangle.
 */
export function fillOutsideRectangle(
  enabledElement: Types.IEnabledElement,
  operationData: OperationData
): void {
  fillRectangle(enabledElement, operationData, false);
}