bdck commited on
Commit
1074f06
·
verified ·
1 Parent(s): 09d788a

Upload learn_region_grow/preprocess.py

Browse files
Files changed (1) hide show
  1. learn_region_grow/preprocess.py +169 -0
learn_region_grow/preprocess.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Preprocessing: voxel equalization, normal / curvature estimation, feature vector."""
2
+
3
+ import numpy as np
4
+ from typing import Tuple, Optional
5
+
6
+
7
+ def voxel_equalize(xyz: np.ndarray, resolution: float = 0.1) -> Tuple[np.ndarray, np.ndarray, dict]:
8
+ """
9
+ Voxelize a point cloud at given resolution, keeping one representative per voxel.
10
+
11
+ This step is critical: it removes density bias so that highly sampled regions
12
+ (e.g. close to a scanner) do not dominate the neighborhood queries later.
13
+
14
+ Parameters
15
+ ----------
16
+ xyz : np.ndarray, shape (N, 3)
17
+ Point coordinates.
18
+ resolution : float
19
+ Voxel grid size in the same unit as xyz (default 0.1 m).
20
+
21
+ Returns
22
+ -------
23
+ eq_xyz : np.ndarray, shape (M, 3)
24
+ Equalized point coordinates (M <= N).
25
+ eq_idx : np.ndarray, shape (M,)
26
+ Indices into the original array of the kept representative.
27
+ voxel_map : dict
28
+ Mapping from voxel key (tuple of ints) -> representative index.
29
+ """
30
+ voxel_map = {}
31
+ eq_idx = []
32
+ for i, pt in enumerate(xyz):
33
+ key = tuple(np.round(pt / resolution).astype(int))
34
+ if key not in voxel_map:
35
+ eq_idx.append(i)
36
+ voxel_map[key] = len(eq_idx) - 1 # index into the EQUALIZED array
37
+ eq_idx = np.array(eq_idx, dtype=np.int64)
38
+ return xyz[eq_idx], eq_idx, voxel_map
39
+
40
+
41
+ def compute_normals_and_curvature(xyz: np.ndarray, resolution: float = 0.1,
42
+ k: int = 30) -> Tuple[np.ndarray, np.ndarray]:
43
+ """
44
+ Estimate per-point normals and curvature using PCA on local neighborhoods.
45
+
46
+ The original LRGNet implementation uses a 3x3x3 voxel window search.
47
+ Here we offer both the exact voxel-window method and a k-NN fallback
48
+ (which is more robust for sparse clouds).
49
+
50
+ Parameters
51
+ ----------
52
+ xyz : np.ndarray, shape (N, 3)
53
+ resolution : float
54
+ Voxel size used to build the grid for fast neighbor lookup.
55
+ k : int
56
+ Minimum number of neighbors. If the voxel-window yields fewer,
57
+ we fall back to k-NN.
58
+
59
+ Returns
60
+ -------
61
+ normals : np.ndarray, shape (N, 3), float32
62
+ Absolute-value unit normals (always positive, matching original code).
63
+ curvature : np.ndarray, shape (N,), float32
64
+ Curvature = smallest_eigenvalue / sum_eigenvalues, in [0, 1].
65
+ """
66
+ n = len(xyz)
67
+ normals = np.zeros((n, 3), dtype=np.float32)
68
+ curvature = np.zeros(n, dtype=np.float32)
69
+
70
+ # Build voxel grid for fast lookups
71
+ voxel_grid = {}
72
+ for i, pt in enumerate(xyz):
73
+ key = tuple(np.round(pt / resolution).astype(int))
74
+ if key not in voxel_grid:
75
+ voxel_grid[key] = []
76
+ voxel_grid[key].append(i)
77
+
78
+ for i, pt in enumerate(xyz):
79
+ voxel = np.round(pt / resolution).astype(int)
80
+ # Search 3x3x3 voxel window (original method)
81
+ neighbors = []
82
+ for dx in (-1, 0, 1):
83
+ for dy in (-1, 0, 1):
84
+ for dz in (-1, 0, 1):
85
+ key = (voxel[0]+dx, voxel[1]+dy, voxel[2]+dz)
86
+ if key in voxel_grid:
87
+ neighbors.extend(voxel_grid[key])
88
+ neighbors = np.array(neighbors, dtype=np.int64)
89
+
90
+ # Fallback to k-NN if too sparse
91
+ if len(neighbors) < k:
92
+ dists = np.linalg.norm(xyz - pt, axis=1)
93
+ neighbors = np.argsort(dists)[:k]
94
+
95
+ # Remove self
96
+ neighbors = neighbors[neighbors != i]
97
+ if len(neighbors) < 3:
98
+ normals[i] = [0, 0, 1]
99
+ curvature[i] = 0.0
100
+ continue
101
+
102
+ local_pts = xyz[neighbors] - pt
103
+ cov = local_pts.T @ local_pts / len(neighbors)
104
+ U, S, Vt = np.linalg.svd(cov)
105
+ # Normal = smallest eigenvector
106
+ normal = np.abs(Vt[2])
107
+ normals[i] = normal
108
+ curv = S[2] / (S[0] + S[1] + S[2] + 1e-8)
109
+ curvature[i] = curv
110
+
111
+ return normals, curvature
112
+
113
+
114
+ def build_feature_vector(xyz: np.ndarray, rgb: Optional[np.ndarray],
115
+ normals: np.ndarray, curvature: np.ndarray,
116
+ room_bbox: Optional[np.ndarray] = None) -> np.ndarray:
117
+ """
118
+ Build the 13-channel feature vector used by LrgNet.
119
+
120
+ Feature layout (index: description):
121
+ 0-2 : XYZ coordinates (absolute, in meters)
122
+ 3-5 : Room-normalized coordinates (0..1 within scene bounding box)
123
+ 6-8 : RGB colors, normalized to [-0.5, +0.5]
124
+ 9-11: Normal vector (absolute value, always positive)
125
+ 12 : Curvature (0..1)
126
+
127
+ Parameters
128
+ ----------
129
+ xyz : np.ndarray, shape (N, 3)
130
+ rgb : np.ndarray, shape (N, 3), uint8 or None
131
+ normals : np.ndarray, shape (N, 3)
132
+ curvature : np.ndarray, shape (N,)
133
+ room_bbox : np.ndarray, shape (2, 3), optional
134
+ Min/max corner of the room bounding box. Computed from xyz if omitted.
135
+
136
+ Returns
137
+ -------
138
+ features : np.ndarray, shape (N, 13), float32
139
+ """
140
+ n = len(xyz)
141
+ features = np.zeros((n, 13), dtype=np.float32)
142
+
143
+ # Channels 0-2: raw XYZ
144
+ features[:, :3] = xyz
145
+
146
+ # Channels 3-5: room-normalized coordinates
147
+ if room_bbox is None:
148
+ mins = xyz.min(axis=0)
149
+ maxs = xyz.max(axis=0)
150
+ else:
151
+ mins, maxs = room_bbox[0], room_bbox[1]
152
+ span = maxs - mins
153
+ span[span == 0] = 1.0
154
+ features[:, 3:6] = (xyz - mins) / span
155
+
156
+ # Channels 6-8: RGB normalized [-0.5, 0.5]
157
+ if rgb is not None:
158
+ rgb_f = rgb.astype(np.float32)
159
+ features[:, 6:9] = rgb_f / 255.0 - 0.5
160
+ else:
161
+ features[:, 6:9] = 0.0
162
+
163
+ # Channels 9-11: normals (already absolute)
164
+ features[:, 9:12] = normals
165
+
166
+ # Channel 12: curvature
167
+ features[:, 12] = curvature
168
+
169
+ return features