# HG changeset patch # User Sebastien Jodogne # Date 1624637625 -7200 # Node ID 7826ac059c310ae6bcc33805a155300c93d30f8f # Parent 4e2247df63271b117a530c663e3091af7dede62e Added Short/Simplify/Full options to format "/modalities/{id}/find-worklist" and "/queries/{id}/retrieve" diff -r 4e2247df6327 -r 7826ac059c31 NEWS --- a/NEWS Fri Jun 25 10:41:35 2021 +0200 +++ b/NEWS Fri Jun 25 18:13:45 2021 +0200 @@ -5,8 +5,10 @@ -------- * API version upgraded to 14 -* Added "Short" and "Full" options to control the format of DICOM tags in: - - POST /modalities/id/find-worklist +* Added "Short", "Simplify" and/or "Full" options to control the format of DICOM tags in: + - POST /modalities/{id}/find-worklist + - POST /queries/{id}/answers/{index}/retrieve + - POST /queries/{id}/retrieve Version 1.9.4 (2021-06-24) @@ -51,6 +53,7 @@ - GET /series/{id}/instances-tags, GET /studies/{id}/shared-tags - GET /patients/{id}/module, GET /patients/{id}/patient-module - GET /series/{id}/module, GET /studies/{id}/module, GET /instances/{id}/module + - GET /queries/{id}/answers&expand, GET /queries/{id}/answers/{index}/content - POST /tools/find * "/studies/{id}/split" accepts "Instances" parameter to split instances instead of series * "/studies/{id}/merge" accepts instances inside its "Resources" parameter diff -r 4e2247df6327 -r 7826ac059c31 OrthancFramework/Sources/Enumerations.cpp --- a/OrthancFramework/Sources/Enumerations.cpp Fri Jun 25 10:41:35 2021 +0200 +++ b/OrthancFramework/Sources/Enumerations.cpp Fri Jun 25 18:13:45 2021 +0200 @@ -1158,6 +1158,25 @@ } + const char* EnumerationToString(DicomToJsonFormat format) + { + switch (format) + { + case DicomToJsonFormat_Full: + return "Full"; + + case DicomToJsonFormat_Human: + return "Simplify"; + + case DicomToJsonFormat_Short: + return "Short"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + Encoding StringToEncoding(const char* encoding) { std::string s(encoding); @@ -1809,6 +1828,27 @@ } + DicomToJsonFormat StringToDicomToJsonFormat(const std::string& format) + { + if (format == "Full") + { + return DicomToJsonFormat_Full; + } + else if (format == "Short") + { + return DicomToJsonFormat_Short; + } + else if (format == "Simplify") + { + return DicomToJsonFormat_Human; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + unsigned int GetBytesPerPixel(PixelFormat format) { switch (format) diff -r 4e2247df6327 -r 7826ac059c31 OrthancFramework/Sources/Enumerations.h --- a/OrthancFramework/Sources/Enumerations.h Fri Jun 25 10:41:35 2021 +0200 +++ b/OrthancFramework/Sources/Enumerations.h Fri Jun 25 18:13:45 2021 +0200 @@ -801,6 +801,9 @@ const char* EnumerationToString(StorageCommitmentFailureReason reason); ORTHANC_PUBLIC + const char* EnumerationToString(DicomToJsonFormat format); + + ORTHANC_PUBLIC Encoding StringToEncoding(const char* encoding); ORTHANC_PUBLIC @@ -832,6 +835,9 @@ MimeType StringToMimeType(const std::string& mime); ORTHANC_PUBLIC + DicomToJsonFormat StringToDicomToJsonFormat(const std::string& format); + + ORTHANC_PUBLIC bool LookupMimeType(MimeType& target, const std::string& source); diff -r 4e2247df6327 -r 7826ac059c31 OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Fri Jun 25 10:41:35 2021 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Fri Jun 25 18:13:45 2021 +0200 @@ -936,6 +936,7 @@ } std::unique_ptr job(new DicomMoveScuJob(context)); + job->SetQueryFormat(OrthancRestApi::GetDicomFormat(body, DicomToJsonFormat_Short)); { QueryAccessor query(call); @@ -979,6 +980,8 @@ static void DocumentRetrieveShared(RestApiPostCall& call) { OrthancRestApi::DocumentSubmitCommandsJob(call); + OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Short); + call.GetDocumentation() .SetTag("Networking") .SetUriArgument("id", "Identifier of the query of interest") diff -r 4e2247df6327 -r 7826ac059c31 OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Fri Jun 25 10:41:35 2021 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Fri Jun 25 18:13:45 2021 +0200 @@ -393,7 +393,6 @@ Json::Value json; if (call.ParseJsonRequest(json)) { - std::cout << json.toStyledString(); OrthancConfiguration::ParseAcceptedTransferSyntaxes(syntaxes, json); } else diff -r 4e2247df6327 -r 7826ac059c31 OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp --- a/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp Fri Jun 25 10:41:35 2021 +0200 +++ b/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp Fri Jun 25 18:13:45 2021 +0200 @@ -33,13 +33,15 @@ #include "DicomMoveScuJob.h" +#include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" #include "../../../OrthancFramework/Sources/SerializationToolbox.h" #include "../ServerContext.h" static const char* const LOCAL_AET = "LocalAet"; -static const char* const TARGET_AET = "TargetAet"; +static const char* const QUERY = "Query"; +static const char* const QUERY_FORMAT = "QueryFormat"; // New in 1.9.5 static const char* const REMOTE = "Remote"; -static const char* const QUERY = "Query"; +static const char* const TARGET_AET = "TargetAet"; static const char* const TIMEOUT = "Timeout"; namespace Orthanc @@ -92,7 +94,6 @@ }; - void DicomMoveScuJob::Retrieve(const DicomMap& findAnswer) { if (connection_.get() == NULL) @@ -104,33 +105,30 @@ } - static void AddTagIfString(Json::Value& target, - const DicomMap& answer, - const DicomTag& tag) + static void AddToQuery(DicomFindAnswers& query, + const DicomMap& item) { - const DicomValue* value = answer.TestAndGetValue(tag); - if (value != NULL && - !value->IsNull() && - !value->IsBinary()) - { - target[tag.Format()] = value->GetContent(); - } + query.Add(item); + + /** + * Compatibility with Orthanc <= 1.9.4: Remove the + * "SpecificCharacterSet" (0008,0005) tag that is automatically + * added if creating a ParsedDicomFile object from a DicomMap. + **/ + query.GetAnswer(query.GetSize() - 1).Remove(DICOM_TAG_SPECIFIC_CHARACTER_SET); } - + void DicomMoveScuJob::AddFindAnswer(const DicomMap& answer) { - assert(query_.type() == Json::arrayValue); - - // Copy the identifiers tags, if they exist - Json::Value item = Json::objectValue; - AddTagIfString(item, answer, DICOM_TAG_QUERY_RETRIEVE_LEVEL); - AddTagIfString(item, answer, DICOM_TAG_PATIENT_ID); - AddTagIfString(item, answer, DICOM_TAG_STUDY_INSTANCE_UID); - AddTagIfString(item, answer, DICOM_TAG_SERIES_INSTANCE_UID); - AddTagIfString(item, answer, DICOM_TAG_SOP_INSTANCE_UID); - AddTagIfString(item, answer, DICOM_TAG_ACCESSION_NUMBER); - query_.append(item); + DicomMap item; + item.CopyTagIfExists(answer, DICOM_TAG_QUERY_RETRIEVE_LEVEL); + item.CopyTagIfExists(answer, DICOM_TAG_PATIENT_ID); + item.CopyTagIfExists(answer, DICOM_TAG_STUDY_INSTANCE_UID); + item.CopyTagIfExists(answer, DICOM_TAG_SERIES_INSTANCE_UID); + item.CopyTagIfExists(answer, DICOM_TAG_SOP_INSTANCE_UID); + item.CopyTagIfExists(answer, DICOM_TAG_ACCESSION_NUMBER); + AddToQuery(query_, item); AddCommand(new Command(*this, answer)); } @@ -222,7 +220,9 @@ value[LOCAL_AET] = parameters_.GetLocalApplicationEntityTitle(); value["RemoteAet"] = parameters_.GetRemoteModality().GetApplicationEntityTitle(); - value["Query"] = query_; + + value[QUERY] = Json::objectValue; + query_.ToJson(value[QUERY], queryFormat_); } @@ -232,13 +232,26 @@ context_(context), parameters_(DicomAssociationParameters::UnserializeJob(serialized)), targetAet_(SerializationToolbox::ReadString(serialized, TARGET_AET)), - query_(Json::arrayValue), + query_(true), queryFormat_(DicomToJsonFormat_Short) { - if (serialized.isMember(QUERY) && - serialized[QUERY].type() == Json::arrayValue) + if (serialized.isMember(QUERY)) { - query_ = serialized[QUERY]; + const Json::Value& query = serialized[QUERY]; + if (query.type() == Json::arrayValue) + { + for (Json::Value::ArrayIndex i = 0; i < query.size(); i++) + { + DicomMap item; + FromDcmtkBridge::FromJson(item, query[i]); + AddToQuery(query_, item); + } + } + } + + if (serialized.isMember(QUERY_FORMAT)) + { + queryFormat_ = StringToDicomToJsonFormat(SerializationToolbox::ReadString(serialized, QUERY_FORMAT)); } } @@ -253,7 +266,13 @@ { parameters_.SerializeJob(target); target[TARGET_AET] = targetAet_; - target[QUERY] = query_; + + // "Short" is for compatibility with Orthanc <= 1.9.4 + target[QUERY] = Json::objectValue; + query_.ToJson(target[QUERY], DicomToJsonFormat_Short); + + target[QUERY_FORMAT] = EnumerationToString(queryFormat_); + return true; } } diff -r 4e2247df6327 -r 7826ac059c31 OrthancServer/Sources/ServerJobs/DicomMoveScuJob.h --- a/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.h Fri Jun 25 10:41:35 2021 +0200 +++ b/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.h Fri Jun 25 18:13:45 2021 +0200 @@ -52,7 +52,7 @@ ServerContext& context_; DicomAssociationParameters parameters_; std::string targetAet_; - Json::Value query_; + DicomFindAnswers query_; DicomToJsonFormat queryFormat_; // New in 1.9.5 std::unique_ptr connection_; @@ -62,7 +62,7 @@ public: explicit DicomMoveScuJob(ServerContext& context) : context_(context), - query_(Json::arrayValue), + query_(true /* this is for worklists */), queryFormat_(DicomToJsonFormat_Short) { } diff -r 4e2247df6327 -r 7826ac059c31 OrthancServer/UnitTestsSources/ServerJobsTests.cpp --- a/OrthancServer/UnitTestsSources/ServerJobsTests.cpp Fri Jun 25 10:41:35 2021 +0200 +++ b/OrthancServer/UnitTestsSources/ServerJobsTests.cpp Fri Jun 25 18:13:45 2021 +0200 @@ -1196,6 +1196,7 @@ ASSERT_EQ(104u, job->GetParameters().GetRemoteModality().GetPortNumber()); ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); ASSERT_EQ(DicomAssociationParameters::GetDefaultTimeout(), job->GetParameters().GetTimeout()); + ASSERT_EQ(DicomToJsonFormat_Short, job->GetQueryFormat()); } { @@ -1208,6 +1209,8 @@ job.SetLocalAet("WORLD"); job.SetRemoteModality(r); job.SetTimeout(43); + job.SetQueryFormat(DicomToJsonFormat_Human); + job.Serialize(v); } @@ -1221,5 +1224,63 @@ ASSERT_EQ(42u, job->GetParameters().GetRemoteModality().GetPortNumber()); ASSERT_EQ(ModalityManufacturer_Generic, job->GetParameters().GetRemoteModality().GetManufacturer()); ASSERT_EQ(43u, job->GetParameters().GetTimeout()); + ASSERT_EQ(DicomToJsonFormat_Human, job->GetQueryFormat()); } } + + +TEST_F(OrthancJobsSerialization, DicomMoveScuJob) +{ + Json::Value command = Json::objectValue; + command["0008,0005"]["Type"] = "String"; + command["0008,0005"]["Content"] = "ISO_IR 100"; + command["0010,0020"]["Type"] = "String"; + command["0010,0020"]["Content"] = "1234"; + + Json::Value query = Json::objectValue; + query["0010,0020"] = "456"; + query["0008,0052"] = "STUDY"; + + Json::Value remote = Json::objectValue; + remote["AET"] = "REMOTE"; + remote["Host"] = "192.168.1.1"; + remote["Port"] = 4242; + + Json::Value s = Json::objectValue; + s["Permissive"] = true; + s["Position"] = 1; + s["Description"] = "test"; + s["Remote"] = remote; + s["LocalAet"] = "LOCAL"; + s["TargetAet"] = "TARGET"; + s["QueryFormat"] = "Full"; + s["Query"] = Json::arrayValue; + s["Query"].append(query); + s["Commands"] = Json::arrayValue; + s["Commands"].append(command); + + Json::Value s2; + + { + DicomMoveScuJob job(GetContext(), s); + job.Serialize(s2); + } + + { + DicomMoveScuJob job(GetContext(), s2); + ASSERT_EQ("TARGET", job.GetTargetAet()); + ASSERT_EQ("LOCAL", job.GetParameters().GetLocalApplicationEntityTitle()); + ASSERT_EQ("REMOTE", job.GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + ASSERT_EQ("192.168.1.1", job.GetParameters().GetRemoteModality().GetHost()); + ASSERT_EQ(4242u, job.GetParameters().GetRemoteModality().GetPortNumber()); + ASSERT_EQ("test", job.GetDescription()); + ASSERT_TRUE(job.IsPermissive()); + ASSERT_EQ(1u, job.GetPosition()); + ASSERT_EQ(1u, job.GetCommandsCount()); + ASSERT_EQ(DicomToJsonFormat_Full, job.GetQueryFormat()); + ASSERT_EQ(1u, s2["Commands"].size()); + ASSERT_EQ(command.toStyledString(), s2["Commands"][0].toStyledString()); + ASSERT_EQ(1u, s2["Query"].size()); + ASSERT_EQ(query.toStyledString(), s2["Query"][0].toStyledString()); + } +}