交互的核心控件是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);
}