@radicalimaging/static-wado-creator 0.6.4 → 0.6.5
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/lib/DeleteStudy.js +1 -1
- package/lib/StaticWado.js +22 -1
- package/lib/dictionary/Tags.js +56 -48
- package/lib/indexMain.js +10 -0
- package/lib/mkdicomwebConfig.js +6 -0
- package/lib/model/TagLists.js +6 -6
- package/lib/operation/InstanceDeduplicate.js +6 -6
- package/lib/operation/StudyData.js +22 -20
- package/lib/operation/adapter/transcodeImage.js +5 -6
- package/lib/util/compressionOptionParser.js +3 -3
- package/lib/writer/CompleteStudyWriter.js +2 -2
- package/lib/writer/HashDataWriter.js +1 -1
- package/package.json +7 -6
- package/lib/operation/adapter/asyncIterableToBuffer.js +0 -112
package/lib/DeleteStudy.js
CHANGED
|
@@ -8,7 +8,7 @@ module.exports = (options) =>
|
|
|
8
8
|
const studyData = await this.scanStudy("studies", studyInstanceUid);
|
|
9
9
|
studyData.delete();
|
|
10
10
|
const allStudies = await JSONReader(options.directoryName, "studies/index.json.gz", []);
|
|
11
|
-
const studiesWithoutDeleted = allStudies.filter((study) => studyInstanceUid != Tags.getValue(study,Tags.StudyInstanceUID));
|
|
11
|
+
const studiesWithoutDeleted = allStudies.filter((study) => studyInstanceUid != Tags.getValue(study, Tags.StudyInstanceUID));
|
|
12
12
|
await JSONWriter(options.directoryName, "studies", studiesWithoutDeleted);
|
|
13
13
|
delete this.studyData;
|
|
14
14
|
};
|
package/lib/StaticWado.js
CHANGED
|
@@ -4,7 +4,8 @@ const dicomParser = require("dicom-parser");
|
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const { dirScanner } = require("@radicalimaging/static-wado-util");
|
|
7
|
-
const
|
|
7
|
+
const { JSONReader } = require("@radicalimaging/static-wado-util");
|
|
8
|
+
const asyncIterableToBuffer = require("@radicalimaging/static-wado-util/lib/asyncIterableToBuffer");
|
|
8
9
|
const getDataSet = require("./operation/getDataSet");
|
|
9
10
|
const InstanceDeduplicate = require("./operation/InstanceDeduplicate");
|
|
10
11
|
const DeduplicateWriter = require("./writer/DeduplicateWriter");
|
|
@@ -19,6 +20,7 @@ const ThumbnailWriter = require("./writer/ThumbnailWriter");
|
|
|
19
20
|
const ThumbnailService = require("./operation/ThumbnailService");
|
|
20
21
|
const DeleteStudy = require("./DeleteStudy");
|
|
21
22
|
const RejectInstance = require("./RejectInstance");
|
|
23
|
+
const JSONWriter = require("./writer/JSONWriter");
|
|
22
24
|
|
|
23
25
|
function setStudyData(studyData) {
|
|
24
26
|
this.studyData = studyData;
|
|
@@ -202,5 +204,24 @@ class StaticWado {
|
|
|
202
204
|
await this.callback.completeStudy();
|
|
203
205
|
Stats.OverallStats.summarize("Completed Study Processing");
|
|
204
206
|
}
|
|
207
|
+
|
|
208
|
+
async reindex() {
|
|
209
|
+
const { directoryName } = this.options;
|
|
210
|
+
const studiesDir = `${directoryName}/studies/`;
|
|
211
|
+
|
|
212
|
+
console.log("Re-indexing", studiesDir);
|
|
213
|
+
const dirs = await fs.promises.readdir(studiesDir);
|
|
214
|
+
const studies = [];
|
|
215
|
+
for (const dir of dirs) {
|
|
216
|
+
const study = await JSONReader(`${studiesDir}/${dir}`, "index.json.gz", null);
|
|
217
|
+
if (study === null) {
|
|
218
|
+
console.log("No study found in", dir);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
console.log("Adding study", dir);
|
|
222
|
+
studies.push(...study);
|
|
223
|
+
}
|
|
224
|
+
return JSONWriter(directoryName, "studies", studies);
|
|
225
|
+
}
|
|
205
226
|
}
|
|
206
227
|
module.exports = StaticWado;
|
package/lib/dictionary/Tags.js
CHANGED
|
@@ -1,41 +1,42 @@
|
|
|
1
|
-
const dataDictionary = require("./dataDictionary");
|
|
2
1
|
const dcmjs = require("dcmjs");
|
|
2
|
+
const dataDictionary = require("./dataDictionary");
|
|
3
3
|
|
|
4
|
-
const { naturalizeDataset, denaturalizeDataset} = dcmjs.data.DicomMetaDictionary
|
|
4
|
+
const { naturalizeDataset, denaturalizeDataset } = dcmjs.data.DicomMetaDictionary;
|
|
5
5
|
|
|
6
6
|
/** Find the actual tag for a private value */
|
|
7
7
|
const findPrivate = (item, tagObject, create) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
if( create ) {
|
|
25
|
-
if( !assignPosition ) throw new Error(`Couldn't find any assign positions for ${creator} ${tag} in ${item}`);
|
|
26
|
-
const creatorTag = `${start}00${assignPosition.toString(16)}`
|
|
27
|
-
item[creatorTag] = { Value: [creator], vr: 'CS' };
|
|
28
|
-
return `${start}${assignPosition.toString(16)}${end}`
|
|
8
|
+
if (typeof tagObject === "string") return tagObject;
|
|
9
|
+
if (typeof tagObject === "number") return `00000000${tagObject.toString(16)}`.slice(-8);
|
|
10
|
+
const { creator, tag } = tagObject;
|
|
11
|
+
if (!creator) return tag;
|
|
12
|
+
const start = tag.substring(0, 4);
|
|
13
|
+
const end = tag.substring(6, 8);
|
|
14
|
+
// Should technically go all the way up, but for now only test 0x30 items
|
|
15
|
+
let assignPosition;
|
|
16
|
+
for (let offset = 0x10; offset < 0x40; offset++) {
|
|
17
|
+
const testTag = `${start}00${offset.toString(16)}`;
|
|
18
|
+
const testCreator = item[testTag];
|
|
19
|
+
if (testCreator === undefined && assignPosition === undefined) assignPosition = offset;
|
|
20
|
+
if (testCreator && testCreator.Value && testCreator.Value[0] === creator) {
|
|
21
|
+
return `${start}${offset.toString(16)}${end}`;
|
|
29
22
|
}
|
|
30
|
-
}
|
|
23
|
+
}
|
|
24
|
+
if (create) {
|
|
25
|
+
if (!assignPosition) throw new Error(`Couldn't find any assign positions for ${creator} ${tag} in ${item}`);
|
|
26
|
+
const creatorTag = `${start}00${assignPosition.toString(16)}`;
|
|
27
|
+
item[creatorTag] = { Value: [creator], vr: "CS" };
|
|
28
|
+
return `${start}${assignPosition.toString(16)}${end}`;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
31
|
|
|
32
32
|
const DeduppedCreator = "dedupped";
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
const Tags = {
|
|
35
35
|
// Raw tags have the x before them, not parsed yet
|
|
36
36
|
RawMinTag: "x00000000",
|
|
37
37
|
RawFirstBodyTag: "x00080000",
|
|
38
38
|
RawTransferSyntaxUID: "x00020010",
|
|
39
|
+
RawSopInstanceUID: "x00080018",
|
|
39
40
|
|
|
40
41
|
// This one isn't defined in the dataDictionary
|
|
41
42
|
AvailableTransferSyntaxUID: "00083002",
|
|
@@ -43,60 +44,67 @@ const Tags = {
|
|
|
43
44
|
DeduppedCreator,
|
|
44
45
|
|
|
45
46
|
// The references to extract data included in this object, 1..n values
|
|
46
|
-
DeduppedRef: { creator: DeduppedCreator, tag: "00091010"},
|
|
47
|
+
DeduppedRef: { creator: DeduppedCreator, tag: "00091010" },
|
|
47
48
|
|
|
48
49
|
// The hash value of THIS object
|
|
49
|
-
DeduppedHash: {creator: DeduppedCreator, tag: "00091011"},
|
|
50
|
+
DeduppedHash: { creator: DeduppedCreator, tag: "00091011" },
|
|
50
51
|
|
|
51
52
|
// Type of hash instance
|
|
52
|
-
DeduppedType: {creator: DeduppedCreator, tag: "00091012"},
|
|
53
|
+
DeduppedType: { creator: DeduppedCreator, tag: "00091012" },
|
|
53
54
|
InstanceType: "instance",
|
|
54
55
|
InfoType: "info",
|
|
55
56
|
|
|
56
|
-
naturalizeDataset,
|
|
57
|
-
|
|
57
|
+
naturalizeDataset,
|
|
58
|
+
denaturalizeDataset,
|
|
59
|
+
|
|
58
60
|
setValue: (item, tag, value) => {
|
|
59
|
-
const actualTag = findPrivate(item,tag,true);
|
|
61
|
+
const actualTag = findPrivate(item, tag, true);
|
|
60
62
|
item[actualTag] = { Value: Array.isArray(value) ? value : [value] };
|
|
61
63
|
return actualTag;
|
|
62
64
|
},
|
|
63
65
|
|
|
64
66
|
setList: (item, tag, value) => {
|
|
65
|
-
const actualTag = findPrivate(item,tag,true);
|
|
66
|
-
item[actualTag] =
|
|
67
|
+
const actualTag = findPrivate(item, tag, true);
|
|
68
|
+
item[actualTag] = { Value: Array.isArray(value) ? value : [value] };
|
|
67
69
|
return actualTag;
|
|
68
70
|
},
|
|
69
71
|
|
|
70
72
|
pushList: (item, tag, value) => {
|
|
71
|
-
const actualTag = findPrivate(item,tag,true);
|
|
72
|
-
if(
|
|
73
|
+
const actualTag = findPrivate(item, tag, true);
|
|
74
|
+
if (!item[actualTag]) item[actualTag] = { Value: [] };
|
|
73
75
|
item[actualTag].Value.push(value);
|
|
74
76
|
return actualTag;
|
|
75
77
|
},
|
|
76
78
|
|
|
77
79
|
getValue: (item, tag) => {
|
|
78
|
-
const actualTag = findPrivate(item,tag);
|
|
79
|
-
if(
|
|
80
|
+
const actualTag = findPrivate(item, tag);
|
|
81
|
+
if (!actualTag) return undefined;
|
|
80
82
|
const value = item[actualTag];
|
|
81
|
-
if(
|
|
82
|
-
if(
|
|
83
|
-
|
|
83
|
+
if (value === undefined) return undefined;
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
if (value.length == 1) return value[0];
|
|
86
|
+
return value.length === 0 ? "" : value;
|
|
87
|
+
}
|
|
88
|
+
if (value.Value) {
|
|
89
|
+
if (value.Value.length == 1) return value.Value[0];
|
|
90
|
+
return value.Value.length === 0 ? "" : value.Value;
|
|
91
|
+
}
|
|
84
92
|
return value;
|
|
85
93
|
},
|
|
86
94
|
|
|
87
95
|
getList: (item, tag) => {
|
|
88
|
-
const actualTag = findPrivate(item,tag);
|
|
89
|
-
if(
|
|
96
|
+
const actualTag = findPrivate(item, tag);
|
|
97
|
+
if (!actualTag) return undefined;
|
|
90
98
|
const value = item[actualTag];
|
|
91
|
-
if(
|
|
92
|
-
if(
|
|
93
|
-
if(
|
|
99
|
+
if (value === undefined) return undefined;
|
|
100
|
+
if (Array.isArray(value)) return value;
|
|
101
|
+
if (value.Value) return value.Value;
|
|
94
102
|
return [value];
|
|
95
103
|
},
|
|
96
104
|
|
|
97
|
-
removeValue: (item,tag) => {
|
|
98
|
-
const actualTag = findPrivate(item,tag);
|
|
99
|
-
if(
|
|
105
|
+
removeValue: (item, tag) => {
|
|
106
|
+
const actualTag = findPrivate(item, tag);
|
|
107
|
+
if (actualTag) delete item[actualTag];
|
|
100
108
|
// TODO, remove creator tag if not used any longer
|
|
101
109
|
},
|
|
102
110
|
|
package/lib/indexMain.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const StaticWado = require("./StaticWado");
|
|
2
|
+
const adaptProgramOpts = require("./util/adaptProgramOpts");
|
|
3
|
+
|
|
4
|
+
module.exports = function createMain(options) {
|
|
5
|
+
const finalOptions = adaptProgramOpts(options, {
|
|
6
|
+
...this,
|
|
7
|
+
});
|
|
8
|
+
const importer = new StaticWado(finalOptions);
|
|
9
|
+
return importer.reindex();
|
|
10
|
+
};
|
package/lib/mkdicomwebConfig.js
CHANGED
|
@@ -4,6 +4,7 @@ const createMain = require("./createMain");
|
|
|
4
4
|
const deleteMain = require("./deleteMain");
|
|
5
5
|
const rejectMain = require("./rejectMain");
|
|
6
6
|
const instanceMain = require("./instanceMain");
|
|
7
|
+
const indexMain = require("./indexMain");
|
|
7
8
|
const groupMain = require("./groupMain");
|
|
8
9
|
const metadataMain = require("./metadataMain");
|
|
9
10
|
const compressionOptionParser = require("./util/compressionOptionParser");
|
|
@@ -114,6 +115,11 @@ const { mkdicomwebConfig } = ConfigPoint.register({
|
|
|
114
115
|
"deduplicated files each time a study instance UID is found, and only updates those studies\n" +
|
|
115
116
|
"having at least one ",
|
|
116
117
|
},
|
|
118
|
+
{
|
|
119
|
+
command: "index",
|
|
120
|
+
main: indexMain,
|
|
121
|
+
helpDescription: "Recreate the index for the DICOMweb data.",
|
|
122
|
+
},
|
|
117
123
|
{
|
|
118
124
|
command: "instance",
|
|
119
125
|
arguments: ["input"],
|
package/lib/model/TagLists.js
CHANGED
|
@@ -9,9 +9,9 @@ const { PatientID, PatientName, IssuerOfPatientID } = Tags;
|
|
|
9
9
|
const { StudyDescription, AccessionNumber, StudyInstanceUID, StudyDate, StudyTime } = Tags;
|
|
10
10
|
const { SeriesDescription, SeriesNumber, SeriesInstanceUID, SeriesDate, SeriesTime } = Tags;
|
|
11
11
|
|
|
12
|
-
const { DeduppedHash, DeduppedRef, DeduppedType
|
|
12
|
+
const { DeduppedHash, DeduppedRef, DeduppedType } = Tags;
|
|
13
13
|
|
|
14
|
-
const { pushList,
|
|
14
|
+
const { pushList, setValue, getValue } = Tags;
|
|
15
15
|
|
|
16
16
|
const PatientQuery = [
|
|
17
17
|
PatientID,
|
|
@@ -106,14 +106,14 @@ const ImageExtract = [
|
|
|
106
106
|
];
|
|
107
107
|
|
|
108
108
|
const addHash = (data, type) => {
|
|
109
|
-
const existing = getValue(data,DeduppedHash);
|
|
109
|
+
const existing = getValue(data, DeduppedHash);
|
|
110
110
|
if (existing) {
|
|
111
111
|
return existing;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
const hashValue = hasher.hash(data);
|
|
115
|
-
setValue(data, DeduppedHash, hashValue
|
|
116
|
-
setValue(data, DeduppedType, type
|
|
115
|
+
setValue(data, DeduppedHash, hashValue);
|
|
116
|
+
setValue(data, DeduppedType, type);
|
|
117
117
|
return hashValue;
|
|
118
118
|
};
|
|
119
119
|
|
|
@@ -141,7 +141,7 @@ const TagSets = {
|
|
|
141
141
|
});
|
|
142
142
|
const hashValue = addHash(ret, type);
|
|
143
143
|
if (hashRef) {
|
|
144
|
-
pushList(data,DeduppedRef,hashValue);
|
|
144
|
+
pushList(data, DeduppedRef, hashValue);
|
|
145
145
|
}
|
|
146
146
|
return ret;
|
|
147
147
|
},
|
|
@@ -3,7 +3,7 @@ const JSONWriter = require("../writer/JSONWriter");
|
|
|
3
3
|
const TagLists = require("../model/TagLists");
|
|
4
4
|
const Tags = require("../dictionary/Tags");
|
|
5
5
|
|
|
6
|
-
const {getValue, setValue} = Tags;
|
|
6
|
+
const { getValue, setValue } = Tags;
|
|
7
7
|
|
|
8
8
|
const extractors = {
|
|
9
9
|
patient: TagLists.PatientQuery,
|
|
@@ -21,26 +21,26 @@ const extractors = {
|
|
|
21
21
|
async function deduplicateSingleInstance(id, imageFrame) {
|
|
22
22
|
if (!imageFrame) return {};
|
|
23
23
|
const studyData = await this.completeStudy.getCurrentStudyData(this, id);
|
|
24
|
-
const seriesUID = getValue(imageFrame,Tags.SeriesInstanceUID);
|
|
25
|
-
const sopUID = getValue(imageFrame,Tags.SOPInstanceUID);
|
|
24
|
+
const seriesUID = getValue(imageFrame, Tags.SeriesInstanceUID);
|
|
25
|
+
const sopUID = getValue(imageFrame, Tags.SOPInstanceUID);
|
|
26
26
|
if (!sopUID) {
|
|
27
27
|
console.warn("No sop instance UID in", imageFrame);
|
|
28
28
|
return {};
|
|
29
29
|
}
|
|
30
30
|
if (studyData.sopExists(sopUID)) {
|
|
31
|
-
if(
|
|
31
|
+
if (this.verbose) console.log("SOP Instance UID", sopUID, "already exists, skipping");
|
|
32
32
|
// TODO - allow replace as an option
|
|
33
33
|
// Null value means skip writing this instance
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
if(
|
|
37
|
+
if (this.verbose) console.log("SOP Instance UID", sopUID, "being added");
|
|
38
38
|
const deduplicated = { ...imageFrame };
|
|
39
39
|
|
|
40
40
|
if (!this.extractors) this.extractors = extractors;
|
|
41
41
|
for (const key of Object.keys(this.extractors)) {
|
|
42
42
|
const extracted = TagLists.extract(deduplicated, key, this.extractors[key], TagLists.RemoveExtract);
|
|
43
|
-
const hashKey = getValue(extracted,Tags.DeduppedHash);
|
|
43
|
+
const hashKey = getValue(extracted, Tags.DeduppedHash);
|
|
44
44
|
await studyData.addExtracted(this, hashKey, extracted);
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -6,11 +6,10 @@ const Tags = require("../dictionary/Tags");
|
|
|
6
6
|
const TagLists = require("../model/TagLists");
|
|
7
7
|
const JSONWriter = require("../writer/JSONWriter");
|
|
8
8
|
|
|
9
|
-
const {getValue, setValue, getList, setList} = Tags;
|
|
9
|
+
const { getValue, setValue, getList, setList } = Tags;
|
|
10
10
|
const hasher = hashFactory();
|
|
11
11
|
|
|
12
|
-
const getSeriesInstanceUid = (seriesInstance) =>
|
|
13
|
-
getValue(seriesInstance,Tags.SeriesInstanceUID);
|
|
12
|
+
const getSeriesInstanceUid = (seriesInstance) => getValue(seriesInstance, Tags.SeriesInstanceUID);
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* StudyData contains information about the grouped study data. It is used to create
|
|
@@ -88,10 +87,10 @@ class StudyData {
|
|
|
88
87
|
return true;
|
|
89
88
|
}
|
|
90
89
|
const info = deduplicatedTopFile[0];
|
|
91
|
-
if (!info || getValue(info,Tags.DeduppedHash) || getValue(info,Tags.DeduppedType) != "info") {
|
|
90
|
+
if (!info || getValue(info, Tags.DeduppedHash) || getValue(info, Tags.DeduppedType) != "info") {
|
|
92
91
|
return true;
|
|
93
92
|
}
|
|
94
|
-
const hashValue = getValue(info,Tags.DeduppedHash);
|
|
93
|
+
const hashValue = getValue(info, Tags.DeduppedHash);
|
|
95
94
|
return this.existingFiles[0].indexOf(hashValue) == -1;
|
|
96
95
|
} catch (e) {
|
|
97
96
|
console.log("Assume study metadata is dirty", e);
|
|
@@ -105,12 +104,12 @@ class StudyData {
|
|
|
105
104
|
this.newInstancesAdded += 1;
|
|
106
105
|
for (let i = 0; i < this.deduplicated.length; i++) {
|
|
107
106
|
const recombined = await this.recombine(i);
|
|
108
|
-
const reSeriesUid = getValue(recombined,Tags.SeriesInstanceUID);
|
|
107
|
+
const reSeriesUid = getValue(recombined, Tags.SeriesInstanceUID);
|
|
109
108
|
if (reSeriesUid !== seriesInstanceUid) continue;
|
|
110
|
-
const reSop = getValue(recombined,Tags.SOPInstanceUID);
|
|
109
|
+
const reSop = getValue(recombined, Tags.SOPInstanceUID);
|
|
111
110
|
if (!reSop) continue;
|
|
112
111
|
if (reSop !== sopInstanceUid) continue;
|
|
113
|
-
setValue(this.deduplicated[i], Tags.DeduppedType,"deleted");
|
|
112
|
+
setValue(this.deduplicated[i], Tags.DeduppedType, "deleted");
|
|
114
113
|
}
|
|
115
114
|
}
|
|
116
115
|
|
|
@@ -150,7 +149,7 @@ class StudyData {
|
|
|
150
149
|
if (index < 0 || index >= this.deduplicated.length) {
|
|
151
150
|
throw new Error(`Can't read index ${index}, out of bounds [0..${this.deduplicated.length})`);
|
|
152
151
|
}
|
|
153
|
-
const refs = getList(deduplicated,Tags.DeduppedRef);
|
|
152
|
+
const refs = getList(deduplicated, Tags.DeduppedRef);
|
|
154
153
|
if (!refs) {
|
|
155
154
|
console.log("No refs for", deduplicated);
|
|
156
155
|
return deduplicated;
|
|
@@ -165,7 +164,7 @@ class StudyData {
|
|
|
165
164
|
|
|
166
165
|
async addExtracted(callback, hashKey, item) {
|
|
167
166
|
if (this.extractData[hashKey]) {
|
|
168
|
-
if(
|
|
167
|
+
if (this.verbose) console.log("Already have extracted", hashKey, getValue(item, Tags.DeduppedType));
|
|
169
168
|
return;
|
|
170
169
|
}
|
|
171
170
|
await callback.bulkdata(this, hashKey, item);
|
|
@@ -192,7 +191,7 @@ class StudyData {
|
|
|
192
191
|
// console.log('Not adding', hashValue, 'because the hash exists');
|
|
193
192
|
return false;
|
|
194
193
|
}
|
|
195
|
-
const sopValue = getValue(data,Tags.SOPInstanceUID);
|
|
194
|
+
const sopValue = getValue(data, Tags.SOPInstanceUID);
|
|
196
195
|
const sopIndex = this.sopInstances[sopValue];
|
|
197
196
|
if (!sopValue) {
|
|
198
197
|
console.warn("No sop value in", filename, "reading", data);
|
|
@@ -275,14 +274,14 @@ class StudyData {
|
|
|
275
274
|
if (type == Tags.InstanceType) {
|
|
276
275
|
this.internalAddDeduplicated(item, name);
|
|
277
276
|
} else if (type == "info") {
|
|
278
|
-
const refs = getList(item,Tags.DeduppedRef);
|
|
277
|
+
const refs = getList(item, Tags.DeduppedRef);
|
|
279
278
|
if (refs) {
|
|
280
279
|
refs.forEach((hashValue) => {
|
|
281
280
|
this.readHashes[hashValue] = `${hashValue}.gz`;
|
|
282
281
|
});
|
|
283
282
|
}
|
|
284
283
|
} else {
|
|
285
|
-
const hashValue = getValue(item,Tags.DeduppedHash);
|
|
284
|
+
const hashValue = getValue(item, Tags.DeduppedHash);
|
|
286
285
|
if (hashValue) {
|
|
287
286
|
this.extractData[hashValue] = item;
|
|
288
287
|
}
|
|
@@ -299,7 +298,7 @@ class StudyData {
|
|
|
299
298
|
|
|
300
299
|
for (let i = 0; i < this.numberOfInstances; i++) {
|
|
301
300
|
const seriesInstance = await this.recombine(i);
|
|
302
|
-
const type = getValue(seriesInstance,Tags.DeduppedType);
|
|
301
|
+
const type = getValue(seriesInstance, Tags.DeduppedType);
|
|
303
302
|
if (type == "deleted") {
|
|
304
303
|
console.log("Skipping", type);
|
|
305
304
|
continue;
|
|
@@ -337,7 +336,7 @@ class StudyData {
|
|
|
337
336
|
numberOfInstances += instances.length;
|
|
338
337
|
numberOfSeries += 1;
|
|
339
338
|
seriesList.push(seriesQuery);
|
|
340
|
-
const modality = getValue(seriesQuery,Tags.Modality);
|
|
339
|
+
const modality = getValue(seriesQuery, Tags.Modality);
|
|
341
340
|
if (modalitiesInStudy.indexOf(modality) == -1) modalitiesInStudy.push(modality);
|
|
342
341
|
await JSONWriter(seriesPath, "metadata", instances, {
|
|
343
342
|
gzip: true,
|
|
@@ -379,8 +378,8 @@ class StudyData {
|
|
|
379
378
|
createInfo() {
|
|
380
379
|
const data = {};
|
|
381
380
|
const hashValue = hasher.hash(this.deduplicated);
|
|
382
|
-
setValue(data,Tags.DeduppedHash,hashValue);
|
|
383
|
-
setValue(data,Tags.DeduppedType,"info");
|
|
381
|
+
setValue(data, Tags.DeduppedHash, hashValue);
|
|
382
|
+
setValue(data, Tags.DeduppedType, "info");
|
|
384
383
|
return data;
|
|
385
384
|
}
|
|
386
385
|
|
|
@@ -393,7 +392,7 @@ class StudyData {
|
|
|
393
392
|
/** Writes the deduplicated group */
|
|
394
393
|
async writeDeduplicatedGroup() {
|
|
395
394
|
const data = this.createInfo();
|
|
396
|
-
const hashValue = getValue(data,Tags.DeduppedHash);
|
|
395
|
+
const hashValue = getValue(data, Tags.DeduppedHash);
|
|
397
396
|
console.log(
|
|
398
397
|
"Writing deduplicated hash data set data with",
|
|
399
398
|
Object.values(this.extractData).length,
|
|
@@ -401,8 +400,11 @@ class StudyData {
|
|
|
401
400
|
this.deduplicated.length,
|
|
402
401
|
"instance items"
|
|
403
402
|
);
|
|
404
|
-
setList(
|
|
405
|
-
|
|
403
|
+
setList(
|
|
404
|
+
data,
|
|
405
|
+
Tags.DeduppedRef,
|
|
406
|
+
Object.keys(this.readHashes).filter(() => this.deduplicatedHashes[hashValue] == undefined)
|
|
407
|
+
);
|
|
406
408
|
const deduplicatedList = [data, ...Object.values(this.extractData), ...this.deduplicated];
|
|
407
409
|
// const naturalList = deduplicatedList.map(Tags.naturalizeDataset);
|
|
408
410
|
// // console.log("naturalList=", JSON.stringify(naturalList,null,2));
|
|
@@ -15,7 +15,7 @@ const transcodeOp = {
|
|
|
15
15
|
const transcodeDestinationMap = {
|
|
16
16
|
lei: {
|
|
17
17
|
transferSyntaxUid: "1.2.840.10008.1.2.1",
|
|
18
|
-
transcodeOp: transcodeOp.none
|
|
18
|
+
transcodeOp: transcodeOp.none,
|
|
19
19
|
},
|
|
20
20
|
jls: {
|
|
21
21
|
transferSyntaxUid: "1.2.840.10008.1.2.4.80",
|
|
@@ -71,9 +71,9 @@ const transcodeSourceMap = {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Get an existing destination transcoder from destination map.
|
|
74
|
+
* Get an existing destination transcoder from destination map.
|
|
75
75
|
*
|
|
76
|
-
* @param {*} id Destination type to compress to
|
|
76
|
+
* @param {*} id Destination type to compress to
|
|
77
77
|
* @returns A partial transcoder definition. Otherwise it returns undefined.
|
|
78
78
|
*/
|
|
79
79
|
function getDestinationTranscoder(id) {
|
|
@@ -99,7 +99,7 @@ function getTranscoder(transferSyntaxUid, { contentType, verbose }) {
|
|
|
99
99
|
if (!sourceTranscoder || !destinationTranscoder) {
|
|
100
100
|
return undefined;
|
|
101
101
|
}
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
return {
|
|
104
104
|
transferSyntaxUid: destinationTranscoder.transferSyntaxUid,
|
|
105
105
|
transcodeOp: sourceTranscoder.transcodeOp | destinationTranscoder.transcodeOp, // eslint-disable-line no-bitwise
|
|
@@ -107,7 +107,6 @@ function getTranscoder(transferSyntaxUid, { contentType, verbose }) {
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
|
|
111
110
|
/**
|
|
112
111
|
* Tell whether given id contain transferSyntaxUid which can be transcoded or not.
|
|
113
112
|
* @param {*} id object containing transferSyntaxUid
|
|
@@ -289,7 +288,7 @@ function transcodeMetadata(metadata, id, options) {
|
|
|
289
288
|
const result = { ...metadata };
|
|
290
289
|
|
|
291
290
|
if (result[Tags.AvailableTransferSyntaxUID]) {
|
|
292
|
-
Tags.setValue(result,Tags.AvailableTransferSyntaxUID, transcodedId.transferSyntaxUid);
|
|
291
|
+
Tags.setValue(result, Tags.AvailableTransferSyntaxUID, transcodedId.transferSyntaxUid);
|
|
293
292
|
}
|
|
294
293
|
|
|
295
294
|
return result;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
const { program } = require("@radicalimaging/static-wado-util");
|
|
2
2
|
const { getDestinationTranscoder } = require("../operation/adapter/transcodeImage");
|
|
3
3
|
|
|
4
|
-
module.exports = function compressionOptionParser(value) {
|
|
4
|
+
module.exports = function compressionOptionParser(value) {
|
|
5
5
|
const destination = getDestinationTranscoder(value);
|
|
6
6
|
if (destination) {
|
|
7
7
|
console.log("Found destination transferSyntax ", destination.transferSyntaxUid, "for ", value);
|
|
8
8
|
} else {
|
|
9
9
|
console.log("No transcoder destination for ", value);
|
|
10
10
|
throw new program.InvalidArgumentError(`No transcoder destination for ${value}`);
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
12
|
return destination.transferSyntaxUid;
|
|
13
|
-
}
|
|
13
|
+
};
|
|
@@ -47,12 +47,12 @@ const CompleteStudyWriter = (options) => {
|
|
|
47
47
|
const studyQuery = await studyData.writeMetadata();
|
|
48
48
|
|
|
49
49
|
const allStudies = await JSONReader(options.directoryName, "studies/index.json.gz", []);
|
|
50
|
-
const studyUID = Tags.getValue(studyQuery,Tags.StudyInstanceUID);
|
|
50
|
+
const studyUID = Tags.getValue(studyQuery, Tags.StudyInstanceUID);
|
|
51
51
|
if (!studyUID) {
|
|
52
52
|
console.error("studyQuery=", studyQuery);
|
|
53
53
|
throw new Error("Study query has null studyUID");
|
|
54
54
|
}
|
|
55
|
-
const studyIndex = allStudies.findIndex((item) => Tags.getValue(item,Tags.StudyInstanceUID) == studyUID);
|
|
55
|
+
const studyIndex = allStudies.findIndex((item) => Tags.getValue(item, Tags.StudyInstanceUID) == studyUID);
|
|
56
56
|
if (studyIndex == -1) {
|
|
57
57
|
allStudies.push(studyQuery);
|
|
58
58
|
} else {
|
|
@@ -47,7 +47,7 @@ HashDataWriter.createHashPath = (data, options = {}) => {
|
|
|
47
47
|
const { mimeType } = options;
|
|
48
48
|
const isRaw = ArrayBuffer.isView(data);
|
|
49
49
|
const extension = isRaw ? (mimeType && extensions[mimeType]) || "" : ".json";
|
|
50
|
-
const hashValue = Tags.getValue(data,Tags.DeduppedHash) || hasher.hash(data);
|
|
50
|
+
const hashValue = Tags.getValue(data, Tags.DeduppedHash) || hasher.hash(data);
|
|
51
51
|
|
|
52
52
|
return {
|
|
53
53
|
// Use string concat as this value is used for the BulkDataURI which needs forward slashes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@radicalimaging/static-wado-creator",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
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"
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"build": "echo \"No build yet\" && exit 0",
|
|
54
54
|
"link:exec": "npm link",
|
|
55
55
|
"lint": "npx eslint --color \"*(bin|lib)/**/*.{js,mjs}\"",
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
"lint:fix": "npx eslint --fix --color \"*(bin|lib)/**/*.{js,mjs}\""
|
|
57
|
+
},
|
|
58
58
|
"jest": {
|
|
59
59
|
"verbose": true
|
|
60
60
|
},
|
|
@@ -63,11 +63,12 @@
|
|
|
63
63
|
"@radicalimaging/static-cs-lite": "^0.0.2",
|
|
64
64
|
"@radicalimaging/static-wado-util": "^0.6.2",
|
|
65
65
|
"canvas": "^2.9.1",
|
|
66
|
-
"config-point": "
|
|
66
|
+
"config-point": "0.5.1",
|
|
67
|
+
"dcmjs": "^0.27.0",
|
|
67
68
|
"dicom-parser": "^1.8.13",
|
|
68
69
|
"glob": "^7.2.0",
|
|
69
|
-
"
|
|
70
|
-
"
|
|
70
|
+
"node-object-hash": "^2.3.10",
|
|
71
|
+
"uuid": "^8.3.2"
|
|
71
72
|
},
|
|
72
73
|
"devDependencies": {
|
|
73
74
|
"must": "^0.13.4"
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
const { Stats } = require("@radicalimaging/static-wado-util");
|
|
2
|
-
/* eslint "no-param-reassign": "off" */
|
|
3
|
-
|
|
4
|
-
const handler = {
|
|
5
|
-
get(obj, key) {
|
|
6
|
-
const ikey = parseInt(key);
|
|
7
|
-
if (ikey == key) return obj.index_get(ikey);
|
|
8
|
-
const handlerFunc = handler[key];
|
|
9
|
-
if (handlerFunc && key != "get") return handlerFunc(obj, key);
|
|
10
|
-
if (!obj._keys[key]) {
|
|
11
|
-
console.log("New call to", key);
|
|
12
|
-
obj._keys[key] = true;
|
|
13
|
-
}
|
|
14
|
-
return obj[key];
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
length: (obj) => obj.combinedLength,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const StreamingFunctions = {
|
|
21
|
-
addChunks(chunks) {
|
|
22
|
-
if (!this.chunks) this.chunks = [];
|
|
23
|
-
chunks.forEach((chunk) => this.chunks.push(chunk));
|
|
24
|
-
this.combinedLength = 0;
|
|
25
|
-
let i = 0;
|
|
26
|
-
this.chunks.forEach((chunk) => {
|
|
27
|
-
chunk.start = this.combinedLength;
|
|
28
|
-
chunk.i = i;
|
|
29
|
-
i += 1;
|
|
30
|
-
this.combinedLength += chunk.length;
|
|
31
|
-
});
|
|
32
|
-
this.lastChunk = this.chunks[0];
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
index_get(ikey) {
|
|
36
|
-
const found = this.findChunk(ikey);
|
|
37
|
-
if (!found) throw Error(`index ${ikey} not found between 0..${this.combinedLength}`);
|
|
38
|
-
return found[ikey - found.start];
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
findChunk(ikey) {
|
|
42
|
-
let i = (this.lastChunk.start <= ikey && this.lastChunk.i) || 0;
|
|
43
|
-
while (i < this.chunks.length) {
|
|
44
|
-
const chunk = this.chunks[i];
|
|
45
|
-
if (ikey >= chunk.start && ikey < chunk.start + chunk.length) {
|
|
46
|
-
this.lastChunk = chunk;
|
|
47
|
-
return chunk;
|
|
48
|
-
}
|
|
49
|
-
i += 1;
|
|
50
|
-
}
|
|
51
|
-
return undefined;
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
slice(start, end) {
|
|
55
|
-
const buflen = end - start;
|
|
56
|
-
const ret = Buffer.alloc(buflen);
|
|
57
|
-
let i = 0;
|
|
58
|
-
while (i < buflen) {
|
|
59
|
-
const chunk = this.findChunk(start + i);
|
|
60
|
-
const chunkI = start + i - chunk.start;
|
|
61
|
-
const useLen = Math.min(buflen, chunk.length - chunkI);
|
|
62
|
-
chunk.copy(ret, i, chunkI, chunkI + useLen);
|
|
63
|
-
i += useLen;
|
|
64
|
-
}
|
|
65
|
-
return ret;
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
hexSlice(start, end) {
|
|
69
|
-
return this.slice(start, end).hexSlice();
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
/** The internal node copy function is a native that directly accesses internal class details, so over-ride it.
|
|
73
|
-
* TODO: Make this efficient by using the internal copy function when available rather than copy one at a time.
|
|
74
|
-
*/
|
|
75
|
-
copy(target, targetStart = 0, srcStart = 0, srcEnd = 0) {
|
|
76
|
-
const { length } = target;
|
|
77
|
-
const srcLength = (srcEnd === undefined && Math.min(this.length, srcEnd)) || this.length;
|
|
78
|
-
const copied = 0;
|
|
79
|
-
while (targetStart < length && srcStart < srcLength) {
|
|
80
|
-
target[targetStart] = this[srcStart];
|
|
81
|
-
targetStart += 1;
|
|
82
|
-
srcStart += 1;
|
|
83
|
-
}
|
|
84
|
-
return copied;
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
_keys: { then: true, lastChunk: true, chunks: true },
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
Object.keys(StreamingFunctions).forEach((key) => {
|
|
91
|
-
StreamingFunctions._keys[key] = true;
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const StreamingBuffer = (chunks) => {
|
|
95
|
-
const buf = Buffer.from("NotUsed");
|
|
96
|
-
Object.assign(buf, StreamingFunctions);
|
|
97
|
-
buf.addChunks(chunks);
|
|
98
|
-
return new Proxy(buf, handler);
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const asyncIteratorToBuffer = async (readable) => {
|
|
102
|
-
if (ArrayBuffer.isView(readable)) return readable;
|
|
103
|
-
const chunks = [];
|
|
104
|
-
for await (const chunk of readable) {
|
|
105
|
-
chunks.push(chunk);
|
|
106
|
-
Stats.BufferStats.add("Read Async", `Read async buffer ${chunks.length}`, 1024);
|
|
107
|
-
}
|
|
108
|
-
Stats.BufferStats.reset();
|
|
109
|
-
return StreamingBuffer(chunks);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
module.exports = asyncIteratorToBuffer;
|