dcmjs 0.49.3 → 0.50.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.
Files changed (55) hide show
  1. package/README.md +50 -0
  2. package/build/dcmjs.es.js +1071 -112
  3. package/build/dcmjs.es.js.map +1 -1
  4. package/build/dcmjs.js +1071 -112
  5. package/build/dcmjs.js.map +1 -1
  6. package/build/dcmjs.min.js +2 -2
  7. package/build/dcmjs.min.js.map +1 -1
  8. package/generate/dictionary.mjs +56029 -0
  9. package/package.json +18 -2
  10. package/.babelrc +0 -9
  11. package/.github/workflows/lint-and-format.yml +0 -27
  12. package/.github/workflows/publish-package.yml +0 -45
  13. package/.github/workflows/tests.yml +0 -24
  14. package/.prettierrc +0 -5
  15. package/.vscode/extensions.json +0 -7
  16. package/.vscode/settings.json +0 -8
  17. package/changelog.md +0 -31
  18. package/docs/ArrayBufferExpanderListener.md +0 -303
  19. package/docs/AsyncDicomReader-skill.md +0 -730
  20. package/eslint.config.mjs +0 -30
  21. package/generate-dictionary.js +0 -145
  22. package/jest.setup.js +0 -39
  23. package/netlify.toml +0 -22
  24. package/rollup.config.mjs +0 -57
  25. package/test/ArrayBufferExpanderListener.test.js +0 -365
  26. package/test/DICOMWEB.test.js +0 -1
  27. package/test/DicomMetaDictionary.test.js +0 -73
  28. package/test/SequenceOfItems.test.js +0 -86
  29. package/test/adapters.test.js +0 -43
  30. package/test/anonymizer.test.js +0 -176
  31. package/test/arrayItem.json +0 -351
  32. package/test/async-data.test.js +0 -575
  33. package/test/data-encoding.test.js +0 -59
  34. package/test/data-options.test.js +0 -199
  35. package/test/data.test.js +0 -1776
  36. package/test/derivations.test.js +0 -1
  37. package/test/helper/DicomDataReadBufferStreamBuilder.js +0 -89
  38. package/test/information-filter.test.js +0 -165
  39. package/test/integration/DicomMessage.readFile.test.js +0 -50
  40. package/test/lossless-read-write.test.js +0 -1407
  41. package/test/mocks/minimal_fields_dataset.json +0 -17
  42. package/test/mocks/null_number_vrs_dataset.json +0 -102
  43. package/test/normalizers.test.js +0 -38
  44. package/test/odd-frame-bit-data.js +0 -138
  45. package/test/rawTags.js +0 -170
  46. package/test/readBufferStream.test.js +0 -158
  47. package/test/sample-dicom.json +0 -904
  48. package/test/sample-op.lei +0 -0
  49. package/test/sample-sr.json +0 -997
  50. package/test/sr-tid.test.js +0 -251
  51. package/test/testUtils.js +0 -85
  52. package/test/utilities/deepEqual.test.js +0 -87
  53. package/test/utilities.test.js +0 -205
  54. package/test/video-test-dict.js +0 -40
  55. package/test/writeBufferStream.test.js +0 -149
@@ -1,730 +0,0 @@
1
- # AsyncDicomReader Skill Guide
2
-
3
- ## Overview
4
-
5
- The `AsyncDicomReader` is an asynchronous binary DICOM file reader that provides streaming capabilities for parsing DICOM files. It's designed to handle
6
- large DICOM files efficiently by reading and processing data incrementally rather than loading entire files into memory at once.
7
-
8
- ## Key Features
9
-
10
- - **Asynchronous/Streaming**: Reads DICOM files incrementally using async/await patterns
11
- - **Memory Efficient**: Uses buffer streaming with automatic clearing to reduce memory footprint
12
- - **Multiple Transfer Syntaxes**: Supports Explicit Little Endian, Explicit Big Endian, and Implicit Little Endian as well as compressed syntaxes.
13
- - **Pixel Data Handling**: Handles both compressed and uncompressed pixel data
14
- - **Sequence Support**: Properly parses DICOM sequences with defined and undefined lengths
15
- - **Character Set Support**: Automatically handles different character encodings, but not multiple character encodings.
16
- - **Error Handling**: Configurable error handling for malformed files
17
-
18
- ## Current Limitations
19
-
20
- - Files must contain the standard DICM preamble
21
- - Interface is preliminary and subject to change
22
-
23
- ## Basic Usage
24
-
25
- ### Reading a Complete DICOM File
26
-
27
- ```javascript
28
- import { AsyncDicomReader } from 'dcmjs';
29
-
30
- async function readDicomFile(arrayBuffer) {
31
- // Create reader instance
32
- const reader = new AsyncDicomReader();
33
-
34
- // Set the data source
35
- reader.stream.setData(arrayBuffer);
36
-
37
- // Read the entire file
38
- await reader.readFile();
39
-
40
- // Access the metadata and dataset
41
- console.log('Meta information:', reader.meta);
42
- console.log('Dataset:', reader.dict);
43
-
44
- return reader;
45
- }
46
- ```
47
-
48
- ### Reading with Custom Options
49
-
50
- ```javascript
51
- async function readDicomFileWithOptions(arrayBuffer) {
52
- const reader = new AsyncDicomReader({
53
- isLittleEndian: true,
54
- clearBuffers: true // Automatically clear consumed buffers
55
- });
56
-
57
- reader.stream.setData(arrayBuffer);
58
-
59
- // Read with error tolerance and custom meta size limit
60
- await reader.readFile({
61
- ignoreErrors: true,
62
- maxSizeMeta: 1024 * 20, // 20KB max for meta header
63
- listener: customListener // Optional custom listener
64
- });
65
-
66
- return reader;
67
- }
68
- ```
69
-
70
- ### Using Custom Listeners
71
-
72
- The reader uses a listener pattern to build the DICOM dataset. You can implement custom listeners to control how data is processed:
73
-
74
- ```javascript
75
- import { DicomMetadataListener } from 'dcmjs';
76
-
77
- class CustomListener extends DicomMetadataListener {
78
- addTag(tag, tagInfo) {
79
- console.log(`Processing tag: ${tag} (${tagInfo.name})`);
80
- super.addTag(tag, tagInfo);
81
- }
82
-
83
- value(val) {
84
- // Process each value as it's read
85
- super.value(val);
86
- }
87
- }
88
-
89
- async function readWithCustomListener(arrayBuffer) {
90
- const reader = new AsyncDicomReader();
91
- reader.stream.setData(arrayBuffer);
92
-
93
- await reader.readFile({
94
- listener: new CustomListener()
95
- });
96
-
97
- return reader;
98
- }
99
- ```
100
-
101
- ## Architecture
102
-
103
- ### Core Components
104
-
105
- 1. **BufferStream**: Manages the underlying data buffer with automatic memory management
106
- 2. **Listener Pattern**: Uses listeners to build the dataset incrementally
107
- 3. **Tag Reading**: Parses DICOM tags with proper VR (Value Representation) handling
108
- 4. **Transfer Syntax Detection**: Automatically detects and adapts to file transfer syntax
109
-
110
- ### Reading Process
111
-
112
- The reader follows this sequence:
113
-
114
- 1. **Preamble & Marker** (128 bytes + "DICM")
115
- 2. **File Meta Information** (Group 0x0002)
116
- 3. **Transfer Syntax Detection** (from meta info)
117
- 4. **Dataset Reading** (main DICOM data)
118
-
119
- ```
120
- ┌─────────────────┐
121
- │ 128B Preamble │
122
- ├─────────────────┤
123
- │ DICM Marker │
124
- ├─────────────────┤
125
- │ Meta Info │ (Group 0x0002, Explicit Little Endian)
126
- ├─────────────────┤
127
- │ Dataset │ (Uses detected Transfer Syntax)
128
- └─────────────────┘
129
- ```
130
-
131
- ## Advanced Usage
132
-
133
- ### Reading Specific Sections
134
-
135
- #### Reading Only Meta Information
136
-
137
- ```javascript
138
- async function readMetaOnly(arrayBuffer) {
139
- const reader = new AsyncDicomReader();
140
- reader.stream.setData(arrayBuffer);
141
-
142
- // Read preamble
143
- const hasPreamble = await reader.readPreamble();
144
- if (!hasPreamble) {
145
- throw new Error('No DICM marker found');
146
- }
147
-
148
- // Read only meta information
149
- const meta = await reader.readMeta();
150
-
151
- console.log('Transfer Syntax:', meta['00020010'].Value[0]);
152
- console.log('SOP Class UID:', meta['00020002'].Value[0]);
153
-
154
- return meta;
155
- }
156
- ```
157
-
158
- #### Reading Until Specific Tag
159
-
160
- ```javascript
161
- async function readUntilTag(arrayBuffer, targetTag) {
162
- const reader = new AsyncDicomReader();
163
- reader.stream.setData(arrayBuffer);
164
-
165
- await reader.readPreamble();
166
- await reader.readMeta();
167
-
168
- const listener = new DicomMetadataListener();
169
-
170
- await reader.read(listener, {
171
- untilTag: targetTag,
172
- includeUntilTagValue: false
173
- });
174
-
175
- return listener.pop();
176
- }
177
- ```
178
-
179
- ### Handling Pixel Data
180
-
181
- The reader automatically detects compressed vs uncompressed pixel data:
182
-
183
- #### Uncompressed Pixel Data
184
-
185
- ```javascript
186
- // Uncompressed pixel data is delivered as arrays
187
- // Each frame is ALWAYS delivered as an array (ArrayBuffer[]), even for single frames
188
- // Binary data can be fragmented - a single frame may be split across multiple ArrayBuffer fragments
189
- // Multiple fragments are combined into one array per frame
190
- // Large frames may be split into multiple fragments based on maxFragmentSize (default 128MB)
191
-
192
- class PixelDataListener extends DicomMetadataListener {
193
- constructor() {
194
- super();
195
- this.frames = [];
196
- }
197
-
198
- value(val) {
199
- if (val instanceof ArrayBuffer || ArrayBuffer.isView(val)) {
200
- this.frames.push(val);
201
- }
202
- super.value(val);
203
- }
204
- }
205
- ```
206
-
207
- #### Compressed Pixel Data
208
-
209
- ```javascript
210
- // Compressed data is read as encapsulated format
211
- // Each frame is ALWAYS delivered as an array (ArrayBuffer[]), even for single frames
212
- // Video transfer syntaxes are handled as though they were a single frame
213
- // Binary data can be delivered fragmented - a single frame may be split across multiple ArrayBuffer fragments
214
- // Multiple fragments are combined into one array per frame
215
- // Large frames may be split into multiple fragments based on maxFragmentSize (default 128MB)
216
-
217
- async function readCompressedImage(arrayBuffer) {
218
- const reader = new AsyncDicomReader();
219
- const listener = new PixelDataListener();
220
-
221
- reader.stream.setData(arrayBuffer);
222
- await reader.readFile({ listener });
223
-
224
- // listener.frames contains each compressed frame (each frame is an ArrayBuffer[])
225
- return listener.frames;
226
- }
227
- ```
228
-
229
- ### Working with Sequences
230
-
231
- Sequences are automatically parsed with proper nesting:
232
-
233
- ```javascript
234
- // Sequences are represented as arrays of objects
235
- // Example: Referenced Series Sequence
236
-
237
- async function readWithSequences(arrayBuffer) {
238
- const reader = new AsyncDicomReader();
239
- reader.stream.setData(arrayBuffer);
240
- await reader.readFile();
241
-
242
- // Access sequence items
243
- const referencedSeries = reader.dict['00081115']; // Referenced Series Seq
244
- if (referencedSeries) {
245
- referencedSeries.Value.forEach((item, index) => {
246
- console.log(`Series ${index}:`, item['0020000E']); // Series Instance UID
247
- });
248
- }
249
-
250
- return reader;
251
- }
252
- ```
253
-
254
- ## Performance Considerations
255
-
256
- ### Memory Management
257
-
258
- The reader uses automatic buffer clearing to minimize memory usage:
259
-
260
- ```javascript
261
- // Enable buffer clearing (default)
262
- const reader = new AsyncDicomReader({
263
- clearBuffers: true
264
- });
265
-
266
- // The stream automatically calls consume() to clear processed data
267
- // This prevents memory buildup during large file reads
268
- ```
269
-
270
- ### Streaming Large Files
271
-
272
- For very large files, the async nature prevents blocking:
273
-
274
- ```javascript
275
- async function streamLargeFile(fileHandle) {
276
- const reader = new AsyncDicomReader();
277
-
278
- // Read file in chunks
279
- const chunkSize = 1024 * 1024; // 1MB chunks
280
- let offset = 0;
281
- const chunks = [];
282
-
283
- while (true) {
284
- const { done, value } = await fileHandle.read(chunkSize);
285
- if (done) break;
286
-
287
- chunks.push(value);
288
- offset += value.byteLength;
289
- }
290
-
291
- // Combine chunks
292
- const fullBuffer = new Uint8Array(offset);
293
- let position = 0;
294
- for (const chunk of chunks) {
295
- fullBuffer.set(new Uint8Array(chunk), position);
296
- position += chunk.byteLength;
297
- }
298
-
299
- reader.stream.setData(fullBuffer.buffer);
300
- await reader.readFile();
301
-
302
- return reader;
303
- }
304
- ```
305
-
306
- ## Error Handling
307
-
308
- ### Handling Malformed Files
309
-
310
- ```javascript
311
- async function readWithErrorHandling(arrayBuffer) {
312
- const reader = new AsyncDicomReader();
313
- reader.stream.setData(arrayBuffer);
314
-
315
- try {
316
- await reader.readFile({
317
- ignoreErrors: true, // Continue reading despite errors
318
- maxSizeMeta: 1024 * 50 // Allow larger meta headers
319
- });
320
- } catch (error) {
321
- console.error('Failed to read DICOM file:', error);
322
-
323
- // Partial data may still be available
324
- if (reader.meta) {
325
- console.log('Meta information was read:', reader.meta);
326
- }
327
-
328
- throw error;
329
- }
330
-
331
- return reader;
332
- }
333
- ```
334
-
335
- ### Validation
336
-
337
- ```javascript
338
- async function validateDicomFile(arrayBuffer) {
339
- const reader = new AsyncDicomReader();
340
- reader.stream.setData(arrayBuffer);
341
-
342
- // Check for DICM marker
343
- const hasPreamble = await reader.readPreamble();
344
- if (!hasPreamble) {
345
- return {
346
- valid: false,
347
- error: 'Missing DICM preamble'
348
- };
349
- }
350
-
351
- // Validate meta information
352
- try {
353
- const meta = await reader.readMeta();
354
-
355
- if (!meta['00020010']) {
356
- return {
357
- valid: false,
358
- error: 'Missing Transfer Syntax UID'
359
- };
360
- }
361
-
362
- return {
363
- valid: true,
364
- transferSyntax: meta['00020010'].Value[0]
365
- };
366
- } catch (error) {
367
- return {
368
- valid: false,
369
- error: error.message
370
- };
371
- }
372
- }
373
- ```
374
-
375
- ## API Reference
376
-
377
- ### Constructor
378
-
379
- ```javascript
380
- new AsyncDicomReader(options)
381
- ```
382
-
383
- **Options:**
384
- - `isLittleEndian` (boolean): Force specific endianness
385
- - `clearBuffers` (boolean): Enable automatic buffer clearing (default: true)
386
- - Additional options passed to `ReadBufferStream`
387
-
388
- ### Methods
389
-
390
- #### `async readPreamble()`
391
- Reads the 128-byte preamble and DICM marker.
392
-
393
- **Returns:** `Promise<boolean>` - true if DICM marker found
394
-
395
- #### `async readFile(options)`
396
- Reads the entire DICOM file including meta information and dataset.
397
-
398
- **Options:**
399
- - `listener` (DicomMetadataListener): Custom listener for dataset building
400
- - `ignoreErrors` (boolean): Continue reading despite errors
401
- - `maxSizeMeta` (number): Maximum bytes to read for meta header
402
-
403
- **Returns:** `Promise<AsyncDicomReader>` - The reader instance
404
-
405
- #### `async readMeta(options)`
406
- Reads only the file meta information (Group 0x0002).
407
-
408
- **Options:**
409
- - `maxSizeMeta` (number): Maximum bytes for meta header (default: 10KB)
410
- - `ignoreErrors` (boolean): Allow reading files with malformed meta
411
-
412
- **Returns:** `Promise<Object>` - Meta information dictionary
413
-
414
- #### `async read(listener, options)`
415
- Reads the dataset portion using the provided listener.
416
-
417
- **Parameters:**
418
- - `listener` (DicomMetadataListener): Listener to build dataset
419
- - `options.untilOffset` (number): Stop reading at specific byte offset
420
-
421
- **Returns:** `Promise<Object>` - Parsed dataset
422
-
423
- #### `readTagHeader(options)`
424
- Reads a single tag header (tag, VR, length).
425
-
426
- **Options:**
427
- - `untilTag` (string): Stop when reaching this tag
428
- - `includeUntilTagValue` (boolean): Include the stop tag's value
429
-
430
- **Returns:** `Object` - Tag information with structure:
431
- ```javascript
432
- {
433
- tag: string, // Tag as hex string (e.g., "00100010")
434
- tagObj: Tag, // Tag object
435
- vr: string, // VR as string (e.g., "PN")
436
- vrObj: VR, // ValueRepresentation object
437
- length: number, // Value length (-1 for undefined)
438
- vm: string, // Value multiplicity
439
- name: string // Tag name from dictionary
440
- }
441
- ```
442
-
443
- ### Properties
444
-
445
- - `syntax` (string): Current transfer syntax UID
446
- - `meta` (Object): File meta information dictionary
447
- - `dict` (Object): Dataset dictionary
448
- - `stream` (ReadBufferStream): Underlying buffer stream
449
- - `listener` (DicomMetadataListener): Current listener instance
450
-
451
- ## Common Patterns
452
-
453
- ### Extract Specific Tags
454
-
455
- ```javascript
456
- async function extractTags(arrayBuffer, tagList) {
457
- const reader = new AsyncDicomReader();
458
- reader.stream.setData(arrayBuffer);
459
- await reader.readFile();
460
-
461
- const result = {};
462
- for (const tag of tagList) {
463
- if (reader.dict[tag]) {
464
- result[tag] = reader.dict[tag].Value;
465
- }
466
- }
467
-
468
- return result;
469
- }
470
-
471
- // Usage
472
- const tags = await extractTags(buffer, [
473
- '00100010', // Patient Name
474
- '00100020', // Patient ID
475
- '0020000D', // Study Instance UID
476
- ]);
477
- ```
478
-
479
- ### Convert to JSON
480
-
481
- ```javascript
482
- async function dicomToJson(arrayBuffer) {
483
- const reader = new AsyncDicomReader();
484
- reader.stream.setData(arrayBuffer);
485
- await reader.readFile();
486
-
487
- return {
488
- meta: reader.meta,
489
- dataset: reader.dict
490
- };
491
- }
492
- ```
493
-
494
- ### Read Multiple Files
495
-
496
- ```javascript
497
- async function readMultipleFiles(arrayBuffers) {
498
- const results = await Promise.all(
499
- arrayBuffers.map(async (buffer) => {
500
- const reader = new AsyncDicomReader();
501
- reader.stream.setData(buffer);
502
- await reader.readFile();
503
- return {
504
- meta: reader.meta,
505
- dataset: reader.dict
506
- };
507
- })
508
- );
509
-
510
- return results;
511
- }
512
- ```
513
-
514
- ## Bulkdata format
515
-
516
- The bulkdata format in JSON is
517
- ```
518
- "54001010": {
519
- "vr": "OW",
520
- "BulkDataURI": "./bulkdata/group1"
521
- }
522
- ```
523
-
524
- where the bulkdata uri can be an absolute or relative URI.
525
-
526
- The encoding of the bulkdata URI is multipart/related. It is recommended to gzip the entire bulkdata instance for storage.
527
-
528
- ## Sequence of listener calls
529
-
530
- The sequence of listener calls generated by `AsyncDicomReader` is:
531
-
532
- ```
533
- // Start of dict parsing (after fmi)
534
- listener.dict ||= {};
535
- listener.startObject(this.dict);
536
- // listener.current.level is 0 - top level object awaiting tags
537
-
538
- // For every tag:
539
- listener.addTag(tagHexString, tagInfo);
540
- // listener.current.level is 1 - top level attributing awaiting value
541
-
542
- if( isSequence ) {
543
- // For sequences, recursively:
544
- listener.startObject([]);
545
- // listener.current.level will be 2
546
- for(const child of sequence) {
547
- listener.startObject();
548
- // listener.current.level will be 3
549
- ... deliver child to listener
550
- listener.pop();
551
- }
552
- listener.pop();
553
- } else {
554
- // For each value in the tag:
555
- values.forEach(value => listener.value(value));
556
- // listener.current should record the value in some way
557
- }
558
-
559
- listener.pop();
560
- ```
561
-
562
- ## Writing new listeners
563
-
564
- Only the modified methods in the listener should be overwritten, as absent methods
565
- simply mean to call the next listener. Listeners should implement the `addTag(next,tag,tagInfo)` version
566
- of the methods and get added to the constructor of `DicomMetadataListener` or
567
- another root listener.
568
-
569
- ### Raw Binary Data Feature
570
-
571
- Starting with the `expectsRaw` feature, listeners can request raw binary data for specific tags by returning an object with `expectsRaw: true` from the `addTag` method. This is useful when you need to:
572
-
573
- - Process tag data in its raw binary form without parsing
574
- - Implement custom parsing logic for specific tags
575
- - Extract binary data for external processing
576
- - Optimize performance by skipping unnecessary parsing
577
-
578
- **How it works:**
579
-
580
- 1. The `addTag` method returns an object with `expectsRaw: true`
581
- 2. AsyncDicomReader delivers the raw binary data as `ArrayBuffer` chunks via `listener.value()`
582
- 3. Binary data can be delivered fragmented - a single tag's data may be split across multiple ArrayBuffer fragments
583
- 4. Multiple fragments are delivered sequentially via multiple `listener.value()` calls
584
- 5. Data is split into fragments based on `maxFragmentSize` (default 128MB) with buffer consumption between chunks for memory efficiency
585
- 6. This only works for non-pixel data tags with positive length
586
- 7. Pixel data and sequences continue to use their specialized handlers
587
-
588
- **Example:**
589
-
590
- ```javascript
591
- class RawBinaryListener extends DicomMetadataListener {
592
- constructor(tagsToReceiveRaw = []) {
593
- super();
594
- this.tagsToReceiveRaw = new Set(tagsToReceiveRaw);
595
- this.rawDataReceived = {};
596
- this.rawChunks = {};
597
- }
598
-
599
- addTag(tag, tagInfo) {
600
- // Call the parent implementation
601
- const result = super.addTag(tag, tagInfo);
602
-
603
- // Request raw binary data for specific tags
604
- if (this.tagsToReceiveRaw.has(tag)) {
605
- // Initialize chunk collector for this tag
606
- this.rawChunks[tag] = [];
607
- return { expectsRaw: true };
608
- }
609
-
610
- return result;
611
- }
612
-
613
- value(v) {
614
- // Track raw binary data received (may be delivered in multiple chunks)
615
- if (this.current && this.tagsToReceiveRaw.has(this.current.tag)) {
616
- if (v instanceof ArrayBuffer) {
617
- this.rawChunks[this.current.tag].push(v);
618
- }
619
- }
620
- return super.value(v);
621
- }
622
-
623
- pop() {
624
- // When tag is complete, combine chunks if needed
625
- if (this.current && this.tagsToReceiveRaw.has(this.current.tag)) {
626
- const tag = this.current.tag;
627
- const chunks = this.rawChunks[tag];
628
-
629
- if (chunks.length === 1) {
630
- this.rawDataReceived[tag] = chunks[0];
631
- } else if (chunks.length > 1) {
632
- // Combine multiple chunks into a single ArrayBuffer
633
- const totalSize = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
634
- const combined = new Uint8Array(totalSize);
635
- let offset = 0;
636
- for (const chunk of chunks) {
637
- combined.set(new Uint8Array(chunk), offset);
638
- offset += chunk.byteLength;
639
- }
640
- this.rawDataReceived[tag] = combined.buffer;
641
- }
642
-
643
- delete this.rawChunks[tag];
644
- }
645
- return super.pop();
646
- }
647
- }
648
-
649
- // Usage
650
- const listener = new RawBinaryListener([
651
- '00100010', // Patient Name
652
- '00080060', // Modality
653
- '00201041', // Slice Location
654
- ]);
655
-
656
- const reader = new AsyncDicomReader();
657
- reader.stream.setData(arrayBuffer);
658
- await reader.readFile({ listener });
659
-
660
- // Access raw binary data (now combined from all chunks)
661
- console.log(listener.rawDataReceived);
662
- ```
663
-
664
- **Limitations:**
665
- - Only works for non-pixel data tags (pixel data uses dedicated handlers)
666
- - Only works for tags with positive length (not undefined length)
667
- - The tag must have actual data present in the file
668
-
669
- ## Image Frames
670
-
671
- Image frames are numbered starting at 1, with the index in the pixel data
672
- tag starting at 0. **Each frame is ALWAYS delivered as an array (ArrayBuffer[]), even for single frames.**
673
-
674
- The value of both uncompressed and compressed image frames is one of:
675
-
676
- - BulkDataURI
677
- - ArrayBuffer[] containing the frame data (always an array, even for single frames)
678
-
679
- **Important behaviors:**
680
-
681
- - **Video transfer syntaxes**: Video is handled as though it were a single frame. All video frames are delivered as a single array containing all frame fragments.
682
- - **Fragmented binary data**: Binary data can be delivered fragmented. A single frame may be split across multiple ArrayBuffer fragments based on `maxFragmentSize` (default 128MB). Multiple fragments are combined into one array per frame.
683
- - **Frame delivery**: Each frame is delivered via `listener.startObject([])` followed by one or more `listener.value(fragment)` calls (one per fragment), then `listener.pop()`.
684
-
685
- ## Best Practices
686
-
687
- 2. **Use error handling** for production code - not all DICOM files are well-formed
688
- 3. **Implement custom listeners** storing data as bulkdata.
689
- 4. **Enable buffer clearing** (default) for memory efficiency
690
- 5. **Validate transfer syntax** before processing pixel data
691
- 6. **Use ignoreErrors cautiously** - it may skip important data
692
-
693
- ## Troubleshooting
694
-
695
- ### Issue: "Invalid DICOM file, meta length tag is malformed"
696
- **Solution:** Use `ignoreErrors: true` option or check if file has proper DICM preamble
697
-
698
- ### Issue: Out of memory errors
699
- **Solution:** Ensure `clearBuffers: true` and process pixel data incrementally with custom listener
700
-
701
- ### Issue: "Can't handle tag with -1 length and not sequence"
702
- **Solution:** File may have non-standard undefined length tags - this is invalid DICOM
703
-
704
- ### Issue: Incorrect character encoding
705
- **Solution:** Check Specific Character Set (0008,0005) tag and verify encoding mapping
706
-
707
- ## Related Components
708
-
709
- - **DicomMessage**: Synchronous DICOM reader for smaller files
710
- - **ReadBufferStream**: Underlying streaming buffer implementation
711
- - **ValueRepresentation**: VR parsing and value extraction
712
- - **DicomMetadataListener**: Default listener for building datasets
713
- - **Tag**: DICOM tag parsing and manipulation
714
-
715
- ## Future Enhancements
716
-
717
- The AsyncDicomReader is marked as preliminary. Future versions may include:
718
-
719
- - Compressed transfer syntax support (JPEG, JPEG-LS, RLE, JPEG 2000)
720
- - Files without DICM preamble
721
- - Improved streaming for network sources
722
- - Progress callbacks
723
- - Cancelable operations
724
- - More robust error recovery
725
-
726
- ## References
727
-
728
- - DICOM Standard PS3.10 (Media Storage and File Format)
729
- - DICOM Standard PS3.5 (Data Structures and Encoding)
730
- - [dcmjs Documentation](https://github.com/dcmjs-org/dcmjs)