view Framework/Radiography/RadiographyWidget.cpp @ 700:059e1fd05fd6 refactor-viewport-controller

Introduced the ViewportController that sits between the application and the Scene2D to handle the trackers and measuring tools. This is a work in progress. The Scene2D is no longer an observable. Message sending is managed by the ViewportController. Move some refs to shared and weak to prevent lifetime issues.
author Benjamin Golinvaux <bgo@osimis.io>
date Sun, 19 May 2019 16:26:17 +0200
parents 1201b12eb9f8
children 4f2416d519b4
line wrap: on
line source

/**
 * Stone of Orthanc
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-2019 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 "RadiographyWidget.h"

#include <Core/OrthancException.h>
#include <Core/Images/Image.h>
#include <Core/Images/ImageProcessing.h>

#include "RadiographyMaskLayer.h"

namespace OrthancStone
{

  bool RadiographyWidget::IsInvertedInternal() const
  {
    // MONOCHROME1 images must be inverted and the user can invert the 
    // image, too -> XOR the two
    return (scene_->GetPreferredPhotomotricDisplayMode() == 
      PhotometricDisplayMode_Monochrome1) ^ invert_; 
  }

  void RadiographyWidget::RenderBackground(
    Orthanc::ImageAccessor& image, float minValue, float maxValue)
  {
    // wipe background before rendering
    float backgroundValue = minValue;

    switch (scene_->GetPreferredPhotomotricDisplayMode())
    {
    case PhotometricDisplayMode_Monochrome1:
    case PhotometricDisplayMode_Default:
      if (IsInvertedInternal())
        backgroundValue = maxValue;
      else
        backgroundValue = minValue;
      break;
    case PhotometricDisplayMode_Monochrome2:
      if (IsInvertedInternal())
        backgroundValue = minValue;
      else
        backgroundValue = maxValue;
      break;
    default:
      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
    }

    Orthanc::ImageProcessing::Set(image, static_cast<int64_t>(backgroundValue));
  }

  bool RadiographyWidget::RenderInternal(unsigned int width,
                                         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 (windowWidth <= 0.001f)  // Avoid division by zero at (*)
    {
      return false;
    }
    else
    {
      if (floatBuffer_.get() == NULL ||
          floatBuffer_->GetWidth() != width ||
          floatBuffer_->GetHeight() != height)
      {
        floatBuffer_.reset(new Orthanc::Image(
          Orthanc::PixelFormat_Float32, width, height, false));
      }

      if (cairoBuffer_.get() == NULL ||
          cairoBuffer_->GetWidth() != width ||
          cairoBuffer_->GetHeight() != height)
      {
        cairoBuffer_.reset(new CairoSurface(width, height, false /* no alpha */));
      }

      RenderBackground(*floatBuffer_, x0, x1);

      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();

      for (unsigned int y = 0; y < height; y++)
      {
        const float* p = reinterpret_cast<const float*>(floatBuffer_->GetConstRow(y));
        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));

        for (unsigned int x = 0; x < width; x++, p++, q += 4)
        {
          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<uint8_t>(scaling * (*p - x0));  // (*)
          }

          if (invert)
          {
            v = 255 - v;
          }

          q[0] = v;
          q[1] = v;
          q[2] = v;
          q[3] = 255;
        }
      }

      return true;
    }
  }


  bool RadiographyWidget::RenderScene(CairoContext& context,
                                      const ViewportGeometry& view)
  {
    cairo_t* cr = context.GetObject();

    if (RenderInternal(context.GetWidth(), context.GetHeight(), interpolation_))
    {
      // https://www.cairographics.org/FAQ/#paint_from_a_surface
      cairo_save(cr);
      cairo_identity_matrix(cr);
      cairo_set_source_surface(cr, cairoBuffer_->GetObject(), 0, 0);
      cairo_paint(cr);
      cairo_restore(cr);
    }
    else
    {
      // https://www.cairographics.org/FAQ/#clear_a_surface
      context.SetSourceColor(0, 0, 0);
      cairo_paint(cr);
    }

    if (hasSelection_)
    {
      scene_->DrawBorder(
        context, static_cast<unsigned int>(selectedLayer_), view.GetZoom());
    }

    return true;
  }


  RadiographyWidget::RadiographyWidget(MessageBroker& broker,
                                       boost::shared_ptr<RadiographyScene> scene,
                                       const std::string& name) :
    WorldSceneWidget(name),
    IObserver(broker),
    invert_(false),
    interpolation_(ImageInterpolation_Nearest),
    hasSelection_(false),
    selectedLayer_(0)    // Dummy initialization
  {
    SetScene(scene);
  }


  void RadiographyWidget::Select(size_t layer)
  {
    hasSelection_ = true;
    selectedLayer_ = layer;
  }

  bool RadiographyWidget::SelectMaskLayer(size_t index)
  {
    std::vector<size_t> layerIndexes;
    size_t count = 0;
    scene_->GetLayersIndexes(layerIndexes);

    for (size_t i = 0; i < layerIndexes.size(); ++i)
    {
      const RadiographyMaskLayer* maskLayer = dynamic_cast<const RadiographyMaskLayer*>(&(scene_->GetLayer(layerIndexes[i])));
      if (maskLayer != NULL)
      {
        if (count == index)
        {
          Select(layerIndexes[i]);
          return true;
        }
        count++;
      }
    }

    return false;
  }

  bool RadiographyWidget::LookupSelectedLayer(size_t& layer)
  {
    if (hasSelection_)
    {
      layer = selectedLayer_;
      return true;
    }
    else
    {
      return false;
    }
  }

  
  void RadiographyWidget::OnGeometryChanged(const RadiographyScene::GeometryChangedMessage& message)
  {
    LOG(INFO) << "Scene geometry has changed";

    FitContent();
  }

  
  void RadiographyWidget::OnContentChanged(const RadiographyScene::ContentChangedMessage& message)
  {
    LOG(INFO) << "Scene content has changed";
    NotifyContentChanged();
  }

  
  void RadiographyWidget::SetInvert(bool invert)
  {
    if (invert_ != invert)
    {
      invert_ = invert;
      NotifyContentChanged();
    }
  }

  
  void RadiographyWidget::SwitchInvert()
  {
    invert_ = !invert_;
    NotifyContentChanged();
  }


  void RadiographyWidget::SetInterpolation(ImageInterpolation interpolation)
  {
    if (interpolation_ != interpolation)
    {
      interpolation_ = interpolation;
      NotifyContentChanged();
    }
  }

  void RadiographyWidget::SetScene(boost::shared_ptr<RadiographyScene> scene)
  {
    if (scene_ != NULL)
    {
      scene_->Unregister(this);
    }

    scene_ = scene;

    scene_->RegisterObserverCallback(
          new Callable<RadiographyWidget, RadiographyScene::GeometryChangedMessage>
          (*this, &RadiographyWidget::OnGeometryChanged));

    scene_->RegisterObserverCallback(
          new Callable<RadiographyWidget, RadiographyScene::ContentChangedMessage>
          (*this, &RadiographyWidget::OnContentChanged));

    NotifyContentChanged();

    // force redraw
    FitContent();
  }
}