@radicalimaging/static-wado-creator 0.6.4

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.
Files changed (49) hide show
  1. package/README.md +285 -0
  2. package/bin/mkdicomweb.js +9 -0
  3. package/lib/DeleteStudy.js +14 -0
  4. package/lib/RejectInstance.js +17 -0
  5. package/lib/StaticWado.js +206 -0
  6. package/lib/createMain.js +14 -0
  7. package/lib/deleteMain.js +12 -0
  8. package/lib/dictionary/Tags.js +111 -0
  9. package/lib/dictionary/dataDictionary.js +22268 -0
  10. package/lib/groupMain.js +14 -0
  11. package/lib/index.js +8 -0
  12. package/lib/instanceMain.js +13 -0
  13. package/lib/metadataMain.js +14 -0
  14. package/lib/mkdicomwebConfig.js +149 -0
  15. package/lib/model/TagLists.js +151 -0
  16. package/lib/model/uids.js +42 -0
  17. package/lib/operation/InstanceDeduplicate.js +100 -0
  18. package/lib/operation/ScanStudy.js +20 -0
  19. package/lib/operation/StudyData.js +416 -0
  20. package/lib/operation/ThumbnailService.js +184 -0
  21. package/lib/operation/adapter/asyncIterableToBuffer.js +112 -0
  22. package/lib/operation/adapter/decodeImage.js +9 -0
  23. package/lib/operation/adapter/getImageInfo.js +21 -0
  24. package/lib/operation/adapter/transcodeImage.js +303 -0
  25. package/lib/operation/extractImageFrames.js +41 -0
  26. package/lib/operation/getDataSet.js +60 -0
  27. package/lib/operation/getEncapsulatedImageFrame.js +23 -0
  28. package/lib/operation/getNumberOfFrames.js +13 -0
  29. package/lib/operation/getUncompressedImageFrame.js +7 -0
  30. package/lib/operation/getVR.js +16 -0
  31. package/lib/operation/getValue.js +193 -0
  32. package/lib/program/index.js +15 -0
  33. package/lib/rejectMain.js +12 -0
  34. package/lib/util/IdCreator.js +25 -0
  35. package/lib/util/adaptProgramOpts.js +58 -0
  36. package/lib/util/compressionOptionParser.js +13 -0
  37. package/lib/writer/CompleteStudyWriter.js +88 -0
  38. package/lib/writer/DeduplicateWriter.js +32 -0
  39. package/lib/writer/ExpandUriPath.js +15 -0
  40. package/lib/writer/HashDataWriter.js +59 -0
  41. package/lib/writer/ImageFrameWriter.js +31 -0
  42. package/lib/writer/JSONWriter.js +18 -0
  43. package/lib/writer/MultipartAttribute.js +8 -0
  44. package/lib/writer/MultipartHeader.js +14 -0
  45. package/lib/writer/ThumbnailWriter.js +21 -0
  46. package/lib/writer/VideoWriter.js +52 -0
  47. package/lib/writer/WriteMultipart.js +22 -0
  48. package/lib/writer/WriteStream.js +51 -0
  49. package/package.json +75 -0
@@ -0,0 +1,112 @@
1
+ const { Stats } = require("@radicalimaging/static-wado-util");
2
+ /* eslint "no-param-reassign": "off" */
3
+
4
+ const handler = {
5
+ get(obj, key) {
6
+ const ikey = parseInt(key);
7
+ if (ikey == key) return obj.index_get(ikey);
8
+ const handlerFunc = handler[key];
9
+ if (handlerFunc && key != "get") return handlerFunc(obj, key);
10
+ if (!obj._keys[key]) {
11
+ console.log("New call to", key);
12
+ obj._keys[key] = true;
13
+ }
14
+ return obj[key];
15
+ },
16
+
17
+ length: (obj) => obj.combinedLength,
18
+ };
19
+
20
+ const StreamingFunctions = {
21
+ addChunks(chunks) {
22
+ if (!this.chunks) this.chunks = [];
23
+ chunks.forEach((chunk) => this.chunks.push(chunk));
24
+ this.combinedLength = 0;
25
+ let i = 0;
26
+ this.chunks.forEach((chunk) => {
27
+ chunk.start = this.combinedLength;
28
+ chunk.i = i;
29
+ i += 1;
30
+ this.combinedLength += chunk.length;
31
+ });
32
+ this.lastChunk = this.chunks[0];
33
+ },
34
+
35
+ index_get(ikey) {
36
+ const found = this.findChunk(ikey);
37
+ if (!found) throw Error(`index ${ikey} not found between 0..${this.combinedLength}`);
38
+ return found[ikey - found.start];
39
+ },
40
+
41
+ findChunk(ikey) {
42
+ let i = (this.lastChunk.start <= ikey && this.lastChunk.i) || 0;
43
+ while (i < this.chunks.length) {
44
+ const chunk = this.chunks[i];
45
+ if (ikey >= chunk.start && ikey < chunk.start + chunk.length) {
46
+ this.lastChunk = chunk;
47
+ return chunk;
48
+ }
49
+ i += 1;
50
+ }
51
+ return undefined;
52
+ },
53
+
54
+ slice(start, end) {
55
+ const buflen = end - start;
56
+ const ret = Buffer.alloc(buflen);
57
+ let i = 0;
58
+ while (i < buflen) {
59
+ const chunk = this.findChunk(start + i);
60
+ const chunkI = start + i - chunk.start;
61
+ const useLen = Math.min(buflen, chunk.length - chunkI);
62
+ chunk.copy(ret, i, chunkI, chunkI + useLen);
63
+ i += useLen;
64
+ }
65
+ return ret;
66
+ },
67
+
68
+ hexSlice(start, end) {
69
+ return this.slice(start, end).hexSlice();
70
+ },
71
+
72
+ /** The internal node copy function is a native that directly accesses internal class details, so over-ride it.
73
+ * TODO: Make this efficient by using the internal copy function when available rather than copy one at a time.
74
+ */
75
+ copy(target, targetStart = 0, srcStart = 0, srcEnd = 0) {
76
+ const { length } = target;
77
+ const srcLength = (srcEnd === undefined && Math.min(this.length, srcEnd)) || this.length;
78
+ const copied = 0;
79
+ while (targetStart < length && srcStart < srcLength) {
80
+ target[targetStart] = this[srcStart];
81
+ targetStart += 1;
82
+ srcStart += 1;
83
+ }
84
+ return copied;
85
+ },
86
+
87
+ _keys: { then: true, lastChunk: true, chunks: true },
88
+ };
89
+
90
+ Object.keys(StreamingFunctions).forEach((key) => {
91
+ StreamingFunctions._keys[key] = true;
92
+ });
93
+
94
+ const StreamingBuffer = (chunks) => {
95
+ const buf = Buffer.from("NotUsed");
96
+ Object.assign(buf, StreamingFunctions);
97
+ buf.addChunks(chunks);
98
+ return new Proxy(buf, handler);
99
+ };
100
+
101
+ const asyncIteratorToBuffer = async (readable) => {
102
+ if (ArrayBuffer.isView(readable)) return readable;
103
+ const chunks = [];
104
+ for await (const chunk of readable) {
105
+ chunks.push(chunk);
106
+ Stats.BufferStats.add("Read Async", `Read async buffer ${chunks.length}`, 1024);
107
+ }
108
+ Stats.BufferStats.reset();
109
+ return StreamingBuffer(chunks);
110
+ };
111
+
112
+ module.exports = asyncIteratorToBuffer;
@@ -0,0 +1,9 @@
1
+ const dicomCodec = require("@cornerstonejs/dicom-codec");
2
+ const getImageInfo = require("./getImageInfo");
3
+
4
+ async function decodeImage(imageFrame, dataset, transferSyntaxUid) {
5
+ const imageInfo = getImageInfo(dataset);
6
+ return dicomCodec.decode(imageFrame, imageInfo, transferSyntaxUid);
7
+ }
8
+
9
+ module.exports = decodeImage;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Minimum image info data to be used on transcode process by dicom-codec api.
3
+ */
4
+ function getImageInfo(dataSet) {
5
+ const rows = dataSet.uint16("x00280010");
6
+ const columns = dataSet.uint16("x00280011");
7
+ const bitsAllocated = dataSet.uint16("x00280100");
8
+ const samplesPerPixel = dataSet.uint16("x00280002");
9
+ const pixelRepresentation = dataSet.uint16("x00280103") || 0; // not yet being used.
10
+
11
+ return {
12
+ bitsAllocated,
13
+ samplesPerPixel,
14
+ rows, // Number with the image rows/height
15
+ columns, // Number with the image columns/width,
16
+ signed: pixelRepresentation === 1,
17
+ pixelRepresentation,
18
+ };
19
+ }
20
+
21
+ module.exports = getImageInfo;
@@ -0,0 +1,303 @@
1
+ const dicomCodec = require("@cornerstonejs/dicom-codec");
2
+ const Tags = require("../../dictionary/Tags");
3
+ const getImageInfo = require("./getImageInfo");
4
+
5
+ const transcodeOp = {
6
+ none: 0,
7
+ decode: 1,
8
+ encode: 2,
9
+ transcode: 3, // decode and then encode
10
+ };
11
+
12
+ /**
13
+ * Static mapping for transcoding decoder destination names/uids - currently only lei, jls and jls-lossy appear to work.
14
+ */
15
+ const transcodeDestinationMap = {
16
+ lei: {
17
+ transferSyntaxUid: "1.2.840.10008.1.2.1",
18
+ transcodeOp: transcodeOp.none
19
+ },
20
+ jls: {
21
+ transferSyntaxUid: "1.2.840.10008.1.2.4.80",
22
+ transcodeOp: transcodeOp.encode,
23
+ },
24
+ "jls-lossy": {
25
+ transferSyntaxUid: "1.2.840.10008.1.2.4.81",
26
+ transcodeOp: transcodeOp.encode,
27
+ },
28
+ jp2: {
29
+ transferSyntaxUid: "1.2.840.10008.1.2.4.90",
30
+ transcodeOp: transcodeOp.encode,
31
+ },
32
+ "jp2-lossy": {
33
+ transferSyntaxUid: "1.2.840.10008.1.2.4.91",
34
+ transcodeOp: transcodeOp.encode,
35
+ },
36
+ };
37
+
38
+ const transcodeSourceMap = {
39
+ "1.2.840.10008.1.2": {
40
+ transcodeOp: transcodeOp.decode,
41
+ alias: "uncompressed",
42
+ },
43
+ "1.2.840.10008.1.2.1": {
44
+ transcodeOp: transcodeOp.none,
45
+ alias: "uncompressed",
46
+ },
47
+ "1.2.840.10008.1.2.2": {
48
+ transcodeOp: transcodeOp.decode,
49
+ alias: "uncompressed",
50
+ },
51
+ "1.2.840.10008.1.2.4.57": {
52
+ transcodeOp: transcodeOp.decode,
53
+ alias: "jpeglossless",
54
+ },
55
+ "1.2.840.10008.1.2.4.70": {
56
+ transcodeOp: transcodeOp.decode,
57
+ alias: "jpeglossless",
58
+ },
59
+ "1.2.840.10008.1.2.4.90": {
60
+ transcodeOp: transcodeOp.decode,
61
+ alias: "jp2",
62
+ },
63
+ "1.2.840.10008.1.2.4.91": {
64
+ transcodeOp: transcodeOp.decode,
65
+ alias: "jp2",
66
+ },
67
+ "1.2.840.10008.1.2.5": {
68
+ transcodeOp: transcodeOp.decode,
69
+ alias: "rle",
70
+ },
71
+ };
72
+
73
+ /**
74
+ * Get an existing destination transcoder from destination map.
75
+ *
76
+ * @param {*} id Destination type to compress to
77
+ * @returns A partial transcoder definition. Otherwise it returns undefined.
78
+ */
79
+ function getDestinationTranscoder(id) {
80
+ const destinationTranscoderEntry = Object.entries(transcodeDestinationMap).find(([key, value]) => key === id || value.transferSyntaxUid === id);
81
+ if (destinationTranscoderEntry) {
82
+ return destinationTranscoderEntry[1];
83
+ }
84
+ return undefined;
85
+ }
86
+
87
+ /**
88
+ * Return a merged source and destination transcoder for the given transferSyntaxUid. Otherwise it returns undefined.
89
+ *
90
+ * @param {*} transferSyntaxUid
91
+ * @returns
92
+ */
93
+ function getTranscoder(transferSyntaxUid, { contentType, verbose }) {
94
+ if (verbose) {
95
+ console.log(`getTranscoder for ${transferSyntaxUid}`);
96
+ }
97
+ const sourceTranscoder = transcodeSourceMap[transferSyntaxUid];
98
+ const destinationTranscoder = getDestinationTranscoder(contentType);
99
+ if (!sourceTranscoder || !destinationTranscoder) {
100
+ return undefined;
101
+ }
102
+
103
+ return {
104
+ transferSyntaxUid: destinationTranscoder.transferSyntaxUid,
105
+ transcodeOp: sourceTranscoder.transcodeOp | destinationTranscoder.transcodeOp, // eslint-disable-line no-bitwise
106
+ alias: sourceTranscoder.alias,
107
+ };
108
+ }
109
+
110
+
111
+ /**
112
+ * Tell whether given id contain transferSyntaxUid which can be transcoded or not.
113
+ * @param {*} id object containing transferSyntaxUid
114
+ * @param {*} options runner options
115
+ */
116
+ function shouldTranscodeImageFrame(id, options) {
117
+ if (!options.recompress) {
118
+ return false;
119
+ }
120
+
121
+ function isValidTranscoder() {
122
+ const { transferSyntaxUid } = id;
123
+ const transcoder = getTranscoder(transferSyntaxUid, options);
124
+ return transcoder && transcoder.transferSyntaxUid && options.recompress.includes(transcoder.alias);
125
+ }
126
+
127
+ return isValidTranscoder();
128
+ }
129
+
130
+ /**
131
+ * It tells if we should use transcoded image to generate thumb or not.
132
+ *
133
+ * @param {*} id
134
+ * @param {*} options
135
+ * @returns True if transcoder.alias is present on intersection of recompressThumb and recompress options.
136
+ */
137
+ function shouldThumbUseTranscoded(id, options) {
138
+ if (!options.recompressThumb) {
139
+ return false;
140
+ }
141
+
142
+ function isValidTranscoder() {
143
+ const { transferSyntaxUid } = id;
144
+ const transcoder = getTranscoder(transferSyntaxUid, options);
145
+ const result = transcoder && transcoder.transferSyntaxUid && options.recompress.includes(transcoder.alias) && options.recompressThumb.includes(transcoder.alias);
146
+
147
+ return result;
148
+ }
149
+
150
+ return isValidTranscoder();
151
+ }
152
+
153
+ function transcodeLog(options, msg, error = "") {
154
+ if (options.verbose) {
155
+ console.log(`\x1b[34m${msg}\x1b[0m`, error);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Transcode imageFrame. It uses transcodeMap to define the target encoding based on source encoding.
161
+ *
162
+ * @param {*} id object containing transferSyntaxUid
163
+ * @param {*} targetId target object containing transferSyntaxUid
164
+ * @param {*} imageFrame data to be transcoded
165
+ * @param {*} dataSet related image frame dataset
166
+ * @param {*} options runner options
167
+ * @returns object result for transcoding operation with id and image frame.
168
+ */
169
+ async function transcodeImageFrame(id, targetIdSrc, imageFrame, dataSet, options = {}) {
170
+ let targetId = targetIdSrc;
171
+ let result = {};
172
+
173
+ if (!shouldTranscodeImageFrame(id, options)) {
174
+ return {
175
+ id,
176
+ imageFrame,
177
+ done: false,
178
+ };
179
+ }
180
+
181
+ const transcoder = getTranscoder(id.transferSyntaxUid, options);
182
+
183
+ // last chance to prevent transcoding
184
+ if (targetId.transferSyntaxUid !== transcoder.transferSyntaxUid) {
185
+ return {
186
+ id,
187
+ imageFrame,
188
+ done: false,
189
+ };
190
+ }
191
+
192
+ if (options.verbose) {
193
+ console.log("Transcoding to", transcoder.transferSyntaxUid);
194
+ }
195
+ const imageInfo = getImageInfo(dataSet);
196
+ let done = false;
197
+ let processResultMsg = "";
198
+
199
+ try {
200
+ switch (transcoder.transcodeOp) {
201
+ case transcodeOp.transcode:
202
+ transcodeLog(options, `Full transcoding image from \x1b[43m${id.transferSyntaxUid}\x1b[0m to \x1b[43m${targetId.transferSyntaxUid}\x1b[0m`);
203
+
204
+ result = await dicomCodec.transcode(imageFrame, imageInfo, id.transferSyntaxUid, targetId.transferSyntaxUid);
205
+
206
+ processResultMsg = `Transcoding finished`;
207
+ break;
208
+ case transcodeOp.encode:
209
+ transcodeLog(options, `Encoding image to \x1b[43m${targetId.transferSyntaxUid}\x1b[0m`);
210
+
211
+ result = await dicomCodec.encode(imageFrame, imageInfo, targetId.transferSyntaxUid);
212
+
213
+ processResultMsg = `Encoding finished`;
214
+ break;
215
+ case transcodeOp.decode:
216
+ transcodeLog(options, `Decoding image from \x1b[43m${id.transferSyntaxUid}\x1b[0m`);
217
+ result = await dicomCodec.decode(imageFrame, imageInfo, id.transferSyntaxUid);
218
+
219
+ processResultMsg = `Decoding finished`;
220
+ break;
221
+ default:
222
+ processResultMsg = "";
223
+ }
224
+
225
+ done = !!result.imageFrame;
226
+ } catch (e) {
227
+ transcodeLog(options, "Failed to transcode image", e);
228
+ }
229
+
230
+ // recover transfer syntax
231
+ if (!done) {
232
+ targetId = id;
233
+ }
234
+
235
+ // log final message only if resultMsg is present
236
+ if (processResultMsg) {
237
+ processResultMsg += done ? " successfully" : " unsuccessfully";
238
+ transcodeLog(options, processResultMsg);
239
+ }
240
+
241
+ const _imageFrame = result.imageFrame ?? imageFrame;
242
+
243
+ return {
244
+ id: targetId,
245
+ imageFrame: _imageFrame,
246
+ done,
247
+ };
248
+ }
249
+
250
+ /**
251
+ * Returns transcoded id object based on the given id's transferSyntaxUid. If transcoding should not occur it will return original id value.
252
+ * It does not mutate id param.
253
+ *
254
+ * @param {*} id Object that contains (but not only) data transferSyntaxUid.
255
+ * @param {*} options runner options.
256
+ * @returns Transcoded id object
257
+ */
258
+ function transcodeId(id, options) {
259
+ if (!shouldTranscodeImageFrame(id, options)) {
260
+ return id;
261
+ }
262
+
263
+ const targetId = { ...id };
264
+ const { transferSyntaxUid } = getTranscoder(id.transferSyntaxUid, options);
265
+
266
+ targetId.transferSyntaxUid = transferSyntaxUid;
267
+
268
+ return targetId;
269
+ }
270
+
271
+ /**
272
+ * Returns transcoded metadata based on the given id's transferSyntaxUid.
273
+ * Basically it changes the metadata AvailableTransferSyntaxUID tag's value to be transcodedId.transferSyntaxUid
274
+ * If transcoding should not occur it will return original metadata value.
275
+ *
276
+ * It does not mutate metadata param.
277
+ * @param {*} metadata Metadata object.
278
+ * @param {*} id Object that contains (but not only) data transferSyntaxUid.
279
+ * @param {*} options runner options.
280
+ * @returns Transcoded metadata.
281
+ */
282
+ function transcodeMetadata(metadata, id, options) {
283
+ if (!shouldTranscodeImageFrame(id, options)) {
284
+ return metadata;
285
+ }
286
+
287
+ const transcodedId = transcodeId(id, options);
288
+
289
+ const result = { ...metadata };
290
+
291
+ if (result[Tags.AvailableTransferSyntaxUID]) {
292
+ Tags.setValue(result,Tags.AvailableTransferSyntaxUID, transcodedId.transferSyntaxUid);
293
+ }
294
+
295
+ return result;
296
+ }
297
+
298
+ exports.shouldTranscodeImageFrame = shouldTranscodeImageFrame;
299
+ exports.shouldThumbUseTranscoded = shouldThumbUseTranscoded;
300
+ exports.transcodeId = transcodeId;
301
+ exports.getDestinationTranscoder = getDestinationTranscoder;
302
+ exports.transcodeImageFrame = transcodeImageFrame;
303
+ exports.transcodeMetadata = transcodeMetadata;
@@ -0,0 +1,41 @@
1
+ const getNumberOfFrames = require("./getNumberOfFrames");
2
+ const getUncompressedImageFrame = require("./getUncompressedImageFrame");
3
+ const getEncapsulatedImageFrame = require("./getEncapsulatedImageFrame");
4
+ const { isVideo } = require("../writer/VideoWriter");
5
+
6
+ const areFramesAreFragmented = (attr, numberOfFrames) => attr.encapsulatedPixelData && numberOfFrames != attr.fragments.length;
7
+
8
+ const getFrameSize = (dataSet) => {
9
+ const rows = dataSet.uint16("x00280010");
10
+ const columns = dataSet.uint16("x00280011");
11
+ const samplesPerPixel = dataSet.uint16("x00280002");
12
+ const bitsAllocated = dataSet.uint16("x00280100");
13
+ return (rows * columns * samplesPerPixel * bitsAllocated) / 8;
14
+ };
15
+
16
+ const extractImageFrames = async (dataSet, attr, vr, callback) => {
17
+ const numberOfFrames = getNumberOfFrames(dataSet);
18
+ const framesAreFragmented = areFramesAreFragmented(attr, numberOfFrames);
19
+ const uncompressedFrameSize = getFrameSize(dataSet);
20
+
21
+ const videoType = isVideo(dataSet);
22
+ if (videoType) {
23
+ // The top level function is asynchronous, so this one doesn't need an additional await
24
+ return callback.videoWriter(dataSet);
25
+ }
26
+
27
+ let BulkDataURI;
28
+
29
+ for (let frameIndex = 0; frameIndex < numberOfFrames; frameIndex++) {
30
+ if (attr.encapsulatedPixelData) {
31
+ const compressedFrame = getEncapsulatedImageFrame(dataSet, attr, frameIndex, framesAreFragmented);
32
+ BulkDataURI = await callback.imageFrame(compressedFrame, { dataSet });
33
+ } else {
34
+ const uncompressedFrame = getUncompressedImageFrame(dataSet, attr, frameIndex, uncompressedFrameSize);
35
+ BulkDataURI = await callback.imageFrame(uncompressedFrame, { dataSet });
36
+ }
37
+ }
38
+ return BulkDataURI;
39
+ };
40
+
41
+ module.exports = extractImageFrames;
@@ -0,0 +1,60 @@
1
+ const getVR = require("./getVR");
2
+ const getValue = require("./getValue");
3
+ const Tags = require("../dictionary/Tags");
4
+
5
+ /**
6
+ * Get dataset.
7
+ *
8
+ * ParentAttr is used to control root/non-root attr scenarios (such as pixel data).
9
+ *
10
+ * @param {*} dataSet
11
+ * @param {*} callback
12
+ * @param {*} options
13
+ * @param {*} parentAttr Parent reference for sequence element tags.
14
+ * @returns
15
+ */
16
+ async function getDataSet(dataSet, callback, options, parentAttr = undefined) {
17
+ const metadata = {};
18
+
19
+ // iterate over dataSet attributes in order
20
+ for (const tag in dataSet.elements) {
21
+ // Raw versions have the x in front of them
22
+ if (tag != Tags.RawTransferSyntaxUID && tag >= Tags.RawMinTag && tag < Tags.RawFirstBodyTag) {
23
+ continue;
24
+ }
25
+ const attr = dataSet.elements[tag];
26
+ /* eslint-disable-next-line no-use-before-define */
27
+ await attributeToJS(metadata, tag, dataSet, attr, callback, options, parentAttr);
28
+ }
29
+ if (metadata[Tags.TransferSyntaxUID]) {
30
+ // console.log(`Found tsuid ${JSON.stringify(metadata[Tags.TransferSyntaxUID])} assigning to ${Tags.AvailableTransferSyntaxUID}`)
31
+ metadata[Tags.AvailableTransferSyntaxUID] = metadata[Tags.TransferSyntaxUID];
32
+ delete metadata[Tags.TransferSyntaxUID];
33
+ }
34
+ return { metadata };
35
+ }
36
+
37
+ async function attributeToJS(metadataSrc, tag, dataSet, attr, callback, options, parentAttr) {
38
+ const metadata = metadataSrc;
39
+ const vr = getVR(attr);
40
+ const value = await getValue(dataSet, attr, vr, getDataSet, callback, options, parentAttr);
41
+ const key = tag.substring(1).toUpperCase();
42
+ if (value === undefined || value === null || value.length === 0) {
43
+ if (!vr) return;
44
+ metadata[key] = {
45
+ vr,
46
+ };
47
+ } else if (value.InlineBinary || value.BulkDataURI) {
48
+ metadata[key] = {
49
+ vr,
50
+ ...value,
51
+ };
52
+ } else {
53
+ metadata[key] = {
54
+ vr,
55
+ Value: value,
56
+ };
57
+ }
58
+ }
59
+
60
+ module.exports = getDataSet;
@@ -0,0 +1,23 @@
1
+ const dicomParser = require("dicom-parser");
2
+
3
+ /**
4
+ * Function to deal with extracting an image frame from an encapsulated data set.
5
+ */
6
+
7
+ const getEncapsulatedImageFrame = (dataSet, attr, frameIndex, framesAreFragmented) => {
8
+ if (dataSet.elements.x7fe00010 && dataSet.elements.x7fe00010.basicOffsetTable.length) {
9
+ // Basic Offset Table is not empty
10
+ return dicomParser.readEncapsulatedImageFrame(dataSet, dataSet.elements.x7fe00010, frameIndex);
11
+ }
12
+
13
+ // Empty basic offset table
14
+ if (framesAreFragmented) {
15
+ const basicOffsetTable = dicomParser.createJPEGBasicOffsetTable(dataSet, dataSet.elements.x7fe00010);
16
+
17
+ return dicomParser.readEncapsulatedImageFrame(dataSet, dataSet.elements.x7fe00010, frameIndex, basicOffsetTable);
18
+ }
19
+
20
+ return dicomParser.readEncapsulatedPixelDataFromFragments(dataSet, dataSet.elements.x7fe00010, frameIndex);
21
+ };
22
+
23
+ module.exports = getEncapsulatedImageFrame;
@@ -0,0 +1,13 @@
1
+ const getNumberOfFrames = (dataSet) => {
2
+ if (dataSet.elements.x00280008) {
3
+ const numberOfFramesString = dataSet.string("x00280008");
4
+ if (numberOfFramesString.length === 0) {
5
+ return 1;
6
+ }
7
+ const numberOfFrames = parseInt(numberOfFramesString);
8
+ return numberOfFrames;
9
+ }
10
+ return 1;
11
+ };
12
+
13
+ module.exports = getNumberOfFrames;
@@ -0,0 +1,7 @@
1
+ function getUncompressedImageFrame(dataSet, attr, frame, uncompressedFrameSize) {
2
+ const start = attr.dataOffset + frame * uncompressedFrameSize;
3
+ const binaryValue = dataSet.byteArray.slice(start, start + uncompressedFrameSize);
4
+ return binaryValue;
5
+ }
6
+
7
+ module.exports = getUncompressedImageFrame;
@@ -0,0 +1,16 @@
1
+ const dataDictionary = require("../dictionary/dataDictionary");
2
+
3
+ const getVR = (attr) => {
4
+ if (attr.vr) {
5
+ return attr.vr;
6
+ }
7
+ // lookup the vr using the data dictionary
8
+ const tag = attr.tag.substring(1).toUpperCase();
9
+ const dataDictAttr = dataDictionary[tag];
10
+ if (dataDictAttr) {
11
+ return dataDictAttr.vr;
12
+ }
13
+ return undefined;
14
+ };
15
+
16
+ module.exports = getVR;