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 }