dicom-curate 0.6.1 → 0.7.0
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/README.md +45 -53
- package/dist/esm/applyMappingsWorker.js +13 -18
- package/dist/esm/collectMappings.js +13 -18
- package/dist/esm/config/sample2PassCurationSpecification.js +61 -68
- package/dist/esm/config/sampleBatchCurationSpecification.js +44 -121
- package/dist/esm/config/specVersion.js +1 -1
- package/dist/esm/curateDict.js +13 -18
- package/dist/esm/curateOne.js +13 -18
- package/dist/esm/index.js +13 -182
- package/dist/types/config/specVersion.d.ts +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/types.d.ts +11 -10
- package/dist/umd/dicom-curate.umd.js +16 -203
- package/dist/umd/dicom-curate.umd.js.map +1 -1
- package/dist/umd/dicom-curate.umd.min.js +3 -3
- package/dist/umd/dicom-curate.umd.min.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -97,8 +97,7 @@ import type { TCurationSpecification } from 'dicom-curate'
|
|
|
97
97
|
* Curation specification for batch-curating DICOM files.
|
|
98
98
|
*/
|
|
99
99
|
export function sampleBatchCurationSpecification(): TCurationSpecification {
|
|
100
|
-
|
|
101
|
-
const identifiers = {
|
|
100
|
+
const hostProps = {
|
|
102
101
|
protocolNumber: 'Sample_Protocol_Number',
|
|
103
102
|
activityProviderName: 'Sample_CRO',
|
|
104
103
|
centerSubjectId: /^[A-Z]{2}\d{2}-\d{3}$/,
|
|
@@ -117,8 +116,8 @@ export function sampleBatchCurationSpecification(): TCurationSpecification {
|
|
|
117
116
|
// collect from a csv file. A client can use regex to validate the input.
|
|
118
117
|
type: 'load',
|
|
119
118
|
collect: {
|
|
120
|
-
CURR_ID:
|
|
121
|
-
StudyDescription:
|
|
119
|
+
CURR_ID: hostProps.centerSubjectId,
|
|
120
|
+
StudyDescription: hostProps.timepointNames,
|
|
122
121
|
MAPPED_ID: /BLIND_\d+/,
|
|
123
122
|
},
|
|
124
123
|
// With this, can refer to mappings as parser.getMapping('blindedId')
|
|
@@ -132,8 +131,8 @@ export function sampleBatchCurationSpecification(): TCurationSpecification {
|
|
|
132
131
|
},
|
|
133
132
|
},
|
|
134
133
|
|
|
135
|
-
version: '
|
|
136
|
-
|
|
134
|
+
version: '3.0',
|
|
135
|
+
hostProps,
|
|
137
136
|
|
|
138
137
|
// This specifies the standardized DICOM de-identification
|
|
139
138
|
dicomPS315EOptions: {
|
|
@@ -141,10 +140,10 @@ export function sampleBatchCurationSpecification(): TCurationSpecification {
|
|
|
141
140
|
cleanDescriptorsExceptions: ['SeriesDescription'],
|
|
142
141
|
retainLongitudinalTemporalInformationOptions: 'Full',
|
|
143
142
|
retainPatientCharacteristicsOption: [
|
|
144
|
-
'
|
|
145
|
-
'
|
|
146
|
-
'
|
|
147
|
-
'
|
|
143
|
+
'PatientWeight',
|
|
144
|
+
'PatientSize',
|
|
145
|
+
'PatientAge',
|
|
146
|
+
'PatientSex',
|
|
148
147
|
'SelectorASValue',
|
|
149
148
|
],
|
|
150
149
|
retainDeviceIdentityOption: true,
|
|
@@ -153,60 +152,53 @@ export function sampleBatchCurationSpecification(): TCurationSpecification {
|
|
|
153
152
|
retainInstitutionIdentityOption: true,
|
|
154
153
|
},
|
|
155
154
|
|
|
156
|
-
|
|
157
|
-
modifications(parser) {
|
|
155
|
+
modifyDicomHeader(parser) {
|
|
158
156
|
const scan = parser.getFilePathComp('scan')
|
|
159
157
|
const centerSubjectId = parser.getFilePathComp('centerSubjectId')
|
|
160
158
|
|
|
161
159
|
return {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
ClinicalTrialSeriesDescription: scan,
|
|
174
|
-
},
|
|
175
|
-
|
|
176
|
-
// This defines the output folder structure.
|
|
177
|
-
outputFilePathComponents: [
|
|
178
|
-
parser.getFilePathComp('protocolNumber'),
|
|
179
|
-
parser.getFilePathComp('activityProvider'),
|
|
180
|
-
centerSubjectId,
|
|
181
|
-
parser.getFilePathComp('timepoint'),
|
|
182
|
-
parser.getFilePathComp('scan') +
|
|
183
|
-
'=' +
|
|
184
|
-
parser.getDicom('SeriesNumber'),
|
|
185
|
-
parser.getFilePathComp(parser.FILEBASENAME) + '.dcm',
|
|
186
|
-
],
|
|
160
|
+
// Align the PatientID DICOM header with the centerSubjectId folder name.
|
|
161
|
+
PatientID: centerSubjectId,
|
|
162
|
+
// This example maps PatientIDs based on the mapping CSV file.
|
|
163
|
+
// PatientID: parser.getMapping('blindedId'),
|
|
164
|
+
PatientName: centerSubjectId,
|
|
165
|
+
// Align the StudyDescription DICOM header with the timepoint folder name.
|
|
166
|
+
StudyDescription: parser.getFilePathComp('timepoint'),
|
|
167
|
+
// The party responsible for assigning a standard ClinicalTrialSeriesDescription
|
|
168
|
+
ClinicalTrialCoordinatingCenterName: hostProps.activityProviderName,
|
|
169
|
+
// Align the ClinicalTrialSeriesDescription DICOM header with the scan folder name.
|
|
170
|
+
ClinicalTrialSeriesDescription: scan,
|
|
187
171
|
}
|
|
188
172
|
},
|
|
189
173
|
|
|
174
|
+
outputFilePathComponents(parser) {
|
|
175
|
+
const scan = parser.getFilePathComp('scan')
|
|
176
|
+
const centerSubjectId = parser.getFilePathComp('centerSubjectId')
|
|
177
|
+
|
|
178
|
+
return [
|
|
179
|
+
parser.getFilePathComp('protocolNumber'),
|
|
180
|
+
parser.getFilePathComp('activityProvider'),
|
|
181
|
+
centerSubjectId,
|
|
182
|
+
parser.getFilePathComp('timepoint'),
|
|
183
|
+
scan + '=' + parser.getDicom('SeriesNumber'),
|
|
184
|
+
parser.getFilePathComp(parser.FILEBASENAME) + '.dcm',
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
|
|
190
188
|
// This section defines the validation rules for the input DICOMs.
|
|
191
189
|
// The processing continues on errors, but errors will have to be fixed
|
|
192
190
|
// or reviewed between the parties.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
errors: [
|
|
200
|
-
// File path
|
|
201
|
-
[
|
|
202
|
-
'Invalid study folder name',
|
|
203
|
-
parser.getFilePathComp('protocolNumber') !==
|
|
204
|
-
identifiers.protocolNumber,
|
|
205
|
-
],
|
|
206
|
-
// DICOM header
|
|
207
|
-
['Missing Modality', parser.missingDicom('Modality')],
|
|
191
|
+
errors(parser) {
|
|
192
|
+
return [
|
|
193
|
+
// File path
|
|
194
|
+
[
|
|
195
|
+
'Invalid study folder name',
|
|
196
|
+
parser.getFilePathComp('protocolNumber') !== hostProps.protocolNumber,
|
|
208
197
|
],
|
|
209
|
-
|
|
198
|
+
// DICOM header
|
|
199
|
+
['Missing Modality', parser.missingDicom('Modality')],
|
|
200
|
+
['Missing SOP Class UID', parser.missingDicom('SOPClassUID')],
|
|
201
|
+
]
|
|
210
202
|
},
|
|
211
203
|
}
|
|
212
204
|
}
|
|
@@ -36670,7 +36670,7 @@ function getParser(inputPathPattern, inputFilePath, naturalData, dicomPS315EOpti
|
|
|
36670
36670
|
}
|
|
36671
36671
|
|
|
36672
36672
|
// src/config/specVersion.ts
|
|
36673
|
-
var specVersion = "
|
|
36673
|
+
var specVersion = "3.0";
|
|
36674
36674
|
|
|
36675
36675
|
// src/collectMappings.ts
|
|
36676
36676
|
var import_lodash2 = __toESM(require_lodash(), 1);
|
|
@@ -36693,18 +36693,14 @@ function collectMappings(inputFilePath, inputFileIndex, dicomData, mappingOption
|
|
|
36693
36693
|
let finalSpec = {
|
|
36694
36694
|
dicomPS315EOptions: defaultPs315Options,
|
|
36695
36695
|
inputPathPattern: "",
|
|
36696
|
-
|
|
36697
|
-
|
|
36698
|
-
|
|
36699
|
-
|
|
36700
|
-
|
|
36701
|
-
|
|
36702
|
-
]
|
|
36703
|
-
};
|
|
36704
|
-
},
|
|
36705
|
-
validation: () => ({ errors: [] })
|
|
36696
|
+
modifyDicomHeader: () => ({}),
|
|
36697
|
+
outputFilePathComponents: (parser2) => [
|
|
36698
|
+
parser2.protectUid(parser2.getDicom("SeriesInstanceUID")),
|
|
36699
|
+
parser2.getFilePathComp(parser2.FILENAME)
|
|
36700
|
+
],
|
|
36701
|
+
errors: () => []
|
|
36706
36702
|
};
|
|
36707
|
-
const {
|
|
36703
|
+
const { modifyDicomHeader, errors, ...restSpec } = mappingOptions.curationSpec();
|
|
36708
36704
|
if (restSpec.version !== specVersion) {
|
|
36709
36705
|
throw new Error(
|
|
36710
36706
|
`Only version ${specVersion} supported in curationSpecification`
|
|
@@ -36712,10 +36708,10 @@ function collectMappings(inputFilePath, inputFileIndex, dicomData, mappingOption
|
|
|
36712
36708
|
}
|
|
36713
36709
|
Object.assign(finalSpec, restSpec);
|
|
36714
36710
|
if (!mappingOptions.skipModifications) {
|
|
36715
|
-
finalSpec.
|
|
36711
|
+
finalSpec.modifyDicomHeader = modifyDicomHeader;
|
|
36716
36712
|
}
|
|
36717
36713
|
if (!mappingOptions.skipValidation) {
|
|
36718
|
-
finalSpec.
|
|
36714
|
+
finalSpec.errors = errors;
|
|
36719
36715
|
}
|
|
36720
36716
|
const finalFilePath = finalSpec.dicomPS315EOptions === "Off" ? inputFilePath : inputFilePath.slice(0, inputFilePath.lastIndexOf("/") + 1) + `${String(inputFileIndex + 1).padStart(5, "0")}.dcm`;
|
|
36721
36717
|
const parser = getParser(
|
|
@@ -36726,8 +36722,7 @@ function collectMappings(inputFilePath, inputFileIndex, dicomData, mappingOption
|
|
|
36726
36722
|
mappingOptions.columnMappings,
|
|
36727
36723
|
finalSpec.additionalData
|
|
36728
36724
|
);
|
|
36729
|
-
|
|
36730
|
-
mapResults.errors = finalSpec.validation(parser).errors.filter(([, failure]) => failure).map(([message]) => message);
|
|
36725
|
+
mapResults.errors = finalSpec.errors(parser).filter(([, failure]) => failure).map(([message]) => message);
|
|
36731
36726
|
if (finalSpec.additionalData?.type === "listing") {
|
|
36732
36727
|
const { lookups, info, collect } = finalSpec.additionalData.collect(parser);
|
|
36733
36728
|
const collectByValue = collect.map((item) => {
|
|
@@ -36744,7 +36739,7 @@ function collectMappings(inputFilePath, inputFileIndex, dicomData, mappingOption
|
|
|
36744
36739
|
});
|
|
36745
36740
|
mapResults.listing = { info: cleanedInfo, collectByValue };
|
|
36746
36741
|
}
|
|
36747
|
-
mapResults.outputFilePath =
|
|
36742
|
+
mapResults.outputFilePath = finalSpec.outputFilePathComponents(parser).join("/");
|
|
36748
36743
|
if (finalSpec.dicomPS315EOptions !== "Off") {
|
|
36749
36744
|
deidentifyPS315E({
|
|
36750
36745
|
naturalData,
|
|
@@ -36754,7 +36749,7 @@ function collectMappings(inputFilePath, inputFileIndex, dicomData, mappingOption
|
|
|
36754
36749
|
originalDicomDict: dicomData.dict
|
|
36755
36750
|
});
|
|
36756
36751
|
}
|
|
36757
|
-
const dicomMap =
|
|
36752
|
+
const dicomMap = finalSpec.modifyDicomHeader(parser);
|
|
36758
36753
|
for (let attrPath in dicomMap) {
|
|
36759
36754
|
mapResults.mappings[attrPath] = [
|
|
36760
36755
|
(0, import_lodash2.get)(naturalData, attrPath),
|
|
@@ -30341,7 +30341,7 @@ function getParser(inputPathPattern, inputFilePath, naturalData, dicomPS315EOpti
|
|
|
30341
30341
|
}
|
|
30342
30342
|
|
|
30343
30343
|
// src/config/specVersion.ts
|
|
30344
|
-
var specVersion = "
|
|
30344
|
+
var specVersion = "3.0";
|
|
30345
30345
|
|
|
30346
30346
|
// src/collectMappings.ts
|
|
30347
30347
|
var import_lodash2 = __toESM(require_lodash(), 1);
|
|
@@ -30364,18 +30364,14 @@ function collectMappings(inputFilePath, inputFileIndex, dicomData, mappingOption
|
|
|
30364
30364
|
let finalSpec = {
|
|
30365
30365
|
dicomPS315EOptions: defaultPs315Options,
|
|
30366
30366
|
inputPathPattern: "",
|
|
30367
|
-
|
|
30368
|
-
|
|
30369
|
-
|
|
30370
|
-
|
|
30371
|
-
|
|
30372
|
-
|
|
30373
|
-
]
|
|
30374
|
-
};
|
|
30375
|
-
},
|
|
30376
|
-
validation: () => ({ errors: [] })
|
|
30367
|
+
modifyDicomHeader: () => ({}),
|
|
30368
|
+
outputFilePathComponents: (parser2) => [
|
|
30369
|
+
parser2.protectUid(parser2.getDicom("SeriesInstanceUID")),
|
|
30370
|
+
parser2.getFilePathComp(parser2.FILENAME)
|
|
30371
|
+
],
|
|
30372
|
+
errors: () => []
|
|
30377
30373
|
};
|
|
30378
|
-
const {
|
|
30374
|
+
const { modifyDicomHeader, errors, ...restSpec } = mappingOptions.curationSpec();
|
|
30379
30375
|
if (restSpec.version !== specVersion) {
|
|
30380
30376
|
throw new Error(
|
|
30381
30377
|
`Only version ${specVersion} supported in curationSpecification`
|
|
@@ -30383,10 +30379,10 @@ function collectMappings(inputFilePath, inputFileIndex, dicomData, mappingOption
|
|
|
30383
30379
|
}
|
|
30384
30380
|
Object.assign(finalSpec, restSpec);
|
|
30385
30381
|
if (!mappingOptions.skipModifications) {
|
|
30386
|
-
finalSpec.
|
|
30382
|
+
finalSpec.modifyDicomHeader = modifyDicomHeader;
|
|
30387
30383
|
}
|
|
30388
30384
|
if (!mappingOptions.skipValidation) {
|
|
30389
|
-
finalSpec.
|
|
30385
|
+
finalSpec.errors = errors;
|
|
30390
30386
|
}
|
|
30391
30387
|
const finalFilePath = finalSpec.dicomPS315EOptions === "Off" ? inputFilePath : inputFilePath.slice(0, inputFilePath.lastIndexOf("/") + 1) + `${String(inputFileIndex + 1).padStart(5, "0")}.dcm`;
|
|
30392
30388
|
const parser = getParser(
|
|
@@ -30397,8 +30393,7 @@ function collectMappings(inputFilePath, inputFileIndex, dicomData, mappingOption
|
|
|
30397
30393
|
mappingOptions.columnMappings,
|
|
30398
30394
|
finalSpec.additionalData
|
|
30399
30395
|
);
|
|
30400
|
-
|
|
30401
|
-
mapResults.errors = finalSpec.validation(parser).errors.filter(([, failure]) => failure).map(([message]) => message);
|
|
30396
|
+
mapResults.errors = finalSpec.errors(parser).filter(([, failure]) => failure).map(([message]) => message);
|
|
30402
30397
|
if (finalSpec.additionalData?.type === "listing") {
|
|
30403
30398
|
const { lookups, info, collect } = finalSpec.additionalData.collect(parser);
|
|
30404
30399
|
const collectByValue = collect.map((item) => {
|
|
@@ -30415,7 +30410,7 @@ function collectMappings(inputFilePath, inputFileIndex, dicomData, mappingOption
|
|
|
30415
30410
|
});
|
|
30416
30411
|
mapResults.listing = { info: cleanedInfo, collectByValue };
|
|
30417
30412
|
}
|
|
30418
|
-
mapResults.outputFilePath =
|
|
30413
|
+
mapResults.outputFilePath = finalSpec.outputFilePathComponents(parser).join("/");
|
|
30419
30414
|
if (finalSpec.dicomPS315EOptions !== "Off") {
|
|
30420
30415
|
deidentifyPS315E({
|
|
30421
30416
|
naturalData,
|
|
@@ -30425,7 +30420,7 @@ function collectMappings(inputFilePath, inputFileIndex, dicomData, mappingOption
|
|
|
30425
30420
|
originalDicomDict: dicomData.dict
|
|
30426
30421
|
});
|
|
30427
30422
|
}
|
|
30428
|
-
const dicomMap =
|
|
30423
|
+
const dicomMap = finalSpec.modifyDicomHeader(parser);
|
|
30429
30424
|
for (let attrPath in dicomMap) {
|
|
30430
30425
|
mapResults.mappings[attrPath] = [
|
|
30431
30426
|
(0, import_lodash2.get)(naturalData, attrPath),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/config/sample2PassCurationSpecification.ts
|
|
2
2
|
function sample2PassCurationSpecification() {
|
|
3
|
-
const
|
|
3
|
+
const hostProps = {
|
|
4
4
|
protocolNumber: "Sample_Protocol_Number",
|
|
5
5
|
activityProviderName: "Sample_CRO",
|
|
6
6
|
centerSubjectId: /^[A-Z]{2}\d{2}-\d{3}$/,
|
|
@@ -24,7 +24,7 @@ function sample2PassCurationSpecification() {
|
|
|
24
24
|
lookups,
|
|
25
25
|
info: [
|
|
26
26
|
// [label, value]
|
|
27
|
-
["Protocol Number",
|
|
27
|
+
["Protocol Number", hostProps.protocolNumber],
|
|
28
28
|
["Patient Name", parser.getDicom("PatientName")],
|
|
29
29
|
["Patient ID", parser.getDicom("PatientID")],
|
|
30
30
|
["Study Date", parser.getDicom("StudyDate")],
|
|
@@ -37,12 +37,12 @@ function sample2PassCurationSpecification() {
|
|
|
37
37
|
// PatNamePatId | PatNameIDSeriesDesc | CenterSubjectId | Timepoint | Comment
|
|
38
38
|
// [lookup header, format, lookup value]
|
|
39
39
|
collect: [
|
|
40
|
-
["CenterSubjectId",
|
|
40
|
+
["CenterSubjectId", hostProps.centerSubjectId, "PatNamePatId"],
|
|
41
41
|
// For now, collecting this on Series not Study level.
|
|
42
42
|
// Trade-off is re-use (study level) vs avoiding confusion due to
|
|
43
43
|
// any mismatch on study vs series level (e.g. 2 studies one visit)
|
|
44
|
-
["Timepoint",
|
|
45
|
-
["ScanName",
|
|
44
|
+
["Timepoint", hostProps.timepointNames, "PatNameIDSeriesDesc"],
|
|
45
|
+
["ScanName", hostProps.scanNames, "PatNameIDSeriesDesc"],
|
|
46
46
|
["Comment", /.*/, "PatNameIDSeriesDesc"]
|
|
47
47
|
]
|
|
48
48
|
};
|
|
@@ -67,18 +67,18 @@ function sample2PassCurationSpecification() {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
|
-
version: "
|
|
71
|
-
|
|
70
|
+
version: "3.0",
|
|
71
|
+
hostProps,
|
|
72
72
|
// This specifies the standardized DICOM de-identification
|
|
73
73
|
dicomPS315EOptions: {
|
|
74
74
|
cleanDescriptorsOption: true,
|
|
75
75
|
cleanDescriptorsExceptions: ["SeriesDescription"],
|
|
76
76
|
retainLongitudinalTemporalInformationOptions: "Full",
|
|
77
77
|
retainPatientCharacteristicsOption: [
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
78
|
+
"PatientWeight",
|
|
79
|
+
"PatientSize",
|
|
80
|
+
"PatientAge",
|
|
81
|
+
"PatientSex",
|
|
82
82
|
"SelectorASValue"
|
|
83
83
|
],
|
|
84
84
|
retainDeviceIdentityOption: true,
|
|
@@ -86,77 +86,70 @@ function sample2PassCurationSpecification() {
|
|
|
86
86
|
retainSafePrivateOption: "Quarantine",
|
|
87
87
|
retainInstitutionIdentityOption: true
|
|
88
88
|
},
|
|
89
|
-
|
|
90
|
-
modifications(parser) {
|
|
89
|
+
modifyDicomHeader(parser) {
|
|
91
90
|
const centerSubjectId = String(parser.getMapping("centerSubjectId"));
|
|
92
91
|
const timepoint = String(parser.getMapping("timepoint"));
|
|
93
92
|
const scanName = String(parser.getMapping("scanName"));
|
|
94
93
|
return {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
ClinicalTrialSeriesDescription: scanName
|
|
107
|
-
},
|
|
108
|
-
// This defines the output folder structure.
|
|
109
|
-
outputFilePathComponents: [
|
|
110
|
-
identifiers.protocolNumber,
|
|
111
|
-
centerSubjectId,
|
|
112
|
-
timepoint,
|
|
113
|
-
scanName + "=" + parser.getDicom("SeriesNumber"),
|
|
114
|
-
parser.getFilePathComp(parser.FILEBASENAME) + ".dcm"
|
|
115
|
-
]
|
|
94
|
+
// Align the PatientID DICOM header with the centerSubjectId folder name.
|
|
95
|
+
PatientID: centerSubjectId,
|
|
96
|
+
// This example maps PatientIDs based on the mapping CSV file.
|
|
97
|
+
// PatientID: parser.getMapping('blindedId'),
|
|
98
|
+
PatientName: centerSubjectId,
|
|
99
|
+
// Align the StudyDescription DICOM header with the timepoint folder name.
|
|
100
|
+
StudyDescription: timepoint,
|
|
101
|
+
// The party responsible for assigning a standard ClinicalTrialSeriesDescription
|
|
102
|
+
// ClinicalTrialCoordinatingCenterName: hostProps.activityProviderName,
|
|
103
|
+
// Align the ClinicalTrialSeriesDescription DICOM header with the scan folder name.
|
|
104
|
+
ClinicalTrialSeriesDescription: scanName
|
|
116
105
|
};
|
|
117
106
|
},
|
|
107
|
+
outputFilePathComponents(parser) {
|
|
108
|
+
const centerSubjectId = String(parser.getMapping("centerSubjectId"));
|
|
109
|
+
const timepoint = String(parser.getMapping("timepoint"));
|
|
110
|
+
const scanName = String(parser.getMapping("scanName"));
|
|
111
|
+
return [
|
|
112
|
+
hostProps.protocolNumber,
|
|
113
|
+
centerSubjectId,
|
|
114
|
+
timepoint,
|
|
115
|
+
scanName + "=" + parser.getDicom("SeriesNumber"),
|
|
116
|
+
parser.getFilePathComp(parser.FILEBASENAME) + ".dcm"
|
|
117
|
+
];
|
|
118
|
+
},
|
|
118
119
|
// This section defines the validation rules for the input DICOMs.
|
|
119
120
|
// The processing continues on errors, but errors will have to be fixed
|
|
120
121
|
// or reviewed between the parties.
|
|
121
|
-
|
|
122
|
+
errors(parser) {
|
|
122
123
|
const filename = parser.getFilePathComp(parser.FILENAME);
|
|
123
124
|
const seriesUid = parser.getDicom("SeriesInstanceUID");
|
|
124
125
|
const centerSubjectId = String(parser.getMapping("centerSubjectId"));
|
|
125
126
|
const timepoint = String(parser.getMapping("timepoint"));
|
|
126
127
|
const scanName = String(parser.getMapping("scanName"));
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
],
|
|
153
|
-
[
|
|
154
|
-
"Missing Study Instance UID",
|
|
155
|
-
parser.missingDicom("StudyInstanceUID")
|
|
156
|
-
],
|
|
157
|
-
["Missing SOP Instance UID", parser.missingDicom("SOPInstanceUID")]
|
|
158
|
-
]
|
|
159
|
-
};
|
|
128
|
+
return [
|
|
129
|
+
// File path
|
|
130
|
+
[
|
|
131
|
+
"Invalid site-subject format",
|
|
132
|
+
!centerSubjectId.match(hostProps.centerSubjectId)
|
|
133
|
+
],
|
|
134
|
+
[
|
|
135
|
+
"Invalid timepoint descriptor",
|
|
136
|
+
!hostProps.timepointNames.includes(timepoint)
|
|
137
|
+
],
|
|
138
|
+
["Invalid scan descriptor", !hostProps.scanNames.includes(scanName)],
|
|
139
|
+
// DICOM header
|
|
140
|
+
["Missing Modality", parser.missingDicom("Modality")],
|
|
141
|
+
["Missing SOP Class UID", parser.missingDicom("SOPClassUID")],
|
|
142
|
+
[
|
|
143
|
+
"Duplicate File Name(s) in series",
|
|
144
|
+
!parser.isUniqueInGroup(filename, seriesUid)
|
|
145
|
+
],
|
|
146
|
+
[
|
|
147
|
+
"Missing Series Instance UID",
|
|
148
|
+
parser.missingDicom("SeriesInstanceUID")
|
|
149
|
+
],
|
|
150
|
+
["Missing Study Instance UID", parser.missingDicom("StudyInstanceUID")],
|
|
151
|
+
["Missing SOP Instance UID", parser.missingDicom("SOPInstanceUID")]
|
|
152
|
+
];
|
|
160
153
|
}
|
|
161
154
|
};
|
|
162
155
|
}
|