All files / packages/tools/src/tools/segmentation/strategies/compositions dynamicThreshold.ts

0% Statements 0/39
0% Branches 0/28
0% Functions 0/6
0% Lines 0/39

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                                                                                                                                                                                                                                           
import { vec3 } from 'gl-matrix';
import type { Types } from '@cornerstonejs/core';
import type { InitializedOperationData } from '../BrushStrategy';
import type BoundsIJK from '../../../../types/BoundsIJK';
import StrategyCallbacks from '../../../../enums/StrategyCallbacks';
 
/**
 * Initializes the threshold values for the dynamic threshold.
 * If the threshold is undefined/null, the threshold will be set
 * by looking at the area centered on the centerIJK, with a delta radius,
 * and taking the range of those pixel values.
 * If the threshold is already set, then the range will be extended by just the
 * center voxel at centerIJK.
 */
export default {
  [StrategyCallbacks.Initialize]: (operationData: InitializedOperationData) => {
    const {
      operationName,
      centerIJK,
      strategySpecificConfiguration,
      segmentationVoxelManager: segmentationVoxelManager,
      imageVoxelManager: imageVoxelManager,
      segmentIndex,
    } = operationData;
    const { THRESHOLD } = strategySpecificConfiguration;
 
    if (!THRESHOLD?.isDynamic || !centerIJK || !segmentIndex) {
      return;
    }
    if (
      operationName === StrategyCallbacks.RejectPreview ||
      operationName === StrategyCallbacks.OnInteractionEnd
    ) {
      return;
    }
 
    const { boundsIJK } = segmentationVoxelManager;
    const { threshold: oldThreshold, dynamicRadius = 0 } = THRESHOLD;
    const useDelta = oldThreshold ? 0 : dynamicRadius;
    const nestedBounds = boundsIJK.map((ijk, idx) => {
      const [min, max] = ijk;
      return [
        Math.max(min, centerIJK[idx] - useDelta),
        Math.min(max, centerIJK[idx] + useDelta),
      ];
    }) as BoundsIJK;
 
    const threshold = oldThreshold || [Infinity, -Infinity];
    // TODO - threshold on all three values separately
    const callback = ({ value }) => {
      const gray = Array.isArray(value) ? vec3.len(value as any) : value;
      threshold[0] = Math.min(gray, threshold[0]);
      threshold[1] = Math.max(gray, threshold[1]);
    };
    imageVoxelManager.forEach(callback, { boundsIJK: nestedBounds });
 
    operationData.strategySpecificConfiguration.THRESHOLD.threshold = threshold;
  },
  // Setup a clear threshold value on mouse/touch down
  [StrategyCallbacks.OnInteractionStart]: (
    operationData: InitializedOperationData
  ) => {
    const { strategySpecificConfiguration, preview } = operationData;
    if (!strategySpecificConfiguration?.THRESHOLD?.isDynamic && !preview) {
      return;
    }
    strategySpecificConfiguration.THRESHOLD.threshold = null;
  },
  /**
   * It computes the inner circle radius in canvas coordinates and stores it
   * in the strategySpecificConfiguration. This is used to show the user
   * the area that is used to compute the threshold.
   */
  [StrategyCallbacks.ComputeInnerCircleRadius]: (
    operationData: InitializedOperationData
  ) => {
    const { configuration, viewport } = operationData;
    const { THRESHOLD: { dynamicRadius = 0 } = {} } =
      configuration.strategySpecificConfiguration || {};
 
    if (dynamicRadius === 0) {
      return;
    }
 
    const { spacing } = (
      viewport as Types.IStackViewport | Types.IVolumeViewport
    ).getImageData();
 
    const centerCanvas = [
      viewport.element.clientWidth / 2,
      viewport.element.clientHeight / 2,
    ] as Types.Point2;
    const radiusInWorld = dynamicRadius * spacing[0];
    const centerCursorInWorld = viewport.canvasToWorld(centerCanvas);
 
    const offSetCenterInWorld = centerCursorInWorld.map(
      (coord) => coord + radiusInWorld
    ) as Types.Point3;
 
    const offSetCenterCanvas = viewport.worldToCanvas(offSetCenterInWorld);
    const dynamicRadiusInCanvas = Math.abs(
      centerCanvas[0] - offSetCenterCanvas[0]
    );
 
    // this is a bit of a hack, since we have switched to using THRESHOLD
    // as strategy but really strategy names are CIRCLE_THRESHOLD and SPHERE_THRESHOLD
    // and we can't really change the name of the strategy in the configuration
    const { strategySpecificConfiguration, activeStrategy } = configuration;
 
    if (!strategySpecificConfiguration[activeStrategy]) {
      strategySpecificConfiguration[activeStrategy] = {};
    }
 
    strategySpecificConfiguration[activeStrategy].dynamicRadiusInCanvas =
      dynamicRadiusInCanvas;
  },
};