@jax-data-science/sci-image-visualizer 0.1.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/LICENSE +21 -0
- package/README.md +404 -0
- package/esm2022/index.mjs +52 -0
- package/esm2022/jax-data-science-sci-image-visualizer.mjs +5 -0
- package/esm2022/lib/channel-histogram/channel-histogram.component.mjs +398 -0
- package/esm2022/lib/contracts/capabilities.contract.mjs +32 -0
- package/esm2022/lib/contracts/cell-segmenter.contract.mjs +12 -0
- package/esm2022/lib/contracts/channel-histogram-api.contract.mjs +14 -0
- package/esm2022/lib/contracts/colormap-lut.mjs +95 -0
- package/esm2022/lib/contracts/coordinate-transform.contract.mjs +2 -0
- package/esm2022/lib/contracts/display-types.mjs +8 -0
- package/esm2022/lib/contracts/image.contract.mjs +11 -0
- package/esm2022/lib/contracts/intensity.mjs +28 -0
- package/esm2022/lib/contracts/plot-type.mjs +112 -0
- package/esm2022/lib/contracts/ports/image-state.port.mjs +3 -0
- package/esm2022/lib/contracts/ports/preferences.port.mjs +3 -0
- package/esm2022/lib/contracts/ports/region-io.port.mjs +3 -0
- package/esm2022/lib/contracts/ports/tile-access.port.mjs +3 -0
- package/esm2022/lib/contracts/region-editor-api.contract.mjs +4 -0
- package/esm2022/lib/contracts/region-overlay.contract.mjs +2 -0
- package/esm2022/lib/contracts/region-store.contract.mjs +2 -0
- package/esm2022/lib/contracts/sam.contract.mjs +12 -0
- package/esm2022/lib/contracts/toolbar-config.mjs +8 -0
- package/esm2022/lib/contracts/visualizer.contract.mjs +10 -0
- package/esm2022/lib/contracts/viz-config.mjs +3 -0
- package/esm2022/lib/hex-color-picker/hex-color-picker.component.mjs +260 -0
- package/esm2022/lib/implementations/base-store-visualizer.mjs +76 -0
- package/esm2022/lib/implementations/napari-js/napari-axes-labels.mjs +106 -0
- package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +847 -0
- package/esm2022/lib/implementations/napari-js/napari-scale-bar.mjs +92 -0
- package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +2283 -0
- package/esm2022/lib/implementations/napari-js/napari-volume-z-handle.mjs +126 -0
- package/esm2022/lib/implementations/osd/display-pipeline.mjs +143 -0
- package/esm2022/lib/implementations/osd/histogram-sampler.mjs +217 -0
- package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +1780 -0
- package/esm2022/lib/implementations/osd/osd-coordinate-transform.mjs +25 -0
- package/esm2022/lib/implementations/osd/osd-coords.mjs +53 -0
- package/esm2022/lib/implementations/osd/osd-lib.mjs +20 -0
- package/esm2022/lib/implementations/osd/osd-region-overlay.mjs +1237 -0
- package/esm2022/lib/implementations/osd/osd-scale-bar.mjs +88 -0
- package/esm2022/lib/implementations/osd/slice-cache.mjs +488 -0
- package/esm2022/lib/implementations/osd/tile-client.mjs +29 -0
- package/esm2022/lib/implementations/plotly/plotly-coordinate-transform.mjs +37 -0
- package/esm2022/lib/implementations/plotly/plotly-region-overlay.mjs +31 -0
- package/esm2022/lib/implementations/plotly/plotly-trace-builders.mjs +183 -0
- package/esm2022/lib/implementations/plotly/plotly.service.mjs +2137 -0
- package/esm2022/lib/implementations/simple-slice-access.service.mjs +99 -0
- package/esm2022/lib/models/bezier.mjs +120 -0
- package/esm2022/lib/models/class-preset.mjs +46 -0
- package/esm2022/lib/models/geometry.mjs +198 -0
- package/esm2022/lib/models/region.mjs +166 -0
- package/esm2022/lib/models/shape.mjs +53 -0
- package/esm2022/lib/plot.utilities.mjs +693 -0
- package/esm2022/lib/processing/image-converter.service.mjs +90 -0
- package/esm2022/lib/processing/image-crop.mjs +62 -0
- package/esm2022/lib/processing/processing-image.mjs +50 -0
- package/esm2022/lib/provide-visualization.mjs +63 -0
- package/esm2022/lib/region-editor/mask-raster.mjs +95 -0
- package/esm2022/lib/region-editor/mask-worker.mjs +10 -0
- package/esm2022/lib/region-editor/region-editor.component.mjs +1190 -0
- package/esm2022/lib/region-ops.service.mjs +301 -0
- package/esm2022/lib/render-orchestrator.mjs +87 -0
- package/esm2022/lib/routing-visualizer.service.mjs +533 -0
- package/esm2022/lib/store/class-color.util.mjs +52 -0
- package/esm2022/lib/store/region-store.service.mjs +1029 -0
- package/esm2022/lib/store/visualizer-store.service.mjs +352 -0
- package/esm2022/lib/toolbar/brush/brush-tool.service.mjs +546 -0
- package/esm2022/lib/toolbar/crop/slide-crop.mjs +57 -0
- package/esm2022/lib/toolbar/segmentation/cell-segment-tool.service.mjs +115 -0
- package/esm2022/lib/toolbar/segmentation/cellpose-segmenter.service.mjs +84 -0
- package/esm2022/lib/toolbar/segmentation/onnx-sam-session.mjs +133 -0
- package/esm2022/lib/toolbar/segmentation/ort-runtime-config.mjs +31 -0
- package/esm2022/lib/toolbar/segmentation/sam-model-registry.mjs +108 -0
- package/esm2022/lib/toolbar/segmentation/sam-onnx-core.mjs +159 -0
- package/esm2022/lib/toolbar/segmentation/sam-point-tool.service.mjs +255 -0
- package/esm2022/lib/toolbar/segmentation/sam-prompt.mjs +94 -0
- package/esm2022/lib/toolbar/segmentation/sam-tool.service.mjs +188 -0
- package/esm2022/lib/toolbar/toolbar.component.mjs +311 -0
- package/esm2022/lib/toolbar/vertex-eraser/vertex-eraser-tool.service.mjs +234 -0
- package/esm2022/lib/toolbar/wand/wand-tool.service.mjs +499 -0
- package/esm2022/lib/toolbar/wand/wand.service.mjs +921 -0
- package/esm2022/lib/toolbar/zoom-to-box/zoom-to-box-tool.service.mjs +157 -0
- package/esm2022/lib/visualization.module.mjs +162 -0
- package/esm2022/lib/visualizer.component.mjs +1597 -0
- package/fesm2022/jax-data-science-sci-image-visualizer-mask-worker-uhepXUhM.mjs +12 -0
- package/fesm2022/jax-data-science-sci-image-visualizer-mask-worker-uhepXUhM.mjs.map +1 -0
- package/fesm2022/jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs +327 -0
- package/fesm2022/jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs.map +1 -0
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +21314 -0
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -0
- package/fesm2022/mask.worker.js +989 -0
- package/fesm2022/onnx-sam.worker.js +202 -0
- package/index.d.ts +39 -0
- package/lib/channel-histogram/channel-histogram.component.d.ts +112 -0
- package/lib/contracts/capabilities.contract.d.ts +30 -0
- package/lib/contracts/cell-segmenter.contract.d.ts +40 -0
- package/lib/contracts/channel-histogram-api.contract.d.ts +98 -0
- package/lib/contracts/colormap-lut.d.ts +21 -0
- package/lib/contracts/coordinate-transform.contract.d.ts +43 -0
- package/lib/contracts/display-types.d.ts +41 -0
- package/lib/contracts/image.contract.d.ts +72 -0
- package/lib/contracts/intensity.d.ts +18 -0
- package/lib/contracts/plot-type.d.ts +108 -0
- package/lib/contracts/ports/image-state.port.d.ts +29 -0
- package/lib/contracts/ports/preferences.port.d.ts +19 -0
- package/lib/contracts/ports/region-io.port.d.ts +29 -0
- package/lib/contracts/ports/tile-access.port.d.ts +27 -0
- package/lib/contracts/region-editor-api.contract.d.ts +83 -0
- package/lib/contracts/region-overlay.contract.d.ts +47 -0
- package/lib/contracts/region-store.contract.d.ts +74 -0
- package/lib/contracts/sam.contract.d.ts +98 -0
- package/lib/contracts/toolbar-config.d.ts +24 -0
- package/lib/contracts/visualizer.contract.d.ts +346 -0
- package/lib/contracts/viz-config.d.ts +25 -0
- package/lib/hex-color-picker/hex-color-picker.component.d.ts +47 -0
- package/lib/implementations/base-store-visualizer.d.ts +71 -0
- package/lib/implementations/napari-js/napari-axes-labels.d.ts +47 -0
- package/lib/implementations/napari-js/napari-region-overlay.d.ts +115 -0
- package/lib/implementations/napari-js/napari-scale-bar.d.ts +31 -0
- package/lib/implementations/napari-js/napari-visualizer.service.d.ts +455 -0
- package/lib/implementations/napari-js/napari-volume-z-handle.d.ts +46 -0
- package/lib/implementations/osd/display-pipeline.d.ts +47 -0
- package/lib/implementations/osd/histogram-sampler.d.ts +85 -0
- package/lib/implementations/osd/openseadragon-visualizer.service.d.ts +476 -0
- package/lib/implementations/osd/osd-coordinate-transform.d.ts +17 -0
- package/lib/implementations/osd/osd-coords.d.ts +16 -0
- package/lib/implementations/osd/osd-lib.d.ts +19 -0
- package/lib/implementations/osd/osd-region-overlay.d.ts +191 -0
- package/lib/implementations/osd/osd-scale-bar.d.ts +17 -0
- package/lib/implementations/osd/slice-cache.d.ts +161 -0
- package/lib/implementations/osd/tile-client.d.ts +32 -0
- package/lib/implementations/plotly/plotly-coordinate-transform.d.ts +19 -0
- package/lib/implementations/plotly/plotly-region-overlay.d.ts +23 -0
- package/lib/implementations/plotly/plotly-trace-builders.d.ts +60 -0
- package/lib/implementations/plotly/plotly.service.d.ts +594 -0
- package/lib/implementations/simple-slice-access.service.d.ts +64 -0
- package/lib/models/bezier.d.ts +59 -0
- package/lib/models/class-preset.d.ts +39 -0
- package/lib/models/geometry.d.ts +66 -0
- package/lib/models/region.d.ts +132 -0
- package/lib/models/shape.d.ts +44 -0
- package/lib/plot.utilities.d.ts +107 -0
- package/lib/processing/image-converter.service.d.ts +28 -0
- package/lib/processing/image-crop.d.ts +18 -0
- package/lib/processing/processing-image.d.ts +17 -0
- package/lib/provide-visualization.d.ts +34 -0
- package/lib/region-editor/mask-raster.d.ts +61 -0
- package/lib/region-editor/mask-worker.d.ts +7 -0
- package/lib/region-editor/region-editor.component.d.ts +305 -0
- package/lib/region-ops.service.d.ts +92 -0
- package/lib/render-orchestrator.d.ts +59 -0
- package/lib/routing-visualizer.service.d.ts +227 -0
- package/lib/store/class-color.util.d.ts +26 -0
- package/lib/store/region-store.service.d.ts +315 -0
- package/lib/store/visualizer-store.service.d.ts +123 -0
- package/lib/toolbar/brush/brush-tool.service.d.ts +143 -0
- package/lib/toolbar/crop/slide-crop.d.ts +30 -0
- package/lib/toolbar/segmentation/cell-segment-tool.service.d.ts +32 -0
- package/lib/toolbar/segmentation/cellpose-segmenter.service.d.ts +39 -0
- package/lib/toolbar/segmentation/onnx-sam-session.d.ts +25 -0
- package/lib/toolbar/segmentation/ort-runtime-config.d.ts +4 -0
- package/lib/toolbar/segmentation/sam-model-registry.d.ts +36 -0
- package/lib/toolbar/segmentation/sam-onnx-core.d.ts +48 -0
- package/lib/toolbar/segmentation/sam-point-tool.service.d.ts +59 -0
- package/lib/toolbar/segmentation/sam-prompt.d.ts +34 -0
- package/lib/toolbar/segmentation/sam-tool.service.d.ts +50 -0
- package/lib/toolbar/toolbar.component.d.ts +167 -0
- package/lib/toolbar/vertex-eraser/vertex-eraser-tool.service.d.ts +64 -0
- package/lib/toolbar/wand/wand-tool.service.d.ts +149 -0
- package/lib/toolbar/wand/wand.service.d.ts +133 -0
- package/lib/toolbar/zoom-to-box/zoom-to-box-tool.service.d.ts +49 -0
- package/lib/visualization.module.d.ts +45 -0
- package/lib/visualizer.component.d.ts +378 -0
- package/package.json +73 -0
- package/src/lib/assets/3d-coordinates.svg +49 -0
- package/src/lib/assets/cells.svg +1 -0
- package/src/lib/assets/click.svg +1 -0
- package/src/lib/assets/colormap-luts.json +1 -0
- package/src/lib/assets/contour.svg +48 -0
- package/src/lib/assets/cube-3d.svg +1 -0
- package/src/lib/assets/edit-selection-white.svg +47 -0
- package/src/lib/assets/heatmap.svg +1 -0
- package/src/lib/assets/icons/colormap-Pastel1.png +0 -0
- package/src/lib/assets/icons/colormap-accent.png +0 -0
- package/src/lib/assets/icons/colormap-afmhot.png +0 -0
- package/src/lib/assets/icons/colormap-autumn.png +0 -0
- package/src/lib/assets/icons/colormap-berlin.png +0 -0
- package/src/lib/assets/icons/colormap-binary.png +0 -0
- package/src/lib/assets/icons/colormap-blackbody.png +0 -0
- package/src/lib/assets/icons/colormap-bluered.png +0 -0
- package/src/lib/assets/icons/colormap-blues.png +0 -0
- package/src/lib/assets/icons/colormap-bone.png +0 -0
- package/src/lib/assets/icons/colormap-brbg.png +0 -0
- package/src/lib/assets/icons/colormap-brg.png +0 -0
- package/src/lib/assets/icons/colormap-bugn.png +0 -0
- package/src/lib/assets/icons/colormap-bupu.png +0 -0
- package/src/lib/assets/icons/colormap-bwr.png +0 -0
- package/src/lib/assets/icons/colormap-cividis.png +0 -0
- package/src/lib/assets/icons/colormap-cmrmap.png +0 -0
- package/src/lib/assets/icons/colormap-cool.png +0 -0
- package/src/lib/assets/icons/colormap-coolwarm.png +0 -0
- package/src/lib/assets/icons/colormap-copper.png +0 -0
- package/src/lib/assets/icons/colormap-cubehelix.png +0 -0
- package/src/lib/assets/icons/colormap-dark2.png +0 -0
- package/src/lib/assets/icons/colormap-earth.png +0 -0
- package/src/lib/assets/icons/colormap-electric.png +0 -0
- package/src/lib/assets/icons/colormap-flag.png +0 -0
- package/src/lib/assets/icons/colormap-gist_earth.png +0 -0
- package/src/lib/assets/icons/colormap-gist_gray.png +0 -0
- package/src/lib/assets/icons/colormap-gist_heat.png +0 -0
- package/src/lib/assets/icons/colormap-gist_ncar.png +0 -0
- package/src/lib/assets/icons/colormap-gist_rainbow.png +0 -0
- package/src/lib/assets/icons/colormap-gist_stern.png +0 -0
- package/src/lib/assets/icons/colormap-gist_yarg.png +0 -0
- package/src/lib/assets/icons/colormap-gnbu.png +0 -0
- package/src/lib/assets/icons/colormap-gnuplot.png +0 -0
- package/src/lib/assets/icons/colormap-gnuplot2.png +0 -0
- package/src/lib/assets/icons/colormap-gray.png +0 -0
- package/src/lib/assets/icons/colormap-greens-inv.png +0 -0
- package/src/lib/assets/icons/colormap-greens.png +0 -0
- package/src/lib/assets/icons/colormap-greys-inv.png +0 -0
- package/src/lib/assets/icons/colormap-greys.png +0 -0
- package/src/lib/assets/icons/colormap-hot.png +0 -0
- package/src/lib/assets/icons/colormap-hsv.png +0 -0
- package/src/lib/assets/icons/colormap-inferno.png +0 -0
- package/src/lib/assets/icons/colormap-jet.png +0 -0
- package/src/lib/assets/icons/colormap-magma.png +0 -0
- package/src/lib/assets/icons/colormap-managua.png +0 -0
- package/src/lib/assets/icons/colormap-nipy_spectral.png +0 -0
- package/src/lib/assets/icons/colormap-ocean.png +0 -0
- package/src/lib/assets/icons/colormap-oranges.png +0 -0
- package/src/lib/assets/icons/colormap-orrd.png +0 -0
- package/src/lib/assets/icons/colormap-pastel2.png +0 -0
- package/src/lib/assets/icons/colormap-picnic.png +0 -0
- package/src/lib/assets/icons/colormap-pink.png +0 -0
- package/src/lib/assets/icons/colormap-piyg.png +0 -0
- package/src/lib/assets/icons/colormap-placeholder.png +0 -0
- package/src/lib/assets/icons/colormap-plasma.png +0 -0
- package/src/lib/assets/icons/colormap-portland.png +0 -0
- package/src/lib/assets/icons/colormap-prgn.png +0 -0
- package/src/lib/assets/icons/colormap-prism.png +0 -0
- package/src/lib/assets/icons/colormap-pubu.png +0 -0
- package/src/lib/assets/icons/colormap-pubugn.png +0 -0
- package/src/lib/assets/icons/colormap-puor.png +0 -0
- package/src/lib/assets/icons/colormap-purd.png +0 -0
- package/src/lib/assets/icons/colormap-purples.png +0 -0
- package/src/lib/assets/icons/colormap-rainbow.png +0 -0
- package/src/lib/assets/icons/colormap-rdbu.png +0 -0
- package/src/lib/assets/icons/colormap-rdgy.png +0 -0
- package/src/lib/assets/icons/colormap-rdpu.png +0 -0
- package/src/lib/assets/icons/colormap-rdylbu.png +0 -0
- package/src/lib/assets/icons/colormap-rdylgn.png +0 -0
- package/src/lib/assets/icons/colormap-reds.png +0 -0
- package/src/lib/assets/icons/colormap-seismic.png +0 -0
- package/src/lib/assets/icons/colormap-set1.png +0 -0
- package/src/lib/assets/icons/colormap-set2.png +0 -0
- package/src/lib/assets/icons/colormap-set3.png +0 -0
- package/src/lib/assets/icons/colormap-spectral.png +0 -0
- package/src/lib/assets/icons/colormap-spring.png +0 -0
- package/src/lib/assets/icons/colormap-summer.png +0 -0
- package/src/lib/assets/icons/colormap-tab10.png +0 -0
- package/src/lib/assets/icons/colormap-tab20.png +0 -0
- package/src/lib/assets/icons/colormap-tab20b.png +0 -0
- package/src/lib/assets/icons/colormap-tab20c.png +0 -0
- package/src/lib/assets/icons/colormap-terrain.png +0 -0
- package/src/lib/assets/icons/colormap-turbo.png +0 -0
- package/src/lib/assets/icons/colormap-twilight.png +0 -0
- package/src/lib/assets/icons/colormap-twilight_shifted.png +0 -0
- package/src/lib/assets/icons/colormap-vanimo.png +0 -0
- package/src/lib/assets/icons/colormap-viridis.png +0 -0
- package/src/lib/assets/icons/colormap-winter.png +0 -0
- package/src/lib/assets/icons/colormap-wistia.png +0 -0
- package/src/lib/assets/icons/colormap-ylgn.png +0 -0
- package/src/lib/assets/icons/colormap-ylgnbu-inv.png +0 -0
- package/src/lib/assets/icons/colormap-ylgnbu.png +0 -0
- package/src/lib/assets/icons/colormap-ylorbr.png +0 -0
- package/src/lib/assets/icons/colormap-ylorrd-inv.png +0 -0
- package/src/lib/assets/icons/colormap-ylorrd.png +0 -0
- package/src/lib/assets/interactive-segmentation-cursor.svg +1 -0
- package/src/lib/assets/invert.svg +1 -0
- package/src/lib/assets/isosurface.svg +7 -0
- package/src/lib/assets/mask.svg +1 -0
- package/src/lib/assets/paintbrush.svg +1 -0
- package/src/lib/assets/picture-segmentation.svg +43 -0
- package/src/lib/assets/polygon-vertices.svg +13 -0
- package/src/lib/assets/polyline.svg +3 -0
- package/src/lib/assets/redo.svg +39 -0
- package/src/lib/assets/region-editor.svg +37 -0
- package/src/lib/assets/region-move.svg +7 -0
- package/src/lib/assets/smooth.svg +56 -0
- package/src/lib/assets/spline-chart.svg +7 -0
- package/src/lib/assets/surface.svg +37 -0
- package/src/lib/assets/test/download-379.svg +1 -0
- package/src/lib/assets/test/download-98.svg +1 -0
- package/src/lib/assets/test/json-1.svg +1 -0
- package/src/lib/assets/test/load-53.svg +1 -0
- package/src/lib/assets/test/mask-44.svg +1 -0
- package/src/lib/assets/test/mask-download-44.svg +15 -0
- package/src/lib/assets/test/mask-save-44.svg +11 -0
- package/src/lib/assets/test/overlay-1.svg +1 -0
- package/src/lib/assets/test/overlay-4.svg +1 -0
- package/src/lib/assets/test/region-26.svg +1 -0
- package/src/lib/assets/test/save-56.svg +1 -0
- package/src/lib/assets/test/upload-43.svg +1 -0
- package/src/lib/assets/to-bezier.svg +15 -0
- package/src/lib/assets/to-polygon.svg +11 -0
- package/src/lib/assets/undo.svg +38 -0
- package/src/lib/assets/vertex-add.svg +12 -0
- package/src/lib/assets/vertex-delete.svg +12 -0
- package/src/lib/assets/wand.svg +54 -0
- package/src/lib/assets/zoom-box.svg +10 -0
|
@@ -0,0 +1,989 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result)
|
|
9
|
+
__defProp(target, key, result);
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/lib/region-editor/mask.worker.ts
|
|
14
|
+
import { encode as encodePng } from "fast-png";
|
|
15
|
+
|
|
16
|
+
// src/lib/toolbar/wand/wand.service.ts
|
|
17
|
+
import { Injectable } from "@angular/core";
|
|
18
|
+
|
|
19
|
+
// src/lib/models/region.ts
|
|
20
|
+
var Polygon = class {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.npoints = 0;
|
|
23
|
+
this.xpoints = [];
|
|
24
|
+
this.ypoints = [];
|
|
25
|
+
this.coordinates = [];
|
|
26
|
+
/** false for open polylines; true (default) for closed polygons */
|
|
27
|
+
this.closed = true;
|
|
28
|
+
/**
|
|
29
|
+
* When true the vertices are treated as bezier-spline **anchors** and the
|
|
30
|
+
* region is rendered/exported as a smooth curve through them. The vertices
|
|
31
|
+
* stay the editable anchors — toggling this flag is the toBezier
|
|
32
|
+
* (`true`) / toPolygon (`false`), which only add/remove the curve, never move
|
|
33
|
+
* the anchors. GeoJSON keeps the flattened curve as the geometry (so QuPath
|
|
34
|
+
* renders it) plus the anchors + handles + flag in properties (so JIT
|
|
35
|
+
* round-trips the editable bezier).
|
|
36
|
+
*/
|
|
37
|
+
this.bezier = false;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/lib/toolbar/wand/wand.service.ts
|
|
42
|
+
var DEFAULT_PATCH_SIZE = 149;
|
|
43
|
+
var DEFAULT_SIGMA = 4;
|
|
44
|
+
var DEFAULT_SENSITIVITY = 2;
|
|
45
|
+
var WandService = class {
|
|
46
|
+
/**
|
|
47
|
+
* @param image full-image pixel matrix (already in memory).
|
|
48
|
+
* @param cx click x in image-pixel coordinates.
|
|
49
|
+
* @param cy click y in image-pixel coordinates.
|
|
50
|
+
* @param options wand parameters; missing fields fall back to defaults.
|
|
51
|
+
* @returns a Polygon in image-pixel coordinates, or null if no region grew.
|
|
52
|
+
*/
|
|
53
|
+
computeRegion(image, cx, cy, options = {}) {
|
|
54
|
+
const patch = this.computePatchMask(image, cx, cy, options);
|
|
55
|
+
if (!patch)
|
|
56
|
+
return null;
|
|
57
|
+
return this.maskToPolygon(
|
|
58
|
+
patch.mask,
|
|
59
|
+
patch.size,
|
|
60
|
+
patch.size,
|
|
61
|
+
image.width,
|
|
62
|
+
image.height,
|
|
63
|
+
Math.round(cx) - (patch.size - 1) / 2,
|
|
64
|
+
Math.round(cy) - (patch.size - 1) / 2
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Compute the wand's per-click flood-fill mask in patch-local coordinates.
|
|
69
|
+
* Same algorithm as `computeRegion` but stops before contour tracing — useful
|
|
70
|
+
* for accumulating per-tick masks across a brush-style stroke.
|
|
71
|
+
*
|
|
72
|
+
* @returns mask of size W*W (0/1) plus W, or null if patchSize is invalid.
|
|
73
|
+
*/
|
|
74
|
+
computePatchMask(image, cx, cy, options = {}) {
|
|
75
|
+
const W = options.patchSize ?? DEFAULT_PATCH_SIZE;
|
|
76
|
+
if (W % 2 === 0) {
|
|
77
|
+
throw new Error(`patchSize must be odd, got ${W}`);
|
|
78
|
+
}
|
|
79
|
+
const sigma = options.sigma ?? DEFAULT_SIGMA;
|
|
80
|
+
const sensitivity = options.sensitivity ?? DEFAULT_SENSITIVITY;
|
|
81
|
+
const isGrayscale = image.isGrayscale;
|
|
82
|
+
const type = options.type ?? (isGrayscale ? "GRAY" : "RGB");
|
|
83
|
+
const simple = !!options.simpleMode;
|
|
84
|
+
const inputChannels = type === "GRAY" ? 1 : 3;
|
|
85
|
+
let buf = this.extractPatch(image, cx, cy, W, type);
|
|
86
|
+
let floodChannels;
|
|
87
|
+
let threshold;
|
|
88
|
+
if (simple) {
|
|
89
|
+
floodChannels = inputChannels;
|
|
90
|
+
threshold = new Array(floodChannels).fill(0);
|
|
91
|
+
} else {
|
|
92
|
+
const blurSigma = Math.max(0.5, sigma);
|
|
93
|
+
buf = this.gaussianBlur(buf, W, inputChannels, blurSigma);
|
|
94
|
+
if (type === "LAB_DISTANCE") {
|
|
95
|
+
const distance = this.labDistanceMap(buf, W);
|
|
96
|
+
const max = distance.max > 0 ? distance.max : 1;
|
|
97
|
+
const scaled = new Float32Array(W * W);
|
|
98
|
+
for (let i = 0; i < scaled.length; i++)
|
|
99
|
+
scaled[i] = distance.values[i] * 255 / max;
|
|
100
|
+
buf = scaled;
|
|
101
|
+
floodChannels = 1;
|
|
102
|
+
threshold = [distance.mean * sensitivity * 255 / max];
|
|
103
|
+
} else {
|
|
104
|
+
floodChannels = inputChannels;
|
|
105
|
+
threshold = this.perChannelThreshold(buf, W, inputChannels, sensitivity);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const mask = this.floodFill(buf, W, floodChannels, threshold);
|
|
109
|
+
const closed = simple ? mask : this.morphClose(mask, W, 5);
|
|
110
|
+
return { mask: closed, size: W };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Standard ray-cast point-in-polygon test. (px, py) and the polygon
|
|
114
|
+
* vertices must be in the same coordinate system.
|
|
115
|
+
*/
|
|
116
|
+
pointInPolygon(px, py, xpoints, ypoints) {
|
|
117
|
+
const n = xpoints.length;
|
|
118
|
+
if (n < 3)
|
|
119
|
+
return false;
|
|
120
|
+
let inside = false;
|
|
121
|
+
for (let i = 0, j = n - 1; i < n; j = i++) {
|
|
122
|
+
const yi = ypoints[i], yj = ypoints[j];
|
|
123
|
+
const xi = xpoints[i], xj = xpoints[j];
|
|
124
|
+
const intersect = yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi;
|
|
125
|
+
if (intersect)
|
|
126
|
+
inside = !inside;
|
|
127
|
+
}
|
|
128
|
+
return inside;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Drop every vertex whose Euclidean distance to (cx, cy) is less than
|
|
132
|
+
* `radius`. Returns trimmed parallel arrays plus how many vertices were
|
|
133
|
+
* removed (so the caller can detect a no-op tick and skip the relayout).
|
|
134
|
+
*/
|
|
135
|
+
dropVerticesWithinRadius(xpoints, ypoints, cx, cy, radius) {
|
|
136
|
+
const r2 = radius * radius;
|
|
137
|
+
const xs = [];
|
|
138
|
+
const ys = [];
|
|
139
|
+
let removed = 0;
|
|
140
|
+
for (let i = 0; i < xpoints.length; i++) {
|
|
141
|
+
const dx = xpoints[i] - cx;
|
|
142
|
+
const dy = ypoints[i] - cy;
|
|
143
|
+
if (dx * dx + dy * dy < r2) {
|
|
144
|
+
removed++;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
xs.push(xpoints[i]);
|
|
148
|
+
ys.push(ypoints[i]);
|
|
149
|
+
}
|
|
150
|
+
return { xpoints: xs, ypoints: ys, removed };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Rasterize a closed polygon into a bbox-relative mask suitable as a
|
|
154
|
+
* starting point for a wand stroke accumulator.
|
|
155
|
+
*
|
|
156
|
+
* Returns the bbox origin (clamped to the image) plus the filled mask, or
|
|
157
|
+
* null if the polygon is degenerate or fully outside the image.
|
|
158
|
+
*/
|
|
159
|
+
rasterizePolygon(xpoints, ypoints, imageWidth, imageHeight, holes) {
|
|
160
|
+
const n = xpoints.length;
|
|
161
|
+
if (n < 3)
|
|
162
|
+
return null;
|
|
163
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
164
|
+
for (let i = 0; i < n; i++) {
|
|
165
|
+
if (xpoints[i] < minX)
|
|
166
|
+
minX = xpoints[i];
|
|
167
|
+
if (xpoints[i] > maxX)
|
|
168
|
+
maxX = xpoints[i];
|
|
169
|
+
if (ypoints[i] < minY)
|
|
170
|
+
minY = ypoints[i];
|
|
171
|
+
if (ypoints[i] > maxY)
|
|
172
|
+
maxY = ypoints[i];
|
|
173
|
+
}
|
|
174
|
+
let bx = Math.floor(minX);
|
|
175
|
+
let by = Math.floor(minY);
|
|
176
|
+
let bx1 = Math.ceil(maxX) + 1;
|
|
177
|
+
let by1 = Math.ceil(maxY) + 1;
|
|
178
|
+
if ((bx1 - bx) * (by1 - by) > 4096 * 4096) {
|
|
179
|
+
bx = Math.max(0, bx);
|
|
180
|
+
by = Math.max(0, by);
|
|
181
|
+
bx1 = Math.min(imageWidth, bx1);
|
|
182
|
+
by1 = Math.min(imageHeight, by1);
|
|
183
|
+
}
|
|
184
|
+
const bw = bx1 - bx;
|
|
185
|
+
const bh = by1 - by;
|
|
186
|
+
if (bw <= 0 || bh <= 0)
|
|
187
|
+
return null;
|
|
188
|
+
const mask = new Uint8Array(bw * bh);
|
|
189
|
+
const fillRing = (rx, ry, value) => {
|
|
190
|
+
const m = rx.length;
|
|
191
|
+
if (m < 3)
|
|
192
|
+
return;
|
|
193
|
+
for (let py = 0; py < bh; py++) {
|
|
194
|
+
const y = by + py + 0.5;
|
|
195
|
+
const xs = [];
|
|
196
|
+
for (let i = 0, j = m - 1; i < m; j = i++) {
|
|
197
|
+
const yi = ry[i], yj = ry[j];
|
|
198
|
+
if (yi <= y && yj > y || yj <= y && yi > y) {
|
|
199
|
+
const t = (y - yi) / (yj - yi);
|
|
200
|
+
xs.push(rx[i] + t * (rx[j] - rx[i]));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
xs.sort((a, b) => a - b);
|
|
204
|
+
for (let k = 0; k + 1 < xs.length; k += 2) {
|
|
205
|
+
const xStart = Math.max(0, Math.ceil(xs[k] - bx));
|
|
206
|
+
const xEnd = Math.min(bw - 1, Math.floor(xs[k + 1] - bx));
|
|
207
|
+
for (let x = xStart; x <= xEnd; x++)
|
|
208
|
+
mask[py * bw + x] = value;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
fillRing(xpoints, ypoints, 1);
|
|
213
|
+
if (holes) {
|
|
214
|
+
for (const ring of holes) {
|
|
215
|
+
fillRing(ring.map((p) => p[0]), ring.map((p) => p[1]), 0);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { bx, by, bw, bh, mask };
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Point-in-region test that honours interior rings (holes): inside the
|
|
222
|
+
* exterior AND outside every hole (even-odd). Coordinates must share the
|
|
223
|
+
* polygon's frame. With no holes this is exactly {@link pointInPolygon}.
|
|
224
|
+
*/
|
|
225
|
+
pointInPolygonWithHoles(px, py, xpoints, ypoints, holes) {
|
|
226
|
+
if (!this.pointInPolygon(px, py, xpoints, ypoints))
|
|
227
|
+
return false;
|
|
228
|
+
if (holes) {
|
|
229
|
+
for (const ring of holes) {
|
|
230
|
+
if (this.pointInPolygon(px, py, ring.map((p) => p[0]), ring.map((p) => p[1])))
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Trace the largest 4-connected blob in `mask` (sized w*h) and return a
|
|
238
|
+
* Polygon translated into image-pixel coordinates via (originX, originY).
|
|
239
|
+
* The traced vertices are NOT clamped to the image bounds: a region drawn while
|
|
240
|
+
* panned/zoomed keeps its true coordinates even where they fall outside the visible
|
|
241
|
+
* window (jit-ui#102). `imageWidth`/`imageHeight` are kept for signature compatibility
|
|
242
|
+
* but no longer used.
|
|
243
|
+
*/
|
|
244
|
+
maskToPolygon(mask, w, h, _imageWidth, _imageHeight, originX, originY) {
|
|
245
|
+
const verticesLocal = this.traceContour(mask, w, h);
|
|
246
|
+
if (!verticesLocal || verticesLocal.length < 3)
|
|
247
|
+
return null;
|
|
248
|
+
const xpoints = [];
|
|
249
|
+
const ypoints = [];
|
|
250
|
+
const coordinates = [];
|
|
251
|
+
for (const v of verticesLocal) {
|
|
252
|
+
const ix = Math.round(originX + v.x);
|
|
253
|
+
const iy = Math.round(originY + v.y);
|
|
254
|
+
xpoints.push(ix);
|
|
255
|
+
ypoints.push(iy);
|
|
256
|
+
coordinates.push([ix, iy]);
|
|
257
|
+
}
|
|
258
|
+
const poly = new Polygon();
|
|
259
|
+
poly.npoints = xpoints.length;
|
|
260
|
+
poly.xpoints = xpoints;
|
|
261
|
+
poly.ypoints = ypoints;
|
|
262
|
+
poly.coordinates = coordinates;
|
|
263
|
+
return poly;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Trace EVERY 4-connected blob in `mask` (sized w*h) whose area is at least
|
|
267
|
+
* `minSize` pixels, returning one Polygon per blob in image-pixel coordinates
|
|
268
|
+
* (translated via originX/originY, clamped to imageWidth/imageHeight), ordered
|
|
269
|
+
* largest-first. Unlike {@link maskToPolygon} (largest blob only), this lets
|
|
270
|
+
* the brush eraser split a region in two when a stroke cuts through it rather
|
|
271
|
+
* than discarding the smaller piece.
|
|
272
|
+
*/
|
|
273
|
+
maskToPolygons(mask, w, h, imageWidth, imageHeight, originX, originY, minSize = 4, minHoleSize = minSize) {
|
|
274
|
+
const labels = new Int32Array(w * h);
|
|
275
|
+
const sizes = [0];
|
|
276
|
+
let nextLabel = 0;
|
|
277
|
+
const queue = [];
|
|
278
|
+
for (let y = 0; y < h; y++) {
|
|
279
|
+
for (let x = 0; x < w; x++) {
|
|
280
|
+
const idx = y * w + x;
|
|
281
|
+
if (!mask[idx] || labels[idx])
|
|
282
|
+
continue;
|
|
283
|
+
nextLabel++;
|
|
284
|
+
labels[idx] = nextLabel;
|
|
285
|
+
let size = 0;
|
|
286
|
+
queue.push(idx);
|
|
287
|
+
while (queue.length) {
|
|
288
|
+
const i = queue.pop();
|
|
289
|
+
size++;
|
|
290
|
+
const px = i % w;
|
|
291
|
+
const py = (i - px) / w;
|
|
292
|
+
if (px > 0) {
|
|
293
|
+
const j = i - 1;
|
|
294
|
+
if (mask[j] && !labels[j]) {
|
|
295
|
+
labels[j] = nextLabel;
|
|
296
|
+
queue.push(j);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (px < w - 1) {
|
|
300
|
+
const j = i + 1;
|
|
301
|
+
if (mask[j] && !labels[j]) {
|
|
302
|
+
labels[j] = nextLabel;
|
|
303
|
+
queue.push(j);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (py > 0) {
|
|
307
|
+
const j = i - w;
|
|
308
|
+
if (mask[j] && !labels[j]) {
|
|
309
|
+
labels[j] = nextLabel;
|
|
310
|
+
queue.push(j);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (py < h - 1) {
|
|
314
|
+
const j = i + w;
|
|
315
|
+
if (mask[j] && !labels[j]) {
|
|
316
|
+
labels[j] = nextLabel;
|
|
317
|
+
queue.push(j);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
sizes[nextLabel] = size;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const wanted = [];
|
|
325
|
+
for (let lbl = 1; lbl <= nextLabel; lbl++) {
|
|
326
|
+
if (sizes[lbl] >= minSize)
|
|
327
|
+
wanted.push(lbl);
|
|
328
|
+
}
|
|
329
|
+
wanted.sort((a, b) => sizes[b] - sizes[a]);
|
|
330
|
+
const holesByLabel = this.detectHoles(
|
|
331
|
+
mask,
|
|
332
|
+
labels,
|
|
333
|
+
w,
|
|
334
|
+
h,
|
|
335
|
+
originX,
|
|
336
|
+
originY,
|
|
337
|
+
imageWidth,
|
|
338
|
+
imageHeight,
|
|
339
|
+
minHoleSize
|
|
340
|
+
);
|
|
341
|
+
const polys = [];
|
|
342
|
+
const comp = new Uint8Array(w * h);
|
|
343
|
+
for (const lbl of wanted) {
|
|
344
|
+
comp.fill(0);
|
|
345
|
+
for (let i = 0; i < comp.length; i++)
|
|
346
|
+
comp[i] = labels[i] === lbl ? 1 : 0;
|
|
347
|
+
const verts = this.mooreBoundary(comp, w, h);
|
|
348
|
+
if (!verts || verts.length < 3)
|
|
349
|
+
continue;
|
|
350
|
+
const xpoints = [];
|
|
351
|
+
const ypoints = [];
|
|
352
|
+
const coordinates = [];
|
|
353
|
+
for (const v of verts) {
|
|
354
|
+
const ix = Math.round(originX + v.x);
|
|
355
|
+
const iy = Math.round(originY + v.y);
|
|
356
|
+
xpoints.push(ix);
|
|
357
|
+
ypoints.push(iy);
|
|
358
|
+
coordinates.push([ix, iy]);
|
|
359
|
+
}
|
|
360
|
+
const poly = new Polygon();
|
|
361
|
+
poly.npoints = xpoints.length;
|
|
362
|
+
poly.xpoints = xpoints;
|
|
363
|
+
poly.ypoints = ypoints;
|
|
364
|
+
poly.coordinates = coordinates;
|
|
365
|
+
const holes = holesByLabel.get(lbl);
|
|
366
|
+
if (holes && holes.length)
|
|
367
|
+
poly.holes = holes;
|
|
368
|
+
polys.push(poly);
|
|
369
|
+
}
|
|
370
|
+
return polys;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Find interior rings (holes) of each foreground component in `mask`: a
|
|
374
|
+
* 4-connected run of background (0) pixels that is fully enclosed — i.e. not
|
|
375
|
+
* reachable from the grid border through background — is a hole, attributed to
|
|
376
|
+
* the foreground component that borders it most. Returns `fgLabel → rings`,
|
|
377
|
+
* each ring a list of `[x, y]` image-pixel pairs (same convention as a
|
|
378
|
+
* polygon's `coordinates`). Holes smaller than `minHoleSize` are dropped.
|
|
379
|
+
*/
|
|
380
|
+
detectHoles(mask, fgLabels, w, h, originX, originY, imageWidth, imageHeight, minHoleSize) {
|
|
381
|
+
const result = /* @__PURE__ */ new Map();
|
|
382
|
+
const outside = new Uint8Array(w * h);
|
|
383
|
+
const stack = [];
|
|
384
|
+
const seed = (idx) => {
|
|
385
|
+
if (!mask[idx] && !outside[idx]) {
|
|
386
|
+
outside[idx] = 1;
|
|
387
|
+
stack.push(idx);
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
for (let x = 0; x < w; x++) {
|
|
391
|
+
seed(x);
|
|
392
|
+
seed((h - 1) * w + x);
|
|
393
|
+
}
|
|
394
|
+
for (let y = 0; y < h; y++) {
|
|
395
|
+
seed(y * w);
|
|
396
|
+
seed(y * w + w - 1);
|
|
397
|
+
}
|
|
398
|
+
while (stack.length) {
|
|
399
|
+
const i = stack.pop();
|
|
400
|
+
const px = i % w, py = (i - px) / w;
|
|
401
|
+
if (px > 0)
|
|
402
|
+
seed(i - 1);
|
|
403
|
+
if (px < w - 1)
|
|
404
|
+
seed(i + 1);
|
|
405
|
+
if (py > 0)
|
|
406
|
+
seed(i - w);
|
|
407
|
+
if (py < h - 1)
|
|
408
|
+
seed(i + w);
|
|
409
|
+
}
|
|
410
|
+
const visited = new Uint8Array(w * h);
|
|
411
|
+
const holeMask = new Uint8Array(w * h);
|
|
412
|
+
const queue = [];
|
|
413
|
+
for (let start = 0; start < mask.length; start++) {
|
|
414
|
+
if (mask[start] || outside[start] || visited[start])
|
|
415
|
+
continue;
|
|
416
|
+
let size = 0;
|
|
417
|
+
const owners = /* @__PURE__ */ new Map();
|
|
418
|
+
const pixels = [];
|
|
419
|
+
visited[start] = 1;
|
|
420
|
+
queue.length = 0;
|
|
421
|
+
queue.push(start);
|
|
422
|
+
while (queue.length) {
|
|
423
|
+
const i = queue.pop();
|
|
424
|
+
size++;
|
|
425
|
+
pixels.push(i);
|
|
426
|
+
const px = i % w, py = (i - px) / w;
|
|
427
|
+
const visit = (j) => {
|
|
428
|
+
if (mask[j]) {
|
|
429
|
+
const lbl = fgLabels[j];
|
|
430
|
+
if (lbl)
|
|
431
|
+
owners.set(lbl, (owners.get(lbl) ?? 0) + 1);
|
|
432
|
+
} else if (!outside[j] && !visited[j]) {
|
|
433
|
+
visited[j] = 1;
|
|
434
|
+
queue.push(j);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
if (px > 0)
|
|
438
|
+
visit(i - 1);
|
|
439
|
+
if (px < w - 1)
|
|
440
|
+
visit(i + 1);
|
|
441
|
+
if (py > 0)
|
|
442
|
+
visit(i - w);
|
|
443
|
+
if (py < h - 1)
|
|
444
|
+
visit(i + w);
|
|
445
|
+
}
|
|
446
|
+
if (size < minHoleSize || owners.size === 0)
|
|
447
|
+
continue;
|
|
448
|
+
let owner = 0, best = -1;
|
|
449
|
+
owners.forEach((cnt, lbl) => {
|
|
450
|
+
if (cnt > best) {
|
|
451
|
+
best = cnt;
|
|
452
|
+
owner = lbl;
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
holeMask.fill(0);
|
|
456
|
+
for (const i of pixels)
|
|
457
|
+
holeMask[i] = 1;
|
|
458
|
+
const verts = this.mooreBoundary(holeMask, w, h);
|
|
459
|
+
if (!verts || verts.length < 3)
|
|
460
|
+
continue;
|
|
461
|
+
const ring = [];
|
|
462
|
+
for (const v of verts) {
|
|
463
|
+
const ix = Math.max(0, Math.min(imageWidth - 1, Math.round(originX + v.x)));
|
|
464
|
+
const iy = Math.max(0, Math.min(imageHeight - 1, Math.round(originY + v.y)));
|
|
465
|
+
ring.push([ix, iy]);
|
|
466
|
+
}
|
|
467
|
+
const list = result.get(owner) ?? [];
|
|
468
|
+
list.push(ring);
|
|
469
|
+
result.set(owner, list);
|
|
470
|
+
}
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Trace each instance in an integer label map (0 = background) into a Polygon
|
|
475
|
+
* — used to turn a cellpose-style segmentation into region outlines. Labels
|
|
476
|
+
* with area below `minSize` are skipped. Coords are translated via
|
|
477
|
+
* originX/originY and clamped to imageWidth/imageHeight.
|
|
478
|
+
*/
|
|
479
|
+
labelsToPolygons(labels, w, h, imageWidth, imageHeight, originX, originY, minSize = 10) {
|
|
480
|
+
let maxLabel = 0;
|
|
481
|
+
for (let i = 0; i < labels.length; i++)
|
|
482
|
+
if (labels[i] > maxLabel)
|
|
483
|
+
maxLabel = labels[i];
|
|
484
|
+
const out = [];
|
|
485
|
+
const bin = new Uint8Array(w * h);
|
|
486
|
+
for (let lbl = 1; lbl <= maxLabel; lbl++) {
|
|
487
|
+
let any = false;
|
|
488
|
+
for (let i = 0; i < labels.length; i++) {
|
|
489
|
+
const on = labels[i] === lbl;
|
|
490
|
+
bin[i] = on ? 1 : 0;
|
|
491
|
+
if (on)
|
|
492
|
+
any = true;
|
|
493
|
+
}
|
|
494
|
+
if (!any)
|
|
495
|
+
continue;
|
|
496
|
+
for (const p of this.maskToPolygons(bin, w, h, imageWidth, imageHeight, originX, originY, minSize)) {
|
|
497
|
+
out.push(p);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return out;
|
|
501
|
+
}
|
|
502
|
+
// ── Patch extraction ────────────────────────────────────────────────
|
|
503
|
+
extractPatch(image, cx, cy, W, type) {
|
|
504
|
+
const channels = type === "GRAY" ? 1 : 3;
|
|
505
|
+
const half = (W - 1) / 2;
|
|
506
|
+
const x0 = Math.round(cx) - half;
|
|
507
|
+
const y0 = Math.round(cy) - half;
|
|
508
|
+
const buf = new Float32Array(W * W * channels);
|
|
509
|
+
for (let py = 0; py < W; py++) {
|
|
510
|
+
const iy = y0 + py;
|
|
511
|
+
if (iy < 0 || iy >= image.height)
|
|
512
|
+
continue;
|
|
513
|
+
const row = image.data[iy];
|
|
514
|
+
if (!row)
|
|
515
|
+
continue;
|
|
516
|
+
for (let px = 0; px < W; px++) {
|
|
517
|
+
const ix = x0 + px;
|
|
518
|
+
if (ix < 0 || ix >= image.width)
|
|
519
|
+
continue;
|
|
520
|
+
const dst = (py * W + px) * channels;
|
|
521
|
+
if (image.isGrayscale) {
|
|
522
|
+
const v = row[ix];
|
|
523
|
+
if (channels === 1) {
|
|
524
|
+
buf[dst] = v;
|
|
525
|
+
} else {
|
|
526
|
+
buf[dst] = v;
|
|
527
|
+
buf[dst + 1] = v;
|
|
528
|
+
buf[dst + 2] = v;
|
|
529
|
+
}
|
|
530
|
+
} else {
|
|
531
|
+
const tuple = row[ix];
|
|
532
|
+
if (channels === 1) {
|
|
533
|
+
buf[dst] = 0.299 * tuple[0] + 0.587 * tuple[1] + 0.114 * tuple[2];
|
|
534
|
+
} else {
|
|
535
|
+
buf[dst] = tuple[0];
|
|
536
|
+
buf[dst + 1] = tuple[1];
|
|
537
|
+
buf[dst + 2] = tuple[2];
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return buf;
|
|
543
|
+
}
|
|
544
|
+
// ── Gaussian blur (separable) ───────────────────────────────────────
|
|
545
|
+
gaussianBlur(buf, W, channels, sigma) {
|
|
546
|
+
const radius = Math.max(1, Math.ceil(sigma * 2));
|
|
547
|
+
const size = radius * 2 + 1;
|
|
548
|
+
const kernel = new Float32Array(size);
|
|
549
|
+
let sum = 0;
|
|
550
|
+
const inv2s2 = 1 / (2 * sigma * sigma);
|
|
551
|
+
for (let i = 0; i < size; i++) {
|
|
552
|
+
const x = i - radius;
|
|
553
|
+
kernel[i] = Math.exp(-x * x * inv2s2);
|
|
554
|
+
sum += kernel[i];
|
|
555
|
+
}
|
|
556
|
+
for (let i = 0; i < size; i++)
|
|
557
|
+
kernel[i] /= sum;
|
|
558
|
+
const horiz = new Float32Array(buf.length);
|
|
559
|
+
for (let y = 0; y < W; y++) {
|
|
560
|
+
for (let x = 0; x < W; x++) {
|
|
561
|
+
for (let c = 0; c < channels; c++) {
|
|
562
|
+
let acc = 0;
|
|
563
|
+
for (let k = 0; k < size; k++) {
|
|
564
|
+
let xx = x + k - radius;
|
|
565
|
+
if (xx < 0)
|
|
566
|
+
xx = 0;
|
|
567
|
+
else if (xx >= W)
|
|
568
|
+
xx = W - 1;
|
|
569
|
+
acc += kernel[k] * buf[(y * W + xx) * channels + c];
|
|
570
|
+
}
|
|
571
|
+
horiz[(y * W + x) * channels + c] = acc;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
const out = new Float32Array(buf.length);
|
|
576
|
+
for (let y = 0; y < W; y++) {
|
|
577
|
+
for (let x = 0; x < W; x++) {
|
|
578
|
+
for (let c = 0; c < channels; c++) {
|
|
579
|
+
let acc = 0;
|
|
580
|
+
for (let k = 0; k < size; k++) {
|
|
581
|
+
let yy = y + k - radius;
|
|
582
|
+
if (yy < 0)
|
|
583
|
+
yy = 0;
|
|
584
|
+
else if (yy >= W)
|
|
585
|
+
yy = W - 1;
|
|
586
|
+
acc += kernel[k] * horiz[(yy * W + x) * channels + c];
|
|
587
|
+
}
|
|
588
|
+
out[(y * W + x) * channels + c] = acc;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return out;
|
|
593
|
+
}
|
|
594
|
+
// ── Threshold computation ───────────────────────────────────────────
|
|
595
|
+
perChannelThreshold(buf, W, channels, sensitivity) {
|
|
596
|
+
const n = W * W;
|
|
597
|
+
const sums = new Array(channels).fill(0);
|
|
598
|
+
const sqs = new Array(channels).fill(0);
|
|
599
|
+
for (let i = 0; i < n; i++) {
|
|
600
|
+
for (let c = 0; c < channels; c++) {
|
|
601
|
+
const v = buf[i * channels + c];
|
|
602
|
+
sums[c] += v;
|
|
603
|
+
sqs[c] += v * v;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
const scale = sensitivity > 0 ? 1 / sensitivity : 100;
|
|
607
|
+
const threshold = new Array(channels);
|
|
608
|
+
for (let c = 0; c < channels; c++) {
|
|
609
|
+
const mean = sums[c] / n;
|
|
610
|
+
const variance = Math.max(0, sqs[c] / n - mean * mean);
|
|
611
|
+
const stddev = Math.sqrt(variance);
|
|
612
|
+
threshold[c] = stddev * scale;
|
|
613
|
+
}
|
|
614
|
+
return threshold;
|
|
615
|
+
}
|
|
616
|
+
labDistanceMap(buf, W) {
|
|
617
|
+
const n = W * W;
|
|
618
|
+
const lab = new Float32Array(n * 3);
|
|
619
|
+
for (let i = 0; i < n; i++) {
|
|
620
|
+
const r = buf[i * 3] / 255;
|
|
621
|
+
const g = buf[i * 3 + 1] / 255;
|
|
622
|
+
const b = buf[i * 3 + 2] / 255;
|
|
623
|
+
const [L, A, B] = srgbToLab(r, g, b);
|
|
624
|
+
lab[i * 3] = L;
|
|
625
|
+
lab[i * 3 + 1] = A;
|
|
626
|
+
lab[i * 3 + 2] = B;
|
|
627
|
+
}
|
|
628
|
+
const mid = Math.floor(n / 2);
|
|
629
|
+
const cL = lab[mid * 3];
|
|
630
|
+
const cA = lab[mid * 3 + 1];
|
|
631
|
+
const cB = lab[mid * 3 + 2];
|
|
632
|
+
const values = new Float32Array(n);
|
|
633
|
+
let max = 0;
|
|
634
|
+
let sum = 0;
|
|
635
|
+
for (let i = 0; i < n; i++) {
|
|
636
|
+
const dL = lab[i * 3] - cL;
|
|
637
|
+
const dA = lab[i * 3 + 1] - cA;
|
|
638
|
+
const dB = lab[i * 3 + 2] - cB;
|
|
639
|
+
const d = Math.sqrt(dL * dL + dA * dA + dB * dB);
|
|
640
|
+
values[i] = d;
|
|
641
|
+
if (d > max)
|
|
642
|
+
max = d;
|
|
643
|
+
sum += d;
|
|
644
|
+
}
|
|
645
|
+
return { values, mean: sum / n, max };
|
|
646
|
+
}
|
|
647
|
+
// ── Flood fill ──────────────────────────────────────────────────────
|
|
648
|
+
/**
|
|
649
|
+
* Fixed-range scanline flood fill from the patch centre. A neighbour is
|
|
650
|
+
* accepted iff for every channel `|p[c] - seed[c]| <= threshold[c]`.
|
|
651
|
+
*/
|
|
652
|
+
floodFill(buf, W, channels, threshold) {
|
|
653
|
+
const mask = new Uint8Array(W * W);
|
|
654
|
+
const seedX = (W - 1) / 2;
|
|
655
|
+
const seedY = (W - 1) / 2;
|
|
656
|
+
const seedIdx = (seedY * W + seedX) * channels;
|
|
657
|
+
const seed = new Array(channels);
|
|
658
|
+
for (let c = 0; c < channels; c++)
|
|
659
|
+
seed[c] = buf[seedIdx + c];
|
|
660
|
+
const accept = (x, y) => {
|
|
661
|
+
const idx = (y * W + x) * channels;
|
|
662
|
+
for (let c = 0; c < channels; c++) {
|
|
663
|
+
const diff = Math.abs(buf[idx + c] - seed[c]);
|
|
664
|
+
if (diff > threshold[c])
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
return true;
|
|
668
|
+
};
|
|
669
|
+
const stack = [seedX, seedY];
|
|
670
|
+
while (stack.length) {
|
|
671
|
+
const y = stack.pop();
|
|
672
|
+
const x = stack.pop();
|
|
673
|
+
if (x < 0 || x >= W || y < 0 || y >= W)
|
|
674
|
+
continue;
|
|
675
|
+
if (mask[y * W + x])
|
|
676
|
+
continue;
|
|
677
|
+
if (!accept(x, y))
|
|
678
|
+
continue;
|
|
679
|
+
let xl = x;
|
|
680
|
+
while (xl > 0 && !mask[y * W + (xl - 1)] && accept(xl - 1, y))
|
|
681
|
+
xl--;
|
|
682
|
+
let xr = x;
|
|
683
|
+
while (xr < W - 1 && !mask[y * W + (xr + 1)] && accept(xr + 1, y))
|
|
684
|
+
xr++;
|
|
685
|
+
for (let xi = xl; xi <= xr; xi++) {
|
|
686
|
+
mask[y * W + xi] = 1;
|
|
687
|
+
}
|
|
688
|
+
if (y > 0) {
|
|
689
|
+
for (let xi = xl; xi <= xr; xi++) {
|
|
690
|
+
if (!mask[(y - 1) * W + xi]) {
|
|
691
|
+
stack.push(xi, y - 1);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (y < W - 1) {
|
|
696
|
+
for (let xi = xl; xi <= xr; xi++) {
|
|
697
|
+
if (!mask[(y + 1) * W + xi]) {
|
|
698
|
+
stack.push(xi, y + 1);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return mask;
|
|
704
|
+
}
|
|
705
|
+
// ── Morphological close (dilate then erode) ─────────────────────────
|
|
706
|
+
morphClose(mask, W, kernelSize) {
|
|
707
|
+
const r = (kernelSize - 1) / 2;
|
|
708
|
+
const dil = this.dilate(mask, W, r);
|
|
709
|
+
return this.erode(dil, W, r);
|
|
710
|
+
}
|
|
711
|
+
dilate(mask, W, r) {
|
|
712
|
+
const out = new Uint8Array(W * W);
|
|
713
|
+
for (let y = 0; y < W; y++) {
|
|
714
|
+
for (let x = 0; x < W; x++) {
|
|
715
|
+
let hit = 0;
|
|
716
|
+
for (let dy = -r; dy <= r && !hit; dy++) {
|
|
717
|
+
const yy = y + dy;
|
|
718
|
+
if (yy < 0 || yy >= W)
|
|
719
|
+
continue;
|
|
720
|
+
for (let dx = -r; dx <= r; dx++) {
|
|
721
|
+
const xx = x + dx;
|
|
722
|
+
if (xx < 0 || xx >= W)
|
|
723
|
+
continue;
|
|
724
|
+
if (mask[yy * W + xx]) {
|
|
725
|
+
hit = 1;
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
out[y * W + x] = hit;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return out;
|
|
734
|
+
}
|
|
735
|
+
erode(mask, W, r) {
|
|
736
|
+
const out = new Uint8Array(W * W);
|
|
737
|
+
for (let y = 0; y < W; y++) {
|
|
738
|
+
for (let x = 0; x < W; x++) {
|
|
739
|
+
let all = 1;
|
|
740
|
+
for (let dy = -r; dy <= r && all; dy++) {
|
|
741
|
+
const yy = y + dy;
|
|
742
|
+
if (yy < 0 || yy >= W) {
|
|
743
|
+
all = 0;
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
for (let dx = -r; dx <= r; dx++) {
|
|
747
|
+
const xx = x + dx;
|
|
748
|
+
if (xx < 0 || xx >= W) {
|
|
749
|
+
all = 0;
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
if (!mask[yy * W + xx]) {
|
|
753
|
+
all = 0;
|
|
754
|
+
break;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
out[y * W + x] = all;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return out;
|
|
762
|
+
}
|
|
763
|
+
// ── Contour tracing (Moore-neighbour boundary follow) ───────────────
|
|
764
|
+
/**
|
|
765
|
+
* Returns the outer boundary of the largest 4-connected blob in the mask
|
|
766
|
+
* sized w*h.
|
|
767
|
+
*/
|
|
768
|
+
traceContour(mask, w, h) {
|
|
769
|
+
const labels = new Int32Array(w * h);
|
|
770
|
+
let bestLabel = 0;
|
|
771
|
+
let bestSize = 0;
|
|
772
|
+
let nextLabel = 0;
|
|
773
|
+
const queue = [];
|
|
774
|
+
for (let y = 0; y < h; y++) {
|
|
775
|
+
for (let x = 0; x < w; x++) {
|
|
776
|
+
const idx = y * w + x;
|
|
777
|
+
if (!mask[idx] || labels[idx])
|
|
778
|
+
continue;
|
|
779
|
+
nextLabel++;
|
|
780
|
+
labels[idx] = nextLabel;
|
|
781
|
+
let size = 0;
|
|
782
|
+
queue.push(idx);
|
|
783
|
+
while (queue.length) {
|
|
784
|
+
const i = queue.pop();
|
|
785
|
+
size++;
|
|
786
|
+
const px = i % w;
|
|
787
|
+
const py = (i - px) / w;
|
|
788
|
+
if (px > 0) {
|
|
789
|
+
const j = i - 1;
|
|
790
|
+
if (mask[j] && !labels[j]) {
|
|
791
|
+
labels[j] = nextLabel;
|
|
792
|
+
queue.push(j);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (px < w - 1) {
|
|
796
|
+
const j = i + 1;
|
|
797
|
+
if (mask[j] && !labels[j]) {
|
|
798
|
+
labels[j] = nextLabel;
|
|
799
|
+
queue.push(j);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (py > 0) {
|
|
803
|
+
const j = i - w;
|
|
804
|
+
if (mask[j] && !labels[j]) {
|
|
805
|
+
labels[j] = nextLabel;
|
|
806
|
+
queue.push(j);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (py < h - 1) {
|
|
810
|
+
const j = i + w;
|
|
811
|
+
if (mask[j] && !labels[j]) {
|
|
812
|
+
labels[j] = nextLabel;
|
|
813
|
+
queue.push(j);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
if (size > bestSize) {
|
|
818
|
+
bestSize = size;
|
|
819
|
+
bestLabel = nextLabel;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (bestSize === 0)
|
|
824
|
+
return null;
|
|
825
|
+
const comp = new Uint8Array(w * h);
|
|
826
|
+
for (let i = 0; i < comp.length; i++)
|
|
827
|
+
comp[i] = labels[i] === bestLabel ? 1 : 0;
|
|
828
|
+
return this.mooreBoundary(comp, w, h);
|
|
829
|
+
}
|
|
830
|
+
mooreBoundary(mask, w, h) {
|
|
831
|
+
let startIdx = -1;
|
|
832
|
+
for (let i = 0; i < mask.length; i++) {
|
|
833
|
+
if (mask[i]) {
|
|
834
|
+
startIdx = i;
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (startIdx < 0)
|
|
839
|
+
return [];
|
|
840
|
+
const sx = startIdx % w;
|
|
841
|
+
const sy = (startIdx - sx) / w;
|
|
842
|
+
const points = [{ x: sx, y: sy }];
|
|
843
|
+
const singleton = (idx) => {
|
|
844
|
+
const x = idx % w;
|
|
845
|
+
const y = (idx - x) / w;
|
|
846
|
+
const at = (xx, yy) => xx >= 0 && xx < w && yy >= 0 && yy < h && mask[yy * w + xx];
|
|
847
|
+
return !(at(x - 1, y) || at(x + 1, y) || at(x, y - 1) || at(x, y + 1));
|
|
848
|
+
};
|
|
849
|
+
if (singleton(startIdx)) {
|
|
850
|
+
return [
|
|
851
|
+
{ x: sx, y: sy },
|
|
852
|
+
{ x: sx + 1, y: sy },
|
|
853
|
+
{ x: sx + 1, y: sy + 1 },
|
|
854
|
+
{ x: sx, y: sy + 1 }
|
|
855
|
+
];
|
|
856
|
+
}
|
|
857
|
+
const dx = [-1, -1, 0, 1, 1, 1, 0, -1];
|
|
858
|
+
const dy = [0, -1, -1, -1, 0, 1, 1, 1];
|
|
859
|
+
let cx = sx, cy = sy;
|
|
860
|
+
let prevDir = 0;
|
|
861
|
+
const maxSteps = w * h * 8;
|
|
862
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
863
|
+
let found = false;
|
|
864
|
+
for (let k = 1; k <= 8; k++) {
|
|
865
|
+
const dir = prevDir + k & 7;
|
|
866
|
+
const nx = cx + dx[dir];
|
|
867
|
+
const ny = cy + dy[dir];
|
|
868
|
+
if (nx < 0 || nx >= w || ny < 0 || ny >= h)
|
|
869
|
+
continue;
|
|
870
|
+
if (mask[ny * w + nx]) {
|
|
871
|
+
prevDir = dir + 4 & 7;
|
|
872
|
+
cx = nx;
|
|
873
|
+
cy = ny;
|
|
874
|
+
points.push({ x: cx, y: cy });
|
|
875
|
+
found = true;
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (!found)
|
|
880
|
+
break;
|
|
881
|
+
if (cx === sx && cy === sy && points.length > 1) {
|
|
882
|
+
points.pop();
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return points;
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
WandService = __decorateClass([
|
|
890
|
+
Injectable({ providedIn: "root" })
|
|
891
|
+
], WandService);
|
|
892
|
+
function srgbToLinear(c) {
|
|
893
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
894
|
+
}
|
|
895
|
+
function srgbToLab(r, g, b) {
|
|
896
|
+
const R = srgbToLinear(r);
|
|
897
|
+
const G = srgbToLinear(g);
|
|
898
|
+
const B = srgbToLinear(b);
|
|
899
|
+
const X = R * 0.4124564 + G * 0.3575761 + B * 0.1804375;
|
|
900
|
+
const Y = R * 0.2126729 + G * 0.7151522 + B * 0.072175;
|
|
901
|
+
const Z = R * 0.0193339 + G * 0.119192 + B * 0.9503041;
|
|
902
|
+
const Xn = 0.95047, Yn = 1, Zn = 1.08883;
|
|
903
|
+
const fx = labF(X / Xn);
|
|
904
|
+
const fy = labF(Y / Yn);
|
|
905
|
+
const fz = labF(Z / Zn);
|
|
906
|
+
const L = 116 * fy - 16;
|
|
907
|
+
const A = 500 * (fx - fy);
|
|
908
|
+
const Bv = 200 * (fy - fz);
|
|
909
|
+
return [L, A, Bv];
|
|
910
|
+
}
|
|
911
|
+
function labF(t) {
|
|
912
|
+
const d = 6 / 29;
|
|
913
|
+
return t > d * d * d ? Math.cbrt(t) : t / (3 * d * d) + 4 / 29;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// src/lib/region-editor/mask-raster.ts
|
|
917
|
+
function regionsToMask(regions, imageWidth, imageHeight, mode, rasterize, onProgress) {
|
|
918
|
+
const W = Math.round(imageWidth);
|
|
919
|
+
const H = Math.round(imageHeight);
|
|
920
|
+
if (!Number.isFinite(W) || !Number.isFinite(H) || W <= 0 || H <= 0 || !regions?.length) {
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
const multiclass = mode === "multiclass";
|
|
924
|
+
const bitDepth = multiclass && regions.length > 255 ? 16 : 8;
|
|
925
|
+
const out = bitDepth === 16 ? new Uint16Array(W * H) : new Uint8Array(W * H);
|
|
926
|
+
let painted = false;
|
|
927
|
+
for (let i = 0; i < regions.length; i++) {
|
|
928
|
+
const value = multiclass ? i + 1 : 255;
|
|
929
|
+
for (const part of regions[i]) {
|
|
930
|
+
const m = rasterize(part.xpoints, part.ypoints, W, H, part.holes);
|
|
931
|
+
if (!m)
|
|
932
|
+
continue;
|
|
933
|
+
painted = true;
|
|
934
|
+
for (let y = 0; y < m.bh; y++) {
|
|
935
|
+
const srcRow = y * m.bw;
|
|
936
|
+
const dstRow = (m.by + y) * W + m.bx;
|
|
937
|
+
for (let x = 0; x < m.bw; x++) {
|
|
938
|
+
if (m.mask[srcRow + x])
|
|
939
|
+
out[dstRow + x] = value;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
onProgress?.(i + 1, regions.length);
|
|
944
|
+
}
|
|
945
|
+
return painted ? { data: out, bitDepth } : null;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// src/lib/region-editor/mask.worker.ts
|
|
949
|
+
var wand = new WandService();
|
|
950
|
+
addEventListener("message", ({ data }) => {
|
|
951
|
+
try {
|
|
952
|
+
const { width, height, originalWidth, originalHeight, scale, mode, sourceName, regions } = data;
|
|
953
|
+
const mask = regionsToMask(
|
|
954
|
+
regions,
|
|
955
|
+
width,
|
|
956
|
+
height,
|
|
957
|
+
mode,
|
|
958
|
+
(xs, ys, w, h, holes) => wand.rasterizePolygon(xs, ys, w, h, holes),
|
|
959
|
+
(done, total) => postMessage({ type: "progress", done, total })
|
|
960
|
+
);
|
|
961
|
+
if (!mask) {
|
|
962
|
+
postMessage({ type: "error", error: "No regions could be rasterized." });
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
postMessage({ type: "encoding" });
|
|
966
|
+
const text = {
|
|
967
|
+
MaskType: mode,
|
|
968
|
+
OriginalWidth: String(originalWidth),
|
|
969
|
+
OriginalHeight: String(originalHeight),
|
|
970
|
+
MaskScale: String(scale)
|
|
971
|
+
};
|
|
972
|
+
if (sourceName)
|
|
973
|
+
text.SourceImage = sourceName;
|
|
974
|
+
const png = encodePng({
|
|
975
|
+
width: Math.round(width),
|
|
976
|
+
height: Math.round(height),
|
|
977
|
+
data: mask.data,
|
|
978
|
+
channels: 1,
|
|
979
|
+
depth: mask.bitDepth,
|
|
980
|
+
text
|
|
981
|
+
});
|
|
982
|
+
postMessage(
|
|
983
|
+
{ type: "done", png },
|
|
984
|
+
[png.buffer]
|
|
985
|
+
);
|
|
986
|
+
} catch (err) {
|
|
987
|
+
postMessage({ type: "error", error: err?.message ?? String(err) });
|
|
988
|
+
}
|
|
989
|
+
});
|