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