arrayview 0.46.2__py3-none-any.whl → 0.47.0__py3-none-any.whl
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.
- arrayview/_routes_loading.py +2 -0
- arrayview/_viewer.html +287 -74
- arrayview/_vscode_extension.py +1 -1
- arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.46.2.dist-info → arrayview-0.47.0.dist-info}/METADATA +1 -1
- {arrayview-0.46.2.dist-info → arrayview-0.47.0.dist-info}/RECORD +9 -9
- {arrayview-0.46.2.dist-info → arrayview-0.47.0.dist-info}/WHEEL +0 -0
- {arrayview-0.46.2.dist-info → arrayview-0.47.0.dist-info}/entry_points.txt +0 -0
- {arrayview-0.46.2.dist-info → arrayview-0.47.0.dist-info}/licenses/LICENSE +0 -0
arrayview/_routes_loading.py
CHANGED
|
@@ -96,10 +96,12 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
|
|
|
96
96
|
result = []
|
|
97
97
|
for session in SESSIONS.values():
|
|
98
98
|
dtype_str = str(getattr(session.data, "dtype", "unknown"))
|
|
99
|
+
code = getattr(session, "code_source", None) or {}
|
|
99
100
|
result.append(
|
|
100
101
|
{
|
|
101
102
|
"sid": session.sid,
|
|
102
103
|
"name": session.name,
|
|
104
|
+
"var": code.get("name") or None,
|
|
103
105
|
"shape": [int(x) for x in session.shape],
|
|
104
106
|
"filepath": session.filepath,
|
|
105
107
|
"dtype": dtype_str,
|
arrayview/_viewer.html
CHANGED
|
@@ -236,22 +236,24 @@
|
|
|
236
236
|
border-radius: 10px;
|
|
237
237
|
overflow: hidden;
|
|
238
238
|
}
|
|
239
|
-
/* Orientation
|
|
240
|
-
small
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
239
|
+
/* Orientation cross — hovering the DimBar (or pressing r / t) shows a
|
|
240
|
+
small axis cross in the origin corner of every image pane, one arrow
|
|
241
|
+
per in-plane direction with its letter. See _showOrientEdgeHighlight().
|
|
242
|
+
Yellow with a black outline so it reads on bright and dark image
|
|
243
|
+
content alike. */
|
|
244
|
+
.orient-cross {
|
|
245
|
+
position: fixed; left: 0; top: 0; width: 64px; height: 64px;
|
|
246
|
+
transform-origin: 0 0; pointer-events: none; z-index: 60;
|
|
245
247
|
opacity: 0; transition: opacity 0.15s ease;
|
|
246
248
|
}
|
|
247
|
-
.orient-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
stroke:
|
|
249
|
+
.orient-cross.showing { opacity: 1; }
|
|
250
|
+
.orient-cross .oc-halo { stroke: rgba(0, 0, 0, 0.7); stroke-width: 4px; fill: none; stroke-linecap: round; }
|
|
251
|
+
.orient-cross .oc-ln { stroke: var(--active-dim); stroke-width: 1.5px; fill: none; stroke-linecap: round; }
|
|
252
|
+
.orient-cross .oc-hd { fill: var(--active-dim); stroke: rgba(0, 0, 0, 0.7); stroke-width: 2.5px; stroke-linejoin: round; paint-order: stroke; }
|
|
253
|
+
.orient-cross text {
|
|
254
|
+
font-family: 'SF Mono', ui-monospace, 'Cascadia Code', 'JetBrains Mono', monospace;
|
|
255
|
+
font-size: 12px; font-weight: 600; fill: var(--active-dim);
|
|
256
|
+
stroke: rgba(0, 0, 0, 0.7); stroke-width: 3px; stroke-linejoin: round; paint-order: stroke;
|
|
255
257
|
}
|
|
256
258
|
#slim-cb-wrap {
|
|
257
259
|
text-align: center;
|
|
@@ -2087,6 +2089,18 @@
|
|
|
2087
2089
|
.drop-action:hover:not(:disabled) { color: var(--active-dim); background: var(--surface-2); }
|
|
2088
2090
|
.drop-action:disabled { color: var(--muted); cursor: default; opacity: 0.5; }
|
|
2089
2091
|
.drop-action-reason { display: block; margin-top: 2px; font-size: 9px; color: var(--muted); }
|
|
2092
|
+
#compare-with-menu {
|
|
2093
|
+
display: none; position: fixed; z-index: 70; left: 50%; top: 50%;
|
|
2094
|
+
transform: translate(-50%, -50%); width: min(340px, calc(100vw - 24px));
|
|
2095
|
+
max-height: calc(100vh - 48px); overflow-y: auto;
|
|
2096
|
+
padding: 8px; background: var(--surface); border: 1px solid var(--border);
|
|
2097
|
+
border-radius: var(--radius-lg); color: var(--text); font: 11px var(--mono);
|
|
2098
|
+
backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur);
|
|
2099
|
+
}
|
|
2100
|
+
#compare-with-menu.visible { display: block; }
|
|
2101
|
+
#compare-with-title { padding: 4px 6px 2px; }
|
|
2102
|
+
#compare-with-summary { color: var(--muted); padding: 0 6px 7px; font-size: 10px; }
|
|
2103
|
+
.compare-with-key { color: var(--muted); margin-right: 8px; }
|
|
2090
2104
|
.mode-badge {
|
|
2091
2105
|
display: inline-block; font-size: 0.62em; font-weight: 600;
|
|
2092
2106
|
letter-spacing: 0.08em; padding: 2px 6px; border-radius: 4px;
|
|
@@ -4247,6 +4261,13 @@
|
|
|
4247
4261
|
/* qMRI 3-plane multiview: one map per row (3 planes) + a vertical colorbar on the right */
|
|
4248
4262
|
.qmri-mv-row { display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 14px; }
|
|
4249
4263
|
.qmri-mv-row .qv-canvas-clip { align-items: center; justify-content: center; }
|
|
4264
|
+
/* qMRI ortho: the 3x3 panes touch and read as one block, so only the
|
|
4265
|
+
outer corners of the block are rounded. */
|
|
4266
|
+
body.rounded-panes .qmri-mv-row .qv-canvas-clip { border-radius: 0; }
|
|
4267
|
+
body.rounded-panes .qmri-mv-row .qv-canvas-clip.qmv-tl { border-top-left-radius: 10px; }
|
|
4268
|
+
body.rounded-panes .qmri-mv-row .qv-canvas-clip.qmv-tr { border-top-right-radius: 10px; }
|
|
4269
|
+
body.rounded-panes .qmri-mv-row .qv-canvas-clip.qmv-bl { border-bottom-left-radius: 10px; }
|
|
4270
|
+
body.rounded-panes .qmri-mv-row .qv-canvas-clip.qmv-br { border-bottom-right-radius: 10px; }
|
|
4250
4271
|
.qmri-mv-row > .qv-row { flex: 0 1 auto; }
|
|
4251
4272
|
.qmri-mv-cb { display: flex; align-items: center; flex-shrink: 0; }
|
|
4252
4273
|
.qmri-mv-vcb-island { padding: 4px 8px; margin-top: 0; flex-direction: column !important; gap: 3px; }
|
|
@@ -5741,6 +5762,11 @@
|
|
|
5741
5762
|
<div id="drop-action-summary"></div>
|
|
5742
5763
|
<div id="drop-action-list"></div>
|
|
5743
5764
|
</div>
|
|
5765
|
+
<div id="compare-with-menu" aria-hidden="true">
|
|
5766
|
+
<div id="compare-with-title">Compare with…</div>
|
|
5767
|
+
<div id="compare-with-summary"></div>
|
|
5768
|
+
<div id="compare-with-list"></div>
|
|
5769
|
+
</div>
|
|
5744
5770
|
<div id="help-overlay">
|
|
5745
5771
|
<div id="help-box">
|
|
5746
5772
|
<div id="help-tabs"></div>
|
|
@@ -7507,6 +7533,7 @@
|
|
|
7507
7533
|
let _cmvCrosshairAnim = null; // animation frame id for compare-mv crosshair fade
|
|
7508
7534
|
let _qmvCrosshairAlpha = 0; // current crosshair opacity for qMRI multiview
|
|
7509
7535
|
let _qmvCrosshairTarget = 0; // target opacity for qMRI multiview
|
|
7536
|
+
let _qmvCrosshairMap = null; // map row the lines are limited to (click-hold); null = every row
|
|
7510
7537
|
let _qmvCrosshairAnim = null; // animation frame id for qMRI-mv crosshair fade
|
|
7511
7538
|
let _qmvScrollHintTimer = null; // fade-out timer after scrolling a qMRI-mv pane
|
|
7512
7539
|
let _qmvScrollHintDir = -1; // plane being scrolled (its own lines stay hidden)
|
|
@@ -20994,6 +21021,20 @@
|
|
|
20994
21021
|
return `<span class="${cls}">${_dimIndexHtml(shownValue, maxValue)}</span>`;
|
|
20995
21022
|
}
|
|
20996
21023
|
|
|
21024
|
+
// Axis letters follow the data, not the screen: the array axis that
|
|
21025
|
+
// started out as x keeps the letter x after t / r, so the DimBar and the
|
|
21026
|
+
// orientation cross both show which way each axis now runs. Remembered
|
|
21027
|
+
// per pair of displayed dims; picking different dims starts over.
|
|
21028
|
+
let _axisIdent = null;
|
|
21029
|
+
function _axisLetter(d) {
|
|
21030
|
+
const key = Math.min(dim_x, dim_y) + ',' + Math.max(dim_x, dim_y);
|
|
21031
|
+
if (!_axisIdent || _axisIdent.key !== key) {
|
|
21032
|
+
_axisIdent = { key, letters: { [dim_x]: 'x', [dim_y]: 'y' } };
|
|
21033
|
+
}
|
|
21034
|
+
if (d === dim_z) return 'z';
|
|
21035
|
+
return _axisIdent.letters[d] || (d === dim_x ? 'x' : 'y');
|
|
21036
|
+
}
|
|
21037
|
+
|
|
20997
21038
|
function renderInfo() {
|
|
20998
21039
|
_ensureQmriActiveDimVisible();
|
|
20999
21040
|
_syncOverlayPaletteSlice();
|
|
@@ -21046,13 +21087,13 @@
|
|
|
21046
21087
|
}
|
|
21047
21088
|
if (i === dim_x) {
|
|
21048
21089
|
const sign = flip_x ? '<span class="dim-flip-sign">-</span>' : '';
|
|
21049
|
-
const inner = sign +
|
|
21090
|
+
const inner = sign + `<span class="dim-axis-letter">${_axisLetter(i)}</span>` + `<span class="dim-slash">/</span><span class="dim-size" id="dim-x-size">${sizeOf(i)}</span>`;
|
|
21050
21091
|
return (active || playing) ? `<span class="spatial-dim ${dimCls} dim-label" ${d}>${inner}</span>`
|
|
21051
21092
|
: `<span class="spatial-dim dim-label" ${d}>${inner}</span>`;
|
|
21052
21093
|
}
|
|
21053
21094
|
if (i === dim_y) {
|
|
21054
21095
|
const sign = flip_y ? '<span class="dim-flip-sign">-</span>' : '';
|
|
21055
|
-
const inner = sign +
|
|
21096
|
+
const inner = sign + `<span class="dim-axis-letter">${_axisLetter(i)}</span>` + `<span class="dim-slash">/</span><span class="dim-size" id="dim-y-size">${sizeOf(i)}</span>`;
|
|
21056
21097
|
return (active || playing) ? `<span class="spatial-dim ${dimCls} dim-label" ${d}>${inner}</span>`
|
|
21057
21098
|
: `<span class="spatial-dim dim-label" ${d}>${inner}</span>`;
|
|
21058
21099
|
}
|
|
@@ -21272,60 +21313,122 @@
|
|
|
21272
21313
|
if (typeof obliqueVecs !== 'undefined' && obliqueVecs) return false;
|
|
21273
21314
|
return true;
|
|
21274
21315
|
}
|
|
21275
|
-
|
|
21276
|
-
|
|
21277
|
-
|
|
21278
|
-
|
|
21316
|
+
const _ORIENT_CROSS_BOX = 64;
|
|
21317
|
+
const _ORIENT_CROSS_MARGIN = 8;
|
|
21318
|
+
let _orientCrossEls = [];
|
|
21319
|
+
let _orientShown = false;
|
|
21320
|
+
let _orientHovering = false;
|
|
21321
|
+
let _orientRaf = 0;
|
|
21322
|
+
let _orientFlashTimer = 0;
|
|
21323
|
+
// Every image pane in the current mode: its canvas, the letters of its
|
|
21324
|
+
// horizontal / vertical dims, and which way each axis is flipped.
|
|
21325
|
+
function _orientPanes() {
|
|
21326
|
+
const out = [];
|
|
21327
|
+
const add = (cv, lx, ly, fx, fy) => { if (cv) out.push({ cv, lx, ly, fx: !!fx, fy: !!fy }); };
|
|
21328
|
+
const grid = (views, dims) => {
|
|
21329
|
+
const letter = (d) => ['x', 'y', 'z'][Math.max(0, dims.indexOf(d))];
|
|
21330
|
+
(views || []).forEach(v => add(v.canvas, letter(v.dimX), letter(v.dimY), _mvFlipX(v), _mvFlipY(v)));
|
|
21331
|
+
};
|
|
21332
|
+
const plain = (cvs) => cvs.forEach(cv => add(cv, _axisLetter(dim_x), _axisLetter(dim_y), flip_x, flip_y));
|
|
21333
|
+
if (stackMosaicActive) plain((stackMosaicViews || []).map(v => v.canvas));
|
|
21334
|
+
else if (compareQmriActive) plain((compareQmriViews || []).map(v => v.canvas));
|
|
21335
|
+
else if (compareMvActive) grid(compareMvViews, compareMvDims);
|
|
21336
|
+
else if (multiViewActive) grid(mvViews, mvDims);
|
|
21337
|
+
else if (compareActive) plain([...document.querySelectorAll('#compare-panes .compare-canvas')]);
|
|
21338
|
+
else if (qmriMvActive) grid(qmriMvViews, qmriMvDims);
|
|
21339
|
+
else if (qmriActive) plain((qmriViews || []).map(v => v.canvas));
|
|
21340
|
+
else if (mipActive || cutawayActive) return out;
|
|
21341
|
+
// Mosaic tiles are flipped one by one, so the whole canvas has no single origin corner.
|
|
21342
|
+
else if (!(dim_z >= 0 && (flip_x || flip_y))) plain([canvas]);
|
|
21343
|
+
return out;
|
|
21279
21344
|
}
|
|
21280
|
-
|
|
21281
|
-
function
|
|
21282
|
-
|
|
21283
|
-
let
|
|
21284
|
-
|
|
21285
|
-
const
|
|
21286
|
-
|
|
21287
|
-
el.
|
|
21288
|
-
|
|
21289
|
-
|
|
21290
|
-
|
|
21291
|
-
}
|
|
21292
|
-
|
|
21345
|
+
// The part of `cv` that is actually on screen: its box clipped by every scrolling/clipping ancestor.
|
|
21346
|
+
function _orientVisibleRect(cv) {
|
|
21347
|
+
const r = cv.getBoundingClientRect();
|
|
21348
|
+
let l = r.left, t = r.top, rt = r.right, bt = r.bottom;
|
|
21349
|
+
for (let el = cv.parentElement; el && el !== document.body; el = el.parentElement) {
|
|
21350
|
+
const cs = getComputedStyle(el);
|
|
21351
|
+
if (cs.overflowX === 'visible' && cs.overflowY === 'visible') continue;
|
|
21352
|
+
const pr = el.getBoundingClientRect();
|
|
21353
|
+
l = Math.max(l, pr.left); t = Math.max(t, pr.top);
|
|
21354
|
+
rt = Math.min(rt, pr.right); bt = Math.min(bt, pr.bottom);
|
|
21355
|
+
}
|
|
21356
|
+
return (rt - l > 8 && bt - t > 8) ? { left: l, top: t, right: rt, bottom: bt } : null;
|
|
21357
|
+
}
|
|
21358
|
+
function _orientCrossSvg(el, letterX, letterY, flipX, flipY) {
|
|
21359
|
+
const key = letterX + letterY + (flipX ? 1 : 0) + (flipY ? 1 : 0);
|
|
21360
|
+
if (el._ocKey === key) return;
|
|
21361
|
+
el._ocKey = key;
|
|
21362
|
+
const B = _ORIENT_CROSS_BOX, o = 8, len = 30, hl = 7, hw = 3.6;
|
|
21363
|
+
const sx = flipX ? -1 : 1, sy = flipY ? -1 : 1;
|
|
21364
|
+
const ox = flipX ? B - o : o, oy = flipY ? B - o : o;
|
|
21365
|
+
const xTip = ox + sx * len, yTip = oy + sy * len;
|
|
21366
|
+
const xHead = `${xTip},${oy} ${xTip - sx * hl},${oy - hw} ${xTip - sx * hl},${oy + hw}`;
|
|
21367
|
+
const yHead = `${ox},${yTip} ${ox - hw},${yTip - sy * hl} ${ox + hw},${yTip - sy * hl}`;
|
|
21368
|
+
const segs = [[ox, oy, xTip - sx * 4, oy], [ox, oy, ox, yTip - sy * 4]];
|
|
21369
|
+
el.innerHTML = segs.map(([x1, y1, x2, y2]) => `<line class="oc-halo" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}"/>`
|
|
21370
|
+
+ `<line class="oc-ln" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}"/>`).join('')
|
|
21371
|
+
+ `<polygon class="oc-hd" points="${xHead}"/><polygon class="oc-hd" points="${yHead}"/>`
|
|
21372
|
+
+ `<circle class="oc-hd" cx="${ox}" cy="${oy}" r="2"/>`
|
|
21373
|
+
+ `<text x="${xTip + sx * 9}" y="${oy + 4}" text-anchor="${flipX ? 'end' : 'start'}">${letterX}</text>`
|
|
21374
|
+
+ `<text x="${ox}" y="${yTip + sy * 13}" text-anchor="middle">${letterY}</text>`;
|
|
21375
|
+
}
|
|
21376
|
+
// Puts one cross in the origin corner of each pane: the x arrow runs
|
|
21377
|
+
// along the top (bottom when flipped) edge toward increasing x, the y
|
|
21378
|
+
// arrow along the left (right when flipped) edge toward increasing y.
|
|
21379
|
+
function _placeOrientCrosses() {
|
|
21380
|
+
const panes = _orientPanes().map(p => ({ p, r: _orientVisibleRect(p.cv) })).filter(q => q.r);
|
|
21381
|
+
while (_orientCrossEls.length < panes.length) {
|
|
21382
|
+
const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
21383
|
+
el.setAttribute('class', 'orient-cross');
|
|
21384
|
+
el.setAttribute('viewBox', `0 0 ${_ORIENT_CROSS_BOX} ${_ORIENT_CROSS_BOX}`);
|
|
21385
|
+
document.body.appendChild(el);
|
|
21386
|
+
_orientCrossEls.push(el);
|
|
21387
|
+
}
|
|
21388
|
+
_orientCrossEls.forEach((el, i) => {
|
|
21389
|
+
const q = panes[i];
|
|
21390
|
+
if (!q) { el.classList.remove('showing'); return; }
|
|
21391
|
+
const { p, r } = q;
|
|
21392
|
+
_orientCrossSvg(el, p.lx, p.ly, p.fx, p.fy);
|
|
21393
|
+
const w = r.right - r.left, h = r.bottom - r.top;
|
|
21394
|
+
// Small panes (grids, mosaics) get a proportionally smaller cross.
|
|
21395
|
+
const k = Math.max(0.55, Math.min(1, Math.min(w, h) / 230));
|
|
21396
|
+
const size = _ORIENT_CROSS_BOX * k, m = _ORIENT_CROSS_MARGIN * k;
|
|
21397
|
+
const x = p.fx ? r.right - m - size : r.left + m;
|
|
21398
|
+
const y = p.fy ? r.bottom - m - size : r.top + m;
|
|
21399
|
+
el.style.transform = `translate(${Math.round(x)}px, ${Math.round(y)}px) scale(${k})`;
|
|
21400
|
+
el.classList.add('showing');
|
|
21401
|
+
});
|
|
21293
21402
|
}
|
|
21294
|
-
//
|
|
21295
|
-
//
|
|
21296
|
-
|
|
21297
|
-
|
|
21298
|
-
|
|
21299
|
-
|
|
21300
|
-
|
|
21301
|
-
if (!el) return;
|
|
21302
|
-
const m = _ORIENT_ARROW_MARGIN + 'px';
|
|
21303
|
-
el.style.left = el.style.right = el.style.top = el.style.bottom = '';
|
|
21304
|
-
if (axis === 'x') {
|
|
21305
|
-
if (flipOwn) el.style.right = m; else el.style.left = m;
|
|
21306
|
-
if (flipOther) el.style.bottom = m; else el.style.top = m;
|
|
21307
|
-
el.style.transform = flipOwn ? 'rotate(180deg)' : 'rotate(0deg)';
|
|
21308
|
-
} else {
|
|
21309
|
-
if (flipOwn) el.style.bottom = m; else el.style.top = m;
|
|
21310
|
-
if (flipOther) el.style.right = m; else el.style.left = m;
|
|
21311
|
-
el.style.transform = flipOwn ? 'rotate(-90deg)' : 'rotate(90deg)';
|
|
21312
|
-
}
|
|
21313
|
-
el.classList.add('showing');
|
|
21403
|
+
// Panes can still be moving or re-rendering when this starts (after
|
|
21404
|
+
// r / t, or while zooming), so keep the crosses glued to them.
|
|
21405
|
+
function _orientLoop() {
|
|
21406
|
+
_orientRaf = 0;
|
|
21407
|
+
if (!_orientShown) return;
|
|
21408
|
+
_placeOrientCrosses();
|
|
21409
|
+
_orientRaf = requestAnimationFrame(_orientLoop);
|
|
21314
21410
|
}
|
|
21315
|
-
function
|
|
21316
|
-
|
|
21317
|
-
if (
|
|
21318
|
-
if (
|
|
21319
|
-
|
|
21320
|
-
|
|
21321
|
-
|
|
21322
|
-
|
|
21323
|
-
|
|
21324
|
-
|
|
21325
|
-
|
|
21326
|
-
|
|
21327
|
-
|
|
21328
|
-
|
|
21411
|
+
function _clearOrientEdgeHighlight() {
|
|
21412
|
+
_orientShown = false;
|
|
21413
|
+
if (_orientRaf) { cancelAnimationFrame(_orientRaf); _orientRaf = 0; }
|
|
21414
|
+
if (_orientFlashTimer) { clearTimeout(_orientFlashTimer); _orientFlashTimer = 0; }
|
|
21415
|
+
_orientCrossEls.forEach(el => el.classList.remove('showing'));
|
|
21416
|
+
}
|
|
21417
|
+
// Shown for every image pane while the pointer is over the DimBar.
|
|
21418
|
+
function _showOrientEdgeHighlight() {
|
|
21419
|
+
if (!_orientEdgeAllowed()) { _clearOrientEdgeHighlight(); return; }
|
|
21420
|
+
_orientShown = true;
|
|
21421
|
+
if (!_orientRaf) _orientLoop();
|
|
21422
|
+
}
|
|
21423
|
+
// Brief look after r / t so a rotate or transpose can be read off the cross.
|
|
21424
|
+
function _flashOrientCross(ms = 1400) {
|
|
21425
|
+
if (!_orientEdgeAllowed()) return;
|
|
21426
|
+
_showOrientEdgeHighlight();
|
|
21427
|
+
if (_orientFlashTimer) clearTimeout(_orientFlashTimer);
|
|
21428
|
+
_orientFlashTimer = setTimeout(() => {
|
|
21429
|
+
_orientFlashTimer = 0;
|
|
21430
|
+
if (!_orientHovering) _clearOrientEdgeHighlight();
|
|
21431
|
+
}, ms);
|
|
21329
21432
|
}
|
|
21330
21433
|
|
|
21331
21434
|
function setStatus(msg) {
|
|
@@ -23317,6 +23420,79 @@
|
|
|
23317
23420
|
}
|
|
23318
23421
|
}, true);
|
|
23319
23422
|
|
|
23423
|
+
// ── Compare with an array that is open in another tab ─────────
|
|
23424
|
+
let _compareWithMenuVisible = false;
|
|
23425
|
+
let _compareWithChoices = [];
|
|
23426
|
+
|
|
23427
|
+
function _closeCompareWithMenu() {
|
|
23428
|
+
if (!_compareWithMenuVisible) return;
|
|
23429
|
+
_compareWithMenuVisible = false;
|
|
23430
|
+
_compareWithChoices = [];
|
|
23431
|
+
_reconcileUI();
|
|
23432
|
+
try { document.getElementById('keyboard-sink').focus(); } catch (_) {}
|
|
23433
|
+
}
|
|
23434
|
+
|
|
23435
|
+
async function _pickCompareWith(choice) {
|
|
23436
|
+
if (!choice || !choice.enabled) return;
|
|
23437
|
+
_closeCompareWithMenu();
|
|
23438
|
+
const ok = await enterCompareModeBySid(choice.sid, choice.label);
|
|
23439
|
+
if (ok !== false) showStatus(`Compare: ${choice.label}`);
|
|
23440
|
+
}
|
|
23441
|
+
|
|
23442
|
+
async function openCompareWithMenu() {
|
|
23443
|
+
let sessions = [];
|
|
23444
|
+
try {
|
|
23445
|
+
const res = await proxyFetch('/sessions');
|
|
23446
|
+
if (res.ok) sessions = await res.json();
|
|
23447
|
+
} catch (_) {}
|
|
23448
|
+
const ownShape = JSON.stringify((shape || []).map(Number));
|
|
23449
|
+
_compareWithChoices = sessions
|
|
23450
|
+
.filter(s => s.sid && s.sid !== sid)
|
|
23451
|
+
.map(s => {
|
|
23452
|
+
const sameShape = JSON.stringify((s.shape || []).map(Number)) === ownShape;
|
|
23453
|
+
const label = s.var || s.name || s.sid.slice(0, 8);
|
|
23454
|
+
if (s.name) compareNameBySid[s.sid] = label;
|
|
23455
|
+
return {
|
|
23456
|
+
sid: s.sid,
|
|
23457
|
+
label,
|
|
23458
|
+
detail: `${(s.shape || []).join(' x ')}${s.var && s.name ? ` · ${s.name}` : ''}`,
|
|
23459
|
+
enabled: sameShape,
|
|
23460
|
+
};
|
|
23461
|
+
});
|
|
23462
|
+
if (!_compareWithChoices.length) {
|
|
23463
|
+
showStatus('No other open arrays to compare with');
|
|
23464
|
+
return;
|
|
23465
|
+
}
|
|
23466
|
+
const list = document.getElementById('compare-with-list');
|
|
23467
|
+
list.innerHTML = _compareWithChoices.map((c, i) => {
|
|
23468
|
+
const keyHint = i < 9 ? `<span class="compare-with-key">${i + 1}</span>` : '';
|
|
23469
|
+
const reason = c.enabled ? c.detail : `${c.detail} · needs shape ${(shape || []).join(' x ')}`;
|
|
23470
|
+
return `<button class="drop-action" data-compare-with="${i}" ${c.enabled ? '' : 'disabled'}>${keyHint}${_escapeHtml(c.label)}<span class="drop-action-reason">${_escapeHtml(reason)}</span></button>`;
|
|
23471
|
+
}).join('');
|
|
23472
|
+
list.querySelectorAll('.drop-action:not(:disabled)').forEach(button => {
|
|
23473
|
+
button.addEventListener('click', () => _pickCompareWith(_compareWithChoices[Number(button.dataset.compareWith)]));
|
|
23474
|
+
});
|
|
23475
|
+
document.getElementById('compare-with-summary').textContent =
|
|
23476
|
+
'Arrays open in your other tabs · click or press a number · Esc to cancel';
|
|
23477
|
+
_compareWithMenuVisible = true;
|
|
23478
|
+
_reconcileUI();
|
|
23479
|
+
}
|
|
23480
|
+
|
|
23481
|
+
document.addEventListener('pointerdown', (event) => {
|
|
23482
|
+
if (_compareWithMenuVisible && !document.getElementById('compare-with-menu').contains(event.target)) {
|
|
23483
|
+
_closeCompareWithMenu();
|
|
23484
|
+
}
|
|
23485
|
+
});
|
|
23486
|
+
window.addEventListener('keydown', (event) => {
|
|
23487
|
+
if (!_compareWithMenuVisible) return;
|
|
23488
|
+
if (['Shift', 'Control', 'Alt', 'Meta'].includes(event.key)) return;
|
|
23489
|
+
event.preventDefault();
|
|
23490
|
+
event.stopImmediatePropagation();
|
|
23491
|
+
if (event.key === 'Escape') { _closeCompareWithMenu(); return; }
|
|
23492
|
+
const n = Number(event.key);
|
|
23493
|
+
if (Number.isInteger(n) && n >= 1 && n <= 9) _pickCompareWith(_compareWithChoices[n - 1]);
|
|
23494
|
+
}, true);
|
|
23495
|
+
|
|
23320
23496
|
/* ── JS: Rendering Pipeline (updateView, play, screenshot) ── */
|
|
23321
23497
|
|
|
23322
23498
|
function updateView() {
|
|
@@ -27757,6 +27933,7 @@
|
|
|
27757
27933
|
_roiClearOnDimChange();
|
|
27758
27934
|
if (typeof _cropReconcileBindings === 'function') _cropReconcileBindings();
|
|
27759
27935
|
renderInfo(); saveState();
|
|
27936
|
+
_flashOrientCross();
|
|
27760
27937
|
return;
|
|
27761
27938
|
}
|
|
27762
27939
|
const wasMosaic = dim_z >= 0;
|
|
@@ -27772,6 +27949,7 @@
|
|
|
27772
27949
|
showStatus(wasMosaic ? 'transposed x/y (mosaic)' : 'transposed x/y');
|
|
27773
27950
|
if (typeof _cropReconcileBindings === 'function') _cropReconcileBindings();
|
|
27774
27951
|
renderInfo(); saveState();
|
|
27952
|
+
_flashOrientCross();
|
|
27775
27953
|
},
|
|
27776
27954
|
},
|
|
27777
27955
|
'view.rotateOrFlip': {
|
|
@@ -27802,6 +27980,7 @@
|
|
|
27802
27980
|
showStatus('rotated 90°'); saveState();
|
|
27803
27981
|
}
|
|
27804
27982
|
if (typeof _cropReconcileBindings === 'function') _cropReconcileBindings();
|
|
27983
|
+
_flashOrientCross();
|
|
27805
27984
|
},
|
|
27806
27985
|
},
|
|
27807
27986
|
'theme.cycle': {
|
|
@@ -29084,6 +29263,11 @@
|
|
|
29084
29263
|
commands['qmri.cycle'].run(ctx, e);
|
|
29085
29264
|
},
|
|
29086
29265
|
},
|
|
29266
|
+
'compare.withOpenArray': {
|
|
29267
|
+
title: 'Compare with an array open in another tab',
|
|
29268
|
+
when: [],
|
|
29269
|
+
run: (ctx, e) => { openCompareWithMenu(); },
|
|
29270
|
+
},
|
|
29087
29271
|
'tool.comparecenterDirect': {
|
|
29088
29272
|
title: 'Cycle compare center mode',
|
|
29089
29273
|
when: [],
|
|
@@ -29515,11 +29699,12 @@
|
|
|
29515
29699
|
},
|
|
29516
29700
|
{
|
|
29517
29701
|
id: 'compare', label: 'Compare',
|
|
29518
|
-
desc: 'View two or more arrays side by side, blend them together, or compute a difference map. Useful for checking registration, comparing model outputs, or inspecting before-and-after changes.
|
|
29702
|
+
desc: 'View two or more arrays side by side, blend them together, or compute a difference map. Useful for checking registration, comparing model outputs, or inspecting before-and-after changes. Press / then c to compare with an array open in another tab.',
|
|
29519
29703
|
sections: [
|
|
29520
29704
|
{
|
|
29521
29705
|
title: 'Layout',
|
|
29522
29706
|
entries: [
|
|
29707
|
+
{ key: '/ c', action: 'Compare with…', hint: 'pick an array that is open in another tab; it must have the same shape' },
|
|
29523
29708
|
{ key: 'g', action: 'Cycle layout', hint: 'horizontal → vertical → grid; with exactly 2 arrays + center mode active, toggles horizontal ↔ big-left' },
|
|
29524
29709
|
{ key: 'Drag title', action: 'Reorder panes by dragging their title bar' },
|
|
29525
29710
|
]
|
|
@@ -29558,6 +29743,7 @@
|
|
|
29558
29743
|
{
|
|
29559
29744
|
title: 'After /', columnBreak: true,
|
|
29560
29745
|
entries: [
|
|
29746
|
+
{ key: '/ c', action: 'Compare with…', hint: 'side by side with an array open in another tab' },
|
|
29561
29747
|
{ key: '/ r', action: 'ROI', hint: 'measure a region and see live stats' },
|
|
29562
29748
|
{ key: '/ S', action: 'Segmentation', hint: 'local mask editing and nnInteractive options' },
|
|
29563
29749
|
{ key: '/ o', action: 'Overlays', hint: 'show mask names and visibility' },
|
|
@@ -29983,6 +30169,16 @@
|
|
|
29983
30169
|
{ key: 'q', toolId: 'qmri', command: 'tool.qmriDirect' },
|
|
29984
30170
|
{ key: 's', toolId: 'save', command: 'saveMenu.open' },
|
|
29985
30171
|
{ key: 'C', toolId: 'crop', command: 'tool.cropDirect' },
|
|
30172
|
+
{
|
|
30173
|
+
key: 'c',
|
|
30174
|
+
id: 'comparewith',
|
|
30175
|
+
label: 'Compare with…',
|
|
30176
|
+
command: 'compare.withOpenArray',
|
|
30177
|
+
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="8" height="14" rx="1"/><rect x="13" y="5" width="8" height="14" rx="1"/></svg>',
|
|
30178
|
+
enabled: () => !isInlineEmbed,
|
|
30179
|
+
active: () => compareActive,
|
|
30180
|
+
unavailableReason: () => 'Not available in notebook output.',
|
|
30181
|
+
},
|
|
29986
30182
|
{ key: 'P', toolId: 'comparecenter', command: 'tool.comparecenterDirect' },
|
|
29987
30183
|
{ key: 'r', toolId: 'roi', command: 'tool.roiDirect' },
|
|
29988
30184
|
{ key: 'S', toolId: 'segmentation', command: 'tool.segmentationDirect' },
|
|
@@ -37771,6 +37967,7 @@
|
|
|
37771
37967
|
if (e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return;
|
|
37772
37968
|
sink.focus();
|
|
37773
37969
|
_qmriMvDragView = view;
|
|
37970
|
+
_qmvCrosshairMap = view.qmriIdx;
|
|
37774
37971
|
_qmvRecomputeCrosshairTarget();
|
|
37775
37972
|
handleQmriMvPointer(view, e);
|
|
37776
37973
|
const onMove = (me) => { if (_qmriMvDragView === view) handleQmriMvPointer(view, me); };
|
|
@@ -37923,7 +38120,7 @@
|
|
|
37923
38120
|
_hoveredQmriMvView = null;
|
|
37924
38121
|
if (_qmvCrosshairAnim) { cancelAnimationFrame(_qmvCrosshairAnim); _qmvCrosshairAnim = null; }
|
|
37925
38122
|
if (_qmvScrollHintTimer) { clearTimeout(_qmvScrollHintTimer); _qmvScrollHintTimer = null; }
|
|
37926
|
-
_qmvCrosshairAlpha = 0; _qmvCrosshairTarget = 0; _qmvScrollHintDir = -1;
|
|
38123
|
+
_qmvCrosshairAlpha = 0; _qmvCrosshairTarget = 0; _qmvScrollHintDir = -1; _qmvCrosshairMap = null;
|
|
37927
38124
|
_reconcileOrthoCursorHidden();
|
|
37928
38125
|
_reconcileMvOrientationVisibility();
|
|
37929
38126
|
qmriMvViews.forEach(v => { if (v.ws) { v.ws.onclose = null; v.ws.close(); } });
|
|
@@ -38097,7 +38294,9 @@
|
|
|
38097
38294
|
}
|
|
38098
38295
|
/** Position lines on the pane's overlay, like compareMvDrawFrame. */
|
|
38099
38296
|
function _qmvDrawCrosshair(v) {
|
|
38100
|
-
const alpha = (_qmvScrollHintTimer && v.sliceDir === _qmvScrollHintDir) ? 0
|
|
38297
|
+
const alpha = (_qmvScrollHintTimer && v.sliceDir === _qmvScrollHintDir) ? 0
|
|
38298
|
+
: (_qmvCrosshairMap !== null && v.qmriIdx !== _qmvCrosshairMap) ? 0
|
|
38299
|
+
: _qmvCrosshairAlpha;
|
|
38101
38300
|
const ov = _getMvCrosshairOverlay(v);
|
|
38102
38301
|
if (!ov) return;
|
|
38103
38302
|
const metrics = _getMvCrosshairMetrics(v);
|
|
@@ -38130,8 +38329,8 @@
|
|
|
38130
38329
|
if (!ready.length) return;
|
|
38131
38330
|
const nRows = Math.max(1, Math.round(qmriMvViews.length / 3));
|
|
38132
38331
|
const nCols = 3;
|
|
38133
|
-
const colGap =
|
|
38134
|
-
const rowGap =
|
|
38332
|
+
const colGap = 0;
|
|
38333
|
+
const rowGap = 0;
|
|
38135
38334
|
const sidePad = 120;
|
|
38136
38335
|
const vcbW = 80; // vertical colorbar island horizontal footprint (bar + padding)
|
|
38137
38336
|
const bottomPad = 16;
|
|
@@ -38147,6 +38346,13 @@
|
|
|
38147
38346
|
const baseScale = absMaxT / maxSide;
|
|
38148
38347
|
const panStub = { x: 0, y: 0, clamp: () => {}, apply: () => {} };
|
|
38149
38348
|
for (const v of ready) {
|
|
38349
|
+
const qi = qmriMvViews.indexOf(v), qr = Math.floor(qi / 3), qc = qi % 3;
|
|
38350
|
+
if (v.clip) {
|
|
38351
|
+
v.clip.classList.toggle('qmv-tl', qr === 0 && qc === 0);
|
|
38352
|
+
v.clip.classList.toggle('qmv-tr', qr === 0 && qc === 2);
|
|
38353
|
+
v.clip.classList.toggle('qmv-bl', qr === nRows - 1 && qc === 0);
|
|
38354
|
+
v.clip.classList.toggle('qmv-br', qr === nRows - 1 && qc === 2);
|
|
38355
|
+
}
|
|
38150
38356
|
_scalePane(
|
|
38151
38357
|
{ canvas: v.canvas, w: v.lastW, h: v.lastH, pan: panStub },
|
|
38152
38358
|
v.clip || null,
|
|
@@ -39155,6 +39361,7 @@
|
|
|
39155
39361
|
* scrolled plane's own lines stay put, so they are left hidden. */
|
|
39156
39362
|
function _qmvScrollHint(view) {
|
|
39157
39363
|
_qmvScrollHintDir = view ? view.sliceDir : -1;
|
|
39364
|
+
if (!_qmriMvDragView && view) _qmvCrosshairMap = view.qmriIdx;
|
|
39158
39365
|
if (_qmvScrollHintTimer) clearTimeout(_qmvScrollHintTimer);
|
|
39159
39366
|
_qmvScrollHintTimer = setTimeout(() => {
|
|
39160
39367
|
_qmvScrollHintTimer = null;
|
|
@@ -39887,9 +40094,10 @@
|
|
|
39887
40094
|
let _histDimHovering = false;
|
|
39888
40095
|
infoEl.addEventListener('mouseover', (e) => {
|
|
39889
40096
|
const span = e.target.closest('[data-dim]');
|
|
40097
|
+
_orientHovering = true;
|
|
40098
|
+
_showOrientEdgeHighlight();
|
|
39890
40099
|
if (span) {
|
|
39891
40100
|
hoveredDim = parseInt(span.dataset.dim);
|
|
39892
|
-
_showOrientEdgeHighlight(hoveredDim);
|
|
39893
40101
|
if (_histDimControlsVisible()) {
|
|
39894
40102
|
_histDimHovering = true;
|
|
39895
40103
|
_showHistTooltip(_tooltipTextForDim(hoveredDim), infoEl.getBoundingClientRect());
|
|
@@ -39905,7 +40113,6 @@
|
|
|
39905
40113
|
return;
|
|
39906
40114
|
}
|
|
39907
40115
|
_histDimHovering = false;
|
|
39908
|
-
_clearOrientEdgeHighlight();
|
|
39909
40116
|
_hideHistTooltip();
|
|
39910
40117
|
if (!_lebesgueManual && primaryCb && primaryCb._expanded && !_histAutoDismissTimer) {
|
|
39911
40118
|
_restartHistAutoDismiss();
|
|
@@ -39915,6 +40122,7 @@
|
|
|
39915
40122
|
infoEl.addEventListener('mouseleave', () => {
|
|
39916
40123
|
hoveredDim = -1;
|
|
39917
40124
|
_histDimHovering = false;
|
|
40125
|
+
_orientHovering = false;
|
|
39918
40126
|
_clearOrientEdgeHighlight();
|
|
39919
40127
|
_hideHistTooltip();
|
|
39920
40128
|
if (!_lebesgueManual && primaryCb && primaryCb._expanded && !_histAutoDismissTimer) {
|
|
@@ -41644,6 +41852,11 @@
|
|
|
41644
41852
|
dropMenu.classList.toggle('visible', _dropMenuVisible);
|
|
41645
41853
|
dropMenu.setAttribute('aria-hidden', _dropMenuVisible ? 'false' : 'true');
|
|
41646
41854
|
}
|
|
41855
|
+
const compareWithMenu = document.getElementById('compare-with-menu');
|
|
41856
|
+
if (compareWithMenu) {
|
|
41857
|
+
compareWithMenu.classList.toggle('visible', _compareWithMenuVisible);
|
|
41858
|
+
compareWithMenu.setAttribute('aria-hidden', _compareWithMenuVisible ? 'false' : 'true');
|
|
41859
|
+
}
|
|
41647
41860
|
if (_zenActive) _setZenQmriChromeOpacity('0');
|
|
41648
41861
|
renderToolMenu();
|
|
41649
41862
|
if (typeof _reconcileOverlayPalette === 'function') _reconcileOverlayPalette();
|
arrayview/_vscode_extension.py
CHANGED
|
@@ -58,7 +58,7 @@ _VSCODE_EXT_FRESH_INSTALL = False # True if we just installed it this session
|
|
|
58
58
|
_VSCODE_EXT_RELOAD_REQUIRED = False # installed files are newer than the live host
|
|
59
59
|
_VSCODE_EXT_INSTALL_FAILED = False # automatic install could not complete safely
|
|
60
60
|
_VSCODE_EXT_NO_LIVE_WINDOW = False # no live host claims this terminal's window id
|
|
61
|
-
_VSCODE_EXT_VERSION = "0.15.
|
|
61
|
+
_VSCODE_EXT_VERSION = "0.15.62" # current bundled extension version
|
|
62
62
|
_VSCODE_CONFIGURED_PORTS: set[int] = set()
|
|
63
63
|
# Version-skew notices already printed, so a long-lived process (Jupyter, a
|
|
64
64
|
# script opening several arrays) states the mismatch once instead of per call.
|
arrayview/arrayview-opener.vsix
CHANGED
|
Binary file
|
|
@@ -23,7 +23,7 @@ arrayview/_render.py,sha256=sSNPWreJumIyFKvZtAS5v2gxbNHRVyAdc7JGj34caPw,34121
|
|
|
23
23
|
arrayview/_routes_analysis.py,sha256=Xz9Ysr04u07pzSRB4Fzmcl6XwVxyaG6xCWxGwYZi_80,33085
|
|
24
24
|
arrayview/_routes_drop.py,sha256=FC4e7STdCo3hYIA0C4Yy9Ka2UKFx7nLDU5dkujmc1Vo,12477
|
|
25
25
|
arrayview/_routes_export.py,sha256=Ha8mMO2E9TlRL5x25-ADV8_8xUiC-jRz9N1oWwgKOsg,3898
|
|
26
|
-
arrayview/_routes_loading.py,sha256=
|
|
26
|
+
arrayview/_routes_loading.py,sha256=ZOHOFdqMDwmdjtzhq07LQUGG7BHmP1od4IiZDWWRZtQ,41662
|
|
27
27
|
arrayview/_routes_persistence.py,sha256=T03csHb7ppGT-8yMDf227XkqhnhjgSLYj0RW2wmg59g,17176
|
|
28
28
|
arrayview/_routes_preferences.py,sha256=SKnhTf2X2vRyYi06Wb2si4dXMB1y0XiSBr28RZ5oKUw,4691
|
|
29
29
|
arrayview/_routes_preload.py,sha256=KOsankNguxdIWmFW9iZcY9p2SvUN1mOKY7KkW1qMGcc,1729
|
|
@@ -43,17 +43,17 @@ arrayview/_synthetic_mri.py,sha256=CNL-2WC835i6WCqkvmWBCO6WQ5w7HE5trNb8OyEfe64,7
|
|
|
43
43
|
arrayview/_torch.py,sha256=13sqIMCUu_-b9SQ9KfDCExDz2UMStL87EDVjnbJxAEo,7536
|
|
44
44
|
arrayview/_tutorial.py,sha256=2zZHJGJDa4n30GE1-9ADjKFAifGBH9vvHXP4uDt7BaA,19547
|
|
45
45
|
arrayview/_vectorfield.py,sha256=eDqMCMEGjO8Jdt-6Auh01cz3WD9dbF7xYUWD9xHAf1E,8115
|
|
46
|
-
arrayview/_viewer.html,sha256=
|
|
46
|
+
arrayview/_viewer.html,sha256=pUCkoJRPvZMCvNEh-zoGDOTtjX0A1D7vdLqHI9Tzr3A,2371938
|
|
47
47
|
arrayview/_vscode.py,sha256=EKnGN2zrjlybc4FQOmYA8BvYdtkcJbLHDEdgtvO7ul4,837
|
|
48
48
|
arrayview/_vscode_browser.py,sha256=VCH9JpAAjFuhO0wQe8tIGjAL5837AXg_bpmBFtWOQqc,20573
|
|
49
|
-
arrayview/_vscode_extension.py,sha256=
|
|
49
|
+
arrayview/_vscode_extension.py,sha256=AUfGJpPBPzKIRyVF0wvrDqr5F0eVrZpFEHoxuNjO15M,53156
|
|
50
50
|
arrayview/_vscode_signal.py,sha256=N3PCHG6-3tCEc_tHLjg2dhSEQ2EnF_psE2IJRBpAuac,54938
|
|
51
51
|
arrayview/_watch.py,sha256=PxxS_5hcj_S6xQf9Ep4p1HLRkrI5iDMh1aRa4olV7ew,1873
|
|
52
|
-
arrayview/arrayview-opener.vsix,sha256=
|
|
52
|
+
arrayview/arrayview-opener.vsix,sha256=1kbHgrG_kyxbDFa6WQ_KojmMPiogHlcu62L86HraVbk,61155
|
|
53
53
|
arrayview/gsap.min.js,sha256=VGihm4idI0E1uqrWezWsiScB9F_jMosn7qs6pPSkeac,72304
|
|
54
54
|
arrayview/todo.md,sha256=KhnRzpn6_jezR0nDDB61-zhox-rbOmEGW-A7Z0_7FnU,622
|
|
55
|
-
arrayview-0.
|
|
56
|
-
arrayview-0.
|
|
57
|
-
arrayview-0.
|
|
58
|
-
arrayview-0.
|
|
59
|
-
arrayview-0.
|
|
55
|
+
arrayview-0.47.0.dist-info/METADATA,sha256=x5u-7ZK2lktztD4BFipekM4rP8bh1YL5KwI9Jd1F5Ss,2288
|
|
56
|
+
arrayview-0.47.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
57
|
+
arrayview-0.47.0.dist-info/entry_points.txt,sha256=-0s08axutXkTbINiKs0Q7iv-33aWuTaamUg2q2-NyQ4,60
|
|
58
|
+
arrayview-0.47.0.dist-info/licenses/LICENSE,sha256=34OivjM9faB2QaPjZpMRTIiSx31V3U9cEyQ6F2k-bH0,1062
|
|
59
|
+
arrayview-0.47.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|