183 lines
6.5 KiB
TypeScript
183 lines
6.5 KiB
TypeScript
import { metaData, utilities } from '@cornerstonejs/core';
|
|
|
|
import OHIF, { DicomMetadataStore } from '@ohif/core';
|
|
import dcmjs from 'dcmjs';
|
|
import { adaptersSR } from '@cornerstonejs/adapters';
|
|
|
|
import getFilteredCornerstoneToolState from './utils/getFilteredCornerstoneToolState';
|
|
import hydrateStructuredReport from './utils/hydrateStructuredReport';
|
|
|
|
const { MeasurementReport } = adaptersSR.Cornerstone3D;
|
|
const { log } = OHIF;
|
|
|
|
interface Options {
|
|
SeriesDescription?: string;
|
|
SeriesInstanceUID?: string;
|
|
SeriesNumber?: number;
|
|
InstanceNumber?: number;
|
|
SeriesDate?: string;
|
|
SeriesTime?: string;
|
|
}
|
|
|
|
/**
|
|
* @param measurementData An array of measurements from the measurements service
|
|
* that you wish to serialize.
|
|
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
|
|
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
|
*
|
|
*/
|
|
const _generateReport = (measurementData, additionalFindingTypes, options: Options = {}) => {
|
|
const filteredToolState = getFilteredCornerstoneToolState(
|
|
measurementData,
|
|
additionalFindingTypes
|
|
);
|
|
|
|
const report = MeasurementReport.generateReport(filteredToolState, metaData, options);
|
|
|
|
const { dataset } = report;
|
|
|
|
// Set the default character set as UTF-8
|
|
// https://dicom.innolitics.com/ciods/nm-image/sop-common/00080005
|
|
if (typeof dataset.SpecificCharacterSet === 'undefined') {
|
|
dataset.SpecificCharacterSet = 'ISO_IR 192';
|
|
}
|
|
|
|
dataset.InstanceNumber = options.InstanceNumber ?? 1;
|
|
|
|
return dataset;
|
|
};
|
|
|
|
const commandsModule = (props: withAppTypes) => {
|
|
const { servicesManager, extensionManager, commandsManager } = props;
|
|
const { customizationService } = servicesManager.services;
|
|
|
|
const actions = {
|
|
changeColorMeasurement: ({ uid }) => {
|
|
// When this gets supported, it probably belongs in cornerstone, not sr
|
|
throw new Error('Unsupported operation: changeColorMeasurement');
|
|
// const { color } = measurementService.getMeasurement(uid);
|
|
// const rgbaColor = {
|
|
// r: color[0],
|
|
// g: color[1],
|
|
// b: color[2],
|
|
// a: color[3] / 255.0,
|
|
// };
|
|
// colorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => {
|
|
// if (actionId === 'cancel') {
|
|
// return;
|
|
// }
|
|
|
|
// const color = [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0];
|
|
// segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color);
|
|
// });
|
|
},
|
|
|
|
/**
|
|
*
|
|
* @param measurementData An array of measurements from the measurements service
|
|
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
|
|
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
|
* as opposed to Finding Sites.
|
|
* that you wish to serialize.
|
|
*/
|
|
downloadReport: ({ measurementData, additionalFindingTypes, options = {} }) => {
|
|
const srDataset = _generateReport(measurementData, additionalFindingTypes, options);
|
|
const reportBlob = dcmjs.data.datasetToBlob(srDataset);
|
|
|
|
//Create a URL for the binary.
|
|
const objectUrl = URL.createObjectURL(reportBlob);
|
|
window.location.assign(objectUrl);
|
|
},
|
|
|
|
/**
|
|
*
|
|
* @param measurementData An array of measurements from the measurements service
|
|
* that you wish to serialize.
|
|
* @param dataSource The dataSource that you wish to use to persist the data.
|
|
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
|
|
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
|
* @return The naturalized report
|
|
*/
|
|
storeMeasurements: async ({
|
|
measurementData,
|
|
dataSource,
|
|
additionalFindingTypes,
|
|
options = {},
|
|
}) => {
|
|
// Use the @cornerstonejs adapter for converting to/from DICOM
|
|
// But it is good enough for now whilst we only have cornerstone as a datasource.
|
|
log.info('[DICOMSR] storeMeasurements');
|
|
|
|
if (!dataSource || !dataSource.store || !dataSource.store.dicom) {
|
|
log.error('[DICOMSR] datasource has no dataSource.store.dicom endpoint!');
|
|
return Promise.reject({});
|
|
}
|
|
|
|
try {
|
|
const naturalizedReport = _generateReport(measurementData, additionalFindingTypes, options);
|
|
|
|
const { StudyInstanceUID, ContentSequence } = naturalizedReport;
|
|
// The content sequence has 5 or more elements, of which
|
|
// the `[4]` element contains the annotation data, so this is
|
|
// checking that there is some annotation data present.
|
|
if (!ContentSequence?.[4].ContentSequence?.length) {
|
|
console.log('naturalizedReport missing imaging content', naturalizedReport);
|
|
throw new Error('Invalid report, no content');
|
|
}
|
|
if (!naturalizedReport.SOPClassUID) {
|
|
throw new Error('No sop class uid');
|
|
}
|
|
|
|
const onBeforeDicomStore = customizationService.getCustomization('onBeforeDicomStore');
|
|
|
|
let dicomDict;
|
|
if (typeof onBeforeDicomStore === 'function') {
|
|
dicomDict = onBeforeDicomStore({ dicomDict, measurementData, naturalizedReport });
|
|
}
|
|
|
|
await dataSource.store.dicom(naturalizedReport, null, dicomDict);
|
|
|
|
if (StudyInstanceUID) {
|
|
dataSource.deleteStudyMetadataPromise(StudyInstanceUID);
|
|
}
|
|
|
|
// The "Mode" route listens for DicomMetadataStore changes
|
|
// When a new instance is added, it listens and
|
|
// automatically calls makeDisplaySets
|
|
DicomMetadataStore.addInstances([naturalizedReport], true);
|
|
|
|
return naturalizedReport;
|
|
} catch (error) {
|
|
console.warn(error);
|
|
log.error(`[DICOMSR] Error while saving the measurements: ${error.message}`);
|
|
throw new Error(error.message || 'Error while saving the measurements.');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Loads measurements by hydrating and loading the SR for the given display set instance UID
|
|
* and displays it in the active viewport.
|
|
*/
|
|
hydrateStructuredReport: ({ displaySetInstanceUID }) => {
|
|
return hydrateStructuredReport(
|
|
{ servicesManager, extensionManager, commandsManager },
|
|
displaySetInstanceUID
|
|
);
|
|
},
|
|
};
|
|
|
|
const definitions = {
|
|
downloadReport: actions.downloadReport,
|
|
storeMeasurements: actions.storeMeasurements,
|
|
hydrateStructuredReport: actions.hydrateStructuredReport,
|
|
};
|
|
|
|
return {
|
|
actions,
|
|
definitions,
|
|
defaultContext: 'CORNERSTONE_STRUCTURED_REPORT',
|
|
};
|
|
};
|
|
|
|
export default commandsModule;
|