Skip to content

Commit fe6cba7

Browse files
gmgunterGitHub Enterprise
authored andcommitted
Fix SNAPHU IntegratePhase() to prevent accumulating large numerical error (#967)
* Update IntegratePhase() to resolve numerical precision issues SNAPHU's IntegratePhase() function determines the unwrapped phase by accumulating the discrete unwrapped phase gradients along an integration path that spans the interferogram (tile). It first scans across the first row, and then down each column. In practice, this can lead to unacceptably large numerical precision errors by summing thousands of floating point numbers. This update modifies the algorithm to instead integrate an integer number of cycles of 2pi in order to avoid the accumulation of floating point rounding errors. The residual phase error is significantly improved and, as a result, the absolute error tolerances used in the SNAPHU unit tests could be decreased by an order of magnitude. In addition, the order in which we integrate over the interferogram was changed from column-by-column to row-by-row, in order to improve memory locality for row-major data structures like those currently used in SNAPHU. * Update SNAPHU tests to use both abs. & rel. error tolerances
1 parent 572a447 commit fe6cba7

File tree

2 files changed

+45
-22
lines changed

2 files changed

+45
-22
lines changed

cxx/isce3/unwrap/snaphu/snaphu_util.cpp

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ static
2727
int IsFalse(char *str);
2828
static
2929
double ModDiff(double f1, double f2);
30+
static
31+
int DiffNCycle(double f1, double f2);
3032

3133

3234
/* function: IsTrue()
@@ -105,6 +107,30 @@ double ModDiff(double f1, double f2){
105107
}
106108

107109

110+
/* function: DiffNCycle()
111+
* -------------------
112+
* Computes the number of cycles of 2pi that must be added to the
113+
* difference between f1 and f2 in order to "wrap" it to the interval
114+
* (-pi,pi]. f1 and f2 should be between [0,2pi). Assumes that PI has
115+
* been defined.
116+
*
117+
* See also: ModDiff()
118+
*/
119+
static
120+
int DiffNCycle(double f1, double f2){
121+
122+
const auto f3=f1-f2;
123+
if(f3>PI){
124+
return -1;
125+
}
126+
if(f3<=-PI){
127+
return 1;
128+
}
129+
130+
return(0);
131+
}
132+
133+
108134
/* function: WrapPhase()
109135
* ---------------------
110136
* Makes sure the passed float array is properly wrapped into the [0,2pi)
@@ -302,30 +328,27 @@ int CalcFlow(Array2D<float>& phase, Array2D<short>* flowsptr, long nrow, long nc
302328
* wrapped phase to create an unwrapped phase field. The unwrapped
303329
* phase field will be the same size as the wrapped field. The array
304330
* rowflow should have size N-1xM and colflow size NxM-1 where the
305-
* phase fields are NxM. Output is saved to a file.
331+
* phase fields are NxM.
306332
*/
307333
int IntegratePhase(Array2D<float>& psi, Array2D<float>& phi, Array2D<short>& flows,
308334
long nrow, long ncol){
309335

310-
long row, col;
311-
312-
auto rowflow = flows.block(0,0,nrow-1,ncol);
313-
auto colflow = flows.block(nrow-1,0,nrow,ncol-1);
314-
315-
/* set first element as seed */
316-
phi(0,0)=psi(0,0);
336+
auto rowflow=flows.block(0,0,nrow-1,ncol);
337+
auto colflow=flows.block(nrow-1,0,nrow,ncol-1);
317338

318-
/* integrate over first row */
319-
for(col=1;col<ncol;col++){
320-
phi(0,col)=phi(0,col-1)+(ModDiff(psi(0,col),psi(0,col-1))
321-
+colflow(0,col-1)*TWOPI);
322-
}
323-
324-
/* integrate over columns */
325-
for(row=1;row<nrow;row++){
326-
for(col=0;col<ncol;col++){
327-
phi(row,col)=phi(row-1,col)+(ModDiff(psi(row,col),psi(row-1,col))
328-
-rowflow(row-1,col)*TWOPI);
339+
/* compute unwrapped phase by accumulating the total number of cycles to be
340+
added along an integration path that spans each row */
341+
long initcycles=0;
342+
for(long row=0;row<nrow;row++){
343+
if(row>0){
344+
initcycles+=DiffNCycle(psi(row,0),psi(row-1,0))-rowflow(row-1,0);
345+
}
346+
long cycles=initcycles;
347+
for(long col=0;col<ncol;col++){
348+
if(col>0){
349+
cycles+=DiffNCycle(psi(row,col),psi(row,col-1))+colflow(row,col-1);
350+
}
351+
phi(row,col)=psi(row,col)+TWOPI*cycles;
329352
}
330353
}
331354

tests/python/packages/isce3/unwrap/snaphu.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def test_smooth_cost(self, init_method):
261261
mphase = phase[mask]
262262
munw = unw_raster.data[mask]
263263
offset = mphase[0] - munw[0]
264-
assert np.allclose(munw + offset, mphase, rtol=0.0, atol=1e-5)
264+
assert np.allclose(mphase - offset, munw, rtol=1e-6, atol=1e-6)
265265

266266
# Check the set of unique labels (masked-out pixels are labeled 0).
267267
unique_cc_labels = set(np.unique(ccl_raster.data))
@@ -371,7 +371,7 @@ def frac_nonzero(a):
371371
mphase = phase[mask]
372372
munw = unw_raster.data[mask]
373373
offset = mphase[0] - munw[0]
374-
good_pixels = np.isclose(munw + offset, mphase, rtol=0.0, atol=1e-4)
374+
good_pixels = np.isclose(mphase - offset, munw, rtol=1e-6, atol=1e-6)
375375
assert frac_nonzero(~good_pixels) < 1e-3
376376

377377
def test_tile_mode(self):
@@ -437,4 +437,4 @@ def test_tile_mode(self):
437437
mphase = phase[mask]
438438
munw = unw_raster.data[mask]
439439
offset = mphase[0] - munw[0]
440-
assert np.allclose(munw + offset, mphase, rtol=0.0, atol=1e-5)
440+
assert np.allclose(mphase - offset, munw, rtol=1e-6, atol=1e-6)

0 commit comments

Comments
 (0)