comparison OrthancFramework/Sources/DicomFormat/DicomMap.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Core/DicomFormat/DicomMap.cpp@e7003b2203a7
children bf7b9edf6b81
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
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-2020 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 "../Compatibility.h"
41 #include "../Endianness.h"
42 #include "../Logging.h"
43 #include "../OrthancException.h"
44 #include "../Toolbox.h"
45 #include "DicomArray.h"
46
47
48 namespace Orthanc
49 {
50 namespace
51 {
52 struct MainDicomTag
53 {
54 const DicomTag tag_;
55 const char* name_;
56 };
57 }
58
59 static const MainDicomTag PATIENT_MAIN_DICOM_TAGS[] =
60 {
61 // { DicomTag(0x0010, 0x1010), "PatientAge" },
62 // { DicomTag(0x0010, 0x1040), "PatientAddress" },
63 { DicomTag(0x0010, 0x0010), "PatientName" },
64 { DicomTag(0x0010, 0x0030), "PatientBirthDate" },
65 { DicomTag(0x0010, 0x0040), "PatientSex" },
66 { DicomTag(0x0010, 0x1000), "OtherPatientIDs" },
67 { DICOM_TAG_PATIENT_ID, "PatientID" }
68 };
69
70 static const MainDicomTag STUDY_MAIN_DICOM_TAGS[] =
71 {
72 // { DicomTag(0x0010, 0x1020), "PatientSize" },
73 // { DicomTag(0x0010, 0x1030), "PatientWeight" },
74 { DICOM_TAG_STUDY_DATE, "StudyDate" },
75 { DicomTag(0x0008, 0x0030), "StudyTime" },
76 { DicomTag(0x0020, 0x0010), "StudyID" },
77 { DICOM_TAG_STUDY_DESCRIPTION, "StudyDescription" },
78 { DICOM_TAG_ACCESSION_NUMBER, "AccessionNumber" },
79 { DICOM_TAG_STUDY_INSTANCE_UID, "StudyInstanceUID" },
80
81 // New in db v6
82 { DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION, "RequestedProcedureDescription" },
83 { DICOM_TAG_INSTITUTION_NAME, "InstitutionName" },
84 { DICOM_TAG_REQUESTING_PHYSICIAN, "RequestingPhysician" },
85 { DICOM_TAG_REFERRING_PHYSICIAN_NAME, "ReferringPhysicianName" }
86 };
87
88 static const MainDicomTag SERIES_MAIN_DICOM_TAGS[] =
89 {
90 // { DicomTag(0x0010, 0x1080), "MilitaryRank" },
91 { DicomTag(0x0008, 0x0021), "SeriesDate" },
92 { DicomTag(0x0008, 0x0031), "SeriesTime" },
93 { DICOM_TAG_MODALITY, "Modality" },
94 { DicomTag(0x0008, 0x0070), "Manufacturer" },
95 { DicomTag(0x0008, 0x1010), "StationName" },
96 { DICOM_TAG_SERIES_DESCRIPTION, "SeriesDescription" },
97 { DicomTag(0x0018, 0x0015), "BodyPartExamined" },
98 { DicomTag(0x0018, 0x0024), "SequenceName" },
99 { DicomTag(0x0018, 0x1030), "ProtocolName" },
100 { DicomTag(0x0020, 0x0011), "SeriesNumber" },
101 { DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES, "CardiacNumberOfImages" },
102 { DICOM_TAG_IMAGES_IN_ACQUISITION, "ImagesInAcquisition" },
103 { DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, "NumberOfTemporalPositions" },
104 { DICOM_TAG_NUMBER_OF_SLICES, "NumberOfSlices" },
105 { DICOM_TAG_NUMBER_OF_TIME_SLICES, "NumberOfTimeSlices" },
106 { DICOM_TAG_SERIES_INSTANCE_UID, "SeriesInstanceUID" },
107
108 // New in db v6
109 { DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "ImageOrientationPatient" },
110 { DICOM_TAG_SERIES_TYPE, "SeriesType" },
111 { DICOM_TAG_OPERATOR_NAME, "OperatorsName" },
112 { DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION, "PerformedProcedureStepDescription" },
113 { DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION, "AcquisitionDeviceProcessingDescription" },
114 { DICOM_TAG_CONTRAST_BOLUS_AGENT, "ContrastBolusAgent" }
115 };
116
117 static const MainDicomTag INSTANCE_MAIN_DICOM_TAGS[] =
118 {
119 { DicomTag(0x0008, 0x0012), "InstanceCreationDate" },
120 { DicomTag(0x0008, 0x0013), "InstanceCreationTime" },
121 { DicomTag(0x0020, 0x0012), "AcquisitionNumber" },
122 { DICOM_TAG_IMAGE_INDEX, "ImageIndex" },
123 { DICOM_TAG_INSTANCE_NUMBER, "InstanceNumber" },
124 { DICOM_TAG_NUMBER_OF_FRAMES, "NumberOfFrames" },
125 { DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER, "TemporalPositionIdentifier" },
126 { DICOM_TAG_SOP_INSTANCE_UID, "SOPInstanceUID" },
127
128 // New in db v6
129 { DICOM_TAG_IMAGE_POSITION_PATIENT, "ImagePositionPatient" },
130 { DICOM_TAG_IMAGE_COMMENTS, "ImageComments" },
131
132 /**
133 * Main DICOM tags that are not part of any release of the
134 * database schema yet, and that will be part of future db v7. In
135 * the meantime, the user must call "/tools/reconstruct" once to
136 * access these tags if the corresponding DICOM files where
137 * indexed in the database by an older version of Orthanc.
138 **/
139 { DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "ImageOrientationPatient" } // New in Orthanc 1.4.2
140 };
141
142
143 static void LoadMainDicomTags(const MainDicomTag*& tags,
144 size_t& size,
145 ResourceType level)
146 {
147 switch (level)
148 {
149 case ResourceType_Patient:
150 tags = PATIENT_MAIN_DICOM_TAGS;
151 size = sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag);
152 break;
153
154 case ResourceType_Study:
155 tags = STUDY_MAIN_DICOM_TAGS;
156 size = sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag);
157 break;
158
159 case ResourceType_Series:
160 tags = SERIES_MAIN_DICOM_TAGS;
161 size = sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag);
162 break;
163
164 case ResourceType_Instance:
165 tags = INSTANCE_MAIN_DICOM_TAGS;
166 size = sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag);
167 break;
168
169 default:
170 throw OrthancException(ErrorCode_ParameterOutOfRange);
171 }
172 }
173
174
175 static void LoadMainDicomTags(std::map<DicomTag, std::string>& target,
176 ResourceType level)
177 {
178 const MainDicomTag* tags = NULL;
179 size_t size;
180 LoadMainDicomTags(tags, size, level);
181
182 assert(tags != NULL &&
183 size != 0);
184
185 for (size_t i = 0; i < size; i++)
186 {
187 assert(target.find(tags[i].tag_) == target.end());
188
189 target[tags[i].tag_] = tags[i].name_;
190 }
191 }
192
193
194 namespace
195 {
196 class DicomTag2 : public DicomTag
197 {
198 public:
199 DicomTag2() :
200 DicomTag(0, 0) // To make std::map<> happy
201 {
202 }
203
204 DicomTag2(const DicomTag& tag) :
205 DicomTag(tag)
206 {
207 }
208 };
209 }
210
211
212 static void LoadMainDicomTags(std::map<std::string, DicomTag2>& target,
213 ResourceType level)
214 {
215 const MainDicomTag* tags = NULL;
216 size_t size;
217 LoadMainDicomTags(tags, size, level);
218
219 assert(tags != NULL &&
220 size != 0);
221
222 for (size_t i = 0; i < size; i++)
223 {
224 assert(target.find(tags[i].name_) == target.end());
225
226 target[tags[i].name_] = tags[i].tag_;
227 }
228 }
229
230
231 void DicomMap::SetValueInternal(uint16_t group,
232 uint16_t element,
233 DicomValue* value)
234 {
235 DicomTag tag(group, element);
236 Content::iterator it = content_.find(tag);
237
238 if (it != content_.end())
239 {
240 delete it->second;
241 it->second = value;
242 }
243 else
244 {
245 content_.insert(std::make_pair(tag, value));
246 }
247 }
248
249
250 void DicomMap::Clear()
251 {
252 for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
253 {
254 assert(it->second != NULL);
255 delete it->second;
256 }
257
258 content_.clear();
259 }
260
261
262 static void ExtractTags(DicomMap& result,
263 const DicomMap::Content& source,
264 const MainDicomTag* tags,
265 size_t count)
266 {
267 result.Clear();
268
269 for (unsigned int i = 0; i < count; i++)
270 {
271 DicomMap::Content::const_iterator it = source.find(tags[i].tag_);
272 if (it != source.end())
273 {
274 result.SetValue(it->first, *it->second /* value will be cloned */);
275 }
276 }
277 }
278
279
280 void DicomMap::ExtractPatientInformation(DicomMap& result) const
281 {
282 ExtractTags(result, content_, PATIENT_MAIN_DICOM_TAGS, sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
283 }
284
285 void DicomMap::ExtractStudyInformation(DicomMap& result) const
286 {
287 ExtractTags(result, content_, STUDY_MAIN_DICOM_TAGS, sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
288 }
289
290 void DicomMap::ExtractSeriesInformation(DicomMap& result) const
291 {
292 ExtractTags(result, content_, SERIES_MAIN_DICOM_TAGS, sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
293 }
294
295 void DicomMap::ExtractInstanceInformation(DicomMap& result) const
296 {
297 ExtractTags(result, content_, INSTANCE_MAIN_DICOM_TAGS, sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
298 }
299
300
301
302 DicomMap* DicomMap::Clone() const
303 {
304 std::unique_ptr<DicomMap> result(new DicomMap);
305
306 for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
307 {
308 result->content_.insert(std::make_pair(it->first, it->second->Clone()));
309 }
310
311 return result.release();
312 }
313
314
315 void DicomMap::Assign(const DicomMap& other)
316 {
317 Clear();
318
319 for (Content::const_iterator it = other.content_.begin(); it != other.content_.end(); ++it)
320 {
321 content_.insert(std::make_pair(it->first, it->second->Clone()));
322 }
323 }
324
325
326 const DicomValue& DicomMap::GetValue(const DicomTag& tag) const
327 {
328 const DicomValue* value = TestAndGetValue(tag);
329
330 if (value)
331 {
332 return *value;
333 }
334 else
335 {
336 throw OrthancException(ErrorCode_InexistentTag);
337 }
338 }
339
340
341 const DicomValue* DicomMap::TestAndGetValue(const DicomTag& tag) const
342 {
343 Content::const_iterator it = content_.find(tag);
344
345 if (it == content_.end())
346 {
347 return NULL;
348 }
349 else
350 {
351 return it->second;
352 }
353 }
354
355
356 void DicomMap::Remove(const DicomTag& tag)
357 {
358 Content::iterator it = content_.find(tag);
359 if (it != content_.end())
360 {
361 delete it->second;
362 content_.erase(it);
363 }
364 }
365
366
367 static void SetupFindTemplate(DicomMap& result,
368 const MainDicomTag* tags,
369 size_t count)
370 {
371 result.Clear();
372
373 for (size_t i = 0; i < count; i++)
374 {
375 result.SetValue(tags[i].tag_, "", false);
376 }
377 }
378
379 void DicomMap::SetupFindPatientTemplate(DicomMap& result)
380 {
381 SetupFindTemplate(result, PATIENT_MAIN_DICOM_TAGS, sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
382 }
383
384 void DicomMap::SetupFindStudyTemplate(DicomMap& result)
385 {
386 SetupFindTemplate(result, STUDY_MAIN_DICOM_TAGS, sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
387 result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
388 result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
389
390 // These main DICOM tags are only indirectly related to the
391 // General Study Module, remove them
392 result.Remove(DICOM_TAG_INSTITUTION_NAME);
393 result.Remove(DICOM_TAG_REQUESTING_PHYSICIAN);
394 result.Remove(DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION);
395 }
396
397 void DicomMap::SetupFindSeriesTemplate(DicomMap& result)
398 {
399 SetupFindTemplate(result, SERIES_MAIN_DICOM_TAGS, sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
400 result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
401 result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
402 result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false);
403
404 // These tags are considered as "main" by Orthanc, but are not in the Series module
405 result.Remove(DicomTag(0x0008, 0x0070)); // Manufacturer
406 result.Remove(DicomTag(0x0008, 0x1010)); // Station name
407 result.Remove(DicomTag(0x0018, 0x0024)); // Sequence name
408 result.Remove(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES);
409 result.Remove(DICOM_TAG_IMAGES_IN_ACQUISITION);
410 result.Remove(DICOM_TAG_NUMBER_OF_SLICES);
411 result.Remove(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS);
412 result.Remove(DICOM_TAG_NUMBER_OF_TIME_SLICES);
413 result.Remove(DICOM_TAG_IMAGE_ORIENTATION_PATIENT);
414 result.Remove(DICOM_TAG_SERIES_TYPE);
415 result.Remove(DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION);
416 result.Remove(DICOM_TAG_CONTRAST_BOLUS_AGENT);
417 }
418
419 void DicomMap::SetupFindInstanceTemplate(DicomMap& result)
420 {
421 SetupFindTemplate(result, INSTANCE_MAIN_DICOM_TAGS, sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
422 result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
423 result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
424 result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false);
425 result.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "", false);
426 }
427
428
429 void DicomMap::CopyTagIfExists(const DicomMap& source,
430 const DicomTag& tag)
431 {
432 if (source.HasTag(tag))
433 {
434 SetValue(tag, source.GetValue(tag));
435 }
436 }
437
438
439 bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level)
440 {
441 const MainDicomTag *tags = NULL;
442 size_t size;
443 LoadMainDicomTags(tags, size, level);
444
445 for (size_t i = 0; i < size; i++)
446 {
447 if (tags[i].tag_ == tag)
448 {
449 return true;
450 }
451 }
452
453 return false;
454 }
455
456 bool DicomMap::IsMainDicomTag(const DicomTag& tag)
457 {
458 return (IsMainDicomTag(tag, ResourceType_Patient) ||
459 IsMainDicomTag(tag, ResourceType_Study) ||
460 IsMainDicomTag(tag, ResourceType_Series) ||
461 IsMainDicomTag(tag, ResourceType_Instance));
462 }
463
464
465 void DicomMap::GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level)
466 {
467 const MainDicomTag *tags = NULL;
468 size_t size;
469 LoadMainDicomTags(tags, size, level);
470
471 for (size_t i = 0; i < size; i++)
472 {
473 result.insert(tags[i].tag_);
474 }
475 }
476
477
478 void DicomMap::GetMainDicomTags(std::set<DicomTag>& result, ResourceType level)
479 {
480 result.clear();
481 GetMainDicomTagsInternal(result, level);
482 }
483
484
485 void DicomMap::GetMainDicomTags(std::set<DicomTag>& result)
486 {
487 result.clear();
488 GetMainDicomTagsInternal(result, ResourceType_Patient);
489 GetMainDicomTagsInternal(result, ResourceType_Study);
490 GetMainDicomTagsInternal(result, ResourceType_Series);
491 GetMainDicomTagsInternal(result, ResourceType_Instance);
492 }
493
494
495 void DicomMap::GetTags(std::set<DicomTag>& tags) const
496 {
497 tags.clear();
498
499 for (Content::const_iterator it = content_.begin();
500 it != content_.end(); ++it)
501 {
502 tags.insert(it->first);
503 }
504 }
505
506
507 static uint16_t ReadUnsignedInteger16(const char* dicom)
508 {
509 return le16toh(*reinterpret_cast<const uint16_t*>(dicom));
510 }
511
512
513 static uint32_t ReadUnsignedInteger32(const char* dicom)
514 {
515 return le32toh(*reinterpret_cast<const uint32_t*>(dicom));
516 }
517
518
519 static bool ValidateTag(const ValueRepresentation& vr,
520 const std::string& value)
521 {
522 switch (vr)
523 {
524 case ValueRepresentation_ApplicationEntity:
525 return value.size() <= 16;
526
527 case ValueRepresentation_AgeString:
528 return (value.size() == 4 &&
529 isdigit(value[0]) &&
530 isdigit(value[1]) &&
531 isdigit(value[2]) &&
532 (value[3] == 'D' || value[3] == 'W' || value[3] == 'M' || value[3] == 'Y'));
533
534 case ValueRepresentation_AttributeTag:
535 return value.size() == 4;
536
537 case ValueRepresentation_CodeString:
538 return value.size() <= 16;
539
540 case ValueRepresentation_Date:
541 return value.size() <= 18;
542
543 case ValueRepresentation_DecimalString:
544 return value.size() <= 16;
545
546 case ValueRepresentation_DateTime:
547 return value.size() <= 54;
548
549 case ValueRepresentation_FloatingPointSingle:
550 return value.size() == 4;
551
552 case ValueRepresentation_FloatingPointDouble:
553 return value.size() == 8;
554
555 case ValueRepresentation_IntegerString:
556 return value.size() <= 12;
557
558 case ValueRepresentation_LongString:
559 return value.size() <= 64;
560
561 case ValueRepresentation_LongText:
562 return value.size() <= 10240;
563
564 case ValueRepresentation_OtherByte:
565 return true;
566
567 case ValueRepresentation_OtherDouble:
568 return value.size() <= (static_cast<uint64_t>(1) << 32) - 8;
569
570 case ValueRepresentation_OtherFloat:
571 return value.size() <= (static_cast<uint64_t>(1) << 32) - 4;
572
573 case ValueRepresentation_OtherLong:
574 return true;
575
576 case ValueRepresentation_OtherWord:
577 return true;
578
579 case ValueRepresentation_PersonName:
580 return true;
581
582 case ValueRepresentation_ShortString:
583 return value.size() <= 16;
584
585 case ValueRepresentation_SignedLong:
586 return value.size() == 4;
587
588 case ValueRepresentation_Sequence:
589 return true;
590
591 case ValueRepresentation_SignedShort:
592 return value.size() == 2;
593
594 case ValueRepresentation_ShortText:
595 return value.size() <= 1024;
596
597 case ValueRepresentation_Time:
598 return value.size() <= 28;
599
600 case ValueRepresentation_UnlimitedCharacters:
601 return value.size() <= (static_cast<uint64_t>(1) << 32) - 2;
602
603 case ValueRepresentation_UniqueIdentifier:
604 return value.size() <= 64;
605
606 case ValueRepresentation_UnsignedLong:
607 return value.size() == 4;
608
609 case ValueRepresentation_Unknown:
610 return true;
611
612 case ValueRepresentation_UniversalResource:
613 return value.size() <= (static_cast<uint64_t>(1) << 32) - 2;
614
615 case ValueRepresentation_UnsignedShort:
616 return value.size() == 2;
617
618 case ValueRepresentation_UnlimitedText:
619 return value.size() <= (static_cast<uint64_t>(1) << 32) - 2;
620
621 default:
622 // Assume unsupported tags are OK
623 return true;
624 }
625 }
626
627
628 static void RemoveTagPadding(std::string& value,
629 const ValueRepresentation& vr)
630 {
631 /**
632 * Remove padding from character strings, if need be. For the time
633 * being, only the UI VR is supported.
634 * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
635 **/
636
637 switch (vr)
638 {
639 case ValueRepresentation_UniqueIdentifier:
640 {
641 /**
642 * "Values with a VR of UI shall be padded with a single
643 * trailing NULL (00H) character when necessary to achieve even
644 * length."
645 **/
646
647 if (!value.empty() &&
648 value[value.size() - 1] == '\0')
649 {
650 value.resize(value.size() - 1);
651 }
652
653 break;
654 }
655
656 /**
657 * TODO implement other VR
658 **/
659
660 default:
661 // No padding is applicable to this VR
662 break;
663 }
664 }
665
666
667 static bool ReadNextTag(DicomTag& tag,
668 ValueRepresentation& vr,
669 std::string& value,
670 const char* dicom,
671 size_t size,
672 size_t& position)
673 {
674 /**
675 * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html#sect_7.1.2
676 * This function reads a data element with Explicit VR encoded using Little-Endian.
677 **/
678
679 if (position + 6 > size)
680 {
681 return false;
682 }
683
684 tag = DicomTag(ReadUnsignedInteger16(dicom + position),
685 ReadUnsignedInteger16(dicom + position + 2));
686
687 vr = StringToValueRepresentation(std::string(dicom + position + 4, 2), true);
688 if (vr == ValueRepresentation_NotSupported)
689 {
690 return false;
691 }
692
693 if (vr == ValueRepresentation_OtherByte ||
694 vr == ValueRepresentation_OtherDouble ||
695 vr == ValueRepresentation_OtherFloat ||
696 vr == ValueRepresentation_OtherLong ||
697 vr == ValueRepresentation_OtherWord ||
698 vr == ValueRepresentation_Sequence ||
699 vr == ValueRepresentation_UnlimitedCharacters ||
700 vr == ValueRepresentation_UniversalResource ||
701 vr == ValueRepresentation_UnlimitedText ||
702 vr == ValueRepresentation_Unknown) // Note that "UN" should never appear in the Meta Information
703 {
704 if (position + 12 > size)
705 {
706 return false;
707 }
708
709 uint32_t length = ReadUnsignedInteger32(dicom + position + 8);
710
711 if (position + 12 + length > size)
712 {
713 return false;
714 }
715
716 value.assign(dicom + position + 12, length);
717 position += (12 + length);
718 }
719 else
720 {
721 if (position + 8 > size)
722 {
723 return false;
724 }
725
726 uint16_t length = ReadUnsignedInteger16(dicom + position + 6);
727
728 if (position + 8 + length > size)
729 {
730 return false;
731 }
732
733 value.assign(dicom + position + 8, length);
734 position += (8 + length);
735 }
736
737 if (!ValidateTag(vr, value))
738 {
739 return false;
740 }
741
742 RemoveTagPadding(value, vr);
743
744 return true;
745 }
746
747
748 bool DicomMap::IsDicomFile(const void* dicom,
749 size_t size)
750 {
751 /**
752 * http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html
753 * According to Table 7.1-1, besides the "DICM" DICOM prefix, the
754 * file preamble (i.e. dicom[0..127]) should not be taken into
755 * account to determine whether the file is or is not a DICOM file.
756 **/
757
758 const uint8_t* p = reinterpret_cast<const uint8_t*>(dicom);
759
760 return (size >= 132 &&
761 p[128] == 'D' &&
762 p[129] == 'I' &&
763 p[130] == 'C' &&
764 p[131] == 'M');
765 }
766
767
768 bool DicomMap::ParseDicomMetaInformation(DicomMap& result,
769 const void* dicom,
770 size_t size)
771 {
772 if (!IsDicomFile(dicom, size))
773 {
774 return false;
775 }
776
777
778 /**
779 * The DICOM File Meta Information must be encoded using the
780 * Explicit VR Little Endian Transfer Syntax
781 * (UID=1.2.840.10008.1.2.1).
782 **/
783
784 result.Clear();
785
786 // First, we read the "File Meta Information Group Length" tag
787 // (0002,0000) to know where to stop reading the meta header
788 size_t position = 132;
789
790 DicomTag tag(0x0000, 0x0000); // Dummy initialization
791 ValueRepresentation vr;
792 std::string value;
793 if (!ReadNextTag(tag, vr, value, reinterpret_cast<const char*>(dicom), size, position) ||
794 tag.GetGroup() != 0x0002 ||
795 tag.GetElement() != 0x0000 ||
796 vr != ValueRepresentation_UnsignedLong ||
797 value.size() != 4)
798 {
799 return false;
800 }
801
802 size_t stopPosition = position + ReadUnsignedInteger32(value.c_str());
803 if (stopPosition > size)
804 {
805 return false;
806 }
807
808 while (position < stopPosition)
809 {
810 if (ReadNextTag(tag, vr, value, reinterpret_cast<const char*>(dicom), size, position))
811 {
812 result.SetValue(tag, value, IsBinaryValueRepresentation(vr));
813 }
814 else
815 {
816 return false;
817 }
818 }
819
820 return true;
821 }
822
823
824 static std::string ValueAsString(const DicomMap& summary,
825 const DicomTag& tag)
826 {
827 const DicomValue& value = summary.GetValue(tag);
828 if (value.IsNull())
829 {
830 return "(null)";
831 }
832 else
833 {
834 return value.GetContent();
835 }
836 }
837
838
839 void DicomMap::LogMissingTagsForStore() const
840 {
841 std::string s, t;
842
843 if (HasTag(DICOM_TAG_PATIENT_ID))
844 {
845 if (t.size() > 0)
846 t += ", ";
847 t += "PatientID=" + ValueAsString(*this, DICOM_TAG_PATIENT_ID);
848 }
849 else
850 {
851 if (s.size() > 0)
852 s += ", ";
853 s += "PatientID";
854 }
855
856 if (HasTag(DICOM_TAG_STUDY_INSTANCE_UID))
857 {
858 if (t.size() > 0)
859 t += ", ";
860 t += "StudyInstanceUID=" + ValueAsString(*this, DICOM_TAG_STUDY_INSTANCE_UID);
861 }
862 else
863 {
864 if (s.size() > 0)
865 s += ", ";
866 s += "StudyInstanceUID";
867 }
868
869 if (HasTag(DICOM_TAG_SERIES_INSTANCE_UID))
870 {
871 if (t.size() > 0)
872 t += ", ";
873 t += "SeriesInstanceUID=" + ValueAsString(*this, DICOM_TAG_SERIES_INSTANCE_UID);
874 }
875 else
876 {
877 if (s.size() > 0)
878 s += ", ";
879 s += "SeriesInstanceUID";
880 }
881
882 if (HasTag(DICOM_TAG_SOP_INSTANCE_UID))
883 {
884 if (t.size() > 0)
885 t += ", ";
886 t += "SOPInstanceUID=" + ValueAsString(*this, DICOM_TAG_SOP_INSTANCE_UID);
887 }
888 else
889 {
890 if (s.size() > 0)
891 s += ", ";
892 s += "SOPInstanceUID";
893 }
894
895 if (t.size() == 0)
896 {
897 LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)";
898 }
899 else
900 {
901 LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t;
902 }
903 }
904
905
906 bool DicomMap::LookupStringValue(std::string& result,
907 const DicomTag& tag,
908 bool allowBinary) const
909 {
910 const DicomValue* value = TestAndGetValue(tag);
911
912 if (value == NULL)
913 {
914 return false;
915 }
916 else
917 {
918 return value->CopyToString(result, allowBinary);
919 }
920 }
921
922 bool DicomMap::ParseInteger32(int32_t& result,
923 const DicomTag& tag) const
924 {
925 const DicomValue* value = TestAndGetValue(tag);
926
927 if (value == NULL)
928 {
929 return false;
930 }
931 else
932 {
933 return value->ParseInteger32(result);
934 }
935 }
936
937 bool DicomMap::ParseInteger64(int64_t& result,
938 const DicomTag& tag) const
939 {
940 const DicomValue* value = TestAndGetValue(tag);
941
942 if (value == NULL)
943 {
944 return false;
945 }
946 else
947 {
948 return value->ParseInteger64(result);
949 }
950 }
951
952 bool DicomMap::ParseUnsignedInteger32(uint32_t& result,
953 const DicomTag& tag) const
954 {
955 const DicomValue* value = TestAndGetValue(tag);
956
957 if (value == NULL)
958 {
959 return false;
960 }
961 else
962 {
963 return value->ParseUnsignedInteger32(result);
964 }
965 }
966
967 bool DicomMap::ParseUnsignedInteger64(uint64_t& result,
968 const DicomTag& tag) const
969 {
970 const DicomValue* value = TestAndGetValue(tag);
971
972 if (value == NULL)
973 {
974 return false;
975 }
976 else
977 {
978 return value->ParseUnsignedInteger64(result);
979 }
980 }
981
982 bool DicomMap::ParseFloat(float& result,
983 const DicomTag& tag) const
984 {
985 const DicomValue* value = TestAndGetValue(tag);
986
987 if (value == NULL)
988 {
989 return false;
990 }
991 else
992 {
993 return value->ParseFloat(result);
994 }
995 }
996
997 bool DicomMap::ParseFirstFloat(float& result,
998 const DicomTag& tag) const
999 {
1000 const DicomValue* value = TestAndGetValue(tag);
1001
1002 if (value == NULL)
1003 {
1004 return false;
1005 }
1006 else
1007 {
1008 return value->ParseFirstFloat(result);
1009 }
1010 }
1011
1012 bool DicomMap::ParseDouble(double& result,
1013 const DicomTag& tag) const
1014 {
1015 const DicomValue* value = TestAndGetValue(tag);
1016
1017 if (value == NULL)
1018 {
1019 return false;
1020 }
1021 else
1022 {
1023 return value->ParseDouble(result);
1024 }
1025 }
1026
1027
1028 void DicomMap::FromDicomAsJson(const Json::Value& dicomAsJson)
1029 {
1030 if (dicomAsJson.type() != Json::objectValue)
1031 {
1032 throw OrthancException(ErrorCode_BadFileFormat);
1033 }
1034
1035 Clear();
1036
1037 Json::Value::Members tags = dicomAsJson.getMemberNames();
1038 for (Json::Value::Members::const_iterator
1039 it = tags.begin(); it != tags.end(); ++it)
1040 {
1041 DicomTag tag(0, 0);
1042 if (!DicomTag::ParseHexadecimal(tag, it->c_str()))
1043 {
1044 throw OrthancException(ErrorCode_CorruptedFile);
1045 }
1046
1047 const Json::Value& value = dicomAsJson[*it];
1048
1049 if (value.type() != Json::objectValue ||
1050 !value.isMember("Type") ||
1051 !value.isMember("Value") ||
1052 value["Type"].type() != Json::stringValue)
1053 {
1054 throw OrthancException(ErrorCode_CorruptedFile);
1055 }
1056
1057 if (value["Type"] == "String")
1058 {
1059 if (value["Value"].type() != Json::stringValue)
1060 {
1061 throw OrthancException(ErrorCode_CorruptedFile);
1062 }
1063 else
1064 {
1065 SetValue(tag, value["Value"].asString(), false /* not binary */);
1066 }
1067 }
1068 }
1069 }
1070
1071
1072 void DicomMap::Merge(const DicomMap& other)
1073 {
1074 for (Content::const_iterator it = other.content_.begin();
1075 it != other.content_.end(); ++it)
1076 {
1077 assert(it->second != NULL);
1078
1079 if (content_.find(it->first) == content_.end())
1080 {
1081 content_[it->first] = it->second->Clone();
1082 }
1083 }
1084 }
1085
1086
1087 void DicomMap::MergeMainDicomTags(const DicomMap& other,
1088 ResourceType level)
1089 {
1090 const MainDicomTag* tags = NULL;
1091 size_t size = 0;
1092
1093 LoadMainDicomTags(tags, size, level);
1094 assert(tags != NULL && size > 0);
1095
1096 for (size_t i = 0; i < size; i++)
1097 {
1098 Content::const_iterator found = other.content_.find(tags[i].tag_);
1099
1100 if (found != other.content_.end() &&
1101 content_.find(tags[i].tag_) == content_.end())
1102 {
1103 assert(found->second != NULL);
1104 content_[tags[i].tag_] = found->second->Clone();
1105 }
1106 }
1107 }
1108
1109
1110 void DicomMap::ExtractMainDicomTags(const DicomMap& other)
1111 {
1112 Clear();
1113 MergeMainDicomTags(other, ResourceType_Patient);
1114 MergeMainDicomTags(other, ResourceType_Study);
1115 MergeMainDicomTags(other, ResourceType_Series);
1116 MergeMainDicomTags(other, ResourceType_Instance);
1117 }
1118
1119
1120 bool DicomMap::HasOnlyMainDicomTags() const
1121 {
1122 // TODO - Speed up possible by making this std::set a global variable
1123
1124 std::set<DicomTag> mainDicomTags;
1125 GetMainDicomTags(mainDicomTags);
1126
1127 for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
1128 {
1129 if (mainDicomTags.find(it->first) == mainDicomTags.end())
1130 {
1131 return false;
1132 }
1133 }
1134
1135 return true;
1136 }
1137
1138
1139 void DicomMap::Serialize(Json::Value& target) const
1140 {
1141 target = Json::objectValue;
1142
1143 for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
1144 {
1145 assert(it->second != NULL);
1146
1147 std::string tag = it->first.Format();
1148
1149 Json::Value value;
1150 it->second->Serialize(value);
1151
1152 target[tag] = value;
1153 }
1154 }
1155
1156
1157 void DicomMap::Unserialize(const Json::Value& source)
1158 {
1159 Clear();
1160
1161 if (source.type() != Json::objectValue)
1162 {
1163 throw OrthancException(ErrorCode_BadFileFormat);
1164 }
1165
1166 Json::Value::Members tags = source.getMemberNames();
1167
1168 for (size_t i = 0; i < tags.size(); i++)
1169 {
1170 DicomTag tag(0, 0);
1171
1172 if (!DicomTag::ParseHexadecimal(tag, tags[i].c_str()) ||
1173 content_.find(tag) != content_.end())
1174 {
1175 throw OrthancException(ErrorCode_BadFileFormat);
1176 }
1177
1178 std::unique_ptr<DicomValue> value(new DicomValue);
1179 value->Unserialize(source[tags[i]]);
1180
1181 content_[tag] = value.release();
1182 }
1183 }
1184
1185
1186 void DicomMap::FromDicomWeb(const Json::Value& source)
1187 {
1188 static const char* const ALPHABETIC = "Alphabetic";
1189 static const char* const IDEOGRAPHIC = "Ideographic";
1190 static const char* const INLINE_BINARY = "InlineBinary";
1191 static const char* const PHONETIC = "Phonetic";
1192 static const char* const VALUE = "Value";
1193 static const char* const VR = "vr";
1194
1195 Clear();
1196
1197 if (source.type() != Json::objectValue)
1198 {
1199 throw OrthancException(ErrorCode_BadFileFormat);
1200 }
1201
1202 Json::Value::Members tags = source.getMemberNames();
1203
1204 for (size_t i = 0; i < tags.size(); i++)
1205 {
1206 const Json::Value& item = source[tags[i]];
1207 DicomTag tag(0, 0);
1208
1209 if (item.type() != Json::objectValue ||
1210 !item.isMember(VR) ||
1211 item[VR].type() != Json::stringValue ||
1212 !DicomTag::ParseHexadecimal(tag, tags[i].c_str()))
1213 {
1214 throw OrthancException(ErrorCode_BadFileFormat);
1215 }
1216
1217 ValueRepresentation vr = StringToValueRepresentation(item[VR].asString(), false);
1218
1219 if (item.isMember(INLINE_BINARY))
1220 {
1221 const Json::Value& value = item[INLINE_BINARY];
1222
1223 if (value.type() == Json::stringValue)
1224 {
1225 std::string decoded;
1226 Toolbox::DecodeBase64(decoded, value.asString());
1227 SetValue(tag, decoded, true /* binary data */);
1228 }
1229 }
1230 else if (!item.isMember(VALUE))
1231 {
1232 // Tag is present, but it has a null value
1233 SetValue(tag, "", false /* not binary */);
1234 }
1235 else
1236 {
1237 const Json::Value& value = item[VALUE];
1238
1239 if (value.type() == Json::arrayValue)
1240 {
1241 bool supported = true;
1242
1243 std::string s;
1244 for (Json::Value::ArrayIndex i = 0; i < value.size() && supported; i++)
1245 {
1246 if (!s.empty())
1247 {
1248 s += '\\';
1249 }
1250
1251 switch (value[i].type())
1252 {
1253 case Json::objectValue:
1254 if (vr == ValueRepresentation_PersonName &&
1255 value[i].type() == Json::objectValue)
1256 {
1257 if (value[i].isMember(ALPHABETIC) &&
1258 value[i][ALPHABETIC].type() == Json::stringValue)
1259 {
1260 s += value[i][ALPHABETIC].asString();
1261 }
1262
1263 bool hasIdeographic = false;
1264
1265 if (value[i].isMember(IDEOGRAPHIC) &&
1266 value[i][IDEOGRAPHIC].type() == Json::stringValue)
1267 {
1268 s += '=' + value[i][IDEOGRAPHIC].asString();
1269 hasIdeographic = true;
1270 }
1271
1272 if (value[i].isMember(PHONETIC) &&
1273 value[i][PHONETIC].type() == Json::stringValue)
1274 {
1275 if (!hasIdeographic)
1276 {
1277 s += '=';
1278 }
1279
1280 s += '=' + value[i][PHONETIC].asString();
1281 }
1282 }
1283 else
1284 {
1285 // This is the case of sequences
1286 supported = false;
1287 }
1288
1289 break;
1290
1291 case Json::stringValue:
1292 s += value[i].asString();
1293 break;
1294
1295 case Json::intValue:
1296 s += boost::lexical_cast<std::string>(value[i].asInt());
1297 break;
1298
1299 case Json::uintValue:
1300 s += boost::lexical_cast<std::string>(value[i].asUInt());
1301 break;
1302
1303 case Json::realValue:
1304 s += boost::lexical_cast<std::string>(value[i].asDouble());
1305 break;
1306
1307 default:
1308 break;
1309 }
1310 }
1311
1312 if (supported)
1313 {
1314 SetValue(tag, s, false /* not binary */);
1315 }
1316 }
1317 }
1318 }
1319 }
1320
1321
1322 std::string DicomMap::GetStringValue(const DicomTag& tag,
1323 const std::string& defaultValue,
1324 bool allowBinary) const
1325 {
1326 std::string s;
1327 if (LookupStringValue(s, tag, allowBinary))
1328 {
1329 return s;
1330 }
1331 else
1332 {
1333 return defaultValue;
1334 }
1335 }
1336
1337
1338 void DicomMap::RemoveBinaryTags()
1339 {
1340 Content kept;
1341
1342 for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
1343 {
1344 assert(it->second != NULL);
1345
1346 if (!it->second->IsBinary() &&
1347 !it->second->IsNull())
1348 {
1349 kept[it->first] = it->second;
1350 }
1351 else
1352 {
1353 delete it->second;
1354 }
1355 }
1356
1357 content_ = kept;
1358 }
1359
1360
1361 void DicomMap::DumpMainDicomTags(Json::Value& target,
1362 ResourceType level) const
1363 {
1364 std::map<DicomTag, std::string> mainTags; // TODO - Create a singleton to hold this map
1365 LoadMainDicomTags(mainTags, level);
1366
1367 target = Json::objectValue;
1368
1369 for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
1370 {
1371 assert(it->second != NULL);
1372
1373 if (!it->second->IsBinary() &&
1374 !it->second->IsNull())
1375 {
1376 std::map<DicomTag, std::string>::const_iterator found = mainTags.find(it->first);
1377
1378 if (found != mainTags.end())
1379 {
1380 target[found->second] = it->second->GetContent();
1381 }
1382 }
1383 }
1384 }
1385
1386
1387 void DicomMap::ParseMainDicomTags(const Json::Value& source,
1388 ResourceType level)
1389 {
1390 if (source.type() != Json::objectValue)
1391 {
1392 throw OrthancException(ErrorCode_BadFileFormat);
1393 }
1394
1395 std::map<std::string, DicomTag2> mainTags; // TODO - Create a singleton to hold this map
1396 LoadMainDicomTags(mainTags, level);
1397
1398 Json::Value::Members members = source.getMemberNames();
1399 for (size_t i = 0; i < members.size(); i++)
1400 {
1401 std::map<std::string, DicomTag2>::const_iterator found = mainTags.find(members[i]);
1402
1403 if (found != mainTags.end())
1404 {
1405 const Json::Value& value = source[members[i]];
1406 if (value.type() != Json::stringValue)
1407 {
1408 throw OrthancException(ErrorCode_BadFileFormat);
1409 }
1410 else
1411 {
1412 SetValue(found->second, value.asString(), false);
1413 }
1414 }
1415 }
1416 }
1417
1418
1419 void DicomMap::Print(FILE* fp) const
1420 {
1421 DicomArray a(*this);
1422 a.Print(fp);
1423 }
1424 }