dcmjs 0.25.0 → 0.26.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/test/data.test.js CHANGED
@@ -1,23 +1,28 @@
1
1
  import "regenerator-runtime/runtime.js";
2
2
 
3
- import { jest } from '@jest/globals'
4
- import dcmjs from '../src/index.js';
3
+ import { getZippedTestDataset, getTestDataset } from "./testUtils.js";
4
+ import { jest } from "@jest/globals";
5
+ import dcmjs from "../src/index.js";
6
+ import { WriteBufferStream } from "../src/BufferStream";
5
7
  import fs from "fs";
6
8
  import fsPromises from "fs/promises";
7
9
  import os from "os";
8
10
  import path from "path";
9
- import unzipper from "unzipper";
10
- import followRedirects from "follow-redirects";
11
-
12
- const { https } = followRedirects;
13
11
 
14
12
  import datasetWithNullNumberVRs from "./mocks/null_number_vrs_dataset.json";
15
13
  import minimalDataset from "./mocks/minimal_fields_dataset.json";
16
14
  import arrayItem from "./arrayItem.json";
17
15
  import { rawTags } from "./rawTags";
16
+ import { promisify } from "util";
18
17
 
19
- const { DicomMetaDictionary, DicomDict, DicomMessage, ReadBufferStream } = dcmjs.data;
18
+ const {
19
+ DicomMetaDictionary,
20
+ DicomDict,
21
+ DicomMessage,
22
+ ReadBufferStream
23
+ } = dcmjs.data;
20
24
 
25
+ const IMPLICIT_LITTLE_ENDIAN = "1.2.840.10008.1.2";
21
26
  const EXPLICIT_LITTLE_ENDIAN = "1.2.840.10008.1.2.1";
22
27
 
23
28
  const fileMetaInformationVersionArray = new Uint8Array(2);
@@ -50,7 +55,7 @@ const metadata = {
50
55
  };
51
56
 
52
57
  const sequenceMetadata = {
53
- "00080081": { "vr": "ST", "Value": [null] },
58
+ "00080081": { vr: "ST", Value: [null] },
54
59
  "00081032": {
55
60
  vr: "SQ",
56
61
  Value: [
@@ -71,7 +76,7 @@ const sequenceMetadata = {
71
76
  ]
72
77
  },
73
78
 
74
- "52009229": {
79
+ 52009229: {
75
80
  vr: "SQ",
76
81
  Value: [
77
82
  {
@@ -81,30 +86,16 @@ const sequenceMetadata = {
81
86
  {
82
87
  "00180088": {
83
88
  vr: "DS",
84
- Value: [0.12],
89
+ Value: [0.12]
85
90
  }
86
91
  }
87
- ],
88
- },
89
- },
90
- ],
91
- },
92
+ ]
93
+ }
94
+ }
95
+ ]
96
+ }
92
97
  };
93
98
 
94
- function downloadToFile(url, filePath) {
95
- return new Promise((resolve, reject) => {
96
- const fileStream = fs.createWriteStream(filePath);
97
- https
98
- .get(url, response => {
99
- response.pipe(fileStream);
100
- fileStream.on("finish", () => {
101
- resolve(filePath);
102
- });
103
- })
104
- .on("error", reject);
105
- });
106
- }
107
-
108
99
  function makeOverlayBitmap({ width, height }) {
109
100
  const topBottom = new Array(width).fill(1, 0, width);
110
101
  const middle = new Array(width).fill(0, 0, width);
@@ -174,19 +165,24 @@ it("test_json_1", () => {
174
165
  );
175
166
 
176
167
  // The match object needs to be done on the actual element, not the proxied value
177
- expect(naturalSequence.ProcedureCodeSequence[0]).toMatchObject({ CodeValue: "IMG1332" });
168
+ expect(naturalSequence.ProcedureCodeSequence[0]).toMatchObject({
169
+ CodeValue: "IMG1332"
170
+ });
178
171
 
179
172
  // tests that single element sequences have been converted
180
173
  // from arrays to values.
181
174
  // See discussion here for more details: https://github.com/dcmjs-org/dcmjs/commit/74571a4bd6c793af2a679a31cec7e197f93e28cc
182
- const spacing = naturalSequence.SharedFunctionalGroupsSequence
183
- .PixelMeasuresSequence.SpacingBetweenSlices;
175
+ const spacing =
176
+ naturalSequence.SharedFunctionalGroupsSequence.PixelMeasuresSequence
177
+ .SpacingBetweenSlices;
184
178
  expect(spacing).toEqual(0.12);
185
- expect(Array.isArray(naturalSequence.SharedFunctionalGroupsSequence)).toEqual(true);
179
+ expect(
180
+ Array.isArray(naturalSequence.SharedFunctionalGroupsSequence)
181
+ ).toEqual(true);
186
182
 
187
183
  expect(naturalSequence.ProcedureCodeSequence[0]).toMatchObject({
188
184
  CodingSchemeDesignator: "L",
189
- CodeMeaning: "MRI SHOULDER WITHOUT IV CONTRAST LEFT",
185
+ CodeMeaning: "MRI SHOULDER WITHOUT IV CONTRAST LEFT"
190
186
  });
191
187
 
192
188
  // expect original data to remain unnaturalized
@@ -210,31 +206,19 @@ it("test_json_1", () => {
210
206
  });
211
207
 
212
208
  it("test_multiframe_1", async () => {
209
+
213
210
  const url =
214
211
  "https://github.com/dcmjs-org/data/releases/download/MRHead/MRHead.zip";
215
- const zipFilePath = path.join(os.tmpdir(), "MRHead.zip");
216
- const unzipPath = path.join(os.tmpdir(), "test_multiframe_1");
217
-
218
- await downloadToFile(url, zipFilePath)
219
-
220
- await new Promise((resolve) => {
221
- fs.createReadStream(zipFilePath).pipe(unzipper.Extract({ path: unzipPath }).on("close", resolve))
222
- });
223
-
212
+ const unzipPath = await getZippedTestDataset(url, "MRHead.zip", "test_multiframe_1");
224
213
  const mrHeadPath = path.join(unzipPath, "MRHead");
225
214
  const fileNames = await fsPromises.readdir(mrHeadPath);
226
215
 
227
216
  const datasets = [];
228
217
  fileNames.forEach(fileName => {
229
- const arrayBuffer = fs.readFileSync(
230
- path.join(mrHeadPath, fileName)
231
- ).buffer;
232
- const dicomDict = DicomMessage.readFile(
233
- arrayBuffer
234
- );
235
- const dataset = DicomMetaDictionary.naturalizeDataset(
236
- dicomDict.dict
237
- );
218
+ const arrayBuffer = fs.readFileSync(path.join(mrHeadPath, fileName))
219
+ .buffer;
220
+ const dicomDict = DicomMessage.readFile(arrayBuffer);
221
+ const dataset = DicomMetaDictionary.naturalizeDataset(dicomDict.dict);
238
222
 
239
223
  datasets.push(dataset);
240
224
  });
@@ -243,8 +227,8 @@ it("test_multiframe_1", async () => {
243
227
  datasets
244
228
  );
245
229
  const spacing =
246
- multiframe.SharedFunctionalGroupsSequence
247
- .PixelMeasuresSequence.SpacingBetweenSlices;
230
+ multiframe.SharedFunctionalGroupsSequence.PixelMeasuresSequence
231
+ .SpacingBetweenSlices;
248
232
  const roundedSpacing = Math.round(100 * spacing) / 100;
249
233
 
250
234
  expect(multiframe.NumberOfFrames).toEqual(130);
@@ -256,16 +240,9 @@ it("test_oneslice_seg", async () => {
256
240
  "https://github.com/dcmjs-org/data/releases/download/CTPelvis/CTPelvis.zip";
257
241
  const segURL =
258
242
  "https://github.com/dcmjs-org/data/releases/download/CTPelvis/Lesion1_onesliceSEG.dcm";
259
- const zipFilePath = path.join(os.tmpdir(), "CTPelvis.zip");
260
- const unzipPath = path.join(os.tmpdir(), "test_oneslice_seg");
261
- const segFilePath = path.join(os.tmpdir(), "Lesion1_onesliceSEG.dcm");
262
-
263
- await downloadToFile(ctPelvisURL, zipFilePath)
264
-
265
- await new Promise((resolve) => {
266
- fs.createReadStream(zipFilePath).pipe(unzipper.Extract({ path: unzipPath }).on("close", resolve))
267
- });
268
-
243
+ const unzipPath = await getZippedTestDataset(ctPelvisURL, "CTPelvis.zip", "test_oneslice_seg");
244
+ const segFileName = "Lesion1_onesliceSEG.dcm"
245
+
269
246
  const ctPelvisPath = path.join(
270
247
  unzipPath,
271
248
  "Series-1.2.840.113704.1.111.1916.1223562191.15"
@@ -275,42 +252,28 @@ it("test_oneslice_seg", async () => {
275
252
 
276
253
  const datasets = [];
277
254
  fileNames.forEach(fileName => {
278
- const arrayBuffer = fs.readFileSync(
279
- path.join(ctPelvisPath, fileName)
280
- ).buffer;
281
- const dicomDict = DicomMessage.readFile(
282
- arrayBuffer
283
- );
284
- const dataset = DicomMetaDictionary.naturalizeDataset(
285
- dicomDict.dict
286
- );
255
+ const arrayBuffer = fs.readFileSync(path.join(ctPelvisPath, fileName))
256
+ .buffer;
257
+ const dicomDict = DicomMessage.readFile(arrayBuffer);
258
+ const dataset = DicomMetaDictionary.naturalizeDataset(dicomDict.dict);
287
259
  datasets.push(dataset);
288
260
  });
289
261
 
290
- let multiframe = dcmjs.normalizers.Normalizer.normalizeToDataset(
291
- datasets
292
- );
262
+ let multiframe = dcmjs.normalizers.Normalizer.normalizeToDataset(datasets);
293
263
  const spacing =
294
- multiframe.SharedFunctionalGroupsSequence
295
- .PixelMeasuresSequence.SpacingBetweenSlices;
264
+ multiframe.SharedFunctionalGroupsSequence.PixelMeasuresSequence
265
+ .SpacingBetweenSlices;
296
266
  const roundedSpacing = Math.round(100 * spacing) / 100;
297
267
 
298
268
  expect(multiframe.NumberOfFrames).toEqual(60);
299
269
  expect(roundedSpacing).toEqual(5);
300
270
 
301
- await downloadToFile(segURL, segFilePath)
302
- const arrayBuffer = fs.readFileSync(segFilePath)
303
- .buffer;
304
- const dicomDict = DicomMessage.readFile(
305
- arrayBuffer
306
- );
307
- const dataset = DicomMetaDictionary.naturalizeDataset(
308
- dicomDict.dict
309
- );
271
+ var segFilePath = await getTestDataset(segURL, segFileName);
272
+ const arrayBuffer = fs.readFileSync(segFilePath).buffer;
273
+ const dicomDict = DicomMessage.readFile(arrayBuffer);
274
+ const dataset = DicomMetaDictionary.naturalizeDataset(dicomDict.dict);
310
275
 
311
- multiframe = dcmjs.normalizers.Normalizer.normalizeToDataset(
312
- [dataset]
313
- );
276
+ multiframe = dcmjs.normalizers.Normalizer.normalizeToDataset([dataset]);
314
277
  expect(dataset.NumberOfFrames).toEqual(1);
315
278
  expect(multiframe.NumberOfFrames).toEqual(1);
316
279
  });
@@ -323,7 +286,7 @@ it("test_normalizer_smaller", () => {
323
286
  const rawTagsLen = JSON.stringify(rawTags).length;
324
287
  const naturalizedTagsLen = JSON.stringify(naturalizedTags).length;
325
288
  expect(naturalizedTagsLen).toBeLessThan(rawTagsLen);
326
- })
289
+ });
327
290
 
328
291
  it("test_multiframe_us", () => {
329
292
  const file = fs.readFileSync("test/cine-test.dcm");
@@ -343,9 +306,7 @@ it("test_multiframe_us", () => {
343
306
  it("test_fragment_multiframe", async () => {
344
307
  const url =
345
308
  "https://github.com/dcmjs-org/data/releases/download/encapsulation/encapsulation-fragment-multiframe.dcm";
346
- const dcmPath = path.join(os.tmpdir(), "encapsulation-fragment-multiframe.dcm");
347
-
348
- await downloadToFile(url, dcmPath)
309
+ const dcmPath = await getTestDataset(url, "encapsulation-fragment-multiframe.dcm")
349
310
  const file = fs.readFileSync(dcmPath);
350
311
  const dicomData = dcmjs.data.DicomMessage.readFile(file.buffer, {
351
312
  // ignoreErrors: true,
@@ -373,17 +334,17 @@ it("test_null_number_vrs", () => {
373
334
  dicomData.dict
374
335
  );
375
336
 
376
- expect(dataset.ImageAndFluoroscopyAreaDoseProduct).toEqual(0);
377
- expect(dataset.InstanceNumber).toEqual(0);
337
+ expect(dataset.ImageAndFluoroscopyAreaDoseProduct).toEqual(null);
338
+ expect(dataset.InstanceNumber).toEqual(null);
378
339
  });
379
340
 
380
341
  it("test_exponential_notation", () => {
381
- const file = fs.readFileSync('test/sample-dicom.dcm');
342
+ const file = fs.readFileSync("test/sample-dicom.dcm");
382
343
  const data = dcmjs.data.DicomMessage.readFile(file.buffer, {
383
344
  // ignoreErrors: true,
384
345
  });
385
346
  const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(data.dict);
386
- dataset.ImagePositionPatient[2] = 7.1945578383e-05;
347
+ dataset.ImagePositionPatient[2] = 7.1945578383e-5;
387
348
  const buffer = data.write();
388
349
  const copy = dcmjs.data.DicomMessage.readFile(buffer);
389
350
  expect(JSON.stringify(data)).toEqual(JSON.stringify(copy));
@@ -458,51 +419,24 @@ it("test_invalid_vr_length", () => {
458
419
  }
459
420
  });
460
421
 
461
- it("test_untiltag", () => {
462
- const buffer = fs.readFileSync('test/sample-dicom.dcm');
463
- console.time('readFile');
464
- const fullData = DicomMessage.readFile(buffer.buffer)
465
- console.timeEnd('readFile');
466
-
467
- console.time('readFile without untilTag');
468
- const dicomData = DicomMessage.readFile(buffer.buffer, { untilTag: '7FE00010', includeUntilTagValue: false });
469
- console.timeEnd('readFile without untilTag');
470
-
471
- console.time('readFile with untilTag');
472
- const dicomData2 = DicomMessage.readFile(buffer.buffer, { untilTag: '7FE00010', includeUntilTagValue: true });
473
- console.timeEnd('readFile with untilTag');
474
-
475
- const full_dataset = DicomMetaDictionary.naturalizeDataset(fullData.dict);
476
- full_dataset._meta = DicomMetaDictionary.namifyDataset(fullData.meta);
477
-
478
- const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
479
- dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
480
-
481
- const dataset2 = DicomMetaDictionary.naturalizeDataset(dicomData2.dict);
482
- dataset2._meta = DicomMetaDictionary.namifyDataset(dicomData2.meta);
483
-
484
- expect(full_dataset.PixelData).toEqual(dataset2.PixelData);
485
- expect(dataset.PixelData).toEqual(0);
486
- });
487
-
488
422
  it("test_encapsulation", async () => {
489
423
  const url =
490
424
  "https://github.com/dcmjs-org/data/releases/download/encapsulation/encapsulation.dcm";
491
- const dcmPath = path.join(os.tmpdir(), "encapsulation.dcm");
492
-
493
- await downloadToFile(url, dcmPath);
425
+ const dcmPath = await getTestDataset(url, "encapsulation.dcm")
494
426
 
495
427
  // given
496
428
  const arrayBuffer = fs.readFileSync(dcmPath).buffer;
497
429
  const dicomDict = DicomMessage.readFile(arrayBuffer);
498
430
 
499
- dicomDict.upsertTag('60000010', 'US', 30); // Overlay Rows
500
- dicomDict.upsertTag('60000011', 'US', 30); // Overlay Columns
501
- dicomDict.upsertTag('60000040', 'CS', 'G'); // Overlay Type
502
- dicomDict.upsertTag('60000045', 'LO', 'AUTOMATED'); // Overlay Subtype
503
- dicomDict.upsertTag('60000050', 'SS', [1 + 50, 1 + 50]); // Overlay Origin
431
+ dicomDict.upsertTag("60000010", "US", 30); // Overlay Rows
432
+ dicomDict.upsertTag("60000011", "US", 30); // Overlay Columns
433
+ dicomDict.upsertTag("60000040", "CS", "G"); // Overlay Type
434
+ dicomDict.upsertTag("60000045", "LO", "AUTOMATED"); // Overlay Subtype
435
+ dicomDict.upsertTag("60000050", "SS", [1 + 50, 1 + 50]); // Overlay Origin
504
436
 
505
- let overlay = dcmjs.data.BitArray.pack(makeOverlayBitmap({ width: 30, height: 30 }));
437
+ let overlay = dcmjs.data.BitArray.pack(
438
+ makeOverlayBitmap({ width: 30, height: 30 })
439
+ );
506
440
 
507
441
  if (overlay.length % 2 !== 0) {
508
442
  const newOverlay = new Uint8Array(overlay.length + 1);
@@ -513,43 +447,57 @@ it("test_encapsulation", async () => {
513
447
  overlay = newOverlay;
514
448
  }
515
449
 
516
- dicomDict.upsertTag('60003000', 'OB', [overlay.buffer]);
450
+ dicomDict.upsertTag("60003000", "OB", [overlay.buffer]);
517
451
 
518
452
  // when
519
453
  const lengths = [];
520
- const stream = new ReadBufferStream(dicomDict.write({ fragmentMultiframe: false })), useSyntax = EXPLICIT_LITTLE_ENDIAN;
454
+ const stream = new ReadBufferStream(
455
+ dicomDict.write({ fragmentMultiframe: false })
456
+ ),
457
+ useSyntax = EXPLICIT_LITTLE_ENDIAN;
521
458
 
522
459
  stream.reset();
523
460
  stream.increment(128);
524
461
 
525
- if (stream.readString(4) !== "DICM") {
462
+ if (stream.readAsciiString(4) !== "DICM") {
526
463
  throw new Error("Invalid a dicom file");
527
464
  }
528
465
 
529
- const el = DicomMessage.readTag(stream, useSyntax), metaLength = el.values[0]; //read header buffer
466
+ const el = DicomMessage._readTag(stream, useSyntax),
467
+ metaLength = el.values[0]; //read header buffer
530
468
  const metaStream = stream.more(metaLength);
531
- const metaHeader = DicomMessage.read(metaStream, useSyntax, false); //get the syntax
469
+ const metaHeader = DicomMessage._read(metaStream, useSyntax); //get the syntax
532
470
  let mainSyntax = metaHeader["00020010"].Value[0];
533
471
 
534
472
  mainSyntax = DicomMessage._normalizeSyntax(mainSyntax);
535
473
 
536
474
  while (!stream.end()) {
537
- const group = new Uint16Array(stream.buffer, stream.offset, 1)[0].toString(16).padStart(4, '0');
538
- const element = new Uint16Array(stream.buffer, stream.offset + 2, 1)[0].toString(16).padStart(4, '0');
539
-
540
- if (group.concat(element) === '60003000') { // Overlay Data
541
- const length = Buffer.from(new Uint8Array(stream.buffer, stream.offset + 8, 4)).readUInt32LE(0);
475
+ const group = new Uint16Array(stream.buffer, stream.offset, 1)[0]
476
+ .toString(16)
477
+ .padStart(4, "0");
478
+ const element = new Uint16Array(stream.buffer, stream.offset + 2, 1)[0]
479
+ .toString(16)
480
+ .padStart(4, "0");
481
+
482
+ if (group.concat(element) === "60003000") {
483
+ // Overlay Data
484
+ const length = Buffer.from(
485
+ new Uint8Array(stream.buffer, stream.offset + 8, 4)
486
+ ).readUInt32LE(0);
542
487
 
543
488
  lengths.push(length);
544
489
  }
545
490
 
546
- if (group.concat(element) === '7fe00010') { // Pixel Data
547
- const length = Buffer.from(new Uint8Array(stream.buffer, stream.offset + 8, 4)).readUInt32LE(0);
491
+ if (group.concat(element) === "7fe00010") {
492
+ // Pixel Data
493
+ const length = Buffer.from(
494
+ new Uint8Array(stream.buffer, stream.offset + 8, 4)
495
+ ).readUInt32LE(0);
548
496
 
549
497
  lengths.push(length);
550
498
  }
551
499
 
552
- DicomMessage.readTag(stream, mainSyntax, null, false);
500
+ DicomMessage._readTag(stream, mainSyntax);
553
501
  }
554
502
 
555
503
  // then
@@ -566,21 +514,207 @@ it("test_custom_dictionary", () => {
566
514
  name: "TrialName",
567
515
  vm: "1",
568
516
  version: "Custom"
569
- }
517
+ };
570
518
 
571
519
  const dicomMetaDictionary = new DicomMetaDictionary(customDictionary);
572
520
  const dicomDict = new DicomDict(metadata);
573
521
  minimalDataset["TrialName"] = "Test Trial";
574
- dicomDict.dict = dicomMetaDictionary.denaturalizeDataset(
575
- minimalDataset
576
- );
522
+ dicomDict.dict = dicomMetaDictionary.denaturalizeDataset(minimalDataset);
577
523
  const part10Buffer = dicomDict.write();
578
524
  const dicomData = DicomMessage.readFile(part10Buffer);
579
- const dataset = DicomMetaDictionary.naturalizeDataset(
580
- dicomData.dict
581
- );
525
+ const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
582
526
 
583
527
  expect(dataset.TrialName).toEqual("Test Trial");
584
528
  //check that all other fields were preserved, 15 original + 1 for _vr and +1 for "TrialName"
585
- expect(Object.keys(dataset).length).toEqual(17)
529
+ expect(Object.keys(dataset).length).toEqual(17);
530
+ });
531
+
532
+ it("Reads DICOM with multiplicity", async () => {
533
+ const url =
534
+ "https://github.com/dcmjs-org/data/releases/download/multiplicity/multiplicity.dcm";
535
+ const dcmPath = await getTestDataset(url, "multiplicity.dcm")
536
+ const file = await promisify(fs.readFile)(dcmPath);
537
+ const dicomDict = DicomMessage.readFile(file.buffer);
538
+
539
+ expect(dicomDict.dict["00101020"].Value).toEqual([1, 2]);
540
+ expect(dicomDict.dict["0018100B"].Value).toEqual(["1.2", "3.4"]);
541
+ });
542
+
543
+ it("Reads binary data into an ArrayBuffer", async () => {
544
+ const url =
545
+ "https://github.com/dcmjs-org/data/releases/download/binary-tag/binary-tag.dcm";
546
+ const dcmPath = await getTestDataset(url, "binary-tag.dcm")
547
+
548
+ const file = await promisify(fs.readFile)(dcmPath);
549
+ const dicomDict = DicomMessage.readFile(file.buffer);
550
+ const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
551
+ dicomDict.dict
552
+ );
553
+
554
+ expect(dataset.PixelData).toBeInstanceOf(Array);
555
+ expect(dataset.PixelData[0]).toBeInstanceOf(ArrayBuffer);
556
+ expect([...new Uint8Array(dataset.PixelData[0])]).toEqual([2, 3, 4, 5, 6]);
557
+ });
558
+
559
+ it("Reads a multiframe DICOM which has trailing padding", async () => {
560
+ const url =
561
+ "https://github.com/dcmjs-org/data/releases/download/binary-parsing-stressors/multiframe-ultrasound.dcm";
562
+ const dcmPath = await getTestDataset(url, "multiframe-ultrasound.dcm")
563
+ const dicomDict = DicomMessage.readFile(fs.readFileSync(dcmPath).buffer);
564
+ const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
565
+ dicomDict.dict
566
+ );
567
+
568
+ expect(dataset.PixelData.length).toEqual(29);
569
+ expect(dataset.PixelData[0]).toBeInstanceOf(ArrayBuffer);
570
+ expect(dataset.PixelData[0].byteLength).toEqual(104976);
571
+ expect(dataset.PixelData[1].byteLength).toEqual(104920);
572
+ expect(dataset.PixelData[27].byteLength).toEqual(103168);
573
+ expect(dataset.PixelData[28].byteLength).toEqual(103194);
574
+ });
575
+
576
+ it("Reads a multiframe DICOM with large private tags before and after the image data", async () => {
577
+ const url =
578
+ "https://github.com/dcmjs-org/data/releases/download/binary-parsing-stressors/large-private-tags.dcm";
579
+ const dcmPath = await getTestDataset(url, "large-private-tags.dcm")
580
+ const dicomDict = DicomMessage.readFile(fs.readFileSync(dcmPath).buffer);
581
+ const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
582
+ dicomDict.dict
583
+ );
584
+
585
+ expect(dataset.PixelData).toBeInstanceOf(Array);
586
+ expect(dataset.PixelData.length).toEqual(130);
587
+ expect(dataset.PixelData[0]).toBeInstanceOf(ArrayBuffer);
588
+ expect(dataset.PixelData[0].byteLength).toEqual(61518);
589
+ expect(dataset.PixelData[1].byteLength).toEqual(61482);
590
+ expect(dataset.PixelData[128].byteLength).toEqual(62144);
591
+ expect(dataset.PixelData[129].byteLength).toEqual(62148);
592
+ });
593
+
594
+ it("Writes encapsulated OB data which has an odd length with a padding byte in its last fragment", async () => {
595
+ const pixelData = [1, 2, 3];
596
+
597
+ const dataset = DicomMetaDictionary.denaturalizeDataset({
598
+ PixelData: [new Uint8Array(pixelData).buffer],
599
+ _vrMap: { PixelData: "OB" }
600
+ });
601
+
602
+ const stream = new WriteBufferStream(1024);
603
+ const bytesWritten = DicomMessage.write(
604
+ dataset,
605
+ stream,
606
+ "1.2.840.10008.1.2.4.50" // JPEG baseline (an encapsulated format)
607
+ );
608
+
609
+ expect(bytesWritten).toEqual(44);
610
+ expect([...new Uint32Array(stream.view.buffer, 0, 11)]).toEqual([
611
+ 0x00107fe0, // PixelData tag's group & element
612
+ 0x0000424f, // VR type "OB"
613
+ 0xffffffff, // Value length (0xffffffff here indicates an undefined length)
614
+ 0xe000fffe, // SequenceItemTag for the BOT (basic offset table)
615
+ 0x00000004, // Size in bytes of the BOT
616
+ 0x00000000, // First (and only) offset in the BOT
617
+ 0xe000fffe, // SequenceItemTag
618
+ 0x00000004, // SequenceItemTag's length in bytes
619
+ 0x00030201, // The actual data for this fragment (specified above), with padding
620
+ 0xe0ddfffe, // SequenceDelimiterTag
621
+ 0x00000000 // SequenceDelimiterTag value (always zero)
622
+ ]);
623
+ });
624
+
625
+ describe("With a SpecificCharacterSet tag", () => {
626
+ it("Reads a long string in the '' character set", async () => {
627
+ expect(readEncodedLongString("", [0x68, 0x69])).toEqual("hi");
628
+ });
629
+
630
+ it("Reads a long string in the ISO_IR 6 (default) character set", async () => {
631
+ expect(readEncodedLongString("ISO_IR 6", [0x68, 0x69])).toEqual("hi");
632
+ });
633
+
634
+ it("Reads a long string in the ISO_IR 13 (shift-jis) character set", async () => {
635
+ expect(readEncodedLongString("ISO_IR 13", [0x83, 0x8b])).toEqual("ル");
636
+ });
637
+
638
+ it("Reads a long string in the ISO_IR 166 (tis-620) character set", async () => {
639
+ expect(readEncodedLongString("ISO_IR 166", [0xb9, 0xf7])).toEqual("น๗");
640
+ });
641
+
642
+ it("Reads a long string in the ISO_IR 192 (utf-8) character set", async () => {
643
+ expect(readEncodedLongString("ISO_IR 192", [0xed, 0x95, 0x9c])).toEqual(
644
+ "한"
645
+ );
646
+ });
647
+
648
+ it("Throws an exception on an unsupported character set", async () => {
649
+ expect(() => readEncodedLongString("nope", [])).toThrow(
650
+ new Error("Unsupported character set: nope")
651
+ );
652
+ });
653
+
654
+ it("Doesn't throw an exception on an unsupported character set when ignoring errors", async () => {
655
+ expect(
656
+ readEncodedLongString("nope", [0x68, 0x69], { ignoreErrors: true })
657
+ ).toEqual("hi");
658
+ });
659
+
660
+ it("Throws an exception on multiple character sets", async () => {
661
+ expect(() =>
662
+ readEncodedLongString("ISO_IR 13\\ISO_IR 166", [])
663
+ ).toThrow(
664
+ /Using multiple character sets is not supported: ISO_IR 13,ISO_IR 166/
665
+ );
666
+ });
667
+
668
+ it("Doesn't throw an exception on multiple character sets when ignoring errors", async () => {
669
+ expect(
670
+ readEncodedLongString("ISO_IR 13\\ISO_IR 166", [0x68, 0x69], {
671
+ ignoreErrors: true
672
+ })
673
+ ).toEqual("hi");
674
+ });
675
+
676
+ function readEncodedLongString(
677
+ specificCharacterSet,
678
+ encodedBytes,
679
+ readOptions = { ignoreErrors: false }
680
+ ) {
681
+ // Pad to even lengths with spaces if needed
682
+ if (specificCharacterSet.length & 1) {
683
+ specificCharacterSet += " ";
684
+ }
685
+ if (encodedBytes.length & 1) {
686
+ encodedBytes.push(0x20);
687
+ }
688
+
689
+ // Manually construct the binary representation for the following two tags:
690
+ // - Tag #1: SpecificCharacterSet specifying the character set
691
+ // - Tag #2: InstitutionName which is a long string tag that will have its value
692
+ // set to the encoded bytes
693
+ const stream = new WriteBufferStream(
694
+ 16 + specificCharacterSet.length + encodedBytes.length
695
+ );
696
+ stream.isLittleEndian = true;
697
+
698
+ // Write SpecificCharacterSet tag
699
+ stream.writeUint32(0x00050008);
700
+ stream.writeUint32(specificCharacterSet.length);
701
+ stream.writeAsciiString(specificCharacterSet);
702
+
703
+ // Write InstitutionName tag
704
+ stream.writeUint32(0x00800008);
705
+ stream.writeUint32(encodedBytes.length);
706
+ for (const encodedByte of encodedBytes) {
707
+ stream.writeUint8(encodedByte);
708
+ }
709
+
710
+ // Read the stream back to get the value of the InstitutionName tag
711
+ const readResult = DicomMessage._read(
712
+ new ReadBufferStream(stream.buffer),
713
+ IMPLICIT_LITTLE_ENDIAN,
714
+ readOptions
715
+ );
716
+
717
+ // Return the resulting UTF-8 string value for InstitutionName
718
+ return readResult["00080080"].Value[0];
719
+ }
586
720
  });
@@ -1 +1 @@
1
- it("No tests yet", () => {})
1
+ it("No tests yet", () => {});
@@ -1 +1 @@
1
- it("No tests yet", () => {})
1
+ it("No tests yet", () => {});