Mercurial > hg > orthanc-stone
diff OrthancStone/Sources/Loaders/SeriesFramesLoader.cpp @ 1512:244ad1e4e76a
reorganization of folders
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 07 Jul 2020 16:21:02 +0200 |
parents | Framework/Loaders/SeriesFramesLoader.cpp@121d01aa328e |
children | e731e62692a9 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Loaders/SeriesFramesLoader.cpp Tue Jul 07 16:21:02 2020 +0200 @@ -0,0 +1,550 @@ +/** + * 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 "SeriesFramesLoader.h" + +#include "../Oracle/ParseDicomFromFileCommand.h" +#include "../Oracle/ParseDicomFromWadoCommand.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +# include <DicomParsing/Internals/DicomImageDecoder.h> +#endif + +#include <DicomFormat/DicomInstanceHasher.h> +#include <Images/Image.h> +#include <Images/ImageProcessing.h> +#include <Images/JpegReader.h> + +#include <boost/algorithm/string/predicate.hpp> + +namespace OrthancStone +{ + class SeriesFramesLoader::Payload : public Orthanc::IDynamicObject + { + private: + DicomSource source_; + size_t seriesIndex_; + std::string sopInstanceUid_; // Only used for debug purpose + unsigned int quality_; + bool hasWindowing_; + float windowingCenter_; + float windowingWidth_; + std::unique_ptr<Orthanc::IDynamicObject> userPayload_; + + public: + Payload(const DicomSource& source, + size_t seriesIndex, + const std::string& sopInstanceUid, + unsigned int quality, + Orthanc::IDynamicObject* userPayload) : + source_(source), + seriesIndex_(seriesIndex), + sopInstanceUid_(sopInstanceUid), + quality_(quality), + hasWindowing_(false), + userPayload_(userPayload) + { + } + + size_t GetSeriesIndex() const + { + return seriesIndex_; + } + + const std::string& GetSopInstanceUid() const + { + return sopInstanceUid_; + } + + unsigned int GetQuality() const + { + return quality_; + } + + void SetWindowing(float center, + float width) + { + hasWindowing_ = true; + windowingCenter_ = center; + windowingWidth_ = width; + } + + bool HasWindowing() const + { + return hasWindowing_; + } + + float GetWindowingCenter() const + { + if (hasWindowing_) + { + return windowingCenter_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + float GetWindowingWidth() const + { + if (hasWindowing_) + { + return windowingWidth_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + const DicomSource& GetSource() const + { + return source_; + } + + Orthanc::IDynamicObject* GetUserPayload() const + { + return userPayload_.get(); + } + }; + + + SeriesFramesLoader::SeriesFramesLoader(ILoadersContext& context, + LoadedDicomResources& instances, + const std::string& dicomDirPath, + boost::shared_ptr<LoadedDicomResources> dicomDir) : + context_(context), + frames_(instances), + dicomDirPath_(dicomDirPath), + dicomDir_(dicomDir) + { + } + + + void SeriesFramesLoader::EmitMessage(const Payload& payload, + const Orthanc::ImageAccessor& image) + { + const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(payload.GetSeriesIndex()); + const Orthanc::DicomMap& instance = frames_.GetInstance(payload.GetSeriesIndex()); + size_t frameIndex = frames_.GetFrameIndex(payload.GetSeriesIndex()); + + if (frameIndex >= parameters.GetImageInformation().GetNumberOfFrames() || + payload.GetSopInstanceUid() != parameters.GetSopInstanceUid()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + LOG(TRACE) << "Decoded instance " << payload.GetSopInstanceUid() << ", frame " + << frameIndex << ": " << image.GetWidth() << "x" + << image.GetHeight() << ", " << Orthanc::EnumerationToString(image.GetFormat()) + << ", quality " << payload.GetQuality(); + + FrameLoadedMessage message(*this, frameIndex, payload.GetQuality(), image, instance, parameters, payload.GetUserPayload()); + BroadcastMessage(message); + } + + +#if ORTHANC_ENABLE_DCMTK == 1 + void SeriesFramesLoader::HandleDicom(const Payload& payload, + Orthanc::ParsedDicomFile& dicom) + { + size_t frameIndex = frames_.GetFrameIndex(payload.GetSeriesIndex()); + + std::unique_ptr<Orthanc::ImageAccessor> decoded; + decoded.reset(Orthanc::DicomImageDecoder::Decode( + dicom, + static_cast<unsigned int>(frameIndex))); + + if (decoded.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + EmitMessage(payload, *decoded); + } +#endif + + + void SeriesFramesLoader::HandleDicomWebRendered(const Payload& payload, + const std::string& body, + const std::map<std::string, std::string>& headers) + { + assert(payload.GetSource().IsDicomWeb() && + payload.HasWindowing()); + + bool ok = false; + for (std::map<std::string, std::string>::const_iterator it = headers.begin(); + it != headers.end(); ++it) + { + if (boost::iequals("content-type", it->first) && + boost::iequals(Orthanc::MIME_JPEG, it->second)) + { + ok = true; + break; + } + } + + if (!ok) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "The WADO-RS server has not generated a JPEG image on /rendered"); + } + + Orthanc::JpegReader reader; + reader.ReadFromMemory(body); + + switch (reader.GetFormat()) + { + case Orthanc::PixelFormat_RGB24: + EmitMessage(payload, reader); + break; + + case Orthanc::PixelFormat_Grayscale8: + { + const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(payload.GetSeriesIndex()); + + Orthanc::Image scaled(parameters.GetExpectedPixelFormat(), reader.GetWidth(), reader.GetHeight(), false); + Orthanc::ImageProcessing::Convert(scaled, reader); + + float w = payload.GetWindowingWidth(); + if (w <= 0.01f) + { + w = 0.01f; // Prevent division by zero + } + + const float c = payload.GetWindowingCenter(); + const float scaling = w / 255.0f; + const float offset = (c - w / 2.0f) / scaling; + + Orthanc::ImageProcessing::ShiftScale(scaled, offset, scaling, false /* truncation to speed up */); + EmitMessage(payload, scaled); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + +#if ORTHANC_ENABLE_DCMTK == 1 + void SeriesFramesLoader::Handle(const ParseDicomSuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + + const Payload& payload = dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()); + if ((payload.GetSource().IsDicomDir() || + payload.GetSource().IsDicomWeb()) && + message.HasPixelData()) + { + HandleDicom(dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()), message.GetDicom()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } +#endif + + + void SeriesFramesLoader::Handle(const GetOrthancImageCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + + const Payload& payload = dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()); + assert(payload.GetSource().IsOrthanc()); + + EmitMessage(payload, message.GetImage()); + } + + + void SeriesFramesLoader::Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + + const Payload& payload = dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()); + assert(payload.GetSource().IsOrthanc()); + + EmitMessage(payload, message.GetImage()); + } + + + void SeriesFramesLoader::Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + // This is to handle "/rendered" in DICOMweb + assert(message.GetOrigin().HasPayload()); + HandleDicomWebRendered(dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()), + message.GetAnswer(), message.GetAnswerHeaders()); + } + + + void SeriesFramesLoader::Handle(const HttpCommand::SuccessMessage& message) + { + // This is to handle "/rendered" in DICOMweb + assert(message.GetOrigin().HasPayload()); + HandleDicomWebRendered(dynamic_cast<const Payload&>(message.GetOrigin().GetPayload()), + message.GetAnswer(), message.GetAnswerHeaders()); + } + + + void SeriesFramesLoader::GetPreviewWindowing(float& center, + float& width, + size_t index) const + { + const Orthanc::DicomMap& instance = frames_.GetInstance(index); + const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(index); + + if (parameters.HasDefaultWindowing()) + { + // TODO - Handle multiple presets (take the largest width) + center = parameters.GetDefaultWindowingCenter(); + width = parameters.GetDefaultWindowingWidth(); + } + else + { + float a, b; + if (instance.ParseFloat(a, Orthanc::DICOM_TAG_SMALLEST_IMAGE_PIXEL_VALUE) && + instance.ParseFloat(b, Orthanc::DICOM_TAG_LARGEST_IMAGE_PIXEL_VALUE) && + a < b) + { + center = (a + b) / 2.0f; + width = (b - a); + } + else + { + // Cannot infer a suitable windowing from the available tags + center = 128.0f; + width = 256.0f; + } + } + } + + + Orthanc::IDynamicObject& SeriesFramesLoader::FrameLoadedMessage::GetUserPayload() const + { + if (userPayload_) + { + return *userPayload_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void SeriesFramesLoader::Factory::SetDicomDir(const std::string& dicomDirPath, + boost::shared_ptr<LoadedDicomResources> dicomDir) + { + dicomDirPath_ = dicomDirPath; + dicomDir_ = dicomDir; + } + + + boost::shared_ptr<IObserver> SeriesFramesLoader::Factory::Create(ILoadersContext::ILock& stone) + { + boost::shared_ptr<SeriesFramesLoader> loader( + new SeriesFramesLoader(stone.GetContext(), instances_, dicomDirPath_, dicomDir_)); + loader->Register<GetOrthancImageCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); + loader->Register<GetOrthancWebViewerJpegCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); + loader->Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); + loader->Register<OrthancRestApiCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); + +#if ORTHANC_ENABLE_DCMTK == 1 + loader->Register<ParseDicomSuccessMessage>(stone.GetOracleObservable(), &SeriesFramesLoader::Handle); +#endif + + return loader; + } + + + void SeriesFramesLoader::ScheduleLoadFrame(int priority, + const DicomSource& source, + size_t index, + unsigned int quality, + Orthanc::IDynamicObject* userPayload) + { + std::unique_ptr<Orthanc::IDynamicObject> protection(userPayload); + + if (index >= frames_.GetFramesCount() || + quality >= source.GetQualityCount()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + const Orthanc::DicomMap& instance = frames_.GetInstance(index); + + std::string sopInstanceUid; + if (!instance.LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Missing SOPInstanceUID in a DICOM instance"); + } + + if (source.IsDicomDir()) + { + if (dicomDir_.get() == NULL) + { + // Should have been set in the factory + throw Orthanc::OrthancException( + Orthanc::ErrorCode_BadSequenceOfCalls, + "SeriesFramesLoader::Factory::SetDicomDir() should have been called"); + } + + assert(quality == 0); + + std::string file; + if (dicomDir_->LookupStringValue(file, sopInstanceUid, Orthanc::DICOM_TAG_REFERENCED_FILE_ID)) + { + std::unique_ptr<ParseDicomFromFileCommand> command(new ParseDicomFromFileCommand(source, dicomDirPath_, file)); + command->SetPixelDataIncluded(true); + command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release())); + + { + std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + else + { + LOG(WARNING) << "Missing tag ReferencedFileID in a DICOMDIR entry"; + } + } + else if (source.IsDicomWeb()) + { + std::string studyInstanceUid, seriesInstanceUid; + if (!instance.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || + !instance.LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Missing StudyInstanceUID or SeriesInstanceUID in a DICOM instance"); + } + + const std::string uri = ("/studies/" + studyInstanceUid + + "/series/" + seriesInstanceUid + + "/instances/" + sopInstanceUid); + + if (source.HasDicomWebRendered() && + quality == 0) + { + float c, w; + GetPreviewWindowing(c, w, index); + + std::map<std::string, std::string> arguments, headers; + arguments["window"] = (boost::lexical_cast<std::string>(c) + "," + + boost::lexical_cast<std::string>(w) + ",linear"); + headers["Accept"] = "image/jpeg"; + + std::unique_ptr<Payload> payload(new Payload(source, index, sopInstanceUid, quality, protection.release())); + payload->SetWindowing(c, w); + + { + std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, + source.CreateDicomWebCommand(uri + "/rendered", arguments, headers, payload.release())); + } + } + else + { + assert((source.HasDicomWebRendered() && quality == 1) || + (!source.HasDicomWebRendered() && quality == 0)); + +#if ORTHANC_ENABLE_DCMTK == 1 + std::unique_ptr<Payload> payload(new Payload(source, index, sopInstanceUid, quality, protection.release())); + + const std::map<std::string, std::string> empty; + + std::unique_ptr<ParseDicomFromWadoCommand> command( + new ParseDicomFromWadoCommand(source, sopInstanceUid, source.CreateDicomWebCommand(uri, empty, empty, NULL))); + command->AcquirePayload(payload.release()); + + { + std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "DCMTK is not enabled, cannot parse a DICOM instance"); +#endif + } + } + else if (source.IsOrthanc()) + { + std::string orthancId; + + { + std::string patientId, studyInstanceUid, seriesInstanceUid; + if (!instance.LookupStringValue(patientId, Orthanc::DICOM_TAG_PATIENT_ID, false) || + !instance.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || + !instance.LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Missing StudyInstanceUID or SeriesInstanceUID in a DICOM instance"); + } + + Orthanc::DicomInstanceHasher hasher(patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid); + orthancId = hasher.HashInstance(); + } + + const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(index); + + if (quality == 0 && source.HasOrthancWebViewer1()) + { + std::unique_ptr<GetOrthancWebViewerJpegCommand> command(new GetOrthancWebViewerJpegCommand); + command->SetInstance(orthancId); + command->SetExpectedPixelFormat(parameters.GetExpectedPixelFormat()); + command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release())); + + { + std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + else if (quality == 0 && source.HasOrthancAdvancedPreview()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + else + { + assert(quality <= 1); + assert(quality == 0 || + source.HasOrthancWebViewer1() || + source.HasOrthancAdvancedPreview()); + + std::unique_ptr<GetOrthancImageCommand> command(new GetOrthancImageCommand); + command->SetFrameUri(orthancId, frames_.GetFrameIndex(index), parameters.GetExpectedPixelFormat()); + command->SetExpectedPixelFormat(parameters.GetExpectedPixelFormat()); + command->SetHttpHeader("Accept", Orthanc::MIME_PAM); + command->AcquirePayload(new Payload(source, index, sopInstanceUid, quality, protection.release())); + + { + std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } +}