Skip to content

Commit

Permalink
Set intersection of nodata in SLC stack to nan in `run_phase_linkin…
Browse files Browse the repository at this point in the history
…g` (#237)

* set intersection of nodata in SLC stack to `nan` in `run_phase_linking`

This should fix #84

* add CHANGELOG [skip ci]

* make copies to avoid nans across tests

* prep CHANGELOG for release [skip ci]
  • Loading branch information
scottstanie authored Feb 17, 2024
1 parent 30d6dac commit b22146b
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 27 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
# [Unreleased](https://github.com/isce-framework/dolphin/compare/v0.14.1...main)
# [Unreleased](https://github.com/isce-framework/dolphin/compare/v0.15.0...main)

# [0.15.0](https://github.com/isce-framework/dolphin/compare/v0.14.1...0.15.0) - 2024-02-16

**Changed**

- Combine the nodata region with the `mask_file` to pass through to unwrappers
- Update regions which are nodata in interferograms to be nodata in unwrapped phase
- Use `uint16` data type for connected component labels

**Fixed**

- Intersection of nodata regions for SLC stack are now all set to `nan` during phase linking

# [0.14.1](https://github.com/isce-framework/dolphin/compare/v0.14.0...0.14.1) - 2024-02-15

**Fixed**
Expand Down
8 changes: 6 additions & 2 deletions src/dolphin/phase_link/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ def run_phase_linking(
# We zero out nodata if all pixels within the window had nodata
mask_looked = take_looks(nodata_mask, *strides, func_type="all")

# Set no data pixels to np.nan
temp_coh = np.where(mask_looked, np.nan, cpl_out.temp_coh)
# Convert from jax array back to np
temp_coh = np.array(cpl_out.temp_coh)

# Fill in the PS pixels from the original SLC stack, if it was given
if np.any(ps_mask):
Expand All @@ -205,6 +205,10 @@ def run_phase_linking(
reference_idx,
)

# Finally, ensure the nodata regions are 0
cpx_phase[:, mask_looked] = np.nan
temp_coh[mask_looked] = np.nan

return PhaseLinkOutput(
cpx_phase,
temp_coh,
Expand Down
57 changes: 33 additions & 24 deletions tests/test_phase_link_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,15 @@ def slc_samples(C_truth):
return simulate.simulate_neighborhood_stack(C, ns)


# CPU versions of the MLE and EVD estimates
@pytest.fixture(scope="module")
def C_hat(slc_samples):
return np.array(covariance.coh_mat_single(slc_samples))


# Make the single-pixel comparisons with simple implementation
@pytest.fixture(scope="module")
def est_mle_verify(C_hat):
return np.angle(simulate.mle(C_hat))


@pytest.fixture(scope="module")
def est_evd_verify(C_hat):
return np.angle(simulate.evd(C_hat))


@pytest.mark.parametrize("use_evd", [False, True])
def test_estimation(C_truth, slc_samples, est_mle_verify, est_evd_verify, use_evd):
def test_estimation(C_truth, slc_samples, use_evd):
_, truth = C_truth

C_hat = np.array(covariance.coh_mat_single(slc_samples))
# Make the single-pixel comparisons with simple implementation
est_mle_verify = np.angle(simulate.mle(C_hat))
est_evd_verify = np.angle(simulate.evd(C_hat))

# Check that the estimates are close to the truth
err_deg = 10
assert np.degrees(simulate.rmse(truth, est_evd_verify)) < err_deg
Expand Down Expand Up @@ -74,7 +62,7 @@ def test_estimation(C_truth, slc_samples, est_mle_verify, est_evd_verify, use_ev


def test_masked(slc_samples, C_truth):
slc_stack = slc_samples.reshape(NUM_ACQ, 11, 11)
slc_stack = slc_samples.copy().reshape(NUM_ACQ, 11, 11)
mask = np.zeros((11, 11), dtype=bool)
# Mask the top row
mask[0, :] = True
Expand All @@ -100,7 +88,7 @@ def test_masked(slc_samples, C_truth):


def test_run_phase_linking(slc_samples):
slc_stack = slc_samples.reshape(NUM_ACQ, 11, 11)
slc_stack = slc_samples.copy().reshape(NUM_ACQ, 11, 11)
pl_out = _core.run_phase_linking(
slc_stack,
half_window=HalfWindow(5, 5),
Expand All @@ -115,19 +103,40 @@ def test_run_phase_linking(slc_samples):
)


def test_run_phase_linking_norm_output(slc_samples):
slc_stack = slc_samples.reshape(NUM_ACQ, 11, 11)
def test_run_phase_linking_use_slc_amp(slc_samples):
slc_stack = slc_samples.copy().reshape(NUM_ACQ, 11, 11)
ps_mask = np.zeros((11, 11), dtype=bool)
# Specify at least 1 ps
ps_mask[1, 1] = True
pl_out = _core.run_phase_linking(
slc_stack,
half_window=HalfWindow(5, 5),
ps_mask=ps_mask,
use_slc_amp=False,
)
# The output should still all have modulus of 1
assert np.allclose(np.abs(pl_out.cpx_phase), 1)


def test_run_phase_linking_with_shift(slc_samples):
slc_stack = slc_samples.copy().reshape(NUM_ACQ, 11, 11)
# Pretend there's a shift which should lead to nodata at the intersection
# First row of the first image
slc_stack[0, 0, :] = np.nan
# last row of the second image
slc_stack[1, -1, :] = np.nan
pl_out = _core.run_phase_linking(
slc_stack,
half_window=HalfWindow(5, 5),
)

assert np.isnan(pl_out.cpx_phase[:, [0, -1], :]).all()
assert np.isnan(pl_out.temp_coh[[0, -1], :]).all()

assert np.all(~np.isnan(pl_out.cpx_phase[:, 1:-1, :]))
assert np.all(~np.isnan(pl_out.temp_coh[1:-1, :]))


@pytest.mark.parametrize("strides", [1, 2, 3, 4, 5])
@pytest.mark.parametrize("half_window", [5, 11])
@pytest.mark.parametrize("shape", [(10, 11), (15, 20), (50, 75)])
Expand All @@ -147,7 +156,7 @@ def test_strides_window_sizes(strides, half_window, shape):
@pytest.mark.parametrize("strides", [1, 2, 3, 4])
def test_ps_fill(slc_samples, strides):
rows, cols = 11, 11
slc_stack = slc_samples.reshape(NUM_ACQ, rows, cols)
slc_stack = slc_samples.copy().reshape(NUM_ACQ, 11, 11)

mle_est = np.zeros((NUM_ACQ, rows // strides, cols // strides), dtype=np.complex64)
temp_coh = np.zeros(mle_est.shape[1:])
Expand Down Expand Up @@ -178,7 +187,7 @@ def test_ps_fill(slc_samples, strides):

@pytest.mark.parametrize("strides", [1, 2, 3])
def test_run_phase_linking_ps_fill(slc_samples, strides):
slc_stack = slc_samples.reshape(NUM_ACQ, 11, 11)
slc_stack = slc_samples.copy().reshape(NUM_ACQ, 11, 11)
ps_idx = 2
ps_mask = np.zeros((11, 11), dtype=bool)
ps_mask[ps_idx, ps_idx] = True
Expand Down

0 comments on commit b22146b

Please sign in to comment.