@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
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
Stats,
|
|
5
|
-
} = require("@radicalimaging/static-wado-util");
|
|
6
|
-
const { Tags } = require("@radicalimaging/static-wado-util");
|
|
7
|
-
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');
|
|
8
4
|
|
|
9
5
|
/**
|
|
10
6
|
* CompleteStudyWriter takes the deduplicated data values, all loaded into the study parameter,
|
|
@@ -12,13 +8,13 @@ const StudyData = require("../operation/StudyData");
|
|
|
12
8
|
* written out as.
|
|
13
9
|
* The studyData object is then removed, so that a new one can be created if required.
|
|
14
10
|
*/
|
|
15
|
-
const CompleteStudyWriter =
|
|
11
|
+
const CompleteStudyWriter = options => {
|
|
16
12
|
async function ret() {
|
|
17
13
|
const { studyData } = this;
|
|
18
14
|
if (!studyData) return;
|
|
19
15
|
|
|
20
16
|
if (!studyData.numberOfInstances) {
|
|
21
|
-
console.log(
|
|
17
|
+
console.log('studyData.deduplicated is empty');
|
|
22
18
|
delete this.studyData;
|
|
23
19
|
return;
|
|
24
20
|
}
|
|
@@ -26,28 +22,24 @@ const CompleteStudyWriter = (options) => {
|
|
|
26
22
|
if (options.isGroup) {
|
|
27
23
|
if (studyData.dirty) {
|
|
28
24
|
await studyData.writeDeduplicatedGroup();
|
|
29
|
-
console.noQuiet(
|
|
30
|
-
"Wrote updated deduplicated data for study",
|
|
31
|
-
studyData.studyInstanceUid,
|
|
32
|
-
);
|
|
25
|
+
console.noQuiet('Wrote updated deduplicated data for study', studyData.studyInstanceUid);
|
|
33
26
|
} else {
|
|
34
27
|
console.noQuiet(
|
|
35
|
-
|
|
36
|
-
studyData.studyInstanceUid
|
|
28
|
+
'Not writing new deduplicated data because it is clean:',
|
|
29
|
+
studyData.studyInstanceUid
|
|
37
30
|
);
|
|
38
31
|
const message = {
|
|
39
|
-
text:
|
|
32
|
+
text: 'Metadata clean, not updating',
|
|
40
33
|
status: 0,
|
|
41
34
|
studyInstanceUid: studyData.studyInstanceUid,
|
|
42
35
|
};
|
|
43
|
-
this.success(
|
|
36
|
+
this.success('metadataClean', message);
|
|
44
37
|
}
|
|
45
38
|
await studyData.deleteInstancesReferenced();
|
|
46
39
|
}
|
|
47
40
|
|
|
48
41
|
if (!options.isStudyData) {
|
|
49
|
-
if (options.notifications)
|
|
50
|
-
this.notificationService.notifyStudy(studyData.studyInstanceUid);
|
|
42
|
+
if (options.notifications) this.notificationService.notifyStudy(studyData.studyInstanceUid);
|
|
51
43
|
delete this.studyData;
|
|
52
44
|
Stats.StudyStats.summarize();
|
|
53
45
|
return;
|
|
@@ -55,47 +47,42 @@ const CompleteStudyWriter = (options) => {
|
|
|
55
47
|
|
|
56
48
|
const isDirtyMetadata = await studyData.dirtyMetadata();
|
|
57
49
|
if (!isDirtyMetadata && !options.force) {
|
|
58
|
-
console.log(
|
|
50
|
+
console.log('Study metadata', studyData.studyInstanceUid, 'is clean.');
|
|
59
51
|
delete this.studyData;
|
|
60
52
|
Stats.StudyStats.summarize(
|
|
61
|
-
`Study metadata ${studyData.studyInstanceUid} has clean metadata, not writing
|
|
53
|
+
`Study metadata ${studyData.studyInstanceUid} has clean metadata, not writing`
|
|
62
54
|
);
|
|
63
55
|
return;
|
|
64
56
|
}
|
|
65
|
-
console.noQuiet(
|
|
57
|
+
console.noQuiet('Writing study metadata', studyData.studyInstanceUid);
|
|
66
58
|
|
|
67
59
|
const studyQuery = await studyData.writeMetadata();
|
|
68
60
|
|
|
69
|
-
const allStudies = await JSONReader(
|
|
70
|
-
options.directoryName,
|
|
71
|
-
"studies/index.json.gz",
|
|
72
|
-
[],
|
|
73
|
-
);
|
|
61
|
+
const allStudies = await JSONReader(options.directoryName, 'studies/index.json.gz', []);
|
|
74
62
|
const studyUID = Tags.getValue(studyQuery, Tags.StudyInstanceUID);
|
|
75
63
|
if (!studyUID) {
|
|
76
|
-
console.error(
|
|
77
|
-
throw new Error(
|
|
64
|
+
console.error('studyQuery=', studyQuery);
|
|
65
|
+
throw new Error('Study query has null studyUID');
|
|
78
66
|
}
|
|
79
67
|
const studyIndex = allStudies.findIndex(
|
|
80
|
-
|
|
68
|
+
item => Tags.getValue(item, Tags.StudyInstanceUID) == studyUID
|
|
81
69
|
);
|
|
82
70
|
if (studyIndex == -1) {
|
|
83
71
|
allStudies.push(studyQuery);
|
|
84
72
|
} else {
|
|
85
73
|
allStudies[studyIndex] = studyQuery;
|
|
86
74
|
}
|
|
87
|
-
await JSONWriter(options.directoryName,
|
|
88
|
-
if (options.notifications)
|
|
89
|
-
this.notificationService.notifyStudy(studyData.studyInstanceUid);
|
|
75
|
+
await JSONWriter(options.directoryName, 'studies', allStudies);
|
|
76
|
+
if (options.notifications) this.notificationService.notifyStudy(studyData.studyInstanceUid);
|
|
90
77
|
const message = {
|
|
91
|
-
action:
|
|
78
|
+
action: 'metadata',
|
|
92
79
|
status: 0,
|
|
93
80
|
studyInstanceUid: studyData.studyInstanceUid,
|
|
94
81
|
};
|
|
95
|
-
this.success(
|
|
82
|
+
this.success('completeStudyWriter', message);
|
|
96
83
|
delete this.studyData;
|
|
97
84
|
Stats.StudyStats.summarize(
|
|
98
|
-
`Wrote study metadata/query files for ${studyData.studyInstanceUid}
|
|
85
|
+
`Wrote study metadata/query files for ${studyData.studyInstanceUid}`
|
|
99
86
|
);
|
|
100
87
|
}
|
|
101
88
|
|
|
@@ -115,7 +102,7 @@ const CompleteStudyWriter = (options) => {
|
|
|
115
102
|
}
|
|
116
103
|
callback.setStudyData(new StudyData(id, options));
|
|
117
104
|
if (!options.isSkipStudyScan) {
|
|
118
|
-
console.verbose(
|
|
105
|
+
console.verbose('Initiate StudyData');
|
|
119
106
|
await callback.studyData.init(options);
|
|
120
107
|
}
|
|
121
108
|
return callback.studyData;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const hashFactory = require(
|
|
1
|
+
const hashFactory = require('node-object-hash');
|
|
2
2
|
|
|
3
3
|
const hasher = hashFactory.hasher();
|
|
4
|
-
const { JSONWriter } = require(
|
|
5
|
-
const { Tags } = require(
|
|
6
|
-
const TagLists = require(
|
|
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,16 +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 =
|
|
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)
|
|
28
|
-
console.log("Writing single instance", id.studyInstanceUid);
|
|
27
|
+
if (options.verbose) console.log('Writing single instance', id.studyInstanceUid);
|
|
29
28
|
await perInstanceWriter(id, data);
|
|
30
29
|
} else if (options.verbose) {
|
|
31
|
-
console.log(
|
|
30
|
+
console.log('Not writing single instance deduplicated');
|
|
32
31
|
}
|
|
33
32
|
studyData.addDeduplicated(data, id.filename);
|
|
34
33
|
};
|
|
@@ -1,17 +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
11
|
if (options.verbose)
|
|
12
|
-
console.log(
|
|
13
|
-
`Expanded path returned: ${prependBulkDataUri}${expandedRelative}${path}`,
|
|
14
|
-
);
|
|
12
|
+
console.log(`Expanded path returned: ${prependBulkDataUri}${expandedRelative}${path}`);
|
|
15
13
|
return `${prependBulkDataUri}${expandedRelative}${path}`;
|
|
16
14
|
};
|
|
17
15
|
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
const hashFactory = require(
|
|
2
|
-
const { createHash } = require(
|
|
1
|
+
const hashFactory = require('node-object-hash');
|
|
2
|
+
const { createHash } = require('crypto');
|
|
3
3
|
|
|
4
|
-
const path = require(
|
|
5
|
-
const { Tags } = require(
|
|
6
|
-
const WriteStream = require(
|
|
7
|
-
const WriteMultipart = require(
|
|
8
|
-
const ExpandUriPath = require(
|
|
9
|
-
const { MultipartHeader } = require(
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { Tags } = require('@radicalimaging/static-wado-util');
|
|
6
|
+
const WriteStream = require('./WriteStream');
|
|
7
|
+
const WriteMultipart = require('./WriteMultipart');
|
|
8
|
+
const ExpandUriPath = require('./ExpandUriPath');
|
|
9
|
+
const { MultipartHeader } = require('./MultipartHeader');
|
|
10
10
|
|
|
11
11
|
// Extensions for encapsulated content. Do NOT add any executable content extensions here.
|
|
12
12
|
const extensions = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
'application/pdf': '.pdf',
|
|
14
|
+
'text/json': '.json',
|
|
15
|
+
'application/xml+cda': '.cda.xml',
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const baseHasher = hashFactory.hasher();
|
|
19
19
|
|
|
20
20
|
const hasher = {
|
|
21
|
-
hash:
|
|
21
|
+
hash: v => {
|
|
22
22
|
const type = v.constructor?.name;
|
|
23
|
-
if (type ===
|
|
24
|
-
return createHash(
|
|
23
|
+
if (type === 'Buffer') {
|
|
24
|
+
return createHash('sha256').update(v).digest('hex');
|
|
25
25
|
}
|
|
26
26
|
return baseHasher.hash(v);
|
|
27
27
|
},
|
|
@@ -29,7 +29,7 @@ const hasher = {
|
|
|
29
29
|
|
|
30
30
|
/** Writes out JSON files to the given file name. Automatically GZips them, and adds the extension */
|
|
31
31
|
const HashDataWriter =
|
|
32
|
-
|
|
32
|
+
options =>
|
|
33
33
|
async (id, key, data, additionalOptions = {}) => {
|
|
34
34
|
const isRaw = ArrayBuffer.isView(data);
|
|
35
35
|
const { mimeType } = additionalOptions;
|
|
@@ -45,8 +45,8 @@ const HashDataWriter =
|
|
|
45
45
|
if (isRaw) {
|
|
46
46
|
await WriteMultipart(
|
|
47
47
|
writeStream,
|
|
48
|
-
[new MultipartHeader(
|
|
49
|
-
rawData
|
|
48
|
+
[new MultipartHeader('Content-Type', 'application/octet-stream')],
|
|
49
|
+
rawData
|
|
50
50
|
);
|
|
51
51
|
} else {
|
|
52
52
|
await writeStream.write(rawData);
|
|
@@ -61,7 +61,7 @@ const HashDataWriter =
|
|
|
61
61
|
HashDataWriter.createHashPath = (data, options = {}) => {
|
|
62
62
|
const { mimeType } = options;
|
|
63
63
|
const isRaw = ArrayBuffer.isView(data);
|
|
64
|
-
const extension = isRaw ? (mimeType && extensions[mimeType]) ||
|
|
64
|
+
const extension = isRaw ? (mimeType && extensions[mimeType]) || '' : '.json';
|
|
65
65
|
const hashValue = Tags.getValue(data, Tags.DeduppedHash) || hasher.hash(data);
|
|
66
66
|
|
|
67
67
|
return {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const uids = require(
|
|
2
|
-
const WriteStream = require(
|
|
3
|
-
const WriteMultipart = require(
|
|
4
|
-
const ExpandUriPath = require(
|
|
5
|
-
const { MultipartHeader, MultipartAttribute } = require(
|
|
1
|
+
const uids = require('../model/uids');
|
|
2
|
+
const WriteStream = require('./WriteStream');
|
|
3
|
+
const WriteMultipart = require('./WriteMultipart');
|
|
4
|
+
const ExpandUriPath = require('./ExpandUriPath');
|
|
5
|
+
const { MultipartHeader, MultipartAttribute } = require('./MultipartHeader');
|
|
6
6
|
|
|
7
|
-
const ImageFrameWriter =
|
|
7
|
+
const ImageFrameWriter = options => {
|
|
8
8
|
const { encapsulatedImage, singlePartImage } = options;
|
|
9
9
|
return async (id, index, imageFrame) => {
|
|
10
10
|
const { transferSyntaxUid } = id;
|
|
@@ -18,48 +18,35 @@ const ImageFrameWriter = (options) => {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
if (encapsulatedImage || !extension) {
|
|
21
|
-
const writeStream = WriteStream(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
gzip: type.gzip,
|
|
26
|
-
mkdir: true,
|
|
27
|
-
},
|
|
28
|
-
);
|
|
21
|
+
const writeStream = WriteStream(id.imageFrameRootPath, `${1 + index}.mht`, {
|
|
22
|
+
gzip: type.gzip,
|
|
23
|
+
mkdir: true,
|
|
24
|
+
});
|
|
29
25
|
await WriteMultipart(
|
|
30
26
|
writeStream,
|
|
31
27
|
[
|
|
32
|
-
new MultipartHeader(
|
|
33
|
-
new MultipartAttribute(
|
|
28
|
+
new MultipartHeader('Content-Type', type.contentType, [
|
|
29
|
+
new MultipartAttribute('transfer-syntax', transferSyntaxUid),
|
|
34
30
|
]),
|
|
35
31
|
],
|
|
36
|
-
content
|
|
32
|
+
content
|
|
37
33
|
);
|
|
38
34
|
await writeStream.close();
|
|
39
35
|
console.verbose(
|
|
40
|
-
|
|
36
|
+
'Wrote encapsulated image frame',
|
|
41
37
|
id.sopInstanceUid,
|
|
42
38
|
index + 1,
|
|
43
|
-
type.contentType
|
|
39
|
+
type.contentType
|
|
44
40
|
);
|
|
45
41
|
}
|
|
46
42
|
if (extension && singlePartImage) {
|
|
47
|
-
const writeStreamSingle = WriteStream(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
gzip: type.gzip,
|
|
52
|
-
mkdir: true,
|
|
53
|
-
},
|
|
54
|
-
);
|
|
43
|
+
const writeStreamSingle = WriteStream(id.imageFrameRootPath, `${1 + index}${extension}`, {
|
|
44
|
+
gzip: type.gzip,
|
|
45
|
+
mkdir: true,
|
|
46
|
+
});
|
|
55
47
|
await writeStreamSingle.write(content);
|
|
56
48
|
await writeStreamSingle.close();
|
|
57
|
-
console.verbose(
|
|
58
|
-
"Wrote single part image frame",
|
|
59
|
-
id.sopInstanceUid,
|
|
60
|
-
index + 1,
|
|
61
|
-
extension,
|
|
62
|
-
);
|
|
49
|
+
console.verbose('Wrote single part image frame', id.sopInstanceUid, index + 1, extension);
|
|
63
50
|
}
|
|
64
51
|
const includeSeries = true;
|
|
65
52
|
return ExpandUriPath(id, `instances/${id.sopInstanceUid}/frames`, {
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
const { Tags } = require(
|
|
2
|
-
const WriteStream = require(
|
|
3
|
-
const WriteMultipart = require(
|
|
4
|
-
const { MultipartHeader } = require(
|
|
1
|
+
const { Tags } = require('@radicalimaging/static-wado-util');
|
|
2
|
+
const WriteStream = require('./WriteStream');
|
|
3
|
+
const WriteMultipart = require('./WriteMultipart');
|
|
4
|
+
const { MultipartHeader } = require('./MultipartHeader');
|
|
5
5
|
|
|
6
|
-
const modalitiesToRawWrite = [
|
|
7
|
-
const sopClassesToRawWrite = [
|
|
6
|
+
const modalitiesToRawWrite = ['SEG', 'SR'];
|
|
7
|
+
const sopClassesToRawWrite = ['1.2.840.10008.5.1.4.1.1.30'];
|
|
8
8
|
|
|
9
|
-
const defaultSelector =
|
|
9
|
+
const defaultSelector = instance => {
|
|
10
10
|
const modality = instance.metadata[Tags.Modality]?.Value?.[0];
|
|
11
11
|
const sopClass = instance.metadata[Tags.SOPClassUID]?.Value?.[0];
|
|
12
12
|
return (
|
|
13
|
-
modalitiesToRawWrite.indexOf(modality) !== -1 ||
|
|
14
|
-
sopClassesToRawWrite.indexOf(sopClass) !== -1
|
|
13
|
+
modalitiesToRawWrite.indexOf(modality) !== -1 || sopClassesToRawWrite.indexOf(sopClass) !== -1
|
|
15
14
|
);
|
|
16
15
|
};
|
|
17
16
|
|
|
@@ -26,8 +25,8 @@ const RawDicomWriter =
|
|
|
26
25
|
if (!selector(instance)) {
|
|
27
26
|
return undefined;
|
|
28
27
|
}
|
|
29
|
-
const contentType = options.contentType ||
|
|
30
|
-
const writeStream = WriteStream(id.sopInstanceRootPath,
|
|
28
|
+
const contentType = options.contentType || 'application/dicom';
|
|
29
|
+
const writeStream = WriteStream(id.sopInstanceRootPath, 'index.mht', {
|
|
31
30
|
gzip: options.gzip,
|
|
32
31
|
mkdir: true,
|
|
33
32
|
});
|
|
@@ -35,8 +34,8 @@ const RawDicomWriter =
|
|
|
35
34
|
const buffer = content.slice(0, length);
|
|
36
35
|
await WriteMultipart(
|
|
37
36
|
writeStream,
|
|
38
|
-
[new MultipartHeader(
|
|
39
|
-
buffer
|
|
37
|
+
[new MultipartHeader('Content-Type', contentType, [])],
|
|
38
|
+
buffer
|
|
40
39
|
);
|
|
41
40
|
await writeStream.close();
|
|
42
41
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const WriteStream = require(
|
|
1
|
+
const WriteStream = require('./WriteStream');
|
|
2
2
|
|
|
3
3
|
const ThumbnailWriter = () => async (filePath, fileName, thumbBuffer) => {
|
|
4
4
|
const writeStream = WriteStream(filePath, fileName, {
|
|
@@ -7,7 +7,7 @@ const ThumbnailWriter = () => async (filePath, fileName, thumbBuffer) => {
|
|
|
7
7
|
|
|
8
8
|
await writeStream.write(thumbBuffer);
|
|
9
9
|
|
|
10
|
-
console.verbose(
|
|
10
|
+
console.verbose('Wrote thumbnail frame', filePath, fileName);
|
|
11
11
|
return writeStream.close();
|
|
12
12
|
};
|
|
13
13
|
|
|
@@ -1,62 +1,51 @@
|
|
|
1
1
|
/** Write raw video file */
|
|
2
|
-
const { Tags } = require(
|
|
3
|
-
const WriteStream = require(
|
|
2
|
+
const { Tags } = require('@radicalimaging/static-wado-util');
|
|
3
|
+
const WriteStream = require('./WriteStream');
|
|
4
4
|
|
|
5
|
-
const MPEG2 =
|
|
6
|
-
const H264 =
|
|
7
|
-
const H265 =
|
|
5
|
+
const MPEG2 = 'mpg';
|
|
6
|
+
const H264 = 'mp4';
|
|
7
|
+
const H265 = 'h265';
|
|
8
8
|
|
|
9
9
|
const VIDEO_TYPES = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
'1.2.840.10008.1.2.4.100': MPEG2,
|
|
11
|
+
'1.2.840.10008.1.2.4.101': MPEG2,
|
|
12
|
+
'1.2.840.10008.1.2.4.102': H264,
|
|
13
|
+
'1.2.840.10008.1.2.4.103': H264,
|
|
14
|
+
'1.2.840.10008.1.2.4.104': H264,
|
|
15
|
+
'1.2.840.10008.1.2.4.105': H264,
|
|
16
|
+
'1.2.840.10008.1.2.4.106': H264,
|
|
17
|
+
'1.2.840.10008.1.2.4.107': H265,
|
|
18
|
+
'1.2.840.10008.1.2.4.108': H265,
|
|
19
19
|
// TODO - add the new multi-segment MPEG2 and H264 variants
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
const isVideo =
|
|
23
|
-
VIDEO_TYPES[
|
|
24
|
-
value && value.string ? value.string(Tags.RawTransferSyntaxUID) : value
|
|
25
|
-
];
|
|
22
|
+
const isVideo = value =>
|
|
23
|
+
VIDEO_TYPES[value && value.string ? value.string(Tags.RawTransferSyntaxUID) : value];
|
|
26
24
|
|
|
27
25
|
const VideoWriter = () =>
|
|
28
26
|
async function run(id, dataSet) {
|
|
29
27
|
console.log(`Writing video ${id.sopInstanceUid}`);
|
|
30
28
|
const extension = VIDEO_TYPES[dataSet.string(Tags.RawTransferSyntaxUID)];
|
|
31
29
|
const filename = `index.${extension}`;
|
|
32
|
-
const writeStream = WriteStream(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
mkdir: true,
|
|
37
|
-
},
|
|
38
|
-
);
|
|
30
|
+
const writeStream = WriteStream(`${id.sopInstanceRootPath}/rendered`, filename, {
|
|
31
|
+
mkdir: true,
|
|
32
|
+
});
|
|
39
33
|
let length = 0;
|
|
40
34
|
const { fragments } = dataSet.elements.x7fe00010;
|
|
41
35
|
|
|
42
36
|
if (!fragments) {
|
|
43
|
-
console.warn(
|
|
44
|
-
return
|
|
37
|
+
console.warn('No video data');
|
|
38
|
+
return '';
|
|
45
39
|
}
|
|
46
40
|
// The zero position fragment isn't available, even though present in the original data
|
|
47
41
|
for (let i = 0; i < fragments.length; i++) {
|
|
48
42
|
const fragment = fragments[i];
|
|
49
|
-
const blob = dataSet.byteArray.slice(
|
|
50
|
-
fragment.position,
|
|
51
|
-
fragment.position + fragment.length,
|
|
52
|
-
);
|
|
43
|
+
const blob = dataSet.byteArray.slice(fragment.position, fragment.position + fragment.length);
|
|
53
44
|
length += blob.length;
|
|
54
45
|
await writeStream.write(blob);
|
|
55
46
|
}
|
|
56
47
|
await writeStream.close();
|
|
57
|
-
console.log(
|
|
58
|
-
`Done video ${id.sopInstanceRootPath}\\${filename} of length ${length}`,
|
|
59
|
-
);
|
|
48
|
+
console.log(`Done video ${id.sopInstanceRootPath}\\${filename} of length ${length}`);
|
|
60
49
|
return `series/${id.seriesInstanceUid}/instances/${id.sopInstanceUid}/rendered?length=${length}&offset=0&accept=video/mp4`;
|
|
61
50
|
};
|
|
62
51
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { v4: uuid } = require(
|
|
1
|
+
const { v4: uuid } = require('uuid');
|
|
2
2
|
|
|
3
3
|
const WriteMultipart = async (writeStream, headers, content) => {
|
|
4
4
|
const boundaryId = uuid();
|
|
@@ -9,14 +9,12 @@ const WriteMultipart = async (writeStream, headers, content) => {
|
|
|
9
9
|
if (header.attributes) {
|
|
10
10
|
for (let j = 0; j < header.attributes.length; j++) {
|
|
11
11
|
const attribute = header.attributes[j];
|
|
12
|
-
await writeStream.write(
|
|
13
|
-
`;${attribute.attributeName}=${attribute.attributeValue}`,
|
|
14
|
-
);
|
|
12
|
+
await writeStream.write(`;${attribute.attributeName}=${attribute.attributeValue}`);
|
|
15
13
|
}
|
|
16
14
|
}
|
|
17
|
-
await writeStream.write(
|
|
15
|
+
await writeStream.write('\r\n');
|
|
18
16
|
}
|
|
19
|
-
await writeStream.write(
|
|
17
|
+
await writeStream.write('\r\n');
|
|
20
18
|
await writeStream.write(content);
|
|
21
19
|
await writeStream.write(`\r\n--BOUNDARY_${boundaryId}--`);
|
|
22
20
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const zlib = require(
|
|
3
|
-
const path = require(
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const zlib = require('zlib');
|
|
3
|
+
const path = require('path');
|
|
4
4
|
|
|
5
5
|
let writeCount = 0;
|
|
6
6
|
|
|
@@ -10,32 +10,26 @@ let writeCount = 0;
|
|
|
10
10
|
* requires syncing.
|
|
11
11
|
*/
|
|
12
12
|
const WriteStream = (dir, nameSrc, options = {}) => {
|
|
13
|
-
const isGzip = nameSrc.indexOf(
|
|
14
|
-
const name =
|
|
15
|
-
|
|
16
|
-
if (options.mkdir) {
|
|
13
|
+
const isGzip = nameSrc.indexOf('.gz') != -1 || options.gzip;
|
|
14
|
+
const name = (isGzip && nameSrc.indexOf('.gz') === -1 && `${nameSrc}.gz`) || nameSrc;
|
|
15
|
+
if (options.mkdir && !fs.existsSync(dir)) {
|
|
17
16
|
fs.mkdirSync(dir, { recursive: true });
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
const tempName = path.join(
|
|
21
|
-
dir,
|
|
22
|
-
`tempFile-${Math.round(Math.random() * 1000000000)}`,
|
|
23
|
-
);
|
|
19
|
+
const tempName = path.join(dir, `tempFile-${Math.round(Math.random() * 1000000000)}`);
|
|
24
20
|
const finalName = path.join(dir, name);
|
|
25
21
|
writeCount++;
|
|
26
22
|
if (writeCount > 10) {
|
|
27
|
-
console.log(
|
|
23
|
+
console.log('Write count', tempName, finalName, writeCount);
|
|
28
24
|
if (writeCount > 100) {
|
|
29
|
-
console.error(
|
|
30
|
-
throw new Error(
|
|
31
|
-
`Write count too high ${writeCount} destination ${finalName}`,
|
|
32
|
-
);
|
|
25
|
+
console.error('Too many open writes', new Error());
|
|
26
|
+
throw new Error(`Write count too high ${writeCount} destination ${finalName}`);
|
|
33
27
|
}
|
|
34
28
|
}
|
|
35
29
|
const rawStream = fs.createWriteStream(tempName);
|
|
36
|
-
const closePromise = new Promise(
|
|
37
|
-
rawStream.on(
|
|
38
|
-
resolve(
|
|
30
|
+
const closePromise = new Promise(resolve => {
|
|
31
|
+
rawStream.on('close', () => {
|
|
32
|
+
resolve('closed');
|
|
39
33
|
});
|
|
40
34
|
});
|
|
41
35
|
|
|
@@ -43,7 +37,7 @@ const WriteStream = (dir, nameSrc, options = {}) => {
|
|
|
43
37
|
if (isGzip) {
|
|
44
38
|
writeStream = zlib.createGzip();
|
|
45
39
|
writeStream.pipe(rawStream);
|
|
46
|
-
writeStream.on(
|
|
40
|
+
writeStream.on('close', () => {
|
|
47
41
|
rawStream.close();
|
|
48
42
|
});
|
|
49
43
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@radicalimaging/static-wado-creator",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.12",
|
|
4
4
|
"description": "Library to convert DICOMP10 to DICOMweb format. Original upstream: https://github.com/chafey/dicomp10-to-dicomweb-js",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|