dicom-synth 1.1.0 → 1.3.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 +115 -39
- package/dist/esm/collection/writer.js +178 -34
- package/dist/esm/index.js +290 -82
- package/dist/esm/schema/validate.js +110 -46
- package/dist/esm/syntheticFixtures/generator.js +73 -27
- package/dist/esm/syntheticFixtures/uid.js +29 -1
- package/dist/types/collection/writer.d.ts +3 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/schema/types.d.ts +29 -11
- package/dist/types/syntheticFixtures/uid.d.ts +5 -0
- package/package.json +1 -1
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
// src/schema/validate.ts
|
|
2
2
|
var TYPE_CAPABILITIES = {
|
|
3
|
-
"valid-
|
|
4
|
-
"invalid-uid-
|
|
5
|
-
"vendor-warnings-
|
|
6
|
-
"large-
|
|
7
|
-
"fake-signature": {
|
|
8
|
-
"non-dicom": {
|
|
9
|
-
dicomdir: {
|
|
3
|
+
"valid-image": { image: true },
|
|
4
|
+
"invalid-uid-image": { image: true },
|
|
5
|
+
"vendor-warnings-image": { image: true },
|
|
6
|
+
"large-image": { image: true },
|
|
7
|
+
"fake-signature": { image: false },
|
|
8
|
+
"non-dicom": { image: false },
|
|
9
|
+
dicomdir: { image: false }
|
|
10
|
+
};
|
|
11
|
+
var VALID_MODALITIES = {
|
|
12
|
+
CT: true,
|
|
13
|
+
PT: true,
|
|
14
|
+
MR: true,
|
|
15
|
+
CR: true
|
|
16
|
+
};
|
|
17
|
+
var VALID_LAYOUTS = {
|
|
18
|
+
flat: true,
|
|
19
|
+
hierarchical: true
|
|
10
20
|
};
|
|
11
21
|
var VALID_TRANSFER_SYNTAXES = {
|
|
12
22
|
"explicit-vr-little-endian": true,
|
|
@@ -23,6 +33,13 @@ var VALID_VIOLATIONS = {
|
|
|
23
33
|
};
|
|
24
34
|
var HEX_TAG_RE = /^[0-9a-fA-F]{8}$/;
|
|
25
35
|
var KEYWORD_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
36
|
+
function validatePositiveInt(value, path) {
|
|
37
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`${path}: must be a positive integer; got ${JSON.stringify(value)}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
26
43
|
function validateTagKey(key, path) {
|
|
27
44
|
if (!HEX_TAG_RE.test(key) && !KEYWORD_RE.test(key)) {
|
|
28
45
|
throw new Error(
|
|
@@ -42,37 +59,31 @@ function validateEntry(entry, path) {
|
|
|
42
59
|
}
|
|
43
60
|
const type = e.type;
|
|
44
61
|
const caps = TYPE_CAPABILITIES[type];
|
|
45
|
-
if ("count" in e) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
62
|
+
if ("count" in e) validatePositiveInt(e.count, `${path}.count`);
|
|
63
|
+
for (const field of [
|
|
64
|
+
"modality",
|
|
65
|
+
"tags",
|
|
66
|
+
"violations",
|
|
67
|
+
"transferSyntax"
|
|
68
|
+
]) {
|
|
69
|
+
if (!caps.image && field in e)
|
|
70
|
+
throw new Error(`${path}.${field}: not supported for type "${type}"`);
|
|
51
71
|
}
|
|
52
72
|
if ("transferSyntax" in e) {
|
|
53
|
-
if (!caps.transferSyntax) {
|
|
54
|
-
throw new Error(
|
|
55
|
-
`${path}.transferSyntax: not supported for type "${type}"`
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
73
|
if (!Object.hasOwn(VALID_TRANSFER_SYNTAXES, e.transferSyntax)) {
|
|
59
74
|
throw new Error(
|
|
60
75
|
`${path}.transferSyntax: must be one of ${Object.keys(VALID_TRANSFER_SYNTAXES).join(", ")}`
|
|
61
76
|
);
|
|
62
77
|
}
|
|
63
78
|
}
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (typeof e.tags !== "object" || e.tags === null || Array.isArray(e.tags)) {
|
|
70
|
-
throw new Error(`${path}.tags: must be an object`);
|
|
71
|
-
}
|
|
72
|
-
for (const key of Object.keys(e.tags)) {
|
|
73
|
-
validateTagKey(key, `${path}.tags`);
|
|
79
|
+
if ("modality" in e) {
|
|
80
|
+
if (typeof e.modality !== "string" || !Object.hasOwn(VALID_MODALITIES, e.modality)) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`${path}.modality: must be one of ${Object.keys(VALID_MODALITIES).join(", ")}; got ${JSON.stringify(e.modality)}`
|
|
83
|
+
);
|
|
74
84
|
}
|
|
75
85
|
}
|
|
86
|
+
if ("tags" in e) validateTags(e.tags, `${path}.tags`);
|
|
76
87
|
if ("violations" in e) {
|
|
77
88
|
if (!Array.isArray(e.violations)) {
|
|
78
89
|
throw new Error(`${path}.violations: must be an array`);
|
|
@@ -85,18 +96,10 @@ function validateEntry(entry, path) {
|
|
|
85
96
|
}
|
|
86
97
|
}
|
|
87
98
|
}
|
|
88
|
-
if (type === "large-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
if (typeof e.columns !== "number" || !Number.isInteger(e.columns) || e.columns < 1) {
|
|
93
|
-
throw new Error(`${path}.columns: must be a positive integer`);
|
|
94
|
-
}
|
|
95
|
-
if ("frames" in e) {
|
|
96
|
-
if (typeof e.frames !== "number" || !Number.isInteger(e.frames) || e.frames < 1) {
|
|
97
|
-
throw new Error(`${path}.frames: must be a positive integer`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
99
|
+
if (type === "large-image") {
|
|
100
|
+
validatePositiveInt(e.rows, `${path}.rows`);
|
|
101
|
+
validatePositiveInt(e.columns, `${path}.columns`);
|
|
102
|
+
if ("frames" in e) validatePositiveInt(e.frames, `${path}.frames`);
|
|
100
103
|
const rows = e.rows;
|
|
101
104
|
const columns = e.columns;
|
|
102
105
|
const frames = typeof e.frames === "number" ? e.frames : 1;
|
|
@@ -108,20 +111,72 @@ function validateEntry(entry, path) {
|
|
|
108
111
|
}
|
|
109
112
|
return e;
|
|
110
113
|
}
|
|
114
|
+
function validateTags(tags, path) {
|
|
115
|
+
if (typeof tags !== "object" || tags === null || Array.isArray(tags)) {
|
|
116
|
+
throw new Error(`${path}: must be an object`);
|
|
117
|
+
}
|
|
118
|
+
for (const key of Object.keys(tags)) {
|
|
119
|
+
validateTagKey(key, path);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function validateSeries(series, path) {
|
|
123
|
+
if (typeof series !== "object" || series === null || Array.isArray(series)) {
|
|
124
|
+
throw new Error(`${path}: must be an object`);
|
|
125
|
+
}
|
|
126
|
+
const s = series;
|
|
127
|
+
if (!Array.isArray(s.entries) || s.entries.length === 0) {
|
|
128
|
+
throw new Error(`${path}.entries: must be a non-empty array`);
|
|
129
|
+
}
|
|
130
|
+
for (let i = 0; i < s.entries.length; i++) {
|
|
131
|
+
const e = validateEntry(s.entries[i], `${path}.entries[${i}]`);
|
|
132
|
+
if (!TYPE_CAPABILITIES[e.type].image) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`${path}.entries[${i}].type: "${e.type}" cannot be used inside a series \u2014 only image-generating types can be grouped`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if ("tags" in s) validateTags(s.tags, `${path}.tags`);
|
|
139
|
+
return s;
|
|
140
|
+
}
|
|
141
|
+
function validateStudy(study, path) {
|
|
142
|
+
if (typeof study !== "object" || study === null || Array.isArray(study)) {
|
|
143
|
+
throw new Error(`${path}: must be an object`);
|
|
144
|
+
}
|
|
145
|
+
const st = study;
|
|
146
|
+
if (!Array.isArray(st.series) || st.series.length === 0) {
|
|
147
|
+
throw new Error(`${path}.series: must be a non-empty array`);
|
|
148
|
+
}
|
|
149
|
+
for (let i = 0; i < st.series.length; i++) {
|
|
150
|
+
validateSeries(st.series[i], `${path}.series[${i}]`);
|
|
151
|
+
}
|
|
152
|
+
if ("count" in st) validatePositiveInt(st.count, `${path}.count`);
|
|
153
|
+
if ("tags" in st) validateTags(st.tags, `${path}.tags`);
|
|
154
|
+
return st;
|
|
155
|
+
}
|
|
111
156
|
function validateDatasetSpec(raw) {
|
|
112
157
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
113
158
|
throw new Error("DatasetSpec: must be a JSON object");
|
|
114
159
|
}
|
|
115
160
|
const r = raw;
|
|
116
|
-
if (!Array.isArray(r.entries)) {
|
|
161
|
+
if ("entries" in r && !Array.isArray(r.entries)) {
|
|
117
162
|
throw new Error("DatasetSpec.entries: must be an array");
|
|
118
163
|
}
|
|
119
|
-
if (r.
|
|
120
|
-
throw new Error("DatasetSpec.
|
|
164
|
+
if ("studies" in r && !Array.isArray(r.studies)) {
|
|
165
|
+
throw new Error("DatasetSpec.studies: must be an array");
|
|
166
|
+
}
|
|
167
|
+
const rawEntries = r.entries ?? [];
|
|
168
|
+
const rawStudies = r.studies ?? [];
|
|
169
|
+
if (rawEntries.length === 0 && rawStudies.length === 0) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
'DatasetSpec: must contain at least one of "entries" or "studies" (non-empty)'
|
|
172
|
+
);
|
|
121
173
|
}
|
|
122
|
-
const entries =
|
|
174
|
+
const entries = rawEntries.map(
|
|
123
175
|
(entry, i) => validateEntry(entry, `entries[${i}]`)
|
|
124
176
|
);
|
|
177
|
+
const studies = rawStudies.map(
|
|
178
|
+
(study, i) => validateStudy(study, `studies[${i}]`)
|
|
179
|
+
);
|
|
125
180
|
if ("seed" in r) {
|
|
126
181
|
if (typeof r.seed !== "number" || !Number.isFinite(r.seed)) {
|
|
127
182
|
throw new Error(
|
|
@@ -129,9 +184,18 @@ function validateDatasetSpec(raw) {
|
|
|
129
184
|
);
|
|
130
185
|
}
|
|
131
186
|
}
|
|
187
|
+
if ("layout" in r) {
|
|
188
|
+
if (typeof r.layout !== "string" || !Object.hasOwn(VALID_LAYOUTS, r.layout)) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`DatasetSpec.layout: must be one of ${Object.keys(VALID_LAYOUTS).join(", ")}; got ${JSON.stringify(r.layout)}`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
132
194
|
return {
|
|
133
|
-
entries,
|
|
134
|
-
...
|
|
195
|
+
...entries.length > 0 ? { entries } : {},
|
|
196
|
+
...studies.length > 0 ? { studies } : {},
|
|
197
|
+
...r.seed !== void 0 ? { seed: r.seed } : {},
|
|
198
|
+
...r.layout !== void 0 ? { layout: r.layout } : {}
|
|
135
199
|
};
|
|
136
200
|
}
|
|
137
201
|
export {
|
|
@@ -44,7 +44,35 @@ var MAX_PIXEL_BYTES = 512 * 1024 * 1024;
|
|
|
44
44
|
var HEX_TAG_RE = /^[0-9a-fA-F]{8}$/;
|
|
45
45
|
|
|
46
46
|
// src/syntheticFixtures/generator.ts
|
|
47
|
-
var
|
|
47
|
+
var MODALITY_PRESETS = {
|
|
48
|
+
CT: {
|
|
49
|
+
sopClassUid: "1.2.840.10008.5.1.4.1.1.2",
|
|
50
|
+
// CT Image Storage
|
|
51
|
+
attributes: {}
|
|
52
|
+
},
|
|
53
|
+
PT: {
|
|
54
|
+
sopClassUid: "1.2.840.10008.5.1.4.1.1.128",
|
|
55
|
+
// PET Image Storage
|
|
56
|
+
attributes: {
|
|
57
|
+
Units: "BQML",
|
|
58
|
+
DecayCorrection: "NONE",
|
|
59
|
+
CorrectedImage: ["ATTN", "DECY"]
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
MR: {
|
|
63
|
+
sopClassUid: "1.2.840.10008.5.1.4.1.1.4",
|
|
64
|
+
// MR Image Storage
|
|
65
|
+
attributes: {
|
|
66
|
+
ScanningSequence: "SE",
|
|
67
|
+
SequenceVariant: "NONE"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
CR: {
|
|
71
|
+
sopClassUid: "1.2.840.10008.5.1.4.1.1.1",
|
|
72
|
+
// CR Image Storage
|
|
73
|
+
attributes: {}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
48
76
|
var TRANSFER_SYNTAX_UID = {
|
|
49
77
|
"explicit-vr-little-endian": "1.2.840.10008.1.2.1",
|
|
50
78
|
"implicit-vr-little-endian": "1.2.840.10008.1.2"
|
|
@@ -77,22 +105,23 @@ function applyTagOverrides(dataset, tags) {
|
|
|
77
105
|
}
|
|
78
106
|
}
|
|
79
107
|
}
|
|
80
|
-
function buildMeta(uid, transferSyntax = "explicit-vr-little-endian") {
|
|
108
|
+
function buildMeta(uid, modality, transferSyntax = "explicit-vr-little-endian") {
|
|
81
109
|
return {
|
|
82
110
|
FileMetaInformationVersion: new Uint8Array([0, 1]).buffer,
|
|
83
|
-
MediaStorageSOPClassUID:
|
|
111
|
+
MediaStorageSOPClassUID: MODALITY_PRESETS[modality].sopClassUid,
|
|
84
112
|
MediaStorageSOPInstanceUID: uid.sop,
|
|
85
113
|
TransferSyntaxUID: TRANSFER_SYNTAX_UID[transferSyntax],
|
|
86
114
|
ImplementationClassUID: "1.2.3.4",
|
|
87
115
|
ImplementationVersionName: "SYNTH"
|
|
88
116
|
};
|
|
89
117
|
}
|
|
90
|
-
function
|
|
118
|
+
function buildBaseImageDataset(uid, modality) {
|
|
119
|
+
const preset = MODALITY_PRESETS[modality];
|
|
91
120
|
return {
|
|
92
121
|
PatientName: "SYNTH^SUBJECT",
|
|
93
122
|
PatientID: "SYNTH_PID",
|
|
94
|
-
Modality:
|
|
95
|
-
SOPClassUID:
|
|
123
|
+
Modality: modality,
|
|
124
|
+
SOPClassUID: preset.sopClassUid,
|
|
96
125
|
SOPInstanceUID: uid.sop,
|
|
97
126
|
SeriesInstanceUID: uid.series,
|
|
98
127
|
StudyInstanceUID: uid.study,
|
|
@@ -109,7 +138,8 @@ function buildBaseCtDataset(uid) {
|
|
|
109
138
|
PixelData: new Uint8Array([0, 0]).buffer,
|
|
110
139
|
// Declares PixelData's VR explicitly — without it dcmjs logs
|
|
111
140
|
// "No value representation given for PixelData" and falls back to OW anyway.
|
|
112
|
-
_vrMap: { PixelData: "OW" }
|
|
141
|
+
_vrMap: { PixelData: "OW" },
|
|
142
|
+
...preset.attributes
|
|
113
143
|
};
|
|
114
144
|
}
|
|
115
145
|
function serializeDict(meta, dataset) {
|
|
@@ -123,36 +153,36 @@ function serializeDict(meta, dataset) {
|
|
|
123
153
|
);
|
|
124
154
|
return Buffer.from(dicomDict.write({ allowInvalidVRLength: true }));
|
|
125
155
|
}
|
|
126
|
-
function
|
|
127
|
-
const dataset =
|
|
156
|
+
function buildValidImageBuffer(uid, modality, tags, transferSyntax) {
|
|
157
|
+
const dataset = buildBaseImageDataset(uid, modality);
|
|
128
158
|
applyTagOverrides(dataset, tags);
|
|
129
|
-
return serializeDict(buildMeta(uid, transferSyntax), dataset);
|
|
159
|
+
return serializeDict(buildMeta(uid, modality, transferSyntax), dataset);
|
|
130
160
|
}
|
|
131
|
-
function
|
|
161
|
+
function buildInvalidUidImageBuffer(uid, modality, tags, transferSyntax) {
|
|
132
162
|
const invalidUid = {
|
|
133
163
|
study: `2.25.invalid.study.${uid.study.slice(-6)}`,
|
|
134
164
|
series: `2.25.invalid.series.${uid.series.slice(-6)}`,
|
|
135
165
|
sop: `2.25.invalid.sop.${uid.sop.slice(-6)}`
|
|
136
166
|
};
|
|
137
|
-
const dataset =
|
|
167
|
+
const dataset = buildBaseImageDataset(invalidUid, modality);
|
|
138
168
|
applyTagOverrides(dataset, tags);
|
|
139
|
-
return serializeDict(buildMeta(invalidUid, transferSyntax), dataset);
|
|
169
|
+
return serializeDict(buildMeta(invalidUid, modality, transferSyntax), dataset);
|
|
140
170
|
}
|
|
141
|
-
function
|
|
142
|
-
const dataset =
|
|
171
|
+
function buildVendorWarningsImageBuffer(uid, modality, tags, transferSyntax) {
|
|
172
|
+
const dataset = buildBaseImageDataset(uid, modality);
|
|
143
173
|
dataset.Laterality = "";
|
|
144
174
|
dataset.PatientWeight = "0";
|
|
145
175
|
applyTagOverrides(dataset, tags);
|
|
146
|
-
return serializeDict(buildMeta(uid, transferSyntax), dataset);
|
|
176
|
+
return serializeDict(buildMeta(uid, modality, transferSyntax), dataset);
|
|
147
177
|
}
|
|
148
|
-
function
|
|
149
|
-
const dataset =
|
|
178
|
+
function buildLargeImageBuffer(uid, modality, rows, columns, frames, tags, transferSyntax) {
|
|
179
|
+
const dataset = buildBaseImageDataset(uid, modality);
|
|
150
180
|
dataset.Rows = rows;
|
|
151
181
|
dataset.Columns = columns;
|
|
152
182
|
dataset.NumberOfFrames = frames;
|
|
153
183
|
dataset.PixelData = new Uint8Array(rows * columns * frames * 2).buffer;
|
|
154
184
|
applyTagOverrides(dataset, tags);
|
|
155
|
-
return serializeDict(buildMeta(uid, transferSyntax), dataset);
|
|
185
|
+
return serializeDict(buildMeta(uid, modality, transferSyntax), dataset);
|
|
156
186
|
}
|
|
157
187
|
function buildFakeSignatureBuffer() {
|
|
158
188
|
const buf = Buffer.alloc(200, 0);
|
|
@@ -164,15 +194,31 @@ function buildNonDicomBuffer(content = "not dicom") {
|
|
|
164
194
|
}
|
|
165
195
|
function buildBufferForSpec(spec, uid) {
|
|
166
196
|
switch (spec.type) {
|
|
167
|
-
case "valid-
|
|
168
|
-
return
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
197
|
+
case "valid-image":
|
|
198
|
+
return buildValidImageBuffer(
|
|
199
|
+
uid,
|
|
200
|
+
spec.modality ?? "CT",
|
|
201
|
+
spec.tags,
|
|
202
|
+
spec.transferSyntax
|
|
203
|
+
);
|
|
204
|
+
case "invalid-uid-image":
|
|
205
|
+
return buildInvalidUidImageBuffer(
|
|
206
|
+
uid,
|
|
207
|
+
spec.modality ?? "CT",
|
|
208
|
+
spec.tags,
|
|
209
|
+
spec.transferSyntax
|
|
210
|
+
);
|
|
211
|
+
case "vendor-warnings-image":
|
|
212
|
+
return buildVendorWarningsImageBuffer(
|
|
213
|
+
uid,
|
|
214
|
+
spec.modality ?? "CT",
|
|
215
|
+
spec.tags,
|
|
216
|
+
spec.transferSyntax
|
|
217
|
+
);
|
|
218
|
+
case "large-image":
|
|
219
|
+
return buildLargeImageBuffer(
|
|
175
220
|
uid,
|
|
221
|
+
spec.modality ?? "CT",
|
|
176
222
|
spec.rows,
|
|
177
223
|
spec.columns,
|
|
178
224
|
spec.frames ?? 1,
|
|
@@ -18,6 +18,33 @@ function randomUid(role) {
|
|
|
18
18
|
function seededUid(next, role) {
|
|
19
19
|
return formatUid(next(), next(), role);
|
|
20
20
|
}
|
|
21
|
+
var GROUP_STREAM_OFFSET = 2654435769;
|
|
22
|
+
function seededStream(seed, offset, a, b) {
|
|
23
|
+
return lcg(seed * 999983 + offset + a * 1000003 + b * 7919 >>> 0);
|
|
24
|
+
}
|
|
25
|
+
function makeGroupUidGenerator(seed) {
|
|
26
|
+
if (seed === void 0) {
|
|
27
|
+
return {
|
|
28
|
+
study: () => randomUid("study"),
|
|
29
|
+
series: () => randomUid("series")
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
study: (studyIndex) => seededUid(
|
|
34
|
+
seededStream(seed, GROUP_STREAM_OFFSET, studyIndex + 1, 0),
|
|
35
|
+
"study"
|
|
36
|
+
),
|
|
37
|
+
series: (studyIndex, seriesIndex) => seededUid(
|
|
38
|
+
seededStream(
|
|
39
|
+
seed,
|
|
40
|
+
GROUP_STREAM_OFFSET,
|
|
41
|
+
studyIndex + 1,
|
|
42
|
+
seriesIndex + 1
|
|
43
|
+
),
|
|
44
|
+
"series"
|
|
45
|
+
)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
21
48
|
function makeUidGenerator(seed) {
|
|
22
49
|
if (seed === void 0) {
|
|
23
50
|
return () => ({
|
|
@@ -27,7 +54,7 @@ function makeUidGenerator(seed) {
|
|
|
27
54
|
});
|
|
28
55
|
}
|
|
29
56
|
return (fileIndex) => {
|
|
30
|
-
const next =
|
|
57
|
+
const next = seededStream(seed, 0, fileIndex, 0);
|
|
31
58
|
return {
|
|
32
59
|
study: seededUid(next, "study"),
|
|
33
60
|
series: seededUid(next, "series"),
|
|
@@ -36,5 +63,6 @@ function makeUidGenerator(seed) {
|
|
|
36
63
|
};
|
|
37
64
|
}
|
|
38
65
|
export {
|
|
66
|
+
makeGroupUidGenerator,
|
|
39
67
|
makeUidGenerator
|
|
40
68
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { DatasetSpec, FileSpec } from '../schema/types.js';
|
|
2
|
+
import type { UidSet } from '../syntheticFixtures/uid.js';
|
|
2
3
|
export type GeneratedFile = {
|
|
3
4
|
filename: string;
|
|
5
|
+
relativePath: string;
|
|
4
6
|
buffer: Buffer;
|
|
5
7
|
type: FileSpec['type'];
|
|
6
8
|
index: number;
|
|
@@ -14,6 +16,7 @@ export declare function generateFile(spec: FileSpec, options?: {
|
|
|
14
16
|
index?: number;
|
|
15
17
|
seed?: number;
|
|
16
18
|
padWidth?: number;
|
|
19
|
+
uid?: Partial<UidSet>;
|
|
17
20
|
}): Promise<GeneratedFile>;
|
|
18
21
|
export declare function generateCollectionFromSpec(spec: DatasetSpec): AsyncGenerator<GeneratedFile>;
|
|
19
22
|
export declare function writeCollectionFromSpec(spec: DatasetSpec, outDir: string): Promise<CollectionManifest>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { type CollectionManifest, type GeneratedFile, generateCollectionFromSpec, generateFile, writeCollectionFromSpec, } from './collection/writer.js';
|
|
2
2
|
export { defaultPublicCasesPath, loadCaseById, loadCasesFromJson, loadDefaultCases, type PublicCaseRecord, type PublicCaseSource, } from './public-fixtures/catalog.js';
|
|
3
3
|
export { caseCachePath, fetchPublicCaseToCache, verifySha256, } from './public-fixtures/fetch.js';
|
|
4
|
-
export type { DatasetSpec, DicomdirSpec, DicomTagOverrides, FakeSignatureSpec, FileSpec,
|
|
4
|
+
export type { DatasetLayout, DatasetSpec, DicomdirSpec, DicomTagOverrides, FakeSignatureSpec, FileSpec, ImageSpec, InvalidUidImageSpec, LargeImageSpec, Modality, NonDicomSpec, SeriesEntrySpec, SeriesSpec, StudySpec, TransferSyntax, ValidImageSpec, VendorWarningsImageSpec, ViolationClass, } from './schema/types.js';
|
|
5
5
|
export { validateDatasetSpec } from './schema/validate.js';
|
|
@@ -7,22 +7,24 @@ export type DicomTagOverrides = {
|
|
|
7
7
|
[tag: string]: unknown;
|
|
8
8
|
};
|
|
9
9
|
export type ViolationClass = 'uid-too-long' | 'non-conformant-uid' | 'missing-meta-header' | 'malformed-sq-delimiter' | 'vr-max-length-exceeded' | 'missing-type1-tag';
|
|
10
|
-
type
|
|
10
|
+
export type Modality = 'CT' | 'PT' | 'MR' | 'CR';
|
|
11
|
+
type BaseImageSpec = {
|
|
12
|
+
modality?: Modality;
|
|
11
13
|
tags?: DicomTagOverrides;
|
|
12
14
|
transferSyntax?: TransferSyntax;
|
|
13
15
|
violations?: ViolationClass[];
|
|
14
16
|
};
|
|
15
|
-
export type
|
|
16
|
-
type: 'valid-
|
|
17
|
+
export type ValidImageSpec = BaseImageSpec & {
|
|
18
|
+
type: 'valid-image';
|
|
17
19
|
};
|
|
18
|
-
export type
|
|
19
|
-
type: 'invalid-uid-
|
|
20
|
+
export type InvalidUidImageSpec = BaseImageSpec & {
|
|
21
|
+
type: 'invalid-uid-image';
|
|
20
22
|
};
|
|
21
|
-
export type
|
|
22
|
-
type: 'vendor-warnings-
|
|
23
|
+
export type VendorWarningsImageSpec = BaseImageSpec & {
|
|
24
|
+
type: 'vendor-warnings-image';
|
|
23
25
|
};
|
|
24
|
-
export type
|
|
25
|
-
type: 'large-
|
|
26
|
+
export type LargeImageSpec = BaseImageSpec & {
|
|
27
|
+
type: 'large-image';
|
|
26
28
|
rows: number;
|
|
27
29
|
columns: number;
|
|
28
30
|
frames?: number;
|
|
@@ -37,12 +39,28 @@ export type NonDicomSpec = {
|
|
|
37
39
|
export type DicomdirSpec = {
|
|
38
40
|
type: 'dicomdir';
|
|
39
41
|
};
|
|
40
|
-
export type
|
|
42
|
+
export type ImageSpec = ValidImageSpec | InvalidUidImageSpec | VendorWarningsImageSpec | LargeImageSpec;
|
|
43
|
+
export type FileSpec = ImageSpec | FakeSignatureSpec | NonDicomSpec | DicomdirSpec;
|
|
41
44
|
export type EntrySpec = FileSpec & {
|
|
42
45
|
count?: number;
|
|
43
46
|
};
|
|
47
|
+
export type SeriesEntrySpec = ImageSpec & {
|
|
48
|
+
count?: number;
|
|
49
|
+
};
|
|
50
|
+
export type SeriesSpec = {
|
|
51
|
+
entries: SeriesEntrySpec[];
|
|
52
|
+
tags?: DicomTagOverrides;
|
|
53
|
+
};
|
|
54
|
+
export type StudySpec = {
|
|
55
|
+
series: SeriesSpec[];
|
|
56
|
+
tags?: DicomTagOverrides;
|
|
57
|
+
count?: number;
|
|
58
|
+
};
|
|
59
|
+
export type DatasetLayout = 'flat' | 'hierarchical';
|
|
44
60
|
export type DatasetSpec = {
|
|
45
|
-
entries
|
|
61
|
+
entries?: EntrySpec[];
|
|
62
|
+
studies?: StudySpec[];
|
|
46
63
|
seed?: number;
|
|
64
|
+
layout?: DatasetLayout;
|
|
47
65
|
};
|
|
48
66
|
export {};
|
|
@@ -3,5 +3,10 @@ export type UidSet = {
|
|
|
3
3
|
series: string;
|
|
4
4
|
sop: string;
|
|
5
5
|
};
|
|
6
|
+
export type GroupUidGenerator = {
|
|
7
|
+
study: (studyIndex: number) => string;
|
|
8
|
+
series: (studyIndex: number, seriesIndex: number) => string;
|
|
9
|
+
};
|
|
10
|
+
export declare function makeGroupUidGenerator(seed?: number): GroupUidGenerator;
|
|
6
11
|
export type UidGenerator = (fileIndex: number) => UidSet;
|
|
7
12
|
export declare function makeUidGenerator(seed?: number): UidGenerator;
|