diff --git a/Modules/Core/include/mitkImageStatisticsHolder.h b/Modules/Core/include/mitkImageStatisticsHolder.h index 55fa1d050d..228528c6c2 100644 --- a/Modules/Core/include/mitkImageStatisticsHolder.h +++ b/Modules/Core/include/mitkImageStatisticsHolder.h @@ -1,174 +1,180 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITKIMAGESTATISTICSHOLDER_H #define MITKIMAGESTATISTICSHOLDER_H #include "mitkImage.h" #include "mitkImageTimeSelector.h" #include #ifndef __itkHistogram_h #include #endif namespace mitk { /** @brief Class holding the statistics informations about a single mitk::Image This computation was previously directly included in the definition and implementation of the mitk::Image class but for having a clear interface, all statistics computation is moved to the ImageStatisticsHolder class. Each mitk::Image holds a normal pointer to its StatisticsHolder object. To get access to the methods, use the - GetStatistics() method - in mitk::Image class. + GetStatistics() method in mitk::Image class. + + Minimum or maximum might by infinite values. 2nd minimum and maximum are guaranteed to be finite values. */ class MITKCORE_EXPORT ImageStatisticsHolder { public: /** Constructor */ ImageStatisticsHolder(mitk::Image *image); /** Desctructor */ virtual ~ImageStatisticsHolder(); typedef itk::Statistics::Histogram HistogramType; virtual const HistogramType *GetScalarHistogram(int t = 0, unsigned int = 0); //##Documentation //## \brief Get the minimum for scalar images. Recomputation performed only when necessary. virtual ScalarType GetScalarValueMin(int t = 0, unsigned int component = 0); //##Documentation //## \brief Get the maximum for scalar images. Recomputation performed only when necessary. virtual ScalarType GetScalarValueMax(int t = 0, unsigned int component = 0); //##Documentation - //## \brief Get the second smallest value for scalar images. Recomputation performed only when necessary. + //## \brief Get the second smallest value for scalar images. + //## Recomputation performed only when necessary. + //## \post The returned value is always a finite value. virtual ScalarType GetScalarValue2ndMin(int t = 0, unsigned int component = 0); //##Documentation //## \brief Get the smallest value for scalar images, but do not recompute it first virtual mitk::ScalarType GetScalarValueMinNoRecompute(unsigned int t = 0) const { if (t < m_ScalarMin.size()) return m_ScalarMin[t]; else return itk::NumericTraits::max(); } //##Documentation //## \brief Get the second smallest value for scalar images, but do not recompute it first + //## \post The returned value is always a finite value. virtual mitk::ScalarType GetScalarValue2ndMinNoRecompute(unsigned int t = 0) const { if (t < m_Scalar2ndMin.size()) return m_Scalar2ndMin[t]; else return itk::NumericTraits::max(); } //##Documentation //## \brief Get the second largest value for scalar images + //## \post The returned value is always a finite value. virtual ScalarType GetScalarValue2ndMax(int t = 0, unsigned int component = 0); //##Documentation //## \brief Get the largest value for scalar images, but do not recompute it first + //## \post The returned value is always a finite value. virtual mitk::ScalarType GetScalarValueMaxNoRecompute(unsigned int t = 0) { if (t < m_ScalarMax.size()) return m_ScalarMax[t]; else return itk::NumericTraits::NonpositiveMin(); } //##Documentation //## \brief Get the second largest value for scalar images, but do not recompute it first virtual mitk::ScalarType GetScalarValue2ndMaxNoRecompute(unsigned int t = 0) { if (t < m_Scalar2ndMax.size()) return m_Scalar2ndMax[t]; else return itk::NumericTraits::NonpositiveMin(); } //##Documentation //## \brief Get the count of voxels with the smallest scalar value in the dataset mitk::ScalarType GetCountOfMinValuedVoxels(int t = 0, unsigned int component = 0); //##Documentation //## \brief Get the count of voxels with the largest scalar value in the dataset mitk::ScalarType GetCountOfMaxValuedVoxels(int t = 0, unsigned int component = 0); //##Documentation //## \brief Get the count of voxels with the largest scalar value in the dataset virtual unsigned int GetCountOfMaxValuedVoxelsNoRecompute(unsigned int t = 0) { if (t < m_CountOfMaxValuedVoxels.size()) return m_CountOfMaxValuedVoxels[t]; else return 0; } //##Documentation //## \brief Get the count of voxels with the smallest scalar value in the dataset virtual unsigned int GetCountOfMinValuedVoxelsNoRecompute(unsigned int t = 0) const { if (t < m_CountOfMinValuedVoxels.size()) return m_CountOfMinValuedVoxels[t]; else return 0; } bool IsValidTimeStep(int t) const; template friend void _ComputeExtremaInItkImage(const ItkImageType *itkImage, mitk::ImageStatisticsHolder *statisticsHolder, int t); template friend void _ComputeExtremaInItkVectorImage(const ItkImageType *itkImage, mitk::ImageStatisticsHolder *statisticsHolder, int t, unsigned int component); protected: virtual void ResetImageStatistics(); virtual void ComputeImageStatistics(int t = 0, unsigned int component = 0); virtual void Expand(unsigned int timeSteps); ImageTimeSelector::Pointer GetTimeSelector(); mitk::Image *m_Image; mutable itk::Object::Pointer m_HistogramGeneratorObject; mutable itk::Object::Pointer m_TimeSelectorForExtremaObject; mutable std::vector m_CountOfMinValuedVoxels; mutable std::vector m_CountOfMaxValuedVoxels; mutable std::vector m_ScalarMin; mutable std::vector m_ScalarMax; mutable std::vector m_Scalar2ndMin; mutable std::vector m_Scalar2ndMax; itk::TimeStamp m_LastRecomputeTimeStamp; }; } // end namespace #endif // MITKIMAGESTATISTICSHOLDER_H diff --git a/Modules/Core/include/mitkLevelWindow.h b/Modules/Core/include/mitkLevelWindow.h index 37775e3f64..6fbb8d8439 100644 --- a/Modules/Core/include/mitkLevelWindow.h +++ b/Modules/Core/include/mitkLevelWindow.h @@ -1,264 +1,267 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef LEVELWINDOW_H_HEADER_INCLUDED_C1F4F02C #define LEVELWINDOW_H_HEADER_INCLUDED_C1F4F02C #include "mitkNumericTypes.h" #include namespace mitk { class Image; /** * @brief The LevelWindow class Class to store level/window values. * * Current min and max value are stored in m_LowerWindowBound and m_UpperWindowBound. - * The maximum and minimum of valid value range is stored in m_RangeMin and m_RangeMax. * 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(); /*! * \brief method that returns the level value, i.e. the center of * the current grey value interval */ ScalarType GetLevel() const; /*! * \brief returns the current window size, i.e the range size of the current grey value interval */ ScalarType GetWindow() const; /*! * \brief method returns the default level value for the image */ ScalarType GetDefaultLevel() const; /*! * \brief returns the default window size for the image */ ScalarType GetDefaultWindow() const; /*! * \brief Resets the level and the window value to the default values */ void ResetDefaultLevelWindow(); /*! * Returns the minimum Value of the window */ ScalarType GetLowerWindowBound() const; /*! * Returns the upper window bound value of the window */ ScalarType GetUpperWindowBound() const; /*! * To set the level and the window value */ void SetLevelWindow(ScalarType level, ScalarType window, bool expandRangesIfNecessary = true); /*! * Set the lower and upper bound of the window */ void SetWindowBounds(ScalarType lowerBound, ScalarType upperBound, bool expandRangesIfNecessary = true); /*! * sets the window to its maximum Size in scaleRange */ void SetToMaxWindowSize(); /*! * Set the range minimum and maximum value */ void SetRangeMinMax(ScalarType min, ScalarType max); /*! * Get the range minimum value */ ScalarType GetRangeMin() const; /*! * Get the range maximum value */ ScalarType GetRangeMax() const; /*! * Get the default range minimum value */ ScalarType GetDefaultLowerBound() const; /*! * Get the default range maximum value */ ScalarType GetDefaultUpperBound() const; /*! * \brief the default min and max range for image will be reset */ void ResetDefaultRangeMinMax(); /**! * \brief returns the size of the grey value range */ ScalarType GetRange() const; /*! * set the default level and window value */ void SetDefaultLevelWindow(ScalarType level, ScalarType window); /*! * set the default Bounderies */ void SetDefaultBoundaries(ScalarType low, ScalarType up); /**! * \brief sets level/window to optimize the contrast of the given Image */ void SetAuto(const Image *image, bool tryPicTags = true, bool guessByCentralSlice = true, unsigned selectedComponent = 0); /**! * \brief sets level/window to the min/max greyvalues of the given Image */ void SetToImageRange(const Image *image); /** * If a level window is set to fixed, the set and get methods won't accept * modifications to the level window settings anymore. This behaviour can * be turned of by setting fixed to false; */ void SetFixed(bool fixed); /** * Returns whether the level window settings are fixed (@see SetFixed(bool)) or not */ bool GetFixed() const; /** * Returns whether the level window settings are fixed (@see SetFixed(bool)) or not */ bool IsFixed() const; /*! * \brief equality operator implementation that allows to compare two level windows */ virtual bool operator==(const LevelWindow &levWin) const; /*! * \brief non equality operator implementation that allows to compare two level windows */ virtual bool operator!=(const LevelWindow &levWin) const; /*! * \brief implementation necessary because operator made * private in itk::Object */ virtual LevelWindow &operator=(const LevelWindow &levWin); /*! * \brief Shows if floating values are accepted */ bool IsFloatingValues() const; /*! * \brief Sets the floating image value */ void SetFloatingValues(bool value); protected: /*! * lower bound of current window */ ScalarType m_LowerWindowBound; /*! * upper bound of current window */ ScalarType m_UpperWindowBound; /*! * minimum gray value of the window */ ScalarType m_RangeMin; /*! * maximum gray value of the window */ ScalarType m_RangeMax; /*! * default minimum gray value of the window */ ScalarType m_DefaultLowerBound; /*! * default maximum gray value of the window */ ScalarType m_DefaultUpperBound; /*! * Image with floating values */ bool m_IsFloatingImage; /*! * Defines whether the level window settings may be changed after * initialization or not. */ bool m_Fixed; /*! * confidence tests * * if m_LowerWindowBound > m_UpperWindowBound, then the values for m_LowerWindowBound and m_UpperWindowBound will be * exchanged * * if m_LowerWindowBound < m_RangeMin, m_LowerWindowBound will be set to m_RangeMin. m_UpperWindowBound will be * decreased the same as m_LowerWindowBound will be increased, but minimum value for m_UpperWindowBound is also * m_RangeMin. * * if m_UpperWindowBound > m_RangeMax, m_UpperWindowBound will be set to m_RangeMax. m_LowerWindowBound will be * increased the same as m_UpperWindowBound will be decreased, but maximum value for m_LowerWindowBound is also * m_RangeMax. * */ inline void EnsureConsistency(); }; } // namespace mitk #endif /* LEVELWINDOW_H_HEADER_INCLUDED_C1F4F02C */ diff --git a/Modules/Core/src/DataManagement/mitkImageStatisticsHolder.cpp b/Modules/Core/src/DataManagement/mitkImageStatisticsHolder.cpp index fffc5876b3..94a091a26d 100644 --- a/Modules/Core/src/DataManagement/mitkImageStatisticsHolder.cpp +++ b/Modules/Core/src/DataManagement/mitkImageStatisticsHolder.cpp @@ -1,390 +1,351 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkImageStatisticsHolder.h" #include "mitkHistogramGenerator.h" -//#include "mitkImageTimeSelector.h" #include mitk::ImageStatisticsHolder::ImageStatisticsHolder(mitk::Image *image) - : m_Image(image) /*, m_TimeSelectorForExtremaObject(nullptr)*/ + : m_Image(image) { m_CountOfMinValuedVoxels.resize(1, 0); m_CountOfMaxValuedVoxels.resize(1, 0); m_ScalarMin.resize(1, itk::NumericTraits::max()); m_ScalarMax.resize(1, itk::NumericTraits::NonpositiveMin()); m_Scalar2ndMin.resize(1, itk::NumericTraits::max()); m_Scalar2ndMax.resize(1, itk::NumericTraits::NonpositiveMin()); mitk::HistogramGenerator::Pointer generator = mitk::HistogramGenerator::New(); m_HistogramGeneratorObject = generator; - - // m_Image = image; - - // create time selector - // this->GetTimeSelector(); } mitk::ImageStatisticsHolder::~ImageStatisticsHolder() { m_HistogramGeneratorObject = nullptr; - // m_TimeSelectorForExtremaObject = nullptr; - // m_Image = nullptr; } const mitk::ImageStatisticsHolder::HistogramType *mitk::ImageStatisticsHolder::GetScalarHistogram( int t, unsigned int /*component*/) { mitk::ImageTimeSelector *timeSelector = this->GetTimeSelector(); if (timeSelector != nullptr) { timeSelector->SetTimeNr(t); timeSelector->UpdateLargestPossibleRegion(); auto *generator = static_cast(m_HistogramGeneratorObject.GetPointer()); generator->SetImage(timeSelector->GetOutput()); generator->ComputeHistogram(); return static_cast(generator->GetHistogram()); } return nullptr; } bool mitk::ImageStatisticsHolder::IsValidTimeStep(int t) const { return m_Image->IsValidTimeStep(t); } mitk::ImageTimeSelector::Pointer mitk::ImageStatisticsHolder::GetTimeSelector() { - // if(m_TimeSelectorForExtremaObject.IsNull()) - //{ - // m_TimeSelectorForExtremaObject = ImageTimeSelector::New(); - ImageTimeSelector::Pointer timeSelector = - ImageTimeSelector::New(); // static_cast( m_TimeSelectorForExtremaObject.GetPointer() ); + ImageTimeSelector::New(); timeSelector->SetInput(m_Image); - //} - return timeSelector; // static_cast( m_TimeSelectorForExtremaObject.GetPointer() ); + return timeSelector; } void mitk::ImageStatisticsHolder::Expand(unsigned int timeSteps) { if (!m_Image->IsValidTimeStep(timeSteps - 1)) return; // The BaseData needs to be expanded, call the mitk::Image::Expand() method m_Image->Expand(timeSteps); if (timeSteps > m_ScalarMin.size()) { m_ScalarMin.resize(timeSteps, itk::NumericTraits::max()); m_ScalarMax.resize(timeSteps, itk::NumericTraits::NonpositiveMin()); m_Scalar2ndMin.resize(timeSteps, itk::NumericTraits::max()); m_Scalar2ndMax.resize(timeSteps, itk::NumericTraits::NonpositiveMin()); m_CountOfMinValuedVoxels.resize(timeSteps, 0); m_CountOfMaxValuedVoxels.resize(timeSteps, 0); } } void mitk::ImageStatisticsHolder::ResetImageStatistics() { m_ScalarMin.assign(1, itk::NumericTraits::max()); m_ScalarMax.assign(1, itk::NumericTraits::NonpositiveMin()); m_Scalar2ndMin.assign(1, itk::NumericTraits::max()); m_Scalar2ndMax.assign(1, itk::NumericTraits::NonpositiveMin()); m_CountOfMinValuedVoxels.assign(1, 0); m_CountOfMaxValuedVoxels.assign(1, 0); } #include "mitkImageAccessByItk.h" //#define BOUNDINGOBJECT_IGNORE template void mitk::_ComputeExtremaInItkImage(const ItkImageType *itkImage, mitk::ImageStatisticsHolder *statisticsHolder, int t) { typename ItkImageType::RegionType region; region = itkImage->GetBufferedRegion(); if (region.Crop(itkImage->GetRequestedRegion()) == false) return; if (region != itkImage->GetRequestedRegion()) return; itk::ImageRegionConstIterator it(itkImage, region); typedef typename ItkImageType::PixelType TPixel; TPixel value = 0; if (statisticsHolder == nullptr || !statisticsHolder->IsValidTimeStep(t)) return; statisticsHolder->Expand(t + 1); // make sure we have initialized all arrays statisticsHolder->m_CountOfMinValuedVoxels[t] = 0; statisticsHolder->m_CountOfMaxValuedVoxels[t] = 0; statisticsHolder->m_Scalar2ndMin[t] = statisticsHolder->m_ScalarMin[t] = itk::NumericTraits::max(); statisticsHolder->m_Scalar2ndMax[t] = statisticsHolder->m_ScalarMax[t] = itk::NumericTraits::NonpositiveMin(); while (!it.IsAtEnd()) { value = it.Get(); -// if ( (value > mitkImage->m_ScalarMin) && (value < mitkImage->m_Scalar2ndMin) ) mitkImage->m_Scalar2ndMin = -// value; -// else if ( (value < mitkImage->m_ScalarMax) && (value > mitkImage->m_Scalar2ndMax) ) mitkImage->m_Scalar2ndMax = -// value; -// else if (value > mitkImage->m_ScalarMax) mitkImage->m_ScalarMax = -// value; -// else if (value < mitkImage->m_ScalarMin) mitkImage->m_ScalarMin = -// value; - -// if numbers start with 2ndMin or 2ndMax and never have that value again, the previous above logic failed #ifdef BOUNDINGOBJECT_IGNORE if (value > -32765) { #endif // update min if (value < statisticsHolder->m_ScalarMin[t]) { statisticsHolder->m_Scalar2ndMin[t] = statisticsHolder->m_ScalarMin[t]; statisticsHolder->m_ScalarMin[t] = value; statisticsHolder->m_CountOfMinValuedVoxels[t] = 1; } else if (value == statisticsHolder->m_ScalarMin[t]) { ++statisticsHolder->m_CountOfMinValuedVoxels[t]; } else if (value < statisticsHolder->m_Scalar2ndMin[t]) { statisticsHolder->m_Scalar2ndMin[t] = value; } // update max if (value > statisticsHolder->m_ScalarMax[t]) { statisticsHolder->m_Scalar2ndMax[t] = statisticsHolder->m_ScalarMax[t]; statisticsHolder->m_ScalarMax[t] = value; statisticsHolder->m_CountOfMaxValuedVoxels[t] = 1; } else if (value == statisticsHolder->m_ScalarMax[t]) { ++statisticsHolder->m_CountOfMaxValuedVoxels[t]; } else if (value > statisticsHolder->m_Scalar2ndMax[t]) { statisticsHolder->m_Scalar2ndMax[t] = value; } #ifdef BOUNDINGOBJECT_IGNORE } #endif ++it; } //// guard for wrong 2dMin/Max on single constant value images if (statisticsHolder->m_ScalarMax[t] == statisticsHolder->m_ScalarMin[t]) { statisticsHolder->m_Scalar2ndMax[t] = statisticsHolder->m_Scalar2ndMin[t] = statisticsHolder->m_ScalarMax[t]; } statisticsHolder->m_LastRecomputeTimeStamp.Modified(); - // MITK_DEBUG <<"extrema "<::NonpositiveMin()<<" "<m_ScalarMin<<" - // "<m_Scalar2ndMin<<" "<m_Scalar2ndMax<<" "<m_ScalarMax<<" - // "<::max(); } template void mitk::_ComputeExtremaInItkVectorImage(const ItkImageType *itkImage, mitk::ImageStatisticsHolder *statisticsHolder, int t, unsigned int component) { typename ItkImageType::RegionType region; region = itkImage->GetBufferedRegion(); if (region.Crop(itkImage->GetRequestedRegion()) == false) return; if (region != itkImage->GetRequestedRegion()) return; itk::ImageRegionConstIterator it(itkImage, region); if (statisticsHolder == nullptr || !statisticsHolder->IsValidTimeStep(t)) return; statisticsHolder->Expand(t + 1); // make sure we have initialized all arrays statisticsHolder->m_CountOfMinValuedVoxels[t] = 0; statisticsHolder->m_CountOfMaxValuedVoxels[t] = 0; statisticsHolder->m_Scalar2ndMin[t] = statisticsHolder->m_ScalarMin[t] = itk::NumericTraits::max(); statisticsHolder->m_Scalar2ndMax[t] = statisticsHolder->m_ScalarMax[t] = itk::NumericTraits::NonpositiveMin(); while (!it.IsAtEnd()) { double value = it.Get()[component]; -// if ( (value > mitkImage->m_ScalarMin) && (value < mitkImage->m_Scalar2ndMin) ) mitkImage->m_Scalar2ndMin = -// value; -// else if ( (value < mitkImage->m_ScalarMax) && (value > mitkImage->m_Scalar2ndMax) ) mitkImage->m_Scalar2ndMax = -// value; -// else if (value > mitkImage->m_ScalarMax) mitkImage->m_ScalarMax = -// value; -// else if (value < mitkImage->m_ScalarMin) mitkImage->m_ScalarMin = -// value; - -// if numbers start with 2ndMin or 2ndMax and never have that value again, the previous above logic failed #ifdef BOUNDINGOBJECT_IGNORE if (value > -32765) { #endif // update min if (value < statisticsHolder->m_ScalarMin[t]) { statisticsHolder->m_Scalar2ndMin[t] = statisticsHolder->m_ScalarMin[t]; statisticsHolder->m_ScalarMin[t] = value; statisticsHolder->m_CountOfMinValuedVoxels[t] = 1; } else if (value == statisticsHolder->m_ScalarMin[t]) { ++statisticsHolder->m_CountOfMinValuedVoxels[t]; } else if (value < statisticsHolder->m_Scalar2ndMin[t]) { statisticsHolder->m_Scalar2ndMin[t] = value; } // update max if (value > statisticsHolder->m_ScalarMax[t]) { statisticsHolder->m_Scalar2ndMax[t] = statisticsHolder->m_ScalarMax[t]; statisticsHolder->m_ScalarMax[t] = value; statisticsHolder->m_CountOfMaxValuedVoxels[t] = 1; } else if (value == statisticsHolder->m_ScalarMax[t]) { ++statisticsHolder->m_CountOfMaxValuedVoxels[t]; } else if (value > statisticsHolder->m_Scalar2ndMax[t]) { statisticsHolder->m_Scalar2ndMax[t] = value; } #ifdef BOUNDINGOBJECT_IGNORE } #endif ++it; } //// guard for wrong 2dMin/Max on single constant value images if (statisticsHolder->m_ScalarMax[t] == statisticsHolder->m_ScalarMin[t]) { statisticsHolder->m_Scalar2ndMax[t] = statisticsHolder->m_Scalar2ndMin[t] = statisticsHolder->m_ScalarMax[t]; } statisticsHolder->m_LastRecomputeTimeStamp.Modified(); - // MITK_DEBUG <<"extrema "<::NonpositiveMin()<<" "<m_ScalarMin<<" - // "<m_Scalar2ndMin<<" "<m_Scalar2ndMax<<" "<m_ScalarMax<<" - // "<::max(); } void mitk::ImageStatisticsHolder::ComputeImageStatistics(int t, unsigned int component) { // timestep valid? if (!m_Image->IsValidTimeStep(t)) return; // image modified? if (this->m_Image->GetMTime() > m_LastRecomputeTimeStamp.GetMTime()) this->ResetImageStatistics(); Expand(t + 1); // do we have valid information already? if (m_ScalarMin[t] != itk::NumericTraits::max() || m_Scalar2ndMin[t] != itk::NumericTraits::max()) return; // Values already calculated before... // used to avoid statistics calculation on Odf images. property will be replaced as soons as bug 17928 is merged and // the diffusion image refactoring is complete. mitk::BoolProperty *isSh = dynamic_cast(m_Image->GetProperty("IsShImage").GetPointer()); mitk::BoolProperty *isOdf = dynamic_cast(m_Image->GetProperty("IsOdfImage").GetPointer()); const mitk::PixelType pType = m_Image->GetPixelType(0); if (pType.GetNumberOfComponents() == 1 && (pType.GetPixelType() != itk::ImageIOBase::UNKNOWNPIXELTYPE) && (pType.GetPixelType() != itk::ImageIOBase::VECTOR)) { // recompute mitk::ImageTimeSelector::Pointer timeSelector = this->GetTimeSelector(); if (timeSelector.IsNotNull()) { timeSelector->SetTimeNr(t); timeSelector->UpdateLargestPossibleRegion(); const mitk::Image *image = timeSelector->GetOutput(); AccessByItk_2(image, _ComputeExtremaInItkImage, this, t); } } else if (pType.GetPixelType() == itk::ImageIOBase::VECTOR && (!isOdf || !isOdf->GetValue()) && (!isSh || !isSh->GetValue())) // we have a vector image { // recompute mitk::ImageTimeSelector::Pointer timeSelector = this->GetTimeSelector(); if (timeSelector.IsNotNull()) { timeSelector->SetTimeNr(t); timeSelector->UpdateLargestPossibleRegion(); const mitk::Image *image = timeSelector->GetOutput(); AccessVectorPixelTypeByItk_n(image, _ComputeExtremaInItkVectorImage, (this, t, component)); } } else { m_ScalarMin[t] = 0; m_ScalarMax[t] = 255; m_Scalar2ndMin[t] = 0; m_Scalar2ndMax[t] = 255; } } mitk::ScalarType mitk::ImageStatisticsHolder::GetScalarValueMin(int t, unsigned int component) { ComputeImageStatistics(t, component); return m_ScalarMin[t]; } mitk::ScalarType mitk::ImageStatisticsHolder::GetScalarValueMax(int t, unsigned int component) { ComputeImageStatistics(t, component); return m_ScalarMax[t]; } mitk::ScalarType mitk::ImageStatisticsHolder::GetScalarValue2ndMin(int t, unsigned int component) { ComputeImageStatistics(t, component); return m_Scalar2ndMin[t]; } mitk::ScalarType mitk::ImageStatisticsHolder::GetScalarValue2ndMax(int t, unsigned int component) { ComputeImageStatistics(t, component); return m_Scalar2ndMax[t]; } mitk::ScalarType mitk::ImageStatisticsHolder::GetCountOfMinValuedVoxels(int t, unsigned int component) { ComputeImageStatistics(t, component); return m_CountOfMinValuedVoxels[t]; } mitk::ScalarType mitk::ImageStatisticsHolder::GetCountOfMaxValuedVoxels(int t, unsigned int component) { ComputeImageStatistics(t, component); return m_CountOfMaxValuedVoxels[t]; } diff --git a/Modules/Core/src/DataManagement/mitkLevelWindow.cpp b/Modules/Core/src/DataManagement/mitkLevelWindow.cpp index 15a5afce57..8e9ac8a779 100644 --- a/Modules/Core/src/DataManagement/mitkLevelWindow.cpp +++ b/Modules/Core/src/DataManagement/mitkLevelWindow.cpp @@ -1,501 +1,511 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkLevelWindow.h" #include "mitkImage.h" #include "mitkImageSliceSelector.h" #include "mitkImageStatisticsHolder.h" #include void mitk::LevelWindow::EnsureConsistency() { // Check if total range is ok { if (m_RangeMin > m_RangeMax) std::swap(m_RangeMin, m_RangeMax); if (m_RangeMin == m_RangeMax) m_RangeMin = m_RangeMax - 1; } // Check if current window is ok { if (m_LowerWindowBound > m_UpperWindowBound) std::swap(m_LowerWindowBound, m_UpperWindowBound); if (m_LowerWindowBound <= m_RangeMin) m_LowerWindowBound = m_RangeMin; if (m_UpperWindowBound <= m_RangeMin) m_UpperWindowBound = m_RangeMin + 1; if (m_LowerWindowBound >= m_RangeMax) m_LowerWindowBound = m_RangeMax - 1; if (m_UpperWindowBound >= m_RangeMax) m_UpperWindowBound = m_RangeMax; if (m_LowerWindowBound == m_UpperWindowBound) { m_UpperWindowBound += 0.5; m_LowerWindowBound -= 0.5; m_UpperWindowBound = std::min(m_UpperWindowBound, m_RangeMax); m_LowerWindowBound = std::max(m_LowerWindowBound, m_RangeMin); } } } mitk::LevelWindow::LevelWindow(mitk::ScalarType level, mitk::ScalarType window) : m_LowerWindowBound(level - window / 2.0), m_UpperWindowBound(level + window / 2.0), m_RangeMin(-2048.0), m_RangeMax(4096.0), m_DefaultLowerBound(-2048.0), m_DefaultUpperBound(4096.0), m_IsFloatingImage(false), m_Fixed(false) { SetDefaultLevelWindow(level, window); SetLevelWindow(level, window, true); } mitk::LevelWindow::LevelWindow(const mitk::LevelWindow &levWin) : m_LowerWindowBound(levWin.GetLowerWindowBound()), m_UpperWindowBound(levWin.GetUpperWindowBound()), m_RangeMin(levWin.GetRangeMin()), m_RangeMax(levWin.GetRangeMax()), m_DefaultLowerBound(levWin.GetDefaultLowerBound()), m_DefaultUpperBound(levWin.GetDefaultUpperBound()), m_IsFloatingImage(levWin.IsFloatingValues()), m_Fixed(levWin.GetFixed()) { } mitk::LevelWindow::~LevelWindow() { } mitk::ScalarType mitk::LevelWindow::GetLevel() const { return (m_UpperWindowBound - m_LowerWindowBound) / 2.0 + m_LowerWindowBound; } mitk::ScalarType mitk::LevelWindow::GetWindow() const { return (m_UpperWindowBound - m_LowerWindowBound); } mitk::ScalarType mitk::LevelWindow::GetDefaultLevel() const { return ((m_DefaultUpperBound + m_DefaultLowerBound) / 2.0); } mitk::ScalarType mitk::LevelWindow::GetDefaultWindow() const { return ((m_DefaultUpperBound - m_DefaultLowerBound)); } void mitk::LevelWindow::ResetDefaultLevelWindow() { SetLevelWindow(GetDefaultLevel(), GetDefaultWindow()); } mitk::ScalarType mitk::LevelWindow::GetLowerWindowBound() const { return m_LowerWindowBound; } mitk::ScalarType mitk::LevelWindow::GetUpperWindowBound() const { return m_UpperWindowBound; } void mitk::LevelWindow::SetDefaultLevelWindow(mitk::ScalarType level, mitk::ScalarType window) { SetDefaultBoundaries((level - (window / 2.0)), (level + (window / 2.0))); } void mitk::LevelWindow::SetLevelWindow(mitk::ScalarType level, mitk::ScalarType window, bool expandRangesIfNecessary) { SetWindowBounds((level - (window / 2.0)), (level + (window / 2.0)), expandRangesIfNecessary); } void mitk::LevelWindow::SetWindowBounds(mitk::ScalarType lowerBound, mitk::ScalarType upperBound, bool expandRangesIfNecessary) { if (IsFixed()) return; m_LowerWindowBound = lowerBound; m_UpperWindowBound = upperBound; if (expandRangesIfNecessary) { /* if caller is sure he wants exactly that level/window, we make sure the limits match */ if (m_LowerWindowBound > m_UpperWindowBound) std::swap(m_LowerWindowBound, m_UpperWindowBound); if (m_LowerWindowBound < m_RangeMin) { m_RangeMin = m_LowerWindowBound; } if (m_UpperWindowBound > m_RangeMax) { m_RangeMax = m_UpperWindowBound; } } EnsureConsistency(); } void mitk::LevelWindow::SetRangeMinMax(mitk::ScalarType min, mitk::ScalarType max) { if (IsFixed()) return; m_RangeMin = min; m_RangeMax = max; EnsureConsistency(); } void mitk::LevelWindow::SetDefaultBoundaries(mitk::ScalarType low, mitk::ScalarType up) { if (IsFixed()) return; m_DefaultLowerBound = low; m_DefaultUpperBound = up; // Check if default window is ok { if (m_DefaultLowerBound > m_DefaultUpperBound) std::swap(m_DefaultLowerBound, m_DefaultUpperBound); if (m_DefaultLowerBound == m_DefaultUpperBound) m_DefaultLowerBound--; } EnsureConsistency(); } void mitk::LevelWindow::SetToMaxWindowSize() { SetWindowBounds(m_RangeMin, m_RangeMax); } mitk::ScalarType mitk::LevelWindow::GetRangeMin() const { return m_RangeMin; } mitk::ScalarType mitk::LevelWindow::GetRangeMax() const { return m_RangeMax; } mitk::ScalarType mitk::LevelWindow::GetRange() const { return m_RangeMax - m_RangeMin; } mitk::ScalarType mitk::LevelWindow::GetDefaultUpperBound() const { return m_DefaultUpperBound; } mitk::ScalarType mitk::LevelWindow::GetDefaultLowerBound() const { return m_DefaultLowerBound; } void mitk::LevelWindow::ResetDefaultRangeMinMax() { SetRangeMinMax(m_DefaultLowerBound, m_DefaultUpperBound); } /*! 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 extrems 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 ((image->GetPixelType().GetComponentType() == 9) || (image->GetPixelType().GetComponentType() == 10)) { // Floating image 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)->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::ImageIOBase::SCALAR && image->GetPixelType().GetComponentType() == itk::ImageIOBase::INT && image->GetPixelType().GetBpe() >= 8) { // the windows compiler complains about ambiguos 'pow' call, therefore static casting to (double, int) if (minValue == -(pow((double)2.0, static_cast(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; } - SetRangeMinMax(minValue, maxValue); - SetDefaultBoundaries(minValue, maxValue); - /* - if ( tryPicTags ) // level and window will be set by informations provided directly by the mitkIpPicDescriptor - { - if ( SetAutoByPicTags(const_cast(image)->GetPic()) ) + + if (!std::isfinite(minValue)) { - return; + minValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(0); } + if (!std::isfinite(maxValue)) + { + maxValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(0); } - */ + + SetRangeMinMax(minValue, maxValue); + SetDefaultBoundaries(minValue, maxValue); unsigned int numPixelsInDataset = image->GetDimensions()[0]; for (unsigned int k = 0; k < image->GetDimension(); ++k) numPixelsInDataset *= image->GetDimensions()[k]; unsigned int minCount = image->GetStatistics()->GetCountOfMinValuedVoxelsNoRecompute(); unsigned int maxCount = image->GetStatistics()->GetCountOfMaxValuedVoxelsNoRecompute(); ScalarType minCountFraction = minCount / ScalarType(numPixelsInDataset); ScalarType maxCountFraction = maxCount / 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); } void mitk::LevelWindow::SetToImageRange(const mitk::Image *image) { if (IsFixed()) return; if (image == nullptr || !image->IsInitialized()) return; ScalarType minValue = image->GetStatistics()->GetScalarValueMin(0); + if (!std::isfinite(minValue)) + { + minValue = image->GetStatistics()->GetScalarValue2ndMinNoRecompute(0); + } ScalarType maxValue = image->GetStatistics()->GetScalarValueMaxNoRecompute(0); + if (!std::isfinite(maxValue)) + { + maxValue = image->GetStatistics()->GetScalarValue2ndMaxNoRecompute(0); + } + SetRangeMinMax(minValue, maxValue); SetDefaultBoundaries(minValue, maxValue); SetWindowBounds(minValue, maxValue); SetDefaultLevelWindow((maxValue - minValue) / 2 + minValue, maxValue - minValue); } void mitk::LevelWindow::SetFixed(bool fixed) { m_Fixed = fixed; } bool mitk::LevelWindow::GetFixed() const { return m_Fixed; } bool mitk::LevelWindow::IsFixed() const { return m_Fixed; } bool mitk::LevelWindow::IsFloatingValues() const { return m_IsFloatingImage; } void mitk::LevelWindow::SetFloatingValues(bool value) { m_IsFloatingImage = value; } bool mitk::LevelWindow::operator==(const mitk::LevelWindow &levWin) const { return mitk::Equal(this->m_RangeMin, levWin.m_RangeMin, mitk::sqrteps) && mitk::Equal(this->m_RangeMax, levWin.m_RangeMax, mitk::sqrteps) && mitk::Equal(this->m_DefaultLowerBound, levWin.m_DefaultLowerBound, mitk::sqrteps) && mitk::Equal(this->m_DefaultUpperBound, levWin.m_DefaultUpperBound, mitk::sqrteps) && mitk::Equal(this->m_LowerWindowBound, levWin.m_LowerWindowBound, mitk::sqrteps) && mitk::Equal(this->m_UpperWindowBound, levWin.m_UpperWindowBound, mitk::sqrteps) && m_Fixed == levWin.IsFixed() && m_IsFloatingImage == levWin.IsFloatingValues(); } bool mitk::LevelWindow::operator!=(const mitk::LevelWindow &levWin) const { return !((*this) == levWin); } mitk::LevelWindow &mitk::LevelWindow::operator=(const mitk::LevelWindow &levWin) { if (this == &levWin) { return *this; } else { m_RangeMin = levWin.GetRangeMin(); m_RangeMax = levWin.GetRangeMax(); m_LowerWindowBound = levWin.GetLowerWindowBound(); m_UpperWindowBound = levWin.GetUpperWindowBound(); m_DefaultLowerBound = levWin.GetDefaultLowerBound(); m_DefaultUpperBound = levWin.GetDefaultUpperBound(); m_Fixed = levWin.GetFixed(); m_IsFloatingImage = levWin.IsFloatingValues(); return *this; } } diff --git a/Modules/QtWidgets/src/QmitkSliderLevelWindowWidget.cpp b/Modules/QtWidgets/src/QmitkSliderLevelWindowWidget.cpp index 6ea65e7bc2..d491d9185c 100644 --- a/Modules/QtWidgets/src/QmitkSliderLevelWindowWidget.cpp +++ b/Modules/QtWidgets/src/QmitkSliderLevelWindowWidget.cpp @@ -1,541 +1,570 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include #include #include #include #include #include #include /** * Constructor */ QmitkSliderLevelWindowWidget::QmitkSliderLevelWindowWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) { m_Manager = mitk::LevelWindowManager::New(); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSliderLevelWindowWidget::OnPropertyModified); m_ObserverTag = m_Manager->AddObserver(itk::ModifiedEvent(), command); m_IsObserverTagSet = true; setMouseTracking(true); m_Resize = false; m_Bottom = false; m_CtrlPressed = false; m_MouseDown = false; m_Font.setPointSize(6); m_MoveHeight = height() - 25; m_ScaleVisible = true; m_Contextmenu = new QmitkLevelWindowWidgetContextMenu(this); //, true); // setBackgroundMode( Qt::NoBackground ); this->hide(); update(); } QmitkSliderLevelWindowWidget::~QmitkSliderLevelWindowWidget() { if (m_IsObserverTagSet) { m_Manager->RemoveObserver(m_ObserverTag); m_IsObserverTagSet = false; } } void QmitkSliderLevelWindowWidget::setLevelWindowManager(mitk::LevelWindowManager *levelWindowManager) { if (m_IsObserverTagSet) { m_Manager->RemoveObserver(m_ObserverTag); m_IsObserverTagSet = false; } m_Manager = levelWindowManager; if (m_Manager.IsNotNull()) { itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSliderLevelWindowWidget::OnPropertyModified); m_ObserverTag = m_Manager->AddObserver(itk::ModifiedEvent(), command); m_IsObserverTagSet = true; } } void QmitkSliderLevelWindowWidget::OnPropertyModified(const itk::EventObject &) { try { m_LevelWindow = m_Manager->GetLevelWindow(); this->show(); update(); } catch (...) { try { this->hide(); } catch (...) { } } } void QmitkSliderLevelWindowWidget::paintEvent(QPaintEvent *itkNotUsed(e)) { QPixmap pm(width(), height()); pm.fill(this->palette().color(this->backgroundRole())); QPainter painter(&pm); painter.setFont(m_Font); painter.setPen(this->palette().color(this->foregroundRole())); QColor c(51, 153, 204); QColor cl = c.light(); QColor cd = c.dark(); painter.setBrush(c); painter.drawRect(m_Rect); float mr = m_LevelWindow.GetRange(); float smallestLevelableValue = 1e-9; + + //This check is needed as safe guard. LevelWindow is refactored to only deduce finite ranges + //from images, but old scene serialization may contain infinite ranges that overwrite the new + //business logic. This roots in two many "jobs" LevelWindow" is used for; see also T24962. + //Until LevelWindow and this widget is refactored the check was the minimal invasive fix. + if (!std::isfinite(mr)) + { + mr = m_LevelWindow.GetWindow(); + } + + // avoiding a division by 0 while still enabling small level windows if (mr < smallestLevelableValue) mr = smallestLevelableValue; float fact = (float)m_MoveHeight / mr; // begin draw scale if (m_ScaleVisible) { double minRange = (double)m_LevelWindow.GetRangeMin(); double maxRange = (double)m_LevelWindow.GetRangeMax(); + + //This check is needed as safe guard. LevelWindow is refactored to only deduce finite ranges + //from images, but old scene serialization may contain infinite ranges that overwrite the new + //business logic. This roots in two many "jobs" LevelWindow" is used for; see also T24962. + //Until LevelWindow and this widget is refactored the check was the minimal invasive fix. + if (!std::isfinite(minRange)) + { + minRange = m_LevelWindow.GetLowerWindowBound(); + } + //This check is needed as safe guard. LevelWindow is refactored to only deduce finite ranges + //from images, but old scene serialization may contain infinite ranges that overwrite the new + //business logic. This roots in two many "jobs" LevelWindow" is used for; see also T24962. + //Until LevelWindow and this widget is refactored the check was the minimal invasive fix. + if (!std::isfinite(maxRange)) + { + maxRange = m_LevelWindow.GetUpperWindowBound(); + } + int yValue = m_MoveHeight + (int)(minRange * fact); QString s = " 0"; if (minRange < 0 && maxRange > 0) { painter.drawLine(5, yValue, 15, yValue); painter.drawText(21, yValue + 3, s); } int count = 1; int k = 5; bool enoughSpace = false; bool enoughSpace2 = false; double dStepSize = pow(10, floor(log10(mr / 100)) + 1); for (int i = m_MoveHeight + (int)(minRange * fact); i < m_MoveHeight;) // negative { if (-count * dStepSize < minRange) { break; } yValue = m_MoveHeight + (int)((minRange + count * dStepSize) * fact); s = QString::number(-count * dStepSize); if (count % k && ((dStepSize * fact) > 2.5)) { painter.drawLine(8, yValue, 12, yValue); enoughSpace = true; } else if (!(count % k)) { if ((k * dStepSize * fact) > 7) { painter.drawLine(5, yValue, 15, yValue); painter.drawText(21, yValue + 3, s); enoughSpace2 = true; } else { k += 5; } } if (enoughSpace) { i = yValue; count++; } else if (enoughSpace2) { i = yValue; count += k; } else { i = yValue; count = k; } } count = 1; k = 5; enoughSpace = false; enoughSpace2 = false; for (int i = m_MoveHeight + (int)(minRange * fact); i >= 0;) { if (count * dStepSize > maxRange) { break; } yValue = m_MoveHeight + (int)((minRange - count * dStepSize) * fact); s = QString::number(count * dStepSize); if (count % k && ((dStepSize * fact) > 2.5)) { if (!(minRange > 0 && (count * dStepSize) < minRange)) painter.drawLine(8, yValue, 12, yValue); enoughSpace = true; } else if (!(count % k)) { if ((k * dStepSize * fact) > 7) { if (!(minRange > 0 && (count * dStepSize) < minRange)) { painter.drawLine(5, yValue, 15, yValue); painter.drawText(21, yValue + 3, s); } enoughSpace2 = true; } else { k += 5; } } if (enoughSpace) { i = yValue; count++; } else if (enoughSpace2) { i = yValue; count += k; } else { i = yValue; count = k; } } } // end draw scale painter.setPen(cl); painter.drawLine(m_Rect.topLeft(), m_Rect.topRight()); painter.drawLine(m_Rect.topLeft(), m_Rect.bottomLeft()); painter.setPen(cd); painter.drawLine(m_Rect.topRight(), m_Rect.bottomRight()); painter.drawLine(m_Rect.bottomRight(), m_Rect.bottomLeft()); painter.end(); QPainter p(this); p.drawPixmap(0, 0, pm); } /** * */ void QmitkSliderLevelWindowWidget::mouseMoveEvent(QMouseEvent *mouseEvent) { if (!mouseEvent) return; if (m_LevelWindow.IsFixed()) return; if (!m_MouseDown) { if (mouseEvent->pos().y() >= 0 && mouseEvent->pos().y() <= (m_Rect.topLeft().y() + 3)) { setCursor(Qt::SizeVerCursor); m_UpperBound.setRect(m_Rect.topLeft().x(), m_Rect.topLeft().y() - 3, 17, 7); this->setToolTip("Ctrl + left click to change only upper bound"); m_Resize = true; } else if (mouseEvent->pos().y() >= (m_Rect.bottomLeft().y() - 3)) { setCursor(Qt::SizeVerCursor); m_LowerBound.setRect(m_Rect.bottomLeft().x(), m_Rect.bottomLeft().y() - 3, 17, 7); this->setToolTip("Ctrl + left click to change only lower bound"); m_Resize = true; m_Bottom = true; } else { setCursor(Qt::ArrowCursor); this->setToolTip("Left click and mouse move to adjust the slider"); m_Resize = false; m_Bottom = false; } } else { float fact = (float)m_MoveHeight / m_LevelWindow.GetRange(); if (m_Leftbutton) { if (m_Resize && !m_CtrlPressed) { double diff = (mouseEvent->pos().y()) / fact; diff -= (m_StartPos.y()) / fact; m_StartPos = mouseEvent->pos(); if (diff == 0) return; float value; if (m_Bottom) value = m_LevelWindow.GetWindow() + ((2 * diff)); else value = m_LevelWindow.GetWindow() - ((2 * diff)); if (value < 0) value = 0; m_LevelWindow.SetLevelWindow(m_LevelWindow.GetLevel(), value); } else if (m_Resize && m_CtrlPressed) { if (!m_Bottom) { double diff = (mouseEvent->pos().y()) / fact; diff -= (m_StartPos.y()) / fact; m_StartPos = mouseEvent->pos(); if (diff == 0) return; float value; value = m_LevelWindow.GetWindow() - ((diff)); if (value < 0) value = 0; float oldWindow; float oldLevel; float newLevel; oldWindow = m_LevelWindow.GetWindow(); oldLevel = m_LevelWindow.GetLevel(); newLevel = oldLevel + (value - oldWindow) / 2; if (!((newLevel + value / 2) > m_LevelWindow.GetRangeMax())) m_LevelWindow.SetLevelWindow(newLevel, value); } else { double diff = (mouseEvent->pos().y()) / fact; diff -= (m_StartPos.y()) / fact; m_StartPos = mouseEvent->pos(); if (diff == 0) return; float value; value = m_LevelWindow.GetWindow() + ((diff)); if (value < 0) value = 0; float oldWindow; float oldLevel; float newLevel; oldWindow = m_LevelWindow.GetWindow(); oldLevel = m_LevelWindow.GetLevel(); newLevel = oldLevel - (value - oldWindow) / 2; if (!((newLevel - value / 2) < m_LevelWindow.GetRangeMin())) m_LevelWindow.SetLevelWindow(newLevel, value); } } else { const float minv = m_LevelWindow.GetRangeMin(); const float level = (m_MoveHeight - mouseEvent->pos().y()) / fact + minv; double diff = (mouseEvent->pos().x()) / fact; diff -= (m_StartPos.x()) / fact; m_StartPos = mouseEvent->pos(); float window; if (m_Bottom) window = m_LevelWindow.GetWindow() + ((2 * diff)); else window = m_LevelWindow.GetWindow() - ((2 * diff)); if (window < 0) window = 0; m_LevelWindow.SetLevelWindow(level, window); } m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } } void QmitkSliderLevelWindowWidget::enterEvent(QEvent * /*event*/) { /* if(event->type() != QEvent::MouseMove) return;*/ // mouseMoveEvent( static_cast< QMouseEvent* > ( event ) ); QPoint p = QCursor::pos(); p = this->mapFromGlobal(p); QMouseEvent ev(QEvent::MouseMove, p, Qt::NoButton, Qt::NoButton, Qt::NoModifier); this->mouseMoveEvent(&ev); } /** * */ void QmitkSliderLevelWindowWidget::mousePressEvent(QMouseEvent *mouseEvent) { if (m_LevelWindow.IsFixed()) return; m_MouseDown = true; m_StartPos = mouseEvent->pos(); if (mouseEvent->button() == Qt::LeftButton) { if (mouseEvent->modifiers() == Qt::ControlModifier || mouseEvent->modifiers() == Qt::ShiftModifier) { m_CtrlPressed = true; } else { m_CtrlPressed = false; } m_Leftbutton = true; } else m_Leftbutton = false; mouseMoveEvent(mouseEvent); } /** * */ void QmitkSliderLevelWindowWidget::resizeEvent(QResizeEvent *event) { m_MoveHeight = event->size().height() - 25; update(); } /** * */ void QmitkSliderLevelWindowWidget::mouseReleaseEvent(QMouseEvent *) { if (m_LevelWindow.IsFixed()) return; m_MouseDown = false; } /** * */ void QmitkSliderLevelWindowWidget::update() { int rectWidth; if (m_ScaleVisible) { rectWidth = 17; setMinimumSize(QSize(50, 50)); setMaximumSize(QSize(50, 2000)); setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding)); } else { rectWidth = 26; setMinimumSize(QSize(40, 50)); setMaximumSize(QSize(50, 2000)); setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding)); } float mr = m_LevelWindow.GetRange(); if (mr < 1e-9) mr = 1e-9; float fact = (float)m_MoveHeight / mr; float rectHeight = m_LevelWindow.GetWindow() * fact; if (rectHeight < 15) rectHeight = 15; if (m_LevelWindow.GetLowerWindowBound() < 0) m_Rect.setRect(2, (int)(m_MoveHeight - (m_LevelWindow.GetUpperWindowBound() - m_LevelWindow.GetRangeMin()) * fact), rectWidth, (int)rectHeight); else m_Rect.setRect(2, (int)(m_MoveHeight - (m_LevelWindow.GetUpperWindowBound() - m_LevelWindow.GetRangeMin()) * fact), rectWidth, (int)rectHeight); QWidget::repaint(); } void QmitkSliderLevelWindowWidget::contextMenuEvent(QContextMenuEvent *) { m_Contextmenu->setLevelWindowManager(m_Manager.GetPointer()); auto contextMenu = new QMenu(this); Q_CHECK_PTR(contextMenu); if (m_ScaleVisible) contextMenu->addAction(tr("Hide Scale"), this, SLOT(hideScale())); else contextMenu->addAction(tr("Show Scale"), this, SLOT(showScale())); contextMenu->addSeparator(); m_Contextmenu->getContextMenu(contextMenu); // Fix: Bug #13327 we need to reset the m_MouseDown value // otherwise the cursor is not correctly restored afterwards m_MouseDown = false; } void QmitkSliderLevelWindowWidget::hideScale() { m_ScaleVisible = false; update(); } void QmitkSliderLevelWindowWidget::showScale() { m_ScaleVisible = true; update(); } void QmitkSliderLevelWindowWidget::setDataStorage(mitk::DataStorage *ds) { m_Manager->SetDataStorage(ds); } mitk::LevelWindowManager *QmitkSliderLevelWindowWidget::GetManager() { return m_Manager.GetPointer(); }