diff --git a/src/io/import/importDataSources.ts b/src/io/import/importDataSources.ts index 656192b6..bdcd11f9 100644 --- a/src/io/import/importDataSources.ts +++ b/src/io/import/importDataSources.ts @@ -172,20 +172,21 @@ export type ImportDataSourcesResult = Awaited< ReturnType >[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]); +} diff --git a/src/store/datasets-layers.ts b/src/store/datasets-layers.ts index 880a68da..a7625629 100644 --- a/src/store/datasets-layers.ts +++ b/src/store/datasets-layers.ts @@ -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(), @@ -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( diff --git a/src/store/load-data.ts b/src/store/load-data.ts index c0533f34..7d72cb71 100644 --- a/src/store/load-data.ts +++ b/src/store/load-data.ts @@ -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'; @@ -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...', @@ -103,6 +112,71 @@ function useLoadingNotifications() { }; } +function pickBaseDicom(loadableDataSources: Array) { + // 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> +) { + return succeeded.flatMap((result) => { + return result.data.filter(isLoadableResult); + }); +} + +function pickBaseDataSource( + succeeded: Array> +) { + 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> +) { + 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(); @@ -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); } }