@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,7 +1,7 @@
|
|
|
1
1
|
// TODO review returning type of some methods
|
|
2
|
-
const { JSONWriter } = require(
|
|
3
|
-
const { Tags } = require(
|
|
4
|
-
const TagLists = require(
|
|
2
|
+
const { JSONWriter } = require('@radicalimaging/static-wado-util');
|
|
3
|
+
const { Tags } = require('@radicalimaging/static-wado-util');
|
|
4
|
+
const TagLists = require('../model/TagLists');
|
|
5
5
|
|
|
6
6
|
const { getValue, setValue } = Tags;
|
|
7
7
|
|
|
@@ -24,18 +24,17 @@ async function deduplicateSingleInstance(id, imageFrame, { force }) {
|
|
|
24
24
|
const seriesUID = getValue(imageFrame, Tags.SeriesInstanceUID);
|
|
25
25
|
const sopUID = getValue(imageFrame, Tags.SOPInstanceUID);
|
|
26
26
|
if (!sopUID) {
|
|
27
|
-
console.warn(
|
|
27
|
+
console.warn('No sop instance UID in', imageFrame);
|
|
28
28
|
return {};
|
|
29
29
|
}
|
|
30
30
|
if (!force && studyData.sopExists(sopUID)) {
|
|
31
|
-
if (this.verbose)
|
|
32
|
-
console.log("SOP Instance UID", sopUID, "already exists, skipping");
|
|
31
|
+
if (this.verbose) console.log('SOP Instance UID', sopUID, 'already exists, skipping');
|
|
33
32
|
// TODO - allow replace as an option
|
|
34
33
|
// Null value means skip writing this instance
|
|
35
34
|
return null;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
if (this.verbose) console.log(
|
|
37
|
+
if (this.verbose) console.log('SOP Instance UID', sopUID, 'being added');
|
|
39
38
|
const deduplicated = { ...imageFrame };
|
|
40
39
|
|
|
41
40
|
if (!this.extractors) this.extractors = extractors;
|
|
@@ -44,7 +43,7 @@ async function deduplicateSingleInstance(id, imageFrame, { force }) {
|
|
|
44
43
|
deduplicated,
|
|
45
44
|
key,
|
|
46
45
|
this.extractors[key],
|
|
47
|
-
TagLists.RemoveExtract
|
|
46
|
+
TagLists.RemoveExtract
|
|
48
47
|
);
|
|
49
48
|
const hashKey = getValue(extracted, Tags.DeduppedHash);
|
|
50
49
|
await studyData.addExtracted(this, hashKey, extracted);
|
|
@@ -59,13 +58,13 @@ async function deduplicateSingleInstance(id, imageFrame, { force }) {
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
/** Canonicalize the JSON data, making Values always arrays, remote "undefined" from the tags etc */
|
|
62
|
-
const canonicalize =
|
|
61
|
+
const canonicalize = json => {
|
|
63
62
|
if (!json) return undefined;
|
|
64
63
|
if (Array.isArray(json)) {
|
|
65
64
|
return json.map(canonicalize);
|
|
66
65
|
}
|
|
67
|
-
Object.keys(json).forEach(
|
|
68
|
-
if (!tag || tag ===
|
|
66
|
+
Object.keys(json).forEach(tag => {
|
|
67
|
+
if (!tag || tag === 'undefined') {
|
|
69
68
|
// console.error('Got an undefined tag:', tag, typeof(tag));
|
|
70
69
|
// TODO check returning value
|
|
71
70
|
return;
|
|
@@ -79,13 +78,13 @@ const canonicalize = (json) => {
|
|
|
79
78
|
return json;
|
|
80
79
|
};
|
|
81
80
|
|
|
82
|
-
const InstanceDeduplicate =
|
|
81
|
+
const InstanceDeduplicate = options =>
|
|
83
82
|
async function run(id, sourceImageFrame) {
|
|
84
83
|
// Notify the existing listeners, if any
|
|
85
84
|
const imageFrame = canonicalize(sourceImageFrame);
|
|
86
85
|
if (options.isInstance) {
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
await JSONWriter(id.sopInstanceRootPath, 'metadata', imageFrame);
|
|
87
|
+
console.verbose('Wrote instance metadata', id.sopInstanceRootPath, 'metadata');
|
|
89
88
|
}
|
|
90
89
|
if (!options.isDeduplicate && !options.isGroup) {
|
|
91
90
|
return;
|
|
@@ -96,11 +95,7 @@ const InstanceDeduplicate = (options) =>
|
|
|
96
95
|
this.verbose = options.verbose;
|
|
97
96
|
}
|
|
98
97
|
|
|
99
|
-
const deduppedInstance = await this.deduplicateSingleInstance(
|
|
100
|
-
id,
|
|
101
|
-
imageFrame,
|
|
102
|
-
options,
|
|
103
|
-
);
|
|
98
|
+
const deduppedInstance = await this.deduplicateSingleInstance(id, imageFrame, options);
|
|
104
99
|
if (deduppedInstance) {
|
|
105
100
|
// this refers to callee
|
|
106
101
|
await this.deduplicated(id, deduppedInstance);
|
|
@@ -1,46 +1,39 @@
|
|
|
1
|
-
const {
|
|
2
|
-
getStudyUIDPathAndSubPath,
|
|
3
|
-
} = require("@radicalimaging/static-wado-util");
|
|
1
|
+
const { getStudyUIDPathAndSubPath } = require('@radicalimaging/static-wado-util');
|
|
4
2
|
|
|
5
|
-
const path = require(
|
|
3
|
+
const path = require('path');
|
|
6
4
|
|
|
7
5
|
function ScanStudy(options) {
|
|
8
|
-
const {
|
|
9
|
-
directoryName,
|
|
10
|
-
deduplicatedRoot,
|
|
11
|
-
deduplicatedInstancesRoot,
|
|
12
|
-
hashStudyUidPath,
|
|
13
|
-
} = options;
|
|
6
|
+
const { directoryName, deduplicatedRoot, deduplicatedInstancesRoot, hashStudyUidPath } = options;
|
|
14
7
|
|
|
15
8
|
return function scanStudy(studyInstanceUid) {
|
|
16
|
-
const { path: hashPath =
|
|
9
|
+
const { path: hashPath = '', subpath: hashSubpath = '' } = hashStudyUidPath
|
|
17
10
|
? getStudyUIDPathAndSubPath(studyInstanceUid)
|
|
18
11
|
: {};
|
|
19
12
|
|
|
20
|
-
console.verbose(
|
|
13
|
+
console.verbose('scanStudy', studyInstanceUid);
|
|
21
14
|
|
|
22
15
|
if (hashPath && hashSubpath) {
|
|
23
|
-
console.verbose(
|
|
24
|
-
console.verbose(
|
|
16
|
+
console.verbose('hashPath', hashPath);
|
|
17
|
+
console.verbose('hashSubpath', hashSubpath);
|
|
25
18
|
}
|
|
26
19
|
|
|
27
20
|
const studySubDir = hashStudyUidPath
|
|
28
21
|
? path.join(hashPath, hashSubpath, studyInstanceUid)
|
|
29
22
|
: studyInstanceUid;
|
|
30
23
|
|
|
31
|
-
const studyPath = path.join(directoryName,
|
|
24
|
+
const studyPath = path.join(directoryName, 'studies', studySubDir);
|
|
32
25
|
const deduplicatedInstancesPath = path.join(
|
|
33
26
|
deduplicatedInstancesRoot,
|
|
34
27
|
// studyInstanceUid,
|
|
35
|
-
studySubDir
|
|
28
|
+
studySubDir
|
|
36
29
|
);
|
|
37
30
|
const deduplicatedPath = path.join(deduplicatedRoot, studySubDir);
|
|
38
31
|
console.verbose(
|
|
39
|
-
|
|
32
|
+
'Importing',
|
|
40
33
|
studyInstanceUid,
|
|
41
34
|
studyPath,
|
|
42
35
|
deduplicatedInstancesPath,
|
|
43
|
-
deduplicatedPath
|
|
36
|
+
deduplicatedPath
|
|
44
37
|
);
|
|
45
38
|
return this.completeStudy.getCurrentStudyData(this, {
|
|
46
39
|
studyPath,
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
const path = require(
|
|
2
|
-
const fs = require(
|
|
3
|
-
const hashFactory = require(
|
|
4
|
-
const { JSONReader, JSONWriter } = require(
|
|
5
|
-
const { Tags } = require(
|
|
6
|
-
const TagLists = require(
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const hashFactory = require('node-object-hash');
|
|
4
|
+
const { JSONReader, JSONWriter } = require('@radicalimaging/static-wado-util');
|
|
5
|
+
const { Tags } = require('@radicalimaging/static-wado-util');
|
|
6
|
+
const TagLists = require('../model/TagLists');
|
|
7
7
|
|
|
8
8
|
const { getValue, setValue, getList, setList } = Tags;
|
|
9
9
|
const hasher = hashFactory.hasher();
|
|
10
10
|
|
|
11
|
-
const getSeriesInstanceUid = (seriesInstance)
|
|
12
|
-
getValue(seriesInstance, Tags.SeriesInstanceUID);
|
|
11
|
+
const getSeriesInstanceUid = seriesInstance => getValue(seriesInstance, Tags.SeriesInstanceUID);
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* StudyData contains information about the grouped study data. It is used to create
|
|
@@ -23,13 +22,8 @@ const getSeriesInstanceUid = (seriesInstance) =>
|
|
|
23
22
|
*/
|
|
24
23
|
class StudyData {
|
|
25
24
|
constructor(
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
studyPath,
|
|
29
|
-
deduplicatedPath,
|
|
30
|
-
deduplicatedInstancesPath,
|
|
31
|
-
},
|
|
32
|
-
{ isGroup, clean, hashStudyUidPath },
|
|
25
|
+
{ studyInstanceUid, studyPath, deduplicatedPath, deduplicatedInstancesPath },
|
|
26
|
+
{ isGroup, clean, hashStudyUidPath }
|
|
33
27
|
) {
|
|
34
28
|
this.studyInstanceUid = studyInstanceUid;
|
|
35
29
|
this.studyPath = studyPath;
|
|
@@ -66,48 +60,37 @@ class StudyData {
|
|
|
66
60
|
// Wipe out the study directory entirely, as well as the deduplicatedRoot and instancesRoot
|
|
67
61
|
}
|
|
68
62
|
|
|
69
|
-
console.verbose(
|
|
70
|
-
console.verbose(
|
|
71
|
-
console.verbose(
|
|
72
|
-
console.verbose(
|
|
73
|
-
"deduplicatedInstancesPath",
|
|
74
|
-
this.deduplicatedInstancesPath,
|
|
75
|
-
);
|
|
63
|
+
console.verbose('studyInstanceUid', this.studyInstanceUid);
|
|
64
|
+
console.verbose('studyPath', this.studyPath);
|
|
65
|
+
console.verbose('deduplicatedPath', this.deduplicatedPath);
|
|
66
|
+
console.verbose('deduplicatedInstancesPath', this.deduplicatedInstancesPath);
|
|
76
67
|
|
|
77
68
|
this.groupFiles = 0;
|
|
78
|
-
const studyDeduplicated = await JSONReader(
|
|
79
|
-
this.studyPath,
|
|
80
|
-
"deduplicated/index.json.gz",
|
|
81
|
-
[],
|
|
82
|
-
);
|
|
69
|
+
const studyDeduplicated = await JSONReader(this.studyPath, 'deduplicated/index.json.gz', []);
|
|
83
70
|
const info = studyDeduplicated[0];
|
|
84
71
|
if (info) {
|
|
85
72
|
const hash = getValue(info, Tags.DeduppedHash);
|
|
86
73
|
if (this.hashStudyUidPath) {
|
|
87
74
|
console.verbose(
|
|
88
|
-
|
|
75
|
+
'Reading studies/<studyUIDPath>/<studyUIDSubpath>/<studyUID>/deduplicated/index.json.gz'
|
|
89
76
|
);
|
|
90
77
|
} else {
|
|
91
|
-
console.verbose(
|
|
92
|
-
"Reading studies/<studyUID>/deduplicated/index.json.gz",
|
|
93
|
-
);
|
|
78
|
+
console.verbose('Reading studies/<studyUID>/deduplicated/index.json.gz');
|
|
94
79
|
}
|
|
95
|
-
this.readDeduplicatedData(
|
|
80
|
+
this.readDeduplicatedData('index.json.gz', studyDeduplicated, hash);
|
|
96
81
|
} else {
|
|
97
82
|
console.log(
|
|
98
|
-
|
|
83
|
+
'No deduplicated/index.json to read in',
|
|
99
84
|
this.studyPath,
|
|
100
|
-
|
|
85
|
+
'/deduplicated/index.json.gz'
|
|
101
86
|
);
|
|
102
87
|
}
|
|
103
88
|
if (this.deduplicatedPath) {
|
|
104
89
|
this.groupFiles = await this.readDeduplicated(this.deduplicatedPath);
|
|
105
|
-
console.verbose(
|
|
90
|
+
console.verbose('Read groupFiles:', this.groupFiles);
|
|
106
91
|
}
|
|
107
92
|
if (this.deduplicatedInstancesPath) {
|
|
108
|
-
this.instanceFiles = await this.readDeduplicated(
|
|
109
|
-
this.deduplicatedInstancesPath,
|
|
110
|
-
);
|
|
93
|
+
this.instanceFiles = await this.readDeduplicated(this.deduplicatedInstancesPath);
|
|
111
94
|
}
|
|
112
95
|
}
|
|
113
96
|
|
|
@@ -121,40 +104,31 @@ class StudyData {
|
|
|
121
104
|
* a separate type of check.
|
|
122
105
|
*/
|
|
123
106
|
get dirty() {
|
|
124
|
-
return
|
|
125
|
-
this.newInstancesAdded > 0 ||
|
|
126
|
-
this.existingFiles.length > 1 ||
|
|
127
|
-
this.instanceFiles > 0
|
|
128
|
-
);
|
|
107
|
+
return this.newInstancesAdded > 0 || this.existingFiles.length > 1 || this.instanceFiles > 0;
|
|
129
108
|
}
|
|
130
109
|
|
|
131
110
|
async dirtyMetadata() {
|
|
132
111
|
if (this.dirty) {
|
|
133
|
-
console.verbose(
|
|
112
|
+
console.verbose('dirtyMetadata::Group data is dirty');
|
|
134
113
|
return true;
|
|
135
114
|
}
|
|
136
115
|
if (this.groupFiles > 0) {
|
|
137
|
-
console.verbose(
|
|
138
|
-
"dirtyMetadata::Study level deduplicated doesn't match group files",
|
|
139
|
-
);
|
|
116
|
+
console.verbose("dirtyMetadata::Study level deduplicated doesn't match group files");
|
|
140
117
|
}
|
|
141
118
|
try {
|
|
142
|
-
const studyFile = await JSONReader(this.studyPath,
|
|
119
|
+
const studyFile = await JSONReader(this.studyPath, 'index.json.gz', null);
|
|
143
120
|
if (!studyFile) {
|
|
144
|
-
console.verbose(
|
|
121
|
+
console.verbose('dirtyMetadata::studyIndex');
|
|
145
122
|
return true;
|
|
146
123
|
}
|
|
147
124
|
const hashValue = getValue(studyFile, Tags.DeduppedHash);
|
|
148
125
|
if (this.existingFiles[0].indexOf(hashValue) == -1) {
|
|
149
126
|
return false;
|
|
150
127
|
}
|
|
151
|
-
console.verbose(
|
|
128
|
+
console.verbose('dirtyMetadata::Dedupped hash missing');
|
|
152
129
|
return true;
|
|
153
130
|
} catch (e) {
|
|
154
|
-
console.verbose(
|
|
155
|
-
"dirtyMetadata::Exception, assume study metadata is dirty",
|
|
156
|
-
e,
|
|
157
|
-
);
|
|
131
|
+
console.verbose('dirtyMetadata::Exception, assume study metadata is dirty', e);
|
|
158
132
|
return true;
|
|
159
133
|
}
|
|
160
134
|
}
|
|
@@ -169,7 +143,7 @@ class StudyData {
|
|
|
169
143
|
const reSop = getValue(recombined, Tags.SOPInstanceUID);
|
|
170
144
|
if (!reSop) continue;
|
|
171
145
|
if (sopInstanceUid && reSop !== sopInstanceUid) continue;
|
|
172
|
-
setValue(this.deduplicated[i], Tags.DeduppedType,
|
|
146
|
+
setValue(this.deduplicated[i], Tags.DeduppedType, 'deleted');
|
|
173
147
|
}
|
|
174
148
|
}
|
|
175
149
|
|
|
@@ -188,7 +162,7 @@ class StudyData {
|
|
|
188
162
|
if (!item) {
|
|
189
163
|
item = await JSONReader.readHashData(this.studyPath, hashKey);
|
|
190
164
|
if (!item) {
|
|
191
|
-
console.error(
|
|
165
|
+
console.error('Unable to read hashdata', hashKey);
|
|
192
166
|
return item;
|
|
193
167
|
}
|
|
194
168
|
this.extractData[hashKey] = item;
|
|
@@ -198,7 +172,7 @@ class StudyData {
|
|
|
198
172
|
|
|
199
173
|
assignUndefined(ret, item) {
|
|
200
174
|
if (!item) return;
|
|
201
|
-
Object.keys(item).forEach(
|
|
175
|
+
Object.keys(item).forEach(key => {
|
|
202
176
|
if (ret[key]) return;
|
|
203
177
|
ret[key] = item[key];
|
|
204
178
|
});
|
|
@@ -208,19 +182,14 @@ class StudyData {
|
|
|
208
182
|
* Create a full study instance data for instance at index
|
|
209
183
|
*/
|
|
210
184
|
async recombine(indexOrSop) {
|
|
211
|
-
const index =
|
|
212
|
-
typeof indexOrSop === "string"
|
|
213
|
-
? this.sopInstances[indexOrSop]
|
|
214
|
-
: indexOrSop;
|
|
185
|
+
const index = typeof indexOrSop === 'string' ? this.sopInstances[indexOrSop] : indexOrSop;
|
|
215
186
|
const deduplicated = this.deduplicated[index];
|
|
216
187
|
if (index < 0 || index >= this.deduplicated.length) {
|
|
217
|
-
throw new Error(
|
|
218
|
-
`Can't read index ${index}, out of bounds [0..${this.deduplicated.length})`,
|
|
219
|
-
);
|
|
188
|
+
throw new Error(`Can't read index ${index}, out of bounds [0..${this.deduplicated.length})`);
|
|
220
189
|
}
|
|
221
190
|
const refs = getList(deduplicated, Tags.DeduppedRef);
|
|
222
191
|
if (!refs) {
|
|
223
|
-
console.log(
|
|
192
|
+
console.log('No refs for', deduplicated);
|
|
224
193
|
return deduplicated;
|
|
225
194
|
}
|
|
226
195
|
const ret = { ...deduplicated };
|
|
@@ -234,11 +203,7 @@ class StudyData {
|
|
|
234
203
|
async addExtracted(callback, hashKey, item) {
|
|
235
204
|
if (this.extractData[hashKey]) {
|
|
236
205
|
if (this.verbose)
|
|
237
|
-
console.log(
|
|
238
|
-
"Already have extracted",
|
|
239
|
-
hashKey,
|
|
240
|
-
getValue(item, Tags.DeduppedType),
|
|
241
|
-
);
|
|
206
|
+
console.log('Already have extracted', hashKey, getValue(item, Tags.DeduppedType));
|
|
242
207
|
return;
|
|
243
208
|
}
|
|
244
209
|
await callback.bulkdata(this, hashKey, item);
|
|
@@ -259,7 +224,7 @@ class StudyData {
|
|
|
259
224
|
}
|
|
260
225
|
|
|
261
226
|
/** Add the instance if not already present */
|
|
262
|
-
internalAddDeduplicated(data, filename =
|
|
227
|
+
internalAddDeduplicated(data, filename = 'internal') {
|
|
263
228
|
const hashValue = TagLists.addHash(data, Tags.InstanceType);
|
|
264
229
|
if (!hashValue || this.deduplicatedHashes[hashValue]) {
|
|
265
230
|
// console.log('Not adding', hashValue, 'because the hash exists');
|
|
@@ -268,13 +233,13 @@ class StudyData {
|
|
|
268
233
|
const sopValue = getValue(data, Tags.SOPInstanceUID);
|
|
269
234
|
const sopIndex = this.sopInstances[sopValue];
|
|
270
235
|
if (!sopValue) {
|
|
271
|
-
console.warn(
|
|
236
|
+
console.warn('No sop value in', filename, 'reading', data);
|
|
272
237
|
return false;
|
|
273
238
|
}
|
|
274
239
|
this.deduplicatedHashes[hashValue] = data;
|
|
275
240
|
this.readHashes[hashValue] = filename;
|
|
276
241
|
if (sopIndex !== undefined) {
|
|
277
|
-
console.noQuiet(
|
|
242
|
+
console.noQuiet('Replacing SOP', sopValue, 'at index', sopIndex);
|
|
278
243
|
this.deduplicated[sopIndex] = data;
|
|
279
244
|
} else {
|
|
280
245
|
this.sopInstances[sopValue] = this.deduplicated.length;
|
|
@@ -287,9 +252,9 @@ class StudyData {
|
|
|
287
252
|
async listJsonFiles(dir) {
|
|
288
253
|
const names = await fs.promises.readdir(dir);
|
|
289
254
|
if (!names || names.length == 0) return [];
|
|
290
|
-
const gzNames = names.filter(
|
|
255
|
+
const gzNames = names.filter(name => name.indexOf('.gz') > 0);
|
|
291
256
|
const statInfo = gzNames
|
|
292
|
-
.map(
|
|
257
|
+
.map(name => {
|
|
293
258
|
const stat = fs.statSync(path.join(dir, name));
|
|
294
259
|
return {
|
|
295
260
|
...stat,
|
|
@@ -318,10 +283,10 @@ class StudyData {
|
|
|
318
283
|
try {
|
|
319
284
|
const files = await this.listJsonFiles(deduplicatedDirectory);
|
|
320
285
|
if (!files || !files.length) {
|
|
321
|
-
console.verbose(
|
|
286
|
+
console.verbose('No deduplicated for', deduplicatedDirectory);
|
|
322
287
|
return 0;
|
|
323
288
|
}
|
|
324
|
-
console.verbose(
|
|
289
|
+
console.verbose('There are', files.length, 'files to check');
|
|
325
290
|
for (let i = 0; i < files.length; i++) {
|
|
326
291
|
const stat = files[i];
|
|
327
292
|
const { hash } = stat;
|
|
@@ -331,7 +296,7 @@ class StudyData {
|
|
|
331
296
|
readCount += 1;
|
|
332
297
|
await this.readDeduplicatedFile(deduplicatedDirectory, stat);
|
|
333
298
|
}
|
|
334
|
-
console.verbose(
|
|
299
|
+
console.verbose('Done checking', deduplicatedDirectory);
|
|
335
300
|
} catch (e) {
|
|
336
301
|
// No-op console.log(e);
|
|
337
302
|
}
|
|
@@ -341,11 +306,11 @@ class StudyData {
|
|
|
341
306
|
async readDeduplicatedFile(dir, stat) {
|
|
342
307
|
const { hash, name } = stat;
|
|
343
308
|
try {
|
|
344
|
-
console.verbose(
|
|
309
|
+
console.verbose('Reading deduplicated file', name);
|
|
345
310
|
const data = await JSONReader(dir, name);
|
|
346
311
|
this.readDeduplicatedData(name, data, hash);
|
|
347
312
|
} catch (e) {
|
|
348
|
-
console.error(
|
|
313
|
+
console.error('Unable to read', dir, name);
|
|
349
314
|
}
|
|
350
315
|
}
|
|
351
316
|
|
|
@@ -353,14 +318,14 @@ class StudyData {
|
|
|
353
318
|
this.readHashes[hash] = name;
|
|
354
319
|
this.existingFiles.push(name);
|
|
355
320
|
const listData = (Array.isArray(data) && data) || [data];
|
|
356
|
-
listData.forEach(
|
|
321
|
+
listData.forEach(item => {
|
|
357
322
|
const type = getValue(item, Tags.DeduppedType);
|
|
358
323
|
if (type === Tags.InstanceType || type === Tags.DeletedType) {
|
|
359
324
|
this.internalAddDeduplicated(item, name);
|
|
360
|
-
} else if (type ==
|
|
325
|
+
} else if (type == 'info') {
|
|
361
326
|
const refs = getList(item, Tags.DeduppedRef);
|
|
362
327
|
if (refs) {
|
|
363
|
-
refs.forEach(
|
|
328
|
+
refs.forEach(hashValue => {
|
|
364
329
|
this.readHashes[hashValue] = `${hashValue}.gz`;
|
|
365
330
|
});
|
|
366
331
|
}
|
|
@@ -380,34 +345,22 @@ class StudyData {
|
|
|
380
345
|
for (let i = 0; i < this.numberOfInstances; i++) {
|
|
381
346
|
const seriesInstance = await this.recombine(i);
|
|
382
347
|
const type = getValue(seriesInstance, Tags.DeduppedType);
|
|
383
|
-
if (type ==
|
|
348
|
+
if (type == 'deleted') {
|
|
384
349
|
console.log(
|
|
385
|
-
|
|
350
|
+
'Skipping deleted instance',
|
|
386
351
|
type,
|
|
387
|
-
getValue(seriesInstance, Tags.SeriesInstanceUID)
|
|
352
|
+
getValue(seriesInstance, Tags.SeriesInstanceUID)
|
|
388
353
|
);
|
|
389
354
|
continue;
|
|
390
355
|
}
|
|
391
356
|
const seriesInstanceUid = getSeriesInstanceUid(seriesInstance);
|
|
392
357
|
if (!seriesInstanceUid) {
|
|
393
|
-
console.log(
|
|
394
|
-
"Cant get seriesUid from",
|
|
395
|
-
Tags.SeriesInstanceUID,
|
|
396
|
-
seriesInstance,
|
|
397
|
-
);
|
|
358
|
+
console.log('Cant get seriesUid from', Tags.SeriesInstanceUID, seriesInstance);
|
|
398
359
|
continue;
|
|
399
360
|
}
|
|
400
361
|
if (!series[seriesInstanceUid]) {
|
|
401
|
-
const seriesQuery = TagLists.extract(
|
|
402
|
-
|
|
403
|
-
"series",
|
|
404
|
-
TagLists.SeriesQuery,
|
|
405
|
-
);
|
|
406
|
-
const seriesPath = path.join(
|
|
407
|
-
this.studyPath,
|
|
408
|
-
"series",
|
|
409
|
-
seriesInstanceUid,
|
|
410
|
-
);
|
|
362
|
+
const seriesQuery = TagLists.extract(seriesInstance, 'series', TagLists.SeriesQuery);
|
|
363
|
+
const seriesPath = path.join(this.studyPath, 'series', seriesInstanceUid);
|
|
411
364
|
series[seriesInstanceUid] = {
|
|
412
365
|
seriesPath,
|
|
413
366
|
seriesQuery,
|
|
@@ -417,7 +370,7 @@ class StudyData {
|
|
|
417
370
|
}
|
|
418
371
|
series[seriesInstanceUid].instances.push(seriesInstance);
|
|
419
372
|
series[seriesInstanceUid].instancesQuery.push(
|
|
420
|
-
TagLists.extract(seriesInstance,
|
|
373
|
+
TagLists.extract(seriesInstance, 'instance', TagLists.InstanceQuery)
|
|
421
374
|
);
|
|
422
375
|
}
|
|
423
376
|
|
|
@@ -428,73 +381,57 @@ class StudyData {
|
|
|
428
381
|
|
|
429
382
|
for (const seriesUid of Object.keys(series)) {
|
|
430
383
|
const singleSeries = series[seriesUid];
|
|
431
|
-
const { seriesQuery, seriesPath, instances, instancesQuery } =
|
|
432
|
-
singleSeries;
|
|
384
|
+
const { seriesQuery, seriesPath, instances, instancesQuery } = singleSeries;
|
|
433
385
|
seriesQuery[Tags.NumberOfSeriesRelatedInstances] = {
|
|
434
|
-
vr:
|
|
386
|
+
vr: 'IS',
|
|
435
387
|
Value: [instances.length],
|
|
436
388
|
};
|
|
437
389
|
numberOfInstances += instances.length;
|
|
438
390
|
numberOfSeries += 1;
|
|
439
391
|
const modality = getValue(seriesQuery, Tags.Modality);
|
|
440
392
|
seriesList.push(seriesQuery);
|
|
441
|
-
if (modalitiesInStudy.indexOf(modality) == -1)
|
|
442
|
-
|
|
443
|
-
await JSONWriter(seriesPath, "metadata", instances, {
|
|
393
|
+
if (modalitiesInStudy.indexOf(modality) == -1) modalitiesInStudy.push(modality);
|
|
394
|
+
await JSONWriter(seriesPath, 'metadata', instances, {
|
|
444
395
|
gzip: true,
|
|
445
396
|
index: false,
|
|
446
397
|
});
|
|
447
398
|
// Write out a series singleton that has just the series response for a single series.
|
|
448
|
-
await JSONWriter(seriesPath,
|
|
399
|
+
await JSONWriter(seriesPath, 'series-singleton.json', [seriesQuery], {
|
|
449
400
|
gzip: true,
|
|
450
401
|
index: false,
|
|
451
402
|
});
|
|
452
|
-
await JSONWriter(seriesPath,
|
|
453
|
-
console.noQuiet(
|
|
454
|
-
"Wrote instances",
|
|
455
|
-
seriesUid,
|
|
456
|
-
"with",
|
|
457
|
-
instancesQuery.length,
|
|
458
|
-
);
|
|
403
|
+
await JSONWriter(seriesPath, 'instances', instancesQuery);
|
|
404
|
+
console.noQuiet('Wrote instances', seriesUid, 'with', instancesQuery.length);
|
|
459
405
|
}
|
|
460
406
|
|
|
461
|
-
await JSONWriter(this.studyPath,
|
|
462
|
-
console.noQuiet(
|
|
463
|
-
"Wrote series",
|
|
464
|
-
this.studyInstanceUid,
|
|
465
|
-
"with",
|
|
466
|
-
seriesList.length,
|
|
467
|
-
);
|
|
407
|
+
await JSONWriter(this.studyPath, 'series', seriesList);
|
|
408
|
+
console.noQuiet('Wrote series', this.studyInstanceUid, 'with', seriesList.length);
|
|
468
409
|
|
|
469
|
-
const studyQuery = TagLists.extract(
|
|
470
|
-
|
|
471
|
-
"study",
|
|
472
|
-
TagLists.PatientStudyQuery,
|
|
473
|
-
);
|
|
474
|
-
studyQuery[Tags.ModalitiesInStudy] = { Value: modalitiesInStudy, vr: "CS" };
|
|
410
|
+
const studyQuery = TagLists.extract(anInstance, 'study', TagLists.PatientStudyQuery);
|
|
411
|
+
studyQuery[Tags.ModalitiesInStudy] = { Value: modalitiesInStudy, vr: 'CS' };
|
|
475
412
|
studyQuery[Tags.NumberOfStudyRelatedInstances] = {
|
|
476
413
|
Value: [numberOfInstances],
|
|
477
|
-
vr:
|
|
414
|
+
vr: 'IS',
|
|
478
415
|
};
|
|
479
416
|
studyQuery[Tags.NumberOfStudyRelatedSeries] = {
|
|
480
417
|
Value: [numberOfSeries],
|
|
481
|
-
vr:
|
|
418
|
+
vr: 'IS',
|
|
482
419
|
};
|
|
483
420
|
// Write the index.json directly in the studyPath directory.
|
|
484
|
-
await JSONWriter(this.studyPath,
|
|
421
|
+
await JSONWriter(this.studyPath, 'index.json', [studyQuery], {
|
|
485
422
|
gzip: true,
|
|
486
423
|
index: false,
|
|
487
424
|
});
|
|
488
425
|
|
|
489
426
|
const infoItem = this.createInfo();
|
|
490
427
|
console.noQuiet(
|
|
491
|
-
|
|
428
|
+
'Writing deduplicated study data with',
|
|
492
429
|
Object.values(this.extractData).length,
|
|
493
|
-
|
|
430
|
+
'extract items and',
|
|
494
431
|
this.deduplicated.length,
|
|
495
|
-
|
|
432
|
+
'instance items'
|
|
496
433
|
);
|
|
497
|
-
await JSONWriter(this.studyPath,
|
|
434
|
+
await JSONWriter(this.studyPath, 'deduplicated', [
|
|
498
435
|
infoItem,
|
|
499
436
|
...Object.values(this.extractData),
|
|
500
437
|
...this.deduplicated,
|
|
@@ -510,12 +447,12 @@ class StudyData {
|
|
|
510
447
|
const data = {};
|
|
511
448
|
const hashValue = hasher.hash(this.deduplicated);
|
|
512
449
|
setValue(data, Tags.DeduppedHash, hashValue);
|
|
513
|
-
setValue(data, Tags.DeduppedType,
|
|
450
|
+
setValue(data, Tags.DeduppedType, 'info');
|
|
514
451
|
return data;
|
|
515
452
|
}
|
|
516
453
|
|
|
517
454
|
removeGz(name) {
|
|
518
|
-
const gzIndex = name.indexOf(
|
|
455
|
+
const gzIndex = name.indexOf('.gz');
|
|
519
456
|
return (gzIndex > 0 && name.substring(0, gzIndex)) || name;
|
|
520
457
|
}
|
|
521
458
|
|
|
@@ -523,28 +460,28 @@ class StudyData {
|
|
|
523
460
|
const deduplicatedDirectory = this.deduplicatedInstancesPath;
|
|
524
461
|
if (!fs.existsSync(deduplicatedDirectory)) return;
|
|
525
462
|
console.log(
|
|
526
|
-
|
|
463
|
+
'Deleting instances referenced in',
|
|
527
464
|
this.studyInstanceUid,
|
|
528
|
-
this.deduplicatedInstancesPath
|
|
465
|
+
this.deduplicatedInstancesPath
|
|
529
466
|
);
|
|
530
467
|
const files = await this.listJsonFiles(deduplicatedDirectory);
|
|
531
|
-
console.noQuiet(
|
|
468
|
+
console.noQuiet('Deleting', files.length, 'files');
|
|
532
469
|
let deleteCount = 0;
|
|
533
470
|
for (let i = 0; i < files.length; i++) {
|
|
534
471
|
const stat = files[i];
|
|
535
472
|
const { hash } = stat;
|
|
536
473
|
if (this.readHashes[hash]) {
|
|
537
|
-
console.noQuiet(
|
|
474
|
+
console.noQuiet('Deleting', deduplicatedDirectory, stat.name);
|
|
538
475
|
try {
|
|
539
476
|
fs.unlinkSync(path.join(deduplicatedDirectory, stat.name));
|
|
540
477
|
deleteCount += 1;
|
|
541
478
|
} catch (e) {
|
|
542
|
-
console.log(
|
|
479
|
+
console.log('Delete failed', e);
|
|
543
480
|
}
|
|
544
481
|
}
|
|
545
482
|
}
|
|
546
483
|
if (deleteCount === files.length) {
|
|
547
|
-
console.noQuiet(
|
|
484
|
+
console.noQuiet('Deleting instances directory', deduplicatedDirectory);
|
|
548
485
|
fs.rmdirSync(deduplicatedDirectory);
|
|
549
486
|
}
|
|
550
487
|
}
|
|
@@ -554,24 +491,18 @@ class StudyData {
|
|
|
554
491
|
const data = this.createInfo();
|
|
555
492
|
const hashValue = getValue(data, Tags.DeduppedHash);
|
|
556
493
|
console.noQuiet(
|
|
557
|
-
|
|
494
|
+
'Writing deduplicated hash data set data with',
|
|
558
495
|
Object.values(this.extractData).length,
|
|
559
|
-
|
|
496
|
+
'extract items and',
|
|
560
497
|
this.deduplicated.length,
|
|
561
|
-
|
|
498
|
+
'instance items'
|
|
562
499
|
);
|
|
563
500
|
setList(
|
|
564
501
|
data,
|
|
565
502
|
Tags.DeduppedRef,
|
|
566
|
-
Object.keys(this.readHashes).filter(
|
|
567
|
-
() => this.deduplicatedHashes[hashValue] == undefined,
|
|
568
|
-
),
|
|
503
|
+
Object.keys(this.readHashes).filter(() => this.deduplicatedHashes[hashValue] == undefined)
|
|
569
504
|
);
|
|
570
|
-
const deduplicatedList = [
|
|
571
|
-
data,
|
|
572
|
-
...Object.values(this.extractData),
|
|
573
|
-
...this.deduplicated,
|
|
574
|
-
];
|
|
505
|
+
const deduplicatedList = [data, ...Object.values(this.extractData), ...this.deduplicated];
|
|
575
506
|
// const naturalList = deduplicatedList.map(Tags.naturalizeDataset);
|
|
576
507
|
// // console.log("naturalList=", JSON.stringify(naturalList,null,2));
|
|
577
508
|
// console.log("Going to write study data", naturalList.length);
|
|
@@ -580,7 +511,7 @@ class StudyData {
|
|
|
580
511
|
gzip: true,
|
|
581
512
|
index: false,
|
|
582
513
|
});
|
|
583
|
-
console.noQuiet(
|
|
514
|
+
console.noQuiet('Wrote naturalized dataset');
|
|
584
515
|
}
|
|
585
516
|
|
|
586
517
|
getSopUids() {
|