Mercurial > hg > orthanc-stone
comparison Resources/Orthanc/Core/DicomFormat/DicomMap.cpp @ 200:03afbee0cc7b
integration of Orthanc core into Stone
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 23 Mar 2018 11:04:03 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
199:dabe9982fca3 | 200:03afbee0cc7b |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2018 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #include "../PrecompiledHeaders.h" | |
35 #include "DicomMap.h" | |
36 | |
37 #include <stdio.h> | |
38 #include <memory> | |
39 | |
40 #include "../Endianness.h" | |
41 #include "../Logging.h" | |
42 #include "../OrthancException.h" | |
43 | |
44 | |
45 namespace Orthanc | |
46 { | |
47 static DicomTag patientTags[] = | |
48 { | |
49 //DicomTag(0x0010, 0x1010), // PatientAge | |
50 //DicomTag(0x0010, 0x1040) // PatientAddress | |
51 DicomTag(0x0010, 0x0010), // PatientName | |
52 DicomTag(0x0010, 0x0030), // PatientBirthDate | |
53 DicomTag(0x0010, 0x0040), // PatientSex | |
54 DicomTag(0x0010, 0x1000), // OtherPatientIDs | |
55 DICOM_TAG_PATIENT_ID | |
56 }; | |
57 | |
58 static DicomTag studyTags[] = | |
59 { | |
60 //DicomTag(0x0010, 0x1020), // PatientSize | |
61 //DicomTag(0x0010, 0x1030) // PatientWeight | |
62 DICOM_TAG_STUDY_DATE, | |
63 DicomTag(0x0008, 0x0030), // StudyTime | |
64 DicomTag(0x0020, 0x0010), // StudyID | |
65 DICOM_TAG_STUDY_DESCRIPTION, | |
66 DICOM_TAG_ACCESSION_NUMBER, | |
67 DICOM_TAG_STUDY_INSTANCE_UID, | |
68 DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION, // New in db v6 | |
69 DICOM_TAG_INSTITUTION_NAME, // New in db v6 | |
70 DICOM_TAG_REQUESTING_PHYSICIAN, // New in db v6 | |
71 DICOM_TAG_REFERRING_PHYSICIAN_NAME // New in db v6 | |
72 }; | |
73 | |
74 static DicomTag seriesTags[] = | |
75 { | |
76 //DicomTag(0x0010, 0x1080), // MilitaryRank | |
77 DicomTag(0x0008, 0x0021), // SeriesDate | |
78 DicomTag(0x0008, 0x0031), // SeriesTime | |
79 DICOM_TAG_MODALITY, | |
80 DicomTag(0x0008, 0x0070), // Manufacturer | |
81 DicomTag(0x0008, 0x1010), // StationName | |
82 DICOM_TAG_SERIES_DESCRIPTION, | |
83 DicomTag(0x0018, 0x0015), // BodyPartExamined | |
84 DicomTag(0x0018, 0x0024), // SequenceName | |
85 DicomTag(0x0018, 0x1030), // ProtocolName | |
86 DicomTag(0x0020, 0x0011), // SeriesNumber | |
87 DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES, | |
88 DICOM_TAG_IMAGES_IN_ACQUISITION, | |
89 DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, | |
90 DICOM_TAG_NUMBER_OF_SLICES, | |
91 DICOM_TAG_NUMBER_OF_TIME_SLICES, | |
92 DICOM_TAG_SERIES_INSTANCE_UID, | |
93 DICOM_TAG_IMAGE_ORIENTATION_PATIENT, // New in db v6 | |
94 DICOM_TAG_SERIES_TYPE, // New in db v6 | |
95 DICOM_TAG_OPERATOR_NAME, // New in db v6 | |
96 DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION, // New in db v6 | |
97 DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION, // New in db v6 | |
98 DICOM_TAG_CONTRAST_BOLUS_AGENT // New in db v6 | |
99 }; | |
100 | |
101 static DicomTag instanceTags[] = | |
102 { | |
103 DicomTag(0x0008, 0x0012), // InstanceCreationDate | |
104 DicomTag(0x0008, 0x0013), // InstanceCreationTime | |
105 DicomTag(0x0020, 0x0012), // AcquisitionNumber | |
106 DICOM_TAG_IMAGE_INDEX, | |
107 DICOM_TAG_INSTANCE_NUMBER, | |
108 DICOM_TAG_NUMBER_OF_FRAMES, | |
109 DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER, | |
110 DICOM_TAG_SOP_INSTANCE_UID, | |
111 DICOM_TAG_IMAGE_POSITION_PATIENT, // New in db v6 | |
112 DICOM_TAG_IMAGE_COMMENTS // New in db v6 | |
113 }; | |
114 | |
115 | |
116 void DicomMap::LoadMainDicomTags(const DicomTag*& tags, | |
117 size_t& size, | |
118 ResourceType level) | |
119 { | |
120 switch (level) | |
121 { | |
122 case ResourceType_Patient: | |
123 tags = patientTags; | |
124 size = sizeof(patientTags) / sizeof(DicomTag); | |
125 break; | |
126 | |
127 case ResourceType_Study: | |
128 tags = studyTags; | |
129 size = sizeof(studyTags) / sizeof(DicomTag); | |
130 break; | |
131 | |
132 case ResourceType_Series: | |
133 tags = seriesTags; | |
134 size = sizeof(seriesTags) / sizeof(DicomTag); | |
135 break; | |
136 | |
137 case ResourceType_Instance: | |
138 tags = instanceTags; | |
139 size = sizeof(instanceTags) / sizeof(DicomTag); | |
140 break; | |
141 | |
142 default: | |
143 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
144 } | |
145 } | |
146 | |
147 | |
148 void DicomMap::SetValue(uint16_t group, | |
149 uint16_t element, | |
150 DicomValue* value) | |
151 { | |
152 DicomTag tag(group, element); | |
153 Map::iterator it = map_.find(tag); | |
154 | |
155 if (it != map_.end()) | |
156 { | |
157 delete it->second; | |
158 it->second = value; | |
159 } | |
160 else | |
161 { | |
162 map_.insert(std::make_pair(tag, value)); | |
163 } | |
164 } | |
165 | |
166 void DicomMap::SetValue(DicomTag tag, | |
167 DicomValue* value) | |
168 { | |
169 SetValue(tag.GetGroup(), tag.GetElement(), value); | |
170 } | |
171 | |
172 | |
173 | |
174 | |
175 void DicomMap::Clear() | |
176 { | |
177 for (Map::iterator it = map_.begin(); it != map_.end(); ++it) | |
178 { | |
179 delete it->second; | |
180 } | |
181 | |
182 map_.clear(); | |
183 } | |
184 | |
185 | |
186 void DicomMap::ExtractTags(DicomMap& result, | |
187 const DicomTag* tags, | |
188 size_t count) const | |
189 { | |
190 result.Clear(); | |
191 | |
192 for (unsigned int i = 0; i < count; i++) | |
193 { | |
194 Map::const_iterator it = map_.find(tags[i]); | |
195 if (it != map_.end()) | |
196 { | |
197 result.SetValue(it->first, it->second->Clone()); | |
198 } | |
199 } | |
200 } | |
201 | |
202 | |
203 void DicomMap::ExtractPatientInformation(DicomMap& result) const | |
204 { | |
205 ExtractTags(result, patientTags, sizeof(patientTags) / sizeof(DicomTag)); | |
206 } | |
207 | |
208 void DicomMap::ExtractStudyInformation(DicomMap& result) const | |
209 { | |
210 ExtractTags(result, studyTags, sizeof(studyTags) / sizeof(DicomTag)); | |
211 } | |
212 | |
213 void DicomMap::ExtractSeriesInformation(DicomMap& result) const | |
214 { | |
215 ExtractTags(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag)); | |
216 } | |
217 | |
218 void DicomMap::ExtractInstanceInformation(DicomMap& result) const | |
219 { | |
220 ExtractTags(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag)); | |
221 } | |
222 | |
223 | |
224 | |
225 DicomMap* DicomMap::Clone() const | |
226 { | |
227 std::auto_ptr<DicomMap> result(new DicomMap); | |
228 | |
229 for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it) | |
230 { | |
231 result->map_.insert(std::make_pair(it->first, it->second->Clone())); | |
232 } | |
233 | |
234 return result.release(); | |
235 } | |
236 | |
237 | |
238 void DicomMap::Assign(const DicomMap& other) | |
239 { | |
240 Clear(); | |
241 | |
242 for (Map::const_iterator it = other.map_.begin(); it != other.map_.end(); ++it) | |
243 { | |
244 map_.insert(std::make_pair(it->first, it->second->Clone())); | |
245 } | |
246 } | |
247 | |
248 | |
249 const DicomValue& DicomMap::GetValue(const DicomTag& tag) const | |
250 { | |
251 const DicomValue* value = TestAndGetValue(tag); | |
252 | |
253 if (value) | |
254 { | |
255 return *value; | |
256 } | |
257 else | |
258 { | |
259 throw OrthancException(ErrorCode_InexistentTag); | |
260 } | |
261 } | |
262 | |
263 | |
264 const DicomValue* DicomMap::TestAndGetValue(const DicomTag& tag) const | |
265 { | |
266 Map::const_iterator it = map_.find(tag); | |
267 | |
268 if (it == map_.end()) | |
269 { | |
270 return NULL; | |
271 } | |
272 else | |
273 { | |
274 return it->second; | |
275 } | |
276 } | |
277 | |
278 | |
279 void DicomMap::Remove(const DicomTag& tag) | |
280 { | |
281 Map::iterator it = map_.find(tag); | |
282 if (it != map_.end()) | |
283 { | |
284 delete it->second; | |
285 map_.erase(it); | |
286 } | |
287 } | |
288 | |
289 | |
290 static void SetupFindTemplate(DicomMap& result, | |
291 const DicomTag* tags, | |
292 size_t count) | |
293 { | |
294 result.Clear(); | |
295 | |
296 for (size_t i = 0; i < count; i++) | |
297 { | |
298 result.SetValue(tags[i], "", false); | |
299 } | |
300 } | |
301 | |
302 void DicomMap::SetupFindPatientTemplate(DicomMap& result) | |
303 { | |
304 SetupFindTemplate(result, patientTags, sizeof(patientTags) / sizeof(DicomTag)); | |
305 } | |
306 | |
307 void DicomMap::SetupFindStudyTemplate(DicomMap& result) | |
308 { | |
309 SetupFindTemplate(result, studyTags, sizeof(studyTags) / sizeof(DicomTag)); | |
310 result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); | |
311 result.SetValue(DICOM_TAG_PATIENT_ID, "", false); | |
312 | |
313 // These main DICOM tags are only indirectly related to the | |
314 // General Study Module, remove them | |
315 result.Remove(DICOM_TAG_INSTITUTION_NAME); | |
316 result.Remove(DICOM_TAG_REQUESTING_PHYSICIAN); | |
317 result.Remove(DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION); | |
318 } | |
319 | |
320 void DicomMap::SetupFindSeriesTemplate(DicomMap& result) | |
321 { | |
322 SetupFindTemplate(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag)); | |
323 result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); | |
324 result.SetValue(DICOM_TAG_PATIENT_ID, "", false); | |
325 result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); | |
326 | |
327 // These tags are considered as "main" by Orthanc, but are not in the Series module | |
328 result.Remove(DicomTag(0x0008, 0x0070)); // Manufacturer | |
329 result.Remove(DicomTag(0x0008, 0x1010)); // Station name | |
330 result.Remove(DicomTag(0x0018, 0x0024)); // Sequence name | |
331 result.Remove(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES); | |
332 result.Remove(DICOM_TAG_IMAGES_IN_ACQUISITION); | |
333 result.Remove(DICOM_TAG_NUMBER_OF_SLICES); | |
334 result.Remove(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS); | |
335 result.Remove(DICOM_TAG_NUMBER_OF_TIME_SLICES); | |
336 result.Remove(DICOM_TAG_IMAGE_ORIENTATION_PATIENT); | |
337 result.Remove(DICOM_TAG_SERIES_TYPE); | |
338 result.Remove(DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION); | |
339 result.Remove(DICOM_TAG_CONTRAST_BOLUS_AGENT); | |
340 } | |
341 | |
342 void DicomMap::SetupFindInstanceTemplate(DicomMap& result) | |
343 { | |
344 SetupFindTemplate(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag)); | |
345 result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); | |
346 result.SetValue(DICOM_TAG_PATIENT_ID, "", false); | |
347 result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); | |
348 result.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "", false); | |
349 } | |
350 | |
351 | |
352 void DicomMap::CopyTagIfExists(const DicomMap& source, | |
353 const DicomTag& tag) | |
354 { | |
355 if (source.HasTag(tag)) | |
356 { | |
357 SetValue(tag, source.GetValue(tag)); | |
358 } | |
359 } | |
360 | |
361 | |
362 bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level) | |
363 { | |
364 DicomTag *tags = NULL; | |
365 size_t size; | |
366 | |
367 switch (level) | |
368 { | |
369 case ResourceType_Patient: | |
370 tags = patientTags; | |
371 size = sizeof(patientTags) / sizeof(DicomTag); | |
372 break; | |
373 | |
374 case ResourceType_Study: | |
375 tags = studyTags; | |
376 size = sizeof(studyTags) / sizeof(DicomTag); | |
377 break; | |
378 | |
379 case ResourceType_Series: | |
380 tags = seriesTags; | |
381 size = sizeof(seriesTags) / sizeof(DicomTag); | |
382 break; | |
383 | |
384 case ResourceType_Instance: | |
385 tags = instanceTags; | |
386 size = sizeof(instanceTags) / sizeof(DicomTag); | |
387 break; | |
388 | |
389 default: | |
390 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
391 } | |
392 | |
393 for (size_t i = 0; i < size; i++) | |
394 { | |
395 if (tags[i] == tag) | |
396 { | |
397 return true; | |
398 } | |
399 } | |
400 | |
401 return false; | |
402 } | |
403 | |
404 bool DicomMap::IsMainDicomTag(const DicomTag& tag) | |
405 { | |
406 return (IsMainDicomTag(tag, ResourceType_Patient) || | |
407 IsMainDicomTag(tag, ResourceType_Study) || | |
408 IsMainDicomTag(tag, ResourceType_Series) || | |
409 IsMainDicomTag(tag, ResourceType_Instance)); | |
410 } | |
411 | |
412 | |
413 void DicomMap::GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level) | |
414 { | |
415 DicomTag *tags = NULL; | |
416 size_t size; | |
417 | |
418 switch (level) | |
419 { | |
420 case ResourceType_Patient: | |
421 tags = patientTags; | |
422 size = sizeof(patientTags) / sizeof(DicomTag); | |
423 break; | |
424 | |
425 case ResourceType_Study: | |
426 tags = studyTags; | |
427 size = sizeof(studyTags) / sizeof(DicomTag); | |
428 break; | |
429 | |
430 case ResourceType_Series: | |
431 tags = seriesTags; | |
432 size = sizeof(seriesTags) / sizeof(DicomTag); | |
433 break; | |
434 | |
435 case ResourceType_Instance: | |
436 tags = instanceTags; | |
437 size = sizeof(instanceTags) / sizeof(DicomTag); | |
438 break; | |
439 | |
440 default: | |
441 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
442 } | |
443 | |
444 for (size_t i = 0; i < size; i++) | |
445 { | |
446 result.insert(tags[i]); | |
447 } | |
448 } | |
449 | |
450 | |
451 void DicomMap::GetMainDicomTags(std::set<DicomTag>& result, ResourceType level) | |
452 { | |
453 result.clear(); | |
454 GetMainDicomTagsInternal(result, level); | |
455 } | |
456 | |
457 | |
458 void DicomMap::GetMainDicomTags(std::set<DicomTag>& result) | |
459 { | |
460 result.clear(); | |
461 GetMainDicomTagsInternal(result, ResourceType_Patient); | |
462 GetMainDicomTagsInternal(result, ResourceType_Study); | |
463 GetMainDicomTagsInternal(result, ResourceType_Series); | |
464 GetMainDicomTagsInternal(result, ResourceType_Instance); | |
465 } | |
466 | |
467 | |
468 void DicomMap::GetTags(std::set<DicomTag>& tags) const | |
469 { | |
470 tags.clear(); | |
471 | |
472 for (Map::const_iterator it = map_.begin(); | |
473 it != map_.end(); ++it) | |
474 { | |
475 tags.insert(it->first); | |
476 } | |
477 } | |
478 | |
479 | |
480 static uint16_t ReadUnsignedInteger16(const char* dicom) | |
481 { | |
482 return le16toh(*reinterpret_cast<const uint16_t*>(dicom)); | |
483 } | |
484 | |
485 | |
486 static uint32_t ReadUnsignedInteger32(const char* dicom) | |
487 { | |
488 return le32toh(*reinterpret_cast<const uint32_t*>(dicom)); | |
489 } | |
490 | |
491 | |
492 static bool ValidateTag(const ValueRepresentation& vr, | |
493 const std::string& value) | |
494 { | |
495 switch (vr) | |
496 { | |
497 case ValueRepresentation_ApplicationEntity: | |
498 return value.size() <= 16; | |
499 | |
500 case ValueRepresentation_AgeString: | |
501 return (value.size() == 4 && | |
502 isdigit(value[0]) && | |
503 isdigit(value[1]) && | |
504 isdigit(value[2]) && | |
505 (value[3] == 'D' || value[3] == 'W' || value[3] == 'M' || value[3] == 'Y')); | |
506 | |
507 case ValueRepresentation_AttributeTag: | |
508 return value.size() == 4; | |
509 | |
510 case ValueRepresentation_CodeString: | |
511 return value.size() <= 16; | |
512 | |
513 case ValueRepresentation_Date: | |
514 return value.size() <= 18; | |
515 | |
516 case ValueRepresentation_DecimalString: | |
517 return value.size() <= 16; | |
518 | |
519 case ValueRepresentation_DateTime: | |
520 return value.size() <= 54; | |
521 | |
522 case ValueRepresentation_FloatingPointSingle: | |
523 return value.size() == 4; | |
524 | |
525 case ValueRepresentation_FloatingPointDouble: | |
526 return value.size() == 8; | |
527 | |
528 case ValueRepresentation_IntegerString: | |
529 return value.size() <= 12; | |
530 | |
531 case ValueRepresentation_LongString: | |
532 return value.size() <= 64; | |
533 | |
534 case ValueRepresentation_LongText: | |
535 return value.size() <= 10240; | |
536 | |
537 case ValueRepresentation_OtherByte: | |
538 return true; | |
539 | |
540 case ValueRepresentation_OtherDouble: | |
541 return value.size() <= (static_cast<uint64_t>(1) << 32) - 8; | |
542 | |
543 case ValueRepresentation_OtherFloat: | |
544 return value.size() <= (static_cast<uint64_t>(1) << 32) - 4; | |
545 | |
546 case ValueRepresentation_OtherLong: | |
547 return true; | |
548 | |
549 case ValueRepresentation_OtherWord: | |
550 return true; | |
551 | |
552 case ValueRepresentation_PersonName: | |
553 return true; | |
554 | |
555 case ValueRepresentation_ShortString: | |
556 return value.size() <= 16; | |
557 | |
558 case ValueRepresentation_SignedLong: | |
559 return value.size() == 4; | |
560 | |
561 case ValueRepresentation_Sequence: | |
562 return true; | |
563 | |
564 case ValueRepresentation_SignedShort: | |
565 return value.size() == 2; | |
566 | |
567 case ValueRepresentation_ShortText: | |
568 return value.size() <= 1024; | |
569 | |
570 case ValueRepresentation_Time: | |
571 return value.size() <= 28; | |
572 | |
573 case ValueRepresentation_UnlimitedCharacters: | |
574 return value.size() <= (static_cast<uint64_t>(1) << 32) - 2; | |
575 | |
576 case ValueRepresentation_UniqueIdentifier: | |
577 return value.size() <= 64; | |
578 | |
579 case ValueRepresentation_UnsignedLong: | |
580 return value.size() == 4; | |
581 | |
582 case ValueRepresentation_Unknown: | |
583 return true; | |
584 | |
585 case ValueRepresentation_UniversalResource: | |
586 return value.size() <= (static_cast<uint64_t>(1) << 32) - 2; | |
587 | |
588 case ValueRepresentation_UnsignedShort: | |
589 return value.size() == 2; | |
590 | |
591 case ValueRepresentation_UnlimitedText: | |
592 return value.size() <= (static_cast<uint64_t>(1) << 32) - 2; | |
593 | |
594 default: | |
595 // Assume unsupported tags are OK | |
596 return true; | |
597 } | |
598 } | |
599 | |
600 | |
601 static void RemoveTagPadding(std::string& value, | |
602 const ValueRepresentation& vr) | |
603 { | |
604 /** | |
605 * Remove padding from character strings, if need be. For the time | |
606 * being, only the UI VR is supported. | |
607 * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html | |
608 **/ | |
609 | |
610 switch (vr) | |
611 { | |
612 case ValueRepresentation_UniqueIdentifier: | |
613 { | |
614 /** | |
615 * "Values with a VR of UI shall be padded with a single | |
616 * trailing NULL (00H) character when necessary to achieve even | |
617 * length." | |
618 **/ | |
619 | |
620 if (!value.empty() && | |
621 value[value.size() - 1] == '\0') | |
622 { | |
623 value.resize(value.size() - 1); | |
624 } | |
625 | |
626 break; | |
627 } | |
628 | |
629 /** | |
630 * TODO implement other VR | |
631 **/ | |
632 | |
633 default: | |
634 // No padding is applicable to this VR | |
635 break; | |
636 } | |
637 } | |
638 | |
639 | |
640 static bool ReadNextTag(DicomTag& tag, | |
641 ValueRepresentation& vr, | |
642 std::string& value, | |
643 const char* dicom, | |
644 size_t size, | |
645 size_t& position) | |
646 { | |
647 /** | |
648 * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html#sect_7.1.2 | |
649 * This function reads a data element with Explicit VR encoded using Little-Endian. | |
650 **/ | |
651 | |
652 if (position + 6 > size) | |
653 { | |
654 return false; | |
655 } | |
656 | |
657 tag = DicomTag(ReadUnsignedInteger16(dicom + position), | |
658 ReadUnsignedInteger16(dicom + position + 2)); | |
659 | |
660 vr = StringToValueRepresentation(std::string(dicom + position + 4, 2), true); | |
661 if (vr == ValueRepresentation_NotSupported) | |
662 { | |
663 return false; | |
664 } | |
665 | |
666 if (vr == ValueRepresentation_OtherByte || | |
667 vr == ValueRepresentation_OtherDouble || | |
668 vr == ValueRepresentation_OtherFloat || | |
669 vr == ValueRepresentation_OtherLong || | |
670 vr == ValueRepresentation_OtherWord || | |
671 vr == ValueRepresentation_Sequence || | |
672 vr == ValueRepresentation_UnlimitedCharacters || | |
673 vr == ValueRepresentation_UniversalResource || | |
674 vr == ValueRepresentation_UnlimitedText || | |
675 vr == ValueRepresentation_Unknown) // Note that "UN" should never appear in the Meta Information | |
676 { | |
677 if (position + 12 > size) | |
678 { | |
679 return false; | |
680 } | |
681 | |
682 uint32_t length = ReadUnsignedInteger32(dicom + position + 8); | |
683 | |
684 if (position + 12 + length > size) | |
685 { | |
686 return false; | |
687 } | |
688 | |
689 value.assign(dicom + position + 12, length); | |
690 position += (12 + length); | |
691 } | |
692 else | |
693 { | |
694 if (position + 8 > size) | |
695 { | |
696 return false; | |
697 } | |
698 | |
699 uint16_t length = ReadUnsignedInteger16(dicom + position + 6); | |
700 | |
701 if (position + 8 + length > size) | |
702 { | |
703 return false; | |
704 } | |
705 | |
706 value.assign(dicom + position + 8, length); | |
707 position += (8 + length); | |
708 } | |
709 | |
710 if (!ValidateTag(vr, value)) | |
711 { | |
712 return false; | |
713 } | |
714 | |
715 RemoveTagPadding(value, vr); | |
716 | |
717 return true; | |
718 } | |
719 | |
720 | |
721 bool DicomMap::ParseDicomMetaInformation(DicomMap& result, | |
722 const char* dicom, | |
723 size_t size) | |
724 { | |
725 /** | |
726 * http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html | |
727 * According to Table 7.1-1, besides the "DICM" DICOM prefix, the | |
728 * file preamble (i.e. dicom[0..127]) should not be taken into | |
729 * account to determine whether the file is or is not a DICOM file. | |
730 **/ | |
731 | |
732 if (size < 132 || | |
733 dicom[128] != 'D' || | |
734 dicom[129] != 'I' || | |
735 dicom[130] != 'C' || | |
736 dicom[131] != 'M') | |
737 { | |
738 return false; | |
739 } | |
740 | |
741 | |
742 /** | |
743 * The DICOM File Meta Information must be encoded using the | |
744 * Explicit VR Little Endian Transfer Syntax | |
745 * (UID=1.2.840.10008.1.2.1). | |
746 **/ | |
747 | |
748 result.Clear(); | |
749 | |
750 // First, we read the "File Meta Information Group Length" tag | |
751 // (0002,0000) to know where to stop reading the meta header | |
752 size_t position = 132; | |
753 | |
754 DicomTag tag(0x0000, 0x0000); // Dummy initialization | |
755 ValueRepresentation vr; | |
756 std::string value; | |
757 if (!ReadNextTag(tag, vr, value, dicom, size, position) || | |
758 tag.GetGroup() != 0x0002 || | |
759 tag.GetElement() != 0x0000 || | |
760 vr != ValueRepresentation_UnsignedLong || | |
761 value.size() != 4) | |
762 { | |
763 return false; | |
764 } | |
765 | |
766 size_t stopPosition = position + ReadUnsignedInteger32(value.c_str()); | |
767 if (stopPosition > size) | |
768 { | |
769 return false; | |
770 } | |
771 | |
772 while (position < stopPosition) | |
773 { | |
774 if (ReadNextTag(tag, vr, value, dicom, size, position)) | |
775 { | |
776 result.SetValue(tag, value, IsBinaryValueRepresentation(vr)); | |
777 } | |
778 else | |
779 { | |
780 return false; | |
781 } | |
782 } | |
783 | |
784 return true; | |
785 } | |
786 | |
787 | |
788 static std::string ValueAsString(const DicomMap& summary, | |
789 const DicomTag& tag) | |
790 { | |
791 const DicomValue& value = summary.GetValue(tag); | |
792 if (value.IsNull()) | |
793 { | |
794 return "(null)"; | |
795 } | |
796 else | |
797 { | |
798 return value.GetContent(); | |
799 } | |
800 } | |
801 | |
802 | |
803 void DicomMap::LogMissingTagsForStore() const | |
804 { | |
805 std::string s, t; | |
806 | |
807 if (HasTag(DICOM_TAG_PATIENT_ID)) | |
808 { | |
809 if (t.size() > 0) | |
810 t += ", "; | |
811 t += "PatientID=" + ValueAsString(*this, DICOM_TAG_PATIENT_ID); | |
812 } | |
813 else | |
814 { | |
815 if (s.size() > 0) | |
816 s += ", "; | |
817 s += "PatientID"; | |
818 } | |
819 | |
820 if (HasTag(DICOM_TAG_STUDY_INSTANCE_UID)) | |
821 { | |
822 if (t.size() > 0) | |
823 t += ", "; | |
824 t += "StudyInstanceUID=" + ValueAsString(*this, DICOM_TAG_STUDY_INSTANCE_UID); | |
825 } | |
826 else | |
827 { | |
828 if (s.size() > 0) | |
829 s += ", "; | |
830 s += "StudyInstanceUID"; | |
831 } | |
832 | |
833 if (HasTag(DICOM_TAG_SERIES_INSTANCE_UID)) | |
834 { | |
835 if (t.size() > 0) | |
836 t += ", "; | |
837 t += "SeriesInstanceUID=" + ValueAsString(*this, DICOM_TAG_SERIES_INSTANCE_UID); | |
838 } | |
839 else | |
840 { | |
841 if (s.size() > 0) | |
842 s += ", "; | |
843 s += "SeriesInstanceUID"; | |
844 } | |
845 | |
846 if (HasTag(DICOM_TAG_SOP_INSTANCE_UID)) | |
847 { | |
848 if (t.size() > 0) | |
849 t += ", "; | |
850 t += "SOPInstanceUID=" + ValueAsString(*this, DICOM_TAG_SOP_INSTANCE_UID); | |
851 } | |
852 else | |
853 { | |
854 if (s.size() > 0) | |
855 s += ", "; | |
856 s += "SOPInstanceUID"; | |
857 } | |
858 | |
859 if (t.size() == 0) | |
860 { | |
861 LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)"; | |
862 } | |
863 else | |
864 { | |
865 LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t; | |
866 } | |
867 } | |
868 | |
869 | |
870 bool DicomMap::CopyToString(std::string& result, | |
871 const DicomTag& tag, | |
872 bool allowBinary) const | |
873 { | |
874 const DicomValue* value = TestAndGetValue(tag); | |
875 | |
876 if (value == NULL) | |
877 { | |
878 return false; | |
879 } | |
880 else | |
881 { | |
882 return value->CopyToString(result, allowBinary); | |
883 } | |
884 } | |
885 | |
886 bool DicomMap::ParseInteger32(int32_t& result, | |
887 const DicomTag& tag) const | |
888 { | |
889 const DicomValue* value = TestAndGetValue(tag); | |
890 | |
891 if (value == NULL) | |
892 { | |
893 return false; | |
894 } | |
895 else | |
896 { | |
897 return value->ParseInteger32(result); | |
898 } | |
899 } | |
900 | |
901 bool DicomMap::ParseInteger64(int64_t& result, | |
902 const DicomTag& tag) const | |
903 { | |
904 const DicomValue* value = TestAndGetValue(tag); | |
905 | |
906 if (value == NULL) | |
907 { | |
908 return false; | |
909 } | |
910 else | |
911 { | |
912 return value->ParseInteger64(result); | |
913 } | |
914 } | |
915 | |
916 bool DicomMap::ParseUnsignedInteger32(uint32_t& result, | |
917 const DicomTag& tag) const | |
918 { | |
919 const DicomValue* value = TestAndGetValue(tag); | |
920 | |
921 if (value == NULL) | |
922 { | |
923 return false; | |
924 } | |
925 else | |
926 { | |
927 return value->ParseUnsignedInteger32(result); | |
928 } | |
929 } | |
930 | |
931 bool DicomMap::ParseUnsignedInteger64(uint64_t& result, | |
932 const DicomTag& tag) const | |
933 { | |
934 const DicomValue* value = TestAndGetValue(tag); | |
935 | |
936 if (value == NULL) | |
937 { | |
938 return false; | |
939 } | |
940 else | |
941 { | |
942 return value->ParseUnsignedInteger64(result); | |
943 } | |
944 } | |
945 | |
946 bool DicomMap::ParseFloat(float& result, | |
947 const DicomTag& tag) const | |
948 { | |
949 const DicomValue* value = TestAndGetValue(tag); | |
950 | |
951 if (value == NULL) | |
952 { | |
953 return false; | |
954 } | |
955 else | |
956 { | |
957 return value->ParseFloat(result); | |
958 } | |
959 } | |
960 | |
961 bool DicomMap::ParseDouble(double& result, | |
962 const DicomTag& tag) const | |
963 { | |
964 const DicomValue* value = TestAndGetValue(tag); | |
965 | |
966 if (value == NULL) | |
967 { | |
968 return false; | |
969 } | |
970 else | |
971 { | |
972 return value->ParseDouble(result); | |
973 } | |
974 } | |
975 } |