# HG changeset patch # User Alain Mazy # Date 1574873493 -3600 # Node ID a5f2a6b04a31eac2ddc92a8eb61b0bad50d0a17b # Parent c6a36ecd641da60b5a2c95cc90bd0012a84b5ed2 RadiographyScene: windowing is now only applied to the Dicom layer diff -r c6a36ecd641d -r a5f2a6b04a31 Applications/Samples/SingleFrameEditorApplication.h --- a/Applications/Samples/SingleFrameEditorApplication.h Tue Nov 26 16:32:29 2019 +0100 +++ b/Applications/Samples/SingleFrameEditorApplication.h Wed Nov 27 17:51:33 2019 +0100 @@ -38,7 +38,6 @@ #include "../../Framework/Toolbox/TextRenderer.h" #include -#include #include #include #include @@ -318,11 +317,6 @@ boost::shared_ptr scene(new RadiographyScene(GetBroker())); RadiographySceneReader reader(*scene, context_->GetOrthancApiClient()); - - Orthanc::FontRegistry fontRegistry; - fontRegistry.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); - - reader.SetFontRegistry(fontRegistry); reader.Read(snapshot); widget.SetScene(scene); @@ -493,8 +487,6 @@ std::string instance = parameters["instance"].as(); //int frame = parameters["frame"].as(); - fontRegistry_.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); - scene_.reset(new RadiographyScene(GetBroker())); RadiographyLayer& dicomLayer = scene_->LoadDicomFrame(context->GetOrthancApiClient(), instance, 0, false, NULL); @@ -520,11 +512,12 @@ std::auto_ptr renderedTextAlpha(TextRenderer::Render(Orthanc::EmbeddedResources::UBUNTU_FONT, 100, "%öÇaA&#")); RadiographyLayer& layer = scene_->LoadAlphaBitmap(renderedTextAlpha.release(), NULL); - dynamic_cast(layer).SetForegroundValue(200); + dynamic_cast(layer).SetForegroundValue(200.0f * 256.0f); } { - RadiographyLayer& layer = scene_->LoadText(fontRegistry_.GetFont(0), "Hello\nworld", NULL); + RadiographyTextLayer::SetFont(Orthanc::EmbeddedResources::UBUNTU_FONT); + RadiographyLayer& layer = scene_->LoadText("Hello\nworld", 20, 128, NULL); layer.SetResizeable(true); } diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographyAlphaLayer.cpp --- a/Framework/Radiography/RadiographyAlphaLayer.cpp Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographyAlphaLayer.cpp Wed Nov 27 17:51:33 2019 +0100 @@ -25,6 +25,7 @@ #include #include +#include "../Toolbox/ImageGeometry.h" namespace OrthancStone { @@ -51,7 +52,10 @@ void RadiographyAlphaLayer::Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, - ImageInterpolation interpolation) const + ImageInterpolation interpolation, + float windowCenter, + float windowWidth, + bool applyWindowing) const { if (alpha_.get() == NULL) { @@ -77,27 +81,37 @@ t.Apply(tmp, cropped, interpolation, true /* clear */); - // Blit - const unsigned int width = buffer.GetWidth(); - const unsigned int height = buffer.GetHeight(); + unsigned int x1, y1, x2, y2; + OrthancStone::GetProjectiveTransformExtent(x1, y1, x2, y2, + t.GetHomogeneousMatrix(), + cropped.GetWidth(), + cropped.GetHeight(), + buffer.GetWidth(), + buffer.GetHeight()); float value = foreground_; - if (useWindowing_) + if (!applyWindowing) // if applying the windowing, it means we are ie rendering the image for a realtime visualization -> the foreground_ value is the value we want to see on the screen -> don't change it { - float center, width; - if (GetScene().GetWindowing(center, width)) + // if not applying the windowing, it means ie that we are saving a dicom image to file and the windowing will be applied by a viewer later on -> we want the "foreground" value to be correct once the windowing will be applied + value = windowCenter - windowWidth/2 + (foreground_ / 65535.0f) * windowWidth; + + if (value < 0.0f) { - value = center + width / 2.0f; // set it to the maximum pixel value of the image + value = 0.0f; + } + if (value > 65535.0f) + { + value = 65535.0f; } } - for (unsigned int y = 0; y < height; y++) + for (unsigned int y = y1; y <= y2; y++) { - float *q = reinterpret_cast(buffer.GetRow(y)); - const uint8_t *p = reinterpret_cast(tmp.GetRow(y)); + float *q = reinterpret_cast(buffer.GetRow(y)) + x1; + const uint8_t *p = reinterpret_cast(tmp.GetRow(y)) + x1; - for (unsigned int x = 0; x < width; x++, p++, q++) + for (unsigned int x = x1; x <= x2; x++, p++, q++) { float a = static_cast(*p) / 255.0f; @@ -109,26 +123,19 @@ bool RadiographyAlphaLayer::GetRange(float& minValue, float& maxValue) const { - if (useWindowing_) - { - return false; - } - else - { - minValue = 0; - maxValue = 0; + minValue = 0; + maxValue = 0; - if (foreground_ < 0) - { - minValue = foreground_; - } + if (foreground_ < 0) + { + minValue = foreground_; + } - if (foreground_ > 0) - { - maxValue = foreground_; - } + if (foreground_ > 0) + { + maxValue = foreground_; + } - return true; - } + return true; } } diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographyAlphaLayer.h --- a/Framework/Radiography/RadiographyAlphaLayer.h Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographyAlphaLayer.h Wed Nov 27 17:51:33 2019 +0100 @@ -33,14 +33,12 @@ class RadiographyAlphaLayer : public RadiographyLayer { private: - std::auto_ptr alpha_; // Grayscale8 - bool useWindowing_; - float foreground_; + std::auto_ptr alpha_; // Grayscale8 in the range [0, 255] 0 = transparent, 255 = opaque -> the foreground value will be displayed + float foreground_; // in the range [0.0, 65535.0] public: RadiographyAlphaLayer(MessageBroker& broker, const RadiographyScene& scene) : RadiographyLayer(broker, scene), - useWindowing_(true), foreground_(0) { } @@ -48,7 +46,6 @@ void SetForegroundValue(float foreground) { - useWindowing_ = false; foreground_ = foreground; } @@ -57,11 +54,6 @@ return foreground_; } - bool IsUsingWindowing() const - { - return useWindowing_; - } - void SetAlpha(Orthanc::ImageAccessor* image); virtual bool GetDefaultWindowing(float& center, @@ -73,7 +65,10 @@ virtual void Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, - ImageInterpolation interpolation) const; + ImageInterpolation interpolation, + float windowCenter, + float windowWidth, + bool applyWindowing) const; virtual bool GetRange(float& minValue, float& maxValue) const; diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographyDicomLayer.cpp --- a/Framework/Radiography/RadiographyDicomLayer.cpp Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographyDicomLayer.cpp Wed Nov 27 17:51:33 2019 +0100 @@ -28,6 +28,7 @@ #include #include #include +#include "../Toolbox/ImageGeometry.h" static OrthancPlugins::DicomTag ConvertTag(const Orthanc::DicomTag& tag) { @@ -138,7 +139,10 @@ void RadiographyDicomLayer::Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, - ImageInterpolation interpolation) const + ImageInterpolation interpolation, + float windowCenter, + float windowWidth, + bool applyWindowing) const { if (converted_.get() != NULL) { @@ -158,6 +162,47 @@ converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); t.Apply(buffer, cropped, interpolation, false); + unsigned int x1, y1, x2, y2; + OrthancStone::GetProjectiveTransformExtent(x1, y1, x2, y2, + t.GetHomogeneousMatrix(), + cropped.GetWidth(), + cropped.GetHeight(), + buffer.GetWidth(), + buffer.GetHeight()); + + if (applyWindowing) + { + // apply windowing but stay in the range [0.0, 65535.0] + float w0 = windowCenter - windowWidth / 2.0f; + float w1 = windowCenter + windowWidth / 2.0f; + + if (windowWidth >= 0.001f) // Avoid division by zero at (*) + { + float scaling = 1.0f / (w1 - w0) * 65535.0f; + for (unsigned int y = y1; y <= y2; y++) + { + float* p = reinterpret_cast(buffer.GetRow(y)) + x1; + + for (unsigned int x = x1; x <= x2; x++, p++) + { + if (*p >= w1) + { + *p = 65535.0; + } + else if (*p <= w0) + { + *p = 0; + } + else + { + // https://en.wikipedia.org/wiki/Linear_interpolation + *p = scaling * (*p - w0); // (*) + } + } + } + } + } + } } diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographyDicomLayer.h --- a/Framework/Radiography/RadiographyDicomLayer.h Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographyDicomLayer.h Wed Nov 27 17:51:33 2019 +0100 @@ -91,7 +91,10 @@ virtual void Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, - ImageInterpolation interpolation) const; + ImageInterpolation interpolation, + float windowCenter, + float windowWidth, + bool applyWindowing) const; virtual bool GetDefaultWindowing(float& center, float& width) const; diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographyLayer.h --- a/Framework/Radiography/RadiographyLayer.h Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographyLayer.h Wed Nov 27 17:51:33 2019 +0100 @@ -350,7 +350,10 @@ virtual void Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, - ImageInterpolation interpolation) const = 0; + ImageInterpolation interpolation, + float windowCenter, + float windowWidth, + bool applyWindowing) const = 0; virtual bool GetRange(float& minValue, float& maxValue) const = 0; diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographyMaskLayer.cpp --- a/Framework/Radiography/RadiographyMaskLayer.cpp Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographyMaskLayer.cpp Wed Nov 27 17:51:33 2019 +0100 @@ -26,10 +26,11 @@ #include "Core/Images/Image.h" #include "Core/Images/ImageProcessing.h" #include +#include "../Toolbox/ImageGeometry.h" namespace OrthancStone { - const unsigned char IN_MASK_VALUE = 0x00; + const unsigned char IN_MASK_VALUE = 0x77; const unsigned char OUT_MASK_VALUE = 0xFF; const AffineTransform2D& RadiographyMaskLayer::GetTransform() const @@ -76,7 +77,10 @@ void RadiographyMaskLayer::Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, - ImageInterpolation interpolation) const + ImageInterpolation interpolation, + float windowCenter, + float windowWidth, + bool applyWindowing) const { if (dicomLayer_.GetWidth() == 0) // nothing to do if the DICOM layer is not displayed (or not loaded) return; @@ -108,20 +112,36 @@ Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false); - t.Apply(tmp, cropped, interpolation, true /* clear */); + t.Apply(tmp, cropped, ImageInterpolation_Nearest, true /* clear */); + + unsigned int x1, y1, x2, y2; + OrthancStone::GetProjectiveTransformExtent(x1, y1, x2, y2, + t.GetHomogeneousMatrix(), + cropped.GetWidth(), + cropped.GetHeight(), + buffer.GetWidth(), + buffer.GetHeight()); + + // we have observed vertical lines at the image border (probably due to bilinear filtering of the DICOM image when it is not aligned with the buffer pixels) + // -> draw the mask one line further on each side + if (x1 >= 1) + { + x1 = x1 - 1; + } + if (x2 < buffer.GetWidth() - 2) + { + x2 = x2 + 1; + } // Blit - const unsigned int width = buffer.GetWidth(); - const unsigned int height = buffer.GetHeight(); - - for (unsigned int y = 0; y < height; y++) + for (unsigned int y = y1; y <= y2; y++) { - float *q = reinterpret_cast(buffer.GetRow(y)); - const uint8_t *p = reinterpret_cast(tmp.GetRow(y)); + float *q = reinterpret_cast(buffer.GetRow(y)) + x1; + const uint8_t *p = reinterpret_cast(tmp.GetRow(y)) + x1; - for (unsigned int x = 0; x < width; x++, p++, q++) + for (unsigned int x = x1; x <= x2; x++, p++, q++) { - if (*p == OUT_MASK_VALUE) + if (*p != IN_MASK_VALUE) *q = foreground_; // else keep the underlying pixel value } diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographyMaskLayer.h --- a/Framework/Radiography/RadiographyMaskLayer.h Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographyMaskLayer.h Wed Nov 27 17:51:33 2019 +0100 @@ -76,7 +76,10 @@ virtual void Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, - ImageInterpolation interpolation) const; + ImageInterpolation interpolation, + float windowCenter, + float windowWidth, + bool applyWindowing) const; std::string GetInstanceId() const; diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographyScene.cpp --- a/Framework/Radiography/RadiographyScene.cpp Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographyScene.cpp Wed Nov 27 17:51:33 2019 +0100 @@ -283,7 +283,7 @@ RadiographyLayer& RadiographyScene::LoadText(const std::string& utf8, - size_t fontSize, + unsigned int fontSize, uint8_t foreground, RadiographyLayer::Geometry* geometry) { @@ -509,7 +509,8 @@ void RadiographyScene::Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, - ImageInterpolation interpolation) const + ImageInterpolation interpolation, + bool applyWindowing) const { // Render layers in the background-to-foreground order for (size_t index = 0; index < countLayers_; index++) @@ -518,7 +519,7 @@ if (it != layers_.end()) { assert(it->second != NULL); - it->second->Render(buffer, viewTransform, interpolation); + it->second->Render(buffer, viewTransform, interpolation, windowingCenter_, windowingWidth_, applyWindowing); } } } @@ -599,7 +600,8 @@ double pixelSpacingY, ImageInterpolation interpolation, bool invert, - int64_t maxValue /* for inversion */) + int64_t maxValue /* for inversion */, + bool applyWindowing) { if (pixelSpacingX <= 0 || pixelSpacingY <= 0) @@ -628,7 +630,7 @@ // wipe background before rendering Orthanc::ImageProcessing::Set(layers, 0); - Render(layers, view, interpolation); + Render(layers, view, interpolation, applyWindowing); std::auto_ptr rendered(new Orthanc::Image(Orthanc::PixelFormat_Grayscale16, layers.GetWidth(), layers.GetHeight(), false)); @@ -651,7 +653,7 @@ { LOG(INFO) << "Exporting RadiographyScene to DICOM"; - std::auto_ptr rendered(ExportToImage(pixelSpacingX, pixelSpacingY, interpolation)); // note: we don't invert the image in the pixels data because we'll set the PhotometricDisplayMode correctly in the DICOM tags + std::auto_ptr rendered(ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, false)); // note: we don't invert the image in the pixels data because we'll set the PhotometricDisplayMode correctly in the DICOM tags createDicomRequestContent["Tags"] = dicomTags; diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographyScene.h --- a/Framework/Radiography/RadiographyScene.h Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographyScene.h Wed Nov 27 17:51:33 2019 +0100 @@ -198,7 +198,7 @@ RadiographyPhotometricDisplayMode GetPreferredPhotomotricDisplayMode() const; RadiographyLayer& LoadText(const std::string& utf8, - size_t fontSize, + unsigned int fontSize, uint8_t foreground, RadiographyLayer::Geometry* geometry); @@ -287,7 +287,8 @@ virtual void Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, - ImageInterpolation interpolation) const; + ImageInterpolation interpolation, + bool applyWindowing) const; bool LookupLayer(size_t& index /* out */, double x, @@ -339,15 +340,17 @@ Orthanc::Image* ExportToImage(double pixelSpacingX, double pixelSpacingY, - ImageInterpolation interpolation) + ImageInterpolation interpolation, + bool applyWindowing) { - return ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, false, 0); + return ExportToImage(pixelSpacingX, pixelSpacingY, interpolation, false, 0, applyWindowing); } Orthanc::Image* ExportToImage(double pixelSpacingX, double pixelSpacingY, ImageInterpolation interpolation, bool invert, - int64_t maxValue /* for inversion */); + int64_t maxValue /* for inversion */, + bool applyWindowing); // i.e.: when }; } diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographySceneWriter.cpp --- a/Framework/Radiography/RadiographySceneWriter.cpp Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographySceneWriter.cpp Wed Nov 27 17:51:33 2019 +0100 @@ -97,7 +97,6 @@ Orthanc::Toolbox::EncodeDataUriScheme(pngContentBase64, "image/png", pngContent); output["content"] = pngContentBase64; output["foreground"] = layer.GetForegroundValue(); - output["isUsingWindowing"] = layer.IsUsingWindowing(); } void RadiographySceneWriter::WriteLayer(Json::Value& output, const RadiographyLayer& layer) diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographyTextLayer.cpp --- a/Framework/Radiography/RadiographyTextLayer.cpp Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographyTextLayer.cpp Wed Nov 27 17:51:33 2019 +0100 @@ -30,7 +30,7 @@ Orthanc::EmbeddedResources::FileResourceId RadiographyTextLayer::fontResourceId_; void RadiographyTextLayer::LoadText(const std::string& utf8, - size_t fontSize, + unsigned int fontSize, uint8_t foreground) { if (!fontHasBeenConfigured_) @@ -45,5 +45,6 @@ SetAlpha(TextRenderer::Render(fontResourceId_, fontSize_, text_)); + SetForegroundValue(foreground * 256.0f); } } diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographyTextLayer.h --- a/Framework/Radiography/RadiographyTextLayer.h Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographyTextLayer.h Wed Nov 27 17:51:33 2019 +0100 @@ -31,7 +31,7 @@ { private: std::string text_; - size_t fontSize_; + unsigned int fontSize_; uint8_t foreground_; static bool fontHasBeenConfigured_; @@ -42,14 +42,14 @@ { } - void LoadText(const std::string& utf8, size_t fontSize, uint8_t foreground); + void LoadText(const std::string& utf8, unsigned int fontSize, uint8_t foreground); const std::string& GetText() const { return text_; } - size_t GetFontSize() const + unsigned int GetFontSize() const { return fontSize_; } diff -r c6a36ecd641d -r a5f2a6b04a31 Framework/Radiography/RadiographyWidget.cpp --- a/Framework/Radiography/RadiographyWidget.cpp Tue Nov 26 16:32:29 2019 +0100 +++ b/Framework/Radiography/RadiographyWidget.cpp Wed Nov 27 17:51:33 2019 +0100 @@ -70,83 +70,66 @@ unsigned int height, ImageInterpolation interpolation) { - float windowCenter, windowWidth; - scene_->GetWindowingWithDefault(windowCenter, windowWidth); - - float x0 = windowCenter - windowWidth / 2.0f; - float x1 = windowCenter + windowWidth / 2.0f; + if (floatBuffer_.get() == NULL || + floatBuffer_->GetWidth() != width || + floatBuffer_->GetHeight() != height) + { + floatBuffer_.reset(new Orthanc::Image( + Orthanc::PixelFormat_Float32, width, height, false)); + } - if (windowWidth <= 0.001f) // Avoid division by zero at (*) - { - return false; - } - else + if (cairoBuffer_.get() == NULL || + cairoBuffer_->GetWidth() != width || + cairoBuffer_->GetHeight() != height) { - if (floatBuffer_.get() == NULL || - floatBuffer_->GetWidth() != width || - floatBuffer_->GetHeight() != height) - { - floatBuffer_.reset(new Orthanc::Image( - Orthanc::PixelFormat_Float32, width, height, false)); - } + cairoBuffer_.reset(new CairoSurface(width, height, false /* no alpha */)); + } + + RenderBackground(*floatBuffer_, 0.0, 65535.0); + + scene_->Render(*floatBuffer_, GetView().GetMatrix(), interpolation, true); - if (cairoBuffer_.get() == NULL || - cairoBuffer_->GetWidth() != width || - cairoBuffer_->GetHeight() != height) - { - cairoBuffer_.reset(new CairoSurface(width, height, false /* no alpha */)); - } - - RenderBackground(*floatBuffer_, x0, x1); + // Conversion from Float32 to BGRA32 (cairo). Very similar to + // GrayscaleFrameRenderer => TODO MERGE? + Orthanc::ImageAccessor target; + cairoBuffer_->GetWriteableAccessor(target); - scene_->Render(*floatBuffer_, GetView().GetMatrix(), interpolation); - - // Conversion from Float32 to BGRA32 (cairo). Very similar to - // GrayscaleFrameRenderer => TODO MERGE? - - Orthanc::ImageAccessor target; - cairoBuffer_->GetWriteableAccessor(target); - - float scaling = 255.0f / (x1 - x0); + bool invert = IsInvertedInternal(); - bool invert = IsInvertedInternal(); + for (unsigned int y = 0; y < height; y++) + { + const float* p = reinterpret_cast(floatBuffer_->GetConstRow(y)); + uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int y = 0; y < height; y++) + for (unsigned int x = 0; x < width; x++, p++, q += 4) { - const float* p = reinterpret_cast(floatBuffer_->GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - - for (unsigned int x = 0; x < width; x++, p++, q += 4) + uint8_t v = 0; + if (*p >= 65535.0) + { + v = 255; + } + else if (*p <= 0.0) { - uint8_t v = 0; - if (*p >= x1) - { - v = 255; - } - else if (*p <= x0) - { - v = 0; - } - else - { - // https://en.wikipedia.org/wiki/Linear_interpolation - v = static_cast(scaling * (*p - x0)); // (*) - } + v = 0; + } + else + { + v = static_cast(*p / 256.0); + } - if (invert) - { - v = 255 - v; - } + if (invert) + { + v = 255 - v; + } - q[0] = v; - q[1] = v; - q[2] = v; - q[3] = 255; - } + q[0] = v; + q[1] = v; + q[2] = v; + q[3] = 255; } + } - return true; - } + return true; }