Skip to content

depth

depth #

The depth-map file scalar; a loaded Depth is the file path.

Reader methods (depth.to_array(), depth.point_cloud(pose)) read self via OpenEXR. A depth map is a non-core extension scalar (dotted reverse-DNS name), so its schema self-describes with kind + extensions and a reader without an EXR decoder can still store or skip the bytes. The format is single-file 32-bit float OpenEXR: the R channel is metric depth in metres, and an optional A channel is per-pixel confidence in [0, 1].

numpy/OpenEXR are lazy-imported so importing the type system stays cheap.

Depth #

Bases: FileScalar

A per-pixel metric depth map as 32-bit float OpenEXR.

A loaded value is its file Path plus the methods below, which read self via OpenEXR. The R channel holds depth in metres; an optional A channel holds per-pixel confidence in [0, 1].

Source code in capturegraph-lib/capturegraph/types/scalars/geometry/depth.py
class Depth(FileScalar, name="edu.cornell.depth", extensions=("exr",)):
    """A per-pixel metric depth map as 32-bit float OpenEXR.

    A loaded value is its file ``Path`` plus the methods below, which read
    ``self`` via OpenEXR. The ``R`` channel holds depth in metres; an optional
    ``A`` channel holds per-pixel confidence in ``[0, 1]``.
    """

    __slots__ = ()

    def to_array(self) -> "np.ndarray":
        """The depth map as an ``H×W`` float array of metres.

        Raises:
            OSError: If the EXR carries no ``R`` (depth) channel.
        """
        channels = _read_channels(str(self))
        if "R" not in channels:
            raise OSError(f"Depth EXR has no R channel: {self}")
        return channels["R"]

    def confidence(self) -> "np.ndarray | None":
        """The per-pixel confidence as an ``H×W`` float array in ``[0, 1]``.

        ``None`` when the depth map carries no confidence channel.
        """
        return _read_channels(str(self)).get("A")

    def point_cloud(self, pose: "Pose") -> "np.ndarray":
        """Back-project the depth map into an ``N×3`` array of camera-space points.

        Points are in metres, using the pinhole intrinsics carried by ``pose``.

        Raises:
            ValueError: If ``pose`` carries no intrinsics
                (``focal_x``/``focal_y``/``center_x``/``center_y``).
        """
        import numpy as np

        if None in (pose.focal_x, pose.focal_y, pose.center_x, pose.center_y):
            raise ValueError(
                "point_cloud requires pose intrinsics (focal_x, focal_y, center_x, center_y)"
            )
        depth = self.to_array()
        height, width = depth.shape[:2]
        scale_x = width / (pose.reference_width or width)
        scale_y = height / (pose.reference_height or height)
        focal_x = pose.focal_x * scale_x  # type: ignore[operator]  # guarded above
        focal_y = pose.focal_y * scale_y  # type: ignore[operator]
        center_x = pose.center_x * scale_x  # type: ignore[operator]
        center_y = pose.center_y * scale_y  # type: ignore[operator]
        rows, cols = np.indices((height, width), dtype=np.float32)
        z = depth.astype(np.float32)
        x = (cols - center_x) * z / focal_x
        y = (rows - center_y) * z / focal_y
        points = np.stack((x, y, z), axis=-1).reshape(-1, 3)
        return points[np.isfinite(points).all(axis=1) & (points[:, 2] > 0)]

confidence() #

The per-pixel confidence as an H×W float array in [0, 1].

None when the depth map carries no confidence channel.

Source code in capturegraph-lib/capturegraph/types/scalars/geometry/depth.py
def confidence(self) -> "np.ndarray | None":
    """The per-pixel confidence as an ``H×W`` float array in ``[0, 1]``.

    ``None`` when the depth map carries no confidence channel.
    """
    return _read_channels(str(self)).get("A")

point_cloud(pose) #

Back-project the depth map into an N×3 array of camera-space points.

Points are in metres, using the pinhole intrinsics carried by pose.

Raises:

Type Description
ValueError

If pose carries no intrinsics (focal_x/focal_y/center_x/center_y).

Source code in capturegraph-lib/capturegraph/types/scalars/geometry/depth.py
def point_cloud(self, pose: "Pose") -> "np.ndarray":
    """Back-project the depth map into an ``N×3`` array of camera-space points.

    Points are in metres, using the pinhole intrinsics carried by ``pose``.

    Raises:
        ValueError: If ``pose`` carries no intrinsics
            (``focal_x``/``focal_y``/``center_x``/``center_y``).
    """
    import numpy as np

    if None in (pose.focal_x, pose.focal_y, pose.center_x, pose.center_y):
        raise ValueError(
            "point_cloud requires pose intrinsics (focal_x, focal_y, center_x, center_y)"
        )
    depth = self.to_array()
    height, width = depth.shape[:2]
    scale_x = width / (pose.reference_width or width)
    scale_y = height / (pose.reference_height or height)
    focal_x = pose.focal_x * scale_x  # type: ignore[operator]  # guarded above
    focal_y = pose.focal_y * scale_y  # type: ignore[operator]
    center_x = pose.center_x * scale_x  # type: ignore[operator]
    center_y = pose.center_y * scale_y  # type: ignore[operator]
    rows, cols = np.indices((height, width), dtype=np.float32)
    z = depth.astype(np.float32)
    x = (cols - center_x) * z / focal_x
    y = (rows - center_y) * z / focal_y
    points = np.stack((x, y, z), axis=-1).reshape(-1, 3)
    return points[np.isfinite(points).all(axis=1) & (points[:, 2] > 0)]

to_array() #

The depth map as an H×W float array of metres.

Raises:

Type Description
OSError

If the EXR carries no R (depth) channel.

Source code in capturegraph-lib/capturegraph/types/scalars/geometry/depth.py
def to_array(self) -> "np.ndarray":
    """The depth map as an ``H×W`` float array of metres.

    Raises:
        OSError: If the EXR carries no ``R`` (depth) channel.
    """
    channels = _read_channels(str(self))
    if "R" not in channels:
        raise OSError(f"Depth EXR has no R channel: {self}")
    return channels["R"]