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

58.73% Statements 37/63
28.57% Branches 4/14
100% Functions 2/2
59.01% Lines 36/61

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          34x   34x 2x 2x         2x 6x 6x     2x       2x   2x       1x 1x                                                                                                               1x           1x 1x 1x 1x   1x 1x     1x   1x 1x 1x   1x   1x       1x     1x   1x 1x   1x 1x   1x 1x 1x           1x           2x        
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 = [];
  Iif (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,
      });
      // 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,
      });
 
      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;