Accélérer le ray-tracing (SVF, openness) ~10x via accumulation des tangentes
This commit is contained in:
@ -298,10 +298,18 @@ def _ray_trace_horizons_core(dem, rows, cols, res, n_dirs, max_dist, radii_m=Non
|
|||||||
recording the max upward angle (positive openness) and max downward angle
|
recording the max upward angle (positive openness) and max downward angle
|
||||||
(negative openness) reached at each radius checkpoint.
|
(negative openness) reached at each radius checkpoint.
|
||||||
|
|
||||||
Padding is done on CPU (numpy) to avoid GPU memory pressure and
|
Optimisation : on accumule la TANGENTE de l'angle (dz/dist) au lieu de
|
||||||
pre-compiled kernel mismatches (CUDA_ERROR_NO_BINARY_FOR_GPU on sm_89).
|
l'angle lui-même — atan étant strictement croissante, max(angles) =
|
||||||
The padded array is transferred to GPU once, then each direction is
|
atan(max(tangentes)). L'arctan (coûteuse, pleine image) n'est donc plus
|
||||||
processed and results are streamed back to CPU.
|
appliquée qu'aux checkpoints de rayon, pas à chaque pas de rayon.
|
||||||
|
Un seul couple de max cumulés est maintenu, snapshoté à chaque checkpoint
|
||||||
|
(les rayons étant emboîtés, chaque checkpoint réutilisait avant le même
|
||||||
|
calcul 3 fois). fmax ignore les NaN du padding : plus de nan_to_num/where.
|
||||||
|
|
||||||
|
Padding on CPU (numpy) to avoid GPU memory pressure and pre-compiled
|
||||||
|
kernel mismatches (CUDA_ERROR_NO_BINARY_FOR_GPU on sm_89). The padded
|
||||||
|
array is transferred to GPU once, then each direction is processed and
|
||||||
|
results are streamed back to CPU.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dem: CPU numpy array — filled DEM (no NaN), shape (rows, cols).
|
dem: CPU numpy array — filled DEM (no NaN), shape (rows, cols).
|
||||||
@ -338,6 +346,12 @@ def _ray_trace_horizons_core(dem, rows, cols, res, n_dirs, max_dist, radii_m=Non
|
|||||||
# Free the CPU copy — we don't need it anymore
|
# Free the CPU copy — we don't need it anymore
|
||||||
del padded_np
|
del padded_np
|
||||||
|
|
||||||
|
# Checkpoints triés par pas : (step, r_idx). Les snapshots sont pris quand
|
||||||
|
# le pas courant atteint le pas du checkpoint — les rayons ne dépassent
|
||||||
|
# donc pas le plus grand checkpoint demandé (équivalent au break d'avant).
|
||||||
|
checkpoints = sorted((radii_steps[r_idx], r_idx) for r_idx in range(n_radii))
|
||||||
|
last_step = checkpoints[-1][0]
|
||||||
|
|
||||||
# Process one direction at a time to limit GPU memory.
|
# Process one direction at a time to limit GPU memory.
|
||||||
# Store results as flat CPU arrays — transfer back to GPU at the end.
|
# Store results as flat CPU arrays — transfer back to GPU at the end.
|
||||||
pos_results = [None] * n_dirs
|
pos_results = [None] * n_dirs
|
||||||
@ -346,9 +360,9 @@ def _ray_trace_horizons_core(dem, rows, cols, res, n_dirs, max_dist, radii_m=Non
|
|||||||
for d_idx in range(n_dirs):
|
for d_idx in range(n_dirs):
|
||||||
ddx, ddy = dx_dir[d_idx], dy_dir[d_idx]
|
ddx, ddy = dx_dir[d_idx], dy_dir[d_idx]
|
||||||
|
|
||||||
# Pre-compute valid steps for this direction
|
# Pre-compute valid steps for this direction (jusqu'au dernier checkpoint)
|
||||||
valid_steps = []
|
valid_steps = []
|
||||||
for step in range(1, max_dist + 1):
|
for step in range(1, last_step + 1):
|
||||||
px = int(round(ddx * step))
|
px = int(round(ddx * step))
|
||||||
py = int(round(ddy * step))
|
py = int(round(ddy * step))
|
||||||
dist_m = math.sqrt((ddx * step * res) ** 2 + (ddy * step * res) ** 2)
|
dist_m = math.sqrt((ddx * step * res) ** 2 + (ddy * step * res) ** 2)
|
||||||
@ -356,11 +370,11 @@ def _ray_trace_horizons_core(dem, rows, cols, res, n_dirs, max_dist, radii_m=Non
|
|||||||
continue
|
continue
|
||||||
valid_steps.append((step, px, py, dist_m))
|
valid_steps.append((step, px, py, dist_m))
|
||||||
|
|
||||||
# Running max angles per radius
|
# Max cumulé des tangentes (float32 : moitié de VRAM vs float64)
|
||||||
running_pos = xp.zeros((n_radii, rows, cols))
|
running_pos = xp.zeros((rows, cols), dtype=np.float32)
|
||||||
running_neg = xp.zeros((n_radii, rows, cols))
|
running_neg = xp.zeros((rows, cols), dtype=np.float32)
|
||||||
# Track which radius checkpoints have been passed
|
snapshots = {}
|
||||||
radii_remaining = set(range(n_radii))
|
cp_queue = list(checkpoints)
|
||||||
|
|
||||||
for step, px, py, dist_m in valid_steps:
|
for step, px, py, dist_m in valid_steps:
|
||||||
# Slice from padded array, subtract original dem
|
# Slice from padded array, subtract original dem
|
||||||
@ -369,34 +383,33 @@ def _ray_trace_horizons_core(dem, rows, cols, res, n_dirs, max_dist, radii_m=Non
|
|||||||
elev_diff = view - dem
|
elev_diff = view - dem
|
||||||
del view # free slice reference
|
del view # free slice reference
|
||||||
|
|
||||||
# Positive: angle to terrain above viewer
|
# Tangentes des angles (positive : terrain au-dessus, négative : en
|
||||||
pos_angle = xp.arctan2(xp.maximum(elev_diff, 0), dist_m)
|
# dessous). fmax propage le non-NaN : le bord de padding ne compte
|
||||||
# Negative: angle to terrain below viewer
|
# pas, comme avec l'ancien where(isnan) — en une seule opération.
|
||||||
neg_angle = xp.arctan2(xp.maximum(-elev_diff, 0), dist_m)
|
running_pos = xp.fmax(running_pos,
|
||||||
|
xp.maximum(elev_diff, 0) / dist_m)
|
||||||
|
running_neg = xp.fmax(running_neg,
|
||||||
|
xp.maximum(-elev_diff, 0) / dist_m)
|
||||||
del elev_diff # free intermediate
|
del elev_diff # free intermediate
|
||||||
|
|
||||||
# Update running max for all radius checkpoints still active
|
# Snapshot du checkpoint atteint : conversion en angle UNE fois
|
||||||
for r_idx in radii_remaining:
|
while cp_queue and step >= cp_queue[0][0]:
|
||||||
pos_angle_safe = xp.nan_to_num(pos_angle, nan=0)
|
_, r_idx = cp_queue.pop(0)
|
||||||
neg_angle_safe = xp.nan_to_num(neg_angle, nan=0)
|
snapshots[r_idx] = (xp.arctan(running_pos),
|
||||||
running_pos[r_idx] = xp.where(xp.isnan(pos_angle), running_pos[r_idx],
|
xp.arctan(running_neg))
|
||||||
xp.maximum(running_pos[r_idx], pos_angle_safe))
|
if not cp_queue:
|
||||||
running_neg[r_idx] = xp.where(xp.isnan(neg_angle), running_neg[r_idx],
|
|
||||||
xp.maximum(running_neg[r_idx], neg_angle_safe))
|
|
||||||
|
|
||||||
# Check which radii have been passed
|
|
||||||
new_remaining = set()
|
|
||||||
for r_idx in radii_remaining:
|
|
||||||
if step < radii_steps[r_idx]:
|
|
||||||
new_remaining.add(r_idx)
|
|
||||||
radii_remaining = new_remaining
|
|
||||||
if not radii_remaining:
|
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Checkpoints jamais atteints (steps invalides) : état final du balayage
|
||||||
|
while cp_queue:
|
||||||
|
_, r_idx = cp_queue.pop(0)
|
||||||
|
snapshots[r_idx] = (xp.arctan(running_pos),
|
||||||
|
xp.arctan(running_neg))
|
||||||
|
|
||||||
# Store results on CPU, free GPU memory before next direction
|
# Store results on CPU, free GPU memory before next direction
|
||||||
pos_results[d_idx] = to_cpu(running_pos)
|
pos_results[d_idx] = to_cpu(xp.stack([snapshots[r][0] for r in range(n_radii)]))
|
||||||
neg_results[d_idx] = to_cpu(running_neg)
|
neg_results[d_idx] = to_cpu(xp.stack([snapshots[r][1] for r in range(n_radii)]))
|
||||||
del running_pos, running_neg
|
del running_pos, running_neg, snapshots
|
||||||
gpu_cleanup()
|
gpu_cleanup()
|
||||||
|
|
||||||
# Free the large padded array
|
# Free the large padded array
|
||||||
|
|||||||
Reference in New Issue
Block a user