Fix crop_with_convex_hull crash when margin_thickness is zero#104
Fix crop_with_convex_hull crash when margin_thickness is zero#104RajdeepKushwaha5 wants to merge 1 commit intomllam:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes a crash in crop_with_convex_hull() when margin_thickness == 0.0 by correctly unpacking the (mask, hull_points) tuple returned by create_convex_hull_mask(), and adds a regression test plus a changelog entry.
Changes:
- Unpack
create_convex_hull_mask()return value in the zero-margin branch to avoid passing a tuple into_mask_with_common_dim(). - Correct
create_convex_hull_mask()return type annotation to match its actual tuple return value. - Add regression test coverage for
margin_thickness=0.0(includingreturn_mask=True) and document the fix inCHANGELOG.md.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
mllam_data_prep/ops/cropping.py |
Fixes the zero-margin crash path and updates the return type annotation. |
tests/test_convex_hull_cropping.py |
Adds a regression test for the zero-margin convex hull cropping behavior. |
CHANGELOG.md |
Records the bugfix under [unreleased] > Fixes. |
Comments suppressed due to low confidence (1)
mllam_data_prep/ops/cropping.py:51
create_convex_hull_maskbuildsda_interior_maskfor points inds(it runscontains_lonlatonda_lon/da_latfromds), but the docstring Return description says the mask indicates points inds_reference. Updating the docstring (and theda/da_refwording) would prevent confusion for callers and future maintenance.
"""
Create a grid-point mask for lat/lon coordinates in `da` indicating which
points are interior to the convex hull of the lat/lon coordinates of
`da_ref`.
Parameters
----------
ds : xarray.Dataset
The dataset for which to create the mask.
ds_reference : xarray.Dataset
The reference dataset from which to create the convex hull of the coordinates.
Returns
-------
xarray.DataArray
A boolean mask indicating which points in `ds_reference` are interior
to the convex hull.
xarray.Dataset
A dataset containing lat lon coordinates for points in `ds` making up
the convex hull.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
mllam_data_prep/ops/cropping.py
Outdated
| @@ -336,7 +336,7 @@ def crop_with_convex_hull( | |||
| raise Exception( | |||
| "With no margin and exclude_interior=False, all points would be excluded." | |||
There was a problem hiding this comment.
The exception message refers to exclude_interior=False, but the function parameter is include_interior_points. This makes the error hard to interpret; consider rewording the message to match the actual parameter name and condition that triggers the exception.
| "With no margin and exclude_interior=False, all points would be excluded." | |
| "With no margin and include_interior_points=False, all points would be excluded." |
CHANGELOG.md
Outdated
| - consider full time information for derived calculation of TOA radiation [\#84](https://github.com/mllam/mllam-data-prep/pull/84) @observingClouds | ||
|
|
||
| ### Fixes | ||
| - fix `crop_with_convex_hull` crash when `margin_thickness == 0.0` due to unpack of tuple returned by `create_convex_hull_mask` [\#99](https://github.com/mllam/mllam-data-prep/issues/99) @RajdeepKushwaha5 |
There was a problem hiding this comment.
The changelog entry says the crash happened "due to unpack of tuple"; it was actually caused by not unpacking the tuple returned by create_convex_hull_mask. Rewording this line would make the release note accurate.
| - fix `crop_with_convex_hull` crash when `margin_thickness == 0.0` due to unpack of tuple returned by `create_convex_hull_mask` [\#99](https://github.com/mllam/mllam-data-prep/issues/99) @RajdeepKushwaha5 | |
| - fix `crop_with_convex_hull` crash when `margin_thickness == 0.0` caused by not unpacking the tuple returned by `create_convex_hull_mask` [\#99](https://github.com/mllam/mllam-data-prep/issues/99) @RajdeepKushwaha5 |
| tmpdir = tempfile.TemporaryDirectory() | ||
| domain_size = 500 * 1.0e3 | ||
| N = 50 | ||
| config_lam = testdata.create_input_datasets_and_config( | ||
| identifier="lam", | ||
| data_categories=["state"], | ||
| tmpdir=tmpdir, | ||
| xlim=[-domain_size / 2.0, domain_size / 2.0], | ||
| ylim=[-domain_size / 2.0, domain_size / 2.0], | ||
| nx=N, | ||
| ny=N, | ||
| add_latlon=True, | ||
| ) | ||
| config_global = testdata.create_input_datasets_and_config( | ||
| identifier="global", | ||
| data_categories=["state"], | ||
| tmpdir=tmpdir, | ||
| xlim=[-domain_size, domain_size], | ||
| ylim=[-domain_size, domain_size], | ||
| nx=N // 2, | ||
| ny=N // 2, | ||
| add_latlon=True, | ||
| ) | ||
|
|
||
| ds_lam = mdp.create_dataset(config=config_lam) | ||
| ds_global = mdp.create_dataset(config=config_global) | ||
|
|
||
| # this previously crashed with AttributeError: 'tuple' object has no | ||
| # attribute 'dims' because create_convex_hull_mask returns a tuple | ||
| ds_cropped = cropping.crop_with_convex_hull( | ||
| ds=ds_global, | ||
| ds_reference=ds_lam, | ||
| margin_thickness=0.0, | ||
| include_interior_points=True, | ||
| ) | ||
|
|
||
| assert isinstance(ds_cropped, xr.Dataset) | ||
| # with zero margin and interior points included, the result should have | ||
| # fewer points than the full global domain | ||
| assert ds_cropped.grid_index.size < ds_global.grid_index.size | ||
| assert ds_cropped.grid_index.size > 0 | ||
|
|
||
| # also test the return_mask path | ||
| ds_cropped_2, da_mask = cropping.crop_with_convex_hull( | ||
| ds=ds_global, | ||
| ds_reference=ds_lam, | ||
| margin_thickness=0.0, | ||
| include_interior_points=True, | ||
| return_mask=True, | ||
| ) | ||
| assert isinstance(da_mask, xr.DataArray) | ||
| assert da_mask.dtype == bool |
There was a problem hiding this comment.
This test creates a TemporaryDirectory() but never explicitly closes it. Using a context manager (or pytest’s tmp_path fixture) would ensure cleanup even if the test fails and avoids leaking temp directories on some platforms.
| tmpdir = tempfile.TemporaryDirectory() | |
| domain_size = 500 * 1.0e3 | |
| N = 50 | |
| config_lam = testdata.create_input_datasets_and_config( | |
| identifier="lam", | |
| data_categories=["state"], | |
| tmpdir=tmpdir, | |
| xlim=[-domain_size / 2.0, domain_size / 2.0], | |
| ylim=[-domain_size / 2.0, domain_size / 2.0], | |
| nx=N, | |
| ny=N, | |
| add_latlon=True, | |
| ) | |
| config_global = testdata.create_input_datasets_and_config( | |
| identifier="global", | |
| data_categories=["state"], | |
| tmpdir=tmpdir, | |
| xlim=[-domain_size, domain_size], | |
| ylim=[-domain_size, domain_size], | |
| nx=N // 2, | |
| ny=N // 2, | |
| add_latlon=True, | |
| ) | |
| ds_lam = mdp.create_dataset(config=config_lam) | |
| ds_global = mdp.create_dataset(config=config_global) | |
| # this previously crashed with AttributeError: 'tuple' object has no | |
| # attribute 'dims' because create_convex_hull_mask returns a tuple | |
| ds_cropped = cropping.crop_with_convex_hull( | |
| ds=ds_global, | |
| ds_reference=ds_lam, | |
| margin_thickness=0.0, | |
| include_interior_points=True, | |
| ) | |
| assert isinstance(ds_cropped, xr.Dataset) | |
| # with zero margin and interior points included, the result should have | |
| # fewer points than the full global domain | |
| assert ds_cropped.grid_index.size < ds_global.grid_index.size | |
| assert ds_cropped.grid_index.size > 0 | |
| # also test the return_mask path | |
| ds_cropped_2, da_mask = cropping.crop_with_convex_hull( | |
| ds=ds_global, | |
| ds_reference=ds_lam, | |
| margin_thickness=0.0, | |
| include_interior_points=True, | |
| return_mask=True, | |
| ) | |
| assert isinstance(da_mask, xr.DataArray) | |
| assert da_mask.dtype == bool | |
| with tempfile.TemporaryDirectory() as tmpdir: | |
| domain_size = 500 * 1.0e3 | |
| N = 50 | |
| config_lam = testdata.create_input_datasets_and_config( | |
| identifier="lam", | |
| data_categories=["state"], | |
| tmpdir=tmpdir, | |
| xlim=[-domain_size / 2.0, domain_size / 2.0], | |
| ylim=[-domain_size / 2.0, domain_size / 2.0], | |
| nx=N, | |
| ny=N, | |
| add_latlon=True, | |
| ) | |
| config_global = testdata.create_input_datasets_and_config( | |
| identifier="global", | |
| data_categories=["state"], | |
| tmpdir=tmpdir, | |
| xlim=[-domain_size, domain_size], | |
| ylim=[-domain_size, domain_size], | |
| nx=N // 2, | |
| ny=N // 2, | |
| add_latlon=True, | |
| add_latlon=True, | |
| ) | |
| ds_lam = mdp.create_dataset(config=config_lam) | |
| ds_global = mdp.create_dataset(config=config_global) | |
| # this previously crashed with AttributeError: 'tuple' object has no | |
| # attribute 'dims' because create_convex_hull_mask returns a tuple | |
| ds_cropped = cropping.crop_with_convex_hull( | |
| ds=ds_global, | |
| ds_reference=ds_lam, | |
| margin_thickness=0.0, | |
| include_interior_points=True, | |
| ) | |
| assert isinstance(ds_cropped, xr.Dataset) | |
| # with zero margin and interior points included, the result should have | |
| # fewer points than the full global domain | |
| assert ds_cropped.grid_index.size < ds_global.grid_index.size | |
| assert ds_cropped.grid_index.size > 0 | |
| # also test the return_mask path | |
| ds_cropped_2, da_mask = cropping.crop_with_convex_hull( | |
| ds=ds_global, | |
| ds_reference=ds_lam, | |
| margin_thickness=0.0, | |
| include_interior_points=True, | |
| return_mask=True, | |
| ) | |
| assert isinstance(da_mask, xr.DataArray) | |
| assert da_mask.dtype == bool |
tests/test_convex_hull_cropping.py
Outdated
| assert ds_cropped.grid_index.size > 0 | ||
|
|
||
| # also test the return_mask path | ||
| ds_cropped_2, da_mask = cropping.crop_with_convex_hull( |
There was a problem hiding this comment.
ds_cropped_2 is assigned but never used; with the repo’s flake8 configuration (select = ... F ...), this will raise F841 (local variable assigned but never used) and fail linting. If you only need the mask, consider unpacking into _ instead.
| ds_cropped_2, da_mask = cropping.crop_with_convex_hull( | |
| _, da_mask = cropping.crop_with_convex_hull( |
create_convex_hull_mask() returns a tuple (DataArray, Dataset), but the margin_thickness==0.0 branch in crop_with_convex_hull() assigned the full tuple to da_mask without unpacking. This caused an AttributeError when _mask_with_common_dim() tried to access da_mask.dims. Fix: unpack the tuple with da_mask, _ = create_convex_hull_mask(...). Also corrected the return type annotation on create_convex_hull_mask() from -> xr.DataArray to -> Tuple[xr.DataArray, xr.Dataset]. Closes mllam#99
81acf34 to
a36a844
Compare
Describe your changes
create_convex_hull_mask()returns a tuple(da_interior_mask, chull_lat_lons), but themargin_thickness == 0.0branch incrop_with_convex_hull()(line 339) assigned the full tuple toda_maskwithout unpacking it. When_mask_with_common_dim()subsequently tried to accessda_mask.dims, it raised anAttributeErrorbecause tuples have no.dimsattribute.Changes:
cropping.pyline 339 — unpack the tuple:da_mask, _ = create_convex_hull_mask(...)cropping.pyline 31 — corrected the return type annotation oncreate_convex_hull_mask()from-> xr.DataArrayto-> Tuple[xr.DataArray, xr.Dataset]to match its actual return value.test_convex_hull_cropping.py— addedtest_crop_with_zero_margin_thicknessregression test covering both the default path (include_interior_points=True) and thereturn_mask=Truepath.[unreleased] > Fixes.No new dependencies are required.
Issue Link
Closes #99
Type of change
Checklist before requesting a review
pullwith--rebaseoption if possible).Checklist for reviewers
Each PR comes with its own improvements and flaws. The reviewer should check the following:
Author checklist after completed review
reflecting type of change (add section where missing):
Checklist for assignee