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