@radicalimaging/static-wado-creator 1.3.2 → 1.6.10
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/CHANGELOG.md +234 -0
- package/README.md +1 -1
- package/bin/mkdicomweb.mjs +8 -0
- package/dist/canvas-xaqc8v6y.node +0 -0
- package/dist/index.js +481518 -0
- package/lib/DeleteStudy.js +9 -2
- package/lib/RejectInstance.js +15 -3
- package/lib/StaticWado.js +245 -41
- package/lib/createPart10.js +8 -4
- package/lib/createThumbnail.js +181 -0
- package/lib/index.js +2 -0
- package/lib/instanceMain.js +1 -0
- package/lib/mkdicomwebConfig.js +122 -19
- package/lib/model/TagLists.js +24 -3
- package/lib/model/uids.js +5 -1
- package/lib/operation/InstanceDeduplicate.js +14 -4
- package/lib/operation/ScanStudy.js +38 -5
- package/lib/operation/StudyData.js +158 -38
- package/lib/operation/ThumbnailService.js +65 -31
- package/lib/operation/adapter/decodeImage.js +5 -2
- package/lib/operation/adapter/getImageInfo.js +19 -1
- package/lib/operation/adapter/transcodeImage.js +174 -52
- package/lib/operation/extractImageFrames.js +19 -4
- package/lib/operation/getDataSet.js +41 -7
- package/lib/operation/getEncapsulatedImageFrame.js +30 -6
- package/lib/operation/getUncompressedImageFrame.js +10 -2
- package/lib/operation/getVR.js +10 -2
- package/lib/operation/getValue.js +40 -12
- package/lib/operation/validateMetadata.js +40 -0
- package/lib/program/index.js +5 -3
- package/lib/serverMain.js +27 -0
- package/lib/util/IdCreator.js +32 -6
- package/lib/util/adaptProgramOpts.js +1 -1
- package/lib/util/compressionOptionParser.js +12 -3
- package/lib/util/resultsReport.ts +88 -0
- package/lib/writer/CompleteStudyWriter.js +49 -13
- package/lib/writer/DeduplicateWriter.js +2 -1
- package/lib/writer/ExpandUriPath.js +4 -1
- package/lib/writer/HashDataWriter.js +5 -1
- package/lib/writer/ImageFrameWriter.js +38 -13
- package/lib/writer/RawDicomWriter.js +11 -2
- package/lib/writer/VideoWriter.js +19 -7
- package/lib/writer/WriteMultipart.js +3 -1
- package/lib/writer/WriteStream.js +12 -4
- package/lib/writer/static-dicomweb.code-workspace +8 -0
- package/package.json +23 -19
- package/bin/mkdicomweb.js +0 -9
- package/license.md +0 -21
|
@@ -11,6 +11,26 @@ const transcodeOp = {
|
|
|
11
11
|
transcode: 3, // decode and then encode
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
const jpegBeforeEncode = (options, encoder) => {
|
|
15
|
+
const quality = options.quality ?? 99;
|
|
16
|
+
// First value is to encode as reversible lossless colour
|
|
17
|
+
return (encoder) => {
|
|
18
|
+
console.log("Calling setQuality", quality, !!encoder.setQuality);
|
|
19
|
+
encoder.setQuality(quality);
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const jlsBeforeEncode = (options) => {
|
|
24
|
+
const delta = (options.delta ?? lossy) ? 3 : 0;
|
|
25
|
+
return (encoder) => encoder.setNearLossless(delta);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const htj2kBeforeEncode = (options) => {
|
|
29
|
+
const { lossy } = options;
|
|
30
|
+
const quality = lossy === true ? 0.002 : -1;
|
|
31
|
+
return (encoder) => encoder.setQuality(!lossy, quality);
|
|
32
|
+
};
|
|
33
|
+
|
|
14
34
|
/**
|
|
15
35
|
* Static mapping for transcoding decoder destination names/uids - currently only lei, jls and jls-lossy appear to work.
|
|
16
36
|
*/
|
|
@@ -22,10 +42,12 @@ const transcodeDestinationMap = {
|
|
|
22
42
|
jls: {
|
|
23
43
|
transferSyntaxUid: "1.2.840.10008.1.2.4.80",
|
|
24
44
|
transcodeOp: transcodeOp.encode,
|
|
45
|
+
beforeEncode: jlsBeforeEncode.bind(null, { lossy: false }),
|
|
25
46
|
},
|
|
26
47
|
"jls-lossy": {
|
|
27
48
|
transferSyntaxUid: "1.2.840.10008.1.2.4.81",
|
|
28
49
|
transcodeOp: transcodeOp.encode,
|
|
50
|
+
beforeEncode: jlsBeforeEncode.bind(null, { lossy: true, delta: 3 }),
|
|
29
51
|
},
|
|
30
52
|
jp2: {
|
|
31
53
|
transferSyntaxUid: "1.2.840.10008.1.2.4.90",
|
|
@@ -36,12 +58,19 @@ const transcodeDestinationMap = {
|
|
|
36
58
|
transcodeOp: transcodeOp.encode,
|
|
37
59
|
},
|
|
38
60
|
jhc: {
|
|
61
|
+
transferSyntaxUid: "1.2.840.10008.1.2.4.201",
|
|
62
|
+
transcodeOp: transcodeOp.encode,
|
|
63
|
+
beforeEncode: htj2kBeforeEncode({}),
|
|
64
|
+
},
|
|
65
|
+
"jhc-lossy": {
|
|
39
66
|
transferSyntaxUid: "1.2.840.10008.1.2.4.202",
|
|
40
67
|
transcodeOp: transcodeOp.encode,
|
|
68
|
+
beforeEncode: htj2kBeforeEncode({ lossy: true }),
|
|
41
69
|
},
|
|
42
70
|
jpeg: {
|
|
43
71
|
transferSyntaxUid: "1.2.840.10008.1.2.4.50",
|
|
44
72
|
transcodeOp: transcodeOp.encode,
|
|
73
|
+
// beforeEncode: jpegBeforeEncode({}),
|
|
45
74
|
},
|
|
46
75
|
};
|
|
47
76
|
|
|
@@ -103,11 +132,12 @@ const transcodeSourceMap = {
|
|
|
103
132
|
* @returns A partial transcoder definition. Otherwise it returns undefined.
|
|
104
133
|
*/
|
|
105
134
|
function getDestinationTranscoder(id) {
|
|
106
|
-
const destinationTranscoderEntry =
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
135
|
+
const destinationTranscoderEntry =
|
|
136
|
+
transcodeDestinationMap[id] ||
|
|
137
|
+
Object.values(transcodeDestinationMap).find(
|
|
138
|
+
(value) => value.transferSyntaxUid === id,
|
|
139
|
+
);
|
|
140
|
+
return destinationTranscoderEntry;
|
|
111
141
|
}
|
|
112
142
|
|
|
113
143
|
/**
|
|
@@ -116,8 +146,13 @@ function getDestinationTranscoder(id) {
|
|
|
116
146
|
* @param {*} transferSyntaxUid
|
|
117
147
|
* @returns
|
|
118
148
|
*/
|
|
119
|
-
function getTranscoder(
|
|
120
|
-
|
|
149
|
+
function getTranscoder(
|
|
150
|
+
transferSyntaxUid,
|
|
151
|
+
{ contentType: greyContentType, colorContentType },
|
|
152
|
+
samplesPerPixel,
|
|
153
|
+
) {
|
|
154
|
+
const contentType =
|
|
155
|
+
samplesPerPixel === 3 ? colorContentType : greyContentType;
|
|
121
156
|
const sourceTranscoder = transcodeSourceMap[transferSyntaxUid];
|
|
122
157
|
const destinationTranscoder = getDestinationTranscoder(contentType);
|
|
123
158
|
if (!sourceTranscoder || !destinationTranscoder) {
|
|
@@ -126,8 +161,10 @@ function getTranscoder(transferSyntaxUid, { contentType: greyContentType, colorC
|
|
|
126
161
|
|
|
127
162
|
return {
|
|
128
163
|
transferSyntaxUid: destinationTranscoder.transferSyntaxUid,
|
|
129
|
-
transcodeOp:
|
|
164
|
+
transcodeOp:
|
|
165
|
+
sourceTranscoder.transcodeOp | destinationTranscoder.transcodeOp,
|
|
130
166
|
alias: sourceTranscoder.alias,
|
|
167
|
+
beforeEncode: destinationTranscoder.beforeEncode,
|
|
131
168
|
};
|
|
132
169
|
}
|
|
133
170
|
|
|
@@ -148,12 +185,22 @@ function shouldTranscodeImageFrame(id, options, samplesPerPixel) {
|
|
|
148
185
|
const transcoder = getTranscoder(transferSyntaxUid, options, samplesPerPixel);
|
|
149
186
|
const validTranscoder = transcoder && transcoder.transferSyntaxUid;
|
|
150
187
|
if (!validTranscoder) {
|
|
151
|
-
console.verbose(
|
|
188
|
+
console.verbose(
|
|
189
|
+
"Not transcoding because no decoder found for",
|
|
190
|
+
transferSyntaxUid,
|
|
191
|
+
samplesPerPixel,
|
|
192
|
+
);
|
|
152
193
|
return false;
|
|
153
194
|
}
|
|
154
|
-
const validRecompress =
|
|
195
|
+
const validRecompress =
|
|
196
|
+
recompress.includes("true") || recompress.includes(transcoder.alias);
|
|
155
197
|
if (!validRecompress) {
|
|
156
|
-
console.verbose(
|
|
198
|
+
console.verbose(
|
|
199
|
+
"Not transcoding because recompress",
|
|
200
|
+
recompress,
|
|
201
|
+
"does not include",
|
|
202
|
+
transcoder.alias,
|
|
203
|
+
);
|
|
157
204
|
return false;
|
|
158
205
|
}
|
|
159
206
|
|
|
@@ -176,7 +223,11 @@ function shouldThumbUseTranscoded(id, options) {
|
|
|
176
223
|
const { transferSyntaxUid } = id;
|
|
177
224
|
// Ignore the samples per pixel for thumbnails
|
|
178
225
|
const transcoder = getTranscoder(transferSyntaxUid, options, "thumbnail");
|
|
179
|
-
const result =
|
|
226
|
+
const result =
|
|
227
|
+
transcoder &&
|
|
228
|
+
transcoder.transferSyntaxUid &&
|
|
229
|
+
options.recompress.includes(transcoder.alias) &&
|
|
230
|
+
options.recompressThumb.includes(transcoder.alias);
|
|
180
231
|
|
|
181
232
|
return result;
|
|
182
233
|
}
|
|
@@ -190,17 +241,9 @@ function transcodeLog(options, msg, error = "") {
|
|
|
190
241
|
}
|
|
191
242
|
}
|
|
192
243
|
|
|
193
|
-
const beforeEncode = (options, encoder) => {
|
|
194
|
-
const lossy = !!options.lossy;
|
|
195
|
-
const quality = lossy ? 0.002 : -1;
|
|
196
|
-
const delta = lossy ? 3 : 0;
|
|
197
|
-
// First value is to encode as reversible lossless colour
|
|
198
|
-
encoder.setQuality?.(!lossy, quality);
|
|
199
|
-
encoder.setNearLossless?.(delta);
|
|
200
|
-
};
|
|
201
|
-
|
|
202
244
|
function scale(imageFrame, imageInfo) {
|
|
203
|
-
const { rows, columns, bitsPerPixel, pixelRepresentation, samplesPerPixel } =
|
|
245
|
+
const { rows, columns, bitsPerPixel, pixelRepresentation, samplesPerPixel } =
|
|
246
|
+
imageInfo;
|
|
204
247
|
let ArrayConstructor = Float32Array;
|
|
205
248
|
if (bitsPerPixel === 8) {
|
|
206
249
|
ArrayConstructor = pixelRepresentation ? Int8Array : Uint8Array;
|
|
@@ -218,7 +261,9 @@ function scale(imageFrame, imageInfo) {
|
|
|
218
261
|
columns: Math.round(columns / 4),
|
|
219
262
|
samplesPerPixel,
|
|
220
263
|
};
|
|
221
|
-
dest.pixelData = new ArrayConstructor(
|
|
264
|
+
dest.pixelData = new ArrayConstructor(
|
|
265
|
+
dest.rows * dest.columns * samplesPerPixel,
|
|
266
|
+
);
|
|
222
267
|
replicate(src, dest);
|
|
223
268
|
|
|
224
269
|
return {
|
|
@@ -243,12 +288,16 @@ function scale(imageFrame, imageInfo) {
|
|
|
243
288
|
async function generateLossyImage(id, decoded, options) {
|
|
244
289
|
if (!options.alternate) return;
|
|
245
290
|
if (!decoded?.imageFrame) return;
|
|
291
|
+
console.verbose("Writing alternate thumbnail");
|
|
246
292
|
|
|
247
293
|
try {
|
|
248
294
|
let { imageFrame, imageInfo } = decoded;
|
|
249
295
|
const lossyId = {
|
|
250
296
|
...id,
|
|
251
|
-
imageFrameRootPath: id.imageFrameRootPath.replace(
|
|
297
|
+
imageFrameRootPath: id.imageFrameRootPath.replace(
|
|
298
|
+
"frames",
|
|
299
|
+
options.alternateName,
|
|
300
|
+
),
|
|
252
301
|
transferSyntaxUid: transcodeDestinationMap.jhc.transferSyntaxUid,
|
|
253
302
|
};
|
|
254
303
|
|
|
@@ -264,7 +313,8 @@ async function generateLossyImage(id, decoded, options) {
|
|
|
264
313
|
}
|
|
265
314
|
|
|
266
315
|
if (options.alternate === "jls") {
|
|
267
|
-
lossyId.transferSyntaxUid =
|
|
316
|
+
lossyId.transferSyntaxUid =
|
|
317
|
+
transcodeDestinationMap["jls-lossy"].transferSyntaxUid;
|
|
268
318
|
} else if (options.alternate === "jlsLossless") {
|
|
269
319
|
lossyId.transferSyntaxUid = transcodeDestinationMap.jls.transferSyntaxUid;
|
|
270
320
|
lossy = false;
|
|
@@ -273,19 +323,32 @@ async function generateLossyImage(id, decoded, options) {
|
|
|
273
323
|
}
|
|
274
324
|
|
|
275
325
|
const encodeOptions = {
|
|
276
|
-
beforeEncode: beforeEncode
|
|
277
|
-
lossy,
|
|
278
|
-
}),
|
|
326
|
+
beforeEncode: transcodeDestinationMap[options.alternate]?.beforeEncode,
|
|
279
327
|
};
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
328
|
+
|
|
329
|
+
const lossyEncoding = await dicomCodec.encode(
|
|
330
|
+
imageFrame,
|
|
331
|
+
imageInfo,
|
|
332
|
+
lossyId.transferSyntaxUid,
|
|
333
|
+
encodeOptions,
|
|
334
|
+
);
|
|
335
|
+
console.verbose(
|
|
336
|
+
"Encoded alternate",
|
|
337
|
+
lossyId.transferSyntaxUid,
|
|
338
|
+
"of size",
|
|
339
|
+
lossyEncoding.imageFrame.length,
|
|
340
|
+
);
|
|
341
|
+
|
|
283
342
|
return { id: lossyId, imageFrame: lossyEncoding.imageFrame };
|
|
284
343
|
} catch (e) {
|
|
285
344
|
console.warn("Unable to create alternate:", e);
|
|
286
345
|
}
|
|
287
346
|
}
|
|
288
347
|
|
|
348
|
+
function isPalette(dataSet) {
|
|
349
|
+
return dataSet.string(Tags.RawPhotometricInterpretation) === "PALETTE COLOR";
|
|
350
|
+
}
|
|
351
|
+
|
|
289
352
|
/**
|
|
290
353
|
* Transcode imageFrame. It uses transcodeMap to define the target encoding based on source encoding.
|
|
291
354
|
*
|
|
@@ -296,13 +359,23 @@ async function generateLossyImage(id, decoded, options) {
|
|
|
296
359
|
* @param {*} options runner options
|
|
297
360
|
* @returns object result for transcoding operation with id and image frame.
|
|
298
361
|
*/
|
|
299
|
-
async function transcodeImageFrame(
|
|
362
|
+
async function transcodeImageFrame(
|
|
363
|
+
id,
|
|
364
|
+
targetIdSrc,
|
|
365
|
+
imageFrame,
|
|
366
|
+
dataSet,
|
|
367
|
+
options = {},
|
|
368
|
+
) {
|
|
300
369
|
let targetId = targetIdSrc;
|
|
301
370
|
let result = {};
|
|
302
371
|
|
|
303
372
|
const samplesPerPixel = dataSet.uint16(Tags.RawSamplesPerPixel);
|
|
304
373
|
const planarConfiguration = dataSet.uint16("x00280006");
|
|
305
|
-
if (
|
|
374
|
+
if (
|
|
375
|
+
!shouldTranscodeImageFrame(id, options, samplesPerPixel) ||
|
|
376
|
+
planarConfiguration === 1 ||
|
|
377
|
+
isPalette(dataSet)
|
|
378
|
+
) {
|
|
306
379
|
console.verbose("Shouldn't transcode");
|
|
307
380
|
return {
|
|
308
381
|
id,
|
|
@@ -311,11 +384,22 @@ async function transcodeImageFrame(id, targetIdSrc, imageFrame, dataSet, options
|
|
|
311
384
|
};
|
|
312
385
|
}
|
|
313
386
|
|
|
314
|
-
const transcoder = getTranscoder(
|
|
387
|
+
const transcoder = getTranscoder(
|
|
388
|
+
id.transferSyntaxUid,
|
|
389
|
+
options,
|
|
390
|
+
samplesPerPixel,
|
|
391
|
+
);
|
|
315
392
|
|
|
316
393
|
// Don't transcode if not required
|
|
317
|
-
if (
|
|
318
|
-
|
|
394
|
+
if (
|
|
395
|
+
targetId.transferSyntaxUid !== transcoder.transferSyntaxUid &&
|
|
396
|
+
!options.forceTranscode
|
|
397
|
+
) {
|
|
398
|
+
console.verbose(
|
|
399
|
+
"Image is already in",
|
|
400
|
+
targetId.transferSyntaxUid,
|
|
401
|
+
"not transcoding",
|
|
402
|
+
);
|
|
319
403
|
return {
|
|
320
404
|
id,
|
|
321
405
|
imageFrame,
|
|
@@ -329,33 +413,63 @@ async function transcodeImageFrame(id, targetIdSrc, imageFrame, dataSet, options
|
|
|
329
413
|
let done = false;
|
|
330
414
|
let processResultMsg = "";
|
|
331
415
|
const encodeOptions = {
|
|
332
|
-
beforeEncode: beforeEncode
|
|
333
|
-
lossy: options.lossy,
|
|
334
|
-
}),
|
|
416
|
+
beforeEncode: transcoder.beforeEncode,
|
|
335
417
|
};
|
|
336
418
|
let decoded;
|
|
337
419
|
|
|
338
420
|
try {
|
|
339
421
|
switch (transcoder.transcodeOp) {
|
|
340
422
|
case transcodeOp.transcode:
|
|
341
|
-
transcodeLog(
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
423
|
+
transcodeLog(
|
|
424
|
+
options,
|
|
425
|
+
`Full transcoding image from \x1b[43m${id.transferSyntaxUid}\x1b[0m to \x1b[43m${targetId.transferSyntaxUid}\x1b[0m`,
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
decoded = await dicomCodec.decode(
|
|
429
|
+
imageFrame,
|
|
430
|
+
imageInfo,
|
|
431
|
+
id.transferSyntaxUid,
|
|
432
|
+
);
|
|
433
|
+
result = await dicomCodec.encode(
|
|
434
|
+
decoded.imageFrame,
|
|
435
|
+
decoded.imageInfo,
|
|
436
|
+
targetId.transferSyntaxUid,
|
|
437
|
+
encodeOptions,
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
console.verbose(
|
|
441
|
+
"transcoded image to",
|
|
442
|
+
targetId.transferSyntaxUid,
|
|
443
|
+
"of size",
|
|
444
|
+
result.imageFrame.length,
|
|
445
|
+
);
|
|
347
446
|
processResultMsg = `Transcoding finished`;
|
|
348
447
|
break;
|
|
349
448
|
case transcodeOp.encode:
|
|
350
|
-
transcodeLog(
|
|
351
|
-
|
|
352
|
-
|
|
449
|
+
transcodeLog(
|
|
450
|
+
options,
|
|
451
|
+
`Encoding image to \x1b[43m${targetId.transferSyntaxUid}\x1b[0m`,
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
result = await dicomCodec.encode(
|
|
455
|
+
imageFrame,
|
|
456
|
+
imageInfo,
|
|
457
|
+
targetId.transferSyntaxUid,
|
|
458
|
+
encodeOptions,
|
|
459
|
+
);
|
|
353
460
|
|
|
354
461
|
processResultMsg = `Encoding finished`;
|
|
355
462
|
break;
|
|
356
463
|
case transcodeOp.decode:
|
|
357
|
-
transcodeLog(
|
|
358
|
-
|
|
464
|
+
transcodeLog(
|
|
465
|
+
options,
|
|
466
|
+
`Decoding image from \x1b[43m${id.transferSyntaxUid}\x1b[0m`,
|
|
467
|
+
);
|
|
468
|
+
result = await dicomCodec.decode(
|
|
469
|
+
imageFrame,
|
|
470
|
+
imageInfo,
|
|
471
|
+
id.transferSyntaxUid,
|
|
472
|
+
);
|
|
359
473
|
|
|
360
474
|
processResultMsg = `Decoding finished`;
|
|
361
475
|
break;
|
|
@@ -403,7 +517,11 @@ function transcodeId(id, options, samplesPerPixel) {
|
|
|
403
517
|
}
|
|
404
518
|
|
|
405
519
|
const targetId = { ...id };
|
|
406
|
-
const { transferSyntaxUid } = getTranscoder(
|
|
520
|
+
const { transferSyntaxUid } = getTranscoder(
|
|
521
|
+
id.transferSyntaxUid,
|
|
522
|
+
options,
|
|
523
|
+
samplesPerPixel,
|
|
524
|
+
);
|
|
407
525
|
|
|
408
526
|
targetId.transferSyntaxUid = transferSyntaxUid;
|
|
409
527
|
|
|
@@ -432,7 +550,11 @@ function transcodeMetadata(metadata, id, options) {
|
|
|
432
550
|
const result = { ...metadata };
|
|
433
551
|
|
|
434
552
|
if (result[Tags.AvailableTransferSyntaxUID]) {
|
|
435
|
-
Tags.setValue(
|
|
553
|
+
Tags.setValue(
|
|
554
|
+
result,
|
|
555
|
+
Tags.AvailableTransferSyntaxUID,
|
|
556
|
+
transcodedId.transferSyntaxUid,
|
|
557
|
+
);
|
|
436
558
|
console.verbose("Apply available tsuid", transcodeId.transferSyntaxUid);
|
|
437
559
|
}
|
|
438
560
|
|
|
@@ -5,7 +5,8 @@ const getUncompressedImageFrame = require("./getUncompressedImageFrame");
|
|
|
5
5
|
const getEncapsulatedImageFrame = require("./getEncapsulatedImageFrame");
|
|
6
6
|
const { isVideo } = require("../writer/VideoWriter");
|
|
7
7
|
|
|
8
|
-
const areFramesAreFragmented = (attr, numberOfFrames) =>
|
|
8
|
+
const areFramesAreFragmented = (attr, numberOfFrames) =>
|
|
9
|
+
attr.encapsulatedPixelData && numberOfFrames != attr.fragments.length;
|
|
9
10
|
|
|
10
11
|
const getFrameSize = (dataSet) => {
|
|
11
12
|
const rows = dataSet.uint16("x00280010");
|
|
@@ -30,11 +31,25 @@ const extractImageFrames = async (dataSet, attr, vr, callback) => {
|
|
|
30
31
|
|
|
31
32
|
for (let frameIndex = 0; frameIndex < numberOfFrames; frameIndex++) {
|
|
32
33
|
if (attr.encapsulatedPixelData) {
|
|
33
|
-
const compressedFrame = getEncapsulatedImageFrame(
|
|
34
|
+
const compressedFrame = getEncapsulatedImageFrame(
|
|
35
|
+
dataSet,
|
|
36
|
+
attr,
|
|
37
|
+
frameIndex,
|
|
38
|
+
framesAreFragmented,
|
|
39
|
+
);
|
|
34
40
|
BulkDataURI = await callback.imageFrame(compressedFrame, { dataSet });
|
|
35
|
-
Stats.OverallStats.add(
|
|
41
|
+
Stats.OverallStats.add(
|
|
42
|
+
"Image Write",
|
|
43
|
+
`Write image frame ${frameIndex + 1}`,
|
|
44
|
+
5000,
|
|
45
|
+
);
|
|
36
46
|
} else {
|
|
37
|
-
const uncompressedFrame = getUncompressedImageFrame(
|
|
47
|
+
const uncompressedFrame = getUncompressedImageFrame(
|
|
48
|
+
dataSet,
|
|
49
|
+
attr,
|
|
50
|
+
frameIndex,
|
|
51
|
+
uncompressedFrameSize,
|
|
52
|
+
);
|
|
38
53
|
BulkDataURI = await callback.imageFrame(uncompressedFrame, { dataSet });
|
|
39
54
|
}
|
|
40
55
|
}
|
|
@@ -13,14 +13,23 @@ const getValue = require("./getValue");
|
|
|
13
13
|
* @param {*} parentAttr Parent reference for sequence element tags.
|
|
14
14
|
* @returns
|
|
15
15
|
*/
|
|
16
|
-
async function getDataSet(
|
|
16
|
+
async function getDataSet(
|
|
17
|
+
dataSet,
|
|
18
|
+
callback,
|
|
19
|
+
optionsOrig,
|
|
20
|
+
parentAttr = undefined,
|
|
21
|
+
) {
|
|
17
22
|
const metadata = {};
|
|
18
23
|
let options = optionsOrig;
|
|
19
24
|
|
|
20
25
|
// iterate over dataSet attributes in order
|
|
21
26
|
for (const tag in dataSet.elements) {
|
|
22
27
|
// Raw versions have the x in front of them
|
|
23
|
-
if (
|
|
28
|
+
if (
|
|
29
|
+
tag != Tags.RawTransferSyntaxUID &&
|
|
30
|
+
tag >= Tags.RawMinTag &&
|
|
31
|
+
tag < Tags.RawFirstBodyTag
|
|
32
|
+
) {
|
|
24
33
|
continue;
|
|
25
34
|
}
|
|
26
35
|
if (tag === Tags.RawSpecificCharacterSet) {
|
|
@@ -28,21 +37,46 @@ async function getDataSet(dataSet, callback, optionsOrig, parentAttr = undefined
|
|
|
28
37
|
options = { ...options, SpecificCharacterSet };
|
|
29
38
|
}
|
|
30
39
|
const attr = dataSet.elements[tag];
|
|
31
|
-
|
|
32
|
-
await attributeToJS(
|
|
40
|
+
|
|
41
|
+
await attributeToJS(
|
|
42
|
+
metadata,
|
|
43
|
+
tag,
|
|
44
|
+
dataSet,
|
|
45
|
+
attr,
|
|
46
|
+
callback,
|
|
47
|
+
options,
|
|
48
|
+
parentAttr,
|
|
49
|
+
);
|
|
33
50
|
}
|
|
34
51
|
if (metadata[Tags.TransferSyntaxUID]) {
|
|
35
52
|
// console.log(`Found tsuid ${JSON.stringify(metadata[Tags.TransferSyntaxUID])} assigning to ${Tags.AvailableTransferSyntaxUID}`)
|
|
36
|
-
metadata[Tags.AvailableTransferSyntaxUID] =
|
|
53
|
+
metadata[Tags.AvailableTransferSyntaxUID] =
|
|
54
|
+
metadata[Tags.TransferSyntaxUID];
|
|
37
55
|
delete metadata[Tags.TransferSyntaxUID];
|
|
38
56
|
}
|
|
39
57
|
return { metadata };
|
|
40
58
|
}
|
|
41
59
|
|
|
42
|
-
async function attributeToJS(
|
|
60
|
+
async function attributeToJS(
|
|
61
|
+
metadataSrc,
|
|
62
|
+
tag,
|
|
63
|
+
dataSet,
|
|
64
|
+
attr,
|
|
65
|
+
callback,
|
|
66
|
+
options,
|
|
67
|
+
parentAttr,
|
|
68
|
+
) {
|
|
43
69
|
const metadata = metadataSrc;
|
|
44
70
|
const vr = getVR(attr);
|
|
45
|
-
const value = await getValue(
|
|
71
|
+
const value = await getValue(
|
|
72
|
+
dataSet,
|
|
73
|
+
attr,
|
|
74
|
+
vr,
|
|
75
|
+
getDataSet,
|
|
76
|
+
callback,
|
|
77
|
+
options,
|
|
78
|
+
parentAttr,
|
|
79
|
+
);
|
|
46
80
|
const key = tag.substring(1).toUpperCase();
|
|
47
81
|
if (value === undefined || value === null || value.length === 0) {
|
|
48
82
|
if (!vr) return;
|
|
@@ -4,20 +4,44 @@ const dicomParser = require("dicom-parser");
|
|
|
4
4
|
* Function to deal with extracting an image frame from an encapsulated data set.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const getEncapsulatedImageFrame = (
|
|
8
|
-
|
|
7
|
+
const getEncapsulatedImageFrame = (
|
|
8
|
+
dataSet,
|
|
9
|
+
attr,
|
|
10
|
+
frameIndex,
|
|
11
|
+
framesAreFragmented,
|
|
12
|
+
) => {
|
|
13
|
+
if (
|
|
14
|
+
dataSet.elements.x7fe00010 &&
|
|
15
|
+
dataSet.elements.x7fe00010.basicOffsetTable.length
|
|
16
|
+
) {
|
|
9
17
|
// Basic Offset Table is not empty
|
|
10
|
-
return dicomParser.readEncapsulatedImageFrame(
|
|
18
|
+
return dicomParser.readEncapsulatedImageFrame(
|
|
19
|
+
dataSet,
|
|
20
|
+
dataSet.elements.x7fe00010,
|
|
21
|
+
frameIndex,
|
|
22
|
+
);
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
// Empty basic offset table
|
|
14
26
|
if (framesAreFragmented) {
|
|
15
|
-
const basicOffsetTable = dicomParser.createJPEGBasicOffsetTable(
|
|
27
|
+
const basicOffsetTable = dicomParser.createJPEGBasicOffsetTable(
|
|
28
|
+
dataSet,
|
|
29
|
+
dataSet.elements.x7fe00010,
|
|
30
|
+
);
|
|
16
31
|
|
|
17
|
-
return dicomParser.readEncapsulatedImageFrame(
|
|
32
|
+
return dicomParser.readEncapsulatedImageFrame(
|
|
33
|
+
dataSet,
|
|
34
|
+
dataSet.elements.x7fe00010,
|
|
35
|
+
frameIndex,
|
|
36
|
+
basicOffsetTable,
|
|
37
|
+
);
|
|
18
38
|
}
|
|
19
39
|
|
|
20
|
-
return dicomParser.readEncapsulatedPixelDataFromFragments(
|
|
40
|
+
return dicomParser.readEncapsulatedPixelDataFromFragments(
|
|
41
|
+
dataSet,
|
|
42
|
+
dataSet.elements.x7fe00010,
|
|
43
|
+
frameIndex,
|
|
44
|
+
);
|
|
21
45
|
};
|
|
22
46
|
|
|
23
47
|
module.exports = getEncapsulatedImageFrame;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
function getUncompressedImageFrame(
|
|
1
|
+
function getUncompressedImageFrame(
|
|
2
|
+
dataSet,
|
|
3
|
+
attr,
|
|
4
|
+
frame,
|
|
5
|
+
uncompressedFrameSize,
|
|
6
|
+
) {
|
|
2
7
|
const start = attr.dataOffset + frame * uncompressedFrameSize;
|
|
3
|
-
const binaryValue = dataSet.byteArray.slice(
|
|
8
|
+
const binaryValue = dataSet.byteArray.slice(
|
|
9
|
+
start,
|
|
10
|
+
start + uncompressedFrameSize,
|
|
11
|
+
);
|
|
4
12
|
return binaryValue;
|
|
5
13
|
}
|
|
6
14
|
|
package/lib/operation/getVR.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const { dataDictionary } = require("@radicalimaging/static-wado-util");
|
|
2
2
|
|
|
3
|
+
const nullVrTags = new Set();
|
|
4
|
+
nullVrTags.add("FFFEE00D");
|
|
5
|
+
|
|
3
6
|
const getVR = (attr) => {
|
|
4
7
|
if (attr.vr) {
|
|
5
8
|
return attr.vr;
|
|
@@ -7,10 +10,15 @@ const getVR = (attr) => {
|
|
|
7
10
|
// lookup the vr using the data dictionary
|
|
8
11
|
const tag = attr.tag.substring(1).toUpperCase();
|
|
9
12
|
const dataDictAttr = dataDictionary[tag];
|
|
10
|
-
if (dataDictAttr) {
|
|
13
|
+
if (dataDictAttr?.vr) {
|
|
11
14
|
return dataDictAttr.vr;
|
|
12
15
|
}
|
|
13
|
-
|
|
16
|
+
if (nullVrTags.has(tag)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log("Unknown VR", tag, attr, dataDictAttr);
|
|
21
|
+
return "UN";
|
|
14
22
|
};
|
|
15
23
|
|
|
16
24
|
module.exports = getVR;
|