Argus / argus /utils /data_io.py
lixi042
Initial commit: Argus metric panoramic 3D reconstruction demo
510e990
Raw
History Blame Contribute Delete
4.28 kB
# Copyright 2026 Realsee. All rights reserved.
# Licensed under the Apache License, Version 2.0.
"""
Shared I/O and preprocessing utilities for panoramic image data.
These functions are used by both evaluation and training pipelines.
"""
import os
os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "1"
import cv2
import numpy as np
def read_image_cv2_360(path: str, rgb: bool = True, shape=(560, 280)) -> np.ndarray:
"""Read and resize a 360 panorama image.
Args:
path: Path to the image file.
rgb: If True, convert BGR to RGB (default: True).
shape: Target (width, height) tuple.
Returns:
Image as numpy array with shape (H, W, 3).
"""
img = cv2.imread(path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
if img.shape[1] != shape[0]:
img = cv2.resize(img, shape, interpolation=cv2.INTER_AREA)
return img
def read_depth_360(path: str, depth_scale=5000.0, shape=(560, 280)) -> np.ndarray:
"""Read and normalize a 360 depth map.
Args:
path: Path to the depth image file.
depth_scale: Scale factor to convert raw depth to meters.
shape: Target (width, height) tuple.
Returns:
Depth map as float32 numpy array with shape (H, W).
"""
d = cv2.imread(path, cv2.IMREAD_UNCHANGED)
if d.shape[1] != shape[0]:
d = cv2.resize(d, shape, interpolation=cv2.INTER_NEAREST)
d = d.astype(np.float32) / depth_scale
return d
def random_rotate_theta(W=560, max_shift_percent=0.5):
"""Generate a random rotation angle for panorama augmentation.
Args:
W: Panorama width in pixels.
max_shift_percent: Maximum horizontal shift as fraction of width.
Returns:
Rotation angle in radians.
"""
max_shift = int(W * max_shift_percent)
shift_pixels = np.random.randint(-max_shift, max_shift + 1)
theta = (shift_pixels * 2 * np.pi) / W
return theta
def rotate_y(theta):
"""Create a 3x3 rotation matrix around the Y-axis.
Args:
theta: Rotation angle in radians.
Returns:
3x3 rotation matrix as float64 numpy array.
"""
cos_theta = np.cos(theta)
sin_theta = np.sin(theta)
return np.array(
[[cos_theta, 0, -sin_theta], [0, 1, 0], [sin_theta, 0, cos_theta]],
dtype=np.float64,
)
def pano_depth_to_points(depth_map, pano_shape=(560, 280), crop=True, crop_ratio=0.15):
"""Convert a panorama depth map to 3D point cloud.
Args:
depth_map: 2D depth map (H, W) or flattened array.
pano_shape: Original panorama (width, height) tuple.
crop: Whether the depth map has been vertically cropped.
crop_ratio: Crop ratio applied to top and bottom.
Returns:
Point cloud as numpy array with shape (N, 3).
"""
w, h = pano_shape
if not crop:
px = np.tile(np.arange(w), int(h))
py = np.arange(0, int(h)).repeat(w)
else:
px = np.tile(np.arange(w), int(h * (1 - 2 * crop_ratio)))
py = np.arange(int(crop_ratio * h), int((1 - crop_ratio) * h)).repeat(w)
dist = depth_map.reshape(-1)
lat = (py / h - 0.5) * np.pi
long = (px / w - 0.5) * np.pi * 2.0
y = dist * np.sin(lat)
tmp = dist * np.cos(lat)
x = tmp * np.sin(long)
z = tmp * np.cos(long)
point_map = np.concatenate([i.reshape(-1, 1) for i in (x, y, z)], axis=-1)
return point_map # (h*w, 3)
def crop_panorama(pano, crop_ratio=0.15):
"""Crop the top and bottom of a panorama by a given ratio.
Args:
pano: Input panorama array with shape (H, W, ...).
crop_ratio: Fraction to crop from top and bottom.
Returns:
Cropped panorama.
"""
H, W = pano.shape[:2]
crop_H_top = int(crop_ratio * H)
crop_H_bottom = H - int(crop_ratio * H)
crop_pano = pano[crop_H_top:crop_H_bottom, ...]
return crop_pano
def rotate_panorama(panorama, theta):
"""Horizontally rotate a panorama by shifting pixels.
Args:
panorama: Input panorama array with shape (H, W, ...).
theta: Rotation angle in radians.
Returns:
Shifted panorama.
"""
H, W = panorama.shape[:2]
shift_pixels = int((theta * W) / (2 * np.pi))
shifted = np.roll(panorama, shift_pixels, axis=1)
return shifted