Passed
Push — dev ( d312fb...03a61e )
by Salim
05:08
created

DicomUploadModel.getInstance   A

Complexity

Conditions 5

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 12
c 0
b 0
f 0
cc 5
rs 9.3333
1
/**
2
 Copyright (C) 2018-2020 KANOUN Salim
3
 This program is free software; you can redistribute it and/or modify
4
 it under the terms of the Affero GNU General Public v.3 License as published by
5
 the Free Software Foundation;
6
 This program is distributed in the hope that it will be useful,
7
 but WITHOUT ANY WARRANTY; without even the implied warranty of
8
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9
 Affero GNU General Public Public for more details.
10
 You should have received a copy of the Affero GNU General Public Public along
11
 with this program; if not, write to the Free Software Foundation, Inc.,
12
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
13
 */
14
15
class DicomUploadModel {
16
	constructor(config) {
17
		this.config = config;
18
19
		// Drop zone object
20
		this.dz = new DicomDropZone('#du-drop-zone');
0 ignored issues
show
Bug introduced by
The variable DicomDropZone seems to be never declared. If this is a global, consider adding a /** global: DicomDropZone */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
21
22
		// Files dropped in the drop zone
23
		this.loadedFiles = [];
24
25
		// Files waiting for processing (and decompressed zip content)
26
		this.queuedFiles = [];
27
28
		// Files that just have been successfully parsed
29
		this.parsedFiles = [];
30
31
		// Files that could not have been recognized as valid dicom file
32
		this.ignoredFiles = [];
33
34
		// ~
35
36
		// Studies list
37
		this.studies = [];
38
39
40
		// Studies which passed the checks successfully
41
		this.validStudies = [];
42
43
		// Studies which do not have critical warnings and
44
		// have both rejected series and valid series ready to upload
45
		this.incompleteStudies = [];
46
47
		// Studies which did not passed the checks
48
		this.rejectedStudies = [];
49
50
51
		// Files waiting for upload
52
		this.queuedStudies = [];
53
54
		// ~
55
56
		this.expectedVisits = [];
57
58
		// ~
59
	}
60
61
	getStudy(studyInstanceUID) {
62
		for (let st of this.studies) {
63
			if (st.studyInstanceUID == studyInstanceUID) {
64
				return st;
65
			}
66
		}
67
		return null;
68
	}
69
70
	getSerie(seriesInstanceUID) {
71
		for (let st of this.studies) {
72
			for (let sr of st.series) {
73
				if (sr.seriesInstanceUID == seriesInstanceUID) {
74
					return sr;
75
				}
76
			}
77
		}
78
		return null;
79
	}
80
81
	getInstance(SOPInstanceUID) {
82
		for (let st of this.studies) {
83
			for (let sr of st.series) {
84
				for (let inst of sr.instances) {
85
					if (inst.SOPInstanceUID == SOPInstanceUID) {
86
						return inst;
87
					}
88
				};
89
			};
90
		};
91
		return null;
92
	}
93
94
	isKnownStudy(dicomFile) {
95
		return this.getStudy(dicomFile.getStudyInstanceUID()) !== null;
96
	}
97
98
	isKnownSerie(dicomFile) {
99
		return this.getSerie(dicomFile.getSeriesInstanceUID()) !== null;
100
	}
101
102
	isKnownInstance(dicomFile) {
103
		return this.getInstance(dicomFile.getSOPInstanceUID()) !== null;
104
	}
105
106
	// ~
107
108
	queueStudy(study) {
109
		study.isQueued = true;
110
		this.put(study, 'queuedStudies');
111
	}
112
113
	dequeueStudy(study) {
114
		study.isQueued = false;
115
		this.remove(study, 'queuedStudies');
116
		study.dequeueAllSeries();
117
	}
118
119
120
	setStatusStudy(study, status) {
121
		study.status = status;
122
		this.remove(study, 'incompleteStudies');
123
		this.remove(study, 'rejectedStudies');
124
		this.remove(study, 'validStudies');
125
		switch (status) {
126
			case 'incomplete':
127
				this.put(study, 'incompleteStudies');
128
				break;
129
			case 'rejected':
130
				this.put(study, 'rejectedStudies');
131
				break;
132
			case 'valid':
133
				this.put(study, 'validStudies');
134
				break;
135
			default:
136
				//console.warn('Invalid Study State');
137
		}
138
	}
139
140
	hasQueuedStudies() {
141
		for (let st of this.queuedStudies) {
142
			if (st.hasQueuedSeries()) {
143
				return true;
144
			}
145
		}
146
		return false;
147
	}
148
149
	// ~
150
151
	/**
152
   * Delete specific element from a given array
153
   */
154
	remove(elmt, fromArrName) {
155
		const index = this[fromArrName].indexOf(elmt);
156
		if (index > -1) {
157
			this[fromArrName].splice(index, 1);
158
		}
159
	}
160
161
	/**
162
	 * Move specific element from a given array to another
163
	 */
164
	move(elmt, fromArrName, toArrName) {
165
		const index = this[fromArrName].indexOf(elmt);
166
		if (index > -1) {
167
			this[fromArrName].splice(index, 1);
168
			this[toArrName].push(elmt);
169
		}
170
	}
171
172
	/**
173
	 * Push an element to an array
174
	 */
175
	put(elmt, toArrName) {
176
		if (!this[toArrName].includes(elmt)) {
177
			this[toArrName].push(elmt);
178
		}
179
	}
180
181
	/**
182
	* Register the study, serie, instance of a dicom file if not known yet
183
	* @throws 'Secondary Capture Image Storage are not allowed.'
184
	* @throws 'Not expected visit.'
185
	* @throws 'DICOM file duplicates found'
186
	*/
187
	register(dicomFile) {
188
189
		// Check SOP Class UID is not Secondary Capture Image Storage
190
		if (dicomFile.isSecondaryCaptureImg()) {
191
			throw 'Secondary Capture Image Storage are not allowed.';
192
		}
193
194
		if(dicomFile.isDicomDir()){
195
			throw 'Dicomdir File, ignoring'
196
		}
197
198
		// Check if the study has already been registered (on client-side)
199
		if (!this.isKnownStudy(dicomFile)) {
200
			let st = new Study(
0 ignored issues
show
Bug introduced by
The variable Study seems to be never declared. If this is a global, consider adding a /** global: Study */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
201
				dicomFile.getStudyInstanceUID(),
202
				dicomFile.getStudyDate(),
203
				dicomFile.getStudyDescription(),
204
				dicomFile.getStudyID(),
205
				dicomFile.getAccessionNumber(),
206
				dicomFile.getAcquisitionDate(),
207
				dicomFile.getPatientID(),
208
				dicomFile.getPatientName(),
209
				dicomFile.getPatientBirthDate(),
210
				dicomFile.getPatientSex()
211
			);
212
213
			this.studies.push(st);
214
215
			// Check if the study is not already registered in the server
216
			if (!DAO.fetchIsNewStudy(this.config, st.getOrthancID(), (isNew) => {
0 ignored issues
show
Bug introduced by
The variable DAO seems to be never declared. If this is a global, consider adding a /** global: DAO */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
217
				if (isNew == 'false') {
218
					st.setWarning('isNotNewStudy', 'This study is already known by the server.');
219
				}
220
			}));
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
221
		}
222
223
		// Check if the series has already been registered
224
		if (!this.isKnownSerie(dicomFile)) {
225
			let study = this.getStudy(dicomFile.getStudyInstanceUID());
226
			study.series.push(new Serie(
0 ignored issues
show
Bug introduced by
The variable Serie seems to be never declared. If this is a global, consider adding a /** global: Serie */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
227
				dicomFile.getSeriesInstanceUID(),
228
				dicomFile.getSeriesNumber(),
229
				dicomFile.getSeriesDate(),
230
				dicomFile.getSeriesDescription(),
231
				dicomFile.getModality()
232
			));
233
		}
234
235
		// Check if the instance has already been registered
236
		if (!this.isKnownInstance(dicomFile)) {
237
			const inst = new Instance(
0 ignored issues
show
Bug introduced by
The variable Instance seems to be never declared. If this is a global, consider adding a /** global: Instance */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
238
				dicomFile.getSOPInstanceUID(),
239
				dicomFile.getInstanceNumber(),
240
				dicomFile
241
			);
242
243
			inst.dicomFile.anonymise();
244
245
			let serie = this.getSerie(dicomFile.getSeriesInstanceUID());
246
			serie.instances.push(inst);
247
248
		} else {
249
			throw 'Duplicates. This instance is already loaded.';
250
		}
251
	}
252
253
	checkStudies() {
254
		for (let st of this.studies) {
255
256
			// Check if the study corresponds to the visits in wait for series upload
257
			let expectedVisit = this.findExpectedVisit(st);
258
			if (expectedVisit === undefined) {
259
				st.setWarning('notExpectedVisit', 'You should check/select the patient. The imported study informations do not match with the expected ones.', true, false, true);
260
			} else {
261
				delete st.warnings['notExpectedVisit'];
262
				if (!this.config.multiImportMode) {
263
					st.visit = expectedVisit;
264
				}
265
			}
266
267
			// Check if visit ID is set
268
			if (st.visit == null || typeof st.visit.idVisit === undefined) {
0 ignored issues
show
Best Practice introduced by
Comparing st.visit to null using the == operator is not safe. Consider using === instead.
Loading history...
269
				st.setWarning('visitID', 'You should check/select the patient. Null visit ID.', false, true, false);
270
			} else {
271
				delete st.warnings['visitID'];
272
			}
273
274
			// Check inner series
275
			this.checkSeries(st);
276
277
			// Check if study has warnings
278
			if (st.hasWarnings()) {
279
				if (!st.hasCriticalWarnings() && st.hasValidSeries()) {
280
					this.setStatusStudy(st, 'incomplete');
281
				} else {
282
					this.setStatusStudy(st, 'rejected');
283
					this.dequeueStudy(st);
284
				}
285
			} else {
286
					this.setStatusStudy(st, 'valid');
287
			}
288
289
		}
290
	}
291
292
	checkSeries(st) {
293
		function isset(e) {
294
			return !(e == 'null' || e === undefined || e == '');
295
		}
296
297
		for (let sr of st.series) {
298
299
			let dicomFile = sr.instances[0].dicomFile;
300
301
			// Check missing tags
302
			if (!isset(dicomFile.getModality())) {
303
				sr.setWarning('missingTag00080060', 'Missing tag: Modality', true);
304
			} else {
305
				if (!isset(dicomFile.getDicomTag('00080021')) && !isset(dicomFile.getDicomTag('00080022')) ) {
306
					sr.setWarning('missingTag00080022', 'Missing tag: SeriesDate', true);
307
				}
308
				if (sr.modality == 'PT') {
309
					if ( !isset(dicomFile.getDicomTag('00101030')) ) {
310
						sr.setWarning('missingTag00101030', 'Missing tag: Patient Weight', true);
311
					}
312
					if ( !isset(dicomFile.getDicomTag('00080031'))  && !isset(dicomFile.getDicomTag('00080032')) ) {
313
						sr.setWarning('missingTag00101031', 'Missing tag: Series Time', true);
314
					}
315
					if ( !isset(dicomFile.getRadiopharmaceuticalTag('00181074')) ) {
316
						sr.setWarning('missingTag00181074', 'Missing tag: Radionuclide Total Dose', true);
317
					}
318
					if (!isset(dicomFile.getRadiopharmaceuticalTag('00181072')) && !isset(dicomFile.getRadiopharmaceuticalTag('00181078')) ) {
319
						sr.setWarning('missingTag00181072', 'Missing tag: Radiopharmaceutical Start Time', true);
320
					}
321
					if ( !isset(dicomFile.getRadiopharmaceuticalTag('00181075')) ) {
322
						sr.setWarning('missingTag00181075', 'Missing tag: Radionuclide Half Life', true);
323
					}
324
				}
325
			}
326
327
			// Check number of instances
328
			if(sr.getNbInstances() < this.config.minNbOfInstances) {
329
				sr.setWarning(`lessThan${this.config.minNbOfInstances}Instances`, `This serie contains less than ${this.config.minNbOfInstances} instances`, true, false);
330
			} else {
331
				delete sr.warnings[`lessThan${this.config.minNbOfInstances}Instances`];
332
			}
333
334
			if (sr.hasWarnings()) {
335
				st.setStatusSerie(sr, 'rejected');
336
				st.dequeueSerie(sr);
337
				st.setWarning('serie' + sr.seriesNumber, 'Invalid serie: #' + sr.seriesNumber + '.', false, false);
338
			} else {
339
				st.setStatusSerie(sr, 'valid');
340
				delete st.warnings['serie' + sr.seriesNumber];
341
			}
342
		}
343
	}
344
345
	findExpectedVisit(st) {
346
		let thisP = st.getPatientName();
347
348
		if (thisP.givenName === undefined) {
349
			return undefined;
350
		}
351
		if (thisP.familyName === undefined) {
352
			return undefined;
353
		}
354
355
		thisP.birthDate = st.getPatientBirthDate();
356
		thisP.sex = st.patientSex;
357
358
		if (thisP.birthDate === undefined || thisP.sex === undefined) {
359
			return undefined;
360
		}
361
362
		// Linear search through expected visits list
363
		for (let visit of this.expectedVisits) {
364
			if (visit.firstName.trim().toUpperCase().charAt(0) == thisP.givenName.trim().toUpperCase().charAt(0)
365
				&& visit.lastName.trim().toUpperCase().charAt(0) == thisP.familyName.trim().toUpperCase().charAt(0)
366
				&& visit.sex.trim().toUpperCase().charAt(0) == thisP.sex.trim().toUpperCase().charAt(0)
367
				&& Util.isProbablyEqualDates(visit.birthDate, thisP.birthDate)
0 ignored issues
show
Bug introduced by
The variable Util seems to be never declared. If this is a global, consider adding a /** global: Util */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
368
				) {
369
				return visit;
370
			}
371
		};
372
		return undefined;
373
374
	}
375
}