Mercurial > hg > orthanc-stone
diff Framework/Loaders/SeriesThumbnailsLoader.cpp @ 1484:121d01aa328e
SeriesThumbnailsLoader working on raw dicom files
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 22 Jun 2020 17:46:40 +0200 |
parents | 4db187d29731 |
children | b931ddbe070e |
line wrap: on
line diff
--- a/Framework/Loaders/SeriesThumbnailsLoader.cpp Sat Jun 20 11:16:55 2020 +0200 +++ b/Framework/Loaders/SeriesThumbnailsLoader.cpp Mon Jun 22 17:46:40 2020 +0200 @@ -21,8 +21,13 @@ #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> @@ -30,6 +35,12 @@ #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 @@ -111,7 +122,7 @@ assert(thumbnail != NULL); std::unique_ptr<Thumbnail> protection(thumbnail); - + Thumbnails::iterator found = thumbnails_.find(seriesInstanceUid); if (found == thumbnails_.end()) { @@ -120,10 +131,21 @@ else { assert(found->second != NULL); - delete found->second; - found->second = protection.release(); + 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); } @@ -284,7 +306,7 @@ std::map<std::string, std::string> arguments, headers; arguments["0020000D"] = GetStudyInstanceUid(); arguments["0020000E"] = GetSeriesInstanceUid(); - arguments["includefield"] = "00080016"; + arguments["includefield"] = "00080016"; // SOP Class UID std::unique_ptr<IOracleCommand> command( GetSource().CreateDicomWebCommand( @@ -419,6 +441,52 @@ }; +#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()); @@ -443,6 +511,7 @@ 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_)); @@ -451,12 +520,91 @@ writer.SetQuality(JPEG_QUALITY); writer.WriteToMemory(jpeg, *resized); - const ThumbnailInformation& info = dynamic_cast<ThumbnailInformation&>(message.GetOrigin().GetPayload()); 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::FitSize(*frame, width_, height_)); + } + else + { + const unsigned int width = frame->GetWidth(); + const unsigned int height = frame->GetHeight(); + + std::unique_ptr<Orthanc::ImageAccessor> converted( + new Orthanc::Image(Orthanc::PixelFormat_Float32, width, height, false)); + Orthanc::ImageProcessing::Convert(*converted, *frame); + + std::unique_ptr<Orthanc::ImageAccessor> resized( + Orthanc::ImageProcessing::FitSize(*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()); @@ -495,6 +643,11 @@ 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; } @@ -556,33 +709,48 @@ { return; } - + if (source.IsDicomWeb()) { if (!source.HasDicomWebRendered()) { - // TODO - Could use DCMTK here - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, - "DICOMweb server is not able to generate renderings of DICOM series"); +#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 } - - const std::string uri = ("/studies/" + studyInstanceUid + - "/series/" + seriesInstanceUid + "/rendered"); + 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_)); + 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; + // 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( - shared_from_this(), source, studyInstanceUid, seriesInstanceUid))); - Schedule(command.release()); + std::unique_ptr<IOracleCommand> command( + source.CreateDicomWebCommand( + uri, arguments, headers, new DicomWebThumbnailHandler( + GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid))); + Schedule(command.release()); + } scheduledSeries_.insert(seriesInstanceUid); } @@ -594,7 +762,7 @@ std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand); command->SetUri("/series/" + hasher.HashSeries()); command->AcquirePayload(new SelectOrthancInstanceHandler( - shared_from_this(), source, studyInstanceUid, seriesInstanceUid)); + GetSharedObserver(), source, studyInstanceUid, seriesInstanceUid)); Schedule(command.release()); scheduledSeries_.insert(seriesInstanceUid);