@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
@@ -113,7 +113,8 @@ export interface SpatialViewState {
113
113
  * setting for a backdrop and a useless one for the measurement.
114
114
  */
115
115
  volumeOpacity: number;
116
- /** Draw the observation cloud (3D). */
116
+ /** Draw the observation markers — the 3D cloud, or the 2D circles. In 2D they also
117
+ * stand in for cells too small on screen to outline. */
117
118
  showPoints: boolean;
118
119
  /**
119
120
  * Restrict the cloud to ONE imaged section, by index into the dataset's sampled
@@ -178,5 +179,99 @@ export interface SpatialViewState {
178
179
  * the markers: turning the cells down to read the field underneath must not
179
180
  * turn the field down with them. */
180
181
  geneMapOpacity: number;
182
+ /**
183
+ * Draw cell boundaries (for a dataset with `polygonTiles` or `polygons`).
184
+ *
185
+ * Outlines are level-of-detail: coarse rings when zoomed out, every vertex at the
186
+ * finest zoom, and none at all once a cell is a few pixels wide — there the
187
+ * observation markers already draw it as a dot.
188
+ *
189
+ * `null` (the default) means automatic: on for any dataset that has outlines, since a
190
+ * segmented cell is better shown as its shape than as a circle. A boolean is the
191
+ * user's explicit choice.
192
+ */
193
+ showCells: boolean | null;
194
+ /** Which boundary set (`cell`, `nucleus`, or `both` — cells as set by
195
+ * {@link cellDraw}, nuclei outlined over them); null takes the dataset's default. */
196
+ cellSet: string | null;
197
+ /**
198
+ * What colours the cells — Xenium Explorer's "Cell Color": the active group
199
+ * ({@link cellTypeColumn}), one gene's expression ({@link cellColorGene}), the cell's
200
+ * transcript density, one flat colour, or how the cell was segmented.
201
+ */
202
+ cellColorMode: 'group' | 'gene' | 'transcriptDensity' | 'single' | 'segmentation';
203
+ cellColorGene: string | null;
204
+ /** `#rrggbb` for {@link cellColorMode} `single`. */
205
+ cellSingleColor: string;
206
+ /**
207
+ * Categories of {@link cellTypeColumn} switched off in the groups list: their cells are
208
+ * not drawn, nor the transcripts inside them. Cleared when the group column changes.
209
+ */
210
+ hiddenGroups: string[];
211
+ /** Draw the tissue image (the Images section's switch). */
212
+ showImage: boolean;
213
+ /** Draw the annotation regions (the Annotations section's switch). Selecting from them
214
+ * still works while they are hidden. */
215
+ showAnnotations: boolean;
216
+ /** Fill the cells with their colour, outline them, or both. */
217
+ cellDraw: 'fill' | 'outline' | 'both';
218
+ /** Opacity of the cell fill (outlines draw opaque). */
219
+ cellOpacity: number;
220
+ /**
221
+ * The categorical column that says what TYPE each cell is: it colours the cells
222
+ * whenever nothing else is chosen, and it colours every transcript by the cell it
223
+ * falls in. Null picks the dataset's first categorical column. Swapping between a
224
+ * pipeline's clustering and a curated annotation is swapping this.
225
+ */
226
+ cellTypeColumn: string | null;
227
+ /** Genes whose transcripts are drawn. */
228
+ transcriptGenes: string[];
229
+ /** Selected genes switched off with their eye toggle — kept in the list, not drawn. */
230
+ transcriptHiddenGenes: string[];
231
+ /** Per-gene marker colour (`#rrggbb`); genes not listed take the palette by position. */
232
+ transcriptGeneColors: Record<string, string>;
233
+ /** Named gene groups (e.g. marker genes per cell type) for the selected-genes tree. */
234
+ transcriptGeneGroups: {
235
+ name: string;
236
+ genes: string[];
237
+ }[];
238
+ /**
239
+ * Draw EVERY gene's transcripts instead of {@link transcriptGenes}: grouped into bins
240
+ * sized by how many transcripts they hold, finer as the camera zooms in, down to one
241
+ * marker per transcript once those fit {@link transcriptBudget}.
242
+ */
243
+ transcriptAllGenes: boolean;
244
+ /**
245
+ * Most transcript markers drawn at once. The level of detail is chosen to stay under it,
246
+ * which is what keeps pan and zoom responsive however dense the data is.
247
+ */
248
+ transcriptBudget: number;
249
+ /**
250
+ * How transcripts are drawn: `circles` sized by how many transcripts each stands for,
251
+ * `glyphs` (one icon shape per gene), or a `density` raster. Circles and glyphs are
252
+ * level-of-detail — aggregated when zoomed out, one per transcript at the finest zoom.
253
+ */
254
+ transcriptMode: 'off' | 'circles' | 'glyphs' | 'density';
255
+ /** Colour transcripts by the type of the cell they fall in, by gene, or by the gene-tree
256
+ * cluster their gene is in (in the colour that cluster's cells have, when a cell grouping
257
+ * names it). */
258
+ transcriptColorBy: 'cellType' | 'gene' | 'cluster';
259
+ /** Glyph per gene for `glyphs` mode; genes not listed take one by position. */
260
+ transcriptGlyphs: Record<string, TranscriptGlyphName>;
261
+ /** Multiplier on transcript marker size. */
262
+ transcriptScale: number;
263
+ transcriptOpacity: number;
264
+ /** `high` keeps only confidently decoded transcripts. */
265
+ transcriptQuality: 'high' | 'all';
266
+ /** Opacity of the transcript density raster. */
267
+ densityOpacity: number;
268
+ /** Density bin edge in observation units (µm): 10, 20, 40 or 80. */
269
+ densityBin: number;
270
+ /** Colour window in transcripts per unit² (µm²); null derives it from the data. */
271
+ densityRange: [number, number] | null;
272
+ /** Colormap for the density bins; null uses Inferno. */
273
+ densityColormap: ColormapValue | null;
181
274
  }
275
+ /** Transcript glyph names — see `TRANSCRIPT_GLYPHS` in `spatial/spatial-tiles.ts`. */
276
+ export type TranscriptGlyphName = 'circle' | 'star' | 'triangle' | 'square' | 'diamond' | 'cross' | 'hexagon' | 'triangle-down' | 'pentagon' | 'x';
182
277
  export declare const DEFAULT_SPATIAL_VIEW: SpatialViewState;
@@ -102,6 +102,15 @@ export interface PlotModeViewport {
102
102
  /** Same rect, emitted once the viewport settles (existing getViewportChange$ semantics).
103
103
  * A new subscriber first receives the current rect when the viewport is ready. */
104
104
  settled$: Observable<PlotModeRect>;
105
+ /**
106
+ * Pan and zoom so a level-0 image rect fills the view (within the viewer's zoom
107
+ * constraints), e.g. to take the user to a region a mode proposes. Animated unless
108
+ * `immediately`. A no-op while the viewport is not ready. Optional: backends without
109
+ * a 2D image viewport leave it out, so callers feature-detect it.
110
+ */
111
+ fitBounds?(rect: PlotModeRect, options?: {
112
+ immediately?: boolean;
113
+ }): void;
105
114
  }
106
115
  /** The class a {@link PlotModeTools.armBrush} stroke paints. */
107
116
  export interface PlotModeBrushClass {
@@ -1,6 +1,21 @@
1
1
  import { InjectionToken } from '@angular/core';
2
2
  import { Observable } from 'rxjs';
3
- import { SpatialColumn, SpatialDataset, SpatialEmbedding, SpatialPolygons } from '../spatial-dataset.contract';
3
+ import { SpatialColumn, SpatialDataset, SpatialDensityRaster, SpatialEmbedding, SpatialPolygonTile, CategoricalColumnMeta, SpatialMarkerGenes, SpatialPolygons, SpatialTranscriptCounts, SpatialTranscriptSummary, SpatialTranscriptTile } from '../spatial-dataset.contract';
4
+ /** Options for {@link SpatialDataPort.getTranscriptTile}. */
5
+ export interface TranscriptTileQuery {
6
+ /**
7
+ * Genes to include, by name; the tile's `gene` codes index this list. `[ALL_GENES]`
8
+ * asks for every gene, unaggregated, at level 0 only and clipped to {@link box} — the
9
+ * `gene` codes are then the dataset's own gene indices.
10
+ */
11
+ genes: string[];
12
+ /** `high` (default) keeps only confidently decoded transcripts. */
13
+ quality?: 'high' | 'all';
14
+ /** Clip to `[x0, y0, x1, y1]` in observation units. */
15
+ box?: [number, number, number, number];
16
+ }
17
+ /** {@link TranscriptTileQuery.genes} value meaning "every gene". */
18
+ export declare const ALL_GENES = "*";
4
19
  /**
5
20
  * Spatial-omics data access, inverted as a port so the visualization library
6
21
  * never learns how the data is stored or served — exactly like
@@ -66,6 +81,60 @@ export interface SpatialDataPort {
66
81
  * `polygons`. Optional — a spot-based assay (Visium) has no segmentation.
67
82
  */
68
83
  getPolygons?(): Promise<SpatialPolygons>;
84
+ /**
85
+ * One tile of boundaries from `polygonTiles`. An empty tile resolves with `count` 0 —
86
+ * a tile outside the tissue is an answer, not an error. Optional: only datasets that
87
+ * advertise `polygonTiles` need it.
88
+ */
89
+ getPolygonTile?(set: string, level: number, gx: number, gy: number): Promise<SpatialPolygonTile>;
90
+ /**
91
+ * One tile of transcripts from `transcriptTiles`, restricted to `query.genes`.
92
+ * Optional: only datasets that advertise `transcriptTiles` need it.
93
+ */
94
+ getTranscriptTile?(level: number, gx: number, gy: number, query: TranscriptTileQuery): Promise<SpatialTranscriptTile>;
95
+ /**
96
+ * One tile of the all-gene grouping pyramid (`transcriptBins`): an entry per bin, with
97
+ * `weight` its transcript count and `observation` its dominant cell.
98
+ */
99
+ getTranscriptBins?(level: number, tx: number, ty: number): Promise<SpatialTranscriptTile>;
100
+ /**
101
+ * One tile of the per-gene levels for `genes`: each entry is one gene's transcripts in one bin
102
+ * (centroid, count, dominant cell); its `gene` is the position in `genes`. Optional.
103
+ */
104
+ getTranscriptGeneBins?(level: number, tx: number, ty: number, genes: string[]): Promise<SpatialTranscriptTile>;
105
+ /**
106
+ * What is inside `box` (observation units): transcript count, distinct genes and cells,
107
+ * the most frequent of each — optionally restricted to `genes` — plus display ids for
108
+ * `cells`. Omit `box` for a cell-id lookup only. Optional; drives the hover details of
109
+ * transcript markers.
110
+ */
111
+ getTranscriptSummary?(query: {
112
+ box?: [number, number, number, number];
113
+ genes?: string[];
114
+ cells?: number[];
115
+ }): Promise<SpatialTranscriptSummary>;
116
+ /**
117
+ * The summed transcript density of `genes` (`[ALL_GENES]` for every gene) on the
118
+ * dataset's `density` raster, re-binned to `binSize` observation units when given.
119
+ * Optional: only datasets that advertise `density` need it.
120
+ */
121
+ getDensity?(genes: string[], binSize?: number): Promise<SpatialDensityRaster>;
122
+ /**
123
+ * Import a cell grouping — a CSV/TSV of `cell_id` and `group` — under `label`. The new
124
+ * categorical column is added to the dataset (which re-emits) and resolved. Optional.
125
+ */
126
+ importGroups?(label: string, table: string): Promise<{
127
+ column: CategoricalColumnMeta;
128
+ matched: number;
129
+ }>;
130
+ /** Transcript totals per gene, for estimating how many markers a selection draws. */
131
+ getTranscriptCounts?(genes: string[]): Promise<SpatialTranscriptCounts>;
132
+ /**
133
+ * The top `perGroup` marker genes of every group of categorical `column`, for building
134
+ * gene groups from cell clusters ("Macrophages: CD163, MRC1, …"). Optional, and possibly
135
+ * slow the first time: the server makes one pass over the expression matrix.
136
+ */
137
+ getMarkerGenes?(column: string, perGroup?: number): Promise<SpatialMarkerGenes>;
69
138
  /**
70
139
  * The reference volume's voxels: a uint8 scalar field, x-fastest, of exactly
71
140
  * `width * height * depth` bytes as the dataset's {@link SpatialVolumeMeta}
@@ -63,6 +63,20 @@ export interface CategoricalColumnMeta extends SpatialColumnMetaBase {
63
63
  kind: 'categorical';
64
64
  /** Category labels; a value's label is `categories[codes[i]]`. */
65
65
  categories: string[];
66
+ /**
67
+ * Heading the column is listed under in a group picker — e.g. "Xenium Onboard Analysis
68
+ * groups" for a pipeline's clusterings, "Imported groups" for ones added by a user.
69
+ */
70
+ section?: string;
71
+ /**
72
+ * Columns that are variants of one choice — k-means at k = 2…10 — share a family: a
73
+ * picker lists the family once and offers the variants beside it.
74
+ */
75
+ family?: {
76
+ id: string;
77
+ label: string;
78
+ variant: string;
79
+ };
66
80
  /** Authored display colours as `#rrggbb`, index-aligned with
67
81
  * {@link categories}. Supply these to keep the viewer's palette identical to
68
82
  * the figures the same analysis produced in R/Python; omit to let the viewer
@@ -228,6 +242,177 @@ export interface SpatialEmbedding {
228
242
  y: Float32Array;
229
243
  z?: Float32Array;
230
244
  }
245
+ /**
246
+ * Level-of-detail geometry, served one grid tile at a time.
247
+ *
248
+ * Whole-dataset vectors stop working somewhere past 10^5 observations: 717k Xenium cell
249
+ * outlines are ~140 MB, and a whole-transcriptome run has ~10^9 transcripts. What scales
250
+ * is what map tiles do — a square grid in the observations' coordinate space, one grid
251
+ * per level, each level's tiles twice the size of the one below and carrying coarser
252
+ * geometry. The renderer asks only for the tiles on screen, at the level the zoom needs.
253
+ *
254
+ * Tile `(gx, gy)` at level `l` covers `[gx·s, (gx+1)·s) × [gy·s, (gy+1)·s)` with
255
+ * `s = levels[l].tileSize`, in the same units as {@link SpatialObservations.x}.
256
+ */
257
+ export interface SpatialTileLevel {
258
+ /** Edge length of one tile, in observation coordinate units. */
259
+ tileSize: number;
260
+ }
261
+ /** Axis-aligned extent `[minX, minY, maxX, maxY]` in observation coordinates. */
262
+ export type SpatialBounds = [number, number, number, number];
263
+ /** Tiled cell/nucleus boundaries. Level 0 is the finest (every vertex). */
264
+ export interface SpatialPolygonTilesMeta {
265
+ bounds: SpatialBounds;
266
+ /** Boundary sets on offer — typically `cell` and `nucleus`. */
267
+ sets: {
268
+ name: string;
269
+ label: string;
270
+ }[];
271
+ /** The set to draw when the user has not chosen. */
272
+ defaultSet?: string;
273
+ levels: SpatialTileLevel[];
274
+ }
275
+ /** Tiled transcripts. Level 0 is one entry per transcript; coarser levels aggregate. */
276
+ export interface SpatialTranscriptTilesMeta {
277
+ bounds: SpatialBounds;
278
+ /** Total transcripts, for display. */
279
+ count?: number;
280
+ /** Real genes (control probes excluded). Names come from the feature search. */
281
+ geneCount: number;
282
+ hasZ: boolean;
283
+ levels: (SpatialTileLevel & {
284
+ /** True when an entry stands for several transcripts (see {@link SpatialTranscriptTile.weight}). */
285
+ aggregated: boolean;
286
+ })[];
287
+ /**
288
+ * Levels up to and including this one assign each entry to the cell it lies in
289
+ * exactly; above it the assignment names the cell nearest an aggregate's centroid.
290
+ */
291
+ exactCellLevel?: number;
292
+ }
293
+ /**
294
+ * The all-gene transcript grouping pyramid: square bins, four times coarser per level,
295
+ * each holding a count, a centroid and the cell that contributed most of its transcripts.
296
+ * Served per tile, in the {@link SpatialTranscriptTile} layout (one entry per bin).
297
+ *
298
+ * Tile `(tx, ty)` of level `l` covers `origin + [tx·s, (tx+1)·s) × [ty·s, (ty+1)·s)` with
299
+ * `s = levels[l].tileSize` — note the origin, which the per-gene tiles do not have.
300
+ */
301
+ export interface SpatialTranscriptBinsMeta {
302
+ bounds: SpatialBounds;
303
+ origin: [number, number];
304
+ /** Transcripts the pyramid was built from. */
305
+ count: number;
306
+ levels: {
307
+ binSize: number;
308
+ tileSize: number;
309
+ }[];
310
+ }
311
+ /**
312
+ * A grouping pyramid the server is still building (it builds one itself when a dataset
313
+ * arrives without it). "All genes" becomes available once it is done.
314
+ */
315
+ export interface SpatialTranscriptBinsStatus {
316
+ state: 'queued' | 'building' | 'failed';
317
+ /** Source tiles processed so far, of `total`. */
318
+ done?: number;
319
+ total?: number;
320
+ message?: string;
321
+ }
322
+ /** What is in an area — the hover details of a transcript marker or grouped bin. */
323
+ export interface SpatialTranscriptSummary {
324
+ /** Absent for a pure cell-id lookup. */
325
+ transcripts?: number;
326
+ /** Distinct genes among them. */
327
+ genes?: number;
328
+ topGenes?: {
329
+ name: string;
330
+ count: number;
331
+ }[];
332
+ /** Distinct cells they fall in, and how many fall in none. */
333
+ cells?: number;
334
+ unassigned?: number;
335
+ topCells?: {
336
+ index: number;
337
+ id?: string;
338
+ count: number;
339
+ }[];
340
+ /** Display ids of the cells asked for, by observation index. */
341
+ cellIds?: Record<number, string>;
342
+ }
343
+ /** A per-gene transcript-count raster covering the section. */
344
+ export interface SpatialDensityMeta {
345
+ /** Size of one raster cell, in observation units, `[x, y]`. */
346
+ gridSize: [number, number];
347
+ /** Observation-space position of raster cell (0, 0)'s near corner. */
348
+ origin: [number, number];
349
+ rows: number;
350
+ cols: number;
351
+ }
352
+ /** "No observation" in {@link SpatialTranscriptTile.observation}. */
353
+ export declare const NO_OBSERVATION = 4294967295;
354
+ /** One tile of boundaries: rings as in {@link SpatialPolygons}, plus who owns each ring. */
355
+ export interface SpatialPolygonTile extends SpatialPolygons {
356
+ /** Observation index of ring `i` — the join key to columns and features. */
357
+ observation: Uint32Array;
358
+ }
359
+ /** One tile of transcripts (or aggregates of them). Every array has length {@link count}. */
360
+ export interface SpatialTranscriptTile {
361
+ readonly count: number;
362
+ aggregated: boolean;
363
+ x: Float32Array;
364
+ y: Float32Array;
365
+ z: Float32Array;
366
+ /** Transcripts each entry stands for — 1 at an unaggregated level. */
367
+ weight: Uint32Array;
368
+ /** Observation (cell) the entry falls in, or {@link NO_OBSERVATION}. */
369
+ observation: Uint32Array;
370
+ /** Index into the `genes` list the tile was requested with. */
371
+ gene: Uint16Array;
372
+ }
373
+ /** Transcript totals for an estimate of how many markers a selection would draw. */
374
+ /** The per-gene levels: each level's bin and tile size, on the all-gene pyramid's grid. */
375
+ export interface SpatialTranscriptGeneBinsMeta {
376
+ origin: [number, number];
377
+ levels: {
378
+ binSize: number;
379
+ tileSize: number;
380
+ }[];
381
+ }
382
+ /**
383
+ * Marker genes of each group of a categorical column: per group, the real genes most
384
+ * specific to it, best first. A gene's score is its mean `log1p(count)` in the group minus
385
+ * that in every other cell; it must be detected in at least 10% of the group's cells, and
386
+ * in more of them than elsewhere.
387
+ */
388
+ export interface SpatialMarkerGenes {
389
+ column: string;
390
+ groups: {
391
+ name: string;
392
+ /** Cells in the group. */
393
+ cells: number;
394
+ genes: {
395
+ name: string;
396
+ score: number;
397
+ pctIn: number;
398
+ pctOut: number;
399
+ }[];
400
+ }[];
401
+ }
402
+ export interface SpatialTranscriptCounts {
403
+ /** Total transcripts per requested gene, over the whole dataset. */
404
+ counts: Record<string, number>;
405
+ /** Total over every gene. */
406
+ total: number;
407
+ /** Extent the totals are spread over (observation units). */
408
+ bounds: SpatialBounds;
409
+ }
410
+ /** A summed density raster for a list of genes; `values` is row-major `rows × cols`. */
411
+ export interface SpatialDensityRaster {
412
+ meta: SpatialDensityMeta;
413
+ genes: string[];
414
+ values: Float32Array;
415
+ }
231
416
  export interface SpatialDataset {
232
417
  /** Stable id — the key the port's lazy accessors are scoped to. */
233
418
  id: string;
@@ -243,6 +428,18 @@ export interface SpatialDataset {
243
428
  volume?: SpatialVolumeMeta;
244
429
  /** Embeddings available for these observations, whose coordinates are fetched on demand. */
245
430
  embeddings?: SpatialEmbeddingMeta[];
431
+ /** Level-of-detail boundaries, fetched per tile — see {@link SpatialPolygonTilesMeta}. */
432
+ polygonTiles?: SpatialPolygonTilesMeta;
433
+ /** Level-of-detail transcripts, fetched per tile — see {@link SpatialTranscriptTilesMeta}. */
434
+ transcriptTiles?: SpatialTranscriptTilesMeta;
435
+ /** A served per-gene transcript density raster. */
436
+ density?: SpatialDensityMeta;
437
+ /** The all-gene transcript grouping pyramid, when it has been built. */
438
+ transcriptBins?: SpatialTranscriptBinsMeta;
439
+ /** Per-gene levels of the transcript pyramid (same bins): a gene selection at any zoom. */
440
+ transcriptGeneBins?: SpatialTranscriptGeneBinsMeta;
441
+ /** Present while the server is still building {@link transcriptBins}. */
442
+ transcriptBinsStatus?: SpatialTranscriptBinsStatus;
246
443
  /**
247
444
  * Microns per observation coordinate unit — what makes a scale bar possible.
248
445
  *
@@ -8,7 +8,7 @@ import { ViewerCapabilities } from './capabilities.contract';
8
8
  import { IRegionOverlay } from './region-overlay.contract';
9
9
  import { IHistogram } from './channel-histogram-api.contract';
10
10
  import { ColormapNode, IWandOptions, IBrushOptions, SpatialViewState, SpatialColorBy } from './display-types';
11
- import { SpatialDataset, SpatialEmbedding } from './spatial-dataset.contract';
11
+ import { CategoricalColumnMeta, SpatialDataset, SpatialEmbedding, SpatialMarkerGenes, SpatialTranscriptCounts } from './spatial-dataset.contract';
12
12
  import { SpatialSelectionMask } from '../spatial/spatial-selection';
13
13
  import type { PlotModeViewport } from './plot-type-contribution.contract';
14
14
  /**
@@ -334,6 +334,33 @@ export interface ISpatialControls {
334
334
  getEmbedding?(name: string): Promise<SpatialEmbedding>;
335
335
  /** Names of the categorical columns available to group by. */
336
336
  categoricalColumns(): string[];
337
+ /**
338
+ * Import a cell grouping (CSV/TSV of `cell_id`, `group`) as a new categorical column.
339
+ * Optional: only data sources that can join cell ids implement it.
340
+ */
341
+ importGroups?(label: string, table: string): Promise<{
342
+ column: CategoricalColumnMeta;
343
+ matched: number;
344
+ }>;
345
+ /** Transcript totals per gene — for the points estimate. Optional. */
346
+ transcriptCounts?(genes: string[]): Promise<SpatialTranscriptCounts>;
347
+ /** Marker genes of each group of a categorical column. Optional (see the port). */
348
+ markerGenes?(column: string, perGroup?: number): Promise<SpatialMarkerGenes>;
349
+ /** Transcripts of each selected gene in the current view, or null when not known
350
+ * (all genes at once, transcripts off, or a selection zoomed out on a dataset without the
351
+ * pyramid's per-gene levels, where clusters are drawn from summed density grids). */
352
+ getGeneCountsInView$?(): Observable<Record<string, number> | null>;
353
+ /** Estimated transcripts in view for the current selection, against the marker budget. */
354
+ getTranscriptEstimate$?(): Observable<{
355
+ points: number;
356
+ max: number;
357
+ } | null>;
358
+ /** The density map's colour window in use (auto-derived or set) and its densest bin. */
359
+ getDensityStats$?(): Observable<{
360
+ lo: number;
361
+ hi: number;
362
+ max: number;
363
+ } | null>;
337
364
  /**
338
365
  * The z positions of the sections the dataset was imaged at, ascending — or
339
366
  * null when its z is continuous rather than sectioned (so there are no sections
@@ -0,0 +1,20 @@
1
+ /**
2
+ * "Transcripts reloading…" at the bottom of the canvas while a layer's data is in flight: one
3
+ * line naming every layer still loading, on a semi-opaque backing so it reads over any tissue.
4
+ *
5
+ * Shown only once something has been loading for {@link SHOW_AFTER_MS}, so a redraw served from
6
+ * cache does not flash it.
7
+ */
8
+ export declare const SHOW_AFTER_MS = 150;
9
+ export declare class NapariLoadingBadge {
10
+ private readonly host;
11
+ private readonly el;
12
+ private names;
13
+ private timer;
14
+ constructor(host: HTMLElement);
15
+ /** The layers loading now; empty hides the badge. */
16
+ set(names: readonly string[]): void;
17
+ /** The text currently set (shown or about to be). */
18
+ get text(): string;
19
+ destroy(): void;
20
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Overview navigator (minimap) for the napari-js 2D views, mirroring the OSD backend's
3
+ * built-in navigator: a thumbnail of the whole image in the bottom-right corner with the
4
+ * current viewport drawn on it. Clicking re-centres the main view on that point at the
5
+ * same zoom; dragging pans continuously — exactly OSD's behaviour.
6
+ *
7
+ * napari keeps layers at full-resolution pixel scale, so the camera's world units ARE
8
+ * image pixels and the thumbnail maps onto `[0, width) × [0, height)` directly.
9
+ */
10
+ /** The slice of the napari 2D camera the navigator reads and drives. */
11
+ export interface NavigatorCamera {
12
+ center: readonly [number, number];
13
+ readonly zoom: number;
14
+ readonly changed: {
15
+ connect(listener: () => void): () => void;
16
+ };
17
+ }
18
+ /** Where the thumbnail sits inside the navigator box, in CSS px. */
19
+ export interface NavigatorLayout {
20
+ /** Box size. */
21
+ width: number;
22
+ height: number;
23
+ /** CSS px per world unit (image pixel). */
24
+ scale: number;
25
+ }
26
+ /** Fraction of the host's width the navigator takes — OSD's `navigatorSizeRatio`. */
27
+ export declare const NAVIGATOR_SIZE_RATIO = 0.16;
28
+ /** Box size and scale for a `worldW × worldH` image in a host `hostW` wide. */
29
+ export declare function navigatorLayout(hostW: number, worldW: number, worldH: number, ratio?: number): NavigatorLayout | null;
30
+ /** The part of the world the camera shows, as `[x0, y0, x1, y1]`. */
31
+ export declare function viewportWorldRect(center: readonly [number, number], zoom: number, hostW: number, hostH: number): [number, number, number, number];
32
+ /** A point in the navigator box → world coordinates, clamped to the image. */
33
+ export declare function navigatorToWorld(px: number, py: number, layout: NavigatorLayout, worldW: number, worldH: number): [number, number];
34
+ export declare class NapariNavigator {
35
+ private readonly host;
36
+ private readonly camera;
37
+ private worldW;
38
+ private worldH;
39
+ /** Called when the pointer enters the navigator — e.g. to hide a hover tooltip that
40
+ * was describing the canvas underneath. */
41
+ private readonly onEnter?;
42
+ private readonly box;
43
+ private readonly canvas;
44
+ private readonly region;
45
+ private readonly disconnectCamera;
46
+ private readonly resizeObserver?;
47
+ /** Aborted by destroy() to remove every DOM listener the box holds. */
48
+ private readonly listeners;
49
+ private layout;
50
+ private image;
51
+ private dragging;
52
+ private visible;
53
+ constructor(host: HTMLElement, camera: NavigatorCamera, worldW: number, worldH: number,
54
+ /** Called when the pointer enters the navigator — e.g. to hide a hover tooltip that
55
+ * was describing the canvas underneath. */
56
+ onEnter?: (() => void) | undefined);
57
+ /** The thumbnail to draw: any image of the whole world (a coarse pyramid level). */
58
+ setImage(image: CanvasImageSource | null): void;
59
+ /** The image's world size changed (another image was loaded). */
60
+ setWorld(width: number, height: number): void;
61
+ setVisible(visible: boolean): void;
62
+ destroy(): void;
63
+ private relayout;
64
+ private drawImage;
65
+ /** Draw the viewport rectangle, clipped to the box (as OSD does when zoomed out). */
66
+ private updateRegion;
67
+ /** Re-centre the main view on the navigator point under the pointer, keeping the zoom. */
68
+ private panTo;
69
+ private readonly onDown;
70
+ private readonly onMove;
71
+ private readonly onUp;
72
+ }
@@ -48,6 +48,7 @@ export declare class NapariRegionOverlay implements IRegionOverlay {
48
48
  private marqueeEl;
49
49
  private draftPath;
50
50
  private drawing;
51
+ private regionsVisible;
51
52
  /** In-progress manipulation (select/move modes): dragging a body, a polygon vertex, or a
52
53
  * rectangle corner. `anchor` is the fixed opposite corner for a rectangle resize. */
53
54
  private edit;
@@ -124,6 +125,8 @@ export declare class NapariRegionOverlay implements IRegionOverlay {
124
125
  private commitRectangle;
125
126
  private commitPolygon;
126
127
  private hitTest;
128
+ /** Show or hide the drawn regions. A region being drawn stays visible either way. */
129
+ setRegionsVisible(visible: boolean): void;
127
130
  redraw(): void;
128
131
  /** Draw a region's classification label at its top-left, when labels are enabled (matches OSD). */
129
132
  private drawLabel;