@niivue/nv-ext-tile 1.0.0-rc.7 → 1.0.0-rc.8
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 +2 -1
- package/dist/dcm2niixDecoders.d.ts +7 -0
- package/dist/dicom.js +464 -430
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -101,7 +101,8 @@ const slide = NVSlide.fromSource(new DicomWsiSource(index.slides[0], files))
|
|
|
101
101
|
that changed since indexing fails rather than decoding garbage.
|
|
102
102
|
- Supported transfer syntaxes: JPEG baseline/extended (browser), JPEG 2000 and JPEG-LS
|
|
103
103
|
(`registerDcm2niixDecoders`, with `@niivue/dcm2niix` as an optional peer dependency;
|
|
104
|
-
a decoder the host already registered wins).
|
|
104
|
+
a decoder the host already registered wins). A dcm2niix pool whose workers die is
|
|
105
|
+
replaced, and the tile or profile retried once.
|
|
105
106
|
- Slides are shown with row 0 of the total pixel matrix at the top (`displayYAxis: 'down'`),
|
|
106
107
|
as OpenSlide shows them.
|
|
107
108
|
- Index speed for the 8 OpenSlide DICOM-WSI test sets (local SSD; Chrome reads through
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { FrameDecoder } from '@niivue/dcm2niix';
|
|
2
|
+
import { SlideIccTransform, SlideTileDecoder } from '@niivue/niivue';
|
|
3
|
+
/** Tile decoder and ICC transform over one lazily created dcm2niix pool, replaced when its workers die. */
|
|
4
|
+
export declare function createDcm2niixDecoders(createDecoder?: () => Promise<FrameDecoder>): {
|
|
5
|
+
decodeTile: SlideTileDecoder;
|
|
6
|
+
bakeProfile: SlideIccTransform;
|
|
7
|
+
};
|
|
1
8
|
/**
|
|
2
9
|
* Register JPEG 2000 and JPEG-LS tile decoders for NVSlide, backed by dcm2niix's
|
|
3
10
|
* WASM worker pool (loaded on the first such tile), and its LittleCMS ICC
|
package/dist/dicom.js
CHANGED
|
@@ -1,117 +1,151 @@
|
|
|
1
1
|
import { NVSlide as q } from "@niivue/niivue";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
function $t(e) {
|
|
3
|
+
return e instanceof Error && e.name === "Error" && !e.message.startsWith("frame decoder ");
|
|
4
|
+
}
|
|
5
|
+
const ct = 33;
|
|
6
|
+
function Mt(e = () => import("@niivue/dcm2niix").then(
|
|
7
|
+
(t) => t.createFrameDecoder({ workers: 2 })
|
|
8
|
+
)) {
|
|
9
|
+
let t = null;
|
|
10
|
+
const r = () => {
|
|
11
|
+
const a = {
|
|
12
|
+
active: 0,
|
|
13
|
+
retired: !1,
|
|
14
|
+
decoder: e().catch((n) => {
|
|
15
|
+
throw t === a && (t = null), n;
|
|
16
|
+
})
|
|
17
|
+
};
|
|
18
|
+
return a;
|
|
19
|
+
}, s = async (a, n, l = !1) => {
|
|
20
|
+
t ?? (t = r());
|
|
21
|
+
const f = t;
|
|
22
|
+
f.active++;
|
|
23
|
+
try {
|
|
24
|
+
return await n(await f.decoder);
|
|
25
|
+
} catch (d) {
|
|
26
|
+
if (d !== (a == null ? void 0 : a.reason) && $t(d) && (f.retired = !0, t === f && (t = null), !l && !(a != null && a.aborted)))
|
|
27
|
+
return s(a, n, !0);
|
|
28
|
+
throw d;
|
|
29
|
+
} finally {
|
|
30
|
+
--f.active === 0 && f.retired && f.decoder.then(
|
|
31
|
+
(d) => d.terminate(),
|
|
32
|
+
() => {
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
}
|
|
14
36
|
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
37
|
+
return { decodeTile: (a, { level: n, signal: l }) => s(l, async (f) => {
|
|
38
|
+
var u;
|
|
39
|
+
const d = {
|
|
40
|
+
photometric: n.photometric,
|
|
41
|
+
bitsStored: n.bitsStored,
|
|
42
|
+
signal: l
|
|
43
|
+
};
|
|
44
|
+
if ((u = n.photometric) != null && u.startsWith("MONOCHROME"))
|
|
45
|
+
return f.decode(a, { ...d, output: "samples" });
|
|
46
|
+
const m = await f.decode(a, d), h = m.rgba;
|
|
47
|
+
return createImageBitmap(new ImageData(h, m.width, m.height));
|
|
48
|
+
}), bakeProfile: (a, n) => s(n, async (l) => {
|
|
49
|
+
const f = await l.iccLut(a, { size: ct, signal: n });
|
|
50
|
+
return f && { size: ct, data: f };
|
|
51
|
+
}) };
|
|
24
52
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
function vt() {
|
|
54
|
+
const { decodeTile: e, bakeProfile: t } = Mt();
|
|
55
|
+
for (const r of ["image/jp2", "image/jls"])
|
|
56
|
+
q.tileDecoder(r) || q.registerTileDecoder(r, e);
|
|
57
|
+
q.iccTransform() || q.registerIccTransform(t);
|
|
58
|
+
}
|
|
59
|
+
const tt = (e) => e.webkitRelativePath || e.name, k = 4294967295, ft = 8, J = 64 << 20, K = 1 << 22, lt = 2 * K, ht = 4096, Et = { US: 2, UL: 4, SL: 4 }, p = (e, t) => (e << 16 | t) >>> 0, w = {
|
|
60
|
+
transferSyntax: p(2, 16),
|
|
61
|
+
imageType: p(8, 8),
|
|
62
|
+
sop: p(8, 24),
|
|
63
|
+
study: p(32, 13),
|
|
64
|
+
series: p(32, 14),
|
|
65
|
+
frameOfReference: p(32, 82),
|
|
66
|
+
organization: p(32, 37649),
|
|
67
|
+
concatNumber: p(32, 37218),
|
|
68
|
+
concatOffset: p(32, 37416),
|
|
69
|
+
photometric: p(40, 4),
|
|
70
|
+
frames: p(40, 8),
|
|
71
|
+
rows: p(40, 16),
|
|
72
|
+
columns: p(40, 17),
|
|
73
|
+
spacing: p(40, 48),
|
|
74
|
+
bitsStored: p(40, 257),
|
|
75
|
+
iccProfile: p(40, 8192),
|
|
76
|
+
colorSpace: p(40, 8194),
|
|
77
|
+
container: p(64, 1298),
|
|
78
|
+
zOffset: p(64, 1866),
|
|
79
|
+
totalColumns: p(72, 6),
|
|
80
|
+
totalRows: p(72, 7),
|
|
81
|
+
opticalPathSeq: p(72, 261),
|
|
82
|
+
opticalPathId: p(72, 262),
|
|
83
|
+
columnPosition: p(72, 542),
|
|
84
|
+
rowPosition: p(72, 543),
|
|
85
|
+
opticalPaths: p(72, 770),
|
|
86
|
+
focalPlanes: p(72, 771),
|
|
87
|
+
shared: p(20992, 37417),
|
|
88
|
+
perFrame: p(20992, 37424),
|
|
89
|
+
eot: p(32736, 1),
|
|
90
|
+
eotLengths: p(32736, 2),
|
|
91
|
+
pixelData: p(32736, 16),
|
|
92
|
+
item: p(65534, 57344),
|
|
93
|
+
itemEnd: p(65534, 57357),
|
|
94
|
+
sequenceEnd: p(65534, 57565)
|
|
95
|
+
}, Pt = new Set("OB OD OF OL OV OW SQ SV UC UN UR UT UV".split(" ")), bt = /* @__PURE__ */ new Set([
|
|
62
96
|
"1.2.840.10008.1.2",
|
|
63
97
|
"1.2.840.10008.1.2.2",
|
|
64
98
|
"1.2.840.10008.1.2.1.99"
|
|
65
99
|
]);
|
|
66
|
-
class
|
|
100
|
+
class P extends Error {
|
|
67
101
|
constructor(t, r) {
|
|
68
102
|
super(r), this.reason = t;
|
|
69
103
|
}
|
|
70
104
|
}
|
|
71
|
-
class
|
|
105
|
+
class z extends Error {
|
|
72
106
|
}
|
|
73
107
|
function A(e, t) {
|
|
74
|
-
if (t + 2 > e.length) throw new
|
|
108
|
+
if (t + 2 > e.length) throw new z();
|
|
75
109
|
return e[t] | e[t + 1] << 8;
|
|
76
110
|
}
|
|
77
111
|
function v(e, t) {
|
|
78
|
-
if (t + 4 > e.length) throw new
|
|
112
|
+
if (t + 4 > e.length) throw new z();
|
|
79
113
|
return (e[t] | e[t + 1] << 8 | e[t + 2] << 16 | e[t + 3] << 24) >>> 0;
|
|
80
114
|
}
|
|
81
|
-
function
|
|
115
|
+
function ut(e, t, r, s, o, c) {
|
|
82
116
|
for (; t < r; ) {
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
85
|
-
if (
|
|
86
|
-
const
|
|
87
|
-
if (!/^[A-Z]{2}$/.test(
|
|
88
|
-
throw new
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
91
|
-
if (
|
|
92
|
-
throw new
|
|
93
|
-
const
|
|
94
|
-
let
|
|
95
|
-
for (let
|
|
96
|
-
const E =
|
|
97
|
-
if (E ===
|
|
98
|
-
|
|
117
|
+
const a = p(A(e, t), A(e, t + 2));
|
|
118
|
+
if (a === w.itemEnd || a === w.sequenceEnd) return t + 8;
|
|
119
|
+
if (o.depth === 0 && a === w.pixelData) return t;
|
|
120
|
+
const n = String.fromCharCode(e[t + 4], e[t + 5]);
|
|
121
|
+
if (!/^[A-Z]{2}$/.test(n))
|
|
122
|
+
throw new P("malformed", `no explicit VR at byte ${t}`);
|
|
123
|
+
const l = Pt.has(n), f = l ? v(e, t + 8) : A(e, t + 6), d = t + (l ? 12 : 8);
|
|
124
|
+
if (n === "SQ" || n === "UN" && f === k) {
|
|
125
|
+
if (o.depth >= ft)
|
|
126
|
+
throw new P("malformed", `sequences nested deeper than ${ft}`);
|
|
127
|
+
const m = f === k ? s : d + f;
|
|
128
|
+
let h = d;
|
|
129
|
+
for (let u = 0; h < m; u++) {
|
|
130
|
+
const E = p(A(e, h), A(e, h + 2));
|
|
131
|
+
if (E === w.sequenceEnd) {
|
|
132
|
+
h += 8;
|
|
99
133
|
break;
|
|
100
134
|
}
|
|
101
|
-
if (E !==
|
|
102
|
-
throw new
|
|
103
|
-
const y = v(e,
|
|
104
|
-
|
|
135
|
+
if (E !== w.item)
|
|
136
|
+
throw new P("malformed", `no item at byte ${h}`);
|
|
137
|
+
const y = v(e, h + 4), S = y === k ? s : h + 8 + y, U = o.depth === 0 ? { root: a, item: u, depth: 1 } : { ...o, depth: o.depth + 1 }, I = ut(e, h + 8, S, s, U, c);
|
|
138
|
+
h = y === k ? I : S;
|
|
105
139
|
}
|
|
106
|
-
t =
|
|
140
|
+
t = h;
|
|
107
141
|
continue;
|
|
108
142
|
}
|
|
109
|
-
if (
|
|
110
|
-
throw new
|
|
111
|
-
if (
|
|
112
|
-
throw new
|
|
113
|
-
if (
|
|
114
|
-
|
|
143
|
+
if (f === k)
|
|
144
|
+
throw new P("malformed", `undefined-length ${n} at byte ${t}`);
|
|
145
|
+
if (d + f > s)
|
|
146
|
+
throw new P("malformed", `${n} at byte ${t} runs past the file`);
|
|
147
|
+
if (d + f > e.length) throw new z();
|
|
148
|
+
c({ tag: a, vr: n, offset: d, length: f, ...o }), t = d + f;
|
|
115
149
|
}
|
|
116
150
|
return t;
|
|
117
151
|
}
|
|
@@ -119,405 +153,405 @@ const N = (e, t) => String.fromCharCode(...e.subarray(t.offset, t.offset + t.len
|
|
|
119
153
|
/[\0 ]+$/,
|
|
120
154
|
""
|
|
121
155
|
);
|
|
122
|
-
function
|
|
123
|
-
const r =
|
|
156
|
+
function W(e, t) {
|
|
157
|
+
const r = Et[t.vr];
|
|
124
158
|
if (!r) return Number(N(e, t).split("\\")[0]);
|
|
125
159
|
if (t.length < r) return Number.NaN;
|
|
126
160
|
const s = new DataView(e.buffer, e.byteOffset + t.offset, r);
|
|
127
161
|
return t.vr === "US" ? s.getUint16(0, !0) : t.vr === "UL" ? s.getUint32(0, !0) : s.getInt32(0, !0);
|
|
128
162
|
}
|
|
129
|
-
function
|
|
130
|
-
const r = t.find((s) => s.tag ===
|
|
131
|
-
if (r &&
|
|
132
|
-
throw new
|
|
163
|
+
function mt(e, t) {
|
|
164
|
+
const r = t.find((s) => s.tag === w.transferSyntax);
|
|
165
|
+
if (r && bt.has(N(e, r)))
|
|
166
|
+
throw new P(
|
|
133
167
|
"transfer-syntax",
|
|
134
168
|
`transfer syntax ${N(e, r)} is not explicit VR little endian`
|
|
135
169
|
);
|
|
136
170
|
}
|
|
137
|
-
async function
|
|
171
|
+
async function xt(e, t) {
|
|
138
172
|
let r = Math.min(t, 1048576), s = new Uint8Array(0);
|
|
139
173
|
for (; ; ) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
const
|
|
174
|
+
const o = await e(s.length, r - s.length), c = new Uint8Array(r);
|
|
175
|
+
c.set(s), c.set(o, s.length), s = c;
|
|
176
|
+
const a = [];
|
|
143
177
|
try {
|
|
144
178
|
if (String.fromCharCode(...s.subarray(128, 132)) !== "DICM")
|
|
145
|
-
throw new
|
|
146
|
-
const
|
|
179
|
+
throw new P("not-dicom", "no DICM preamble");
|
|
180
|
+
const n = ut(
|
|
147
181
|
s,
|
|
148
182
|
132,
|
|
149
183
|
t,
|
|
150
184
|
t,
|
|
151
185
|
{ root: 0, item: 0, depth: 0 },
|
|
152
|
-
(
|
|
186
|
+
(l) => a.push(l)
|
|
153
187
|
);
|
|
154
|
-
if (
|
|
155
|
-
return
|
|
156
|
-
} catch (
|
|
157
|
-
if (
|
|
188
|
+
if (n + 20 > s.length) throw new z();
|
|
189
|
+
return mt(s, a), { b: s, pixel: n, elements: a };
|
|
190
|
+
} catch (n) {
|
|
191
|
+
if (mt(s, a), !(n instanceof z)) throw n;
|
|
158
192
|
if (r >= t)
|
|
159
|
-
throw new
|
|
160
|
-
if (r >=
|
|
161
|
-
throw new
|
|
162
|
-
r = Math.min(t,
|
|
193
|
+
throw new P("not-tiled", "no Pixel Data (e.g. a DICOMDIR)");
|
|
194
|
+
if (r >= J)
|
|
195
|
+
throw new P("malformed", `header over ${J >> 20} MiB`);
|
|
196
|
+
r = Math.min(t, J, r * 4);
|
|
163
197
|
}
|
|
164
198
|
}
|
|
165
199
|
}
|
|
166
|
-
async function
|
|
167
|
-
const t = e.size, r = async (
|
|
168
|
-
let
|
|
169
|
-
for (const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
200
|
+
async function Ut(e) {
|
|
201
|
+
const t = e.size, r = async (i, g) => new Uint8Array(await e.slice(i, i + g).arrayBuffer()), { b: s, pixel: o, elements: c } = await xt(r, t), a = /* @__PURE__ */ new Map(), n = [], l = [], f = [];
|
|
202
|
+
let d, m, h, u = 0, E = !1;
|
|
203
|
+
for (const i of c)
|
|
204
|
+
i.depth === 0 ? a.set(i.tag, i) : i.root === w.opticalPathSeq && i.depth === 1 ? (i.tag === w.opticalPathId && (n[i.item] = N(s, i)), i.tag === w.iccProfile && (l[i.item] = { offset: i.offset, length: i.length }), i.tag === w.colorSpace && (f[i.item] = N(s, i))) : i.root === w.shared ? (i.tag === w.spacing && (d = N(s, i)), i.tag === w.opticalPathId && (m = N(s, i)), i.tag === w.zOffset && (h = W(s, i))) : i.root === w.perFrame && (u = Math.max(u, i.item + 1), i.tag === w.columnPosition && (E = !0));
|
|
205
|
+
l.forEach((i, g) => {
|
|
206
|
+
i && f[g] && (i.colorSpace = f[g]);
|
|
173
207
|
});
|
|
174
|
-
const y = (
|
|
175
|
-
const g =
|
|
208
|
+
const y = (i) => {
|
|
209
|
+
const g = a.get(i);
|
|
176
210
|
return g ? N(s, g) : "";
|
|
177
|
-
}, S = (
|
|
178
|
-
const
|
|
179
|
-
if (!Number.isInteger(
|
|
180
|
-
throw new
|
|
181
|
-
return
|
|
182
|
-
}, U = S(
|
|
183
|
-
if (!U || !I) throw new
|
|
184
|
-
const
|
|
185
|
-
if (
|
|
186
|
-
throw new
|
|
187
|
-
const C = S(
|
|
211
|
+
}, S = (i, g, R, j) => {
|
|
212
|
+
const it = a.get(i), B = it ? W(s, it) : g;
|
|
213
|
+
if (!Number.isInteger(B) || B < R || B > j)
|
|
214
|
+
throw new P("malformed", `tag ${i.toString(16)} is ${B}`);
|
|
215
|
+
return B;
|
|
216
|
+
}, U = S(w.columns, 0, 0, 65535), I = S(w.rows, 0, 0, 65535);
|
|
217
|
+
if (!U || !I) throw new P("not-tiled", "no Rows/Columns");
|
|
218
|
+
const x = S(w.frames, 1, 1, Math.floor((t - o) / 8)), D = S(w.totalColumns, U, 1, 4294967295), $ = S(w.totalRows, I, 1, 4294967295), O = Math.ceil(D / U), M = Math.ceil($ / I), b = O * M;
|
|
219
|
+
if (b > K)
|
|
220
|
+
throw new P("malformed", `${b} tiles in one level (over ${K})`);
|
|
221
|
+
const C = S(w.focalPlanes, 1, 1, 65535), et = S(w.concatOffset, 0, 0, 4294967295), nt = y(w.organization), X = nt === "TILED_SPARSE", G = nt === "TILED_FULL" || !X && !E;
|
|
188
222
|
if (G) {
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
throw new
|
|
223
|
+
const i = S(w.opticalPaths, 1, 1, 65535);
|
|
224
|
+
if (et + x > b * C * i)
|
|
225
|
+
throw new P(
|
|
192
226
|
"malformed",
|
|
193
|
-
`frames past ${
|
|
227
|
+
`frames past ${b} tiles x ${C} planes x ${i} paths`
|
|
194
228
|
);
|
|
195
229
|
}
|
|
196
230
|
const L = {
|
|
197
231
|
instance: {
|
|
198
|
-
id: y(
|
|
199
|
-
file:
|
|
232
|
+
id: y(w.sop),
|
|
233
|
+
file: tt(e),
|
|
200
234
|
size: t,
|
|
201
235
|
version: String(e.lastModified),
|
|
202
|
-
transferSyntax: y(
|
|
203
|
-
photometric: y(
|
|
204
|
-
bitsStored: S(
|
|
236
|
+
transferSyntax: y(w.transferSyntax),
|
|
237
|
+
photometric: y(w.photometric),
|
|
238
|
+
bitsStored: S(w.bitsStored, 8, 1, 32)
|
|
205
239
|
},
|
|
206
|
-
study: y(
|
|
207
|
-
series: y(
|
|
208
|
-
frameOfReference: y(
|
|
209
|
-
container: y(
|
|
210
|
-
volume: y(
|
|
240
|
+
study: y(w.study),
|
|
241
|
+
series: y(w.series),
|
|
242
|
+
frameOfReference: y(w.frameOfReference),
|
|
243
|
+
container: y(w.container) || void 0,
|
|
244
|
+
volume: y(w.imageType).includes("VOLUME"),
|
|
211
245
|
totalColumns: D,
|
|
212
246
|
totalRows: $,
|
|
213
247
|
tileWidth: U,
|
|
214
248
|
tileHeight: I,
|
|
215
249
|
columns: O,
|
|
216
250
|
rows: M,
|
|
217
|
-
frameCount:
|
|
251
|
+
frameCount: x,
|
|
218
252
|
sparse: X,
|
|
219
|
-
concatNumber: S(
|
|
220
|
-
pathIds:
|
|
221
|
-
icc:
|
|
222
|
-
column: new Int32Array(
|
|
223
|
-
row: new Int32Array(
|
|
224
|
-
path: new Uint32Array(
|
|
225
|
-
z: new Float64Array(
|
|
253
|
+
concatNumber: S(w.concatNumber, 0, 0, 65535),
|
|
254
|
+
pathIds: n,
|
|
255
|
+
icc: l,
|
|
256
|
+
column: new Int32Array(x),
|
|
257
|
+
row: new Int32Array(x),
|
|
258
|
+
path: new Uint32Array(x),
|
|
259
|
+
z: new Float64Array(x),
|
|
226
260
|
zStated: !1,
|
|
227
261
|
planes: [],
|
|
228
|
-
offset: new Float64Array(
|
|
229
|
-
length: new Uint32Array(
|
|
262
|
+
offset: new Float64Array(x),
|
|
263
|
+
length: new Uint32Array(x)
|
|
230
264
|
};
|
|
231
|
-
if (
|
|
232
|
-
const [
|
|
233
|
-
L.pixelSpacingMM = [g,
|
|
265
|
+
if (d) {
|
|
266
|
+
const [i, g] = d.split("\\").map(Number);
|
|
267
|
+
L.pixelSpacingMM = [g, i];
|
|
234
268
|
}
|
|
235
|
-
const { column: Z, row: Y, path: F, z:
|
|
236
|
-
if (X && !
|
|
237
|
-
throw new
|
|
238
|
-
if (
|
|
239
|
-
throw new
|
|
269
|
+
const { column: Z, row: Y, path: F, z: _ } = L;
|
|
270
|
+
if (X && !u)
|
|
271
|
+
throw new P("malformed", "TILED_SPARSE without per-frame positions");
|
|
272
|
+
if (u && u !== x)
|
|
273
|
+
throw new P(
|
|
240
274
|
"malformed",
|
|
241
|
-
`${
|
|
275
|
+
`${u} per-frame items for ${x} frames`
|
|
242
276
|
);
|
|
243
277
|
if (!G)
|
|
244
278
|
Z.fill(-1), Y.fill(-1);
|
|
245
279
|
else
|
|
246
|
-
for (let
|
|
247
|
-
const g =
|
|
248
|
-
Z[
|
|
280
|
+
for (let i = 0; i < x; i++) {
|
|
281
|
+
const g = et + i, R = g % b;
|
|
282
|
+
Z[i] = R % O, Y[i] = Math.floor(R / O), _[i] = Math.floor(g / b) % C, F[i] = Math.floor(g / (b * C));
|
|
249
283
|
}
|
|
250
284
|
const V = /* @__PURE__ */ new Map();
|
|
251
|
-
|
|
252
|
-
V.has(
|
|
285
|
+
n.forEach((i, g) => {
|
|
286
|
+
V.has(i) || V.set(i, g);
|
|
253
287
|
});
|
|
254
|
-
const
|
|
255
|
-
const g = V.get(
|
|
256
|
-
return g !== void 0 ? g : (V.set(
|
|
288
|
+
const ot = (i) => {
|
|
289
|
+
const g = V.get(i);
|
|
290
|
+
return g !== void 0 ? g : (V.set(i, n.length), n.push(i) - 1);
|
|
257
291
|
};
|
|
258
|
-
|
|
259
|
-
const rt = (
|
|
260
|
-
let T = 0,
|
|
261
|
-
for (const
|
|
262
|
-
if (
|
|
263
|
-
const g =
|
|
264
|
-
|
|
292
|
+
m !== void 0 && F.fill(ot(m));
|
|
293
|
+
const rt = (i, g) => Number.isInteger(i) && i >= 1 && (i - 1) % g === 0 ? (i - 1) / g : -1;
|
|
294
|
+
let T = 0, st = -1;
|
|
295
|
+
for (const i of c) {
|
|
296
|
+
if (i.root !== w.perFrame) continue;
|
|
297
|
+
const g = i.item;
|
|
298
|
+
i.tag === w.columnPosition ? Z[g] = rt(W(s, i), U) : i.tag === w.rowPosition ? Y[g] = rt(W(s, i), I) : i.tag === w.zOffset ? (_[g] = W(s, i), g !== st && T++, st = g) : i.tag === w.opticalPathId && (F[g] = ot(N(s, i)));
|
|
265
299
|
}
|
|
266
|
-
if (T && T !==
|
|
267
|
-
throw new
|
|
268
|
-
const
|
|
269
|
-
|
|
300
|
+
if (T && T !== x)
|
|
301
|
+
throw new P("malformed", `ZOffset on ${T} of ${x} frames`);
|
|
302
|
+
const at = !T && h !== void 0 && (!G || C === 1);
|
|
303
|
+
at && _.fill(h), L.zStated = T > 0 || at;
|
|
270
304
|
const { planes: H } = L;
|
|
271
|
-
let
|
|
272
|
-
for (let
|
|
273
|
-
const g = Math.round(
|
|
305
|
+
let St = 0;
|
|
306
|
+
for (let i = 0; i < x; i++) {
|
|
307
|
+
const g = Math.round(_[i] * 1e3) / 1e3;
|
|
274
308
|
if (!Number.isFinite(g))
|
|
275
|
-
throw new
|
|
276
|
-
|
|
277
|
-
const R = F[
|
|
309
|
+
throw new P("malformed", `frame ${i + 1} has no finite focal plane`);
|
|
310
|
+
_[i] = g;
|
|
311
|
+
const R = F[i];
|
|
278
312
|
H[R] ?? (H[R] = /* @__PURE__ */ new Set());
|
|
279
313
|
const j = H[R];
|
|
280
|
-
if (!j.has(g) && (j.add(g), ++
|
|
281
|
-
throw new
|
|
314
|
+
if (!j.has(g) && (j.add(g), ++St > ht))
|
|
315
|
+
throw new P(
|
|
282
316
|
"malformed",
|
|
283
|
-
`over ${
|
|
317
|
+
`over ${ht} (optical path, focal plane) pairs`
|
|
284
318
|
);
|
|
285
319
|
}
|
|
286
|
-
return await
|
|
320
|
+
return await Ot(L, s, o, a, r), L;
|
|
287
321
|
}
|
|
288
|
-
async function
|
|
289
|
-
const { frameCount:
|
|
290
|
-
if (
|
|
291
|
-
throw new
|
|
292
|
-
if (v(t, r + 8) !==
|
|
293
|
-
throw new
|
|
294
|
-
if (
|
|
295
|
-
throw new
|
|
296
|
-
const
|
|
297
|
-
if (
|
|
298
|
-
throw new
|
|
299
|
-
const
|
|
300
|
-
const O = await
|
|
301
|
-
return { tag:
|
|
302
|
-
},
|
|
303
|
-
if (
|
|
304
|
-
const $ = new DataView(t.buffer, t.byteOffset +
|
|
322
|
+
async function Ot(e, t, r, s, o) {
|
|
323
|
+
const { frameCount: c, offset: a, length: n } = e, { size: l } = e.instance, f = String.fromCharCode(t[r + 4], t[r + 5]);
|
|
324
|
+
if (f !== "OB" && f !== "OW")
|
|
325
|
+
throw new P("malformed", `Pixel Data has VR ${f}`);
|
|
326
|
+
if (v(t, r + 8) !== k)
|
|
327
|
+
throw new P("uncompressed", "native (uncompressed) pixel data");
|
|
328
|
+
if (p(A(t, r + 12), A(t, r + 14)) !== w.item)
|
|
329
|
+
throw new P("malformed", "no Basic Offset Table item");
|
|
330
|
+
const d = v(t, r + 16), m = r + 20 + d;
|
|
331
|
+
if (m > l)
|
|
332
|
+
throw new P("malformed", "the Basic Offset Table runs past the file");
|
|
333
|
+
const h = async ($) => {
|
|
334
|
+
const O = await o($, 8);
|
|
335
|
+
return { tag: p(A(O, 0), A(O, 2)), length: v(O, 4) };
|
|
336
|
+
}, u = s.get(w.eot), E = s.get(w.eotLengths);
|
|
337
|
+
if (u && E && u.length >= 8 * c && E.length >= 8 * c) {
|
|
338
|
+
const $ = new DataView(t.buffer, t.byteOffset + u.offset, u.length), O = new DataView(
|
|
305
339
|
t.buffer,
|
|
306
340
|
t.byteOffset + E.offset,
|
|
307
341
|
E.length
|
|
308
342
|
);
|
|
309
343
|
let M = !0;
|
|
310
|
-
for (let
|
|
311
|
-
|
|
312
|
-
const C = Number(O.getBigUint64(8 *
|
|
313
|
-
M = C <= 4294967295 &&
|
|
344
|
+
for (let b = 0; b < c && M; b++) {
|
|
345
|
+
a[b] = m + Number($.getBigUint64(8 * b, !0)) + 8;
|
|
346
|
+
const C = Number(O.getBigUint64(8 * b, !0));
|
|
347
|
+
M = C <= 4294967295 && a[b] + C <= l, n[b] = C;
|
|
314
348
|
}
|
|
315
349
|
if (M) return;
|
|
316
|
-
} else if (
|
|
317
|
-
const $ = await
|
|
318
|
-
for (let M = 0; M <
|
|
319
|
-
let O = v($, 0) === 0 && c
|
|
320
|
-
for (let M = 0; M + 1 <
|
|
321
|
-
const
|
|
322
|
-
O =
|
|
350
|
+
} else if (d === 4 * c && d > 0) {
|
|
351
|
+
const $ = await o(r + 20, d);
|
|
352
|
+
for (let M = 0; M < c; M++) a[M] = m + v($, 4 * M) + 8;
|
|
353
|
+
let O = v($, 0) === 0 && a[c - 1] <= l;
|
|
354
|
+
for (let M = 0; M + 1 < c && O; M++) {
|
|
355
|
+
const b = a[M + 1] - 8 - a[M];
|
|
356
|
+
O = b >= 0, n[M] = b;
|
|
323
357
|
}
|
|
324
358
|
if (O) {
|
|
325
|
-
let M = c
|
|
326
|
-
for (; M + 8 <=
|
|
327
|
-
const
|
|
328
|
-
if (
|
|
329
|
-
M += 8 +
|
|
359
|
+
let M = a[c - 1] - 8;
|
|
360
|
+
for (; M + 8 <= l; ) {
|
|
361
|
+
const b = await h(M);
|
|
362
|
+
if (b.tag !== w.item || M + 8 + b.length > l) break;
|
|
363
|
+
M += 8 + b.length;
|
|
330
364
|
}
|
|
331
|
-
if (M > c
|
|
332
|
-
|
|
365
|
+
if (M > a[c - 1] - 8) {
|
|
366
|
+
n[c - 1] = M - a[c - 1];
|
|
333
367
|
return;
|
|
334
368
|
}
|
|
335
369
|
}
|
|
336
370
|
}
|
|
337
371
|
let y = new Uint8Array(0), S = 0, U = 0;
|
|
338
|
-
const I = async ($) => (($ < S || $ + 12 > S + y.length) && (S = $, y = await
|
|
339
|
-
let
|
|
372
|
+
const I = async ($) => (($ < S || $ + 12 > S + y.length) && (S = $, y = await o($, Math.min(U < 4096 ? 65536 : 12, l - $))), y.subarray($ - S, $ - S + 12));
|
|
373
|
+
let x = m, D = -1;
|
|
340
374
|
for (; ; ) {
|
|
341
|
-
const $ = await I(
|
|
342
|
-
if (O ===
|
|
343
|
-
if (O !==
|
|
344
|
-
throw new
|
|
375
|
+
const $ = await I(x), O = p(A($, 0), A($, 2));
|
|
376
|
+
if (O === w.sequenceEnd) break;
|
|
377
|
+
if (O !== w.item)
|
|
378
|
+
throw new P("malformed", `no fragment item at byte ${x}`);
|
|
345
379
|
const M = v($, 4);
|
|
346
|
-
if (U = M,
|
|
347
|
-
throw new
|
|
380
|
+
if (U = M, x + 8 + M > l)
|
|
381
|
+
throw new P(
|
|
348
382
|
"malformed",
|
|
349
|
-
`fragment past the end of the file at byte ${
|
|
383
|
+
`fragment past the end of the file at byte ${x}`
|
|
350
384
|
);
|
|
351
385
|
if (($[8] === 255 && $[9] === 216 || // JPEG and JPEG-LS SOI
|
|
352
386
|
$[8] === 255 && $[9] === 79 && $[10] === 255 && $[11] === 81 || // J2K start-of-codestream + image-size markers
|
|
353
|
-
$[8] === 0 && $[9] === 0 && $[10] === 0 && $[11] === 12) && D + 1 <
|
|
354
|
-
throw new
|
|
387
|
+
$[8] === 0 && $[9] === 0 && $[10] === 0 && $[11] === 12) && D + 1 < c && (a[++D] = x + 8), D < 0)
|
|
388
|
+
throw new P(
|
|
355
389
|
"malformed",
|
|
356
390
|
"the first fragment does not start a codestream"
|
|
357
391
|
);
|
|
358
|
-
|
|
392
|
+
n[D] = x + 8 + M - a[D], x += 8 + M;
|
|
359
393
|
}
|
|
360
|
-
if (D + 1 !==
|
|
361
|
-
throw new
|
|
394
|
+
if (D + 1 !== c)
|
|
395
|
+
throw new P("malformed", `${D + 1} codestreams for ${c} frames`);
|
|
362
396
|
}
|
|
363
|
-
async function
|
|
397
|
+
async function Lt(e) {
|
|
364
398
|
const t = [], r = [];
|
|
365
399
|
for (const s of e)
|
|
366
400
|
try {
|
|
367
|
-
t.push(await
|
|
368
|
-
} catch (
|
|
401
|
+
t.push(await Ut(s));
|
|
402
|
+
} catch (o) {
|
|
369
403
|
r.push({
|
|
370
|
-
file:
|
|
371
|
-
reason:
|
|
372
|
-
message:
|
|
404
|
+
file: tt(s),
|
|
405
|
+
reason: o instanceof P ? o.reason : "malformed",
|
|
406
|
+
message: o instanceof Error ? o.message : String(o)
|
|
373
407
|
});
|
|
374
408
|
}
|
|
375
409
|
return {
|
|
376
410
|
version: 1,
|
|
377
411
|
producer: "nv-ext-tile/indexDicomWsi",
|
|
378
|
-
slides:
|
|
412
|
+
slides: Dt(t, r),
|
|
379
413
|
skipped: r
|
|
380
414
|
};
|
|
381
415
|
}
|
|
382
|
-
const
|
|
416
|
+
const wt = (e, t) => e.pathIds[t] ?? String(t), Q = (e, t) => [
|
|
383
417
|
e.study,
|
|
384
418
|
e.series,
|
|
385
419
|
e.frameOfReference,
|
|
386
420
|
e.container ?? "",
|
|
387
|
-
|
|
421
|
+
wt(e, t)
|
|
388
422
|
].join("|");
|
|
389
|
-
function
|
|
390
|
-
const t = e.filter((
|
|
391
|
-
for (const
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
for (const
|
|
395
|
-
r.set(
|
|
423
|
+
function At(e) {
|
|
424
|
+
const t = e.filter((o) => o.volume && o.zStated), r = /* @__PURE__ */ new Map();
|
|
425
|
+
for (const o of t)
|
|
426
|
+
o.planes.forEach((c, a) => {
|
|
427
|
+
const n = Q(o, a), l = r.get(n) ?? /* @__PURE__ */ new Set();
|
|
428
|
+
for (const f of c) l.add(f);
|
|
429
|
+
r.set(n, l);
|
|
396
430
|
});
|
|
397
431
|
const s = new Map(
|
|
398
|
-
[...r].map(([
|
|
399
|
-
|
|
400
|
-
new Map([...
|
|
432
|
+
[...r].map(([o, c]) => [
|
|
433
|
+
o,
|
|
434
|
+
new Map([...c].sort((a, n) => a - n).map((a, n) => [a, n]))
|
|
401
435
|
])
|
|
402
436
|
);
|
|
403
|
-
for (const
|
|
404
|
-
const
|
|
405
|
-
(
|
|
406
|
-
), { z:
|
|
407
|
-
for (let
|
|
408
|
-
const
|
|
409
|
-
|
|
437
|
+
for (const o of t) {
|
|
438
|
+
const c = o.planes.map(
|
|
439
|
+
(l, f) => s.get(Q(o, f))
|
|
440
|
+
), { z: a, path: n } = o;
|
|
441
|
+
for (let l = 0; l < o.frameCount; l++) {
|
|
442
|
+
const f = c[n[l]];
|
|
443
|
+
a[l] = f.get(a[l]);
|
|
410
444
|
}
|
|
411
445
|
}
|
|
412
446
|
}
|
|
413
|
-
function
|
|
414
|
-
|
|
447
|
+
function Dt(e, t) {
|
|
448
|
+
At(e);
|
|
415
449
|
const r = /* @__PURE__ */ new Map();
|
|
416
|
-
for (const
|
|
417
|
-
if (!
|
|
418
|
-
const
|
|
419
|
-
for (let
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
|
|
450
|
+
for (const o of e) {
|
|
451
|
+
if (!o.volume) continue;
|
|
452
|
+
const c = [];
|
|
453
|
+
for (let a = 0; a < o.frameCount; a++) {
|
|
454
|
+
const n = o.path[a];
|
|
455
|
+
c[n] ?? (c[n] = /* @__PURE__ */ new Map());
|
|
456
|
+
const l = c[n], f = o.z[a], d = l.get(f);
|
|
457
|
+
d ? d.push(a) : l.set(f, [a]);
|
|
424
458
|
}
|
|
425
|
-
|
|
426
|
-
const
|
|
427
|
-
for (const [
|
|
428
|
-
const
|
|
429
|
-
let
|
|
430
|
-
if (!
|
|
431
|
-
const { study: E, series: y, frameOfReference: S, container: U } =
|
|
432
|
-
|
|
459
|
+
c.forEach((a, n) => {
|
|
460
|
+
const l = wt(o, n), f = Q(o, n);
|
|
461
|
+
for (const [d, m] of a) {
|
|
462
|
+
const h = `${f}|${d}`;
|
|
463
|
+
let u = r.get(h);
|
|
464
|
+
if (!u) {
|
|
465
|
+
const { study: E, series: y, frameOfReference: S, container: U } = o;
|
|
466
|
+
u = {
|
|
433
467
|
slide: {
|
|
434
468
|
study: E,
|
|
435
469
|
series: y,
|
|
436
470
|
frameOfReference: S,
|
|
437
471
|
container: U,
|
|
438
|
-
opticalPath:
|
|
439
|
-
focalPlane:
|
|
472
|
+
opticalPath: l,
|
|
473
|
+
focalPlane: d,
|
|
440
474
|
instances: [],
|
|
441
475
|
levels: []
|
|
442
476
|
},
|
|
443
477
|
own: []
|
|
444
|
-
}, r.set(
|
|
478
|
+
}, r.set(h, u);
|
|
445
479
|
}
|
|
446
|
-
|
|
480
|
+
u.own.push({ info: o, pathIndex: n, frames: m });
|
|
447
481
|
}
|
|
448
482
|
});
|
|
449
483
|
}
|
|
450
484
|
const s = /* @__PURE__ */ new Set();
|
|
451
|
-
return [...r.values()].map(({ slide:
|
|
452
|
-
|
|
453
|
-
(
|
|
485
|
+
return [...r.values()].map(({ slide: o, own: c }) => {
|
|
486
|
+
c.sort(
|
|
487
|
+
(m, h) => h.info.totalColumns * h.info.totalRows - m.info.totalColumns * m.info.totalRows || m.info.concatNumber - h.info.concatNumber
|
|
454
488
|
);
|
|
455
|
-
const
|
|
456
|
-
let
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
let
|
|
460
|
-
return
|
|
461
|
-
file:
|
|
489
|
+
const a = /* @__PURE__ */ new Map();
|
|
490
|
+
let n = 0;
|
|
491
|
+
const l = c.filter(({ info: m }) => {
|
|
492
|
+
const h = `${m.totalColumns}x${m.totalRows}`;
|
|
493
|
+
let u = a.get(h);
|
|
494
|
+
return u === void 0 && (n += m.columns * m.rows, u = n <= lt, a.set(h, u)), !u && !s.has(m.instance.file) && (s.add(m.instance.file), t.push({
|
|
495
|
+
file: m.instance.file,
|
|
462
496
|
reason: "malformed",
|
|
463
|
-
message: `level ${
|
|
464
|
-
})),
|
|
497
|
+
message: `level ${h} takes its slide past ${lt} tiles`
|
|
498
|
+
})), u;
|
|
465
499
|
});
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
E ? E.push(
|
|
500
|
+
o.instances = l.map((m) => m.info.instance);
|
|
501
|
+
const f = /* @__PURE__ */ new Map();
|
|
502
|
+
l.forEach(({ info: m }, h) => {
|
|
503
|
+
const u = `${m.totalColumns}x${m.totalRows}`, E = f.get(u);
|
|
504
|
+
E ? E.push(h) : f.set(u, [h]);
|
|
471
505
|
});
|
|
472
|
-
for (const
|
|
473
|
-
const
|
|
474
|
-
return
|
|
506
|
+
for (const m of f.values()) o.levels.push(It(m, l));
|
|
507
|
+
const d = l[0];
|
|
508
|
+
return o.pixelSpacingMM = d.info.pixelSpacingMM, o.iccProfile = d.info.icc[d.pathIndex], o;
|
|
475
509
|
});
|
|
476
510
|
}
|
|
477
|
-
function
|
|
478
|
-
const r = t[e[0]].info, { columns: s, rows:
|
|
479
|
-
instance: new Uint32Array(
|
|
480
|
-
frame: new Uint32Array(
|
|
481
|
-
offset: new Float64Array(
|
|
482
|
-
length: new Uint32Array(
|
|
483
|
-
tile: new Int32Array(
|
|
511
|
+
function It(e, t) {
|
|
512
|
+
const r = t[e[0]].info, { columns: s, rows: o } = r, c = e.reduce((h, u) => h + t[u].frames.length, 0), a = {
|
|
513
|
+
instance: new Uint32Array(c),
|
|
514
|
+
frame: new Uint32Array(c),
|
|
515
|
+
offset: new Float64Array(c),
|
|
516
|
+
length: new Uint32Array(c),
|
|
517
|
+
tile: new Int32Array(c)
|
|
484
518
|
};
|
|
485
|
-
let
|
|
486
|
-
for (const
|
|
487
|
-
const
|
|
488
|
-
for (const y of
|
|
519
|
+
let n = 0;
|
|
520
|
+
for (const h of e) {
|
|
521
|
+
const u = t[h], { info: E } = u;
|
|
522
|
+
for (const y of u.frames) {
|
|
489
523
|
const S = E.column[y], U = E.row[y];
|
|
490
|
-
|
|
524
|
+
a.instance[n] = h, a.frame[n] = y + 1, a.offset[n] = E.offset[y], a.length[n] = E.length[y], a.tile[n] = S < 0 || U < 0 || S >= s || U >= o ? -1 : U * s + S, n++;
|
|
491
525
|
}
|
|
492
526
|
}
|
|
493
|
-
const
|
|
494
|
-
let
|
|
495
|
-
for (const
|
|
496
|
-
|
|
497
|
-
const
|
|
527
|
+
const l = a.tile.slice().sort();
|
|
528
|
+
let f = 0, d = -1;
|
|
529
|
+
for (const h of l)
|
|
530
|
+
h > d && (f++, d = h);
|
|
531
|
+
const m = s * o - f;
|
|
498
532
|
return {
|
|
499
533
|
width: r.totalColumns,
|
|
500
534
|
height: r.totalRows,
|
|
501
535
|
tileWidth: r.tileWidth,
|
|
502
536
|
tileHeight: r.tileHeight,
|
|
503
537
|
columns: s,
|
|
504
|
-
rows:
|
|
538
|
+
rows: o,
|
|
505
539
|
instances: e,
|
|
506
|
-
frames:
|
|
507
|
-
duplicates:
|
|
508
|
-
holes:
|
|
509
|
-
sparse:
|
|
540
|
+
frames: a,
|
|
541
|
+
duplicates: c - f,
|
|
542
|
+
holes: m,
|
|
543
|
+
sparse: m > 0 || e.some((h) => t[h].info.sparse)
|
|
510
544
|
};
|
|
511
545
|
}
|
|
512
|
-
function
|
|
546
|
+
function dt(e) {
|
|
513
547
|
const t = new Int32Array(e.columns * e.rows).fill(-1), { tile: r } = e.frames;
|
|
514
548
|
for (let s = r.length - 1; s >= 0; s--) {
|
|
515
|
-
const
|
|
516
|
-
|
|
549
|
+
const o = r[s];
|
|
550
|
+
o >= 0 && (t[o] = s);
|
|
517
551
|
}
|
|
518
552
|
return t;
|
|
519
553
|
}
|
|
520
|
-
const
|
|
554
|
+
const Ct = {
|
|
521
555
|
"1.2.840.10008.1.2.4.50": "image/jpeg",
|
|
522
556
|
// JPEG Baseline (Process 1)
|
|
523
557
|
"1.2.840.10008.1.2.4.51": "image/jpeg",
|
|
@@ -531,34 +565,34 @@ const Nt = {
|
|
|
531
565
|
"1.2.840.10008.1.2.4.91": "image/jp2"
|
|
532
566
|
// JPEG 2000
|
|
533
567
|
};
|
|
534
|
-
function
|
|
535
|
-
const r = new Map(t.map((s) => [
|
|
568
|
+
function pt(e, t) {
|
|
569
|
+
const r = new Map(t.map((s) => [tt(s), s]));
|
|
536
570
|
return e.instances.map((s) => {
|
|
537
|
-
const
|
|
538
|
-
if (!
|
|
539
|
-
if (
|
|
571
|
+
const o = r.get(s.file);
|
|
572
|
+
if (!o) throw new Error(`${s.file} is not among the files`);
|
|
573
|
+
if (o.size !== s.size || String(o.lastModified) !== s.version)
|
|
540
574
|
throw new Error(`${s.file} changed since it was indexed`);
|
|
541
|
-
return
|
|
575
|
+
return o;
|
|
542
576
|
});
|
|
543
577
|
}
|
|
544
|
-
function
|
|
578
|
+
function gt(e, t) {
|
|
545
579
|
const r = new DataView(e.buffer, e.byteOffset, e.byteLength), s = [];
|
|
546
|
-
for (let
|
|
547
|
-
if (
|
|
580
|
+
for (let o = 0; o < e.length; ) {
|
|
581
|
+
if (o + 8 > e.length || r.getUint16(o, !0) !== 65534 || r.getUint16(o + 2, !0) !== 57344)
|
|
548
582
|
throw new Error(
|
|
549
|
-
|
|
583
|
+
o === 0 ? `${t} is not where the index says` : `${t}: no fragment item at byte ${o}`
|
|
550
584
|
);
|
|
551
|
-
const
|
|
552
|
-
if (
|
|
585
|
+
const c = r.getUint32(o + 4, !0);
|
|
586
|
+
if (o + 8 + c > e.length)
|
|
553
587
|
throw new Error(`${t}: a fragment runs past its span`);
|
|
554
|
-
s.push({ offset:
|
|
588
|
+
s.push({ offset: o + 8, length: c }), o += 8 + c;
|
|
555
589
|
}
|
|
556
590
|
if (!s.length) throw new Error(`${t}: an empty span`);
|
|
557
591
|
return s;
|
|
558
592
|
}
|
|
559
|
-
class
|
|
593
|
+
class Tt {
|
|
560
594
|
constructor(t, r) {
|
|
561
|
-
this.slide = t, this.host = null, this.files =
|
|
595
|
+
this.slide = t, this.host = null, this.files = pt(t, r), this.maps = t.levels.map(dt), this.manifest = yt(t, this.maps);
|
|
562
596
|
}
|
|
563
597
|
bind(t) {
|
|
564
598
|
this.host = t;
|
|
@@ -567,144 +601,144 @@ class Bt {
|
|
|
567
601
|
async fetchIccProfile(t) {
|
|
568
602
|
const r = this.slide.iccProfile, s = this.slide.levels[0];
|
|
569
603
|
if (!r || !s) return null;
|
|
570
|
-
const
|
|
571
|
-
await
|
|
604
|
+
const o = this.files[s.instances[0]], c = new Uint8Array(
|
|
605
|
+
await o.slice(r.offset, r.offset + r.length).arrayBuffer()
|
|
572
606
|
);
|
|
573
|
-
return t == null || t.throwIfAborted(),
|
|
607
|
+
return t == null || t.throwIfAborted(), c;
|
|
574
608
|
}
|
|
575
|
-
async fetchTileBytes(t, r, s,
|
|
609
|
+
async fetchTileBytes(t, r, s, o) {
|
|
576
610
|
var y;
|
|
577
|
-
const { frames:
|
|
578
|
-
if (!(
|
|
579
|
-
const
|
|
580
|
-
await
|
|
611
|
+
const { frames: c } = this.slide.levels[t.index], n = this.maps[t.index][r.y * t.columns + r.x];
|
|
612
|
+
if (!(n >= 0)) throw new Error(`no frame for tile ${r.x},${r.y}`);
|
|
613
|
+
const l = this.files[c.instance[n]], f = c.offset[n], d = c.length[n], m = new Uint8Array(
|
|
614
|
+
await l.slice(f - 8, f + d).arrayBuffer()
|
|
581
615
|
);
|
|
582
|
-
|
|
583
|
-
const
|
|
584
|
-
if (
|
|
585
|
-
const
|
|
616
|
+
o == null || o.throwIfAborted(), (y = this.host) == null || y.addWireBytes(m.byteLength);
|
|
617
|
+
const h = gt(m, `${l.name} frame ${c.frame[n]}`);
|
|
618
|
+
if (h.length === 1) return m.subarray(8);
|
|
619
|
+
const u = new Uint8Array(h.reduce((S, U) => S + U.length, 0));
|
|
586
620
|
let E = 0;
|
|
587
|
-
for (const S of
|
|
588
|
-
|
|
589
|
-
return
|
|
621
|
+
for (const S of h)
|
|
622
|
+
u.set(m.subarray(S.offset, S.offset + S.length), E), E += S.length;
|
|
623
|
+
return u;
|
|
590
624
|
}
|
|
591
625
|
}
|
|
592
|
-
function
|
|
626
|
+
function yt(e, t, {
|
|
593
627
|
format: r = "dicom-wsi-index-v1",
|
|
594
|
-
fileUrl: s = (
|
|
628
|
+
fileUrl: s = (o) => o.file
|
|
595
629
|
} = {}) {
|
|
596
|
-
const
|
|
597
|
-
if (!
|
|
598
|
-
const
|
|
599
|
-
const
|
|
600
|
-
if (!
|
|
630
|
+
const o = e.levels[0];
|
|
631
|
+
if (!o) throw new Error("the slide has no levels");
|
|
632
|
+
const c = e.instances[o.instances[0]], a = e.levels.map((n, l) => {
|
|
633
|
+
const f = e.instances[n.instances[0]], d = Ct[f.transferSyntax];
|
|
634
|
+
if (!d)
|
|
601
635
|
throw new Error(
|
|
602
|
-
`transfer syntax ${
|
|
636
|
+
`transfer syntax ${f.transferSyntax} cannot be decoded (JPEG, JPEG 2000 and JPEG-LS are supported)`
|
|
603
637
|
);
|
|
604
|
-
const
|
|
605
|
-
t[
|
|
606
|
-
(
|
|
607
|
-
if (
|
|
608
|
-
const E =
|
|
638
|
+
const m = Array.from(
|
|
639
|
+
t[l],
|
|
640
|
+
(h, u) => {
|
|
641
|
+
if (h < 0) return null;
|
|
642
|
+
const E = u % n.columns, y = Math.floor(u / n.columns);
|
|
609
643
|
return {
|
|
610
644
|
x: E,
|
|
611
645
|
y,
|
|
612
|
-
width: Math.min(
|
|
646
|
+
width: Math.min(n.tileWidth, n.width - E * n.tileWidth),
|
|
613
647
|
height: Math.min(
|
|
614
|
-
|
|
615
|
-
|
|
648
|
+
n.tileHeight,
|
|
649
|
+
n.height - y * n.tileHeight
|
|
616
650
|
),
|
|
617
|
-
frame:
|
|
651
|
+
frame: n.frames.frame[h]
|
|
618
652
|
};
|
|
619
653
|
}
|
|
620
654
|
);
|
|
621
655
|
return {
|
|
622
|
-
index:
|
|
623
|
-
width:
|
|
624
|
-
height:
|
|
625
|
-
downsample:
|
|
626
|
-
tileWidth:
|
|
627
|
-
tileHeight:
|
|
628
|
-
columns:
|
|
629
|
-
rows:
|
|
630
|
-
codec:
|
|
631
|
-
transferSyntaxUid:
|
|
632
|
-
photometric:
|
|
633
|
-
bitsStored:
|
|
634
|
-
frames:
|
|
656
|
+
index: l,
|
|
657
|
+
width: n.width,
|
|
658
|
+
height: n.height,
|
|
659
|
+
downsample: o.width / n.width,
|
|
660
|
+
tileWidth: n.tileWidth,
|
|
661
|
+
tileHeight: n.tileHeight,
|
|
662
|
+
columns: n.columns,
|
|
663
|
+
rows: n.rows,
|
|
664
|
+
codec: d,
|
|
665
|
+
transferSyntaxUid: f.transferSyntax,
|
|
666
|
+
photometric: f.photometric,
|
|
667
|
+
bitsStored: f.bitsStored,
|
|
668
|
+
frames: n.frames.offset.length,
|
|
635
669
|
// The index made one slide per optical path and focal plane.
|
|
636
670
|
opticalPaths: 1,
|
|
637
671
|
focalPlanes: 1,
|
|
638
|
-
...
|
|
639
|
-
tiles:
|
|
672
|
+
...n.sparse ? { sparse: !0 } : {},
|
|
673
|
+
tiles: m
|
|
640
674
|
};
|
|
641
675
|
});
|
|
642
676
|
return {
|
|
643
677
|
id: `${e.series}/${e.opticalPath}/${e.focalPlane}`,
|
|
644
678
|
name: e.container ?? e.series,
|
|
645
679
|
format: r,
|
|
646
|
-
width:
|
|
647
|
-
height:
|
|
680
|
+
width: o.width,
|
|
681
|
+
height: o.height,
|
|
648
682
|
// Row 0 at the top, as OpenSlide and the TIFF/DZI sources show a slide.
|
|
649
683
|
displayYAxis: "down",
|
|
650
|
-
tileSize:
|
|
684
|
+
tileSize: o.tileWidth,
|
|
651
685
|
dtype: "uint8",
|
|
652
686
|
channels: "encoded-rgb",
|
|
653
687
|
...e.pixelSpacingMM ? { pixelSpacingMM: e.pixelSpacingMM } : {},
|
|
654
|
-
...e.iccProfile ? { iccProfile: { url: s(
|
|
655
|
-
levels:
|
|
688
|
+
...e.iccProfile ? { iccProfile: { url: s(c), ...e.iccProfile } } : {},
|
|
689
|
+
levels: a
|
|
656
690
|
};
|
|
657
691
|
}
|
|
658
692
|
async function kt(e, t, r) {
|
|
659
|
-
const s =
|
|
693
|
+
const s = pt(e, t), o = e.levels.map(dt), c = yt(e, o, {
|
|
660
694
|
format: "dicom-wsi-range-v1",
|
|
661
695
|
fileUrl: r
|
|
662
696
|
});
|
|
663
|
-
for (const [
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
e.instances[
|
|
697
|
+
for (const [a, n] of c.levels.entries()) {
|
|
698
|
+
const l = e.levels[a], { frames: f } = l, d = l.instances.length > 1;
|
|
699
|
+
d || (n.fileUrl = r(
|
|
700
|
+
e.instances[l.instances[0]]
|
|
667
701
|
));
|
|
668
|
-
const
|
|
669
|
-
for (const [
|
|
670
|
-
if (
|
|
671
|
-
const E =
|
|
702
|
+
const m = [];
|
|
703
|
+
for (const [h, u] of o[a].entries()) {
|
|
704
|
+
if (u < 0) continue;
|
|
705
|
+
const E = n.tiles[h], y = f.instance[u], S = await Rt(
|
|
672
706
|
s[y],
|
|
673
|
-
|
|
674
|
-
|
|
707
|
+
f.offset[u],
|
|
708
|
+
f.length[u]
|
|
675
709
|
);
|
|
676
|
-
|
|
710
|
+
m.push({
|
|
677
711
|
...E,
|
|
678
712
|
...S,
|
|
679
|
-
...
|
|
713
|
+
...d ? { fileUrl: r(e.instances[y]) } : {}
|
|
680
714
|
});
|
|
681
715
|
}
|
|
682
|
-
|
|
716
|
+
n.tiles = m;
|
|
683
717
|
}
|
|
684
|
-
return
|
|
718
|
+
return c;
|
|
685
719
|
}
|
|
686
|
-
async function
|
|
720
|
+
async function Rt(e, t, r) {
|
|
687
721
|
const s = new Uint8Array(
|
|
688
722
|
await e.slice(t - 8, t).arrayBuffer()
|
|
689
|
-
),
|
|
690
|
-
if (
|
|
691
|
-
if (
|
|
692
|
-
throw new Error(`${
|
|
723
|
+
), o = `${e.name} at byte ${t - 8}`, c = new DataView(s.buffer);
|
|
724
|
+
if (c.getUint32(4, !0) === r) {
|
|
725
|
+
if (c.getUint32(0, !0) !== 3758161918)
|
|
726
|
+
throw new Error(`${o}: no item where the index says`);
|
|
693
727
|
return { offset: t, length: r };
|
|
694
728
|
}
|
|
695
|
-
const
|
|
729
|
+
const a = new Uint8Array(
|
|
696
730
|
await e.slice(t - 8, t + r).arrayBuffer()
|
|
697
731
|
);
|
|
698
732
|
return {
|
|
699
|
-
fragments:
|
|
700
|
-
offset: t - 8 +
|
|
701
|
-
length:
|
|
733
|
+
fragments: gt(a, o).map((l) => ({
|
|
734
|
+
offset: t - 8 + l.offset,
|
|
735
|
+
length: l.length
|
|
702
736
|
}))
|
|
703
737
|
};
|
|
704
738
|
}
|
|
705
739
|
export {
|
|
706
|
-
|
|
707
|
-
|
|
740
|
+
Tt as DicomWsiSource,
|
|
741
|
+
Lt as indexDicomWsi,
|
|
708
742
|
kt as manifestFromIndex,
|
|
709
|
-
|
|
743
|
+
vt as registerDcm2niixDecoders
|
|
710
744
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@niivue/nv-ext-tile",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Tiled image formats for NiiVue's NVSlide: pyramidal TIFF/OME-TIFF/SVS by HTTP Range (/tiff) and DICOM-WSI folders and hosted manifests (/dicom)",
|
|
6
6
|
"exports": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"comment:niivue-peer": "workspace:^, not workspace:* (which bun publish pins to the exact rc): published as ^<core version at publish>, so one release accepts later niivue rcs and 1.x, and the floor never predates an API this package uses.",
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"@niivue/dcm2niix": "^1.4.20261105-rc.3",
|
|
22
|
-
"@niivue/niivue": "^1.0.0-rc.
|
|
22
|
+
"@niivue/niivue": "^1.0.0-rc.117"
|
|
23
23
|
},
|
|
24
24
|
"peerDependenciesMeta": {
|
|
25
25
|
"@niivue/dcm2niix": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@niivue/dcm2niix": "^1.4.20261105-rc.3",
|
|
34
|
-
"@niivue/niivue": "1.0.0-rc.
|
|
34
|
+
"@niivue/niivue": "1.0.0-rc.117",
|
|
35
35
|
"@types/bun": "^1.3.12",
|
|
36
36
|
"typescript": "~5.8.3",
|
|
37
37
|
"vite": "^6.3.2",
|