* [WIP] Fix contour annotations RTSS saving * fix: Export image sop image reference provider * Fixes for download with filename * Updates to fix metadata issues with CS3D * Updates to save menu * Fixing some additional re-save metadata * Add support for saving with predecessor image sequence * PR comment fixes * fix: Load RTSS after save * fix segmentation save * fix load of mixed seg/rt studies * Use frame module to get information on the frame shown * Fix study browser to use instances.length when numImageFrames not present * fix: Load of display set split from multiple non-FOR referenes * Fix RT and SEG applying to wrong series because of using referenced series sequence without checkout sop instances * Fix save seg * Update to released CS3D versions * test: Add revokeObjectURL to test download blob * Add hard dependency on mode-basic to fix build order issues * fix: build * Try to fix build segmentation * PR comments * PR comment update
237 lines
7.1 KiB
TypeScript
237 lines
7.1 KiB
TypeScript
import { utils, Types as OhifTypes } from '@ohif/core';
|
|
import i18n from '@ohif/i18n';
|
|
|
|
import { SOPClassHandlerId } from './id';
|
|
import loadRTStruct from './loadRTStruct';
|
|
|
|
const { sopClassDictionary } = utils;
|
|
|
|
const sopClassUids = [sopClassDictionary.RTStructureSetStorage];
|
|
|
|
const cachedRTStructsSEG = new Set<string>();
|
|
|
|
const loadPromises = {};
|
|
|
|
function _getDisplaySetsFromSeries(
|
|
instances,
|
|
servicesManager: AppTypes.ServicesManager,
|
|
extensionManager
|
|
) {
|
|
utils.sortStudyInstances(instances);
|
|
// Choose the LAST instance in the list as the most recently created one.
|
|
const instance = instances[instances.length - 1];
|
|
|
|
const {
|
|
StudyInstanceUID,
|
|
SeriesInstanceUID,
|
|
SOPInstanceUID,
|
|
SeriesDescription = '',
|
|
SeriesNumber,
|
|
SeriesDate,
|
|
SeriesTime,
|
|
StructureSetDate,
|
|
StructureSetTime,
|
|
SOPClassUID,
|
|
wadoRoot,
|
|
wadoUri,
|
|
wadoUriRoot,
|
|
imageId: predecessorImageId,
|
|
} = instance;
|
|
|
|
const displaySet = {
|
|
Modality: 'RTSTRUCT',
|
|
loading: false,
|
|
isReconstructable: false,
|
|
displaySetInstanceUID: utils.guid(),
|
|
SeriesDescription,
|
|
SeriesNumber,
|
|
/**
|
|
* The "SeriesDate" for a display set is really the display set date, which
|
|
* should be the date of the instance being used, which will be the structure
|
|
* set date in this case.
|
|
*/
|
|
SeriesDate: StructureSetDate || SeriesDate,
|
|
SeriesTime: StructureSetTime || SeriesTime,
|
|
SOPInstanceUID,
|
|
SeriesInstanceUID,
|
|
StudyInstanceUID,
|
|
SOPClassHandlerId,
|
|
SOPClassUID,
|
|
referencedImages: null,
|
|
referencedSeriesInstanceUID: null,
|
|
referencedDisplaySetInstanceUID: null,
|
|
isDerivedDisplaySet: true,
|
|
isLoaded: false,
|
|
isHydrated: false,
|
|
structureSet: null,
|
|
sopClassUids,
|
|
instance,
|
|
instances,
|
|
predecessorImageId,
|
|
wadoRoot,
|
|
wadoUriRoot,
|
|
wadoUri,
|
|
isOverlayDisplaySet: true,
|
|
label: SeriesDescription || `${i18n.t('Series')} ${SeriesNumber} - ${i18n.t('RTSTRUCT')}`,
|
|
};
|
|
|
|
let referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
|
if (
|
|
instance.ReferencedFrameOfReferenceSequence?.RTReferencedStudySequence &&
|
|
!instance.ReferencedSeriesSequence
|
|
) {
|
|
instance.ReferencedSeriesSequence = _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence(
|
|
instance.ReferencedFrameOfReferenceSequence
|
|
);
|
|
referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
|
}
|
|
|
|
if (!referencedSeriesSequence) {
|
|
console.error('ReferencedSeriesSequence is missing for the RTSTRUCT');
|
|
return;
|
|
}
|
|
|
|
const referencedSeries = referencedSeriesSequence[0];
|
|
|
|
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
|
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
|
|
|
const { displaySetService } = servicesManager.services;
|
|
const referencedDisplaySets =
|
|
displaySetService.getDisplaySetsForReferences(referencedSeriesSequence);
|
|
if (referencedDisplaySets?.length > 1) {
|
|
console.warn(
|
|
'Reference applies to more than 1 display set for Contours, applying only to first display set'
|
|
);
|
|
}
|
|
|
|
if (!referencedDisplaySets || referencedDisplaySets.length === 0) {
|
|
// Instead of throwing error, subscribe to display sets added
|
|
const { unsubscribe } = displaySetService.subscribe(
|
|
displaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
|
({ displaySetsAdded }) => {
|
|
const addedDisplaySet = displaySetsAdded[0];
|
|
if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) {
|
|
displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID;
|
|
displaySet.isReconstructable = addedDisplaySet.isReconstructable;
|
|
unsubscribe();
|
|
}
|
|
}
|
|
);
|
|
} else {
|
|
const [referencedDisplaySet] = referencedDisplaySets;
|
|
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
|
displaySet.isReconstructable = referencedDisplaySet.isReconstructable;
|
|
}
|
|
|
|
displaySet.load = ({ headers, createSegmentation = true }) =>
|
|
_load(displaySet, servicesManager, extensionManager, headers, createSegmentation);
|
|
|
|
return [displaySet];
|
|
}
|
|
|
|
function _load(
|
|
rtDisplaySet,
|
|
servicesManager: AppTypes.ServicesManager,
|
|
extensionManager,
|
|
headers,
|
|
createSegmentation = true
|
|
) {
|
|
const { SOPInstanceUID } = rtDisplaySet;
|
|
const { segmentationService } = servicesManager.services;
|
|
|
|
if (
|
|
(rtDisplaySet.loading || rtDisplaySet.isLoaded) &&
|
|
loadPromises[SOPInstanceUID] &&
|
|
cachedRTStructsSEG.has(rtDisplaySet.displaySetInstanceUID)
|
|
) {
|
|
return loadPromises[SOPInstanceUID];
|
|
}
|
|
|
|
rtDisplaySet.loading = true;
|
|
|
|
const { unsubscribe } = segmentationService.subscribe(
|
|
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
|
|
(evt: { rtDisplaySet: { displaySetInstanceUID: string } }) => {
|
|
if (evt.rtDisplaySet?.displaySetInstanceUID === rtDisplaySet.displaySetInstanceUID) {
|
|
cachedRTStructsSEG.add(rtDisplaySet.displaySetInstanceUID);
|
|
unsubscribe();
|
|
}
|
|
}
|
|
);
|
|
|
|
// We don't want to fire multiple loads, so we'll wait for the first to finish
|
|
// and also return the same promise to any other callers.
|
|
loadPromises[SOPInstanceUID] = new Promise<void>(async (resolve, reject) => {
|
|
try {
|
|
if (!rtDisplaySet.structureSet) {
|
|
const structureSet = await loadRTStruct(extensionManager, rtDisplaySet, headers);
|
|
rtDisplaySet.structureSet = structureSet;
|
|
}
|
|
|
|
if (createSegmentation) {
|
|
await segmentationService.createSegmentationForRTDisplaySet(rtDisplaySet);
|
|
}
|
|
|
|
resolve();
|
|
} catch (error) {
|
|
reject(error);
|
|
} finally {
|
|
rtDisplaySet.loading = false;
|
|
}
|
|
});
|
|
|
|
return loadPromises[SOPInstanceUID];
|
|
}
|
|
|
|
function _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence(
|
|
ReferencedFrameOfReferenceSequence
|
|
) {
|
|
const ReferencedSeriesSequence = [];
|
|
|
|
ReferencedFrameOfReferenceSequence.forEach(referencedFrameOfReference => {
|
|
const { RTReferencedStudySequence } = referencedFrameOfReference;
|
|
|
|
RTReferencedStudySequence.forEach(rtReferencedStudy => {
|
|
const { RTReferencedSeriesSequence } = rtReferencedStudy;
|
|
|
|
RTReferencedSeriesSequence.forEach(rtReferencedSeries => {
|
|
const ReferencedInstanceSequence = [];
|
|
const { ContourImageSequence, SeriesInstanceUID } = rtReferencedSeries;
|
|
|
|
ContourImageSequence.forEach(contourImage => {
|
|
ReferencedInstanceSequence.push({
|
|
ReferencedSOPInstanceUID: contourImage.ReferencedSOPInstanceUID,
|
|
ReferencedSOPClassUID: contourImage.ReferencedSOPClassUID,
|
|
});
|
|
});
|
|
|
|
const referencedSeries = {
|
|
SeriesInstanceUID,
|
|
ReferencedInstanceSequence,
|
|
};
|
|
|
|
ReferencedSeriesSequence.push(referencedSeries);
|
|
});
|
|
});
|
|
});
|
|
|
|
return ReferencedSeriesSequence;
|
|
}
|
|
|
|
function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) {
|
|
const { servicesManager, extensionManager } = params;
|
|
|
|
return [
|
|
{
|
|
name: 'dicom-rt',
|
|
sopClassUids,
|
|
getDisplaySetsFromSeries: instances => {
|
|
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
export default getSopClassHandlerModule;
|