Mettre à jour la pyramide pendant le rendu et réduire son stockage

Le pipeline régénère l'inventaire après chaque dalle par défaut (tous
lanceurs, sauf --no-index), avec une passe différée quand l'anti-rebond
ignore une dalle. La carte surveille l'inventaire toutes les 10 s et place
les tuiles des dalles modifiées en tête de file de maintenance : la
pyramide suit le rendu au lieu d'attendre la fin du lot.

Stockage réduit pour le Raspberry Pi : seuls les niveaux standard pairs
jusqu'à z16 sont écrits sur disque, en AVIF ; les autres, dont le niveau
le plus fin (~75 % de la pyramide), sont rendus à la volée avec un cache
mémoire, y compris en mode cache seule. L'interface ne demande que les
niveaux pairs et réduit ceux du niveau supérieur aux zooms impairs.
Mesuré sur 8 dalles : ~24 Mo de pyramide avant, 1,2 Mo après.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Antoine Jacquin
2026-09-27 02:31:11 +02:00
parent f836408d6f
commit 434b7f12fd
11 changed files with 299 additions and 35 deletions

View File

@ -239,8 +239,11 @@ def test_tile_edges_transparent_outside_data(tmp_path):
assert img.getpixel((255, 255))[3] == 0
def test_get_tile_cache_and_staleness(tmp_path):
def test_get_tile_cache_and_staleness(tmp_path, monkeypatch):
"""Le cache disque est réutilisé, puis invalidé par une dalle régénérée."""
from lidar_pipeline import tiles as _t
monkeypatch.setattr(_t, "TILE_EVEN_LEVELS", False) # règle historique : tout est stocké
monkeypatch.setattr(_t, "TILE_CACHE_MAX_Z", 99)
import os
import time
from lidar_pipeline import tiles
@ -270,8 +273,11 @@ def test_get_tile_cache_and_staleness(tmp_path):
assert cache.stat().st_mtime > first
def test_get_tile_empty_marker(tmp_path):
def test_get_tile_empty_marker(tmp_path, monkeypatch):
"""Tuile sans donnée : None + marqueur .empty mémorisé."""
from lidar_pipeline import tiles as _t
monkeypatch.setattr(_t, "TILE_EVEN_LEVELS", False) # règle historique : tout est stocké
monkeypatch.setattr(_t, "TILE_CACHE_MAX_Z", 99)
from lidar_pipeline import tiles
_make_dalle(tmp_path, 1054, 6882, ["aspect"])
tiles.source_index(tmp_path, force=True)
@ -514,3 +520,33 @@ def test_cached_tile_states(tmp_path):
data, state = tiles.cached_tile(tmp_path, "slope", 14, 0, 0)
assert state == "empty" and data is None
assert tiles.empty_marker_exists(tmp_path, "slope", 14, 0, 0)
def test_storage_policy_even_levels_up_to_max(monkeypatch):
"""Stockage réduit : niveaux standard pairs ≤ LIDAR_TILE_CACHE_MAX_Z (une
tuile @2x de niveau z vaut une 256 px de z+1)."""
from lidar_pipeline import tiles
monkeypatch.setattr(tiles, "TILE_EVEN_LEVELS", True)
monkeypatch.setattr(tiles, "TILE_CACHE_MAX_Z", 16)
assert tiles.zoom_cached(16, 1) and not tiles.zoom_cached(15, 1)
assert tiles.zoom_cached(15, 2) and not tiles.zoom_cached(16, 2) # @2x z15 = z16 standard
assert not tiles.zoom_cached(18, 1) and not tiles.zoom_cached(17, 2) # niveau fin : à la volée
def test_unstored_level_rendered_on_the_fly_without_disk(tmp_path, monkeypatch):
"""Niveau non stocké : tuile rendue, rien d'écrit sur disque, seconde
demande servie par le cache mémoire."""
from lidar_pipeline import tiles
monkeypatch.setattr(tiles, "TILE_EVEN_LEVELS", True)
monkeypatch.setattr(tiles, "TILE_CACHE_MAX_Z", 16)
_make_dalle(tmp_path, 1054, 6882, ["aspect"])
tiles.source_index(tmp_path, force=True)
z = 17
x, y = _tile_of_cell(1054, 6882, z)
calls = []
real = tiles.render_tile
monkeypatch.setattr(tiles, "render_tile", lambda *a, **k: calls.append(1) or real(*a, **k))
data = tiles.get_tile(tmp_path, "aspect", z, x, y, 1, "png")
assert data and data[:4] == b"\x89PNG"
assert not tiles.tile_cache_path(tmp_path, "aspect", z, x, y, 1, "png").exists()
assert tiles.get_tile(tmp_path, "aspect", z, x, y, 1, "png") == data and len(calls) == 1