dicom-synth 1.3.0 → 1.5.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 +81 -16
- package/bin/dicom-synth-generate.mjs +100 -40
- package/dist/esm/collection/writer.js +37 -54
- package/dist/esm/index.js +248 -85
- package/dist/esm/schema/parametric.js +327 -0
- package/dist/esm/schema/validate.js +136 -32
- package/dist/esm/syntheticFixtures/generator.js +37 -54
- package/dist/esm/syntheticFixtures/uid.js +4 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/schema/parametric.d.ts +2 -0
- package/dist/types/schema/types.d.ts +26 -7
- package/dist/types/schema/validate.d.ts +2 -1
- package/dist/types/syntheticFixtures/uid.d.ts +2 -0
- package/package.json +2 -2
package/dist/esm/index.js
CHANGED
|
@@ -49,7 +49,6 @@ var TYPE_CAPABILITIES = {
|
|
|
49
49
|
"valid-image": { image: true },
|
|
50
50
|
"invalid-uid-image": { image: true },
|
|
51
51
|
"vendor-warnings-image": { image: true },
|
|
52
|
-
"large-image": { image: true },
|
|
53
52
|
"fake-signature": { image: false },
|
|
54
53
|
"non-dicom": { image: false },
|
|
55
54
|
dicomdir: { image: false }
|
|
@@ -86,6 +85,25 @@ function validatePositiveInt(value, path) {
|
|
|
86
85
|
);
|
|
87
86
|
}
|
|
88
87
|
}
|
|
88
|
+
function validateSeed(value, path) {
|
|
89
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`${path}: must be a finite number; got ${JSON.stringify(value)}`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function validateSizeKbLimit(kb, path) {
|
|
96
|
+
if (kb * 1024 > MAX_PIXEL_BYTES) {
|
|
97
|
+
throw new Error(`${path}: exceeds the 512 MB limit`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function validateEnum(value, valid, path) {
|
|
101
|
+
if (typeof value !== "string" || !Object.hasOwn(valid, value)) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`${path}: must be one of ${Object.keys(valid).join(", ")}; got ${JSON.stringify(value)}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
89
107
|
function validateTagKey(key, path) {
|
|
90
108
|
if (!HEX_TAG_RE.test(key) && !KEYWORD_RE.test(key)) {
|
|
91
109
|
throw new Error(
|
|
@@ -98,16 +116,16 @@ function validateEntry(entry, path) {
|
|
|
98
116
|
throw new Error(`${path}: must be an object`);
|
|
99
117
|
}
|
|
100
118
|
const e = entry;
|
|
101
|
-
|
|
102
|
-
throw new Error(
|
|
103
|
-
`${path}.type: must be one of ${Object.keys(TYPE_CAPABILITIES).join(", ")}; got ${JSON.stringify(e.type)}`
|
|
104
|
-
);
|
|
105
|
-
}
|
|
119
|
+
validateEnum(e.type, TYPE_CAPABILITIES, `${path}.type`);
|
|
106
120
|
const type = e.type;
|
|
107
121
|
const caps = TYPE_CAPABILITIES[type];
|
|
108
122
|
if ("count" in e) validatePositiveInt(e.count, `${path}.count`);
|
|
109
123
|
for (const field of [
|
|
110
124
|
"modality",
|
|
125
|
+
"rows",
|
|
126
|
+
"columns",
|
|
127
|
+
"frames",
|
|
128
|
+
"targetSizeKb",
|
|
111
129
|
"tags",
|
|
112
130
|
"violations",
|
|
113
131
|
"transferSyntax"
|
|
@@ -116,18 +134,14 @@ function validateEntry(entry, path) {
|
|
|
116
134
|
throw new Error(`${path}.${field}: not supported for type "${type}"`);
|
|
117
135
|
}
|
|
118
136
|
if ("transferSyntax" in e) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
validateEnum(
|
|
138
|
+
e.transferSyntax,
|
|
139
|
+
VALID_TRANSFER_SYNTAXES,
|
|
140
|
+
`${path}.transferSyntax`
|
|
141
|
+
);
|
|
124
142
|
}
|
|
125
143
|
if ("modality" in e) {
|
|
126
|
-
|
|
127
|
-
throw new Error(
|
|
128
|
-
`${path}.modality: must be one of ${Object.keys(VALID_MODALITIES).join(", ")}; got ${JSON.stringify(e.modality)}`
|
|
129
|
-
);
|
|
130
|
-
}
|
|
144
|
+
validateEnum(e.modality, VALID_MODALITIES, `${path}.modality`);
|
|
131
145
|
}
|
|
132
146
|
if ("tags" in e) validateTags(e.tags, `${path}.tags`);
|
|
133
147
|
if ("violations" in e) {
|
|
@@ -135,14 +149,22 @@ function validateEntry(entry, path) {
|
|
|
135
149
|
throw new Error(`${path}.violations: must be an array`);
|
|
136
150
|
}
|
|
137
151
|
for (const [i, v] of e.violations.entries()) {
|
|
138
|
-
|
|
139
|
-
throw new Error(
|
|
140
|
-
`${path}.violations[${i}]: must be one of ${Object.keys(VALID_VIOLATIONS).join(", ")}; got ${JSON.stringify(v)}`
|
|
141
|
-
);
|
|
142
|
-
}
|
|
152
|
+
validateEnum(v, VALID_VIOLATIONS, `${path}.violations[${i}]`);
|
|
143
153
|
}
|
|
144
154
|
}
|
|
145
|
-
|
|
155
|
+
const hasDims = "rows" in e || "columns" in e || "frames" in e;
|
|
156
|
+
if ("targetSizeKb" in e) {
|
|
157
|
+
if (hasDims) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`${path}: targetSizeKb cannot be combined with rows/columns/frames \u2014 use one sizing mechanism`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
validatePositiveInt(e.targetSizeKb, `${path}.targetSizeKb`);
|
|
163
|
+
validateSizeKbLimit(e.targetSizeKb, `${path}.targetSizeKb`);
|
|
164
|
+
} else if (hasDims) {
|
|
165
|
+
if (!("rows" in e) || !("columns" in e)) {
|
|
166
|
+
throw new Error(`${path}: rows and columns must be provided together`);
|
|
167
|
+
}
|
|
146
168
|
validatePositiveInt(e.rows, `${path}.rows`);
|
|
147
169
|
validatePositiveInt(e.columns, `${path}.columns`);
|
|
148
170
|
if ("frames" in e) validatePositiveInt(e.frames, `${path}.frames`);
|
|
@@ -223,23 +245,104 @@ function validateDatasetSpec(raw) {
|
|
|
223
245
|
const studies = rawStudies.map(
|
|
224
246
|
(study, i) => validateStudy(study, `studies[${i}]`)
|
|
225
247
|
);
|
|
226
|
-
if ("seed" in r)
|
|
227
|
-
|
|
248
|
+
if ("seed" in r) validateSeed(r.seed, "DatasetSpec.seed");
|
|
249
|
+
if ("layout" in r) {
|
|
250
|
+
validateEnum(r.layout, VALID_LAYOUTS, "DatasetSpec.layout");
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
...entries.length > 0 ? { entries } : {},
|
|
254
|
+
...studies.length > 0 ? { studies } : {},
|
|
255
|
+
...r.seed !== void 0 ? { seed: r.seed } : {},
|
|
256
|
+
...r.layout !== void 0 ? { layout: r.layout } : {}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function validateRange(value, path) {
|
|
260
|
+
if (typeof value === "number") {
|
|
261
|
+
validatePositiveInt(value, path);
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`${path}: must be a positive integer or { min, max }; got ${JSON.stringify(value)}`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
const r = value;
|
|
270
|
+
validatePositiveInt(r.min, `${path}.min`);
|
|
271
|
+
validatePositiveInt(r.max, `${path}.max`);
|
|
272
|
+
if (r.min > r.max) {
|
|
273
|
+
throw new Error(`${path}: min (${r.min}) must be \u2264 max (${r.max})`);
|
|
274
|
+
}
|
|
275
|
+
return value;
|
|
276
|
+
}
|
|
277
|
+
function rangeMax(range) {
|
|
278
|
+
return typeof range === "number" ? range : range.max;
|
|
279
|
+
}
|
|
280
|
+
function validateParametricStudies(studies, path) {
|
|
281
|
+
if (typeof studies !== "object" || studies === null || Array.isArray(studies)) {
|
|
282
|
+
throw new Error(`${path}: must be an object`);
|
|
283
|
+
}
|
|
284
|
+
const s = studies;
|
|
285
|
+
for (const field of ["count", "seriesPerStudy", "filesPerSeries"]) {
|
|
286
|
+
if (!(field in s)) throw new Error(`${path}.${field}: is required`);
|
|
287
|
+
validateRange(s[field], `${path}.${field}`);
|
|
288
|
+
}
|
|
289
|
+
if ("fileSizeKb" in s) {
|
|
290
|
+
validateRange(s.fileSizeKb, `${path}.fileSizeKb`);
|
|
291
|
+
validateSizeKbLimit(rangeMax(s.fileSizeKb), `${path}.fileSizeKb`);
|
|
292
|
+
}
|
|
293
|
+
if ("modalities" in s) {
|
|
294
|
+
if (!Array.isArray(s.modalities) || s.modalities.length === 0) {
|
|
295
|
+
throw new Error(`${path}.modalities: must be a non-empty array`);
|
|
296
|
+
}
|
|
297
|
+
for (const [i, m] of s.modalities.entries()) {
|
|
298
|
+
validateEnum(m, VALID_MODALITIES, `${path}.modalities[${i}]`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if ("tags" in s) validateTags(s.tags, `${path}.tags`);
|
|
302
|
+
return s;
|
|
303
|
+
}
|
|
304
|
+
function validateEdgeCase(edgeCase, path) {
|
|
305
|
+
const e = validateEntry(edgeCase, path);
|
|
306
|
+
if ("frequency" in e) {
|
|
307
|
+
if ("count" in e) {
|
|
228
308
|
throw new Error(
|
|
229
|
-
|
|
309
|
+
`${path}: frequency cannot be combined with count \u2014 frequency draws the count at resolution time`
|
|
230
310
|
);
|
|
231
311
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (typeof r.layout !== "string" || !Object.hasOwn(VALID_LAYOUTS, r.layout)) {
|
|
312
|
+
const f = e.frequency;
|
|
313
|
+
if (typeof f !== "number" || !(f >= 0 && f <= 1)) {
|
|
235
314
|
throw new Error(
|
|
236
|
-
|
|
315
|
+
`${path}.frequency: must be a number between 0 and 1; got ${JSON.stringify(f)}`
|
|
237
316
|
);
|
|
238
317
|
}
|
|
239
318
|
}
|
|
319
|
+
return e;
|
|
320
|
+
}
|
|
321
|
+
function validateParametricSpec(raw) {
|
|
322
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
323
|
+
throw new Error("ParametricSpec: must be a JSON object");
|
|
324
|
+
}
|
|
325
|
+
const r = raw;
|
|
326
|
+
if (!("studies" in r)) {
|
|
327
|
+
throw new Error("ParametricSpec.studies: is required");
|
|
328
|
+
}
|
|
329
|
+
const studies = validateParametricStudies(r.studies, "studies");
|
|
330
|
+
const edgeCases = [];
|
|
331
|
+
if ("edgeCases" in r) {
|
|
332
|
+
if (!Array.isArray(r.edgeCases)) {
|
|
333
|
+
throw new Error("ParametricSpec.edgeCases: must be an array");
|
|
334
|
+
}
|
|
335
|
+
for (const [i, e] of r.edgeCases.entries()) {
|
|
336
|
+
edgeCases.push(validateEdgeCase(e, `edgeCases[${i}]`));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if ("seed" in r) validateSeed(r.seed, "ParametricSpec.seed");
|
|
340
|
+
if ("layout" in r) {
|
|
341
|
+
validateEnum(r.layout, VALID_LAYOUTS, "ParametricSpec.layout");
|
|
342
|
+
}
|
|
240
343
|
return {
|
|
241
|
-
|
|
242
|
-
...
|
|
344
|
+
studies,
|
|
345
|
+
...edgeCases.length > 0 ? { edgeCases } : {},
|
|
243
346
|
...r.seed !== void 0 ? { seed: r.seed } : {},
|
|
244
347
|
...r.layout !== void 0 ? { layout: r.layout } : {}
|
|
245
348
|
};
|
|
@@ -355,36 +458,46 @@ function serializeDict(meta, dataset) {
|
|
|
355
458
|
);
|
|
356
459
|
return Buffer.from(dicomDict.write({ allowInvalidVRLength: true }));
|
|
357
460
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
461
|
+
var MAX_DIMENSION = 65535;
|
|
462
|
+
function applySizing(dataset, meta, spec) {
|
|
463
|
+
if (spec.targetSizeKb !== void 0) {
|
|
464
|
+
const clampDim = (n) => Math.min(MAX_DIMENSION, Math.max(1, Math.round(n)));
|
|
465
|
+
const minimalPixelBytes = dataset.PixelData.byteLength;
|
|
466
|
+
const overhead = serializeDict(meta, dataset).length - minimalPixelBytes;
|
|
467
|
+
const cells = Math.max(
|
|
468
|
+
1,
|
|
469
|
+
Math.round((spec.targetSizeKb * 1024 - overhead) / 2)
|
|
470
|
+
);
|
|
471
|
+
const rows = clampDim(Math.sqrt(cells));
|
|
472
|
+
const columns = clampDim(cells / rows);
|
|
473
|
+
dataset.Rows = rows;
|
|
474
|
+
dataset.Columns = columns;
|
|
475
|
+
dataset.PixelData = new ArrayBuffer(rows * columns * 2);
|
|
476
|
+
} else if (spec.rows !== void 0 && spec.columns !== void 0) {
|
|
477
|
+
dataset.Rows = spec.rows;
|
|
478
|
+
dataset.Columns = spec.columns;
|
|
479
|
+
if (spec.frames !== void 0) dataset.NumberOfFrames = spec.frames;
|
|
480
|
+
dataset.PixelData = new ArrayBuffer(
|
|
481
|
+
spec.rows * spec.columns * (spec.frames ?? 1) * 2
|
|
482
|
+
);
|
|
483
|
+
}
|
|
362
484
|
}
|
|
363
|
-
function
|
|
364
|
-
const
|
|
485
|
+
function buildImageBuffer(spec, uid) {
|
|
486
|
+
const modality = spec.modality ?? "CT";
|
|
487
|
+
const effectiveUid = spec.type === "invalid-uid-image" ? {
|
|
365
488
|
study: `2.25.invalid.study.${uid.study.slice(-6)}`,
|
|
366
489
|
series: `2.25.invalid.series.${uid.series.slice(-6)}`,
|
|
367
490
|
sop: `2.25.invalid.sop.${uid.sop.slice(-6)}`
|
|
368
|
-
};
|
|
369
|
-
const dataset = buildBaseImageDataset(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
dataset
|
|
377
|
-
|
|
378
|
-
return serializeDict(buildMeta(uid, modality, transferSyntax), dataset);
|
|
379
|
-
}
|
|
380
|
-
function buildLargeImageBuffer(uid, modality, rows, columns, frames, tags, transferSyntax) {
|
|
381
|
-
const dataset = buildBaseImageDataset(uid, modality);
|
|
382
|
-
dataset.Rows = rows;
|
|
383
|
-
dataset.Columns = columns;
|
|
384
|
-
dataset.NumberOfFrames = frames;
|
|
385
|
-
dataset.PixelData = new Uint8Array(rows * columns * frames * 2).buffer;
|
|
386
|
-
applyTagOverrides(dataset, tags);
|
|
387
|
-
return serializeDict(buildMeta(uid, modality, transferSyntax), dataset);
|
|
491
|
+
} : uid;
|
|
492
|
+
const dataset = buildBaseImageDataset(effectiveUid, modality);
|
|
493
|
+
if (spec.type === "vendor-warnings-image") {
|
|
494
|
+
dataset.Laterality = "";
|
|
495
|
+
dataset.PatientWeight = "0";
|
|
496
|
+
}
|
|
497
|
+
applyTagOverrides(dataset, spec.tags);
|
|
498
|
+
const meta = buildMeta(effectiveUid, modality, spec.transferSyntax);
|
|
499
|
+
applySizing(dataset, meta, spec);
|
|
500
|
+
return serializeDict(meta, dataset);
|
|
388
501
|
}
|
|
389
502
|
function buildFakeSignatureBuffer() {
|
|
390
503
|
const buf = Buffer.alloc(200, 0);
|
|
@@ -397,36 +510,9 @@ function buildNonDicomBuffer(content = "not dicom") {
|
|
|
397
510
|
function buildBufferForSpec(spec, uid) {
|
|
398
511
|
switch (spec.type) {
|
|
399
512
|
case "valid-image":
|
|
400
|
-
return buildValidImageBuffer(
|
|
401
|
-
uid,
|
|
402
|
-
spec.modality ?? "CT",
|
|
403
|
-
spec.tags,
|
|
404
|
-
spec.transferSyntax
|
|
405
|
-
);
|
|
406
513
|
case "invalid-uid-image":
|
|
407
|
-
return buildInvalidUidImageBuffer(
|
|
408
|
-
uid,
|
|
409
|
-
spec.modality ?? "CT",
|
|
410
|
-
spec.tags,
|
|
411
|
-
spec.transferSyntax
|
|
412
|
-
);
|
|
413
514
|
case "vendor-warnings-image":
|
|
414
|
-
return
|
|
415
|
-
uid,
|
|
416
|
-
spec.modality ?? "CT",
|
|
417
|
-
spec.tags,
|
|
418
|
-
spec.transferSyntax
|
|
419
|
-
);
|
|
420
|
-
case "large-image":
|
|
421
|
-
return buildLargeImageBuffer(
|
|
422
|
-
uid,
|
|
423
|
-
spec.modality ?? "CT",
|
|
424
|
-
spec.rows,
|
|
425
|
-
spec.columns,
|
|
426
|
-
spec.frames ?? 1,
|
|
427
|
-
spec.tags,
|
|
428
|
-
spec.transferSyntax
|
|
429
|
-
);
|
|
515
|
+
return buildImageBuffer(spec, uid);
|
|
430
516
|
case "fake-signature":
|
|
431
517
|
return buildFakeSignatureBuffer();
|
|
432
518
|
case "non-dicom":
|
|
@@ -457,6 +543,7 @@ function seededUid(next, role) {
|
|
|
457
543
|
return formatUid(next(), next(), role);
|
|
458
544
|
}
|
|
459
545
|
var GROUP_STREAM_OFFSET = 2654435769;
|
|
546
|
+
var PARAMETRIC_STREAM_OFFSET = 2246822507;
|
|
460
547
|
function seededStream(seed, offset, a, b) {
|
|
461
548
|
return lcg(seed * 999983 + offset + a * 1000003 + b * 7919 >>> 0);
|
|
462
549
|
}
|
|
@@ -793,6 +880,80 @@ async function fetchPublicCaseToCache(record, cacheRoot = DEFAULT_CACHE_ROOT) {
|
|
|
793
880
|
writeFileSync2(dest, buf);
|
|
794
881
|
return dest;
|
|
795
882
|
}
|
|
883
|
+
|
|
884
|
+
// src/schema/parametric.ts
|
|
885
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
886
|
+
function sample(next, range) {
|
|
887
|
+
if (typeof range === "number") return range;
|
|
888
|
+
return range.min + next() % (range.max - range.min + 1);
|
|
889
|
+
}
|
|
890
|
+
function pick(next, items) {
|
|
891
|
+
return items[next() % items.length];
|
|
892
|
+
}
|
|
893
|
+
function fraction(next) {
|
|
894
|
+
return next() / 4294967296;
|
|
895
|
+
}
|
|
896
|
+
function resolveParametricSpec(spec) {
|
|
897
|
+
const validated = validateParametricSpec(spec);
|
|
898
|
+
const seed = validated.seed ?? randomBytes2(4).readUInt32BE(0);
|
|
899
|
+
const next = seededStream(seed, PARAMETRIC_STREAM_OFFSET, 0, 0);
|
|
900
|
+
const params = validated.studies;
|
|
901
|
+
const studies = [];
|
|
902
|
+
let totalFiles = 0;
|
|
903
|
+
const studyCount = sample(next, params.count);
|
|
904
|
+
for (let st = 0; st < studyCount; st++) {
|
|
905
|
+
const seriesCount = sample(next, params.seriesPerStudy);
|
|
906
|
+
const series = [];
|
|
907
|
+
for (let se = 0; se < seriesCount; se++) {
|
|
908
|
+
const fileCount = sample(next, params.filesPerSeries);
|
|
909
|
+
totalFiles += fileCount;
|
|
910
|
+
const modality = params.modalities ? pick(next, params.modalities) : void 0;
|
|
911
|
+
const base = {
|
|
912
|
+
type: "valid-image",
|
|
913
|
+
...modality !== void 0 ? { modality } : {}
|
|
914
|
+
};
|
|
915
|
+
const entries2 = [];
|
|
916
|
+
if (params.fileSizeKb !== void 0 && typeof params.fileSizeKb !== "number") {
|
|
917
|
+
for (let f = 0; f < fileCount; f++) {
|
|
918
|
+
entries2.push({
|
|
919
|
+
...base,
|
|
920
|
+
targetSizeKb: sample(next, params.fileSizeKb)
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
} else {
|
|
924
|
+
entries2.push({
|
|
925
|
+
...base,
|
|
926
|
+
...params.fileSizeKb !== void 0 ? { targetSizeKb: params.fileSizeKb } : {},
|
|
927
|
+
...fileCount > 1 ? { count: fileCount } : {}
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
series.push({ entries: entries2 });
|
|
931
|
+
}
|
|
932
|
+
studies.push({
|
|
933
|
+
series,
|
|
934
|
+
...params.tags !== void 0 ? { tags: params.tags } : {}
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
const entries = [];
|
|
938
|
+
for (const edgeCase of validated.edgeCases ?? []) {
|
|
939
|
+
const { frequency, ...entry } = edgeCase;
|
|
940
|
+
if (frequency === void 0) {
|
|
941
|
+
entries.push(entry);
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
let drawn = 0;
|
|
945
|
+
for (let i = 0; i < totalFiles; i++) {
|
|
946
|
+
if (fraction(next) < frequency) drawn++;
|
|
947
|
+
}
|
|
948
|
+
if (drawn > 0) entries.push({ ...entry, count: drawn });
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
...entries.length > 0 ? { entries } : {},
|
|
952
|
+
studies,
|
|
953
|
+
seed,
|
|
954
|
+
...validated.layout !== void 0 ? { layout: validated.layout } : {}
|
|
955
|
+
};
|
|
956
|
+
}
|
|
796
957
|
export {
|
|
797
958
|
caseCachePath,
|
|
798
959
|
defaultPublicCasesPath,
|
|
@@ -802,7 +963,9 @@ export {
|
|
|
802
963
|
loadCaseById,
|
|
803
964
|
loadCasesFromJson,
|
|
804
965
|
loadDefaultCases,
|
|
966
|
+
resolveParametricSpec,
|
|
805
967
|
validateDatasetSpec,
|
|
968
|
+
validateParametricSpec,
|
|
806
969
|
verifySha256,
|
|
807
970
|
writeCollectionFromSpec
|
|
808
971
|
};
|