1 /*
2     SPDX-FileCopyrightText: 2019 Patrick Molenaar <pr_molenaar@hotmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "houghline.h"
8 
9 #include <qmath.h>
10 #include <QVector>
11 #include <fits_debug.h>
12 
13 /**
14  * Initialises the hough line
15  */
HoughLine(double theta,double r,int width,int height,int score)16 HoughLine::HoughLine(double theta, double r, int width, int height, int score)
17     : QLineF()
18 {
19     this->theta = theta;
20     this->r = r;
21     this->score = score;
22 
23     setP1(RotatePoint(0, r, theta, width, height));
24     setP2(RotatePoint(width - 1, r, theta, width, height));
25 }
26 
RotatePoint(int x1,double r,double theta,int width,int height)27 QPointF HoughLine::RotatePoint(int x1, double r, double theta, int width, int height)
28 {
29     int hx, hy;
30 
31     hx = qFloor((width + 1) / 2.0);
32     hy = qFloor((height + 1) / 2.0);
33 
34     double sinAngle = qSin(-theta);
35     double cosAngle = qCos(-theta);
36 
37     // translate point back to origin:
38     double x2 = x1 - hx;
39     double y2 = r - hy;
40 
41     // rotate point
42     double xnew = x2 * cosAngle - y2 * sinAngle;
43     double ynew = x2 * sinAngle + y2 * cosAngle;
44 
45     // translate point back:
46     x2 = xnew + hx;
47     y2 = ynew + hy;
48 
49     return QPointF(x2, y2);
50 }
51 
getScore() const52 int HoughLine::getScore() const
53 {
54     return score;
55 }
56 
getR() const57 double HoughLine::getR() const
58 {
59     return r;
60 }
61 
getTheta() const62 double HoughLine::getTheta() const
63 {
64     return theta;
65 }
66 
setTheta(const double theta)67 void HoughLine::setTheta(const double theta)
68 {
69     this->theta = theta;
70 }
71 
compareByScore(const HoughLine * line1,const HoughLine * line2)72 bool HoughLine::compareByScore(const HoughLine *line1,const HoughLine *line2)
73 {
74     return (line1->getScore() < line2->getScore());
75 }
76 
compareByTheta(const HoughLine * line1,const HoughLine * line2)77 bool HoughLine::compareByTheta(const HoughLine *line1,const HoughLine *line2)
78 {
79     return (line1->getTheta() < line2->getTheta());
80 }
81 
printHoughLine()82 void HoughLine::printHoughLine()
83 {
84     qCDebug(KSTARS_FITS) << "Houghline: [score: " << score << ", r: " << r << ", theta: " << theta << " [rad]="
85                          << (theta * 180.0 / M_PI) << " [deg], p1: " << p1().x() << ", " << p1().y() << ", p2: "
86                          << p2().x() << ", " << p2().y() << "]";
87 }
88 
89 /**
90  * Sources for intersection and distance calculations came from
91  *     http://paulbourke.net/geometry/pointlineplane/
92  * Also check https://doc.qt.io/archives/qt-4.8/qlinef.html for more line methods
93  */
Intersect(const HoughLine & other_line,QPointF & intersection)94 HoughLine::IntersectResult HoughLine::Intersect(const HoughLine& other_line, QPointF& intersection)
95 {
96     double denom = ((other_line.p2().y() - other_line.p1().y()) * (p2().x() - p1().x())) -
97             ((other_line.p2().x() - other_line.p1().x()) * (p2().y() - p1().y()));
98 
99     double nume_a = ((other_line.p2().x() - other_line.p1().x()) * (p1().y() - other_line.p1().y())) -
100             ((other_line.p2().y() - other_line.p1().y()) * (p1().x() - other_line.p1().x()));
101 
102     double nume_b = ((p2().x() - p1().x()) * (p1().y() - other_line.p1().y())) -
103             ((p2().y() - p1().y()) * (p1().x() - other_line.p1().x()));
104 
105     if (denom == 0.0f)
106     {
107         if(nume_a == 0.0f && nume_b == 0.0f)
108         {
109             return COINCIDENT;
110         }
111         return PARALLEL;
112     }
113 
114     double ua = nume_a / denom;
115     double ub = nume_b / denom;
116 
117     if(ua >= 0.0f && ua <= 1.0f && ub >= 0.0f && ub <= 1.0f)
118     {
119         // Get the intersection point.
120         intersection.setX(qreal(p1().x() + ua * (p2().x() - p1().x())));
121         intersection.setY(qreal(p1().y() + ua * (p2().y() - p1().y())));
122         return INTERESECTING;
123     }
124 
125     return NOT_INTERESECTING;
126 }
127 
Magnitude(const QPointF & point1,const QPointF & point2)128 double HoughLine::Magnitude(const QPointF& point1, const QPointF& point2)
129 {
130     QPointF vector = point2 - point1;
131     return qSqrt(vector.x() * vector.x() + vector.y() * vector.y());
132 }
133 
DistancePointLine(const QPointF & point,QPointF & intersection,double & distance)134 bool HoughLine::DistancePointLine(const QPointF& point, QPointF& intersection, double& distance)
135 {
136     double lineMag = length();
137 
138     double U = qreal((((point.x() - p1().x()) * (p2().x() - p1().x())) +
139             ((point.y() - p1().y()) * (p2().y() - p1().y()))) /
140             (lineMag * lineMag));
141 
142     if (U < 0.0 || U > 1.0) {
143         return false; // closest point does not fall within the line segment
144     }
145 
146     intersection.setX(p1().x() + U * (p2().x() - p1().x()));
147     intersection.setY(p1().y() + U * (p2().y() - p1().y()));
148 
149     distance = Magnitude(point, intersection);
150 
151     return true;
152 }
153 
Offset(const int offsetX,const int offsetY)154 void HoughLine::Offset(const int offsetX, const int offsetY)
155 {
156     setP1(QPointF(p1().x() + offsetX, p1().y() + offsetY));
157     setP2(QPointF(p2().x() + offsetX, p2().y() + offsetY));
158 }
159 
getSortedTopThreeLines(QVector<HoughLine * > & houghLines,QVector<HoughLine * > & top3Lines)160 void HoughLine::getSortedTopThreeLines(QVector<HoughLine*> &houghLines, QVector<HoughLine*> &top3Lines)
161 {
162     // Sort houghLines by score (highest scores are clearest lines)
163     // For use of sort compare methods see: https://www.off-soft.net/en/develop/qt/qtb1.html
164     qSort(houghLines.begin(), houghLines.end(), HoughLine::compareByScore);
165 
166     // Get top three lines (these should represent the three lines matching the bahtinov mask lines
167     top3Lines = houghLines.mid(0, 3);
168     // Verify the angle of these lines with regard to the bahtinov mask angle, correct the angle if necessary
169     HoughLine* lineR = top3Lines[0];
170     HoughLine* lineG = top3Lines[1];
171     HoughLine* lineB = top3Lines[2];
172     double thetaR = lineR->getTheta();
173     double thetaG = lineG->getTheta();
174     double thetaB = lineB->getTheta();
175     // Calculate angle between each line
176     double bahtinovMaskAngle = qDegreesToRadians(20.0 + 5.0); // bahtinov mask angle plus 5 degree margin
177     double dGR = thetaR - thetaG;
178     double dBG = thetaB - thetaG;
179     double dBR = thetaB - thetaR;
180     if (dGR > bahtinovMaskAngle && dBR > bahtinovMaskAngle) {
181         // lineR has theta that is 180 degrees rotated
182         thetaR -= M_PI;
183         // update theta
184         lineR->setTheta(thetaR);
185     }
186     if (dBR > bahtinovMaskAngle && dBG > bahtinovMaskAngle) {
187         // lineB has theta that is 180 degrees rotated
188         thetaB -= M_PI;
189         // update theta
190         lineB->setTheta(thetaB);
191     }
192     if (dGR > bahtinovMaskAngle && dBG > bahtinovMaskAngle) {
193         // lineG has theta that is 180 degrees rotated
194         thetaG -= M_PI;
195         // update theta
196         lineG->setTheta(thetaG);
197     }
198     // Now sort top3lines array according to calculated new angles
199     qSort(top3Lines.begin(),top3Lines.end(), HoughLine::compareByTheta);
200 }
201