changeset 488:b0e9d55ee247

Fix compliance with DICOM >= 2016c: WADO-RS Retrieve transcodes to Little Endian Explicit by default
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 18 Jan 2021 16:46:17 +0100
parents 327acd0232d8
children 4cb232b42168
files NEWS Plugin/WadoRs.cpp
diffstat 2 files changed, 76 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Jan 06 17:35:27 2021 +0100
+++ b/NEWS	Mon Jan 18 16:46:17 2021 +0100
@@ -1,6 +1,9 @@
 Pending changes in the mainline
 ===============================
 
+* Fix compliance with DICOM >= 2016c: If no "transfer-syntax" is provided in WADO-RS
+  Retrieve Instance/Series/Study, DICOM shall be transcoded to Little Endian Explicit
+
 
 Version 1.4 (2020-12-18)
 ========================
--- a/Plugin/WadoRs.cpp	Wed Jan 06 17:35:27 2021 +0100
+++ b/Plugin/WadoRs.cpp	Mon Jan 18 16:46:17 2021 +0100
@@ -57,16 +57,41 @@
 
 
 
-static bool AcceptMultipartDicom(bool& transcode,
+static void AcceptMultipartDicom(bool& transcode,
                                  Orthanc::DicomTransferSyntax& targetSyntax /* only if transcoding */,
                                  const OrthancPluginHttpRequest* request)
 {
-  transcode = false;
+  /**
+   * Up to release 1.4 of the DICOMweb plugin, WADO-RS
+   * RetrieveInstance, RetrieveSeries and RetrieveStudy did *NOT*
+   * transcode if no transer syntax was explicitly provided. This was
+   * because the DICOM standard didn't specify a behavior in this case
+   * up to DICOM 2016b:
+   * http://dicom.nema.org/medical/dicom/2016b/output/chtml/part18/sect_6.5.3.html
+   *
+   * However, starting with DICOM 2016c, it is explicitly stated that
+   * "If transfer-syntax is not specified in the dcm-parameters the
+   * origin server shall use the Explicit VR Little Endian Transfer
+   * Syntax "1.2.840.10008.1.2.1" for each Instance":
+   * http://dicom.nema.org/medical/dicom/2016c/output/chtml/part18/sect_6.5.3.html
+   * 
+   * As a consequence, starting with release 1.5 of the DICOMweb
+   * plugin, transcoding to "Little Endian Explicit" takes place by
+   * default. If this transcoding is not desirable, the "Accept" HTTP
+   * header can be set to
+   * "multipart/related;type=application/dicom;transfer-syntax=*" (not
+   * the asterisk "*") in order to prevent transcoding. The same
+   * convention is used by the Google Cloud Platform:
+   * https://cloud.google.com/healthcare/docs/dicom
+   **/
+  transcode = true;
+  targetSyntax = Orthanc::DicomTransferSyntax_LittleEndianExplicit;
+  
   std::string accept;
 
   if (!OrthancPlugins::LookupHttpHeader(accept, request, "accept"))
   {
-    return true;   // By default, return "multipart/related; type=application/dicom;"
+    return;   // By default, return "multipart/related; type=application/dicom;"
   }
 
   std::string application;
@@ -116,8 +141,6 @@
       }
     }
   }
-
-  return true;
 }
 
 
@@ -274,15 +297,41 @@
   {
     throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
   }
-  
+
   for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
   {
-    std::string uri = "/instances/" + instances[i]["ID"].asString() + "/file";
+    const std::string uri = "/instances/" + instances[i]["ID"].asString();
 
+    bool transcodeThisInstance;
+    
+    std::string sourceTransferSyntax;
+    if (!transcode)
+    {
+      transcodeThisInstance = false;      
+    }
+    else if (OrthancPlugins::RestApiGetString(sourceTransferSyntax, uri + "/metadata/TransferSyntax", false))
+    {
+      // Avoid transcoding if the source file already uses the expected transfer syntax
+      Orthanc::DicomTransferSyntax syntax;
+      if (Orthanc::LookupTransferSyntax(syntax, sourceTransferSyntax))
+      {
+        transcodeThisInstance = (syntax != targetSyntax);
+      }
+      else
+      {
+        transcodeThisInstance = true;
+      }
+    }
+    else
+    {
+      // The transfer syntax of the source file is unknown, transcode it to be sure
+      transcodeThisInstance = true;
+    }
+    
     OrthancPlugins::MemoryBuffer dicom;
-    if (dicom.RestApiGet(uri, false))
+    if (dicom.RestApiGet(uri + "/file", false))
     {
-      if (transcode)
+      if (transcodeThisInstance)
       {
         std::unique_ptr<OrthancPlugins::DicomInstance> transcoded(
           OrthancPlugins::DicomInstance::Transcode(
@@ -907,18 +956,13 @@
 {
   bool transcode;
   Orthanc::DicomTransferSyntax targetSyntax;
+
+  AcceptMultipartDicom(transcode, targetSyntax, request);
   
-  if (!AcceptMultipartDicom(transcode, targetSyntax, request))
+  std::string orthancId, studyInstanceUid;
+  if (LocateStudy(output, orthancId, studyInstanceUid, request))
   {
-    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
-  }
-  else
-  {
-    std::string orthancId, studyInstanceUid;
-    if (LocateStudy(output, orthancId, studyInstanceUid, request))
-    {
-      AnswerListOfDicomInstances(output, Orthanc::ResourceType_Study, orthancId, transcode, targetSyntax);
-    }
+    AnswerListOfDicomInstances(output, Orthanc::ResourceType_Study, orthancId, transcode, targetSyntax);
   }
 }
 
@@ -930,17 +974,12 @@
   bool transcode;
   Orthanc::DicomTransferSyntax targetSyntax;
   
-  if (!AcceptMultipartDicom(transcode, targetSyntax, request))
-  {
-    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
-  }
-  else
+  AcceptMultipartDicom(transcode, targetSyntax, request);
+  
+  std::string orthancId, studyInstanceUid, seriesInstanceUid;
+  if (LocateSeries(output, orthancId, studyInstanceUid, seriesInstanceUid, request))
   {
-    std::string orthancId, studyInstanceUid, seriesInstanceUid;
-    if (LocateSeries(output, orthancId, studyInstanceUid, seriesInstanceUid, request))
-    {
-      AnswerListOfDicomInstances(output, Orthanc::ResourceType_Series, orthancId, transcode, targetSyntax);
-    }
+    AnswerListOfDicomInstances(output, Orthanc::ResourceType_Series, orthancId, transcode, targetSyntax);
   }
 }
 
@@ -953,17 +992,12 @@
   bool transcode;
   Orthanc::DicomTransferSyntax targetSyntax;
   
-  if (!AcceptMultipartDicom(transcode, targetSyntax, request))
-  {
-    OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */);
-  }
-  else
+  AcceptMultipartDicom(transcode, targetSyntax, request);
+  
+  std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;
+  if (LocateInstance(output, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request))
   {
-    std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid;
-    if (LocateInstance(output, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request))
-    {
-      AnswerListOfDicomInstances(output, Orthanc::ResourceType_Instance, orthancId, transcode, targetSyntax);
-    }
+    AnswerListOfDicomInstances(output, Orthanc::ResourceType_Instance, orthancId, transcode, targetSyntax);
   }
 }