@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,7 +1,7 @@
1
- const extractImageFrames = require("./extractImageFrames");
1
+ const extractImageFrames = require('./extractImageFrames');
2
2
 
3
3
  function decodeUtf8(s, options) {
4
- if (options?.SpecificCharacterSet === "ISO_IR 192") {
4
+ if (options?.SpecificCharacterSet === 'ISO_IR 192') {
5
5
  return decodeURIComponent(escape(s));
6
6
  }
7
7
  return s;
@@ -22,19 +22,14 @@ const getStrings = (dataSet, attr, options) => {
22
22
 
23
23
  const getValuePatientName = (dataSet, attr) => {
24
24
  const strings = getStrings(dataSet, attr);
25
- return (
26
- (strings && strings.map((item) => ({ Alphabetic: item }))) || undefined
27
- );
25
+ return (strings && strings.map(item => ({ Alphabetic: item }))) || undefined;
28
26
  };
29
27
 
30
28
  /** Gets either InlineBinary or BulkDataURI, if already defined */
31
29
  const getValueInlineBinary = (dataSet, attr) => {
32
30
  if (attr.BulkDataURI) return { BulkDataURI: attr.BulkDataURI };
33
- const binaryValue = dataSet.byteArray.slice(
34
- attr.dataOffset,
35
- attr.dataOffset + attr.length
36
- );
37
- return { InlineBinary: binaryValue.toString("base64") };
31
+ const binaryValue = dataSet.byteArray.slice(attr.dataOffset, attr.dataOffset + attr.length);
32
+ return { InlineBinary: binaryValue.toString('base64') };
38
33
  };
39
34
 
40
35
  const getValueInlineSignedShort = (dataSet, attr) => {
@@ -79,10 +74,10 @@ const getValueInlineFloat = (dataSet, attr) => {
79
74
  };
80
75
 
81
76
  const getValueInlineIntString = (dataSet, attr) =>
82
- getStrings(dataSet, attr).map((val) => parseInt(val));
77
+ getStrings(dataSet, attr).map(val => parseInt(val));
83
78
 
84
79
  const getValueInlineFloatString = (dataSet, attr) =>
85
- getStrings(dataSet, attr).map((val) => parseFloat(val));
80
+ getStrings(dataSet, attr).map(val => parseFloat(val));
86
81
 
87
82
  const getValueInlineFloatDouble = (dataSet, attr) => {
88
83
  if (attr.length > 8) {
@@ -105,63 +100,63 @@ const getValueInline = (dataSet, attr, vr, options) => {
105
100
  }
106
101
 
107
102
  switch (vr) {
108
- case "AE":
109
- case "AS":
103
+ case 'AE':
104
+ case 'AS':
110
105
  return getValueInlineString(dataSet, attr, options);
111
- case "AT":
106
+ case 'AT':
112
107
  return getValueInlineAttributeTag(dataSet, attr);
113
- case "DS":
108
+ case 'DS':
114
109
  return getValueInlineFloatString(dataSet, attr);
115
- case "CS":
116
- case "DA":
117
- case "DT":
110
+ case 'CS':
111
+ case 'DA':
112
+ case 'DT':
118
113
  return getValueInlineString(dataSet, attr, options);
119
- case "FL":
114
+ case 'FL':
120
115
  return getValueInlineFloat(dataSet, attr);
121
- case "FD":
116
+ case 'FD':
122
117
  return getValueInlineFloatDouble(dataSet, attr);
123
- case "IS":
118
+ case 'IS':
124
119
  return getValueInlineIntString(dataSet, attr);
125
- case "LO":
126
- case "LT":
120
+ case 'LO':
121
+ case 'LT':
127
122
  return getValueInlineString(dataSet, attr, options);
128
- case "OB":
129
- case "OF":
130
- case "OW":
123
+ case 'OB':
124
+ case 'OF':
125
+ case 'OW':
131
126
  return getValueInlineBinary(dataSet, attr);
132
- case "PN":
127
+ case 'PN':
133
128
  return getValuePatientName(dataSet, attr);
134
- case "SH":
129
+ case 'SH':
135
130
  return getValueInlineString(dataSet, attr, options);
136
- case "SL":
131
+ case 'SL':
137
132
  return getValueInlineSignedLong(dataSet, attr);
138
- case "SS":
133
+ case 'SS':
139
134
  return getValueInlineSignedShort(dataSet, attr);
140
- case "ST":
141
- case "TM":
142
- case "UI":
135
+ case 'ST':
136
+ case 'TM':
137
+ case 'UI':
143
138
  return getValueInlineString(dataSet, attr, options);
144
- case "UL":
139
+ case 'UL':
145
140
  return getValueInlineUnsignedLong(dataSet, attr);
146
- case "UN":
141
+ case 'UN':
147
142
  return getValueInlineBinary(dataSet, attr);
148
- case "US":
143
+ case 'US':
149
144
  return getValueInlineUnsignedShort(dataSet, attr);
150
- case "UT":
145
+ case 'UT':
151
146
  return getValueInlineString(dataSet, attr, options);
152
147
  default:
153
148
  return getValueInlineBinary(dataSet, attr);
154
149
  }
155
150
  };
156
151
 
157
- const isPrivate = (attr) => {
152
+ const isPrivate = attr => {
158
153
  const { tag } = attr;
159
154
  const ch = tag.substr(4, 1);
160
155
  const chHex = parseInt(ch, 16);
161
156
  return chHex % 2 == 1;
162
157
  };
163
158
 
164
- const RawEncapsulatedDocument = "x00420011";
159
+ const RawEncapsulatedDocument = 'x00420011';
165
160
 
166
161
  const isValueInline = (attr, options) => {
167
162
  if (isPrivate(attr)) {
@@ -187,30 +182,15 @@ const isValueInline = (attr, options) => {
187
182
  * @param {*} parentAttr attr's parent. If not present means attr is at root level.
188
183
  * @returns
189
184
  */
190
- const getValue = async (
191
- dataSet,
192
- attr,
193
- vr,
194
- getDataSet,
195
- callback,
196
- options,
197
- parentAttr
198
- ) => {
185
+ const getValue = async (dataSet, attr, vr, getDataSet, callback, options, parentAttr) => {
199
186
  // It will only process pixelData tag if on metadata root. Otherwise it will be skiped.
200
- if (attr.tag === "x7fe00010" && !parentAttr) {
201
- const BulkDataURI = await extractImageFrames(
202
- dataSet,
203
- attr,
204
- vr,
205
- callback,
206
- options
207
- );
187
+ if (attr.tag === 'x7fe00010' && !parentAttr) {
188
+ const BulkDataURI = await extractImageFrames(dataSet, attr, vr, callback, options);
208
189
  return { BulkDataURI };
209
190
  }
210
- if (attr.tag === "xfffee00d") return undefined;
191
+ if (attr.tag === 'xfffee00d') return undefined;
211
192
  // Group length
212
- if (attr.tag.length == 9 && attr.tag.substring(5, 9) === "0000")
213
- return undefined;
193
+ if (attr.tag.length == 9 && attr.tag.substring(5, 9) === '0000') return undefined;
214
194
  if (attr.items) {
215
195
  // sequences
216
196
  const result = [];
@@ -227,11 +207,8 @@ const getValue = async (
227
207
  if (isValueInline(attr, options)) {
228
208
  return getValueInline(dataSet, attr, vr, options);
229
209
  }
230
- const binaryValue = dataSet.byteArray.slice(
231
- attr.dataOffset,
232
- attr.dataOffset + attr.length
233
- );
234
- const mimeType = attr.tag == "x00420011" && dataSet.string("x00420012");
210
+ const binaryValue = dataSet.byteArray.slice(attr.dataOffset, attr.dataOffset + attr.length);
211
+ const mimeType = attr.tag == 'x00420011' && dataSet.string('x00420012');
235
212
 
236
213
  const BulkDataURI = await callback.bulkdata(binaryValue, { mimeType });
237
214
  return { BulkDataURI, vr };
@@ -0,0 +1,40 @@
1
+ const { Tags } = require('@radicalimaging/static-wado-util');
2
+
3
+ function validateMetadata(options) {
4
+ const serviceUrl = '';
5
+ const { verify, multipart: stowResponse } = options;
6
+
7
+ return function (id, result) {
8
+ const getValue = Tags.getValue.bind(null, result.metadata);
9
+ if (verify) {
10
+ console.verbose('Validating metadata', id);
11
+ if (id.studyInstanceUid.length > 64) {
12
+ throw new Error(`StudyInstanceUID too long: ${id.studyInstanceUid}`);
13
+ }
14
+ if (id.seriesInstanceUid.length > 64) {
15
+ throw new Error(`SeriesInstanceUID too long: ${id.studyInstanceUid}`);
16
+ }
17
+ }
18
+ if (stowResponse) {
19
+ const RetrieveURL = `${serviceUrl}${id.studyInstanceUid}/series/${id.seriesInstanceUid}/instances/${id.sopInstanceUid}`;
20
+
21
+ // TODO - add check here from plugin to add remote query checks, eg kheops or similar
22
+ const { filename } = id;
23
+ const successMessage = {
24
+ ReferencedSOPSequence: [
25
+ {
26
+ StudyInstanceUID: id.studyInstanceUid,
27
+ SeriesInstanceUID: id.seriesInstanceUid,
28
+ ReferencedSOPClassUID: getValue(Tags.SOPClassUID),
29
+ ReferencedSOPInstanceUID: id.sopInstanceUid,
30
+ RetrieveURL,
31
+ StorageURL: filename,
32
+ },
33
+ ],
34
+ };
35
+ this.success('instanceStored', successMessage, filename);
36
+ }
37
+ };
38
+ }
39
+
40
+ module.exports = validateMetadata;
@@ -1,4 +1,4 @@
1
- const staticWadoUtil = require("@radicalimaging/static-wado-util");
1
+ const staticWadoUtil = require('@radicalimaging/static-wado-util');
2
2
 
3
3
  /**
4
4
  * Configure static-wado-creator commander program.
@@ -8,8 +8,7 @@ const staticWadoUtil = require("@radicalimaging/static-wado-util");
8
8
  */
9
9
  async function configureProgram(defaults) {
10
10
  const configurationFile = await staticWadoUtil.loadConfiguration(defaults, process.argv);
11
- console.log("Loaded configuration from", configurationFile);
12
- staticWadoUtil.configureCommands(defaults);
11
+ return staticWadoUtil.configureCommands(defaults);
13
12
  }
14
13
 
15
14
  exports.configureProgram = configureProgram;
package/lib/rejectMain.js CHANGED
@@ -1,5 +1,5 @@
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 rejectMain(args, options) {
5
5
  const finalOptions = adaptProgramOpts(options, {
@@ -0,0 +1,21 @@
1
+ const readline = require('node:readline');
2
+ const { argv, argv0, exit, stdin: input, stdout: output } = require('node:process');
3
+
4
+ const StaticWado = require('./StaticWado');
5
+ const adaptProgramOpts = require('./util/adaptProgramOpts');
6
+
7
+ module.exports = async function createMain(options, program) {
8
+ const rl = readline.createInterface({ input, output });
9
+ for await (const line of rl) {
10
+ if (!line) continue;
11
+ if (line.startsWith('exit')) {
12
+ exit(0);
13
+ }
14
+ argv.splice(2, argv.length - 2, ...line.split(' '));
15
+ console.verbose('exec mkdicomweb', JSON.stringify(argv));
16
+
17
+ // Configure program commander
18
+ await StaticWado.configureProgram(StaticWado.mkdicomwebConfig);
19
+ output.write('mkdicomweb server -->\n');
20
+ }
21
+ };
@@ -1,13 +1,30 @@
1
- const path = require("path");
1
+ const path = require('path');
2
+ const { getStudyUIDPathAndSubPath } = require('@radicalimaging/static-wado-util');
2
3
 
3
- function IdCreator({ directoryName, deduplicatedRoot, deduplicatedInstancesRoot }) {
4
+ function IdCreator({
5
+ directoryName,
6
+ deduplicatedRoot,
7
+ deduplicatedInstancesRoot,
8
+ hashStudyUidPath,
9
+ }) {
4
10
  return (uids, filename) => {
5
- const studyPath = path.join(directoryName, "studies", uids.studyInstanceUid);
6
- const seriesRootPath = path.join(studyPath, "series", uids.seriesInstanceUid);
7
- const sopInstanceRootPath = path.join(studyPath, "series", uids.seriesInstanceUid, "instances", uids.sopInstanceUid);
8
- const deduplicatedPath = path.join(deduplicatedRoot, uids.studyInstanceUid);
9
- const deduplicatedInstancesPath = path.join(deduplicatedInstancesRoot, uids.studyInstanceUid);
10
- const imageFrameRootPath = path.join(sopInstanceRootPath, "frames");
11
+ const { studyInstanceUid, seriesInstanceUid, sopInstanceUid } = uids;
12
+
13
+ const { path: hashPath = '', subpath: hashSubpath = '' } = hashStudyUidPath
14
+ ? getStudyUIDPathAndSubPath(studyInstanceUid)
15
+ : {};
16
+
17
+ const studySubDir = hashStudyUidPath
18
+ ? path.join(hashPath, hashSubpath, studyInstanceUid)
19
+ : studyInstanceUid;
20
+
21
+ const studyPath = path.join(directoryName, 'studies', studySubDir);
22
+ const seriesRootPath = path.join(studyPath, 'series', seriesInstanceUid);
23
+ const sopInstanceRootPath = path.join(seriesRootPath, 'instances', sopInstanceUid);
24
+ const imageFrameRootPath = path.join(sopInstanceRootPath, 'frames');
25
+
26
+ const deduplicatedPath = path.join(deduplicatedRoot, studySubDir);
27
+ const deduplicatedInstancesPath = path.join(deduplicatedInstancesRoot, studySubDir);
11
28
 
12
29
  return {
13
30
  ...uids,
@@ -1,5 +1,5 @@
1
1
  const assignDefined = (dest, src) => {
2
- Object.keys(src).forEach((key) => {
2
+ Object.keys(src).forEach(key => {
3
3
  const val = src[key];
4
4
  if (val !== undefined) {
5
5
  dest[key] = val;
@@ -1,12 +1,12 @@
1
- const { program } = require("@radicalimaging/static-wado-util");
2
- const { getDestinationTranscoder } = require("../operation/adapter/transcodeImage");
1
+ const { program } = require('@radicalimaging/static-wado-util');
2
+ const { getDestinationTranscoder } = require('../operation/adapter/transcodeImage');
3
3
 
4
4
  module.exports = function compressionOptionParser(value) {
5
5
  const destination = getDestinationTranscoder(value);
6
6
  if (destination) {
7
- console.log("Found destination transferSyntax ", destination.transferSyntaxUid, "for ", value);
7
+ console.log('Found destination transferSyntax ', destination.transferSyntaxUid, 'for ', value);
8
8
  } else {
9
- console.log("No transcoder destination for ", value);
9
+ console.log('No transcoder destination for ', value);
10
10
  throw new program.InvalidArgumentError(`No transcoder destination for ${value}`);
11
11
  }
12
12
  return destination.transferSyntaxUid;
@@ -0,0 +1,87 @@
1
+ import fs from 'fs';
2
+
3
+ const results = [];
4
+ let sopResponse = null;
5
+
6
+ function ensureSOPResponse(data) {
7
+ if (!data.ReferencedSOPSequence && !data.FailedSOPSequence) {
8
+ return;
9
+ }
10
+ if (!sopResponse) {
11
+ sopResponse = {
12
+ ...data,
13
+ FailedSOPSequence: undefined,
14
+ ReferencedSOPSequence: undefined,
15
+ };
16
+ results.push(sopResponse);
17
+ }
18
+ if (data.ReferencedSOPSequence) {
19
+ sopResponse.ReferencedSOPSequence ||= [];
20
+ sopResponse.ReferencedSOPSequence.push(...data.ReferencedSOPSequence);
21
+ }
22
+ if (data.FailedSOPSequence) {
23
+ sopResponse.FailedSOPSequence ||= [];
24
+ sopResponse.FailedSOPSequence.push(...data.FailedSOPSequence);
25
+ }
26
+ }
27
+ export function Success(options) {
28
+ const { deleteInput = false } = options;
29
+
30
+ return (_action, data, file) => {
31
+ if (data.ReferencedSOPSequence) {
32
+ ensureSOPResponse(data);
33
+ } else {
34
+ results.push(data);
35
+ }
36
+ if (file && deleteInput) {
37
+ console.noQuiet('Deleting', file);
38
+ fs.unlinkSync(file);
39
+ } else if (file) {
40
+ console.verbose('Not deleting', file);
41
+ }
42
+ };
43
+ }
44
+
45
+ export function Failure(options) {
46
+ const { deleteFailed } = options;
47
+
48
+ return (_action, data, file) => {
49
+ if (data.FailedSOPSequence) {
50
+ ensureSOPResponse(data);
51
+ } else {
52
+ results.push(data);
53
+ }
54
+ if (file && deleteFailed) {
55
+ console.noQuiet('Deleting failure', file);
56
+ fs.unlinkSync(file);
57
+ } else if (file) {
58
+ console.verbose('Not deleting failure file', file);
59
+ }
60
+ };
61
+ }
62
+
63
+ export function clearResults() {
64
+ results.splice(0, results.length);
65
+ sopResponse = null;
66
+ }
67
+
68
+ export function WriteResults(options) {
69
+ const { multipart } = options;
70
+
71
+ return function () {
72
+ if (multipart) {
73
+ const str =
74
+ results.length === 1
75
+ ? JSON.stringify(results[0])
76
+ : `[\n${results.map(r => JSON.stringify(r, null, 2)).join(',\n')}\n]`;
77
+ console.log(
78
+ '\r\n--boundary-response\r\n' +
79
+ 'content-type: application/json\r\n\r\n' +
80
+ str +
81
+ '\r\n--boundary-response--\r\n'
82
+ );
83
+ }
84
+
85
+ clearResults();
86
+ };
87
+ }
@@ -1,6 +1,6 @@
1
- const { JSONReader, JSONWriter, Stats } = require("@radicalimaging/static-wado-util");
2
- const { Tags } = require("@radicalimaging/static-wado-util");
3
- const StudyData = require("../operation/StudyData");
1
+ const { JSONReader, JSONWriter, Stats } = require('@radicalimaging/static-wado-util');
2
+ const { Tags } = require('@radicalimaging/static-wado-util');
3
+ const StudyData = require('../operation/StudyData');
4
4
 
5
5
  /**
6
6
  * CompleteStudyWriter takes the deduplicated data values, all loaded into the study parameter,
@@ -8,13 +8,13 @@ const StudyData = require("../operation/StudyData");
8
8
  * written out as.
9
9
  * The studyData object is then removed, so that a new one can be created if required.
10
10
  */
11
- const CompleteStudyWriter = (options) => {
11
+ const CompleteStudyWriter = options => {
12
12
  async function ret() {
13
13
  const { studyData } = this;
14
14
  if (!studyData) return;
15
15
 
16
16
  if (!studyData.numberOfInstances) {
17
- console.log("studyData.deduplicated is empty");
17
+ console.log('studyData.deduplicated is empty');
18
18
  delete this.studyData;
19
19
  return;
20
20
  }
@@ -22,15 +22,23 @@ const CompleteStudyWriter = (options) => {
22
22
  if (options.isGroup) {
23
23
  if (studyData.dirty) {
24
24
  await studyData.writeDeduplicatedGroup();
25
- console.log("Wrote updated deduplicated data for study", studyData.studyInstanceUid);
25
+ console.noQuiet('Wrote updated deduplicated data for study', studyData.studyInstanceUid);
26
26
  } else {
27
- console.log("Not writing new deduplicated data because it is clean:", studyData.studyInstanceUid);
27
+ console.noQuiet(
28
+ 'Not writing new deduplicated data because it is clean:',
29
+ studyData.studyInstanceUid
30
+ );
31
+ const message = {
32
+ text: 'Metadata clean, not updating',
33
+ status: 0,
34
+ studyInstanceUid: studyData.studyInstanceUid,
35
+ };
36
+ this.success('metadataClean', message);
28
37
  }
29
38
  await studyData.deleteInstancesReferenced();
30
39
  }
31
40
 
32
41
  if (!options.isStudyData) {
33
- console.verbose("Not configured to write study metadata", studyData.studyInstanceUid);
34
42
  if (options.notifications) this.notificationService.notifyStudy(studyData.studyInstanceUid);
35
43
  delete this.studyData;
36
44
  Stats.StudyStats.summarize();
@@ -38,32 +46,44 @@ const CompleteStudyWriter = (options) => {
38
46
  }
39
47
 
40
48
  const isDirtyMetadata = await studyData.dirtyMetadata();
41
- if (!isDirtyMetadata) {
42
- console.log("Study metadata", studyData.studyInstanceUid, "is clean.");
49
+ if (!isDirtyMetadata && !options.force) {
50
+ console.log('Study metadata', studyData.studyInstanceUid, 'is clean.');
43
51
  delete this.studyData;
44
- Stats.StudyStats.summarize(`Study metadata ${studyData.studyInstanceUid} has clean metadata, not writing`);
52
+ Stats.StudyStats.summarize(
53
+ `Study metadata ${studyData.studyInstanceUid} has clean metadata, not writing`
54
+ );
45
55
  return;
46
56
  }
47
- console.log("Writing study metadata", studyData.studyInstanceUid);
57
+ console.noQuiet('Writing study metadata', studyData.studyInstanceUid);
48
58
 
49
59
  const studyQuery = await studyData.writeMetadata();
50
60
 
51
- const allStudies = await JSONReader(options.directoryName, "studies/index.json.gz", []);
61
+ const allStudies = await JSONReader(options.directoryName, 'studies/index.json.gz', []);
52
62
  const studyUID = Tags.getValue(studyQuery, Tags.StudyInstanceUID);
53
63
  if (!studyUID) {
54
- console.error("studyQuery=", studyQuery);
55
- throw new Error("Study query has null studyUID");
64
+ console.error('studyQuery=', studyQuery);
65
+ throw new Error('Study query has null studyUID');
56
66
  }
57
- const studyIndex = allStudies.findIndex((item) => Tags.getValue(item, Tags.StudyInstanceUID) == studyUID);
67
+ const studyIndex = allStudies.findIndex(
68
+ item => Tags.getValue(item, Tags.StudyInstanceUID) == studyUID
69
+ );
58
70
  if (studyIndex == -1) {
59
71
  allStudies.push(studyQuery);
60
72
  } else {
61
73
  allStudies[studyIndex] = studyQuery;
62
74
  }
63
- await JSONWriter(options.directoryName, "studies", allStudies);
75
+ await JSONWriter(options.directoryName, 'studies', allStudies);
64
76
  if (options.notifications) this.notificationService.notifyStudy(studyData.studyInstanceUid);
77
+ const message = {
78
+ action: 'metadata',
79
+ status: 0,
80
+ studyInstanceUid: studyData.studyInstanceUid,
81
+ };
82
+ this.success('completeStudyWriter', message);
65
83
  delete this.studyData;
66
- Stats.StudyStats.summarize(`Wrote study metadata/query files for ${studyData.studyInstanceUid}`);
84
+ Stats.StudyStats.summarize(
85
+ `Wrote study metadata/query files for ${studyData.studyInstanceUid}`
86
+ );
67
87
  }
68
88
 
69
89
  /**
@@ -81,7 +101,10 @@ const CompleteStudyWriter = (options) => {
81
101
  await callback.completeStudy(studyData);
82
102
  }
83
103
  callback.setStudyData(new StudyData(id, options));
84
- await callback.studyData.init(options);
104
+ if (!options.isSkipStudyScan) {
105
+ console.verbose('Initiate StudyData');
106
+ await callback.studyData.init(options);
107
+ }
85
108
  return callback.studyData;
86
109
  };
87
110
 
@@ -1,9 +1,9 @@
1
- const hashFactory = require("node-object-hash");
1
+ const hashFactory = require('node-object-hash');
2
2
 
3
3
  const hasher = hashFactory.hasher();
4
- const { JSONWriter } = require("@radicalimaging/static-wado-util");
5
- const { Tags } = require("@radicalimaging/static-wado-util");
6
- const TagLists = require("../model/TagLists");
4
+ const { JSONWriter } = require('@radicalimaging/static-wado-util');
5
+ const { Tags } = require('@radicalimaging/static-wado-util');
6
+ const TagLists = require('../model/TagLists');
7
7
 
8
8
  async function writeDeduplicatedFile(dir, data, hashValueSrc) {
9
9
  const hashValue = hashValueSrc || hasher.hash(data);
@@ -19,15 +19,15 @@ const perInstanceWriter = async (id, data) => {
19
19
  };
20
20
 
21
21
  /** Writes out JSON files to the given file name. Automatically GZips them, and adds the extension */
22
- const DeduplicateWriter = (options) =>
22
+ const DeduplicateWriter = options =>
23
23
  async function DeduplicateWriterInstance(id, data) {
24
24
  const studyData = await this.completeStudy.getCurrentStudyData(this, id);
25
25
 
26
26
  if (options.isDeduplicate) {
27
- if (options.verbose) console.log("Writing single instance", id.studyInstanceUid);
27
+ if (options.verbose) console.log('Writing single instance', id.studyInstanceUid);
28
28
  await perInstanceWriter(id, data);
29
29
  } else if (options.verbose) {
30
- console.log("Not writing single instance deduplicated");
30
+ console.log('Not writing single instance deduplicated');
31
31
  }
32
32
  studyData.addDeduplicated(data, id.filename);
33
33
  };
@@ -1,14 +1,15 @@
1
1
  const ExpandUriPath = (id, path, options) => {
2
2
  const { prependBulkDataUri, expandBulkDataUri, includeSeries } = options;
3
- let expandedRelative = "";
3
+ let expandedRelative = '';
4
4
  if (expandBulkDataUri) {
5
- let expandedRelativeSeries = "";
5
+ let expandedRelativeSeries = '';
6
6
  if (includeSeries) {
7
7
  expandedRelativeSeries = `series/${id.seriesInstanceUid}/`;
8
8
  }
9
9
  expandedRelative = `studies/${id.studyInstanceUid}/${expandedRelativeSeries}`;
10
10
  }
11
- if (options.verbose) console.log(`Expanded path returned: ${prependBulkDataUri}${expandedRelative}${path}`);
11
+ if (options.verbose)
12
+ console.log(`Expanded path returned: ${prependBulkDataUri}${expandedRelative}${path}`);
12
13
  return `${prependBulkDataUri}${expandedRelative}${path}`;
13
14
  };
14
15