diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.cpp b/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.cpp index bece5803bf..0c967a5194 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.cpp +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.cpp @@ -1,703 +1,707 @@ /*=================================================================== 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 "mitkPlanarFigure.h" #include "mitkGeometry2D.h" #include "mitkProperties.h" #include "algorithm" mitk::PlanarFigure::PlanarFigure() : m_SelectedControlPoint( -1 ), m_PreviewControlPointVisible( false ), m_FigurePlaced( false ), m_Geometry2D( NULL ), m_PolyLineUpToDate(false), m_HelperLinesUpToDate(false), m_FeaturesUpToDate(false), m_FeaturesMTime( 0 ) { m_HelperPolyLinesToBePainted = BoolContainerType::New(); m_DisplaySize.first = 0.0; m_DisplaySize.second = 0; this->SetProperty( "closed", mitk::BoolProperty::New( false ) ); // Currently only single-time-step geometries are supported this->InitializeTimeSlicedGeometry( 1 ); } mitk::PlanarFigure::~PlanarFigure() { } void mitk::PlanarFigure::SetGeometry2D( mitk::Geometry2D *geometry ) { this->SetGeometry( geometry ); m_Geometry2D = geometry; } const mitk::Geometry2D *mitk::PlanarFigure::GetGeometry2D() const { return m_Geometry2D; } bool mitk::PlanarFigure::IsClosed() const { mitk::BoolProperty* closed = dynamic_cast< mitk::BoolProperty* >( this->GetProperty( "closed" ).GetPointer() ); if ( closed != NULL ) { return closed->GetValue(); } return false; } void mitk::PlanarFigure::PlaceFigure( const mitk::Point2D& point ) { for ( unsigned int i = 0; i < this->GetNumberOfControlPoints(); ++i ) { m_ControlPoints.push_back( this->ApplyControlPointConstraints( i, point ) ); } m_FigurePlaced = true; m_SelectedControlPoint = 1; } bool mitk::PlanarFigure::AddControlPoint( const mitk::Point2D& point, int position ) { // if we already have the maximum number of control points, do nothing if ( m_NumberOfControlPoints < this->GetMaximumNumberOfControlPoints() ) { // if position has not been defined or position would be the last control point, just append the new one // we also append a new point if we click onto the line between the first two control-points if the second control-point is selected // -> special case for PlanarCross if ( position == -1 || position > (int)m_NumberOfControlPoints-1 || (position == 1 && m_SelectedControlPoint == 2) ) { if ( m_ControlPoints.size() > this->GetMaximumNumberOfControlPoints()-1 ) { // get rid of deprecated control points in the list. This is necessary // as ::ResetNumberOfControlPoints() only sets the member, does not resize the list! m_ControlPoints.resize( this->GetNumberOfControlPoints() ); } m_ControlPoints.push_back( this->ApplyControlPointConstraints( m_NumberOfControlPoints, point ) ); m_SelectedControlPoint = m_NumberOfControlPoints; } else { // insert the point at the given position and set it as selected point ControlPointListType::iterator iter = m_ControlPoints.begin() + position; m_ControlPoints.insert( iter, this->ApplyControlPointConstraints( position, point ) ); for( unsigned int i = 0; i < m_ControlPoints.size(); ++i ) { if( point == m_ControlPoints.at(i) ) { m_SelectedControlPoint = i; } } } // polylines & helperpolylines need to be repainted m_PolyLineUpToDate = false; m_HelperLinesUpToDate = false; m_FeaturesUpToDate = false; // one control point more ++m_NumberOfControlPoints; return true; } else { return false; } } bool mitk::PlanarFigure::SetControlPoint( unsigned int index, const Point2D& point, bool createIfDoesNotExist ) { bool controlPointSetCorrectly = false; if (createIfDoesNotExist) { if ( m_NumberOfControlPoints <= index ) { m_ControlPoints.push_back( this->ApplyControlPointConstraints( index, point ) ); m_NumberOfControlPoints++; } else { m_ControlPoints.at( index ) = this->ApplyControlPointConstraints( index, point ); } controlPointSetCorrectly = true; } else if ( index < m_NumberOfControlPoints ) { m_ControlPoints.at( index ) = this->ApplyControlPointConstraints( index, point ); controlPointSetCorrectly = true; } else { return false; } if ( controlPointSetCorrectly ) { m_PolyLineUpToDate = false; m_HelperLinesUpToDate = false; m_FeaturesUpToDate = false; } return controlPointSetCorrectly; } bool mitk::PlanarFigure::SetCurrentControlPoint( const Point2D& point ) { if ( (m_SelectedControlPoint < 0) || (m_SelectedControlPoint >= (int)m_NumberOfControlPoints) ) { return false; } return this->SetControlPoint(m_SelectedControlPoint, point, false); } unsigned int mitk::PlanarFigure::GetNumberOfControlPoints() const { return m_NumberOfControlPoints; } bool mitk::PlanarFigure::SelectControlPoint( unsigned int index ) { if ( index < this->GetNumberOfControlPoints() ) { m_SelectedControlPoint = index; return true; } else { return false; } } -void mitk::PlanarFigure::DeselectControlPoint() +bool mitk::PlanarFigure::DeselectControlPoint() { + bool wasSelected = ( m_SelectedControlPoint != -1); + m_SelectedControlPoint = -1; + + return wasSelected; } void mitk::PlanarFigure::SetPreviewControlPoint( const Point2D& point ) { m_PreviewControlPoint = point; m_PreviewControlPointVisible = true; } void mitk::PlanarFigure::ResetPreviewContolPoint() { m_PreviewControlPointVisible = false; } mitk::Point2D mitk::PlanarFigure::GetPreviewControlPoint() { return m_PreviewControlPoint; } bool mitk::PlanarFigure::IsPreviewControlPointVisible() { return m_PreviewControlPointVisible; } mitk::Point2D mitk::PlanarFigure::GetControlPoint( unsigned int index ) const { if ( index < m_NumberOfControlPoints ) { return m_ControlPoints.at( index ); } itkExceptionMacro( << "GetControlPoint(): Invalid index!" ); } mitk::Point3D mitk::PlanarFigure::GetWorldControlPoint( unsigned int index ) const { Point3D point3D; if ( (m_Geometry2D != NULL) && (index < m_NumberOfControlPoints) ) { m_Geometry2D->Map( m_ControlPoints.at( index ), point3D ); return point3D; } itkExceptionMacro( << "GetWorldControlPoint(): Invalid index!" ); } const mitk::PlanarFigure::PolyLineType mitk::PlanarFigure::GetPolyLine(unsigned int index) { mitk::PlanarFigure::PolyLineType polyLine; if ( index > m_PolyLines.size() || !m_PolyLineUpToDate ) { this->GeneratePolyLine(); m_PolyLineUpToDate = true; } return m_PolyLines.at( index );; } const mitk::PlanarFigure::PolyLineType mitk::PlanarFigure::GetPolyLine(unsigned int index) const { return m_PolyLines.at( index ); } void mitk::PlanarFigure::ClearPolyLines() { for ( std::vector::size_type i=0; iGenerateHelperPolyLine(mmPerDisplayUnit, displayHeight); m_HelperLinesUpToDate = true; // store these parameters to be able to check next time if somebody zoomed in or out m_DisplaySize.first = mmPerDisplayUnit; m_DisplaySize.second = displayHeight; } helperPolyLine = m_HelperPolyLines.at(index); } return helperPolyLine; } void mitk::PlanarFigure::ClearHelperPolyLines() { for ( std::vector::size_type i=0; iGeneratePolyLine(); } this->EvaluateFeaturesInternal(); m_FeaturesUpToDate = true; } } void mitk::PlanarFigure::UpdateOutputInformation() { // Bounds are NOT calculated here, since the Geometry2D defines a fixed // frame (= bounds) for the planar figure. Superclass::UpdateOutputInformation(); this->GetTimeSlicedGeometry()->UpdateInformation(); } void mitk::PlanarFigure::SetRequestedRegionToLargestPossibleRegion() { } bool mitk::PlanarFigure::RequestedRegionIsOutsideOfTheBufferedRegion() { return false; } bool mitk::PlanarFigure::VerifyRequestedRegion() { return true; } void mitk::PlanarFigure::SetRequestedRegion( itk::DataObject * /*data*/ ) { } void mitk::PlanarFigure::ResetNumberOfControlPoints( int numberOfControlPoints ) { // DO NOT resize the list here, will cause crash!! m_NumberOfControlPoints = numberOfControlPoints; } mitk::Point2D mitk::PlanarFigure::ApplyControlPointConstraints( unsigned int /*index*/, const Point2D& point ) { if ( m_Geometry2D == NULL ) { return point; } Point2D indexPoint; m_Geometry2D->WorldToIndex( point, indexPoint ); BoundingBox::BoundsArrayType bounds = m_Geometry2D->GetBounds(); if ( indexPoint[0] < bounds[0] ) { indexPoint[0] = bounds[0]; } if ( indexPoint[0] > bounds[1] ) { indexPoint[0] = bounds[1]; } if ( indexPoint[1] < bounds[2] ) { indexPoint[1] = bounds[2]; } if ( indexPoint[1] > bounds[3] ) { indexPoint[1] = bounds[3]; } Point2D constrainedPoint; m_Geometry2D->IndexToWorld( indexPoint, constrainedPoint ); return constrainedPoint; } unsigned int mitk::PlanarFigure::AddFeature( const char *featureName, const char *unitName ) { unsigned int index = m_Features.size(); Feature newFeature( featureName, unitName ); m_Features.push_back( newFeature ); return index; } void mitk::PlanarFigure::SetFeatureName( unsigned int index, const char *featureName ) { if ( index < m_Features.size() ) { m_Features[index].Name = featureName; } } void mitk::PlanarFigure::SetFeatureUnit( unsigned int index, const char *unitName ) { if ( index < m_Features.size() ) { m_Features[index].Unit = unitName; } } void mitk::PlanarFigure::SetQuantity( unsigned int index, double quantity ) { if ( index < m_Features.size() ) { m_Features[index].Quantity = quantity; } } void mitk::PlanarFigure::ActivateFeature( unsigned int index ) { if ( index < m_Features.size() ) { m_Features[index].Active = true; } } void mitk::PlanarFigure::DeactivateFeature( unsigned int index ) { if ( index < m_Features.size() ) { m_Features[index].Active = false; } } void mitk::PlanarFigure::InitializeTimeSlicedGeometry( unsigned int timeSteps ) { mitk::TimeSlicedGeometry::Pointer timeGeometry = this->GetTimeSlicedGeometry(); mitk::Geometry2D::Pointer geometry2D = mitk::Geometry2D::New(); geometry2D->Initialize(); if ( timeSteps > 1 ) { mitk::ScalarType timeBounds[] = {0.0, 1.0}; geometry2D->SetTimeBounds( timeBounds ); } // The geometry is propagated automatically to all time steps, // if EvenlyTimed is true... timeGeometry->InitializeEvenlyTimed( geometry2D, timeSteps ); } void mitk::PlanarFigure::PrintSelf( std::ostream& os, itk::Indent indent) const { Superclass::PrintSelf( os, indent ); os << indent << this->GetNameOfClass() << ":\n"; if (this->IsClosed()) os << indent << "This figure is closed\n"; else os << indent << "This figure is not closed\n"; os << indent << "Minimum number of control points: " << this->GetMinimumNumberOfControlPoints() << std::endl; os << indent << "Maximum number of control points: " << this->GetMaximumNumberOfControlPoints() << std::endl; os << indent << "Current number of control points: " << this->GetNumberOfControlPoints() << std::endl; os << indent << "Control points:" << std::endl; for ( unsigned int i = 0; i < this->GetNumberOfControlPoints(); ++i ) { //os << indent.GetNextIndent() << i << ": " << m_ControlPoints->ElementAt( i ) << std::endl; os << indent.GetNextIndent() << i << ": " << m_ControlPoints.at( i ) << std::endl; } os << indent << "Geometry:\n"; this->GetGeometry2D()->Print(os, indent.GetNextIndent()); } unsigned short mitk::PlanarFigure::GetPolyLinesSize() { if ( !m_PolyLineUpToDate ) { this->GeneratePolyLine(); m_PolyLineUpToDate = true; } return m_PolyLines.size(); } unsigned short mitk::PlanarFigure::GetHelperPolyLinesSize() { return m_HelperPolyLines.size(); } bool mitk::PlanarFigure::IsHelperToBePainted(unsigned int index) { return m_HelperPolyLinesToBePainted->GetElement( index ); } bool mitk::PlanarFigure::ResetOnPointSelect() { return false; } void mitk::PlanarFigure::RemoveControlPoint( unsigned int index ) { if ( index > m_ControlPoints.size() ) return; if ( (m_ControlPoints.size() -1) < this->GetMinimumNumberOfControlPoints() ) return; ControlPointListType::iterator iter; iter = m_ControlPoints.begin() + index; m_ControlPoints.erase( iter ); m_PolyLineUpToDate = false; m_HelperLinesUpToDate = false; m_FeaturesUpToDate = false; --m_NumberOfControlPoints; } void mitk::PlanarFigure::RemoveLastControlPoint() { RemoveControlPoint( m_ControlPoints.size()-1 ); } void mitk::PlanarFigure::DeepCopy(Self::Pointer oldFigure) { //DeepCopy only same types of planar figures //Notice to get typeid polymorph you have to use the *operator if(typeid(*oldFigure) != typeid(*this)) { itkExceptionMacro( << "DeepCopy(): Inconsistent type of source (" << typeid(*oldFigure).name() << ") and destination figure (" << typeid(*this).name() << ")!" ); return; } m_ControlPoints.clear(); this->ClearPolyLines(); this->ClearHelperPolyLines(); // clone base data members SetPropertyList(oldFigure->GetPropertyList()->Clone()); /// deep copy members m_FigurePlaced = oldFigure->m_FigurePlaced; m_SelectedControlPoint = oldFigure->m_SelectedControlPoint; m_FeaturesMTime = oldFigure->m_FeaturesMTime; m_Features = oldFigure->m_Features; m_NumberOfControlPoints = oldFigure->m_NumberOfControlPoints; //copy geometry 2D of planar figure SetGeometry2D((mitk::Geometry2D*)oldFigure->m_Geometry2D->Clone().GetPointer()); for(unsigned long index=0; index < oldFigure->GetNumberOfControlPoints(); index++) { m_ControlPoints.push_back( oldFigure->GetControlPoint( index )); } //After setting the control points we can generate the polylines this->GeneratePolyLine(); } void mitk::PlanarFigure::SetNumberOfPolyLines( unsigned int numberOfPolyLines ) { m_PolyLines.resize(numberOfPolyLines); } void mitk::PlanarFigure::SetNumberOfHelperPolyLines( unsigned int numberOfHerlperPolyLines ) { m_HelperPolyLines.resize(numberOfHerlperPolyLines); } void mitk::PlanarFigure::AppendPointToPolyLine( unsigned int index, PolyLineElement element ) { if ( index < m_PolyLines.size() ) { m_PolyLines.at( index ).push_back( element ); m_PolyLineUpToDate = false; } else { MITK_ERROR << "Tried to add point to PolyLine " << index+1 << ", although only " << m_PolyLines.size() << " exists"; } } void mitk::PlanarFigure::AppendPointToHelperPolyLine( unsigned int index, PolyLineElement element ) { if ( index < m_HelperPolyLines.size() ) { m_HelperPolyLines.at( index ).push_back( element ); m_HelperLinesUpToDate = false; } else { MITK_ERROR << "Tried to add point to HelperPolyLine " << index+1 << ", although only " << m_HelperPolyLines.size() << " exists"; } } diff --git a/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.h b/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.h index 1efd3d5f78..d8453e3451 100644 --- a/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.h +++ b/Modules/PlanarFigure/DataManagement/mitkPlanarFigure.h @@ -1,407 +1,407 @@ /*=================================================================== 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 _MITK_PLANAR_FIGURE_H_ #define _MITK_PLANAR_FIGURE_H_ #include "PlanarFigureExports.h" #include "mitkBaseData.h" #include "mitkCommon.h" #include namespace mitk { class Geometry2D; /** * \brief Base-class for geometric planar (2D) figures, such as * lines, circles, rectangles, polygons, etc. * * \warning Currently does not support time-resolved data handling * * Behavior and appearance of PlanarFigures are controlled by various properties; for a detailed * list of appearance properties see mitk::PlanarFigureMapper2D * * The following properties control general PlanarFigure behavior: * *
    *
  • "selected": true if the planar figure is selected *
  • "planarfigure.ishovering": true if the mouse "hovers" over the planar figure *
  • "planarfigure.iseditable": true if the planar figure can be edited (otherwise, * it can only be picked/selected, but its control points cannot be edited); default is true *
  • "planarfigure.isextendable": true if new control points can be inserted into the list of control points; * default is false *
* * * TODO: Implement local 2D transform (including center of rotation...) * */ class PlanarFigure_EXPORT PlanarFigure : public BaseData { public: mitkClassMacro( PlanarFigure, BaseData ); struct PolyLineElement { PolyLineElement( Point2D point, int index ) : Point( point ), Index( index ) { }; Point2D Point; int Index; }; typedef itk::VectorContainer< unsigned long, bool> BoolContainerType; typedef std::deque< Point2D > ControlPointListType; typedef std::list< PolyLineElement > PolyLineType; /** \brief Sets the 2D geometry on which this figure will be placed. * * In most cases, this is a Geometry already owned by another object, e.g. * describing the slice of the image on which measurements will be * performed. */ virtual void SetGeometry2D( mitk::Geometry2D *geometry ); /** \brief Returns (previously set) 2D geometry of this figure. */ virtual const Geometry2D *GetGeometry2D() const; /** \brief True if the planar figure is closed. * * Default is false. The "closed" boolean property must be set in sub-classes. */ virtual bool IsClosed() const; /** \brief True if the planar figure has been placed (and can be * displayed/interacted with). */ virtual bool IsPlaced() const { return m_FigurePlaced; }; /** \brief Place figure at the given point (in 2D index coordinates) onto * the given 2D geometry. * * By default, the first two control points of the figure are set to the * passed point. Further points can be set via AddControlPoint(), if the * current number of control points is below the maximum number of control * points. * * Can be re-implemented in sub-classes as needed. */ virtual void PlaceFigure( const Point2D& point ); /** * \brief Adds / inserts new control-points * * This method adds a new control-point with the coordinates defined by point at the given index. * If 'index' == -1 or index is greater than the number of control-points the new point is appended * to the back of the list of control points. * If a control-point already exists for 'index', an additional point is inserted at that position. * It is not possible to add more points if the maximum number of control-points (GetMaximumNumberOfControlPoints()) * has been reached. */ virtual bool AddControlPoint( const Point2D& point, int index = -1 ); virtual bool SetControlPoint( unsigned int index, const Point2D& point, bool createIfDoesNotExist = false); virtual bool SetCurrentControlPoint( const Point2D& point ); /** \brief Returns the current number of 2D control points defining this figure. */ unsigned int GetNumberOfControlPoints() const; /** \brief Returns the minimum number of control points needed to represent * this figure. * * Must be implemented in sub-classes. */ virtual unsigned int GetMinimumNumberOfControlPoints() const = 0; /** \brief Returns the maximum number of control points allowed for * this figure (e.g. 3 for triangles). * * Must be implemented in sub-classes. */ virtual unsigned int GetMaximumNumberOfControlPoints() const = 0; /** \brief Selects currently active control points. */ virtual bool SelectControlPoint( unsigned int index ); /** \brief Deselect control point; no control point active. */ - virtual void DeselectControlPoint(); + virtual bool DeselectControlPoint(); /** \brief Return currently selected control point. */ virtual int GetSelectedControlPoint() const { return m_SelectedControlPoint; } /** \brief Returns specified control point in 2D world coordinates. */ Point2D GetControlPoint( unsigned int index ) const; /** \brief Returns specified control point in world coordinates. */ Point3D GetWorldControlPoint( unsigned int index ) const; /** \brief Returns the polyline representing the planar figure * (for rendering, measurements, etc.). */ const PolyLineType GetPolyLine(unsigned int index); /** \brief Returns the polyline representing the planar figure * (for rendering, measurments, etc.). */ const PolyLineType GetPolyLine(unsigned int index) const; /** \brief Returns the polyline that should be drawn the same size at every scale * (for text, angles, etc.). */ const PolyLineType GetHelperPolyLine( unsigned int index, double mmPerDisplayUnit, unsigned int displayHeight ); /** \brief Sets the position of the PreviewControlPoint. Automatically sets it visible.*/ void SetPreviewControlPoint( const Point2D& point ); /** \brief Marks the PreviewControlPoint as invisible.*/ void ResetPreviewContolPoint(); /** \brief Returns whether or not the PreviewControlPoint is visible.*/ bool IsPreviewControlPointVisible(); /** \brief Returns the coordinates of the PreviewControlPoint. */ Point2D GetPreviewControlPoint(); /** \brief Returns the number of features available for this PlanarFigure * (such as, radius, area, ...). */ virtual unsigned int GetNumberOfFeatures() const; /** \brief Returns the name (identifier) of the specified features. */ const char *GetFeatureName( unsigned int index ) const; /** \brief Returns the physical unit of the specified features. */ const char *GetFeatureUnit( unsigned int index ) const; /** Returns quantity of the specified feature (e.g., length, radius, * area, ... ) */ double GetQuantity( unsigned int index ) const; /** \brief Returns true if the feature with the specified index exists and * is active (an inactive feature may e.g. be the area of a non-closed * polygon. */ bool IsFeatureActive( unsigned int index ) const; /** \brief Returns true if the feature with the specified index exists and is set visible */ bool IsFeatureVisible( unsigned int index ) const; /** \brief Defines if the feature with the specified index will be shown as an * overlay in the RenderWindow */ void SetFeatureVisible( unsigned int index, bool visible ); /** \brief Calculates quantities of all features of this planar figure. */ virtual void EvaluateFeatures(); /** \brief Intherited from parent */ virtual void UpdateOutputInformation(); /** \brief Intherited from parent */ virtual void SetRequestedRegionToLargestPossibleRegion(); /** \brief Intherited from parent */ virtual bool RequestedRegionIsOutsideOfTheBufferedRegion(); /** \brief Intherited from parent */ virtual bool VerifyRequestedRegion(); /** \brief Intherited from parent */ virtual void SetRequestedRegion(itk::DataObject *data); /** \brief Returns the current number of polylines */ virtual unsigned short GetPolyLinesSize(); /** \brief Returns the current number of helperpolylines */ virtual unsigned short GetHelperPolyLinesSize(); /** \brief Returns whether a helper polyline should be painted or not */ virtual bool IsHelperToBePainted(unsigned int index); /** \brief Returns true if the planar figure is reset to "add points" mode * when a point is selected. * * Default return value is false. Subclasses can overwrite this method and * execute any reset / initialization statements required. */ virtual bool ResetOnPointSelect(); /** \brief removes the point with the given index from the list of controlpoints. */ virtual void RemoveControlPoint( unsigned int index ); /** \brief Removes last control point */ virtual void RemoveLastControlPoint(); /** \brief Copies contents and state of a figre provided as parameter to the current object. Requires a matching type of both figures. */ void DeepCopy(Self::Pointer oldFigure); /** \brief Allow sub-classes to apply constraints on control points. * * Sub-classes can define spatial constraints to certain control points by * overwriting this method and returning a constrained point. By default, * the points are constrained by the image bounds. */ virtual Point2D ApplyControlPointConstraints( unsigned int /*index*/, const Point2D& point ); protected: PlanarFigure(); virtual ~PlanarFigure(); /** \brief Set the initial number of control points of the planar figure */ void ResetNumberOfControlPoints( int numberOfControlPoints ); /** Adds feature (e.g., circumference, radius, angle, ...) to feature vector * of a planar figure object and returns integer ID for the feature element. * Should be called in sub-class constructors. */ virtual unsigned int AddFeature( const char *featureName, const char *unitName ); /** Sets the name of the specified feature. INTERNAL METHOD. */ void SetFeatureName( unsigned int index, const char *featureName ); /** Sets the physical unit of the specified feature. INTERNAL METHOD. */ void SetFeatureUnit( unsigned int index, const char *unitName ); /** Sets quantity of the specified feature. INTERNAL METHOD. */ void SetQuantity( unsigned int index, double quantity ); /** Sets the specified feature as active. INTERAL METHOD. */ void ActivateFeature( unsigned int index ); /** Sets the specified feature as active. INTERAL METHOD. */ void DeactivateFeature( unsigned int index ); /** \brief Generates the poly-line representation of the planar figure. * Must be implemented in sub-classes. */ virtual void GeneratePolyLine() = 0; /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom. * Must be implemented in sub-classes. */ virtual void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight) = 0; /** \brief Calculates quantities of all features of this planar figure. * Must be implemented in sub-classes. */ virtual void EvaluateFeaturesInternal() = 0; /** \brief Initializes the TimeSlicedGeometry describing the (time-resolved) * geometry of this figure. Note that each time step holds one Geometry2D. */ virtual void InitializeTimeSlicedGeometry( unsigned int timeSteps = 1 ); /** \brief defines the number of PolyLines that will be available */ void SetNumberOfPolyLines( unsigned int numberOfPolyLines ); /** \brief Append a point to the PolyLine # index */ void AppendPointToPolyLine( unsigned int index, PolyLineElement element ); /** \brief clears the list of PolyLines. Call before re-calculating a new Polyline. */ void ClearPolyLines(); /** \brief defines the number of HelperPolyLines that will be available */ void SetNumberOfHelperPolyLines( unsigned int numberOfHelperPolyLines ); /** \brief Append a point to the HelperPolyLine # index */ void AppendPointToHelperPolyLine( unsigned int index, PolyLineElement element ); /** \brief clears the list of HelperPolyLines. Call before re-calculating a new HelperPolyline. */ void ClearHelperPolyLines(); virtual void PrintSelf( std::ostream& os, itk::Indent indent ) const; ControlPointListType m_ControlPoints; unsigned int m_NumberOfControlPoints; // Currently selected control point; -1 means no point selected int m_SelectedControlPoint; std::vector m_PolyLines; std::vector m_HelperPolyLines; BoolContainerType::Pointer m_HelperPolyLinesToBePainted; // this point is used to store the coordiantes an additional 'ControlPoint' that is rendered // when the mouse cursor is above the figure (and not a control-point) and when the // property 'planarfigure.isextendable' is set to true Point2D m_PreviewControlPoint; bool m_PreviewControlPointVisible; bool m_FigurePlaced; private: struct Feature { Feature( const char *name, const char *unit ) : Name( name ), Unit( unit ), Quantity( 0.0 ), Active( true ), Visible( true ) { }; std::string Name; std::string Unit; double Quantity; bool Active; bool Visible; }; Geometry2D *m_Geometry2D; bool m_PolyLineUpToDate; bool m_HelperLinesUpToDate; bool m_FeaturesUpToDate; // Vector of features available for this geometric figure typedef std::vector< Feature > FeatureVectorType; FeatureVectorType m_Features; unsigned long m_FeaturesMTime; // this pair is used to store the mmInDisplayUnits (m_DisplaySize.first) and the displayHeight (m_DisplaySize.second) // that the helperPolyLines have been calculated for. // It's used to determine whether or not GetHelperPolyLine() needs to recalculate the HelperPolyLines. std::pair m_DisplaySize; }; } // namespace mitk #endif //_MITK_PLANAR_FIGURE_H_ diff --git a/Modules/PlanarFigure/Interactions/mitkPlanarFigureInteractor.cpp b/Modules/PlanarFigure/Interactions/mitkPlanarFigureInteractor.cpp index 4b58daf048..72c72c9392 100644 --- a/Modules/PlanarFigure/Interactions/mitkPlanarFigureInteractor.cpp +++ b/Modules/PlanarFigure/Interactions/mitkPlanarFigureInteractor.cpp @@ -1,1082 +1,1092 @@ /*=================================================================== 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. ===================================================================*/ #define PLANARFIGUREINTERACTOR_DBG MITK_DEBUG("PlanarFigureInteractor") << __LINE__ << ": " #include "mitkPlanarFigureInteractor.h" #include "mitkPointOperation.h" #include "mitkPositionEvent.h" #include "mitkPlanarFigure.h" #include "mitkPlanarPolygon.h" #include "mitkStatusBar.h" #include "mitkDataNode.h" #include "mitkInteractionConst.h" #include "mitkAction.h" #include "mitkStateEvent.h" #include "mitkOperationEvent.h" #include "mitkUndoController.h" #include "mitkStateMachineFactory.h" #include "mitkStateTransitionOperation.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateOr.h" //how precise must the user pick the point //default value mitk::PlanarFigureInteractor ::PlanarFigureInteractor(const char * type, DataNode* dataNode, int /* n */ ) : Interactor( type, dataNode ), m_Precision( 6.5 ), m_MinimumPointDistance( 25.0 ), m_IsHovering( false ), m_LastPointWasValid( false ) { } mitk::PlanarFigureInteractor::~PlanarFigureInteractor() { } void mitk::PlanarFigureInteractor::SetPrecision( mitk::ScalarType precision ) { m_Precision = precision; } void mitk::PlanarFigureInteractor::SetMinimumPointDistance( ScalarType minimumDistance ) { m_MinimumPointDistance = minimumDistance; } // Overwritten since this class can handle it better! float mitk::PlanarFigureInteractor ::CanHandleEvent(StateEvent const* stateEvent) const { // If it is a key event that can be handled in the current state, // then return 0.5 mitk::DisplayPositionEvent const *disPosEvent = dynamic_cast (stateEvent->GetEvent()); const mitk::PositionEvent *positionEvent = dynamic_cast< const mitk::PositionEvent * > ( stateEvent->GetEvent() ); if ( positionEvent == NULL ) { return 0.0; } // Key event handling: if (disPosEvent == NULL) { // Check if the current state has a transition waiting for that key event. if (this->GetCurrentState()->GetTransition(stateEvent->GetId())!=NULL) { return 0.5; } else { return 0.0; } } mitk::PlanarFigure *planarFigure = dynamic_cast( m_DataNode->GetData() ); if ( planarFigure != NULL ) { bool selected = false; m_DataNode->GetBoolProperty("selected", selected); mitk::Point3D worldPoint3D = positionEvent->GetWorldPosition(); mitk::Geometry2D *planarFigureGeometry2D = dynamic_cast< Geometry2D * >( planarFigure->GetGeometry( 0 ) ); double planeThickness = planarFigureGeometry2D->GetExtentInMM( 2 ); if ( planarFigure->IsPlaced() && planarFigureGeometry2D->Distance( worldPoint3D ) > planeThickness ) { // don't react, when interaction is too far away return 0.0; } if ( planarFigure->IsPlaced() && selected ) { // if a figure is placed, it has to return a higher value than one // that is not, even if the new one is already 'selected' return 0.75; } else if ( planarFigure->IsPlaced() ) { return 0.7; } else if ( selected ) { return 0.6; } // else fall through } return 0.42; } bool mitk::PlanarFigureInteractor ::ExecuteAction( Action *action, mitk::StateEvent const *stateEvent ) { bool ok = false; // Check corresponding data; has to be sub-class of mitk::PlanarFigure mitk::PlanarFigure *planarFigure = dynamic_cast< mitk::PlanarFigure * >( m_DataNode->GetData() ); if ( planarFigure == NULL ) { return false; } // Get the timestep to also support 3D+t const mitk::Event *theEvent = stateEvent->GetEvent(); int timeStep = 0; //mitk::ScalarType timeInMS = 0.0; if ( theEvent ) { if (theEvent->GetSender() != NULL) { timeStep = theEvent->GetSender()->GetTimeStep( planarFigure ); //timeInMS = theEvent->GetSender()->GetTime(); } } // Get Geometry2D of PlanarFigure mitk::Geometry2D *planarFigureGeometry = dynamic_cast< mitk::Geometry2D * >( planarFigure->GetGeometry( timeStep ) ); // Get the Geometry2D of the window the user interacts with (for 2D point // projection) mitk::BaseRenderer *renderer = NULL; const Geometry2D *projectionPlane = NULL; if ( theEvent ) { renderer = theEvent->GetSender(); projectionPlane = renderer->GetCurrentWorldGeometry2D(); } // TODO: Check if display and PlanarFigure geometries are parallel (if they are PlaneGeometries) switch (action->GetActionId()) { case AcDONOTHING: PLANARFIGUREINTERACTOR_DBG << "AcDONOTHING"; ok = true; break; case AcCHECKOBJECT: { PLANARFIGUREINTERACTOR_DBG << "AcCHECKOBJECT"; if ( planarFigure->IsPlaced() ) { this->HandleEvent( new mitk::StateEvent( EIDYES, NULL ) ); } else { this->HandleEvent( new mitk::StateEvent( EIDNO, NULL ) ); } ok = false; break; } case AcADD: { PLANARFIGUREINTERACTOR_DBG << "AcADD"; // Invoke event to notify listeners that placement of this PF starts now planarFigure->InvokeEvent( StartPlacementPlanarFigureEvent() ); // Use Geometry2D of the renderer clicked on for this PlanarFigure mitk::PlaneGeometry *planeGeometry = const_cast< mitk::PlaneGeometry * >( dynamic_cast< const mitk::PlaneGeometry * >( renderer->GetSliceNavigationController()->GetCurrentPlaneGeometry() ) ); if ( planeGeometry != NULL ) { planarFigureGeometry = planeGeometry; planarFigure->SetGeometry2D( planeGeometry ); } else { ok = false; break; } // Extract point in 2D world coordinates (relative to Geometry2D of // PlanarFigure) Point2D point2D; if ( !this->TransformPositionEventToPoint2D( stateEvent, point2D, planarFigureGeometry ) ) { ok = false; break; } // Place PlanarFigure at this point planarFigure->PlaceFigure( point2D ); // Re-evaluate features planarFigure->EvaluateFeatures(); //this->LogPrintPlanarFigureQuantities( planarFigure ); // Set a bool property indicating that the figure has been placed in // the current RenderWindow. This is required so that the same render // window can be re-aligned to the Geometry2D of the PlanarFigure later // on in an application. m_DataNode->SetBoolProperty( "PlanarFigureInitializedWindow", true, renderer ); // Update rendered scene renderer->GetRenderingManager()->RequestUpdateAll(); ok = true; break; } case AcMOVEPOINT: { PLANARFIGUREINTERACTOR_DBG << "AcMOVEPOINT"; bool isEditable = true; m_DataNode->GetBoolProperty( "planarfigure.iseditable", isEditable ); // Extract point in 2D world coordinates (relative to Geometry2D of // PlanarFigure) Point2D point2D; if ( !this->TransformPositionEventToPoint2D( stateEvent, point2D, planarFigureGeometry ) || !isEditable ) { ok = false; break; } // check if the control points shall be hidden during interaction bool hidecontrolpointsduringinteraction = false; m_DataNode->GetBoolProperty( "planarfigure.hidecontrolpointsduringinteraction", hidecontrolpointsduringinteraction ); // hide the control points if necessary m_DataNode->SetBoolProperty( "planarfigure.drawcontrolpoints", !hidecontrolpointsduringinteraction ); // Move current control point to this point planarFigure->SetCurrentControlPoint( point2D ); // Re-evaluate features planarFigure->EvaluateFeatures(); //this->LogPrintPlanarFigureQuantities( planarFigure ); // Update rendered scene renderer->GetRenderingManager()->RequestUpdateAll(); ok = true; break; } case AcCHECKNMINUS1: { PLANARFIGUREINTERACTOR_DBG << "AcCHECKNMINUS1"; if ( planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints() ) { // Initial placement finished: deselect control point and send an // event to notify application listeners planarFigure->Modified(); planarFigure->DeselectControlPoint(); planarFigure->InvokeEvent( EndPlacementPlanarFigureEvent() ); planarFigure->InvokeEvent( EndInteractionPlanarFigureEvent() ); planarFigure->SetProperty( "initiallyplaced", mitk::BoolProperty::New( true ) ); m_DataNode->SetBoolProperty( "planarfigure.drawcontrolpoints", true ); m_DataNode->Modified(); this->HandleEvent( new mitk::StateEvent( EIDYES, stateEvent->GetEvent() ) ); } else { this->HandleEvent( new mitk::StateEvent( EIDNO, stateEvent->GetEvent() ) ); } // Update rendered scene renderer->GetRenderingManager()->RequestUpdateAll(); ok = true; break; } case AcCHECKEQUALS1: { PLANARFIGUREINTERACTOR_DBG << "AcCHECKEQUALS1"; // NOTE: Action name is a bit misleading; this action checks whether // the figure has already the minimum number of required points to // be finished (by double-click) const mitk::PositionEvent *positionEvent = dynamic_cast< const mitk::PositionEvent * > ( stateEvent->GetEvent() ); if ( positionEvent == NULL ) { ok = false; break; } if ( planarFigure->GetNumberOfControlPoints() > planarFigure->GetMinimumNumberOfControlPoints() ) { // Initial placement finished: deselect control point and send an // event to notify application listeners planarFigure->Modified(); planarFigure->DeselectControlPoint(); planarFigure->RemoveLastControlPoint(); planarFigure->SetProperty( "initiallyplaced", mitk::BoolProperty::New( true ) ); m_DataNode->SetBoolProperty( "planarfigure.drawcontrolpoints", true ); m_DataNode->Modified(); planarFigure->InvokeEvent( EndPlacementPlanarFigureEvent() ); planarFigure->InvokeEvent( EndInteractionPlanarFigureEvent() ); this->HandleEvent( new mitk::StateEvent( EIDYES, NULL ) ); } else { this->HandleEvent( new mitk::StateEvent( EIDNO, NULL ) ); } // Update rendered scene renderer->GetRenderingManager()->RequestUpdateAll(); ok = true; break; } case AcCHECKPOINT: { PLANARFIGUREINTERACTOR_DBG << "AcCHECKPOINT"; // Check if the distance of the current point to the previously set point in display coordinates // is sufficient (if a previous point exists) // Extract display position const mitk::PositionEvent *positionEvent = dynamic_cast< const mitk::PositionEvent * > ( stateEvent->GetEvent() ); if ( positionEvent == NULL ) { ok = false; break; } m_LastPointWasValid = IsMousePositionAcceptableAsNewControlPoint( stateEvent, planarFigure ); if (m_LastPointWasValid) { this->HandleEvent( new mitk::StateEvent( EIDYES, stateEvent->GetEvent() ) ); } else { this->HandleEvent( new mitk::StateEvent( EIDNO, stateEvent->GetEvent() ) ); } ok = true; break; } case AcADDPOINT: { PLANARFIGUREINTERACTOR_DBG << "AcADDPOINT"; bool selected = false; bool isEditable = true; m_DataNode->GetBoolProperty("selected", selected); m_DataNode->GetBoolProperty( "planarfigure.iseditable", isEditable ); if ( !selected || !isEditable ) { ok = false; break; } + // If the planarFigure already has reached the maximum number + if ( planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints() ) + { + ok = false; + break; + } + // Extract point in 2D world coordinates (relative to Geometry2D of // PlanarFigure) Point2D point2D, projectedPoint; if ( !this->TransformPositionEventToPoint2D( stateEvent, point2D, planarFigureGeometry ) ) { ok = false; break; } - // TODO: check segement of polyline we clicked in + // TODO: check segment of polyline we clicked in int nextIndex = -1; // We only need to check which position to insert the control point // when interacting with a PlanarPolygon. For all other types // new control points will always be appended /* * Added check for "initiallyplaced" due to bug 13097: * * There are two possible cases in which a point can be inserted into a PlanarPolygon: * * 1. The figure is currently drawn -> the point will be appended at the end of the figure * 2. A point is inserted at a userdefined position after the initial placement of the figure is finished * * In the second case we need to determine the proper insertion index. In the first case the index always has * to be -1 so that the point is appended to the end. * * These changes are neccessary because of a mac os x specific issue: If a users draws a PlanarPolygon then the * next point to be added moves according to the mouse position. If then the user left clicks in order to add * a point one would assume the last move position is identical to the left click position. This is actually the * case for windows and linux but somehow NOT for mac. Because of the insertion logic of a new point in the * PlanarFigure then for mac the wrong current selected point is determined. * * With this check here this problem can be avoided. However a redesign of the insertion logic should be considered */ bool isFigureFinished = false; planarFigure->GetPropertyList()->GetBoolProperty( "initiallyplaced", isFigureFinished ); if ( dynamic_cast( planarFigure ) && isFigureFinished) { nextIndex = this->IsPositionOverFigure( stateEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer->GetDisplayGeometry(), projectedPoint ); } // Add point as new control point renderer->GetDisplayGeometry()->DisplayToWorld( projectedPoint, projectedPoint ); if ( planarFigure->IsPreviewControlPointVisible() ) { point2D = planarFigure->GetPreviewControlPoint(); } planarFigure->AddControlPoint( point2D, nextIndex ); if ( planarFigure->IsPreviewControlPointVisible() ) { planarFigure->SelectControlPoint( nextIndex ); planarFigure->ResetPreviewContolPoint(); } // Re-evaluate features planarFigure->EvaluateFeatures(); //this->LogPrintPlanarFigureQuantities( planarFigure ); // Update rendered scene renderer->GetRenderingManager()->RequestUpdateAll(); ok = true; break; } case AcDESELECTPOINT: { PLANARFIGUREINTERACTOR_DBG << "AcDESELECTPOINT"; - planarFigure->DeselectControlPoint(); + bool wasSelected = planarFigure->DeselectControlPoint(); - // Issue event so that listeners may update themselves - planarFigure->Modified(); - planarFigure->InvokeEvent( EndInteractionPlanarFigureEvent() ); + if ( wasSelected ) + { + // Issue event so that listeners may update themselves + planarFigure->Modified(); + planarFigure->InvokeEvent( EndInteractionPlanarFigureEvent() ); - m_DataNode->SetBoolProperty( "planarfigure.drawcontrolpoints", true ); - m_DataNode->SetBoolProperty( "planarfigure.ishovering", false ); - m_DataNode->Modified(); + m_DataNode->SetBoolProperty( "planarfigure.drawcontrolpoints", true ); + m_DataNode->SetBoolProperty( "planarfigure.ishovering", false ); + m_DataNode->Modified(); + } // falls through break; } case AcCHECKHOVERING: { PLANARFIGUREINTERACTOR_DBG << "AcCHECKHOVERING"; mitk::Point2D pointProjectedOntoLine; int previousControlPoint = mitk::PlanarFigureInteractor::IsPositionOverFigure( stateEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer->GetDisplayGeometry(), pointProjectedOntoLine ); bool isHovering = ( previousControlPoint != -1 ); int pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( stateEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer->GetDisplayGeometry() ); int initiallySelectedControlPoint = planarFigure->GetSelectedControlPoint(); if ( pointIndex >= 0 ) { // If mouse is above control point, mark it as selected planarFigure->SelectControlPoint( pointIndex ); // If mouse is hovering above a marker, it is also hovering above the figure isHovering = true; } else { // Mouse in not above control point --> deselect point planarFigure->DeselectControlPoint(); } bool renderingUpdateNeeded = true; if ( isHovering ) { if ( !m_IsHovering ) { // Invoke hover event once when the mouse is entering the figure area m_IsHovering = true; planarFigure->InvokeEvent( StartHoverPlanarFigureEvent() ); // Set bool property to indicate that planar figure is currently in "hovering" mode m_DataNode->SetBoolProperty( "planarfigure.ishovering", true ); renderingUpdateNeeded = true; } bool selected = false; bool isExtendable = false; bool isEditable = true; m_DataNode->GetBoolProperty("selected", selected); m_DataNode->GetBoolProperty("planarfigure.isextendable", isExtendable); m_DataNode->GetBoolProperty( "planarfigure.iseditable", isEditable ); if ( selected && isHovering && isExtendable && pointIndex == -1 && isEditable ) { const mitk::PositionEvent *positionEvent = dynamic_cast< const mitk::PositionEvent * > ( stateEvent->GetEvent() ); if ( positionEvent != NULL ) { renderer->GetDisplayGeometry()->DisplayToWorld( pointProjectedOntoLine, pointProjectedOntoLine ); planarFigure->SetPreviewControlPoint( pointProjectedOntoLine ); renderingUpdateNeeded = true; } } else { planarFigure->ResetPreviewContolPoint(); } if ( planarFigure->GetSelectedControlPoint() != initiallySelectedControlPoint ) { // the selected control point has changed -> rendering update necessary renderingUpdateNeeded = true; } this->HandleEvent( new mitk::StateEvent( EIDYES, stateEvent->GetEvent() ) ); // Return true: only this interactor is eligible to react on this event ok = true; } else { if ( m_IsHovering ) { planarFigure->ResetPreviewContolPoint(); // Invoke end-hover event once the mouse is exiting the figure area m_IsHovering = false; planarFigure->InvokeEvent( EndHoverPlanarFigureEvent() ); // Set bool property to indicate that planar figure is no longer in "hovering" mode m_DataNode->SetBoolProperty( "planarfigure.ishovering", false ); renderingUpdateNeeded = true; } this->HandleEvent( new mitk::StateEvent( EIDNO, NULL ) ); // Return false so that other (PlanarFigure) Interactors may react on this // event as well ok = false; } // Update rendered scene if necessray if ( renderingUpdateNeeded ) { renderer->GetRenderingManager()->RequestUpdateAll(); } break; } case AcCHECKSELECTED: { PLANARFIGUREINTERACTOR_DBG << "AcCHECKSELECTED"; bool selected = false; m_DataNode->GetBoolProperty("selected", selected); if ( selected ) { this->HandleEvent( new mitk::StateEvent( EIDYES, stateEvent->GetEvent() ) ); } else { // Invoke event to notify listeners that this planar figure should be selected planarFigure->InvokeEvent( SelectPlanarFigureEvent() ); this->HandleEvent( new mitk::StateEvent( EIDNO, NULL ) ); } } case AcSELECTPICKEDOBJECT: { PLANARFIGUREINTERACTOR_DBG << "AcSELECTPICKEDOBJECT"; //// Invoke event to notify listeners that this planar figure should be selected //planarFigure->InvokeEvent( SelectPlanarFigureEvent() ); //planarFigure->InvokeEvent( StartInteractionPlanarFigureEvent() ); // Check if planar figure is marked as "editable" bool isEditable = true; m_DataNode->GetBoolProperty( "planarfigure.iseditable", isEditable ); int pointIndex = -1; if ( isEditable ) { // If planar figure is editable, check if mouse is over a control point pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( stateEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer->GetDisplayGeometry() ); } // If editing is enabled and the mouse is currently over a control point, select it if ( pointIndex >= 0 ) { this->HandleEvent( new mitk::StateEvent( EIDYES, stateEvent->GetEvent() ) ); // Return true: only this interactor is eligible to react on this event ok = true; } else { // we're not hovering above a control point -> deselect! planarFigure->DeselectControlPoint(); this->HandleEvent( new mitk::StateEvent( EIDNO, stateEvent->GetEvent() ) ); // Return false so that other (PlanarFigure) Interactors may react on this // event as well ok = false; } ok = true; break; } case AcENTEROBJECT: { PLANARFIGUREINTERACTOR_DBG << "AcENTEROBJECT"; bool selected = false; m_DataNode->GetBoolProperty("selected", selected); // no need to invoke this if the figure is already selected if ( !selected ) { planarFigure->InvokeEvent( SelectPlanarFigureEvent() ); } planarFigure->InvokeEvent( ContextMenuPlanarFigureEvent() ); ok = true; // we HAVE TO proceed with 'EIDNO' here to ensure correct states // and convenient application behaviour this->HandleEvent( new mitk::StateEvent( EIDNO, stateEvent->GetEvent() ) ); break; } case AcSELECTPOINT: { PLANARFIGUREINTERACTOR_DBG << "AcSELECTPOINT"; // Invoke event to notify listeners that interaction with this PF starts now planarFigure->InvokeEvent( StartInteractionPlanarFigureEvent() ); // Reset the PlanarFigure if required if ( planarFigure->ResetOnPointSelect() ) { this->HandleEvent( new mitk::StateEvent( EIDYES, stateEvent->GetEvent() ) ); } else { this->HandleEvent( new mitk::StateEvent( EIDNO, stateEvent->GetEvent() ) ); } ok = true; break; } case AcREMOVEPOINT: { PLANARFIGUREINTERACTOR_DBG << "AcREMOVEPOINT"; bool isExtendable = false; m_DataNode->GetBoolProperty("planarfigure.isextendable", isExtendable); if ( isExtendable ) { int selectedControlPoint = planarFigure->GetSelectedControlPoint(); planarFigure->RemoveControlPoint( selectedControlPoint ); // Re-evaluate features planarFigure->EvaluateFeatures(); planarFigure->Modified(); m_DataNode->SetBoolProperty( "planarfigure.drawcontrolpoints", true ); planarFigure->InvokeEvent( EndInteractionPlanarFigureEvent() ); renderer->GetRenderingManager()->RequestUpdateAll(); this->HandleEvent( new mitk::StateEvent( EIDYES, NULL ) ); } else { this->HandleEvent( new mitk::StateEvent( EIDNO, NULL ) ); } } //case AcMOVEPOINT: //case AcMOVESELECTED: // { // // Update the display // renderer->GetRenderingManager()->RequestUpdateAll(); // ok = true; // break; // } //case AcFINISHMOVE: // { // ok = true; // break; // } default: return Superclass::ExecuteAction( action, stateEvent ); } return ok; } bool mitk::PlanarFigureInteractor::TransformPositionEventToPoint2D( const StateEvent *stateEvent, Point2D &point2D, const Geometry2D *planarFigureGeometry ) { // Extract world position, and from this position on geometry, if // available const mitk::PositionEvent *positionEvent = dynamic_cast< const mitk::PositionEvent * > ( stateEvent->GetEvent() ); if ( positionEvent == NULL ) { return false; } mitk::Point3D worldPoint3D = positionEvent->GetWorldPosition(); // TODO: proper handling of distance tolerance if ( planarFigureGeometry->Distance( worldPoint3D ) > 0.1 ) { return false; } // Project point onto plane of this PlanarFigure planarFigureGeometry->Map( worldPoint3D, point2D ); return true; } bool mitk::PlanarFigureInteractor::TransformObjectToDisplay( const mitk::Point2D &point2D, mitk::Point2D &displayPoint, const mitk::Geometry2D *objectGeometry, const mitk::Geometry2D *rendererGeometry, const mitk::DisplayGeometry *displayGeometry ) const { mitk::Point3D point3D; // Map circle point from local 2D geometry into 3D world space objectGeometry->Map( point2D, point3D ); // TODO: proper handling of distance tolerance if ( displayGeometry->Distance( point3D ) < 0.1 ) { // Project 3D world point onto display geometry rendererGeometry->Map( point3D, displayPoint ); displayGeometry->WorldToDisplay( displayPoint, displayPoint ); return true; } return false; } bool mitk::PlanarFigureInteractor::IsPointNearLine( const mitk::Point2D& point, const mitk::Point2D& startPoint, const mitk::Point2D& endPoint, mitk::Point2D& projectedPoint ) const { mitk::Vector2D n1 = endPoint - startPoint; n1.Normalize(); // Determine dot products between line vector and startpoint-point / endpoint-point vectors double l1 = n1 * (point - startPoint); double l2 = -n1 * (point - endPoint); // Determine projection of specified point onto line defined by start / end point mitk::Point2D crossPoint = startPoint + n1 * l1; projectedPoint = crossPoint; // Point is inside encompassing rectangle IF // - its distance to its projected point is small enough // - it is not further outside of the line than the defined tolerance if (((crossPoint.SquaredEuclideanDistanceTo(point) < 20.0) && (l1 > 0.0) && (l2 > 0.0)) || endPoint.SquaredEuclideanDistanceTo(point) < 20.0 || startPoint.SquaredEuclideanDistanceTo(point) < 20.0) { return true; } return false; } int mitk::PlanarFigureInteractor::IsPositionOverFigure( const StateEvent *stateEvent, PlanarFigure *planarFigure, const Geometry2D *planarFigureGeometry, const Geometry2D *rendererGeometry, const DisplayGeometry *displayGeometry, Point2D& pointProjectedOntoLine ) const { // Extract display position const mitk::PositionEvent *positionEvent = dynamic_cast< const mitk::PositionEvent * > ( stateEvent->GetEvent() ); if ( positionEvent == NULL ) { return -1; } mitk::Point2D displayPosition = positionEvent->GetDisplayPosition(); // Iterate over all polylines of planar figure, and check if // any one is close to the current display position typedef mitk::PlanarFigure::PolyLineType VertexContainerType; mitk::Point2D worldPoint2D, displayControlPoint; mitk::Point3D worldPoint3D; for ( unsigned short loop = 0; loop < planarFigure->GetPolyLinesSize(); ++loop ) { const VertexContainerType polyLine = planarFigure->GetPolyLine( loop ); Point2D polyLinePoint; Point2D firstPolyLinePoint; Point2D previousPolyLinePoint; bool firstPoint = true; for ( VertexContainerType::const_iterator it = polyLine.begin(); it != polyLine.end(); ++it ) { // Get plane coordinates of this point of polyline (if possible) if ( !this->TransformObjectToDisplay( it->Point, polyLinePoint, planarFigureGeometry, rendererGeometry, displayGeometry ) ) { break; // Poly line invalid (not on current 2D plane) --> skip it } if ( firstPoint ) { firstPolyLinePoint = polyLinePoint; firstPoint = false; } else if ( this->IsPointNearLine( displayPosition, previousPolyLinePoint, polyLinePoint, pointProjectedOntoLine ) ) { // Point is close enough to line segment --> Return index of the segment return it->Index; } previousPolyLinePoint = polyLinePoint; } // For closed figures, also check last line segment if ( planarFigure->IsClosed() && this->IsPointNearLine( displayPosition, polyLinePoint, firstPolyLinePoint, pointProjectedOntoLine ) ) { return 0; // Return index of first control point } } return -1; } int mitk::PlanarFigureInteractor::IsPositionInsideMarker( const StateEvent *stateEvent, const PlanarFigure *planarFigure, const Geometry2D *planarFigureGeometry, const Geometry2D *rendererGeometry, const DisplayGeometry *displayGeometry ) const { // Extract display position const mitk::PositionEvent *positionEvent = dynamic_cast< const mitk::PositionEvent * > ( stateEvent->GetEvent() ); if ( positionEvent == NULL ) { return -1; } mitk::Point2D displayPosition = positionEvent->GetDisplayPosition(); // Iterate over all control points of planar figure, and check if // any one is close to the current display position mitk::Point2D worldPoint2D, displayControlPoint; mitk::Point3D worldPoint3D; int numberOfControlPoints = planarFigure->GetNumberOfControlPoints(); for ( int i=0; iTransformObjectToDisplay( planarFigure->GetControlPoint(i), displayControlPoint, planarFigureGeometry, rendererGeometry, displayGeometry ) ) { // TODO: variable size of markers if ( displayPosition.SquaredEuclideanDistanceTo( displayControlPoint ) < 20.0 ) { return i; } } } //for ( it = controlPoints.begin(); it != controlPoints.end(); ++it ) //{ // Point2D displayControlPoint; // if ( this->TransformObjectToDisplay( it->Point, displayControlPoint, // planarFigureGeometry, rendererGeometry, displayGeometry ) ) // { // // TODO: variable size of markers // if ( (abs(displayPosition[0] - displayControlPoint[0]) < 4 ) // && (abs(displayPosition[1] - displayControlPoint[1]) < 4 ) ) // { // return index; // } // } //} return -1; } void mitk::PlanarFigureInteractor::LogPrintPlanarFigureQuantities( const PlanarFigure *planarFigure ) { MITK_INFO << "PlanarFigure: " << planarFigure->GetNameOfClass(); for ( unsigned int i = 0; i < planarFigure->GetNumberOfFeatures(); ++i ) { MITK_INFO << "* " << planarFigure->GetFeatureName( i ) << ": " << planarFigure->GetQuantity( i ) << " " << planarFigure->GetFeatureUnit( i ); } } bool mitk::PlanarFigureInteractor::IsMousePositionAcceptableAsNewControlPoint( mitk::StateEvent const * stateEvent, const PlanarFigure* planarFigure ) { assert(stateEvent && planarFigure); BaseRenderer* renderer = stateEvent->GetEvent()->GetSender(); assert(renderer); // Get the timestep to support 3D+t int timeStep( renderer->GetTimeStep( planarFigure ) ); // Get current display position of the mouse //Point2D currentDisplayPosition = positionEvent->GetDisplayPosition(); // Check if a previous point has been set bool tooClose = false; const Geometry2D *renderingPlane = renderer->GetCurrentWorldGeometry2D(); mitk::Geometry2D *planarFigureGeometry = dynamic_cast< mitk::Geometry2D * >( planarFigure->GetGeometry( timeStep ) ); Point2D point2D, correctedPoint; // Get the point2D from the positionEvent if ( !this->TransformPositionEventToPoint2D( stateEvent, point2D, planarFigureGeometry ) ) { return false; } // apply the controlPoint constraints of the planarFigure to get the // coordinates that would actually be used. correctedPoint = const_cast( planarFigure )->ApplyControlPointConstraints( 0, point2D ); // map the 2D coordinates of the new point to world-coordinates // and transform those to display-coordinates mitk::Point3D newPoint3D; planarFigureGeometry->Map( correctedPoint, newPoint3D ); mitk::Point2D newDisplayPosition; renderingPlane->Map( newPoint3D, newDisplayPosition ); renderer->GetDisplayGeometry()->WorldToDisplay( newDisplayPosition, newDisplayPosition ); for( int i=0; i < (int)planarFigure->GetNumberOfControlPoints(); i++ ) { if ( i != planarFigure->GetSelectedControlPoint() ) { // Try to convert previous point to current display coordinates mitk::Point3D previousPoint3D; // map the 2D coordinates of the control-point to world-coordinates planarFigureGeometry->Map( planarFigure->GetControlPoint( i ), previousPoint3D ); if ( renderer->GetDisplayGeometry()->Distance( previousPoint3D ) < 0.1 ) // ugly, but assert makes this work { mitk::Point2D previousDisplayPosition; // transform the world-coordinates into display-coordinates renderingPlane->Map( previousPoint3D, previousDisplayPosition ); renderer->GetDisplayGeometry()->WorldToDisplay( previousDisplayPosition, previousDisplayPosition ); //Calculate the distance. We use display-coordinates here to make // the check independent of the zoom-level of the rendering scene. double a = newDisplayPosition[0] - previousDisplayPosition[0]; double b = newDisplayPosition[1] - previousDisplayPosition[1]; // If point is to close, do not set a new point tooClose = (a * a + b * b < m_MinimumPointDistance ); } if ( tooClose ) return false; // abort loop early } } return !tooClose; // default }