Skip to content

Commit 3e32d55

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

4 files changed

Lines changed: 128 additions & 82 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/vtkFieldRef.ts

Lines changed: 120 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { MaybeRef, Ref, computed, customRef, triggerRef, unref } from 'vue';
1+
import {
2+
MaybeRef,
3+
Ref,
4+
computed,
5+
customRef,
6+
triggerRef,
7+
unref,
8+
watch,
9+
isRef,
10+
} from 'vue';
211
import { vtkObject } from '@kitware/vtk.js/interfaces';
312
import { capitalize } from '@kitware/vtk.js/macros';
413
import { onPausableVTKEvent } from '@/src/composables/onPausableVTKEvent';
@@ -52,23 +61,48 @@ export function vtkFieldRef<T extends Maybe<vtkObject>, R>(
5261
factory: GetterSetterFactory<R>
5362
): Ref<R>;
5463

55-
export function vtkFieldRef<T extends Maybe<vtkObject>>(
56-
obj: MaybeRef<T>,
57-
fieldNameOrFactory: string | GetterSetterFactory<any>
58-
): any {
59-
let getter: () => any;
60-
let setter: (v: any) => boolean | undefined;
64+
/**
65+
* A customRef wrapper that triggers the ref based on a vtk object modification event,
66+
* using the provided function as a getter. The ref will be read-only.
67+
* @param obj The vtkObject or a Ref to it.
68+
* @param getterFunc A function that retrieves the value from the vtkObject.
69+
*/
70+
export function vtkFieldRef<TValue extends Maybe<vtkObject>, R>(
71+
obj: MaybeRef<TValue>,
72+
getterFunc: () => R
73+
): Ref<R>;
74+
75+
export function vtkFieldRef<TValue extends Maybe<vtkObject>>(
76+
obj: MaybeRef<TValue>,
77+
config: string | GetterSetterFactory<any> | (() => any)
78+
): Ref<any> {
79+
let _sourceGet: () => any;
80+
let _finalSetter: (v: any) => boolean | undefined;
6181
let lastValue: any;
6282
let lastValueIsArray = false;
6383

64-
if (typeof fieldNameOrFactory === 'string') {
65-
const getterName = `get${capitalize(fieldNameOrFactory)}` as keyof T;
66-
const setterName = `set${capitalize(fieldNameOrFactory)}` as keyof T;
84+
const updateLastValueAndReturnClonedIfArray = (rawValue: any) => {
85+
if (Array.isArray(rawValue)) {
86+
lastValue = [...rawValue];
87+
lastValueIsArray = true;
88+
} else {
89+
lastValue = rawValue;
90+
lastValueIsArray = false;
91+
}
92+
return lastValue;
93+
};
94+
95+
if (typeof config === 'string') {
96+
const fieldName = config;
97+
const getterName = `get${capitalize(fieldName)}` as keyof TValue;
98+
const setterName = `set${capitalize(fieldName)}` as keyof TValue;
6799

68-
const _getter = computed(
100+
const _objGetter = computed(
69101
() => unref(obj)?.[getterName] as (() => any) | undefined
70102
);
71-
const _setter = computed(
103+
_sourceGet = () => _objGetter.value?.();
104+
105+
const _objSetter = computed(
72106
() =>
73107
unref(obj)?.[setterName] as ((...args: any[]) => boolean) | undefined
74108
);
@@ -79,67 +113,54 @@ export function vtkFieldRef<T extends Maybe<vtkObject>>(
79113
return val ? setterName in val : false;
80114
});
81115

82-
getter = () => {
83-
const value = _getter.value?.();
84-
// create a new reference to trigger update
85-
if (Array.isArray(value)) {
86-
lastValue = [...value];
87-
lastValueIsArray = true;
88-
return lastValue;
89-
}
90-
lastValue = value;
91-
lastValueIsArray = false;
92-
return value;
93-
};
94-
95-
setter = (v: any) => {
96-
const set = _setter.value;
116+
_finalSetter = (v: any) => {
117+
const set = _objSetter.value;
97118
if (!notNull.value) return false;
98119
if (!hasSetter.value || !set)
99-
throw new Error(`No setter for field '${fieldNameOrFactory}'`);
120+
throw new Error(`No setter for field '${fieldName}'`);
100121

101-
// handle certain array setters not accepting an array as input
102122
if (Array.isArray(v) && set.length === v.length) {
103123
return (set as ArraySetter)(...v);
104124
}
105125
return set(v);
106126
};
107-
} else {
108-
const originalGetter = fieldNameOrFactory.get;
109-
getter = () => {
110-
const value = originalGetter();
111-
// create a new reference to trigger update
112-
if (Array.isArray(value)) {
113-
lastValue = [...value];
114-
lastValueIsArray = true;
115-
return lastValue;
116-
}
117-
lastValue = value;
118-
lastValueIsArray = false;
119-
return value;
127+
} else if (typeof config === 'function') {
128+
_sourceGet = config;
129+
_finalSetter = () => {
130+
console.warn(
131+
'[vtkFieldRef] Attempted to set a ref that was defined with a getter function only. This ref is read-only.'
132+
);
133+
return false;
120134
};
121-
setter = fieldNameOrFactory.set;
135+
} else {
136+
_sourceGet = config.get;
137+
_finalSetter = config.set;
122138
}
123139

124-
let pause: () => void;
125-
let resume: () => void;
140+
const refGetter = () => {
141+
const rawValue = _sourceGet();
142+
return updateLastValueAndReturnClonedIfArray(rawValue);
143+
};
144+
145+
refGetter(); // Initialize lastValue
146+
147+
let currentPauseListener: () => void = () => {};
148+
let currentResumeListener: () => void = () => {};
126149

127150
const ref = customRef<any>((track, trigger) => {
128151
return {
129152
get: () => {
130153
track();
131-
return getter();
154+
return refGetter();
132155
},
133156
set: (v) => {
157+
currentPauseListener();
134158
let changed = false;
135-
pause();
136-
137159
try {
138-
const ret = setter(v);
139-
// in the event a setter returns undefined, assume something changed.
160+
const ret = _finalSetter(v);
140161
changed = ret === undefined ? true : ret;
141162
} finally {
142-
resume();
163+
currentResumeListener();
143164
}
144165

145166
if (changed) {
@@ -150,36 +171,59 @@ export function vtkFieldRef<T extends Maybe<vtkObject>>(
150171
});
151172

152173
const onModified = batchForNextTask(() => {
153-
if (unref(obj)?.isDeleted()) return;
154-
155-
// Special handling for array values that might have been mutated
156-
if (lastValueIsArray) {
157-
const currentValue = getter();
158-
if (Array.isArray(currentValue)) {
159-
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
168-
triggerRef(ref);
169-
return;
170-
}
174+
const currentVtkObj = unref(obj);
175+
if (currentVtkObj?.isDeleted()) return;
176+
177+
const currentSourceValue = _sourceGet();
178+
let valueChanged = false;
179+
180+
if (lastValueIsArray && Array.isArray(currentSourceValue)) {
181+
if (
182+
lastValue.length !== currentSourceValue.length ||
183+
lastValue.some(
184+
(val: any, idx: number) => val !== currentSourceValue[idx]
185+
)
186+
) {
187+
valueChanged = true;
171188
}
189+
} else if (lastValue !== currentSourceValue) {
190+
valueChanged = true;
172191
}
173192

174-
// Always trigger for non-array values
175-
triggerRef(ref);
193+
if (valueChanged) {
194+
triggerRef(ref);
195+
}
176196
});
177197

178-
({ pause, resume } = onPausableVTKEvent(
179-
obj as vtkObject,
180-
'onModified',
181-
onModified
182-
));
198+
const setupVtkListener = (targetVtkObject: Maybe<vtkObject>) => {
199+
currentPauseListener(); // Clean up previous listener's effect
200+
201+
if (targetVtkObject && !targetVtkObject.isDeleted()) {
202+
const { pause: newPause, resume: newResume } = onPausableVTKEvent(
203+
targetVtkObject,
204+
'onModified',
205+
onModified
206+
);
207+
currentPauseListener = newPause;
208+
currentResumeListener = newResume;
209+
} else {
210+
currentPauseListener = () => {};
211+
currentResumeListener = () => {};
212+
}
213+
};
214+
215+
if (isRef(obj)) {
216+
watch(
217+
obj,
218+
(newVtkObj) => {
219+
setupVtkListener(newVtkObj);
220+
triggerRef(ref); // Trigger dependents as the source object has changed
221+
},
222+
{ immediate: true }
223+
);
224+
} else {
225+
setupVtkListener(unref(obj));
226+
}
183227

184228
return ref;
185229
}

src/store/image-stats.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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 = vtkFieldRef(activeScalars, () =>
117+
activeScalars.value?.getRange(0)
118+
);
119119

120120
watch(
121121
scalarRange,

0 commit comments

Comments
 (0)