Simulation Exit Fixes
This commit is contained in:
@@ -1,14 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Run Recorder — Captures logs, flight path, camera frames, and summary for each run.
|
||||
|
||||
Creates timestamped folders in /results with:
|
||||
flight_path.avi — Flight tracker video
|
||||
camera.avi — Downward camera video
|
||||
flight_path.png — Final flight tracker snapshot
|
||||
log.txt — Full console output
|
||||
summary.json — Run metadata and results
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -23,8 +13,6 @@ from datetime import datetime
|
||||
|
||||
|
||||
class RunRecorder:
|
||||
"""Records all outputs from a simulation run."""
|
||||
|
||||
def __init__(self, results_dir=None, fps=5):
|
||||
if results_dir is None:
|
||||
project_dir = Path(__file__).resolve().parent.parent
|
||||
@@ -33,7 +21,6 @@ class RunRecorder:
|
||||
results_dir = Path(results_dir)
|
||||
results_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Find next sequential run number
|
||||
existing = [d.name for d in results_dir.iterdir()
|
||||
if d.is_dir() and d.name.startswith("run_")]
|
||||
nums = []
|
||||
@@ -51,7 +38,6 @@ class RunRecorder:
|
||||
self.fps = fps
|
||||
self.start_time = time.time()
|
||||
|
||||
# Log capture
|
||||
self._log_path = self.run_dir / "log.txt"
|
||||
self._log_file = open(self._log_path, "w")
|
||||
self._original_stdout = sys.stdout
|
||||
@@ -59,29 +45,24 @@ class RunRecorder:
|
||||
self._tee_stdout = _TeeWriter(sys.stdout, self._log_file)
|
||||
self._tee_stderr = _TeeWriter(sys.stderr, self._log_file)
|
||||
|
||||
# Video writers (initialized lazily on first frame)
|
||||
self._tracker_writer = None
|
||||
self._camera_writer = None
|
||||
self._tracker_size = None
|
||||
self._camera_size = None
|
||||
|
||||
# Frame counters
|
||||
self._tracker_frames = 0
|
||||
self._camera_frames = 0
|
||||
|
||||
# Snapshot storage
|
||||
self._last_tracker_frame = None
|
||||
self._last_camera_frame = None
|
||||
self._camera_snapshots = []
|
||||
|
||||
# Recording thread
|
||||
self._recording = False
|
||||
self._tracker_ref = None
|
||||
self._camera_ref = None
|
||||
self._record_thread = None
|
||||
self._lock = threading.Lock()
|
||||
|
||||
# Metadata
|
||||
self.metadata = {
|
||||
"run": run_num,
|
||||
"start_time": datetime.now().isoformat(),
|
||||
@@ -91,17 +72,14 @@ class RunRecorder:
|
||||
print(f"[REC] Recording to: {self.run_dir}")
|
||||
|
||||
def start_logging(self):
|
||||
"""Redirect stdout/stderr to both console and log file."""
|
||||
sys.stdout = self._tee_stdout
|
||||
sys.stderr = self._tee_stderr
|
||||
|
||||
def stop_logging(self):
|
||||
"""Restore original stdout/stderr."""
|
||||
sys.stdout = self._original_stdout
|
||||
sys.stderr = self._original_stderr
|
||||
|
||||
def start_recording(self, tracker=None, camera=None):
|
||||
"""Start background recording of tracker and camera frames."""
|
||||
self._tracker_ref = tracker
|
||||
self._camera_ref = camera
|
||||
self._recording = True
|
||||
@@ -111,12 +89,10 @@ class RunRecorder:
|
||||
self._record_thread.start()
|
||||
|
||||
def _record_loop(self):
|
||||
"""Periodically capture frames from tracker and camera."""
|
||||
interval = 1.0 / self.fps
|
||||
while self._recording:
|
||||
t0 = time.time()
|
||||
|
||||
# Capture tracker frame
|
||||
if self._tracker_ref is not None:
|
||||
try:
|
||||
frame = self._tracker_ref.draw()
|
||||
@@ -126,7 +102,6 @@ class RunRecorder:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Capture camera frame
|
||||
if self._camera_ref is not None:
|
||||
try:
|
||||
frame = self._camera_ref.frames.get("downward")
|
||||
@@ -141,7 +116,6 @@ class RunRecorder:
|
||||
time.sleep(sleep_time)
|
||||
|
||||
def _write_tracker_frame(self, frame):
|
||||
"""Write a frame to the tracker video."""
|
||||
h, w = frame.shape[:2]
|
||||
if self._tracker_writer is None:
|
||||
self._tracker_size = (w, h)
|
||||
@@ -152,7 +126,6 @@ class RunRecorder:
|
||||
self._tracker_frames += 1
|
||||
|
||||
def _write_camera_frame(self, frame):
|
||||
"""Write a frame to the camera video."""
|
||||
h, w = frame.shape[:2]
|
||||
if self._camera_writer is None:
|
||||
self._camera_size = (w, h)
|
||||
@@ -163,7 +136,6 @@ class RunRecorder:
|
||||
self._camera_frames += 1
|
||||
|
||||
def snapshot_camera(self, label="snapshot"):
|
||||
"""Save a named snapshot of the current camera frame."""
|
||||
if self._camera_ref is None:
|
||||
return
|
||||
frame = self._camera_ref.frames.get("downward")
|
||||
@@ -178,7 +150,6 @@ class RunRecorder:
|
||||
|
||||
def save_summary(self, search_mode="", altitude=0, markers=None,
|
||||
landed=False, extra=None):
|
||||
"""Write the run summary JSON."""
|
||||
duration = time.time() - self.start_time
|
||||
mins = int(duration // 60)
|
||||
secs = int(duration % 60)
|
||||
@@ -223,29 +194,24 @@ class RunRecorder:
|
||||
print(f"[REC] Summary saved: {path}")
|
||||
|
||||
def stop(self):
|
||||
"""Stop recording and finalize all outputs."""
|
||||
self._recording = False
|
||||
if self._record_thread:
|
||||
self._record_thread.join(timeout=3.0)
|
||||
|
||||
# Save final flight path image
|
||||
if self._last_tracker_frame is not None:
|
||||
path = self.run_dir / "flight_path.png"
|
||||
cv2.imwrite(str(path), self._last_tracker_frame)
|
||||
print(f"[REC] Flight path saved: {path}")
|
||||
|
||||
# Save final camera frame
|
||||
if self._last_camera_frame is not None:
|
||||
path = self.run_dir / "camera_final.png"
|
||||
cv2.imwrite(str(path), self._last_camera_frame)
|
||||
|
||||
# Release video writers
|
||||
if self._tracker_writer:
|
||||
self._tracker_writer.release()
|
||||
if self._camera_writer:
|
||||
self._camera_writer.release()
|
||||
|
||||
# Stop log capture
|
||||
self.stop_logging()
|
||||
self._log_file.close()
|
||||
|
||||
@@ -253,7 +219,6 @@ class RunRecorder:
|
||||
mins = int(duration // 60)
|
||||
secs = int(duration % 60)
|
||||
|
||||
# Print to original stdout since we stopped the tee
|
||||
self._original_stdout.write(
|
||||
f"\n[REC] Run recorded: {self.run_dir}\n"
|
||||
f"[REC] Duration: {mins}m {secs}s | "
|
||||
@@ -263,8 +228,6 @@ class RunRecorder:
|
||||
|
||||
|
||||
class _TeeWriter:
|
||||
"""Writes to both a stream and a file simultaneously."""
|
||||
|
||||
def __init__(self, stream, log_file):
|
||||
self._stream = stream
|
||||
self._log = log_file
|
||||
@@ -272,9 +235,7 @@ class _TeeWriter:
|
||||
def write(self, data):
|
||||
self._stream.write(data)
|
||||
try:
|
||||
# Strip ANSI escape codes for the log file
|
||||
clean = data
|
||||
self._log.write(clean)
|
||||
self._log.write(data)
|
||||
self._log.flush()
|
||||
except (ValueError, IOError):
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user