comparison OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp @ 4717:783f8a048035 openssl-3.x

integration mainline->openssl-3.x
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 23 Jun 2021 16:23:23 +0200
parents f0038043fb97 fb98db281d1d
children 61da49321754
comparison
equal deleted inserted replaced
4711:816a9ecc6ea1 4717:783f8a048035
36 36
37 #define INFO_SUBSEQUENCES \ 37 #define INFO_SUBSEQUENCES \
38 "Starting with Orthanc 1.9.4, paths to subsequences can be provided using the "\ 38 "Starting with Orthanc 1.9.4, paths to subsequences can be provided using the "\
39 "same syntax as the `dcmodify` command-line tool (wildcards are supported as well)." 39 "same syntax as the `dcmodify` command-line tool (wildcards are supported as well)."
40 40
41
42 static const char* const CONTENT = "Content";
43 static const char* const FORCE = "Force";
44 static const char* const INSTANCES = "Instances";
45 static const char* const INTERPRET_BINARY_TAGS = "InterpretBinaryTags";
46 static const char* const KEEP = "Keep";
47 static const char* const KEEP_PRIVATE_TAGS = "KeepPrivateTags";
48 static const char* const KEEP_SOURCE = "KeepSource";
49 static const char* const PARENT = "Parent";
50 static const char* const PRIVATE_CREATOR = "PrivateCreator";
51 static const char* const REMOVE = "Remove";
52 static const char* const REPLACE = "Replace";
53 static const char* const RESOURCES = "Resources";
54 static const char* const SERIES = "Series";
55 static const char* const TAGS = "Tags";
56 static const char* const TRANSCODE = "Transcode";
57
58
41 namespace Orthanc 59 namespace Orthanc
42 { 60 {
43 // Modification of DICOM instances ------------------------------------------ 61 // Modification of DICOM instances ------------------------------------------
44 62
45 63
52 70
53 static void DocumentModifyOptions(RestApiPostCall& call) 71 static void DocumentModifyOptions(RestApiPostCall& call)
54 { 72 {
55 // Check out "DicomModification::ParseModifyRequest()" 73 // Check out "DicomModification::ParseModifyRequest()"
56 call.GetDocumentation() 74 call.GetDocumentation()
57 .SetRequestField("Transcode", RestApiCallDocumentation::Type_String, 75 .SetRequestField(TRANSCODE, RestApiCallDocumentation::Type_String,
58 "Transcode the DICOM instances to the provided DICOM transfer syntax: " 76 "Transcode the DICOM instances to the provided DICOM transfer syntax: "
59 "https://book.orthanc-server.com/faq/transcoding.html", false) 77 "https://book.orthanc-server.com/faq/transcoding.html", false)
60 .SetRequestField("Force", RestApiCallDocumentation::Type_Boolean, 78 .SetRequestField(FORCE, RestApiCallDocumentation::Type_Boolean,
61 "Allow the modification of tags related to DICOM identifiers, at the risk of " 79 "Allow the modification of tags related to DICOM identifiers, at the risk of "
62 "breaking the DICOM model of the real world", false) 80 "breaking the DICOM model of the real world", false)
63 .SetRequestField("RemovePrivateTags", RestApiCallDocumentation::Type_Boolean, 81 .SetRequestField("RemovePrivateTags", RestApiCallDocumentation::Type_Boolean,
64 "Remove the private tags from the DICOM instances (defaults to `false`)", false) 82 "Remove the private tags from the DICOM instances (defaults to `false`)", false)
65 .SetRequestField("Replace", RestApiCallDocumentation::Type_JsonObject, 83 .SetRequestField(REPLACE, RestApiCallDocumentation::Type_JsonObject,
66 "Associative array to change the value of some DICOM tags in the DICOM instances. " INFO_SUBSEQUENCES, false) 84 "Associative array to change the value of some DICOM tags in the DICOM instances. " INFO_SUBSEQUENCES, false)
67 .SetRequestField("Remove", RestApiCallDocumentation::Type_JsonListOfStrings, 85 .SetRequestField(REMOVE, RestApiCallDocumentation::Type_JsonListOfStrings,
68 "List of tags that must be removed from the DICOM instances. " INFO_SUBSEQUENCES, false) 86 "List of tags that must be removed from the DICOM instances. " INFO_SUBSEQUENCES, false)
69 .SetRequestField("Keep", RestApiCallDocumentation::Type_JsonListOfStrings, 87 .SetRequestField(KEEP, RestApiCallDocumentation::Type_JsonListOfStrings,
70 "Keep the original value of the specified tags, to be chosen among the `StudyInstanceUID`, " 88 "Keep the original value of the specified tags, to be chosen among the `StudyInstanceUID`, "
71 "`SeriesInstanceUID` and `SOPInstanceUID` tags. Avoid this feature as much as possible, " 89 "`SeriesInstanceUID` and `SOPInstanceUID` tags. Avoid this feature as much as possible, "
72 "as this breaks the DICOM model of the real world.", false) 90 "as this breaks the DICOM model of the real world.", false)
73 .SetRequestField("PrivateCreator", RestApiCallDocumentation::Type_String, 91 .SetRequestField(PRIVATE_CREATOR, RestApiCallDocumentation::Type_String,
74 "The private creator to be used for private tags in `Replace`", false); 92 "The private creator to be used for private tags in `Replace`", false);
75 } 93 }
76 94
77 95
78 static void DocumentAnonymizationOptions(RestApiPostCall& call) 96 static void DocumentAnonymizationOptions(RestApiPostCall& call)
79 { 97 {
80 // Check out "DicomModification::ParseAnonymizationRequest()" 98 // Check out "DicomModification::ParseAnonymizationRequest()"
81 call.GetDocumentation() 99 call.GetDocumentation()
82 .SetRequestField("Force", RestApiCallDocumentation::Type_Boolean, 100 .SetRequestField(FORCE, RestApiCallDocumentation::Type_Boolean,
83 "Allow the modification of tags related to DICOM identifiers, at the risk of " 101 "Allow the modification of tags related to DICOM identifiers, at the risk of "
84 "breaking the DICOM model of the real world", false) 102 "breaking the DICOM model of the real world", false)
85 .SetRequestField("DicomVersion", RestApiCallDocumentation::Type_String, 103 .SetRequestField("DicomVersion", RestApiCallDocumentation::Type_String,
86 "Version of the DICOM standard to be used for anonymization. Check out " 104 "Version of the DICOM standard to be used for anonymization. Check out "
87 "configuration option `DeidentifyLogsDicomVersion` for possible values.", false) 105 "configuration option `DeidentifyLogsDicomVersion` for possible values.", false)
88 .SetRequestField("KeepPrivateTags", RestApiCallDocumentation::Type_Boolean, 106 .SetRequestField(KEEP_PRIVATE_TAGS, RestApiCallDocumentation::Type_Boolean,
89 "Keep the private tags from the DICOM instances (defaults to `false`)", false) 107 "Keep the private tags from the DICOM instances (defaults to `false`)", false)
90 .SetRequestField("Replace", RestApiCallDocumentation::Type_JsonObject, 108 .SetRequestField(REPLACE, RestApiCallDocumentation::Type_JsonObject,
91 "Associative array to change the value of some DICOM tags in the DICOM instances. " INFO_SUBSEQUENCES, false) 109 "Associative array to change the value of some DICOM tags in the DICOM instances. " INFO_SUBSEQUENCES, false)
92 .SetRequestField("Remove", RestApiCallDocumentation::Type_JsonListOfStrings, 110 .SetRequestField(REMOVE, RestApiCallDocumentation::Type_JsonListOfStrings,
93 "List of additional tags to be removed from the DICOM instances. " INFO_SUBSEQUENCES, false) 111 "List of additional tags to be removed from the DICOM instances. " INFO_SUBSEQUENCES, false)
94 .SetRequestField("Keep", RestApiCallDocumentation::Type_JsonListOfStrings, 112 .SetRequestField(KEEP, RestApiCallDocumentation::Type_JsonListOfStrings,
95 "List of DICOM tags whose value must not be destroyed by the anonymization. " INFO_SUBSEQUENCES, false) 113 "List of DICOM tags whose value must not be destroyed by the anonymization. " INFO_SUBSEQUENCES, false)
96 .SetRequestField("PrivateCreator", RestApiCallDocumentation::Type_String, 114 .SetRequestField(PRIVATE_CREATOR, RestApiCallDocumentation::Type_String,
97 "The private creator to be used for private tags in `Replace`", false); 115 "The private creator to be used for private tags in `Replace`", false);
98 } 116 }
99 117
100 118
101 static void ParseModifyRequest(Json::Value& request, 119 static void ParseModifyRequest(Json::Value& request,
239 Json::Value request; 257 Json::Value request;
240 ParseModifyRequest(request, modification, call); 258 ParseModifyRequest(request, modification, call);
241 259
242 modification.SetLevel(DetectModifyLevel(modification)); 260 modification.SetLevel(DetectModifyLevel(modification));
243 261
244 static const char* TRANSCODE = "Transcode";
245 if (request.isMember(TRANSCODE)) 262 if (request.isMember(TRANSCODE))
246 { 263 {
247 std::string s = SerializationToolbox::ReadString(request, TRANSCODE); 264 std::string s = SerializationToolbox::ReadString(request, TRANSCODE);
248 265
249 DicomTransferSyntax syntax; 266 DicomTransferSyntax syntax;
291 308
292 309
293 static void SetKeepSource(CleaningInstancesJob& job, 310 static void SetKeepSource(CleaningInstancesJob& job,
294 const Json::Value& body) 311 const Json::Value& body)
295 { 312 {
296 static const char* KEEP_SOURCE = "KeepSource";
297 if (body.isMember(KEEP_SOURCE)) 313 if (body.isMember(KEEP_SOURCE))
298 { 314 {
299 job.SetKeepSource(SerializationToolbox::ReadBoolean(body, KEEP_SOURCE)); 315 job.SetKeepSource(SerializationToolbox::ReadBoolean(body, KEEP_SOURCE));
300 } 316 }
301 } 317 }
323 } 339 }
324 340
325 job->SetOrigin(call); 341 job->SetOrigin(call);
326 SetKeepSource(*job, body); 342 SetKeepSource(*job, body);
327 343
328 static const char* TRANSCODE = "Transcode";
329 if (body.isMember(TRANSCODE)) 344 if (body.isMember(TRANSCODE))
330 { 345 {
331 job->SetTranscode(SerializationToolbox::ReadString(body, TRANSCODE)); 346 job->SetTranscode(SerializationToolbox::ReadString(body, TRANSCODE));
332 } 347 }
333 348
363 bool isAnonymization, 378 bool isAnonymization,
364 RestApiPostCall& call, 379 RestApiPostCall& call,
365 const Json::Value& body) 380 const Json::Value& body)
366 { 381 {
367 std::set<std::string> resources; 382 std::set<std::string> resources;
368 SerializationToolbox::ReadSetOfStrings(resources, body, "Resources"); 383 SerializationToolbox::ReadSetOfStrings(resources, body, RESOURCES);
369 384
370 SubmitModificationJob(modification, isAnonymization, 385 SubmitModificationJob(modification, isAnonymization,
371 call, body, ResourceType_Instance /* arbitrary value, unused */, 386 call, body, ResourceType_Instance /* arbitrary value, unused */,
372 false /* multiple resources */, resources); 387 false /* multiple resources */, resources);
373 } 388 }
412 OrthancRestApi::DocumentSubmitCommandsJob(call); 427 OrthancRestApi::DocumentSubmitCommandsJob(call);
413 DocumentModifyOptions(call); 428 DocumentModifyOptions(call);
414 call.GetDocumentation() 429 call.GetDocumentation()
415 .SetTag("System") 430 .SetTag("System")
416 .SetSummary("Modify a set of resources") 431 .SetSummary("Modify a set of resources")
417 .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings, 432 .SetRequestField(RESOURCES, RestApiCallDocumentation::Type_JsonListOfStrings,
418 "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true) 433 "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true)
419 .SetDescription("Start a job that will modify all the DICOM patients, studies, series or instances " 434 .SetDescription("Start a job that will modify all the DICOM patients, studies, series or instances "
420 "whose identifiers are provided in the `Resources` field.") 435 "whose identifiers are provided in the `Resources` field.")
421 .AddAnswerType(MimeType_Json, "The list of all the resources that have been altered by this modification"); 436 .AddAnswerType(MimeType_Json, "The list of all the resources that have been altered by this modification");
422 return; 437 return;
470 OrthancRestApi::DocumentSubmitCommandsJob(call); 485 OrthancRestApi::DocumentSubmitCommandsJob(call);
471 DocumentAnonymizationOptions(call); 486 DocumentAnonymizationOptions(call);
472 call.GetDocumentation() 487 call.GetDocumentation()
473 .SetTag("System") 488 .SetTag("System")
474 .SetSummary("Anonymize a set of resources") 489 .SetSummary("Anonymize a set of resources")
475 .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings, 490 .SetRequestField(RESOURCES, RestApiCallDocumentation::Type_JsonListOfStrings,
476 "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true) 491 "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true)
477 .SetDescription("Start a job that will anonymize all the DICOM patients, studies, series or instances " 492 .SetDescription("Start a job that will anonymize all the DICOM patients, studies, series or instances "
478 "whose identifiers are provided in the `Resources` field.") 493 "whose identifiers are provided in the `Resources` field.")
479 .AddAnswerType(MimeType_Json, "The list of all the resources that have been created by this anonymization"); 494 .AddAnswerType(MimeType_Json, "The list of all the resources that have been created by this anonymization");
480 return; 495 return;
624 { 639 {
625 payload = &content[i]; 640 payload = &content[i];
626 } 641 }
627 else if (content[i].type() == Json::objectValue) 642 else if (content[i].type() == Json::objectValue)
628 { 643 {
629 if (!content[i].isMember("Content")) 644 if (!content[i].isMember(CONTENT))
630 { 645 {
631 throw OrthancException(ErrorCode_CreateDicomNoPayload); 646 throw OrthancException(ErrorCode_CreateDicomNoPayload);
632 } 647 }
633 648
634 payload = &content[i]["Content"]; 649 payload = &content[i][CONTENT];
635 650
636 if (content[i].isMember("Tags")) 651 if (content[i].isMember(TAGS))
637 { 652 {
638 InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags, privateCreator, force); 653 InjectTags(*dicom, content[i][TAGS], decodeBinaryTags, privateCreator, force);
639 } 654 }
640 } 655 }
641 656
642 if (payload == NULL || 657 if (payload == NULL ||
643 payload->type() != Json::stringValue) 658 payload->type() != Json::stringValue)
675 690
676 691
677 static void CreateDicomV2(RestApiPostCall& call, 692 static void CreateDicomV2(RestApiPostCall& call,
678 const Json::Value& request) 693 const Json::Value& request)
679 { 694 {
680 static const char* const CONTENT = "Content";
681 static const char* const FORCE = "Force";
682 static const char* const INTERPRET_BINARY_TAGS = "InterpretBinaryTags";
683 static const char* const PARENT = "Parent";
684 static const char* const PRIVATE_CREATOR = "PrivateCreator";
685 static const char* const SPECIFIC_CHARACTER_SET_2 = "SpecificCharacterSet"; 695 static const char* const SPECIFIC_CHARACTER_SET_2 = "SpecificCharacterSet";
686 static const char* const TAGS = "Tags";
687 static const char* const TYPE = "Type"; 696 static const char* const TYPE = "Type";
688 static const char* const VALUE = "Value"; 697 static const char* const VALUE = "Value";
689 698
690 assert(request.isObject()); 699 assert(request.isObject());
691 ServerContext& context = OrthancRestApi::GetContext(call); 700 ServerContext& context = OrthancRestApi::GetContext(call);
755 } 764 }
756 765
757 766
758 // Choose the same encoding as the parent resource 767 // Choose the same encoding as the parent resource
759 { 768 {
760 static const char* SPECIFIC_CHARACTER_SET = "0008,0005"; 769 static const char* const SPECIFIC_CHARACTER_SET = "0008,0005";
761 770
762 if (siblingTags.isMember(SPECIFIC_CHARACTER_SET)) 771 if (siblingTags.isMember(SPECIFIC_CHARACTER_SET))
763 { 772 {
764 Encoding encoding; 773 Encoding encoding;
765 774
934 { 943 {
935 call.GetDocumentation() 944 call.GetDocumentation()
936 .SetTag("System") 945 .SetTag("System")
937 .SetSummary("Create one DICOM instance") 946 .SetSummary("Create one DICOM instance")
938 .SetDescription("Create one DICOM instance, and store it into Orthanc") 947 .SetDescription("Create one DICOM instance, and store it into Orthanc")
939 .SetRequestField("Tags", RestApiCallDocumentation::Type_JsonObject, 948 .SetRequestField(TAGS, RestApiCallDocumentation::Type_JsonObject,
940 "Associative array containing the tags of the new instance to be created", true) 949 "Associative array containing the tags of the new instance to be created", true)
941 .SetRequestField("Content", RestApiCallDocumentation::Type_String, 950 .SetRequestField(CONTENT, RestApiCallDocumentation::Type_String,
942 "This field can be used to embed an image (pixel data) or a PDF inside the created DICOM instance. " 951 "This field can be used to embed an image (pixel data) or a PDF inside the created DICOM instance. "
943 "The PNG image, the JPEG image or the PDF file must be provided using their " 952 "The PNG image, the JPEG image or the PDF file must be provided using their "
944 "[data URI scheme encoding](https://en.wikipedia.org/wiki/Data_URI_scheme). " 953 "[data URI scheme encoding](https://en.wikipedia.org/wiki/Data_URI_scheme). "
945 "This field can possibly contain a JSON array, in which case a DICOM series is created " 954 "This field can possibly contain a JSON array, in which case a DICOM series is created "
946 "containing one DICOM instance for each item in the `Content` field.", false) 955 "containing one DICOM instance for each item in the `Content` field.", false)
947 .SetRequestField("Parent", RestApiCallDocumentation::Type_String, 956 .SetRequestField(PARENT, RestApiCallDocumentation::Type_String,
948 "If present, the newly created instance will be attached to the parent DICOM resource " 957 "If present, the newly created instance will be attached to the parent DICOM resource "
949 "whose Orthanc identifier is contained in this field. The DICOM tags of the parent " 958 "whose Orthanc identifier is contained in this field. The DICOM tags of the parent "
950 "modules in the DICOM hierarchy will be automatically copied to the newly created instance.", false) 959 "modules in the DICOM hierarchy will be automatically copied to the newly created instance.", false)
951 .SetRequestField("InterpretBinaryTags", RestApiCallDocumentation::Type_Boolean, 960 .SetRequestField(INTERPRET_BINARY_TAGS, RestApiCallDocumentation::Type_Boolean,
952 "If some value in the `Tags` associative array is formatted according to some " 961 "If some value in the `Tags` associative array is formatted according to some "
953 "[data URI scheme encoding](https://en.wikipedia.org/wiki/Data_URI_scheme), " 962 "[data URI scheme encoding](https://en.wikipedia.org/wiki/Data_URI_scheme), "
954 "whether this value is decoded to a binary value or kept as such (`true` by default)", false) 963 "whether this value is decoded to a binary value or kept as such (`true` by default)", false)
955 .SetRequestField("PrivateCreator", RestApiCallDocumentation::Type_String, 964 .SetRequestField(PRIVATE_CREATOR, RestApiCallDocumentation::Type_String,
956 "The private creator to be used for private tags in `Tags`", false) 965 "The private creator to be used for private tags in `Tags`", false)
957 .SetRequestField("Force", RestApiCallDocumentation::Type_Boolean, 966 .SetRequestField(FORCE, RestApiCallDocumentation::Type_Boolean,
958 "Avoid the consistency checks for the DICOM tags that enforce the DICOM model of the real-world. " 967 "Avoid the consistency checks for the DICOM tags that enforce the DICOM model of the real-world. "
959 "You can notably use this flag if you need to manually set the tags `StudyInstanceUID`, " 968 "You can notably use this flag if you need to manually set the tags `StudyInstanceUID`, "
960 "`SeriesInstanceUID`, or `SOPInstanceUID`. Be careful with this feature.", false) 969 "`SeriesInstanceUID`, or `SOPInstanceUID`. Be careful with this feature.", false)
961 .SetAnswerField("ID", RestApiCallDocumentation::Type_String, "Orthanc identifier of the newly created instance") 970 .SetAnswerField("ID", RestApiCallDocumentation::Type_String, "Orthanc identifier of the newly created instance")
962 .SetAnswerField("Path", RestApiCallDocumentation::Type_String, "Path to access the instance in the REST API"); 971 .SetAnswerField("Path", RestApiCallDocumentation::Type_String, "Path to access the instance in the REST API");
968 !request.isObject()) 977 !request.isObject())
969 { 978 {
970 throw OrthancException(ErrorCode_BadRequest); 979 throw OrthancException(ErrorCode_BadRequest);
971 } 980 }
972 981
973 if (request.isMember("Tags")) 982 if (request.isMember(TAGS))
974 { 983 {
975 CreateDicomV2(call, request); 984 CreateDicomV2(call, request);
976 } 985 }
977 else 986 else
978 { 987 {
993 OrthancRestApi::DocumentSubmitCommandsJob(call); 1002 OrthancRestApi::DocumentSubmitCommandsJob(call);
994 call.GetDocumentation() 1003 call.GetDocumentation()
995 .SetTag("Studies") 1004 .SetTag("Studies")
996 .SetSummary("Split study") 1005 .SetSummary("Split study")
997 .SetDescription("Start a new job so as to split the DICOM study whose Orthanc identifier is provided in the URL, " 1006 .SetDescription("Start a new job so as to split the DICOM study whose Orthanc identifier is provided in the URL, "
998 "by taking some of its children series out of it and putting them into a brand new study (this " 1007 "by taking some of its children series or instances out of it and putting them into a brand new study "
999 "new study is created by setting the `StudyInstanceUID` tag to a random identifier): " 1008 "(this new study is created by setting the `StudyInstanceUID` tag to a random identifier): "
1000 "https://book.orthanc-server.com/users/anonymization.html#splitting") 1009 "https://book.orthanc-server.com/users/anonymization.html#splitting")
1001 .SetUriArgument("id", "Orthanc identifier of the study of interest") 1010 .SetUriArgument("id", "Orthanc identifier of the study of interest")
1002 .SetRequestField("Series", RestApiCallDocumentation::Type_JsonListOfStrings, 1011 .SetRequestField(SERIES, RestApiCallDocumentation::Type_JsonListOfStrings,
1003 "The list of series to be separated from the parent study (mandatory option). " 1012 "The list of series to be separated from the parent study. "
1004 "These series must all be children of the same source study, that is specified in the URI.", true) 1013 "These series must all be children of the same source study, that is specified in the URI.", false)
1005 .SetRequestField("Replace", RestApiCallDocumentation::Type_JsonObject, 1014 .SetRequestField(REPLACE, RestApiCallDocumentation::Type_JsonObject,
1006 "Associative array to change the value of some DICOM tags in the new study. " 1015 "Associative array to change the value of some DICOM tags in the new study. "
1007 "These tags must be part of the \"Patient Module Attributes\" or the \"General Study " 1016 "These tags must be part of the \"Patient Module Attributes\" or the \"General Study "
1008 "Module Attributes\", as specified by the DICOM 2011 standard in Tables C.7-1 and C.7-3.", false) 1017 "Module Attributes\", as specified by the DICOM 2011 standard in Tables C.7-1 and C.7-3.", false)
1009 .SetRequestField("Remove", RestApiCallDocumentation::Type_JsonListOfStrings, 1018 .SetRequestField(REMOVE, RestApiCallDocumentation::Type_JsonListOfStrings,
1010 "List of tags that must be removed in the new study (from the same modules as in the `Replace` option)", false) 1019 "List of tags that must be removed in the new study (from the same modules as in the `Replace` option)", false)
1011 .SetRequestField("KeepSource", RestApiCallDocumentation::Type_Boolean, 1020 .SetRequestField(KEEP_SOURCE, RestApiCallDocumentation::Type_Boolean,
1012 "If set to `true`, instructs Orthanc to keep a copy of the original series in the source study. " 1021 "If set to `true`, instructs Orthanc to keep a copy of the original series/instances in the source study. "
1013 "By default, the original series are deleted from Orthanc.", false); 1022 "By default, the original series/instances are deleted from Orthanc.", false)
1023 .SetRequestField(INSTANCES, RestApiCallDocumentation::Type_JsonListOfStrings,
1024 "The list of instances to be separated from the parent study. "
1025 "These instances must all be children of the same source study, that is specified in the URI.", false);
1014 return; 1026 return;
1015 } 1027 }
1016 1028
1017 ServerContext& context = OrthancRestApi::GetContext(call); 1029 ServerContext& context = OrthancRestApi::GetContext(call);
1018 1030
1026 const std::string study = call.GetUriComponent("id", ""); 1038 const std::string study = call.GetUriComponent("id", "");
1027 1039
1028 std::unique_ptr<SplitStudyJob> job(new SplitStudyJob(context, study)); 1040 std::unique_ptr<SplitStudyJob> job(new SplitStudyJob(context, study));
1029 job->SetOrigin(call); 1041 job->SetOrigin(call);
1030 1042
1031 std::vector<std::string> series; 1043 bool ok = false;
1032 SerializationToolbox::ReadArrayOfStrings(series, request, "Series"); 1044 if (request.isMember(SERIES))
1033 1045 {
1034 for (size_t i = 0; i < series.size(); i++) 1046 std::vector<std::string> series;
1035 { 1047 SerializationToolbox::ReadArrayOfStrings(series, request, SERIES);
1036 job->AddSourceSeries(series[i]); 1048
1037 } 1049 for (size_t i = 0; i < series.size(); i++)
1050 {
1051 job->AddSourceSeries(series[i]);
1052 ok = true;
1053 }
1054 }
1055
1056 if (request.isMember(INSTANCES))
1057 {
1058 std::vector<std::string> instances;
1059 SerializationToolbox::ReadArrayOfStrings(instances, request, INSTANCES);
1060
1061 for (size_t i = 0; i < instances.size(); i++)
1062 {
1063 job->AddSourceInstance(instances[i]);
1064 ok = true;
1065 }
1066 }
1067
1068 if (!ok)
1069 {
1070 throw OrthancException(ErrorCode_BadRequest, "Both the \"Series\" and the \"Instances\" fields are missing");
1071 }
1038 1072
1039 job->AddTrailingStep(); 1073 job->AddTrailingStep();
1040 1074
1041 SetKeepSource(*job, request); 1075 SetKeepSource(*job, request);
1042 1076
1043 static const char* REMOVE = "Remove";
1044 if (request.isMember(REMOVE)) 1077 if (request.isMember(REMOVE))
1045 { 1078 {
1046 if (request[REMOVE].type() != Json::arrayValue) 1079 if (request[REMOVE].type() != Json::arrayValue)
1047 { 1080 {
1048 throw OrthancException(ErrorCode_BadFileFormat); 1081 throw OrthancException(ErrorCode_BadFileFormat);
1059 job->Remove(FromDcmtkBridge::ParseTag(request[REMOVE][i].asCString())); 1092 job->Remove(FromDcmtkBridge::ParseTag(request[REMOVE][i].asCString()));
1060 } 1093 }
1061 } 1094 }
1062 } 1095 }
1063 1096
1064 static const char* REPLACE = "Replace";
1065 if (request.isMember(REPLACE)) 1097 if (request.isMember(REPLACE))
1066 { 1098 {
1067 if (request[REPLACE].type() != Json::objectValue) 1099 if (request[REPLACE].type() != Json::objectValue)
1068 { 1100 {
1069 throw OrthancException(ErrorCode_BadFileFormat); 1101 throw OrthancException(ErrorCode_BadFileFormat);
1097 { 1129 {
1098 OrthancRestApi::DocumentSubmitCommandsJob(call); 1130 OrthancRestApi::DocumentSubmitCommandsJob(call);
1099 call.GetDocumentation() 1131 call.GetDocumentation()
1100 .SetTag("Studies") 1132 .SetTag("Studies")
1101 .SetSummary("Merge study") 1133 .SetSummary("Merge study")
1102 .SetDescription("Start a new job so as to move some DICOM series into the DICOM study whose Orthanc identifier " 1134 .SetDescription("Start a new job so as to move some DICOM resources into the DICOM study whose Orthanc identifier "
1103 "is provided in the URL: https://book.orthanc-server.com/users/anonymization.html#merging") 1135 "is provided in the URL: https://book.orthanc-server.com/users/anonymization.html#merging")
1104 .SetUriArgument("id", "Orthanc identifier of the study of interest") 1136 .SetUriArgument("id", "Orthanc identifier of the study of interest")
1105 .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings, 1137 .SetRequestField(RESOURCES, RestApiCallDocumentation::Type_JsonListOfStrings,
1106 "The list of DICOM resources (patients, studies, series, and/or instances) to be merged " 1138 "The list of DICOM resources (studies, series, and/or instances) to be merged "
1107 "into the study of interest (mandatory option)", true) 1139 "into the study of interest (mandatory option)", true)
1108 .SetRequestField("KeepSource", RestApiCallDocumentation::Type_Boolean, 1140 .SetRequestField(KEEP_SOURCE, RestApiCallDocumentation::Type_Boolean,
1109 "If set to `true`, instructs Orthanc to keep a copy of the original resources in their source study. " 1141 "If set to `true`, instructs Orthanc to keep a copy of the original resources in their source study. "
1110 "By default, the original resources are deleted from Orthanc.", false); 1142 "By default, the original resources are deleted from Orthanc.", false);
1111 return; 1143 return;
1112 } 1144 }
1113 1145
1124 1156
1125 std::unique_ptr<MergeStudyJob> job(new MergeStudyJob(context, study)); 1157 std::unique_ptr<MergeStudyJob> job(new MergeStudyJob(context, study));
1126 job->SetOrigin(call); 1158 job->SetOrigin(call);
1127 1159
1128 std::vector<std::string> resources; 1160 std::vector<std::string> resources;
1129 SerializationToolbox::ReadArrayOfStrings(resources, request, "Resources"); 1161 SerializationToolbox::ReadArrayOfStrings(resources, request, RESOURCES);
1130 1162
1131 for (size_t i = 0; i < resources.size(); i++) 1163 for (size_t i = 0; i < resources.size(); i++)
1132 { 1164 {
1133 job->AddSource(resources[i]); 1165 job->AddSource(resources[i]);
1134 } 1166 }