Skip to content

Commit 70ff9d1

Browse files
rad-eng-59GitHub Enterprise
authored andcommitted
Fix issue 951 on accessing DEM raster of DEMInterpolator (#963)
- Add missing pybind11 Eigen header to pybind script "DEMInterpolator.cpp". - Add an overloaded constructor to pybind "DEMInterpolator.cpp" to construct DEM directly from a raster object. - Augment the respective DEM python test suite "dem.py" to test new feature. - Add a very short DEM raster file over Himalayas for testing DEM. - Add a description for DEM raster file in "tests/data/README.md" - Rename alias "DI" to "DEMInterp" in pybind "DEMInterpolator.cpp" - Replace raw pointers with smart ones for members in class "DEMInterpolator" - Replace raw pointer members "_proj" and "_interp" by shared smart ones - Get rid of dynamic pointers for return values in the pybind code and instead build DEMInterpolator object on the stack - Run clang-format only on pybind script
1 parent c9c60cf commit 70ff9d1

File tree

6 files changed

+146
-85
lines changed

6 files changed

+146
-85
lines changed

cxx/isce3/geometry/DEMInterpolator.cpp

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,10 @@
1010
#include <isce3/core/Projections.h>
1111
#include <isce3/io/Raster.h>
1212

13-
isce3::geometry::DEMInterpolator::
14-
~DEMInterpolator() {
15-
if (_interp) {
16-
delete _interp;
17-
}
18-
if (_proj) {
19-
delete _proj;
20-
}
21-
}
22-
2313
/** Set EPSG code for input DEM */
2414
void isce3::geometry::DEMInterpolator::epsgCode(int epsgcode) {
2515
_epsgcode = epsgcode;
26-
_proj = isce3::core::createProj(epsgcode);
16+
_proj = isce3::core::makeProjection(epsgcode);
2717
}
2818

2919
// Load DEM subset into memory
@@ -58,7 +48,7 @@ isce3::error::ErrorCode isce3::geometry::DEMInterpolator::loadDEM(
5848
//Initialize projection
5949
int epsgcode = demRaster.getEPSG();
6050
_epsgcode = epsgcode;
61-
_proj = isce3::core::createProj(epsgcode);
51+
_proj = isce3::core::makeProjection(epsgcode);
6252

6353
// Validate requested geographic bounds with input DEM raster
6454
if (minX < firstX) {
@@ -112,7 +102,7 @@ isce3::error::ErrorCode isce3::geometry::DEMInterpolator::loadDEM(
112102
demRaster.getBlock(_dem.data(), xstart, ystart, width, length);
113103

114104
// Initialize internal interpolator
115-
_interp = isce3::core::createInterpolator<float>(_interpMethod);
105+
_interp = std::unique_ptr<isce3::core::Interpolator<float>>(isce3::core::createInterpolator<float>(_interpMethod));
116106

117107
// Indicate we have loaded a valid raster
118108
_haveRaster = true;
@@ -144,7 +134,7 @@ loadDEM(isce3::io::Raster & demRaster) {
144134
//Initialize projection
145135
int epsgcode = demRaster.getEPSG();
146136
_epsgcode = epsgcode;
147-
_proj = isce3::core::createProj(epsgcode);
137+
_proj = isce3::core::makeProjection(epsgcode);
148138

149139
// Store actual starting lat/lon for raster subset
150140
_xstart = firstX;
@@ -159,7 +149,7 @@ loadDEM(isce3::io::Raster & demRaster) {
159149
demRaster.getBlock(_dem.data(), 0, 0, width, length);
160150

161151
// Initialize internal interpolator
162-
_interp = isce3::core::createInterpolator<float>(_interpMethod);
152+
_interp = std::unique_ptr<isce3::core::Interpolator<float>>(isce3::core::createInterpolator<float>(_interpMethod));
163153

164154
// Indicate we have loaded a valid raster
165155
_haveRaster = true;

cxx/isce3/geometry/DEMInterpolator.h

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#pragma once
88

9+
#include <memory>
910
#include "forward.h"
1011

1112
// pyre
@@ -52,9 +53,7 @@ class isce3::geometry::DEMInterpolator {
5253
_maxValue{height},
5354
_epsgcode{epsg},
5455
_interpMethod{method} {}
55-
56-
/** Destructor */
57-
~DEMInterpolator();
56+
5857

5958
/** Read in subset of data from a DEM with a supported projection */
6059
isce3::error::ErrorCode loadDEM(isce3::io::Raster& demRaster,
@@ -138,7 +137,7 @@ class isce3::geometry::DEMInterpolator {
138137
void epsgCode(int epsgcode);
139138

140139
/** Get Pointer to a ProjectionBase */
141-
inline isce3::core::ProjectionBase* proj() const { return _proj; }
140+
inline isce3::core::ProjectionBase* proj() const {return _proj.get(); }
142141

143142
/** Get interpolator method enum */
144143
inline isce3::core::dataInterpMethod interpMethod() const {
@@ -159,10 +158,10 @@ class isce3::geometry::DEMInterpolator {
159158
float _maxValue;
160159
// Pointer to a ProjectionBase
161160
int _epsgcode;
162-
isce3::core::ProjectionBase * _proj = nullptr;
161+
std::shared_ptr<isce3::core::ProjectionBase> _proj;
163162
// Pointer to an Interpolator
164163
isce3::core::dataInterpMethod _interpMethod;
165-
isce3::core::Interpolator<float> * _interp = nullptr;
164+
std::shared_ptr<isce3::core::Interpolator<float>> _interp;
166165
// 2D array for storing DEM subset
167166
isce3::core::Matrix<float> _dem;
168167
// Starting x/y for DEM subset and spacing
Lines changed: 77 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,94 @@
11
#include "DEMInterpolator.h"
22

3-
#include <isce3/core/Constants.h>
4-
#include <isce3/io/Raster.h>
5-
#include <Eigen/Dense>
3+
#include <memory>
4+
#include <pybind11/eigen.h>
65
#include <stdexcept>
76
#include <string>
87

8+
#include <Eigen/Dense>
9+
10+
#include <isce3/core/Constants.h>
11+
#include <isce3/io/Raster.h>
12+
913
namespace py = pybind11;
1014

11-
using DI = isce3::geometry::DEMInterpolator;
15+
using DEMInterp = isce3::geometry::DEMInterpolator;
1216

13-
void addbinding(pybind11::class_<DI> & pyDEMInterpolator)
17+
void addbinding(pybind11::class_<DEMInterp>& pyDEMInterpolator)
1418
{
1519
pyDEMInterpolator
16-
.def(py::init<double, isce3::core::dataInterpMethod, int>(),
17-
py::arg("height") = 0.0,
18-
py::arg("method") = isce3::core::BILINEAR_METHOD,
19-
py::arg("epsg") = 4326)
20-
// For convenience allow a string, too.
21-
.def(py::init([](double h, const std::string & method, int epsg) {
20+
.def(py::init<double, isce3::core::dataInterpMethod, int>(),
21+
py::arg("height") = 0.0,
22+
py::arg("method") = isce3::core::BILINEAR_METHOD,
23+
py::arg("epsg") = 4326)
24+
// For convenience allow a string, too.
25+
.def(py::init([](double h, const std::string& method, int epsg) {
2226
auto m = parseDataInterpMethod(method);
23-
return new DI(h, m, epsg);
27+
return DEMInterp(h, m, epsg);
28+
}),
29+
py::arg("height") = 0.0, py::arg("method") = "bilinear",
30+
py::arg("epsg") = 4326)
31+
32+
// This constructor is similar to method "loadDEM" but is more
33+
// convenient!
34+
.def(py::init([](isce3::io::Raster& raster_obj) {
35+
DEMInterp dem {};
36+
dem.loadDEM(raster_obj);
37+
return dem;
2438
}),
25-
py::arg("height") = 0.0,
26-
py::arg("method") = "bilinear",
27-
py::arg("epsg") = 4326)
39+
"Construct DEM from ISCE3 Raster object",
40+
py::arg("raster_obj"))
2841

29-
.def("load_dem",
30-
py::overload_cast<isce3::io::Raster&>(&DI::loadDEM))
31-
.def("load_dem",
32-
py::overload_cast<isce3::io::Raster&, double, double, double, double>
33-
(&DI::loadDEM),
34-
py::arg("raster"), py::arg("min_x"), py::arg("max_x"),
35-
py::arg("min_y"), py::arg("max_y"))
42+
.def("load_dem",
43+
py::overload_cast<isce3::io::Raster&>(&DEMInterp::loadDEM))
44+
.def("load_dem",
45+
py::overload_cast<isce3::io::Raster&, double, double,
46+
double, double>(&DEMInterp::loadDEM),
47+
py::arg("raster"), py::arg("min_x"), py::arg("max_x"),
48+
py::arg("min_y"), py::arg("max_y"))
3649

37-
.def("interpolate_lonlat", &DI::interpolateLonLat)
38-
.def("interpolate_xy", &DI::interpolateXY)
50+
.def("interpolate_lonlat", &DEMInterp::interpolateLonLat)
51+
.def("interpolate_xy", &DEMInterp::interpolateXY)
3952

40-
.def_property("ref_height",
41-
py::overload_cast<>(&DI::refHeight, py::const_),
42-
py::overload_cast<double>(&DI::refHeight))
43-
.def_property_readonly("have_raster", &DI::haveRaster)
44-
.def_property("interp_method",
45-
py::overload_cast<>(&DI::interpMethod, py::const_),
46-
py::overload_cast<isce3::core::dataInterpMethod>(&DI::interpMethod))
53+
.def_property("ref_height",
54+
py::overload_cast<>(&DEMInterp::refHeight, py::const_),
55+
py::overload_cast<double>(&DEMInterp::refHeight))
56+
.def_property_readonly("have_raster", &DEMInterp::haveRaster)
57+
.def_property("interp_method",
58+
py::overload_cast<>(&DEMInterp::interpMethod, py::const_),
59+
py::overload_cast<isce3::core::dataInterpMethod>(
60+
&DEMInterp::interpMethod))
4761

48-
// Define all these as readonly even though writable in C++ API.
49-
// Probably better to just convert your data to a GDAL format than try
50-
// to build a DEM on the fly.
51-
.def_property_readonly("data", [](DI & self) { // .data() isn't const
52-
if (!self.haveRaster()) {
53-
throw std::out_of_range("Tried to access DEM data but size=0");
54-
}
55-
using namespace Eigen;
56-
using MatF = Eigen::Matrix<float, Dynamic, Dynamic, RowMajor>;
57-
Map<const MatF> mat(self.data(), self.length(), self.width());
58-
return mat;
59-
}, py::return_value_policy::reference_internal)
60-
.def_property_readonly("x_start", py::overload_cast<>(&DI::xStart, py::const_))
61-
.def_property_readonly("y_start", py::overload_cast<>(&DI::yStart, py::const_))
62-
.def_property_readonly("delta_x", py::overload_cast<>(&DI::deltaX, py::const_))
63-
.def_property_readonly("delta_y", py::overload_cast<>(&DI::deltaY, py::const_))
64-
.def_property_readonly("width", py::overload_cast<>(&DI::width, py::const_))
65-
.def_property_readonly("length", py::overload_cast<>(&DI::length, py::const_))
66-
.def_property_readonly("epsg_code", py::overload_cast<>(&DI::epsgCode, py::const_))
67-
;
62+
// Define all these as readonly even though writable in C++ API.
63+
// Probably better to just convert your data to a GDAL format than
64+
// try to build a DEM on the fly.
65+
.def_property_readonly(
66+
"data",
67+
[](DEMInterp& self) { // .data() isn't const
68+
if (!self.haveRaster()) {
69+
throw std::out_of_range(
70+
"Tried to access DEM data but size=0");
71+
}
72+
using namespace Eigen;
73+
using MatF = Eigen::Matrix<float, Dynamic, Dynamic,
74+
RowMajor>;
75+
Map<const MatF> mat(
76+
self.data(), self.length(), self.width());
77+
return mat;
78+
},
79+
py::return_value_policy::reference_internal)
80+
.def_property_readonly("x_start",
81+
py::overload_cast<>(&DEMInterp::xStart, py::const_))
82+
.def_property_readonly("y_start",
83+
py::overload_cast<>(&DEMInterp::yStart, py::const_))
84+
.def_property_readonly("delta_x",
85+
py::overload_cast<>(&DEMInterp::deltaX, py::const_))
86+
.def_property_readonly("delta_y",
87+
py::overload_cast<>(&DEMInterp::deltaY, py::const_))
88+
.def_property_readonly(
89+
"width", py::overload_cast<>(&DEMInterp::width, py::const_))
90+
.def_property_readonly("length",
91+
py::overload_cast<>(&DEMInterp::length, py::const_))
92+
.def_property_readonly("epsg_code",
93+
py::overload_cast<>(&DEMInterp::epsgCode, py::const_));
6894
}

tests/data/README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,13 @@ and then post-processed and converted into L0B product via its python utility to
117117
The Tx range lines types are of HPA, LNA, and BYPASS. BYPASS range line interval is 20.
118118

119119
[1]: M. Shimada et al., "PALSAR Radiometric and Geometric Calibration",
120-
*IEEE Trans. Geosci. Remote Sens.*, pp. 3915-3932, December 2009.
120+
*IEEE Trans. Geosci. Remote Sens.*, pp. 3915-3932, December 2009.
121+
122+
## DEM
123+
- **dem_himalayas_E81p5_N28p3_short.tiff**
124+
125+
DEM raster file over a very small area of Himalayas downloaded from NISAR-DEM AWS S3
126+
bucket as follows:
127+
```
128+
$ stage_dem.py -b 81.45 28.29 81.5 28.3 -o dem_himalayas_E81p5_N28p3_short.vrt
129+
```
710 KB
Binary file not shown.
Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,79 @@
11
#!/usr/bin/env python3
22
import pytest
3-
import pybind_isce3.geometry as m
3+
from isce3.geometry import DEMInterpolator
4+
from isce3.io import Raster
45
from pybind_isce3.core import DataInterpMethod
6+
import iscetest
57

6-
def test_const():
8+
import os
9+
import collections as cl
10+
import numpy.testing as npt
11+
12+
from osgeo import gdal
13+
14+
15+
def dem_info_from_gdal(file_raster: str) -> cl.namedtuple:
16+
"""Get shape, min, max, mean of dem"""
17+
dset = gdal.Open(file_raster, gdal.GA_ReadOnly)
18+
band = dset.GetRasterBand(1)
19+
dem = band.ReadAsArray()
20+
return cl.namedtuple('dem_info', 'shape min max mean')(
21+
dem.shape, dem.min(), dem.max(), dem.mean())
22+
23+
24+
def test_constructor_ref_height():
725
href = 10.
826

9-
dem = m.DEMInterpolator()
27+
dem = DEMInterpolator()
1028
dem.ref_height = href
1129
assert dem.ref_height == href
1230

13-
dem = m.DEMInterpolator(href)
31+
dem = DEMInterpolator(href)
1432
assert dem.ref_height == href
1533

1634
assert dem.interpolate_xy(0, 0) == href
1735
assert dem.interpolate_lonlat(0, 0) == href
1836

19-
assert dem.have_raster == False
37+
npt.assert_equal(dem.have_raster, False)
38+
39+
40+
def test_constructor_raster_obj():
41+
# filename of the DEM ratster
42+
filename_dem = 'dem_himalayas_E81p5_N28p3_short.tiff'
43+
file_raster = os.path.join(iscetest.data, filename_dem)
44+
# get some DEM info via gdal to be used as a reference for V&V
45+
dem_info = dem_info_from_gdal(file_raster)
46+
# build DEM object
47+
raster_obj = Raster(file_raster)
48+
dem_obj = DEMInterpolator(raster_obj)
49+
# validate existence and details of DEM data
50+
npt.assert_equal(dem_obj.have_raster, True, err_msg='No DEM ratser data')
51+
npt.assert_equal(dem_obj.data.shape, dem_info.shape,
52+
err_msg='Wrong shape of DEM ratser data')
53+
npt.assert_allclose(dem_obj.data.min(), dem_info.min,
54+
err_msg='Wrong min DEM height')
55+
npt.assert_allclose(dem_obj.data.max(), dem_info.max,
56+
err_msg='Wrong max DEM height')
57+
npt.assert_allclose(dem_obj.data.mean(), dem_info.mean,
58+
err_msg='Wrong mean DEM height')
2059

2160

2261
def test_methods():
2362
# pybind11::enum_ is not iterable
2463
for name in "SINC BILINEAR BICUBIC NEAREST BIQUINTIC".split():
2564
# enum constructor
2665
method = getattr(DataInterpMethod, name)
27-
dem = m.DEMInterpolator(method=method)
66+
dem = DEMInterpolator(method=method)
2867
assert dem.interp_method == method
2968
# string constructor
30-
dem = m.DEMInterpolator(method=name)
69+
dem = DEMInterpolator(method=name)
3170
assert dem.interp_method == method
3271

33-
dem = m.DEMInterpolator(method="bicubic")
72+
dem = DEMInterpolator(method="bicubic")
3473
assert dem.interp_method == DataInterpMethod.BICUBIC
3574

36-
dem = m.DEMInterpolator(method="biCUBic")
75+
dem = DEMInterpolator(method="biCUBic")
3776
assert dem.interp_method == DataInterpMethod.BICUBIC
3877

3978
with pytest.raises(ValueError):
40-
dem = m.DEMInterpolator(method="TigerKing")
41-
42-
# TODO Test other methods once we have isce::io::Raster bindings.
79+
dem = DEMInterpolator(method="TigerKing")

0 commit comments

Comments
 (0)