@radicalimaging/static-wado-creator 1.7.3 → 1.7.6

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 (48) hide show
  1. package/dist/DeleteStudy.js +16 -0
  2. package/dist/RejectInstance.js +25 -0
  3. package/dist/StaticWado.js +419 -0
  4. package/dist/createMain.js +18 -0
  5. package/dist/createPart10.js +113 -0
  6. package/dist/createThumbnail.js +163 -0
  7. package/dist/deleteMain.js +12 -0
  8. package/dist/groupMain.js +17 -0
  9. package/dist/index.mjs +18 -0
  10. package/dist/indexMain.js +10 -0
  11. package/dist/instanceMain.js +16 -0
  12. package/dist/metadataMain.js +15 -0
  13. package/dist/mkdicomwebConfig.js +342 -0
  14. package/dist/operation/InstanceDeduplicate.js +103 -0
  15. package/dist/operation/ScanStudy.js +47 -0
  16. package/dist/operation/StudyData.js +520 -0
  17. package/dist/operation/ThumbnailService.js +198 -0
  18. package/dist/operation/adapter/decodeImage.js +12 -0
  19. package/dist/operation/adapter/getImageInfo.js +38 -0
  20. package/dist/operation/adapter/transcodeImage.js +519 -0
  21. package/dist/operation/extractImageFrames.js +55 -0
  22. package/dist/operation/getDataSet.js +65 -0
  23. package/dist/operation/getEncapsulatedImageFrame.js +35 -0
  24. package/dist/operation/getNumberOfFrames.js +13 -0
  25. package/dist/operation/getUncompressedImageFrame.js +7 -0
  26. package/dist/operation/getVR.js +24 -0
  27. package/dist/operation/getValue.js +216 -0
  28. package/dist/operation/validateMetadata.js +40 -0
  29. package/dist/program/index.js +14 -0
  30. package/dist/rejectMain.js +12 -0
  31. package/dist/serverMain.js +21 -0
  32. package/dist/util/IdCreator.js +42 -0
  33. package/dist/util/adaptProgramOpts.js +39 -0
  34. package/dist/util/compressionOptionParser.js +13 -0
  35. package/dist/util/resultsReport.ts +87 -0
  36. package/dist/writer/CompleteStudyWriter.js +129 -0
  37. package/dist/writer/DeduplicateWriter.js +33 -0
  38. package/dist/writer/ExpandUriPath.js +16 -0
  39. package/dist/writer/HashDataWriter.js +74 -0
  40. package/dist/writer/ImageFrameWriter.js +59 -0
  41. package/dist/writer/MultipartAttribute.js +8 -0
  42. package/dist/writer/MultipartHeader.js +14 -0
  43. package/dist/writer/RawDicomWriter.js +44 -0
  44. package/dist/writer/ThumbnailWriter.js +14 -0
  45. package/dist/writer/VideoWriter.js +53 -0
  46. package/dist/writer/WriteMultipart.js +22 -0
  47. package/dist/writer/WriteStream.js +64 -0
  48. package/package.json +11 -6
@@ -0,0 +1,198 @@
1
+ const path = require('path');
2
+ const glob = require('glob');
3
+ const fs = require('fs');
4
+ const { Tags, execSpawn, Stats } = require('@radicalimaging/static-wado-util');
5
+ const { shouldThumbUseTranscoded } = require('./adapter/transcodeImage');
6
+
7
+ /**
8
+ * Return the middle index of given list
9
+ * @param {*} listThumbs
10
+ * @returns
11
+ */
12
+ function getThumbIndex(listThumbs) {
13
+ return Math.trunc(listThumbs / 2);
14
+ }
15
+
16
+ /**
17
+ * ThumbObj wrapper containing both original and transcoded content.
18
+ *
19
+ * @typedef {Object} ThumbObjWrapper
20
+ * @property {Object} id object containing study properties such as seriesUid, instanceUid
21
+ * @property {*} transcodedId transcoded object containing study properties such as seriesUid, instanceUid (this might be the same as original id depending on options)
22
+ * @property {*} imageFrame original image frame
23
+ * @property {*} transcodedImageFrame transcoded image frame (this might be the same as original frame depending on options)
24
+ * @property {number} frameIndex
25
+ */
26
+
27
+ /**
28
+ * ThumbObj containing imageFrame, id to be used.
29
+ *
30
+ * @typedef {Object} ThumbObj
31
+ * @property {Object} id object containing study properties such as seriesUid, instanceUid
32
+ * @property {*} imageFrame
33
+ * @property {number} frameIndex
34
+ */
35
+
36
+ /**
37
+ * Service to handle thumbnail creation.
38
+ * @class
39
+ * @constructor
40
+ * @property {Array<ThumbObj>} framesThumbnailObj Auxiliary list that contains all thumb obj.
41
+ * @property {ThumbObj} favoriteThumbnailObj Favorite thumb obj to be used on thumbnail creation
42
+ * @property {String} thumbFileName thumb file (with extension) to be used when creating the file.
43
+ * @public
44
+ */
45
+ class ThumbnailService {
46
+ constructor() {
47
+ this.framesThumbnailObj = [];
48
+ this.favoriteThumbnailObj = {};
49
+ this.thumbFileName = 'thumbnail';
50
+ }
51
+
52
+ /**
53
+ * Add thumbObj to list of possible thumbs.
54
+ * It might use original content or transcoded depending on options flags
55
+ * @param {ThumbObjWrapper} thumbObjWrapper
56
+ * @param {Object} programOpts
57
+ */
58
+ queueThumbnail(thumbObjWrapper, programOpts) {
59
+ const { id, imageFrame, transcodedId, transcodedImageFrame, frameIndex } = thumbObjWrapper;
60
+ const getThumbContent = (originalContent, trancodedContent) =>
61
+ shouldThumbUseTranscoded(id, programOpts) ? trancodedContent : originalContent;
62
+
63
+ const thumbObj = {
64
+ imageFrame: getThumbContent(imageFrame, transcodedImageFrame),
65
+ id: getThumbContent(id, transcodedId),
66
+ frameIndex,
67
+ };
68
+
69
+ this.framesThumbnailObj.push(thumbObj);
70
+
71
+ this.setFavoriteThumbnailObj();
72
+ }
73
+
74
+ setFavoriteThumbnailObj() {
75
+ const favIndex = getThumbIndex(this.framesThumbnailObj.length);
76
+
77
+ this.favoriteThumbnailObj = this.framesThumbnailObj[favIndex];
78
+ }
79
+
80
+ ffmpeg(input, output) {
81
+ execSpawn(`ffmpeg -i "${input}" -y -f image2 -frames:v 1 -update true "${output}"`);
82
+ }
83
+
84
+ dcm2jpg(input, output, options) {
85
+ const script = ['dcm2jpg', `"${input}" "${output}"`];
86
+ if (options?.format) {
87
+ script.push('-F', options.format);
88
+ }
89
+ execSpawn(script.join(' '));
90
+ }
91
+
92
+ /** Generates a rendered copy of this, assuming --rendered is set.
93
+ * Requires dcm2jpg to be available.
94
+ */
95
+ async generateRendered(itemid, dataSet, metadata, callback, options) {
96
+ if (!options.rendered) {
97
+ return null;
98
+ }
99
+ const { id } = this.favoriteThumbnailObj;
100
+ const rendered = id.imageFrameRootPath.replace(/frames/, 'rendered');
101
+ return this.dcm2jpg(id.filename, rendered, { format: 'png' });
102
+ }
103
+
104
+ /**
105
+ * Generates thumbnails for the levels: instances, series, study. This is asynchronous
106
+ *
107
+ * @param {*} dataSet
108
+ * @param {*} metadata
109
+ * @param {*} callback
110
+ */
111
+ async generateThumbnails(itemId, dataSet, metadata, callback, options) {
112
+ const { imageFrame, id } = this.favoriteThumbnailObj;
113
+
114
+ // There are various reasons no thumbnails might be generated, so just return
115
+ if (!id) {
116
+ const pixelData = metadata[Tags.PixelData];
117
+ if (pixelData) {
118
+ const { BulkDataURI } = pixelData;
119
+ if (BulkDataURI?.indexOf('mp4')) {
120
+ fs.mkdirSync(`${itemId.sopInstanceRootPath}/rendered`, {
121
+ recursive: true,
122
+ });
123
+ const mp4Path = path.join(itemId.sopInstanceRootPath, 'rendered/index.mp4');
124
+ const thumbPath = path.join(itemId.sopInstanceRootPath, 'thumbnail');
125
+ console.verbose('MP4 - converting video format', mp4Path);
126
+ this.ffmpeg(mp4Path, thumbPath);
127
+ return thumbPath;
128
+ }
129
+ } else {
130
+ console.verbose('Series is of other type...', metadata[Tags.Modality]);
131
+ }
132
+ return null;
133
+ }
134
+
135
+ if (!options.thumb) {
136
+ return null;
137
+ }
138
+
139
+ if (options.dcm2jpg) {
140
+ return this.dcm2jpg(id.filename, id.imageFrameRootPath.replace(/frames/, 'thumbnail'), {});
141
+ }
142
+
143
+ await callback.internalGenerateImage(
144
+ imageFrame,
145
+ dataSet,
146
+ metadata,
147
+ id.transferSyntaxUid,
148
+ async thumbBuffer => {
149
+ try {
150
+ if (thumbBuffer) {
151
+ await callback.thumbWriter(id.sopInstanceRootPath, this.thumbFileName, thumbBuffer);
152
+
153
+ this.copySyncThumbnail(id.sopInstanceRootPath, id.seriesRootPath);
154
+ this.copySyncThumbnail(id.seriesRootPath, id.studyPath);
155
+ Stats.StudyStats.add('Thumbnail Write', `Write thumbnail ${this.thumbFileName}`, 100);
156
+ }
157
+ return this.thumbFileName;
158
+ } catch (e) {
159
+ console.log("Couldn't generate thumbnail", this.thumbFileName, e);
160
+ }
161
+ }
162
+ );
163
+ }
164
+
165
+ /**
166
+ * Copy thumbnail from sourceFolder to targetFolderPath. It usually is used to copy from child to parent folder.
167
+ * If there is already some existing thumbnails under targetFolderPath it actually get thumb on the index defined by getThumbIndex.
168
+ * Copy is a syncrhonous process.
169
+ *
170
+ * @param {*} sourceFolderPath
171
+ * @param {*} targetFolderPath
172
+ * @returns
173
+ */
174
+ async copySyncThumbnail(sourceFolderPath, targetFolderPath) {
175
+ const parentPathLevel = path.join(sourceFolderPath, '../');
176
+ const thumbFilesPath = glob.sync(`${parentPathLevel}*/${this.thumbFileName}`);
177
+
178
+ const thumbIndex = getThumbIndex(thumbFilesPath.length);
179
+ const thumbFilePath = thumbFilesPath[thumbIndex];
180
+
181
+ if (!fs.existsSync(thumbFilePath)) {
182
+ console.verbose('Thumbnail to copy does not exists');
183
+ return;
184
+ }
185
+
186
+ try {
187
+ if (!fs.lstatSync(targetFolderPath).isDirectory()) {
188
+ throw new Error(`Target path: ${targetFolderPath} is not a directory`);
189
+ }
190
+
191
+ fs.copyFileSync(thumbFilePath, `${targetFolderPath}/${this.thumbFileName}`);
192
+ } catch (e) {
193
+ console.verbose('The file could not be copied', e);
194
+ }
195
+ }
196
+ }
197
+
198
+ module.exports = ThumbnailService;
@@ -0,0 +1,12 @@
1
+ const dicomCodec = require('@cornerstonejs/dicom-codec');
2
+ const getImageInfo = require('./getImageInfo');
3
+
4
+ function decodeImage(imageFrame, dataset, instance, transferSyntaxUid) {
5
+ const imageInfo = getImageInfo(dataset, instance);
6
+ if (imageFrame && !imageFrame.length) {
7
+ imageFrame = new Uint8Array(imageFrame);
8
+ }
9
+ return dicomCodec.decode(imageFrame, imageInfo, transferSyntaxUid);
10
+ }
11
+
12
+ module.exports = decodeImage;
@@ -0,0 +1,38 @@
1
+ const { Tags } = require('@radicalimaging/static-wado-util');
2
+ /**
3
+ * Minimum image info data to be used on transcode process by dicom-codec api.
4
+ */
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.
27
+
28
+ return {
29
+ bitsAllocated,
30
+ samplesPerPixel,
31
+ rows, // Number with the image rows/height
32
+ columns, // Number with the image columns/width,
33
+ signed: pixelRepresentation === 1,
34
+ pixelRepresentation,
35
+ };
36
+ }
37
+
38
+ module.exports = getImageInfo;