feat: cornerstone3D stack and volume viewports (#2787)
* feat: cs3d working stack viewport and tools (#19) * Squashed everything * fix weird react issue * fix eslint / prettier stuff * thumbnails work now with cpu rendering * feat: use new loadImageToCanvas in cs3d * make jump to measurement work * fix active thumbnail * fix measurement delete * fix window level presets * remove segmentation and sync groups for now * fix the dicom pdf and dicom video * fix cornerstone window assignment for cypress * apply review comments Co-authored-by: Erik <erik.sweed@gmail.com> * feat: cs3d tools and toolGroups (#20) * add more tools to work with cs3d mode * fix hotkeys * add stack manager usage for stach viewports * add image scrollbar * wip viewport overlay * fix toAnnotation schema for tools * fix the unnecessary size change that triggered resize * hanging protocol improvement to allow unmatched errors * study description matching for hanging protocol * fix the displaysetOptions to work * fix handle the active tool when a new viewport is added * fix separate toolGroups for mode * apply review comments * apply review comments * yarn lock * feat: overlay component (#21) * fix default displayset options * add viewport overlay * apply review comments * feat: loading and orientation indicators (#22) * add loading indicator * add orientation marker initial work * apply review comments * fix: orientation markers (#23) * finished the orientation markers * fix various broken cypress tests * apply review comments * update yarn lock * feat: re-working measurement tracking mode with cornerstone3d (#2805) * feat: cs3d working stack viewport and tools (#19) * Squashed everything * fix weird react issue * fix eslint / prettier stuff * thumbnails work now with cpu rendering * feat: use new loadImageToCanvas in cs3d * make jump to measurement work * fix active thumbnail * fix measurement delete * fix window level presets * remove segmentation and sync groups for now * fix the dicom pdf and dicom video * fix cornerstone window assignment for cypress * apply review comments Co-authored-by: Erik <erik.sweed@gmail.com> * feat: Add Measurement tracking mode with cs3D (#2789) * feat: first render for cornerstone3d tracked viewport * make tool active work * wip for SR extension * renamed dicom sr to cornerstone dicom sr * remove cornerstone from dicom pdf and video * move dicom sr logic to sr extension * feat: Add hydration for the length tool * fix SR display tool for length using cs3d * fix default config * fix: various bugs with sr viewport and tracking * fix promptying to continue tracking for when SR is created * feat: add keep trackign of unique identifiers * fix hydration for same imageIds * feat: Add SR toolGroup creation on modeEnter * feat: remove the need for separate mapper for SR hydration * add SR display for ellipse * handle hydration of elliptical ROI tool * remove cornerstone extension * add arrow mapping * feat: Add ArrowAnnotate SR display and hydration * apply review comments * apply review comments * move viewport labels to the viewportData * apply review comments * fix: integration cypress tests with cornerstone3D and add CINE tool (#2795) * fix: integration cypress tests with cornerstone3D * revert to addOrUpdate as it makes more sense * fix local drag and drop * fix tests * move dicomLoaderService to cornerstone extension * fix various import bugs * fix bug for local PT series * fix various unit tests * bump cs3d versions * add angle and magnify tool * bump dependencies to avoid broken peerDeps * feat: add initial work for capture using cs3d * feat: show annotations on the image capture * feat: add svg layer export * feat: Add CINE Tool * feat: remove unnecessary viewport rendering for cine state changes Co-authored-by: Erik Ziegler <erik.sweed@gmail.com> * docs: modify and improve documentation (#2800) * cleanup docs versionings * feat: Add all version explanations * version docs for 3.0 * wip for changing docs * wip for updated docs * add utility module documentation * fix demo with nohoisting of history * add slides and video to resources * apply review comments * fix: drag and drop SR into SR viewport (#2803) * update yarn lock Co-authored-by: Erik <erik.sweed@gmail.com> * update pathnames to match v3-stable * update the e2e pathname * fix: various bugs with regard to tracking workflow (#2811) * fix: various issues with measurement panel * fix: update default tool style for annotations * fix: annotatoin label getting removed * feat: Add backward compatibility for SR hydration with legacy cornerstone * fix: cursors and ellipse ROI max style * fix: ArrowAnnotate SRDisplay * apply review comments * bump package versions * fix: bugin rehydration of SR * fix: e2e tests * fix active viewport thickness and arrowTool ui * add readme for measurement tracking * use uploaded image for readme * add back images * try to fix e2e test * fix: window level presets hotkeys * Update README.md * update yarn lock * feat: volume api and TMTV mode (#2817) * feat: volumeAPI and TMTV mode * feat: use cs3d cache service to obtain viewportData * wip for volume api * wip: fusion viewport * feat: add blend mode option * wip for image scrollbar * fix drag and drop thumbnail * wip for image scrollbar for voluems * fix: element mismatch bug for scroll * feat: Add image scrollbar to volumes * feat: add syncGroups to volume api * feat: add tmtv mode initial setup * feat: add initial image options for the stack viewports * feat: Add custom load strategy for volume viewports via HP * apply review comments fix: Jump presets cs3d (#2812) * feat: Add JumpPreset to OHIF for Cornerstone3D * fix: accessing viewport service from servicesManager feat: volume API and TMTV mode (#2814) * fix: do not display overlays on mip viewports * feat: add optional disableCommands for toggle buttons * fix: toggleCrossharis * feat: config the crosshairs * feat: add PetSUV Panel for changing metadata * feat: initial work for the rectangleROIThreshold panel * wip: measurement service * roi threshold working * feat: Add displayText to segmentations * feat: add remove segmentation * feat: add csv export * feat: add RT export for annotations in tmtv mode * fix: fusion to use pt in tmtv mode and measuremet mappings * fix: various bugs * apply review comments * apply review comments * feat: add fusion color maps * add readme to tmtv mode * Update README.md * fix: try to fix build * fix unit tests * Update README.md * feat: add about to the panel * fix: changing strategy in roi panel * apply review comments * update package versions * wip for stackPrefetch * fix: cornerstone3d hydration and renaming (#2818) * update readme * renamed cornerstone extension * wip for renaming cornerstone3D variables * wip for renaming cornerstone3D variables * wip for fixing bugs for SR viewport * fix: jumpToMeasurement and initial label after hydration * fix: fileName capitalization * feat: use the new prefetch stack in the cs3d (#2820) * feat: use the new prefetch stack in the cs3d * use viewport scroll api for stack viewport * fix cine stop when scrollbar changes * feat: use new prefetch events * fix loading state to not show repeatedly * fix: various bugs for tmtv mode thresholding and new icons (#2823) * feat: make tmtv mode available in worklist * feat: add new icons for tmtv mode * feat: add fusion color icon * fix: parallel scale calculation * fix: bump Cornerstone to get large image support working * fix: Fix issues with CPU viewport flipping, including config files * fix: bump cornerstone to fix magnify tool * fix: Bump Cornerstone version to fix resetCamera issue in StackViewport * ci: Add _headers file to enable CORS headers for Netlify Drag/Drop deploys * fix: WADO-URI was not working. PET Metadata was coming from the wrong place. Fixed some minor React errors * feat(OHIF):Allow modes and extensions to be added after commpile time. (#2838) Also works with the compile time add that the existing cli uses, so that both build types work. Eric and I agreed this doesn't change existing functionality, but is almost entirely build issues/fixes. * bump: dependency versions to fix hydration bugs (#2848) * bump: dcmjs version to fix hydration bugs * try to fix tests * bump dependency versions Co-authored-by: Alireza <ar.sedghi@gmail.com> Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
668 files changed
+41669
-13410
No files matched your search
@@ -0,0 +1,262 @@
|
||||
import AnnotationToPointData from './measurements/AnnotationToPointData';
|
||||
import dcmjs from 'dcmjs';
|
||||
import { DicomMetadataStore } from '@ohif/core';
|
||||
|
||||
const { DicomMetaDictionary } = dcmjs.data;
|
||||
|
||||
export default class RTSSReport {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* 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 generateReport(annotations, metadataProvider, options) {
|
||||
let dataset = initializeDataset(annotations, 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, metadataProvider)
|
||||
);
|
||||
|
||||
// ReferencedSeriesSequence
|
||||
// Todo: handle more than one series
|
||||
dataset.ReferencedSeriesSequence = getReferencedSeriesSequence(
|
||||
annotation,
|
||||
index,
|
||||
metadataProvider
|
||||
);
|
||||
|
||||
// ReferencedFrameOfReferenceSequence
|
||||
dataset.ReferencedFrameOfReferenceSequence = getReferencedFrameOfReferenceSequence(
|
||||
annotation,
|
||||
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 = {}) {
|
||||
// Todo
|
||||
console.warn('RTSSReport.generateToolState not implemented');
|
||||
}
|
||||
}
|
||||
|
||||
function initializeDataset(annotations, metadataProvider) {
|
||||
const rtSOPInstanceUID = DicomMetaDictionary.uid();
|
||||
|
||||
// get the first annotation data
|
||||
const {
|
||||
referencedImageId: imageId,
|
||||
FrameOfReferenceUID,
|
||||
} = annotations[0].metadata;
|
||||
|
||||
const { studyInstanceUID } = metadataProvider.get(
|
||||
'generalSeriesModule',
|
||||
imageId
|
||||
);
|
||||
|
||||
const patientModule = getPatientModule(imageId, metadataProvider);
|
||||
const rtSeriesModule = getRTSeriesModule(imageId, metadataProvider);
|
||||
|
||||
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: '',
|
||||
StructureSetName: '',
|
||||
ReferringPhysicianName: '',
|
||||
OperatorsName: '',
|
||||
StructureSetDate: DicomMetaDictionary.date(),
|
||||
StructureSetTime: DicomMetaDictionary.time(),
|
||||
};
|
||||
}
|
||||
|
||||
function getPatientModule(imageId, metadataProvider) {
|
||||
const generalSeriesModule = metadataProvider.get(
|
||||
'generalSeriesModule',
|
||||
imageId
|
||||
);
|
||||
const generalStudyModule = metadataProvider.get(
|
||||
'generalStudyModule',
|
||||
imageId
|
||||
);
|
||||
const patientStudyModule = metadataProvider.get(
|
||||
'patientStudyModule',
|
||||
imageId
|
||||
);
|
||||
const patientModule = metadataProvider.get('patientModule', imageId);
|
||||
const patientDemographicModule = metadataProvider.get(
|
||||
'patientDemographicModule',
|
||||
imageId
|
||||
);
|
||||
|
||||
return {
|
||||
Modality: generalSeriesModule.modality,
|
||||
PatientID: patientModule.patientId,
|
||||
PatientName: patientModule.patientName,
|
||||
PatientBirthDate: '',
|
||||
PatientAge: patientStudyModule.patientAge,
|
||||
PatientSex: patientDemographicModule.patientSex,
|
||||
PatientWeight: patientStudyModule.patientWeight,
|
||||
StudyDate: generalStudyModule.studyDate,
|
||||
StudyTime: generalStudyModule.studyTime,
|
||||
StudyID: 'ToDo',
|
||||
AccessionNumber: generalStudyModule.accessionNumber,
|
||||
};
|
||||
}
|
||||
|
||||
function getReferencedFrameOfReferenceSequence(
|
||||
toolData,
|
||||
metadataProvider,
|
||||
dataset
|
||||
) {
|
||||
const { referencedImageId: imageId, FrameOfReferenceUID } = toolData.metadata;
|
||||
const instance = metadataProvider.get('instance', imageId);
|
||||
const { SeriesInstanceUID } = instance;
|
||||
|
||||
const { ReferencedSeriesSequence } = dataset;
|
||||
|
||||
return [
|
||||
{
|
||||
FrameOfReferenceUID,
|
||||
RTReferencedStudySequence: [
|
||||
{
|
||||
ReferencedSOPClassUID: dataset.SOPClassUID,
|
||||
ReferencedSOPInstanceUID: dataset.SOPInstanceUID,
|
||||
RTReferencedSeriesSequence: [
|
||||
{
|
||||
SeriesInstanceUID,
|
||||
ContourImageSequence: [
|
||||
...ReferencedSeriesSequence[0].ReferencedInstanceSequence,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getReferencedSeriesSequence(toolData, index, metadataProvider) {
|
||||
// grab imageId from toolData
|
||||
const { referencedImageId: imageId } = toolData.metadata;
|
||||
const instance = metadataProvider.get('instance', imageId);
|
||||
const { SeriesInstanceUID, StudyInstanceUID } = instance;
|
||||
|
||||
const ReferencedSeriesSequence = [];
|
||||
if (SeriesInstanceUID) {
|
||||
const series = DicomMetadataStore.getSeries(
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
);
|
||||
|
||||
const ReferencedSeries = {
|
||||
SeriesInstanceUID,
|
||||
ReferencedInstanceSequence: [],
|
||||
};
|
||||
|
||||
series.instances.forEach(instance => {
|
||||
const { SOPInstanceUID, SOPClassUID } = instance;
|
||||
ReferencedSeries.ReferencedInstanceSequence.push({
|
||||
ReferencedSOPClassUID: SOPClassUID,
|
||||
ReferencedSOPInstanceUID: SOPInstanceUID,
|
||||
});
|
||||
});
|
||||
|
||||
ReferencedSeriesSequence.push(ReferencedSeries);
|
||||
}
|
||||
|
||||
return ReferencedSeriesSequence;
|
||||
}
|
||||
|
||||
function getRTSeriesModule(imageId, metadataProvider) {
|
||||
return {
|
||||
SeriesInstanceUID: DicomMetaDictionary.uid(), // generate a new series instance uid
|
||||
SeriesNumber: '99', // Todo:: what should be the series number?
|
||||
};
|
||||
}
|
||||
|
||||
function getStructureSetModule(toolData, index, metadataProvider) {
|
||||
const { FrameOfReferenceUID } = toolData.metadata;
|
||||
|
||||
return {
|
||||
ROINumber: index + 1,
|
||||
ROIName: `Todo: name ${index + 1}`,
|
||||
ROIDescription: `Todo: description ${index + 1}`,
|
||||
ROIGenerationAlgorithm: 'Todo: algorithm',
|
||||
ReferencedFrameOfReferenceUID: FrameOfReferenceUID,
|
||||
};
|
||||
}
|
||||
|
||||
function getRTROIObservationsSequence(toolData, index, metadataProvider) {
|
||||
return {
|
||||
ObservationNumber: index + 1,
|
||||
ReferencedROINumber: index + 1,
|
||||
RTROIInterpretedType: 'Todo: type',
|
||||
ROIInterpreter: 'Todo: interpreter',
|
||||
};
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import RTSSReport from './RTSSReport';
|
||||
import dcmjs from 'dcmjs';
|
||||
import { classes } from '@ohif/core';
|
||||
|
||||
const { datasetToBlob } = dcmjs.data;
|
||||
const metadataProvider = classes.MetadataProvider;
|
||||
|
||||
export default function dicomRTAnnotationExport(annotations) {
|
||||
const dataset = RTSSReport.generateReport(annotations, metadataProvider);
|
||||
const reportBlob = datasetToBlob(dataset);
|
||||
|
||||
//Create a URL for the binary.
|
||||
var objectUrl = URL.createObjectURL(reportBlob);
|
||||
window.location.assign(objectUrl);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import dicomRTAnnotationExport from './dicomRTAnnotationExport';
|
||||
|
||||
export default dicomRTAnnotationExport;
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import RectangleROIStartEndThreshold from './RectangleROIStartEndThreshold';
|
||||
|
||||
function validateAnnotation(annotation) {
|
||||
if (!annotation?.data) {
|
||||
throw new Error('Tool data is empty');
|
||||
}
|
||||
|
||||
if (!annotation.metadata || annotation.metadata.referenceImageId) {
|
||||
throw new Error('Tool data is not associated with any imageId');
|
||||
}
|
||||
}
|
||||
|
||||
class AnnotationToPointData {
|
||||
constructor() {}
|
||||
|
||||
static convert(annotation, index, metadataProvider) {
|
||||
validateAnnotation(annotation);
|
||||
|
||||
const { toolName } = annotation.metadata;
|
||||
const toolClass = AnnotationToPointData.TOOL_NAMES[toolName];
|
||||
|
||||
if (!toolClass) {
|
||||
throw new Error(
|
||||
`Unknown tool type: ${toolName}, cannot convert to RTSSReport`
|
||||
);
|
||||
}
|
||||
|
||||
// Each toolData should become a list of contours, ContourSequence
|
||||
// contains a list of contours with their pointData, their geometry
|
||||
// type and their length.
|
||||
const ContourSequence = toolClass.getContourSequence(
|
||||
annotation,
|
||||
metadataProvider
|
||||
);
|
||||
|
||||
// Todo: random rgb color for now, options should be passed in
|
||||
const color = [
|
||||
Math.floor(Math.random() * 255),
|
||||
Math.floor(Math.random() * 255),
|
||||
Math.floor(Math.random() * 255),
|
||||
];
|
||||
|
||||
return {
|
||||
ReferencedROINumber: index + 1,
|
||||
ROIDisplayColor: color,
|
||||
ContourSequence,
|
||||
};
|
||||
}
|
||||
|
||||
static register(toolClass) {
|
||||
AnnotationToPointData.TOOL_NAMES[toolClass.toolName] = toolClass;
|
||||
}
|
||||
}
|
||||
|
||||
AnnotationToPointData.TOOL_NAMES = {};
|
||||
AnnotationToPointData.register(RectangleROIStartEndThreshold);
|
||||
|
||||
export default AnnotationToPointData;
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// comment
|
||||
class RectangleROIStartEndThreshold {
|
||||
constructor() {}
|
||||
|
||||
static getContourSequence(toolData, metadataProvider) {
|
||||
const { data } = toolData;
|
||||
const { projectionPoints, projectionPointsImageIds } = data.cachedStats;
|
||||
|
||||
return projectionPoints.map((point, index) => {
|
||||
const ContourData = getPointData(point);
|
||||
const ContourImageSequence = getContourImageSequence(
|
||||
projectionPointsImageIds[index],
|
||||
metadataProvider
|
||||
);
|
||||
|
||||
return {
|
||||
NumberOfContourPoints: ContourData.length / 3,
|
||||
ContourImageSequence,
|
||||
ContourGeometricType: 'CLOSED_PLANAR',
|
||||
ContourData,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RectangleROIStartEndThreshold.toolName = 'RectangleROIStartEndThreshold';
|
||||
|
||||
function getPointData(points) {
|
||||
// Since this is a closed contour, the order of the points is important.
|
||||
// re-order the points to be in the correct order clockwise
|
||||
// Spread to make sure Float32Arrays are converted to arrays
|
||||
const orderedPoints = [
|
||||
...points[0],
|
||||
...points[1],
|
||||
...points[3],
|
||||
...points[2],
|
||||
];
|
||||
const pointsArray = orderedPoints.flat();
|
||||
|
||||
// reduce the precision of the points to 2 decimal places
|
||||
const pointsArrayWithPrecision = pointsArray.map(point => {
|
||||
return point.toFixed(2);
|
||||
});
|
||||
|
||||
return pointsArrayWithPrecision;
|
||||
}
|
||||
|
||||
function getContourImageSequence(imageId, metadataProvider) {
|
||||
const sopCommon = metadataProvider.get('sopCommonModule', imageId);
|
||||
|
||||
return {
|
||||
ReferencedSOPClassUID: sopCommon.sopClassUID,
|
||||
ReferencedSOPInstanceUID: sopCommon.sopInstanceUID,
|
||||
};
|
||||
}
|
||||
export default RectangleROIStartEndThreshold;
|
||||
Reference in new issue
Block a user