@jax-data-science/sci-image-visualizer 0.3.3 → 0.5.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 +268 -4
- package/esm2022/index.mjs +21 -1
- package/esm2022/lib/contracts/display-types.mjs +20 -2
- package/esm2022/lib/contracts/image.contract.mjs +1 -1
- package/esm2022/lib/contracts/plot-type-contribution.contract.mjs +66 -0
- package/esm2022/lib/contracts/plot-type.mjs +41 -1
- package/esm2022/lib/contracts/ports/spatial-data.port.mjs +3 -0
- package/esm2022/lib/contracts/spatial-dataset.contract.mjs +45 -0
- package/esm2022/lib/contracts/visualizer.contract.mjs +1 -1
- package/esm2022/lib/implementations/napari-js/napari-axes-labels.mjs +10 -19
- package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +71 -7
- package/esm2022/lib/implementations/napari-js/napari-scale-bar.mjs +1 -1
- package/esm2022/lib/implementations/napari-js/napari-spatial-tooltip.mjs +96 -0
- package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +1934 -55
- package/esm2022/lib/implementations/napari-js/napari-zoom.mjs +18 -0
- package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +73 -8
- package/esm2022/lib/implementations/osd/osd-coordinate-transform.mjs +8 -1
- package/esm2022/lib/implementations/osd/osd-region-overlay.mjs +3 -4
- package/esm2022/lib/implementations/osd/osd-zoom.mjs +20 -0
- package/esm2022/lib/implementations/plotly/omics-trace-builders.mjs +555 -0
- package/esm2022/lib/implementations/plotly/plotly.service.mjs +13 -7
- package/esm2022/lib/implementations/spatial-data-http/spatial-data-http.service.mjs +304 -0
- package/esm2022/lib/implementations/spatial-data-http/spatial-wire.mjs +179 -0
- package/esm2022/lib/plot-mode/plot-mode-controller.mjs +329 -0
- package/esm2022/lib/routing-visualizer.service.mjs +228 -12
- package/esm2022/lib/spatial/embedding-compute.mjs +142 -0
- package/esm2022/lib/spatial/gene-search.mjs +77 -0
- package/esm2022/lib/spatial/spatial-density.mjs +224 -0
- package/esm2022/lib/spatial/spatial-encoding.mjs +232 -0
- package/esm2022/lib/spatial/spatial-expression.mjs +257 -0
- package/esm2022/lib/spatial/spatial-framing.mjs +60 -0
- package/esm2022/lib/spatial/spatial-heatmap.mjs +185 -0
- package/esm2022/lib/spatial/spatial-hover.mjs +79 -0
- package/esm2022/lib/spatial/spatial-sections.mjs +96 -0
- package/esm2022/lib/spatial/spatial-selection.mjs +264 -0
- package/esm2022/lib/spatial/spatial-volume-image.mjs +163 -0
- package/esm2022/lib/spatial/tsne-worker.mjs +10 -0
- package/esm2022/lib/spatial/world-grid.mjs +77 -0
- package/esm2022/lib/spatial-controls/spatial-charts/spatial-charts.component.mjs +1278 -0
- package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +621 -0
- package/esm2022/lib/store/spatial-selection.service.mjs +47 -0
- package/esm2022/lib/store/visualizer-store.service.mjs +23 -1
- package/esm2022/lib/toolbar/toolbar.component.mjs +61 -21
- package/esm2022/lib/visualization.module.mjs +23 -5
- package/esm2022/lib/visualizer.component.mjs +527 -44
- package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-BwP-A11m.mjs} +6 -4
- package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-BwP-A11m.mjs.map} +1 -1
- package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs +12 -0
- package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs.map +1 -0
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +15971 -7808
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/fesm2022/tsne.worker.js +19091 -0
- package/index.d.ts +16 -0
- package/lib/contracts/display-types.d.ts +131 -0
- package/lib/contracts/image.contract.d.ts +7 -0
- package/lib/contracts/plot-type-contribution.contract.d.ts +172 -0
- package/lib/contracts/plot-type.d.ts +52 -2
- package/lib/contracts/ports/spatial-data.port.d.ts +90 -0
- package/lib/contracts/spatial-dataset.contract.d.ts +281 -0
- package/lib/contracts/visualizer.contract.d.ts +104 -1
- package/lib/implementations/napari-js/napari-axes-labels.d.ts +9 -7
- package/lib/implementations/napari-js/napari-region-overlay.d.ts +38 -4
- package/lib/implementations/napari-js/napari-scale-bar.d.ts +7 -3
- package/lib/implementations/napari-js/napari-spatial-tooltip.d.ts +32 -0
- package/lib/implementations/napari-js/napari-visualizer.service.d.ts +497 -16
- package/lib/implementations/napari-js/napari-zoom.d.ts +15 -0
- package/lib/implementations/osd/openseadragon-visualizer.service.d.ts +21 -0
- package/lib/implementations/osd/osd-coordinate-transform.d.ts +6 -0
- package/lib/implementations/osd/osd-zoom.d.ts +19 -0
- package/lib/implementations/plotly/omics-trace-builders.d.ts +193 -0
- package/lib/implementations/plotly/plotly.service.d.ts +9 -0
- package/lib/implementations/spatial-data-http/spatial-data-http.service.d.ts +123 -0
- package/lib/implementations/spatial-data-http/spatial-wire.d.ts +115 -0
- package/lib/plot-mode/plot-mode-controller.d.ts +130 -0
- package/lib/routing-visualizer.service.d.ts +49 -6
- package/lib/spatial/embedding-compute.d.ts +73 -0
- package/lib/spatial/gene-search.d.ts +48 -0
- package/lib/spatial/spatial-density.d.ts +73 -0
- package/lib/spatial/spatial-encoding.d.ts +155 -0
- package/lib/spatial/spatial-expression.d.ts +144 -0
- package/lib/spatial/spatial-framing.d.ts +37 -0
- package/lib/spatial/spatial-heatmap.d.ts +111 -0
- package/lib/spatial/spatial-hover.d.ts +49 -0
- package/lib/spatial/spatial-sections.d.ts +59 -0
- package/lib/spatial/spatial-selection.d.ts +113 -0
- package/lib/spatial/spatial-volume-image.d.ts +67 -0
- package/lib/spatial/tsne-worker.d.ts +7 -0
- package/lib/spatial/world-grid.d.ts +58 -0
- package/lib/spatial-controls/spatial-charts/spatial-charts.component.d.ts +439 -0
- package/lib/spatial-controls/spatial-controls.component.d.ts +257 -0
- package/lib/store/spatial-selection.service.d.ts +27 -0
- package/lib/store/visualizer-store.service.d.ts +13 -0
- package/lib/toolbar/toolbar.component.d.ts +36 -8
- package/lib/visualization.module.d.ts +28 -24
- package/lib/visualizer.component.d.ts +139 -7
- package/package.json +6 -2
- package/src/lib/assets/spatial-omics-3d.svg +31 -0
- package/src/lib/assets/spatial-omics.svg +27 -0
package/index.d.ts
CHANGED
|
@@ -11,18 +11,29 @@ export * from './lib/contracts/ports/image-state.port';
|
|
|
11
11
|
export * from './lib/contracts/ports/tile-access.port';
|
|
12
12
|
export * from './lib/contracts/ports/region-io.port';
|
|
13
13
|
export * from './lib/contracts/ports/preferences.port';
|
|
14
|
+
export * from './lib/contracts/ports/spatial-data.port';
|
|
15
|
+
export * from './lib/contracts/spatial-dataset.contract';
|
|
14
16
|
export * from './lib/contracts/channel-histogram-api.contract';
|
|
15
17
|
export * from './lib/contracts/viz-config';
|
|
16
18
|
export * from './lib/contracts/image.contract';
|
|
17
19
|
export * from './lib/contracts/plot-type';
|
|
18
20
|
export * from './lib/contracts/toolbar-config';
|
|
19
21
|
export * from './lib/contracts/toolbar-tool.contract';
|
|
22
|
+
export * from './lib/contracts/plot-type-contribution.contract';
|
|
20
23
|
export * from './lib/contracts/capabilities.contract';
|
|
21
24
|
export * from './lib/contracts/region-overlay.contract';
|
|
22
25
|
export * from './lib/contracts/display-types';
|
|
23
26
|
export * from './lib/contracts/sam.contract';
|
|
24
27
|
export * from './lib/contracts/cell-segmenter.contract';
|
|
25
28
|
export { CellposeSegmenterService } from './lib/toolbar/segmentation/cellpose-segmenter.service';
|
|
29
|
+
export { SpatialDataHttpService, SupersededError } from './lib/implementations/spatial-data-http/spatial-data-http.service';
|
|
30
|
+
export { SPATIAL_WIRE_VERSION, isLittleEndian, assertManifestVersion, datasetFromManifest, decodeCoords, decodeRadius, decodeColumn, decodeFeatureVector, decodePolygons, } from './lib/implementations/spatial-data-http/spatial-wire';
|
|
31
|
+
export type { SpatialManifest, SpatialDatasetSummary, SpatialRadiusSpec, } from './lib/implementations/spatial-data-http/spatial-wire';
|
|
32
|
+
export { encodeCategorical, encodeContinuous, resolveCategoryColors, contrastWindow, markerDiameters, toRgbaTuples, lutFor, DEFAULT_CATEGORICAL_PALETTE, DEFAULT_MUTED_OPACITY, MISSING_COLOR, } from './lib/spatial/spatial-encoding';
|
|
33
|
+
export type { RGBA, CategoricalEncodingOptions, ContinuousEncodingOptions, } from './lib/spatial/spatial-encoding';
|
|
34
|
+
export { selectInRegions, selectByCategory, mutedFromSelection, pointInRing, emptySelection, countMask, maskToIndices, } from './lib/spatial/spatial-selection';
|
|
35
|
+
export type { SpatialSelectionMask } from './lib/spatial/spatial-selection';
|
|
36
|
+
export { SpatialSelectionStore } from './lib/store/spatial-selection.service';
|
|
26
37
|
export * from './lib/contracts/instance-segmenter.contract';
|
|
27
38
|
export * from './lib/contracts/semantic-segmenter.contract';
|
|
28
39
|
export { ProcessingImage } from './lib/processing/processing-image';
|
|
@@ -40,3 +51,8 @@ export { VisualizerComponent } from './lib/visualizer.component';
|
|
|
40
51
|
export { RegionEditorComponent } from './lib/region-editor/region-editor.component';
|
|
41
52
|
export { HexColorPickerComponent } from './lib/hex-color-picker/hex-color-picker.component';
|
|
42
53
|
export { ChannelHistogramComponent } from './lib/channel-histogram/channel-histogram.component';
|
|
54
|
+
export { SpatialControlsComponent, } from './lib/spatial-controls/spatial-controls.component';
|
|
55
|
+
export type { SpatialLegendEntry } from './lib/spatial-controls/spatial-controls.component';
|
|
56
|
+
export { SpatialChartsComponent } from './lib/spatial-controls/spatial-charts/spatial-charts.component';
|
|
57
|
+
export { buildOmicsTraces, omicsLayout, benefitsFromGrouping, } from './lib/implementations/plotly/omics-trace-builders';
|
|
58
|
+
export type { OmicsChartKind, OmicsGrouping, OmicsTraceInput, } from './lib/implementations/plotly/omics-trace-builders';
|
|
@@ -39,3 +39,134 @@ export interface IBrushOptions {
|
|
|
39
39
|
/** Brush diameter in matrix (image) pixels. */
|
|
40
40
|
size?: number;
|
|
41
41
|
}
|
|
42
|
+
/** What drives point colour in the spatial-omics mode: an annotation column
|
|
43
|
+
* from the dataset, or one feature (gene) vector fetched on demand. */
|
|
44
|
+
export interface SpatialColorBy {
|
|
45
|
+
kind: 'column' | 'feature';
|
|
46
|
+
name: string;
|
|
47
|
+
}
|
|
48
|
+
/** Display state for the spatial-omics plot mode. Lives in `VisualizerStore`
|
|
49
|
+
* alongside the colormap/channel state so the renderer reacts to edits the
|
|
50
|
+
* same way it does for an image. */
|
|
51
|
+
export interface SpatialViewState {
|
|
52
|
+
/** Colour source; `null` renders every observation in one neutral colour
|
|
53
|
+
* (useful on its own — it shows where the tissue actually is). */
|
|
54
|
+
colorBy: SpatialColorBy | null;
|
|
55
|
+
/** Multiplier on the dataset's physical marker size. 1 = true size, which for
|
|
56
|
+
* a 55 µm Visium spot is correct but can be invisible when zoomed out. */
|
|
57
|
+
pointScale: number;
|
|
58
|
+
/** Alpha for points that are not muted. */
|
|
59
|
+
opacity: number;
|
|
60
|
+
/** Log-scale continuous values before mapping them onto the colormap. Seeded
|
|
61
|
+
* from the column's `logScaleHint` — count data needs it. */
|
|
62
|
+
logScale: boolean;
|
|
63
|
+
/** Percentile clip for the contrast window as `[lo, hi]` fractions, so a
|
|
64
|
+
* handful of saturated observations don't flatten the rest. */
|
|
65
|
+
percentileClip: [number, number];
|
|
66
|
+
/**
|
|
67
|
+
* Colormap for CONTINUOUS spatial colouring — a gene, a numeric column — as a
|
|
68
|
+
* {@link ColormapValue}, the same shape the display colormap carries: a scale
|
|
69
|
+
* NAME for the built-ins, or an inline `[stop, colour]` array for the rest.
|
|
70
|
+
* Null follows the display colormap.
|
|
71
|
+
*
|
|
72
|
+
* Its own setting because the display colormap belongs to the tissue image and
|
|
73
|
+
* is usually a grey ramp, which is the wrong thing to read a measurement
|
|
74
|
+
* through: grey over grey cannot be told apart from the anatomy. Following it
|
|
75
|
+
* (with a Viridis fallback for exactly that case) is a reasonable default and a
|
|
76
|
+
* poor ceiling, since which gradient reads best is a judgement about the data.
|
|
77
|
+
*
|
|
78
|
+
* One setting for the markers, the 2D gene map and the 3D gene map together, so
|
|
79
|
+
* the field under the cells and the cells over it cannot disagree about what a
|
|
80
|
+
* colour means — and the panel's own colour bar is built from it too.
|
|
81
|
+
*/
|
|
82
|
+
continuousColormap: ColormapValue | null;
|
|
83
|
+
/**
|
|
84
|
+
* What the 3D scene draws. The three things in it — the reference volume, the
|
|
85
|
+
* observation cloud and the cluster density volumes — occupy the same space, so
|
|
86
|
+
* any two of them hide each other to some degree. These are independent because
|
|
87
|
+
* the useful views are the combinations: the volumes alone to read an estimated
|
|
88
|
+
* distribution, one section's cells over them to check that estimate against the
|
|
89
|
+
* measurement, the anatomy alone to find a landmark.
|
|
90
|
+
*
|
|
91
|
+
* Visibility only — the layers stay built, so toggling one back on is immediate
|
|
92
|
+
* and does not re-run a rasterisation. {@link densityVolume} is the exception and
|
|
93
|
+
* genuinely gates construction, because building six volumes is not free.
|
|
94
|
+
*/
|
|
95
|
+
showVolume: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* The reference volume's opacity.
|
|
98
|
+
*
|
|
99
|
+
* Its own, not {@link opacity} (which belongs to the markers): the volume is a
|
|
100
|
+
* BACKDROP, and reading the cloud or a density field through it means turning
|
|
101
|
+
* the anatomy down without touching the data drawn over it. Ranges lower than
|
|
102
|
+
* the markers' slider for the same reason — a faint anatomical hint is a useful
|
|
103
|
+
* setting for a backdrop and a useless one for the measurement.
|
|
104
|
+
*/
|
|
105
|
+
volumeOpacity: number;
|
|
106
|
+
/** Draw the observation cloud (3D). */
|
|
107
|
+
showPoints: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Restrict the cloud to ONE imaged section, by index into the dataset's sampled
|
|
110
|
+
* sections; null draws every section.
|
|
111
|
+
*
|
|
112
|
+
* The whole cloud is a stack of discs that hides whatever is drawn between them.
|
|
113
|
+
* One section at a time is the view that answers "does the estimated field
|
|
114
|
+
* actually follow the cells I measured" — and it is only offered for data that
|
|
115
|
+
* really is sectioned (see `sampledSections`).
|
|
116
|
+
*/
|
|
117
|
+
pointSection: number | null;
|
|
118
|
+
/**
|
|
119
|
+
* Draw each cluster as a translucent DENSITY volume alongside the 3D cloud.
|
|
120
|
+
*
|
|
121
|
+
* Serial sections hundreds of microns apart make a cloud hard to read as an
|
|
122
|
+
* anatomical distribution — the eye cannot integrate discs into a shape. A
|
|
123
|
+
* density field can be estimated between the imaged planes (a continuous
|
|
124
|
+
* estimate, unlike individual cells, which have no correspondence to
|
|
125
|
+
* interpolate along) and renders as a cloud that reads as an estimate.
|
|
126
|
+
*/
|
|
127
|
+
densityVolume: boolean;
|
|
128
|
+
/** Multiplier on the default kernel bandwidth. Above 1 is smoother and less
|
|
129
|
+
* committal; below 1 exposes the individual sections. */
|
|
130
|
+
densitySmoothing: number;
|
|
131
|
+
/**
|
|
132
|
+
* Draw the active GENE as a continuous field under the observations (2D).
|
|
133
|
+
*
|
|
134
|
+
* Colouring the markers by a gene says which cells express it; it cannot say
|
|
135
|
+
* where, because the eye will not integrate thousands of dots into a territory.
|
|
136
|
+
* The field is an estimate — a kernel-weighted mean per cell, transparent where
|
|
137
|
+
* no cell was measured — and it is drawn beneath the cells, not instead of them,
|
|
138
|
+
* so the measurement stays on screen next to the interpolation.
|
|
139
|
+
*
|
|
140
|
+
* Ignored unless the colour source is a gene: there is nothing else to map.
|
|
141
|
+
*/
|
|
142
|
+
geneMap: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Restrict the 3D gene map to ONE imaged section, by index into the dataset's
|
|
145
|
+
* sampled sections; null draws every section's sheet.
|
|
146
|
+
*
|
|
147
|
+
* Separate from {@link pointSection} so the combinations stay open: all the
|
|
148
|
+
* sheets with one section's cells over them is a useful view, and so is one
|
|
149
|
+
* sheet inside the whole cloud. Ignored while {@link geneMapVolume} is set —
|
|
150
|
+
* interpolating a single section along z would smear one slide through the
|
|
151
|
+
* specimen's whole depth and call it an estimate.
|
|
152
|
+
*/
|
|
153
|
+
geneMapSection: number | null;
|
|
154
|
+
/**
|
|
155
|
+
* Smooth the per-section gene maps along z into a continuous VOLUME (3D).
|
|
156
|
+
*
|
|
157
|
+
* The sheets are what was measured — one field per imaged slide, with the gaps
|
|
158
|
+
* between them empty. This turns them into an estimate defined between the
|
|
159
|
+
* slides, the same move the cluster density volumes make and with the same
|
|
160
|
+
* caveat: a field may be interpolated between sections, individual cells may
|
|
161
|
+
* not, and the result is drawn as a translucent cloud so it cannot be mistaken
|
|
162
|
+
* for measurement.
|
|
163
|
+
*/
|
|
164
|
+
geneMapVolume: boolean;
|
|
165
|
+
/** Multiplier on the gene map's kernel bandwidth. */
|
|
166
|
+
geneMapSmoothing: number;
|
|
167
|
+
/** The gene map's own opacity. Separate from {@link opacity}, which belongs to
|
|
168
|
+
* the markers: turning the cells down to read the field underneath must not
|
|
169
|
+
* turn the field down with them. */
|
|
170
|
+
geneMapOpacity: number;
|
|
171
|
+
}
|
|
172
|
+
export declare const DEFAULT_SPATIAL_VIEW: SpatialViewState;
|
|
@@ -31,6 +31,13 @@ export interface IImageMetadata {
|
|
|
31
31
|
/** Physical pixel size in µm; null/undefined when the format reports none. */
|
|
32
32
|
mppX?: number | null;
|
|
33
33
|
mppY?: number | null;
|
|
34
|
+
/** Physical spacing BETWEEN slices in µm — the z counterpart of `mppX`/`mppY`,
|
|
35
|
+
* for a stack that knows it (a resampled volume, a confocal stack). It is what
|
|
36
|
+
* lets the 3D volume view render true anisotropy: with all three present the
|
|
37
|
+
* world box is the image's real physical extent, so 40 x 40 x 200 µm voxels
|
|
38
|
+
* read as a brain and not as a cube-aspect brick. Absent, the view falls back
|
|
39
|
+
* to its resolution-invariant reference box, which is shape-only. */
|
|
40
|
+
mppZ?: number | null;
|
|
34
41
|
/** Per-channel name/color/LUT for multichannel (non-RGB) images, when known. */
|
|
35
42
|
channelInfo?: IChannelInfo[] | null;
|
|
36
43
|
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a plot mode gets into the plot-type selector without this library knowing
|
|
3
|
+
* what it is.
|
|
4
|
+
*
|
|
5
|
+
* A package contributes a mode by providing a {@link PlotTypeContribution} on the
|
|
6
|
+
* {@link PLOT_TYPE_CONTRIBUTIONS} multi-provider token, the same way
|
|
7
|
+
* `TOOLBAR_TOOLS` contributes toolbar tools. Nothing registers at import time:
|
|
8
|
+
* installing a package makes a mode available, and adding its provider turns it
|
|
9
|
+
* on. A plain object works, so the contributing package needs no Angular
|
|
10
|
+
* compiler and no decorators:
|
|
11
|
+
*
|
|
12
|
+
* { provide: PLOT_TYPE_CONTRIBUTIONS, useValue: myContribution, multi: true }
|
|
13
|
+
* { provide: PLOT_TYPE_CONTRIBUTIONS, useFactory: () => makeContribution(), multi: true }
|
|
14
|
+
*
|
|
15
|
+
* Provide nothing and the selector, the render path and the panel area behave
|
|
16
|
+
* exactly as they did before this extension point existed.
|
|
17
|
+
*
|
|
18
|
+
* WHY A CONTRIBUTED MODE RIDES ON A BUILT-IN ONE
|
|
19
|
+
* A contributed mode does not bring a renderer. It names a built-in
|
|
20
|
+
* {@link ContributedPlotTypeDescriptor.baseType} — in v1 only `PlotType.IMAGE`,
|
|
21
|
+
* the OpenSeadragon view — and the visualizer plots exactly as if that type had
|
|
22
|
+
* been selected: same backend, same toolbar, same region tools, same wheel
|
|
23
|
+
* handling. The contribution then draws over it through the
|
|
24
|
+
* {@link PlotModeViewport} and talks to the viewer through the public
|
|
25
|
+
* {@link IVisualizer}. That keeps every rendering decision in one place and means
|
|
26
|
+
* a contribution can never be half-supported by some code path that switches on
|
|
27
|
+
* the plot type.
|
|
28
|
+
*
|
|
29
|
+
* WHAT THE VISUALIZER GUARANTEES
|
|
30
|
+
* 1. Contributed descriptors are appended after the built-in ones and curated by
|
|
31
|
+
* the same `productionLabel` / test-mode / `requires*` rules.
|
|
32
|
+
* 2. Selecting one plots with `baseType`'s backend, then calls `activate(ctx)`
|
|
33
|
+
* once the viewport is ready.
|
|
34
|
+
* 3. Leaving the mode — another type, an image switch (or any re-render of the
|
|
35
|
+
* base view), or the visualizer being destroyed — calls `session.deactivate()`
|
|
36
|
+
* exactly once, before the next mode draws. If the mode is still selected
|
|
37
|
+
* when the new image has plotted, a fresh session is activated for it.
|
|
38
|
+
* 4. `panel` is shown only while a session is live.
|
|
39
|
+
* 5. A thrown error or rejected promise from `activate`, `deactivate`, `mount`
|
|
40
|
+
* or a mount's teardown is caught and logged; none escapes, so a contribution
|
|
41
|
+
* cannot break the viewer. Failing to start (`activate`, `mount`, or a panel
|
|
42
|
+
* component throwing while it is created / first rendered) falls back
|
|
43
|
+
* to `baseType`. Failing to clean up falls back when that same mode is being
|
|
44
|
+
* re-activated (base re-render, image switch) — immediately if it already is
|
|
45
|
+
* live again — and is only logged when the user has moved to another mode; the
|
|
46
|
+
* user explicitly selecting it again gets a fresh attempt.
|
|
47
|
+
* 6. {@link PLOT_TYPE_CONTRIBUTIONS} has no factory: no providers, no modes.
|
|
48
|
+
*/
|
|
49
|
+
import { InjectionToken, Type } from '@angular/core';
|
|
50
|
+
import type { Observable } from 'rxjs';
|
|
51
|
+
import type { IImageInfo } from './image.contract';
|
|
52
|
+
import type { PlotType, PlotTypeDescriptor, PlotTypeId } from './plot-type';
|
|
53
|
+
import type { IVisualizer } from './visualizer.contract';
|
|
54
|
+
/** A rectangle in level-0 image pixels. */
|
|
55
|
+
export interface PlotModeRect {
|
|
56
|
+
x: number;
|
|
57
|
+
y: number;
|
|
58
|
+
width: number;
|
|
59
|
+
height: number;
|
|
60
|
+
}
|
|
61
|
+
/** Selector metadata for a contributed plot mode. */
|
|
62
|
+
export interface ContributedPlotTypeDescriptor {
|
|
63
|
+
/** Unique id, namespaced, e.g. 'dianne'. Must not clash with PlotType values. */
|
|
64
|
+
type: string;
|
|
65
|
+
/** Test-mode label, e.g. 'Digital Pathology - DIANNE'. */
|
|
66
|
+
label: string;
|
|
67
|
+
/** Default-selector label; omit to make the mode test-only (same rule as built-ins). */
|
|
68
|
+
productionLabel?: string;
|
|
69
|
+
/** 'pi pi-…' class or asset path, same as PlotTypeDescriptor.icon. */
|
|
70
|
+
icon?: string;
|
|
71
|
+
/** Always '2d' in v1. */
|
|
72
|
+
dimensions: '2d';
|
|
73
|
+
/** Built-in type whose backend renders the base image. v1 supports PlotType.IMAGE (OSD). */
|
|
74
|
+
baseType: PlotType;
|
|
75
|
+
/** Same gates as PlotTypeDescriptor, evaluated in addition to the base type's own. */
|
|
76
|
+
requiresGrayscale?: boolean;
|
|
77
|
+
requiresStack?: boolean;
|
|
78
|
+
/** Hidden until a `SpatialDataset` is published on SPATIAL_DATA_PORT (an overlay of spatial observations). */
|
|
79
|
+
requiresSpatialData?: boolean;
|
|
80
|
+
/** Hidden unless the published dataset carries `observations.z`. */
|
|
81
|
+
requiresSpatial3d?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/** The base view's viewport, as a contributed mode sees it. */
|
|
84
|
+
export interface PlotModeViewport {
|
|
85
|
+
/** Element the mode's overlay canvas/SVG attaches to; fills the plot area. */
|
|
86
|
+
getOverlayContainer(): HTMLElement | null;
|
|
87
|
+
/** Level-0 image px -> client px, and the reverse. */
|
|
88
|
+
dataToClient(x: number, y: number): {
|
|
89
|
+
x: number;
|
|
90
|
+
y: number;
|
|
91
|
+
};
|
|
92
|
+
clientToData(clientX: number, clientY: number): {
|
|
93
|
+
x: number;
|
|
94
|
+
y: number;
|
|
95
|
+
};
|
|
96
|
+
/** Screen px per image px at the current zoom. */
|
|
97
|
+
dataLengthToScreen(len: number): number;
|
|
98
|
+
isReady(): boolean;
|
|
99
|
+
/** Visible image rect in level-0 px, emitted on every redraw frame (pan/zoom animation).
|
|
100
|
+
* A new subscriber first receives the current rect when the viewport is ready. */
|
|
101
|
+
frame$: Observable<PlotModeRect>;
|
|
102
|
+
/** Same rect, emitted once the viewport settles (existing getViewportChange$ semantics).
|
|
103
|
+
* A new subscriber first receives the current rect when the viewport is ready. */
|
|
104
|
+
settled$: Observable<PlotModeRect>;
|
|
105
|
+
}
|
|
106
|
+
/** Everything a contributed mode gets to work with while it is active. */
|
|
107
|
+
export interface PlotModeContext {
|
|
108
|
+
/** Full public visualizer API: regions, region overlay (tool modes), undo, etc. */
|
|
109
|
+
visualizer: IVisualizer;
|
|
110
|
+
viewport: PlotModeViewport;
|
|
111
|
+
/** Current image info from IMAGE_STATE_PORT (null until an image is loaded). */
|
|
112
|
+
imageInfo$: Observable<IImageInfo | null>;
|
|
113
|
+
}
|
|
114
|
+
/** A live activation of a contributed mode. */
|
|
115
|
+
export interface PlotModeSession {
|
|
116
|
+
/** Called when the user leaves the mode, the image changes, or the visualizer is destroyed. */
|
|
117
|
+
deactivate(): void;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The mode's side panel, shown in the visualizer's right-hand panel area while
|
|
121
|
+
* a session is live. Two forms:
|
|
122
|
+
*
|
|
123
|
+
* - `component`: an Angular component, rendered through `NgComponentOutlet`
|
|
124
|
+
* with {@link PLOT_MODE_CONTEXT} and {@link PLOT_MODE_SESSION} in its
|
|
125
|
+
* injector. Needs an AOT-compiled (partial-Ivy) component.
|
|
126
|
+
* - `mount`: framework-agnostic. The visualizer hands over an empty host
|
|
127
|
+
* element after `activate` resolves; the function renders into it and returns
|
|
128
|
+
* a teardown. The teardown is called exactly once when the mode deactivates,
|
|
129
|
+
* before `session.deactivate()`, and the host element is then removed.
|
|
130
|
+
*/
|
|
131
|
+
export type PlotModePanel = {
|
|
132
|
+
title: string;
|
|
133
|
+
component: Type<unknown>;
|
|
134
|
+
} | {
|
|
135
|
+
title: string;
|
|
136
|
+
mount(host: HTMLElement, ctx: PlotModeContext, session: PlotModeSession): () => void;
|
|
137
|
+
};
|
|
138
|
+
/** A plot mode contributed to the selector. A plain object is enough. */
|
|
139
|
+
export interface PlotTypeContribution {
|
|
140
|
+
descriptor: ContributedPlotTypeDescriptor;
|
|
141
|
+
/** Called after the base backend has plotted the image in this mode. */
|
|
142
|
+
activate(ctx: PlotModeContext): PlotModeSession | Promise<PlotModeSession>;
|
|
143
|
+
/**
|
|
144
|
+
* Optional side panel, rendered in the visualizer's right-hand panel area while the
|
|
145
|
+
* mode is active. A component panel can inject PLOT_MODE_CONTEXT and PLOT_MODE_SESSION.
|
|
146
|
+
*/
|
|
147
|
+
panel?: PlotModePanel;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Multi-provider token for contributed plot modes.
|
|
151
|
+
*
|
|
152
|
+
* Deliberately has no `factory`: unregistered means no contributed modes, and the
|
|
153
|
+
* library behaves exactly as before. The visualizer injects it `{ optional: true }`
|
|
154
|
+
* and treats null as empty.
|
|
155
|
+
*/
|
|
156
|
+
export declare const PLOT_TYPE_CONTRIBUTIONS: InjectionToken<readonly PlotTypeContribution[]>;
|
|
157
|
+
/** The active mode's context, in a component panel's injector. */
|
|
158
|
+
export declare const PLOT_MODE_CONTEXT: InjectionToken<PlotModeContext>;
|
|
159
|
+
/** The active mode's session, in a component panel's injector. */
|
|
160
|
+
export declare const PLOT_MODE_SESSION: InjectionToken<PlotModeSession>;
|
|
161
|
+
/**
|
|
162
|
+
* One entry in the plot-type selector: a built-in {@link PlotTypeDescriptor}
|
|
163
|
+
* as-is, or a contributed descriptor completed with its base type's `source`.
|
|
164
|
+
* Every entry therefore carries the same fields the selector curates by.
|
|
165
|
+
*/
|
|
166
|
+
export interface PlotTypeOption extends Omit<PlotTypeDescriptor, 'type'> {
|
|
167
|
+
type: PlotTypeId;
|
|
168
|
+
/** Set only for a contributed mode: the built-in type that renders it. */
|
|
169
|
+
baseType?: PlotType;
|
|
170
|
+
}
|
|
171
|
+
/** The selector entry for a contributed mode, given its base type's descriptor. */
|
|
172
|
+
export declare function contributedPlotTypeOption(d: ContributedPlotTypeDescriptor, base: PlotTypeDescriptor): PlotTypeOption;
|
|
@@ -31,16 +31,38 @@ export declare enum PlotType {
|
|
|
31
31
|
/** WebGPU 2D scatter of region centroids (napari-js analog of Plotly SCATTER). */
|
|
32
32
|
NAPARI_SCATTER = "napari-scatter",
|
|
33
33
|
/** WebGPU 3D scatter of the downsampled voxel cloud (napari-js analog of Plotly SCATTER3D). */
|
|
34
|
-
NAPARI_SCATTER3D = "napari-scatter3d"
|
|
34
|
+
NAPARI_SCATTER3D = "napari-scatter3d",
|
|
35
|
+
/** Spatial-omics observations (Visium spots, segmented cells) drawn over the tissue image and
|
|
36
|
+
* coloured by an annotation column or a gene. Needs a `SpatialDataset` on `SPATIAL_DATA_PORT`,
|
|
37
|
+
* so it is gated by `requiresSpatialData` and never appears for an image-only host. */
|
|
38
|
+
SPATIAL_OMICS = "spatial-omics",
|
|
39
|
+
/** Spatial-omics observations as a 3D point cloud, for assays whose observations carry a z —
|
|
40
|
+
* serial sections registered into a common frame (e.g. the Allen CCF), or a genuinely
|
|
41
|
+
* volumetric assay. Gated by `requiresSpatial3d`, a strictly narrower gate than
|
|
42
|
+
* {@link SPATIAL_OMICS}: a single-plane dataset has no z to render. */
|
|
43
|
+
SPATIAL_OMICS_3D = "spatial-omics-3d"
|
|
35
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* A value the plot-type selector can hold: a built-in {@link PlotType}, or the
|
|
47
|
+
* `type` of a contributed mode (`ContributedPlotTypeDescriptor.type`, see
|
|
48
|
+
* `plot-type-contribution.contract.ts`). Everything that decides HOW to render
|
|
49
|
+
* keeps taking a {@link PlotType}; a contributed id is resolved to its
|
|
50
|
+
* `baseType` first, so no rendering switch ever sees one.
|
|
51
|
+
*/
|
|
52
|
+
export type PlotTypeId = PlotType | string;
|
|
53
|
+
/** Whether an id names a built-in plot type (as opposed to a contributed one). */
|
|
54
|
+
export declare function isBuiltinPlotType(id: PlotTypeId | null | undefined): id is PlotType;
|
|
36
55
|
/** How many spatial dimensions a plot type renders in. */
|
|
37
56
|
export type PlotDimensions = '2d' | '3d';
|
|
38
57
|
/**
|
|
39
58
|
* Where a plot type pulls its data from:
|
|
40
59
|
* - `image` — the loaded pixel matrices / z-stack (HEATMAP, SURFACE, …)
|
|
41
60
|
* - `regions` — the drawn/derived regions (SCATTER of region centroids, …)
|
|
61
|
+
* - `spatial` — a `SpatialDataset` supplied through `SPATIAL_DATA_PORT`: N
|
|
62
|
+
* observations (spots/cells) with categorical + continuous columns and a
|
|
63
|
+
* lazily-fetched feature matrix, drawn over the tissue image they came from.
|
|
42
64
|
*/
|
|
43
|
-
export type PlotDataSource = 'image' | 'regions';
|
|
65
|
+
export type PlotDataSource = 'image' | 'regions' | 'spatial';
|
|
44
66
|
/**
|
|
45
67
|
* Backend-neutral metadata about a plot type. Drives UI affordances (which
|
|
46
68
|
* types are 3D, which need an image stack, how to label them in a dropdown)
|
|
@@ -75,6 +97,17 @@ export interface PlotTypeDescriptor {
|
|
|
75
97
|
* surface, intensity profile, isosurface) — only meaningful for grayscale.
|
|
76
98
|
* Image/Heatmap render RGB fine; Scatter uses regions. */
|
|
77
99
|
requiresGrayscale?: boolean;
|
|
100
|
+
/** True when the type renders a `SpatialDataset` (`source: 'spatial'`) and is
|
|
101
|
+
* therefore meaningless without one. The selector hides it until a dataset is
|
|
102
|
+
* published on `SPATIAL_DATA_PORT` — the same shape of gate as
|
|
103
|
+
* {@link requiresStack} for a z-stack, so a host with no spatial data never
|
|
104
|
+
* sees the mode. */
|
|
105
|
+
requiresSpatialData?: boolean;
|
|
106
|
+
/** True when the type needs observations with a z coordinate, not merely a
|
|
107
|
+
* `SpatialDataset`. Narrower than {@link requiresSpatialData}: most spatial
|
|
108
|
+
* assays are a single plane, so this hides the 3D mode unless the published
|
|
109
|
+
* dataset actually carries `observations.z`. */
|
|
110
|
+
requiresSpatial3d?: boolean;
|
|
78
111
|
}
|
|
79
112
|
/**
|
|
80
113
|
* Single source of truth for plot-type metadata. LINE is intentionally absent —
|
|
@@ -95,6 +128,23 @@ export declare function isNapariSurface(type: PlotType): boolean;
|
|
|
95
128
|
export declare function isNapariScatter(type: PlotType): boolean;
|
|
96
129
|
/** napari-js 3D scatter (voxel point cloud). */
|
|
97
130
|
export declare function isNapariScatter3d(type: PlotType): boolean;
|
|
131
|
+
/** Spatial-omics observations over the tissue image. */
|
|
132
|
+
export declare function isSpatialOmics(type: PlotType): boolean;
|
|
133
|
+
/** Spatial-omics observations as a 3D point cloud. */
|
|
134
|
+
export declare function isSpatialOmics3d(type: PlotType): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Whether the renderer, rather than the shell, should handle wheel zoom.
|
|
137
|
+
*
|
|
138
|
+
* The shell's wheel listener applies a FIXED step per event, which is far too sensitive for a
|
|
139
|
+
* renderer that reads the scroll delta: it works out at 30% per event against napari-js's 3%, and
|
|
140
|
+
* ignores the device normalization and easing the renderer does. So every plot type drawn by a
|
|
141
|
+
* renderer that owns the wheel has to opt out here.
|
|
142
|
+
*
|
|
143
|
+
* The image view always did. The spatial-omics modes did NOT, despite being drawn by the same
|
|
144
|
+
* napari-js viewer — the shell gated on an `isHeatmap` flag that defaults to true and only goes
|
|
145
|
+
* false for the 3D Plotly scenes, so it silently took the wheel for them too.
|
|
146
|
+
*/
|
|
147
|
+
export declare function rendererOwnsWheel(type: PlotType): boolean;
|
|
98
148
|
/** Any napari-js 3D plot type (volume, isosurface, surface, or 3D scatter). Resolution is a runtime
|
|
99
149
|
* decimate factor (Full / ½ / ¼ / ⅛) — see the service's `resolutionScale`. */
|
|
100
150
|
export declare function isNapari3d(type: PlotType): boolean;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
import { SpatialColumn, SpatialDataset, SpatialEmbedding, SpatialPolygons } from '../spatial-dataset.contract';
|
|
4
|
+
/**
|
|
5
|
+
* Spatial-omics data access, inverted as a port so the visualization library
|
|
6
|
+
* never learns how the data is stored or served — exactly like
|
|
7
|
+
* `TILE_ACCESS_PORT` for tiles and `REGION_IO_PORT` for annotations. The host
|
|
8
|
+
* supplies an adapter; the library consumes typed arrays.
|
|
9
|
+
*
|
|
10
|
+
* WHY A PORT AND NOT A READER
|
|
11
|
+
* ---------------------------
|
|
12
|
+
* The interchange format for this data (SpatialData Zarr v3 + AnnData
|
|
13
|
+
* conventions + GeoParquet shapes) needs three parsers, and its expression
|
|
14
|
+
* matrix is stored observation-major — so reading a single gene's column means
|
|
15
|
+
* touching every row. A server can pre-index that once; a browser cannot. So
|
|
16
|
+
* the library takes vectors it can render and leaves ingest to whoever owns the
|
|
17
|
+
* data. `SpatialDataHttpService` is a ready-made adapter for the wire format
|
|
18
|
+
* the bundled example server speaks; a host with its own backend implements
|
|
19
|
+
* this interface instead.
|
|
20
|
+
*
|
|
21
|
+
* LAZINESS IS THE POINT
|
|
22
|
+
* ---------------------
|
|
23
|
+
* `getDataset$()` yields only what is cheap to hold: coordinates plus column
|
|
24
|
+
* and feature *metadata*. Every accessor below fetches one vector at a time,
|
|
25
|
+
* for the one column or gene currently being displayed.
|
|
26
|
+
*/
|
|
27
|
+
export interface SpatialDataPort {
|
|
28
|
+
/**
|
|
29
|
+
* The dataset currently being visualized, or null when none is selected.
|
|
30
|
+
* Emits again when the host switches datasets, so the view can rebuild.
|
|
31
|
+
*/
|
|
32
|
+
getDataset$(): Observable<SpatialDataset | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Values for one annotation column, index-aligned with the observations.
|
|
35
|
+
* The column must be one advertised in `SpatialDataset.columns`; reject
|
|
36
|
+
* (don't resolve empty) when it isn't, so a typo surfaces instead of
|
|
37
|
+
* rendering as "no data".
|
|
38
|
+
*
|
|
39
|
+
* Implementations should cache — colouring by cluster, then by a gene, then
|
|
40
|
+
* back by cluster must not refetch.
|
|
41
|
+
*/
|
|
42
|
+
getColumn(name: string): Promise<SpatialColumn>;
|
|
43
|
+
/**
|
|
44
|
+
* One feature's (gene's) expression vector, index-aligned with the
|
|
45
|
+
* observations. Rejects for an unknown feature.
|
|
46
|
+
*/
|
|
47
|
+
getFeatureVector(name: string): Promise<Float32Array>;
|
|
48
|
+
/**
|
|
49
|
+
* One embedding's coordinates, index-aligned with the observations.
|
|
50
|
+
*
|
|
51
|
+
* The name must be one advertised in `SpatialDataset.embeddings`; rejects otherwise, for the
|
|
52
|
+
* same reason `getColumn` does — a typo should surface rather than render as an empty plot.
|
|
53
|
+
*
|
|
54
|
+
* Optional: a host with no embeddings simply omits it, and the UI offers no embedding views.
|
|
55
|
+
*/
|
|
56
|
+
getEmbedding?(name: string): Promise<SpatialEmbedding>;
|
|
57
|
+
/**
|
|
58
|
+
* Typeahead over feature names, for datasets too wide to inline the list
|
|
59
|
+
* (`SpatialFeatureMeta.names` absent). Optional: a host whose panel is small
|
|
60
|
+
* enough to inline every name need not implement it — the UI falls back to
|
|
61
|
+
* filtering `names` locally.
|
|
62
|
+
*/
|
|
63
|
+
searchFeatures?(query: string, limit?: number): Promise<string[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Per-observation boundary geometry, when the dataset advertises
|
|
66
|
+
* `polygons`. Optional — a spot-based assay (Visium) has no segmentation.
|
|
67
|
+
*/
|
|
68
|
+
getPolygons?(): Promise<SpatialPolygons>;
|
|
69
|
+
/**
|
|
70
|
+
* The reference volume's voxels: a uint8 scalar field, x-fastest, of exactly
|
|
71
|
+
* `width * height * depth` bytes as the dataset's {@link SpatialVolumeMeta}
|
|
72
|
+
* declares.
|
|
73
|
+
*
|
|
74
|
+
* Optional, and only meaningful when the dataset advertises a volume. Separate
|
|
75
|
+
* from `getDataset$()` because it is megabytes: a host that never opens the 3D
|
|
76
|
+
* mode should never pay for it.
|
|
77
|
+
*/
|
|
78
|
+
getVolume?(): Promise<Uint8Array>;
|
|
79
|
+
/**
|
|
80
|
+
* Server-side "which observations fall inside this polygon", for datasets
|
|
81
|
+
* too large to hit-test client-side. Optional: without it the library
|
|
82
|
+
* point-in-polygons the resident coordinates itself, which is fine into the
|
|
83
|
+
* 10^5 range. Coordinates are in the same space as the observations.
|
|
84
|
+
*/
|
|
85
|
+
queryRoi?(polygon: {
|
|
86
|
+
x: number[];
|
|
87
|
+
y: number[];
|
|
88
|
+
}): Promise<Uint32Array>;
|
|
89
|
+
}
|
|
90
|
+
export declare const SPATIAL_DATA_PORT: InjectionToken<SpatialDataPort>;
|