Skip to content

Commit 4cc449e

Browse files
committed
refactor(image-stats): better watcher lifecycle management
1 parent 7760c6e commit 4cc449e

2 files changed

Lines changed: 70 additions & 70 deletions

File tree

src/composables/useWindowingConfigInitializer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ export function useWindowingConfigInitializer(
4646
});
4747
}
4848

49-
const jsonWidthLevel = store.runtimeConfigWindowLevel;
50-
if (jsonWidthLevel) {
49+
const widthLevel = store.runtimeConfigWindowLevel;
50+
if (widthLevel) {
5151
store.updateConfig(viewIdVal, imageIdVal, {
52-
...jsonWidthLevel,
52+
...widthLevel,
5353
});
5454
}
5555
}

src/store/image-stats.ts

Lines changed: 67 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { defineStore } from 'pinia';
2-
import { reactive, watch, computed, MaybeRef, unref } from 'vue';
2+
import {
3+
reactive,
4+
watch,
5+
computed,
6+
MaybeRef,
7+
unref,
8+
effectScope,
9+
type EffectScope,
10+
} from 'vue';
311
import * as Comlink from 'comlink';
412
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
513
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
@@ -8,6 +16,7 @@ import { WLAutoRanges, WL_HIST_BINS } from '@/src/constants';
816
import { HistogramWorker } from '@/src/utils/histogram.worker';
917
import { Maybe } from '@/src/types';
1018
import { useImage } from '@/src/composables/useCurrentImage';
19+
import { ensureError } from '@/src/utils';
1120
import { useImageCacheStore } from './image-cache';
1221
import { useMessageStore } from './messages';
1322

@@ -18,13 +27,8 @@ export type ImageStats = {
1827
};
1928

2029
async function computeAutoRangeValues(
21-
imageData: vtkImageData,
22-
isImageLoading: boolean
30+
imageData: vtkImageData
2331
): Promise<Record<string, [number, number]>> {
24-
if (isImageLoading || !imageData) {
25-
return {};
26-
}
27-
2832
const scalars = imageData.getPointData()?.getScalars();
2933
if (!scalars) {
3034
return {};
@@ -71,7 +75,7 @@ export const useImageStatsStore = defineStore('image-stats', () => {
7175
const imageCacheStore = useImageCacheStore();
7276
const messageStore = useMessageStore();
7377

74-
const scalarRangeWatchers: Record<string, () => void> = {};
78+
const statsEffectScope: Record<string, EffectScope> = {};
7579
const autoRangeComputations: Record<
7680
string,
7781
Promise<Record<string, [number, number]>>
@@ -94,7 +98,7 @@ export const useImageStatsStore = defineStore('image-stats', () => {
9498
autoValues: Record<string, [number, number]>
9599
) => {
96100
stats[imageID] = {
97-
...(stats[imageID] ?? { scalarMin: 0, scalarMax: 0 }),
101+
...stats[imageID],
98102
autoRangeValues: autoValues,
99103
};
100104
};
@@ -103,11 +107,7 @@ export const useImageStatsStore = defineStore('image-stats', () => {
103107
delete stats[imageID];
104108
};
105109

106-
const setupImageWatcher = (id: string) => {
107-
if (scalarRangeWatchers[id]) {
108-
scalarRangeWatchers[id]();
109-
}
110-
110+
const setupImageWatchers = (id: string) => {
111111
const { imageData, isLoading: isImageLoading } = useImage(
112112
computed(() => id)
113113
);
@@ -117,77 +117,68 @@ export const useImageStatsStore = defineStore('image-stats', () => {
117117
);
118118
const scalarRange = vtkFieldRef(activeScalars, 'range');
119119

120-
scalarRangeWatchers[id] = watch(
120+
watch(
121121
scalarRange,
122122
(range) => {
123-
if (imageData.value && range) {
123+
if (range) {
124124
internalSetScalarRange(id, range[0], range[1]);
125-
} else {
126-
internalRemoveStats(id);
127125
}
128126
},
129127
{ immediate: true }
130128
);
131129

132-
const updateAutoRangeValuesIfNeeded = () => {
133-
const currentImageData = imageData.value;
134-
const currentIsLoading = isImageLoading.value;
135-
136-
if (!currentIsLoading && currentImageData) {
137-
if (id in autoRangeComputations) {
138-
return;
139-
}
140-
autoRangeComputations[id] = computeAutoRangeValues(
141-
currentImageData,
142-
currentIsLoading
143-
);
144-
145-
autoRangeComputations[id]
146-
.then((autoValues) => {
147-
if (imageCacheStore.imageIds.includes(id)) {
148-
internalSetAutoRangeValues(id, autoValues);
149-
}
150-
})
151-
.catch((error) => {
152-
console.error(
153-
`[ImageStatsStore] Auto range computation for image ${id} FAILED:`,
154-
error
155-
);
156-
messageStore.addError(
157-
`Auto range computation failed for image ${id}`,
158-
error instanceof Error ? error : String(error)
159-
);
160-
if (imageCacheStore.imageIds.includes(id)) {
161-
internalSetAutoRangeValues(id, {});
162-
}
163-
})
164-
.finally(() => {
165-
delete autoRangeComputations[id];
166-
});
167-
} else if (!currentImageData) {
168-
internalSetAutoRangeValues(id, {});
169-
if (id in autoRangeComputations) {
130+
const triggerAutoRangeComputation = (image: vtkImageData) => {
131+
autoRangeComputations[id] = computeAutoRangeValues(image);
132+
133+
autoRangeComputations[id]
134+
.then((autoValues) => {
135+
if (imageCacheStore.imageIds.includes(id)) {
136+
// not deleted yet, save values
137+
internalSetAutoRangeValues(id, autoValues);
138+
}
139+
})
140+
.catch((error) => {
141+
console.error(
142+
`[ImageStatsStore] Auto range computation for image ${id} FAILED:`,
143+
error
144+
);
145+
messageStore.addError(
146+
`Auto range computation failed for image ${id}`,
147+
ensureError(error)
148+
);
149+
})
150+
.finally(() => {
170151
delete autoRangeComputations[id];
171-
}
172-
}
152+
});
173153
};
174154

175155
watch(
176156
[imageData, isImageLoading],
177157
() => {
178-
updateAutoRangeValuesIfNeeded();
158+
if (
159+
isImageLoading.value ||
160+
!imageData.value ||
161+
id in autoRangeComputations ||
162+
(stats[id] && stats[id].autoRangeValues)
163+
)
164+
return;
165+
triggerAutoRangeComputation(imageData.value);
179166
},
180-
{ immediate: true, deep: false }
167+
{ immediate: true }
181168
);
182169
};
183170

184-
const cleanupImageWatcher = (id: string) => {
171+
const cleanupImage = (id: string) => {
185172
internalRemoveStats(id);
186-
if (scalarRangeWatchers[id]) {
187-
scalarRangeWatchers[id]();
188-
delete scalarRangeWatchers[id];
173+
174+
if (statsEffectScope[id]) {
175+
statsEffectScope[id].stop();
176+
delete statsEffectScope[id];
177+
}
178+
179+
if (id in autoRangeComputations) {
180+
delete autoRangeComputations[id];
189181
}
190-
delete autoRangeComputations[id];
191182
};
192183

193184
watch(
@@ -200,8 +191,17 @@ export const useImageStatsStore = defineStore('image-stats', () => {
200191
(id) => !currentImageIds.includes(id)
201192
);
202193

203-
removedIds.forEach(cleanupImageWatcher);
204-
addedIds.forEach(setupImageWatcher);
194+
removedIds.forEach(cleanupImage);
195+
addedIds.forEach((id) => {
196+
if (statsEffectScope[id]) {
197+
cleanupImage(id);
198+
console.error(`Setting up stats for ${id} twice!`);
199+
}
200+
statsEffectScope[id] = effectScope();
201+
statsEffectScope[id].run(() => {
202+
setupImageWatchers(id);
203+
});
204+
});
205205
},
206206
{ immediate: true }
207207
);

0 commit comments

Comments
 (0)