@jax-data-science/sci-image-visualizer 0.6.0 → 0.8.0

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.
Files changed (34) hide show
  1. package/esm2022/lib/contracts/display-types.mjs +28 -1
  2. package/esm2022/lib/contracts/plot-type-contribution.contract.mjs +1 -1
  3. package/esm2022/lib/contracts/ports/spatial-data.port.mjs +3 -1
  4. package/esm2022/lib/contracts/spatial-dataset.contract.mjs +3 -1
  5. package/esm2022/lib/contracts/visualizer.contract.mjs +1 -1
  6. package/esm2022/lib/implementations/napari-js/napari-loading-badge.mjs +73 -0
  7. package/esm2022/lib/implementations/napari-js/napari-navigator.mjs +195 -0
  8. package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +10 -2
  9. package/esm2022/lib/implementations/napari-js/napari-spatial-tiles.mjs +1487 -0
  10. package/esm2022/lib/implementations/napari-js/napari-spatial-tooltip.mjs +6 -3
  11. package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +219 -8
  12. package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +8 -1
  13. package/esm2022/lib/implementations/spatial-data-http/spatial-data-http.service.mjs +131 -2
  14. package/esm2022/lib/implementations/spatial-data-http/spatial-wire.mjs +77 -1
  15. package/esm2022/lib/routing-visualizer.service.mjs +14 -1
  16. package/esm2022/lib/spatial/spatial-tiles.mjs +649 -0
  17. package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +848 -19
  18. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +3773 -78
  19. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
  20. package/lib/contracts/display-types.d.ts +96 -1
  21. package/lib/contracts/plot-type-contribution.contract.d.ts +9 -0
  22. package/lib/contracts/ports/spatial-data.port.d.ts +70 -1
  23. package/lib/contracts/spatial-dataset.contract.d.ts +197 -0
  24. package/lib/contracts/visualizer.contract.d.ts +28 -1
  25. package/lib/implementations/napari-js/napari-loading-badge.d.ts +20 -0
  26. package/lib/implementations/napari-js/napari-navigator.d.ts +72 -0
  27. package/lib/implementations/napari-js/napari-region-overlay.d.ts +3 -0
  28. package/lib/implementations/napari-js/napari-spatial-tiles.d.ts +315 -0
  29. package/lib/implementations/napari-js/napari-visualizer.service.d.ts +49 -2
  30. package/lib/implementations/spatial-data-http/spatial-data-http.service.d.ts +28 -2
  31. package/lib/implementations/spatial-data-http/spatial-wire.d.ts +35 -1
  32. package/lib/spatial/spatial-tiles.d.ts +259 -0
  33. package/lib/spatial-controls/spatial-controls.component.d.ts +288 -3
  34. package/package.json +1 -1
@@ -0,0 +1,315 @@
1
+ import type { Layer, Viewer } from 'napari-js';
2
+ import type { Rgb } from '../../contracts/colormap-lut';
3
+ import { type SpatialDataPort } from '../../contracts/ports/spatial-data.port';
4
+ import type { SpatialViewState } from '../../contracts/display-types';
5
+ import { SpatialDataset, SpatialDensityRaster, SpatialImageRef, SpatialPolygonTile, SpatialTranscriptTile } from '../../contracts/spatial-dataset.contract';
6
+ import { SpatialSelectionMask } from '../../spatial/spatial-selection';
7
+ import { DataRect } from '../../spatial/spatial-tiles';
8
+ /**
9
+ * Level-of-detail cell outlines, transcripts and transcript density for the 2D spatial view.
10
+ *
11
+ * WHY A SEPARATE CLASS
12
+ * --------------------
13
+ * Everything else the spatial view draws is built once per (dataset, view) and only
14
+ * restyled afterwards. These layers also depend on the CAMERA: which tiles are on screen
15
+ * and which level of detail the zoom calls for. Keeping that loop — plan on camera idle,
16
+ * fetch tiles, merge, draw — out of the visualizer service keeps both readable.
17
+ *
18
+ * LAYER ORDER
19
+ * -----------
20
+ * napari-js's layer list is append-only (add / remove / clear), so order is kept by
21
+ * re-adding: cell fill, cell outline, the transcript density, then transcripts on top.
22
+ * The service's observation markers sit under all of them; while outlines are drawn the
23
+ * markers are hidden (a cell is its outline then, not a dot), so the cells never have to
24
+ * be ordered against them. The density goes OVER the markers on purpose: under 10^5
25
+ * dots it would only show in the gaps between cells. {@link afterObservations} restores
26
+ * the order after the service re-adds its markers.
27
+ *
28
+ * ONE LAYER PER GROUP
29
+ * -------------------
30
+ * Each group is one merged layer over every visible tile, rebuilt when the tile set or
31
+ * level changes. Recolouring (a new cell-type column, a selection) only rewrites the
32
+ * per-shape values — the geometry is not re-expanded.
33
+ */
34
+ export interface SpatialTileHost {
35
+ /** Latest dataset/view/selection, as the spatial subscription saw them. */
36
+ latest(): [SpatialDataset | null, SpatialViewState, SpatialSelectionMask] | null;
37
+ /** Canvas size in CSS pixels. */
38
+ canvasSize(): [number, number];
39
+ /** The continuous LUT the view is using (so cells coloured by a gene match the markers). */
40
+ continuousLut(view: SpatialViewState): Rgb[];
41
+ /** Outlines appeared or disappeared: the markers' visibility follows. */
42
+ polygonsShownChanged(shown: boolean): void;
43
+ /** The density window changed (auto-derived or set). */
44
+ densityChanged?(stats: {
45
+ lo: number;
46
+ hi: number;
47
+ max: number;
48
+ }): void;
49
+ /** Estimated transcripts in view for the current selection, against the budget. */
50
+ estimateChanged?(estimate: TranscriptEstimate | null): void;
51
+ /** Transcripts of each selected gene inside the view, or null when not known. */
52
+ geneCountsChanged?(counts: Record<string, number> | null): void;
53
+ /** The layers whose data is in flight now ("Transcripts", "Cells"…); empty when none. */
54
+ loadingChanged?(layers: string[]): void;
55
+ }
56
+ /** Xenium Explorer's "Estimated Transcript Points". */
57
+ export interface TranscriptEstimate {
58
+ /** Transcripts of the visible genes estimated to be in view. */
59
+ points: number;
60
+ /** The marker budget. */
61
+ max: number;
62
+ }
63
+ /** A transcript fetch the planner can key before running. */
64
+ interface TranscriptJob {
65
+ /** What one entry is — decides what hovering it says. */
66
+ kind: 'genes' | 'individual' | 'bins';
67
+ /** For bins: their size and grid origin, so a hovered bin's square can be recovered. */
68
+ bin?: {
69
+ size: number;
70
+ origin: [number, number];
71
+ };
72
+ /** Identifies what will be drawn, so an unchanged plan is a no-op. */
73
+ key: string;
74
+ /** Fetch and merge; `px` is each entry's marker diameter in screen pixels. A job may
75
+ * switch to another kind once it sees the data (individual → bins); it then says so. */
76
+ load(): Promise<TranscriptLoad>;
77
+ }
78
+ interface TranscriptLoad {
79
+ merged: SpatialTranscriptTile;
80
+ px: Float32Array;
81
+ /** For a gene selection: group it on this bin ladder once hidden genes are dropped. */
82
+ ladder?: {
83
+ baseBin: number;
84
+ levels: number;
85
+ start: number | null;
86
+ };
87
+ /** Already one marker per cluster per bin (zoomed out, from the density grids): no cells. */
88
+ clustered?: {
89
+ group: Int32Array;
90
+ names: string[];
91
+ genes: string[][];
92
+ bin: number;
93
+ origin: [number, number];
94
+ };
95
+ kind?: TranscriptJob['kind'];
96
+ bin?: TranscriptJob['bin'];
97
+ }
98
+ export declare class NapariSpatialTileLayers {
99
+ private readonly port;
100
+ private readonly host;
101
+ private viewer;
102
+ private cameraOff;
103
+ private timer;
104
+ /** Set when a tile of the current plan failed to load (see fetchAll). */
105
+ private planIncomplete;
106
+ /** Consecutive retries of an incomplete view. */
107
+ private tileRetries;
108
+ private token;
109
+ private readonly layers;
110
+ /** What each group currently shows, so an unchanged plan is a no-op. */
111
+ private readonly keys;
112
+ private polygonsShown;
113
+ /** Rings currently drawn and the (dataset, set, tiles) they came from. */
114
+ private currentRings;
115
+ private cellGeometryKey;
116
+ private currentNuclei;
117
+ private cellStyleKey;
118
+ /** Selection identity → revision, so a change key can name a selection cheaply. */
119
+ private lastSelection;
120
+ private selectionRevision;
121
+ constructor(port: SpatialDataPort, host: SpatialTileHost);
122
+ attach(viewer: Viewer): void;
123
+ /**
124
+ * Group a gene selection for the zoom: one marker per gene-tree cluster (a gene in no
125
+ * cluster is its own) per bin of the pyramid ladder, starting at the finest bin whose
126
+ * markers are 14 px apart and stepping up while the markers would exceed the budget.
127
+ * Null to draw every transcript: zoomed in, with the selection within the budget.
128
+ */
129
+ private groupSelection;
130
+ /** µm per observation unit of the drawn dataset, for describing a grouped marker's area. */
131
+ private micronsPerUnit;
132
+ /** Loads in flight, per layer label, for the loading badge. */
133
+ private readonly loading;
134
+ /** Report `label` loading while `work` is in flight. */
135
+ private track;
136
+ /** The loaded per-gene transcripts the in-view counts are taken from. */
137
+ private countSource;
138
+ /**
139
+ * Transcripts of each selected gene inside `rect`: each entry counts for the transcripts it
140
+ * stands for (an aggregate at a coarse level holds several). Null without per-gene data.
141
+ */
142
+ geneCountsIn(rect: DataRect): Record<string, number> | null;
143
+ /** Whether `layer` is one of the overlays drawn here (density, cells, transcripts). */
144
+ owns(layer: Layer): boolean;
145
+ detach(): void;
146
+ /** True while outlines are on screen — the service hides its dots then. */
147
+ get outlinesShown(): boolean;
148
+ /** Dataset, view or selection changed: re-plan now rather than on camera idle. */
149
+ refresh(): void;
150
+ /** Called right after the service adds its marker layer: everything goes back on top. */
151
+ afterObservations(): void;
152
+ private schedule;
153
+ private plan;
154
+ private planCells;
155
+ private selectionRev;
156
+ /** Per-shape values + colormap for the rings' owners. */
157
+ /**
158
+ * Per-shape values + colormap for the rings' owners, by the view's cell-colour mode
159
+ * (Xenium Explorer's "Cell Color"). A selection mutes what it leaves out.
160
+ */
161
+ private cellColors;
162
+ /** Codes of the group column that are switched off, or null when none are. */
163
+ private hiddenCodes;
164
+ private cellTypeColumnName;
165
+ private categoricalCodes;
166
+ private categoricalColormap;
167
+ /**
168
+ * The cell rings (and, for "both", the nucleus rings) covering `rect` at `level`, minus
169
+ * the hidden groups — reused when nothing that decides them changed. Null when the
170
+ * view went stale or the dataset has no set to draw.
171
+ */
172
+ private cellGeometry;
173
+ private planTranscripts;
174
+ /** One sized circle per entry, the size saying how many transcripts it stands for. */
175
+ private drawTranscriptCircles;
176
+ private drawTranscriptGlyphs;
177
+ private drawn;
178
+ private hoverTypes;
179
+ /** Details already fetched, by hovered entry. */
180
+ private readonly hoverCache;
181
+ private hoverKey;
182
+ private hoverTimer;
183
+ /**
184
+ * Tooltip lines for the transcript marker under world point `(wx, wy)`, or null.
185
+ *
186
+ * Returns what is known at once (gene, count, cell type); anything that needs the
187
+ * server — distinct genes in a group, its top genes and cells, the cell's display id —
188
+ * arrives through `onDetails` once the pointer has rested on the marker briefly.
189
+ */
190
+ hoverAt(wx: number, wy: number, radiusWorld: number, onDetails: (lines: string[]) => void): string[] | null;
191
+ private fetchDetails;
192
+ /** The tooltip text for entry `i`, with the server's details when they have arrived. */
193
+ private describe;
194
+ /** Physical marker size needs µm; null when the dataset's unit is unknown. */
195
+ private pxPerMicron;
196
+ /**
197
+ * The chosen genes, at the level the zoom calls for — or coarser, while the markers in
198
+ * view would exceed the budget.
199
+ */
200
+ private geneJob;
201
+ /**
202
+ * A gene selection from the pyramid's per-gene levels: starting at the level whose bins are
203
+ * `startBin`, fetch the view's tiles for the selected genes and step to a coarser level while
204
+ * one marker per cluster per bin would exceed the max. Hidden genes are fetched too (their
205
+ * counts in view still show) and dropped before drawing; grouping by cluster happens there.
206
+ */
207
+ private geneBinsJob;
208
+ /** The selection's clusters: each gene-tree group's visible genes, then each ungrouped gene. */
209
+ private selectionClusters;
210
+ /**
211
+ * Per-gene density grids summed for `genes`, cached (a cluster is asked for once per bin).
212
+ * Keyed by dataset too: this manager outlives a dataset, and another may share gene names.
213
+ */
214
+ private densityFor;
215
+ private readonly densityCache;
216
+ /**
217
+ * A gene selection zoomed out: one marker per cluster per bin, from the per-gene density
218
+ * grids. The bin starts where markers are 14 px apart and doubles until the markers fit the
219
+ * max; each marker sits at the count-weighted centre of the finer grid inside it, so markers
220
+ * follow the tissue rather than a lattice.
221
+ */
222
+ private clusterDensityJob;
223
+ /**
224
+ * Every gene: the transcripts themselves once they fit the budget, else bins of the
225
+ * all-gene pyramid sized by how many transcripts each holds.
226
+ */
227
+ private allGenesJob;
228
+ /** Bins of the all-gene pyramid at `level`, sized by how many transcripts each holds. */
229
+ private binsJob;
230
+ /** Per-entry colours both as RGBA (points) and as colormap values (glyph shapes). */
231
+ private transcriptColors;
232
+ private planDensity;
233
+ private countsCache;
234
+ /**
235
+ * Transcripts in view for the visible genes: each gene's dataset total times the share
236
+ * of the tissue on screen. An estimate — expression is not uniform — which is also what
237
+ * Xenium Explorer shows; it tells the user whether the budget will force grouping.
238
+ */
239
+ private planEstimate;
240
+ /** The density window in use and the densest bin, for the panel's threshold control. */
241
+ densityStats: {
242
+ lo: number;
243
+ hi: number;
244
+ max: number;
245
+ } | null;
246
+ /**
247
+ * Fetch every tile, drawing what arrived. A failure does not fail the view, but it marks the
248
+ * plan incomplete: the caller must not cache it as done, and `plan()` retries it.
249
+ */
250
+ private fetchAll;
251
+ /**
252
+ * Put a shapes layer in place for `group`: a geometry change builds a new layer, a
253
+ * colour-only change mutates values/colormap on the existing one.
254
+ */
255
+ private upsertShapes;
256
+ /**
257
+ * Install `layer` as `group`'s layer and restore the order of the groups above it.
258
+ * The layer was just added by the caller (so it is on top); groups that belong above
259
+ * it are removed and re-added.
260
+ */
261
+ private replace;
262
+ private drop;
263
+ private dropCells;
264
+ private setPolygonsShown;
265
+ }
266
+ /** What the transcript layer currently shows — what hovering needs to name a marker. */
267
+ interface DrawnTranscripts {
268
+ kind: TranscriptJob['kind'];
269
+ bin?: TranscriptJob['bin'];
270
+ merged: SpatialTranscriptTile;
271
+ /** Marker radius per entry, in observation units. */
272
+ radius: Float32Array;
273
+ genes: string[];
274
+ ref: SpatialImageRef | null;
275
+ /** For a grouped gene selection: each entry's group, and the groups' names. */
276
+ entryGroup?: Int32Array;
277
+ groupNames?: string[];
278
+ /** Each group's genes, when the markers came from the density grids (for the hover). */
279
+ groupGenes?: string[][];
280
+ /** Lazily built spatial index: bucket → entry indices. */
281
+ grid: {
282
+ size: number;
283
+ buckets: Map<number, number[]>;
284
+ } | null;
285
+ }
286
+ /** Index of the entry under `(x, y)` — within its own radius or `tolerance` — or -1. */
287
+ export declare function pickNearest(d: Pick<DrawnTranscripts, 'merged' | 'radius' | 'grid'>, x: number, y: number, tolerance: number): number;
288
+ /** Rings whose owning cell is in a switched-off group removed. */
289
+ export declare function filterRings(rings: SpatialPolygonTile, hidden: {
290
+ codes: Uint16Array;
291
+ hidden: Uint8Array;
292
+ } | null): SpatialPolygonTile;
293
+ /** Entries in a hidden group's cell, or of a hidden gene, removed (with their sizes). */
294
+ export declare function filterTranscripts(t: SpatialTranscriptTile, px: Float32Array, hidden: {
295
+ codes: Uint16Array;
296
+ hidden: Uint8Array;
297
+ } | null, hiddenGenes: Uint8Array | null): {
298
+ merged: SpatialTranscriptTile;
299
+ px: Float32Array;
300
+ };
301
+ /** Concatenate tiles' rings into one set, dropping rings already seen (a cell straddling
302
+ * two tiles may be listed by both). */
303
+ export declare function mergePolygonTiles(tiles: SpatialPolygonTile[]): SpatialPolygonTile;
304
+ /** Concatenate transcript tiles, stopping at `limit` entries. */
305
+ export declare function mergeTranscriptTiles(tiles: SpatialTranscriptTile[], limit?: number): SpatialTranscriptTile;
306
+ /**
307
+ * One marker per (cluster, `bin` square) from per-cluster density grids: the counts of the grid
308
+ * cells inside `area` summed per square, at their count-weighted centre. `slots[c]` is the gene
309
+ * slot whose icon cluster c's markers take.
310
+ */
311
+ export declare function clusterMarkers(rasters: SpatialDensityRaster[], slots: number[], bin: number, area: DataRect): {
312
+ tile: SpatialTranscriptTile;
313
+ group: Int32Array;
314
+ };
315
+ export {};
@@ -1,8 +1,9 @@
1
- import { Observable } from 'rxjs';
1
+ import { Observable, BehaviorSubject } from 'rxjs';
2
2
  import { Image } from 'image-js';
3
3
  import { IImageInfo } from '../../contracts/image.contract';
4
4
  import { SpatialDataPort } from '../../contracts/ports/spatial-data.port';
5
5
  import { SpatialObservations } from '../../contracts/spatial-dataset.contract';
6
+ import { TranscriptEstimate } from './napari-spatial-tiles';
6
7
  import { SpatialSelectionStore } from '../../store/spatial-selection.service';
7
8
  import { PlotType, PlotTypeDescriptor } from '../../contracts/plot-type';
8
9
  import { IVisualizer, PixelData, IntensityProfile, IIsosurfaceControls, IIntensityControls, ISurface3dControls } from '../../contracts/visualizer.contract';
@@ -162,6 +163,18 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
162
163
  /** Dataset the 3D scale bar was built for. */
163
164
  private spatialScaleBarKey;
164
165
  private spatialSub;
166
+ /** Level-of-detail cell outlines, transcripts and density over the 2D view — created on
167
+ * first use, and only when a spatial port is bound. */
168
+ private spatialTilesMgr;
169
+ /** For the panel: the transcripts-in-view estimate and the density window in use. */
170
+ readonly transcriptEstimate$: BehaviorSubject<TranscriptEstimate | null>;
171
+ /** Transcripts of each selected gene in view (see NapariSpatialTileLayers.geneCountsIn). */
172
+ readonly geneCountsInView$: BehaviorSubject<Record<string, number> | null>;
173
+ readonly densityStats$: BehaviorSubject<{
174
+ lo: number;
175
+ hi: number;
176
+ max: number;
177
+ } | null>;
165
178
  /** Latest (dataset, view, selection) the spatial subscription saw, so a slice
166
179
  * change can rebuild the markers for the new plane. */
167
180
  private spatialLatest;
@@ -172,6 +185,10 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
172
185
  * round-trip, so a fast sequence of colour-by changes can resolve out of
173
186
  * order. Only the newest rebuild is allowed to touch the layer. */
174
187
  private spatialRebuildToken;
188
+ /** "x reloading…" at the bottom of the canvas: the tile layers' loads and the observations'. */
189
+ private loadingBadge;
190
+ private tileLoading;
191
+ private observationsLoading;
175
192
  /** Monotonic load generation. Bumped by {@link reset} and {@link cancelLoading}; the frame-loading
176
193
  * loops (volume assembly, surface preload) capture it and bail when it changes, so a Cancel (or a
177
194
  * new plot) actually stops fetching frames instead of running to completion in the background. */
@@ -219,6 +236,12 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
219
236
  private invertEnabled;
220
237
  /** Physical scale bar overlay for the 2D image (null when 3D or the image has no µm/pixel). */
221
238
  private scaleBar;
239
+ /** Overview minimap (bottom-right), as OSD's navigator. */
240
+ private navigator;
241
+ /** Whether the navigator is shown — the same host setting OSD's navigator honours. */
242
+ private navigatorVisible;
243
+ /** Bumped per thumbnail request, so a slow one cannot overwrite a newer slice's. */
244
+ private navigatorToken;
222
245
  /** SVG region-drawing overlay for the 2D image (null until a 2D image is plotted). */
223
246
  private regionOverlay;
224
247
  /** Pixel-tool plumbing: the plot div id, coord transform, and bound tool hosts. */
@@ -331,6 +354,20 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
331
354
  /** (Re)install the physical scale bar over the 2D image, sized from the image's µm/pixel
332
355
  * (`/tiles/info` mppX, falling back to the image metadata). No-op without a physical size. */
333
356
  private installScaleBar;
357
+ /**
358
+ * The overview navigator for a 2D view with an image: a coarse thumbnail of the whole
359
+ * image with the viewport on it; click or drag to pan at the current zoom (as OSD).
360
+ */
361
+ private showLoading;
362
+ private installNavigator;
363
+ /**
364
+ * Draw the navigator's thumbnail from the coarsest pyramid level. A multichannel image
365
+ * is composited from its channels in their display colours, so the overview looks like
366
+ * the view rather than like channel 0 in grey.
367
+ */
368
+ private refreshNavigatorImage;
369
+ /** Show/hide the overview navigator (same setting as OSD's). */
370
+ setNavigatorVisible(visible: boolean): void;
334
371
  /** Convert a napari-js `Histogram` (bin count + min/max) to the pane's `IHistogram` (bin edges). */
335
372
  private toIHistogram;
336
373
  /**
@@ -421,6 +458,17 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
421
458
  * driven by drawn ROIs, so without the overlay there is no way to make a selection at all.
422
459
  */
423
460
  private mountSpatialOmics;
461
+ /**
462
+ * Whether the 2D observation markers are drawn: when the user wants them, and not while
463
+ * cell outlines are on screen — then a cell IS its outline, and a circle on top of it is
464
+ * noise. Zoomed out past the outline threshold, the circles stand in for the cells.
465
+ */
466
+ private spatialPointsVisible;
467
+ /**
468
+ * The tiled-geometry manager, built on first use. Its host callbacks read this
469
+ * service's live state, so it never holds a stale dataset or colormap.
470
+ */
471
+ private spatialTiles;
424
472
  /**
425
473
  * Region drawing for the 3D cloud.
426
474
  *
@@ -838,7 +886,6 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
838
886
  zoomIn(): void;
839
887
  zoomOut(): void;
840
888
  setDragMode(_mode: string | false): void;
841
- setNavigatorVisible(_visible: boolean): void;
842
889
  setImageSmoothingEnabled(enabled: boolean): void;
843
890
  setShowStack(_showstack: boolean): void;
844
891
  setZIndex(zIndex: number): void;
@@ -1,7 +1,7 @@
1
1
  import { HttpClient } from '@angular/common/http';
2
2
  import { Observable } from 'rxjs';
3
- import { SpatialDataPort } from '../../contracts/ports/spatial-data.port';
4
- import { SpatialColumn, SpatialDataset, SpatialEmbedding, SpatialPolygons } from '../../contracts/spatial-dataset.contract';
3
+ import { SpatialDataPort, TranscriptTileQuery } from '../../contracts/ports/spatial-data.port';
4
+ import { SpatialColumn, SpatialDataset, SpatialDensityRaster, SpatialEmbedding, SpatialPolygonTile, SpatialPolygons, CategoricalColumnMeta, SpatialMarkerGenes, SpatialTranscriptCounts, SpatialTranscriptSummary, SpatialTranscriptTile } from '../../contracts/spatial-dataset.contract';
5
5
  import { SpatialDatasetSummary, SpatialManifest } from './spatial-wire';
6
6
  import * as i0 from "@angular/core";
7
7
  /**
@@ -58,6 +58,12 @@ export declare class SpatialDataHttpService implements SpatialDataPort {
58
58
  private static readonly CACHE_LIMIT;
59
59
  /** In-flight requests, so double-clicking a gene issues one fetch. */
60
60
  private readonly inFlight;
61
+ /**
62
+ * Tiles get their own, larger cache: panning revisits them constantly, and one screen
63
+ * of a zoomed-out section is dozens of them — far past the vector cache's limit.
64
+ */
65
+ private readonly tileCache;
66
+ private static readonly TILE_CACHE_LIMIT;
61
67
  private volumePromise;
62
68
  private polygonsPromise;
63
69
  constructor(http: HttpClient);
@@ -89,6 +95,26 @@ export declare class SpatialDataHttpService implements SpatialDataPort {
89
95
  getFeatureVector(name: string): Promise<Float32Array>;
90
96
  searchFeatures(query: string, limit?: number): Promise<string[]>;
91
97
  getPolygons(): Promise<SpatialPolygons>;
98
+ getPolygonTile(set: string, level: number, gx: number, gy: number): Promise<SpatialPolygonTile>;
99
+ getTranscriptTile(level: number, gx: number, gy: number, query: TranscriptTileQuery): Promise<SpatialTranscriptTile>;
100
+ getTranscriptGeneBins(level: number, tx: number, ty: number, genes: string[]): Promise<SpatialTranscriptTile>;
101
+ getTranscriptBins(level: number, tx: number, ty: number): Promise<SpatialTranscriptTile>;
102
+ /** Small JSON answers, keyed by URL — hovering back and forth must not refetch. */
103
+ private readonly summaryCache;
104
+ getTranscriptSummary(query: {
105
+ box?: [number, number, number, number];
106
+ genes?: string[];
107
+ cells?: number[];
108
+ }): Promise<SpatialTranscriptSummary>;
109
+ getDensity(genes: string[], binSize?: number): Promise<SpatialDensityRaster>;
110
+ importGroups(label: string, table: string): Promise<{
111
+ column: CategoricalColumnMeta;
112
+ matched: number;
113
+ }>;
114
+ getMarkerGenes(column: string, perGroup?: number): Promise<SpatialMarkerGenes>;
115
+ getTranscriptCounts(genes: string[]): Promise<SpatialTranscriptCounts>;
116
+ /** LRU over tile promises, so concurrent asks share one request and a failure is not kept. */
117
+ private cachedTile;
92
118
  /**
93
119
  * The reference volume's voxels, single-flighted like the polygons.
94
120
  *
@@ -1,4 +1,4 @@
1
- import { SpatialColumn, SpatialColumnMeta, SpatialDataset, SpatialEmbedding, SpatialEmbeddingMeta, SpatialFeatureMeta, SpatialImageRef, SpatialObservations, SpatialPolygons, SpatialVolumeMeta } from '../../contracts/spatial-dataset.contract';
1
+ import { SpatialColumn, SpatialColumnMeta, SpatialDataset, SpatialDensityMeta, SpatialDensityRaster, SpatialEmbedding, SpatialEmbeddingMeta, SpatialFeatureMeta, SpatialImageRef, SpatialObservations, SpatialPolygonTile, SpatialPolygonTilesMeta, SpatialPolygons, SpatialTranscriptBinsMeta, SpatialTranscriptGeneBinsMeta, SpatialTranscriptBinsStatus, SpatialTranscriptTile, SpatialTranscriptTilesMeta, SpatialVolumeMeta } from '../../contracts/spatial-dataset.contract';
2
2
  /**
3
3
  * The wire format the bundled example server speaks, and its decoders.
4
4
  *
@@ -33,7 +33,18 @@ import { SpatialColumn, SpatialColumnMeta, SpatialDataset, SpatialEmbedding, Spa
33
33
  * GET {base}/spatial/{id}/feature/{name} -> f32[N]
34
34
  * GET {base}/spatial/{id}/features?q=&limit= -> { names: string[] }
35
35
  * GET {base}/spatial/{id}/polygons -> u32 count, u32[count+1] offsets, f32[2*rings] coords
36
+ * GET {base}/spatial/{id}/polygon-tile/{set}/{level}/{gx}/{gy}
37
+ * -> u32 count, u32[count] obs, u32[count+1] offsets, f32 coords
38
+ * GET {base}/spatial/{id}/transcript-tile/{level}/{gx}/{gy}?genes=A,B&quality=high|all
39
+ * -> see decodeTranscriptTile
40
+ * GET {base}/spatial/{id}/density?genes=A,B -> f32[rows*cols]
41
+ * GET {base}/spatial/{id}/transcript-bins/{level}/{tx}/{ty}
42
+ * -> transcript-tile layout, one entry per bin
36
43
  * ```
44
+ *
45
+ * The three tiled routes are optional and advertised in the manifest
46
+ * (`polygonTiles`, `transcriptTiles`, `density`); a server without them never
47
+ * sees them requested.
37
48
  */
38
49
  /** Bumped when the layout changes incompatibly; the client refuses anything else. */
39
50
  export declare const SPATIAL_WIRE_VERSION = 1;
@@ -63,6 +74,12 @@ export interface SpatialManifest {
63
74
  volume?: SpatialVolumeMeta;
64
75
  embeddings?: SpatialEmbeddingMeta[];
65
76
  micronsPerUnit?: number;
77
+ polygonTiles?: SpatialPolygonTilesMeta;
78
+ transcriptTiles?: SpatialTranscriptTilesMeta;
79
+ density?: SpatialDensityMeta;
80
+ transcriptBins?: SpatialTranscriptBinsMeta;
81
+ transcriptBinsStatus?: SpatialTranscriptBinsStatus;
82
+ transcriptGeneBins?: SpatialTranscriptGeneBinsMeta;
66
83
  }
67
84
  /** `GET /spatial/datasets` */
68
85
  export interface SpatialDatasetSummary {
@@ -104,6 +121,23 @@ export declare function decodeColumn(buf: ArrayBuffer, meta: SpatialColumnMeta,
104
121
  * field is 4-byte wide so each typed-array view stays aligned.
105
122
  */
106
123
  export declare function decodePolygons(buf: ArrayBuffer): SpatialPolygons;
124
+ /**
125
+ * `GET /polygon-tile/...` → one tile of rings with their owning observations. Layout
126
+ * `[u32 count][u32 obs × count][u32 offsets × (count+1)][f32 coords × 2·offsets[count]]`.
127
+ */
128
+ export declare function decodePolygonTile(buf: ArrayBuffer): SpatialPolygonTile;
129
+ /**
130
+ * `GET /transcript-tile/...` → transcripts or aggregates. Layout, 4-byte aligned:
131
+ * `[u32 n][u32 flags][f32 x·n][f32 y·n][f32 z·n][u32 weight·n][u32 obs·n][u16 gene·n, padded]`;
132
+ * flags bit 0 marks an aggregated level.
133
+ */
134
+ export declare function decodeTranscriptTile(buf: ArrayBuffer): SpatialTranscriptTile;
135
+ /**
136
+ * `GET /density?genes=&bin=` → the summed raster. Layout
137
+ * `[u32 rows][u32 cols][f32 cellW][f32 cellH][f32 originX][f32 originY][f32 rows·cols]`:
138
+ * the geometry travels with the values because re-binning changes it.
139
+ */
140
+ export declare function decodeDensity(buf: ArrayBuffer, genes: string[]): SpatialDensityRaster;
107
141
  /**
108
142
  * Fold a manifest plus its fetched coordinate/id/radius vectors into the
109
143
  * library-facing {@link SpatialDataset}. Column and feature *values* are not