diff --git a/Core/Code/Resources/Interactions/Legacy/StateMachine.xml b/Core/Code/Resources/Interactions/Legacy/StateMachine.xml index 7ce0de4ab3..0cd6f2be12 100644 --- a/Core/Code/Resources/Interactions/Legacy/StateMachine.xml +++ b/Core/Code/Resources/Interactions/Legacy/StateMachine.xml @@ -1,4061 +1,4101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/CMakeLists.txt b/Modules/CMakeLists.txt index 1220512bd6..dc1781f1a1 100644 --- a/Modules/CMakeLists.txt +++ b/Modules/CMakeLists.txt @@ -1,60 +1,60 @@ set(LIBPOSTFIX "Ext") # Modules must be listed according to their dependencies set(module_dirs SceneSerializationBase PlanarFigure ImageExtraction ImageStatistics LegacyAdaptors IpPicSupport MitkExt SceneSerialization + GraphAlgorithms Segmentation Qmitk QmitkExt Properties - GraphAlgorithms DiffusionImaging GPGPU IGT CameraCalibration IGTUI RigidRegistration RigidRegistrationUI DeformableRegistration DeformableRegistrationUI OpenCL OpenCVVideoSupport Overlays InputDevices ToFHardware ToFProcessing ToFUI US ClippingTools USUI DicomUI Simulation Python ) set(MITK_DEFAULT_SUBPROJECTS MITK-Modules) foreach(module_dir ${module_dirs}) add_subdirectory(${module_dir}) endforeach() if(MITK_PRIVATE_MODULES) file(GLOB all_subdirs RELATIVE ${MITK_PRIVATE_MODULES} ${MITK_PRIVATE_MODULES}/*) foreach(subdir ${all_subdirs}) string(FIND ${subdir} "." _result) if(_result EQUAL -1) if(EXISTS ${MITK_PRIVATE_MODULES}/${subdir}/CMakeLists.txt) message(STATUS "Found private module ${subdir}") add_subdirectory(${MITK_PRIVATE_MODULES}/${subdir} private_modules/${subdir}) endif() endif() endforeach() endif(MITK_PRIVATE_MODULES) diff --git a/Modules/GraphAlgorithms/files.cmake b/Modules/GraphAlgorithms/files.cmake index 0de34bc90f..433112a1c8 100644 --- a/Modules/GraphAlgorithms/files.cmake +++ b/Modules/GraphAlgorithms/files.cmake @@ -1,9 +1,10 @@ set(CPP_FILES itkShortestPathNode.cpp ) set(H_FILES itkShortestPathCostFunction.h itkShortestPathCostFunctionTbss.h itkShortestPathNode.h itkShortestPathImageFilter.h + itkShortestPathCostFunctionLiveWire.h ) diff --git a/Modules/GraphAlgorithms/itkShortestPathCostFunctionLiveWire.h b/Modules/GraphAlgorithms/itkShortestPathCostFunctionLiveWire.h new file mode 100644 index 0000000000..033c92951c --- /dev/null +++ b/Modules/GraphAlgorithms/itkShortestPathCostFunctionLiveWire.h @@ -0,0 +1,204 @@ +/*=================================================================== + +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 __itkShortestPathCostFunctionLiveWire_h +#define __itkShortestPathCostFunctionLiveWire_h + +#include "itkObject.h" +#include "itkObjectFactory.h" +#include "itkShortestPathCostFunction.h" // Superclass of Metrics + +#include + + + + +namespace itk +{ + /** \brief Cost function for LiveWire purposes. + Specific features are considered to calculate cummulative + costs of a link between two pixels. These are: + + - Gradient Magnitude + - Gradient Direction + - Laplacian Zero Crossing + + By default the Gradient Magnitude is mapped linear to costs + between 0 (good) and 1 (bad). Via SetDynamicCostMap( std::map< int, int > &costMap) + a cost map can be set to dynamically map Gradient Magnitude (non + linear). Thus lower values can be considered with lower costs + than higher values of gradient magnitudes. + To compute the costs of the gradient magnitude dynamically + a iverted map of the histogram of gradient magnitude image is used. + + */ + template + class ITK_EXPORT ShortestPathCostFunctionLiveWire : public ShortestPathCostFunction + { + public: + /** Standard class typedefs. */ + typedef ShortestPathCostFunctionLiveWire Self; + typedef ShortestPathCostFunction Superclass; + typedef SmartPointer Pointer; + typedef SmartPointer ConstPointer; + typedef itk::ImageRegionConstIterator ConstIteratorType; + + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkTypeMacro(ShortestPathCostFunctionLiveWire, ShortestPathCostFunction); + + + typedef itk::Image UnsignedCharImageType; + typedef itk::Image FloatImageType; + + typedef float ComponentType; + typedef itk::CovariantVector< ComponentType, 2 > OutputPixelType; + typedef itk::Image< OutputPixelType, 2 > VectorOutputImageType; + + typedef typename TInputImageType::IndexType IndexType; + typedef TInputImageType ImageType; + typedef itk::ImageRegion<2> RegionType; + + + + /** \brief calculates the costs for going from p1 to p2*/ + virtual double GetCost(IndexType p1, IndexType p2); + + /** \brief returns the minimal costs possible (needed for A*)*/ + virtual double GetMinCost(); + + /** \brief Initialize the metric*/ + virtual void Initialize (); + + + /** \brief Set repulsive path*/ + virtual void AddRepulsivePoint( itk::Index<3> ); + + /** \brief Clear repulsive path*/ + virtual void ClearRepulsivePoints( ); + + + + ShortestPathCostFunctionLiveWire(); + + + itkSetMacro (RequestedRegion, RegionType); + itkGetMacro (RequestedRegion, RegionType); + + // Set/Get function for sigma parameter + itkSetMacro (UseApproximateGradient, bool); + itkGetMacro (UseApproximateGradient, bool); + + virtual void SetImage(const TInputImageType* _arg) + { + if (this->m_Image != _arg) + { + this->m_Image = _arg; + this->Modified(); + this->m_Initialized = false; + } + } + + void SetDynamicCostMap( std::map< int, int > &costMap) + { + this->m_CostMap = costMap; + this->m_UseCostMap = true; + this->m_MaxMapCosts = -1; + this->Modified(); + } + + void SetUseCostMap(bool useCostMap) + { + this->m_UseCostMap = useCostMap; + } + + /** + \brief Set the maximum of the dynamic cost map to save computation time. + */ + void SetCostMapMaximum(double max) + { + this->m_MaxMapCosts = max; + } + + + enum Constants{ + MAPSCALEFACTOR = 10 + }; + + /** \brief Returns the y value of gaussian with given offset and amplitude + + gaussian approximation + f(x) = v(bin) * e^ ( -1/2 * (|x-k(bin)| / sigma)^2 ) + + \param x + \param xOfGaussian - offset + \param yOfGaussian - amplitude + */ + static double Gaussian(double x, double xOfGaussian, double yOfGaussian); + + protected: + + virtual ~ShortestPathCostFunctionLiveWire() {}; + + + typename ImageType::Pointer m_GradientMagnImage; + typename UnsignedCharImageType::Pointer m_ZeroCrossingsImage; + typename FloatImageType::Pointer m_EdgeImage; + typename VectorOutputImageType::Pointer m_GradientImage; + + double minCosts; + + bool m_UseRepulsivePoint; + + std::vector< itk::Index<3> > m_RepulsivePoints; + + typename Superclass::PixelType val; + + typename Superclass::PixelType startValue; + typename Superclass::PixelType endValue; + + double m_GradientMax; + + RegionType m_RequestedRegion; + + bool m_UseApproximateGradient; + + bool m_Initialized; + + std::map< int, int > m_CostMap; + + bool m_UseCostMap; + + double m_MaxMapCosts; + + private: + + double SigmoidFunction(double I, double max, double min, double alpha, double beta); + + + }; + +} // end namespace itk + + +#ifndef ITK_MANUAL_INSTANTIATION +#include "itkShortestPathCostFunctionLiveWire.txx" +#endif + +#endif /* __itkShortestPathCostFunctionLiveWire_h */ diff --git a/Modules/GraphAlgorithms/itkShortestPathCostFunctionLiveWire.txx b/Modules/GraphAlgorithms/itkShortestPathCostFunctionLiveWire.txx new file mode 100644 index 0000000000..a9ad943a75 --- /dev/null +++ b/Modules/GraphAlgorithms/itkShortestPathCostFunctionLiveWire.txx @@ -0,0 +1,441 @@ +/*=================================================================== + +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 __itkShortestPathCostFunctionLiveWire_txx +#define __itkShortestPathCostFunctionLiveWire_txx + +#include "itkShortestPathCostFunctionLiveWire.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace itk +{ + + // Constructor + template + ShortestPathCostFunctionLiveWire + ::ShortestPathCostFunctionLiveWire() + { + m_UseRepulsivePoint = false; + m_GradientMax = 0.0; + m_Initialized = false; + m_UseCostMap = false; + m_MaxMapCosts = -1.0; + } + + + + template + void ShortestPathCostFunctionLiveWire + ::AddRepulsivePoint( itk::Index<3> c ) + { + m_RepulsivePoints.push_back(c); + m_UseRepulsivePoint = true; + } + + + + template + void ShortestPathCostFunctionLiveWire + ::ClearRepulsivePoints() + { + m_RepulsivePoints.clear(); + m_UseRepulsivePoint = false; + } + + + + template + double ShortestPathCostFunctionLiveWire + ::GetCost(IndexType p1 ,IndexType p2) + { + + unsigned long xMAX = this->m_Image->GetLargestPossibleRegion().GetSize()[0]; + unsigned long yMAX = this->m_Image->GetLargestPossibleRegion().GetSize()[1]; + + double gradientX, gradientY; + gradientX = gradientY = 0.0; + + double gradientCost; + + double gradientMagnitude; + + /* ++++++++++++++++++++ GradientMagnitude costs ++++++++++++++++++++++++++*/ + + gradientMagnitude = this->m_GradientMagnImage->GetPixel(p2); + gradientX = m_GradientImage->GetPixel(p2)[0]; + gradientY = m_GradientImage->GetPixel(p2)[1]; + + + + if(m_UseCostMap && !m_CostMap.empty()) + { + std::map< int, int >::iterator end = m_CostMap.end(); + std::map< int, int >::iterator last = --(m_CostMap.end()); + + //current position + std::map< int, int >::iterator x; + //std::map< int, int >::key_type keyOfX = static_cast::key_type>(gradientMagnitude * 1000); + int keyOfX = static_cast(gradientMagnitude /* ShortestPathCostFunctionLiveWire::MAPSCALEFACTOR*/); + x = m_CostMap.find( keyOfX ); + + std::map< int, int >::iterator left2; + std::map< int, int >::iterator left1; + std::map< int, int >::iterator right1; + std::map< int, int >::iterator right2; + + if( x == end ) + {//x can also be == end if the key is not in the map but between two other keys + //search next key within map from x upwards + right1 = m_CostMap.lower_bound( keyOfX ); + } + else + { + right1 = x; + } + + if(right1 == end || right1 == last ) + { + right2 = end; + } + else//( right1 != (end-1) ) + { + std::map< int, int >::iterator temp = right1; + right2 = ++right1;//rght1 + 1 + right1 = temp; + } + + + if( right1 == m_CostMap.begin() ) + { + left1 = end; + left2 = end; + } + else if( right1 == (++(m_CostMap.begin())) ) + { + std::map< int, int >::iterator temp = right1; + left1 = --right1;//rght1 - 1 + right1 = temp; + left2 = end; + } + else + { + std::map< int, int >::iterator temp = right1; + left1 = --right1;//rght1 - 1 + left2 = --right1;//rght1 - 2 + right1 = temp; + } + + double partRight1, partRight2, partLeft1, partLeft2; + partRight1 = partRight2 = partLeft1 = partLeft2 = 0.0; + + + /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + f(x) = v(bin) * e^ ( -1/2 * (|x-k(bin)| / sigma)^2 ) + + gaussian approximation + + where + v(bin) is the value in the map + k(bin) is the key + */ + if( left2 != end ) + { + partLeft2 = ShortestPathCostFunctionLiveWire::Gaussian(keyOfX, left2->first, left2->second); + } + + if( left1 != end ) + { + partLeft1 = ShortestPathCostFunctionLiveWire::Gaussian(keyOfX, left1->first, left1->second); + } + + if( right1 != end ) + { + partRight1 = ShortestPathCostFunctionLiveWire::Gaussian(keyOfX, right1->first, right1->second); + } + + if( right2 != end ) + { + partRight2 = ShortestPathCostFunctionLiveWire::Gaussian(keyOfX, right2->first, right2->second); + } + /*----------------------------------------------------------------------------*/ + + + if( m_MaxMapCosts > 0.0 ) + { + gradientCost = 1.0 - ( (partRight1 + partRight2 + partLeft1 + partLeft2) / m_MaxMapCosts ); + } + else + {//use linear mapping + gradientCost = 1.0 - (gradientMagnitude / m_GradientMax); + } + + } + else + {//use linear mapping + //value between 0 (good) and 1 (bad) + gradientCost = 1.0 - (gradientMagnitude / m_GradientMax); + + } + /* -----------------------------------------------------------------------------*/ + + + + /* ++++++++++++++++++++ Laplacian zero crossing costs ++++++++++++++++++++++++++*/ + // f(p) = 0; if I(p)=0 + // or 1; if I(p)!=0 + double laplacianCost; + typename Superclass::PixelType laplaceImageValue; + + + laplaceImageValue = m_EdgeImage->GetPixel(p2); + + if(laplaceImageValue < 0 || laplaceImageValue > 0) + { + laplacianCost = 1.0; + } + else + { + laplacianCost = 0.0; + } + + /* -----------------------------------------------------------------------------*/ + + + + /* ++++++++++++++++++++ Gradient direction costs ++++++++++++++++++++++++++*/ + //vector q-p i.e. p2-p1 + double vQP[2]; + vQP[0] = p2[0] - p1[0]; + vQP[1] = p2[1] - p1[1]; + //------- + + //vector p-q i.e. p1-p2 + double vPQ[2]; + vPQ[0] = p1[0] - p2[0]; + vPQ[1] = p1[1] - p2[1]; + //------- + + // gradient vector at p1 + double nGradientAtP1[2]; + nGradientAtP1[0] = gradientX;//previously computed for gradient magnitude + nGradientAtP1[1] = gradientY; + + //gradient direction unit vector of p1 + nGradientAtP1[0] /= gradientMagnitude; + nGradientAtP1[1] /= gradientMagnitude; + //------- + + // gradient vector at p1 + double nGradientAtP2[2]; + + + nGradientAtP2[0] = m_GradientImage->GetPixel(p2)[0]; + nGradientAtP2[1] = m_GradientImage->GetPixel(p2)[1]; + + nGradientAtP2[0] /= m_GradientMagnImage->GetPixel(p2); + nGradientAtP2[1] /= m_GradientMagnImage->GetPixel(p2); + + + double scalarProduct = (nGradientAtP1[0] * nGradientAtP2[0]) + (nGradientAtP1[1] * nGradientAtP2[1]); + if( abs(scalarProduct) >= 1.0) + { + //this should probably not happen; make sure the input for acos is valid + scalarProduct = 0.999999999; + } + + double gradientDirectionCost = acos( scalarProduct ) / 3.14159265; + /*------------------------------------------------------------------------*/ + + + + + /*+++++++++++++++++++++ local component costs +++++++++++++++++++++++++++*/ + /*weights*/ + double w1; + double w2; + double w3; + double costs = 0.0; + + if (this->m_UseCostMap){ + w1 = 0.43; + w2= 0.43; + w3 = 0.14; + }else{ + w1 = 0.10; + w2= 0.85; + w3 = 0.05; + } + costs = w1 * laplacianCost + w2 * gradientCost + w3 * gradientDirectionCost; + + + //scale by euclidian distance + double costScale; + if( p1[0] == p2[0] || p1[1] == p2[1]) + { + //horizontal or vertical neighbor + costScale = 1.0; + } + else + { + //diagonal neighbor + costScale = sqrt(2.0); + } + + costs *= costScale; + + return costs; + } + + + + template + double ShortestPathCostFunctionLiveWire + ::GetMinCost() + { + return minCosts; + } + + + + template + void ShortestPathCostFunctionLiveWire + ::Initialize() + { + if(!m_Initialized) + { + + + typedef itk::CastImageFilter< TInputImageType, FloatImageType > CastFilterType; + typename CastFilterType::Pointer castFilter = CastFilterType::New(); + castFilter->SetInput(this->m_Image); + + + // init gradient magnitude image + typedef itk::GradientMagnitudeImageFilter< FloatImageType, FloatImageType> GradientMagnitudeFilterType; + typename GradientMagnitudeFilterType::Pointer gradientFilter = GradientMagnitudeFilterType::New(); + gradientFilter->SetInput(castFilter->GetOutput()); + //gradientFilter->SetNumberOfThreads(4); + //gradientFilter->GetOutput()->SetRequestedRegion(m_RequestedRegion); + + gradientFilter->Update(); + m_GradientMagnImage = gradientFilter->GetOutput(); + + typedef itk::StatisticsImageFilter StatisticsImageFilterType; + typename StatisticsImageFilterType::Pointer statisticsImageFilter = StatisticsImageFilterType::New(); + statisticsImageFilter->SetInput(this->m_GradientMagnImage); + statisticsImageFilter->Update(); + + m_GradientMax = statisticsImageFilter->GetMaximum(); + + + + //Filter class is instantiated + /*typedef itk::GradientRecursiveGaussianImageFilter GradientFilterType;*/ + + typedef itk::GradientImageFilter< FloatImageType > GradientFilterType; + + typename GradientFilterType::Pointer filter = GradientFilterType::New(); + //sigma is specified in millimeters + //filter->SetSigma( 1.5 ); + filter->SetInput(castFilter->GetOutput()); + filter->Update(); + + m_GradientImage = filter->GetOutput(); + + + // init zero crossings + //typedef itk::ZeroCrossingImageFilter< TInputImageType, UnsignedCharImageType > ZeroCrossingImageFilterType; + //ZeroCrossingImageFilterType::Pointer zeroCrossingImageFilter = ZeroCrossingImageFilterType::New(); + //zeroCrossingImageFilter->SetInput(this->m_Image); + //zeroCrossingImageFilter->SetBackgroundValue(1); + //zeroCrossingImageFilter->SetForegroundValue(0); + //zeroCrossingImageFilter->SetNumberOfThreads(4); + //zeroCrossingImageFilter->Update(); + + //m_EdgeImage = zeroCrossingImageFilter->GetOutput(); + + + //cast image to float to apply canny edge dection filter + /*typedef itk::CastImageFilter< TInputImageType, FloatImageType > CastFilterType; + CastFilterType::Pointer castFilter = CastFilterType::New(); + castFilter->SetInput(this->m_Image);*/ + + //typedef itk::LaplacianImageFilter filterType; + //filterType::Pointer laplacianFilter = filterType::New(); + //laplacianFilter->SetInput( castFilter->GetOutput() ); // NOTE: input image type must be double or float + //laplacianFilter->Update(); + + //m_EdgeImage = laplacianFilter->GetOutput(); + + //init canny edge detection + typedef itk::CannyEdgeDetectionImageFilter CannyEdgeDetectionImageFilterType; + typename CannyEdgeDetectionImageFilterType::Pointer cannyEdgeDetectionfilter = CannyEdgeDetectionImageFilterType::New(); + cannyEdgeDetectionfilter->SetInput(castFilter->GetOutput()); + cannyEdgeDetectionfilter->SetUpperThreshold(30); + cannyEdgeDetectionfilter->SetLowerThreshold(15); + cannyEdgeDetectionfilter->SetVariance(4); + cannyEdgeDetectionfilter->SetMaximumError(.01f); + + cannyEdgeDetectionfilter->Update(); + m_EdgeImage = cannyEdgeDetectionfilter->GetOutput(); + + + // set minCosts + minCosts = 0.0; // The lower, the more thouroughly! 0 = dijkstra. If estimate costs are lower than actual costs everything is fine. If estimation is higher than actual costs, you might not get the shortest but a different path. + + m_Initialized = true; + } + + // check start/end point value + startValue= this->m_Image->GetPixel(this->m_StartIndex); + endValue= this->m_Image->GetPixel(this->m_EndIndex); + } + + + + template + double ShortestPathCostFunctionLiveWire::SigmoidFunction(double I, double max, double min, double alpha, double beta) + { + // Using the SIgmoid formula from ITK Software Guide 6.3.2 Non Linear Mappings + double Exponent = -1 * ((I - beta) / alpha); + double Factor = 1 / (1 + exp(Exponent)); + double newI = (max - min) * Factor + min; + + return newI; + } + + + template + double ShortestPathCostFunctionLiveWire::Gaussian(double x, double xOfGaussian, double yOfGaussian) + { + return yOfGaussian * exp( -0.5 * pow( (x - xOfGaussian), 2) ); + } + +} // end namespace itk + +#endif // __itkShortestPathCostFunctionLiveWire_txx diff --git a/Modules/GraphAlgorithms/itkShortestPathImageFilter.h b/Modules/GraphAlgorithms/itkShortestPathImageFilter.h index c93a0204a3..1a3216a6dc 100644 --- a/Modules/GraphAlgorithms/itkShortestPathImageFilter.h +++ b/Modules/GraphAlgorithms/itkShortestPathImageFilter.h @@ -1,225 +1,232 @@ /*=================================================================== 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 __itkShortestPathImageFilter_h #define __itkShortestPathImageFilter_h #include "itkImageToImageFilter.h" #include "itkShortestPathCostFunction.h" #include "itkShortestPathNode.h" #include #include // ------- INFORMATION ---------- /// SET FUNCTIONS //void SetInput( ItkImage ) // Compulsory //void SetStartIndex (const IndexType & StartIndex); // Compulsory //void SetEndIndex(const IndexType & EndIndex); // Compulsory //void SetFullNeighborsMode(bool) // Optional (default=false), if false N4, if true N26 //void SetActivateTimeOut(bool) // Optional (default=false), for debug issues: after 30s algorithms terminates. You can have a look at the VectorOrderImage to see how far it came //void SetMakeOutputImage(bool) // Optional (default=true), Generate an outputimage of the path. You can also get the path directoy with GetVectorPath() //void SetCalcAllDistances(bool) // Optional (default=false), Calculate Distances over the whole image. CAREFUL, algorithm time extends a lot. Necessary for GetDistanceImage //void SetStoreVectorOrder(bool) // Optional (default=false), Stores in which order the pixels were checked. Necessary for GetVectorOrderImage //void AddEndIndex(const IndexType & EndIndex) //Optional. By calling this function you can add several endpoints! The algorithm will look for several shortest Pathes. From Start to all Endpoints. // /// GET FUNCTIONS //std::vector< itk::Index<3> > GetVectorPath(); // returns the shortest path as vector //std::vector< std::vector< itk::Index<3> > GetMultipleVectorPathe(); // returns a vector of shortest Pathes (which are vectors of points) //GetDistanceImage // Returns the distance image //GetVectorOrderIMage // Returns the Vector Order image // // EXAMPLE USE // pleae see qmitkmitralvalvesegmentation4dtee bundle namespace itk { template class ShortestPathImageFilter : public ImageToImageFilter { public: //Standard Typedefs typedef ShortestPathImageFilter Self; typedef ImageToImageFilter Superclass; typedef SmartPointer Pointer; typedef SmartPointer ConstPointer; // Typdefs for metric typedef ShortestPathCostFunction< TInputImageType > CostFunctionType; typedef typename CostFunctionType::Pointer CostFunctionTypePointer; // More typdefs for convenience typedef TInputImageType InputImageType; typedef typename TInputImageType::Pointer InputImagePointer; typedef typename TInputImageType::PixelType InputImagePixelType; typedef typename TInputImageType::SizeType InputImageSizeType; typedef typename TInputImageType::IndexType IndexType; typedef typename itk::ImageRegionIteratorWithIndex< InputImageType > InputImageIteratorType; typedef TOutputImageType OutputImageType; typedef typename TOutputImageType::Pointer OutputImagePointer; typedef typename TOutputImageType::PixelType OutputImagePixelType; typedef typename TOutputImageType::IndexType OutputImageIndexType; typedef ImageRegionIteratorWithIndex< OutputImageType > OutputImageIteratorType; typedef itk::ShapedNeighborhoodIterator< TInputImageType > itkShapedNeighborhoodIteratorType; // New Macro for smartpointer instantiation itkNewMacro(Self) // Run-time type information itkTypeMacro(ShortestPathImageFilter, ImageToImageFilter) // Display void PrintSelf( std::ostream& os, Indent indent ) const; // Compare function for A_STAR struct CompareNodeStar { bool operator()(ShortestPathNode *a, ShortestPathNode *b) { return (a->distAndEst > b->distAndEst); } }; // \brief Set Starpoint for ShortestPath Calculation void SetStartIndex (const IndexType & StartIndex); // \brief Adds Endpoint for multiple ShortestPath Calculation void AddEndIndex (const IndexType & index); // \brief Set Endpoint for ShortestPath Calculation void SetEndIndex(const IndexType & EndIndex); // \brief Set FullNeighborsMode. false = no diagonal neighbors, in 2D this means N4 Neigborhood. true = would be N8 in 2D itkSetMacro (FullNeighborsMode, bool); itkGetMacro (FullNeighborsMode, bool); // \brief (default=true), Produce output image, which shows the shortest path. But you can also get the shortest Path directly as vector with the function GetVectorPath itkSetMacro (MakeOutputImage, bool); itkGetMacro (MakeOutputImage, bool); // \brief (default=false), Store an Vector of Order, so you can call getVectorOrderImage after update itkSetMacro (StoreVectorOrder, bool); itkGetMacro (StoreVectorOrder, bool); // \brief (default=false), // Calculate all Distances to all pixels, so you can call getDistanceImage after update (warning algo will take a long time) itkSetMacro (CalcAllDistances, bool); itkGetMacro (CalcAllDistances, bool); // \brief (default=false), for debug issues: after 30s algorithms terminates. You can have a look at the VectorOrderImage to see how far it came itkSetMacro (ActivateTimeOut, bool); itkGetMacro (ActivateTimeOut, bool); // \brief returns shortest Path as vector - std::vector< itk::Index<3> > GetVectorPath(); + std::vector< IndexType > GetVectorPath(); // \brief returns Multiple shortest Paths. You can call this function, when u performed a multiple shortest path search (one start, several ends) - std::vector< std::vector< itk::Index<3> > > GetMultipleVectorPaths(); + std::vector< std::vector< IndexType > > GetMultipleVectorPaths(); // \brief returns the vector order image. It shows in which order the pixels were checked. good for debugging. Be sure to have m_StoreVectorOrder=true OutputImagePointer GetVectorOrderImage(); // \brief returns the distance image. It shows the distances from the startpoint to all other pixels. Be sure to have m_CalcAllDistances=true OutputImagePointer GetDistanceImage(); + + // \brief Fill m_VectorPath + void MakeShortestPathVector(); + // \brief cleans up the filter void CleanUp(); itkSetObjectMacro( CostFunction, CostFunctionType ); // itkSetObjectMacro = set function that uses pointer as parameter itkGetObjectMacro( CostFunction, CostFunctionType ); protected: std::vector< IndexType > m_endPoints; // if you fill this vector, the algo will not rest until all endPoints have been reached std::vector< IndexType > m_endPointsClosed; ShortestPathNode* m_Nodes; // main list that contains all nodes NodeNumType m_Graph_NumberOfNodes; NodeNumType m_Graph_StartNode; NodeNumType m_Graph_EndNode; int m_ImageDimensions; bool m_Graph_fullNeighbors; std::vector m_Graph_DiscoveredNodeList; ShortestPathImageFilter(Self&); // intentionally not implemented void operator=(const Self&); // intentionally not implemented const static int BACKGROUND = 0; const static int FOREGROUND = 255; bool m_FullNeighborsMode; bool m_MakeOutputImage; bool m_StoreVectorOrder; // Store an Vector of Order, so you can call getVectorOrderImage after update bool m_CalcAllDistances; // Calculate all Distances, so you can call getDistanceImage after update (warning algo will take a long time) bool multipleEndPoints; bool m_ActivateTimeOut; // if true, then i search max. 30 secs. then abort + + bool m_Initialized; + + CostFunctionTypePointer m_CostFunction; IndexType m_StartIndex, m_EndIndex; - std::vector< itk::Index<3> > m_VectorPath; - std::vector< std::vector< itk::Index<3> > > m_MultipleVectorPaths; + std::vector< IndexType > m_VectorPath; + std::vector< std::vector< IndexType > > m_MultipleVectorPaths; std::vector< NodeNumType > m_VectorOrder; ShortestPathImageFilter(); - // \brief Fill m_VectorPath - void MakeShortestPathVector(); + ~ShortestPathImageFilter(); // \brief Create all the outputs void MakeOutputs(); // \brief Generate Data void GenerateData(); // \brief gets the estimate costs from pixel a to target. double getEstimatedCostsToTarget(const IndexType & a); typename InputImageType::Pointer m_magnitudeImage; // \brief Convert a indexnumber of a node in m_Nodes to image coordinates typename TInputImageType::IndexType NodeToCoord(NodeNumType); // \brief Convert image coordinate to a indexnumber of a node in m_Nodes unsigned int CoordToNode(IndexType); // \brief Returns the neighbors of a node std::vector GetNeighbors(NodeNumType nodeNum, bool FullNeighbors); // \brief Check if coords are in bounds of image bool CoordIsInBounds(IndexType); // \brief Initializes the graph void InitGraph(); // \brief Start ShortestPathSearch void StartShortestPathSearch(); }; } // end of namespace itk #include "itkShortestPathImageFilter.txx" #endif diff --git a/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx b/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx index a255040bfb..933130fe43 100644 --- a/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx +++ b/Modules/GraphAlgorithms/itkShortestPathImageFilter.txx @@ -1,939 +1,951 @@ /*=================================================================== 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 __itkShortestPathImageFilter_txx #define __itkShortestPathImageFilter_txx #include "time.h" #include "mitkMemoryUtilities.h" #include #include #include namespace itk { // Constructor (initialize standard values) template ShortestPathImageFilter ::ShortestPathImageFilter() : m_FullNeighborsMode(false), m_MakeOutputImage(true), m_StoreVectorOrder(false), m_CalcAllDistances(false), m_ActivateTimeOut(false), - multipleEndPoints(false), - m_Nodes(0), - m_Graph_NumberOfNodes(0) + multipleEndPoints(false), + m_Initialized(false), + m_Nodes(0), + m_Graph_NumberOfNodes(0) { - m_endPoints.clear(); - m_endPointsClosed.clear(); + m_endPoints.clear(); + m_endPointsClosed.clear(); - if (m_MakeOutputImage) - { - this->SetNumberOfRequiredOutputs(1); - this->SetNthOutput( 0, OutputImageType::New() ); - } + if (m_MakeOutputImage) + { + this->SetNumberOfRequiredOutputs(1); + this->SetNthOutput( 0, OutputImageType::New() ); + } } + + + template + ShortestPathImageFilter + ::~ShortestPathImageFilter() + { + delete [] m_Nodes; + } + + + + template inline typename ShortestPathImageFilter::IndexType ShortestPathImageFilter ::NodeToCoord (NodeNumType node) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; IndexType coord; if (dim == 2) { coord[1] = node / size[0]; coord[0] = node % size[0]; if ((coord[0] >= size[0]) || (coord[1] >= size[1])) { coord[0] = 0; coord[1] = 0; } } if (dim == 3) { coord[2] = node / (size[0]*size[1]); coord[1] = (node % (size[0]*size[1])) / size[0]; coord[0] = (node % (size[0]*size[1])) % size[0]; if ((coord[0] >= size[0]) || (coord[1] >= size[1]) || (coord[2] >= size[2])) { coord[0] = 0; coord[1] = 0; coord[2] = 0; } } return coord; } template inline typename itk::NodeNumType ShortestPathImageFilter:: CoordToNode (IndexType coord) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; NodeNumType node = 0; if (dim == 2) { node = (coord[1]*size[0]) + coord[0]; } if (dim == 3) { node = (coord[2]*size[0]*size[1]) + (coord[1]*size[0]) + coord[0]; } if ((m_Graph_NumberOfNodes > 0) && (node >= m_Graph_NumberOfNodes)) - { - MITK_INFO << "WARNING! Coordinates outside image!"; - MITK_INFO << "Coords = " << coord ; - MITK_INFO << "ImageDim = " << dim ; - MITK_INFO << "RequestedRegionSize = " << size ; + { + /*MITK_INFO << "WARNING! Coordinates outside image!"; + MITK_INFO << "Coords = " << coord ; + MITK_INFO << "ImageDim = " << dim ; + MITK_INFO << "RequestedRegionSize = " << size ;*/ node = 0; - } + } return node; } template inline bool ShortestPathImageFilter:: CoordIsInBounds (IndexType coord) { const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); int dim = InputImageType::ImageDimension; if (dim == 2) { if ((coord[0] >= 0) && (coord[0] < size[0]) && (coord[1] >= 0 ) && (coord[1] < size[1] )) { return true; } } if (dim == 3) { if ((coord[0] >= 0) && (coord[0] < size[0]) && (coord[1] >= 0 ) && (coord[1] < size[1] ) && (coord[2] >= 0 ) && (coord[2] < size[2] )) { return true; } } return false; } template inline std::vector< ShortestPathNode* > ShortestPathImageFilter:: GetNeighbors (unsigned int nodeNum, bool FullNeighbors) { // returns a vector of nodepointers.. these nodes are the neighbors int dim = InputImageType::ImageDimension; IndexType Coord = NodeToCoord(nodeNum); IndexType NeighborCoord; std::vector nodeList; int neighborDistance = 1; //if i increase that, i might not hit the endnote - // maybe use itkNeighborhoodIterator here, might be faster + // maybe use itkNeighborhoodIterator here, might be faster if ( dim == 2) { // N4 NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]-neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]+neighborDistance; NeighborCoord[1] = Coord[1]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]+neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]-neighborDistance; NeighborCoord[1] = Coord[1]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); if (FullNeighbors) { // N8 NeighborCoord[0] = Coord[0]-neighborDistance; NeighborCoord[1] = Coord[1]-neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]+neighborDistance; NeighborCoord[1] = Coord[1]-neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]-neighborDistance; NeighborCoord[1] = Coord[1]+neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]+neighborDistance; NeighborCoord[1] = Coord[1]+neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); } } if ( dim == 3) { // N6 NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]-neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]+neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]+neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]-neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]+neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]-neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); if (FullNeighbors) { // N26 // Middle Slice NeighborCoord[0] = Coord[0]-neighborDistance; NeighborCoord[1] = Coord[1]-neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]+neighborDistance; NeighborCoord[1] = Coord[1]-neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]-neighborDistance; NeighborCoord[1] = Coord[1]+neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]+neighborDistance; NeighborCoord[1] = Coord[1]+neighborDistance; NeighborCoord[2] = Coord[2]; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // BackSlice (Diagonal) NeighborCoord[0] = Coord[0]-neighborDistance; NeighborCoord[1] = Coord[1]-neighborDistance; NeighborCoord[2] = Coord[2]-neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]+neighborDistance; NeighborCoord[1] = Coord[1]-neighborDistance; NeighborCoord[2] = Coord[2]-neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]-neighborDistance; NeighborCoord[1] = Coord[1]+neighborDistance; NeighborCoord[2] = Coord[2]-neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]+neighborDistance; NeighborCoord[1] = Coord[1]+neighborDistance; NeighborCoord[2] = Coord[2]-neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); //BackSlice (Non-Diag) NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]-neighborDistance; NeighborCoord[2] = Coord[2]-neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]+neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]-neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]+neighborDistance; NeighborCoord[2] = Coord[2]-neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]-neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]-neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); // FrontSlice (Diagonal) NeighborCoord[0] = Coord[0]-neighborDistance; NeighborCoord[1] = Coord[1]-neighborDistance; NeighborCoord[2] = Coord[2]+neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]+neighborDistance; NeighborCoord[1] = Coord[1]-neighborDistance; NeighborCoord[2] = Coord[2]+neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]-neighborDistance; NeighborCoord[1] = Coord[1]+neighborDistance; NeighborCoord[2] = Coord[2]+neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]+neighborDistance; NeighborCoord[1] = Coord[1]+neighborDistance; NeighborCoord[2] = Coord[2]+neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); //FrontSlice(Non-Diag) NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]-neighborDistance; NeighborCoord[2] = Coord[2]+neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]+neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]+neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]; NeighborCoord[1] = Coord[1]+neighborDistance; NeighborCoord[2] = Coord[2]+neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); NeighborCoord[0] = Coord[0]-neighborDistance; NeighborCoord[1] = Coord[1]; NeighborCoord[2] = Coord[2]+neighborDistance; if (CoordIsInBounds(NeighborCoord)) nodeList.push_back(&m_Nodes[CoordToNode(NeighborCoord)]); } } return nodeList; } template void ShortestPathImageFilter:: SetStartIndex (const typename TInputImageType::IndexType &StartIndex) { for (unsigned int i=0;i v; + itk::Vector v; v[0] = m_EndIndex[0]-a[0]; v[1] = m_EndIndex[1]-a[1]; v[2] = m_EndIndex[2]-a[2]; - return m_CostFunction->GetMinCost() * v.GetNorm(); + return m_CostFunction->GetMinCost() * v.GetNorm(); } template void ShortestPathImageFilter:: InitGraph() { - // Clean up previous stuff - CleanUp(); + if(!m_Initialized) + { + // Clean up previous stuff + CleanUp(); - // initalize cost function - m_CostFunction->Initialize(); + // Calc Number of nodes + m_ImageDimensions = TInputImageType::ImageDimension; + const InputImageSizeType &size = this->GetInput()->GetRequestedRegion().GetSize(); + m_Graph_NumberOfNodes = 1; + for (NodeNumType i=0; iGetInput()->GetRequestedRegion().GetSize(); - m_Graph_NumberOfNodes = 1; - for (NodeNumType i=0; iInitialize(); } template void ShortestPathImageFilter:: StartShortestPathSearch() { - // Setup Timer + // Setup Timer clock_t startAll = clock(); clock_t stopAll = clock(); - // init variables + // init variables double durationAll = 0; bool timeout = false; bool makeNewHeapNecessary = false; - NodeNumType mainNodeListIndex = 0; + NodeNumType mainNodeListIndex = 0; DistanceType curNodeDistance = 0; DistanceType curNodeDistAndEst = 0; - NodeNumType numberOfNodesChecked = 0; + NodeNumType numberOfNodesChecked = 0; - // Create Multimap (tree structure for fast searching) - std::multimap myMap; - std::pair< std::multimap::iterator, std::multimap::iterator> ret; - std::multimap::iterator it; + // Create Multimap (tree structure for fast searching) + std::multimap myMap; + std::pair< std::multimap::iterator, std::multimap::iterator> ret; + std::multimap::iterator it; - // At first, only startNote is discovered. - myMap.insert( std::pair (m_Nodes[m_Graph_StartNode].distAndEst, &m_Nodes[m_Graph_StartNode]) ); + // At first, only startNote is discovered. + myMap.insert( std::pair (m_Nodes[m_Graph_StartNode].distAndEst, &m_Nodes[m_Graph_StartNode]) ); // While there are discovered Nodes, pick the one with lowest distance, - // update its neighbors and eventually delete it from the discovered Nodes list. + // update its neighbors and eventually delete it from the discovered Nodes list. while(!myMap.empty()) { - numberOfNodesChecked++; - if ( (numberOfNodesChecked % (m_Graph_NumberOfNodes/100)) == 0) - { - MITK_INFO << "Checked " << ( numberOfNodesChecked / (m_Graph_NumberOfNodes/100) ) << "% List: " << myMap.size() << "\n"; - } - - // Get element with lowest score - mainNodeListIndex = myMap.begin()->second->mainListIndex; + numberOfNodesChecked++; + /*if ( (numberOfNodesChecked % (m_Graph_NumberOfNodes/100)) == 0) + { + MITK_INFO << "Checked " << ( numberOfNodesChecked / (m_Graph_NumberOfNodes/100) ) << "% List: " << myMap.size() << "\n"; + }*/ + + // Get element with lowest score + mainNodeListIndex = myMap.begin()->second->mainListIndex; curNodeDistAndEst = myMap.begin()->second->distAndEst; curNodeDistance = myMap.begin()->second->distance; - myMap.begin()->second->closed = true; // close it + myMap.begin()->second->closed = true; // close it - // Debug: - //MITK_INFO << "INFO: size " << myMap.size(); - /* - for (it = myMap.begin(); it != myMap.end(); ++it) - { - MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; - } - */ + // Debug: + //MITK_INFO << "INFO: size " << myMap.size(); + /* + for (it = myMap.begin(); it != myMap.end(); ++it) + { + MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; + } + */ // Kicks out element with lowest score - myMap.erase( myMap.begin() ); + myMap.erase( myMap.begin() ); - // if wanted, store vector order + // if wanted, store vector order if (m_StoreVectorOrder) - { + { m_VectorOrder.push_back(mainNodeListIndex); - } + } // Check neighbors std::vector neighborNodes = GetNeighbors(mainNodeListIndex, m_Graph_fullNeighbors); for (NodeNumType i=0; iclosed) - continue; // this nodes is already closed, go to next neighbor + if (neighborNodes[i]->closed) + continue; // this nodes is already closed, go to next neighbor IndexType coordCurNode = NodeToCoord(mainNodeListIndex); IndexType coordNeighborNode = NodeToCoord(neighborNodes[i]->mainListIndex); // calculate the new Distance to the current neighbor double newDistance = curNodeDistance + (m_CostFunction->GetCost(coordCurNode, coordNeighborNode)); // if it is shorter than any yet known path to this neighbor, than the current path is better. Save that! if ((newDistance < neighborNodes[i]->distance) || (neighborNodes[i]->distance == -1) ) { // if that neighbornode is not in discoverednodeList yet, Push it there and update if (neighborNodes[i]->distance == -1) { neighborNodes[i]->distance = newDistance; neighborNodes[i]->distAndEst = newDistance + getEstimatedCostsToTarget(coordNeighborNode); neighborNodes[i]->prevNode = mainNodeListIndex; - myMap.insert( std::pair (m_Nodes[neighborNodes[i]->mainListIndex].distAndEst, &m_Nodes[neighborNodes[i]->mainListIndex]) ); - /* - MITK_INFO << "Inserted: " << m_Nodes[neighborNodes[i]->mainListIndex].distAndEst << "|" << m_Nodes[neighborNodes[i]->mainListIndex].mainListIndex; - MITK_INFO << "INFO: size " << myMap.size(); - for (it = myMap.begin(); it != myMap.end(); ++it) - { - MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; - } - */ + myMap.insert( std::pair (m_Nodes[neighborNodes[i]->mainListIndex].distAndEst, &m_Nodes[neighborNodes[i]->mainListIndex]) ); + /* + MITK_INFO << "Inserted: " << m_Nodes[neighborNodes[i]->mainListIndex].distAndEst << "|" << m_Nodes[neighborNodes[i]->mainListIndex].mainListIndex; + MITK_INFO << "INFO: size " << myMap.size(); + for (it = myMap.begin(); it != myMap.end(); ++it) + { + MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; + } + */ } // or if is already in discoverednodelist, update else { - /* - it = myMap.find(neighborNodes[i]->distAndEst); - if (it == myMap.end() ) - { - MITK_INFO << "Nothing!"; - // look further - for (it = myMap.begin(); it != myMap.end(); ++it) - { - if ((*it).second->mainListIndex == lookForId) - { - MITK_INFO << "But it is there!!!"; - MITK_INFO << "Searched for: " << lookFor << " but had: " << (*it).second->distAndEst; - } - - } - } - */ + /* + it = myMap.find(neighborNodes[i]->distAndEst); + if (it == myMap.end() ) + { + MITK_INFO << "Nothing!"; + // look further + for (it = myMap.begin(); it != myMap.end(); ++it) + { + if ((*it).second->mainListIndex == lookForId) + { + MITK_INFO << "But it is there!!!"; + MITK_INFO << "Searched for: " << lookFor << " but had: " << (*it).second->distAndEst; + } - // 1st : find and delete old element - bool found = false; - double lookFor = neighborNodes[i]->distAndEst; - unsigned int lookForId = neighborNodes[i]->mainListIndex; - ret = myMap.equal_range(neighborNodes[i]->distAndEst); + } + } + */ - if ((ret.first == ret.second)) - { - MITK_INFO << "No exact match!"; // if this happens, you are screwed - /* - MITK_INFO << "Was looking for: " << lookFor << " ID: " << lookForId; - if (ret.first != myMap.end() ) - { - it = ret.first; - MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; - ++it; - MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; - --it; - --it; - MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; - } - - // look if that ID is found in the map - for (it = myMap.begin(); it != myMap.end(); ++it) - { - if ((*it).second->mainListIndex == lookForId) - { - MITK_INFO << "But it is there!!!"; - MITK_INFO << "Searched dist: " << lookFor << " found dist: " << (*it).second->distAndEst; - } - - } - */ - } - else - { - for (it=ret.first; it!=ret.second; ++it) - { - if (it->second->mainListIndex == neighborNodes[i]->mainListIndex) - { - found = true; - myMap.erase(it); - /* - MITK_INFO << "INFO: size " << myMap.size(); - MITK_INFO << "Erase: " << it->first << "|" << it->second->mainListIndex; - - MITK_INFO << "INFO: size " << myMap.size(); - for (it = myMap.begin(); it != myMap.end(); ++it) - { - MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; - } - */ - break; - } - } - } + // 1st : find and delete old element + bool found = false; + double lookFor = neighborNodes[i]->distAndEst; + unsigned int lookForId = neighborNodes[i]->mainListIndex; + ret = myMap.equal_range(neighborNodes[i]->distAndEst); + if ((ret.first == ret.second)) + { + /*+++++++++++++ no exact match +++++++++++++*/ + //MITK_INFO << "No exact match!"; // if this happens, you are screwed + /* + MITK_INFO << "Was looking for: " << lookFor << " ID: " << lookForId; + if (ret.first != myMap.end() ) + { + it = ret.first; + MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; + ++it; + MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; + --it; + --it; + MITK_INFO << "Found: " << it->first << " ID: " << it->second->mainListIndex; + } + + // look if that ID is found in the map + for (it = myMap.begin(); it != myMap.end(); ++it) + { + if ((*it).second->mainListIndex == lookForId) + { + MITK_INFO << "But it is there!!!"; + MITK_INFO << "Searched dist: " << lookFor << " found dist: " << (*it).second->distAndEst; + } + + } + */ + } + else + { + for (it=ret.first; it!=ret.second; ++it) + { + if (it->second->mainListIndex == neighborNodes[i]->mainListIndex) + { + found = true; + myMap.erase(it); + /* + MITK_INFO << "INFO: size " << myMap.size(); + MITK_INFO << "Erase: " << it->first << "|" << it->second->mainListIndex; - if (!found) + MITK_INFO << "INFO: size " << myMap.size(); + for (it = myMap.begin(); it != myMap.end(); ++it) { - MITK_INFO << "Could not find it! :("; - continue; + MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; } + */ + break; + } + } + } - // 2nd: update and insert new element - neighborNodes[i]->distance = newDistance; + if (!found) + { + //MITK_INFO << "Could not find it! :("; + continue; + } + + // 2nd: update and insert new element + neighborNodes[i]->distance = newDistance; neighborNodes[i]->distAndEst = newDistance + getEstimatedCostsToTarget(coordNeighborNode); neighborNodes[i]->prevNode = mainNodeListIndex; - //myMap.insert( std::pair (neighborNodes[i]->distAndEst, neighborNodes[i])); - myMap.insert( std::pair (m_Nodes[neighborNodes[i]->mainListIndex].distAndEst, &m_Nodes[neighborNodes[i]->mainListIndex]) ); + //myMap.insert( std::pair (neighborNodes[i]->distAndEst, neighborNodes[i])); + myMap.insert( std::pair (m_Nodes[neighborNodes[i]->mainListIndex].distAndEst, &m_Nodes[neighborNodes[i]->mainListIndex]) ); - //MITK_INFO << "Re-Inserted: " << m_Nodes[neighborNodes[i]->mainListIndex].distAndEst << "|" << m_Nodes[neighborNodes[i]->mainListIndex].mainListIndex; - //MITK_INFO << "INFO: size " << myMap.size(); - /*for (it = myMap.begin(); it != myMap.end(); ++it) - { - MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; - }*/ + //MITK_INFO << "Re-Inserted: " << m_Nodes[neighborNodes[i]->mainListIndex].distAndEst << "|" << m_Nodes[neighborNodes[i]->mainListIndex].mainListIndex; + //MITK_INFO << "INFO: size " << myMap.size(); + /*for (it = myMap.begin(); it != myMap.end(); ++it) + { + MITK_INFO << "(1) " << it->first << "|" << it->second->distAndEst << "|"<second->mainListIndex; + }*/ } } } // finished with checking all neighbors. // Check Timeout, if activated if (m_ActivateTimeOut) { stopAll = clock(); durationAll = (double)(stopAll - startAll) / CLOCKS_PER_SEC; if (durationAll >= 30) { - MITK_INFO << "TIMEOUT!! Search took over 30 seconds"; + //MITK_INFO << "TIMEOUT!! Search took over 30 seconds"; timeout = true ; } } - // Check end criteria: - // For multiple points - if ( multipleEndPoints ) - { - // super slow, make it faster - for (int i=0 ;i void ShortestPathImageFilter:: MakeOutputs() { - MITK_INFO << "Make Output"; + //MITK_INFO << "Make Output"; - if (m_MakeOutputImage) + if (m_MakeOutputImage) + { + OutputImagePointer output0 = this->GetOutput(0); + output0->SetRegions( this->GetInput()->GetLargestPossibleRegion() ); + output0->Allocate(); + OutputImageIteratorType shortestPathImageIt (output0, output0->GetRequestedRegion()); + // Create ShortestPathImage (Output 0) + for (shortestPathImageIt.GoToBegin(); !shortestPathImageIt.IsAtEnd(); ++shortestPathImageIt) { - OutputImagePointer output0 = this->GetOutput(0); - output0->SetRegions( this->GetInput()->GetLargestPossibleRegion() ); - output0->Allocate(); - OutputImageIteratorType shortestPathImageIt (output0, output0->GetRequestedRegion()); - // Create ShortestPathImage (Output 0) - for (shortestPathImageIt.GoToBegin(); !shortestPathImageIt.IsAtEnd(); ++shortestPathImageIt) - { - // First intialize with background color - shortestPathImageIt.Set( BACKGROUND ) ; - } - - if (!multipleEndPoints) // Only one path was calculated - { - for (int i=0; i< m_VectorPath.size(); i++) + // First intialize with background color + shortestPathImageIt.Set( BACKGROUND ) ; + } + + if (!multipleEndPoints) // Only one path was calculated + { + for (int i=0; i< m_VectorPath.size(); i++) + { + shortestPathImageIt.SetIndex( m_VectorPath[i] ); + shortestPathImageIt.Set( FOREGROUND ) ; + } + } + else // multiple pathes has been calculated, draw all + { + for (int i =0; i - typename ShortestPathImageFilter::OutputImagePointer + typename ShortestPathImageFilter::OutputImagePointer ShortestPathImageFilter:: GetVectorOrderImage() { - // Create Vector Order Image - // Return it + // Create Vector Order Image + // Return it - OutputImagePointer image = OutputImageType::New(); - image->SetRegions( this->GetInput()->GetLargestPossibleRegion() ); - image->Allocate(); - OutputImageIteratorType vectorOrderImageIt (image, image->GetRequestedRegion()); + OutputImagePointer image = OutputImageType::New(); + image->SetRegions( this->GetInput()->GetLargestPossibleRegion() ); + image->Allocate(); + OutputImageIteratorType vectorOrderImageIt (image, image->GetRequestedRegion()); - MITK_INFO << "GetVectorOrderImage"; - for (vectorOrderImageIt.GoToBegin(); !vectorOrderImageIt.IsAtEnd(); ++vectorOrderImageIt) - { - // First intialize with background color - vectorOrderImageIt.Value() = BACKGROUND ; - } - for (int i=0; i< m_VectorOrder.size(); i++) - { - vectorOrderImageIt.SetIndex(NodeToCoord(m_VectorOrder[i]) ); - vectorOrderImageIt.Set( BACKGROUND+i) ; - } + //MITK_INFO << "GetVectorOrderImage"; + for (vectorOrderImageIt.GoToBegin(); !vectorOrderImageIt.IsAtEnd(); ++vectorOrderImageIt) + { + // First intialize with background color + vectorOrderImageIt.Value() = BACKGROUND ; + } + for (int i=0; i< m_VectorOrder.size(); i++) + { + vectorOrderImageIt.SetIndex(NodeToCoord(m_VectorOrder[i]) ); + vectorOrderImageIt.Set( BACKGROUND+i) ; + } return image; } template typename ShortestPathImageFilter::OutputImagePointer ShortestPathImageFilter:: GetDistanceImage() { - // Create Distance Image - // Return it - - OutputImagePointer image = OutputImageType::New(); - image->SetRegions( this->GetInput()->GetLargestPossibleRegion() ); - image->Allocate();; - OutputImageIteratorType distanceImageIt (image, image->GetRequestedRegion()); - // Create Distance Image (Output 1) - NodeNumType myNodeNum; - for (distanceImageIt.GoToBegin(); !distanceImageIt.IsAtEnd(); ++distanceImageIt) - { - IndexType index = distanceImageIt.GetIndex(); - myNodeNum = CoordToNode(index); - double newVal = m_Nodes[myNodeNum].distance; - distanceImageIt.Set(newVal); - } - } + // Create Distance Image + // Return it + + OutputImagePointer image = OutputImageType::New(); + image->SetRegions( this->GetInput()->GetLargestPossibleRegion() ); + image->Allocate();; + OutputImageIteratorType distanceImageIt (image, image->GetRequestedRegion()); + // Create Distance Image (Output 1) + NodeNumType myNodeNum; + for (distanceImageIt.GoToBegin(); !distanceImageIt.IsAtEnd(); ++distanceImageIt) + { + IndexType index = distanceImageIt.GetIndex(); + myNodeNum = CoordToNode(index); + double newVal = m_Nodes[myNodeNum].distance; + distanceImageIt.Set(newVal); + } + } template - std::vector< itk::Index<3> > + std::vector< typename ShortestPathImageFilter::IndexType > ShortestPathImageFilter:: GetVectorPath() { return m_VectorPath; } - template - std::vector< std::vector< itk::Index<3> > > + template + std::vector< std::vector< typename ShortestPathImageFilter::IndexType > > ShortestPathImageFilter:: GetMultipleVectorPaths() { return m_MultipleVectorPaths; } template void ShortestPathImageFilter:: MakeShortestPathVector() { - MITK_INFO << "Make ShortestPath Vec"; - - // single end point - if ( !multipleEndPoints ) - { - // fill m_VectorPath with the Shortest Path - m_VectorPath.clear(); + //MITK_INFO << "Make ShortestPath Vec"; - // Go backwards from endnote to startnode - NodeNumType prevNode = m_Graph_EndNode; - while (prevNode != -1) - { - m_VectorPath.push_back( NodeToCoord(prevNode) ); + // single end point + if ( !multipleEndPoints ) + { + // fill m_VectorPath with the Shortest Path + m_VectorPath.clear(); - if (prevNode == m_Graph_StartNode) - break; + // Go backwards from endnote to startnode + NodeNumType prevNode = m_Graph_EndNode; + while (prevNode != -1) + { + m_VectorPath.push_back( NodeToCoord(prevNode) ); - prevNode = m_Nodes[prevNode].prevNode; - } + if (prevNode == m_Graph_StartNode) + break; - // reverse it - std::reverse(m_VectorPath.begin(), m_VectorPath.end() ); + prevNode = m_Nodes[prevNode].prevNode; } - // Multiple end end points and pathes - else + + // reverse it + std::reverse(m_VectorPath.begin(), m_VectorPath.end() ); + } + // Multiple end end points and pathes + else + { + for (int i=0; i void ShortestPathImageFilter:: CleanUp() { - - m_VectorPath.clear(); - //TODO: if multiple Path, clear all multiple Paths - /* - for (NodeNumType i=0; i void ShortestPathImageFilter:: GenerateData() { // Build Graph InitGraph(); // Calc Shortest Parth StartShortestPathSearch(); // Fill Shortest Path MakeShortestPathVector(); // Make Outputs MakeOutputs(); } template void ShortestPathImageFilter:: PrintSelf( std::ostream& os, Indent indent ) const { Superclass::PrintSelf(os,indent); } } /* end namespace itk */ #endif diff --git a/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.cpp b/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.cpp new file mode 100644 index 0000000000..69094bfa56 --- /dev/null +++ b/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.cpp @@ -0,0 +1,416 @@ +/*=================================================================== + +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 "mitkImageLiveWireContourModelFilter.h" + + +#include +#include +#include + + +mitk::ImageLiveWireContourModelFilter::ImageLiveWireContourModelFilter() +{ + OutputType::Pointer output = dynamic_cast ( this->MakeOutput( 0 ).GetPointer() ); + this->SetNumberOfRequiredInputs(1); + this->SetNumberOfOutputs( 1 ); + this->SetNthOutput(0, output.GetPointer()); + m_CostFunction = ImageLiveWireContourModelFilter::CostFunctionType::New(); + m_ShortestPathFilter = ShortestPathImageFilterType::New(); + m_ShortestPathFilter->SetCostFunction(m_CostFunction); + m_UseDynamicCostMap = false; + m_ImageModified = false; + m_Timestep = 0; +} + +mitk::ImageLiveWireContourModelFilter::~ImageLiveWireContourModelFilter() +{ + +} + + +mitk::ImageLiveWireContourModelFilter::OutputType* mitk::ImageLiveWireContourModelFilter::GetOutput() +{ + return Superclass::GetOutput(); +} + +void mitk::ImageLiveWireContourModelFilter::SetInput ( const mitk::ImageLiveWireContourModelFilter::InputType* input ) +{ + this->SetInput( 0, input ); +} + +void mitk::ImageLiveWireContourModelFilter::SetInput ( unsigned int idx, const mitk::ImageLiveWireContourModelFilter::InputType* input ) +{ + if ( idx + 1 > this->GetNumberOfInputs() ) + { + this->SetNumberOfRequiredInputs(idx + 1); + } + if ( input != static_cast ( this->ProcessObject::GetInput ( idx ) ) ) + { + this->ProcessObject::SetNthInput ( idx, const_cast ( input ) ); + this->Modified(); + this->m_ImageModified = true; + m_ShortestPathFilter = ShortestPathImageFilterType::New(); + m_ShortestPathFilter->SetCostFunction(m_CostFunction); + } +} + + + +const mitk::ImageLiveWireContourModelFilter::InputType* mitk::ImageLiveWireContourModelFilter::GetInput( void ) +{ + if (this->GetNumberOfInputs() < 1) + return NULL; + return static_cast(this->ProcessObject::GetInput(0)); +} + + +const mitk::ImageLiveWireContourModelFilter::InputType* mitk::ImageLiveWireContourModelFilter::GetInput( unsigned int idx ) +{ + if (this->GetNumberOfInputs() < 1) + return NULL; + return static_cast(this->ProcessObject::GetInput(idx)); +} + + +void mitk::ImageLiveWireContourModelFilter::GenerateData() +{ + mitk::Image::ConstPointer input = dynamic_cast(this->GetInput()); + + if(!input) + { + MITK_ERROR << "No input available."; + itkExceptionMacro("mitk::ImageToLiveWireContourFilter: No input available. Please set the input!"); + return; + } + + if( input->GetDimension() != 2 ) + { + MITK_ERROR << "Filter is only working on 2D images."; + itkExceptionMacro("mitk::ImageToLiveWireContourFilter: Filter is only working on 2D images.. Please make sure that the input is 2D!"); + return; + } + + + input->GetGeometry()->WorldToIndex(m_StartPoint, m_StartPointInIndex); + input->GetGeometry()->WorldToIndex(m_EndPoint, m_EndPointInIndex); + + //only start calculating if both indices are inside image geometry + if( input->GetGeometry()->IsIndexInside(this->m_StartPointInIndex) && input->GetGeometry()->IsIndexInside(this->m_EndPointInIndex) ) + { + AccessFixedDimensionByItk(input, ItkProcessImage, 2); + m_ImageModified = false; + } +} + + + +template +void mitk::ImageLiveWireContourModelFilter::ItkProcessImage (itk::Image* inputImage) +{ + typedef itk::Image< TPixel, VImageDimension > InputImageType; + typedef typename InputImageType::IndexType IndexType; + + + /* compute the requested region for itk filters */ + + IndexType startPoint, endPoint; + + startPoint[0] = m_StartPointInIndex[0]; + startPoint[1] = m_StartPointInIndex[1]; + + endPoint[0] = m_EndPointInIndex[0]; + endPoint[1] = m_EndPointInIndex[1]; + + //minimum value in each direction for startRegion + IndexType startRegion; + startRegion[0] = startPoint[0] < endPoint[0] ? startPoint[0] : endPoint[0]; + startRegion[1] = startPoint[1] < endPoint[1] ? startPoint[1] : endPoint[1]; + + //maximum value in each direction for size + typename InputImageType::SizeType size; + size[0] = abs( startPoint[0] - endPoint[0] ); + size[1] = abs( startPoint[1] - endPoint[1] ); + + + typename CostFunctionType::RegionType region; + region.SetSize( size ); + region.SetIndex( startRegion ); + /*---------------------------------------------*/ + + //inputImage->SetRequestedRegion(region); + + typedef itk::CastImageFilter< InputImageType, FloatImageType > CastFilterType; + typename CastFilterType::Pointer castFilter = CastFilterType::New(); + castFilter->SetInput(inputImage); + castFilter->Update(); + /* extracts features from image and calculates costs */ + if( m_ImageModified ) + m_CostFunction->SetImage(castFilter->GetOutput()); + m_CostFunction->SetStartIndex(startPoint); + m_CostFunction->SetEndIndex(endPoint); + m_CostFunction->SetRequestedRegion(region); + m_CostFunction->SetUseCostMap(m_UseDynamicCostMap); + /*---------------------------------------------*/ + + + /* calculate shortest path between start and end point */ + m_ShortestPathFilter->SetFullNeighborsMode(true); + m_ShortestPathFilter->SetInput(castFilter->GetOutput()); + m_ShortestPathFilter->SetMakeOutputImage(false); + + //m_ShortestPathFilter->SetCalcAllDistances(true); + m_ShortestPathFilter->SetStartIndex(startPoint); + m_ShortestPathFilter->SetEndIndex(endPoint); + + + m_ShortestPathFilter->Update(); + + /*---------------------------------------------*/ + + + /* construct contour from path image */ + //get the shortest path as vector + typename std::vector< ShortestPathImageFilterType::IndexType> shortestPath = m_ShortestPathFilter->GetVectorPath(); + + //fill the output contour with controll points from the path + OutputType::Pointer output = dynamic_cast ( this->MakeOutput( 0 ).GetPointer() ); + this->SetNthOutput(0, output.GetPointer()); + + output->Expand(m_Timestep+1); + + mitk::Image::ConstPointer input = dynamic_cast(this->GetInput()); + + typename std::vector< ShortestPathImageFilterType::IndexType>::iterator pathIterator = shortestPath.begin(); + + while(pathIterator != shortestPath.end()) + { + mitk::Point3D currentPoint; + currentPoint[0] = (*pathIterator)[0]; + currentPoint[1] = (*pathIterator)[1]; + currentPoint[2] = 0; + + + input->GetGeometry()->IndexToWorld(currentPoint, currentPoint); + output->AddVertex(currentPoint, false, m_Timestep); + + pathIterator++; + } + /*---------------------------------------------*/ +} + +bool mitk::ImageLiveWireContourModelFilter::CreateDynamicCostMap(mitk::ContourModel* path) +{ + mitk::Image::ConstPointer input = dynamic_cast(this->GetInput()); + if(input) + { + AccessFixedDimensionByItk_1(input,CreateDynamicCostMapByITK, 2, path); + return true; + } + else + { + return false; + } +} + + + +template +void mitk::ImageLiveWireContourModelFilter::CreateDynamicCostMapByITK( itk::Image* inputImage, mitk::ContourModel* path ) +{ + /*++++++++++ create dynamic cost transfer map ++++++++++*/ + + /* Compute the costs of the gradient magnitude dynamically. + * using a map of the histogram of gradient magnitude image. + * Use the histogram gradient map to interpolate the costs + * with gaussing function including next two bins right and left + * to current position x. With the histogram gradient costs are interpolated + * with a gaussing function summation of next two bins right and left + * to current position x. + */ + std::vector< itk::Index > shortestPath; + + mitk::Image::ConstPointer input = dynamic_cast(this->GetInput()); + if(path == NULL) + { + OutputType::Pointer output = this->GetOutput(); + mitk::ContourModel::VertexIterator it = output->IteratorBegin(); + while( it != output->IteratorEnd() ) + { + itk::Index cur; + mitk::Point3D c = (*it)->Coordinates; + input->GetGeometry()->WorldToIndex(c, c); + cur[0] = c[0]; + cur[1] = c[1]; + + shortestPath.push_back( cur); + it++; + } + } + else + { + + mitk::ContourModel::VertexIterator it = path->IteratorBegin(); + while( it != path->IteratorEnd() ) + { + itk::Index cur; + mitk::Point3D c = (*it)->Coordinates; + input->GetGeometry()->WorldToIndex(c, c); + cur[0] = c[0]; + cur[1] = c[1]; + + shortestPath.push_back( cur); + it++; + } + + } + + + /*+++ filter image gradient magnitude +++*/ + typedef itk::GradientMagnitudeImageFilter< itk::Image, itk::Image > GradientMagnitudeFilterType; + typename GradientMagnitudeFilterType::Pointer gradientFilter = GradientMagnitudeFilterType::New(); + gradientFilter->SetInput(inputImage); + gradientFilter->Update(); + typename itk::Image::Pointer gradientMagnImage = gradientFilter->GetOutput(); + + //get the path + + + //iterator of path + typename std::vector< itk::Index >::iterator pathIterator = shortestPath.begin(); + + std::map< int, int > histogram; + + //create histogram within path + while(pathIterator != shortestPath.end()) + { + //count pixel values + //use scale factor to avoid mapping gradients between 0.0 and 1.0 to same bin + histogram[ static_cast( gradientMagnImage->GetPixel((*pathIterator)) * ImageLiveWireContourModelFilter::CostFunctionType::MAPSCALEFACTOR ) ] += 1; + + pathIterator++; + } + + double max = 1.0; + + if( !histogram.empty() ) + { + + std::map< int, int >::iterator itMAX; + + //get max of histogramm + int currentMaxValue = 0; + std::map< int, int >::iterator it = histogram.begin(); + while( it != histogram.end()) + { + if((*it).second > currentMaxValue) + { + itMAX = it; + currentMaxValue = (*it).second; + } + it++; + } + + + std::map< int, int >::key_type keyOfMax = itMAX->first; + + + /*+++++++++++++++++++++++++ compute the to max of gaussian summation ++++++++++++++++++++++++*/ + std::map< int, int >::iterator end = histogram.end(); + std::map< int, int >::iterator last = --(histogram.end()); + + std::map< int, int >::iterator left2; + std::map< int, int >::iterator left1; + std::map< int, int >::iterator right1; + std::map< int, int >::iterator right2; + + right1 = itMAX; + + + if(right1 == end || right1 == last ) + { + right2 = end; + } + else//( right1 <= last ) + { + std::map< int, int >::iterator temp = right1; + right2 = ++right1;//rght1 + 1 + right1 = temp; + } + + + if( right1 == histogram.begin() ) + { + left1 = end; + left2 = end; + } + else if( right1 == (++(histogram.begin())) ) + { + std::map< int, int >::iterator temp = right1; + left1 = --right1;//rght1 - 1 + right1 = temp; + left2 = end; + } + else + { + std::map< int, int >::iterator temp = right1; + left1 = --right1;//rght1 - 1 + left2 = --right1;//rght1 - 2 + right1 = temp; + } + + double partRight1, partRight2, partLeft1, partLeft2; + partRight1 = partRight2 = partLeft1 = partLeft2 = 0.0; + + + /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + f(x) = v(bin) * e^ ( -1/2 * (|x-k(bin)| / sigma)^2 ) + + gaussian approximation + + where + v(bin) is the value in the map + k(bin) is the key + */ + if( left2 != end ) + { + partLeft2 = ImageLiveWireContourModelFilter::CostFunctionType::Gaussian(keyOfMax, left2->first, left2->second); + } + + if( left1 != end ) + { + partLeft1 = ImageLiveWireContourModelFilter::CostFunctionType::Gaussian(keyOfMax, left1->first, left1->second); + } + + if( right1 != end ) + { + partRight1 = ImageLiveWireContourModelFilter::CostFunctionType::Gaussian(keyOfMax, right1->first, right1->second); + } + + if( right2 != end ) + { + partRight2 = ImageLiveWireContourModelFilter::CostFunctionType::Gaussian(keyOfMax, right2->first, right2->second); + } + /*----------------------------------------------------------------------------*/ + + max = (partRight1 + partRight2 + partLeft1 + partLeft2); + + } + + this->m_CostFunction->SetDynamicCostMap(histogram); + this->m_CostFunction->SetCostMapMaximum(max); + +} diff --git a/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h b/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h new file mode 100644 index 0000000000..74ff4fa4e8 --- /dev/null +++ b/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h @@ -0,0 +1,153 @@ +/*=================================================================== + +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 _mitkImageLiveWireContourModelFilter_h__ +#define _mitkImageLiveWireContourModelFilter_h__ + +#include "mitkCommon.h" +#include "SegmentationExports.h" +#include "mitkContourModel.h" +#include "mitkContourModelSource.h" + +#include +#include +#include + +#include +#include + + +namespace mitk { + + /** + + \brief Calculates a LiveWire contour between two points in an image. + + For defining costs between two pixels specific features are extraced from the image and tranformed into a single cost value. + \sa ShortestPathCostFunctionLiveWire + + The filter is able to create dynamic cost tranfer map and thus use on the fly training. + \Note On the fly training will only be used for next update. + The computation uses the last calculated segment to map cost according to features in the area of the segment. + + For time resolved purposes use ImageLiveWireContourModelFilter::SetTimestep( unsigned int ) to create the LiveWire contour + at a specific timestep. + + \ingroup ContourModelFilters + \ingroup Process + */ + class Segmentation_EXPORT ImageLiveWireContourModelFilter : public ContourModelSource + { + + public: + + mitkClassMacro(ImageLiveWireContourModelFilter, ContourModelSource); + itkNewMacro(Self); + + + typedef ContourModel OutputType; + typedef OutputType::Pointer OutputTypePointer; + typedef mitk::Image InputType; + + typedef itk::Image< float, 2 > FloatImageType; + typedef itk::ShortestPathImageFilter< FloatImageType, FloatImageType > ShortestPathImageFilterType; + typedef itk::ShortestPathCostFunctionLiveWire< FloatImageType > CostFunctionType; + + + /** \brief start point in worldcoordinates*/ + itkSetMacro(StartPoint, mitk::Point3D); + itkGetMacro(StartPoint, mitk::Point3D); + + /** \brief end point in woorldcoordinates*/ + itkSetMacro(EndPoint, mitk::Point3D); + itkGetMacro(EndPoint, mitk::Point3D); + + /** \brief Create dynamic cost tranfer map - use on the fly training. + \Note On the fly training will be used for next update only. + The computation uses the last calculated segment to map cost according to features in the area of the segment. + */ + itkSetMacro(UseDynamicCostMap, bool); + itkGetMacro(UseDynamicCostMap, bool); + + + virtual void SetInput( const InputType *input); + + virtual void SetInput( unsigned int idx, const InputType * input); + + const InputType* GetInput(void); + + const InputType* GetInput(unsigned int idx); + + virtual OutputType* GetOutput(); + + + /** \brief Create dynamic cost tranfer map - on the fly training*/ + bool CreateDynamicCostMap(mitk::ContourModel* path=NULL); + + void SetTimestep( unsigned int timestep ) + { + m_Timestep = timestep; + } + + unsigned int GetTimestep() + { + return m_Timestep; + } + + protected: + ImageLiveWireContourModelFilter(); + + virtual ~ImageLiveWireContourModelFilter(); + + void GenerateOutputInformation() {}; + + void GenerateData(); + + /** \brief start point in worldcoordinates*/ + mitk::Point3D m_StartPoint; + + /** \brief end point in woorldcoordinates*/ + mitk::Point3D m_EndPoint; + + /** \brief Start point in index*/ + mitk::Point3D m_StartPointInIndex; + + /** \brief End point in index*/ + mitk::Point3D m_EndPointInIndex; + + /** \brief The cost function to compute costs between two pixels*/ + CostFunctionType::Pointer m_CostFunction; + + /** \brief Shortest path filter according to cost function m_CostFunction*/ + ShortestPathImageFilterType::Pointer m_ShortestPathFilter; + + /** \brief Flag to use a dynmic cost map or not*/ + bool m_UseDynamicCostMap; + + bool m_ImageModified; + + unsigned int m_Timestep; + + template + void ItkProcessImage (itk::Image* inputImage); + + template + void CreateDynamicCostMapByITK(itk::Image* inputImage, mitk::ContourModel* path=NULL); + }; + +} + +#endif diff --git a/Modules/Segmentation/Algorithms/mitkImageToContourModelFilter.cpp b/Modules/Segmentation/Algorithms/mitkImageToContourModelFilter.cpp new file mode 100644 index 0000000000..5468ca98c7 --- /dev/null +++ b/Modules/Segmentation/Algorithms/mitkImageToContourModelFilter.cpp @@ -0,0 +1,76 @@ +/*=================================================================== + +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 "mitkImageToContourModelFilter.h" + + + +mitk::ImageToContourModelFilter::ImageToContourModelFilter() +{ +} + + + +mitk::ImageToContourModelFilter::~ImageToContourModelFilter() +{ + +} + + + +void mitk::ImageToContourModelFilter::SetInput ( const mitk::ImageToContourModelFilter::InputType* input ) +{ + this->SetInput( 0, input ); +} + + + +void mitk::ImageToContourModelFilter::SetInput ( unsigned int idx, const mitk::ImageToContourModelFilter::InputType* input ) +{ + if ( idx + 1 > this->GetNumberOfInputs() ) + { + this->SetNumberOfRequiredInputs(idx + 1); + } + if ( input != static_cast ( this->ProcessObject::GetInput ( idx ) ) ) + { + this->ProcessObject::SetNthInput ( idx, const_cast ( input ) ); + this->Modified(); + } +} + + + +const mitk::ImageToContourModelFilter::InputType* mitk::ImageToContourModelFilter::GetInput( void ) +{ + if (this->GetNumberOfInputs() < 1) + return NULL; + return static_cast(this->ProcessObject::GetInput(0)); +} + + + +const mitk::ImageToContourModelFilter::InputType* mitk::ImageToContourModelFilter::GetInput( unsigned int idx ) +{ + if (this->GetNumberOfInputs() < 1) + return NULL; + return static_cast(this->ProcessObject::GetInput(idx)); +} + + +void mitk::ImageToContourModelFilter::GenerateData() +{ + +} diff --git a/Modules/Segmentation/Algorithms/mitkImageToContourModelFilter.h b/Modules/Segmentation/Algorithms/mitkImageToContourModelFilter.h new file mode 100644 index 0000000000..40fc5d9a4f --- /dev/null +++ b/Modules/Segmentation/Algorithms/mitkImageToContourModelFilter.h @@ -0,0 +1,66 @@ +/*=================================================================== + +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 _mitkImageToContourModelFilter_h__ +#define _mitkImageToContourModelFilter_h__ + +#include "mitkCommon.h" +#include "SegmentationExports.h" +#include "mitkContourModel.h" +#include "mitkContourModelSource.h" +#include + + +namespace mitk { + + /** + * + * \brief Base class for all filters with mitk::Image as input and mitk::ContourModel + * + * \ingroup ContourModelFilters + * \ingroup Process + */ + class Segmentation_EXPORT ImageToContourModelFilter : public ContourModelSource + { + + public: + + mitkClassMacro(ImageToContourModelFilter, ContourModelSource); + itkNewMacro(Self); + + typedef mitk::Image InputType; + + + virtual void SetInput( const InputType *input); + + virtual void SetInput( unsigned int idx, const InputType * input); + + const InputType* GetInput(void); + + const InputType* GetInput(unsigned int idx); + + protected: + ImageToContourModelFilter(); + + virtual ~ImageToContourModelFilter(); + + void GenerateData(); + + }; + +} + +#endif diff --git a/Modules/Segmentation/Algorithms/mitkImageToLiveWireContourFilter.cpp b/Modules/Segmentation/Algorithms/mitkImageToLiveWireContourFilter.cpp new file mode 100644 index 0000000000..a9b7a18f0d --- /dev/null +++ b/Modules/Segmentation/Algorithms/mitkImageToLiveWireContourFilter.cpp @@ -0,0 +1,194 @@ +/*=================================================================== + +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 "mitkImageToLiveWireContourFilter.h" + + +#include +#include +#include + + + +mitk::ImageToLiveWireContourFilter::ImageToLiveWireContourFilter() +{ + OutputType::Pointer output = dynamic_cast ( this->MakeOutput( 0 ).GetPointer() ); + this->SetNumberOfRequiredInputs(1); + this->SetNumberOfOutputs( 1 ); + this->SetNthOutput(0, output.GetPointer()); +} + + + +mitk::ImageToLiveWireContourFilter::~ImageToLiveWireContourFilter() +{ + +} + + + +void mitk::ImageToLiveWireContourFilter::SetInput ( const mitk::ImageToLiveWireContourFilter::InputType* input ) +{ + this->SetInput( 0, input ); +} + +void mitk::ImageToLiveWireContourFilter::SetInput ( unsigned int idx, const mitk::ImageToLiveWireContourFilter::InputType* input ) +{ + if ( idx + 1 > this->GetNumberOfInputs() ) + { + this->SetNumberOfRequiredInputs(idx + 1); + } + if ( input != static_cast ( this->ProcessObject::GetInput ( idx ) ) ) + { + this->ProcessObject::SetNthInput ( idx, const_cast ( input ) ); + this->Modified(); + } +} + + + +const mitk::ImageToLiveWireContourFilter::InputType* mitk::ImageToLiveWireContourFilter::GetInput( void ) +{ + if (this->GetNumberOfInputs() < 1) + return NULL; + return static_cast(this->ProcessObject::GetInput(0)); +} + + +const mitk::ImageToLiveWireContourFilter::InputType* mitk::ImageToLiveWireContourFilter::GetInput( unsigned int idx ) +{ + if (this->GetNumberOfInputs() < 1) + return NULL; + return static_cast(this->ProcessObject::GetInput(idx)); +} + + + +void mitk::ImageToLiveWireContourFilter::GenerateData() +{ + mitk::Image::ConstPointer input = dynamic_cast(this->GetInput()); + + if(!input) + { + MITK_ERROR << "No input available."; + itkExceptionMacro("mitk::ImageToLiveWireContourFilter: No input available. Please set the input!"); + return; + } + + if( input->GetDimension() != 2 ) + { + MITK_ERROR << "Filter is only working on 2D images."; + itkExceptionMacro("mitk::ImageToLiveWireContourFilter: Filter is only working on 2D images.. Please make sure that the input is 2D!"); + return; + } + + + input->GetGeometry()->WorldToIndex(m_StartPoint, m_StartPointInIndex); + input->GetGeometry()->WorldToIndex(m_EndPoint, m_EndPointInIndex); + + + AccessFixedDimensionByItk(input, ItkProcessImage, 2); +} + + + +template +void mitk::ImageToLiveWireContourFilter::ItkProcessImage (itk::Image* inputImage) +{ + //typedef itk::Image< TPixel, VImageDimension > InputImageType; + //typedef itk::Image< float, 2 > FloatImageType; + + //typedef typename itk::ShortestPathImageFilter< InputImageType, InputImageType > ShortestPathImageFilterType; + //typedef typename itk::ShortestPathCostFunctionLiveWire< InputImageType > CostFunctionType; + + //typedef InputImageType::IndexType IndexType; + + + ///* compute the requested region for itk filters */ + + //typename IndexType startPoint, endPoint; + // + //startPoint[0] = m_StartPointInIndex[0]; + //startPoint[1] = m_StartPointInIndex[1]; + + //endPoint[0] = m_EndPointInIndex[0]; + //endPoint[1] = m_EndPointInIndex[1]; + + ////minimum value in each direction for startRegion + //typename IndexType startRegion; + //startRegion[0] = startPoint[0] < endPoint[0] ? startPoint[0] : endPoint[0]; + //startRegion[1] = startPoint[1] < endPoint[1] ? startPoint[1] : endPoint[1]; + + ////maximum value in each direction for size + //typename InputImageType::SizeType size; + //size[0] = startPoint[0] > endPoint[0] ? startPoint[0] : endPoint[0]; + //size[1] = startPoint[1] > endPoint[1] ? startPoint[1] : endPoint[1]; + + + //typename InputImageType::RegionType region; + //region.SetSize( size ); + //region.SetIndex( startRegion ); + ///*---------------------------------------------*/ + + + ///* extracts features from image and calculates costs */ + //typename CostFunctionType::Pointer costFunction = CostFunctionType::New(); + //costFunction->SetImage(inputImage); + //costFunction->SetStartIndex(startPoint); + //costFunction->SetEndIndex(endPoint); + //costFunction->SetRequestedRegion(region); + ///*---------------------------------------------*/ + + + ///* calculate shortest path between start and end point */ + //ShortestPathImageFilterType::Pointer shortestPathFilter = ShortestPathImageFilterType::New(); + //shortestPathFilter->SetFullNeighborsMode(true); + //shortestPathFilter->SetInput(inputImage); + //shortestPathFilter->SetMakeOutputImage(true); + //shortestPathFilter->SetStoreVectorOrder(false); + ////shortestPathFilter->SetActivateTimeOut(true); + //shortestPathFilter->SetStartIndex(startPoint); + //shortestPathFilter->SetEndIndex(endPoint); + + //shortestPathFilter->Update(); + + ///*---------------------------------------------*/ + + + ///* construct contour from path image */ + ////get the shortest path as vector + //std::vector< itk::Index<3> > shortestPath = shortestPathFilter->GetVectorPath(); + + ////fill the output contour with controll points from the path + //OutputType::Pointer outputContour = this->GetOutput(); + //mitk::Image::ConstPointer input = dynamic_cast(this->GetInput()); + + //std::vector< itk::Index<3> >::iterator pathIterator = shortestPath.begin(); + + //while(pathIterator != shortestPath.end()) + //{ + // mitk::Point3D currentPoint; + // currentPoint[0] = (*pathIterator)[0]; + // currentPoint[1] = (*pathIterator)[1]; + + // input->GetGeometry(0)->IndexToWorld(currentPoint, currentPoint); + // outputContour->AddVertex(currentPoint); + // + // pathIterator++; + //} + /*---------------------------------------------*/ + +} diff --git a/Modules/Segmentation/Algorithms/mitkImageToLiveWireContourFilter.h b/Modules/Segmentation/Algorithms/mitkImageToLiveWireContourFilter.h new file mode 100644 index 0000000000..eebf622d6b --- /dev/null +++ b/Modules/Segmentation/Algorithms/mitkImageToLiveWireContourFilter.h @@ -0,0 +1,92 @@ +/*=================================================================== + +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 _mitkImageToLiveWireContourFilter_h__ +#define _mitkImageToLiveWireContourFilter_h__ + +#include "mitkCommon.h" +#include "SegmentationExports.h" +#include "mitkContourModel.h" +#include "mitkContourModelSource.h" + +#include +#include +#include + + + + +namespace mitk { + + /** + * + * \brief + * + * \ingroup ContourModelFilters + * \ingroup Process + */ + class Segmentation_EXPORT ImageToLiveWireContourFilter : public ContourModelSource + { + + public: + + mitkClassMacro(ImageToLiveWireContourFilter, ContourModelSource); + itkNewMacro(Self); + + typedef ContourModel OutputType; + typedef OutputType::Pointer OutputTypePointer; + typedef mitk::Image InputType; + + itkSetMacro(StartPoint, mitk::Point3D); + itkGetMacro(StartPoint, mitk::Point3D); + + itkSetMacro(EndPoint, mitk::Point3D); + itkGetMacro(EndPoint, mitk::Point3D); + + + virtual void SetInput( const InputType *input); + + virtual void SetInput( unsigned int idx, const InputType * input); + + const InputType* GetInput(void); + + const InputType* GetInput(unsigned int idx); + + protected: + ImageToLiveWireContourFilter(); + + virtual ~ImageToLiveWireContourFilter(); + + void GenerateData(); + + void GenerateOutputInformation() {}; + + mitk::Point3D m_StartPoint; + mitk::Point3D m_EndPoint; + + mitk::Point3D m_StartPointInIndex; + mitk::Point3D m_EndPointInIndex; + + private: + + template + void ItkProcessImage (itk::Image* inputImage); + + + }; + +} +#endif diff --git a/Modules/Segmentation/CMakeLists.txt b/Modules/Segmentation/CMakeLists.txt index 133b39a60c..f156909325 100644 --- a/Modules/Segmentation/CMakeLists.txt +++ b/Modules/Segmentation/CMakeLists.txt @@ -1,10 +1,10 @@ #configure_file(${PROJECT_SOURCE_DIR}/CMake/ToolExtensionITKFactory.cpp.in $#{PROJECT_BINARY_DIR}/ToolExtensionITKFactory.cpp.in COPYONLY) #configure_file(${PROJECT_SOURCE_DIR}/CMake/ToolExtensionITKFactoryLoader.cpp.in $#{PROJECT_BINARY_DIR}/ToolExtensionITKFactoryLoader.cpp.in COPYONLY) #configure_file(${PROJECT_SOURCE_DIR}/CMake/ToolGUIExtensionITKFactory.cpp.in $#{PROJECT_BINARY_DIR}/ToolGUIExtensionITKFactory.cpp.in COPYONLY) MITK_CREATE_MODULE( Segmentation INCLUDE_DIRS Algorithms Controllers DataManagement Interactions IO Rendering - DEPENDS Mitk ipSegmentation mitkIpFunc MitkExt + DEPENDS Mitk ipSegmentation mitkIpFunc MitkExt MitkGraphAlgorithms ) add_subdirectory(Testing) \ No newline at end of file diff --git a/Modules/Segmentation/DataManagement/mitkContourElement.cpp b/Modules/Segmentation/DataManagement/mitkContourElement.cpp index 02357705ba..f617e988ba 100644 --- a/Modules/Segmentation/DataManagement/mitkContourElement.cpp +++ b/Modules/Segmentation/DataManagement/mitkContourElement.cpp @@ -1,351 +1,356 @@ /*=================================================================== 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 mitk::ContourElement::ContourElement() { this->m_Vertices = new VertexListType(); this->m_IsClosed = false; } mitk::ContourElement::ContourElement(const mitk::ContourElement &other) : m_Vertices(other.m_Vertices), m_IsClosed(other.m_IsClosed) { } mitk::ContourElement::~ContourElement() { delete this->m_Vertices; } void mitk::ContourElement::AddVertex(mitk::Point3D &vertex, bool isControlPoint) { this->m_Vertices->push_back(new VertexType(vertex, isControlPoint)); } void mitk::ContourElement::AddVertex(VertexType &vertex) { this->m_Vertices->push_back(&vertex); } void mitk::ContourElement::AddVertexAtFront(mitk::Point3D &vertex, bool isControlPoint) { this->m_Vertices->push_front(new VertexType(vertex, isControlPoint)); } void mitk::ContourElement::AddVertexAtFront(VertexType &vertex) { this->m_Vertices->push_front(&vertex); } void mitk::ContourElement::InsertVertexAtIndex(mitk::Point3D &vertex, bool isControlPoint, int index) { if(index > 0 && this->GetSize() > index) { VertexIterator _where = this->m_Vertices->begin(); _where += index; this->m_Vertices->insert(_where, new VertexType(vertex, isControlPoint)); } } mitk::ContourElement::VertexType* mitk::ContourElement::GetVertexAt(int index) { return this->m_Vertices->at(index); } mitk::ContourElement::VertexType* mitk::ContourElement::GetVertexAt(const mitk::Point3D &point, float eps) { /* current version iterates over the whole deque - should some kind of an octree with spatial query*/ if(eps > 0) { + if(this->m_Vertices->size() == 1) + { + return this->m_Vertices->at(0); + } + if(true ) //currently no method with better performance is available { return BruteForceGetVertexAt(point, eps); } else { return OptimizedGetVertexAt(point, eps); } }//if eps < 0 return NULL; } mitk::ContourElement::VertexType* mitk::ContourElement::BruteForceGetVertexAt(const mitk::Point3D &point, float eps) { if(eps > 0) { std::deque< std::pair > nearestlist; ConstVertexIterator it = this->m_Vertices->begin(); ConstVertexIterator end = this->m_Vertices->end(); while(it != end) { mitk::Point3D currentPoint = (*it)->Coordinates; double distance = currentPoint.EuclideanDistanceTo(point); if(distance < eps) { //if list is emtpy, add point to list if(nearestlist.size() < 1) { nearestlist.push_front(std::pair( (*it)->Coordinates.EuclideanDistanceTo(point), (*it) )); } //found an approximate point - check if current is closer then first in nearestlist else if( distance < nearestlist.front().first ) { //found even closer vertex nearestlist.push_front(std::pair( (*it)->Coordinates.EuclideanDistanceTo(point), (*it) )); } }//if distance > eps it++; }//while if(nearestlist.size() > 0) { /*++++++++++++++++++++ return the nearest active point if one was found++++++++++++++++++*/ std::deque< std::pair >::iterator it = nearestlist.begin(); std::deque< std::pair >::iterator end = nearestlist.end(); while(it != end) { if( (*it).second->IsControlPoint ) { return (*it).second; } it++; } /*---------------------------------------------------------------------------------------*/ //return closest point return nearestlist.front().second; } } return NULL; } mitk::ContourElement::VertexType* mitk::ContourElement::OptimizedGetVertexAt(const mitk::Point3D &point, float eps) { if( (eps > 0) && (this->m_Vertices->size()>0) ) { int k = 1; int dim = 3; int nPoints = this->m_Vertices->size(); ANNpointArray pointsArray; ANNpoint queryPoint; ANNidxArray indexArray; ANNdistArray distanceArray; ANNkd_tree* kdTree; queryPoint = annAllocPt(dim); pointsArray = annAllocPts(nPoints, dim); indexArray = new ANNidx[k]; distanceArray = new ANNdist[k]; int i = 0; //fill points array with our control points for(VertexIterator it = this->m_Vertices->begin(); it != this->m_Vertices->end(); it++, i++) { mitk::Point3D cur = (*it)->Coordinates; pointsArray[i][0]= cur[0]; pointsArray[i][1]= cur[1]; pointsArray[i][2]= cur[2]; } //create the kd tree kdTree = new ANNkd_tree(pointsArray,nPoints, dim); //fill mitk::Point3D into ANN query point queryPoint[0] = point[0]; queryPoint[1] = point[1]; queryPoint[2] = point[2]; //k nearest neighbour search kdTree->annkSearch(queryPoint, k, indexArray, distanceArray, eps); VertexType* ret = NULL; try { ret = this->m_Vertices->at(indexArray[0]); } catch(std::out_of_range ex) { //ret stays NULL return ret; } //clean up ANN delete [] indexArray; delete [] distanceArray; delete kdTree; annClose(); return ret; } return NULL; } mitk::ContourElement::VertexListType* mitk::ContourElement::GetVertexList() { return this->m_Vertices; } bool mitk::ContourElement::IsClosed() { return this->m_IsClosed; } void mitk::ContourElement::Close() { this->m_IsClosed = true; } void mitk::ContourElement::Open() { this->m_IsClosed = false; } void mitk::ContourElement::SetIsClosed( bool isClosed) { isClosed ? this->Close() : this->Open(); } void mitk::ContourElement::Concatenate(mitk::ContourElement* other) { if( other->GetSize() > 0) { ConstVertexIterator it = other->m_Vertices->begin(); ConstVertexIterator end = other->m_Vertices->end(); //add all vertices of other after last vertex while(it != end) { this->m_Vertices->push_back(*it); it++; } } } bool mitk::ContourElement::RemoveVertex(mitk::ContourElement::VertexType* vertex) { VertexIterator it = this->m_Vertices->begin(); VertexIterator end = this->m_Vertices->end(); //search for vertex and remove it if exists while(it != end) { if((*it) == vertex) { this->m_Vertices->erase(it); return true; } it++; } return false; } bool mitk::ContourElement::RemoveVertexAt(int index) { if( index >= 0 && index < this->m_Vertices->size() ) { this->m_Vertices->erase(this->m_Vertices->begin()+index); return true; } else { return false; } } bool mitk::ContourElement::RemoveVertexAt(mitk::Point3D &point, float eps) { /* current version iterates over the whole deque - should be some kind of an octree with spatial query*/ if(eps > 0){ VertexIterator it = this->m_Vertices->begin(); VertexIterator end = this->m_Vertices->end(); while(it != end) { mitk::Point3D currentPoint = (*it)->Coordinates; if(currentPoint.EuclideanDistanceTo(point) < eps) { //approximate point found //now erase it this->m_Vertices->erase(it); return true; } it++; } } return false; } void mitk::ContourElement::Clear() { this->m_Vertices->clear(); } diff --git a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp new file mode 100644 index 0000000000..71fdceeff9 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp @@ -0,0 +1,349 @@ +/*=================================================================== + +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 "mitkContourModelLiveWireInteractor.h" + +#include "mitkToolManager.h" + +#include "mitkBaseRenderer.h" +#include "mitkRenderingManager.h" + +#include +#include + +#include + + +mitk::ContourModelLiveWireInteractor::ContourModelLiveWireInteractor(DataNode* dataNode) +:ContourModelInteractor(dataNode) +{ + m_LiveWireFilter = mitk::ImageLiveWireContourModelFilter::New(); + + mitk::ContourModel::Pointer m_ContourLeft = mitk::ContourModel::New(); + mitk::ContourModel::Pointer m_ContourRight = mitk::ContourModel::New(); + m_NextActiveVertexDown.Fill(0); + m_NextActiveVertexUp.Fill(0); +} + + +mitk::ContourModelLiveWireInteractor::~ContourModelLiveWireInteractor() +{ +} + + + +bool mitk::ContourModelLiveWireInteractor::OnCheckPointClick( Action* action, const StateEvent* stateEvent) +{ + const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); + if (!positionEvent) { + this->HandleEvent( new mitk::StateEvent(EIDNO, stateEvent->GetEvent()) ); + return false; + } + + + + mitk::StateEvent* newStateEvent = NULL; + + int timestep = stateEvent->GetEvent()->GetSender()->GetTimeStep(); + + mitk::ContourModel *contour = dynamic_cast( + m_DataNode->GetData() ); + + + contour->Deselect(); + + /* + * Check distance to any vertex. + * Transition YES if click close to a vertex + */ + mitk::Point3D click = positionEvent->GetWorldPosition(); + + + if (contour->SelectVertexAt(click, 1.5, timestep) ) + { + contour->SetSelectedVertexAsControlPoint(); + + assert( stateEvent->GetEvent()->GetSender()->GetRenderWindow() ); + mitk::RenderingManager::GetInstance()->RequestUpdate( stateEvent->GetEvent()->GetSender()->GetRenderWindow() ); + newStateEvent = new mitk::StateEvent(EIDYES, stateEvent->GetEvent()); + m_lastMousePosition = click; + + + mitk::ContourModel *contour = dynamic_cast( m_DataNode->GetData() ); + + // + m_ContourLeft = mitk::ContourModel::New(); + + //get coordinates of next active vertex downwards from selected vertex + int downIndex = SplitContourFromSelectedVertex( contour, m_ContourLeft, false, timestep); + + mitk::ContourModel::VertexIterator itDown = contour->IteratorBegin() + downIndex; + m_NextActiveVertexDown = (*itDown)->Coordinates; + + + // + m_ContourRight = mitk::ContourModel::New(); + + //get coordinates of next active vertex upwards from selected vertex + int upIndex = SplitContourFromSelectedVertex( contour, m_ContourRight, true, timestep); + + mitk::ContourModel::VertexIterator itUp = contour->IteratorBegin() + upIndex; + m_NextActiveVertexUp = (*itUp)->Coordinates; + + } + else + { + newStateEvent = new mitk::StateEvent(EIDNO, stateEvent->GetEvent()); + } + + this->HandleEvent( newStateEvent ); + + return true; +} + + + +bool mitk::ContourModelLiveWireInteractor::OnDeletePoint( Action* action, const StateEvent* stateEvent) +{ + + int timestep = stateEvent->GetEvent()->GetSender()->GetTimeStep(); + + mitk::ContourModel *contour = dynamic_cast( m_DataNode->GetData() ); + + if(contour->GetSelectedVertex()) + { + + mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); + newContour->Expand(contour->GetTimeSteps()); + newContour->Concatenate( m_ContourLeft, timestep ); + + + //recompute contour between neighbored two active control points + this->m_LiveWireFilter->SetStartPoint( m_NextActiveVertexDown ); + this->m_LiveWireFilter->SetEndPoint( m_NextActiveVertexUp ); + this->m_LiveWireFilter->Update(); + + mitk::ContourModel::Pointer liveWireContour = mitk::ContourModel::New(); + liveWireContour = this->m_LiveWireFilter->GetOutput(); + + + //insert new liveWire computed points + newContour->Concatenate( liveWireContour, timestep ); + + newContour->Concatenate( m_ContourRight, timestep ); + + newContour->SetIsClosed(contour->IsClosed(timestep), timestep); + + m_DataNode->SetData(newContour); + + + assert( stateEvent->GetEvent()->GetSender()->GetRenderWindow() ); + mitk::RenderingManager::GetInstance()->RequestUpdate( stateEvent->GetEvent()->GetSender()->GetRenderWindow() ); + + } + return true; +} + + + + +bool mitk::ContourModelLiveWireInteractor::OnMovePoint( Action* action, const StateEvent* stateEvent) +{ + const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); + if (!positionEvent) return false; + + int timestep = stateEvent->GetEvent()->GetSender()->GetTimeStep(); + + mitk::ContourModel *contour = dynamic_cast( m_DataNode->GetData() ); + + mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); + newContour->Expand(contour->GetTimeSteps()); + + /*++++++++++++++ concatenate LEFT ++++++++++++++++++++++++++++++*/ + newContour->Concatenate( m_ContourLeft, timestep ); + + + mitk::Point3D currentPosition = positionEvent->GetWorldPosition(); + + + /*+++++++++++++++ start computation ++++++++++++++++++++*/ + //recompute contour between previous active vertex and selected vertex + this->m_LiveWireFilter->SetStartPoint( m_NextActiveVertexDown ); + this->m_LiveWireFilter->SetEndPoint( currentPosition ); + this->m_LiveWireFilter->Update(); + + mitk::ContourModel::Pointer liveWireContour = mitk::ContourModel::New(); + liveWireContour = this->m_LiveWireFilter->GetOutput(); + + //remove point at current position because it is included in next livewire segment too. + liveWireContour->RemoveVertexAt( liveWireContour->GetNumberOfVertices(timestep) - 1, timestep); + + /*++++++++++++++ concatenate LIVEWIRE ++++++++++++++++++++++++++++++*/ + //insert new liveWire computed points + newContour->Concatenate( liveWireContour, timestep ); + + + //recompute contour between selected vertex and next active vertex + this->m_LiveWireFilter->SetStartPoint( currentPosition ); + this->m_LiveWireFilter->SetEndPoint( m_NextActiveVertexUp ); + this->m_LiveWireFilter->Update(); + + liveWireContour = this->m_LiveWireFilter->GetOutput(); + + //make point at mouse position active again, so it is drawn + const_cast( liveWireContour->GetVertexAt(0, timestep) )->IsControlPoint = true; + + /*++++++++++++++ concatenate RIGHT ++++++++++++++++++++++++++++++*/ + //insert new liveWire computed points + newContour->Concatenate( liveWireContour, timestep ); + /*------------------------------------------------*/ + + + newContour->Concatenate( m_ContourRight, timestep ); + + newContour->SetIsClosed(contour->IsClosed(timestep), timestep); + newContour->Deselect();//just to make sure + m_DataNode->SetData(newContour); + + this->m_lastMousePosition = positionEvent->GetWorldPosition(); + + assert( positionEvent->GetSender()->GetRenderWindow() ); + mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); + + return true; +} + + + +int mitk::ContourModelLiveWireInteractor::SplitContourFromSelectedVertex(mitk::ContourModel* sourceContour, mitk::ContourModel* destinationContour, bool fromSelectedUpwards, int timestep) +{ + + mitk::ContourModel::VertexIterator end =sourceContour->IteratorEnd(); + mitk::ContourModel::VertexIterator begin =sourceContour->IteratorBegin(); + + //search next active control point to left and rigth and set as start and end point for filter + mitk::ContourModel::VertexIterator itSelected = sourceContour->IteratorBegin(); + + + //move iterator to position + while((*itSelected) != sourceContour->GetSelectedVertex()) + { + itSelected++; + } + + //CASE search upwards for next control point + if(fromSelectedUpwards) + { + mitk::ContourModel::VertexIterator itUp = itSelected; + + if(itUp != end) + { + itUp++;//step once up otherwise the the loop breaks immediately + } + + while( itUp != end && !((*itUp)->IsControlPoint)){ itUp++; } + + mitk::ContourModel::VertexIterator it = itUp; + + + if(itSelected != begin) + { + //copy the rest of the original contour + while(it != end) + { + destinationContour->AddVertex( (*it)->Coordinates, (*it)->IsControlPoint, timestep); + it++; + } + } + //else do not copy the contour + + + //return the offset of iterator at one before next-vertex-upwards + if(itUp != begin) + { + return std::distance( begin, itUp) - 1; + } + else + { + return std::distance( begin, itUp); + } + + } + else //CASE search downwards for next control point + { + mitk::ContourModel::VertexIterator itDown = itSelected; + mitk::ContourModel::VertexIterator it = sourceContour->IteratorBegin(); + + if( itSelected != begin ) + { + if(itDown != begin) + { + itDown--;//step once down otherwise the the loop breaks immediately + } + + while( itDown != begin && !((*itDown)->IsControlPoint)){ itDown--; } + + if(it != end)//if not empty + { + //always add the first vertex + destinationContour->AddVertex( (*it)->Coordinates, (*it)->IsControlPoint, timestep); + it++; + } + //copy from begin to itDown + while(it <= itDown) + { + destinationContour->AddVertex( (*it)->Coordinates, (*it)->IsControlPoint, timestep); + it++; + } + } + else + { + //if selected vertex is the first element search from end of contour downwards + itDown = end; + itDown--; + while(!((*itDown)->IsControlPoint)){itDown--;} + + //move one forward as we don't want the first control point + it++; + //move iterator to second control point + while( (it!=end) && !((*it)->IsControlPoint) ){it++;} + //copy from begin to itDown + while(it <= itDown) + { + //copy the contour from second control point to itDown + destinationContour->AddVertex( (*it)->Coordinates, (*it)->IsControlPoint, timestep); + it++; + } + } + + + //add vertex at itDown - it's not considered during while loop + if( it != begin && it != end) + { + //destinationContour->AddVertex( (*it)->Coordinates, (*it)->IsControlPoint, timestep); + } + + //return the offset of iterator at one after next-vertex-downwards + if( itDown != end) + { + return std::distance( begin, itDown);// + 1;//index of next vertex + } + else + { + return std::distance( begin, itDown) - 1; + } + } +} diff --git a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h new file mode 100644 index 0000000000..ae44899f95 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.h @@ -0,0 +1,89 @@ +/*=================================================================== + +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 mitkContourModelLiveWireInteractor_h_Included +#define mitkContourModelLiveWireInteractor_h_Included + +#include "mitkCommon.h" +#include "SegmentationExports.h" +#include "mitkContourModelInteractor.h" +#include +#include + +#include + +namespace mitk +{ + + + /** + \brief + + \sa Interactor + \sa ContourModelInteractor + + \ingroup Interaction + + + \warning Make sure the working image is properly set, otherwise the algorithm for computing livewire contour segments will not work! + + */ + class Segmentation_EXPORT ContourModelLiveWireInteractor : public ContourModelInteractor + { + public: + + mitkClassMacro(ContourModelLiveWireInteractor, ContourModelInteractor); + mitkNewMacro1Param(Self, DataNode*); + + + virtual void SetWorkingImage (mitk::Image* _arg) + { + if (this->m_WorkingImage != _arg) + { + this->m_WorkingImage = _arg; + this->m_LiveWireFilter->SetInput(this->m_WorkingImage); + this->Modified(); + } + } + + protected: + + ContourModelLiveWireInteractor(DataNode* dataNode); + virtual ~ContourModelLiveWireInteractor(); + + + virtual bool OnDeletePoint(Action*, const StateEvent*); + virtual bool OnMovePoint(Action*, const StateEvent*); + virtual bool OnCheckPointClick( Action* action, const StateEvent* stateEvent); + + int SplitContourFromSelectedVertex(mitk::ContourModel* sourceContour, + mitk::ContourModel* destinationContour, + bool fromSelectedUpwards, + int timestep); + + mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilter; + mitk::Image::Pointer m_WorkingImage; + + mitk::Point3D m_NextActiveVertexDown; + mitk::Point3D m_NextActiveVertexUp; + mitk::ContourModel::Pointer m_ContourLeft; + mitk::ContourModel::Pointer m_ContourRight; + + }; + +} // namespace + +#endif diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp new file mode 100644 index 0000000000..20082f9f8e --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp @@ -0,0 +1,653 @@ +/*=================================================================== + +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 "mitkLiveWireTool2D.h" + +#include "mitkToolManager.h" +#include "mitkBaseRenderer.h" +#include "mitkRenderingManager.h" + +#include "mitkLiveWireTool2D.xpm" + +#include +#include +#include + +#include "mitkContourUtils.h" +#include "mitkContour.h" + +#include + + +namespace mitk { + MITK_TOOL_MACRO(Segmentation_EXPORT, LiveWireTool2D, "LiveWire tool"); +} + + + +mitk::LiveWireTool2D::LiveWireTool2D() +:SegTool2D("LiveWireTool") +{ + m_Contour = mitk::ContourModel::New(); + m_ContourModelNode = mitk::DataNode::New(); + m_ContourModelNode->SetData( m_Contour ); + m_ContourModelNode->SetProperty("name", StringProperty::New("working contour node")); + m_ContourModelNode->SetProperty("visible", BoolProperty::New(true)); + m_ContourModelNode->AddProperty( "contour.color", ColorProperty::New(0.9, 1.0, 0.1), NULL, true ); + m_ContourModelNode->AddProperty( "selectedcolor", ColorProperty::New(1.0, 0.0, 0.1), NULL, true ); + + + m_LiveWireContour = mitk::ContourModel::New(); + m_LiveWireContourNode = mitk::DataNode::New(); + //m_LiveWireContourNode->SetData( m_LiveWireContour ); + m_LiveWireContourNode->SetProperty("name", StringProperty::New("active livewire node")); + m_LiveWireContourNode->SetProperty("visible", BoolProperty::New(true)); + m_LiveWireContourNode->AddProperty( "contour.color", ColorProperty::New(0.1, 1.0, 0.1), NULL, true ); + m_LiveWireContourNode->AddProperty( "selectedcolor", ColorProperty::New(0.5, 0.5, 0.1), NULL, true ); + + + m_LiveWireFilter = mitk::ImageLiveWireContourModelFilter::New(); + + + // great magic numbers + CONNECT_ACTION( AcINITNEWOBJECT, OnInitLiveWire ); + CONNECT_ACTION( AcADDPOINT, OnAddPoint ); + CONNECT_ACTION( AcMOVE, OnMouseMoveNoDynamicCosts ); + CONNECT_ACTION( AcCHECKPOINT, OnCheckPoint ); + CONNECT_ACTION( AcFINISH, OnFinish ); + CONNECT_ACTION( AcDELETEPOINT, OnLastSegmentDelete ); + CONNECT_ACTION( AcADDLINE, OnMouseMoved ); +} + + + + +mitk::LiveWireTool2D::~LiveWireTool2D() +{ + m_Contours.clear(); +} + + + +float mitk::LiveWireTool2D::CanHandleEvent( StateEvent const *stateEvent) const +{ + mitk::PositionEvent const *positionEvent = + dynamic_cast (stateEvent->GetEvent()); + + //Key event handling: + if (positionEvent == NULL) + { + //check for delete and escape event + if(stateEvent->GetId() == 12 || stateEvent->GetId() == 14) + { + return 1.0; + } + //check, if the current state has a transition waiting for that key event. + else if (this->GetCurrentState()->GetTransition(stateEvent->GetId())!=NULL) + { + return 0.5; + } + else + { + return 0.0; + } + } + else + { + if ( positionEvent->GetSender()->GetMapperID() != BaseRenderer::Standard2D ) + return 0.0; // we don't want anything but 2D + + return 1.0; + } + +} + + + + +const char** mitk::LiveWireTool2D::GetXPM() const +{ + return mitkLiveWireTool2D_xpm; +} + + + + +const char* mitk::LiveWireTool2D::GetName() const +{ + return "LiveWire"; +} + + + + +void mitk::LiveWireTool2D::Activated() +{ + Superclass::Activated(); +} + + + + +void mitk::LiveWireTool2D::Deactivated() +{ + this->FinishTool(); + + DataNode* workingNode( m_ToolManager->GetWorkingData(0) ); + if ( !workingNode ) return; + + Image* workingImage = dynamic_cast(workingNode->GetData()); + if ( !workingImage ) return; + + ContourUtils::Pointer contourUtils = mitk::ContourUtils::New(); + + /*+++++++++++++++++++++++ for all contours in list (currently created by tool) ++++++++++++++++++++++++++++++++++++*/ + std::vector< std::pair >::iterator it = m_Contours.begin(); + while(it != m_Contours.end() ) + { + + //++++++++++if node contains data + if( it->first->GetData() ) + { + + //+++++++++++++++if this is a contourModel + mitk::ContourModel* contourModel = dynamic_cast(it->first->GetData()); + if( contourModel ) + { + + //++++++++++++++++++++++ for each timestep of this contourModel + for( int currentTimestep = 0; currentTimestep < contourModel->GetTimeSlicedGeometry()->GetTimeSteps(); currentTimestep++) + { + + //get the segmentation image slice at current timestep + mitk::Image::Pointer workingSlice = this->GetAffectedImageSliceAs2DImage(it->second, workingImage, currentTimestep); + + /*++++++++++++++++++++++ transfer to plain old contour to use contour util functionality +++++++++++++++++++++++*/ + mitk::Contour::Pointer plainOldContour = mitk::Contour::New(); + mitk::ContourModel::VertexIterator iter = contourModel->IteratorBegin(currentTimestep); + while(iter != contourModel->IteratorEnd(currentTimestep) ) + { + plainOldContour->AddVertex( (*iter)->Coordinates ); + iter++; + } + /*-------------------------------------------------------------------------------*/ + + + mitk::Contour::Pointer projectedContour = contourUtils->ProjectContourTo2DSlice(workingSlice, plainOldContour, true, false); + contourUtils->FillContourInSlice(projectedContour, workingSlice, 1.0); + + //write back to image volume + this->WriteBackSegmentationResult(it->second, workingSlice, currentTimestep); + } + + //remove contour node from datastorage + m_ToolManager->GetDataStorage()->Remove( it->first ); + } + } + + ++it; + } + + m_Contours.clear(); + + Superclass::Deactivated(); +} + + + + +bool mitk::LiveWireTool2D::OnInitLiveWire (Action* action, const StateEvent* stateEvent) +{ + const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); + if (!positionEvent) return false; + + m_LastEventSender = positionEvent->GetSender(); + m_LastEventSlice = m_LastEventSender->GetSlice(); + + if ( Superclass::CanHandleEvent(stateEvent) < 1.0 ) return false; + + + int timestep = positionEvent->GetSender()->GetTimeStep(); + + m_Contour->Expand(timestep+1); + + m_ToolManager->GetDataStorage()->Add( m_ContourModelNode ); + + m_ToolManager->GetDataStorage()->Add( m_LiveWireContourNode ); + + //set current slice as input for ImageToLiveWireContourFilter + m_WorkingSlice = this->GetAffectedReferenceSlice(positionEvent); + m_LiveWireFilter->SetInput(m_WorkingSlice); + + //map click to pixel coordinates + mitk::Point3D click = const_cast(positionEvent->GetWorldPosition()); + itk::Index<3> idx; + m_WorkingSlice->GetGeometry()->WorldToIndex(click, idx); + + /*+++++++++++++++++++++++ get the pixel the gradient in region of 5x5 ++++++++++++++++++++++++++*/ + itk::Index<3> indexWithHighestGradient; + AccessFixedDimensionByItk_2(m_WorkingSlice, FindHighestGradientMagnitudeByITK, 2, idx, indexWithHighestGradient); + /*----------------------------------------------------------------------------------------------------------------*/ + + //itk::Index to mitk::Point3D + click[0] = indexWithHighestGradient[0]; + click[1] = indexWithHighestGradient[1]; + click[2] = indexWithHighestGradient[2]; + m_WorkingSlice->GetGeometry()->IndexToWorld(click, click); + + //set initial start point + m_Contour->AddVertex( click, true, timestep ); + m_LiveWireFilter->SetStartPoint(click); + + m_CreateAndUseDynamicCosts = true; + + //render + assert( positionEvent->GetSender()->GetRenderWindow() ); + mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); + + return true; +} + + + + +bool mitk::LiveWireTool2D::OnAddPoint (Action* action, const StateEvent* stateEvent) +{ + //complete LiveWire interaction for last segment + //add current LiveWire contour to the finished contour and reset + //to start new segment and computation + + /* check if event can be handled */ + const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); + if (!positionEvent) return false; + + if ( Superclass::CanHandleEvent(stateEvent) < 1.0 ) return false; + /* END check if event can be handled */ + + + int timestep = positionEvent->GetSender()->GetTimeStep(); + + //remove duplicate first vertex, it's already contained in m_Contour + m_LiveWireContour->RemoveVertexAt(0, timestep); + + + /* TODO fix this hack*/ + //set last to active added point + if( m_LiveWireContour->GetNumberOfVertices(timestep) > 0) + { + const_cast( m_LiveWireContour->GetVertexAt(m_LiveWireContour->GetNumberOfVertices(timestep)-1, timestep) )->IsControlPoint = true; + } + + //merge contours + m_Contour->Concatenate(m_LiveWireContour, timestep); + + + //clear the livewire contour and reset the corresponding datanode + m_LiveWireContour->Clear(timestep); + + //set new start point + m_LiveWireFilter->SetStartPoint(const_cast(positionEvent->GetWorldPosition())); + + if( m_CreateAndUseDynamicCosts ) + { + //use dynamic cost map for next update + m_LiveWireFilter->CreateDynamicCostMap(m_Contour); + m_LiveWireFilter->SetUseDynamicCostMap(true); + //m_CreateAndUseDynamicCosts = false; + } + + //render + assert( positionEvent->GetSender()->GetRenderWindow() ); + mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); + + return true; +} + + + + +bool mitk::LiveWireTool2D::OnMouseMoved( Action* action, const StateEvent* stateEvent) +{ + //compute LiveWire segment from last control point to current mouse position + + /* check if event can be handled */ + if ( Superclass::CanHandleEvent(stateEvent) < 1.0 ) return false; + + const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); + if (!positionEvent) return false; + /* END check if event can be handled */ + + + /* actual LiveWire computation */ + int timestep = positionEvent->GetSender()->GetTimeStep(); + + m_LiveWireFilter->SetEndPoint(const_cast(positionEvent->GetWorldPosition())); + + m_LiveWireFilter->SetTimestep(timestep); + m_LiveWireFilter->Update(); + + + //ContourModel::VertexType* currentVertex = const_cast(m_LiveWireContour->GetVertexAt(0)); + + this->m_LiveWireContour = this->m_LiveWireFilter->GetOutput(); + this->m_LiveWireContourNode->SetData(this->m_LiveWireFilter->GetOutput()); + + /* END actual LiveWire computation */ + + //render + assert( positionEvent->GetSender()->GetRenderWindow() ); + mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); + + return true; +} + + + +bool mitk::LiveWireTool2D::OnMouseMoveNoDynamicCosts(Action* action, const StateEvent* stateEvent) +{ + //do not use dynamic cost map + m_LiveWireFilter->SetUseDynamicCostMap(false); + OnMouseMoved(action, stateEvent); + m_LiveWireFilter->SetUseDynamicCostMap(true); + return true; +} + + + + + +bool mitk::LiveWireTool2D::OnCheckPoint( Action* action, const StateEvent* stateEvent) +{ + //check double click on first control point to finish the LiveWire tool + // + //Check distance to first point. + //Transition YES if click close to first control point + // + + mitk::StateEvent* newStateEvent = NULL; + + const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); + if (!positionEvent) + { + //stay in current state + newStateEvent = new mitk::StateEvent(EIDNO, stateEvent->GetEvent()); + } + else + { + int timestep = positionEvent->GetSender()->GetTimeStep(); + + mitk::Point3D click = positionEvent->GetWorldPosition(); + + mitk::Point3D first = this->m_Contour->GetVertexAt(0, timestep)->Coordinates; + + + if (first.EuclideanDistanceTo(click) < 1.5) + { + //finish + newStateEvent = new mitk::StateEvent(EIDYES, stateEvent->GetEvent()); + }else + { + //stay active + newStateEvent = new mitk::StateEvent(EIDNO, stateEvent->GetEvent()); + } + } + + this->HandleEvent( newStateEvent ); + + + return true; +} + + + + +bool mitk::LiveWireTool2D::OnFinish( Action* action, const StateEvent* stateEvent) +{ + // finish livewire tool interaction + + /* check if event can be handled */ + if ( Superclass::CanHandleEvent(stateEvent) < 1.0 ) return false; + + const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); + if (!positionEvent) return false; + /* END check if event can be handled */ + + + //actual timestep + int timestep = positionEvent->GetSender()->GetTimeStep(); + + //remove last control point being added by double click + m_Contour->RemoveVertexAt(m_Contour->GetNumberOfVertices(timestep) - 1, timestep); + + //save contour and corresponding plane geometry to list + std::pair contourPair(m_ContourModelNode.GetPointer(), dynamic_cast(positionEvent->GetSender()->GetCurrentWorldGeometry2D()->Clone().GetPointer()) ); + m_Contours.push_back(contourPair); + + m_LiveWireFilter->SetUseDynamicCostMap(false); + this->FinishTool(); + + return true; +} + + + +void mitk::LiveWireTool2D::FinishTool() +{ + + unsigned int numberOfTimesteps = m_Contour->GetTimeSlicedGeometry()->GetTimeSteps(); + + //close contour in each timestep + for( int i = 0; i <= numberOfTimesteps; i++) + { + m_Contour->Close(i); + } + + //clear LiveWire node + m_ToolManager->GetDataStorage()->Remove( m_LiveWireContourNode ); + m_LiveWireContourNode = NULL; + m_LiveWireContour = NULL; + + + //TODO visual feedback for completing livewire tool + m_ContourModelNode->AddProperty( "contour.color", ColorProperty::New(1.0, 1.0, 0.1), NULL, true ); + m_ContourModelNode->SetProperty("name", StringProperty::New("contour node")); + + //set the livewire interactor to edit control points + mitk::ContourModelLiveWireInteractor::Pointer interactor = mitk::ContourModelLiveWireInteractor::New(m_ContourModelNode); + interactor->SetWorkingImage(this->m_WorkingSlice); + m_ContourModelNode->SetInteractor(interactor); + + //add interactor to globalInteraction instance + mitk::GlobalInteraction::GetInstance()->AddInteractor(interactor); + /* END complete livewire tool interaction */ + + + /* reset contours and datanodes */ + m_Contour = mitk::ContourModel::New(); + m_ContourModelNode = mitk::DataNode::New(); + m_ContourModelNode->SetData( m_Contour ); + m_ContourModelNode->SetProperty("name", StringProperty::New("working contour node")); + m_ContourModelNode->SetProperty("visible", BoolProperty::New(true)); + m_ContourModelNode->AddProperty( "contour.color", ColorProperty::New(0.9, 1.0, 0.1), NULL, true ); + m_ContourModelNode->AddProperty( "points.color", ColorProperty::New(1.0, 0.0, 0.1), NULL, true ); + + m_LiveWireContour = mitk::ContourModel::New(); + m_LiveWireContourNode = mitk::DataNode::New(); + //m_LiveWireContourNode->SetData( m_LiveWireContour ); + m_LiveWireContourNode->SetProperty("name", StringProperty::New("active livewire node")); + m_LiveWireContourNode->SetProperty("visible", BoolProperty::New(true)); + m_LiveWireContourNode->AddProperty( "contour.color", ColorProperty::New(0.1, 1.0, 0.1), NULL, true ); + m_LiveWireContourNode->AddProperty( "points.color", ColorProperty::New(0.5, 0.5, 0.1), NULL, true ); + /* END reset contours and datanodes */ +} + + + + +bool mitk::LiveWireTool2D::OnLastSegmentDelete( Action* action, const StateEvent* stateEvent) +{ + int timestep = stateEvent->GetEvent()->GetSender()->GetTimeStep(); + + //if last point of current contour will be removed go to start state and remove nodes + if( m_Contour->GetNumberOfVertices(timestep) <= 1 ) + { + m_ToolManager->GetDataStorage()->Remove( m_LiveWireContourNode ); + m_ToolManager->GetDataStorage()->Remove( m_ContourModelNode ); + m_LiveWireContour = mitk::ContourModel::New(); + m_Contour = mitk::ContourModel::New(); + m_ContourModelNode->SetData( m_Contour ); + m_LiveWireContourNode->SetData( m_LiveWireContour ); + Superclass::Deactivated(); //go to start state + } + else //remove last segment from contour and reset livewire contour + { + + m_LiveWireContour = mitk::ContourModel::New(); + + m_LiveWireContourNode->SetData(m_LiveWireContour); + + + mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); + newContour->Expand(m_Contour->GetTimeSteps()); + + mitk::ContourModel::VertexIterator begin = m_Contour->IteratorBegin(); + + //iterate from last point to next active point + mitk::ContourModel::VertexIterator newLast = m_Contour->IteratorBegin() + (m_Contour->GetNumberOfVertices() - 1); + + //go at least one down + if(newLast != begin) + { + newLast--; + } + + //search next active control point + while(newLast != begin && !((*newLast)->IsControlPoint) ) + { + newLast--; + } + + //set position of start point for livewire filter to coordinates of the new last point + m_LiveWireFilter->SetStartPoint((*newLast)->Coordinates); + + mitk::ContourModel::VertexIterator it = m_Contour->IteratorBegin(); + + //fill new Contour + while(it <= newLast) + { + newContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); + it++; + } + + newContour->SetIsClosed(m_Contour->IsClosed()); + + //set new contour visible + m_ContourModelNode->SetData(newContour); + + m_Contour = newContour; + + assert( stateEvent->GetEvent()->GetSender()->GetRenderWindow() ); + mitk::RenderingManager::GetInstance()->RequestUpdate( stateEvent->GetEvent()->GetSender()->GetRenderWindow() ); + } + + return true; +} + + +template +void mitk::LiveWireTool2D::FindHighestGradientMagnitudeByITK(itk::Image* inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex) +{ + typedef itk::Image InputImageType; + typedef typename InputImageType::IndexType IndexType; + + unsigned long xMAX = inputImage->GetLargestPossibleRegion().GetSize()[0]; + unsigned long yMAX = inputImage->GetLargestPossibleRegion().GetSize()[1]; + + returnIndex[0] = index[0]; + returnIndex[1] = index[1]; + returnIndex[2] = 0.0; + + + double gradientMagnitude = 0.0; + double maxGradientMagnitude = 0.0; + + /* + the size and thus the region of 7x7 is only used to calculate the gradient magnitude in that region + not for searching the maximum value + */ + + //maximum value in each direction for size + typename InputImageType::SizeType size; + size[0] = 7; + size[1] = 7; + + //minimum value in each direction for startRegion + IndexType startRegion; + startRegion[0] = index[0] - 3; + startRegion[1] = index[1] - 3; + if(startRegion[0] < 0) startRegion[0] = 0; + if(startRegion[1] < 0) startRegion[1] = 0; + if(xMAX - index[0] < 7) startRegion[0] = xMAX - 7; + if(yMAX - index[1] < 7) startRegion[1] = yMAX - 7; + + index[0] = startRegion[0] + 3; + index[1] = startRegion[1] + 3; + + + + typename InputImageType::RegionType region; + region.SetSize( size ); + region.SetIndex( startRegion ); + + typedef typename itk::GradientMagnitudeImageFilter< InputImageType, InputImageType> GradientMagnitudeFilterType; + typename GradientMagnitudeFilterType::Pointer gradientFilter = GradientMagnitudeFilterType::New(); + gradientFilter->SetInput(inputImage); + gradientFilter->GetOutput()->SetRequestedRegion(region); + + gradientFilter->Update(); + typename InputImageType::Pointer gradientMagnImage; + gradientMagnImage = gradientFilter->GetOutput(); + + + IndexType currentIndex; + currentIndex[0] = 0; + currentIndex[1] = 0; + + // search max (approximate) gradient magnitude + for( int x = -1; x <= 1; ++x) + { + currentIndex[0] = index[0] + x; + + for( int y = -1; y <= 1; ++y) + { + currentIndex[1] = index[1] + y; + + gradientMagnitude = gradientMagnImage->GetPixel(currentIndex); + + //check for new max + if(maxGradientMagnitude < gradientMagnitude) + { + maxGradientMagnitude = gradientMagnitude; + returnIndex[0] = currentIndex[0]; + returnIndex[1] = currentIndex[1]; + returnIndex[2] = 0.0; + }//end if + }//end for y + + currentIndex[1] = index[1]; + }//end for x + +} diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h new file mode 100644 index 0000000000..99366a5b80 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h @@ -0,0 +1,121 @@ +/*=================================================================== + +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 mitkCorrectorTool2D_h_Included +#define mitkCorrectorTool2D_h_Included + +#include "mitkCommon.h" +#include "SegmentationExports.h" +#include "mitkSegTool2D.h" +#include +#include + +#include + +namespace mitk +{ + + +/** + \brief A 2D segmentation tool based on LiveWire approach. + The contour between the last user added point and the current mouse position + is computed by searching the shortest path according to specific features of + the image. The contour thus snappest to the boundary of objects. + + + \sa SegTool2D + \sa ImageLiveWireContourModelFilter + + \ingroup Interaction + \ingroup ToolManagerEtAl + + \warning Only to be instantiated by mitk::ToolManager. +*/ +class Segmentation_EXPORT LiveWireTool2D : public SegTool2D +{ + public: + + mitkClassMacro(LiveWireTool2D, SegTool2D); + itkNewMacro(LiveWireTool2D); + + virtual const char** GetXPM() const; + virtual const char* GetName() const; + + protected: + + LiveWireTool2D(); + virtual ~LiveWireTool2D(); + + /** + * \brief Calculates how good the data, this statemachine handles, is hit by the event. + * + */ + virtual float CanHandleEvent( StateEvent const *stateEvent) const; + + virtual void Activated(); + virtual void Deactivated(); + + /// \brief Initialize tool + virtual bool OnInitLiveWire (Action*, const StateEvent*); + + /// \brief Add a control point and finish current segment + virtual bool OnAddPoint (Action*, const StateEvent*); + + /// \breif Actual LiveWire computation + virtual bool OnMouseMoved(Action*, const StateEvent*); + + /// \brief Check double click on first control point to finish the LiveWire tool + virtual bool OnCheckPoint(Action*, const StateEvent*); + + /// \brief Finish LiveWire tool + virtual bool OnFinish(Action*, const StateEvent*); + + /// \brief Close the contour + virtual bool OnLastSegmentDelete(Action*, const StateEvent*); + + /// \brief Don't use dynamic cost map for LiveWire calculation + virtual bool OnMouseMoveNoDynamicCosts(Action*, const StateEvent*); + + /// \brief Finish contour interaction. + void FinishTool(); + + + //the contour already set by the user + mitk::ContourModel::Pointer m_Contour; + //the corresponding datanode + mitk::DataNode::Pointer m_ContourModelNode; + + //the current LiveWire computed contour + mitk::ContourModel::Pointer m_LiveWireContour; + //the corresponding datanode + mitk::DataNode::Pointer m_LiveWireContourNode; + + //the current reference image + mitk::Image::Pointer m_WorkingSlice; + + mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilter; + + bool m_CreateAndUseDynamicCosts; + + std::vector< std::pair > m_Contours; + + template + void FindHighestGradientMagnitudeByITK(itk::Image* inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex); +}; + +} // namespace + +#endif diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.xpm b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.xpm new file mode 100644 index 0000000000..e5c4a22a1a --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.xpm @@ -0,0 +1,99 @@ +/* XPM */ +static const char * mitkLiveWireTool2D_xpm[] = { +"24 24 72 1", +" c None", +". c #179FBF", +"+ c #01AAD2", +"@ c #00AAD3", +"# c #0BA4CA", +"$ c #14A0C1", +"% c #11A2C4", +"& c #06A7CE", +"* c #0AA5CB", +"= c #2895B1", +"- c #229AB5", +"; c #02A9D1", +"> c #0DA3C8", +", c #1F99B7", +"' c #2598B5", +") c #0CA4C9", +"! c #13A1C2", +"~ c #03A8D0", +"{ c #2795B1", +"] c #179EBF", +"^ c #04A9D0", +"/ c #1C9BBA", +"( c #2497B3", +"_ c #1E9AB9", +": c #209AB9", +"< c #13A0C3", +"[ c #03A9D1", +"} c #189DBE", +"| c #07A7CD", +"1 c #1A9CBC", +"2 c #1E9BBA", +"3 c #2699B4", +"4 c #2498B4", +"5 c #229AB6", +"6 c #0EA3C8", +"7 c #07A7CE", +"8 c #189EBE", +"9 c #1C9CBA", +"0 c #04A7CF", +"a c #11A1C5", +"b c #07A6CD", +"c c #09A6CC", +"d c #2A94AD", +"e c #1C9CBB", +"f c #1E9BB8", +"g c #259AB4", +"h c #05A7CE", +"i c #2E92A9", +"j c #0EA2C6", +"k c #3A8FA2", +"l c #1B9CBC", +"m c #1D9BB9", +"n c #169FC1", +"o c #00A9D2", +"p c #1D9BBA", +"q c #2B94AC", +"r c #16A0C1", +"s c #10A2C6", +"t c #209BB9", +"u c #1D9CBB", +"v c #179FC0", +"w c #000000", +"x c #219AB4", +"y c #FFFFFF", +"z c #F3F6F1", +"A c #189EC0", +"B c #00AAD2", +"C c #16A7BC", +"D c #17A0BF", +"E c #2299B7", +"F c #1D9BB8", +"G c #1A9CBD", +" .+@#$%&@*= ", +" -;>, '); ", +" !~{ &) ", +" ]^ /@ ", +" (@_ :@ ", +" <[ }| ", +" @1 +2 ", +" @345 67 ", +" +@890 a^ ", +" }bcdef gh|i ", +" jk&@l(mn^op ", +" qr+@@@@st ", +" u& ", +" 0vwwwwwwwww ", +" @xwwwyyzyzww ", +" ABwwwwzzzzzzww ", +" oCwwwwwwwwzzzw ", +" D@ wwzzw ", +" c@@@ wzzw ", +" EFG wwzzw ", +" wwwwwwwwzzzw ", +" wwwwzzzzzyww ", +" wwwwyyyyyww ", +" wwwwwwwwww "}; diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index 739ed49b80..e4abb59b68 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp @@ -1,373 +1,414 @@ /*=================================================================== 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 "mitkSegTool2D.h" #include "mitkToolManager.h" #include "mitkDataStorage.h" #include "mitkBaseRenderer.h" #include "mitkPlaneGeometry.h" #include "mitkExtractImageFilter.h" #include "mitkExtractDirectedPlaneImageFilter.h" //Include of the new ImageExtractor #include "mitkExtractDirectedPlaneImageFilterNew.h" #include "mitkPlanarCircle.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkOverwriteDirectedPlaneImageFilter.h" #include "mitkGetModuleContext.h" //Includes for 3DSurfaceInterpolation #include "mitkImageToContourFilter.h" #include "mitkSurfaceInterpolationController.h" //includes for resling and overwriting #include #include #include #include #include #include "mitkOperationEvent.h" #include "mitkUndoController.h" #define ROUND(a) ((a)>0 ? (int)((a)+0.5) : -(int)(0.5-(a))) mitk::SegTool2D::SegTool2D(const char* type) :Tool(type), - m_LastEventSender(NULL), - m_LastEventSlice(0), - m_Contourmarkername ("Position"), - m_ShowMarkerNodes (false), - m_3DInterpolationEnabled(true) +m_LastEventSender(NULL), +m_LastEventSlice(0), +m_Contourmarkername ("Position"), +m_ShowMarkerNodes (false), +m_3DInterpolationEnabled(true) { } mitk::SegTool2D::~SegTool2D() { } float mitk::SegTool2D::CanHandleEvent( StateEvent const *stateEvent) const { const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return 0.0; if ( positionEvent->GetSender()->GetMapperID() != BaseRenderer::Standard2D ) return 0.0; // we don't want anything but 2D //This are the mouse event that are used by the statemachine patterns for zooming and panning. This must be possible although a tool is activ if (stateEvent->GetId() == EIDRIGHTMOUSEBTN || stateEvent->GetId() == EIDMIDDLEMOUSEBTN || stateEvent->GetId() == EIDRIGHTMOUSEBTNANDCTRL || - stateEvent->GetId() == EIDMIDDLEMOUSERELEASE || stateEvent->GetId() == EIDRIGHTMOUSERELEASE || stateEvent->GetId() == EIDRIGHTMOUSEBTNANDMOUSEMOVE || - stateEvent->GetId() == EIDMIDDLEMOUSEBTNANDMOUSEMOVE || stateEvent->GetId() == EIDCTRLANDRIGHTMOUSEBTNANDMOUSEMOVE || stateEvent->GetId() == EIDCTRLANDRIGHTMOUSEBTNRELEASE ) + stateEvent->GetId() == EIDMIDDLEMOUSERELEASE || stateEvent->GetId() == EIDRIGHTMOUSERELEASE || stateEvent->GetId() == EIDRIGHTMOUSEBTNANDMOUSEMOVE || + stateEvent->GetId() == EIDMIDDLEMOUSEBTNANDMOUSEMOVE || stateEvent->GetId() == EIDCTRLANDRIGHTMOUSEBTNANDMOUSEMOVE || stateEvent->GetId() == EIDCTRLANDRIGHTMOUSEBTNRELEASE ) { //Since the usual segmentation tools currently do not need right click interaction but the mitkDisplayVectorInteractor return 0.0; } else { return 1.0; } } bool mitk::SegTool2D::DetermineAffectedImageSlice( const Image* image, const PlaneGeometry* plane, int& affectedDimension, int& affectedSlice ) { assert(image); assert(plane); // compare normal of plane to the three axis vectors of the image Vector3D normal = plane->GetNormal(); Vector3D imageNormal0 = image->GetSlicedGeometry()->GetAxisVector(0); Vector3D imageNormal1 = image->GetSlicedGeometry()->GetAxisVector(1); Vector3D imageNormal2 = image->GetSlicedGeometry()->GetAxisVector(2); normal.Normalize(); imageNormal0.Normalize(); imageNormal1.Normalize(); imageNormal2.Normalize(); imageNormal0.Set_vnl_vector( vnl_cross_3d(normal.Get_vnl_vector(),imageNormal0.Get_vnl_vector()) ); imageNormal1.Set_vnl_vector( vnl_cross_3d(normal.Get_vnl_vector(),imageNormal1.Get_vnl_vector()) ); imageNormal2.Set_vnl_vector( vnl_cross_3d(normal.Get_vnl_vector(),imageNormal2.Get_vnl_vector()) ); double eps( 0.00001 ); // axial if ( imageNormal2.GetNorm() <= eps ) { affectedDimension = 2; } // sagittal else if ( imageNormal1.GetNorm() <= eps ) { affectedDimension = 1; } // frontal else if ( imageNormal0.GetNorm() <= eps ) { affectedDimension = 0; } else { affectedDimension = -1; // no idea return false; } // determine slice number in image Geometry3D* imageGeometry = image->GetGeometry(0); Point3D testPoint = imageGeometry->GetCenter(); Point3D projectedPoint; plane->Project( testPoint, projectedPoint ); Point3D indexPoint; imageGeometry->WorldToIndex( projectedPoint, indexPoint ); affectedSlice = ROUND( indexPoint[affectedDimension] ); MITK_DEBUG << "indexPoint " << indexPoint << " affectedDimension " << affectedDimension << " affectedSlice " << affectedSlice; // check if this index is still within the image if ( affectedSlice < 0 || affectedSlice >= static_cast(image->GetDimension(affectedDimension)) ) return false; return true; } + mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const PositionEvent* positionEvent, const Image* image) { if (!positionEvent) return NULL; assert( positionEvent->GetSender() ); // sure, right? unsigned int timeStep = positionEvent->GetSender()->GetTimeStep( image ); // get the timestep of the visible part (time-wise) of the image // first, we determine, which slice is affected const PlaneGeometry* planeGeometry( dynamic_cast (positionEvent->GetSender()->GetCurrentWorldGeometry2D() ) ); + return this->GetAffectedImageSliceAs2DImage(planeGeometry, image, timeStep); +} + + +mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const PlaneGeometry* planeGeometry, const Image* image, unsigned int timeStep) +{ if ( !image || !planeGeometry ) return NULL; //Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); //set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); //use ExtractSliceFilter with our specific vtkImageReslice for overwriting and extracting mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput( image ); extractor->SetTimeStep( timeStep ); extractor->SetWorldGeometry( planeGeometry ); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry( image->GetTimeSlicedGeometry()->GetGeometry3D( timeStep ) ); extractor->Modified(); extractor->Update(); Image::Pointer slice = extractor->GetOutput(); /*============= BEGIN undo feature block ========================*/ //specify the undo operation with the non edited slice m_undoOperation = new DiffSliceOperation(const_cast(image), extractor->GetVtkOutput(), slice->GetGeometry(), timeStep, const_cast(planeGeometry)); /*============= END undo feature block ========================*/ return slice; } + mitk::Image::Pointer mitk::SegTool2D::GetAffectedWorkingSlice(const PositionEvent* positionEvent) { DataNode* workingNode( m_ToolManager->GetWorkingData(0) ); if ( !workingNode ) return NULL; Image* workingImage = dynamic_cast(workingNode->GetData()); if ( !workingImage ) return NULL; return GetAffectedImageSliceAs2DImage( positionEvent, workingImage ); } + mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const PositionEvent* positionEvent) { DataNode* referenceNode( m_ToolManager->GetReferenceData(0) ); if ( !referenceNode ) return NULL; Image* referenceImage = dynamic_cast(referenceNode->GetData()); if ( !referenceImage ) return NULL; return GetAffectedImageSliceAs2DImage( positionEvent, referenceImage ); } void mitk::SegTool2D::WriteBackSegmentationResult (const PositionEvent* positionEvent, Image* slice) { + if(!positionEvent) return; + const PlaneGeometry* planeGeometry( dynamic_cast (positionEvent->GetSender()->GetCurrentWorldGeometry2D() ) ); + if( planeGeometry && slice) + { + DataNode* workingNode( m_ToolManager->GetWorkingData(0) ); + Image* image = dynamic_cast(workingNode->GetData()); + unsigned int timeStep = positionEvent->GetSender()->GetTimeStep( image ); + this->WriteBackSegmentationResult(planeGeometry, slice, timeStep); + + slice->DisconnectPipeline(); + ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); + contourExtractor->SetInput(slice); + contourExtractor->Update(); + mitk::Surface::Pointer contour = contourExtractor->GetOutput(); + + if (m_3DInterpolationEnabled && contour->GetVtkPolyData()->GetNumberOfPoints() > 0 ) + { + unsigned int pos = this->AddContourmarker(positionEvent); + mitk::ServiceReference serviceRef = mitk::GetModuleContext()->GetServiceReference(); + PlanePositionManagerService* service = dynamic_cast(mitk::GetModuleContext()->GetService(serviceRef)); + mitk::SurfaceInterpolationController::GetInstance()->AddNewContour( contour, service->GetPlanePosition(pos)); + contour->DisconnectPipeline(); + } + } + +} + + +void mitk::SegTool2D::WriteBackSegmentationResult (const PlaneGeometry* planeGeometry, Image* slice, unsigned int timeStep) +{ + if(!planeGeometry || !slice) return; + + DataNode* workingNode( m_ToolManager->GetWorkingData(0) ); Image* image = dynamic_cast(workingNode->GetData()); - unsigned int timeStep = positionEvent->GetSender()->GetTimeStep( image ); - //Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk reslicer + //Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); //Set the slice as 'input' reslice->SetInputSlice(slice->GetVtkImageData()); //set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput( image ); extractor->SetTimeStep( timeStep ); extractor->SetWorldGeometry( planeGeometry ); extractor->SetVtkOutputRequest(true); extractor->SetResliceTransformByGeometry( image->GetTimeSlicedGeometry()->GetGeometry3D( timeStep ) ); extractor->Modified(); extractor->Update(); //the image was modified within the pipeline, but not marked so image->Modified(); image->GetVtkImageData()->Modified(); /*============= BEGIN undo feature block ========================*/ //specify the undo operation with the edited slice m_doOperation = new DiffSliceOperation(image, extractor->GetVtkOutput(),slice->GetGeometry(), timeStep, const_cast(planeGeometry)); //create an operation event for the undo stack OperationEvent* undoStackItem = new OperationEvent( DiffSliceOperationApplier::GetInstance(), m_doOperation, m_undoOperation, "Segmentation" ); //add it to the undo controller UndoController::GetCurrentUndoModel()->SetOperationEvent( undoStackItem ); //clear the pointers as the operation are stored in the undocontroller and also deleted from there m_undoOperation = NULL; m_doOperation = NULL; /*============= END undo feature block ========================*/ slice->DisconnectPipeline(); ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); contourExtractor->SetInput(slice); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (m_3DInterpolationEnabled) { unsigned int pos = this->AddContourmarker(positionEvent); mitk::ServiceReference serviceRef = mitk::GetModuleContext()->GetServiceReference(); PlanePositionManagerService* service = dynamic_cast(mitk::GetModuleContext()->GetService(serviceRef)); mitk::SurfaceInterpolationController::GetInstance()->AddNewContour( contour, service->GetPlanePosition(pos)); contour->DisconnectPipeline(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } void mitk::SegTool2D::SetShowMarkerNodes(bool status) { - m_ShowMarkerNodes = status; + m_ShowMarkerNodes = status; } void mitk::SegTool2D::SetEnable3DInterpolation(bool enabled) { m_3DInterpolationEnabled = enabled; } unsigned int mitk::SegTool2D::AddContourmarker ( const PositionEvent* positionEvent ) { const mitk::Geometry2D* plane = dynamic_cast (dynamic_cast< const mitk::SlicedGeometry3D*>( positionEvent->GetSender()->GetSliceNavigationController()->GetCurrentGeometry3D())->GetGeometry2D(0)); mitk::ServiceReference serviceRef = mitk::GetModuleContext()->GetServiceReference(); PlanePositionManagerService* service = dynamic_cast(mitk::GetModuleContext()->GetService(serviceRef)); unsigned int size = service->GetNumberOfPlanePositions(); unsigned int id = service->AddNewPlanePosition(plane, positionEvent->GetSender()->GetSliceNavigationController()->GetSlice()->GetPos()); mitk::PlanarCircle::Pointer contourMarker = mitk::PlanarCircle::New(); mitk::Point2D p1; plane->Map(plane->GetCenter(), p1); mitk::Point2D p2 = p1; p2[0] -= plane->GetSpacing()[0]; p2[1] -= plane->GetSpacing()[1]; contourMarker->PlaceFigure( p1 ); contourMarker->SetCurrentControlPoint( p1 ); contourMarker->SetGeometry2D( const_cast(plane)); std::stringstream markerStream; mitk::DataNode* workingNode (m_ToolManager->GetWorkingData(0)); markerStream << m_Contourmarkername ; markerStream << " "; markerStream << id+1; DataNode::Pointer rotatedContourNode = DataNode::New(); rotatedContourNode->SetData(contourMarker); rotatedContourNode->SetProperty( "name", StringProperty::New(markerStream.str()) ); rotatedContourNode->SetProperty( "isContourMarker", BoolProperty::New(true)); rotatedContourNode->SetBoolProperty( "PlanarFigureInitializedWindow", true, positionEvent->GetSender() ); rotatedContourNode->SetProperty( "includeInBoundingBox", BoolProperty::New(false)); rotatedContourNode->SetProperty( "helper object", mitk::BoolProperty::New(!m_ShowMarkerNodes)); rotatedContourNode->SetProperty( "planarfigure.drawcontrolpoints", BoolProperty::New(false)); rotatedContourNode->SetProperty( "planarfigure.drawname", BoolProperty::New(false)); rotatedContourNode->SetProperty( "planarfigure.drawoutline", BoolProperty::New(false)); rotatedContourNode->SetProperty( "planarfigure.drawshadow", BoolProperty::New(false)); if (plane) { if ( id == size ) { m_ToolManager->GetDataStorage()->Add(rotatedContourNode, workingNode); } else { - mitk::NodePredicateProperty::Pointer isMarker = mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true)); - - mitk::DataStorage::SetOfObjects::ConstPointer markers = m_ToolManager->GetDataStorage()->GetDerivations(workingNode,isMarker); - - for ( mitk::DataStorage::SetOfObjects::const_iterator iter = markers->begin(); - iter != markers->end(); - ++iter) + mitk::NodePredicateProperty::Pointer isMarker = mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true)); + + mitk::DataStorage::SetOfObjects::ConstPointer markers = m_ToolManager->GetDataStorage()->GetDerivations(workingNode,isMarker); + + for ( mitk::DataStorage::SetOfObjects::const_iterator iter = markers->begin(); + iter != markers->end(); + ++iter) + { + std::string nodeName = (*iter)->GetName(); + unsigned int t = nodeName.find_last_of(" "); + unsigned int markerId = atof(nodeName.substr(t+1).c_str())-1; + if(id == markerId) { - std::string nodeName = (*iter)->GetName(); - unsigned int t = nodeName.find_last_of(" "); - unsigned int markerId = atof(nodeName.substr(t+1).c_str())-1; - if(id == markerId) - { - return id; - } + return id; } - m_ToolManager->GetDataStorage()->Add(rotatedContourNode, workingNode); + } + m_ToolManager->GetDataStorage()->Add(rotatedContourNode, workingNode); } } return id; } void mitk::SegTool2D::InteractiveSegmentationBugMessage( const std::string& message ) { MITK_ERROR << "********************************************************************************" << std::endl << " " << message << std::endl << "********************************************************************************" << std::endl << " " << std::endl << " If your image is rotated or the 2D views don't really contain the patient image, try to press the button next to the image selection. " << std::endl << " " << std::endl << " Please file a BUG REPORT: " << std::endl << " http://bugs.mitk.org" << std::endl << " Contain the following information:" << std::endl << " - What image were you working on?" << std::endl << " - Which region of the image?" << std::endl << " - Which tool did you use?" << std::endl << " - What did you do?" << std::endl << " - What happened (not)? What did you expect?" << std::endl; } - diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.h b/Modules/Segmentation/Interactions/mitkSegTool2D.h index fa039ef25a..e49e148054 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.h +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.h @@ -1,145 +1,151 @@ /*=================================================================== 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 mitkSegTool2D_h_Included #define mitkSegTool2D_h_Included #include "mitkCommon.h" #include "SegmentationExports.h" #include "mitkTool.h" #include "mitkImage.h" #include "mitkStateEvent.h" #include "mitkPositionEvent.h" #include "mitkPlanePositionManager.h" #include "mitkRestorePlanePositionOperation.h" #include "mitkInteractionConst.h" #include namespace mitk { class BaseRenderer; /** \brief Abstract base class for segmentation tools. \sa Tool \ingroup Interaction \ingroup ToolManagerEtAl Implements 2D segmentation specific helper methods, that might be of use to all kind of 2D segmentation tools. At the moment these are: - Determination of the slice where the user paints upon (DetermineAffectedImageSlice) - Projection of a 3D contour onto a 2D plane/slice SegTool2D tries to structure the interaction a bit. If you pass "PressMoveRelease" as the interaction type of your derived tool, you might implement the methods OnMousePressed, OnMouseMoved, and OnMouseReleased. Yes, your guess about when they are called is correct. \warning Only to be instantiated by mitk::ToolManager. $Author$ */ class Segmentation_EXPORT SegTool2D : public Tool { public: mitkClassMacro(SegTool2D, Tool); /** \brief Calculates for a given Image and PlaneGeometry, which slice of the image (in index corrdinates) is meant by the plane. \return false, if no slice direction seems right (e.g. rotated planes) \param affectedDimension The image dimension, which is constant for all points in the plane, e.g. Axial --> 2 \param affectedSlice The index of the image slice */ static bool DetermineAffectedImageSlice( const Image* image, const PlaneGeometry* plane, int& affectedDimension, int& affectedSlice ); void SetShowMarkerNodes(bool); /** * \brief Enables or disables the 3D interpolation after writing back the 2D segmentation result, and defaults to true. */ void SetEnable3DInterpolation(bool); protected: SegTool2D(); // purposely hidden SegTool2D(const char*); // purposely hidden virtual ~SegTool2D(); /** * \brief Calculates how good the data, this statemachine handles, is hit by the event. * */ virtual float CanHandleEvent( StateEvent const *stateEvent) const; /** \brief Extract the slice of an image that the user just scribbles on. \return NULL if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position. */ Image::Pointer GetAffectedImageSliceAs2DImage(const PositionEvent*, const Image* image); + /** + \brief Extract the slice of an image cut by given plane. + \return NULL if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position. + */ + Image::Pointer GetAffectedImageSliceAs2DImage(const PlaneGeometry* planeGeometry, const Image* image, unsigned int timeStep); + /** \brief Extract the slice of the currently selected working image that the user just scribbles on. \return NULL if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position, or just no working image is selected. */ Image::Pointer GetAffectedWorkingSlice(const PositionEvent*); /** \brief Extract the slice of the currently selected reference image that the user just scribbles on. \return NULL if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position, or just no reference image is selected. */ Image::Pointer GetAffectedReferenceSlice(const PositionEvent*); void WriteBackSegmentationResult (const PositionEvent*, Image*); + void WriteBackSegmentationResult (const PlaneGeometry* planeGeometry, Image*, unsigned int timeStep); + /** \brief Adds a new node called Contourmarker to the datastorage which holds a mitk::PlanarFigure. By selecting this node the slicestack will be reoriented according to the PlanarFigure's Geometry */ unsigned int AddContourmarker ( const PositionEvent* ); void InteractiveSegmentationBugMessage( const std::string& message ); BaseRenderer* m_LastEventSender; unsigned int m_LastEventSlice; private: //The prefix of the contourmarkername. Suffix is a consecutive number const std::string m_Contourmarkername; bool m_ShowMarkerNodes; bool m_3DInterpolationEnabled; DiffSliceOperation* m_doOperation; DiffSliceOperation* m_undoOperation; }; } // namespace #endif - - diff --git a/Modules/Segmentation/Rendering/mitkContourModelGLMapper2D.cpp b/Modules/Segmentation/Rendering/mitkContourModelGLMapper2D.cpp index 4cf207e539..62f3632b67 100644 --- a/Modules/Segmentation/Rendering/mitkContourModelGLMapper2D.cpp +++ b/Modules/Segmentation/Rendering/mitkContourModelGLMapper2D.cpp @@ -1,277 +1,280 @@ /*=================================================================== 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 "mitkContourModelGLMapper2D.h" #include "mitkBaseRenderer.h" #include "mitkPlaneGeometry.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkContourModel.h" #include "mitkContourModelSubDivisionFilter.h" #include #include "mitkGL.h" mitk::ContourModelGLMapper2D::ContourModelGLMapper2D() { } mitk::ContourModelGLMapper2D::~ContourModelGLMapper2D() { } void mitk::ContourModelGLMapper2D::Paint(mitk::BaseRenderer * renderer) { bool visible = true; GetDataNode()->GetVisibility(visible, renderer, "visible"); if ( !visible ) return; bool updateNeccesary=true; int timestep = renderer->GetTimeStep(); mitk::ContourModel::Pointer input = const_cast(this->GetInput()); mitk::ContourModel::Pointer renderingContour = input; bool subdivision = false; this->GetDataNode()->GetBoolProperty( "subdivision curve", subdivision, renderer ); if (subdivision) { mitk::ContourModel::Pointer subdivContour = mitk::ContourModel::New(); mitk::ContourModelSubDivisionFilter::Pointer subdivFilter = mitk::ContourModelSubDivisionFilter::New(); subdivFilter->SetInput(input); subdivFilter->Update(); subdivContour = subdivFilter->GetOutput(); if(subdivContour->GetNumberOfVertices() == 0 ) { subdivContour = input; } renderingContour = subdivContour; } renderingContour->UpdateOutputInformation(); if( renderingContour->GetMTime() < this->m_LastUpdateTime ) updateNeccesary = false; if(renderingContour->GetNumberOfVertices(timestep) < 1) updateNeccesary = false; if (updateNeccesary) { // ok, das ist aus GenerateData kopiert mitk::DisplayGeometry::Pointer displayGeometry = renderer->GetDisplayGeometry(); assert(displayGeometry.IsNotNull()); //apply color and opacity read from the PropertyList ApplyProperties(renderer); mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty("contour.color", renderer)); if(colorprop) { //set the color of the contour double red = colorprop->GetColor().GetRed(); double green = colorprop->GetColor().GetGreen(); double blue = colorprop->GetColor().GetBlue(); glColor4f(red,green,blue,0.5); } mitk::ColorProperty::Pointer selectedcolor = dynamic_cast(GetDataNode()->GetProperty("points.color", renderer)); if(!selectedcolor) { selectedcolor = mitk::ColorProperty::New(1.0,0.0,0.1); } vtkLinearTransform* transform = GetDataNode()->GetVtkTransform(); // ContourModel::OutputType point; mitk::Point3D point; mitk::Point3D p, projected_p; float vtkp[3]; float lineWidth = 3.0; if (dynamic_cast(this->GetDataNode()->GetProperty("contour.width")) != NULL) lineWidth = dynamic_cast(this->GetDataNode()->GetProperty("contour.width"))->GetValue(); glLineWidth(lineWidth); bool drawit=false; mitk::ContourModel::VertexIterator pointsIt = renderingContour->IteratorBegin(timestep); Point2D pt2d; // projected_p in display coordinates Point2D lastPt2d; while ( pointsIt != renderingContour->IteratorEnd(timestep) ) { lastPt2d = pt2d; point = (*pointsIt)->Coordinates; itk2vtk(point, vtkp); transform->TransformPoint(vtkp, vtkp); vtk2itk(vtkp,p); displayGeometry->Project(p, projected_p); displayGeometry->Map(projected_p, pt2d); displayGeometry->WorldToDisplay(pt2d, pt2d); Vector3D diff=p-projected_p; - ScalarType scalardiff = diff.GetSquaredNorm(); + ScalarType scalardiff = diff.GetNorm(); //draw lines bool projectmode=false; - GetDataNode()->GetVisibility(projectmode, renderer, "project"); + GetDataNode()->GetVisibility(projectmode, renderer, "contour.project-onto-plane"); if(projectmode) + { drawit=true; - else + } + else if(scalardiff<0.25) { - if(diff.GetSquaredNorm()<0.5) - drawit=true; + drawit=true; } + if(drawit) { //lastPt2d is not valid in first step if( !(pointsIt == renderingContour->IteratorBegin(timestep)) ) { glBegin (GL_LINES); glVertex2f(pt2d[0], pt2d[1]); glVertex2f(lastPt2d[0], lastPt2d[1]); glEnd(); } //draw active points if ((*pointsIt)->IsControlPoint) { float pointsize = 4; Point2D tmp; Vector2D horz,vert; - horz[0]=pointsize-scalardiff*2; horz[1]=0; - vert[0]=0; vert[1]=pointsize-scalardiff*2; + horz[1]=0; + vert[0]=0; horz[0]=pointsize; vert[1]=pointsize; glColor3f(selectedcolor->GetColor().GetRed(), selectedcolor->GetColor().GetBlue(), selectedcolor->GetColor().GetGreen()); glLineWidth(1); //a rectangle around the point with the selected color glBegin (GL_LINE_LOOP); tmp=pt2d-horz; glVertex2fv(&tmp[0]); tmp=pt2d+vert; glVertex2fv(&tmp[0]); tmp=pt2d+horz; glVertex2fv(&tmp[0]); tmp=pt2d-vert; glVertex2fv(&tmp[0]); - glEnd (); + glEnd(); glLineWidth(1); //the actual point in the specified color to see the usual color of the point glColor3f(colorprop->GetColor().GetRed(),colorprop->GetColor().GetGreen(),colorprop->GetColor().GetBlue()); glPointSize(1); glBegin (GL_POINTS); tmp=pt2d; glVertex2fv(&tmp[0]); glEnd (); } } pointsIt++; }//end while iterate over controlpoints //close contour if necessary if(renderingContour->IsClosed(timestep) && drawit) { lastPt2d = pt2d; point = renderingContour->GetVertexAt(0,timestep)->Coordinates; itk2vtk(point, vtkp); transform->TransformPoint(vtkp, vtkp); vtk2itk(vtkp,p); displayGeometry->Project(p, projected_p); displayGeometry->Map(projected_p, pt2d); displayGeometry->WorldToDisplay(pt2d, pt2d); glBegin (GL_LINES); glVertex2f(lastPt2d[0], lastPt2d[1]); glVertex2f( pt2d[0], pt2d[1] ); glEnd(); } //draw selected vertex if exists if(renderingContour->GetSelectedVertex()) { //transform selected vertex point = renderingContour->GetSelectedVertex()->Coordinates; itk2vtk(point, vtkp); transform->TransformPoint(vtkp, vtkp); vtk2itk(vtkp,p); displayGeometry->Project(p, projected_p); displayGeometry->Map(projected_p, pt2d); displayGeometry->WorldToDisplay(pt2d, pt2d); Vector3D diff=p-projected_p; - ScalarType scalardiff = diff.GetSquaredNorm(); + ScalarType scalardiff = diff.GetNorm(); //---------------------------------- //draw point if close to plane - if(scalardiff<0.5) + if(scalardiff<0.25) { float pointsize = 3.2; Point2D tmp; glColor3f(0.0, 1.0, 0.0); glLineWidth(1); //a diamond around the point glBegin (GL_LINE_LOOP); //begin from upper left corner and paint clockwise tmp[0]=pt2d[0]-pointsize; tmp[1]=pt2d[1]+pointsize; glVertex2fv(&tmp[0]); tmp[0]=pt2d[0]+pointsize; tmp[1]=pt2d[1]+pointsize; glVertex2fv(&tmp[0]); tmp[0]=pt2d[0]+pointsize; tmp[1]=pt2d[1]-pointsize; glVertex2fv(&tmp[0]); tmp[0]=pt2d[0]-pointsize; tmp[1]=pt2d[1]-pointsize; glVertex2fv(&tmp[0]); glEnd (); } //------------------------------------ } } } const mitk::ContourModel* mitk::ContourModelGLMapper2D::GetInput(void) { return static_cast ( GetDataNode()->GetData() ); } void mitk::ContourModelGLMapper2D::SetDefaultProperties(mitk::DataNode* node, mitk::BaseRenderer* renderer, bool overwrite) { node->AddProperty( "contour.color", ColorProperty::New(0.9, 1.0, 0.1), renderer, overwrite ); node->AddProperty( "points.color", ColorProperty::New(1.0, 0.0, 0.1), renderer, overwrite ); node->AddProperty( "contour.width", mitk::FloatProperty::New( 1.0 ), renderer, overwrite ); node->AddProperty( "subdivision curve", mitk::BoolProperty::New( false ), renderer, overwrite ); + node->AddProperty( "contour.project-onto-plane", mitk::BoolProperty::New( false ), renderer, overwrite ); Superclass::SetDefaultProperties(node, renderer, overwrite); } diff --git a/Modules/Segmentation/Rendering/mitkContourModelMapper3D.cpp b/Modules/Segmentation/Rendering/mitkContourModelMapper3D.cpp index 45075dc3da..8c2971b0dc 100644 --- a/Modules/Segmentation/Rendering/mitkContourModelMapper3D.cpp +++ b/Modules/Segmentation/Rendering/mitkContourModelMapper3D.cpp @@ -1,242 +1,242 @@ /*=================================================================== 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 mitk::ContourModelMapper3D::ContourModelMapper3D() { } mitk::ContourModelMapper3D::~ContourModelMapper3D() { } const mitk::ContourModel* mitk::ContourModelMapper3D::GetInput( void ) { //convient way to get the data from the dataNode return static_cast< const mitk::ContourModel * >( GetDataNode()->GetData() ); } vtkProp* mitk::ContourModelMapper3D::GetVtkProp(mitk::BaseRenderer* renderer) { //return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actor; } void mitk::ContourModelMapper3D::GenerateDataForRenderer( mitk::BaseRenderer *renderer ) { /* First convert the contourModel to vtkPolyData, then tube filter it and * set it input for our mapper */ LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::ContourModel* inputContour = static_cast< mitk::ContourModel* >( GetDataNode()->GetData() ); localStorage->m_OutlinePolyData = this->CreateVtkPolyDataFromContour(inputContour); this->ApplyContourProperties(renderer); //tube filter the polyData localStorage->m_TubeFilter->SetInput(localStorage->m_OutlinePolyData); float lineWidth(1.0); if (this->GetDataNode()->GetFloatProperty( "3D contour width", lineWidth, renderer )) { localStorage->m_TubeFilter->SetRadius(lineWidth); }else { - localStorage->m_TubeFilter->SetRadius(0.2); + localStorage->m_TubeFilter->SetRadius(0.5); } localStorage->m_TubeFilter->CappingOn(); localStorage->m_TubeFilter->SetNumberOfSides(10); localStorage->m_TubeFilter->Update(); localStorage->m_Mapper->SetInput(localStorage->m_TubeFilter->GetOutput()); } void mitk::ContourModelMapper3D::Update(mitk::BaseRenderer* renderer) { bool visible = true; GetDataNode()->GetVisibility(visible, renderer, "visible"); mitk::ContourModel* data = static_cast< mitk::ContourModel*>( GetDataNode()->GetData() ); if ( data == NULL ) { return; } // Calculate time step of the input data for the specified renderer (integer value) this->CalculateTimeStep( renderer ); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // Check if time step is valid const TimeSlicedGeometry *dataTimeGeometry = data->GetTimeSlicedGeometry(); if ( ( dataTimeGeometry == NULL ) || ( dataTimeGeometry->GetTimeSteps() == 0 ) || ( !dataTimeGeometry->IsValidTime( renderer->GetTimeStep() ) ) ) { //clear the rendered polydata localStorage->m_Mapper->SetInput(vtkSmartPointer::New()); return; } const DataNode *node = this->GetDataNode(); data->UpdateOutputInformation(); //check if something important has changed and we need to rerender if ( (localStorage->m_LastUpdateTime < node->GetMTime()) //was the node modified? || (localStorage->m_LastUpdateTime < data->GetPipelineMTime()) //Was the data modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldGeometry2DUpdateTime()) //was the geometry modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldGeometry2D()->GetMTime()) || (localStorage->m_LastUpdateTime < node->GetPropertyList()->GetMTime()) //was a property modified? || (localStorage->m_LastUpdateTime < node->GetPropertyList(renderer)->GetMTime()) ) { this->GenerateDataForRenderer( renderer ); } // since we have checked that nothing important has changed, we can set // m_LastUpdateTime to the current time localStorage->m_LastUpdateTime.Modified(); } vtkSmartPointer mitk::ContourModelMapper3D::CreateVtkPolyDataFromContour(mitk::ContourModel* inputContour) { unsigned int timestep = this->GetTimestep(); //the points to draw vtkSmartPointer points = vtkSmartPointer::New(); //the lines to connect the points vtkSmartPointer lines = vtkSmartPointer::New(); // Create a polydata to store everything in vtkSmartPointer polyData = vtkSmartPointer::New(); //iterate over the control points mitk::ContourModel::VertexIterator current = inputContour->IteratorBegin(timestep); mitk::ContourModel::VertexIterator next = inputContour->IteratorBegin(timestep); if(next != inputContour->IteratorEnd(timestep)) { next++; mitk::ContourModel::VertexIterator end = inputContour->IteratorEnd(timestep); while(next != end) { mitk::ContourModel::VertexType* currentControlPoint = *current; mitk::ContourModel::VertexType* nextControlPoint = *next; if( !(currentControlPoint->Coordinates[0] == nextControlPoint->Coordinates[0] && currentControlPoint->Coordinates[1] == nextControlPoint->Coordinates[1] && currentControlPoint->Coordinates[2] == nextControlPoint->Coordinates[2])) { vtkIdType p1 = points->InsertNextPoint(currentControlPoint->Coordinates[0], currentControlPoint->Coordinates[1], currentControlPoint->Coordinates[2]); vtkIdType p2 = points->InsertNextPoint(nextControlPoint->Coordinates[0], nextControlPoint->Coordinates[1], nextControlPoint->Coordinates[2]); //add the line between both contorlPoints lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } current++; next++; } if(inputContour->IsClosed(timestep)) { // If the contour is closed add a line from the last to the first control point mitk::ContourModel::VertexType* firstControlPoint = *(inputContour->IteratorBegin(timestep)); mitk::ContourModel::VertexType* lastControlPoint = *(--(inputContour->IteratorEnd(timestep))); if( lastControlPoint->Coordinates[0] != firstControlPoint->Coordinates[0] && lastControlPoint->Coordinates[1] != firstControlPoint->Coordinates[1] && lastControlPoint->Coordinates[2] != firstControlPoint->Coordinates[2]) { vtkIdType p2 = points->InsertNextPoint(lastControlPoint->Coordinates[0], lastControlPoint->Coordinates[1], lastControlPoint->Coordinates[2]); vtkIdType p1 = points->InsertNextPoint(firstControlPoint->Coordinates[0], firstControlPoint->Coordinates[1], firstControlPoint->Coordinates[2]); //add the line to the cellArray lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } } // Add the points to the dataset polyData->SetPoints(points); // Add the lines to the dataset polyData->SetLines(lines); } return polyData; } void mitk::ContourModelMapper3D::ApplyContourProperties(mitk::BaseRenderer* renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty ("color", renderer)); if(colorprop) { //set the color of the contour double red = colorprop->GetColor().GetRed(); double green = colorprop->GetColor().GetGreen(); double blue = colorprop->GetColor().GetBlue(); localStorage->m_Actor->GetProperty()->SetColor(red, green, blue); } } /*+++++++++++++++++++ LocalStorage part +++++++++++++++++++++++++*/ mitk::ContourModelMapper3D::LocalStorage* mitk::ContourModelMapper3D::GetLocalStorage(mitk::BaseRenderer* renderer) { return m_LSH.GetLocalStorage(renderer); } mitk::ContourModelMapper3D::LocalStorage::LocalStorage() { m_Mapper = vtkSmartPointer::New(); m_Actor = vtkSmartPointer::New(); m_OutlinePolyData = vtkSmartPointer::New(); m_TubeFilter = vtkSmartPointer::New(); //set the mapper for the actor m_Actor->SetMapper(m_Mapper); } void mitk::ContourModelMapper3D::SetDefaultProperties(mitk::DataNode* node, mitk::BaseRenderer* renderer, bool overwrite) { node->AddProperty( "color", ColorProperty::New(1.0,0.0,0.0), renderer, overwrite ); - node->AddProperty( "3D contour width", mitk::FloatProperty::New( 0.2 ), renderer, overwrite ); + node->AddProperty( "3D contour width", mitk::FloatProperty::New( 0.5 ), renderer, overwrite ); Superclass::SetDefaultProperties(node, renderer, overwrite); } diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index 5d11450bea..47bd8f2216 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,70 +1,75 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkComputeContourSetNormalsFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkCreateDistanceImageFromSurfaceFilter.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkImageToContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOverwriteDirectedPlaneImageFilter.cpp Algorithms/mitkOverwriteSliceImageFilter.cpp Algorithms/mitkReduceContourSetFilter.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkSegmentationSink.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkContourModelSource.cpp Algorithms/mitkContourModelToPointSetFilter.cpp Algorithms/mitkContourModelToSurfaceFilter.cpp Algorithms/mitkContourModelSubDivisionFilter.cpp +Algorithms/mitkImageToContourModelFilter.cpp +Algorithms/mitkImageToLiveWireContourFilter.cpp +Algorithms/mitkImageLiveWireContourModelFilter.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkSurfaceInterpolationController.cpp # DataManagement/mitkApplyDiffImageOperation.cpp DataManagement/mitkContour.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp DataManagement/mitkContourModel.cpp DataManagement/mitkContourElement.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkAutoSegmentationTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCalculateGrayValueStatisticsTool.cpp Interactions/mitkCalculateVolumetryTool.cpp Interactions/mitkContourInteractor.cpp Interactions/mitkContourTool.cpp Interactions/mitkCorrectorTool2D.cpp Interactions/mitkCreateSurfaceTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkExtrudedContourInteractor.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkRegionGrow3DTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSetRegionTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkContourModelInteractor.cpp +Interactions/mitkContourModelLiveWireInteractor.cpp +Interactions/mitkLiveWireTool2D.cpp IO/mitkContourModelIOFactory.cpp IO/mitkContourModelReader.cpp IO/mitkContourModelWriter.cpp IO/mitkContourModelWriterFactory.cpp Rendering/mitkContourMapper2D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp Rendering/mitkContourModelMapper2D.cpp Rendering/mitkContourModelMapper3D.cpp Rendering/mitkContourModelGLMapper2D.cpp ) diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/org_mitk_gui_qt_segmentation.dox b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/org_mitk_gui_qt_segmentation.dox index e087268bb7..5a7657ce68 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/org_mitk_gui_qt_segmentation.dox +++ b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/org_mitk_gui_qt_segmentation.dox @@ -1,292 +1,309 @@ /** \page org_mitk_views_segmentation The Segmentation Plugin \image html segmentation.png "Icon of the Plugin" Some of the features described below are closed source additions to the open source toolkit MITK and are not available in every application. Available sections: - \ref org_mitk_gui_qt_segmentationUserManualOverview - \ref org_mitk_gui_qt_segmentationUserManualTechnical - \ref org_mitk_gui_qt_segmentationUserManualImageSelection - \ref org_mitk_gui_qt_segmentationUserManualManualKringeling - \ref org_mitk_gui_qt_segmentationUserManualManualKringeling1 - \ref org_mitk_gui_qt_segmentationUserManualManualKringeling2 - \ref org_mitk_gui_qt_segmentationUserManualManualKringeling3 - \ref org_mitk_gui_qt_segmentationUserManualManualKringeling4 - \ref org_mitk_gui_qt_segmentationUserManualManualKringeling5 - \ref org_mitk_gui_qt_segmentationUserManualOrganSegmentation - \ref org_mitk_gui_qt_segmentationUserManualOrganSegmentation1 - \ref org_mitk_gui_qt_segmentationUserManualOrganSegmentation2 - \ref org_mitk_gui_qt_segmentationUserManualOrganSegmentation99 - \ref org_mitk_gui_qt_segmentationUserManualLesionSegmentation - \ref org_mitk_gui_qt_segmentationUserManualPostprocessing - \ref org_mitk_gui_qt_segmentationUserManualSurfaceMasking - \ref org_mitk_gui_qt_segmentationUserManualTechnicalDetail \section org_mitk_gui_qt_segmentationUserManualOverview Overview The Segmentation perspective allows you to create segmentations of anatomical and pathological structures in medical images of the human body. The perspective groups a number of tools which can be used for:
  • (semi-)automatic segmentation of organs on CT or MR image volumes
  • semi-automatic segmentation of lesions such as enlarged lymph nodes or tumors
  • manual segmentation of any structures you might want to delineate
\image html org_mitk_gui_qt_segmentationIMGapplication.png Segmentation perspective consisting of the Data Manager view and the Segmentation view If you wonder what segmentations are good for, we shortly revisit the concept of a segmentation here. A CT or MR image is made up of volume of physical measurements (volume elements are called voxels). In CT images, for example, the gray value of each voxel corresponds to the mass absorbtion coefficient for X-rays in this voxel, which is similar in many %parts of the human body. The gray value does not contain any further information, so the computer does not know whether a given voxel is part of the body or the background, nor can it tell a brain from a liver. However, the distinction between a foreground and a background structure is required when:
  • you want to know the volume of a given organ (the computer needs to know which %parts of the image belong to this organ)
  • you want to create 3D polygon visualizations (the computer needs to know the surfaces of structures that should be drawn)
  • as a necessary pre-processing step for therapy planning, therapy support, and therapy monitoring
Creating this distinction between foreground and background is called segmentation. The Segmentation perspective of the MITK Workbench uses a voxel based approach to segmentation, i.e. each voxel of an image must be completely assigned to either foreground or background. This is in contrast to some other applications which might use an approach based on contours, where the border of a structure might cut a voxel into two %parts. The remainder of this document will summarize the features of the Segmentation perspective and how they are used. \section org_mitk_gui_qt_segmentationUserManualTechnical Technical Issues The Segmentation perspective makes a number of assumptions. To know what this view can be used for, it will help you to know that:
  • Images must be 2D, 3D, or 3D+t
  • Images must be single-values, i.e. CT, MRI or "normal" ultrasound. Images from color doppler or photographic (RGB) images are not supported
  • Segmentations are handled as binary images of the same extent as the original image
\section org_mitk_gui_qt_segmentationUserManualImageSelection Image Selection The Segmentation perspective makes use of the Data Manager view to give you an overview of all images and segmentations. \image html org_mitk_gui_qt_segmentationIMGselection.png Data Manager is used for selecting the current segmentation. The reference image is selected in the drop down box of the control area. To select the reference image (e.g. the original CT/MR image) use the drop down box in the control area of the Segmentation view. The segmentation image selected in the Data Manager is displayed below the drop down box. If no segmentation image exists or none is selected create a new segmentation image by using the "New segmentation" button. Some items of the graphical user interface might be disabled when no image is selected. In any case, the application will give you hints if a selection is needed. \section org_mitk_gui_qt_segmentationUserManualManualKringeling Manual Contouring With manual contouring you define which voxels are part of the segmentation and which are not. This allows you to create segmentations of any structeres that you may find in an image, even if they are not part of the human body. You might also use manual contouring to correct segmentations that result from sub-optimal automatic methods. The drawback of manual contouring is that you might need to define contours on many 2D slices. However, this is moderated by the interpolation feature, which will make suggestions for a segmentation. \subsection org_mitk_gui_qt_segmentationUserManualManualKringeling1 Creating New Segmentations Unless you want to edit existing segmentations, you have to create a new, empty segmentation before you can edit it. To do so, click the "New manual segmentation" button. Input fields will appear where you can choose a name for the new segmentation and a color for its display. Click the checkmark button to confirm or the X button to cancel the new segmentation. Notice that the input field suggests names once you %start typing and that it also suggests colors for known organ names. If you use names that are not yet known to the application, it will automatically remember these names and consider them the next time you create a new segmentation. Once you created a new segmentation, you can notice a new item with the "binary mask" icon in the Data Manager tree view. This item is automatically selected for you, allowing you to %start editing the new segmentation right away. \subsection org_mitk_gui_qt_segmentationUserManualManualKringeling2 Selecting Segmentations for Editing As you might want to have segmentations of multiple structures in a single patient image, the application needs to know which of them to use for editing. You select a segmenation by clicking it in the tree view of Data Manager. Note that segmentations are usually displayed as sub-items of "their" patient image. In the rare case, where you need to edit a segmentation that is not displayed as a a sub-item, you can click both the original image AND the segmentation while holding down CTRL or for Mac OS X the CMD on the keyboard. When a selection is made, the Segmentation View will hide all but the selected segmentation and the corresponding original image. When there are multiple segmentations, the unselected ones will remain in the Data Manager, you can make them visible at any time by selecting them. \subsection org_mitk_gui_qt_segmentationUserManualManualKringeling3 Selecting Editing Tools If you are familiar with the MITK Workbench, you know that clicking and moving the mouse in any of the 2D render windows will move around the crosshair that defines what part of the image is displayed. This behavior is disabled while any of the manual segmentation tools are active -- otherwise you might have a hard time concentrating on the contour you are drawing. To %start using one of the editing tools, click its button the the displayed toolbox. The selected editing tool will be active and its corresponding button will stay pressed until you click the button again. Selecting a different tool also deactivates the previous one. If you have to delineate a lot of images, you should try using shortcuts to switch tools. Just hit the first letter of each tool to activate it (A for Add, S for Subtract, etc.). \subsection org_mitk_gui_qt_segmentationUserManualManualKringeling4 Using Editing Tools All of the editing tools work by the same principle: you use the mouse (left button) to click anywhere in a 2D window (any of the orientations axial, sagittal, or frontal), move the mouse while holding the mouse button and release to finish the editing action. Multi-step undo and redo is fully supported by all editing tools. Use the application-wide undo button in the toolbar to revert erroneous %actions. \image html org_mitk_gui_qt_segmentationIMGiconAddSubtract.png Add and Subtract Tools Use the left mouse button to draw a closed contour. When releasing the mouse button, the contour will be added (Add tool) to or removed from (Subtract tool) the current segmentation. Hold down the CTRL / CMD key to invert the operation (this will switch tools temporarily to allow for quick corrections). \image html org_mitk_gui_qt_segmentationIMGiconPaintWipe.png Paint and Wipe Tools Use the slider below the toolbox to change the radius of these round paintbrush tools. Move the mouse in any 2D window and press the left button to draw or erase pixels. As the Add/Subtract tools, holding CTRL / CMD while drawing will invert the current tool's behavior. \image html org_mitk_gui_qt_segmentationIMGiconRegionGrowing.png Region Growing Tool Click at one point in a 2D slice widget to add an image region to the segmentation with the region growing tool. Moving up the cursor while holding the left mouse button widens the range for the included grey values; moving it down narrows it. When working on an image with a high range of grey values, the selection range can be influenced more strongly by moving the cursor at higher velocity. Region Growing selects all pixels around the mouse cursor that have a similar gray value as the pixel below the mouse cursor. This enables you to quickly create segmentations of structures that have a good contrast to surrounding tissue, e.g. the lungs. The tool will select more or less pixels (corresponding to a changing gray value interval width) when you move the mouse up or down while holding down the left mouse button. A common issue with region growing is the so called "leakage" which happens when the structure of interest is connected to other pixels, of similar gray values, through a narrow "bridge" at the border of the structure. The Region Growing tool comes with a "leakage detection/removal" feature. If leakage happens, you can left-click into the leakage region and the tool will try to automatically remove this region (see illustration below). \image html org_mitk_gui_qt_segmentationIMGleakage.png Leakage correction feature of the Region Growing tool
\image html org_mitk_gui_qt_segmentationIMGiconCorrection.png Correction Tool You do not have to draw a closed contour to use the Correction tool and do not need to switch between the Add and Substract tool to perform small corrective changes. The following figure shows the usage of this tool:
  • if the user draws a line which %starts and ends outside the segmenation AND it intersects no other segmentation the endpoints of the line are connected and the resulting contour is filled
  • if the user draws a line which %starts and ends outside the segmenation a part of it is cut off (left image)
  • if the line is drawn fully inside the segmentation the marked region is added to the segmentation (right image)
\image html org_mitk_gui_qt_segmentationIMGcorrectionActions.png %Actions of the Correction tool illustrated.
\image html org_mitk_gui_qt_segmentationIMGiconFill.png Fill Tool Left-click inside a segmentation with holes to completely fill all holes. \image html org_mitk_gui_qt_segmentationIMGiconErase.png Erase Tool This tool removes a connected part of pixels that form a segmentation. You may use it to remove so called islands (see picture) or to clear a whole slice at once (hold CTRL while clicking). +\image html org_mitk_gui_qt_segmentationIMGiconLiveWire.png LiveWire Tool + +The LiveWire Tool acts as a magnetic lasso with a contour snapping to edges of objects. + +\image html org_mitk_gui_qt_segmentationIMGLiveWireUsage.png Steps for using LiveWire Tool + +
    +
  • (1) To start the Tool you have to double click near the edge of the object you want to segment. The initial anchor point will snap to the edge within a 3x3 region. +
  • (2) Move the mouse. You don't have trace the edge of the object. The contour will automatically snap to it. +
  • (3) To fix a segment you can set anchor points by single left mouse button click. +
  • (4) Go on with moving the mouse and setting anchor points. +
  • (5) To close the contour double click on the initial anchor point. +
  • (6) After closing the contour can be edited by moving, inserting and deleting anchor points. +
+ +The contour will be transfered to its binary image representation by deactivating the tool. + \subsection org_mitk_gui_qt_segmentationUserManualManualKringeling5 Interpolation Creating segmentations for modern CT volumes is very time-consuming, because structures of interest can easily cover a range of 50 or more slices. The Manual Segmentation View offers two helpful features for these cases:
  • 3D Interpolation
  • 2D Interpolation

The 3D interpolation is activated by default when using the manual segmentation tools. That means if you start contouring, from the second contour onwards, the surface of the segmented area will be interpolated based on the given contour information. The interpolation works with all available manual tools. Please note that this is currently a pure mathematical interpolation, i.e. image intensity information is not taken into account. With each further contour the interpolation result will be improved, but the more contours you provide the longer the recalculation will take. To achieve an optimal interpolation result and in this way a most accurate segmentation you should try to describe the surface with sparse contours by segmenting in arbitrary oriented planes. The 3D interpolation is not meant to be used for parallel slice-wise segmentation. \image html org_mitk_gui_qt_segmentation3DInterpolationWrongRight.png 3D Interpolation HowTo You can accept the interpolation result by clicking the "Accept" - button below the tool buttons. In this case the 3D interpolation will be deactivated automatically so that the result can be postprocessed without any interpolation running in background. During recalculation the interpolated surface is blinking yellow/white. When the interpolation has finished the surface is shown yellow with a small opacity. Additional to the surface, black contours are shown in the 3D render window. They mark the positions of all the drawn contours which were used for the interpolation. You can navigate between the drawn contours by clicking on the „Position“ - Nodes in the datamanager which are located below the selected segmentation. If you don't want to see these nodes just unckeck the „Show Position Nodes“ Checkbox and these nodes will be hidden. If you want to delete a drawn contour we recommend to use the Erase-Tool since Redo/Undo is not yet working for 3D interpolation.
The 2D Interpolation creates suggestions for a segmentation whenever you have a slice that
  • has got neighboring slices with segmentations (these do not need to be direct neighbors but could also be a couple of slices away) AND
  • is completely clear of a manual segmentation -- i.e. there will be no suggestion if there is even only a single pixel of segmentation in the current slice.
Interpolated suggestions are displayed in a different way than manual segmentations are, until you "accept" them as part of the segmentation. To accept single slices, click the "Accept" button below the toolbox. If you have segmented a whole organ in every-x-slice, you may also review the interpolations and then accept all of them at once by clicking "... all slices". \section org_mitk_gui_qt_segmentationUserManualOrganSegmentation Organ Segmentation \note This feature is only available in our 3M3 Demo Application (http://www.mint-medical.de/productssolutions/mitk3m3/mitk3m3/#downloads) but not in the open source part of MITK The manual contouring described above is a fallback option that will work for any kind of images and structures of interest. However, manual contouring is very time-consuming and tedious. This is why a major part of image analysis research is working towards automatic segmentation methods. The Segmentation View comprises a number of easy-to-use tools for segmentation of CT images (Liver) and MR image (left ventricle and wall, left and right lung). \subsection org_mitk_gui_qt_segmentationUserManualOrganSegmentation1 Liver on CT Images On CT image volumes, preferrably with a contrast agent in the portal venous phase, the Liver tool will fully automatically analyze and segment the image. All you have to do is to load and select the image, then click the "Liver" button. During the process, which takes a minute or two, you will get visual progress feedback by means of a contour that moves closer and closer to the real liver boundaries. \subsection org_mitk_gui_qt_segmentationUserManualOrganSegmentation2 Heart, Lung, and Hippocampus on MRI While liver segmentation is performed fully automatic, the following tools for segmentation of the heart, the lungs, and the hippocampus need a minimum amount of guidance. Click one of the buttons on the "Organ segmentation" page to add an average %model of the respective organ to the image. This %model can be dragged to the right position by using the left mouse button while holding down the CTRL key. You can also use CTRL + middle mouse button to rotate or CTRL + right mouse button to scale the %model. Before starting the automatic segmentation process by clicking the "Start segmentation" button, try placing the %model closely to the organ in the MR image (in most cases, you do not need to rotate or scale the %model). During the segmentation process, a green contour that moves closer and closer to the real liver boundaries will provide you with visual feedback of the segmentation progress. The algorithms used for segmentation of the heart and lung are method which need training by a number of example images. They will not work well with other kind of images, so here is a list of the image types that were used for training:
  • Hippocampus segmentation: T1-weighted MR images, 1.5 Tesla scanner (Magnetom Vision, Siemens Medical Solutions), 1.0 mm isotropic resolution
  • Heart: Left ventricle inner segmentation (LV Model): MRI; velocity encoded cine (VEC-cine) MRI sequence; trained on systole and diastole
  • Heart: Left ventricular wall segmentation (LV Inner Wall, LV Outer Wall): 4D MRI; short axis 12 slice spin lock sequence(SA_12_sl); trained on whole heart cycle
  • Lung segmentation: 3D and 4D MRI; works best on FLASH3D and TWIST4D sequences
\subsection org_mitk_gui_qt_segmentationUserManualOrganSegmentation99 Other Organs As mentioned in the Heart/Lung section, most of the underlying methods are based on "training". The basic algorithm is versatile and can be applied on all kinds of segmentation problems where the structure of interest is topologically like a sphere (and not like a torus etc.). If you are interested in other organs than those offered by the current version of the Segmentation view, please contact our research team. \section org_mitk_gui_qt_segmentationUserManualLesionSegmentation Lesion Segmentation \note This feature is only available in our 3M3 Demo Application (http://www.mint-medical.de/productssolutions/mitk3m3/mitk3m3/#downloads) but not in the open source part of MITK Lesion segmentation is a little different from organ segmentation. Since lesions are not part of the healthy body, they sometimes have a diffused border, and are often found in varying places all over the body. The tools in this section offer efficient ways to create 3D segmentations of such lesions. The Segmentation View currently offers supoprt for enlarged lymph nodes. To segment an enlarged lymph node, find a more or less central slice of it, activate the "Lymph Node" tool and draw a rough contour on the inside of the lymph node. When releaseing the mouse button, a segmentation algorithm is started in a background task. The result will become visible after a couple of seconds, but you do not have to wait for it. If you need to segment several lymph nodes, you can continue to inspect the image right after closing the drawn contour. If the lymph node segmentation is not to your content, you can select the "Lymph Node Correction" tool and drag %parts of the lymph node surface towards the right position (works in 3D, not slice-by-slice). This kind of correction helps in many cases. If nothing else helps, you can still use the pure manual tools as a fallback. \section org_mitk_gui_qt_segmentationUserManualPostprocessing Things you can do with segmentations As mentioned in the introduction, segmentations are never an end in themselves. Consequently, the Segmentation view adds a couple of "post-processing" %actions to the Data Manager. These %actions are accessible through the context-menu of segmentations in Data Manager's list view \image html org_mitk_gui_qt_segmentationIMGDataManagerContextMenu.png Context menu items for segmentations.
  • Create polygon %model applies the marching cubes algorithms to the segmentation. This polygon %model can be used for visualization in 3D or other things such as stereolithography (3D printing).
  • Create smoothed polygon %model uses smoothing in addition to the marching cubes algorithms, which creates models that do not follow the exact outlines of the segmentation, but look smoother.
  • Statistics goes through all the voxels in the patient image that are part of the segmentation and calculates some statistical measures (minumum, maximum, median, histogram, etc.). Note that the statistics are ALWAYS calculated for the parent element of the segmentation as shown in Data Manager.
  • Autocrop can save memory. Manual segmentations have the same extent as the patient image, even if the segmentation comprises only a small sub-volume. This invisible and meaningless margin is removed by autocropping.
\section org_mitk_gui_qt_segmentationUserManualSurfaceMasking Surface Masking You can use the surface masking tool to create binary images from a surface which is used used as a mask on an image. This task is demonstrated below: \image html segmentationFromSurfaceBefore.png Load an image and a surface. Select the image and the surface in the corresponding drop-down boxes (both are selected automatically if there is just one image and one surface) \image html segmentationFromSurfaceAfter.png Create segmentation from surface After clicking "Create segmentation from surface" the newly created binary image is inserted in the DataManager and can be used for further processing \section org_mitk_gui_qt_segmentationUserManualTechnicalDetail Technical Information for Developers For technical specifications see \subpage QmitkSegmentationTechnicalPage and for information on the extensions of the tools system \subpage toolextensions . */ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/org_mitk_gui_qt_segmentationIMGLiveWireUsage.PNG b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/org_mitk_gui_qt_segmentationIMGLiveWireUsage.PNG new file mode 100644 index 0000000000..fb667cdc1c Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/org_mitk_gui_qt_segmentationIMGLiveWireUsage.PNG differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/org_mitk_gui_qt_segmentationIMGiconLiveWire.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/org_mitk_gui_qt_segmentationIMGiconLiveWire.png new file mode 100644 index 0000000000..befc1f39bd Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/org_mitk_gui_qt_segmentationIMGiconLiveWire.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index e0410c695d..ab6fb31ecd 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1252 +1,1251 @@ /*=================================================================== 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 "mitkDataNodeObject.h" #include "mitkProperties.h" #include "mitkSegTool2D.h" #include "mitkGlobalInteraction.h" #include "QmitkStdMultiWidget.h" #include "QmitkNewSegmentationDialog.h" #include #include #include "QmitkSegmentationView.h" #include "QmitkSegmentationPostProcessing.h" #include "QmitkSegmentationOrganNamesHandling.cpp" #include #include //For Segmentation in rotated slices //TODO clean up includes #include "mitkVtkResliceInterpolationProperty.h" #include "mitkPlanarCircle.h" #include "mitkGetModuleContext.h" #include "mitkModule.h" #include "mitkModuleRegistry.h" #include "mitkSegmentationObjectFactory.h" const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; // public methods QmitkSegmentationView::QmitkSegmentationView() :m_Parent(NULL) ,m_Controls(NULL) ,m_MultiWidget(NULL) ,m_RenderingManagerObserverTag(0) ,m_DataSelectionChanged(false) { RegisterSegmentationObjectFactory(); } QmitkSegmentationView::~QmitkSegmentationView() { // delete m_PostProcessing; delete m_Controls; } void QmitkSegmentationView::NewNodesGenerated() { // ForceDisplayPreferencesUponAllImages(); } void QmitkSegmentationView::NewNodeObjectsGenerated(mitk::ToolManager::DataVectorType* nodes) { if (!nodes) return; mitk::ToolManager* toolManager = m_Controls->m_ManualToolSelectionBox->GetToolManager(); if (!toolManager) return; for (mitk::ToolManager::DataVectorType::iterator iter = nodes->begin(); iter != nodes->end(); ++iter) { this->FireNodeSelected( *iter ); // only last iteration meaningful, multiple generated objects are not taken into account here } } void QmitkSegmentationView::Visible() { if (m_DataSelectionChanged) { this->OnSelectionChanged(this->GetDataManagerSelection()); } } void QmitkSegmentationView::Activated() { // should be moved to ::BecomesVisible() or similar if( m_Controls ) { m_Controls->m_ManualToolSelectionBox->setEnabled( true ); m_Controls->m_OrganToolSelectionBox->setEnabled( true ); m_Controls->m_LesionToolSelectionBox->setEnabled( true ); m_Controls->m_SlicesInterpolator->Enable3DInterpolation( m_Controls->widgetStack->currentWidget() == m_Controls->pageManual ); //TODO Remove Observer itk::ReceptorMemberCommand::Pointer command1 = itk::ReceptorMemberCommand::New(); command1->SetCallbackFunction( this, &QmitkSegmentationView::RenderingManagerReinitialized ); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver( mitk::RenderingManagerViewsInitializedEvent(), command1 ); //Adding observers for node visibility to existing segmentations mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); mitk::NodePredicateProperty::Pointer isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateAnd::Pointer isSegmentation = mitk::NodePredicateAnd::New( isImage, isBinary ); mitk::DataStorage::SetOfObjects::ConstPointer segmentations = this->GetDefaultDataStorage()->GetSubset( isSegmentation ); mitk::DataStorage::SetOfObjects::ConstPointer image = this->GetDefaultDataStorage()->GetSubset( isImage ); if (!image->empty()) { OnSelectionChanged(*image->begin()); } for ( mitk::DataStorage::SetOfObjects::const_iterator iter = segmentations->begin(); iter != segmentations->end(); ++iter) { mitk::DataNode* node = *iter; itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::OnWorkingNodeVisibilityChanged); m_WorkingDataObserverTags.insert( std::pair( node, node->GetProperty("visible")->AddObserver( itk::ModifiedEvent(), command ) ) ); } if(segmentations->Size() > 0) { FireNodeSelected(segmentations->ElementAt(0)); segmentations->ElementAt(0)->GetProperty("visible")->Modified(); } } } void QmitkSegmentationView::Deactivated() { if( m_Controls ) { mitk::RenderingManager::GetInstance()->RemoveObserver( m_RenderingManagerObserverTag ); m_Controls->m_ManualToolSelectionBox->setEnabled( false ); //deactivate all tools m_Controls->m_ManualToolSelectionBox->GetToolManager()->ActivateTool(-1); m_Controls->m_OrganToolSelectionBox->setEnabled( false ); m_Controls->m_LesionToolSelectionBox->setEnabled( false ); m_Controls->m_SlicesInterpolator->EnableInterpolation( false ); //Removing all observers for ( NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter ) { (*dataIter).first->GetProperty("visible")->RemoveObserver( (*dataIter).second ); } m_WorkingDataObserverTags.clear(); if (m_MultiWidget) { mitk::SlicesCoordinator *coordinator = m_MultiWidget->GetSlicesRotator(); if (coordinator) coordinator->RemoveObserver(m_SlicesRotationObserverTag1); coordinator = m_MultiWidget->GetSlicesSwiveller(); if (coordinator) coordinator->RemoveObserver(m_SlicesRotationObserverTag2); } // gets the context of the "Mitk" (Core) module (always has id 1) // TODO Workaround until CTL plugincontext is available mitk::ModuleContext* context = mitk::ModuleRegistry::GetModule(1)->GetModuleContext(); // Workaround end mitk::ServiceReference serviceRef = context->GetServiceReference(); //mitk::ServiceReference serviceRef = mitk::GetModuleContext()->GetServiceReference(); mitk::PlanePositionManagerService* service = dynamic_cast(context->GetService(serviceRef)); service->RemoveAllPlanePositions(); } } void QmitkSegmentationView::StdMultiWidgetAvailable( QmitkStdMultiWidget& stdMultiWidget ) { SetMultiWidget(&stdMultiWidget); } void QmitkSegmentationView::StdMultiWidgetNotAvailable() { SetMultiWidget(NULL); } void QmitkSegmentationView::StdMultiWidgetClosed( QmitkStdMultiWidget& /*stdMultiWidget*/ ) { SetMultiWidget(NULL); } void QmitkSegmentationView::SetMultiWidget(QmitkStdMultiWidget* multiWidget) { if (m_MultiWidget) { mitk::SlicesCoordinator* coordinator = m_MultiWidget->GetSlicesRotator(); if (coordinator) { coordinator->RemoveObserver( m_SlicesRotationObserverTag1 ); } coordinator = m_MultiWidget->GetSlicesSwiveller(); if (coordinator) { coordinator->RemoveObserver( m_SlicesRotationObserverTag2 ); } } // save the current multiwidget as the working widget m_MultiWidget = multiWidget; //TODO Remove Observers if (m_MultiWidget) { mitk::SlicesCoordinator* coordinator = m_MultiWidget->GetSlicesRotator(); if (coordinator) { itk::ReceptorMemberCommand::Pointer command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction( this, &QmitkSegmentationView::SliceRotation ); m_SlicesRotationObserverTag1 = coordinator->AddObserver( mitk::SliceRotationEvent(), command2 ); } coordinator = m_MultiWidget->GetSlicesSwiveller(); if (coordinator) { itk::ReceptorMemberCommand::Pointer command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction( this, &QmitkSegmentationView::SliceRotation ); m_SlicesRotationObserverTag2 = coordinator->AddObserver( mitk::SliceRotationEvent(), command2 ); } } //TODO End Remove Observers if (m_Parent) { m_Parent->setEnabled(m_MultiWidget); } // tell the interpolation about toolmanager and multiwidget (and data storage) if (m_Controls && m_MultiWidget) { mitk::ToolManager* toolManager = m_Controls->m_ManualToolSelectionBox->GetToolManager(); m_Controls->m_SlicesInterpolator->SetDataStorage( *(this->GetDefaultDataStorage())); m_Controls->m_SlicesInterpolator->Initialize( toolManager, m_MultiWidget ); } } void QmitkSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences*) { ForceDisplayPreferencesUponAllImages(); } //TODO remove function void QmitkSegmentationView::RenderingManagerReinitialized(const itk::EventObject&) { CheckImageAlignment(); } //TODO remove function void QmitkSegmentationView::SliceRotation(const itk::EventObject&) { CheckImageAlignment(); } // protected slots void QmitkSegmentationView::CreateNewSegmentation() { mitk::DataNode::Pointer node = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0); if (node.IsNotNull()) { mitk::Image::Pointer image = dynamic_cast( node->GetData() ); if (image.IsNotNull()) { if (image->GetDimension()>1) { // ask about the name and organ type of the new segmentation QmitkNewSegmentationDialog* dialog = new QmitkNewSegmentationDialog( m_Parent ); // needs a QWidget as parent, "this" is not QWidget QString storedList = QString::fromStdString( this->GetPreferences()->GetByteArray("Organ-Color-List","") ); QStringList organColors; if (storedList.isEmpty()) { organColors = GetDefaultOrganColorString(); } else { /* a couple of examples of how organ names are stored: a simple item is built up like 'name#AABBCC' where #AABBCC is the hexadecimal notation of a color as known from HTML items are stored separated by ';' this makes it necessary to escape occurrences of ';' in name. otherwise the string "hugo;ypsilon#AABBCC;eugen#AABBCC" could not be parsed as two organs but we would get "hugo" and "ypsilon#AABBCC" and "eugen#AABBCC" so the organ name "hugo;ypsilon" is stored as "hugo\;ypsilon" and must be unescaped after loading the following lines could be one split with Perl's negative lookbehind */ // recover string list from BlueBerry view's preferences QString storedString = QString::fromStdString( this->GetPreferences()->GetByteArray("Organ-Color-List","") ); MITK_DEBUG << "storedString: " << storedString.toStdString(); // match a string consisting of any number of repetitions of either "anything but ;" or "\;". This matches everything until the next unescaped ';' QRegExp onePart("(?:[^;]|\\\\;)*"); MITK_DEBUG << "matching " << onePart.pattern().toStdString(); int count = 0; int pos = 0; while( (pos = onePart.indexIn( storedString, pos )) != -1 ) { ++count; int length = onePart.matchedLength(); if (length == 0) break; QString matchedString = storedString.mid(pos, length); MITK_DEBUG << " Captured length " << length << ": " << matchedString.toStdString(); pos += length + 1; // skip separating ';' // unescape possible occurrences of '\;' in the string matchedString.replace("\\;", ";"); // add matched string part to output list organColors << matchedString; } MITK_DEBUG << "Captured " << count << " organ name/colors"; } dialog->SetSuggestionList( organColors ); int dialogReturnValue = dialog->exec(); if ( dialogReturnValue == QDialog::Rejected ) return; // user clicked cancel or pressed Esc or something similar // ask the user about an organ type and name, add this information to the image's (!) propertylist // create a new image of the same dimensions and smallest possible pixel type mitk::ToolManager* toolManager = m_Controls->m_ManualToolSelectionBox->GetToolManager(); mitk::Tool* firstTool = toolManager->GetToolById(0); if (firstTool) { try { mitk::DataNode::Pointer emptySegmentation = firstTool->CreateEmptySegmentationNode( image, dialog->GetSegmentationName().toStdString(), dialog->GetColor() ); //Here we change the reslice interpolation mode for a segmentation, so that contours in rotated slice can be shown correctly emptySegmentation->SetProperty( "reslice interpolation", mitk::VtkResliceInterpolationProperty::New(VTK_RESLICE_NEAREST) ); // initialize showVolume to false to prevent recalculating the volume while working on the segmentation emptySegmentation->SetProperty( "showVolume", mitk::BoolProperty::New( false ) ); if (!emptySegmentation) return; // could be aborted by user UpdateOrganList( organColors, dialog->GetSegmentationName(), dialog->GetColor() ); /* escape ';' here (replace by '\;'), see longer comment above */ std::string stringForStorage = organColors.replaceInStrings(";","\\;").join(";").toStdString(); MITK_DEBUG << "Will store: " << stringForStorage; this->GetPreferences()->PutByteArray("Organ-Color-List", stringForStorage ); this->GetPreferences()->Flush(); if(m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetWorkingData(0)) { m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetWorkingData(0)->SetSelected(false); } emptySegmentation->SetSelected(true); this->GetDefaultDataStorage()->Add( emptySegmentation, node ); // add as a child, because the segmentation "derives" from the original this->FireNodeSelected( emptySegmentation ); this->OnSelectionChanged( emptySegmentation ); this->SetToolManagerSelection(node, emptySegmentation); } catch (std::bad_alloc) { QMessageBox::warning(NULL,"Create new segmentation","Could not allocate memory for new segmentation"); } } } else { QMessageBox::information(NULL,"Segmentation","Segmentation is currently not supported for 2D images"); } } } else { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a patient image is selected..."; } } void QmitkSegmentationView::OnWorkingNodeVisibilityChanged(/*const itk::Object* caller, const itk::EventObject& e*/) { if (!m_Parent || !m_Parent->isVisible()) return; // The new selection behaviour is: // // When clicking on the checkbox of a segmentation the node will e selected and its reference node either // The previous selected segmentation (if there is one) will be deselected. Additionally a reinit on the // selected segmenation will be performed. // If more than one segmentation is selected the tools will be disabled. if (!m_Controls) return; // might happen on initialization (preferences loaded) mitk::DataNode::Pointer referenceDataNew; mitk::DataNode::Pointer workingData; bool workingNodeIsVisible (true); unsigned int numberOfSelectedSegmentations (0); // iterate all images mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDefaultDataStorage()->GetSubset( isImage ); for ( mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { mitk::DataNode* node = *iter; // apply display preferences ApplyDisplayOptions(node); bool isSegmentation(false); node->GetBoolProperty("binary", isSegmentation); if (node->IsSelected() && isSegmentation) { workingNodeIsVisible = node->IsVisible(mitk::BaseRenderer::GetInstance( mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget1"))); if (!workingNodeIsVisible) return; numberOfSelectedSegmentations++; workingData = node; if (this->GetDefaultDataStorage()->GetSources(node)->Size() != 0) { referenceDataNew = this->GetDefaultDataStorage()->GetSources(node)->ElementAt(0); } bool isBinary(false); //Find topmost source or first source which is no binary image while (referenceDataNew.IsNotNull() && this->GetDefaultDataStorage()->GetSources(referenceDataNew)->Size() != 0) { referenceDataNew = this->GetDefaultDataStorage()->GetSources(referenceDataNew)->ElementAt(0); referenceDataNew->GetBoolProperty("binary",isBinary); if (!isBinary) break; } if (workingNodeIsVisible && referenceDataNew) { //Since the binary property of a segmentation can be set to false and afterwards you can create a new segmentation out of it //->could lead to a deadloop NodeTagMapType::iterator searchIter = m_WorkingDataObserverTags.find( referenceDataNew ); if ( searchIter != m_WorkingDataObserverTags.end()) { referenceDataNew->GetProperty("visible")->RemoveObserver( (*searchIter).second ); } referenceDataNew->SetVisibility(true); } //set comboBox to reference image disconnect( m_Controls->refImageSelector, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnComboBoxSelectionChanged( const mitk::DataNode* ) ) ); m_Controls->refImageSelector->setCurrentIndex( m_Controls->refImageSelector->Find(referenceDataNew) ); connect( m_Controls->refImageSelector, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnComboBoxSelectionChanged( const mitk::DataNode* ) ) ); continue; } if (workingData.IsNull() || (workingNodeIsVisible && node != referenceDataNew)) { node->SetVisibility((false)); } } if(numberOfSelectedSegmentations == 1) SetToolManagerSelection(referenceDataNew, workingData); mitk::DataStorage::SetOfObjects::Pointer temp = mitk::DataStorage::SetOfObjects::New(); temp->InsertElement(0,workingData); mitk::TimeSlicedGeometry::Pointer bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(temp); // Reinit current node ForceDisplayPreferencesUponAllImages(); // initialize the views to the bounding geometry /*mitk::RenderingManager::GetInstance()->InitializeViews(bounds); mitk::RenderingManager::GetInstance()->RequestUpdateAll();*/ } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { bool isSeg(false); bool isHelperObject(false); node->GetBoolProperty("helper object", isHelperObject); node->GetBoolProperty("binary", isSeg); mitk::Image* image = dynamic_cast(node->GetData()); if(isSeg && !isHelperObject && image) { mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations(node, mitk::NodePredicateProperty::New("isContourMarker" , mitk::BoolProperty::New(true))); // gets the context of the "Mitk" (Core) module (always has id 1) // TODO Workaround until CTL plugincontext is available mitk::ModuleContext* context = mitk::ModuleRegistry::GetModule(1)->GetModuleContext(); // Workaround end mitk::ServiceReference serviceRef = context->GetServiceReference(); //mitk::ServiceReference serviceRef = mitk::GetModuleContext()->GetServiceReference(); mitk::PlanePositionManagerService* service = dynamic_cast(context->GetService(serviceRef)); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t+1).c_str())-1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } mitk::DataNode* tempNode = const_cast(node); node->GetProperty("visible")->RemoveObserver( m_WorkingDataObserverTags[tempNode] ); m_WorkingDataObserverTags.erase(tempNode); mitk::SurfaceInterpolationController::GetInstance()->RemoveSegmentationFromContourList(image); } if((m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0) == node)|| (m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetWorkingData(0) == node)) { //as we don't know which node was actually remove e.g. our reference node, disable 'New Segmentation' button. //consider the case that there is no more image in the datastorage this->SetToolManagerSelection(NULL, NULL); } } void QmitkSegmentationView::CreateSegmentationFromSurface() { mitk::DataNode::Pointer surfaceNode = m_Controls->MaskSurfaces->GetSelectedNode(); mitk::Surface::Pointer surface(0); if(surfaceNode.IsNotNull()) surface = dynamic_cast ( surfaceNode->GetData() ); if(surface.IsNull()) { this->HandleException( "No surface selected.", m_Parent, true); return; } mitk::DataNode::Pointer imageNode = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0); mitk::Image::Pointer image(0); if (imageNode.IsNotNull()) image = dynamic_cast( imageNode->GetData() ); if(image.IsNull()) { this->HandleException( "No image selected.", m_Parent, true); return; } mitk::SurfaceToImageFilter::Pointer s2iFilter = mitk::SurfaceToImageFilter::New(); s2iFilter->MakeOutputBinaryOn(); s2iFilter->SetInput(surface); s2iFilter->SetImage(image); s2iFilter->Update(); mitk::DataNode::Pointer resultNode = mitk::DataNode::New(); std::string nameOfResultImage = imageNode->GetName(); nameOfResultImage.append(surfaceNode->GetName()); resultNode->SetProperty("name", mitk::StringProperty::New(nameOfResultImage) ); resultNode->SetProperty("binary", mitk::BoolProperty::New(true) ); resultNode->SetData( s2iFilter->GetOutput() ); this->GetDataStorage()->Add(resultNode, imageNode); } void QmitkSegmentationView::ToolboxStackPageChanged(int id) { // interpolation only with manual tools visible m_Controls->m_SlicesInterpolator->EnableInterpolation( id == 0 ); if( id == 0 ) { mitk::DataNode::Pointer workingData = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetWorkingData(0); if( workingData.IsNotNull() ) { m_Controls->lblSegmentation->setText( workingData->GetName().c_str() ); m_Controls->lblSegImage->show(); m_Controls->lblSegmentation->show(); } } else { m_Controls->lblSegImage->hide(); m_Controls->lblSegmentation->hide(); } // this is just a workaround, should be removed when all tools support 3D+t if (id==2) // lesions { mitk::DataNode::Pointer node = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0); if (node.IsNotNull()) { mitk::Image::Pointer image = dynamic_cast( node->GetData() ); if (image.IsNotNull()) { if (image->GetDimension()>3) { m_Controls->widgetStack->setCurrentIndex(0); QMessageBox::information(NULL,"Segmentation","Lesion segmentation is currently not supported for 4D images"); } } } } } // protected void QmitkSegmentationView::OnComboBoxSelectionChanged( const mitk::DataNode* node ) { mitk::DataNode* selectedNode = const_cast(node); if( selectedNode != NULL ) { m_Controls->refImageSelector->show(); m_Controls->lblReferenceImageSelectionWarning->hide(); bool isBinary(false); selectedNode->GetBoolProperty("binary", isBinary); if ( isBinary ) { FireNodeSelected(selectedNode); selectedNode->SetVisibility(true); } else if (node != m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0)) { if (m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0)) m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0)->SetVisibility(false); if (m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetWorkingData(0)) { m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetWorkingData(0)->SetVisibility(false); } FireNodeSelected(selectedNode); selectedNode->SetVisibility(true); SetToolManagerSelection(selectedNode, NULL); } } else { m_Controls->refImageSelector->hide(); m_Controls->lblReferenceImageSelectionWarning->show(); } } void QmitkSegmentationView::OnShowMarkerNodes (bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetTools().size(); for(unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetToolById(i)); if (manualSegmentationTool) { if(state == true) { manualSegmentationTool->SetShowMarkerNodes( true ); } else { manualSegmentationTool->SetShowMarkerNodes( false ); } } } } void QmitkSegmentationView::OnSelectionChanged(mitk::DataNode* node) { std::vector nodes; nodes.push_back( node ); this->OnSelectionChanged( nodes ); } void QmitkSegmentationView::OnSurfaceSelectionChanged() { // if Image and Surface are selected, enable button if ( (m_Controls->refImageSelector->GetSelectedNode().IsNull()) || (m_Controls->MaskSurfaces->GetSelectedNode().IsNull())) m_Controls->CreateSegmentationFromSurface->setEnabled(false); else m_Controls->CreateSegmentationFromSurface->setEnabled(true); } void QmitkSegmentationView::OnSelectionChanged(std::vector nodes) { // if the selected node is a contourmarker if ( !nodes.empty() ) { std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at( 0 )->GetName(); if ( ( numberOfNodes == 1 ) && ( nodeName.find( markerName ) == 0) ) { this->OnContourMarkerSelected( nodes.at( 0 ) ); } } // if Image and Surface are selected, enable button if ( (m_Controls->refImageSelector->GetSelectedNode().IsNull()) || (m_Controls->MaskSurfaces->GetSelectedNode().IsNull())) m_Controls->CreateSegmentationFromSurface->setEnabled(false); else m_Controls->CreateSegmentationFromSurface->setEnabled(true); m_DataSelectionChanged = false; if (!m_Parent || !m_Parent->isVisible()) { m_DataSelectionChanged = true; return; } // reaction to BlueBerry selection events // this method will try to figure out if a relevant segmentation and its corresponding original image were selected // a warning is issued if the selection is invalid // appropriate reactions are triggered otherwise mitk::DataNode::Pointer referenceData = FindFirstRegularImage( nodes ); //m_Controls->refImageSelector->GetSelectedNode(); //FindFirstRegularImage( nodes ); mitk::DataNode::Pointer workingData = FindFirstSegmentation( nodes ); if(referenceData.IsNull() && workingData.IsNull()) return; bool invalidSelection( !nodes.empty() && ( nodes.size() > 2 || // maximum 2 selected nodes (nodes.size() == 2 && (workingData.IsNull() || referenceData.IsNull()) ) || // with two nodes, one must be the original image, one the segmentation ( workingData.GetPointer() == referenceData.GetPointer() ) //one node is selected as reference and working image // one item is always ok (might be working or reference or nothing ) ); if (invalidSelection) { // TODO visible warning when two images are selected MITK_ERROR << "WARNING: No image, too many (>2) or two equal images were selected."; workingData = NULL; if( m_Controls->refImageSelector->GetSelectedNode().IsNull() ) referenceData = NULL; } if ( workingData.IsNotNull() && referenceData.IsNull() ) { // find the DataStorage parent of workingData // try to find a "normal image" parent, select this as reference image mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); mitk::NodePredicateProperty::Pointer isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateNot::Pointer isNotBinary = mitk::NodePredicateNot::New( isBinary ); mitk::NodePredicateAnd::Pointer isNormalImage = mitk::NodePredicateAnd::New( isImage, isNotBinary ); mitk::DataStorage::SetOfObjects::ConstPointer possibleParents = this->GetDefaultDataStorage()->GetSources( workingData, isNormalImage ); if (possibleParents->size() > 0) { if (possibleParents->size() > 1) { // TODO visible warning for this rare case MITK_ERROR << "Selected binary image has multiple parents. Using arbitrary first one for segmentation."; } referenceData = (*possibleParents)[0]; } NodeTagMapType::iterator searchIter = m_WorkingDataObserverTags.find( workingData ); if ( searchIter == m_WorkingDataObserverTags.end() ) { //MITK_INFO<<"Creating new observer"; itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::OnWorkingNodeVisibilityChanged); m_WorkingDataObserverTags.insert( std::pair( workingData, workingData->GetProperty("visible")->AddObserver( itk::ModifiedEvent(), command ) ) ); workingData->GetProperty("visible")->Modified(); return; } if(workingData->IsVisible(mitk::BaseRenderer::GetInstance( mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget1")))) { //set comboBox to reference image disconnect( m_Controls->refImageSelector, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnComboBoxSelectionChanged( const mitk::DataNode* ) ) ); m_Controls->refImageSelector->setCurrentIndex( m_Controls->refImageSelector->Find(workingData) ); connect( m_Controls->refImageSelector, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnComboBoxSelectionChanged( const mitk::DataNode* ) ) ); // if Image and Surface are selected, enable button if ( (m_Controls->refImageSelector->GetSelectedNode().IsNull()) || (m_Controls->MaskSurfaces->GetSelectedNode().IsNull()) || (!referenceData)) m_Controls->CreateSegmentationFromSurface->setEnabled(false); else m_Controls->CreateSegmentationFromSurface->setEnabled(true); SetToolManagerSelection(referenceData, workingData); FireNodeSelected(workingData); } else { SetToolManagerSelection(NULL, NULL); FireNodeSelected(workingData); } } else { //set comboBox to reference image disconnect( m_Controls->refImageSelector, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnComboBoxSelectionChanged( const mitk::DataNode* ) ) ); m_Controls->refImageSelector->setCurrentIndex( m_Controls->refImageSelector->Find(referenceData) ); connect( m_Controls->refImageSelector, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnComboBoxSelectionChanged( const mitk::DataNode* ) ) ); // if Image and Surface are selected, enable button if ( (m_Controls->refImageSelector->GetSelectedNode().IsNull()) || (m_Controls->MaskSurfaces->GetSelectedNode().IsNull()) || (!referenceData)) m_Controls->CreateSegmentationFromSurface->setEnabled(false); else m_Controls->CreateSegmentationFromSurface->setEnabled(true); SetToolManagerSelection(referenceData, workingData); FireNodeSelected(referenceData); } } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode *node) { //TODO renderWindow anders bestimmen, siehe CheckAlignment QmitkRenderWindow* selectedRenderWindow = 0; QmitkRenderWindow* RenderWindow1 = this->GetActiveStdMultiWidget()->GetRenderWindow1(); QmitkRenderWindow* RenderWindow2 = this->GetActiveStdMultiWidget()->GetRenderWindow2(); QmitkRenderWindow* RenderWindow3 = this->GetActiveStdMultiWidget()->GetRenderWindow3(); QmitkRenderWindow* RenderWindow4 = this->GetActiveStdMultiWidget()->GetRenderWindow4(); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, RenderWindow1->GetRenderer())) { selectedRenderWindow = RenderWindow1; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, RenderWindow2->GetRenderer())) { selectedRenderWindow = RenderWindow2; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, RenderWindow3->GetRenderer())) { selectedRenderWindow = RenderWindow3; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, RenderWindow4->GetRenderer())) { selectedRenderWindow = RenderWindow4; } // make node visible if (selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t+1).c_str())-1; // gets the context of the "Mitk" (Core) module (always has id 1) // TODO Workaround until CTL plugincontext is available mitk::ModuleContext* context = mitk::ModuleRegistry::GetModule(1)->GetModuleContext(); // Workaround end mitk::ServiceReference serviceRef = context->GetServiceReference(); //mitk::ServiceReference serviceRef = mitk::GetModuleContext()->GetServiceReference(); mitk::PlanePositionManagerService* service = dynamic_cast(context->GetService(serviceRef)); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); selectedRenderWindow->GetRenderer()->GetDisplayGeometry()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } mitk::DataNode::Pointer QmitkSegmentationView::FindFirstRegularImage( std::vector nodes ) { if (nodes.empty()) return NULL; for(unsigned int i = 0; i < nodes.size(); ++i) { //mitk::DataNode::Pointer node = i.value() bool isImage(false); if (nodes.at(i)->GetData()) { isImage = dynamic_cast(nodes.at(i)->GetData()) != NULL; } // make sure this is not a binary image bool isSegmentation(false); nodes.at(i)->GetBoolProperty("binary", isSegmentation); // return first proper mitk::Image if (isImage && !isSegmentation) return nodes.at(i); } return NULL; } mitk::DataNode::Pointer QmitkSegmentationView::FindFirstSegmentation( std::vector nodes ) { if (nodes.empty()) return NULL; for(unsigned int i = 0; i < nodes.size(); ++i) { bool isImage(false); if (nodes.at(i)->GetData()) { isImage = dynamic_cast(nodes.at(i)->GetData()) != NULL; } bool isSegmentation(false); nodes.at(i)->GetBoolProperty("binary", isSegmentation); // return first proper binary mitk::Image if (isImage && isSegmentation) { return nodes.at(i); } } return NULL; } void QmitkSegmentationView::SetToolManagerSelection(const mitk::DataNode* referenceData, const mitk::DataNode* workingData) { // called as a result of new BlueBerry selections // tells the ToolManager for manual segmentation about new selections // updates GUI information about what the user should select mitk::ToolManager* toolManager = m_Controls->m_ManualToolSelectionBox->GetToolManager(); toolManager->SetReferenceData(const_cast(referenceData)); toolManager->SetWorkingData( const_cast(workingData)); // check original image m_Controls->btnNewSegmentation->setEnabled(referenceData != NULL); if (referenceData) { m_Controls->lblReferenceImageSelectionWarning->hide(); } else { m_Controls->lblReferenceImageSelectionWarning->show(); m_Controls->lblWorkingImageSelectionWarning->hide(); m_Controls->lblSegImage->hide(); m_Controls->lblSegmentation->hide(); } //TODO remove statement // check, wheter reference image is aligned like render windows. Otherwise display a visible warning (because 2D tools will probably not work) CheckImageAlignment(); // check segmentation if (referenceData) { if (!workingData) { m_Controls->lblWorkingImageSelectionWarning->show(); if( m_Controls->widgetStack->currentIndex() == 0 ) { m_Controls->lblSegImage->hide(); m_Controls->lblSegmentation->hide(); } } else { m_Controls->lblWorkingImageSelectionWarning->hide(); this->FireNodeSelected(const_cast(workingData)); if( m_Controls->widgetStack->currentIndex() == 0 ) { m_Controls->lblSegmentation->setText( workingData->GetName().c_str() ); m_Controls->lblSegmentation->show(); m_Controls->lblSegImage->show(); } } } } //TODO remove function void QmitkSegmentationView::CheckImageAlignment() { bool wrongAlignment(true); mitk::DataNode::Pointer node = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0); if (node.IsNotNull()) { mitk::Image::Pointer image = dynamic_cast( node->GetData() ); if (image.IsNotNull() && m_MultiWidget) { wrongAlignment = !( IsRenderWindowAligned(m_MultiWidget->GetRenderWindow1(), image ) && IsRenderWindowAligned(m_MultiWidget->GetRenderWindow2(), image ) && IsRenderWindowAligned(m_MultiWidget->GetRenderWindow3(), image ) ); } } m_Controls->lblAlignmentWarning->setVisible(wrongAlignment); } //TODO remove function bool QmitkSegmentationView::IsRenderWindowAligned(QmitkRenderWindow* renderWindow, mitk::Image* image) { if (!renderWindow) return false; // for all 2D renderwindows of m_MultiWidget check alignment mitk::PlaneGeometry::ConstPointer displayPlane = dynamic_cast( renderWindow->GetRenderer()->GetCurrentWorldGeometry2D() ); if (displayPlane.IsNull()) return false; int affectedDimension(-1); int affectedSlice(-1); return mitk::SegTool2D::DetermineAffectedImageSlice( image, displayPlane, affectedDimension, affectedSlice ); } //TODO remove function void QmitkSegmentationView::ForceDisplayPreferencesUponAllImages() { if (!m_Parent || !m_Parent->isVisible()) return; // check all images and segmentations in DataStorage: // (items in brackets are implicitly done by previous steps) // 1. // if a reference image is selected, // show the reference image // and hide all other images (orignal and segmentation), // (and hide all segmentations of the other original images) // and show all the reference's segmentations // if no reference image is selected, do do nothing // // 2. // if a segmentation is selected, // show it // (and hide all all its siblings (childs of the same parent, incl, NULL parent)) // if no segmentation is selected, do nothing if (!m_Controls) return; // might happen on initialization (preferences loaded) mitk::DataNode::Pointer referenceData = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0); // 1. if (referenceData.IsNotNull()) { // iterate all images mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDefaultDataStorage()->GetSubset( isImage ); //mitk::DataStorage::SetOfObjects::ConstPointer allSegmentationChilds = this->GetDefaultDataStorage()->GetDerivations(referenceData, isImage ); for ( mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { mitk::DataNode* node = *iter; // apply display preferences ApplyDisplayOptions(node); // set visibility if(!node->IsSelected() || (node->IsSelected() && !node->IsVisible(mitk::BaseRenderer::GetInstance( mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget1"))))) node->SetVisibility((node == referenceData) || node->IsSelected() ); } // Reinit current node mitk::RenderingManager::GetInstance()->InitializeViews( referenceData->GetData()->GetTimeSlicedGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true ); } // 2. //if (workingData.IsNotNull() && !workingData->IsSelected()) //{ // workingData->SetVisibility(true); //} mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (!node) return; bool isBinary(false); node->GetPropertyValue("binary", isBinary); if (isBinary) { node->SetProperty( "outline binary", mitk::BoolProperty::New( this->GetPreferences()->GetBool("draw outline", true)) ); node->SetProperty( "outline width", mitk::FloatProperty::New( 2.0 ) ); node->SetProperty( "opacity", mitk::FloatProperty::New( this->GetPreferences()->GetBool("draw outline", true) ? 1.0 : 0.3 ) ); node->SetProperty( "volumerendering", mitk::BoolProperty::New( this->GetPreferences()->GetBool("volume rendering", false) ) ); } } void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { // setup the basic GUI of this view m_Parent = parent; m_Controls = new Ui::QmitkSegmentationControls; m_Controls->setupUi(parent); m_Controls->lblWorkingImageSelectionWarning->hide(); m_Controls->lblAlignmentWarning->hide(); m_Controls->lblSegImage->hide(); m_Controls->lblSegmentation->hide(); m_Controls->refImageSelector->SetDataStorage(this->GetDefaultDataStorage()); mitk::TNodePredicateDataType::Pointer isMitkImage = mitk::TNodePredicateDataType::New(); mitk::NodePredicateDataType::Pointer isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); mitk::NodePredicateDataType::Pointer isDti = mitk::NodePredicateDataType::New("TensorImage"); mitk::NodePredicateDataType::Pointer isQbi = mitk::NodePredicateDataType::New("QBallImage"); mitk::NodePredicateOr::Pointer isDiffusionImage = mitk::NodePredicateOr::New(isDwi, isDti); isDiffusionImage = mitk::NodePredicateOr::New(isDiffusionImage, isQbi); mitk::NodePredicateOr::Pointer isImage = mitk::NodePredicateOr::New(isDiffusionImage, isMitkImage); m_Controls->refImageSelector->SetPredicate(isImage); if( m_Controls->refImageSelector->GetSelectedNode().IsNotNull() ) m_Controls->lblReferenceImageSelectionWarning->hide(); else m_Controls->refImageSelector->hide(); mitk::ToolManager* toolManager = m_Controls->m_ManualToolSelectionBox->GetToolManager(); toolManager->SetDataStorage( *(this->GetDefaultDataStorage()) ); assert ( toolManager ); // all part of open source MITK m_Controls->m_ManualToolSelectionBox->SetGenerateAccelerators(true); m_Controls->m_ManualToolSelectionBox->SetToolGUIArea( m_Controls->m_ManualToolGUIContainer ); - m_Controls->m_ManualToolSelectionBox->SetDisplayedToolGroups("Add Subtract Paint Wipe 'Region Growing' Correction Fill Erase"); + m_Controls->m_ManualToolSelectionBox->SetDisplayedToolGroups("Add Subtract Paint Wipe 'Region Growing' Correction Fill Erase LiveWire"); m_Controls->m_ManualToolSelectionBox->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingData ); // available only in the 3M application if ( !m_Controls->m_OrganToolSelectionBox->children().count() ) { m_Controls->widgetStack->setItemEnabled( 1, false ); } m_Controls->m_OrganToolSelectionBox->SetToolManager( *toolManager ); m_Controls->m_OrganToolSelectionBox->SetToolGUIArea( m_Controls->m_OrganToolGUIContainer ); m_Controls->m_OrganToolSelectionBox->SetDisplayedToolGroups("'Hippocampus left' 'Hippocampus right' 'Lung left' 'Lung right' 'Liver' 'Heart LV' 'Endocard LV' 'Epicard LV' 'Prostate'"); m_Controls->m_OrganToolSelectionBox->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceData ); // available only in the 3M application if ( !m_Controls->m_LesionToolSelectionBox->children().count() ) { m_Controls->widgetStack->setItemEnabled( 2, false ); } m_Controls->m_LesionToolSelectionBox->SetToolManager( *toolManager ); m_Controls->m_LesionToolSelectionBox->SetToolGUIArea( m_Controls->m_LesionToolGUIContainer ); m_Controls->m_LesionToolSelectionBox->SetDisplayedToolGroups("'Lymph Node'"); m_Controls->m_LesionToolSelectionBox->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceData ); toolManager->NewNodesGenerated += mitk::MessageDelegate( this, &QmitkSegmentationView::NewNodesGenerated ); // update the list of segmentations toolManager->NewNodeObjectsGenerated += mitk::MessageDelegate1( this, &QmitkSegmentationView::NewNodeObjectsGenerated ); // update the list of segmentations // create signal/slot connections connect( m_Controls->refImageSelector, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnComboBoxSelectionChanged( const mitk::DataNode* ) ) ); connect( m_Controls->btnNewSegmentation, SIGNAL(clicked()), this, SLOT(CreateNewSegmentation()) ); connect( m_Controls->CreateSegmentationFromSurface, SIGNAL(clicked()), this, SLOT(CreateSegmentationFromSurface()) ); connect( m_Controls->widgetStack, SIGNAL(currentChanged(int)), this, SLOT(ToolboxStackPageChanged(int)) ); connect(m_Controls->MaskSurfaces, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnSurfaceSelectionChanged( ) ) ); connect(m_Controls->MaskSurfaces, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnSurfaceSelectionChanged( ) ) ); connect(m_Controls->m_SlicesInterpolator, SIGNAL(SignalShowMarkerNodes(bool)), this, SLOT(OnShowMarkerNodes(bool))); m_Controls->MaskSurfaces->SetDataStorage(this->GetDefaultDataStorage()); m_Controls->MaskSurfaces->SetPredicate(mitk::NodePredicateDataType::New("Surface")); //// create helper class to provide context menus for segmentations in data manager // m_PostProcessing = new QmitkSegmentationPostProcessing(this->GetDefaultDataStorage(), this, m_Parent); } //void QmitkSegmentationView::OnPlaneModeChanged(int i) //{ // //if plane mode changes, disable all tools // if (m_MultiWidget) // { // mitk::ToolManager* toolManager = m_Controls->m_ManualToolSelectionBox->GetToolManager(); // // if (toolManager) // { // if (toolManager->GetActiveToolID() >= 0) // { // toolManager->ActivateTool(-1); // } // else // { // m_MultiWidget->EnableNavigationControllerEventListening(); // } // } // } //} // ATTENTION some methods for handling the known list of (organ names, colors) are defined in QmitkSegmentationOrganNamesHandling.cpp -