@jax-data-science/sci-image-visualizer 0.3.2 → 0.4.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 (93) hide show
  1. package/README.md +215 -11
  2. package/esm2022/index.mjs +20 -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.mjs +36 -1
  6. package/esm2022/lib/contracts/ports/spatial-data.port.mjs +3 -0
  7. package/esm2022/lib/contracts/spatial-dataset.contract.mjs +45 -0
  8. package/esm2022/lib/contracts/visualizer.contract.mjs +1 -1
  9. package/esm2022/lib/implementations/napari-js/napari-axes-labels.mjs +10 -19
  10. package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +71 -7
  11. package/esm2022/lib/implementations/napari-js/napari-scale-bar.mjs +1 -1
  12. package/esm2022/lib/implementations/napari-js/napari-spatial-tooltip.mjs +96 -0
  13. package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +1934 -55
  14. package/esm2022/lib/implementations/napari-js/napari-zoom.mjs +18 -0
  15. package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +5 -3
  16. package/esm2022/lib/implementations/osd/osd-region-overlay.mjs +3 -4
  17. package/esm2022/lib/implementations/osd/osd-zoom.mjs +20 -0
  18. package/esm2022/lib/implementations/plotly/omics-trace-builders.mjs +555 -0
  19. package/esm2022/lib/implementations/plotly/plotly.service.mjs +13 -7
  20. package/esm2022/lib/implementations/spatial-data-http/spatial-data-http.service.mjs +304 -0
  21. package/esm2022/lib/implementations/spatial-data-http/spatial-wire.mjs +179 -0
  22. package/esm2022/lib/routing-visualizer.service.mjs +223 -12
  23. package/esm2022/lib/spatial/embedding-compute.mjs +142 -0
  24. package/esm2022/lib/spatial/gene-search.mjs +77 -0
  25. package/esm2022/lib/spatial/spatial-density.mjs +224 -0
  26. package/esm2022/lib/spatial/spatial-encoding.mjs +232 -0
  27. package/esm2022/lib/spatial/spatial-expression.mjs +257 -0
  28. package/esm2022/lib/spatial/spatial-framing.mjs +60 -0
  29. package/esm2022/lib/spatial/spatial-heatmap.mjs +185 -0
  30. package/esm2022/lib/spatial/spatial-hover.mjs +79 -0
  31. package/esm2022/lib/spatial/spatial-sections.mjs +96 -0
  32. package/esm2022/lib/spatial/spatial-selection.mjs +264 -0
  33. package/esm2022/lib/spatial/spatial-volume-image.mjs +163 -0
  34. package/esm2022/lib/spatial/tsne-worker.mjs +10 -0
  35. package/esm2022/lib/spatial/world-grid.mjs +77 -0
  36. package/esm2022/lib/spatial-controls/spatial-charts/spatial-charts.component.mjs +1278 -0
  37. package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +621 -0
  38. package/esm2022/lib/store/spatial-selection.service.mjs +47 -0
  39. package/esm2022/lib/store/visualizer-store.service.mjs +23 -1
  40. package/esm2022/lib/toolbar/segmentation/cellpose-segmenter.service.mjs +2 -2
  41. package/esm2022/lib/toolbar/segmentation/sam-model-registry.mjs +9 -9
  42. package/esm2022/lib/toolbar/toolbar.component.mjs +30 -7
  43. package/esm2022/lib/visualization.module.mjs +23 -5
  44. package/esm2022/lib/visualizer.component.mjs +283 -22
  45. 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
  46. 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
  47. package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs +12 -0
  48. package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs.map +1 -0
  49. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +15282 -7829
  50. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
  51. package/fesm2022/tsne.worker.js +19091 -0
  52. package/index.d.ts +15 -0
  53. package/lib/contracts/display-types.d.ts +131 -0
  54. package/lib/contracts/image.contract.d.ts +7 -0
  55. package/lib/contracts/plot-type.d.ts +42 -2
  56. package/lib/contracts/ports/spatial-data.port.d.ts +90 -0
  57. package/lib/contracts/spatial-dataset.contract.d.ts +281 -0
  58. package/lib/contracts/visualizer.contract.d.ts +99 -1
  59. package/lib/implementations/napari-js/napari-axes-labels.d.ts +9 -7
  60. package/lib/implementations/napari-js/napari-region-overlay.d.ts +38 -4
  61. package/lib/implementations/napari-js/napari-scale-bar.d.ts +7 -3
  62. package/lib/implementations/napari-js/napari-spatial-tooltip.d.ts +32 -0
  63. package/lib/implementations/napari-js/napari-visualizer.service.d.ts +497 -16
  64. package/lib/implementations/napari-js/napari-zoom.d.ts +15 -0
  65. package/lib/implementations/osd/osd-zoom.d.ts +19 -0
  66. package/lib/implementations/plotly/omics-trace-builders.d.ts +193 -0
  67. package/lib/implementations/plotly/plotly.service.d.ts +9 -0
  68. package/lib/implementations/spatial-data-http/spatial-data-http.service.d.ts +123 -0
  69. package/lib/implementations/spatial-data-http/spatial-wire.d.ts +115 -0
  70. package/lib/routing-visualizer.service.d.ts +45 -6
  71. package/lib/spatial/embedding-compute.d.ts +73 -0
  72. package/lib/spatial/gene-search.d.ts +48 -0
  73. package/lib/spatial/spatial-density.d.ts +73 -0
  74. package/lib/spatial/spatial-encoding.d.ts +155 -0
  75. package/lib/spatial/spatial-expression.d.ts +144 -0
  76. package/lib/spatial/spatial-framing.d.ts +37 -0
  77. package/lib/spatial/spatial-heatmap.d.ts +111 -0
  78. package/lib/spatial/spatial-hover.d.ts +49 -0
  79. package/lib/spatial/spatial-sections.d.ts +59 -0
  80. package/lib/spatial/spatial-selection.d.ts +113 -0
  81. package/lib/spatial/spatial-volume-image.d.ts +67 -0
  82. package/lib/spatial/tsne-worker.d.ts +7 -0
  83. package/lib/spatial/world-grid.d.ts +58 -0
  84. package/lib/spatial-controls/spatial-charts/spatial-charts.component.d.ts +439 -0
  85. package/lib/spatial-controls/spatial-controls.component.d.ts +257 -0
  86. package/lib/store/spatial-selection.service.d.ts +27 -0
  87. package/lib/store/visualizer-store.service.d.ts +13 -0
  88. package/lib/toolbar/toolbar.component.d.ts +19 -3
  89. package/lib/visualization.module.d.ts +28 -24
  90. package/lib/visualizer.component.d.ts +73 -2
  91. package/package.json +7 -3
  92. package/src/lib/assets/spatial-omics-3d.svg +31 -0
  93. package/src/lib/assets/spatial-omics.svg +27 -0
@@ -0,0 +1,142 @@
1
+ export class EmbeddingComputeRun {
2
+ /**
3
+ * `factory` is optional: without one the real worker module is imported on demand.
4
+ *
5
+ * Dynamically, and that is not laziness for its own sake — the module holds an
6
+ * `import.meta.url`, which ts-jest's CommonJS compile rejects outright, so a static
7
+ * import here would break every spec that touches this file.
8
+ */
9
+ constructor(factory) {
10
+ this.factory = factory;
11
+ this.worker = null;
12
+ this.settled = false;
13
+ /**
14
+ * Settles a run that is still waiting, set for as long as one is.
15
+ *
16
+ * `terminate()` is called from outside — a dataset switch, a component being destroyed —
17
+ * and killing the worker on its own leaves the promise `run()` returned pending FOR
18
+ * EVER, because the settlement it was waiting for arrives as a worker message. The
19
+ * awaiting closure, and everything it captured, is retained with it. So termination
20
+ * settles the run itself.
21
+ */
22
+ this.abandon = null;
23
+ }
24
+ /**
25
+ * Start a run. Resolves with the coordinates, or null if cancelled.
26
+ *
27
+ * Rejects only on a real failure. Cancellation is a NULL rather than a rejection: a user
28
+ * pressing Cancel is not an error, and making callers tell the two apart in a catch
29
+ * block is how a cancelled run ends up reported as a crash.
30
+ */
31
+ async run(request, meta, onProgress) {
32
+ if (this.worker)
33
+ throw new Error('a computation is already running');
34
+ const create = this.factory
35
+ ?? (await import('./tsne-worker')).createTsneWorker;
36
+ const worker = create();
37
+ this.worker = worker;
38
+ this.settled = false;
39
+ const { scores, nObs, nDims, dims, perplexity = 30, iterations = 1000, seed = 0, } = request;
40
+ let backend = null;
41
+ return new Promise((resolve, reject) => {
42
+ const finish = (fn) => {
43
+ if (this.settled)
44
+ return;
45
+ this.settled = true;
46
+ // Cleared BEFORE terminating: `terminate()` calls it, and this is what keeps the
47
+ // two from calling each other round in a circle.
48
+ this.abandon = null;
49
+ this.terminate();
50
+ fn();
51
+ };
52
+ // An abandoned run resolves NULL, exactly as a cancelled one does. It is the same
53
+ // event from the caller's side — the answer was no longer wanted — and rejecting
54
+ // would make a routine dataset switch look like a failed computation.
55
+ this.abandon = () => finish(() => resolve(null));
56
+ worker.onmessage = (event) => {
57
+ const data = event.data ?? {};
58
+ switch (data.type) {
59
+ case 'backend':
60
+ backend = data.backend;
61
+ onProgress({ fraction: null, backend, message: null });
62
+ break;
63
+ case 'warning':
64
+ onProgress({ fraction: null, backend, message: data.message });
65
+ break;
66
+ case 'progress':
67
+ onProgress({ fraction: data.done / data.total, backend, message: null });
68
+ break;
69
+ case 'cancelled':
70
+ finish(() => resolve(null));
71
+ break;
72
+ case 'done': {
73
+ const flat = new Float32Array(data.embedding);
74
+ // Struct of arrays, matching what the port serves, so the chart cannot tell a
75
+ // locally-computed embedding from a fetched one.
76
+ const planes = [];
77
+ for (let d = 0; d < data.dims; d++) {
78
+ const plane = new Float32Array(nObs);
79
+ for (let i = 0; i < nObs; i++)
80
+ plane[i] = flat[i * data.dims + d];
81
+ planes.push(plane);
82
+ }
83
+ finish(() => resolve({
84
+ meta: {
85
+ ...meta,
86
+ derived: true,
87
+ params: `PCA(${nDims}) then t-SNE, perplexity ${data.perplexity}, `
88
+ + `${iterations} iterations, seed ${seed}`,
89
+ },
90
+ x: planes[0],
91
+ y: planes[1],
92
+ ...(planes[2] ? { z: planes[2] } : {}),
93
+ }));
94
+ break;
95
+ }
96
+ case 'error':
97
+ finish(() => reject(new Error(data.message)));
98
+ break;
99
+ default:
100
+ break;
101
+ }
102
+ };
103
+ // A worker that dies takes the promise with it; without this the caller waits for
104
+ // ever on a progress bar that has stopped moving.
105
+ worker.onerror = (event) => finish(() => reject(new Error(`embedding worker failed: ${event.message ?? 'unknown'}`)));
106
+ // Copied, then transferred: the caller keeps its scores, and the worker gets the
107
+ // buffer without a structured clone.
108
+ const payload = scores.slice();
109
+ worker.postMessage({
110
+ type: 'start',
111
+ scores: payload.buffer,
112
+ nObs,
113
+ nDims,
114
+ dims,
115
+ perplexity,
116
+ iterations,
117
+ seed,
118
+ }, [payload.buffer]);
119
+ });
120
+ }
121
+ /** Ask the run to stop. It resolves with null shortly after. */
122
+ cancel() {
123
+ this.worker?.postMessage({ type: 'cancel' });
124
+ }
125
+ /**
126
+ * Stop immediately, abandoning any result. Safe to call twice.
127
+ *
128
+ * Unlike {@link cancel}, this does not wait for the worker to acknowledge — the run is
129
+ * settled here, with null, because nothing is left to deliver the answer.
130
+ */
131
+ terminate() {
132
+ const abandon = this.abandon;
133
+ this.abandon = null;
134
+ this.worker?.terminate();
135
+ this.worker = null;
136
+ abandon?.();
137
+ }
138
+ get running() {
139
+ return this.worker !== null && !this.settled;
140
+ }
141
+ }
142
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW1iZWRkaW5nLWNvbXB1dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3NwYXRpYWwvZW1iZWRkaW5nLWNvbXB1dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBcUNBLE1BQU0sT0FBTyxtQkFBbUI7SUFnQjlCOzs7Ozs7T0FNRztJQUNILFlBQTZCLE9BQXVCO1FBQXZCLFlBQU8sR0FBUCxPQUFPLENBQWdCO1FBdEI1QyxXQUFNLEdBQWtCLElBQUksQ0FBQztRQUU3QixZQUFPLEdBQUcsS0FBSyxDQUFDO1FBRXhCOzs7Ozs7OztXQVFHO1FBQ0ssWUFBTyxHQUF3QixJQUFJLENBQUM7SUFTVyxDQUFDO0lBRXhEOzs7Ozs7T0FNRztJQUNILEtBQUssQ0FBQyxHQUFHLENBQ1AsT0FBdUIsRUFDdkIsSUFBMEIsRUFDMUIsVUFBK0M7UUFFL0MsSUFBSSxJQUFJLENBQUMsTUFBTTtZQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMsa0NBQWtDLENBQUMsQ0FBQztRQUNyRSxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsT0FBTztlQUN0QixDQUFDLE1BQU0sTUFBTSxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLENBQUM7UUFDdEQsTUFBTSxNQUFNLEdBQUcsTUFBTSxFQUFFLENBQUM7UUFDeEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUM7UUFFckIsTUFBTSxFQUNKLE1BQU0sRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLElBQUksRUFDekIsVUFBVSxHQUFHLEVBQUUsRUFBRSxVQUFVLEdBQUcsSUFBSSxFQUFFLElBQUksR0FBRyxDQUFDLEdBQzdDLEdBQUcsT0FBTyxDQUFDO1FBQ1osSUFBSSxPQUFPLEdBQWtCLElBQUksQ0FBQztRQUVsQyxPQUFPLElBQUksT0FBTyxDQUEwQixDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtZ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O1FBQ1AsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQztRQUM3QixJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQztRQUNwQixJQUFJLENBQUMsTUFBTSxFQUFFLFNBQVMsRUFBRSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDO1FBQ25CLE9BQU8sRUFBRSxFQUFFLENBQUM7SUFDZCxDQUFDO0lBRUQsSUFBSSxPQUFPO1FBQ1QsT0FBTyxJQUFJLENBQUMsTUFBTSxLQUFLLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUM7SUFDL0MsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgU3BhdGlhbEVtYmVkZGluZywgU3BhdGlhbEVtYmVkZGluZ01ldGEgfSBmcm9tICcuLi9jb250cmFjdHMvc3BhdGlhbC1kYXRhc2V0LmNvbnRyYWN0JztcblxuLyoqXG4gKiBSdW4gYSB0LVNORSBpbiBhIFdlYiBXb3JrZXIgYW5kIHJlcG9ydCBwcm9ncmVzcy5cbiAqXG4gKiBUaGUgcGFuZWwgb2ZmZXJzIHRoaXMgZm9yIGEgZGF0YXNldCB0aGF0IHB1Ymxpc2hlcyBubyB0LVNORSDigJQgYSBkcm9wcGVkLWluIGAuaDVhZGBcbiAqIHR5cGljYWxseSBjYXJyaWVzIG9uZSBVTUFQIGFuZCBub3RoaW5nIGVsc2UuIFRoZSBzZXJ2ZXIgcmVkdWNlcyB0byBQQ0EgKGl0IG5lZWRzIHRoZVxuICogd2hvbGUgZXhwcmVzc2lvbiBtYXRyaXgsIHdoaWNoIHRoZSBicm93c2VyIHNob3VsZCBub3QgZG93bmxvYWQpOyB0aGlzIHRha2VzIHRob3NlXG4gKiBzY29yZXMgYW5kIGVtYmVkcyB0aGVtIGhlcmUsIHdoZXJlIHRoZSBHUFUgaXMuXG4gKlxuICogQSB0aGluIHdyYXBwZXIgb24gcHVycG9zZTogbm8gQW5ndWxhciwgbm8gc3RvcmUuIEl0IG93bnMgb25lIHdvcmtlciBhbmQgb25lIHJ1biwgc28gYVxuICogY29tcG9uZW50IGNhbiBob2xkIGl0LCBzaG93IGEgcHJvZ3Jlc3MgYmFyLCBhbmQgY2FuY2VsIOKAlCBhbmQgc28gdGhlIHdob2xlIHRoaW5nIGNhbiBiZVxuICogdGVzdGVkIGJ5IHN1YnN0aXR1dGluZyBhIHdvcmtlci5cbiAqL1xuXG5leHBvcnQgaW50ZXJmYWNlIENvbXB1dGVQcm9ncmVzcyB7XG4gIC8qKiAwLi4xLCBvciBudWxsIGJlZm9yZSB0aGUgZmlyc3QgcmVwb3J0IOKAlCBhIHJ1biBzdGFydHMgd2l0aCBrTk4gYW5kIFAsIG5vdCBpdGVyYXRpb25zLiAqL1xuICBmcmFjdGlvbjogbnVtYmVyIHwgbnVsbDtcbiAgLyoqICd3ZWJncHUnLCAnd2FzbScgb3IgJ2NwdScgb25jZSBrbm93bjogaXQgY2hhbmdlcyB3aGF0IGEgbG9uZyB3YWl0IG1lYW5zLiAqL1xuICBiYWNrZW5kOiBzdHJpbmcgfCBudWxsO1xuICBtZXNzYWdlOiBzdHJpbmcgfCBudWxsO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIENvbXB1dGVSZXF1ZXN0IHtcbiAgLyoqIFBDQSBzY29yZXMsIHJvdy1tYWpvciBgbk9icyB4IG5EaW1zYC4gKi9cbiAgc2NvcmVzOiBGbG9hdDMyQXJyYXk7XG4gIG5PYnM6IG51bWJlcjtcbiAgbkRpbXM6IG51bWJlcjtcbiAgZGltczogMiB8IDM7XG4gIHBlcnBsZXhpdHk/OiBudW1iZXI7XG4gIGl0ZXJhdGlvbnM/OiBudW1iZXI7XG4gIHNlZWQ/OiBudW1iZXI7XG59XG5cbi8qKiBGYWN0b3J5LCBzbyBhIHRlc3QgY2FuIHN1cHBseSBhIHdvcmtlciB3aXRob3V0IGEgYnVuZGxlci4gKi9cbmV4cG9ydCB0eXBlIFdvcmtlckZhY3RvcnkgPSAoKSA9PiBXb3JrZXI7XG5cbmV4cG9ydCBjbGFzcyBFbWJlZGRpbmdDb21wdXRlUnVuIHtcbiAgcHJpdmF0ZSB3b3JrZXI6IFdvcmtlciB8IG51bGwgPSBudWxsO1xuXG4gIHByaXZhdGUgc2V0dGxlZCA9IGZhbHNlO1xuXG4gIC8qKlxuICAgKiBTZXR0bGVzIGEgcnVuIHRoYXQgaXMgc3RpbGwgd2FpdGluZywgc2V0IGZvciBhcyBsb25nIGFzIG9uZSBpcy5cbiAgICpcbiAgICogYHRlcm1pbmF0ZSgpYCBpcyBjYWxsZWQgZnJvbSBvdXRzaWRlIOKAlCBhIGRhdGFzZXQgc3dpdGNoLCBhIGNvbXBvbmVudCBiZWluZyBkZXN0cm95ZWQg4oCUXG4gICAqIGFuZCBraWxsaW5nIHRoZSB3b3JrZXIgb24gaXRzIG93biBsZWF2ZXMgdGhlIHByb21pc2UgYHJ1bigpYCByZXR1cm5lZCBwZW5kaW5nIEZPUlxuICAgKiBFVkVSLCBiZWNhdXNlIHRoZSBzZXR0bGVtZW50IGl0IHdhcyB3YWl0aW5nIGZvciBhcnJpdmVzIGFzIGEgd29ya2VyIG1lc3NhZ2UuIFRoZVxuICAgKiBhd2FpdGluZyBjbG9zdXJlLCBhbmQgZXZlcnl0aGluZyBpdCBjYXB0dXJlZCwgaXMgcmV0YWluZWQgd2l0aCBpdC4gU28gdGVybWluYXRpb25cbiAgICogc2V0dGxlcyB0aGUgcnVuIGl0c2VsZi5cbiAgICovXG4gIHByaXZhdGUgYWJhbmRvbjogKCgpID0+IHZvaWQpIHwgbnVsbCA9IG51bGw7XG5cbiAgLyoqXG4gICAqIGBmYWN0b3J5YCBpcyBvcHRpb25hbDogd2l0aG91dCBvbmUgdGhlIHJlYWwgd29ya2VyIG1vZHVsZSBpcyBpbXBvcnRlZCBvbiBkZW1hbmQuXG4gICAqXG4gICAqIER5bmFtaWNhbGx5LCBhbmQgdGhhdCBpcyBub3QgbGF6aW5lc3MgZm9yIGl0cyBvd24gc2FrZSDigJQgdGhlIG1vZHVsZSBob2xkcyBhblxuICAgKiBgaW1wb3J0Lm1ldGEudXJsYCwgd2hpY2ggdHMtamVzdCdzIENvbW1vbkpTIGNvbXBpbGUgcmVqZWN0cyBvdXRyaWdodCwgc28gYSBzdGF0aWNcbiAgICogaW1wb3J0IGhlcmUgd291bGQgYnJlYWsgZXZlcnkgc3BlYyB0aGF0IHRvdWNoZXMgdGhpcyBmaWxlLlxuICAgKi9cbiAgY29uc3RydWN0b3IocHJpdmF0ZSByZWFkb25seSBmYWN0b3J5PzogV29ya2VyRmFjdG9yeSkge31cblxuICAvKipcbiAgICogU3RhcnQgYSBydW4uIFJlc29sdmVzIHdpdGggdGhlIGNvb3JkaW5hdGVzLCBvciBudWxsIGlmIGNhbmNlbGxlZC5cbiAgICpcbiAgICogUmVqZWN0cyBvbmx5IG9uIGEgcmVhbCBmYWlsdXJlLiBDYW5jZWxsYXRpb24gaXMgYSBOVUxMIHJhdGhlciB0aGFuIGEgcmVqZWN0aW9uOiBhIHVzZXJcbiAgICogcHJlc3NpbmcgQ2FuY2VsIGlzIG5vdCBhbiBlcnJvciwgYW5kIG1ha2luZyBjYWxsZXJzIHRlbGwgdGhlIHR3byBhcGFydCBpbiBhIGNhdGNoXG4gICAqIGJsb2NrIGlzIGhvdyBhIGNhbmNlbGxlZCBydW4gZW5kcyB1cCByZXBvcnRlZCBhcyBhIGNyYXNoLlxuICAgKi9cbiAgYXN5bmMgcnVuKFxuICAgIHJlcXVlc3Q6IENvbXB1dGVSZXF1ZXN0LFxuICAgIG1ldGE6IFNwYXRpYWxFbWJlZGRpbmdNZXRhLFxuICAgIG9uUHJvZ3Jlc3M6IChwcm9ncmVzczogQ29tcHV0ZVByb2dyZXNzKSA9PiB2b2lkLFxuICApOiBQcm9taXNlPFNwYXRpYWxFbWJlZGRpbmcgfCBudWxsPiB7XG4gICAgaWYgKHRoaXMud29ya2VyKSB0aHJvdyBuZXcgRXJyb3IoJ2EgY29tcHV0YXRpb24gaXMgYWxyZWFkeSBydW5uaW5nJyk7XG4gICAgY29uc3QgY3JlYXRlID0gdGhpcy5mYWN0b3J5XG4gICAgICA/PyAoYXdhaXQgaW1wb3J0KCcuL3RzbmUtd29ya2VyJykpLmNyZWF0ZVRzbmVXb3JrZXI7XG4gICAgY29uc3Qgd29ya2VyID0gY3JlYXRlKCk7XG4gICAgdGhpcy53b3JrZXIgPSB3b3JrZXI7XG4gICAgdGhpcy5zZXR0bGVkID0gZmFsc2U7XG5cbiAgICBjb25zdCB7XG4gICAgICBzY29yZXMsIG5PYnMsIG5EaW1zLCBkaW1zLFxuICAgICAgcGVycGxleGl0eSA9IDMwLCBpdGVyYXRpb25zID0gMTAwMCwgc2VlZCA9IDAsXG4gICAgfSA9IHJlcXVlc3Q7XG4gICAgbGV0IGJhY2tlbmQ6IHN0cmluZyB8IG51bGwgPSBudWxsO1xuXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlPFNwYXRpYWxFbWJlZGRpbmcgfCBudWxsPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICBjb25zdCBmaW5pc2ggPSAoZm46ICgpID0+IHZvaWQpID0+IHtcbiAgICAgICAgaWYgKHRoaXMuc2V0dGxlZCkgcmV0dXJuO1xuICAgICAgICB0aGlzLnNldHRsZWQgPSB0cnVlO1xuICAgICAgICAvLyBDbGVhcmVkIEJFRk9SRSB0ZXJtaW5hdGluZzogYHRlcm1pbmF0ZSgpYCBjYWxscyBpdCwgYW5kIHRoaXMgaXMgd2hhdCBrZWVwcyB0aGVcbiAgICAgICAgLy8gdHdvIGZyb20gY2FsbGluZyBlYWNoIG90aGVyIHJvdW5kIGluIGEgY2lyY2xlLlxuICAgICAgICB0aGlzLmFiYW5kb24gPSBudWxsO1xuICAgICAgICB0aGlzLnRlcm1pbmF0ZSgpO1xuICAgICAgICBmbigpO1xuICAgICAgfTtcbiAgICAgIC8vIEFuIGFiYW5kb25lZCBydW4gcmVzb2x2ZXMgTlVMTCwgZXhhY3RseSBhcyBhIGNhbmNlbGxlZCBvbmUgZG9lcy4gSXQgaXMgdGhlIHNhbWVcbiAgICAgIC8vIGV2ZW50IGZyb20gdGhlIGNhbGxlcidzIHNpZGUg4oCUIHRoZSBhbnN3ZXIgd2FzIG5vIGxvbmdlciB3YW50ZWQg4oCUIGFuZCByZWplY3RpbmdcbiAgICAgIC8vIHdvdWxkIG1ha2UgYSByb3V0aW5lIGRhdGFzZXQgc3dpdGNoIGxvb2sgbGlrZSBhIGZhaWxlZCBjb21wdXRhdGlvbi5cbiAgICAgIHRoaXMuYWJhbmRvbiA9ICgpID0+IGZpbmlzaCgoKSA9PiByZXNvbHZlKG51bGwpKTtcblxuICAgICAgd29ya2VyLm9ubWVzc2FnZSA9IChldmVudDogTWVzc2FnZUV2ZW50KSA9PiB7XG4gICAgICAgIGNvbnN0IGRhdGEgPSBldmVudC5kYXRhID8/IHt9O1xuICAgICAgICBzd2l0Y2ggKGRhdGEudHlwZSkge1xuICAgICAgICAgIGNhc2UgJ2JhY2tlbmQnOlxuICAgICAgICAgICAgYmFja2VuZCA9IGRhdGEuYmFja2VuZDtcbiAgICAgICAgICAgIG9uUHJvZ3Jlc3MoeyBmcmFjdGlvbjogbnVsbCwgYmFja2VuZCwgbWVzc2FnZTogbnVsbCB9KTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgJ3dhcm5pbmcnOlxuICAgICAgICAgICAgb25Qcm9ncmVzcyh7IGZyYWN0aW9uOiBudWxsLCBiYWNrZW5kLCBtZXNzYWdlOiBkYXRhLm1lc3NhZ2UgfSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdwcm9ncmVzcyc6XG4gICAgICAgICAgICBvblByb2dyZXNzKHsgZnJhY3Rpb246IGRhdGEuZG9uZSAvIGRhdGEudG90YWwsIGJhY2tlbmQsIG1lc3NhZ2U6IG51bGwgfSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdjYW5jZWxsZWQnOlxuICAgICAgICAgICAgZmluaXNoKCgpID0+IHJlc29sdmUobnVsbCkpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSAnZG9uZSc6IHtcbiAgICAgICAgICAgIGNvbnN0IGZsYXQgPSBuZXcgRmxvYXQzMkFycmF5KGRhdGEuZW1iZWRkaW5nKTtcbiAgICAgICAgICAgIC8vIFN0cnVjdCBvZiBhcnJheXMsIG1hdGNoaW5nIHdoYXQgdGhlIHBvcnQgc2VydmVzLCBzbyB0aGUgY2hhcnQgY2Fubm90IHRlbGwgYVxuICAgICAgICAgICAgLy8gbG9jYWxseS1jb21wdXRlZCBlbWJlZGRpbmcgZnJvbSBhIGZldGNoZWQgb25lLlxuICAgICAgICAgICAgY29uc3QgcGxhbmVzOiBGbG9hdDMyQXJyYXlbXSA9IFtdO1xuICAgICAgICAgICAgZm9yIChsZXQgZCA9IDA7IGQgPCBkYXRhLmRpbXM7IGQrKykge1xuICAgICAgICAgICAgICBjb25zdCBwbGFuZSA9IG5ldyBGbG9hdDMyQXJyYXkobk9icyk7XG4gICAgICAgICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbk9iczsgaSsrKSBwbGFuZVtpXSA9IGZsYXRbaSAqIGRhdGEuZGltcyArIGRdO1xuICAgICAgICAgICAgICBwbGFuZXMucHVzaChwbGFuZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBmaW5pc2goKCkgPT4gcmVzb2x2ZSh7XG4gICAgICAgICAgICAgIG1ldGE6IHtcbiAgICAgICAgICAgICAgICAuLi5tZXRhLFxuICAgICAgICAgICAgICAgIGRlcml2ZWQ6IHRydWUsXG4gICAgICAgICAgICAgICAgcGFyYW1zOiBgUENBKCR7bkRpbXN9KSB0aGVuIHQtU05FLCBwZXJwbGV4aXR5ICR7ZGF0YS5wZXJwbGV4aXR5fSwgYFxuICAgICAgICAgICAgICAgICAgKyBgJHtpdGVyYXRpb25zfSBpdGVyYXRpb25zLCBzZWVkICR7c2VlZH1gLFxuICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICB4OiBwbGFuZXNbMF0sXG4gICAgICAgICAgICAgIHk6IHBsYW5lc1sxXSxcbiAgICAgICAgICAgICAgLi4uKHBsYW5lc1syXSA/IHsgejogcGxhbmVzWzJdIH0gOiB7fSksXG4gICAgICAgICAgICB9KSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICB9XG4gICAgICAgICAgY2FzZSAnZXJyb3InOlxuICAgICAgICAgICAgZmluaXNoKCgpID0+IHJlamVjdChuZXcgRXJyb3IoZGF0YS5tZXNzYWdlKSkpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICB9O1xuICAgICAgLy8gQSB3b3JrZXIgdGhhdCBkaWVzIHRha2VzIHRoZSBwcm9taXNlIHdpdGggaXQ7IHdpdGhvdXQgdGhpcyB0aGUgY2FsbGVyIHdhaXRzIGZvclxuICAgICAgLy8gZXZlciBvbiBhIHByb2dyZXNzIGJhciB0aGF0IGhhcyBzdG9wcGVkIG1vdmluZy5cbiAgICAgIHdvcmtlci5vbmVycm9yID0gKGV2ZW50KSA9PiBmaW5pc2goXG4gICAgICAgICgpID0+IHJlamVjdChuZXcgRXJyb3IoYGVtYmVkZGluZyB3b3JrZXIgZmFpbGVkOiAke2V2ZW50Lm1lc3NhZ2UgPz8gJ3Vua25vd24nfWApKSxcbiAgICAgICk7XG5cbiAgICAgIC8vIENvcGllZCwgdGhlbiB0cmFuc2ZlcnJlZDogdGhlIGNhbGxlciBrZWVwcyBpdHMgc2NvcmVzLCBhbmQgdGhlIHdvcmtlciBnZXRzIHRoZVxuICAgICAgLy8gYnVmZmVyIHdpdGhvdXQgYSBzdHJ1Y3R1cmVkIGNsb25lLlxuICAgICAgY29uc3QgcGF5bG9hZCA9IHNjb3Jlcy5zbGljZSgpO1xuICAgICAgd29ya2VyLnBvc3RNZXNzYWdlKFxuICAgICAgICB7XG4gICAgICAgICAgdHlwZTogJ3N0YXJ0JyxcbiAgICAgICAgICBzY29yZXM6IHBheWxvYWQuYnVmZmVyLFxuICAgICAgICAgIG5PYnMsXG4gICAgICAgICAgbkRpbXMsXG4gICAgICAgICAgZGltcyxcbiAgICAgICAgICBwZXJwbGV4aXR5LFxuICAgICAgICAgIGl0ZXJhdGlvbnMsXG4gICAgICAgICAgc2VlZCxcbiAgICAgICAgfSxcbiAgICAgICAgW3BheWxvYWQuYnVmZmVyXSxcbiAgICAgICk7XG4gICAgfSk7XG4gIH1cblxuICAvKiogQXNrIHRoZSBydW4gdG8gc3RvcC4gSXQgcmVzb2x2ZXMgd2l0aCBudWxsIHNob3J0bHkgYWZ0ZXIuICovXG4gIGNhbmNlbCgpOiB2b2lkIHtcbiAgICB0aGlzLndvcmtlcj8ucG9zdE1lc3NhZ2UoeyB0eXBlOiAnY2FuY2VsJyB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdG9wIGltbWVkaWF0ZWx5LCBhYmFuZG9uaW5nIGFueSByZXN1bHQuIFNhZmUgdG8gY2FsbCB0d2ljZS5cbiAgICpcbiAgICogVW5saWtlIHtAbGluayBjYW5jZWx9LCB0aGlzIGRvZXMgbm90IHdhaXQgZm9yIHRoZSB3b3JrZXIgdG8gYWNrbm93bGVkZ2Ug4oCUIHRoZSBydW4gaXNcbiAgICogc2V0dGxlZCBoZXJlLCB3aXRoIG51bGwsIGJlY2F1c2Ugbm90aGluZyBpcyBsZWZ0IHRvIGRlbGl2ZXIgdGhlIGFuc3dlci5cbiAgICovXG4gIHRlcm1pbmF0ZSgpOiB2b2lkIHtcbiAgICBjb25zdCBhYmFuZG9uID0gdGhpcy5hYmFuZG9uO1xuICAgIHRoaXMuYWJhbmRvbiA9IG51bGw7XG4gICAgdGhpcy53b3JrZXI/LnRlcm1pbmF0ZSgpO1xuICAgIHRoaXMud29ya2VyID0gbnVsbDtcbiAgICBhYmFuZG9uPy4oKTtcbiAgfVxuXG4gIGdldCBydW5uaW5nKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLndvcmtlciAhPT0gbnVsbCAmJiAhdGhpcy5zZXR0bGVkO1xuICB9XG59XG4iXX0=
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Narrowing a gene list to the handful a picker should actually render.
3
+ *
4
+ * A targeted panel has a few hundred genes and can simply be listed. A
5
+ * whole-transcriptome assay has ~18k-31k, and handing that many options to a dropdown
6
+ * costs a visible freeze on every keystroke — the control rebuilds and re-filters the
7
+ * whole array, and the browser lays out what the virtual scroller has not yet clipped.
8
+ * Measured on the Visium H&E bundle: 18,078 names.
9
+ *
10
+ * The names themselves are cheap to hold — strings already parsed from the manifest — so
11
+ * this does not fetch anything. What it caps is how many OPTION OBJECTS are ever
12
+ * materialised for the view. The rest of the corpus stays a plain array, searched per
13
+ * keystroke, which is the work a dropdown was doing anyway; the saving is in not asking
14
+ * a component to render, diff and re-filter thousands of rows it will never show.
15
+ *
16
+ * Ranking is PREFIX FIRST, then substring. Typing "Mb" should surface `Mbp` above
17
+ * `Adam1b`, which a plain `includes` would not do. It deliberately mirrors the example
18
+ * server's `/spatial/:id/features?q=` (see `lib/spatial.mjs`) so a dataset that inlines
19
+ * its names and one that does not order their suggestions the same way — the picker must
20
+ * not appear to rank differently depending on how the data happens to be served.
21
+ *
22
+ * Pure — no Angular, no port, no DOM — like `spatial-heatmap.ts`, and tested the same way.
23
+ */
24
+ /**
25
+ * How many options a picker will materialise at once.
26
+ *
27
+ * Above the virtual-scroll threshold, so a capped list is still scrolled rather than laid
28
+ * out in full, and far below the point where rebuilding the array is perceptible.
29
+ */
30
+ export const GENE_OPTIONS_MAX = 500;
31
+ /**
32
+ * The best `limit` matches for `query`, prefix matches first.
33
+ *
34
+ * An empty query returns the head of the list rather than nothing: the dropdown opens
35
+ * before anything is typed, and an empty menu reads as "no genes" rather than "start
36
+ * typing". Matching is case-insensitive, because gene symbols are capitalised by
37
+ * convention and nobody types `Slc17a7` exactly.
38
+ */
39
+ export function searchGeneNames(names, query, limit = GENE_OPTIONS_MAX) {
40
+ if (limit <= 0)
41
+ return [];
42
+ const q = query.trim().toLowerCase();
43
+ if (!q)
44
+ return names.slice(0, limit);
45
+ const prefix = [];
46
+ const contains = [];
47
+ for (const name of names) {
48
+ const lower = name.toLowerCase();
49
+ if (lower.startsWith(q)) {
50
+ prefix.push(name);
51
+ // Enough prefix matches to fill the list: nothing a substring match could add
52
+ // would outrank them, so the rest of the corpus cannot change the answer.
53
+ if (prefix.length >= limit)
54
+ return prefix;
55
+ }
56
+ else if (lower.includes(q) && contains.length < limit) {
57
+ contains.push(name);
58
+ }
59
+ }
60
+ return [...prefix, ...contains].slice(0, limit);
61
+ }
62
+ /**
63
+ * The options a picker should show: the best matches, plus anything already chosen.
64
+ *
65
+ * A multi-select whose option list is capped must still carry its SELECTED values, or the
66
+ * control cannot resolve them — the chips lose their labels and, worse, a value absent
67
+ * from the options can be dropped from the model on the next change. Selections come
68
+ * first so they stay visible while a search narrows everything else.
69
+ */
70
+ export function geneOptionsFor(names, query, selected = [], limit = GENE_OPTIONS_MAX) {
71
+ const matches = searchGeneNames(names, query, limit);
72
+ if (selected.length === 0)
73
+ return matches;
74
+ const seen = new Set(selected);
75
+ return [...selected, ...matches.filter((n) => !seen.has(n))];
76
+ }
77
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZS1zZWFyY2guanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3NwYXRpYWwvZ2VuZS1zZWFyY2gudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FzQkc7QUFFSDs7Ozs7R0FLRztBQUNILE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLEdBQUcsQ0FBQztBQUVwQzs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLGVBQWUsQ0FDN0IsS0FBd0IsRUFDeEIsS0FBYSxFQUNiLEtBQUssR0FBRyxnQkFBZ0I7SUFFeEIsSUFBSSxLQUFLLElBQUksQ0FBQztRQUFFLE9BQU8sRUFBRSxDQUFDO0lBQzFCLE1BQU0sQ0FBQyxHQUFHLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUNyQyxJQUFJLENBQUMsQ0FBQztRQUFFLE9BQU8sS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFFckMsTUFBTSxNQUFNLEdBQWEsRUFBRSxDQUFDO0lBQzVCLE1BQU0sUUFBUSxHQUFhLEVBQUUsQ0FBQztJQUM5QixLQUFLLE1BQU0sSUFBSSxJQUFJLEtBQUssRUFBRSxDQUFDO1FBQ3pCLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUNqQyxJQUFJLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUN4QixNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ2xCLDhFQUE4RTtZQUM5RSwwRUFBMEU7WUFDMUUsSUFBSSxNQUFNLENBQUMsTUFBTSxJQUFJLEtBQUs7Z0JBQUUsT0FBTyxNQUFNLENBQUM7UUFDNUMsQ0FBQzthQUFNLElBQUksS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxRQUFRLENBQUMsTUFBTSxHQUFHLEtBQUssRUFBRSxDQUFDO1lBQ3hELFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDdEIsQ0FBQztJQUNILENBQUM7SUFDRCxPQUFPLENBQUMsR0FBRyxNQUFNLEVBQUUsR0FBRyxRQUFRLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBQ2xELENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLGNBQWMsQ0FDNUIsS0FBd0IsRUFDeEIsS0FBYSxFQUNiLFdBQThCLEVBQUUsRUFDaEMsS0FBSyxHQUFHLGdCQUFnQjtJQUV4QixNQUFNLE9BQU8sR0FBRyxlQUFlLENBQUMsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBQztJQUNyRCxJQUFJLFFBQVEsQ0FBQyxNQUFNLEtBQUssQ0FBQztRQUFFLE9BQU8sT0FBTyxDQUFDO0lBQzFDLE1BQU0sSUFBSSxHQUFHLElBQUksR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQy9CLE9BQU8sQ0FBQyxHQUFHLFFBQVEsRUFBRSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDL0QsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogTmFycm93aW5nIGEgZ2VuZSBsaXN0IHRvIHRoZSBoYW5kZnVsIGEgcGlja2VyIHNob3VsZCBhY3R1YWxseSByZW5kZXIuXG4gKlxuICogQSB0YXJnZXRlZCBwYW5lbCBoYXMgYSBmZXcgaHVuZHJlZCBnZW5lcyBhbmQgY2FuIHNpbXBseSBiZSBsaXN0ZWQuIEFcbiAqIHdob2xlLXRyYW5zY3JpcHRvbWUgYXNzYXkgaGFzIH4xOGstMzFrLCBhbmQgaGFuZGluZyB0aGF0IG1hbnkgb3B0aW9ucyB0byBhIGRyb3Bkb3duXG4gKiBjb3N0cyBhIHZpc2libGUgZnJlZXplIG9uIGV2ZXJ5IGtleXN0cm9rZSDigJQgdGhlIGNvbnRyb2wgcmVidWlsZHMgYW5kIHJlLWZpbHRlcnMgdGhlXG4gKiB3aG9sZSBhcnJheSwgYW5kIHRoZSBicm93c2VyIGxheXMgb3V0IHdoYXQgdGhlIHZpcnR1YWwgc2Nyb2xsZXIgaGFzIG5vdCB5ZXQgY2xpcHBlZC5cbiAqIE1lYXN1cmVkIG9uIHRoZSBWaXNpdW0gSCZFIGJ1bmRsZTogMTgsMDc4IG5hbWVzLlxuICpcbiAqIFRoZSBuYW1lcyB0aGVtc2VsdmVzIGFyZSBjaGVhcCB0byBob2xkIOKAlCBzdHJpbmdzIGFscmVhZHkgcGFyc2VkIGZyb20gdGhlIG1hbmlmZXN0IOKAlCBzb1xuICogdGhpcyBkb2VzIG5vdCBmZXRjaCBhbnl0aGluZy4gV2hhdCBpdCBjYXBzIGlzIGhvdyBtYW55IE9QVElPTiBPQkpFQ1RTIGFyZSBldmVyXG4gKiBtYXRlcmlhbGlzZWQgZm9yIHRoZSB2aWV3LiBUaGUgcmVzdCBvZiB0aGUgY29ycHVzIHN0YXlzIGEgcGxhaW4gYXJyYXksIHNlYXJjaGVkIHBlclxuICoga2V5c3Ryb2tlLCB3aGljaCBpcyB0aGUgd29yayBhIGRyb3Bkb3duIHdhcyBkb2luZyBhbnl3YXk7IHRoZSBzYXZpbmcgaXMgaW4gbm90IGFza2luZ1xuICogYSBjb21wb25lbnQgdG8gcmVuZGVyLCBkaWZmIGFuZCByZS1maWx0ZXIgdGhvdXNhbmRzIG9mIHJvd3MgaXQgd2lsbCBuZXZlciBzaG93LlxuICpcbiAqIFJhbmtpbmcgaXMgUFJFRklYIEZJUlNULCB0aGVuIHN1YnN0cmluZy4gVHlwaW5nIFwiTWJcIiBzaG91bGQgc3VyZmFjZSBgTWJwYCBhYm92ZVxuICogYEFkYW0xYmAsIHdoaWNoIGEgcGxhaW4gYGluY2x1ZGVzYCB3b3VsZCBub3QgZG8uIEl0IGRlbGliZXJhdGVseSBtaXJyb3JzIHRoZSBleGFtcGxlXG4gKiBzZXJ2ZXIncyBgL3NwYXRpYWwvOmlkL2ZlYXR1cmVzP3E9YCAoc2VlIGBsaWIvc3BhdGlhbC5tanNgKSBzbyBhIGRhdGFzZXQgdGhhdCBpbmxpbmVzXG4gKiBpdHMgbmFtZXMgYW5kIG9uZSB0aGF0IGRvZXMgbm90IG9yZGVyIHRoZWlyIHN1Z2dlc3Rpb25zIHRoZSBzYW1lIHdheSDigJQgdGhlIHBpY2tlciBtdXN0XG4gKiBub3QgYXBwZWFyIHRvIHJhbmsgZGlmZmVyZW50bHkgZGVwZW5kaW5nIG9uIGhvdyB0aGUgZGF0YSBoYXBwZW5zIHRvIGJlIHNlcnZlZC5cbiAqXG4gKiBQdXJlIOKAlCBubyBBbmd1bGFyLCBubyBwb3J0LCBubyBET00g4oCUIGxpa2UgYHNwYXRpYWwtaGVhdG1hcC50c2AsIGFuZCB0ZXN0ZWQgdGhlIHNhbWUgd2F5LlxuICovXG5cbi8qKlxuICogSG93IG1hbnkgb3B0aW9ucyBhIHBpY2tlciB3aWxsIG1hdGVyaWFsaXNlIGF0IG9uY2UuXG4gKlxuICogQWJvdmUgdGhlIHZpcnR1YWwtc2Nyb2xsIHRocmVzaG9sZCwgc28gYSBjYXBwZWQgbGlzdCBpcyBzdGlsbCBzY3JvbGxlZCByYXRoZXIgdGhhbiBsYWlkXG4gKiBvdXQgaW4gZnVsbCwgYW5kIGZhciBiZWxvdyB0aGUgcG9pbnQgd2hlcmUgcmVidWlsZGluZyB0aGUgYXJyYXkgaXMgcGVyY2VwdGlibGUuXG4gKi9cbmV4cG9ydCBjb25zdCBHRU5FX09QVElPTlNfTUFYID0gNTAwO1xuXG4vKipcbiAqIFRoZSBiZXN0IGBsaW1pdGAgbWF0Y2hlcyBmb3IgYHF1ZXJ5YCwgcHJlZml4IG1hdGNoZXMgZmlyc3QuXG4gKlxuICogQW4gZW1wdHkgcXVlcnkgcmV0dXJucyB0aGUgaGVhZCBvZiB0aGUgbGlzdCByYXRoZXIgdGhhbiBub3RoaW5nOiB0aGUgZHJvcGRvd24gb3BlbnNcbiAqIGJlZm9yZSBhbnl0aGluZyBpcyB0eXBlZCwgYW5kIGFuIGVtcHR5IG1lbnUgcmVhZHMgYXMgXCJubyBnZW5lc1wiIHJhdGhlciB0aGFuIFwic3RhcnRcbiAqIHR5cGluZ1wiLiBNYXRjaGluZyBpcyBjYXNlLWluc2Vuc2l0aXZlLCBiZWNhdXNlIGdlbmUgc3ltYm9scyBhcmUgY2FwaXRhbGlzZWQgYnlcbiAqIGNvbnZlbnRpb24gYW5kIG5vYm9keSB0eXBlcyBgU2xjMTdhN2AgZXhhY3RseS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNlYXJjaEdlbmVOYW1lcyhcbiAgbmFtZXM6IHJlYWRvbmx5IHN0cmluZ1tdLFxuICBxdWVyeTogc3RyaW5nLFxuICBsaW1pdCA9IEdFTkVfT1BUSU9OU19NQVgsXG4pOiBzdHJpbmdbXSB7XG4gIGlmIChsaW1pdCA8PSAwKSByZXR1cm4gW107XG4gIGNvbnN0IHEgPSBxdWVyeS50cmltKCkudG9Mb3dlckNhc2UoKTtcbiAgaWYgKCFxKSByZXR1cm4gbmFtZXMuc2xpY2UoMCwgbGltaXQpO1xuXG4gIGNvbnN0IHByZWZpeDogc3RyaW5nW10gPSBbXTtcbiAgY29uc3QgY29udGFpbnM6IHN0cmluZ1tdID0gW107XG4gIGZvciAoY29uc3QgbmFtZSBvZiBuYW1lcykge1xuICAgIGNvbnN0IGxvd2VyID0gbmFtZS50b0xvd2VyQ2FzZSgpO1xuICAgIGlmIChsb3dlci5zdGFydHNXaXRoKHEpKSB7XG4gICAgICBwcmVmaXgucHVzaChuYW1lKTtcbiAgICAgIC8vIEVub3VnaCBwcmVmaXggbWF0Y2hlcyB0byBmaWxsIHRoZSBsaXN0OiBub3RoaW5nIGEgc3Vic3RyaW5nIG1hdGNoIGNvdWxkIGFkZFxuICAgICAgLy8gd291bGQgb3V0cmFuayB0aGVtLCBzbyB0aGUgcmVzdCBvZiB0aGUgY29ycHVzIGNhbm5vdCBjaGFuZ2UgdGhlIGFuc3dlci5cbiAgICAgIGlmIChwcmVmaXgubGVuZ3RoID49IGxpbWl0KSByZXR1cm4gcHJlZml4O1xuICAgIH0gZWxzZSBpZiAobG93ZXIuaW5jbHVkZXMocSkgJiYgY29udGFpbnMubGVuZ3RoIDwgbGltaXQpIHtcbiAgICAgIGNvbnRhaW5zLnB1c2gobmFtZSk7XG4gICAgfVxuICB9XG4gIHJldHVybiBbLi4ucHJlZml4LCAuLi5jb250YWluc10uc2xpY2UoMCwgbGltaXQpO1xufVxuXG4vKipcbiAqIFRoZSBvcHRpb25zIGEgcGlja2VyIHNob3VsZCBzaG93OiB0aGUgYmVzdCBtYXRjaGVzLCBwbHVzIGFueXRoaW5nIGFscmVhZHkgY2hvc2VuLlxuICpcbiAqIEEgbXVsdGktc2VsZWN0IHdob3NlIG9wdGlvbiBsaXN0IGlzIGNhcHBlZCBtdXN0IHN0aWxsIGNhcnJ5IGl0cyBTRUxFQ1RFRCB2YWx1ZXMsIG9yIHRoZVxuICogY29udHJvbCBjYW5ub3QgcmVzb2x2ZSB0aGVtIOKAlCB0aGUgY2hpcHMgbG9zZSB0aGVpciBsYWJlbHMgYW5kLCB3b3JzZSwgYSB2YWx1ZSBhYnNlbnRcbiAqIGZyb20gdGhlIG9wdGlvbnMgY2FuIGJlIGRyb3BwZWQgZnJvbSB0aGUgbW9kZWwgb24gdGhlIG5leHQgY2hhbmdlLiBTZWxlY3Rpb25zIGNvbWVcbiAqIGZpcnN0IHNvIHRoZXkgc3RheSB2aXNpYmxlIHdoaWxlIGEgc2VhcmNoIG5hcnJvd3MgZXZlcnl0aGluZyBlbHNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2VuZU9wdGlvbnNGb3IoXG4gIG5hbWVzOiByZWFkb25seSBzdHJpbmdbXSxcbiAgcXVlcnk6IHN0cmluZyxcbiAgc2VsZWN0ZWQ6IHJlYWRvbmx5IHN0cmluZ1tdID0gW10sXG4gIGxpbWl0ID0gR0VORV9PUFRJT05TX01BWCxcbik6IHN0cmluZ1tdIHtcbiAgY29uc3QgbWF0Y2hlcyA9IHNlYXJjaEdlbmVOYW1lcyhuYW1lcywgcXVlcnksIGxpbWl0KTtcbiAgaWYgKHNlbGVjdGVkLmxlbmd0aCA9PT0gMCkgcmV0dXJuIG1hdGNoZXM7XG4gIGNvbnN0IHNlZW4gPSBuZXcgU2V0KHNlbGVjdGVkKTtcbiAgcmV0dXJuIFsuLi5zZWxlY3RlZCwgLi4ubWF0Y2hlcy5maWx0ZXIoKG4pID0+ICFzZWVuLmhhcyhuKSldO1xufVxuIl19
@@ -0,0 +1,224 @@
1
+ /**
2
+ * The lattice to estimate on.
3
+ *
4
+ * A dataset with a registered volume estimates on that volume's own box, so the
5
+ * field lands exactly on the anatomy it will be drawn inside — `stride` coarsens
6
+ * it (density is smooth by construction, and an eighth of the voxels is an eighth
7
+ * of the work) while keeping the physical extent identical, so the two still
8
+ * align. Without a volume the box comes from the observations' own bounds.
9
+ */
10
+ export function densityGrid(dataset, stride = 2, targetLongAxis = 128, zStride = stride) {
11
+ const volume = dataset.volume;
12
+ if (volume) {
13
+ const cells = (n, by) => Math.max(1, Math.ceil(n / by));
14
+ // z can be coarsened less than x/y — a gene map's sheets need one plane per
15
+ // imaged section, while its in-plane detail is smooth by construction. The
16
+ // physical extent is unchanged either way, so the box still aligns.
17
+ const [w, h, d] = [
18
+ cells(volume.width, stride), cells(volume.height, stride), cells(volume.depth, zStride),
19
+ ];
20
+ // Voxel size from the SPAN, not stride x original: `ceil` can add a fraction
21
+ // of a voxel, and scaling the original size would push the far edge past the
22
+ // reference volume's.
23
+ return {
24
+ width: w,
25
+ height: h,
26
+ depth: d,
27
+ voxelSize: [
28
+ (volume.width * volume.voxelSize[0]) / w,
29
+ (volume.height * volume.voxelSize[1]) / h,
30
+ (volume.depth * volume.voxelSize[2]) / d,
31
+ ],
32
+ };
33
+ }
34
+ const obs = dataset.observations;
35
+ if (!obs.z || obs.count === 0)
36
+ return null;
37
+ // Measured from the coordinate ORIGIN, not from the cloud's own minimum: the box
38
+ // then has its near corner at the origin exactly as a volume's does, which is
39
+ // what lets a renderer centre either one with the same half-box offset. A cloud
40
+ // sitting far from the origin just gets a coarser grid, not a wrong one.
41
+ const extent = (v) => {
42
+ let hi = 0;
43
+ for (let i = 0; i < obs.count; i++)
44
+ if (v[i] > hi)
45
+ hi = v[i];
46
+ return Math.max(hi, 1);
47
+ };
48
+ const [sx, sy, sz] = [extent(obs.x), extent(obs.y), extent(obs.z)];
49
+ const voxel = Math.max(sx, sy, sz) / targetLongAxis;
50
+ return {
51
+ width: Math.max(1, Math.ceil(sx / voxel)),
52
+ height: Math.max(1, Math.ceil(sy / voxel)),
53
+ depth: Math.max(1, Math.ceil(sz / voxel)),
54
+ voxelSize: [voxel, voxel, voxel],
55
+ };
56
+ }
57
+ /**
58
+ * In-place separable Gaussian along one axis of a w x h x d field.
59
+ *
60
+ * Exported because the gene-map volume estimates a different quantity on the same
61
+ * lattice with the same kernel (see `spatial-expression.ts`): two copies of a
62
+ * separable blur would be two chances to smooth the estimate and its normaliser
63
+ * differently, which is the one thing a Nadaraya-Watson field cannot survive.
64
+ */
65
+ export function blurVolumeAxis(field, w, h, d, axis, sigmaVox) {
66
+ if (!(sigmaVox > 0.01))
67
+ return;
68
+ const radius = Math.max(1, Math.ceil(sigmaVox * 3));
69
+ const kernel = new Float32Array(radius * 2 + 1);
70
+ let sum = 0;
71
+ for (let i = -radius; i <= radius; i++) {
72
+ const v = Math.exp(-(i * i) / (2 * sigmaVox * sigmaVox));
73
+ kernel[i + radius] = v;
74
+ sum += v;
75
+ }
76
+ for (let i = 0; i < kernel.length; i++)
77
+ kernel[i] /= sum;
78
+ const len = axis === 0 ? w : axis === 1 ? h : d;
79
+ const stride = axis === 0 ? 1 : axis === 1 ? w : w * h;
80
+ const lines = axis === 0 ? h * d : axis === 1 ? w * d : w * h;
81
+ const line = new Float32Array(len);
82
+ for (let l = 0; l < lines; l++) {
83
+ // Offset of this line's first element: the two axes that are not being
84
+ // blurred, laid back out in x-fastest order.
85
+ let base;
86
+ if (axis === 0)
87
+ base = (l % h) * w + Math.floor(l / h) * w * h;
88
+ else if (axis === 1)
89
+ base = (l % w) + Math.floor(l / w) * w * h;
90
+ else
91
+ base = l;
92
+ for (let i = 0; i < len; i++)
93
+ line[i] = field[base + i * stride];
94
+ for (let i = 0; i < len; i++) {
95
+ let acc = 0;
96
+ for (let k = -radius; k <= radius; k++) {
97
+ // Clamp at the edges: zero-padding would darken the specimen's own
98
+ // boundary, which is exactly where a cluster often sits.
99
+ const j = Math.min(len - 1, Math.max(0, i + k));
100
+ acc += line[j] * kernel[k + radius];
101
+ }
102
+ field[base + i * stride] = acc;
103
+ }
104
+ }
105
+ }
106
+ /**
107
+ * Which z planes of the grid contain any observation at all — the sampled
108
+ * support, taken from the WHOLE dataset rather than from the subset being
109
+ * rasterised. A rare cluster is sparse because it is rare; the gaps between
110
+ * sections are empty because nobody imaged there, and only the second is a
111
+ * sampling artefact to correct for.
112
+ */
113
+ function sampledPlanes(obs, grid) {
114
+ const cover = new Float32Array(grid.depth);
115
+ const z = obs.z;
116
+ if (!z)
117
+ return { cover: cover.fill(1), first: 0, last: grid.depth - 1 };
118
+ let first = grid.depth;
119
+ let last = -1;
120
+ for (let i = 0; i < obs.count; i++) {
121
+ const k = Math.floor(z[i] / grid.voxelSize[2]);
122
+ if (k < 0 || k >= grid.depth)
123
+ continue;
124
+ cover[k] = 1;
125
+ if (k < first)
126
+ first = k;
127
+ if (k > last)
128
+ last = k;
129
+ }
130
+ return { cover, first, last };
131
+ }
132
+ /** Blur a 1D profile with the same kernel `blurVolumeAxis` uses, so the correction and
133
+ * the field are smoothed identically. */
134
+ function blur1d(profile, sigmaVox) {
135
+ const out = profile.slice();
136
+ blurVolumeAxis(out, 1, 1, out.length, 2, sigmaVox);
137
+ return out;
138
+ }
139
+ /**
140
+ * Rasterise observations into a uint8 density field on `grid`.
141
+ *
142
+ * Returned as uint8 scaled to its own maximum: a `VolumeLayer` renders 0..255, and
143
+ * the absolute cell count per voxel is not the readable quantity anyway — where
144
+ * the cluster is dense RELATIVE to itself is. Null when nothing lands on the grid,
145
+ * so the caller draws no layer rather than an empty box.
146
+ */
147
+ export function rasterizeDensity(obs, grid, opts) {
148
+ const { width: w, height: h, depth: d, voxelSize } = grid;
149
+ const field = new Float32Array(w * h * d);
150
+ const idx = opts.indices;
151
+ const n = idx ? idx.length : obs.count;
152
+ const z = obs.z;
153
+ let placed = 0;
154
+ for (let k = 0; k < n; k++) {
155
+ const i = idx ? idx[k] : k;
156
+ const vx = Math.floor(obs.x[i] / voxelSize[0]);
157
+ const vy = Math.floor(obs.y[i] / voxelSize[1]);
158
+ const vz = z ? Math.floor(z[i] / voxelSize[2]) : 0;
159
+ if (vx < 0 || vx >= w || vy < 0 || vy >= h || vz < 0 || vz >= d)
160
+ continue;
161
+ field[(vz * h + vy) * w + vx] += 1;
162
+ placed++;
163
+ }
164
+ if (!placed)
165
+ return null;
166
+ const sigmaVox = [
167
+ opts.sigma[0] / voxelSize[0], opts.sigma[1] / voxelSize[1], opts.sigma[2] / voxelSize[2],
168
+ ];
169
+ blurVolumeAxis(field, w, h, d, 0, sigmaVox[0]);
170
+ blurVolumeAxis(field, w, h, d, 1, sigmaVox[1]);
171
+ blurVolumeAxis(field, w, h, d, 2, sigmaVox[2]);
172
+ // Coverage correction, per plane: the same kernel over the sampled support says
173
+ // how much of each plane's neighbourhood was actually imaged.
174
+ const { cover, first, last } = sampledPlanes(obs, grid);
175
+ const smoothedCover = blur1d(cover, sigmaVox[2]);
176
+ let max = 0;
177
+ const plane = w * h;
178
+ for (let k = 0; k < d; k++) {
179
+ // OUTSIDE the sampled range there is nothing to interpolate between, only
180
+ // one side to extrapolate from — so the field is zeroed rather than scaled.
181
+ // The blur leaves a positive tail past the first and last imaged planes, and
182
+ // keeping it would put estimated cells in front of the specimen's first
183
+ // section and behind its last, which is exactly what "smoothed BETWEEN the
184
+ // imaged sections" promises not to do.
185
+ if (k < first || k > last) {
186
+ field.fill(0, k * plane, (k + 1) * plane);
187
+ continue;
188
+ }
189
+ // Inside it, the correction BRIDGES gaps. Capping the amplification at 2x
190
+ // (coverage floored at 0.5) lifts a plane sitting between two imaged sections
191
+ // — where a bandwidth spanning one gap leaves coverage around a half —
192
+ // without inflating a trace of smoothing leakage into a signal.
193
+ const scale = 1 / Math.max(smoothedCover[k], 0.5);
194
+ for (let i = k * plane; i < (k + 1) * plane; i++) {
195
+ const v = field[i] * scale;
196
+ field[i] = v;
197
+ if (v > max)
198
+ max = v;
199
+ }
200
+ }
201
+ if (!(max > 0))
202
+ return null;
203
+ const out = new Uint8Array(field.length);
204
+ for (let i = 0; i < field.length; i++)
205
+ out[i] = Math.round((field[i] / max) * 255);
206
+ return out;
207
+ }
208
+ /**
209
+ * Default kernel for a grid: in-plane tight, along z at least the section
210
+ * spacing.
211
+ *
212
+ * The z term is the load-bearing one — 1.5 voxels of a grid whose depth voxel IS
213
+ * the section spacing puts σ just above one section gap, which is the smallest
214
+ * bandwidth that can bridge one without leaving a disc per plane. `smoothing`
215
+ * scales all three for the user control.
216
+ */
217
+ export function defaultSigma(grid, smoothing = 1) {
218
+ return [
219
+ grid.voxelSize[0] * 1.5 * smoothing,
220
+ grid.voxelSize[1] * 1.5 * smoothing,
221
+ grid.voxelSize[2] * 1.5 * smoothing,
222
+ ];
223
+ }
224
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhdGlhbC1kZW5zaXR5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9zcGF0aWFsL3NwYXRpYWwtZGVuc2l0eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUF1Q0E7Ozs7Ozs7O0dBUUc7QUFDSCxNQUFNLFVBQVUsV0FBVyxDQUN6QixPQUF1QixFQUFFLE1BQU0sR0FBRyxDQUFDLEVBQUUsY0FBYyxHQUFHLEdBQUcsRUFBRSxPQUFPLEdBQUcsTUFBTTtJQUUzRSxNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDO0lBQzlCLElBQUksTUFBTSxFQUFFLENBQUM7UUFDWCxNQUFNLEtBQUssR0FBRyxDQUFDLENBQVMsRUFBRSxFQUFVLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFDeEUsNEVBQTRFO1FBQzVFLDJFQUEyRTtRQUMzRSxvRUFBb0U7UUFDcEUsTUFBTSxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLEdBQUc7WUFDaEIsS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLEVBQUUsS0FBSyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsTUFBTSxDQUFDLEVBQUUsS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsT0FBTyxDQUFDO1NBQ3hGLENBQUM7UUFDRiw2RUFBNkU7UUFDN0UsNkVBQTZFO1FBQzdFLHNCQUFzQjtRQUN0QixPQUFPO1lBQ0wsS0FBSyxFQUFFLENBQUM7WUFDUixNQUFNLEVBQUUsQ0FBQztZQUNULEtBQUssRUFBRSxDQUFDO1lBQ1IsU0FBUyxFQUFFO2dCQUNULENBQUMsTUFBTSxDQUFDLEtBQUssR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQztnQkFDeEMsQ0FB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QUFDLENBQUMsR0FBRyxLQUFLLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztZQUNqRCxNQUFNLENBQUMsR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsS0FBSyxDQUFDO1lBQzNCLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDYixJQUFJLENBQUMsR0FBRyxHQUFHO2dCQUFFLEdBQUcsR0FBRyxDQUFDLENBQUM7UUFDdkIsQ0FBQztJQUNILENBQUM7SUFDRCxJQUFJLENBQUMsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFFNUIsTUFBTSxHQUFHLEdBQUcsSUFBSSxVQUFVLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ3pDLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRTtRQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxDQUFDO0lBQ25GLE9BQU8sR0FBRyxDQUFDO0FBQ2IsQ0FBQztBQUVEOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxVQUFVLFlBQVksQ0FBQyxJQUFpQixFQUFFLFNBQVMsR0FBRyxDQUFDO0lBQzNELE9BQU87UUFDTCxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxHQUFHLEdBQUcsR0FBRyxTQUFTO1FBQ25DLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLEdBQUcsR0FBRyxHQUFHLFNBQVM7UUFDbkMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsR0FBRyxHQUFHLEdBQUcsU0FBUztLQUNwQyxDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFNwYXRpYWxEYXRhc2V0LCBTcGF0aWFsT2JzZXJ2YXRpb25zIH0gZnJvbSAnLi4vY29udHJhY3RzL3NwYXRpYWwtZGF0YXNldC5jb250cmFjdCc7XG5cbi8qKlxuICogVHVybmluZyBhIGNlbGwgY2xvdWQgaW50byBhIGRlbnNpdHkgZmllbGQg4oCUIHRoZSBob25lc3Qgd2F5IHRvIHJlbmRlciBhIGNsdXN0ZXJcbiAqIGluIDNEIHdoZW4gdGhlIHNlY3Rpb25zIGl0IHdhcyBtZWFzdXJlZCBvbiBhcmUgaHVuZHJlZHMgb2YgbWljcm9ucyBhcGFydC5cbiAqXG4gKiBJbmRpdmlkdWFsIGNlbGxzIENBTk5PVCBiZSBpbnRlcnBvbGF0ZWQgYmV0d2VlbiBzZWN0aW9uczogY29uc2VjdXRpdmUgc2VjdGlvbnNcbiAqIHNhbXBsZSBlbnRpcmVseSBkaWZmZXJlbnQgY2VsbHMsIHNvIHRoZXJlIGlzIG5vIGNvcnJlc3BvbmRlbmNlIHRvIGludGVycG9sYXRlXG4gKiBhbG9uZywgYW5kIGludmVudGluZyBwb3NpdGlvbnMgd291bGQgZmFicmljYXRlIG9ic2VydmF0aW9ucyBpbmRpc3Rpbmd1aXNoYWJsZVxuICogZnJvbSBtZWFzdXJlZCBvbmVzLiBBIGRlbnNpdHkgZmllbGQgaXMgYSBkaWZmZXJlbnQgb2JqZWN0OiBhIGNvbnRpbnVvdXNcbiAqIGVzdGltYXRlLCBsZWdpdGltYXRlbHkgZGVmaW5lZCBiZXR3ZWVuIHRoZSBwbGFuZXMgdGhhdCB3ZXJlIGltYWdlZCwgYW5kIGl0XG4gKiByZW5kZXJzIGFzIGEgdHJhbnNsdWNlbnQgY2xvdWQgcmF0aGVyIHRoYW4gYXMgZG90cywgc28gaXQgcmVhZHMgYXMgYW4gZXN0aW1hdGUuXG4gKlxuICogVHdvIHRoaW5ncyBtYWtlIHRoZSBlc3RpbWF0ZSBob25lc3QgcmF0aGVyIHRoYW4gZGVjb3JhdGl2ZSwgYW5kIGJvdGggYXJlXG4gKiBpbXBsZW1lbnRlZCBoZXJlOlxuICogIC0gdGhlIGtlcm5lbCBpcyAqKmFuaXNvdHJvcGljKiosIHdpdGggYSB6IGJhbmR3aWR0aCBhdCBsZWFzdCB0aGUgc2VjdGlvblxuICogICAgc3BhY2luZy4gQW4gaXNvdHJvcGljIGtlcm5lbCBsZWF2ZXMgb25lIGRpc2MgcGVyIHNlY3Rpb24g4oCUIGEgc2FtcGxpbmdcbiAqICAgIGFydGVmYWN0IHRoYXQgbG9va3MgbGlrZSBiaW9sb2d5O1xuICogIC0gdGhlIGZpZWxkIGlzICoqY292ZXJhZ2Utbm9ybWFsaXNlZCoqIGFsb25nIHogKE5hZGFyYXlh4oCTV2F0c29uKTogdW5zYW1wbGVkXG4gKiAgICBwbGFuZXMgd291bGQgb3RoZXJ3aXNlIHJlYWQgYXMgZ2VudWluZWx5IGVtcHR5IHRpc3N1ZSByYXRoZXIgdGhhbiBhcyB0aXNzdWVcbiAqICAgIG5vYm9keSBpbWFnZWQuXG4gKi9cblxuLyoqIFRoZSB2b3hlbCBsYXR0aWNlIGEgZGVuc2l0eSBmaWVsZCBpcyBlc3RpbWF0ZWQgb24uICovXG5leHBvcnQgaW50ZXJmYWNlIERlbnNpdHlHcmlkIHtcbiAgd2lkdGg6IG51bWJlcjtcbiAgaGVpZ2h0OiBudW1iZXI7XG4gIGRlcHRoOiBudW1iZXI7XG4gIC8qKiBXb3JsZCBzaXplIG9mIG9uZSB2b3hlbCBwZXIgYXhpcywgaW4gdGhlIG9ic2VydmF0aW9ucycgdW5pdHMuICovXG4gIHZveGVsU2l6ZTogW251bWJlciwgbnVtYmVyLCBudW1iZXJdO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIERlbnNpdHlPcHRpb25zIHtcbiAgLyoqIEtlcm5lbCDPgyBwZXIgYXhpcywgaW4gdGhlIG9ic2VydmF0aW9ucycgdW5pdHMuICovXG4gIHNpZ21hOiBbbnVtYmVyLCBudW1iZXIsIG51bWJlcl07XG4gIC8qKiBPYnNlcnZhdGlvbnMgdG8gaW5jbHVkZS4gRXZlcnkgb2JzZXJ2YXRpb24gd2hlbiBhYnNlbnQuICovXG4gIGluZGljZXM/OiBVaW50MzJBcnJheTtcbn1cblxuLyoqXG4gKiBUaGUgbGF0dGljZSB0byBlc3RpbWF0ZSBvbi5cbiAqXG4gKiBBIGRhdGFzZXQgd2l0aCBhIHJlZ2lzdGVyZWQgdm9sdW1lIGVzdGltYXRlcyBvbiB0aGF0IHZvbHVtZSdzIG93biBib3gsIHNvIHRoZVxuICogZmllbGQgbGFuZHMgZXhhY3RseSBvbiB0aGUgYW5hdG9teSBpdCB3aWxsIGJlIGRyYXduIGluc2lkZSDigJQgYHN0cmlkZWAgY29hcnNlbnNcbiAqIGl0IChkZW5zaXR5IGlzIHNtb290aCBieSBjb25zdHJ1Y3Rpb24sIGFuZCBhbiBlaWdodGggb2YgdGhlIHZveGVscyBpcyBhbiBlaWdodGhcbiAqIG9mIHRoZSB3b3JrKSB3aGlsZSBrZWVwaW5nIHRoZSBwaHlzaWNhbCBleHRlbnQgaWRlbnRpY2FsLCBzbyB0aGUgdHdvIHN0aWxsXG4gKiBhbGlnbi4gV2l0aG91dCBhIHZvbHVtZSB0aGUgYm94IGNvbWVzIGZyb20gdGhlIG9ic2VydmF0aW9ucycgb3duIGJvdW5kcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRlbnNpdHlHcmlkKFxuICBkYXRhc2V0OiBTcGF0aWFsRGF0YXNldCwgc3RyaWRlID0gMiwgdGFyZ2V0TG9uZ0F4aXMgPSAxMjgsIHpTdHJpZGUgPSBzdHJpZGUsXG4pOiBEZW5zaXR5R3JpZCB8IG51bGwge1xuICBjb25zdCB2b2x1bWUgPSBkYXRhc2V0LnZvbHVtZTtcbiAgaWYgKHZvbHVtZSkge1xuICAgIGNvbnN0IGNlbGxzID0gKG46IG51bWJlciwgYnk6IG51bWJlcikgPT4gTWF0aC5tYXgoMSwgTWF0aC5jZWlsKG4gLyBieSkpO1xuICAgIC8vIHogY2FuIGJlIGNvYXJzZW5lZCBsZXNzIHRoYW4geC95IOKAlCBhIGdlbmUgbWFwJ3Mgc2hlZXRzIG5lZWQgb25lIHBsYW5lIHBlclxuICAgIC8vIGltYWdlZCBzZWN0aW9uLCB3aGlsZSBpdHMgaW4tcGxhbmUgZGV0YWlsIGlzIHNtb290aCBieSBjb25zdHJ1Y3Rpb24uIFRoZVxuICAgIC8vIHBoeXNpY2FsIGV4dGVudCBpcyB1bmNoYW5nZWQgZWl0aGVyIHdheSwgc28gdGhlIGJveCBzdGlsbCBhbGlnbnMuXG4gICAgY29uc3QgW3csIGgsIGRdID0gW1xuICAgICAgY2VsbHModm9sdW1lLndpZHRoLCBzdHJpZGUpLCBjZWxscyh2b2x1bWUuaGVpZ2h0LCBzdHJpZGUpLCBjZWxscyh2b2x1bWUuZGVwdGgsIHpTdHJpZGUpLFxuICAgIF07XG4gICAgLy8gVm94ZWwgc2l6ZSBmcm9tIHRoZSBTUEFOLCBub3Qgc3RyaWRlIHggb3JpZ2luYWw6IGBjZWlsYCBjYW4gYWRkIGEgZnJhY3Rpb25cbiAgICAvLyBvZiBhIHZveGVsLCBhbmQgc2NhbGluZyB0aGUgb3JpZ2luYWwgc2l6ZSB3b3VsZCBwdXNoIHRoZSBmYXIgZWRnZSBwYXN0IHRoZVxuICAgIC8vIHJlZmVyZW5jZSB2b2x1bWUncy5cbiAgICByZXR1cm4ge1xuICAgICAgd2lkdGg6IHcsXG4gICAgICBoZWlnaHQ6IGgsXG4gICAgICBkZXB0aDogZCxcbiAgICAgIHZveGVsU2l6ZTogW1xuICAgICAgICAodm9sdW1lLndpZHRoICogdm9sdW1lLnZveGVsU2l6ZVswXSkgLyB3LFxuICAgICAgICAodm9sdW1lLmhlaWdodCAqIHZvbHVtZS52b3hlbFNpemVbMV0pIC8gaCxcbiAgICAgICAgKHZvbHVtZS5kZXB0aCAqIHZvbHVtZS52b3hlbFNpemVbMl0pIC8gZCxcbiAgICAgIF0sXG4gICAgfTtcbiAgfVxuXG4gIGNvbnN0IG9icyA9IGRhdGFzZXQub2JzZXJ2YXRpb25zO1xuICBpZiAoIW9icy56IHx8IG9icy5jb3VudCA9PT0gMCkgcmV0dXJuIG51bGw7XG4gIC8vIE1lYXN1cmVkIGZyb20gdGhlIGNvb3JkaW5hdGUgT1JJR0lOLCBub3QgZnJvbSB0aGUgY2xvdWQncyBvd24gbWluaW11bTogdGhlIGJveFxuICAvLyB0aGVuIGhhcyBpdHMgbmVhciBjb3JuZXIgYXQgdGhlIG9yaWdpbiBleGFjdGx5IGFzIGEgdm9sdW1lJ3MgZG9lcywgd2hpY2ggaXNcbiAgLy8gd2hhdCBsZXRzIGEgcmVuZGVyZXIgY2VudHJlIGVpdGhlciBvbmUgd2l0aCB0aGUgc2FtZSBoYWxmLWJveCBvZmZzZXQuIEEgY2xvdWRcbiAgLy8gc2l0dGluZyBmYXIgZnJvbSB0aGUgb3JpZ2luIGp1c3QgZ2V0cyBhIGNvYXJzZXIgZ3JpZCwgbm90IGEgd3Jvbmcgb25lLlxuICBjb25zdCBleHRlbnQgPSAodjogRmxvYXQzMkFycmF5KSA9PiB7XG4gICAgbGV0IGhpID0gMDtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IG9icy5jb3VudDsgaSsrKSBpZiAodltpXSA+IGhpKSBoaSA9IHZbaV07XG4gICAgcmV0dXJuIE1hdGgubWF4KGhpLCAxKTtcbiAgfTtcbiAgY29uc3QgW3N4LCBzeSwgc3pdID0gW2V4dGVudChvYnMueCksIGV4dGVudChvYnMueSksIGV4dGVudChvYnMueildO1xuICBjb25zdCB2b3hlbCA9IE1hdGgubWF4KHN4LCBzeSwgc3opIC8gdGFyZ2V0TG9uZ0F4aXM7XG4gIHJldHVybiB7XG4gICAgd2lkdGg6IE1hdGgubWF4KDEsIE1hdGguY2VpbChzeCAvIHZveGVsKSksXG4gICAgaGVpZ2h0OiBNYXRoLm1heCgxLCBNYXRoLmNlaWwoc3kgLyB2b3hlbCkpLFxuICAgIGRlcHRoOiBNYXRoLm1heCgxLCBNYXRoLmNlaWwoc3ogLyB2b3hlbCkpLFxuICAgIHZveGVsU2l6ZTogW3ZveGVsLCB2b3hlbCwgdm94ZWxdLFxuICB9O1xufVxuXG4vKipcbiAqIEluLXBsYWNlIHNlcGFyYWJsZSBHYXVzc2lhbiBhbG9uZyBvbmUgYXhpcyBvZiBhIHcgeCBoIHggZCBmaWVsZC5cbiAqXG4gKiBFeHBvcnRlZCBiZWNhdXNlIHRoZSBnZW5lLW1hcCB2b2x1bWUgZXN0aW1hdGVzIGEgZGlmZmVyZW50IHF1YW50aXR5IG9uIHRoZSBzYW1lXG4gKiBsYXR0aWNlIHdpdGggdGhlIHNhbWUga2VybmVsIChzZWUgYHNwYXRpYWwtZXhwcmVzc2lvbi50c2ApOiB0d28gY29waWVzIG9mIGFcbiAqIHNlcGFyYWJsZSBibHVyIHdvdWxkIGJlIHR3byBjaGFuY2VzIHRvIHNtb290aCB0aGUgZXN0aW1hdGUgYW5kIGl0cyBub3JtYWxpc2VyXG4gKiBkaWZmZXJlbnRseSwgd2hpY2ggaXMgdGhlIG9uZSB0aGluZyBhIE5hZGFyYXlhLVdhdHNvbiBmaWVsZCBjYW5ub3Qgc3Vydml2ZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGJsdXJWb2x1bWVBeGlzKFxuICBmaWVsZDogRmxvYXQzMkFycmF5LCB3OiBudW1iZXIsIGg6IG51bWJlciwgZDogbnVtYmVyLCBheGlzOiAwIHwgMSB8IDIsIHNpZ21hVm94OiBudW1iZXIsXG4pOiB2b2lkIHtcbiAgaWYgKCEoc2lnbWFWb3ggPiAwLjAxKSkgcmV0dXJuO1xuICBjb25zdCByYWRpdXMgPSBNYXRoLm1heCgxLCBNYXRoLmNlaWwoc2lnbWFWb3ggKiAzKSk7XG4gIGNvbnN0IGtlcm5lbCA9IG5ldyBGbG9hdDMyQXJyYXkocmFkaXVzICogMiArIDEpO1xuICBsZXQgc3VtID0gMDtcbiAgZm9yIChsZXQgaSA9IC1yYWRpdXM7IGkgPD0gcmFkaXVzOyBpKyspIHtcbiAgICBjb25zdCB2ID0gTWF0aC5leHAoLShpICogaSkgLyAoMiAqIHNpZ21hVm94ICogc2lnbWFWb3gpKTtcbiAgICBrZXJuZWxbaSArIHJhZGl1c10gPSB2O1xuICAgIHN1bSArPSB2O1xuICB9XG4gIGZvciAobGV0IGkgPSAwOyBpIDwga2VybmVsLmxlbmd0aDsgaSsrKSBrZXJuZWxbaV0gLz0gc3VtO1xuXG4gIGNvbnN0IGxlbiA9IGF4aXMgPT09IDAgPyB3IDogYXhpcyA9PT0gMSA/IGggOiBkO1xuICBjb25zdCBzdHJpZGUgPSBheGlzID09PSAwID8gMSA6IGF4aXMgPT09IDEgPyB3IDogdyAqIGg7XG4gIGNvbnN0IGxpbmVzID0gYXhpcyA9PT0gMCA/IGggKiBkIDogYXhpcyA9PT0gMSA/IHcgKiBkIDogdyAqIGg7XG4gIGNvbnN0IGxpbmUgPSBuZXcgRmxvYXQzMkFycmF5KGxlbik7XG4gIGZvciAobGV0IGwgPSAwOyBsIDwgbGluZXM7IGwrKykge1xuICAgIC8vIE9mZnNldCBvZiB0aGlzIGxpbmUncyBmaXJzdCBlbGVtZW50OiB0aGUgdHdvIGF4ZXMgdGhhdCBhcmUgbm90IGJlaW5nXG4gICAgLy8gYmx1cnJlZCwgbGFpZCBiYWNrIG91dCBpbiB4LWZhc3Rlc3Qgb3JkZXIuXG4gICAgbGV0IGJhc2U6IG51bWJlcjtcbiAgICBpZiAoYXhpcyA9PT0gMCkgYmFzZSA9IChsICUgaCkgKiB3ICsgTWF0aC5mbG9vcihsIC8gaCkgKiB3ICogaDtcbiAgICBlbHNlIGlmIChheGlzID09PSAxKSBiYXNlID0gKGwgJSB3KSArIE1hdGguZmxvb3IobCAvIHcpICogdyAqIGg7XG4gICAgZWxzZSBiYXNlID0gbDtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGxlbjsgaSsrKSBsaW5lW2ldID0gZmllbGRbYmFzZSArIGkgKiBzdHJpZGVdO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgIGxldCBhY2MgPSAwO1xuICAgICAgZm9yIChsZXQgayA9IC1yYWRpdXM7IGsgPD0gcmFkaXVzOyBrKyspIHtcbiAgICAgICAgLy8gQ2xhbXAgYXQgdGhlIGVkZ2VzOiB6ZXJvLXBhZGRpbmcgd291bGQgZGFya2VuIHRoZSBzcGVjaW1lbidzIG93blxuICAgICAgICAvLyBib3VuZGFyeSwgd2hpY2ggaXMgZXhhY3RseSB3aGVyZSBhIGNsdXN0ZXIgb2Z0ZW4gc2l0cy5cbiAgICAgICAgY29uc3QgaiA9IE1hdGgubWluKGxlbiAtIDEsIE1hdGgubWF4KDAsIGkgKyBrKSk7XG4gICAgICAgIGFjYyArPSBsaW5lW2pdICoga2VybmVsW2sgKyByYWRpdXNdO1xuICAgICAgfVxuICAgICAgZmllbGRbYmFzZSArIGkgKiBzdHJpZGVdID0gYWNjO1xuICAgIH1cbiAgfVxufVxuXG4vKipcbiAqIFdoaWNoIHogcGxhbmVzIG9mIHRoZSBncmlkIGNvbnRhaW4gYW55IG9ic2VydmF0aW9uIGF0IGFsbCDigJQgdGhlIHNhbXBsZWRcbiAqIHN1cHBvcnQsIHRha2VuIGZyb20gdGhlIFdIT0xFIGRhdGFzZXQgcmF0aGVyIHRoYW4gZnJvbSB0aGUgc3Vic2V0IGJlaW5nXG4gKiByYXN0ZXJpc2VkLiBBIHJhcmUgY2x1c3RlciBpcyBzcGFyc2UgYmVjYXVzZSBpdCBpcyByYXJlOyB0aGUgZ2FwcyBiZXR3ZWVuXG4gKiBzZWN0aW9ucyBhcmUgZW1wdHkgYmVjYXVzZSBub2JvZHkgaW1hZ2VkIHRoZXJlLCBhbmQgb25seSB0aGUgc2Vjb25kIGlzIGFcbiAqIHNhbXBsaW5nIGFydGVmYWN0IHRvIGNvcnJlY3QgZm9yLlxuICovXG5mdW5jdGlvbiBzYW1wbGVkUGxhbmVzKFxuICBvYnM6IFNwYXRpYWxPYnNlcnZhdGlvbnMsIGdyaWQ6IERlbnNpdHlHcmlkLFxuKTogeyBjb3ZlcjogRmxvYXQzMkFycmF5OyBmaXJzdDogbnVtYmVyOyBsYXN0OiBudW1iZXIgfSB7XG4gIGNvbnN0IGNvdmVyID0gbmV3IEZsb2F0MzJBcnJheShncmlkLmRlcHRoKTtcbiAgY29uc3QgeiA9IG9icy56O1xuICBpZiAoIXopIHJldHVybiB7IGNvdmVyOiBjb3Zlci5maWxsKDEpLCBmaXJzdDogMCwgbGFzdDogZ3JpZC5kZXB0aCAtIDEgfTtcbiAgbGV0IGZpcnN0ID0gZ3JpZC5kZXB0aDtcbiAgbGV0IGxhc3QgPSAtMTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBvYnMuY291bnQ7IGkrKykge1xuICAgIGNvbnN0IGsgPSBNYXRoLmZsb29yKHpbaV0gLyBncmlkLnZveGVsU2l6ZVsyXSk7XG4gICAgaWYgKGsgPCAwIHx8IGsgPj0gZ3JpZC5kZXB0aCkgY29udGludWU7XG4gICAgY292ZXJba10gPSAxO1xuICAgIGlmIChrIDwgZmlyc3QpIGZpcnN0ID0gaztcbiAgICBpZiAoayA+IGxhc3QpIGxhc3QgPSBrO1xuICB9XG4gIHJldHVybiB7IGNvdmVyLCBmaXJzdCwgbGFzdCB9O1xufVxuXG4vKiogQmx1ciBhIDFEIHByb2ZpbGUgd2l0aCB0aGUgc2FtZSBrZXJuZWwgYGJsdXJWb2x1bWVBeGlzYCB1c2VzLCBzbyB0aGUgY29ycmVjdGlvbiBhbmRcbiAqICB0aGUgZmllbGQgYXJlIHNtb290aGVkIGlkZW50aWNhbGx5LiAqL1xuZnVuY3Rpb24gYmx1cjFkKHByb2ZpbGU6IEZsb2F0MzJBcnJheSwgc2lnbWFWb3g6IG51bWJlcik6IEZsb2F0MzJBcnJheSB7XG4gIGNvbnN0IG91dCA9IHByb2ZpbGUuc2xpY2UoKTtcbiAgYmx1clZvbHVtZUF4aXMob3V0LCAxLCAxLCBvdXQubGVuZ3RoLCAyLCBzaWdtYVZveCk7XG4gIHJldHVybiBvdXQ7XG59XG5cbi8qKlxuICogUmFzdGVyaXNlIG9ic2VydmF0aW9ucyBpbnRvIGEgdWludDggZGVuc2l0eSBmaWVsZCBvbiBgZ3JpZGAuXG4gKlxuICogUmV0dXJuZWQgYXMgdWludDggc2NhbGVkIHRvIGl0cyBvd24gbWF4aW11bTogYSBgVm9sdW1lTGF5ZXJgIHJlbmRlcnMgMC4uMjU1LCBhbmRcbiAqIHRoZSBhYnNvbHV0ZSBjZWxsIGNvdW50IHBlciB2b3hlbCBpcyBub3QgdGhlIHJlYWRhYmxlIHF1YW50aXR5IGFueXdheSDigJQgd2hlcmVcbiAqIHRoZSBjbHVzdGVyIGlzIGRlbnNlIFJFTEFUSVZFIHRvIGl0c2VsZiBpcy4gTnVsbCB3aGVuIG5vdGhpbmcgbGFuZHMgb24gdGhlIGdyaWQsXG4gKiBzbyB0aGUgY2FsbGVyIGRyYXdzIG5vIGxheWVyIHJhdGhlciB0aGFuIGFuIGVtcHR5IGJveC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJhc3Rlcml6ZURlbnNpdHkoXG4gIG9iczogU3BhdGlhbE9ic2VydmF0aW9ucywgZ3JpZDogRGVuc2l0eUdyaWQsIG9wdHM6IERlbnNpdHlPcHRpb25zLFxuKTogVWludDhBcnJheSB8IG51bGwge1xuICBjb25zdCB7IHdpZHRoOiB3LCBoZWlnaHQ6IGgsIGRlcHRoOiBkLCB2b3hlbFNpemUgfSA9IGdyaWQ7XG4gIGNvbnN0IGZpZWxkID0gbmV3IEZsb2F0MzJBcnJheSh3ICogaCAqIGQpO1xuICBjb25zdCBpZHggPSBvcHRzLmluZGljZXM7XG4gIGNvbnN0IG4gPSBpZHggPyBpZHgubGVuZ3RoIDogb2JzLmNvdW50O1xuICBjb25zdCB6ID0gb2JzLno7XG4gIGxldCBwbGFjZWQgPSAwO1xuICBmb3IgKGxldCBrID0gMDsgayA8IG47IGsrKykge1xuICAgIGNvbnN0IGkgPSBpZHggPyBpZHhba10gOiBrO1xuICAgIGNvbnN0IHZ4ID0gTWF0aC5mbG9vcihvYnMueFtpXSAvIHZveGVsU2l6ZVswXSk7XG4gICAgY29uc3QgdnkgPSBNYXRoLmZsb29yKG9icy55W2ldIC8gdm94ZWxTaXplWzFdKTtcbiAgICBjb25zdCB2eiA9IHogPyBNYXRoLmZsb29yKHpbaV0gLyB2b3hlbFNpemVbMl0pIDogMDtcbiAgICBpZiAodnggPCAwIHx8IHZ4ID49IHcgfHwgdnkgPCAwIHx8IHZ5ID49IGggfHwgdnogPCAwIHx8IHZ6ID49IGQpIGNvbnRpbnVlO1xuICAgIGZpZWxkWyh2eiAqIGggKyB2eSkgKiB3ICsgdnhdICs9IDE7XG4gICAgcGxhY2VkKys7XG4gIH1cbiAgaWYgKCFwbGFjZWQpIHJldHVybiBudWxsO1xuXG4gIGNvbnN0IHNpZ21hVm94OiBbbnVtYmVyLCBudW1iZXIsIG51bWJlcl0gPSBbXG4gICAgb3B0cy5zaWdtYVswXSAvIHZveGVsU2l6ZVswXSwgb3B0cy5zaWdtYVsxXSAvIHZveGVsU2l6ZVsxXSwgb3B0cy5zaWdtYVsyXSAvIHZveGVsU2l6ZVsyXSxcbiAgXTtcbiAgYmx1clZvbHVtZUF4aXMoZmllbGQsIHcsIGgsIGQsIDAsIHNpZ21hVm94WzBdKTtcbiAgYmx1clZvbHVtZUF4aXMoZmllbGQsIHcsIGgsIGQsIDEsIHNpZ21hVm94WzFdKTtcbiAgYmx1clZvbHVtZUF4aXMoZmllbGQsIHcsIGgsIGQsIDIsIHNpZ21hVm94WzJdKTtcblxuICAvLyBDb3ZlcmFnZSBjb3JyZWN0aW9uLCBwZXIgcGxhbmU6IHRoZSBzYW1lIGtlcm5lbCBvdmVyIHRoZSBzYW1wbGVkIHN1cHBvcnQgc2F5c1xuICAvLyBob3cgbXVjaCBvZiBlYWNoIHBsYW5lJ3MgbmVpZ2hib3VyaG9vZCB3YXMgYWN0dWFsbHkgaW1hZ2VkLlxuICBjb25zdCB7IGNvdmVyLCBmaXJzdCwgbGFzdCB9ID0gc2FtcGxlZFBsYW5lcyhvYnMsIGdyaWQpO1xuICBjb25zdCBzbW9vdGhlZENvdmVyID0gYmx1cjFkKGNvdmVyLCBzaWdtYVZveFsyXSk7XG4gIGxldCBtYXggPSAwO1xuICBjb25zdCBwbGFuZSA9IHcgKiBoO1xuICBmb3IgKGxldCBrID0gMDsgayA8IGQ7IGsrKykge1xuICAgIC8vIE9VVFNJREUgdGhlIHNhbXBsZWQgcmFuZ2UgdGhlcmUgaXMgbm90aGluZyB0byBpbnRlcnBvbGF0ZSBiZXR3ZWVuLCBvbmx5XG4gICAgLy8gb25lIHNpZGUgdG8gZXh0cmFwb2xhdGUgZnJvbSDigJQgc28gdGhlIGZpZWxkIGlzIHplcm9lZCByYXRoZXIgdGhhbiBzY2FsZWQuXG4gICAgLy8gVGhlIGJsdXIgbGVhdmVzIGEgcG9zaXRpdmUgdGFpbCBwYXN0IHRoZSBmaXJzdCBhbmQgbGFzdCBpbWFnZWQgcGxhbmVzLCBhbmRcbiAgICAvLyBrZWVwaW5nIGl0IHdvdWxkIHB1dCBlc3RpbWF0ZWQgY2VsbHMgaW4gZnJvbnQgb2YgdGhlIHNwZWNpbWVuJ3MgZmlyc3RcbiAgICAvLyBzZWN0aW9uIGFuZCBiZWhpbmQgaXRzIGxhc3QsIHdoaWNoIGlzIGV4YWN0bHkgd2hhdCBcInNtb290aGVkIEJFVFdFRU4gdGhlXG4gICAgLy8gaW1hZ2VkIHNlY3Rpb25zXCIgcHJvbWlzZXMgbm90IHRvIGRvLlxuICAgIGlmIChrIDwgZmlyc3QgfHwgayA+IGxhc3QpIHtcbiAgICAgIGZpZWxkLmZpbGwoMCwgayAqIHBsYW5lLCAoayArIDEpICogcGxhbmUpO1xuICAgICAgY29udGludWU7XG4gICAgfVxuICAgIC8vIEluc2lkZSBpdCwgdGhlIGNvcnJlY3Rpb24gQlJJREdFUyBnYXBzLiBDYXBwaW5nIHRoZSBhbXBsaWZpY2F0aW9uIGF0IDJ4XG4gICAgLy8gKGNvdmVyYWdlIGZsb29yZWQgYXQgMC41KSBsaWZ0cyBhIHBsYW5lIHNpdHRpbmcgYmV0d2VlbiB0d28gaW1hZ2VkIHNlY3Rpb25zXG4gICAgLy8g4oCUIHdoZXJlIGEgYmFuZHdpZHRoIHNwYW5uaW5nIG9uZSBnYXAgbGVhdmVzIGNvdmVyYWdlIGFyb3VuZCBhIGhhbGYg4oCUXG4gICAgLy8gd2l0aG91dCBpbmZsYXRpbmcgYSB0cmFjZSBvZiBzbW9vdGhpbmcgbGVha2FnZSBpbnRvIGEgc2lnbmFsLlxuICAgIGNvbnN0IHNjYWxlID0gMSAvIE1hdGgubWF4KHNtb290aGVkQ292ZXJba10sIDAuNSk7XG4gICAgZm9yIChsZXQgaSA9IGsgKiBwbGFuZTsgaSA8IChrICsgMSkgKiBwbGFuZTsgaSsrKSB7XG4gICAgICBjb25zdCB2ID0gZmllbGRbaV0gKiBzY2FsZTtcbiAgICAgIGZpZWxkW2ldID0gdjtcbiAgICAgIGlmICh2ID4gbWF4KSBtYXggPSB2O1xuICAgIH1cbiAgfVxuICBpZiAoIShtYXggPiAwKSkgcmV0dXJuIG51bGw7XG5cbiAgY29uc3Qgb3V0ID0gbmV3IFVpbnQ4QXJyYXkoZmllbGQubGVuZ3RoKTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBmaWVsZC5sZW5ndGg7IGkrKykgb3V0W2ldID0gTWF0aC5yb3VuZCgoZmllbGRbaV0gLyBtYXgpICogMjU1KTtcbiAgcmV0dXJuIG91dDtcbn1cblxuLyoqXG4gKiBEZWZhdWx0IGtlcm5lbCBmb3IgYSBncmlkOiBpbi1wbGFuZSB0aWdodCwgYWxvbmcgeiBhdCBsZWFzdCB0aGUgc2VjdGlvblxuICogc3BhY2luZy5cbiAqXG4gKiBUaGUgeiB0ZXJtIGlzIHRoZSBsb2FkLWJlYXJpbmcgb25lIOKAlCAxLjUgdm94ZWxzIG9mIGEgZ3JpZCB3aG9zZSBkZXB0aCB2b3hlbCBJU1xuICogdGhlIHNlY3Rpb24gc3BhY2luZyBwdXRzIM+DIGp1c3QgYWJvdmUgb25lIHNlY3Rpb24gZ2FwLCB3aGljaCBpcyB0aGUgc21hbGxlc3RcbiAqIGJhbmR3aWR0aCB0aGF0IGNhbiBicmlkZ2Ugb25lIHdpdGhvdXQgbGVhdmluZyBhIGRpc2MgcGVyIHBsYW5lLiBgc21vb3RoaW5nYFxuICogc2NhbGVzIGFsbCB0aHJlZSBmb3IgdGhlIHVzZXIgY29udHJvbC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRlZmF1bHRTaWdtYShncmlkOiBEZW5zaXR5R3JpZCwgc21vb3RoaW5nID0gMSk6IFtudW1iZXIsIG51bWJlciwgbnVtYmVyXSB7XG4gIHJldHVybiBbXG4gICAgZ3JpZC52b3hlbFNpemVbMF0gKiAxLjUgKiBzbW9vdGhpbmcsXG4gICAgZ3JpZC52b3hlbFNpemVbMV0gKiAxLjUgKiBzbW9vdGhpbmcsXG4gICAgZ3JpZC52b3hlbFNpemVbMl0gKiAxLjUgKiBzbW9vdGhpbmcsXG4gIF07XG59XG4iXX0=