diff Framework/Loaders/SeriesMetadataLoader.cpp @ 1228:c471a0aa137b broker

adding the next generation of loaders
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 09 Dec 2019 13:58:37 +0100
parents
children a8248b08115c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Loaders/SeriesMetadataLoader.cpp	Mon Dec 09 13:58:37 2019 +0100
@@ -0,0 +1,347 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., 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 "SeriesMetadataLoader.h"
+
+#include <Core/DicomFormat/DicomInstanceHasher.h>
+
+namespace OrthancStone
+{
+  SeriesMetadataLoader::SeriesMetadataLoader(boost::shared_ptr<DicomResourcesLoader>& loader) :
+    loader_(loader),
+    state_(State_Setup)
+  {
+  }
+
+
+  bool SeriesMetadataLoader::IsScheduledWithHigherPriority(const std::string& seriesInstanceUid,
+                                                           int priority) const
+  {
+    if (series_.find(seriesInstanceUid) != series_.end())
+    {
+      // This series is readily available
+      return true;
+    }
+    else
+    {
+      std::map<std::string, int>::const_iterator found = scheduled_.find(seriesInstanceUid);
+
+      return (found != scheduled_.end() &&
+              found->second < priority);
+    }
+  }
+
+
+  void SeriesMetadataLoader::Handle(const DicomResourcesLoader::SuccessMessage& message)
+  {
+    assert(message.GetResources());
+
+    switch (state_)
+    {
+      case State_Setup:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+
+      case State_Default:
+      {
+        std::string studyInstanceUid;
+        std::string seriesInstanceUid;
+
+        if (message.GetResources()->LookupTagValueConsensus(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID) &&
+            message.GetResources()->LookupTagValueConsensus(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID))
+        {
+          series_[seriesInstanceUid] = message.GetResources();
+
+          SeriesLoadedMessage loadedMessage(*this, message.GetDicomSource(), studyInstanceUid,
+                                            seriesInstanceUid, *message.GetResources());
+          BroadcastMessage(loadedMessage);
+        }
+
+        break;
+      }
+
+      case State_DicomDir:
+      {
+        assert(!dicomDir_);
+        assert(seriesSize_.empty());
+
+        dicomDir_ = message.GetResources();
+            
+        for (size_t i = 0; i < message.GetResources()->GetSize(); i++)
+        {
+          std::string seriesInstanceUid;
+          if (message.GetResources()->GetResource(i).LookupStringValue
+              (seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false))
+          {
+            boost::shared_ptr<OrthancStone::LoadedDicomResources> target
+              (new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_SOP_INSTANCE_UID));
+
+            if (loader_->ScheduleLoadDicomFile(target, message.GetPriority(), message.GetDicomSource(), dicomDirPath_, 
+                                               message.GetResources()->GetResource(i), false /* no need for pixel data */,
+                                               NULL /* TODO PAYLOAD */))
+            {
+              std::map<std::string, unsigned int>::iterator found = seriesSize_.find(seriesInstanceUid);
+              if (found == seriesSize_.end())
+              {
+                series_[seriesInstanceUid].reset
+                  (new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_SOP_INSTANCE_UID));
+                seriesSize_[seriesInstanceUid] = 1;
+              }
+              else
+              {
+                found->second ++;
+              }
+            }
+          }
+        }
+
+        LOG(INFO) << "Read a DICOMDIR containing " << seriesSize_.size() << " series";            
+
+        state_ = State_DicomFile;
+        break;
+      }
+
+      case State_DicomFile:
+      {
+        assert(dicomDir_);
+        assert(message.GetResources()->GetSize() <= 1);  // Could be zero if corrupted DICOM instance
+
+        if (message.GetResources()->GetSize() == 1)
+        {
+          const Orthanc::DicomMap& instance = message.GetResources()->GetResource(0);
+
+          std::string studyInstanceUid;
+          std::string seriesInstanceUid;
+          if (instance.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) &&
+              instance.LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false))
+          {
+            Series::const_iterator series = series_.find(seriesInstanceUid);
+            std::map<std::string, unsigned int>::const_iterator size = seriesSize_.find(seriesInstanceUid);
+
+            if (series == series_.end() ||
+                size == seriesSize_.end())
+            {
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+            }
+            else
+            {
+              series->second->AddResource(instance);
+
+              if (series->second->GetSize() > size->second)
+              {
+                throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+              }
+              else if (series->second->GetSize() == size->second)
+              {
+                // The series is complete
+                SeriesLoadedMessage loadedMessage(
+                  *this, message.GetDicomSource(),
+                  studyInstanceUid, seriesInstanceUid, *series->second);
+                loadedMessage.SetDicomDir(dicomDirPath_, dicomDir_);
+                BroadcastMessage(loadedMessage);
+              }
+            }
+          }
+        }
+
+        break;
+      }
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  SeriesMetadataLoader::SeriesLoadedMessage::SeriesLoadedMessage(
+    const SeriesMetadataLoader& loader,
+    const DicomSource& source,
+    const std::string& studyInstanceUid,
+    const std::string& seriesInstanceUid,
+    LoadedDicomResources& instances) :
+    OriginMessage(loader),
+    source_(source),
+    studyInstanceUid_(studyInstanceUid),
+    seriesInstanceUid_(seriesInstanceUid),
+    instances_(instances)
+  {
+    LOG(INFO) << "Loaded series " << seriesInstanceUid
+              << ", number of instances: " << instances_.GetSize();
+  }
+
+
+  boost::shared_ptr<IObserver> SeriesMetadataLoader::Factory::Create(ILoadersContext::ILock& context)
+  {
+    DicomResourcesLoader::Factory factory;
+    boost::shared_ptr<DicomResourcesLoader> loader
+      (boost::dynamic_pointer_cast<DicomResourcesLoader>(factory.Create(context)));
+      
+    boost::shared_ptr<SeriesMetadataLoader> obj(new SeriesMetadataLoader(loader));
+    obj->Register<DicomResourcesLoader::SuccessMessage>(*loader, &SeriesMetadataLoader::Handle);
+    return obj;
+  }
+
+
+  SeriesMetadataLoader::Accessor::Accessor(SeriesMetadataLoader& that,
+                                           const std::string& seriesInstanceUid)
+  {
+    Series::const_iterator found = that.series_.find(seriesInstanceUid);
+    if (found != that.series_.end())
+    {
+      assert(found->second != NULL);
+      series_ = found->second;
+    }
+  }
+
+
+  size_t SeriesMetadataLoader::Accessor::GetInstancesCount() const
+  {
+    if (IsComplete())
+    {
+      return series_->GetSize();
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  const Orthanc::DicomMap& SeriesMetadataLoader::Accessor::GetInstance(size_t index) const
+  {
+    if (IsComplete())
+    {
+      return series_->GetResource(index);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }     
+  }
+
+
+  void SeriesMetadataLoader::ScheduleLoadSeries(int priority,
+                                                const DicomSource& source,
+                                                const std::string& studyInstanceUid,
+                                                const std::string& seriesInstanceUid)
+  {
+    if (state_ != State_Setup &&
+        state_ != State_Default)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                      "The loader is working in DICOMDIR state");
+    }
+
+    state_ = State_Default;
+
+    // Only re-schedule the loading if the previous loading was with lower priority
+    if (!IsScheduledWithHigherPriority(seriesInstanceUid, priority))
+    {
+      if (source.IsDicomWeb())
+      {
+        boost::shared_ptr<LoadedDicomResources> target
+          (new LoadedDicomResources(Orthanc::DICOM_TAG_SOP_INSTANCE_UID));
+        loader_->ScheduleWado(target, priority, source,
+                              "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/metadata",
+                              NULL /* TODO PAYLOAD */);
+
+        scheduled_[seriesInstanceUid] = priority;
+      }
+      else if (source.IsOrthanc())
+      {
+        // This flavor of the method is only available with DICOMweb, as
+        // Orthanc requires the "PatientID" to be known
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                        "The PatientID must be provided on Orthanc sources");
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+  }
+
+
+  void SeriesMetadataLoader::ScheduleLoadSeries(int priority,
+                                                const DicomSource& source,
+                                                const std::string& patientId,
+                                                const std::string& studyInstanceUid,
+                                                const std::string& seriesInstanceUid)
+  {
+    if (state_ != State_Setup &&
+        state_ != State_Default)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                      "The loader is working in DICOMDIR state");
+    }
+
+    state_ = State_Default;
+
+    if (source.IsDicomWeb())
+    {
+      ScheduleLoadSeries(priority, source, studyInstanceUid, seriesInstanceUid);
+    }
+    else if (!IsScheduledWithHigherPriority(seriesInstanceUid, priority))
+    {
+      if (source.IsOrthanc())
+      {
+        // Dummy SOP Instance UID, as we are working at the "series" level
+        Orthanc::DicomInstanceHasher hasher(patientId, studyInstanceUid, seriesInstanceUid, "dummy");
+
+        boost::shared_ptr<LoadedDicomResources> target
+          (new LoadedDicomResources(Orthanc::DICOM_TAG_SOP_INSTANCE_UID));
+        
+        loader_->ScheduleLoadOrthancResources(target, priority, source, Orthanc::ResourceType_Series,
+                                              hasher.HashSeries(), Orthanc::ResourceType_Instance,
+                                              NULL /* TODO PAYLOAD */);
+
+        scheduled_[seriesInstanceUid] = priority;
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+  }
+
+
+  void SeriesMetadataLoader::ScheduleLoadDicomDir(int priority,
+                                                  const DicomSource& source,
+                                                  const std::string& path)
+  {
+    if (!source.IsDicomDir())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (state_ != State_Setup)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls,
+                                      "The loader cannot load two different DICOMDIR");
+    }
+
+    state_ = State_DicomDir;
+    dicomDirPath_ = path;
+    boost::shared_ptr<LoadedDicomResources> dicomDir
+      (new LoadedDicomResources(Orthanc::DICOM_TAG_REFERENCED_SOP_INSTANCE_UID_IN_FILE));
+    loader_->ScheduleLoadDicomDir(dicomDir, priority, source, path,
+                                  NULL /* TODO PAYLOAD */);
+  }
+}