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