@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/README.md +1 -1
  3. package/bin/mkdicomweb.mjs +8 -0
  4. package/dist/canvas-xaqc8v6y.node +0 -0
  5. package/dist/index.js +511433 -0
  6. package/lib/DeleteStudy.js +11 -9
  7. package/lib/RejectInstance.js +6 -5
  8. package/lib/StaticWado.js +222 -73
  9. package/lib/createMain.js +3 -3
  10. package/lib/createPart10.js +19 -19
  11. package/lib/createThumbnail.js +163 -0
  12. package/lib/deleteMain.js +2 -2
  13. package/lib/groupMain.js +2 -2
  14. package/lib/index.js +7 -5
  15. package/lib/indexMain.js +2 -2
  16. package/lib/instanceMain.js +3 -2
  17. package/lib/metadataMain.js +2 -2
  18. package/lib/mkdicomwebConfig.js +210 -115
  19. package/lib/model/TagLists.js +13 -4
  20. package/lib/model/uids.js +32 -28
  21. package/lib/operation/InstanceDeduplicate.js +18 -13
  22. package/lib/operation/ScanStudy.js +33 -7
  23. package/lib/operation/StudyData.js +122 -71
  24. package/lib/operation/ThumbnailService.js +44 -36
  25. package/lib/operation/adapter/decodeImage.js +7 -4
  26. package/lib/operation/adapter/getImageInfo.js +23 -6
  27. package/lib/operation/adapter/transcodeImage.js +157 -90
  28. package/lib/operation/extractImageFrames.js +25 -14
  29. package/lib/operation/getDataSet.js +4 -4
  30. package/lib/operation/getEncapsulatedImageFrame.js +16 -4
  31. package/lib/operation/getNumberOfFrames.js +2 -2
  32. package/lib/operation/getVR.js +12 -4
  33. package/lib/operation/getValue.js +42 -65
  34. package/lib/operation/validateMetadata.js +40 -0
  35. package/lib/program/index.js +2 -3
  36. package/lib/rejectMain.js +2 -2
  37. package/lib/serverMain.js +21 -0
  38. package/lib/util/IdCreator.js +25 -8
  39. package/lib/util/adaptProgramOpts.js +1 -1
  40. package/lib/util/compressionOptionParser.js +4 -4
  41. package/lib/util/resultsReport.ts +87 -0
  42. package/lib/writer/CompleteStudyWriter.js +42 -19
  43. package/lib/writer/DeduplicateWriter.js +7 -7
  44. package/lib/writer/ExpandUriPath.js +4 -3
  45. package/lib/writer/HashDataWriter.js +21 -17
  46. package/lib/writer/ImageFrameWriter.js +22 -10
  47. package/lib/writer/MultipartHeader.js +1 -1
  48. package/lib/writer/RawDicomWriter.js +18 -10
  49. package/lib/writer/ThumbnailWriter.js +2 -2
  50. package/lib/writer/VideoWriter.js +23 -34
  51. package/lib/writer/WriteMultipart.js +3 -3
  52. package/lib/writer/WriteStream.js +14 -12
  53. package/package.json +22 -18
  54. package/bin/mkdicomweb.js +0 -9
@@ -1,14 +1,16 @@
1
- const { JSONReader } = require("@radicalimaging/static-wado-util");
2
- const { JSONWriter } = require("@radicalimaging/static-wado-util");
3
- const { Tags } = require("@radicalimaging/static-wado-util");
1
+ const { JSONReader } = require('@radicalimaging/static-wado-util');
2
+ const { JSONWriter } = require('@radicalimaging/static-wado-util');
3
+ const { Tags } = require('@radicalimaging/static-wado-util');
4
4
 
5
- module.exports = (options) =>
5
+ module.exports = options =>
6
6
  async function (studyInstanceUid) {
7
- console.log("Delete Study", studyInstanceUid);
8
- const studyData = await this.scanStudy("studies", studyInstanceUid);
7
+ console.log('Delete Study', studyInstanceUid);
8
+ const studyData = await this.scanStudy('studies', studyInstanceUid);
9
9
  studyData.delete();
10
- const allStudies = await JSONReader(options.directoryName, "studies/index.json.gz", []);
11
- const studiesWithoutDeleted = allStudies.filter((study) => studyInstanceUid != Tags.getValue(study, Tags.StudyInstanceUID));
12
- await JSONWriter(options.directoryName, "studies", studiesWithoutDeleted);
10
+ const allStudies = await JSONReader(options.directoryName, 'studies/index.json.gz', []);
11
+ const studiesWithoutDeleted = allStudies.filter(
12
+ study => studyInstanceUid != Tags.getValue(study, Tags.StudyInstanceUID)
13
+ );
14
+ await JSONWriter(options.directoryName, 'studies', studiesWithoutDeleted);
13
15
  delete this.studyData;
14
16
  };
@@ -1,23 +1,24 @@
1
- const dataExtractor = /^(.*studies)?[\\/]?([0-9.a-zA-Z]+)[\\/](series[\\/])?([0-9.a-zA-Z]+)([\\/]instances[\\/]([0-9.a-zA-Z]*))?$/;
1
+ const dataExtractor =
2
+ /^(.*studies)?[\\/]?([0-9.a-zA-Z]+)[\\/](series[\\/])?([0-9.a-zA-Z]+)([\\/]instances[\\/]([0-9.a-zA-Z]*))?$/;
2
3
 
3
4
  module.exports = () =>
4
5
  async function rejectInstanceWithOptions(args) {
5
6
  let studyInstanceUid = args[0];
6
7
  let seriesInstanceUid;
7
8
  let sopInstanceUid;
8
- const reason = args[2] || "REJECT";
9
+ const reason = args[2] || 'REJECT';
9
10
  const extracted = args[0].match(dataExtractor);
10
11
  if (extracted) {
11
12
  studyInstanceUid = extracted[2];
12
13
  seriesInstanceUid = extracted[4];
13
14
  sopInstanceUid = extracted[6];
14
- console.log("Extracted", studyInstanceUid, seriesInstanceUid, sopInstanceUid, reason);
15
+ console.log('Extracted', studyInstanceUid, seriesInstanceUid, sopInstanceUid, reason);
15
16
  } else {
16
- console.log("Not extracting from URL", studyInstanceUid, seriesInstanceUid, reason);
17
+ console.log('Not extracting from URL', studyInstanceUid, seriesInstanceUid, reason);
17
18
  seriesInstanceUid = args[1];
18
19
  }
19
20
  const studyData = await this.scanStudy(studyInstanceUid);
20
- console.verbose("Trying to reject", seriesInstanceUid, "because", reason);
21
+ console.verbose('Trying to reject', seriesInstanceUid, 'because', reason);
21
22
  await studyData.reject(seriesInstanceUid, sopInstanceUid, reason);
22
23
  await studyData.writeDeduplicatedGroup();
23
24
  return studyData.writeMetadata();
package/lib/StaticWado.js CHANGED
@@ -1,55 +1,105 @@
1
- const dicomCodec = require("@cornerstonejs/dicom-codec");
2
- const staticCS = require("@radicalimaging/static-cs-lite");
3
- const { Stats, handleHomeRelative, dirScanner, JSONReader, JSONWriter, asyncIterableToBuffer, Tags } = require("@radicalimaging/static-wado-util");
4
- const dicomParser = require("dicom-parser");
5
- const fs = require("fs");
6
- const path = require("path");
7
- const { NotificationService } = require("@radicalimaging/static-wado-util");
8
- const getDataSet = require("./operation/getDataSet");
9
- const InstanceDeduplicate = require("./operation/InstanceDeduplicate");
10
- const DeduplicateWriter = require("./writer/DeduplicateWriter");
11
- const ImageFrameWriter = require("./writer/ImageFrameWriter");
12
- const WriteStream = require("./writer/WriteStream");
13
- const CompleteStudyWriter = require("./writer/CompleteStudyWriter");
14
- const IdCreator = require("./util/IdCreator");
15
- const ScanStudy = require("./operation/ScanStudy");
16
- const HashDataWriter = require("./writer/HashDataWriter");
17
- const VideoWriter = require("./writer/VideoWriter");
18
- const { transcodeImageFrame, generateLossyImage, transcodeId, transcodeMetadata } = require("./operation/adapter/transcodeImage");
19
- const ThumbnailWriter = require("./writer/ThumbnailWriter");
20
- const decodeImage = require("./operation/adapter/decodeImage");
21
- const ThumbnailService = require("./operation/ThumbnailService");
22
- const DeleteStudy = require("./DeleteStudy");
23
- const RejectInstance = require("./RejectInstance");
24
- const RawDicomWriter = require("./writer/RawDicomWriter");
25
- const { isVideo } = require("./writer/VideoWriter");
1
+ const dicomCodec = require('@cornerstonejs/dicom-codec');
2
+ const staticCS = require('@radicalimaging/cs3d');
3
+ const {
4
+ Stats,
5
+ handleHomeRelative,
6
+ dirScanner,
7
+ JSONReader,
8
+ JSONWriter,
9
+ asyncIterableToBuffer,
10
+ Tags,
11
+ } = require('@radicalimaging/static-wado-util');
12
+ const dicomParser = require('dicom-parser');
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { NotificationService } = require('@radicalimaging/static-wado-util');
16
+ const getDataSet = require('./operation/getDataSet');
17
+ const InstanceDeduplicate = require('./operation/InstanceDeduplicate');
18
+ const DeduplicateWriter = require('./writer/DeduplicateWriter');
19
+ const ImageFrameWriter = require('./writer/ImageFrameWriter');
20
+ const WriteStream = require('./writer/WriteStream');
21
+ const CompleteStudyWriter = require('./writer/CompleteStudyWriter');
22
+ const IdCreator = require('./util/IdCreator');
23
+ const ScanStudy = require('./operation/ScanStudy');
24
+ const HashDataWriter = require('./writer/HashDataWriter');
25
+ const VideoWriter = require('./writer/VideoWriter');
26
+ const {
27
+ transcodeImageFrame,
28
+ generateLossyImage,
29
+ transcodeId,
30
+ transcodeMetadata,
31
+ } = require('./operation/adapter/transcodeImage');
32
+ const ThumbnailWriter = require('./writer/ThumbnailWriter');
33
+ const decodeImage = require('./operation/adapter/decodeImage');
34
+ const ThumbnailService = require('./operation/ThumbnailService');
35
+ const DeleteStudy = require('./DeleteStudy');
36
+ const RejectInstance = require('./RejectInstance');
37
+ const RawDicomWriter = require('./writer/RawDicomWriter');
38
+ const { isVideo } = require('./writer/VideoWriter');
39
+ const validateMetadata = require('./operation/validateMetadata.js');
40
+ const { Success, Failure, clearResults, WriteResults } = require('./util/resultsReport');
26
41
 
27
42
  function setStudyData(studyData) {
28
43
  this.studyData = studyData;
29
44
  }
30
45
 
31
- function internalGenerateImage(originalImageFrame, dataset, metadata, transferSyntaxUid, doneCallback) {
32
- decodeImage(originalImageFrame, dataset, transferSyntaxUid)
33
- .then((decodeResult = {}) => {
34
- if (isVideo(transferSyntaxUid)) {
35
- console.log("Video data - no thumbnail generator yet");
36
- } else {
37
- const { imageFrame, imageInfo } = decodeResult;
38
- const pixelData = dicomCodec.getPixelData(imageFrame, imageInfo, transferSyntaxUid);
39
- staticCS.getRenderedBuffer(transferSyntaxUid, pixelData, metadata, doneCallback);
40
- }
41
- })
42
- .catch((error) => {
43
- console.log(`Error while generating thumbnail:: ${error}`);
44
- });
46
+ async function cs3dThumbnail(
47
+ originalImageFrame,
48
+ dataset,
49
+ metadata,
50
+ transferSyntaxUid,
51
+ doneCallback
52
+ ) {
53
+ if (!originalImageFrame) {
54
+ throw new Error(`No originalImageFrame data available`);
55
+ }
56
+ if (!dataset && !metadata) {
57
+ throw new Error(`Neither dataset ${!!dataset} nor metadata ${!!metadata} available.`);
58
+ }
59
+ if (isVideo(transferSyntaxUid)) {
60
+ console.log('Video data - no thumbnail generator yet');
61
+ return;
62
+ }
63
+
64
+ let decodeResult;
65
+ try {
66
+ decodeResult = await decodeImage(originalImageFrame, dataset, metadata, transferSyntaxUid);
67
+ } catch (error) {
68
+ console.noQuiet('Error while decoding image:', error);
69
+ return;
70
+ }
71
+
72
+ if (!decodeResult) {
73
+ console.warn("No decode result, can't create thumbnail");
74
+ return;
75
+ }
76
+
77
+ const { imageFrame, imageInfo } = decodeResult;
78
+ const pixelData = dicomCodec.getPixelData(imageFrame, imageInfo, transferSyntaxUid);
79
+ return staticCS.getRenderedBuffer(transferSyntaxUid, pixelData, metadata, doneCallback, {
80
+ quality: 0.6,
81
+ width: 256,
82
+ height: 256,
83
+ });
45
84
  }
46
85
 
47
86
  class StaticWado {
48
87
  constructor(configuration) {
49
- const { rootDir = "~/dicomweb", pathDeduplicated = "deduplicated", pathInstances = "instances", verbose } = configuration;
88
+ const {
89
+ rootDir = '~/dicomweb',
90
+ pathDeduplicated = 'deduplicated',
91
+ pathInstances = 'instances',
92
+ verbose,
93
+ quiet = false,
94
+ multipart = false,
95
+ showProgress = !quiet && !multipart && !verbose,
96
+ } = configuration;
50
97
 
51
98
  dicomCodec.setConfig({ verbose });
52
99
  const directoryName = handleHomeRelative(rootDir);
100
+ this.showProgress = showProgress;
101
+ this.processedFiles = 0;
102
+ this.totalFiles = 0;
53
103
 
54
104
  this.options = {
55
105
  ...configuration,
@@ -57,7 +107,7 @@ class StaticWado {
57
107
  deduplicatedRoot: path.join(directoryName, pathDeduplicated),
58
108
  deduplicatedInstancesRoot: path.join(directoryName, pathInstances),
59
109
  // TODO - make this configurable and auto-detect by updating the parseDicom library
60
- TransferSyntaxUID: "1.2.840.10008.1.2.1",
110
+ TransferSyntaxUID: '1.2.840.10008.1.2.1',
61
111
  };
62
112
  this.callback = {
63
113
  uids: IdCreator(this.options),
@@ -66,15 +116,20 @@ class StaticWado {
66
116
  videoWriter: VideoWriter(this.options),
67
117
  thumbWriter: ThumbnailWriter(this.options),
68
118
  completeStudy: CompleteStudyWriter(this.options),
119
+ validateMetadata: validateMetadata(this.options),
69
120
  metadata: InstanceDeduplicate(this.options),
70
121
  deduplicated: DeduplicateWriter(this.options),
71
122
  scanStudy: ScanStudy(this.options),
72
123
  reject: RejectInstance(this.options),
73
124
  delete: DeleteStudy(this.options),
125
+ success: Success(this.options),
126
+ failure: Failure(this.options),
127
+ writeResults: WriteResults(this.options),
128
+ clearResults,
74
129
  setStudyData,
75
130
  rawDicomWriter: RawDicomWriter(this.options),
76
131
  notificationService: new NotificationService(this.options.notificationDir),
77
- internalGenerateImage,
132
+ internalGenerateImage: cs3dThumbnail,
78
133
  };
79
134
  }
80
135
 
@@ -95,7 +150,7 @@ class StaticWado {
95
150
  * @param {string[]} args listing the studies to remove
96
151
  */
97
152
  async delete(args) {
98
- args.forEach((studyUID) => this.callback.delete(studyUID));
153
+ args.forEach(studyUID => this.callback.delete(studyUID));
99
154
  }
100
155
 
101
156
  /**
@@ -106,20 +161,84 @@ class StaticWado {
106
161
  * @param {*} callback
107
162
  * @param {*} params
108
163
  */
164
+ updateProgress() {
165
+ if (!this.showProgress) return;
166
+ this.processedFiles++;
167
+ const percentage = Math.round((this.processedFiles / this.totalFiles) * 100);
168
+ const progressBar =
169
+ '='.repeat(Math.floor(percentage / 4)) + '-'.repeat(25 - Math.floor(percentage / 4));
170
+ process.stdout.write(
171
+ `\r[${progressBar}] ${percentage}% | ${this.processedFiles}/${this.totalFiles} files`
172
+ );
173
+ }
174
+
109
175
  async processFiles(files, params) {
110
- return dirScanner(files, {
176
+ if (this.showProgress) {
177
+ // Count total files first
178
+ for (const file of files) {
179
+ try {
180
+ if (fs.statSync(file).isDirectory()) {
181
+ const dirFiles = fs.readdirSync(file, { recursive: true });
182
+ this.totalFiles += dirFiles.filter(
183
+ f => !fs.statSync(path.join(file, f)).isDirectory()
184
+ ).length;
185
+ } else {
186
+ this.totalFiles++;
187
+ }
188
+ } catch (e) {
189
+ console.verbose('File not dicom', e);
190
+ this.callback.failure(
191
+ 'File not DICOM',
192
+ {
193
+ FailedSOPSequence: [{ FailureReason: 0xd000, TextValue: 'File not DICOM' }],
194
+ },
195
+ file
196
+ );
197
+ }
198
+ }
199
+ console.noQuiet(`\nProcessing ${this.totalFiles} DICOM files...\n`);
200
+ }
201
+
202
+ let filesProcessed = 0;
203
+ const result = await dirScanner(files, {
111
204
  ...params,
112
- callback: async (file) => {
205
+ callback: async file => {
113
206
  try {
114
207
  const dicomp10stream = fs.createReadStream(file);
115
208
  await this.importBinaryDicom(dicomp10stream, { ...params, file });
116
- Stats.StudyStats.add("DICOM P10", "Parse DICOM P10 file");
209
+ filesProcessed++;
210
+ Stats.StudyStats.add('DICOM P10', 'Parse DICOM P10 file');
211
+ this.updateProgress();
117
212
  } catch (e) {
118
- console.error("Couldn't process", file);
119
- console.verbose("Error", e);
213
+ console.noQuiet('Create failure', e);
214
+ this.updateProgress();
215
+ const message = {
216
+ FailedSOPSequence: [
217
+ {
218
+ FailureReason: 0xd000,
219
+ TextValue: 'File not DICOM',
220
+ StorageURL: file,
221
+ },
222
+ ],
223
+ };
224
+ this.callback.failure('Not DICOM', message, file, e);
120
225
  }
121
226
  },
122
227
  });
228
+
229
+ if (!filesProcessed) {
230
+ const message = {
231
+ action: 'metadata',
232
+ count: 0,
233
+ status: -1,
234
+ };
235
+ this.callback.failure('No files processed', message);
236
+ }
237
+
238
+ if (this.showProgress) {
239
+ console.log('\n'); // Move to next line after progress bar
240
+ }
241
+ return result;
123
242
  }
124
243
 
125
244
  /**
@@ -130,7 +249,7 @@ class StaticWado {
130
249
  async processStudyDir(studyUids /* , options */) {
131
250
  for (const studyUid of studyUids) {
132
251
  const study = await this.callback.scanStudy(studyUid);
133
- console.log("Processed study", study.studyInstanceUid);
252
+ console.log('Processed study', study.studyInstanceUid);
134
253
  }
135
254
  }
136
255
 
@@ -141,10 +260,10 @@ class StaticWado {
141
260
  // Parse it
142
261
  const dataSet = dicomParser.parseDicom(buffer, params);
143
262
 
144
- const studyInstanceUid = dataSet.string("x0020000d");
263
+ const studyInstanceUid = dataSet.string('x0020000d');
145
264
 
146
265
  if (!studyInstanceUid) {
147
- console.log("No study UID, can't import file", params.file, dataSet.elements);
266
+ console.log("No study UID, can't import file", params.file);
148
267
  return undefined;
149
268
  }
150
269
 
@@ -152,9 +271,9 @@ class StaticWado {
152
271
  const id = this.callback.uids(
153
272
  {
154
273
  studyInstanceUid,
155
- seriesInstanceUid: dataSet.string("x0020000e"),
156
- sopInstanceUid: dataSet.string("x00080018"),
157
- transferSyntaxUid: dataSet.string("x00020010"),
274
+ seriesInstanceUid: dataSet.string('x0020000e'),
275
+ sopInstanceUid: dataSet.string('x00080018'),
276
+ transferSyntaxUid: dataSet.string('x00020010'),
158
277
  },
159
278
  params.file
160
279
  );
@@ -170,9 +289,9 @@ class StaticWado {
170
289
  const _bulkDataIndex = bulkDataIndex;
171
290
  bulkDataIndex += 1;
172
291
  // TODO - handle other types here too as single part rendered
173
- if (options?.mimeType === "application/pdf") {
174
- console.log("Writing rendered mimeType", options.mimeType);
175
- const writeStream = WriteStream(id.sopInstanceRootPath, "rendered.pdf", {
292
+ if (options?.mimeType === 'application/pdf') {
293
+ console.verbose('Writing rendered mimeType', options.mimeType);
294
+ const writeStream = WriteStream(id.sopInstanceRootPath, 'rendered.pdf', {
176
295
  gzip: false,
177
296
  mkdir: true,
178
297
  });
@@ -181,8 +300,12 @@ class StaticWado {
181
300
  }
182
301
  return this.callback.bulkdata(targetId, _bulkDataIndex, bulkData, options);
183
302
  },
184
- imageFrame: async (originalImageFrame) => {
185
- const { imageFrame: transcodedImageFrame, decoded, id: transcodedId } = await transcodeImageFrame(id, targetId, originalImageFrame, dataSet, this.options);
303
+ imageFrame: async originalImageFrame => {
304
+ const {
305
+ imageFrame: transcodedImageFrame,
306
+ decoded,
307
+ id: transcodedId,
308
+ } = await transcodeImageFrame(id, targetId, originalImageFrame, dataSet, this.options);
186
309
 
187
310
  const lossyImage = await generateLossyImage(id, decoded, this.options);
188
311
 
@@ -190,7 +313,11 @@ class StaticWado {
190
313
  imageFrameIndex += 1;
191
314
 
192
315
  if (lossyImage) {
193
- await this.callback.imageFrame(lossyImage.id, currentImageFrameIndex, lossyImage.imageFrame);
316
+ await this.callback.imageFrame(
317
+ lossyImage.id,
318
+ currentImageFrameIndex,
319
+ lossyImage.imageFrame
320
+ );
194
321
  }
195
322
 
196
323
  thumbnailService.queueThumbnail(
@@ -206,17 +333,31 @@ class StaticWado {
206
333
 
207
334
  return this.callback.imageFrame(transcodedId, currentImageFrameIndex, transcodedImageFrame);
208
335
  },
209
- videoWriter: async (_dataSet) => this.callback.videoWriter(id, _dataSet),
336
+ videoWriter: async _dataSet => this.callback.videoWriter(id, _dataSet),
210
337
  };
211
338
 
212
339
  // convert to DICOMweb MetaData and BulkData
213
340
  const result = await getDataSet(dataSet, generator, this.options);
214
341
 
342
+ await this.callback.validateMetadata?.(id, result, buffer);
343
+
215
344
  await this.callback.rawDicomWriter?.(id, result, buffer);
216
345
 
217
346
  const transcodedMeta = transcodeMetadata(result.metadata, id, this.options);
218
- await thumbnailService.generateThumbnails(id, dataSet, transcodedMeta, this.callback, this.options);
219
- await thumbnailService.generateRendered(id, dataSet, transcodedMeta, this.callback, this.options);
347
+ await thumbnailService.generateThumbnails(
348
+ id,
349
+ dataSet,
350
+ transcodedMeta,
351
+ this.callback,
352
+ this.options
353
+ );
354
+ await thumbnailService.generateRendered(
355
+ id,
356
+ dataSet,
357
+ transcodedMeta,
358
+ this.callback,
359
+ this.options
360
+ );
220
361
  await this.callback.metadata(targetId, transcodedMeta);
221
362
 
222
363
  // resolve promise with statistics
@@ -227,8 +368,14 @@ class StaticWado {
227
368
  return getDataSet(dataSet, generator, params);
228
369
  }
229
370
 
230
- static internalGenerateImage(originalImageFrame, dataSet, metadata, transferSyntaxUid, doneCallback) {
231
- return internalGenerateImage(originalImageFrame, dataSet, metadata, transferSyntaxUid, doneCallback);
371
+ static internalGenerateImage(
372
+ originalImageFrame,
373
+ dataSet,
374
+ metadata,
375
+ transferSyntaxUid,
376
+ doneCallback
377
+ ) {
378
+ return cs3dThumbnail(originalImageFrame, dataSet, metadata, transferSyntaxUid, doneCallback);
232
379
  }
233
380
 
234
381
  /**
@@ -237,39 +384,41 @@ class StaticWado {
237
384
  * data in memory by default on a study level, which avoids needing to run the load process.
238
385
  */
239
386
  async executeCommand(input) {
387
+ this.callback.clearResults();
240
388
  if (this.options.scanStudies) {
241
- console.log("Scanning study dir", input);
389
+ console.log('Scanning study dir', input);
242
390
  // Scan one of the study directories - in this case, files is a set of study directories
243
391
  await this.processStudyDir(input, this.options);
244
392
  } else {
245
- console.log("Scanning files", input);
393
+ console.noQuiet('Scanning files', input);
246
394
  await this.processFiles(input, this.options);
247
395
  }
248
396
  await this.close();
397
+ this.callback.writeResults();
249
398
  }
250
399
 
251
400
  async close() {
252
401
  await this.callback.completeStudy(this.callback);
253
- Stats.OverallStats.summarize("Completed Study Processing");
402
+ Stats.OverallStats.summarize('Completed Study Processing');
254
403
  }
255
404
 
256
405
  async reindex() {
257
406
  const { directoryName } = this.options;
258
407
  const studiesDir = `${directoryName}/studies/`;
259
408
 
260
- console.log("Re-indexing", studiesDir);
409
+ console.noQuiet('Re-indexing', studiesDir);
261
410
  const dirs = await fs.promises.readdir(studiesDir);
262
411
  const studies = [];
263
412
  for (const dir of dirs) {
264
- const study = await JSONReader(`${studiesDir}/${dir}`, "index.json.gz", null);
413
+ const study = await JSONReader(`${studiesDir}/${dir}`, 'index.json.gz', null);
265
414
  if (study === null) {
266
- console.log("No study found in", dir);
415
+ console.log('No study found in', dir);
267
416
  continue;
268
417
  }
269
- console.log("Adding study", dir);
418
+ console.log('Adding study', dir);
270
419
  studies.push(...study);
271
420
  }
272
- return JSONWriter(directoryName, "studies", studies);
421
+ return JSONWriter(directoryName, 'studies', studies);
273
422
  }
274
423
  }
275
424
  module.exports = StaticWado;
package/lib/createMain.js CHANGED
@@ -1,11 +1,11 @@
1
- const StaticWado = require("./StaticWado");
2
- const adaptProgramOpts = require("./util/adaptProgramOpts");
1
+ const StaticWado = require('./StaticWado');
2
+ const adaptProgramOpts = require('./util/adaptProgramOpts');
3
3
 
4
4
  module.exports = function createMain(options, program) {
5
5
  const finalOptions = adaptProgramOpts(options, {
6
6
  ...this,
7
7
  // Instance metadata is the instances/<sopUID>/metadata.gz files
8
- isInstance: true,
8
+ isInstance: false,
9
9
  // Deduplicated data is single instance deduplicated data
10
10
  isDeduplicate: false,
11
11
  // Group data is the group file directories
@@ -1,28 +1,28 @@
1
- /* eslint-disable no-param-reassign */
2
- const { Tags, readBulkData } = require("@radicalimaging/static-wado-util");
3
- const dcmjs = require("dcmjs");
1
+ const { Tags, readBulkData } = require('@radicalimaging/static-wado-util');
2
+ const dcmjs = require('dcmjs');
4
3
 
5
- const StaticWado = require("./StaticWado");
6
- const adaptProgramOpts = require("./util/adaptProgramOpts");
7
- const WriteStream = require("./writer/WriteStream");
4
+ const StaticWado = require('./StaticWado');
5
+ const adaptProgramOpts = require('./util/adaptProgramOpts');
6
+ const WriteStream = require('./writer/WriteStream');
8
7
 
9
- const UncompressedLEIExplicit = "1.2.840.10008.1.2.1";
8
+ const UncompressedLEIExplicit = '1.2.840.10008.1.2.1';
10
9
  const { DicomDict, DicomMetaDictionary } = dcmjs.data;
11
10
 
12
11
  const fileMetaInformationVersionArray = new Uint8Array(2);
13
12
  fileMetaInformationVersionArray[1] = 1;
14
13
 
15
- const createFmi = (instance) => {
14
+ const createFmi = instance => {
16
15
  // Assume the TSUID is in the value 0
17
- const TransferSyntaxUID = Tags.getValue(instance, Tags.AvailableTransferSyntaxUID) || UncompressedLEIExplicit;
16
+ const TransferSyntaxUID =
17
+ Tags.getValue(instance, Tags.AvailableTransferSyntaxUID) || UncompressedLEIExplicit;
18
18
  const MediaStorageSOPClassUID = Tags.getValue(instance, Tags.MediaStorageSOPClassUID);
19
19
  const SOPInstanceUID = Tags.getValue(instance, Tags.SOPInstanceUID);
20
20
  const naturalFmi = {
21
21
  MediaStorageSOPClassUID,
22
22
  MediaStorageSOPInstanceUID: SOPInstanceUID,
23
- ImplementationVersionName: "mkdicomweb",
23
+ ImplementationVersionName: 'mkdicomweb',
24
24
  TransferSyntaxUID,
25
- ImplementationClassUID: "2.25.984723498557234098.001",
25
+ ImplementationClassUID: '2.25.984723498557234098.001',
26
26
  FileMetaInformationVersion: fileMetaInformationVersionArray.buffer,
27
27
  };
28
28
  const denaturalized = DicomMetaDictionary.denaturalizeDataset(naturalFmi);
@@ -31,11 +31,11 @@ const createFmi = (instance) => {
31
31
 
32
32
  const readBulkDataValue = async (studyDir, instance, value) => {
33
33
  const { BulkDataURI } = value;
34
- value.vr = "OB";
34
+ value.vr = 'OB';
35
35
  const seriesUid = Tags.getValue(instance, Tags.SeriesInstanceUID);
36
36
  const numberOfFrames = Tags.getValue(instance, Tags.NumberOfFrames) || 1;
37
37
  const dir = `${studyDir}/series/${seriesUid}`;
38
- if (BulkDataURI.indexOf("/frames") !== -1) {
38
+ if (BulkDataURI.indexOf('/frames') !== -1) {
39
39
  // Really only support a limited number of frames based on memory size
40
40
  value.Value = [];
41
41
  for (let frame = 1; frame <= numberOfFrames; frame++) {
@@ -57,15 +57,15 @@ const readBinaryData = async (dir, instance) => {
57
57
  }
58
58
  if (!v.vr) {
59
59
  const value0 = v.Value?.[0];
60
- if (typeof value0 === "string") {
61
- v.vr = "LT";
60
+ if (typeof value0 === 'string') {
61
+ v.vr = 'LT';
62
62
  } else {
63
- console.log("Deleting", tag, v.Value, v);
63
+ console.log('Deleting', tag, v.Value, v);
64
64
  delete instance[tag];
65
65
  }
66
66
  continue;
67
67
  }
68
- if (v.vr === "SQ") {
68
+ if (v.vr === 'SQ') {
69
69
  await Promise.all(v.Values.map(readBinaryData));
70
70
  continue;
71
71
  }
@@ -96,14 +96,14 @@ module.exports = async function createMain(options, program) {
96
96
  const importer = new StaticWado(finalOptions);
97
97
  const studyInstanceUid = program.args[0];
98
98
  const dir = `${importer.options.rootDir}/studies/${studyInstanceUid}`;
99
- console.log("Creating part 10 for", studyInstanceUid, dir);
99
+ console.log('Creating part 10 for', studyInstanceUid, dir);
100
100
  const study = await importer.callback.scanStudy(studyInstanceUid);
101
101
  const outputPath = `./studies/${studyInstanceUid}`;
102
102
  for (const sop of study.getSopUids()) {
103
103
  const instance = await study.recombine(sop);
104
104
  const fmi = createFmi(instance);
105
105
 
106
- console.log("Processing sop", sop);
106
+ console.noQuiet('Processing sop', sop);
107
107
  const dicomDict = new DicomDict(fmi);
108
108
  await readBinaryData(dir, instance);
109
109
  dicomDict.dict = instance;