Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: unify data IDs #594

Merged
merged 2 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading