changeset 285:4031f73fe0e4

access to the raw dicom tags
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 12 Dec 2012 17:53:14 +0100
parents 06aa7b7b6723
children 727a6d766dde
files Core/Cache/MemoryCache.cpp Core/Cache/MemoryCache.h Core/HttpServer/HttpOutput.h Core/RestApi/RestApi.h Core/RestApi/RestApiOutput.h NEWS OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/OrthancRestApi.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h UnitTests/MemoryCache.cpp
diffstat 12 files changed, 300 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- a/Core/Cache/MemoryCache.cpp	Wed Dec 12 15:40:18 2012 +0100
+++ b/Core/Cache/MemoryCache.cpp	Wed Dec 12 17:53:14 2012 +0100
@@ -82,9 +82,8 @@
     }
   }
 
-  MemoryCache::Accessor* MemoryCache::Access(const std::string& id)
+  IDynamicObject& MemoryCache::Access(const std::string& id)
   {
-    Page& element = Load(id);
-    return new Accessor(element);
+    return *Load(id).content_;
   }
 }
--- a/Core/Cache/MemoryCache.h	Wed Dec 12 15:40:18 2012 +0100
+++ b/Core/Cache/MemoryCache.h	Wed Dec 12 17:53:14 2012 +0100
@@ -57,40 +57,11 @@
     Page& Load(const std::string& id);
 
   public:
-    class Accessor
-    {
-      friend class MemoryCache;
-
-    private:
-      Page& element_;
-
-      Accessor(Page& element) : 
-        element_(element)
-      {
-      }
-
-    public:
-      const std::string GetId() const
-      {
-        return element_.id_;
-      }
-
-      IDynamicObject& GetContent()
-      {
-        return *element_.content_;
-      }
-
-      const IDynamicObject& GetContent() const
-      {
-        return *element_.content_;
-      }
-    };
-
     MemoryCache(ICachePageProvider& provider,
                 size_t cacheSize);
 
     ~MemoryCache();
 
-    Accessor* Access(const std::string& id);
+    IDynamicObject& Access(const std::string& id);
   };
 }
--- a/Core/HttpServer/HttpOutput.h	Wed Dec 12 15:40:18 2012 +0100
+++ b/Core/HttpServer/HttpOutput.h	Wed Dec 12 17:53:14 2012 +0100
@@ -43,11 +43,6 @@
   private:
     void SendHeaderInternal(Orthanc_HttpStatus status);
 
-    void SendOkHeader(const char* contentType,
-                      bool hasContentLength,
-                      uint64_t contentLength,
-                      const char* contentFilename);
-
   public:
     virtual ~HttpOutput()
     {
@@ -55,6 +50,11 @@
 
     virtual void Send(const void* buffer, size_t length) = 0;
 
+    void SendOkHeader(const char* contentType,
+                      bool hasContentLength,
+                      uint64_t contentLength,
+                      const char* contentFilename);
+
     void SendCustomOkHeader(const std::string& customHeader);
 
     void SendString(const std::string& s);
--- a/Core/RestApi/RestApi.h	Wed Dec 12 15:40:18 2012 +0100
+++ b/Core/RestApi/RestApi.h	Wed Dec 12 17:53:14 2012 +0100
@@ -70,6 +70,11 @@
       {
         return *fullUri_;
       }
+    
+      const UriComponents& GetTrailingUri() const
+      {
+        return *trailing_;
+      }
 
       std::string GetUriComponent(const std::string& name,
                                   const std::string& defaultValue) const
--- a/Core/RestApi/RestApiOutput.h	Wed Dec 12 15:40:18 2012 +0100
+++ b/Core/RestApi/RestApiOutput.h	Wed Dec 12 17:53:14 2012 +0100
@@ -52,6 +52,16 @@
 
     ~RestApiOutput();
 
+    HttpOutput& GetLowLevelOutput()
+    {
+      return output_;
+    }
+
+    void MarkLowLevelOutputDone()
+    {
+      alreadySent_ = true;
+    }
+
     void AnswerFile(HttpFileSender& sender);
 
     void AnswerJson(const Json::Value& value);
--- a/NEWS	Wed Dec 12 15:40:18 2012 +0100
+++ b/NEWS	Wed Dec 12 17:53:14 2012 +0100
@@ -3,6 +3,7 @@
 
 * Recycling of disk space
 * Protection of patients against recycling (also in Orthanc Explorer)
+* Raw access to the value of the DICOM tags in the REST API
 
 
 Version 0.3.1 (2012/12/05)
--- a/OrthancServer/FromDcmtkBridge.cpp	Wed Dec 12 15:40:18 2012 +0100
+++ b/OrthancServer/FromDcmtkBridge.cpp	Wed Dec 12 17:53:14 2012 +0100
@@ -63,6 +63,193 @@
 
 namespace Orthanc
 {
+  ParsedDicomFile::ParsedDicomFile(const std::string& content)
+  {
+    DcmInputBufferStream is;
+    if (content.size() > 0)
+    {
+      is.setBuffer(&content[0], content.size());
+    }
+    is.setEos();
+
+    file_.reset(new DcmFileFormat);
+    if (!file_->read(is).good())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  static void SendPathValueForDictionary(RestApiOutput& output,
+                                       DcmItem& dicom)
+  {
+    Json::Value v = Json::arrayValue;
+
+    for (unsigned long i = 0; i < dicom.card(); i++)
+    {
+      DcmElement* element = dicom.getElement(i);
+      if (element)
+      {
+        char buf[16];
+        sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag());
+        v.append(buf);
+      }
+    }
+
+    output.AnswerJson(v);
+  }
+
+  static inline uint16_t GetCharValue(char c)
+  {
+    if (c >= '0' && c <= '9')
+      return c - '0';
+    else if (c >= 'a' && c <= 'f')
+      return c - 'a' + 10;
+    else if (c >= 'A' && c <= 'F')
+      return c - 'A' + 10;
+    else
+      return 0;
+  }
+
+  static inline uint16_t GetTagValue(const char* c)
+  {
+    return ((GetCharValue(c[0]) << 12) + 
+            (GetCharValue(c[1]) << 8) + 
+            (GetCharValue(c[2]) << 4) + 
+            GetCharValue(c[3]));
+  }
+
+  static bool ParseTagAndGroup(DcmTagKey& key,
+                               const std::string& tag)
+  {
+    if (tag.size() != 9 ||
+        !isxdigit(tag[0]) ||
+        !isxdigit(tag[1]) ||
+        !isxdigit(tag[2]) ||
+        !isxdigit(tag[3]) ||
+        tag[4] != '-' ||
+        !isxdigit(tag[5]) ||
+        !isxdigit(tag[6]) ||
+        !isxdigit(tag[7]) ||
+        !isxdigit(tag[8]))        
+    {
+      return false;
+    }
+
+    uint16_t group = GetTagValue(tag.c_str());
+    uint16_t element = GetTagValue(tag.c_str() + 5);
+
+    key = DcmTagKey(group, element);
+
+    return true;
+  }
+
+  static void SendPathValueForLeaf(RestApiOutput& output,
+                                   const std::string& tag,
+                                   DcmItem& dicom)
+  {
+    DcmTagKey k;
+    if (!ParseTagAndGroup(k, tag))
+    {
+      return;
+    }
+
+    DcmElement* element = NULL;
+    if (dicom.findAndGetElement(k, element).good() && element != NULL)
+    {
+      if (element->getVR() == EVR_SQ)
+      {
+        // This element is a sequence
+        Json::Value v = Json::arrayValue;
+        DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
+
+        for (unsigned long i = 0; i < sequence.card(); i++)
+        {
+          v.append(boost::lexical_cast<std::string>(i));
+        }
+
+        output.AnswerJson(v);
+      }
+      else
+      {
+        // This element is not a sequence
+        std::string buffer;
+        buffer.resize(65536);
+        Uint32 length = element->getLength();
+        Uint32 offset = 0;
+
+        output.GetLowLevelOutput().SendOkHeader("application/octet-stream", true, length, NULL);
+
+        while (offset < length)
+        {
+          Uint32 nbytes;
+          if (length - offset < buffer.size())
+          {
+            nbytes = length - offset;
+          }
+          else
+          {
+            nbytes = buffer.size();
+          }
+
+          if (element->getPartialValue(&buffer[0], offset, nbytes).good())
+          {
+            output.GetLowLevelOutput().Send(&buffer[0], nbytes);
+            offset += nbytes;
+          }
+          else
+          {
+            return;
+          }
+        }
+
+        output.MarkLowLevelOutputDone();
+      }
+    }
+  }
+
+  void ParsedDicomFile::SendPathValue(RestApiOutput& output,
+                                      const UriComponents& uri)
+  {
+    DcmItem* dicom = file_->getDataset();
+
+    // Go down in the tag hierarchy according to the URI
+    for (size_t pos = 0; pos < uri.size() / 2; pos++)
+    {
+      size_t index;
+      try
+      {
+        index = boost::lexical_cast<size_t>(uri[2 * pos + 1]);
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return;
+      }
+
+      DcmTagKey k;
+      DcmItem *child = NULL;
+      if (!ParseTagAndGroup(k, uri[2 * pos]) ||
+          !dicom->findAndGetSequenceItem(k, child, index).good() ||
+          child == NULL)
+      {
+        return;
+      }
+
+      dicom = child;
+    }
+
+    // We have reached the end of the URI
+    if (uri.size() % 2 == 0)
+    {
+      SendPathValueForDictionary(output, *dicom);
+    }
+    else
+    {
+      SendPathValueForLeaf(output, uri.back(), *dicom);
+    }
+  }
+
+
   void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset)
   {
     target.Clear();
--- a/OrthancServer/FromDcmtkBridge.h	Wed Dec 12 15:40:18 2012 +0100
+++ b/OrthancServer/FromDcmtkBridge.h	Wed Dec 12 17:53:14 2012 +0100
@@ -33,8 +33,13 @@
 #pragma once
 
 #include "../Core/DicomFormat/DicomMap.h"
+#include "../Core/RestApi/RestApiOutput.h"
+#include "../Core/Toolbox.h"
+
 #include <dcmtk/dcmdata/dcdatset.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
 #include <json/json.h>
+#include <memory>
 
 namespace Orthanc
 {
@@ -52,6 +57,23 @@
     DicomRootLevel_Instance
   };
 
+  class ParsedDicomFile : public IDynamicObject
+  {
+  private:
+    std::auto_ptr<DcmFileFormat> file_;
+
+  public:
+    ParsedDicomFile(const std::string& content);
+
+    DcmFileFormat& GetDicom()
+    {
+      return *file_;
+    }
+
+    void SendPathValue(RestApiOutput& output,
+                       const UriComponents& uri);
+  };
+
   class FromDcmtkBridge
   {
   public:
--- a/OrthancServer/OrthancRestApi.cpp	Wed Dec 12 15:40:18 2012 +0100
+++ b/OrthancServer/OrthancRestApi.cpp	Wed Dec 12 17:53:14 2012 +0100
@@ -847,6 +847,23 @@
 
 
 
+  // Raw access to the DICOM tags of an instance ------------------------------
+
+  static void GetRawContent(RestApi::GetCall& call)
+  {
+    // TODO IMPROVE MULTITHREADING
+    static boost::mutex mutex_;
+    boost::mutex::scoped_lock lock(mutex_);
+
+    RETRIEVE_CONTEXT(call);
+    std::string id = call.GetUriComponent("id", "");
+    ParsedDicomFile& dicom = context.GetDicomFile(id);
+    dicom.SendPathValue(call.GetOutput(), call.GetTrailingUri());
+  }
+
+
+
+
   // Registration of the various REST handlers --------------------------------
 
   OrthancRestApi::OrthancRestApi(ServerContext& context) : 
@@ -885,6 +902,7 @@
     Register("/instances/{id}/tags", GetInstanceTags<false>);
     Register("/instances/{id}/simplified-tags", GetInstanceTags<true>);
     Register("/instances/{id}/frames", ListFrames);
+    Register("/instances/{id}/content/*", GetRawContent);
 
     Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>);
     Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
--- a/OrthancServer/ServerContext.cpp	Wed Dec 12 15:40:18 2012 +0100
+++ b/OrthancServer/ServerContext.cpp	Wed Dec 12 17:53:14 2012 +0100
@@ -37,6 +37,8 @@
 #include <glog/logging.h>
 
 
+static const size_t DICOM_CACHE_SIZE = 2;
+
 /**
  * IMPORTANT: We make the assumption that the same instance of
  * FileStorage can be accessed from multiple threads. This seems OK
@@ -51,7 +53,9 @@
   ServerContext::ServerContext(const boost::filesystem::path& path) :
     storage_(path.string()),
     index_(*this, path.string()),
-    accessor_(storage_)
+    accessor_(storage_),
+    provider_(*this),
+    dicomCache_(provider_, DICOM_CACHE_SIZE)
   {
     // TODO RECYCLING SETUP HERE
     //index_.SetMaximumPatientCount(4);
@@ -165,4 +169,18 @@
     accessor_.SetCompressionForNextOperations(attachment.GetCompressionType());
     accessor_.Read(result, attachment.GetUuid());
   }
+
+
+  IDynamicObject* ServerContext::DicomCacheProvider::Provide(const std::string& instancePublicId)
+  {
+    std::string content;
+    context_.ReadFile(content, instancePublicId, FileContentType_Dicom);
+    return new ParsedDicomFile(content);
+  }
+
+
+  ParsedDicomFile& ServerContext::GetDicomFile(const std::string& instancePublicId)
+  {
+    return dynamic_cast<ParsedDicomFile&>(dicomCache_.Access(instancePublicId));
+  }
 }
--- a/OrthancServer/ServerContext.h	Wed Dec 12 15:40:18 2012 +0100
+++ b/OrthancServer/ServerContext.h	Wed Dec 12 17:53:14 2012 +0100
@@ -32,10 +32,12 @@
 
 #pragma once
 
-#include "ServerIndex.h"
+#include "../Core/Cache/MemoryCache.h"
 #include "../Core/FileStorage/CompressedFileStorageAccessor.h"
 #include "../Core/FileStorage/FileStorage.h"
 #include "../Core/RestApi/RestApiOutput.h"
+#include "ServerIndex.h"
+#include "FromDcmtkBridge.h"
 
 namespace Orthanc
 {
@@ -47,10 +49,26 @@
   class ServerContext
   {
   private:
+    class DicomCacheProvider : public ICachePageProvider
+    {
+    private:
+      ServerContext& context_;
+
+    public:
+      DicomCacheProvider(ServerContext& context) : context_(context)
+      {
+      }
+      
+      virtual IDynamicObject* Provide(const std::string& id);
+    };
+
     FileStorage storage_;
     ServerIndex index_;
     CompressedFileStorageAccessor accessor_;
     bool compressionEnabled_;
+    
+    DicomCacheProvider provider_;
+    MemoryCache dicomCache_;
 
   public:
     ServerContext(const boost::filesystem::path& path);
@@ -86,5 +104,8 @@
     void ReadFile(std::string& result,
                   const std::string& instancePublicId,
                   FileContentType content);
+
+    // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD
+    ParsedDicomFile& GetDicomFile(const std::string& instancePublicId);
   };
 }
--- a/UnitTests/MemoryCache.cpp	Wed Dec 12 15:40:18 2012 +0100
+++ b/UnitTests/MemoryCache.cpp	Wed Dec 12 17:53:14 2012 +0100
@@ -113,15 +113,14 @@
 
   {
     Orthanc::MemoryCache cache(provider, 3);
-    std::auto_ptr<Orthanc::MemoryCache::Accessor> a;
-    a.reset(cache.Access("42"));  // 42 -> exit
-    a.reset(cache.Access("43"));  // 43, 42 -> exit
-    a.reset(cache.Access("45"));  // 45, 43, 42 -> exit
-    a.reset(cache.Access("42"));  // 42, 45, 43 -> exit
-    a.reset(cache.Access("43"));  // 43, 42, 45 -> exit
-    a.reset(cache.Access("47"));  // 45 is removed; 47, 43, 42 -> exit 
-    a.reset(cache.Access("44"));  // 42 is removed; 44, 47, 43 -> exit
-    a.reset(cache.Access("42"));  // 43 is removed; 42, 44, 47 -> exit
+    cache.Access("42");  // 42 -> exit
+    cache.Access("43");  // 43, 42 -> exit
+    cache.Access("45");  // 45, 43, 42 -> exit
+    cache.Access("42");  // 42, 45, 43 -> exit
+    cache.Access("43");  // 43, 42, 45 -> exit
+    cache.Access("47");  // 45 is removed; 47, 43, 42 -> exit 
+    cache.Access("44");  // 42 is removed; 44, 47, 43 -> exit
+    cache.Access("42");  // 43 is removed; 42, 44, 47 -> exit
     // Closing the cache: 47, 44, 42 are successively removed
   }