changeset 4456:3e4f7b7840f0

new class: ParsedDicomCache()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 19 Jan 2021 16:11:23 +0100
parents a8f554ca5ac6
children 789676a8c96a
files OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake OrthancFramework/Sources/Cache/MemoryObjectCache.cpp OrthancFramework/Sources/Cache/MemoryObjectCache.h OrthancFramework/Sources/DicomParsing/ParsedDicomCache.cpp OrthancFramework/Sources/DicomParsing/ParsedDicomCache.h OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp
diffstat 6 files changed, 335 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Tue Jan 19 14:31:04 2021 +0100
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Tue Jan 19 16:11:23 2021 +0100
@@ -517,6 +517,7 @@
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomParsing/DicomModification.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomParsing/DicomWebJsonVisitor.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomParsing/FromDcmtkBridge.cpp
+    ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomParsing/ParsedDicomCache.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomParsing/ParsedDicomDir.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomParsing/ParsedDicomFile.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomParsing/ToDcmtkBridge.cpp
--- a/OrthancFramework/Sources/Cache/MemoryObjectCache.cpp	Tue Jan 19 14:31:04 2021 +0100
+++ b/OrthancFramework/Sources/Cache/MemoryObjectCache.cpp	Tue Jan 19 16:11:23 2021 +0100
@@ -99,6 +99,16 @@
   }
 
 
+  size_t MemoryObjectCache::GetCurrentSize()
+  {
+#if !defined(__EMSCRIPTEN__)
+    boost::mutex::scoped_lock lock(cacheMutex_);
+#endif
+
+    return currentSize_;
+  }
+
+
   size_t MemoryObjectCache::GetMaximumSize()
   {
 #if !defined(__EMSCRIPTEN__)
--- a/OrthancFramework/Sources/Cache/MemoryObjectCache.h	Tue Jan 19 14:31:04 2021 +0100
+++ b/OrthancFramework/Sources/Cache/MemoryObjectCache.h	Tue Jan 19 16:11:23 2021 +0100
@@ -64,6 +64,8 @@
 
     ~MemoryObjectCache();
 
+    size_t GetCurrentSize();  // For unit tests only
+
     size_t GetMaximumSize();
 
     void SetMaximumSize(size_t size);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomCache.cpp	Tue Jan 19 16:11:23 2021 +0100
@@ -0,0 +1,186 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "ParsedDicomCache.h"
+
+#include "../OrthancException.h"
+
+namespace Orthanc
+{
+  class ParsedDicomCache::Item : public ICacheable
+  {
+  private:
+    std::unique_ptr<ParsedDicomFile>  dicom_;
+    size_t                            fileSize_;
+
+  public:
+    Item(ParsedDicomFile* dicom,
+         size_t fileSize) :
+      dicom_(dicom),
+      fileSize_(fileSize)
+    {
+      if (dicom == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+    }
+
+    virtual size_t GetMemoryUsage() const ORTHANC_OVERRIDE
+    {
+      return fileSize_;
+    }
+
+    ParsedDicomFile& GetDicom() const
+    {
+      assert(dicom_.get() != NULL);
+      return *dicom_;
+    }
+  };
+
+
+  ParsedDicomCache::ParsedDicomCache(size_t size) :
+    cacheSize_(size),
+    largeSize_(0)
+  {
+    if (size == 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  
+  size_t ParsedDicomCache::GetCurrentSize()  // For unit tests only
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (cache_.get() == NULL)
+    {
+      return largeSize_;
+    }
+    else
+    {
+      assert(largeSize_ == 0);
+      return cache_->GetCurrentSize();
+    }
+  }
+
+  
+  void ParsedDicomCache::Invalidate(const std::string& id)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+      
+    if (cache_.get() != NULL)
+    {
+      cache_->Invalidate(id);
+    }
+
+    if (largeId_ == id)
+    {
+      largeDicom_.reset(NULL);
+      largeSize_ = 0;
+    }
+  }
+
+  
+  void ParsedDicomCache::Acquire(const std::string& id,
+                                 ParsedDicomFile* dicom,  // Takes ownership
+                                 size_t fileSize)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+      
+    if (fileSize >= cacheSize_)
+    {
+      cache_.reset(NULL);
+      largeDicom_.reset(dicom);
+      largeId_ = id;
+      largeSize_ = fileSize;
+    }
+    else
+    {
+      largeDicom_.reset(NULL);
+      largeSize_ = 0;
+
+      if (cache_.get() == NULL)
+      {
+        cache_.reset(new MemoryObjectCache);
+        cache_->SetMaximumSize(cacheSize_);
+      }
+
+      cache_->Acquire(id, new Item(dicom, fileSize));
+    }
+  }
+
+
+  ParsedDicomCache::Accessor::Accessor(ParsedDicomCache& that,
+                                       const std::string& id) :
+    lock_(that.mutex_),
+    id_(id),
+    file_(NULL),
+    fileSize_(0)
+  {
+    if (that.largeDicom_.get() != NULL &&
+        that.largeId_ == id)
+    {
+      file_ = that.largeDicom_.get();
+      fileSize_ = that.largeSize_;
+    }
+    else if (that.cache_.get() != NULL)
+    {
+      accessor_.reset(new MemoryObjectCache::Accessor(
+                        *that.cache_, id, true /* unique */));
+      if (accessor_->IsValid())
+      {            
+        const Item& item = dynamic_cast<const Item&>(accessor_->GetValue());
+        file_ = &item.GetDicom();
+        fileSize_ = item.GetMemoryUsage();
+      }
+    }
+  }
+
+
+  ParsedDicomFile& ParsedDicomCache::Accessor::GetDicom() const
+  {
+    if (IsValid())
+    {
+      assert(file_ != NULL);
+      return *file_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  size_t ParsedDicomCache::Accessor::GetFileSize() const
+  {
+    if (IsValid())
+    {
+      return fileSize_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomCache.h	Tue Jan 19 16:11:23 2021 +0100
@@ -0,0 +1,77 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Cache/MemoryObjectCache.h"
+#include "ParsedDicomFile.h"
+
+namespace Orthanc
+{
+  class ParsedDicomCache : public boost::noncopyable
+  {
+  private:
+    class Item;
+
+    boost::mutex                        mutex_;
+    size_t                              cacheSize_;
+    std::unique_ptr<MemoryObjectCache>  cache_;
+    std::unique_ptr<ParsedDicomFile>    largeDicom_;
+    std::string                         largeId_;
+    size_t                              largeSize_;
+
+  public:
+    explicit ParsedDicomCache(size_t size);
+
+    size_t GetCurrentSize();  // For unit tests only
+
+    void Invalidate(const std::string& id);
+
+    void Acquire(const std::string& id,
+                 ParsedDicomFile* dicom,  // Takes ownership
+                 size_t fileSize);
+
+    class Accessor : public boost::noncopyable
+    {
+    private:
+      boost::mutex::scoped_lock  lock_;
+      std::string                id_;
+      ParsedDicomFile*           file_;
+      size_t                     fileSize_;
+
+      std::unique_ptr<MemoryObjectCache::Accessor>  accessor_;
+      
+    public:
+      Accessor(ParsedDicomCache& that,
+               const std::string& id);
+
+      bool IsValid() const
+      {
+        return file_ != NULL;
+      }
+
+      ParsedDicomFile& GetDicom() const;
+
+      size_t GetFileSize() const;
+    };
+  };
+}
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Tue Jan 19 14:31:04 2021 +0100
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Tue Jan 19 16:11:23 2021 +0100
@@ -41,6 +41,7 @@
 #include "../Sources/DicomParsing/DicomWebJsonVisitor.h"
 #include "../Sources/DicomParsing/FromDcmtkBridge.h"
 #include "../Sources/DicomParsing/ToDcmtkBridge.h"
+#include "../Sources/DicomParsing/ParsedDicomCache.h"
 #include "../Sources/Endianness.h"
 #include "../Sources/Images/Image.h"
 #include "../Sources/Images/ImageBuffer.h"
@@ -2084,6 +2085,64 @@
 }
 
 
+TEST(ParsedDicomCache, Basic)
+{
+  ParsedDicomCache cache(10);
+  ASSERT_EQ(0, cache.GetCurrentSize());
+
+  DicomMap tags;
+  tags.SetValue(DICOM_TAG_PATIENT_ID, "patient1", false);
+  cache.Acquire("a", new ParsedDicomFile(tags, Encoding_Latin1, true), 20);
+  ASSERT_EQ(20, cache.GetCurrentSize());
+
+  {
+    ParsedDicomCache::Accessor accessor(cache, "b");
+    ASSERT_FALSE(accessor.IsValid());
+    ASSERT_THROW(accessor.GetDicom(), OrthancException);
+    ASSERT_THROW(accessor.GetFileSize(), OrthancException);
+  }
+  
+  {
+    ParsedDicomCache::Accessor accessor(cache, "a");
+    ASSERT_TRUE(accessor.IsValid());
+    std::string s;
+    ASSERT_TRUE(accessor.GetDicom().GetTagValue(s, DICOM_TAG_PATIENT_ID));
+    ASSERT_EQ("patient1", s);
+    ASSERT_EQ(20u, accessor.GetFileSize());
+  }
+  
+  tags.SetValue(DICOM_TAG_PATIENT_ID, "patient2", false);
+  cache.Acquire("b", new ParsedDicomFile(tags, Encoding_Latin1, true), 5);  
+  ASSERT_EQ(5, cache.GetCurrentSize());
+
+  cache.Acquire("c", new ParsedDicomFile(true), 5);
+  ASSERT_EQ(10, cache.GetCurrentSize());
+
+  {
+    ParsedDicomCache::Accessor accessor(cache, "b");
+    ASSERT_TRUE(accessor.IsValid());
+    std::string s;
+    ASSERT_TRUE(accessor.GetDicom().GetTagValue(s, DICOM_TAG_PATIENT_ID));
+    ASSERT_EQ("patient2", s);
+    ASSERT_EQ(5u, accessor.GetFileSize());
+  }
+  
+  cache.Acquire("d", new ParsedDicomFile(true), 5);
+  ASSERT_EQ(10, cache.GetCurrentSize());
+
+  ASSERT_TRUE(ParsedDicomCache::Accessor(cache, "b").IsValid());
+  ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "c").IsValid());  // recycled by LRU
+  ASSERT_TRUE(ParsedDicomCache::Accessor(cache, "d").IsValid());
+
+  cache.Acquire("e", new ParsedDicomFile(true), 15);
+  ASSERT_EQ(15, cache.GetCurrentSize());
+
+  ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "c").IsValid());
+  ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "d").IsValid());
+  ASSERT_TRUE(ParsedDicomCache::Accessor(cache, "e").IsValid());
+}
+
+
 
 
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1