Mercurial > hg > orthanc
comparison OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp @ 4712:bad8935cd5f2
"/studies/{id}/split" accepts "Instances" parameter to split instances instead of series
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 23 Jun 2021 14:33:20 +0200 |
parents | facea16b055b |
children | fb98db281d1d |
comparison
equal
deleted
inserted
replaced
4707:81ad9d8a0fa6 | 4712:bad8935cd5f2 |
---|---|
48 | 48 |
49 #define INFO_SUBSEQUENCES \ | 49 #define INFO_SUBSEQUENCES \ |
50 "Starting with Orthanc 1.9.4, paths to subsequences can be provided using the "\ | 50 "Starting with Orthanc 1.9.4, paths to subsequences can be provided using the "\ |
51 "same syntax as the `dcmodify` command-line tool (wildcards are supported as well)." | 51 "same syntax as the `dcmodify` command-line tool (wildcards are supported as well)." |
52 | 52 |
53 | |
54 static const char* const CONTENT = "Content"; | |
55 static const char* const FORCE = "Force"; | |
56 static const char* const INSTANCES = "Instances"; | |
57 static const char* const INTERPRET_BINARY_TAGS = "InterpretBinaryTags"; | |
58 static const char* const KEEP = "Keep"; | |
59 static const char* const KEEP_PRIVATE_TAGS = "KeepPrivateTags"; | |
60 static const char* const KEEP_SOURCE = "KeepSource"; | |
61 static const char* const PARENT = "Parent"; | |
62 static const char* const PRIVATE_CREATOR = "PrivateCreator"; | |
63 static const char* const REMOVE = "Remove"; | |
64 static const char* const REPLACE = "Replace"; | |
65 static const char* const RESOURCES = "Resources"; | |
66 static const char* const SERIES = "Series"; | |
67 static const char* const TAGS = "Tags"; | |
68 static const char* const TRANSCODE = "Transcode"; | |
69 | |
70 | |
53 namespace Orthanc | 71 namespace Orthanc |
54 { | 72 { |
55 // Modification of DICOM instances ------------------------------------------ | 73 // Modification of DICOM instances ------------------------------------------ |
56 | 74 |
57 | 75 |
64 | 82 |
65 static void DocumentModifyOptions(RestApiPostCall& call) | 83 static void DocumentModifyOptions(RestApiPostCall& call) |
66 { | 84 { |
67 // Check out "DicomModification::ParseModifyRequest()" | 85 // Check out "DicomModification::ParseModifyRequest()" |
68 call.GetDocumentation() | 86 call.GetDocumentation() |
69 .SetRequestField("Transcode", RestApiCallDocumentation::Type_String, | 87 .SetRequestField(TRANSCODE, RestApiCallDocumentation::Type_String, |
70 "Transcode the DICOM instances to the provided DICOM transfer syntax: " | 88 "Transcode the DICOM instances to the provided DICOM transfer syntax: " |
71 "https://book.orthanc-server.com/faq/transcoding.html", false) | 89 "https://book.orthanc-server.com/faq/transcoding.html", false) |
72 .SetRequestField("Force", RestApiCallDocumentation::Type_Boolean, | 90 .SetRequestField(FORCE, RestApiCallDocumentation::Type_Boolean, |
73 "Allow the modification of tags related to DICOM identifiers, at the risk of " | 91 "Allow the modification of tags related to DICOM identifiers, at the risk of " |
74 "breaking the DICOM model of the real world", false) | 92 "breaking the DICOM model of the real world", false) |
75 .SetRequestField("RemovePrivateTags", RestApiCallDocumentation::Type_Boolean, | 93 .SetRequestField("RemovePrivateTags", RestApiCallDocumentation::Type_Boolean, |
76 "Remove the private tags from the DICOM instances (defaults to `false`)", false) | 94 "Remove the private tags from the DICOM instances (defaults to `false`)", false) |
77 .SetRequestField("Replace", RestApiCallDocumentation::Type_JsonObject, | 95 .SetRequestField(REPLACE, RestApiCallDocumentation::Type_JsonObject, |
78 "Associative array to change the value of some DICOM tags in the DICOM instances. " INFO_SUBSEQUENCES, false) | 96 "Associative array to change the value of some DICOM tags in the DICOM instances. " INFO_SUBSEQUENCES, false) |
79 .SetRequestField("Remove", RestApiCallDocumentation::Type_JsonListOfStrings, | 97 .SetRequestField(REMOVE, RestApiCallDocumentation::Type_JsonListOfStrings, |
80 "List of tags that must be removed from the DICOM instances. " INFO_SUBSEQUENCES, false) | 98 "List of tags that must be removed from the DICOM instances. " INFO_SUBSEQUENCES, false) |
81 .SetRequestField("Keep", RestApiCallDocumentation::Type_JsonListOfStrings, | 99 .SetRequestField(KEEP, RestApiCallDocumentation::Type_JsonListOfStrings, |
82 "Keep the original value of the specified tags, to be chosen among the `StudyInstanceUID`, " | 100 "Keep the original value of the specified tags, to be chosen among the `StudyInstanceUID`, " |
83 "`SeriesInstanceUID` and `SOPInstanceUID` tags. Avoid this feature as much as possible, " | 101 "`SeriesInstanceUID` and `SOPInstanceUID` tags. Avoid this feature as much as possible, " |
84 "as this breaks the DICOM model of the real world.", false) | 102 "as this breaks the DICOM model of the real world.", false) |
85 .SetRequestField("PrivateCreator", RestApiCallDocumentation::Type_String, | 103 .SetRequestField(PRIVATE_CREATOR, RestApiCallDocumentation::Type_String, |
86 "The private creator to be used for private tags in `Replace`", false); | 104 "The private creator to be used for private tags in `Replace`", false); |
87 } | 105 } |
88 | 106 |
89 | 107 |
90 static void DocumentAnonymizationOptions(RestApiPostCall& call) | 108 static void DocumentAnonymizationOptions(RestApiPostCall& call) |
91 { | 109 { |
92 // Check out "DicomModification::ParseAnonymizationRequest()" | 110 // Check out "DicomModification::ParseAnonymizationRequest()" |
93 call.GetDocumentation() | 111 call.GetDocumentation() |
94 .SetRequestField("Force", RestApiCallDocumentation::Type_Boolean, | 112 .SetRequestField(FORCE, RestApiCallDocumentation::Type_Boolean, |
95 "Allow the modification of tags related to DICOM identifiers, at the risk of " | 113 "Allow the modification of tags related to DICOM identifiers, at the risk of " |
96 "breaking the DICOM model of the real world", false) | 114 "breaking the DICOM model of the real world", false) |
97 .SetRequestField("DicomVersion", RestApiCallDocumentation::Type_String, | 115 .SetRequestField("DicomVersion", RestApiCallDocumentation::Type_String, |
98 "Version of the DICOM standard to be used for anonymization. Check out " | 116 "Version of the DICOM standard to be used for anonymization. Check out " |
99 "configuration option `DeidentifyLogsDicomVersion` for possible values.", false) | 117 "configuration option `DeidentifyLogsDicomVersion` for possible values.", false) |
100 .SetRequestField("KeepPrivateTags", RestApiCallDocumentation::Type_Boolean, | 118 .SetRequestField(KEEP_PRIVATE_TAGS, RestApiCallDocumentation::Type_Boolean, |
101 "Keep the private tags from the DICOM instances (defaults to `false`)", false) | 119 "Keep the private tags from the DICOM instances (defaults to `false`)", false) |
102 .SetRequestField("Replace", RestApiCallDocumentation::Type_JsonObject, | 120 .SetRequestField(REPLACE, RestApiCallDocumentation::Type_JsonObject, |
103 "Associative array to change the value of some DICOM tags in the DICOM instances. " INFO_SUBSEQUENCES, false) | 121 "Associative array to change the value of some DICOM tags in the DICOM instances. " INFO_SUBSEQUENCES, false) |
104 .SetRequestField("Remove", RestApiCallDocumentation::Type_JsonListOfStrings, | 122 .SetRequestField(REMOVE, RestApiCallDocumentation::Type_JsonListOfStrings, |
105 "List of additional tags to be removed from the DICOM instances. " INFO_SUBSEQUENCES, false) | 123 "List of additional tags to be removed from the DICOM instances. " INFO_SUBSEQUENCES, false) |
106 .SetRequestField("Keep", RestApiCallDocumentation::Type_JsonListOfStrings, | 124 .SetRequestField(KEEP, RestApiCallDocumentation::Type_JsonListOfStrings, |
107 "List of DICOM tags whose value must not be destroyed by the anonymization. " INFO_SUBSEQUENCES, false) | 125 "List of DICOM tags whose value must not be destroyed by the anonymization. " INFO_SUBSEQUENCES, false) |
108 .SetRequestField("PrivateCreator", RestApiCallDocumentation::Type_String, | 126 .SetRequestField(PRIVATE_CREATOR, RestApiCallDocumentation::Type_String, |
109 "The private creator to be used for private tags in `Replace`", false); | 127 "The private creator to be used for private tags in `Replace`", false); |
110 } | 128 } |
111 | 129 |
112 | 130 |
113 static void ParseModifyRequest(Json::Value& request, | 131 static void ParseModifyRequest(Json::Value& request, |
251 Json::Value request; | 269 Json::Value request; |
252 ParseModifyRequest(request, modification, call); | 270 ParseModifyRequest(request, modification, call); |
253 | 271 |
254 modification.SetLevel(DetectModifyLevel(modification)); | 272 modification.SetLevel(DetectModifyLevel(modification)); |
255 | 273 |
256 static const char* TRANSCODE = "Transcode"; | |
257 if (request.isMember(TRANSCODE)) | 274 if (request.isMember(TRANSCODE)) |
258 { | 275 { |
259 std::string s = SerializationToolbox::ReadString(request, TRANSCODE); | 276 std::string s = SerializationToolbox::ReadString(request, TRANSCODE); |
260 | 277 |
261 DicomTransferSyntax syntax; | 278 DicomTransferSyntax syntax; |
303 | 320 |
304 | 321 |
305 static void SetKeepSource(CleaningInstancesJob& job, | 322 static void SetKeepSource(CleaningInstancesJob& job, |
306 const Json::Value& body) | 323 const Json::Value& body) |
307 { | 324 { |
308 static const char* KEEP_SOURCE = "KeepSource"; | |
309 if (body.isMember(KEEP_SOURCE)) | 325 if (body.isMember(KEEP_SOURCE)) |
310 { | 326 { |
311 job.SetKeepSource(SerializationToolbox::ReadBoolean(body, KEEP_SOURCE)); | 327 job.SetKeepSource(SerializationToolbox::ReadBoolean(body, KEEP_SOURCE)); |
312 } | 328 } |
313 } | 329 } |
335 } | 351 } |
336 | 352 |
337 job->SetOrigin(call); | 353 job->SetOrigin(call); |
338 SetKeepSource(*job, body); | 354 SetKeepSource(*job, body); |
339 | 355 |
340 static const char* TRANSCODE = "Transcode"; | |
341 if (body.isMember(TRANSCODE)) | 356 if (body.isMember(TRANSCODE)) |
342 { | 357 { |
343 job->SetTranscode(SerializationToolbox::ReadString(body, TRANSCODE)); | 358 job->SetTranscode(SerializationToolbox::ReadString(body, TRANSCODE)); |
344 } | 359 } |
345 | 360 |
375 bool isAnonymization, | 390 bool isAnonymization, |
376 RestApiPostCall& call, | 391 RestApiPostCall& call, |
377 const Json::Value& body) | 392 const Json::Value& body) |
378 { | 393 { |
379 std::set<std::string> resources; | 394 std::set<std::string> resources; |
380 SerializationToolbox::ReadSetOfStrings(resources, body, "Resources"); | 395 SerializationToolbox::ReadSetOfStrings(resources, body, RESOURCES); |
381 | 396 |
382 SubmitModificationJob(modification, isAnonymization, | 397 SubmitModificationJob(modification, isAnonymization, |
383 call, body, ResourceType_Instance /* arbitrary value, unused */, | 398 call, body, ResourceType_Instance /* arbitrary value, unused */, |
384 false /* multiple resources */, resources); | 399 false /* multiple resources */, resources); |
385 } | 400 } |
424 OrthancRestApi::DocumentSubmitCommandsJob(call); | 439 OrthancRestApi::DocumentSubmitCommandsJob(call); |
425 DocumentModifyOptions(call); | 440 DocumentModifyOptions(call); |
426 call.GetDocumentation() | 441 call.GetDocumentation() |
427 .SetTag("System") | 442 .SetTag("System") |
428 .SetSummary("Modify a set of resources") | 443 .SetSummary("Modify a set of resources") |
429 .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings, | 444 .SetRequestField(RESOURCES, RestApiCallDocumentation::Type_JsonListOfStrings, |
430 "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true) | 445 "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true) |
431 .SetDescription("Start a job that will modify all the DICOM patients, studies, series or instances " | 446 .SetDescription("Start a job that will modify all the DICOM patients, studies, series or instances " |
432 "whose identifiers are provided in the `Resources` field.") | 447 "whose identifiers are provided in the `Resources` field.") |
433 .AddAnswerType(MimeType_Json, "The list of all the resources that have been altered by this modification"); | 448 .AddAnswerType(MimeType_Json, "The list of all the resources that have been altered by this modification"); |
434 return; | 449 return; |
482 OrthancRestApi::DocumentSubmitCommandsJob(call); | 497 OrthancRestApi::DocumentSubmitCommandsJob(call); |
483 DocumentAnonymizationOptions(call); | 498 DocumentAnonymizationOptions(call); |
484 call.GetDocumentation() | 499 call.GetDocumentation() |
485 .SetTag("System") | 500 .SetTag("System") |
486 .SetSummary("Anonymize a set of resources") | 501 .SetSummary("Anonymize a set of resources") |
487 .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings, | 502 .SetRequestField(RESOURCES, RestApiCallDocumentation::Type_JsonListOfStrings, |
488 "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true) | 503 "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true) |
489 .SetDescription("Start a job that will anonymize all the DICOM patients, studies, series or instances " | 504 .SetDescription("Start a job that will anonymize all the DICOM patients, studies, series or instances " |
490 "whose identifiers are provided in the `Resources` field.") | 505 "whose identifiers are provided in the `Resources` field.") |
491 .AddAnswerType(MimeType_Json, "The list of all the resources that have been created by this anonymization"); | 506 .AddAnswerType(MimeType_Json, "The list of all the resources that have been created by this anonymization"); |
492 return; | 507 return; |
636 { | 651 { |
637 payload = &content[i]; | 652 payload = &content[i]; |
638 } | 653 } |
639 else if (content[i].type() == Json::objectValue) | 654 else if (content[i].type() == Json::objectValue) |
640 { | 655 { |
641 if (!content[i].isMember("Content")) | 656 if (!content[i].isMember(CONTENT)) |
642 { | 657 { |
643 throw OrthancException(ErrorCode_CreateDicomNoPayload); | 658 throw OrthancException(ErrorCode_CreateDicomNoPayload); |
644 } | 659 } |
645 | 660 |
646 payload = &content[i]["Content"]; | 661 payload = &content[i][CONTENT]; |
647 | 662 |
648 if (content[i].isMember("Tags")) | 663 if (content[i].isMember(TAGS)) |
649 { | 664 { |
650 InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags, privateCreator, force); | 665 InjectTags(*dicom, content[i][TAGS], decodeBinaryTags, privateCreator, force); |
651 } | 666 } |
652 } | 667 } |
653 | 668 |
654 if (payload == NULL || | 669 if (payload == NULL || |
655 payload->type() != Json::stringValue) | 670 payload->type() != Json::stringValue) |
687 | 702 |
688 | 703 |
689 static void CreateDicomV2(RestApiPostCall& call, | 704 static void CreateDicomV2(RestApiPostCall& call, |
690 const Json::Value& request) | 705 const Json::Value& request) |
691 { | 706 { |
692 static const char* const CONTENT = "Content"; | |
693 static const char* const FORCE = "Force"; | |
694 static const char* const INTERPRET_BINARY_TAGS = "InterpretBinaryTags"; | |
695 static const char* const PARENT = "Parent"; | |
696 static const char* const PRIVATE_CREATOR = "PrivateCreator"; | |
697 static const char* const SPECIFIC_CHARACTER_SET_2 = "SpecificCharacterSet"; | 707 static const char* const SPECIFIC_CHARACTER_SET_2 = "SpecificCharacterSet"; |
698 static const char* const TAGS = "Tags"; | |
699 static const char* const TYPE = "Type"; | 708 static const char* const TYPE = "Type"; |
700 static const char* const VALUE = "Value"; | 709 static const char* const VALUE = "Value"; |
701 | 710 |
702 assert(request.isObject()); | 711 assert(request.isObject()); |
703 ServerContext& context = OrthancRestApi::GetContext(call); | 712 ServerContext& context = OrthancRestApi::GetContext(call); |
767 } | 776 } |
768 | 777 |
769 | 778 |
770 // Choose the same encoding as the parent resource | 779 // Choose the same encoding as the parent resource |
771 { | 780 { |
772 static const char* SPECIFIC_CHARACTER_SET = "0008,0005"; | 781 static const char* const SPECIFIC_CHARACTER_SET = "0008,0005"; |
773 | 782 |
774 if (siblingTags.isMember(SPECIFIC_CHARACTER_SET)) | 783 if (siblingTags.isMember(SPECIFIC_CHARACTER_SET)) |
775 { | 784 { |
776 Encoding encoding; | 785 Encoding encoding; |
777 | 786 |
946 { | 955 { |
947 call.GetDocumentation() | 956 call.GetDocumentation() |
948 .SetTag("System") | 957 .SetTag("System") |
949 .SetSummary("Create one DICOM instance") | 958 .SetSummary("Create one DICOM instance") |
950 .SetDescription("Create one DICOM instance, and store it into Orthanc") | 959 .SetDescription("Create one DICOM instance, and store it into Orthanc") |
951 .SetRequestField("Tags", RestApiCallDocumentation::Type_JsonObject, | 960 .SetRequestField(TAGS, RestApiCallDocumentation::Type_JsonObject, |
952 "Associative array containing the tags of the new instance to be created", true) | 961 "Associative array containing the tags of the new instance to be created", true) |
953 .SetRequestField("Content", RestApiCallDocumentation::Type_String, | 962 .SetRequestField(CONTENT, RestApiCallDocumentation::Type_String, |
954 "This field can be used to embed an image (pixel data) or a PDF inside the created DICOM instance. " | 963 "This field can be used to embed an image (pixel data) or a PDF inside the created DICOM instance. " |
955 "The PNG image, the JPEG image or the PDF file must be provided using their " | 964 "The PNG image, the JPEG image or the PDF file must be provided using their " |
956 "[data URI scheme encoding](https://en.wikipedia.org/wiki/Data_URI_scheme). " | 965 "[data URI scheme encoding](https://en.wikipedia.org/wiki/Data_URI_scheme). " |
957 "This field can possibly contain a JSON array, in which case a DICOM series is created " | 966 "This field can possibly contain a JSON array, in which case a DICOM series is created " |
958 "containing one DICOM instance for each item in the `Content` field.", false) | 967 "containing one DICOM instance for each item in the `Content` field.", false) |
959 .SetRequestField("Parent", RestApiCallDocumentation::Type_String, | 968 .SetRequestField(PARENT, RestApiCallDocumentation::Type_String, |
960 "If present, the newly created instance will be attached to the parent DICOM resource " | 969 "If present, the newly created instance will be attached to the parent DICOM resource " |
961 "whose Orthanc identifier is contained in this field. The DICOM tags of the parent " | 970 "whose Orthanc identifier is contained in this field. The DICOM tags of the parent " |
962 "modules in the DICOM hierarchy will be automatically copied to the newly created instance.", false) | 971 "modules in the DICOM hierarchy will be automatically copied to the newly created instance.", false) |
963 .SetRequestField("InterpretBinaryTags", RestApiCallDocumentation::Type_Boolean, | 972 .SetRequestField(INTERPRET_BINARY_TAGS, RestApiCallDocumentation::Type_Boolean, |
964 "If some value in the `Tags` associative array is formatted according to some " | 973 "If some value in the `Tags` associative array is formatted according to some " |
965 "[data URI scheme encoding](https://en.wikipedia.org/wiki/Data_URI_scheme), " | 974 "[data URI scheme encoding](https://en.wikipedia.org/wiki/Data_URI_scheme), " |
966 "whether this value is decoded to a binary value or kept as such (`true` by default)", false) | 975 "whether this value is decoded to a binary value or kept as such (`true` by default)", false) |
967 .SetRequestField("PrivateCreator", RestApiCallDocumentation::Type_String, | 976 .SetRequestField(PRIVATE_CREATOR, RestApiCallDocumentation::Type_String, |
968 "The private creator to be used for private tags in `Tags`", false) | 977 "The private creator to be used for private tags in `Tags`", false) |
969 .SetRequestField("Force", RestApiCallDocumentation::Type_Boolean, | 978 .SetRequestField(FORCE, RestApiCallDocumentation::Type_Boolean, |
970 "Avoid the consistency checks for the DICOM tags that enforce the DICOM model of the real-world. " | 979 "Avoid the consistency checks for the DICOM tags that enforce the DICOM model of the real-world. " |
971 "You can notably use this flag if you need to manually set the tags `StudyInstanceUID`, " | 980 "You can notably use this flag if you need to manually set the tags `StudyInstanceUID`, " |
972 "`SeriesInstanceUID`, or `SOPInstanceUID`. Be careful with this feature.", false) | 981 "`SeriesInstanceUID`, or `SOPInstanceUID`. Be careful with this feature.", false) |
973 .SetAnswerField("ID", RestApiCallDocumentation::Type_String, "Orthanc identifier of the newly created instance") | 982 .SetAnswerField("ID", RestApiCallDocumentation::Type_String, "Orthanc identifier of the newly created instance") |
974 .SetAnswerField("Path", RestApiCallDocumentation::Type_String, "Path to access the instance in the REST API"); | 983 .SetAnswerField("Path", RestApiCallDocumentation::Type_String, "Path to access the instance in the REST API"); |
980 !request.isObject()) | 989 !request.isObject()) |
981 { | 990 { |
982 throw OrthancException(ErrorCode_BadRequest); | 991 throw OrthancException(ErrorCode_BadRequest); |
983 } | 992 } |
984 | 993 |
985 if (request.isMember("Tags")) | 994 if (request.isMember(TAGS)) |
986 { | 995 { |
987 CreateDicomV2(call, request); | 996 CreateDicomV2(call, request); |
988 } | 997 } |
989 else | 998 else |
990 { | 999 { |
1005 OrthancRestApi::DocumentSubmitCommandsJob(call); | 1014 OrthancRestApi::DocumentSubmitCommandsJob(call); |
1006 call.GetDocumentation() | 1015 call.GetDocumentation() |
1007 .SetTag("Studies") | 1016 .SetTag("Studies") |
1008 .SetSummary("Split study") | 1017 .SetSummary("Split study") |
1009 .SetDescription("Start a new job so as to split the DICOM study whose Orthanc identifier is provided in the URL, " | 1018 .SetDescription("Start a new job so as to split the DICOM study whose Orthanc identifier is provided in the URL, " |
1010 "by taking some of its children series out of it and putting them into a brand new study (this " | 1019 "by taking some of its children series or instances out of it and putting them into a brand new study " |
1011 "new study is created by setting the `StudyInstanceUID` tag to a random identifier): " | 1020 "(this new study is created by setting the `StudyInstanceUID` tag to a random identifier): " |
1012 "https://book.orthanc-server.com/users/anonymization.html#splitting") | 1021 "https://book.orthanc-server.com/users/anonymization.html#splitting") |
1013 .SetUriArgument("id", "Orthanc identifier of the study of interest") | 1022 .SetUriArgument("id", "Orthanc identifier of the study of interest") |
1014 .SetRequestField("Series", RestApiCallDocumentation::Type_JsonListOfStrings, | 1023 .SetRequestField(SERIES, RestApiCallDocumentation::Type_JsonListOfStrings, |
1015 "The list of series to be separated from the parent study (mandatory option). " | 1024 "The list of series to be separated from the parent study. " |
1016 "These series must all be children of the same source study, that is specified in the URI.", true) | 1025 "These series must all be children of the same source study, that is specified in the URI.", false) |
1017 .SetRequestField("Replace", RestApiCallDocumentation::Type_JsonObject, | 1026 .SetRequestField(REPLACE, RestApiCallDocumentation::Type_JsonObject, |
1018 "Associative array to change the value of some DICOM tags in the new study. " | 1027 "Associative array to change the value of some DICOM tags in the new study. " |
1019 "These tags must be part of the \"Patient Module Attributes\" or the \"General Study " | 1028 "These tags must be part of the \"Patient Module Attributes\" or the \"General Study " |
1020 "Module Attributes\", as specified by the DICOM 2011 standard in Tables C.7-1 and C.7-3.", false) | 1029 "Module Attributes\", as specified by the DICOM 2011 standard in Tables C.7-1 and C.7-3.", false) |
1021 .SetRequestField("Remove", RestApiCallDocumentation::Type_JsonListOfStrings, | 1030 .SetRequestField(REMOVE, RestApiCallDocumentation::Type_JsonListOfStrings, |
1022 "List of tags that must be removed in the new study (from the same modules as in the `Replace` option)", false) | 1031 "List of tags that must be removed in the new study (from the same modules as in the `Replace` option)", false) |
1023 .SetRequestField("KeepSource", RestApiCallDocumentation::Type_Boolean, | 1032 .SetRequestField(KEEP_SOURCE, RestApiCallDocumentation::Type_Boolean, |
1024 "If set to `true`, instructs Orthanc to keep a copy of the original series in the source study. " | 1033 "If set to `true`, instructs Orthanc to keep a copy of the original series/instances in the source study. " |
1025 "By default, the original series are deleted from Orthanc.", false); | 1034 "By default, the original series/instances are deleted from Orthanc.", false) |
1035 .SetRequestField(INSTANCES, RestApiCallDocumentation::Type_JsonListOfStrings, | |
1036 "The list of instances to be separated from the parent study. " | |
1037 "These instances must all be children of the same source study, that is specified in the URI.", false); | |
1026 return; | 1038 return; |
1027 } | 1039 } |
1028 | 1040 |
1029 ServerContext& context = OrthancRestApi::GetContext(call); | 1041 ServerContext& context = OrthancRestApi::GetContext(call); |
1030 | 1042 |
1038 const std::string study = call.GetUriComponent("id", ""); | 1050 const std::string study = call.GetUriComponent("id", ""); |
1039 | 1051 |
1040 std::unique_ptr<SplitStudyJob> job(new SplitStudyJob(context, study)); | 1052 std::unique_ptr<SplitStudyJob> job(new SplitStudyJob(context, study)); |
1041 job->SetOrigin(call); | 1053 job->SetOrigin(call); |
1042 | 1054 |
1043 std::vector<std::string> series; | 1055 bool ok = false; |
1044 SerializationToolbox::ReadArrayOfStrings(series, request, "Series"); | 1056 if (request.isMember(SERIES)) |
1045 | 1057 { |
1046 for (size_t i = 0; i < series.size(); i++) | 1058 std::vector<std::string> series; |
1047 { | 1059 SerializationToolbox::ReadArrayOfStrings(series, request, SERIES); |
1048 job->AddSourceSeries(series[i]); | 1060 |
1049 } | 1061 for (size_t i = 0; i < series.size(); i++) |
1062 { | |
1063 job->AddSourceSeries(series[i]); | |
1064 ok = true; | |
1065 } | |
1066 } | |
1067 | |
1068 if (request.isMember(INSTANCES)) | |
1069 { | |
1070 std::vector<std::string> instances; | |
1071 SerializationToolbox::ReadArrayOfStrings(instances, request, INSTANCES); | |
1072 | |
1073 for (size_t i = 0; i < instances.size(); i++) | |
1074 { | |
1075 job->AddSourceInstance(instances[i]); | |
1076 ok = true; | |
1077 } | |
1078 } | |
1079 | |
1080 if (!ok) | |
1081 { | |
1082 throw OrthancException(ErrorCode_BadRequest, "Both the \"Series\" and the \"Instances\" fields are missing"); | |
1083 } | |
1050 | 1084 |
1051 job->AddTrailingStep(); | 1085 job->AddTrailingStep(); |
1052 | 1086 |
1053 SetKeepSource(*job, request); | 1087 SetKeepSource(*job, request); |
1054 | 1088 |
1055 static const char* REMOVE = "Remove"; | |
1056 if (request.isMember(REMOVE)) | 1089 if (request.isMember(REMOVE)) |
1057 { | 1090 { |
1058 if (request[REMOVE].type() != Json::arrayValue) | 1091 if (request[REMOVE].type() != Json::arrayValue) |
1059 { | 1092 { |
1060 throw OrthancException(ErrorCode_BadFileFormat); | 1093 throw OrthancException(ErrorCode_BadFileFormat); |
1071 job->Remove(FromDcmtkBridge::ParseTag(request[REMOVE][i].asCString())); | 1104 job->Remove(FromDcmtkBridge::ParseTag(request[REMOVE][i].asCString())); |
1072 } | 1105 } |
1073 } | 1106 } |
1074 } | 1107 } |
1075 | 1108 |
1076 static const char* REPLACE = "Replace"; | |
1077 if (request.isMember(REPLACE)) | 1109 if (request.isMember(REPLACE)) |
1078 { | 1110 { |
1079 if (request[REPLACE].type() != Json::objectValue) | 1111 if (request[REPLACE].type() != Json::objectValue) |
1080 { | 1112 { |
1081 throw OrthancException(ErrorCode_BadFileFormat); | 1113 throw OrthancException(ErrorCode_BadFileFormat); |
1112 .SetTag("Studies") | 1144 .SetTag("Studies") |
1113 .SetSummary("Merge study") | 1145 .SetSummary("Merge study") |
1114 .SetDescription("Start a new job so as to move some DICOM series into the DICOM study whose Orthanc identifier " | 1146 .SetDescription("Start a new job so as to move some DICOM series into the DICOM study whose Orthanc identifier " |
1115 "is provided in the URL: https://book.orthanc-server.com/users/anonymization.html#merging") | 1147 "is provided in the URL: https://book.orthanc-server.com/users/anonymization.html#merging") |
1116 .SetUriArgument("id", "Orthanc identifier of the study of interest") | 1148 .SetUriArgument("id", "Orthanc identifier of the study of interest") |
1117 .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings, | 1149 .SetRequestField(RESOURCES, RestApiCallDocumentation::Type_JsonListOfStrings, |
1118 "The list of DICOM resources (patients, studies, series, and/or instances) to be merged " | 1150 "The list of DICOM resources (patients, studies, series, and/or instances) to be merged " |
1119 "into the study of interest (mandatory option)", true) | 1151 "into the study of interest (mandatory option)", true) |
1120 .SetRequestField("KeepSource", RestApiCallDocumentation::Type_Boolean, | 1152 .SetRequestField(KEEP_SOURCE, RestApiCallDocumentation::Type_Boolean, |
1121 "If set to `true`, instructs Orthanc to keep a copy of the original resources in their source study. " | 1153 "If set to `true`, instructs Orthanc to keep a copy of the original resources in their source study. " |
1122 "By default, the original resources are deleted from Orthanc.", false); | 1154 "By default, the original resources are deleted from Orthanc.", false); |
1123 return; | 1155 return; |
1124 } | 1156 } |
1125 | 1157 |
1136 | 1168 |
1137 std::unique_ptr<MergeStudyJob> job(new MergeStudyJob(context, study)); | 1169 std::unique_ptr<MergeStudyJob> job(new MergeStudyJob(context, study)); |
1138 job->SetOrigin(call); | 1170 job->SetOrigin(call); |
1139 | 1171 |
1140 std::vector<std::string> resources; | 1172 std::vector<std::string> resources; |
1141 SerializationToolbox::ReadArrayOfStrings(resources, request, "Resources"); | 1173 SerializationToolbox::ReadArrayOfStrings(resources, request, RESOURCES); |
1142 | 1174 |
1143 for (size_t i = 0; i < resources.size(); i++) | 1175 for (size_t i = 0; i < resources.size(); i++) |
1144 { | 1176 { |
1145 job->AddSource(resources[i]); | 1177 job->AddSource(resources[i]); |
1146 } | 1178 } |