@jax-data-science/sci-image-visualizer 0.3.3 → 0.4.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 +208 -4
- package/esm2022/index.mjs +20 -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.mjs +36 -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 +5 -3
- 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/routing-visualizer.service.mjs +223 -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 +30 -7
- package/esm2022/lib/visualization.module.mjs +23 -5
- package/esm2022/lib/visualizer.component.mjs +283 -22
- 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 +15769 -8316
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/fesm2022/tsne.worker.js +19091 -0
- package/index.d.ts +15 -0
- package/lib/contracts/display-types.d.ts +131 -0
- package/lib/contracts/image.contract.d.ts +7 -0
- package/lib/contracts/plot-type.d.ts +42 -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 +99 -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/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/routing-visualizer.service.d.ts +45 -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 +19 -3
- package/lib/visualization.module.d.ts +28 -24
- package/lib/visualizer.component.d.ts +73 -2
- 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,6 +11,8 @@ 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';
|
|
@@ -23,6 +25,14 @@ export * from './lib/contracts/display-types';
|
|
|
23
25
|
export * from './lib/contracts/sam.contract';
|
|
24
26
|
export * from './lib/contracts/cell-segmenter.contract';
|
|
25
27
|
export { CellposeSegmenterService } from './lib/toolbar/segmentation/cellpose-segmenter.service';
|
|
28
|
+
export { SpatialDataHttpService, SupersededError } from './lib/implementations/spatial-data-http/spatial-data-http.service';
|
|
29
|
+
export { SPATIAL_WIRE_VERSION, isLittleEndian, assertManifestVersion, datasetFromManifest, decodeCoords, decodeRadius, decodeColumn, decodeFeatureVector, decodePolygons, } from './lib/implementations/spatial-data-http/spatial-wire';
|
|
30
|
+
export type { SpatialManifest, SpatialDatasetSummary, SpatialRadiusSpec, } from './lib/implementations/spatial-data-http/spatial-wire';
|
|
31
|
+
export { encodeCategorical, encodeContinuous, resolveCategoryColors, contrastWindow, markerDiameters, toRgbaTuples, lutFor, DEFAULT_CATEGORICAL_PALETTE, DEFAULT_MUTED_OPACITY, MISSING_COLOR, } from './lib/spatial/spatial-encoding';
|
|
32
|
+
export type { RGBA, CategoricalEncodingOptions, ContinuousEncodingOptions, } from './lib/spatial/spatial-encoding';
|
|
33
|
+
export { selectInRegions, selectByCategory, mutedFromSelection, pointInRing, emptySelection, countMask, maskToIndices, } from './lib/spatial/spatial-selection';
|
|
34
|
+
export type { SpatialSelectionMask } from './lib/spatial/spatial-selection';
|
|
35
|
+
export { SpatialSelectionStore } from './lib/store/spatial-selection.service';
|
|
26
36
|
export * from './lib/contracts/instance-segmenter.contract';
|
|
27
37
|
export * from './lib/contracts/semantic-segmenter.contract';
|
|
28
38
|
export { ProcessingImage } from './lib/processing/processing-image';
|
|
@@ -40,3 +50,8 @@ export { VisualizerComponent } from './lib/visualizer.component';
|
|
|
40
50
|
export { RegionEditorComponent } from './lib/region-editor/region-editor.component';
|
|
41
51
|
export { HexColorPickerComponent } from './lib/hex-color-picker/hex-color-picker.component';
|
|
42
52
|
export { ChannelHistogramComponent } from './lib/channel-histogram/channel-histogram.component';
|
|
53
|
+
export { SpatialControlsComponent, } from './lib/spatial-controls/spatial-controls.component';
|
|
54
|
+
export type { SpatialLegendEntry } from './lib/spatial-controls/spatial-controls.component';
|
|
55
|
+
export { SpatialChartsComponent } from './lib/spatial-controls/spatial-charts/spatial-charts.component';
|
|
56
|
+
export { buildOmicsTraces, omicsLayout, benefitsFromGrouping, } from './lib/implementations/plotly/omics-trace-builders';
|
|
57
|
+
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
|
}
|
|
@@ -31,7 +31,16 @@ 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
|
}
|
|
36
45
|
/** How many spatial dimensions a plot type renders in. */
|
|
37
46
|
export type PlotDimensions = '2d' | '3d';
|
|
@@ -39,8 +48,11 @@ export type PlotDimensions = '2d' | '3d';
|
|
|
39
48
|
* Where a plot type pulls its data from:
|
|
40
49
|
* - `image` — the loaded pixel matrices / z-stack (HEATMAP, SURFACE, …)
|
|
41
50
|
* - `regions` — the drawn/derived regions (SCATTER of region centroids, …)
|
|
51
|
+
* - `spatial` — a `SpatialDataset` supplied through `SPATIAL_DATA_PORT`: N
|
|
52
|
+
* observations (spots/cells) with categorical + continuous columns and a
|
|
53
|
+
* lazily-fetched feature matrix, drawn over the tissue image they came from.
|
|
42
54
|
*/
|
|
43
|
-
export type PlotDataSource = 'image' | 'regions';
|
|
55
|
+
export type PlotDataSource = 'image' | 'regions' | 'spatial';
|
|
44
56
|
/**
|
|
45
57
|
* Backend-neutral metadata about a plot type. Drives UI affordances (which
|
|
46
58
|
* types are 3D, which need an image stack, how to label them in a dropdown)
|
|
@@ -75,6 +87,17 @@ export interface PlotTypeDescriptor {
|
|
|
75
87
|
* surface, intensity profile, isosurface) — only meaningful for grayscale.
|
|
76
88
|
* Image/Heatmap render RGB fine; Scatter uses regions. */
|
|
77
89
|
requiresGrayscale?: boolean;
|
|
90
|
+
/** True when the type renders a `SpatialDataset` (`source: 'spatial'`) and is
|
|
91
|
+
* therefore meaningless without one. The selector hides it until a dataset is
|
|
92
|
+
* published on `SPATIAL_DATA_PORT` — the same shape of gate as
|
|
93
|
+
* {@link requiresStack} for a z-stack, so a host with no spatial data never
|
|
94
|
+
* sees the mode. */
|
|
95
|
+
requiresSpatialData?: boolean;
|
|
96
|
+
/** True when the type needs observations with a z coordinate, not merely a
|
|
97
|
+
* `SpatialDataset`. Narrower than {@link requiresSpatialData}: most spatial
|
|
98
|
+
* assays are a single plane, so this hides the 3D mode unless the published
|
|
99
|
+
* dataset actually carries `observations.z`. */
|
|
100
|
+
requiresSpatial3d?: boolean;
|
|
78
101
|
}
|
|
79
102
|
/**
|
|
80
103
|
* Single source of truth for plot-type metadata. LINE is intentionally absent —
|
|
@@ -95,6 +118,23 @@ export declare function isNapariSurface(type: PlotType): boolean;
|
|
|
95
118
|
export declare function isNapariScatter(type: PlotType): boolean;
|
|
96
119
|
/** napari-js 3D scatter (voxel point cloud). */
|
|
97
120
|
export declare function isNapariScatter3d(type: PlotType): boolean;
|
|
121
|
+
/** Spatial-omics observations over the tissue image. */
|
|
122
|
+
export declare function isSpatialOmics(type: PlotType): boolean;
|
|
123
|
+
/** Spatial-omics observations as a 3D point cloud. */
|
|
124
|
+
export declare function isSpatialOmics3d(type: PlotType): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Whether the renderer, rather than the shell, should handle wheel zoom.
|
|
127
|
+
*
|
|
128
|
+
* The shell's wheel listener applies a FIXED step per event, which is far too sensitive for a
|
|
129
|
+
* renderer that reads the scroll delta: it works out at 30% per event against napari-js's 3%, and
|
|
130
|
+
* ignores the device normalization and easing the renderer does. So every plot type drawn by a
|
|
131
|
+
* renderer that owns the wheel has to opt out here.
|
|
132
|
+
*
|
|
133
|
+
* The image view always did. The spatial-omics modes did NOT, despite being drawn by the same
|
|
134
|
+
* napari-js viewer — the shell gated on an `isHeatmap` flag that defaults to true and only goes
|
|
135
|
+
* false for the 3D Plotly scenes, so it silently took the wheel for them too.
|
|
136
|
+
*/
|
|
137
|
+
export declare function rendererOwnsWheel(type: PlotType): boolean;
|
|
98
138
|
/** Any napari-js 3D plot type (volume, isosurface, surface, or 3D scatter). Resolution is a runtime
|
|
99
139
|
* decimate factor (Full / ½ / ¼ / ⅛) — see the service's `resolutionScale`. */
|
|
100
140
|
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>;
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend-neutral data model for a **spatial-omics dataset**: N observations
|
|
3
|
+
* (Visium spots, segmented cells, nuclei, …) positioned in the pixel space of a
|
|
4
|
+
* tissue image, each carrying categorical and continuous annotations, plus an
|
|
5
|
+
* optional feature (gene) matrix and optional per-observation boundaries.
|
|
6
|
+
*
|
|
7
|
+
* DESIGN
|
|
8
|
+
* ------
|
|
9
|
+
* 1. **Struct-of-arrays, not object-per-cell.** Real datasets run 10^3 (Visium)
|
|
10
|
+
* to 10^6 (Xenium/CosMx) observations. `{x, y}[]` at 500k cells is ~500k
|
|
11
|
+
* objects the GC has to walk; two `Float32Array`s are two allocations and
|
|
12
|
+
* upload to the GPU without a copy.
|
|
13
|
+
*
|
|
14
|
+
* 2. **Metadata is eager, values are lazy.** A dataset advertises *which*
|
|
15
|
+
* columns and features exist ({@link SpatialColumnMeta},
|
|
16
|
+
* {@link SpatialFeatureMeta}); the vectors themselves are fetched on demand
|
|
17
|
+
* through {@link SpatialDataPort}. A Visium table is ~30k genes wide — the
|
|
18
|
+
* dense matrix is ~800 MB — so "load the dataset" can never mean "load the
|
|
19
|
+
* matrix". Only the handful of columns actually being displayed is resident.
|
|
20
|
+
*
|
|
21
|
+
* 3. **Data stays data.** No I/O, no RxJS, no Angular in this file — the same
|
|
22
|
+
* rule `plotly-trace-builders.ts` follows. Fetching lives on the port.
|
|
23
|
+
*
|
|
24
|
+
* The shape mirrors how the field already stores this (SpatialData/AnnData:
|
|
25
|
+
* `obs` annotations, `var` features, `obsm/spatial` coordinates, shapes with a
|
|
26
|
+
* coordinate transform) so an adapter is a projection, not a translation.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Observation positions. Every array is length {@link count} and index-aligned:
|
|
30
|
+
* observation `i` is at `(x[i], y[i])`, and column/feature vectors index the
|
|
31
|
+
* same way. This shared index is the join key for the whole model.
|
|
32
|
+
*/
|
|
33
|
+
export interface SpatialObservations {
|
|
34
|
+
/** N — number of observations. */
|
|
35
|
+
readonly count: number;
|
|
36
|
+
/** X in the reference image's pixel space (see {@link SpatialImageRef}). */
|
|
37
|
+
x: Float32Array;
|
|
38
|
+
/** Y in the reference image's pixel space. */
|
|
39
|
+
y: Float32Array;
|
|
40
|
+
/** Z, for serial sections or genuinely 3D assays. Absent for a single plane. */
|
|
41
|
+
z?: Float32Array;
|
|
42
|
+
/** Stable per-observation ids (barcodes, cell ids) for tooltips and export.
|
|
43
|
+
* Optional — omit for large datasets where the strings cost more than they
|
|
44
|
+
* are worth. */
|
|
45
|
+
ids?: string[];
|
|
46
|
+
/** Marker RADIUS in image pixels: one value shared by all observations (a
|
|
47
|
+
* Visium spot is a fixed 55 µm ⇒ `27.5 / mppX` px), or one per observation
|
|
48
|
+
* (segmented cells). Absent ⇒ the renderer picks a display default. */
|
|
49
|
+
radius?: Float32Array | number;
|
|
50
|
+
}
|
|
51
|
+
/** Fields common to every column descriptor. */
|
|
52
|
+
interface SpatialColumnMetaBase {
|
|
53
|
+
/** Column name as the user sees it (`leiden`, `total_counts`). */
|
|
54
|
+
name: string;
|
|
55
|
+
/** Longer human-readable description for tooltips/menus. */
|
|
56
|
+
description?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A discrete annotation — cluster, cell type, region, sample. Rendered as a
|
|
60
|
+
* palette + legend; a legend click selects the category's observations.
|
|
61
|
+
*/
|
|
62
|
+
export interface CategoricalColumnMeta extends SpatialColumnMetaBase {
|
|
63
|
+
kind: 'categorical';
|
|
64
|
+
/** Category labels; a value's label is `categories[codes[i]]`. */
|
|
65
|
+
categories: string[];
|
|
66
|
+
/** Authored display colours as `#rrggbb`, index-aligned with
|
|
67
|
+
* {@link categories}. Supply these to keep the viewer's palette identical to
|
|
68
|
+
* the figures the same analysis produced in R/Python; omit to let the viewer
|
|
69
|
+
* derive one. */
|
|
70
|
+
colors?: string[];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A continuous measurement — expression, QC metric, area, density. Rendered
|
|
74
|
+
* through the shared colormap/LUT + contrast window.
|
|
75
|
+
*/
|
|
76
|
+
export interface ContinuousColumnMeta extends SpatialColumnMetaBase {
|
|
77
|
+
kind: 'continuous';
|
|
78
|
+
/** Unit for axis/tooltip labels (`counts`, `µm²`). */
|
|
79
|
+
unit?: string;
|
|
80
|
+
/** True when the column reads best log-scaled — count data almost always
|
|
81
|
+
* does, and a linear scale collapses it against a few bright outliers. */
|
|
82
|
+
logScaleHint?: boolean;
|
|
83
|
+
/** Observed extremes, when the producer knows them. Lets the UI seed a
|
|
84
|
+
* contrast window and axis range without first scanning the vector. */
|
|
85
|
+
min?: number;
|
|
86
|
+
max?: number;
|
|
87
|
+
}
|
|
88
|
+
export type SpatialColumnMeta = CategoricalColumnMeta | ContinuousColumnMeta;
|
|
89
|
+
/**
|
|
90
|
+
* Code meaning "this observation has no category" (unassigned / filtered out /
|
|
91
|
+
* not in tissue). Renderers should draw it in the muted background style rather
|
|
92
|
+
* than as a real category, and it must never index {@link
|
|
93
|
+
* CategoricalColumnMeta.categories}.
|
|
94
|
+
*/
|
|
95
|
+
export declare const NO_CATEGORY = 65535;
|
|
96
|
+
/** A loaded categorical column: per-observation indices into `meta.categories`,
|
|
97
|
+
* or {@link NO_CATEGORY}. */
|
|
98
|
+
export interface CategoricalColumn {
|
|
99
|
+
meta: CategoricalColumnMeta;
|
|
100
|
+
/** Length = {@link SpatialObservations.count}. */
|
|
101
|
+
codes: Uint16Array;
|
|
102
|
+
}
|
|
103
|
+
/** A loaded continuous column. `NaN` marks a missing value. */
|
|
104
|
+
export interface ContinuousColumn {
|
|
105
|
+
meta: ContinuousColumnMeta;
|
|
106
|
+
/** Length = {@link SpatialObservations.count}. */
|
|
107
|
+
values: Float32Array;
|
|
108
|
+
}
|
|
109
|
+
export type SpatialColumn = CategoricalColumn | ContinuousColumn;
|
|
110
|
+
export declare function isCategoricalColumn(c: SpatialColumn): c is CategoricalColumn;
|
|
111
|
+
export declare function isContinuousColumn(c: SpatialColumn): c is ContinuousColumn;
|
|
112
|
+
/**
|
|
113
|
+
* The feature (gene) matrix, described but never delivered whole. Vectors come
|
|
114
|
+
* one at a time from {@link SpatialDataPort.getFeatureVector}.
|
|
115
|
+
*/
|
|
116
|
+
export interface SpatialFeatureMeta {
|
|
117
|
+
/** Number of features available. */
|
|
118
|
+
count: number;
|
|
119
|
+
/**
|
|
120
|
+
* All feature names, when the producer chose to inline them. Present for
|
|
121
|
+
* targeted panels (Xenium/CosMx ship 300–5,000 genes ⇒ a few dozen KB) and
|
|
122
|
+
* absent for whole-transcriptome data (Visium ships ~31k ⇒ ~350 KB), where
|
|
123
|
+
* the UI should use {@link SpatialDataPort.searchFeatures} for typeahead
|
|
124
|
+
* instead of downloading the list. Consumers must handle both.
|
|
125
|
+
*/
|
|
126
|
+
names?: string[];
|
|
127
|
+
/** What a feature value means (`log1p normalized`, `raw counts`). */
|
|
128
|
+
unit?: string;
|
|
129
|
+
/** True when feature vectors read best log-scaled (raw counts). */
|
|
130
|
+
logScaleHint?: boolean;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Per-observation boundaries (cell/nucleus segmentation, spot outlines) as flat
|
|
134
|
+
* rings — deliberately NOT GeoJSON objects, which cost one object + one array
|
|
135
|
+
* per cell and would dominate memory at 10^5 polygons.
|
|
136
|
+
*
|
|
137
|
+
* Ring `i` occupies `coords[2*offsets[i] .. 2*offsets[i+1])` as `x0,y0,x1,y1,…`
|
|
138
|
+
* and is implicitly closed. `offsets` has length `count + 1`.
|
|
139
|
+
*/
|
|
140
|
+
export interface SpatialPolygons {
|
|
141
|
+
coords: Float32Array;
|
|
142
|
+
offsets: Uint32Array;
|
|
143
|
+
/** Number of rings — `offsets.length - 1`. */
|
|
144
|
+
readonly count: number;
|
|
145
|
+
}
|
|
146
|
+
/** Presence/size of the boundary geometry, without loading it. */
|
|
147
|
+
export interface SpatialPolygonsMeta {
|
|
148
|
+
count: number;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* How observation coordinates relate to the tissue image they are drawn over.
|
|
152
|
+
* Applied as `world = coord * scale + translate`, matching the affine that
|
|
153
|
+
* SpatialData records per coordinate system — and the full-resolution world
|
|
154
|
+
* convention the napari-js backend already uses so pre-saved regions align on
|
|
155
|
+
* pyramidal slides.
|
|
156
|
+
*/
|
|
157
|
+
export interface SpatialImageRef {
|
|
158
|
+
/** Host-defined id of the image these coordinates live in, so the host can
|
|
159
|
+
* resolve it to an `IImageInfo`. */
|
|
160
|
+
imageId?: string;
|
|
161
|
+
/** Data→world scale. Defaults to `[1, 1]` (coordinates already in
|
|
162
|
+
* full-resolution image pixels). */
|
|
163
|
+
scale?: [number, number];
|
|
164
|
+
/** Data→world translation, in the same units as {@link scale}'s output. */
|
|
165
|
+
translate?: [number, number];
|
|
166
|
+
/** Physical pixel size in µm — drives the scale bar and physical marker
|
|
167
|
+
* sizing (a 55 µm Visium spot). */
|
|
168
|
+
mppX?: number;
|
|
169
|
+
mppY?: number;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* A spatial-omics dataset: everything cheap enough to hold resident. Column and
|
|
173
|
+
* feature *values*, and polygon *geometry*, are fetched through
|
|
174
|
+
* {@link SpatialDataPort} as they are displayed.
|
|
175
|
+
*/
|
|
176
|
+
/**
|
|
177
|
+
* A precomputed low-dimensional embedding over the SAME observations — UMAP, t-SNE, PCA.
|
|
178
|
+
*
|
|
179
|
+
* Announced in the dataset and fetched on demand, exactly like a column or a gene: the
|
|
180
|
+
* coordinates are another per-observation vector, and a dataset may carry several.
|
|
181
|
+
*/
|
|
182
|
+
export interface SpatialEmbeddingMeta {
|
|
183
|
+
/** Key as the source names it, e.g. `X_umap`. */
|
|
184
|
+
name: string;
|
|
185
|
+
/** Short label for menus; falls back to {@link name}. */
|
|
186
|
+
label?: string;
|
|
187
|
+
/** 2 for a plane, 3 for a cloud. */
|
|
188
|
+
dims: 2 | 3;
|
|
189
|
+
/**
|
|
190
|
+
* True when the coordinates were COMPUTED for this bundle rather than published with the
|
|
191
|
+
* dataset. Worth surfacing: a UMAP recomputed with different parameters is a different picture,
|
|
192
|
+
* and a reader comparing against a paper's figure needs to know which they are looking at.
|
|
193
|
+
*/
|
|
194
|
+
derived?: boolean;
|
|
195
|
+
/**
|
|
196
|
+
* Fraction of total variance each axis accounts for, index-aligned with the dimensions.
|
|
197
|
+
*
|
|
198
|
+
* Only meaningful for a LINEAR embedding — PCA. Its axes are ordered, and each one explains a
|
|
199
|
+
* measurable share of the variance, which is the whole reason to look at a PCA rather than a
|
|
200
|
+
* UMAP: "PC1 (23%)" says something, where "UMAP 1" says nothing at all. A UMAP's coordinates
|
|
201
|
+
* are an arbitrary output of an optimisation with no variance to report, so this stays absent
|
|
202
|
+
* there rather than being faked.
|
|
203
|
+
*/
|
|
204
|
+
varianceRatio?: number[];
|
|
205
|
+
/**
|
|
206
|
+
* How a derived embedding was computed, in a few words — e.g. "PCA(50) then perplexity 30,
|
|
207
|
+
* seed 0".
|
|
208
|
+
*
|
|
209
|
+
* Not decoration. A t-SNE or UMAP is stochastic, and its picture changes materially with
|
|
210
|
+
* perplexity or `n_neighbors`; two runs at different settings are different pictures of the same
|
|
211
|
+
* cells, and neither is more correct. Saying "computed here" without saying HOW leaves a reader
|
|
212
|
+
* unable to reproduce or compare it. Absent for a published embedding, whose parameters belong
|
|
213
|
+
* to whoever published it.
|
|
214
|
+
*/
|
|
215
|
+
params?: string;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* One embedding's coordinates.
|
|
219
|
+
*
|
|
220
|
+
* Deliberately shaped like {@link SpatialObservations}' `x`/`y`/`z` rather than an (N, D) matrix:
|
|
221
|
+
* the point of an embedding here is to be swapped in as the scatter's coordinate source, and
|
|
222
|
+
* matching that shape means the renderer, the hover hit-test and the selection code need no
|
|
223
|
+
* variant for it.
|
|
224
|
+
*/
|
|
225
|
+
export interface SpatialEmbedding {
|
|
226
|
+
meta: SpatialEmbeddingMeta;
|
|
227
|
+
x: Float32Array;
|
|
228
|
+
y: Float32Array;
|
|
229
|
+
z?: Float32Array;
|
|
230
|
+
}
|
|
231
|
+
export interface SpatialDataset {
|
|
232
|
+
/** Stable id — the key the port's lazy accessors are scoped to. */
|
|
233
|
+
id: string;
|
|
234
|
+
/** Human-readable name for menus. */
|
|
235
|
+
name: string;
|
|
236
|
+
observations: SpatialObservations;
|
|
237
|
+
/** Every column available, whether or not its values are loaded. */
|
|
238
|
+
columns: SpatialColumnMeta[];
|
|
239
|
+
features?: SpatialFeatureMeta;
|
|
240
|
+
polygons?: SpatialPolygonsMeta;
|
|
241
|
+
imageRef?: SpatialImageRef;
|
|
242
|
+
/** Presence + geometry of a reference volume the observations sit inside. */
|
|
243
|
+
volume?: SpatialVolumeMeta;
|
|
244
|
+
/** Embeddings available for these observations, whose coordinates are fetched on demand. */
|
|
245
|
+
embeddings?: SpatialEmbeddingMeta[];
|
|
246
|
+
/**
|
|
247
|
+
* Microns per observation coordinate unit — what makes a scale bar possible.
|
|
248
|
+
*
|
|
249
|
+
* The 2D path gets this from `imageRef.mppX`, because there the coordinates are
|
|
250
|
+
* image pixels. A 3D cloud has no image, so it has to say for itself: `1` means
|
|
251
|
+
* the coordinates are already microns.
|
|
252
|
+
*
|
|
253
|
+
* Absent means the unit is UNKNOWN, and a renderer must then draw no scale bar
|
|
254
|
+
* rather than assume one. A bar labelled in microns over pixel-space
|
|
255
|
+
* coordinates is worse than no bar: it looks like a measurement.
|
|
256
|
+
*/
|
|
257
|
+
micronsPerUnit?: number;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* A 3D scalar reference volume registered to the observation coordinates — the
|
|
261
|
+
* anatomical backdrop for a point cloud (an atlas template, or an image z-stack).
|
|
262
|
+
*
|
|
263
|
+
* Geometry only; the voxels are fetched through
|
|
264
|
+
* {@link SpatialDataPort.getVolume} when something is going to draw them. A cloud
|
|
265
|
+
* without one still renders, just in empty space.
|
|
266
|
+
*
|
|
267
|
+
* The observations' coordinate frame must be the volume's own: voxel `(i, j, k)`
|
|
268
|
+
* covers `[i * voxelSize[0], (i + 1) * voxelSize[0])` on x, and so on. That means
|
|
269
|
+
* the volume's near corner sits at the coordinate origin, so a renderer can place
|
|
270
|
+
* the two together knowing nothing else.
|
|
271
|
+
*/
|
|
272
|
+
export interface SpatialVolumeMeta {
|
|
273
|
+
width: number;
|
|
274
|
+
height: number;
|
|
275
|
+
depth: number;
|
|
276
|
+
/** World size of one voxel per axis, in the observations' units. */
|
|
277
|
+
voxelSize: [number, number, number];
|
|
278
|
+
}
|
|
279
|
+
/** Look a column's descriptor up by name. */
|
|
280
|
+
export declare function findColumnMeta(dataset: SpatialDataset, name: string): SpatialColumnMeta | undefined;
|
|
281
|
+
export {};
|