comparison OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp @ 787:ac18946afa74

refactoring of anonymization/modification
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 05 May 2014 15:52:14 +0200
parents a60040857ce6
children 7ebe4bf87196
comparison
equal deleted inserted replaced
786:b6d6b65142e8 787:ac18946afa74
30 **/ 30 **/
31 31
32 32
33 #include "OrthancRestApi.h" 33 #include "OrthancRestApi.h"
34 34
35 #include "../DicomModification.h"
36
35 #include <glog/logging.h> 37 #include <glog/logging.h>
36 38
37 namespace Orthanc 39 namespace Orthanc
38 { 40 {
39 // Modification of DICOM instances ------------------------------------------ 41 // Modification of DICOM instances ------------------------------------------
40 42
41 namespace 43 enum TagOperation
42 { 44 {
43 typedef std::set<DicomTag> Removals; 45 TagOperation_Keep,
44 typedef std::map<DicomTag, std::string> Replacements; 46 TagOperation_Remove
45 typedef std::map< std::pair<DicomRootLevel, std::string>, std::string> UidMap; 47 };
46 } 48
47 49 static void ParseListOfTags(DicomModification& target,
48 static void ReplaceInstanceInternal(ParsedDicomFile& toModify, 50 const Json::Value& query,
49 const Removals& removals, 51 TagOperation operation)
50 const Replacements& replacements, 52 {
51 bool removePrivateTags) 53 if (!query.isArray())
52 {
53 if (removePrivateTags)
54 {
55 toModify.RemovePrivateTags();
56 }
57
58 for (Removals::const_iterator it = removals.begin();
59 it != removals.end(); ++it)
60 {
61 toModify.Remove(*it);
62 }
63
64 for (Replacements::const_iterator it = replacements.begin();
65 it != replacements.end(); ++it)
66 {
67 toModify.Replace(it->first, it->second, DicomReplaceMode_InsertIfAbsent);
68 }
69
70 // A new SOP instance UID is automatically generated
71 std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance);
72 toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, DicomReplaceMode_InsertIfAbsent);
73 }
74
75
76 static void ParseRemovals(Removals& target,
77 const Json::Value& removals)
78 {
79 if (!removals.isArray())
80 { 54 {
81 throw OrthancException(ErrorCode_BadRequest); 55 throw OrthancException(ErrorCode_BadRequest);
82 } 56 }
83 57
84 for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++) 58 for (Json::Value::ArrayIndex i = 0; i < query.size(); i++)
85 { 59 {
86 std::string name = removals[i].asString(); 60 std::string name = query[i].asString();
61
87 DicomTag tag = FromDcmtkBridge::ParseTag(name); 62 DicomTag tag = FromDcmtkBridge::ParseTag(name);
88 target.insert(tag); 63
89 64 switch (operation)
90 VLOG(1) << "Removal: " << name << " " << tag << std::endl; 65 {
91 } 66 case TagOperation_Keep:
92 } 67 target.Keep(tag);
93 68 VLOG(1) << "Keep: " << name << " " << tag << std::endl;
94 69 break;
95 static void ParseReplacements(Replacements& target, 70
71 case TagOperation_Remove:
72 target.Remove(tag);
73 VLOG(1) << "Remove: " << name << " " << tag << std::endl;
74 break;
75
76 default:
77 throw OrthancException(ErrorCode_InternalError);
78 }
79 }
80 }
81
82
83 static void ParseReplacements(DicomModification& target,
96 const Json::Value& replacements) 84 const Json::Value& replacements)
97 { 85 {
98 if (!replacements.isObject()) 86 if (!replacements.isObject())
99 { 87 {
100 throw OrthancException(ErrorCode_BadRequest); 88 throw OrthancException(ErrorCode_BadRequest);
105 { 93 {
106 const std::string& name = members[i]; 94 const std::string& name = members[i];
107 std::string value = replacements[name].asString(); 95 std::string value = replacements[name].asString();
108 96
109 DicomTag tag = FromDcmtkBridge::ParseTag(name); 97 DicomTag tag = FromDcmtkBridge::ParseTag(name);
110 target[tag] = value; 98 target.Replace(tag, value);
111 99
112 VLOG(1) << "Replacement: " << name << " " << tag << " == " << value << std::endl; 100 VLOG(1) << "Replace: " << name << " " << tag << " == " << value << std::endl;
113 } 101 }
114 } 102 }
115 103
116 104
117 static std::string GeneratePatientName(ServerContext& context) 105 static std::string GeneratePatientName(ServerContext& context)
119 uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence); 107 uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence);
120 return "Anonymized" + boost::lexical_cast<std::string>(seq); 108 return "Anonymized" + boost::lexical_cast<std::string>(seq);
121 } 109 }
122 110
123 111
124 static void SetupAnonymization(Removals& removals, 112 static bool ParseModifyRequest(DicomModification& target,
125 Replacements& replacements)
126 {
127 // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles
128 removals.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID
129 //removals.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set by ReplaceInstanceInternal()
130 removals.insert(DicomTag(0x0008, 0x0050)); // Accession Number
131 removals.insert(DicomTag(0x0008, 0x0080)); // Institution Name
132 removals.insert(DicomTag(0x0008, 0x0081)); // Institution Address
133 removals.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name
134 removals.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address
135 removals.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers
136 removals.insert(DicomTag(0x0008, 0x1010)); // Station Name
137 removals.insert(DicomTag(0x0008, 0x1030)); // Study Description
138 removals.insert(DicomTag(0x0008, 0x103e)); // Series Description
139 removals.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name
140 removals.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record
141 removals.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name
142 removals.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study
143 removals.insert(DicomTag(0x0008, 0x1070)); // Operators' Name
144 removals.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description
145 removals.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID
146 removals.insert(DicomTag(0x0008, 0x2111)); // Derivation Description
147 removals.insert(DicomTag(0x0010, 0x0010)); // Patient's Name
148 //removals.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*)
149 removals.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date
150 removals.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time
151 removals.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex
152 removals.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids
153 removals.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names
154 removals.insert(DicomTag(0x0010, 0x1010)); // Patient's Age
155 removals.insert(DicomTag(0x0010, 0x1020)); // Patient's Size
156 removals.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight
157 removals.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator
158 removals.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group
159 removals.insert(DicomTag(0x0010, 0x2180)); // Occupation
160 removals.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History
161 removals.insert(DicomTag(0x0010, 0x4000)); // Patient Comments
162 removals.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number
163 removals.insert(DicomTag(0x0018, 0x1030)); // Protocol Name
164 //removals.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => cf. below (*)
165 //removals.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => cf. below (*)
166 removals.insert(DicomTag(0x0020, 0x0010)); // Study ID
167 removals.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID
168 removals.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID
169 removals.insert(DicomTag(0x0020, 0x4000)); // Image Comments
170 removals.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence
171 removals.insert(DicomTag(0x0040, 0xa124)); // UID
172 removals.insert(DicomTag(0x0040, 0xa730)); // Content Sequence
173 removals.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID
174 removals.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID
175 removals.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID
176
177 /**
178 * (*) Patient ID, Study Instance UID and Series Instance UID
179 * are modified by "AnonymizeInstance()" if anonymizing a single
180 * instance, or by "RetrieveMappedUid()" if anonymizing a
181 * patient/study/series.
182 **/
183
184
185 // Some more removals (from the experience of DICOM files at the CHU of Liege)
186 removals.insert(DicomTag(0x0010, 0x1040)); // Patient's Address
187 removals.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician
188 removals.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers
189 removals.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts
190
191 // Set the DeidentificationMethod tag
192 replacements.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"));
193
194 // Set the PatientIdentityRemoved tag
195 replacements.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES"));
196 }
197
198
199 static bool ParseModifyRequest(Removals& removals,
200 Replacements& replacements,
201 bool& removePrivateTags,
202 const RestApi::PostCall& call) 113 const RestApi::PostCall& call)
203 { 114 {
204 removePrivateTags = false; 115 // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"}}'
116
205 Json::Value request; 117 Json::Value request;
206 if (call.ParseJsonRequest(request) && 118 if (call.ParseJsonRequest(request) && request.isObject())
207 request.isObject()) 119 {
208 { 120 if (request.isMember("RemovePrivateTags"))
209 Json::Value removalsPart = Json::arrayValue; 121 {
210 Json::Value replacementsPart = Json::objectValue; 122 target.SetRemovePrivateTags(true);
123 }
211 124
212 if (request.isMember("Remove")) 125 if (request.isMember("Remove"))
213 { 126 {
214 removalsPart = request["Remove"]; 127 ParseListOfTags(target, request["Remove"], TagOperation_Remove);
215 } 128 }
216 129
217 if (request.isMember("Replace")) 130 if (request.isMember("Replace"))
218 { 131 {
219 replacementsPart = request["Replace"]; 132 ParseReplacements(target, request["Replace"]);
220 } 133 }
221
222 if (request.isMember("RemovePrivateTags"))
223 {
224 removePrivateTags = true;
225 }
226
227 ParseRemovals(removals, removalsPart);
228 ParseReplacements(replacements, replacementsPart);
229 134
230 return true; 135 return true;
231 } 136 }
232 else 137 else
233 { 138 {
234 return false; 139 return false;
235 } 140 }
236 } 141 }
237 142
238 143
239 static bool ParseAnonymizationRequest(Removals& removals, 144 static bool ParseAnonymizationRequest(DicomModification& target,
240 Replacements& replacements,
241 bool& removePrivateTags,
242 bool& keepPatientId,
243 RestApi::PostCall& call) 145 RestApi::PostCall& call)
244 { 146 {
245 ServerContext& context = OrthancRestApi::GetContext(call); 147 // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": null,"Remove":["Modality"]}' > Anonymized.dcm
246 148
247 removePrivateTags = true; 149 target.SetupAnonymization();
248 keepPatientId = false; 150 std::string patientName = target.GetReplacement(DICOM_TAG_PATIENT_NAME);
249 151
250 Json::Value request; 152 Json::Value request;
251 if (call.ParseJsonRequest(request) && 153 if (call.ParseJsonRequest(request) && request.isObject())
252 request.isObject()) 154 {
253 { 155 if (request.isMember("KeepPrivateTags"))
254 Json::Value keepPart = Json::arrayValue; 156 {
255 Json::Value removalsPart = Json::arrayValue; 157 target.SetRemovePrivateTags(false);
256 Json::Value replacementsPart = Json::objectValue; 158 }
159
160 if (request.isMember("Remove"))
161 {
162 ParseListOfTags(target, request["Remove"], TagOperation_Remove);
163 }
164
165 if (request.isMember("Replace"))
166 {
167 ParseReplacements(target, request["Replace"]);
168 }
257 169
258 if (request.isMember("Keep")) 170 if (request.isMember("Keep"))
259 { 171 {
260 keepPart = request["Keep"]; 172 ParseListOfTags(target, request["Keep"], TagOperation_Keep);
261 } 173 }
262 174
263 if (request.isMember("KeepPrivateTags")) 175 if (target.GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName)
264 { 176 {
265 removePrivateTags = false; 177 // Overwrite the random Patient's Name by one that is more
266 } 178 // user-friendly (provided none was specified by the user)
267 179 target.Replace(DICOM_TAG_PATIENT_NAME, GeneratePatientName(OrthancRestApi::GetContext(call)));
268 if (request.isMember("Replace"))
269 {
270 replacementsPart = request["Replace"];
271 }
272
273 Removals toKeep;
274 ParseRemovals(toKeep, keepPart);
275
276 SetupAnonymization(removals, replacements);
277
278 for (Removals::iterator it = toKeep.begin(); it != toKeep.end(); ++it)
279 {
280 if (*it == DICOM_TAG_PATIENT_ID)
281 {
282 keepPatientId = true;
283 }
284
285 removals.erase(*it);
286 }
287
288 Removals additionalRemovals;
289 ParseRemovals(additionalRemovals, removalsPart);
290
291 for (Removals::iterator it = additionalRemovals.begin();
292 it != additionalRemovals.end(); ++it)
293 {
294 removals.insert(*it);
295 }
296
297 ParseReplacements(replacements, replacementsPart);
298
299 // Generate random Patient's Name if none is specified
300 if (toKeep.find(DICOM_TAG_PATIENT_NAME) == toKeep.end() &&
301 replacements.find(DICOM_TAG_PATIENT_NAME) == replacements.end())
302 {
303 replacements.insert(std::make_pair(DICOM_TAG_PATIENT_NAME, GeneratePatientName(context)));
304 } 180 }
305 181
306 return true; 182 return true;
307 } 183 }
308 else 184 else
310 return false; 186 return false;
311 } 187 }
312 } 188 }
313 189
314 190
315 static void AnonymizeOrModifyInstance(Removals& removals, 191 static void AnonymizeOrModifyInstance(DicomModification& modification,
316 Replacements& replacements,
317 bool removePrivateTags,
318 RestApi::PostCall& call) 192 RestApi::PostCall& call)
319 { 193 {
320 std::string id = call.GetUriComponent("id", ""); 194 std::string id = call.GetUriComponent("id", "");
321 195
322 ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id); 196 ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id);
323 197
324 std::auto_ptr<ParsedDicomFile> modified(locker.GetDicom().Clone()); 198 std::auto_ptr<ParsedDicomFile> modified(locker.GetDicom().Clone());
325 ReplaceInstanceInternal(*modified, removals, replacements, removePrivateTags); 199 modification.Apply(*modified);
326 modified->Answer(call.GetOutput()); 200 modified->Answer(call.GetOutput());
327 } 201 }
328 202
329 203
330 static bool RetrieveMappedUid(ParsedDicomFile& dicom, 204 static void AnonymizeOrModifyResource(DicomModification& modification,
331 DicomRootLevel level,
332 Replacements& replacements,
333 UidMap& uidMap)
334 {
335 std::auto_ptr<DicomTag> tag;
336
337 switch (level)
338 {
339 case DicomRootLevel_Series:
340 tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID));
341 break;
342
343 case DicomRootLevel_Study:
344 tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID));
345 break;
346
347 case DicomRootLevel_Patient:
348 tag.reset(new DicomTag(DICOM_TAG_PATIENT_ID));
349 break;
350
351 default:
352 throw OrthancException(ErrorCode_InternalError);
353 }
354
355 std::string original;
356 if (!dicom.GetTagValue(original, *tag))
357 {
358 throw OrthancException(ErrorCode_InternalError);
359 }
360
361 std::string mapped;
362 bool isNew;
363
364 UidMap::const_iterator previous = uidMap.find(std::make_pair(level, original));
365 if (previous == uidMap.end())
366 {
367 mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level);
368 uidMap.insert(std::make_pair(std::make_pair(level, original), mapped));
369 isNew = true;
370 }
371 else
372 {
373 mapped = previous->second;
374 isNew = false;
375 }
376
377 replacements[*tag] = mapped;
378 return isNew;
379 }
380
381
382 static void AnonymizeOrModifyResource(Removals& removals,
383 Replacements& replacements,
384 bool removePrivateTags,
385 bool keepPatientId,
386 MetadataType metadataType, 205 MetadataType metadataType,
387 ChangeType changeType, 206 ChangeType changeType,
388 ResourceType resourceType, 207 ResourceType resourceType,
389 RestApi::PostCall& call) 208 RestApi::PostCall& call)
390 { 209 {
391 typedef std::list<std::string> Instances;
392
393 bool isFirst = true; 210 bool isFirst = true;
394 Json::Value result(Json::objectValue); 211 Json::Value result(Json::objectValue);
395 212
396 ServerContext& context = OrthancRestApi::GetContext(call); 213 ServerContext& context = OrthancRestApi::GetContext(call);
397 214
215 typedef std::list<std::string> Instances;
398 Instances instances; 216 Instances instances;
399 std::string id = call.GetUriComponent("id", ""); 217 std::string id = call.GetUriComponent("id", "");
400 context.GetIndex().GetChildInstances(instances, id); 218 context.GetIndex().GetChildInstances(instances, id);
401 219
402 if (instances.empty()) 220 if (instances.empty())
403 { 221 {
404 return; 222 return;
405 } 223 }
406 224
225
407 /** 226 /**
408 * Loop over all the instances of the resource. 227 * Loop over all the instances of the resource.
409 **/ 228 **/
410 229
411 UidMap uidMap;
412 for (Instances::const_iterator it = instances.begin(); 230 for (Instances::const_iterator it = instances.begin();
413 it != instances.end(); ++it) 231 it != instances.end(); ++it)
414 { 232 {
415 LOG(INFO) << "Modifying instance " << *it; 233 LOG(INFO) << "Modifying instance " << *it;
416 234
425 // This child instance has been removed in between 243 // This child instance has been removed in between
426 continue; 244 continue;
427 } 245 }
428 246
429 ParsedDicomFile& original = locker->GetDicom(); 247 ParsedDicomFile& original = locker->GetDicom();
430
431 DicomInstanceHasher originalHasher = original.GetHasher(); 248 DicomInstanceHasher originalHasher = original.GetHasher();
432
433 if (isFirst && keepPatientId)
434 {
435 std::string patientId = originalHasher.GetPatientId();
436 uidMap[std::make_pair(DicomRootLevel_Patient, patientId)] = patientId;
437 }
438
439 bool isNewSeries = RetrieveMappedUid(original, DicomRootLevel_Series, replacements, uidMap);
440 bool isNewStudy = RetrieveMappedUid(original, DicomRootLevel_Study, replacements, uidMap);
441 bool isNewPatient = RetrieveMappedUid(original, DicomRootLevel_Patient, replacements, uidMap);
442 249
443 250
444 /** 251 /**
445 * Compute the resulting DICOM instance and store it into the Orthanc store. 252 * Compute the resulting DICOM instance and store it into the Orthanc store.
446 **/ 253 **/
447 254
448 std::auto_ptr<ParsedDicomFile> modified(original.Clone()); 255 std::auto_ptr<ParsedDicomFile> modified(original.Clone());
449 ReplaceInstanceInternal(*modified, removals, replacements, removePrivateTags); 256 modification.Apply(*modified);
450 257
451 std::string modifiedInstance; 258 std::string modifiedInstance;
452 if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success) 259 if (context.Store(modifiedInstance, modified->GetDicom()) != StoreStatus_Success)
453 { 260 {
454 LOG(ERROR) << "Error while storing a modified instance " << *it; 261 LOG(ERROR) << "Error while storing a modified instance " << *it;
460 * Record metadata information (AnonymizedFrom/ModifiedFrom). 267 * Record metadata information (AnonymizedFrom/ModifiedFrom).
461 **/ 268 **/
462 269
463 DicomInstanceHasher modifiedHasher = modified->GetHasher(); 270 DicomInstanceHasher modifiedHasher = modified->GetHasher();
464 271
465 if (isNewSeries) 272 if (originalHasher.HashSeries() != modifiedHasher.HashSeries())
466 { 273 {
467 context.GetIndex().SetMetadata(modifiedHasher.HashSeries(), 274 context.GetIndex().SetMetadata(modifiedHasher.HashSeries(),
468 metadataType, originalHasher.HashSeries()); 275 metadataType, originalHasher.HashSeries());
469 } 276 }
470 277
471 if (isNewStudy) 278 if (originalHasher.HashStudy() != modifiedHasher.HashStudy())
472 { 279 {
473 context.GetIndex().SetMetadata(modifiedHasher.HashStudy(), 280 context.GetIndex().SetMetadata(modifiedHasher.HashStudy(),
474 metadataType, originalHasher.HashStudy()); 281 metadataType, originalHasher.HashStudy());
475 } 282 }
476 283
477 if (isNewPatient) 284 if (originalHasher.HashPatient() != modifiedHasher.HashPatient())
478 { 285 {
479 context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), 286 context.GetIndex().SetMetadata(modifiedHasher.HashPatient(),
480 metadataType, originalHasher.HashPatient()); 287 metadataType, originalHasher.HashPatient());
481 } 288 }
482 289
524 331
525 332
526 333
527 static void ModifyInstance(RestApi::PostCall& call) 334 static void ModifyInstance(RestApi::PostCall& call)
528 { 335 {
529 Removals removals; 336 DicomModification modification;
530 Replacements replacements; 337
531 bool removePrivateTags; 338 // TODO : modification.SetLevel(DicomRootLevel_Series); ?????
532 339
533 if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) 340 if (ParseModifyRequest(modification, call))
534 { 341 {
535 AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); 342 AnonymizeOrModifyInstance(modification, call);
536 } 343 }
537 } 344 }
538 345
539 346
540 static void AnonymizeInstance(RestApi::PostCall& call) 347 static void AnonymizeInstance(RestApi::PostCall& call)
541 { 348 {
542 Removals removals; 349 DicomModification modification;
543 Replacements replacements; 350
544 bool removePrivateTags, keepPatientId; 351 if (ParseAnonymizationRequest(modification, call))
545 352 {
546 if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call)) 353 AnonymizeOrModifyInstance(modification, call);
547 { 354 }
548 // TODO Handle "keepPatientId" 355 }
549 356
550 // Generate random patient ID if not specified 357
551 if (replacements.find(DICOM_TAG_PATIENT_ID) == replacements.end()) 358 template <enum ChangeType changeType,
552 { 359 enum ResourceType resourceType>
553 replacements.insert(std::make_pair(DICOM_TAG_PATIENT_ID, 360 static void ModifyResource(RestApi::PostCall& call)
554 FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient))); 361 {
555 } 362 DicomModification modification;
556 363
557 // Generate random study UID if not specified 364 switch (resourceType)
558 if (replacements.find(DICOM_TAG_STUDY_INSTANCE_UID) == replacements.end()) 365 {
559 { 366 case ResourceType_Series:
560 replacements.insert(std::make_pair(DICOM_TAG_STUDY_INSTANCE_UID, 367 modification.SetLevel(DicomRootLevel_Series);
561 FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study))); 368 break;
562 } 369
563 370 case ResourceType_Study:
564 // Generate random series UID if not specified 371 modification.SetLevel(DicomRootLevel_Study);
565 if (replacements.find(DICOM_TAG_SERIES_INSTANCE_UID) == replacements.end()) 372 break;
566 { 373
567 replacements.insert(std::make_pair(DICOM_TAG_SERIES_INSTANCE_UID, 374 case ResourceType_Patient:
568 FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series))); 375 modification.SetLevel(DicomRootLevel_Patient);
569 } 376 break;
570 377
571 AnonymizeOrModifyInstance(removals, replacements, removePrivateTags, call); 378 default:
572 } 379 throw OrthancException(ErrorCode_InternalError);
573 } 380 }
574 381
575 382 if (ParseModifyRequest(modification, call))
576 static void ModifySeriesInplace(RestApi::PostCall& call) 383 {
577 { 384 AnonymizeOrModifyResource(modification, MetadataType_ModifiedFrom,
578 Removals removals; 385 changeType, resourceType, call);
579 Replacements replacements; 386 }
580 bool removePrivateTags; 387 }
581 388
582 if (ParseModifyRequest(removals, replacements, removePrivateTags, call)) 389
583 { 390 template <enum ChangeType changeType,
584 AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/, 391 enum ResourceType resourceType>
585 MetadataType_ModifiedFrom, ChangeType_ModifiedSeries, 392 static void AnonymizeResource(RestApi::PostCall& call)
586 ResourceType_Series, call); 393 {
587 } 394 DicomModification modification;
588 } 395
589 396 if (ParseAnonymizationRequest(modification, call))
590 397 {
591 static void AnonymizeSeriesInplace(RestApi::PostCall& call) 398 AnonymizeOrModifyResource(modification, MetadataType_AnonymizedFrom,
592 { 399 changeType, resourceType, call);
593 Removals removals; 400 }
594 Replacements replacements; 401 }
595 bool removePrivateTags, keepPatientId;
596
597 if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call))
598 {
599 AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId,
600 MetadataType_AnonymizedFrom, ChangeType_AnonymizedSeries,
601 ResourceType_Series, call);
602 }
603 }
604
605
606 static void ModifyStudyInplace(RestApi::PostCall& call)
607 {
608 Removals removals;
609 Replacements replacements;
610 bool removePrivateTags;
611
612 if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
613 {
614 AnonymizeOrModifyResource(removals, replacements, removePrivateTags, true /*keepPatientId*/,
615 MetadataType_ModifiedFrom, ChangeType_ModifiedStudy,
616 ResourceType_Study, call);
617 }
618 }
619
620
621 static void AnonymizeStudyInplace(RestApi::PostCall& call)
622 {
623 Removals removals;
624 Replacements replacements;
625 bool removePrivateTags, keepPatientId;
626
627 if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call))
628 {
629 AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId,
630 MetadataType_AnonymizedFrom, ChangeType_AnonymizedStudy,
631 ResourceType_Study, call);
632 }
633 }
634
635
636 /*static void ModifyPatientInplace(RestApi::PostCall& call)
637 {
638 Removals removals;
639 Replacements replacements;
640 bool removePrivateTags;
641
642 if (ParseModifyRequest(removals, replacements, removePrivateTags, call))
643 {
644 AnonymizeOrModifyResource(false, removals, replacements, removePrivateTags,
645 MetadataType_ModifiedFrom, ChangeType_ModifiedPatient,
646 ResourceType_Patient, call);
647 }
648 }*/
649
650
651 static void AnonymizePatientInplace(RestApi::PostCall& call)
652 {
653 Removals removals;
654 Replacements replacements;
655 bool removePrivateTags, keepPatientId;
656
657 if (ParseAnonymizationRequest(removals, replacements, removePrivateTags, keepPatientId, call))
658 {
659 AnonymizeOrModifyResource(removals, replacements, removePrivateTags, keepPatientId,
660 MetadataType_AnonymizedFrom, ChangeType_AnonymizedPatient,
661 ResourceType_Patient, call);
662 }
663 }
664
665 402
666 403
667 void OrthancRestApi::RegisterAnonymizeModify() 404 void OrthancRestApi::RegisterAnonymizeModify()
668 { 405 {
669 Register("/instances/{id}/modify", ModifyInstance); 406 Register("/instances/{id}/modify", ModifyInstance);
670 Register("/series/{id}/modify", ModifySeriesInplace); 407 Register("/series/{id}/modify", ModifyResource<ChangeType_ModifiedSeries, ResourceType_Series>);
671 Register("/studies/{id}/modify", ModifyStudyInplace); 408 Register("/studies/{id}/modify", ModifyResource<ChangeType_ModifiedStudy, ResourceType_Study>);
672 //Register("/patients/{id}/modify", ModifyPatientInplace); 409 //Register("/patients/{id}/modify", ModifyResource<ChangeType_ModifiedPatient, ResourceType_Patient>);
673 410
674 Register("/instances/{id}/anonymize", AnonymizeInstance); 411 Register("/instances/{id}/anonymize", AnonymizeInstance);
675 Register("/series/{id}/anonymize", AnonymizeSeriesInplace); 412 Register("/series/{id}/anonymize", AnonymizeResource<ChangeType_ModifiedSeries, ResourceType_Series>);
676 Register("/studies/{id}/anonymize", AnonymizeStudyInplace); 413 Register("/studies/{id}/anonymize", AnonymizeResource<ChangeType_ModifiedStudy, ResourceType_Study>);
677 Register("/patients/{id}/anonymize", AnonymizePatientInplace); 414 Register("/patients/{id}/anonymize", AnonymizeResource<ChangeType_ModifiedPatient, ResourceType_Patient>);
678 } 415 }
679 } 416 }