@jax-data-science/sci-image-visualizer 0.8.2 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +56 -19
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +55 -18
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/lib/implementations/napari-js/napari-visualizer.service.d.ts +6 -1
- package/package.json +1 -1
|
@@ -29393,6 +29393,10 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29393
29393
|
this.loadingBadge = null;
|
|
29394
29394
|
this.tileLoading = [];
|
|
29395
29395
|
this.observationsLoading = 0;
|
|
29396
|
+
/** Image tiles in flight, and the scene they belong to: a fetch that outlives {@link reset}
|
|
29397
|
+
* settles against the old generation and leaves the new scene's count alone. */
|
|
29398
|
+
this.imageTilesLoading = 0;
|
|
29399
|
+
this.imageTilesGeneration = 0;
|
|
29396
29400
|
/** Monotonic load generation. Bumped by {@link reset} and {@link cancelLoading}; the frame-loading
|
|
29397
29401
|
* loops (volume assembly, surface preload) capture it and bail when it changes, so a Cancel (or a
|
|
29398
29402
|
* new plot) actually stops fetching frames instead of running to completion in the background. */
|
|
@@ -29711,6 +29715,9 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29711
29715
|
return false;
|
|
29712
29716
|
}
|
|
29713
29717
|
this.reset();
|
|
29718
|
+
// This plot's scene. A newer plot resets into the next one while this one still awaits, and
|
|
29719
|
+
// a superseded plot must not go on to draw (or count image tiles) into the newer scene.
|
|
29720
|
+
const scene = this.imageTilesGeneration;
|
|
29714
29721
|
this.host = host;
|
|
29715
29722
|
this.plotDivId = plotDiv;
|
|
29716
29723
|
this.currentPlotType = plotType;
|
|
@@ -29752,6 +29759,8 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29752
29759
|
});
|
|
29753
29760
|
this.viewer = viewer;
|
|
29754
29761
|
await viewer.ready;
|
|
29762
|
+
if (scene !== this.imageTilesGeneration)
|
|
29763
|
+
return false;
|
|
29755
29764
|
if (isSpatialOmics3d(plotType)) {
|
|
29756
29765
|
await this.mountSpatialOmics3d(viewer, host);
|
|
29757
29766
|
}
|
|
@@ -29848,11 +29857,15 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29848
29857
|
const v = this.viewer;
|
|
29849
29858
|
if (!v)
|
|
29850
29859
|
return;
|
|
29860
|
+
const scene = this.imageTilesGeneration;
|
|
29851
29861
|
const desc = await this.ensureDescriptor();
|
|
29862
|
+
// Reset into a newer scene while the descriptor was in flight: this render is superseded.
|
|
29863
|
+
if (scene !== this.imageTilesGeneration)
|
|
29864
|
+
return;
|
|
29852
29865
|
if (desc && desc.levels?.length) {
|
|
29853
29866
|
if (token != null && token !== this.sliceReq)
|
|
29854
29867
|
return;
|
|
29855
|
-
await this.renderImageTiled(z, desc);
|
|
29868
|
+
await this.renderImageTiled(z, desc, scene);
|
|
29856
29869
|
return;
|
|
29857
29870
|
}
|
|
29858
29871
|
return this.renderImageStitched(z, token);
|
|
@@ -29945,7 +29958,7 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29945
29958
|
* coordinates so regions align. Same three modes as the stitch path. Per-channel layers use the
|
|
29946
29959
|
* REAL pyramid levels (per-channel tiles only exist there); the composite uses all levels.
|
|
29947
29960
|
*/
|
|
29948
|
-
async renderImageTiled(z, desc) {
|
|
29961
|
+
async renderImageTiled(z, desc, scene) {
|
|
29949
29962
|
const v = this.viewer;
|
|
29950
29963
|
if (!v)
|
|
29951
29964
|
return;
|
|
@@ -29962,7 +29975,7 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29962
29975
|
const st = states.find((s) => s.index === c);
|
|
29963
29976
|
const color = st?.color ?? desc.channelInfo?.[c]?.color ?? tintFor(c);
|
|
29964
29977
|
views.push({
|
|
29965
|
-
source: this.buildTiledSource(desc, c, 1),
|
|
29978
|
+
source: this.buildTiledSource(desc, c, 1, scene),
|
|
29966
29979
|
tint: color,
|
|
29967
29980
|
name: st?.name ?? `ch${c}`,
|
|
29968
29981
|
contrastLimits: [st?.min ?? 0, st?.max ?? 255],
|
|
@@ -29978,7 +29991,7 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29978
29991
|
const st = states[0];
|
|
29979
29992
|
this.channelView.render('grayscale', [
|
|
29980
29993
|
{
|
|
29981
|
-
source: this.buildTiledSource(desc, undefined, 1),
|
|
29994
|
+
source: this.buildTiledSource(desc, undefined, 1, scene),
|
|
29982
29995
|
colormap: this.grayscaleColormap(),
|
|
29983
29996
|
contrastLimits: [st?.min ?? 0, st?.max ?? 255],
|
|
29984
29997
|
gamma: st?.gamma ?? 1,
|
|
@@ -29988,7 +30001,7 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29988
30001
|
}
|
|
29989
30002
|
else {
|
|
29990
30003
|
this.imageMode = 'rgb';
|
|
29991
|
-
this.channelView.render('rgb', [{ source: this.buildTiledSource(desc, undefined, 4) }], {
|
|
30004
|
+
this.channelView.render('rgb', [{ source: this.buildTiledSource(desc, undefined, 4, scene) }], {
|
|
29992
30005
|
interpolation,
|
|
29993
30006
|
});
|
|
29994
30007
|
}
|
|
@@ -29999,8 +30012,9 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29999
30012
|
await this.refreshHistogramSamples(z, desc);
|
|
30000
30013
|
}
|
|
30001
30014
|
/** Build a pyramidal TiledSource backed by the server `/tile` endpoint. `channel` selects a band
|
|
30002
|
-
* (grayscale luminance, real levels only); omit it for the composite (RGBA, all levels).
|
|
30003
|
-
|
|
30015
|
+
* (grayscale luminance, real levels only); omit it for the composite (RGBA, all levels).
|
|
30016
|
+
* `scene` is the generation of the render that asked for it, taken before that render's awaits. */
|
|
30017
|
+
buildTiledSource(desc, channel, channels, scene) {
|
|
30004
30018
|
const infoB64 = this.tiles.getSelectedInfoB64() ?? '';
|
|
30005
30019
|
// Per-channel tiles exist only at REAL Bio-Formats levels; the composite exists at all levels.
|
|
30006
30020
|
const usable = channel == null
|
|
@@ -30010,6 +30024,9 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
30010
30024
|
const tileSize = desc.tileSize || TILE_SIZE;
|
|
30011
30025
|
const ch = channel == null ? '' : `&channel=${channel}`;
|
|
30012
30026
|
const api = this.api;
|
|
30027
|
+
// The scene this source draws: its tiles count on the badge only while that scene is current,
|
|
30028
|
+
// so a request the disposed source still issues after a reset never shows on the new one.
|
|
30029
|
+
const generation = scene;
|
|
30013
30030
|
return {
|
|
30014
30031
|
kind: 'tiled',
|
|
30015
30032
|
width: desc.width,
|
|
@@ -30023,17 +30040,31 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
30023
30040
|
fetchTile: async (key) => {
|
|
30024
30041
|
const res = usable[key.level]?.res ?? key.level;
|
|
30025
30042
|
const url = `${api}tile?info=${infoB64}&res=${res}&col=${key.col}&row=${key.row}&z=${key.z}&tileSize=${tileSize}${ch}`;
|
|
30026
|
-
|
|
30027
|
-
|
|
30028
|
-
|
|
30029
|
-
|
|
30030
|
-
|
|
30031
|
-
|
|
30043
|
+
// "Image reloading…" on the loading badge while any tile of the image is in flight.
|
|
30044
|
+
const counted = generation === this.imageTilesGeneration;
|
|
30045
|
+
if (counted) {
|
|
30046
|
+
this.imageTilesLoading++;
|
|
30047
|
+
this.showLoading();
|
|
30048
|
+
}
|
|
30049
|
+
try {
|
|
30050
|
+
const headers = await this.tiles
|
|
30051
|
+
.getAuthHeaders()
|
|
30052
|
+
.catch(() => ({}));
|
|
30053
|
+
const resp = await fetch(url, { headers });
|
|
30054
|
+
if (!resp.ok) {
|
|
30055
|
+
throw new Error(`[napari-js] tile ${key.level}/${key.col}/${key.row} → ${resp.status}`);
|
|
30056
|
+
}
|
|
30057
|
+
const bmp = await createImageBitmap(await resp.blob());
|
|
30058
|
+
if (channels === 4)
|
|
30059
|
+
return { width: bmp.width, height: bmp.height, data: bmp };
|
|
30060
|
+
return this.bitmapToLuminance(bmp);
|
|
30061
|
+
}
|
|
30062
|
+
finally {
|
|
30063
|
+
if (counted && generation === this.imageTilesGeneration) {
|
|
30064
|
+
this.imageTilesLoading--;
|
|
30065
|
+
this.showLoading();
|
|
30066
|
+
}
|
|
30032
30067
|
}
|
|
30033
|
-
const bmp = await createImageBitmap(await resp.blob());
|
|
30034
|
-
if (channels === 4)
|
|
30035
|
-
return { width: bmp.width, height: bmp.height, data: bmp };
|
|
30036
|
-
return this.bitmapToLuminance(bmp);
|
|
30037
30068
|
},
|
|
30038
30069
|
};
|
|
30039
30070
|
}
|
|
@@ -30195,7 +30226,11 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
30195
30226
|
if (!this.host)
|
|
30196
30227
|
return;
|
|
30197
30228
|
this.loadingBadge ??= new NapariLoadingBadge(this.host);
|
|
30198
|
-
this.loadingBadge.set([
|
|
30229
|
+
this.loadingBadge.set([
|
|
30230
|
+
...(this.imageTilesLoading > 0 ? ['Image'] : []),
|
|
30231
|
+
...(this.observationsLoading > 0 ? ['Observations'] : []),
|
|
30232
|
+
...this.tileLoading,
|
|
30233
|
+
]);
|
|
30199
30234
|
}
|
|
30200
30235
|
installNavigator(z) {
|
|
30201
30236
|
this.navigator?.destroy();
|
|
@@ -32744,6 +32779,8 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
32744
32779
|
this.loadingBadge = null;
|
|
32745
32780
|
this.tileLoading = [];
|
|
32746
32781
|
this.observationsLoading = 0;
|
|
32782
|
+
this.imageTilesLoading = 0;
|
|
32783
|
+
this.imageTilesGeneration++;
|
|
32747
32784
|
this.regionOverlay?.destroy();
|
|
32748
32785
|
this.regionOverlay = null;
|
|
32749
32786
|
this.axesLabels?.destroy();
|