dicom-synth 1.7.0 → 1.9.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.
@@ -0,0 +1,356 @@
1
+ // src/syntheticFixtures/streamWrite.ts
2
+ import { closeSync, mkdirSync as mkdirSync2, openSync, writeSync } from "node:fs";
3
+ import { resolve as resolve2 } from "node:path";
4
+
5
+ // src/schema/limits.ts
6
+ var MAX_PIXEL_BYTES = 512 * 1024 * 1024;
7
+ var MAX_STREAM_BYTES = 50 * 1024 * 1024 * 1024;
8
+
9
+ // src/loadDcmjs.ts
10
+ import dcmjsDefaultImport, * as dcmjsNamespace from "dcmjs";
11
+ function loadDcmjs() {
12
+ if (dcmjsNamespace.data?.DicomDict) {
13
+ return dcmjsNamespace;
14
+ }
15
+ const d = dcmjsDefaultImport;
16
+ if (d?.data?.DicomDict) {
17
+ return d;
18
+ }
19
+ if (d?.default?.data?.DicomDict) {
20
+ return d.default;
21
+ }
22
+ throw new Error(
23
+ "dcmjs failed to load (missing data.DicomDict). Install dcmjs >= 0.29."
24
+ );
25
+ }
26
+ var dcmjs = loadDcmjs();
27
+
28
+ // src/nonStandardDicom/dicomdir.ts
29
+ import { mkdirSync, writeFileSync } from "node:fs";
30
+ import { resolve } from "node:path";
31
+
32
+ // src/schema/validate.ts
33
+ var HEX_TAG_RE = /^[0-9a-fA-F]{8}$/;
34
+
35
+ // src/syntheticFixtures/generator.ts
36
+ var MODALITY_PRESETS = {
37
+ CT: {
38
+ sopClassUid: "1.2.840.10008.5.1.4.1.1.2",
39
+ // CT Image Storage
40
+ attributes: {}
41
+ },
42
+ PT: {
43
+ sopClassUid: "1.2.840.10008.5.1.4.1.1.128",
44
+ // PET Image Storage
45
+ attributes: {
46
+ Units: "BQML",
47
+ DecayCorrection: "NONE",
48
+ CorrectedImage: ["ATTN", "DECY"]
49
+ }
50
+ },
51
+ MR: {
52
+ sopClassUid: "1.2.840.10008.5.1.4.1.1.4",
53
+ // MR Image Storage
54
+ attributes: {
55
+ ScanningSequence: "SE",
56
+ SequenceVariant: "NONE"
57
+ }
58
+ },
59
+ CR: {
60
+ sopClassUid: "1.2.840.10008.5.1.4.1.1.1",
61
+ // CR Image Storage
62
+ attributes: {}
63
+ }
64
+ };
65
+ var TRANSFER_SYNTAX_UID = {
66
+ "explicit-vr-little-endian": "1.2.840.10008.1.2.1",
67
+ "implicit-vr-little-endian": "1.2.840.10008.1.2"
68
+ };
69
+ function buildTagToKeyword() {
70
+ const map = /* @__PURE__ */ new Map();
71
+ const nm = dcmjs.data?.DicomMetaDictionary?.nameMap;
72
+ if (!nm) return map;
73
+ for (const [keyword, info] of Object.entries(nm)) {
74
+ if (info?.tag) {
75
+ const hex = info.tag.replace(/[(,)]/g, "").toUpperCase();
76
+ if (hex.length === 8) map.set(hex, keyword);
77
+ }
78
+ }
79
+ return map;
80
+ }
81
+ var tagToKeyword = buildTagToKeyword();
82
+ function applyTagOverrides(dataset, tags) {
83
+ if (!tags) return;
84
+ for (const [key, value] of Object.entries(tags)) {
85
+ if (HEX_TAG_RE.test(key)) {
86
+ const keyword = tagToKeyword.get(key.toUpperCase());
87
+ if (keyword) {
88
+ dataset[keyword] = value;
89
+ } else {
90
+ console.warn(`dicom-synth: unknown hex tag "${key}" \u2014 skipping`);
91
+ }
92
+ } else {
93
+ dataset[key] = value;
94
+ }
95
+ }
96
+ }
97
+ function buildMeta(uid, modality, transferSyntax = "explicit-vr-little-endian") {
98
+ return {
99
+ FileMetaInformationVersion: new Uint8Array([0, 1]).buffer,
100
+ MediaStorageSOPClassUID: MODALITY_PRESETS[modality].sopClassUid,
101
+ MediaStorageSOPInstanceUID: uid.sop,
102
+ TransferSyntaxUID: TRANSFER_SYNTAX_UID[transferSyntax],
103
+ ImplementationClassUID: "1.2.3.4",
104
+ ImplementationVersionName: "SYNTH"
105
+ };
106
+ }
107
+ function buildBaseImageDataset(uid, modality) {
108
+ const preset = MODALITY_PRESETS[modality];
109
+ return {
110
+ PatientName: "SYNTH^SUBJECT",
111
+ PatientID: "SYNTH_PID",
112
+ Modality: modality,
113
+ SOPClassUID: preset.sopClassUid,
114
+ SOPInstanceUID: uid.sop,
115
+ SeriesInstanceUID: uid.series,
116
+ StudyInstanceUID: uid.study,
117
+ StudyDescription: "Synthetic study",
118
+ SeriesDescription: "Synthetic series",
119
+ Rows: 1,
120
+ Columns: 1,
121
+ BitsAllocated: 16,
122
+ BitsStored: 16,
123
+ HighBit: 15,
124
+ SamplesPerPixel: 1,
125
+ PhotometricInterpretation: "MONOCHROME2",
126
+ PixelRepresentation: 0,
127
+ PixelData: new Uint8Array([0, 0]).buffer,
128
+ // Declares PixelData's VR explicitly — without it dcmjs logs
129
+ // "No value representation given for PixelData" and falls back to OW anyway.
130
+ _vrMap: { PixelData: "OW" },
131
+ ...preset.attributes
132
+ };
133
+ }
134
+ function serializeDict(meta, dataset) {
135
+ const dicomDict = new dcmjs.data.DicomDict(
136
+ dcmjs.data.DicomMetaDictionary.denaturalizeDataset(
137
+ meta
138
+ )
139
+ );
140
+ dicomDict.dict = dcmjs.data.DicomMetaDictionary.denaturalizeDataset(
141
+ dataset
142
+ );
143
+ return Buffer.from(dicomDict.write({ allowInvalidVRLength: true }));
144
+ }
145
+
146
+ // src/syntheticFixtures/uid.ts
147
+ import { randomBytes } from "node:crypto";
148
+ var ROLE_CODE = { study: 1, series: 2, sop: 3 };
149
+ function lcg(seed) {
150
+ let s = seed >>> 0;
151
+ return () => {
152
+ s = Math.imul(1664525, s) + 1013904223 >>> 0;
153
+ return s;
154
+ };
155
+ }
156
+ function formatUid(a, b, role) {
157
+ return `2.25.${a}.${b}.${ROLE_CODE[role]}`;
158
+ }
159
+ function randomUid(role) {
160
+ const buf = randomBytes(8);
161
+ return formatUid(buf.readUInt32BE(0), buf.readUInt32BE(4), role);
162
+ }
163
+ function seededUid(next, role) {
164
+ return formatUid(next(), next(), role);
165
+ }
166
+ function seededStream(seed, offset, a, b) {
167
+ return lcg(seed * 999983 + offset + a * 1000003 + b * 7919 >>> 0);
168
+ }
169
+ function makeUidGenerator(seed) {
170
+ if (seed === void 0) {
171
+ return () => ({
172
+ study: randomUid("study"),
173
+ series: randomUid("series"),
174
+ sop: randomUid("sop")
175
+ });
176
+ }
177
+ return (fileIndex) => {
178
+ const next = seededStream(seed, 0, fileIndex, 0);
179
+ return {
180
+ study: seededUid(next, "study"),
181
+ series: seededUid(next, "series"),
182
+ sop: seededUid(next, "sop")
183
+ };
184
+ };
185
+ }
186
+
187
+ // src/syntheticFixtures/streamWrite.ts
188
+ var OW_MAX_BYTES = 4294967294;
189
+ var FRAGMENT_MAX_BYTES = 4294967294;
190
+ var DEFAULT_CHUNK_BYTES = 8 * 1024 * 1024;
191
+ var RLE_TRANSFER_SYNTAX_UID = "1.2.840.10008.1.2.5";
192
+ var PIXEL_DATA_OW_HEADER = Buffer.from([224, 127, 16, 0, 79, 87]);
193
+ var PIXEL_DATA_OB_HEADER = Buffer.from([224, 127, 16, 0, 79, 66]);
194
+ var ITEM_TAG_GROUP = 65534;
195
+ var ITEM_TAG_ELEMENT = 57344;
196
+ var SEQUENCE_DELIMITER_ELEMENT = 57565;
197
+ function itemTag(element, byteLength = 0) {
198
+ const b = Buffer.alloc(8);
199
+ b.writeUInt16LE(ITEM_TAG_GROUP, 0);
200
+ b.writeUInt16LE(element, 2);
201
+ b.writeUInt32LE(byteLength, 4);
202
+ return b;
203
+ }
204
+ function nativeDimensions(pixelBytes) {
205
+ const cells = Math.max(1, Math.floor(pixelBytes / 2));
206
+ const rows = Math.min(65535, Math.max(1, Math.round(Math.sqrt(cells))));
207
+ const columns = Math.min(65535, Math.max(1, Math.floor(cells / rows)));
208
+ return { rows, columns, bytes: rows * columns * 2 };
209
+ }
210
+ function streamZeros(fd, totalBytes, zeroChunk) {
211
+ let remaining = totalBytes;
212
+ while (remaining > 0) {
213
+ const n = Math.min(zeroChunk.length, remaining);
214
+ writeSync(fd, zeroChunk, 0, n);
215
+ remaining -= n;
216
+ }
217
+ }
218
+ function writeNative(outPath, params, dims, zeroChunk) {
219
+ const { modality, uid, tags } = params;
220
+ const meta = buildMeta(uid, modality);
221
+ const dataset = buildBaseImageDataset(uid, modality);
222
+ applyTagOverrides(dataset, tags);
223
+ dataset.Rows = dims.rows;
224
+ dataset.Columns = dims.columns;
225
+ const minimalPixelBytes = dataset.PixelData.byteLength;
226
+ const probe = serializeDict(meta, dataset);
227
+ const pixelElement = Buffer.alloc(12);
228
+ PIXEL_DATA_OW_HEADER.copy(pixelElement);
229
+ pixelElement.writeUInt32LE(minimalPixelBytes, 8);
230
+ const owIdx = probe.indexOf(pixelElement);
231
+ if (owIdx < 0) {
232
+ throw new Error("failed to locate native PixelData element header");
233
+ }
234
+ const dataStart = owIdx + 12;
235
+ const header = Buffer.from(probe.subarray(0, dataStart));
236
+ header.writeUInt32LE(dims.bytes, owIdx + 8);
237
+ const trailing = probe.subarray(dataStart + minimalPixelBytes);
238
+ const fd = openSync(outPath, "w");
239
+ try {
240
+ writeSync(fd, header);
241
+ streamZeros(fd, dims.bytes, zeroChunk);
242
+ if (trailing.length > 0) writeSync(fd, trailing);
243
+ } finally {
244
+ closeSync(fd);
245
+ }
246
+ return header.length + dims.bytes + trailing.length;
247
+ }
248
+ function writeEncapsulated(outPath, params, pixelBytes, fragmentBytes, zeroChunk) {
249
+ const { modality, uid, tags } = params;
250
+ const meta = {
251
+ ...buildMeta(uid, modality),
252
+ TransferSyntaxUID: RLE_TRANSFER_SYNTAX_UID
253
+ };
254
+ const dataset = buildBaseImageDataset(uid, modality);
255
+ applyTagOverrides(dataset, tags);
256
+ dataset.PixelData = [new Uint8Array([]).buffer, new Uint8Array([0, 0]).buffer];
257
+ dataset._vrMap = { PixelData: "OB" };
258
+ const probe = serializeDict(meta, dataset);
259
+ const idx = probe.indexOf(PIXEL_DATA_OB_HEADER);
260
+ if (idx < 0 || probe.readUInt32LE(idx + 8) !== 4294967295) {
261
+ throw new Error("failed to locate encapsulated PixelData element header");
262
+ }
263
+ const prefix = Buffer.from(probe.subarray(0, idx + 12));
264
+ const fd = openSync(outPath, "w");
265
+ let bytesWritten = 0;
266
+ try {
267
+ writeSync(fd, prefix);
268
+ bytesWritten += prefix.length;
269
+ const bot = itemTag(ITEM_TAG_ELEMENT, 0);
270
+ writeSync(fd, bot);
271
+ bytesWritten += bot.length;
272
+ let remaining = pixelBytes;
273
+ while (remaining > 0) {
274
+ const fragLen = Math.min(fragmentBytes, remaining);
275
+ const fh = itemTag(ITEM_TAG_ELEMENT, fragLen);
276
+ writeSync(fd, fh);
277
+ streamZeros(fd, fragLen, zeroChunk);
278
+ bytesWritten += fh.length + fragLen;
279
+ remaining -= fragLen;
280
+ }
281
+ const delimiter = itemTag(SEQUENCE_DELIMITER_ELEMENT);
282
+ writeSync(fd, delimiter);
283
+ bytesWritten += delimiter.length;
284
+ } finally {
285
+ closeSync(fd);
286
+ }
287
+ return bytesWritten;
288
+ }
289
+ function streamLargeImage(outPath, options) {
290
+ const {
291
+ targetBytes,
292
+ chunkBytes = DEFAULT_CHUNK_BYTES,
293
+ owMaxBytes = OW_MAX_BYTES,
294
+ fragmentBytes = FRAGMENT_MAX_BYTES
295
+ } = options;
296
+ const modality = options.modality ?? "CT";
297
+ const uid = options.uid ?? makeUidGenerator(options.seed)(0);
298
+ const identity = { modality, uid, tags: options.tags };
299
+ mkdirSync2(resolve2(outPath, ".."), { recursive: true });
300
+ const zeroChunk = Buffer.alloc(Math.max(1, chunkBytes));
301
+ const probe = serializeDict(
302
+ buildMeta(uid, modality),
303
+ buildBaseImageDataset(uid, modality)
304
+ );
305
+ const nativeHeaderLen = probe.length - 2;
306
+ const requestedPixel = Math.max(2, targetBytes - nativeHeaderLen);
307
+ if (requestedPixel <= owMaxBytes) {
308
+ const dims = nativeDimensions(requestedPixel);
309
+ const bytesWritten2 = writeNative(outPath, identity, dims, zeroChunk);
310
+ return {
311
+ path: outPath,
312
+ bytesWritten: bytesWritten2,
313
+ encoding: "native",
314
+ pixelBytes: dims.bytes
315
+ };
316
+ }
317
+ const estimateFrags = Math.max(
318
+ 1,
319
+ Math.ceil((targetBytes - nativeHeaderLen - 16) / fragmentBytes)
320
+ );
321
+ const overhead = nativeHeaderLen + 8 + estimateFrags * 8 + 8;
322
+ let pixelBytes = Math.max(2, targetBytes - overhead);
323
+ pixelBytes -= pixelBytes % 2;
324
+ const bytesWritten = writeEncapsulated(
325
+ outPath,
326
+ identity,
327
+ pixelBytes,
328
+ fragmentBytes - fragmentBytes % 2,
329
+ zeroChunk
330
+ );
331
+ return { path: outPath, bytesWritten, encoding: "encapsulated", pixelBytes };
332
+ }
333
+ function writeLargeImageFile(spec, outPath, options = {}) {
334
+ const { targetBytes } = spec;
335
+ if (!Number.isInteger(targetBytes)) {
336
+ throw new Error("writeLargeImageFile: targetBytes must be an integer");
337
+ }
338
+ if (targetBytes <= MAX_PIXEL_BYTES) {
339
+ throw new Error(
340
+ "writeLargeImageFile is for files above the 512 MB in-memory limit \u2014 use targetSizeKb for smaller files"
341
+ );
342
+ }
343
+ if (targetBytes > MAX_STREAM_BYTES) {
344
+ throw new Error("writeLargeImageFile: targetBytes exceeds the 50 GB limit");
345
+ }
346
+ return streamLargeImage(outPath, {
347
+ targetBytes,
348
+ modality: spec.modality,
349
+ seed: spec.seed,
350
+ chunkBytes: options.chunkBytes
351
+ });
352
+ }
353
+ export {
354
+ streamLargeImage,
355
+ writeLargeImageFile
356
+ };
@@ -18,5 +18,13 @@ export declare function generateFile(spec: FileSpec, options?: {
18
18
  padWidth?: number;
19
19
  uid?: Partial<UidSet>;
20
20
  }): Promise<GeneratedFile>;
21
+ export declare function withResolvedSeed(spec: DatasetSpec): DatasetSpec;
21
22
  export declare function generateCollectionFromSpec(spec: DatasetSpec): AsyncGenerator<GeneratedFile>;
23
+ export type CollectionPreviewItem = {
24
+ relativePath: string;
25
+ type: FileSpec['type'];
26
+ index: number;
27
+ approxBytes: number;
28
+ };
29
+ export declare function previewCollection(spec: DatasetSpec): AsyncGenerator<CollectionPreviewItem>;
22
30
  export declare function writeCollectionFromSpec(spec: DatasetSpec, outDir: string): Promise<CollectionManifest>;
@@ -10,4 +10,5 @@ export type DescribeResult = {
10
10
  unknownModality: number;
11
11
  };
12
12
  };
13
+ export declare function readHeaderOnly(path: string): Record<string, unknown> | null;
13
14
  export declare function describeDirectory(dir: string, options?: DescribeOptions): DescribeResult;
@@ -1,7 +1,9 @@
1
- export { type CollectionManifest, type GeneratedFile, generateCollectionFromSpec, generateFile, writeCollectionFromSpec, } from './collection/writer.js';
1
+ export { type CollectionManifest, type CollectionPreviewItem, type GeneratedFile, generateCollectionFromSpec, generateFile, previewCollection, withResolvedSeed, writeCollectionFromSpec, } from './collection/writer.js';
2
2
  export { type DescribeOptions, type DescribeResult, describeDirectory, } from './describe/describe.js';
3
3
  export { defaultPublicCasesPath, loadCaseById, loadCasesFromJson, loadDefaultCases, type PublicCaseRecord, type PublicCaseSource, } from './public-fixtures/catalog.js';
4
4
  export { caseCachePath, fetchPublicCaseToCache, verifySha256, } from './public-fixtures/fetch.js';
5
+ export { MAX_PIXEL_BYTES, MAX_STREAM_BYTES } from './schema/limits.js';
5
6
  export { resolveParametricSpec } from './schema/parametric.js';
6
- export type { DatasetLayout, DatasetSpec, DicomdirSpec, DicomTagOverrides, FakeSignatureSpec, FileSpec, ImageSpec, InvalidUidImageSpec, Modality, NonDicomSpec, ParametricEdgeCase, ParametricSpec, ParametricStudies, PathQuirk, Range, SeriesEntrySpec, SeriesSpec, StudySpec, TransferSyntax, ValidImageSpec, VendorWarningsImageSpec, ViolationClass, } from './schema/types.js';
7
+ export type { DatasetLayout, DatasetSpec, DicomdirSpec, DicomTagOverrides, FakeSignatureSpec, FileSpec, ImageSpec, InvalidUidImageSpec, LargeFileSpec, Modality, NonDicomSpec, ParametricEdgeCase, ParametricSpec, ParametricStudies, PathQuirk, Range, SeriesEntrySpec, SeriesSpec, StudySpec, TransferSyntax, ValidImageSpec, VendorWarningsImageSpec, ViolationClass, } from './schema/types.js';
7
8
  export { validateDatasetSpec, validateParametricSpec, } from './schema/validate.js';
9
+ export { type LargeFileEncoding, type LargeFileResult, type LargeImageSpec, type StreamLargeImageOptions, streamLargeImage, writeLargeImageFile, } from './syntheticFixtures/streamWrite.js';
@@ -0,0 +1,2 @@
1
+ export declare const MAX_PIXEL_BYTES: number;
2
+ export declare const MAX_STREAM_BYTES: number;
@@ -37,12 +37,18 @@ export type NonDicomSpec = {
37
37
  export type DicomdirSpec = {
38
38
  type: 'dicomdir';
39
39
  };
40
+ export type LargeFileSpec = {
41
+ type: 'large-image';
42
+ targetBytes: number;
43
+ modality?: Modality;
44
+ tags?: DicomTagOverrides;
45
+ };
40
46
  export type ImageSpec = ValidImageSpec | InvalidUidImageSpec | VendorWarningsImageSpec;
41
- export type FileSpec = ImageSpec | FakeSignatureSpec | NonDicomSpec | DicomdirSpec;
47
+ export type FileSpec = ImageSpec | FakeSignatureSpec | NonDicomSpec | DicomdirSpec | LargeFileSpec;
42
48
  export type EntrySpec = FileSpec & {
43
49
  count?: number;
44
50
  };
45
- export type SeriesEntrySpec = ImageSpec & {
51
+ export type SeriesEntrySpec = (ImageSpec | LargeFileSpec) & {
46
52
  count?: number;
47
53
  };
48
54
  export type SeriesSpec = {
@@ -74,6 +80,8 @@ export type ParametricStudies = {
74
80
  fileSizeKb?: Range;
75
81
  modalities?: Modality[];
76
82
  tags?: DicomTagOverrides;
83
+ largeFilesPerSeries?: Range;
84
+ largeFileBytes?: Range;
77
85
  };
78
86
  export type ParametricEdgeCase = EntrySpec & {
79
87
  frequency?: number;
@@ -1,3 +1,7 @@
1
- import type { FileSpec } from '../schema/types.js';
1
+ import type { DicomTagOverrides, FileSpec, Modality, TransferSyntax } from '../schema/types.js';
2
2
  import type { UidSet } from './uid.js';
3
+ export declare function applyTagOverrides(dataset: Record<string, unknown>, tags: DicomTagOverrides | undefined): void;
4
+ export declare function buildMeta(uid: UidSet, modality: Modality, transferSyntax?: TransferSyntax): Record<string, unknown>;
5
+ export declare function buildBaseImageDataset(uid: UidSet, modality: Modality): Record<string, unknown>;
6
+ export declare function serializeDict(meta: Record<string, unknown>, dataset: Record<string, unknown>): Buffer;
3
7
  export declare function buildBufferForSpec(spec: FileSpec, uid: UidSet): Buffer;
@@ -0,0 +1,49 @@
1
+ import type { DicomTagOverrides, Modality } from '../schema/types.js';
2
+ import { type UidSet } from './uid.js';
3
+ export type LargeImageSpec = {
4
+ /** Approximate total file size in bytes (must exceed the 512 MB cap). */
5
+ targetBytes: number;
6
+ modality?: Modality;
7
+ /** Drawn when omitted; fixes the instance UIDs for reproducibility. */
8
+ seed?: number;
9
+ };
10
+ export type LargeFileEncoding = 'native' | 'encapsulated';
11
+ export type LargeFileResult = {
12
+ path: string;
13
+ bytesWritten: number;
14
+ encoding: LargeFileEncoding;
15
+ pixelBytes: number;
16
+ };
17
+ export type StreamLargeImageOptions = {
18
+ /** Approximate total file size in bytes (no floor — small values are valid). */
19
+ targetBytes: number;
20
+ modality?: Modality;
21
+ /** Drawn when omitted; fixes the instance UIDs for reproducibility. */
22
+ seed?: number;
23
+ /** Explicit UID set — used as-is (e.g. collection threading shared study/series UIDs). */
24
+ uid?: UidSet;
25
+ /** Tag overrides applied to the dataset (e.g. InstanceNumber, study/series tags). */
26
+ tags?: DicomTagOverrides;
27
+ chunkBytes?: number;
28
+ owMaxBytes?: number;
29
+ fragmentBytes?: number;
30
+ };
31
+ /**
32
+ * Low-level streaming writer: chooses the encoding from the requested size and
33
+ * the (overridable) ceilings, and applies **no policy bounds** — `targetBytes`
34
+ * may be tiny. This is the entry point for exercising the streamed path inline
35
+ * in tests/CI: override `owMaxBytes`/`fragmentBytes` to force either encoding at
36
+ * KB scale. Normal callers want {@link writeLargeImageFile}, which adds the
37
+ * 512 MB–50 GB policy bounds.
38
+ */
39
+ export declare function streamLargeImage(outPath: string, options: StreamLargeImageOptions): LargeFileResult;
40
+ /**
41
+ * Stream a single DICOM image of approximately `targetBytes` to disk without
42
+ * holding it in memory. Disk-only: the in-memory `generateFile` path keeps the
43
+ * 512 MB cap. Files up to ~4 GB use a conformant native (OW) encoding; larger
44
+ * files (to 50 GB) use encapsulated fragments — a valid container with
45
+ * non-conformant zero pixel content.
46
+ */
47
+ export declare function writeLargeImageFile(spec: LargeImageSpec, outPath: string, options?: {
48
+ chunkBytes?: number;
49
+ }): LargeFileResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dicom-synth",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "Toolkit for synthetic DICOM fixtures and public fixture fetch/cache.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",