diff --git a/Modules/PlanarFigure/include/mitkPlanarBezierCurve.h b/Modules/PlanarFigure/include/mitkPlanarBezierCurve.h index f15f649e7e..f3e9cff84c 100644 --- a/Modules/PlanarFigure/include/mitkPlanarBezierCurve.h +++ b/Modules/PlanarFigure/include/mitkPlanarBezierCurve.h @@ -1,62 +1,69 @@ /*=================================================================== 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 mitkPlanarBezierCurve_h #define mitkPlanarBezierCurve_h #include #include namespace mitk { class MITKPLANARFIGURE_EXPORT PlanarBezierCurve : public PlanarFigure { public: mitkClassMacro(PlanarBezierCurve, PlanarFigure) itkFactorylessNewMacro(Self) itkCloneMacro(Self) unsigned int GetNumberOfSegments() const; void SetNumberOfSegments(unsigned int numSegments); + /** + * \brief Returns the id of the control-point that corresponds to the given + * polyline-point. + */ + int GetControlPointForPolylinePoint( int indexOfPolylinePoint, int polyLineIndex ) const; + + virtual unsigned int GetMaximumNumberOfControlPoints() const override; virtual unsigned int GetMinimumNumberOfControlPoints() const override; virtual bool IsHelperToBePainted(unsigned int index) override; const unsigned int FEATURE_ID_LENGTH; virtual bool Equals(const mitk::PlanarFigure& other)const override; protected: PlanarBezierCurve(); virtual ~PlanarBezierCurve(); mitkCloneMacro(Self) virtual void EvaluateFeaturesInternal() override; virtual void GenerateHelperPolyLine(double, unsigned int) override; virtual void GeneratePolyLine() override; private: Point2D ComputeDeCasteljauPoint(ScalarType t); std::vector m_DeCasteljauPoints; unsigned int m_NumberOfSegments; }; } #endif diff --git a/Modules/PlanarFigure/include/mitkPlanarFigure.h b/Modules/PlanarFigure/include/mitkPlanarFigure.h index 40f2ca3f0d..2c5cd7855d 100644 --- a/Modules/PlanarFigure/include/mitkPlanarFigure.h +++ b/Modules/PlanarFigure/include/mitkPlanarFigure.h @@ -1,406 +1,412 @@ /*=================================================================== 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 #include "mitkBaseData.h" #include "mitkCommon.h" #include namespace mitk { class PlaneGeometry; /** * \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 MITKPLANARFIGURE_EXPORT PlanarFigure : public BaseData { public: mitkClassMacro( PlanarFigure, BaseData ) itkCloneMacro( Self ) typedef Point2D PolyLineElement; typedef itk::VectorContainer< unsigned long, bool> BoolContainerType; typedef std::deque< Point2D > ControlPointListType; typedef std::vector< 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 SetPlaneGeometry( mitk::PlaneGeometry *geometry ); /** \brief Returns (previously set) 2D geometry of this figure. */ virtual const PlaneGeometry *GetPlaneGeometry() 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 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 the id of the control-point that corresponds to the given + * polyline-point. + */ + int GetControlPointForPolylinePoint( int indexOfPolylinePoint, int polyLineIndex ) 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() override; /** \brief Intherited from parent */ virtual void SetRequestedRegionToLargestPossibleRegion() override; /** \brief Intherited from parent */ virtual bool RequestedRegionIsOutsideOfTheBufferedRegion() override; /** \brief Intherited from parent */ virtual bool VerifyRequestedRegion() override; /** \brief Intherited from parent */ virtual void SetRequestedRegion( const itk::DataObject *data) override; /** \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 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 ); /** * \brief Compare two PlanarFigure objects * Note: all subclasses have to implement the method on their own. */ virtual bool Equals(const mitk::PlanarFigure& other) const; protected: PlanarFigure(); virtual ~PlanarFigure(); PlanarFigure(const Self& other); /** \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 TimeGeometry describing the (time-resolved) * geometry of this figure. Note that each time step holds one PlaneGeometry. */ virtual void InitializeTimeGeometry( unsigned int timeSteps = 1 ) override; /** \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 override; 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: // not implemented to prevent PlanarFigure::New() calls which would create an itk::Object. static Pointer New(); 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; }; virtual itk::LightObject::Pointer InternalClone() const override = 0; PlaneGeometry *m_PlaneGeometry; 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; }; MITKPLANARFIGURE_EXPORT bool Equal( const mitk::PlanarFigure& leftHandSide, const mitk::PlanarFigure& rightHandSide, ScalarType eps, bool verbose ); } // namespace mitk #endif //_MITK_PLANAR_FIGURE_H_ diff --git a/Modules/PlanarFigure/include/mitkPlanarSubdivisionPolygon.h b/Modules/PlanarFigure/include/mitkPlanarSubdivisionPolygon.h index 917aaede51..be437a12bb 100644 --- a/Modules/PlanarFigure/include/mitkPlanarSubdivisionPolygon.h +++ b/Modules/PlanarFigure/include/mitkPlanarSubdivisionPolygon.h @@ -1,109 +1,114 @@ /*=================================================================== 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_SUBDIVISION_POLYGON_H_ #define _MITK_PLANAR_SUBDIVISION_POLYGON_H_ #include "mitkPlanarFigure.h" #include #include "mitkPlanarPolygon.h" namespace mitk { class PlaneGeometry; /** * \brief Implementation of PlanarFigure representing a polygon * with two or more control points */ class MITKPLANARFIGURE_EXPORT PlanarSubdivisionPolygon : public PlanarPolygon { public: mitkClassMacro( PlanarSubdivisionPolygon, PlanarFigure ); itkFactorylessNewMacro(Self) itkCloneMacro(Self) /** \brief Subdivision Polygon has 3 control points per definition. */ unsigned int GetMinimumNumberOfControlPoints() const override { return 3; } /** \brief Polygon maximum number of control points is principally not limited. */ unsigned int GetMaximumNumberOfControlPoints() const override { return 1000; } /** \brief How many times should we generate a round of subdivisions? */ unsigned int GetSubdivisionRounds() const { return m_SubdivisionRounds; } void SetSubdivisionRounds( int subdivisionRounds ) { m_SubdivisionRounds = subdivisionRounds; } + /** + * \brief Returns the id of the control-point that corresponds to the given + * polyline-point. + */ + int GetControlPointForPolylinePoint( int indexOfPolylinePoint, int polyLineIndex ) const; /** \brief Parameter w_tension defines the tension. * the higher w_tension, the lower the "tension" on points. * Rule: 0 < w_tension < 0.1 * 0.0625 (1 / 16) seems to be a good value. */ float GetTensionParameter() const { return m_TensionParameter; } void SetTensionParameter(float tensionParameter ) { m_TensionParameter = tensionParameter; } std::vector CheckForLineIntersection( const Point2D& p1, const Point2D& p2 ) const; void IncreaseSubdivisions(); void DecreaseSubdivisions(); virtual bool Equals(const mitk::PlanarFigure& other) const override; protected: PlanarSubdivisionPolygon(); virtual ~PlanarSubdivisionPolygon(); mitkCloneMacro(Self); /** \brief Generates the poly-line representation of the planar figure. */ virtual void GeneratePolyLine() override; float m_TensionParameter; int m_SubdivisionRounds; private: }; } // namespace mitk #endif //_MITK_PLANAR_SUBDIVISION_POLYGON_H_ diff --git a/Modules/PlanarFigure/src/DataManagement/mitkPlanarBezierCurve.cpp b/Modules/PlanarFigure/src/DataManagement/mitkPlanarBezierCurve.cpp index cab3d12c23..7c994ad670 100644 --- a/Modules/PlanarFigure/src/DataManagement/mitkPlanarBezierCurve.cpp +++ b/Modules/PlanarFigure/src/DataManagement/mitkPlanarBezierCurve.cpp @@ -1,136 +1,165 @@ /*=================================================================== 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 "mitkPlanarBezierCurve.h" #include #include mitk::PlanarBezierCurve::PlanarBezierCurve() : FEATURE_ID_LENGTH(Superclass::AddFeature("Length", "mm")), m_NumberOfSegments(100) { this->ResetNumberOfControlPoints(2); this->SetNumberOfPolyLines(1); this->SetNumberOfHelperPolyLines(1); } mitk::PlanarBezierCurve::~PlanarBezierCurve() { } void mitk::PlanarBezierCurve::EvaluateFeaturesInternal() { double length = 0.0; for (unsigned int i = 0; i < m_NumberOfSegments; ++i) length += static_cast(m_PolyLines[0][i]).EuclideanDistanceTo(static_cast(m_PolyLines[0][i + 1])); this->SetQuantity(FEATURE_ID_LENGTH, length); } unsigned int mitk::PlanarBezierCurve::GetNumberOfSegments() const { return m_NumberOfSegments; } void mitk::PlanarBezierCurve::SetNumberOfSegments(unsigned int numSegments) { m_NumberOfSegments = std::max(1U, numSegments); if (this->IsPlaced()) { this->GeneratePolyLine(); this->Modified(); } } void mitk::PlanarBezierCurve::GenerateHelperPolyLine(double, unsigned int) { this->ClearHelperPolyLines(); unsigned int numHelperPolyLinePoints = m_ControlPoints.size(); for (unsigned int i = 0; i < numHelperPolyLinePoints; ++i) this->AppendPointToHelperPolyLine(0, m_ControlPoints[i]); } void mitk::PlanarBezierCurve::GeneratePolyLine() { this->ClearPolyLines(); const unsigned int numPolyLinePoints = m_NumberOfSegments + 1; for (unsigned int i = 0; i < numPolyLinePoints; ++i) this->AppendPointToPolyLine(0, this->ComputeDeCasteljauPoint(i / static_cast(m_NumberOfSegments))); } mitk::Point2D mitk::PlanarBezierCurve::ComputeDeCasteljauPoint(mitk::ScalarType t) { unsigned int n = m_ControlPoints.size() - 1; if (m_DeCasteljauPoints.size() != n) m_DeCasteljauPoints.resize(n); for (unsigned int i = 0; i < n; ++i) { m_DeCasteljauPoints[i][0] = (1 - t) * m_ControlPoints[i][0] + t * m_ControlPoints[i + 1][0]; m_DeCasteljauPoints[i][1] = (1 - t) * m_ControlPoints[i][1] + t * m_ControlPoints[i + 1][1]; } for (--n; n > 0; --n) { for (unsigned int i = 0; i < n; ++i) { m_DeCasteljauPoints[i][0] = (1 - t) * m_DeCasteljauPoints[i][0] + t * m_DeCasteljauPoints[i + 1][0]; m_DeCasteljauPoints[i][1] = (1 - t) * m_DeCasteljauPoints[i][1] + t * m_DeCasteljauPoints[i + 1][1]; } } return m_DeCasteljauPoints[0]; } +int mitk::PlanarBezierCurve::GetControlPointForPolylinePoint( int indexOfPolylinePoint, int polyLineIndex ) const +{ + mitk::PlanarFigure::PolyLineType polyLine = GetPolyLine( polyLineIndex ); + if ( indexOfPolylinePoint > polyLine.size() ) + { + return -1; + } + + mitk::PlanarFigure::ControlPointListType::const_iterator elem; + mitk::PlanarFigure::ControlPointListType::const_iterator first = m_ControlPoints.cbegin(); + mitk::PlanarFigure::ControlPointListType::const_iterator end = m_ControlPoints.cend(); + + mitk::PlanarFigure::PolyLineType::const_iterator polyLineIter; + mitk::PlanarFigure::PolyLineType::const_iterator polyLineEnd = polyLine.cend(); + mitk::PlanarFigure::PolyLineType::const_iterator polyLineStart = polyLine.cbegin(); + polyLineStart += indexOfPolylinePoint; + + for ( polyLineIter = polyLineStart; polyLineIter != polyLineEnd; ++polyLineIter ) + { + elem = std::find( first, end, *polyLineIter ); + if ( elem != end ) + { + return std::distance( first, elem ); + } + } + + return GetNumberOfControlPoints(); +} + unsigned int mitk::PlanarBezierCurve::GetMaximumNumberOfControlPoints() const { return std::numeric_limits::max(); } unsigned int mitk::PlanarBezierCurve::GetMinimumNumberOfControlPoints() const { return 2; } bool mitk::PlanarBezierCurve::IsHelperToBePainted(unsigned int index) { return index == 0 && m_ControlPoints.size() > 2; } bool mitk::PlanarBezierCurve::Equals(const PlanarFigure &other) const { const mitk::PlanarBezierCurve* otherBezierCurve = dynamic_cast(&other); if ( otherBezierCurve ) { if( this->m_NumberOfSegments != otherBezierCurve->m_NumberOfSegments ) return false; if( this->m_DeCasteljauPoints != otherBezierCurve->m_DeCasteljauPoints ) return false; return Superclass::Equals(other); } else { return false; } } diff --git a/Modules/PlanarFigure/src/DataManagement/mitkPlanarFigure.cpp b/Modules/PlanarFigure/src/DataManagement/mitkPlanarFigure.cpp index c8c868f524..3712e7df4f 100644 --- a/Modules/PlanarFigure/src/DataManagement/mitkPlanarFigure.cpp +++ b/Modules/PlanarFigure/src/DataManagement/mitkPlanarFigure.cpp @@ -1,797 +1,803 @@ /*=================================================================== 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 "mitkPlaneGeometry.h" #include #include #include mitk::PlanarFigure::PlanarFigure() : m_SelectedControlPoint( -1 ), m_PreviewControlPointVisible( false ), m_FigurePlaced( false ), m_PlaneGeometry( nullptr ), 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->InitializeTimeGeometry( 1 ); } mitk::PlanarFigure::~PlanarFigure() { } mitk::PlanarFigure::PlanarFigure(const Self& other) : BaseData(other), m_ControlPoints(other.m_ControlPoints), m_NumberOfControlPoints(other.m_NumberOfControlPoints), m_SelectedControlPoint(other.m_SelectedControlPoint), m_PolyLines(other.m_PolyLines), m_HelperPolyLines(other.m_HelperPolyLines), m_HelperPolyLinesToBePainted(other.m_HelperPolyLinesToBePainted->Clone()), m_PreviewControlPoint(other.m_PreviewControlPoint), m_PreviewControlPointVisible(other.m_PreviewControlPointVisible), m_FigurePlaced(other.m_FigurePlaced), m_PlaneGeometry(other.m_PlaneGeometry), // do not clone since SetPlaneGeometry() doesn't clone either m_PolyLineUpToDate(other.m_PolyLineUpToDate), m_HelperLinesUpToDate(other.m_HelperLinesUpToDate), m_FeaturesUpToDate(other.m_FeaturesUpToDate), m_Features(other.m_Features), m_FeaturesMTime(other.m_FeaturesMTime), m_DisplaySize(other.m_DisplaySize) { } void mitk::PlanarFigure::SetPlaneGeometry( mitk::PlaneGeometry *geometry ) { this->SetGeometry( geometry ); m_PlaneGeometry = dynamic_cast(GetGeometry(0));//geometry; } const mitk::PlaneGeometry *mitk::PlanarFigure::GetPlaneGeometry() const { return m_PlaneGeometry; } bool mitk::PlanarFigure::IsClosed() const { mitk::BoolProperty* closed = dynamic_cast< mitk::BoolProperty* >( this->GetProperty( "closed" ).GetPointer() ); if ( closed != nullptr ) { 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 auto 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; } } 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_PlaneGeometry != nullptr) && (index < m_NumberOfControlPoints) ) { m_PlaneGeometry->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 PlaneGeometry defines a fixed // frame (= bounds) for the planar figure. Superclass::UpdateOutputInformation(); this->GetTimeGeometry()->Update(); } void mitk::PlanarFigure::SetRequestedRegionToLargestPossibleRegion() { } bool mitk::PlanarFigure::RequestedRegionIsOutsideOfTheBufferedRegion() { return false; } bool mitk::PlanarFigure::VerifyRequestedRegion() { return true; } void mitk::PlanarFigure::SetRequestedRegion(const 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_PlaneGeometry == nullptr ) { return point; } Point2D indexPoint; m_PlaneGeometry->WorldToIndex( point, indexPoint ); BoundingBox::BoundsArrayType bounds = m_PlaneGeometry->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_PlaneGeometry->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::InitializeTimeGeometry( unsigned int timeSteps ) { mitk::PlaneGeometry::Pointer geometry2D = mitk::PlaneGeometry::New(); geometry2D->Initialize(); // The geometry is propagated automatically to all time steps, // if EvenlyTimed is true... ProportionalTimeGeometry::Pointer timeGeometry = ProportionalTimeGeometry::New(); timeGeometry->Initialize(geometry2D, timeSteps); SetTimeGeometry(timeGeometry); } 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->GetPlaneGeometry()->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::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[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[index].push_back(element); m_HelperLinesUpToDate = false; } else { MITK_ERROR << "Tried to add point to HelperPolyLine " << index+1 << ", although only " << m_HelperPolyLines.size() << " exists"; } } bool mitk::PlanarFigure::Equals(const mitk::PlanarFigure& other) const { //check geometries if ( this->GetPlaneGeometry() && other.GetPlaneGeometry() ) { if( !Equal(*(this->GetPlaneGeometry()), *(other.GetPlaneGeometry()), mitk::eps, true)) { return false; } } else { MITK_ERROR << "Geometry is not equal"; return false; } //check isPlaced member if ( this->m_FigurePlaced != other.m_FigurePlaced) { MITK_ERROR << "Is_Placed is not equal"; return false; } //check closed property if (this->IsClosed() != other.IsClosed()) { MITK_ERROR << "Is_closed is not equal"; return false; } //check poly lines if (this->m_PolyLines.size() != other.m_PolyLines.size()) { return false; } else { auto itThis = this->m_PolyLines.begin(); auto itEnd = this->m_PolyLines.end(); auto itOther = other.m_PolyLines.begin(); while( itThis != itEnd ) { if(itThis->size() != itOther->size()) return false; else { auto itLineThis = itThis->begin(); auto itLineEnd = itThis->end(); auto itLineOther = itOther->begin(); while(itLineThis != itLineEnd) { Point2D p1 = *itLineThis; Point2D p2 = *itLineOther; ScalarType delta = fabs(p1[0]-p2[0])+fabs(p1[1]-p2[1]); if(delta > .001) { MITK_ERROR << "Poly line is not equal"; MITK_ERROR << p1 << "/" << p2; return false; } ++itLineThis; ++itLineOther; } } ++itThis; ++itOther; } } //check features if (this->GetNumberOfFeatures() != other.GetNumberOfFeatures()) { MITK_ERROR << "Number of Features is Different"; return false; } else { auto itThis = m_Features.begin(); auto itEnd = m_Features.end(); auto itOther = other.m_Features.begin(); while(itThis != itEnd) { if(( itThis->Quantity - itOther->Quantity) > .001 ) { MITK_ERROR << "Quantity is Different" << itThis->Quantity << "/" << itOther->Quantity; return false; } if( itThis->Unit.compare(itOther->Unit) != 0 ) { MITK_ERROR << "Unit is Different" << itThis->Unit << "/" << itOther->Unit; return false; } if( itThis->Name.compare(itOther->Name) != 0 ) { MITK_ERROR << "Name of Measure is Different " << itThis->Name << "/ " << itOther->Name;; return false; } ++itThis; ++itOther; } } return true; } bool mitk::Equal( const mitk::PlanarFigure& leftHandSide, const mitk::PlanarFigure& rightHandSide, ScalarType /*eps*/, bool /*verbose*/ ) { // FIXME: use eps and verbose return leftHandSide.Equals(rightHandSide); } diff --git a/Modules/PlanarFigure/src/DataManagement/mitkPlanarSubdivisionPolygon.cpp b/Modules/PlanarFigure/src/DataManagement/mitkPlanarSubdivisionPolygon.cpp index 0ca442e346..c35e59ec10 100644 --- a/Modules/PlanarFigure/src/DataManagement/mitkPlanarSubdivisionPolygon.cpp +++ b/Modules/PlanarFigure/src/DataManagement/mitkPlanarSubdivisionPolygon.cpp @@ -1,147 +1,177 @@ /*=================================================================== 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 "mitkPlanarSubdivisionPolygon.h" #include "mitkPlaneGeometry.h" #include "mitkProperties.h" // stl related includes #include mitk::PlanarSubdivisionPolygon::PlanarSubdivisionPolygon(): m_TensionParameter(0.0625), m_SubdivisionRounds(5) { // Polygon is subdivision (in contrast to parent class PlanarPolygon this->SetProperty( "closed", mitk::BoolProperty::New( true ) ); this->SetProperty( "subdivision", mitk::BoolProperty::New( true ) ); // Other properties are inherited / already initialized by parent class PlanarPolygon } mitk::PlanarSubdivisionPolygon::~PlanarSubdivisionPolygon() { } void mitk::PlanarSubdivisionPolygon::GeneratePolyLine() { this->ClearPolyLines(); ControlPointListType subdivisionPoints; ControlPointListType newSubdivisionPoints; subdivisionPoints.clear(); subdivisionPoints = m_ControlPoints; if( m_ControlPoints.size() >= GetMinimumNumberOfControlPoints() ) { for( unsigned int i=0; i < GetSubdivisionRounds(); i++ ) { // Indices unsigned int index, indexPrev, indexNext, indexNextNext; unsigned int numberOfPoints = subdivisionPoints.size(); Point2D newPoint; // Keep cycling our array indices forward until they wrap around at the end for ( index = 0; index < numberOfPoints; ++index ) { // Create new subdivision point according to formula // p_new = (0.5 + tension) * (p_here + p_next) - tension * (p_prev + p_nextnext) indexPrev = (numberOfPoints + index - 1) % numberOfPoints; indexNext = (index + 1) % numberOfPoints; indexNextNext = (index + 2) % numberOfPoints; newPoint[0] = (0.5 + GetTensionParameter()) * (double)( subdivisionPoints[index][0] + subdivisionPoints[indexNext][0] ) - GetTensionParameter() * (double)( subdivisionPoints[indexPrev][0] + subdivisionPoints[indexNextNext][0]); newPoint[1] = (0.5 + GetTensionParameter()) * (double)( subdivisionPoints[index][1] + subdivisionPoints[indexNext][1] ) - GetTensionParameter() * (double)( subdivisionPoints[indexPrev][1] + subdivisionPoints[indexNextNext][1]); newSubdivisionPoints.push_back( newPoint ); } ControlPointListType mergedSubdivisionPoints; ControlPointListType::iterator it, itNew; for ( it = subdivisionPoints.begin() , itNew = newSubdivisionPoints.begin(); it != subdivisionPoints.end(); ++it, ++itNew ) { mergedSubdivisionPoints.push_back( *it ); mergedSubdivisionPoints.push_back( *itNew ); } subdivisionPoints = mergedSubdivisionPoints; newSubdivisionPoints.clear(); } } bool isInitiallyPlaced = this->GetProperty("initiallyplaced"); unsigned int i; ControlPointListType::iterator it; for ( it = subdivisionPoints.begin(), i = 0; it != subdivisionPoints.end(); ++it, ++i ) { // Determine the index of the control point FOLLOWING this poly-line element // (this is needed by PlanarFigureInteractor to insert new points at the correct position, // namely BEFORE the next control point) unsigned int nextIndex; if ( i == 0 ) { // For the FIRST polyline point, use the index of the LAST control point // (it will used to check if the mouse is near the very last polyline element) nextIndex = m_ControlPoints.size() - 1; } else { // For all other polyline points, use the index of the control point succeeding it // (for polyline points lying on control points, the index of the previous control point // is used) nextIndex = (((i - 1) >> this->GetSubdivisionRounds()) + 1) % m_ControlPoints.size(); if(!isInitiallyPlaced && nextIndex > m_ControlPoints.size()-2) { this->AppendPointToPolyLine( 0, m_ControlPoints[m_ControlPoints.size()-1] ); break; } } this->AppendPointToPolyLine( 0, *it ); } subdivisionPoints.clear(); } - bool mitk::PlanarSubdivisionPolygon::Equals(const mitk::PlanarFigure& other) const +bool mitk::PlanarSubdivisionPolygon::Equals(const mitk::PlanarFigure& other) const { const mitk::PlanarSubdivisionPolygon* otherSubDivPoly = dynamic_cast(&other); if ( otherSubDivPoly ) { if ( this->m_SubdivisionRounds != otherSubDivPoly->m_SubdivisionRounds) return false; if ( std::abs(this->m_TensionParameter - otherSubDivPoly->m_TensionParameter) > mitk::eps) return false; return Superclass::Equals(other); } else { return false; } } + + +int mitk::PlanarSubdivisionPolygon::GetControlPointForPolylinePoint( int indexOfPolylinePoint, int polyLineIndex ) const +{ + mitk::PlanarFigure::PolyLineType polyLine = GetPolyLine( polyLineIndex ); + if ( indexOfPolylinePoint > polyLine.size() ) + { + return -1; + } + + mitk::PlanarFigure::ControlPointListType::const_iterator elem; + mitk::PlanarFigure::ControlPointListType::const_iterator first = m_ControlPoints.cbegin(); + mitk::PlanarFigure::ControlPointListType::const_iterator end = m_ControlPoints.cend(); + + mitk::PlanarFigure::PolyLineType::const_iterator polyLineIter; + mitk::PlanarFigure::PolyLineType::const_iterator polyLineEnd = polyLine.cend(); + mitk::PlanarFigure::PolyLineType::const_iterator polyLineStart = polyLine.cbegin(); + polyLineStart += indexOfPolylinePoint; + + for ( polyLineIter = polyLineStart; polyLineIter != polyLineEnd; ++polyLineIter ) + { + elem = std::find( first, end, *polyLineIter ); + if ( elem != end ) + { + return std::distance( first, elem ); + } + } + + return GetNumberOfControlPoints(); +}