Mercurial > hg > orthanc
comparison OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.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 | OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp@5fe8c6d3212e |
children | 05b8fd21089c |
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 "../PrecompiledHeadersServer.h" | |
35 #include "OrthancRestApi.h" | |
36 | |
37 #include "../../Core/DicomParsing/FromDcmtkBridge.h" | |
38 #include "../../Core/Logging.h" | |
39 #include "../../Core/SerializationToolbox.h" | |
40 #include "../OrthancConfiguration.h" | |
41 #include "../ServerContext.h" | |
42 #include "../ServerJobs/MergeStudyJob.h" | |
43 #include "../ServerJobs/ResourceModificationJob.h" | |
44 #include "../ServerJobs/SplitStudyJob.h" | |
45 | |
46 #include <boost/lexical_cast.hpp> | |
47 #include <boost/algorithm/string/predicate.hpp> | |
48 | |
49 namespace Orthanc | |
50 { | |
51 // Modification of DICOM instances ------------------------------------------ | |
52 | |
53 | |
54 static std::string GeneratePatientName(ServerContext& context) | |
55 { | |
56 uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence); | |
57 return "Anonymized" + boost::lexical_cast<std::string>(seq); | |
58 } | |
59 | |
60 | |
61 static void ParseModifyRequest(Json::Value& request, | |
62 DicomModification& target, | |
63 const RestApiPostCall& call) | |
64 { | |
65 // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"},"Priority":9}' | |
66 | |
67 { | |
68 OrthancConfiguration::ReaderLock lock; | |
69 target.SetPrivateCreator(lock.GetConfiguration().GetDefaultPrivateCreator()); | |
70 } | |
71 | |
72 if (call.ParseJsonRequest(request)) | |
73 { | |
74 target.ParseModifyRequest(request); | |
75 } | |
76 else | |
77 { | |
78 throw OrthancException(ErrorCode_BadFileFormat); | |
79 } | |
80 } | |
81 | |
82 | |
83 static void ParseAnonymizationRequest(Json::Value& request, | |
84 DicomModification& target, | |
85 RestApiPostCall& call) | |
86 { | |
87 // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": true,"Remove":["Modality"]}' > Anonymized.dcm | |
88 | |
89 { | |
90 OrthancConfiguration::ReaderLock lock; | |
91 target.SetPrivateCreator(lock.GetConfiguration().GetDefaultPrivateCreator()); | |
92 } | |
93 | |
94 if (call.ParseJsonRequest(request) && | |
95 request.isObject()) | |
96 { | |
97 bool patientNameReplaced; | |
98 target.ParseAnonymizationRequest(patientNameReplaced, request); | |
99 | |
100 if (patientNameReplaced) | |
101 { | |
102 // Overwrite the random Patient's Name by one that is more | |
103 // user-friendly (provided none was specified by the user) | |
104 target.Replace(DICOM_TAG_PATIENT_NAME, GeneratePatientName(OrthancRestApi::GetContext(call)), true); | |
105 } | |
106 } | |
107 else | |
108 { | |
109 throw OrthancException(ErrorCode_BadFileFormat); | |
110 } | |
111 } | |
112 | |
113 | |
114 static void AnonymizeOrModifyInstance(DicomModification& modification, | |
115 RestApiPostCall& call, | |
116 bool transcode, | |
117 DicomTransferSyntax targetSyntax) | |
118 { | |
119 ServerContext& context = OrthancRestApi::GetContext(call); | |
120 std::string id = call.GetUriComponent("id", ""); | |
121 | |
122 std::unique_ptr<ParsedDicomFile> modified; | |
123 | |
124 { | |
125 ServerContext::DicomCacheLocker locker(context, id); | |
126 modified.reset(locker.GetDicom().Clone(true)); | |
127 } | |
128 | |
129 modification.Apply(*modified); | |
130 | |
131 if (transcode) | |
132 { | |
133 IDicomTranscoder::DicomImage source; | |
134 source.AcquireParsed(*modified); // "modified" is invalid below this point | |
135 | |
136 IDicomTranscoder::DicomImage transcoded; | |
137 | |
138 std::set<DicomTransferSyntax> s; | |
139 s.insert(targetSyntax); | |
140 | |
141 if (context.Transcode(transcoded, source, s, true)) | |
142 { | |
143 call.GetOutput().AnswerBuffer(transcoded.GetBufferData(), | |
144 transcoded.GetBufferSize(), MimeType_Dicom); | |
145 } | |
146 else | |
147 { | |
148 throw OrthancException(ErrorCode_InternalError, | |
149 "Cannot transcode to transfer syntax: " + | |
150 std::string(GetTransferSyntaxUid(targetSyntax))); | |
151 } | |
152 } | |
153 else | |
154 { | |
155 modified->Answer(call.GetOutput()); | |
156 } | |
157 } | |
158 | |
159 | |
160 static void ModifyInstance(RestApiPostCall& call) | |
161 { | |
162 DicomModification modification; | |
163 modification.SetAllowManualIdentifiers(true); | |
164 | |
165 Json::Value request; | |
166 ParseModifyRequest(request, modification, call); | |
167 | |
168 if (modification.IsReplaced(DICOM_TAG_PATIENT_ID)) | |
169 { | |
170 modification.SetLevel(ResourceType_Patient); | |
171 } | |
172 else if (modification.IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) | |
173 { | |
174 modification.SetLevel(ResourceType_Study); | |
175 } | |
176 else if (modification.IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) | |
177 { | |
178 modification.SetLevel(ResourceType_Series); | |
179 } | |
180 else | |
181 { | |
182 modification.SetLevel(ResourceType_Instance); | |
183 } | |
184 | |
185 static const char* TRANSCODE = "Transcode"; | |
186 if (request.isMember(TRANSCODE)) | |
187 { | |
188 std::string s = SerializationToolbox::ReadString(request, TRANSCODE); | |
189 | |
190 DicomTransferSyntax syntax; | |
191 if (LookupTransferSyntax(syntax, s)) | |
192 { | |
193 AnonymizeOrModifyInstance(modification, call, true, syntax); | |
194 } | |
195 else | |
196 { | |
197 throw OrthancException(ErrorCode_ParameterOutOfRange, "Unknown transfer syntax: " + s); | |
198 } | |
199 } | |
200 else | |
201 { | |
202 AnonymizeOrModifyInstance(modification, call, false /* no transcoding */, | |
203 DicomTransferSyntax_LittleEndianImplicit /* unused */); | |
204 } | |
205 } | |
206 | |
207 | |
208 static void AnonymizeInstance(RestApiPostCall& call) | |
209 { | |
210 DicomModification modification; | |
211 modification.SetAllowManualIdentifiers(true); | |
212 | |
213 Json::Value request; | |
214 ParseAnonymizationRequest(request, modification, call); | |
215 | |
216 AnonymizeOrModifyInstance(modification, call, false /* no transcoding */, | |
217 DicomTransferSyntax_LittleEndianImplicit /* unused */); | |
218 } | |
219 | |
220 | |
221 static void SetKeepSource(CleaningInstancesJob& job, | |
222 const Json::Value& body) | |
223 { | |
224 static const char* KEEP_SOURCE = "KeepSource"; | |
225 if (body.isMember(KEEP_SOURCE)) | |
226 { | |
227 job.SetKeepSource(SerializationToolbox::ReadBoolean(body, KEEP_SOURCE)); | |
228 } | |
229 } | |
230 | |
231 | |
232 static void SubmitModificationJob(std::unique_ptr<DicomModification>& modification, | |
233 bool isAnonymization, | |
234 RestApiPostCall& call, | |
235 const Json::Value& body, | |
236 ResourceType level) | |
237 { | |
238 ServerContext& context = OrthancRestApi::GetContext(call); | |
239 | |
240 std::unique_ptr<ResourceModificationJob> job(new ResourceModificationJob(context)); | |
241 | |
242 job->SetModification(modification.release(), level, isAnonymization); | |
243 job->SetOrigin(call); | |
244 SetKeepSource(*job, body); | |
245 | |
246 static const char* TRANSCODE = "Transcode"; | |
247 if (body.isMember(TRANSCODE)) | |
248 { | |
249 job->SetTranscode(SerializationToolbox::ReadString(body, TRANSCODE)); | |
250 } | |
251 | |
252 context.AddChildInstances(*job, call.GetUriComponent("id", "")); | |
253 job->AddTrailingStep(); | |
254 | |
255 OrthancRestApi::GetApi(call).SubmitCommandsJob | |
256 (call, job.release(), true /* synchronous by default */, body); | |
257 } | |
258 | |
259 | |
260 template <enum ResourceType resourceType> | |
261 static void ModifyResource(RestApiPostCall& call) | |
262 { | |
263 std::unique_ptr<DicomModification> modification(new DicomModification); | |
264 | |
265 Json::Value body; | |
266 ParseModifyRequest(body, *modification, call); | |
267 | |
268 modification->SetLevel(resourceType); | |
269 | |
270 SubmitModificationJob(modification, false /* not an anonymization */, | |
271 call, body, resourceType); | |
272 } | |
273 | |
274 | |
275 template <enum ResourceType resourceType> | |
276 static void AnonymizeResource(RestApiPostCall& call) | |
277 { | |
278 std::unique_ptr<DicomModification> modification(new DicomModification); | |
279 | |
280 Json::Value body; | |
281 ParseAnonymizationRequest(body, *modification, call); | |
282 | |
283 SubmitModificationJob(modification, true /* anonymization */, | |
284 call, body, resourceType); | |
285 } | |
286 | |
287 | |
288 static void StoreCreatedInstance(std::string& id /* out */, | |
289 RestApiPostCall& call, | |
290 ParsedDicomFile& dicom, | |
291 bool sendAnswer) | |
292 { | |
293 DicomInstanceToStore toStore; | |
294 toStore.SetOrigin(DicomInstanceOrigin::FromRest(call)); | |
295 toStore.SetParsedDicomFile(dicom); | |
296 | |
297 ServerContext& context = OrthancRestApi::GetContext(call); | |
298 StoreStatus status = context.Store(id, toStore, StoreInstanceMode_Default); | |
299 | |
300 if (status == StoreStatus_Failure) | |
301 { | |
302 throw OrthancException(ErrorCode_CannotStoreInstance); | |
303 } | |
304 | |
305 if (sendAnswer) | |
306 { | |
307 OrthancRestApi::GetApi(call).AnswerStoredInstance(call, toStore, status, id); | |
308 } | |
309 } | |
310 | |
311 | |
312 static void CreateDicomV1(ParsedDicomFile& dicom, | |
313 RestApiPostCall& call, | |
314 const Json::Value& request) | |
315 { | |
316 // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World"}' | |
317 // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World","PixelData":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="}' | |
318 | |
319 assert(request.isObject()); | |
320 LOG(WARNING) << "Using a deprecated call to /tools/create-dicom"; | |
321 | |
322 Json::Value::Members members = request.getMemberNames(); | |
323 for (size_t i = 0; i < members.size(); i++) | |
324 { | |
325 const std::string& name = members[i]; | |
326 if (request[name].type() != Json::stringValue) | |
327 { | |
328 throw OrthancException(ErrorCode_CreateDicomNotString); | |
329 } | |
330 | |
331 std::string value = request[name].asString(); | |
332 | |
333 DicomTag tag = FromDcmtkBridge::ParseTag(name); | |
334 if (tag == DICOM_TAG_PIXEL_DATA) | |
335 { | |
336 dicom.EmbedContent(value); | |
337 } | |
338 else | |
339 { | |
340 // This is V1, don't try and decode data URI scheme | |
341 dicom.ReplacePlainString(tag, value); | |
342 } | |
343 } | |
344 } | |
345 | |
346 | |
347 static void InjectTags(ParsedDicomFile& dicom, | |
348 const Json::Value& tags, | |
349 bool decodeBinaryTags, | |
350 const std::string& privateCreator) | |
351 { | |
352 if (tags.type() != Json::objectValue) | |
353 { | |
354 throw OrthancException(ErrorCode_BadRequest, "Tags field is not an array"); | |
355 } | |
356 | |
357 // Inject the user-specified tags | |
358 Json::Value::Members members = tags.getMemberNames(); | |
359 for (size_t i = 0; i < members.size(); i++) | |
360 { | |
361 const std::string& name = members[i]; | |
362 DicomTag tag = FromDcmtkBridge::ParseTag(name); | |
363 | |
364 if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) | |
365 { | |
366 if (tag != DICOM_TAG_PATIENT_ID && | |
367 tag != DICOM_TAG_ACQUISITION_DATE && | |
368 tag != DICOM_TAG_ACQUISITION_TIME && | |
369 tag != DICOM_TAG_CONTENT_DATE && | |
370 tag != DICOM_TAG_CONTENT_TIME && | |
371 tag != DICOM_TAG_INSTANCE_CREATION_DATE && | |
372 tag != DICOM_TAG_INSTANCE_CREATION_TIME && | |
373 tag != DICOM_TAG_SERIES_DATE && | |
374 tag != DICOM_TAG_SERIES_TIME && | |
375 tag != DICOM_TAG_STUDY_DATE && | |
376 tag != DICOM_TAG_STUDY_TIME && | |
377 dicom.HasTag(tag)) | |
378 { | |
379 throw OrthancException(ErrorCode_CreateDicomOverrideTag, name); | |
380 } | |
381 | |
382 if (tag == DICOM_TAG_PIXEL_DATA) | |
383 { | |
384 throw OrthancException(ErrorCode_CreateDicomUseContent); | |
385 } | |
386 else | |
387 { | |
388 dicom.Replace(tag, tags[name], decodeBinaryTags, DicomReplaceMode_InsertIfAbsent, privateCreator); | |
389 } | |
390 } | |
391 } | |
392 } | |
393 | |
394 | |
395 static void CreateSeries(RestApiPostCall& call, | |
396 ParsedDicomFile& base /* in */, | |
397 const Json::Value& content, | |
398 bool decodeBinaryTags, | |
399 const std::string& privateCreator) | |
400 { | |
401 assert(content.isArray()); | |
402 assert(content.size() > 0); | |
403 ServerContext& context = OrthancRestApi::GetContext(call); | |
404 | |
405 base.ReplacePlainString(DICOM_TAG_IMAGES_IN_ACQUISITION, boost::lexical_cast<std::string>(content.size())); | |
406 base.ReplacePlainString(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, "1"); | |
407 | |
408 std::string someInstance; | |
409 | |
410 try | |
411 { | |
412 for (Json::ArrayIndex i = 0; i < content.size(); i++) | |
413 { | |
414 std::unique_ptr<ParsedDicomFile> dicom(base.Clone(false)); | |
415 const Json::Value* payload = NULL; | |
416 | |
417 if (content[i].type() == Json::stringValue) | |
418 { | |
419 payload = &content[i]; | |
420 } | |
421 else if (content[i].type() == Json::objectValue) | |
422 { | |
423 if (!content[i].isMember("Content")) | |
424 { | |
425 throw OrthancException(ErrorCode_CreateDicomNoPayload); | |
426 } | |
427 | |
428 payload = &content[i]["Content"]; | |
429 | |
430 if (content[i].isMember("Tags")) | |
431 { | |
432 InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags, privateCreator); | |
433 } | |
434 } | |
435 | |
436 if (payload == NULL || | |
437 payload->type() != Json::stringValue) | |
438 { | |
439 throw OrthancException(ErrorCode_CreateDicomUseDataUriScheme); | |
440 } | |
441 | |
442 dicom->EmbedContent(payload->asString()); | |
443 dicom->ReplacePlainString(DICOM_TAG_INSTANCE_NUMBER, boost::lexical_cast<std::string>(i + 1)); | |
444 dicom->ReplacePlainString(DICOM_TAG_IMAGE_INDEX, boost::lexical_cast<std::string>(i + 1)); | |
445 | |
446 StoreCreatedInstance(someInstance, call, *dicom, false); | |
447 } | |
448 } | |
449 catch (OrthancException&) | |
450 { | |
451 // Error: Remove the newly-created series | |
452 | |
453 std::string series; | |
454 if (context.GetIndex().LookupParent(series, someInstance)) | |
455 { | |
456 Json::Value dummy; | |
457 context.GetIndex().DeleteResource(dummy, series, ResourceType_Series); | |
458 } | |
459 | |
460 throw; | |
461 } | |
462 | |
463 std::string series; | |
464 if (context.GetIndex().LookupParent(series, someInstance)) | |
465 { | |
466 OrthancRestApi::GetApi(call).AnswerStoredResource(call, series, ResourceType_Series, StoreStatus_Success); | |
467 } | |
468 } | |
469 | |
470 | |
471 static void CreateDicomV2(RestApiPostCall& call, | |
472 const Json::Value& request) | |
473 { | |
474 assert(request.isObject()); | |
475 ServerContext& context = OrthancRestApi::GetContext(call); | |
476 | |
477 if (!request.isMember("Tags") || | |
478 request["Tags"].type() != Json::objectValue) | |
479 { | |
480 throw OrthancException(ErrorCode_BadRequest); | |
481 } | |
482 | |
483 ParsedDicomFile dicom(true); | |
484 | |
485 { | |
486 Encoding encoding; | |
487 | |
488 if (request["Tags"].isMember("SpecificCharacterSet")) | |
489 { | |
490 const char* tmp = request["Tags"]["SpecificCharacterSet"].asCString(); | |
491 if (!GetDicomEncoding(encoding, tmp)) | |
492 { | |
493 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
494 "Unknown specific character set: " + std::string(tmp)); | |
495 } | |
496 } | |
497 else | |
498 { | |
499 encoding = GetDefaultDicomEncoding(); | |
500 } | |
501 | |
502 dicom.SetEncoding(encoding); | |
503 } | |
504 | |
505 ResourceType parentType = ResourceType_Instance; | |
506 | |
507 if (request.isMember("Parent")) | |
508 { | |
509 // Locate the parent tags | |
510 std::string parent = request["Parent"].asString(); | |
511 if (!context.GetIndex().LookupResourceType(parentType, parent)) | |
512 { | |
513 throw OrthancException(ErrorCode_CreateDicomBadParent); | |
514 } | |
515 | |
516 if (parentType == ResourceType_Instance) | |
517 { | |
518 throw OrthancException(ErrorCode_CreateDicomParentIsInstance); | |
519 } | |
520 | |
521 // Select one existing child instance of the parent resource, to | |
522 // retrieve all its tags | |
523 Json::Value siblingTags; | |
524 std::string siblingInstanceId; | |
525 | |
526 { | |
527 // Retrieve all the instances of the parent resource | |
528 std::list<std::string> siblingInstances; | |
529 context.GetIndex().GetChildInstances(siblingInstances, parent); | |
530 | |
531 if (siblingInstances.empty()) | |
532 { | |
533 // Error: No instance (should never happen) | |
534 throw OrthancException(ErrorCode_InternalError); | |
535 } | |
536 | |
537 siblingInstanceId = siblingInstances.front(); | |
538 context.ReadDicomAsJson(siblingTags, siblingInstanceId); | |
539 } | |
540 | |
541 | |
542 // Choose the same encoding as the parent resource | |
543 { | |
544 static const char* SPECIFIC_CHARACTER_SET = "0008,0005"; | |
545 | |
546 if (siblingTags.isMember(SPECIFIC_CHARACTER_SET)) | |
547 { | |
548 Encoding encoding; | |
549 | |
550 if (!siblingTags[SPECIFIC_CHARACTER_SET].isMember("Value") || | |
551 siblingTags[SPECIFIC_CHARACTER_SET]["Value"].type() != Json::stringValue || | |
552 !GetDicomEncoding(encoding, siblingTags[SPECIFIC_CHARACTER_SET]["Value"].asCString())) | |
553 { | |
554 LOG(WARNING) << "Instance with an incorrect Specific Character Set, " | |
555 << "using the default Orthanc encoding: " << siblingInstanceId; | |
556 encoding = GetDefaultDicomEncoding(); | |
557 } | |
558 | |
559 dicom.SetEncoding(encoding); | |
560 } | |
561 } | |
562 | |
563 | |
564 // Retrieve the tags for all the parent modules | |
565 typedef std::set<DicomTag> ModuleTags; | |
566 ModuleTags moduleTags; | |
567 | |
568 ResourceType type = parentType; | |
569 for (;;) | |
570 { | |
571 DicomTag::AddTagsForModule(moduleTags, GetModule(type)); | |
572 | |
573 if (type == ResourceType_Patient) | |
574 { | |
575 break; // We're done | |
576 } | |
577 | |
578 // Go up | |
579 std::string tmp; | |
580 if (!context.GetIndex().LookupParent(tmp, parent)) | |
581 { | |
582 throw OrthancException(ErrorCode_InternalError); | |
583 } | |
584 | |
585 parent = tmp; | |
586 type = GetParentResourceType(type); | |
587 } | |
588 | |
589 for (ModuleTags::const_iterator it = moduleTags.begin(); | |
590 it != moduleTags.end(); ++it) | |
591 { | |
592 std::string t = it->Format(); | |
593 if (siblingTags.isMember(t)) | |
594 { | |
595 const Json::Value& tag = siblingTags[t]; | |
596 if (tag["Type"] == "Null") | |
597 { | |
598 dicom.ReplacePlainString(*it, ""); | |
599 } | |
600 else if (tag["Type"] == "String") | |
601 { | |
602 std::string value = tag["Value"].asString(); // This is an UTF-8 value (as it comes from JSON) | |
603 dicom.ReplacePlainString(*it, value); | |
604 } | |
605 } | |
606 } | |
607 } | |
608 | |
609 | |
610 bool decodeBinaryTags = true; | |
611 if (request.isMember("InterpretBinaryTags")) | |
612 { | |
613 const Json::Value& v = request["InterpretBinaryTags"]; | |
614 if (v.type() != Json::booleanValue) | |
615 { | |
616 throw OrthancException(ErrorCode_BadRequest); | |
617 } | |
618 | |
619 decodeBinaryTags = v.asBool(); | |
620 } | |
621 | |
622 | |
623 // New argument in Orthanc 1.6.0 | |
624 std::string privateCreator; | |
625 if (request.isMember("PrivateCreator")) | |
626 { | |
627 const Json::Value& v = request["PrivateCreator"]; | |
628 if (v.type() != Json::stringValue) | |
629 { | |
630 throw OrthancException(ErrorCode_BadRequest); | |
631 } | |
632 | |
633 privateCreator = v.asString(); | |
634 } | |
635 else | |
636 { | |
637 OrthancConfiguration::ReaderLock lock; | |
638 privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator(); | |
639 } | |
640 | |
641 | |
642 // Inject time-related information | |
643 std::string date, time; | |
644 SystemToolbox::GetNowDicom(date, time, true /* use UTC time (not local time) */); | |
645 dicom.ReplacePlainString(DICOM_TAG_ACQUISITION_DATE, date); | |
646 dicom.ReplacePlainString(DICOM_TAG_ACQUISITION_TIME, time); | |
647 dicom.ReplacePlainString(DICOM_TAG_CONTENT_DATE, date); | |
648 dicom.ReplacePlainString(DICOM_TAG_CONTENT_TIME, time); | |
649 dicom.ReplacePlainString(DICOM_TAG_INSTANCE_CREATION_DATE, date); | |
650 dicom.ReplacePlainString(DICOM_TAG_INSTANCE_CREATION_TIME, time); | |
651 | |
652 if (parentType == ResourceType_Patient || | |
653 parentType == ResourceType_Study || | |
654 parentType == ResourceType_Instance /* no parent */) | |
655 { | |
656 dicom.ReplacePlainString(DICOM_TAG_SERIES_DATE, date); | |
657 dicom.ReplacePlainString(DICOM_TAG_SERIES_TIME, time); | |
658 } | |
659 | |
660 if (parentType == ResourceType_Patient || | |
661 parentType == ResourceType_Instance /* no parent */) | |
662 { | |
663 dicom.ReplacePlainString(DICOM_TAG_STUDY_DATE, date); | |
664 dicom.ReplacePlainString(DICOM_TAG_STUDY_TIME, time); | |
665 } | |
666 | |
667 | |
668 InjectTags(dicom, request["Tags"], decodeBinaryTags, privateCreator); | |
669 | |
670 | |
671 // Inject the content (either an image, or a PDF file) | |
672 if (request.isMember("Content")) | |
673 { | |
674 const Json::Value& content = request["Content"]; | |
675 | |
676 if (content.type() == Json::stringValue) | |
677 { | |
678 dicom.EmbedContent(request["Content"].asString()); | |
679 | |
680 } | |
681 else if (content.type() == Json::arrayValue) | |
682 { | |
683 if (content.size() > 0) | |
684 { | |
685 // Let's create a series instead of a single instance | |
686 CreateSeries(call, dicom, content, decodeBinaryTags, privateCreator); | |
687 return; | |
688 } | |
689 } | |
690 else | |
691 { | |
692 throw OrthancException(ErrorCode_CreateDicomUseDataUriScheme); | |
693 } | |
694 } | |
695 | |
696 std::string id; | |
697 StoreCreatedInstance(id, call, dicom, true); | |
698 } | |
699 | |
700 | |
701 static void CreateDicom(RestApiPostCall& call) | |
702 { | |
703 Json::Value request; | |
704 if (!call.ParseJsonRequest(request) || | |
705 !request.isObject()) | |
706 { | |
707 throw OrthancException(ErrorCode_BadRequest); | |
708 } | |
709 | |
710 if (request.isMember("Tags")) | |
711 { | |
712 CreateDicomV2(call, request); | |
713 } | |
714 else | |
715 { | |
716 // Compatibility with Orthanc <= 0.9.3 | |
717 ParsedDicomFile dicom(true); | |
718 CreateDicomV1(dicom, call, request); | |
719 | |
720 std::string id; | |
721 StoreCreatedInstance(id, call, dicom, true); | |
722 } | |
723 } | |
724 | |
725 | |
726 static void SplitStudy(RestApiPostCall& call) | |
727 { | |
728 ServerContext& context = OrthancRestApi::GetContext(call); | |
729 | |
730 Json::Value request; | |
731 if (!call.ParseJsonRequest(request)) | |
732 { | |
733 // Bad JSON request | |
734 throw OrthancException(ErrorCode_BadFileFormat); | |
735 } | |
736 | |
737 const std::string study = call.GetUriComponent("id", ""); | |
738 | |
739 std::unique_ptr<SplitStudyJob> job(new SplitStudyJob(context, study)); | |
740 job->SetOrigin(call); | |
741 | |
742 std::vector<std::string> series; | |
743 SerializationToolbox::ReadArrayOfStrings(series, request, "Series"); | |
744 | |
745 for (size_t i = 0; i < series.size(); i++) | |
746 { | |
747 job->AddSourceSeries(series[i]); | |
748 } | |
749 | |
750 job->AddTrailingStep(); | |
751 | |
752 SetKeepSource(*job, request); | |
753 | |
754 static const char* REMOVE = "Remove"; | |
755 if (request.isMember(REMOVE)) | |
756 { | |
757 if (request[REMOVE].type() != Json::arrayValue) | |
758 { | |
759 throw OrthancException(ErrorCode_BadFileFormat); | |
760 } | |
761 | |
762 for (Json::Value::ArrayIndex i = 0; i < request[REMOVE].size(); i++) | |
763 { | |
764 if (request[REMOVE][i].type() != Json::stringValue) | |
765 { | |
766 throw OrthancException(ErrorCode_BadFileFormat); | |
767 } | |
768 else | |
769 { | |
770 job->Remove(FromDcmtkBridge::ParseTag(request[REMOVE][i].asCString())); | |
771 } | |
772 } | |
773 } | |
774 | |
775 static const char* REPLACE = "Replace"; | |
776 if (request.isMember(REPLACE)) | |
777 { | |
778 if (request[REPLACE].type() != Json::objectValue) | |
779 { | |
780 throw OrthancException(ErrorCode_BadFileFormat); | |
781 } | |
782 | |
783 Json::Value::Members tags = request[REPLACE].getMemberNames(); | |
784 | |
785 for (size_t i = 0; i < tags.size(); i++) | |
786 { | |
787 const Json::Value& value = request[REPLACE][tags[i]]; | |
788 | |
789 if (value.type() != Json::stringValue) | |
790 { | |
791 throw OrthancException(ErrorCode_BadFileFormat); | |
792 } | |
793 else | |
794 { | |
795 job->Replace(FromDcmtkBridge::ParseTag(tags[i]), value.asString()); | |
796 } | |
797 } | |
798 } | |
799 | |
800 OrthancRestApi::GetApi(call).SubmitCommandsJob | |
801 (call, job.release(), true /* synchronous by default */, request); | |
802 } | |
803 | |
804 | |
805 static void MergeStudy(RestApiPostCall& call) | |
806 { | |
807 ServerContext& context = OrthancRestApi::GetContext(call); | |
808 | |
809 Json::Value request; | |
810 if (!call.ParseJsonRequest(request)) | |
811 { | |
812 // Bad JSON request | |
813 throw OrthancException(ErrorCode_BadFileFormat); | |
814 } | |
815 | |
816 const std::string study = call.GetUriComponent("id", ""); | |
817 | |
818 std::unique_ptr<MergeStudyJob> job(new MergeStudyJob(context, study)); | |
819 job->SetOrigin(call); | |
820 | |
821 std::vector<std::string> resources; | |
822 SerializationToolbox::ReadArrayOfStrings(resources, request, "Resources"); | |
823 | |
824 for (size_t i = 0; i < resources.size(); i++) | |
825 { | |
826 job->AddSource(resources[i]); | |
827 } | |
828 | |
829 job->AddTrailingStep(); | |
830 | |
831 SetKeepSource(*job, request); | |
832 | |
833 OrthancRestApi::GetApi(call).SubmitCommandsJob | |
834 (call, job.release(), true /* synchronous by default */, request); | |
835 } | |
836 | |
837 | |
838 void OrthancRestApi::RegisterAnonymizeModify() | |
839 { | |
840 Register("/instances/{id}/modify", ModifyInstance); | |
841 Register("/series/{id}/modify", ModifyResource<ResourceType_Series>); | |
842 Register("/studies/{id}/modify", ModifyResource<ResourceType_Study>); | |
843 Register("/patients/{id}/modify", ModifyResource<ResourceType_Patient>); | |
844 | |
845 Register("/instances/{id}/anonymize", AnonymizeInstance); | |
846 Register("/series/{id}/anonymize", AnonymizeResource<ResourceType_Series>); | |
847 Register("/studies/{id}/anonymize", AnonymizeResource<ResourceType_Study>); | |
848 Register("/patients/{id}/anonymize", AnonymizeResource<ResourceType_Patient>); | |
849 | |
850 Register("/tools/create-dicom", CreateDicom); | |
851 | |
852 Register("/studies/{id}/split", SplitStudy); | |
853 Register("/studies/{id}/merge", MergeStudy); | |
854 } | |
855 } |