@jax-data-science/sci-image-visualizer 0.7.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 (29) hide show
  1. package/esm2022/lib/contracts/display-types.mjs +1 -1
  2. package/esm2022/lib/contracts/plot-type-contribution.contract.mjs +1 -1
  3. package/esm2022/lib/contracts/ports/spatial-data.port.mjs +1 -1
  4. package/esm2022/lib/contracts/spatial-dataset.contract.mjs +1 -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-spatial-tiles.mjs +418 -29
  8. package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +35 -1
  9. package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +8 -1
  10. package/esm2022/lib/implementations/spatial-data-http/spatial-data-http.service.mjs +12 -1
  11. package/esm2022/lib/implementations/spatial-data-http/spatial-wire.mjs +2 -1
  12. package/esm2022/lib/routing-visualizer.service.mjs +5 -1
  13. package/esm2022/lib/spatial/spatial-tiles.mjs +144 -1
  14. package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +198 -9
  15. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +878 -30
  16. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
  17. package/lib/contracts/display-types.d.ts +4 -2
  18. package/lib/contracts/plot-type-contribution.contract.d.ts +9 -0
  19. package/lib/contracts/ports/spatial-data.port.d.ts +12 -1
  20. package/lib/contracts/spatial-dataset.contract.d.ts +30 -0
  21. package/lib/contracts/visualizer.contract.d.ts +7 -1
  22. package/lib/implementations/napari-js/napari-loading-badge.d.ts +20 -0
  23. package/lib/implementations/napari-js/napari-spatial-tiles.d.ts +76 -1
  24. package/lib/implementations/napari-js/napari-visualizer.service.d.ts +7 -0
  25. package/lib/implementations/spatial-data-http/spatial-data-http.service.d.ts +3 -1
  26. package/lib/implementations/spatial-data-http/spatial-wire.d.ts +2 -1
  27. package/lib/spatial/spatial-tiles.d.ts +38 -1
  28. package/lib/spatial-controls/spatial-controls.component.d.ts +54 -0
  29. package/package.json +1 -1
@@ -252,8 +252,10 @@ export interface SpatialViewState {
252
252
  * level-of-detail — aggregated when zoomed out, one per transcript at the finest zoom.
253
253
  */
254
254
  transcriptMode: 'off' | 'circles' | 'glyphs' | 'density';
255
- /** Colour transcripts by the type of the cell they fall in, or by gene. */
256
- transcriptColorBy: 'cellType' | 'gene';
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';
257
259
  /** Glyph per gene for `glyphs` mode; genes not listed take one by position. */
258
260
  transcriptGlyphs: Record<string, TranscriptGlyphName>;
259
261
  /** Multiplier on transcript marker size. */
@@ -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,6 @@
1
1
  import { InjectionToken } from '@angular/core';
2
2
  import { Observable } from 'rxjs';
3
- import { SpatialColumn, SpatialDataset, SpatialDensityRaster, SpatialEmbedding, SpatialPolygonTile, CategoricalColumnMeta, SpatialPolygons, SpatialTranscriptCounts, SpatialTranscriptSummary, SpatialTranscriptTile } from '../spatial-dataset.contract';
3
+ import { SpatialColumn, SpatialDataset, SpatialDensityRaster, SpatialEmbedding, SpatialPolygonTile, CategoricalColumnMeta, SpatialMarkerGenes, SpatialPolygons, SpatialTranscriptCounts, SpatialTranscriptSummary, SpatialTranscriptTile } from '../spatial-dataset.contract';
4
4
  /** Options for {@link SpatialDataPort.getTranscriptTile}. */
5
5
  export interface TranscriptTileQuery {
6
6
  /**
@@ -97,6 +97,11 @@ export interface SpatialDataPort {
97
97
  * `weight` its transcript count and `observation` its dominant cell.
98
98
  */
99
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>;
100
105
  /**
101
106
  * What is inside `box` (observation units): transcript count, distinct genes and cells,
102
107
  * the most frequent of each — optionally restricted to `genes` — plus display ids for
@@ -124,6 +129,12 @@ export interface SpatialDataPort {
124
129
  }>;
125
130
  /** Transcript totals per gene, for estimating how many markers a selection draws. */
126
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>;
127
138
  /**
128
139
  * The reference volume's voxels: a uint8 scalar field, x-fastest, of exactly
129
140
  * `width * height * depth` bytes as the dataset's {@link SpatialVolumeMeta}
@@ -371,6 +371,34 @@ export interface SpatialTranscriptTile {
371
371
  gene: Uint16Array;
372
372
  }
373
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
+ }
374
402
  export interface SpatialTranscriptCounts {
375
403
  /** Total transcripts per requested gene, over the whole dataset. */
376
404
  counts: Record<string, number>;
@@ -408,6 +436,8 @@ export interface SpatialDataset {
408
436
  density?: SpatialDensityMeta;
409
437
  /** The all-gene transcript grouping pyramid, when it has been built. */
410
438
  transcriptBins?: SpatialTranscriptBinsMeta;
439
+ /** Per-gene levels of the transcript pyramid (same bins): a gene selection at any zoom. */
440
+ transcriptGeneBins?: SpatialTranscriptGeneBinsMeta;
411
441
  /** Present while the server is still building {@link transcriptBins}. */
412
442
  transcriptBinsStatus?: SpatialTranscriptBinsStatus;
413
443
  /**
@@ -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 { CategoricalColumnMeta, SpatialDataset, SpatialEmbedding, SpatialTranscriptCounts } 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
  /**
@@ -344,6 +344,12 @@ export interface ISpatialControls {
344
344
  }>;
345
345
  /** Transcript totals per gene — for the points estimate. Optional. */
346
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>;
347
353
  /** Estimated transcripts in view for the current selection, against the marker budget. */
348
354
  getTranscriptEstimate$?(): Observable<{
349
355
  points: number;
@@ -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
+ }
@@ -2,8 +2,9 @@ import type { Layer, Viewer } from 'napari-js';
2
2
  import type { Rgb } from '../../contracts/colormap-lut';
3
3
  import { type SpatialDataPort } from '../../contracts/ports/spatial-data.port';
4
4
  import type { SpatialViewState } from '../../contracts/display-types';
5
- import { SpatialDataset, SpatialImageRef, SpatialPolygonTile, SpatialTranscriptTile } from '../../contracts/spatial-dataset.contract';
5
+ import { SpatialDataset, SpatialDensityRaster, SpatialImageRef, SpatialPolygonTile, SpatialTranscriptTile } from '../../contracts/spatial-dataset.contract';
6
6
  import { SpatialSelectionMask } from '../../spatial/spatial-selection';
7
+ import { DataRect } from '../../spatial/spatial-tiles';
7
8
  /**
8
9
  * Level-of-detail cell outlines, transcripts and transcript density for the 2D spatial view.
9
10
  *
@@ -47,6 +48,10 @@ export interface SpatialTileHost {
47
48
  }): void;
48
49
  /** Estimated transcripts in view for the current selection, against the budget. */
49
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;
50
55
  }
51
56
  /** Xenium Explorer's "Estimated Transcript Points". */
52
57
  export interface TranscriptEstimate {
@@ -73,6 +78,20 @@ interface TranscriptJob {
73
78
  interface TranscriptLoad {
74
79
  merged: SpatialTranscriptTile;
75
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
+ };
76
95
  kind?: TranscriptJob['kind'];
77
96
  bin?: TranscriptJob['bin'];
78
97
  }
@@ -101,6 +120,26 @@ export declare class NapariSpatialTileLayers {
101
120
  private selectionRevision;
102
121
  constructor(port: SpatialDataPort, host: SpatialTileHost);
103
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;
104
143
  /** Whether `layer` is one of the overlays drawn here (density, cells, transcripts). */
105
144
  owns(layer: Layer): boolean;
106
145
  detach(): void;
@@ -159,6 +198,28 @@ export declare class NapariSpatialTileLayers {
159
198
  * view would exceed the budget.
160
199
  */
161
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;
162
223
  /**
163
224
  * Every gene: the transcripts themselves once they fit the budget, else bins of the
164
225
  * all-gene pyramid sized by how many transcripts each holds.
@@ -211,6 +272,11 @@ interface DrawnTranscripts {
211
272
  radius: Float32Array;
212
273
  genes: string[];
213
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[][];
214
280
  /** Lazily built spatial index: bucket → entry indices. */
215
281
  grid: {
216
282
  size: number;
@@ -237,4 +303,13 @@ export declare function filterTranscripts(t: SpatialTranscriptTile, px: Float32A
237
303
  export declare function mergePolygonTiles(tiles: SpatialPolygonTile[]): SpatialPolygonTile;
238
304
  /** Concatenate transcript tiles, stopping at `limit` entries. */
239
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
+ };
240
315
  export {};
@@ -168,6 +168,8 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
168
168
  private spatialTilesMgr;
169
169
  /** For the panel: the transcripts-in-view estimate and the density window in use. */
170
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>;
171
173
  readonly densityStats$: BehaviorSubject<{
172
174
  lo: number;
173
175
  hi: number;
@@ -183,6 +185,10 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
183
185
  * round-trip, so a fast sequence of colour-by changes can resolve out of
184
186
  * order. Only the newest rebuild is allowed to touch the layer. */
185
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;
186
192
  /** Monotonic load generation. Bumped by {@link reset} and {@link cancelLoading}; the frame-loading
187
193
  * loops (volume assembly, surface preload) capture it and bail when it changes, so a Cancel (or a
188
194
  * new plot) actually stops fetching frames instead of running to completion in the background. */
@@ -352,6 +358,7 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
352
358
  * The overview navigator for a 2D view with an image: a coarse thumbnail of the whole
353
359
  * image with the viewport on it; click or drag to pan at the current zoom (as OSD).
354
360
  */
361
+ private showLoading;
355
362
  private installNavigator;
356
363
  /**
357
364
  * Draw the navigator's thumbnail from the coarsest pyramid level. A multichannel image
@@ -1,7 +1,7 @@
1
1
  import { HttpClient } from '@angular/common/http';
2
2
  import { Observable } from 'rxjs';
3
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';
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
  /**
@@ -97,6 +97,7 @@ export declare class SpatialDataHttpService implements SpatialDataPort {
97
97
  getPolygons(): Promise<SpatialPolygons>;
98
98
  getPolygonTile(set: string, level: number, gx: number, gy: number): Promise<SpatialPolygonTile>;
99
99
  getTranscriptTile(level: number, gx: number, gy: number, query: TranscriptTileQuery): Promise<SpatialTranscriptTile>;
100
+ getTranscriptGeneBins(level: number, tx: number, ty: number, genes: string[]): Promise<SpatialTranscriptTile>;
100
101
  getTranscriptBins(level: number, tx: number, ty: number): Promise<SpatialTranscriptTile>;
101
102
  /** Small JSON answers, keyed by URL — hovering back and forth must not refetch. */
102
103
  private readonly summaryCache;
@@ -110,6 +111,7 @@ export declare class SpatialDataHttpService implements SpatialDataPort {
110
111
  column: CategoricalColumnMeta;
111
112
  matched: number;
112
113
  }>;
114
+ getMarkerGenes(column: string, perGroup?: number): Promise<SpatialMarkerGenes>;
113
115
  getTranscriptCounts(genes: string[]): Promise<SpatialTranscriptCounts>;
114
116
  /** LRU over tile promises, so concurrent asks share one request and a failure is not kept. */
115
117
  private cachedTile;
@@ -1,4 +1,4 @@
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';
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
  *
@@ -79,6 +79,7 @@ export interface SpatialManifest {
79
79
  density?: SpatialDensityMeta;
80
80
  transcriptBins?: SpatialTranscriptBinsMeta;
81
81
  transcriptBinsStatus?: SpatialTranscriptBinsStatus;
82
+ transcriptGeneBins?: SpatialTranscriptGeneBinsMeta;
82
83
  }
83
84
  /** `GET /spatial/datasets` */
84
85
  export interface SpatialDatasetSummary {
@@ -13,7 +13,7 @@
13
13
  * `zoom · scale`. For a Xenium bundle the data unit is the micron and the world unit the
14
14
  * morphology pixel.
15
15
  */
16
- import type { SpatialBounds, SpatialImageRef, SpatialTileLevel } from '../contracts/spatial-dataset.contract';
16
+ import type { SpatialBounds, SpatialImageRef, SpatialTileLevel, SpatialTranscriptTile } from '../contracts/spatial-dataset.contract';
17
17
  import type { SpatialViewState } from '../contracts/display-types';
18
18
  import type { SpatialDataset } from '../contracts/spatial-dataset.contract';
19
19
  /** A rectangle in observation coordinates. */
@@ -83,6 +83,43 @@ export declare function polygonLevelFor(pxPerUnit: number, cellDiameter: number,
83
83
  * keeps the view fast.
84
84
  */
85
85
  export declare function transcriptLevelFor(pxPerUnit: number, levels: readonly SpatialTileLevel[], targetTilePx?: number): number;
86
+ /**
87
+ * The bin a selection of genes is grouped into at this zoom, in observation units, or null
88
+ * to draw every transcript. Bins follow the all-gene pyramid's ladder (`baseBin` × 2^m) so a
89
+ * gene selection and "all genes" group alike: the finest bin whose markers land at least
90
+ * `spacingPx` apart on screen. Once the finest bin is that wide, transcripts are drawn as
91
+ * themselves.
92
+ */
93
+ export declare function geneBinSize(pxPerUnit: number, baseBin: number, levels?: number, spacingPx?: number): number | null;
94
+ /** The entries of `t` inside `rect` (observation units). */
95
+ export declare function clipTranscripts(t: SpatialTranscriptTile, rect: DataRect): SpatialTranscriptTile;
96
+ /**
97
+ * Group transcripts into `bin`-wide squares of a grid anchored at the origin (so a pan does
98
+ * not move them), one entry per (group, square): `groupOf(gene slot)` says which group a gene
99
+ * is in — its cluster in the gene tree, or itself. Each entry sits at its transcripts'
100
+ * centroid, weighted by how many each stands for; it takes the gene and the cell holding most
101
+ * of them, each summed over all its transcripts in the entry. `group[i]` is entry i's group.
102
+ */
103
+ export declare function groupTranscripts(t: SpatialTranscriptTile, bin: number, groupOf?: (geneSlot: number) => number): {
104
+ tile: SpatialTranscriptTile;
105
+ group: Int32Array;
106
+ };
107
+ /**
108
+ * The colour of each gene-tree cluster of a selection, for colouring transcripts by cluster: a
109
+ * cluster named like a group of the cells' grouping takes that group's colour (its cells and its
110
+ * transcripts agree); the others take palette colours in tree order. Clusters are the tree's
111
+ * groups holding a selected gene, then each ungrouped selected gene on its own. Every gene of a
112
+ * cluster gets the same colour — the tree's swatches and the markers use this one map.
113
+ */
114
+ export declare function clusterColorMap(genes: readonly string[], groups: readonly {
115
+ name: string;
116
+ genes: readonly string[];
117
+ }[], cellColors: ReadonlyMap<string, string>, palette: readonly string[]): Map<string, string>;
118
+ /** The cluster a selected gene is in: its first gene-tree group holding it, else itself. */
119
+ export declare function clusterOfGene(gene: string, groups: readonly {
120
+ name: string;
121
+ genes: readonly string[];
122
+ }[]): string;
86
123
  /** Smallest and largest transcript marker, in canvas pixels. */
87
124
  export declare const TRANSCRIPT_MIN_PX = 4;
88
125
  export declare const TRANSCRIPT_MAX_PX = 32;
@@ -390,6 +390,21 @@ export declare class SpatialControlsComponent implements OnInit, OnDestroy {
390
390
  /** The eye toggle: hide or show genes without removing them from the selection. */
391
391
  onGenesShown(genes: string[], on: boolean): void;
392
392
  onGeneColor(gene: string, hex: string): void;
393
+ /** Swatches in the picker: the categorical palette genes are coloured from by default. */
394
+ readonly colorPresets: string[];
395
+ /** The gene the open picker edits. */
396
+ styleGene: string | null;
397
+ openGeneStyle(event: Event, gene: string, panel: {
398
+ toggle(e: Event): void;
399
+ hide(): void;
400
+ }): void;
401
+ /** The icon the open picker shows as chosen. */
402
+ get styleGlyph(): TranscriptGlyphName | null;
403
+ glyphPoints(glyph: TranscriptGlyphName): string;
404
+ /** A typed hex colour, accepted as `#rrggbb` or `rrggbb`; anything else is ignored. */
405
+ onGeneHex(gene: string, text: string): void;
406
+ /** Back to the gene's default icon and colour (by its position in the list). */
407
+ resetGeneStyle(gene: string): void;
393
408
  /** '±': every gene, or back to the chosen list. */
394
409
  onToggleAllGenes(): void;
395
410
  /** '⋮' menu. */
@@ -407,6 +422,39 @@ export declare class SpatialControlsComponent implements OnInit, OnDestroy {
407
422
  get cellOpacityPercent(): number;
408
423
  get densityOpacityPercent(): number;
409
424
  geneGroupError: string | null;
425
+ /** Transcripts of each selected gene in the current view, from the renderer. */
426
+ geneCounts: Record<string, number> | null;
427
+ geneCountOf(gene: string): number | null;
428
+ markersOpen: boolean;
429
+ markerColumn: string | null;
430
+ markerPerGroup: number;
431
+ readonly markerPerGroupOptions: {
432
+ label: string;
433
+ value: number;
434
+ }[];
435
+ markerClusters: string[];
436
+ markerClusterOptions: {
437
+ label: string;
438
+ value: string;
439
+ }[];
440
+ markerLoading: boolean;
441
+ markerError: string | null;
442
+ get canAddMarkers(): boolean;
443
+ /** Every categorical column but the segmentation method, which says nothing about genes. */
444
+ get markerColumnOptions(): {
445
+ label: string;
446
+ value: string;
447
+ }[];
448
+ openMarkers(): void;
449
+ onMarkerColumn(column: string): void;
450
+ /** The clusters of the chosen column, all picked to start with. */
451
+ private refreshMarkerClusters;
452
+ /**
453
+ * Add each picked cluster's top marker genes as a gene group named after it, and select
454
+ * them. A gene that marks several clusters goes to the one it is most specific to, so the
455
+ * tree lists it once.
456
+ */
457
+ addMarkerGenes(): Promise<void>;
410
458
  onNewGeneGroup(): void;
411
459
  /**
412
460
  * Import gene groups: CSV/TSV with a group and a gene column (header optional), e.g.
@@ -451,6 +499,12 @@ export declare class SpatialControlsComponent implements OnInit, OnDestroy {
451
499
  onGlyph(gene: string, glyph: TranscriptGlyphName): void;
452
500
  /** The colour gene `slot` is drawn in when transcripts are coloured by gene. */
453
501
  geneColor(slot: number): string;
502
+ /** Colours of the cells' groups, by name — what a cluster of the same name is drawn in. */
503
+ private cellGroupColors;
504
+ private cellGroupColorsFor;
505
+ private refreshCellGroupColors;
506
+ /** In Cluster colouring, a gene's swatch is its cluster's colour, as its markers are. */
507
+ geneSwatchOf(gene: string): string;
454
508
  geneColorOf(gene: string): string;
455
509
  reset(): void;
456
510
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jax-data-science/sci-image-visualizer",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "engines": {
5
5
  "node": ">=20.9.0"
6
6
  },