@jax-data-science/sci-image-visualizer 0.8.1 → 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/esm2022/lib/visualizer.component.mjs +26 -1
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +80 -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/lib/visualizer.component.d.ts +6 -0
- package/package.json +1 -1
|
@@ -13397,6 +13397,10 @@ class VisualizerComponent {
|
|
|
13397
13397
|
* the newer render's overlay, or applying its ROIs.
|
|
13398
13398
|
*/
|
|
13399
13399
|
this.renderToken = 0;
|
|
13400
|
+
/** Set once the template, and with it the plot div, exists (see ngAfterViewInit). */
|
|
13401
|
+
this.viewReady = false;
|
|
13402
|
+
/** An image-less dataset whose draw waits for the view: see plotSpatialWithoutImage. */
|
|
13403
|
+
this.pendingSpatialDraw = null;
|
|
13400
13404
|
this.zIndex = 0;
|
|
13401
13405
|
this.maxIndex = 0;
|
|
13402
13406
|
this.contextMenuItems = [];
|
|
@@ -14444,6 +14448,20 @@ class VisualizerComponent {
|
|
|
14444
14448
|
requestAnimationFrame(() => this.renderIntensityInset());
|
|
14445
14449
|
};
|
|
14446
14450
|
window.addEventListener('resize', this.profileResizeListener);
|
|
14451
|
+
this.onViewReady();
|
|
14452
|
+
}
|
|
14453
|
+
/** The plot div exists now: run an image-less draw that arrived before it did. */
|
|
14454
|
+
onViewReady() {
|
|
14455
|
+
this.viewReady = true;
|
|
14456
|
+
const pending = this.pendingSpatialDraw;
|
|
14457
|
+
this.pendingSpatialDraw = null;
|
|
14458
|
+
// Only if it is still the dataset on offer and no image has arrived meanwhile. By id, not by
|
|
14459
|
+
// object: the port may re-emit the same dataset as a new object (a colour-column change does),
|
|
14460
|
+
// and then the current object is the one to draw.
|
|
14461
|
+
const current = this.spatialDataset;
|
|
14462
|
+
if (pending && current && current.id === pending.id && !this.imageInfo) {
|
|
14463
|
+
void this.plotSpatialWithoutImage(current);
|
|
14464
|
+
}
|
|
14447
14465
|
}
|
|
14448
14466
|
onProfilePanelDragStart(e) {
|
|
14449
14467
|
this.profilePanelDragging = true;
|
|
@@ -14740,6 +14758,13 @@ class VisualizerComponent {
|
|
|
14740
14758
|
* dataset, so regions drawn here are kept per dataset like any image's.
|
|
14741
14759
|
*/
|
|
14742
14760
|
async plotSpatialWithoutImage(dataset) {
|
|
14761
|
+
if (!this.viewReady) {
|
|
14762
|
+
// A host that creates the visualizer AFTER publishing the dataset (jit-ui opening a file
|
|
14763
|
+
// from its tree) has it replayed into ngOnInit, before the template and its plot div
|
|
14764
|
+
// exist; drawing now finds no target. Draw once the view is ready (onViewReady).
|
|
14765
|
+
this.pendingSpatialDraw = dataset;
|
|
14766
|
+
return;
|
|
14767
|
+
}
|
|
14743
14768
|
// The same generation as the image pipeline: a newer image or dataset supersedes this
|
|
14744
14769
|
// draw, and a superseded draw neither reports nor releases the newer one's loading state.
|
|
14745
14770
|
const token = ++this.renderToken;
|
|
@@ -29368,6 +29393,10 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29368
29393
|
this.loadingBadge = null;
|
|
29369
29394
|
this.tileLoading = [];
|
|
29370
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;
|
|
29371
29400
|
/** Monotonic load generation. Bumped by {@link reset} and {@link cancelLoading}; the frame-loading
|
|
29372
29401
|
* loops (volume assembly, surface preload) capture it and bail when it changes, so a Cancel (or a
|
|
29373
29402
|
* new plot) actually stops fetching frames instead of running to completion in the background. */
|
|
@@ -29686,6 +29715,9 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29686
29715
|
return false;
|
|
29687
29716
|
}
|
|
29688
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;
|
|
29689
29721
|
this.host = host;
|
|
29690
29722
|
this.plotDivId = plotDiv;
|
|
29691
29723
|
this.currentPlotType = plotType;
|
|
@@ -29727,6 +29759,8 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29727
29759
|
});
|
|
29728
29760
|
this.viewer = viewer;
|
|
29729
29761
|
await viewer.ready;
|
|
29762
|
+
if (scene !== this.imageTilesGeneration)
|
|
29763
|
+
return false;
|
|
29730
29764
|
if (isSpatialOmics3d(plotType)) {
|
|
29731
29765
|
await this.mountSpatialOmics3d(viewer, host);
|
|
29732
29766
|
}
|
|
@@ -29823,11 +29857,15 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29823
29857
|
const v = this.viewer;
|
|
29824
29858
|
if (!v)
|
|
29825
29859
|
return;
|
|
29860
|
+
const scene = this.imageTilesGeneration;
|
|
29826
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;
|
|
29827
29865
|
if (desc && desc.levels?.length) {
|
|
29828
29866
|
if (token != null && token !== this.sliceReq)
|
|
29829
29867
|
return;
|
|
29830
|
-
await this.renderImageTiled(z, desc);
|
|
29868
|
+
await this.renderImageTiled(z, desc, scene);
|
|
29831
29869
|
return;
|
|
29832
29870
|
}
|
|
29833
29871
|
return this.renderImageStitched(z, token);
|
|
@@ -29920,7 +29958,7 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29920
29958
|
* coordinates so regions align. Same three modes as the stitch path. Per-channel layers use the
|
|
29921
29959
|
* REAL pyramid levels (per-channel tiles only exist there); the composite uses all levels.
|
|
29922
29960
|
*/
|
|
29923
|
-
async renderImageTiled(z, desc) {
|
|
29961
|
+
async renderImageTiled(z, desc, scene) {
|
|
29924
29962
|
const v = this.viewer;
|
|
29925
29963
|
if (!v)
|
|
29926
29964
|
return;
|
|
@@ -29937,7 +29975,7 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29937
29975
|
const st = states.find((s) => s.index === c);
|
|
29938
29976
|
const color = st?.color ?? desc.channelInfo?.[c]?.color ?? tintFor(c);
|
|
29939
29977
|
views.push({
|
|
29940
|
-
source: this.buildTiledSource(desc, c, 1),
|
|
29978
|
+
source: this.buildTiledSource(desc, c, 1, scene),
|
|
29941
29979
|
tint: color,
|
|
29942
29980
|
name: st?.name ?? `ch${c}`,
|
|
29943
29981
|
contrastLimits: [st?.min ?? 0, st?.max ?? 255],
|
|
@@ -29953,7 +29991,7 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29953
29991
|
const st = states[0];
|
|
29954
29992
|
this.channelView.render('grayscale', [
|
|
29955
29993
|
{
|
|
29956
|
-
source: this.buildTiledSource(desc, undefined, 1),
|
|
29994
|
+
source: this.buildTiledSource(desc, undefined, 1, scene),
|
|
29957
29995
|
colormap: this.grayscaleColormap(),
|
|
29958
29996
|
contrastLimits: [st?.min ?? 0, st?.max ?? 255],
|
|
29959
29997
|
gamma: st?.gamma ?? 1,
|
|
@@ -29963,7 +30001,7 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29963
30001
|
}
|
|
29964
30002
|
else {
|
|
29965
30003
|
this.imageMode = 'rgb';
|
|
29966
|
-
this.channelView.render('rgb', [{ source: this.buildTiledSource(desc, undefined, 4) }], {
|
|
30004
|
+
this.channelView.render('rgb', [{ source: this.buildTiledSource(desc, undefined, 4, scene) }], {
|
|
29967
30005
|
interpolation,
|
|
29968
30006
|
});
|
|
29969
30007
|
}
|
|
@@ -29974,8 +30012,9 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29974
30012
|
await this.refreshHistogramSamples(z, desc);
|
|
29975
30013
|
}
|
|
29976
30014
|
/** Build a pyramidal TiledSource backed by the server `/tile` endpoint. `channel` selects a band
|
|
29977
|
-
* (grayscale luminance, real levels only); omit it for the composite (RGBA, all levels).
|
|
29978
|
-
|
|
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) {
|
|
29979
30018
|
const infoB64 = this.tiles.getSelectedInfoB64() ?? '';
|
|
29980
30019
|
// Per-channel tiles exist only at REAL Bio-Formats levels; the composite exists at all levels.
|
|
29981
30020
|
const usable = channel == null
|
|
@@ -29985,6 +30024,9 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29985
30024
|
const tileSize = desc.tileSize || TILE_SIZE;
|
|
29986
30025
|
const ch = channel == null ? '' : `&channel=${channel}`;
|
|
29987
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;
|
|
29988
30030
|
return {
|
|
29989
30031
|
kind: 'tiled',
|
|
29990
30032
|
width: desc.width,
|
|
@@ -29998,17 +30040,31 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
29998
30040
|
fetchTile: async (key) => {
|
|
29999
30041
|
const res = usable[key.level]?.res ?? key.level;
|
|
30000
30042
|
const url = `${api}tile?info=${infoB64}&res=${res}&col=${key.col}&row=${key.row}&z=${key.z}&tileSize=${tileSize}${ch}`;
|
|
30001
|
-
|
|
30002
|
-
|
|
30003
|
-
|
|
30004
|
-
|
|
30005
|
-
|
|
30006
|
-
|
|
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
|
+
}
|
|
30007
30067
|
}
|
|
30008
|
-
const bmp = await createImageBitmap(await resp.blob());
|
|
30009
|
-
if (channels === 4)
|
|
30010
|
-
return { width: bmp.width, height: bmp.height, data: bmp };
|
|
30011
|
-
return this.bitmapToLuminance(bmp);
|
|
30012
30068
|
},
|
|
30013
30069
|
};
|
|
30014
30070
|
}
|
|
@@ -30170,7 +30226,11 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
30170
30226
|
if (!this.host)
|
|
30171
30227
|
return;
|
|
30172
30228
|
this.loadingBadge ??= new NapariLoadingBadge(this.host);
|
|
30173
|
-
this.loadingBadge.set([
|
|
30229
|
+
this.loadingBadge.set([
|
|
30230
|
+
...(this.imageTilesLoading > 0 ? ['Image'] : []),
|
|
30231
|
+
...(this.observationsLoading > 0 ? ['Observations'] : []),
|
|
30232
|
+
...this.tileLoading,
|
|
30233
|
+
]);
|
|
30174
30234
|
}
|
|
30175
30235
|
installNavigator(z) {
|
|
30176
30236
|
this.navigator?.destroy();
|
|
@@ -32719,6 +32779,8 @@ class NapariVisualizerService extends BaseStoreVisualizer {
|
|
|
32719
32779
|
this.loadingBadge = null;
|
|
32720
32780
|
this.tileLoading = [];
|
|
32721
32781
|
this.observationsLoading = 0;
|
|
32782
|
+
this.imageTilesLoading = 0;
|
|
32783
|
+
this.imageTilesGeneration++;
|
|
32722
32784
|
this.regionOverlay?.destroy();
|
|
32723
32785
|
this.regionOverlay = null;
|
|
32724
32786
|
this.axesLabels?.destroy();
|