changeset 2129:0c09d1af22f3

"/tools/invalidate-tags" to invalidate the JSON summary of all the DICOM files
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 07 Nov 2016 17:44:10 +0100
parents 9329ba17a069
children 72cb107a7346
files Core/FileStorage/FilesystemStorage.cpp Core/Toolbox.cpp Core/Toolbox.h NEWS OrthancServer/FromDcmtkBridge.cpp OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/ServerToolbox.cpp Resources/Configuration.json
diffstat 8 files changed, 105 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/Core/FileStorage/FilesystemStorage.cpp	Mon Nov 07 15:13:16 2016 +0100
+++ b/Core/FileStorage/FilesystemStorage.cpp	Mon Nov 07 17:44:10 2016 +0100
@@ -223,9 +223,9 @@
 
 
   void FilesystemStorage::Remove(const std::string& uuid,
-                                 FileContentType /*type*/)
+                                 FileContentType type)
   {
-    LOG(INFO) << "Deleting attachment \"" << uuid << "\"";
+    LOG(INFO) << "Deleting attachment \"" << uuid << "\" of type " << static_cast<int>(type);
 
     namespace fs = boost::filesystem;
 
--- a/Core/Toolbox.cpp	Mon Nov 07 15:13:16 2016 +0100
+++ b/Core/Toolbox.cpp	Mon Nov 07 17:44:10 2016 +0100
@@ -844,6 +844,23 @@
   }
 
 
+  bool Toolbox::IsAsciiString(const void* data,
+                              size_t size)
+  {
+    const uint8_t* p = reinterpret_cast<const uint8_t*>(data);
+
+    for (size_t i = 0; i < size; i++, p++)
+    {
+      if (*p > 127 || (*p != 0 && iscntrl(*p)))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
   std::string Toolbox::ConvertToAscii(const std::string& source)
   {
     std::string result;
--- a/Core/Toolbox.h	Mon Nov 07 15:13:16 2016 +0100
+++ b/Core/Toolbox.h	Mon Nov 07 17:44:10 2016 +0100
@@ -163,6 +163,9 @@
     std::string ConvertFromUtf8(const std::string& source,
                                 Encoding targetEncoding);
 
+    bool IsAsciiString(const void* data,
+                       size_t size);
+
     std::string ConvertToAscii(const std::string& source);
 
     std::string StripSpaces(const std::string& source);
--- a/NEWS	Mon Nov 07 15:13:16 2016 +0100
+++ b/NEWS	Mon Nov 07 17:44:10 2016 +0100
@@ -10,10 +10,12 @@
 REST API
 --------
 
+* "/tools/invalidate-tags" to invalidate the JSON summary of all the DICOM files
+  (useful if private tags are registered, or if changing the default encoding)
 * "Permissive" flag for URI "/modalities/{...}/store" to ignore C-Store transfer errors
 * "Asynchronous" flag for URIs "/modalities/{...}/store" and "/peers/{...}/store"
   to avoid waiting for the completion of image transfers
-* Possibility to DELETE "dicom-as-json" attachments to reconstruct them
+* Possibility to DELETE "dicom-as-json" attachments to reconstruct the JSON summaries
 
 Plugins
 -------
--- a/OrthancServer/FromDcmtkBridge.cpp	Mon Nov 07 15:13:16 2016 +0100
+++ b/OrthancServer/FromDcmtkBridge.cpp	Mon Nov 07 17:44:10 2016 +0100
@@ -423,7 +423,7 @@
         if (maxStringLength != 0 &&
             utf8.size() > maxStringLength)
         {
-          return new DicomValue;  // Create a NULL value
+          return new DicomValue;  // Too long, create a NULL value
         }
         else
         {
@@ -432,6 +432,47 @@
       }
     }
 
+
+    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.
+      DictionaryLocker locker;
+      
+      const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), 
+                                                    element.getTag().getPrivateCreator());
+      if (entry != NULL && 
+          entry->getVR().isaString())
+      {
+        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 (data == NULL)
+          {
+            return new DicomValue("", false);   // Empty string
+          }
+          else if (maxStringLength != 0 &&
+                   element.getLength() > maxStringLength)
+          {
+            return new DicomValue;  // Too long, create a NULL value
+          }
+          else
+          {
+            std::string s(reinterpret_cast<const char*>(data), element.getLength());
+            return new DicomValue(s, false);
+          }
+        }
+      }
+    }
+
+
     try
     {
       // http://support.dcmtk.org/docs/dcvr_8h-source.html
@@ -758,7 +799,8 @@
 
     if (element.isLeaf())
     {
-      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, maxStringLength, encoding));
+      // The "0" below lets "LeafValueToJson()" take care of "TooLong" values
+      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, 0, encoding));
       LeafValueToJson(target, *v, format, flags, maxStringLength);
     }
     else
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Nov 07 15:13:16 2016 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Nov 07 17:44:10 2016 +0100
@@ -1354,6 +1354,32 @@
   }
 
 
+  static void InvalidateTags(RestApiPostCall& call)
+  {
+    ServerIndex& index = OrthancRestApi::GetIndex(call);
+    
+    // Loop over the instances, grouping them by parent studies so as
+    // to avoid large memory consumption
+    std::list<std::string> studies;
+    index.GetAllUuids(studies, ResourceType_Study);
+
+    for (std::list<std::string>::const_iterator 
+           study = studies.begin(); study != studies.end(); ++study)
+    {
+      std::list<std::string> instances;
+      index.GetChildInstances(instances, *study);
+
+      for (std::list<std::string>::const_iterator 
+             instance = instances.begin(); instance != instances.end(); ++instance)
+      {
+        index.DeleteAttachment(*instance, FileContentType_DicomAsJson);
+      }
+    }
+
+    call.GetOutput().AnswerBuffer("", "text/plain");
+  }
+
+
   void OrthancRestApi::RegisterResources()
   {
     Register("/instances", ListResources<ResourceType_Instance>);
@@ -1428,6 +1454,7 @@
     Register("/{resourceType}/{id}/attachments/{name}/uncompress", ChangeAttachmentCompression<CompressionType_None>);
     Register("/{resourceType}/{id}/attachments/{name}/verify-md5", VerifyAttachment);
 
+    Register("/tools/invalidate-tags", InvalidateTags);
     Register("/tools/lookup", Lookup);
     Register("/tools/find", Find);
 
--- a/OrthancServer/ServerToolbox.cpp	Mon Nov 07 15:13:16 2016 +0100
+++ b/OrthancServer/ServerToolbox.cpp	Mon Nov 07 17:44:10 2016 +0100
@@ -334,6 +334,13 @@
     {
       // WARNING: The database should be locked with a transaction!
 
+      // TODO: This function might consume much memory if level ==
+      // ResourceType_Instance. To improve this, first download the
+      // list of studies, then remove the instances for each single
+      // study (check out OrthancRestApi::InvalidateTags for an
+      // example). Take this improvement into consideration for the
+      // next upgrade of the database schema.
+
       const char* plural = NULL;
 
       switch (level)
--- a/Resources/Configuration.json	Mon Nov 07 15:13:16 2016 +0100
+++ b/Resources/Configuration.json	Mon Nov 07 17:44:10 2016 +0100
@@ -326,6 +326,7 @@
   // possibly the Private Creator (for private tags).
   "Dictionary" : {
     // "0014,1020" : [ "DA", "ValidationExpiryDate", 1, 1 ]
-    // "00e1,10c2" : [ "SH", "PET-CT Multi Modality Name", 1, 1, "ELSCINT1" ]
+    // "00e1,10c2" : [ "UI", "PET-CT Multi Modality Name", 1, 1, "ELSCINT1" ],
+    // "7053,1003" : [ "ST", "Original Image Filename", 1, 1, "Philips PET Private Group" ]
   }
 }