view Framework/Loaders/SeriesMetadataLoader.cpp @ 1327:4f8db2d202c8 broker

OrthancSeriesProgressiveLoader now has two modes that can be selected at object creation : - progressive (will first load jpeg50, then jpeg90 then PAM) - non-progressive (will directly load PAM (uncompressed)) Please note that the slice loading order remains dynamic and depending upon the slice that the client code wishes to extract from the volume.
author Benjamin Golinvaux <bgo@osimis.io>
date Wed, 25 Mar 2020 14:34:27 +0100
parents 0ca50d275b9a
children 30deba7bc8e2
line wrap: on
line source

/**
 * Stone of Orthanc
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-2020 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_->ScheduleGetDicomWeb(
          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 */);
  }
}