Skip to content

OccupancyGrid

Represents a 2D grid mapping world cells to occupancy status, using the same FREE/OCCUPIED/UNKNOWN convention as ROS's nav_msgs/OccupancyGrid.

Reference semantics on construction

Construction does not defensively copy a contiguous input np.ndarray — the array is stored by reference. If you intend to mutate the source array after constructing the OccupancyGrid, pass arr.copy() explicitly. The data property always returns a defensive copy, so reads are safe either way.

Parameters

FieldTypeDescription
datanp.ndarray(H, W) int8 array of cell values (FREE, OCCUPIED, or UNKNOWN). Stored by reference, not copied — see the warning above.
resolutionfloatMeters per cell.
origin_xfloatWorld x-coordinate of cell (0, 0)'s lower-left corner. Defaults to 0.0.
origin_yfloatWorld y-coordinate of cell (0, 0)'s lower-left corner. Defaults to 0.0.
origin_yawfloatRotation of the grid relative to world axes, in radians. Defaults to 0.0. ROS represents this as a quaternion on info.origin.orientation; here it's stored as a plain yaw float instead.

Raises

ExceptionCondition
TypeErrordata is not an np.ndarray
ValueErrordata.dtype is not int8
ValueErrordata is not 2-D
ValueErrordata contains a value outside {UNKNOWN}[FREE, OCCUPIED] (i.e. outside {-1}[0, 100])

Constants

ConstantValueDescription
FREE0Cell is known to be unoccupied.
OCCUPIED100Cell is known to be occupied.
UNKNOWN-1Cell occupancy is unknown.

These are class-level constants, accessible as OccupancyGrid.FREE (or off any instance) without constructing a grid.

Attributes

AttributeTypeDescription
datanp.ndarrayDefensive copy of the underlying (H, W) int8 grid. Mutating the returned array does not affect the OccupancyGrid.
shapetuple[int, int](height, width) cell counts.
heightintGrid height in cells (shape[0]).
widthintGrid width in cells (shape[1]).
resolutionfloatMeters per cell.
origin_xfloatWorld x-coordinate of cell (0, 0)'s lower-left corner.
origin_yfloatWorld y-coordinate of cell (0, 0)'s lower-left corner.
origin_yawfloatGrid's rotation relative to world axes, in radians.

None of these attributes have setters — an OccupancyGrid is immutable after construction (unless you mutate the array you originally passed in by reference; see the warning above).

Methods

MethodDescription
OccupancyGrid.coerce(value)Returns value unchanged if it's already an OccupancyGrid; if it's a dict, calls OccupancyGrid(**value) (requires keys data/resolution, origin_x/origin_y/origin_yaw optional). Raises TypeError for any other input.

Operators

OperationBehavior
g == otherTrue only if other is also an OccupancyGrid with element-equal grid data and equal resolution/origin_x/origin_y/origin_yaw. False if other is an OccupancyGrid that differs in any of those. NotImplemented (effectively False) if other isn't an OccupancyGrid.
np.asarray(g)Works directly via __array__, returning the (H, W) int8 grid (metadata is not included). Always returns a copy; copy=False raises ValueError.
hash(g)Not supported — the grid array is stored by reference and can be mutated in place.

len(g) is not supported — OccupancyGrid doesn't define __len__.

Example

python
"""Demonstrates the Telekinesis OccupancyGrid datatype."""

import time

import numpy as np
from loguru import logger

from telekinesis import datatypes

def occupancy_grid_example():
    """Demonstrate creation with the FREE/OCCUPIED/UNKNOWN constants, access, and serialization."""

    # ======================= Create ============================================
    height, width = 20, 20

    data = np.full((height, width), datatypes.OccupancyGrid.UNKNOWN, dtype=np.int8)
    data[10, 5] = datatypes.OccupancyGrid.OCCUPIED
    data[10, 6] = datatypes.OccupancyGrid.FREE
    data[10, 7] = datatypes.OccupancyGrid.OCCUPIED

    grid = datatypes.OccupancyGrid(data, resolution=0.05, origin_x=-5.0, origin_y=-5.0, origin_yaw=0.0)

    logger.info(f"Created OccupancyGrid: {grid}")

    # ======================= Inspect ===========================================
    logger.info(
        f"shape={grid.shape}, height={grid.height}, width={grid.width}, "
        f"resolution={grid.resolution} m/cell"
    )
    logger.info(f"origin_x={grid.origin_x}, origin_y={grid.origin_y}, origin_yaw={grid.origin_yaw}")

    occupied = int(np.sum(grid.data == datatypes.OccupancyGrid.OCCUPIED))
    free = int(np.sum(grid.data == datatypes.OccupancyGrid.FREE))
    unknown = int(np.sum(grid.data == datatypes.OccupancyGrid.UNKNOWN))

    logger.info(f"occupied={occupied}, free={free}, unknown={unknown}")

    # ======================= Serialize / Deserialize ===========================
    start = time.perf_counter()
    serialized = datatypes.serialize(grid)
    serialization_ms = (time.perf_counter() - start) * 1000

    start = time.perf_counter()
    deserialized = datatypes.deserialize(serialized)["param_0"]
    deserialization_ms = (time.perf_counter() - start) * 1000

    assert grid == deserialized, "round-trip mismatch"

    logger.info(f"Deserialized OccupancyGrid: {deserialized}")
    logger.info(f"Round-trip successful: {grid == deserialized}")
    logger.info(f"Serialization time: {serialization_ms:.3f} ms")
    logger.info(f"Deserialization time: {deserialization_ms:.3f} ms")


if __name__ == "__main__":
    occupancy_grid_example()