From 84fa2bcb7a91874c0e7fd0a4ef9e707326d9181c Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Tue, 3 Nov 2026 08:12:47 -0500 Subject: [PATCH] fix(mobile): clip occupancy updates to grid bounds --- src/roboticstoolbox/mobile/OccGrid.py | 8 +++++-- tests/test_mobile_maps.py | 31 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/roboticstoolbox/mobile/OccGrid.py b/src/roboticstoolbox/mobile/OccGrid.py index 8b490acb2..1c7fb9484 100644 --- a/src/roboticstoolbox/mobile/OccGrid.py +++ b/src/roboticstoolbox/mobile/OccGrid.py @@ -285,11 +285,15 @@ def set(self, region: ArrayLike4, value: float | bool) -> None: :param value: value to set cells to The cells that contain the corners of the region, and all those between - them, are set. + them, are set. Only cells inside the grid are changed; a region + entirely outside the grid has no effect. """ bl = self.w2g([region[0], region[2]]) tr = self.w2g([region[1], region[3]]) - self.grid[bl[1] : tr[1] + 1, bl[0] : tr[0] + 1] = value + bl = np.maximum(bl, 0) + tr = np.minimum(tr, np.array(self.grid.shape[::-1]) - 1) + if np.all(bl <= tr): + self.grid[bl[1] : tr[1] + 1, bl[0] : tr[0] + 1] = value def grid_to_world(self, p: ArrayLike2) -> R2: """ diff --git a/tests/test_mobile_maps.py b/tests/test_mobile_maps.py index c5c31ffdd..60c249f44 100644 --- a/tests/test_mobile_maps.py +++ b/tests/test_mobile_maps.py @@ -11,11 +11,42 @@ import matplotlib.pyplot as plt import numpy as np import numpy.testing as nt +import pytest from spatialmath import Polygon2 from roboticstoolbox import BinaryOccupancyGrid, LandmarkMap, PolygonMap +@pytest.mark.parametrize("cellsize, origin", [(1.0, (0.0, 0.0)), (0.5, (10.0, -3.0))]) +@pytest.mark.parametrize( + "bounds", + [ + (-2, 2, -1, 1), + (4, 8, 2, 5), + (-5, -3, 1, 2), + (7, 9, 1, 2), + (1, 2, -4, -2), + (1, 2, 5, 7), + ], +) +def test_set_region_clips_to_grid( + cellsize: float, origin: tuple[float, float], bounds: tuple[int, int, int, int] +) -> None: + grid = BinaryOccupancyGrid(np.zeros((4, 6)), cellsize=cellsize, origin=origin) + region = np.array(bounds) * cellsize + np.repeat(origin, 2) + columns, rows = np.meshgrid(np.arange(6), np.arange(4)) + expected = ( + (columns >= bounds[0]) + & (columns <= bounds[1]) + & (rows >= bounds[2]) + & (rows <= bounds[3]) + ) + + grid.set(region, True) + + nt.assert_array_equal(grid.grid, expected) + + class TestOccupancyGrid(unittest.TestCase): def test_unknown_argument_is_an_error(self): # ``size`` used to be silently ignored, the argument is ``cellsize``