@jax-data-science/sci-image-visualizer 0.3.3 → 0.5.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 (98) hide show
  1. package/README.md +268 -4
  2. package/esm2022/index.mjs +21 -1
  3. package/esm2022/lib/contracts/display-types.mjs +20 -2
  4. package/esm2022/lib/contracts/image.contract.mjs +1 -1
  5. package/esm2022/lib/contracts/plot-type-contribution.contract.mjs +66 -0
  6. package/esm2022/lib/contracts/plot-type.mjs +41 -1
  7. package/esm2022/lib/contracts/ports/spatial-data.port.mjs +3 -0
  8. package/esm2022/lib/contracts/spatial-dataset.contract.mjs +45 -0
  9. package/esm2022/lib/contracts/visualizer.contract.mjs +1 -1
  10. package/esm2022/lib/implementations/napari-js/napari-axes-labels.mjs +10 -19
  11. package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +71 -7
  12. package/esm2022/lib/implementations/napari-js/napari-scale-bar.mjs +1 -1
  13. package/esm2022/lib/implementations/napari-js/napari-spatial-tooltip.mjs +96 -0
  14. package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +1934 -55
  15. package/esm2022/lib/implementations/napari-js/napari-zoom.mjs +18 -0
  16. package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +73 -8
  17. package/esm2022/lib/implementations/osd/osd-coordinate-transform.mjs +8 -1
  18. package/esm2022/lib/implementations/osd/osd-region-overlay.mjs +3 -4
  19. package/esm2022/lib/implementations/osd/osd-zoom.mjs +20 -0
  20. package/esm2022/lib/implementations/plotly/omics-trace-builders.mjs +555 -0
  21. package/esm2022/lib/implementations/plotly/plotly.service.mjs +13 -7
  22. package/esm2022/lib/implementations/spatial-data-http/spatial-data-http.service.mjs +304 -0
  23. package/esm2022/lib/implementations/spatial-data-http/spatial-wire.mjs +179 -0
  24. package/esm2022/lib/plot-mode/plot-mode-controller.mjs +329 -0
  25. package/esm2022/lib/routing-visualizer.service.mjs +228 -12
  26. package/esm2022/lib/spatial/embedding-compute.mjs +142 -0
  27. package/esm2022/lib/spatial/gene-search.mjs +77 -0
  28. package/esm2022/lib/spatial/spatial-density.mjs +224 -0
  29. package/esm2022/lib/spatial/spatial-encoding.mjs +232 -0
  30. package/esm2022/lib/spatial/spatial-expression.mjs +257 -0
  31. package/esm2022/lib/spatial/spatial-framing.mjs +60 -0
  32. package/esm2022/lib/spatial/spatial-heatmap.mjs +185 -0
  33. package/esm2022/lib/spatial/spatial-hover.mjs +79 -0
  34. package/esm2022/lib/spatial/spatial-sections.mjs +96 -0
  35. package/esm2022/lib/spatial/spatial-selection.mjs +264 -0
  36. package/esm2022/lib/spatial/spatial-volume-image.mjs +163 -0
  37. package/esm2022/lib/spatial/tsne-worker.mjs +10 -0
  38. package/esm2022/lib/spatial/world-grid.mjs +77 -0
  39. package/esm2022/lib/spatial-controls/spatial-charts/spatial-charts.component.mjs +1278 -0
  40. package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +621 -0
  41. package/esm2022/lib/store/spatial-selection.service.mjs +47 -0
  42. package/esm2022/lib/store/visualizer-store.service.mjs +23 -1
  43. package/esm2022/lib/toolbar/toolbar.component.mjs +61 -21
  44. package/esm2022/lib/visualization.module.mjs +23 -5
  45. package/esm2022/lib/visualizer.component.mjs +527 -44
  46. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-BwP-A11m.mjs} +6 -4
  47. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-BwP-A11m.mjs.map} +1 -1
  48. package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs +12 -0
  49. package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs.map +1 -0
  50. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +15971 -7808
  51. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
  52. package/fesm2022/tsne.worker.js +19091 -0
  53. package/index.d.ts +16 -0
  54. package/lib/contracts/display-types.d.ts +131 -0
  55. package/lib/contracts/image.contract.d.ts +7 -0
  56. package/lib/contracts/plot-type-contribution.contract.d.ts +172 -0
  57. package/lib/contracts/plot-type.d.ts +52 -2
  58. package/lib/contracts/ports/spatial-data.port.d.ts +90 -0
  59. package/lib/contracts/spatial-dataset.contract.d.ts +281 -0
  60. package/lib/contracts/visualizer.contract.d.ts +104 -1
  61. package/lib/implementations/napari-js/napari-axes-labels.d.ts +9 -7
  62. package/lib/implementations/napari-js/napari-region-overlay.d.ts +38 -4
  63. package/lib/implementations/napari-js/napari-scale-bar.d.ts +7 -3
  64. package/lib/implementations/napari-js/napari-spatial-tooltip.d.ts +32 -0
  65. package/lib/implementations/napari-js/napari-visualizer.service.d.ts +497 -16
  66. package/lib/implementations/napari-js/napari-zoom.d.ts +15 -0
  67. package/lib/implementations/osd/openseadragon-visualizer.service.d.ts +21 -0
  68. package/lib/implementations/osd/osd-coordinate-transform.d.ts +6 -0
  69. package/lib/implementations/osd/osd-zoom.d.ts +19 -0
  70. package/lib/implementations/plotly/omics-trace-builders.d.ts +193 -0
  71. package/lib/implementations/plotly/plotly.service.d.ts +9 -0
  72. package/lib/implementations/spatial-data-http/spatial-data-http.service.d.ts +123 -0
  73. package/lib/implementations/spatial-data-http/spatial-wire.d.ts +115 -0
  74. package/lib/plot-mode/plot-mode-controller.d.ts +130 -0
  75. package/lib/routing-visualizer.service.d.ts +49 -6
  76. package/lib/spatial/embedding-compute.d.ts +73 -0
  77. package/lib/spatial/gene-search.d.ts +48 -0
  78. package/lib/spatial/spatial-density.d.ts +73 -0
  79. package/lib/spatial/spatial-encoding.d.ts +155 -0
  80. package/lib/spatial/spatial-expression.d.ts +144 -0
  81. package/lib/spatial/spatial-framing.d.ts +37 -0
  82. package/lib/spatial/spatial-heatmap.d.ts +111 -0
  83. package/lib/spatial/spatial-hover.d.ts +49 -0
  84. package/lib/spatial/spatial-sections.d.ts +59 -0
  85. package/lib/spatial/spatial-selection.d.ts +113 -0
  86. package/lib/spatial/spatial-volume-image.d.ts +67 -0
  87. package/lib/spatial/tsne-worker.d.ts +7 -0
  88. package/lib/spatial/world-grid.d.ts +58 -0
  89. package/lib/spatial-controls/spatial-charts/spatial-charts.component.d.ts +439 -0
  90. package/lib/spatial-controls/spatial-controls.component.d.ts +257 -0
  91. package/lib/store/spatial-selection.service.d.ts +27 -0
  92. package/lib/store/visualizer-store.service.d.ts +13 -0
  93. package/lib/toolbar/toolbar.component.d.ts +36 -8
  94. package/lib/visualization.module.d.ts +28 -24
  95. package/lib/visualizer.component.d.ts +139 -7
  96. package/package.json +6 -2
  97. package/src/lib/assets/spatial-omics-3d.svg +31 -0
  98. package/src/lib/assets/spatial-omics.svg +27 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Framing a 2D scatter on its own coordinates.
3
+ *
4
+ * The 2D spatial camera is normally fitted to the IMAGE, because the observations are recorded in
5
+ * that image's pixel space — frame the image and they come with it. A dataset with no reference
6
+ * image has coordinates in whatever units its source used: seqFISH's span about 5 x 7. Left with
7
+ * the previous image's framing, such a dataset lands as a handful of pixels far off-centre, which
8
+ * looks exactly like nothing having loaded rather than like a framing problem.
9
+ *
10
+ * Pure, like `spatial-density.ts` and the rest of this directory — the renderer applies the result.
11
+ */
12
+ /** Fraction of the viewport the coordinates are fitted into, so the outermost markers are not
13
+ * flush against the edge. Matches napari-js's own fit margin. */
14
+ export const SPATIAL_FIT_MARGIN = 0.95;
15
+ /**
16
+ * Camera centre and zoom that fit `positions` into a `viewportW` x `viewportH` box.
17
+ *
18
+ * `positions` is interleaved `[x0, y0, x1, y1, …]`, the layout the points layer takes.
19
+ *
20
+ * Null when there is nothing to fit — no points, a viewport with no area, or coordinates that are
21
+ * entirely non-finite. Null means LEAVE THE CAMERA ALONE: a caller that substituted a default
22
+ * would move the view for a dataset it cannot frame, which is worse than not moving it.
23
+ *
24
+ * A degenerate extent — one point, or every point on a line — yields a centre but no zoom, since
25
+ * there is no span to divide by. The caller keeps its current zoom and just recentres, rather than
26
+ * dividing by zero into an infinite one.
27
+ */
28
+ export function framePositions(positions, viewportW, viewportH, margin = SPATIAL_FIT_MARGIN) {
29
+ if (positions.length < 2 || viewportW <= 0 || viewportH <= 0)
30
+ return null;
31
+ let xMin = Infinity;
32
+ let xMax = -Infinity;
33
+ let yMin = Infinity;
34
+ let yMax = -Infinity;
35
+ for (let i = 0; i + 1 < positions.length; i += 2) {
36
+ const x = positions[i];
37
+ const y = positions[i + 1];
38
+ // A NaN coordinate is a hole in the data, not a position — including it would
39
+ // poison the extent and frame on nothing.
40
+ if (!Number.isFinite(x) || !Number.isFinite(y))
41
+ continue;
42
+ if (x < xMin)
43
+ xMin = x;
44
+ if (x > xMax)
45
+ xMax = x;
46
+ if (y < yMin)
47
+ yMin = y;
48
+ if (y > yMax)
49
+ yMax = y;
50
+ }
51
+ if (!Number.isFinite(xMin) || !Number.isFinite(yMin))
52
+ return null;
53
+ const center = [(xMin + xMax) / 2, (yMin + yMax) / 2];
54
+ const spanX = xMax - xMin;
55
+ const spanY = yMax - yMin;
56
+ if (spanX <= 0 || spanY <= 0)
57
+ return { center, zoom: null };
58
+ return { center, zoom: margin * Math.min(viewportW / spanX, viewportH / spanY) };
59
+ }
60
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhdGlhbC1mcmFtaW5nLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9zcGF0aWFsL3NwYXRpYWwtZnJhbWluZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7OztHQVVHO0FBRUg7a0VBQ2tFO0FBQ2xFLE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLElBQUksQ0FBQztBQVN2Qzs7Ozs7Ozs7Ozs7O0dBWUc7QUFDSCxNQUFNLFVBQVUsY0FBYyxDQUM1QixTQUF1QixFQUN2QixTQUFpQixFQUNqQixTQUFpQixFQUNqQixNQUFNLEdBQUcsa0JBQWtCO0lBRTNCLElBQUksU0FBUyxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksU0FBUyxJQUFJLENBQUMsSUFBSSxTQUFTLElBQUksQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRTFFLElBQUksSUFBSSxHQUFHLFFBQVEsQ0FBQztJQUNwQixJQUFJLElBQUksR0FBRyxDQUFDLFFBQVEsQ0FBQztJQUNyQixJQUFJLElBQUksR0FBRyxRQUFRLENBQUM7SUFDcEIsSUFBSSxJQUFJLEdBQUcsQ0FBQyxRQUFRLENBQUM7SUFDckIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUMsR0FBRyxTQUFTLENBQUMsTUFBTSxFQUFFLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztRQUNqRCxNQUFNLENBQUMsR0FBRyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDdkIsTUFBTSxDQUFDLEdBQUcsU0FBUyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUMzQiw4RUFBOEU7UUFDOUUsMENBQTBDO1FBQzFDLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUM7WUFBRSxTQUFTO1FBQ3pELElBQUksQ0FBQyxHQUFHLElBQUk7WUFBRSxJQUFJLEdBQUcsQ0FBQyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxHQUFHLElBQUk7WUFBRSxJQUFJLEdBQUcsQ0FBQyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxHQUFHLElBQUk7WUFBRSxJQUFJLEdBQUcsQ0FBQyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxHQUFHLElBQUk7WUFBRSxJQUFJLEdBQUcsQ0FBQyxDQUFDO0lBQ3pCLENBQUM7SUFDRCxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFFbEUsTUFBTSxNQUFNLEdBQXFCLENBQUMsQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ3hFLE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxJQUFJLENBQUM7SUFDMUIsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLElBQUksQ0FBQztJQUMxQixJQUFJLEtBQUssSUFBSSxDQUFDLElBQUksS0FBSyxJQUFJLENBQUM7UUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsQ0FBQztJQUM1RCxPQUFPLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxNQUFNLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxTQUFTLEdBQUcsS0FBSyxFQUFFLFNBQVMsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDO0FBQ25GLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEZyYW1pbmcgYSAyRCBzY2F0dGVyIG9uIGl0cyBvd24gY29vcmRpbmF0ZXMuXG4gKlxuICogVGhlIDJEIHNwYXRpYWwgY2FtZXJhIGlzIG5vcm1hbGx5IGZpdHRlZCB0byB0aGUgSU1BR0UsIGJlY2F1c2UgdGhlIG9ic2VydmF0aW9ucyBhcmUgcmVjb3JkZWQgaW5cbiAqIHRoYXQgaW1hZ2UncyBwaXhlbCBzcGFjZSDigJQgZnJhbWUgdGhlIGltYWdlIGFuZCB0aGV5IGNvbWUgd2l0aCBpdC4gQSBkYXRhc2V0IHdpdGggbm8gcmVmZXJlbmNlXG4gKiBpbWFnZSBoYXMgY29vcmRpbmF0ZXMgaW4gd2hhdGV2ZXIgdW5pdHMgaXRzIHNvdXJjZSB1c2VkOiBzZXFGSVNIJ3Mgc3BhbiBhYm91dCA1IHggNy4gTGVmdCB3aXRoXG4gKiB0aGUgcHJldmlvdXMgaW1hZ2UncyBmcmFtaW5nLCBzdWNoIGEgZGF0YXNldCBsYW5kcyBhcyBhIGhhbmRmdWwgb2YgcGl4ZWxzIGZhciBvZmYtY2VudHJlLCB3aGljaFxuICogbG9va3MgZXhhY3RseSBsaWtlIG5vdGhpbmcgaGF2aW5nIGxvYWRlZCByYXRoZXIgdGhhbiBsaWtlIGEgZnJhbWluZyBwcm9ibGVtLlxuICpcbiAqIFB1cmUsIGxpa2UgYHNwYXRpYWwtZGVuc2l0eS50c2AgYW5kIHRoZSByZXN0IG9mIHRoaXMgZGlyZWN0b3J5IOKAlCB0aGUgcmVuZGVyZXIgYXBwbGllcyB0aGUgcmVzdWx0LlxuICovXG5cbi8qKiBGcmFjdGlvbiBvZiB0aGUgdmlld3BvcnQgdGhlIGNvb3JkaW5hdGVzIGFyZSBmaXR0ZWQgaW50bywgc28gdGhlIG91dGVybW9zdCBtYXJrZXJzIGFyZSBub3RcbiAqICBmbHVzaCBhZ2FpbnN0IHRoZSBlZGdlLiBNYXRjaGVzIG5hcGFyaS1qcydzIG93biBmaXQgbWFyZ2luLiAqL1xuZXhwb3J0IGNvbnN0IFNQQVRJQUxfRklUX01BUkdJTiA9IDAuOTU7XG5cbmV4cG9ydCBpbnRlcmZhY2UgU3BhdGlhbEZyYW1pbmcge1xuICAvKiogV29ybGQgcG9pbnQgdG8gcHV0IGF0IHRoZSBjZW50cmUgb2YgdGhlIHZpZXdwb3J0LiAqL1xuICBjZW50ZXI6IFtudW1iZXIsIG51bWJlcl07XG4gIC8qKiBXb3JsZCB1bml0cyBwZXIgcGl4ZWwsIGludmVydGVkIOKAlCB0aGUgY2FtZXJhJ3MgYHpvb21gLiAqL1xuICB6b29tOiBudW1iZXI7XG59XG5cbi8qKlxuICogQ2FtZXJhIGNlbnRyZSBhbmQgem9vbSB0aGF0IGZpdCBgcG9zaXRpb25zYCBpbnRvIGEgYHZpZXdwb3J0V2AgeCBgdmlld3BvcnRIYCBib3guXG4gKlxuICogYHBvc2l0aW9uc2AgaXMgaW50ZXJsZWF2ZWQgYFt4MCwgeTAsIHgxLCB5MSwg4oCmXWAsIHRoZSBsYXlvdXQgdGhlIHBvaW50cyBsYXllciB0YWtlcy5cbiAqXG4gKiBOdWxsIHdoZW4gdGhlcmUgaXMgbm90aGluZyB0byBmaXQg4oCUIG5vIHBvaW50cywgYSB2aWV3cG9ydCB3aXRoIG5vIGFyZWEsIG9yIGNvb3JkaW5hdGVzIHRoYXQgYXJlXG4gKiBlbnRpcmVseSBub24tZmluaXRlLiBOdWxsIG1lYW5zIExFQVZFIFRIRSBDQU1FUkEgQUxPTkU6IGEgY2FsbGVyIHRoYXQgc3Vic3RpdHV0ZWQgYSBkZWZhdWx0XG4gKiB3b3VsZCBtb3ZlIHRoZSB2aWV3IGZvciBhIGRhdGFzZXQgaXQgY2Fubm90IGZyYW1lLCB3aGljaCBpcyB3b3JzZSB0aGFuIG5vdCBtb3ZpbmcgaXQuXG4gKlxuICogQSBkZWdlbmVyYXRlIGV4dGVudCDigJQgb25lIHBvaW50LCBvciBldmVyeSBwb2ludCBvbiBhIGxpbmUg4oCUIHlpZWxkcyBhIGNlbnRyZSBidXQgbm8gem9vbSwgc2luY2VcbiAqIHRoZXJlIGlzIG5vIHNwYW4gdG8gZGl2aWRlIGJ5LiBUaGUgY2FsbGVyIGtlZXBzIGl0cyBjdXJyZW50IHpvb20gYW5kIGp1c3QgcmVjZW50cmVzLCByYXRoZXIgdGhhblxuICogZGl2aWRpbmcgYnkgemVybyBpbnRvIGFuIGluZmluaXRlIG9uZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZyYW1lUG9zaXRpb25zKFxuICBwb3NpdGlvbnM6IEZsb2F0MzJBcnJheSxcbiAgdmlld3BvcnRXOiBudW1iZXIsXG4gIHZpZXdwb3J0SDogbnVtYmVyLFxuICBtYXJnaW4gPSBTUEFUSUFMX0ZJVF9NQVJHSU4sXG4pOiBTcGF0aWFsRnJhbWluZyB8IHsgY2VudGVyOiBbbnVtYmVyLCBudW1iZXJdOyB6b29tOiBudWxsIH0gfCBudWxsIHtcbiAgaWYgKHBvc2l0aW9ucy5sZW5ndGggPCAyIHx8IHZpZXdwb3J0VyA8PSAwIHx8IHZpZXdwb3J0SCA8PSAwKSByZXR1cm4gbnVsbDtcblxuICBsZXQgeE1pbiA9IEluZmluaXR5O1xuICBsZXQgeE1heCA9IC1JbmZpbml0eTtcbiAgbGV0IHlNaW4gPSBJbmZpbml0eTtcbiAgbGV0IHlNYXggPSAtSW5maW5pdHk7XG4gIGZvciAobGV0IGkgPSAwOyBpICsgMSA8IHBvc2l0aW9ucy5sZW5ndGg7IGkgKz0gMikge1xuICAgIGNvbnN0IHggPSBwb3NpdGlvbnNbaV07XG4gICAgY29uc3QgeSA9IHBvc2l0aW9uc1tpICsgMV07XG4gICAgLy8gQSBOYU4gY29vcmRpbmF0ZSBpcyBhIGhvbGUgaW4gdGhlIGRhdGEsIG5vdCBhIHBvc2l0aW9uIOKAlCBpbmNsdWRpbmcgaXQgd291bGRcbiAgICAvLyBwb2lzb24gdGhlIGV4dGVudCBhbmQgZnJhbWUgb24gbm90aGluZy5cbiAgICBpZiAoIU51bWJlci5pc0Zpbml0ZSh4KSB8fCAhTnVtYmVyLmlzRmluaXRlKHkpKSBjb250aW51ZTtcbiAgICBpZiAoeCA8IHhNaW4pIHhNaW4gPSB4O1xuICAgIGlmICh4ID4geE1heCkgeE1heCA9IHg7XG4gICAgaWYgKHkgPCB5TWluKSB5TWluID0geTtcbiAgICBpZiAoeSA+IHlNYXgpIHlNYXggPSB5O1xuICB9XG4gIGlmICghTnVtYmVyLmlzRmluaXRlKHhNaW4pIHx8ICFOdW1iZXIuaXNGaW5pdGUoeU1pbikpIHJldHVybiBudWxsO1xuXG4gIGNvbnN0IGNlbnRlcjogW251bWJlciwgbnVtYmVyXSA9IFsoeE1pbiArIHhNYXgpIC8gMiwgKHlNaW4gKyB5TWF4KSAvIDJdO1xuICBjb25zdCBzcGFuWCA9IHhNYXggLSB4TWluO1xuICBjb25zdCBzcGFuWSA9IHlNYXggLSB5TWluO1xuICBpZiAoc3BhblggPD0gMCB8fCBzcGFuWSA8PSAwKSByZXR1cm4geyBjZW50ZXIsIHpvb206IG51bGwgfTtcbiAgcmV0dXJuIHsgY2VudGVyLCB6b29tOiBtYXJnaW4gKiBNYXRoLm1pbih2aWV3cG9ydFcgLyBzcGFuWCwgdmlld3BvcnRIIC8gc3BhblkpIH07XG59XG4iXX0=
@@ -0,0 +1,185 @@
1
+ import { NO_CATEGORY } from '../contracts/spatial-dataset.contract';
2
+ /** Default floor for a group to earn a column. */
3
+ export const HEATMAP_MIN_CELLS = 3;
4
+ /**
5
+ * Mean expression of each gene in each group.
6
+ *
7
+ * Null when nothing survives — no genes, no groups, or every group filtered
8
+ * out — so the caller draws no chart rather than an empty grid.
9
+ */
10
+ export function heatmapMatrix(genes, groups, opts = {}) {
11
+ const nGroups = groups.categories.length;
12
+ if (genes.length === 0 || nGroups === 0)
13
+ return null;
14
+ const minCells = opts.minCells ?? HEATMAP_MIN_CELLS;
15
+ const indices = opts.indices;
16
+ const n = indices ? indices.length : groups.codes.length;
17
+ // Cells per group, counted once from the codes rather than per gene: the
18
+ // membership does not depend on which gene is being averaged.
19
+ const groupCount = new Uint32Array(nGroups);
20
+ for (let k = 0; k < n; k++) {
21
+ const i = indices ? indices[k] : k;
22
+ const code = groups.codes[i];
23
+ if (code === NO_CATEGORY || code >= nGroups)
24
+ continue;
25
+ groupCount[code]++;
26
+ }
27
+ const keep = [];
28
+ for (let g = 0; g < nGroups; g++)
29
+ if (groupCount[g] >= minCells)
30
+ keep.push(g);
31
+ if (keep.length === 0)
32
+ return null;
33
+ const rows = genes.map((g) => g.name);
34
+ const cols = keep.map((g) => groups.categories[g]);
35
+ const counts = Uint32Array.from(keep, (g) => groupCount[g]);
36
+ const values = new Float32Array(rows.length * cols.length).fill(NaN);
37
+ for (let r = 0; r < genes.length; r++) {
38
+ const gene = genes[r];
39
+ const sum = new Float64Array(nGroups);
40
+ const measured = new Uint32Array(nGroups);
41
+ for (let k = 0; k < n; k++) {
42
+ const i = indices ? indices[k] : k;
43
+ const code = groups.codes[i];
44
+ if (code === NO_CATEGORY || code >= nGroups)
45
+ continue;
46
+ const v = gene.values[i];
47
+ // Not measured for this cell — skipping keeps the mean about the cells
48
+ // that were actually assayed.
49
+ if (!Number.isFinite(v))
50
+ continue;
51
+ sum[code] += v;
52
+ measured[code]++;
53
+ }
54
+ for (let c = 0; c < keep.length; c++) {
55
+ const g = keep[c];
56
+ if (measured[g] === 0)
57
+ continue; // stays NaN: a gap, not a zero
58
+ values[r * cols.length + c] = sum[g] / measured[g];
59
+ }
60
+ }
61
+ if (opts.zScore !== false)
62
+ zScoreRows(values, rows.length, cols.length);
63
+ // Ranked AFTER scaling, so "strongest signal" means strongest relative to each
64
+ // gene's own spread rather than to whichever gene has the largest units.
65
+ let hiddenCols = 0;
66
+ let outCols = cols;
67
+ let outValues = values;
68
+ let outCounts = counts;
69
+ if (opts.maxCols !== undefined && cols.length > opts.maxCols) {
70
+ const strength = cols.map((_, c) => {
71
+ let peak = 0;
72
+ for (let r = 0; r < rows.length; r++) {
73
+ const v = Math.abs(values[r * cols.length + c]);
74
+ if (Number.isFinite(v) && v > peak)
75
+ peak = v;
76
+ }
77
+ return { c, peak };
78
+ });
79
+ // Strongest first, then original order for ties, so the choice is stable.
80
+ strength.sort((a, b) => (b.peak - a.peak) || (a.c - b.c));
81
+ const picked = strength.slice(0, opts.maxCols).map((s) => s.c).sort((a, b) => a - b);
82
+ hiddenCols = cols.length - picked.length;
83
+ outCols = picked.map((c) => cols[c]);
84
+ outCounts = Uint32Array.from(picked, (c) => counts[c]);
85
+ outValues = new Float32Array(rows.length * picked.length);
86
+ for (let r = 0; r < rows.length; r++) {
87
+ for (let k = 0; k < picked.length; k++) {
88
+ outValues[r * picked.length + k] = values[r * cols.length + picked[k]];
89
+ }
90
+ }
91
+ }
92
+ let lo = Infinity;
93
+ let hi = -Infinity;
94
+ for (let i = 0; i < outValues.length; i++) {
95
+ const v = outValues[i];
96
+ if (!Number.isFinite(v))
97
+ continue;
98
+ if (v < lo)
99
+ lo = v;
100
+ if (v > hi)
101
+ hi = v;
102
+ }
103
+ if (!Number.isFinite(lo))
104
+ return null;
105
+ return {
106
+ rows,
107
+ hiddenCols,
108
+ cols: outCols,
109
+ values: outValues,
110
+ counts: outCounts,
111
+ range: hi > lo ? [lo, hi] : [lo, lo + 1],
112
+ };
113
+ }
114
+ /**
115
+ * Z-score each row in place, over its finite entries only.
116
+ *
117
+ * A row with no spread across groups — a gene expressed evenly everywhere, or
118
+ * measured in only one group — becomes all zeros rather than dividing by zero:
119
+ * "this gene distinguishes nothing here" is the correct reading, and it is the
120
+ * one a NaN row would hide.
121
+ */
122
+ function zScoreRows(values, nRows, nCols) {
123
+ for (let r = 0; r < nRows; r++) {
124
+ const base = r * nCols;
125
+ let sum = 0;
126
+ let count = 0;
127
+ for (let c = 0; c < nCols; c++) {
128
+ const v = values[base + c];
129
+ if (!Number.isFinite(v))
130
+ continue;
131
+ sum += v;
132
+ count++;
133
+ }
134
+ if (count === 0)
135
+ continue;
136
+ const mean = sum / count;
137
+ let variance = 0;
138
+ for (let c = 0; c < nCols; c++) {
139
+ const v = values[base + c];
140
+ if (!Number.isFinite(v))
141
+ continue;
142
+ variance += (v - mean) * (v - mean);
143
+ }
144
+ // Population SD: these are all the groups there are, not a sample of them.
145
+ const sd = Math.sqrt(variance / count);
146
+ for (let c = 0; c < nCols; c++) {
147
+ const v = values[base + c];
148
+ if (!Number.isFinite(v))
149
+ continue;
150
+ values[base + c] = sd > 0 ? (v - mean) / sd : 0;
151
+ }
152
+ }
153
+ }
154
+ /**
155
+ * Groups standing in for individual cells, for the in-selection view.
156
+ *
157
+ * Inside an ROI, "which classes express what" is often the wrong question and
158
+ * "what is in front of me" is the right one — so each cell becomes its own
159
+ * column. Capped: past a few hundred columns a heatmap is a texture, not a
160
+ * readable panel, and the cap is applied by even thinning so the selection is
161
+ * still represented across its whole extent rather than truncated to its first
162
+ * cells.
163
+ *
164
+ * NOTE: every column here holds exactly ONE cell, so the caller must pass
165
+ * `minCells: 1` to {@link heatmapMatrix}. The default floor of
166
+ * {@link HEATMAP_MIN_CELLS} exists to suppress noisy group means and would
167
+ * filter out every column of this view, returning null.
168
+ */
169
+ export function cellsAsGroups(indices, observationCount, max = 200) {
170
+ const step = indices.length > max ? indices.length / max : 1;
171
+ const take = indices.length > max ? max : indices.length;
172
+ const picked = new Uint32Array(take);
173
+ for (let k = 0; k < take; k++)
174
+ picked[k] = indices[Math.floor(k * step)];
175
+ // One category per picked cell: codes are dense over the picked order, so the
176
+ // matrix columns line up with `picked` without a lookup table.
177
+ const codes = new Uint16Array(observationCount).fill(NO_CATEGORY);
178
+ const categories = [];
179
+ for (let k = 0; k < take; k++) {
180
+ codes[picked[k]] = k;
181
+ categories.push(`#${picked[k]}`);
182
+ }
183
+ return { groups: { codes, categories }, indices: picked };
184
+ }
185
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhdGlhbC1oZWF0bWFwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9zcGF0aWFsL3NwYXRpYWwtaGVhdG1hcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sdUNBQXVDLENBQUM7QUEwRnBFLGtEQUFrRDtBQUNsRCxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBRyxDQUFDLENBQUM7QUFFbkM7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsYUFBYSxDQUMzQixLQUE2QixFQUM3QixNQUFxQixFQUNyQixPQUE2QixFQUFFO0lBRS9CLE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDO0lBQ3pDLElBQUksS0FBSyxDQUFDLE1BQU0sS0FBSyxDQUFDLElBQUksT0FBTyxLQUFLLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUNyRCxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsUUFBUSxJQUFJLGlCQUFpQixDQUFDO0lBQ3BELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUM7SUFDN0IsTUFBTSxDQUFDLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQztJQUV6RCx5RUFBeUU7SUFDekUsOERBQThEO0lBQzlELE1BQU0sVUFBVSxHQUFHLElBQUksV0FBVyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzVDLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUMzQixNQUFNLENBQUMsR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ25DLE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDN0IsSUFBSSxJQUFJLEtBQUssV0FBVyxJQUFJLElBQUksSUFBSSxPQUFPO1lBQUUsU0FBUztRQUN0RCxVQUFVLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztJQUNyQixDQUFDO0lBQ0QsTUFBTSxJQUFJLEdBQWEsRUFBRSxDQUFDO0lBQzFCLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxPQUFPLEVBQUUsQ0FBQyxFQUFFO1FBQUUsSUFBSSxVQUFVLENBQUMsQ0FBQyxDQUFDLElBQUksUUFBUTtZQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDOUUsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUVuQyxNQUFNLElBQUksR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDdEMsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ25ELE1BQU0sTUFBTSxHQUFHLFdBQVcsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM1RCxNQUFNLE1BQU0sR0FBRyxJQUFJLFlBQVksQ0FBQyxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFFckUsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUN0QyxNQUFNLElBQUksR0FBRyxLQUFLLENBQUMsQ0FBQyxD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QyxDQUFDLENBQUMsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUMxRCxNQUFNLE1BQU0sR0FBRyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ3JGLFVBQVUsR0FBRyxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7UUFDekMsT0FBTyxHQUFHLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3JDLFNBQVMsR0FBRyxXQUFXLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDdkQsU0FBUyxHQUFHLElBQUksWUFBWSxDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzFELEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUM7WUFDckMsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztnQkFDdkMsU0FBUyxDQUFDLENBQUMsR0FBRyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFD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RyxDQUFDLEVBQUUsQ0FBQyxHQUFHLElBQUksRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQzlCLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDckIsVUFBVSxDQUFDLElBQUksQ0FBQyxJQUFJLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDbkMsQ0FBQztJQUNELE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxDQUFDO0FBQzVELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBOT19DQVRFR09SWSB9IGZyb20gJy4uL2NvbnRyYWN0cy9zcGF0aWFsLWRhdGFzZXQuY29udHJhY3QnO1xuXG4vKipcbiAqIE1lYW4gZXhwcmVzc2lvbiBwZXIgZ3JvdXAsIGZvciB0aGUgZ2VuZSDDlyBjbGFzcyBoZWF0bWFwLlxuICpcbiAqIEEgc2NhdHRlciBjb2xvdXJlZCBieSBvbmUgZ2VuZSBhbnN3ZXJzIFwid2hlcmUgaXMgdGhpcyBnZW5lXCIuIEl0IGNhbm5vdCBhbnN3ZXJcbiAqIFwid2hpY2ggY2xhc3NlcyBleHByZXNzIHdoaWNoIGdlbmVzXCIsIHdoaWNoIGlzIHRoZSBxdWVzdGlvbiBhIG1hcmtlciBwYW5lbCBpc1xuICogYWN0dWFsbHkgcmVhZCBmb3Ig4oCUIGFuZCB0aGUgc3RhbmRhcmQgb2JqZWN0IGZvciB0aGF0IGlzIHRoZSBtZWFuLWV4cHJlc3Npb25cbiAqIG1hdHJpeCBwZXIgY2x1c3RlciAoc2NhbnB5J3MgYG1hdHJpeHBsb3RgL2Bkb3RwbG90YCksIGdlbmVzIGFnYWluc3QgY2xhc3Nlcy5cbiAqXG4gKiBEZWxpYmVyYXRlbHkgTk9UIGNlbGxzIMOXIGdlbmVzIGF0IHRoZSBmdWxsIGRhdGFzZXQ6IDM3NGsgY29sdW1ucyBpcyBub3QgYVxuICogaGVhdG1hcC4gQ2VsbHMgYmVjb21lIGNvbHVtbnMgb25seSBpbnNpZGUgYSBzZWxlY3Rpb24sIHdoZXJlIGEgZmV3IHRob3VzYW5kXG4gKiBvZiB0aGVtIHJlbmRlciBhbmQgdGhlIHF1ZXN0aW9uIGNoYW5nZXMgdG8gXCJ3aGF0IGlzIGluIHRoaXMgcmVnaW9uXCIuXG4gKlxuICogVHdvIHRoaW5ncyB0aGlzIGhhcyB0byBnZXQgcmlnaHQgdG8gYmUgcmVhZGFibGU6XG4gKlxuICogIC0gQSBjZWxsIHdpdGggbm8gbWVhc3VyZW1lbnQgZm9yIGEgZ2VuZSAoYE5hTmApIGlzIFNLSVBQRUQsIG5vdCBjb3VudGVkIGFzXG4gKiAgICB6ZXJvLiBDb3VudGluZyBpdCB3b3VsZCBkcmFnIGEgZ3JvdXAncyBtZWFuIHRvd2FyZHMgYWJzZW5jZSBmb3IgYSByZWFzb25cbiAqICAgIHRoYXQgaGFzIG5vdGhpbmcgdG8gZG8gd2l0aCB0aGUgYmlvbG9neS5cbiAqICAtIFotc2NvcmluZyBpcyBwZXIgR0VORSwgYWNyb3NzIGdyb3Vwcy4gV2l0aG91dCBpdCBhIGdlbmUgZXhwcmVzc2VkIGFuIG9yZGVyXG4gKiAgICBvZiBtYWduaXR1ZGUgYWJvdmUgdGhlIG90aGVycyBzYXR1cmF0ZXMgdGhlIGNvbG91ciBzY2FsZSBhbmQgdGhlIHJlc3Qgb2ZcbiAqICAgIHRoZSBwYW5lbCByZWFkcyBhcyB1bmlmb3JtbHkgYmxhbmsg4oCUIHRoZSBtYXRyaXggd291bGQgc2hvdyB3aGljaCBnZW5lIGlzXG4gKiAgICBsb3VkZXN0IHJhdGhlciB0aGFuIHdoaWNoIGNsYXNzIGV4cHJlc3NlcyB3aGF0LlxuICpcbiAqIFB1cmUg4oCUIG5vIEFuZ3VsYXIsIG5vIFBsb3RseSwgbm8gcmVuZGVyZXIg4oCUIGxpa2UgYHNwYXRpYWwtZGVuc2l0eS50c2AsIGFuZFxuICogdGVzdGVkIHRoZSBzYW1lIHdheS5cbiAqL1xuXG4vKiogT25lIGdlbmUncyB2YWx1ZXMsIHdpdGggdGhlIG5hbWUgdG8gbGFiZWwgaXRzIHJvdy4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSGVhdG1hcEdlbmUge1xuICBuYW1lOiBzdHJpbmc7XG4gIC8qKiBPbmUgdmFsdWUgcGVyIG9ic2VydmF0aW9uOyBgTmFOYCB3aGVyZSB0aGUgZ2VuZSB3YXMgbm90IG1lYXN1cmVkLiAqL1xuICB2YWx1ZXM6IEZsb2F0MzJBcnJheTtcbn1cblxuLyoqIFRoZSBjb2x1bW5zIHRvIGFnZ3JlZ2F0ZSBpbnRvOiBhIGNhdGVnb3JpY2FsIGNvbHVtbidzIGNhdGVnb3JpZXMuICovXG5leHBvcnQgaW50ZXJmYWNlIEhlYXRtYXBHcm91cHMge1xuICAvKiogUGVyLW9ic2VydmF0aW9uIGNhdGVnb3J5IGluZGV4LCBvciB7QGxpbmsgTk9fQ0FURUdPUll9LiAqL1xuICBjb2RlczogVWludDE2QXJyYXk7XG4gIGNhdGVnb3JpZXM6IHJlYWRvbmx5IHN0cmluZ1tdO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEhlYXRtYXBNYXRyaXhPcHRpb25zIHtcbiAgLyoqIE9ic2VydmF0aW9ucyB0byBpbmNsdWRlOyBldmVyeSBvbmUgd2hlbiBhYnNlbnQgKGFuIFJPSSBzZWxlY3Rpb24pLiAqL1xuICBpbmRpY2VzPzogVWludDMyQXJyYXk7XG4gIC8qKlxuICAgKiBaLXNjb3JlIGVhY2ggZ2VuZSBhY3Jvc3MgdGhlIGdyb3Vwcywgc28gb25lIGxvdWQgZ2VuZSBjYW5ub3QgZmxhdHRlbiB0aGVcbiAgICogcGFuZWwuIE9uIGJ5IGRlZmF1bHQg4oCUIHRoZSByYXcgbWVhbnMgYXJlIG9ubHkgcmVhZGFibGUgd2hlbiBldmVyeSBnZW5lIGlzXG4gICAqIG9uIGEgY29tcGFyYWJsZSBzY2FsZS5cbiAgICovXG4gIHpTY29yZT86IGJvb2xlYW47XG4gIC8qKiBEcm9wIGdyb3VwcyB3aXRoIGZld2VyIHRoYW4gdGhpcyBtYW55IG1lYXN1cmVkIGNlbGxzLiBBIG1lYW4gb3ZlciBvbmUgb3JcbiAgICogIHR3byBjZWxscyBpcyBub2lzZSBwcmVzZW50ZWQgd2l0aCB0aGUgc2FtZSB3ZWlnaHQgYXMgYSByZWFsIG9uZS4gKi9cbiAgbWluQ2VsbHM/OiBudW1iZXI7XG4gIC8qKlxuICAgKiBLZWVwIGF0IG1vc3QgdGhpcyBtYW55IGNvbHVtbnMsIGNob3NlbiBieSB0aGUgU1RST05HRVNUIHNpZ25hbCBhbnkgcGlja2VkXG4gICAqIGdlbmUgc2hvd3MgaW4gdGhlbS5cbiAgICpcbiAgICogYHN1YmNsYXNzYCBoYXMgMzM4IGNhdGVnb3JpZXMuIERyYXduIGluIGZ1bGwgdGhhdCBpcyBhIHRleHR1cmUgcmF0aGVyIHRoYW4gYVxuICAgKiBwYW5lbCDigJQgZXZlcnkgY29sdW1uIGEgY291cGxlIG9mIHBpeGVscyB3aWRlLCBldmVyeSBsYWJlbCB1bnJlYWRhYmxlIOKAlCBhbmRcbiAgICogdGhlIHF1ZXN0aW9uIGJlaW5nIGFza2VkIChcIndoaWNoIGdyb3VwcyBleHByZXNzIG15IGdlbmVzXCIpIGlzIGFuc3dlcmVkIGJ5XG4gICAqIHRoZSBoYW5kZnVsIG9mIGdyb3VwcyB3aGVyZSBhIGdlbmUgYWN0dWFsbHkgc3RhbmRzIG91dC4gUmFua2luZyBieVxuICAgKiBtYXggfHZhbHVlfCBhY3Jvc3MgdGhlIHJvd3Mgc3VyZmFjZXMgZXhhY3RseSB0aG9zZTsgcmFua2luZyBieSBjZWxsIGNvdW50XG4gICAqIHdvdWxkIGluc3RlYWQgc3VyZmFjZSB0aGUgYmlnZ2VzdCBncm91cHMsIHdoaWNoIGlzIGEgZGlmZmVyZW50IHF1ZXN0aW9uLlxuICAgKlxuICAgKiBVbnNldCBrZWVwcyBldmVyeSBjb2x1bW4gdGhhdCBjbGVhcmVkIGBtaW5DZWxsc2AuXG4gICAqL1xuICBtYXhDb2xzPzogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEhlYXRtYXBNYXRyaXgge1xuICAvKiogR2VuZSBuYW1lcywgb25lIHBlciByb3csIGluIHRoZSBvcmRlciBnaXZlbi4gKi9cbiAgcm93czogc3RyaW5nW107XG4gIC8qKiBDb2x1bW5zIGRyb3BwZWQgYnkgYG1heENvbHNgLCBzbyB0aGUgY2FsbGVyIGNhbiBzYXkgaG93IG1hbnkgYXJlIGhpZGRlblxuICAgKiAgcmF0aGVyIHRoYW4gc2lsZW50bHkgc2hvd2luZyBhIHN1YnNldCBhcyBpZiBpdCB3ZXJlIGV2ZXJ5dGhpbmcuICovXG4gIGhpZGRlbkNvbHM6IG51bWJlcjtcbiAgLyoqIEdyb3VwIGxhYmVscywgb25lIHBlciBjb2x1bW4g4oCUIG9ubHkgdGhlIGdyb3VwcyB0aGF0IHN1cnZpdmVkIGBtaW5DZWxsc2AuICovXG4gIGNvbHM6IHN0cmluZ1tdO1xuICAvKipcbiAgICogYHJvd3MubGVuZ3RoIMOXIGNvbHMubGVuZ3RoYCwgcm93LW1ham9yOiBgdmFsdWVzW3IgKiBjb2xzLmxlbmd0aCArIGNdYC5cbiAgICogYE5hTmAgd2hlcmUgYSBncm91cCBoYWQgbm90aGluZyBtZWFzdXJlZCBmb3IgdGhhdCBnZW5lLCB3aGljaCB0aGUgY2FsbGVyXG4gICAqIGRyYXdzIGFzIGEgZ2FwIHJhdGhlciB0aGFuIGFzIGEgemVyby5cbiAgICovXG4gIHZhbHVlczogRmxvYXQzMkFycmF5O1xuICAvKiogTWVhc3VyZWQgY2VsbHMgYmVoaW5kIGVhY2ggY29sdW1uLCBpbmRleC1hbGlnbmVkIHdpdGgge0BsaW5rIGNvbHN9LiAqL1xuICBjb3VudHM6IFVpbnQzMkFycmF5O1xuICAvKiogRmluaXRlIHJhbmdlIG9mIGB2YWx1ZXNgLCBmb3IgYSBzeW1tZXRyaWMgb3IgYWJzb2x1dGUgY29sb3VyIHNjYWxlLiAqL1xuICByYW5nZTogW251bWJlciwgbnVtYmVyXTtcbn1cblxuLyoqIERlZmF1bHQgZmxvb3IgZm9yIGEgZ3JvdXAgdG8gZWFybiBhIGNvbHVtbi4gKi9cbmV4cG9ydCBjb25zdCBIRUFUTUFQX01JTl9DRUxMUyA9IDM7XG5cbi8qKlxuICogTWVhbiBleHByZXNzaW9uIG9mIGVhY2ggZ2VuZSBpbiBlYWNoIGdyb3VwLlxuICpcbiAqIE51bGwgd2hlbiBub3RoaW5nIHN1cnZpdmVzIOKAlCBubyBnZW5lcywgbm8gZ3JvdXBzLCBvciBldmVyeSBncm91cCBmaWx0ZXJlZFxuICogb3V0IOKAlCBzbyB0aGUgY2FsbGVyIGRyYXdzIG5vIGNoYXJ0IHJhdGhlciB0aGFuIGFuIGVtcHR5IGdyaWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBoZWF0bWFwTWF0cml4KFxuICBnZW5lczogcmVhZG9ubHkgSGVhdG1hcEdlbmVbXSxcbiAgZ3JvdXBzOiBIZWF0bWFwR3JvdXBzLFxuICBvcHRzOiBIZWF0bWFwTWF0cml4T3B0aW9ucyA9IHt9LFxuKTogSGVhdG1hcE1hdHJpeCB8IG51bGwge1xuICBjb25zdCBuR3JvdXBzID0gZ3JvdXBzLmNhdGVnb3JpZXMubGVuZ3RoO1xuICBpZiAoZ2VuZXMubGVuZ3RoID09PSAwIHx8IG5Hcm91cHMgPT09IDApIHJldHVybiBudWxsO1xuICBjb25zdCBtaW5DZWxscyA9IG9wdHMubWluQ2VsbHMgPz8gSEVBVE1BUF9NSU5fQ0VMTFM7XG4gIGNvbnN0IGluZGljZXMgPSBvcHRzLmluZGljZXM7XG4gIGNvbnN0IG4gPSBpbmRpY2VzID8gaW5kaWNlcy5sZW5ndGggOiBncm91cHMuY29kZXMubGVuZ3RoO1xuXG4gIC8vIENlbGxzIHBlciBncm91cCwgY291bnRlZCBvbmNlIGZyb20gdGhlIGNvZGVzIHJhdGhlciB0aGFuIHBlciBnZW5lOiB0aGVcbiAgLy8gbWVtYmVyc2hpcCBkb2VzIG5vdCBkZXBlbmQgb24gd2hpY2ggZ2VuZSBpcyBiZWluZyBhdmVyYWdlZC5cbiAgY29uc3QgZ3JvdXBDb3VudCA9IG5ldyBVaW50MzJBcnJheShuR3JvdXBzKTtcbiAgZm9yIChsZXQgayA9IDA7IGsgPCBuOyBrKyspIHtcbiAgICBjb25zdCBpID0gaW5kaWNlcyA/IGluZGljZXNba10gOiBrO1xuICAgIGNvbnN0IGNvZGUgPSBncm91cHMuY29kZXNbaV07XG4gICAgaWYgKGNvZGUgPT09IE5PX0NBVEVHT1JZIHx8IGNvZGUgPj0gbkdyb3VwcykgY29udGludWU7XG4gICAgZ3JvdXBDb3VudFtjb2RlXSsrO1xuICB9XG4gIGNvbnN0IGtlZXA6IG51bWJlcltdID0gW107XG4gIGZvciAobGV0IGcgPSAwOyBnIDwgbkdyb3VwczsgZysrKSBpZiAoZ3JvdXBDb3VudFtnXSA+PSBtaW5DZWxscykga2VlcC5wdXNoKGcpO1xuICBpZiAoa2VlcC5sZW5ndGggPT09IDApIHJldHVybiBudWxsO1xuXG4gIGNvbnN0IHJvd3MgPSBnZW5lcy5tYXAoKGcpID0+IGcubmFtZSk7XG4gIGNvbnN0IGNvbHMgPSBrZWVwLm1hcCgoZykgPT4gZ3JvdXBzLmNhdGVnb3JpZXNbZ10pO1xuICBjb25zdCBjb3VudHMgPSBVaW50MzJBcnJheS5mcm9tKGtlZXAsIChnKSA9PiBncm91cENvdW50W2ddKTtcbiAgY29uc3QgdmFsdWVzID0gbmV3IEZsb2F0MzJBcnJheShyb3dzLmxlbmd0aCAqIGNvbHMubGVuZ3RoKS5maWxsKE5hTik7XG5cbiAgZm9yIChsZXQgciA9IDA7IHIgPCBnZW5lcy5sZW5ndGg7IHIrKykge1xuICAgIGNvbnN0IGdlbmUgPSBnZW5lc1tyXTtcbiAgICBjb25zdCBzdW0gPSBuZXcgRmxvYXQ2NEFycmF5KG5Hcm91cHMpO1xuICAgIGNvbnN0IG1lYXN1cmVkID0gbmV3IFVpbnQzMkFycmF5KG5Hcm91cHMpO1xuICAgIGZvciAobGV0IGsgPSAwOyBrIDwgbjsgaysrKSB7XG4gICAgICBjb25zdCBpID0gaW5kaWNlcyA/IGluZGljZXNba10gOiBrO1xuICAgICAgY29uc3QgY29kZSA9IGdyb3Vwcy5jb2Rlc1tpXTtcbiAgICAgIGlmIChjb2RlID09PSBOT19DQVRFR09SWSB8fCBjb2RlID49IG5Hcm91cHMpIGNvbnRpbnVlO1xuICAgICAgY29uc3QgdiA9IGdlbmUudmFsdWVzW2ldO1xuICAgICAgLy8gTm90IG1lYXN1cmVkIGZvciB0aGlzIGNlbGwg4oCUIHNraXBwaW5nIGtlZXBzIHRoZSBtZWFuIGFib3V0IHRoZSBjZWxsc1xuICAgICAgLy8gdGhhdCB3ZXJlIGFjdHVhbGx5IGFzc2F5ZWQuXG4gICAgICBpZiAoIU51bWJlci5pc0Zpbml0ZSh2KSkgY29udGludWU7XG4gICAgICBzdW1bY29kZV0gKz0gdjtcbiAgICAgIG1lYXN1cmVkW2NvZGVdKys7XG4gICAgfVxuICAgIGZvciAobGV0IGMgPSAwOyBjIDwga2VlcC5sZW5ndGg7IGMrKykge1xuICAgICAgY29uc3QgZyA9IGtlZXBbY107XG4gICAgICBpZiAobWVhc3VyZWRbZ10gPT09IDApIGNvbnRpbnVlOyAvLyBzdGF5cyBOYU46IGEgZ2FwLCBub3QgYSB6ZXJvXG4gICAgICB2YWx1ZXNbciAqIGNvbHMubGVuZ3RoICsgY10gPSBzdW1bZ10gLyBtZWFzdXJlZFtnXTtcbiAgICB9XG4gIH1cblxuICBpZiAob3B0cy56U2NvcmUgIT09IGZhbHNlKSB6U2NvcmVSb3dzKHZhbHVlcywgcm93cy5sZW5ndGgsIGNvbHMubGVuZ3RoKTtcblxuICAvLyBSYW5rZWQgQUZURVIgc2NhbGluZywgc28gXCJzdHJvbmdlc3Qgc2lnbmFsXCIgbWVhbnMgc3Ryb25nZXN0IHJlbGF0aXZlIHRvIGVhY2hcbiAgLy8gZ2VuZSdzIG93biBzcHJlYWQgcmF0aGVyIHRoYW4gdG8gd2hpY2hldmVyIGdlbmUgaGFzIHRoZSBsYXJnZXN0IHVuaXRzLlxuICBsZXQgaGlkZGVuQ29scyA9IDA7XG4gIGxldCBvdXRDb2xzID0gY29scztcbiAgbGV0IG91dFZhbHVlcyA9IHZhbHVlcztcbiAgbGV0IG91dENvdW50cyA9IGNvdW50cztcbiAgaWYgKG9wdHMubWF4Q29scyAhPT0gdW5kZWZpbmVkICYmIGNvbHMubGVuZ3RoID4gb3B0cy5tYXhDb2xzKSB7XG4gICAgY29uc3Qgc3RyZW5ndGggPSBjb2xzLm1hcCgoXywgYykgPT4ge1xuICAgICAgbGV0IHBlYWsgPSAwO1xuICAgICAgZm9yIChsZXQgciA9IDA7IHIgPCByb3dzLmxlbmd0aDsgcisrKSB7XG4gICAgICAgIGNvbnN0IHYgPSBNYXRoLmFicyh2YWx1ZXNbciAqIGNvbHMubGVuZ3RoICsgY10pO1xuICAgICAgICBpZiAoTnVtYmVyLmlzRmluaXRlKHYpICYmIHYgPiBwZWFrKSBwZWFrID0gdjtcbiAgICAgIH1cbiAgICAgIHJldHVybiB7IGMsIHBlYWsgfTtcbiAgICB9KTtcbiAgICAvLyBTdHJvbmdlc3QgZmlyc3QsIHRoZW4gb3JpZ2luYWwgb3JkZXIgZm9yIHRpZXMsIHNvIHRoZSBjaG9pY2UgaXMgc3RhYmxlLlxuICAgIHN0cmVuZ3RoLnNvcnQoKGEsIGIpID0+IChiLnBlYWsgLSBhLnBlYWspIHx8IChhLmMgLSBiLmMpKTtcbiAgICBjb25zdCBwaWNrZWQgPSBzdHJlbmd0aC5zbGljZSgwLCBvcHRzLm1heENvbHMpLm1hcCgocykgPT4gcy5jKS5zb3J0KChhLCBiKSA9PiBhIC0gYik7XG4gICAgaGlkZGVuQ29scyA9IGNvbHMubGVuZ3RoIC0gcGlja2VkLmxlbmd0aDtcbiAgICBvdXRDb2xzID0gcGlja2VkLm1hcCgoYykgPT4gY29sc1tjXSk7XG4gICAgb3V0Q291bnRzID0gVWludDMyQXJyYXkuZnJvbShwaWNrZWQsIChjKSA9PiBjb3VudHNbY10pO1xuICAgIG91dFZhbHVlcyA9IG5ldyBGbG9hdDMyQXJyYXkocm93cy5sZW5ndGggKiBwaWNrZWQubGVuZ3RoKTtcbiAgICBmb3IgKGxldCByID0gMDsgciA8IHJvd3MubGVuZ3RoOyByKyspIHtcbiAgICAgIGZvciAobGV0IGsgPSAwOyBrIDwgcGlja2VkLmxlbmd0aDsgaysrKSB7XG4gICAgICAgIG91dFZhbHVlc1tyICogcGlja2VkLmxlbmd0aCArIGtdID0gdmFsdWVzW3IgKiBjb2xzLmxlbmd0aCArIHBpY2tlZFtrXV07XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgbGV0IGxvID0gSW5maW5pdHk7XG4gIGxldCBoaSA9IC1JbmZpbml0eTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBvdXRWYWx1ZXMubGVuZ3RoOyBpKyspIHtcbiAgICBjb25zdCB2ID0gb3V0VmFsdWVzW2ldO1xuICAgIGlmICghTnVtYmVyLmlzRmluaXRlKHYpKSBjb250aW51ZTtcbiAgICBpZiAodiA8IGxvKSBsbyA9IHY7XG4gICAgaWYgKHYgPiBoaSkgaGkgPSB2O1xuICB9XG4gIGlmICghTnVtYmVyLmlzRmluaXRlKGxvKSkgcmV0dXJuIG51bGw7XG5cbiAgcmV0dXJuIHtcbiAgICByb3dzLFxuICAgIGhpZGRlbkNvbHMsXG4gICAgY29sczogb3V0Q29scyxcbiAgICB2YWx1ZXM6IG91dFZhbHVlcyxcbiAgICBjb3VudHM6IG91dENvdW50cyxcbiAgICByYW5nZTogaGkgPiBsbyA/IFtsbywgaGldIDogW2xvLCBsbyArIDFdLFxuICB9O1xufVxuXG4vKipcbiAqIFotc2NvcmUgZWFjaCByb3cgaW4gcGxhY2UsIG92ZXIgaXRzIGZpbml0ZSBlbnRyaWVzIG9ubHkuXG4gKlxuICogQSByb3cgd2l0aCBubyBzcHJlYWQgYWNyb3NzIGdyb3VwcyDigJQgYSBnZW5lIGV4cHJlc3NlZCBldmVubHkgZXZlcnl3aGVyZSwgb3JcbiAqIG1lYXN1cmVkIGluIG9ubHkgb25lIGdyb3VwIOKAlCBiZWNvbWVzIGFsbCB6ZXJvcyByYXRoZXIgdGhhbiBkaXZpZGluZyBieSB6ZXJvOlxuICogXCJ0aGlzIGdlbmUgZGlzdGluZ3Vpc2hlcyBub3RoaW5nIGhlcmVcIiBpcyB0aGUgY29ycmVjdCByZWFkaW5nLCBhbmQgaXQgaXMgdGhlXG4gKiBvbmUgYSBOYU4gcm93IHdvdWxkIGhpZGUuXG4gKi9cbmZ1bmN0aW9uIHpTY29yZVJvd3ModmFsdWVzOiBGbG9hdDMyQXJyYXksIG5Sb3dzOiBudW1iZXIsIG5Db2xzOiBudW1iZXIpOiB2b2lkIHtcbiAgZm9yIChsZXQgciA9IDA7IHIgPCBuUm93czsgcisrKSB7XG4gICAgY29uc3QgYmFzZSA9IHIgKiBuQ29scztcbiAgICBsZXQgc3VtID0gMDtcbiAgICBsZXQgY291bnQgPSAwO1xuICAgIGZvciAobGV0IGMgPSAwOyBjIDwgbkNvbHM7IGMrKykge1xuICAgICAgY29uc3QgdiA9IHZhbHVlc1tiYXNlICsgY107XG4gICAgICBpZiAoIU51bWJlci5pc0Zpbml0ZSh2KSkgY29udGludWU7XG4gICAgICBzdW0gKz0gdjtcbiAgICAgIGNvdW50Kys7XG4gICAgfVxuICAgIGlmIChjb3VudCA9PT0gMCkgY29udGludWU7XG4gICAgY29uc3QgbWVhbiA9IHN1bSAvIGNvdW50O1xuICAgIGxldCB2YXJpYW5jZSA9IDA7XG4gICAgZm9yIChsZXQgYyA9IDA7IGMgPCBuQ29sczsgYysrKSB7XG4gICAgICBjb25zdCB2ID0gdmFsdWVzW2Jhc2UgKyBjXTtcbiAgICAgIGlmICghTnVtYmVyLmlzRmluaXRlKHYpKSBjb250aW51ZTtcbiAgICAgIHZhcmlhbmNlICs9ICh2IC0gbWVhbikgKiAodiAtIG1lYW4pO1xuICAgIH1cbiAgICAvLyBQb3B1bGF0aW9uIFNEOiB0aGVzZSBhcmUgYWxsIHRoZSBncm91cHMgdGhlcmUgYXJlLCBub3QgYSBzYW1wbGUgb2YgdGhlbS5cbiAgICBjb25zdCBzZCA9IE1hdGguc3FydCh2YXJpYW5jZSAvIGNvdW50KTtcbiAgICBmb3IgKGxldCBjID0gMDsgYyA8IG5Db2xzOyBjKyspIHtcbiAgICAgIGNvbnN0IHYgPSB2YWx1ZXNbYmFzZSArIGNdO1xuICAgICAgaWYgKCFOdW1iZXIuaXNGaW5pdGUodikpIGNvbnRpbnVlO1xuICAgICAgdmFsdWVzW2Jhc2UgKyBjXSA9IHNkID4gMCA/ICh2IC0gbWVhbikgLyBzZCA6IDA7XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogR3JvdXBzIHN0YW5kaW5nIGluIGZvciBpbmRpdmlkdWFsIGNlbGxzLCBmb3IgdGhlIGluLXNlbGVjdGlvbiB2aWV3LlxuICpcbiAqIEluc2lkZSBhbiBST0ksIFwid2hpY2ggY2xhc3NlcyBleHByZXNzIHdoYXRcIiBpcyBvZnRlbiB0aGUgd3JvbmcgcXVlc3Rpb24gYW5kXG4gKiBcIndoYXQgaXMgaW4gZnJvbnQgb2YgbWVcIiBpcyB0aGUgcmlnaHQgb25lIOKAlCBzbyBlYWNoIGNlbGwgYmVjb21lcyBpdHMgb3duXG4gKiBjb2x1bW4uIENhcHBlZDogcGFzdCBhIGZldyBodW5kcmVkIGNvbHVtbnMgYSBoZWF0bWFwIGlzIGEgdGV4dHVyZSwgbm90IGFcbiAqIHJlYWRhYmxlIHBhbmVsLCBhbmQgdGhlIGNhcCBpcyBhcHBsaWVkIGJ5IGV2ZW4gdGhpbm5pbmcgc28gdGhlIHNlbGVjdGlvbiBpc1xuICogc3RpbGwgcmVwcmVzZW50ZWQgYWNyb3NzIGl0cyB3aG9sZSBleHRlbnQgcmF0aGVyIHRoYW4gdHJ1bmNhdGVkIHRvIGl0cyBmaXJzdFxuICogY2VsbHMuXG4gKlxuICogTk9URTogZXZlcnkgY29sdW1uIGhlcmUgaG9sZHMgZXhhY3RseSBPTkUgY2VsbCwgc28gdGhlIGNhbGxlciBtdXN0IHBhc3NcbiAqIGBtaW5DZWxsczogMWAgdG8ge0BsaW5rIGhlYXRtYXBNYXRyaXh9LiBUaGUgZGVmYXVsdCBmbG9vciBvZlxuICoge0BsaW5rIEhFQVRNQVBfTUlOX0NFTExTfSBleGlzdHMgdG8gc3VwcHJlc3Mgbm9pc3kgZ3JvdXAgbWVhbnMgYW5kIHdvdWxkXG4gKiBmaWx0ZXIgb3V0IGV2ZXJ5IGNvbHVtbiBvZiB0aGlzIHZpZXcsIHJldHVybmluZyBudWxsLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY2VsbHNBc0dyb3VwcyhcbiAgaW5kaWNlczogVWludDMyQXJyYXksXG4gIG9ic2VydmF0aW9uQ291bnQ6IG51bWJlcixcbiAgbWF4ID0gMjAwLFxuKTogeyBncm91cHM6IEhlYXRtYXBHcm91cHM7IGluZGljZXM6IFVpbnQzMkFycmF5IH0ge1xuICBjb25zdCBzdGVwID0gaW5kaWNlcy5sZW5ndGggPiBtYXggPyBpbmRpY2VzLmxlbmd0aCAvIG1heCA6IDE7XG4gIGNvbnN0IHRha2UgPSBpbmRpY2VzLmxlbmd0aCA+IG1heCA/IG1heCA6IGluZGljZXMubGVuZ3RoO1xuICBjb25zdCBwaWNrZWQgPSBuZXcgVWludDMyQXJyYXkodGFrZSk7XG4gIGZvciAobGV0IGsgPSAwOyBrIDwgdGFrZTsgaysrKSBwaWNrZWRba10gPSBpbmRpY2VzW01hdGguZmxvb3IoayAqIHN0ZXApXTtcblxuICAvLyBPbmUgY2F0ZWdvcnkgcGVyIHBpY2tlZCBjZWxsOiBjb2RlcyBhcmUgZGVuc2Ugb3ZlciB0aGUgcGlja2VkIG9yZGVyLCBzbyB0aGVcbiAgLy8gbWF0cml4IGNvbHVtbnMgbGluZSB1cCB3aXRoIGBwaWNrZWRgIHdpdGhvdXQgYSBsb29rdXAgdGFibGUuXG4gIGNvbnN0IGNvZGVzID0gbmV3IFVpbnQxNkFycmF5KG9ic2VydmF0aW9uQ291bnQpLmZpbGwoTk9fQ0FURUdPUlkpO1xuICBjb25zdCBjYXRlZ29yaWVzOiBzdHJpbmdbXSA9IFtdO1xuICBmb3IgKGxldCBrID0gMDsgayA8IHRha2U7IGsrKykge1xuICAgIGNvZGVzW3BpY2tlZFtrXV0gPSBrO1xuICAgIGNhdGVnb3JpZXMucHVzaChgIyR7cGlja2VkW2tdfWApO1xuICB9XG4gIHJldHVybiB7IGdyb3VwczogeyBjb2RlcywgY2F0ZWdvcmllcyB9LCBpbmRpY2VzOiBwaWNrZWQgfTtcbn1cbiJdfQ==
@@ -0,0 +1,79 @@
1
+ /**
2
+ * What is under the cursor, and what to say about it.
3
+ *
4
+ * A coloured cloud answers "how is this annotation distributed"; it cannot answer
5
+ * "which class is THAT" — the legend has 34 entries, several of them similar
6
+ * colours, and matching a dot to a swatch by eye is exactly the task a tooltip
7
+ * exists to remove.
8
+ *
9
+ * Pure — no DOM, no camera, no data access. The renderer supplies screen
10
+ * positions (it owns the projection) and the active colour source; everything
11
+ * here is arithmetic and string formatting, so it is tested directly.
12
+ */
13
+ /**
14
+ * Index of the drawn observation nearest `(x, y)` within `maxDist` screen pixels,
15
+ * or -1.
16
+ *
17
+ * `screen` is `[x0, y0, x1, y1, …]` indexed BY OBSERVATION, with NaN for anything
18
+ * not currently drawn — off screen, behind the eye, or on a hidden section. NaN
19
+ * fails every comparison, so those are skipped without a special case.
20
+ *
21
+ * Ties go to the LATER observation, which is the one drawn on top and therefore
22
+ * the one the user believes they are pointing at.
23
+ */
24
+ export function nearestObservation(screen, x, y, maxDist) {
25
+ const limit = maxDist * maxDist;
26
+ let best = -1;
27
+ let bestDist = Infinity;
28
+ const n = screen.length >> 1;
29
+ for (let i = 0; i < n; i++) {
30
+ const dx = screen[i * 2] - x;
31
+ const dy = screen[i * 2 + 1] - y;
32
+ const d = dx * dx + dy * dy;
33
+ // `<=` so a later point at the same distance wins: it is drawn on top.
34
+ if (d <= bestDist && d <= limit) {
35
+ bestDist = d;
36
+ best = i;
37
+ }
38
+ }
39
+ return best;
40
+ }
41
+ /** A number for a tooltip: enough digits to distinguish, few enough to read. */
42
+ function formatValue(v) {
43
+ if (!Number.isFinite(v))
44
+ return '—';
45
+ if (v === 0)
46
+ return '0';
47
+ const magnitude = Math.abs(v);
48
+ if (magnitude >= 1000 || magnitude < 0.01)
49
+ return v.toExponential(2);
50
+ return String(Math.round(v * 100) / 100);
51
+ }
52
+ /**
53
+ * The tooltip's lines for one observation: what it is, then which column said so.
54
+ *
55
+ * Null when the source cannot describe this observation — a code outside the
56
+ * category list, or a value that was never measured. Saying nothing is better
57
+ * than a tooltip that reads "undefined", and better than inventing a label for a
58
+ * cell whose annotation is genuinely missing.
59
+ */
60
+ export function hoverText(source, index) {
61
+ if (!source || index < 0)
62
+ return null;
63
+ if (source.kind === 'categorical') {
64
+ const code = source.codes?.[index];
65
+ const label = code === undefined ? undefined : source.categories?.[code];
66
+ if (label === undefined)
67
+ return null;
68
+ return [label, source.name];
69
+ }
70
+ // Guarded rather than indexed straight: this runs from a pointermove handler,
71
+ // so a malformed source would throw on every mouse movement — far worse than a
72
+ // tooltip that stays quiet.
73
+ const value = source.values?.[index];
74
+ if (value === undefined || !Number.isFinite(value))
75
+ return null;
76
+ const unit = source.unit ? ` ${source.unit}` : '';
77
+ return [`${formatValue(value)}${unit}`, source.name];
78
+ }
79
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhdGlhbC1ob3Zlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvc3BhdGlhbC9zcGF0aWFsLWhvdmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7OztHQVdHO0FBcUJIOzs7Ozs7Ozs7O0dBVUc7QUFDSCxNQUFNLFVBQVUsa0JBQWtCLENBQ2hDLE1BQW9CLEVBQ3BCLENBQVMsRUFDVCxDQUFTLEVBQ1QsT0FBZTtJQUVmLE1BQU0sS0FBSyxHQUFHLE9BQU8sR0FBRyxPQUFPLENBQUM7SUFDaEMsSUFBSSxJQUFJLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDZCxJQUFJLFFBQVEsR0FBRyxRQUFRLENBQUM7SUFDeEIsTUFBTSxDQUFDLEdBQUcsTUFBTSxDQUFDLE1BQU0sSUFBSSxDQUFDLENBQUM7SUFDN0IsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQzNCLE1BQU0sRUFBRSxHQUFHLE1BQU0sQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQzdCLE1BQU0sRUFBRSxHQUFHLE1BQU0sQ0FBQyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNqQyxNQUFNLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLENBQUM7UUFDNUIsdUVBQXVFO1FBQ3ZFLElBQUksQ0FBQyxJQUFJLFFBQVEsSUFBSSxDQUFDLElBQUksS0FBSyxFQUFFLENBQUM7WUFDaEMsUUFBUSxHQUFHLENBQUMsQ0FBQztZQUNiLElBQUksR0FBRyxDQUFDLENBQUM7UUFDWCxDQUFDO0lBQ0gsQ0FBQztJQUNELE9BQU8sSUFBSSxDQUFDO0FBQ2QsQ0FBQztBQUVELGdGQUFnRjtBQUNoRixTQUFTLFdBQVcsQ0FBQyxDQUFTO0lBQzVCLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztRQUFFLE9BQU8sR0FBRyxDQUFDO0lBQ3BDLElBQUksQ0FBQyxLQUFLLENBQUM7UUFBRSxPQUFPLEdBQUcsQ0FBQztJQUN4QixNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzlCLElBQUksU0FBUyxJQUFJLElBQUksSUFBSSxTQUFTLEdBQUcsSUFBSTtRQUFFLE9BQU8sQ0FBQyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNyRSxPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxHQUFHLENBQUMsR0FBRyxHQUFHLENBQUMsQ0FBQztBQUMzQyxDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILE1BQU0sVUFBVSxTQUFTLENBQUMsTUFBMEIsRUFBRSxLQUFhO0lBQ2pFLElBQUksQ0FBQyxNQUFNLElBQUksS0FBSyxHQUFHLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUN0QyxJQUFJLE1BQU0sQ0FBQyxJQUFJLEtBQUssYUFBYSxFQUFFLENBQUM7UUFDbEMsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLEtBQUssRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ25DLE1BQU0sS0FBSyxHQUFHLElBQUksS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3pFLElBQUksS0FBSyxLQUFLLFNBQVM7WUFBRSxPQUFPLElBQUksQ0FBQztRQUNyQyxPQUFPLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM5QixDQUFDO0lBQ0QsOEVBQThFO0lBQzlFLCtFQUErRTtJQUMvRSw0QkFBNEI7SUFDNUIsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3JDLElBQUksS0FBSyxLQUFLLFNBQVMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDaEUsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUNsRCxPQUFPLENBQUMsR0FBRyxXQUFXLENBQUMsS0FBSyxDQUFDLEdBQUcsSUFBSSxFQUFFLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQ3ZELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFdoYXQgaXMgdW5kZXIgdGhlIGN1cnNvciwgYW5kIHdoYXQgdG8gc2F5IGFib3V0IGl0LlxuICpcbiAqIEEgY29sb3VyZWQgY2xvdWQgYW5zd2VycyBcImhvdyBpcyB0aGlzIGFubm90YXRpb24gZGlzdHJpYnV0ZWRcIjsgaXQgY2Fubm90IGFuc3dlclxuICogXCJ3aGljaCBjbGFzcyBpcyBUSEFUXCIg4oCUIHRoZSBsZWdlbmQgaGFzIDM0IGVudHJpZXMsIHNldmVyYWwgb2YgdGhlbSBzaW1pbGFyXG4gKiBjb2xvdXJzLCBhbmQgbWF0Y2hpbmcgYSBkb3QgdG8gYSBzd2F0Y2ggYnkgZXllIGlzIGV4YWN0bHkgdGhlIHRhc2sgYSB0b29sdGlwXG4gKiBleGlzdHMgdG8gcmVtb3ZlLlxuICpcbiAqIFB1cmUg4oCUIG5vIERPTSwgbm8gY2FtZXJhLCBubyBkYXRhIGFjY2Vzcy4gVGhlIHJlbmRlcmVyIHN1cHBsaWVzIHNjcmVlblxuICogcG9zaXRpb25zIChpdCBvd25zIHRoZSBwcm9qZWN0aW9uKSBhbmQgdGhlIGFjdGl2ZSBjb2xvdXIgc291cmNlOyBldmVyeXRoaW5nXG4gKiBoZXJlIGlzIGFyaXRobWV0aWMgYW5kIHN0cmluZyBmb3JtYXR0aW5nLCBzbyBpdCBpcyB0ZXN0ZWQgZGlyZWN0bHkuXG4gKi9cblxuLyoqIFRoZSBjb2xvdXIgc291cmNlIGEgdG9vbHRpcCBkZXNjcmliZXMsIGFzIHRoZSByZW5kZXJlciBhbHJlYWR5IGhhcyBpdC4gKi9cbmV4cG9ydCB0eXBlIEhvdmVyU291cmNlID1cbiAgfCB7XG4gICAga2luZDogJ2NhdGVnb3JpY2FsJztcbiAgICAvKiogQ29sdW1uIG5hbWUsIGZvciB0aGUgdG9vbHRpcCdzIGZpcnN0IGxpbmUuICovXG4gICAgbmFtZTogc3RyaW5nO1xuICAgIGNhdGVnb3JpZXM6IHJlYWRvbmx5IHN0cmluZ1tdO1xuICAgIC8qKiBQZXItb2JzZXJ2YXRpb24gY2F0ZWdvcnkgaW5kZXgsIG9yIGBOT19DQVRFR09SWWAuICovXG4gICAgY29kZXM6IFVpbnQxNkFycmF5O1xuICB9XG4gIHwge1xuICAgIGtpbmQ6ICdjb250aW51b3VzJztcbiAgICAvKiogQ29sdW1uIG9yIGdlbmUgbmFtZS4gKi9cbiAgICBuYW1lOiBzdHJpbmc7XG4gICAgdmFsdWVzOiBGbG9hdDMyQXJyYXk7XG4gICAgLyoqIFVuaXQgZm9yIHRoZSB2YWx1ZSwgd2hlbiB0aGUgZGF0YXNldCBkZWNsYXJlcyBvbmUuICovXG4gICAgdW5pdD86IHN0cmluZztcbiAgfTtcblxuLyoqXG4gKiBJbmRleCBvZiB0aGUgZHJhd24gb2JzZXJ2YXRpb24gbmVhcmVzdCBgKHgsIHkpYCB3aXRoaW4gYG1heERpc3RgIHNjcmVlbiBwaXhlbHMsXG4gKiBvciAtMS5cbiAqXG4gKiBgc2NyZWVuYCBpcyBgW3gwLCB5MCwgeDEsIHkxLCDigKZdYCBpbmRleGVkIEJZIE9CU0VSVkFUSU9OLCB3aXRoIE5hTiBmb3IgYW55dGhpbmdcbiAqIG5vdCBjdXJyZW50bHkgZHJhd24g4oCUIG9mZiBzY3JlZW4sIGJlaGluZCB0aGUgZXllLCBvciBvbiBhIGhpZGRlbiBzZWN0aW9uLiBOYU5cbiAqIGZhaWxzIGV2ZXJ5IGNvbXBhcmlzb24sIHNvIHRob3NlIGFyZSBza2lwcGVkIHdpdGhvdXQgYSBzcGVjaWFsIGNhc2UuXG4gKlxuICogVGllcyBnbyB0byB0aGUgTEFURVIgb2JzZXJ2YXRpb24sIHdoaWNoIGlzIHRoZSBvbmUgZHJhd24gb24gdG9wIGFuZCB0aGVyZWZvcmVcbiAqIHRoZSBvbmUgdGhlIHVzZXIgYmVsaWV2ZXMgdGhleSBhcmUgcG9pbnRpbmcgYXQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBuZWFyZXN0T2JzZXJ2YXRpb24oXG4gIHNjcmVlbjogRmxvYXQzMkFycmF5LFxuICB4OiBudW1iZXIsXG4gIHk6IG51bWJlcixcbiAgbWF4RGlzdDogbnVtYmVyLFxuKTogbnVtYmVyIHtcbiAgY29uc3QgbGltaXQgPSBtYXhEaXN0ICogbWF4RGlzdDtcbiAgbGV0IGJlc3QgPSAtMTtcbiAgbGV0IGJlc3REaXN0ID0gSW5maW5pdHk7XG4gIGNvbnN0IG4gPSBzY3JlZW4ubGVuZ3RoID4+IDE7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgbjsgaSsrKSB7XG4gICAgY29uc3QgZHggPSBzY3JlZW5baSAqIDJdIC0geDtcbiAgICBjb25zdCBkeSA9IHNjcmVlbltpICogMiArIDFdIC0geTtcbiAgICBjb25zdCBkID0gZHggKiBkeCArIGR5ICogZHk7XG4gICAgLy8gYDw9YCBzbyBhIGxhdGVyIHBvaW50IGF0IHRoZSBzYW1lIGRpc3RhbmNlIHdpbnM6IGl0IGlzIGRyYXduIG9uIHRvcC5cbiAgICBpZiAoZCA8PSBiZXN0RGlzdCAmJiBkIDw9IGxpbWl0KSB7XG4gICAgICBiZXN0RGlzdCA9IGQ7XG4gICAgICBiZXN0ID0gaTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIGJlc3Q7XG59XG5cbi8qKiBBIG51bWJlciBmb3IgYSB0b29sdGlwOiBlbm91Z2ggZGlnaXRzIHRvIGRpc3Rpbmd1aXNoLCBmZXcgZW5vdWdoIHRvIHJlYWQuICovXG5mdW5jdGlvbiBmb3JtYXRWYWx1ZSh2OiBudW1iZXIpOiBzdHJpbmcge1xuICBpZiAoIU51bWJlci5pc0Zpbml0ZSh2KSkgcmV0dXJuICfigJQnO1xuICBpZiAodiA9PT0gMCkgcmV0dXJuICcwJztcbiAgY29uc3QgbWFnbml0dWRlID0gTWF0aC5hYnModik7XG4gIGlmIChtYWduaXR1ZGUgPj0gMTAwMCB8fCBtYWduaXR1ZGUgPCAwLjAxKSByZXR1cm4gdi50b0V4cG9uZW50aWFsKDIpO1xuICByZXR1cm4gU3RyaW5nKE1hdGgucm91bmQodiAqIDEwMCkgLyAxMDApO1xufVxuXG4vKipcbiAqIFRoZSB0b29sdGlwJ3MgbGluZXMgZm9yIG9uZSBvYnNlcnZhdGlvbjogd2hhdCBpdCBpcywgdGhlbiB3aGljaCBjb2x1bW4gc2FpZCBzby5cbiAqXG4gKiBOdWxsIHdoZW4gdGhlIHNvdXJjZSBjYW5ub3QgZGVzY3JpYmUgdGhpcyBvYnNlcnZhdGlvbiDigJQgYSBjb2RlIG91dHNpZGUgdGhlXG4gKiBjYXRlZ29yeSBsaXN0LCBvciBhIHZhbHVlIHRoYXQgd2FzIG5ldmVyIG1lYXN1cmVkLiBTYXlpbmcgbm90aGluZyBpcyBiZXR0ZXJcbiAqIHRoYW4gYSB0b29sdGlwIHRoYXQgcmVhZHMgXCJ1bmRlZmluZWRcIiwgYW5kIGJldHRlciB0aGFuIGludmVudGluZyBhIGxhYmVsIGZvciBhXG4gKiBjZWxsIHdob3NlIGFubm90YXRpb24gaXMgZ2VudWluZWx5IG1pc3NpbmcuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBob3ZlclRleHQoc291cmNlOiBIb3ZlclNvdXJjZSB8IG51bGwsIGluZGV4OiBudW1iZXIpOiBzdHJpbmdbXSB8IG51bGwge1xuICBpZiAoIXNvdXJjZSB8fCBpbmRleCA8IDApIHJldHVybiBudWxsO1xuICBpZiAoc291cmNlLmtpbmQgPT09ICdjYXRlZ29yaWNhbCcpIHtcbiAgICBjb25zdCBjb2RlID0gc291cmNlLmNvZGVzPy5baW5kZXhdO1xuICAgIGNvbnN0IGxhYmVsID0gY29kZSA9PT0gdW5kZWZpbmVkID8gdW5kZWZpbmVkIDogc291cmNlLmNhdGVnb3JpZXM/Lltjb2RlXTtcbiAgICBpZiAobGFiZWwgPT09IHVuZGVmaW5lZCkgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIFtsYWJlbCwgc291cmNlLm5hbWVdO1xuICB9XG4gIC8vIEd1YXJkZWQgcmF0aGVyIHRoYW4gaW5kZXhlZCBzdHJhaWdodDogdGhpcyBydW5zIGZyb20gYSBwb2ludGVybW92ZSBoYW5kbGVyLFxuICAvLyBzbyBhIG1hbGZvcm1lZCBzb3VyY2Ugd291bGQgdGhyb3cgb24gZXZlcnkgbW91c2UgbW92ZW1lbnQg4oCUIGZhciB3b3JzZSB0aGFuIGFcbiAgLy8gdG9vbHRpcCB0aGF0IHN0YXlzIHF1aWV0LlxuICBjb25zdCB2YWx1ZSA9IHNvdXJjZS52YWx1ZXM/LltpbmRleF07XG4gIGlmICh2YWx1ZSA9PT0gdW5kZWZpbmVkIHx8ICFOdW1iZXIuaXNGaW5pdGUodmFsdWUpKSByZXR1cm4gbnVsbDtcbiAgY29uc3QgdW5pdCA9IHNvdXJjZS51bml0ID8gYCAke3NvdXJjZS51bml0fWAgOiAnJztcbiAgcmV0dXJuIFtgJHtmb3JtYXRWYWx1ZSh2YWx1ZSl9JHt1bml0fWAsIHNvdXJjZS5uYW1lXTtcbn1cbiJdfQ==
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The planes a **serially sectioned** dataset was actually imaged at.
3
+ *
4
+ * A 3D spatial-omics dataset of this kind is not a cloud that happens to have a z:
5
+ * it is a stack of thin sections, hundreds of microns apart, each one a separate
6
+ * slide. Every observation on a given slide shares that slide's registered z
7
+ * exactly, so the distinct z values ARE the sections — no binning, no tolerance,
8
+ * and no need for a dataset-specific section-label column.
9
+ *
10
+ * That matters for the 3D view, where the cloud reads as a stack of discs and the
11
+ * discs hide the density volumes drawn between them. Being able to show one
12
+ * section's cells against the estimated field is how you check the estimate against
13
+ * the measurement; being able to hide them is how you see the field at all.
14
+ *
15
+ * Deliberately NOT a binning of a continuous z (that is
16
+ * `observationsInSlice`'s job, against a volume's voxel planes). If the z is
17
+ * continuous rather than sectioned there are no sections to offer, and
18
+ * {@link sampledSections} says so by returning null rather than inventing some.
19
+ *
20
+ * Pure — no Angular, no GPU — like `spatial-density.ts`, and tested the same way.
21
+ */
22
+ /**
23
+ * Distinct z values beyond which the data is treated as continuous rather than
24
+ * sectioned. Serial sectioning runs to tens or low hundreds of slides (53 for the
25
+ * ABC whole-brain MERFISH atlas); a z that keeps producing new values is a
26
+ * measurement axis, and a "pick one section" control over it would be a lie.
27
+ */
28
+ export const MAX_SAMPLED_SECTIONS = 512;
29
+ /**
30
+ * The dataset's sampled section positions, ascending — or null when it has no z,
31
+ * no observations, or a z too finely divided to be sections at all.
32
+ *
33
+ * Bails out as soon as the distinct count passes `max`, so a continuous z costs one
34
+ * partial pass rather than a set with one entry per observation.
35
+ */
36
+ export function sampledSections(obs, max = MAX_SAMPLED_SECTIONS) {
37
+ const z = obs.z;
38
+ if (!z || obs.count === 0)
39
+ return null;
40
+ const seen = new Set();
41
+ for (let i = 0; i < obs.count; i++) {
42
+ const v = z[i];
43
+ // A non-finite z places an observation on no section; it must not become one.
44
+ if (!Number.isFinite(v))
45
+ continue;
46
+ seen.add(v);
47
+ if (seen.size > max)
48
+ return null;
49
+ }
50
+ if (seen.size === 0)
51
+ return null;
52
+ return Float32Array.from(seen).sort();
53
+ }
54
+ /**
55
+ * Indices of the observations lying on one sampled section.
56
+ *
57
+ * Exact equality on purpose: `z` comes from {@link sampledSections}, which read the
58
+ * values out of this same array, so the comparison cannot drift. Passing a z that
59
+ * is not one of the sampled positions yields nothing, which is the honest answer.
60
+ */
61
+ export function observationsInSection(obs, z) {
62
+ const zs = obs.z;
63
+ if (!zs)
64
+ return new Uint32Array(0);
65
+ const out = new Uint32Array(obs.count);
66
+ let n = 0;
67
+ for (let i = 0; i < obs.count; i++) {
68
+ if (zs[i] === z)
69
+ out[n++] = i;
70
+ }
71
+ return out.subarray(0, n);
72
+ }
73
+ /** Memoized per observations object; see {@link sectionsOf}. */
74
+ const memo = new WeakMap();
75
+ /**
76
+ * {@link sampledSections} for a dataset's observations, computed once.
77
+ *
78
+ * Both the renderer (which filters the cloud's geometry to one section) and the
79
+ * control panel (which offers the sections to pick from) need this list, and the
80
+ * scan reads the z of up to 3.7M observations — so it is memoized on the
81
+ * observations object itself rather than each caller keeping its own copy keyed by
82
+ * a dataset id it has to remember to invalidate. Still referentially transparent:
83
+ * the same observations always give the same sections.
84
+ *
85
+ * Default `max` only; a caller wanting a different cap should call
86
+ * {@link sampledSections} directly and cache it itself.
87
+ */
88
+ export function sectionsOf(obs) {
89
+ const hit = memo.get(obs);
90
+ if (hit !== undefined)
91
+ return hit;
92
+ const sections = sampledSections(obs);
93
+ memo.set(obs, sections);
94
+ return sections;
95
+ }
96
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhdGlhbC1zZWN0aW9ucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvc3BhdGlhbC9zcGF0aWFsLXNlY3Rpb25zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW9CRztBQUVIOzs7OztHQUtHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sb0JBQW9CLEdBQUcsR0FBRyxDQUFDO0FBRXhDOzs7Ozs7R0FNRztBQUNILE1BQU0sVUFBVSxlQUFlLENBQzdCLEdBQXdCLEVBQ3hCLEdBQUcsR0FBRyxvQkFBb0I7SUFFMUIsTUFBTSxDQUFDLEdBQUcsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUNoQixJQUFJLENBQUMsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEtBQUssQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRXZDLE1BQU0sSUFBSSxHQUFHLElBQUksR0FBRyxFQUFVLENBQUM7SUFDL0IsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUNuQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDZiw4RUFBOEU7UUFDOUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO1lBQUUsU0FBUztRQUNsQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ1osSUFBSSxJQUFJLENBQUMsSUFBSSxHQUFHLEdBQUc7WUFBRSxPQUFPLElBQUksQ0FBQztJQUNuQyxDQUFDO0lBQ0QsSUFBSSxJQUFJLENBQUMsSUFBSSxLQUFLLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUNqQyxPQUFPLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7QUFDeEMsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILE1BQU0sVUFBVSxxQkFBcUIsQ0FBQyxHQUF3QixFQUFFLENBQVM7SUFDdkUsTUFBTSxFQUFFLEdBQUcsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUNqQixJQUFJLENBQUMsRUFBRTtRQUFFLE9BQU8sSUFBSSxXQUFXLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDbkMsTUFBTSxHQUFHLEdBQUcsSUFBSSxXQUFXLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3ZDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNWLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUM7UUFDbkMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztZQUFFLEdBQUcsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNoQyxDQUFDO0lBQ0QsT0FBTyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUM1QixDQUFDO0FBRUQsZ0VBQWdFO0FBQ2hFLE1BQU0sSUFBSSxHQUFHLElBQUksT0FBTyxFQUE0QyxDQUFDO0FBRXJFOzs7Ozs7Ozs7Ozs7R0FZRztBQUNILE1BQU0sVUFBVSxVQUFVLENBQUMsR0FBd0I7SUFDakQsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUMxQixJQUFJLEdBQUcsS0FBSyxTQUFTO1FBQUUsT0FBTyxHQUFHLENBQUM7SUFDbEMsTUFBTSxRQUFRLEdBQUcsZUFBZSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ3RDLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBQ3hCLE9BQU8sUUFBUSxDQUFDO0FBQ2xCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBTcGF0aWFsT2JzZXJ2YXRpb25zIH0gZnJvbSAnLi4vY29udHJhY3RzL3NwYXRpYWwtZGF0YXNldC5jb250cmFjdCc7XG5cbi8qKlxuICogVGhlIHBsYW5lcyBhICoqc2VyaWFsbHkgc2VjdGlvbmVkKiogZGF0YXNldCB3YXMgYWN0dWFsbHkgaW1hZ2VkIGF0LlxuICpcbiAqIEEgM0Qgc3BhdGlhbC1vbWljcyBkYXRhc2V0IG9mIHRoaXMga2luZCBpcyBub3QgYSBjbG91ZCB0aGF0IGhhcHBlbnMgdG8gaGF2ZSBhIHo6XG4gKiBpdCBpcyBhIHN0YWNrIG9mIHRoaW4gc2VjdGlvbnMsIGh1bmRyZWRzIG9mIG1pY3JvbnMgYXBhcnQsIGVhY2ggb25lIGEgc2VwYXJhdGVcbiAqIHNsaWRlLiBFdmVyeSBvYnNlcnZhdGlvbiBvbiBhIGdpdmVuIHNsaWRlIHNoYXJlcyB0aGF0IHNsaWRlJ3MgcmVnaXN0ZXJlZCB6XG4gKiBleGFjdGx5LCBzbyB0aGUgZGlzdGluY3QgeiB2YWx1ZXMgQVJFIHRoZSBzZWN0aW9ucyDigJQgbm8gYmlubmluZywgbm8gdG9sZXJhbmNlLFxuICogYW5kIG5vIG5lZWQgZm9yIGEgZGF0YXNldC1zcGVjaWZpYyBzZWN0aW9uLWxhYmVsIGNvbHVtbi5cbiAqXG4gKiBUaGF0IG1hdHRlcnMgZm9yIHRoZSAzRCB2aWV3LCB3aGVyZSB0aGUgY2xvdWQgcmVhZHMgYXMgYSBzdGFjayBvZiBkaXNjcyBhbmQgdGhlXG4gKiBkaXNjcyBoaWRlIHRoZSBkZW5zaXR5IHZvbHVtZXMgZHJhd24gYmV0d2VlbiB0aGVtLiBCZWluZyBhYmxlIHRvIHNob3cgb25lXG4gKiBzZWN0aW9uJ3MgY2VsbHMgYWdhaW5zdCB0aGUgZXN0aW1hdGVkIGZpZWxkIGlzIGhvdyB5b3UgY2hlY2sgdGhlIGVzdGltYXRlIGFnYWluc3RcbiAqIHRoZSBtZWFzdXJlbWVudDsgYmVpbmcgYWJsZSB0byBoaWRlIHRoZW0gaXMgaG93IHlvdSBzZWUgdGhlIGZpZWxkIGF0IGFsbC5cbiAqXG4gKiBEZWxpYmVyYXRlbHkgTk9UIGEgYmlubmluZyBvZiBhIGNvbnRpbnVvdXMgeiAodGhhdCBpc1xuICogYG9ic2VydmF0aW9uc0luU2xpY2VgJ3Mgam9iLCBhZ2FpbnN0IGEgdm9sdW1lJ3Mgdm94ZWwgcGxhbmVzKS4gSWYgdGhlIHogaXNcbiAqIGNvbnRpbnVvdXMgcmF0aGVyIHRoYW4gc2VjdGlvbmVkIHRoZXJlIGFyZSBubyBzZWN0aW9ucyB0byBvZmZlciwgYW5kXG4gKiB7QGxpbmsgc2FtcGxlZFNlY3Rpb25zfSBzYXlzIHNvIGJ5IHJldHVybmluZyBudWxsIHJhdGhlciB0aGFuIGludmVudGluZyBzb21lLlxuICpcbiAqIFB1cmUg4oCUIG5vIEFuZ3VsYXIsIG5vIEdQVSDigJQgbGlrZSBgc3BhdGlhbC1kZW5zaXR5LnRzYCwgYW5kIHRlc3RlZCB0aGUgc2FtZSB3YXkuXG4gKi9cblxuLyoqXG4gKiBEaXN0aW5jdCB6IHZhbHVlcyBiZXlvbmQgd2hpY2ggdGhlIGRhdGEgaXMgdHJlYXRlZCBhcyBjb250aW51b3VzIHJhdGhlciB0aGFuXG4gKiBzZWN0aW9uZWQuIFNlcmlhbCBzZWN0aW9uaW5nIHJ1bnMgdG8gdGVucyBvciBsb3cgaHVuZHJlZHMgb2Ygc2xpZGVzICg1MyBmb3IgdGhlXG4gKiBBQkMgd2hvbGUtYnJhaW4gTUVSRklTSCBhdGxhcyk7IGEgeiB0aGF0IGtlZXBzIHByb2R1Y2luZyBuZXcgdmFsdWVzIGlzIGFcbiAqIG1lYXN1cmVtZW50IGF4aXMsIGFuZCBhIFwicGljayBvbmUgc2VjdGlvblwiIGNvbnRyb2wgb3ZlciBpdCB3b3VsZCBiZSBhIGxpZS5cbiAqL1xuZXhwb3J0IGNvbnN0IE1BWF9TQU1QTEVEX1NFQ1RJT05TID0gNTEyO1xuXG4vKipcbiAqIFRoZSBkYXRhc2V0J3Mgc2FtcGxlZCBzZWN0aW9uIHBvc2l0aW9ucywgYXNjZW5kaW5nIOKAlCBvciBudWxsIHdoZW4gaXQgaGFzIG5vIHosXG4gKiBubyBvYnNlcnZhdGlvbnMsIG9yIGEgeiB0b28gZmluZWx5IGRpdmlkZWQgdG8gYmUgc2VjdGlvbnMgYXQgYWxsLlxuICpcbiAqIEJhaWxzIG91dCBhcyBzb29uIGFzIHRoZSBkaXN0aW5jdCBjb3VudCBwYXNzZXMgYG1heGAsIHNvIGEgY29udGludW91cyB6IGNvc3RzIG9uZVxuICogcGFydGlhbCBwYXNzIHJhdGhlciB0aGFuIGEgc2V0IHdpdGggb25lIGVudHJ5IHBlciBvYnNlcnZhdGlvbi5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNhbXBsZWRTZWN0aW9ucyhcbiAgb2JzOiBTcGF0aWFsT2JzZXJ2YXRpb25zLFxuICBtYXggPSBNQVhfU0FNUExFRF9TRUNUSU9OUyxcbik6IEZsb2F0MzJBcnJheSB8IG51bGwge1xuICBjb25zdCB6ID0gb2JzLno7XG4gIGlmICgheiB8fCBvYnMuY291bnQgPT09IDApIHJldHVybiBudWxsO1xuXG4gIGNvbnN0IHNlZW4gPSBuZXcgU2V0PG51bWJlcj4oKTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBvYnMuY291bnQ7IGkrKykge1xuICAgIGNvbnN0IHYgPSB6W2ldO1xuICAgIC8vIEEgbm9uLWZpbml0ZSB6IHBsYWNlcyBhbiBvYnNlcnZhdGlvbiBvbiBubyBzZWN0aW9uOyBpdCBtdXN0IG5vdCBiZWNvbWUgb25lLlxuICAgIGlmICghTnVtYmVyLmlzRmluaXRlKHYpKSBjb250aW51ZTtcbiAgICBzZWVuLmFkZCh2KTtcbiAgICBpZiAoc2Vlbi5zaXplID4gbWF4KSByZXR1cm4gbnVsbDtcbiAgfVxuICBpZiAoc2Vlbi5zaXplID09PSAwKSByZXR1cm4gbnVsbDtcbiAgcmV0dXJuIEZsb2F0MzJBcnJheS5mcm9tKHNlZW4pLnNvcnQoKTtcbn1cblxuLyoqXG4gKiBJbmRpY2VzIG9mIHRoZSBvYnNlcnZhdGlvbnMgbHlpbmcgb24gb25lIHNhbXBsZWQgc2VjdGlvbi5cbiAqXG4gKiBFeGFjdCBlcXVhbGl0eSBvbiBwdXJwb3NlOiBgemAgY29tZXMgZnJvbSB7QGxpbmsgc2FtcGxlZFNlY3Rpb25zfSwgd2hpY2ggcmVhZCB0aGVcbiAqIHZhbHVlcyBvdXQgb2YgdGhpcyBzYW1lIGFycmF5LCBzbyB0aGUgY29tcGFyaXNvbiBjYW5ub3QgZHJpZnQuIFBhc3NpbmcgYSB6IHRoYXRcbiAqIGlzIG5vdCBvbmUgb2YgdGhlIHNhbXBsZWQgcG9zaXRpb25zIHlpZWxkcyBub3RoaW5nLCB3aGljaCBpcyB0aGUgaG9uZXN0IGFuc3dlci5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG9ic2VydmF0aW9uc0luU2VjdGlvbihvYnM6IFNwYXRpYWxPYnNlcnZhdGlvbnMsIHo6IG51bWJlcik6IFVpbnQzMkFycmF5IHtcbiAgY29uc3QgenMgPSBvYnMuejtcbiAgaWYgKCF6cykgcmV0dXJuIG5ldyBVaW50MzJBcnJheSgwKTtcbiAgY29uc3Qgb3V0ID0gbmV3IFVpbnQzMkFycmF5KG9icy5jb3VudCk7XG4gIGxldCBuID0gMDtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBvYnMuY291bnQ7IGkrKykge1xuICAgIGlmICh6c1tpXSA9PT0geikgb3V0W24rK10gPSBpO1xuICB9XG4gIHJldHVybiBvdXQuc3ViYXJyYXkoMCwgbik7XG59XG5cbi8qKiBNZW1vaXplZCBwZXIgb2JzZXJ2YXRpb25zIG9iamVjdDsgc2VlIHtAbGluayBzZWN0aW9uc09mfS4gKi9cbmNvbnN0IG1lbW8gPSBuZXcgV2Vha01hcDxTcGF0aWFsT2JzZXJ2YXRpb25zLCBGbG9hdDMyQXJyYXkgfCBudWxsPigpO1xuXG4vKipcbiAqIHtAbGluayBzYW1wbGVkU2VjdGlvbnN9IGZvciBhIGRhdGFzZXQncyBvYnNlcnZhdGlvbnMsIGNvbXB1dGVkIG9uY2UuXG4gKlxuICogQm90aCB0aGUgcmVuZGVyZXIgKHdoaWNoIGZpbHRlcnMgdGhlIGNsb3VkJ3MgZ2VvbWV0cnkgdG8gb25lIHNlY3Rpb24pIGFuZCB0aGVcbiAqIGNvbnRyb2wgcGFuZWwgKHdoaWNoIG9mZmVycyB0aGUgc2VjdGlvbnMgdG8gcGljayBmcm9tKSBuZWVkIHRoaXMgbGlzdCwgYW5kIHRoZVxuICogc2NhbiByZWFkcyB0aGUgeiBvZiB1cCB0byAzLjdNIG9ic2VydmF0aW9ucyDigJQgc28gaXQgaXMgbWVtb2l6ZWQgb24gdGhlXG4gKiBvYnNlcnZhdGlvbnMgb2JqZWN0IGl0c2VsZiByYXRoZXIgdGhhbiBlYWNoIGNhbGxlciBrZWVwaW5nIGl0cyBvd24gY29weSBrZXllZCBieVxuICogYSBkYXRhc2V0IGlkIGl0IGhhcyB0byByZW1lbWJlciB0byBpbnZhbGlkYXRlLiBTdGlsbCByZWZlcmVudGlhbGx5IHRyYW5zcGFyZW50OlxuICogdGhlIHNhbWUgb2JzZXJ2YXRpb25zIGFsd2F5cyBnaXZlIHRoZSBzYW1lIHNlY3Rpb25zLlxuICpcbiAqIERlZmF1bHQgYG1heGAgb25seTsgYSBjYWxsZXIgd2FudGluZyBhIGRpZmZlcmVudCBjYXAgc2hvdWxkIGNhbGxcbiAqIHtAbGluayBzYW1wbGVkU2VjdGlvbnN9IGRpcmVjdGx5IGFuZCBjYWNoZSBpdCBpdHNlbGYuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZWN0aW9uc09mKG9iczogU3BhdGlhbE9ic2VydmF0aW9ucyk6IEZsb2F0MzJBcnJheSB8IG51bGwge1xuICBjb25zdCBoaXQgPSBtZW1vLmdldChvYnMpO1xuICBpZiAoaGl0ICE9PSB1bmRlZmluZWQpIHJldHVybiBoaXQ7XG4gIGNvbnN0IHNlY3Rpb25zID0gc2FtcGxlZFNlY3Rpb25zKG9icyk7XG4gIG1lbW8uc2V0KG9icywgc2VjdGlvbnMpO1xuICByZXR1cm4gc2VjdGlvbnM7XG59XG4iXX0=