Initial commit from prod-batam
This commit is contained in:
703
extensions/tmtv/src/commandsModule.ts
Normal file
703
extensions/tmtv/src/commandsModule.ts
Normal file
@@ -0,0 +1,703 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import * as cs from '@cornerstonejs/core';
|
||||
import * as csTools from '@cornerstonejs/tools';
|
||||
import { classes } from '@ohif/core';
|
||||
import getThresholdValues from './utils/getThresholdValue';
|
||||
import createAndDownloadTMTVReport from './utils/createAndDownloadTMTVReport';
|
||||
|
||||
import dicomRTAnnotationExport from './utils/dicomRTAnnotationExport/RTStructureSet';
|
||||
|
||||
import { getWebWorkerManager } from '@cornerstonejs/core';
|
||||
import { Enums } from '@cornerstonejs/tools';
|
||||
|
||||
const { SegmentationRepresentations } = Enums;
|
||||
|
||||
const metadataProvider = classes.MetadataProvider;
|
||||
const ROI_THRESHOLD_MANUAL_TOOL_IDS = [
|
||||
'RectangleROIStartEndThreshold',
|
||||
'RectangleROIThreshold',
|
||||
'CircleROIStartEndThreshold',
|
||||
];
|
||||
|
||||
const workerManager = getWebWorkerManager();
|
||||
|
||||
const options = {
|
||||
maxWorkerInstances: 1,
|
||||
autoTerminateOnIdle: {
|
||||
enabled: true,
|
||||
idleTimeThreshold: 3000,
|
||||
},
|
||||
};
|
||||
|
||||
// Register the task
|
||||
const workerFn = () => {
|
||||
return new Worker(new URL('./utils/calculateSUVPeakWorker.js', import.meta.url), {
|
||||
name: 'suv-peak-worker', // name used by the browser to name the worker
|
||||
});
|
||||
};
|
||||
|
||||
function getVolumesFromSegmentation(segmentationId) {
|
||||
const csSegmentation = csTools.segmentation.state.getSegmentation(segmentationId);
|
||||
const labelmapData = csSegmentation.representationData[
|
||||
SegmentationRepresentations.Labelmap
|
||||
] as csTools.Types.LabelmapToolOperationDataVolume;
|
||||
|
||||
const { volumeId, referencedVolumeId } = labelmapData;
|
||||
const labelmapVolume = cs.cache.getVolume(volumeId);
|
||||
const referencedVolume = cs.cache.getVolume(referencedVolumeId);
|
||||
|
||||
return { labelmapVolume, referencedVolume };
|
||||
}
|
||||
|
||||
function getLabelmapVolumeFromSegmentation(segmentation) {
|
||||
const { representationData } = segmentation;
|
||||
const { volumeId } = representationData[
|
||||
SegmentationRepresentations.Labelmap
|
||||
] as csTools.Types.LabelmapToolOperationDataVolume;
|
||||
|
||||
return cs.cache.getVolume(volumeId);
|
||||
}
|
||||
|
||||
const commandsModule = ({ servicesManager, commandsManager, extensionManager }: withAppTypes) => {
|
||||
const {
|
||||
viewportGridService,
|
||||
uiNotificationService,
|
||||
displaySetService,
|
||||
hangingProtocolService,
|
||||
toolGroupService,
|
||||
cornerstoneViewportService,
|
||||
segmentationService,
|
||||
} = servicesManager.services;
|
||||
|
||||
const utilityModule = extensionManager.getModuleEntry(
|
||||
'@ohif/extension-cornerstone.utilityModule.common'
|
||||
);
|
||||
|
||||
const { getEnabledElement } = utilityModule.exports;
|
||||
|
||||
function _getActiveViewportsEnabledElement() {
|
||||
const { activeViewportId } = viewportGridService.getState();
|
||||
const { element } = getEnabledElement(activeViewportId) || {};
|
||||
const enabledElement = cs.getEnabledElement(element);
|
||||
return enabledElement;
|
||||
}
|
||||
|
||||
function _getAnnotationsSelectedByToolNames(toolNames) {
|
||||
return toolNames.reduce((allAnnotationUIDs, toolName) => {
|
||||
const annotationUIDs =
|
||||
csTools.annotation.selection.getAnnotationsSelectedByToolName(toolName);
|
||||
|
||||
return allAnnotationUIDs.concat(annotationUIDs);
|
||||
}, []);
|
||||
}
|
||||
|
||||
const actions = {
|
||||
getMatchingPTDisplaySet: ({ viewportMatchDetails }) => {
|
||||
// Todo: this is assuming that the hanging protocol has successfully matched
|
||||
// the correct PT. For future, we should have a way to filter out the PTs
|
||||
// that are in the viewer layout (but then we have the problem of the attenuation
|
||||
// corrected PT vs the non-attenuation correct PT)
|
||||
|
||||
let ptDisplaySet = null;
|
||||
for (const [viewportId, viewportDetails] of viewportMatchDetails) {
|
||||
const { displaySetsInfo } = viewportDetails;
|
||||
const displaySets = displaySetsInfo.map(({ displaySetInstanceUID }) =>
|
||||
displaySetService.getDisplaySetByUID(displaySetInstanceUID)
|
||||
);
|
||||
|
||||
if (!displaySets || displaySets.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ptDisplaySet = displaySets.find(displaySet => displaySet.Modality === 'PT');
|
||||
if (ptDisplaySet) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ptDisplaySet;
|
||||
},
|
||||
getPTMetadata: ({ ptDisplaySet }) => {
|
||||
const dataSource = extensionManager.getDataSources()[0];
|
||||
const imageIds = dataSource.getImageIdsForDisplaySet(ptDisplaySet);
|
||||
|
||||
const firstImageId = imageIds[0];
|
||||
const instance = metadataProvider.get('instance', firstImageId);
|
||||
if (instance.Modality !== 'PT') {
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = {
|
||||
SeriesTime: instance.SeriesTime,
|
||||
Modality: instance.Modality,
|
||||
PatientSex: instance.PatientSex,
|
||||
PatientWeight: instance.PatientWeight,
|
||||
RadiopharmaceuticalInformationSequence: {
|
||||
RadionuclideTotalDose:
|
||||
instance.RadiopharmaceuticalInformationSequence[0].RadionuclideTotalDose,
|
||||
RadionuclideHalfLife:
|
||||
instance.RadiopharmaceuticalInformationSequence[0].RadionuclideHalfLife,
|
||||
RadiopharmaceuticalStartTime:
|
||||
instance.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartTime,
|
||||
RadiopharmaceuticalStartDateTime:
|
||||
instance.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartDateTime,
|
||||
},
|
||||
};
|
||||
|
||||
return metadata;
|
||||
},
|
||||
createNewLabelmapFromPT: async ({ label }) => {
|
||||
// Create a segmentation of the same resolution as the source data
|
||||
// using volumeLoader.createAndCacheDerivedVolume.
|
||||
|
||||
const { viewportMatchDetails } = hangingProtocolService.getMatchDetails();
|
||||
|
||||
const ptDisplaySet = actions.getMatchingPTDisplaySet({
|
||||
viewportMatchDetails,
|
||||
});
|
||||
|
||||
let withPTViewportId = null;
|
||||
|
||||
for (const [viewportId, { displaySetsInfo }] of viewportMatchDetails.entries()) {
|
||||
const isPT = displaySetsInfo.some(
|
||||
({ displaySetInstanceUID }) =>
|
||||
displaySetInstanceUID === ptDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
|
||||
if (isPT) {
|
||||
withPTViewportId = viewportId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ptDisplaySet) {
|
||||
uiNotificationService.error('No matching PT display set found');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentSegmentations =
|
||||
segmentationService.getSegmentationRepresentations(withPTViewportId);
|
||||
|
||||
const displaySet = displaySetService.getDisplaySetByUID(ptDisplaySet.displaySetInstanceUID);
|
||||
|
||||
const segmentationId = await segmentationService.createLabelmapForDisplaySet(displaySet, {
|
||||
label: `Segmentation ${currentSegmentations.length + 1}`,
|
||||
segments: { 1: { label: 'Segment 1', active: true } },
|
||||
});
|
||||
|
||||
segmentationService.addSegmentationRepresentation(withPTViewportId, {
|
||||
segmentationId,
|
||||
});
|
||||
|
||||
return segmentationId;
|
||||
},
|
||||
thresholdSegmentationByRectangleROITool: ({ segmentationId, config, segmentIndex }) => {
|
||||
const segmentation = csTools.segmentation.state.getSegmentation(segmentationId);
|
||||
|
||||
const { representationData } = segmentation;
|
||||
const { displaySetMatchDetails: matchDetails } = hangingProtocolService.getMatchDetails();
|
||||
const volumeLoaderScheme = 'cornerstoneStreamingImageVolume'; // Loader id which defines which volume loader to use
|
||||
|
||||
const ctDisplaySet = matchDetails.get('ctDisplaySet');
|
||||
const ctVolumeId = `${volumeLoaderScheme}:${ctDisplaySet.displaySetInstanceUID}`; // VolumeId with loader id + volume id
|
||||
|
||||
const { volumeId: segVolumeId } = representationData[
|
||||
SegmentationRepresentations.Labelmap
|
||||
] as csTools.Types.LabelmapToolOperationDataVolume;
|
||||
const { referencedVolumeId } = cs.cache.getVolume(segVolumeId);
|
||||
|
||||
const annotationUIDs = _getAnnotationsSelectedByToolNames(ROI_THRESHOLD_MANUAL_TOOL_IDS);
|
||||
|
||||
if (annotationUIDs.length === 0) {
|
||||
uiNotificationService.show({
|
||||
title: 'Commands Module',
|
||||
message: 'No ROIThreshold Tool is Selected',
|
||||
type: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const labelmapVolume = cs.cache.getVolume(segmentationId);
|
||||
let referencedVolume = cs.cache.getVolume(referencedVolumeId);
|
||||
const ctReferencedVolume = cs.cache.getVolume(ctVolumeId);
|
||||
|
||||
// check if viewport is
|
||||
|
||||
if (!referencedVolume) {
|
||||
throw new Error('No Reference volume found');
|
||||
}
|
||||
|
||||
if (!labelmapVolume) {
|
||||
throw new Error('No Reference labelmap found');
|
||||
}
|
||||
|
||||
const annotation = csTools.annotation.state.getAnnotation(annotationUIDs[0]);
|
||||
|
||||
const {
|
||||
metadata: {
|
||||
enabledElement: { viewport },
|
||||
},
|
||||
} = annotation;
|
||||
|
||||
const showingReferenceVolume = viewport.hasVolumeId(referencedVolumeId);
|
||||
|
||||
if (!showingReferenceVolume) {
|
||||
// if the reference volume is not being displayed, we can't
|
||||
// rely on it for thresholding, we have couple of options here
|
||||
// 1. We choose whatever volume is being displayed
|
||||
// 2. We check if it is a fusion viewport, we pick the volume
|
||||
// that matches the size and dimensions of the labelmap. This might
|
||||
// happen if the 4D PT is converted to a computed volume and displayed
|
||||
// and wants to threshold the labelmap
|
||||
// 3. We throw an error
|
||||
const displaySetInstanceUIDs = viewportGridService.getDisplaySetsUIDsForViewport(
|
||||
viewport.id
|
||||
);
|
||||
|
||||
displaySetInstanceUIDs.forEach(displaySetInstanceUID => {
|
||||
const volume = cs.cache
|
||||
.getVolumes()
|
||||
.find(volume => volume.volumeId.includes(displaySetInstanceUID));
|
||||
|
||||
if (
|
||||
cs.utilities.isEqual(volume.dimensions, labelmapVolume.dimensions) &&
|
||||
cs.utilities.isEqual(volume.spacing, labelmapVolume.spacing)
|
||||
) {
|
||||
referencedVolume = volume;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const { ptLower, ptUpper, ctLower, ctUpper } = getThresholdValues(
|
||||
annotationUIDs,
|
||||
[referencedVolume, ctReferencedVolume],
|
||||
config
|
||||
);
|
||||
|
||||
return csTools.utilities.segmentation.rectangleROIThresholdVolumeByRange(
|
||||
annotationUIDs,
|
||||
labelmapVolume,
|
||||
[
|
||||
{ volume: referencedVolume, lower: ptLower, upper: ptUpper },
|
||||
{ volume: ctReferencedVolume, lower: ctLower, upper: ctUpper },
|
||||
],
|
||||
{ overwrite: true, segmentIndex }
|
||||
);
|
||||
},
|
||||
calculateSuvPeak: async ({ segmentationId, segmentIndex }) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
|
||||
const { representationData } = segmentation;
|
||||
const { volumeId, referencedVolumeId } = representationData[
|
||||
SegmentationRepresentations.Labelmap
|
||||
] as csTools.Types.LabelmapToolOperationDataVolume;
|
||||
|
||||
const labelmap = cs.cache.getVolume(volumeId);
|
||||
const referencedVolume = cs.cache.getVolume(referencedVolumeId);
|
||||
|
||||
// if we put it in the top, it will appear in other modes
|
||||
workerManager.registerWorker('suv-peak-worker', workerFn, options);
|
||||
|
||||
const annotationUIDs = _getAnnotationsSelectedByToolNames(ROI_THRESHOLD_MANUAL_TOOL_IDS);
|
||||
|
||||
const annotations = annotationUIDs.map(annotationUID =>
|
||||
csTools.annotation.state.getAnnotation(annotationUID)
|
||||
);
|
||||
|
||||
const labelmapProps = {
|
||||
dimensions: labelmap.dimensions,
|
||||
origin: labelmap.origin,
|
||||
direction: labelmap.direction,
|
||||
spacing: labelmap.spacing,
|
||||
metadata: labelmap.metadata,
|
||||
scalarData: labelmap.voxelManager.getCompleteScalarDataArray(),
|
||||
};
|
||||
|
||||
const referenceVolumeProps = {
|
||||
dimensions: referencedVolume.dimensions,
|
||||
origin: referencedVolume.origin,
|
||||
direction: referencedVolume.direction,
|
||||
spacing: referencedVolume.spacing,
|
||||
metadata: referencedVolume.metadata,
|
||||
scalarData: referencedVolume.voxelManager.getCompleteScalarDataArray(),
|
||||
};
|
||||
|
||||
// metadata in annotations has enabledElement which is not serializable
|
||||
// we need to remove it
|
||||
// Todo: we should probably have a sanitization function for this
|
||||
const annotationsToSend = annotations.map(annotation => {
|
||||
return {
|
||||
...annotation,
|
||||
metadata: {
|
||||
...annotation.metadata,
|
||||
enabledElement: {
|
||||
...annotation.metadata.enabledElement,
|
||||
viewport: null,
|
||||
renderingEngine: null,
|
||||
element: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const suvPeak =
|
||||
(await workerManager.executeTask('suv-peak-worker', 'calculateSuvPeak', {
|
||||
labelmapProps,
|
||||
referenceVolumeProps,
|
||||
annotations: annotationsToSend,
|
||||
segmentIndex,
|
||||
})) || {};
|
||||
|
||||
return {
|
||||
suvPeak: suvPeak.mean,
|
||||
suvMax: suvPeak.max,
|
||||
suvMaxIJK: suvPeak.maxIJK,
|
||||
suvMaxLPS: suvPeak.maxLPS,
|
||||
};
|
||||
},
|
||||
getLesionStats: ({ segmentationId, segmentIndex = 1 }) => {
|
||||
const { labelmapVolume, referencedVolume } = getVolumesFromSegmentation(segmentationId);
|
||||
const { voxelManager: segVoxelManager, imageData, spacing } = labelmapVolume;
|
||||
const { voxelManager: refVoxelManager } = referencedVolume;
|
||||
|
||||
let segmentationMax = -Infinity;
|
||||
let segmentationMin = Infinity;
|
||||
const segmentationValues = [];
|
||||
let voxelCount = 0;
|
||||
|
||||
const callback = ({ value, index }) => {
|
||||
if (value === segmentIndex) {
|
||||
const refValue = refVoxelManager.getAtIndex(index) as number;
|
||||
segmentationValues.push(refValue);
|
||||
if (refValue > segmentationMax) {
|
||||
segmentationMax = refValue;
|
||||
}
|
||||
if (refValue < segmentationMin) {
|
||||
segmentationMin = refValue;
|
||||
}
|
||||
voxelCount++;
|
||||
}
|
||||
};
|
||||
|
||||
segVoxelManager.forEach(callback, { imageData });
|
||||
const mean = segmentationValues.reduce((a, b) => a + b, 0) / voxelCount;
|
||||
const stats = {
|
||||
minValue: segmentationMin,
|
||||
maxValue: segmentationMax,
|
||||
meanValue: mean,
|
||||
stdValue: Math.sqrt(
|
||||
segmentationValues.map(k => (k - mean) ** 2).reduce((acc, curr) => acc + curr, 0) /
|
||||
voxelCount
|
||||
),
|
||||
volume: voxelCount * spacing[0] * spacing[1] * spacing[2] * 1e-3,
|
||||
};
|
||||
|
||||
return stats;
|
||||
},
|
||||
calculateLesionGlycolysis: ({ lesionStats }) => {
|
||||
const { meanValue, volume } = lesionStats;
|
||||
|
||||
return {
|
||||
lesionGlyoclysisStats: volume * meanValue,
|
||||
};
|
||||
},
|
||||
calculateTMTV: async ({ segmentations }) => {
|
||||
const labelmapProps = segmentations.map(segmentation => {
|
||||
const labelmap = getLabelmapVolumeFromSegmentation(segmentation);
|
||||
return {
|
||||
dimensions: labelmap.dimensions,
|
||||
spacing: labelmap.spacing,
|
||||
scalarData: labelmap.voxelManager.getCompleteScalarDataArray(),
|
||||
origin: labelmap.origin,
|
||||
direction: labelmap.direction,
|
||||
};
|
||||
});
|
||||
|
||||
if (!labelmapProps.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return await workerManager.executeTask('suv-peak-worker', 'calculateTMTV', labelmapProps);
|
||||
},
|
||||
exportTMTVReportCSV: async ({ segmentations, tmtv, config, options }) => {
|
||||
const segReport = commandsManager.runCommand('getSegmentationCSVReport', {
|
||||
segmentations,
|
||||
});
|
||||
|
||||
const tlg = await actions.getTotalLesionGlycolysis({ segmentations });
|
||||
const additionalReportRows = [
|
||||
{ key: 'Total Lesion Glycolysis', value: { tlg: tlg.toFixed(4) } },
|
||||
{ key: 'Threshold Configuration', value: { ...config } },
|
||||
];
|
||||
|
||||
if (tmtv !== undefined) {
|
||||
additionalReportRows.unshift({
|
||||
key: 'Total Metabolic Tumor Volume',
|
||||
value: { tmtv },
|
||||
});
|
||||
}
|
||||
|
||||
createAndDownloadTMTVReport(segReport, additionalReportRows, options);
|
||||
},
|
||||
getTotalLesionGlycolysis: async ({ segmentations }) => {
|
||||
const labelmapProps = segmentations.map(segmentation => {
|
||||
const labelmap = getLabelmapVolumeFromSegmentation(segmentation);
|
||||
return {
|
||||
dimensions: labelmap.dimensions,
|
||||
spacing: labelmap.spacing,
|
||||
scalarData: labelmap.voxelManager.getCompleteScalarDataArray(),
|
||||
origin: labelmap.origin,
|
||||
direction: labelmap.direction,
|
||||
};
|
||||
});
|
||||
|
||||
const { referencedVolume: ptVolume } = getVolumesFromSegmentation(
|
||||
segmentations[0].segmentationId
|
||||
);
|
||||
|
||||
const ptVolumeProps = {
|
||||
dimensions: ptVolume.dimensions,
|
||||
spacing: ptVolume.spacing,
|
||||
scalarData: ptVolume.voxelManager.getCompleteScalarDataArray(),
|
||||
origin: ptVolume.origin,
|
||||
direction: ptVolume.direction,
|
||||
};
|
||||
|
||||
return await workerManager.executeTask('suv-peak-worker', 'getTotalLesionGlycolysis', {
|
||||
labelmapProps,
|
||||
referenceVolumeProps: ptVolumeProps,
|
||||
});
|
||||
},
|
||||
setStartSliceForROIThresholdTool: () => {
|
||||
const { viewport } = _getActiveViewportsEnabledElement();
|
||||
const { focalPoint } = viewport.getCamera();
|
||||
|
||||
const selectedAnnotationUIDs = _getAnnotationsSelectedByToolNames(
|
||||
ROI_THRESHOLD_MANUAL_TOOL_IDS
|
||||
);
|
||||
|
||||
const annotationUID = selectedAnnotationUIDs[0];
|
||||
|
||||
const annotation = csTools.annotation.state.getAnnotation(annotationUID);
|
||||
|
||||
// set the current focal point
|
||||
annotation.data.startCoordinate = focalPoint;
|
||||
// IMPORTANT: invalidate the toolData for the cached stat to get updated
|
||||
// and re-calculate the projection points
|
||||
annotation.invalidated = true;
|
||||
viewport.render();
|
||||
},
|
||||
setEndSliceForROIThresholdTool: () => {
|
||||
const { viewport } = _getActiveViewportsEnabledElement();
|
||||
|
||||
const selectedAnnotationUIDs = _getAnnotationsSelectedByToolNames(
|
||||
ROI_THRESHOLD_MANUAL_TOOL_IDS
|
||||
);
|
||||
|
||||
const annotationUID = selectedAnnotationUIDs[0];
|
||||
|
||||
const annotation = csTools.annotation.state.getAnnotation(annotationUID);
|
||||
|
||||
// get the current focal point
|
||||
const focalPointToEnd = viewport.getCamera().focalPoint;
|
||||
annotation.data.endCoordinate = focalPointToEnd;
|
||||
|
||||
// IMPORTANT: invalidate the toolData for the cached stat to get updated
|
||||
// and re-calculate the projection points
|
||||
annotation.invalidated = true;
|
||||
|
||||
viewport.render();
|
||||
},
|
||||
createTMTVRTReport: () => {
|
||||
// get all Rectangle ROI annotation
|
||||
const stateManager = csTools.annotation.state.getAnnotationManager();
|
||||
|
||||
const annotations = [];
|
||||
|
||||
Object.keys(stateManager.annotations).forEach(frameOfReferenceUID => {
|
||||
const forAnnotations = stateManager.annotations[frameOfReferenceUID];
|
||||
const ROIAnnotations = ROI_THRESHOLD_MANUAL_TOOL_IDS.reduce(
|
||||
(annotations, toolName) => [...annotations, ...(forAnnotations[toolName] ?? [])],
|
||||
[]
|
||||
);
|
||||
|
||||
annotations.push(...ROIAnnotations);
|
||||
});
|
||||
|
||||
commandsManager.runCommand('exportRTReportForAnnotations', {
|
||||
annotations,
|
||||
});
|
||||
},
|
||||
getSegmentationCSVReport: ({ segmentations }) => {
|
||||
if (!segmentations || !segmentations.length) {
|
||||
segmentations = segmentationService.getSegmentations();
|
||||
}
|
||||
|
||||
const report = {};
|
||||
|
||||
for (const segmentation of segmentations) {
|
||||
const { label, segmentationId, representationData } =
|
||||
segmentation as csTools.Types.Segmentation;
|
||||
const id = segmentationId;
|
||||
|
||||
const segReport = { id, label };
|
||||
|
||||
if (!representationData) {
|
||||
report[id] = segReport;
|
||||
continue;
|
||||
}
|
||||
|
||||
const { cachedStats } = segmentation.segments[1] || {}; // Assuming we want stats from the first segment
|
||||
|
||||
if (cachedStats) {
|
||||
Object.entries(cachedStats).forEach(([key, value]) => {
|
||||
if (typeof value !== 'object') {
|
||||
segReport[key] = value;
|
||||
} else {
|
||||
Object.entries(value).forEach(([subKey, subValue]) => {
|
||||
const newKey = `${key}_${subKey}`;
|
||||
segReport[newKey] = subValue;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const labelmapVolume =
|
||||
segmentation.representationData[SegmentationRepresentations.Labelmap];
|
||||
|
||||
if (!labelmapVolume) {
|
||||
report[id] = segReport;
|
||||
continue;
|
||||
}
|
||||
|
||||
const referencedVolumeId = labelmapVolume.referencedVolumeId;
|
||||
|
||||
const referencedVolume = cs.cache.getVolume(referencedVolumeId);
|
||||
|
||||
if (!referencedVolume) {
|
||||
report[id] = segReport;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!referencedVolume.imageIds || !referencedVolume.imageIds.length) {
|
||||
report[id] = segReport;
|
||||
continue;
|
||||
}
|
||||
|
||||
const firstImageId = referencedVolume.imageIds[0];
|
||||
const instance = OHIF.classes.MetadataProvider.get('instance', firstImageId);
|
||||
|
||||
if (!instance) {
|
||||
report[id] = segReport;
|
||||
continue;
|
||||
}
|
||||
|
||||
report[id] = {
|
||||
...segReport,
|
||||
PatientID: instance.PatientID ?? '000000',
|
||||
PatientName: instance.PatientName.Alphabetic,
|
||||
StudyInstanceUID: instance.StudyInstanceUID,
|
||||
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||
StudyDate: instance.StudyDate,
|
||||
};
|
||||
}
|
||||
|
||||
return report;
|
||||
},
|
||||
exportRTReportForAnnotations: ({ annotations }) => {
|
||||
dicomRTAnnotationExport(annotations);
|
||||
},
|
||||
setFusionPTColormap: ({ toolGroupId, colormap }) => {
|
||||
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
|
||||
|
||||
if (!toolGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { viewportMatchDetails } = hangingProtocolService.getMatchDetails();
|
||||
|
||||
const ptDisplaySet = actions.getMatchingPTDisplaySet({
|
||||
viewportMatchDetails,
|
||||
});
|
||||
|
||||
if (!ptDisplaySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fusionViewportIds = toolGroup.getViewportIds();
|
||||
|
||||
const viewports = [];
|
||||
fusionViewportIds.forEach(viewportId => {
|
||||
commandsManager.runCommand('setViewportColormap', {
|
||||
viewportId,
|
||||
displaySetInstanceUID: ptDisplaySet.displaySetInstanceUID,
|
||||
colormap: {
|
||||
name: colormap,
|
||||
},
|
||||
});
|
||||
|
||||
viewports.push(cornerstoneViewportService.getCornerstoneViewport(viewportId));
|
||||
});
|
||||
|
||||
viewports.forEach(viewport => {
|
||||
viewport.render();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
setEndSliceForROIThresholdTool: {
|
||||
commandFn: actions.setEndSliceForROIThresholdTool,
|
||||
},
|
||||
setStartSliceForROIThresholdTool: {
|
||||
commandFn: actions.setStartSliceForROIThresholdTool,
|
||||
},
|
||||
getMatchingPTDisplaySet: {
|
||||
commandFn: actions.getMatchingPTDisplaySet,
|
||||
},
|
||||
getPTMetadata: {
|
||||
commandFn: actions.getPTMetadata,
|
||||
},
|
||||
createNewLabelmapFromPT: {
|
||||
commandFn: actions.createNewLabelmapFromPT,
|
||||
},
|
||||
thresholdSegmentationByRectangleROITool: {
|
||||
commandFn: actions.thresholdSegmentationByRectangleROITool,
|
||||
},
|
||||
getTotalLesionGlycolysis: {
|
||||
commandFn: actions.getTotalLesionGlycolysis,
|
||||
},
|
||||
calculateSuvPeak: {
|
||||
commandFn: actions.calculateSuvPeak,
|
||||
},
|
||||
getLesionStats: {
|
||||
commandFn: actions.getLesionStats,
|
||||
},
|
||||
calculateTMTV: {
|
||||
commandFn: actions.calculateTMTV,
|
||||
},
|
||||
exportTMTVReportCSV: {
|
||||
commandFn: actions.exportTMTVReportCSV,
|
||||
},
|
||||
createTMTVRTReport: {
|
||||
commandFn: actions.createTMTVRTReport,
|
||||
},
|
||||
getSegmentationCSVReport: {
|
||||
commandFn: actions.getSegmentationCSVReport,
|
||||
},
|
||||
exportRTReportForAnnotations: {
|
||||
commandFn: actions.exportRTReportForAnnotations,
|
||||
},
|
||||
setFusionPTColormap: {
|
||||
commandFn: actions.setFusionPTColormap,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
actions,
|
||||
definitions,
|
||||
defaultContext: 'TMTV:CORNERSTONE',
|
||||
};
|
||||
};
|
||||
|
||||
export default commandsModule;
|
||||
Reference in New Issue
Block a user