dispatcher trim auto prefix hors-eau avant reconstruct
Les sessions record commencent systematiquement avec l AUV sur le pont ou en surface. Les frames hors-eau polluent le model et bloquent l alignment ICP du stitch. trim_above_water_prefix detecte underwater par absorption du canal rouge (mean_R < mean_G-5 ET mean_R < mean_B-5) et exige un streak consecutif de 10 frames underwater pour lock le start. Tout ce qui precede est supprime avant demo.py. Opencv charge les frames en REDUCED_COLOR_4 pour acceleration. Execute dans le venv lingbot-map cote worker (cv2 dispo).
This commit is contained in:
@@ -142,6 +142,68 @@ def count_frames(worker: dict, frames_dir: str) -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# Record sessions always start with the AUV on deck or at the surface — these frames pollute
|
||||||
|
# reconstruction. Detect the first sustained underwater run (red channel absorbed by water, so
|
||||||
|
# mean_R < mean_G and mean_R < mean_B) and delete the hors-eau prefix before demo.py runs.
|
||||||
|
_AUTO_TRIM_SCRIPT = r"""
|
||||||
|
import cv2, glob, os, sys
|
||||||
|
frames_dir = sys.argv[1]
|
||||||
|
need_streak = 10 # consecutive underwater frames required to lock start
|
||||||
|
paths = sorted(glob.glob(os.path.join(frames_dir, 'frame_*.jpg')))
|
||||||
|
if not paths:
|
||||||
|
print('TRIM_RESULT 0 0'); sys.exit(0)
|
||||||
|
start = 0
|
||||||
|
streak = 0
|
||||||
|
for i, p in enumerate(paths):
|
||||||
|
img = cv2.imread(p, cv2.IMREAD_REDUCED_COLOR_4)
|
||||||
|
if img is None:
|
||||||
|
continue
|
||||||
|
mean_b, mean_g, mean_r = [float(c) for c in cv2.mean(img)[:3]]
|
||||||
|
# Underwater = red is absorbed → R noticeably lower than both G and B
|
||||||
|
underwater = mean_r < mean_g - 5 and mean_r < mean_b - 5
|
||||||
|
if underwater:
|
||||||
|
streak += 1
|
||||||
|
if streak >= need_streak:
|
||||||
|
start = i - need_streak + 1
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
streak = 0
|
||||||
|
if start <= 0:
|
||||||
|
print(f'TRIM_RESULT 0 {len(paths)}'); sys.exit(0)
|
||||||
|
for p in paths[:start]:
|
||||||
|
try: os.remove(p)
|
||||||
|
except OSError: pass
|
||||||
|
print(f'TRIM_RESULT {start} {len(paths) - start}')
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def trim_above_water_prefix(worker: dict, frames_dir: str) -> tuple[int, int]:
|
||||||
|
"""Delete leading out-of-water frames. Returns (removed, remaining)."""
|
||||||
|
script_remote = f"/tmp/cosma-trim-{os.getpid()}.py"
|
||||||
|
# Write script on worker and run it inside the lingbot-map venv (has cv2)
|
||||||
|
rc, _, err = ssh(
|
||||||
|
worker["ssh_alias"],
|
||||||
|
f"cat > {shlex.quote(script_remote)} << 'PYEOF'\n{_AUTO_TRIM_SCRIPT}\nPYEOF",
|
||||||
|
timeout=15,
|
||||||
|
)
|
||||||
|
if rc != 0:
|
||||||
|
print(f" ↳ trim script upload failed: {err[:150]}")
|
||||||
|
return (0, 0)
|
||||||
|
rc, out, err = ssh(
|
||||||
|
worker["ssh_alias"],
|
||||||
|
f"source {shlex.quote(worker['lingbot_path'])}/.venv/bin/activate && "
|
||||||
|
f"python3 {shlex.quote(script_remote)} {shlex.quote(frames_dir)}; rm -f {shlex.quote(script_remote)}",
|
||||||
|
timeout=600,
|
||||||
|
)
|
||||||
|
for line in out.splitlines():
|
||||||
|
if line.startswith("TRIM_RESULT"):
|
||||||
|
parts = line.split()
|
||||||
|
removed, remaining = int(parts[1]), int(parts[2])
|
||||||
|
return (removed, remaining)
|
||||||
|
print(f" ↳ trim unexpected output: {out[:200]} / {err[:200]}")
|
||||||
|
return (0, 0)
|
||||||
|
|
||||||
|
|
||||||
def scp_to_worker(local_path: str, worker: dict, remote_path: str):
|
def scp_to_worker(local_path: str, worker: dict, remote_path: str):
|
||||||
"""Copy a file to the worker.
|
"""Copy a file to the worker.
|
||||||
|
|
||||||
@@ -235,6 +297,11 @@ def do_extract(job: sqlite3.Row, worker: dict) -> str:
|
|||||||
# are 1-11 GB each. Frames are already extracted so worker_src is no longer needed.
|
# are 1-11 GB each. Frames are already extracted so worker_src is no longer needed.
|
||||||
ssh(worker["ssh_alias"], f"rm -f {shlex.quote(worker_src)}")
|
ssh(worker["ssh_alias"], f"rm -f {shlex.quote(worker_src)}")
|
||||||
set_status(job["id"], frame_count=idx, progress=min(99, idx * 100 // total_frames_est))
|
set_status(job["id"], frame_count=idx, progress=min(99, idx * 100 // total_frames_est))
|
||||||
|
# Drop the hors-eau prefix before reconstruction — always present at session start.
|
||||||
|
removed, remaining = trim_above_water_prefix(worker, frames_dir)
|
||||||
|
if removed:
|
||||||
|
print(f" ↳ job #{job['id']}: trimmed {removed} out-of-water frames, {remaining} kept")
|
||||||
|
set_status(job["id"], frame_count=remaining)
|
||||||
# Trim once per job so LVM thin pool on the host actually reclaims the freed blocks.
|
# Trim once per job so LVM thin pool on the host actually reclaims the freed blocks.
|
||||||
ssh(worker["ssh_alias"], "sudo fstrim / 2>/dev/null || fstrim / 2>/dev/null", timeout=60)
|
ssh(worker["ssh_alias"], "sudo fstrim / 2>/dev/null || fstrim / 2>/dev/null", timeout=60)
|
||||||
return frames_dir
|
return frames_dir
|
||||||
|
|||||||
Reference in New Issue
Block a user