Improve visualizations: adaptive scales, revert z-score to std normalization

- MSRM/TPI/roughness/anomalies: revert z-score (x-mean)/std to std normalization x/std
  to preserve contrast and visibility of linear features (paths, ditches, trenches)
- MSRM: adaptive scales based on resolution, archaeological weight combination
- TPI: extend from 2 to 4 scales (3m/15m/50m/200m) with weighted combination
- Hillshade: 8 directions instead of 4, altitude 35° instead of 30°
- LRM: adaptive sigma based on resolution
- Openness: doubled radius (100m instead of 50m)
- Roughness: multi-scale (3m fine + 15m broad) instead of single 5x5 window
- Anomalies: uses MSRM multi-scale relief instead of single LRM 15m
- Wavelet: 8 adaptive scales, std normalization, archaeological weights
- Remove svf (Sky-View Factor) and local_dominance visualizations
- Add AVIF format support (default), quality 98
- Add multi-resolution support (-r 0.5,0.2)
- Improve Ctrl+C handling for immediate process termination
- Update rendering.py descriptions for all modified visualizations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-05-14 23:12:08 +02:00
parent ac56ba8084
commit d334892880
8 changed files with 344 additions and 180 deletions

View File

@ -131,13 +131,19 @@ Exemples:
parser.add_argument(
"--quality",
type=int,
default=85,
help="Qualité WebP (1-100, défaut: 85). Utilisez 100 pour lossless."
default=98,
help="Qualité image (1-100, défaut: 98). Utilisez 100 pour lossless."
)
parser.add_argument(
"--lossless",
action="store_true",
help="Forcer la compression WebP lossless (équivalent à --quality 100)"
help="Forcer la compression lossless (équivalent à --quality 100)"
)
parser.add_argument(
"--format",
choices=["webp", "avif"],
default="avif",
help="Format de sortie : avif (défaut, meilleure qualité) ou webp"
)
parser.add_argument(
"--only",
@ -194,6 +200,13 @@ Exemples:
try:
quality = 100 if args.lossless else args.quality
# Parse --only and --skip: accept comma-separated values
only_viz = None
if args.only:
only_viz = [v.strip() for item in args.only for v in item.split(',')]
skip_viz = None
if args.skip:
skip_viz = [v.strip() for item in args.skip for v in item.split(',')]
pipeline = LidarArchaeoPipeline(
input_dir=args.input,
output_dir=args.output,
@ -204,8 +217,9 @@ Exemples:
force_classify=args.force_classification,
keep_tif=args.keep_tif,
quality=quality,
only_viz=args.only,
skip_viz=args.skip,
only_viz=only_viz,
skip_viz=skip_viz,
output_format=args.format,
)
# If --file is specified, process only matching files
@ -270,9 +284,15 @@ def _kill_orphan_pdal(signum=None, frame=None):
"""Kill orphan PDAL processes on interrupt or exit."""
import subprocess
try:
subprocess.run(["pkill", "-f", "pdal"], capture_output=True, timeout=5)
subprocess.run(["pkill", "-9", "-f", "pdal"], capture_output=True, timeout=3)
except Exception:
pass
if signum is not None:
logger.info("Interruption — nettoyage des processus PDAL")
logger.info("Interruption — nettoyage des processus")
# Force-kill all child processes immediately
try:
import os
os.killpg(os.getpgrp(), signal.SIGKILL)
except Exception:
pass
sys.exit(130)