Mercurial > hg > orthanc
comparison OrthancServer/OrthancRestApi.cpp @ 316:d526ac73c886
anonymization of studies and series
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 21 Dec 2012 17:27:16 +0100 |
parents | fc856d175d18 |
children | 6253c70b197b |
comparison
equal
deleted
inserted
replaced
315:fc856d175d18 | 316:d526ac73c886 |
---|---|
58 | 58 |
59 | 59 |
60 namespace Orthanc | 60 namespace Orthanc |
61 { | 61 { |
62 // TODO IMPROVE MULTITHREADING | 62 // TODO IMPROVE MULTITHREADING |
63 // Every call to "ParsedDicomFile" must lock this mutex!!! | |
63 static boost::mutex cacheMutex_; | 64 static boost::mutex cacheMutex_; |
64 | 65 |
65 | 66 |
66 // DICOM SCU ---------------------------------------------------------------- | 67 // DICOM SCU ---------------------------------------------------------------- |
67 | 68 |
1036 | 1037 |
1037 | 1038 |
1038 static bool ParseAnonymizationRequest(Removals& removals, | 1039 static bool ParseAnonymizationRequest(Removals& removals, |
1039 Replacements& replacements, | 1040 Replacements& replacements, |
1040 bool& removePrivateTags, | 1041 bool& removePrivateTags, |
1041 const RestApi::PostCall& call) | 1042 RestApi::PostCall& call) |
1042 { | 1043 { |
1044 RETRIEVE_CONTEXT(call); | |
1045 | |
1043 removePrivateTags = true; | 1046 removePrivateTags = true; |
1044 | 1047 |
1045 Json::Value request; | 1048 Json::Value request; |
1046 if (call.ParseJsonRequest(request) && | 1049 if (call.ParseJsonRequest(request) && |
1047 request.isObject()) | 1050 request.isObject()) |
1083 { | 1086 { |
1084 removals.erase(*it); | 1087 removals.erase(*it); |
1085 } | 1088 } |
1086 | 1089 |
1087 ParseReplacements(replacements, replacementsPart); | 1090 ParseReplacements(replacements, replacementsPart); |
1091 | |
1092 // Generate random Patient's Name if none is specified | |
1093 if (replacements.find(DicomTag(0x0010, 0x0010)) == replacements.end()) | |
1094 { | |
1095 replacements.insert(std::make_pair(DicomTag(0x0010, 0x0010), GeneratePatientName(context))); | |
1096 } | |
1097 | |
1098 // Generate random Patient's ID if none is specified | |
1099 if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end()) | |
1100 { | |
1101 replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, | |
1102 FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient))); | |
1103 } | |
1088 | 1104 |
1089 return true; | 1105 return true; |
1090 } | 1106 } |
1091 else | 1107 else |
1092 { | 1108 { |
1111 ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); | 1127 ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); |
1112 modified->Answer(call.GetOutput()); | 1128 modified->Answer(call.GetOutput()); |
1113 } | 1129 } |
1114 | 1130 |
1115 | 1131 |
1132 static void AnonymizeOrModifySeries(Removals removals, | |
1133 Replacements replacements, | |
1134 bool removePrivateTags, | |
1135 MetadataType metadataType, | |
1136 ChangeType changeType, | |
1137 RestApi::PostCall& call) | |
1138 { | |
1139 RETRIEVE_CONTEXT(call); | |
1140 | |
1141 boost::mutex::scoped_lock lock(cacheMutex_); | |
1142 | |
1143 typedef std::list<std::string> Instances; | |
1144 Instances instances; | |
1145 std::string id = call.GetUriComponent("id", ""); | |
1146 context.GetIndex().GetChildInstances(instances, id); | |
1147 | |
1148 if (instances.size() == 0) | |
1149 { | |
1150 return; | |
1151 } | |
1152 | |
1153 replacements[DICOM_TAG_SERIES_INSTANCE_UID] = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series); | |
1154 | |
1155 std::string newSeriesId; | |
1156 for (Instances::const_iterator it = instances.begin(); | |
1157 it != instances.end(); it++) | |
1158 { | |
1159 LOG(INFO) << "Modifying instance " << *it; | |
1160 ParsedDicomFile& original = context.GetDicomFile(*it); | |
1161 std::auto_ptr<ParsedDicomFile> modified(original.Clone()); | |
1162 ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); | |
1163 | |
1164 std::string modifiedInstance; | |
1165 if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success) | |
1166 { | |
1167 LOG(ERROR) << "Error while storing a modified instance " << *it; | |
1168 return; | |
1169 } | |
1170 | |
1171 DicomInstanceHasher modifiedHasher = modified->GetHasher(); | |
1172 DicomInstanceHasher originalHasher = original.GetHasher(); | |
1173 | |
1174 if (newSeriesId.size() == 0) | |
1175 { | |
1176 assert(id == originalHasher.HashSeries()); | |
1177 newSeriesId = modifiedHasher.HashSeries(); | |
1178 context.GetIndex().SetMetadata(newSeriesId, metadataType, id); | |
1179 } | |
1180 | |
1181 assert(*it == originalHasher.HashInstance()); | |
1182 assert(modifiedInstance == modifiedHasher.HashInstance()); | |
1183 context.GetIndex().SetMetadata(modifiedInstance, metadataType, *it); | |
1184 } | |
1185 | |
1186 context.GetIndex().LogChange(changeType, newSeriesId); | |
1187 | |
1188 assert(newSeriesId.size() != 0); | |
1189 Json::Value result = Json::objectValue; | |
1190 result["ID"] = newSeriesId; | |
1191 result["Path"] = GetBasePath(ResourceType_Series, newSeriesId); | |
1192 call.GetOutput().AnswerJson(result); | |
1193 } | |
1194 | |
1195 | |
1196 static void AnonymizeOrModifyStudy(Removals removals, | |
1197 Replacements replacements, | |
1198 bool removePrivateTags, | |
1199 MetadataType metadataType, | |
1200 ChangeType changeType, | |
1201 RestApi::PostCall& call) | |
1202 { | |
1203 RETRIEVE_CONTEXT(call); | |
1204 boost::mutex::scoped_lock lock(cacheMutex_); | |
1205 | |
1206 typedef std::list<std::string> Instances; | |
1207 typedef std::map<std::string, std::string> SeriesUidMap; | |
1208 | |
1209 Instances instances; | |
1210 std::string id = call.GetUriComponent("id", ""); | |
1211 context.GetIndex().GetChildInstances(instances, id); | |
1212 | |
1213 if (instances.size() == 0) | |
1214 { | |
1215 return; | |
1216 } | |
1217 | |
1218 std::string newStudyId; | |
1219 replacements[DICOM_TAG_STUDY_INSTANCE_UID] = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study); | |
1220 | |
1221 SeriesUidMap seriesUidMap; | |
1222 for (Instances::const_iterator it = instances.begin(); | |
1223 it != instances.end(); it++) | |
1224 { | |
1225 LOG(INFO) << "Modifying instance " << *it; | |
1226 ParsedDicomFile& original = context.GetDicomFile(*it); | |
1227 | |
1228 std::string seriesUid; | |
1229 if (!original.GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID)) | |
1230 { | |
1231 throw OrthancException(ErrorCode_InternalError); | |
1232 } | |
1233 | |
1234 bool isNewSeries; | |
1235 SeriesUidMap::const_iterator it2 = seriesUidMap.find(seriesUid); | |
1236 if (it2 == seriesUidMap.end()) | |
1237 { | |
1238 std::string newSeriesUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series); | |
1239 seriesUidMap[seriesUid] = newSeriesUid; | |
1240 replacements[DICOM_TAG_SERIES_INSTANCE_UID] = newSeriesUid; | |
1241 isNewSeries = true; | |
1242 } | |
1243 else | |
1244 { | |
1245 replacements[DICOM_TAG_SERIES_INSTANCE_UID] = it2->second; | |
1246 isNewSeries = false; | |
1247 } | |
1248 | |
1249 std::auto_ptr<ParsedDicomFile> modified(original.Clone()); | |
1250 ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); | |
1251 | |
1252 std::string modifiedInstance; | |
1253 if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success) | |
1254 { | |
1255 LOG(ERROR) << "Error while storing a modified instance " << *it; | |
1256 return; | |
1257 } | |
1258 | |
1259 DicomInstanceHasher modifiedHasher = modified->GetHasher(); | |
1260 DicomInstanceHasher originalHasher = original.GetHasher(); | |
1261 | |
1262 if (isNewSeries) | |
1263 { | |
1264 context.GetIndex().SetMetadata | |
1265 (modifiedHasher.HashSeries(), MetadataType_ModifiedFrom, originalHasher.HashSeries()); | |
1266 } | |
1267 | |
1268 if (newStudyId.size() == 0) | |
1269 { | |
1270 newStudyId = modifiedHasher.HashStudy(); | |
1271 context.GetIndex().SetMetadata(newStudyId, MetadataType_ModifiedFrom, originalHasher.HashStudy()); | |
1272 } | |
1273 | |
1274 assert(*it == originalHasher.HashInstance()); | |
1275 assert(modifiedInstance == modifiedHasher.HashInstance()); | |
1276 context.GetIndex().SetMetadata(modifiedInstance, MetadataType_ModifiedFrom, *it); | |
1277 } | |
1278 | |
1279 context.GetIndex().LogChange(ChangeType_ModifiedStudy, newStudyId); | |
1280 | |
1281 assert(newStudyId.size() != 0); | |
1282 Json::Value result = Json::objectValue; | |
1283 result["ID"] = newStudyId; | |
1284 result["Path"] = GetBasePath(ResourceType_Study, newStudyId); | |
1285 call.GetOutput().AnswerJson(result); | |
1286 } | |
1287 | |
1288 | |
1289 | |
1116 static void ModifyInstance(RestApi::PostCall& call) | 1290 static void ModifyInstance(RestApi::PostCall& call) |
1117 { | 1291 { |
1118 Removals removals; | 1292 Removals removals; |
1119 Replacements replacements; | 1293 Replacements replacements; |
1120 bool removePrivateTags; | 1294 bool removePrivateTags; |
1126 } | 1300 } |
1127 | 1301 |
1128 | 1302 |
1129 static void AnonymizeInstance(RestApi::PostCall& call) | 1303 static void AnonymizeInstance(RestApi::PostCall& call) |
1130 { | 1304 { |
1131 RETRIEVE_CONTEXT(call); | |
1132 | |
1133 Removals removals; | 1305 Removals removals; |
1134 Replacements replacements; | 1306 Replacements replacements; |
1135 bool removePrivateTags; | 1307 bool removePrivateTags; |
1136 | 1308 |
1137 if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call)) | 1309 if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call)) |
1138 { | 1310 { |
1139 // Generate random Patient's Name if none is specified | |
1140 if (replacements.find(DicomTag(0x0010, 0x0010)) == replacements.end()) | |
1141 { | |
1142 replacements.insert(std::make_pair(DicomTag(0x0010, 0x0010), GeneratePatientName(context))); | |
1143 } | |
1144 | |
1145 // Generate random Patient's ID if none is specified | |
1146 if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end()) | |
1147 { | |
1148 replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, | |
1149 FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient))); | |
1150 } | |
1151 | |
1152 AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); | 1311 AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); |
1153 } | 1312 } |
1154 } | 1313 } |
1155 | 1314 |
1156 | 1315 |
1157 static void ModifySeriesInplace(RestApi::PostCall& call) | 1316 static void ModifySeriesInplace(RestApi::PostCall& call) |
1158 { | 1317 { |
1159 RETRIEVE_CONTEXT(call); | |
1160 | |
1161 Removals removals; | 1318 Removals removals; |
1162 Replacements replacements; | 1319 Replacements replacements; |
1163 bool removePrivateTags; | 1320 bool removePrivateTags; |
1164 | 1321 |
1165 if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) | 1322 if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) |
1166 { | 1323 { |
1167 boost::mutex::scoped_lock lock(cacheMutex_); | 1324 AnonymizeOrModifySeries(removals, replacements, removePrivateTags, |
1168 | 1325 MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, call); |
1169 typedef std::list<std::string> Instances; | 1326 } |
1170 Instances instances; | 1327 } |
1171 std::string id = call.GetUriComponent("id", ""); | 1328 |
1172 context.GetIndex().GetChildInstances(instances, id); | 1329 |
1173 | 1330 static void AnonymizeSeriesInplace(RestApi::PostCall& call) |
1174 if (instances.size() == 0) | 1331 { |
1175 { | |
1176 return; | |
1177 } | |
1178 | |
1179 replacements[DICOM_TAG_SERIES_INSTANCE_UID] = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series); | |
1180 | |
1181 std::string newSeriesId; | |
1182 for (Instances::const_iterator it = instances.begin(); | |
1183 it != instances.end(); it++) | |
1184 { | |
1185 LOG(INFO) << "Modifying instance " << *it; | |
1186 ParsedDicomFile& original = context.GetDicomFile(*it); | |
1187 std::auto_ptr<ParsedDicomFile> modified(original.Clone()); | |
1188 ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); | |
1189 | |
1190 std::string modifiedInstance; | |
1191 if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success) | |
1192 { | |
1193 LOG(ERROR) << "Error while storing a modified instance " << *it; | |
1194 return; | |
1195 } | |
1196 | |
1197 DicomInstanceHasher modifiedHasher = modified->GetHasher(); | |
1198 DicomInstanceHasher originalHasher = original.GetHasher(); | |
1199 | |
1200 if (newSeriesId.size() == 0) | |
1201 { | |
1202 assert(id == originalHasher.HashSeries()); | |
1203 newSeriesId = modifiedHasher.HashSeries(); | |
1204 context.GetIndex().SetMetadata(newSeriesId, MetadataType_ModifiedFrom, id); | |
1205 } | |
1206 | |
1207 assert(*it == originalHasher.HashInstance()); | |
1208 assert(modifiedInstance == modifiedHasher.HashInstance()); | |
1209 context.GetIndex().SetMetadata(modifiedInstance, MetadataType_ModifiedFrom, *it); | |
1210 } | |
1211 | |
1212 context.GetIndex().LogChange(ChangeType_ModifiedSeries, newSeriesId); | |
1213 | |
1214 assert(newSeriesId.size() != 0); | |
1215 Json::Value result = Json::objectValue; | |
1216 result["ID"] = newSeriesId; | |
1217 result["Path"] = GetBasePath(ResourceType_Series, newSeriesId); | |
1218 call.GetOutput().AnswerJson(result); | |
1219 } | |
1220 } | |
1221 | |
1222 | |
1223 static void ModifyStudyInplace(RestApi::PostCall& call) | |
1224 { | |
1225 RETRIEVE_CONTEXT(call); | |
1226 | |
1227 typedef std::list<std::string> Instances; | |
1228 typedef std::map<std::string, std::string> SeriesUidMap; | |
1229 | |
1230 SeriesUidMap seriesUidMap; | |
1231 Removals removals; | 1332 Removals removals; |
1232 Replacements replacements; | 1333 Replacements replacements; |
1233 bool removePrivateTags; | 1334 bool removePrivateTags; |
1234 | 1335 |
1336 if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call)) | |
1337 { | |
1338 AnonymizeOrModifySeries(removals, replacements, removePrivateTags, | |
1339 MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries, call); | |
1340 } | |
1341 } | |
1342 | |
1343 | |
1344 static void ModifyStudyInplace(RestApi::PostCall& call) | |
1345 { | |
1346 Removals removals; | |
1347 Replacements replacements; | |
1348 bool removePrivateTags; | |
1349 | |
1235 if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) | 1350 if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) |
1236 { | 1351 { |
1237 boost::mutex::scoped_lock lock(cacheMutex_); | 1352 AnonymizeOrModifyStudy(removals, replacements, removePrivateTags, |
1238 | 1353 MetadataType_ModifiedFrom, ChangeType_ModifiedStudy, call); |
1239 Instances instances; | 1354 } |
1240 std::string id = call.GetUriComponent("id", ""); | 1355 } |
1241 context.GetIndex().GetChildInstances(instances, id); | 1356 |
1242 | 1357 |
1243 if (instances.size() == 0) | 1358 static void AnonymizeStudyInplace(RestApi::PostCall& call) |
1244 { | 1359 { |
1245 return; | 1360 Removals removals; |
1246 } | 1361 Replacements replacements; |
1247 | 1362 bool removePrivateTags; |
1248 std::string newStudyId; | 1363 |
1249 replacements[DICOM_TAG_STUDY_INSTANCE_UID] = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study); | 1364 if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, call)) |
1250 | 1365 { |
1251 for (Instances::const_iterator it = instances.begin(); | 1366 AnonymizeOrModifyStudy(removals, replacements, removePrivateTags, |
1252 it != instances.end(); it++) | 1367 MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy, call); |
1253 { | 1368 } |
1254 LOG(INFO) << "Modifying instance " << *it; | 1369 } |
1255 ParsedDicomFile& original = context.GetDicomFile(*it); | |
1256 | |
1257 std::string seriesUid; | |
1258 if (!original.GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID)) | |
1259 { | |
1260 throw OrthancException(ErrorCode_InternalError); | |
1261 } | |
1262 | |
1263 bool isNewSeries; | |
1264 SeriesUidMap::const_iterator it2 = seriesUidMap.find(seriesUid); | |
1265 if (it2 == seriesUidMap.end()) | |
1266 { | |
1267 std::string newSeriesUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series); | |
1268 seriesUidMap[seriesUid] = newSeriesUid; | |
1269 replacements[DICOM_TAG_SERIES_INSTANCE_UID] = newSeriesUid; | |
1270 isNewSeries = true; | |
1271 } | |
1272 else | |
1273 { | |
1274 replacements[DICOM_TAG_SERIES_INSTANCE_UID] = it2->second; | |
1275 isNewSeries = false; | |
1276 } | |
1277 | |
1278 std::auto_ptr<ParsedDicomFile> modified(original.Clone()); | |
1279 ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent, removePrivateTags); | |
1280 | |
1281 std::string modifiedInstance; | |
1282 if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success) | |
1283 { | |
1284 LOG(ERROR) << "Error while storing a modified instance " << *it; | |
1285 return; | |
1286 } | |
1287 | |
1288 DicomInstanceHasher modifiedHasher = modified->GetHasher(); | |
1289 DicomInstanceHasher originalHasher = original.GetHasher(); | |
1290 | |
1291 if (isNewSeries) | |
1292 { | |
1293 context.GetIndex().SetMetadata | |
1294 (modifiedHasher.HashSeries(), MetadataType_ModifiedFrom, originalHasher.HashSeries()); | |
1295 } | |
1296 | |
1297 if (newStudyId.size() == 0) | |
1298 { | |
1299 newStudyId = modifiedHasher.HashStudy(); | |
1300 context.GetIndex().SetMetadata(newStudyId, MetadataType_ModifiedFrom, originalHasher.HashStudy()); | |
1301 } | |
1302 | |
1303 assert(*it == originalHasher.HashInstance()); | |
1304 assert(modifiedInstance == modifiedHasher.HashInstance()); | |
1305 context.GetIndex().SetMetadata(modifiedInstance, MetadataType_ModifiedFrom, *it); | |
1306 } | |
1307 | |
1308 context.GetIndex().LogChange(ChangeType_ModifiedStudy, newStudyId); | |
1309 | |
1310 assert(newStudyId.size() != 0); | |
1311 Json::Value result = Json::objectValue; | |
1312 result["ID"] = newStudyId; | |
1313 result["Path"] = GetBasePath(ResourceType_Study, newStudyId); | |
1314 call.GetOutput().AnswerJson(result); | |
1315 } | |
1316 } | |
1317 | |
1318 | 1370 |
1319 | 1371 |
1320 | 1372 |
1321 // Registration of the various REST handlers -------------------------------- | 1373 // Registration of the various REST handlers -------------------------------- |
1322 | 1374 |
1376 Register("/instances/{id}/modify", ModifyInstance); | 1428 Register("/instances/{id}/modify", ModifyInstance); |
1377 Register("/series/{id}/modify", ModifySeriesInplace); | 1429 Register("/series/{id}/modify", ModifySeriesInplace); |
1378 Register("/studies/{id}/modify", ModifyStudyInplace); | 1430 Register("/studies/{id}/modify", ModifyStudyInplace); |
1379 | 1431 |
1380 Register("/instances/{id}/anonymize", AnonymizeInstance); | 1432 Register("/instances/{id}/anonymize", AnonymizeInstance); |
1433 Register("/series/{id}/anonymize", AnonymizeSeriesInplace); | |
1434 Register("/studies/{id}/anonymize", AnonymizeStudyInplace); | |
1381 } | 1435 } |
1382 } | 1436 } |