dicom-synth 1.3.0 → 1.4.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 CHANGED
@@ -191,30 +191,43 @@ An `EntrySpec` is a `FileSpec` plus an optional `count` field (how many times to
191
191
  | `valid-image` | Standards-valid image (CT by default — see `modality`); numeric UIDs, complete meta header | strict |
192
192
  | `invalid-uid-image` | Image with non-numeric UIDs (letters in UID components) | edge |
193
193
  | `vendor-warnings-image` | Image with empty `Laterality` and `PatientWeight = 0` — produces dciodvfy warnings | edge |
194
- | `large-image` | Image with configurable pixel dimensions; `rows` and `columns` required | strict |
195
194
  | `fake-signature` | 200-byte buffer with `XXXX` at preamble offset — no DICM magic | edge |
196
195
  | `non-dicom` | Arbitrary text buffer; no extension in filename | edge |
197
196
  | `dicomdir` | Minimal DICOMDIR file | strict |
198
197
 
199
- **`large-image` fields:**
198
+ **`non-dicom` fields:**
200
199
 
201
200
  ```json
202
- { "type": "large-image", "rows": 512, "columns": 512, "frames": 100 }
201
+ { "type": "non-dicom", "content": "not a dicom file" }
203
202
  ```
204
203
 
205
- `frames` defaults to 1. A 512×512×100 image produces a buffer of ~52 MB.
204
+ `content` defaults to `"not dicom"`.
206
205
 
207
- **`non-dicom` fields:**
206
+ ### Sizing (`rows`/`columns`/`frames` or `targetSizeKb`)
207
+
208
+ Available on all image types. Two mutually exclusive mechanisms; omitting both produces a minimal 1×1 image.
209
+
210
+ **Explicit pixel dimensions** — for geometry-shaped cases (large frames, strips, multi-frame):
208
211
 
209
212
  ```json
210
- { "type": "non-dicom", "content": "not a dicom file" }
213
+ { "type": "valid-image", "rows": 512, "columns": 512, "frames": 100 }
211
214
  ```
212
215
 
213
- `content` defaults to `"not dicom"`.
216
+ `rows` and `columns` must be provided together; `frames` defaults to 1. A 512×512×100 image produces a buffer of ~52 MB.
217
+
218
+ **Target file size** — for byte-shaped cases (upload limits, memory ceilings):
219
+
220
+ ```json
221
+ { "type": "valid-image", "targetSizeKb": 250000 }
222
+ ```
223
+
224
+ Pads `PixelData` so the file lands within ±2% or ±4 KB of the target (whichever is larger), accounting for tag and transfer-syntax overhead. `Rows`/`Columns` stay consistent with the pixel data, so the file remains conformant.
225
+
226
+ Both mechanisms are capped at 512 MB of pixel data to prevent accidental OOM. A target smaller than the file's fixed overhead (roughly 1 KB) produces the minimal possible file, slightly above target. When sizing fields are set they control `Rows`, `Columns`, and `PixelData` — tag overrides of those keywords do not apply.
214
227
 
215
228
  ### Modality presets (`modality`)
216
229
 
217
- Available on `valid-image`, `invalid-uid-image`, `vendor-warnings-image`, and `large-image`. Defaults to `CT`.
230
+ Available on all image types. Defaults to `CT`.
218
231
 
219
232
  ```json
220
233
  { "type": "valid-image", "modality": "PT" }
@@ -276,7 +289,7 @@ import type { StudySpec, SeriesSpec, SeriesEntrySpec } from 'dicom-synth'
276
289
 
277
290
  ### Tag overrides (`tags`)
278
291
 
279
- Available on `valid-image`, `invalid-uid-image`, `vendor-warnings-image`, and `large-image`.
292
+ Available on all image types.
280
293
 
281
294
  ```json
282
295
  {
@@ -297,7 +310,7 @@ import type { DicomTagOverrides } from 'dicom-synth'
297
310
 
298
311
  ### Transfer syntax (`transferSyntax`)
299
312
 
300
- Available on `valid-image`, `invalid-uid-image`, `vendor-warnings-image`, and `large-image`.
313
+ Available on all image types.
301
314
 
302
315
  | Value | `TransferSyntaxUID` in meta header |
303
316
  |---|---|
@@ -307,7 +320,7 @@ Available on `valid-image`, `invalid-uid-image`, `vendor-warnings-image`, and `l
307
320
 
308
321
  ### Violation injection (`violations`)
309
322
 
310
- Available on `valid-image`, `invalid-uid-image`, `vendor-warnings-image`, and `large-image`. Violations are applied as post-processing transforms to an otherwise valid buffer.
323
+ Available on all image types. Violations are applied as post-processing transforms to an otherwise valid buffer.
311
324
 
312
325
  | Violation | Effect | Detectable by dciodvfy |
313
326
  |---|---|---|
@@ -346,7 +359,8 @@ The validator throws with a human-readable message on any structural error.
346
359
  { "type": "valid-image", "count": 10, "tags": { "PatientID": "P001" }, "transferSyntax": "explicit-vr-little-endian" },
347
360
  { "type": "invalid-uid-image", "count": 3 },
348
361
  { "type": "vendor-warnings-image", "count": 2 },
349
- { "type": "large-image", "count": 1, "rows": 512, "columns": 512, "frames": 100 },
362
+ { "type": "valid-image", "count": 1, "rows": 512, "columns": 512, "frames": 100 },
363
+ { "type": "valid-image", "count": 1, "targetSizeKb": 250000 },
350
364
  { "type": "valid-image", "count": 1, "violations": ["uid-too-long", "missing-meta-header"] },
351
365
  { "type": "fake-signature", "count": 5 },
352
366
  { "type": "non-dicom", "count": 2, "content": "garbage" },
@@ -158,36 +158,46 @@ function serializeDict(meta, dataset) {
158
158
  );
159
159
  return Buffer.from(dicomDict.write({ allowInvalidVRLength: true }));
160
160
  }
161
- function buildValidImageBuffer(uid, modality, tags, transferSyntax) {
162
- const dataset = buildBaseImageDataset(uid, modality);
163
- applyTagOverrides(dataset, tags);
164
- return serializeDict(buildMeta(uid, modality, transferSyntax), dataset);
161
+ var MAX_DIMENSION = 65535;
162
+ function applySizing(dataset, meta, spec) {
163
+ if (spec.targetSizeKb !== void 0) {
164
+ const clampDim = (n) => Math.min(MAX_DIMENSION, Math.max(1, Math.round(n)));
165
+ const minimalPixelBytes = dataset.PixelData.byteLength;
166
+ const overhead = serializeDict(meta, dataset).length - minimalPixelBytes;
167
+ const cells = Math.max(
168
+ 1,
169
+ Math.round((spec.targetSizeKb * 1024 - overhead) / 2)
170
+ );
171
+ const rows = clampDim(Math.sqrt(cells));
172
+ const columns = clampDim(cells / rows);
173
+ dataset.Rows = rows;
174
+ dataset.Columns = columns;
175
+ dataset.PixelData = new ArrayBuffer(rows * columns * 2);
176
+ } else if (spec.rows !== void 0 && spec.columns !== void 0) {
177
+ dataset.Rows = spec.rows;
178
+ dataset.Columns = spec.columns;
179
+ if (spec.frames !== void 0) dataset.NumberOfFrames = spec.frames;
180
+ dataset.PixelData = new ArrayBuffer(
181
+ spec.rows * spec.columns * (spec.frames ?? 1) * 2
182
+ );
183
+ }
165
184
  }
166
- function buildInvalidUidImageBuffer(uid, modality, tags, transferSyntax) {
167
- const invalidUid = {
185
+ function buildImageBuffer(spec, uid) {
186
+ const modality = spec.modality ?? "CT";
187
+ const effectiveUid = spec.type === "invalid-uid-image" ? {
168
188
  study: `2.25.invalid.study.${uid.study.slice(-6)}`,
169
189
  series: `2.25.invalid.series.${uid.series.slice(-6)}`,
170
190
  sop: `2.25.invalid.sop.${uid.sop.slice(-6)}`
171
- };
172
- const dataset = buildBaseImageDataset(invalidUid, modality);
173
- applyTagOverrides(dataset, tags);
174
- return serializeDict(buildMeta(invalidUid, modality, transferSyntax), dataset);
175
- }
176
- function buildVendorWarningsImageBuffer(uid, modality, tags, transferSyntax) {
177
- const dataset = buildBaseImageDataset(uid, modality);
178
- dataset.Laterality = "";
179
- dataset.PatientWeight = "0";
180
- applyTagOverrides(dataset, tags);
181
- return serializeDict(buildMeta(uid, modality, transferSyntax), dataset);
182
- }
183
- function buildLargeImageBuffer(uid, modality, rows, columns, frames, tags, transferSyntax) {
184
- const dataset = buildBaseImageDataset(uid, modality);
185
- dataset.Rows = rows;
186
- dataset.Columns = columns;
187
- dataset.NumberOfFrames = frames;
188
- dataset.PixelData = new Uint8Array(rows * columns * frames * 2).buffer;
189
- applyTagOverrides(dataset, tags);
190
- return serializeDict(buildMeta(uid, modality, transferSyntax), dataset);
191
+ } : uid;
192
+ const dataset = buildBaseImageDataset(effectiveUid, modality);
193
+ if (spec.type === "vendor-warnings-image") {
194
+ dataset.Laterality = "";
195
+ dataset.PatientWeight = "0";
196
+ }
197
+ applyTagOverrides(dataset, spec.tags);
198
+ const meta = buildMeta(effectiveUid, modality, spec.transferSyntax);
199
+ applySizing(dataset, meta, spec);
200
+ return serializeDict(meta, dataset);
191
201
  }
192
202
  function buildFakeSignatureBuffer() {
193
203
  const buf = Buffer.alloc(200, 0);
@@ -200,36 +210,9 @@ function buildNonDicomBuffer(content = "not dicom") {
200
210
  function buildBufferForSpec(spec, uid) {
201
211
  switch (spec.type) {
202
212
  case "valid-image":
203
- return buildValidImageBuffer(
204
- uid,
205
- spec.modality ?? "CT",
206
- spec.tags,
207
- spec.transferSyntax
208
- );
209
213
  case "invalid-uid-image":
210
- return buildInvalidUidImageBuffer(
211
- uid,
212
- spec.modality ?? "CT",
213
- spec.tags,
214
- spec.transferSyntax
215
- );
216
214
  case "vendor-warnings-image":
217
- return buildVendorWarningsImageBuffer(
218
- uid,
219
- spec.modality ?? "CT",
220
- spec.tags,
221
- spec.transferSyntax
222
- );
223
- case "large-image":
224
- return buildLargeImageBuffer(
225
- uid,
226
- spec.modality ?? "CT",
227
- spec.rows,
228
- spec.columns,
229
- spec.frames ?? 1,
230
- spec.tags,
231
- spec.transferSyntax
232
- );
215
+ return buildImageBuffer(spec, uid);
233
216
  case "fake-signature":
234
217
  return buildFakeSignatureBuffer();
235
218
  case "non-dicom":
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 }
@@ -108,6 +107,10 @@ function validateEntry(entry, path) {
108
107
  if ("count" in e) validatePositiveInt(e.count, `${path}.count`);
109
108
  for (const field of [
110
109
  "modality",
110
+ "rows",
111
+ "columns",
112
+ "frames",
113
+ "targetSizeKb",
111
114
  "tags",
112
115
  "violations",
113
116
  "transferSyntax"
@@ -142,7 +145,21 @@ function validateEntry(entry, path) {
142
145
  }
143
146
  }
144
147
  }
145
- if (type === "large-image") {
148
+ const hasDims = "rows" in e || "columns" in e || "frames" in e;
149
+ if ("targetSizeKb" in e) {
150
+ if (hasDims) {
151
+ throw new Error(
152
+ `${path}: targetSizeKb cannot be combined with rows/columns/frames \u2014 use one sizing mechanism`
153
+ );
154
+ }
155
+ validatePositiveInt(e.targetSizeKb, `${path}.targetSizeKb`);
156
+ if (e.targetSizeKb * 1024 > MAX_PIXEL_BYTES) {
157
+ throw new Error(`${path}.targetSizeKb: exceeds the 512 MB limit`);
158
+ }
159
+ } else if (hasDims) {
160
+ if (!("rows" in e) || !("columns" in e)) {
161
+ throw new Error(`${path}: rows and columns must be provided together`);
162
+ }
146
163
  validatePositiveInt(e.rows, `${path}.rows`);
147
164
  validatePositiveInt(e.columns, `${path}.columns`);
148
165
  if ("frames" in e) validatePositiveInt(e.frames, `${path}.frames`);
@@ -355,36 +372,46 @@ function serializeDict(meta, dataset) {
355
372
  );
356
373
  return Buffer.from(dicomDict.write({ allowInvalidVRLength: true }));
357
374
  }
358
- function buildValidImageBuffer(uid, modality, tags, transferSyntax) {
359
- const dataset = buildBaseImageDataset(uid, modality);
360
- applyTagOverrides(dataset, tags);
361
- return serializeDict(buildMeta(uid, modality, transferSyntax), dataset);
375
+ var MAX_DIMENSION = 65535;
376
+ function applySizing(dataset, meta, spec) {
377
+ if (spec.targetSizeKb !== void 0) {
378
+ const clampDim = (n) => Math.min(MAX_DIMENSION, Math.max(1, Math.round(n)));
379
+ const minimalPixelBytes = dataset.PixelData.byteLength;
380
+ const overhead = serializeDict(meta, dataset).length - minimalPixelBytes;
381
+ const cells = Math.max(
382
+ 1,
383
+ Math.round((spec.targetSizeKb * 1024 - overhead) / 2)
384
+ );
385
+ const rows = clampDim(Math.sqrt(cells));
386
+ const columns = clampDim(cells / rows);
387
+ dataset.Rows = rows;
388
+ dataset.Columns = columns;
389
+ dataset.PixelData = new ArrayBuffer(rows * columns * 2);
390
+ } else if (spec.rows !== void 0 && spec.columns !== void 0) {
391
+ dataset.Rows = spec.rows;
392
+ dataset.Columns = spec.columns;
393
+ if (spec.frames !== void 0) dataset.NumberOfFrames = spec.frames;
394
+ dataset.PixelData = new ArrayBuffer(
395
+ spec.rows * spec.columns * (spec.frames ?? 1) * 2
396
+ );
397
+ }
362
398
  }
363
- function buildInvalidUidImageBuffer(uid, modality, tags, transferSyntax) {
364
- const invalidUid = {
399
+ function buildImageBuffer(spec, uid) {
400
+ const modality = spec.modality ?? "CT";
401
+ const effectiveUid = spec.type === "invalid-uid-image" ? {
365
402
  study: `2.25.invalid.study.${uid.study.slice(-6)}`,
366
403
  series: `2.25.invalid.series.${uid.series.slice(-6)}`,
367
404
  sop: `2.25.invalid.sop.${uid.sop.slice(-6)}`
368
- };
369
- const dataset = buildBaseImageDataset(invalidUid, modality);
370
- applyTagOverrides(dataset, tags);
371
- return serializeDict(buildMeta(invalidUid, modality, transferSyntax), dataset);
372
- }
373
- function buildVendorWarningsImageBuffer(uid, modality, tags, transferSyntax) {
374
- const dataset = buildBaseImageDataset(uid, modality);
375
- dataset.Laterality = "";
376
- dataset.PatientWeight = "0";
377
- applyTagOverrides(dataset, tags);
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);
405
+ } : uid;
406
+ const dataset = buildBaseImageDataset(effectiveUid, modality);
407
+ if (spec.type === "vendor-warnings-image") {
408
+ dataset.Laterality = "";
409
+ dataset.PatientWeight = "0";
410
+ }
411
+ applyTagOverrides(dataset, spec.tags);
412
+ const meta = buildMeta(effectiveUid, modality, spec.transferSyntax);
413
+ applySizing(dataset, meta, spec);
414
+ return serializeDict(meta, dataset);
388
415
  }
389
416
  function buildFakeSignatureBuffer() {
390
417
  const buf = Buffer.alloc(200, 0);
@@ -397,36 +424,9 @@ function buildNonDicomBuffer(content = "not dicom") {
397
424
  function buildBufferForSpec(spec, uid) {
398
425
  switch (spec.type) {
399
426
  case "valid-image":
400
- return buildValidImageBuffer(
401
- uid,
402
- spec.modality ?? "CT",
403
- spec.tags,
404
- spec.transferSyntax
405
- );
406
427
  case "invalid-uid-image":
407
- return buildInvalidUidImageBuffer(
408
- uid,
409
- spec.modality ?? "CT",
410
- spec.tags,
411
- spec.transferSyntax
412
- );
413
428
  case "vendor-warnings-image":
414
- return buildVendorWarningsImageBuffer(
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
- );
429
+ return buildImageBuffer(spec, uid);
430
430
  case "fake-signature":
431
431
  return buildFakeSignatureBuffer();
432
432
  case "non-dicom":
@@ -3,7 +3,6 @@ var TYPE_CAPABILITIES = {
3
3
  "valid-image": { image: true },
4
4
  "invalid-uid-image": { image: true },
5
5
  "vendor-warnings-image": { image: true },
6
- "large-image": { image: true },
7
6
  "fake-signature": { image: false },
8
7
  "non-dicom": { image: false },
9
8
  dicomdir: { image: false }
@@ -62,6 +61,10 @@ function validateEntry(entry, path) {
62
61
  if ("count" in e) validatePositiveInt(e.count, `${path}.count`);
63
62
  for (const field of [
64
63
  "modality",
64
+ "rows",
65
+ "columns",
66
+ "frames",
67
+ "targetSizeKb",
65
68
  "tags",
66
69
  "violations",
67
70
  "transferSyntax"
@@ -96,7 +99,21 @@ function validateEntry(entry, path) {
96
99
  }
97
100
  }
98
101
  }
99
- if (type === "large-image") {
102
+ const hasDims = "rows" in e || "columns" in e || "frames" in e;
103
+ if ("targetSizeKb" in e) {
104
+ if (hasDims) {
105
+ throw new Error(
106
+ `${path}: targetSizeKb cannot be combined with rows/columns/frames \u2014 use one sizing mechanism`
107
+ );
108
+ }
109
+ validatePositiveInt(e.targetSizeKb, `${path}.targetSizeKb`);
110
+ if (e.targetSizeKb * 1024 > MAX_PIXEL_BYTES) {
111
+ throw new Error(`${path}.targetSizeKb: exceeds the 512 MB limit`);
112
+ }
113
+ } else if (hasDims) {
114
+ if (!("rows" in e) || !("columns" in e)) {
115
+ throw new Error(`${path}: rows and columns must be provided together`);
116
+ }
100
117
  validatePositiveInt(e.rows, `${path}.rows`);
101
118
  validatePositiveInt(e.columns, `${path}.columns`);
102
119
  if ("frames" in e) validatePositiveInt(e.frames, `${path}.frames`);
@@ -153,36 +153,46 @@ function serializeDict(meta, dataset) {
153
153
  );
154
154
  return Buffer.from(dicomDict.write({ allowInvalidVRLength: true }));
155
155
  }
156
- function buildValidImageBuffer(uid, modality, tags, transferSyntax) {
157
- const dataset = buildBaseImageDataset(uid, modality);
158
- applyTagOverrides(dataset, tags);
159
- return serializeDict(buildMeta(uid, modality, transferSyntax), dataset);
156
+ var MAX_DIMENSION = 65535;
157
+ function applySizing(dataset, meta, spec) {
158
+ if (spec.targetSizeKb !== void 0) {
159
+ const clampDim = (n) => Math.min(MAX_DIMENSION, Math.max(1, Math.round(n)));
160
+ const minimalPixelBytes = dataset.PixelData.byteLength;
161
+ const overhead = serializeDict(meta, dataset).length - minimalPixelBytes;
162
+ const cells = Math.max(
163
+ 1,
164
+ Math.round((spec.targetSizeKb * 1024 - overhead) / 2)
165
+ );
166
+ const rows = clampDim(Math.sqrt(cells));
167
+ const columns = clampDim(cells / rows);
168
+ dataset.Rows = rows;
169
+ dataset.Columns = columns;
170
+ dataset.PixelData = new ArrayBuffer(rows * columns * 2);
171
+ } else if (spec.rows !== void 0 && spec.columns !== void 0) {
172
+ dataset.Rows = spec.rows;
173
+ dataset.Columns = spec.columns;
174
+ if (spec.frames !== void 0) dataset.NumberOfFrames = spec.frames;
175
+ dataset.PixelData = new ArrayBuffer(
176
+ spec.rows * spec.columns * (spec.frames ?? 1) * 2
177
+ );
178
+ }
160
179
  }
161
- function buildInvalidUidImageBuffer(uid, modality, tags, transferSyntax) {
162
- const invalidUid = {
180
+ function buildImageBuffer(spec, uid) {
181
+ const modality = spec.modality ?? "CT";
182
+ const effectiveUid = spec.type === "invalid-uid-image" ? {
163
183
  study: `2.25.invalid.study.${uid.study.slice(-6)}`,
164
184
  series: `2.25.invalid.series.${uid.series.slice(-6)}`,
165
185
  sop: `2.25.invalid.sop.${uid.sop.slice(-6)}`
166
- };
167
- const dataset = buildBaseImageDataset(invalidUid, modality);
168
- applyTagOverrides(dataset, tags);
169
- return serializeDict(buildMeta(invalidUid, modality, transferSyntax), dataset);
170
- }
171
- function buildVendorWarningsImageBuffer(uid, modality, tags, transferSyntax) {
172
- const dataset = buildBaseImageDataset(uid, modality);
173
- dataset.Laterality = "";
174
- dataset.PatientWeight = "0";
175
- applyTagOverrides(dataset, tags);
176
- return serializeDict(buildMeta(uid, modality, transferSyntax), dataset);
177
- }
178
- function buildLargeImageBuffer(uid, modality, rows, columns, frames, tags, transferSyntax) {
179
- const dataset = buildBaseImageDataset(uid, modality);
180
- dataset.Rows = rows;
181
- dataset.Columns = columns;
182
- dataset.NumberOfFrames = frames;
183
- dataset.PixelData = new Uint8Array(rows * columns * frames * 2).buffer;
184
- applyTagOverrides(dataset, tags);
185
- return serializeDict(buildMeta(uid, modality, transferSyntax), dataset);
186
+ } : uid;
187
+ const dataset = buildBaseImageDataset(effectiveUid, modality);
188
+ if (spec.type === "vendor-warnings-image") {
189
+ dataset.Laterality = "";
190
+ dataset.PatientWeight = "0";
191
+ }
192
+ applyTagOverrides(dataset, spec.tags);
193
+ const meta = buildMeta(effectiveUid, modality, spec.transferSyntax);
194
+ applySizing(dataset, meta, spec);
195
+ return serializeDict(meta, dataset);
186
196
  }
187
197
  function buildFakeSignatureBuffer() {
188
198
  const buf = Buffer.alloc(200, 0);
@@ -195,36 +205,9 @@ function buildNonDicomBuffer(content = "not dicom") {
195
205
  function buildBufferForSpec(spec, uid) {
196
206
  switch (spec.type) {
197
207
  case "valid-image":
198
- return buildValidImageBuffer(
199
- uid,
200
- spec.modality ?? "CT",
201
- spec.tags,
202
- spec.transferSyntax
203
- );
204
208
  case "invalid-uid-image":
205
- return buildInvalidUidImageBuffer(
206
- uid,
207
- spec.modality ?? "CT",
208
- spec.tags,
209
- spec.transferSyntax
210
- );
211
209
  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(
220
- uid,
221
- spec.modality ?? "CT",
222
- spec.rows,
223
- spec.columns,
224
- spec.frames ?? 1,
225
- spec.tags,
226
- spec.transferSyntax
227
- );
210
+ return buildImageBuffer(spec, uid);
228
211
  case "fake-signature":
229
212
  return buildFakeSignatureBuffer();
230
213
  case "non-dicom":
@@ -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 { DatasetLayout, DatasetSpec, DicomdirSpec, DicomTagOverrides, FakeSignatureSpec, FileSpec, ImageSpec, InvalidUidImageSpec, LargeImageSpec, Modality, NonDicomSpec, SeriesEntrySpec, SeriesSpec, StudySpec, TransferSyntax, ValidImageSpec, VendorWarningsImageSpec, ViolationClass, } from './schema/types.js';
4
+ export type { DatasetLayout, DatasetSpec, DicomdirSpec, DicomTagOverrides, FakeSignatureSpec, FileSpec, ImageSpec, InvalidUidImageSpec, Modality, NonDicomSpec, SeriesEntrySpec, SeriesSpec, StudySpec, TransferSyntax, ValidImageSpec, VendorWarningsImageSpec, ViolationClass, } from './schema/types.js';
5
5
  export { validateDatasetSpec } from './schema/validate.js';
@@ -10,6 +10,10 @@ export type ViolationClass = 'uid-too-long' | 'non-conformant-uid' | 'missing-me
10
10
  export type Modality = 'CT' | 'PT' | 'MR' | 'CR';
11
11
  type BaseImageSpec = {
12
12
  modality?: Modality;
13
+ rows?: number;
14
+ columns?: number;
15
+ frames?: number;
16
+ targetSizeKb?: number;
13
17
  tags?: DicomTagOverrides;
14
18
  transferSyntax?: TransferSyntax;
15
19
  violations?: ViolationClass[];
@@ -23,12 +27,6 @@ export type InvalidUidImageSpec = BaseImageSpec & {
23
27
  export type VendorWarningsImageSpec = BaseImageSpec & {
24
28
  type: 'vendor-warnings-image';
25
29
  };
26
- export type LargeImageSpec = BaseImageSpec & {
27
- type: 'large-image';
28
- rows: number;
29
- columns: number;
30
- frames?: number;
31
- };
32
30
  export type FakeSignatureSpec = {
33
31
  type: 'fake-signature';
34
32
  };
@@ -39,7 +37,7 @@ export type NonDicomSpec = {
39
37
  export type DicomdirSpec = {
40
38
  type: 'dicomdir';
41
39
  };
42
- export type ImageSpec = ValidImageSpec | InvalidUidImageSpec | VendorWarningsImageSpec | LargeImageSpec;
40
+ export type ImageSpec = ValidImageSpec | InvalidUidImageSpec | VendorWarningsImageSpec;
43
41
  export type FileSpec = ImageSpec | FakeSignatureSpec | NonDicomSpec | DicomdirSpec;
44
42
  export type EntrySpec = FileSpec & {
45
43
  count?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dicom-synth",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Toolkit for synthetic DICOM fixtures and public fixture fetch/cache.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -90,6 +90,6 @@
90
90
  "semantic-release": "^25.0.3",
91
91
  "tsx": "^4.19.3",
92
92
  "typescript": "^5.8.3",
93
- "vitest": "^3.2.4"
93
+ "vitest": "^3.2.6"
94
94
  }
95
95
  }