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/.eslintrc.json +9 -9
- package/.prettierrc +2 -1
- package/.vscode/settings.json +6 -1
- package/README.md +4 -1
- package/build/dcmjs.es.js +2945 -797
- package/build/dcmjs.es.js.map +1 -1
- package/build/dcmjs.js +2949 -801
- package/build/dcmjs.js.map +1 -1
- package/package.json +8 -7
- package/test/DICOMWEB.test.js +1 -1
- package/test/anonymizer.test.js +82 -3
- package/test/data-encoding.test.js +54 -0
- package/test/data-options.test.js +200 -0
- package/test/data.test.js +289 -155
- package/test/derivations.test.js +1 -1
- package/test/normalizers.test.js +1 -1
- package/test/rawTags.js +166 -160
- package/test/sr.test.js +1 -1
- package/test/testUtils.js +63 -0
- package/test/utilities.test.js +45 -26
- package/build/dcmje.es.js +0 -14059
- package/build/dcmje.es.js.map +0 -1
- package/test/untilTag.test.js +0 -38
package/test/data.test.js
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
import "regenerator-runtime/runtime.js";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
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 {
|
|
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": {
|
|
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
|
-
|
|
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({
|
|
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 =
|
|
183
|
-
.PixelMeasuresSequence
|
|
175
|
+
const spacing =
|
|
176
|
+
naturalSequence.SharedFunctionalGroupsSequence.PixelMeasuresSequence
|
|
177
|
+
.SpacingBetweenSlices;
|
|
184
178
|
expect(spacing).toEqual(0.12);
|
|
185
|
-
expect(
|
|
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
|
|
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
|
-
|
|
231
|
-
)
|
|
232
|
-
const
|
|
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
|
-
.
|
|
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
|
|
260
|
-
const
|
|
261
|
-
|
|
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
|
-
|
|
280
|
-
)
|
|
281
|
-
const
|
|
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
|
-
.
|
|
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
|
|
302
|
-
const arrayBuffer = fs.readFileSync(segFilePath)
|
|
303
|
-
|
|
304
|
-
const
|
|
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 =
|
|
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(
|
|
377
|
-
expect(dataset.InstanceNumber).toEqual(
|
|
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(
|
|
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-
|
|
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 =
|
|
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(
|
|
500
|
-
dicomDict.upsertTag(
|
|
501
|
-
dicomDict.upsertTag(
|
|
502
|
-
dicomDict.upsertTag(
|
|
503
|
-
dicomDict.upsertTag(
|
|
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(
|
|
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(
|
|
450
|
+
dicomDict.upsertTag("60003000", "OB", [overlay.buffer]);
|
|
517
451
|
|
|
518
452
|
// when
|
|
519
453
|
const lengths = [];
|
|
520
|
-
const stream = new ReadBufferStream(
|
|
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.
|
|
462
|
+
if (stream.readAsciiString(4) !== "DICM") {
|
|
526
463
|
throw new Error("Invalid a dicom file");
|
|
527
464
|
}
|
|
528
465
|
|
|
529
|
-
const el = DicomMessage.
|
|
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.
|
|
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]
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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) ===
|
|
547
|
-
|
|
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.
|
|
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
|
});
|
package/test/derivations.test.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
it("No tests yet", () => {})
|
|
1
|
+
it("No tests yet", () => {});
|
package/test/normalizers.test.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
it("No tests yet", () => {})
|
|
1
|
+
it("No tests yet", () => {});
|