Skip to content

Commit

Permalink
fix(windowing): more robust loading of multi-component DICOMs
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulHax committed Apr 26, 2024
1 parent 0934933 commit 6658f90
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 25 deletions.
13 changes: 2 additions & 11 deletions src/components/tools/windowing/WindowLevelControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import useWindowingStore, {
} from '@/src/store/view-configs/windowing';
import { useViewStore } from '@/src/store/views';
import { WLAutoRanges, WLPresetsCT, WL_AUTO_DEFAULT } from '@/src/constants';
import { useDICOMStore } from '@/src/store/datasets-dicom';
import { getWindowLevels, useDICOMStore } from '@/src/store/datasets-dicom';
export default defineComponent({
setup() {
Expand Down Expand Up @@ -97,22 +97,13 @@ export default defineComponent({
});
// --- Tag WL Options --- //
function parseTags(text: string) {
return text.split('\\');
}
const tags = computed(() => {
if (
currentImageID.value &&
currentImageID.value in dicomStore.imageIDToVolumeKey
) {
const volKey = dicomStore.imageIDToVolumeKey[currentImageID.value];
const { WindowWidth, WindowLevel } = dicomStore.volumeInfo[volKey];
const levels = parseTags(WindowLevel);
return parseTags(WindowWidth).map((val, idx) => {
return { width: parseFloat(val), level: parseFloat(levels[idx]) };
});
return getWindowLevels(dicomStore.volumeInfo[volKey]);
}
return [];
});
Expand Down
58 changes: 44 additions & 14 deletions src/composables/useWindowingConfigInitializer.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
import { useImage } from '@/src/composables/useCurrentImage';
import { useWindowingConfig } from '@/src/composables/useWindowingConfig';
import { WLAutoRanges, WL_AUTO_DEFAULT, WL_HIST_BINS } from '@/src/constants';
import { useDICOMStore } from '@/src/store/datasets-dicom';
import { getWindowLevels, useDICOMStore } from '@/src/store/datasets-dicom';
import useWindowingStore from '@/src/store/view-configs/windowing';
import { Maybe } from '@/src/types';
import type { TypedArray } from '@kitware/vtk.js/types';
import { watchImmediate } from '@vueuse/core';
import { MaybeRef, computed, unref, watch } from 'vue';

// Original source from https://www.npmjs.com/package/compute-range and vtkDataArray
// Modified to assume one component array
function fastComputeRange(arr: number[] | TypedArray) {
const len = arr.length;
let min = Number.MAX_VALUE;
let max = -Number.MAX_VALUE;
let x;
let i;

// find first non-NaN value
for (i = 0; i < len; i++) {
if (!Number.isNaN(arr[i])) {
min = arr[i];
max = min;
break;
}
}

for (; i < len; i++) {
x = arr[i];
if (x < min) {
min = x;
} else if (x > max) {
max = x;
}
}
return { min, max };
}

function useAutoRangeValues(imageID: MaybeRef<Maybe<string>>) {
const { imageData } = useImage(imageID);

Expand All @@ -30,7 +59,8 @@ function useAutoRangeValues(imageID: MaybeRef<Maybe<string>>) {

// Pre-compute the auto-range values
const scalarData = imageData.value.getPointData().getScalars();
const [min, max] = scalarData.getRange();
// Assumes all data is one component
const { min, max } = fastComputeRange(scalarData.getData());
const hist = histogram(scalarData.getData(), [min, max], WL_HIST_BINS);
const cumm = hist.reduce((acc, val, idx) => {
const prev = idx !== 0 ? acc[idx - 1] : 0;
Expand Down Expand Up @@ -75,13 +105,12 @@ export function useWindowingConfigInitializer(
const id = unref(imageID);
if (id && id in dicomStore.imageIDToVolumeKey) {
const volKey = dicomStore.imageIDToVolumeKey[id];
const { WindowWidth, WindowLevel } = dicomStore.volumeInfo[volKey];
return {
width: WindowWidth.split('\\')[0],
level: WindowLevel.split('\\')[0],
};
const windowLevels = getWindowLevels(dicomStore.volumeInfo[volKey]);
if (windowLevels.length) {
return windowLevels[0];
}
}
return {};
return undefined;
});

watchImmediate(windowConfig, (config) => {
Expand All @@ -103,16 +132,17 @@ export function useWindowingConfigInitializer(
return;
}

const range = autoRangeValues.value[autoRange.value];
const [min, max] = autoRangeValues.value[autoRange.value];
store.updateConfig(viewIdVal, imageIdVal, {
min: range[0],
max: range[1],
min,
max,
});
if (firstTag.value?.width) {
const firstTagVal = unref(firstTag);
if (firstTagVal?.width) {
store.updateConfig(viewIdVal, imageIdVal, {
preset: {
width: parseFloat(firstTag.value.width),
level: parseFloat(firstTag.value.level),
width: firstTagVal.width,
level: firstTagVal.level,
},
});
}
Expand Down
21 changes: 21 additions & 0 deletions src/store/datasets-dicom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,27 @@ export const getDisplayName = (info: VolumeInfo) => {
);
};

export const getWindowLevels = (info: VolumeInfo) => {
const { WindowWidth, WindowLevel } = info;
if (WindowWidth === '') return []; // missing tag
const widths = WindowWidth.split('\\').map(parseFloat);
const levels = WindowLevel.split('\\').map(parseFloat);
if (
widths.some((w) => Number.isNaN(w)) ||
levels.some((l) => Number.isNaN(l))
) {
console.error('Invalid WindowWidth or WindowLevel DICOM tags');
return [];
}
if (widths.length !== levels.length) {
console.error(
'Different numbers of WindowWidth and WindowLevel DICOM tags'
);
return [];
}
return widths.map((width, i) => ({ width, level: levels[i] }));
};

export const useDICOMStore = defineStore('dicom', {
state: (): State => ({
sliceData: {},
Expand Down

0 comments on commit 6658f90

Please sign in to comment.