1 // This is core/vpgl/algo/vpgl_backproject_dem.cxx
2 #include <limits>
3 #include <algorithm>
4 #include "vpgl_backproject.h"
5 #include "vpgl_backproject_dem.h"
6 #include <vpgl/file_formats/vpgl_geo_camera.h>
7 #include "vgl/vgl_point_2d.h"
8 #include "vgl/vgl_point_3d.h"
9 #include "vgl/vgl_plane_3d.h"
10 #include "vgl/vgl_ray_3d.h"
11 #include <vpgl/algo/vpgl_ray.h>
12 #include "vil/vil_image_resource.h"
13 #include "vil/vil_math.h"
14 #include "vnl/vnl_cost_function.h"
15 #include <vnl/algo/vnl_brent_minimizer.h>
16 class dem_bproj_cost_function : public vnl_cost_function
17 {
18 public:
dem_bproj_cost_function(vil_image_view<float> const & dem_view,vpgl_geo_camera * geo_cam,vgl_ray_3d<double> const & ray,bool verbose=false)19   dem_bproj_cost_function(vil_image_view<float> const & dem_view,
20                           vpgl_geo_camera * geo_cam,
21                           vgl_ray_3d<double> const & ray,
22                           bool verbose = false)
23     : vnl_cost_function(1)
24     , ray_(ray)
25     , geo_cam_(geo_cam)
26     , dview_(&dem_view)
27     , verbose_(verbose)
28   {}
29   //: x is the parameter that runs along the ray, with x=0 at the ray origin
30   double
f(vnl_vector<double> const & x)31   f(vnl_vector<double> const & x) override
32   {
33     // get the lon and lat values for a given parameter value
34     vgl_point_3d<double> org = ray_.origin(), rayp;
35     vgl_vector_3d<double> dir = ray_.direction();
36     rayp = org + x[0] * dir;
37     double lon = rayp.x(), lat = rayp.y(), elev = rayp.z();
38     double ud, vd;
39     // determine the dem pixel coordinates and check bounds
40     geo_cam_->project(lon, lat, elev, ud, vd);
41     int u = static_cast<int>(ud + 0.5), v = static_cast<int>(vd + 0.5);
42     int ni = dview_->ni(), nj = dview_->nj();
43     if (u < 0 || u >= ni || v < 0 || v >= nj)
44     {
45       if (verbose_)
46         std::cout << "warning: dem backprojection cost function - outside DEM bounds" << std::endl;
47       return std::numeric_limits<double>::max();
48     }
49     // within bounds so get squared distance between dem z and ray z
50     double z = (*dview_)(u, v);
51     return (elev - z) * (elev - z);
52   }
53 
54 private:
55   vgl_ray_3d<double> ray_;
56   vpgl_geo_camera * geo_cam_;
57   const vil_image_view<float> * dview_;
58   bool verbose_;
59 };
vpgl_backproject_dem(vil_image_resource_sptr const & dem,double zmin,double zmax)60 vpgl_backproject_dem::vpgl_backproject_dem(vil_image_resource_sptr const & dem, double zmin, double zmax)
61   : verbose_(false)
62   , min_samples_(5000.0)
63   , tail_fract_(0.025)
64   , dem_(dem)
65 {
66   // construct a geo_camera for the dem (an orthographic view looking straight down)
67   if (!vpgl_geo_camera::init_geo_camera(dem_, geo_cam_))
68   {
69     if (verbose_)
70       std::cout << "WARNING! - units unknown in vpgl_backproject_dem - assume degrees meters" << std::endl;
71   }
72   if (!geo_cam_)
73     return;
74   // get the image of elevations
75   dem_view_ = dem_->get_view();
76   unsigned ni = dem_view_.ni(), nj = dem_view_.nj();
77 
78   // get the center of the dem
79   unsigned nhi = ni / 2, nhj = nj / 2;
80   double lon, lat;
81   geo_cam_->img_to_global(nhi, nhj, lon, lat);
82   double elev = dem_view_(nhi, nhj);
83   geo_center_.set(lon, lat, elev);
84 
85   // get the corners of the dem
86   geo_cam_->img_to_global(0, 0, lon, lat);
87   elev = dem_view_(0, 0);
88   dem_corners_.emplace_back(lon, lat, elev);
89 
90   geo_cam_->img_to_global(ni - 1, 0, lon, lat);
91   elev = dem_view_(ni - 1, 0);
92   dem_corners_.emplace_back(lon, lat, elev);
93 
94   geo_cam_->img_to_global(ni - 1, nj - 1, lon, lat);
95   elev = dem_view_(ni - 1, nj - 1);
96   dem_corners_.emplace_back(lon, lat, elev);
97 
98   geo_cam_->img_to_global(0, nj - 1, lon, lat);
99   elev = dem_view_(0, nj - 1);
100   dem_corners_.emplace_back(lon, lat, elev);
101 
102   // check for appropriate zmin/zmax inputs
103   if (zmax > zmin)
104   {
105     z_min_ = zmin;
106     z_max_ = zmax;
107   }
108   else
109   {
110     if (verbose_)
111       std::cout << "Calculating Z-range from DEM..." << std::endl;
112     // get the bounds on elevation by sampling according to a fraction of the dem area
113     // compute the pixel interval (stride) for sampling the fraction
114     double area = ni * nj;
115     double stride_area = area / min_samples_;
116     auto stride_interval = static_cast<unsigned>(std::sqrt(stride_area));
117 
118     // sample elevations
119     std::vector<double> z_samples;
120     float zmin_calc = std::numeric_limits<float>::max(), zmax_calc = -zmin_calc;
121     for (unsigned j = 0; j < nj; j += stride_interval)
122       for (unsigned i = 0; i < ni; i += stride_interval)
123       {
124         float z = dem_view_(i, j);
125         if (z <= 0.0)
126           continue;
127         z_samples.push_back(z);
128       }
129     // sort the samples to remove tails due to DEM errors
130     std::sort(z_samples.begin(), z_samples.end());
131     // remove the tails and compute min max elevations
132     auto ns = static_cast<double>(z_samples.size());
133     auto band_size = static_cast<unsigned>(ns * tail_fract_);
134     for (unsigned k = band_size; k < (ns - band_size); ++k)
135     {
136       double z = z_samples[k];
137       if (z < zmin_calc)
138         zmin_calc = static_cast<float>(z);
139       if (z > zmax_calc)
140         zmax_calc = z;
141     }
142     // the final elevation bounds
143     z_min_ = zmin_calc;
144     z_max_ = zmax_calc;
145   }
146 
147   if (verbose_)
148     std::cout << "[ZMIN,ZMAX]=[" << z_min_ << "," << z_max_ << "]" << std::endl;
149 }
~vpgl_backproject_dem()150 vpgl_backproject_dem::~vpgl_backproject_dem()
151 {
152   if (geo_cam_)
153     delete geo_cam_;
154   geo_cam_ = nullptr;
155 }
156 // the function to backproject onto the dem using vgl objects
157 bool
bproj_dem(const vpgl_camera<double> * cam,vgl_point_2d<double> const & image_point,double max_z,double min_z,vgl_point_3d<double> const & initial_guess,vgl_point_3d<double> & world_point,double error_tol)158 vpgl_backproject_dem::bproj_dem(const vpgl_camera<double> * cam,
159                                 vgl_point_2d<double> const & image_point,
160                                 double max_z,
161                                 double min_z,
162                                 vgl_point_3d<double> const & initial_guess,
163                                 vgl_point_3d<double> & world_point,
164                                 double error_tol)
165 {
166   if (verbose_)
167     std::cout << "vpgl_backproj_dem " << image_point << " max_z " << max_z << " min_z " << min_z << " init_guess "
168               << initial_guess << " error tol " << error_tol << std::endl;
169   // compute the ray corresponding to the image point
170   double dz = (max_z - min_z);
171   vgl_point_2d<double> initial_xy(initial_guess.x(), initial_guess.y());
172   vgl_ray_3d<double> ray;
173   if (!vpgl_ray::ray(cam, image_point, initial_xy, max_z, dz, ray))
174   {
175     if (verbose_)
176       std::cout << " compute camera ray failed - Fatal!" << std::endl;
177     return false;
178   }
179   vgl_point_3d<double> origin = ray.origin();
180   // find min parameter on ray
181   vgl_vector_3d<double> dir = ray.direction();
182   if (std::fabs(dir.z()) < 0.001)
183   {
184     if (verbose_)
185       std::cout << "Ray parallel to XY plane - Fatal!" << std::endl;
186     return false;
187   }
188   // inital guess at ray parameter
189   double t = (initial_guess.z() - max_z) / dir.z();
190 
191   // check if guess is inside DEM - could be too conservative for very oblique cameras
192   vgl_point_3d<double> guess_point = origin + t * dir;
193   double lon = guess_point.x(), lat = guess_point.y(), elev = guess_point.z();
194   double ud, vd;
195   // Note that elevation has no effect in the project function - just required to meet the interface of an abstract
196   // camera
197   geo_cam_->project(lon, lat, elev, ud, vd);
198   int u = static_cast<int>(ud + 0.5), v = static_cast<int>(vd + 0.5);
199   int ni = dem_view_.ni(), nj = dem_view_.nj();
200   if (u < 0 || u >= ni || v < 0 || v >= nj)
201   {
202     if (verbose_)
203       std::cout << "Initial guess for DEM intersection is outside DEM bounds - Fatal!" << std::endl;
204     return false;
205   }
206   // construct the brent minimizer
207   // note the brent minimzier is not fully integrated with the vil_nonlinear_minimizer interface
208   // thus the non standard call for error below
209   dem_bproj_cost_function c(dem_view_, geo_cam_, ray, verbose_);
210   vnl_brent_minimizer brm(c);
211   double tmin = brm.minimize(t);
212   double error = brm.f_at_last_minimum();
213   if (error > error_tol)
214     return false;
215 
216   world_point = origin + tmin * dir;
217 
218   if (verbose_)
219     std::cout << "success! ray/dem intersection " << world_point << std::endl;
220   return true;
221 }
222 
223 bool
bproj_dem(const vpgl_camera<double> * cam,vnl_double_2 const & image_point,double max_z,double min_z,vnl_double_3 const & initial_guess,vnl_double_3 & world_point,double error_tol)224 vpgl_backproject_dem::bproj_dem(const vpgl_camera<double> * cam,
225                                 vnl_double_2 const & image_point,
226                                 double max_z,
227                                 double min_z,
228                                 vnl_double_3 const & initial_guess,
229                                 vnl_double_3 & world_point,
230                                 double error_tol)
231 {
232   vgl_point_2d<double> img_pt;
233   vgl_point_3d<double> init_guess;
234   vgl_point_3d<double> wrld_pt;
235   img_pt.set(image_point[0], image_point[1]);
236   init_guess.set(initial_guess[0], initial_guess[1], initial_guess[2]);
237   bool good = this->bproj_dem(cam, img_pt, max_z, min_z, init_guess, wrld_pt, error_tol);
238 
239   if (!good)
240   {
241     return false;
242   }
243   world_point[0] = wrld_pt.x();
244   world_point[1] = wrld_pt.y();
245   world_point[2] = wrld_pt.z();
246   return true;
247 }
248 
249 bool
bproj_dem(vpgl_rational_camera<double> const & rcam,vnl_double_2 const & image_point,double max_z,double min_z,vnl_double_3 const & initial_guess,vnl_double_3 & world_point,double error_tol)250 vpgl_backproject_dem::bproj_dem(vpgl_rational_camera<double> const & rcam,
251                                 vnl_double_2 const & image_point,
252                                 double max_z,
253                                 double min_z,
254                                 vnl_double_3 const & initial_guess,
255                                 vnl_double_3 & world_point,
256                                 double error_tol)
257 {
258 
259   const auto * cam = dynamic_cast<const vpgl_camera<double> *>(&rcam);
260   return this->bproj_dem(cam, image_point, max_z, min_z, initial_guess, world_point, error_tol);
261 }
262 
263 bool
bproj_dem(vpgl_rational_camera<double> const & rcam,vgl_point_2d<double> const & image_point,double max_z,double min_z,vgl_point_3d<double> const & initial_guess,vgl_point_3d<double> & world_point,double error_tol)264 vpgl_backproject_dem::bproj_dem(vpgl_rational_camera<double> const & rcam,
265                                 vgl_point_2d<double> const & image_point,
266                                 double max_z,
267                                 double min_z,
268                                 vgl_point_3d<double> const & initial_guess,
269                                 vgl_point_3d<double> & world_point,
270                                 double error_tol)
271 {
272 
273   const auto * cam = dynamic_cast<const vpgl_camera<double> *>(&rcam);
274   return this->bproj_dem(cam, image_point, max_z, min_z, initial_guess, world_point, error_tol);
275 }
276