Mercurial > hg > orthanc
comparison OrthancFramework/Sources/DicomParsing/DicomModification.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | Core/DicomParsing/DicomModification.cpp@9ccbbd55bc23 |
children | bf7b9edf6b81 |
comparison
equal
deleted
inserted
replaced
4043:6c6239aec462 | 4044:d25f4c0fa160 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #include "../PrecompiledHeaders.h" | |
35 #include "DicomModification.h" | |
36 | |
37 #include "../Compatibility.h" | |
38 #include "../Logging.h" | |
39 #include "../OrthancException.h" | |
40 #include "../SerializationToolbox.h" | |
41 #include "FromDcmtkBridge.h" | |
42 #include "ITagVisitor.h" | |
43 | |
44 #include <memory> // For std::unique_ptr | |
45 | |
46 | |
47 static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2008 = | |
48 "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"; | |
49 | |
50 static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2017c = | |
51 "Orthanc " ORTHANC_VERSION " - PS 3.15-2017c Table E.1-1 Basic Profile"; | |
52 | |
53 namespace Orthanc | |
54 { | |
55 class DicomModification::RelationshipsVisitor : public ITagVisitor | |
56 { | |
57 private: | |
58 DicomModification& that_; | |
59 | |
60 bool IsEnabled(const DicomTag& tag) const | |
61 { | |
62 return (!that_.IsCleared(tag) && | |
63 !that_.IsRemoved(tag) && | |
64 !that_.IsReplaced(tag)); | |
65 } | |
66 | |
67 void RemoveIfEnabled(ParsedDicomFile& dicom, | |
68 const DicomTag& tag) const | |
69 { | |
70 if (IsEnabled(tag)) | |
71 { | |
72 dicom.Remove(tag); | |
73 } | |
74 } | |
75 | |
76 | |
77 public: | |
78 RelationshipsVisitor(DicomModification& that) : | |
79 that_(that) | |
80 { | |
81 } | |
82 | |
83 virtual void VisitNotSupported(const std::vector<DicomTag>& parentTags, | |
84 const std::vector<size_t>& parentIndexes, | |
85 const DicomTag& tag, | |
86 ValueRepresentation vr) | |
87 { | |
88 } | |
89 | |
90 virtual void VisitEmptySequence(const std::vector<DicomTag>& parentTags, | |
91 const std::vector<size_t>& parentIndexes, | |
92 const DicomTag& tag) | |
93 { | |
94 } | |
95 | |
96 virtual void VisitBinary(const std::vector<DicomTag>& parentTags, | |
97 const std::vector<size_t>& parentIndexes, | |
98 const DicomTag& tag, | |
99 ValueRepresentation vr, | |
100 const void* data, | |
101 size_t size) | |
102 { | |
103 } | |
104 | |
105 virtual void VisitIntegers(const std::vector<DicomTag>& parentTags, | |
106 const std::vector<size_t>& parentIndexes, | |
107 const DicomTag& tag, | |
108 ValueRepresentation vr, | |
109 const std::vector<int64_t>& values) | |
110 { | |
111 } | |
112 | |
113 virtual void VisitDoubles(const std::vector<DicomTag>& parentTags, | |
114 const std::vector<size_t>& parentIndexes, | |
115 const DicomTag& tag, | |
116 ValueRepresentation vr, | |
117 const std::vector<double>& value) | |
118 { | |
119 } | |
120 | |
121 virtual void VisitAttributes(const std::vector<DicomTag>& parentTags, | |
122 const std::vector<size_t>& parentIndexes, | |
123 const DicomTag& tag, | |
124 const std::vector<DicomTag>& value) | |
125 { | |
126 } | |
127 | |
128 virtual Action VisitString(std::string& newValue, | |
129 const std::vector<DicomTag>& parentTags, | |
130 const std::vector<size_t>& parentIndexes, | |
131 const DicomTag& tag, | |
132 ValueRepresentation vr, | |
133 const std::string& value) | |
134 { | |
135 if (!IsEnabled(tag)) | |
136 { | |
137 return Action_None; | |
138 } | |
139 else if (parentTags.size() == 2 && | |
140 parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE && | |
141 parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE && | |
142 tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID) | |
143 { | |
144 // in RT-STRUCT, this ReferencedSOPInstanceUID is actually referencing a StudyInstanceUID !! | |
145 // (observed in many data sets including: https://wiki.cancerimagingarchive.net/display/Public/Lung+CT+Segmentation+Challenge+2017) | |
146 // tested in test_anonymize_relationships_5 | |
147 newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Study); | |
148 return Action_Replace; | |
149 } | |
150 else if (tag == DICOM_TAG_FRAME_OF_REFERENCE_UID || | |
151 tag == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID || | |
152 tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID || | |
153 tag == DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID) | |
154 { | |
155 newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Instance); | |
156 return Action_Replace; | |
157 } | |
158 else if (parentTags.size() == 1 && | |
159 parentTags[0] == DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE && | |
160 tag == DICOM_TAG_STUDY_INSTANCE_UID) | |
161 { | |
162 newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Study); | |
163 return Action_Replace; | |
164 } | |
165 else if (parentTags.size() == 2 && | |
166 parentTags[0] == DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE && | |
167 parentTags[1] == DICOM_TAG_REFERENCED_SERIES_SEQUENCE && | |
168 tag == DICOM_TAG_SERIES_INSTANCE_UID) | |
169 { | |
170 newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series); | |
171 return Action_Replace; | |
172 } | |
173 else if (parentTags.size() == 3 && | |
174 parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE && | |
175 parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE && | |
176 parentTags[2] == DICOM_TAG_RT_REFERENCED_SERIES_SEQUENCE && | |
177 tag == DICOM_TAG_SERIES_INSTANCE_UID) | |
178 { | |
179 newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series); | |
180 return Action_Replace; | |
181 } | |
182 else if (parentTags.size() == 1 && | |
183 parentTags[0] == DICOM_TAG_REFERENCED_SERIES_SEQUENCE && | |
184 tag == DICOM_TAG_SERIES_INSTANCE_UID) | |
185 { | |
186 newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series); | |
187 return Action_Replace; | |
188 } | |
189 else | |
190 { | |
191 return Action_None; | |
192 } | |
193 } | |
194 | |
195 void RemoveRelationships(ParsedDicomFile& dicom) const | |
196 { | |
197 // Sequences containing the UID relationships | |
198 RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_IMAGE_SEQUENCE); | |
199 RemoveIfEnabled(dicom, DICOM_TAG_SOURCE_IMAGE_SEQUENCE); | |
200 | |
201 // Individual tags | |
202 RemoveIfEnabled(dicom, DICOM_TAG_FRAME_OF_REFERENCE_UID); | |
203 | |
204 // The tags below should never occur at the first level of the | |
205 // hierarchy, but remove them anyway | |
206 RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID); | |
207 RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_SOP_INSTANCE_UID); | |
208 RemoveIfEnabled(dicom, DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID); | |
209 } | |
210 }; | |
211 | |
212 | |
213 bool DicomModification::CancelReplacement(const DicomTag& tag) | |
214 { | |
215 Replacements::iterator it = replacements_.find(tag); | |
216 | |
217 if (it != replacements_.end()) | |
218 { | |
219 delete it->second; | |
220 replacements_.erase(it); | |
221 return true; | |
222 } | |
223 else | |
224 { | |
225 return false; | |
226 } | |
227 } | |
228 | |
229 | |
230 void DicomModification::ReplaceInternal(const DicomTag& tag, | |
231 const Json::Value& value) | |
232 { | |
233 Replacements::iterator it = replacements_.find(tag); | |
234 | |
235 if (it != replacements_.end()) | |
236 { | |
237 delete it->second; | |
238 it->second = NULL; // In the case of an exception during the clone | |
239 it->second = new Json::Value(value); // Clone | |
240 } | |
241 else | |
242 { | |
243 replacements_[tag] = new Json::Value(value); // Clone | |
244 } | |
245 } | |
246 | |
247 | |
248 void DicomModification::ClearReplacements() | |
249 { | |
250 for (Replacements::iterator it = replacements_.begin(); | |
251 it != replacements_.end(); ++it) | |
252 { | |
253 delete it->second; | |
254 } | |
255 | |
256 replacements_.clear(); | |
257 } | |
258 | |
259 | |
260 void DicomModification::MarkNotOrthancAnonymization() | |
261 { | |
262 Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD); | |
263 | |
264 if (it != replacements_.end() && | |
265 (it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2008 || | |
266 it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2017c)) | |
267 { | |
268 delete it->second; | |
269 replacements_.erase(it); | |
270 } | |
271 } | |
272 | |
273 void DicomModification::RegisterMappedDicomIdentifier(const std::string& original, | |
274 const std::string& mapped, | |
275 ResourceType level) | |
276 { | |
277 UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original)); | |
278 | |
279 if (previous == uidMap_.end()) | |
280 { | |
281 uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped)); | |
282 } | |
283 } | |
284 | |
285 std::string DicomModification::MapDicomIdentifier(const std::string& original, | |
286 ResourceType level) | |
287 { | |
288 std::string mapped; | |
289 | |
290 UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original)); | |
291 | |
292 if (previous == uidMap_.end()) | |
293 { | |
294 if (identifierGenerator_ == NULL) | |
295 { | |
296 mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); | |
297 } | |
298 else | |
299 { | |
300 if (!identifierGenerator_->Apply(mapped, original, level, currentSource_)) | |
301 { | |
302 throw OrthancException(ErrorCode_InternalError, | |
303 "Unable to generate an anonymized ID"); | |
304 } | |
305 } | |
306 | |
307 uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped)); | |
308 } | |
309 else | |
310 { | |
311 mapped = previous->second; | |
312 } | |
313 | |
314 return mapped; | |
315 } | |
316 | |
317 | |
318 void DicomModification::MapDicomTags(ParsedDicomFile& dicom, | |
319 ResourceType level) | |
320 { | |
321 std::unique_ptr<DicomTag> tag; | |
322 | |
323 switch (level) | |
324 { | |
325 case ResourceType_Study: | |
326 tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); | |
327 break; | |
328 | |
329 case ResourceType_Series: | |
330 tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); | |
331 break; | |
332 | |
333 case ResourceType_Instance: | |
334 tag.reset(new DicomTag(DICOM_TAG_SOP_INSTANCE_UID)); | |
335 break; | |
336 | |
337 default: | |
338 throw OrthancException(ErrorCode_InternalError); | |
339 } | |
340 | |
341 std::string original; | |
342 if (!dicom.GetTagValue(original, *tag)) | |
343 { | |
344 original = ""; | |
345 } | |
346 | |
347 std::string mapped = MapDicomIdentifier(Toolbox::StripSpaces(original), level); | |
348 | |
349 dicom.Replace(*tag, mapped, | |
350 false /* don't try and decode data URI scheme for UIDs */, | |
351 DicomReplaceMode_InsertIfAbsent, privateCreator_); | |
352 } | |
353 | |
354 | |
355 DicomModification::DicomModification() : | |
356 removePrivateTags_(false), | |
357 level_(ResourceType_Instance), | |
358 allowManualIdentifiers_(true), | |
359 keepStudyInstanceUid_(false), | |
360 keepSeriesInstanceUid_(false), | |
361 keepSopInstanceUid_(false), | |
362 updateReferencedRelationships_(true), | |
363 isAnonymization_(false), | |
364 //privateCreator_("PrivateCreator"), | |
365 identifierGenerator_(NULL) | |
366 { | |
367 } | |
368 | |
369 DicomModification::~DicomModification() | |
370 { | |
371 ClearReplacements(); | |
372 } | |
373 | |
374 void DicomModification::Keep(const DicomTag& tag) | |
375 { | |
376 bool wasRemoved = IsRemoved(tag); | |
377 bool wasCleared = IsCleared(tag); | |
378 | |
379 removals_.erase(tag); | |
380 clearings_.erase(tag); | |
381 | |
382 bool wasReplaced = CancelReplacement(tag); | |
383 | |
384 if (tag == DICOM_TAG_STUDY_INSTANCE_UID) | |
385 { | |
386 keepStudyInstanceUid_ = true; | |
387 } | |
388 else if (tag == DICOM_TAG_SERIES_INSTANCE_UID) | |
389 { | |
390 keepSeriesInstanceUid_ = true; | |
391 } | |
392 else if (tag == DICOM_TAG_SOP_INSTANCE_UID) | |
393 { | |
394 keepSopInstanceUid_ = true; | |
395 } | |
396 else if (tag.IsPrivate()) | |
397 { | |
398 privateTagsToKeep_.insert(tag); | |
399 } | |
400 else if (!wasRemoved && | |
401 !wasReplaced && | |
402 !wasCleared) | |
403 { | |
404 LOG(WARNING) << "Marking this tag as to be kept has no effect: " << tag.Format(); | |
405 } | |
406 | |
407 MarkNotOrthancAnonymization(); | |
408 } | |
409 | |
410 void DicomModification::Remove(const DicomTag& tag) | |
411 { | |
412 removals_.insert(tag); | |
413 clearings_.erase(tag); | |
414 CancelReplacement(tag); | |
415 privateTagsToKeep_.erase(tag); | |
416 | |
417 MarkNotOrthancAnonymization(); | |
418 } | |
419 | |
420 void DicomModification::Clear(const DicomTag& tag) | |
421 { | |
422 removals_.erase(tag); | |
423 clearings_.insert(tag); | |
424 CancelReplacement(tag); | |
425 privateTagsToKeep_.erase(tag); | |
426 | |
427 MarkNotOrthancAnonymization(); | |
428 } | |
429 | |
430 bool DicomModification::IsRemoved(const DicomTag& tag) const | |
431 { | |
432 return removals_.find(tag) != removals_.end(); | |
433 } | |
434 | |
435 bool DicomModification::IsCleared(const DicomTag& tag) const | |
436 { | |
437 return clearings_.find(tag) != clearings_.end(); | |
438 } | |
439 | |
440 void DicomModification::Replace(const DicomTag& tag, | |
441 const Json::Value& value, | |
442 bool safeForAnonymization) | |
443 { | |
444 clearings_.erase(tag); | |
445 removals_.erase(tag); | |
446 privateTagsToKeep_.erase(tag); | |
447 ReplaceInternal(tag, value); | |
448 | |
449 if (!safeForAnonymization) | |
450 { | |
451 MarkNotOrthancAnonymization(); | |
452 } | |
453 } | |
454 | |
455 | |
456 bool DicomModification::IsReplaced(const DicomTag& tag) const | |
457 { | |
458 return replacements_.find(tag) != replacements_.end(); | |
459 } | |
460 | |
461 const Json::Value& DicomModification::GetReplacement(const DicomTag& tag) const | |
462 { | |
463 Replacements::const_iterator it = replacements_.find(tag); | |
464 | |
465 if (it == replacements_.end()) | |
466 { | |
467 throw OrthancException(ErrorCode_InexistentItem); | |
468 } | |
469 else | |
470 { | |
471 return *it->second; | |
472 } | |
473 } | |
474 | |
475 | |
476 std::string DicomModification::GetReplacementAsString(const DicomTag& tag) const | |
477 { | |
478 const Json::Value& json = GetReplacement(tag); | |
479 | |
480 if (json.type() != Json::stringValue) | |
481 { | |
482 throw OrthancException(ErrorCode_BadParameterType); | |
483 } | |
484 else | |
485 { | |
486 return json.asString(); | |
487 } | |
488 } | |
489 | |
490 | |
491 void DicomModification::SetRemovePrivateTags(bool removed) | |
492 { | |
493 removePrivateTags_ = removed; | |
494 | |
495 if (!removed) | |
496 { | |
497 MarkNotOrthancAnonymization(); | |
498 } | |
499 } | |
500 | |
501 void DicomModification::SetLevel(ResourceType level) | |
502 { | |
503 uidMap_.clear(); | |
504 level_ = level; | |
505 | |
506 if (level != ResourceType_Patient) | |
507 { | |
508 MarkNotOrthancAnonymization(); | |
509 } | |
510 } | |
511 | |
512 | |
513 void DicomModification::SetupAnonymization2008() | |
514 { | |
515 // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles | |
516 // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2008/08_15pu.pdf | |
517 | |
518 removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID | |
519 //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply() | |
520 removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number | |
521 removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name | |
522 removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address | |
523 removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name | |
524 removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address | |
525 removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers | |
526 removals_.insert(DicomTag(0x0008, 0x1010)); // Station Name | |
527 removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description | |
528 removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description | |
529 removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name | |
530 removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record | |
531 removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name | |
532 removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study | |
533 removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name | |
534 removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description | |
535 //removals_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID => RelationshipsVisitor | |
536 removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description | |
537 //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*) | |
538 //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) | |
539 removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date | |
540 removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time | |
541 removals_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex | |
542 removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids | |
543 removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names | |
544 removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age | |
545 removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size | |
546 removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight | |
547 removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator | |
548 removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group | |
549 removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation | |
550 removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History | |
551 removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments | |
552 removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number | |
553 removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name | |
554 //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply() | |
555 //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply() | |
556 removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID | |
557 //removals_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID => cf. RelationshipsVisitor | |
558 removals_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID | |
559 removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments | |
560 removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence | |
561 removals_.insert(DicomTag(0x0040, 0xa124)); // UID | |
562 removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence | |
563 removals_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID | |
564 //removals_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID => RelationshipsVisitor | |
565 //removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID => RelationshipsVisitor | |
566 | |
567 // Some more removals (from the experience of DICOM files at the CHU of Liege) | |
568 removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address | |
569 removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician | |
570 removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers | |
571 removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts | |
572 | |
573 // Set the DeidentificationMethod tag | |
574 ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2008); | |
575 } | |
576 | |
577 | |
578 void DicomModification::SetupAnonymization2017c() | |
579 { | |
580 /** | |
581 * This is Table E.1-1 from PS 3.15-2017c (DICOM Part 15: Security | |
582 * and System Management Profiles), "basic profile" column. It was | |
583 * generated automatically with the | |
584 * "../Resources/GenerateAnonymizationProfile.py" script. | |
585 * https://raw.githubusercontent.com/jodogne/dicom-specification/master/2017c/part15.pdf | |
586 **/ | |
587 | |
588 // TODO: (50xx,xxxx) with rule X // Curve Data | |
589 // TODO: (60xx,3000) with rule X // Overlay Data | |
590 // TODO: (60xx,4000) with rule X // Overlay Comments | |
591 // Tag (0x0008, 0x0018) is set in Apply() /* U */ // SOP Instance UID | |
592 // Tag (0x0008, 0x1140) => RelationshipsVisitor /* X/Z/U* */ // Referenced Image Sequence | |
593 // Tag (0x0008, 0x1155) => RelationshipsVisitor /* U */ // Referenced SOP Instance UID | |
594 // Tag (0x0008, 0x2112) => RelationshipsVisitor /* X/Z/U* */ // Source Image Sequence | |
595 // Tag (0x0010, 0x0010) is set below (*) /* Z */ // Patient's Name | |
596 // Tag (0x0010, 0x0020) is set below (*) /* Z */ // Patient ID | |
597 // Tag (0x0020, 0x000d) is set in Apply() /* U */ // Study Instance UID | |
598 // Tag (0x0020, 0x000e) is set in Apply() /* U */ // Series Instance UID | |
599 // Tag (0x0020, 0x0052) => RelationshipsVisitor /* U */ // Frame of Reference UID | |
600 // Tag (0x3006, 0x0024) => RelationshipsVisitor /* U */ // Referenced Frame of Reference UID | |
601 // Tag (0x3006, 0x00c2) => RelationshipsVisitor /* U */ // Related Frame of Reference UID | |
602 clearings_.insert(DicomTag(0x0008, 0x0020)); // Study Date | |
603 clearings_.insert(DicomTag(0x0008, 0x0023)); /* Z/D */ // Content Date | |
604 clearings_.insert(DicomTag(0x0008, 0x0030)); // Study Time | |
605 clearings_.insert(DicomTag(0x0008, 0x0033)); /* Z/D */ // Content Time | |
606 clearings_.insert(DicomTag(0x0008, 0x0050)); // Accession Number | |
607 clearings_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name | |
608 clearings_.insert(DicomTag(0x0008, 0x009c)); // Consulting Physician's Name | |
609 clearings_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date | |
610 clearings_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex | |
611 clearings_.insert(DicomTag(0x0018, 0x0010)); /* Z/D */ // Contrast Bolus Agent | |
612 clearings_.insert(DicomTag(0x0020, 0x0010)); // Study ID | |
613 clearings_.insert(DicomTag(0x0040, 0x1101)); /* D */ // Person Identification Code Sequence | |
614 clearings_.insert(DicomTag(0x0040, 0x2016)); // Placer Order Number / Imaging Service Request | |
615 clearings_.insert(DicomTag(0x0040, 0x2017)); // Filler Order Number / Imaging Service Request | |
616 clearings_.insert(DicomTag(0x0040, 0xa073)); /* D */ // Verifying Observer Sequence | |
617 clearings_.insert(DicomTag(0x0040, 0xa075)); /* D */ // Verifying Observer Name | |
618 clearings_.insert(DicomTag(0x0040, 0xa088)); // Verifying Observer Identification Code Sequence | |
619 clearings_.insert(DicomTag(0x0040, 0xa123)); /* D */ // Person Name | |
620 clearings_.insert(DicomTag(0x0070, 0x0001)); /* D */ // Graphic Annotation Sequence | |
621 clearings_.insert(DicomTag(0x0070, 0x0084)); // Content Creator's Name | |
622 removals_.insert(DicomTag(0x0000, 0x1000)); // Affected SOP Instance UID | |
623 removals_.insert(DicomTag(0x0000, 0x1001)); /* TODO UID */ // Requested SOP Instance UID | |
624 removals_.insert(DicomTag(0x0002, 0x0003)); /* TODO UID */ // Media Storage SOP Instance UID | |
625 removals_.insert(DicomTag(0x0004, 0x1511)); /* TODO UID */ // Referenced SOP Instance UID in File | |
626 removals_.insert(DicomTag(0x0008, 0x0014)); /* TODO UID */ // Instance Creator UID | |
627 removals_.insert(DicomTag(0x0008, 0x0015)); // Instance Coercion DateTime | |
628 removals_.insert(DicomTag(0x0008, 0x0021)); /* X/D */ // Series Date | |
629 removals_.insert(DicomTag(0x0008, 0x0022)); /* X/Z */ // Acquisition Date | |
630 removals_.insert(DicomTag(0x0008, 0x0024)); // Overlay Date | |
631 removals_.insert(DicomTag(0x0008, 0x0025)); // Curve Date | |
632 removals_.insert(DicomTag(0x0008, 0x002a)); /* X/D */ // Acquisition DateTime | |
633 removals_.insert(DicomTag(0x0008, 0x0031)); /* X/D */ // Series Time | |
634 removals_.insert(DicomTag(0x0008, 0x0032)); /* X/Z */ // Acquisition Time | |
635 removals_.insert(DicomTag(0x0008, 0x0034)); // Overlay Time | |
636 removals_.insert(DicomTag(0x0008, 0x0035)); // Curve Time | |
637 removals_.insert(DicomTag(0x0008, 0x0058)); /* TODO UID */ // Failed SOP Instance UID List | |
638 removals_.insert(DicomTag(0x0008, 0x0080)); /* X/Z/D */ // Institution Name | |
639 removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address | |
640 removals_.insert(DicomTag(0x0008, 0x0082)); /* X/Z/D */ // Institution Code Sequence | |
641 removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address | |
642 removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers | |
643 removals_.insert(DicomTag(0x0008, 0x0096)); // Referring Physician Identification Sequence | |
644 removals_.insert(DicomTag(0x0008, 0x009d)); // Consulting Physician Identification Sequence | |
645 removals_.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC | |
646 removals_.insert(DicomTag(0x0008, 0x1010)); /* X/Z/D */ // Station Name | |
647 removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description | |
648 removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description | |
649 removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name | |
650 removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record | |
651 removals_.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of Record Identification Sequence | |
652 removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name | |
653 removals_.insert(DicomTag(0x0008, 0x1052)); // Performing Physician Identification Sequence | |
654 removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study | |
655 removals_.insert(DicomTag(0x0008, 0x1062)); // Physician(s) Reading Study Identification Sequence | |
656 removals_.insert(DicomTag(0x0008, 0x1070)); /* X/Z/D */ // Operators' Name | |
657 removals_.insert(DicomTag(0x0008, 0x1072)); /* X/D */ // Operators' Identification Sequence | |
658 removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description | |
659 removals_.insert(DicomTag(0x0008, 0x1084)); // Admitting Diagnoses Code Sequence | |
660 removals_.insert(DicomTag(0x0008, 0x1110)); /* X/Z */ // Referenced Study Sequence | |
661 removals_.insert(DicomTag(0x0008, 0x1111)); /* X/Z/D */ // Referenced Performed Procedure Step Sequence | |
662 removals_.insert(DicomTag(0x0008, 0x1120)); // Referenced Patient Sequence | |
663 removals_.insert(DicomTag(0x0008, 0x1195)); /* TODO UID */ // Transaction UID | |
664 removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description | |
665 removals_.insert(DicomTag(0x0008, 0x3010)); /* TODO UID */ // Irradiation Event UID | |
666 removals_.insert(DicomTag(0x0008, 0x4000)); // Identifying Comments | |
667 removals_.insert(DicomTag(0x0010, 0x0021)); // Issuer of Patient ID | |
668 removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time | |
669 removals_.insert(DicomTag(0x0010, 0x0050)); // Patient's Insurance Plan Code Sequence | |
670 removals_.insert(DicomTag(0x0010, 0x0101)); // Patient's Primary Language Code Sequence | |
671 removals_.insert(DicomTag(0x0010, 0x0102)); // Patient's Primary Language Modifier Code Sequence | |
672 removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient IDs | |
673 removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names | |
674 removals_.insert(DicomTag(0x0010, 0x1002)); // Other Patient IDs Sequence | |
675 removals_.insert(DicomTag(0x0010, 0x1005)); // Patient's Birth Name | |
676 removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age | |
677 removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size | |
678 removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight | |
679 removals_.insert(DicomTag(0x0010, 0x1040)); // Patient Address | |
680 removals_.insert(DicomTag(0x0010, 0x1050)); // Insurance Plan Identification | |
681 removals_.insert(DicomTag(0x0010, 0x1060)); // Patient's Mother's Birth Name | |
682 removals_.insert(DicomTag(0x0010, 0x1080)); // Military Rank | |
683 removals_.insert(DicomTag(0x0010, 0x1081)); // Branch of Service | |
684 removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator | |
685 removals_.insert(DicomTag(0x0010, 0x1100)); // Referenced Patient Photo Sequence | |
686 removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts | |
687 removals_.insert(DicomTag(0x0010, 0x2110)); // Allergies | |
688 removals_.insert(DicomTag(0x0010, 0x2150)); // Country of Residence | |
689 removals_.insert(DicomTag(0x0010, 0x2152)); // Region of Residence | |
690 removals_.insert(DicomTag(0x0010, 0x2154)); // Patient's Telephone Numbers | |
691 removals_.insert(DicomTag(0x0010, 0x2155)); // Patient's Telecom Information | |
692 removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group | |
693 removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation | |
694 removals_.insert(DicomTag(0x0010, 0x21a0)); // Smoking Status | |
695 removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History | |
696 removals_.insert(DicomTag(0x0010, 0x21c0)); // Pregnancy Status | |
697 removals_.insert(DicomTag(0x0010, 0x21d0)); // Last Menstrual Date | |
698 removals_.insert(DicomTag(0x0010, 0x21f0)); // Patient's Religious Preference | |
699 removals_.insert(DicomTag(0x0010, 0x2203)); /* X/Z */ // Patient Sex Neutered | |
700 removals_.insert(DicomTag(0x0010, 0x2297)); // Responsible Person | |
701 removals_.insert(DicomTag(0x0010, 0x2299)); // Responsible Organization | |
702 removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments | |
703 removals_.insert(DicomTag(0x0018, 0x1000)); /* X/Z/D */ // Device Serial Number | |
704 removals_.insert(DicomTag(0x0018, 0x1002)); /* TODO UID */ // Device UID | |
705 removals_.insert(DicomTag(0x0018, 0x1004)); // Plate ID | |
706 removals_.insert(DicomTag(0x0018, 0x1005)); // Generator ID | |
707 removals_.insert(DicomTag(0x0018, 0x1007)); // Cassette ID | |
708 removals_.insert(DicomTag(0x0018, 0x1008)); // Gantry ID | |
709 removals_.insert(DicomTag(0x0018, 0x1030)); /* X/D */ // Protocol Name | |
710 removals_.insert(DicomTag(0x0018, 0x1400)); /* X/D */ // Acquisition Device Processing Description | |
711 removals_.insert(DicomTag(0x0018, 0x2042)); /* TODO UID */ // Target UID | |
712 removals_.insert(DicomTag(0x0018, 0x4000)); // Acquisition Comments | |
713 removals_.insert(DicomTag(0x0018, 0x700a)); /* X/D */ // Detector ID | |
714 removals_.insert(DicomTag(0x0018, 0x9424)); // Acquisition Protocol Description | |
715 removals_.insert(DicomTag(0x0018, 0x9516)); /* X/D */ // Start Acquisition DateTime | |
716 removals_.insert(DicomTag(0x0018, 0x9517)); /* X/D */ // End Acquisition DateTime | |
717 removals_.insert(DicomTag(0x0018, 0xa003)); // Contribution Description | |
718 removals_.insert(DicomTag(0x0020, 0x0200)); /* TODO UID */ // Synchronization Frame of Reference UID | |
719 removals_.insert(DicomTag(0x0020, 0x3401)); // Modifying Device ID | |
720 removals_.insert(DicomTag(0x0020, 0x3404)); // Modifying Device Manufacturer | |
721 removals_.insert(DicomTag(0x0020, 0x3406)); // Modified Image Description | |
722 removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments | |
723 removals_.insert(DicomTag(0x0020, 0x9158)); // Frame Comments | |
724 removals_.insert(DicomTag(0x0020, 0x9161)); /* TODO UID */ // Concatenation UID | |
725 removals_.insert(DicomTag(0x0020, 0x9164)); /* TODO UID */ // Dimension Organization UID | |
726 removals_.insert(DicomTag(0x0028, 0x1199)); /* TODO UID */ // Palette Color Lookup Table UID | |
727 removals_.insert(DicomTag(0x0028, 0x1214)); /* TODO UID */ // Large Palette Color Lookup Table UID | |
728 removals_.insert(DicomTag(0x0028, 0x4000)); // Image Presentation Comments | |
729 removals_.insert(DicomTag(0x0032, 0x0012)); // Study ID Issuer | |
730 removals_.insert(DicomTag(0x0032, 0x1020)); // Scheduled Study Location | |
731 removals_.insert(DicomTag(0x0032, 0x1021)); // Scheduled Study Location AE Title | |
732 removals_.insert(DicomTag(0x0032, 0x1030)); // Reason for Study | |
733 removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician | |
734 removals_.insert(DicomTag(0x0032, 0x1033)); // Requesting Service | |
735 removals_.insert(DicomTag(0x0032, 0x1060)); /* X/Z */ // Requested Procedure Description | |
736 removals_.insert(DicomTag(0x0032, 0x1070)); // Requested Contrast Agent | |
737 removals_.insert(DicomTag(0x0032, 0x4000)); // Study Comments | |
738 removals_.insert(DicomTag(0x0038, 0x0004)); // Referenced Patient Alias Sequence | |
739 removals_.insert(DicomTag(0x0038, 0x0010)); // Admission ID | |
740 removals_.insert(DicomTag(0x0038, 0x0011)); // Issuer of Admission ID | |
741 removals_.insert(DicomTag(0x0038, 0x001e)); // Scheduled Patient Institution Residence | |
742 removals_.insert(DicomTag(0x0038, 0x0020)); // Admitting Date | |
743 removals_.insert(DicomTag(0x0038, 0x0021)); // Admitting Time | |
744 removals_.insert(DicomTag(0x0038, 0x0040)); // Discharge Diagnosis Description | |
745 removals_.insert(DicomTag(0x0038, 0x0050)); // Special Needs | |
746 removals_.insert(DicomTag(0x0038, 0x0060)); // Service Episode ID | |
747 removals_.insert(DicomTag(0x0038, 0x0061)); // Issuer of Service Episode ID | |
748 removals_.insert(DicomTag(0x0038, 0x0062)); // Service Episode Description | |
749 removals_.insert(DicomTag(0x0038, 0x0300)); // Current Patient Location | |
750 removals_.insert(DicomTag(0x0038, 0x0400)); // Patient's Institution Residence | |
751 removals_.insert(DicomTag(0x0038, 0x0500)); // Patient State | |
752 removals_.insert(DicomTag(0x0038, 0x4000)); // Visit Comments | |
753 removals_.insert(DicomTag(0x0040, 0x0001)); // Scheduled Station AE Title | |
754 removals_.insert(DicomTag(0x0040, 0x0002)); // Scheduled Procedure Step Start Date | |
755 removals_.insert(DicomTag(0x0040, 0x0003)); // Scheduled Procedure Step Start Time | |
756 removals_.insert(DicomTag(0x0040, 0x0004)); // Scheduled Procedure Step End Date | |
757 removals_.insert(DicomTag(0x0040, 0x0005)); // Scheduled Procedure Step End Time | |
758 removals_.insert(DicomTag(0x0040, 0x0006)); // Scheduled Performing Physician Name | |
759 removals_.insert(DicomTag(0x0040, 0x0007)); // Scheduled Procedure Step Description | |
760 removals_.insert(DicomTag(0x0040, 0x000b)); // Scheduled Performing Physician Identification Sequence | |
761 removals_.insert(DicomTag(0x0040, 0x0010)); // Scheduled Station Name | |
762 removals_.insert(DicomTag(0x0040, 0x0011)); // Scheduled Procedure Step Location | |
763 removals_.insert(DicomTag(0x0040, 0x0012)); // Pre-Medication | |
764 removals_.insert(DicomTag(0x0040, 0x0241)); // Performed Station AE Title | |
765 removals_.insert(DicomTag(0x0040, 0x0242)); // Performed Station Name | |
766 removals_.insert(DicomTag(0x0040, 0x0243)); // Performed Location | |
767 removals_.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date | |
768 removals_.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time | |
769 removals_.insert(DicomTag(0x0040, 0x0250)); // Performed Procedure Step End Date | |
770 removals_.insert(DicomTag(0x0040, 0x0251)); // Performed Procedure Step End Time | |
771 removals_.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID | |
772 removals_.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description | |
773 removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence | |
774 removals_.insert(DicomTag(0x0040, 0x0280)); // Comments on the Performed Procedure Step | |
775 removals_.insert(DicomTag(0x0040, 0x0555)); // Acquisition Context Sequence | |
776 removals_.insert(DicomTag(0x0040, 0x1001)); // Requested Procedure ID | |
777 removals_.insert(DicomTag(0x0040, 0x1004)); // Patient Transport Arrangements | |
778 removals_.insert(DicomTag(0x0040, 0x1005)); // Requested Procedure Location | |
779 removals_.insert(DicomTag(0x0040, 0x1010)); // Names of Intended Recipient of Results | |
780 removals_.insert(DicomTag(0x0040, 0x1011)); // Intended Recipients of Results Identification Sequence | |
781 removals_.insert(DicomTag(0x0040, 0x1102)); // Person Address | |
782 removals_.insert(DicomTag(0x0040, 0x1103)); // Person's Telephone Numbers | |
783 removals_.insert(DicomTag(0x0040, 0x1104)); // Person's Telecom Information | |
784 removals_.insert(DicomTag(0x0040, 0x1400)); // Requested Procedure Comments | |
785 removals_.insert(DicomTag(0x0040, 0x2001)); // Reason for the Imaging Service Request | |
786 removals_.insert(DicomTag(0x0040, 0x2008)); // Order Entered By | |
787 removals_.insert(DicomTag(0x0040, 0x2009)); // Order Enterer Location | |
788 removals_.insert(DicomTag(0x0040, 0x2010)); // Order Callback Phone Number | |
789 removals_.insert(DicomTag(0x0040, 0x2011)); // Order Callback Telecom Information | |
790 removals_.insert(DicomTag(0x0040, 0x2400)); // Imaging Service Request Comments | |
791 removals_.insert(DicomTag(0x0040, 0x3001)); // Confidentiality Constraint on Patient Data Description | |
792 removals_.insert(DicomTag(0x0040, 0x4005)); // Scheduled Procedure Step Start DateTime | |
793 removals_.insert(DicomTag(0x0040, 0x4010)); // Scheduled Procedure Step Modification DateTime | |
794 removals_.insert(DicomTag(0x0040, 0x4011)); // Expected Completion DateTime | |
795 removals_.insert(DicomTag(0x0040, 0x4023)); /* TODO UID */ // Referenced General Purpose Scheduled Procedure Step Transaction UID | |
796 removals_.insert(DicomTag(0x0040, 0x4025)); // Scheduled Station Name Code Sequence | |
797 removals_.insert(DicomTag(0x0040, 0x4027)); // Scheduled Station Geographic Location Code Sequence | |
798 removals_.insert(DicomTag(0x0040, 0x4028)); // Performed Station Name Code Sequence | |
799 removals_.insert(DicomTag(0x0040, 0x4030)); // Performed Station Geographic Location Code Sequence | |
800 removals_.insert(DicomTag(0x0040, 0x4034)); // Scheduled Human Performers Sequence | |
801 removals_.insert(DicomTag(0x0040, 0x4035)); // Actual Human Performers Sequence | |
802 removals_.insert(DicomTag(0x0040, 0x4036)); // Human Performers Organization | |
803 removals_.insert(DicomTag(0x0040, 0x4037)); // Human Performers Name | |
804 removals_.insert(DicomTag(0x0040, 0x4050)); // Performed Procedure Step Start DateTime | |
805 removals_.insert(DicomTag(0x0040, 0x4051)); // Performed Procedure Step End DateTime | |
806 removals_.insert(DicomTag(0x0040, 0x4052)); // Procedure Step Cancellation DateTime | |
807 removals_.insert(DicomTag(0x0040, 0xa027)); // Verifying Organization | |
808 removals_.insert(DicomTag(0x0040, 0xa078)); // Author Observer Sequence | |
809 removals_.insert(DicomTag(0x0040, 0xa07a)); // Participant Sequence | |
810 removals_.insert(DicomTag(0x0040, 0xa07c)); // Custodial Organization Sequence | |
811 removals_.insert(DicomTag(0x0040, 0xa124)); /* TODO UID */ // UID | |
812 removals_.insert(DicomTag(0x0040, 0xa171)); /* TODO UID */ // Observation UID | |
813 removals_.insert(DicomTag(0x0040, 0xa172)); /* TODO UID */ // Referenced Observation UID (Trial) | |
814 removals_.insert(DicomTag(0x0040, 0xa192)); // Observation Date (Trial) | |
815 removals_.insert(DicomTag(0x0040, 0xa193)); // Observation Time (Trial) | |
816 removals_.insert(DicomTag(0x0040, 0xa307)); // Current Observer (Trial) | |
817 removals_.insert(DicomTag(0x0040, 0xa352)); // Verbal Source (Trial) | |
818 removals_.insert(DicomTag(0x0040, 0xa353)); // Address (Trial) | |
819 removals_.insert(DicomTag(0x0040, 0xa354)); // Telephone Number (Trial) | |
820 removals_.insert(DicomTag(0x0040, 0xa358)); // Verbal Source Identifier Code Sequence (Trial) | |
821 removals_.insert(DicomTag(0x0040, 0xa402)); /* TODO UID */ // Observation Subject UID (Trial) | |
822 removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence | |
823 removals_.insert(DicomTag(0x0040, 0xdb0c)); /* TODO UID */ // Template Extension Organization UID | |
824 removals_.insert(DicomTag(0x0040, 0xdb0d)); /* TODO UID */ // Template Extension Creator UID | |
825 removals_.insert(DicomTag(0x0062, 0x0021)); /* TODO UID */ // Tracking UID | |
826 removals_.insert(DicomTag(0x0070, 0x0086)); // Content Creator's Identification Code Sequence | |
827 removals_.insert(DicomTag(0x0070, 0x031a)); /* TODO UID */ // Fiducial UID | |
828 removals_.insert(DicomTag(0x0070, 0x1101)); /* TODO UID */ // Presentation Display Collection UID | |
829 removals_.insert(DicomTag(0x0070, 0x1102)); /* TODO UID */ // Presentation Sequence Collection UID | |
830 removals_.insert(DicomTag(0x0088, 0x0140)); /* TODO UID */ // Storage Media File-set UID | |
831 removals_.insert(DicomTag(0x0088, 0x0200)); // Icon Image Sequence(see Note 12) | |
832 removals_.insert(DicomTag(0x0088, 0x0904)); // Topic Title | |
833 removals_.insert(DicomTag(0x0088, 0x0906)); // Topic Subject | |
834 removals_.insert(DicomTag(0x0088, 0x0910)); // Topic Author | |
835 removals_.insert(DicomTag(0x0088, 0x0912)); // Topic Keywords | |
836 removals_.insert(DicomTag(0x0400, 0x0100)); // Digital Signature UID | |
837 removals_.insert(DicomTag(0x0400, 0x0402)); // Referenced Digital Signature Sequence | |
838 removals_.insert(DicomTag(0x0400, 0x0403)); // Referenced SOP Instance MAC Sequence | |
839 removals_.insert(DicomTag(0x0400, 0x0404)); // MAC | |
840 removals_.insert(DicomTag(0x0400, 0x0550)); // Modified Attributes Sequence | |
841 removals_.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence | |
842 removals_.insert(DicomTag(0x2030, 0x0020)); // Text String | |
843 removals_.insert(DicomTag(0x3008, 0x0105)); // Source Serial Number | |
844 removals_.insert(DicomTag(0x300a, 0x0013)); /* TODO UID */ // Dose Reference UID | |
845 removals_.insert(DicomTag(0x300c, 0x0113)); // Reason for Omission Description | |
846 removals_.insert(DicomTag(0x300e, 0x0008)); /* X/Z */ // Reviewer Name | |
847 removals_.insert(DicomTag(0x4000, 0x0010)); // Arbitrary | |
848 removals_.insert(DicomTag(0x4000, 0x4000)); // Text Comments | |
849 removals_.insert(DicomTag(0x4008, 0x0042)); // Results ID Issuer | |
850 removals_.insert(DicomTag(0x4008, 0x0102)); // Interpretation Recorder | |
851 removals_.insert(DicomTag(0x4008, 0x010a)); // Interpretation Transcriber | |
852 removals_.insert(DicomTag(0x4008, 0x010b)); // Interpretation Text | |
853 removals_.insert(DicomTag(0x4008, 0x010c)); // Interpretation Author | |
854 removals_.insert(DicomTag(0x4008, 0x0111)); // Interpretation Approver Sequence | |
855 removals_.insert(DicomTag(0x4008, 0x0114)); // Physician Approving Interpretation | |
856 removals_.insert(DicomTag(0x4008, 0x0115)); // Interpretation Diagnosis Description | |
857 removals_.insert(DicomTag(0x4008, 0x0118)); // Results Distribution List Sequence | |
858 removals_.insert(DicomTag(0x4008, 0x0119)); // Distribution Name | |
859 removals_.insert(DicomTag(0x4008, 0x011a)); // Distribution Address | |
860 removals_.insert(DicomTag(0x4008, 0x0202)); // Interpretation ID Issuer | |
861 removals_.insert(DicomTag(0x4008, 0x0300)); // Impressions | |
862 removals_.insert(DicomTag(0x4008, 0x4000)); // Results Comments | |
863 removals_.insert(DicomTag(0xfffa, 0xfffa)); // Digital Signatures Sequence | |
864 removals_.insert(DicomTag(0xfffc, 0xfffc)); // Data Set Trailing Padding | |
865 | |
866 // Set the DeidentificationMethod tag | |
867 ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2017c); | |
868 } | |
869 | |
870 | |
871 void DicomModification::SetupAnonymization(DicomVersion version) | |
872 { | |
873 isAnonymization_ = true; | |
874 | |
875 removals_.clear(); | |
876 clearings_.clear(); | |
877 ClearReplacements(); | |
878 removePrivateTags_ = true; | |
879 level_ = ResourceType_Patient; | |
880 uidMap_.clear(); | |
881 privateTagsToKeep_.clear(); | |
882 | |
883 switch (version) | |
884 { | |
885 case DicomVersion_2008: | |
886 SetupAnonymization2008(); | |
887 break; | |
888 | |
889 case DicomVersion_2017c: | |
890 SetupAnonymization2017c(); | |
891 break; | |
892 | |
893 default: | |
894 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
895 } | |
896 | |
897 // Set the PatientIdentityRemoved tag | |
898 ReplaceInternal(DicomTag(0x0012, 0x0062), "YES"); | |
899 | |
900 // (*) Choose a random patient name and ID | |
901 std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient); | |
902 ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId); | |
903 ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId); | |
904 } | |
905 | |
906 void DicomModification::Apply(ParsedDicomFile& toModify) | |
907 { | |
908 // Check the request | |
909 assert(ResourceType_Patient + 1 == ResourceType_Study && | |
910 ResourceType_Study + 1 == ResourceType_Series && | |
911 ResourceType_Series + 1 == ResourceType_Instance); | |
912 | |
913 if (IsRemoved(DICOM_TAG_PATIENT_ID) || | |
914 IsRemoved(DICOM_TAG_STUDY_INSTANCE_UID) || | |
915 IsRemoved(DICOM_TAG_SERIES_INSTANCE_UID) || | |
916 IsRemoved(DICOM_TAG_SOP_INSTANCE_UID)) | |
917 { | |
918 throw OrthancException(ErrorCode_BadRequest); | |
919 } | |
920 | |
921 | |
922 // Sanity checks at the patient level | |
923 if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID)) | |
924 { | |
925 throw OrthancException(ErrorCode_BadRequest, | |
926 "When modifying a patient, her PatientID is required to be modified"); | |
927 } | |
928 | |
929 if (!allowManualIdentifiers_) | |
930 { | |
931 if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) | |
932 { | |
933 throw OrthancException(ErrorCode_BadRequest, | |
934 "When modifying a patient, the StudyInstanceUID cannot be manually modified"); | |
935 } | |
936 | |
937 if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) | |
938 { | |
939 throw OrthancException(ErrorCode_BadRequest, | |
940 "When modifying a patient, the SeriesInstanceUID cannot be manually modified"); | |
941 } | |
942 | |
943 if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) | |
944 { | |
945 throw OrthancException(ErrorCode_BadRequest, | |
946 "When modifying a patient, the SopInstanceUID cannot be manually modified"); | |
947 } | |
948 } | |
949 | |
950 | |
951 // Sanity checks at the study level | |
952 if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_PATIENT_ID)) | |
953 { | |
954 throw OrthancException(ErrorCode_BadRequest, | |
955 "When modifying a study, the parent PatientID cannot be manually modified"); | |
956 } | |
957 | |
958 if (!allowManualIdentifiers_) | |
959 { | |
960 if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) | |
961 { | |
962 throw OrthancException(ErrorCode_BadRequest, | |
963 "When modifying a study, the SeriesInstanceUID cannot be manually modified"); | |
964 } | |
965 | |
966 if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) | |
967 { | |
968 throw OrthancException(ErrorCode_BadRequest, | |
969 "When modifying a study, the SopInstanceUID cannot be manually modified"); | |
970 } | |
971 } | |
972 | |
973 | |
974 // Sanity checks at the series level | |
975 if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_PATIENT_ID)) | |
976 { | |
977 throw OrthancException(ErrorCode_BadRequest, | |
978 "When modifying a series, the parent PatientID cannot be manually modified"); | |
979 } | |
980 | |
981 if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) | |
982 { | |
983 throw OrthancException(ErrorCode_BadRequest, | |
984 "When modifying a series, the parent StudyInstanceUID cannot be manually modified"); | |
985 } | |
986 | |
987 if (!allowManualIdentifiers_) | |
988 { | |
989 if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) | |
990 { | |
991 throw OrthancException(ErrorCode_BadRequest, | |
992 "When modifying a series, the SopInstanceUID cannot be manually modified"); | |
993 } | |
994 } | |
995 | |
996 | |
997 // Sanity checks at the instance level | |
998 if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_PATIENT_ID)) | |
999 { | |
1000 throw OrthancException(ErrorCode_BadRequest, | |
1001 "When modifying an instance, the parent PatientID cannot be manually modified"); | |
1002 } | |
1003 | |
1004 if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) | |
1005 { | |
1006 throw OrthancException(ErrorCode_BadRequest, | |
1007 "When modifying an instance, the parent StudyInstanceUID cannot be manually modified"); | |
1008 } | |
1009 | |
1010 if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) | |
1011 { | |
1012 throw OrthancException(ErrorCode_BadRequest, | |
1013 "When modifying an instance, the parent SeriesInstanceUID cannot be manually modified"); | |
1014 } | |
1015 | |
1016 // (0) Create a summary of the source file, if a custom generator | |
1017 // is provided | |
1018 if (identifierGenerator_ != NULL) | |
1019 { | |
1020 toModify.ExtractDicomSummary(currentSource_); | |
1021 } | |
1022 | |
1023 // (1) Make sure the relationships are updated with the ids that we force too | |
1024 // i.e: an RT-STRUCT is referencing its own StudyInstanceUID | |
1025 if (isAnonymization_ && updateReferencedRelationships_) | |
1026 { | |
1027 if (IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) | |
1028 { | |
1029 std::string original; | |
1030 std::string replacement = GetReplacementAsString(DICOM_TAG_STUDY_INSTANCE_UID); | |
1031 toModify.GetTagValue(original, DICOM_TAG_STUDY_INSTANCE_UID); | |
1032 RegisterMappedDicomIdentifier(original, replacement, ResourceType_Study); | |
1033 } | |
1034 | |
1035 if (IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) | |
1036 { | |
1037 std::string original; | |
1038 std::string replacement = GetReplacementAsString(DICOM_TAG_SERIES_INSTANCE_UID); | |
1039 toModify.GetTagValue(original, DICOM_TAG_SERIES_INSTANCE_UID); | |
1040 RegisterMappedDicomIdentifier(original, replacement, ResourceType_Series); | |
1041 } | |
1042 | |
1043 if (IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) | |
1044 { | |
1045 std::string original; | |
1046 std::string replacement = GetReplacementAsString(DICOM_TAG_SOP_INSTANCE_UID); | |
1047 toModify.GetTagValue(original, DICOM_TAG_SOP_INSTANCE_UID); | |
1048 RegisterMappedDicomIdentifier(original, replacement, ResourceType_Instance); | |
1049 } | |
1050 } | |
1051 | |
1052 | |
1053 // (2) Remove the private tags, if need be | |
1054 if (removePrivateTags_) | |
1055 { | |
1056 toModify.RemovePrivateTags(privateTagsToKeep_); | |
1057 } | |
1058 | |
1059 // (3) Clear the tags specified by the user | |
1060 for (SetOfTags::const_iterator it = clearings_.begin(); | |
1061 it != clearings_.end(); ++it) | |
1062 { | |
1063 toModify.Clear(*it, true /* only clear if the tag exists in the original file */); | |
1064 } | |
1065 | |
1066 // (4) Remove the tags specified by the user | |
1067 for (SetOfTags::const_iterator it = removals_.begin(); | |
1068 it != removals_.end(); ++it) | |
1069 { | |
1070 toModify.Remove(*it); | |
1071 } | |
1072 | |
1073 // (5) Replace the tags | |
1074 for (Replacements::const_iterator it = replacements_.begin(); | |
1075 it != replacements_.end(); ++it) | |
1076 { | |
1077 toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, | |
1078 DicomReplaceMode_InsertIfAbsent, privateCreator_); | |
1079 } | |
1080 | |
1081 // (6) Update the DICOM identifiers | |
1082 if (level_ <= ResourceType_Study && | |
1083 !IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) | |
1084 { | |
1085 if (keepStudyInstanceUid_) | |
1086 { | |
1087 LOG(WARNING) << "Modifying a study while keeping its original StudyInstanceUID: This should be avoided!"; | |
1088 } | |
1089 else | |
1090 { | |
1091 MapDicomTags(toModify, ResourceType_Study); | |
1092 } | |
1093 } | |
1094 | |
1095 if (level_ <= ResourceType_Series && | |
1096 !IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) | |
1097 { | |
1098 if (keepSeriesInstanceUid_) | |
1099 { | |
1100 LOG(WARNING) << "Modifying a series while keeping its original SeriesInstanceUID: This should be avoided!"; | |
1101 } | |
1102 else | |
1103 { | |
1104 MapDicomTags(toModify, ResourceType_Series); | |
1105 } | |
1106 } | |
1107 | |
1108 if (level_ <= ResourceType_Instance && // Always true | |
1109 !IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) | |
1110 { | |
1111 if (keepSopInstanceUid_) | |
1112 { | |
1113 LOG(WARNING) << "Modifying an instance while keeping its original SOPInstanceUID: This should be avoided!"; | |
1114 } | |
1115 else | |
1116 { | |
1117 MapDicomTags(toModify, ResourceType_Instance); | |
1118 } | |
1119 } | |
1120 | |
1121 // (7) Update the "referenced" relationships in the case of an anonymization | |
1122 if (isAnonymization_) | |
1123 { | |
1124 RelationshipsVisitor visitor(*this); | |
1125 | |
1126 if (updateReferencedRelationships_) | |
1127 { | |
1128 toModify.Apply(visitor); | |
1129 } | |
1130 else | |
1131 { | |
1132 visitor.RemoveRelationships(toModify); | |
1133 } | |
1134 } | |
1135 } | |
1136 | |
1137 | |
1138 static bool IsDatabaseKey(const DicomTag& tag) | |
1139 { | |
1140 return (tag == DICOM_TAG_PATIENT_ID || | |
1141 tag == DICOM_TAG_STUDY_INSTANCE_UID || | |
1142 tag == DICOM_TAG_SERIES_INSTANCE_UID || | |
1143 tag == DICOM_TAG_SOP_INSTANCE_UID); | |
1144 } | |
1145 | |
1146 | |
1147 static void ParseListOfTags(DicomModification& target, | |
1148 const Json::Value& query, | |
1149 DicomModification::TagOperation operation, | |
1150 bool force) | |
1151 { | |
1152 if (!query.isArray()) | |
1153 { | |
1154 throw OrthancException(ErrorCode_BadRequest); | |
1155 } | |
1156 | |
1157 for (Json::Value::ArrayIndex i = 0; i < query.size(); i++) | |
1158 { | |
1159 if (query[i].type() != Json::stringValue) | |
1160 { | |
1161 throw OrthancException(ErrorCode_BadRequest); | |
1162 } | |
1163 | |
1164 std::string name = query[i].asString(); | |
1165 | |
1166 DicomTag tag = FromDcmtkBridge::ParseTag(name); | |
1167 | |
1168 if (!force && IsDatabaseKey(tag)) | |
1169 { | |
1170 throw OrthancException(ErrorCode_BadRequest, | |
1171 "Marking tag \"" + name + "\" as to be " + | |
1172 (operation == DicomModification::TagOperation_Keep ? "kept" : "removed") + | |
1173 " requires the \"Force\" option to be set to true"); | |
1174 } | |
1175 | |
1176 switch (operation) | |
1177 { | |
1178 case DicomModification::TagOperation_Keep: | |
1179 target.Keep(tag); | |
1180 VLOG(1) << "Keep: " << name << " " << tag; | |
1181 break; | |
1182 | |
1183 case DicomModification::TagOperation_Remove: | |
1184 target.Remove(tag); | |
1185 VLOG(1) << "Remove: " << name << " " << tag; | |
1186 break; | |
1187 | |
1188 default: | |
1189 throw OrthancException(ErrorCode_InternalError); | |
1190 } | |
1191 } | |
1192 } | |
1193 | |
1194 | |
1195 static void ParseReplacements(DicomModification& target, | |
1196 const Json::Value& replacements, | |
1197 bool force) | |
1198 { | |
1199 if (!replacements.isObject()) | |
1200 { | |
1201 throw OrthancException(ErrorCode_BadRequest); | |
1202 } | |
1203 | |
1204 Json::Value::Members members = replacements.getMemberNames(); | |
1205 for (size_t i = 0; i < members.size(); i++) | |
1206 { | |
1207 const std::string& name = members[i]; | |
1208 const Json::Value& value = replacements[name]; | |
1209 | |
1210 DicomTag tag = FromDcmtkBridge::ParseTag(name); | |
1211 | |
1212 if (!force && IsDatabaseKey(tag)) | |
1213 { | |
1214 throw OrthancException(ErrorCode_BadRequest, | |
1215 "Marking tag \"" + name + "\" as to be replaced " + | |
1216 "requires the \"Force\" option to be set to true"); | |
1217 } | |
1218 | |
1219 target.Replace(tag, value, false); | |
1220 | |
1221 VLOG(1) << "Replace: " << name << " " << tag | |
1222 << " == " << value.toStyledString(); | |
1223 } | |
1224 } | |
1225 | |
1226 | |
1227 static bool GetBooleanValue(const std::string& member, | |
1228 const Json::Value& json, | |
1229 bool defaultValue) | |
1230 { | |
1231 if (!json.isMember(member)) | |
1232 { | |
1233 return defaultValue; | |
1234 } | |
1235 else if (json[member].type() == Json::booleanValue) | |
1236 { | |
1237 return json[member].asBool(); | |
1238 } | |
1239 else | |
1240 { | |
1241 throw OrthancException(ErrorCode_BadFileFormat, | |
1242 "Member \"" + member + "\" should be a Boolean value"); | |
1243 } | |
1244 } | |
1245 | |
1246 | |
1247 void DicomModification::ParseModifyRequest(const Json::Value& request) | |
1248 { | |
1249 if (!request.isObject()) | |
1250 { | |
1251 throw OrthancException(ErrorCode_BadFileFormat); | |
1252 } | |
1253 | |
1254 bool force = GetBooleanValue("Force", request, false); | |
1255 | |
1256 if (GetBooleanValue("RemovePrivateTags", request, false)) | |
1257 { | |
1258 SetRemovePrivateTags(true); | |
1259 } | |
1260 | |
1261 if (request.isMember("Remove")) | |
1262 { | |
1263 ParseListOfTags(*this, request["Remove"], TagOperation_Remove, force); | |
1264 } | |
1265 | |
1266 if (request.isMember("Replace")) | |
1267 { | |
1268 ParseReplacements(*this, request["Replace"], force); | |
1269 } | |
1270 | |
1271 // The "Keep" operation only makes sense for the tags | |
1272 // StudyInstanceUID, SeriesInstanceUID and SOPInstanceUID. Avoid | |
1273 // this feature as much as possible, as this breaks the DICOM | |
1274 // model of the real world, except if you know exactly what | |
1275 // you're doing! | |
1276 if (request.isMember("Keep")) | |
1277 { | |
1278 ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force); | |
1279 } | |
1280 | |
1281 // New in Orthanc 1.6.0 | |
1282 if (request.isMember("PrivateCreator")) | |
1283 { | |
1284 privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator"); | |
1285 } | |
1286 } | |
1287 | |
1288 | |
1289 void DicomModification::ParseAnonymizationRequest(bool& patientNameReplaced, | |
1290 const Json::Value& request) | |
1291 { | |
1292 if (!request.isObject()) | |
1293 { | |
1294 throw OrthancException(ErrorCode_BadFileFormat); | |
1295 } | |
1296 | |
1297 bool force = GetBooleanValue("Force", request, false); | |
1298 | |
1299 // As of Orthanc 1.3.0, the default anonymization is done | |
1300 // according to PS 3.15-2017c Table E.1-1 (basic profile) | |
1301 DicomVersion version = DicomVersion_2017c; | |
1302 if (request.isMember("DicomVersion")) | |
1303 { | |
1304 if (request["DicomVersion"].type() != Json::stringValue) | |
1305 { | |
1306 throw OrthancException(ErrorCode_BadFileFormat); | |
1307 } | |
1308 else | |
1309 { | |
1310 version = StringToDicomVersion(request["DicomVersion"].asString()); | |
1311 } | |
1312 } | |
1313 | |
1314 SetupAnonymization(version); | |
1315 | |
1316 std::string patientName = GetReplacementAsString(DICOM_TAG_PATIENT_NAME); | |
1317 | |
1318 if (GetBooleanValue("KeepPrivateTags", request, false)) | |
1319 { | |
1320 SetRemovePrivateTags(false); | |
1321 } | |
1322 | |
1323 if (request.isMember("Remove")) | |
1324 { | |
1325 ParseListOfTags(*this, request["Remove"], TagOperation_Remove, force); | |
1326 } | |
1327 | |
1328 if (request.isMember("Replace")) | |
1329 { | |
1330 ParseReplacements(*this, request["Replace"], force); | |
1331 } | |
1332 | |
1333 if (request.isMember("Keep")) | |
1334 { | |
1335 ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force); | |
1336 } | |
1337 | |
1338 patientNameReplaced = (IsReplaced(DICOM_TAG_PATIENT_NAME) && | |
1339 GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName); | |
1340 | |
1341 // New in Orthanc 1.6.0 | |
1342 if (request.isMember("PrivateCreator")) | |
1343 { | |
1344 privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator"); | |
1345 } | |
1346 } | |
1347 | |
1348 | |
1349 | |
1350 | |
1351 static const char* REMOVE_PRIVATE_TAGS = "RemovePrivateTags"; | |
1352 static const char* LEVEL = "Level"; | |
1353 static const char* ALLOW_MANUAL_IDENTIFIERS = "AllowManualIdentifiers"; | |
1354 static const char* KEEP_STUDY_INSTANCE_UID = "KeepStudyInstanceUID"; | |
1355 static const char* KEEP_SERIES_INSTANCE_UID = "KeepSeriesInstanceUID"; | |
1356 static const char* KEEP_SOP_INSTANCE_UID = "KeepSOPInstanceUID"; | |
1357 static const char* UPDATE_REFERENCED_RELATIONSHIPS = "UpdateReferencedRelationships"; | |
1358 static const char* IS_ANONYMIZATION = "IsAnonymization"; | |
1359 static const char* REMOVALS = "Removals"; | |
1360 static const char* CLEARINGS = "Clearings"; | |
1361 static const char* PRIVATE_TAGS_TO_KEEP = "PrivateTagsToKeep"; | |
1362 static const char* REPLACEMENTS = "Replacements"; | |
1363 static const char* MAP_PATIENTS = "MapPatients"; | |
1364 static const char* MAP_STUDIES = "MapStudies"; | |
1365 static const char* MAP_SERIES = "MapSeries"; | |
1366 static const char* MAP_INSTANCES = "MapInstances"; | |
1367 static const char* PRIVATE_CREATOR = "PrivateCreator"; // New in Orthanc 1.6.0 | |
1368 | |
1369 void DicomModification::Serialize(Json::Value& value) const | |
1370 { | |
1371 if (identifierGenerator_ != NULL) | |
1372 { | |
1373 throw OrthancException(ErrorCode_InternalError, | |
1374 "Cannot serialize a DicomModification with a custom identifier generator"); | |
1375 } | |
1376 | |
1377 value = Json::objectValue; | |
1378 value[REMOVE_PRIVATE_TAGS] = removePrivateTags_; | |
1379 value[LEVEL] = EnumerationToString(level_); | |
1380 value[ALLOW_MANUAL_IDENTIFIERS] = allowManualIdentifiers_; | |
1381 value[KEEP_STUDY_INSTANCE_UID] = keepStudyInstanceUid_; | |
1382 value[KEEP_SERIES_INSTANCE_UID] = keepSeriesInstanceUid_; | |
1383 value[KEEP_SOP_INSTANCE_UID] = keepSopInstanceUid_; | |
1384 value[UPDATE_REFERENCED_RELATIONSHIPS] = updateReferencedRelationships_; | |
1385 value[IS_ANONYMIZATION] = isAnonymization_; | |
1386 value[PRIVATE_CREATOR] = privateCreator_; | |
1387 | |
1388 SerializationToolbox::WriteSetOfTags(value, removals_, REMOVALS); | |
1389 SerializationToolbox::WriteSetOfTags(value, clearings_, CLEARINGS); | |
1390 SerializationToolbox::WriteSetOfTags(value, privateTagsToKeep_, PRIVATE_TAGS_TO_KEEP); | |
1391 | |
1392 Json::Value& tmp = value[REPLACEMENTS]; | |
1393 | |
1394 tmp = Json::objectValue; | |
1395 | |
1396 for (Replacements::const_iterator it = replacements_.begin(); | |
1397 it != replacements_.end(); ++it) | |
1398 { | |
1399 assert(it->second != NULL); | |
1400 tmp[it->first.Format()] = *it->second; | |
1401 } | |
1402 | |
1403 Json::Value& mapPatients = value[MAP_PATIENTS]; | |
1404 Json::Value& mapStudies = value[MAP_STUDIES]; | |
1405 Json::Value& mapSeries = value[MAP_SERIES]; | |
1406 Json::Value& mapInstances = value[MAP_INSTANCES]; | |
1407 | |
1408 mapPatients = Json::objectValue; | |
1409 mapStudies = Json::objectValue; | |
1410 mapSeries = Json::objectValue; | |
1411 mapInstances = Json::objectValue; | |
1412 | |
1413 for (UidMap::const_iterator it = uidMap_.begin(); it != uidMap_.end(); ++it) | |
1414 { | |
1415 Json::Value* tmp = NULL; | |
1416 | |
1417 switch (it->first.first) | |
1418 { | |
1419 case ResourceType_Patient: | |
1420 tmp = &mapPatients; | |
1421 break; | |
1422 | |
1423 case ResourceType_Study: | |
1424 tmp = &mapStudies; | |
1425 break; | |
1426 | |
1427 case ResourceType_Series: | |
1428 tmp = &mapSeries; | |
1429 break; | |
1430 | |
1431 case ResourceType_Instance: | |
1432 tmp = &mapInstances; | |
1433 break; | |
1434 | |
1435 default: | |
1436 throw OrthancException(ErrorCode_InternalError); | |
1437 } | |
1438 | |
1439 assert(tmp != NULL); | |
1440 (*tmp) [it->first.second] = it->second; | |
1441 } | |
1442 } | |
1443 | |
1444 | |
1445 void DicomModification::UnserializeUidMap(ResourceType level, | |
1446 const Json::Value& serialized, | |
1447 const char* field) | |
1448 { | |
1449 if (!serialized.isMember(field) || | |
1450 serialized[field].type() != Json::objectValue) | |
1451 { | |
1452 throw OrthancException(ErrorCode_BadFileFormat); | |
1453 } | |
1454 | |
1455 Json::Value::Members names = serialized[field].getMemberNames(); | |
1456 | |
1457 for (Json::Value::Members::const_iterator it = names.begin(); it != names.end(); ++it) | |
1458 { | |
1459 const Json::Value& value = serialized[field][*it]; | |
1460 | |
1461 if (value.type() != Json::stringValue) | |
1462 { | |
1463 throw OrthancException(ErrorCode_BadFileFormat); | |
1464 } | |
1465 else | |
1466 { | |
1467 uidMap_[std::make_pair(level, *it)] = value.asString(); | |
1468 } | |
1469 } | |
1470 } | |
1471 | |
1472 | |
1473 DicomModification::DicomModification(const Json::Value& serialized) : | |
1474 identifierGenerator_(NULL) | |
1475 { | |
1476 removePrivateTags_ = SerializationToolbox::ReadBoolean(serialized, REMOVE_PRIVATE_TAGS); | |
1477 level_ = StringToResourceType(SerializationToolbox::ReadString(serialized, LEVEL).c_str()); | |
1478 allowManualIdentifiers_ = SerializationToolbox::ReadBoolean(serialized, ALLOW_MANUAL_IDENTIFIERS); | |
1479 keepStudyInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_STUDY_INSTANCE_UID); | |
1480 keepSeriesInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SERIES_INSTANCE_UID); | |
1481 keepSopInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SOP_INSTANCE_UID); | |
1482 updateReferencedRelationships_ = SerializationToolbox::ReadBoolean | |
1483 (serialized, UPDATE_REFERENCED_RELATIONSHIPS); | |
1484 isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION); | |
1485 | |
1486 if (serialized.isMember(PRIVATE_CREATOR)) | |
1487 { | |
1488 privateCreator_ = SerializationToolbox::ReadString(serialized, PRIVATE_CREATOR); | |
1489 } | |
1490 | |
1491 SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS); | |
1492 SerializationToolbox::ReadSetOfTags(clearings_, serialized, CLEARINGS); | |
1493 SerializationToolbox::ReadSetOfTags(privateTagsToKeep_, serialized, PRIVATE_TAGS_TO_KEEP); | |
1494 | |
1495 if (!serialized.isMember(REPLACEMENTS) || | |
1496 serialized[REPLACEMENTS].type() != Json::objectValue) | |
1497 { | |
1498 throw OrthancException(ErrorCode_BadFileFormat); | |
1499 } | |
1500 | |
1501 Json::Value::Members names = serialized[REPLACEMENTS].getMemberNames(); | |
1502 | |
1503 for (Json::Value::Members::const_iterator it = names.begin(); it != names.end(); ++it) | |
1504 { | |
1505 DicomTag tag(0, 0); | |
1506 if (!DicomTag::ParseHexadecimal(tag, it->c_str())) | |
1507 { | |
1508 throw OrthancException(ErrorCode_BadFileFormat); | |
1509 } | |
1510 else | |
1511 { | |
1512 const Json::Value& value = serialized[REPLACEMENTS][*it]; | |
1513 replacements_.insert(std::make_pair(tag, new Json::Value(value))); | |
1514 } | |
1515 } | |
1516 | |
1517 UnserializeUidMap(ResourceType_Patient, serialized, MAP_PATIENTS); | |
1518 UnserializeUidMap(ResourceType_Study, serialized, MAP_STUDIES); | |
1519 UnserializeUidMap(ResourceType_Series, serialized, MAP_SERIES); | |
1520 UnserializeUidMap(ResourceType_Instance, serialized, MAP_INSTANCES); | |
1521 } | |
1522 } |