Mercurial > hg > orthanc
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 { |