dicom-synth 1.14.0 → 1.16.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/bin/dicom-synth-describe.mjs +14 -2
- package/dist/esm/collection/writer.js +167 -45
- package/dist/esm/describe/describe.js +314 -40
- package/dist/esm/index.js +473 -89
- package/dist/esm/schema/parametric.js +44 -2
- package/dist/esm/schema/validate.js +154 -4
- package/dist/esm/syntheticFixtures/generator.js +20 -4
- package/dist/esm/syntheticFixtures/streamWrite.js +10 -0
- package/dist/types/describe/describe.d.ts +2 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/schema/types.d.ts +13 -0
- package/dist/types/schema/validate.d.ts +3 -1
- package/dist/types/syntheticFixtures/generator.d.ts +3 -1
- package/package.json +1 -1
|
@@ -97,11 +97,14 @@ function validateUidSalt(value, path) {
|
|
|
97
97
|
);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
-
function
|
|
101
|
-
if (
|
|
100
|
+
function validateInMemoryByteLimit(bytes, path) {
|
|
101
|
+
if (bytes > MAX_PIXEL_BYTES) {
|
|
102
102
|
throw new Error(`${path}: exceeds the 512 MB limit`);
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
function validateSizeKbLimit(kb, path) {
|
|
106
|
+
validateInMemoryByteLimit(kb * 1024, path);
|
|
107
|
+
}
|
|
105
108
|
function validateLargeTargetBytes(value, path) {
|
|
106
109
|
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
107
110
|
throw new Error(`${path}: must be an integer; got ${JSON.stringify(value)}`);
|
|
@@ -139,6 +142,29 @@ function validateLargeImageEntry(e, path) {
|
|
|
139
142
|
}
|
|
140
143
|
if ("tags" in e) validateTags(e.tags, `${path}.tags`);
|
|
141
144
|
}
|
|
145
|
+
function validateNonDicomEntry(e, path) {
|
|
146
|
+
const sizing = ["content", "targetSizeKb", "targetBytes"].filter(
|
|
147
|
+
(f) => f in e
|
|
148
|
+
);
|
|
149
|
+
if (sizing.length > 1) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`${path}: "content", "targetSizeKb" and "targetBytes" are mutually exclusive`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if ("content" in e && typeof e.content !== "string") {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`${path}.content: must be a string; got ${JSON.stringify(e.content)}`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if ("targetSizeKb" in e) {
|
|
160
|
+
validatePositiveInt(e.targetSizeKb, `${path}.targetSizeKb`);
|
|
161
|
+
validateSizeKbLimit(e.targetSizeKb, `${path}.targetSizeKb`);
|
|
162
|
+
}
|
|
163
|
+
if ("targetBytes" in e) {
|
|
164
|
+
validatePositiveInt(e.targetBytes, `${path}.targetBytes`);
|
|
165
|
+
validateInMemoryByteLimit(e.targetBytes, `${path}.targetBytes`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
142
168
|
function validateEnum(value, valid, path) {
|
|
143
169
|
if (typeof value !== "string" || !Object.hasOwn(valid, value)) {
|
|
144
170
|
throw new Error(
|
|
@@ -166,6 +192,22 @@ function validateEntry(entry, path) {
|
|
|
166
192
|
validateLargeImageEntry(e, path);
|
|
167
193
|
return e;
|
|
168
194
|
}
|
|
195
|
+
if (type === "non-dicom") {
|
|
196
|
+
for (const field of [
|
|
197
|
+
"modality",
|
|
198
|
+
"rows",
|
|
199
|
+
"columns",
|
|
200
|
+
"frames",
|
|
201
|
+
"tags",
|
|
202
|
+
"violations",
|
|
203
|
+
"transferSyntax"
|
|
204
|
+
]) {
|
|
205
|
+
if (field in e)
|
|
206
|
+
throw new Error(`${path}.${field}: not supported for type "${type}"`);
|
|
207
|
+
}
|
|
208
|
+
validateNonDicomEntry(e, path);
|
|
209
|
+
return e;
|
|
210
|
+
}
|
|
169
211
|
if ("targetBytes" in e) {
|
|
170
212
|
throw new Error(
|
|
171
213
|
`${path}.targetBytes: only supported for type "large-image"`
|
|
@@ -28,6 +28,9 @@ var TYPE_CAPABILITIES = {
|
|
|
28
28
|
"non-dicom": { image: false },
|
|
29
29
|
dicomdir: { image: false }
|
|
30
30
|
};
|
|
31
|
+
function isImageType(type) {
|
|
32
|
+
return TYPE_CAPABILITIES[type].image;
|
|
33
|
+
}
|
|
31
34
|
var VALID_MODALITIES = {
|
|
32
35
|
CT: true,
|
|
33
36
|
PT: true,
|
|
@@ -86,11 +89,14 @@ function validateUidSalt(value, path) {
|
|
|
86
89
|
);
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
|
-
function
|
|
90
|
-
if (
|
|
92
|
+
function validateInMemoryByteLimit(bytes, path) {
|
|
93
|
+
if (bytes > MAX_PIXEL_BYTES) {
|
|
91
94
|
throw new Error(`${path}: exceeds the 512 MB limit`);
|
|
92
95
|
}
|
|
93
96
|
}
|
|
97
|
+
function validateSizeKbLimit(kb, path) {
|
|
98
|
+
validateInMemoryByteLimit(kb * 1024, path);
|
|
99
|
+
}
|
|
94
100
|
function validateLargeTargetBytes(value, path) {
|
|
95
101
|
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
96
102
|
throw new Error(`${path}: must be an integer; got ${JSON.stringify(value)}`);
|
|
@@ -128,6 +134,29 @@ function validateLargeImageEntry(e, path) {
|
|
|
128
134
|
}
|
|
129
135
|
if ("tags" in e) validateTags(e.tags, `${path}.tags`);
|
|
130
136
|
}
|
|
137
|
+
function validateNonDicomEntry(e, path) {
|
|
138
|
+
const sizing = ["content", "targetSizeKb", "targetBytes"].filter(
|
|
139
|
+
(f) => f in e
|
|
140
|
+
);
|
|
141
|
+
if (sizing.length > 1) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`${path}: "content", "targetSizeKb" and "targetBytes" are mutually exclusive`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if ("content" in e && typeof e.content !== "string") {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`${path}.content: must be a string; got ${JSON.stringify(e.content)}`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if ("targetSizeKb" in e) {
|
|
152
|
+
validatePositiveInt(e.targetSizeKb, `${path}.targetSizeKb`);
|
|
153
|
+
validateSizeKbLimit(e.targetSizeKb, `${path}.targetSizeKb`);
|
|
154
|
+
}
|
|
155
|
+
if ("targetBytes" in e) {
|
|
156
|
+
validatePositiveInt(e.targetBytes, `${path}.targetBytes`);
|
|
157
|
+
validateInMemoryByteLimit(e.targetBytes, `${path}.targetBytes`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
131
160
|
function validateEnum(value, valid, path) {
|
|
132
161
|
if (typeof value !== "string" || !Object.hasOwn(valid, value)) {
|
|
133
162
|
throw new Error(
|
|
@@ -155,6 +184,22 @@ function validateEntry(entry, path) {
|
|
|
155
184
|
validateLargeImageEntry(e, path);
|
|
156
185
|
return e;
|
|
157
186
|
}
|
|
187
|
+
if (type === "non-dicom") {
|
|
188
|
+
for (const field of [
|
|
189
|
+
"modality",
|
|
190
|
+
"rows",
|
|
191
|
+
"columns",
|
|
192
|
+
"frames",
|
|
193
|
+
"tags",
|
|
194
|
+
"violations",
|
|
195
|
+
"transferSyntax"
|
|
196
|
+
]) {
|
|
197
|
+
if (field in e)
|
|
198
|
+
throw new Error(`${path}.${field}: not supported for type "${type}"`);
|
|
199
|
+
}
|
|
200
|
+
validateNonDicomEntry(e, path);
|
|
201
|
+
return e;
|
|
202
|
+
}
|
|
158
203
|
if ("targetBytes" in e) {
|
|
159
204
|
throw new Error(
|
|
160
205
|
`${path}.targetBytes: only supported for type "large-image"`
|
|
@@ -362,6 +407,101 @@ function validateStudy(study, path) {
|
|
|
362
407
|
if ("tags" in st) validateTags(st.tags, `${path}.tags`);
|
|
363
408
|
return st;
|
|
364
409
|
}
|
|
410
|
+
function positionalName(prefix, ordinal) {
|
|
411
|
+
return `${prefix}-${String(ordinal).padStart(3, "0")}`;
|
|
412
|
+
}
|
|
413
|
+
var VALID_TREE_ROLES = {
|
|
414
|
+
study: true,
|
|
415
|
+
series: true
|
|
416
|
+
};
|
|
417
|
+
function validateNodeName(value, path) {
|
|
418
|
+
if (typeof value !== "string" || value.length === 0 || value === "." || value === ".." || /[/\\]/.test(value)) {
|
|
419
|
+
throw new Error(
|
|
420
|
+
`${path}: must be a non-empty name without path separators; got ${JSON.stringify(value)}`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function validateTreeNode(node, path, inStudy, inSeries) {
|
|
425
|
+
if (typeof node !== "object" || node === null || Array.isArray(node)) {
|
|
426
|
+
throw new Error(`${path}: must be an object`);
|
|
427
|
+
}
|
|
428
|
+
const n = node;
|
|
429
|
+
if ("children" in n) {
|
|
430
|
+
if ("type" in n) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`${path}: a node cannot have both "children" (directory) and "type" (file)`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
if ("name" in n) validateNodeName(n.name, `${path}.name`);
|
|
436
|
+
if ("tags" in n) validateTags(n.tags, `${path}.tags`);
|
|
437
|
+
let childStudy = inStudy;
|
|
438
|
+
let childSeries = inSeries;
|
|
439
|
+
if ("role" in n) {
|
|
440
|
+
validateEnum(n.role, VALID_TREE_ROLES, `${path}.role`);
|
|
441
|
+
if (n.role === "study") {
|
|
442
|
+
if (inStudy) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
`${path}.role: a study directory cannot nest inside another study`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
childStudy = true;
|
|
448
|
+
} else {
|
|
449
|
+
if (!inStudy) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`${path}.role: a series directory requires an enclosing study-role directory`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
if (inSeries) {
|
|
455
|
+
throw new Error(
|
|
456
|
+
`${path}.role: a series directory cannot nest inside another series`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
childSeries = true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (!Array.isArray(n.children) || n.children.length === 0) {
|
|
463
|
+
throw new Error(`${path}.children: must be a non-empty array`);
|
|
464
|
+
}
|
|
465
|
+
validateTreeChildren(
|
|
466
|
+
n.children,
|
|
467
|
+
`${path}.children`,
|
|
468
|
+
childStudy,
|
|
469
|
+
childSeries
|
|
470
|
+
);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const e = validateEntry(node, path);
|
|
474
|
+
if ("name" in n) {
|
|
475
|
+
validateNodeName(n.name, `${path}.name`);
|
|
476
|
+
if ((e.count ?? 1) > 1) {
|
|
477
|
+
throw new Error(
|
|
478
|
+
`${path}: "name" cannot be combined with count > 1 \u2014 the copies would collide on one path`
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function validateTreeChildren(children, basePath, inStudy, inSeries) {
|
|
484
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
485
|
+
let dirOrdinal = 0;
|
|
486
|
+
children.forEach((child, i) => {
|
|
487
|
+
const path = `${basePath}[${i}]`;
|
|
488
|
+
validateTreeNode(child, path, inStudy, inSeries);
|
|
489
|
+
const n = child;
|
|
490
|
+
let name = "name" in n ? n.name : void 0;
|
|
491
|
+
if ("children" in n) {
|
|
492
|
+
dirOrdinal++;
|
|
493
|
+
name ?? (name = positionalName("dir", dirOrdinal));
|
|
494
|
+
}
|
|
495
|
+
if (name === void 0) return;
|
|
496
|
+
const prior = claimed.get(name);
|
|
497
|
+
if (prior !== void 0) {
|
|
498
|
+
throw new Error(
|
|
499
|
+
`${path}: name "${name}" collides with ${prior} \u2014 siblings must resolve to distinct paths`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
claimed.set(name, path);
|
|
503
|
+
});
|
|
504
|
+
}
|
|
365
505
|
function validateDatasetSpec(raw) {
|
|
366
506
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
367
507
|
throw new Error("DatasetSpec: must be a JSON object");
|
|
@@ -373,11 +513,15 @@ function validateDatasetSpec(raw) {
|
|
|
373
513
|
if ("studies" in r && !Array.isArray(r.studies)) {
|
|
374
514
|
throw new Error("DatasetSpec.studies: must be an array");
|
|
375
515
|
}
|
|
516
|
+
if ("tree" in r && !Array.isArray(r.tree)) {
|
|
517
|
+
throw new Error("DatasetSpec.tree: must be an array");
|
|
518
|
+
}
|
|
376
519
|
const rawEntries = r.entries ?? [];
|
|
377
520
|
const rawStudies = r.studies ?? [];
|
|
378
|
-
|
|
521
|
+
const rawTree = r.tree ?? [];
|
|
522
|
+
if (rawEntries.length === 0 && rawStudies.length === 0 && rawTree.length === 0) {
|
|
379
523
|
throw new Error(
|
|
380
|
-
'DatasetSpec: must contain at least one of "entries" or "
|
|
524
|
+
'DatasetSpec: must contain at least one of "entries", "studies" or "tree" (non-empty)'
|
|
381
525
|
);
|
|
382
526
|
}
|
|
383
527
|
const entries = rawEntries.map(
|
|
@@ -386,6 +530,9 @@ function validateDatasetSpec(raw) {
|
|
|
386
530
|
const studies = rawStudies.map(
|
|
387
531
|
(study, i) => validateStudy(study, `studies[${i}]`)
|
|
388
532
|
);
|
|
533
|
+
if (rawTree.length > 0) {
|
|
534
|
+
validateTreeChildren(rawTree, "tree", false, false);
|
|
535
|
+
}
|
|
389
536
|
if ("seed" in r) validateSeed(r.seed, "DatasetSpec.seed");
|
|
390
537
|
if ("uidSalt" in r) validateUidSalt(r.uidSalt, "DatasetSpec.uidSalt");
|
|
391
538
|
if ("layout" in r) {
|
|
@@ -402,6 +549,7 @@ function validateDatasetSpec(raw) {
|
|
|
402
549
|
return {
|
|
403
550
|
...entries.length > 0 ? { entries } : {},
|
|
404
551
|
...studies.length > 0 ? { studies } : {},
|
|
552
|
+
...rawTree.length > 0 ? { tree: r.tree } : {},
|
|
405
553
|
...r.seed !== void 0 ? { seed: r.seed } : {},
|
|
406
554
|
...r.uidSalt !== void 0 ? { uidSalt: r.uidSalt } : {},
|
|
407
555
|
...r.layout !== void 0 ? { layout: r.layout } : {},
|
|
@@ -533,6 +681,8 @@ function validateParametricSpec(raw) {
|
|
|
533
681
|
}
|
|
534
682
|
export {
|
|
535
683
|
HEX_TAG_RE,
|
|
684
|
+
isImageType,
|
|
685
|
+
positionalName,
|
|
536
686
|
validateDatasetSpec,
|
|
537
687
|
validateParametricSpec
|
|
538
688
|
};
|
|
@@ -108,6 +108,14 @@ function applyTagOverrides(dataset, tags) {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
+
function syncMetaWithDataset(meta, dataset) {
|
|
112
|
+
if (typeof dataset.SOPInstanceUID === "string") {
|
|
113
|
+
meta.MediaStorageSOPInstanceUID = dataset.SOPInstanceUID;
|
|
114
|
+
}
|
|
115
|
+
if (typeof dataset.SOPClassUID === "string") {
|
|
116
|
+
meta.MediaStorageSOPClassUID = dataset.SOPClassUID;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
111
119
|
function buildMeta(uid, modality, transferSyntax = "explicit-vr-little-endian") {
|
|
112
120
|
return {
|
|
113
121
|
FileMetaInformationVersion: new Uint8Array([0, 1]).buffer,
|
|
@@ -194,6 +202,7 @@ function buildImageBuffer(spec, uid) {
|
|
|
194
202
|
}
|
|
195
203
|
applyTagOverrides(dataset, spec.tags);
|
|
196
204
|
const meta = buildMeta(effectiveUid, modality, spec.transferSyntax);
|
|
205
|
+
syncMetaWithDataset(meta, dataset);
|
|
197
206
|
applySizing(dataset, meta, spec);
|
|
198
207
|
return serializeDict(meta, dataset);
|
|
199
208
|
}
|
|
@@ -202,8 +211,13 @@ function buildFakeSignatureBuffer() {
|
|
|
202
211
|
buf.write("XXXX", 128, 4, "ascii");
|
|
203
212
|
return buf;
|
|
204
213
|
}
|
|
205
|
-
function
|
|
206
|
-
return
|
|
214
|
+
function sizedNonDicomBytes(spec) {
|
|
215
|
+
return spec.targetBytes ?? (spec.targetSizeKb !== void 0 ? spec.targetSizeKb * 1024 : void 0);
|
|
216
|
+
}
|
|
217
|
+
function buildNonDicomBuffer(spec) {
|
|
218
|
+
const bytes = sizedNonDicomBytes(spec);
|
|
219
|
+
if (bytes !== void 0) return Buffer.alloc(bytes, "not dicom ");
|
|
220
|
+
return Buffer.from(spec.content ?? "not dicom", "utf8");
|
|
207
221
|
}
|
|
208
222
|
function buildBufferForSpec(spec, uid) {
|
|
209
223
|
switch (spec.type) {
|
|
@@ -214,7 +228,7 @@ function buildBufferForSpec(spec, uid) {
|
|
|
214
228
|
case "fake-signature":
|
|
215
229
|
return buildFakeSignatureBuffer();
|
|
216
230
|
case "non-dicom":
|
|
217
|
-
return buildNonDicomBuffer(spec
|
|
231
|
+
return buildNonDicomBuffer(spec);
|
|
218
232
|
case "dicomdir":
|
|
219
233
|
return buildDicomdirBuffer();
|
|
220
234
|
case "large-image":
|
|
@@ -228,5 +242,7 @@ export {
|
|
|
228
242
|
buildBaseImageDataset,
|
|
229
243
|
buildBufferForSpec,
|
|
230
244
|
buildMeta,
|
|
231
|
-
serializeDict
|
|
245
|
+
serializeDict,
|
|
246
|
+
sizedNonDicomBytes,
|
|
247
|
+
syncMetaWithDataset
|
|
232
248
|
};
|
|
@@ -94,6 +94,14 @@ function applyTagOverrides(dataset, tags) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
+
function syncMetaWithDataset(meta, dataset) {
|
|
98
|
+
if (typeof dataset.SOPInstanceUID === "string") {
|
|
99
|
+
meta.MediaStorageSOPInstanceUID = dataset.SOPInstanceUID;
|
|
100
|
+
}
|
|
101
|
+
if (typeof dataset.SOPClassUID === "string") {
|
|
102
|
+
meta.MediaStorageSOPClassUID = dataset.SOPClassUID;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
97
105
|
function buildMeta(uid, modality, transferSyntax = "explicit-vr-little-endian") {
|
|
98
106
|
return {
|
|
99
107
|
FileMetaInformationVersion: new Uint8Array([0, 1]).buffer,
|
|
@@ -204,6 +212,7 @@ function writeNative(outPath, params, dims, zeroChunk) {
|
|
|
204
212
|
const meta = buildMeta(uid, modality);
|
|
205
213
|
const dataset = buildBaseImageDataset(uid, modality);
|
|
206
214
|
applyTagOverrides(dataset, tags);
|
|
215
|
+
syncMetaWithDataset(meta, dataset);
|
|
207
216
|
dataset.Rows = dims.rows;
|
|
208
217
|
dataset.Columns = dims.columns;
|
|
209
218
|
const minimalPixelBytes = dataset.PixelData.byteLength;
|
|
@@ -237,6 +246,7 @@ function writeEncapsulated(outPath, params, pixelBytes, fragmentBytes, zeroChunk
|
|
|
237
246
|
};
|
|
238
247
|
const dataset = buildBaseImageDataset(uid, modality);
|
|
239
248
|
applyTagOverrides(dataset, tags);
|
|
249
|
+
syncMetaWithDataset(meta, dataset);
|
|
240
250
|
dataset.PixelData = [new Uint8Array([]).buffer, new Uint8Array([0, 0]).buffer];
|
|
241
251
|
dataset._vrMap = { PixelData: "OB" };
|
|
242
252
|
const probe = serializeDict(meta, dataset);
|
|
@@ -4,6 +4,7 @@ export type DescribeOptions = {
|
|
|
4
4
|
sizeTolerance?: number;
|
|
5
5
|
preserveUids?: boolean;
|
|
6
6
|
uidSalt?: string;
|
|
7
|
+
captureTree?: boolean;
|
|
7
8
|
};
|
|
8
9
|
export type DescribeResult = {
|
|
9
10
|
spec: DatasetSpec;
|
|
@@ -11,6 +12,7 @@ export type DescribeResult = {
|
|
|
11
12
|
dicomFiles: number;
|
|
12
13
|
skipped: number;
|
|
13
14
|
unknownModality: number;
|
|
15
|
+
nonDicomFiles: number;
|
|
14
16
|
};
|
|
15
17
|
};
|
|
16
18
|
export declare function readHeaderOnly(path: string): Record<string, unknown> | null;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { defaultPublicCasesPath, loadCaseById, loadCasesFromJson, loadDefaultCas
|
|
|
4
4
|
export { caseCachePath, fetchPublicCaseToCache, verifySha256, } from './public-fixtures/fetch.js';
|
|
5
5
|
export { MAX_PIXEL_BYTES, MAX_STREAM_BYTES } from './schema/limits.js';
|
|
6
6
|
export { resolveParametricSpec } from './schema/parametric.js';
|
|
7
|
-
export type { DatasetLayout, DatasetSpec, DicomdirSpec, DicomTagOverrides, FakeSignatureSpec, FileSpec, ImageSpec, InvalidUidImageSpec, LargeFileSpec, Modality, NonDicomSpec, ParametricEdgeCase, ParametricSpec, ParametricStudies, PathQuirk, Range, SeriesEntrySpec, SeriesInstances, SeriesSpec, StudySpec, TransferSyntax, ValidImageSpec, VendorWarningsImageSpec, ViolationClass, } from './schema/types.js';
|
|
7
|
+
export type { DatasetLayout, DatasetSpec, DicomdirSpec, DicomTagOverrides, EntrySpec, FakeSignatureSpec, FileSpec, ImageSpec, InvalidUidImageSpec, LargeFileSpec, Modality, NonDicomSpec, ParametricEdgeCase, ParametricSpec, ParametricStudies, PathQuirk, Range, SeriesEntrySpec, SeriesInstances, SeriesSpec, StudySpec, TransferSyntax, TreeDirNode, TreeFileNode, TreeNode, ValidImageSpec, VendorWarningsImageSpec, ViolationClass, } from './schema/types.js';
|
|
8
8
|
export { validateDatasetSpec, validateParametricSpec, } from './schema/validate.js';
|
|
9
9
|
export { type LargeFileEncoding, type LargeFileResult, type LargeImageSpec, type StreamLargeImageOptions, streamLargeImage, writeLargeImageFile, } from './syntheticFixtures/streamWrite.js';
|
|
10
10
|
export { resolveTagTemplates, type TagContext, TEMPLATE_VOCAB, } from './syntheticFixtures/tagTemplate.js';
|
|
@@ -33,6 +33,8 @@ export type FakeSignatureSpec = {
|
|
|
33
33
|
export type NonDicomSpec = {
|
|
34
34
|
type: 'non-dicom';
|
|
35
35
|
content?: string;
|
|
36
|
+
targetSizeKb?: number;
|
|
37
|
+
targetBytes?: number;
|
|
36
38
|
};
|
|
37
39
|
export type DicomdirSpec = {
|
|
38
40
|
type: 'dicomdir';
|
|
@@ -68,11 +70,22 @@ export type StudySpec = {
|
|
|
68
70
|
tags?: DicomTagOverrides;
|
|
69
71
|
count?: number;
|
|
70
72
|
};
|
|
73
|
+
export type TreeFileNode = EntrySpec & {
|
|
74
|
+
name?: string;
|
|
75
|
+
};
|
|
76
|
+
export type TreeDirNode = {
|
|
77
|
+
name?: string;
|
|
78
|
+
role?: 'study' | 'series';
|
|
79
|
+
tags?: DicomTagOverrides;
|
|
80
|
+
children: TreeNode[];
|
|
81
|
+
};
|
|
82
|
+
export type TreeNode = TreeDirNode | TreeFileNode;
|
|
71
83
|
export type DatasetLayout = 'flat' | 'hierarchical';
|
|
72
84
|
export type PathQuirk = 'trailing-dot' | 'unicode' | 'deep-nesting' | 'long-name';
|
|
73
85
|
export type DatasetSpec = {
|
|
74
86
|
entries?: EntrySpec[];
|
|
75
87
|
studies?: StudySpec[];
|
|
88
|
+
tree?: TreeNode[];
|
|
76
89
|
seed?: number;
|
|
77
90
|
uidSalt?: string;
|
|
78
91
|
layout?: DatasetLayout;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type { DatasetSpec, ParametricSpec } from './types.js';
|
|
1
|
+
import type { DatasetSpec, FileSpec, ParametricSpec } from './types.js';
|
|
2
|
+
export declare function isImageType(type: FileSpec['type']): boolean;
|
|
2
3
|
export declare const HEX_TAG_RE: RegExp;
|
|
4
|
+
export declare function positionalName(prefix: string, ordinal: number): string;
|
|
3
5
|
export declare function validateDatasetSpec(raw: unknown): DatasetSpec;
|
|
4
6
|
export declare function validateParametricSpec(raw: unknown): ParametricSpec;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { DicomTagOverrides, FileSpec, Modality, TransferSyntax } from '../schema/types.js';
|
|
1
|
+
import type { DicomTagOverrides, FileSpec, Modality, NonDicomSpec, TransferSyntax } from '../schema/types.js';
|
|
2
2
|
import type { UidSet } from './uid.js';
|
|
3
3
|
export declare function applyTagOverrides(dataset: Record<string, unknown>, tags: DicomTagOverrides | undefined): void;
|
|
4
|
+
export declare function syncMetaWithDataset(meta: Record<string, unknown>, dataset: Record<string, unknown>): void;
|
|
4
5
|
export declare function buildMeta(uid: UidSet, modality: Modality, transferSyntax?: TransferSyntax): Record<string, unknown>;
|
|
5
6
|
export declare function buildBaseImageDataset(uid: UidSet, modality: Modality): Record<string, unknown>;
|
|
6
7
|
export declare function serializeDict(meta: Record<string, unknown>, dataset: Record<string, unknown>): Buffer;
|
|
8
|
+
export declare function sizedNonDicomBytes(spec: NonDicomSpec): number | undefined;
|
|
7
9
|
export declare function buildBufferForSpec(spec: FileSpec, uid: UidSet): Buffer;
|