@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,163 @@
1
+ const { Tags, readBulkData, handleHomeRelative } = require('@radicalimaging/static-wado-util');
2
+ const dcmjs = require('dcmjs');
3
+
4
+ const StaticWado = require('./StaticWado');
5
+ const adaptProgramOpts = require('./util/adaptProgramOpts');
6
+ const WriteStream = require('./writer/WriteStream');
7
+
8
+ const UncompressedLEIExplicit = '1.2.840.10008.1.2.1';
9
+ const { DicomDict, DicomMetaDictionary } = dcmjs.data;
10
+
11
+ const fileMetaInformationVersionArray = new Uint8Array(2);
12
+ fileMetaInformationVersionArray[1] = 1;
13
+
14
+ const readBulkDataValue = async (studyDir, instance, value, options) => {
15
+ const { BulkDataURI } = value;
16
+ value.vr = 'OB';
17
+ const seriesUid = Tags.getValue(instance, Tags.SeriesInstanceUID);
18
+ const numberOfFrames = Tags.getValue(instance, Tags.NumberOfFrames) || 1;
19
+ if (BulkDataURI.indexOf('/frames') !== -1) {
20
+ const seriesDir = `${studyDir}/series/${seriesUid}`;
21
+ if (options?.frame === false) {
22
+ return;
23
+ }
24
+ // Really only support a limited number of frames based on memory size
25
+ value.Value = [];
26
+ if (typeof options?.frame === 'number') {
27
+ console.noQuiet('Reading frame', options.frame);
28
+ const bulk = await readBulkData(seriesDir, BulkDataURI, options.frame);
29
+ if (!bulk) {
30
+ return;
31
+ }
32
+ value.Value[options.frame - 1] = bulk.binaryData;
33
+ value.transferSyntaxUid = bulk.transferSyntaxUid;
34
+ value.contentType = bulk.contentType;
35
+ return;
36
+ }
37
+ console.noQuiet('Reading frames', 1, '...', numberOfFrames);
38
+ for (let frame = 1; frame <= numberOfFrames; frame++) {
39
+ const bulk = await readBulkData(seriesDir, BulkDataURI, frame);
40
+ if (!bulk) break;
41
+ value.Value.push(bulk.binaryData);
42
+ value.transferSyntaxUid = bulk.transferSyntaxUid;
43
+ value.contentType = bulk.contentType;
44
+ }
45
+ } else {
46
+ const bulk = await readBulkData(studyDir, BulkDataURI);
47
+ value.Value = [new ArrayBuffer(bulk.binaryData)];
48
+ value.contentType = bulk.contentType;
49
+ }
50
+ };
51
+
52
+ const readBinaryData = async (dir, instance, options = { frame: true }) => {
53
+ for (const tag of Object.keys(instance)) {
54
+ const v = instance[tag];
55
+ if (v.BulkDataURI) {
56
+ await readBulkDataValue(dir, instance, v, options);
57
+ continue;
58
+ }
59
+ if (!v.vr) {
60
+ const value0 = v.Value?.[0];
61
+ if (typeof value0 === 'string') {
62
+ v.vr = 'LT';
63
+ } else {
64
+ console.log('Deleting', tag, v.Value, v);
65
+ delete instance[tag];
66
+ }
67
+ continue;
68
+ }
69
+ if (v.vr === 'SQ' && v.Values?.length) {
70
+ await Promise.all(v.Values.map(readBinaryData));
71
+ continue;
72
+ }
73
+ }
74
+ };
75
+
76
+ module.exports = async function createThumbnail(options, program) {
77
+ const finalOptions = adaptProgramOpts(options, {
78
+ ...this,
79
+ // Instance metadata is the instances/<sopUID>/metadata.gz files
80
+ isInstance: false,
81
+ // Deduplicated data is single instance deduplicated data
82
+ isDeduplicate: false,
83
+ // Group data is the group file directories
84
+ isGroup: false,
85
+ isStudyData: false,
86
+ isDeleteInstances: false,
87
+ });
88
+ const importer = new StaticWado(finalOptions);
89
+ const studyInstanceUid = program.args[0];
90
+ const dir = `${importer.options.rootDir}/studies/${studyInstanceUid}`;
91
+ console.noQuiet('Creating thumbnail for', studyInstanceUid, dir);
92
+ const study = await importer.callback.scanStudy(studyInstanceUid);
93
+ const outputPath = `./studies/${studyInstanceUid}`;
94
+ const { sopInstanceUid, seriesInstanceUid, seriesThumbnail, studyThumbnail } = options;
95
+ const sops = sopInstanceUid ? new Set() : null;
96
+ if (sopInstanceUid) {
97
+ for (const sop of sopInstanceUid.split(',').map(s => s.trim())) {
98
+ sops.add(sop);
99
+ }
100
+ }
101
+ const promises = [];
102
+
103
+ const writeThumbnail = async (id, buffer) => {
104
+ console.verbose('Write thumbnail', id, buffer);
105
+ await importer.callback.thumbWriter(id.sopInstanceRootPath, 'thumbnail', buffer);
106
+ if (seriesThumbnail) {
107
+ await importer.callback.thumbWriter(id.seriesRootPath, 'thumbnail', buffer);
108
+ }
109
+ if (studyThumbnail) {
110
+ await importer.callback.thumbWriter(id.studyPath, 'thumbnail', buffer);
111
+ }
112
+ };
113
+
114
+ // TODO - choose middle frame of multiframe and choose middle image of
115
+ // series for series thumbnail, and first series for study thumbnail
116
+ const codecOptions = { frame: 1 };
117
+
118
+ for (const sop of study.getSopUids()) {
119
+ if (sops && !sops.has(sop)) {
120
+ continue;
121
+ }
122
+ const instance = await study.recombine(sop);
123
+
124
+ if (
125
+ seriesInstanceUid &&
126
+ Tags.getValue(instance, Tags.SeriesInstanceUID) !== seriesInstanceUid
127
+ ) {
128
+ continue;
129
+ }
130
+
131
+ await readBinaryData(dir, instance, codecOptions);
132
+
133
+ const availableTransferSyntaxUID = Tags.getValue(instance, Tags.AvailableTransferSyntaxUID);
134
+ const pixelData = Tags.getValue(instance, Tags.PixelData);
135
+ if (!pixelData) {
136
+ console.warn('No pixel data found in instance');
137
+ continue;
138
+ }
139
+ const transferSyntaxUid =
140
+ instance[Tags.PixelData].transferSyntaxUid || availableTransferSyntaxUID;
141
+ const frame = Array.isArray(pixelData.Value) ? pixelData.Value[0] : pixelData;
142
+ console.verbose('Use transfer syntax uid:', transferSyntaxUid);
143
+ const id = importer.callback.uids({
144
+ studyInstanceUid,
145
+ seriesInstanceUid: Tags.getValue(instance, Tags.SeriesInstanceUID),
146
+ sopInstanceUid: sop,
147
+ transferSyntaxUid,
148
+ });
149
+
150
+ const promise = importer.callback.internalGenerateImage(
151
+ frame,
152
+ null,
153
+ instance,
154
+ id.transferSyntaxUid,
155
+ writeThumbnail.bind(null, id)
156
+ );
157
+ promises.push(promise);
158
+ if (seriesThumbnail) {
159
+ break;
160
+ }
161
+ }
162
+ await Promise.all(promises);
163
+ };
@@ -0,0 +1,12 @@
1
+ const StaticWado = require('./StaticWado');
2
+ const adaptProgramOpts = require('./util/adaptProgramOpts');
3
+
4
+ module.exports = function deleteMain(options, program) {
5
+ const finalOptions = adaptProgramOpts(options, {
6
+ ...this,
7
+ isGroup: true,
8
+ isStudyData: true,
9
+ });
10
+ const importer = new StaticWado(finalOptions);
11
+ return importer.delete(program.args);
12
+ };
@@ -0,0 +1,17 @@
1
+ const StaticWado = require('./StaticWado');
2
+ const adaptProgramOpts = require('./util/adaptProgramOpts');
3
+
4
+ module.exports = function createMain(options, program) {
5
+ const finalOptions = adaptProgramOpts(options, {
6
+ ...this,
7
+ isInstance: false,
8
+ isDeduplicate: true,
9
+ isGroup: true,
10
+ isStudyData: false,
11
+ scanStudies: true,
12
+ isDeleteInstances: true,
13
+ });
14
+ const importer = new StaticWado(finalOptions);
15
+ // Either do a scan or on specified studies
16
+ return importer.executeCommand(program.args);
17
+ };
package/dist/index.mjs ADDED
@@ -0,0 +1,18 @@
1
+ import mkdicomwebConfig from './mkdicomwebConfig.js';
2
+ import StaticWado from './StaticWado.js';
3
+ import programIndex from './program/index.js';
4
+ import createMain from './createMain.js';
5
+ import deleteMain from './deleteMain.js';
6
+ import adaptProgramOpts from './util/adaptProgramOpts.js';
7
+ import { uids } from '@radicalimaging/static-wado-util';
8
+
9
+ const { configureProgram } = programIndex;
10
+
11
+ StaticWado.mkdicomwebConfig = mkdicomwebConfig;
12
+ StaticWado.createMain = createMain;
13
+ StaticWado.deleteMain = deleteMain;
14
+ StaticWado.adaptProgramOpts = adaptProgramOpts;
15
+ StaticWado.configureProgram = configureProgram;
16
+
17
+ export default StaticWado;
18
+ export { uids, StaticWado };
@@ -0,0 +1,10 @@
1
+ const StaticWado = require('./StaticWado');
2
+ const adaptProgramOpts = require('./util/adaptProgramOpts');
3
+
4
+ module.exports = function createMain(options) {
5
+ const finalOptions = adaptProgramOpts(options, {
6
+ ...this,
7
+ });
8
+ const importer = new StaticWado(finalOptions);
9
+ return importer.reindex();
10
+ };
@@ -0,0 +1,16 @@
1
+ const StaticWado = require('./StaticWado');
2
+ const adaptProgramOpts = require('./util/adaptProgramOpts');
3
+
4
+ module.exports = function createMain(options, program) {
5
+ const finalOptions = adaptProgramOpts(options, {
6
+ ...this,
7
+ isInstance: true,
8
+ // Deduplicated data is single instance deduplicated data
9
+ isDeduplicate: true,
10
+ isGroup: false,
11
+ isStudyData: false,
12
+ isSkipStudyScan: true,
13
+ });
14
+ const importer = new StaticWado(finalOptions);
15
+ return importer.executeCommand(program.args);
16
+ };
@@ -0,0 +1,15 @@
1
+ const StaticWado = require('./StaticWado');
2
+ const adaptProgramOpts = require('./util/adaptProgramOpts');
3
+
4
+ module.exports = function createMain(options, program) {
5
+ const finalOptions = adaptProgramOpts(options, {
6
+ ...this,
7
+ isInstance: false,
8
+ isGroup: true,
9
+ isStudyData: true,
10
+ scanStudies: true,
11
+ });
12
+ const importer = new StaticWado(finalOptions);
13
+ // Either do a scan or on specified studies
14
+ return importer.executeCommand(program.args);
15
+ };
@@ -0,0 +1,342 @@
1
+ const ConfigPoint = require('config-point');
2
+ const { staticWadoConfig } = require('@radicalimaging/static-wado-util');
3
+ const createMain = require('./createMain');
4
+ const createPart10 = require('./createPart10');
5
+ const createThumbnail = require('./createThumbnail');
6
+ const deleteMain = require('./deleteMain');
7
+ const rejectMain = require('./rejectMain');
8
+ const serverMain = require('./serverMain');
9
+ const instanceMain = require('./instanceMain');
10
+ const indexMain = require('./indexMain');
11
+ const groupMain = require('./groupMain');
12
+ const metadataMain = require('./metadataMain');
13
+ const compressionOptionParser = require('./util/compressionOptionParser');
14
+
15
+ /**
16
+ * Defines the basic configuration values for the dicomwebserver component. See the README for more details.
17
+ * TODO - fix the default values so they come from the configuration file. In the meantime, leave the defaults blank.
18
+ */
19
+ const { mkdicomwebConfig } = ConfigPoint.register({
20
+ mkdicomwebConfig: {
21
+ // This declare the inheritted configuration, don't assume this is directly accessible
22
+ configBase: staticWadoConfig,
23
+ isStudyData: true,
24
+ isGroup: true,
25
+ options: [
26
+ {
27
+ key: '-c <configFile.json5>',
28
+ description: 'Use an alternate configuration file',
29
+ defaultValue: '~/static-wado.json5',
30
+ },
31
+ {
32
+ key: '--clean',
33
+ description: 'Clean the outputs before generating/starting to write new values.',
34
+ defaultValue: false,
35
+ },
36
+ {
37
+ key: '-d, --deployments <listvalue...>',
38
+ description: 'List of deployments from configuration to deploy to. Separated by space.',
39
+ defaultValue: undefined,
40
+ },
41
+ {
42
+ key: '--no-notifications',
43
+ description: 'Turns off notifications',
44
+ },
45
+ {
46
+ key: '--delete',
47
+ description: 'Delete the imported instances',
48
+ defaultValue: false,
49
+ },
50
+ {
51
+ key: '--lossy',
52
+ description: 'Generate lossy instances instead of lossless',
53
+ defaultValue: false,
54
+ },
55
+ {
56
+ key: '--alternate <type>',
57
+ description:
58
+ 'Generates an alternate representaton of the image generally in the /lossy sub-directory',
59
+ choices: ['jhc', 'jls', 'jhcLossless', 'jlsLossless'],
60
+ },
61
+ {
62
+ key: '--alternate-thumbnail',
63
+ description: 'Generates a thumbnail for the alternate representation',
64
+ },
65
+ {
66
+ key: '--alternate-name <dir>',
67
+ description: 'Uses a given sub directory name',
68
+ defaultValue: 'lossy',
69
+ },
70
+ {
71
+ key: '-v, --verbose',
72
+ description: 'Write verbose output',
73
+ defaultValue: false,
74
+ },
75
+ {
76
+ key: '--show-progress',
77
+ description: 'Show progress during DICOM file processing',
78
+ },
79
+ {
80
+ key: '-t, --content-type <type>',
81
+ description:
82
+ 'Destination type to compress to (choices: "jpeg", "jls", "lei", "jls-lossy", "jhc", "jxl" or DICOM Transfer Syntax UID - default: "jls")',
83
+ defaultValue: 'jls',
84
+ customParser: compressionOptionParser,
85
+ },
86
+ {
87
+ key: '--encapsulated-image',
88
+ description:
89
+ 'Avoid encapsulating the image frame. Writes with the extension and without multipart',
90
+ },
91
+ {
92
+ key: '-e, --no-encapsulated-image',
93
+ description:
94
+ 'Avoid encapsulating the image frame. Writes with the extension and without multipart',
95
+ },
96
+ {
97
+ key: '--single-part-image',
98
+ description: 'Writes single part image data',
99
+ },
100
+ {
101
+ key: '--no-single-part-image',
102
+ description: 'Writes single part image data',
103
+ },
104
+ {
105
+ key: '-m, --maximum-inline-public-length <value>',
106
+ description: 'Maximum length of public binary data',
107
+ defaultValue: 128 * 1024 + 2,
108
+ },
109
+ {
110
+ key: '-M, --maximum-inline-private-length <value>',
111
+ description: 'Maximum length of private binary data',
112
+ defaultValue: 64,
113
+ },
114
+ {
115
+ key: '-r, --recompress <listvalue...>',
116
+ description: 'List of types to recompress separated by space',
117
+ defaultValue: ['uncompressed', 'jp2', 'jls', 'jll'],
118
+ choices: [
119
+ 'uncompressed',
120
+ 'jp2',
121
+ 'jpeg',
122
+ 'jpeglossless',
123
+ 'rle',
124
+ 'jph',
125
+ 'jls',
126
+ 'true',
127
+ 'none',
128
+ ],
129
+ },
130
+ {
131
+ key: '--recompress-color <listvalue...>',
132
+ description: 'List of types to recompress for color images, separated by space',
133
+ defaultValue: ['uncompressed'],
134
+ choices: [
135
+ 'uncompressed',
136
+ 'jpeg',
137
+ 'jp2',
138
+ 'jpeglossless',
139
+ 'rle',
140
+ 'jph',
141
+ 'jls',
142
+ 'true',
143
+ 'none',
144
+ ],
145
+ },
146
+ {
147
+ key: '-f, --force',
148
+ description: 'Force the update even if the SOP exists',
149
+ defaultValue: false,
150
+ },
151
+ {
152
+ key: '--delete-input',
153
+ description: 'Deletes successful input files',
154
+ defaultValue: false,
155
+ },
156
+ {
157
+ key: '--delete-failed',
158
+ description: 'Deletes unsuccessful input files',
159
+ defaultValue: false,
160
+ },
161
+ {
162
+ key: '--recompress-thumb <listvalue...>',
163
+ description: 'List of types to recompress thumb separated by space',
164
+ defaultValue: ['uncompressed', 'jp2'],
165
+ choices: ['uncompressed', 'jp2', 'jpeglossless', 'rle', 'none'],
166
+ },
167
+ {
168
+ key: '--no-recompress',
169
+ description: 'Force no recompression',
170
+ defaultValue: false,
171
+ },
172
+ {
173
+ key: '--no-recompress-thumb',
174
+ description: 'Force no recompression thumbnail',
175
+ defaultValue: false,
176
+ },
177
+ {
178
+ key: '--no-thumb',
179
+ description: 'Skip generating thumbnails using CS3D internal render',
180
+ defaultValue: true,
181
+ },
182
+ {
183
+ key: '--dcm2jpg',
184
+ description: 'Use dcm2jpg for thumbnail image generation',
185
+ defaultValue: false,
186
+ },
187
+ {
188
+ key: '--rendered',
189
+ description: 'Use dcm2jpg to generate a rendered PNG image',
190
+ defaultValue: false,
191
+ },
192
+ {
193
+ key: '-T, --color-content-type <value>',
194
+ description: 'Colour content type',
195
+ defaultValue: 'jpeg',
196
+ },
197
+ {
198
+ key: '--path-deduplicated <path>',
199
+ description: 'Set the deduplicate data directory path (relative to dir)',
200
+ defaultValue: 'deduplicated',
201
+ },
202
+ {
203
+ key: '--path-instances <path>',
204
+ description: 'Set the instances directory path (relative to dir)',
205
+ defaultValue: 'instances',
206
+ },
207
+ {
208
+ key: '--store-multipart-bulk-data',
209
+ description: 'Use multipart encoding for non image frame bulkdata',
210
+ defaultValue: false,
211
+ },
212
+ {
213
+ key: '--prepend-bulk-data-uri <value>',
214
+ description:
215
+ 'Prepend bulkdata uri (ex. to use absolute Uri like http://host:3000/dicomweb)',
216
+ defaultValue: '',
217
+ },
218
+ {
219
+ key: '--expand-bulk-data-uri',
220
+ description:
221
+ 'expand bulkdata relative uri to use full relative path (should also be set when using --prepend-bulk-data-uri)',
222
+ defaultValue: false,
223
+ },
224
+ {
225
+ key: '--quiet',
226
+ description: 'Run quietly, minimizing extraneous output',
227
+ defaultValue: false,
228
+ },
229
+ {
230
+ key: '--multipart',
231
+ description: 'Add a multipart result output',
232
+ defaultValue: false,
233
+ },
234
+ {
235
+ key: '--no-verify',
236
+ description: 'Verify',
237
+ defaultValue: true,
238
+ },
239
+ {
240
+ key: '-o, --dir <value>',
241
+ description: 'Set output directory',
242
+ defaultValue: {
243
+ configOperation: 'reference',
244
+ source: 'staticWadoConfig',
245
+ reference: 'rootDir',
246
+ },
247
+ },
248
+ {
249
+ key: '--sop-instance-uid <value>',
250
+ description: 'Choose sop instances to run against (only for instance runs)',
251
+ },
252
+ {
253
+ key: '--series-instance-uid <value>',
254
+ description: 'Choose series instances to run against (only for instance runs)',
255
+ },
256
+
257
+ {
258
+ key: '--hash-study-uid-path',
259
+ description: 'Enable hashing of studyUID folder structure',
260
+ defaultValue: false,
261
+ },
262
+ {
263
+ key: '--no-index',
264
+ description:
265
+ 'Skip updating the shared studies index file in dicomweb/studies/index.json.gz',
266
+ defaultValue: true,
267
+ },
268
+ ],
269
+ programs: [
270
+ {
271
+ command: 'create',
272
+ isDefault: true,
273
+ arguments: ['input'],
274
+ main: createMain,
275
+ helpDescription:
276
+ 'Make DICOMweb query and metadata from binary Part 10 DICOM files. Does a full read\n' +
277
+ 'of deduplicated files each time a study instance UID is found, and only updates\n' +
278
+ 'those studies having at least one ',
279
+ },
280
+ {
281
+ command: 'server',
282
+ main: serverMain,
283
+ helpDescription: 'Run server deploy instances single threaded/separated exec process',
284
+ },
285
+ {
286
+ command: 'part10',
287
+ arguments: ['studyUID'],
288
+ main: createPart10,
289
+ helpDescription: 'Store the specified study as part 10 data',
290
+ },
291
+ {
292
+ command: 'index',
293
+ main: indexMain,
294
+ helpDescription: 'Recreate the index for the DICOMweb data.',
295
+ },
296
+ {
297
+ command: 'thumbnail',
298
+ main: createThumbnail,
299
+ helpDescription: 'Create a thumbnail for the specified image',
300
+ options: [
301
+ {
302
+ key: '--series-thumbnail',
303
+ description: 'Generate a series thumbnail instead of instance',
304
+ },
305
+ ],
306
+ },
307
+ {
308
+ command: 'instance',
309
+ arguments: ['input'],
310
+ main: instanceMain,
311
+ helpDescription:
312
+ "Make instance level DICOMweb metadata and bulkdata, but don't group or write series metadata",
313
+ },
314
+ {
315
+ command: 'group',
316
+ arguments: ['input'],
317
+ main: groupMain,
318
+ helpDescription:
319
+ 'Group instance level metadata into deduplicated data.\nDeletes instance level deduplicated information once it is confirmed written.',
320
+ },
321
+ {
322
+ command: 'metadata',
323
+ arguments: ['input'],
324
+ main: metadataMain,
325
+ helpDescription:
326
+ 'Write the metadata object (series and study details) from the grouped deduplicated data.',
327
+ },
328
+ {
329
+ command: 'delete',
330
+ main: deleteMain,
331
+ helpDescription: 'Delete the given study, series or instance (not yet implemented)',
332
+ },
333
+ {
334
+ command: 'reject <studyUID...>',
335
+ main: rejectMain,
336
+ helpDescription: 'Reject the specified series',
337
+ },
338
+ ],
339
+ },
340
+ });
341
+
342
+ module.exports = mkdicomwebConfig;