diff --git a/Modules/PlanarFigure/include/mitkPlanarCircle.h b/Modules/PlanarFigure/include/mitkPlanarCircle.h index e51cd66d03..12eb82b6a6 100644 --- a/Modules/PlanarFigure/include/mitkPlanarCircle.h +++ b/Modules/PlanarFigure/include/mitkPlanarCircle.h @@ -1,100 +1,105 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef _MITK_PLANAR_CIRCLE_H_ #define _MITK_PLANAR_CIRCLE_H_ #include "mitkPlanarFigure.h" #include namespace mitk { class PlaneGeometry; /** * \brief Implementation of PlanarFigure representing a circle - * through two control points + * either through two control points or by one control point (fixed radius mode) + * The mode is defined by the chosen constructor. */ class MITKPLANARFIGURE_EXPORT PlanarCircle : public PlanarFigure { public: mitkClassMacro(PlanarCircle, PlanarFigure); - + mitkNewMacro1Param(PlanarCircle, double); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** \brief Place figure in its minimal configuration (a point at least) * onto the given 2D geometry. * * Must be implemented in sub-classes. */ // virtual void Initialize(); bool SetControlPoint(unsigned int index, const Point2D &point, bool createIfDoesNotExist = false) override; /** \brief Circle has 2 control points per definition. */ - unsigned int GetMinimumNumberOfControlPoints() const override { return 2; } + unsigned int GetMinimumNumberOfControlPoints() const override { return (m_RadiusFixed) ? 1 : 2; } /** \brief Circle has 2 control points per definition. */ - unsigned int GetMaximumNumberOfControlPoints() const override { return 2; } + unsigned int GetMaximumNumberOfControlPoints() const override { return (m_RadiusFixed) ? 1 : 2; } /** \brief Sets the minimum radius */ void SetMinimumRadius(double radius) { m_MinRadius = radius; } /** \brief Gets the minimum radius */ double GetMinimumRadius() { return m_MinRadius; } /** \brief Sets the maximum radius */ void SetMaximumRadius(double radius) { m_MaxRadius = radius; } /** \brief Gets the minimum radius */ double GetMaximumRadius() { return m_MaxRadius; } void ActivateMinMaxRadiusContstraints(bool active) { m_MinMaxRadiusContraintsActive = active; } bool SetCurrentControlPoint(const Point2D &point) override; bool Equals(const mitk::PlanarFigure &other) const override; protected: PlanarCircle(); + /** Constructor for fixed radius mode.*/ + PlanarCircle(double fixedRadius); mitkCloneMacro(Self); /** \brief Generates the poly-line representation of the planar figure. */ void GeneratePolyLine() override; /** \brief Generates the poly-lines that should be drawn the same size regardless of zoom.*/ void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight) override; /** \brief Spatially constrain control points of second (orthogonal) line */ Point2D ApplyControlPointConstraints(unsigned int index, const Point2D &point) override; /** \brief Calculates feature quantities of the planar figure. */ void EvaluateFeaturesInternal() override; void PrintSelf(std::ostream &os, itk::Indent indent) const override; // Feature identifiers const unsigned int FEATURE_ID_RADIUS; const unsigned int FEATURE_ID_DIAMETER; const unsigned int FEATURE_ID_AREA; // Member variables: double m_MinRadius; double m_MaxRadius; bool m_MinMaxRadiusContraintsActive; + //indicate if the circle is created with fixed radius. The radius is stored in m_MinRadius + bool m_RadiusFixed; private: }; } // namespace mitk #endif //_MITK_PLANAR_CIRCLE_H_ diff --git a/Modules/PlanarFigure/include/mitkPlanarDoubleEllipse.h b/Modules/PlanarFigure/include/mitkPlanarDoubleEllipse.h index 1245cfdb49..9e9139931b 100644 --- a/Modules/PlanarFigure/include/mitkPlanarDoubleEllipse.h +++ b/Modules/PlanarFigure/include/mitkPlanarDoubleEllipse.h @@ -1,58 +1,74 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkPlanarDoubleEllipse_h #define mitkPlanarDoubleEllipse_h #include #include namespace mitk { + /** \brief Planar representing a double ellipse. + The double ellipse is either represented by 4 control points (center, outer major axis, outer minor axis and inner major axis) + or be one control point (center, fixed size mode). The mode is selected via the constructor. + */ class MITKPLANARFIGURE_EXPORT PlanarDoubleEllipse : public PlanarFigure { public: mitkClassMacro(PlanarDoubleEllipse, PlanarFigure); - itkFactorylessNewMacro(Self); + mitkNewMacro2Param(PlanarDoubleEllipse, double, double) + + itkFactorylessNewMacro(Self); itkCloneMacro(Self); unsigned int GetNumberOfSegments() const; void SetNumberOfSegments(unsigned int numSegments); unsigned int GetMaximumNumberOfControlPoints() const override; unsigned int GetMinimumNumberOfControlPoints() const override; bool SetControlPoint(unsigned int index, const Point2D &point, bool createIfDoesNotExist = true) override; const unsigned int FEATURE_ID_MAJOR_AXIS; const unsigned int FEATURE_ID_MINOR_AXIS; const unsigned int FEATURE_ID_THICKNESS; + static const unsigned int CP_CENTER = 0; + static const unsigned int CP_OUTER_MAJOR_AXIS = 1; + static const unsigned int CP_OUTER_MINOR_AXIS = 2; + static const unsigned int CP_INNER_MAJOR_AXIS = 3; + bool Equals(const mitk::PlanarFigure &other) const override; protected: PlanarDoubleEllipse(); + /** Constructor for fixed size mode.*/ + PlanarDoubleEllipse(double fixedRadius, double fixedThickness); mitkCloneMacro(Self); mitk::Point2D ApplyControlPointConstraints(unsigned int index, const Point2D &point) override; void EvaluateFeaturesInternal() override; void GenerateHelperPolyLine(double, unsigned int) override; void GeneratePolyLine() override; private: unsigned int m_NumberOfSegments; bool m_ConstrainCircle; bool m_ConstrainThickness; + double m_FixedRadius = 0; + double m_FixedThickness = 0; + bool m_SizeIsFixed = false; }; } #endif diff --git a/Modules/PlanarFigure/src/DataManagement/mitkPlanarCircle.cpp b/Modules/PlanarFigure/src/DataManagement/mitkPlanarCircle.cpp index 199977f3db..12e8abb842 100644 --- a/Modules/PlanarFigure/src/DataManagement/mitkPlanarCircle.cpp +++ b/Modules/PlanarFigure/src/DataManagement/mitkPlanarCircle.cpp @@ -1,187 +1,216 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkPlanarCircle.h" #include "mitkPlaneGeometry.h" #include "mitkProperties.h" mitk::PlanarCircle::PlanarCircle() : FEATURE_ID_RADIUS(this->AddFeature("Radius", "mm")), FEATURE_ID_DIAMETER(this->AddFeature("Diameter", "mm")), FEATURE_ID_AREA(this->AddFeature("Area", "mm2")), m_MinRadius(0), m_MaxRadius(100), - m_MinMaxRadiusContraintsActive(false) + m_MinMaxRadiusContraintsActive(false), + m_RadiusFixed(false) { // Circle has two control points this->ResetNumberOfControlPoints(2); this->SetNumberOfPolyLines(1); this->SetProperty("closed", mitk::BoolProperty::New(true)); } +mitk::PlanarCircle::PlanarCircle(double fixedRadius) + : FEATURE_ID_RADIUS(this->AddFeature("Radius", "mm")), + FEATURE_ID_DIAMETER(this->AddFeature("Diameter", "mm")), + FEATURE_ID_AREA(this->AddFeature("Area", "mm2")), + m_MinRadius(fixedRadius), + m_MaxRadius(100), + m_MinMaxRadiusContraintsActive(false), + m_RadiusFixed(true) +{ + // Fixed Circle has 1 control points + this->ResetNumberOfControlPoints(1); + this->SetNumberOfPolyLines(1); + this->SetProperty("closed", mitk::BoolProperty::New(true)); +} + bool mitk::PlanarCircle::SetControlPoint(unsigned int index, const Point2D &point, bool /*createIfDoesNotExist*/) { // moving center point if (index == 0) { const Point2D ¢erPoint = GetControlPoint(0); - Point2D boundaryPoint = GetControlPoint(1); - const vnl_vector vec = (point.GetVnlVector() - centerPoint.GetVnlVector()); + if (!m_RadiusFixed) + { + Point2D boundaryPoint = GetControlPoint(1); + const vnl_vector vec = (point.GetVnlVector() - centerPoint.GetVnlVector()); - boundaryPoint[0] += vec[0]; - boundaryPoint[1] += vec[1]; + boundaryPoint[0] += vec[0]; + boundaryPoint[1] += vec[1]; + PlanarFigure::SetControlPoint(1, boundaryPoint); + } PlanarFigure::SetControlPoint(0, point); - PlanarFigure::SetControlPoint(1, boundaryPoint); return true; } else if (index == 1) { PlanarFigure::SetControlPoint(index, point); return true; } return false; } mitk::Point2D mitk::PlanarCircle::ApplyControlPointConstraints(unsigned int index, const Point2D &point) { if (this->GetPlaneGeometry() == nullptr) { return point; } Point2D indexPoint; this->GetPlaneGeometry()->WorldToIndex(point, indexPoint); const BoundingBox::BoundsArrayType bounds = this->GetPlaneGeometry()->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; this->GetPlaneGeometry()->IndexToWorld(indexPoint, constrainedPoint); if (m_MinMaxRadiusContraintsActive) { if (index != 0) { const Point2D ¢erPoint = this->GetControlPoint(0); const double euclideanDinstanceFromCenterToPoint1 = centerPoint.EuclideanDistanceTo(point); Vector2D vectorProjectedPoint = point - centerPoint; vectorProjectedPoint.Normalize(); if (euclideanDinstanceFromCenterToPoint1 > m_MaxRadius) { vectorProjectedPoint *= m_MaxRadius; constrainedPoint = centerPoint; constrainedPoint += vectorProjectedPoint; } else if (euclideanDinstanceFromCenterToPoint1 < m_MinRadius) { vectorProjectedPoint *= m_MinRadius; constrainedPoint = centerPoint; constrainedPoint += vectorProjectedPoint; } } } return constrainedPoint; } void mitk::PlanarCircle::GeneratePolyLine() { // TODO: start circle at specified boundary point... // clear the PolyLine-Contrainer, it will be reconstructed soon enough... this->ClearPolyLines(); const Point2D ¢erPoint = GetControlPoint(0); - const Point2D &boundaryPoint = GetControlPoint(1); - const double radius = centerPoint.EuclideanDistanceTo(boundaryPoint); + double radius = m_MinRadius; + + if (!m_RadiusFixed) + { + const Point2D &boundaryPoint = GetControlPoint(1); + radius = centerPoint.EuclideanDistanceTo(boundaryPoint); + } // Generate poly-line with 64 segments for (int t = 0; t < 64; ++t) { const double alpha = (double)t * vnl_math::pi / 32.0; // construct the new polyline point ... Point2D polyLinePoint; polyLinePoint[0] = centerPoint[0] + radius * cos(alpha); polyLinePoint[1] = centerPoint[1] + radius * sin(alpha); // ... and append it to the PolyLine. // No extending supported here, so we can set the index of the PolyLineElement to '0' this->AppendPointToPolyLine(0, polyLinePoint); } } void mitk::PlanarCircle::GenerateHelperPolyLine(double /*mmPerDisplayUnit*/, unsigned int /*displayHeight*/) { // A circle does not require a helper object } void mitk::PlanarCircle::EvaluateFeaturesInternal() { // Calculate circle radius and area const Point3D &p0 = this->GetWorldControlPoint(0); - const Point3D &p1 = this->GetWorldControlPoint(1); + double radius = m_MinRadius; + + if (!m_RadiusFixed) + { + const Point3D &p1 = this->GetWorldControlPoint(1); + radius = p0.EuclideanDistanceTo(p1); + } - const double radius = p0.EuclideanDistanceTo(p1); const double area = vnl_math::pi * radius * radius; this->SetQuantity(FEATURE_ID_RADIUS, radius); this->SetQuantity(FEATURE_ID_DIAMETER, 2 * radius); this->SetQuantity(FEATURE_ID_AREA, area); } void mitk::PlanarCircle::PrintSelf(std::ostream &os, itk::Indent indent) const { Superclass::PrintSelf(os, indent); } bool mitk::PlanarCircle::SetCurrentControlPoint(const Point2D &point) { if (m_SelectedControlPoint < 0) { m_SelectedControlPoint = 1; } return this->SetControlPoint(m_SelectedControlPoint, point, false); } bool mitk::PlanarCircle::Equals(const PlanarFigure &other) const { const auto *otherCircle = dynamic_cast(&other); if (otherCircle) { return Superclass::Equals(other); } else { return false; } } diff --git a/Modules/PlanarFigure/src/DataManagement/mitkPlanarDoubleEllipse.cpp b/Modules/PlanarFigure/src/DataManagement/mitkPlanarDoubleEllipse.cpp index f2fd9030f1..7e553743fc 100644 --- a/Modules/PlanarFigure/src/DataManagement/mitkPlanarDoubleEllipse.cpp +++ b/Modules/PlanarFigure/src/DataManagement/mitkPlanarDoubleEllipse.cpp @@ -1,264 +1,294 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkPlanarDoubleEllipse.h" #include #include mitk::PlanarDoubleEllipse::PlanarDoubleEllipse() : FEATURE_ID_MAJOR_AXIS(Superclass::AddFeature("Major Axis", "mm")), FEATURE_ID_MINOR_AXIS(Superclass::AddFeature("Minor Axis", "mm")), FEATURE_ID_THICKNESS(Superclass::AddFeature("Thickness", "mm")), m_NumberOfSegments(64), m_ConstrainCircle(true), m_ConstrainThickness(true) { this->ResetNumberOfControlPoints(4); this->SetNumberOfPolyLines(2); this->SetProperty("closed", mitk::BoolProperty::New(true)); } +mitk::PlanarDoubleEllipse::PlanarDoubleEllipse(double fixedRadius, double fixedThickness) + : FEATURE_ID_MAJOR_AXIS(Superclass::AddFeature("Major Axis", "mm")), + FEATURE_ID_MINOR_AXIS(Superclass::AddFeature("Minor Axis", "mm")), + FEATURE_ID_THICKNESS(Superclass::AddFeature("Thickness", "mm")), + m_NumberOfSegments(64), + m_ConstrainCircle(true), + m_ConstrainThickness(true), + m_FixedRadius(fixedRadius), + m_FixedThickness(fixedThickness), + m_SizeIsFixed(true) +{ + this->ResetNumberOfControlPoints(1); + this->SetNumberOfPolyLines(2); + this->SetProperty("closed", mitk::BoolProperty::New(true)); + + if (fixedThickness>fixedRadius) + { + mitkThrow() << "Invalid constructor of fixed sized double ellipses. Thickness (" << fixedThickness << ") is greater than the radius (" << fixedRadius << ")"; + } +} + mitk::Point2D mitk::PlanarDoubleEllipse::ApplyControlPointConstraints(unsigned int index, const Point2D &point) { if (index == 2 && !m_ConstrainCircle) { - const Point2D centerPoint = this->GetControlPoint(0); - const Vector2D outerMajorVector = this->GetControlPoint(1) - centerPoint; + const Point2D centerPoint = this->GetControlPoint(CP_CENTER); + const Vector2D outerMajorVector = this->GetControlPoint(CP_OUTER_MAJOR_AXIS) - centerPoint; Vector2D minorDirection; minorDirection[0] = outerMajorVector[1]; minorDirection[1] = -outerMajorVector[0]; minorDirection.Normalize(); const double outerMajorRadius = outerMajorVector.GetNorm(); - const double innerMajorRadius = (this->GetControlPoint(3) - centerPoint).GetNorm(); + const double innerMajorRadius = (this->GetControlPoint(CP_INNER_MAJOR_AXIS) - centerPoint).GetNorm(); const ScalarType radius = std::max(outerMajorRadius - innerMajorRadius, std::min(centerPoint.EuclideanDistanceTo(point), outerMajorRadius)); return centerPoint + minorDirection * radius; } else if (index == 3 && !m_ConstrainThickness) { - const Point2D centerPoint = this->GetControlPoint(0); - Vector2D outerMajorVector = this->GetControlPoint(1) - centerPoint; + const Point2D centerPoint = this->GetControlPoint(CP_CENTER); + Vector2D outerMajorVector = this->GetControlPoint(CP_OUTER_MAJOR_AXIS) - centerPoint; const double outerMajorRadius = outerMajorVector.GetNorm(); - const double outerMinorRadius = (this->GetControlPoint(2) - centerPoint).GetNorm(); + const double outerMinorRadius = (this->GetControlPoint(CP_OUTER_MINOR_AXIS) - centerPoint).GetNorm(); const ScalarType radius = std::max(outerMajorRadius - outerMinorRadius, std::min(centerPoint.EuclideanDistanceTo(point), outerMajorRadius)); outerMajorVector.Normalize(); return centerPoint - outerMajorVector * radius; } return point; } void mitk::PlanarDoubleEllipse::EvaluateFeaturesInternal() { - const Point2D centerPoint = this->GetControlPoint(0); - const ScalarType outerMajorRadius = centerPoint.EuclideanDistanceTo(this->GetControlPoint(1)); + const Point2D centerPoint = this->GetControlPoint(CP_CENTER); + const ScalarType outerMajorRadius = (m_SizeIsFixed)? m_FixedRadius : centerPoint.EuclideanDistanceTo(this->GetControlPoint(CP_OUTER_MAJOR_AXIS)); + const ScalarType outerMinorRadius = (m_SizeIsFixed)? m_FixedRadius : centerPoint.EuclideanDistanceTo(this->GetControlPoint(CP_OUTER_MINOR_AXIS)); + const ScalarType thickness = (m_SizeIsFixed)? m_FixedThickness : outerMajorRadius - centerPoint.EuclideanDistanceTo(this->GetControlPoint(CP_INNER_MAJOR_AXIS)); this->SetQuantity(FEATURE_ID_MAJOR_AXIS, 2 * outerMajorRadius); - this->SetQuantity(FEATURE_ID_MINOR_AXIS, 2 * centerPoint.EuclideanDistanceTo(this->GetControlPoint(2))); - this->SetQuantity(FEATURE_ID_THICKNESS, outerMajorRadius - centerPoint.EuclideanDistanceTo(this->GetControlPoint(3))); + this->SetQuantity(FEATURE_ID_MINOR_AXIS, 2 * outerMinorRadius); + this->SetQuantity(FEATURE_ID_THICKNESS, thickness); } void mitk::PlanarDoubleEllipse::GenerateHelperPolyLine(double, unsigned int) { } void mitk::PlanarDoubleEllipse::GeneratePolyLine() { this->ClearPolyLines(); - const Point2D centerPoint = this->GetControlPoint(0); - const Point2D outerMajorPoint = this->GetControlPoint(1); + const Point2D centerPoint = this->GetControlPoint(CP_CENTER); - Vector2D direction = outerMajorPoint - centerPoint; - direction.Normalize(); + Vector2D direction(0.); + direction[0] = 1.; + if (!m_SizeIsFixed) + { + direction = this->GetControlPoint(CP_OUTER_MAJOR_AXIS) - centerPoint; + direction.Normalize(); + } const ScalarType deltaAngle = vnl_math::pi / (m_NumberOfSegments / 2); int start = 0; int end = m_NumberOfSegments; if (direction[1] < 0.0) { direction[0] = -direction[0]; end = m_NumberOfSegments / 2; start = -end; } vnl_matrix_fixed rotation; rotation[1][0] = std::sin(std::acos(direction[0])); rotation[0][0] = direction[0]; rotation[1][1] = direction[0]; rotation[0][1] = -rotation[1][0]; - const ScalarType outerMajorRadius = centerPoint.EuclideanDistanceTo(outerMajorPoint); - const ScalarType outerMinorRadius = centerPoint.EuclideanDistanceTo(this->GetControlPoint(2)); - const ScalarType innerMajorRadius = centerPoint.EuclideanDistanceTo(this->GetControlPoint(3)); - const ScalarType innerMinorRadius = innerMajorRadius - (outerMajorRadius - outerMinorRadius); + const ScalarType outerMajorRadius = (m_SizeIsFixed) ? m_FixedRadius : centerPoint.EuclideanDistanceTo(this->GetControlPoint(CP_OUTER_MAJOR_AXIS)); + const ScalarType outerMinorRadius = (m_SizeIsFixed) ? m_FixedRadius : centerPoint.EuclideanDistanceTo(this->GetControlPoint(CP_OUTER_MINOR_AXIS)); + const ScalarType innerMajorRadius = (m_SizeIsFixed) ? (m_FixedRadius-m_FixedThickness) : centerPoint.EuclideanDistanceTo(this->GetControlPoint(CP_INNER_MAJOR_AXIS)); + const ScalarType innerMinorRadius = (m_SizeIsFixed) ? (m_FixedRadius-m_FixedThickness) : innerMajorRadius - (outerMajorRadius - outerMinorRadius); - ScalarType angle; - ScalarType cosAngle; - ScalarType sinAngle; + ScalarType angle = 0; + ScalarType cosAngle = 0; + ScalarType sinAngle = 0; vnl_vector_fixed vector; Point2D point; for (int i = start; i < end; ++i) { angle = i * deltaAngle; cosAngle = std::cos(angle); sinAngle = std::sin(angle); vector[0] = outerMajorRadius * cosAngle; vector[1] = outerMinorRadius * sinAngle; vector = rotation * vector; point[0] = centerPoint[0] + vector[0]; point[1] = centerPoint[1] + vector[1]; this->AppendPointToPolyLine(0, point); vector[0] = innerMajorRadius * cosAngle; vector[1] = innerMinorRadius * sinAngle; vector = rotation * vector; point[0] = centerPoint[0] + vector[0]; point[1] = centerPoint[1] + vector[1]; this->AppendPointToPolyLine(1, point); } } unsigned int mitk::PlanarDoubleEllipse::GetNumberOfSegments() const { return m_NumberOfSegments; } void mitk::PlanarDoubleEllipse::SetNumberOfSegments(unsigned int numSegments) { m_NumberOfSegments = std::max(4U, numSegments); if (this->IsPlaced()) { this->GeneratePolyLine(); this->Modified(); } } unsigned int mitk::PlanarDoubleEllipse::GetMaximumNumberOfControlPoints() const { - return 4; + return (m_SizeIsFixed)? 1 : 4; } unsigned int mitk::PlanarDoubleEllipse::GetMinimumNumberOfControlPoints() const { - return 4; + return (m_SizeIsFixed)? 1 : 4; } bool mitk::PlanarDoubleEllipse::SetControlPoint(unsigned int index, const Point2D &point, bool createIfDoesNotExist) { switch (index) { case 0: { - const Point2D centerPoint = this->GetControlPoint(0); + const Point2D centerPoint = this->GetControlPoint(CP_CENTER); const Vector2D vector = point - centerPoint; Superclass::SetControlPoint(0, point, createIfDoesNotExist); - Superclass::SetControlPoint(1, this->GetControlPoint(1) + vector, createIfDoesNotExist); - Superclass::SetControlPoint(2, this->GetControlPoint(2) + vector, createIfDoesNotExist); - Superclass::SetControlPoint(3, this->GetControlPoint(3) + vector, createIfDoesNotExist); + if (!m_SizeIsFixed) + { + Superclass::SetControlPoint(1, this->GetControlPoint(CP_OUTER_MAJOR_AXIS) + vector, createIfDoesNotExist); + Superclass::SetControlPoint(2, this->GetControlPoint(CP_OUTER_MINOR_AXIS) + vector, createIfDoesNotExist); + Superclass::SetControlPoint(3, this->GetControlPoint(CP_INNER_MAJOR_AXIS) + vector, createIfDoesNotExist); + } break; } case 1: { - const Vector2D vector = point - this->GetControlPoint(1); + const Vector2D vector = point - this->GetControlPoint(CP_OUTER_MAJOR_AXIS); Superclass::SetControlPoint(1, point, createIfDoesNotExist); - const Point2D centerPoint = this->GetControlPoint(0); + const Point2D centerPoint = this->GetControlPoint(CP_CENTER); const Vector2D outerMajorVector = point - centerPoint; Vector2D outerMinorVector; outerMinorVector[0] = outerMajorVector[1]; outerMinorVector[1] = -outerMajorVector[0]; if (!m_ConstrainCircle) { outerMinorVector.Normalize(); - outerMinorVector *= centerPoint.EuclideanDistanceTo(this->GetControlPoint(2)); + outerMinorVector *= centerPoint.EuclideanDistanceTo(this->GetControlPoint(CP_OUTER_MINOR_AXIS)); } Superclass::SetControlPoint(2, centerPoint + outerMinorVector, createIfDoesNotExist); Vector2D innerMajorVector = outerMajorVector; if (!m_ConstrainThickness) { innerMajorVector.Normalize(); - innerMajorVector *= centerPoint.EuclideanDistanceTo(this->GetControlPoint(3) - vector); + innerMajorVector *= centerPoint.EuclideanDistanceTo(this->GetControlPoint(CP_INNER_MAJOR_AXIS) - vector); } Superclass::SetControlPoint(3, centerPoint - innerMajorVector, createIfDoesNotExist); break; } case 2: { m_ConstrainCircle = false; Superclass::SetControlPoint(2, point, createIfDoesNotExist); break; } case 3: { m_ConstrainThickness = false; Superclass::SetControlPoint(3, point, createIfDoesNotExist); break; } default: return false; } return true; } bool mitk::PlanarDoubleEllipse::Equals(const mitk::PlanarFigure &other) const { const auto *otherDoubleEllipse = dynamic_cast(&other); if (otherDoubleEllipse) { if (this->m_ConstrainCircle != otherDoubleEllipse->m_ConstrainCircle) return false; if (this->m_ConstrainThickness != otherDoubleEllipse->m_ConstrainThickness) return false; if (this->m_NumberOfSegments != otherDoubleEllipse->m_NumberOfSegments) return false; return Superclass::Equals(other); } else { return false; } } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp index 8a4f70accc..728b8cf75c 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp @@ -1,829 +1,875 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMeasurementView.h" #include #include #include #include #include #include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include "ctkDoubleSpinBox.h" + #include "mitkPluginActivator.h" #include "usModuleRegistry.h" #include "mitkInteractionEventObserver.h" #include "mitkDisplayInteractor.h" #include "usGetModuleContext.h" #include "usModuleContext.h" #include US_INITIALIZE_MODULE struct QmitkPlanarFigureData { QmitkPlanarFigureData() : m_EndPlacementObserverTag(0), m_SelectObserverTag(0), m_StartInteractionObserverTag(0), m_EndInteractionObserverTag(0) { } mitk::PlanarFigure::Pointer m_Figure; unsigned int m_EndPlacementObserverTag; unsigned int m_SelectObserverTag; unsigned int m_StartInteractionObserverTag; unsigned int m_EndInteractionObserverTag; }; struct QmitkMeasurementViewData { QmitkMeasurementViewData() : m_LineCounter(0), m_PathCounter(0), m_AngleCounter(0), m_FourPointAngleCounter(0), m_CircleCounter(0), m_EllipseCounter(0), m_DoubleEllipseCounter(0), m_RectangleCounter(0), m_PolygonCounter(0), m_BezierCurveCounter(0), m_SubdivisionPolygonCounter(0), m_UnintializedPlanarFigure(false), m_ScrollEnabled(true), m_Parent(nullptr), m_SingleNodeSelectionWidget(nullptr), m_DrawLine(nullptr), m_DrawPath(nullptr), m_DrawAngle(nullptr), m_DrawFourPointAngle(nullptr), m_DrawRectangle(nullptr), m_DrawPolygon(nullptr), m_DrawCircle(nullptr), m_DrawEllipse(nullptr), m_DrawDoubleEllipse(nullptr), m_DrawBezierCurve(nullptr), m_DrawSubdivisionPolygon(nullptr), m_DrawActionsToolBar(nullptr), m_DrawActionsGroup(nullptr), m_SelectedPlanarFiguresText(nullptr), m_CopyToClipboard(nullptr), - m_Layout(nullptr) + m_Layout(nullptr), + m_Radius(nullptr), + m_Thickness(nullptr), + m_FixedParameterBox(nullptr) { } unsigned int m_LineCounter; unsigned int m_PathCounter; unsigned int m_AngleCounter; unsigned int m_FourPointAngleCounter; unsigned int m_CircleCounter; unsigned int m_EllipseCounter; unsigned int m_DoubleEllipseCounter; unsigned int m_RectangleCounter; unsigned int m_PolygonCounter; unsigned int m_BezierCurveCounter; unsigned int m_SubdivisionPolygonCounter; QList m_CurrentSelection; std::map m_DataNodeToPlanarFigureData; mitk::DataNode::Pointer m_SelectedImageNode; bool m_UnintializedPlanarFigure; bool m_ScrollEnabled; QWidget* m_Parent; QmitkSingleNodeSelectionWidget* m_SingleNodeSelectionWidget; QAction* m_DrawLine; QAction* m_DrawPath; QAction* m_DrawAngle; QAction* m_DrawFourPointAngle; QAction* m_DrawRectangle; QAction* m_DrawPolygon; QAction* m_DrawCircle; QAction* m_DrawEllipse; QAction* m_DrawDoubleEllipse; QAction* m_DrawBezierCurve; QAction* m_DrawSubdivisionPolygon; QToolBar* m_DrawActionsToolBar; QActionGroup* m_DrawActionsGroup; QTextBrowser* m_SelectedPlanarFiguresText; QPushButton* m_CopyToClipboard; QGridLayout* m_Layout; + ctkDoubleSpinBox* m_Radius; + ctkDoubleSpinBox* m_Thickness; + QGroupBox* m_FixedParameterBox; }; const std::string QmitkMeasurementView::VIEW_ID = "org.mitk.views.measurement"; QmitkMeasurementView::QmitkMeasurementView() : d(new QmitkMeasurementViewData) { } QmitkMeasurementView::~QmitkMeasurementView() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeRemoved(it.Value()); delete d; } void QmitkMeasurementView::CreateQtPartControl(QWidget* parent) { d->m_Parent = parent; d->m_SingleNodeSelectionWidget = new QmitkSingleNodeSelectionWidget(); d->m_SingleNodeSelectionWidget->SetDataStorage(GetDataStorage()); d->m_SingleNodeSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); d->m_SingleNodeSelectionWidget->SetSelectionIsOptional(true); d->m_SingleNodeSelectionWidget->SetEmptyInfo(QStringLiteral("Please select a reference image")); d->m_SingleNodeSelectionWidget->SetPopUpTitel(QStringLiteral("Select a reference image")); d->m_DrawActionsToolBar = new QToolBar; d->m_DrawActionsGroup = new QActionGroup(this); d->m_DrawActionsGroup->setExclusive(true); auto* currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/line.png"), tr("Draw Line")); currentAction->setCheckable(true); d->m_DrawLine = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/path.png"), tr("Draw Path")); currentAction->setCheckable(true); d->m_DrawPath = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/angle.png"), tr("Draw Angle")); currentAction->setCheckable(true); d->m_DrawAngle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/four-point-angle.png"), tr("Draw Four Point Angle")); currentAction->setCheckable(true); d->m_DrawFourPointAngle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/circle.png"), tr("Draw Circle")); currentAction->setCheckable(true); d->m_DrawCircle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/ellipse.png"), tr("Draw Ellipse")); currentAction->setCheckable(true); d->m_DrawEllipse = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/doubleellipse.png"), tr("Draw Double Ellipse")); currentAction->setCheckable(true); d->m_DrawDoubleEllipse = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/rectangle.png"), tr("Draw Rectangle")); currentAction->setCheckable(true); d->m_DrawRectangle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/polygon.png"), tr("Draw Polygon")); currentAction->setCheckable(true); d->m_DrawPolygon = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/beziercurve.png"), tr("Draw Bezier Curve")); currentAction->setCheckable(true); d->m_DrawBezierCurve = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/subdivisionpolygon.png"), tr("Draw Subdivision Polygon")); currentAction->setCheckable(true); d->m_DrawSubdivisionPolygon = currentAction; d->m_DrawActionsToolBar->setEnabled(false); + // fixed parameter section + auto fixedLayout = new QGridLayout(); + + d->m_FixedParameterBox = new QGroupBox(); + d->m_FixedParameterBox->setCheckable(true); + d->m_FixedParameterBox->setChecked(false); + d->m_FixedParameterBox->setTitle("Fixed sized circle/double ellipse"); + d->m_FixedParameterBox->setToolTip("If activated, circles and double ellipses (as rings) figures will always be created with the set parameters as fixed size."); + d->m_FixedParameterBox->setAlignment(Qt::AlignLeft); + + auto labelRadius1 = new QLabel(QString("Radius")); + d->m_Radius = new ctkDoubleSpinBox(); + d->m_Radius->setMinimum(0); + d->m_Radius->setValue(10); + d->m_Radius->setSuffix(" mm"); + d->m_Radius->setAlignment(Qt::AlignLeft); + d->m_Radius->setToolTip("Sets the radius for following planar figures: circle, double ellipse (as ring)."); + + auto labelThickness = new QLabel(QString("Thickness")); + d->m_Thickness = new ctkDoubleSpinBox(); + d->m_Thickness->setMinimum(0); + d->m_Thickness->setMaximum(10); + d->m_Thickness->setValue(5); + d->m_Thickness->setSuffix(" mm"); + d->m_Thickness->setAlignment(Qt::AlignLeft); + d->m_Thickness->setToolTip("Sets the thickness for following planar figures: double ellipse (as ring)."); + + fixedLayout->addWidget(labelRadius1,0,0); + fixedLayout->addWidget(d->m_Radius,0,1); + fixedLayout->addWidget(labelThickness,1,0); + fixedLayout->addWidget(d->m_Thickness,1,1); + + d->m_FixedParameterBox->setLayout(fixedLayout); + // planar figure details text d->m_SelectedPlanarFiguresText = new QTextBrowser; // copy to clipboard button d->m_CopyToClipboard = new QPushButton(tr("Copy to Clipboard")); d->m_Layout = new QGridLayout; d->m_Layout->addWidget(d->m_SingleNodeSelectionWidget, 0, 0, 1, 2); d->m_Layout->addWidget(d->m_DrawActionsToolBar, 1, 0, 1, 2); - d->m_Layout->addWidget(d->m_SelectedPlanarFiguresText, 2, 0, 1, 2); - d->m_Layout->addWidget(d->m_CopyToClipboard, 3, 0, 1, 2); + d->m_Layout->addWidget(d->m_FixedParameterBox, 2, 0, 1, 2); + d->m_Layout->addWidget(d->m_SelectedPlanarFiguresText, 3, 0, 1, 2); + d->m_Layout->addWidget(d->m_CopyToClipboard, 4, 0, 1, 2); d->m_Parent->setLayout(d->m_Layout); this->CreateConnections(); this->AddAllInteractors(); } void QmitkMeasurementView::CreateConnections() { connect(d->m_SingleNodeSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMeasurementView::OnCurrentSelectionChanged); connect(d->m_DrawLine, SIGNAL(triggered(bool)), this, SLOT(OnDrawLineTriggered(bool))); connect(d->m_DrawPath, SIGNAL(triggered(bool)), this, SLOT(OnDrawPathTriggered(bool))); connect(d->m_DrawAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawAngleTriggered(bool))); connect(d->m_DrawFourPointAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawFourPointAngleTriggered(bool))); connect(d->m_DrawCircle, SIGNAL(triggered(bool)), this, SLOT(OnDrawCircleTriggered(bool))); connect(d->m_DrawEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawEllipseTriggered(bool))); connect(d->m_DrawDoubleEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawDoubleEllipseTriggered(bool))); connect(d->m_DrawRectangle, SIGNAL(triggered(bool)), this, SLOT(OnDrawRectangleTriggered(bool))); connect(d->m_DrawPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawPolygonTriggered(bool))); connect(d->m_DrawBezierCurve, SIGNAL(triggered(bool)), this, SLOT(OnDrawBezierCurveTriggered(bool))); connect(d->m_DrawSubdivisionPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawSubdivisionPolygonTriggered(bool))); connect(d->m_CopyToClipboard, SIGNAL(clicked(bool)), this, SLOT(OnCopyToClipboard(bool))); + connect(d->m_Radius, QOverload::of(&ctkDoubleSpinBox::valueChanged), d->m_Thickness, &ctkDoubleSpinBox::setMaximum); } void QmitkMeasurementView::OnCurrentSelectionChanged(QList nodes) { if (nodes.empty() || nodes.front().IsNull()) { d->m_SelectedImageNode = nullptr; d->m_DrawActionsToolBar->setEnabled(false); } else { d->m_SelectedImageNode = nodes.front(); d->m_DrawActionsToolBar->setEnabled(true); } } void QmitkMeasurementView::NodeAdded(const mitk::DataNode* node) { // add observer for selection in renderwindow mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); auto isPositionMarker = false; node->GetBoolProperty("isContourMarker", isPositionMarker); if (planarFigure.IsNotNull() && !isPositionMarker) { auto nonConstNode = const_cast(node); mitk::PlanarFigureInteractor::Pointer interactor = dynamic_cast(node->GetDataInteractor().GetPointer()); if (interactor.IsNull()) { interactor = mitk::PlanarFigureInteractor::New(); auto planarFigureModule = us::ModuleRegistry::GetModule("MitkPlanarFigure"); interactor->LoadStateMachine("PlanarFigureInteraction.xml", planarFigureModule); interactor->SetEventConfig("PlanarFigureConfig.xml", planarFigureModule); } interactor->SetDataNode(nonConstNode); QmitkPlanarFigureData data; data.m_Figure = planarFigure; typedef itk::SimpleMemberCommand SimpleCommandType; typedef itk::MemberCommand MemberCommandType; // add observer for event when figure has been placed auto initializationCommand = SimpleCommandType::New(); initializationCommand->SetCallbackFunction(this, &QmitkMeasurementView::PlanarFigureInitialized); data.m_EndPlacementObserverTag = planarFigure->AddObserver(mitk::EndPlacementPlanarFigureEvent(), initializationCommand); // add observer for event when figure is picked (selected) auto selectCommand = MemberCommandType::New(); selectCommand->SetCallbackFunction(this, &QmitkMeasurementView::PlanarFigureSelected); data.m_SelectObserverTag = planarFigure->AddObserver(mitk::SelectPlanarFigureEvent(), selectCommand); // add observer for event when interaction with figure starts auto startInteractionCommand = SimpleCommandType::New(); startInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::DisableCrosshairNavigation); data.m_StartInteractionObserverTag = planarFigure->AddObserver(mitk::StartInteractionPlanarFigureEvent(), startInteractionCommand); // add observer for event when interaction with figure starts auto endInteractionCommand = SimpleCommandType::New(); endInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::EnableCrosshairNavigation); data.m_EndInteractionObserverTag = planarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), endInteractionCommand); // adding to the map of tracked planarfigures d->m_DataNodeToPlanarFigureData[nonConstNode] = data; } } void QmitkMeasurementView::NodeChanged(const mitk::DataNode* node) { auto it = std::find(d->m_CurrentSelection.begin(), d->m_CurrentSelection.end(), node); if (it != d->m_CurrentSelection.end()) { this->UpdateMeasurementText(); } } void QmitkMeasurementView::NodeRemoved(const mitk::DataNode* node) { auto nonConstNode = const_cast(node); auto it = d->m_DataNodeToPlanarFigureData.find(nonConstNode); auto isFigureFinished = false; auto isPlaced = false; if (it != d->m_DataNodeToPlanarFigureData.end()) { QmitkPlanarFigureData& data = it->second; data.m_Figure->RemoveObserver(data.m_EndPlacementObserverTag); data.m_Figure->RemoveObserver(data.m_SelectObserverTag); data.m_Figure->RemoveObserver(data.m_StartInteractionObserverTag); data.m_Figure->RemoveObserver(data.m_EndInteractionObserverTag); isFigureFinished = data.m_Figure->GetPropertyList()->GetBoolProperty("initiallyplaced", isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons d->m_DataNodeToPlanarFigureData.erase( it ); } if (nonConstNode != nullptr) nonConstNode->SetDataInteractor(nullptr); auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto nodes = this->GetDataStorage()->GetDerivations(node, isPlanarFigure); for (unsigned int x = 0; x < nodes->size(); ++x) { mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(nodes->at(x)->GetData()); if (planarFigure.IsNotNull()) { isFigureFinished = planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced",isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode { this->GetDataStorage()->Remove(nodes->at(x)); if (!d->m_DataNodeToPlanarFigureData.empty()) { it = d->m_DataNodeToPlanarFigureData.find(nodes->at(x)); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_DataNodeToPlanarFigureData.erase(it); this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons this->EnableCrosshairNavigation(); } } } } } } void QmitkMeasurementView::PlanarFigureSelected(itk::Object* object, const itk::EventObject&) { d->m_CurrentSelection.clear(); auto lambda = [&object](const std::pair& element) { return element.second.m_Figure == object; }; auto it = std::find_if(d->m_DataNodeToPlanarFigureData.begin(), d->m_DataNodeToPlanarFigureData.end(), lambda); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_CurrentSelection.push_back(it->first); } this->UpdateMeasurementText(); this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::PlanarFigureInitialized() { d->m_UnintializedPlanarFigure = false; d->m_DrawActionsToolBar->setEnabled(true); d->m_DrawLine->setChecked(false); d->m_DrawPath->setChecked(false); d->m_DrawAngle->setChecked(false); d->m_DrawFourPointAngle->setChecked(false); d->m_DrawCircle->setChecked(false); d->m_DrawEllipse->setChecked(false); d->m_DrawDoubleEllipse->setChecked(false); d->m_DrawRectangle->setChecked(false); d->m_DrawPolygon->setChecked(false); d->m_DrawBezierCurve->setChecked(false); d->m_DrawSubdivisionPolygon->setChecked(false); } void QmitkMeasurementView::OnSelectionChanged(berry::IWorkbenchPart::Pointer, const QList& nodes) { d->m_CurrentSelection = nodes; this->UpdateMeasurementText(); // bug 16600: deselecting all planarfigures by clicking on datamanager when no node is selected if (d->m_CurrentSelection.size() == 0) { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto planarFigures = this->GetDataStorage()->GetSubset(isPlanarFigure); // setting all planar figures which are not helper objects not selected for (mitk::DataStorage::SetOfObjects::ConstIterator it = planarFigures->Begin(); it != planarFigures->End(); ++it) { auto node = it.Value(); auto isHelperObject = false; node->GetBoolProperty("helper object", isHelperObject); if (!isHelperObject) node->SetSelected(false); } } for (int i = d->m_CurrentSelection.size() - 1; i >= 0; --i) { auto node = d->m_CurrentSelection[i]; mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); // the last selected planar figure if (planarFigure.IsNotNull() && planarFigure->GetPlaneGeometry()) { auto planarFigureInitializedWindow = false; auto linkedRenderWindow = dynamic_cast(this->GetRenderWindowPart()); QmitkRenderWindow* selectedRenderWindow; if (!linkedRenderWindow) return; auto axialRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("axial"); auto sagittalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("sagittal"); auto coronalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("coronal"); auto threeDimRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("3d"); if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, threeDimRenderWindow->GetRenderer())) { selectedRenderWindow = threeDimRenderWindow; } else { selectedRenderWindow = nullptr; } auto planeGeometry = dynamic_cast(planarFigure->GetPlaneGeometry()); auto normal = planeGeometry->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer axialPlane = axialRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto axialNormal = axialPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer sagittalPlane = sagittalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto sagittalNormal = sagittalPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer coronalPlane = coronalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto coronalNormal = coronalPlane->GetNormalVnl(); normal[0] = fabs(normal[0]); normal[1] = fabs(normal[1]); normal[2] = fabs(normal[2]); axialNormal[0] = fabs(axialNormal[0]); axialNormal[1] = fabs(axialNormal[1]); axialNormal[2] = fabs(axialNormal[2]); sagittalNormal[0] = fabs(sagittalNormal[0]); sagittalNormal[1] = fabs(sagittalNormal[1]); sagittalNormal[2] = fabs(sagittalNormal[2]); coronalNormal[0] = fabs(coronalNormal[0]); coronalNormal[1] = fabs(coronalNormal[1]); coronalNormal[2] = fabs(coronalNormal[2]); auto ang1 = angle(normal, axialNormal); auto ang2 = angle(normal, sagittalNormal); auto ang3 = angle(normal, coronalNormal); if (ang1 < ang2 && ang1 < ang3) { selectedRenderWindow = axialRenderWindow; } else { if (ang2 < ang3) { selectedRenderWindow = sagittalRenderWindow; } else { selectedRenderWindow = coronalRenderWindow; } } // re-orient view if (selectedRenderWindow) selectedRenderWindow->GetSliceNavigationController()->ReorientSlices(planeGeometry->GetOrigin(), planeGeometry->GetNormal()); } break; } this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::OnDrawLineTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarLine::New(), QString("Line%1").arg(++d->m_LineCounter)); } void QmitkMeasurementView::OnDrawPathTriggered(bool) { auto propertyFilters = mitk::CoreServices::GetPropertyFilters(); if (propertyFilters != nullptr) { mitk::PropertyFilter filter; filter.AddEntry("ClosedPlanarPolygon", mitk::PropertyFilter::Blacklist); propertyFilters->AddFilter(filter, "PlanarPolygon"); } mitk::PlanarPolygon::Pointer planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOff(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Path%1").arg(++d->m_PathCounter)); node->SetProperty("ClosedPlanarPolygon", mitk::BoolProperty::New(false)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnDrawAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarAngle::New(), QString("Angle%1").arg(++d->m_AngleCounter)); } void QmitkMeasurementView::OnDrawFourPointAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarFourPointAngle::New(), QString("Four Point Angle%1").arg(++d->m_FourPointAngleCounter)); } void QmitkMeasurementView::OnDrawCircleTriggered(bool) { - this->AddFigureToDataStorage( - mitk::PlanarCircle::New(), - QString("Circle%1").arg(++d->m_CircleCounter)); + auto circle = (d->m_FixedParameterBox->isChecked()) ? mitk::PlanarCircle::New(d->m_Radius->value()) : mitk::PlanarCircle::New(); + + this->AddFigureToDataStorage(circle, QString("Circle%1").arg(++d->m_CircleCounter)); } void QmitkMeasurementView::OnDrawEllipseTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarEllipse::New(), QString("Ellipse%1").arg(++d->m_EllipseCounter)); } void QmitkMeasurementView::OnDrawDoubleEllipseTriggered(bool) { - this->AddFigureToDataStorage( - mitk::PlanarDoubleEllipse::New(), - QString("DoubleEllipse%1").arg(++d->m_DoubleEllipseCounter)); + auto ellipse = (d->m_FixedParameterBox->isChecked()) ? mitk::PlanarDoubleEllipse::New(d->m_Radius->value(),d->m_Thickness->value()) : mitk::PlanarDoubleEllipse::New(); + + this->AddFigureToDataStorage(ellipse, QString("DoubleEllipse%1").arg(++d->m_DoubleEllipseCounter)); } void QmitkMeasurementView::OnDrawBezierCurveTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarBezierCurve::New(), QString("BezierCurve%1").arg(++d->m_BezierCurveCounter)); } void QmitkMeasurementView::OnDrawSubdivisionPolygonTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarSubdivisionPolygon::New(), QString("SubdivisionPolygon%1").arg(++d->m_SubdivisionPolygonCounter)); } void QmitkMeasurementView::OnDrawRectangleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarRectangle::New(), QString("Rectangle%1").arg(++d->m_RectangleCounter)); } void QmitkMeasurementView::OnDrawPolygonTriggered(bool) { auto planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOn(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Polygon%1").arg(++d->m_PolygonCounter)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnCopyToClipboard(bool) { QApplication::clipboard()->setText(d->m_SelectedPlanarFiguresText->toPlainText(), QClipboard::Clipboard); } mitk::DataNode::Pointer QmitkMeasurementView::AddFigureToDataStorage(mitk::PlanarFigure* figure, const QString& name) { auto newNode = mitk::DataNode::New(); newNode->SetName(name.toStdString()); newNode->SetData(figure); newNode->SetSelected(true); if (d->m_SelectedImageNode.IsNotNull()) { this->GetDataStorage()->Add(newNode, d->m_SelectedImageNode); } else { this->GetDataStorage()->Add(newNode); } for (auto &node : d->m_CurrentSelection) node->SetSelected(false); d->m_CurrentSelection.clear(); d->m_CurrentSelection.push_back(newNode); this->UpdateMeasurementText(); this->DisableCrosshairNavigation(); d->m_DrawActionsToolBar->setEnabled(false); d->m_UnintializedPlanarFigure = true; return newNode; } void QmitkMeasurementView::UpdateMeasurementText() { d->m_SelectedPlanarFiguresText->clear(); QString infoText; QString plainInfoText; int j = 1; mitk::PlanarFigure::Pointer planarFigure; mitk::PlanarAngle::Pointer planarAngle; mitk::PlanarFourPointAngle::Pointer planarFourPointAngle; mitk::DataNode::Pointer node; for (int i = 0; i < d->m_CurrentSelection.size(); ++i, ++j) { plainInfoText.clear(); node = d->m_CurrentSelection[i]; planarFigure = dynamic_cast(node->GetData()); if (planarFigure.IsNull()) continue; if (j > 1) infoText.append("
"); infoText.append(QString("%1
").arg(QString::fromStdString(node->GetName()))); plainInfoText.append(QString("%1").arg(QString::fromStdString(node->GetName()))); planarAngle = dynamic_cast (planarFigure.GetPointer()); if (planarAngle.IsNull()) planarFourPointAngle = dynamic_cast (planarFigure.GetPointer()); double featureQuantity = 0.0; for (unsigned int k = 0; k < planarFigure->GetNumberOfFeatures(); ++k) { if (!planarFigure->IsFeatureActive(k)) continue; featureQuantity = planarFigure->GetQuantity(k); if ((planarAngle.IsNotNull() && k == planarAngle->FEATURE_ID_ANGLE) || (planarFourPointAngle.IsNotNull() && k == planarFourPointAngle->FEATURE_ID_ANGLE)) featureQuantity = featureQuantity * 180 / vnl_math::pi; infoText.append(QString("%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); plainInfoText.append(QString("\n%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); if (k + 1 != planarFigure->GetNumberOfFeatures()) infoText.append("
"); } if (j != d->m_CurrentSelection.size()) infoText.append("
"); } d->m_SelectedPlanarFiguresText->setHtml(infoText); } void QmitkMeasurementView::AddAllInteractors() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeAdded(it.Value()); } void QmitkMeasurementView::EnableCrosshairNavigation() { // enable the crosshair navigation // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (const auto& displayInteractorConfig : m_DisplayInteractorConfigs) { if (displayInteractorConfig.first) { auto displayInteractor = static_cast(us::GetModuleContext()->GetService(displayInteractorConfig.first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(displayInteractorConfig.second); } } } m_DisplayInteractorConfigs.clear(); d->m_ScrollEnabled = true; } void QmitkMeasurementView::DisableCrosshairNavigation() { // dont deactivate twice, else we will clutter the config list ... if (d->m_ScrollEnabled == false) return; // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction will still be enabled m_DisplayInteractorConfigs.clear(); auto eventObservers = us::GetModuleContext()->GetServiceReferences(); for (const auto& eventObserver : eventObservers) { auto displayInteractor = dynamic_cast(us::GetModuleContext()->GetService(eventObserver)); if (displayInteractor != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(eventObserver, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded displayInteractor->SetEventConfig("DisplayConfigMITKLimited.xml"); } } d->m_ScrollEnabled = false; } mitk::DataStorage::SetOfObjects::ConstPointer QmitkMeasurementView::GetAllPlanarFigures() const { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto isNotHelperObject = mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(false)); auto isNotHelperButPlanarFigure = mitk::NodePredicateAnd::New( isPlanarFigure, isNotHelperObject ); return this->GetDataStorage()->GetSubset(isPlanarFigure); }