@jax-data-science/sci-image-visualizer 0.4.0 → 0.6.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 +109 -0
- package/esm2022/index.mjs +2 -1
- package/esm2022/lib/contracts/display-types.mjs +1 -1
- package/esm2022/lib/contracts/plot-type-contribution.contract.mjs +66 -0
- package/esm2022/lib/contracts/plot-type.mjs +6 -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-visualizer.service.mjs +2 -3
- package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +70 -7
- package/esm2022/lib/implementations/osd/osd-coordinate-transform.mjs +8 -1
- package/esm2022/lib/implementations/plotly/plotly.service.mjs +2 -3
- package/esm2022/lib/plot-mode/plot-mode-controller.mjs +329 -0
- package/esm2022/lib/routing-visualizer.service.mjs +6 -1
- package/esm2022/lib/toolbar/brush/brush-tool.service.mjs +44 -3
- package/esm2022/lib/toolbar/toolbar.component.mjs +50 -21
- package/esm2022/lib/visualizer.component.mjs +479 -43
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +1092 -75
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/index.d.ts +1 -0
- package/lib/contracts/display-types.d.ts +10 -0
- package/lib/contracts/plot-type-contribution.contract.d.ts +199 -0
- package/lib/contracts/plot-type.d.ts +10 -0
- package/lib/contracts/toolbar-tool.contract.d.ts +84 -5
- package/lib/contracts/visualizer.contract.d.ts +5 -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/plot-mode/plot-mode-controller.d.ts +130 -0
- package/lib/routing-visualizer.service.d.ts +4 -0
- package/lib/toolbar/brush/brush-tool.service.d.ts +21 -0
- package/lib/toolbar/toolbar.component.d.ts +28 -8
- package/lib/visualizer.component.d.ts +121 -8
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export * from './lib/contracts/image.contract';
|
|
|
19
19
|
export * from './lib/contracts/plot-type';
|
|
20
20
|
export * from './lib/contracts/toolbar-config';
|
|
21
21
|
export * from './lib/contracts/toolbar-tool.contract';
|
|
22
|
+
export * from './lib/contracts/plot-type-contribution.contract';
|
|
22
23
|
export * from './lib/contracts/capabilities.contract';
|
|
23
24
|
export * from './lib/contracts/region-overlay.contract';
|
|
24
25
|
export * from './lib/contracts/display-types';
|
|
@@ -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. */
|
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
/** 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
|
+
}
|
|
131
|
+
/** Everything a contributed mode gets to work with while it is active. */
|
|
132
|
+
export interface PlotModeContext {
|
|
133
|
+
/** Full public visualizer API: regions, region overlay (tool modes), undo, etc. */
|
|
134
|
+
visualizer: IVisualizer;
|
|
135
|
+
viewport: PlotModeViewport;
|
|
136
|
+
/** Current image info from IMAGE_STATE_PORT (null until an image is loaded). */
|
|
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;
|
|
140
|
+
}
|
|
141
|
+
/** A live activation of a contributed mode. */
|
|
142
|
+
export interface PlotModeSession {
|
|
143
|
+
/** Called when the user leaves the mode, the image changes, or the visualizer is destroyed. */
|
|
144
|
+
deactivate(): void;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The mode's side panel, shown in the visualizer's right-hand panel area while
|
|
148
|
+
* a session is live. Two forms:
|
|
149
|
+
*
|
|
150
|
+
* - `component`: an Angular component, rendered through `NgComponentOutlet`
|
|
151
|
+
* with {@link PLOT_MODE_CONTEXT} and {@link PLOT_MODE_SESSION} in its
|
|
152
|
+
* injector. Needs an AOT-compiled (partial-Ivy) component.
|
|
153
|
+
* - `mount`: framework-agnostic. The visualizer hands over an empty host
|
|
154
|
+
* element after `activate` resolves; the function renders into it and returns
|
|
155
|
+
* a teardown. The teardown is called exactly once when the mode deactivates,
|
|
156
|
+
* before `session.deactivate()`, and the host element is then removed.
|
|
157
|
+
*/
|
|
158
|
+
export type PlotModePanel = {
|
|
159
|
+
title: string;
|
|
160
|
+
component: Type<unknown>;
|
|
161
|
+
} | {
|
|
162
|
+
title: string;
|
|
163
|
+
mount(host: HTMLElement, ctx: PlotModeContext, session: PlotModeSession): () => void;
|
|
164
|
+
};
|
|
165
|
+
/** A plot mode contributed to the selector. A plain object is enough. */
|
|
166
|
+
export interface PlotTypeContribution {
|
|
167
|
+
descriptor: ContributedPlotTypeDescriptor;
|
|
168
|
+
/** Called after the base backend has plotted the image in this mode. */
|
|
169
|
+
activate(ctx: PlotModeContext): PlotModeSession | Promise<PlotModeSession>;
|
|
170
|
+
/**
|
|
171
|
+
* Optional side panel, rendered in the visualizer's right-hand panel area while the
|
|
172
|
+
* mode is active. A component panel can inject PLOT_MODE_CONTEXT and PLOT_MODE_SESSION.
|
|
173
|
+
*/
|
|
174
|
+
panel?: PlotModePanel;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Multi-provider token for contributed plot modes.
|
|
178
|
+
*
|
|
179
|
+
* Deliberately has no `factory`: unregistered means no contributed modes, and the
|
|
180
|
+
* library behaves exactly as before. The visualizer injects it `{ optional: true }`
|
|
181
|
+
* and treats null as empty.
|
|
182
|
+
*/
|
|
183
|
+
export declare const PLOT_TYPE_CONTRIBUTIONS: InjectionToken<readonly PlotTypeContribution[]>;
|
|
184
|
+
/** The active mode's context, in a component panel's injector. */
|
|
185
|
+
export declare const PLOT_MODE_CONTEXT: InjectionToken<PlotModeContext>;
|
|
186
|
+
/** The active mode's session, in a component panel's injector. */
|
|
187
|
+
export declare const PLOT_MODE_SESSION: InjectionToken<PlotModeSession>;
|
|
188
|
+
/**
|
|
189
|
+
* One entry in the plot-type selector: a built-in {@link PlotTypeDescriptor}
|
|
190
|
+
* as-is, or a contributed descriptor completed with its base type's `source`.
|
|
191
|
+
* Every entry therefore carries the same fields the selector curates by.
|
|
192
|
+
*/
|
|
193
|
+
export interface PlotTypeOption extends Omit<PlotTypeDescriptor, 'type'> {
|
|
194
|
+
type: PlotTypeId;
|
|
195
|
+
/** Set only for a contributed mode: the built-in type that renders it. */
|
|
196
|
+
baseType?: PlotType;
|
|
197
|
+
}
|
|
198
|
+
/** The selector entry for a contributed mode, given its base type's descriptor. */
|
|
199
|
+
export declare function contributedPlotTypeOption(d: ContributedPlotTypeDescriptor, base: PlotTypeDescriptor): PlotTypeOption;
|
|
@@ -42,6 +42,16 @@ export declare enum PlotType {
|
|
|
42
42
|
* {@link SPATIAL_OMICS}: a single-plane dataset has no z to render. */
|
|
43
43
|
SPATIAL_OMICS_3D = "spatial-omics-3d"
|
|
44
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;
|
|
45
55
|
/** How many spatial dimensions a plot type renders in. */
|
|
46
56
|
export type PlotDimensions = '2d' | '3d';
|
|
47
57
|
/**
|
|
@@ -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[];
|
|
@@ -10,6 +10,7 @@ import { IHistogram } from './channel-histogram-api.contract';
|
|
|
10
10
|
import { ColormapNode, IWandOptions, IBrushOptions, SpatialViewState, SpatialColorBy } from './display-types';
|
|
11
11
|
import { SpatialDataset, SpatialEmbedding } from './spatial-dataset.contract';
|
|
12
12
|
import { SpatialSelectionMask } from '../spatial/spatial-selection';
|
|
13
|
+
import type { PlotModeViewport } from './plot-type-contribution.contract';
|
|
13
14
|
/**
|
|
14
15
|
* Backend-neutral visualization contract. Plotly is one implementation;
|
|
15
16
|
* OpenSeadragon is another (for the image plot type). The abstraction renders
|
|
@@ -419,6 +420,10 @@ export interface IVisualizer extends IDataRenderer, IRegionStore, IToolControlle
|
|
|
419
420
|
* Optional: only the routing service implements it, since the state is shared
|
|
420
421
|
* rather than owned by any one backend. */
|
|
421
422
|
getSpatialControls?(): ISpatialControls | null;
|
|
423
|
+
/** The viewport a contributed plot mode draws over, when the backend on
|
|
424
|
+
* screen can provide one (OpenSeadragon), else null. Optional: backends that
|
|
425
|
+
* no contributed mode rides on need not implement it. */
|
|
426
|
+
getPlotModeViewport?(): PlotModeViewport | null;
|
|
422
427
|
/** Binned intensity histogram for a channel from the currently-displayed
|
|
423
428
|
* pixels, or null when none are available. Feeds the Channels & Histogram
|
|
424
429
|
* pane. */
|
|
@@ -10,6 +10,7 @@ import { PlotType, PlotTypeDescriptor } from '../../contracts/plot-type';
|
|
|
10
10
|
import { IVisualizer, PixelData, IntensityProfile, IIsosurfaceControls, IIntensityControls, ISurface3dControls } from '../../contracts/visualizer.contract';
|
|
11
11
|
import { ViewerCapabilities } from '../../contracts/capabilities.contract';
|
|
12
12
|
import { IRegionOverlay } from '../../contracts/region-overlay.contract';
|
|
13
|
+
import { PlotModeViewport } from '../../contracts/plot-type-contribution.contract';
|
|
13
14
|
import { BaseStoreVisualizer } from '../base-store-visualizer';
|
|
14
15
|
import { SimpleSliceAccessService } from '../simple-slice-access.service';
|
|
15
16
|
import { WandToolService } from '../../toolbar/wand/wand-tool.service';
|
|
@@ -221,6 +222,11 @@ export declare class OpenSeadragonVisualizerService extends BaseStoreVisualizer
|
|
|
221
222
|
/** Visible image region (full-image pixel coords) emitted when the view
|
|
222
223
|
* settles, so the intensity inset can re-sample at the current zoom level. */
|
|
223
224
|
private readonly viewportChange$;
|
|
225
|
+
/** The visible rect on every redraw, coalesced to one emission per animation
|
|
226
|
+
* frame. Feeds {@link PlotModeViewport.frame$}. */
|
|
227
|
+
private readonly frame$;
|
|
228
|
+
private frameRaf;
|
|
229
|
+
private plotModeViewport;
|
|
224
230
|
private readonly intensityProfile$;
|
|
225
231
|
/**
|
|
226
232
|
* Subscribe to the shared VisualizerStore colormap/reverse so OSD recolors in
|
|
@@ -410,6 +416,21 @@ export declare class OpenSeadragonVisualizerService extends BaseStoreVisualizer
|
|
|
410
416
|
/** Compute the current viewport's image-pixel rectangle (clamped to the image)
|
|
411
417
|
* and broadcast it. */
|
|
412
418
|
private emitViewportChange;
|
|
419
|
+
/** The current viewport's image-pixel rectangle, clamped to the image, or
|
|
420
|
+
* null when there is no laid-out viewport. */
|
|
421
|
+
private visibleImageRect;
|
|
422
|
+
/** Coalesce redraws into one {@link frame$} emission per animation frame.
|
|
423
|
+
* Skipped entirely while nothing listens. */
|
|
424
|
+
private scheduleFrame;
|
|
425
|
+
/**
|
|
426
|
+
* The viewport a contributed plot mode draws over. One stable object per
|
|
427
|
+
* service: every method reads the CURRENT viewer, so it stays valid across a
|
|
428
|
+
* viewer rebuild and reports `isReady() === false` while none is mounted
|
|
429
|
+
* (conversions then return NaN rather than throwing).
|
|
430
|
+
*/
|
|
431
|
+
getPlotModeViewport(): PlotModeViewport;
|
|
432
|
+
/** `source`, preceded by the current visible rect for each subscriber (when ready). */
|
|
433
|
+
private withCurrentRect;
|
|
413
434
|
/** OSD targets the image display; the scalar/3D plot types belong to Plotly. */
|
|
414
435
|
getPlotTypeDescriptors(): PlotTypeDescriptor[];
|
|
415
436
|
getIntensityProfile$(): Observable<IntensityProfile[]>;
|
|
@@ -13,5 +13,11 @@ export declare class OsdCoordinateTransform implements ICoordinateTransform {
|
|
|
13
13
|
x: number;
|
|
14
14
|
y: number;
|
|
15
15
|
};
|
|
16
|
+
/** Data (image-pixel) coordinates -> client (mouse event) pixel. The inverse
|
|
17
|
+
* of {@link clientToData}. */
|
|
18
|
+
dataToClient(x: number, y: number): {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
};
|
|
16
22
|
dataLengthToScreen(dataLength: number): number;
|
|
17
23
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { PlotType, PlotTypeId } from '../contracts/plot-type';
|
|
2
|
+
import { ContributedPlotTypeDescriptor, PlotModeContext, PlotModeSession, PlotTypeContribution } from '../contracts/plot-type-contribution.contract';
|
|
3
|
+
/**
|
|
4
|
+
* Lifecycle of the contributed plot modes ({@link PlotTypeContribution}) for one
|
|
5
|
+
* visualizer instance. Package-internal: the visualizer component owns one and
|
|
6
|
+
* drives it from its render pipeline.
|
|
7
|
+
*
|
|
8
|
+
* Owns three things the component should not have to reason about:
|
|
9
|
+
* - which contributions are usable (validated and de-duplicated once);
|
|
10
|
+
* - the single live session, so `deactivate()` runs exactly once per session
|
|
11
|
+
* however many exits race each other (type change, image change, destroy,
|
|
12
|
+
* a superseded async activation);
|
|
13
|
+
* - isolation: nothing a contribution throws or rejects escapes this class.
|
|
14
|
+
* A failure is logged and reported through `onFailed`, and the component
|
|
15
|
+
* falls back to the mode's base type.
|
|
16
|
+
*/
|
|
17
|
+
/** The live activation of a contributed mode, as the component renders it. */
|
|
18
|
+
export interface ActivePlotMode {
|
|
19
|
+
readonly contribution: PlotTypeContribution;
|
|
20
|
+
readonly ctx: PlotModeContext;
|
|
21
|
+
readonly session: PlotModeSession;
|
|
22
|
+
/** Host element handed to a `mount` panel, or null for a component / no panel. */
|
|
23
|
+
readonly panelHost: HTMLElement | null;
|
|
24
|
+
}
|
|
25
|
+
export interface PlotModeControllerHooks {
|
|
26
|
+
/** A session is live (and a `mount` panel, if any, has been mounted). */
|
|
27
|
+
onActivated(active: ActivePlotMode): void;
|
|
28
|
+
/** The live session is about to end. Called BEFORE any teardown, so a
|
|
29
|
+
* component panel can be destroyed while its session is still valid. */
|
|
30
|
+
onDeactivating(active: ActivePlotMode): void;
|
|
31
|
+
/** Activation failed (threw, rejected, never got a ready viewport, or its
|
|
32
|
+
* panel failed to mount). The component falls back to the base type. */
|
|
33
|
+
onFailed(contribution: PlotTypeContribution, error: unknown): void;
|
|
34
|
+
}
|
|
35
|
+
/** Minimal logger, injectable for tests. */
|
|
36
|
+
export interface PlotModeLogger {
|
|
37
|
+
warn(...args: unknown[]): void;
|
|
38
|
+
error(...args: unknown[]): void;
|
|
39
|
+
}
|
|
40
|
+
/** Base types a contributed mode can ride on in this version. */
|
|
41
|
+
export declare const SUPPORTED_CONTRIBUTION_BASE_TYPES: readonly PlotType[];
|
|
42
|
+
/**
|
|
43
|
+
* Validate what arrived on `PLOT_TYPE_CONTRIBUTIONS`. A malformed or clashing
|
|
44
|
+
* contribution is dropped with a warning rather than breaking the selector:
|
|
45
|
+
* - a nested array (`useValue: [a, b], multi: true`) is flattened one level;
|
|
46
|
+
* - `type` must be a non-empty string that is not a built-in `PlotType` value;
|
|
47
|
+
* - the first contribution with a given `type` wins;
|
|
48
|
+
* - `baseType` must be one of {@link SUPPORTED_CONTRIBUTION_BASE_TYPES};
|
|
49
|
+
* - `activate` must be a function.
|
|
50
|
+
*/
|
|
51
|
+
export declare function normalizeContributions(raw: readonly unknown[] | null | undefined, log?: PlotModeLogger): PlotTypeContribution[];
|
|
52
|
+
export declare class PlotModeController {
|
|
53
|
+
private readonly hooks;
|
|
54
|
+
private readonly log;
|
|
55
|
+
readonly contributions: readonly PlotTypeContribution[];
|
|
56
|
+
private readonly byId;
|
|
57
|
+
private active;
|
|
58
|
+
private teardown;
|
|
59
|
+
/** Bumped by every activate/deactivate, so a pending async activation can
|
|
60
|
+
* tell it has been superseded. */
|
|
61
|
+
private generation;
|
|
62
|
+
private readyFrame;
|
|
63
|
+
/** Contributions whose last cleanup (panel teardown / `deactivate()`) threw or
|
|
64
|
+
* rejected. Their next automatic re-activation (base re-render, image switch)
|
|
65
|
+
* falls back to the base type instead; an explicit selection clears the mark. */
|
|
66
|
+
private readonly failedCleanup;
|
|
67
|
+
private readyCb;
|
|
68
|
+
constructor(raw: readonly unknown[] | null | undefined, hooks: PlotModeControllerHooks, log?: PlotModeLogger);
|
|
69
|
+
/** Descriptors of the usable contributions, in registration order. */
|
|
70
|
+
descriptors(): ContributedPlotTypeDescriptor[];
|
|
71
|
+
/** The contribution for a selector id, if it is a contributed mode. */
|
|
72
|
+
find(id: PlotTypeId | null | undefined): PlotTypeContribution | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* The built-in type that renders `id`: a built-in type is itself, a
|
|
75
|
+
* contributed id is its `baseType`, and an id that is neither (a stale
|
|
76
|
+
* contributed id whose provider is gone) resolves to `PlotType.IMAGE`, the
|
|
77
|
+
* default view.
|
|
78
|
+
*/
|
|
79
|
+
baseTypeOf(id: PlotTypeId | null | undefined): PlotType;
|
|
80
|
+
/** The live session, if any. */
|
|
81
|
+
get current(): ActivePlotMode | null;
|
|
82
|
+
/** Whether an activation is waiting on the contribution or the viewport. */
|
|
83
|
+
get pending(): boolean;
|
|
84
|
+
private pendingId;
|
|
85
|
+
/**
|
|
86
|
+
* Start a session for `contribution`. Ends any live one first. Resolves when
|
|
87
|
+
* the attempt has finished (successfully or not); it never rejects.
|
|
88
|
+
*/
|
|
89
|
+
activate(contribution: PlotTypeContribution, ctx: PlotModeContext): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* End the live session, if any: the component panel is dropped (via
|
|
92
|
+
* `onDeactivating`), a `mount` panel's teardown runs, then
|
|
93
|
+
* `session.deactivate()`, then the mount host is removed. Each step is
|
|
94
|
+
* isolated. A pending activation is cancelled; if it resolves later its
|
|
95
|
+
* session is deactivated on arrival. Idempotent.
|
|
96
|
+
*/
|
|
97
|
+
deactivate(): void;
|
|
98
|
+
/**
|
|
99
|
+
* Forget recorded cleanup failures. The host calls this when the USER picks a
|
|
100
|
+
* plot type: an explicit choice gets a fresh attempt, only automatic
|
|
101
|
+
* re-activations fall back.
|
|
102
|
+
*/
|
|
103
|
+
clearCleanupFailures(): void;
|
|
104
|
+
private start;
|
|
105
|
+
/**
|
|
106
|
+
* The host could not make the live mode usable (its panel threw while being
|
|
107
|
+
* created or first rendered): end the session once, then fall back to its base.
|
|
108
|
+
* No-op when nothing is live.
|
|
109
|
+
*/
|
|
110
|
+
failActive(err: unknown): void;
|
|
111
|
+
private fail;
|
|
112
|
+
private guard;
|
|
113
|
+
/** Like `guard`, for a contribution callback typed `void` that may still return a
|
|
114
|
+
* promise: a rejection is logged instead of escaping as an unhandled rejection. */
|
|
115
|
+
private guardLogged;
|
|
116
|
+
/** Run a live session's cleanup; a throw OR a rejection goes to `cleanupFailed`. */
|
|
117
|
+
private cleanup;
|
|
118
|
+
/**
|
|
119
|
+
* A contribution's cleanup failed. If that same contribution is live or starting
|
|
120
|
+
* again (a base re-render / image switch re-activated it — or, for an async
|
|
121
|
+
* rejection, already has), end it and fall back to its base type. Otherwise the
|
|
122
|
+
* user has moved on: remember it, so the next automatic re-activation falls back.
|
|
123
|
+
*/
|
|
124
|
+
private cleanupFailed;
|
|
125
|
+
/** Call `cb(true)` once the viewport reports ready (now, or on a later
|
|
126
|
+
* animation frame), or `cb(false)` after {@link MAX_READY_FRAMES}. */
|
|
127
|
+
private whenReady;
|
|
128
|
+
/** Stop waiting for the viewport; the waiting activation resolves as superseded. */
|
|
129
|
+
private cancelReadyWait;
|
|
130
|
+
}
|
|
@@ -17,6 +17,7 @@ import { IChannelHistogramApi, IChannelState, IHistogram } from './contracts/cha
|
|
|
17
17
|
import { VisualizerStore } from './store/visualizer-store.service';
|
|
18
18
|
import { NapariVisualizerService } from './implementations/napari-js/napari-visualizer.service';
|
|
19
19
|
import { VizConfig } from './contracts/viz-config';
|
|
20
|
+
import { PlotModeViewport } from './contracts/plot-type-contribution.contract';
|
|
20
21
|
import * as i0 from "@angular/core";
|
|
21
22
|
export declare class RoutingVisualizerService implements IVisualizer, IRegionEditorApi, IChannelHistogramApi {
|
|
22
23
|
private plotly;
|
|
@@ -98,6 +99,9 @@ export declare class RoutingVisualizerService implements IVisualizer, IRegionEdi
|
|
|
98
99
|
resetSurfaceCamera(): void;
|
|
99
100
|
setResolutionScale(scale: number): void;
|
|
100
101
|
getResolutionScale(): number;
|
|
102
|
+
/** The viewport of the backend on screen, for a contributed plot mode. Null
|
|
103
|
+
* when that backend cannot provide one (e.g. OSD fell back to Plotly). */
|
|
104
|
+
getPlotModeViewport(): PlotModeViewport | null;
|
|
101
105
|
getPlotTypeDescriptors(): PlotTypeDescriptor[];
|
|
102
106
|
setStackLoading(b: boolean): void;
|
|
103
107
|
cancelLoading(): void;
|
|
@@ -4,6 +4,15 @@ import * as i0 from "@angular/core";
|
|
|
4
4
|
/** Brush parameters. `size` is the brush *diameter* in matrix (image) pixels. */
|
|
5
5
|
export interface BrushOptions {
|
|
6
6
|
size?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Paint as one class. With `label` set, new regions take this label, and the
|
|
9
|
+
* stroke only adopts or merges regions that already have it, so painting one
|
|
10
|
+
* class never grows another class's region. `color` colours the painted
|
|
11
|
+
* regions instead of the toolbar's shape colour. Leave both unset for the
|
|
12
|
+
* plain toolbar brush.
|
|
13
|
+
*/
|
|
14
|
+
label?: string;
|
|
15
|
+
color?: string;
|
|
7
16
|
}
|
|
8
17
|
/**
|
|
9
18
|
* The brush host is identical to the wand's — both need the cached-image
|
|
@@ -60,6 +69,8 @@ export declare class BrushToolService {
|
|
|
60
69
|
private lastMatrix;
|
|
61
70
|
private dragging;
|
|
62
71
|
private size;
|
|
72
|
+
/** The class being painted ({@link BrushOptions.label} / `color`); null = plain brush. */
|
|
73
|
+
private paintClass;
|
|
63
74
|
private readonly boundMouseDown;
|
|
64
75
|
private readonly boundMouseMove;
|
|
65
76
|
private readonly boundMouseUp;
|
|
@@ -68,6 +79,13 @@ export declare class BrushToolService {
|
|
|
68
79
|
bindHost(host: BrushToolHost): void;
|
|
69
80
|
/** Toggle the brush on/off. */
|
|
70
81
|
setMode(active: boolean, options?: BrushOptions): void;
|
|
82
|
+
/**
|
|
83
|
+
* Update size and/or class while armed. The class changes only when `label`
|
|
84
|
+
* or `color` is passed (as a key, even `undefined`), so a size-only update
|
|
85
|
+
* keeps it. Switching class drops the active region, so the next stroke
|
|
86
|
+
* starts a region of the new class.
|
|
87
|
+
*/
|
|
88
|
+
setOptions(options?: BrushOptions): void;
|
|
71
89
|
/** Live-update the brush size (matrix-pixel diameter). */
|
|
72
90
|
setSize(size: number): void;
|
|
73
91
|
/** Drop the active brush region so the next stroke starts a new one. */
|
|
@@ -102,6 +120,9 @@ export declare class BrushToolService {
|
|
|
102
120
|
* erase stroke deletes every pixel.
|
|
103
121
|
*/
|
|
104
122
|
private dropActiveShape;
|
|
123
|
+
/** Whether the stroke may adopt or merge `region`: always for the plain brush,
|
|
124
|
+
* otherwise only a region of the class being painted. */
|
|
125
|
+
private paintsInto;
|
|
105
126
|
/** A region's closed-polygon vertices (image/data coords), or null when it
|
|
106
127
|
* isn't a fillable closed polygon (rectangles, open polylines). */
|
|
107
128
|
private regionVerts;
|