@jax-data-science/sci-image-visualizer 0.5.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/README.md +49 -0
- package/esm2022/lib/contracts/display-types.mjs +28 -1
- package/esm2022/lib/contracts/plot-type-contribution.contract.mjs +1 -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/toolbar-tool.contract.mjs +46 -4
- 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 +186 -10
- package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +2 -2
- package/esm2022/lib/implementations/plotly/plotly.service.mjs +2 -3
- 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/esm2022/lib/toolbar/brush/brush-tool.service.mjs +44 -3
- package/esm2022/lib/toolbar/toolbar.component.mjs +15 -3
- package/esm2022/lib/visualizer.component.mjs +221 -7
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +3238 -84
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/lib/contracts/display-types.d.ts +104 -1
- package/lib/contracts/plot-type-contribution.contract.d.ts +27 -0
- package/lib/contracts/ports/spatial-data.port.d.ts +59 -1
- package/lib/contracts/spatial-dataset.contract.d.ts +167 -0
- package/lib/contracts/toolbar-tool.contract.d.ts +84 -5
- 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/lib/toolbar/brush/brush-tool.service.d.ts +21 -0
- package/lib/toolbar/toolbar.component.d.ts +11 -3
- package/lib/visualizer.component.d.ts +54 -2
- package/package.json +1 -1
|
@@ -38,6 +38,16 @@ export interface IWandOptions {
|
|
|
38
38
|
export interface IBrushOptions {
|
|
39
39
|
/** Brush diameter in matrix (image) pixels. */
|
|
40
40
|
size?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Paint as one class: new regions take this label, and a stroke only extends
|
|
43
|
+
* or merges regions with the same label. Unset = the plain brush (label
|
|
44
|
+
* `'Region'`, extends any region). `setBrushMode(true, …)` sets the class in
|
|
45
|
+
* full; `setBrushOptions` changes it only when `label` or `color` is passed,
|
|
46
|
+
* so a size-only update keeps it.
|
|
47
|
+
*/
|
|
48
|
+
label?: string;
|
|
49
|
+
/** Colour for the painted regions instead of the shape colour. Kept against preset re-apply. */
|
|
50
|
+
color?: string;
|
|
41
51
|
}
|
|
42
52
|
/** What drives point colour in the spatial-omics mode: an annotation column
|
|
43
53
|
* from the dataset, or one feature (gene) vector fetched on demand. */
|
|
@@ -103,7 +113,8 @@ export interface SpatialViewState {
|
|
|
103
113
|
* setting for a backdrop and a useless one for the measurement.
|
|
104
114
|
*/
|
|
105
115
|
volumeOpacity: number;
|
|
106
|
-
/** Draw the observation cloud
|
|
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. */
|
|
107
118
|
showPoints: boolean;
|
|
108
119
|
/**
|
|
109
120
|
* Restrict the cloud to ONE imaged section, by index into the dataset's sampled
|
|
@@ -168,5 +179,97 @@ export interface SpatialViewState {
|
|
|
168
179
|
* the markers: turning the cells down to read the field underneath must not
|
|
169
180
|
* turn the field down with them. */
|
|
170
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, or by gene. */
|
|
256
|
+
transcriptColorBy: 'cellType' | 'gene';
|
|
257
|
+
/** Glyph per gene for `glyphs` mode; genes not listed take one by position. */
|
|
258
|
+
transcriptGlyphs: Record<string, TranscriptGlyphName>;
|
|
259
|
+
/** Multiplier on transcript marker size. */
|
|
260
|
+
transcriptScale: number;
|
|
261
|
+
transcriptOpacity: number;
|
|
262
|
+
/** `high` keeps only confidently decoded transcripts. */
|
|
263
|
+
transcriptQuality: 'high' | 'all';
|
|
264
|
+
/** Opacity of the transcript density raster. */
|
|
265
|
+
densityOpacity: number;
|
|
266
|
+
/** Density bin edge in observation units (µm): 10, 20, 40 or 80. */
|
|
267
|
+
densityBin: number;
|
|
268
|
+
/** Colour window in transcripts per unit² (µm²); null derives it from the data. */
|
|
269
|
+
densityRange: [number, number] | null;
|
|
270
|
+
/** Colormap for the density bins; null uses Inferno. */
|
|
271
|
+
densityColormap: ColormapValue | null;
|
|
171
272
|
}
|
|
273
|
+
/** Transcript glyph names — see `TRANSCRIPT_GLYPHS` in `spatial/spatial-tiles.ts`. */
|
|
274
|
+
export type TranscriptGlyphName = 'circle' | 'star' | 'triangle' | 'square' | 'diamond' | 'cross' | 'hexagon' | 'triangle-down' | 'pentagon' | 'x';
|
|
172
275
|
export declare const DEFAULT_SPATIAL_VIEW: SpatialViewState;
|
|
@@ -103,6 +103,31 @@ export interface PlotModeViewport {
|
|
|
103
103
|
* A new subscriber first receives the current rect when the viewport is ready. */
|
|
104
104
|
settled$: Observable<PlotModeRect>;
|
|
105
105
|
}
|
|
106
|
+
/** The class a {@link PlotModeTools.armBrush} stroke paints. */
|
|
107
|
+
export interface PlotModeBrushClass {
|
|
108
|
+
/** Label for the painted regions; the stroke only extends or merges regions with it. */
|
|
109
|
+
label?: string;
|
|
110
|
+
/** Colour for the painted regions (instead of the toolbar's shape colour). */
|
|
111
|
+
color?: string;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The visualizer's own toolbar tools, armed on a mode's behalf. Going through
|
|
115
|
+
* this rather than {@link IVisualizer} keeps the toolbar in step: the armed
|
|
116
|
+
* tool's button is highlighted and its controls (e.g. the brush size) show.
|
|
117
|
+
*/
|
|
118
|
+
export interface PlotModeTools {
|
|
119
|
+
/**
|
|
120
|
+
* Arm the toolbar brush, painting `brushClass`. Calling it again while the
|
|
121
|
+
* brush is armed switches class; the next stroke starts a region of it.
|
|
122
|
+
* The user picking any tool from the toolbar (including the brush) replaces
|
|
123
|
+
* this with the plain tool.
|
|
124
|
+
*/
|
|
125
|
+
armBrush(brushClass?: PlotModeBrushClass): void;
|
|
126
|
+
/** Disarm whatever tool is armed, as if the user toggled it off. */
|
|
127
|
+
disarm(): void;
|
|
128
|
+
/** The armed toolbar tool id ('brush', 'pan', … or null). Replays the current value. */
|
|
129
|
+
activeTool$: Observable<string | null>;
|
|
130
|
+
}
|
|
106
131
|
/** Everything a contributed mode gets to work with while it is active. */
|
|
107
132
|
export interface PlotModeContext {
|
|
108
133
|
/** Full public visualizer API: regions, region overlay (tool modes), undo, etc. */
|
|
@@ -110,6 +135,8 @@ export interface PlotModeContext {
|
|
|
110
135
|
viewport: PlotModeViewport;
|
|
111
136
|
/** Current image info from IMAGE_STATE_PORT (null until an image is loaded). */
|
|
112
137
|
imageInfo$: Observable<IImageInfo | null>;
|
|
138
|
+
/** Toolbar tools (since 0.6.0). Optional so a context built for 0.5.0 still type-checks. */
|
|
139
|
+
tools?: PlotModeTools;
|
|
113
140
|
}
|
|
114
141
|
/** A live activation of a contributed mode. */
|
|
115
142
|
export interface PlotModeSession {
|
|
@@ -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, 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,49 @@ 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
|
+
* What is inside `box` (observation units): transcript count, distinct genes and cells,
|
|
102
|
+
* the most frequent of each — optionally restricted to `genes` — plus display ids for
|
|
103
|
+
* `cells`. Omit `box` for a cell-id lookup only. Optional; drives the hover details of
|
|
104
|
+
* transcript markers.
|
|
105
|
+
*/
|
|
106
|
+
getTranscriptSummary?(query: {
|
|
107
|
+
box?: [number, number, number, number];
|
|
108
|
+
genes?: string[];
|
|
109
|
+
cells?: number[];
|
|
110
|
+
}): Promise<SpatialTranscriptSummary>;
|
|
111
|
+
/**
|
|
112
|
+
* The summed transcript density of `genes` (`[ALL_GENES]` for every gene) on the
|
|
113
|
+
* dataset's `density` raster, re-binned to `binSize` observation units when given.
|
|
114
|
+
* Optional: only datasets that advertise `density` need it.
|
|
115
|
+
*/
|
|
116
|
+
getDensity?(genes: string[], binSize?: number): Promise<SpatialDensityRaster>;
|
|
117
|
+
/**
|
|
118
|
+
* Import a cell grouping — a CSV/TSV of `cell_id` and `group` — under `label`. The new
|
|
119
|
+
* categorical column is added to the dataset (which re-emits) and resolved. Optional.
|
|
120
|
+
*/
|
|
121
|
+
importGroups?(label: string, table: string): Promise<{
|
|
122
|
+
column: CategoricalColumnMeta;
|
|
123
|
+
matched: number;
|
|
124
|
+
}>;
|
|
125
|
+
/** Transcript totals per gene, for estimating how many markers a selection draws. */
|
|
126
|
+
getTranscriptCounts?(genes: string[]): Promise<SpatialTranscriptCounts>;
|
|
69
127
|
/**
|
|
70
128
|
* The reference volume's voxels: a uint8 scalar field, x-fastest, of exactly
|
|
71
129
|
* `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,149 @@ 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
|
+
export interface SpatialTranscriptCounts {
|
|
375
|
+
/** Total transcripts per requested gene, over the whole dataset. */
|
|
376
|
+
counts: Record<string, number>;
|
|
377
|
+
/** Total over every gene. */
|
|
378
|
+
total: number;
|
|
379
|
+
/** Extent the totals are spread over (observation units). */
|
|
380
|
+
bounds: SpatialBounds;
|
|
381
|
+
}
|
|
382
|
+
/** A summed density raster for a list of genes; `values` is row-major `rows × cols`. */
|
|
383
|
+
export interface SpatialDensityRaster {
|
|
384
|
+
meta: SpatialDensityMeta;
|
|
385
|
+
genes: string[];
|
|
386
|
+
values: Float32Array;
|
|
387
|
+
}
|
|
231
388
|
export interface SpatialDataset {
|
|
232
389
|
/** Stable id — the key the port's lazy accessors are scoped to. */
|
|
233
390
|
id: string;
|
|
@@ -243,6 +400,16 @@ export interface SpatialDataset {
|
|
|
243
400
|
volume?: SpatialVolumeMeta;
|
|
244
401
|
/** Embeddings available for these observations, whose coordinates are fetched on demand. */
|
|
245
402
|
embeddings?: SpatialEmbeddingMeta[];
|
|
403
|
+
/** Level-of-detail boundaries, fetched per tile — see {@link SpatialPolygonTilesMeta}. */
|
|
404
|
+
polygonTiles?: SpatialPolygonTilesMeta;
|
|
405
|
+
/** Level-of-detail transcripts, fetched per tile — see {@link SpatialTranscriptTilesMeta}. */
|
|
406
|
+
transcriptTiles?: SpatialTranscriptTilesMeta;
|
|
407
|
+
/** A served per-gene transcript density raster. */
|
|
408
|
+
density?: SpatialDensityMeta;
|
|
409
|
+
/** The all-gene transcript grouping pyramid, when it has been built. */
|
|
410
|
+
transcriptBins?: SpatialTranscriptBinsMeta;
|
|
411
|
+
/** Present while the server is still building {@link transcriptBins}. */
|
|
412
|
+
transcriptBinsStatus?: SpatialTranscriptBinsStatus;
|
|
246
413
|
/**
|
|
247
414
|
* Microns per observation coordinate unit — what makes a scale bar possible.
|
|
248
415
|
*
|
|
@@ -35,6 +35,17 @@
|
|
|
35
35
|
* than an escape hatch that returns a component — the moment one tool draws
|
|
36
36
|
* itself, the toolbar stops being able to lay tools out consistently.
|
|
37
37
|
*
|
|
38
|
+
* DIALOG TOOLS
|
|
39
|
+
* Some tools are not "set parameters, run once" but an interactive session: the
|
|
40
|
+
* user paints, the tool retrains and redraws, over and over (DIANNE). A schema
|
|
41
|
+
* cannot describe that, so {@link ToolbarDialogToolContribution} is the one
|
|
42
|
+
* variant that renders its own body. The toolbar still owns the button, its
|
|
43
|
+
* placement and the dialog chrome; the tool only fills the dialog, with plain
|
|
44
|
+
* DOM (`mount`), so it needs neither Angular nor this library's PrimeNG version.
|
|
45
|
+
* Its button sits with the host's own actions at the start of the toolbar, and
|
|
46
|
+
* shows only in the Image view, the view its context ({@link ToolDialogContext})
|
|
47
|
+
* draws over.
|
|
48
|
+
*
|
|
38
49
|
* WHY AN EMPTY MODEL LIST HIDES THE TOOL
|
|
39
50
|
* {@link ToolbarToolContribution.models} returning `[]` removes the tool from
|
|
40
51
|
* the toolbar entirely. This preserves the behaviour the built-in tools already
|
|
@@ -45,6 +56,7 @@
|
|
|
45
56
|
import { InjectionToken } from '@angular/core';
|
|
46
57
|
import type { Observable } from 'rxjs';
|
|
47
58
|
import type { IVisualizer } from './visualizer.contract';
|
|
59
|
+
import type { PlotModeContext, PlotModeSession, PlotModeTools } from './plot-type-contribution.contract';
|
|
48
60
|
/** A number field. Rendered as a stepper, matching the built-in dialogs. */
|
|
49
61
|
export interface NumberParamSpec {
|
|
50
62
|
id: string;
|
|
@@ -177,14 +189,81 @@ export interface ToolbarToolContribution {
|
|
|
177
189
|
run(viz: IVisualizer, params: Record<string, unknown>): Promise<number>;
|
|
178
190
|
}
|
|
179
191
|
/**
|
|
180
|
-
*
|
|
192
|
+
* What a dialog tool gets while its dialog is open: the same surface a
|
|
193
|
+
* contributed plot mode gets (the public visualizer, the Image view's viewport
|
|
194
|
+
* and the current image), with the toolbar tools always present.
|
|
195
|
+
*/
|
|
196
|
+
export type ToolDialogContext = PlotModeContext & {
|
|
197
|
+
tools: PlotModeTools;
|
|
198
|
+
};
|
|
199
|
+
/** A live dialog-tool session. `deactivate()` runs exactly once, after the body's teardown. */
|
|
200
|
+
export type ToolDialogSession = PlotModeSession;
|
|
201
|
+
/**
|
|
202
|
+
* A toolbar tool that opens a dialog instead of running once (see DIALOG TOOLS
|
|
203
|
+
* above). Clicking its button opens the dialog and starts a session; clicking it
|
|
204
|
+
* again, or closing the dialog, ends it.
|
|
205
|
+
*
|
|
206
|
+
* The session is bound to the Image view on screen. When that view is re-rendered
|
|
207
|
+
* (another image, another slice) the session ends and, with the dialog still
|
|
208
|
+
* open, a fresh one starts once the new view is ready. Leaving the Image view
|
|
209
|
+
* closes the dialog. As with plot modes, nothing the tool throws or rejects
|
|
210
|
+
* escapes: a failed start closes the dialog with a warning.
|
|
211
|
+
*/
|
|
212
|
+
export interface ToolbarDialogToolContribution {
|
|
213
|
+
kind: 'dialog';
|
|
214
|
+
/** Stable identity. Must not clash with another toolbar tool. */
|
|
215
|
+
id: string;
|
|
216
|
+
/** Short name; the dialog title unless `dialog.title` is set. */
|
|
217
|
+
label: string;
|
|
218
|
+
icon: ToolIcon;
|
|
219
|
+
/** Tooltip on the button. */
|
|
220
|
+
tooltip: string;
|
|
221
|
+
/** Order among the dialog tools. Lower sorts first; ties keep registration order. */
|
|
222
|
+
order?: number;
|
|
223
|
+
dialog?: {
|
|
224
|
+
title?: string;
|
|
225
|
+
/** CSS width of the dialog. Default `'22rem'`. */
|
|
226
|
+
width?: string;
|
|
227
|
+
};
|
|
228
|
+
/** Entry for the help dialog's tool list, rendered as HTML (trusted content). */
|
|
229
|
+
help?: {
|
|
230
|
+
body: string;
|
|
231
|
+
};
|
|
232
|
+
/** Start a session, once the Image view's viewport is ready. */
|
|
233
|
+
activate(ctx: ToolDialogContext): ToolDialogSession | Promise<ToolDialogSession>;
|
|
234
|
+
/**
|
|
235
|
+
* Render the dialog body into `host`; return its teardown (called before
|
|
236
|
+
* `deactivate()`). Called after `activate` resolves, once the dialog has rendered and
|
|
237
|
+
* `host` is in the document, so the body can measure itself.
|
|
238
|
+
*/
|
|
239
|
+
mount(host: HTMLElement, ctx: ToolDialogContext, session: ToolDialogSession): () => void;
|
|
240
|
+
}
|
|
241
|
+
/** Anything provided on {@link TOOLBAR_TOOLS}. A run tool has no `kind`. */
|
|
242
|
+
export type ToolbarContribution = ToolbarToolContribution | ToolbarDialogToolContribution;
|
|
243
|
+
/** Whether a contribution is a {@link ToolbarDialogToolContribution}. */
|
|
244
|
+
export declare function isDialogToolContribution(c: unknown): c is ToolbarDialogToolContribution;
|
|
245
|
+
/**
|
|
246
|
+
* Multi-provider token for contributed tools: run tools
|
|
247
|
+
* ({@link ToolbarToolContribution}) and, since 0.6.0, dialog tools
|
|
248
|
+
* ({@link ToolbarDialogToolContribution}).
|
|
181
249
|
*
|
|
182
250
|
* Deliberately has no `factory`: unregistered means no tools, which is what
|
|
183
251
|
* makes this library shippable without the closed model packages. Inject it
|
|
184
252
|
* `{ optional: true }` and treat null as empty.
|
|
185
253
|
*/
|
|
186
|
-
export declare const TOOLBAR_TOOLS: InjectionToken<readonly
|
|
254
|
+
export declare const TOOLBAR_TOOLS: InjectionToken<readonly ToolbarContribution[]>;
|
|
187
255
|
/** Sort contributed tools into display order. */
|
|
188
|
-
export declare function sortToolContributions
|
|
189
|
-
|
|
190
|
-
|
|
256
|
+
export declare function sortToolContributions<T extends {
|
|
257
|
+
order?: number;
|
|
258
|
+
}>(tools: readonly T[]): T[];
|
|
259
|
+
/** The run tools a toolbar should actually show: those with at least one model.
|
|
260
|
+
* Dialog tools are left out (see {@link dialogToolContributions}). */
|
|
261
|
+
export declare function visibleToolContributions(tools: readonly ToolbarContribution[] | null | undefined): ToolbarToolContribution[];
|
|
262
|
+
/**
|
|
263
|
+
* The dialog tools, in display order. A malformed one (no string `id`, no
|
|
264
|
+
* `activate` or `mount` function) or a repeated `id` is dropped with a warning
|
|
265
|
+
* rather than breaking the toolbar.
|
|
266
|
+
*/
|
|
267
|
+
export declare function dialogToolContributions(tools: readonly ToolbarContribution[] | null | undefined, log?: {
|
|
268
|
+
warn(...args: unknown[]): void;
|
|
269
|
+
}): ToolbarDialogToolContribution[];
|
|
@@ -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, 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,27 @@ 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
|
+
/** Estimated transcripts in view for the current selection, against the marker budget. */
|
|
348
|
+
getTranscriptEstimate$?(): Observable<{
|
|
349
|
+
points: number;
|
|
350
|
+
max: number;
|
|
351
|
+
} | null>;
|
|
352
|
+
/** The density map's colour window in use (auto-derived or set) and its densest bin. */
|
|
353
|
+
getDensityStats$?(): Observable<{
|
|
354
|
+
lo: number;
|
|
355
|
+
hi: number;
|
|
356
|
+
max: number;
|
|
357
|
+
} | null>;
|
|
337
358
|
/**
|
|
338
359
|
* The z positions of the sections the dataset was imaged at, ascending — or
|
|
339
360
|
* null when its z is continuous rather than sectioned (so there are no sections
|
|
@@ -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;
|