@jax-data-science/sci-image-visualizer 0.2.18 → 0.3.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 (35) hide show
  1. package/esm2022/index.mjs +2 -9
  2. package/esm2022/lib/contracts/instance-segmenter.contract.mjs +10 -10
  3. package/esm2022/lib/contracts/semantic-segmenter.contract.mjs +10 -9
  4. package/esm2022/lib/contracts/toolbar-tool.contract.mjs +64 -0
  5. package/esm2022/lib/toolbar/model-info.mjs +32 -0
  6. package/esm2022/lib/toolbar/toolbar.component.mjs +82 -55
  7. package/esm2022/lib/visualizer.component.mjs +131 -138
  8. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-iPNL2JyC.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs} +2 -2
  9. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-iPNL2JyC.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs.map} +1 -1
  10. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +710 -1565
  11. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
  12. package/index.d.ts +1 -6
  13. package/lib/contracts/instance-segmenter.contract.d.ts +7 -3
  14. package/lib/contracts/semantic-segmenter.contract.d.ts +7 -2
  15. package/lib/contracts/toolbar-tool.contract.d.ts +190 -0
  16. package/lib/toolbar/model-info.d.ts +17 -0
  17. package/lib/toolbar/toolbar.component.d.ts +53 -30
  18. package/lib/visualizer.component.d.ts +49 -58
  19. package/package.json +2 -4
  20. package/src/lib/assets/cell-detection.svg +37 -0
  21. package/src/lib/assets/cells.svg +1 -1
  22. package/src/lib/assets/sam-box-prompt.svg +61 -0
  23. package/src/lib/assets/sam-point-prompt.svg +91 -0
  24. package/esm2022/lib/toolbar/segmentation/retinal-layer-tool.service.mjs +0 -197
  25. package/esm2022/lib/toolbar/segmentation/retinal-model-registry.mjs +0 -119
  26. package/esm2022/lib/toolbar/segmentation/retinal-segmenter.service.mjs +0 -172
  27. package/esm2022/lib/toolbar/segmentation/yolo-detect-tool.service.mjs +0 -208
  28. package/esm2022/lib/toolbar/segmentation/yolo-model-registry.mjs +0 -129
  29. package/esm2022/lib/toolbar/segmentation/yolo-segmenter.service.mjs +0 -172
  30. package/lib/toolbar/segmentation/retinal-layer-tool.service.d.ts +0 -49
  31. package/lib/toolbar/segmentation/retinal-model-registry.d.ts +0 -71
  32. package/lib/toolbar/segmentation/retinal-segmenter.service.d.ts +0 -60
  33. package/lib/toolbar/segmentation/yolo-detect-tool.service.d.ts +0 -51
  34. package/lib/toolbar/segmentation/yolo-model-registry.d.ts +0 -59
  35. package/lib/toolbar/segmentation/yolo-segmenter.service.d.ts +0 -50
package/index.d.ts CHANGED
@@ -16,6 +16,7 @@ export * from './lib/contracts/viz-config';
16
16
  export * from './lib/contracts/image.contract';
17
17
  export * from './lib/contracts/plot-type';
18
18
  export * from './lib/contracts/toolbar-config';
19
+ export * from './lib/contracts/toolbar-tool.contract';
19
20
  export * from './lib/contracts/capabilities.contract';
20
21
  export * from './lib/contracts/region-overlay.contract';
21
22
  export * from './lib/contracts/display-types';
@@ -23,17 +24,11 @@ export * from './lib/contracts/sam.contract';
23
24
  export * from './lib/contracts/cell-segmenter.contract';
24
25
  export { CellposeSegmenterService } from './lib/toolbar/segmentation/cellpose-segmenter.service';
25
26
  export * from './lib/contracts/instance-segmenter.contract';
26
- export { YoloSegmenterService } from './lib/toolbar/segmentation/yolo-segmenter.service';
27
- export { YoloDetectToolService, YOLO_REGION_SOURCE } from './lib/toolbar/segmentation/yolo-detect-tool.service';
28
27
  export * from './lib/contracts/semantic-segmenter.contract';
29
- export { RetinalSegmenterService } from './lib/toolbar/segmentation/retinal-segmenter.service';
30
- export { RetinalLayerToolService, RETINAL_REGION_SOURCE, } from './lib/toolbar/segmentation/retinal-layer-tool.service';
31
28
  export { ProcessingImage } from './lib/processing/processing-image';
32
29
  export { cropImage, CropImageOptions } from './lib/processing/image-crop';
33
30
  export { ImageConverterService } from './lib/processing/image-converter.service';
34
31
  export { SAM_MODELS, DEFAULT_SAM_MODEL_ID, getSamModel, isSamModelReady, setSamModelUrls, setDefaultSamModel, getDefaultSamModelId, } from './lib/toolbar/segmentation/sam-model-registry';
35
- export { YOLO_MODELS, DEFAULT_YOLO_MODEL_ID, getYoloModel, isYoloModelReady, setYoloModelUrls, setDefaultYoloModel, getDefaultYoloModelId, YoloModelDef, } from './lib/toolbar/segmentation/yolo-model-registry';
36
- export { RETINAL_MODELS, DEFAULT_RETINAL_MODEL_ID, getRetinalModel, isRetinalModelReady, readyRetinalModels, setRetinalModelUrls, setDefaultRetinalModel, getDefaultRetinalModelId, RetinalModelDef, } from './lib/toolbar/segmentation/retinal-model-registry';
37
32
  export { setOrtWasmBase, getOrtWasmBase } from './lib/toolbar/segmentation/ort-runtime-config';
38
33
  export * from './lib/models/region';
39
34
  export * from './lib/models/class-preset';
@@ -128,8 +128,12 @@ export interface IInstanceSegmenter {
128
128
  }, opts?: InstanceSegmentOptions, progress?: InstanceSegmentProgress): Promise<InstanceSegmentation>;
129
129
  }
130
130
  /**
131
- * DI token for the automatic instance segmenter. Defaults to the in-library
132
- * {@link YoloSegmenterService} (yolo-segdetect-js, lazy-loaded); override it in
133
- * the host to swap implementations — for example to delegate to the server.
131
+ * DI token for the automatic instance segmenter.
132
+ *
133
+ * **No default.** It used to fall back to an in-library YOLO service, and that
134
+ * factory was what pulled `yolo-segdetect-js` into every bundle of this package
135
+ * — which is not shippable while the checkpoints are closed. A host provides an
136
+ * implementation (see `@jax-data-science/sci-image-visualizer-jax-tools`), or
137
+ * nothing does and no tool needing it is registered.
134
138
  */
135
139
  export declare const INSTANCE_SEGMENTER: InjectionToken<IInstanceSegmenter>;
@@ -99,7 +99,12 @@ export interface ISemanticSegmenter {
99
99
  }, opts?: SemanticSegmentOptions, progress?: SemanticSegmentProgress): Promise<SemanticSegmentation>;
100
100
  }
101
101
  /**
102
- * DI token for the semantic segmenter, defaulting to the in-library
103
- * jax-ai-js-backed implementation. Hosts override it to supply their own.
102
+ * DI token for the semantic segmenter.
103
+ *
104
+ * **No default.** It used to fall back to an in-library jax-ai-js service, and
105
+ * that factory was what pulled `jax-ai-js` into every bundle of this package —
106
+ * which is not shippable while the checkpoints are closed. A host provides an
107
+ * implementation (see `@jax-data-science/sci-image-visualizer-jax-tools`), or
108
+ * nothing does and no tool needing it is registered.
104
109
  */
105
110
  export declare const SEMANTIC_SEGMENTER: InjectionToken<ISemanticSegmenter>;
@@ -0,0 +1,190 @@
1
+ /**
2
+ * How a tool gets into the toolbar without this library knowing what it is.
3
+ *
4
+ * WHY THIS EXISTS
5
+ * The YOLO detector and the retinal-layer segmenter used to be built in: their
6
+ * buttons were hardcoded in the toolbar template, their model registries named
7
+ * JAX checkpoints, and the `INSTANCE_SEGMENTER` / `SEMANTIC_SEGMENTER` tokens
8
+ * defaulted to in-library services that imported `yolo-segdetect-js` and
9
+ * `jax-ai-js`. Those default factories were the only thing pulling either
10
+ * package into the bundle — the contracts themselves were already clean.
11
+ *
12
+ * That arrangement cannot ship in an open library while the models are closed.
13
+ * So the tools move out, into a package that depends on this one, and arrive
14
+ * back through {@link TOOLBAR_TOOLS}. This library keeps the contracts, the
15
+ * chrome and the region plumbing; it no longer knows that YOLO exists.
16
+ *
17
+ * A host registers tools with a multi-provider:
18
+ *
19
+ * { provide: TOOLBAR_TOOLS, useExisting: YoloToolContribution, multi: true }
20
+ *
21
+ * Register nothing and the toolbar has no segmentation tools, the help dialog
22
+ * lists none, and neither model package is in the graph.
23
+ *
24
+ * WHY PARAMS ARE DECLARED, NOT RENDERED
25
+ * A contribution describes its parameters ({@link ToolParamSpec}) instead of
26
+ * supplying a component to draw them. The toolbar renders one generic dialog
27
+ * from that description. The alternative — each plugin shipping its own dialog
28
+ * component — would make every plugin depend on this library's exact PrimeNG
29
+ * version and styling internals to look like the rest of the toolbar, and the
30
+ * two dialogs that exist today are entirely numbers and one checkbox. A schema
31
+ * covers them, and it is the same shape jit-ui already uses for its pipeline
32
+ * step parameters, so the vocabulary is familiar.
33
+ *
34
+ * If a tool ever needs a control this cannot express, add a variant here rather
35
+ * than an escape hatch that returns a component — the moment one tool draws
36
+ * itself, the toolbar stops being able to lay tools out consistently.
37
+ *
38
+ * WHY AN EMPTY MODEL LIST HIDES THE TOOL
39
+ * {@link ToolbarToolContribution.models} returning `[]` removes the tool from
40
+ * the toolbar entirely. This preserves the behaviour the built-in tools already
41
+ * had (their buttons were `*ngIf`'d on a filtered registry), and it gives a host
42
+ * one switch for "the weights are not configured in this deployment" that does
43
+ * not require unregistering the provider.
44
+ */
45
+ import { InjectionToken } from '@angular/core';
46
+ import type { Observable } from 'rxjs';
47
+ import type { IVisualizer } from './visualizer.contract';
48
+ /** A number field. Rendered as a stepper, matching the built-in dialogs. */
49
+ export interface NumberParamSpec {
50
+ id: string;
51
+ label: string;
52
+ type: 'number';
53
+ min?: number;
54
+ max?: number;
55
+ step?: number;
56
+ /** Decimal places to show. Omit for integers. */
57
+ fractionDigits?: number;
58
+ /** Hover help. Plain text; it is not rendered as HTML. */
59
+ tooltip?: string;
60
+ }
61
+ /** A checkbox. `label` sits beside it and may run to a sentence or two. */
62
+ export interface BooleanParamSpec {
63
+ id: string;
64
+ label: string;
65
+ type: 'boolean';
66
+ tooltip?: string;
67
+ }
68
+ /** A fixed set of choices. */
69
+ export interface SelectParamSpec {
70
+ id: string;
71
+ label: string;
72
+ type: 'select';
73
+ options: {
74
+ label: string;
75
+ value: string | number;
76
+ }[];
77
+ tooltip?: string;
78
+ }
79
+ export type ToolParamSpec = NumberParamSpec | BooleanParamSpec | SelectParamSpec;
80
+ /** One selectable checkpoint. */
81
+ export interface ToolModelOption {
82
+ id: string;
83
+ label: string;
84
+ /**
85
+ * Describes the checkpoint in the picker's hover tooltip — size, speed,
86
+ * training domain, accuracy. **Rendered as HTML**, so it is trusted content
87
+ * from the contributing package, never anything user-supplied.
88
+ */
89
+ info?: string;
90
+ /**
91
+ * Parameter values this checkpoint wants, merged over
92
+ * {@link ToolbarToolContribution.defaultParams} when it is selected. Defaults
93
+ * belong to the model rather than the tool — a detector trained on native 40x
94
+ * patches wants different tiling from one trained on thumbnails.
95
+ */
96
+ defaults?: Record<string, unknown>;
97
+ }
98
+ /**
99
+ * The `status$` / `busy$` / `progress$` surface the toolbar drives its sticky
100
+ * toast and progress bar from. `progress$` is a 0..1 fraction, or -1 for
101
+ * indeterminate.
102
+ */
103
+ export interface ToolProgress {
104
+ status$: Observable<string>;
105
+ busy$: Observable<boolean>;
106
+ progress$: Observable<number>;
107
+ }
108
+ /** Toolbar icon: a published asset, or a PrimeIcon class name. */
109
+ export type ToolIcon = {
110
+ src: string;
111
+ pi?: never;
112
+ } | {
113
+ pi: string;
114
+ src?: never;
115
+ };
116
+ /**
117
+ * A tool contributed to the toolbar.
118
+ *
119
+ * Implement it on an `@Injectable()` service in the contributing package, so it
120
+ * can inject its own segmenter and model registry — this library provides
121
+ * neither and must not learn to.
122
+ */
123
+ export interface ToolbarToolContribution {
124
+ /** Stable identity, used for tracking and persisted preferences. */
125
+ id: string;
126
+ /** Short name, used in the toast and the help dialog heading. */
127
+ label: string;
128
+ icon: ToolIcon;
129
+ /** Tooltip on the run button — say what it does to the current view. */
130
+ runTooltip: string;
131
+ /** Tooltip on the checkpoint picker. Defaults to "Pick the model". */
132
+ modelTooltip?: string;
133
+ /** Tooltip on the parameters button. Defaults to "Parameters". */
134
+ paramsTooltip?: string;
135
+ /**
136
+ * Where it sits among the segmentation tools. Lower sorts first; ties fall
137
+ * back to registration order. The built-in prompted tools occupy 0-99, so
138
+ * contributed tools should start at 100 to land after them.
139
+ */
140
+ order?: number;
141
+ /** Selectable checkpoints. **Empty hides the tool.** */
142
+ models(): ToolModelOption[];
143
+ /** Which checkpoint runs when the user has not chosen. */
144
+ defaultModelId(): string;
145
+ /**
146
+ * Called when the user picks a checkpoint, before parameters are re-seeded.
147
+ * Use it to record the choice in the contributing package's own registry.
148
+ */
149
+ onModelChange?(modelId: string): void;
150
+ /** Parameter fields, in dialog order. Empty hides the parameters button. */
151
+ params: ToolParamSpec[];
152
+ /**
153
+ * Baseline parameter values for a checkpoint. Called on first use and again
154
+ * on "Reset to model defaults", so it must be pure and must return every key
155
+ * the tool reads — a parameter the dialog shows but this omits renders as
156
+ * empty and reaches `run` as undefined.
157
+ */
158
+ defaultParams(modelId: string): Record<string, unknown>;
159
+ /**
160
+ * Entry for the help dialog's tool list. `body` is rendered as HTML and may
161
+ * reference published assets (e.g. the tool's own icon).
162
+ */
163
+ help?: {
164
+ body: string;
165
+ };
166
+ /** Progress surface for the toast. */
167
+ progress: ToolProgress;
168
+ /**
169
+ * Run over what is currently displayed, and write whatever it finds through
170
+ * `viz`. Resolves with the number of regions added.
171
+ *
172
+ * Everything the tool needs is on {@link IVisualizer}, which is part of this
173
+ * library's public surface — a contribution never reaches into internals.
174
+ * Region writes go through the same path the built-in tools use, so the
175
+ * index-based overlays and the Regions table stay consistent.
176
+ */
177
+ run(viz: IVisualizer, params: Record<string, unknown>): Promise<number>;
178
+ }
179
+ /**
180
+ * Multi-provider token for contributed tools.
181
+ *
182
+ * Deliberately has no `factory`: unregistered means no tools, which is what
183
+ * makes this library shippable without the closed model packages. Inject it
184
+ * `{ optional: true }` and treat null as empty.
185
+ */
186
+ export declare const TOOLBAR_TOOLS: InjectionToken<readonly ToolbarToolContribution[]>;
187
+ /** 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[];
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Per-model descriptions for the info icon beside each item of the SAM / YOLO /
3
+ * retinal model dropdowns, keyed by registry model id.
4
+ *
5
+ * Lives in the toolbar layer rather than in the model registries for two
6
+ * reasons: the toolbar receives only `{ id, label }` from its host, so registry
7
+ * copy would have to be threaded through every host; and this text was already
8
+ * owned here — the SAM entries are the prose from the "About the SAM models"
9
+ * overlay panel this replaced, moved next to the model it describes.
10
+ *
11
+ * The quoted numbers come from the registries (`sizeMb`, `miou`, `patchSize`,
12
+ * per-model tiling `defaults`) — keep them in step when those change.
13
+ *
14
+ * Rendered with `[escape]="false"`, so `<b>` / `<br>` / entities are honoured.
15
+ * A model with no entry here simply gets no info icon.
16
+ */
17
+ export declare const MODEL_INFO: Record<string, string>;
@@ -3,6 +3,7 @@ import { MenuItem } from 'primeng/api';
3
3
  import { IImageInfo } from '../contracts/image.contract';
4
4
  import { PlotType, PlotTypeDescriptor } from '../contracts/plot-type';
5
5
  import { ToolbarToolVisibility } from '../contracts/toolbar-config';
6
+ import { ToolbarToolContribution } from '../contracts/toolbar-tool.contract';
6
7
  import * as i0 from "@angular/core";
7
8
  /**
8
9
  * Presentational toolbar for the plotting viewport.
@@ -66,18 +67,14 @@ export declare class ToolbarComponent implements OnChanges {
66
67
  label: string;
67
68
  }[];
68
69
  samModelId: string;
69
- /** YOLO checkpoints offered by the host's registry, and the active one. */
70
- yoloModels: {
71
- id: string;
72
- label: string;
73
- }[];
74
- yoloModelId: string;
75
- /** Retinal-layer checkpoints offered by the host's registry, and the active one. */
76
- retinalModels: {
77
- id: string;
78
- label: string;
79
- }[];
80
- retinalModelId: string;
70
+ /**
71
+ * Tools contributed through {@link TOOLBAR_TOOLS}, already filtered to the
72
+ * ones with models and sorted. The host resolves them so it can own the
73
+ * per-tool parameter state; this component only draws them.
74
+ */
75
+ contributedTools: ToolbarToolContribution[];
76
+ /** Active checkpoint per contributed tool, keyed by tool id. */
77
+ toolModelIds: Record<string, string>;
81
78
  selectPlotType: EventEmitter<PlotType>;
82
79
  /** Intensity (LINE) mode: add another colored line ROI + inset trace. */
83
80
  addProfileLine: EventEmitter<void>;
@@ -117,18 +114,15 @@ export declare class ToolbarComponent implements OnChanges {
117
114
  segmentRegions: EventEmitter<void>;
118
115
  /** Run cellpose-SAM (auto) inside each drawn rectangle's crop (jit-ui#90). */
119
116
  segmentCellpose: EventEmitter<void>;
120
- /** Run YOLO detection over the current view. */
121
- detectYolo: EventEmitter<void>;
122
- /** Pick the YOLO checkpoint. */
123
- yoloModelChange: EventEmitter<string>;
124
- /** Open the YOLO parameter dialog. */
125
- openYoloParams: EventEmitter<void>;
126
- /** Run retinal-layer segmentation over the current view. */
127
- segmentRetinal: EventEmitter<void>;
128
- /** Pick the retinal-layer checkpoint. */
129
- retinalModelChange: EventEmitter<string>;
130
- /** Open the retinal-layer parameter dialog. */
131
- openRetinalParams: EventEmitter<void>;
117
+ /** Run a contributed tool over the current view, by tool id. */
118
+ runTool: EventEmitter<string>;
119
+ /** Pick a contributed tool's checkpoint. */
120
+ toolModelChange: EventEmitter<{
121
+ toolId: string;
122
+ modelId: string;
123
+ }>;
124
+ /** Open a contributed tool's parameter dialog, by tool id. */
125
+ openToolParams: EventEmitter<string>;
132
126
  /** SAM model picker (jit-ui#90 P1). */
133
127
  samModelChange: EventEmitter<string>;
134
128
  wandSensitivityChange: EventEmitter<number | undefined>;
@@ -155,15 +149,44 @@ export declare class ToolbarComponent implements OnChanges {
155
149
  * Held as a stable array (rebuilt only when `samModels`/`samModelId` change),
156
150
  * NOT a getter: a getter returns a fresh array with new `command` closures on
157
151
  * every change-detection tick, which makes the bound `p-menu` overlay rebuild
158
- * its DOM mid-interaction and swallow the click on a menu item. */
152
+ * its DOM mid-interaction and swallow the click on a menu item.
153
+ *
154
+ * `tooltip` carries the model's description from {@link MODEL_INFO}; the
155
+ * menus' shared item template turns it into the hover info icon. p-menu's own
156
+ * rendering ignores the field (it reads `item.title`), so it is free to use. */
159
157
  samMenuItems: MenuItem[];
160
- /** Same stable-array reasoning as {@link samMenuItems}. */
161
- yoloMenuItems: MenuItem[];
162
- /** Same stable-array reasoning as {@link samMenuItems}. */
163
- retinalMenuItems: MenuItem[];
158
+ /**
159
+ * Model menus for the contributed tools, keyed by tool id. Same stable-array
160
+ * reasoning as {@link samMenuItems} — and it is why this is a map rebuilt in
161
+ * `ngOnChanges` rather than a method called from the template, which would
162
+ * hand `p-menu` a new array every tick.
163
+ *
164
+ * A contributed tool supplies its own per-model description, so unlike the
165
+ * SAM menu these do not consult {@link MODEL_INFO}: this library has no
166
+ * copy for checkpoints it does not know about.
167
+ */
168
+ toolMenuItems: Record<string, MenuItem[]>;
164
169
  /** Rebuild the SAM model menu when the model list or active selection
165
170
  * changes (keeps the array reference stable across other CD ticks). */
166
171
  ngOnChanges(changes: SimpleChanges): void;
172
+ /**
173
+ * Strip markup from a model description so it can be read aloud.
174
+ *
175
+ * The copy in MODEL_INFO and in a contribution's `ToolModelOption.info` is
176
+ * written for a visual tooltip rendered with `[escape]="false"`, so it carries
177
+ * `<b>` and `<br>`. Passed to `aria-label` verbatim a screen reader announces
178
+ * the tags, so they are removed and `<br>` becomes a sentence break. Entities
179
+ * used in that copy (`&nbsp;`, `&times;`, `&amp;`) are decoded for the same
180
+ * reason.
181
+ */
182
+ plainText(html: string): string;
183
+ /** Contributed tool names for the help dialog's prompted/no-prompt contrast,
184
+ * e.g. "YOLO, Retinal layers". Empty when nothing is registered, which is
185
+ * why the sentence that uses it is itself conditional. */
186
+ get contributedToolNames(): string;
187
+ /** Keeps each contributed tool's `p-menu` overlay alive across CD ticks —
188
+ * re-creating it mid-interaction swallows the click on a menu item. */
189
+ trackToolById(_index: number, tool: ToolbarToolContribution): string;
167
190
  /** The Image plot type renders as a natively pan/zoom-able raster, so the
168
191
  * generic zoom/pan tools are hidden. */
169
192
  get isImageView(): boolean;
@@ -191,5 +214,5 @@ export declare class ToolbarComponent implements OnChanges {
191
214
  get isIntensityCapable(): boolean;
192
215
  showHelp(): void;
193
216
  static ɵfac: i0.ɵɵFactoryDeclaration<ToolbarComponent, never>;
194
- static ɵcmp: i0.ɵɵComponentDeclaration<ToolbarComponent, "plotting-toolbar", never, { "imageInfo": { "alias": "imageInfo"; "required": false; }; "plotTypeOptions": { "alias": "plotTypeOptions"; "required": false; }; "selectedPlotType": { "alias": "selectedPlotType"; "required": false; }; "isoRange": { "alias": "isoRange"; "required": false; }; "maxIndex": { "alias": "maxIndex"; "required": false; }; "zIndex": { "alias": "zIndex"; "required": false; }; "isHeatmap": { "alias": "isHeatmap"; "required": false; }; "activeDragMode": { "alias": "activeDragMode"; "required": false; }; "activeSurface3dMode": { "alias": "activeSurface3dMode"; "required": false; }; "axesVisible": { "alias": "axesVisible"; "required": false; }; "wireframeActive": { "alias": "wireframeActive"; "required": false; }; "resolutionScale": { "alias": "resolutionScale"; "required": false; }; "wandSensitivity": { "alias": "wandSensitivity"; "required": false; }; "brushSize": { "alias": "brushSize"; "required": false; }; "vertexEraserRadius": { "alias": "vertexEraserRadius"; "required": false; }; "canUndo": { "alias": "canUndo"; "required": false; }; "canRedo": { "alias": "canRedo"; "required": false; }; "stackOptions": { "alias": "stackOptions"; "required": false; }; "tools": { "alias": "tools"; "required": false; }; "imageSmoothingEnabled": { "alias": "imageSmoothingEnabled"; "required": false; }; "samModels": { "alias": "samModels"; "required": false; }; "samModelId": { "alias": "samModelId"; "required": false; }; "yoloModels": { "alias": "yoloModels"; "required": false; }; "yoloModelId": { "alias": "yoloModelId"; "required": false; }; "retinalModels": { "alias": "retinalModels"; "required": false; }; "retinalModelId": { "alias": "retinalModelId"; "required": false; }; }, { "selectPlotType": "selectPlotType"; "addProfileLine": "addProfileLine"; "toggleImageSmoothing": "toggleImageSmoothing"; "isoRangeChange": "isoRangeChange"; "openChannelHistogram": "openChannelHistogram"; "openRegionEditor": "openRegionEditor"; "selectStackOption": "selectStackOption"; "zScrub": "zScrub"; "zSlide": "zSlide"; "zIndexInput": "zIndexInput"; "reloadAndPlot": "reloadAndPlot"; "downloadImage": "downloadImage"; "autoscaleImage": "autoscaleImage"; "toggleDragMode": "toggleDragMode"; "zoomIn": "zoomIn"; "zoomOut": "zoomOut"; "toggleSurface3dMode": "toggleSurface3dMode"; "resetSurfaceCamera": "resetSurfaceCamera"; "toggleAxes": "toggleAxes"; "toggleWireframe": "toggleWireframe"; "selectResolution": "selectResolution"; "deleteRegion": "deleteRegion"; "undoRegion": "undoRegion"; "redoRegion": "redoRegion"; "segmentRegions": "segmentRegions"; "segmentCellpose": "segmentCellpose"; "detectYolo": "detectYolo"; "yoloModelChange": "yoloModelChange"; "openYoloParams": "openYoloParams"; "segmentRetinal": "segmentRetinal"; "retinalModelChange": "retinalModelChange"; "openRetinalParams": "openRetinalParams"; "samModelChange": "samModelChange"; "wandSensitivityChange": "wandSensitivityChange"; "brushSizeChange": "brushSizeChange"; "vertexEraserRadiusChange": "vertexEraserRadiusChange"; }, never, ["*"], false, never>;
217
+ static ɵcmp: i0.ɵɵComponentDeclaration<ToolbarComponent, "plotting-toolbar", never, { "imageInfo": { "alias": "imageInfo"; "required": false; }; "plotTypeOptions": { "alias": "plotTypeOptions"; "required": false; }; "selectedPlotType": { "alias": "selectedPlotType"; "required": false; }; "isoRange": { "alias": "isoRange"; "required": false; }; "maxIndex": { "alias": "maxIndex"; "required": false; }; "zIndex": { "alias": "zIndex"; "required": false; }; "isHeatmap": { "alias": "isHeatmap"; "required": false; }; "activeDragMode": { "alias": "activeDragMode"; "required": false; }; "activeSurface3dMode": { "alias": "activeSurface3dMode"; "required": false; }; "axesVisible": { "alias": "axesVisible"; "required": false; }; "wireframeActive": { "alias": "wireframeActive"; "required": false; }; "resolutionScale": { "alias": "resolutionScale"; "required": false; }; "wandSensitivity": { "alias": "wandSensitivity"; "required": false; }; "brushSize": { "alias": "brushSize"; "required": false; }; "vertexEraserRadius": { "alias": "vertexEraserRadius"; "required": false; }; "canUndo": { "alias": "canUndo"; "required": false; }; "canRedo": { "alias": "canRedo"; "required": false; }; "stackOptions": { "alias": "stackOptions"; "required": false; }; "tools": { "alias": "tools"; "required": false; }; "imageSmoothingEnabled": { "alias": "imageSmoothingEnabled"; "required": false; }; "samModels": { "alias": "samModels"; "required": false; }; "samModelId": { "alias": "samModelId"; "required": false; }; "contributedTools": { "alias": "contributedTools"; "required": false; }; "toolModelIds": { "alias": "toolModelIds"; "required": false; }; }, { "selectPlotType": "selectPlotType"; "addProfileLine": "addProfileLine"; "toggleImageSmoothing": "toggleImageSmoothing"; "isoRangeChange": "isoRangeChange"; "openChannelHistogram": "openChannelHistogram"; "openRegionEditor": "openRegionEditor"; "selectStackOption": "selectStackOption"; "zScrub": "zScrub"; "zSlide": "zSlide"; "zIndexInput": "zIndexInput"; "reloadAndPlot": "reloadAndPlot"; "downloadImage": "downloadImage"; "autoscaleImage": "autoscaleImage"; "toggleDragMode": "toggleDragMode"; "zoomIn": "zoomIn"; "zoomOut": "zoomOut"; "toggleSurface3dMode": "toggleSurface3dMode"; "resetSurfaceCamera": "resetSurfaceCamera"; "toggleAxes": "toggleAxes"; "toggleWireframe": "toggleWireframe"; "selectResolution": "selectResolution"; "deleteRegion": "deleteRegion"; "undoRegion": "undoRegion"; "redoRegion": "redoRegion"; "segmentRegions": "segmentRegions"; "segmentCellpose": "segmentCellpose"; "runTool": "runTool"; "toolModelChange": "toolModelChange"; "openToolParams": "openToolParams"; "samModelChange": "samModelChange"; "wandSensitivityChange": "wandSensitivityChange"; "brushSizeChange": "brushSizeChange"; "vertexEraserRadiusChange": "vertexEraserRadiusChange"; }, never, ["*"], false, never>;
195
218
  }
@@ -12,10 +12,7 @@ import { IVisualizer } from './contracts/visualizer.contract';
12
12
  import { SamToolService } from './toolbar/segmentation/sam-tool.service';
13
13
  import { SamPointToolService } from './toolbar/segmentation/sam-point-tool.service';
14
14
  import { CellSegmentToolService } from './toolbar/segmentation/cell-segment-tool.service';
15
- import { YoloDetectToolService } from './toolbar/segmentation/yolo-detect-tool.service';
16
- import type { IInstanceSegmenter, InstanceSegmentOptions } from './contracts/instance-segmenter.contract';
17
- import { RetinalLayerToolService } from './toolbar/segmentation/retinal-layer-tool.service';
18
- import type { ISemanticSegmenter, SemanticSegmentOptions } from './contracts/semantic-segmenter.contract';
15
+ import { NumberParamSpec, SelectParamSpec, ToolParamSpec, ToolbarToolContribution } from './contracts/toolbar-tool.contract';
19
16
  import { ToolbarToolVisibility } from './contracts/toolbar-config';
20
17
  import { VizConfig } from './contracts/viz-config';
21
18
  import * as i0 from "@angular/core";
@@ -28,10 +25,6 @@ export declare class VisualizerComponent implements OnInit, OnChanges, AfterView
28
25
  private session;
29
26
  private samTool;
30
27
  private cellSegmentTool;
31
- private yoloDetectTool;
32
- private instanceSegmenter;
33
- private semanticSegmenter;
34
- private retinalLayerTool;
35
28
  private samPointTool;
36
29
  private regionOps;
37
30
  private vizConfig?;
@@ -131,28 +124,22 @@ export declare class VisualizerComponent implements OnInit, OnChanges, AfterView
131
124
  label: string;
132
125
  }[];
133
126
  samModelId: string;
134
- /** YOLO checkpoints with a configured URL, and the active one. */
135
- yoloModels: {
136
- id: string;
137
- label: string;
138
- }[];
139
- yoloModelId: string;
140
- /** Parameter dialog state. Seeded from the active model's own defaults, which
141
- * encode the scale and crowding each checkpoint was trained for. */
142
- showYoloParams: boolean;
143
- yoloParams: InstanceSegmentOptions;
144
- /** Retinal-layer checkpoints with a configured URL, and the active one.
145
- * Only VNet ships — see the registry for why the ResUNet-a entries are off. */
146
- retinalModels: {
147
- id: string;
148
- label: string;
149
- }[];
150
- retinalModelId: string;
151
- showRetinalParams: boolean;
152
- retinalParams: SemanticSegmentOptions;
153
- /** Download size of the active retinal model, surfaced in the dialog because
154
- * ~590 MB is worth knowing before clicking Run rather than after. */
155
- get retinalModelSizeMb(): number;
127
+ /**
128
+ * No-prompt tools registered through {@link TOOLBAR_TOOLS}, filtered to those
129
+ * with configured models and sorted. This library registers none — a host
130
+ * supplies them — so an open build has an empty array here and the toolbar
131
+ * and help dialog simply omit that group.
132
+ */
133
+ contributedTools: ToolbarToolContribution[];
134
+ /** Active checkpoint per contributed tool. */
135
+ toolModelIds: Record<string, string>;
136
+ /**
137
+ * Parameter values per contributed tool, seeded from each tool's own defaults
138
+ * — which encode the scale and crowding its checkpoint was trained for.
139
+ */
140
+ toolParams: Record<string, Record<string, unknown>>;
141
+ /** Which tool's parameter dialog is open, or null. */
142
+ openParamsToolId: string | null;
156
143
  /** SAM download/segment toast state (bound by the `sam` p-toast template). */
157
144
  samStatus: string;
158
145
  samProgress: number;
@@ -263,7 +250,7 @@ export declare class VisualizerComponent implements OnInit, OnChanges, AfterView
263
250
  private wheelListener?;
264
251
  /** Debounced z-slice scrubbing (see SliceScrubber — refactoring plan Step 7). */
265
252
  private readonly scrubber;
266
- constructor(state: ImageStatePort, plotService: IVisualizer, messageService: MessageService, ngZone: NgZone, cdr: ChangeDetectorRef, session: VisualizerStore, samTool: SamToolService, cellSegmentTool: CellSegmentToolService, yoloDetectTool: YoloDetectToolService, instanceSegmenter: IInstanceSegmenter, semanticSegmenter: ISemanticSegmenter, retinalLayerTool: RetinalLayerToolService, samPointTool: SamPointToolService, regionOps: RegionOpsService, vizConfig?: VizConfig | undefined);
253
+ constructor(state: ImageStatePort, plotService: IVisualizer, messageService: MessageService, ngZone: NgZone, cdr: ChangeDetectorRef, session: VisualizerStore, samTool: SamToolService, cellSegmentTool: CellSegmentToolService, samPointTool: SamPointToolService, regionOps: RegionOpsService, vizConfig?: VizConfig | undefined, toolContributions?: readonly ToolbarToolContribution[]);
267
254
  /**
268
255
  * Plot types offered in the selector for the current image:
269
256
  * - Outside **test mode**, only the curated default set (descriptors with a
@@ -411,37 +398,41 @@ export declare class VisualizerComponent implements OnInit, OnChanges, AfterView
411
398
  * (jit-ui#90). Each box is cropped (browser slide-crop) then run through the
412
399
  * cellpose-js model; the same sticky `sam` toast + progress bar is reused. */
413
400
  segmentCellpose(): Promise<void>;
414
- /** Per-model defaults from the registry — what the checkpoint was trained for. */
415
- private defaultYoloParams;
401
+ /**
402
+ * Parameters currently bound by the generic parameter dialog, for whichever
403
+ * tool is open. Seeded lazily: a tool's defaults are only asked for when the
404
+ * user first touches it, so registering a tool costs nothing until used.
405
+ */
406
+ paramsFor(toolId: string): Record<string, unknown>;
407
+ /** Active checkpoint for a tool, falling back to the tool's own default. */
408
+ modelIdFor(tool: ToolbarToolContribution): string;
416
409
  /** Switching checkpoint re-seeds the parameters, since the defaults belong to
417
410
  * the model rather than to the tool. */
418
- onYoloModelChange(id: string): void;
419
- openYoloParams(): void;
420
- resetYoloParams(): void;
421
- /** Detect objects across the current view. No prompt: a detector finds objects
422
- * in a field rather than being pointed at one. Reuses the shared segmentation
423
- * toast so progress and status read the same as the SAM/cellpose tools. */
424
- detectYolo(): Promise<void>;
411
+ onToolModelChange(e: {
412
+ toolId: string;
413
+ modelId: string;
414
+ }): void;
415
+ /** A tool's defaults for a checkpoint, with that checkpoint's own overrides
416
+ * applied on top — per-model defaults beat per-tool ones. */
417
+ private seedParams;
425
418
  /**
426
- * Defaults for the retinal tool.
419
+ * Narrow a param spec for the template.
427
420
  *
428
- * `classThreshold` is 0.5 to match the reference worker, which thresholds each
429
- * class channel independently rather than taking an argmax — with a softmax
430
- * output that makes it argmax plus a confidence floor.
431
- *
432
- * `downsamplingFactor` is 1 rather than YOLO's 5: these checkpoints were
433
- * trained on native 40x patches, so the model wants full resolution and
434
- * downsampling actively hurts.
421
+ * `*ngSwitchCase` does not narrow a discriminated union in Angular's template
422
+ * type checker the way a `switch` does in TypeScript, so the template asks for
423
+ * the variant it has already matched on.
435
424
  */
436
- private defaultRetinalParams;
437
- /** Switching checkpoint re-seeds the parameters, since the defaults belong to
438
- * the model rather than to the tool. */
439
- onRetinalModelChange(id: string): void;
440
- openRetinalParams(): void;
441
- resetRetinalParams(): void;
442
- /** Segment retinal layers across the current view. Like YOLO this takes no
443
- * prompt — it labels every pixel — and reuses the shared segmentation toast. */
444
- segmentRetinal(): Promise<void>;
425
+ numberParam(p: ToolParamSpec): NumberParamSpec;
426
+ selectParam(p: ToolParamSpec): SelectParamSpec;
427
+ openToolParams(toolId: string): void;
428
+ /** The tool whose parameter dialog is open, for the generic dialog's header
429
+ * and field list. */
430
+ get openParamsTool(): ToolbarToolContribution | undefined;
431
+ resetToolParams(toolId: string): void;
432
+ /** Run a contributed tool over the current view. No prompt: these sweep the
433
+ * whole view rather than being pointed at something. Reuses the shared
434
+ * segmentation toast so progress reads the same as the SAM/cellpose tools. */
435
+ runTool(toolId: string): Promise<void>;
445
436
  /** Shared driver for the box-prompt segment tools: wires the tool's status +
446
437
  * download progress into the sticky `sam` toast, runs `op`, and reports the
447
438
  * region count. Keeps the toast open until the run settles (bar hits 100%). */
@@ -485,6 +476,6 @@ export declare class VisualizerComponent implements OnInit, OnChanges, AfterView
485
476
  onSelectPlotType(type: PlotType): void;
486
477
  /** Deactivate whatever tool is armed and clear every tool mode. */
487
478
  private deactivateActiveTool;
488
- static ɵfac: i0.ɵɵFactoryDeclaration<VisualizerComponent, [null, null, null, null, null, null, null, null, null, null, null, null, null, null, { optional: true; }]>;
479
+ static ɵfac: i0.ɵɵFactoryDeclaration<VisualizerComponent, [null, null, null, null, null, null, null, null, null, null, { optional: true; }, { optional: true; }]>;
489
480
  static ɵcmp: i0.ɵɵComponentDeclaration<VisualizerComponent, "visualizer", never, { "toolbarTools": { "alias": "toolbarTools"; "required": false; }; "testMode": { "alias": "testMode"; "required": false; }; }, { "isStackEvent": "isStackEvent"; "isGrayscaleEvent": "isGrayscaleEvent"; }, never, ["*"], false, never>;
490
481
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jax-data-science/sci-image-visualizer",
3
- "version": "0.2.18",
3
+ "version": "0.3.0",
4
4
  "description": "Browser-based scientific image visualization for Angular — gigapixel whole-slide (OpenSeadragon), 2D/3D (Plotly) and WebGPU (napari-js) rendering, with region/annotation editing and in-browser SAM segmentation.",
5
5
  "keywords": [
6
6
  "angular",
@@ -36,12 +36,10 @@
36
36
  "dependencies": {
37
37
  "cellpose-js": "^0.4.1",
38
38
  "fast-png": "^6.4.0",
39
- "jax-ai-js": "^0.1.1",
40
39
  "napari-js": "^0.11.0",
41
40
  "openseadragon": "^6.0.2",
42
41
  "plotly.js-dist-min": "^3.0.1",
43
- "tslib": "^2.3.0",
44
- "yolo-segdetect-js": "^0.1.2"
42
+ "tslib": "^2.3.0"
45
43
  },
46
44
  "peerDependencies": {
47
45
  "@angular/animations": "^17.3.0",
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ version="1.1"
4
+ viewBox="-5 -10 79.999863 79.999832"
5
+ id="svg2"
6
+ sodipodi:docname="cell-detection.svg"
7
+ width="79.999863"
8
+ height="79.999832"
9
+ inkscape:version="1.4.4 (dcaf3e7, 2026-05-05)"
10
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ xmlns:svg="http://www.w3.org/2000/svg">
14
+ <defs
15
+ id="defs2" />
16
+ <sodipodi:namedview
17
+ id="namedview2"
18
+ pagecolor="#ffffff"
19
+ bordercolor="#000000"
20
+ borderopacity="0.25"
21
+ inkscape:showpageshadow="2"
22
+ inkscape:pageopacity="0.0"
23
+ inkscape:pagecheckerboard="0"
24
+ inkscape:deskcolor="#d1d1d1"
25
+ inkscape:zoom="7.4740741"
26
+ inkscape:cx="40.004955"
27
+ inkscape:cy="47.56442"
28
+ inkscape:window-width="1024"
29
+ inkscape:window-height="1186"
30
+ inkscape:window-x="0"
31
+ inkscape:window-y="30"
32
+ inkscape:window-maximized="0"
33
+ inkscape:current-layer="svg2" />
34
+ <path
35
+ d="m 3.74574,-1.255 c 11.664,-11.66 30.57,-11.66 42.234,0 9.9609,9.9648 11.41,25.215 4.3516,36.719 l 5.4609,5.4609 c 2.1211,0.02344 4.2422,0.73828 6.0039,2.1602 l 9.4961,7.6562 c 4.6094,3.7148 4.9766,10.609 0.78906,14.797 l -1.543,1.543 c -4.1875,4.1875 -11.082,3.8203 -14.797,-0.78906 l -7.6562,-9.4961 c -1.4219,-1.7617 -2.1367,-3.8867 -2.1602,-6.0078 l -5.4609,-5.457 c -11.504,7.0586 -26.754,5.6094 -36.719,-4.3516 -11.66,-11.664 -11.66,-30.57 0,-42.234 z m 55.539,47.453 c -2.2852,-1.8477 -5.5977,-1.668 -7.6758,0.41016 -2.0781,2.07816 -2.2578,5.3867 -0.41016,7.6758 l 7.6562,9.4961 c 2.2227,2.7578 6.3477,2.9766 8.8555,0.47266 l 1.543,-1.543 c 2.5039,-2.5078 2.2852,-6.6328 -0.47266,-8.8555 z m -11.305,-7.4297 c -0.625,0.76172 -1.2891,1.5 -2,2.2109 -0.71094,0.70703 -1.4492,1.375 -2.2109,1.9961 l 3.2344,3.2344 c 0.45312,-0.875 1.043,-1.6992 1.7773,-2.4297 0.73047,-0.73438 1.5547,-1.3242 2.4336,-1.7773 z m -4.8281,-37.195 c -10.102,-10.098 -26.477,-10.098 -36.578,0 -10.098,10.102 -10.098,26.477 0,36.574 10.102,10.102 26.477,10.102 36.578,0 10.098,-10.098 10.098,-26.473 0,-36.574 z M 25.56964,-0.79 c 0.98828,-0.49609 2.1875,-0.09375 2.6836,0.89453 0.42578,0.85156 0.18359,1.8594 -0.52344,2.4375 0.92188,0.15234 1.8164,0.375 2.6797,0.66797 1.1055,-1.4023 2.7461,-2.3125 4.6016,-2.3086 1.1055,0 2,0.89844 1.9961,2 0,1.1055 -0.89453,2 -2,2 -0.24219,0 -0.50391,0.0625 -0.76172,0.19141 0.65625,0.4375 1.2812,0.91406 1.8672,1.4336 1.8555,-1.0742 4.1367,-1.2266 6.0391,-0.125 0.95312,0.55469 1.2773,1.7773 0.72656,2.7344 -0.55469,0.95312 -1.7812,1.2812 -2.7344,0.72656 -0.32812,-0.19141 -0.76953,-0.27344 -1.2578,-0.17578 0.42969,0.63672 0.82031,1.3086 1.1602,2.0039 2.0352,0.25 3.8711,1.4062 4.7656,3.3164 0.46484,1.0039 0.03516,2.1914 -0.96484,2.6602 -0.74609,0.34766 -1.6016,0.19531 -2.1797,-0.32031 0.05859,0.56641 0.08984,1.1406 0.08984,1.7227 0,0.26563 -0.0078,0.52734 -0.01953,0.78906 1.4531,1.1133 2.4023,2.7891 2.4023,4.6875 -0.0039,1.1016 -0.89844,1.9961 -2.0039,1.9961 -0.71875,0 -1.3477,-0.37891 -1.6992,-0.94922 -0.27734,0.66016 -0.59766,1.2969 -0.95703,1.9102 1.1133,1.0859 1.8125,2.5469 1.8086,4.1758 0,1.1016 -0.89844,1.9961 -2,1.9961 -1.1055,0 -2,-0.89844 -2,-2.0039 0,-0.29297 -0.08984,-0.61328 -0.28125,-0.91797 -0.64062,0.66016 -1.332,1.2656 -2.0742,1.8086 0.71094,0.96484 1.1367,2.1289 1.1367,3.3945 -0.0039,1.1055 -0.89844,2 -2.0039,2 -1.1055,-0.0039 -2,-0.89844 -1.9961,-2.0039 0,-0.45312 -0.21094,-0.96875 -0.69141,-1.3906 -1.3281,0.54688 -2.7422,0.92578 -4.2148,1.1133 -0.29297,1.082 -0.88281,2.0742 -1.7656,2.8398 -0.83984,0.71875 -2.1016,0.625 -2.8203,-0.21094 -0.67578,-0.78125 -0.63672,-1.9297 0.05078,-2.668 -1.25,-0.17969 -2.457,-0.5 -3.6016,-0.94141 -0.6875,1.3008 -1.8516,2.3047 -3.332,2.7891 -1.0508,0.34375 -2.1797,-0.22656 -2.5234,-1.2773 -0.3437,-1.05074 0.22656,-2.1797 1.2773,-2.5234 0.47266,-0.15625 0.83594,-0.45703 1.0469,-0.86719 -0.88281,-0.61719 -1.707,-1.3203 -2.457,-2.0938 -0.76172,0.96094 -1.8008,1.6992 -3.043,2.1055 -1.0508,0.34375 -2.1797,-0.23047 -2.5234,-1.2812 -0.34375,-1.0469 0.23047,-2.1797 1.2773,-2.5234 0.88672,-0.28906 1.4844,-0.97656 1.6367,-1.8906 -0.38672,-0.70312 -0.72266,-1.4336 -1.0078,-2.1953 -0.76562,0.33203 -1.6133,0.49219 -2.5,0.4375 -1.1016,-0.06641 -1.9414,-1.0156 -1.875,-2.1172 0.06641,-1.1016 1.0156,-1.9414 2.1172,-1.875 0.53906,0.03125 0.99609,-0.19531 1.2852,-0.64844 -0.05469,-0.54688 -0.08203,-1.1016 -0.08203,-1.6641 0,-0.56641 0.02734,-1.1289 0.08203,-1.6797 -1.293,-0.44531 -2.4258,-1.3086 -3.1406,-2.5547 -0.55078,-0.95703 -0.21875,-2.1797 0.73828,-2.7305 0.95703,-0.55078 2.1797,-0.21875 2.7305,0.73828 0.125,0.21875 0.31641,0.41797 0.56641,0.57422 0.27344,-0.76953 0.60547,-1.5117 0.98438,-2.2227 -1.3125,-1.4922 -1.918,-3.5078 -1.3828,-5.4844 0.28516,-1.0664 1.3828,-1.6992 2.4492,-1.4141 1.0664,0.28906 1.6992,1.3867 1.4102,2.4531 -0.07422,0.27344 -0.07031,0.59766 0.02734,0.92969 0.75391,-0.8125 1.5859,-1.5508 2.4844,-2.1992 -0.38281,-1.9414 0.10156,-3.9766 1.543,-5.4219 0.78125,-0.77734 2.0469,-0.77734 2.8281,0.0039 0.7812,0.781246 0.78125,2.0469 0,2.8281 -0.12109,0.11719 -0.22266,0.26562 -0.30469,0.4375 1.1016,-0.40234 2.2617,-0.6875 3.457,-0.85156 0.42188,-1.3945 1.3945,-2.5781 2.7734,-3.2656 z m -0.15625,26.992 c 0,-0.14453 0.01563,-0.28516 0.04688,-0.42188 -0.13672,0.02734 -0.27734,0.04297 -0.42188,0.04297 -0.20312,0 -0.37109,0.16797 -0.37109,0.37891 0,0.21094 0.16797,0.37891 0.37109,0.37891 0.20313,0 0.375,-0.16797 0.375,-0.37891 z m -6.1719,-8.5586 c 0,-0.21484 -0.17188,-0.37891 -0.375,-0.37891 -0.20313,0 -0.37109,0.16406 -0.37109,0.37891 0,0.21094 0.16797,0.375 0.37109,0.375 0.20312,0 0.375,-0.16406 0.375,-0.375 z m 7.8828,-7.1211 c 2.0938,-1.2109 4.7695,-0.48828 5.9766,1.6055 1.207,2.0898 0.49219,4.7656 -1.6016,5.9766 -0.95703,0.55469 -2.1797,0.22656 -2.7305,-0.73047 -0.55469,-0.95703 -0.22656,-2.1797 0.73047,-2.7305 0.17578,-0.10547 0.24219,-0.33594 0.13672,-0.51953 -0.10547,-0.17969 -0.33203,-0.23828 -0.50781,-0.13672 -0.95703,0.55078 -2.1797,0.22656 -2.7344,-0.73047 -0.55078,-0.95703 -0.22656,-2.1797 0.73047,-2.7344 z m 2.2891,15.68 c 0,2.4141 -1.957,4.3789 -4.375,4.3789 -2.418,0 -4.3711,-1.9648 -4.3711,-4.3789 0,-2.4141 1.9531,-4.3789 4.3711,-4.3789 1.1055,0 2,0.89844 2,2 0,0.14453 -0.01562,0.28906 -0.04687,0.42188 0.13672,-0.02734 0.27734,-0.04297 0.42187,-0.04297 1.1055,0 2,0.89453 2,2 z m -17.09,-7.1328 c 0,7.0391 5.6953,12.742 12.715,12.742 7.0195,0 12.719,-5.7031 12.719,-12.742 0,-7.0391 -5.6992,-12.742 -12.719,-12.742 -7.0195,0 -12.715,5.7031 -12.715,12.742 z m 10.918,-1.4258 c 0,2.4141 -1.957,4.375 -4.375,4.375 -2.418,0 -4.3711,-1.9609 -4.3711,-4.375 0,-2.4141 1.9531,-4.3789 4.3711,-4.3789 2.418,0 4.375,1.9648 4.375,4.3789 z"
36
+ id="path1" />
37
+ </svg>