MITK中窗宽窗位相关代码

本文涉及的产品
可视分析地图(DataV-Atlas),3 个项目,100M 存储空间
数据可视化DataV,5个大屏 1个月
简介: 本文详细介绍了MITK中窗宽窗位的实现,包括基本数据结构、自动设定算法、属性设置、事件触发和控件创建等方面的代码和方法。

交互的核心控件是QmitkSliderLevelWindowWidget

控件修改值通过DisplaySetLevelWindowEvent类,以如下方式添加:

  level += displayActionEvent->GetLevel();  
  window += displayActionEvent->GetWindow();

1 基本数据结构Image


    /**
      * initialize new (or re-initialize) image information by @a itkimage,
      * a templated itk-image.
      * Only the header is used, not the data vector! Use
      * SetVolume(itkimage->GetBufferPointer()) to set the data vector.
      *
      * @param itkimage
      * @param channels
      * @param tDim override time dimension in @a itkimage (if >0 and <)
      * @param sDim override z-space dimension in @a itkimage (if >0 and <)
      */
    template <typename itkImageType>
    void InitializeByItk(const itkImageType *itkimage, int channels = 1, int tDim = -1, int sDim = -1)
    {
   
      if (itkimage == nullptr)
        return;

      MITK_DEBUG << "Initializing MITK image from ITK image.";
      // build array with dimensions in each direction with at least 4 entries
      m_Dimension = itkimage->GetImageDimension();
      unsigned int i, *tmpDimensions = new unsigned int[m_Dimension > 4 ? m_Dimension : 4];
      for (i = 0; i < m_Dimension; ++i)
        tmpDimensions[i] = itkimage->GetLargestPossibleRegion().GetSize().GetSize()[i];
      if (m_Dimension < 4)
      {
   
        unsigned int *p;
        for (i = 0, p = tmpDimensions + m_Dimension; i < 4 - m_Dimension; ++i, ++p)
          *p = 1;
      }

      // overwrite number of slices if sDim is set
      if ((m_Dimension > 2) && (sDim >= 0))
        tmpDimensions[2] = sDim;
      // overwrite number of time points if tDim is set
      if ((m_Dimension > 3) && (tDim >= 0))
        tmpDimensions[3] = tDim;

      // rough initialization of Image
      // mitk::PixelType importType = ImportItkPixelType( itkimage::PixelType );

      Initialize(
        MakePixelType<itkImageType>(itkimage->GetNumberOfComponentsPerPixel()), m_Dimension, tmpDimensions, channels);
      const typename itkImageType::SpacingType &itkspacing = itkimage->GetSpacing();

      MITK_DEBUG << "ITK spacing " << itkspacing;
      // access spacing of itk::Image
      Vector3D spacing;
      FillVector3D(spacing, itkspacing[0], 1.0, 1.0);
      if (m_Dimension >= 2)
        spacing[1] = itkspacing[1];
      if (m_Dimension >= 3)
        spacing[2] = itkspacing[2];

      // access origin of itk::Image
      Point3D origin;
      const typename itkImageType::PointType &itkorigin = itkimage->GetOrigin();
      MITK_DEBUG << "ITK origin " << itkorigin;
      FillVector3D(origin, itkorigin[0], 0.0, 0.0);
      if (m_Dimension >= 2)
        origin[1] = itkorigin[1];
      if (m_Dimension >= 3)
        origin[2] = itkorigin[2];

      // access direction of itk::Imagm_PixelType = new mitk::PixelType(type);e and include spacing
      const typename itkImageType::DirectionType &itkdirection = itkimage->GetDirection();
      MITK_DEBUG << "ITK direction " << itkdirection;
      mitk::Matrix3D matrix;
      matrix.SetIdentity();
      unsigned int j, itkDimMax3 = (m_Dimension >= 3 ? 3 : m_Dimension);
      // check if spacing has no zero entry and itkdirection has no zero columns
      bool itkdirectionOk = true;
      mitk::ScalarType columnSum;
      for (j = 0; j < itkDimMax3; ++j)
      {
   
        columnSum = 0.0;
        for (i = 0; i < itkDimMax3; ++i)
        {
   
          columnSum += fabs(itkdirection[i][j]);
        }
        if (columnSum < mitk::eps)
        {
   
          itkdirectionOk = false;
        }
        if ((spacing[j] < -mitk::eps) // (normally sized) negative value
            &&
            (j == 2) && (m_Dimensions[2] == 1))
        {
   
          // Negative spacings can occur when reading single DICOM slices with ITK via GDCMIO
          // In these cases spacing is not determined by ITK correctly (because it distinguishes correctly
          // between slice thickness and inter slice distance -- slice distance is meaningless for
          // single slices).
          // I experienced that ITK produced something meaningful nonetheless because it is
          // evaluating the tag "(0018,0088) Spacing between slices" as a fallback. This tag is not
          // reliable (http://www.itk.org/pipermail/insight-users/2005-September/014711.html)
          // but gives at least a hint.
          // In real world cases I experienced that this tag contained the correct inter slice distance
          // with a negative sign, so we just invert such negative spacings.
          MITK_WARN << "Illegal value of itk::Image::GetSpacing()[" << j << "]=" << spacing[j]
                    << ". Using inverted value " << -spacing[j];
          spacing[j] = -spacing[j];
        }
        else if (spacing[j] < mitk::eps) // value near zero
        {
   
          MITK_ERROR << "Illegal value of itk::Image::GetSpacing()[" << j << "]=" << spacing[j]
                     << ". Using 1.0 instead.";
          spacing[j] = 1.0;
        }
      }
      if (itkdirectionOk == false)
      {
   
        MITK_ERROR << "Illegal matrix returned by itk::Image::GetDirection():" << itkdirection
                   << " Using identity instead.";
        for (i = 0; i < itkDimMax3; ++i)
          for (j = 0; j < itkDimMax3; ++j)
            if (i == j)
              matrix[i][j] = spacing[j];
            else
              matrix[i][j] = 0.0;
      }
      else
      {
   
        for (i = 0; i < itkDimMax3; ++i)
          for (j = 0; j < itkDimMax3; ++j)
            matrix[i][j] = itkdirection[i][j] * spacing[j];
      }

      // re-initialize PlaneGeometry with origin and direction
      PlaneGeometry *planeGeometry = static_cast<PlaneGeometry *>(GetSlicedGeometry(0)->GetPlaneGeometry(0));
      planeGeometry->SetOrigin(origin);
      planeGeometry->GetIndexToWorldTransform()->SetMatrix(matrix);

      // re-initialize SlicedGeometry3D
      SlicedGeometry3D *slicedGeometry = GetSlicedGeometry(0);
      slicedGeometry->InitializeEvenlySpaced(planeGeometry, m_Dimensions[2]);
      slicedGeometry->SetSpacing(spacing);

      // re-initialize TimeGeometry
      ProportionalTimeGeometry::Pointer timeGeometry = ProportionalTimeGeometry::New();
      timeGeometry->Initialize(slicedGeometry, m_Dimensions[3]);
      SetTimeGeometry(timeGeometry);

      // clean-up
      delete[] tmpDimensions;

      this->Initialize();
    }

2 窗宽窗位mitkLevelWindow定义类

  /**
  * @brief The LevelWindow class Class to store level/window values.
  *
  * Current min and max value are stored in m_LowerWindowBound and m_UpperWindowBound.
  * m_DefaultLevel amd m_DefaultWindow store the initial Level/Window values for the image.
  * m_DefaultRangeMin and m_DefaultRangeMax store the initial minrange and maxrange for the image.
  *
  * The finite maximum and minimum of valid value range is stored in m_RangeMin and m_RangeMax.
  * If deduced from an image by default the minimum or maximum of it statistics is used. If one
  * of these values are infinite the 2nd extrimum (which is guaranteed to be finite), will be used.
  *
  * See documentation of SetAuto for information on how the level window is initialized from an image.
  *
  * @ingroup DataManagement
  *
  * @note If you want to apply the mitk::LevelWindow to an mitk::Image, make sure
  * to use the mitk::LevelWindowProperty and set the mitk::RenderingModeProperty
  * to a mode which supports level window (e.g. LEVELWINDOW_COLOR).
  * Make sure to check the documentation of the mitk::RenderingModeProperty. For a
  * code example how to use the mitk::LevelWindowProperty check the
  * mitkImageVtkMapper2DLevelWindowTest.cpp in Core/Code/Testing.
  */
  class MITKCORE_EXPORT LevelWindow
  {
   
  public:
    LevelWindow(ScalarType level = 127.5, ScalarType window = 255.0);
    LevelWindow(const mitk::LevelWindow &levWin);
    virtual ~LevelWindow();

......

3 窗宽窗位自动设定算法


/*!
This method initializes a mitk::LevelWindow from an mitk::Image. The algorithm is as follows:

Default to taking the central image slice for quick analysis.

Compute the smallest (minValue), second smallest (min2ndValue), second largest (max2ndValue), and
largest (maxValue) data value by traversing the pixel values only once. In the
same scan it also computes the count of minValue values and maxValue values.
After that a basic histogram with specific information about the
extremes is complete.

If minValue == maxValue, the center slice is uniform and the above scan is repeated for
the complete image, not just one slice

Next, special cases of images with only 1, 2 or 3 distinct data values
have hand assigned level window ranges.

Next the level window is set relative to the inner range IR = lengthOf([min2ndValue, max2ndValue])

For count(minValue) > 20% the smallest values are frequent and should be
distinct from the min2ndValue and larger values (minValue may be std:min, may signify
something special) hence the lower end of the level window is set to min2ndValue - 0.5 * IR

For count(minValue) <= 20% the smallest values are not so important and can
blend with the next ones => min(level window) = min2ndValue

And analog for max(level window):
count(max2ndValue) > 20%:  max(level window) = max2ndValue + 0.5 * IR
count(max2ndValue) < 20%:  max(level window) = max2ndValue

In both 20%+ cases the level window bounds are clamped to the [minValue, maxValue] range

In consequence the level window maximizes contrast with minimal amount of
computation and does do useful things if the data contains std::min or
std:max values or has only 1 or 2 or 3 data values.
*/
void mitk::LevelWindow::SetAuto(const mitk::Image *image,
                                bool /*tryPicTags*/,
                                bool guessByCentralSlice,
                                unsigned selectedComponent)
{
   
  if (IsFixed())
    return;

  if (image == nullptr || !image->IsInitialized())
    return;

  if (itk::IOComponentEnum::FLOAT == image->GetPixelType().GetComponentType()
  ||  itk::IOComponentEnum::DOUBLE == image->GetPixelType().GetComponentType())
  {
   
    m_IsFloatingImage = true;
  }
  else
  {
   
    m_IsFloatingImage = false;
  }

  const mitk::Image *wholeImage = image;
  ScalarType minValue = 0.0;
  ScalarType maxValue = 0.0;
  ScalarType min2ndValue = 0.0;
  ScalarType max2ndValue = 0.0;
  mitk::ImageSliceSelector::Pointer sliceSelector = mitk::ImageSliceSelector::New();
  if (guessByCentralSlice)
  {
   
    sliceSelector->SetInput(image);
    sliceSelector->SetSliceNr(image->GetDimension(2) / 2);
    sliceSelector->SetTimeNr(image->GetDimension(3) / 2);
    sliceSelector->SetChannelNr(image->GetDimension(4) / 2);
    sliceSelector->Update();
    image = sliceSelector->GetOutput();
    if (image == nullptr || !image->IsInitialized())
      return;

    minValue = image->GetStatistics()->GetScalarValueMin(0, selectedComponent);
    maxValue = image->GetStatistics()->GetScalarValueMaxNoRecompute();
    min2ndValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute();
    max2ndValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute();
    if (minValue == maxValue)
    {
   
      // guessByCentralSlice seems to have failed, lets look at all data
      image = wholeImage;
      minValue = image->GetStatistics()->GetScalarValueMin(0, selectedComponent);
      maxValue = image->GetStatistics()->GetScalarValueMaxNoRecompute();
      min2ndValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute();
      max2ndValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute();
    }
  }
  else
  {
   
    const_cast<Image *>(image)->Update();
    minValue = image->GetStatistics()->GetScalarValueMin(0, selectedComponent);
    maxValue = image->GetStatistics()->GetScalarValueMaxNoRecompute(0);
    min2ndValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(0);
    max2ndValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(0);
    for (unsigned int i = 1; i < image->GetDimension(3); ++i)
    {
   
      ScalarType minValueTemp = image->GetStatistics()->GetScalarValueMin(i, selectedComponent);
      if (minValue > minValueTemp)
        minValue = minValueTemp;
      ScalarType maxValueTemp = image->GetStatistics()->GetScalarValueMaxNoRecompute(i);
      if (maxValue < maxValueTemp)
        maxValue = maxValueTemp;
      ScalarType min2ndValueTemp = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(i);
      if (min2ndValue > min2ndValueTemp)
        min2ndValue = min2ndValueTemp;
      ScalarType max2ndValueTemp = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(i);
      if (max2ndValue > max2ndValueTemp)
        max2ndValue = max2ndValueTemp;
    }
  }

  // Fix for bug# 344 Level Window wird bei Eris Cut bildern nicht richtig gesetzt
  if (image->GetPixelType().GetPixelType() == itk::IOPixelEnum::SCALAR &&
      image->GetPixelType().GetComponentType() == itk::IOComponentEnum::INT && image->GetPixelType().GetBpe() >= 8)
  {
   
    // the windows compiler complains about ambiguous 'pow' call, therefore static casting to (double, int)
    if (minValue == -(pow((double)2.0, static_cast<int>(image->GetPixelType().GetBpe() / 2))))
    {
   
      minValue = min2ndValue;
    }
  }
  // End fix

   uniform image
  if (minValue == maxValue)
  {
   
    minValue = maxValue - 1;
  }
  else
  {
   
    // Due to bug #8690 level window now is no longer of fixed range by default but the range adapts according to
    // levelwindow interaction
    // This is done because the range should be a little bit larger from the beginning so that the scale doesn't start
    // to resize right from the beginning
    double additionalRange = 0.15 * (maxValue - minValue);
    minValue -= additionalRange;
    maxValue += additionalRange;
  }

  if (!std::isfinite(minValue))
  {
   
    minValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(0);
  }
  if (!std::isfinite(maxValue))
  {
   
    maxValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(0);
  }

  SetRangeMinMax(minValue, maxValue);
  SetDefaultBoundaries(minValue, maxValue);

  size_t numPixelsInDataset = image->GetDimensions()[0];
  for (decltype(image->GetDimension()) k = 1; k < image->GetDimension(); ++k)
    numPixelsInDataset *= image->GetDimensions()[k];
  const auto minCount = image->GetStatistics()->GetCountOfMinValuedVoxelsNoRecompute();
  const auto maxCount = image->GetStatistics()->GetCountOfMaxValuedVoxelsNoRecompute();
  const auto minCountFraction = minCount / static_cast<ScalarType>(numPixelsInDataset);
  const auto maxCountFraction = maxCount / static_cast<ScalarType>(numPixelsInDataset);

   binary image
  if (min2ndValue == maxValue)
  {
   
    // noop; full range is fine
  }

   triple value image, put middle value in center of gray level ramp
  else if (min2ndValue == max2ndValue)
  {
   
    ScalarType minDelta = std::min(min2ndValue - minValue, maxValue - min2ndValue);
    minValue = min2ndValue - minDelta;
    maxValue = min2ndValue + minDelta;
  }

  // now we can assume more than three distict scalar values
  else
  {
   
    ScalarType innerRange = max2ndValue - min2ndValue;

    if (minCountFraction > 0.2)  lots of min values -> make different from rest, but not miles away
    {
   
      ScalarType halfInnerRangeGapMinValue = min2ndValue - innerRange / 2.0;
      minValue = std::max(minValue, halfInnerRangeGapMinValue);
    }
    else  few min values -> focus on innerRange
    {
   
      minValue = min2ndValue;
    }

    if (maxCountFraction > 0.2)  lots of max values -> make different from rest
    {
   
      ScalarType halfInnerRangeGapMaxValue = max2ndValue + innerRange / 2.0;
      maxValue = std::min(maxValue, halfInnerRangeGapMaxValue);
    }
    else  few max values -> focus on innerRange
    {
   
      maxValue = max2ndValue;
    }
  }
  SetWindowBounds(minValue, maxValue);
  SetDefaultLevelWindow((maxValue - minValue) / 2 + minValue, maxValue - minValue);
}

4 窗宽窗位的属性设置LevelWindowProperty 类


namespace mitk
{
   
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4522)
#endif

  /**
   * @brief The LevelWindowProperty class Property for the mitk::LevelWindow
   *
   * @ingroup DataManagement
   *
   * @note If you want to apply the mitk::LevelWindowProperty to an mitk::Image,
   * make sure to set the mitk::RenderingModeProperty to a mode which supports
   * level window (e.g. LEVELWINDOW_COLOR). Make sure to check the documentation
   * of the mitk::RenderingModeProperty. For a code example how to use the
   * mitk::LevelWindowProperty check the mitkImageVtkMapper2DLevelWindowTest.cpp
   * in Core/Code/Testing.
   */
  class MITKCORE_EXPORT LevelWindowProperty : public BaseProperty
  {
   
  protected:
    LevelWindow m_LevWin;

    LevelWindowProperty();

    LevelWindowProperty(const LevelWindowProperty &other);

    LevelWindowProperty(const mitk::LevelWindow &levWin);

  public:
    mitkClassMacro(LevelWindowProperty, BaseProperty);

    itkFactorylessNewMacro(Self);

    itkCloneMacro(Self) mitkNewMacro1Param(LevelWindowProperty, const mitk::LevelWindow &);

    typedef LevelWindow ValueType;

    ~LevelWindowProperty() override;

    const mitk::LevelWindow &GetLevelWindow() const;
    const mitk::LevelWindow &GetValue() const;

    void SetLevelWindow(const LevelWindow &levWin);
    void SetValue(const ValueType &levWin);

    std::string GetValueAsString() const override;

    using BaseProperty::operator=;

  private:
    // purposely not implemented
    LevelWindowProperty &operator=(const LevelWindowProperty &);

    itk::LightObject::Pointer InternalClone() const override;

    bool IsEqual(const BaseProperty &property) const override;
    bool Assign(const BaseProperty &property) override;
  };

#ifdef _MSC_VER
#pragma warning(pop)
#endif

} // namespace mitk

5 mitkDisplayActionEventFunctions类中DisplayActionEventFunctions触发窗宽窗位改变

    /**
    * @brief Returns an 'std::function' that can be used  to react on the 'DisplaySetLevelWindowEvent'.
    *   The function sets the 'levelwindow' property of the topmost visible image that is display by the sending renderer.
    *   The level and window value for this property were previously determined by the mouse interaction event.
    */
    MITKCORE_EXPORT StdFunctionCommand::ActionFunction SetLevelWindowAction();



mitk::StdFunctionCommand::ActionFunction mitk::DisplayActionEventFunctions::SetLevelWindowAction()
{
   
  auto actionFunction = [](const itk::EventObject& displayInteractorEvent)
  {
   
    if (DisplaySetLevelWindowEvent().CheckEvent(&displayInteractorEvent))
    {
   
      const DisplaySetLevelWindowEvent* displayActionEvent = dynamic_cast<const DisplaySetLevelWindowEvent*>(&displayInteractorEvent);
      const BaseRenderer::Pointer sendingRenderer = displayActionEvent->GetSender();
      if (nullptr == sendingRenderer)
      {
   
        return;
      }

      // get the the topmost visible image of the sending renderer
      DataStorage::Pointer storage = sendingRenderer->GetDataStorage();
      DataStorage::SetOfObjects::ConstPointer allImageNodes = storage->GetSubset(NodePredicateDataType::New("Image"));
      Point3D worldposition;
      const auto* positionEvent = dynamic_cast<const InteractionPositionEvent*>(displayActionEvent->GetInteractionEvent());
      sendingRenderer->DisplayToWorld(positionEvent->GetPointerPositionOnScreen(), worldposition);
      auto globalCurrentTimePoint = sendingRenderer->GetTime();
      DataNode::Pointer node = FindTopmostVisibleNode(allImageNodes, worldposition, globalCurrentTimePoint, sendingRenderer);
      if (node.IsNull())
      {
   
        return;
      }

      LevelWindow levelWindow = LevelWindow();
      node->GetLevelWindow(levelWindow);
      ScalarType level = levelWindow.GetLevel();
      ScalarType window = levelWindow.GetWindow();

      level += displayActionEvent->GetLevel();
      window += displayActionEvent->GetWindow();

      levelWindow.SetLevelWindow(level, window);
      auto* levelWindowProperty = dynamic_cast<LevelWindowProperty*>(node->GetProperty("levelwindow"));
      if (nullptr != levelWindowProperty)
      {
   
        levelWindowProperty->SetLevelWindow(levelWindow);
        RenderingManager::GetInstance()->RequestUpdateAll();
      }
    }
  };

6 DisplaySetLevelWindowEvent类得到窗体交互新增的窗宽窗位值


  class MITKCORE_EXPORT DisplaySetLevelWindowEvent : public DisplayActionEvent
  {
   
  public:
    typedef DisplaySetLevelWindowEvent Self;
    typedef DisplayActionEvent Superclass;

    DisplaySetLevelWindowEvent() : Superclass() {
   }
    DisplaySetLevelWindowEvent(InteractionEvent* interactionEvent, ScalarType level, ScalarType window)
      : Superclass(interactionEvent)
      , m_Level(level)
      , m_Window(window)
    {
   
    }
    ~DisplaySetLevelWindowEvent() override {
   }
    const char* GetEventName() const override {
    return "DisplaySetLevelWindowEvent"; }
    bool CheckEvent(const itk::EventObject *e) const override {
    return dynamic_cast<const Self *>(e) != nullptr; }
    itk::EventObject* MakeObject() const override {
    return new Self(GetInteractionEvent(), m_Level, m_Window); }
    ScalarType GetLevel() const {
    return m_Level; }
    ScalarType GetWindow() const {
    return m_Window; }
    DisplaySetLevelWindowEvent(const Self& s) : Superclass(s), m_Level(s.GetLevel()), m_Window(s.GetWindow()) {
   };

  private:
    ScalarType m_Level;
    ScalarType m_Window;
  };

7 QmitkLevelWindowWidgetContextMenu


/**
 * \ingroup QmitkModule
 * \brief Provides a contextmenu for Level/Window functionality.
 *
 * Either creates
 * a new contextmenu with standard functions or adds Level/Window standard
 * functions to an predefined contextmenu.
 */
class MITKQTWIDGETS_EXPORT QmitkLevelWindowWidgetContextMenu : public QWidget
{
   
  Q_OBJECT

public:
  /// constructor
  QmitkLevelWindowWidgetContextMenu(QWidget *parent, Qt::WindowFlags f = nullptr);
  ~QmitkLevelWindowWidgetContextMenu() override;

8 QmitkLineEditLevelWindowWidget

/**
 * \ingroup QmitkModule
 * \brief Provides a widget with two lineedit fields, one to change the
 * window value of the current image and one to change the level value of
 * the current image.
 */
class MITKQTWIDGETS_EXPORT QmitkLineEditLevelWindowWidget : public QWidget
{
   
  Q_OBJECT

public:
  /// constructor
  QmitkLineEditLevelWindowWidget(QWidget *parent = nullptr, Qt::WindowFlags f = nullptr);

  /// destructor
  ~QmitkLineEditLevelWindowWidget() override;

  /// inputfield for level value
  QLineEdit *m_LevelInput;

  /// inputfield for window value
  QLineEdit *m_WindowInput;

9 QmitkSliderLevelWindowWidget


#ifndef QMITKSLIDERLEVELWINDOWWIDGET_H
#define QMITKSLIDERLEVELWINDOWWIDGET_H

#include <MitkQtWidgetsExports.h>

#include <QWidget>
#include <mitkLevelWindowManager.h>

class QmitkLevelWindowWidgetContextMenu;

/**
 * \ingroup QmitkModule
 *
 * \brief Provides a widget with a slider to change the level and
 * window value of the current image.
 *
 * This documentation actually refers to the QmitkLevelWindowWidget
 * and is only put in this class due to technical issues (should be
 * moved later).
 *
 * The QmitkLevelWindowWidget is a kind of container for a
 * QmitkSliderLevelWindowWidget (this is the cyan bar above the text
 * input fields) and a QmitkLineEditLevelWindowWidget (with two text
 * input fields). It holds a reference to a mitk::LevelWindowManager
 * variable, which keeps the LevelWindowProperty of the currently
 * selected image. Level/Window is manipulated by the text inputs and
 * the Slider to adjust brightness/contrast of a single image. All
 * changes on the slider or in the text input fields affect the current
 * image by giving new values to LevelWindowManager. LevelWindowManager
 * then sends a signal to tell other listeners about changes.
 *
 * Which image is changed is determined by mitkLevelWindowManager. If
 * m_AutoTopMost is true, always the topmost image in data tree (layer
 * property) is affected by changes. The image which is affected by
 * changes can also be changed by QmitkLevelWindowWidgetContextMenu,
 * the context menu for QmitkSliderLevelWindowWidget and
 * QmitkLineEditLevelWindowWidget. There you have the possibility to
 * set a certain image or always the topmost image in the data tree
 * (layer property) to be affected by changes.
 *
 * The internal mitk::LevelWindow variable contains a range that is
 * valid for a given image. It should not be possible to move the
 * level/window parameters outside this range. The range can be changed
 * and reset to its default values by QmitkLevelWindowWidgetContextMenu,
 * the context menu for QmitkSliderLevelWindowWidget and
 * QmitkLineEditLevelWindowWidget.
 *
 * Now for the behaviour of the text inputs: The upper one contains the
 * value of the level (brightness), the lower one shows the window (contrast).
 *
 * The behaviour of the cyan bar is more obvious: the scale in the
 * background shows the valid range. The cyan bar in front displays the
 * currently selected level/window setting. You can change the level by
 * dragging the bar with the left mouse button or clicking somewhere inside
 * the scalerange with the left mouse button. The window is changed by
 * moving the mouse on the upper or lower bound of the bar until the cursor
 * becomes an vertical double-arrowed symbol. Then you can change the
 * windowsize by clicking the left mouse button and move the mouse upwards
 * or downwards. The bar becomes greater upwards as well as downwards. If
 * you want to change the size of the window in only one direction you
 * have to press the CTRL-key while doing the same as mentioned above.
 * This information is also presented by a tooltip text when moving the
 * mouse on the upper or lower bound of the bar.
 */
class MITKQTWIDGETS_EXPORT QmitkSliderLevelWindowWidget : public QWidget
{
   
  Q_OBJECT

public:
  /// constructor
  QmitkSliderLevelWindowWidget(QWidget *parent = nullptr, Qt::WindowFlags f = nullptr);

  /// destructor
  ~QmitkSliderLevelWindowWidget() override;

  /// sets the manager who is responsible to collect and deliver changes on Level/Window
  void SetLevelWindowManager(mitk::LevelWindowManager *levelWindowManager);

  /// sets the DataStorage which holds all image-nodes
  void SetDataStorage(mitk::DataStorage *ds);

  /// returns the manager who is responsible to collect and deliver changes on Level/Window
  mitk::LevelWindowManager *GetManager();

  mitk::LevelWindow m_LevelWindow;

  /// manager who is responsible to collect and deliver changes on Level/Window
  mitk::LevelWindowManager::Pointer m_Manager;

private:
  /// creates the context menu for this widget from class QmitkLevelWindowWidgetContextMenu
  void contextMenuEvent(QContextMenuEvent *) override;

  /// change notifications from the mitkLevelWindowManager
  void OnPropertyModified(const itk::EventObject &e);

protected:
  /// recalculate the size and position of the slider bar
  virtual void Update();

  /*!
  * helper for drawing the component
  */
  QRect m_Rect;

  /*!
  * helper for drawing the component
  */
  QPoint m_StartPos;

  bool m_Resize;
  bool m_Bottom;
  bool m_MouseDown;
  bool m_Leftbutton;
  bool m_CtrlPressed;
  int m_MoveHeight;
  bool m_ScaleVisible;
  QRect m_LowerBound;
  QRect m_UpperBound;
  unsigned long m_ObserverTag;
  bool m_IsObserverTagSet;

  QFont m_Font;

  /*!
  *  data structure which creates the context menu for QmitkLineEditLevelWindowWidget
  */
  QmitkLevelWindowWidgetContextMenu *m_Contextmenu;

  /*!
  * repaint the slider and the scale
  */
  void paintEvent(QPaintEvent *e) override;

  /*!
  * method implements the component behavior
  *
  * checks if cursor is on upper or lower bound of slider bar and changes cursor symbol
  *
  * checks if left mouse button is pressed and if CTRL is pressed and changes sliderbar in move-direction accordingly
  */
  void mouseMoveEvent(QMouseEvent *mouseEvent) override;

  void enterEvent(QEvent *event) override;

  /*!
  *  registers events when a mousebutton is pressed
  *
  * if leftbutton is pressed m_Leftbutton is set to true
  *
  * also checks if CTRL is pressed and sets the bool variable m_CtrlPressed
  */
  void mousePressEvent(QMouseEvent *mouseEvent) override;

  /*!
  *  sets the variable m_MouseDown to false
  */
  void mouseReleaseEvent(QMouseEvent *mouseEvent) override;

  /*!
  * causes an update of the sliderbar when resizing the window
  */
  void resizeEvent(QResizeEvent *event) override;

protected Q_SLOTS:

  /** @brief Hide the scale if "Hide Scale" is selected in the context menu
  */
  void HideScale();

  /** @brief Shows the scale if "Show Scale" is selected in the context menu
  */
  void ShowScale();
};

#endif // QMITKSLIDERLEVELWINDOWWIDGET_H

10 QmitkLevelWindowWidget

#include "QmitkLevelWindowWidget.h"
#include "QmitkSliderLevelWindowWidget.h"

QmitkLevelWindowWidget::QmitkLevelWindowWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f)
{
   
  this->setupUi(this);

  m_Manager = mitk::LevelWindowManager::New();

  SliderLevelWindowWidget->SetLevelWindowManager(m_Manager.GetPointer());
  LineEditLevelWindowWidget->SetLevelWindowManager(m_Manager.GetPointer());
}

void QmitkLevelWindowWidget::SetDataStorage(mitk::DataStorage *ds)
{
   
  m_Manager->SetDataStorage(ds);
}

mitk::LevelWindowManager *QmitkLevelWindowWidget::GetManager()
{
   
  return m_Manager.GetPointer();
}

11 LevelWindowManager


#ifndef MITKLEVELWINDOWMANAGER_H
#define MITKLEVELWINDOWMANAGER_H

// mitk core
#include "mitkBaseProperty.h"
#include "mitkDataStorage.h"
#include "mitkLevelWindowProperty.h"

//  c++
#include <map>
#include <utility>

namespace mitk
{
   
  /**
    @brief Provides access to the LevelWindowProperty object and LevelWindow of the "current" image.

    - provides a LevelWindowProperty for purposes like GUI editors
    - this property comes from one of two possible sources
      - either something (e.g. the application) sets the property because of some user selection
      - OR the "Auto top-most" logic is used to search a DataStorage for the image with the highest "layer" property
        value

    Changes on Level/Window can be set with SetLevelWindow() and will affect either the topmost layer image,
    if IsAutoTopMost() returns true, or an image which is set by SetLevelWindowProperty(LevelWindowProperty::Pointer
    levelWindowProperty).
    Additionally the changes on Level/Window will affect one or multiple selected images, if IsSelectedImages() returns true.
    Only one of the two different modes can be enabled at the same time.

    Changes to Level/Window, when another image gets active or by SetLevelWindow(const LevelWindow& levelWindow),
    will be sent to all listeners by Modified().

    DataStorageChanged() listens to the DataStorage for new or removed images. Depending on the currently enabled mode,
    the new image becomes active or not. If an image is removed from the DataStorage and m_AutoTopMost is false,
    there is a check to proof, if the active image is still available. If not, then m_AutoTopMost becomes true.

    Note that this class is not thread safe at the moment!
  */
  class MITKCORE_EXPORT LevelWindowManager : public itk::Object
  {
   
  public:

    mitkClassMacroItkParent(LevelWindowManager, itk::Object);
    itkFactorylessNewMacro(Self);
    itkCloneMacro(Self);

    void SetDataStorage(DataStorage* dataStorage);
    DataStorage *GetDataStorage();

    /**
    * @brief (Re-)Initialize the LevelWindowManager by setting the topmost image.
    *        Use the removedNode parameter if a node was removed.
    *
    * @param autoTopMost    Set the topmost layer image to be affected by changes, if true.
    * @param removedNode    A node was removed from the data storage if != nullptr.
    */
    void SetAutoTopMostImage(bool autoTopMost, const DataNode *removedNode = nullptr);
    /**
    * @brief (Re-)Initialize the LevelWindowManager by setting the selected images.
    *         Use the removedNode parameter if a node was removed.
    *
    * @param selectedImagesMode  Set the selected images to be affected by changes, if true.
    * @param removedNode         A node was removed from the data storage if != nullptr.
    */
    void SetSelectedImages(bool selectedImagesMode, const DataNode *removedNode = nullptr);

    void RecalculateLevelWindowForSelectedComponent(const itk::EventObject&);
    /**
    * @brief Update the level window.
    *        This function is called if a property of a data node is changed.
    *        Relevant properties are defined in the protected 'ObserverToPropertyValueMap'-members.
    */
    void Update(const itk::EventObject&);
    /**
    * @brief Update the level window.
    *        This function is only called if the 'selected' property of a data node is changed.
    *        This is done in order to avoid finding the correct image each time a node is selected but
    *        the 'm_SelectedImages' bool value is set to false (as the normal 'Update'-function would do).
    *        Changes of the 'selected' property happen quite a lot so this should not slow down the application.
    */
    void UpdateSelected(const itk::EventObject&);
    /**
     * @brief Set a specific LevelWindowProperty; all changes will affect the image belonging to this property.
     * @throw mitk::Exception Throw an exception if the there is no image in the data storage which belongs to this
     *        property.
     */
    void SetLevelWindowProperty(LevelWindowProperty::Pointer levelWindowProperty);
    /**
    *   @brief Set new Level/Window values and inform all listeners about changes.
    */
    void SetLevelWindow(const LevelWindow &levelWindow);
    /**
     * @brief Return the current LevelWindowProperty object from the image that is affected by changes.
     *
     * @return The current LevelWindowProperty
     */
    LevelWindowProperty::Pointer GetLevelWindowProperty() const;
    /**
    * @brief Return Level/Window values for the current image
    *
    * @return The LevelWindow value for the current image.
    */
    const LevelWindow &GetLevelWindow() const;
    /**
    * @brief Return true, if level window changes will affect the topmost layer image.
    *
    * @return Return the member value that denotes the auto-topmost mode.
    */
    bool IsAutoTopMost() const;
    /**
    * @brief Return true, if level window changes will affect the currently selected images.
    *
    * @return Return the member value that denotes the selected-images mode.
    */
    bool IsSelectedImages() const;
    /**
     * @brief This method is called when a node is added to the data storage.
     *        A listener on the data storage is used to call this method automatically after a node was added.
     * @throw mitk::Exception Throws an exception if something is wrong, e.g. if the number of observers differs from
     *        the number of nodes.
     */
    void DataStorageAddedNode(const DataNode *dataNode = nullptr);
    /**
     * @brief This method is called when a node is removed from the data storage.
     *        A listener on the data storage is used to call this method automatically before a node will be removed.
     * @throw mitk::Exception Throws an exception if something is wrong, e.g. if the number of observers differs from
     *        the number of nodes.
     */
    void DataStorageRemovedNode(const DataNode *dataNode = nullptr);
    /**
    * @brief Change notifications from mitkLevelWindowProperty.
    */
    void OnPropertyModified(const itk::EventObject&);
    /**
    * @brief Return the currently active image.
    *
    * @return The member variable holding the currently active image.
    */
    Image *GetCurrentImage() const;
    /**
     * @brief Return the number of observers for data node's "visible" property.
     *        This basically returns the number of relevant nodes to observe.
     *
     * @return The current number of observers which are registered in this object.
     */
    int GetNumberOfObservers() const;
    /**
    * @brief Return all nodes in the data storage that have the following properties:
    *   - "binary" == false
    *   - "levelwindow"
    *   - DataType == Image / DiffusionImage / TensorImage / OdfImage / ShImage
    *
    @ return The filtered list of relevant nodes in the data storage
    */
    DataStorage::SetOfObjects::ConstPointer GetRelevantNodes() const;

  private:
    LevelWindowManager();
    ~LevelWindowManager() override;

    DataStorage::Pointer m_DataStorage;
    LevelWindowProperty::Pointer m_LevelWindowProperty;

    typedef std::pair<unsigned long, DataNode::Pointer> PropDataPair;
    typedef std::map<PropDataPair, BaseProperty::Pointer> ObserverToPropertyValueMap;

    ObserverToPropertyValueMap m_ObserverToVisibleProperty;
    ObserverToPropertyValueMap m_ObserverToLayerProperty;
    ObserverToPropertyValueMap m_ObserverToRenderingModeProperty;
    ObserverToPropertyValueMap m_ObserverToDisplayedComponentProperty;
    ObserverToPropertyValueMap m_ObserverToLevelWindowImageProperty;
    ObserverToPropertyValueMap m_ObserverToSelectedProperty;

    void UpdateObservers();
    void ClearPropertyObserverMaps();
    void CreatePropertyObserverMaps();

    bool HasLevelWindowRenderingMode(DataNode *dataNode) const;

    // This variable holds a data node which will be deleted from the datastorage immediately.
    const DataNode *m_NodeMarkedToDelete;

    bool m_AutoTopMost;
    bool m_SelectedImagesMode;
    unsigned long m_PropertyModifiedTag;
    Image *m_CurrentImage;
    std::vector<DataNode::Pointer> m_DataNodesForLevelWindow;
    bool m_IsPropertyModifiedTagSet;
    bool m_LevelWindowMutex;
  };
}

#endif // MITKLEVELWINDOWMANAGER_H

12 多视图窗体中创建窗宽窗位控件




  // create level window slider on the right side
  if (nullptr == m_Impl->m_LevelWindowWidget)
  {
   
    m_Impl->m_LevelWindowWidget = new QmitkLevelWindowWidget(parent);
    m_Impl->m_LevelWindowWidget->setObjectName(QString::fromUtf8("levelWindowWidget"));

    QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
    sizePolicy.setHorizontalStretch(0);
    sizePolicy.setVerticalStretch(0);
    sizePolicy.setHeightForWidth(m_Impl->m_LevelWindowWidget->sizePolicy().hasHeightForWidth());
    m_Impl->m_LevelWindowWidget->setSizePolicy(sizePolicy);
    m_Impl->m_LevelWindowWidget->setMaximumWidth(50);
  }
相关实践学习
DataV Board用户界面概览
本实验带领用户熟悉DataV Board这款可视化产品的用户界面
阿里云实时数仓实战 - 项目介绍及架构设计
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3&nbsp;)前置知识要求 &nbsp; 课程大纲 第一章&nbsp;了解数据仓库概念 初步了解数据仓库是干什么的 第二章&nbsp;按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章&nbsp;数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章&nbsp;采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章&nbsp;用户行为数据仓库 严格按照企业的标准开发 第六章&nbsp;搭建业务数仓理论基础和对表的分类同步 第七章&nbsp;业务数仓的搭建&nbsp; 业务行为数仓效果图&nbsp;&nbsp;
相关文章
|
2月前
|
小程序
小程序消除图片下边距的三个方法
小程序消除图片下边距的三个方法
49 11
简单好用的图片取色器【可取RGB数值】
这篇文章介绍了如何使用Snipaste工具进行截图和取色,包括按下快捷键F1截图、选择图片区域、移动鼠标取色以及复制颜色值的步骤,并提供了操作界面的截图。
|
4月前
|
前端开发 容器
你不知道的css——3. 内外尺寸、流宽度、格式化宽度、格式化高度、首选最小宽度、包裹性、最大宽度
你不知道的css——3. 内外尺寸、流宽度、格式化宽度、格式化高度、首选最小宽度、包裹性、最大宽度
40 2
|
6月前
根据字符串内容、最大宽度和字体计算行宽和高度
根据字符串内容、最大宽度和字体计算行宽和高度
41 0
|
算法 前端开发 JavaScript
图片转ASCII字符图案的原理(可调整亮度对比度 宽高度)
平时看代码会看到很多标点符号的字符拼起来的图案, 特别有趣, 像kong(一个高性能API网关), 除了源代码里面有图案, 命令行也藏了彩蛋. 我今天要玩的会深入一点: 基于图片的灰度值来生成图案. 此时的图片不单单有轮廓, 还有光影效果, 也就是素描中提及的黑白灰.
94 0
设计一个长方形类,成员变量包括长度和宽度,成员函数除包括计算周长和计算面积外,还包括用 Set 方法设置长和宽,以及用 get 方法来获取长
设计一个长方形类,成员变量包括长度和宽度,成员函数除包括计算周长和计算面积外,还包括用 Set 方法设置长和宽,以及用 get 方法来获取长
203 0
|
Java
JDK的drawRect(),实际绘制范围比宽度大一个像素
JDK的drawRect(),实际绘制范围比宽度大一个像素
96 0
JDK的drawRect(),实际绘制范围比宽度大一个像素
|
API Android开发
【Android 内存优化】自定义组件长图组件 ( 获取图像宽高 | 计算解码区域 | 设置图像解码属性 复用 像素格式 | 图像绘制 )
【Android 内存优化】自定义组件长图组件 ( 获取图像宽高 | 计算解码区域 | 设置图像解码属性 复用 像素格式 | 图像绘制 )
171 0
【Android 内存优化】自定义组件长图组件 ( 获取图像宽高 | 计算解码区域 | 设置图像解码属性 复用 像素格式 | 图像绘制 )
|
容器
左边固定宽,右边自适应的6种方法
左边固定宽,右边自适应的6种方法
167 0
|
Android开发
安卓控件显示等宽字体的办法
安卓控件显示等宽字体的办法
178 0