Skip to content

Commit

Permalink
feat: unify data IDs
Browse files Browse the repository at this point in the history
Volume keys are now used as image data IDs. This simplifies going back
and forth between the image and dicom store, and simplifies the
structure of the selection object to be just an ID string.
  • Loading branch information
floryst committed May 21, 2024
1 parent a7b6290 commit 1f02f96
Show file tree
Hide file tree
Showing 26 changed files with 178 additions and 359 deletions.
5 changes: 2 additions & 3 deletions src/components/DataBrowser.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { computed, defineComponent, ref, watch } from 'vue';
import { isRegularImage } from '@/src/utils/dataSelection';
import SampleDataBrowser from './SampleDataBrowser.vue';
import { useDicomWebStore } from '../store/dicom-web/dicom-web-store';
import ImageDataBrowser from './ImageDataBrowser.vue';
Expand Down Expand Up @@ -42,9 +43,7 @@ export default defineComponent({
);
const hasAnonymousImages = computed(
() =>
imageStore.idList.filter((id) => !(id in dicomStore.imageIDToVolumeKey))
.length > 0
() => imageStore.idList.filter((id) => isRegularImage(id)).length > 0
);
const panels = ref<string[]>([SAMPLE_DATA_KEY, DICOM_WEB_KEY]);
Expand Down
5 changes: 3 additions & 2 deletions src/components/DicomQuickInfoButton.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { useDICOMStore } from '@/src/store/datasets-dicom';
import { Maybe } from '@/src/types';
import { isDicomImage } from '@/src/utils/dataSelection';
import { computed, toRef } from 'vue';
interface Props {
Expand All @@ -12,8 +13,8 @@ const imageId = toRef(props, 'imageId');
const dicomStore = useDICOMStore();
const dicomInfo = computed(() => {
if (imageId.value != null && imageId.value in dicomStore.imageIDToVolumeKey) {
const volumeKey = dicomStore.imageIDToVolumeKey[imageId.value];
const volumeKey = imageId.value;
if (volumeKey && isDicomImage(volumeKey)) {
const volumeInfo = dicomStore.volumeInfo[volumeKey];
const studyKey = dicomStore.volumeStudy[volumeKey];
const studyInfo = dicomStore.studyInfo[studyKey];
Expand Down
35 changes: 12 additions & 23 deletions src/components/ImageDataBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ import ImageListCard from '@/src/components/ImageListCard.vue';
import { createVTKImageThumbnailer } from '@/src/core/thumbnailers/vtk-image';
import { useSegmentGroupStore } from '@/src/store/segmentGroups';
import {
DataSelection,
ImageSelection,
isRegularImage,
type DataSelection,
selectionEquals,
} from '@/src/utils/dataSelection';
import { useImageStore } from '../store/datasets-images';
import { useDICOMStore } from '../store/datasets-dicom';
import { useDatasetStore } from '../store/datasets';
import { useMultiSelection } from '../composables/useMultiSelection';
Expand All @@ -29,39 +28,32 @@ export default defineComponent({
},
setup() {
const imageStore = useImageStore();
const dicomStore = useDICOMStore();
const dataStore = useDatasetStore();
const layersStore = useLayersStore();
const segmentGroupStore = useSegmentGroupStore();
const primarySelection = computed(() => dataStore.primarySelection);
const nonDICOMImages = computed(() =>
imageStore.idList.filter((id) => !(id in dicomStore.imageIDToVolumeKey))
imageStore.idList.filter((id) => isRegularImage(id))
);
const images = computed(() => {
const { metadata } = imageStore;
const layerImages = layersStore
.getLayers(primarySelection.value)
.filter(({ selection }) => selection.type === 'image');
const layerImageIDs = layerImages.map(
({ selection }) => (selection as ImageSelection).dataID
);
.filter(({ selection }) => isRegularImage(selection));
const layerImageIDs = layerImages.map(({ selection }) => selection);
const loadedLayerImageIDs = layerImages
.filter(({ id }) => id in layersStore.layerImages)
.map(({ selection }) => (selection as ImageSelection).dataID);
.map(({ selection }) => selection);
const selectedImageID =
primarySelection.value?.type === 'image' &&
primarySelection.value?.dataID;
isRegularImage(primarySelection.value) && primarySelection.value;
return nonDICOMImages.value.map((id) => {
const selectionKey = {
type: 'image',
dataID: id,
} as DataSelection;
const selectionKey = id as DataSelection;
const isLayer = layerImageIDs.includes(id);
const layerLoaded = loadedLayerImageIDs.includes(id);
const layerLoading = isLayer && !layerLoaded;
Expand Down Expand Up @@ -136,10 +128,7 @@ export default defineComponent({
function convertToLabelMap(key: string) {
if (primarySelection.value) {
segmentGroupStore.convertImageToLabelmap(
{ type: 'image', dataID: key },
primarySelection.value
);
segmentGroupStore.convertImageToLabelmap(key, primarySelection.value);
}
}
Expand Down Expand Up @@ -261,9 +250,9 @@ export default defineComponent({
image.layerable ? convertToLabelMap(image.id) : null
"
>
<v-icon v-if="!image.layerable" class="mr-1"
>mdi-alert</v-icon
>
<v-icon v-if="!image.layerable" class="mr-1">
mdi-alert
</v-icon>
Convert to Segment Group
<v-tooltip
activator="parent"
Expand Down
5 changes: 1 addition & 4 deletions src/components/LayerProperties.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts">
import { computed, defineComponent, PropType, toRefs } from 'vue';
import { getImageID } from '@/src/utils/dataSelection';
import { InitViewSpecs } from '../config';
import { useImageStore } from '../store/datasets-images';
import { BlendConfig } from '../types/views';
Expand All @@ -25,9 +24,7 @@ export default defineComponent({
const imageName = computed(() => {
const { selection } = props.layer;
const imageID = getImageID(selection);
if (imageID === undefined) throw new Error('imageID is undefined');
return imageStore.metadata[imageID].name;
return imageStore.metadata[selection].name;
});
const layerColoringStore = useLayerColoringStore();
Expand Down
2 changes: 1 addition & 1 deletion src/components/PatientBrowser.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { computed, defineComponent, ref, toRefs, watch } from 'vue';
import ItemGroup from '@/src/components/ItemGroup.vue';
import { DataSelection, selectionEquals } from '@/src/utils/dataSelection';
import { type DataSelection, selectionEquals } from '@/src/utils/dataSelection';
import { useDICOMStore } from '../store/datasets-dicom';
import { useDatasetStore } from '../store/datasets';
import { useMultiSelection } from '../composables/useMultiSelection';
Expand Down
17 changes: 6 additions & 11 deletions src/components/PatientStudyVolumeBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { computed, defineComponent, reactive, toRefs, watch } from 'vue';
import { Image } from 'itk-wasm';
import type { PropType } from 'vue';
import GroupableItem from '@/src/components/GroupableItem.vue';
import { DataSelection, DICOMSelection } from '@/src/utils/dataSelection';
import { DataSelection, isDicomImage } from '@/src/utils/dataSelection';
import { getDisplayName, useDICOMStore } from '../store/datasets-dicom';
import { useDatasetStore } from '../store/datasets';
import { useMultiSelection } from '../composables/useMultiSelection';
Expand Down Expand Up @@ -78,21 +78,16 @@ export default defineComponent({
const primarySelection = primarySelectionRef.value;
const layerVolumes = layersStore
.getLayers(primarySelection)
.filter(({ selection }) => selection.type === 'dicom');
const layerVolumeKeys = layerVolumes.map(
({ selection }) => (selection as DICOMSelection).volumeKey
);
.filter(({ selection }) => isDicomImage(selection));
const layerVolumeKeys = layerVolumes.map(({ selection }) => selection);
const loadedLayerVolumeKeys = layerVolumes
.filter(({ id }) => id in layersStore.layerImages)
.map(({ selection }) => (selection as DICOMSelection).volumeKey);
.map(({ selection }) => selection);
const selectedVolumeKey =
primarySelection?.type === 'dicom' && primarySelection.volumeKey;
isDicomImage(primarySelection) && primarySelection;
return volumeKeys.value.map((volumeKey) => {
const selectionKey = {
type: 'dicom',
volumeKey,
} as DataSelection;
const selectionKey = volumeKey as DataSelection;
const isLayer = layerVolumeKeys.includes(volumeKey);
const layerLoaded = loadedLayerVolumeKeys.includes(volumeKey);
const layerLoading = isLayer && !layerLoaded;
Expand Down
8 changes: 3 additions & 5 deletions src/components/SampleDataBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,10 @@ export default defineComponent({
const selection = convertSuccessResultToDataSelection(loadResult);
if (selection) {
const id =
selection.type === 'image' ? selection.dataID : selection.volumeKey;
loaded.idToURL[id] = sample.url;
loaded.urlToID[sample.url] = id;
loaded.idToURL[selection] = sample.url;
loaded.urlToID[sample.url] = selection;
useVolumeColoringStore().setDefaults(id, {
useVolumeColoringStore().setDefaults(selection, {
transferFunction: {
preset: sample.defaults?.colorPreset,
},
Expand Down
8 changes: 8 additions & 0 deletions src/components/SliceViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ import VtkMouseInteractionManipulator from '@/src/components/vtk/VtkMouseInterac
import vtkMouseCameraTrackballPanManipulator from '@kitware/vtk.js/Interaction/Manipulators/MouseCameraTrackballPanManipulator';
import vtkMouseCameraTrackballZoomToMouseManipulator from '@kitware/vtk.js/Interaction/Manipulators/MouseCameraTrackballZoomToMouseManipulator';
import { useResetViewsEvents } from '@/src/components/tools/ResetViews.vue';
import { whenever } from '@vueuse/core';
interface Props extends LayoutViewProps {
viewDirection: LPSAxisDir;
Expand Down Expand Up @@ -216,6 +217,13 @@ const { slice: currentSlice, range: sliceRange } = useSliceConfig(
currentImageID
);
whenever(
computed(() => !isImageLoading.value),
() => {
resetCamera();
}
);
// segmentations
const segmentations = computed(() => {
if (!currentImageID.value) return [];
Expand Down
8 changes: 8 additions & 0 deletions src/components/VolumeViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import VtkOrientationMarker from '@/src/components/vtk/VtkOrientationMarker.vue'
import ViewOverlayGrid from '@/src/components/ViewOverlayGrid.vue';
import useVolumeColoringStore from '@/src/store/view-configs/volume-coloring';
import { useResetViewsEvents } from '@/src/components/tools/ResetViews.vue';
import { whenever } from '@vueuse/core';
interface Props extends LayoutViewProps {
viewDirection: LPSAxisDir;
Expand All @@ -100,6 +101,13 @@ useViewAnimationListener(vtkView, viewId, viewType);
// base image
const { currentImageID, isImageLoading } = useCurrentImage();
whenever(
computed(() => !isImageLoading.value),
() => {
resetCamera();
}
);
// color preset
const coloringStore = useVolumeColoringStore();
const coloringConfig = computed(() =>
Expand Down
15 changes: 5 additions & 10 deletions src/components/tools/windowing/WindowLevelControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import useWindowingStore, {
import { useViewStore } from '@/src/store/views';
import { WLAutoRanges, WLPresetsCT, WL_AUTO_DEFAULT } from '@/src/constants';
import { getWindowLevels, useDICOMStore } from '@/src/store/datasets-dicom';
import { isDicomImage } from '@/src/utils/dataSelection';
export default defineComponent({
setup() {
Expand All @@ -31,11 +32,8 @@ export default defineComponent({
// --- CT Preset Options --- //
const modality = computed(() => {
if (
currentImageID.value &&
currentImageID.value in dicomStore.imageIDToVolumeKey
) {
const volKey = dicomStore.imageIDToVolumeKey[currentImageID.value];
if (currentImageID.value && isDicomImage(currentImageID.value)) {
const volKey = currentImageID.value;
const { Modality } = dicomStore.volumeInfo[volKey];
return Modality;
}
Expand Down Expand Up @@ -98,11 +96,8 @@ export default defineComponent({
// --- Tag WL Options --- //
const tags = computed(() => {
if (
currentImageID.value &&
currentImageID.value in dicomStore.imageIDToVolumeKey
) {
const volKey = dicomStore.imageIDToVolumeKey[currentImageID.value];
if (currentImageID.value && isDicomImage(currentImageID.value)) {
const volKey = currentImageID.value;
return getWindowLevels(dicomStore.volumeInfo[volKey]);
}
return [];
Expand Down
14 changes: 4 additions & 10 deletions src/composables/useCurrentImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
import { useLayersStore } from '@/src/store/datasets-layers';
import { createLPSBounds, getAxisBounds } from '@/src/utils/lps';
import { useDatasetStore } from '@/src/store/datasets';
import { getDataSelection, getImageID } from '@/src/utils/dataSelection';
import { storeToRefs } from 'pinia';

export interface CurrentImageContext {
Expand Down Expand Up @@ -57,21 +56,16 @@ export function getImageData(imageID: Maybe<string>) {
}

export function getIsImageLoading(imageID: Maybe<string>) {
const dataStore = useDatasetStore();
if (!dataStore.primarySelection) return false;

const selectedImageID = getImageID(dataStore.primarySelection);
if (selectedImageID !== unref(imageID)) return false;

return !!dataStore.primarySelection && !dataStore.primaryDataset;
if (!imageID) return false;
const imageStore = useImageStore();
return !imageStore.dataIndex[imageID];
}

export function getImageLayers(imageID: Maybe<string>) {
if (!imageID) return [];
const selection = getDataSelection(imageID);
const layersStore = useLayersStore();
return layersStore
.getLayers(selection)
.getLayers(imageID)
.filter(({ id }) => id in layersStore.layerImages);
}

Expand Down
19 changes: 12 additions & 7 deletions src/composables/useSliceConfigInitializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function useSliceConfigInitializer(
) {
const store = useViewSliceStore();
const { config: sliceConfig } = useSliceConfig(viewID, imageID);
const { metadata } = useImage(imageID);
const { metadata, isLoading } = useImage(imageID);

const viewAxis = computed(() => getLPSAxisFromDir(unref(viewDirection)));
const sliceDomain = computed(() => {
Expand All @@ -33,17 +33,22 @@ export function useSliceConfigInitializer(
});

watchImmediate(
[toRef(sliceDomain), toRef(viewDirection)] as const,
([domain, axisDirection]) => {
[
toRef(sliceDomain),
toRef(viewDirection),
toRef(imageID),
isLoading,
] as const,
([domain, axisDirection, id, loading]) => {
if (loading || !id) return;

const configExisted = !!sliceConfig.value;
const imageIdVal = unref(imageID);
if (!imageIdVal) return;
store.updateConfig(unref(viewID), imageIdVal, {
store.updateConfig(unref(viewID), id, {
...domain,
axisDirection,
});
if (!configExisted) {
store.resetSlice(unref(viewID), imageIdVal);
store.resetSlice(unref(viewID), id);
}
}
);
Expand Down
6 changes: 3 additions & 3 deletions src/composables/useVolumeColoringInitializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export function useVolumeColoringInitializer(
store.getConfig(unref(viewId), unref(imageId))
);

const { imageData } = useImage(imageId);
const { imageData, isLoading } = useImage(imageId);

watchImmediate(coloringConfig, (config) => {
if (config) return;
watchImmediate([coloringConfig, viewId, imageId, isLoading], () => {
if (coloringConfig.value || isLoading.value) return;

const viewIdVal = unref(viewId);
const imageIdVal = unref(imageId);
Expand Down
5 changes: 3 additions & 2 deletions src/composables/useWindowingConfigInitializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getWindowLevels, useDICOMStore } from '@/src/store/datasets-dicom';
import useWindowingStore from '@/src/store/view-configs/windowing';
import { Maybe } from '@/src/types';
import { useResetViewsEvents } from '@/src/components/tools/ResetViews.vue';
import { isDicomImage } from '@/src/utils/dataSelection';

function useAutoRangeValues(imageID: MaybeRef<Maybe<string>>) {
const { imageData } = useImage(imageID);
Expand Down Expand Up @@ -80,8 +81,8 @@ export function useWindowingConfigInitializer(

const firstTag = computed(() => {
const id = unref(imageID);
if (id && id in dicomStore.imageIDToVolumeKey) {
const volKey = dicomStore.imageIDToVolumeKey[id];
if (id && isDicomImage(id)) {
const volKey = id;
const windowLevels = getWindowLevels(dicomStore.volumeInfo[volKey]);
if (windowLevels.length) {
return windowLevels[0];
Expand Down
Loading

0 comments on commit 1f02f96

Please sign in to comment.