@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.
Files changed (32) hide show
  1. package/README.md +109 -0
  2. package/esm2022/index.mjs +2 -1
  3. package/esm2022/lib/contracts/display-types.mjs +1 -1
  4. package/esm2022/lib/contracts/plot-type-contribution.contract.mjs +66 -0
  5. package/esm2022/lib/contracts/plot-type.mjs +6 -1
  6. package/esm2022/lib/contracts/toolbar-tool.contract.mjs +46 -4
  7. package/esm2022/lib/contracts/visualizer.contract.mjs +1 -1
  8. package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +2 -3
  9. package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +70 -7
  10. package/esm2022/lib/implementations/osd/osd-coordinate-transform.mjs +8 -1
  11. package/esm2022/lib/implementations/plotly/plotly.service.mjs +2 -3
  12. package/esm2022/lib/plot-mode/plot-mode-controller.mjs +329 -0
  13. package/esm2022/lib/routing-visualizer.service.mjs +6 -1
  14. package/esm2022/lib/toolbar/brush/brush-tool.service.mjs +44 -3
  15. package/esm2022/lib/toolbar/toolbar.component.mjs +50 -21
  16. package/esm2022/lib/visualizer.component.mjs +479 -43
  17. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +1092 -75
  18. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
  19. package/index.d.ts +1 -0
  20. package/lib/contracts/display-types.d.ts +10 -0
  21. package/lib/contracts/plot-type-contribution.contract.d.ts +199 -0
  22. package/lib/contracts/plot-type.d.ts +10 -0
  23. package/lib/contracts/toolbar-tool.contract.d.ts +84 -5
  24. package/lib/contracts/visualizer.contract.d.ts +5 -0
  25. package/lib/implementations/osd/openseadragon-visualizer.service.d.ts +21 -0
  26. package/lib/implementations/osd/osd-coordinate-transform.d.ts +6 -0
  27. package/lib/plot-mode/plot-mode-controller.d.ts +130 -0
  28. package/lib/routing-visualizer.service.d.ts +4 -0
  29. package/lib/toolbar/brush/brush-tool.service.d.ts +21 -0
  30. package/lib/toolbar/toolbar.component.d.ts +28 -8
  31. package/lib/visualizer.component.d.ts +121 -8
  32. 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
- * Multi-provider token for contributed tools.
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 ToolbarToolContribution[]>;
254
+ export declare const TOOLBAR_TOOLS: InjectionToken<readonly ToolbarContribution[]>;
187
255
  /** Sort contributed tools into display order. */
188
- export declare function sortToolContributions(tools: readonly ToolbarToolContribution[]): ToolbarToolContribution[];
189
- /** The tools a toolbar should actually show: those with at least one model. */
190
- export declare function visibleToolContributions(tools: readonly ToolbarToolContribution[] | null | undefined): ToolbarToolContribution[];
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;