Skip to content

Commit 0146f22

Browse files
Add option to directly pass the bounding box as a tuple
1 parent 06afd01 commit 0146f22

2 files changed

Lines changed: 74 additions & 7 deletions

File tree

sarxarray/utils.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,17 @@ def _compute_coherence(numerator, denominator):
155155
return coherence
156156

157157

158-
def crop(data: xr.Dataset | xr.DataArray, geom: sg.Polygon) -> xr.Dataset:
158+
def crop(data: xr.Dataset | xr.DataArray, geom: sg.Polygon | tuple) -> xr.Dataset:
159159
"""Crop a radar image or stack of radar images to the bounding box of a polygon.
160160
161161
Parameters
162162
----------
163163
data: xr.Dataset | xr.DataArray
164164
The dataset or data array to be cropped in azimuth and range
165-
geom: sg.Polygon
165+
geom: sg.Polygon | tuple
166166
shapely.geometry.Polygon in radar coordinates of the area that should be
167-
kept, in [azimuth, range] format
167+
kept, in [azimuth, range] format, OR a tuple of the bounding box of the crop in
168+
(min_azimuth, min_range, max_azimuth, max_range) format
168169
169170
Returns
170171
-------
@@ -174,12 +175,31 @@ def crop(data: xr.Dataset | xr.DataArray, geom: sg.Polygon) -> xr.Dataset:
174175
Raises
175176
------
176177
ValueError
177-
If the azimuth or range coordinate does not exist in `data`
178+
- If the azimuth or range coordinate does not exist in `data`
179+
- If `geom` is not `shapely.geometry.Polygon` or `tuple`
180+
181+
AssertionError
182+
- When a tuple is passed to `geom` that falls in one of three categories:
183+
- The tuple does not have exactly 4 entries
184+
- The minimum azimuth coordinate is larger than or equal
185+
to the maximum azimuth coordinate
186+
- The minimum range coordinate is larger than or equal
187+
to the maximum range coordinate
178188
"""
179189
if not {"azimuth", "range"}.issubset(data.dims):
180190
raise ValueError("The data must have azimuth and range dimensions.")
181191

182-
bounding_box = geom.bounds # returns (min_az, min_r, max_az, max_r)
192+
if isinstance(geom, sg.Polygon):
193+
bounding_box = geom.bounds # returns (min_az, min_r, max_az, max_r)
194+
elif isinstance(geom, tuple):
195+
assert len(geom) == 4, f"geom as tuple must have four entries, got {len(geom)}"
196+
assert geom[0] < geom[2], f"geom Azimuth wrong: min {geom[0]} >= max {geom[2]}"
197+
assert geom[1] < geom[3], f"geom Range wrong: min {geom[1]} >= max {geom[3]}"
198+
bounding_box = geom
199+
else:
200+
raise ValueError(
201+
f"geom must be tuple or shapely.geometry.Polygon, is {type(geom)}."
202+
)
183203
data = data.sel(
184204
azimuth=range(int(bounding_box[0]), int(np.ceil(bounding_box[2]))+1),
185205
range=range(int(bounding_box[1]), int(np.ceil(bounding_box[3]))+1)

tests/test_utils.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,14 @@ def crop_geometry():
3838
[606, 1408],
3939
[606, 1409],
4040
[603, 1409],
41-
[602, 1405]
41+
[601.9, 1405]
4242
])
4343

44+
# Create a bounding box for cropping
45+
@pytest.fixture
46+
def crop_geometry_bbox():
47+
return (601.9, 1405, 606, 1409)
48+
4449
# this class tests multi_look with dataarray. For testing with dataset, see
4550
# test_stack.py
4651
class TestUtilsMultiLook:
@@ -292,17 +297,59 @@ def test_complex_coherence_bad_args(
292297

293298

294299
class TestUtilsCrop:
295-
def test_crop(self, synthetic_dataarray, crop_geometry):
300+
def test_crop_poly(self, synthetic_dataarray, crop_geometry):
296301
da = synthetic_dataarray
297302
geom = crop_geometry
298303
da_crop = crop(da, geom)
299304
assert da_crop.azimuth.size == 6
300305
assert da_crop.range.size == 5
301306
assert da_crop.time.size == da.time.size
302307

308+
def test_crop_bbox(self, synthetic_dataarray, crop_geometry_bbox):
309+
da = synthetic_dataarray
310+
geom = crop_geometry_bbox
311+
da_crop = crop(da, geom)
312+
assert da_crop.azimuth.size == 6
313+
assert da_crop.range.size == 5
314+
assert da_crop.time.size == da.time.size
315+
303316
def test_crop_wrong_dimname(self, synthetic_dataarray, crop_geometry):
304317
da = synthetic_dataarray
305318
da = da.rename({"azimuth": "az"}) # rename azimuth to a wrong name
306319
geom = crop_geometry
307320
with pytest.raises(ValueError):
308321
_ = crop(da, geom)
322+
323+
def test_crop_bbox_wrong_length(self, synthetic_dataarray, crop_geometry_bbox):
324+
da = synthetic_dataarray
325+
geom = crop_geometry_bbox[:3]
326+
with pytest.raises(AssertionError):
327+
_ = crop(da, geom)
328+
329+
def test_crop_bbox_wrong_az(self, synthetic_dataarray, crop_geometry_bbox):
330+
da = synthetic_dataarray
331+
geom = ( # swap min and max azimuth
332+
crop_geometry_bbox[2],
333+
crop_geometry_bbox[1],
334+
crop_geometry_bbox[0],
335+
crop_geometry_bbox[3]
336+
)
337+
with pytest.raises(AssertionError):
338+
_ = crop(da, geom)
339+
340+
def test_crop_bbox_wrong_r(self, synthetic_dataarray, crop_geometry_bbox):
341+
da = synthetic_dataarray
342+
geom = ( # swap min and max range
343+
crop_geometry_bbox[0],
344+
crop_geometry_bbox[3],
345+
crop_geometry_bbox[2],
346+
crop_geometry_bbox[1]
347+
)
348+
with pytest.raises(AssertionError):
349+
_ = crop(da, geom)
350+
351+
def test_crop_bbox_wrong_geom_type(self, synthetic_dataarray, crop_geometry_bbox):
352+
da = synthetic_dataarray
353+
geom = list(crop_geometry_bbox)
354+
with pytest.raises(ValueError):
355+
_ = crop(da, geom)

0 commit comments

Comments
 (0)