Mercurial > hg > orthanc
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 } |