@radicalimaging/static-wado-creator 1.5.0 → 1.6.11
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 +511433 -0
- package/lib/DeleteStudy.js +11 -9
- package/lib/RejectInstance.js +6 -5
- package/lib/StaticWado.js +222 -73
- package/lib/createMain.js +3 -3
- package/lib/createPart10.js +19 -19
- package/lib/createThumbnail.js +163 -0
- package/lib/deleteMain.js +2 -2
- package/lib/groupMain.js +2 -2
- package/lib/index.js +7 -5
- package/lib/indexMain.js +2 -2
- package/lib/instanceMain.js +3 -2
- package/lib/metadataMain.js +2 -2
- package/lib/mkdicomwebConfig.js +210 -115
- package/lib/model/TagLists.js +13 -4
- package/lib/model/uids.js +32 -28
- package/lib/operation/InstanceDeduplicate.js +18 -13
- package/lib/operation/ScanStudy.js +33 -7
- package/lib/operation/StudyData.js +122 -71
- package/lib/operation/ThumbnailService.js +44 -36
- package/lib/operation/adapter/decodeImage.js +7 -4
- package/lib/operation/adapter/getImageInfo.js +23 -6
- package/lib/operation/adapter/transcodeImage.js +157 -90
- package/lib/operation/extractImageFrames.js +25 -14
- package/lib/operation/getDataSet.js +4 -4
- package/lib/operation/getEncapsulatedImageFrame.js +16 -4
- package/lib/operation/getNumberOfFrames.js +2 -2
- package/lib/operation/getVR.js +12 -4
- package/lib/operation/getValue.js +42 -65
- package/lib/operation/validateMetadata.js +40 -0
- package/lib/program/index.js +2 -3
- package/lib/rejectMain.js +2 -2
- package/lib/serverMain.js +21 -0
- package/lib/util/IdCreator.js +25 -8
- package/lib/util/adaptProgramOpts.js +1 -1
- package/lib/util/compressionOptionParser.js +4 -4
- package/lib/util/resultsReport.ts +87 -0
- package/lib/writer/CompleteStudyWriter.js +42 -19
- package/lib/writer/DeduplicateWriter.js +7 -7
- package/lib/writer/ExpandUriPath.js +4 -3
- package/lib/writer/HashDataWriter.js +21 -17
- package/lib/writer/ImageFrameWriter.js +22 -10
- package/lib/writer/MultipartHeader.js +1 -1
- package/lib/writer/RawDicomWriter.js +18 -10
- package/lib/writer/ThumbnailWriter.js +2 -2
- package/lib/writer/VideoWriter.js +23 -34
- package/lib/writer/WriteMultipart.js +3 -3
- package/lib/writer/WriteStream.js +14 -12
- package/package.json +22 -18
- package/bin/mkdicomweb.js +0 -9
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
const dicomCodec = require(
|
|
2
|
-
const getImageInfo = require(
|
|
1
|
+
const dicomCodec = require('@cornerstonejs/dicom-codec');
|
|
2
|
+
const getImageInfo = require('./getImageInfo');
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const imageInfo = getImageInfo(dataset);
|
|
4
|
+
function decodeImage(imageFrame, dataset, instance, transferSyntaxUid) {
|
|
5
|
+
const imageInfo = getImageInfo(dataset, instance);
|
|
6
|
+
if (imageFrame && !imageFrame.length) {
|
|
7
|
+
imageFrame = new Uint8Array(imageFrame);
|
|
8
|
+
}
|
|
6
9
|
return dicomCodec.decode(imageFrame, imageInfo, transferSyntaxUid);
|
|
7
10
|
}
|
|
8
11
|
|
|
@@ -1,12 +1,29 @@
|
|
|
1
|
+
const { Tags } = require('@radicalimaging/static-wado-util');
|
|
1
2
|
/**
|
|
2
3
|
* Minimum image info data to be used on transcode process by dicom-codec api.
|
|
3
4
|
*/
|
|
4
|
-
function getImageInfo(dataSet) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
function getImageInfo(dataSet, instance) {
|
|
6
|
+
if (instance) {
|
|
7
|
+
const rows = Tags.getValue(instance, Tags.Rows);
|
|
8
|
+
const columns = Tags.getValue(instance, Tags.Columns);
|
|
9
|
+
const bitsAllocated = Tags.getValue(instance, Tags.BitsAllocated);
|
|
10
|
+
const samplesPerPixel = Tags.getValue(instance, Tags.SamplesPerPixel);
|
|
11
|
+
const pixelRepresentation = Tags.getValue(instance, Tags.PixelRepresentation) || 0; // not yet being used.
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
bitsAllocated,
|
|
15
|
+
samplesPerPixel,
|
|
16
|
+
rows, // Number with the image rows/height
|
|
17
|
+
columns, // Number with the image columns/width,
|
|
18
|
+
signed: pixelRepresentation === 1,
|
|
19
|
+
pixelRepresentation,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const rows = dataSet.uint16('x00280010');
|
|
23
|
+
const columns = dataSet.uint16('x00280011');
|
|
24
|
+
const bitsAllocated = dataSet.uint16('x00280100');
|
|
25
|
+
const samplesPerPixel = dataSet.uint16('x00280002');
|
|
26
|
+
const pixelRepresentation = dataSet.uint16('x00280103') || 0; // not yet being used.
|
|
10
27
|
|
|
11
28
|
return {
|
|
12
29
|
bitsAllocated,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const dicomCodec = require(
|
|
2
|
-
const { Tags, replicate } = require(
|
|
3
|
-
const getImageInfo = require(
|
|
1
|
+
const dicomCodec = require('@cornerstonejs/dicom-codec');
|
|
2
|
+
const { Tags, replicate } = require('@radicalimaging/static-wado-util');
|
|
3
|
+
const getImageInfo = require('./getImageInfo');
|
|
4
4
|
|
|
5
|
-
dicomCodec.setConfig(
|
|
5
|
+
dicomCodec.setConfig('verbose: false');
|
|
6
6
|
|
|
7
7
|
const transcodeOp = {
|
|
8
8
|
none: 0,
|
|
@@ -11,88 +11,117 @@ 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
|
*/
|
|
17
37
|
const transcodeDestinationMap = {
|
|
18
38
|
lei: {
|
|
19
|
-
transferSyntaxUid:
|
|
39
|
+
transferSyntaxUid: '1.2.840.10008.1.2.1',
|
|
20
40
|
transcodeOp: transcodeOp.none,
|
|
21
41
|
},
|
|
22
42
|
jls: {
|
|
23
|
-
transferSyntaxUid:
|
|
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
|
-
|
|
27
|
-
transferSyntaxUid:
|
|
47
|
+
'jls-lossy': {
|
|
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
|
-
transferSyntaxUid:
|
|
53
|
+
transferSyntaxUid: '1.2.840.10008.1.2.4.90',
|
|
32
54
|
transcodeOp: transcodeOp.encode,
|
|
33
55
|
},
|
|
34
|
-
|
|
35
|
-
transferSyntaxUid:
|
|
56
|
+
'jp2-lossy': {
|
|
57
|
+
transferSyntaxUid: '1.2.840.10008.1.2.4.91',
|
|
36
58
|
transcodeOp: transcodeOp.encode,
|
|
37
59
|
},
|
|
38
60
|
jhc: {
|
|
39
|
-
transferSyntaxUid:
|
|
61
|
+
transferSyntaxUid: '1.2.840.10008.1.2.4.201',
|
|
62
|
+
transcodeOp: transcodeOp.encode,
|
|
63
|
+
beforeEncode: htj2kBeforeEncode({}),
|
|
64
|
+
},
|
|
65
|
+
'jhc-lossy': {
|
|
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
|
-
transferSyntaxUid:
|
|
71
|
+
transferSyntaxUid: '1.2.840.10008.1.2.4.50',
|
|
44
72
|
transcodeOp: transcodeOp.encode,
|
|
73
|
+
// beforeEncode: jpegBeforeEncode({}),
|
|
45
74
|
},
|
|
46
75
|
};
|
|
47
76
|
|
|
48
77
|
const transcodeSourceMap = {
|
|
49
|
-
|
|
78
|
+
'1.2.840.10008.1.2': {
|
|
50
79
|
transcodeOp: transcodeOp.decode,
|
|
51
|
-
alias:
|
|
80
|
+
alias: 'uncompressed',
|
|
52
81
|
},
|
|
53
|
-
|
|
82
|
+
'1.2.840.10008.1.2.1': {
|
|
54
83
|
transcodeOp: transcodeOp.none,
|
|
55
|
-
alias:
|
|
84
|
+
alias: 'uncompressed',
|
|
56
85
|
},
|
|
57
|
-
|
|
86
|
+
'1.2.840.10008.1.2.2': {
|
|
58
87
|
transcodeOp: transcodeOp.decode,
|
|
59
|
-
alias:
|
|
88
|
+
alias: 'uncompressed',
|
|
60
89
|
},
|
|
61
|
-
|
|
90
|
+
'1.2.840.10008.1.2.4.50': {
|
|
62
91
|
transcodeOp: transcodeOp.decode,
|
|
63
|
-
alias:
|
|
92
|
+
alias: 'jpeg',
|
|
64
93
|
},
|
|
65
|
-
|
|
94
|
+
'1.2.840.10008.1.2.4.57': {
|
|
66
95
|
transcodeOp: transcodeOp.decode,
|
|
67
|
-
alias:
|
|
96
|
+
alias: 'jls',
|
|
68
97
|
},
|
|
69
|
-
|
|
98
|
+
'1.2.840.10008.1.2.4.70': {
|
|
70
99
|
transcodeOp: transcodeOp.decode,
|
|
71
|
-
alias:
|
|
100
|
+
alias: 'jpll',
|
|
72
101
|
},
|
|
73
|
-
|
|
102
|
+
'1.2.840.10008.1.2.4.80': {
|
|
74
103
|
transcodeOp: transcodeOp.decode,
|
|
75
|
-
alias:
|
|
104
|
+
alias: 'jls',
|
|
76
105
|
},
|
|
77
|
-
|
|
106
|
+
'1.2.840.10008.1.2.4.90': {
|
|
78
107
|
transcodeOp: transcodeOp.decode,
|
|
79
|
-
alias:
|
|
108
|
+
alias: 'jp2',
|
|
80
109
|
},
|
|
81
|
-
|
|
110
|
+
'1.2.840.10008.1.2.4.91': {
|
|
82
111
|
transcodeOp: transcodeOp.decode,
|
|
83
|
-
alias:
|
|
112
|
+
alias: 'jp2',
|
|
84
113
|
},
|
|
85
|
-
|
|
114
|
+
'3.2.840.10008.1.2.4.96': {
|
|
86
115
|
transcodeOp: transcodeOp.decode,
|
|
87
|
-
alias:
|
|
116
|
+
alias: 'jhc',
|
|
88
117
|
},
|
|
89
|
-
|
|
118
|
+
'1.2.840.10008.1.2.4.202': {
|
|
90
119
|
transcodeOp: transcodeOp.decode,
|
|
91
|
-
alias:
|
|
120
|
+
alias: 'jhc',
|
|
92
121
|
},
|
|
93
|
-
|
|
122
|
+
'1.2.840.10008.1.2.5': {
|
|
94
123
|
transcodeOp: transcodeOp.decode,
|
|
95
|
-
alias:
|
|
124
|
+
alias: 'rle',
|
|
96
125
|
},
|
|
97
126
|
};
|
|
98
127
|
|
|
@@ -103,11 +132,10 @@ 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
|
-
return undefined;
|
|
135
|
+
const destinationTranscoderEntry =
|
|
136
|
+
transcodeDestinationMap[id] ||
|
|
137
|
+
Object.values(transcodeDestinationMap).find(value => value.transferSyntaxUid === id);
|
|
138
|
+
return destinationTranscoderEntry;
|
|
111
139
|
}
|
|
112
140
|
|
|
113
141
|
/**
|
|
@@ -116,7 +144,11 @@ function getDestinationTranscoder(id) {
|
|
|
116
144
|
* @param {*} transferSyntaxUid
|
|
117
145
|
* @returns
|
|
118
146
|
*/
|
|
119
|
-
function getTranscoder(
|
|
147
|
+
function getTranscoder(
|
|
148
|
+
transferSyntaxUid,
|
|
149
|
+
{ contentType: greyContentType, colorContentType },
|
|
150
|
+
samplesPerPixel
|
|
151
|
+
) {
|
|
120
152
|
const contentType = samplesPerPixel === 3 ? colorContentType : greyContentType;
|
|
121
153
|
const sourceTranscoder = transcodeSourceMap[transferSyntaxUid];
|
|
122
154
|
const destinationTranscoder = getDestinationTranscoder(contentType);
|
|
@@ -126,8 +158,9 @@ function getTranscoder(transferSyntaxUid, { contentType: greyContentType, colorC
|
|
|
126
158
|
|
|
127
159
|
return {
|
|
128
160
|
transferSyntaxUid: destinationTranscoder.transferSyntaxUid,
|
|
129
|
-
transcodeOp: sourceTranscoder.transcodeOp | destinationTranscoder.transcodeOp,
|
|
161
|
+
transcodeOp: sourceTranscoder.transcodeOp | destinationTranscoder.transcodeOp,
|
|
130
162
|
alias: sourceTranscoder.alias,
|
|
163
|
+
beforeEncode: destinationTranscoder.beforeEncode,
|
|
131
164
|
};
|
|
132
165
|
}
|
|
133
166
|
|
|
@@ -140,7 +173,7 @@ function shouldTranscodeImageFrame(id, options, samplesPerPixel) {
|
|
|
140
173
|
const { recompress: recompressGrey, recompressColor } = options;
|
|
141
174
|
const recompress = samplesPerPixel === 3 ? recompressColor : recompressGrey;
|
|
142
175
|
if (!recompress) {
|
|
143
|
-
console.verbose(
|
|
176
|
+
console.verbose('Not transcoding because recompress not set');
|
|
144
177
|
return false;
|
|
145
178
|
}
|
|
146
179
|
|
|
@@ -148,12 +181,21 @@ function shouldTranscodeImageFrame(id, options, samplesPerPixel) {
|
|
|
148
181
|
const transcoder = getTranscoder(transferSyntaxUid, options, samplesPerPixel);
|
|
149
182
|
const validTranscoder = transcoder && transcoder.transferSyntaxUid;
|
|
150
183
|
if (!validTranscoder) {
|
|
151
|
-
console.verbose(
|
|
184
|
+
console.verbose(
|
|
185
|
+
'Not transcoding because no decoder found for',
|
|
186
|
+
transferSyntaxUid,
|
|
187
|
+
samplesPerPixel
|
|
188
|
+
);
|
|
152
189
|
return false;
|
|
153
190
|
}
|
|
154
|
-
const validRecompress = recompress.includes(
|
|
191
|
+
const validRecompress = recompress.includes('true') || recompress.includes(transcoder.alias);
|
|
155
192
|
if (!validRecompress) {
|
|
156
|
-
console.verbose(
|
|
193
|
+
console.verbose(
|
|
194
|
+
'Not transcoding because recompress',
|
|
195
|
+
recompress,
|
|
196
|
+
'does not include',
|
|
197
|
+
transcoder.alias
|
|
198
|
+
);
|
|
157
199
|
return false;
|
|
158
200
|
}
|
|
159
201
|
|
|
@@ -175,8 +217,12 @@ function shouldThumbUseTranscoded(id, options) {
|
|
|
175
217
|
function isValidTranscoder() {
|
|
176
218
|
const { transferSyntaxUid } = id;
|
|
177
219
|
// Ignore the samples per pixel for thumbnails
|
|
178
|
-
const transcoder = getTranscoder(transferSyntaxUid, options,
|
|
179
|
-
const result =
|
|
220
|
+
const transcoder = getTranscoder(transferSyntaxUid, options, 'thumbnail');
|
|
221
|
+
const result =
|
|
222
|
+
transcoder &&
|
|
223
|
+
transcoder.transferSyntaxUid &&
|
|
224
|
+
options.recompress.includes(transcoder.alias) &&
|
|
225
|
+
options.recompressThumb.includes(transcoder.alias);
|
|
180
226
|
|
|
181
227
|
return result;
|
|
182
228
|
}
|
|
@@ -184,21 +230,12 @@ function shouldThumbUseTranscoded(id, options) {
|
|
|
184
230
|
return isValidTranscoder();
|
|
185
231
|
}
|
|
186
232
|
|
|
187
|
-
function transcodeLog(options, msg, error =
|
|
233
|
+
function transcodeLog(options, msg, error = '') {
|
|
188
234
|
if (options.verbose) {
|
|
189
235
|
console.log(`\x1b[34m${msg}\x1b[0m`, error);
|
|
190
236
|
}
|
|
191
237
|
}
|
|
192
238
|
|
|
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
239
|
function scale(imageFrame, imageInfo) {
|
|
203
240
|
const { rows, columns, bitsPerPixel, pixelRepresentation, samplesPerPixel } = imageInfo;
|
|
204
241
|
let ArrayConstructor = Float32Array;
|
|
@@ -243,12 +280,13 @@ function scale(imageFrame, imageInfo) {
|
|
|
243
280
|
async function generateLossyImage(id, decoded, options) {
|
|
244
281
|
if (!options.alternate) return;
|
|
245
282
|
if (!decoded?.imageFrame) return;
|
|
283
|
+
console.verbose('Writing alternate thumbnail');
|
|
246
284
|
|
|
247
285
|
try {
|
|
248
286
|
let { imageFrame, imageInfo } = decoded;
|
|
249
287
|
const lossyId = {
|
|
250
288
|
...id,
|
|
251
|
-
imageFrameRootPath: id.imageFrameRootPath.replace(
|
|
289
|
+
imageFrameRootPath: id.imageFrameRootPath.replace('frames', options.alternateName),
|
|
252
290
|
transferSyntaxUid: transcodeDestinationMap.jhc.transferSyntaxUid,
|
|
253
291
|
};
|
|
254
292
|
|
|
@@ -263,31 +301,40 @@ async function generateLossyImage(id, decoded, options) {
|
|
|
263
301
|
imageInfo = scaled.imageInfo;
|
|
264
302
|
}
|
|
265
303
|
|
|
266
|
-
if (options.alternate ===
|
|
267
|
-
lossyId.transferSyntaxUid = transcodeDestinationMap[
|
|
268
|
-
} else if (options.alternate ===
|
|
304
|
+
if (options.alternate === 'jls') {
|
|
305
|
+
lossyId.transferSyntaxUid = transcodeDestinationMap['jls-lossy'].transferSyntaxUid;
|
|
306
|
+
} else if (options.alternate === 'jlsLossless') {
|
|
269
307
|
lossyId.transferSyntaxUid = transcodeDestinationMap.jls.transferSyntaxUid;
|
|
270
308
|
lossy = false;
|
|
271
|
-
} else if (options.alternate ===
|
|
309
|
+
} else if (options.alternate === 'jhcLossless') {
|
|
272
310
|
lossy = false;
|
|
273
311
|
}
|
|
274
312
|
|
|
275
313
|
const encodeOptions = {
|
|
276
|
-
beforeEncode: beforeEncode
|
|
277
|
-
lossy,
|
|
278
|
-
}),
|
|
314
|
+
beforeEncode: transcodeDestinationMap[options.alternate]?.beforeEncode,
|
|
279
315
|
};
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
316
|
+
|
|
317
|
+
const lossyEncoding = await dicomCodec.encode(
|
|
318
|
+
imageFrame,
|
|
319
|
+
imageInfo,
|
|
320
|
+
lossyId.transferSyntaxUid,
|
|
321
|
+
encodeOptions
|
|
322
|
+
);
|
|
323
|
+
console.verbose(
|
|
324
|
+
'Encoded alternate',
|
|
325
|
+
lossyId.transferSyntaxUid,
|
|
326
|
+
'of size',
|
|
327
|
+
lossyEncoding.imageFrame.length
|
|
328
|
+
);
|
|
329
|
+
|
|
283
330
|
return { id: lossyId, imageFrame: lossyEncoding.imageFrame };
|
|
284
331
|
} catch (e) {
|
|
285
|
-
console.warn(
|
|
332
|
+
console.warn('Unable to create alternate:', e);
|
|
286
333
|
}
|
|
287
334
|
}
|
|
288
335
|
|
|
289
336
|
function isPalette(dataSet) {
|
|
290
|
-
return dataSet.string(Tags.RawPhotometricInterpretation) ===
|
|
337
|
+
return dataSet.string(Tags.RawPhotometricInterpretation) === 'PALETTE COLOR';
|
|
291
338
|
}
|
|
292
339
|
|
|
293
340
|
/**
|
|
@@ -305,8 +352,12 @@ async function transcodeImageFrame(id, targetIdSrc, imageFrame, dataSet, options
|
|
|
305
352
|
let result = {};
|
|
306
353
|
|
|
307
354
|
const samplesPerPixel = dataSet.uint16(Tags.RawSamplesPerPixel);
|
|
308
|
-
const planarConfiguration = dataSet.uint16(
|
|
309
|
-
if (
|
|
355
|
+
const planarConfiguration = dataSet.uint16('x00280006');
|
|
356
|
+
if (
|
|
357
|
+
!shouldTranscodeImageFrame(id, options, samplesPerPixel) ||
|
|
358
|
+
planarConfiguration === 1 ||
|
|
359
|
+
isPalette(dataSet)
|
|
360
|
+
) {
|
|
310
361
|
console.verbose("Shouldn't transcode");
|
|
311
362
|
return {
|
|
312
363
|
id,
|
|
@@ -319,7 +370,7 @@ async function transcodeImageFrame(id, targetIdSrc, imageFrame, dataSet, options
|
|
|
319
370
|
|
|
320
371
|
// Don't transcode if not required
|
|
321
372
|
if (targetId.transferSyntaxUid !== transcoder.transferSyntaxUid && !options.forceTranscode) {
|
|
322
|
-
console.verbose(
|
|
373
|
+
console.verbose('Image is already in', targetId.transferSyntaxUid, 'not transcoding');
|
|
323
374
|
return {
|
|
324
375
|
id,
|
|
325
376
|
imageFrame,
|
|
@@ -327,33 +378,49 @@ async function transcodeImageFrame(id, targetIdSrc, imageFrame, dataSet, options
|
|
|
327
378
|
};
|
|
328
379
|
}
|
|
329
380
|
|
|
330
|
-
console.verbose(
|
|
381
|
+
console.verbose('Transcoding to', transcoder.transferSyntaxUid);
|
|
331
382
|
|
|
332
383
|
const imageInfo = getImageInfo(dataSet);
|
|
333
384
|
let done = false;
|
|
334
|
-
let processResultMsg =
|
|
385
|
+
let processResultMsg = '';
|
|
335
386
|
const encodeOptions = {
|
|
336
|
-
beforeEncode: beforeEncode
|
|
337
|
-
lossy: options.lossy,
|
|
338
|
-
}),
|
|
387
|
+
beforeEncode: transcoder.beforeEncode,
|
|
339
388
|
};
|
|
340
389
|
let decoded;
|
|
341
390
|
|
|
342
391
|
try {
|
|
343
392
|
switch (transcoder.transcodeOp) {
|
|
344
393
|
case transcodeOp.transcode:
|
|
345
|
-
transcodeLog(
|
|
394
|
+
transcodeLog(
|
|
395
|
+
options,
|
|
396
|
+
`Full transcoding image from \x1b[43m${id.transferSyntaxUid}\x1b[0m to \x1b[43m${targetId.transferSyntaxUid}\x1b[0m`
|
|
397
|
+
);
|
|
346
398
|
|
|
347
399
|
decoded = await dicomCodec.decode(imageFrame, imageInfo, id.transferSyntaxUid);
|
|
348
|
-
result = await dicomCodec.encode(
|
|
349
|
-
|
|
350
|
-
|
|
400
|
+
result = await dicomCodec.encode(
|
|
401
|
+
decoded.imageFrame,
|
|
402
|
+
decoded.imageInfo,
|
|
403
|
+
targetId.transferSyntaxUid,
|
|
404
|
+
encodeOptions
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
console.verbose(
|
|
408
|
+
'transcoded image to',
|
|
409
|
+
targetId.transferSyntaxUid,
|
|
410
|
+
'of size',
|
|
411
|
+
result.imageFrame.length
|
|
412
|
+
);
|
|
351
413
|
processResultMsg = `Transcoding finished`;
|
|
352
414
|
break;
|
|
353
415
|
case transcodeOp.encode:
|
|
354
416
|
transcodeLog(options, `Encoding image to \x1b[43m${targetId.transferSyntaxUid}\x1b[0m`);
|
|
355
417
|
|
|
356
|
-
result = await dicomCodec.encode(
|
|
418
|
+
result = await dicomCodec.encode(
|
|
419
|
+
imageFrame,
|
|
420
|
+
imageInfo,
|
|
421
|
+
targetId.transferSyntaxUid,
|
|
422
|
+
encodeOptions
|
|
423
|
+
);
|
|
357
424
|
|
|
358
425
|
processResultMsg = `Encoding finished`;
|
|
359
426
|
break;
|
|
@@ -364,12 +431,12 @@ async function transcodeImageFrame(id, targetIdSrc, imageFrame, dataSet, options
|
|
|
364
431
|
processResultMsg = `Decoding finished`;
|
|
365
432
|
break;
|
|
366
433
|
default:
|
|
367
|
-
processResultMsg =
|
|
434
|
+
processResultMsg = '';
|
|
368
435
|
}
|
|
369
436
|
|
|
370
437
|
done = !!result.imageFrame;
|
|
371
438
|
} catch (e) {
|
|
372
|
-
transcodeLog(options,
|
|
439
|
+
transcodeLog(options, 'Failed to transcode image', e);
|
|
373
440
|
}
|
|
374
441
|
|
|
375
442
|
// recover transfer syntax
|
|
@@ -379,7 +446,7 @@ async function transcodeImageFrame(id, targetIdSrc, imageFrame, dataSet, options
|
|
|
379
446
|
|
|
380
447
|
// log final message only if resultMsg is present
|
|
381
448
|
if (processResultMsg) {
|
|
382
|
-
processResultMsg += done ?
|
|
449
|
+
processResultMsg += done ? ' successfully' : ' unsuccessfully';
|
|
383
450
|
transcodeLog(options, processResultMsg);
|
|
384
451
|
}
|
|
385
452
|
|
|
@@ -437,7 +504,7 @@ function transcodeMetadata(metadata, id, options) {
|
|
|
437
504
|
|
|
438
505
|
if (result[Tags.AvailableTransferSyntaxUID]) {
|
|
439
506
|
Tags.setValue(result, Tags.AvailableTransferSyntaxUID, transcodedId.transferSyntaxUid);
|
|
440
|
-
console.verbose(
|
|
507
|
+
console.verbose('Apply available tsuid', transcodeId.transferSyntaxUid);
|
|
441
508
|
}
|
|
442
509
|
|
|
443
510
|
return result;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
const { Stats } = require(
|
|
1
|
+
const { Stats } = require('@radicalimaging/static-wado-util');
|
|
2
2
|
|
|
3
|
-
const getNumberOfFrames = require(
|
|
4
|
-
const getUncompressedImageFrame = require(
|
|
5
|
-
const getEncapsulatedImageFrame = require(
|
|
6
|
-
const { isVideo } = require(
|
|
3
|
+
const getNumberOfFrames = require('./getNumberOfFrames');
|
|
4
|
+
const getUncompressedImageFrame = require('./getUncompressedImageFrame');
|
|
5
|
+
const getEncapsulatedImageFrame = require('./getEncapsulatedImageFrame');
|
|
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
|
-
const getFrameSize =
|
|
11
|
-
const rows = dataSet.uint16(
|
|
12
|
-
const columns = dataSet.uint16(
|
|
13
|
-
const samplesPerPixel = dataSet.uint16(
|
|
14
|
-
const bitsAllocated = dataSet.uint16(
|
|
11
|
+
const getFrameSize = dataSet => {
|
|
12
|
+
const rows = dataSet.uint16('x00280010');
|
|
13
|
+
const columns = dataSet.uint16('x00280011');
|
|
14
|
+
const samplesPerPixel = dataSet.uint16('x00280002');
|
|
15
|
+
const bitsAllocated = dataSet.uint16('x00280100');
|
|
15
16
|
return (rows * columns * samplesPerPixel * bitsAllocated) / 8;
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -30,11 +31,21 @@ 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('Image Write', `Write image frame ${frameIndex + 1}`, 5000);
|
|
36
42
|
} else {
|
|
37
|
-
const uncompressedFrame = getUncompressedImageFrame(
|
|
43
|
+
const uncompressedFrame = getUncompressedImageFrame(
|
|
44
|
+
dataSet,
|
|
45
|
+
attr,
|
|
46
|
+
frameIndex,
|
|
47
|
+
uncompressedFrameSize
|
|
48
|
+
);
|
|
38
49
|
BulkDataURI = await callback.imageFrame(uncompressedFrame, { dataSet });
|
|
39
50
|
}
|
|
40
51
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const { Tags } = require(
|
|
2
|
-
const getVR = require(
|
|
3
|
-
const getValue = require(
|
|
1
|
+
const { Tags } = require('@radicalimaging/static-wado-util');
|
|
2
|
+
const getVR = require('./getVR');
|
|
3
|
+
const getValue = require('./getValue');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Get dataset.
|
|
@@ -28,7 +28,7 @@ async function getDataSet(dataSet, callback, optionsOrig, parentAttr = undefined
|
|
|
28
28
|
options = { ...options, SpecificCharacterSet };
|
|
29
29
|
}
|
|
30
30
|
const attr = dataSet.elements[tag];
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
await attributeToJS(metadata, tag, dataSet, attr, callback, options, parentAttr);
|
|
33
33
|
}
|
|
34
34
|
if (metadata[Tags.TransferSyntaxUID]) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const dicomParser = require(
|
|
1
|
+
const dicomParser = require('dicom-parser');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Function to deal with extracting an image frame from an encapsulated data set.
|
|
@@ -12,12 +12,24 @@ const getEncapsulatedImageFrame = (dataSet, attr, frameIndex, framesAreFragmente
|
|
|
12
12
|
|
|
13
13
|
// Empty basic offset table
|
|
14
14
|
if (framesAreFragmented) {
|
|
15
|
-
const basicOffsetTable = dicomParser.createJPEGBasicOffsetTable(
|
|
15
|
+
const basicOffsetTable = dicomParser.createJPEGBasicOffsetTable(
|
|
16
|
+
dataSet,
|
|
17
|
+
dataSet.elements.x7fe00010
|
|
18
|
+
);
|
|
16
19
|
|
|
17
|
-
return dicomParser.readEncapsulatedImageFrame(
|
|
20
|
+
return dicomParser.readEncapsulatedImageFrame(
|
|
21
|
+
dataSet,
|
|
22
|
+
dataSet.elements.x7fe00010,
|
|
23
|
+
frameIndex,
|
|
24
|
+
basicOffsetTable
|
|
25
|
+
);
|
|
18
26
|
}
|
|
19
27
|
|
|
20
|
-
return dicomParser.readEncapsulatedPixelDataFromFragments(
|
|
28
|
+
return dicomParser.readEncapsulatedPixelDataFromFragments(
|
|
29
|
+
dataSet,
|
|
30
|
+
dataSet.elements.x7fe00010,
|
|
31
|
+
frameIndex
|
|
32
|
+
);
|
|
21
33
|
};
|
|
22
34
|
|
|
23
35
|
module.exports = getEncapsulatedImageFrame;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const getNumberOfFrames =
|
|
1
|
+
const getNumberOfFrames = dataSet => {
|
|
2
2
|
if (dataSet.elements.x00280008) {
|
|
3
|
-
const numberOfFramesString = dataSet.string(
|
|
3
|
+
const numberOfFramesString = dataSet.string('x00280008');
|
|
4
4
|
if (numberOfFramesString.length === 0) {
|
|
5
5
|
return 1;
|
|
6
6
|
}
|
package/lib/operation/getVR.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
const { dataDictionary } = require(
|
|
1
|
+
const { dataDictionary } = require('@radicalimaging/static-wado-util');
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const nullVrTags = new Set();
|
|
4
|
+
nullVrTags.add('FFFEE00D');
|
|
5
|
+
|
|
6
|
+
const getVR = attr => {
|
|
4
7
|
if (attr.vr) {
|
|
5
8
|
return attr.vr;
|
|
6
9
|
}
|
|
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;
|