Files
ohif-viewer/extensions/default/src/getSopClassHandlerModule.js
T

287 lines
10 KiB
JavaScript
Raw Normal View History

import { utils, classes } from '@ohif/core';
import { id } from './id';
import getDisplaySetMessages from './getDisplaySetMessages';
import getDisplaySetsFromUnsupportedSeries from './getDisplaySetsFromUnsupportedSeries';
import { chartHandler } from './SOPClassHandlers/chartSOPClassHandler';
2020-05-11 18:56:46 +02:00
const { isImage, sopClassDictionary, isDisplaySetReconstructable } = utils;
const { ImageSet } = classes;
const DEFAULT_VOLUME_LOADER_SCHEME = 'cornerstoneStreamingImageVolume';
const DYNAMIC_VOLUME_LOADER_SCHEME = 'cornerstoneStreamingDynamicImageVolume';
2020-05-11 18:56:46 +02:00
const sopClassHandlerName = 'stack';
let appContext = {};
const getDynamicVolumeInfo = instances => {
const { extensionManager } = appContext;
if (!extensionManager) {
throw new Error('extensionManager is not available');
}
const imageIds = instances.map(({ imageId }) => imageId);
const volumeLoaderUtility = extensionManager.getModuleEntry(
'@ohif/extension-cornerstone.utilityModule.volumeLoader'
);
const { getDynamicVolumeInfo: csGetDynamicVolumeInfo } = volumeLoaderUtility.exports;
return csGetDynamicVolumeInfo(imageIds);
};
const isMultiFrame = instance => {
return instance.NumberOfFrames > 1;
};
function getDisplaySetInfo(instances) {
const dynamicVolumeInfo = getDynamicVolumeInfo(instances);
const { isDynamicVolume, timePoints } = dynamicVolumeInfo;
let displaySetInfo;
const { appConfig } = appContext;
if (isDynamicVolume) {
const timePoint = timePoints[0];
const instancesMap = new Map();
// O(n) to convert it into a map and O(1) to find each instance
instances.forEach(instance => instancesMap.set(instance.imageId, instance));
const firstTimePointInstances = timePoint.map(imageId => instancesMap.get(imageId));
displaySetInfo = isDisplaySetReconstructable(firstTimePointInstances, appConfig);
} else {
displaySetInfo = isDisplaySetReconstructable(instances, appConfig);
}
return {
isDynamicVolume,
...displaySetInfo,
dynamicVolumeInfo,
};
}
2020-05-11 18:56:46 +02:00
const makeDisplaySet = instances => {
const instance = instances[0];
const imageSet = new ImageSet(instances);
const {
isDynamicVolume,
value: isReconstructable,
averageSpacingBetweenFrames,
dynamicVolumeInfo,
} = getDisplaySetInfo(instances);
const volumeLoaderSchema = isDynamicVolume
? DYNAMIC_VOLUME_LOADER_SCHEME
: DEFAULT_VOLUME_LOADER_SCHEME;
// set appropriate attributes to image set...
const messages = getDisplaySetMessages(instances, isReconstructable, isDynamicVolume);
imageSet.setAttributes({
volumeLoaderSchema,
2020-05-15 19:07:21 +02:00
displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID
2020-05-11 18:56:46 +02:00
SeriesDate: instance.SeriesDate,
SeriesTime: instance.SeriesTime,
SeriesInstanceUID: instance.SeriesInstanceUID,
StudyInstanceUID: instance.StudyInstanceUID,
2021-08-20 12:14:19 -04:00
SeriesNumber: instance.SeriesNumber || 0,
2020-05-11 18:56:46 +02:00
FrameRate: instance.FrameTime,
SOPClassUID: instance.SOPClassUID,
2021-08-20 12:14:19 -04:00
SeriesDescription: instance.SeriesDescription || '',
2020-05-15 10:05:40 +02:00
Modality: instance.Modality,
isMultiFrame: isMultiFrame(instance),
countIcon: isReconstructable ? 'icon-mpr' : undefined,
2020-05-14 13:04:39 +02:00
numImageFrames: instances.length,
2020-05-11 18:56:46 +02:00
SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`,
isReconstructable,
messages,
averageSpacingBetweenFrames: averageSpacingBetweenFrames || null,
isDynamicVolume,
dynamicVolumeInfo,
});
// Sort the images in this series if needed
const shallSort = true; //!OHIF.utils.ObjectPath.get(Meteor, 'settings.public.ui.sortSeriesByIncomingOrder');
if (shallSort) {
imageSet.sortBy((a, b) => {
// Sort by InstanceNumber (0020,0013)
return (parseInt(a.InstanceNumber) || 0) - (parseInt(b.InstanceNumber) || 0);
});
}
// Include the first image instance number (after sorted)
/*imageSet.setAttribute(
'instanceNumber',
imageSet.getImage(0).InstanceNumber
);*/
/*const isReconstructable = isDisplaySetReconstructable(series, instances);
imageSet.isReconstructable = isReconstructable.value;
if (isReconstructable.missingFrames) {
// TODO -> This is currently unused, but may be used for reconstructing
// Volumes with gaps later on.
imageSet.missingFrames = isReconstructable.missingFrames;
}*/
return imageSet;
};
const isSingleImageModality = modality => {
return modality === 'CR' || modality === 'MG' || modality === 'DX';
};
function getSopClassUids(instances) {
const uniqueSopClassUidsInSeries = new Set();
instances.forEach(instance => {
uniqueSopClassUidsInSeries.add(instance.SOPClassUID);
});
const sopClassUids = Array.from(uniqueSopClassUidsInSeries);
return sopClassUids;
}
/**
* Basic SOPClassHandler:
* - For all Image types that are stackable, create
* a displaySet with a stack of images
*
* @param {SeriesMetadata} series The series metadata object from which the display sets will be created
* @returns {Array} The list of display sets created for the given series object
*/
2020-05-11 18:56:46 +02:00
function getDisplaySetsFromSeries(instances) {
// If the series has no instances, stop here
if (!instances || !instances.length) {
throw new Error('No instances were provided');
}
const displaySets = [];
const sopClassUids = getSopClassUids(instances);
// Search through the instances (InstanceMetadata object) of this series
// Split Multi-frame instances and Single-image modalities
// into their own specific display sets. Place the rest of each
// series into another display set.
const stackableInstances = [];
instances.forEach(instance => {
// All imaging modalities must have a valid value for sopClassUid (x00080016) or rows (x00280010)
if (!isImage(instance.SOPClassUID) && !instance.Rows) {
return;
}
let displaySet;
if (isMultiFrame(instance)) {
displaySet = makeDisplaySet([instance]);
displaySet.setAttributes({
sopClassUids,
numImageFrames: instance.NumberOfFrames,
instanceNumber: instance.InstanceNumber,
acquisitionDatetime: instance.AcquisitionDateTime,
});
displaySets.push(displaySet);
} else if (isSingleImageModality(instance.Modality)) {
displaySet = makeDisplaySet([instance]);
displaySet.setAttributes({
sopClassUids,
instanceNumber: instance.InstanceNumber,
acquisitionDatetime: instance.AcquisitionDateTime,
});
displaySets.push(displaySet);
} else {
stackableInstances.push(instance);
}
});
if (stackableInstances.length) {
const displaySet = makeDisplaySet(stackableInstances);
displaySet.setAttribute('studyInstanceUid', instances[0].StudyInstanceUID);
displaySet.setAttributes({
sopClassUids,
});
displaySets.push(displaySet);
}
return displaySets;
}
const sopClassUids = [
sopClassDictionary.ComputedRadiographyImageStorage,
sopClassDictionary.DigitalXRayImageStorageForPresentation,
sopClassDictionary.DigitalXRayImageStorageForProcessing,
sopClassDictionary.DigitalMammographyXRayImageStorageForPresentation,
sopClassDictionary.DigitalMammographyXRayImageStorageForProcessing,
sopClassDictionary.DigitalIntraOralXRayImageStorageForPresentation,
sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing,
sopClassDictionary.CTImageStorage,
sopClassDictionary.EnhancedCTImageStorage,
sopClassDictionary.LegacyConvertedEnhancedCTImageStorage,
sopClassDictionary.UltrasoundMultiframeImageStorage,
sopClassDictionary.MRImageStorage,
sopClassDictionary.EnhancedMRImageStorage,
sopClassDictionary.EnhancedMRColorImageStorage,
sopClassDictionary.LegacyConvertedEnhancedMRImageStorage,
sopClassDictionary.UltrasoundImageStorage,
sopClassDictionary.UltrasoundImageStorageRET,
sopClassDictionary.SecondaryCaptureImageStorage,
sopClassDictionary.MultiframeSingleBitSecondaryCaptureImageStorage,
sopClassDictionary.MultiframeGrayscaleByteSecondaryCaptureImageStorage,
sopClassDictionary.MultiframeGrayscaleWordSecondaryCaptureImageStorage,
sopClassDictionary.MultiframeTrueColorSecondaryCaptureImageStorage,
sopClassDictionary.XRayAngiographicImageStorage,
sopClassDictionary.EnhancedXAImageStorage,
sopClassDictionary.XRayRadiofluoroscopicImageStorage,
sopClassDictionary.EnhancedXRFImageStorage,
sopClassDictionary.XRay3DAngiographicImageStorage,
sopClassDictionary.XRay3DCraniofacialImageStorage,
sopClassDictionary.BreastTomosynthesisImageStorage,
sopClassDictionary.BreastProjectionXRayImageStorageForPresentation,
sopClassDictionary.BreastProjectionXRayImageStorageForProcessing,
sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForPresentation,
sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForProcessing,
sopClassDictionary.NuclearMedicineImageStorage,
sopClassDictionary.VLEndoscopicImageStorage,
sopClassDictionary.VideoEndoscopicImageStorage,
sopClassDictionary.VLMicroscopicImageStorage,
sopClassDictionary.VideoMicroscopicImageStorage,
sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage,
sopClassDictionary.VLPhotographicImageStorage,
sopClassDictionary.VideoPhotographicImageStorage,
sopClassDictionary.OphthalmicPhotography8BitImageStorage,
sopClassDictionary.OphthalmicPhotography16BitImageStorage,
sopClassDictionary.OphthalmicTomographyImageStorage,
// Handled by another sop class module
// sopClassDictionary.VLWholeSlideMicroscopyImageStorage,
sopClassDictionary.PositronEmissionTomographyImageStorage,
sopClassDictionary.EnhancedPETImageStorage,
sopClassDictionary.LegacyConvertedEnhancedPETImageStorage,
sopClassDictionary.RTImageStorage,
2022-09-20 14:47:18 -04:00
sopClassDictionary.EnhancedUSVolumeStorage,
];
function getSopClassHandlerModule(appContextParam) {
appContext = appContextParam;
return [
{
2020-05-11 18:56:46 +02:00
name: sopClassHandlerName,
sopClassUids,
2020-05-11 18:56:46 +02:00
getDisplaySetsFromSeries,
},
{
name: 'not-supported-display-sets-handler',
sopClassUids: [],
getDisplaySetsFromSeries: getDisplaySetsFromUnsupportedSeries,
},
{
name: chartHandler.name,
sopClassUids: chartHandler.sopClassUids,
getDisplaySetsFromSeries: chartHandler.getDisplaySetsFromSeries,
},
];
}
export default getSopClassHandlerModule;