Skip to content

Commit

Permalink
Merge pull request Kitware#568 from PaulHax/pick-primary
Browse files Browse the repository at this point in the history
Prefer some DICOMs modalities as primary selection and layer a PET on a CT if it exists
  • Loading branch information
floryst authored Apr 2, 2024
2 parents b570e48 + c19f848 commit b27e73a
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 15 deletions.
19 changes: 10 additions & 9 deletions src/io/import/importDataSources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,20 +172,21 @@ export type ImportDataSourcesResult = Awaited<
ReturnType<typeof importDataSources>
>[number];

export function convertSuccessResultToDataSelection(
result: ImportDataSourcesResult
) {
if (!isSelectable(result)) return null;

const { dataID, dataType } = result.data[0];

export function toDataSelection(loadable: LoadableResult) {
const { dataID, dataType } = loadable;
if (dataType === 'dicom') {
return makeDICOMSelection(dataID);
}

if (dataType === 'image') {
return makeImageSelection(dataID);
}

return null;
}

export function convertSuccessResultToDataSelection(
result: ImportDataSourcesResult
) {
if (!isSelectable(result)) return null;

return toDataSelection(result.data[0]);
}
18 changes: 15 additions & 3 deletions src/store/datasets-layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ export const useLayersStore = defineStore('layer', () => {
[parent, source].map(getImage)
);

if (!sourceImage) {
throw new Error('Failed to load layer image');
}
if (
!vtkBoundingBox.intersects(
parentImage.getBounds(),
Expand Down Expand Up @@ -110,9 +113,18 @@ export const useLayersStore = defineStore('layer', () => {
parent: DataSelection,
source: DataSelection
) {
return useErrorMessage('Failed to build layer', () =>
this._addLayer(parent, source)
);
return useErrorMessage('Failed to build layer', async () => {
try {
await this._addLayer(parent, source);
} catch (error) {
// remove failed layer from parent's layer list
const parentKey = toDataSelectionKey(parent);
this.parentToLayers[parentKey] = this.parentToLayers[parentKey]?.filter(
({ selection }) => !selectionEquals(selection, source)
);
throw error;
}
});
}

function deleteLayer(
Expand Down
82 changes: 79 additions & 3 deletions src/store/load-data.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { partitionResults } from '@/src/core/pipeline';
import { PipelineResultSuccess, partitionResults } from '@/src/core/pipeline';
import { DataSource, getDataSourceName } from '@/src/io/import/dataSource';
import {
ImportDataSourcesResult,
convertSuccessResultToDataSelection,
importDataSources,
toDataSelection,
} from '@/src/io/import/importDataSources';
import {
ImportResult,
LoadableResult,
isLoadableResult,
} from '@/src/io/import/common';
import { useDICOMStore } from '@/src/store/datasets-dicom';
import { useDatasetStore } from '@/src/store/datasets';
import { useMessageStore } from '@/src/store/messages';
import { Maybe } from '@/src/types';
Expand All @@ -14,6 +20,9 @@ import { computed, ref, watch } from 'vue';
import { useToast } from '@/src/composables/useToast';
import { TYPE } from 'vue-toastification';
import { ToastID, ToastOptions } from 'vue-toastification/dist/types/types';
import { useLayersStore } from './datasets-layers';

const BASE_MODALITY_TYPES = ['CT', 'MR', 'DX', 'US'];

const NotificationMessages = {
Loading: 'Loading datasets...',
Expand Down Expand Up @@ -103,6 +112,71 @@ function useLoadingNotifications() {
};
}

function pickBaseDicom(loadableDataSources: Array<LoadableResult>) {
// pick dicom dataset as primary selection if available
const dicoms = loadableDataSources.filter(
({ dataType }) => dataType === 'dicom'
);
// prefer some modalities as base
const dicomStore = useDICOMStore();
return dicoms.find((dicomSource) => {
const volumeInfo = dicomStore.volumeInfo[dicomSource.dataID];
const modality = volumeInfo?.Modality;
return BASE_MODALITY_TYPES.includes(modality);
});
}

function pickLoadableDataSources(
succeeded: Array<PipelineResultSuccess<ImportResult>>
) {
return succeeded.flatMap((result) => {
return result.data.filter(isLoadableResult);
});
}

function pickBaseDataSource(
succeeded: Array<PipelineResultSuccess<ImportResult>>
) {
const loadableDataSources = pickLoadableDataSources(succeeded);
const baseDicom = pickBaseDicom(loadableDataSources);
return baseDicom ?? loadableDataSources[0];
}

function getStudyUID(volumeID: string) {
const dicomStore = useDICOMStore();
const studyKey = dicomStore.volumeStudy[volumeID];
return dicomStore.studyInfo[studyKey].StudyInstanceUID;
}

function loadLayers(
primaryDataSource: LoadableResult,
succeeded: Array<PipelineResultSuccess<ImportResult>>
) {
if (primaryDataSource.dataType !== 'dicom') return;
const dicomDataSources = pickLoadableDataSources(succeeded).filter(
({ dataType }) => dataType === 'dicom'
);
const studyID = getStudyUID(primaryDataSource.dataID);
const otherVolumesInStudy = dicomDataSources.filter((ds) => {
const studyUID = getStudyUID(ds.dataID);
return studyUID === studyID && ds.dataID !== primaryDataSource.dataID;
});
const dicomStore = useDICOMStore();
const primaryModality =
dicomStore.volumeInfo[primaryDataSource.dataID].Modality;
// Look for one PET volume to layer with CT. Only one as there are often multiple "White Balance" corrected PET volumes.
const toLayer = otherVolumesInStudy.find((ds) => {
const otherModality = dicomStore.volumeInfo[ds.dataID].Modality;
return primaryModality === 'CT' && otherModality === 'PT';
});
if (!toLayer) return;

const primarySelection = toDataSelection(primaryDataSource)!;
const layersStore = useLayersStore();
const layerSelection = toDataSelection(toLayer)!;
layersStore.addLayer(primarySelection, layerSelection);
}

const useLoadDataStore = defineStore('loadData', () => {
const { startLoading, stopLoading, setError, isLoading } =
useLoadingNotifications();
Expand Down Expand Up @@ -132,9 +206,11 @@ const useLoadDataStore = defineStore('loadData', () => {
const [succeeded, errored] = partitionResults(results);

if (!dataStore.primarySelection && succeeded.length) {
const selection = convertSuccessResultToDataSelection(succeeded[0]);
const primaryDataSource = pickBaseDataSource(succeeded);
const selection = toDataSelection(primaryDataSource);
if (selection) {
dataStore.setPrimarySelection(selection);
loadLayers(primaryDataSource, succeeded);
}
}

Expand Down

0 comments on commit b27e73a

Please sign in to comment.