changeset 1004:a226e0959d8b lua-scripting

DicomInstanceToStore
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 08 Jul 2014 14:06:05 +0200
parents 1d35281d967c
children 84b6d7bca6db
files OrthancServer/DicomInstanceToStore.cpp OrthancServer/DicomInstanceToStore.h OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/Internals/StoreScp.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h
diffstat 10 files changed, 403 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/DicomInstanceToStore.cpp	Fri Jul 04 16:51:33 2014 +0200
+++ b/OrthancServer/DicomInstanceToStore.cpp	Tue Jul 08 14:06:05 2014 +0200
@@ -32,42 +32,21 @@
 
 #include "DicomInstanceToStore.h"
 
+#include "FromDcmtkBridge.h"
+
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <glog/logging.h>
+
+
 namespace Orthanc
 {
-  DicomInstanceToStore::DicomInstanceToStore() :
-    hasBuffer_(false),
-    parsed_(NULL),
-    summary_(NULL),
-    json_(NULL)
-  {
-  }
-
-  void DicomInstanceToStore::SetBuffer(const std::string& dicom)
+  static DcmDataset& GetDataset(ParsedDicomFile& file)
   {
-    hasBuffer_ = true;
-    bufferSize_ = dicom.size();
-
-    if (dicom.size() == 0)
-    {
-      buffer_ = NULL;
-    }
-    else
-    {
-      buffer_ = &dicom[0];
-    }
+    return *reinterpret_cast<DcmFileFormat*>(file.GetDcmtkObject())->getDataset();
   }
 
 
-  void DicomInstanceToStore::SetBuffer(const char* buffer, 
-                                       size_t size)
-  {
-    hasBuffer_ = true;
-    buffer_ = buffer;
-    bufferSize_ = size;
-  }
-
-
-  void DicomInstanceToStore::SetMetadata(ResourceType level,
+  void DicomInstanceToStore::AddMetadata(ResourceType level,
                                          MetadataType metadata,
                                          const std::string& value)
   {
@@ -77,23 +56,119 @@
 
   void DicomInstanceToStore::ComputeMissingInformation()
   {
-    // TODO
+    if (buffer_.HasContent() &&
+        summary_.HasContent() &&
+        json_.HasContent())
+    {
+      // Fine, everything is available
+      return; 
+    }
+    
+    if (!buffer_.HasContent())
+    {
+      if (!parsed_.HasContent())
+      {
+        throw OrthancException(ErrorCode_NotImplemented);
+      }
+      else
+      {
+        // Serialize the parsed DICOM file
+        buffer_.Allocate();
+        if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer_.GetContent(), GetDataset(parsed_.GetContent())))
+        {
+          LOG(ERROR) << "Unable to serialize a DICOM file to a memory buffer";
+          throw OrthancException(ErrorCode_InternalError);
+        }
+      }
+    }
 
-    assert(hasBuffer_ && (buffer_ != NULL || bufferSize_ == 0));
+    if (summary_.HasContent() &&
+        json_.HasContent())
+    {
+      return;
+    }
+
+    // At this point, we know that the DICOM file is available as a
+    // memory buffer, but that its summary or its JSON version is
+    // missing
+
+    if (!parsed_.HasContent())
+    {
+      parsed_.TakeOwnership(new ParsedDicomFile(buffer_.GetContent()));
+    }
+
+    // At this point, we have parsed the DICOM file
+    
+    if (!summary_.HasContent())
+    {
+      summary_.Allocate();
+      FromDcmtkBridge::Convert(summary_.GetContent(), GetDataset(parsed_.GetContent()));
+    }
+    
+    if (!json_.HasContent())
+    {
+      json_.Allocate();
+      FromDcmtkBridge::ToJson(json_.GetContent(), GetDataset(parsed_.GetContent()));
+    }
   }
 
 
 
-  const char* DicomInstanceToStore::GetBuffer()
+  const char* DicomInstanceToStore::GetBufferData()
   {
     ComputeMissingInformation();
-    return buffer_;
+    
+    if (!buffer_.HasContent())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (buffer_.GetConstContent().size() == 0)
+    {
+      return NULL;
+    }
+    else
+    {
+      return buffer_.GetConstContent().c_str();
+    }
   }
 
 
   size_t DicomInstanceToStore::GetBufferSize()
   {
     ComputeMissingInformation();
-    return bufferSize_;
+    
+    if (!buffer_.HasContent())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    return buffer_.GetConstContent().size();
+  }
+
+
+  const DicomMap& DicomInstanceToStore::GetSummary()
+  {
+    ComputeMissingInformation();
+    
+    if (!summary_.HasContent())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    return summary_.GetConstContent();
+  }
+
+    
+  const Json::Value& DicomInstanceToStore::GetJson()
+  {
+    ComputeMissingInformation();
+    
+    if (!json_.HasContent())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    return json_.GetConstContent();
   }
 }
--- a/OrthancServer/DicomInstanceToStore.h	Fri Jul 04 16:51:33 2014 +0200
+++ b/OrthancServer/DicomInstanceToStore.h	Tue Jul 08 14:06:05 2014 +0200
@@ -34,19 +34,149 @@
 
 #include "ParsedDicomFile.h"
 #include "ServerIndex.h"
+#include "../Core/OrthancException.h"
 
 namespace Orthanc
 {
   class DicomInstanceToStore
   {
   private:
-    bool hasBuffer_;
-    const char* buffer_;
-    size_t bufferSize_;
+    template <typename T>
+    class SmartContainer
+    {
+    private:
+      T* content_;
+      bool toDelete_;
+      bool isReadOnly_;
+
+      void Deallocate()
+      {
+        if (content_ && toDelete_)
+        {
+          delete content_;
+          toDelete_ = false;
+          content_ = NULL;
+        }
+      }
+
+    public:
+      SmartContainer() : content_(NULL), toDelete_(false)
+      {
+      }
+
+      ~SmartContainer()
+      {
+        Deallocate();
+      }
+
+      void Allocate()
+      {
+        Deallocate();
+        content_ = new T;
+        toDelete_ = true;
+        isReadOnly_ = false;
+      }
+
+      void TakeOwnership(T* content)
+      {
+        if (content == NULL)
+        {
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+
+        Deallocate();
+        content_ = content;
+        toDelete_ = true;
+        isReadOnly_ = true;
+      }
+
+      void SetReference(const T& content)   // Read-only assign, without transfering ownership
+      {
+        Deallocate();
+        content_ = &const_cast<T&>(content);
+        toDelete_ = false;
+        isReadOnly_ = true;
+      }
+
+      bool HasContent() const
+      {
+        return content_ != NULL;
+      }
 
-    ParsedDicomFile* parsed_;
-    const DicomMap* summary_;
-    const Json::Value*  json_;
+      T& GetContent()
+      {
+        if (content_ == NULL)
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+
+        if (isReadOnly_)
+        {
+          throw OrthancException(ErrorCode_ReadOnly);
+        }
+
+        return *content_;
+      }
+
+      const T& GetConstContent() const
+      {
+        if (content_ == NULL)
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+
+        return *content_;
+      }
+    };
+
+
+    /*class MemoryBuffer
+    {
+    private:
+      const char* buffer_;
+      size_t size_;
+
+    public:
+      MemoryBuffer() : buffer_(NULL), size_(0)
+      {
+      }
+
+      const char* GetBuffer() const
+      {
+        return buffer_;
+      }
+
+      size_t GetSize() const
+      {
+        return size_;
+      }
+
+      void Assign(const char* buffer, size_t size)
+      {
+        buffer_ = buffer;
+        size_ = size;
+      }
+
+      void Assign(const std::string& buffer)
+      {
+        size_ = buffer.size();
+
+        if (size_ == 0)
+        {
+          buffer_ = NULL;
+        }
+        else
+        {
+          buffer_ = &buffer[0];
+        }
+      }
+    };*/
+
+
+    SmartContainer<std::string>  buffer_;
+    SmartContainer<ParsedDicomFile>  parsed_;
+    SmartContainer<DicomMap>  summary_;
+    SmartContainer<Json::Value>  json_;
 
     std::string remoteAet_;
     ServerIndex::MetadataMap metadata_;
@@ -54,26 +184,24 @@
     void ComputeMissingInformation();
 
   public:
-    DicomInstanceToStore();
-
-    void SetBuffer(const std::string& dicom);
-
-    void SetBuffer(const char* buffer, 
-                   size_t size);
+    void SetBuffer(const std::string& dicom)
+    {
+      buffer_.SetReference(dicom);
+    }
 
     void SetParsedDicomFile(ParsedDicomFile& parsed)
     {
-      parsed_ = &parsed;
+      parsed_.SetReference(parsed);
     }
 
     void SetSummary(const DicomMap& summary)
     {
-      summary_ = &summary;
+      summary_.SetReference(summary);
     }
 
     void SetJson(const Json::Value& json)
     {
-      json_ = &json;
+      json_.SetReference(json);
     }
 
     const std::string GetRemoteAet() const
@@ -86,7 +214,7 @@
       remoteAet_ = aet;
     }
 
-    void SetMetadata(ResourceType level,
+    void AddMetadata(ResourceType level,
                      MetadataType metadata,
                      const std::string& value);
 
@@ -95,8 +223,17 @@
       return metadata_;
     }
 
-    const char* GetBuffer();
+    ServerIndex::MetadataMap& GetMetadata()
+    {
+      return metadata_;
+    }
+
+    const char* GetBufferData();
 
     size_t GetBufferSize();
+
+    const DicomMap& GetSummary();
+    
+    const Json::Value& GetJson();
   };
 }
--- a/OrthancServer/FromDcmtkBridge.cpp	Fri Jul 04 16:51:33 2014 +0200
+++ b/OrthancServer/FromDcmtkBridge.cpp	Tue Jul 08 14:06:05 2014 +0200
@@ -634,7 +634,7 @@
   }
 
   bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
-                                           DcmDataset* dataSet)
+                                           DcmDataset& dataSet)
   {
     // Determine the transfer syntax which shall be used to write the
     // information to the file. We always switch to the Little Endian
@@ -649,7 +649,7 @@
      * dataset into memory. We now keep the original transfer syntax
      * (if available).
      **/
-    E_TransferSyntax xfer = dataSet->getOriginalXfer();
+    E_TransferSyntax xfer = dataSet.getOriginalXfer();
     if (xfer == EXS_Unknown)
     {
       // No information about the original transfer syntax: This is
@@ -660,7 +660,7 @@
     E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;
 
     // Create the meta-header information
-    DcmFileFormat ff(dataSet);
+    DcmFileFormat ff(&dataSet);
     ff.validateMetaInfo(xfer);
 
     // Create a memory buffer with the proper size
--- a/OrthancServer/FromDcmtkBridge.h	Fri Jul 04 16:51:33 2014 +0200
+++ b/OrthancServer/FromDcmtkBridge.h	Tue Jul 08 14:06:05 2014 +0200
@@ -104,6 +104,6 @@
     static std::string GenerateUniqueIdentifier(ResourceType level);
 
     static bool SaveToMemoryBuffer(std::string& buffer,
-                                   DcmDataset* dataSet);
+                                   DcmDataset& dataSet);
   };
 }
--- a/OrthancServer/Internals/StoreScp.cpp	Fri Jul 04 16:51:33 2014 +0200
+++ b/OrthancServer/Internals/StoreScp.cpp	Tue Jul 08 14:06:05 2014 +0200
@@ -168,7 +168,7 @@
             FromDcmtkBridge::Convert(summary, **imageDataSet);
             FromDcmtkBridge::ToJson(dicomJson, **imageDataSet);       
 
-            if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, *imageDataSet))
+            if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet))
             {
               LOG(ERROR) << "cannot write DICOM file to memory";
               rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
--- a/OrthancServer/ParsedDicomFile.cpp	Fri Jul 04 16:51:33 2014 +0200
+++ b/OrthancServer/ParsedDicomFile.cpp	Tue Jul 08 14:06:05 2014 +0200
@@ -873,7 +873,7 @@
   void ParsedDicomFile::Answer(RestApiOutput& output)
   {
     std::string serialized;
-    if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, pimpl_->file_->getDataset()))
+    if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *pimpl_->file_->getDataset()))
     {
       output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM);
     }
@@ -956,7 +956,7 @@
 
   void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer)
   {
-    FromDcmtkBridge::SaveToMemoryBuffer(buffer, pimpl_->file_->getDataset());
+    FromDcmtkBridge::SaveToMemoryBuffer(buffer, *pimpl_->file_->getDataset());
   }
 
 
--- a/OrthancServer/ServerContext.cpp	Fri Jul 04 16:51:33 2014 +0200
+++ b/OrthancServer/ServerContext.cpp	Tue Jul 08 14:06:05 2014 +0200
@@ -159,6 +159,106 @@
   }
 
 
+  StoreStatus ServerContext::Store(std::string& resultPublicId,
+                                   DicomInstanceToStore& dicom)
+  {
+    try
+    {
+      DicomInstanceHasher hasher(dicom.GetSummary());
+      resultPublicId = hasher.HashInstance();
+
+      Json::Value simplified;
+      SimplifyTags(simplified, dicom.GetJson());
+
+      // Test if the instance must be filtered out
+      if (!ApplyReceivedInstanceFilter(simplified, dicom.GetRemoteAet()))
+      {
+        LOG(INFO) << "An incoming instance has been discarded by the filter";
+        return StoreStatus_FilteredOut;
+      }
+
+      if (compressionEnabled_)
+      {
+        accessor_.SetCompressionForNextOperations(CompressionType_Zlib);
+      }
+      else
+      {
+        accessor_.SetCompressionForNextOperations(CompressionType_None);
+      }      
+
+      FileInfo dicomInfo = accessor_.Write(dicom.GetBufferData(), dicom.GetBufferSize(), FileContentType_Dicom);
+      FileInfo jsonInfo = accessor_.Write(dicom.GetJson().toStyledString(), FileContentType_DicomAsJson);
+
+      ServerIndex::Attachments attachments;
+      attachments.push_back(dicomInfo);
+      attachments.push_back(jsonInfo);
+
+      StoreStatus status = index_.Store(dicom.GetSummary(), attachments, dicom.GetRemoteAet(), dicom.GetMetadata());
+
+      if (status != StoreStatus_Success)
+      {
+        storage_.Remove(dicomInfo.GetUuid());
+        storage_.Remove(jsonInfo.GetUuid());
+      }
+
+      switch (status)
+      {
+        case StoreStatus_Success:
+          LOG(INFO) << "New instance stored";
+          break;
+
+        case StoreStatus_AlreadyStored:
+          LOG(INFO) << "Already stored";
+          break;
+
+        case StoreStatus_Failure:
+          LOG(ERROR) << "Store failure";
+          break;
+
+        default:
+          // This should never happen
+          break;
+      }
+
+      if (status == StoreStatus_Success ||
+          status == StoreStatus_AlreadyStored)
+      {
+        try
+        {
+          Json::Value metadata = Json::objectValue;
+          for (ServerIndex::MetadataMap::const_iterator 
+                 it = dicom.GetMetadata().begin(); 
+               it != dicom.GetMetadata().end(); ++it)
+          {
+            if (it->first.first == ResourceType_Instance)
+            {
+              metadata[EnumerationToString(it->first.second)] = it->second;
+            }
+          }
+
+          ApplyOnStoredInstance(resultPublicId, simplified, metadata);
+        }
+        catch (OrthancException&)
+        {
+          LOG(ERROR) << "Error when dealing with OnStoredInstance";
+        }
+      }
+
+      return status;
+    }
+    catch (OrthancException& e)
+    {
+      if (e.GetErrorCode() == ErrorCode_InexistentTag)
+      {
+        LogMissingRequiredTag(dicom.GetSummary());
+      }
+
+      throw;
+    }
+  }
+
+
+
   StoreStatus ServerContext::Store(const char* dicomInstance,
                                    size_t dicomSize,
                                    const DicomMap& dicomSummary,
@@ -192,7 +292,8 @@
     attachments.push_back(dicomInfo);
     attachments.push_back(jsonInfo);
 
-    StoreStatus status = index_.Store(dicomSummary, attachments, remoteAet, metadata);
+    // TODO REMOVE CONST_CAST !!!!
+    StoreStatus status = index_.Store(dicomSummary, attachments, remoteAet, const_cast<ServerIndex::MetadataMap&>(metadata));
 
     if (status != StoreStatus_Success)
     {
@@ -379,7 +480,7 @@
                                    const ServerIndex::MetadataMap& metadata)
   {
     std::string buffer;
-    if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, GetDicom(dicomInstance).getDataset()))
+    if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, *GetDicom(dicomInstance).getDataset()))
     {
       throw OrthancException(ErrorCode_InternalError);
     }
--- a/OrthancServer/ServerContext.h	Fri Jul 04 16:51:33 2014 +0200
+++ b/OrthancServer/ServerContext.h	Tue Jul 08 14:06:05 2014 +0200
@@ -41,6 +41,7 @@
 #include "ParsedDicomFile.h"
 #include "DicomProtocol/ReusableDicomUserConnection.h"
 #include "Scheduler/ServerScheduler.h"
+#include "DicomInstanceToStore.h"
 
 namespace Orthanc
 {
@@ -86,6 +87,11 @@
     boost::mutex luaMutex_;
     LuaContext lua_;
 
+    StoreStatus Store(std::string& resultPublicId,
+                      const char* dicomBuffer,
+                      size_t dicomSize,
+                      const ServerIndex::MetadataMap& metadata = ServerIndex::MetadataMap());
+
   public:
     class DicomCacheLocker : public boost::noncopyable
     {
@@ -152,6 +158,9 @@
 
 
     // TODO SIMPLIFY THESE MANY "Store" methods!
+    StoreStatus Store(std::string& resultPublicId,
+                      DicomInstanceToStore& dicom);
+
     StoreStatus Store(const char* dicomInstance,
                       size_t dicomSize,
                       const DicomMap& dicomSummary,
@@ -170,11 +179,6 @@
                       const ServerIndex::MetadataMap& metadata = ServerIndex::MetadataMap());
 
     StoreStatus Store(std::string& resultPublicId,
-                      const char* dicomBuffer,
-                      size_t dicomSize,
-                      const ServerIndex::MetadataMap& metadata = ServerIndex::MetadataMap());
-
-    StoreStatus Store(std::string& resultPublicId,
                       const std::string& dicomContent,
                       const ServerIndex::MetadataMap& metadata = ServerIndex::MetadataMap());
 
--- a/OrthancServer/ServerIndex.cpp	Fri Jul 04 16:51:33 2014 +0200
+++ b/OrthancServer/ServerIndex.cpp	Tue Jul 08 14:06:05 2014 +0200
@@ -385,7 +385,7 @@
   StoreStatus ServerIndex::Store(const DicomMap& dicomSummary,
                                  const Attachments& attachments,
                                  const std::string& remoteAet,
-                                 const MetadataMap* metadata)
+                                 MetadataMap* metadata)
   {
     boost::mutex::scoped_lock lock(mutex_);
     listener_->Reset();
@@ -550,27 +550,41 @@
         }
       }
 
-      // Attach the auto-computer metadata
+      // Attach the auto-computed metadata for the patient/study/series levels
       std::string now = Toolbox::GetNowIsoString();
-      db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, now);
       db_->SetMetadata(series, MetadataType_LastUpdate, now);
       db_->SetMetadata(study, MetadataType_LastUpdate, now);
       db_->SetMetadata(patient, MetadataType_LastUpdate, now);
+
+      // Attach the auto-computed metadata for the instance level,
+      // reflecting these additions into the input metadata map
+      db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, now);
       db_->SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet);
 
+      if (metadata)
+      {
+        (*metadata) [std::make_pair(ResourceType_Instance, MetadataType_Instance_ReceptionDate)] = now;
+        (*metadata) [std::make_pair(ResourceType_Instance, MetadataType_Instance_RemoteAet)] = remoteAet;
+      }
+
       const DicomValue* value;
       if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
           (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
       {
         db_->SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString());
+
+        if (metadata)
+        {
+          (*metadata) [std::make_pair(ResourceType_Instance, MetadataType_Instance_IndexInSeries)] = value->AsString();
+        }
       }
 
+      // Check whether the series of this new instance is now completed
       if (isNewSeries)
       {
         ComputeExpectedNumberOfInstances(*db_, series, dicomSummary);
       }
 
-      // Check whether the series of this new instance is now completed
       SeriesStatus seriesStatus = GetSeriesStatus(series);
       if (seriesStatus == SeriesStatus_Complete)
       {
--- a/OrthancServer/ServerIndex.h	Fri Jul 04 16:51:33 2014 +0200
+++ b/OrthancServer/ServerIndex.h	Tue Jul 08 14:06:05 2014 +0200
@@ -105,7 +105,7 @@
     StoreStatus Store(const DicomMap& dicomSummary,
                       const Attachments& attachments,
                       const std::string& remoteAet,
-                      const MetadataMap* metadata);
+                      MetadataMap* metadata);
 
   public:
     ServerIndex(ServerContext& context,
@@ -139,7 +139,7 @@
     StoreStatus Store(const DicomMap& dicomSummary,
                       const Attachments& attachments,
                       const std::string& remoteAet,
-                      const MetadataMap& metadata)
+                      MetadataMap& metadata)
     {
       return Store(dicomSummary, attachments, remoteAet, &metadata);
     }