diff --git a/extensions/dicom-rt/package.json b/extensions/dicom-rt/package.json index f5167e514..7983016d3 100644 --- a/extensions/dicom-rt/package.json +++ b/extensions/dicom-rt/package.json @@ -32,6 +32,7 @@ "cornerstone-core": "^2.2.8", "cornerstone-tools": "^4.20.1", "dcmjs": "0.16.5", + "gl-matrix": "^3.3.0", "prop-types": "^15.6.2", "react": "^16.8.6", "react-dom": "^16.8.6", diff --git a/extensions/dicom-rt/src/loadRTStruct.js b/extensions/dicom-rt/src/loadRTStruct.js index f95172941..59da0b71b 100644 --- a/extensions/dicom-rt/src/loadRTStruct.js +++ b/extensions/dicom-rt/src/loadRTStruct.js @@ -4,6 +4,7 @@ import cornerstone from 'cornerstone-core'; import cornerstoneTools from 'cornerstone-tools'; import transformPointsToImagePlane from './utils/transformPointsToImagePlane'; import TOOL_NAMES from './utils/toolNames'; +import { vec3 } from 'gl-matrix'; const dicomlab2RGB = dcmjs.data.Colors.dicomlab2RGB; const globalImageIdSpecificToolStateManager = @@ -66,8 +67,6 @@ export default async function loadRTStruct( const rtStructDisplayToolName = TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL; - debugger; - for (let i = 0; i < ROIContourSequence.length; i++) { const ROIContour = ROIContourSequence[i]; const { ReferencedROINumber, ContourSequence } = ROIContour; @@ -88,7 +87,14 @@ export default async function loadRTStruct( ContourGeometricType, } = ContourSequenceArray[c]; - const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID; + const sopInstanceUID = ContourImageSequence + ? ContourImageSequence.ReferencedSOPInstanceUID + : _getClosestSOPInstanceUID( + ContourData, + ContourGeometricType, + NumberOfContourPoints, + imageIdSopInstanceUidPairs + ); const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID); const imageIdSpecificToolData = _getOrCreateImageIdSpecificToolData( toolState, @@ -326,3 +332,78 @@ function _getImageIdSopInstanceUidPairsForDisplaySet( function _toArray(objOrArray) { return Array.isArray(objOrArray) ? objOrArray : [objOrArray]; } + +function _getClosestSOPInstanceUID( + ContourData, + ContourGeometricType, + NumberOfContourPoints, + imageIdSopInstanceUidPairs +) { + const closest = { + distance: Infinity, + sopInstanceUID: null, + }; + + let point; + + switch (ContourGeometricType) { + case 'POINT': + point = ContourData; + break; + case 'CLOSED_PLANAR': + case 'OPEN_PLANAR': + // These are defined as planar, so get the of the region to get the + // Best mapping to a plane even if its potentially off center. + + point = [0, 0, 0]; + for (let p = 0; p < NumberOfContourPoints * 3; p += 3) { + point[0] += ContourData[p]; + point[1] += ContourData[p + 1]; + point[2] += ContourData[p + 2]; + } + + point[0] /= NumberOfContourPoints; + point[1] /= NumberOfContourPoints; + point[2] /= NumberOfContourPoints; + } + + imageIdSopInstanceUidPairs.forEach(pair => { + const { imageId } = pair; + + const imagePlaneModule = cornerstone.metaData.get( + 'imagePlaneModule', + imageId + ); + + const distance = distanceFromPointToPlane(point, imagePlaneModule); + + if (distance < closest.distance) { + closest.distance = distance; + closest.sopInstanceUID = pair.sopInstanceUID; + } + }); + + return closest.sopInstanceUID; +} + +/** + * + * @param {number[3]} P - The point + * @param {object} imagePlaneModule The cornerstone metadata object for the imagePlane + */ +function distanceFromPointToPlane(P, imagePlaneModule) { + const { + rowCosines, + columnCosines, + imagePositionPatient: Q, + } = imagePlaneModule; + + let N = []; + vec3.cross(N, rowCosines, columnCosines); + + const [A, B, C] = N; + + const D = -A * Q[0] - B * Q[1] - C * Q[2]; + + return Math.abs(A * P[0] + B * P[1] + C * P[2] + D); // Denominator is sqrt(A**2 + B**2 + C**2) which is 1 as its a normal vector +}