Translate the whole project to English and fix outdated comments and help
Comments, docstrings, logs, CLI help, map UI, legends, PDF sheet, scripts, compose files and AGENTS.md are now English. Data keys stay unchanged (relief_oriente, densite_sol, visualisations/, API JSON keys, link params). Wrong comments and help defaults found along the way are corrected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
"""Tests for rendering module (colormaps, tif_to_png)."""
|
||||
"""Tests for the rendering module (colormaps, tif_to_png, tif_to_crop)."""
|
||||
|
||||
import numpy as np
|
||||
import rasterio
|
||||
@ -39,7 +39,7 @@ class TestColormaps:
|
||||
'neg_open': 'negative_openness',
|
||||
'hillshade': 'hillshade_multi',
|
||||
}
|
||||
# Images RGB (fonds IGN, relief orienté) — pas de colormap
|
||||
# RGB images (IGN backgrounds, oriented relief) — no colormap
|
||||
from lidar_pipeline.rendering import RGB_KEYWORDS
|
||||
skip = set(RGB_KEYWORDS)
|
||||
for name, _ in VIZ_STEPS:
|
||||
@ -100,32 +100,32 @@ class TestApplyColormap:
|
||||
assert result.exists()
|
||||
|
||||
def test_knots_mode_fixed_transfer(self):
|
||||
"""L'étalonnage quantile figé (ondelette) : même valeur → même couleur.
|
||||
"""Frozen quantile calibration (wavelet): same value → same color.
|
||||
|
||||
Les nœuds sont constants (pas de percentile local) : clamp aux
|
||||
extrêmes, médiane (1.0) → 0.5, transfert monotone.
|
||||
The knots are constant (no local percentile): clamped at the
|
||||
extremes, median (1.0) → 0.5, monotonic transfer.
|
||||
"""
|
||||
from lidar_pipeline.rendering import COLORMAPS, _apply_colormap
|
||||
info = COLORMAPS['wavelet']
|
||||
kv, kt = info['knots'][0.5]
|
||||
assert kv[6] == 1.0 and kt[6] == 0.5 # nœud médian
|
||||
assert kv[6] == 1.0 and kt[6] == 0.5 # median knot
|
||||
vals = np.array([0.05, kv[0], 1.0, kv[-1], 50.0], dtype=float)
|
||||
out, cmap, *_ = _apply_colormap(vals, 'x_wavelet.tif', resolution=0.5)
|
||||
assert cmap == 'inferno'
|
||||
assert out[0] == 0.0 and out[1] == 0.01 # clamp bas
|
||||
assert abs(out[2] - 0.5) < 1e-9 # médiane → 0.5
|
||||
assert out[3] == 0.995 and out[4] == 1.0 # nœud haut, puis clamp au-delà
|
||||
# Monotonie (pas d'inversion de teinte)
|
||||
assert out[0] == 0.0 and out[1] == 0.01 # low clamp
|
||||
assert abs(out[2] - 0.5) < 1e-9 # median → 0.5
|
||||
assert out[3] == 0.995 and out[4] == 1.0 # top knot, then clamped beyond
|
||||
# Monotonicity (no hue inversion)
|
||||
s = np.sort(np.random.default_rng(1).uniform(0.2, 3.0, 500))
|
||||
o, *_ = _apply_colormap(s, 'x_wavelet.tif', resolution=0.5)
|
||||
assert np.all(np.diff(o) >= -1e-12)
|
||||
# Les nœuds 0.2 m diffèrent de ceux 0.5 m (calibrations distinctes)
|
||||
# The 0.2 m knots differ from the 0.5 m ones (separate calibrations)
|
||||
kv02, _ = info['knots'][0.2]
|
||||
assert kv02 != kv
|
||||
|
||||
|
||||
class TestTifToCrop:
|
||||
"""Conversion TIF → dalle cartographique (tif_to_crop)."""
|
||||
"""TIF → map tile conversion (tif_to_crop)."""
|
||||
|
||||
@staticmethod
|
||||
def _write_named_tif(tmp_path, name, arr):
|
||||
@ -140,11 +140,11 @@ class TestTifToCrop:
|
||||
return tif_file
|
||||
|
||||
def test_nodata_renders_black(self, tmp_path):
|
||||
"""Le nodata restant est rendu en noir (comportement historique).
|
||||
"""Remaining nodata is rendered black (historical behavior).
|
||||
|
||||
Les trous du MNT sont comblés en amont (interpolation dans
|
||||
create_dtm_fast, tous modes) ; ce qui reste en nodata doit rester
|
||||
visible en noir sur la dalle plutôt qu'inventé au rendu.
|
||||
DTM holes are filled upstream (gap filling in create_dtm_fast);
|
||||
whatever is still nodata must stay visible as black on the tile
|
||||
rather than being invented at render time.
|
||||
"""
|
||||
from PIL import Image as PILImage
|
||||
from lidar_pipeline.rendering import tif_to_crop
|
||||
@ -153,19 +153,19 @@ class TestTifToCrop:
|
||||
data[15:25, 15:25] = np.nan
|
||||
tif_file = self._write_named_tif(tmp_path, "LHD_test_slope.tif", data)
|
||||
|
||||
# WebP lossless : l'encodeur AVIF de l'image « saigne » légèrement les
|
||||
# bords du noir même en lossless — on teste la logique nodata→noir,
|
||||
# pas les artefacts du codec.
|
||||
# Lossless WebP: the image's AVIF encoder slightly "bleeds" the edges
|
||||
# of the black area even in lossless mode — we test the nodata→black
|
||||
# logic, not codec artifacts.
|
||||
out = tif_to_crop(tif_file, tmp_path, 5.0, keep_tif=True,
|
||||
quality=100, output_format='webp')
|
||||
assert out is not None and out.exists()
|
||||
|
||||
rgb = np.asarray(PILImage.open(str(out)).convert('RGB'))
|
||||
hole = rgb[15:25, 15:25, :]
|
||||
assert np.all(hole < 10), "le nodata doit être rendu en noir"
|
||||
assert np.all(hole < 10), "nodata must be rendered black"
|
||||
|
||||
def test_without_nodata(self, tmp_path):
|
||||
"""Un TIF sans nodata est converti sans crash, taille préservée."""
|
||||
"""A TIF without nodata converts without crashing, size preserved."""
|
||||
from PIL import Image as PILImage
|
||||
from lidar_pipeline.rendering import tif_to_crop
|
||||
|
||||
@ -178,7 +178,7 @@ class TestTifToCrop:
|
||||
assert img.size == (40, 40)
|
||||
|
||||
class TestCoreTileWindow:
|
||||
"""Recadrage des TIF à bande de raccord sur la dalle nominale 1 km."""
|
||||
"""Cropping of edge-buffered TIFs to the nominal 1 km tile."""
|
||||
|
||||
def _write_tif(self, tmp_path, name, bounds, size):
|
||||
transform = from_bounds(*bounds, size, size)
|
||||
@ -204,7 +204,7 @@ class TestCoreTileWindow:
|
||||
assert abs(wt.f - 6628000.0) < 1e-6
|
||||
|
||||
def test_no_window_without_overflow(self, tmp_path):
|
||||
"""TIF historique sur les bornes d'en-tête (~999,99 m) : rien à recadrer."""
|
||||
"""Historical TIF on the header bounds (~999.99 m): nothing to crop."""
|
||||
from lidar_pipeline.rendering import _core_tile_window
|
||||
tif = self._write_tif(tmp_path, "LHD_FXX_0638_6628_PTS_LAMB93_IGN69_slope.tif",
|
||||
(638000, 6627000, 638999.99, 6627999.99), 5000)
|
||||
@ -220,8 +220,8 @@ class TestCoreTileWindow:
|
||||
|
||||
|
||||
class TestDensiteSolCrop:
|
||||
"""Densité de points : WebP sans perte en niveaux de gris, sous-tuiles
|
||||
écrites depuis l'image d'origine (un seul encodage)."""
|
||||
"""Point density: lossless grayscale WebP, sub-tiles written from the
|
||||
original image (a single encoding)."""
|
||||
|
||||
def test_lossless_gray_levels_and_subtiles(self, tmp_path):
|
||||
from PIL import Image as PILImage
|
||||
@ -233,20 +233,20 @@ class TestDensiteSolCrop:
|
||||
tif = TestTifToCrop._write_named_tif(vis, f"{base}_densite_sol.tif", levels)
|
||||
out = tif_to_crop(tif, vis, 0.2, output_format='avif', subtiles_dir=tmp_path)
|
||||
assert out is not None and out.suffix == ".webp"
|
||||
# WebP n'a pas de mode gris : relu en RGB à canaux égaux
|
||||
# WebP has no gray mode: read back as RGB with equal channels
|
||||
rgb = np.asarray(PILImage.open(str(out)).convert("RGB")).astype(int)
|
||||
assert (rgb[..., 0] == rgb[..., 1]).all() and (rgb[..., 1] == rgb[..., 2]).all()
|
||||
img = PILImage.fromarray(rgb[..., 0].astype(np.uint8))
|
||||
row = np.asarray(img)[0, ::4].astype(int)
|
||||
assert len(set(row.tolist())) == 16 and np.all(np.diff(row) > 0)
|
||||
# Sous-tuiles 2 × 2 (0,2 m/px) en WebP sans perte, identiques à la dalle
|
||||
# 2 × 2 sub-tiles (0.2 m/px) in lossless WebP, identical to the tile
|
||||
q = tmp_path / "index_subtiles" / f"{base}_r0p2_densite_sol_0_1.webp"
|
||||
assert q.exists()
|
||||
assert np.array_equal(np.asarray(PILImage.open(str(q)).convert("L")), np.asarray(img)[:32, :32])
|
||||
assert (tmp_path / "index_subtiles" / f"{base}_r0p2_densite_sol_0_1_mid.webp").exists()
|
||||
|
||||
def test_relief_subtiles_encoded_from_source(self, tmp_path):
|
||||
"""Relief : sous-tuiles AVIF écrites par tif_to_crop, plus récentes que la dalle."""
|
||||
"""Relief: AVIF sub-tiles written by tif_to_crop, newer than the tile."""
|
||||
import pytest
|
||||
from lidar_pipeline.rendering import tif_to_crop
|
||||
base = "LHD_FXX_0660_6701_PTS_LAMB93_IGN69"
|
||||
@ -261,7 +261,7 @@ class TestDensiteSolCrop:
|
||||
try:
|
||||
out = tif_to_crop(tif, vis, 0.2, output_format='avif', subtiles_dir=tmp_path)
|
||||
except Exception:
|
||||
pytest.skip("encodeur AVIF indisponible")
|
||||
pytest.skip("AVIF encoder unavailable")
|
||||
sub = tmp_path / "index_subtiles"
|
||||
quads = sorted(sub.glob(f"{base}_r0p2_relief_oriente_?_?.avif"))
|
||||
assert len(quads) == 4
|
||||
|
||||
Reference in New Issue
Block a user