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.
- package/README.md +50 -0
- package/build/dcmjs.es.js +1071 -112
- package/build/dcmjs.es.js.map +1 -1
- package/build/dcmjs.js +1071 -112
- package/build/dcmjs.js.map +1 -1
- package/build/dcmjs.min.js +2 -2
- package/build/dcmjs.min.js.map +1 -1
- package/generate/dictionary.mjs +56029 -0
- package/package.json +18 -2
- package/.babelrc +0 -9
- package/.github/workflows/lint-and-format.yml +0 -27
- package/.github/workflows/publish-package.yml +0 -45
- package/.github/workflows/tests.yml +0 -24
- package/.prettierrc +0 -5
- package/.vscode/extensions.json +0 -7
- package/.vscode/settings.json +0 -8
- package/changelog.md +0 -31
- package/docs/ArrayBufferExpanderListener.md +0 -303
- package/docs/AsyncDicomReader-skill.md +0 -730
- package/eslint.config.mjs +0 -30
- package/generate-dictionary.js +0 -145
- package/jest.setup.js +0 -39
- package/netlify.toml +0 -22
- package/rollup.config.mjs +0 -57
- package/test/ArrayBufferExpanderListener.test.js +0 -365
- package/test/DICOMWEB.test.js +0 -1
- package/test/DicomMetaDictionary.test.js +0 -73
- package/test/SequenceOfItems.test.js +0 -86
- package/test/adapters.test.js +0 -43
- package/test/anonymizer.test.js +0 -176
- package/test/arrayItem.json +0 -351
- package/test/async-data.test.js +0 -575
- package/test/data-encoding.test.js +0 -59
- package/test/data-options.test.js +0 -199
- package/test/data.test.js +0 -1776
- package/test/derivations.test.js +0 -1
- package/test/helper/DicomDataReadBufferStreamBuilder.js +0 -89
- package/test/information-filter.test.js +0 -165
- package/test/integration/DicomMessage.readFile.test.js +0 -50
- package/test/lossless-read-write.test.js +0 -1407
- package/test/mocks/minimal_fields_dataset.json +0 -17
- package/test/mocks/null_number_vrs_dataset.json +0 -102
- package/test/normalizers.test.js +0 -38
- package/test/odd-frame-bit-data.js +0 -138
- package/test/rawTags.js +0 -170
- package/test/readBufferStream.test.js +0 -158
- package/test/sample-dicom.json +0 -904
- package/test/sample-op.lei +0 -0
- package/test/sample-sr.json +0 -997
- package/test/sr-tid.test.js +0 -251
- package/test/testUtils.js +0 -85
- package/test/utilities/deepEqual.test.js +0 -87
- package/test/utilities.test.js +0 -205
- package/test/video-test-dict.js +0 -40
- package/test/writeBufferStream.test.js +0 -149
|
@@ -1,1407 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import crypto from "crypto";
|
|
3
|
-
import dcmjs from "../src/index.js";
|
|
4
|
-
import { deepEqual } from "../src/utilities/deepEqual";
|
|
5
|
-
import {
|
|
6
|
-
DEFLATED_EXPLICIT_LITTLE_ENDIAN,
|
|
7
|
-
UNDEFINED_LENGTH,
|
|
8
|
-
TagHex
|
|
9
|
-
} from "../src/constants/dicom.js";
|
|
10
|
-
|
|
11
|
-
import { getTestDataset } from "./testUtils";
|
|
12
|
-
import { DicomMetaDictionary } from "../src/DicomMetaDictionary";
|
|
13
|
-
|
|
14
|
-
const { DicomDict, DicomMessage } = dcmjs.data;
|
|
15
|
-
|
|
16
|
-
// Implicit VR Little Endian - dataset uses tag (4) + length (4) + value
|
|
17
|
-
const IMPLICIT_LITTLE_ENDIAN_UID = "1.2.840.10008.1.2";
|
|
18
|
-
// VRs that use 32-bit length in Explicit VR (reserved 2 + length 4 after VR)
|
|
19
|
-
const EXPLICIT_VR_LENGTH32 = [
|
|
20
|
-
"OB",
|
|
21
|
-
"OW",
|
|
22
|
-
"OF",
|
|
23
|
-
"SQ",
|
|
24
|
-
"UN",
|
|
25
|
-
"UC",
|
|
26
|
-
"UR",
|
|
27
|
-
"UT",
|
|
28
|
-
"OD"
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Parses a raw DICOM buffer and returns the PixelData element's length and raw bytes.
|
|
33
|
-
* Reads up to PixelData, then reads the tag's length field (checking it's not -1) and value.
|
|
34
|
-
* Supports both Implicit and Explicit VR Little Endian for the dataset.
|
|
35
|
-
*
|
|
36
|
-
* @param {ArrayBuffer|Uint8Array} buffer - Raw DICOM file buffer
|
|
37
|
-
* @param {string} [transferSyntaxUID] - Optional. If provided, used to decide Implicit vs Explicit VR (e.g. from meta); avoids parsing meta.
|
|
38
|
-
* @returns {{ length: number, data: ArrayBuffer } | null} - PixelData length and bytes, or null if not found
|
|
39
|
-
*/
|
|
40
|
-
function readPixelDataFromRawBuffer(buffer, transferSyntaxUIDHint) {
|
|
41
|
-
// Ensure we have a contiguous ArrayBuffer (getBuffer may return view with shared buffer)
|
|
42
|
-
let arrayBuf;
|
|
43
|
-
if (buffer instanceof ArrayBuffer) {
|
|
44
|
-
arrayBuf = buffer;
|
|
45
|
-
} else {
|
|
46
|
-
const len = buffer.byteLength ?? buffer.length;
|
|
47
|
-
arrayBuf = buffer.buffer.slice(
|
|
48
|
-
buffer.byteOffset ?? 0,
|
|
49
|
-
(buffer.byteOffset ?? 0) + len
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
const view = new DataView(arrayBuf);
|
|
53
|
-
const isLittleEndian = true;
|
|
54
|
-
|
|
55
|
-
let offset = 132; // Skip preamble (128) + "DICM" (4)
|
|
56
|
-
if (offset + 4 > arrayBuf.byteLength) return null;
|
|
57
|
-
|
|
58
|
-
// Parse meta header - first element is FileMetaInformationGroupLength (Explicit VR)
|
|
59
|
-
offset += 4; // tag
|
|
60
|
-
offset += 2; // VR
|
|
61
|
-
offset += 2; // elem length (2 for UL)
|
|
62
|
-
const metaLength = view.getUint32(offset, isLittleEndian);
|
|
63
|
-
offset += 4;
|
|
64
|
-
const metaEnd = Math.min(offset + metaLength, arrayBuf.byteLength);
|
|
65
|
-
|
|
66
|
-
// Parse meta to find Transfer Syntax UID (0002,0010) for dataset VR format (unless hint provided)
|
|
67
|
-
let transferSyntaxUID = transferSyntaxUIDHint ?? IMPLICIT_LITTLE_ENDIAN_UID;
|
|
68
|
-
if (transferSyntaxUIDHint == null) {
|
|
69
|
-
while (offset < metaEnd && offset + 8 <= arrayBuf.byteLength) {
|
|
70
|
-
const group = view.getUint16(offset, isLittleEndian);
|
|
71
|
-
const element = view.getUint16(offset + 2, isLittleEndian);
|
|
72
|
-
offset += 4;
|
|
73
|
-
const vr = String.fromCharCode(
|
|
74
|
-
view.getUint8(offset),
|
|
75
|
-
view.getUint8(offset + 1)
|
|
76
|
-
);
|
|
77
|
-
offset += 2;
|
|
78
|
-
let elemLen;
|
|
79
|
-
if (EXPLICIT_VR_LENGTH32.includes(vr)) {
|
|
80
|
-
offset += 2; // reserved
|
|
81
|
-
elemLen = view.getUint32(offset, isLittleEndian);
|
|
82
|
-
offset += 4;
|
|
83
|
-
} else {
|
|
84
|
-
elemLen = view.getUint16(offset, isLittleEndian);
|
|
85
|
-
offset += 2;
|
|
86
|
-
}
|
|
87
|
-
if (offset + elemLen > arrayBuf.byteLength) break;
|
|
88
|
-
if (group === 0x0002 && element === 0x0010) {
|
|
89
|
-
// Transfer Syntax UID - value is ASCII
|
|
90
|
-
transferSyntaxUID = String.fromCharCode(
|
|
91
|
-
...new Uint8Array(arrayBuf, offset, elemLen)
|
|
92
|
-
)
|
|
93
|
-
.replace(/\0/g, "")
|
|
94
|
-
.trim();
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
offset += elemLen;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
offset = metaEnd;
|
|
101
|
-
|
|
102
|
-
// Detect Implicit vs Explicit from Transfer Syntax UID; fallback: Explicit if first dataset tag has 2-char VR (A-Z) after tag
|
|
103
|
-
let implicitDataset = transferSyntaxUID === IMPLICIT_LITTLE_ENDIAN_UID;
|
|
104
|
-
if (offset + 6 <= arrayBuf.byteLength) {
|
|
105
|
-
const b0 = view.getUint8(offset + 4);
|
|
106
|
-
const b1 = view.getUint8(offset + 5);
|
|
107
|
-
const looksLikeExplicitVR =
|
|
108
|
-
b0 >= 0x41 && b0 <= 0x5a && b1 >= 0x41 && b1 <= 0x5a;
|
|
109
|
-
if (looksLikeExplicitVR) implicitDataset = false;
|
|
110
|
-
}
|
|
111
|
-
const PIXEL_DATA_TAG = 0x7fe00010;
|
|
112
|
-
const SEQUENCE_ITEM_TAG = 0xfffee000;
|
|
113
|
-
const SEQUENCE_DELIMITER_TAG = 0xfffee0dd;
|
|
114
|
-
const ITEM_DELIMITATION_TAG = 0xfffee00d;
|
|
115
|
-
|
|
116
|
-
while (offset < arrayBuf.byteLength - 8) {
|
|
117
|
-
const group = view.getUint16(offset, isLittleEndian);
|
|
118
|
-
const element = view.getUint16(offset + 2, isLittleEndian);
|
|
119
|
-
const tagValue = (group << 16) | element;
|
|
120
|
-
offset += 4;
|
|
121
|
-
|
|
122
|
-
let length;
|
|
123
|
-
if (implicitDataset) {
|
|
124
|
-
length = view.getUint32(offset, isLittleEndian);
|
|
125
|
-
offset += 4;
|
|
126
|
-
} else {
|
|
127
|
-
const vr = String.fromCharCode(
|
|
128
|
-
view.getUint8(offset),
|
|
129
|
-
view.getUint8(offset + 1)
|
|
130
|
-
);
|
|
131
|
-
offset += 2;
|
|
132
|
-
if (EXPLICIT_VR_LENGTH32.includes(vr)) {
|
|
133
|
-
offset += 2; // reserved
|
|
134
|
-
length = view.getUint32(offset, isLittleEndian);
|
|
135
|
-
offset += 4;
|
|
136
|
-
} else {
|
|
137
|
-
length = view.getUint16(offset, isLittleEndian);
|
|
138
|
-
offset += 2;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (tagValue === PIXEL_DATA_TAG) {
|
|
143
|
-
if (length === UNDEFINED_LENGTH || length === -1) {
|
|
144
|
-
return { length: -1, data: null };
|
|
145
|
-
}
|
|
146
|
-
if (offset + length > arrayBuf.byteLength) return null;
|
|
147
|
-
const data = arrayBuf.slice(offset, offset + length);
|
|
148
|
-
return { length, data };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (length === UNDEFINED_LENGTH) {
|
|
152
|
-
// Skip undefined-length sequence (Item FFFE,E000 or Delimiter FFFE,E0DD)
|
|
153
|
-
while (offset < arrayBuf.byteLength - 8) {
|
|
154
|
-
const itemGroup = view.getUint16(offset, isLittleEndian);
|
|
155
|
-
const itemElement = view.getUint16(offset + 2, isLittleEndian);
|
|
156
|
-
const itemTagValue = (itemGroup << 16) | itemElement;
|
|
157
|
-
offset += 4;
|
|
158
|
-
const itemLength = view.getUint32(offset, isLittleEndian);
|
|
159
|
-
offset += 4;
|
|
160
|
-
if (itemTagValue === SEQUENCE_DELIMITER_TAG) break;
|
|
161
|
-
if (itemTagValue === SEQUENCE_ITEM_TAG) {
|
|
162
|
-
if (
|
|
163
|
-
itemLength === UNDEFINED_LENGTH ||
|
|
164
|
-
itemLength === 0xffffffff
|
|
165
|
-
) {
|
|
166
|
-
// Skip undefined-length item until Item Delimitation (FFFE,E00D); item uses same VR as dataset
|
|
167
|
-
let itemOffset = offset;
|
|
168
|
-
while (itemOffset < arrayBuf.byteLength - 8) {
|
|
169
|
-
const delGroup = view.getUint16(
|
|
170
|
-
itemOffset,
|
|
171
|
-
isLittleEndian
|
|
172
|
-
);
|
|
173
|
-
const delElement = view.getUint16(
|
|
174
|
-
itemOffset + 2,
|
|
175
|
-
isLittleEndian
|
|
176
|
-
);
|
|
177
|
-
const delTag = (delGroup << 16) | delElement;
|
|
178
|
-
itemOffset += 4;
|
|
179
|
-
let delLen;
|
|
180
|
-
if (implicitDataset) {
|
|
181
|
-
delLen = view.getUint32(
|
|
182
|
-
itemOffset,
|
|
183
|
-
isLittleEndian
|
|
184
|
-
);
|
|
185
|
-
itemOffset += 4;
|
|
186
|
-
} else {
|
|
187
|
-
const vr = String.fromCharCode(
|
|
188
|
-
view.getUint8(itemOffset),
|
|
189
|
-
view.getUint8(itemOffset + 1)
|
|
190
|
-
);
|
|
191
|
-
itemOffset += 2;
|
|
192
|
-
if (EXPLICIT_VR_LENGTH32.includes(vr)) {
|
|
193
|
-
itemOffset += 2;
|
|
194
|
-
delLen = view.getUint32(
|
|
195
|
-
itemOffset,
|
|
196
|
-
isLittleEndian
|
|
197
|
-
);
|
|
198
|
-
itemOffset += 4;
|
|
199
|
-
} else {
|
|
200
|
-
delLen = view.getUint16(
|
|
201
|
-
itemOffset,
|
|
202
|
-
isLittleEndian
|
|
203
|
-
);
|
|
204
|
-
itemOffset += 2;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if (delTag === ITEM_DELIMITATION_TAG) break;
|
|
208
|
-
itemOffset += delLen > 0 ? delLen : 0;
|
|
209
|
-
}
|
|
210
|
-
offset = itemOffset;
|
|
211
|
-
} else {
|
|
212
|
-
offset += itemLength;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
} else if (length > 0) {
|
|
217
|
-
offset += length;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function getPixelDataBytes(pixelDataValue) {
|
|
224
|
-
const chunks = [];
|
|
225
|
-
const flatten = arr => {
|
|
226
|
-
for (const item of arr) {
|
|
227
|
-
if (item instanceof ArrayBuffer) {
|
|
228
|
-
chunks.push(new Uint8Array(item));
|
|
229
|
-
} else if (Array.isArray(item)) {
|
|
230
|
-
flatten(item);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
flatten(pixelDataValue);
|
|
235
|
-
const totalLength = chunks.reduce((sum, c) => sum + c.byteLength, 0);
|
|
236
|
-
const result = new Uint8Array(totalLength);
|
|
237
|
-
let offset = 0;
|
|
238
|
-
for (const chunk of chunks) {
|
|
239
|
-
result.set(chunk, offset);
|
|
240
|
-
offset += chunk.byteLength;
|
|
241
|
-
}
|
|
242
|
-
return result.buffer;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function hashArrayBuffer(buffer) {
|
|
246
|
-
return crypto
|
|
247
|
-
.createHash("sha256")
|
|
248
|
-
.update(new Uint8Array(buffer))
|
|
249
|
-
.digest("hex");
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
describe("lossless-read-write", () => {
|
|
253
|
-
describe("storeRaw option", () => {
|
|
254
|
-
const dataset = {
|
|
255
|
-
"00080008": {
|
|
256
|
-
vr: "CS",
|
|
257
|
-
Value: ["DERIVED"]
|
|
258
|
-
},
|
|
259
|
-
"00082112": {
|
|
260
|
-
vr: "SQ",
|
|
261
|
-
Value: [
|
|
262
|
-
{
|
|
263
|
-
"00081150": {
|
|
264
|
-
vr: "UI",
|
|
265
|
-
Value: ["1.2.840.10008.5.1.4.1.1.7"]
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
]
|
|
269
|
-
},
|
|
270
|
-
"00180050": {
|
|
271
|
-
vr: "DS",
|
|
272
|
-
Value: [1]
|
|
273
|
-
},
|
|
274
|
-
"00181708": {
|
|
275
|
-
vr: "IS",
|
|
276
|
-
Value: [426]
|
|
277
|
-
},
|
|
278
|
-
"00189328": {
|
|
279
|
-
vr: "FD",
|
|
280
|
-
Value: [30.98]
|
|
281
|
-
},
|
|
282
|
-
"0020000D": {
|
|
283
|
-
vr: "UI",
|
|
284
|
-
Value: [
|
|
285
|
-
"1.3.6.1.4.1.5962.99.1.2280943358.716200484.1363785608958.3.0"
|
|
286
|
-
]
|
|
287
|
-
},
|
|
288
|
-
"00400254": {
|
|
289
|
-
vr: "LO",
|
|
290
|
-
Value: ["DUCTO/GALACTOGRAM 1 DUCT LT"]
|
|
291
|
-
},
|
|
292
|
-
"7FE00010": {
|
|
293
|
-
vr: "OW",
|
|
294
|
-
Value: [new Uint8Array([0x00, 0x00]).buffer]
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
test("storeRaw flag on VR should be respected by read", () => {
|
|
299
|
-
const tagsWithoutRaw = ["00082112", "7FE00010"];
|
|
300
|
-
|
|
301
|
-
const dicomDict = new DicomDict({});
|
|
302
|
-
dicomDict.dict = dataset;
|
|
303
|
-
|
|
304
|
-
// write and re-read
|
|
305
|
-
const outputDicomDict = DicomMessage.readFile(dicomDict.write());
|
|
306
|
-
for (const tag in outputDicomDict.dict) {
|
|
307
|
-
if (tagsWithoutRaw.includes(tag)) {
|
|
308
|
-
expect(outputDicomDict.dict[tag]._rawValue).toBeFalsy();
|
|
309
|
-
} else {
|
|
310
|
-
expect(outputDicomDict.dict[tag]._rawValue).toBeTruthy();
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
test("forceStoreRaw read option should override VR setting", () => {
|
|
316
|
-
const dicomDict = new DicomDict({});
|
|
317
|
-
dicomDict.dict = dataset;
|
|
318
|
-
|
|
319
|
-
// write and re-read
|
|
320
|
-
const outputDicomDict = DicomMessage.readFile(dicomDict.write(), {
|
|
321
|
-
forceStoreRaw: true
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
for (const tag in outputDicomDict.dict) {
|
|
325
|
-
expect(outputDicomDict.dict[tag]._rawValue).toBeTruthy();
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
test("test DS value with additional allowed characters is written to file", () => {
|
|
331
|
-
const dataset = {
|
|
332
|
-
"00181041": {
|
|
333
|
-
_rawValue: [" +1.4000 ", "-0.00", "1.2345e2", "1E34"],
|
|
334
|
-
Value: [1.4, -0, 123.45, 1e34],
|
|
335
|
-
vr: "DS"
|
|
336
|
-
}
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
const dicomDict = new DicomDict({});
|
|
340
|
-
dicomDict.dict = dataset;
|
|
341
|
-
|
|
342
|
-
// write and re-read
|
|
343
|
-
const outputDicomDict = DicomMessage.readFile(dicomDict.write());
|
|
344
|
-
|
|
345
|
-
// expect raw value to be unchanged, and Value parsed as Number to lose precision
|
|
346
|
-
expect(outputDicomDict.dict["00181041"]._rawValue).toEqual([
|
|
347
|
-
" +1.4000 ",
|
|
348
|
-
"-0.00",
|
|
349
|
-
"1.2345e2",
|
|
350
|
-
"1E34"
|
|
351
|
-
]);
|
|
352
|
-
expect(outputDicomDict.dict["00181041"].Value).toEqual([
|
|
353
|
-
1.4, -0, 123.45, 1e34
|
|
354
|
-
]);
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
test("test DS value that exceeds Number.MAX_SAFE_INTEGER is written to file", () => {
|
|
358
|
-
const dataset = {
|
|
359
|
-
"00181041": {
|
|
360
|
-
_rawValue: ["9007199254740993"],
|
|
361
|
-
Value: [9007199254740993],
|
|
362
|
-
vr: "DS"
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
const dicomDict = new DicomDict({});
|
|
367
|
-
dicomDict.dict = dataset;
|
|
368
|
-
|
|
369
|
-
// write and re-read
|
|
370
|
-
const outputDicomDict = DicomMessage.readFile(dicomDict.write());
|
|
371
|
-
|
|
372
|
-
// expect raw value to be unchanged, and Value parsed as Number to lose precision
|
|
373
|
-
expect(outputDicomDict.dict["00181041"]._rawValue).toEqual([
|
|
374
|
-
"9007199254740993"
|
|
375
|
-
]);
|
|
376
|
-
expect(outputDicomDict.dict["00181041"].Value).toEqual([
|
|
377
|
-
9007199254740992
|
|
378
|
-
]);
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
test("test DS with multiplicity > 1 and added space for even padding is read and written correctly", () => {
|
|
382
|
-
const dataset = {
|
|
383
|
-
"00200037": {
|
|
384
|
-
vr: "DS",
|
|
385
|
-
Value: [
|
|
386
|
-
0.99924236548978, -0.0322633220972, -0.0217663285287,
|
|
387
|
-
0.02949870928067, 0.99267261121054, -0.1171789789306
|
|
388
|
-
]
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
const dicomDict = new DicomDict({});
|
|
393
|
-
dicomDict.dict = dataset;
|
|
394
|
-
|
|
395
|
-
// write and re-read
|
|
396
|
-
const outputDicomDict = DicomMessage.readFile(dicomDict.write());
|
|
397
|
-
|
|
398
|
-
// ensure _rawValue strings have no added trailing spaces
|
|
399
|
-
const expectedDataset = {
|
|
400
|
-
"00200037": {
|
|
401
|
-
vr: "DS",
|
|
402
|
-
Value: [
|
|
403
|
-
0.99924236548978, -0.0322633220972, -0.0217663285287,
|
|
404
|
-
0.02949870928067, 0.99267261121054, -0.1171789789306
|
|
405
|
-
],
|
|
406
|
-
_rawValue: [
|
|
407
|
-
"0.99924236548978",
|
|
408
|
-
"-0.0322633220972",
|
|
409
|
-
"-0.0217663285287",
|
|
410
|
-
"0.02949870928067",
|
|
411
|
-
"0.99267261121054",
|
|
412
|
-
"-0.1171789789306"
|
|
413
|
-
]
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
expect(deepEqual(expectedDataset, outputDicomDict.dict)).toBeTruthy();
|
|
418
|
-
|
|
419
|
-
// re-write should succeeed
|
|
420
|
-
const outputDicomDictPass2 = DicomMessage.readFile(
|
|
421
|
-
outputDicomDict.write()
|
|
422
|
-
);
|
|
423
|
-
|
|
424
|
-
// dataset should still be equal
|
|
425
|
-
expect(
|
|
426
|
-
deepEqual(expectedDataset, outputDicomDictPass2.dict)
|
|
427
|
-
).toBeTruthy();
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
test("test DS with multiplicity > 1 with padding byte on last element within VR max length is losslessly read", () => {
|
|
431
|
-
const dataset = {
|
|
432
|
-
"00200037": {
|
|
433
|
-
vr: "DS",
|
|
434
|
-
Value: [
|
|
435
|
-
0.99924236548978, -0.0322633220972, -0.0217663285287, 0
|
|
436
|
-
],
|
|
437
|
-
_rawValue: [
|
|
438
|
-
"0.99924236548978",
|
|
439
|
-
"-0.0322633220972",
|
|
440
|
-
"-0.0217663285287",
|
|
441
|
-
" +0.00 "
|
|
442
|
-
]
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
const dicomDict = new DicomDict({});
|
|
447
|
-
dicomDict.dict = dataset;
|
|
448
|
-
|
|
449
|
-
// write and re-read
|
|
450
|
-
const outputDicomDict = DicomMessage.readFile(dicomDict.write());
|
|
451
|
-
|
|
452
|
-
// ensure _rawValue strings have no added trailing spaces and retain original encoding details for + and spaces
|
|
453
|
-
const expectedDataset = {
|
|
454
|
-
"00200037": {
|
|
455
|
-
vr: "DS",
|
|
456
|
-
Value: [
|
|
457
|
-
0.99924236548978, -0.0322633220972, -0.0217663285287, 0
|
|
458
|
-
],
|
|
459
|
-
_rawValue: [
|
|
460
|
-
"0.99924236548978",
|
|
461
|
-
"-0.0322633220972",
|
|
462
|
-
"-0.0217663285287",
|
|
463
|
-
" +0.00"
|
|
464
|
-
]
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
expect(outputDicomDict.dict).toEqual(expectedDataset);
|
|
469
|
-
|
|
470
|
-
// re-write should succeeed
|
|
471
|
-
const outputDicomDictPass2 = DicomMessage.readFile(
|
|
472
|
-
outputDicomDict.write()
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
// dataset should still be equal
|
|
476
|
-
expect(outputDicomDictPass2.dict).toEqual(expectedDataset);
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
test("test IS with multiplicity > 1 and added space for even padding is read and written correctly", () => {
|
|
480
|
-
const dataset = {
|
|
481
|
-
"00081160": {
|
|
482
|
-
vr: "IS",
|
|
483
|
-
Value: [1234, 5678]
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
|
-
|
|
487
|
-
const dicomDict = new DicomDict({});
|
|
488
|
-
dicomDict.dict = dataset;
|
|
489
|
-
|
|
490
|
-
// write and re-read
|
|
491
|
-
const outputDicomDict = DicomMessage.readFile(dicomDict.write());
|
|
492
|
-
|
|
493
|
-
// last _rawValue strings does allow trailing space as it does not exceed max length
|
|
494
|
-
const expectedDataset = {
|
|
495
|
-
"00081160": {
|
|
496
|
-
vr: "IS",
|
|
497
|
-
Value: [1234, 5678],
|
|
498
|
-
_rawValue: ["1234", "5678"]
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
expect(outputDicomDict.dict).toEqual(expectedDataset);
|
|
503
|
-
|
|
504
|
-
// re-write should succeeed
|
|
505
|
-
const outputDicomDictPass2 = DicomMessage.readFile(
|
|
506
|
-
outputDicomDict.write()
|
|
507
|
-
);
|
|
508
|
-
|
|
509
|
-
// dataset should still be equal
|
|
510
|
-
expect(outputDicomDictPass2.dict).toEqual(expectedDataset);
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
describe("Multiplicity for non-binary String VRs", () => {
|
|
514
|
-
const maxLengthCases = [
|
|
515
|
-
{
|
|
516
|
-
vr: "AE",
|
|
517
|
-
Value: ["MAX_LENGTH_CHARS", "MAX_LENGTH_CHARS"],
|
|
518
|
-
_rawValue: ["MAX_LENGTH_CHARS", "MAX_LENGTH_CHARS"]
|
|
519
|
-
},
|
|
520
|
-
{
|
|
521
|
-
vr: "AS",
|
|
522
|
-
Value: ["120D", "045Y"],
|
|
523
|
-
_rawValue: ["120D", "045Y"]
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
vr: "AT",
|
|
527
|
-
Value: [0x00207e14, 0x0012839a],
|
|
528
|
-
_rawValue: [0x00207e14, 0x0012839a]
|
|
529
|
-
},
|
|
530
|
-
{
|
|
531
|
-
vr: "CS",
|
|
532
|
-
Value: ["MAX_LENGTH_CHARS", "MAX_LENGTH_CHARS"],
|
|
533
|
-
_rawValue: ["MAX_LENGTH_CHARS", "MAX_LENGTH_CHARS"]
|
|
534
|
-
},
|
|
535
|
-
{
|
|
536
|
-
vr: "DA",
|
|
537
|
-
Value: ["20230826", "20230826"],
|
|
538
|
-
_rawValue: ["20230826", "20230826"]
|
|
539
|
-
},
|
|
540
|
-
{
|
|
541
|
-
vr: "DS",
|
|
542
|
-
Value: [123456789012.345, 123456789012.345],
|
|
543
|
-
_rawValue: ["123456789012.345", "123456789012.345"]
|
|
544
|
-
},
|
|
545
|
-
{
|
|
546
|
-
vr: "DT",
|
|
547
|
-
Value: [
|
|
548
|
-
"20230826123045.123456+0100",
|
|
549
|
-
"20230826123045.123456+0100"
|
|
550
|
-
],
|
|
551
|
-
_rawValue: [
|
|
552
|
-
"20230826123045.123456+0100",
|
|
553
|
-
"20230826123045.123456+0100"
|
|
554
|
-
]
|
|
555
|
-
},
|
|
556
|
-
{
|
|
557
|
-
vr: "IS",
|
|
558
|
-
Value: [123456789012, 123456789012],
|
|
559
|
-
_rawValue: ["123456789012", "123456789012"]
|
|
560
|
-
},
|
|
561
|
-
{
|
|
562
|
-
vr: "LO",
|
|
563
|
-
Value: [
|
|
564
|
-
"ABCDEFGHIJKLMNOPQRSTUVWXABCDEFGHIJKLMNOPQRSTUVWXABCDEFGHIJKLMNOP",
|
|
565
|
-
"ABCDEFGHIJKLMNOPQRSTUVWXABCDEFGHIJKLMNOPQRSTUVWXABCDEFGHIJKLMNOP"
|
|
566
|
-
],
|
|
567
|
-
_rawValue: [
|
|
568
|
-
"ABCDEFGHIJKLMNOPQRSTUVWXABCDEFGHIJKLMNOPQRSTUVWXABCDEFGHIJKLMNOP",
|
|
569
|
-
"ABCDEFGHIJKLMNOPQRSTUVWXABCDEFGHIJKLMNOPQRSTUVWXABCDEFGHIJKLMNOP"
|
|
570
|
-
]
|
|
571
|
-
},
|
|
572
|
-
{
|
|
573
|
-
vr: "SH",
|
|
574
|
-
Value: ["ABCDEFGHIJKLMNOP", "ABCDEFGHIJKLMNOP"],
|
|
575
|
-
_rawValue: ["ABCDEFGHIJKLMNOP", "ABCDEFGHIJKLMNOP"]
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
vr: "UI",
|
|
579
|
-
Value: [
|
|
580
|
-
"1.2.840.12345678901234567890123456789012345678901234567890123456",
|
|
581
|
-
"1.2.840.12345678901234567890123456789012345678901234567890123456"
|
|
582
|
-
],
|
|
583
|
-
_rawValue: [
|
|
584
|
-
"1.2.840.12345678901234567890123456789012345678901234567890123456",
|
|
585
|
-
"1.2.840.12345678901234567890123456789012345678901234567890123456"
|
|
586
|
-
]
|
|
587
|
-
},
|
|
588
|
-
{
|
|
589
|
-
vr: "TM",
|
|
590
|
-
Value: ["142530.1234567", "142530.1234567"],
|
|
591
|
-
_rawValue: ["142530.1234567", "142530.1234567"]
|
|
592
|
-
}
|
|
593
|
-
];
|
|
594
|
-
|
|
595
|
-
test.each(maxLengthCases)(
|
|
596
|
-
`Test multiple values with VR max length handle pad byte correctly during read and write - $vr`,
|
|
597
|
-
dataElement => {
|
|
598
|
-
const dataset = {
|
|
599
|
-
"00081160": {
|
|
600
|
-
vr: dataElement.vr,
|
|
601
|
-
Value: dataElement.Value
|
|
602
|
-
}
|
|
603
|
-
};
|
|
604
|
-
|
|
605
|
-
const dicomDict = new DicomDict({});
|
|
606
|
-
dicomDict.dict = dataset;
|
|
607
|
-
|
|
608
|
-
// write and re-read
|
|
609
|
-
const outputDicomDict = DicomMessage.readFile(
|
|
610
|
-
dicomDict.write()
|
|
611
|
-
);
|
|
612
|
-
|
|
613
|
-
// expect full _rawValue to match following read
|
|
614
|
-
const expectedDataset = {
|
|
615
|
-
"00081160": {
|
|
616
|
-
...dataElement
|
|
617
|
-
}
|
|
618
|
-
};
|
|
619
|
-
|
|
620
|
-
expect(outputDicomDict.dict).toEqual(expectedDataset);
|
|
621
|
-
|
|
622
|
-
// re-write should succeed without max length issues
|
|
623
|
-
const outputDicomDictPass2 = DicomMessage.readFile(
|
|
624
|
-
outputDicomDict.write()
|
|
625
|
-
);
|
|
626
|
-
|
|
627
|
-
// dataset should still be equal
|
|
628
|
-
expect(expectedDataset).toEqual(outputDicomDictPass2.dict);
|
|
629
|
-
}
|
|
630
|
-
);
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
describe("Individual VR comparisons", () => {
|
|
634
|
-
const unchangedTestCases = [
|
|
635
|
-
{
|
|
636
|
-
vr: "AE",
|
|
637
|
-
_rawValue: [" TEST_AE "], // spaces non-significant for interpretation but allowed
|
|
638
|
-
Value: ["TEST_AE"]
|
|
639
|
-
},
|
|
640
|
-
{
|
|
641
|
-
vr: "AS",
|
|
642
|
-
_rawValue: ["045Y"],
|
|
643
|
-
Value: ["045Y"]
|
|
644
|
-
},
|
|
645
|
-
{
|
|
646
|
-
vr: "AT",
|
|
647
|
-
_rawValue: [0x00207e14, 0x0012839a],
|
|
648
|
-
Value: [0x00207e14, 0x0012839a]
|
|
649
|
-
},
|
|
650
|
-
{
|
|
651
|
-
vr: "CS",
|
|
652
|
-
_rawValue: ["ORIGINAL ", " PRIMARY"], // spaces non-significant for interpretation but allowed
|
|
653
|
-
Value: ["ORIGINAL", "PRIMARY"]
|
|
654
|
-
},
|
|
655
|
-
{
|
|
656
|
-
vr: "DA",
|
|
657
|
-
_rawValue: ["20240101"],
|
|
658
|
-
Value: ["20240101"]
|
|
659
|
-
},
|
|
660
|
-
{
|
|
661
|
-
vr: "DS",
|
|
662
|
-
_rawValue: ["0000123.45"], // leading zeros allowed
|
|
663
|
-
Value: [123.45]
|
|
664
|
-
},
|
|
665
|
-
{
|
|
666
|
-
vr: "DT",
|
|
667
|
-
_rawValue: ["20240101123045.1 "], // trailing spaces allowed
|
|
668
|
-
Value: ["20240101123045.1 "]
|
|
669
|
-
},
|
|
670
|
-
{
|
|
671
|
-
vr: "FL",
|
|
672
|
-
_rawValue: [3.125],
|
|
673
|
-
Value: [3.125]
|
|
674
|
-
},
|
|
675
|
-
{
|
|
676
|
-
vr: "FD",
|
|
677
|
-
_rawValue: [3.14159265358979], // trailing spaces allowed
|
|
678
|
-
Value: [3.14159265358979]
|
|
679
|
-
},
|
|
680
|
-
{
|
|
681
|
-
vr: "IS",
|
|
682
|
-
_rawValue: [" -123 "], // leading/trailing spaces & sign allowed
|
|
683
|
-
Value: [-123]
|
|
684
|
-
},
|
|
685
|
-
{
|
|
686
|
-
vr: "LO",
|
|
687
|
-
_rawValue: [" A long string with spaces "], // leading/trailing spaces allowed
|
|
688
|
-
Value: ["A long string with spaces"]
|
|
689
|
-
},
|
|
690
|
-
{
|
|
691
|
-
vr: "LT",
|
|
692
|
-
_rawValue: [
|
|
693
|
-
" It may contain the Graphic Character set and the Control Characters, CR\r, LF\n, FF\f, and ESC\x1b. "
|
|
694
|
-
], // leading spaces significant, trailing spaces allowed
|
|
695
|
-
Value: [
|
|
696
|
-
" It may contain the Graphic Character set and the Control Characters, CR\r, LF\n, FF\f, and ESC\x1b."
|
|
697
|
-
]
|
|
698
|
-
},
|
|
699
|
-
{
|
|
700
|
-
vr: "OB",
|
|
701
|
-
_rawValue: [
|
|
702
|
-
new Uint8Array([
|
|
703
|
-
0x13, 0x40, 0x80, 0x88, 0x88, 0x90, 0x88, 0x88
|
|
704
|
-
]).buffer
|
|
705
|
-
],
|
|
706
|
-
Value: [
|
|
707
|
-
new Uint8Array([
|
|
708
|
-
0x13, 0x40, 0x80, 0x88, 0x88, 0x90, 0x88, 0x88
|
|
709
|
-
]).buffer
|
|
710
|
-
]
|
|
711
|
-
},
|
|
712
|
-
{
|
|
713
|
-
vr: "OD",
|
|
714
|
-
_rawValue: [
|
|
715
|
-
new Uint8Array([
|
|
716
|
-
0x00, 0x00, 0x00, 0x54, 0x34, 0x6f, 0x9d, 0x41
|
|
717
|
-
]).buffer
|
|
718
|
-
],
|
|
719
|
-
Value: [
|
|
720
|
-
new Uint8Array([
|
|
721
|
-
0x00, 0x00, 0x00, 0x54, 0x34, 0x6f, 0x9d, 0x41
|
|
722
|
-
]).buffer
|
|
723
|
-
]
|
|
724
|
-
},
|
|
725
|
-
{
|
|
726
|
-
vr: "OF",
|
|
727
|
-
_rawValue: [
|
|
728
|
-
new Uint8Array([
|
|
729
|
-
0x00, 0x00, 0x28, 0x41, 0x00, 0x00, 0x30, 0xc0, 0x00,
|
|
730
|
-
0x00, 0xf6, 0x42
|
|
731
|
-
]).buffer
|
|
732
|
-
],
|
|
733
|
-
Value: [
|
|
734
|
-
new Uint8Array([
|
|
735
|
-
0x00, 0x00, 0x28, 0x41, 0x00, 0x00, 0x30, 0xc0, 0x00,
|
|
736
|
-
0x00, 0xf6, 0x42
|
|
737
|
-
]).buffer
|
|
738
|
-
]
|
|
739
|
-
},
|
|
740
|
-
// TODO: VRs currently unimplemented
|
|
741
|
-
// {
|
|
742
|
-
// vr: 'OL',
|
|
743
|
-
// _rawValue: [new Uint8Array([0x00, 0x00, 0x30, 0xC0, 0x00, 0x00, 0x28, 0x41, 0x00, 0x00, 0xF6, 0x42, 0x00, 0x00, 0x28, 0x41]).buffer],
|
|
744
|
-
// Value: [new Uint8Array([0x00, 0x00, 0x30, 0xC0, 0x00, 0x00, 0x28, 0x41, 0x00, 0x00, 0xF6, 0x42, 0x00, 0x00, 0x28, 0x41]).buffer],
|
|
745
|
-
// },
|
|
746
|
-
// {
|
|
747
|
-
// vr: 'OV',
|
|
748
|
-
// _rawValue: [new Uint8Array([0x00, 0x00, 0x30, 0xC0, 0x00, 0x00, 0x28, 0x41]).buffer],
|
|
749
|
-
// Value: [new Uint8Array([0x00, 0x00, 0x30, 0xC0, 0x00, 0x00, 0x28, 0x41]).buffer],
|
|
750
|
-
// },
|
|
751
|
-
{
|
|
752
|
-
vr: "OW",
|
|
753
|
-
_rawValue: [
|
|
754
|
-
new Uint8Array([
|
|
755
|
-
0x13, 0x40, 0x80, 0x88, 0x88, 0x90, 0x88, 0x88
|
|
756
|
-
]).buffer
|
|
757
|
-
],
|
|
758
|
-
Value: [
|
|
759
|
-
new Uint8Array([
|
|
760
|
-
0x13, 0x40, 0x80, 0x88, 0x88, 0x90, 0x88, 0x88
|
|
761
|
-
]).buffer
|
|
762
|
-
]
|
|
763
|
-
},
|
|
764
|
-
{
|
|
765
|
-
vr: "PN",
|
|
766
|
-
_rawValue: "Doe^John^A^Jr.^MD ", // trailing spaces allowed
|
|
767
|
-
Value: [{ Alphabetic: "Doe^John^A^Jr.^MD " }]
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
vr: "SH",
|
|
771
|
-
_rawValue: [" CT_SCAN_01 "], // leading/trailing spaces allowed
|
|
772
|
-
Value: ["CT_SCAN_01"]
|
|
773
|
-
},
|
|
774
|
-
{
|
|
775
|
-
vr: "SL",
|
|
776
|
-
_rawValue: [-2147483648],
|
|
777
|
-
Value: [-2147483648]
|
|
778
|
-
},
|
|
779
|
-
{
|
|
780
|
-
vr: "SS",
|
|
781
|
-
_rawValue: [-32768, 1234, 832],
|
|
782
|
-
Value: [-32768, 1234, 832]
|
|
783
|
-
},
|
|
784
|
-
{
|
|
785
|
-
vr: "ST",
|
|
786
|
-
_rawValue: [
|
|
787
|
-
"Patient complains of headaches over the last week. "
|
|
788
|
-
], // trailing spaces allowed
|
|
789
|
-
Value: ["Patient complains of headaches over the last week."]
|
|
790
|
-
},
|
|
791
|
-
// TODO: VR currently unimplemented
|
|
792
|
-
// {
|
|
793
|
-
// vr: 'SV',
|
|
794
|
-
// _rawValue: [9007199254740993], // trailing spaces allowed
|
|
795
|
-
// Value: [9007199254740993],
|
|
796
|
-
// },
|
|
797
|
-
{
|
|
798
|
-
vr: "TM",
|
|
799
|
-
_rawValue: ["42530.123456 "], // trailing spaces allowed
|
|
800
|
-
Value: ["42530.123456"]
|
|
801
|
-
},
|
|
802
|
-
{
|
|
803
|
-
vr: "UC",
|
|
804
|
-
_rawValue: [
|
|
805
|
-
"Detailed description of procedure or clinical notes that could be very long. "
|
|
806
|
-
], // trailing spaces allowed
|
|
807
|
-
Value: [
|
|
808
|
-
"Detailed description of procedure or clinical notes that could be very long."
|
|
809
|
-
]
|
|
810
|
-
},
|
|
811
|
-
{
|
|
812
|
-
vr: "UI",
|
|
813
|
-
_rawValue: ["1.2.840.10008.1.2.1"],
|
|
814
|
-
Value: ["1.2.840.10008.1.2.1"]
|
|
815
|
-
},
|
|
816
|
-
{
|
|
817
|
-
vr: "UL",
|
|
818
|
-
_rawValue: [4294967295],
|
|
819
|
-
Value: [4294967295]
|
|
820
|
-
},
|
|
821
|
-
{
|
|
822
|
-
vr: "UR",
|
|
823
|
-
_rawValue: ["http://dicom.nema.org "], // trailing spaces ignored but allowed
|
|
824
|
-
Value: ["http://dicom.nema.org "]
|
|
825
|
-
},
|
|
826
|
-
{
|
|
827
|
-
vr: "US",
|
|
828
|
-
_rawValue: [65535],
|
|
829
|
-
Value: [65535]
|
|
830
|
-
},
|
|
831
|
-
{
|
|
832
|
-
vr: "UT",
|
|
833
|
-
_rawValue: [
|
|
834
|
-
" This is a detailed explanation that can span multiple lines and paragraphs in the DICOM dataset. "
|
|
835
|
-
], // leading spaces significant, trailing spaces allowed
|
|
836
|
-
Value: [
|
|
837
|
-
" This is a detailed explanation that can span multiple lines and paragraphs in the DICOM dataset."
|
|
838
|
-
]
|
|
839
|
-
}
|
|
840
|
-
// TODO: VR currently unimplemented
|
|
841
|
-
// {
|
|
842
|
-
// vr: 'UV',
|
|
843
|
-
// _rawValue: [18446744073709551616], // 2^64
|
|
844
|
-
// Value: [18446744073709551616],
|
|
845
|
-
// },
|
|
846
|
-
];
|
|
847
|
-
test.each(unchangedTestCases)(
|
|
848
|
-
`Test unchanged value is retained following read and write - $vr`,
|
|
849
|
-
dataElement => {
|
|
850
|
-
const dataset = {
|
|
851
|
-
"00181041": {
|
|
852
|
-
...dataElement
|
|
853
|
-
}
|
|
854
|
-
};
|
|
855
|
-
|
|
856
|
-
const dicomDict = new DicomDict({});
|
|
857
|
-
dicomDict.dict = dataset;
|
|
858
|
-
|
|
859
|
-
// write and re-read
|
|
860
|
-
const outputDicomDict = DicomMessage.readFile(
|
|
861
|
-
dicomDict.write(),
|
|
862
|
-
{ forceStoreRaw: true }
|
|
863
|
-
);
|
|
864
|
-
|
|
865
|
-
// expect raw value to be unchanged, and Value parsed as Number to lose precision
|
|
866
|
-
expect(outputDicomDict.dict["00181041"]._rawValue).toEqual(
|
|
867
|
-
dataElement._rawValue
|
|
868
|
-
);
|
|
869
|
-
expect(outputDicomDict.dict["00181041"].Value).toEqual(
|
|
870
|
-
dataElement.Value
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
);
|
|
874
|
-
|
|
875
|
-
const changedTestCases = [
|
|
876
|
-
{
|
|
877
|
-
vr: "AE",
|
|
878
|
-
_rawValue: [" TEST_AE "], // spaces non-significant for interpretation but allowed
|
|
879
|
-
Value: ["NEW_AE"]
|
|
880
|
-
},
|
|
881
|
-
{
|
|
882
|
-
vr: "AS",
|
|
883
|
-
_rawValue: ["045Y"],
|
|
884
|
-
Value: ["999Y"]
|
|
885
|
-
},
|
|
886
|
-
{
|
|
887
|
-
vr: "AT",
|
|
888
|
-
_rawValue: [0x00207e14, 0x0012839a],
|
|
889
|
-
Value: [0x00200010]
|
|
890
|
-
},
|
|
891
|
-
{
|
|
892
|
-
vr: "CS",
|
|
893
|
-
_rawValue: ["ORIGINAL ", " PRIMARY "], // spaces non-significant for interpretation but allowed
|
|
894
|
-
Value: ["ORIGINAL", "PRIMARY", "SECONDARY"]
|
|
895
|
-
},
|
|
896
|
-
{
|
|
897
|
-
vr: "DA",
|
|
898
|
-
_rawValue: ["20240101"],
|
|
899
|
-
Value: ["20231225"]
|
|
900
|
-
},
|
|
901
|
-
{
|
|
902
|
-
vr: "DS",
|
|
903
|
-
_rawValue: ["0000123.45"], // leading zeros allowed
|
|
904
|
-
Value: [123.456],
|
|
905
|
-
newRawValue: ["123.456 "]
|
|
906
|
-
},
|
|
907
|
-
{
|
|
908
|
-
vr: "DT",
|
|
909
|
-
_rawValue: ["20240101123045.1 "], // trailing spaces allowed
|
|
910
|
-
Value: ["20240101123045.3"]
|
|
911
|
-
},
|
|
912
|
-
{
|
|
913
|
-
vr: "FL",
|
|
914
|
-
_rawValue: [3.125],
|
|
915
|
-
Value: [22]
|
|
916
|
-
},
|
|
917
|
-
{
|
|
918
|
-
vr: "FD",
|
|
919
|
-
_rawValue: [3.14159265358979], // trailing spaces allowed
|
|
920
|
-
Value: [50.1242]
|
|
921
|
-
},
|
|
922
|
-
{
|
|
923
|
-
vr: "IS",
|
|
924
|
-
_rawValue: [" -123 "], // leading/trailing spaces & sign allowed
|
|
925
|
-
Value: [0],
|
|
926
|
-
newRawValue: ["0 "]
|
|
927
|
-
},
|
|
928
|
-
{
|
|
929
|
-
vr: "LO",
|
|
930
|
-
_rawValue: [" A long string with spaces "], // leading/trailing spaces allowed
|
|
931
|
-
Value: ["A changed string that is still long."]
|
|
932
|
-
},
|
|
933
|
-
{
|
|
934
|
-
vr: "LT",
|
|
935
|
-
_rawValue: [
|
|
936
|
-
" It may contain the Graphic Character set and the Control Characters, CR\r, LF\n, FF\f, and ESC\x1b. "
|
|
937
|
-
], // leading spaces significant, trailing spaces allowed
|
|
938
|
-
Value: [" A modified string of text"]
|
|
939
|
-
},
|
|
940
|
-
{
|
|
941
|
-
vr: "OB",
|
|
942
|
-
_rawValue: [
|
|
943
|
-
new Uint8Array([
|
|
944
|
-
0x13, 0x40, 0x80, 0x88, 0x88, 0x90, 0x88, 0x88
|
|
945
|
-
]).buffer
|
|
946
|
-
],
|
|
947
|
-
Value: [new Uint8Array([0x01, 0x02]).buffer]
|
|
948
|
-
},
|
|
949
|
-
{
|
|
950
|
-
vr: "OD",
|
|
951
|
-
_rawValue: [
|
|
952
|
-
new Uint8Array([
|
|
953
|
-
0x00, 0x00, 0x00, 0x54, 0x34, 0x6f, 0x9d, 0x41
|
|
954
|
-
]).buffer
|
|
955
|
-
],
|
|
956
|
-
Value: [
|
|
957
|
-
new Uint8Array([
|
|
958
|
-
0x00, 0x00, 0x00, 0x54, 0x35, 0x6e, 0x9e, 0x42
|
|
959
|
-
]).buffer
|
|
960
|
-
]
|
|
961
|
-
},
|
|
962
|
-
{
|
|
963
|
-
vr: "OF",
|
|
964
|
-
_rawValue: [
|
|
965
|
-
new Uint8Array([
|
|
966
|
-
0x00, 0x00, 0x28, 0x41, 0x00, 0x00, 0x30, 0xc0, 0x00,
|
|
967
|
-
0x00, 0xf6, 0x42
|
|
968
|
-
]).buffer
|
|
969
|
-
],
|
|
970
|
-
Value: [
|
|
971
|
-
new Uint8Array([
|
|
972
|
-
0x00, 0x00, 0x28, 0x41, 0x00, 0x00, 0x30, 0xc0, 0x00,
|
|
973
|
-
0x00, 0xf6, 0x43
|
|
974
|
-
]).buffer
|
|
975
|
-
]
|
|
976
|
-
},
|
|
977
|
-
// TODO: VRs currently unimplemented
|
|
978
|
-
// {
|
|
979
|
-
// vr: 'OL',
|
|
980
|
-
// _rawValue: [new Uint8Array([0x00, 0x00, 0x30, 0xC0, 0x00, 0x00, 0x28, 0x41, 0x00, 0x00, 0xF6, 0x42, 0x00, 0x00, 0x28, 0x41]).buffer],
|
|
981
|
-
// },
|
|
982
|
-
// {
|
|
983
|
-
// vr: 'OV',
|
|
984
|
-
// _rawValue: [new Uint8Array([0x00, 0x00, 0x30, 0xC0, 0x00, 0x00, 0x28, 0x41]).buffer],
|
|
985
|
-
// },
|
|
986
|
-
{
|
|
987
|
-
vr: "OW",
|
|
988
|
-
_rawValue: [
|
|
989
|
-
new Uint8Array([
|
|
990
|
-
0x13, 0x40, 0x80, 0x88, 0x89, 0x91, 0x89, 0x89
|
|
991
|
-
]).buffer
|
|
992
|
-
],
|
|
993
|
-
Value: [
|
|
994
|
-
new Uint8Array([
|
|
995
|
-
0x13, 0x40, 0x80, 0x88, 0x88, 0x90, 0x88, 0x88
|
|
996
|
-
]).buffer
|
|
997
|
-
]
|
|
998
|
-
},
|
|
999
|
-
{
|
|
1000
|
-
vr: "PN",
|
|
1001
|
-
_rawValue: "Doe^John^A^Jr.^MD ", // trailing spaces allowed
|
|
1002
|
-
Value: [{ Alphabetic: "Doe^Jane^A^Jr.^MD" }],
|
|
1003
|
-
newRawValue: "Doe^Jane^A^Jr.^MD"
|
|
1004
|
-
},
|
|
1005
|
-
{
|
|
1006
|
-
vr: "SH",
|
|
1007
|
-
_rawValue: [" CT_SCAN_01 "], // leading/trailing spaces allowed
|
|
1008
|
-
Value: ["MR_SCAN_91"]
|
|
1009
|
-
},
|
|
1010
|
-
{
|
|
1011
|
-
vr: "SL",
|
|
1012
|
-
_rawValue: [-2147483648],
|
|
1013
|
-
Value: [-2147481234]
|
|
1014
|
-
},
|
|
1015
|
-
{
|
|
1016
|
-
vr: "SS",
|
|
1017
|
-
_rawValue: [-32768, 1234, 832],
|
|
1018
|
-
Value: [1234]
|
|
1019
|
-
},
|
|
1020
|
-
{
|
|
1021
|
-
vr: "ST",
|
|
1022
|
-
_rawValue: [
|
|
1023
|
-
"Patient complains of headaches over the last week. "
|
|
1024
|
-
], // trailing spaces allowed
|
|
1025
|
-
Value: ["Patient complains of headaches"]
|
|
1026
|
-
},
|
|
1027
|
-
// TODO: VR currently unimplemented
|
|
1028
|
-
// {
|
|
1029
|
-
// vr: 'SV',
|
|
1030
|
-
// _rawValue: [9007199254740993], // trailing spaces allowed
|
|
1031
|
-
// },
|
|
1032
|
-
{
|
|
1033
|
-
vr: "TM",
|
|
1034
|
-
_rawValue: ["42530.123456 "], // trailing spaces allowed
|
|
1035
|
-
Value: ["42530"],
|
|
1036
|
-
newRawValue: ["42530 "]
|
|
1037
|
-
},
|
|
1038
|
-
{
|
|
1039
|
-
vr: "UC",
|
|
1040
|
-
_rawValue: [
|
|
1041
|
-
"Detailed description of procedure or clinical notes that could be very long. "
|
|
1042
|
-
], // trailing spaces allowed
|
|
1043
|
-
Value: ["Detailed description of procedure and other things"]
|
|
1044
|
-
},
|
|
1045
|
-
{
|
|
1046
|
-
vr: "UI",
|
|
1047
|
-
_rawValue: ["1.2.840.10008.1.2.1"],
|
|
1048
|
-
Value: ["1.2.840.10008.1.2.2"]
|
|
1049
|
-
},
|
|
1050
|
-
{
|
|
1051
|
-
vr: "UL",
|
|
1052
|
-
_rawValue: [4294967295],
|
|
1053
|
-
Value: [1]
|
|
1054
|
-
},
|
|
1055
|
-
{
|
|
1056
|
-
vr: "UR",
|
|
1057
|
-
_rawValue: ["http://dicom.nema.org "], // trailing spaces ignored but allowed
|
|
1058
|
-
Value: ["https://github.com/dcmjs-org"]
|
|
1059
|
-
},
|
|
1060
|
-
{
|
|
1061
|
-
vr: "US",
|
|
1062
|
-
_rawValue: [65535],
|
|
1063
|
-
Value: [1]
|
|
1064
|
-
},
|
|
1065
|
-
{
|
|
1066
|
-
vr: "UT",
|
|
1067
|
-
_rawValue: [
|
|
1068
|
-
" This is a detailed explanation that can span multiple lines and paragraphs in the DICOM dataset. "
|
|
1069
|
-
], // leading spaces significant, trailing spaces allowed
|
|
1070
|
-
Value: [""]
|
|
1071
|
-
}
|
|
1072
|
-
// TODO: VR currently unimplemented
|
|
1073
|
-
// {
|
|
1074
|
-
// vr: 'UV',
|
|
1075
|
-
// _rawValue: [18446744073709551616], // 2^64
|
|
1076
|
-
// },
|
|
1077
|
-
];
|
|
1078
|
-
|
|
1079
|
-
test.each(changedTestCases)(
|
|
1080
|
-
`Test changed value overwrites original value following read and write - $vr`,
|
|
1081
|
-
dataElement => {
|
|
1082
|
-
const dataset = {
|
|
1083
|
-
"00181041": {
|
|
1084
|
-
...dataElement
|
|
1085
|
-
}
|
|
1086
|
-
};
|
|
1087
|
-
|
|
1088
|
-
const dicomDict = new DicomDict({});
|
|
1089
|
-
dicomDict.dict = dataset;
|
|
1090
|
-
|
|
1091
|
-
// write and re-read
|
|
1092
|
-
const outputDicomDict = DicomMessage.readFile(
|
|
1093
|
-
dicomDict.write(),
|
|
1094
|
-
{ forceStoreRaw: true }
|
|
1095
|
-
);
|
|
1096
|
-
|
|
1097
|
-
// expect raw value to be updated to match new Value parsed as Number to lose precision
|
|
1098
|
-
expect(outputDicomDict.dict["00181041"]._rawValue).toEqual(
|
|
1099
|
-
dataElement.newRawValue ?? dataElement.Value
|
|
1100
|
-
);
|
|
1101
|
-
expect(outputDicomDict.dict["00181041"].Value).toEqual(
|
|
1102
|
-
dataElement.Value
|
|
1103
|
-
);
|
|
1104
|
-
}
|
|
1105
|
-
);
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
describe("sequences", () => {
|
|
1109
|
-
test("nested sequences should support lossless round trip", () => {
|
|
1110
|
-
const dataset = {
|
|
1111
|
-
52009229: {
|
|
1112
|
-
vr: "SQ",
|
|
1113
|
-
Value: [
|
|
1114
|
-
{
|
|
1115
|
-
"0020000E": {
|
|
1116
|
-
vr: "UI",
|
|
1117
|
-
Value: [
|
|
1118
|
-
"1.3.6.1.4.1.5962.99.1.2280943358.716200484.1363785608958.1.1"
|
|
1119
|
-
]
|
|
1120
|
-
},
|
|
1121
|
-
"00089123": {
|
|
1122
|
-
vr: "SQ",
|
|
1123
|
-
Value: [
|
|
1124
|
-
{
|
|
1125
|
-
"00181030": {
|
|
1126
|
-
vr: "AE",
|
|
1127
|
-
_rawValue: [" TEST_AE "],
|
|
1128
|
-
Value: ["TEST_AE"]
|
|
1129
|
-
},
|
|
1130
|
-
"00180050": {
|
|
1131
|
-
vr: "DS",
|
|
1132
|
-
Value: [5.0],
|
|
1133
|
-
_rawValue: ["5.000 "]
|
|
1134
|
-
}
|
|
1135
|
-
},
|
|
1136
|
-
{
|
|
1137
|
-
"00181030": {
|
|
1138
|
-
vr: "AE",
|
|
1139
|
-
_rawValue: [" TEST_AE "],
|
|
1140
|
-
Value: ["TEST_AE"]
|
|
1141
|
-
},
|
|
1142
|
-
"00180050": {
|
|
1143
|
-
vr: "DS",
|
|
1144
|
-
Value: [6.0],
|
|
1145
|
-
_rawValue: ["6.000 "]
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
]
|
|
1149
|
-
}
|
|
1150
|
-
},
|
|
1151
|
-
{
|
|
1152
|
-
"0020000E": {
|
|
1153
|
-
vr: "UI",
|
|
1154
|
-
Value: [
|
|
1155
|
-
"1.3.6.1.4.1.5962.99.1.2280943358.716200484.1363785608958.1.2"
|
|
1156
|
-
]
|
|
1157
|
-
},
|
|
1158
|
-
"00089123": {
|
|
1159
|
-
vr: "SQ",
|
|
1160
|
-
Value: [
|
|
1161
|
-
{
|
|
1162
|
-
"00181030": {
|
|
1163
|
-
vr: "LO",
|
|
1164
|
-
Value: ["ABDOMEN MRI"]
|
|
1165
|
-
},
|
|
1166
|
-
"00180050": {
|
|
1167
|
-
vr: "IS",
|
|
1168
|
-
_rawValue: [" -123 "], // leading/trailing spaces & sign allowed
|
|
1169
|
-
Value: [-123]
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
]
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
]
|
|
1176
|
-
}
|
|
1177
|
-
};
|
|
1178
|
-
|
|
1179
|
-
const dicomDict = new DicomDict({});
|
|
1180
|
-
dicomDict.dict = dataset;
|
|
1181
|
-
|
|
1182
|
-
// confirm after write raw values are re-encoded
|
|
1183
|
-
const outputBuffer = dicomDict.write();
|
|
1184
|
-
const outputDicomDict = DicomMessage.readFile(outputBuffer);
|
|
1185
|
-
|
|
1186
|
-
// lossless read/write should match entire data set
|
|
1187
|
-
deepEqual(dicomDict.dict, outputDicomDict.dict);
|
|
1188
|
-
});
|
|
1189
|
-
});
|
|
1190
|
-
|
|
1191
|
-
test("File dataset should be equal after read and write", async () => {
|
|
1192
|
-
const inputBuffer = await getDcmjsDataFile(
|
|
1193
|
-
"unknown-VR",
|
|
1194
|
-
"sample-dicom-with-un-vr.dcm"
|
|
1195
|
-
);
|
|
1196
|
-
const dicomDict = DicomMessage.readFile(inputBuffer);
|
|
1197
|
-
|
|
1198
|
-
// confirm raw string representation of DS contains extra additional metadata
|
|
1199
|
-
// represented by bytes [30 2E 31 34 30 5C 30 2E 31 34 30 20]
|
|
1200
|
-
expect(dicomDict.dict["00280030"]._rawValue).toEqual([
|
|
1201
|
-
"0.140",
|
|
1202
|
-
"0.140 "
|
|
1203
|
-
]);
|
|
1204
|
-
expect(dicomDict.dict["00280030"].Value).toEqual([0.14, 0.14]);
|
|
1205
|
-
|
|
1206
|
-
// confirm after write raw values are re-encoded
|
|
1207
|
-
const outputBuffer = dicomDict.write();
|
|
1208
|
-
const outputDicomDict = DicomMessage.readFile(outputBuffer);
|
|
1209
|
-
|
|
1210
|
-
// explicitly verify for DS for clarity
|
|
1211
|
-
expect(outputDicomDict.dict["00280030"]._rawValue).toEqual([
|
|
1212
|
-
"0.140",
|
|
1213
|
-
"0.140 "
|
|
1214
|
-
]);
|
|
1215
|
-
expect(outputDicomDict.dict["00280030"].Value).toEqual([0.14, 0.14]);
|
|
1216
|
-
|
|
1217
|
-
// lossless read/write should match entire data set
|
|
1218
|
-
deepEqual(dicomDict.dict, outputDicomDict.dict);
|
|
1219
|
-
});
|
|
1220
|
-
|
|
1221
|
-
test("0 length PN tag should be retained following naturalize and de-naturalize", async () => {
|
|
1222
|
-
const inputBuffer = await getDcmjsDataFile(
|
|
1223
|
-
"empty-tag-round-trip",
|
|
1224
|
-
"empty-person-name.dcm"
|
|
1225
|
-
);
|
|
1226
|
-
const origDicomDict = DicomMessage.readFile(inputBuffer);
|
|
1227
|
-
const origNaturalizedDataset = DicomMetaDictionary.naturalizeDataset(
|
|
1228
|
-
origDicomDict.dict
|
|
1229
|
-
);
|
|
1230
|
-
|
|
1231
|
-
// confirm starting dataset contains empty tag value for referring physician person name
|
|
1232
|
-
expect(origDicomDict.dict["00080090"]._rawValue).toEqual("");
|
|
1233
|
-
expect(origNaturalizedDataset.ReferringPhysicianName).toEqual([]);
|
|
1234
|
-
|
|
1235
|
-
// re-encode the unnaturalized object
|
|
1236
|
-
origDicomDict.dict = DicomMetaDictionary.denaturalizeDataset(
|
|
1237
|
-
origNaturalizedDataset
|
|
1238
|
-
);
|
|
1239
|
-
const outputBuffer = origDicomDict.write();
|
|
1240
|
-
const newDicomDict = DicomMessage.readFile(outputBuffer);
|
|
1241
|
-
const newNaturalizedDataset = DicomMetaDictionary.naturalizeDataset(
|
|
1242
|
-
origDicomDict.dict
|
|
1243
|
-
);
|
|
1244
|
-
|
|
1245
|
-
// confirm output referring physician name remains the same
|
|
1246
|
-
expect(newDicomDict.dict["00080090"]._rawValue).toEqual("");
|
|
1247
|
-
expect(newNaturalizedDataset.ReferringPhysicianName).toEqual([]);
|
|
1248
|
-
|
|
1249
|
-
// confirm no other changes to the rest of the file
|
|
1250
|
-
deepEqual(origDicomDict, newDicomDict);
|
|
1251
|
-
});
|
|
1252
|
-
|
|
1253
|
-
test("uncompressed data should be read correctly as arraybuffer", () => {
|
|
1254
|
-
const buffer = fs.readFileSync("test/sample-dicom.dcm");
|
|
1255
|
-
const dicomDict = DicomMessage.readFile(buffer.buffer);
|
|
1256
|
-
// console.warn("fullData=", fullData);
|
|
1257
|
-
const { dict } = dicomDict;
|
|
1258
|
-
const [originalPixelArray] = dict["7FE00010"].Value;
|
|
1259
|
-
expect(originalPixelArray).toBeInstanceOf(ArrayBuffer);
|
|
1260
|
-
expect(originalPixelArray.byteLength).toBe(512 * 512 * 2);
|
|
1261
|
-
const uint = new Uint16Array(originalPixelArray);
|
|
1262
|
-
expect(uint[39138]).toBe(1);
|
|
1263
|
-
|
|
1264
|
-
const natural = DicomMetaDictionary.naturalizeDataset(dict);
|
|
1265
|
-
dicomDict.dict = DicomMetaDictionary.denaturalizeDataset(natural);
|
|
1266
|
-
|
|
1267
|
-
const outputBuffer = dicomDict.write();
|
|
1268
|
-
const outputDicomDict = DicomMessage.readFile(outputBuffer);
|
|
1269
|
-
|
|
1270
|
-
const [outputPixelArray] = outputDicomDict.dict["7FE00010"].Value;
|
|
1271
|
-
expect(outputPixelArray).toBeInstanceOf(ArrayBuffer);
|
|
1272
|
-
const uintOut = new Uint16Array(outputPixelArray);
|
|
1273
|
-
expect(uintOut.length).toBe(uint.length);
|
|
1274
|
-
for (let i = 0; i < uint.length; i++) {
|
|
1275
|
-
expect(uintOut[i]).toBe(uint[i]);
|
|
1276
|
-
}
|
|
1277
|
-
});
|
|
1278
|
-
|
|
1279
|
-
test("uncompressed PixelData written with explicit length (524288) for streaming read", () => {
|
|
1280
|
-
// test/sample-dicom.dcm is uncompressed data
|
|
1281
|
-
const buffer = fs.readFileSync("test/sample-dicom.dcm");
|
|
1282
|
-
const dicomDict = DicomMessage.readFile(buffer.buffer);
|
|
1283
|
-
const { dict } = dicomDict;
|
|
1284
|
-
|
|
1285
|
-
// Get original pixel data and compute hash
|
|
1286
|
-
const originalPixelBytes = getPixelDataBytes(
|
|
1287
|
-
dict[TagHex.PixelData].Value
|
|
1288
|
-
);
|
|
1289
|
-
const originalHash = hashArrayBuffer(originalPixelBytes);
|
|
1290
|
-
|
|
1291
|
-
// Write to memory buffer
|
|
1292
|
-
const natural = DicomMetaDictionary.naturalizeDataset(dict);
|
|
1293
|
-
dicomDict.dict = DicomMetaDictionary.denaturalizeDataset(natural);
|
|
1294
|
-
const outputBuffer = dicomDict.write();
|
|
1295
|
-
const writtenDict = DicomMessage.readFile(outputBuffer);
|
|
1296
|
-
|
|
1297
|
-
// PixelData must be written with explicit length 524288 (512*512*2), NOT undefined length, so a streaming reader can read it
|
|
1298
|
-
const writtenPixelBytes = getPixelDataBytes(
|
|
1299
|
-
writtenDict.dict[TagHex.PixelData].Value
|
|
1300
|
-
);
|
|
1301
|
-
expect(writtenPixelBytes.byteLength).toBe(524288);
|
|
1302
|
-
|
|
1303
|
-
// Hash of pixel data must match original
|
|
1304
|
-
const writtenHash = hashArrayBuffer(writtenPixelBytes);
|
|
1305
|
-
expect(writtenHash).toBe(originalHash);
|
|
1306
|
-
});
|
|
1307
|
-
|
|
1308
|
-
test("Deflated Explicit VR Little Endian: PixelData written with explicit length (unencapsulated)", () => {
|
|
1309
|
-
// When Transfer Syntax is DEFLATED_EXPLICIT_LITTLE_ENDIAN, pixel data is unencapsulated
|
|
1310
|
-
// (raw bytes). The fix ensures unencapsulatedTransferSyntaxes includes it so PixelData
|
|
1311
|
-
// is written with explicit length, not as encapsulated (undefined length). This test
|
|
1312
|
-
// fails on master (without the fix) and passes with the fix.
|
|
1313
|
-
const pixelBytes = new ArrayBuffer(4);
|
|
1314
|
-
new Uint8Array(pixelBytes).set([0x01, 0x02, 0x03, 0x04]);
|
|
1315
|
-
const transferSyntaxUid = DEFLATED_EXPLICIT_LITTLE_ENDIAN;
|
|
1316
|
-
const meta = {
|
|
1317
|
-
[TagHex.FileMetaInformationGroupLength]: {
|
|
1318
|
-
vr: "UL",
|
|
1319
|
-
Value: [32]
|
|
1320
|
-
},
|
|
1321
|
-
[TagHex.TransferSyntaxUID]: {
|
|
1322
|
-
vr: "UI",
|
|
1323
|
-
Value: [transferSyntaxUid]
|
|
1324
|
-
}
|
|
1325
|
-
};
|
|
1326
|
-
const dict = {
|
|
1327
|
-
[TagHex.PixelData]: {
|
|
1328
|
-
vr: "OW",
|
|
1329
|
-
Value: [pixelBytes]
|
|
1330
|
-
}
|
|
1331
|
-
};
|
|
1332
|
-
const dicomDict = new DicomDict(meta);
|
|
1333
|
-
dicomDict.dict = dict;
|
|
1334
|
-
|
|
1335
|
-
const outputBuffer = dicomDict.write();
|
|
1336
|
-
const arrayBuf =
|
|
1337
|
-
outputBuffer instanceof ArrayBuffer
|
|
1338
|
-
? outputBuffer
|
|
1339
|
-
: outputBuffer.buffer.slice(
|
|
1340
|
-
outputBuffer.byteOffset,
|
|
1341
|
-
outputBuffer.byteOffset + outputBuffer.byteLength
|
|
1342
|
-
);
|
|
1343
|
-
const pixelInfo = readPixelDataFromRawBuffer(
|
|
1344
|
-
arrayBuf,
|
|
1345
|
-
transferSyntaxUid
|
|
1346
|
-
);
|
|
1347
|
-
expect(pixelInfo).not.toBeNull();
|
|
1348
|
-
expect(pixelInfo.length).not.toBe(-1);
|
|
1349
|
-
expect(pixelInfo.data).not.toBeNull();
|
|
1350
|
-
expect(pixelInfo.data.byteLength).toBe(4);
|
|
1351
|
-
});
|
|
1352
|
-
|
|
1353
|
-
test("compressed data should be read correctly as arraybuffer", () => {
|
|
1354
|
-
const buffer = fs.readFileSync("test/sample-op.dcm");
|
|
1355
|
-
const dicomDict = DicomMessage.readFile(buffer.buffer);
|
|
1356
|
-
// console.warn("fullData=", fullData);
|
|
1357
|
-
const { dict } = dicomDict;
|
|
1358
|
-
const [originalPixelArray] = dict["7FE00010"].Value;
|
|
1359
|
-
expect(originalPixelArray).toBeInstanceOf(ArrayBuffer);
|
|
1360
|
-
// Values from dcmdump
|
|
1361
|
-
expect(originalPixelArray.byteLength).toBe(101304);
|
|
1362
|
-
const originalPixelBytes = new Uint8Array(originalPixelArray);
|
|
1363
|
-
expect(originalPixelBytes[0]).toBe(255);
|
|
1364
|
-
expect(originalPixelBytes[1]).toBe(216);
|
|
1365
|
-
|
|
1366
|
-
const outputBuffer = dicomDict.write({ fragmentMultiframe: false });
|
|
1367
|
-
const outputDicomDict = DicomMessage.readFile(outputBuffer);
|
|
1368
|
-
|
|
1369
|
-
const [outputPixelArray] = outputDicomDict.dict["7FE00010"].Value;
|
|
1370
|
-
expect(outputPixelArray).toBeInstanceOf(ArrayBuffer);
|
|
1371
|
-
const outputPixelBytes = new Uint8Array(outputPixelArray);
|
|
1372
|
-
expect(outputPixelBytes.length).toBe(originalPixelBytes.length);
|
|
1373
|
-
for (let i = 0; i < originalPixelBytes.length; i++) {
|
|
1374
|
-
expect(outputPixelBytes[i]).toBe(originalPixelBytes[i]);
|
|
1375
|
-
}
|
|
1376
|
-
});
|
|
1377
|
-
|
|
1378
|
-
test("0 length US should use default value for both Value and rawValue", async () => {
|
|
1379
|
-
const inputBuffer = await getDcmjsDataFile(
|
|
1380
|
-
"empty-tag-round-trip",
|
|
1381
|
-
"zero-length-US.dcm"
|
|
1382
|
-
);
|
|
1383
|
-
const origDicomDict = DicomMessage.readFile(inputBuffer);
|
|
1384
|
-
|
|
1385
|
-
// expect sequence to be in file
|
|
1386
|
-
expect(origDicomDict.dict["00180012"].Value).toBeTruthy();
|
|
1387
|
-
|
|
1388
|
-
// Fetch bolus agent number from first sequence element
|
|
1389
|
-
const contrastBolusAgentSq = origDicomDict.dict["00180012"].Value;
|
|
1390
|
-
const bolusAgentNum = contrastBolusAgentSq[0]["00189337"];
|
|
1391
|
-
|
|
1392
|
-
// verify default values parsed correctly
|
|
1393
|
-
expect(bolusAgentNum.Value).toEqual([0]);
|
|
1394
|
-
expect(bolusAgentNum._rawValue).toEqual([0]);
|
|
1395
|
-
});
|
|
1396
|
-
});
|
|
1397
|
-
|
|
1398
|
-
const getDcmjsDataFile = async (release, fileName) => {
|
|
1399
|
-
const url =
|
|
1400
|
-
"https://github.com/dcmjs-org/data/releases/download/" +
|
|
1401
|
-
release +
|
|
1402
|
-
"/" +
|
|
1403
|
-
fileName;
|
|
1404
|
-
const dcmPath = await getTestDataset(url, fileName);
|
|
1405
|
-
|
|
1406
|
-
return fs.readFileSync(dcmPath).buffer;
|
|
1407
|
-
};
|