786
|
1 /**
|
|
2 * Orthanc - A Lightweight, RESTful DICOM Store
|
|
3 * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
|
|
4 * Belgium
|
|
5 *
|
|
6 * This program is free software: you can redistribute it and/or
|
|
7 * modify it under the terms of the GNU General Public License as
|
|
8 * published by the Free Software Foundation, either version 3 of the
|
|
9 * License, or (at your option) any later version.
|
|
10 *
|
|
11 * In addition, as a special exception, the copyright holders of this
|
|
12 * program give permission to link the code of its release with the
|
|
13 * OpenSSL project's "OpenSSL" library (or with modified versions of it
|
|
14 * that use the same license as the "OpenSSL" library), and distribute
|
|
15 * the linked executables. You must obey the GNU General Public License
|
|
16 * in all respects for all of the code used other than "OpenSSL". If you
|
|
17 * modify file(s) with this exception, you may extend this exception to
|
|
18 * your version of the file(s), but you are not obligated to do so. If
|
|
19 * you do not wish to do so, delete this exception statement from your
|
|
20 * version. If you delete this exception statement from all source files
|
|
21 * in the program, then also delete it here.
|
|
22 *
|
|
23 * This program is distributed in the hope that it will be useful, but
|
|
24 * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
26 * General Public License for more details.
|
|
27 *
|
|
28 * You should have received a copy of the GNU General Public License
|
|
29 * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
30 **/
|
|
31
|
|
32
|
|
33 #include "DicomModification.h"
|
|
34
|
|
35 #include "../Core/OrthancException.h"
|
|
36
|
|
37 namespace Orthanc
|
|
38 {
|
|
39 void DicomModification::MapDicomIdentifier(ParsedDicomFile& dicom,
|
|
40 DicomRootLevel level)
|
|
41 {
|
|
42 std::auto_ptr<DicomTag> tag;
|
|
43
|
|
44 switch (level)
|
|
45 {
|
|
46 case DicomRootLevel_Study:
|
|
47 tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID));
|
|
48 break;
|
|
49
|
|
50 case DicomRootLevel_Series:
|
|
51 tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID));
|
|
52 break;
|
|
53
|
|
54 case DicomRootLevel_Instance:
|
|
55 tag.reset(new DicomTag(DICOM_TAG_SOP_INSTANCE_UID));
|
|
56 break;
|
|
57
|
|
58 default:
|
|
59 throw OrthancException(ErrorCode_InternalError);
|
|
60 }
|
|
61
|
|
62 std::string original;
|
|
63 if (!dicom.GetTagValue(original, *tag))
|
|
64 {
|
|
65 original = "";
|
|
66 }
|
|
67
|
|
68 std::string mapped;
|
|
69
|
|
70 UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original));
|
|
71 if (previous == uidMap_.end())
|
|
72 {
|
|
73 mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level);
|
|
74 uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped));
|
|
75 }
|
|
76 else
|
|
77 {
|
|
78 mapped = previous->second;
|
|
79 }
|
|
80
|
|
81 dicom.Replace(*tag, mapped);
|
|
82 }
|
|
83
|
|
84 DicomModification::DicomModification()
|
|
85 {
|
|
86 removePrivateTags_ = false;
|
|
87 level_ = DicomRootLevel_Instance;
|
|
88 }
|
|
89
|
|
90 void DicomModification::Reset(const DicomTag& tag)
|
|
91 {
|
|
92 removals_.erase(tag);
|
|
93 replacements_.erase(tag);
|
|
94 }
|
|
95
|
|
96 void DicomModification::Remove(const DicomTag& tag)
|
|
97 {
|
|
98 removals_.insert(tag);
|
|
99 replacements_.erase(tag);
|
|
100 }
|
|
101
|
|
102 bool DicomModification::IsRemoved(const DicomTag& tag) const
|
|
103 {
|
|
104 return removals_.find(tag) != removals_.end();
|
|
105 }
|
|
106
|
|
107 void DicomModification::Replace(const DicomTag& tag,
|
|
108 const std::string& value)
|
|
109 {
|
|
110 removals_.erase(tag);
|
|
111 replacements_[tag] = value;
|
|
112 }
|
|
113
|
|
114 bool DicomModification::IsReplaced(const DicomTag& tag) const
|
|
115 {
|
|
116 return replacements_.find(tag) != replacements_.end();
|
|
117 }
|
|
118
|
|
119 const std::string& DicomModification::GetReplacement(const DicomTag& tag) const
|
|
120 {
|
|
121 Replacements::const_iterator it = replacements_.find(tag);
|
|
122
|
|
123 if (it == replacements_.end())
|
|
124 {
|
|
125 throw OrthancException(ErrorCode_InexistentItem);
|
|
126 }
|
|
127 else
|
|
128 {
|
|
129 return it->second;
|
|
130 }
|
|
131 }
|
|
132
|
|
133 void DicomModification::SetRemovePrivateTags(bool removed)
|
|
134 {
|
|
135 removePrivateTags_ = removed;
|
|
136 }
|
|
137
|
|
138 void DicomModification::SetLevel(DicomRootLevel level)
|
|
139 {
|
|
140 uidMap_.clear();
|
|
141 level_ = level;
|
|
142 }
|
|
143
|
|
144 void DicomModification::SetupAnonymization()
|
|
145 {
|
|
146 removals_.clear();
|
|
147 replacements_.clear();
|
|
148 removePrivateTags_ = true;
|
|
149 level_ = DicomRootLevel_Patient;
|
|
150 uidMap_.clear();
|
|
151
|
|
152 // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles
|
|
153 removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID
|
|
154 //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply()
|
|
155 removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number
|
|
156 removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name
|
|
157 removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address
|
|
158 removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name
|
|
159 removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address
|
|
160 removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers
|
|
161 removals_.insert(DicomTag(0x0008, 0x1010)); // Station Name
|
|
162 removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description
|
|
163 removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description
|
|
164 removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name
|
|
165 removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record
|
|
166 removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name
|
|
167 removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study
|
|
168 removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name
|
|
169 removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description
|
|
170 removals_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID
|
|
171 removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description
|
|
172 //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*)
|
|
173 //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*)
|
|
174 removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date
|
|
175 removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time
|
|
176 removals_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex
|
|
177 removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids
|
|
178 removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names
|
|
179 removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age
|
|
180 removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size
|
|
181 removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight
|
|
182 removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator
|
|
183 removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group
|
|
184 removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation
|
|
185 removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History
|
|
186 removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments
|
|
187 removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number
|
|
188 removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name
|
|
189 //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply()
|
|
190 //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply()
|
|
191 removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID
|
|
192 removals_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID
|
|
193 removals_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID
|
|
194 removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments
|
|
195 removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence
|
|
196 removals_.insert(DicomTag(0x0040, 0xa124)); // UID
|
|
197 removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence
|
|
198 removals_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID
|
|
199 removals_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID
|
|
200 removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID
|
|
201
|
|
202 // Some more removals (from the experience of DICOM files at the CHU of Liege)
|
|
203 removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address
|
|
204 removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician
|
|
205 removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers
|
|
206 removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts
|
|
207
|
|
208 // Set the DeidentificationMethod tag
|
|
209 replacements_.insert(std::make_pair(DicomTag(0x0012, 0x0063), "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"));
|
|
210
|
|
211 // Set the PatientIdentityRemoved tag
|
|
212 replacements_.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES"));
|
|
213
|
|
214 // (*) Choose a random patient name and ID
|
|
215 std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient);
|
|
216 replacements_[DICOM_TAG_PATIENT_ID] = patientId;
|
|
217 replacements_[DICOM_TAG_PATIENT_NAME] = patientId;
|
|
218 }
|
|
219
|
|
220 void DicomModification::Apply(ParsedDicomFile& toModify)
|
|
221 {
|
|
222 // Check the request
|
|
223 assert(DicomRootLevel_Patient + 1 == DicomRootLevel_Study &&
|
|
224 DicomRootLevel_Study + 1 == DicomRootLevel_Series &&
|
|
225 DicomRootLevel_Series + 1 == DicomRootLevel_Instance);
|
|
226
|
|
227 if (IsRemoved(DICOM_TAG_PATIENT_ID) ||
|
|
228 IsRemoved(DICOM_TAG_STUDY_INSTANCE_UID) ||
|
|
229 IsRemoved(DICOM_TAG_SERIES_INSTANCE_UID) ||
|
|
230 IsRemoved(DICOM_TAG_SOP_INSTANCE_UID))
|
|
231 {
|
|
232 throw OrthancException(ErrorCode_BadRequest);
|
|
233 }
|
|
234
|
|
235 if (level_ == DicomRootLevel_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID))
|
|
236 {
|
|
237 throw OrthancException(ErrorCode_BadRequest);
|
|
238 }
|
|
239
|
|
240 if (level_ > DicomRootLevel_Patient && IsReplaced(DICOM_TAG_PATIENT_ID))
|
|
241 {
|
|
242 throw OrthancException(ErrorCode_BadRequest);
|
|
243 }
|
|
244
|
|
245 if (level_ > DicomRootLevel_Study && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
|
|
246 {
|
|
247 throw OrthancException(ErrorCode_BadRequest);
|
|
248 }
|
|
249
|
|
250 if (level_ > DicomRootLevel_Series && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
|
|
251 {
|
|
252 throw OrthancException(ErrorCode_BadRequest);
|
|
253 }
|
|
254
|
|
255 // (1) Remove the private tags, if need be
|
|
256 if (removePrivateTags_)
|
|
257 {
|
|
258 toModify.RemovePrivateTags();
|
|
259 }
|
|
260
|
|
261 // (2) Remove the tags specified by the user
|
|
262 for (Removals::const_iterator it = removals_.begin();
|
|
263 it != removals_.end(); ++it)
|
|
264 {
|
|
265 toModify.Remove(*it);
|
|
266 }
|
|
267
|
|
268 // (3) Replace the tags
|
|
269 for (Replacements::const_iterator it = replacements_.begin();
|
|
270 it != replacements_.end(); ++it)
|
|
271 {
|
|
272 toModify.Replace(it->first, it->second, DicomReplaceMode_InsertIfAbsent);
|
|
273 }
|
|
274
|
|
275 // (4) Update the DICOM identifiers
|
|
276 if (level_ <= DicomRootLevel_Study)
|
|
277 {
|
|
278 MapDicomIdentifier(toModify, DicomRootLevel_Study);
|
|
279 }
|
|
280
|
|
281 if (level_ <= DicomRootLevel_Series)
|
|
282 {
|
|
283 MapDicomIdentifier(toModify, DicomRootLevel_Series);
|
|
284 }
|
|
285
|
|
286 if (level_ <= DicomRootLevel_Instance) // Always true
|
|
287 {
|
|
288 MapDicomIdentifier(toModify, DicomRootLevel_Instance);
|
|
289 }
|
|
290 }
|
|
291 }
|