@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.
- package/CHANGELOG.md +234 -0
- package/README.md +1 -1
- package/bin/mkdicomweb.mjs +8 -0
- package/dist/canvas-xaqc8v6y.node +0 -0
- package/dist/index.js +481518 -0
- package/lib/DeleteStudy.js +9 -2
- package/lib/RejectInstance.js +15 -3
- package/lib/StaticWado.js +245 -41
- package/lib/createPart10.js +8 -4
- package/lib/createThumbnail.js +181 -0
- package/lib/index.js +2 -0
- package/lib/instanceMain.js +1 -0
- package/lib/mkdicomwebConfig.js +122 -19
- package/lib/model/TagLists.js +24 -3
- package/lib/model/uids.js +5 -1
- package/lib/operation/InstanceDeduplicate.js +14 -4
- package/lib/operation/ScanStudy.js +38 -5
- package/lib/operation/StudyData.js +158 -38
- package/lib/operation/ThumbnailService.js +65 -31
- package/lib/operation/adapter/decodeImage.js +5 -2
- package/lib/operation/adapter/getImageInfo.js +19 -1
- package/lib/operation/adapter/transcodeImage.js +174 -52
- package/lib/operation/extractImageFrames.js +19 -4
- package/lib/operation/getDataSet.js +41 -7
- package/lib/operation/getEncapsulatedImageFrame.js +30 -6
- package/lib/operation/getUncompressedImageFrame.js +10 -2
- package/lib/operation/getVR.js +10 -2
- package/lib/operation/getValue.js +40 -12
- package/lib/operation/validateMetadata.js +40 -0
- package/lib/program/index.js +5 -3
- package/lib/serverMain.js +27 -0
- package/lib/util/IdCreator.js +32 -6
- package/lib/util/adaptProgramOpts.js +1 -1
- package/lib/util/compressionOptionParser.js +12 -3
- package/lib/util/resultsReport.ts +88 -0
- package/lib/writer/CompleteStudyWriter.js +49 -13
- package/lib/writer/DeduplicateWriter.js +2 -1
- package/lib/writer/ExpandUriPath.js +4 -1
- package/lib/writer/HashDataWriter.js +5 -1
- package/lib/writer/ImageFrameWriter.js +38 -13
- package/lib/writer/RawDicomWriter.js +11 -2
- package/lib/writer/VideoWriter.js +19 -7
- package/lib/writer/WriteMultipart.js +3 -1
- package/lib/writer/WriteStream.js +12 -4
- package/lib/writer/static-dicomweb.code-workspace +8 -0
- package/package.json +23 -19
- package/bin/mkdicomweb.js +0 -9
- package/license.md +0 -21
package/lib/instanceMain.js
CHANGED
package/lib/mkdicomwebConfig.js
CHANGED
|
@@ -2,8 +2,10 @@ const ConfigPoint = require("config-point");
|
|
|
2
2
|
const { staticWadoConfig } = require("@radicalimaging/static-wado-util");
|
|
3
3
|
const createMain = require("./createMain");
|
|
4
4
|
const createPart10 = require("./createPart10");
|
|
5
|
+
const createThumbnail = require("./createThumbnail");
|
|
5
6
|
const deleteMain = require("./deleteMain");
|
|
6
7
|
const rejectMain = require("./rejectMain");
|
|
8
|
+
const serverMain = require("./serverMain");
|
|
7
9
|
const instanceMain = require("./instanceMain");
|
|
8
10
|
const indexMain = require("./indexMain");
|
|
9
11
|
const groupMain = require("./groupMain");
|
|
@@ -28,12 +30,14 @@ const { mkdicomwebConfig } = ConfigPoint.register({
|
|
|
28
30
|
},
|
|
29
31
|
{
|
|
30
32
|
key: "--clean",
|
|
31
|
-
description:
|
|
33
|
+
description:
|
|
34
|
+
"Clean the outputs before generating/starting to write new values.",
|
|
32
35
|
defaultValue: false,
|
|
33
36
|
},
|
|
34
37
|
{
|
|
35
38
|
key: "-d, --deployments <listvalue...>",
|
|
36
|
-
description:
|
|
39
|
+
description:
|
|
40
|
+
"List of deployments from configuration to deploy to. Separated by space.",
|
|
37
41
|
defaultValue: undefined,
|
|
38
42
|
},
|
|
39
43
|
{
|
|
@@ -52,7 +56,8 @@ const { mkdicomwebConfig } = ConfigPoint.register({
|
|
|
52
56
|
},
|
|
53
57
|
{
|
|
54
58
|
key: "--alternate <type>",
|
|
55
|
-
description:
|
|
59
|
+
description:
|
|
60
|
+
"Generates an alternate representaton of the image generally in the /lossy sub-directory",
|
|
56
61
|
choices: ["jhc", "jls", "jhcLossless", "jlsLossless"],
|
|
57
62
|
},
|
|
58
63
|
{
|
|
@@ -69,19 +74,26 @@ const { mkdicomwebConfig } = ConfigPoint.register({
|
|
|
69
74
|
description: "Write verbose output",
|
|
70
75
|
defaultValue: false,
|
|
71
76
|
},
|
|
77
|
+
{
|
|
78
|
+
key: "--show-progress",
|
|
79
|
+
description: "Show progress during DICOM file processing",
|
|
80
|
+
},
|
|
72
81
|
{
|
|
73
82
|
key: "-t, --content-type <type>",
|
|
74
|
-
description:
|
|
83
|
+
description:
|
|
84
|
+
'Destination type to compress to (choices: "jpeg", "jls", "lei", "jls-lossy", "jhc", "jxl" or DICOM Transfer Syntax UID - default: "jls")',
|
|
75
85
|
defaultValue: "jls",
|
|
76
86
|
customParser: compressionOptionParser,
|
|
77
87
|
},
|
|
78
88
|
{
|
|
79
89
|
key: "--encapsulated-image",
|
|
80
|
-
description:
|
|
90
|
+
description:
|
|
91
|
+
"Avoid encapsulating the image frame. Writes with the extension and without multipart",
|
|
81
92
|
},
|
|
82
93
|
{
|
|
83
94
|
key: "-e, --no-encapsulated-image",
|
|
84
|
-
description:
|
|
95
|
+
description:
|
|
96
|
+
"Avoid encapsulating the image frame. Writes with the extension and without multipart",
|
|
85
97
|
},
|
|
86
98
|
{
|
|
87
99
|
key: "--single-part-image",
|
|
@@ -105,19 +117,50 @@ const { mkdicomwebConfig } = ConfigPoint.register({
|
|
|
105
117
|
key: "-r, --recompress <listvalue...>",
|
|
106
118
|
description: "List of types to recompress separated by space",
|
|
107
119
|
defaultValue: ["uncompressed", "jp2", "jls", "jll"],
|
|
108
|
-
choices: [
|
|
120
|
+
choices: [
|
|
121
|
+
"uncompressed",
|
|
122
|
+
"jp2",
|
|
123
|
+
"jpeg",
|
|
124
|
+
"jpeglossless",
|
|
125
|
+
"rle",
|
|
126
|
+
"jph",
|
|
127
|
+
"jls",
|
|
128
|
+
"true",
|
|
129
|
+
"none",
|
|
130
|
+
],
|
|
109
131
|
},
|
|
110
132
|
{
|
|
111
133
|
key: "--recompress-color <listvalue...>",
|
|
112
|
-
description:
|
|
134
|
+
description:
|
|
135
|
+
"List of types to recompress for color images, separated by space",
|
|
113
136
|
defaultValue: ["uncompressed"],
|
|
114
|
-
choices: [
|
|
137
|
+
choices: [
|
|
138
|
+
"uncompressed",
|
|
139
|
+
"jpeg",
|
|
140
|
+
"jp2",
|
|
141
|
+
"jpeglossless",
|
|
142
|
+
"rle",
|
|
143
|
+
"jph",
|
|
144
|
+
"jls",
|
|
145
|
+
"true",
|
|
146
|
+
"none",
|
|
147
|
+
],
|
|
115
148
|
},
|
|
116
149
|
{
|
|
117
150
|
key: "-f, --force",
|
|
118
151
|
description: "Force the update even if the SOP exists",
|
|
119
152
|
defaultValue: false,
|
|
120
153
|
},
|
|
154
|
+
{
|
|
155
|
+
key: "--delete-input",
|
|
156
|
+
description: "Deletes successful input files",
|
|
157
|
+
defaultValue: false,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
key: "--delete-failed",
|
|
161
|
+
description: "Deletes unsuccessful input files",
|
|
162
|
+
defaultValue: false,
|
|
163
|
+
},
|
|
121
164
|
{
|
|
122
165
|
key: "--recompress-thumb <listvalue...>",
|
|
123
166
|
description: "List of types to recompress thumb separated by space",
|
|
@@ -135,8 +178,9 @@ const { mkdicomwebConfig } = ConfigPoint.register({
|
|
|
135
178
|
defaultValue: false,
|
|
136
179
|
},
|
|
137
180
|
{
|
|
138
|
-
key: "--thumb",
|
|
139
|
-
description: "
|
|
181
|
+
key: "--no-thumb",
|
|
182
|
+
description: "Skip generating thumbnails using CS3D internal render",
|
|
183
|
+
defaultValue: true,
|
|
140
184
|
},
|
|
141
185
|
{
|
|
142
186
|
key: "--dcm2jpg",
|
|
@@ -155,7 +199,8 @@ const { mkdicomwebConfig } = ConfigPoint.register({
|
|
|
155
199
|
},
|
|
156
200
|
{
|
|
157
201
|
key: "--path-deduplicated <path>",
|
|
158
|
-
description:
|
|
202
|
+
description:
|
|
203
|
+
"Set the deduplicate data directory path (relative to dir)",
|
|
159
204
|
defaultValue: "deduplicated",
|
|
160
205
|
},
|
|
161
206
|
{
|
|
@@ -170,18 +215,55 @@ const { mkdicomwebConfig } = ConfigPoint.register({
|
|
|
170
215
|
},
|
|
171
216
|
{
|
|
172
217
|
key: "--prepend-bulk-data-uri <value>",
|
|
173
|
-
description:
|
|
218
|
+
description:
|
|
219
|
+
"Prepend bulkdata uri (ex. to use absolute Uri like http://host:3000/dicomweb)",
|
|
174
220
|
defaultValue: "",
|
|
175
221
|
},
|
|
176
222
|
{
|
|
177
223
|
key: "--expand-bulk-data-uri",
|
|
178
|
-
description:
|
|
224
|
+
description:
|
|
225
|
+
"expand bulkdata relative uri to use full relative path (should also be set when using --prepend-bulk-data-uri)",
|
|
179
226
|
defaultValue: false,
|
|
180
227
|
},
|
|
228
|
+
{
|
|
229
|
+
key: "--quiet",
|
|
230
|
+
description: "Run quietly, minimizing extraneous output",
|
|
231
|
+
defaultValue: false,
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
key: "--multipart",
|
|
235
|
+
description: "Add a multipart result output",
|
|
236
|
+
defaultValue: false,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
key: "--no-verify",
|
|
240
|
+
description: "Verify",
|
|
241
|
+
defaultValue: true,
|
|
242
|
+
},
|
|
181
243
|
{
|
|
182
244
|
key: "-o, --dir <value>",
|
|
183
245
|
description: "Set output directory",
|
|
184
|
-
defaultValue: {
|
|
246
|
+
defaultValue: {
|
|
247
|
+
configOperation: "reference",
|
|
248
|
+
source: "staticWadoConfig",
|
|
249
|
+
reference: "rootDir",
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
key: "--sop-instance-uid <value>",
|
|
254
|
+
description:
|
|
255
|
+
"Choose sop instances to run against (only for instance runs)",
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
key: "--series-instance-uid <value>",
|
|
259
|
+
description:
|
|
260
|
+
"Choose series instances to run against (only for instance runs)",
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
{
|
|
264
|
+
key: "--hash-study-uid-path",
|
|
265
|
+
description: "Enable hashing of studyUID folder structure",
|
|
266
|
+
defaultValue: false,
|
|
185
267
|
},
|
|
186
268
|
],
|
|
187
269
|
programs: [
|
|
@@ -195,6 +277,12 @@ const { mkdicomwebConfig } = ConfigPoint.register({
|
|
|
195
277
|
"of deduplicated files each time a study instance UID is found, and only updates\n" +
|
|
196
278
|
"those studies having at least one ",
|
|
197
279
|
},
|
|
280
|
+
{
|
|
281
|
+
command: "server",
|
|
282
|
+
main: serverMain,
|
|
283
|
+
helpDescription:
|
|
284
|
+
"Run server deploy instances single threaded/separated exec process",
|
|
285
|
+
},
|
|
198
286
|
{
|
|
199
287
|
command: "part10",
|
|
200
288
|
arguments: ["studyUID"],
|
|
@@ -206,28 +294,43 @@ const { mkdicomwebConfig } = ConfigPoint.register({
|
|
|
206
294
|
main: indexMain,
|
|
207
295
|
helpDescription: "Recreate the index for the DICOMweb data.",
|
|
208
296
|
},
|
|
297
|
+
{
|
|
298
|
+
command: "thumbnail",
|
|
299
|
+
main: createThumbnail,
|
|
300
|
+
helpDescription: "Create a thumbnail for the specified image",
|
|
301
|
+
options: [
|
|
302
|
+
{
|
|
303
|
+
key: "--series-thumbnail",
|
|
304
|
+
description: "Generate a series thumbnail instead of instance",
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
},
|
|
209
308
|
{
|
|
210
309
|
command: "instance",
|
|
211
310
|
arguments: ["input"],
|
|
212
311
|
main: instanceMain,
|
|
213
|
-
helpDescription:
|
|
312
|
+
helpDescription:
|
|
313
|
+
"Make instance level DICOMweb metadata and bulkdata, but don't group or write series metadata",
|
|
214
314
|
},
|
|
215
315
|
{
|
|
216
316
|
command: "group",
|
|
217
317
|
arguments: ["input"],
|
|
218
318
|
main: groupMain,
|
|
219
|
-
helpDescription:
|
|
319
|
+
helpDescription:
|
|
320
|
+
"Group instance level metadata into deduplicated data.\nDeletes instance level deduplicated information once it is confirmed written.",
|
|
220
321
|
},
|
|
221
322
|
{
|
|
222
323
|
command: "metadata",
|
|
223
324
|
arguments: ["input"],
|
|
224
325
|
main: metadataMain,
|
|
225
|
-
helpDescription:
|
|
326
|
+
helpDescription:
|
|
327
|
+
"Write the metadata object (series and study details) from the grouped deduplicated data.",
|
|
226
328
|
},
|
|
227
329
|
{
|
|
228
330
|
command: "delete",
|
|
229
331
|
main: deleteMain,
|
|
230
|
-
helpDescription:
|
|
332
|
+
helpDescription:
|
|
333
|
+
"Delete the given study, series or instance (not yet implemented)",
|
|
231
334
|
},
|
|
232
335
|
{
|
|
233
336
|
command: "reject <studyUID...>",
|
package/lib/model/TagLists.js
CHANGED
|
@@ -6,8 +6,20 @@ const { Tags } = require("@radicalimaging/static-wado-util");
|
|
|
6
6
|
const hasher = hashFactory.hasher();
|
|
7
7
|
|
|
8
8
|
const { PatientID, PatientName, IssuerOfPatientID } = Tags;
|
|
9
|
-
const {
|
|
10
|
-
|
|
9
|
+
const {
|
|
10
|
+
StudyDescription,
|
|
11
|
+
AccessionNumber,
|
|
12
|
+
StudyInstanceUID,
|
|
13
|
+
StudyDate,
|
|
14
|
+
StudyTime,
|
|
15
|
+
} = Tags;
|
|
16
|
+
const {
|
|
17
|
+
SeriesDescription,
|
|
18
|
+
SeriesNumber,
|
|
19
|
+
SeriesInstanceUID,
|
|
20
|
+
SeriesDate,
|
|
21
|
+
SeriesTime,
|
|
22
|
+
} = Tags;
|
|
11
23
|
|
|
12
24
|
const { DeduppedHash, DeduppedRef, DeduppedType } = Tags;
|
|
13
25
|
|
|
@@ -24,7 +36,16 @@ const PatientQuery = [
|
|
|
24
36
|
Tags.PatientIdentityRemoved,
|
|
25
37
|
Tags.DeidentificationMethodCodeSequence,
|
|
26
38
|
];
|
|
27
|
-
const StudyQuery = [
|
|
39
|
+
const StudyQuery = [
|
|
40
|
+
StudyDescription,
|
|
41
|
+
AccessionNumber,
|
|
42
|
+
StudyInstanceUID,
|
|
43
|
+
StudyDate,
|
|
44
|
+
StudyTime,
|
|
45
|
+
Tags.StudyStatusID,
|
|
46
|
+
Tags.StudyPriorityID,
|
|
47
|
+
Tags.StudyID,
|
|
48
|
+
];
|
|
28
49
|
|
|
29
50
|
const PatientStudyQuery = [...PatientQuery, ...StudyQuery];
|
|
30
51
|
|
package/lib/model/uids.js
CHANGED
|
@@ -21,7 +21,11 @@ const uids = {
|
|
|
21
21
|
"1.2.840.10008.1.2.1": uncompressed,
|
|
22
22
|
"1.2.840.10008.1.2.1.99": uncompressed,
|
|
23
23
|
"1.2.840.10008.1.2.2": uncompressed,
|
|
24
|
-
"1.2.840.10008.1.2.4.50": {
|
|
24
|
+
"1.2.840.10008.1.2.4.50": {
|
|
25
|
+
contentType: jpeg,
|
|
26
|
+
lossy: true,
|
|
27
|
+
extension: ".jpeg",
|
|
28
|
+
},
|
|
25
29
|
"1.2.840.10008.1.2.4.51": { contentType: jpeg, lossy: true },
|
|
26
30
|
"1.2.840.10008.1.2.4.57": { contentType: jpeg },
|
|
27
31
|
"1.2.840.10008.1.2.4.70": { contentType: jll, extension: ".jll" },
|
|
@@ -28,7 +28,8 @@ async function deduplicateSingleInstance(id, imageFrame, { force }) {
|
|
|
28
28
|
return {};
|
|
29
29
|
}
|
|
30
30
|
if (!force && studyData.sopExists(sopUID)) {
|
|
31
|
-
if (this.verbose)
|
|
31
|
+
if (this.verbose)
|
|
32
|
+
console.log("SOP Instance UID", sopUID, "already exists, skipping");
|
|
32
33
|
// TODO - allow replace as an option
|
|
33
34
|
// Null value means skip writing this instance
|
|
34
35
|
return null;
|
|
@@ -39,7 +40,12 @@ async function deduplicateSingleInstance(id, imageFrame, { force }) {
|
|
|
39
40
|
|
|
40
41
|
if (!this.extractors) this.extractors = extractors;
|
|
41
42
|
for (const key of Object.keys(this.extractors)) {
|
|
42
|
-
const extracted = TagLists.extract(
|
|
43
|
+
const extracted = TagLists.extract(
|
|
44
|
+
deduplicated,
|
|
45
|
+
key,
|
|
46
|
+
this.extractors[key],
|
|
47
|
+
TagLists.RemoveExtract,
|
|
48
|
+
);
|
|
43
49
|
const hashKey = getValue(extracted, Tags.DeduppedHash);
|
|
44
50
|
await studyData.addExtracted(this, hashKey, extracted);
|
|
45
51
|
}
|
|
@@ -78,7 +84,7 @@ const InstanceDeduplicate = (options) =>
|
|
|
78
84
|
// Notify the existing listeners, if any
|
|
79
85
|
const imageFrame = canonicalize(sourceImageFrame);
|
|
80
86
|
if (options.isInstance) {
|
|
81
|
-
|
|
87
|
+
console.verbose("Writing instance metadata");
|
|
82
88
|
await JSONWriter(id.sopInstanceRootPath, "metadata", imageFrame);
|
|
83
89
|
}
|
|
84
90
|
if (!options.isDeduplicate && !options.isGroup) {
|
|
@@ -90,7 +96,11 @@ const InstanceDeduplicate = (options) =>
|
|
|
90
96
|
this.verbose = options.verbose;
|
|
91
97
|
}
|
|
92
98
|
|
|
93
|
-
const deduppedInstance = await this.deduplicateSingleInstance(
|
|
99
|
+
const deduppedInstance = await this.deduplicateSingleInstance(
|
|
100
|
+
id,
|
|
101
|
+
imageFrame,
|
|
102
|
+
options,
|
|
103
|
+
);
|
|
94
104
|
if (deduppedInstance) {
|
|
95
105
|
// this refers to callee
|
|
96
106
|
await this.deduplicated(id, deduppedInstance);
|
|
@@ -1,14 +1,47 @@
|
|
|
1
|
+
const {
|
|
2
|
+
getStudyUIDPathAndSubPath,
|
|
3
|
+
} = require("@radicalimaging/static-wado-util");
|
|
4
|
+
|
|
1
5
|
const path = require("path");
|
|
2
6
|
|
|
3
7
|
function ScanStudy(options) {
|
|
4
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
directoryName,
|
|
10
|
+
deduplicatedRoot,
|
|
11
|
+
deduplicatedInstancesRoot,
|
|
12
|
+
hashStudyUidPath,
|
|
13
|
+
} = options;
|
|
5
14
|
|
|
6
15
|
return function scanStudy(studyInstanceUid) {
|
|
16
|
+
const { path: hashPath = "", subpath: hashSubpath = "" } = hashStudyUidPath
|
|
17
|
+
? getStudyUIDPathAndSubPath(studyInstanceUid)
|
|
18
|
+
: {};
|
|
19
|
+
|
|
7
20
|
console.verbose("scanStudy", studyInstanceUid);
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
21
|
+
|
|
22
|
+
if (hashPath && hashSubpath) {
|
|
23
|
+
console.verbose("hashPath", hashPath);
|
|
24
|
+
console.verbose("hashSubpath", hashSubpath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const studySubDir = hashStudyUidPath
|
|
28
|
+
? path.join(hashPath, hashSubpath, studyInstanceUid)
|
|
29
|
+
: studyInstanceUid;
|
|
30
|
+
|
|
31
|
+
const studyPath = path.join(directoryName, "studies", studySubDir);
|
|
32
|
+
const deduplicatedInstancesPath = path.join(
|
|
33
|
+
deduplicatedInstancesRoot,
|
|
34
|
+
// studyInstanceUid,
|
|
35
|
+
studySubDir,
|
|
36
|
+
);
|
|
37
|
+
const deduplicatedPath = path.join(deduplicatedRoot, studySubDir);
|
|
38
|
+
console.verbose(
|
|
39
|
+
"Importing",
|
|
40
|
+
studyInstanceUid,
|
|
41
|
+
studyPath,
|
|
42
|
+
deduplicatedInstancesPath,
|
|
43
|
+
deduplicatedPath,
|
|
44
|
+
);
|
|
12
45
|
return this.completeStudy.getCurrentStudyData(this, {
|
|
13
46
|
studyPath,
|
|
14
47
|
deduplicatedPath,
|