comparison OrthancFramework/Sources/DicomParsing/DicomModification.cpp @ 4693:45bce660ce3a

added routes for bulk anonymization/modification
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 16 Jun 2021 16:44:04 +0200
parents a08ef46c95a1
children dd6274412ff4
comparison
equal deleted inserted replaced
4692:e68edf92e5cc 4693:45bce660ce3a
153 { 153 {
154 // We are on a first-level tag 154 // We are on a first-level tag
155 if (that_.uids_.find(tag) != that_.uids_.end() && 155 if (that_.uids_.find(tag) != that_.uids_.end() &&
156 !IsManuallyModified(tag)) 156 !IsManuallyModified(tag))
157 { 157 {
158 // This is a first-level UID tag that must be anonymized 158 if (tag == DICOM_TAG_PATIENT_ID ||
159 assert(vr == ValueRepresentation_UniqueIdentifier || 159 tag == DICOM_TAG_PATIENT_NAME)
160 vr == ValueRepresentation_NotSupported /* for older versions of DCMTK */); 160 {
161 newValue = that_.MapDicomIdentifier(value, ResourceType_Instance); 161 assert(vr == ValueRepresentation_LongString ||
162 vr == ValueRepresentation_PersonName);
163 newValue = that_.MapDicomIdentifier(value, ResourceType_Patient);
164 }
165 else
166 {
167 // This is a first-level UID tag that must be anonymized
168 assert(vr == ValueRepresentation_UniqueIdentifier ||
169 vr == ValueRepresentation_NotSupported /* for older versions of DCMTK */);
170 newValue = that_.MapDicomIdentifier(value, ResourceType_Instance);
171 }
172
162 return Action_Replace; 173 return Action_Replace;
163 } 174 }
164 else 175 else
165 { 176 {
166 return Action_None; 177 return Action_None;
200 newValue = that_.MapDicomIdentifier(value, ResourceType_Instance); 211 newValue = that_.MapDicomIdentifier(value, ResourceType_Instance);
201 return Action_Replace; 212 return Action_Replace;
202 } 213 }
203 else if (that_.uids_.find(tag) != that_.uids_.end()) 214 else if (that_.uids_.find(tag) != that_.uids_.end())
204 { 215 {
205 assert(vr == ValueRepresentation_UniqueIdentifier || 216 if (tag == DICOM_TAG_PATIENT_ID ||
206 vr == ValueRepresentation_NotSupported /* for older versions of DCMTK */); 217 tag == DICOM_TAG_PATIENT_NAME)
207
208 if (parentTags.size() == 2 &&
209 parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE &&
210 parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE &&
211 tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID)
212 { 218 {
213 /** 219 newValue = that_.MapDicomIdentifier(value, ResourceType_Patient);
214 * In RT-STRUCT, this ReferencedSOPInstanceUID is actually
215 * referencing a StudyInstanceUID !! (observed in many
216 * data sets including:
217 * https://wiki.cancerimagingarchive.net/display/Public/Lung+CT+Segmentation+Challenge+2017)
218 * Tested in "test_anonymize_relationships_5". Introduced
219 * in: https://hg.orthanc-server.com/orthanc/rev/3513
220 **/
221 newValue = that_.MapDicomIdentifier(value, ResourceType_Study);
222 } 220 }
223 else 221 else
224 { 222 {
225 newValue = that_.MapDicomIdentifier(value, ResourceType_Instance); 223 assert(vr == ValueRepresentation_UniqueIdentifier ||
224 vr == ValueRepresentation_NotSupported /* for older versions of DCMTK */);
225
226 if (parentTags.size() == 2 &&
227 parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE &&
228 parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE &&
229 tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID)
230 {
231 /**
232 * In RT-STRUCT, this ReferencedSOPInstanceUID is actually
233 * referencing a StudyInstanceUID !! (observed in many
234 * data sets including:
235 * https://wiki.cancerimagingarchive.net/display/Public/Lung+CT+Segmentation+Challenge+2017)
236 * Tested in "test_anonymize_relationships_5". Introduced
237 * in: https://hg.orthanc-server.com/orthanc/rev/3513
238 **/
239 newValue = that_.MapDicomIdentifier(value, ResourceType_Study);
240 }
241 else
242 {
243 newValue = that_.MapDicomIdentifier(value, ResourceType_Instance);
244 }
226 } 245 }
227 246
228 return Action_Replace; 247 return Action_Replace;
229 } 248 }
230 else 249 else
617 * DicomModification::RelationshipsVisitor::RemoveRelationships() 636 * DicomModification::RelationshipsVisitor::RemoveRelationships()
618 * https://hg.orthanc-server.com/orthanc/file/Orthanc-1.9.3/OrthancFramework/Sources/DicomParsing/DicomModification.cpp#l117 637 * https://hg.orthanc-server.com/orthanc/file/Orthanc-1.9.3/OrthancFramework/Sources/DicomParsing/DicomModification.cpp#l117
619 **/ 638 **/
620 uids_.clear(); 639 uids_.clear();
621 640
641 // (*) "PatientID" and "PatientName" are handled as UIDs since Orthanc 1.9.4
642 uids_.insert(DICOM_TAG_PATIENT_ID);
643 uids_.insert(DICOM_TAG_PATIENT_NAME);
644
622 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0008, 0x0014)); // Instance Creator UID <= from SetupAnonymization2008() 645 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0008, 0x0014)); // Instance Creator UID <= from SetupAnonymization2008()
623 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID <= from VisitString() + RemoveRelationships() 646 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID <= from VisitString() + RemoveRelationships()
624 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0020, 0x0052)); // Frame of Reference UID <= from VisitString() + RemoveRelationships() 647 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0020, 0x0052)); // Frame of Reference UID <= from VisitString() + RemoveRelationships()
625 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID <= from SetupAnonymization2008() 648 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID <= from SetupAnonymization2008()
626 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0040, 0xa124)); // UID <= from SetupAnonymization2008() 649 SetupUidsFromOrthancInternal(uids_, removals_, DicomTag(0x0040, 0xa124)); // UID <= from SetupAnonymization2008()
770 793
771 // Set the PatientIdentityRemoved tag 794 // Set the PatientIdentityRemoved tag
772 ReplaceInternal(DicomTag(0x0012, 0x0062), "YES"); 795 ReplaceInternal(DicomTag(0x0012, 0x0062), "YES");
773 796
774 // (*) Choose a random patient name and ID 797 // (*) Choose a random patient name and ID
775 std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient); 798 uids_.insert(DICOM_TAG_PATIENT_ID);
776 ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId); 799 uids_.insert(DICOM_TAG_PATIENT_NAME);
777 ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId);
778 800
779 // Sanity check 801 // Sanity check
780 for (SetOfTags::const_iterator it = uids_.begin(); it != uids_.end(); ++it) 802 for (SetOfTags::const_iterator it = uids_.begin(); it != uids_.end(); ++it)
781 { 803 {
782 ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(*it); 804 ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(*it);
783 if (vr != ValueRepresentation_UniqueIdentifier && 805 if (*it == DICOM_TAG_PATIENT_ID)
784 vr != ValueRepresentation_NotSupported /* for older versions of DCMTK */) 806 {
807 if (vr != ValueRepresentation_LongString)
808 {
809 throw OrthancException(ErrorCode_InternalError);
810 }
811 }
812 else if (*it == DICOM_TAG_PATIENT_NAME)
813 {
814 if (vr != ValueRepresentation_PersonName)
815 {
816 throw OrthancException(ErrorCode_InternalError);
817 }
818 }
819 else if (vr != ValueRepresentation_UniqueIdentifier &&
820 vr != ValueRepresentation_NotSupported /* for older versions of DCMTK */)
785 { 821 {
786 throw OrthancException(ErrorCode_InternalError); 822 throw OrthancException(ErrorCode_InternalError);
787 } 823 }
788 } 824 }
789 } 825 }
803 throw OrthancException(ErrorCode_BadRequest); 839 throw OrthancException(ErrorCode_BadRequest);
804 } 840 }
805 841
806 842
807 // Sanity checks at the patient level 843 // Sanity checks at the patient level
808 if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID)) 844 bool isReplacedPatientId = (IsReplaced(DICOM_TAG_PATIENT_ID) ||
845 uids_.find(DICOM_TAG_PATIENT_ID) != uids_.end());
846
847 if (level_ == ResourceType_Patient && !isReplacedPatientId)
809 { 848 {
810 throw OrthancException(ErrorCode_BadRequest, 849 throw OrthancException(ErrorCode_BadRequest,
811 "When modifying a patient, her PatientID is required to be modified"); 850 "When modifying a patient, her PatientID is required to be modified");
812 } 851 }
813 852
832 } 871 }
833 } 872 }
834 873
835 874
836 // Sanity checks at the study level 875 // Sanity checks at the study level
837 if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_PATIENT_ID)) 876 if (level_ == ResourceType_Study && isReplacedPatientId)
838 { 877 {
839 throw OrthancException(ErrorCode_BadRequest, 878 throw OrthancException(ErrorCode_BadRequest,
840 "When modifying a study, the parent PatientID cannot be manually modified"); 879 "When modifying a study, the parent PatientID cannot be manually modified");
841 } 880 }
842 881
855 } 894 }
856 } 895 }
857 896
858 897
859 // Sanity checks at the series level 898 // Sanity checks at the series level
860 if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_PATIENT_ID)) 899 if (level_ == ResourceType_Series && isReplacedPatientId)
861 { 900 {
862 throw OrthancException(ErrorCode_BadRequest, 901 throw OrthancException(ErrorCode_BadRequest,
863 "When modifying a series, the parent PatientID cannot be manually modified"); 902 "When modifying a series, the parent PatientID cannot be manually modified");
864 } 903 }
865 904
878 } 917 }
879 } 918 }
880 919
881 920
882 // Sanity checks at the instance level 921 // Sanity checks at the instance level
883 if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_PATIENT_ID)) 922 if (level_ == ResourceType_Instance && isReplacedPatientId)
884 { 923 {
885 throw OrthancException(ErrorCode_BadRequest, 924 throw OrthancException(ErrorCode_BadRequest,
886 "When modifying an instance, the parent PatientID cannot be manually modified"); 925 "When modifying an instance, the parent PatientID cannot be manually modified");
887 } 926 }
888 927
1201 privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator"); 1240 privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator");
1202 } 1241 }
1203 } 1242 }
1204 1243
1205 1244
1206 void DicomModification::ParseAnonymizationRequest(bool& patientNameReplaced, 1245 void DicomModification::ParseAnonymizationRequest(bool& patientNameOverridden,
1207 const Json::Value& request) 1246 const Json::Value& request)
1208 { 1247 {
1209 if (!request.isObject()) 1248 if (!request.isObject())
1210 { 1249 {
1211 throw OrthancException(ErrorCode_BadFileFormat); 1250 throw OrthancException(ErrorCode_BadFileFormat);
1228 } 1267 }
1229 } 1268 }
1230 1269
1231 SetupAnonymization(version); 1270 SetupAnonymization(version);
1232 1271
1233 std::string patientName = GetReplacementAsString(DICOM_TAG_PATIENT_NAME);
1234
1235 if (GetBooleanValue("KeepPrivateTags", request, false)) 1272 if (GetBooleanValue("KeepPrivateTags", request, false))
1236 { 1273 {
1237 SetRemovePrivateTags(false); 1274 SetRemovePrivateTags(false);
1238 } 1275 }
1239 1276
1250 if (request.isMember("Keep")) 1287 if (request.isMember("Keep"))
1251 { 1288 {
1252 ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force); 1289 ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force);
1253 } 1290 }
1254 1291
1255 patientNameReplaced = (IsReplaced(DICOM_TAG_PATIENT_NAME) && 1292 patientNameOverridden = (uids_.find(DICOM_TAG_PATIENT_NAME) == uids_.end());
1256 GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName); 1293
1257
1258 // New in Orthanc 1.6.0 1294 // New in Orthanc 1.6.0
1259 if (request.isMember("PrivateCreator")) 1295 if (request.isMember("PrivateCreator"))
1260 { 1296 {
1261 privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator"); 1297 privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator");
1262 } 1298 }