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 }