diff --git a/Modules/ImageStatistics/Testing/files.cmake b/Modules/ImageStatistics/Testing/files.cmake index 3e962988b6..b01d4dbfb1 100644 --- a/Modules/ImageStatistics/Testing/files.cmake +++ b/Modules/ImageStatistics/Testing/files.cmake @@ -1,11 +1,11 @@ set(MODULE_TESTS - # mitkImageStatisticsCalculatorTest.cpp + # mitkImageStatisticsCalculatorTest.cpp # mitkPointSetStatisticsCalculatorTest.cpp # mitkPointSetDifferenceStatisticsCalculatorTest.cpp # mitkImageStatisticsTextureAnalysisTest.cpp ) set(MODULE_CUSTOM_TESTS # mitkImageStatisticsHotspotTest.cpp # mitkMultiGaussianTest.cpp # TODO: activate test to generate new test cases for mitkImageStatisticsHotspotTest ) diff --git a/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp b/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp index cb42282e4b..0386beb5e7 100644 --- a/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp +++ b/Modules/ImageStatistics/mitkImageStatisticsCalculator.cpp @@ -1,507 +1,515 @@ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "itkImageFileWriter.h" namespace mitk { void ImageStatisticsCalculator::SetInputImage(mitk::Image::Pointer image) { if (image != m_Image) { m_Image = image; m_StatisticsUpdateRequiredByTimeStep.resize(m_Image->GetTimeSteps()); m_StatisticsByTimeStep.resize(m_Image->GetTimeSteps()); this->SetAllStatisticsToUpdateRequired(); } } void ImageStatisticsCalculator::SetMask(mitk::MaskGenerator::Pointer mask) { if (mask != m_MaskGenerator) { m_MaskGenerator = mask; this->SetAllStatisticsToUpdateRequired(); } } void ImageStatisticsCalculator::SetSecondaryMask(mitk::MaskGenerator::Pointer mask) { if (mask != m_SecondaryMaskGenerator) { m_SecondaryMaskGenerator = mask; this->SetAllStatisticsToUpdateRequired(); } } void ImageStatisticsCalculator::SetNBinsForHistogramStatistics(unsigned int nBins) { if (nBins != m_nBinsForHistogramStatistics) { m_nBinsForHistogramStatistics = nBins; this->SetAllStatisticsToUpdateRequired(); } } unsigned int ImageStatisticsCalculator::GetNBinsForHistogramStatistics() const { return m_nBinsForHistogramStatistics; } ImageStatisticsCalculator::StatisticsContainer::Pointer ImageStatisticsCalculator::GetStatistics(unsigned int timeStep, unsigned int label) { if (timeStep >= m_StatisticsByTimeStep.size()) { throw std::runtime_error("invalid timeStep in ImageStatisticsCalculator_v2::GetStatistics"); } if (m_Image.IsNull()) { throw std::runtime_error("no image"); } if (m_MaskGenerator.IsNotNull()) { m_MaskGenerator->SetTimeStep(timeStep); m_InternalMask = m_MaskGenerator->GetMask(); - if (m_SecondaryMaskGenerator.IsNotNull()) - { - m_SecondaryMaskGenerator->SetTimeStep(timeStep); - m_SecondaryMask = m_SecondaryMaskGenerator->GetMask(); - } + } + + if (m_SecondaryMaskGenerator.IsNotNull()) + { + m_SecondaryMaskGenerator->SetTimeStep(timeStep); + m_SecondaryMask = m_SecondaryMaskGenerator->GetMask(); } ImageTimeSelector::Pointer imgTimeSel = ImageTimeSelector::New(); imgTimeSel->SetInput(m_Image); imgTimeSel->SetTimeNr(timeStep); imgTimeSel->UpdateLargestPossibleRegion(); m_ImageTimeSlice = imgTimeSel->GetOutput(); if (m_StatisticsUpdateRequiredByTimeStep[timeStep]) { // Calculate statistics with/without mask - - if (m_MaskGenerator.IsNull()) + if (m_MaskGenerator.IsNull() && m_SecondaryMaskGenerator.IsNull()) { // 1) calculate statistics unmasked: - // plug image into itkstatisticsimagefilter (will be replaced by my awesome filter later on) - // retrieve statistics and save them AccessByItk_1(m_ImageTimeSlice, InternalCalculateStatisticsUnmasked, timeStep) } else { // 2) calculate statistics masked - // extract mask image region - // plug mask and image into itklabelstatisticsimagefilter AccessByItk_1(m_ImageTimeSlice, InternalCalculateStatisticsMasked, timeStep) } m_StatisticsUpdateRequiredByTimeStep[timeStep]=false; } for (std::vector::iterator it = m_StatisticsByTimeStep[timeStep].begin(); it != m_StatisticsByTimeStep[timeStep].end(); ++it) { StatisticsContainer::Pointer statCont = *it; if (statCont->GetLabel() == label) { return statCont; } } + // these lines will ony be executed if the requested label could not be found! MITK_WARN << "Invalid label: " << label << " in time step: " << timeStep; return StatisticsContainer::New(); } void ImageStatisticsCalculator::SetAllStatisticsToUpdateRequired() { for (unsigned int i = 0; i < m_StatisticsUpdateRequiredByTimeStep.size(); i++) { this->SetStatsTimeStepToUpdateRequired(i); } } void ImageStatisticsCalculator::SetStatsTimeStepToUpdateRequired(unsigned int timeStep) { if (timeStep >= m_StatisticsUpdateRequiredByTimeStep.size()) { throw std::runtime_error("invalid timeStep in ImageStatisticsCalculator_v2::SetStatsTimeStepToUpdateRequired"); } m_StatisticsUpdateRequiredByTimeStep[timeStep] = true; m_StatisticsByTimeStep[timeStep].resize(0); } template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalCalculateStatisticsUnmasked( typename itk::Image< TPixel, VImageDimension >* image, unsigned int timeStep) { typedef typename itk::Image< TPixel, VImageDimension > ImageType; typedef typename itk::ExtendedStatisticsImageFilter ImageStatisticsFilterType; typedef typename itk::MinMaxImageFilterWithIndex MinMaxFilterType; StatisticsContainer::Pointer statisticsResult = StatisticsContainer::New(); typename ImageStatisticsFilterType::Pointer statisticsFilter = ImageStatisticsFilterType::New(); statisticsFilter->SetInput(image); statisticsFilter->SetCoordinateTolerance(0.001); statisticsFilter->SetDirectionTolerance(0.001); // TODO: this is single threaded. Implement our own image filter that does this multi threaded // typename itk::MinimumMaximumImageCalculator::Pointer imgMinMaxFilter = itk::MinimumMaximumImageCalculator::New(); // imgMinMaxFilter->SetImage(image); // imgMinMaxFilter->Compute(); vnl_vector minIndex, maxIndex; typename MinMaxFilterType::Pointer minMaxFilter = MinMaxFilterType::New(); minMaxFilter->SetInput(image); minMaxFilter->UpdateLargestPossibleRegion(); typename ImageType::PixelType minval = minMaxFilter->GetMin(); typename ImageType::PixelType maxval = minMaxFilter->GetMax(); typename ImageType::IndexType tmpMinIndex = minMaxFilter->GetMinIndex(); typename ImageType::IndexType tmpMaxIndex = minMaxFilter->GetMaxIndex(); // typename ImageType::IndexType tmpMinIndex = imgMinMaxFilter->GetIndexOfMinimum(); // typename ImageType::IndexType tmpMaxIndex = imgMinMaxFilter->GetIndexOfMaximum(); minIndex.set_size(tmpMaxIndex.GetIndexDimension()); maxIndex.set_size(tmpMaxIndex.GetIndexDimension()); for (unsigned int i=0; i < tmpMaxIndex.GetIndexDimension(); i++) { minIndex[i] = tmpMinIndex[i]; maxIndex[i] = tmpMaxIndex[i]; } statisticsResult->SetMinIndex(minIndex); statisticsResult->SetMaxIndex(maxIndex); //statisticsFilter->SetHistogramParameters(m_nBinsForHistogramStatistics, imgMinMaxFilter->GetMinimum(), imgMinMaxFilter->GetMaximum()); statisticsFilter->SetHistogramParameters(m_nBinsForHistogramStatistics, minval, maxval); try { statisticsFilter->Update(); } catch (const itk::ExceptionObject& e) { mitkThrow() << "Image statistics calculation failed due to following ITK Exception: \n " << e.what(); } // no mask, therefore just one label = the whole image m_StatisticsByTimeStep[timeStep].resize(1); statisticsResult->SetLabel(1); statisticsResult->SetN(image->GetLargestPossibleRegion().GetNumberOfPixels()); statisticsResult->SetMean(statisticsFilter->GetMean()); statisticsResult->SetMin(statisticsFilter->GetMinimum()); statisticsResult->SetMax(statisticsFilter->GetMaximum()); statisticsResult->SetVariance(statisticsFilter->GetVariance()); statisticsResult->SetStd(statisticsFilter->GetSigma()); statisticsResult->SetSkewness(statisticsFilter->GetSkewness()); statisticsResult->SetKurtosis(statisticsFilter->GetKurtosis()); statisticsResult->SetRMS(std::sqrt(std::pow(statisticsFilter->GetMean(), 2.) + statisticsFilter->GetVariance())); // variance = sigma^2 statisticsResult->SetMPP(statisticsFilter->GetMPP()); statisticsResult->SetEntropy(statisticsFilter->GetEntropy()); statisticsResult->SetMedian(statisticsFilter->GetMedian()); statisticsResult->SetUniformity(statisticsFilter->GetUniformity()); statisticsResult->SetUPP(statisticsFilter->GetUPP()); statisticsResult->SetHistogram(statisticsFilter->GetHistogram()); m_StatisticsByTimeStep[timeStep][0] = statisticsResult; } template < typename TPixel, unsigned int VImageDimension > void ImageStatisticsCalculator::InternalCalculateStatisticsMasked( typename itk::Image< TPixel, VImageDimension >* image, unsigned int timeStep) { typedef itk::Image< TPixel, VImageDimension > ImageType; typedef itk::Image< unsigned short, VImageDimension > MaskType; typedef typename MaskType::PixelType LabelPixelType; typedef itk::ExtendedLabelStatisticsImageFilter< ImageType, MaskType > ImageStatisticsFilterType; typedef MaskUtilities< TPixel, VImageDimension > MaskUtilType; typedef typename itk::MinMaxLabelImageFilterWithIndex MinMaxLabelFilterType; typedef typename ImageType::PixelType InputImgPixelType; + // workaround: if m_SecondaryMaskGenerator ist not null but m_MaskGenerator is! (this is the case if we request a 'ignore zuero valued pixels' + // mask in the gui but do not define a primary mask) + bool swapMasks = false; + if (m_SecondaryMask.IsNotNull() && m_InternalMask.IsNull()) + { + m_InternalMask = m_SecondaryMask; + m_SecondaryMask = nullptr; + swapMasks = true; + } + // maskImage has to have the same dimension as image typename MaskType::Pointer maskImage = MaskType::New(); maskImage = ImageToItkImage< unsigned short, VImageDimension >(m_InternalMask); // if we have a secondary mask (say a ignoreZeroPixelMask) we need to combine the masks (corresponds to AND) if (m_SecondaryMask.IsNotNull()) { typename MaskType::Pointer secondaryMaskImage = MaskType::New(); secondaryMaskImage = ImageToItkImage< unsigned short, VImageDimension >(m_SecondaryMask); + // secondary mask should be a ignore zero value pixel mask derived from image. it has to be cropped to the mask region (which may be planar or simply smaller) + typename MaskUtilities::Pointer secondaryMaskMaskUtil = MaskUtilities::New(); + secondaryMaskMaskUtil->SetImage(secondaryMaskImage.GetPointer()); + secondaryMaskMaskUtil->SetMask(maskImage.GetPointer()); + typename MaskType::Pointer adaptedSecondaryMaskImage = secondaryMaskMaskUtil->ExtractMaskImageRegion(); + typename itk::MaskImageFilter2::Pointer maskFilter = itk::MaskImageFilter2::New(); maskFilter->SetInput1(maskImage); - maskFilter->SetInput2(secondaryMaskImage); + maskFilter->SetInput2(adaptedSecondaryMaskImage); maskFilter->SetMaskingValue(1); // all pixels of maskImage where secondaryMaskImage==1 will be kept, all the others are set to 0 maskFilter->UpdateLargestPossibleRegion(); maskImage = maskFilter->GetOutput(); - - // TODO: Check this by outputting the result and verifying it manually } typename MaskUtilType::Pointer maskUtil = MaskUtilType::New(); maskUtil->SetImage(image); maskUtil->SetMask(maskImage.GetPointer()); // if mask is smaller than image, extract the image region where the mask is typename ImageType::Pointer adaptedImage = ImageType::New(); adaptedImage = maskUtil->ExtractMaskImageRegion(); // this also checks mask sanity -// typename itk::ImageFileWriter::Pointer imgFileWriter = itk::ImageFileWriter::New(); -// imgFileWriter->SetInput(adaptedImage); -// imgFileWriter->SetFileName("/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/ct_leber_adapted_image.nrrd"); -// imgFileWriter->Update(); - -// typename itk::ImageFileWriter::Pointer imgFileWriter2 = itk::ImageFileWriter::New(); -// imgFileWriter2->SetInput(maskImage); -// imgFileWriter2->SetFileName("/home/fabian/MITK/MITK_platform_project/bin/MITK-superbuild/MITK-Data/ct_leber_adapted_image_mask.nrrd"); -// imgFileWriter2->Update(); - // find min, max, minindex and maxindex typename MinMaxLabelFilterType::Pointer minMaxFilter = MinMaxLabelFilterType::New(); minMaxFilter->SetInput(adaptedImage); minMaxFilter->SetLabelInput(maskImage); minMaxFilter->UpdateLargestPossibleRegion(); // set histogram parameters for each label individually typedef typename std::map MapType; typedef typename std::pair PairType; std::vector relevantLabels = minMaxFilter->GetRelevantLabels(); MapType minVals; MapType maxVals; std::map nBins; for (LabelPixelType label:relevantLabels) { minVals.insert(PairType(label, minMaxFilter->GetMin(label))); maxVals.insert(PairType(label, minMaxFilter->GetMax(label))); nBins.insert(typename std::pair(label, m_nBinsForHistogramStatistics)); } // minVal = minMaxFilter->GetGlobalMin(); // maxVal = minMaxFilter->GetGlobalMax(); typename ImageStatisticsFilterType::Pointer imageStatisticsFilter = ImageStatisticsFilterType::New(); imageStatisticsFilter->SetDirectionTolerance(0.001); imageStatisticsFilter->SetCoordinateTolerance(0.001); imageStatisticsFilter->SetInput(adaptedImage); imageStatisticsFilter->SetLabelInput(maskImage); //imageStatisticsFilter->SetHistogramParameters(m_nBinsForHistogramStatistics, floor(minVal), ceil(maxVal)); imageStatisticsFilter->SetHistogramParametersForLabels(nBins, minVals, maxVals); imageStatisticsFilter->Update(); std::list labels = imageStatisticsFilter->GetRelevantLabels(); std::list::iterator it = labels.begin(); m_StatisticsByTimeStep[timeStep].resize(0); while(it != labels.end()) { StatisticsContainer::Pointer statisticsResult = StatisticsContainer::New(); // find min, max, minindex and maxindex // make sure to only look in the masked region, use a masker for this vnl_vector minIndex, maxIndex; typename ImageType::IndexType tmpMinIndex = minMaxFilter->GetMinIndex(*it); typename ImageType::IndexType tmpMaxIndex = minMaxFilter->GetMaxIndex(*it); minIndex.set_size(tmpMaxIndex.GetIndexDimension()); maxIndex.set_size(tmpMaxIndex.GetIndexDimension()); for (unsigned int i=0; i < tmpMaxIndex.GetIndexDimension(); i++) { minIndex[i] = tmpMinIndex[i] + (maskImage->GetOrigin()[i] - image->GetOrigin()[i]) / (double) maskImage->GetSpacing()[i]; maxIndex[i] = tmpMaxIndex[i] + (maskImage->GetOrigin()[i] - image->GetOrigin()[i]) / (double) maskImage->GetSpacing()[i]; } statisticsResult->SetMinIndex(minIndex); statisticsResult->SetMaxIndex(maxIndex); // just debug assert(minMaxFilter->GetMax(*it) == imageStatisticsFilter->GetMaximum(*it)); assert(minMaxFilter->GetMin(*it) == imageStatisticsFilter->GetMinimum(*it)); statisticsResult->SetN(imageStatisticsFilter->GetSum(*it) / (double) imageStatisticsFilter->GetMean(*it)); statisticsResult->SetMean(imageStatisticsFilter->GetMean(*it)); statisticsResult->SetMin(imageStatisticsFilter->GetMinimum(*it)); statisticsResult->SetMax(imageStatisticsFilter->GetMaximum(*it)); statisticsResult->SetVariance(imageStatisticsFilter->GetVariance(*it)); statisticsResult->SetStd(imageStatisticsFilter->GetSigma(*it)); statisticsResult->SetSkewness(imageStatisticsFilter->GetSkewness(*it)); statisticsResult->SetKurtosis(imageStatisticsFilter->GetKurtosis(*it)); statisticsResult->SetRMS(std::sqrt(std::pow(imageStatisticsFilter->GetMean(*it), 2.) + imageStatisticsFilter->GetVariance(*it))); // variance = sigma^2 statisticsResult->SetMPP(imageStatisticsFilter->GetMPP(*it)); statisticsResult->SetLabel(*it); statisticsResult->SetEntropy(imageStatisticsFilter->GetEntropy(*it)); statisticsResult->SetMedian(imageStatisticsFilter->GetMedian(*it)); statisticsResult->SetUniformity(imageStatisticsFilter->GetUniformity(*it)); statisticsResult->SetUPP(imageStatisticsFilter->GetUPP(*it)); statisticsResult->SetHistogram(imageStatisticsFilter->GetHistogram(*it)); m_StatisticsByTimeStep[timeStep].push_back(statisticsResult); ++it; } + + // swap maskGenerators back + if (swapMasks) + { + m_SecondaryMask = m_InternalMask; + m_InternalMask = nullptr; + } } ImageStatisticsCalculator::StatisticsContainer::StatisticsContainer(): m_N(std::numeric_limits::max()), m_Mean(std::numeric_limits::max()), m_Min(std::numeric_limits::min()), m_Max(std::numeric_limits::max()), m_Std(std::numeric_limits::max()), m_Variance(std::numeric_limits::max()), m_Skewness(std::numeric_limits::max()), m_Kurtosis(std::numeric_limits::max()), m_RMS(std::numeric_limits::max()), m_MPP(std::numeric_limits::max()), m_Median(std::numeric_limits::max()), m_Uniformity(std::numeric_limits::max()), m_UPP(std::numeric_limits::max()), m_Entropy(std::numeric_limits::max()) { m_minIndex.set_size(0); m_maxIndex.set_size(0); } ImageStatisticsCalculator::statisticsMapType ImageStatisticsCalculator::StatisticsContainer::GetStatisticsAsMap() { ImageStatisticsCalculator::statisticsMapType statisticsAsMap; statisticsAsMap["N"] = m_N; statisticsAsMap["Mean"] = m_Mean; statisticsAsMap["Min"] = m_Min; statisticsAsMap["Max"] = m_Max; statisticsAsMap["StandardDeviation"] = m_Std; statisticsAsMap["Variance"] = m_Variance; statisticsAsMap["Skewness"] = m_Skewness; statisticsAsMap["Kurtosis"] = m_Kurtosis; statisticsAsMap["RMS"] = m_RMS; statisticsAsMap["MPP"] = m_MPP; statisticsAsMap["Median"] = m_Median; statisticsAsMap["Uniformity"] = m_Uniformity; statisticsAsMap["UPP"] = m_UPP; statisticsAsMap["Entropy"] = m_Entropy; return statisticsAsMap; } void ImageStatisticsCalculator::StatisticsContainer::Reset() { m_N = std::numeric_limits::max(); m_Mean = std::numeric_limits::max(); m_Min = std::numeric_limits::max(); m_Max = std::numeric_limits::max(); m_Std = std::numeric_limits::max(); m_Variance = std::numeric_limits::max(); m_Skewness = std::numeric_limits::max(); m_Kurtosis = std::numeric_limits::max(); m_RMS = std::numeric_limits::max(); m_MPP = std::numeric_limits::max(); m_Median = std::numeric_limits::max(); m_Uniformity = std::numeric_limits::max(); m_UPP = std::numeric_limits::max(); m_Entropy = std::numeric_limits::max(); } void ImageStatisticsCalculator::StatisticsContainer::Print() { ImageStatisticsCalculator::statisticsMapType statMap = this->GetStatisticsAsMap(); // print all map key value pairs // const auto& val:statMap for (auto it = statMap.begin(); it != statMap.end(); ++it) { std::cout << it->first << ": " << it->second << std::endl; } // print the min and max index std::cout << "Min Index:" << std::endl; for (auto it = this->GetMinIndex().begin(); it != this->GetMinIndex().end(); it++) { std::cout << *it << " "; } std::cout << std::endl; // print the min and max index std::cout << "Max Index:" << std::endl; for (auto it = this->GetMaxIndex().begin(); it != this->GetMaxIndex().end(); it++) { std::cout << *it << " "; } std::cout << std::endl; } std::string ImageStatisticsCalculator::StatisticsContainer::GetAsString() { std::string res = ""; ImageStatisticsCalculator::statisticsMapType statMap = this->GetStatisticsAsMap(); // print all map key value pairs // const auto& val:statMap for (auto it = statMap.begin(); it != statMap.end(); ++it) { res += std::string(it->first) + ": " + std::to_string(it->second) + "\n"; } // print the min and max index res += "Min Index:" + std::string("\n"); for (auto it = this->GetMinIndex().begin(); it != this->GetMinIndex().end(); it++) { res += std::to_string(*it) + std::string(" "); } res += "\n"; // print the min and max index res += "Max Index:" + std::string("\n"); for (auto it = this->GetMaxIndex().begin(); it != this->GetMaxIndex().end(); it++) { res += std::to_string(*it) + " "; } res += "\n"; return res; } } diff --git a/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp b/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp index 4266f175b2..79da20102c 100644 --- a/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp +++ b/Modules/ImageStatistics/mitkPlanarFigureMaskGenerator.cpp @@ -1,404 +1,404 @@ #include #include #include #include "mitkImageAccessByItk.h" #include #include #include #include #include #include #include - +#include #include #include #include #include #include #include namespace mitk { void PlanarFigureMaskGenerator::SetPlanarFigure(mitk::PlanarFigure::Pointer planarFigure) { if ( planarFigure.IsNull() ) { throw std::runtime_error( "Error: planar figure empty!" ); } if ( !planarFigure->IsClosed() ) { throw std::runtime_error( "Masking not possible for non-closed figures" ); } const PlaneGeometry *planarFigurePlaneGeometry = planarFigure->GetPlaneGeometry(); if ( planarFigurePlaneGeometry == nullptr ) { throw std::runtime_error( "Planar-Figure not yet initialized!" ); } const PlaneGeometry *planarFigureGeometry = dynamic_cast< const PlaneGeometry * >( planarFigurePlaneGeometry ); if ( planarFigureGeometry == nullptr ) { throw std::runtime_error( "Non-planar planar figures not supported!" ); } if (planarFigure != m_PlanarFigure) { m_Modified = true; m_PlanarFigure = planarFigure; } } void PlanarFigureMaskGenerator::SetImage(mitk::Image::Pointer image) { // check dimension unsigned int dimension = image->GetDimension(); if (dimension > 3) { MITK_ERROR << "unsupported image dimension"; } const BaseGeometry *imageGeometry = image->GetGeometry(); if ( imageGeometry == nullptr ) { throw std::runtime_error( "Image geometry invalid!" ); } if (image != m_InternalInputImage) { m_Modified = true; m_InternalInputImage = image; } } template < typename TPixel, unsigned int VImageDimension > void PlanarFigureMaskGenerator::InternalCalculateMaskFromPlanarFigure( const itk::Image< TPixel, VImageDimension > *image, unsigned int axis ) { typedef itk::Image< TPixel, VImageDimension > ImageType; typedef itk::Image< unsigned short, 2 > MaskImage2DType; // typedef itk::CastImageFilter< ImageType, MaskImage2DType > CastFilterType; // Generate mask image as new image with same header as input image and // initialize with 1. // typename CastFilterType::Pointer castFilter = CastFilterType::New(); // castFilter->SetInput( image ); // castFilter->Update(); // castFilter->GetOutput()->FillBuffer( 1 ); typename MaskImage2DType::Pointer maskImage = MaskImage2DType::New(); maskImage->SetOrigin(image->GetOrigin()); maskImage->SetSpacing(image->GetSpacing()); maskImage->SetLargestPossibleRegion(image->GetLargestPossibleRegion()); maskImage->SetBufferedRegion(image->GetBufferedRegion()); maskImage->SetDirection(image->GetDirection()); maskImage->SetNumberOfComponentsPerPixel(image->GetNumberOfComponentsPerPixel()); maskImage->Allocate(); maskImage->FillBuffer(1); // all PolylinePoints of the PlanarFigure are stored in a vtkPoints object. // These points are used by the vtkLassoStencilSource to create // a vtkImageStencil. const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); const typename PlanarFigure::PolyLineType planarFigurePolyline = m_PlanarFigure->GetPolyLine( 0 ); const mitk::BaseGeometry *imageGeometry3D = m_InternalInputImage->GetGeometry( 0 ); // If there is a second poly line in a closed planar figure, treat it as a hole. PlanarFigure::PolyLineType planarFigureHolePolyline; if (m_PlanarFigure->GetPolyLinesSize() == 2) planarFigureHolePolyline = m_PlanarFigure->GetPolyLine(1); // Determine x- and y-dimensions depending on principal axis // TODO use plane geometry normal to determine that automatically, then check whether the PF is aligned with one of the three principal axis int i0, i1; switch ( axis ) { case 0: i0 = 1; i1 = 2; break; case 1: i0 = 0; i1 = 2; break; case 2: default: i0 = 0; i1 = 1; break; } // store the polyline contour as vtkPoints object bool outOfBounds = false; vtkSmartPointer points = vtkSmartPointer::New(); typename PlanarFigure::PolyLineType::const_iterator it; for ( it = planarFigurePolyline.begin(); it != planarFigurePolyline.end(); ++it ) { Point3D point3D; // Convert 2D point back to the local index coordinates of the selected // image // Fabian: From PlaneGeometry documentation: // Converts a 2D point given in mm (pt2d_mm) relative to the upper-left corner of the geometry into the corresponding world-coordinate (a 3D point in mm, pt3d_mm). // To convert a 2D point given in units (e.g., pixels in case of an image) into a 2D point given in mm (as required by this method), use IndexToWorld. planarFigurePlaneGeometry->Map( *it, point3D ); // Polygons (partially) outside of the image bounds can not be processed // further due to a bug in vtkPolyDataToImageStencil if ( !imageGeometry3D->IsInside( point3D ) ) { outOfBounds = true; } imageGeometry3D->WorldToIndex( point3D, point3D ); points->InsertNextPoint( point3D[i0], point3D[i1], 0 ); } vtkSmartPointer holePoints = nullptr; if (!planarFigureHolePolyline.empty()) { holePoints = vtkSmartPointer::New(); Point3D point3D; PlanarFigure::PolyLineType::const_iterator end = planarFigureHolePolyline.end(); for (it = planarFigureHolePolyline.begin(); it != end; ++it) { // Fabian: same as above planarFigurePlaneGeometry->Map(*it, point3D); imageGeometry3D->WorldToIndex(point3D, point3D); holePoints->InsertNextPoint(point3D[i0], point3D[i1], 0); } } // mark a malformed 2D planar figure ( i.e. area = 0 ) as out of bounds // this can happen when all control points of a rectangle lie on the same line = two of the three extents are zero double bounds[6] = {0, 0, 0, 0, 0, 0}; points->GetBounds( bounds ); bool extent_x = (fabs(bounds[0] - bounds[1])) < mitk::eps; bool extent_y = (fabs(bounds[2] - bounds[3])) < mitk::eps; bool extent_z = (fabs(bounds[4] - bounds[5])) < mitk::eps; // throw an exception if a closed planar figure is deformed, i.e. has only one non-zero extent if ( m_PlanarFigure->IsClosed() && ((extent_x && extent_y) || (extent_x && extent_z) || (extent_y && extent_z))) { mitkThrow() << "Figure has a zero area and cannot be used for masking."; } if ( outOfBounds ) { throw std::runtime_error( "Figure at least partially outside of image bounds!" ); } // create a vtkLassoStencilSource and set the points of the Polygon vtkSmartPointer lassoStencil = vtkSmartPointer::New(); lassoStencil->SetShapeToPolygon(); lassoStencil->SetPoints( points ); vtkSmartPointer holeLassoStencil = nullptr; if (holePoints.GetPointer() != nullptr) { holeLassoStencil = vtkSmartPointer::New(); holeLassoStencil->SetShapeToPolygon(); holeLassoStencil->SetPoints(holePoints); } // Export from ITK to VTK (to use a VTK filter) typedef itk::VTKImageImport< MaskImage2DType > ImageImportType; typedef itk::VTKImageExport< MaskImage2DType > ImageExportType; typename ImageExportType::Pointer itkExporter = ImageExportType::New(); itkExporter->SetInput( maskImage ); // itkExporter->SetInput( castFilter->GetOutput() ); vtkSmartPointer vtkImporter = vtkSmartPointer::New(); this->ConnectPipelines( itkExporter, vtkImporter ); // Apply the generated image stencil to the input image vtkSmartPointer imageStencilFilter = vtkSmartPointer::New(); imageStencilFilter->SetInputConnection( vtkImporter->GetOutputPort() ); imageStencilFilter->SetStencilConnection(lassoStencil->GetOutputPort()); imageStencilFilter->ReverseStencilOff(); imageStencilFilter->SetBackgroundValue( 0 ); imageStencilFilter->Update(); vtkSmartPointer holeStencilFilter = nullptr; if (holeLassoStencil.GetPointer() != nullptr) { holeStencilFilter = vtkSmartPointer::New(); holeStencilFilter->SetInputConnection(imageStencilFilter->GetOutputPort()); holeStencilFilter->SetStencilConnection(holeLassoStencil->GetOutputPort()); holeStencilFilter->ReverseStencilOn(); holeStencilFilter->SetBackgroundValue(0); holeStencilFilter->Update(); } // Export from VTK back to ITK vtkSmartPointer vtkExporter = vtkSmartPointer::New(); vtkExporter->SetInputConnection( holeStencilFilter.GetPointer() == nullptr ? imageStencilFilter->GetOutputPort() : holeStencilFilter->GetOutputPort()); vtkExporter->Update(); typename ImageImportType::Pointer itkImporter = ImageImportType::New(); this->ConnectPipelines( vtkExporter, itkImporter ); itkImporter->Update(); typedef itk::ImageDuplicator< ImageImportType::OutputImageType > DuplicatorType; DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage( itkImporter->GetOutput() ); duplicator->Update(); // Store mask m_InternalITKImageMask2D = duplicator->GetOutput(); } bool PlanarFigureMaskGenerator::GetPrincipalAxis( const BaseGeometry *geometry, Vector3D vector, unsigned int &axis ) { vector.Normalize(); for ( unsigned int i = 0; i < 3; ++i ) { Vector3D axisVector = geometry->GetAxisVector( i ); axisVector.Normalize(); if ( fabs( fabs( axisVector * vector ) - 1.0) < mitk::eps ) { axis = i; return true; } } return false; } void PlanarFigureMaskGenerator::CalculateMask() { if (m_InternalInputImage->GetTimeSteps() > 0) { mitk::ImageTimeSelector::Pointer imgTimeSel = mitk::ImageTimeSelector::New(); imgTimeSel->SetInput(m_InternalInputImage); imgTimeSel->SetTimeNr(m_TimeStep); imgTimeSel->UpdateLargestPossibleRegion(); m_InternalTimeSliceImage = imgTimeSel->GetOutput(); } else { m_InternalTimeSliceImage = m_InternalInputImage; } m_InternalITKImageMask2D = nullptr; const PlaneGeometry *planarFigurePlaneGeometry = m_PlanarFigure->GetPlaneGeometry(); const PlaneGeometry *planarFigureGeometry = dynamic_cast< const PlaneGeometry * >( planarFigurePlaneGeometry ); const BaseGeometry *imageGeometry = m_InternalInputImage->GetGeometry(); // Find principal direction of PlanarFigure in input image unsigned int axis; if ( !this->GetPrincipalAxis( imageGeometry, planarFigureGeometry->GetNormal(), axis ) ) { throw std::runtime_error( "Non-aligned planar figures not supported!" ); } m_PlanarFigureAxis = axis; // Find slice number corresponding to PlanarFigure in input image typename itk::Image< unsigned short, 3 >::IndexType index; imageGeometry->WorldToIndex( planarFigureGeometry->GetOrigin(), index ); unsigned int slice = index[axis]; // extract image slice which corresponds to the planarFigure and sotre it in m_InternalImageSlice mitk::Image::Pointer inputImageSlice = extract2DImageSlice(axis, slice); // Compute mask from PlanarFigure AccessFixedDimensionByItk_1(inputImageSlice, InternalCalculateMaskFromPlanarFigure, 2, axis) //convert itk mask to mitk::Image::Pointer and return it mitk::Image::Pointer planarFigureMaskImage; planarFigureMaskImage = mitk::GrabItkImageMemory(m_InternalITKImageMask2D); planarFigureMaskImage->SetGeometry(inputImageSlice->GetGeometry()); Convert2Dto3DImageFilter::Pointer sliceTo3DImageConverter = Convert2Dto3DImageFilter::New(); sliceTo3DImageConverter->SetInput(planarFigureMaskImage); sliceTo3DImageConverter->Update(); m_InternalMask = sliceTo3DImageConverter->GetOutput(); } mitk::Image::Pointer PlanarFigureMaskGenerator::GetMask() { if (m_Modified) { if (m_InternalInputImage.IsNull()) { MITK_ERROR << "Image is not set."; } if (m_PlanarFigure.IsNull()) { MITK_ERROR << "PlanarFigure is not set."; } if (m_TimeStep != 0) { MITK_WARN << "Multiple TimeSteps are not supported in PlanarFigureMaskGenerator (yet)."; } this->CalculateMask(); } m_Modified = false; return m_InternalMask; } mitk::Image::Pointer PlanarFigureMaskGenerator::extract2DImageSlice(unsigned int axis, unsigned int slice) { // Extract slice with given position and direction from image unsigned int dimension = m_InternalTimeSliceImage->GetDimension(); mitk::Image::Pointer imageSlice = mitk::Image::New(); if (dimension == 3) { ExtractImageFilter::Pointer imageExtractor = ExtractImageFilter::New(); imageExtractor->SetInput( m_InternalTimeSliceImage ); imageExtractor->SetSliceDimension( axis ); imageExtractor->SetSliceIndex( slice ); imageExtractor->Update(); imageSlice = imageExtractor->GetOutput(); } else if(dimension == 2) { imageSlice = m_InternalTimeSliceImage; } else { MITK_ERROR << "Unsupported image dimension. Dimension is: " << dimension << ". Only 2D and 3D images are supported."; } return imageSlice; } } diff --git a/Modules/QtWidgetsExt/src/QmitkHistogramJSWidget.cpp b/Modules/QtWidgetsExt/src/QmitkHistogramJSWidget.cpp index 7d8382bff8..72db145e9f 100644 --- a/Modules/QtWidgetsExt/src/QmitkHistogramJSWidget.cpp +++ b/Modules/QtWidgetsExt/src/QmitkHistogramJSWidget.cpp @@ -1,247 +1,249 @@ /*=================================================================== 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 "QmitkHistogramJSWidget.h" #include "mitkPixelTypeMultiplex.h" #include #include #include "mitkRenderingManager.h" #include "mitkBaseRenderer.h" #include "mitkImageTimeSelector.h" #include "mitkExtractSliceFilter.h" #include QmitkHistogramJSWidget::QmitkHistogramJSWidget(QWidget *parent) : QWebView(parent) { // set histogram type to barchart in first instance m_UseLineGraph = false; m_Page = new QmitkJSWebPage(this); setPage(m_Page); // set html from source connect(page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(AddJSObject())); QUrl myUrl = QUrl("qrc:///QtWidgetsExt/Histogram.html"); setUrl(myUrl); // set Scrollbars to be always disabled page()->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); page()->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); m_ParametricPath = ParametricPathType::New(); + m_Statistics = mitk::ImageStatisticsCalculator::StatisticsContainer::New(); + } QmitkHistogramJSWidget::~QmitkHistogramJSWidget() { } // adds an Object of Type QmitkHistogramJSWidget to the JavaScript, using QtWebkitBridge void QmitkHistogramJSWidget::AddJSObject() { page()->mainFrame()->addToJavaScriptWindowObject(QString("histogramData"), this); } // reloads WebView, everytime its size has been changed, so the size of the Histogram fits to the size of the widget void QmitkHistogramJSWidget::resizeEvent(QResizeEvent* resizeEvent) { QWebView::resizeEvent(resizeEvent); // workaround for Qt Bug: https://bugs.webkit.org/show_bug.cgi?id=75984 page()->mainFrame()->evaluateJavaScript("disconnectSignals()"); this->reload(); } // method to expose data to JavaScript by using properties void QmitkHistogramJSWidget::ComputeHistogram(HistogramType* histogram) { m_Histogram = histogram; HistogramConstIteratorType startIt = m_Histogram->End(); HistogramConstIteratorType endIt = m_Histogram->End(); HistogramConstIteratorType it = m_Histogram->Begin(); ClearData(); unsigned int i = 0; bool firstValue = false; // removes frequencies of 0, which are outside the first and last bin for (; it != m_Histogram->End(); ++it) { if (it.GetFrequency() > 0.0) { endIt = it; if (!firstValue) { firstValue = true; startIt = it; } } } ++endIt; // generating Lists of measurement and frequencies for (it = startIt ; it != endIt; ++it, ++i) { QVariant frequency = QVariant::fromValue(it.GetFrequency()); QVariant measurement = it.GetMeasurementVector()[0]; m_Frequency.insert(i, frequency); m_Measurement.insert(i, measurement); } m_IntensityProfile = false; this->SignalDataChanged(); } void QmitkHistogramJSWidget::ClearData() { m_Frequency.clear(); m_Measurement.clear(); } void QmitkHistogramJSWidget::ClearHistogram() { this->ClearData(); this->SignalDataChanged(); } QList QmitkHistogramJSWidget::GetFrequency() { return m_Frequency; } QList QmitkHistogramJSWidget::GetMeasurement() { return m_Measurement; } bool QmitkHistogramJSWidget::GetUseLineGraph() { return m_UseLineGraph; } void QmitkHistogramJSWidget::OnBarRadioButtonSelected() { if (m_UseLineGraph) { m_UseLineGraph = false; this->SignalGraphChanged(); } } void QmitkHistogramJSWidget::OnLineRadioButtonSelected() { if (!m_UseLineGraph) { m_UseLineGraph = true; this->SignalGraphChanged(); } } void QmitkHistogramJSWidget::SetImage(mitk::Image* image) { m_Image = image; } void QmitkHistogramJSWidget::SetPlanarFigure(const mitk::PlanarFigure* planarFigure) { m_PlanarFigure = planarFigure; } template void ReadPixel(mitk::PixelType, mitk::Image::Pointer image, itk::Index<3> indexPoint, double& value) { if (image->GetDimension() == 2) { mitk::ImagePixelReadAccessor readAccess(image, image->GetSliceData(0)); itk::Index<2> idx; idx[0] = indexPoint[0]; idx[1] = indexPoint[1]; value = readAccess.GetPixelByIndex(idx); } else if (image->GetDimension() == 3) { mitk::ImagePixelReadAccessor readAccess(image, image->GetVolumeData(0)); itk::Index<3> idx; idx[0] = indexPoint[0]; idx[1] = indexPoint[1]; idx[2] = indexPoint[2]; value = readAccess.GetPixelByIndex(idx); } else { //unhandled } } void QmitkHistogramJSWidget::ComputeIntensityProfile(unsigned int timeStep, bool computeStatistics) { this->ClearData(); m_ParametricPath->Initialize(); if (m_PlanarFigure.IsNull()) { mitkThrow() << "PlanarFigure not set!"; } if (m_Image.IsNull()) { mitkThrow() << "Image not set!"; } mitk::Image::Pointer image; if (m_Image->GetDimension() == 4) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Image); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); image = timeSelector->GetOutput(); } else { image = m_Image; } mitk::IntensityProfile::Pointer intensityProfile = mitk::ComputeIntensityProfile(image, const_cast(m_PlanarFigure.GetPointer())); m_Frequency.clear(); m_Measurement.clear(); int i = -1; mitk::IntensityProfile::ConstIterator end = intensityProfile->End(); for (mitk::IntensityProfile::ConstIterator it = intensityProfile->Begin(); it != end; ++it) { m_Frequency.push_back(it.GetMeasurementVector()[0]); m_Measurement.push_back(++i); } if ( computeStatistics ) { mitk::ComputeIntensityProfileStatistics( intensityProfile, m_Statistics ); } m_IntensityProfile = true; m_UseLineGraph = true; this->SignalDataChanged(); } bool QmitkHistogramJSWidget::GetIntensityProfile() { return m_IntensityProfile; } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.cpp index a09f5b24f5..a163340ca4 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsCalculationThread.cpp @@ -1,223 +1,240 @@ /*=================================================================== 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 "QmitkImageStatisticsCalculationThread.h" //QT headers #include #include #include #include #include QmitkImageStatisticsCalculationThread::QmitkImageStatisticsCalculationThread():QThread(), m_StatisticsImage(nullptr), m_BinaryMask(nullptr), m_PlanarFigureMask(nullptr), m_TimeStep(0), m_IgnoreZeros(false), m_CalculationSuccessful(false), m_StatisticChanged(false), m_HistogramBinSize(1.0), m_UseDefaultBinSize(true) { } QmitkImageStatisticsCalculationThread::~QmitkImageStatisticsCalculationThread() { } void QmitkImageStatisticsCalculationThread::Initialize( mitk::Image::Pointer image, mitk::Image::Pointer binaryImage, mitk::PlanarFigure::Pointer planarFig ) { // reset old values if( this->m_StatisticsImage.IsNotNull() ) this->m_StatisticsImage = nullptr; if( this->m_BinaryMask.IsNotNull() ) this->m_BinaryMask = nullptr; if( this->m_PlanarFigureMask.IsNotNull()) this->m_PlanarFigureMask = nullptr; // set new values if passed in if(image.IsNotNull()) this->m_StatisticsImage = image->Clone(); if(binaryImage.IsNotNull()) this->m_BinaryMask = binaryImage->Clone(); if(planarFig.IsNotNull()) this->m_PlanarFigureMask = planarFig->Clone(); } void QmitkImageStatisticsCalculationThread::SetUseDefaultBinSize(bool useDefault) { m_UseDefaultBinSize = useDefault; } void QmitkImageStatisticsCalculationThread::SetTimeStep( int times ) { this->m_TimeStep = times; } int QmitkImageStatisticsCalculationThread::GetTimeStep() { return this->m_TimeStep; } std::vector QmitkImageStatisticsCalculationThread::GetStatisticsData() { return this->m_StatisticsVector; } mitk::Image::Pointer QmitkImageStatisticsCalculationThread::GetStatisticsImage() { return this->m_StatisticsImage; } void QmitkImageStatisticsCalculationThread::SetIgnoreZeroValueVoxel(bool _arg) { this->m_IgnoreZeros = _arg; } bool QmitkImageStatisticsCalculationThread::GetIgnoreZeroValueVoxel() { return this->m_IgnoreZeros; } void QmitkImageStatisticsCalculationThread::SetHistogramBinSize(double size) { this->m_HistogramBinSize = size; } double QmitkImageStatisticsCalculationThread::GetHistogramBinSize() { return this->m_HistogramBinSize; } std::string QmitkImageStatisticsCalculationThread::GetLastErrorMessage() { return m_message; } QmitkImageStatisticsCalculationThread::HistogramType::Pointer QmitkImageStatisticsCalculationThread::GetTimeStepHistogram(unsigned int t) { if (t >= this->m_HistogramVector.size()) return nullptr; return this->m_HistogramVector[t]; } bool QmitkImageStatisticsCalculationThread::GetStatisticsChangedFlag() { return m_StatisticChanged; } bool QmitkImageStatisticsCalculationThread::GetStatisticsUpdateSuccessFlag() { return m_CalculationSuccessful; } void QmitkImageStatisticsCalculationThread::run() { bool statisticCalculationSuccessful = true; mitk::ImageStatisticsCalculator::Pointer calculator = mitk::ImageStatisticsCalculator::New(); if(this->m_StatisticsImage.IsNotNull()) { calculator->SetInputImage(m_StatisticsImage); } else { statisticCalculationSuccessful = false; } // Bug 13416 : The ImageStatistics::SetImageMask() method can throw exceptions, i.e. when the dimensionality // of the masked and input image differ, we need to catch them and mark the calculation as failed // the same holds for the ::SetPlanarFigure() try { if(this->m_BinaryMask.IsNotNull()) { mitk::ImageMaskGenerator::Pointer imgMask = mitk::ImageMaskGenerator::New(); imgMask->SetImageMask(m_BinaryMask); calculator->SetMask(imgMask.GetPointer()); } if(this->m_PlanarFigureMask.IsNotNull()) { mitk::PlanarFigureMaskGenerator::Pointer pfMaskGen = mitk::PlanarFigureMaskGenerator::New(); pfMaskGen->SetImage(m_StatisticsImage); pfMaskGen->SetPlanarFigure(m_PlanarFigureMask); calculator->SetMask(pfMaskGen.GetPointer()); } } catch( const itk::ExceptionObject& e) { MITK_ERROR << "ITK Exception:" << e.what(); statisticCalculationSuccessful = false; } + catch( const mitk::Exception& e ) + { + MITK_ERROR<< "MITK Exception: " << e.what(); + statisticCalculationSuccessful = false; + } + catch ( const std::runtime_error &e ) + { + MITK_ERROR<< "Runtime Exception: " << e.what(); + statisticCalculationSuccessful = false; + } + catch ( const std::exception &e ) + { + //m_message = "Failure: " + std::string(e.what()); + MITK_ERROR<< "Standard Exception: " << e.what(); + statisticCalculationSuccessful = false; + } + bool statisticChanged = false; if (this->m_IgnoreZeros) { mitk::IgnorePixelMaskGenerator::Pointer ignorePixelValueMaskGen = mitk::IgnorePixelMaskGenerator::New(); ignorePixelValueMaskGen->SetIgnoredPixelValue(0); ignorePixelValueMaskGen->SetImage(m_StatisticsImage); calculator->SetSecondaryMask(ignorePixelValueMaskGen.GetPointer()); } else { calculator->SetSecondaryMask(nullptr); } calculator->SetNBinsForHistogramStatistics(m_HistogramBinSize); //calculator->SetHistogramBinSize( m_HistogramBinSize ); //calculator->SetUseDefaultBinSize( m_UseDefaultBinSize ); for (unsigned int i = 0; i < m_StatisticsImage->GetTimeSteps(); i++) { try { calculator->GetStatistics(i); } catch ( mitk::Exception& e) { //m_message = e.GetDescription(); MITK_ERROR<< "MITK Exception: " << e.what(); statisticCalculationSuccessful = false; } catch ( const std::runtime_error &e ) { //m_message = "Failure: " + std::string(e.what()); MITK_ERROR<< "Runtime Exception: " << e.what(); statisticCalculationSuccessful = false; } catch ( const std::exception &e ) { //m_message = "Failure: " + std::string(e.what()); MITK_ERROR<< "Standard Exception: " << e.what(); statisticCalculationSuccessful = false; } } this->m_StatisticChanged = statisticChanged; this->m_CalculationSuccessful = statisticCalculationSuccessful; if(statisticCalculationSuccessful) { this->m_StatisticsVector.clear(); this->m_HistogramVector.clear(); for (unsigned int i = 0; i < m_StatisticsImage->GetTimeSteps(); i++) { this->m_StatisticsVector.push_back(calculator->GetStatistics(i)); this->m_HistogramVector.push_back((HistogramType*)this->m_StatisticsVector[i]->GetHistogram()); } } m_HistogramBinSize = calculator->GetNBinsForHistogramStatistics(); } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp index 6578b8c074..79b2f9f0a2 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsView.cpp @@ -1,1211 +1,1317 @@ /*=================================================================== 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 "QmitkImageStatisticsView.h" // Qt includes #include #include #include // berry includes #include // mitk includes #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateOr.h" #include "mitkPlanarFigureInteractor.h" // itk includes #include "itksys/SystemTools.hxx" #include #include +#include + const std::string QmitkImageStatisticsView::VIEW_ID = "org.mitk.views.imagestatistics"; const int QmitkImageStatisticsView::STAT_TABLE_BASE_HEIGHT = 180; QmitkImageStatisticsView::QmitkImageStatisticsView(QObject* /*parent*/, const char* /*name*/) : m_Controls( NULL ), m_TimeStepperAdapter( NULL ), m_SelectedImage( NULL ), m_SelectedImageMask( NULL ), m_SelectedPlanarFigure( NULL ), m_ImageObserverTag( -1 ), m_ImageMaskObserverTag( -1 ), m_PlanarFigureObserverTag( -1 ), m_TimeObserverTag( -1 ), m_CurrentStatisticsValid( false ), m_StatisticsUpdatePending( false ), m_DataNodeSelectionChanged ( false ), m_Visible(false) { this->m_CalculationThread = new QmitkImageStatisticsCalculationThread; } QmitkImageStatisticsView::~QmitkImageStatisticsView() { if ( m_SelectedImage != NULL ) m_SelectedImage->RemoveObserver( m_ImageObserverTag ); if ( m_SelectedImageMask != NULL ) m_SelectedImageMask->RemoveObserver( m_ImageMaskObserverTag ); if ( m_SelectedPlanarFigure != NULL ) m_SelectedPlanarFigure->RemoveObserver( m_PlanarFigureObserverTag ); while(this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } delete this->m_CalculationThread; } void QmitkImageStatisticsView::CreateQtPartControl(QWidget *parent) { if (m_Controls == NULL) { m_Controls = new Ui::QmitkImageStatisticsViewControls; m_Controls->setupUi(parent); CreateConnections(); m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_BinSizeFrame->setVisible(false); } } void QmitkImageStatisticsView::CreateConnections() { if ( m_Controls ) { connect( (QObject*)(this->m_Controls->m_ButtonCopyHistogramToClipboard), SIGNAL(clicked()),(QObject*) this, SLOT(OnClipboardHistogramButtonClicked()) ); connect( (QObject*)(this->m_Controls->m_ButtonCopyStatisticsToClipboard), SIGNAL(clicked()),(QObject*) this, SLOT(OnClipboardStatisticsButtonClicked()) ); connect( (QObject*)(this->m_Controls->m_IgnoreZerosCheckbox), SIGNAL(clicked()),(QObject*) this, SLOT(OnIgnoreZerosCheckboxClicked()) ); connect( (QObject*) this->m_CalculationThread, SIGNAL(finished()),this, SLOT( OnThreadedStatisticsCalculationEnds()),Qt::QueuedConnection); connect( (QObject*) this, SIGNAL(StatisticsUpdate()),this, SLOT( RequestStatisticsUpdate()), Qt::QueuedConnection); connect( (QObject*) this->m_Controls->m_StatisticsTable, SIGNAL(cellDoubleClicked(int,int)),this, SLOT( JumpToCoordinates(int,int)) ); connect( (QObject*) (this->m_Controls->m_barRadioButton), SIGNAL(clicked()), (QObject*) (this->m_Controls->m_JSHistogram), SLOT(OnBarRadioButtonSelected())); connect( (QObject*) (this->m_Controls->m_lineRadioButton), SIGNAL(clicked()), (QObject*) (this->m_Controls->m_JSHistogram), SLOT(OnLineRadioButtonSelected())); connect( (QObject*) (this->m_Controls->m_HistogramBinSizeSpinbox), SIGNAL(editingFinished()), this, SLOT(OnHistogramBinSizeBoxValueChanged())); connect( (QObject*)(this->m_Controls->m_UseDefaultBinSizeBox), SIGNAL(clicked()),(QObject*) this, SLOT(OnDefaultBinSizeBoxChanged()) ); } } void QmitkImageStatisticsView::OnDefaultBinSizeBoxChanged() { if (m_CalculationThread!=NULL) m_Controls->m_HistogramBinSizeSpinbox->setValue(m_CalculationThread->GetHistogramBinSize()); if (m_Controls->m_UseDefaultBinSizeBox->isChecked()) m_Controls->m_BinSizeFrame->setVisible(false); else m_Controls->m_BinSizeFrame->setVisible(true); } void QmitkImageStatisticsView::PartClosed(const berry::IWorkbenchPartReference::Pointer& ) { } void QmitkImageStatisticsView::OnTimeChanged(const itk::EventObject& e) { if (this->m_SelectedDataNodes.isEmpty() || this->m_SelectedImage == NULL) return; const mitk::SliceNavigationController::GeometryTimeEvent* timeEvent = dynamic_cast(&e); assert(timeEvent != NULL); unsigned int timestep = timeEvent->GetPos(); if (this->m_SelectedImage->GetTimeSteps() > 1) { for (int x = 0; x < this->m_Controls->m_StatisticsTable->columnCount(); x++) { for (int y = 0; y < this->m_Controls->m_StatisticsTable->rowCount(); y++) { QTableWidgetItem* item = this->m_Controls->m_StatisticsTable->item(y, x); if (item == NULL) break; if (x == timestep) { item->setBackgroundColor(Qt::yellow); } else { if (y % 2 == 0) item->setBackground(this->m_Controls->m_StatisticsTable->palette().base()); else item->setBackground(this->m_Controls->m_StatisticsTable->palette().alternateBase()); } } } this->m_Controls->m_StatisticsTable->viewport()->update(); } if ((this->m_SelectedImage->GetTimeSteps() == 1 && timestep == 0) || this->m_SelectedImage->GetTimeSteps() > 1) { // display histogram for selected timestep this->m_Controls->m_JSHistogram->ClearHistogram(); QmitkImageStatisticsCalculationThread::HistogramType::Pointer histogram = this->m_CalculationThread->GetTimeStepHistogram(timestep); if (histogram.IsNotNull()) { bool closedFigure = this->m_CalculationThread->GetStatisticsUpdateSuccessFlag(); if ( closedFigure ) { this->m_Controls->m_JSHistogram->ComputeHistogram(histogram.GetPointer()); } //this->m_Controls->m_JSHistogram->ComputeHistogram(histogram.GetPointer()); /*else { m_Controls->m_JSHistogram->ComputeIntensityProfile(timestep, true); }*/ // this->m_Controls->m_JSHistogram->SignalGraphChanged(); // hacky way to make sure the protected SignalGraphChanged() is called if (this->m_Controls->m_JSHistogram->GetUseLineGraph()) { this->m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); this->m_Controls->m_JSHistogram->OnLineRadioButtonSelected(); } else { this->m_Controls->m_JSHistogram->OnLineRadioButtonSelected(); this->m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); } } } } void QmitkImageStatisticsView::JumpToCoordinates(int row ,int col) { if(m_SelectedDataNodes.isEmpty()) { MITK_WARN("QmitkImageStatisticsView") << "No data node selected for statistics calculation." ; return; } mitk::Point3D world; if (row==4 && !m_WorldMinList.empty()) world = m_WorldMinList[col]; else if (row==3 && !m_WorldMaxList.empty()) world = m_WorldMaxList[col]; else return; mitk::IRenderWindowPart* part = this->GetRenderWindowPart(); if (part) { part->GetQmitkRenderWindow("axial")->GetSliceNavigationController()->SelectSliceByPoint(world); part->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()->SelectSliceByPoint(world); part->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()->SelectSliceByPoint(world); mitk::SliceNavigationController::GeometryTimeEvent timeEvent(this->m_SelectedImage->GetTimeGeometry(), col); part->GetQmitkRenderWindow("axial")->GetSliceNavigationController()->SetGeometryTime(timeEvent); } } void QmitkImageStatisticsView::OnIgnoreZerosCheckboxClicked() { emit StatisticsUpdate(); } void QmitkImageStatisticsView::OnClipboardHistogramButtonClicked() { if ( m_CurrentStatisticsValid && !( m_SelectedPlanarFigure != NULL)) { const unsigned int t = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); typedef mitk::ImageStatisticsCalculator::HistogramType HistogramType; const HistogramType *histogram = this->m_CalculationThread->GetTimeStepHistogram(t).GetPointer(); QString clipboard( "Measurement \t Frequency\n" ); for ( HistogramType::ConstIterator it = histogram->Begin(); it != histogram->End(); ++it ) { if( m_Controls->m_HistogramBinSizeSpinbox->value() == 1.0) { clipboard = clipboard.append( "%L1 \t %L2\n" ) .arg( it.GetMeasurementVector()[0], 0, 'f', 0 ) .arg( it.GetFrequency() ); } else { clipboard = clipboard.append( "%L1 \t %L2\n" ) .arg( it.GetMeasurementVector()[0], 0, 'f', 2 ) .arg( it.GetFrequency() ); } } QApplication::clipboard()->setText( clipboard, QClipboard::Clipboard ); } // If a (non-closed) PlanarFigure is selected, display a line profile widget else if ( m_CurrentStatisticsValid && (m_SelectedPlanarFigure != NULL )) { auto intensity = m_Controls->m_JSHistogram->GetFrequency(); auto pixel = m_Controls->m_JSHistogram->GetMeasurement(); QString clipboard( "Pixel \t Intensity\n" ); auto j = pixel.begin(); for (auto i = intensity.begin(); i < intensity.end(); i++) { assert(j != pixel.end()); clipboard = clipboard.append( "%L1 \t %L2\n" ) .arg( (*j).toString()) .arg( (*i).toString()); j++; } QApplication::clipboard()->setText( clipboard, QClipboard::Clipboard ); } else { QApplication::clipboard()->clear(); } } void QmitkImageStatisticsView::OnClipboardStatisticsButtonClicked() { QLocale tempLocal; QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); if ( m_CurrentStatisticsValid && !( m_SelectedPlanarFigure != NULL)) { const std::vector &statistics = this->m_CalculationThread->GetStatisticsData(); // Set time borders for for loop ;) unsigned int startT, endT; if(this->m_Controls->m_CheckBox4dCompleteTable->checkState()==Qt::CheckState::Unchecked) { startT = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()-> GetPos(); endT = startT+1; } else { startT = 0; endT = statistics.size(); } QVector< QVector > statisticsTable; QStringList headline; // Create Headline headline << " " << "Mean" << "Median" << "StdDev" << "RMS" << "Max" << "Min" << "NumberOfVoxels" << "Skewness" << "Kurtosis" << "Uniformity" << "Entropy" << "MPP" << "UPP" << "V [mm³]"; for(int i=0;i row; row.append(headline.at(i)); statisticsTable.append(row); } // Fill Table for(unsigned int t=startT;tGetMean()) << QString::number(statistics[t]->GetMedian()) << QString::number(statistics[t]->GetStd()) << QString::number(statistics[t]->GetRMS()) << QString::number(statistics[t]->GetMax()) << QString::number(statistics[t]->GetMin()) << QString::number(statistics[t]->GetN()) << QString::number(statistics[t]->GetSkewness()) << QString::number(statistics[t]->GetKurtosis()) << QString::number(statistics[t]->GetUniformity()) << QString::number(statistics[t]->GetEntropy()) << QString::number(statistics[t]->GetMPP()) << QString::number(statistics[t]->GetUPP()) << QString::number(m_Controls->m_StatisticsTable->item(7, 0)->data(Qt::DisplayRole).toDouble()); for(int z=0;zsetText(clipboard, QClipboard::Clipboard); } else { QApplication::clipboard()->clear(); } QLocale::setDefault(tempLocal); } void QmitkImageStatisticsView::OnSelectionChanged( berry::IWorkbenchPart::Pointer /*part*/, const QList &selectedNodes ) { if (this->m_Visible) { this->SelectionChanged( selectedNodes ); } else { this->m_DataNodeSelectionChanged = true; } } void QmitkImageStatisticsView::SelectionChanged(const QList &selectedNodes) { if( this->m_StatisticsUpdatePending ) { this->m_DataNodeSelectionChanged = true; return; // not ready for new data now! } if (selectedNodes.size() == this->m_SelectedDataNodes.size()) { int i = 0; for (; i < selectedNodes.size(); ++i) { if (selectedNodes.at(i) != this->m_SelectedDataNodes.at(i)) { break; } } // node selection did not change if (i == selectedNodes.size()) return; } //reset the feature image and image mask field m_Controls->m_SelectedFeatureImageLabel->setText("None"); m_Controls->m_SelectedMaskLabel->setText("None"); this->ReinitData(); if (selectedNodes.isEmpty()) { m_Controls->m_JSHistogram->ClearHistogram(); m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); // m_Controls->horizontalLayout_3->setEnabled(false); m_Controls->groupBox->setEnabled(false); m_Controls->groupBox_3->setEnabled(false); } else { // m_Controls->horizontalLayout_3->setEnabled(true); m_Controls->groupBox->setEnabled(true); m_Controls->groupBox_3->setEnabled(true); } if(selectedNodes.size() == 1 || selectedNodes.size() == 2) { bool isBinary = false; selectedNodes.value(0)->GetBoolProperty("binary",isBinary); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); isBinary |= isLabelSet->CheckNode(selectedNodes.value(0)); if(isBinary) { m_Controls->m_JSHistogram->ClearHistogram(); m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); } for (int i= 0; i< selectedNodes.size(); ++i) { this->m_SelectedDataNodes.push_back(selectedNodes.at(i)); } this->m_DataNodeSelectionChanged = false; this->m_Controls->m_ErrorMessageLabel->setText( "" ); this->m_Controls->m_ErrorMessageLabel->hide(); emit StatisticsUpdate(); } else { this->m_DataNodeSelectionChanged = false; } } void QmitkImageStatisticsView::ReinitData() { while( this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } if(this->m_SelectedImage != NULL) { this->m_SelectedImage->RemoveObserver( this->m_ImageObserverTag); this->m_SelectedImage = NULL; } if(this->m_SelectedImageMask != NULL) { this->m_SelectedImageMask->RemoveObserver( this->m_ImageMaskObserverTag); this->m_SelectedImageMask = NULL; } if(this->m_SelectedPlanarFigure != NULL) { this->m_SelectedPlanarFigure->RemoveObserver( this->m_PlanarFigureObserverTag); this->m_SelectedPlanarFigure = NULL; } this->m_SelectedDataNodes.clear(); this->m_StatisticsUpdatePending = false; m_Controls->m_ErrorMessageLabel->setText( "" ); m_Controls->m_ErrorMessageLabel->hide(); this->InvalidateStatisticsTableView(); m_Controls->m_JSHistogram->ClearHistogram(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); } void QmitkImageStatisticsView::OnThreadedStatisticsCalculationEnds() { std::stringstream message; message << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->hide(); this->WriteStatisticsToGUI(); } void QmitkImageStatisticsView::UpdateStatistics() { mitk::IRenderWindowPart* renderPart = this->GetRenderWindowPart(); if ( renderPart == NULL ) { this->m_StatisticsUpdatePending = false; return; } m_WorldMinList.clear(); m_WorldMaxList.clear(); // classify selected nodes mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); mitk::NodePredicateOr::Pointer imagePredicate = mitk::NodePredicateOr::New(isImage, isLabelSet); std::string maskName = std::string(); std::string maskType = std::string(); std::string featureImageName = std::string(); unsigned int maskDimension = 0; // reset data from last run ITKCommandType::Pointer changeListener = ITKCommandType::New(); changeListener->SetCallbackFunction( this, &QmitkImageStatisticsView::SelectedDataModified ); mitk::DataNode::Pointer planarFigureNode; for( int i= 0 ; i < this->m_SelectedDataNodes.size(); ++i) { mitk::PlanarFigure::Pointer planarFig = dynamic_cast(this->m_SelectedDataNodes.at(i)->GetData()); if( imagePredicate->CheckNode(this->m_SelectedDataNodes.at(i)) ) { bool isMask = false; this->m_SelectedDataNodes.at(i)->GetPropertyValue("binary", isMask); isMask |= isLabelSet->CheckNode(this->m_SelectedDataNodes.at(i)); if( this->m_SelectedImageMask == NULL && isMask) { this->m_SelectedImageMask = dynamic_cast(this->m_SelectedDataNodes.at(i)->GetData()); this->m_ImageMaskObserverTag = this->m_SelectedImageMask->AddObserver(itk::ModifiedEvent(), changeListener); maskName = this->m_SelectedDataNodes.at(i)->GetName(); maskType = m_SelectedImageMask->GetNameOfClass(); maskDimension = 3; } else if( !isMask ) { if(this->m_SelectedImage == NULL) { this->m_SelectedImage = static_cast(this->m_SelectedDataNodes.at(i)->GetData()); this->m_ImageObserverTag = this->m_SelectedImage->AddObserver(itk::ModifiedEvent(), changeListener); } featureImageName = this->m_SelectedDataNodes.at(i)->GetName(); } } else if (planarFig.IsNotNull()) { if(this->m_SelectedPlanarFigure == NULL) { this->m_SelectedPlanarFigure = planarFig; this->m_PlanarFigureObserverTag = this->m_SelectedPlanarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), changeListener); maskName = this->m_SelectedDataNodes.at(i)->GetName(); maskType = this->m_SelectedPlanarFigure->GetNameOfClass(); maskDimension = 2; planarFigureNode = m_SelectedDataNodes.at(i); } } else { std::stringstream message; message << "" << "Invalid data node type!" << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); } } if(maskName == "") { maskName = "None"; maskType = ""; maskDimension = 0; } if(featureImageName == "") { featureImageName = "None"; } if (m_SelectedPlanarFigure != NULL && m_SelectedImage == NULL) { mitk::DataStorage::SetOfObjects::ConstPointer parentSet = this->GetDataStorage()->GetSources(planarFigureNode); for (int i=0; iSize(); i++) { mitk::DataNode::Pointer node = parentSet->ElementAt(i); if( imagePredicate->CheckNode(node) ) { bool isMask = false; node->GetPropertyValue("binary", isMask); isMask |= isLabelSet->CheckNode(node); if( !isMask ) { if(this->m_SelectedImage == NULL) { this->m_SelectedImage = static_cast(node->GetData()); this->m_ImageObserverTag = this->m_SelectedImage->AddObserver(itk::ModifiedEvent(), changeListener); } } } } } unsigned int timeStep = renderPart->GetTimeNavigationController()->GetTime()->GetPos(); if ( m_SelectedImage != NULL && m_SelectedImage->IsInitialized()) { // Check if a the selected image is a multi-channel image. If yes, statistics // cannot be calculated currently. if ( m_SelectedImage->GetPixelType().GetNumberOfComponents() > 1 ) { std::stringstream message; message << "Multi-component images not supported."; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_JSHistogram->ClearHistogram(); m_CurrentStatisticsValid = false; this->m_StatisticsUpdatePending = false; m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); return; } std::stringstream maskLabel; maskLabel << maskName; if ( maskDimension > 0 ) { maskLabel << " [" << maskDimension << "D " << maskType << "]"; } m_Controls->m_SelectedMaskLabel->setText( maskLabel.str().c_str() ); m_Controls->m_SelectedFeatureImageLabel->setText(featureImageName.c_str()); // check time step validity if(m_SelectedImage->GetDimension() <= 3 && timeStep > m_SelectedImage->GetDimension(3)-1) { timeStep = m_SelectedImage->GetDimension(3)-1; } // Add the used mask time step to the mask label so the user knows which mask time step was used // if the image time step is bigger than the total number of mask time steps (see // ImageStatisticsCalculator::ExtractImageAndMask) if (m_SelectedImageMask != NULL) { unsigned int maskTimeStep = timeStep; if (maskTimeStep >= m_SelectedImageMask->GetTimeSteps()) { maskTimeStep = m_SelectedImageMask->GetTimeSteps() - 1; } m_Controls->m_SelectedMaskLabel->setText(m_Controls->m_SelectedMaskLabel->text() + QString(" (t=") + QString::number(maskTimeStep) + QString(")")); } //// initialize thread and trigger it this->m_CalculationThread->SetIgnoreZeroValueVoxel( m_Controls->m_IgnoreZerosCheckbox->isChecked() ); this->m_CalculationThread->Initialize( m_SelectedImage, m_SelectedImageMask, m_SelectedPlanarFigure ); this->m_CalculationThread->SetTimeStep( timeStep ); this->m_CalculationThread->SetHistogramBinSize(m_Controls->m_HistogramBinSizeSpinbox->value()); std::stringstream message; message << "Calculating statistics..."; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); try { // Compute statistics this->m_CalculationThread->SetUseDefaultBinSize(m_Controls->m_UseDefaultBinSizeBox->isChecked()); this->m_CalculationThread->start(); } catch ( const mitk::Exception& e) { std::stringstream message; message << "" << e.GetDescription() << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } catch ( const std::runtime_error &e ) { // In case of exception, print error message on GUI std::stringstream message; message << "" << e.what() << ""; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } catch ( const std::exception &e ) { MITK_ERROR << "Caught exception: " << e.what(); // In case of exception, print error message on GUI std::stringstream message; message << "Error! Unequal Dimensions of Image and Segmentation. No recompute possible "; m_Controls->m_ErrorMessageLabel->setText( message.str().c_str() ); m_Controls->m_ErrorMessageLabel->show(); this->m_StatisticsUpdatePending = false; } } else { this->m_StatisticsUpdatePending = false; } } void QmitkImageStatisticsView::SelectedDataModified() { if( !m_StatisticsUpdatePending ) { emit StatisticsUpdate(); } } void QmitkImageStatisticsView::NodeRemoved(const mitk::DataNode *node) { while(this->m_CalculationThread->isRunning()) // wait until thread has finished { itksys::SystemTools::Delay(100); } if (node->GetData() == m_SelectedImage) { m_SelectedImage = NULL; } } void QmitkImageStatisticsView::RequestStatisticsUpdate() { if ( !m_StatisticsUpdatePending ) { if(this->m_DataNodeSelectionChanged) { this->SelectionChanged(this->GetCurrentSelection()); } else { this->m_StatisticsUpdatePending = true; this->UpdateStatistics(); } } if (this->GetRenderWindowPart()) this->GetRenderWindowPart()->RequestUpdate(); } void QmitkImageStatisticsView::OnHistogramBinSizeBoxValueChanged() { this->UpdateStatistics(); } void QmitkImageStatisticsView::WriteStatisticsToGUI() { m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); m_Controls->m_InfoLabel->setText(QString("")); if(m_DataNodeSelectionChanged) { this->m_StatisticsUpdatePending = false; this->RequestStatisticsUpdate(); return; // stop visualization of results and calculate statistics of new selection } if ( this->m_CalculationThread->GetStatisticsUpdateSuccessFlag()) { if ( this->m_CalculationThread->GetStatisticsChangedFlag() ) { // Do not show any error messages m_Controls->m_ErrorMessageLabel->hide(); m_CurrentStatisticsValid = true; } if (m_Controls->m_barRadioButton->isChecked()) { m_Controls->m_JSHistogram->OnBarRadioButtonSelected(); } m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_HistogramBinSizeSpinbox->setValue( this->m_CalculationThread->GetHistogramBinSize() ); //m_Controls->m_JSHistogram->ComputeHistogram( this->m_CalculationThread->GetTimeStepHistogram(this->m_CalculationThread->GetTimeStep()).GetPointer() ); this->FillStatisticsTableView( this->m_CalculationThread->GetStatisticsData(), this->m_CalculationThread->GetStatisticsImage()); } else { m_Controls->m_SelectedMaskLabel->setText( "None" ); m_Controls->m_ErrorMessageLabel->setText( m_CalculationThread->GetLastErrorMessage().c_str() ); m_Controls->m_ErrorMessageLabel->show(); // Clear statistics and histogram this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); //m_Controls->m_JSHistogram->clearHistogram(); m_CurrentStatisticsValid = false; // If a (non-closed) PlanarFigure is selected, display a line profile widget if ( m_SelectedPlanarFigure != NULL ) { // Check if the (closed) planar figure is out of bounds and so no image mask could be calculated--> Intensity Profile can not be calculated bool outOfBounds = false; if ( m_SelectedPlanarFigure->IsClosed() && m_SelectedImageMask == NULL) { outOfBounds = true; std::stringstream message; message << "Planar figure is on a rotated image plane or outside the image bounds."; m_Controls->m_InfoLabel->setText(message.str().c_str()); } // check whether PlanarFigure is initialized const mitk::PlaneGeometry *planarFigurePlaneGeometry = m_SelectedPlanarFigure->GetPlaneGeometry(); if ( !(planarFigurePlaneGeometry == NULL || outOfBounds)) { unsigned int timeStep = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()->GetPos(); m_Controls->m_JSHistogram->SetImage(this->m_CalculationThread->GetStatisticsImage()); m_Controls->m_JSHistogram->SetPlanarFigure(m_SelectedPlanarFigure); m_Controls->m_JSHistogram->ComputeIntensityProfile(timeStep, true); //m_Controls->m_JSHistogram->ComputeIntensityProfile(timeStep); m_Controls->m_lineRadioButton->setEnabled(false); m_Controls->m_barRadioButton->setEnabled(false); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(false); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(false); // m_Controls->m_HistogramBinSizeLabel->setEnabled(false); this->FillLinearProfileStatisticsTableView( this->m_CalculationThread->GetStatisticsImage() ); std::stringstream message; message << "Only linegraph available for an intensity profile!"; m_Controls->m_InfoLabel->setText(message.str().c_str()); m_CurrentStatisticsValid = true; } else { // Clear statistics, histogram, and GUI this->InvalidateStatisticsTableView(); m_Controls->m_StatisticsWidgetStack->setCurrentIndex( 0 ); m_Controls->m_JSHistogram->ClearHistogram(); m_CurrentStatisticsValid = false; m_Controls->m_ErrorMessageLabel->hide(); m_Controls->m_SelectedMaskLabel->setText( "None" ); this->m_StatisticsUpdatePending = false; m_Controls->m_lineRadioButton->setEnabled(true); m_Controls->m_barRadioButton->setEnabled(true); m_Controls->m_HistogramBinSizeSpinbox->setEnabled(true); m_Controls->m_HistogramBinSizeCaptionLabel->setEnabled(true); // m_Controls->m_HistogramBinSizeLabel->setEnabled(true); if (!outOfBounds) m_Controls->m_InfoLabel->setText(QString("")); return; // Sebastian Wirkert: would suggest to remove this return, since it is an artifact of previous // code architecture. However, removing it will cause m_StatisticsUpdatePending to be set to false // in case of invalid statistics which it previously was not. } } } this->m_StatisticsUpdatePending = false; } void QmitkImageStatisticsView::FillStatisticsTableView( const std::vector &s, const mitk::Image *image ) { this->m_Controls->m_StatisticsTable->setColumnCount(image->GetTimeSteps()); this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(image->GetTimeSteps() > 1); // Set Checkbox for complete copy of statistic table if(image->GetTimeSteps()>1) { this->m_Controls->m_CheckBox4dCompleteTable->setEnabled(true); } else { this->m_Controls->m_CheckBox4dCompleteTable->setEnabled(false); this->m_Controls->m_CheckBox4dCompleteTable->setChecked(false); } int decimals = 2; mitk::PixelType doublePix = mitk::MakeScalarPixelType< double >(); mitk::PixelType floatPix = mitk::MakeScalarPixelType< float >(); if (image->GetPixelType()==doublePix || image->GetPixelType()==floatPix) { decimals = 5; } for (unsigned int t = 0; t < image->GetTimeSteps(); t++) { this->m_Controls->m_StatisticsTable->setHorizontalHeaderItem(t, new QTableWidgetItem(QString::number(t))); if (s[t]->GetMaxIndex().size()==3) { mitk::Point3D index, max, min; index[0] = s[t]->GetMaxIndex()[0]; index[1] = s[t]->GetMaxIndex()[1]; index[2] = s[t]->GetMaxIndex()[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, max); this->m_WorldMaxList.push_back(max); index[0] = s[t]->GetMinIndex()[0]; index[1] = s[t]->GetMinIndex()[1]; index[2] = s[t]->GetMinIndex()[2]; m_SelectedImage->GetGeometry()->IndexToWorld(index, min); this->m_WorldMinList.push_back(min); } + typedef mitk::ImageStatisticsCalculator::StatisticsContainer::RealType RealType; + RealType maxVal = std::numeric_limits::max(); + this->m_Controls->m_StatisticsTable->setItem( 0, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetMean(), 0, 'f', decimals) ) ); + this->m_Controls->m_StatisticsTable->setItem( 1, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetMedian(), 0, 'f', decimals) ) ); + this->m_Controls->m_StatisticsTable->setItem( 2, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetStd(), 0, 'f', decimals) ) ); + this->m_Controls->m_StatisticsTable->setItem( 3, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetRMS(), 0, 'f', decimals) ) ); QString max; max.append(QString("%1").arg(s[t]->GetMax(), 0, 'f', decimals)); max += " ("; for (int i=0; iGetMaxIndex().size(); i++) { max += QString::number(s[t]->GetMaxIndex()[i]); if (iGetMaxIndex().size()-1) max += ","; } max += ")"; this->m_Controls->m_StatisticsTable->setItem( 4, t, new QTableWidgetItem( max ) ); QString min; min.append(QString("%1").arg(s[t]->GetMin(), 0, 'f', decimals)); min += " ("; for (int i=0; iGetMinIndex().size(); i++) { min += QString::number(s[t]->GetMinIndex()[i]); if (iGetMinIndex().size()-1) min += ","; } min += ")"; this->m_Controls->m_StatisticsTable->setItem( 5, t, new QTableWidgetItem( min ) ); this->m_Controls->m_StatisticsTable->setItem( 6, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetN()) ) ); const mitk::BaseGeometry *geometry = image->GetGeometry(); if ( geometry != NULL ) { const mitk::Vector3D &spacing = image->GetGeometry()->GetSpacing(); double volume = spacing[0] * spacing[1] * spacing[2] * (double) s[t]->GetN(); this->m_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( QString("%1").arg(volume, 0, 'f', decimals) ) ); } else { this->m_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( "NA" ) ); } //statistics of higher order should have 5 decimal places because they used to be very small this->m_Controls->m_StatisticsTable->setItem( 8, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetSkewness(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 9, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetKurtosis(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 10, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetUniformity(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 11, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetEntropy(), 0, 'f', 5) ) ); this->m_Controls->m_StatisticsTable->setItem( 12, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetMPP(), 0, 'f', decimals) ) ); this->m_Controls->m_StatisticsTable->setItem( 13, t, new QTableWidgetItem( QString("%1").arg(s[t]->GetUPP(), 0, 'f', 5) ) ); } this->m_Controls->m_StatisticsTable->resizeColumnsToContents(); int height = STAT_TABLE_BASE_HEIGHT; if (this->m_Controls->m_StatisticsTable->horizontalHeader()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalHeader()->height(); if (this->m_Controls->m_StatisticsTable->horizontalScrollBar()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalScrollBar()->height(); this->m_Controls->m_StatisticsTable->setMinimumHeight(height); // make sure the current timestep's column is highlighted (and the correct histogram is displayed) unsigned int t = this->GetRenderWindowPart()->GetTimeNavigationController()->GetTime()-> GetPos(); mitk::SliceNavigationController::GeometryTimeEvent timeEvent(this->m_SelectedImage->GetTimeGeometry(), t); this->OnTimeChanged(timeEvent); t = std::min(image->GetTimeSteps() - 1, t); // See bug 18340 /*QString hotspotMean; hotspotMean.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMean(), 0, 'f', decimals)); hotspotMean += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 7, t, new QTableWidgetItem( hotspotMean ) ); QString hotspotMax; hotspotMax.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMax(), 0, 'f', decimals)); hotspotMax += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 8, t, new QTableWidgetItem( hotspotMax ) ); QString hotspotMin; hotspotMin.append(QString("%1").arg(s[t].GetHotspotStatistics().GetMin(), 0, 'f', decimals)); hotspotMin += " ("; for (int i=0; im_Controls->m_StatisticsTable->setItem( 9, t, new QTableWidgetItem( hotspotMin ) );*/ } std::vector QmitkImageStatisticsView::CalculateStatisticsForPlanarFigure( const mitk::Image *image) { std::vector result; int decimals = 2; mitk::PixelType doublePix = mitk::MakeScalarPixelType< double >(); mitk::PixelType floatPix = mitk::MakeScalarPixelType< float >(); if (image->GetPixelType()==doublePix || image->GetPixelType()==floatPix) { decimals = 5; } mitk::ImageStatisticsCalculator::StatisticsContainer::Pointer stats = m_Controls->m_JSHistogram->GetStatistics(); - result.push_back(QString("%1").arg(stats->GetMean(), 0, 'f', decimals)); - result.push_back(QString("%1").arg(stats->GetMedian(), 0, 'f', decimals)); + typedef mitk::ImageStatisticsCalculator::StatisticsContainer::RealType RealType; + RealType maxVal = std::numeric_limits::max(); + + if (stats->GetMean() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetMean(), 0, 'f', decimals)); + } + + if (stats->GetMedian() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetMedian(), 0, 'f', decimals)); + } + + if (stats->GetStd() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back( QString("%1").arg( stats->GetStd(), 0, 'f', decimals)); + } - double stdDev = sqrt( stats->GetVariance() ); - result.push_back( QString("%1").arg( stdDev, 0, 'f', decimals)); + if (stats->GetRMS() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg( stats->GetRMS(), 0, 'f', decimals)); + } - double rms = stats->GetRMS(); - result.push_back(QString("%1").arg( rms, 0, 'f', decimals)); + if (stats->GetMax() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + QString max; + max.append(QString("%1").arg(stats->GetMax(), 0, 'f', decimals)); + result.push_back(max); + } - QString max; max.append(QString("%1").arg(stats->GetMax(), 0, 'f', decimals)); - result.push_back(max); - QString min; min.append(QString("%1").arg(stats->GetMin(), 0, 'f', decimals)); - result.push_back(min); + if (stats->GetMin() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + QString min; + min.append(QString("%1").arg(stats->GetMin(), 0, 'f', decimals)); + result.push_back(min); - result.push_back(QString("%1").arg(stats->GetN())); + } + + + if (stats->GetN() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetN())); + } result.push_back(QString("NA")); //statistics of higher order should have 5 decimal places because they used to be very small - result.push_back(QString("%1").arg(stats->GetSkewness(), 0, 'f', 5 )); + if (stats->GetSkewness() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetSkewness(), 0, 'f', 5 )); + } - result.push_back(QString("%1").arg(stats->GetKurtosis(), 0, 'f', 5) ); + if (stats->GetKurtosis() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetKurtosis(), 0, 'f', 5) ); + } - result.push_back(QString("%1").arg(stats->GetUniformity(), 0, 'f', 5) ); + if (stats->GetUniformity() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetUniformity(), 0, 'f', 5) ); + } - result.push_back(QString("%1").arg(stats->GetEntropy(), 0, 'f', 5) ); + if (stats->GetEntropy() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetEntropy(), 0, 'f', 5) ); + } - result.push_back(QString("%1").arg(stats->GetMPP(), 0, 'f', decimals) ); + if (stats->GetMPP() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetMPP(), 0, 'f', decimals) ); + } - result.push_back(QString("%1").arg(stats->GetUPP(), 0, 'f', 5) ); + if (stats->GetUPP() == maxVal) + { + result.push_back(QString("NA")); + } + else + { + result.push_back(QString("%1").arg(stats->GetUPP(), 0, 'f', 5) ); + } return result; } void QmitkImageStatisticsView::FillLinearProfileStatisticsTableView( const mitk::Image *image ) { this->m_Controls->m_StatisticsTable->setColumnCount(1); this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(false); m_PlanarFigureStatistics = this->CalculateStatisticsForPlanarFigure(image); for (int i = 0; i< m_PlanarFigureStatistics.size(); i++) { this->m_Controls->m_StatisticsTable->setItem( i, 0, new QTableWidgetItem(m_PlanarFigureStatistics[i] )); } this->m_Controls->m_StatisticsTable->resizeColumnsToContents(); int height = STAT_TABLE_BASE_HEIGHT; if (this->m_Controls->m_StatisticsTable->horizontalHeader()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalHeader()->height(); if (this->m_Controls->m_StatisticsTable->horizontalScrollBar()->isVisible()) height += this->m_Controls->m_StatisticsTable->horizontalScrollBar()->height(); this->m_Controls->m_StatisticsTable->setMinimumHeight(height); } void QmitkImageStatisticsView::InvalidateStatisticsTableView() { this->m_Controls->m_StatisticsTable->horizontalHeader()->setVisible(false); this->m_Controls->m_StatisticsTable->setColumnCount(1); for ( unsigned int i = 0; i < this->m_Controls->m_StatisticsTable->rowCount(); ++i ) { { this->m_Controls->m_StatisticsTable->setItem( i, 0, new QTableWidgetItem( "NA" ) ); } } this->m_Controls->m_StatisticsTable->setMinimumHeight(STAT_TABLE_BASE_HEIGHT); } void QmitkImageStatisticsView::Activated() { } void QmitkImageStatisticsView::Deactivated() { } void QmitkImageStatisticsView::Visible() { m_Visible = true; mitk::IRenderWindowPart* renderWindow = GetRenderWindowPart(); if (renderWindow) { itk::ReceptorMemberCommand::Pointer cmdTimeEvent = itk::ReceptorMemberCommand::New(); cmdTimeEvent->SetCallbackFunction(this, &QmitkImageStatisticsView::OnTimeChanged); // It is sufficient to add the observer to the axial render window since the GeometryTimeEvent // is always triggered by all views. m_TimeObserverTag = renderWindow->GetQmitkRenderWindow("axial")-> GetSliceNavigationController()-> AddObserver(mitk::SliceNavigationController::GeometryTimeEvent(NULL, 0), cmdTimeEvent); } if (m_DataNodeSelectionChanged) { if (this->IsCurrentSelectionValid()) { this->SelectionChanged(this->GetCurrentSelection()); } else { this->SelectionChanged(this->GetDataManagerSelection()); } m_DataNodeSelectionChanged = false; } } void QmitkImageStatisticsView::Hidden() { m_Visible = false; // The slice navigation controller observer is removed here instead of in the destructor. // If it was called in the destructor, the application would freeze because the view's // destructor gets called after the render windows have been destructed. if ( m_TimeObserverTag != NULL ) { mitk::IRenderWindowPart* renderWindow = GetRenderWindowPart(); if (renderWindow) { renderWindow->GetQmitkRenderWindow("axial")->GetSliceNavigationController()-> RemoveObserver( m_TimeObserverTag ); } m_TimeObserverTag = NULL; } } void QmitkImageStatisticsView::SetFocus() { } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui index 9d496cf7ea..d53335a4d7 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkImageStatisticsViewControls.ui @@ -1,625 +1,625 @@ QmitkImageStatisticsViewControls true 0 0 548 800 Form 0 0 Qt::LeftToRight Feature Image: 0 0 None 0 0 Mask: 0 0 None -1 0 0 color: rgb(255, 0, 0); Error Message Qt::AutoText Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Ignore zero-valued voxels false Statistics 9 9 9 100 180 16777215 16777215 Qt::ScrollBarAsNeeded Qt::ScrollBarAsNeeded true QAbstractItemView::NoEditTriggers true true Qt::DotLine false 14 false false 80 true 80 false true true false 25 25 false false Mean Median StdDev RMS Max Min N V (mm³) Skewness Kurtosis Uniformity Entropy MPP UPP 0 0 0 0 0 0 Copy to Clipboard Qt::Horizontal QSizePolicy::Fixed 20 20 false copy complete table Qt::Horizontal 40 20 false 150 160 Histogram false 0 0 0 0 16777215 16777215 Plot 0 0 Barchart true 0 0 0 0 Linegraph Qt::Horizontal 40 20 Use default bin size true QFrame::NoFrame QFrame::Raised 0 0 0 0 0 60 0 100 16777215 Bin size: Press enter to recalculate statistics with new bin size. true 5 0.000010000000000 1000000.000000000000000 - 10.000000000000000 + 100.000000000000000 0 0 0 0 0 0 0 0 Copy to Clipboard Qt::Horizontal 40 20 Qt::Vertical 20 40 QmitkHistogramJSWidget QWidget
QmitkHistogramJSWidget.h
1