libpappsomspp
Library for mass spectrometry
selectionpolygon.cpp
Go to the documentation of this file.
1 // Copyright 2021 Filippo Rusconi
2 // GPLv3+
3 
4 
5 /////////////////////// StdLib includes
6 #include <limits>
7 #include <cmath>
8 
9 
10 /////////////////////// Qt includes
11 #include <QDebug>
12 
13 
14 /////////////////////// Local includes
15 #include "selectionpolygon.h"
16 
17 
18 namespace pappso
19 {
20 
22 {
23  // When we create a polygon, we create it as immense as possible, so that any
24  // other polygon will fill inside it and *this polygon by necessity will
25  // contain another one based on experimental data. See the header file for the
26  // creation of the four points.
27 }
28 
29 
30 SelectionPolygon::SelectionPolygon(QPointF top_left_point,
31  QPointF top_right_point)
32 {
33  // First clear the default values points because we want to push_back
34  // new points and we want to only ever have 4 points.
35  m_points.clear();
36 
37  // We get only two points that provide the horizontal range of the polygon.
38  // These two points show the x range of the polygon. We need to craft a
39  // polygon that has:
40  //
41  // that specified x range and
42  //
43  // the widest y range possible.
44 
45  // top left point
46  m_points.push_back(
47  QPointF(top_left_point.x(), std::numeric_limits<double>::max()));
48 
49  // top right point
50  m_points.push_back(
51  QPointF(top_right_point.x(), std::numeric_limits<double>::max()));
52 
53  // bottom right point
54  m_points.push_back(
55  QPointF(top_right_point.x(), std::numeric_limits<double>::min()));
56 
57  // bottom left point
58  m_points.push_back(
59  QPointF(top_left_point.x(), std::numeric_limits<double>::min()));
60 
61  // Compute the min|max x|y coordinates of the polygon that will be used to
62  // quickly check if a point is outside.
64 }
65 
66 
67 SelectionPolygon::SelectionPolygon(QPointF top_left_point,
68  QPointF top_right_point,
69  QPointF bottom_right_point,
70  QPointF bottom_left_point)
71 {
72  // First clear the default values points.
73  m_points.clear();
74 
75  // Attention, we need to push back the points starting top left and clockwise.
76 
77  m_points.push_back(top_left_point);
78  m_points.push_back(top_right_point);
79  m_points.push_back(bottom_right_point);
80  m_points.push_back(bottom_left_point);
81 
82  // Compute the min|max x|y coordinates of the polygon that will be used to
83  // quickly check if a point is outside.
85 }
86 
87 
89 {
90  if(other.m_points.size() != static_cast<int>(PointSpecs::ENUM_LAST))
91  qFatal(
92  "The template selection polygon must have four points, no less, no more");
93 
94  // First clear the default values points.
95  m_points.clear();
96 
97  for(int iter = 0; iter < static_cast<int>(PointSpecs::ENUM_LAST); ++iter)
98  {
99  m_points.push_back(other.m_points[iter]);
100  }
101 
102  m_minX = other.m_minX;
103  m_minY = other.m_minY;
104 
105  m_maxX = other.m_maxX;
106  m_maxY = other.m_maxY;
107 }
108 
109 
111 {
112 }
113 
114 
115 void
116 SelectionPolygon::setPoint(PointSpecs point_spec, double x, double y)
117 {
118  m_points[static_cast<int>(point_spec)].setX(x);
119  m_points[static_cast<int>(point_spec)].setY(y);
120 
122 }
123 
124 
125 void
126 SelectionPolygon::setPoint(PointSpecs point_spec, QPointF point)
127 {
128  setPoint(point_spec, point.x(), point.y());
129 
131 }
132 
133 
134 void
136  PointSpecs point_spec_dest)
137 {
138  QPointF src_point = getPoint(point_spec_src);
139  setPoint(point_spec_dest, src_point);
140 
142 }
143 
144 
145 void
146 SelectionPolygon::set1D(double x_range_start, double x_range_end)
147 {
148  // We get only two points that provide the horizontal range of the polygon.
149  // These two points show the x range of the polygon. We need to craft a
150  // polygon that has:
151  //
152  // that specified x range and
153  //
154  // the widest y range possible.
155 
156  resetPoints();
157 
158  // top left point
160  QPointF(x_range_start, std::numeric_limits<double>::max()));
161 
162  // top right point
164  QPointF(x_range_end, std::numeric_limits<double>::max()));
165 
166  // bottom right point
168  QPointF(x_range_end, std::numeric_limits<double>::min()));
169 
170  // bottom left point
172  QPointF(x_range_start, std::numeric_limits<double>::min()));
173 
174  // Compute the min|max x|y coordinates of the polygon that will be used to
175  // quickly check if a point is outside.
177 }
178 
179 
180 void
181 SelectionPolygon::set2D(QPointF top_left,
182  QPointF top_right,
183  QPointF bottom_right,
184  QPointF bottom_left)
185 {
186  resetPoints();
187 
188  // top left point
190  // qDebug() << "PointSpecs::TOP_LEFT_POINT:" << top_left;
191 
192  // top right point
194  // qDebug() << "PointSpecs::TOP_RIGHT_POINT:" << top_right;
195 
196  // bottom right point
198  // qDebug() << "PointSpecs::BOTTOM_RIGHT_POINT:" << bottom_right;
199 
200  // bottom left point
202  // qDebug() << "PointSpecs::BOTTOM_LEFT_POINT:" << bottom_left;
203 
204  // Compute the min|max x|y coordinates of the polygon that will be used to
205  // quickly check if a point is outside.
207 
208  // qDebug() << toString();
209 }
210 
211 
212 void
214 {
215  // When a 2D polygon is converted to a 1D polygon, the x axis range is
216  // unchanged, but the height is set to its maximum possible with the bottom
217  // line at y = min and the top line at y = max.
218 
220 
221  set1D(m_minX, m_maxX);
222 }
223 
224 
225 QPointF
227 {
228  // When we say leftmost, that means that we are implicitely interesed in
229  // x-axis coordinate of the points.
230 
231  QPointF temp_point(std::numeric_limits<double>::max(), 0);
232 
233  for(int iter = 0; iter < static_cast<int>(PointSpecs::ENUM_LAST); ++iter)
234  {
235  if(m_points[iter].x() < temp_point.x())
236  {
237  temp_point = m_points[iter];
238  }
239  }
240 
241  return temp_point;
242 }
243 
244 
245 QPointF
247 {
248  // When we say rightmost, that means that we are implicitely interesed in
249  // x-axis coordinate of the points.
250 
251  QPointF temp_point(std::numeric_limits<double>::min(), 0);
252 
253  for(int iter = 0; iter < static_cast<int>(PointSpecs::ENUM_LAST); ++iter)
254  {
255  if(m_points[iter].x() > temp_point.x())
256  {
257  temp_point = m_points[iter];
258  }
259  }
260 
261  return temp_point;
262 }
263 
264 
265 QPointF
267 {
268  // When we say topmost or bottommost , that means that we are implicitely
269  // interesed in y-axis coordinate of the points.
270 
271  QPointF temp_point(0, std::numeric_limits<double>::min());
272 
273  for(int iter = 0; iter < static_cast<int>(PointSpecs::ENUM_LAST); ++iter)
274  {
275  if(m_points[iter].y() > temp_point.y())
276  {
277  temp_point = m_points[iter];
278  }
279  }
280 
281  return temp_point;
282 }
283 
284 
285 QPointF
287 {
288  // When we say topmost or bottommost , that means that we are implicitely
289  // interesed in y-axis coordinate of the points.
290 
291  QPointF temp_point(0, std::numeric_limits<double>::max());
292 
293  for(int iter = 0; iter < static_cast<int>(PointSpecs::ENUM_LAST); ++iter)
294  {
295  if(m_points[iter].y() < temp_point.y())
296  {
297  temp_point = m_points[iter];
298  }
299  }
300 
301  return temp_point;
302 }
303 
304 
305 const std::vector<QPointF> &
307 {
308  return m_points;
309 }
310 
311 
312 QPointF
314 {
315  return m_points[static_cast<int>(point_spec)];
316 }
317 
318 
319 bool
321 {
322  // Set the variable to starting values that allow easy value comparisons with
323  // std::min() and std::max() for checking the x|y values below.
324 
325  m_minX = std::numeric_limits<double>::max();
326  m_minY = std::numeric_limits<double>::max();
327  m_maxX = std::numeric_limits<double>::min();
328  m_maxY = std::numeric_limits<double>::min();
329 
330  for(int iter = 0; iter < static_cast<int>(PointSpecs::ENUM_LAST); ++iter)
331  {
332  m_minX = std::min(m_points.at(iter).x(), m_minX);
333  m_maxX = std::max(m_points.at(iter).x(), m_maxX);
334 
335  m_minY = std::min(m_points.at(iter).y(), m_minY);
336  m_maxY = std::max(m_points.at(iter).y(), m_maxY);
337  }
338 
339  return true;
340 }
341 
342 
343 bool
345  double &max_x,
346  double &min_y,
347  double &max_y) const
348 {
349  // Set the variable to starting values that allow easy value comparisons with
350  // std::min() and std::max() for checking the x|y values below.
351 
352  min_x = std::numeric_limits<double>::max();
353  min_y = std::numeric_limits<double>::max();
354  max_x = std::numeric_limits<double>::min();
355  max_y = std::numeric_limits<double>::min();
356 
357  for(int iter = 0; iter < static_cast<int>(PointSpecs::ENUM_LAST); ++iter)
358  {
359  min_x = std::min(m_points.at(iter).x(), min_x);
360  max_x = std::max(m_points.at(iter).x(), max_x);
361 
362  min_y = std::min(m_points.at(iter).y(), min_y);
363  max_y = std::max(m_points.at(iter).y(), max_y);
364  }
365 
366  // qDebug() << "min_x:" << min_x << "max_x:" << max_x << "min_y:" << min_y
367  //<< "max_y:" << max_y;
368 
369  return true;
370 }
371 
372 
373 double
374 SelectionPolygon::width(bool &ok) const
375 {
376  double min_x;
377  double min_y;
378  double max_x;
379  double max_y;
380 
381  computeMinMaxCoordinates(min_x, max_x, min_y, max_y);
382 
383  ok = true;
384  return max_x - min_x;
385 }
386 
387 
388 double
390 {
391  double min_x;
392  double min_y;
393  double max_x;
394  double max_y;
395 
396  computeMinMaxCoordinates(min_x, max_x, min_y, max_y);
397 
398  ok = true;
399  return max_y - min_y;
400 }
401 
402 
403 bool
404 SelectionPolygon::rangeX(double &range_start, double &range_end) const
405 {
406  double min_y = std::numeric_limits<double>::max();
407  double max_y = std::numeric_limits<double>::min();
408 
409  return computeMinMaxCoordinates(range_start, range_end, min_y, max_y);
410 }
411 
412 
413 bool
414 SelectionPolygon::rangeY(double &range_start, double &range_end) const
415 {
416  double min_x = std::numeric_limits<double>::max();
417  double max_x = std::numeric_limits<double>::min();
418 
419  return computeMinMaxCoordinates(min_x, max_x, range_start, range_end);
420 }
421 
422 
423 bool
424 SelectionPolygon::range(Axis axis, double &range_start, double &range_end) const
425 {
426  if(axis == Axis::x)
427  return rangeX(range_start, range_end);
428  else if(axis == Axis::y)
429  return rangeY(range_start, range_end);
430 
431  return false;
432 }
433 
434 
435 // All this is valid only if the polygon has four sides.
436 //
437 // Transposing means that we take each point and permute x with y.
438 // During transposition, the TOP_RIGHT_POINT BOTTOM_LEFT_POINT are invariant.
439 //
440 // Transposition is like rotating the polygon arount the
441 // BOTTOM_LEFT_POINT--TOP_RIGHT_POINT axis, which make them invariant and
442 // permutates TOP_LEFT_POINT with BOTTOM_RIGHT_POINT.
443 //
444 // If iteration in the points is clockwise before, iteration below
445 // counter-clockwise after transposition, that is:
446 // TOP_LEFT_POINT becomes BOTTOM_RIGHT_POINT
447 //
450 {
451  SelectionPolygon selection_polygon;
452 
453  // Make sure we do this for a polygon with four sides.
454 
455  if(m_points.size() != static_cast<int>(PointSpecs::ENUM_LAST))
456  qFatal("The polygon must have four points, no less, no more");
457 
458  // The two invariant points, that is, the two points that do no change
459  // position in the polygon corners. Of course, x becomes y.
460  selection_polygon.setPoint(
464 
465  selection_polygon.setPoint(
469 
470  // The two other points.
471 
472  selection_polygon.setPoint(PointSpecs::BOTTOM_RIGHT_POINT,
475 
476  selection_polygon.setPoint(
480 
481  return selection_polygon;
482 }
483 
484 
485 bool
486 SelectionPolygon::contains(const QPointF &tested_point) const
487 {
488  // Easy check: if the point lies outside of most external limits of the
489  // polygon, return false.
490 
491  if(tested_point.x() < m_minX || tested_point.x() > m_maxX ||
492  tested_point.y() < m_minY || tested_point.y() > m_maxY)
493  {
494  //qDebug() << "Testing point:" << tested_point
495  //<< "aginst polygon:" << toString()
496  //<< "is out of x and y ranges.";
497  return false;
498  }
499 
500  // There are two situations:
501  //
502  // 1. The selection polygon is a rectangle, we can check the tested_point very
503  // easily.
504  //
505  // 2. The selection polygon is a skewed rectangle, that is, it is a
506  // parallelogram, we need to really use the point-in-polygon algorithm.
507 
508  if(isRectangle())
509  {
510  //qDebug() << "Selection polygon *is* rectangle.";
511 
512  double x = tested_point.x();
513  double y = tested_point.y();
514 
515  // return (x >= getPoint(PointSpecs::TOP_LEFT_POINT).x() &&
516  // x <= getPoint(PointSpecs::TOP_RIGHT_POINT).x() &&
517  // y >= getPoint(PointSpecs::BOTTOM_LEFT_POINT).y() &&
518  // y <= getPoint(PointSpecs::TOP_LEFT_POINT).y());
519 
520  bool res = x >= m_minX && x <= m_maxX && y >= m_minY && y <= m_maxY;
521 
522  //qDebug() << qSetRealNumberPrecision(10) << "Returning: " << res
523  //<< "for point:" << tested_point
524  //<< "and selection polygon:" << toString();
525 
526  return res;
527  }
528 
529  //qDebug() << "Testing point:" << tested_point
530  //<< "aginst polygon:" << toString()
531  //<< "is tested against a skewed selection polygon rectangle.";
532 
533  // At this point, we know the selection polygon is not rectangle, we have to
534  // make the real check using the point-in-polygon algorithm.
535 
536  // This code is inspired by the work described here:
537  // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
538 
539  // int pnpoly(int vertex_count, float *vertx, float *verty, float testx,
540  // float testy)
541 
542  int i = 0;
543  int j = 0;
544  bool is_inside = false;
545 
546  int vertex_count = m_points.size();
547 
548  for(i = 0, j = vertex_count - 1; i < vertex_count; j = i++)
549  {
550  if(((m_points.at(i).y() > tested_point.y()) !=
551  (m_points.at(j).y() > tested_point.y())) &&
552  (tested_point.x() < (m_points.at(j).x() - m_points.at(i).x()) *
553  (tested_point.y() - m_points.at(i).y()) /
554  (m_points.at(j).y() - m_points.at(i).y()) +
555  m_points.at(i).x()))
556  is_inside = !is_inside;
557  }
558 
559  //if(is_inside)
560  //qDebug() << "Testing point:" << tested_point
561  //<< "aginst polygon:" << toString() << "turns out be in.";
562  //else
563  //qDebug() << "Testing point:" << tested_point
564  //<< "aginst polygon:" << toString() << "turns out be out.";
565 
566  return is_inside;
567 }
568 
569 
570 bool
571 SelectionPolygon::contains(const SelectionPolygon &selection_polygon) const
572 {
573  // A polygon is inside another polygon if all its points are inside the
574  // polygon.
575 
576  bool is_inside = true;
577 
578  for(int iter = 0; iter < static_cast<int>(PointSpecs::ENUM_LAST); ++iter)
579  {
580  if(!contains(selection_polygon.getPoint(static_cast<PointSpecs>(iter))))
581  is_inside = false;
582  }
583 
584  return is_inside;
585 }
586 
587 
590 {
591  if(this == &other)
592  return *this;
593 
594  if(other.m_points.size() != static_cast<int>(PointSpecs::ENUM_LAST))
595  qFatal("Programming error.");
596 
597  if(m_points.size() != static_cast<int>(PointSpecs::ENUM_LAST))
598  qFatal("Programming error.");
599 
600  for(int iter = 0; iter < static_cast<int>(PointSpecs::ENUM_LAST); ++iter)
601  m_points[iter] = other.m_points[iter];
602 
603  m_minX = other.m_minX;
604  m_minY = other.m_minY;
605 
606  m_maxX = other.m_maxX;
607  m_maxY = other.m_maxY;
608 
609  return *this;
610 }
611 
612 
613 void
615 {
616  // Reset the points exactly as they were set upon construction of an empty
617  // polygon.
618 
619  m_points[0] = QPointF(std::numeric_limits<double>::min(),
620  std::numeric_limits<double>::max());
621  m_points[0] = QPointF(std::numeric_limits<double>::max(),
622  std::numeric_limits<double>::max());
623  m_points[0] = QPointF(std::numeric_limits<double>::max(),
624  std::numeric_limits<double>::min());
625  m_points[0] = QPointF(std::numeric_limits<double>::min(),
626  std::numeric_limits<double>::max());
627 }
628 
629 
630 bool
632 {
633  // qDebug() << "Selection polygon:" << toString();
634 
635  bool ok = false;
636 
637  double width_value = width(ok);
638  if(!ok)
639  return false;
640 
641  double height_value = height(ok);
642  if(!ok)
643  return false;
644 
645  // qDebug() << "Width and height computations succeeded:"
646  //<< "width:" << width_value << "height:" << height_value;
647 
648  // A polygon is mono-dimensional if it has both non-0 width and no (max-min)
649  // width AND if the height is 0 or (max-min).
650  return (
651  (width_value > 0 && width_value < std::numeric_limits<double>::max() -
652  std::numeric_limits<double>::min()) &&
653  (height_value == 0 ||
654  height_value == std::numeric_limits<double>::max() -
655  std::numeric_limits<double>::min()));
656 }
657 
658 
659 bool
661 {
662  // A selection polygon can behave like a line segment if the bottom side
663  // confounds with the top side.
664 
665  bool ok = false;
666 
667  double width_value = width(ok);
668  if(!ok)
669  return false;
670 
671  double height_value = height(ok);
672  if(!ok)
673  return false;
674 
675  // A polygon is two-dimensional if it has both non-0 width and no (max-min)
676  // width AND same for height.
677  return (
678  (width_value > 0 && width_value < std::numeric_limits<double>::max() -
679  std::numeric_limits<double>::min()) &&
680  (height_value > 0 && height_value < std::numeric_limits<double>::max() -
681  std::numeric_limits<double>::min()));
682 }
683 
684 
685 bool
687 {
688  // A skewed rectangle polygon has the following conditions verified:
689  //
690  // 1. If its left|right sides are vertical, then its top|bottom lines are
691  // *not* horizontal.
692  //
693  // 2 If its top|bottom lines are horizontal, then its left|right sides are
694  // *not* vertical.
695  //
696  // 3. Then, if a selection polygon is rectangle, its top|bottom lines are
697  // horizontal and its left|right lines are vertical.
698 
699  // A line is vertical if its two defining points have the same X.
700  // A line is horizontal if its two defining points have the same Y.
701 
702  // Try the horiontal top|bottom lines.
703 
708  {
709  // We have horizontal top|bottom lines
710 
711  // Try the vertical lines
712 
717  {
718  // The top|bottom lines are vertical
719 
720  return true;
721  }
722  }
723 
724  return false;
725 }
726 
727 
728 QString
730 {
731  // By essence, a selection polygon is designed to always have 4 points.
732 
733  if(m_points.size() != static_cast<int>(PointSpecs::ENUM_LAST))
734  qFatal("Programming error.");
735 
736  // qDebug() << "size:" << m_points.size();
737 
738  QString text = "Selection polygon points, from top left, clockwise\n";
739 
740  for(int iter = 0; iter < static_cast<int>(PointSpecs::ENUM_LAST); ++iter)
741  {
742  QPointF iter_point = m_points[iter];
743 
744  QString x_string = "NOT_SET";
745 
746  if(iter_point.x() != std::numeric_limits<double>::min() &&
747  iter_point.x() != std::numeric_limits<double>::max())
748  x_string = QString("%1").arg(iter_point.x(), 0, 'f', 10);
749 
750  QString y_string = "NOT_SET";
751 
752  if(iter_point.y() != std::numeric_limits<double>::min() &&
753  iter_point.y() != std::numeric_limits<double>::max())
754  y_string = QString("%1").arg(iter_point.y(), 0, 'f', 10);
755 
756  text += QString("(%1,%2)\n").arg(x_string).arg(y_string);
757  }
758 
759  if(m_minX != std::numeric_limits<double>::min() && m_minX != std::numeric_limits<double>::max())
760  text += QString("minX: %1 - ").arg(m_minX, 0, 'f', 10);
761  else
762  text += QString("minX: NOT_SET - ");
763 
764  if(m_maxX != std::numeric_limits<double>::min() && m_maxX != std::numeric_limits<double>::max())
765  text += QString("maxX: %1 - ").arg(m_maxX, 0, 'f', 10);
766  else
767  text += QString("maxX: NOT_SET - ");
768 
769  if(m_minY != std::numeric_limits<double>::min() && m_minY != std::numeric_limits<double>::max())
770  text += QString("minY: %1 - ").arg(m_minY, 0, 'f', 10);
771  else
772  text += QString("minY: NOT_SET - ");
773 
774  if(m_maxY != std::numeric_limits<double>::min() && m_maxY != std::numeric_limits<double>::max())
775  text += QString("maxY: %1 - ").arg(m_maxY, 0, 'f', 10);
776  else
777  text += QString("maxY: NOT_SET - ");
778 
779  return text;
780 }
781 
782 
783 QString
785 {
786  // By essence, a selection polygon is designed to always have 4 points.
787 
788  if(m_points.size() != static_cast<int>(PointSpecs::ENUM_LAST))
789  qFatal("Programming error.");
790 
791  // qDebug() << "size:" << m_points.size();
792 
793  QString text = "[";
794 
795  QString x_string = "NOT_SET";
796  QString y_string = "NOT_SET";
797 
798  // There are two situations:
799  //
800  // 1. The selection polygon is 1D, we only need to provide two points
801  //
802  // 2. The selection polygon is 2D, we need to provide four points.
803 
804  if(is1D())
805  {
806  text += QString("(%1,%2)").arg(getLeftMostPoint().x()).arg("NOT_SET");
807  text += QString("(%1,%2)").arg(getRightMostPoint().x()).arg("NOT_SET");
808  }
809  else
810  {
811  for(int iter = 0; iter < static_cast<int>(PointSpecs::ENUM_LAST); ++iter)
812  {
813  QPointF iter_point = m_points[iter];
814 
815 
816  if(iter_point.x() != std::numeric_limits<double>::min() &&
817  iter_point.x() != std::numeric_limits<double>::max())
818  x_string = QString("%1").arg(iter_point.x(), 0, 'f', 3);
819 
820  if(iter_point.y() != std::numeric_limits<double>::min() &&
821  iter_point.y() != std::numeric_limits<double>::max())
822  y_string = QString("%1").arg(iter_point.y(), 0, 'f', 3);
823 
824  text += QString("(%1,%2)").arg(x_string).arg(y_string);
825  }
826  }
827 
828  text += "]";
829 
830  return text;
831 }
832 
833 
834 void
836  const QPointF &tested_point)
837 {
838  bool is_point_inside = false;
839 
840  QString debug_string;
841 
842  is_point_inside = selection_polygon.contains(tested_point);
843  debug_string = QString("(%1,%2) is inside: %3")
844  .arg(tested_point.x(), 0, 'f', 10)
845  .arg(tested_point.y(), 0, 'f', 10)
846  .arg(is_point_inside ? "true" : "false");
847  qDebug().noquote() << debug_string;
848 }
849 } // namespace pappso
850 
851 #if 0
852 
853 // This is to test the algo that check if a point is left of a line or right of
854 // it or onto it. In this implementation, two lines define the vertical sides of
855 // a polygon (square to ease analysis) and if the point is on the left line, then it
856 // is considered ok, that is, that it is on the right side of the line and if it
857 // is on the right line, then it is considered ok, that is that it is on the
858 // left side of the line. If both conditions are ok, then the point is inside
859 // the vertical lines of the polygon.
860 
861  qDebug();
862 
863  pappso::SelectionPolygon selection_polygon(QPointF(22.4, 473),
864  QPointF(28.9, 473),
865  QPointF(28.9, 250),
866  QPointF(22.4, 250));
867  qDebug() << "The test selection polygon:" << selection_polygon.toString();
868 
869  std::vector<QPointF> test_points;
870 
871  test_points.push_back(QPointF(25, 250));
872  test_points.push_back(QPointF(22.3, 362));
873  test_points.push_back(QPointF(22.4, 473));
874  test_points.push_back(QPointF(22.4, 473.5));
875  test_points.push_back(QPointF(25, 250));
876  test_points.push_back(QPointF(25, 250.5));
877  test_points.push_back(QPointF(25, 360));
878  test_points.push_back(QPointF(28.9, 250));
879  test_points.push_back(QPointF(29, 250));
880  test_points.push_back(QPointF(29, 360));
881  test_points.push_back(QPointF(28.9, 473));
882  test_points.push_back(QPointF(28.9, 473.5));
883  test_points.push_back(QPointF(20, 200));
884  test_points.push_back(QPointF(20, 600));
885  test_points.push_back(QPointF(35, 200));
886  test_points.push_back(QPointF(35, 600));
887 
888  // double res = sideofline(XX;YY;xA;yA;xB;yB) =
889  // (xB-xA) * (YY-yA) - (yB-yA) * (XX-xA)
890 
891  // If res == 0, the point is on the line
892  // If rest < 0, the point is on the right of the line
893  // If rest > 0, the point is on the left of the line
894 
895  for(auto &&data_point : test_points)
896  {
897  // Left vertical line of the polygon
898 
899  // Bottom point
900  double xA_left =
901  selection_polygon.getPoint(pappso::PointSpecs::BOTTOM_LEFT_POINT).x();
902  double yA_left =
903  selection_polygon.getPoint(pappso::PointSpecs::BOTTOM_LEFT_POINT).y();
904 
905  // Top point
906  double xB_left =
907  selection_polygon.getPoint(pappso::PointSpecs::TOP_LEFT_POINT).x();
908  double yB_left =
909  selection_polygon.getPoint(pappso::PointSpecs::TOP_LEFT_POINT).y();
910 
911  if((xB_left - xA_left) * (data_point.y() - yA_left) -
912  (yB_left - yA_left) * (data_point.x() - xA_left) >
913  0)
914  {
915  // The point is left of the left line. We can remove the point
916  // from the mass spectrum.
917 
918  qDebug() << qSetRealNumberPrecision(10)
919  << "Filtered out point (left of left line):"
920  << data_point.x() << "-" << data_point.y();
921  continue;
922  }
923  else
924  {
925  qDebug() << qSetRealNumberPrecision(10)
926  << "Kept point (right of left line):" << data_point.x()
927  << "-" << data_point.y();
928  }
929 
930  // Right vertical line of the polygon
931 
932  // Bottom point
933  double xA_right =
934  selection_polygon.getPoint(pappso::PointSpecs::BOTTOM_RIGHT_POINT).x();
935  double yA_right =
936  selection_polygon.getPoint(pappso::PointSpecs::BOTTOM_RIGHT_POINT).y();
937 
938  // Top point
939  double xB_right =
940  selection_polygon.getPoint(pappso::PointSpecs::TOP_RIGHT_POINT).x();
941  double yB_right =
942  selection_polygon.getPoint(pappso::PointSpecs::TOP_RIGHT_POINT).y();
943 
944  if((xB_right - xA_right) * (data_point.y() - yA_right) -
945  (yB_right - yA_right) * (data_point.x() - xA_right) <
946  0)
947  {
948  qDebug() << qSetRealNumberPrecision(10)
949  << "Filtered out point (right of right line):"
950  << data_point.x() << "-" << data_point.y();
951  }
952  else
953  {
954  qDebug() << qSetRealNumberPrecision(10)
955  << "Definitively kept point (left of right line):"
956  << data_point.x() << "-" << data_point.y();
957  }
958  }
959 #endif
960 
961 
962 #if 0
963  // This is code to test the algorithm we have to establish if a given
964  // point is inside a selection polygon.
965 
966  // First polygon that is square.
967  SelectionPolygon first_polygon(
968  QPointF(3, 8), QPointF(12, 8), QPointF(12, 3), QPointF(3, 3));
969 
970  qDebug() << "square rectangle polygon: " << first_polygon.toString();
971 
972  qDebug() << "outside";
973 
974  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(2,1));
975  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(2.999999,5));
976  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(2.999999,8.000001));
977  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(2,5));
978  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(2,9));
979  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,1));
980  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,8.0000001));
981  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(12,1));
982  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(12.0000001,3));
983  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(14,3));
984  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(14,5));
985  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(14,8));
986  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(14,9));
987  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,9));
988  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,2.9999999));
989 
990  qDebug() << "on the lines";
991 
992  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(3,4));
993  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,3));
994  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(12,3));
995  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(12,7));
996  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(12,8));
997  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,8));
998 
999  qDebug() << "inside";
1000 
1001  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(4,4));
1002  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(3.00001, 3.00001));
1003  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,4));
1004  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,3.1));
1005  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(11,5));
1006  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(11.99999,5));
1007  SelectionPolygon::debugAlgorithm(first_polygon, QPointF(7,7.9));
1008  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1009 
1010  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1011  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1012  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1013  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1014 
1015  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1016  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1017  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1018  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1019 
1020  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1021  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1022  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1023  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1024 
1025  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1026  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1027  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1028  SelectionPolygon::debugAlgorithm(first_polygon, QPointF());
1029 
1030 
1031  // Second polygon that is skewed.
1032  SelectionPolygon second_polygon(
1033  QPointF(9, 8), QPointF(12, 8), QPointF(6, 2), QPointF(3, 2));
1034 
1035  qDebug() << "skewed rectangle polygon: " << second_polygon.toString();
1036 
1037  qDebug() << "outside";
1038 
1039  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(2,1));
1040  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(2.999999,1.999999));
1041  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(2.999999,3));
1042  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(2.999999,8.000001));
1043  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(2,5));
1044  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(2,9));
1045  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(6,5.0000001));
1046  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(7,1));
1047  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(7,2.999999));
1048  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(9,4.999999));
1049  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(9,8.0000001));
1050  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(12.00000001,8));
1051  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(12,7.999999));
1052  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(14,3));
1053  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(14,5));
1054 
1055  qDebug() << "on the lines";
1056 
1057  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(3,2));
1058  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(6,5));
1059  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(12,8));
1060  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(9,8));
1061  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(9,5));
1062  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(11,7));
1063  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(7,6));
1064  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(4,3));
1065 
1066  qDebug() << "inside";
1067 
1068  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(3.00001,2.000001));
1069  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(4,2.000001));
1070  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(6.000001,2.00003));
1071  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(7,4));
1072  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(8.99999,5));
1073  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(10,7.99999));
1074  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(5,3));
1075  SelectionPolygon::debugAlgorithm(second_polygon, QPointF(5,3.99999));
1076 #endif
1077 
1078 
1079 #if 0
1080 
1081 void
1082 SelectionPolygon::reorderPoints()
1083 {
1084  // We want to make sure that we actually have the right QPointF instances at
1085  // the right corners.
1086 
1087  computeMinMaxCoordinates();
1088 
1089  // Start by finding a point almost centered in the polygon. Not exactly
1090  // centered because our polygon can be squared and we'll look at angles
1091  // between the center-point->polygon-point_n and
1092  // center-point->polygon-point_n'.
1093 
1094  double PI = 3.14159265358979323846;
1095  QPointF center_point(0, 0);
1096 
1097  for(auto &point : m_points)
1098  {
1099  center_point.setX(center_point.x() + point.x());
1100  center_point.setY(center_point.y() + point.y());
1101  }
1102 
1103  center_point.setX(center_point.x() / m_points.size());
1104  center_point.setY(center_point.y() / m_points.size());
1105 
1106  // For a rectangle polygon, that would be the exact center and the angles
1107  // between the line segments would be similar two-by-two. So we need to move
1108  // the center_point a bit.
1109 
1110  double distance_x = center_point.x() - m_minX;
1111  double distance_y = center_point.y() - m_minY;
1112 
1113  center_point.setX(center_point.x() - (distance_x / 20));
1114  center_point.setY(center_point.y() - (distance_y / 20));
1115 
1116  std::sort(m_points.begin(),
1117  m_points.end(),
1118  [center_point, PI](const QPointF &a, const QPointF &b) -> bool {
1119  double a1 = std::fmod(
1120  std::atan2(a.x() - center_point.x(), a.y() - center_point.y()) *
1121  180.0 / PI +
1122  360,
1123  (double)360);
1124 
1125  double a2 = std::fmod(
1126  std::atan2(b.x() - center_point.x(), b.y() - center_point.y()) *
1127  180.0 / PI +
1128  360,
1129  (double)360);
1130 
1131  // Original version that had problems because arguments to '%'
1132  // were double and int. fmod() is % for double.
1133  //(std::atan2(b.x() - center_point.x(), b.y() - center_point.y())
1134  //* 180.0 / PI + 360) % 360;
1135 
1136  return (int)(a1 - a2);
1137  });
1138 }
1139 
1140 #endif
1141 
void copyPoint(PointSpecs point_spec_src, PointSpecs point_spec_dest)
bool rangeX(double &range_start, double &range_end) const
bool rangeY(double &range_start, double &range_end) const
void setPoint(PointSpecs point_spec, double x, double y)
static void debugAlgorithm(const SelectionPolygon &selection_polygon, const QPointF &tested_point)
SelectionPolygon transpose() const
bool range(Axis axis, double &range_start, double &range_end) const
QString toShort4PointsString() const
QPointF getRightMostPoint() const
void set2D(QPointF top_left, QPointF top_right, QPointF bottom_right, QPointF bottom_left)
const std::vector< QPointF > & getPoints() const
double width(bool &ok) const
QPointF getLeftMostPoint() const
SelectionPolygon & operator=(const SelectionPolygon &other)
QPointF getBottomMostPoint() const
void set1D(double x_range_start, double x_range_end)
double height(bool &ok) const
bool contains(const QPointF &tested_point) const
QPointF getPoint(PointSpecs point_spec) const
std::vector< QPointF > m_points
tries to keep as much as possible monoisotopes, removing any possible C13 peaks and changes multichar...
Definition: aa.cpp:39
Axis
Definition: types.h:181