File size: 4,283 Bytes
510e990
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# 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