changeset 4527:31f940334496

Handle public tags with "UN" value representation and containing a string (cf. DICOM CP 246)
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 24 Feb 2021 12:39:35 +0100
parents 59b667dd58a8
children 93a51d228d80
files NEWS OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp OrthancFramework/Sources/Toolbox.cpp
diffstat 3 files changed, 75 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Tue Feb 23 20:05:26 2021 +0100
+++ b/NEWS	Wed Feb 24 12:39:35 2021 +0100
@@ -6,7 +6,7 @@
 
 * The "dicom-as-json" attachments are not explicitly stored anymore to improve performance
 * If the storage area doesn't support range reading, or if "StorageCompression"
-  is enabled, a new type of attachment "dicom-until-pixel-data" is generated.
+  is enabled, a new type of attachment "dicom-until-pixel-data" is generated
 * New metadata automatically computed at the instance level: "PixelDataOffset"
 * New configuration option related to networking:
   - "Timeout" in "DicomModalities" to set DICOM SCU timeout on a per-modality basis
@@ -17,7 +17,8 @@
 
 * API version upgraded to 11
 * BREAKING CHANGES:
-  - External applications should *not* call "/instances/.../attachments/dicom-as-json" anymore
+  - External applications should not call "/instances/.../attachments/dicom-as-json" anymore,
+    and should use "/instances/.../tags" instead
   - "/instances/.../tags" route does not report the tags after "Pixel Data" (7fe0,0010) anymore
 * "/peers/{id}/store-straight": Synchronously send the DICOM instance in POST body to the peer
 * New arguments in the REST API:
@@ -37,6 +38,7 @@
 * Improved precision of floating-point numbers in DICOM-as-JSON and DICOM summary
 * Optimization in C-STORE SCP by avoiding an unnecessary DICOM parsing
 * Fix build on big-endian architectures
+* Handle public tags with "UN" value representation and containing a string (cf. DICOM CP 246)
 * The numbering of sequences in Orthanc Explorer now uses the DICOM convention (starts at 1)
 * Possibility to generate a static library containing the Orthanc Framework
 
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Tue Feb 23 20:05:26 2021 +0100
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Wed Feb 24 12:39:35 2021 +0100
@@ -554,6 +554,24 @@
   }
 
 
+  static DicomValue* CreateValueFromUtf8String(const DicomTag& tag,
+                                               const std::string& utf8,
+                                               unsigned int maxStringLength,
+                                               const std::set<DicomTag>& ignoreTagLength)
+  {
+    if (maxStringLength != 0 &&
+        utf8.size() > maxStringLength &&
+        ignoreTagLength.find(tag) == ignoreTagLength.end())
+    {
+      return new DicomValue;  // Too long, create a NULL value
+    }
+    else
+    {
+      return new DicomValue(utf8, false);
+    }
+  }
+
+
   DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element,
                                                   DicomToJsonFlags flags,
                                                   unsigned int maxStringLength,
@@ -567,28 +585,20 @@
       throw OrthancException(ErrorCode_BadParameterType);
     }
 
-    char *c = NULL;
-    if (element.isaString() &&
-        element.getString(c).good())
     {
-      if (c == NULL)  // This case corresponds to the empty string
+      char *c = NULL;
+      if (element.isaString() &&
+          element.getString(c).good())
       {
-        return new DicomValue("", false);
-      }
-      else
-      {
-        std::string s(c);
-        std::string utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions);
-
-        if (maxStringLength != 0 &&
-            utf8.size() > maxStringLength &&
-            ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end())
+        if (c == NULL)  // This case corresponds to the empty string
         {
-          return new DicomValue;  // Too long, create a NULL value
+          return new DicomValue("", false);
         }
         else
         {
-          return new DicomValue(utf8, false);
+          const std::string s(c);
+          const std::string utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions);
+          return CreateValueFromUtf8String(GetTag(element), utf8, maxStringLength, ignoreTagLength);
         }
       }
     }
@@ -596,9 +606,14 @@
 
     if (element.getVR() == EVR_UN)
     {
-      // Unknown value representation: Lookup in the dictionary. This
-      // is notably the case for private tags registered with the
-      // "Dictionary" configuration option.
+      /**
+       * Unknown value representation: Lookup in the dictionary. This
+       * is notably the case for private tags registered with the
+       * "Dictionary" configuration option, or for public tags with
+       * "EVR_UN" in the case of Little Endian Implicit transfer
+       * syntax (cf. DICOM CP 246).
+       * ftp://medical.nema.org/medical/dicom/final/cp246_ft.pdf
+       **/
       DictionaryLocker locker;
       
       const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), 
@@ -608,27 +623,49 @@
       {
         Uint8* data = NULL;
 
-        // At (*), we do not try and convert to UTF-8, as nothing says
-        // the encoding of the private tag is the same as that of the
-        // remaining of the DICOM dataset. Only go for ASCII strings.
-
-        if (element.getUint8Array(data) == EC_Normal &&
-            Toolbox::IsAsciiString(data, element.getLength()))   // (*)
+        if (element.getUint8Array(data) == EC_Normal)
         {
-          if (data == NULL)
+          Uint32 length = element.getLength();
+
+          if (data == NULL ||
+              length == 0)
           {
             return new DicomValue("", false);   // Empty string
           }
-          else if (maxStringLength != 0 &&
-                   element.getLength() > maxStringLength &&
-                   ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end())
+
+          // Remove the trailing padding, if any
+          if (length > 0 &&
+              length % 2 == 0 &&
+              data[length - 1] == '\0')
+          {
+            length = length - 1;
+          }
+
+          if (element.getTag().isPrivate())
           {
-            return new DicomValue;  // Too long, create a NULL value
+            // For private tags, we do not try and convert to UTF-8,
+            // as nothing ensures that the encoding of the private tag
+            // is the same as that of the remaining of the DICOM
+            // dataset. Only go for ASCII strings.
+            if (Toolbox::IsAsciiString(data, length))
+            {
+              const std::string s(reinterpret_cast<const char*>(data), length);
+              return CreateValueFromUtf8String(GetTag(element), s, maxStringLength, ignoreTagLength);
+            }
+            else
+            {
+              // Not a plain ASCII string: Consider it as a binary
+              // value that is handled in the switch-case below
+            }
           }
           else
           {
-            std::string s(reinterpret_cast<const char*>(data), element.getLength());
-            return new DicomValue(s, false);
+            // For public tags, convert to UTF-8 by using the
+            // "SpecificCharacterSet" tag, if present. This branch is
+            // new in Orthanc 1.9.1 (cf. DICOM CP 246).
+            const std::string s(reinterpret_cast<const char*>(data), length);
+            const std::string utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions);
+            return CreateValueFromUtf8String(GetTag(element), utf8, maxStringLength, ignoreTagLength);
           }
         }
       }
--- a/OrthancFramework/Sources/Toolbox.cpp	Tue Feb 23 20:05:26 2021 +0100
+++ b/OrthancFramework/Sources/Toolbox.cpp	Wed Feb 24 12:39:35 2021 +0100
@@ -2270,7 +2270,8 @@
         target[name] = v["Value"].asString();
       }
       else if (type == "TooLong" ||
-               type == "Null")
+               type == "Null" ||
+               type == "Binary")
       {
         target[name] = Json::nullValue;
       }