MITK中窗宽窗位相关代码

简介: 本文详细介绍了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);
  }
相关实践学习
基于Hologres轻量实时的高性能OLAP分析
本教程基于GitHub Archive公开数据集,通过DataWorks将GitHub中的项⽬、行为等20多种事件类型数据实时采集至Hologres进行分析,同时使用DataV内置模板,快速搭建实时可视化数据大屏,从开发者、项⽬、编程语⾔等多个维度了解GitHub实时数据变化情况。
阿里云实时数仓实战 - 用户行为数仓搭建
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3&nbsp;)前置知识要求:熟练掌握 SQL 语法熟悉 Linux 命令,对 Hadoop 大数据体系有一定的了解 &nbsp; 课程大纲 第一章&nbsp;了解数据仓库概念 初步了解数据仓库是干什么的 第二章&nbsp;按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章&nbsp;数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章&nbsp;采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章&nbsp;用户行为数据仓库 严格按照企业的标准开发 第六章&nbsp;搭建业务数仓理论基础和对表的分类同步 第七章&nbsp;业务数仓的搭建&nbsp; 业务行为数仓效果图&nbsp;&nbsp;
相关文章
|
Python
pycharm使用debug的时候遇到断点不停的问题
pycharm使用debug的时候遇到断点不停的问题
1106 0
|
存储 负载均衡 安全
Java并发基础:ArrayBlockingQueue全面解析!
ArrayBlockingQueue类是一个高效、线程安全的队列实现,它基于数组,提供了快速的元素访问,并支持多线程间的同步操作,作为有界队列,它能有效防止内存溢出,并通过阻塞机制平衡生产者和消费者的速度差异,它还提供了公平性和非公平性策略,满足不同场景下的需求。
250 1
Java并发基础:ArrayBlockingQueue全面解析!
|
12月前
|
Unix 网络虚拟化 C++
VS2022+Qt5.14.2成功编译MITK2022.10
使用VS2022和Qt5.14.2成功编译MITK2022.10的过程,包括编译结果的截图、遇到的编译问题的解决方法、两个重要的注意事项(patch文件格式的修改和ITK-gitclone-lastrun文件的存在),以及参考链接。文中详细描述了如何解决编译过程中遇到的错误C2220和警告C4819,以及如何修改文件编码和尾行格式。
788 1
VS2022+Qt5.14.2成功编译MITK2022.10
|
12月前
taichi学习记录
关于Taichi编程语言学习的记录,涉及Taichi的安装方法、运行结果截图以及相关学习资源的链接,包括中文语音视频教程、系统学习课程和知乎资料。
112 9
|
12月前
编译QCefView+VS2019+QT5.15.2
本文介绍了如何编译QCefView项目,并在VS2019和Qt 5.15.2环境下集成,包括编译结果、要点、cmake部署Qt的方法和相关参考链接。
742 2
编译QCefView+VS2019+QT5.15.2
|
12月前
|
算法 JavaScript 前端开发
第一个算法项目 | JS实现并查集迷宫算法Demo学习
本文是关于使用JavaScript实现并查集迷宫算法的中国象棋demo的学习记录,包括项目运行方法、知识点梳理、代码赏析以及相关CSS样式表文件的介绍。
第一个算法项目 | JS实现并查集迷宫算法Demo学习
|
12月前
|
Java Maven 开发工具
第一个安卓项目 | 中国象棋demo学习
本文是作者关于其第一个安卓项目——中国象棋demo的学习记录,展示了demo的运行结果、爬坑记录以及参考资料,包括解决Android Studio和maven相关问题的方法。
162 7
第一个安卓项目 | 中国象棋demo学习
|
12月前
|
关系型数据库 MySQL Java
跨专业大白8小时实现计算机准毕业生作品
本文是作者通过模仿前辈的项目,成功实现一个图书管理系统的心得分享,涵盖了Java和MySQL数据库的使用,以及在开发过程中遇到的问题和解决方案。
跨专业大白8小时实现计算机准毕业生作品
|
机器学习/深度学习 存储 人工智能
ONNX 与安全:保护模型免受攻击
【8月更文第27天】随着人工智能和机器学习模型的应用越来越广泛,模型的安全性也成为了人们关注的重点。Open Neural Network Exchange (ONNX) 作为一种开放的标准格式,不仅可以促进不同框架之间的模型共享,还面临着如何保护模型不被恶意攻击的风险。本文将探讨 ONNX 在模型安全方面的考虑,以及如何利用 ONNX 和其他技术来保护模型免受攻击。
618 4
|
12月前
MITK中的数据结构和常量定义
本文介绍了MITK中的数据结构、反射机制、常量定义、DataNode类和类宏定义,包括多图映射、反射接口、事件宏和属性列表等高级特性。
324 0

热门文章

最新文章