@jax-data-science/sci-image-visualizer 0.6.0 → 0.7.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.
- package/esm2022/lib/contracts/display-types.mjs +28 -1
- package/esm2022/lib/contracts/ports/spatial-data.port.mjs +3 -1
- package/esm2022/lib/contracts/spatial-dataset.contract.mjs +3 -1
- package/esm2022/lib/contracts/visualizer.contract.mjs +1 -1
- package/esm2022/lib/implementations/napari-js/napari-navigator.mjs +195 -0
- package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +10 -2
- package/esm2022/lib/implementations/napari-js/napari-spatial-tiles.mjs +1098 -0
- package/esm2022/lib/implementations/napari-js/napari-spatial-tooltip.mjs +6 -3
- package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +185 -8
- package/esm2022/lib/implementations/spatial-data-http/spatial-data-http.service.mjs +120 -2
- package/esm2022/lib/implementations/spatial-data-http/spatial-wire.mjs +76 -1
- package/esm2022/lib/routing-visualizer.service.mjs +10 -1
- package/esm2022/lib/spatial/spatial-tiles.mjs +506 -0
- package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +659 -19
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +2909 -62
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/lib/contracts/display-types.d.ts +94 -1
- package/lib/contracts/ports/spatial-data.port.d.ts +59 -1
- package/lib/contracts/spatial-dataset.contract.d.ts +167 -0
- package/lib/contracts/visualizer.contract.d.ts +22 -1
- package/lib/implementations/napari-js/napari-navigator.d.ts +72 -0
- package/lib/implementations/napari-js/napari-region-overlay.d.ts +3 -0
- package/lib/implementations/napari-js/napari-spatial-tiles.d.ts +240 -0
- package/lib/implementations/napari-js/napari-visualizer.service.d.ts +42 -2
- package/lib/implementations/spatial-data-http/spatial-data-http.service.d.ts +26 -2
- package/lib/implementations/spatial-data-http/spatial-wire.d.ts +34 -1
- package/lib/spatial/spatial-tiles.d.ts +222 -0
- package/lib/spatial-controls/spatial-controls.component.d.ts +234 -3
- package/package.json +1 -1
|
@@ -0,0 +1,240 @@
|
|
|
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, SpatialImageRef, SpatialPolygonTile, SpatialTranscriptTile } from '../../contracts/spatial-dataset.contract';
|
|
6
|
+
import { SpatialSelectionMask } from '../../spatial/spatial-selection';
|
|
7
|
+
/**
|
|
8
|
+
* Level-of-detail cell outlines, transcripts and transcript density for the 2D spatial view.
|
|
9
|
+
*
|
|
10
|
+
* WHY A SEPARATE CLASS
|
|
11
|
+
* --------------------
|
|
12
|
+
* Everything else the spatial view draws is built once per (dataset, view) and only
|
|
13
|
+
* restyled afterwards. These layers also depend on the CAMERA: which tiles are on screen
|
|
14
|
+
* and which level of detail the zoom calls for. Keeping that loop — plan on camera idle,
|
|
15
|
+
* fetch tiles, merge, draw — out of the visualizer service keeps both readable.
|
|
16
|
+
*
|
|
17
|
+
* LAYER ORDER
|
|
18
|
+
* -----------
|
|
19
|
+
* napari-js's layer list is append-only (add / remove / clear), so order is kept by
|
|
20
|
+
* re-adding: cell fill, cell outline, the transcript density, then transcripts on top.
|
|
21
|
+
* The service's observation markers sit under all of them; while outlines are drawn the
|
|
22
|
+
* markers are hidden (a cell is its outline then, not a dot), so the cells never have to
|
|
23
|
+
* be ordered against them. The density goes OVER the markers on purpose: under 10^5
|
|
24
|
+
* dots it would only show in the gaps between cells. {@link afterObservations} restores
|
|
25
|
+
* the order after the service re-adds its markers.
|
|
26
|
+
*
|
|
27
|
+
* ONE LAYER PER GROUP
|
|
28
|
+
* -------------------
|
|
29
|
+
* Each group is one merged layer over every visible tile, rebuilt when the tile set or
|
|
30
|
+
* level changes. Recolouring (a new cell-type column, a selection) only rewrites the
|
|
31
|
+
* per-shape values — the geometry is not re-expanded.
|
|
32
|
+
*/
|
|
33
|
+
export interface SpatialTileHost {
|
|
34
|
+
/** Latest dataset/view/selection, as the spatial subscription saw them. */
|
|
35
|
+
latest(): [SpatialDataset | null, SpatialViewState, SpatialSelectionMask] | null;
|
|
36
|
+
/** Canvas size in CSS pixels. */
|
|
37
|
+
canvasSize(): [number, number];
|
|
38
|
+
/** The continuous LUT the view is using (so cells coloured by a gene match the markers). */
|
|
39
|
+
continuousLut(view: SpatialViewState): Rgb[];
|
|
40
|
+
/** Outlines appeared or disappeared: the markers' visibility follows. */
|
|
41
|
+
polygonsShownChanged(shown: boolean): void;
|
|
42
|
+
/** The density window changed (auto-derived or set). */
|
|
43
|
+
densityChanged?(stats: {
|
|
44
|
+
lo: number;
|
|
45
|
+
hi: number;
|
|
46
|
+
max: number;
|
|
47
|
+
}): void;
|
|
48
|
+
/** Estimated transcripts in view for the current selection, against the budget. */
|
|
49
|
+
estimateChanged?(estimate: TranscriptEstimate | null): void;
|
|
50
|
+
}
|
|
51
|
+
/** Xenium Explorer's "Estimated Transcript Points". */
|
|
52
|
+
export interface TranscriptEstimate {
|
|
53
|
+
/** Transcripts of the visible genes estimated to be in view. */
|
|
54
|
+
points: number;
|
|
55
|
+
/** The marker budget. */
|
|
56
|
+
max: number;
|
|
57
|
+
}
|
|
58
|
+
/** A transcript fetch the planner can key before running. */
|
|
59
|
+
interface TranscriptJob {
|
|
60
|
+
/** What one entry is — decides what hovering it says. */
|
|
61
|
+
kind: 'genes' | 'individual' | 'bins';
|
|
62
|
+
/** For bins: their size and grid origin, so a hovered bin's square can be recovered. */
|
|
63
|
+
bin?: {
|
|
64
|
+
size: number;
|
|
65
|
+
origin: [number, number];
|
|
66
|
+
};
|
|
67
|
+
/** Identifies what will be drawn, so an unchanged plan is a no-op. */
|
|
68
|
+
key: string;
|
|
69
|
+
/** Fetch and merge; `px` is each entry's marker diameter in screen pixels. A job may
|
|
70
|
+
* switch to another kind once it sees the data (individual → bins); it then says so. */
|
|
71
|
+
load(): Promise<TranscriptLoad>;
|
|
72
|
+
}
|
|
73
|
+
interface TranscriptLoad {
|
|
74
|
+
merged: SpatialTranscriptTile;
|
|
75
|
+
px: Float32Array;
|
|
76
|
+
kind?: TranscriptJob['kind'];
|
|
77
|
+
bin?: TranscriptJob['bin'];
|
|
78
|
+
}
|
|
79
|
+
export declare class NapariSpatialTileLayers {
|
|
80
|
+
private readonly port;
|
|
81
|
+
private readonly host;
|
|
82
|
+
private viewer;
|
|
83
|
+
private cameraOff;
|
|
84
|
+
private timer;
|
|
85
|
+
/** Set when a tile of the current plan failed to load (see fetchAll). */
|
|
86
|
+
private planIncomplete;
|
|
87
|
+
/** Consecutive retries of an incomplete view. */
|
|
88
|
+
private tileRetries;
|
|
89
|
+
private token;
|
|
90
|
+
private readonly layers;
|
|
91
|
+
/** What each group currently shows, so an unchanged plan is a no-op. */
|
|
92
|
+
private readonly keys;
|
|
93
|
+
private polygonsShown;
|
|
94
|
+
/** Rings currently drawn and the (dataset, set, tiles) they came from. */
|
|
95
|
+
private currentRings;
|
|
96
|
+
private cellGeometryKey;
|
|
97
|
+
private currentNuclei;
|
|
98
|
+
private cellStyleKey;
|
|
99
|
+
/** Selection identity → revision, so a change key can name a selection cheaply. */
|
|
100
|
+
private lastSelection;
|
|
101
|
+
private selectionRevision;
|
|
102
|
+
constructor(port: SpatialDataPort, host: SpatialTileHost);
|
|
103
|
+
attach(viewer: Viewer): void;
|
|
104
|
+
/** Whether `layer` is one of the overlays drawn here (density, cells, transcripts). */
|
|
105
|
+
owns(layer: Layer): boolean;
|
|
106
|
+
detach(): void;
|
|
107
|
+
/** True while outlines are on screen — the service hides its dots then. */
|
|
108
|
+
get outlinesShown(): boolean;
|
|
109
|
+
/** Dataset, view or selection changed: re-plan now rather than on camera idle. */
|
|
110
|
+
refresh(): void;
|
|
111
|
+
/** Called right after the service adds its marker layer: everything goes back on top. */
|
|
112
|
+
afterObservations(): void;
|
|
113
|
+
private schedule;
|
|
114
|
+
private plan;
|
|
115
|
+
private planCells;
|
|
116
|
+
private selectionRev;
|
|
117
|
+
/** Per-shape values + colormap for the rings' owners. */
|
|
118
|
+
/**
|
|
119
|
+
* Per-shape values + colormap for the rings' owners, by the view's cell-colour mode
|
|
120
|
+
* (Xenium Explorer's "Cell Color"). A selection mutes what it leaves out.
|
|
121
|
+
*/
|
|
122
|
+
private cellColors;
|
|
123
|
+
/** Codes of the group column that are switched off, or null when none are. */
|
|
124
|
+
private hiddenCodes;
|
|
125
|
+
private cellTypeColumnName;
|
|
126
|
+
private categoricalCodes;
|
|
127
|
+
private categoricalColormap;
|
|
128
|
+
/**
|
|
129
|
+
* The cell rings (and, for "both", the nucleus rings) covering `rect` at `level`, minus
|
|
130
|
+
* the hidden groups — reused when nothing that decides them changed. Null when the
|
|
131
|
+
* view went stale or the dataset has no set to draw.
|
|
132
|
+
*/
|
|
133
|
+
private cellGeometry;
|
|
134
|
+
private planTranscripts;
|
|
135
|
+
/** One sized circle per entry, the size saying how many transcripts it stands for. */
|
|
136
|
+
private drawTranscriptCircles;
|
|
137
|
+
private drawTranscriptGlyphs;
|
|
138
|
+
private drawn;
|
|
139
|
+
private hoverTypes;
|
|
140
|
+
/** Details already fetched, by hovered entry. */
|
|
141
|
+
private readonly hoverCache;
|
|
142
|
+
private hoverKey;
|
|
143
|
+
private hoverTimer;
|
|
144
|
+
/**
|
|
145
|
+
* Tooltip lines for the transcript marker under world point `(wx, wy)`, or null.
|
|
146
|
+
*
|
|
147
|
+
* Returns what is known at once (gene, count, cell type); anything that needs the
|
|
148
|
+
* server — distinct genes in a group, its top genes and cells, the cell's display id —
|
|
149
|
+
* arrives through `onDetails` once the pointer has rested on the marker briefly.
|
|
150
|
+
*/
|
|
151
|
+
hoverAt(wx: number, wy: number, radiusWorld: number, onDetails: (lines: string[]) => void): string[] | null;
|
|
152
|
+
private fetchDetails;
|
|
153
|
+
/** The tooltip text for entry `i`, with the server's details when they have arrived. */
|
|
154
|
+
private describe;
|
|
155
|
+
/** Physical marker size needs µm; null when the dataset's unit is unknown. */
|
|
156
|
+
private pxPerMicron;
|
|
157
|
+
/**
|
|
158
|
+
* The chosen genes, at the level the zoom calls for — or coarser, while the markers in
|
|
159
|
+
* view would exceed the budget.
|
|
160
|
+
*/
|
|
161
|
+
private geneJob;
|
|
162
|
+
/**
|
|
163
|
+
* Every gene: the transcripts themselves once they fit the budget, else bins of the
|
|
164
|
+
* all-gene pyramid sized by how many transcripts each holds.
|
|
165
|
+
*/
|
|
166
|
+
private allGenesJob;
|
|
167
|
+
/** Bins of the all-gene pyramid at `level`, sized by how many transcripts each holds. */
|
|
168
|
+
private binsJob;
|
|
169
|
+
/** Per-entry colours both as RGBA (points) and as colormap values (glyph shapes). */
|
|
170
|
+
private transcriptColors;
|
|
171
|
+
private planDensity;
|
|
172
|
+
private countsCache;
|
|
173
|
+
/**
|
|
174
|
+
* Transcripts in view for the visible genes: each gene's dataset total times the share
|
|
175
|
+
* of the tissue on screen. An estimate — expression is not uniform — which is also what
|
|
176
|
+
* Xenium Explorer shows; it tells the user whether the budget will force grouping.
|
|
177
|
+
*/
|
|
178
|
+
private planEstimate;
|
|
179
|
+
/** The density window in use and the densest bin, for the panel's threshold control. */
|
|
180
|
+
densityStats: {
|
|
181
|
+
lo: number;
|
|
182
|
+
hi: number;
|
|
183
|
+
max: number;
|
|
184
|
+
} | null;
|
|
185
|
+
/**
|
|
186
|
+
* Fetch every tile, drawing what arrived. A failure does not fail the view, but it marks the
|
|
187
|
+
* plan incomplete: the caller must not cache it as done, and `plan()` retries it.
|
|
188
|
+
*/
|
|
189
|
+
private fetchAll;
|
|
190
|
+
/**
|
|
191
|
+
* Put a shapes layer in place for `group`: a geometry change builds a new layer, a
|
|
192
|
+
* colour-only change mutates values/colormap on the existing one.
|
|
193
|
+
*/
|
|
194
|
+
private upsertShapes;
|
|
195
|
+
/**
|
|
196
|
+
* Install `layer` as `group`'s layer and restore the order of the groups above it.
|
|
197
|
+
* The layer was just added by the caller (so it is on top); groups that belong above
|
|
198
|
+
* it are removed and re-added.
|
|
199
|
+
*/
|
|
200
|
+
private replace;
|
|
201
|
+
private drop;
|
|
202
|
+
private dropCells;
|
|
203
|
+
private setPolygonsShown;
|
|
204
|
+
}
|
|
205
|
+
/** What the transcript layer currently shows — what hovering needs to name a marker. */
|
|
206
|
+
interface DrawnTranscripts {
|
|
207
|
+
kind: TranscriptJob['kind'];
|
|
208
|
+
bin?: TranscriptJob['bin'];
|
|
209
|
+
merged: SpatialTranscriptTile;
|
|
210
|
+
/** Marker radius per entry, in observation units. */
|
|
211
|
+
radius: Float32Array;
|
|
212
|
+
genes: string[];
|
|
213
|
+
ref: SpatialImageRef | null;
|
|
214
|
+
/** Lazily built spatial index: bucket → entry indices. */
|
|
215
|
+
grid: {
|
|
216
|
+
size: number;
|
|
217
|
+
buckets: Map<number, number[]>;
|
|
218
|
+
} | null;
|
|
219
|
+
}
|
|
220
|
+
/** Index of the entry under `(x, y)` — within its own radius or `tolerance` — or -1. */
|
|
221
|
+
export declare function pickNearest(d: Pick<DrawnTranscripts, 'merged' | 'radius' | 'grid'>, x: number, y: number, tolerance: number): number;
|
|
222
|
+
/** Rings whose owning cell is in a switched-off group removed. */
|
|
223
|
+
export declare function filterRings(rings: SpatialPolygonTile, hidden: {
|
|
224
|
+
codes: Uint16Array;
|
|
225
|
+
hidden: Uint8Array;
|
|
226
|
+
} | null): SpatialPolygonTile;
|
|
227
|
+
/** Entries in a hidden group's cell, or of a hidden gene, removed (with their sizes). */
|
|
228
|
+
export declare function filterTranscripts(t: SpatialTranscriptTile, px: Float32Array, hidden: {
|
|
229
|
+
codes: Uint16Array;
|
|
230
|
+
hidden: Uint8Array;
|
|
231
|
+
} | null, hiddenGenes: Uint8Array | null): {
|
|
232
|
+
merged: SpatialTranscriptTile;
|
|
233
|
+
px: Float32Array;
|
|
234
|
+
};
|
|
235
|
+
/** Concatenate tiles' rings into one set, dropping rings already seen (a cell straddling
|
|
236
|
+
* two tiles may be listed by both). */
|
|
237
|
+
export declare function mergePolygonTiles(tiles: SpatialPolygonTile[]): SpatialPolygonTile;
|
|
238
|
+
/** Concatenate transcript tiles, stopping at `limit` entries. */
|
|
239
|
+
export declare function mergeTranscriptTiles(tiles: SpatialTranscriptTile[], limit?: number): SpatialTranscriptTile;
|
|
240
|
+
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,16 @@ 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
|
+
readonly densityStats$: BehaviorSubject<{
|
|
172
|
+
lo: number;
|
|
173
|
+
hi: number;
|
|
174
|
+
max: number;
|
|
175
|
+
} | null>;
|
|
165
176
|
/** Latest (dataset, view, selection) the spatial subscription saw, so a slice
|
|
166
177
|
* change can rebuild the markers for the new plane. */
|
|
167
178
|
private spatialLatest;
|
|
@@ -219,6 +230,12 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
|
|
|
219
230
|
private invertEnabled;
|
|
220
231
|
/** Physical scale bar overlay for the 2D image (null when 3D or the image has no µm/pixel). */
|
|
221
232
|
private scaleBar;
|
|
233
|
+
/** Overview minimap (bottom-right), as OSD's navigator. */
|
|
234
|
+
private navigator;
|
|
235
|
+
/** Whether the navigator is shown — the same host setting OSD's navigator honours. */
|
|
236
|
+
private navigatorVisible;
|
|
237
|
+
/** Bumped per thumbnail request, so a slow one cannot overwrite a newer slice's. */
|
|
238
|
+
private navigatorToken;
|
|
222
239
|
/** SVG region-drawing overlay for the 2D image (null until a 2D image is plotted). */
|
|
223
240
|
private regionOverlay;
|
|
224
241
|
/** Pixel-tool plumbing: the plot div id, coord transform, and bound tool hosts. */
|
|
@@ -331,6 +348,19 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
|
|
|
331
348
|
/** (Re)install the physical scale bar over the 2D image, sized from the image's µm/pixel
|
|
332
349
|
* (`/tiles/info` mppX, falling back to the image metadata). No-op without a physical size. */
|
|
333
350
|
private installScaleBar;
|
|
351
|
+
/**
|
|
352
|
+
* The overview navigator for a 2D view with an image: a coarse thumbnail of the whole
|
|
353
|
+
* image with the viewport on it; click or drag to pan at the current zoom (as OSD).
|
|
354
|
+
*/
|
|
355
|
+
private installNavigator;
|
|
356
|
+
/**
|
|
357
|
+
* Draw the navigator's thumbnail from the coarsest pyramid level. A multichannel image
|
|
358
|
+
* is composited from its channels in their display colours, so the overview looks like
|
|
359
|
+
* the view rather than like channel 0 in grey.
|
|
360
|
+
*/
|
|
361
|
+
private refreshNavigatorImage;
|
|
362
|
+
/** Show/hide the overview navigator (same setting as OSD's). */
|
|
363
|
+
setNavigatorVisible(visible: boolean): void;
|
|
334
364
|
/** Convert a napari-js `Histogram` (bin count + min/max) to the pane's `IHistogram` (bin edges). */
|
|
335
365
|
private toIHistogram;
|
|
336
366
|
/**
|
|
@@ -421,6 +451,17 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
|
|
|
421
451
|
* driven by drawn ROIs, so without the overlay there is no way to make a selection at all.
|
|
422
452
|
*/
|
|
423
453
|
private mountSpatialOmics;
|
|
454
|
+
/**
|
|
455
|
+
* Whether the 2D observation markers are drawn: when the user wants them, and not while
|
|
456
|
+
* cell outlines are on screen — then a cell IS its outline, and a circle on top of it is
|
|
457
|
+
* noise. Zoomed out past the outline threshold, the circles stand in for the cells.
|
|
458
|
+
*/
|
|
459
|
+
private spatialPointsVisible;
|
|
460
|
+
/**
|
|
461
|
+
* The tiled-geometry manager, built on first use. Its host callbacks read this
|
|
462
|
+
* service's live state, so it never holds a stale dataset or colormap.
|
|
463
|
+
*/
|
|
464
|
+
private spatialTiles;
|
|
424
465
|
/**
|
|
425
466
|
* Region drawing for the 3D cloud.
|
|
426
467
|
*
|
|
@@ -838,7 +879,6 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
|
|
|
838
879
|
zoomIn(): void;
|
|
839
880
|
zoomOut(): void;
|
|
840
881
|
setDragMode(_mode: string | false): void;
|
|
841
|
-
setNavigatorVisible(_visible: boolean): void;
|
|
842
882
|
setImageSmoothingEnabled(enabled: boolean): void;
|
|
843
883
|
setShowStack(_showstack: boolean): void;
|
|
844
884
|
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, 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,24 @@ 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
|
+
getTranscriptBins(level: number, tx: number, ty: number): Promise<SpatialTranscriptTile>;
|
|
101
|
+
/** Small JSON answers, keyed by URL — hovering back and forth must not refetch. */
|
|
102
|
+
private readonly summaryCache;
|
|
103
|
+
getTranscriptSummary(query: {
|
|
104
|
+
box?: [number, number, number, number];
|
|
105
|
+
genes?: string[];
|
|
106
|
+
cells?: number[];
|
|
107
|
+
}): Promise<SpatialTranscriptSummary>;
|
|
108
|
+
getDensity(genes: string[], binSize?: number): Promise<SpatialDensityRaster>;
|
|
109
|
+
importGroups(label: string, table: string): Promise<{
|
|
110
|
+
column: CategoricalColumnMeta;
|
|
111
|
+
matched: number;
|
|
112
|
+
}>;
|
|
113
|
+
getTranscriptCounts(genes: string[]): Promise<SpatialTranscriptCounts>;
|
|
114
|
+
/** LRU over tile promises, so concurrent asks share one request and a failure is not kept. */
|
|
115
|
+
private cachedTile;
|
|
92
116
|
/**
|
|
93
117
|
* The reference volume's voxels, single-flighted like the polygons.
|
|
94
118
|
*
|
|
@@ -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, 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,11 @@ 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;
|
|
66
82
|
}
|
|
67
83
|
/** `GET /spatial/datasets` */
|
|
68
84
|
export interface SpatialDatasetSummary {
|
|
@@ -104,6 +120,23 @@ export declare function decodeColumn(buf: ArrayBuffer, meta: SpatialColumnMeta,
|
|
|
104
120
|
* field is 4-byte wide so each typed-array view stays aligned.
|
|
105
121
|
*/
|
|
106
122
|
export declare function decodePolygons(buf: ArrayBuffer): SpatialPolygons;
|
|
123
|
+
/**
|
|
124
|
+
* `GET /polygon-tile/...` → one tile of rings with their owning observations. Layout
|
|
125
|
+
* `[u32 count][u32 obs × count][u32 offsets × (count+1)][f32 coords × 2·offsets[count]]`.
|
|
126
|
+
*/
|
|
127
|
+
export declare function decodePolygonTile(buf: ArrayBuffer): SpatialPolygonTile;
|
|
128
|
+
/**
|
|
129
|
+
* `GET /transcript-tile/...` → transcripts or aggregates. Layout, 4-byte aligned:
|
|
130
|
+
* `[u32 n][u32 flags][f32 x·n][f32 y·n][f32 z·n][u32 weight·n][u32 obs·n][u16 gene·n, padded]`;
|
|
131
|
+
* flags bit 0 marks an aggregated level.
|
|
132
|
+
*/
|
|
133
|
+
export declare function decodeTranscriptTile(buf: ArrayBuffer): SpatialTranscriptTile;
|
|
134
|
+
/**
|
|
135
|
+
* `GET /density?genes=&bin=` → the summed raster. Layout
|
|
136
|
+
* `[u32 rows][u32 cols][f32 cellW][f32 cellH][f32 originX][f32 originY][f32 rows·cols]`:
|
|
137
|
+
* the geometry travels with the values because re-binning changes it.
|
|
138
|
+
*/
|
|
139
|
+
export declare function decodeDensity(buf: ArrayBuffer, genes: string[]): SpatialDensityRaster;
|
|
107
140
|
/**
|
|
108
141
|
* Fold a manifest plus its fetched coordinate/id/radius vectors into the
|
|
109
142
|
* library-facing {@link SpatialDataset}. Column and feature *values* are not
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Level-of-detail planning for tiled spatial geometry — cell outlines and transcripts.
|
|
3
|
+
*
|
|
4
|
+
* The renderer knows its camera (a world-space centre, a zoom in canvas pixels per world
|
|
5
|
+
* unit, a canvas size); a tiled dataset knows its grids (one per level, in observation
|
|
6
|
+
* units). This module is the bridge: which level the zoom calls for, which tiles are on
|
|
7
|
+
* screen, how big a transcript marker should be. Everything here is pure, so the policy
|
|
8
|
+
* — the part that decides whether the view is fast and legible — is testable without a
|
|
9
|
+
* GPU.
|
|
10
|
+
*
|
|
11
|
+
* Observation units map onto world units through the dataset's `imageRef` affine
|
|
12
|
+
* (`world = data · scale + translate`), so "pixels per observation unit" is
|
|
13
|
+
* `zoom · scale`. For a Xenium bundle the data unit is the micron and the world unit the
|
|
14
|
+
* morphology pixel.
|
|
15
|
+
*/
|
|
16
|
+
import type { SpatialBounds, SpatialImageRef, SpatialTileLevel } from '../contracts/spatial-dataset.contract';
|
|
17
|
+
import type { SpatialViewState } from '../contracts/display-types';
|
|
18
|
+
import type { SpatialDataset } from '../contracts/spatial-dataset.contract';
|
|
19
|
+
/** A rectangle in observation coordinates. */
|
|
20
|
+
export interface DataRect {
|
|
21
|
+
x0: number;
|
|
22
|
+
y0: number;
|
|
23
|
+
x1: number;
|
|
24
|
+
y1: number;
|
|
25
|
+
}
|
|
26
|
+
/** Tile address. */
|
|
27
|
+
export interface TileKey {
|
|
28
|
+
level: number;
|
|
29
|
+
gx: number;
|
|
30
|
+
gy: number;
|
|
31
|
+
}
|
|
32
|
+
/** Whether cell outlines are drawn: the user's choice, else on when the dataset has any. */
|
|
33
|
+
export declare function cellsShown(dataset: Pick<SpatialDataset, 'polygonTiles' | 'polygons'> | null, view: Pick<SpatialViewState, 'showCells'>): boolean;
|
|
34
|
+
/** A column holding a hand-curated cell-type annotation, by naming convention. */
|
|
35
|
+
export declare function isCuratedColumn(name: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The categorical column that names each cell's type: the view's choice when it is a
|
|
38
|
+
* column of this dataset, else the first categorical column that is not a curated
|
|
39
|
+
* annotation (a pipeline's clustering is the default; curation is opted into), else
|
|
40
|
+
* any categorical column.
|
|
41
|
+
*/
|
|
42
|
+
export declare function cellTypeColumnFor(dataset: SpatialDataset, view: Pick<SpatialViewState, 'cellTypeColumn'>): string | null;
|
|
43
|
+
export declare function tileId(k: TileKey): string;
|
|
44
|
+
/** Canvas pixels per observation unit along x, for a camera zoom and a data→world affine. */
|
|
45
|
+
export declare function pixelsPerDataUnit(zoom: number, ref?: SpatialImageRef | null): number;
|
|
46
|
+
/**
|
|
47
|
+
* The part of observation space the camera shows, padded by `margin` (a fraction of the
|
|
48
|
+
* viewport) so tiles just off screen are already there when a pan brings them in.
|
|
49
|
+
*/
|
|
50
|
+
export declare function visibleDataRect(center: readonly [number, number], zoom: number, canvasW: number, canvasH: number, ref?: SpatialImageRef | null, margin?: number): DataRect | null;
|
|
51
|
+
/**
|
|
52
|
+
* Tiles of one level intersecting `rect`, clipped to the dataset's `bounds` (so zooming
|
|
53
|
+
* out over empty canvas does not ask for tiles that cannot exist). Ordered from the
|
|
54
|
+
* rectangle's centre outward, so the middle of the screen fills in first.
|
|
55
|
+
*/
|
|
56
|
+
export declare function tilesInRect(rect: DataRect, level: number, levels: readonly SpatialTileLevel[], bounds?: SpatialBounds | null, limit?: number): TileKey[];
|
|
57
|
+
/**
|
|
58
|
+
* Typical cell diameter, in observation units, used to judge how large cells appear.
|
|
59
|
+
* Derived from the per-observation radius when there is one; 12 µm otherwise.
|
|
60
|
+
*/
|
|
61
|
+
export declare function typicalCellDiameter(radius: Float32Array | number | undefined, fallback?: number): number;
|
|
62
|
+
/**
|
|
63
|
+
* On-screen cell diameter (px) at or above which each polygon level is used. A level
|
|
64
|
+
* halves the vertices of the one below, so each should take over when the cell is about
|
|
65
|
+
* half as large on screen. Below the last finite threshold outlines are not drawn at
|
|
66
|
+
* all — a cell a few pixels wide is a dot, and the observation markers already draw it
|
|
67
|
+
* as one.
|
|
68
|
+
*
|
|
69
|
+
* Xenium's fourth level (3 vertices per cell) is deliberately never used: seen on
|
|
70
|
+
* screen it is a field of triangles, which reads as an artefact rather than as cells.
|
|
71
|
+
* The dots below 8 px say the same thing more honestly.
|
|
72
|
+
*/
|
|
73
|
+
export declare const POLYGON_LEVEL_MIN_CELL_PX: number[];
|
|
74
|
+
/**
|
|
75
|
+
* Which polygon level suits this zoom, or -1 for "too small to outline".
|
|
76
|
+
* `detail` > 1 prefers finer levels (sharper, slower); < 1 coarser.
|
|
77
|
+
*/
|
|
78
|
+
export declare function polygonLevelFor(pxPerUnit: number, cellDiameter: number, levelCount: number, detail?: number): number;
|
|
79
|
+
/**
|
|
80
|
+
* Which transcript level suits this zoom: the one whose tiles come out closest to
|
|
81
|
+
* `targetTilePx` on screen. Coarser levels aggregate more, so this keeps the number of
|
|
82
|
+
* markers per screen roughly constant however far out the camera is — the property that
|
|
83
|
+
* keeps the view fast.
|
|
84
|
+
*/
|
|
85
|
+
export declare function transcriptLevelFor(pxPerUnit: number, levels: readonly SpatialTileLevel[], targetTilePx?: number): number;
|
|
86
|
+
/** Smallest and largest transcript marker, in canvas pixels. */
|
|
87
|
+
export declare const TRANSCRIPT_MIN_PX = 4;
|
|
88
|
+
export declare const TRANSCRIPT_MAX_PX = 32;
|
|
89
|
+
/**
|
|
90
|
+
* Physical diameter a single transcript is drawn at, in µm — clearly larger than the
|
|
91
|
+
* imaged spot (~0.3 µm) so it stays legible, and small enough that neighbouring
|
|
92
|
+
* molecules inside one cell stay apart.
|
|
93
|
+
*/
|
|
94
|
+
export declare const TRANSCRIPT_PHYSICAL_UM = 1.2;
|
|
95
|
+
/**
|
|
96
|
+
* Marker diameter in canvas pixels for an entry standing for `weight` transcripts.
|
|
97
|
+
*
|
|
98
|
+
* The convention spatial viewers share (deck.gl's `radiusMinPixels`/`radiusMaxPixels`,
|
|
99
|
+
* Xenium Explorer's transcript size): a PHYSICAL size, clamped to a screen-pixel range.
|
|
100
|
+
* Zoomed out, markers sit at the minimum and never vanish; zoomed in, they grow with the
|
|
101
|
+
* tissue instead of shrinking to specks next to cells hundreds of pixels wide. A fixed
|
|
102
|
+
* screen size does the opposite at high zoom, which is what made transcripts disappear
|
|
103
|
+
* at pixel-level zoom.
|
|
104
|
+
*
|
|
105
|
+
* Aggregates grow with the cube root of their count: area ∝ count would let one dense
|
|
106
|
+
* aggregate swallow its neighbours, the cube root keeps a 1000-transcript cluster clearly
|
|
107
|
+
* larger than a 10-transcript one without covering the tile.
|
|
108
|
+
*
|
|
109
|
+
* `pxPerMicron` is omitted for data with no known physical unit; the marker is then
|
|
110
|
+
* screen-sized only.
|
|
111
|
+
*/
|
|
112
|
+
export declare function transcriptMarkerPx(weight: number, scale?: number, pxPerMicron?: number): number;
|
|
113
|
+
/** Marker shapes for the transcript "icon" mode. All star-convex, so a fan fills them. */
|
|
114
|
+
export declare const TRANSCRIPT_GLYPHS: readonly ["circle", "star", "triangle", "square", "diamond", "cross", "hexagon", "triangle-down", "pentagon", "x"];
|
|
115
|
+
export type TranscriptGlyph = typeof TRANSCRIPT_GLYPHS[number];
|
|
116
|
+
/** Unit outline of a glyph, radius ≈ 1, centred on the origin, as `[x0, y0, x1, y1, …]`. */
|
|
117
|
+
export declare function glyphOutline(glyph: TranscriptGlyph): Float32Array;
|
|
118
|
+
/** The glyph a gene gets by default: by its position in the selected list. */
|
|
119
|
+
export declare function defaultGlyphFor(slot: number): TranscriptGlyph;
|
|
120
|
+
/**
|
|
121
|
+
* Expand point markers into glyph rings for a shapes layer: entry `i` becomes a copy of
|
|
122
|
+
* `glyphs[gene[i]]` scaled to `radius[i]` and centred on `(x[i], y[i])`.
|
|
123
|
+
*/
|
|
124
|
+
export declare function glyphRings(x: Float32Array, y: Float32Array, radius: Float32Array, glyphOf: (i: number) => Float32Array): {
|
|
125
|
+
coords: Float32Array;
|
|
126
|
+
offsets: Uint32Array;
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* A shapes layer colours through a colormap LUT, not per-shape RGBA. A categorical
|
|
130
|
+
* palette becomes a piecewise-constant colormap: category `c` of `n` owns the band
|
|
131
|
+
* `[c/n, (c+1)/n)`, and a shape takes the value at its band's centre, so the LUT's
|
|
132
|
+
* linear filtering never blends two categories.
|
|
133
|
+
*
|
|
134
|
+
* Returns the stops (linear 0..1 RGB) and a function mapping a category code to the
|
|
135
|
+
* value to hand the layer. `missing` is the colour of the extra band used for "no
|
|
136
|
+
* category".
|
|
137
|
+
*/
|
|
138
|
+
export declare function discreteColormapStops(rgb: readonly (readonly [number, number, number])[], missing: readonly [number, number, number]): {
|
|
139
|
+
stops: {
|
|
140
|
+
t: number;
|
|
141
|
+
color: [number, number, number];
|
|
142
|
+
}[];
|
|
143
|
+
valueOf: (code: number) => number;
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Separable Gaussian blur of a row-major raster, `sigma` in raster cells.
|
|
147
|
+
*
|
|
148
|
+
* A per-gene count raster at 10 µm is mostly zeros and ones — drawn as-is it is speckle,
|
|
149
|
+
* not a density. A σ of one to two cells (10–20 µm, about a cell diameter) turns it into
|
|
150
|
+
* a field that reads as territory while keeping structures a few cells wide.
|
|
151
|
+
*/
|
|
152
|
+
export declare function smoothRaster(values: Float32Array, rows: number, cols: number, sigma: number): Float32Array;
|
|
153
|
+
/**
|
|
154
|
+
* Colour a density raster as RGBA bytes: log-scaled, windowed at a high percentile of the
|
|
155
|
+
* non-zero values, and faded in from transparent so empty tissue shows what is under it.
|
|
156
|
+
*/
|
|
157
|
+
export declare function colorDensity(values: Float32Array, lut: readonly (readonly [number, number, number])[], opacity: number, { log, percentile }?: {
|
|
158
|
+
log?: boolean | undefined;
|
|
159
|
+
percentile?: number | undefined;
|
|
160
|
+
}): Uint8Array;
|
|
161
|
+
/** Smallest on-screen spacing between groups, in px — below it groups read as noise. */
|
|
162
|
+
export declare const GROUP_MIN_SPACING_PX = 14;
|
|
163
|
+
/** Area of `rect` inside `bounds` (the whole rect when there are none). */
|
|
164
|
+
export declare function visibleArea(rect: DataRect, bounds?: SpatialBounds | null): number;
|
|
165
|
+
export type AllGenesPlan = {
|
|
166
|
+
kind: 'individual';
|
|
167
|
+
} | {
|
|
168
|
+
kind: 'bins';
|
|
169
|
+
level: number;
|
|
170
|
+
} | {
|
|
171
|
+
kind: 'none';
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* How to draw every transcript in view without exceeding `budget` markers.
|
|
175
|
+
*
|
|
176
|
+
* - The transcripts themselves, once the estimated count in view fits the budget.
|
|
177
|
+
* - Otherwise the finest pyramid level whose bins are at least `minSpacingPx` apart on
|
|
178
|
+
* screen and number no more than the budget — groups big enough to read, few enough
|
|
179
|
+
* to draw.
|
|
180
|
+
*
|
|
181
|
+
* The count in view is estimated from the average density, which over- or under-shoots
|
|
182
|
+
* locally; the renderer still caps what it draws, so an estimate is enough to choose.
|
|
183
|
+
*/
|
|
184
|
+
export declare function allGenesPlan(o: {
|
|
185
|
+
rect: DataRect;
|
|
186
|
+
bounds: SpatialBounds;
|
|
187
|
+
total: number;
|
|
188
|
+
pxPerUnit: number;
|
|
189
|
+
levels: readonly {
|
|
190
|
+
binSize: number;
|
|
191
|
+
}[];
|
|
192
|
+
budget: number;
|
|
193
|
+
canIndividual: boolean;
|
|
194
|
+
minSpacingPx?: number;
|
|
195
|
+
}): AllGenesPlan;
|
|
196
|
+
/**
|
|
197
|
+
* Diameter in px of a group of `count` transcripts in a bin `binPx` wide, where
|
|
198
|
+
* `refCount` is a typical busy bin in view (its 95th percentile).
|
|
199
|
+
*
|
|
200
|
+
* Area grows with the count — a bin with a quarter of the transcripts gets half the
|
|
201
|
+
* diameter — between a third of the bin (so a sparse bin is still visible) and a little
|
|
202
|
+
* over the full bin (so dense neighbours just touch rather than pile up).
|
|
203
|
+
*/
|
|
204
|
+
export declare function groupedMarkerPx(count: number, refCount: number, binPx: number, scale?: number): number;
|
|
205
|
+
/** The `p` quantile (0..1) of a count vector — a sample of it for large vectors. */
|
|
206
|
+
export declare function quantileOf(values: ArrayLike<number>, p: number): number;
|
|
207
|
+
/** {@link tilesInRect} for a grid whose tile (0, 0) starts at `origin`. */
|
|
208
|
+
export declare function tilesInRectFrom(origin: readonly [number, number], rect: DataRect, level: number, levels: readonly SpatialTileLevel[], bounds?: SpatialBounds | null, limit?: number): TileKey[];
|
|
209
|
+
/** A density window from the non-empty bins: 1st to 99th percentile. */
|
|
210
|
+
export declare function densityAutoRange(values: ArrayLike<number>): [number, number];
|
|
211
|
+
/**
|
|
212
|
+
* Colour density bins through `lut` over the window `[lo, hi]` — Xenium Explorer's
|
|
213
|
+
* "scale threshold". Bins at or below `lo` are transparent, so empty tissue shows the
|
|
214
|
+
* image under it; bins at or above `hi` saturate.
|
|
215
|
+
*/
|
|
216
|
+
export declare function colorDensityWindow(values: ArrayLike<number>, lut: readonly (readonly [number, number, number])[], opacity: number, lo: number, hi: number): Uint8Array;
|
|
217
|
+
/**
|
|
218
|
+
* Inferno (matplotlib), as an inline `[stop, colour]` scale: the density map's default,
|
|
219
|
+
* as in Xenium Explorer. Inline because the colormap tree's `INFERNO_LUT` key is resolved
|
|
220
|
+
* at runtime by the store, which the renderer does not see.
|
|
221
|
+
*/
|
|
222
|
+
export declare const INFERNO_SCALE: [number, string][];
|