# HG changeset patch # User Sebastien Jodogne # Date 1539946238 -7200 # Node ID b3b3fa0e3689e6214374914760274ed8c6092da3 # Parent c4d4213f095ce35f6565d6fa6d092503b4d6f8e2 BitmapStack diff -r c4d4213f095c -r b3b3fa0e3689 Applications/Samples/CMakeLists.txt --- a/Applications/Samples/CMakeLists.txt Thu Oct 18 20:07:09 2018 +0200 +++ b/Applications/Samples/CMakeLists.txt Fri Oct 19 12:50:38 2018 +0200 @@ -8,6 +8,7 @@ include(../../Resources/CMake/OrthancStoneParameters.cmake) +#set(ENABLE_DCMTK ON) set(ENABLE_SDL OFF CACHE BOOL "Target SDL Native application") set(ENABLE_QT OFF CACHE BOOL "Target Qt Native application") diff -r c4d4213f095c -r b3b3fa0e3689 Applications/Samples/SingleFrameEditorApplication.h --- a/Applications/Samples/SingleFrameEditorApplication.h Thu Oct 18 20:07:09 2018 +0200 +++ b/Applications/Samples/SingleFrameEditorApplication.h Fri Oct 19 12:50:38 2018 +0200 @@ -25,22 +25,215 @@ #include "../../Framework/Layers/OrthancFrameLayerSource.h" +#include +#include #include +#include +#include namespace OrthancStone { - - class GrayscaleBitmapStack : - public WorldSceneWidget, + class BitmapStack : public IObserver, public IObservable { public: - typedef OriginMessage GeometryChangedMessage; - typedef OriginMessage ContentChangedMessage; + typedef OriginMessage GeometryChangedMessage; + typedef OriginMessage ContentChangedMessage; private: + class Bitmap : public boost::noncopyable + { + private: + std::string uuid_; // TODO is this necessary? + std::auto_ptr source_; + std::auto_ptr converted_; // Float32 or RGB24 + std::auto_ptr alpha_; // Grayscale8 (if any) + std::auto_ptr converter_; + + void ApplyConverter() + { + if (source_.get() != NULL && + converter_.get() != NULL) + { + printf("CONVERTED!\n"); + converted_.reset(converter_->ConvertFrame(*source_)); + } + } + + public: + Bitmap(const std::string& uuid) : + uuid_(uuid) + { + } + + void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset) + { + converter_.reset(new DicomFrameConverter); + converter_->ReadParameters(dataset); + ApplyConverter(); + } + + void SetSourceImage(Orthanc::ImageAccessor* image) // Takes ownership + { + source_.reset(image); + ApplyConverter(); + } + + bool GetDefaultWindowing(float& center, + float& width) const + { + if (converter_.get() != NULL && + converter_->HasDefaultWindow()) + { + center = static_cast(converter_->GetDefaultWindowCenter()); + width = static_cast(converter_->GetDefaultWindowWidth()); + } + } + }; + + + typedef std::map Bitmaps; + OrthancApiClient& orthanc_; + bool hasWindowing_; + float windowingCenter_; + float windowingWidth_; + Bitmaps bitmaps_; + + public: + BitmapStack(MessageBroker& broker, + OrthancApiClient& orthanc) : + IObserver(broker), + IObservable(broker), + orthanc_(orthanc), + hasWindowing_(false), + windowingCenter_(0), // Dummy initialization + windowingWidth_(0) // Dummy initialization + { + } + + + virtual ~BitmapStack() + { + for (Bitmaps::iterator it = bitmaps_.begin(); it != bitmaps_.end(); it++) + { + assert(it->second != NULL); + delete it->second; + } + } + + + std::string LoadFrame(const std::string& instance, + unsigned int frame, + bool httpCompression) + { + std::string uuid; + + for (;;) + { + uuid = Orthanc::Toolbox::GenerateUuid(); + if (bitmaps_.find(uuid) == bitmaps_.end()) + { + break; + } + } + + bitmaps_[uuid] = new Bitmap(uuid); + + + { + IWebService::Headers headers; + std::string uri = "/instances/" + instance + "/tags"; + orthanc_.GetBinaryAsync(uri, headers, + new Callable + (*this, &BitmapStack::OnTagsReceived), NULL, + new Orthanc::SingleValueObject(uuid)); + } + + { + IWebService::Headers headers; + headers["Accept"] = "image/x-portable-arbitrarymap"; + + if (httpCompression) + { + headers["Accept-Encoding"] = "gzip"; + } + + std::string uri = "/instances/" + instance + "/frames/" + boost::lexical_cast(frame) + "/image-uint16"; + orthanc_.GetBinaryAsync(uri, headers, + new Callable + (*this, &BitmapStack::OnFrameReceived), NULL, + new Orthanc::SingleValueObject(uuid)); + } + + return uuid; + } + + + void OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) + { + const std::string& uuid = dynamic_cast*>(message.Payload)->GetValue(); + + printf("JSON received: [%s] (%d bytes) for bitmap %s\n", + message.Uri.c_str(), message.AnswerSize, uuid.c_str()); + + Bitmaps::iterator bitmap = bitmaps_.find(uuid); + if (bitmap != bitmaps_.end()) + { + assert(bitmap->second != NULL); + + OrthancPlugins::FullOrthancDataset dicom(message.Answer, message.AnswerSize); + bitmap->second->SetDicomTags(dicom); + + float c, w; + if (!hasWindowing_ && + bitmap->second->GetDefaultWindowing(c, w)) + { + hasWindowing_ = true; + windowingCenter_ = c; + windowingWidth_ = w; + } + + EmitMessage(GeometryChangedMessage(*this)); + } + } + + + void OnFrameReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) + { + const std::string& uuid = dynamic_cast*>(message.Payload)->GetValue(); + + printf("Frame received: [%s] (%d bytes) for bitmap %s\n", message.Uri.c_str(), message.AnswerSize, uuid.c_str()); + + Bitmaps::iterator bitmap = bitmaps_.find(uuid); + if (bitmap != bitmaps_.end()) + { + assert(bitmap->second != NULL); + + std::string content; + if (message.AnswerSize > 0) + { + content.assign(reinterpret_cast(message.Answer), message.AnswerSize); + } + + std::auto_ptr reader(new Orthanc::PamReader); + reader->ReadFromMemory(content); + bitmap->second->SetSourceImage(reader.release()); + + EmitMessage(ContentChangedMessage(*this)); + } + } + }; + + + class BitmapStackWidget : + public WorldSceneWidget, + public IObservable, + public IObserver + { + private: + BitmapStack& stack_; protected: virtual Extent2D GetSceneExtent() @@ -55,27 +248,29 @@ } public: - GrayscaleBitmapStack(MessageBroker& broker, - OrthancApiClient& orthanc, - const std::string& name) : + BitmapStackWidget(MessageBroker& broker, + BitmapStack& stack, + const std::string& name) : WorldSceneWidget(name), + IObservable(broker), IObserver(broker), - IObservable(broker), - orthanc_(orthanc) + stack_(stack) { + stack.RegisterObserverCallback(new Callable(*this, &BitmapStackWidget::OnGeometryChanged)); + stack.RegisterObserverCallback(new Callable(*this, &BitmapStackWidget::OnContentChanged)); } - void LoadDicom(const std::string& dicom) + void OnGeometryChanged(const BitmapStack::GeometryChangedMessage& message) { - orthanc_.GetBinaryAsync("/instances/" + dicom + "/file", "application/dicom", - new Callable(*this, &GrayscaleBitmapStack::OnDicomReceived)); - } - - void OnDicomReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) - { - printf("DICOM received: [%s] (%d bytes)\n", message.Uri.c_str(), message.AnswerSize); + printf("Geometry has changed\n"); + FitContent(); } + void OnContentChanged(const BitmapStack::ContentChangedMessage& message) + { + printf("Content has changed\n"); + NotifyContentChanged(); + } }; @@ -197,13 +392,9 @@ } }; - void OnGeometryChanged(const GrayscaleBitmapStack::GeometryChangedMessage& message) - { - mainWidget_->FitContent(); - } - std::auto_ptr mainWidgetInteractor_; std::auto_ptr orthancApiClient_; + std::auto_ptr stack_; Tools currentTool_; const OrthancFrameLayerSource* source_; unsigned int slice_; @@ -251,15 +442,11 @@ orthancApiClient_.reset(new OrthancApiClient(IObserver::broker_, context_->GetWebService())); + stack_.reset(new BitmapStack(IObserver::broker_, *orthancApiClient_)); + stack_->LoadFrame(instance, frame, false); + stack_->LoadFrame(instance, frame, false); - mainWidget_ = new GrayscaleBitmapStack(broker_, *orthancApiClient_, "main-widget"); - dynamic_cast(mainWidget_)->LoadDicom(instance); - - dynamic_cast(mainWidget_)->RegisterObserverCallback( - new Callable - (*this, &SingleFrameEditorApplication::OnGeometryChanged)); - + mainWidget_ = new BitmapStackWidget(IObserver::broker_, *stack_, "main-widget"); mainWidget_->SetTransmitMouseOver(true); mainWidgetInteractor_.reset(new Interactor(*this)); diff -r c4d4213f095c -r b3b3fa0e3689 Framework/Layers/GrayscaleFrameRenderer.cpp --- a/Framework/Layers/GrayscaleFrameRenderer.cpp Thu Oct 18 20:07:09 2018 +0200 +++ b/Framework/Layers/GrayscaleFrameRenderer.cpp Fri Oct 19 12:50:38 2018 +0200 @@ -27,6 +27,8 @@ { CairoSurface* GrayscaleFrameRenderer::GenerateDisplay(const RenderStyle& style) { + assert(frame_->GetFormat() == Orthanc::PixelFormat_Float32); + std::auto_ptr result; float windowCenter, windowWidth; @@ -126,7 +128,7 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - converter.ConvertFrame(frame_); + converter.ConvertFrameInplace(frame_); assert(frame_.get() != NULL); if (frame_->GetFormat() != Orthanc::PixelFormat_Float32) diff -r c4d4213f095c -r b3b3fa0e3689 Framework/Toolbox/DicomFrameConverter.cpp --- a/Framework/Toolbox/DicomFrameConverter.cpp Thu Oct 18 20:07:09 2018 +0200 +++ b/Framework/Toolbox/DicomFrameConverter.cpp Fri Oct 19 12:50:38 2018 +0200 @@ -30,6 +30,19 @@ namespace OrthancStone { + static const Orthanc::DicomTag IMAGE_TAGS[] = + { + Orthanc::DICOM_TAG_BITS_STORED, + Orthanc::DICOM_TAG_DOSE_GRID_SCALING, + Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION, + Orthanc::DICOM_TAG_PIXEL_REPRESENTATION, + Orthanc::DICOM_TAG_RESCALE_INTERCEPT, + Orthanc::DICOM_TAG_RESCALE_SLOPE, + Orthanc::DICOM_TAG_WINDOW_CENTER, + Orthanc::DICOM_TAG_WINDOW_WIDTH + }; + + void DicomFrameConverter::SetDefaultParameters() { isSigned_ = true; @@ -37,6 +50,7 @@ hasRescale_ = false; rescaleIntercept_ = 0; rescaleSlope_ = 1; + hasDefaultWindow_ = false; defaultWindowCenter_ = 128; defaultWindowWidth_ = 256; expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; @@ -53,6 +67,7 @@ c.size() > 0 && w.size() > 0) { + hasDefaultWindow_ = true; defaultWindowCenter_ = static_cast(c[0]); defaultWindowWidth_ = static_cast(w[0]); } @@ -139,8 +154,27 @@ } } + + void DicomFrameConverter::ReadParameters(const OrthancPlugins::IDicomDataset& dicom) + { + Orthanc::DicomMap converted; - void DicomFrameConverter::ConvertFrame(std::auto_ptr& source) const + for (size_t i = 0; i < sizeof(IMAGE_TAGS) / sizeof(Orthanc::DicomTag); i++) + { + OrthancPlugins::DicomTag tag(IMAGE_TAGS[i].GetGroup(), IMAGE_TAGS[i].GetElement()); + + std::string value; + if (dicom.GetStringValue(value, tag)) + { + converted.SetValue(IMAGE_TAGS[i], value, false); + } + } + + ReadParameters(converted); + } + + + void DicomFrameConverter::ConvertFrameInplace(std::auto_ptr& source) const { assert(sizeof(float) == 4); @@ -149,7 +183,24 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - Orthanc::PixelFormat sourceFormat = source->GetFormat(); + if (source->GetFormat() == GetExpectedPixelFormat() && + source->GetFormat() == Orthanc::PixelFormat_RGB24) + { + // No conversion has to be done, check out (*) + return; + } + else + { + source.reset(ConvertFrame(*source)); + } + } + + + Orthanc::ImageAccessor* DicomFrameConverter::ConvertFrame(const Orthanc::ImageAccessor& source) const + { + assert(sizeof(float) == 4); + + Orthanc::PixelFormat sourceFormat = source.GetFormat(); if (sourceFormat != GetExpectedPixelFormat()) { @@ -158,27 +209,32 @@ if (sourceFormat == Orthanc::PixelFormat_RGB24) { - // No conversion has to be done - return; + // This is the case of a color image. No conversion has to be done (*) + std::auto_ptr converted(new Orthanc::Image(Orthanc::PixelFormat_RGB24, + source.GetWidth(), + source.GetHeight(), + false)); + Orthanc::ImageProcessing::Copy(*converted, source); + return converted.release(); } - - assert(sourceFormat == Orthanc::PixelFormat_Grayscale16 || - sourceFormat == Orthanc::PixelFormat_Grayscale32 || - sourceFormat == Orthanc::PixelFormat_SignedGrayscale16); + else + { + assert(sourceFormat == Orthanc::PixelFormat_Grayscale16 || + sourceFormat == Orthanc::PixelFormat_Grayscale32 || + sourceFormat == Orthanc::PixelFormat_SignedGrayscale16); - // This is the case of a grayscale frame. Convert it to Float32. - std::auto_ptr converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, - source->GetWidth(), - source->GetHeight(), - false)); - Orthanc::ImageProcessing::Convert(*converted, *source); + // This is the case of a grayscale frame. Convert it to Float32. + std::auto_ptr converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, + source.GetWidth(), + source.GetHeight(), + false)); + Orthanc::ImageProcessing::Convert(*converted, source); - source.reset(NULL); // We don't need the source frame anymore - - // Correct rescale slope/intercept if need be - ApplyRescale(*converted, sourceFormat != Orthanc::PixelFormat_Grayscale32); + // Correct rescale slope/intercept if need be + ApplyRescale(*converted, sourceFormat != Orthanc::PixelFormat_Grayscale32); - source = converted; + return converted.release(); + } } diff -r c4d4213f095c -r b3b3fa0e3689 Framework/Toolbox/DicomFrameConverter.h --- a/Framework/Toolbox/DicomFrameConverter.h Thu Oct 18 20:07:09 2018 +0200 +++ b/Framework/Toolbox/DicomFrameConverter.h Fri Oct 19 12:50:38 2018 +0200 @@ -21,6 +21,7 @@ #pragma once +#include #include #include @@ -43,6 +44,7 @@ bool hasRescale_; double rescaleIntercept_; double rescaleSlope_; + bool hasDefaultWindow_; double defaultWindowCenter_; double defaultWindowWidth_; @@ -69,6 +71,13 @@ void ReadParameters(const Orthanc::DicomMap& dicom); + void ReadParameters(const OrthancPlugins::IDicomDataset& dicom); + + bool HasDefaultWindow() const + { + return hasDefaultWindow_; + } + double GetDefaultWindowCenter() const { return defaultWindowCenter_; @@ -89,7 +98,9 @@ return rescaleSlope_; } - void ConvertFrame(std::auto_ptr& source) const; + void ConvertFrameInplace(std::auto_ptr& source) const; + + Orthanc::ImageAccessor* ConvertFrame(const Orthanc::ImageAccessor& source) const; void ApplyRescale(Orthanc::ImageAccessor& image, bool useDouble) const; diff -r c4d4213f095c -r b3b3fa0e3689 Framework/Toolbox/ImageGeometry.cpp --- a/Framework/Toolbox/ImageGeometry.cpp Thu Oct 18 20:07:09 2018 +0200 +++ b/Framework/Toolbox/ImageGeometry.cpp Fri Oct 19 12:50:38 2018 +0200 @@ -341,6 +341,24 @@ } break; + case Orthanc::PixelFormat_Float32: + switch (interpolation) + { + case ImageInterpolation_Nearest: + ApplyAffineInternal(target, source, a); + break; + + case ImageInterpolation_Bilinear: + ApplyAffineInternal(target, source, a); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + break; + case Orthanc::PixelFormat_RGB24: switch (interpolation) { @@ -539,6 +557,24 @@ } break; + case Orthanc::PixelFormat_Float32: + switch (interpolation) + { + case ImageInterpolation_Nearest: + ApplyProjectiveInternal(target, source, a, inva); + break; + + case ImageInterpolation_Bilinear: + ApplyProjectiveInternal(target, source, a, inva); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + break; + case Orthanc::PixelFormat_RGB24: switch (interpolation) { diff -r c4d4213f095c -r b3b3fa0e3689 Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Thu Oct 18 20:07:09 2018 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Fri Oct 19 12:50:38 2018 +0200 @@ -22,6 +22,12 @@ ## Configure the Orthanc Framework ##################################################################### +if (ENABLE_DCMTK) + set(ENABLE_LOCALE ON) +else() + set(ENABLE_LOCALE OFF) # Disable support for locales (notably in Boost) +endif() + include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake) include_directories(${ORTHANC_ROOT}) diff -r c4d4213f095c -r b3b3fa0e3689 Resources/CMake/OrthancStoneParameters.cmake --- a/Resources/CMake/OrthancStoneParameters.cmake Thu Oct 18 20:07:09 2018 +0200 +++ b/Resources/CMake/OrthancStoneParameters.cmake Fri Oct 19 12:50:38 2018 +0200 @@ -25,7 +25,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/../../Resources/Orthanc/DownloadOrthancFramework.cmake) include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake) -set(ENABLE_LOCALE OFF) # Disable support for locales (notably in Boost) +set(ENABLE_DCMTK OFF) set(ENABLE_GOOGLE_TEST ON) set(ENABLE_SQLITE OFF) set(ENABLE_JPEG ON)