comparison OrthancFramework/Sources/DicomParsing/DicomModification.cpp @ 4678:2e850edf03d6

Full support for the anonymization of subsequences containing tags whose VR is UI
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 04 Jun 2021 17:38:43 +0200
parents 521e39b3f2c0
children e3810750dc9d
comparison
equal deleted inserted replaced
4677:521e39b3f2c0 4678:2e850edf03d6
42 static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2021b = 42 static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2021b =
43 "Orthanc " ORTHANC_VERSION " - PS 3.15-2021b Table E.1-1 Basic Profile"; 43 "Orthanc " ORTHANC_VERSION " - PS 3.15-2021b Table E.1-1 Basic Profile";
44 44
45 namespace Orthanc 45 namespace Orthanc
46 { 46 {
47 DicomModification::DicomTagRange::DicomTagRange(uint16_t groupFrom,
48 uint16_t groupTo,
49 uint16_t elementFrom,
50 uint16_t elementTo) :
51 groupFrom_(groupFrom),
52 groupTo_(groupTo),
53 elementFrom_(elementFrom),
54 elementTo_(elementTo)
55 {
56 }
57
58
59 bool DicomModification::DicomTagRange::Contains(const DicomTag& tag) const
60 {
61 return (tag.GetGroup() >= groupFrom_ &&
62 tag.GetGroup() <= groupTo_ &&
63 tag.GetElement() >= elementFrom_ &&
64 tag.GetElement() <= elementTo_);
65 }
66
67
47 class DicomModification::RelationshipsVisitor : public ITagVisitor 68 class DicomModification::RelationshipsVisitor : public ITagVisitor
48 { 69 {
49 private: 70 private:
50 DicomModification& that_; 71 DicomModification& that_;
51 72
122 const std::vector<size_t>& parentIndexes, 143 const std::vector<size_t>& parentIndexes,
123 const DicomTag& tag, 144 const DicomTag& tag,
124 ValueRepresentation vr, 145 ValueRepresentation vr,
125 const std::string& value) 146 const std::string& value)
126 { 147 {
148 // Note that all the tags in "uids_" have the VR UI (unique
149 // identifier), and are considered as strings
150
127 if (!IsEnabled(tag)) 151 if (!IsEnabled(tag))
128 { 152 {
129 return Action_None; 153 return Action_None;
130 } 154 }
131 else if (parentTags.size() == 2 && 155 else if (!parentTags.empty() && // Don't anonymize twice the anonymization done by "MapDicomTags()"
132 parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE && 156 tag == DICOM_TAG_STUDY_INSTANCE_UID)
133 parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE && 157 {
134 tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID) 158 newValue = that_.MapDicomIdentifier(value, ResourceType_Study);
135 {
136 // in RT-STRUCT, this ReferencedSOPInstanceUID is actually referencing a StudyInstanceUID !!
137 // (observed in many data sets including: https://wiki.cancerimagingarchive.net/display/Public/Lung+CT+Segmentation+Challenge+2017)
138 // tested in test_anonymize_relationships_5
139 newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Study);
140 return Action_Replace; 159 return Action_Replace;
141 } 160 }
142 else if (tag == DICOM_TAG_FRAME_OF_REFERENCE_UID || 161 else if (!parentTags.empty() && // Don't anonymize twice the anonymization done by "MapDicomTags()"
143 tag == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID || 162 tag == DICOM_TAG_SERIES_INSTANCE_UID)
144 tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID || 163 {
145 tag == DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID) 164 newValue = that_.MapDicomIdentifier(value, ResourceType_Series);
146 {
147 newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Instance);
148 return Action_Replace; 165 return Action_Replace;
149 } 166 }
150 else if (parentTags.size() == 1 && 167 else if (!parentTags.empty() && // Don't anonymize twice the anonymization done by "MapDicomTags()"
151 parentTags[0] == DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE && 168 tag == DICOM_TAG_SOP_INSTANCE_UID)
152 tag == DICOM_TAG_STUDY_INSTANCE_UID) 169 {
153 { 170 newValue = that_.MapDicomIdentifier(value, ResourceType_Instance);
154 newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Study);
155 return Action_Replace; 171 return Action_Replace;
156 } 172 }
157 else if (parentTags.size() == 2 && 173 else if (that_.uids_.find(tag) != that_.uids_.end())
158 parentTags[0] == DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE && 174 {
159 parentTags[1] == DICOM_TAG_REFERENCED_SERIES_SEQUENCE && 175 assert(vr == ValueRepresentation_UniqueIdentifier ||
160 tag == DICOM_TAG_SERIES_INSTANCE_UID) 176 vr == ValueRepresentation_NotSupported /* for older versions of DCMTK */);
161 { 177
162 newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series); 178 if (parentTags.size() == 2 &&
179 parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE &&
180 parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE &&
181 tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID)
182 {
183 // in RT-STRUCT, this ReferencedSOPInstanceUID is actually referencing a StudyInstanceUID !!
184 // (observed in many data sets including: https://wiki.cancerimagingarchive.net/display/Public/Lung+CT+Segmentation+Challenge+2017)
185 // tested in test_anonymize_relationships_5
186 newValue = that_.MapDicomIdentifier(value, ResourceType_Study);
187 }
188 else
189 {
190 newValue = that_.MapDicomIdentifier(value, ResourceType_Instance);
191 }
192
163 return Action_Replace; 193 return Action_Replace;
164 } 194 }
165 else if (parentTags.size() == 3 &&
166 parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE &&
167 parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE &&
168 parentTags[2] == DICOM_TAG_RT_REFERENCED_SERIES_SEQUENCE &&
169 tag == DICOM_TAG_SERIES_INSTANCE_UID)
170 {
171 newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series);
172 return Action_Replace;
173 }
174 else if (parentTags.size() == 1 &&
175 parentTags[0] == DICOM_TAG_REFERENCED_SERIES_SEQUENCE &&
176 tag == DICOM_TAG_SERIES_INSTANCE_UID)
177 {
178 newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series);
179 return Action_Replace;
180 }
181 else 195 else
182 { 196 {
183 return Action_None; 197 return Action_None;
184 } 198 }
185 } 199 }
186 200
187 void RemoveRelationships(ParsedDicomFile& dicom) const 201 void RemoveRelationships(ParsedDicomFile& dicom) const
188 { 202 {
189 // Sequences containing the UID relationships 203 for (SetOfTags::const_iterator it = that_.uids_.begin(); it != that_.uids_.end(); ++it)
204 {
205 if (*it != DICOM_TAG_STUDY_INSTANCE_UID &&
206 *it != DICOM_TAG_SERIES_INSTANCE_UID &&
207 *it != DICOM_TAG_SOP_INSTANCE_UID)
208 {
209 RemoveIfEnabled(dicom, *it);
210 }
211 }
212
213 // The only two sequences with to the "X/Z/U*" rule in the
214 // basic profile. They were already present in Orthanc 1.9.3.
190 RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_IMAGE_SEQUENCE); 215 RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_IMAGE_SEQUENCE);
191 RemoveIfEnabled(dicom, DICOM_TAG_SOURCE_IMAGE_SEQUENCE); 216 RemoveIfEnabled(dicom, DICOM_TAG_SOURCE_IMAGE_SEQUENCE);
192
193 // Individual tags
194 RemoveIfEnabled(dicom, DICOM_TAG_FRAME_OF_REFERENCE_UID);
195
196 // The tags below should never occur at the first level of the
197 // hierarchy, but remove them anyway
198 RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID);
199 RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_SOP_INSTANCE_UID);
200 RemoveIfEnabled(dicom, DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID);
201 } 217 }
202 }; 218 };
203 219
204 220
205 bool DicomModification::CancelReplacement(const DicomTag& tag) 221 bool DicomModification::CancelReplacement(const DicomTag& tag)
276 } 292 }
277 293
278 std::string DicomModification::MapDicomIdentifier(const std::string& original, 294 std::string DicomModification::MapDicomIdentifier(const std::string& original,
279 ResourceType level) 295 ResourceType level)
280 { 296 {
297 const std::string stripped = Toolbox::StripSpaces(original);
298
281 std::string mapped; 299 std::string mapped;
282 300
283 UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original)); 301 UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, stripped));
284 302
285 if (previous == uidMap_.end()) 303 if (previous == uidMap_.end())
286 { 304 {
287 if (identifierGenerator_ == NULL) 305 if (identifierGenerator_ == NULL)
288 { 306 {
289 mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); 307 mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level);
290 } 308 }
291 else 309 else
292 { 310 {
293 if (!identifierGenerator_->Apply(mapped, original, level, currentSource_)) 311 if (!identifierGenerator_->Apply(mapped, stripped, level, currentSource_))
294 { 312 {
295 throw OrthancException(ErrorCode_InternalError, 313 throw OrthancException(ErrorCode_InternalError,
296 "Unable to generate an anonymized ID"); 314 "Unable to generate an anonymized ID");
297 } 315 }
298 } 316 }
299 317
300 uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped)); 318 uidMap_.insert(std::make_pair(std::make_pair(level, stripped), mapped));
301 } 319 }
302 else 320 else
303 { 321 {
304 mapped = previous->second; 322 mapped = previous->second;
305 } 323 }
335 if (!const_cast<const ParsedDicomFile&>(dicom).GetTagValue(original, *tag)) 353 if (!const_cast<const ParsedDicomFile&>(dicom).GetTagValue(original, *tag))
336 { 354 {
337 original = ""; 355 original = "";
338 } 356 }
339 357
340 std::string mapped = MapDicomIdentifier(Toolbox::StripSpaces(original), level); 358 std::string mapped = MapDicomIdentifier(original, level);
341 359
342 dicom.Replace(*tag, mapped, 360 dicom.Replace(*tag, mapped,
343 false /* don't try and decode data URI scheme for UIDs */, 361 false /* don't try and decode data URI scheme for UIDs */,
344 DicomReplaceMode_InsertIfAbsent, privateCreator_); 362 DicomReplaceMode_InsertIfAbsent, privateCreator_);
345 } 363 }
369 bool wasRemoved = IsRemoved(tag); 387 bool wasRemoved = IsRemoved(tag);
370 bool wasCleared = IsCleared(tag); 388 bool wasCleared = IsCleared(tag);
371 389
372 removals_.erase(tag); 390 removals_.erase(tag);
373 clearings_.erase(tag); 391 clearings_.erase(tag);
392 uids_.erase(tag);
374 393
375 bool wasReplaced = CancelReplacement(tag); 394 bool wasReplaced = CancelReplacement(tag);
376 395
377 if (tag == DICOM_TAG_STUDY_INSTANCE_UID) 396 if (tag == DICOM_TAG_STUDY_INSTANCE_UID)
378 { 397 {
402 421
403 void DicomModification::Remove(const DicomTag& tag) 422 void DicomModification::Remove(const DicomTag& tag)
404 { 423 {
405 removals_.insert(tag); 424 removals_.insert(tag);
406 clearings_.erase(tag); 425 clearings_.erase(tag);
426 uids_.erase(tag);
407 CancelReplacement(tag); 427 CancelReplacement(tag);
408 privateTagsToKeep_.erase(tag); 428 privateTagsToKeep_.erase(tag);
409 429
410 MarkNotOrthancAnonymization(); 430 MarkNotOrthancAnonymization();
411 } 431 }
412 432
413 void DicomModification::Clear(const DicomTag& tag) 433 void DicomModification::Clear(const DicomTag& tag)
414 { 434 {
415 removals_.erase(tag); 435 removals_.erase(tag);
416 clearings_.insert(tag); 436 clearings_.insert(tag);
437 uids_.erase(tag);
417 CancelReplacement(tag); 438 CancelReplacement(tag);
418 privateTagsToKeep_.erase(tag); 439 privateTagsToKeep_.erase(tag);
419 440
420 MarkNotOrthancAnonymization(); 441 MarkNotOrthancAnonymization();
421 } 442 }
422 443
423 bool DicomModification::IsRemoved(const DicomTag& tag) const 444 bool DicomModification::IsRemoved(const DicomTag& tag) const
424 { 445 {
425 return removals_.find(tag) != removals_.end(); 446 if (removals_.find(tag) != removals_.end())
447 {
448 return true;
449 }
450 else
451 {
452 for (RemovedRanges::const_iterator it = removedRanges_.begin();
453 it != removedRanges_.end(); it++)
454 {
455 if (it->Contains(tag))
456 {
457 return true;
458 }
459 }
460
461 return false;
462 }
426 } 463 }
427 464
428 bool DicomModification::IsCleared(const DicomTag& tag) const 465 bool DicomModification::IsCleared(const DicomTag& tag) const
429 { 466 {
430 return clearings_.find(tag) != clearings_.end(); 467 return clearings_.find(tag) != clearings_.end();
434 const Json::Value& value, 471 const Json::Value& value,
435 bool safeForAnonymization) 472 bool safeForAnonymization)
436 { 473 {
437 clearings_.erase(tag); 474 clearings_.erase(tag);
438 removals_.erase(tag); 475 removals_.erase(tag);
476 uids_.erase(tag);
439 privateTagsToKeep_.erase(tag); 477 privateTagsToKeep_.erase(tag);
440 ReplaceInternal(tag, value); 478 ReplaceInternal(tag, value);
441 479
442 if (!safeForAnonymization) 480 if (!safeForAnonymization)
443 { 481 {
511 { 549 {
512 return level_; 550 return level_;
513 } 551 }
514 552
515 553
554 static void SetupUidsFromOrthancInternal(std::set<DicomTag>& uids,
555 std::set<DicomTag>& removals,
556 const DicomTag& tag)
557 {
558 uids.insert(tag);
559 removals.erase(tag); // Necessary if unserializing a job from 1.9.3
560 }
561
562
563 void DicomModification::SetupUidsFromOrthanc_1_9_3()
564 {
565 /**
566 * Values below come from the hardcoded UID of Orthanc 1.9.3
567 * in DicomModification::RelationshipsVisitor::VisitString() and
568 * DicomModification::RelationshipsVisitor::RemoveRelationships()
569 * https://hg.orthanc-server.com/orthanc/file/Orthanc-1.9.3/OrthancFramework/Sources/DicomParsing/DicomModification.cpp#l117
570 **/
571 uids_.clear();
572
573 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0008, 0x0014)); // Instance Creator UID <= from SetupAnonymization2008()
574 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID <= from VisitString() + RemoveRelationships()
575 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0020, 0x0052)); // Frame of Reference UID <= from VisitString() + RemoveRelationships()
576 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID <= from SetupAnonymization2008()
577 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0040, 0xa124)); // UID <= from SetupAnonymization2008()
578 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0088, 0x0140)); // Storage Media File-set UID <= from SetupAnonymization2008()
579 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID <= from VisitString() + RemoveRelationships()
580 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID <= from VisitString() + RemoveRelationships()
581 }
582
583
516 void DicomModification::SetupAnonymization2008() 584 void DicomModification::SetupAnonymization2008()
517 { 585 {
518 // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles 586 // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles
519 // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2008/08_15pu.pdf 587 // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2008/08_15pu.pdf
588
589 SetupUidsFromOrthanc_1_9_3();
520 590
521 removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID 591 //uids_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID => set in SetupUidsFromOrthanc_1_9_3()
522 //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply() 592 //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply()
523 removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number 593 removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number
524 removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name 594 removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name
525 removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address 595 removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address
526 removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name 596 removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name
533 removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record 603 removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record
534 removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name 604 removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name
535 removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study 605 removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study
536 removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name 606 removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name
537 removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description 607 removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description
538 //removals_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID => RelationshipsVisitor 608 //uids_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID => set in SetupUidsFromOrthanc_1_9_3()
539 removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description 609 removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description
540 //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*) 610 //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*)
541 //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) 611 //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*)
542 removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date 612 removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date
543 removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time 613 removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time
555 removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number 625 removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number
556 removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name 626 removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name
557 //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply() 627 //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply()
558 //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply() 628 //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply()
559 removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID 629 removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID
560 //removals_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID => cf. RelationshipsVisitor 630 //uids_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID => set in SetupUidsFromOrthanc_1_9_3()
561 removals_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID 631 //uids_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID => set in SetupUidsFromOrthanc_1_9_3()
562 removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments 632 removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments
563 removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence 633 removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence
564 removals_.insert(DicomTag(0x0040, 0xa124)); // UID 634 //uids_.insert(DicomTag(0x0040, 0xa124)); // UID => set in SetupUidsFromOrthanc_1_9_3()
565 removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence 635 removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence
566 removals_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID 636 //uids_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID => set in SetupUidsFromOrthanc_1_9_3()
567 //removals_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID => RelationshipsVisitor 637 //uids_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID => set in SetupUidsFromOrthanc_1_9_3()
568 //removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID => RelationshipsVisitor 638 //uids_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID => set in SetupUidsFromOrthanc_1_9_3()
569 639
570 // Some more removals (from the experience of DICOM files at the CHU of Liege) 640 // Some more removals (from the experience of DICOM files at the CHU of Liege)
571 removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address 641 removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address
572 removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician 642 removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician
573 removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers 643 removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers
619 { 689 {
620 isAnonymization_ = true; 690 isAnonymization_ = true;
621 691
622 removals_.clear(); 692 removals_.clear();
623 clearings_.clear(); 693 clearings_.clear();
694 removedRanges_.clear();
695 uids_.clear();
624 ClearReplacements(); 696 ClearReplacements();
625 removePrivateTags_ = true; 697 removePrivateTags_ = true;
626 level_ = ResourceType_Patient; 698 level_ = ResourceType_Patient;
627 uidMap_.clear(); 699 uidMap_.clear();
628 privateTagsToKeep_.clear(); 700 privateTagsToKeep_.clear();
650 722
651 // (*) Choose a random patient name and ID 723 // (*) Choose a random patient name and ID
652 std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient); 724 std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient);
653 ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId); 725 ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId);
654 ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId); 726 ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId);
727
728 // Sanity check
729 for (SetOfTags::const_iterator it = uids_.begin(); it != uids_.end(); ++it)
730 {
731 ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(*it);
732 if (vr != ValueRepresentation_UniqueIdentifier &&
733 vr != ValueRepresentation_NotSupported /* for older versions of DCMTK */)
734 {
735 throw OrthancException(ErrorCode_InternalError);
736 }
737 }
655 } 738 }
656 739
657 void DicomModification::Apply(ParsedDicomFile& toModify) 740 void DicomModification::Apply(ParsedDicomFile& toModify)
658 { 741 {
659 // Check the request 742 // Check the request
1129 static const char* MAP_PATIENTS = "MapPatients"; 1212 static const char* MAP_PATIENTS = "MapPatients";
1130 static const char* MAP_STUDIES = "MapStudies"; 1213 static const char* MAP_STUDIES = "MapStudies";
1131 static const char* MAP_SERIES = "MapSeries"; 1214 static const char* MAP_SERIES = "MapSeries";
1132 static const char* MAP_INSTANCES = "MapInstances"; 1215 static const char* MAP_INSTANCES = "MapInstances";
1133 static const char* PRIVATE_CREATOR = "PrivateCreator"; // New in Orthanc 1.6.0 1216 static const char* PRIVATE_CREATOR = "PrivateCreator"; // New in Orthanc 1.6.0
1217 static const char* UIDS = "Uids"; // New in Orthanc 1.9.4
1218 static const char* REMOVED_RANGES = "RemovedRanges"; // New in Orthanc 1.9.4
1134 1219
1135 void DicomModification::Serialize(Json::Value& value) const 1220 void DicomModification::Serialize(Json::Value& value) const
1136 { 1221 {
1137 if (identifierGenerator_ != NULL) 1222 if (identifierGenerator_ != NULL)
1138 { 1223 {
1203 } 1288 }
1204 1289
1205 assert(tmp2 != NULL); 1290 assert(tmp2 != NULL);
1206 (*tmp2) [it->first.second] = it->second; 1291 (*tmp2) [it->first.second] = it->second;
1207 } 1292 }
1293
1294 // New in Orthanc 1.9.4
1295 SerializationToolbox::WriteSetOfTags(value, uids_, UIDS);
1296
1297 // New in Orthanc 1.9.4
1298 Json::Value ranges = Json::arrayValue;
1299
1300 for (RemovedRanges::const_iterator it = removedRanges_.begin(); it != removedRanges_.end(); ++it)
1301 {
1302 Json::Value item = Json::arrayValue;
1303 item.append(it->GetGroupFrom());
1304 item.append(it->GetGroupTo());
1305 item.append(it->GetElementFrom());
1306 item.append(it->GetElementTo());
1307 ranges.append(item);
1308 }
1309
1310 value[REMOVED_RANGES] = ranges;
1208 } 1311 }
1209 1312
1210 void DicomModification::UnserializeUidMap(ResourceType level, 1313 void DicomModification::UnserializeUidMap(ResourceType level,
1211 const Json::Value& serialized, 1314 const Json::Value& serialized,
1212 const char* field) 1315 const char* field)
1281 1384
1282 UnserializeUidMap(ResourceType_Patient, serialized, MAP_PATIENTS); 1385 UnserializeUidMap(ResourceType_Patient, serialized, MAP_PATIENTS);
1283 UnserializeUidMap(ResourceType_Study, serialized, MAP_STUDIES); 1386 UnserializeUidMap(ResourceType_Study, serialized, MAP_STUDIES);
1284 UnserializeUidMap(ResourceType_Series, serialized, MAP_SERIES); 1387 UnserializeUidMap(ResourceType_Series, serialized, MAP_SERIES);
1285 UnserializeUidMap(ResourceType_Instance, serialized, MAP_INSTANCES); 1388 UnserializeUidMap(ResourceType_Instance, serialized, MAP_INSTANCES);
1389
1390 // New in Orthanc 1.9.4
1391 if (serialized.isMember(UIDS)) // Backward compatibility with Orthanc <= 1.9.3
1392 {
1393 SerializationToolbox::ReadSetOfTags(uids_, serialized, UIDS);
1394 }
1395 else
1396 {
1397 SetupUidsFromOrthanc_1_9_3();
1398 }
1399
1400 // New in Orthanc 1.9.4
1401 removedRanges_.clear();
1402 if (serialized.isMember(REMOVED_RANGES)) // Backward compatibility with Orthanc <= 1.9.3
1403 {
1404 const Json::Value& ranges = serialized[REMOVED_RANGES];
1405
1406 if (ranges.type() != Json::arrayValue)
1407 {
1408 throw OrthancException(ErrorCode_BadFileFormat);
1409 }
1410 else
1411 {
1412 for (Json::Value::ArrayIndex i = 0; i < ranges.size(); i++)
1413 {
1414 if (ranges[i].type() != Json::arrayValue ||
1415 ranges[i].size() != 4 ||
1416 !ranges[i][0].isUInt() ||
1417 !ranges[i][1].isUInt() ||
1418 !ranges[i][2].isUInt() ||
1419 !ranges[i][3].isUInt())
1420 {
1421 throw OrthancException(ErrorCode_BadFileFormat);
1422 }
1423 else
1424 {
1425 Json::LargestUInt groupFrom = ranges[i][0].asUInt();
1426 Json::LargestUInt groupTo = ranges[i][1].asUInt();
1427 Json::LargestUInt elementFrom = ranges[i][2].asUInt();
1428 Json::LargestUInt elementTo = ranges[i][3].asUInt();
1429
1430 if (groupFrom > groupTo ||
1431 elementFrom > elementTo ||
1432 groupTo > 0xffffu ||
1433 elementTo > 0xffffu)
1434 {
1435 throw OrthancException(ErrorCode_BadFileFormat);
1436 }
1437 else
1438 {
1439 removedRanges_.push_back(DicomTagRange(groupFrom, groupTo, elementFrom, elementTo));
1440 }
1441 }
1442 }
1443 }
1444 }
1286 } 1445 }
1287 1446
1288 1447
1289 void DicomModification::SetPrivateCreator(const std::string &privateCreator) 1448 void DicomModification::SetPrivateCreator(const std::string &privateCreator)
1290 { 1449 {