# HG changeset patch # User Sebastien Jodogne # Date 1574259860 -3600 # Node ID 177e7d431cd171a2cf0aeac1d69936035aa88805 # Parent 3c7cdbf32e2ac94054192d7ade1a3eebb7ee58ce log scale in textures, remove redundant code for LUTs diff -r 3c7cdbf32e2a -r 177e7d431cd1 Framework/Scene2D/FloatTextureSceneLayer.cpp --- a/Framework/Scene2D/FloatTextureSceneLayer.cpp Wed Nov 20 14:12:11 2019 +0100 +++ b/Framework/Scene2D/FloatTextureSceneLayer.cpp Wed Nov 20 15:24:20 2019 +0100 @@ -131,6 +131,7 @@ cloned->customCenter_ = customCenter_; cloned->customWidth_ = customWidth_; cloned->inverted_ = inverted_; + cloned->applyLog_ = applyLog_; return cloned.release(); } diff -r 3c7cdbf32e2a -r 177e7d431cd1 Framework/Scene2D/GrayscaleStyleConfigurator.cpp --- a/Framework/Scene2D/GrayscaleStyleConfigurator.cpp Wed Nov 20 14:12:11 2019 +0100 +++ b/Framework/Scene2D/GrayscaleStyleConfigurator.cpp Wed Nov 20 15:24:20 2019 +0100 @@ -27,6 +27,17 @@ namespace OrthancStone { + GrayscaleStyleConfigurator::GrayscaleStyleConfigurator() : + revision_(0), + linearInterpolation_(false), + hasWindowing_(false), + customWindowWidth_(0), + customWindowCenter_(0), + inverted_(false), + applyLog_(false) + { + } + void GrayscaleStyleConfigurator::SetWindowing(ImageWindowing windowing) { hasWindowing_ = true; diff -r 3c7cdbf32e2a -r 177e7d431cd1 Framework/Scene2D/GrayscaleStyleConfigurator.h --- a/Framework/Scene2D/GrayscaleStyleConfigurator.h Wed Nov 20 14:12:11 2019 +0100 +++ b/Framework/Scene2D/GrayscaleStyleConfigurator.h Wed Nov 20 15:24:20 2019 +0100 @@ -42,16 +42,7 @@ bool applyLog_; public: - GrayscaleStyleConfigurator() : - revision_(0), - linearInterpolation_(false), - hasWindowing_(false), - customWindowWidth_(0), - customWindowCenter_(0), - inverted_(false), - applyLog_(false) - { - } + GrayscaleStyleConfigurator(); void SetWindowing(ImageWindowing windowing); diff -r 3c7cdbf32e2a -r 177e7d431cd1 Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp --- a/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Wed Nov 20 14:12:11 2019 +0100 +++ b/Framework/Scene2D/Internals/CairoFloatTextureRenderer.cpp Wed Nov 20 15:24:20 2019 +0100 @@ -53,6 +53,8 @@ target.GetFormat() == Orthanc::PixelFormat_BGRA32 && sizeof(float) == 4); + static const float LOG_NORMALIZATION = 255.0f / log(1.0f + 255.0f); + for (unsigned int y = 0; y < height; y++) { const float* p = reinterpret_cast(source.GetConstRow(y)); @@ -70,6 +72,14 @@ v = 255; } + if (l.IsApplyLog()) + { + // https://theailearner.com/2019/01/01/log-transformation/ + v = LOG_NORMALIZATION * log(1.0f + static_cast(v)); + } + + assert(v >= 0.0f && v <= 255.0f); + uint8_t vv = static_cast(v); if (l.IsInverted()) diff -r 3c7cdbf32e2a -r 177e7d431cd1 Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp --- a/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp Wed Nov 20 14:12:11 2019 +0100 +++ b/Framework/Scene2D/Internals/CairoLookupTableTextureRenderer.cpp Wed Nov 20 15:24:20 2019 +0100 @@ -37,18 +37,6 @@ textureTransform_ = l.GetTransform(); isLinearInterpolation_ = l.IsLinearInterpolation(); - const float a = l.GetMinValue(); - float slope; - - if (l.GetMinValue() >= l.GetMaxValue()) - { - slope = 0; - } - else - { - slope = 256.0f / (l.GetMaxValue() - l.GetMinValue()); - } - const Orthanc::ImageAccessor& source = l.GetTexture(); const unsigned int width = source.GetWidth(); const unsigned int height = source.GetHeight(); @@ -56,60 +44,8 @@ Orthanc::ImageAccessor target; texture_.GetWriteableAccessor(target); - - const std::vector& lut = l.GetLookupTable(); - if (lut.size() != 4 * 256) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - assert(source.GetFormat() == Orthanc::PixelFormat_Float32 && - target.GetFormat() == Orthanc::PixelFormat_BGRA32 && - sizeof(float) == 4); - - for (unsigned int y = 0; y < height; y++) - { - const float* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - - for (unsigned int x = 0; x < width; x++) - { - float v = (*p - a) * slope; - if (v <= 0) - { - v = 0; - } - else if (v >= 255) - { - v = 255; - } - - if (1) //l.IsApplyLog()) - { - // https://theailearner.com/2019/01/01/log-transformation/ - v = 255.0f / log(1.0f + 255.0f * 1.5f) * log(1.0f + static_cast(v)); - if (v <= 0) - { - v = 0; - } - else if (v >= 255) - { - v = 255; - } - } - - uint8_t vv = static_cast(v); - - q[0] = lut[4 * vv + 2]; // B - q[1] = lut[4 * vv + 1]; // G - q[2] = lut[4 * vv + 0]; // R - q[3] = lut[4 * vv + 3]; // A - - p++; - q += 4; - } - } - + l.Render(target); + cairo_surface_mark_dirty(texture_.GetObject()); } diff -r 3c7cdbf32e2a -r 177e7d431cd1 Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp --- a/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp Wed Nov 20 14:12:11 2019 +0100 +++ b/Framework/Scene2D/Internals/OpenGLFloatTextureRenderer.cpp Wed Nov 20 15:24:20 2019 +0100 @@ -21,6 +21,8 @@ #include "OpenGLFloatTextureRenderer.h" +#include + namespace OrthancStone { namespace Internals @@ -32,6 +34,11 @@ { if (loadTexture) { + if (layer.IsApplyLog()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + context_.MakeCurrent(); texture_.reset(new OpenGLFloatTextureProgram::Data( context_, layer.GetTexture(), layer.IsLinearInterpolation())); diff -r 3c7cdbf32e2a -r 177e7d431cd1 Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp --- a/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp Wed Nov 20 14:12:11 2019 +0100 +++ b/Framework/Scene2D/Internals/OpenGLLookupTableTextureRenderer.cpp Wed Nov 20 15:24:20 2019 +0100 @@ -36,74 +36,14 @@ const unsigned int width = source.GetWidth(); const unsigned int height = source.GetHeight(); - if ((texture_.get() == NULL) || - (texture_->GetWidth() != width) || - (texture_->GetHeight() != height)) + if (texture_.get() == NULL || + texture_->GetWidth() != width || + texture_->GetHeight() != height) { - - texture_.reset(new Orthanc::Image( - Orthanc::PixelFormat_RGBA32, - width, - height, - false)); + texture_.reset(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, width, height, false)); } - { - - const float a = layer.GetMinValue(); - float slope = 0; - - if (layer.GetMinValue() >= layer.GetMaxValue()) - { - slope = 0; - } - else - { - slope = 256.0f / (layer.GetMaxValue() - layer.GetMinValue()); - } - - Orthanc::ImageAccessor target; - texture_->GetWriteableAccessor(target); - - const std::vector& lut = layer.GetLookupTable(); - if (lut.size() != 4 * 256) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - assert(source.GetFormat() == Orthanc::PixelFormat_Float32 && - target.GetFormat() == Orthanc::PixelFormat_RGBA32 && - sizeof(float) == 4); - - for (unsigned int y = 0; y < height; y++) - { - const float* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - - for (unsigned int x = 0; x < width; x++) - { - float v = (*p - a) * slope; - if (v <= 0) - { - v = 0; - } - else if (v >= 255) - { - v = 255; - } - - uint8_t vv = static_cast(v); - - q[0] = lut[4 * vv + 0]; // R - q[1] = lut[4 * vv + 1]; // G - q[2] = lut[4 * vv + 2]; // B - q[3] = lut[4 * vv + 3]; // A - - p++; - q += 4; - } - } - } + layer.Render(*texture_); context_.MakeCurrent(); glTexture_.reset(new OpenGL::OpenGLTexture(context_)); diff -r 3c7cdbf32e2a -r 177e7d431cd1 Framework/Scene2D/LookupTableStyleConfigurator.cpp --- a/Framework/Scene2D/LookupTableStyleConfigurator.cpp Wed Nov 20 14:12:11 2019 +0100 +++ b/Framework/Scene2D/LookupTableStyleConfigurator.cpp Wed Nov 20 15:24:20 2019 +0100 @@ -39,7 +39,8 @@ LookupTableStyleConfigurator::LookupTableStyleConfigurator() : revision_(0), hasLut_(false), - hasRange_(false) + hasRange_(false), + applyLog_(false) { } @@ -82,6 +83,12 @@ } } + void LookupTableStyleConfigurator::SetApplyLog(bool apply) + { + applyLog_ = apply; + revision_++; + } + TextureBaseSceneLayer* LookupTableStyleConfigurator::CreateTextureFromImage(const Orthanc::ImageAccessor& image) const { throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); @@ -104,5 +111,7 @@ { l.FitRange(); } + + l.SetApplyLog(applyLog_); } } diff -r 3c7cdbf32e2a -r 177e7d431cd1 Framework/Scene2D/LookupTableStyleConfigurator.h --- a/Framework/Scene2D/LookupTableStyleConfigurator.h Wed Nov 20 14:12:11 2019 +0100 +++ b/Framework/Scene2D/LookupTableStyleConfigurator.h Wed Nov 20 15:24:20 2019 +0100 @@ -39,6 +39,7 @@ bool hasRange_; float minValue_; float maxValue_; + bool applyLog_; public: LookupTableStyleConfigurator(); @@ -55,6 +56,13 @@ void SetRange(float minValue, float maxValue); + void SetApplyLog(bool apply); + + bool IsApplyLog() const + { + return applyLog_; + } + virtual uint64_t GetRevision() const { return revision_; @@ -63,7 +71,7 @@ virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const; virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame, - const DicomInstanceParameters& parameters) const + const DicomInstanceParameters& parameters) const { return parameters.CreateLookupTableTexture(frame); } diff -r 3c7cdbf32e2a -r 177e7d431cd1 Framework/Scene2D/LookupTableTextureSceneLayer.cpp --- a/Framework/Scene2D/LookupTableTextureSceneLayer.cpp Wed Nov 20 14:12:11 2019 +0100 +++ b/Framework/Scene2D/LookupTableTextureSceneLayer.cpp Wed Nov 20 15:24:20 2019 +0100 @@ -132,6 +132,12 @@ } } + void LookupTableTextureSceneLayer::SetApplyLog(bool apply) + { + applyLog_ = apply; + IncrementRevision(); + } + void LookupTableTextureSceneLayer::FitRange() { Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue_, maxValue_, GetTexture()); @@ -158,4 +164,147 @@ return cloned.release(); } + + + // Templatized function to speed up computations, by avoiding + // testing conditions on each pixel + template + static void RenderInternal(Orthanc::ImageAccessor& target, + const Orthanc::ImageAccessor& source, + float minValue, + float slope, + const std::vector& lut) + { + static const float LOG_NORMALIZATION = 255.0f / log(1.0f + 255.0f); + + const unsigned int width = source.GetWidth(); + const unsigned int height = source.GetHeight(); + + for (unsigned int y = 0; y < height; y++) + { + const float* p = reinterpret_cast(source.GetConstRow(y)); + uint8_t* q = reinterpret_cast(target.GetRow(y)); + + for (unsigned int x = 0; x < width; x++) + { + float v = (*p - minValue) * slope; + if (v <= 0) + { + v = 0; + } + else if (v >= 255) + { + v = 255; + } + + if (IsApplyLog) + { + // https://theailearner.com/2019/01/01/log-transformation/ + v = LOG_NORMALIZATION * log(1.0f + static_cast(v)); + } + + assert(v >= 0.0f && v <= 255.0f); + + uint8_t vv = static_cast(v); + + switch (TargetFormat) + { + case Orthanc::PixelFormat_BGRA32: + // For Cairo surfaces + q[0] = lut[4 * vv + 2]; // B + q[1] = lut[4 * vv + 1]; // G + q[2] = lut[4 * vv + 0]; // R + q[3] = lut[4 * vv + 3]; // A + break; + + case Orthanc::PixelFormat_RGBA32: + // For OpenGL + q[0] = lut[4 * vv + 0]; // R + q[1] = lut[4 * vv + 1]; // G + q[2] = lut[4 * vv + 2]; // B + q[3] = lut[4 * vv + 3]; // A + break; + + default: + assert(0); + } + + p++; + q += 4; + } + } + } + + + void LookupTableTextureSceneLayer::Render(Orthanc::ImageAccessor& target) const + { + assert(sizeof(float) == 4); + + if (!HasTexture()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + const Orthanc::ImageAccessor& source = GetTexture(); + + if (source.GetFormat() != Orthanc::PixelFormat_Float32 || + (target.GetFormat() != Orthanc::PixelFormat_RGBA32 && + target.GetFormat() != Orthanc::PixelFormat_BGRA32)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (source.GetWidth() != target.GetWidth() || + source.GetHeight() != target.GetHeight()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + const float minValue = GetMinValue(); + float slope; + + if (GetMinValue() >= GetMaxValue()) + { + slope = 0; + } + else + { + slope = 256.0f / (GetMaxValue() - GetMinValue()); + } + + const std::vector& lut = GetLookupTable(); + if (lut.size() != 4 * 256) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + switch (target.GetFormat()) + { + case Orthanc::PixelFormat_RGBA32: + if (applyLog_) + { + RenderInternal(target, source, minValue, slope, lut); + } + else + { + RenderInternal(target, source, minValue, slope, lut); + } + break; + + case Orthanc::PixelFormat_BGRA32: + if (applyLog_) + { + RenderInternal(target, source, minValue, slope, lut); + } + else + { + RenderInternal(target, source, minValue, slope, lut); + } + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } } diff -r 3c7cdbf32e2a -r 177e7d431cd1 Framework/Scene2D/LookupTableTextureSceneLayer.h --- a/Framework/Scene2D/LookupTableTextureSceneLayer.h Wed Nov 20 14:12:11 2019 +0100 +++ b/Framework/Scene2D/LookupTableTextureSceneLayer.h Wed Nov 20 15:24:20 2019 +0100 @@ -32,6 +32,7 @@ float minValue_; float maxValue_; std::vector lut_; + bool applyLog_; void SetLookupTableRgb(const std::vector& lut); @@ -66,11 +67,22 @@ return lut_; } + void SetApplyLog(bool apply); + + bool IsApplyLog() const + { + return applyLog_; + } + virtual ISceneLayer* Clone() const; virtual Type GetType() const { return Type_LookupTableTexture; } + + // Render the texture to a color image of format BGRA32 (Cairo + // surfaces) or RGBA32 (OpenGL) + void Render(Orthanc::ImageAccessor& target) const; }; }