Skip to content

Commit 31e7dcc

Browse files
authored
Merge pull request #749 from PaulHax/range-fix
Image Range Fixes
2 parents 1d78dab + a5f0fb9 commit 31e7dcc

5 files changed

Lines changed: 82 additions & 16 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/useVtkComputed.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 useVtkComputed<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: 22 additions & 6 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 { useVtkComputed } from '@/src/core/vtk/useVtkComputed';
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,23 @@ 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+
function getAllComponentRange(scalars: vtkDataArray) {
30+
const numberOfComponents = scalars.getNumberOfComponents();
31+
32+
// slice off magnitude range if present.
33+
const ranges = scalars.getRanges(false).slice(0, numberOfComponents);
34+
35+
const min = ranges
36+
.map((range) => range.min)
37+
.reduce((acc, val) => Math.min(acc, val), Infinity);
38+
const max = ranges
39+
.map((range) => range.max)
40+
.reduce((acc, val) => Math.max(acc, val), -Infinity);
41+
42+
return { min, max };
43+
}
44+
45+
async function computeAutoRangeValues(imageData: vtkImageData) {
3246
const scalars = imageData.getPointData()?.getScalars();
3347
if (!scalars) {
3448
return {};
@@ -40,8 +54,8 @@ async function computeAutoRangeValues(
4054
})
4155
);
4256

57+
const { min, max } = getAllComponentRange(scalars);
4358
const scalarData = scalars.getData() as number[];
44-
const { min, max } = vtkDataArray.fastComputeRange(scalarData, 0, 1);
4559
const hist = await worker.histogram(scalarData, [min, max], WL_HIST_BINS);
4660
worker[Comlink.releaseProxy]();
4761

@@ -115,7 +129,9 @@ export const useImageStatsStore = defineStore('image-stats', () => {
115129
const activeScalars = computed(() =>
116130
imageData.value?.getPointData()?.getScalars()
117131
);
118-
const scalarRange = vtkFieldRef(activeScalars, 'range');
132+
const scalarRange = useVtkComputed(activeScalars, () =>
133+
activeScalars.value?.getRange(0)
134+
);
119135

120136
watch(
121137
scalarRange,

0 commit comments

Comments
 (0)