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);
  }
相关文章
|
11天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
8天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2522 18
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
8天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1525 15
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
4天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
10天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
590 14
|
1月前
|
运维 Cloud Native Devops
一线实战:运维人少,我们从 0 到 1 实践 DevOps 和云原生
上海经证科技有限公司为有效推进软件项目管理和开发工作,选择了阿里云云效作为 DevOps 解决方案。通过云效,实现了从 0 开始,到现在近百个微服务、数百条流水线与应用交付的全面覆盖,有效支撑了敏捷开发流程。
19283 30
|
10天前
|
人工智能 自动驾驶 机器人
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
过去22个月,AI发展速度超过任何历史时期,但我们依然还处于AGI变革的早期。生成式AI最大的想象力,绝不是在手机屏幕上做一两个新的超级app,而是接管数字世界,改变物理世界。
491 49
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
|
1月前
|
人工智能 自然语言处理 搜索推荐
阿里云Elasticsearch AI搜索实践
本文介绍了阿里云 Elasticsearch 在AI 搜索方面的技术实践与探索。
18842 20
|
1月前
|
Rust Apache 对象存储
Apache Paimon V0.9最新进展
Apache Paimon V0.9 版本即将发布,此版本带来了多项新特性并解决了关键挑战。Paimon自2022年从Flink社区诞生以来迅速成长,已成为Apache顶级项目,并广泛应用于阿里集团内外的多家企业。
17530 13
Apache Paimon V0.9最新进展
|
2天前
|
云安全 存储 运维
叮咚!您有一份六大必做安全操作清单,请查收
云安全态势管理(CSPM)开启免费试用
367 4
叮咚!您有一份六大必做安全操作清单,请查收