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 }