comparison OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp @ 2519:2e6b7862ccf2

ParseAnonymizationRequest/ParseModifyRequest now in DicomModification
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 29 Mar 2018 14:52:11 +0200
parents 63d2cc0fb40a
children 6db878376018
comparison
equal deleted inserted replaced
2518:63d2cc0fb40a 2519:2e6b7862ccf2
44 44
45 namespace Orthanc 45 namespace Orthanc
46 { 46 {
47 // Modification of DICOM instances ------------------------------------------ 47 // Modification of DICOM instances ------------------------------------------
48 48
49 enum TagOperation 49
50 {
51 TagOperation_Keep,
52 TagOperation_Remove
53 };
54
55 static bool IsDatabaseKey(const DicomTag& tag)
56 {
57 return (tag == DICOM_TAG_PATIENT_ID ||
58 tag == DICOM_TAG_STUDY_INSTANCE_UID ||
59 tag == DICOM_TAG_SERIES_INSTANCE_UID ||
60 tag == DICOM_TAG_SOP_INSTANCE_UID);
61 }
62
63 static void ParseListOfTags(DicomModification& target,
64 const Json::Value& query,
65 TagOperation operation,
66 bool force)
67 {
68 if (!query.isArray())
69 {
70 throw OrthancException(ErrorCode_BadRequest);
71 }
72
73 for (Json::Value::ArrayIndex i = 0; i < query.size(); i++)
74 {
75 std::string name = query[i].asString();
76
77 DicomTag tag = FromDcmtkBridge::ParseTag(name);
78
79 if (!force && IsDatabaseKey(tag))
80 {
81 LOG(ERROR) << "Marking tag \"" << name << "\" as to be "
82 << (operation == TagOperation_Keep ? "kept" : "removed")
83 << " requires the \"Force\" option to be set to true";
84 throw OrthancException(ErrorCode_BadRequest);
85 }
86
87 switch (operation)
88 {
89 case TagOperation_Keep:
90 target.Keep(tag);
91 VLOG(1) << "Keep: " << name << " " << tag << std::endl;
92 break;
93
94 case TagOperation_Remove:
95 target.Remove(tag);
96 VLOG(1) << "Remove: " << name << " " << tag << std::endl;
97 break;
98
99 default:
100 throw OrthancException(ErrorCode_InternalError);
101 }
102 }
103 }
104
105
106 static void ParseReplacements(DicomModification& target,
107 const Json::Value& replacements,
108 bool force)
109 {
110 if (!replacements.isObject())
111 {
112 throw OrthancException(ErrorCode_BadRequest);
113 }
114
115 Json::Value::Members members = replacements.getMemberNames();
116 for (size_t i = 0; i < members.size(); i++)
117 {
118 const std::string& name = members[i];
119 const Json::Value& value = replacements[name];
120
121 DicomTag tag = FromDcmtkBridge::ParseTag(name);
122
123 if (!force && IsDatabaseKey(tag))
124 {
125 LOG(ERROR) << "Marking tag \"" << name << "\" as to be replaced "
126 << "requires the \"Force\" option to be set to true";
127 throw OrthancException(ErrorCode_BadRequest);
128 }
129
130 target.Replace(tag, value, false);
131
132 VLOG(1) << "Replace: " << name << " " << tag
133 << " == " << value.toStyledString() << std::endl;
134 }
135 }
136
137
138 static std::string GeneratePatientName(ServerContext& context) 50 static std::string GeneratePatientName(ServerContext& context)
139 { 51 {
140 uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence); 52 uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence);
141 return "Anonymized" + boost::lexical_cast<std::string>(seq); 53 return "Anonymized" + boost::lexical_cast<std::string>(seq);
142 } 54 }
143 55
144 56
145 static bool GetBooleanValue(const std::string& member,
146 const Json::Value& json,
147 bool defaultValue)
148 {
149 if (!json.isMember(member))
150 {
151 return defaultValue;
152 }
153 else if (json[member].type() == Json::booleanValue)
154 {
155 return json[member].asBool();
156 }
157 else
158 {
159 LOG(ERROR) << "Member \"" << member << "\" should be a Boolean value";
160 throw OrthancException(ErrorCode_BadFileFormat);
161 }
162 }
163
164
165 void OrthancRestApi::ParseModifyRequest(DicomModification& target,
166 const Json::Value& request)
167 {
168 if (!request.isObject())
169 {
170 throw OrthancException(ErrorCode_BadFileFormat);
171 }
172
173 bool force = GetBooleanValue("Force", request, false);
174
175 if (GetBooleanValue("RemovePrivateTags", request, false))
176 {
177 target.SetRemovePrivateTags(true);
178 }
179
180 if (request.isMember("Remove"))
181 {
182 ParseListOfTags(target, request["Remove"], TagOperation_Remove, force);
183 }
184
185 if (request.isMember("Replace"))
186 {
187 ParseReplacements(target, request["Replace"], force);
188 }
189
190 // The "Keep" operation only makes sense for the tags
191 // StudyInstanceUID, SeriesInstanceUID and SOPInstanceUID. Avoid
192 // this feature as much as possible, as this breaks the DICOM
193 // model of the real world, except if you know exactly what
194 // you're doing!
195 if (request.isMember("Keep"))
196 {
197 ParseListOfTags(target, request["Keep"], TagOperation_Keep, force);
198 }
199 }
200
201
202 static void ParseAnonymizationRequest(DicomModification& target,
203 bool& patientNameReplaced,
204 const Json::Value& request)
205 {
206 if (!request.isObject())
207 {
208 throw OrthancException(ErrorCode_BadFileFormat);
209 }
210
211 bool force = GetBooleanValue("Force", request, false);
212
213 // As of Orthanc 1.3.0, the default anonymization is done
214 // according to PS 3.15-2017c Table E.1-1 (basic profile)
215 DicomVersion version = DicomVersion_2017c;
216 if (request.isMember("DicomVersion"))
217 {
218 if (request["DicomVersion"].type() != Json::stringValue)
219 {
220 throw OrthancException(ErrorCode_BadFileFormat);
221 }
222 else
223 {
224 version = StringToDicomVersion(request["DicomVersion"].asString());
225 }
226 }
227
228 target.SetupAnonymization(version);
229
230 std::string patientName = target.GetReplacementAsString(DICOM_TAG_PATIENT_NAME);
231
232 if (GetBooleanValue("KeepPrivateTags", request, false))
233 {
234 target.SetRemovePrivateTags(false);
235 }
236
237 if (request.isMember("Remove"))
238 {
239 ParseListOfTags(target, request["Remove"], TagOperation_Remove, force);
240 }
241
242 if (request.isMember("Replace"))
243 {
244 ParseReplacements(target, request["Replace"], force);
245 }
246
247 if (request.isMember("Keep"))
248 {
249 ParseListOfTags(target, request["Keep"], TagOperation_Keep, force);
250 }
251
252 patientNameReplaced = (target.IsReplaced(DICOM_TAG_PATIENT_NAME) &&
253 target.GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName);
254 }
255
256
257 static void ParseModifyRequest(DicomModification& target, 57 static void ParseModifyRequest(DicomModification& target,
258 const RestApiPostCall& call) 58 const RestApiPostCall& call)
259 { 59 {
260 // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"}}' 60 // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"}}'
261 61
262 Json::Value request; 62 Json::Value request;
263 if (call.ParseJsonRequest(request)) 63 if (call.ParseJsonRequest(request))
264 { 64 {
265 OrthancRestApi::ParseModifyRequest(target, request); 65 target.ParseModifyRequest(request);
266 } 66 }
267 else 67 else
268 { 68 {
269 throw OrthancException(ErrorCode_BadFileFormat); 69 throw OrthancException(ErrorCode_BadFileFormat);
270 } 70 }
279 Json::Value request; 79 Json::Value request;
280 if (call.ParseJsonRequest(request) && 80 if (call.ParseJsonRequest(request) &&
281 request.isObject()) 81 request.isObject())
282 { 82 {
283 bool patientNameReplaced; 83 bool patientNameReplaced;
284 ParseAnonymizationRequest(target, patientNameReplaced, request); 84 target.ParseAnonymizationRequest(patientNameReplaced, request);
285 85
286 if (patientNameReplaced) 86 if (patientNameReplaced)
287 { 87 {
288 // Overwrite the random Patient's Name by one that is more 88 // Overwrite the random Patient's Name by one that is more
289 // user-friendly (provided none was specified by the user) 89 // user-friendly (provided none was specified by the user)