Skip to content

Commit e2b6b39

Browse files
rad-eng-59GitHub Enterprise
authored andcommitted
Cost Function of (rising) Edge Method in EL Pointing Estimation (roll offset) (#856)
- Add new C++ classes "RootFind1dNerwton" and "RootFind1dSecant" under "isce3/math" for finding the root of univariate function (1-D). The both are derived classes from a common base class "RootFind1dBase" sitting under "isce3/math/detail". - Add a single C++ test suite "root_find1d" for all root finding classes. - Add a C++ module "edge_method_cost_func" under "isce3/antenna" containing overloaded cost function as the core of rising "edge method" used for EL pointing estimation (roll angle offset). - Add a C++ test suite for "edge_method_cost_func". - Add a pybind11 module for "edge_method_cost_func". - Add a python test suite for "edge_method_cost_func" similar to its C++ counterpart. - Add a reference to "references.bib". - Remove extra blank line in module "geometryfunc.h" - Run clang-format - Run autopep8
1 parent cd18cc2 commit e2b6b39

22 files changed

+1381
-4
lines changed

cxx/isce3/Headers.cmake

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
set(HEADERS
22
antenna/detail/WinChirpRgCompPow.h
33
antenna/detail/WinChirpRgCompPow.icc
4+
antenna/edge_method_cost_func.h
45
antenna/forward.h
56
antenna/ElPatternEst.h
67
antenna/geometryfunc.h
@@ -136,7 +137,10 @@ matchtemplate/ampcor/dom/SLC.h
136137
matchtemplate/ampcor/dom/SLC.icc
137138
math/Bessel.h
138139
math/ComplexMultiply.h
140+
math/detail/RootFind1dBase.h
139141
math/polyfunc.h
142+
math/RootFind1dNewton.h
143+
math/RootFind1dSecant.h
140144
math/Sinc.h
141145
math/Sinc.icc
142146
polsar/symmetrize.h

cxx/isce3/Sources.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
set(SRCS
2+
antenna/edge_method_cost_func.cpp
23
antenna/ElPatternEst.cpp
34
antenna/geometryfunc.cpp
45
core/Attitude.cpp
@@ -73,6 +74,8 @@ matchtemplate/ampcor/dom/Raster.cc
7374
matchtemplate/ampcor/dom/SLC.cc
7475
math/Bessel.cpp
7576
math/polyfunc.cpp
77+
math/RootFind1dNewton.cpp
78+
math/RootFind1dSecant.cpp
7679
polsar/symmetrize.cpp
7780
product/GeoGridParameters.cpp
7881
product/Product.cpp

cxx/isce3/antenna/detail/WinChirpRgCompPow.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace isce3 { namespace antenna { namespace detail {
1313

1414
// Aliases, typedef:
15-
// fuzzy naming convention by isce3::core::EMtarix module!!!
1615
using RowMatrixXcf = isce3::core::EMatrix2D<std::complex<float>>;
1716

1817
// Functions:
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#include <algorithm>
2+
#include <cmath>
3+
#include <vector>
4+
5+
#include <Eigen/Dense>
6+
7+
#include <isce3/antenna/edge_method_cost_func.h>
8+
#include <isce3/except/Error.h>
9+
#include <isce3/math/RootFind1dNewton.h>
10+
#include <isce3/math/polyfunc.h>
11+
12+
namespace isce3 { namespace antenna {
13+
14+
std::tuple<double, double, bool, int> rollAngleOffsetFromEdge(
15+
const poly1d_t& polyfit_echo, const poly1d_t& polyfit_ant,
16+
const isce3::core::Linspace<double>& look_ang,
17+
std::optional<poly1d_t> polyfit_weight)
18+
{
19+
// check the input arguments
20+
if (polyfit_echo.order != 3 || polyfit_ant.order != 3)
21+
throw isce3::except::InvalidArgument(ISCE_SRCINFO(),
22+
"Requires 3rd-order poly-fit object for both "
23+
"Echo and Antenna!");
24+
constexpr double a_tol {1e-5};
25+
if (std::abs(polyfit_echo.mean - polyfit_ant.mean) > a_tol ||
26+
std::abs(polyfit_echo.norm - polyfit_ant.norm) > a_tol)
27+
throw isce3::except::InvalidArgument(ISCE_SRCINFO(),
28+
"Requires same (mean, std) for Echo and Antenna Poly1d obj!");
29+
30+
if (!(polyfit_echo.norm > 0.0))
31+
throw isce3::except::InvalidArgument(ISCE_SRCINFO(),
32+
"Requires positive std of Echo and Antenna Poly1d obj!");
33+
34+
if (polyfit_weight) {
35+
if (polyfit_weight->order < 0)
36+
throw isce3::except::InvalidArgument(ISCE_SRCINFO(),
37+
"The order of polyfit for weights must be "
38+
"at least 0 (constant weights)!");
39+
if (!(polyfit_weight->norm > 0.0))
40+
throw isce3::except::InvalidArgument(ISCE_SRCINFO(),
41+
"Requires positive std of weight Poly1d obj!");
42+
}
43+
44+
// create a copy polyfit objects "echo" and "ant" with zero mean and unit
45+
// std
46+
auto pf_echo_cp = polyfit_echo;
47+
pf_echo_cp.mean = 0.0;
48+
pf_echo_cp.norm = 1.0;
49+
auto pf_ant_cp = polyfit_ant;
50+
pf_ant_cp.mean = 0.0;
51+
pf_ant_cp.norm = 1.0;
52+
53+
// declare and initialize a look angle vector
54+
Eigen::ArrayXd lka_vec(look_ang.size());
55+
for (int idx = 0; idx < look_ang.size(); ++idx)
56+
lka_vec(idx) = look_ang[idx];
57+
58+
// create a weighting vector from look vector and weighting Poly1d
59+
Eigen::ArrayXd wgt_vec;
60+
if (polyfit_weight) {
61+
Eigen::Map<Eigen::ArrayXd> wgt_coef(
62+
polyfit_weight->coeffs.data(), polyfit_weight->coeffs.size());
63+
wgt_vec = isce3::math::polyval(
64+
wgt_coef, lka_vec, polyfit_weight->mean, polyfit_weight->norm);
65+
// normalize power in dB
66+
wgt_vec -= wgt_vec.maxCoeff();
67+
// convert from dB to linear power scale
68+
wgt_vec = Eigen::pow(10, 0.1 * wgt_vec);
69+
}
70+
// centralized and scaled the look vector based on mean/std of the echo
71+
// Poly1d to be used for both antenna and echo in the cost function.
72+
lka_vec -= polyfit_echo.mean;
73+
const auto std_inv = 1.0 / polyfit_echo.norm;
74+
lka_vec *= std_inv;
75+
76+
// form some derivatives used in the cost function
77+
auto pf_echo_der = pf_echo_cp.derivative();
78+
auto pf_ant_der = pf_ant_cp.derivative();
79+
auto pf_ant_der2 = pf_ant_der.derivative();
80+
// create a memmap of the coeff for the first and second derivatives
81+
Eigen::Map<Eigen::ArrayXd> coef_ant_der(
82+
pf_ant_der.coeffs.data(), pf_ant_der.coeffs.size());
83+
Eigen::Map<Eigen::ArrayXd> coef_ant_der2(
84+
pf_ant_der2.coeffs.data(), pf_ant_der2.coeffs.size());
85+
Eigen::Map<Eigen::ArrayXd> coef_echo_der(
86+
pf_echo_der.coeffs.data(), pf_echo_der.coeffs.size());
87+
// form some arrays over scaled look angles for diff of first derivatives
88+
// and for second derivative
89+
auto ant_echo_der_dif_vec =
90+
isce3::math::polyval(coef_ant_der - coef_echo_der, lka_vec);
91+
auto ant_der2_vec = isce3::math::polyval(coef_ant_der2, lka_vec);
92+
93+
// build cost function in the form of Poly1d object (3th order polynimal!)
94+
auto cf_pf = isce3::core::Poly1d(3, 0.0, 1.0);
95+
// fill up the coeff for the derivative of the WMSE cost function:
96+
// cost(ofs) = pf_wgt*(pf_echo_der(el) - pf_ant_der(el + ofs))**2
97+
// See section 1.1 of the cited reference.
98+
if (polyfit_weight) {
99+
auto tmp1 = wgt_vec * ant_echo_der_dif_vec;
100+
auto tmp2 = wgt_vec * ant_der2_vec;
101+
cf_pf.coeffs[0] = (tmp1 * ant_der2_vec).sum();
102+
cf_pf.coeffs[1] = (tmp2 * ant_der2_vec).sum() +
103+
6 * pf_ant_cp.coeffs[3] * tmp1.sum();
104+
cf_pf.coeffs[2] = 9 * pf_ant_cp.coeffs[3] * tmp2.sum();
105+
cf_pf.coeffs[3] =
106+
18 * pf_ant_cp.coeffs[3] * pf_ant_cp.coeffs[3] * wgt_vec.sum();
107+
} else // no weighting
108+
{
109+
cf_pf.coeffs[0] = (ant_echo_der_dif_vec * ant_der2_vec).sum();
110+
cf_pf.coeffs[1] = ant_der2_vec.square().sum() +
111+
6 * pf_ant_cp.coeffs[3] * ant_echo_der_dif_vec.sum();
112+
cf_pf.coeffs[2] = 9 * pf_ant_cp.coeffs[3] * ant_der2_vec.sum();
113+
cf_pf.coeffs[3] = 18 * pf_ant_cp.coeffs[3] * pf_ant_cp.coeffs[3] *
114+
look_ang.size();
115+
}
116+
// form Root finding object
117+
auto rf_obj =
118+
isce3::math::RootFind1dNewton(1e-4, 20, look_ang.spacing() / 10.);
119+
// solve for the root/roll offset via Newton
120+
auto [roll, f_val, flag, n_iter] = rf_obj.root(cf_pf);
121+
// scale back the roll angle by std of original poly1d object
122+
roll *= polyfit_echo.norm;
123+
124+
return {roll, f_val, flag, n_iter};
125+
}
126+
127+
std::tuple<double, double, bool, int> rollAngleOffsetFromEdge(
128+
const poly1d_t& polyfit_echo, const poly1d_t& polyfit_ant,
129+
double look_ang_near, double look_ang_far, double look_ang_prec,
130+
std::optional<poly1d_t> polyfit_weight)
131+
{
132+
if (!(look_ang_near > 0.0 && look_ang_far > 0.0 && look_ang_prec > 0.0))
133+
throw isce3::except::InvalidArgument(ISCE_SRCINFO(),
134+
"All look angles values must be positive numbers!");
135+
if (look_ang_near >= (look_ang_far - look_ang_prec))
136+
throw isce3::except::InvalidArgument(ISCE_SRCINFO(),
137+
"Near-range look angle shall be smaller than "
138+
"far one by at least one prec!");
139+
140+
const auto ang_size =
141+
static_cast<int>((look_ang_far - look_ang_near) / look_ang_prec) +
142+
1;
143+
auto look_ang = isce3::core::Linspace<double>::from_interval(
144+
look_ang_near, look_ang_far, ang_size);
145+
146+
return rollAngleOffsetFromEdge(
147+
polyfit_echo, polyfit_ant, look_ang, polyfit_weight);
148+
}
149+
150+
}} // namespace isce3::antenna
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/** @file edge_method_cost_func.h
2+
* Cost functions used in EL antenna pointing estimation via edge method
3+
*/
4+
#pragma once
5+
6+
#include <optional>
7+
#include <tuple>
8+
9+
#include <isce3/core/Linspace.h>
10+
#include <isce3/core/Poly1d.h>
11+
12+
/** @namespace isce3::antenna */
13+
namespace isce3 { namespace antenna {
14+
15+
// Aliases
16+
using poly1d_t = isce3::core::Poly1d;
17+
18+
/**
19+
* Estimate roll angle offset via edge method from poly-fitted
20+
* power patterns obtained from echo raw data and antenna pattern.
21+
* The cost function is solved via Newton method and final solution
22+
* is the weighted average of individual solution within look
23+
* (off-nadir) angles [near, far] with desired angle precision all
24+
* defined by isce3 Linspace.
25+
* See equations for cost function in section 1.1 of the reference
26+
* @cite EdgeMethodElPointDoc
27+
* The only difference is that the look angles are in (rad) rather than in
28+
* (deg). Note that the respective magnitudes for both echo and antenna can be
29+
* either 2-way or 1-way power patterns.
30+
* @param[in] polyfit_echo isce3 Poly1d object for polyfitted magnitude
31+
* of either range compressed (preferred) or raw echo data.
32+
* It must be third-order polynomial of relative magnitude/power in (dB)
33+
* as a function of look angle in (rad)
34+
* @param[in] polyfit_ant isce3 Poly1d object for antenna EL power pattern.
35+
* It must be third-order polynomial of relative magnitude/power in (dB)
36+
* as a function of look angle in (rad).
37+
* It must have the same mean and std as that of "polyfit_echo"!
38+
* @param[in] look_ang isce3 Linspace object to cover desired range of
39+
* look angles (rad) with desired precision/spacing.
40+
* @param[in] polyfit_weight (optional) isce3 Poly1d object for weightings used
41+
* in final weighted averaged of individual solutions over desired look angle
42+
* coverage. It shall represent relative magnitude/power in (dB) as a function
43+
* of look angle in (rad).
44+
* The order of the polynomial must be at least 0 (constant weights).
45+
* @return roll angle offset (rad)
46+
* Note that the roll offset shall be added to EL angles in antenna frame
47+
* to align EL power pattern from antenna to the one extracted from echo given
48+
* the cost function optimized for offset applied to polyfitted antenna data.
49+
* @return max cost function value among all iterations
50+
* @return overall convergence flag (true or false)
51+
* @return max number of iterations among all iterations
52+
* @exception InvalidArgument
53+
*/
54+
std::tuple<double, double, bool, int> rollAngleOffsetFromEdge(
55+
const poly1d_t& polyfit_echo, const poly1d_t& polyfit_ant,
56+
const isce3::core::Linspace<double>& look_ang,
57+
std::optional<poly1d_t> polyfit_weight = {});
58+
59+
/**
60+
* Estimate roll angle offset via edge method from poly-fitted
61+
* power patterns obtained from echo raw data and antenna pattern.
62+
* The cost function is solved via Newton method and final solution
63+
* is the weighted average of individual solution within look
64+
* (off-nadir) angles [near, far] with desired angle precision "look_ang_prec".
65+
* See equations for cost function in section 1.1 of the reference
66+
* @cite EdgeMethodElPointDoc
67+
* The only difference is that the look angles are in (rad) rather than in
68+
* (deg). Note that the respective magnitudes for both echo and antenna can be
69+
* either 2-way or 1-way power patterns.
70+
* @param[in] polyfit_echo isce3 Poly1d object for polyfitted magnitude
71+
* of either range compressed (preferred) or raw echo data.
72+
* It must be third-order polynomial of relative magnitude/power in (dB)
73+
* as a function of look angle in (rad)
74+
* @param[in] polyfit_ant isce3 Poly1d object for antenna EL power pattern.
75+
* It must be third-order polynomial of relative magnitude/power in (dB)
76+
* as a function of look angle in (rad).
77+
* It must have the same mean and std as that of "polyfit_echo"!
78+
* @param[in] look_ang_near look angle for near range in (rad)
79+
* @param[in] look_ang_far look angle for far range in (rad)
80+
* @param[in] look_ang_prec look angle precision/resolution in (rad)
81+
* @param[in] polyfit_weight (optional) isce3 Poly1d object for weightings used
82+
* in final weighted averaged of individual solutions over desired look angle
83+
* coverage. It shall represent relative magnitude/power in (dB) as a function
84+
* of look angle in (rad).
85+
* The order of the polynomial must be at least 0 (constant weights).
86+
* @return roll angle offset (rad)
87+
* Note that the roll offset shall be added to EL angles in antenna frame
88+
* to align EL power pattern from antenna to the one extracted from echo given
89+
* the cost function optimized for offset applied to polyfitted antenna data.
90+
* @return max cost function value among all iterations
91+
* @return overall convergence flag (true or false)
92+
* @return max number of iterations among all iterations
93+
* @exception InvalidArgument
94+
*/
95+
std::tuple<double, double, bool, int> rollAngleOffsetFromEdge(
96+
const poly1d_t& polyfit_echo, const poly1d_t& polyfit_ant,
97+
double look_ang_near, double look_ang_far, double look_ang_prec,
98+
std::optional<poly1d_t> polyfit_weight = {});
99+
100+
}} // namespace isce3::antenna

cxx/isce3/antenna/geometryfunc.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ namespace isce3 { namespace antenna {
4848
* @exception InvalidArgument, RuntimeError
4949
* @cite ReeTechDesDoc
5050
*/
51-
5251
std::tuple<double, double, bool> ant2rgdop(double el_theta, double az_phi,
5352
const isce3::core::Vec3& pos_ecef, const isce3::core::Vec3& vel_ecef,
5453
const isce3::core::Quaternion& quat, double wavelength,
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#include "RootFind1dNewton.h"
2+
3+
#include <cmath>
4+
5+
#include <isce3/except/Error.h>
6+
7+
namespace isce3 { namespace math {
8+
9+
// regular methods
10+
typename RootFind1dNewton::tuple4_t RootFind1dNewton::root(
11+
const poly1d_t& f, double x0) const
12+
{
13+
// check the order/degree of the polynomial
14+
if (f.order <= 1)
15+
throw isce3::except::InvalidArgument(
16+
ISCE_SRCINFO(), "Polynomial degree must be at least 1!");
17+
return root(poly2func(f), poly2func(f.derivative()), x0);
18+
}
19+
20+
typename RootFind1dNewton::tuple4_t RootFind1dNewton::root(
21+
const func2_t& f, double x0) const
22+
{
23+
// if optional x_tol exists run stricter Newton approach
24+
if (x_tol)
25+
return _newton_strict(f, x0);
26+
// otherwise run regular Newton
27+
return _newton(f, x0);
28+
}
29+
30+
typename RootFind1dNewton::tuple4_t RootFind1dNewton::root(
31+
const func_t& f, const func_t& f_der, double x0) const
32+
{
33+
// create a single function from two functions (f, f_der)
34+
func2_t f2 = [=](double x) { return std::make_tuple(f(x), f_der(x)); };
35+
// call the other overloaded root method
36+
return root(f2, x0);
37+
}
38+
39+
// helper functions
40+
typename RootFind1dNewton::tuple4_t RootFind1dNewton::_newton(
41+
const func2_t& f, double x0) const
42+
{
43+
// setting initial values and iterations
44+
auto x1 = x0;
45+
int n_itr {0};
46+
auto [f_val, fp_eval] = f(x1);
47+
do {
48+
if (std::abs(fp_eval) > 0.0)
49+
x1 -= f_val / fp_eval;
50+
std::tie(f_val, fp_eval) = f(x1);
51+
++n_itr;
52+
} while (std::abs(f_val) > f_tol && n_itr < max_iter);
53+
bool flag {(std::abs(f_val) <= f_tol && n_itr <= max_iter)};
54+
return {x1, f_val, flag, n_itr};
55+
}
56+
57+
typename RootFind1dNewton::tuple4_t RootFind1dNewton::_newton_strict(
58+
const func2_t& f, double x0) const
59+
{
60+
int n_itr {0};
61+
auto x = x0;
62+
auto [f_val, fp_eval] = f(x);
63+
double dx;
64+
do {
65+
dx = (std::abs(fp_eval) > 0.0) ? -f_val / fp_eval : 0.0;
66+
x += dx;
67+
std::tie(f_val, fp_eval) = f(x);
68+
++n_itr;
69+
} while ((std::abs(f_val) > f_tol || dx > *x_tol) && n_itr < max_iter);
70+
bool flag {(std::abs(f_val) <= f_tol && dx <= *x_tol && n_itr <= max_iter)};
71+
return {x, f_val, flag, n_itr};
72+
}
73+
74+
}} // namespace isce3::math

0 commit comments

Comments
 (0)