comparison UnitTestsSources/FromDcmtk.cpp @ 786:b6d6b65142e8

DicomModification
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 05 May 2014 13:07:10 +0200
parents 7cbed653476f
children ecedd89055db
comparison
equal deleted inserted replaced
785:7cbed653476f 786:b6d6b65142e8
1 #include "gtest/gtest.h" 1 #include "gtest/gtest.h"
2 2
3 #include "../OrthancServer/FromDcmtkBridge.h" 3 #include "../OrthancServer/FromDcmtkBridge.h"
4 #include "../OrthancServer/OrthancInitialization.h" 4 #include "../OrthancServer/OrthancInitialization.h"
5 #include "../OrthancServer/DicomModification.h"
5 #include "../Core/OrthancException.h" 6 #include "../Core/OrthancException.h"
6 7
7 using namespace Orthanc; 8 using namespace Orthanc;
8 9
9 TEST(DicomFormat, Tag) 10 TEST(DicomFormat, Tag)
20 21
21 // Test ==() and !=() operators 22 // Test ==() and !=() operators
22 ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020)); 23 ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020));
23 ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020)); 24 ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020));
24 } 25 }
25
26
27 namespace Orthanc
28 {
29 class DicomModification
30 {
31 /**
32 * Process:
33 * (1) Remove private tags
34 * (2) Remove tags specified by the user
35 * (3) Replace tags
36 **/
37
38 private:
39 typedef std::set<DicomTag> Removals;
40 typedef std::map<DicomTag, std::string> Replacements;
41 typedef std::map< std::pair<DicomRootLevel, std::string>, std::string> UidMap;
42
43 Removals removals_;
44 Replacements replacements_;
45 bool removePrivateTags_;
46 DicomRootLevel level_;
47 UidMap uidMap_;
48
49 void MapDicomIdentifier(ParsedDicomFile& dicom,
50 DicomRootLevel level)
51 {
52 std::auto_ptr<DicomTag> tag;
53
54 switch (level)
55 {
56 case DicomRootLevel_Study:
57 tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID));
58 break;
59
60 case DicomRootLevel_Series:
61 tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID));
62 break;
63
64 case DicomRootLevel_Instance:
65 tag.reset(new DicomTag(DICOM_TAG_SOP_INSTANCE_UID));
66 break;
67
68 default:
69 throw OrthancException(ErrorCode_InternalError);
70 }
71
72 std::string original;
73 if (!dicom.GetTagValue(original, *tag))
74 {
75 original = "";
76 }
77
78 std::string mapped;
79 //bool isNew;
80
81 UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original));
82 if (previous == uidMap_.end())
83 {
84 mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level);
85 uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped));
86 //isNew = true;
87 }
88 else
89 {
90 mapped = previous->second;
91 //isNew = false;
92 }
93
94 dicom.Replace(*tag, mapped);
95
96 //return isNew;
97 }
98
99 public:
100 DicomModification()
101 {
102 removePrivateTags_ = false;
103 level_ = DicomRootLevel_Instance;
104 }
105
106 void Keep(const DicomTag& tag)
107 {
108 removals_.erase(tag);
109 }
110
111 void Remove(const DicomTag& tag)
112 {
113 removals_.insert(tag);
114 replacements_.erase(tag);
115 }
116
117 bool IsRemoved(const DicomTag& tag) const
118 {
119 return removals_.find(tag) != removals_.end();
120 }
121
122 void Replace(const DicomTag& tag,
123 const std::string& value)
124 {
125 removals_.erase(tag);
126 replacements_[tag] = value;
127 }
128
129 bool IsReplaced(const DicomTag& tag) const
130 {
131 return replacements_.find(tag) != replacements_.end();
132 }
133
134 const std::string& GetReplacement(const DicomTag& tag) const
135 {
136 Replacements::const_iterator it = replacements_.find(tag);
137
138 if (it == replacements_.end())
139 {
140 throw OrthancException(ErrorCode_InexistentItem);
141 }
142 else
143 {
144 return it->second;
145 }
146 }
147
148 void SetRemovePrivateTags(bool removed)
149 {
150 removePrivateTags_ = removed;
151 }
152
153 bool ArePrivateTagsRemoved() const
154 {
155 return removePrivateTags_;
156 }
157
158 void SetLevel(DicomRootLevel level)
159 {
160 uidMap_.clear();
161 level_ = level;
162 }
163
164 DicomRootLevel GetLevel() const
165 {
166 return level_;
167 }
168
169 void SetupAnonymization()
170 {
171 removals_.clear();
172 replacements_.clear();
173 removePrivateTags_ = true;
174 level_ = DicomRootLevel_Patient;
175 uidMap_.clear();
176
177 // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles
178 removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID
179 //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply()
180 removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number
181 removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name
182 removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address
183 removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name
184 removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address
185 removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers
186 removals_.insert(DicomTag(0x0008, 0x1010)); // Station Name
187 removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description
188 removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description
189 removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name
190 removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record
191 removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name
192 removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study
193 removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name
194 removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description
195 removals_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID
196 removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description
197 //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*)
198 //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*)
199 removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date
200 removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time
201 removals_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex
202 removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids
203 removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names
204 removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age
205 removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size
206 removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight
207 removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator
208 removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group
209 removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation
210 removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History
211 removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments
212 removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number
213 removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name
214 //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply()
215 //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply()
216 removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID
217 removals_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID
218 removals_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID
219 removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments
220 removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence
221 removals_.insert(DicomTag(0x0040, 0xa124)); // UID
222 removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence
223 removals_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID
224 removals_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID
225 removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID
226
227 // Some more removals (from the experience of DICOM files at the CHU of Liege)
228 removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address
229 removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician
230 removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers
231 removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts
232
233 // Set the DeidentificationMethod tag
234 replacements_.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"));
235
236 // Set the PatientIdentityRemoved tag
237 replacements_.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES"));
238
239 // (*) Choose a random patient name and ID
240 std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient);
241 replacements_[DICOM_TAG_PATIENT_ID] = patientId;
242 replacements_[DICOM_TAG_PATIENT_NAME] = patientId;
243 }
244
245 void Apply(ParsedDicomFile& toModify)
246 {
247 // Check the request
248 assert(DicomRootLevel_Patient + 1 == DicomRootLevel_Study &&
249 DicomRootLevel_Study + 1 == DicomRootLevel_Series &&
250 DicomRootLevel_Series + 1 == DicomRootLevel_Instance);
251
252 if (IsRemoved(DICOM_TAG_PATIENT_ID) ||
253 IsRemoved(DICOM_TAG_STUDY_INSTANCE_UID) ||
254 IsRemoved(DICOM_TAG_SERIES_INSTANCE_UID) ||
255 IsRemoved(DICOM_TAG_SOP_INSTANCE_UID))
256 {
257 throw OrthancException(ErrorCode_BadRequest);
258 }
259
260 if (level_ == DicomRootLevel_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID))
261 {
262 throw OrthancException(ErrorCode_BadRequest);
263 }
264
265 if (level_ > DicomRootLevel_Patient && IsReplaced(DICOM_TAG_PATIENT_ID))
266 {
267 throw OrthancException(ErrorCode_BadRequest);
268 }
269
270 if (level_ > DicomRootLevel_Study && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
271 {
272 throw OrthancException(ErrorCode_BadRequest);
273 }
274
275 if (level_ > DicomRootLevel_Series && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
276 {
277 throw OrthancException(ErrorCode_BadRequest);
278 }
279
280 // (1) Remove the private tags, if need be
281 if (removePrivateTags_)
282 {
283 toModify.RemovePrivateTags();
284 }
285
286 // (2) Remove the tags specified by the user
287 for (Removals::const_iterator it = removals_.begin();
288 it != removals_.end(); ++it)
289 {
290 toModify.Remove(*it);
291 }
292
293 // (3) Replace the tags
294 for (Replacements::const_iterator it = replacements_.begin();
295 it != replacements_.end(); ++it)
296 {
297 toModify.Replace(it->first, it->second, DicomReplaceMode_InsertIfAbsent);
298 }
299
300 // (4) Update the DICOM identifiers
301 if (level_ <= DicomRootLevel_Study)
302 {
303 MapDicomIdentifier(toModify, DicomRootLevel_Study);
304 }
305
306 if (level_ <= DicomRootLevel_Series)
307 {
308 MapDicomIdentifier(toModify, DicomRootLevel_Series);
309 }
310
311 if (level_ <= DicomRootLevel_Instance) // Always true
312 {
313 MapDicomIdentifier(toModify, DicomRootLevel_Instance);
314 }
315 }
316 };
317 }
318
319 26
320 27
321 TEST(DicomModification, Basic) 28 TEST(DicomModification, Basic)
322 { 29 {
323 DicomModification m; 30 DicomModification m;