@jax-data-science/sci-image-visualizer 0.3.3 → 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 +208 -4
- 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/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 +15769 -8316
- 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 +6 -2
- package/src/lib/assets/spatial-omics-3d.svg +31 -0
- package/src/lib/assets/spatial-omics.svg +27 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/** An empty selection over `count` observations. */
|
|
2
|
+
export function emptySelection(count = 0) {
|
|
3
|
+
return { mask: new Uint8Array(count), count: 0 };
|
|
4
|
+
}
|
|
5
|
+
/** Number of set entries in a mask. */
|
|
6
|
+
export function countMask(mask) {
|
|
7
|
+
let n = 0;
|
|
8
|
+
for (let i = 0; i < mask.length; i++)
|
|
9
|
+
if (mask[i])
|
|
10
|
+
n++;
|
|
11
|
+
return n;
|
|
12
|
+
}
|
|
13
|
+
/** Selected indices, for export or a downstream query. */
|
|
14
|
+
export function maskToIndices(mask) {
|
|
15
|
+
const out = new Uint32Array(countMask(mask));
|
|
16
|
+
let at = 0;
|
|
17
|
+
for (let i = 0; i < mask.length; i++)
|
|
18
|
+
if (mask[i])
|
|
19
|
+
out[at++] = i;
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Renderer-facing inverse: which observations to MUTE. With nothing selected
|
|
24
|
+
* nothing is muted (the whole tissue reads normally); with a selection, every
|
|
25
|
+
* unselected observation is muted — the CosMx highlight-vs-mute rule.
|
|
26
|
+
*/
|
|
27
|
+
export function mutedFromSelection(selection) {
|
|
28
|
+
if (selection.count === 0)
|
|
29
|
+
return null;
|
|
30
|
+
const muted = new Uint8Array(selection.mask.length);
|
|
31
|
+
for (let i = 0; i < muted.length; i++)
|
|
32
|
+
muted[i] = selection.mask[i] ? 0 : 1;
|
|
33
|
+
return muted;
|
|
34
|
+
}
|
|
35
|
+
/** Even-odd ray cast: is (px, py) inside the closed ring xs/ys? */
|
|
36
|
+
export function pointInRing(xs, ys, px, py) {
|
|
37
|
+
let inside = false;
|
|
38
|
+
const n = xs.length;
|
|
39
|
+
for (let i = 0, j = n - 1; i < n; j = i++) {
|
|
40
|
+
const yi = ys[i];
|
|
41
|
+
const yj = ys[j];
|
|
42
|
+
// Half-open comparison so a vertex on the ray is counted once, not twice.
|
|
43
|
+
if ((yi > py) !== (yj > py)) {
|
|
44
|
+
const t = (py - yi) / (yj - yi);
|
|
45
|
+
if (px < xs[i] + t * (xs[j] - xs[i]))
|
|
46
|
+
inside = !inside;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return inside;
|
|
50
|
+
}
|
|
51
|
+
function ringBounds(xs, ys) {
|
|
52
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
53
|
+
for (let i = 0; i < xs.length; i++) {
|
|
54
|
+
if (xs[i] < minX)
|
|
55
|
+
minX = xs[i];
|
|
56
|
+
if (xs[i] > maxX)
|
|
57
|
+
maxX = xs[i];
|
|
58
|
+
if (ys[i] < minY)
|
|
59
|
+
minY = ys[i];
|
|
60
|
+
if (ys[i] > maxY)
|
|
61
|
+
maxY = ys[i];
|
|
62
|
+
}
|
|
63
|
+
return { minX, minY, maxX, maxY };
|
|
64
|
+
}
|
|
65
|
+
function inBounds(b, px, py) {
|
|
66
|
+
return px >= b.minX && px <= b.maxX && py >= b.minY && py <= b.maxY;
|
|
67
|
+
}
|
|
68
|
+
/** Polygon-with-holes: inside the exterior and outside every hole. */
|
|
69
|
+
function polygonShape(xs, ys, holes) {
|
|
70
|
+
if (xs.length < 3)
|
|
71
|
+
return null;
|
|
72
|
+
const bounds = ringBounds(xs, ys);
|
|
73
|
+
return {
|
|
74
|
+
bounds,
|
|
75
|
+
hit: (px, py) => {
|
|
76
|
+
if (!pointInRing(xs, ys, px, py))
|
|
77
|
+
return false;
|
|
78
|
+
// The region model is explicit: a point inside the exterior AND inside a
|
|
79
|
+
// hole is OUTSIDE the region.
|
|
80
|
+
for (const ring of holes ?? []) {
|
|
81
|
+
if (ring.length >= 3
|
|
82
|
+
&& pointInRing(ring.map((p) => p[0]), ring.map((p) => p[1]), px, py)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Flatten a region to testable shapes. Returns an empty array for anything that
|
|
92
|
+
* encloses no area — an open polyline, an intensity-profile line, a degenerate
|
|
93
|
+
* rectangle — so those never contribute to a selection.
|
|
94
|
+
*/
|
|
95
|
+
export function regionShapes(region) {
|
|
96
|
+
// Profile lines belong to the intensity tool, not the annotation set.
|
|
97
|
+
if (region?.kind === 'profile')
|
|
98
|
+
return [];
|
|
99
|
+
const b = region?.bounds;
|
|
100
|
+
if (!b)
|
|
101
|
+
return [];
|
|
102
|
+
if (Array.isArray(b.polygons)) {
|
|
103
|
+
return b.polygons.flatMap((p) => p.closed === false ? [] : (polygonShape(p.xpoints ?? [], p.ypoints ?? [], p.holes) ?? []));
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(b.xpoints)) {
|
|
106
|
+
if (b.closed === false)
|
|
107
|
+
return []; // open polyline encloses nothing
|
|
108
|
+
const shape = polygonShape(b.xpoints, b.ypoints ?? [], b.holes);
|
|
109
|
+
return shape ? [shape] : [];
|
|
110
|
+
}
|
|
111
|
+
if (typeof b.width === 'number' && typeof b.height === 'number') {
|
|
112
|
+
const x = b.x ?? 0;
|
|
113
|
+
const y = b.y ?? 0;
|
|
114
|
+
// Normalise a rectangle dragged right-to-left / bottom-to-top.
|
|
115
|
+
const minX = Math.min(x, x + b.width);
|
|
116
|
+
const maxX = Math.max(x, x + b.width);
|
|
117
|
+
const minY = Math.min(y, y + b.height);
|
|
118
|
+
const maxY = Math.max(y, y + b.height);
|
|
119
|
+
if (maxX === minX || maxY === minY)
|
|
120
|
+
return [];
|
|
121
|
+
const bounds = { minX, minY, maxX, maxY };
|
|
122
|
+
return [{ bounds, hit: (px, py) => inBounds(bounds, px, py) }];
|
|
123
|
+
}
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Observations inside ANY of `regions` (union). Coordinates are transformed by
|
|
128
|
+
* `imageRef` first, so the test happens in the same world space the regions were
|
|
129
|
+
* drawn in.
|
|
130
|
+
*
|
|
131
|
+
* `candidates`, when given, restricts the test to those observation indices — how
|
|
132
|
+
* a 2D view of a 3D dataset keeps a drawn region meaning what it looks like it
|
|
133
|
+
* means: the shape was drawn over ONE section, so it selects that section's cells
|
|
134
|
+
* rather than the whole depth of the specimen behind them. The returned mask stays
|
|
135
|
+
* indexed by observation, so every consumer (charts, legend, the 3D cloud) reads it
|
|
136
|
+
* the same way.
|
|
137
|
+
*/
|
|
138
|
+
/**
|
|
139
|
+
* A selection from an explicit list of observation indices.
|
|
140
|
+
*
|
|
141
|
+
* What a lasso in a linked plot produces: the plot knows which points the user drew
|
|
142
|
+
* around, and the map needs that as a mask over ALL observations. Out-of-range and
|
|
143
|
+
* duplicate indices are ignored rather than rejected — a plot may hand back the same
|
|
144
|
+
* point twice, and a stale index from a plot drawn before a dataset change must not
|
|
145
|
+
* throw in the middle of a selection gesture.
|
|
146
|
+
*/
|
|
147
|
+
export function selectByIndices(indices, observationCount) {
|
|
148
|
+
const mask = new Uint8Array(observationCount);
|
|
149
|
+
let count = 0;
|
|
150
|
+
for (const i of indices) {
|
|
151
|
+
if (!Number.isInteger(i) || i < 0 || i >= observationCount)
|
|
152
|
+
continue;
|
|
153
|
+
if (mask[i])
|
|
154
|
+
continue;
|
|
155
|
+
mask[i] = 1;
|
|
156
|
+
count++;
|
|
157
|
+
}
|
|
158
|
+
return { mask, count };
|
|
159
|
+
}
|
|
160
|
+
export function selectInRegions(observations, imageRef, regions, candidates) {
|
|
161
|
+
const n = observations.count;
|
|
162
|
+
const mask = new Uint8Array(n);
|
|
163
|
+
const shapes = regions.flatMap((r) => regionShapes(r));
|
|
164
|
+
if (shapes.length === 0)
|
|
165
|
+
return { mask, count: 0 };
|
|
166
|
+
const [sx, sy] = imageRef?.scale ?? [1, 1];
|
|
167
|
+
const [tx, ty] = imageRef?.translate ?? [0, 0];
|
|
168
|
+
let count = 0;
|
|
169
|
+
const total = candidates ? candidates.length : n;
|
|
170
|
+
for (let k = 0; k < total; k++) {
|
|
171
|
+
const i = candidates ? candidates[k] : k;
|
|
172
|
+
const px = observations.x[i] * sx + tx;
|
|
173
|
+
const py = observations.y[i] * sy + ty;
|
|
174
|
+
for (const shape of shapes) {
|
|
175
|
+
// Bounds first: a ray cast per observation per region would dominate at
|
|
176
|
+
// 10^5 observations, and most points miss most regions.
|
|
177
|
+
if (!inBounds(shape.bounds, px, py))
|
|
178
|
+
continue;
|
|
179
|
+
if (shape.hit(px, py)) {
|
|
180
|
+
mask[i] = 1;
|
|
181
|
+
count++;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { mask, count };
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Select observations whose SCREEN projection falls inside the drawn regions.
|
|
190
|
+
*
|
|
191
|
+
* This is how a region selection works in the 3D cloud. There is no data-space
|
|
192
|
+
* affine to map a drawn shape through, because the shape was drawn on a 2D screen
|
|
193
|
+
* against a perspective camera — so the observations come the other way, already
|
|
194
|
+
* projected to canvas pixels by the renderer (which owns the camera), and the
|
|
195
|
+
* regions are tested in that same screen space.
|
|
196
|
+
*
|
|
197
|
+
* The consequence is worth being explicit about: a screen-space lasso selects
|
|
198
|
+
* through the WHOLE DEPTH of the cloud, like a cookie cutter, not a slab at some
|
|
199
|
+
* chosen z. That is inherent to drawing on a flat screen, and it is what napari
|
|
200
|
+
* and every other orbit-camera point picker do. Orbit and draw again to cut from
|
|
201
|
+
* another angle.
|
|
202
|
+
*
|
|
203
|
+
* `screen` is `[x0, y0, x1, y1, …]` in canvas pixels, `count` entries long. An
|
|
204
|
+
* observation the camera puts behind the eye is given NaN by the projector and
|
|
205
|
+
* never selected.
|
|
206
|
+
*/
|
|
207
|
+
export function selectInRegionsProjected(screen, count, regions) {
|
|
208
|
+
const mask = new Uint8Array(count);
|
|
209
|
+
const shapes = regions.flatMap((r) => regionShapes(r));
|
|
210
|
+
if (shapes.length === 0)
|
|
211
|
+
return { mask, count: 0 };
|
|
212
|
+
let hits = 0;
|
|
213
|
+
for (let i = 0; i < count; i++) {
|
|
214
|
+
const px = screen[i * 2];
|
|
215
|
+
const py = screen[i * 2 + 1];
|
|
216
|
+
// Behind the camera, or otherwise unprojectable.
|
|
217
|
+
if (!Number.isFinite(px) || !Number.isFinite(py))
|
|
218
|
+
continue;
|
|
219
|
+
for (const shape of shapes) {
|
|
220
|
+
if (!inBounds(shape.bounds, px, py))
|
|
221
|
+
continue;
|
|
222
|
+
if (shape.hit(px, py)) {
|
|
223
|
+
mask[i] = 1;
|
|
224
|
+
hits++;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return { mask, count: hits };
|
|
230
|
+
}
|
|
231
|
+
/** Every observation whose categorical code equals `code` — the legend click. */
|
|
232
|
+
/**
|
|
233
|
+
* Whether two selections hold exactly the same observations.
|
|
234
|
+
*
|
|
235
|
+
* Lets a click toggle against the SELECTION rather than against a remembered
|
|
236
|
+
* click: picking a class in the legend and then clicking that same class on the
|
|
237
|
+
* canvas should clear it, and a remembered click cannot know about the legend.
|
|
238
|
+
* Counts are compared first, so two different selections usually cost nothing.
|
|
239
|
+
*/
|
|
240
|
+
export function sameSelection(a, b) {
|
|
241
|
+
if (a.count !== b.count)
|
|
242
|
+
return false;
|
|
243
|
+
if (a.mask.length !== b.mask.length)
|
|
244
|
+
return false;
|
|
245
|
+
for (let i = 0; i < a.mask.length; i++) {
|
|
246
|
+
// Normalised to 0/1: the masks are flags, and a writer is free to use any
|
|
247
|
+
// non-zero value for "selected".
|
|
248
|
+
if ((a.mask[i] ? 1 : 0) !== (b.mask[i] ? 1 : 0))
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
export function selectByCategory(codes, code) {
|
|
254
|
+
const mask = new Uint8Array(codes.length);
|
|
255
|
+
let count = 0;
|
|
256
|
+
for (let i = 0; i < codes.length; i++) {
|
|
257
|
+
if (codes[i] === code) {
|
|
258
|
+
mask[i] = 1;
|
|
259
|
+
count++;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return { mask, count };
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhdGlhbC1zZWxlY3Rpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3NwYXRpYWwvc3BhdGlhbC1zZWxlY3Rpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBNkJBLG9EQUFvRDtBQUNwRCxNQUFNLFVBQVUsY0FBYyxDQUFDLEtBQUssR0FBRyxDQUFDO0lBQ3RDLE9BQU8sRUFBRSxJQUFJLEVBQUUsSUFBSSxVQUFVLENBQUMsS0FBSyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUMsRUFBRSxDQUFDO0FBQ25ELENBQUM7QUFFRCx1Q0FBdUM7QUFDdkMsTUFBTSxVQUFVLFNBQVMsQ0FBQyxJQUFnQjtJQUN4QyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDVixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUU7UUFBRSxJQUFJLElBQUksQ0FBQyxDQUFDLENBQUM7WUFBRSxDQUFDLEVBQUUsQ0FBQztJQUN2RCxPQUFPLENBQUMsQ0FBQztBQUNYLENBQUM7QUFFRCwwREFBMEQ7QUFDMUQsTUFBTSxVQUFVLGFBQWEsQ0FBQyxJQUFnQjtJQUM1QyxNQUFNLEdBQUcsR0FBRyxJQUFJLFdBQVcsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUM3QyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDWCxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUU7UUFBRSxJQUFJLElBQUksQ0FBQyxDQUFDLENBQUM7WUFBRSxHQUFHLENBQUMsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDakUsT0FBTyxHQUFHLENBQUM7QUFDYixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxrQkFBa0IsQ0FBQyxTQUErQjtJQUNoRSxJQUFJ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b3J0IHsgU3BhdGlhbEltYWdlUmVmLCBTcGF0aWFsT2JzZXJ2YXRpb25zIH0gZnJvbSAnLi4vY29udHJhY3RzL3NwYXRpYWwtZGF0YXNldC5jb250cmFjdCc7XG5cbi8qKlxuICogV2hpY2ggb2JzZXJ2YXRpb25zIGZhbGwgaW5zaWRlIGEgc2V0IG9mIGRyYXduIHJlZ2lvbnMuXG4gKlxuICogUHVyZSwgbGlrZSBgc3BhdGlhbC1lbmNvZGluZy50c2Ag4oCUIG5vIHN0b3JlLCBubyByZW5kZXJlciDigJQgc28gdGhlIGdlb21ldHJ5IGlzXG4gKiB0ZXN0YWJsZSBvbiBpdHMgb3duLlxuICpcbiAqIFdIWSBUSElTIElTIFRIRSBXSE9MRSBTRUxFQ1RJT04gTUVDSEFOSVNNXG4gKiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogUmVnaW9ucyBhbHJlYWR5IGNvbWUgZnJvbSBldmVyeSBvbi1jYW52YXMgdG9vbCB0aGUgbGlicmFyeSBoYXM6IHJlY3RhbmdsZSxcbiAqIHBvbHlnb24sIGZyZWVoYW5kLCBtYWdpYyB3YW5kLCBicnVzaC4gVGVzdGluZyBvYnNlcnZhdGlvbnMgYWdhaW5zdCB0aGVtIHR1cm5zXG4gKiBhbGwgb2YgdGhvc2UgaW50byBzcGF0aWFsLW9taWNzIHNlbGVjdGlvbiB0b29scyB3aXRob3V0IGFkZGluZyBhIHNpbmdsZSBuZXdcbiAqIGNhbnZhcyBpbnRlcmFjdGlvbiDigJQgd2hpY2ggaXMgd2h5IHRoZXJlIGlzIG5vIGJlc3Bva2UgbWFycXVlZSBoZXJlLlxuICpcbiAqIENPT1JESU5BVEUgRlJBTUVTXG4gKiAtLS0tLS0tLS0tLS0tLS0tLVxuICogUmVnaW9ucyBhcmUgZHJhd24gaW4gV09STEQgKGltYWdlKSBjb29yZGluYXRlcy4gT2JzZXJ2YXRpb25zIGxpdmUgaW4gdGhlXG4gKiBkYXRhc2V0J3Mgb3duIGZyYW1lIGFuZCByZWFjaCB0aGUgd29ybGQgdGhyb3VnaCBgaW1hZ2VSZWZgIOKAlCBzbyB0aGV5IGFyZVxuICogdHJhbnNmb3JtZWQgZm9yd2FyZCBiZWZvcmUgdGVzdGluZywgbWF0Y2hpbmcgZXhhY3RseSB3aGF0IHRoZSByZW5kZXJlciBkcmF3cy5cbiAqL1xuXG4vKiogQSBzZWxlY3Rpb24gb3ZlciBOIG9ic2VydmF0aW9uczogYG1hc2tbaV0gPT09IDFgIHdoZW4gaSBpcyBzZWxlY3RlZC4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU3BhdGlhbFNlbGVjdGlvbk1hc2sge1xuICBtYXNrOiBVaW50OEFycmF5O1xuICBjb3VudDogbnVtYmVyO1xufVxuXG4vKiogQW4gZW1wdHkgc2VsZWN0aW9uIG92ZXIgYGNvdW50YCBvYnNlcnZhdGlvbnMuICovXG5leHBvcnQgZnVuY3Rpb24gZW1wdHlTZWxlY3Rpb24oY291bnQgPSAwKTogU3BhdGlhbFNlbGVjdGlvbk1hc2sge1xuICByZXR1cm4geyBtYXNrOiBuZXcgVWludDhBcnJheShjb3VudCksIGNvdW50OiAwIH07XG59XG5cbi8qKiBOdW1iZXIgb2Ygc2V0IGVudHJpZXMgaW4gYSBtYXNrLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNvdW50TWFzayhtYXNrOiBVaW50OEFycmF5KTogbnVtYmVyIHtcbiAgbGV0IG4gPSAwO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IG1hc2subGVuZ3RoOyBpKyspIGlmIChtYXNrW2ldKSBuKys7XG4gIHJldHVybiBuO1xufVxuXG4vKiogU2VsZWN0ZWQgaW5kaWNlcywgZm9yIGV4cG9ydCBvciBhIGRvd25zdHJlYW0gcXVlcnkuICovXG5leHBvcnQgZnVuY3Rpb24gbWFza1RvSW5kaWNlcyhtYXNrOiBVaW50OEFycmF5KTogVWludDMyQXJyYXkge1xuICBjb25zdCBvdXQgPSBuZXcgVWludDMyQXJyYXkoY291bnRNYXNrKG1hc2spKTtcbiAgbGV0IGF0ID0gMDtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBtYXNrLmxlbmd0aDsgaSsrKSBpZiAobWFza1tpXSkgb3V0W2F0KytdID0gaTtcbiAgcmV0dXJuIG91dDtcbn1cblxuLyoqXG4gKiBSZW5kZXJlci1mYWNpbmcgaW52ZXJzZTogd2hpY2ggb2JzZXJ2YXRpb25zIHRvIE1VVEUuIFdpdGggbm90aGluZyBzZWxlY3RlZFxuICogbm90aGluZyBpcyBtdXRlZCAodGhlIHdob2xlIHRpc3N1ZSByZWFkcyBub3JtYWxseSk7IHdpdGggYSBzZWxlY3Rpb24sIGV2ZXJ5XG4gKiB1bnNlbGVjdGVkIG9ic2VydmF0aW9uIGlzIG11dGVkIOKAlCB0aGUgQ29zTXggaGlnaGxpZ2h0LXZzLW11dGUgcnVsZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG11dGVkRnJvbVNlbGVjdGlvbihzZWxlY3Rpb246IFNwYXRpYWxTZWxlY3Rpb25NYXNrKTogVWludDhBcnJheSB8IG51bGwge1xuICBpZiAoc2VsZWN0aW9uLmNvdW50ID09PSAwKSByZXR1cm4gbnVsbDtcbiAgY29uc3QgbXV0ZWQgPSBuZXcgVWludDhBcnJheShzZWxlY3Rpb24ubWFzay5sZW5ndGgpO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IG11dGVkLmxlbmd0aDsgaSsrKSBtdXRlZFtpXSA9IHNlbGVjdGlvbi5tYXNrW2ldID8gMCA6IDE7XG4gIHJldHVybiBtdXRlZDtcbn1cblxuLyoqIEV2ZW4tb2RkIHJheSBjYXN0OiBpcyAocHgsIHB5KSBpbnNpZGUgdGhlIGNsb3NlZCByaW5nIHhzL3lzPyAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBvaW50SW5SaW5nKHhzOiByZWFkb25seSBudW1iZXJbXSwgeXM6IHJlYWRvbmx5IG51bWJlcltdLCBweDogbnVtYmVyLCBweTogbnVtYmVyKTogYm9vbGVhbiB7XG4gIGxldCBpbnNpZGUgPSBmYWxzZTtcbiAgY29uc3QgbiA9IHhzLmxlbmd0aDtcbiAgZm9yIChsZXQgaSA9IDAsIGogPSBuIC0gMTsgaSA8IG47IGogPSBpKyspIHtcbiAgICBjb25zdCB5aSA9IHlzW2ldO1xuICAgIGNvbnN0IHlqID0geXNbal07XG4gICAgLy8gSGFsZi1vcGVuIGNvbXBhcmlzb24gc28gYSB2ZXJ0ZXggb24gdGhlIHJheSBpcyBjb3VudGVkIG9uY2UsIG5vdCB0d2ljZS5cbiAgICBpZiAoKHlpID4gcHkpICE9PSAoeWogPiBweSkpIHtcbiAgICAgIGNvbnN0IHQgPSAocHkgLSB5aSkgLyAoeWogLSB5aSk7XG4gICAgICBpZiAocHggPCB4c1tpXSArIHQgKiAoeHNbal0gLSB4c1tpXSkpIGluc2lkZSA9ICFpbnNpZGU7XG4gICAgfVxuICB9XG4gIHJldHVybiBpbnNpZGU7XG59XG5cbi8qKiBBeGlzLWFsaWduZWQgYm91bmRzIG9mIGEgcmluZywgZm9yIGEgY2hlYXAgcmVqZWN0IGJlZm9yZSB0aGUgcmF5IGNhc3QuICovXG5pbnRlcmZhY2UgQm91bmRzIHsgbWluWDogbnVtYmVyOyBtaW5ZOiBudW1iZXI7IG1heFg6IG51bWJlcjsgbWF4WTogbnVtYmVyIH1cblxuZnVuY3Rpb24gcmluZ0JvdW5kcyh4czogcmVhZG9ubHkgbnVtYmVyW10sIHlzOiByZWFkb25seSBudW1iZXJbXSk6IEJvdW5kcyB7XG4gIGxldCBtaW5YID0gSW5maW5pdHksIG1pblkgPSBJbmZpbml0eSwgbWF4WCA9IC1JbmZpbml0eSwgbWF4WSA9IC1JbmZpbml0eTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCB4cy5sZW5ndGg7IGkrKykge1xuICAgIGlmICh4c1tpXSA8IG1pblgpIG1pblggPSB4c1tpXTtcbiAgICBpZiAoeHNbaV0gPiBtYXhYKSBtYXhYID0geHNbaV07XG4gICAgaWYgKHlzW2ldIDwgbWluWSkgbWluWSA9IHlzW2ldO1xuICAgIGlmICh5c1tpXSA+IG1heFkpIG1heFkgPSB5c1tpXTtcbiAgfVxuICByZXR1cm4geyBtaW5YLCBtaW5ZLCBtYXhYLCBtYXhZIH07XG59XG5cbmZ1bmN0aW9uIGluQm91bmRzKGI6IEJvdW5kcywgcHg6IG51bWJlciwgcHk6IG51bWJlcik6IGJvb2xlYW4ge1xuICByZXR1cm4gcHggPj0gYi5taW5YICYmIHB4IDw9IGIubWF4WCAmJiBweSA+PSBiLm1pblkgJiYgcHkgPD0gYi5tYXhZO1xufVxuXG4vKiogQSByZWdpb24gZmxhdHRlbmVkIHRvIHNvbWV0aGluZyB0ZXN0YWJsZTogYm91bmRzICsgYSBoaXQgdGVzdC4gKi9cbmludGVyZmFjZSBTaGFwZSB7XG4gIGJvdW5kczogQm91bmRzO1xuICBoaXQocHg6IG51bWJlciwgcHk6IG51bWJlcik6IGJvb2xlYW47XG59XG5cbi8qKiBQb2x5Z29uLXdpdGgtaG9sZXM6IGluc2lkZSB0aGUgZXh0ZXJpb3IgYW5kIG91dHNpZGUgZXZlcnkgaG9sZS4gKi9cbmZ1bmN0aW9uIHBvbHlnb25TaGFwZShcbiAgeHM6IG51bWJlcltdLCB5czogbnVtYmVyW10sIGhvbGVzPzogbnVtYmVyW11bXVtdLFxuKTogU2hhcGUgfCBudWxsIHtcbiAgaWYgKHhzLmxlbmd0aCA8IDMpIHJldHVybiBudWxsO1xuICBjb25zdCBib3VuZHMgPSByaW5nQm91bmRzKHhzLCB5cyk7XG4gIHJldHVybiB7XG4gICAgYm91bmRzLFxuICAgIGhpdDogKHB4LCBweSkgPT4ge1xuICAgICAgaWYgKCFwb2ludEluUmluZyh4cywgeXMsIHB4LCBweSkpIHJldHVybiBmYWxzZTtcbiAgICAgIC8vIFRoZSByZWdpb24gbW9kZWwgaXMgZXhwbGljaXQ6IGEgcG9pbnQgaW5zaWRlIHRoZSBleHRlcmlvciBBTkQgaW5zaWRlIGFcbiAgICAgIC8vIGhvbGUgaXMgT1VUU0lERSB0aGUgcmVnaW9uLlxuICAgICAgZm9yIChjb25zdCByaW5nIG9mIGhvbGVzID8/IFtdKSB7XG4gICAgICAgIGlmIChyaW5nLmxlbmd0aCA+PSAzXG4gICAgICAgICAgJiYgcG9pbnRJblJpbmcocmluZy5tYXAoKHApID0+IHBbMF0pLCByaW5nLm1hcCgocCkgPT4gcFsxXSksIHB4LCBweSkpIHtcbiAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH0sXG4gIH07XG59XG5cbi8qKlxuICogRmxhdHRlbiBhIHJlZ2lvbiB0byB0ZXN0YWJsZSBzaGFwZXMuIFJldHVybnMgYW4gZW1wdHkgYXJyYXkgZm9yIGFueXRoaW5nIHRoYXRcbiAqIGVuY2xvc2VzIG5vIGFyZWEg4oCUIGFuIG9wZW4gcG9seWxpbmUsIGFuIGludGVuc2l0eS1wcm9maWxlIGxpbmUsIGEgZGVnZW5lcmF0ZVxuICogcmVjdGFuZ2xlIOKAlCBzbyB0aG9zZSBuZXZlciBjb250cmlidXRlIHRvIGEgc2VsZWN0aW9uLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVnaW9uU2hhcGVzKHJlZ2lvbjogUmVnaW9uKTogU2hhcGVbXSB7XG4gIC8vIFByb2ZpbGUgbGluZXMgYmVsb25nIHRvIHRoZSBpbnRlbnNpdHkgdG9vbCwgbm90IHRoZSBhbm5vdGF0aW9uIHNldC5cbiAgaWYgKChyZWdpb24gYXMgdW5rbm93biBhcyB7IGtpbmQ/OiBzdHJpbmcgfSk/LmtpbmQgPT09ICdwcm9maWxlJykgcmV0dXJuIFtdO1xuICBjb25zdCBiID0gcmVnaW9uPy5ib3VuZHMgYXMgdW5rbm93biBhcyB7XG4gICAgeD86IG51bWJlcjsgeT86IG51bWJlcjsgd2lkdGg/OiBudW1iZXI7IGhlaWdodD86IG51bWJlcjtcbiAgICB4cG9pbnRzPzogbnVtYmVyW107IHlwb2ludHM/OiBudW1iZXJbXTsgY2xvc2VkPzogYm9vbGVhbjsgaG9sZXM/OiBudW1iZXJbXVtdW107XG4gICAgcG9seWdvbnM/OiB7IHhwb2ludHM6IG51bWJlcltdOyB5cG9pbnRzOiBudW1iZXJbXTsgY2xvc2VkPzogYm9vbGVhbjsgaG9sZXM/OiBudW1iZXJbXVtdW10gfVtdO1xuICB9IHwgbnVsbCB8IHVuZGVmaW5lZDtcbiAgaWYgKCFiKSByZXR1cm4gW107XG5cbiAgaWYgKEFycmF5LmlzQXJyYXkoYi5wb2x5Z29ucykpIHtcbiAgICByZXR1cm4gYi5wb2x5Z29ucy5mbGF0TWFwKChwKSA9PlxuICAgICAgcC5jbG9zZWQgPT09IGZhbHNlID8gW10gOiAocG9seWdvblNoYXBlKHAueHBvaW50cyA/PyBbXSwgcC55cG9pbnRzID8/IFtdLCBwLmhvbGVzKSA/PyBbXSkpO1xuICB9XG4gIGlmIChBcnJheS5pc0FycmF5KGIueHBvaW50cykpIHtcbiAgICBpZiAoYi5jbG9zZWQgPT09IGZhbHNlKSByZXR1cm4gW107IC8vIG9wZW4gcG9seWxpbmUgZW5jbG9zZXMgbm90aGluZ1xuICAgIGNvbnN0IHNoYXBlID0gcG9seWdvblNoYXBlKGIueHBvaW50cywgYi55cG9pbnRzID8/IFtdLCBiLmhvbGVzKTtcbiAgICByZXR1cm4gc2hhcGUgPyBbc2hhcGVdIDogW107XG4gIH1cbiAgaWYgKHR5cGVvZiBiLndpZHRoID09PSAnbnVtYmVyJyAmJiB0eXBlb2YgYi5oZWlnaHQgPT09ICdudW1iZXInKSB7XG4gICAgY29uc3QgeCA9IGIueCA/PyAwO1xuICAgIGNvbnN0IHkgPSBiLnkgPz8gMDtcbiAgICAvLyBOb3JtYWxpc2UgYSByZWN0YW5nbGUgZHJhZ2dlZCByaWdodC10by1sZWZ0IC8gYm90dG9tLXRvLXRvcC5cbiAgICBjb25zdCBtaW5YID0gTWF0aC5taW4oeCwgeCArIGIud2lkdGgpO1xuICAgIGNvbnN0IG1heFggPSBNYXRoLm1heCh4LCB4ICsgYi53aWR0aCk7XG4gICAgY29uc3QgbWluWSA9IE1hdGgubWluKHksIHkgKyBiLmhlaWdodCk7XG4gICAgY29uc3QgbWF4WSA9IE1hdGgubWF4KHksIHkgKyBiLmhlaWdodCk7XG4gICAgaWYgKG1heFggPT09IG1pblggfHwgbWF4WSA9PT0gbWluWSkgcmV0dXJuIFtdO1xuICAgIGNvbnN0IGJvdW5kcyA9IHsgbWluWCwgbWluWSwgbWF4WCwgbWF4WSB9O1xuICAgIHJldHVybiBbeyBib3VuZHMsIGhpdDogKHB4LCBweSkgPT4gaW5Cb3VuZHMoYm91bmRzLCBweCwgcHkpIH1dO1xuICB9XG4gIHJldHVybiBbXTtcbn1cblxuLyoqXG4gKiBPYnNlcnZhdGlvbnMgaW5zaWRlIEFOWSBvZiBgcmVnaW9uc2AgKHVuaW9uKS4gQ29vcmRpbmF0ZXMgYXJlIHRyYW5zZm9ybWVkIGJ5XG4gKiBgaW1hZ2VSZWZgIGZpcnN0LCBzbyB0aGUgdGVzdCBoYXBwZW5zIGluIHRoZSBzYW1lIHdvcmxkIHNwYWNlIHRoZSByZWdpb25zIHdlcmVcbiAqIGRyYXduIGluLlxuICpcbiAqIGBjYW5kaWRhdGVzYCwgd2hlbiBnaXZlbiwgcmVzdHJpY3RzIHRoZSB0ZXN0IHRvIHRob3NlIG9ic2VydmF0aW9uIGluZGljZXMg4oCUIGhvd1xuICogYSAyRCB2aWV3IG9mIGEgM0QgZGF0YXNldCBrZWVwcyBhIGRyYXduIHJlZ2lvbiBtZWFuaW5nIHdoYXQgaXQgbG9va3MgbGlrZSBpdFxuICogbWVhbnM6IHRoZSBzaGFwZSB3YXMgZHJhd24gb3ZlciBPTkUgc2VjdGlvbiwgc28gaXQgc2VsZWN0cyB0aGF0IHNlY3Rpb24ncyBjZWxsc1xuICogcmF0aGVyIHRoYW4gdGhlIHdob2xlIGRlcHRoIG9mIHRoZSBzcGVjaW1lbiBiZWhpbmQgdGhlbS4gVGhlIHJldHVybmVkIG1hc2sgc3RheXNcbiAqIGluZGV4ZWQgYnkgb2JzZXJ2YXRpb24sIHNvIGV2ZXJ5IGNvbnN1bWVyIChjaGFydHMsIGxlZ2VuZCwgdGhlIDNEIGNsb3VkKSByZWFkcyBpdFxuICogdGhlIHNhbWUgd2F5LlxuICovXG4vKipcbiAqIEEgc2VsZWN0aW9uIGZyb20gYW4gZXhwbGljaXQgbGlzdCBvZiBvYnNlcnZhdGlvbiBpbmRpY2VzLlxuICpcbiAqIFdoYXQgYSBsYXNzbyBpbiBhIGxpbmtlZCBwbG90IHByb2R1Y2VzOiB0aGUgcGxvdCBrbm93cyB3aGljaCBwb2ludHMgdGhlIHVzZXIgZHJld1xuICogYXJvdW5kLCBhbmQgdGhlIG1hcCBuZWVkcyB0aGF0IGFzIGEgbWFzayBvdmVyIEFMTCBvYnNlcnZhdGlvbnMuIE91dC1vZi1yYW5nZSBhbmRcbiAqIGR1cGxpY2F0ZSBpbmRpY2VzIGFyZSBpZ25vcmVkIHJhdGhlciB0aGFuIHJlamVjdGVkIOKAlCBhIHBsb3QgbWF5IGhhbmQgYmFjayB0aGUgc2FtZVxuICogcG9pbnQgdHdpY2UsIGFuZCBhIHN0YWxlIGluZGV4IGZyb20gYSBwbG90IGRyYXduIGJlZm9yZSBhIGRhdGFzZXQgY2hhbmdlIG11c3Qgbm90XG4gKiB0aHJvdyBpbiB0aGUgbWlkZGxlIG9mIGEgc2VsZWN0aW9uIGdlc3R1cmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZWxlY3RCeUluZGljZXMoXG4gIGluZGljZXM6IEl0ZXJhYmxlPG51bWJlcj4sIG9ic2VydmF0aW9uQ291bnQ6IG51bWJlcixcbik6IFNwYXRpYWxTZWxlY3Rpb25NYXNrIHtcbiAgY29uc3QgbWFzayA9IG5ldyBVaW50OEFycmF5KG9ic2VydmF0aW9uQ291bnQpO1xuICBsZXQgY291bnQgPSAwO1xuICBmb3IgKGNvbnN0IGkgb2YgaW5kaWNlcykge1xuICAgIGlmICghTnVtYmVyLmlzSW50ZWdlcihpKSB8fCBpIDwgMCB8fCBpID49IG9ic2VydmF0aW9uQ291bnQpIGNvbnRpbnVlO1xuICAgIGlmIChtYXNrW2ldKSBjb250aW51ZTtcbiAgICBtYXNrW2ldID0gMTtcbiAgICBjb3VudCsrO1xuICB9XG4gIHJldHVybiB7IG1hc2ssIGNvdW50IH07XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBzZWxlY3RJblJlZ2lvbnMoXG4gIG9ic2VydmF0aW9uczogU3BhdGlhbE9ic2VydmF0aW9ucyxcbiAgaW1hZ2VSZWY6IFNwYXRpYWxJbWFnZVJlZiB8IHVuZGVmaW5lZCxcbiAgcmVnaW9uczogcmVhZG9ubHkgUmVnaW9uW10sXG4gIGNhbmRpZGF0ZXM/OiBVaW50MzJBcnJheSxcbik6IFNwYXRpYWxTZWxlY3Rpb25NYXNrIHtcbiAgY29uc3QgbiA9IG9ic2VydmF0aW9ucy5jb3VudDtcbiAgY29uc3QgbWFzayA9IG5ldyBVaW50OEFycmF5KG4pO1xuICBjb25zdCBzaGFwZXMgPSByZWdpb25zLmZsYXRNYXAoKHIpID0+IHJlZ2lvblNoYXBlcyhyKSk7XG4gIGlmIChzaGFwZXMubGVuZ3RoID09PSAwKSByZXR1cm4geyBtYXNrLCBjb3VudDogMCB9O1xuXG4gIGNvbnN0IFtzeCwgc3ldID0gaW1hZ2VSZWY/LnNjYWxlID8/IFsxLCAxXTtcbiAgY29uc3QgW3R4LCB0eV0gPSBpbWFnZVJlZj8udHJhbnNsYXRlID8/IFswLCAwXTtcbiAgbGV0IGNvdW50ID0gMDtcbiAgY29uc3QgdG90YWwgPSBjYW5kaWRhdGVzID8gY2FuZGlkYXRlcy5sZW5ndGggOiBuO1xuICBmb3IgKGxldCBrID0gMDsgayA8IHRvdGFsOyBrKyspIHtcbiAgICBjb25zdCBpID0gY2FuZGlkYXRlcyA/IGNhbmRpZGF0ZXNba10gOiBrO1xuICAgIGNvbnN0IHB4ID0gb2JzZXJ2YXRpb25zLnhbaV0gKiBzeCArIHR4O1xuICAgIGNvbnN0IHB5ID0gb2JzZXJ2YXRpb25zLnlbaV0gKiBzeSArIHR5O1xuICAgIGZvciAoY29uc3Qgc2hhcGUgb2Ygc2hhcGVzKSB7XG4gICAgICAvLyBCb3VuZHMgZmlyc3Q6IGEgcmF5IGNhc3QgcGVyIG9ic2VydmF0aW9uIHBlciByZWdpb24gd291bGQgZG9taW5hdGUgYXRcbiAgICAgIC8vIDEwXjUgb2JzZXJ2YXRpb25zLCBhbmQgbW9zdCBwb2ludHMgbWlzcyBtb3N0IHJlZ2lvbnMuXG4gICAgICBpZiAoIWluQm91bmRzKHNoYXBlLmJvdW5kcywgcHgsIHB5KSkgY29udGludWU7XG4gICAgICBpZiAoc2hhcGUuaGl0KHB4LCBweSkpIHtcbiAgICAgICAgbWFza1tpXSA9IDE7XG4gICAgICAgIGNvdW50Kys7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICByZXR1cm4geyBtYXNrLCBjb3VudCB9O1xufVxuXG4vKipcbiAqIFNlbGVjdCBvYnNlcnZhdGlvbnMgd2hvc2UgU0NSRUVOIHByb2plY3Rpb24gZmFsbHMgaW5zaWRlIHRoZSBkcmF3biByZWdpb25zLlxuICpcbiAqIFRoaXMgaXMgaG93IGEgcmVnaW9uIHNlbGVjdGlvbiB3b3JrcyBpbiB0aGUgM0QgY2xvdWQuIFRoZXJlIGlzIG5vIGRhdGEtc3BhY2VcbiAqIGFmZmluZSB0byBtYXAgYSBkcmF3biBzaGFwZSB0aHJvdWdoLCBiZWNhdXNlIHRoZSBzaGFwZSB3YXMgZHJhd24gb24gYSAyRCBzY3JlZW5cbiAqIGFnYWluc3QgYSBwZXJzcGVjdGl2ZSBjYW1lcmEg4oCUIHNvIHRoZSBvYnNlcnZhdGlvbnMgY29tZSB0aGUgb3RoZXIgd2F5LCBhbHJlYWR5XG4gKiBwcm9qZWN0ZWQgdG8gY2FudmFzIHBpeGVscyBieSB0aGUgcmVuZGVyZXIgKHdoaWNoIG93bnMgdGhlIGNhbWVyYSksIGFuZCB0aGVcbiAqIHJlZ2lvbnMgYXJlIHRlc3RlZCBpbiB0aGF0IHNhbWUgc2NyZWVuIHNwYWNlLlxuICpcbiAqIFRoZSBjb25zZXF1ZW5jZSBpcyB3b3J0aCBiZWluZyBleHBsaWNpdCBhYm91dDogYSBzY3JlZW4tc3BhY2UgbGFzc28gc2VsZWN0c1xuICogdGhyb3VnaCB0aGUgV0hPTEUgREVQVEggb2YgdGhlIGNsb3VkLCBsaWtlIGEgY29va2llIGN1dHRlciwgbm90IGEgc2xhYiBhdCBzb21lXG4gKiBjaG9zZW4gei4gVGhhdCBpcyBpbmhlcmVudCB0byBkcmF3aW5nIG9uIGEgZmxhdCBzY3JlZW4sIGFuZCBpdCBpcyB3aGF0IG5hcGFyaVxuICogYW5kIGV2ZXJ5IG90aGVyIG9yYml0LWNhbWVyYSBwb2ludCBwaWNrZXIgZG8uIE9yYml0IGFuZCBkcmF3IGFnYWluIHRvIGN1dCBmcm9tXG4gKiBhbm90aGVyIGFuZ2xlLlxuICpcbiAqIGBzY3JlZW5gIGlzIGBbeDAsIHkwLCB4MSwgeTEsIOKApl1gIGluIGNhbnZhcyBwaXhlbHMsIGBjb3VudGAgZW50cmllcyBsb25nLiBBblxuICogb2JzZXJ2YXRpb24gdGhlIGNhbWVyYSBwdXRzIGJlaGluZCB0aGUgZXllIGlzIGdpdmVuIE5hTiBieSB0aGUgcHJvamVjdG9yIGFuZFxuICogbmV2ZXIgc2VsZWN0ZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZWxlY3RJblJlZ2lvbnNQcm9qZWN0ZWQoXG4gIHNjcmVlbjogRmxvYXQzMkFycmF5LFxuICBjb3VudDogbnVtYmVyLFxuICByZWdpb25zOiByZWFkb25seSBSZWdpb25bXSxcbik6IFNwYXRpYWxTZWxlY3Rpb25NYXNrIHtcbiAgY29uc3QgbWFzayA9IG5ldyBVaW50OEFycmF5KGNvdW50KTtcbiAgY29uc3Qgc2hhcGVzID0gcmVnaW9ucy5mbGF0TWFwKChyKSA9PiByZWdpb25TaGFwZXMocikpO1xuICBpZiAoc2hhcGVzLmxlbmd0aCA9PT0gMCkgcmV0dXJuIHsgbWFzaywgY291bnQ6IDAgfTtcblxuICBsZXQgaGl0cyA9IDA7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgY291bnQ7IGkrKykge1xuICAgIGNvbnN0IHB4ID0gc2NyZWVuW2kgKiAyXTtcbiAgICBjb25zdCBweSA9IHNjcmVlbltpICogMiArIDFdO1xuICAgIC8vIEJlaGluZCB0aGUgY2FtZXJhLCBvciBvdGhlcndpc2UgdW5wcm9qZWN0YWJsZS5cbiAgICBpZiAoIU51bWJlci5pc0Zpbml0ZShweCkgfHwgIU51bWJlci5pc0Zpbml0ZShweSkpIGNvbnRpbnVlO1xuICAgIGZvciAoY29uc3Qgc2hhcGUgb2Ygc2hhcGVzKSB7XG4gICAgICBpZiAoIWluQm91bmRzKHNoYXBlLmJvdW5kcywgcHgsIHB5KSkgY29udGludWU7XG4gICAgICBpZiAoc2hhcGUuaGl0KHB4LCBweSkpIHtcbiAgICAgICAgbWFza1tpXSA9IDE7XG4gICAgICAgIGhpdHMrKztcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHJldHVybiB7IG1hc2ssIGNvdW50OiBoaXRzIH07XG59XG5cbi8qKiBFdmVyeSBvYnNlcnZhdGlvbiB3aG9zZSBjYXRlZ29yaWNhbCBjb2RlIGVxdWFscyBgY29kZWAg4oCUIHRoZSBsZWdlbmQgY2xpY2suICovXG4vKipcbiAqIFdoZXRoZXIgdHdvIHNlbGVjdGlvbnMgaG9sZCBleGFjdGx5IHRoZSBzYW1lIG9ic2VydmF0aW9ucy5cbiAqXG4gKiBMZXRzIGEgY2xpY2sgdG9nZ2xlIGFnYWluc3QgdGhlIFNFTEVDVElPTiByYXRoZXIgdGhhbiBhZ2FpbnN0IGEgcmVtZW1iZXJlZFxuICogY2xpY2s6IHBpY2tpbmcgYSBjbGFzcyBpbiB0aGUgbGVnZW5kIGFuZCB0aGVuIGNsaWNraW5nIHRoYXQgc2FtZSBjbGFzcyBvbiB0aGVcbiAqIGNhbnZhcyBzaG91bGQgY2xlYXIgaXQsIGFuZCBhIHJlbWVtYmVyZWQgY2xpY2sgY2Fubm90IGtub3cgYWJvdXQgdGhlIGxlZ2VuZC5cbiAqIENvdW50cyBhcmUgY29tcGFyZWQgZmlyc3QsIHNvIHR3byBkaWZmZXJlbnQgc2VsZWN0aW9ucyB1c3VhbGx5IGNvc3Qgbm90aGluZy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNhbWVTZWxlY3Rpb24oYTogU3BhdGlhbFNlbGVjdGlvbk1hc2ssIGI6IFNwYXRpYWxTZWxlY3Rpb25NYXNrKTogYm9vbGVhbiB7XG4gIGlmIChhLmNvdW50ICE9PSBiLmNvdW50KSByZXR1cm4gZmFsc2U7XG4gIGlmIChhLm1hc2subGVuZ3RoICE9PSBiLm1hc2subGVuZ3RoKSByZXR1cm4gZmFsc2U7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgYS5tYXNrLmxlbmd0aDsgaSsrKSB7XG4gICAgLy8gTm9ybWFsaXNlZCB0byAwLzE6IHRoZSBtYXNrcyBhcmUgZmxhZ3MsIGFuZCBhIHdyaXRlciBpcyBmcmVlIHRvIHVzZSBhbnlcbiAgICAvLyBub24temVybyB2YWx1ZSBmb3IgXCJzZWxlY3RlZFwiLlxuICAgIGlmICgoYS5tYXNrW2ldID8gMSA6IDApICE9PSAoYi5tYXNrW2ldID8gMSA6IDApKSByZXR1cm4gZmFsc2U7XG4gIH1cbiAgcmV0dXJuIHRydWU7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBzZWxlY3RCeUNhdGVnb3J5KGNvZGVzOiBVaW50MTZBcnJheSwgY29kZTogbnVtYmVyKTogU3BhdGlhbFNlbGVjdGlvbk1hc2sge1xuICBjb25zdCBtYXNrID0gbmV3IFVpbnQ4QXJyYXkoY29kZXMubGVuZ3RoKTtcbiAgbGV0IGNvdW50ID0gMDtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBjb2Rlcy5sZW5ndGg7IGkrKykge1xuICAgIGlmIChjb2Rlc1tpXSA9PT0gY29kZSkge1xuICAgICAgbWFza1tpXSA9IDE7XG4gICAgICBjb3VudCsrO1xuICAgIH1cbiAgfVxuICByZXR1cm4geyBtYXNrLCBjb3VudCB9O1xufVxuIl19
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/** Encode one canvas as a PNG blob, over whichever canvas flavour this runtime has. */
|
|
2
|
+
async function toPngBlob(canvas) {
|
|
3
|
+
if ('convertToBlob' in canvas)
|
|
4
|
+
return canvas.convertToBlob({ type: 'image/png' });
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error('toBlob produced nothing'))), 'image/png');
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Turn a spatial dataset's registered volume into a scrubbable z-stack image.
|
|
11
|
+
*
|
|
12
|
+
* A 3D omics dataset arrives as one volume and NO `imageRef`, so there is no image
|
|
13
|
+
* behind it — which left every image-shaped surface (the Image view, the slice
|
|
14
|
+
* bar, the contrast window, regions) with nothing to work on, and the Image view
|
|
15
|
+
* showing whatever slide happened to be loaded before. Encoding each z plane as a
|
|
16
|
+
* blob PNG makes the volume itself the image, so the existing stack machinery
|
|
17
|
+
* drives it unchanged: the same `urls[z]` + `isStack` shape a host emits for a
|
|
18
|
+
* multi-page TIFF, which is exactly what this volume is.
|
|
19
|
+
*
|
|
20
|
+
* Every plane is encoded up front rather than on demand, because the slice count
|
|
21
|
+
* comes from `urls.length` — the component has to know the depth before the user
|
|
22
|
+
* scrubs. The voxels are already resident (megabytes, fetched in one call), and a
|
|
23
|
+
* plane is a few KB of PNG, so the cost is bounded by the volume the host already
|
|
24
|
+
* chose to fetch.
|
|
25
|
+
*
|
|
26
|
+
* Returns null when the dataset declares no volume, or the byte count contradicts
|
|
27
|
+
* the declared geometry — a short buffer would silently produce slices of shifted
|
|
28
|
+
* anatomy, which is worse than no image at all.
|
|
29
|
+
*/
|
|
30
|
+
export async function buildVolumeStackImage(dataset, voxels) {
|
|
31
|
+
const meta = dataset.volume;
|
|
32
|
+
if (!meta)
|
|
33
|
+
return null;
|
|
34
|
+
const { width, height, depth } = meta;
|
|
35
|
+
const plane = width * height;
|
|
36
|
+
if (voxels.length !== plane * depth) {
|
|
37
|
+
console.warn(`[spatial] volume is ${voxels.length} bytes but its geometry declares ` +
|
|
38
|
+
`${plane * depth} (${width}x${height}x${depth}) — not rendering it as an image`);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
let canvas;
|
|
42
|
+
if (typeof OffscreenCanvas !== 'undefined') {
|
|
43
|
+
canvas = new OffscreenCanvas(width, height);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
canvas = document.createElement('canvas');
|
|
47
|
+
canvas.width = width;
|
|
48
|
+
canvas.height = height;
|
|
49
|
+
}
|
|
50
|
+
const ctx = canvas.getContext('2d');
|
|
51
|
+
if (!ctx)
|
|
52
|
+
throw new Error('[spatial] volume slice encode: 2D context unavailable');
|
|
53
|
+
// One canvas and one RGBA buffer for the whole stack — a canvas per plane would
|
|
54
|
+
// be `depth` GPU-backed surfaces alive at once for no gain.
|
|
55
|
+
const rgba = new Uint8ClampedArray(plane * 4);
|
|
56
|
+
const urls = [];
|
|
57
|
+
for (let z = 0; z < depth; z++) {
|
|
58
|
+
const base = z * plane;
|
|
59
|
+
for (let i = 0; i < plane; i++) {
|
|
60
|
+
const v = voxels[base + i];
|
|
61
|
+
const o = i * 4;
|
|
62
|
+
rgba[o] = v;
|
|
63
|
+
rgba[o + 1] = v;
|
|
64
|
+
rgba[o + 2] = v;
|
|
65
|
+
rgba[o + 3] = 255;
|
|
66
|
+
}
|
|
67
|
+
ctx.putImageData(new ImageData(rgba, width, height), 0, 0);
|
|
68
|
+
urls.push(URL.createObjectURL(await toPngBlob(canvas)));
|
|
69
|
+
}
|
|
70
|
+
// voxelSize is in the OBSERVATIONS' units, so it only means microns once
|
|
71
|
+
// `micronsPerUnit` says so. Absent, the unit is unknown and mpp stays null —
|
|
72
|
+
// the contract's rule for the scale bar: no bar beats a bar that looks like a
|
|
73
|
+
// measurement and is not one.
|
|
74
|
+
const perUnit = dataset.micronsPerUnit;
|
|
75
|
+
const mppX = perUnit != null ? meta.voxelSize[0] * perUnit : null;
|
|
76
|
+
const mppY = perUnit != null ? meta.voxelSize[1] * perUnit : null;
|
|
77
|
+
// The z counterpart, which is what carries the volume's anisotropy into the
|
|
78
|
+
// Volume / Isosurface views: they raymarch the image stack, and a stack with no
|
|
79
|
+
// declared slice spacing can only be given a shape-only world box.
|
|
80
|
+
const mppZ = perUnit != null ? meta.voxelSize[2] * perUnit : null;
|
|
81
|
+
return {
|
|
82
|
+
urls,
|
|
83
|
+
info: {
|
|
84
|
+
// A scalar field: one band, no colour of its own — which is also what puts
|
|
85
|
+
// the scalar modes (Heatmap / Contour / Surface / Volume) legitimately on
|
|
86
|
+
// offer for a dataset that has no camera image anywhere in it.
|
|
87
|
+
isGrayscale: true,
|
|
88
|
+
trueImageSize: [width, height],
|
|
89
|
+
urls,
|
|
90
|
+
isStack: depth > 1,
|
|
91
|
+
showStack: false,
|
|
92
|
+
scaleRatio: true,
|
|
93
|
+
// Stable AND unique per dataset: the slice-blob cache keys off the file name,
|
|
94
|
+
// so a name that changed per load would re-fetch a volume that never moved —
|
|
95
|
+
// and a name SHARED by two datasets (only `id` is required to be unique)
|
|
96
|
+
// would serve one dataset's slices for the other.
|
|
97
|
+
fileName: `${dataset.name} · reference volume [${dataset.id}]`,
|
|
98
|
+
imageMeta: [
|
|
99
|
+
{ channelCount: 1, rgbChannels: 1, x: width, y: height, z: depth, mppX, mppY, mppZ },
|
|
100
|
+
],
|
|
101
|
+
// Blob URLs are complete images: OSD must open them directly, not ask a tile
|
|
102
|
+
// server for a pyramid that does not exist.
|
|
103
|
+
tiled: false,
|
|
104
|
+
// Open mid-volume. The first and last planes of an anatomical volume are
|
|
105
|
+
// outside the specimen — opening on slice 0 shows an empty frame and reads
|
|
106
|
+
// as a failed load.
|
|
107
|
+
initialZIndex: depth > 1 ? depth >> 1 : undefined,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The data→image affine a registered volume implies.
|
|
113
|
+
*
|
|
114
|
+
* Once the volume IS the image ({@link buildVolumeStackImage}), one slice is a
|
|
115
|
+
* `width x height` pixel grid whose voxel `(i, j)` covers
|
|
116
|
+
* `[i * voxelSize[0], (i + 1) * voxelSize[0])` of the observations' coordinate
|
|
117
|
+
* space — the contract's rule that the volume's near corner sits at the
|
|
118
|
+
* coordinate origin. So observations reach pixel space by dividing out the voxel
|
|
119
|
+
* size, and nothing has to move.
|
|
120
|
+
*
|
|
121
|
+
* Shaped as a `SpatialImageRef` on purpose: the marker layer and the ROI
|
|
122
|
+
* selection then take the SAME transform they take for a dataset that ships a
|
|
123
|
+
* real `imageRef`, instead of each growing a volume special case.
|
|
124
|
+
*/
|
|
125
|
+
export function volumeImageRef(volume, micronsPerUnit) {
|
|
126
|
+
const [vx, vy] = volume.voxelSize;
|
|
127
|
+
return {
|
|
128
|
+
scale: [1 / vx, 1 / vy],
|
|
129
|
+
translate: [0, 0],
|
|
130
|
+
...(micronsPerUnit != null ? { mppX: vx * micronsPerUnit, mppY: vy * micronsPerUnit } : {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** The voxel plane a coordinate falls in, clamped into the volume. */
|
|
134
|
+
export function sliceIndexOf(z, volume) {
|
|
135
|
+
const k = Math.floor(z / volume.voxelSize[2]);
|
|
136
|
+
return Math.max(0, Math.min(volume.depth - 1, k));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Indices of the observations that fall in one voxel plane of the volume.
|
|
140
|
+
*
|
|
141
|
+
* A 2D view of a 3D dataset shows ONE plane, so it must draw one plane's
|
|
142
|
+
* observations: without this the whole depth of the specimen piles onto a single
|
|
143
|
+
* section and reads as a solid smear rather than a section's cells. The plane is
|
|
144
|
+
* a slab `voxelSize[2]` thick, which is exactly the sampling the volume itself
|
|
145
|
+
* has — for serial sections registered into a common frame, one slab is one
|
|
146
|
+
* section.
|
|
147
|
+
*
|
|
148
|
+
* Every observation is in the plane when the dataset carries no z: a genuinely
|
|
149
|
+
* flat dataset over a volume has nothing to filter on.
|
|
150
|
+
*/
|
|
151
|
+
export function observationsInSlice(obs, volume, slice) {
|
|
152
|
+
const z = obs.z;
|
|
153
|
+
if (!z)
|
|
154
|
+
return Uint32Array.from({ length: obs.count }, (_, i) => i);
|
|
155
|
+
const out = new Uint32Array(obs.count);
|
|
156
|
+
let n = 0;
|
|
157
|
+
for (let i = 0; i < obs.count; i++) {
|
|
158
|
+
if (sliceIndexOf(z[i], volume) === slice)
|
|
159
|
+
out[n++] = i;
|
|
160
|
+
}
|
|
161
|
+
return out.subarray(0, n);
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BhdGlhbC12b2x1bWUtaW1hZ2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3NwYXRpYWwvc3BhdGlhbC12b2x1bWUtaW1hZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBa0JBLHVGQUF1RjtBQUN2RixLQUFLLFVBQVUsU0FBUyxDQUFDLE1BQTJDO0lBQ2xFLElBQUksZUFBZSxJQUFJLE1BQU07UUFBRSxPQUFPLE1BQU0sQ0FBQyxhQUFhLENBQUMsRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLENBQUMsQ0FBQztJQUNsRixPQUFPLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1FBQzNDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDLENBQUMsRUFBRSxXQUFXLENBQUMsQ0FBQztJQUM5RyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FvQkc7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLHFCQUFxQixDQUN6QyxPQUF1QixFQUN2QixNQUFrQjtJQUVsQixNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDO0lBQzVCLElBQUksQ0FBQyxJQUFJO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDdkIsTUFBTSxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLEdBQUcsSUFBSSxDQUFDO0lBQ3RDLE1BQU0sS0FBSyxHQUFHLEtBQUssR0FBRyxNQUFNLENBQUM7SUFDN0IsSUFBSSxNQUFNLENBQUMsTUFBTSxLQUFLLEtBQUssR0FBRyxLQUFLLEVBQUUsQ0FBQztRQUNwQyxPQUFPLENBQUMsSUFBSSxD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QztRQUNuQyxJQUFJLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsTUFBTSxDQUFDLEtBQUssS0FBSztZQUFFLEdBQUcsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUN6RCxDQUFDO0lBQ0QsT0FBTyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUM1QixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSUltYWdlSW5mbyB9IGZyb20gJy4uL2NvbnRyYWN0cy9pbWFnZS5jb250cmFjdCc7XG5pbXBvcnQge1xuICBTcGF0aWFsRGF0YXNldCwgU3BhdGlhbEltYWdlUmVmLCBTcGF0aWFsT2JzZXJ2YXRpb25zLCBTcGF0aWFsVm9sdW1lTWV0YSxcbn0gZnJvbSAnLi4vY29udHJhY3RzL3NwYXRpYWwtZGF0YXNldC5jb250cmFjdCc7XG5cbi8qKlxuICogQSBkYXRhc2V0J3MgcmVmZXJlbmNlIHZvbHVtZSwgZW5jb2RlZCBhcyB0aGUgei1zdGFjayBpbWFnZSB0aGUgMkQgcGF0aCByZW5kZXJzLFxuICogcGx1cyB0aGUgYmxvYiBVUkxzIGJhY2tpbmcgaXQuXG4gKlxuICogVGhlIFVSTHMgYmVsb25nIHRvIHRoZSBDQUxMRVI6IG5vdGhpbmcgZWxzZSBjYW4ga25vdyB3aGVuIHRoZSBpbWFnZSBzdG9wcyBiZWluZ1xuICogZGlzcGxheWVkLCBhbmQgYSBkYXRhc2V0IHN3aXRjaCB0aGF0IGZvcmdldHMgdG8gcmV2b2tlIHRoZW0gbGVha3MgYSB2b2x1bWUnc1xuICogd29ydGggb2YgUE5HcyBwZXIgc3dpdGNoLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFZvbHVtZVN0YWNrSW1hZ2Uge1xuICBpbmZvOiBJSW1hZ2VJbmZvO1xuICB1cmxzOiBzdHJpbmdbXTtcbn1cblxuLyoqIEVuY29kZSBvbmUgY2FudmFzIGFzIGEgUE5HIGJsb2IsIG92ZXIgd2hpY2hldmVyIGNhbnZhcyBmbGF2b3VyIHRoaXMgcnVudGltZSBoYXMuICovXG5hc3luYyBmdW5jdGlvbiB0b1BuZ0Jsb2IoY2FudmFzOiBIVE1MQ2FudmFzRWxlbWVudCB8IE9mZnNjcmVlbkNhbnZhcyk6IFByb21pc2U8QmxvYj4ge1xuICBpZiAoJ2NvbnZlcnRUb0Jsb2InIGluIGNhbnZhcykgcmV0dXJuIGNhbnZhcy5jb252ZXJ0VG9CbG9iKHsgdHlwZTogJ2ltYWdlL3BuZycgfSk7XG4gIHJldHVybiBuZXcgUHJvbWlzZTxCbG9iPigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgY2FudmFzLnRvQmxvYigoYmxvYikgPT4gKGJsb2IgPyByZXNvbHZlKGJsb2IpIDogcmVqZWN0KG5ldyBFcnJvcigndG9CbG9iIHByb2R1Y2VkIG5vdGhpbmcnKSkpLCAnaW1hZ2UvcG5nJyk7XG4gIH0pO1xufVxuXG4vKipcbiAqIFR1cm4gYSBzcGF0aWFsIGRhdGFzZXQncyByZWdpc3RlcmVkIHZvbHVtZSBpbnRvIGEgc2NydWJiYWJsZSB6LXN0YWNrIGltYWdlLlxuICpcbiAqIEEgM0Qgb21pY3MgZGF0YXNldCBhcnJpdmVzIGFzIG9uZSB2b2x1bWUgYW5kIE5PIGBpbWFnZVJlZmAsIHNvIHRoZXJlIGlzIG5vIGltYWdlXG4gKiBiZWhpbmQgaXQg4oCUIHdoaWNoIGxlZnQgZXZlcnkgaW1hZ2Utc2hhcGVkIHN1cmZhY2UgKHRoZSBJbWFnZSB2aWV3LCB0aGUgc2xpY2VcbiAqIGJhciwgdGhlIGNvbnRyYXN0IHdpbmRvdywgcmVnaW9ucykgd2l0aCBub3RoaW5nIHRvIHdvcmsgb24sIGFuZCB0aGUgSW1hZ2Ugdmlld1xuICogc2hvd2luZyB3aGF0ZXZlciBzbGlkZSBoYXBwZW5lZCB0byBiZSBsb2FkZWQgYmVmb3JlLiBFbmNvZGluZyBlYWNoIHogcGxhbmUgYXMgYVxuICogYmxvYiBQTkcgbWFrZXMgdGhlIHZvbHVtZSBpdHNlbGYgdGhlIGltYWdlLCBzbyB0aGUgZXhpc3Rpbmcgc3RhY2sgbWFjaGluZXJ5XG4gKiBkcml2ZXMgaXQgdW5jaGFuZ2VkOiB0aGUgc2FtZSBgdXJsc1t6XWAgKyBgaXNTdGFja2Agc2hhcGUgYSBob3N0IGVtaXRzIGZvciBhXG4gKiBtdWx0aS1wYWdlIFRJRkYsIHdoaWNoIGlzIGV4YWN0bHkgd2hhdCB0aGlzIHZvbHVtZSBpcy5cbiAqXG4gKiBFdmVyeSBwbGFuZSBpcyBlbmNvZGVkIHVwIGZyb250IHJhdGhlciB0aGFuIG9uIGRlbWFuZCwgYmVjYXVzZSB0aGUgc2xpY2UgY291bnRcbiAqIGNvbWVzIGZyb20gYHVybHMubGVuZ3RoYCDigJQgdGhlIGNvbXBvbmVudCBoYXMgdG8ga25vdyB0aGUgZGVwdGggYmVmb3JlIHRoZSB1c2VyXG4gKiBzY3J1YnMuIFRoZSB2b3hlbHMgYXJlIGFscmVhZHkgcmVzaWRlbnQgKG1lZ2FieXRlcywgZmV0Y2hlZCBpbiBvbmUgY2FsbCksIGFuZCBhXG4gKiBwbGFuZSBpcyBhIGZldyBLQiBvZiBQTkcsIHNvIHRoZSBjb3N0IGlzIGJvdW5kZWQgYnkgdGhlIHZvbHVtZSB0aGUgaG9zdCBhbHJlYWR5XG4gKiBjaG9zZSB0byBmZXRjaC5cbiAqXG4gKiBSZXR1cm5zIG51bGwgd2hlbiB0aGUgZGF0YXNldCBkZWNsYXJlcyBubyB2b2x1bWUsIG9yIHRoZSBieXRlIGNvdW50IGNvbnRyYWRpY3RzXG4gKiB0aGUgZGVjbGFyZWQgZ2VvbWV0cnkg4oCUIGEgc2hvcnQgYnVmZmVyIHdvdWxkIHNpbGVudGx5IHByb2R1Y2Ugc2xpY2VzIG9mIHNoaWZ0ZWRcbiAqIGFuYXRvbXksIHdoaWNoIGlzIHdvcnNlIHRoYW4gbm8gaW1hZ2UgYXQgYWxsLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gYnVpbGRWb2x1bWVTdGFja0ltYWdlKFxuICBkYXRhc2V0OiBTcGF0aWFsRGF0YXNldCxcbiAgdm94ZWxzOiBVaW50OEFycmF5LFxuKTogUHJvbWlzZTxWb2x1bWVTdGFja0ltYWdlIHwgbnVsbD4ge1xuICBjb25zdCBtZXRhID0gZGF0YXNldC52b2x1bWU7XG4gIGlmICghbWV0YSkgcmV0dXJuIG51bGw7XG4gIGNvbnN0IHsgd2lkdGgsIGhlaWdodCwgZGVwdGggfSA9IG1ldGE7XG4gIGNvbnN0IHBsYW5lID0gd2lkdGggKiBoZWlnaHQ7XG4gIGlmICh2b3hlbHMubGVuZ3RoICE9PSBwbGFuZSAqIGRlcHRoKSB7XG4gICAgY29uc29sZS53YXJuKFxuICAgICAgYFtzcGF0aWFsXSB2b2x1bWUgaXMgJHt2b3hlbHMubGVuZ3RofSBieXRlcyBidXQgaXRzIGdlb21ldHJ5IGRlY2xhcmVzIGAgK1xuICAgICAgICBgJHtwbGFuZSAqIGRlcHRofSAoJHt3aWR0aH14JHtoZWlnaHR9eCR7ZGVwdGh9KSDigJQgbm90IHJlbmRlcmluZyBpdCBhcyBhbiBpbWFnZWAsXG4gICAgKTtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIGxldCBjYW52YXM6IEhUTUxDYW52YXNFbGVtZW50IHwgT2Zmc2NyZWVuQ2FudmFzO1xuICBpZiAodHlwZW9mIE9mZnNjcmVlbkNhbnZhcyAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICBjYW52YXMgPSBuZXcgT2Zmc2NyZWVuQ2FudmFzKHdpZHRoLCBoZWlnaHQpO1xuICB9IGVsc2Uge1xuICAgIGNhbnZhcyA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2NhbnZhcycpO1xuICAgIGNhbnZhcy53aWR0aCA9IHdpZHRoO1xuICAgIGNhbnZhcy5oZWlnaHQgPSBoZWlnaHQ7XG4gIH1cbiAgY29uc3QgY3R4ID0gY2FudmFzLmdldENvbnRleHQoJzJkJykgYXNcbiAgICB8IENhbnZhc1JlbmRlcmluZ0NvbnRleHQyRFxuICAgIHwgT2Zmc2NyZWVuQ2FudmFzUmVuZGVyaW5nQ29udGV4dDJEXG4gICAgfCBudWxsO1xuICBpZiAoIWN0eCkgdGhyb3cgbmV3IEVycm9yKCdbc3BhdGlhbF0gdm9sdW1lIHNsaWNlIGVuY29kZTogMkQgY29udGV4dCB1bmF2YWlsYWJsZScpO1xuXG4gIC8vIE9uZSBjYW52YXMgYW5kIG9uZSBSR0JBIGJ1ZmZlciBmb3IgdGhlIHdob2xlIHN0YWNrIOKAlCBhIGNhbnZhcyBwZXIgcGxhbmUgd291bGRcbiAgLy8gYmUgYGRlcHRoYCBHUFUtYmFja2VkIHN1cmZhY2VzIGFsaXZlIGF0IG9uY2UgZm9yIG5vIGdhaW4uXG4gIGNvbnN0IHJnYmEgPSBuZXcgVWludDhDbGFtcGVkQXJyYXkocGxhbmUgKiA0KTtcbiAgY29uc3QgdXJsczogc3RyaW5nW10gPSBbXTtcbiAgZm9yIChsZXQgeiA9IDA7IHogPCBkZXB0aDsgeisrKSB7XG4gICAgY29uc3QgYmFzZSA9IHogKiBwbGFuZTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHBsYW5lOyBpKyspIHtcbiAgICAgIGNvbnN0IHYgPSB2b3hlbHNbYmFzZSArIGldO1xuICAgICAgY29uc3QgbyA9IGkgKiA0O1xuICAgICAgcmdiYVtvXSA9IHY7XG4gICAgICByZ2JhW28gKyAxXSA9IHY7XG4gICAgICByZ2JhW28gKyAyXSA9IHY7XG4gICAgICByZ2JhW28gKyAzXSA9IDI1NTtcbiAgICB9XG4gICAgY3R4LnB1dEltYWdlRGF0YShuZXcgSW1hZ2VEYXRhKHJnYmEsIHdpZHRoLCBoZWlnaHQpLCAwLCAwKTtcbiAgICB1cmxzLnB1c2goVVJMLmNyZWF0ZU9iamVjdFVSTChhd2FpdCB0b1BuZ0Jsb2IoY2FudmFzKSkpO1xuICB9XG5cbiAgLy8gdm94ZWxTaXplIGlzIGluIHRoZSBPQlNFUlZBVElPTlMnIHVuaXRzLCBzbyBpdCBvbmx5IG1lYW5zIG1pY3JvbnMgb25jZVxuICAvLyBgbWljcm9uc1BlclVuaXRgIHNheXMgc28uIEFic2VudCwgdGhlIHVuaXQgaXMgdW5rbm93biBhbmQgbXBwIHN0YXlzIG51bGwg4oCUXG4gIC8vIHRoZSBjb250cmFjdCdzIHJ1bGUgZm9yIHRoZSBzY2FsZSBiYXI6IG5vIGJhciBiZWF0cyBhIGJhciB0aGF0IGxvb2tzIGxpa2UgYVxuICAvLyBtZWFzdXJlbWVudCBhbmQgaXMgbm90IG9uZS5cbiAgY29uc3QgcGVyVW5pdCA9IGRhdGFzZXQubWljcm9uc1BlclVuaXQ7XG4gIGNvbnN0IG1wcFggPSBwZXJVbml0ICE9IG51bGwgPyBtZXRhLnZveGVsU2l6ZVswXSAqIHBlclVuaXQgOiBudWxsO1xuICBjb25zdCBtcHBZID0gcGVyVW5pdCAhPSBudWxsID8gbWV0YS52b3hlbFNpemVbMV0gKiBwZXJVbml0IDogbnVsbDtcbiAgLy8gVGhlIHogY291bnRlcnBhcnQsIHdoaWNoIGlzIHdoYXQgY2FycmllcyB0aGUgdm9sdW1lJ3MgYW5pc290cm9weSBpbnRvIHRoZVxuICAvLyBWb2x1bWUgLyBJc29zdXJmYWNlIHZpZXdzOiB0aGV5IHJheW1hcmNoIHRoZSBpbWFnZSBzdGFjaywgYW5kIGEgc3RhY2sgd2l0aCBub1xuICAvLyBkZWNsYXJlZCBzbGljZSBzcGFjaW5nIGNhbiBvbmx5IGJlIGdpdmVuIGEgc2hhcGUtb25seSB3b3JsZCBib3guXG4gIGNvbnN0IG1wcFogPSBwZXJVbml0ICE9IG51bGwgPyBtZXRhLnZveGVsU2l6ZVsyXSAqIHBlclVuaXQgOiBudWxsO1xuXG4gIHJldHVybiB7XG4gICAgdXJscyxcbiAgICBpbmZvOiB7XG4gICAgICAvLyBBIHNjYWxhciBmaWVsZDogb25lIGJhbmQsIG5vIGNvbG91ciBvZiBpdHMgb3duIOKAlCB3aGljaCBpcyBhbHNvIHdoYXQgcHV0c1xuICAgICAgLy8gdGhlIHNjYWxhciBtb2RlcyAoSGVhdG1hcCAvIENvbnRvdXIgLyBTdXJmYWNlIC8gVm9sdW1lKSBsZWdpdGltYXRlbHkgb25cbiAgICAgIC8vIG9mZmVyIGZvciBhIGRhdGFzZXQgdGhhdCBoYXMgbm8gY2FtZXJhIGltYWdlIGFueXdoZXJlIGluIGl0LlxuICAgICAgaXNHcmF5c2NhbGU6IHRydWUsXG4gICAgICB0cnVlSW1hZ2VTaXplOiBbd2lkdGgsIGhlaWdodF0sXG4gICAgICB1cmxzLFxuICAgICAgaXNTdGFjazogZGVwdGggPiAxLFxuICAgICAgc2hvd1N0YWNrOiBmYWxzZSxcbiAgICAgIHNjYWxlUmF0aW86IHRydWUsXG4gICAgICAvLyBTdGFibGUgQU5EIHVuaXF1ZSBwZXIgZGF0YXNldDogdGhlIHNsaWNlLWJsb2IgY2FjaGUga2V5cyBvZmYgdGhlIGZpbGUgbmFtZSxcbiAgICAgIC8vIHNvIGEgbmFtZSB0aGF0IGNoYW5nZWQgcGVyIGxvYWQgd291bGQgcmUtZmV0Y2ggYSB2b2x1bWUgdGhhdCBuZXZlciBtb3ZlZCDigJRcbiAgICAgIC8vIGFuZCBhIG5hbWUgU0hBUkVEIGJ5IHR3byBkYXRhc2V0cyAob25seSBgaWRgIGlzIHJlcXVpcmVkIHRvIGJlIHVuaXF1ZSlcbiAgICAgIC8vIHdvdWxkIHNlcnZlIG9uZSBkYXRhc2V0J3Mgc2xpY2VzIGZvciB0aGUgb3RoZXIuXG4gICAgICBmaWxlTmFtZTogYCR7ZGF0YXNldC5uYW1lfSDCtyByZWZlcmVuY2Ugdm9sdW1lIFske2RhdGFzZXQuaWR9XWAsXG4gICAgICBpbWFnZU1ldGE6IFtcbiAgICAgICAgeyBjaGFubmVsQ291bnQ6IDEsIHJnYkNoYW5uZWxzOiAxLCB4OiB3aWR0aCwgeTogaGVpZ2h0LCB6OiBkZXB0aCwgbXBwWCwgbXBwWSwgbXBwWiB9LFxuICAgICAgXSxcbiAgICAgIC8vIEJsb2IgVVJMcyBhcmUgY29tcGxldGUgaW1hZ2VzOiBPU0QgbXVzdCBvcGVuIHRoZW0gZGlyZWN0bHksIG5vdCBhc2sgYSB0aWxlXG4gICAgICAvLyBzZXJ2ZXIgZm9yIGEgcHlyYW1pZCB0aGF0IGRvZXMgbm90IGV4aXN0LlxuICAgICAgdGlsZWQ6IGZhbHNlLFxuICAgICAgLy8gT3BlbiBtaWQtdm9sdW1lLiBUaGUgZmlyc3QgYW5kIGxhc3QgcGxhbmVzIG9mIGFuIGFuYXRvbWljYWwgdm9sdW1lIGFyZVxuICAgICAgLy8gb3V0c2lkZSB0aGUgc3BlY2ltZW4g4oCUIG9wZW5pbmcgb24gc2xpY2UgMCBzaG93cyBhbiBlbXB0eSBmcmFtZSBhbmQgcmVhZHNcbiAgICAgIC8vIGFzIGEgZmFpbGVkIGxvYWQuXG4gICAgICBpbml0aWFsWkluZGV4OiBkZXB0aCA+IDEgPyBkZXB0aCA+PiAxIDogdW5kZWZpbmVkLFxuICAgIH0sXG4gIH07XG59XG5cbi8qKlxuICogVGhlIGRhdGHihpJpbWFnZSBhZmZpbmUgYSByZWdpc3RlcmVkIHZvbHVtZSBpbXBsaWVzLlxuICpcbiAqIE9uY2UgdGhlIHZvbHVtZSBJUyB0aGUgaW1hZ2UgKHtAbGluayBidWlsZFZvbHVtZVN0YWNrSW1hZ2V9KSwgb25lIHNsaWNlIGlzIGFcbiAqIGB3aWR0aCB4IGhlaWdodGAgcGl4ZWwgZ3JpZCB3aG9zZSB2b3hlbCBgKGksIGopYCBjb3ZlcnNcbiAqIGBbaSAqIHZveGVsU2l6ZVswXSwgKGkgKyAxKSAqIHZveGVsU2l6ZVswXSlgIG9mIHRoZSBvYnNlcnZhdGlvbnMnIGNvb3JkaW5hdGVcbiAqIHNwYWNlIOKAlCB0aGUgY29udHJhY3QncyBydWxlIHRoYXQgdGhlIHZvbHVtZSdzIG5lYXIgY29ybmVyIHNpdHMgYXQgdGhlXG4gKiBjb29yZGluYXRlIG9yaWdpbi4gU28gb2JzZXJ2YXRpb25zIHJlYWNoIHBpeGVsIHNwYWNlIGJ5IGRpdmlkaW5nIG91dCB0aGUgdm94ZWxcbiAqIHNpemUsIGFuZCBub3RoaW5nIGhhcyB0byBtb3ZlLlxuICpcbiAqIFNoYXBlZCBhcyBhIGBTcGF0aWFsSW1hZ2VSZWZgIG9uIHB1cnBvc2U6IHRoZSBtYXJrZXIgbGF5ZXIgYW5kIHRoZSBST0lcbiAqIHNlbGVjdGlvbiB0aGVuIHRha2UgdGhlIFNBTUUgdHJhbnNmb3JtIHRoZXkgdGFrZSBmb3IgYSBkYXRhc2V0IHRoYXQgc2hpcHMgYVxuICogcmVhbCBgaW1hZ2VSZWZgLCBpbnN0ZWFkIG9mIGVhY2ggZ3Jvd2luZyBhIHZvbHVtZSBzcGVjaWFsIGNhc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB2b2x1bWVJbWFnZVJlZihcbiAgdm9sdW1lOiBTcGF0aWFsVm9sdW1lTWV0YSwgbWljcm9uc1BlclVuaXQ/OiBudW1iZXIsXG4pOiBTcGF0aWFsSW1hZ2VSZWYge1xuICBjb25zdCBbdngsIHZ5XSA9IHZvbHVtZS52b3hlbFNpemU7XG4gIHJldHVybiB7XG4gICAgc2NhbGU6IFsxIC8gdngsIDEgLyB2eV0sXG4gICAgdHJhbnNsYXRlOiBbMCwgMF0sXG4gICAgLi4uKG1pY3JvbnNQZXJVbml0ICE9IG51bGwgPyB7IG1wcFg6IHZ4ICogbWljcm9uc1BlclVuaXQsIG1wcFk6IHZ5ICogbWljcm9uc1BlclVuaXQgfSA6IHt9KSxcbiAgfTtcbn1cblxuLyoqIFRoZSB2b3hlbCBwbGFuZSBhIGNvb3JkaW5hdGUgZmFsbHMgaW4sIGNsYW1wZWQgaW50byB0aGUgdm9sdW1lLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNsaWNlSW5kZXhPZih6OiBudW1iZXIsIHZvbHVtZTogU3BhdGlhbFZvbHVtZU1ldGEpOiBudW1iZXIge1xuICBjb25zdCBrID0gTWF0aC5mbG9vcih6IC8gdm9sdW1lLnZveGVsU2l6ZVsyXSk7XG4gIHJldHVybiBNYXRoLm1heCgwLCBNYXRoLm1pbih2b2x1bWUuZGVwdGggLSAxLCBrKSk7XG59XG5cbi8qKlxuICogSW5kaWNlcyBvZiB0aGUgb2JzZXJ2YXRpb25zIHRoYXQgZmFsbCBpbiBvbmUgdm94ZWwgcGxhbmUgb2YgdGhlIHZvbHVtZS5cbiAqXG4gKiBBIDJEIHZpZXcgb2YgYSAzRCBkYXRhc2V0IHNob3dzIE9ORSBwbGFuZSwgc28gaXQgbXVzdCBkcmF3IG9uZSBwbGFuZSdzXG4gKiBvYnNlcnZhdGlvbnM6IHdpdGhvdXQgdGhpcyB0aGUgd2hvbGUgZGVwdGggb2YgdGhlIHNwZWNpbWVuIHBpbGVzIG9udG8gYSBzaW5nbGVcbiAqIHNlY3Rpb24gYW5kIHJlYWRzIGFzIGEgc29saWQgc21lYXIgcmF0aGVyIHRoYW4gYSBzZWN0aW9uJ3MgY2VsbHMuIFRoZSBwbGFuZSBpc1xuICogYSBzbGFiIGB2b3hlbFNpemVbMl1gIHRoaWNrLCB3aGljaCBpcyBleGFjdGx5IHRoZSBzYW1wbGluZyB0aGUgdm9sdW1lIGl0c2VsZlxuICogaGFzIOKAlCBmb3Igc2VyaWFsIHNlY3Rpb25zIHJlZ2lzdGVyZWQgaW50byBhIGNvbW1vbiBmcmFtZSwgb25lIHNsYWIgaXMgb25lXG4gKiBzZWN0aW9uLlxuICpcbiAqIEV2ZXJ5IG9ic2VydmF0aW9uIGlzIGluIHRoZSBwbGFuZSB3aGVuIHRoZSBkYXRhc2V0IGNhcnJpZXMgbm8gejogYSBnZW51aW5lbHlcbiAqIGZsYXQgZGF0YXNldCBvdmVyIGEgdm9sdW1lIGhhcyBub3RoaW5nIHRvIGZpbHRlciBvbi5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG9ic2VydmF0aW9uc0luU2xpY2UoXG4gIG9iczogU3BhdGlhbE9ic2VydmF0aW9ucywgdm9sdW1lOiBTcGF0aWFsVm9sdW1lTWV0YSwgc2xpY2U6IG51bWJlcixcbik6IFVpbnQzMkFycmF5IHtcbiAgY29uc3QgeiA9IG9icy56O1xuICBpZiAoIXopIHJldHVybiBVaW50MzJBcnJheS5mcm9tKHsgbGVuZ3RoOiBvYnMuY291bnQgfSwgKF8sIGkpID0+IGkpO1xuICBjb25zdCBvdXQgPSBuZXcgVWludDMyQXJyYXkob2JzLmNvdW50KTtcbiAgbGV0IG4gPSAwO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IG9icy5jb3VudDsgaSsrKSB7XG4gICAgaWYgKHNsaWNlSW5kZXhPZih6W2ldLCB2b2x1bWUpID09PSBzbGljZSkgb3V0W24rK10gPSBpO1xuICB9XG4gIHJldHVybiBvdXQuc3ViYXJyYXkoMCwgbik7XG59XG4iXX0=
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for the t-SNE Web Worker, isolated in its own module so the `import.meta.url`
|
|
3
|
+
* reference — which the bundler needs in order to locate the worker — is never pulled
|
|
4
|
+
* into the ts-jest CommonJS compile of anything that uses it. Unit tests `jest.mock` this
|
|
5
|
+
* module, or pass their own factory. Same arrangement as `mask-worker.ts`.
|
|
6
|
+
*/
|
|
7
|
+
export function createTsneWorker() {
|
|
8
|
+
return new Worker(new URL('./tsne.worker', import.meta.url), { type: 'module' });
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHNuZS13b3JrZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3NwYXRpYWwvdHNuZS13b3JrZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsZ0JBQWdCO0lBQzlCLE9BQU8sSUFBSSxNQUFNLENBQUMsSUFBSSxHQUFHLENBQUMsZUFBZSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQztBQUNuRixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBGYWN0b3J5IGZvciB0aGUgdC1TTkUgV2ViIFdvcmtlciwgaXNvbGF0ZWQgaW4gaXRzIG93biBtb2R1bGUgc28gdGhlIGBpbXBvcnQubWV0YS51cmxgXG4gKiByZWZlcmVuY2Ug4oCUIHdoaWNoIHRoZSBidW5kbGVyIG5lZWRzIGluIG9yZGVyIHRvIGxvY2F0ZSB0aGUgd29ya2VyIOKAlCBpcyBuZXZlciBwdWxsZWRcbiAqIGludG8gdGhlIHRzLWplc3QgQ29tbW9uSlMgY29tcGlsZSBvZiBhbnl0aGluZyB0aGF0IHVzZXMgaXQuIFVuaXQgdGVzdHMgYGplc3QubW9ja2AgdGhpc1xuICogbW9kdWxlLCBvciBwYXNzIHRoZWlyIG93biBmYWN0b3J5LiBTYW1lIGFycmFuZ2VtZW50IGFzIGBtYXNrLXdvcmtlci50c2AuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVUc25lV29ya2VyKCk6IFdvcmtlciB7XG4gIHJldHVybiBuZXcgV29ya2VyKG5ldyBVUkwoJy4vdHNuZS53b3JrZXInLCBpbXBvcnQubWV0YS51cmwpLCB7IHR5cGU6ICdtb2R1bGUnIH0pO1xufVxuIl19
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How finely a drawn shape's vertices may be placed in world space.
|
|
3
|
+
*
|
|
4
|
+
* Region geometry has always been stored in whole world units, and for an image that is
|
|
5
|
+
* exactly right: world units ARE pixels, a region should align to them, and integers keep
|
|
6
|
+
* the stored geometry exact and tidy.
|
|
7
|
+
*
|
|
8
|
+
* It is wrong for a dataset whose coordinates are not pixels. seqFISH's observations span
|
|
9
|
+
* about 5 x 7 units in total, so snapping vertices to integers leaves roughly SIX BY EIGHT
|
|
10
|
+
* distinct positions in the whole sample — every ROI collapses to the same handful of
|
|
11
|
+
* boxes and drawing a fine shape is not possible at any zoom. Nothing errors; the tool
|
|
12
|
+
* simply cannot express what the user is drawing.
|
|
13
|
+
*
|
|
14
|
+
* So the step is chosen from the world's own extent rather than assumed to be one. An
|
|
15
|
+
* image keeps integers, because its extent is thousands of units and a finer step would
|
|
16
|
+
* buy nothing; a unitless dataset gets a step fine enough to draw with.
|
|
17
|
+
*
|
|
18
|
+
* Pure — no renderer, no store — so both region overlays can share one answer, and the
|
|
19
|
+
* napari and OSD paths cannot drift into disagreeing about what a vertex may be.
|
|
20
|
+
*/
|
|
21
|
+
/** One world unit: the step for a world whose units are image pixels. */
|
|
22
|
+
export const PIXEL_WORLD_QUANTUM = 1;
|
|
23
|
+
/**
|
|
24
|
+
* Distinct positions a drawn shape should be able to reach across the world's extent.
|
|
25
|
+
*
|
|
26
|
+
* Set by what a person can actually place with a pointer, not by float precision: a few
|
|
27
|
+
* thousand steps across the sample is finer than any screen it will be drawn on, so the
|
|
28
|
+
* limit stops being the grid and becomes the hand.
|
|
29
|
+
*/
|
|
30
|
+
export const WORLD_GRID_STEPS = 4096;
|
|
31
|
+
/**
|
|
32
|
+
* The step size for a world of this extent, for a world whose units are NOT pixels.
|
|
33
|
+
*
|
|
34
|
+
* The caller decides which world it has, because the extent cannot say: a 2,000 unit world
|
|
35
|
+
* is a small slide if the units are pixels and an enormous one if they are microns, and
|
|
36
|
+
* only the caller knows which. An image keeps {@link PIXEL_WORLD_QUANTUM} by never asking.
|
|
37
|
+
*
|
|
38
|
+
* Never returns anything coarser than {@link PIXEL_WORLD_QUANTUM}. A non-pixel world can
|
|
39
|
+
* still be large — the Allen atlas is ~11,000 microns across — and there a finer step than
|
|
40
|
+
* one unit buys nothing.
|
|
41
|
+
*
|
|
42
|
+
* Rounded DOWN to a power of ten, so the step is a number a person reading the stored
|
|
43
|
+
* geometry can recognise — 0.001 rather than 0.0017044 — and so it does not shift when the
|
|
44
|
+
* extent changes slightly between datasets.
|
|
45
|
+
*
|
|
46
|
+
* A non-finite or empty extent falls back to the pixel grid: an unknown world is treated
|
|
47
|
+
* as the conservative, established case rather than given an invented precision.
|
|
48
|
+
*/
|
|
49
|
+
export function worldQuantumForExtent(spanX, spanY) {
|
|
50
|
+
const span = Math.max(spanX, spanY);
|
|
51
|
+
if (!Number.isFinite(span) || span <= 0)
|
|
52
|
+
return PIXEL_WORLD_QUANTUM;
|
|
53
|
+
const wanted = span / WORLD_GRID_STEPS;
|
|
54
|
+
if (wanted >= PIXEL_WORLD_QUANTUM)
|
|
55
|
+
return PIXEL_WORLD_QUANTUM;
|
|
56
|
+
return 10 ** Math.floor(Math.log10(wanted));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Snap one world coordinate to the grid.
|
|
60
|
+
*
|
|
61
|
+
* A step of zero or less means no snapping — the caller keeps full float precision.
|
|
62
|
+
* Division and multiplication by the step leaves a float artefact (0.001 * 3 is not
|
|
63
|
+
* exactly 0.003), so the result is rounded to the step's own number of decimals; without
|
|
64
|
+
* that, "tidy" geometry acquires a trail of digits the grid was supposed to remove.
|
|
65
|
+
*/
|
|
66
|
+
export function snapToWorldGrid(value, quantum) {
|
|
67
|
+
if (!Number.isFinite(value))
|
|
68
|
+
return value;
|
|
69
|
+
if (!Number.isFinite(quantum) || quantum <= 0)
|
|
70
|
+
return value;
|
|
71
|
+
if (quantum === PIXEL_WORLD_QUANTUM)
|
|
72
|
+
return Math.round(value);
|
|
73
|
+
const snapped = Math.round(value / quantum) * quantum;
|
|
74
|
+
const decimals = Math.max(0, Math.min(20, -Math.floor(Math.log10(quantum))));
|
|
75
|
+
return Number(snapped.toFixed(decimals));
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ybGQtZ3JpZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvc3BhdGlhbC93b3JsZC1ncmlkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBbUJHO0FBRUgseUVBQXlFO0FBQ3pFLE1BQU0sQ0FBQyxNQUFNLG1CQUFtQixHQUFHLENBQUMsQ0FBQztBQUVyQzs7Ozs7O0dBTUc7QUFDSCxNQUFNLENBQUMsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLENBQUM7QUFFckM7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJHO0FBQ0gsTUFBTSxVQUFVLHFCQUFxQixDQUFDLEtBQWEsRUFBRSxLQUFhO0lBQ2hFLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ3BDLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLElBQUksSUFBSSxDQUFDO1FBQUUsT0FBTyxtQkFBbUIsQ0FBQztJQUNwRSxNQUFNLE1BQU0sR0FBRyxJQUFJLEdBQUcsZ0JBQWdCLENBQUM7SUFDdkMsSUFBSSxNQUFNLElBQUksbUJBQW1CO1FBQUUsT0FBTyxtQkFBbUIsQ0FBQztJQUM5RCxPQUFPLEVBQUUsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztBQUM5QyxDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILE1BQU0sVUFBVSxlQUFlLENBQUMsS0FBYSxFQUFFLE9BQWU7SUFDNUQsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDMUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLElBQUksT0FBTyxJQUFJLENBQUM7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUM1RCxJQUFJLE9BQU8sS0FBSyxtQkFBbUI7UUFBRSxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDOUQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLEdBQUcsT0FBTyxDQUFDLEdBQUcsT0FBTyxDQUFDO0lBQ3RELE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzdFLE9BQU8sTUFBTSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztBQUMzQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBIb3cgZmluZWx5IGEgZHJhd24gc2hhcGUncyB2ZXJ0aWNlcyBtYXkgYmUgcGxhY2VkIGluIHdvcmxkIHNwYWNlLlxuICpcbiAqIFJlZ2lvbiBnZW9tZXRyeSBoYXMgYWx3YXlzIGJlZW4gc3RvcmVkIGluIHdob2xlIHdvcmxkIHVuaXRzLCBhbmQgZm9yIGFuIGltYWdlIHRoYXQgaXNcbiAqIGV4YWN0bHkgcmlnaHQ6IHdvcmxkIHVuaXRzIEFSRSBwaXhlbHMsIGEgcmVnaW9uIHNob3VsZCBhbGlnbiB0byB0aGVtLCBhbmQgaW50ZWdlcnMga2VlcFxuICogdGhlIHN0b3JlZCBnZW9tZXRyeSBleGFjdCBhbmQgdGlkeS5cbiAqXG4gKiBJdCBpcyB3cm9uZyBmb3IgYSBkYXRhc2V0IHdob3NlIGNvb3JkaW5hdGVzIGFyZSBub3QgcGl4ZWxzLiBzZXFGSVNIJ3Mgb2JzZXJ2YXRpb25zIHNwYW5cbiAqIGFib3V0IDUgeCA3IHVuaXRzIGluIHRvdGFsLCBzbyBzbmFwcGluZyB2ZXJ0aWNlcyB0byBpbnRlZ2VycyBsZWF2ZXMgcm91Z2hseSBTSVggQlkgRUlHSFRcbiAqIGRpc3RpbmN0IHBvc2l0aW9ucyBpbiB0aGUgd2hvbGUgc2FtcGxlIOKAlCBldmVyeSBST0kgY29sbGFwc2VzIHRvIHRoZSBzYW1lIGhhbmRmdWwgb2ZcbiAqIGJveGVzIGFuZCBkcmF3aW5nIGEgZmluZSBzaGFwZSBpcyBub3QgcG9zc2libGUgYXQgYW55IHpvb20uIE5vdGhpbmcgZXJyb3JzOyB0aGUgdG9vbFxuICogc2ltcGx5IGNhbm5vdCBleHByZXNzIHdoYXQgdGhlIHVzZXIgaXMgZHJhd2luZy5cbiAqXG4gKiBTbyB0aGUgc3RlcCBpcyBjaG9zZW4gZnJvbSB0aGUgd29ybGQncyBvd24gZXh0ZW50IHJhdGhlciB0aGFuIGFzc3VtZWQgdG8gYmUgb25lLiBBblxuICogaW1hZ2Uga2VlcHMgaW50ZWdlcnMsIGJlY2F1c2UgaXRzIGV4dGVudCBpcyB0aG91c2FuZHMgb2YgdW5pdHMgYW5kIGEgZmluZXIgc3RlcCB3b3VsZFxuICogYnV5IG5vdGhpbmc7IGEgdW5pdGxlc3MgZGF0YXNldCBnZXRzIGEgc3RlcCBmaW5lIGVub3VnaCB0byBkcmF3IHdpdGguXG4gKlxuICogUHVyZSDigJQgbm8gcmVuZGVyZXIsIG5vIHN0b3JlIOKAlCBzbyBib3RoIHJlZ2lvbiBvdmVybGF5cyBjYW4gc2hhcmUgb25lIGFuc3dlciwgYW5kIHRoZVxuICogbmFwYXJpIGFuZCBPU0QgcGF0aHMgY2Fubm90IGRyaWZ0IGludG8gZGlzYWdyZWVpbmcgYWJvdXQgd2hhdCBhIHZlcnRleCBtYXkgYmUuXG4gKi9cblxuLyoqIE9uZSB3b3JsZCB1bml0OiB0aGUgc3RlcCBmb3IgYSB3b3JsZCB3aG9zZSB1bml0cyBhcmUgaW1hZ2UgcGl4ZWxzLiAqL1xuZXhwb3J0IGNvbnN0IFBJWEVMX1dPUkxEX1FVQU5UVU0gPSAxO1xuXG4vKipcbiAqIERpc3RpbmN0IHBvc2l0aW9ucyBhIGRyYXduIHNoYXBlIHNob3VsZCBiZSBhYmxlIHRvIHJlYWNoIGFjcm9zcyB0aGUgd29ybGQncyBleHRlbnQuXG4gKlxuICogU2V0IGJ5IHdoYXQgYSBwZXJzb24gY2FuIGFjdHVhbGx5IHBsYWNlIHdpdGggYSBwb2ludGVyLCBub3QgYnkgZmxvYXQgcHJlY2lzaW9uOiBhIGZld1xuICogdGhvdXNhbmQgc3RlcHMgYWNyb3NzIHRoZSBzYW1wbGUgaXMgZmluZXIgdGhhbiBhbnkgc2NyZWVuIGl0IHdpbGwgYmUgZHJhd24gb24sIHNvIHRoZVxuICogbGltaXQgc3RvcHMgYmVpbmcgdGhlIGdyaWQgYW5kIGJlY29tZXMgdGhlIGhhbmQuXG4gKi9cbmV4cG9ydCBjb25zdCBXT1JMRF9HUklEX1NURVBTID0gNDA5NjtcblxuLyoqXG4gKiBUaGUgc3RlcCBzaXplIGZvciBhIHdvcmxkIG9mIHRoaXMgZXh0ZW50LCBmb3IgYSB3b3JsZCB3aG9zZSB1bml0cyBhcmUgTk9UIHBpeGVscy5cbiAqXG4gKiBUaGUgY2FsbGVyIGRlY2lkZXMgd2hpY2ggd29ybGQgaXQgaGFzLCBiZWNhdXNlIHRoZSBleHRlbnQgY2Fubm90IHNheTogYSAyLDAwMCB1bml0IHdvcmxkXG4gKiBpcyBhIHNtYWxsIHNsaWRlIGlmIHRoZSB1bml0cyBhcmUgcGl4ZWxzIGFuZCBhbiBlbm9ybW91cyBvbmUgaWYgdGhleSBhcmUgbWljcm9ucywgYW5kXG4gKiBvbmx5IHRoZSBjYWxsZXIga25vd3Mgd2hpY2guIEFuIGltYWdlIGtlZXBzIHtAbGluayBQSVhFTF9XT1JMRF9RVUFOVFVNfSBieSBuZXZlciBhc2tpbmcuXG4gKlxuICogTmV2ZXIgcmV0dXJucyBhbnl0aGluZyBjb2Fyc2VyIHRoYW4ge0BsaW5rIFBJWEVMX1dPUkxEX1FVQU5UVU19LiBBIG5vbi1waXhlbCB3b3JsZCBjYW5cbiAqIHN0aWxsIGJlIGxhcmdlIOKAlCB0aGUgQWxsZW4gYXRsYXMgaXMgfjExLDAwMCBtaWNyb25zIGFjcm9zcyDigJQgYW5kIHRoZXJlIGEgZmluZXIgc3RlcCB0aGFuXG4gKiBvbmUgdW5pdCBidXlzIG5vdGhpbmcuXG4gKlxuICogUm91bmRlZCBET1dOIHRvIGEgcG93ZXIgb2YgdGVuLCBzbyB0aGUgc3RlcCBpcyBhIG51bWJlciBhIHBlcnNvbiByZWFkaW5nIHRoZSBzdG9yZWRcbiAqIGdlb21ldHJ5IGNhbiByZWNvZ25pc2Ug4oCUIDAuMDAxIHJhdGhlciB0aGFuIDAuMDAxNzA0NCDigJQgYW5kIHNvIGl0IGRvZXMgbm90IHNoaWZ0IHdoZW4gdGhlXG4gKiBleHRlbnQgY2hhbmdlcyBzbGlnaHRseSBiZXR3ZWVuIGRhdGFzZXRzLlxuICpcbiAqIEEgbm9uLWZpbml0ZSBvciBlbXB0eSBleHRlbnQgZmFsbHMgYmFjayB0byB0aGUgcGl4ZWwgZ3JpZDogYW4gdW5rbm93biB3b3JsZCBpcyB0cmVhdGVkXG4gKiBhcyB0aGUgY29uc2VydmF0aXZlLCBlc3RhYmxpc2hlZCBjYXNlIHJhdGhlciB0aGFuIGdpdmVuIGFuIGludmVudGVkIHByZWNpc2lvbi5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHdvcmxkUXVhbnR1bUZvckV4dGVudChzcGFuWDogbnVtYmVyLCBzcGFuWTogbnVtYmVyKTogbnVtYmVyIHtcbiAgY29uc3Qgc3BhbiA9IE1hdGgubWF4KHNwYW5YLCBzcGFuWSk7XG4gIGlmICghTnVtYmVyLmlzRmluaXRlKHNwYW4pIHx8IHNwYW4gPD0gMCkgcmV0dXJuIFBJWEVMX1dPUkxEX1FVQU5UVU07XG4gIGNvbnN0IHdhbnRlZCA9IHNwYW4gLyBXT1JMRF9HUklEX1NURVBTO1xuICBpZiAod2FudGVkID49IFBJWEVMX1dPUkxEX1FVQU5UVU0pIHJldHVybiBQSVhFTF9XT1JMRF9RVUFOVFVNO1xuICByZXR1cm4gMTAgKiogTWF0aC5mbG9vcihNYXRoLmxvZzEwKHdhbnRlZCkpO1xufVxuXG4vKipcbiAqIFNuYXAgb25lIHdvcmxkIGNvb3JkaW5hdGUgdG8gdGhlIGdyaWQuXG4gKlxuICogQSBzdGVwIG9mIHplcm8gb3IgbGVzcyBtZWFucyBubyBzbmFwcGluZyDigJQgdGhlIGNhbGxlciBrZWVwcyBmdWxsIGZsb2F0IHByZWNpc2lvbi5cbiAqIERpdmlzaW9uIGFuZCBtdWx0aXBsaWNhdGlvbiBieSB0aGUgc3RlcCBsZWF2ZXMgYSBmbG9hdCBhcnRlZmFjdCAoMC4wMDEgKiAzIGlzIG5vdFxuICogZXhhY3RseSAwLjAwMyksIHNvIHRoZSByZXN1bHQgaXMgcm91bmRlZCB0byB0aGUgc3RlcCdzIG93biBudW1iZXIgb2YgZGVjaW1hbHM7IHdpdGhvdXRcbiAqIHRoYXQsIFwidGlkeVwiIGdlb21ldHJ5IGFjcXVpcmVzIGEgdHJhaWwgb2YgZGlnaXRzIHRoZSBncmlkIHdhcyBzdXBwb3NlZCB0byByZW1vdmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzbmFwVG9Xb3JsZEdyaWQodmFsdWU6IG51bWJlciwgcXVhbnR1bTogbnVtYmVyKTogbnVtYmVyIHtcbiAgaWYgKCFOdW1iZXIuaXNGaW5pdGUodmFsdWUpKSByZXR1cm4gdmFsdWU7XG4gIGlmICghTnVtYmVyLmlzRmluaXRlKHF1YW50dW0pIHx8IHF1YW50dW0gPD0gMCkgcmV0dXJuIHZhbHVlO1xuICBpZiAocXVhbnR1bSA9PT0gUElYRUxfV09STERfUVVBTlRVTSkgcmV0dXJuIE1hdGgucm91bmQodmFsdWUpO1xuICBjb25zdCBzbmFwcGVkID0gTWF0aC5yb3VuZCh2YWx1ZSAvIHF1YW50dW0pICogcXVhbnR1bTtcbiAgY29uc3QgZGVjaW1hbHMgPSBNYXRoLm1heCgwLCBNYXRoLm1pbigyMCwgLU1hdGguZmxvb3IoTWF0aC5sb2cxMChxdWFudHVtKSkpKTtcbiAgcmV0dXJuIE51bWJlcihzbmFwcGVkLnRvRml4ZWQoZGVjaW1hbHMpKTtcbn1cbiJdfQ==
|