diff Plugin/Plugin.cpp @ 0:02f7a0400a91

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 25 Feb 2015 13:45:35 +0100
parents
children ecefd45026bf
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Plugin.cpp	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,350 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include <boost/thread.hpp>
+#include <boost/lexical_cast.hpp>
+#include <EmbeddedResources.h>
+
+#include "../Orthanc/OrthancException.h"
+#include "ViewerToolbox.h"
+#include "ViewerPrefetchPolicy.h"
+#include "DecodedImageAdapter.h"
+#include "InstanceInformationAdapter.h"
+#include "SeriesInformationAdapter.h"
+
+
+
+class CacheContext
+{
+private:
+  std::auto_ptr<Orthanc::FilesystemStorage>  storage_;
+  std::auto_ptr<Orthanc::SQLite::Connection>  db_;
+  std::auto_ptr<OrthancPlugins::CacheManager>  cache_;
+  std::auto_ptr<OrthancPlugins::CacheScheduler>  scheduler_;
+
+public:
+  CacheContext(const std::string& path)
+  {
+    boost::filesystem::path p(path);
+
+    storage_.reset(new Orthanc::FilesystemStorage(path));
+    db_.reset(new Orthanc::SQLite::Connection());
+    db_->Open((p / "cache.db").string());
+
+    cache_.reset(new OrthancPlugins::CacheManager(*db_, *storage_));
+    //cache_->SetSanityCheckEnabled(true);  // For debug
+
+    scheduler_.reset(new OrthancPlugins::CacheScheduler(*cache_, 100));
+  }
+
+  OrthancPlugins::CacheScheduler& GetScheduler()
+  {
+    return *scheduler_;
+  }
+};
+
+
+static OrthancPluginContext* context_ = NULL;
+static CacheContext* cache_ = NULL;
+
+
+
+static int32_t OnChangeCallback(OrthancPluginChangeType changeType,
+                                OrthancPluginResourceType resourceType,
+                                const char* resourceId)
+{
+  try
+  {
+    if (changeType == OrthancPluginChangeType_NewInstance &&
+        resourceType == OrthancPluginResourceType_Instance)
+    {
+      // On the reception of a new instance, precompute its spatial position
+      cache_->GetScheduler().Prefetch(OrthancPlugins::CacheBundle_InstanceInformation, resourceId);
+     
+      // Indalidate the parent series of the instance
+      std::string uri = "/instances/" + std::string(resourceId);
+      Json::Value instance;
+      if (OrthancPlugins::GetJsonFromOrthanc(instance, context_, uri))
+      {
+        std::string seriesId = instance["ParentSeries"].asString();
+        cache_->GetScheduler().Invalidate(OrthancPlugins::CacheBundle_SeriesInformation, seriesId);
+      }
+    }
+
+    return 0;
+  }
+  catch (std::runtime_error& e)
+  {
+    OrthancPluginLogError(context_, e.what());
+    return 0;  // Ignore error
+  }
+}
+
+
+
+template <enum OrthancPlugins::CacheBundle bundle>
+int32_t ServeCache(OrthancPluginRestOutput* output,
+                   const char* url,
+                   const OrthancPluginHttpRequest* request)
+{
+  try
+  {
+    if (request->method != OrthancPluginHttpMethod_Get)
+    {
+      OrthancPluginSendMethodNotAllowed(context_, output, "GET");
+      return 0;
+    }
+
+    const std::string id = request->groups[0];
+    std::string content;
+
+    if (cache_->GetScheduler().Access(content, bundle, id))
+    {
+      OrthancPluginAnswerBuffer(context_, output, content.c_str(), content.size(), "application/json");
+    }
+    else
+    {
+      OrthancPluginSendHttpStatusCode(context_, output, 404);
+    }
+
+    return 0;
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    OrthancPluginLogError(context_, e.What());
+    return -1;
+  }
+  catch (std::runtime_error& e)
+  {
+    OrthancPluginLogError(context_, e.what());
+    return -1;
+  }
+  catch (boost::bad_lexical_cast&)
+  {
+    OrthancPluginLogError(context_, "Bad lexical cast");
+    return -1;
+  }
+}
+
+
+
+
+#if ORTHANC_STANDALONE == 0
+static int32_t ServeWebViewer(OrthancPluginRestOutput* output,
+                              const char* url,
+                              const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context_, output, "GET");
+    return 0;
+  }
+
+  const std::string path = std::string(WEB_VIEWER_PATH) + std::string(request->groups[0]);
+  const char* mime = OrthancPlugins::GetMimeType(path);
+
+  std::string s;
+  if (OrthancPlugins::ReadFile(s, path))
+  {
+    const char* resource = s.size() ? s.c_str() : NULL;
+    OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime);
+  }
+  else
+  {
+    std::string s = "Inexistent file in served folder: " + path;
+    OrthancPluginLogError(context_, s.c_str());
+    OrthancPluginSendHttpStatusCode(context_, output, 404);
+  }
+
+  return 0;
+}
+#endif
+
+
+
+template <enum OrthancPlugins::EmbeddedResources::DirectoryResourceId folder>
+static int32_t ServeEmbeddedFolder(OrthancPluginRestOutput* output,
+                                   const char* url,
+                                   const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context_, output, "GET");
+    return 0;
+  }
+
+  std::string path = "/" + std::string(request->groups[0]);
+  const char* mime = OrthancPlugins::GetMimeType(path);
+
+  try
+  {
+    std::string s;
+    OrthancPlugins::EmbeddedResources::GetDirectoryResource(s, folder, path.c_str());
+
+    const char* resource = s.size() ? s.c_str() : NULL;
+    OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime);
+
+    return 0;
+  }
+  catch (std::runtime_error&)
+  {
+    std::string s = "Unknown static resource in plugin: " + std::string(request->groups[0]);
+    OrthancPluginLogError(context_, s.c_str());
+    OrthancPluginSendHttpStatusCode(context_, output, 404);
+    return 0;
+  }
+}
+
+
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    using namespace OrthancPlugins;
+
+    context_ = context;
+    OrthancPluginLogWarning(context_, "Initializing the Web viewer");
+
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(context_) == 0)
+    {
+      char info[1024];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              context_->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(context_, info);
+      return -1;
+    }
+
+    OrthancPluginSetDescription(context_, "Provides a Web viewer of DICOM series within Orthanc.");
+
+
+    /* By default, use half of the available processing cores for the decoding of DICOM images */
+    int decodingThreads = boost::thread::hardware_concurrency() / 2;
+    if (decodingThreads == 0)
+    {
+      decodingThreads = 1;
+    }
+
+
+    try
+    {
+      /* Read the configuration of the Web viewer */
+      Json::Value configuration;
+      if (!ReadConfiguration(configuration, context))
+      {
+        OrthancPluginLogError(context_, "Unable to read the configuration file of Orthanc");
+        return -1;
+      }
+
+      std::string cachePath = "WebViewerCache";
+    
+      if (configuration.isMember("WebViewer"))
+      {
+        cachePath = GetStringValue(configuration["WebViewer"], "Cache", cachePath);
+        decodingThreads = GetIntegerValue(configuration["WebViewer"], "Threads", decodingThreads);
+      }
+
+      std::string message = ("Web viewer using " + boost::lexical_cast<std::string>(decodingThreads) + 
+                             " threads for the decoding of the DICOM images");
+      OrthancPluginLogWarning(context_, message.c_str());
+
+      if (decodingThreads <= 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      message = "Storing the cache of the Web viewer in folder: " + cachePath;
+      OrthancPluginLogWarning(context_, message.c_str());
+
+   
+      /* Create the cache */
+      cache_ = new CacheContext(cachePath);
+      cache_->GetScheduler().RegisterPolicy(new ViewerPrefetchPolicy(context_));
+      cache_->GetScheduler().Register(CacheBundle_SeriesInformation, 
+                                      new SeriesInformationAdapter(context_, cache_->GetScheduler()), 1);
+      cache_->GetScheduler().Register(CacheBundle_InstanceInformation, 
+                                      new InstanceInformationAdapter(context_), 1);
+      cache_->GetScheduler().Register(CacheBundle_DecodedImage, 
+                                      new DecodedImageAdapter(context_), decodingThreads);
+    }
+    catch (std::runtime_error& e)
+    {
+      OrthancPluginLogError(context_, e.what());
+      return -1;
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      OrthancPluginLogError(context_, e.What());
+      return -1;
+    }
+
+
+    /* Install the callbacks */
+    OrthancPluginRegisterRestCallback(context_, "/web-viewer/series/(.*)", ServeCache<CacheBundle_SeriesInformation>);
+    OrthancPluginRegisterRestCallback(context_, "/web-viewer/instances/(.*)", ServeCache<CacheBundle_DecodedImage>);
+    OrthancPluginRegisterRestCallback(context, "/web-viewer/libs/(.*)", ServeEmbeddedFolder<EmbeddedResources::JAVASCRIPT_LIBS>);
+
+#if ORTHANC_STANDALONE == 1
+    OrthancPluginRegisterRestCallback(context, "/web-viewer/app/(.*)", ServeEmbeddedFolder<EmbeddedResources::WEB_VIEWER>);
+#else
+    OrthancPluginRegisterRestCallback(context, "/web-viewer/app/(.*)", ServeWebViewer);
+#endif
+
+    OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback);
+
+
+    /* Extend the default Orthanc Explorer with custom JavaScript */
+    std::string explorer;
+    EmbeddedResources::GetFileResource(explorer, EmbeddedResources::ORTHANC_EXPLORER);
+    OrthancPluginExtendOrthancExplorer(context_, explorer.c_str());
+
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    OrthancPluginLogWarning(context_, "Finalizing the Web viewer");
+
+    if (cache_ != NULL)
+    {
+      delete cache_;
+      cache_ = NULL;
+    }
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "web-viewer";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return "1.0";
+  }
+}