dicom-synth 1.18.0 → 1.19.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 +1 -1
- package/dist/esm/collection/generate.js +977 -0
- package/dist/esm/collection/writer.js +243 -109
- package/dist/esm/describe/describe.js +142 -5
- package/dist/esm/index.js +396 -223
- package/dist/esm/nonStandardDicom/dicomdir.js +14 -15
- package/dist/esm/platform/bytes.js +39 -0
- package/dist/esm/platform/random.js +22 -0
- package/dist/esm/platform/sha256.js +131 -0
- package/dist/esm/schema/parametric.js +73 -4
- package/dist/esm/syntheticFixtures/generator.js +31 -14
- package/dist/esm/syntheticFixtures/streamWrite.js +156 -13
- package/dist/esm/syntheticFixtures/uid.js +148 -4
- package/dist/esm/syntheticFixtures/violations.js +18 -6
- package/dist/types/collection/generate.d.ts +43 -0
- package/dist/types/collection/writer.d.ts +0 -27
- package/dist/types/index.d.ts +2 -1
- package/dist/types/nonStandardDicom/dicomdir.d.ts +1 -6
- package/dist/types/platform/bytes.d.ts +4 -0
- package/dist/types/platform/random.d.ts +2 -0
- package/dist/types/platform/sha256.d.ts +1 -0
- package/dist/types/syntheticFixtures/generator.d.ts +2 -2
- package/dist/types/syntheticFixtures/violations.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,90 @@
|
|
|
1
1
|
// src/collection/writer.ts
|
|
2
|
-
import {
|
|
3
|
-
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
2
|
+
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
4
3
|
import { writeFile } from "node:fs/promises";
|
|
5
|
-
import { dirname, resolve as
|
|
4
|
+
import { dirname, resolve as resolve2 } from "node:path";
|
|
6
5
|
|
|
7
6
|
// src/schema/limits.ts
|
|
8
7
|
var MAX_PIXEL_BYTES = 512 * 1024 * 1024;
|
|
9
8
|
var MAX_STREAM_BYTES = 50 * 1024 * 1024 * 1024;
|
|
10
9
|
|
|
10
|
+
// src/syntheticFixtures/streamWrite.ts
|
|
11
|
+
import { closeSync, mkdirSync, openSync, writeSync } from "node:fs";
|
|
12
|
+
import { resolve } from "node:path";
|
|
13
|
+
|
|
14
|
+
// src/loadDcmjs.ts
|
|
15
|
+
import dcmjsDefaultImport, * as dcmjsNamespace from "dcmjs";
|
|
16
|
+
function loadDcmjs() {
|
|
17
|
+
if (dcmjsNamespace.data?.DicomDict) {
|
|
18
|
+
return dcmjsNamespace;
|
|
19
|
+
}
|
|
20
|
+
const d = dcmjsDefaultImport;
|
|
21
|
+
if (d?.data?.DicomDict) {
|
|
22
|
+
return d;
|
|
23
|
+
}
|
|
24
|
+
if (d?.default?.data?.DicomDict) {
|
|
25
|
+
return d.default;
|
|
26
|
+
}
|
|
27
|
+
throw new Error(
|
|
28
|
+
"dcmjs failed to load (missing data.DicomDict). Install dcmjs >= 0.29."
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
var dcmjs = loadDcmjs();
|
|
32
|
+
|
|
33
|
+
// src/platform/bytes.ts
|
|
34
|
+
function bytesToHex(bytes) {
|
|
35
|
+
let hex = "";
|
|
36
|
+
for (const byte of bytes) {
|
|
37
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
38
|
+
}
|
|
39
|
+
return hex;
|
|
40
|
+
}
|
|
41
|
+
function utf8Bytes(text) {
|
|
42
|
+
return new TextEncoder().encode(text);
|
|
43
|
+
}
|
|
44
|
+
function concatBytes(...parts) {
|
|
45
|
+
const out = new Uint8Array(parts.reduce((sum, p) => sum + p.length, 0));
|
|
46
|
+
let offset = 0;
|
|
47
|
+
for (const part of parts) {
|
|
48
|
+
out.set(part, offset);
|
|
49
|
+
offset += part.length;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
function patternBytes(length, pattern) {
|
|
54
|
+
const out = new Uint8Array(length);
|
|
55
|
+
const seed = utf8Bytes(pattern);
|
|
56
|
+
if (length === 0 || seed.length === 0) return out;
|
|
57
|
+
out.set(seed.subarray(0, Math.min(seed.length, length)));
|
|
58
|
+
let filled = Math.min(seed.length, length);
|
|
59
|
+
while (filled < length) {
|
|
60
|
+
const chunk = Math.min(filled, length - filled);
|
|
61
|
+
out.copyWithin(filled, 0, chunk);
|
|
62
|
+
filled += chunk;
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/nonStandardDicom/dicomdir.ts
|
|
68
|
+
var DICOMDIR_SOP_CLASS_UID = "1.2.840.10008.1.3.10";
|
|
69
|
+
function buildDicomdirBuffer() {
|
|
70
|
+
const uid = DICOMDIR_SOP_CLASS_UID;
|
|
71
|
+
const buf = new Uint8Array(256);
|
|
72
|
+
const view = new DataView(buf.buffer);
|
|
73
|
+
buf.set(utf8Bytes("DICM"), 128);
|
|
74
|
+
let off = 132;
|
|
75
|
+
view.setUint16(off, 2, true);
|
|
76
|
+
off += 2;
|
|
77
|
+
view.setUint16(off, 2, true);
|
|
78
|
+
off += 2;
|
|
79
|
+
buf.set(utf8Bytes("UI"), off);
|
|
80
|
+
off += 2;
|
|
81
|
+
view.setUint16(off, uid.length, true);
|
|
82
|
+
off += 2;
|
|
83
|
+
buf.set(utf8Bytes(uid), off);
|
|
84
|
+
off += uid.length;
|
|
85
|
+
return buf.subarray(0, off);
|
|
86
|
+
}
|
|
87
|
+
|
|
11
88
|
// src/syntheticFixtures/tagTemplate.ts
|
|
12
89
|
var TEMPLATE_VOCAB = [
|
|
13
90
|
"index",
|
|
@@ -63,47 +140,6 @@ function positionalName(prefix, ordinal) {
|
|
|
63
140
|
return `${prefix}-${String(ordinal).padStart(3, "0")}`;
|
|
64
141
|
}
|
|
65
142
|
|
|
66
|
-
// src/loadDcmjs.ts
|
|
67
|
-
import dcmjsDefaultImport, * as dcmjsNamespace from "dcmjs";
|
|
68
|
-
function loadDcmjs() {
|
|
69
|
-
if (dcmjsNamespace.data?.DicomDict) {
|
|
70
|
-
return dcmjsNamespace;
|
|
71
|
-
}
|
|
72
|
-
const d = dcmjsDefaultImport;
|
|
73
|
-
if (d?.data?.DicomDict) {
|
|
74
|
-
return d;
|
|
75
|
-
}
|
|
76
|
-
if (d?.default?.data?.DicomDict) {
|
|
77
|
-
return d.default;
|
|
78
|
-
}
|
|
79
|
-
throw new Error(
|
|
80
|
-
"dcmjs failed to load (missing data.DicomDict). Install dcmjs >= 0.29."
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
var dcmjs = loadDcmjs();
|
|
84
|
-
|
|
85
|
-
// src/nonStandardDicom/dicomdir.ts
|
|
86
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
87
|
-
import { resolve } from "node:path";
|
|
88
|
-
var DICOMDIR_SOP_CLASS_UID = "1.2.840.10008.1.3.10";
|
|
89
|
-
function buildDicomdirBuffer() {
|
|
90
|
-
const uid = DICOMDIR_SOP_CLASS_UID;
|
|
91
|
-
const buf = Buffer.alloc(256, 0);
|
|
92
|
-
buf.write("DICM", 128, 4, "ascii");
|
|
93
|
-
let off = 132;
|
|
94
|
-
buf.writeUInt16LE(2, off);
|
|
95
|
-
off += 2;
|
|
96
|
-
buf.writeUInt16LE(2, off);
|
|
97
|
-
off += 2;
|
|
98
|
-
buf.write("UI", off, 2, "ascii");
|
|
99
|
-
off += 2;
|
|
100
|
-
buf.writeUInt16LE(uid.length, off);
|
|
101
|
-
off += 2;
|
|
102
|
-
buf.write(uid, off, uid.length, "ascii");
|
|
103
|
-
off += uid.length;
|
|
104
|
-
return buf.subarray(0, off);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
143
|
// src/syntheticFixtures/generator.ts
|
|
108
144
|
var MODALITY_PRESETS = {
|
|
109
145
|
CT: {
|
|
@@ -246,7 +282,7 @@ function serializeDict(meta, dataset, rawElements) {
|
|
|
246
282
|
if (rawElements) {
|
|
247
283
|
Object.assign(dicomDict.dict, rawElements);
|
|
248
284
|
}
|
|
249
|
-
return
|
|
285
|
+
return new Uint8Array(dicomDict.write({ allowInvalidVRLength: true }));
|
|
250
286
|
}
|
|
251
287
|
var MAX_DIMENSION = 65535;
|
|
252
288
|
function applySizing(dataset, meta, spec, rawElements) {
|
|
@@ -291,8 +327,8 @@ function buildImageBuffer(spec, uid) {
|
|
|
291
327
|
return { buffer: serializeDict(meta, dataset, rawElements), rawElements };
|
|
292
328
|
}
|
|
293
329
|
function buildFakeSignatureBuffer() {
|
|
294
|
-
const buf =
|
|
295
|
-
buf.
|
|
330
|
+
const buf = new Uint8Array(200);
|
|
331
|
+
buf.set(utf8Bytes("XXXX"), 128);
|
|
296
332
|
return buf;
|
|
297
333
|
}
|
|
298
334
|
function sizedNonDicomBytes(spec) {
|
|
@@ -300,8 +336,8 @@ function sizedNonDicomBytes(spec) {
|
|
|
300
336
|
}
|
|
301
337
|
function buildNonDicomBuffer(spec) {
|
|
302
338
|
const bytes = sizedNonDicomBytes(spec);
|
|
303
|
-
if (bytes !== void 0) return
|
|
304
|
-
return
|
|
339
|
+
if (bytes !== void 0) return patternBytes(bytes, "not dicom ");
|
|
340
|
+
return utf8Bytes(spec.content ?? "not dicom");
|
|
305
341
|
}
|
|
306
342
|
function buildBufferForSpec(spec, uid) {
|
|
307
343
|
switch (spec.type) {
|
|
@@ -322,19 +358,150 @@ function buildBufferForSpec(spec, uid) {
|
|
|
322
358
|
}
|
|
323
359
|
}
|
|
324
360
|
|
|
325
|
-
// src/
|
|
326
|
-
|
|
327
|
-
|
|
361
|
+
// src/platform/random.ts
|
|
362
|
+
function randomHex(byteLength) {
|
|
363
|
+
return bytesToHex(
|
|
364
|
+
globalThis.crypto.getRandomValues(new Uint8Array(byteLength))
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/platform/sha256.ts
|
|
369
|
+
var K = new Uint32Array([
|
|
370
|
+
1116352408,
|
|
371
|
+
1899447441,
|
|
372
|
+
3049323471,
|
|
373
|
+
3921009573,
|
|
374
|
+
961987163,
|
|
375
|
+
1508970993,
|
|
376
|
+
2453635748,
|
|
377
|
+
2870763221,
|
|
378
|
+
3624381080,
|
|
379
|
+
310598401,
|
|
380
|
+
607225278,
|
|
381
|
+
1426881987,
|
|
382
|
+
1925078388,
|
|
383
|
+
2162078206,
|
|
384
|
+
2614888103,
|
|
385
|
+
3248222580,
|
|
386
|
+
3835390401,
|
|
387
|
+
4022224774,
|
|
388
|
+
264347078,
|
|
389
|
+
604807628,
|
|
390
|
+
770255983,
|
|
391
|
+
1249150122,
|
|
392
|
+
1555081692,
|
|
393
|
+
1996064986,
|
|
394
|
+
2554220882,
|
|
395
|
+
2821834349,
|
|
396
|
+
2952996808,
|
|
397
|
+
3210313671,
|
|
398
|
+
3336571891,
|
|
399
|
+
3584528711,
|
|
400
|
+
113926993,
|
|
401
|
+
338241895,
|
|
402
|
+
666307205,
|
|
403
|
+
773529912,
|
|
404
|
+
1294757372,
|
|
405
|
+
1396182291,
|
|
406
|
+
1695183700,
|
|
407
|
+
1986661051,
|
|
408
|
+
2177026350,
|
|
409
|
+
2456956037,
|
|
410
|
+
2730485921,
|
|
411
|
+
2820302411,
|
|
412
|
+
3259730800,
|
|
413
|
+
3345764771,
|
|
414
|
+
3516065817,
|
|
415
|
+
3600352804,
|
|
416
|
+
4094571909,
|
|
417
|
+
275423344,
|
|
418
|
+
430227734,
|
|
419
|
+
506948616,
|
|
420
|
+
659060556,
|
|
421
|
+
883997877,
|
|
422
|
+
958139571,
|
|
423
|
+
1322822218,
|
|
424
|
+
1537002063,
|
|
425
|
+
1747873779,
|
|
426
|
+
1955562222,
|
|
427
|
+
2024104815,
|
|
428
|
+
2227730452,
|
|
429
|
+
2361852424,
|
|
430
|
+
2428436474,
|
|
431
|
+
2756734187,
|
|
432
|
+
3204031479,
|
|
433
|
+
3329325298
|
|
434
|
+
]);
|
|
435
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
436
|
+
function sha256(input) {
|
|
437
|
+
const data2 = typeof input === "string" ? new TextEncoder().encode(input) : input;
|
|
438
|
+
const padded = new Uint8Array((data2.length + 8 >> 6) + 1 << 6);
|
|
439
|
+
padded.set(data2);
|
|
440
|
+
padded[data2.length] = 128;
|
|
441
|
+
new DataView(padded.buffer).setBigUint64(
|
|
442
|
+
padded.length - 8,
|
|
443
|
+
BigInt(data2.length * 8),
|
|
444
|
+
false
|
|
445
|
+
);
|
|
446
|
+
const h = new Uint32Array([
|
|
447
|
+
1779033703,
|
|
448
|
+
3144134277,
|
|
449
|
+
1013904242,
|
|
450
|
+
2773480762,
|
|
451
|
+
1359893119,
|
|
452
|
+
2600822924,
|
|
453
|
+
528734635,
|
|
454
|
+
1541459225
|
|
455
|
+
]);
|
|
456
|
+
const w = new Uint32Array(64);
|
|
457
|
+
const view = new DataView(padded.buffer);
|
|
458
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
459
|
+
for (let t = 0; t < 16; t++) w[t] = view.getUint32(offset + t * 4, false);
|
|
460
|
+
for (let t = 16; t < 64; t++) {
|
|
461
|
+
const s0 = rotr(w[t - 15], 7) ^ rotr(w[t - 15], 18) ^ w[t - 15] >>> 3;
|
|
462
|
+
const s1 = rotr(w[t - 2], 17) ^ rotr(w[t - 2], 19) ^ w[t - 2] >>> 10;
|
|
463
|
+
w[t] = w[t - 16] + s0 + w[t - 7] + s1 >>> 0;
|
|
464
|
+
}
|
|
465
|
+
let [a, b, c, d, e, f, g, hh] = h;
|
|
466
|
+
for (let t = 0; t < 64; t++) {
|
|
467
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
468
|
+
const ch = e & f ^ ~e & g;
|
|
469
|
+
const temp1 = hh + S1 + ch + K[t] + w[t] >>> 0;
|
|
470
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
471
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
472
|
+
const temp2 = S0 + maj >>> 0;
|
|
473
|
+
hh = g;
|
|
474
|
+
g = f;
|
|
475
|
+
f = e;
|
|
476
|
+
e = d + temp1 >>> 0;
|
|
477
|
+
d = c;
|
|
478
|
+
c = b;
|
|
479
|
+
b = a;
|
|
480
|
+
a = temp1 + temp2 >>> 0;
|
|
481
|
+
}
|
|
482
|
+
h[0] = h[0] + a >>> 0;
|
|
483
|
+
h[1] = h[1] + b >>> 0;
|
|
484
|
+
h[2] = h[2] + c >>> 0;
|
|
485
|
+
h[3] = h[3] + d >>> 0;
|
|
486
|
+
h[4] = h[4] + e >>> 0;
|
|
487
|
+
h[5] = h[5] + f >>> 0;
|
|
488
|
+
h[6] = h[6] + g >>> 0;
|
|
489
|
+
h[7] = h[7] + hh >>> 0;
|
|
490
|
+
}
|
|
491
|
+
const out = new Uint8Array(32);
|
|
492
|
+
const outView = new DataView(out.buffer);
|
|
493
|
+
for (let i = 0; i < 8; i++) outView.setUint32(i * 4, h[i], false);
|
|
494
|
+
return out;
|
|
495
|
+
}
|
|
328
496
|
|
|
329
497
|
// src/syntheticFixtures/uid.ts
|
|
330
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
331
498
|
function hashUid(salt, key) {
|
|
332
|
-
const digest =
|
|
333
|
-
const n = BigInt(`0x${digest.subarray(0, 16)
|
|
499
|
+
const digest = sha256(`${salt} ${key}`);
|
|
500
|
+
const n = BigInt(`0x${bytesToHex(digest.subarray(0, 16))}`);
|
|
334
501
|
return `2.25.${n.toString()}`;
|
|
335
502
|
}
|
|
336
503
|
function randomUid() {
|
|
337
|
-
const n = BigInt(`0x${
|
|
504
|
+
const n = BigInt(`0x${randomHex(16)}`);
|
|
338
505
|
return `2.25.${n.toString()}`;
|
|
339
506
|
}
|
|
340
507
|
function seedToSalt(seed) {
|
|
@@ -370,6 +537,9 @@ var PIXEL_DATA_OB_HEADER = Buffer.from([224, 127, 16, 0, 79, 66]);
|
|
|
370
537
|
var ITEM_TAG_GROUP = 65534;
|
|
371
538
|
var ITEM_TAG_ELEMENT = 57344;
|
|
372
539
|
var SEQUENCE_DELIMITER_ELEMENT = 57565;
|
|
540
|
+
function asBuffer(bytes) {
|
|
541
|
+
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
542
|
+
}
|
|
373
543
|
function itemTag(element, byteLength = 0) {
|
|
374
544
|
const b = Buffer.alloc(8);
|
|
375
545
|
b.writeUInt16LE(ITEM_TAG_GROUP, 0);
|
|
@@ -400,7 +570,7 @@ function writeNative(outPath, params, dims, zeroChunk) {
|
|
|
400
570
|
dataset.Rows = dims.rows;
|
|
401
571
|
dataset.Columns = dims.columns;
|
|
402
572
|
const minimalPixelBytes = dataset.PixelData.byteLength;
|
|
403
|
-
const probe = serializeDict(meta, dataset, rawElements);
|
|
573
|
+
const probe = asBuffer(serializeDict(meta, dataset, rawElements));
|
|
404
574
|
const pixelElement = Buffer.alloc(12);
|
|
405
575
|
PIXEL_DATA_OW_HEADER.copy(pixelElement);
|
|
406
576
|
pixelElement.writeUInt32LE(minimalPixelBytes, 8);
|
|
@@ -433,7 +603,7 @@ function writeEncapsulated(outPath, params, pixelBytes, fragmentBytes, zeroChunk
|
|
|
433
603
|
syncMetaWithDataset(meta, dataset, rawElements);
|
|
434
604
|
dataset.PixelData = [new Uint8Array([]).buffer, new Uint8Array([0, 0]).buffer];
|
|
435
605
|
dataset._vrMap = { PixelData: "OB" };
|
|
436
|
-
const probe = serializeDict(meta, dataset, rawElements);
|
|
606
|
+
const probe = asBuffer(serializeDict(meta, dataset, rawElements));
|
|
437
607
|
const idx = probe.indexOf(PIXEL_DATA_OB_HEADER);
|
|
438
608
|
if (idx < 0 || probe.readUInt32LE(idx + 8) !== 4294967295) {
|
|
439
609
|
throw new Error("failed to locate encapsulated PixelData element header");
|
|
@@ -474,7 +644,7 @@ function streamLargeImage(outPath, options) {
|
|
|
474
644
|
const modality = options.modality ?? "CT";
|
|
475
645
|
const uid = options.uid ?? makeUidGenerator(options.uidSalt ?? seedToSalt(options.seed))(0);
|
|
476
646
|
const identity = { modality, uid, tags: options.tags };
|
|
477
|
-
|
|
647
|
+
mkdirSync(resolve(outPath, ".."), { recursive: true });
|
|
478
648
|
const zeroChunk = Buffer.alloc(Math.max(1, chunkBytes));
|
|
479
649
|
const probe = serializeDict(
|
|
480
650
|
buildMeta(uid, modality),
|
|
@@ -519,7 +689,7 @@ function parseBuffer(buffer) {
|
|
|
519
689
|
return dcmjsAny.data.DicomMessage.readFile(ab);
|
|
520
690
|
}
|
|
521
691
|
function reserialize(parsed) {
|
|
522
|
-
return
|
|
692
|
+
return new Uint8Array(parsed.write({ allowInvalidVRLength: true }));
|
|
523
693
|
}
|
|
524
694
|
var OVERLONG_UID = `2.25.${"0".repeat(60)}`;
|
|
525
695
|
var VIOLATION_LEVEL = {
|
|
@@ -584,11 +754,12 @@ function applyByteLevel(buffer, violations) {
|
|
|
584
754
|
result = result.subarray(132);
|
|
585
755
|
break;
|
|
586
756
|
case "malformed-sq-delimiter": {
|
|
587
|
-
const delimiter =
|
|
588
|
-
delimiter.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
757
|
+
const delimiter = new Uint8Array(8);
|
|
758
|
+
const view = new DataView(delimiter.buffer);
|
|
759
|
+
view.setUint16(0, 65534, true);
|
|
760
|
+
view.setUint16(2, 57565, true);
|
|
761
|
+
view.setUint32(4, 4, true);
|
|
762
|
+
result = concatBytes(result, delimiter);
|
|
592
763
|
break;
|
|
593
764
|
}
|
|
594
765
|
default:
|
|
@@ -609,7 +780,7 @@ function applyViolations(buffer, violations, rawElements) {
|
|
|
609
780
|
);
|
|
610
781
|
}
|
|
611
782
|
|
|
612
|
-
// src/collection/
|
|
783
|
+
// src/collection/generate.ts
|
|
613
784
|
var NO_EXTENSION_TYPES = /* @__PURE__ */ new Set([
|
|
614
785
|
"fake-signature",
|
|
615
786
|
"non-dicom"
|
|
@@ -748,12 +919,6 @@ function hierarchicalName(studyOrdinal, seriesIndex, instanceNumber) {
|
|
|
748
919
|
relativePath: `study-${pad3(studyOrdinal + 1)}/series-${pad3(seriesIndex + 1)}/${name}`
|
|
749
920
|
};
|
|
750
921
|
}
|
|
751
|
-
function withResolvedSeed(spec) {
|
|
752
|
-
return spec.seed === void 0 ? { ...spec, seed: randomBytes2(4).readUInt32BE(0) } : spec;
|
|
753
|
-
}
|
|
754
|
-
function withResolvedSalt(spec) {
|
|
755
|
-
return spec.uidSalt === void 0 ? { ...spec, uidSalt: randomBytes2(16).toString("hex") } : spec;
|
|
756
|
-
}
|
|
757
922
|
function computeNames(type, index, padWidth, grouped, quirks) {
|
|
758
923
|
let names;
|
|
759
924
|
if (grouped) {
|
|
@@ -923,49 +1088,23 @@ async function materialisePlan(plan, salt) {
|
|
|
923
1088
|
});
|
|
924
1089
|
return { ...file, filename: plan.filename, relativePath: plan.relativePath };
|
|
925
1090
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
for (const plan of planCollection(spec)) {
|
|
929
|
-
yield await materialisePlan(plan, salt);
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
async function* previewCollection(spec) {
|
|
933
|
-
const salt = effectiveSalt(spec);
|
|
934
|
-
for (const plan of planCollection(spec)) {
|
|
935
|
-
const knownBytes = plan.fileSpec.type === "large-image" ? plan.fileSpec.targetBytes : plan.fileSpec.type === "non-dicom" ? sizedNonDicomBytes(plan.fileSpec) : void 0;
|
|
936
|
-
if (knownBytes !== void 0) {
|
|
937
|
-
yield {
|
|
938
|
-
relativePath: plan.relativePath,
|
|
939
|
-
type: plan.fileSpec.type,
|
|
940
|
-
index: plan.index,
|
|
941
|
-
approxBytes: knownBytes
|
|
942
|
-
};
|
|
943
|
-
} else {
|
|
944
|
-
const file = await materialisePlan(plan, salt);
|
|
945
|
-
yield {
|
|
946
|
-
relativePath: plan.relativePath,
|
|
947
|
-
type: plan.fileSpec.type,
|
|
948
|
-
index: plan.index,
|
|
949
|
-
approxBytes: file.buffer.length
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
}
|
|
1091
|
+
|
|
1092
|
+
// src/collection/writer.ts
|
|
954
1093
|
async function writeCollectionFromSpec(spec, outDir) {
|
|
955
|
-
const root =
|
|
956
|
-
|
|
1094
|
+
const root = resolve2(outDir);
|
|
1095
|
+
mkdirSync2(root, { recursive: true });
|
|
957
1096
|
const manifest = [];
|
|
958
1097
|
const salt = effectiveSalt(spec);
|
|
959
1098
|
const createdDirs = /* @__PURE__ */ new Set([root]);
|
|
960
1099
|
const ensureDir = (filePath) => {
|
|
961
1100
|
const dir = dirname(filePath);
|
|
962
1101
|
if (!createdDirs.has(dir)) {
|
|
963
|
-
|
|
1102
|
+
mkdirSync2(dir, { recursive: true });
|
|
964
1103
|
createdDirs.add(dir);
|
|
965
1104
|
}
|
|
966
1105
|
};
|
|
967
1106
|
for (const plan of planCollection(spec)) {
|
|
968
|
-
const filePath =
|
|
1107
|
+
const filePath = resolve2(root, plan.relativePath);
|
|
969
1108
|
ensureDir(filePath);
|
|
970
1109
|
if (plan.fileSpec.type === "large-image") {
|
|
971
1110
|
const large = plan.fileSpec;
|
|
@@ -994,10 +1133,5 @@ async function writeCollectionFromSpec(spec, outDir) {
|
|
|
994
1133
|
return manifest;
|
|
995
1134
|
}
|
|
996
1135
|
export {
|
|
997
|
-
generateCollectionFromSpec,
|
|
998
|
-
generateFile,
|
|
999
|
-
previewCollection,
|
|
1000
|
-
withResolvedSalt,
|
|
1001
|
-
withResolvedSeed,
|
|
1002
1136
|
writeCollectionFromSpec
|
|
1003
1137
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/describe/describe.ts
|
|
2
|
-
import { createHash
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
3
|
import {
|
|
4
4
|
closeSync,
|
|
5
5
|
openSync,
|
|
@@ -687,11 +687,148 @@ function validateDatasetSpec(raw) {
|
|
|
687
687
|
};
|
|
688
688
|
}
|
|
689
689
|
|
|
690
|
+
// src/platform/bytes.ts
|
|
691
|
+
function bytesToHex(bytes) {
|
|
692
|
+
let hex = "";
|
|
693
|
+
for (const byte of bytes) {
|
|
694
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
695
|
+
}
|
|
696
|
+
return hex;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/platform/sha256.ts
|
|
700
|
+
var K = new Uint32Array([
|
|
701
|
+
1116352408,
|
|
702
|
+
1899447441,
|
|
703
|
+
3049323471,
|
|
704
|
+
3921009573,
|
|
705
|
+
961987163,
|
|
706
|
+
1508970993,
|
|
707
|
+
2453635748,
|
|
708
|
+
2870763221,
|
|
709
|
+
3624381080,
|
|
710
|
+
310598401,
|
|
711
|
+
607225278,
|
|
712
|
+
1426881987,
|
|
713
|
+
1925078388,
|
|
714
|
+
2162078206,
|
|
715
|
+
2614888103,
|
|
716
|
+
3248222580,
|
|
717
|
+
3835390401,
|
|
718
|
+
4022224774,
|
|
719
|
+
264347078,
|
|
720
|
+
604807628,
|
|
721
|
+
770255983,
|
|
722
|
+
1249150122,
|
|
723
|
+
1555081692,
|
|
724
|
+
1996064986,
|
|
725
|
+
2554220882,
|
|
726
|
+
2821834349,
|
|
727
|
+
2952996808,
|
|
728
|
+
3210313671,
|
|
729
|
+
3336571891,
|
|
730
|
+
3584528711,
|
|
731
|
+
113926993,
|
|
732
|
+
338241895,
|
|
733
|
+
666307205,
|
|
734
|
+
773529912,
|
|
735
|
+
1294757372,
|
|
736
|
+
1396182291,
|
|
737
|
+
1695183700,
|
|
738
|
+
1986661051,
|
|
739
|
+
2177026350,
|
|
740
|
+
2456956037,
|
|
741
|
+
2730485921,
|
|
742
|
+
2820302411,
|
|
743
|
+
3259730800,
|
|
744
|
+
3345764771,
|
|
745
|
+
3516065817,
|
|
746
|
+
3600352804,
|
|
747
|
+
4094571909,
|
|
748
|
+
275423344,
|
|
749
|
+
430227734,
|
|
750
|
+
506948616,
|
|
751
|
+
659060556,
|
|
752
|
+
883997877,
|
|
753
|
+
958139571,
|
|
754
|
+
1322822218,
|
|
755
|
+
1537002063,
|
|
756
|
+
1747873779,
|
|
757
|
+
1955562222,
|
|
758
|
+
2024104815,
|
|
759
|
+
2227730452,
|
|
760
|
+
2361852424,
|
|
761
|
+
2428436474,
|
|
762
|
+
2756734187,
|
|
763
|
+
3204031479,
|
|
764
|
+
3329325298
|
|
765
|
+
]);
|
|
766
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
767
|
+
function sha256(input) {
|
|
768
|
+
const data2 = typeof input === "string" ? new TextEncoder().encode(input) : input;
|
|
769
|
+
const padded = new Uint8Array((data2.length + 8 >> 6) + 1 << 6);
|
|
770
|
+
padded.set(data2);
|
|
771
|
+
padded[data2.length] = 128;
|
|
772
|
+
new DataView(padded.buffer).setBigUint64(
|
|
773
|
+
padded.length - 8,
|
|
774
|
+
BigInt(data2.length * 8),
|
|
775
|
+
false
|
|
776
|
+
);
|
|
777
|
+
const h = new Uint32Array([
|
|
778
|
+
1779033703,
|
|
779
|
+
3144134277,
|
|
780
|
+
1013904242,
|
|
781
|
+
2773480762,
|
|
782
|
+
1359893119,
|
|
783
|
+
2600822924,
|
|
784
|
+
528734635,
|
|
785
|
+
1541459225
|
|
786
|
+
]);
|
|
787
|
+
const w = new Uint32Array(64);
|
|
788
|
+
const view = new DataView(padded.buffer);
|
|
789
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
790
|
+
for (let t = 0; t < 16; t++) w[t] = view.getUint32(offset + t * 4, false);
|
|
791
|
+
for (let t = 16; t < 64; t++) {
|
|
792
|
+
const s0 = rotr(w[t - 15], 7) ^ rotr(w[t - 15], 18) ^ w[t - 15] >>> 3;
|
|
793
|
+
const s1 = rotr(w[t - 2], 17) ^ rotr(w[t - 2], 19) ^ w[t - 2] >>> 10;
|
|
794
|
+
w[t] = w[t - 16] + s0 + w[t - 7] + s1 >>> 0;
|
|
795
|
+
}
|
|
796
|
+
let [a, b, c, d, e, f, g, hh] = h;
|
|
797
|
+
for (let t = 0; t < 64; t++) {
|
|
798
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
799
|
+
const ch = e & f ^ ~e & g;
|
|
800
|
+
const temp1 = hh + S1 + ch + K[t] + w[t] >>> 0;
|
|
801
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
802
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
803
|
+
const temp2 = S0 + maj >>> 0;
|
|
804
|
+
hh = g;
|
|
805
|
+
g = f;
|
|
806
|
+
f = e;
|
|
807
|
+
e = d + temp1 >>> 0;
|
|
808
|
+
d = c;
|
|
809
|
+
c = b;
|
|
810
|
+
b = a;
|
|
811
|
+
a = temp1 + temp2 >>> 0;
|
|
812
|
+
}
|
|
813
|
+
h[0] = h[0] + a >>> 0;
|
|
814
|
+
h[1] = h[1] + b >>> 0;
|
|
815
|
+
h[2] = h[2] + c >>> 0;
|
|
816
|
+
h[3] = h[3] + d >>> 0;
|
|
817
|
+
h[4] = h[4] + e >>> 0;
|
|
818
|
+
h[5] = h[5] + f >>> 0;
|
|
819
|
+
h[6] = h[6] + g >>> 0;
|
|
820
|
+
h[7] = h[7] + hh >>> 0;
|
|
821
|
+
}
|
|
822
|
+
const out = new Uint8Array(32);
|
|
823
|
+
const outView = new DataView(out.buffer);
|
|
824
|
+
for (let i = 0; i < 8; i++) outView.setUint32(i * 4, h[i], false);
|
|
825
|
+
return out;
|
|
826
|
+
}
|
|
827
|
+
|
|
690
828
|
// src/syntheticFixtures/uid.ts
|
|
691
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
692
829
|
function hashUid(salt, key) {
|
|
693
|
-
const digest =
|
|
694
|
-
const n = BigInt(`0x${digest.subarray(0, 16)
|
|
830
|
+
const digest = sha256(`${salt} ${key}`);
|
|
831
|
+
const n = BigInt(`0x${bytesToHex(digest.subarray(0, 16))}`);
|
|
695
832
|
return `2.25.${n.toString()}`;
|
|
696
833
|
}
|
|
697
834
|
|
|
@@ -1032,7 +1169,7 @@ function hashUidTags(uids, salt, map) {
|
|
|
1032
1169
|
return tags;
|
|
1033
1170
|
}
|
|
1034
1171
|
function deriveSalt(studyUids) {
|
|
1035
|
-
const h =
|
|
1172
|
+
const h = createHash("sha256");
|
|
1036
1173
|
for (const uid of [...studyUids].sort()) h.update(uid).update("\n");
|
|
1037
1174
|
return h.digest("hex");
|
|
1038
1175
|
}
|