-
Notifications
You must be signed in to change notification settings - Fork 245
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(adapter): add RTSS Adapter and Labelmaps to Contours convertor (#…
…734) * -first commit -extracted RTSS logic extension from OHIF viewer PR 3092 * -rename RT to RTStruct -move RTSS formatting out of contour to segmentation function -refactor generateROIContoursFromSegmentations to generateContourSetFromSegmentation -add check if no volume is found when generating contousr from segmentation -rename contourUtils to utilities -move helper functions out of RTSS.js to utilities folder -clean unnecessary console logs * -reduce cs components to be passed * -port Annotations to RTSS code from OHIF -adjust utility functions to work with both * try to make contour for each segment * -change to process single segmentation -change roiContour to match segments -fix for segments thresholding -use segment labels for contours -use Segmentation label for RTSS * optimize performance * more cleanup ---------
- Loading branch information
Showing
16 changed files
with
1,131 additions
and
1 deletion.
There are no files selected for viewing
343 changes: 343 additions & 0 deletions
343
packages/adapters/src/adapters/Cornerstone3D/RTStruct/RTSS.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,343 @@ | ||
// Code and structure based on OHIF Viewer: | ||
// extensions\tmtv\src\utils\dicomRTAnnotationExport\RTStructureSet\RTSSReport.js | ||
|
||
import { | ||
generateContourSetsFromLabelmap, | ||
AnnotationToPointData | ||
} from "./utilities"; | ||
import dcmjs from "dcmjs"; | ||
import { | ||
getPatientModule, | ||
getReferencedFrameOfReferenceSequence, | ||
getReferencedSeriesSequence, | ||
getRTROIObservationsSequence, | ||
getRTSeriesModule, | ||
getStructureSetModule | ||
} from "./utilities"; | ||
|
||
const { DicomMetaDictionary } = dcmjs.data; | ||
|
||
export default class RTSS { | ||
/** | ||
* Convert handles to RTSS report containing the dcmjs dicom dataset. | ||
* | ||
* Note: current WIP and using segmentation to contour conversion, | ||
* routine that is not fully tested | ||
* | ||
* @param segmentations - Cornerstone tool segmentations data | ||
* @param metadataProvider - Metadata provider | ||
* @param DicomMetadataStore - metadata store instance | ||
* @param cs - cornerstone instance | ||
* @param csTools - cornerstone tool instance | ||
* @returns Report object containing the dataset | ||
*/ | ||
static generateRTSSFromSegmentations( | ||
segmentations, | ||
metadataProvider, | ||
DicomMetadataStore, | ||
cornerstoneCache, | ||
cornerstoneToolsEnums, | ||
vtkUtils | ||
) { | ||
// Convert segmentations to ROIContours | ||
const roiContours = []; | ||
|
||
const contourSets = generateContourSetsFromLabelmap({ | ||
segmentations, | ||
cornerstoneCache, | ||
cornerstoneToolsEnums, | ||
vtkUtils | ||
}); | ||
|
||
contourSets.forEach((contourSet, segIndex) => { | ||
// Check contour set isn't undefined | ||
if (contourSet) { | ||
const contourSequence = []; | ||
contourSet.sliceContours.forEach(sliceContour => { | ||
/** | ||
* addContour - Adds a new ROI with related contours to ROIContourSequence | ||
* | ||
* @param newContour - cornerstoneTools `ROIContour` object | ||
* | ||
* newContour = { | ||
* name: string, | ||
* description: string, | ||
* contourSequence: array[contour] | ||
* color: array[number], | ||
* metadata: { | ||
* referencedImageId: string, | ||
* FrameOfReferenceUID: string | ||
* } | ||
* } | ||
* | ||
* contour = { | ||
* ContourImageSequence: array[ | ||
* { ReferencedSOPClassUID: string, ReferencedSOPInstanceUID: string} | ||
* ] | ||
* ContourGeometricType: string, | ||
* NumberOfContourPoints: number, | ||
* ContourData: array[number] | ||
* } | ||
*/ | ||
// Note: change needed if support non-planar contour representation is needed | ||
const sopCommon = metadataProvider.get( | ||
"sopCommonModule", | ||
sliceContour.referencedImageId | ||
); | ||
const ReferencedSOPClassUID = sopCommon.sopClassUID; | ||
const ReferencedSOPInstanceUID = sopCommon.sopInstanceUID; | ||
const ContourImageSequence = [ | ||
{ ReferencedSOPClassUID, ReferencedSOPInstanceUID } // NOTE: replace in dcmjs? | ||
]; | ||
|
||
const sliceContourPolyData = sliceContour.polyData; | ||
|
||
sliceContour.contours.forEach((contour, index) => { | ||
const ContourGeometricType = contour.type; | ||
const NumberOfContourPoints = | ||
contour.contourPoints.length; | ||
const ContourData = []; | ||
|
||
contour.contourPoints.forEach(point => { | ||
const pointData = | ||
sliceContourPolyData.points[point]; | ||
pointData[0] = +pointData[0].toFixed(2); | ||
pointData[1] = +pointData[1].toFixed(2); | ||
pointData[2] = +pointData[2].toFixed(2); | ||
ContourData.push(pointData[0]); | ||
ContourData.push(pointData[1]); | ||
ContourData.push(pointData[2]); | ||
}); | ||
|
||
contourSequence.push({ | ||
ContourImageSequence, | ||
ContourGeometricType, | ||
NumberOfContourPoints, | ||
ContourNumber: index + 1, | ||
ContourData | ||
}); | ||
}); | ||
}); | ||
|
||
const segLabel = contourSet.label || `Segment ${segIndex + 1}`; | ||
|
||
const ROIContour = { | ||
name: segLabel, | ||
description: segLabel, | ||
contourSequence, | ||
color: contourSet.color, | ||
metadata: contourSet.metadata | ||
}; | ||
|
||
roiContours.push(ROIContour); | ||
} | ||
}); | ||
|
||
const rtMetadata = { | ||
name: segmentations.label, | ||
label: segmentations.label | ||
}; | ||
|
||
let dataset = initializeDataset( | ||
rtMetadata, | ||
roiContours[0].metadata, | ||
metadataProvider | ||
); | ||
|
||
roiContours.forEach((contour, index) => { | ||
const roiContour = { | ||
ROIDisplayColor: contour.color || [255, 0, 0], | ||
ContourSequence: contour.contourSequence, | ||
ReferencedROINumber: index + 1 | ||
}; | ||
|
||
dataset.StructureSetROISequence.push( | ||
getStructureSetModule(contour, index, metadataProvider) | ||
); | ||
|
||
dataset.ROIContourSequence.push(roiContour); | ||
|
||
// ReferencedSeriesSequence | ||
dataset.ReferencedSeriesSequence = getReferencedSeriesSequence( | ||
contour.metadata, | ||
index, | ||
metadataProvider, | ||
DicomMetadataStore | ||
); | ||
|
||
// ReferencedFrameOfReferenceSequence | ||
dataset.ReferencedFrameOfReferenceSequence = | ||
getReferencedFrameOfReferenceSequence( | ||
contour.metadata, | ||
metadataProvider, | ||
dataset | ||
); | ||
}); | ||
|
||
const fileMetaInformationVersionArray = new Uint8Array(2); | ||
fileMetaInformationVersionArray[1] = 1; | ||
|
||
const _meta = { | ||
FileMetaInformationVersion: { | ||
Value: [fileMetaInformationVersionArray.buffer], | ||
vr: "OB" | ||
}, | ||
TransferSyntaxUID: { | ||
Value: ["1.2.840.10008.1.2.1"], | ||
vr: "UI" | ||
}, | ||
ImplementationClassUID: { | ||
Value: [DicomMetaDictionary.uid()], // TODO: could be git hash or other valid id | ||
vr: "UI" | ||
}, | ||
ImplementationVersionName: { | ||
Value: ["dcmjs"], | ||
vr: "SH" | ||
} | ||
}; | ||
|
||
dataset._meta = _meta; | ||
|
||
return dataset; | ||
} | ||
|
||
/** | ||
* Convert handles to RTSSReport report object containing the dcmjs dicom dataset. | ||
* | ||
* Note: The tool data needs to be formatted in a specific way, and currently | ||
* it is limited to the RectangleROIStartEndTool in the Cornerstone. | ||
* | ||
* @param annotations Array of Cornerstone tool annotation data | ||
* @param metadataProvider Metadata provider | ||
* @param options report generation options | ||
* @returns Report object containing the dataset | ||
*/ | ||
static generateRTSSFromAnnotations( | ||
annotations, | ||
metadataProvider, | ||
DicomMetadataStore, | ||
options | ||
) { | ||
const rtMetadata = { | ||
name: "RTSS from Annotations", | ||
label: "RTSS from Annotations" | ||
}; | ||
let dataset = initializeDataset( | ||
rtMetadata, | ||
annotations[0].metadata, | ||
metadataProvider | ||
); | ||
|
||
annotations.forEach((annotation, index) => { | ||
const ContourSequence = AnnotationToPointData.convert( | ||
annotation, | ||
index, | ||
metadataProvider, | ||
options | ||
); | ||
|
||
dataset.StructureSetROISequence.push( | ||
getStructureSetModule(annotation, index, metadataProvider) | ||
); | ||
|
||
dataset.ROIContourSequence.push(ContourSequence); | ||
dataset.RTROIObservationsSequence.push( | ||
getRTROIObservationsSequence(annotation, index) | ||
); | ||
|
||
// ReferencedSeriesSequence | ||
// Todo: handle more than one series | ||
dataset.ReferencedSeriesSequence = getReferencedSeriesSequence( | ||
annotation.metadata, | ||
index, | ||
metadataProvider, | ||
DicomMetadataStore | ||
); | ||
|
||
// ReferencedFrameOfReferenceSequence | ||
dataset.ReferencedFrameOfReferenceSequence = | ||
getReferencedFrameOfReferenceSequence( | ||
annotation.metadata, | ||
metadataProvider, | ||
dataset | ||
); | ||
}); | ||
|
||
const fileMetaInformationVersionArray = new Uint8Array(2); | ||
fileMetaInformationVersionArray[1] = 1; | ||
|
||
const _meta = { | ||
FileMetaInformationVersion: { | ||
Value: [fileMetaInformationVersionArray.buffer], | ||
vr: "OB" | ||
}, | ||
TransferSyntaxUID: { | ||
Value: ["1.2.840.10008.1.2.1"], | ||
vr: "UI" | ||
}, | ||
ImplementationClassUID: { | ||
Value: [DicomMetaDictionary.uid()], // TODO: could be git hash or other valid id | ||
vr: "UI" | ||
}, | ||
ImplementationVersionName: { | ||
Value: ["dcmjs"], | ||
vr: "SH" | ||
} | ||
}; | ||
|
||
dataset._meta = _meta; | ||
|
||
return dataset; | ||
} | ||
|
||
/** | ||
* Generate Cornerstone tool state from dataset | ||
* @param {object} dataset dataset | ||
* @param {object} hooks | ||
* @param {function} hooks.getToolClass Function to map dataset to a tool class | ||
* @returns | ||
*/ | ||
//static generateToolState(_dataset, _hooks = {}) { | ||
static generateToolState() { | ||
// Todo | ||
console.warn("RTSS.generateToolState not implemented"); | ||
} | ||
} | ||
|
||
function initializeDataset(rtMetadata, imgMetadata, metadataProvider) { | ||
const rtSOPInstanceUID = DicomMetaDictionary.uid(); | ||
|
||
// get the first annotation data | ||
const { referencedImageId: imageId, FrameOfReferenceUID } = imgMetadata; | ||
|
||
const { studyInstanceUID } = metadataProvider.get( | ||
"generalSeriesModule", | ||
imageId | ||
); | ||
|
||
const patientModule = getPatientModule(imageId, metadataProvider); | ||
const rtSeriesModule = getRTSeriesModule(DicomMetaDictionary); | ||
|
||
return { | ||
StructureSetROISequence: [], | ||
ROIContourSequence: [], | ||
RTROIObservationsSequence: [], | ||
ReferencedSeriesSequence: [], | ||
ReferencedFrameOfReferenceSequence: [], | ||
...patientModule, | ||
...rtSeriesModule, | ||
StudyInstanceUID: studyInstanceUID, | ||
SOPClassUID: "1.2.840.10008.5.1.4.1.1.481.3", // RT Structure Set Storage | ||
SOPInstanceUID: rtSOPInstanceUID, | ||
Manufacturer: "dcmjs", | ||
Modality: "RTSTRUCT", | ||
FrameOfReferenceUID, | ||
PositionReferenceIndicator: "", | ||
StructureSetLabel: rtMetadata.label || "", | ||
StructureSetName: rtMetadata.name || "", | ||
ReferringPhysicianName: "", | ||
OperatorsName: "", | ||
StructureSetDate: DicomMetaDictionary.date(), | ||
StructureSetTime: DicomMetaDictionary.time() | ||
}; | ||
} |
4 changes: 4 additions & 0 deletions
4
packages/adapters/src/adapters/Cornerstone3D/RTStruct/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { generateContourSetsFromLabelmap } from "./utilities/generateContourSetsFromLabelmap"; | ||
import RTSS from "./RTSS"; | ||
|
||
export { generateContourSetsFromLabelmap, RTSS }; |
Oops, something went wrong.