changeset 4730:7826ac059c31

Added Short/Simplify/Full options to format "/modalities/{id}/find-worklist" and "/queries/{id}/retrieve"
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 25 Jun 2021 18:13:45 +0200
parents 4e2247df6327
children 283d246fafdb
files NEWS OrthancFramework/Sources/Enumerations.cpp OrthancFramework/Sources/Enumerations.h OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp OrthancServer/Sources/ServerJobs/DicomMoveScuJob.h OrthancServer/UnitTestsSources/ServerJobsTests.cpp
diffstat 8 files changed, 167 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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)
--- 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);
   
--- 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<DicomMoveScuJob> 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")
--- 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
--- 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;
     }
   }
--- 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<DicomControlUserConnection>  connection_;
@@ -62,7 +62,7 @@
   public:
     explicit DicomMoveScuJob(ServerContext& context) :
       context_(context),
-      query_(Json::arrayValue),
+      query_(true /* this is for worklists */),
       queryFormat_(DicomToJsonFormat_Short)
     {
     }
--- 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());
+  }
+}