@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.
Files changed (48) 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 +481518 -0
  6. package/lib/DeleteStudy.js +9 -2
  7. package/lib/RejectInstance.js +15 -3
  8. package/lib/StaticWado.js +245 -41
  9. package/lib/createPart10.js +8 -4
  10. package/lib/createThumbnail.js +181 -0
  11. package/lib/index.js +2 -0
  12. package/lib/instanceMain.js +1 -0
  13. package/lib/mkdicomwebConfig.js +122 -19
  14. package/lib/model/TagLists.js +24 -3
  15. package/lib/model/uids.js +5 -1
  16. package/lib/operation/InstanceDeduplicate.js +14 -4
  17. package/lib/operation/ScanStudy.js +38 -5
  18. package/lib/operation/StudyData.js +158 -38
  19. package/lib/operation/ThumbnailService.js +65 -31
  20. package/lib/operation/adapter/decodeImage.js +5 -2
  21. package/lib/operation/adapter/getImageInfo.js +19 -1
  22. package/lib/operation/adapter/transcodeImage.js +174 -52
  23. package/lib/operation/extractImageFrames.js +19 -4
  24. package/lib/operation/getDataSet.js +41 -7
  25. package/lib/operation/getEncapsulatedImageFrame.js +30 -6
  26. package/lib/operation/getUncompressedImageFrame.js +10 -2
  27. package/lib/operation/getVR.js +10 -2
  28. package/lib/operation/getValue.js +40 -12
  29. package/lib/operation/validateMetadata.js +40 -0
  30. package/lib/program/index.js +5 -3
  31. package/lib/serverMain.js +27 -0
  32. package/lib/util/IdCreator.js +32 -6
  33. package/lib/util/adaptProgramOpts.js +1 -1
  34. package/lib/util/compressionOptionParser.js +12 -3
  35. package/lib/util/resultsReport.ts +88 -0
  36. package/lib/writer/CompleteStudyWriter.js +49 -13
  37. package/lib/writer/DeduplicateWriter.js +2 -1
  38. package/lib/writer/ExpandUriPath.js +4 -1
  39. package/lib/writer/HashDataWriter.js +5 -1
  40. package/lib/writer/ImageFrameWriter.js +38 -13
  41. package/lib/writer/RawDicomWriter.js +11 -2
  42. package/lib/writer/VideoWriter.js +19 -7
  43. package/lib/writer/WriteMultipart.js +3 -1
  44. package/lib/writer/WriteStream.js +12 -4
  45. package/lib/writer/static-dicomweb.code-workspace +8 -0
  46. package/package.json +23 -19
  47. package/bin/mkdicomweb.js +0 -9
  48. package/license.md +0 -21
@@ -8,7 +8,8 @@ const TagLists = require("../model/TagLists");
8
8
  const { getValue, setValue, getList, setList } = Tags;
9
9
  const hasher = hashFactory.hasher();
10
10
 
11
- const getSeriesInstanceUid = (seriesInstance) => getValue(seriesInstance, Tags.SeriesInstanceUID);
11
+ const getSeriesInstanceUid = (seriesInstance) =>
12
+ getValue(seriesInstance, Tags.SeriesInstanceUID);
12
13
 
13
14
  /**
14
15
  * StudyData contains information about the grouped study data. It is used to create
@@ -21,12 +22,21 @@ const getSeriesInstanceUid = (seriesInstance) => getValue(seriesInstance, Tags.S
21
22
  * level data multiple times when it already exists.
22
23
  */
23
24
  class StudyData {
24
- constructor({ studyInstanceUid, studyPath, deduplicatedPath, deduplicatedInstancesPath }, { isGroup, clean }) {
25
+ constructor(
26
+ {
27
+ studyInstanceUid,
28
+ studyPath,
29
+ deduplicatedPath,
30
+ deduplicatedInstancesPath,
31
+ },
32
+ { isGroup, clean, hashStudyUidPath },
33
+ ) {
25
34
  this.studyInstanceUid = studyInstanceUid;
26
35
  this.studyPath = studyPath;
27
36
  this.isGroup = isGroup;
28
37
  this.deduplicatedPath = deduplicatedPath;
29
38
  this.deduplicatedInstancesPath = deduplicatedInstancesPath;
39
+ this.hashStudyUidPath = hashStudyUidPath;
30
40
  this.clean = clean;
31
41
  this.clear();
32
42
  }
@@ -55,22 +65,49 @@ class StudyData {
55
65
  if (clean) {
56
66
  // Wipe out the study directory entirely, as well as the deduplicatedRoot and instancesRoot
57
67
  }
68
+
69
+ console.verbose("studyInstanceUid", this.studyInstanceUid);
70
+ console.verbose("studyPath", this.studyPath);
71
+ console.verbose("deduplicatedPath", this.deduplicatedPath);
72
+ console.verbose(
73
+ "deduplicatedInstancesPath",
74
+ this.deduplicatedInstancesPath,
75
+ );
76
+
58
77
  this.groupFiles = 0;
59
- const studyDeduplicated = await JSONReader(this.studyPath, "deduplicated/index.json.gz", []);
78
+ const studyDeduplicated = await JSONReader(
79
+ this.studyPath,
80
+ "deduplicated/index.json.gz",
81
+ [],
82
+ );
60
83
  const info = studyDeduplicated[0];
61
84
  if (info) {
62
85
  const hash = getValue(info, Tags.DeduppedHash);
63
- console.log("Reading studies/<studyUID>/deduplicated/index.json.gz");
86
+ if (this.hashStudyUidPath) {
87
+ console.verbose(
88
+ "Reading studies/<studyUIDPath>/<studyUIDSubpath>/<studyUID>/deduplicated/index.json.gz",
89
+ );
90
+ } else {
91
+ console.verbose(
92
+ "Reading studies/<studyUID>/deduplicated/index.json.gz",
93
+ );
94
+ }
64
95
  this.readDeduplicatedData("index.json.gz", studyDeduplicated, hash);
65
96
  } else {
66
- console.log("No deduplicated/index.json to read in", this.studyPath, "/deduplicated/index.json.gz");
97
+ console.log(
98
+ "No deduplicated/index.json to read in",
99
+ this.studyPath,
100
+ "/deduplicated/index.json.gz",
101
+ );
67
102
  }
68
103
  if (this.deduplicatedPath) {
69
104
  this.groupFiles = await this.readDeduplicated(this.deduplicatedPath);
70
105
  console.verbose("Read groupFiles:", this.groupFiles);
71
106
  }
72
107
  if (this.deduplicatedInstancesPath) {
73
- this.instanceFiles = await this.readDeduplicated(this.deduplicatedInstancesPath);
108
+ this.instanceFiles = await this.readDeduplicated(
109
+ this.deduplicatedInstancesPath,
110
+ );
74
111
  }
75
112
  }
76
113
 
@@ -84,7 +121,11 @@ class StudyData {
84
121
  * a separate type of check.
85
122
  */
86
123
  get dirty() {
87
- return this.newInstancesAdded > 0 || this.existingFiles.length > 1 || this.instanceFiles > 0;
124
+ return (
125
+ this.newInstancesAdded > 0 ||
126
+ this.existingFiles.length > 1 ||
127
+ this.instanceFiles > 0
128
+ );
88
129
  }
89
130
 
90
131
  async dirtyMetadata() {
@@ -93,7 +134,9 @@ class StudyData {
93
134
  return true;
94
135
  }
95
136
  if (this.groupFiles > 0) {
96
- console.verbose("dirtyMetadata::Study level deduplicated doesn't match group files");
137
+ console.verbose(
138
+ "dirtyMetadata::Study level deduplicated doesn't match group files",
139
+ );
97
140
  }
98
141
  try {
99
142
  const studyFile = await JSONReader(this.studyPath, "index.json.gz", null);
@@ -108,7 +151,10 @@ class StudyData {
108
151
  console.verbose("dirtyMetadata::Dedupped hash missing");
109
152
  return true;
110
153
  } catch (e) {
111
- console.verbose("dirtyMetadata::Exception, assume study metadata is dirty", e);
154
+ console.verbose(
155
+ "dirtyMetadata::Exception, assume study metadata is dirty",
156
+ e,
157
+ );
112
158
  return true;
113
159
  }
114
160
  }
@@ -129,7 +175,10 @@ class StudyData {
129
175
 
130
176
  async delete() {
131
177
  await fs.rmSync(this.studyPath, { recursive: true, force: true });
132
- await fs.rmSync(this.deduplicatedInstancesPath, { recursive: true, force: true });
178
+ await fs.rmSync(this.deduplicatedInstancesPath, {
179
+ recursive: true,
180
+ force: true,
181
+ });
133
182
  await fs.rmSync(this.deduplicatedPath, { recursive: true, force: true });
134
183
  this.clear();
135
184
  }
@@ -159,10 +208,15 @@ class StudyData {
159
208
  * Create a full study instance data for instance at index
160
209
  */
161
210
  async recombine(indexOrSop) {
162
- const index = typeof indexOrSop === "string" ? this.sopInstances[indexOrSop] : indexOrSop;
211
+ const index =
212
+ typeof indexOrSop === "string"
213
+ ? this.sopInstances[indexOrSop]
214
+ : indexOrSop;
163
215
  const deduplicated = this.deduplicated[index];
164
216
  if (index < 0 || index >= this.deduplicated.length) {
165
- throw new Error(`Can't read index ${index}, out of bounds [0..${this.deduplicated.length})`);
217
+ throw new Error(
218
+ `Can't read index ${index}, out of bounds [0..${this.deduplicated.length})`,
219
+ );
166
220
  }
167
221
  const refs = getList(deduplicated, Tags.DeduppedRef);
168
222
  if (!refs) {
@@ -179,7 +233,12 @@ class StudyData {
179
233
 
180
234
  async addExtracted(callback, hashKey, item) {
181
235
  if (this.extractData[hashKey]) {
182
- if (this.verbose) console.log("Already have extracted", hashKey, getValue(item, Tags.DeduppedType));
236
+ if (this.verbose)
237
+ console.log(
238
+ "Already have extracted",
239
+ hashKey,
240
+ getValue(item, Tags.DeduppedType),
241
+ );
183
242
  return;
184
243
  }
185
244
  await callback.bulkdata(this, hashKey, item);
@@ -215,7 +274,7 @@ class StudyData {
215
274
  this.deduplicatedHashes[hashValue] = data;
216
275
  this.readHashes[hashValue] = filename;
217
276
  if (sopIndex !== undefined) {
218
- console.log("Replacing SOP", sopValue, "at index", sopIndex);
277
+ console.noQuiet("Replacing SOP", sopValue, "at index", sopIndex);
219
278
  this.deduplicated[sopIndex] = data;
220
279
  } else {
221
280
  this.sopInstances[sopValue] = this.deduplicated.length;
@@ -282,7 +341,7 @@ class StudyData {
282
341
  async readDeduplicatedFile(dir, stat) {
283
342
  const { hash, name } = stat;
284
343
  try {
285
- if (this.verbose) console.log("Reading deduplicated file", name);
344
+ console.verbose("Reading deduplicated file", name);
286
345
  const data = await JSONReader(dir, name);
287
346
  this.readDeduplicatedData(name, data, hash);
288
347
  } catch (e) {
@@ -322,17 +381,33 @@ class StudyData {
322
381
  const seriesInstance = await this.recombine(i);
323
382
  const type = getValue(seriesInstance, Tags.DeduppedType);
324
383
  if (type == "deleted") {
325
- console.log("Skipping deleted instance", type, getValue(seriesInstance, Tags.SeriesInstanceUID));
384
+ console.log(
385
+ "Skipping deleted instance",
386
+ type,
387
+ getValue(seriesInstance, Tags.SeriesInstanceUID),
388
+ );
326
389
  continue;
327
390
  }
328
391
  const seriesInstanceUid = getSeriesInstanceUid(seriesInstance);
329
392
  if (!seriesInstanceUid) {
330
- console.log("Cant get seriesUid from", Tags.SeriesInstanceUID, seriesInstance);
393
+ console.log(
394
+ "Cant get seriesUid from",
395
+ Tags.SeriesInstanceUID,
396
+ seriesInstance,
397
+ );
331
398
  continue;
332
399
  }
333
400
  if (!series[seriesInstanceUid]) {
334
- const seriesQuery = TagLists.extract(seriesInstance, "series", TagLists.SeriesQuery);
335
- const seriesPath = path.join(this.studyPath, "series", seriesInstanceUid);
401
+ const seriesQuery = TagLists.extract(
402
+ seriesInstance,
403
+ "series",
404
+ TagLists.SeriesQuery,
405
+ );
406
+ const seriesPath = path.join(
407
+ this.studyPath,
408
+ "series",
409
+ seriesInstanceUid,
410
+ );
336
411
  series[seriesInstanceUid] = {
337
412
  seriesPath,
338
413
  seriesQuery,
@@ -341,16 +416,20 @@ class StudyData {
341
416
  };
342
417
  }
343
418
  series[seriesInstanceUid].instances.push(seriesInstance);
344
- series[seriesInstanceUid].instancesQuery.push(TagLists.extract(seriesInstance, "instance", TagLists.InstanceQuery));
419
+ series[seriesInstanceUid].instancesQuery.push(
420
+ TagLists.extract(seriesInstance, "instance", TagLists.InstanceQuery),
421
+ );
345
422
  }
346
423
 
347
424
  const seriesList = [];
348
425
  const modalitiesInStudy = [];
349
426
  let numberOfInstances = 0;
350
427
  let numberOfSeries = 0;
428
+
351
429
  for (const seriesUid of Object.keys(series)) {
352
430
  const singleSeries = series[seriesUid];
353
- const { seriesQuery, seriesPath, instances, instancesQuery } = singleSeries;
431
+ const { seriesQuery, seriesPath, instances, instancesQuery } =
432
+ singleSeries;
354
433
  seriesQuery[Tags.NumberOfSeriesRelatedInstances] = {
355
434
  vr: "IS",
356
435
  Value: [instances.length],
@@ -359,20 +438,39 @@ class StudyData {
359
438
  numberOfSeries += 1;
360
439
  const modality = getValue(seriesQuery, Tags.Modality);
361
440
  seriesList.push(seriesQuery);
362
- if (modalitiesInStudy.indexOf(modality) == -1) modalitiesInStudy.push(modality);
441
+ if (modalitiesInStudy.indexOf(modality) == -1)
442
+ modalitiesInStudy.push(modality);
363
443
  await JSONWriter(seriesPath, "metadata", instances, {
364
444
  gzip: true,
365
445
  index: false,
366
446
  });
367
447
  // Write out a series singleton that has just the series response for a single series.
368
- await JSONWriter(seriesPath, "series-singleton.json", [seriesQuery], { gzip: true, index: false });
448
+ await JSONWriter(seriesPath, "series-singleton.json", [seriesQuery], {
449
+ gzip: true,
450
+ index: false,
451
+ });
369
452
  await JSONWriter(seriesPath, "instances", instancesQuery);
453
+ console.noQuiet(
454
+ "Wrote instances",
455
+ seriesUid,
456
+ "with",
457
+ instancesQuery.length,
458
+ );
370
459
  }
371
460
 
372
461
  await JSONWriter(this.studyPath, "series", seriesList);
373
- console.log("Wrote series with", seriesList.length);
462
+ console.noQuiet(
463
+ "Wrote series",
464
+ this.studyInstanceUid,
465
+ "with",
466
+ seriesList.length,
467
+ );
374
468
 
375
- const studyQuery = TagLists.extract(anInstance, "study", TagLists.PatientStudyQuery);
469
+ const studyQuery = TagLists.extract(
470
+ anInstance,
471
+ "study",
472
+ TagLists.PatientStudyQuery,
473
+ );
376
474
  studyQuery[Tags.ModalitiesInStudy] = { Value: modalitiesInStudy, vr: "CS" };
377
475
  studyQuery[Tags.NumberOfStudyRelatedInstances] = {
378
476
  Value: [numberOfInstances],
@@ -389,8 +487,18 @@ class StudyData {
389
487
  });
390
488
 
391
489
  const infoItem = this.createInfo();
392
- console.log("Writing deduplicated study data with", Object.values(this.extractData).length, "extract items and", this.deduplicated.length, "instance items");
393
- await JSONWriter(this.studyPath, "deduplicated", [infoItem, ...Object.values(this.extractData), ...this.deduplicated]);
490
+ console.noQuiet(
491
+ "Writing deduplicated study data with",
492
+ Object.values(this.extractData).length,
493
+ "extract items and",
494
+ this.deduplicated.length,
495
+ "instance items",
496
+ );
497
+ await JSONWriter(this.studyPath, "deduplicated", [
498
+ infoItem,
499
+ ...Object.values(this.extractData),
500
+ ...this.deduplicated,
501
+ ]);
394
502
 
395
503
  return studyQuery;
396
504
  }
@@ -406,7 +514,6 @@ class StudyData {
406
514
  return data;
407
515
  }
408
516
 
409
- /* eslint-disable-next-line class-methods-use-this */
410
517
  removeGz(name) {
411
518
  const gzIndex = name.indexOf(".gz");
412
519
  return (gzIndex > 0 && name.substring(0, gzIndex)) || name;
@@ -415,15 +522,19 @@ class StudyData {
415
522
  async deleteInstancesReferenced() {
416
523
  const deduplicatedDirectory = this.deduplicatedInstancesPath;
417
524
  if (!fs.existsSync(deduplicatedDirectory)) return;
418
- console.log("Deleting instances referenced in", this.studyInstanceUid, this.deduplicatedInstancesPath);
525
+ console.log(
526
+ "Deleting instances referenced in",
527
+ this.studyInstanceUid,
528
+ this.deduplicatedInstancesPath,
529
+ );
419
530
  const files = await this.listJsonFiles(deduplicatedDirectory);
420
- console.log("Deleting", files.length, "files");
531
+ console.noQuiet("Deleting", files.length, "files");
421
532
  let deleteCount = 0;
422
533
  for (let i = 0; i < files.length; i++) {
423
534
  const stat = files[i];
424
535
  const { hash } = stat;
425
536
  if (this.readHashes[hash]) {
426
- console.log("Deleting", deduplicatedDirectory, stat.name);
537
+ console.noQuiet("Deleting", deduplicatedDirectory, stat.name);
427
538
  try {
428
539
  fs.unlinkSync(path.join(deduplicatedDirectory, stat.name));
429
540
  deleteCount += 1;
@@ -433,7 +544,7 @@ class StudyData {
433
544
  }
434
545
  }
435
546
  if (deleteCount === files.length) {
436
- console.log("Deleting instances directory", deduplicatedDirectory);
547
+ console.noQuiet("Deleting instances directory", deduplicatedDirectory);
437
548
  fs.rmdirSync(deduplicatedDirectory);
438
549
  }
439
550
  }
@@ -442,25 +553,34 @@ class StudyData {
442
553
  async writeDeduplicatedGroup() {
443
554
  const data = this.createInfo();
444
555
  const hashValue = getValue(data, Tags.DeduppedHash);
445
- console.log(
556
+ console.noQuiet(
446
557
  "Writing deduplicated hash data set data with",
447
558
  Object.values(this.extractData).length,
448
559
  "extract items and",
449
560
  this.deduplicated.length,
450
- "instance items"
561
+ "instance items",
451
562
  );
452
563
  setList(
453
564
  data,
454
565
  Tags.DeduppedRef,
455
- Object.keys(this.readHashes).filter(() => this.deduplicatedHashes[hashValue] == undefined)
566
+ Object.keys(this.readHashes).filter(
567
+ () => this.deduplicatedHashes[hashValue] == undefined,
568
+ ),
456
569
  );
457
- const deduplicatedList = [data, ...Object.values(this.extractData), ...this.deduplicated];
570
+ const deduplicatedList = [
571
+ data,
572
+ ...Object.values(this.extractData),
573
+ ...this.deduplicated,
574
+ ];
458
575
  // const naturalList = deduplicatedList.map(Tags.naturalizeDataset);
459
576
  // // console.log("naturalList=", JSON.stringify(naturalList,null,2));
460
577
  // console.log("Going to write study data", naturalList.length);
461
578
  // await JSONWriter(this.deduplicatedPath, hashValue, naturalList, { gzip: true, index: false });
462
- await JSONWriter(this.deduplicatedPath, hashValue, deduplicatedList, { gzip: true, index: false });
463
- console.log("Wrote naturalized dataset");
579
+ await JSONWriter(this.deduplicatedPath, hashValue, deduplicatedList, {
580
+ gzip: true,
581
+ index: false,
582
+ });
583
+ console.noQuiet("Wrote naturalized dataset");
464
584
  }
465
585
 
466
586
  getSopUids() {
@@ -56,8 +56,12 @@ class ThumbnailService {
56
56
  * @param {Object} programOpts
57
57
  */
58
58
  queueThumbnail(thumbObjWrapper, programOpts) {
59
- const { id, imageFrame, transcodedId, transcodedImageFrame, frameIndex } = thumbObjWrapper;
60
- const getThumbContent = (originalContent, trancodedContent) => (shouldThumbUseTranscoded(id, programOpts) ? trancodedContent : originalContent);
59
+ const { id, imageFrame, transcodedId, transcodedImageFrame, frameIndex } =
60
+ thumbObjWrapper;
61
+ const getThumbContent = (originalContent, trancodedContent) =>
62
+ shouldThumbUseTranscoded(id, programOpts)
63
+ ? trancodedContent
64
+ : originalContent;
61
65
 
62
66
  const thumbObj = {
63
67
  imageFrame: getThumbContent(imageFrame, transcodedImageFrame),
@@ -77,7 +81,9 @@ class ThumbnailService {
77
81
  }
78
82
 
79
83
  ffmpeg(input, output) {
80
- execSpawn(`ffmpeg -i "${input}" -vf "thumbnail,scale=640:360" -frames:v 1 -f singlejpeg "${output}"`);
84
+ execSpawn(
85
+ `ffmpeg -i "${input}" -y -f image2 -frames:v 1 -update true "${output}"`,
86
+ );
81
87
  }
82
88
 
83
89
  dcm2jpg(input, output, options) {
@@ -110,47 +116,70 @@ class ThumbnailService {
110
116
  async generateThumbnails(itemId, dataSet, metadata, callback, options) {
111
117
  const { imageFrame, id } = this.favoriteThumbnailObj;
112
118
 
113
- if (!options.thumb) {
114
- return null;
115
- }
116
-
117
119
  // There are various reasons no thumbnails might be generated, so just return
118
120
  if (!id) {
119
121
  const pixelData = metadata[Tags.PixelData];
120
122
  if (pixelData) {
121
123
  const { BulkDataURI } = pixelData;
122
124
  if (BulkDataURI?.indexOf("mp4")) {
123
- fs.mkdirSync(`${itemId.sopInstanceRootPath}/rendered`, { recursive: true });
124
- const mp4Path = path.join(itemId.sopInstanceRootPath, "rendered/index.mp4");
125
- // Generate as rendered, as more back ends support that.
126
- const thumbPath = path.join(itemId.sopInstanceRootPath, "rendered/1.jpg");
127
- console.log("MP4 - converting video format", mp4Path);
125
+ fs.mkdirSync(`${itemId.sopInstanceRootPath}/rendered`, {
126
+ recursive: true,
127
+ });
128
+ const mp4Path = path.join(
129
+ itemId.sopInstanceRootPath,
130
+ "rendered/index.mp4",
131
+ );
132
+ const thumbPath = path.join(itemId.sopInstanceRootPath, "thumbnail");
133
+ console.verbose("MP4 - converting video format", mp4Path);
128
134
  this.ffmpeg(mp4Path, thumbPath);
129
135
  return thumbPath;
130
136
  }
131
137
  } else {
132
- console.log("Series is of other type...", metadata[Tags.Modality]);
138
+ console.verbose("Series is of other type...", metadata[Tags.Modality]);
133
139
  }
134
140
  return null;
135
141
  }
136
- if (options.dcm2jpg) {
137
- return this.dcm2jpg(id.filename, id.imageFrameRootPath.replace(/frames/, "thumbnail"), {});
142
+
143
+ if (!options.thumb) {
144
+ return null;
138
145
  }
139
146
 
140
- await callback.internalGenerateImage(imageFrame, dataSet, metadata, id.transferSyntaxUid, async (thumbBuffer) => {
141
- try {
142
- if (thumbBuffer) {
143
- await callback.thumbWriter(id.sopInstanceRootPath, this.thumbFileName, thumbBuffer);
147
+ if (options.dcm2jpg) {
148
+ return this.dcm2jpg(
149
+ id.filename,
150
+ id.imageFrameRootPath.replace(/frames/, "thumbnail"),
151
+ {},
152
+ );
153
+ }
144
154
 
145
- this.copySyncThumbnail(id.sopInstanceRootPath, id.seriesRootPath);
146
- this.copySyncThumbnail(id.seriesRootPath, id.studyPath);
147
- Stats.StudyStats.add("Thumbnail Write", `Write thumbnail ${this.thumbFileName}`, 100);
155
+ await callback.internalGenerateImage(
156
+ imageFrame,
157
+ dataSet,
158
+ metadata,
159
+ id.transferSyntaxUid,
160
+ async (thumbBuffer) => {
161
+ try {
162
+ if (thumbBuffer) {
163
+ await callback.thumbWriter(
164
+ id.sopInstanceRootPath,
165
+ this.thumbFileName,
166
+ thumbBuffer,
167
+ );
168
+
169
+ this.copySyncThumbnail(id.sopInstanceRootPath, id.seriesRootPath);
170
+ this.copySyncThumbnail(id.seriesRootPath, id.studyPath);
171
+ Stats.StudyStats.add(
172
+ "Thumbnail Write",
173
+ `Write thumbnail ${this.thumbFileName}`,
174
+ 100,
175
+ );
176
+ }
177
+ return this.thumbFileName;
178
+ } catch (e) {
179
+ console.log("Couldn't generate thumbnail", this.thumbFileName, e);
148
180
  }
149
- return this.thumbFileName;
150
- } catch (e) {
151
- console.log("Couldn't generate thumbnail", this.thumbFileName, e);
152
- }
153
- });
181
+ },
182
+ );
154
183
  }
155
184
 
156
185
  /**
@@ -164,13 +193,15 @@ class ThumbnailService {
164
193
  */
165
194
  async copySyncThumbnail(sourceFolderPath, targetFolderPath) {
166
195
  const parentPathLevel = path.join(sourceFolderPath, "../");
167
- const thumbFilesPath = glob.sync(`${parentPathLevel}*/${this.thumbFileName}`);
196
+ const thumbFilesPath = glob.sync(
197
+ `${parentPathLevel}*/${this.thumbFileName}`,
198
+ );
168
199
 
169
200
  const thumbIndex = getThumbIndex(thumbFilesPath.length);
170
201
  const thumbFilePath = thumbFilesPath[thumbIndex];
171
202
 
172
203
  if (!fs.existsSync(thumbFilePath)) {
173
- console.log("Thumbnail to copy does not exists");
204
+ console.verbose("Thumbnail to copy does not exists");
174
205
  return;
175
206
  }
176
207
 
@@ -179,9 +210,12 @@ class ThumbnailService {
179
210
  throw new Error(`Target path: ${targetFolderPath} is not a directory`);
180
211
  }
181
212
 
182
- fs.copyFileSync(thumbFilePath, `${targetFolderPath}/${this.thumbFileName}`);
213
+ fs.copyFileSync(
214
+ thumbFilePath,
215
+ `${targetFolderPath}/${this.thumbFileName}`,
216
+ );
183
217
  } catch (e) {
184
- console.log("The file could not be copied", e);
218
+ console.verbose("The file could not be copied", e);
185
219
  }
186
220
  }
187
221
  }
@@ -1,8 +1,11 @@
1
1
  const dicomCodec = require("@cornerstonejs/dicom-codec");
2
2
  const getImageInfo = require("./getImageInfo");
3
3
 
4
- async function decodeImage(imageFrame, dataset, transferSyntaxUid) {
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,7 +1,25 @@
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
+ 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 =
12
+ Tags.getValue(instance, Tags.PixelRepresentation) || 0; // not yet being used.
13
+
14
+ return {
15
+ bitsAllocated,
16
+ samplesPerPixel,
17
+ rows, // Number with the image rows/height
18
+ columns, // Number with the image columns/width,
19
+ signed: pixelRepresentation === 1,
20
+ pixelRepresentation,
21
+ };
22
+ }
5
23
  const rows = dataSet.uint16("x00280010");
6
24
  const columns = dataSet.uint16("x00280011");
7
25
  const bitsAllocated = dataSet.uint16("x00280100");