Skip to content

Commit

Permalink
Merge pull request #1268 from rolalaro/fixVpImageCircle
Browse files Browse the repository at this point in the history
Fix problems of roundings in the cvpImageCircle::omputeAngularCoverageInRoI
  • Loading branch information
fspindle authored Oct 31, 2023
2 parents fa84fee + e03dfe3 commit 6582d80
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 19 deletions.
6 changes: 4 additions & 2 deletions modules/core/include/visp3/core/vpImageCircle.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,19 +76,21 @@ class VISP_EXPORT vpImageCircle
* Compute the angular coverage, in terms of radians, that is contained in the Region of Interest (RoI).
* \sa computeArcLengthInRoI(), computeArcLengthInRoI(const vpRect &roi)
* \param[in] roi The rectangular RoI in which we want to know the number of pixels of the circle that are contained.
* \param[in] roundingTolerance The tolerance on the angle when the angle is close to a negative multiple of 2 * M_PIf.
* \return Returns angular coverage of a circle in a ROI as an angle value in radians.
* More precisely, it returns 2.f * M_PI for a circle that is fully visible in the RoI, or the sum of the angles
* of the arc(s) that is(are) visible in the RoI.
*/
float computeAngularCoverageInRoI(const vpRect &roi) const;
float computeAngularCoverageInRoI(const vpRect &roi, const float &roundingTolerance = 0.001f) const;

/*!
* Compute the arc length, in terms of number of pixels, that is contained in the Region of Interest (RoI).
* \sa computeAngularCoverageInRoI(), computeAngularCoverageInRoI(const vpRect &roi)
* \param[in] roi The rectangular RoI in which we want to know the number of pixels of the circle that are contained.
* \param[in] roundingTolerance The tolerance on the angle when the angle is close to 2.f * M_PIf .
* \return The number of pixels of the circle that are contained in the RoI.
*/
float computeArcLengthInRoI(const vpRect &roi) const;
float computeArcLengthInRoI(const vpRect &roi, const float &roundingTolerance = 0.001f) const;

/*!
* Get the center of the image (2D) circle
Expand Down
48 changes: 39 additions & 9 deletions modules/core/include/visp3/core/vpMath.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class VISP_EXPORT vpMath
if (theta1 > M_PIf) {
theta1 -= 2.0f * M_PIf;
}
else if (theta1 < -M_PIf) {
else if (theta1 <= -M_PIf) {
theta1 += 2.0f * M_PIf;
}
return theta1;
Expand All @@ -164,6 +164,36 @@ class VISP_EXPORT vpMath
return theta1;
}

/**
* \brief Gives the rest of \b value divided by \b modulo when
* the quotient can only be an integer.
*
* \param[in] value The value we want to know the rest in the "modulo" operation.
* \param[in] modulo The divider.
* \return float The rest as in a modulo operation.
*/
static float modulo(const float &value, const float &modulo)
{
float quotient = std::floor(value / modulo);
float rest = value - quotient * modulo;
return rest;
}

/**
* \brief Gives the rest of \b value divided by \b modulo when
* the quotient can only be an integer.
*
* \param[in] value The value we want to know the rest in the "modulo" operation.
* \param[in] modulo The divider.
* \return double The rest as in a modulo operation.
*/
static double modulo(const double &value, const double &modulo)
{
double quotient = std::floor(value / modulo);
double rest = value - quotient * modulo;
return rest;
}

/*!
Compute x square value.
\return Square value \f$ x^2 \f$.
Expand Down Expand Up @@ -193,9 +223,9 @@ class VISP_EXPORT vpMath
}
return (v < lower) ? lower : (upper < v) ? upper : v;
#endif
}
}

// round x to the nearest integer
// round x to the nearest integer
static inline int round(double x);

// return the sign of x (+-1)
Expand Down Expand Up @@ -330,14 +360,14 @@ class VISP_EXPORT vpMath
private:
static const double ang_min_sinc;
static const double ang_min_mc;
};
};

// Begining of the inline functions definition
// Begining of the inline functions definition

/*!
Computes and returns x!
\param x : parameter of factorial function.
*/
/*!
Computes and returns x!
\param x : parameter of factorial function.
*/
double vpMath::fact(unsigned int x)
{
if ((x == 1) || (x == 0))
Expand Down
37 changes: 29 additions & 8 deletions modules/core/src/image/vpImageCircle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ void computeIntersectionsLeftBorderOnly(const float &u_c, const float &umin_roi,
float theta_min = std::min(theta1, theta2);
float theta_max = std::max(theta1, theta2);
delta_theta = theta_max - theta_min;
if (u_c < umin_roi && std::abs(delta_theta - 2 * M_PIf) < 2.f * std::numeric_limits<float>::epsilon()) {
delta_theta = 0.f;
}
}

/*!
Expand All @@ -99,6 +102,9 @@ void computeIntersectionsRightBorderOnly(const float &u_c, const float &umax_roi
float theta_min = std::min(theta1, theta2);
float theta_max = std::max(theta1, theta2);
delta_theta = 2.f * M_PIf - (theta_max - theta_min);
if (u_c > umax_roi && std::abs(delta_theta - 2 * M_PIf) < 2.f * std::numeric_limits<float>::epsilon()) {
delta_theta = 0.f;
}
}

/*!
Expand Down Expand Up @@ -138,6 +144,9 @@ void computeIntersectionsTopBorderOnly(const float &v_c, const float &vmin_roi,
else {
delta_theta = theta_max - theta_min;
}
if (v_c < vmin_roi && std::abs(delta_theta - 2 * M_PIf) < 2.f * std::numeric_limits<float>::epsilon()) {
delta_theta = 0.f;
}
}

/*!
Expand Down Expand Up @@ -177,6 +186,9 @@ void computeIntersectionsBottomBorderOnly(const float &v_c, const float &vmax_ro
else {
delta_theta = 2.f * M_PIf - (theta_max - theta_min);
}
if (v_c > vmax_roi && std::abs(delta_theta - 2 * M_PIf) < 2.f * std::numeric_limits<float>::epsilon()) {
delta_theta = 0.f;
}
}

/*!
Expand Down Expand Up @@ -481,7 +493,7 @@ void computeIntersectionsTopLeftBottom(const float &u_c, const float &v_c, const
std::pair<float, float> crossing_theta_u_min, crossing_theta_u_max;
std::pair<float, float> crossing_theta_v_min, crossing_theta_v_max;
float crossing_u_top = vmin_roi; // We cross the u-axis of the top axis of the RoI at the minimum v-coordinate of the RoI
float crossing_v = vmin_roi; // We cross the v-axis of the RoI at the minimum u-coordinate of the RoI
float crossing_v = umin_roi; // We cross the v-axis of the RoI at the minimum u-coordinate of the RoI
computePerpendicularAxesIntersections(u_c, v_c, radius, crossing_u_top, crossing_v,
crossing_theta_u_min, crossing_theta_u_max,
crossing_theta_v_min, crossing_theta_v_max);
Expand Down Expand Up @@ -894,7 +906,7 @@ void computeIntersectionsAllAxes(const float &u_c, const float &v_c, const float
delta_theta += (theta_v_max_right - theta_u_max_bottom) + (theta_u_min_bottom - theta_v_max_left);
}

float vpImageCircle::computeAngularCoverageInRoI(const vpRect &roi) const
float vpImageCircle::computeAngularCoverageInRoI(const vpRect &roi, const float &roundingTolerance) const
{
float delta_theta = 0.f;
vpImagePoint center = m_center;
Expand Down Expand Up @@ -989,16 +1001,25 @@ float vpImageCircle::computeAngularCoverageInRoI(const vpRect &roi) const
std::cerr << "vmin_roi = " << vmin_roi << "\tvmax_roi = " << vmax_roi << std::endl << std::flush;
throw(vpException(vpException::fatalError, "This case should never happen. Please contact Inria to make fix the problem"));
}

if (delta_theta < 0 || delta_theta > 2.f * M_PIf) { // Needed since M_PIf is used
float rest = vpMath::modulo(delta_theta, 2.f * M_PIf);
if (rest < roundingTolerance && (delta_theta < -M_PIf || delta_theta > M_PIf)) {
// If the angle is a negative multiple of 2.f * M_PIf we consider it to be 2.f * M_PIf
delta_theta = 2.f * M_PIf;
}
else {
//Otherwise, it corresponds to delta_theta modulo 2.f * M_PIf
delta_theta = rest;
}
}

return delta_theta;
}

float vpImageCircle::computeArcLengthInRoI(const vpRect &roi) const
float vpImageCircle::computeArcLengthInRoI(const vpRect &roi, const float &roundingTolerance) const
{
float delta_theta = computeAngularCoverageInRoI(roi);
if (delta_theta < 0) { // Needed since M_PIf is used
delta_theta += 4 * M_PIf;
}

float delta_theta = computeAngularCoverageInRoI(roi, roundingTolerance);
return delta_theta * m_radius;
}

Expand Down
101 changes: 101 additions & 0 deletions modules/core/test/tools/geometry/testImageCircle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,31 @@ int main()
hasSucceeded &= isValueOK;
}

// Test with circle touching the left border, all the circle is hidden
{
// Formula: uc = OFFSET - RADIUS * cos(theta)
// theta := PI
float uc = OFFSET - RADIUS;
float vc = OFFSET - 100.f;
vpImageCircle circle(vpImagePoint(vc, uc), RADIUS);
float arcLengthCircle = circle.computeArcLengthInRoI(roi);
float theoreticalValue = 0.f;
bool isValueOK = equal(arcLengthCircle, theoreticalValue);
std::string statusTest;
if (isValueOK) {
statusTest = "SUCCESS";
}
else {
statusTest = "FAILED";
}
std::cout << "Test with circle touching the left border, all the circle is hidden." << std::endl;
std::cout << "\tarc length =" << arcLengthCircle << std::endl;
std::cout << "\ttheoretical length =" << theoreticalValue << std::endl;
std::cout << "\ttest status = " << statusTest << std::endl;

hasSucceeded &= isValueOK;
}

// Test with intersections with the right border, more than half a circle visible
{
// Formula: uc = OFFSET + WIDTH - RADIUS * cos(theta)
Expand Down Expand Up @@ -248,6 +273,31 @@ int main()
hasSucceeded &= isValueOK;
}

// Test with circle touching the right border, all the circle is hidden
{
// Formula: uc = OFFSET + WIDTH - RADIUS * cos(theta)
// theta := 0
float uc = OFFSET + WIDTH + RADIUS;
float vc = OFFSET + 100.f;
vpImageCircle circle(vpImagePoint(vc, uc), RADIUS);
float arcLengthCircle = circle.computeArcLengthInRoI(roi);
float theoreticalValue = 0.f;
bool isValueOK = equal(arcLengthCircle, theoreticalValue);
std::string statusTest;
if (isValueOK) {
statusTest = "SUCCESS";
}
else {
statusTest = "FAILED";
}
std::cout << "Test with circle touching the right border, all the circle is hidden." << std::endl;
std::cout << "\tarc length =" << arcLengthCircle << std::endl;
std::cout << "\ttheoretical length =" << theoreticalValue << std::endl;
std::cout << "\ttest status = " << statusTest << std::endl;

hasSucceeded &= isValueOK;
}

// Test with intersections with the top border, more than half a circle visible
{
// v = vc - r sin(theta)
Expand Down Expand Up @@ -326,6 +376,32 @@ int main()
hasSucceeded &= isValueOK;
}

// Test with circle touching the top border, all the circle is hidden
{
// v = vc - r sin(theta)
// Formula: vc = OFFSET + RADIUS * sin(theta)
float theta = -M_PI_2f;
float uc = OFFSET + 100.f;
float vc = OFFSET + RADIUS * sin(theta);
vpImageCircle circle(vpImagePoint(vc, uc), RADIUS);
float arcLengthCircle = circle.computeArcLengthInRoI(roi);
float theoreticalValue = 0.f;
bool isValueOK = equal(arcLengthCircle, theoreticalValue);
std::string statusTest;
if (isValueOK) {
statusTest = "SUCCESS";
}
else {
statusTest = "FAILED";
}
std::cout << "Test with circle touching the top border, all the circle is hidden." << std::endl;
std::cout << "\tarc length =" << arcLengthCircle << std::endl;
std::cout << "\ttheoretical length =" << theoreticalValue << std::endl;
std::cout << "\ttest status = " << statusTest << std::endl;

hasSucceeded &= isValueOK;
}

// Test with intersections with the bottom border, more than half a circle visible
{
// v = vc - r sin(theta)
Expand Down Expand Up @@ -402,6 +478,30 @@ int main()
hasSucceeded &= isValueOK;
}

// Test with circle touching the bottom border, all the circle is hidden
{
// Formula: vc = OFFSET + HEIGHT + RADIUS * sin(theta)
float uc = OFFSET + 100.f;
float vc = OFFSET + HEIGHT + RADIUS;
vpImageCircle circle(vpImagePoint(vc, uc), RADIUS);
float arcLengthCircle = circle.computeArcLengthInRoI(roi);
float theoreticalValue = 0.f;
bool isValueOK = equal(arcLengthCircle, theoreticalValue);
std::string statusTest;
if (isValueOK) {
statusTest = "SUCCESS";
}
else {
statusTest = "FAILED";
}
std::cout << "Test with circle touching the bottom border, all the circle is hidden." << std::endl;
std::cout << "\tarc length =" << arcLengthCircle << std::endl;
std::cout << "\ttheoretical length =" << theoreticalValue << std::endl;
std::cout << "\ttest status = " << statusTest << std::endl;

hasSucceeded &= isValueOK;
}

// Test with intersections with the top and the left border, crossing each axis once in the RoI
{
// Formula: u_cross_top_max = uc + r cos (theta_u_top_max) >= umin ; vmin = vc - r sin(theta_u_top_max)
Expand Down Expand Up @@ -2000,6 +2100,7 @@ int main()
std::cout << "testImageCircle overall result: SUCCESS" << std::endl;
return EXIT_SUCCESS;
}

std::cout << "testImageCircle overall result: FAILED" << std::endl;
return EXIT_FAILURE;
}

0 comments on commit 6582d80

Please sign in to comment.