Skip to content

Commit 96c7fe1

Browse files
committed
fix: handle images with 0 range or multiple components
1 parent 1d78dab commit 96c7fe1

5 files changed

Lines changed: 65 additions & 15 deletions

File tree

src/components/vtk/VtkSliceViewWindowManipulator.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ const wlConfig = useWindowingConfig(viewId, imageId);
5050
useWindowingConfigInitializer(viewId, imageId);
5151
5252
const computeStep = (range: Vector2) => {
53-
return Math.min(range[1] - range[0], 1) / 256;
53+
const diff = range[1] - range[0] || 1;
54+
return Math.min(diff, 1) / 256;
5455
};
5556
const wlStep = computed(() => computeStep(wlConfig.range.value));
5657

src/core/streaming/dicomChunkImage.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ export default class DicomChunkImage
276276
this.dataRangeFromChunks().forEach(([min, max], compIdx) => {
277277
scalars.setRange({ min, max }, compIdx);
278278
});
279+
scalars.modified(); // so image-stats will trigger update of range
279280
}
280281

281282
private dataRangeFromChunks() {
@@ -363,8 +364,8 @@ export default class DicomChunkImage
363364
const newMin = rangeAlreadyInitialized ? Math.min(min, curRange[0]) : min;
364365
const newMax = rangeAlreadyInitialized ? Math.max(max, curRange[1]) : max;
365366
scalars.setRange({ min: newMin, max: newMax }, comp);
366-
scalars.modified(); // so image-stats will trigger update of range
367367
}
368+
scalars.modified(); // so image-stats will trigger update of range
368369

369370
chunk.setUserData(DATA_RANGE_KEY, chunkDataRange);
370371

src/core/vtk/useVtkProperty.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { MaybeRef, computed, unref, ref, Ref } from 'vue';
2+
import type { vtkObject } from '@kitware/vtk.js/interfaces';
3+
import { onPausableVTKEvent } from '@/src/composables/onPausableVTKEvent';
4+
import { batchForNextTask } from '@/src/utils/batchForNextTask';
5+
import { arrayEquals } from '@/src/utils';
6+
import type { Maybe } from '@/src/types';
7+
8+
/**
9+
* A computed property that derives a reactive property from a VTK object using a getter function.
10+
* The computed property updates when the underlying VTK object emits an 'onModified' event.
11+
*
12+
* @param vtkObjectRef A Vue Ref or direct reference to a VTK object (or null/undefined).
13+
* @param propertyGetter A function that computes the property value. It will be called reactively.
14+
* @returns A read-only ComputedRef to the derived property.
15+
*/
16+
export function useVtkProperty<T extends Maybe<vtkObject>, R>(
17+
obj: MaybeRef<T>,
18+
propertyGetter: () => R
19+
) {
20+
const initialValue = propertyGetter();
21+
const trackedValue = ref(
22+
Array.isArray(initialValue) ? [...initialValue] : initialValue
23+
) as Ref<R>;
24+
let lastValueIsArray = Array.isArray(initialValue);
25+
const onModified = batchForNextTask(() => {
26+
if (unref(obj)?.isDeleted()) return;
27+
28+
const currentValue = propertyGetter(); // Avoid unnecessary updates when array contents haven't changed
29+
if (lastValueIsArray && Array.isArray(currentValue)) {
30+
const previousValue = trackedValue.value;
31+
if (
32+
Array.isArray(previousValue) &&
33+
!arrayEquals(previousValue as any[], currentValue as any[])
34+
) {
35+
trackedValue.value = [...currentValue] as R;
36+
return;
37+
}
38+
return;
39+
}
40+
41+
if (Array.isArray(currentValue)) {
42+
trackedValue.value = [...currentValue] as R;
43+
lastValueIsArray = true;
44+
} else {
45+
trackedValue.value = currentValue;
46+
lastValueIsArray = false;
47+
}
48+
});
49+
50+
onPausableVTKEvent(obj as vtkObject, 'onModified', onModified);
51+
return computed(() => {
52+
return trackedValue.value;
53+
});
54+
}

src/core/vtk/vtkFieldRef.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { capitalize } from '@kitware/vtk.js/macros';
44
import { onPausableVTKEvent } from '@/src/composables/onPausableVTKEvent';
55
import { batchForNextTask } from '@/src/utils/batchForNextTask';
66
import { Maybe } from '@/src/types';
7+
import { arrayEquals } from '@/src/utils';
78

89
type NonEmptyString<T extends string> = T extends '' ? never : T;
910

@@ -157,14 +158,7 @@ export function vtkFieldRef<T extends Maybe<vtkObject>>(
157158
const currentValue = getter();
158159
if (Array.isArray(currentValue)) {
159160
const previousValue = lastValue;
160-
// Check if array contents changed
161-
if (
162-
previousValue.length !== currentValue.length ||
163-
previousValue.some(
164-
(val: any, idx: number) => val !== currentValue[idx]
165-
)
166-
) {
167-
// Values changed, trigger the ref
161+
if (!arrayEquals(previousValue as any[], currentValue as any[])) {
168162
triggerRef(ref);
169163
return;
170164
}

src/store/image-stats.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import * as Comlink from 'comlink';
1212
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
1313
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
14-
import { vtkFieldRef } from '@/src/core/vtk/vtkFieldRef';
14+
import { useVtkProperty } from '@/src/core/vtk/useVtkProperty';
1515
import { WLAutoRanges, WL_HIST_BINS } from '@/src/constants';
1616
import { HistogramWorker } from '@/src/utils/histogram.worker';
1717
import { Maybe } from '@/src/types';
@@ -26,9 +26,7 @@ export type ImageStats = {
2626
autoRangeValues?: Record<string, [number, number]>;
2727
};
2828

29-
async function computeAutoRangeValues(
30-
imageData: vtkImageData
31-
): Promise<Record<string, [number, number]>> {
29+
async function computeAutoRangeValues(imageData: vtkImageData) {
3230
const scalars = imageData.getPointData()?.getScalars();
3331
if (!scalars) {
3432
return {};
@@ -115,7 +113,9 @@ export const useImageStatsStore = defineStore('image-stats', () => {
115113
const activeScalars = computed(() =>
116114
imageData.value?.getPointData()?.getScalars()
117115
);
118-
const scalarRange = vtkFieldRef(activeScalars, 'range');
116+
const scalarRange = useVtkProperty(activeScalars, () =>
117+
activeScalars.value?.getRange(0)
118+
);
119119

120120
watch(
121121
scalarRange,

0 commit comments

Comments
 (0)