Medical Imaging Interaction Toolkit  2018.4.99-389bf124
Medical Imaging Interaction Toolkit
mitkPlanarCross.cpp
Go to the documentation of this file.
1 /*============================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center (DKFZ)
6 All rights reserved.
7 
8 Use of this source code is governed by a 3-clause BSD license that can be
9 found in the LICENSE file.
10 
11 ============================================================================*/
12 
13 #include "mitkPlanarCross.h"
14 #include "mitkPlaneGeometry.h"
15 #include "mitkProperties.h"
16 
18  : FEATURE_ID_LONGESTDIAMETER(this->AddFeature("Longest Axis", "mm")),
19  FEATURE_ID_SHORTAXISDIAMETER(this->AddFeature("Short Axis", "mm"))
20 {
21  // Cross has two control points at the beginning
23 
24  // Create property for SingleLineMode (default: false)
25  this->SetProperty("SingleLineMode", mitk::BoolProperty::New(false));
26 
27  // Create helper polyline object (for drawing the orthogonal orientation line)
29  m_HelperPolyLinesToBePainted->InsertElement(0, false);
30 }
31 
32 void mitk::PlanarCross::SetSingleLineMode(bool singleLineMode)
33 {
34  this->SetProperty("SingleLineMode", mitk::BoolProperty::New(singleLineMode));
35  this->Modified();
36 }
37 
39 {
40  const mitk::BoolProperty *singleLineMode =
41  dynamic_cast<mitk::BoolProperty *>(this->GetProperty("SingleLineMode").GetPointer());
42 
43  if (singleLineMode != nullptr)
44  {
45  return singleLineMode->GetValue();
46  }
47  return false;
48 }
49 
51 {
52  return this->GetSingleLineMode() == false || (m_SelectedControlPoint >= 0 && m_SelectedControlPoint <= 3);
53 }
54 
56 {
57  if (this->GetSingleLineMode())
58  {
59  // In single line mode --> nothing to reset
60  return false;
61  }
62 
63  switch (m_SelectedControlPoint)
64  {
65  default:
66  // Nothing selected --> nothing to reset
67  return false;
68  case 0:
69  // Control point 0 selected: exchange points 0 and 1
70  {
71  const Point2D tmpPoint = this->GetControlPoint(0);
72  this->SetControlPoint(0, this->GetControlPoint(1));
73  this->SetControlPoint(1, tmpPoint);
74  }
75  // FALLTHRU!
76  case 1:
77  // Control point 0 or 1 selected: reset number of control points to two
79  this->SelectControlPoint(1);
80  return true;
81  case 2:
82  // Control point 2 selected: replace point 0 with point 3 and point 1 with point 2
83  this->SetControlPoint(0, this->GetControlPoint(3));
84  this->SetControlPoint(1, this->GetControlPoint(2));
85 
86  // Adjust selected control point, reset number of control points to two
88  this->SelectControlPoint(1);
89  return true;
90  case 3:
91  // Control point 3 selected: replace point 0 with point 2 and point 1 with point 3
92 
93  this->SetControlPoint(0, this->GetControlPoint(2));
94  this->SetControlPoint(1, this->GetControlPoint(3));
95 
96  // Adjust selected control point, reset number of control points to two
98  this->SelectControlPoint(1);
99  return true;
100  }
101 }
102 
104 {
105  if (this->GetSingleLineMode() || (this->GetNumberOfControlPoints() < 4))
106  {
107  return 1;
108  }
109  else
110  {
111  return 2;
112  }
113 }
114 
116 {
117  // Apply spatial constraints from superclass and from this class until the resulting constrained
118  // point converges. Although not an optimal implementation, this iterative approach
119  // helps to respect both constraints from the superclass and from this class. Without this,
120  // situations may occur where control points are constrained by the superclass, but again
121  // moved out of the superclass bounds by the subclass, or vice versa.
122 
123  unsigned int count = 0; // ensures stop of approach if point does not converge in reasonable time
124  Point2D confinedPoint = point;
125  Point2D superclassConfinedPoint;
126  do
127  {
128  superclassConfinedPoint = Superclass::ApplyControlPointConstraints(index, confinedPoint);
129  confinedPoint = this->InternalApplyControlPointConstraints(index, superclassConfinedPoint);
130  ++count;
131  } while ((confinedPoint.EuclideanDistanceTo(superclassConfinedPoint) > mitk::eps) && (count < 32));
132 
133  return confinedPoint;
134 }
135 
136 mitk::Point2D mitk::PlanarCross::InternalApplyControlPointConstraints(unsigned int index, const Point2D &point)
137 {
138  // Apply constraints depending on current interaction state
139  switch (index)
140  {
141  case 2:
142  {
143  // Check if 3rd control point is outside of the range (2D area) defined by the first
144  // line (via the first two control points); if it is outside, clip it to the bounds
145  const Point2D p1 = this->GetControlPoint(0);
146  const Point2D p2 = this->GetControlPoint(1);
147 
148  Vector2D n1 = p2 - p1;
149  n1.Normalize();
150 
151  const Vector2D v1 = point - p1;
152  const double dotProduct = n1 * v1;
153  const Point2D crossPoint = p1 + n1 * dotProduct;
154  ;
155  const Vector2D crossVector = point - crossPoint;
156 
157  if (dotProduct < 0.0)
158  {
159  // Out-of-bounds on the left: clip point to left boundary
160  return (p1 + crossVector);
161  }
162  else if (dotProduct > p2.EuclideanDistanceTo(p1))
163  {
164  // Out-of-bounds on the right: clip point to right boundary
165  return (p2 + crossVector);
166  }
167  else
168  {
169  // Pass back original point
170  return point;
171  }
172  }
173 
174  case 3:
175  {
176  // Constrain 4th control point so that with the 3rd control point it forms
177  // a line orthogonal to the first line (constraint 1); the 4th control point
178  // must lie on the opposite side of the line defined by the first two control
179  // points than the 3rd control point (constraint 2)
180  const Point2D p1 = this->GetControlPoint(0);
181  const Point2D p2 = this->GetControlPoint(1);
182  const Point2D p3 = this->GetControlPoint(2);
183 
184  // Calculate distance of original point from orthogonal line the corrected
185  // point should lie on to project the point onto this line
186  Vector2D n1 = p2 - p1;
187  n1.Normalize();
188 
189  const Vector2D v1 = point - p3;
190  const double dotProduct1 = n1 * v1;
191 
192  const Point2D pointOnLine = point - n1 * dotProduct1;
193 
194  // Project new point onto line [p1, p2]
195  const Vector2D v2 = pointOnLine - p1;
196  double dotProduct2 = n1 * v2;
197 
198  const Point2D crossingPoint = p1 + n1 * dotProduct2;
199 
200  // Determine whether the projected point on the line, or the crossing point should be
201  // used (according to the second constraint in the comment above)
202  if ((pointOnLine.SquaredEuclideanDistanceTo(p3) > crossingPoint.SquaredEuclideanDistanceTo(p3)) &&
203  (pointOnLine.SquaredEuclideanDistanceTo(p3) > pointOnLine.SquaredEuclideanDistanceTo(crossingPoint)))
204  {
205  return pointOnLine;
206  }
207  else
208  {
209  return crossingPoint;
210  }
211  }
212 
213  default:
214  return point;
215  }
216 }
217 
219 {
220  this->SetNumberOfPolyLines(1);
221  this->ClearPolyLines();
222 
223  if (this->GetNumberOfControlPoints() > 2)
224  this->SetNumberOfPolyLines(2);
225 
226  for (unsigned int i = 0; i < this->GetNumberOfControlPoints(); ++i)
227  {
228  if (i < 2)
229  this->AppendPointToPolyLine(0, this->GetControlPoint(i));
230 
231  if (i > 1)
232  this->AppendPointToPolyLine(1, this->GetControlPoint(i));
233  }
234 }
235 
236 void mitk::PlanarCross::GenerateHelperPolyLine(double /*mmPerDisplayUnit*/, unsigned int /*displayHeight*/)
237 {
238  // Generate helper polyline (orientation line orthogonal to first line)
239  // if the third control point is currently being set
240  if (this->GetNumberOfControlPoints() != 3)
241  {
242  m_HelperPolyLinesToBePainted->SetElement(0, false);
243  return;
244  }
245 
246  m_HelperPolyLinesToBePainted->SetElement(0, true);
247 
248  this->ClearHelperPolyLines();
249 
250  // Calculate cross point of first line (p1 to p2) and orthogonal line through
251  // the third control point (p3)
252  const Point2D p1 = this->GetControlPoint(0);
253  const Point2D p2 = this->GetControlPoint(1);
254  const Point2D p3 = this->GetControlPoint(2);
255 
256  Vector2D n1 = p2 - p1;
257  n1.Normalize();
258 
259  const Vector2D v1 = p3 - p1;
260  const Point2D crossPoint = p1 + n1 * (n1 * v1);
261 
262  const Vector2D v2 = crossPoint - p3;
263  if (v2.GetNorm() < 1.0)
264  {
265  // If third point is on the first line, draw orthogonal "infinite" line
266  // through cross point on line
267  Vector2D v0;
268  v0[0] = n1[1];
269  v0[1] = -n1[0];
270  this->AppendPointToHelperPolyLine(0, Point2D(p3 - v0 * 10000.0));
271  this->AppendPointToHelperPolyLine(0, Point2D(p3 + v0 * 10000.0));
272  }
273  else
274  {
275  // Else, draw orthogonal line starting from third point and crossing the
276  // first line, open-ended only on the other side
277  this->AppendPointToHelperPolyLine(0, p3);
278  this->AppendPointToHelperPolyLine(0, Point2D(p3 + v2 * 10000.0));
279  }
280 }
281 
283 {
284  // Calculate length of first line
285  const Point3D &p0 = this->GetWorldControlPoint(0);
286  const Point3D &p1 = this->GetWorldControlPoint(1);
287  double l1 = p0.EuclideanDistanceTo(p1);
288 
289  // Calculate length of second line
290  double l2 = 0.0;
291  if (!this->GetSingleLineMode() && (this->GetNumberOfControlPoints() > 3))
292  {
293  const Point3D &p2 = this->GetWorldControlPoint(2);
294  const Point3D &p3 = this->GetWorldControlPoint(3);
295  l2 = p2.EuclideanDistanceTo(p3);
296  }
297 
298  double longestDiameter;
299  double shortAxisDiameter;
300  if (l1 > l2)
301  {
302  longestDiameter = l1;
303  shortAxisDiameter = l2;
304  }
305  else
306  {
307  longestDiameter = l2;
308  shortAxisDiameter = l1;
309  }
310 
311  this->SetQuantity(FEATURE_ID_LONGESTDIAMETER, longestDiameter);
312  this->SetQuantity(FEATURE_ID_SHORTAXISDIAMETER, shortAxisDiameter);
313 }
314 
315 void mitk::PlanarCross::PrintSelf(std::ostream &os, itk::Indent indent) const
316 {
317  Superclass::PrintSelf(os, indent);
318 }
319 
321 {
322  const auto *otherCross = dynamic_cast<const mitk::PlanarCross *>(&other);
323  if (otherCross)
324  {
325  return Superclass::Equals(other);
326  }
327  else
328  {
329  return false;
330  }
331 }
bool Equals(const mitk::PlanarFigure &other) const override
Compare two PlanarFigure objects Note: all subclasses have to implement the method on their own...
Point< ScalarType, 2 > Point2D
Definition: mitkPoint.h:94
Point2D GetControlPoint(unsigned int index) const
Returns specified control point in 2D world coordinates.
Point2D ApplyControlPointConstraints(unsigned int index, const Point2D &point) override
Spatially constrain control points of second (orthogonal) line.
bool GetSingleLineMode() const
Indicates whether the PlanarFigure shall represent only a single line instead of an orthogonal cross...
Point3D GetWorldControlPoint(unsigned int index) const
Returns specified control point in world coordinates.
const unsigned int FEATURE_ID_LONGESTDIAMETER
unsigned int GetNumberOfFeatures() const override
Returns the number of features available for this PlanarCross (1 or 2).
void PrintSelf(std::ostream &os, itk::Indent indent) const override
void ResetNumberOfControlPoints(int numberOfControlPoints)
Set the initial number of control points of the planar figure.
void SetProperty(const std::string &propertyKey, BaseProperty *property, const std::string &contextName="", bool fallBackOnDefaultContext=false) override
Add new or change existent property.
virtual T GetValue() const
unsigned int GetNumberOfControlPoints() const
Returns the current number of 2D control points defining this figure.
static Pointer New()
void SetQuantity(unsigned int index, double quantity)
bool ResetOnPointSelect() override
The cross shall be reset to a single line when a control point is selected.
void SetNumberOfHelperPolyLines(unsigned int numberOfHelperPolyLines)
defines the number of HelperPolyLines that will be available
void SetSingleLineMode(bool singleLineMode)
Indicates whether the PlanarFigure shall represent only a single line instead of an orthogonal cross...
void AppendPointToPolyLine(unsigned int index, PolyLineElement element)
Append a point to the PolyLine # index.
bool ResetOnPointSelectNeeded() const override
mitk::BaseProperty::Pointer GetProperty(const char *propertyKey) const
Get the property (instance of BaseProperty) with key propertyKey from the PropertyList, and set it to this, respectively;.
void SetNumberOfPolyLines(unsigned int numberOfPolyLines)
defines the number of PolyLines that will be available
Implementation of PlanarFigure modeling a cross with two orthogonal lines on a plane.
virtual bool SetControlPoint(unsigned int index, const Point2D &point, bool createIfDoesNotExist=false)
Base-class for geometric planar (2D) figures, such as lines, circles, rectangles, polygons...
void AppendPointToHelperPolyLine(unsigned int index, PolyLineElement element)
Append a point to the HelperPolyLine # index.
void ClearHelperPolyLines()
clears the list of HelperPolyLines. Call before re-calculating a new HelperPolyline.
BoolContainerType::Pointer m_HelperPolyLinesToBePainted
void GeneratePolyLine() override
Generates the poly-line representation of the planar figure.
MITKCORE_EXPORT const ScalarType eps
virtual bool SelectControlPoint(unsigned int index)
Selects currently active control points.
void GenerateHelperPolyLine(double mmPerDisplayUnit, unsigned int displayHeight) override
Generates the poly-lines that should be drawn the same size regardless of zoom.
void EvaluateFeaturesInternal() override
Calculates feature quantities of the planar figure.
const unsigned int FEATURE_ID_SHORTAXISDIAMETER
void ClearPolyLines()
clears the list of PolyLines. Call before re-calculating a new Polyline.