@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.
- package/README.md +215 -11
- package/esm2022/index.mjs +20 -1
- package/esm2022/lib/contracts/display-types.mjs +20 -2
- package/esm2022/lib/contracts/image.contract.mjs +1 -1
- package/esm2022/lib/contracts/plot-type.mjs +36 -1
- package/esm2022/lib/contracts/ports/spatial-data.port.mjs +3 -0
- package/esm2022/lib/contracts/spatial-dataset.contract.mjs +45 -0
- package/esm2022/lib/contracts/visualizer.contract.mjs +1 -1
- package/esm2022/lib/implementations/napari-js/napari-axes-labels.mjs +10 -19
- package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +71 -7
- package/esm2022/lib/implementations/napari-js/napari-scale-bar.mjs +1 -1
- package/esm2022/lib/implementations/napari-js/napari-spatial-tooltip.mjs +96 -0
- package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +1934 -55
- package/esm2022/lib/implementations/napari-js/napari-zoom.mjs +18 -0
- package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +5 -3
- package/esm2022/lib/implementations/osd/osd-region-overlay.mjs +3 -4
- package/esm2022/lib/implementations/osd/osd-zoom.mjs +20 -0
- package/esm2022/lib/implementations/plotly/omics-trace-builders.mjs +555 -0
- package/esm2022/lib/implementations/plotly/plotly.service.mjs +13 -7
- package/esm2022/lib/implementations/spatial-data-http/spatial-data-http.service.mjs +304 -0
- package/esm2022/lib/implementations/spatial-data-http/spatial-wire.mjs +179 -0
- package/esm2022/lib/routing-visualizer.service.mjs +223 -12
- package/esm2022/lib/spatial/embedding-compute.mjs +142 -0
- package/esm2022/lib/spatial/gene-search.mjs +77 -0
- package/esm2022/lib/spatial/spatial-density.mjs +224 -0
- package/esm2022/lib/spatial/spatial-encoding.mjs +232 -0
- package/esm2022/lib/spatial/spatial-expression.mjs +257 -0
- package/esm2022/lib/spatial/spatial-framing.mjs +60 -0
- package/esm2022/lib/spatial/spatial-heatmap.mjs +185 -0
- package/esm2022/lib/spatial/spatial-hover.mjs +79 -0
- package/esm2022/lib/spatial/spatial-sections.mjs +96 -0
- package/esm2022/lib/spatial/spatial-selection.mjs +264 -0
- package/esm2022/lib/spatial/spatial-volume-image.mjs +163 -0
- package/esm2022/lib/spatial/tsne-worker.mjs +10 -0
- package/esm2022/lib/spatial/world-grid.mjs +77 -0
- package/esm2022/lib/spatial-controls/spatial-charts/spatial-charts.component.mjs +1278 -0
- package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +621 -0
- package/esm2022/lib/store/spatial-selection.service.mjs +47 -0
- package/esm2022/lib/store/visualizer-store.service.mjs +23 -1
- package/esm2022/lib/toolbar/segmentation/cellpose-segmenter.service.mjs +2 -2
- package/esm2022/lib/toolbar/segmentation/sam-model-registry.mjs +9 -9
- package/esm2022/lib/toolbar/toolbar.component.mjs +30 -7
- package/esm2022/lib/visualization.module.mjs +23 -5
- package/esm2022/lib/visualizer.component.mjs +283 -22
- 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
- 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
- package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs +12 -0
- package/fesm2022/jax-data-science-sci-image-visualizer-tsne-worker-B_nXDGf0.mjs.map +1 -0
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +15282 -7829
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/fesm2022/tsne.worker.js +19091 -0
- package/index.d.ts +15 -0
- package/lib/contracts/display-types.d.ts +131 -0
- package/lib/contracts/image.contract.d.ts +7 -0
- package/lib/contracts/plot-type.d.ts +42 -2
- package/lib/contracts/ports/spatial-data.port.d.ts +90 -0
- package/lib/contracts/spatial-dataset.contract.d.ts +281 -0
- package/lib/contracts/visualizer.contract.d.ts +99 -1
- package/lib/implementations/napari-js/napari-axes-labels.d.ts +9 -7
- package/lib/implementations/napari-js/napari-region-overlay.d.ts +38 -4
- package/lib/implementations/napari-js/napari-scale-bar.d.ts +7 -3
- package/lib/implementations/napari-js/napari-spatial-tooltip.d.ts +32 -0
- package/lib/implementations/napari-js/napari-visualizer.service.d.ts +497 -16
- package/lib/implementations/napari-js/napari-zoom.d.ts +15 -0
- package/lib/implementations/osd/osd-zoom.d.ts +19 -0
- package/lib/implementations/plotly/omics-trace-builders.d.ts +193 -0
- package/lib/implementations/plotly/plotly.service.d.ts +9 -0
- package/lib/implementations/spatial-data-http/spatial-data-http.service.d.ts +123 -0
- package/lib/implementations/spatial-data-http/spatial-wire.d.ts +115 -0
- package/lib/routing-visualizer.service.d.ts +45 -6
- package/lib/spatial/embedding-compute.d.ts +73 -0
- package/lib/spatial/gene-search.d.ts +48 -0
- package/lib/spatial/spatial-density.d.ts +73 -0
- package/lib/spatial/spatial-encoding.d.ts +155 -0
- package/lib/spatial/spatial-expression.d.ts +144 -0
- package/lib/spatial/spatial-framing.d.ts +37 -0
- package/lib/spatial/spatial-heatmap.d.ts +111 -0
- package/lib/spatial/spatial-hover.d.ts +49 -0
- package/lib/spatial/spatial-sections.d.ts +59 -0
- package/lib/spatial/spatial-selection.d.ts +113 -0
- package/lib/spatial/spatial-volume-image.d.ts +67 -0
- package/lib/spatial/tsne-worker.d.ts +7 -0
- package/lib/spatial/world-grid.d.ts +58 -0
- package/lib/spatial-controls/spatial-charts/spatial-charts.component.d.ts +439 -0
- package/lib/spatial-controls/spatial-controls.component.d.ts +257 -0
- package/lib/store/spatial-selection.service.d.ts +27 -0
- package/lib/store/visualizer-store.service.d.ts +13 -0
- package/lib/toolbar/toolbar.component.d.ts +19 -3
- package/lib/visualization.module.d.ts +28 -24
- package/lib/visualizer.component.d.ts +73 -2
- package/package.json +7 -3
- package/src/lib/assets/spatial-omics-3d.svg +31 -0
- 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=
|