Mercurial > hg > orthanc-stone
diff OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.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/SeriesThumbnailsLoader.cpp@b931ddbe070e |
children | e731e62692a9 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.cpp Tue Jul 07 16:21:02 2020 +0200 @@ -0,0 +1,773 @@ +/** + * 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 "SeriesThumbnailsLoader.h" + +#include "LoadedDicomResources.h" +#include "../Oracle/ParseDicomFromWadoCommand.h" +#include "../Toolbox/ImageToolbox.h" + +#include <DicomFormat/DicomMap.h> +#include <DicomFormat/DicomInstanceHasher.h> +#include <Images/Image.h> +#include <Images/ImageProcessing.h> +#include <Images/JpegReader.h> +#include <Images/JpegWriter.h> +#include <OrthancException.h> + +#include <boost/algorithm/string/predicate.hpp> + +#if ORTHANC_ENABLE_DCMTK == 1 +# include <DicomParsing/ParsedDicomFile.h> +# include <DicomParsing/Internals/DicomImageDecoder.h> +#endif + + +static const unsigned int JPEG_QUALITY = 70; // Only used for Orthanc source + +namespace OrthancStone +{ + static SeriesThumbnailType ExtractSopClassUid(const std::string& sopClassUid) + { + if (sopClassUid == "1.2.840.10008.5.1.4.1.1.104.1") // Encapsulated PDF Storage + { + return SeriesThumbnailType_Pdf; + } + else if (sopClassUid == "1.2.840.10008.5.1.4.1.1.77.1.1.1" || // Video Endoscopic Image Storage + sopClassUid == "1.2.840.10008.5.1.4.1.1.77.1.2.1" || // Video Microscopic Image Storage + sopClassUid == "1.2.840.10008.5.1.4.1.1.77.1.4.1") // Video Photographic Image Storage + { + return SeriesThumbnailType_Video; + } + else + { + return SeriesThumbnailType_Unsupported; + } + } + + + SeriesThumbnailsLoader::Thumbnail::Thumbnail(const std::string& image, + const std::string& mime) : + type_(SeriesThumbnailType_Image), + image_(image), + mime_(mime) + { + } + + + SeriesThumbnailsLoader::Thumbnail::Thumbnail(SeriesThumbnailType type) : + type_(type) + { + if (type == SeriesThumbnailType_Image) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + Orthanc::ImageAccessor* SeriesThumbnailsLoader::SuccessMessage::DecodeImage() const + { + if (GetType() != SeriesThumbnailType_Image) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + Orthanc::MimeType mime; + if (!Orthanc::LookupMimeType(mime, GetMime())) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "Unsupported MIME type for thumbnail: " + GetMime()); + } + + switch (mime) + { + case Orthanc::MimeType_Jpeg: + { + std::unique_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader); + reader->ReadFromMemory(GetEncodedImage()); + return reader.release(); + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "Cannot decode MIME type for thumbnail: " + GetMime()); + } + } + + + + void SeriesThumbnailsLoader::AcquireThumbnail(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + SeriesThumbnailsLoader::Thumbnail* thumbnail) + { + assert(thumbnail != NULL); + + std::unique_ptr<Thumbnail> protection(thumbnail); + + Thumbnails::iterator found = thumbnails_.find(seriesInstanceUid); + if (found == thumbnails_.end()) + { + thumbnails_[seriesInstanceUid] = protection.release(); + } + else + { + assert(found->second != NULL); + if (protection->GetType() == SeriesThumbnailType_NotLoaded || + protection->GetType() == SeriesThumbnailType_Unsupported) + { + // Don't replace an old entry if the current one is worse + return; + } + else + { + delete found->second; + found->second = protection.release(); + } + } + + LOG(INFO) << "Thumbnail updated for series: " << seriesInstanceUid << ": " << thumbnail->GetType(); + + SuccessMessage message(*this, source, studyInstanceUid, seriesInstanceUid, *thumbnail); + BroadcastMessage(message); + } + + + class SeriesThumbnailsLoader::Handler : public Orthanc::IDynamicObject + { + private: + boost::shared_ptr<SeriesThumbnailsLoader> loader_; + DicomSource source_; + std::string studyInstanceUid_; + std::string seriesInstanceUid_; + + public: + Handler(boost::shared_ptr<SeriesThumbnailsLoader> loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) : + loader_(loader), + source_(source), + studyInstanceUid_(studyInstanceUid), + seriesInstanceUid_(seriesInstanceUid) + { + if (!loader) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + boost::shared_ptr<SeriesThumbnailsLoader> GetLoader() + { + return loader_; + } + + const DicomSource& GetSource() const + { + return source_; + } + + const std::string& GetStudyInstanceUid() const + { + return studyInstanceUid_; + } + + const std::string& GetSeriesInstanceUid() const + { + return seriesInstanceUid_; + } + + virtual void HandleSuccess(const std::string& body, + const std::map<std::string, std::string>& headers) = 0; + + virtual void HandleError() + { + LOG(INFO) << "Cannot generate thumbnail for SeriesInstanceUID: " << seriesInstanceUid_; + } + }; + + + class SeriesThumbnailsLoader::DicomWebSopClassHandler : public SeriesThumbnailsLoader::Handler + { + private: + static bool GetSopClassUid(std::string& sopClassUid, + const Json::Value& json) + { + Orthanc::DicomMap dicom; + dicom.FromDicomWeb(json); + + return dicom.LookupStringValue(sopClassUid, Orthanc::DICOM_TAG_SOP_CLASS_UID, false); + } + + public: + DicomWebSopClassHandler(boost::shared_ptr<SeriesThumbnailsLoader> loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) : + Handler(loader, source, studyInstanceUid, seriesInstanceUid) + { + } + + virtual void HandleSuccess(const std::string& body, + const std::map<std::string, std::string>& headers) + { + Json::Reader reader; + Json::Value value; + + if (!reader.parse(body, value) || + value.type() != Json::arrayValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + else + { + SeriesThumbnailType type = SeriesThumbnailType_Unsupported; + + std::string sopClassUid; + if (value.size() > 0 && + GetSopClassUid(sopClassUid, value[0])) + { + bool ok = true; + + for (Json::Value::ArrayIndex i = 1; i < value.size() && ok; i++) + { + std::string s; + if (!GetSopClassUid(s, value[i]) || + s != sopClassUid) + { + ok = false; + } + } + + if (ok) + { + type = ExtractSopClassUid(sopClassUid); + } + } + + GetLoader()->AcquireThumbnail(GetSource(), GetStudyInstanceUid(), + GetSeriesInstanceUid(), new Thumbnail(type)); + } + } + }; + + + class SeriesThumbnailsLoader::DicomWebThumbnailHandler : public SeriesThumbnailsLoader::Handler + { + public: + DicomWebThumbnailHandler(boost::shared_ptr<SeriesThumbnailsLoader> loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) : + Handler(loader, source, studyInstanceUid, seriesInstanceUid) + { + } + + virtual void HandleSuccess(const std::string& body, + const std::map<std::string, std::string>& headers) + { + std::string mime = Orthanc::MIME_JPEG; + for (std::map<std::string, std::string>::const_iterator + it = headers.begin(); it != headers.end(); ++it) + { + if (boost::iequals(it->first, "content-type")) + { + mime = it->second; + } + } + + GetLoader()->AcquireThumbnail(GetSource(), GetStudyInstanceUid(), + GetSeriesInstanceUid(), new Thumbnail(body, mime)); + } + + virtual void HandleError() + { + // The DICOMweb wasn't able to generate a thumbnail, try to + // retrieve the SopClassUID tag using QIDO-RS + + std::map<std::string, std::string> arguments, headers; + arguments["0020000D"] = GetStudyInstanceUid(); + arguments["0020000E"] = GetSeriesInstanceUid(); + arguments["includefield"] = "00080016"; // SOP Class UID + + std::unique_ptr<IOracleCommand> command( + GetSource().CreateDicomWebCommand( + "/instances", arguments, headers, new DicomWebSopClassHandler( + GetLoader(), GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid()))); + GetLoader()->Schedule(command.release()); + } + }; + + + class SeriesThumbnailsLoader::ThumbnailInformation : public Orthanc::IDynamicObject + { + private: + DicomSource source_; + std::string studyInstanceUid_; + std::string seriesInstanceUid_; + + public: + ThumbnailInformation(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) : + source_(source), + studyInstanceUid_(studyInstanceUid), + seriesInstanceUid_(seriesInstanceUid) + { + } + + const DicomSource& GetDicomSource() const + { + return source_; + } + + const std::string& GetStudyInstanceUid() const + { + return studyInstanceUid_; + } + + const std::string& GetSeriesInstanceUid() const + { + return seriesInstanceUid_; + } + }; + + + class SeriesThumbnailsLoader::OrthancSopClassHandler : public SeriesThumbnailsLoader::Handler + { + private: + std::string instanceId_; + + public: + OrthancSopClassHandler(boost::shared_ptr<SeriesThumbnailsLoader> loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& instanceId) : + Handler(loader, source, studyInstanceUid, seriesInstanceUid), + instanceId_(instanceId) + { + } + + virtual void HandleSuccess(const std::string& body, + const std::map<std::string, std::string>& headers) + { + SeriesThumbnailType type = ExtractSopClassUid(body); + + if (type == SeriesThumbnailType_Pdf || + type == SeriesThumbnailType_Video) + { + GetLoader()->AcquireThumbnail(GetSource(), GetStudyInstanceUid(), + GetSeriesInstanceUid(), new Thumbnail(type)); + } + else + { + std::unique_ptr<GetOrthancImageCommand> command(new GetOrthancImageCommand); + command->SetUri("/instances/" + instanceId_ + "/preview"); + command->SetHttpHeader("Accept", Orthanc::MIME_JPEG); + command->AcquirePayload(new ThumbnailInformation( + GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid())); + GetLoader()->Schedule(command.release()); + } + } + }; + + + class SeriesThumbnailsLoader::SelectOrthancInstanceHandler : public SeriesThumbnailsLoader::Handler + { + public: + SelectOrthancInstanceHandler(boost::shared_ptr<SeriesThumbnailsLoader> loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) : + Handler(loader, source, studyInstanceUid, seriesInstanceUid) + { + } + + virtual void HandleSuccess(const std::string& body, + const std::map<std::string, std::string>& headers) + { + static const char* const INSTANCES = "Instances"; + + Json::Value json; + Json::Reader reader; + if (!reader.parse(body, json) || + json.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + if (json.isMember(INSTANCES) && + json[INSTANCES].type() == Json::arrayValue && + json[INSTANCES].size() > 0) + { + // Select one instance of the series to generate the thumbnail + Json::Value::ArrayIndex index = json[INSTANCES].size() / 2; + if (json[INSTANCES][index].type() == Json::stringValue) + { + std::map<std::string, std::string> arguments, headers; + arguments["quality"] = boost::lexical_cast<std::string>(JPEG_QUALITY); + headers["Accept"] = Orthanc::MIME_JPEG; + + const std::string instance = json[INSTANCES][index].asString(); + + std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetUri("/instances/" + instance + "/metadata/SopClassUid"); + command->AcquirePayload( + new OrthancSopClassHandler( + GetLoader(), GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid(), instance)); + GetLoader()->Schedule(command.release()); + } + } + } + }; + + +#if ORTHANC_ENABLE_DCMTK == 1 + class SeriesThumbnailsLoader::SelectDicomWebInstanceHandler : public SeriesThumbnailsLoader::Handler + { + public: + SelectDicomWebInstanceHandler(boost::shared_ptr<SeriesThumbnailsLoader> loader, + const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) : + Handler(loader, source, studyInstanceUid, seriesInstanceUid) + { + } + + virtual void HandleSuccess(const std::string& body, + const std::map<std::string, std::string>& headers) + { + Json::Value json; + Json::Reader reader; + if (!reader.parse(body, json) || + json.type() != Json::arrayValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + LoadedDicomResources instances(Orthanc::DICOM_TAG_SOP_INSTANCE_UID); + instances.AddFromDicomWeb(json); + + std::string sopInstanceUid; + if (instances.GetSize() == 0 || + !instances.GetResource(0).LookupStringValue(sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) + { + LOG(ERROR) << "Series without an instance: " << GetSeriesInstanceUid(); + } + else + { + GetLoader()->Schedule( + ParseDicomFromWadoCommand::Create( + GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid(), sopInstanceUid, false, + Orthanc::DicomTransferSyntax_LittleEndianExplicit /* useless, as no transcoding */, + new ThumbnailInformation( + GetSource(), GetStudyInstanceUid(), GetSeriesInstanceUid()))); + } + } + }; +#endif + + + void SeriesThumbnailsLoader::Schedule(IOracleCommand* command) + { + std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority_, command); + } + + + void SeriesThumbnailsLoader::Handle(const HttpCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + dynamic_cast<Handler&>(message.GetOrigin().GetPayload()).HandleSuccess(message.GetAnswer(), message.GetAnswerHeaders()); + } + + + void SeriesThumbnailsLoader::Handle(const OrthancRestApiCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + dynamic_cast<Handler&>(message.GetOrigin().GetPayload()).HandleSuccess(message.GetAnswer(), message.GetAnswerHeaders()); + } + + + void SeriesThumbnailsLoader::Handle(const GetOrthancImageCommand::SuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + const ThumbnailInformation& info = dynamic_cast<ThumbnailInformation&>(message.GetOrigin().GetPayload()); + + std::unique_ptr<Orthanc::ImageAccessor> resized(Orthanc::ImageProcessing::FitSize(message.GetImage(), width_, height_)); + + std::string jpeg; + Orthanc::JpegWriter writer; + writer.SetQuality(JPEG_QUALITY); + writer.WriteToMemory(jpeg, *resized); + + AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(), + info.GetSeriesInstanceUid(), new Thumbnail(jpeg, Orthanc::MIME_JPEG)); + } + + +#if ORTHANC_ENABLE_DCMTK == 1 + void SeriesThumbnailsLoader::Handle(const ParseDicomSuccessMessage& message) + { + assert(message.GetOrigin().HasPayload()); + const ParseDicomFromWadoCommand& origin = + dynamic_cast<const ParseDicomFromWadoCommand&>(message.GetOrigin()); + const ThumbnailInformation& info = dynamic_cast<ThumbnailInformation&>(origin.GetPayload()); + + std::string tmp; + Orthanc::DicomTransferSyntax transferSyntax; + if (!message.GetDicom().LookupTransferSyntax(tmp)) + { + + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "DICOM instance without a transfer syntax: " + origin.GetSopInstanceUid()); + } + else if (!Orthanc::LookupTransferSyntax(transferSyntax, tmp) || + !ImageToolbox::IsDecodingSupported(transferSyntax)) + { + LOG(INFO) << "Asking the DICOMweb server to transcode, " + << "as I don't support this transfer syntax: " << tmp; + + Schedule(ParseDicomFromWadoCommand::Create( + origin.GetSource(), info.GetStudyInstanceUid(), info.GetSeriesInstanceUid(), + origin.GetSopInstanceUid(), true, Orthanc::DicomTransferSyntax_LittleEndianExplicit, + new ThumbnailInformation( + origin.GetSource(), info.GetStudyInstanceUid(), info.GetSeriesInstanceUid()))); + } + else + { + std::unique_ptr<Orthanc::ImageAccessor> frame( + Orthanc::DicomImageDecoder::Decode(message.GetDicom(), 0)); + + std::unique_ptr<Orthanc::ImageAccessor> thumbnail; + + if (frame->GetFormat() == Orthanc::PixelFormat_RGB24) + { + thumbnail.reset(Orthanc::ImageProcessing::FitSizeKeepAspectRatio(*frame, width_, height_)); + } + else + { + std::unique_ptr<Orthanc::ImageAccessor> converted( + new Orthanc::Image(Orthanc::PixelFormat_Float32, frame->GetWidth(), frame->GetHeight(), false)); + Orthanc::ImageProcessing::Convert(*converted, *frame); + + std::unique_ptr<Orthanc::ImageAccessor> resized( + Orthanc::ImageProcessing::FitSizeKeepAspectRatio(*converted, width_, height_)); + + float minValue, maxValue; + Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *resized); + if (minValue + 0.01f < maxValue) + { + Orthanc::ImageProcessing::ShiftScale(*resized, -minValue, 255.0f / (maxValue - minValue), false); + } + else + { + Orthanc::ImageProcessing::Set(*resized, 0); + } + + converted.reset(NULL); + + thumbnail.reset(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width_, height_, false)); + Orthanc::ImageProcessing::Convert(*thumbnail, *resized); + } + + std::string jpeg; + Orthanc::JpegWriter writer; + writer.SetQuality(JPEG_QUALITY); + writer.WriteToMemory(jpeg, *thumbnail); + + AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(), + info.GetSeriesInstanceUid(), new Thumbnail(jpeg, Orthanc::MIME_JPEG)); + } + } +#endif + + + void SeriesThumbnailsLoader::Handle(const OracleCommandExceptionMessage& message) + { + const OracleCommandBase& command = dynamic_cast<const OracleCommandBase&>(message.GetOrigin()); + assert(command.HasPayload()); + + if (command.GetType() == IOracleCommand::Type_GetOrthancImage) + { + // This is presumably a HTTP status 301 (Moved permanently) + // because of an unsupported DICOM file in "/preview" + const ThumbnailInformation& info = dynamic_cast<const ThumbnailInformation&>(command.GetPayload()); + AcquireThumbnail(info.GetDicomSource(), info.GetStudyInstanceUid(), + info.GetSeriesInstanceUid(), new Thumbnail(SeriesThumbnailType_Unsupported)); + } + else + { + dynamic_cast<Handler&>(command.GetPayload()).HandleError(); + } + } + + + SeriesThumbnailsLoader::SeriesThumbnailsLoader(ILoadersContext& context, + int priority) : + context_(context), + priority_(priority), + width_(128), + height_(128) + { + } + + + boost::shared_ptr<SeriesThumbnailsLoader> SeriesThumbnailsLoader::Create(ILoadersContext::ILock& stone, + int priority) + { + boost::shared_ptr<SeriesThumbnailsLoader> result(new SeriesThumbnailsLoader(stone.GetContext(), priority)); + result->Register<GetOrthancImageCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); + result->Register<HttpCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); + result->Register<OracleCommandExceptionMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); + result->Register<OrthancRestApiCommand::SuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); + +#if ORTHANC_ENABLE_DCMTK == 1 + result->Register<ParseDicomSuccessMessage>(stone.GetOracleObservable(), &SeriesThumbnailsLoader::Handle); +#endif + + return result; + } + + + void SeriesThumbnailsLoader::SetThumbnailSize(unsigned int width, + unsigned int height) + { + if (width <= 0 || + height <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + width_ = width; + height_ = height; + } + } + + + void SeriesThumbnailsLoader::Clear() + { + for (Thumbnails::iterator it = thumbnails_.begin(); it != thumbnails_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + + thumbnails_.clear(); + } + + + SeriesThumbnailType SeriesThumbnailsLoader::GetSeriesThumbnail(std::string& image, + std::string& mime, + const std::string& seriesInstanceUid) const + { + Thumbnails::const_iterator found = thumbnails_.find(seriesInstanceUid); + + if (found == thumbnails_.end()) + { + return SeriesThumbnailType_NotLoaded; + } + else + { + assert(found->second != NULL); + image.assign(found->second->GetImage()); + mime.assign(found->second->GetMime()); + return found->second->GetType(); + } + } + + + void SeriesThumbnailsLoader::ScheduleLoadThumbnail(const DicomSource& source, + const std::string& patientId, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) + { + if (IsScheduledSeries(seriesInstanceUid)) + { + return; + } + + if (source.IsDicomWeb()) + { + if (!source.HasDicomWebRendered()) + { +#if ORTHANC_ENABLE_DCMTK == 1 + // Issue a QIDO-RS request to select one of the instances in the series + std::map<std::string, std::string> arguments, headers; + arguments["0020000D"] = studyInstanceUid; + arguments["0020000E"] = seriesInstanceUid; + arguments["includefield"] = "00080018"; // SOP Instance UID is mandatory + + std::unique_ptr<IOracleCommand> command( + source.CreateDicomWebCommand( + "/instances", arguments, headers, new SelectDicomWebInstanceHandler( + GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid))); + Schedule(command.release()); +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "Stone of Orthanc was built without support to decode DICOM images"); +#endif + } + else + { + const std::string uri = ("/studies/" + studyInstanceUid + + "/series/" + seriesInstanceUid + "/rendered"); + + std::map<std::string, std::string> arguments, headers; + arguments["viewport"] = (boost::lexical_cast<std::string>(width_) + "," + + boost::lexical_cast<std::string>(height_)); + + // Needed to set this header explicitly, as long as emscripten + // does not include macro "EMSCRIPTEN_FETCH_RESPONSE_HEADERS" + // https://github.com/emscripten-core/emscripten/pull/8486 + headers["Accept"] = Orthanc::MIME_JPEG; + + std::unique_ptr<IOracleCommand> command( + source.CreateDicomWebCommand( + uri, arguments, headers, new DicomWebThumbnailHandler( + GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid))); + Schedule(command.release()); + } + + scheduledSeries_.insert(seriesInstanceUid); + } + else if (source.IsOrthanc()) + { + // Dummy SOP Instance UID, as we are working at the "series" level + Orthanc::DicomInstanceHasher hasher(patientId, studyInstanceUid, seriesInstanceUid, "dummy"); + + std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); + command->SetUri("/series/" + hasher.HashSeries()); + command->AcquirePayload(new SelectOrthancInstanceHandler( + GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid)); + Schedule(command.release()); + + scheduledSeries_.insert(seriesInstanceUid); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "Can only load thumbnails from Orthanc or DICOMweb"); + } + } +}