Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2883f9d
use align vectors instead of from matrix for a channel-wise rotation
ctuguinay Feb 18, 2025
5b37e39
use normalization logic, add warnings, and add tests
ctuguinay Feb 25, 2025
3734591
simplify test
ctuguinay Feb 25, 2025
96c2ea3
small word change
ctuguinay Feb 25, 2025
50e29c5
Update echopype/consolidate/ek_depth_utils.py
ctuguinay Apr 3, 2025
e06d9cb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 3, 2025
5faf016
replace previous APA7 citation with recent ICES APA7 citation (#1455)
ctuguinay Feb 16, 2025
6186616
pin scipy to temporarily ensure that rotation matrix calculation does…
ctuguinay Feb 17, 2025
095d54f
[pre-commit.ci] pre-commit autoupdate (#1461)
pre-commit-ci[bot] Feb 17, 2025
62a78b0
Check if there exist any swap files before cleaning them up [all test…
ctuguinay Feb 18, 2025
9f1a1f7
remove __setattr__ from EchoData (#1457)
leewujung Feb 18, 2025
79cf828
compute dask array before np array equal (#1452)
ctuguinay Feb 18, 2025
a01785e
Dataset.dims to Dataset.sizes [all tests ci] (#1453)
ctuguinay Feb 18, 2025
14d613c
Chunks as dictionaries in `_get_auto_chunk` [all tests ci] (#1454)
ctuguinay Feb 18, 2025
2c430fd
set decode timedelta to False since it will default to this in later …
ctuguinay Feb 18, 2025
51a573e
Fix deprecation warning for truth value of an empty array [all tests …
ctuguinay Feb 19, 2025
aff32d9
Fill in NaN for missing EK80 coefficients [all tests ci] (#1458)
leewujung Feb 19, 2025
9f3ef6c
Backward compatibility of raw-converted dataset with `xr.DataTree` [a…
oftfrfbf Feb 20, 2025
48ccd56
Add v0.9.1 and v0.10.0 release notes to docs (#1465)
leewujung Feb 21, 2025
e98e213
Update workflows to use python 3.12 and ubuntu 22.04 [all tests ci] (…
leewujung Feb 21, 2025
690c542
Replace `pkg_resources.resource_string` with `importlib.resources.fil…
leewujung Feb 22, 2025
0fea7dd
chore(deps): bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4 (…
dependabot[bot] Feb 22, 2025
df17355
chore(deps): bump actions/cache from 4.2.0 to 4.2.1 (#1469)
dependabot[bot] Feb 24, 2025
1c1d18f
not -> no (#1471)
jmeischner Feb 25, 2025
e0fa5a4
[pre-commit.ci] pre-commit autoupdate (#1472)
pre-commit-ci[bot] Mar 4, 2025
e7e31e0
chore(deps): bump actions/cache from 4.2.1 to 4.2.2 (#1473)
dependabot[bot] Mar 4, 2025
4a9b4fc
chore(deps): bump actions/setup-python from 5.4.0 to 5.5.0 (#1481)
dependabot[bot] Apr 1, 2025
f9d30f5
[pre-commit.ci] pre-commit autoupdate (#1480)
pre-commit-ci[bot] Apr 1, 2025
a6acb53
chore(deps): bump actions/cache from 4.2.2 to 4.2.3 (#1479)
dependabot[bot] Apr 1, 2025
6bd05d6
expand ping time preemptively and add corresponding chunking tests (#…
ctuguinay Apr 3, 2025
d1c5744
Enhance `compute_MVBS` feasability (#1470)
ctuguinay Apr 3, 2025
ff3cfaa
merge main
ctuguinay Apr 3, 2025
a993c55
indent line of code
ctuguinay Apr 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 27 additions & 22 deletions echopype/consolidate/ek_depth_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,36 +74,41 @@ def ek_use_platform_angles(platform_ds: xr.Dataset, ping_time_da: xr.DataArray)
return align_to_ping_time(echo_range_scaling, "time2", ping_time_da)


def ek_use_beam_angles(
beam_ds: xr.Dataset,
) -> xr.DataArray:
def ek_use_beam_angles(beam_ds: xr.Dataset) -> xr.DataArray:
"""
Use `beam_direction_x`, `beam_direction_y`, and `beam_direction_z` from the EK Beam group to
compute echo range rotational values.
Compute echo range scaling from beam_direction components. For each channel, we expect that
the beam direction vector is normalized. If not, then we normalize the z direction and set
that as the echo range scaling.
Additionally, if a nonzero vector is not normalized, a warning is issued and it is normalized.
If a channel-wise beam direction vector is zero, a warning is issued and the returned z value
is set to NaN.
"""
# Check and log NaNs if they exist in the Beam group variables
# Check and log NaNs if they exist in the Beam direction variables
_check_and_log_nans(
beam_ds, "Sonar/Beam_group1", ["beam_direction_x", "beam_direction_y", "beam_direction_z"]
)

# Grab beam angles from beam group
beam_direction_x = beam_ds["beam_direction_x"]
beam_direction_y = beam_ds["beam_direction_y"]
beam_direction_z = beam_ds["beam_direction_z"]

# Compute echo range scaling from pitch roll rotations
beam_dir_rotmatrix_stack = [
[
np.array([0, 0, beam_direction_x[c]]),
np.array([0, 0, beam_direction_y[c]]),
np.array([0, 0, beam_direction_z[c]]),
]
for c in range(len(beam_direction_x))
]
rot_beam_direction = R.from_matrix(beam_dir_rotmatrix_stack)
echo_range_scaling = rot_beam_direction.as_matrix()[:, -1, -1]
echo_range_scaling = xr.DataArray(
echo_range_scaling, dims="channel", coords={"channel": beam_ds["channel"]}
)

# Calculate the norm for each channel
norm = np.sqrt(beam_direction_x**2 + beam_direction_y**2 + beam_direction_z**2)

# Warn if any nonzero vector is not normalized
tolerance = 1e-8
if ((norm > tolerance) & (np.abs(norm - 1) > tolerance)).any():
logger.warning(
"Beam direction vector was not normalized; applying normalization. "
"By definition, it should have been normalized."
)

# Warn if any channel has a (nearly) zero vector
if (norm < tolerance).any():
logger.warning("Some beam direction vectors are zero. Outputting NaN for those channels.")

# For channels with near-zero norm, we return NaN. Otherwise, we return the normalized
# z component.
Comment on lines +110 to +111
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This statement is reversed, right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so since xr.where(norm < tolerance, np.nan, beam_direction_z / norm) will return NaN if norm is small
image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh i know what I read wrong! I read "near-zero" as "non-zero" 😵
This is resolved then!

normed_z = xr.where(norm < tolerance, np.nan, beam_direction_z / norm)
echo_range_scaling = normed_z
return echo_range_scaling
Comment thread
ctuguinay marked this conversation as resolved.
Outdated
58 changes: 52 additions & 6 deletions echopype/tests/consolidate/test_add_depth.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,28 +104,74 @@ def test_ek_use_platform_angles_output():


@pytest.mark.unit
def test_ek_use_beam_angles_output():
def test_ek_use_beam_angles_output(caplog):
"""
Test `use_beam_angle` outputs for 2 sideways looking beams and 1 vertical looking beam.
Test `use_beam_angle` outputs for 2 sideways looking beams, 1 vertical looking beam, and 1
beam that does not have normalized directions.
"""
# Create a Dataset with channel-specific beam direction vectors
# In channels 1 and 2, the transducers are not pointing vertically at all so the echo
# range scaling should be 0 (i.e zeros out the entire depth).
# In channel 3, the transducer is completely vertical so the echo range scaling should
# be 1 (i.e no change).
# In channel 4, the transducer is tilted to the x direction by 30 deg, so the
# echo range scaling should be sqrt(3)/2.
# In channel 4, we do not have a normalized vector so the echo range scaling should be
# sqrt(3)/2 divided by the norm of channel 4.
channel_da = xr.DataArray(["chan1", "chan2", "chan3", "chan4"], dims=("channel"))
beam_ds = xr.Dataset(
{
"beam_direction_x": xr.DataArray([1, 0, 0, 1/2], dims=("channel")),
"beam_direction_x": xr.DataArray([1, 0, 0, 1], dims=("channel")),
"beam_direction_y": xr.DataArray([0, 1, 0, 0], dims=("channel")),
"beam_direction_z": xr.DataArray([0, 0, 1, np.sqrt(3)/2], dims=("channel")),
},
coords={"channel": channel_da}
)

# Turn on logger verbosity
ep.utils.log.verbose(override=False)

# Compute beam angle echo range scaling
echo_range_scaling = ep.consolidate.ek_depth_utils.ek_use_beam_angles(beam_ds)
assert np.allclose(echo_range_scaling.values, np.array([0.0, 0.0, 1.0, np.sqrt(3)/2]))

# Turn off logger verbosity
ep.utils.log.verbose(override=True)

# Verify the correct warning
assert "Beam direction vector was not normalized" in caplog.text

# Compute and compare manual test values with function values
fourth_value = (np.sqrt(3)/2) / np.sqrt(((np.sqrt(3)/2) ** 2) + 1)
assert np.allclose(echo_range_scaling.values, np.array([0.0, 0.0, 1.0, fourth_value]))


@pytest.mark.unit
def test_warning_zero_vector(caplog):
"""
Test that a warning is logged and NaN is returned for channels with zero beam direction vector.
"""
# Create a dataset with two channels: zero vector [0, 0, 0] and nonzero normalized vector [0, 1, 0].
beam_ds = xr.Dataset(
{
"beam_direction_x": xr.DataArray([0, 0], dims=("channel")),
"beam_direction_y": xr.DataArray([0, 1], dims=("channel")),
"beam_direction_z": xr.DataArray([0, 0], dims=("channel")),
}
)

# Turn on logger verbosity
ep.utils.log.verbose(override=False)

# Compute beam angle echo range scaling
echo_range_scaling = ep.consolidate.ek_depth_utils.ek_use_beam_angles(beam_ds)

# Verify the correct warning
assert "Some beam direction vectors are zero" in caplog.text

# Check that channel 0 output is NaN and channel 1 output is 0
assert np.isnan(echo_range_scaling.values[0])
assert np.isclose(echo_range_scaling.values[1], 0.0)

# Turn off logger verbosity
ep.utils.log.verbose(override=True)


@pytest.mark.integration
Expand Down