All files / extensions/cornerstone-dicom-sr/src/utils getRenderableData.ts

61.19% Statements 41/67
43.75% Branches 7/16
100% Functions 2/2
60% Lines 39/65

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          198x   198x 28x 28x 10x 10x     18x 86x 86x     28x       28x   28x       27x 27x                                                                                                                               1x           1x             1x 1x 1x 1x   1x 1x     1x   1x 1x 1x   1x   1x       1x     1x   1x 1x   1x 1x   1x 1x 1x           1x           28x        
import { vec3 } from 'gl-matrix';
import { metaData, utilities, Types as csTypes } from '@cornerstonejs/core';
 
import { SCOORDTypes } from '../enums';
 
const EPSILON = 1e-4;
 
const getRenderableCoords = ({ GraphicData, ValueType, imageId }) => {
  const renderableData = [];
  if (ValueType === 'SCOORD3D') {
    for (let i = 0; i < GraphicData.length; i += 3) {
      renderableData.push([GraphicData[i], GraphicData[i + 1], GraphicData[i + 2]]);
    }
  } else {
    for (let i = 0; i < GraphicData.length; i += 2) {
      const worldPos = utilities.imageToWorldCoords(imageId, [GraphicData[i], GraphicData[i + 1]]);
      renderableData.push(worldPos);
    }
  }
  return renderableData;
};
 
function getRenderableData({ GraphicType, GraphicData, ValueType, imageId }) {
  let renderableData = [];
 
  switch (GraphicType) {
    case SCOORDTypes.POINT:
    case SCOORDTypes.MULTIPOINT:
    case SCOORDTypes.POLYLINE: {
      renderableData = getRenderableCoords({ GraphicData, ValueType, imageId });
      break;
    }
    case SCOORDTypes.CIRCLE: {
      const pointsWorld: csTypes.Point3[] = getRenderableCoords({
        GraphicData,
        ValueType,
        imageId,
      });
 
      Iif (!imageId) {
        // without the image id it's not possible to perform the calculations below
        // these calculations also do not seem to be needed, since everything works
        // just fine when we skip them. At least for SCOORD3D annotations.
        return pointsWorld;
      }
 
      // We do not have an explicit draw circle svg helper in Cornerstone3D at
      // this time, but we can use the ellipse svg helper to draw a circle, so
      // here we reshape the data for that purpose.
      const center = pointsWorld[0];
      const onPerimeter = pointsWorld[1];
      const radius = vec3.distance(center, onPerimeter);
 
      const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
      Iif (!imagePlaneModule) {
        throw new Error('No imagePlaneModule found');
      }
 
      const {
        columnCosines,
        rowCosines,
      }: {
        columnCosines: csTypes.Point3;
        rowCosines: csTypes.Point3;
      } = imagePlaneModule;
 
      // we need to get major/minor axis (which are both the same size major = minor)
 
      const firstAxisStart = vec3.create();
      vec3.scaleAndAdd(firstAxisStart, center, columnCosines, radius);
 
      const firstAxisEnd = vec3.create();
      vec3.scaleAndAdd(firstAxisEnd, center, columnCosines, -radius);
 
      const secondAxisStart = vec3.create();
      vec3.scaleAndAdd(secondAxisStart, center, rowCosines, radius);
 
      const secondAxisEnd = vec3.create();
      vec3.scaleAndAdd(secondAxisEnd, center, rowCosines, -radius);
 
      renderableData = [
        firstAxisStart as csTypes.Point3,
        firstAxisEnd as csTypes.Point3,
        secondAxisStart as csTypes.Point3,
        secondAxisEnd as csTypes.Point3,
      ];
 
      break;
    }
    case SCOORDTypes.ELLIPSE: {
      // GraphicData is ordered as [majorAxisStartX, majorAxisStartY, majorAxisEndX, majorAxisEndY, minorAxisStartX, minorAxisStartY, minorAxisEndX, minorAxisEndY]
      // But Cornerstone3D points are ordered as top, bottom, left, right for the
      // ellipse so we need to identify if the majorAxis is horizontal or vertical
      // and then choose the correct points to use for the ellipse.
      const pointsWorld: csTypes.Point3[] = getRenderableCoords({
        GraphicData,
        ValueType,
        imageId,
      });
 
      Iif (!imageId) {
        // without the image id it's not possible to perform the calculations below
        // these calculations also do not seem to be needed, since everything works
        // just fine when we skip them. At least for SCOORD3D annotations.
        return pointsWorld;
      }
 
      const majorAxisStart = vec3.fromValues(...pointsWorld[0]);
      const majorAxisEnd = vec3.fromValues(...pointsWorld[1]);
      const minorAxisStart = vec3.fromValues(...pointsWorld[2]);
      const minorAxisEnd = vec3.fromValues(...pointsWorld[3]);
 
      const majorAxisVec = vec3.create();
      vec3.sub(majorAxisVec, majorAxisEnd, majorAxisStart);
 
      // normalize majorAxisVec to avoid scaling issues
      vec3.normalize(majorAxisVec, majorAxisVec);
 
      const minorAxisVec = vec3.create();
      vec3.sub(minorAxisVec, minorAxisEnd, minorAxisStart);
      vec3.normalize(minorAxisVec, minorAxisVec);
 
      const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
 
      Iif (!imagePlaneModule) {
        throw new Error('imageId does not have imagePlaneModule metadata');
      }
 
      const { columnCosines }: { columnCosines: csTypes.Point3 } = imagePlaneModule;
 
      // find which axis is parallel to the columnCosines
      const columnCosinesVec = vec3.fromValues(...columnCosines);
 
      const projectedMajorAxisOnColVec = Math.abs(vec3.dot(columnCosinesVec, majorAxisVec));
      const projectedMinorAxisOnColVec = Math.abs(vec3.dot(columnCosinesVec, minorAxisVec));
 
      const absoluteOfMajorDotProduct = Math.abs(projectedMajorAxisOnColVec);
      const absoluteOfMinorDotProduct = Math.abs(projectedMinorAxisOnColVec);
 
      renderableData = [];
      if (Math.abs(absoluteOfMajorDotProduct - 1) < EPSILON) {
        renderableData = [pointsWorld[0], pointsWorld[1], pointsWorld[2], pointsWorld[3]];
      } else Eif (Math.abs(absoluteOfMinorDotProduct - 1) < EPSILON) {
        renderableData = [pointsWorld[2], pointsWorld[3], pointsWorld[0], pointsWorld[1]];
      } else {
        console.warn('OBLIQUE ELLIPSE NOT YET SUPPORTED');
      }
      break;
    }
    default:
      console.warn('Unsupported GraphicType:', GraphicType);
  }
 
  return renderableData;
}
 
export default getRenderableData;