Cloud Saves Update
This commit is contained in:
@@ -10,7 +10,7 @@ marker:
|
|||||||
size: 0.5 # Physical marker size in meters
|
size: 0.5 # Physical marker size in meters
|
||||||
landing_ids: [0] # Marker IDs that trigger landing (on UGV)
|
landing_ids: [0] # Marker IDs that trigger landing (on UGV)
|
||||||
target_ids: [1] # Marker IDs to find and report to UGV
|
target_ids: [1] # Marker IDs to find and report to UGV
|
||||||
target_position: [-5.0, -5.0] # Initial X, Y location of the target Aruco map in the map
|
target_position: [3.0, 3.0] # Initial X, Y location of the target Aruco map in the map
|
||||||
|
|
||||||
# ── Search Patterns ──────────────────────────────────────────
|
# ── Search Patterns ──────────────────────────────────────────
|
||||||
spiral:
|
spiral:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class SearchMode(Enum):
|
|||||||
LAWNMOWER = "lawnmower"
|
LAWNMOWER = "lawnmower"
|
||||||
LEVY = "levy"
|
LEVY = "levy"
|
||||||
class Search:
|
class Search:
|
||||||
POSITION_TOLERANCE = 0.2
|
POSITION_TOLERANCE = 0.05
|
||||||
CHECK_INTERVAL = 0.5
|
CHECK_INTERVAL = 0.5
|
||||||
MAX_TRAVEL_TIME = 300.0
|
MAX_TRAVEL_TIME = 300.0
|
||||||
|
|
||||||
|
|||||||
@@ -142,11 +142,10 @@ class Controller:
|
|||||||
# bit 0x20 = pred_pos_horiz_abs (predicted, not enough for arming)
|
# bit 0x20 = pred_pos_horiz_abs (predicted, not enough for arming)
|
||||||
pos_horiz_abs = bool(msg.flags & 0x08)
|
pos_horiz_abs = bool(msg.flags & 0x08)
|
||||||
if pos_horiz_abs:
|
if pos_horiz_abs:
|
||||||
print(f"\n[UAV] EKF has position estimate (flags=0x{msg.flags:04x})")
|
|
||||||
return True
|
|
||||||
elapsed = int(time.time() - t0)
|
elapsed = int(time.time() - t0)
|
||||||
print(f"\r[UAV] Waiting for EKF ... {elapsed}s ", end='', flush=True)
|
print(f"[UAV] EKF converged in {elapsed}s (flags=0x{msg.flags:04x})")
|
||||||
print("\n[UAV] EKF wait timed out")
|
return True
|
||||||
|
print(f"[UAV] EKF wait timed out after {int(time.time() - t0)}s")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def arm(self, retries: int = 15):
|
def arm(self, retries: int = 15):
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ def setup_ardupilot(ctrl: Controller):
|
|||||||
ctrl.set_param('SCHED_LOOP_RATE', 200)
|
ctrl.set_param('SCHED_LOOP_RATE', 200)
|
||||||
ctrl.set_param('FS_THR_ENABLE', 0)
|
ctrl.set_param('FS_THR_ENABLE', 0)
|
||||||
ctrl.set_param('FS_GCS_ENABLE', 0)
|
ctrl.set_param('FS_GCS_ENABLE', 0)
|
||||||
ctrl.configure_speed_limits()
|
|
||||||
sleep(2)
|
sleep(2)
|
||||||
|
|
||||||
|
|
||||||
@@ -211,10 +210,14 @@ def main():
|
|||||||
recorder.set_phase("takeoff")
|
recorder.set_phase("takeoff")
|
||||||
|
|
||||||
ctrl.takeoff(altitude)
|
ctrl.takeoff(altitude)
|
||||||
ctrl.wait_altitude(altitude, tolerance=1.0, timeout=30)
|
tracker.reset_timer()
|
||||||
|
|
||||||
if recorder:
|
if recorder:
|
||||||
recorder.set_phase("search")
|
recorder.set_phase("search")
|
||||||
|
|
||||||
|
ctrl.wait_altitude(altitude, tolerance=1.0, timeout=30)
|
||||||
|
|
||||||
|
if recorder:
|
||||||
recorder.start_recording(tracker=tracker, camera=camera)
|
recorder.start_recording(tracker=tracker, camera=camera)
|
||||||
recorder.snapshot_camera("pre_search")
|
recorder.snapshot_camera("pre_search")
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ class FlightTracker:
|
|||||||
self.panel_width = 200
|
self.panel_width = 200
|
||||||
self.total_width = self.window_size + self.panel_width
|
self.total_width = self.window_size + self.panel_width
|
||||||
|
|
||||||
|
def reset_timer(self):
|
||||||
|
self.start_time = time.time()
|
||||||
|
|
||||||
def world_to_pixel(self, x, y):
|
def world_to_pixel(self, x, y):
|
||||||
px = int(self.window_size / 2 + (y / self.world_range) * (self.window_size / 2))
|
px = int(self.window_size / 2 + (y / self.world_range) * (self.window_size / 2))
|
||||||
py = int(self.window_size / 2 - (x / self.world_range) * (self.window_size / 2))
|
py = int(self.window_size / 2 - (x / self.world_range) * (self.window_size / 2))
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Run recorder — captures simulation video and logs to a results folder.
|
"""Run recorder — captures simulation video and logs, and uploads to local REST API backend.
|
||||||
|
|
||||||
Each run gets a folder like results/run_1/ containing:
|
Each run gets a temporary folder, and all resources are uploaded directly to the backend.
|
||||||
- log.txt Process logs (stdout/stderr)
|
|
||||||
- flight_path.avi Flight tracker video
|
|
||||||
- flight_path.png Final flight path image
|
|
||||||
- camera.avi Downward camera video
|
|
||||||
- camera_*.png Camera snapshots at key moments
|
|
||||||
- gazebo.avi Gazebo simulation window recording (via xwd)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -19,8 +13,11 @@ import cv2
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
|
import tempfile
|
||||||
|
import requests
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import yaml
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import PIL.Image
|
import PIL.Image
|
||||||
@@ -28,32 +25,57 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_PIL = False
|
HAS_PIL = False
|
||||||
|
|
||||||
|
API_URL = os.environ.get("API_URL", "https://simlink.sirblob.co")
|
||||||
|
|
||||||
class RunRecorder:
|
class RunRecorder:
|
||||||
def __init__(self, results_dir=None, fps=5):
|
def __init__(self, results_dir=None, fps=5):
|
||||||
if results_dir is None:
|
|
||||||
project_dir = Path(__file__).resolve().parent.parent.parent
|
project_dir = Path(__file__).resolve().parent.parent.parent
|
||||||
results_dir = project_dir / "results"
|
|
||||||
|
|
||||||
results_dir = Path(results_dir)
|
|
||||||
results_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
existing = [d.name for d in results_dir.iterdir()
|
|
||||||
if d.is_dir() and d.name.startswith("run_")]
|
|
||||||
nums = []
|
|
||||||
for name in existing:
|
|
||||||
try:
|
|
||||||
nums.append(int(name.split("_")[1]))
|
|
||||||
except (IndexError, ValueError):
|
|
||||||
pass
|
|
||||||
run_num = max(nums, default=0) + 1
|
|
||||||
|
|
||||||
self.run_dir = results_dir / f"run_{run_num}"
|
|
||||||
self.run_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
self.run_num = run_num
|
|
||||||
|
|
||||||
self.fps = fps
|
self.fps = fps
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
|
|
||||||
|
self.search_start = 0.0
|
||||||
|
self.search_duration = 0.0
|
||||||
|
|
||||||
|
# Gather config data as JSON
|
||||||
|
config_data = {}
|
||||||
|
for cfg in ["search.yaml", "ugv.yaml", "uav.yaml"]:
|
||||||
|
p = project_dir / "config" / cfg
|
||||||
|
if p.exists():
|
||||||
|
try:
|
||||||
|
with open(p, 'r') as f:
|
||||||
|
data = yaml.safe_load(f)
|
||||||
|
if data:
|
||||||
|
key = cfg.replace('.yaml', '')
|
||||||
|
config_data[key] = data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[REC] Failed to load config {cfg}: {e}")
|
||||||
|
|
||||||
|
if "ugv" in config_data and "topics" in config_data["ugv"]:
|
||||||
|
del config_data["ugv"]["topics"]
|
||||||
|
|
||||||
|
if "uav" in config_data and "connection" in config_data["uav"]:
|
||||||
|
del config_data["uav"]["connection"]
|
||||||
|
|
||||||
|
self.sim_id = 0
|
||||||
|
self.sim_name = "simulation_unknown"
|
||||||
|
try:
|
||||||
|
resp = requests.post(f"{API_URL}/api/simulations/create", json=config_data, timeout=10)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
self.sim_id = data.get("id", 0)
|
||||||
|
self.sim_name = data.get("name", f"simulation_{self.sim_id}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[REC] API create failed: {e}")
|
||||||
|
|
||||||
|
if results_dir:
|
||||||
|
self.run_dir = Path(results_dir) / self.sim_name
|
||||||
|
else:
|
||||||
|
self.run_dir = project_dir / "results" / self.sim_name
|
||||||
|
self.run_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
self.run_num = self.sim_id
|
||||||
|
|
||||||
|
print(f"[REC] Recording local results to {self.run_dir.name} and API ({self.sim_name}, ID: {self.sim_id})")
|
||||||
self._log_path = self.run_dir / "log.txt"
|
self._log_path = self.run_dir / "log.txt"
|
||||||
self._log_file = open(self._log_path, "w")
|
self._log_file = open(self._log_path, "w")
|
||||||
self._original_stdout = sys.stdout
|
self._original_stdout = sys.stdout
|
||||||
@@ -87,18 +109,23 @@ class RunRecorder:
|
|||||||
self._gazebo_wid = None
|
self._gazebo_wid = None
|
||||||
self._find_gazebo_window()
|
self._find_gazebo_window()
|
||||||
|
|
||||||
self.metadata = {
|
def _upload_file(self, path, filename):
|
||||||
"run": run_num,
|
if not self.sim_id:
|
||||||
"start_time": datetime.now().isoformat(),
|
return
|
||||||
"run_dir": str(self.run_dir),
|
try:
|
||||||
}
|
with open(path, 'rb') as f:
|
||||||
|
resp = requests.post(
|
||||||
print(f"[REC] Recording to: {self.run_dir}")
|
f"{API_URL}/api/simulations/{self.sim_id}/upload",
|
||||||
|
files={"file": (filename, f)},
|
||||||
|
timeout=60
|
||||||
|
)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[REC] Upload failed for {filename}: {e}")
|
||||||
|
|
||||||
def _find_gazebo_window(self):
|
def _find_gazebo_window(self):
|
||||||
"""Attempts to find the Gazebo window ID using xwininfo."""
|
|
||||||
try:
|
try:
|
||||||
# Find window ID
|
|
||||||
cmd = "xwininfo -root -tree | grep -i 'gazebo\|gz sim'"
|
cmd = "xwininfo -root -tree | grep -i 'gazebo\|gz sim'"
|
||||||
output = subprocess.check_output(cmd, shell=True).decode('utf-8')
|
output = subprocess.check_output(cmd, shell=True).decode('utf-8')
|
||||||
lines = output.strip().split('\n')
|
lines = output.strip().split('\n')
|
||||||
@@ -106,24 +133,24 @@ class RunRecorder:
|
|||||||
print("[REC] Gazebo window not found")
|
print("[REC] Gazebo window not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Pick the first one
|
|
||||||
wid_line = lines[0]
|
wid_line = lines[0]
|
||||||
wid_match = re.search(r'(0x[0-9a-fA-F]+)', wid_line)
|
wid_match = re.search(r'(0x[0-9a-fA-F]+)', wid_line)
|
||||||
if not wid_match:
|
if not wid_match:
|
||||||
return
|
return
|
||||||
self._gazebo_wid = wid_match.group(1)
|
self._gazebo_wid = wid_match.group(1)
|
||||||
print(f"[REC] Found Gazebo window ID: {self._gazebo_wid}")
|
print(f"[REC] Found Gazebo window ID: {self._gazebo_wid}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[REC] Error finding Gazebo window: {e}")
|
print(f"[REC] Error finding Gazebo window: {e}")
|
||||||
|
|
||||||
def set_phase(self, phase: str):
|
def set_phase(self, phase: str):
|
||||||
# No-op since CSV logging is removed
|
if phase == "takeoff":
|
||||||
pass
|
self.start_time = time.time()
|
||||||
|
elif phase == "search":
|
||||||
|
self.search_start = time.time()
|
||||||
|
elif phase != "search" and phase != "takeoff" and self.search_start > 0 and self.search_duration == 0:
|
||||||
|
self.search_duration = time.time() - self.search_start
|
||||||
|
|
||||||
def log_position(self, uav_x=0, uav_y=0, uav_alt=0, uav_heading=0,
|
def log_position(self, uav_x=0, uav_y=0, uav_alt=0, uav_heading=0, ugv_x=0, ugv_y=0):
|
||||||
ugv_x=0, ugv_y=0):
|
|
||||||
# No-op since CSV logging is removed
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def start_logging(self):
|
def start_logging(self):
|
||||||
@@ -139,13 +166,10 @@ class RunRecorder:
|
|||||||
self._camera_ref = camera
|
self._camera_ref = camera
|
||||||
self._recording = True
|
self._recording = True
|
||||||
|
|
||||||
# Re-check Gazebo window
|
|
||||||
if not self._gazebo_wid:
|
if not self._gazebo_wid:
|
||||||
self._find_gazebo_window()
|
self._find_gazebo_window()
|
||||||
|
|
||||||
self._record_thread = threading.Thread(
|
self._record_thread = threading.Thread(target=self._record_loop, daemon=True)
|
||||||
target=self._record_loop, daemon=True
|
|
||||||
)
|
|
||||||
self._record_thread.start()
|
self._record_thread.start()
|
||||||
|
|
||||||
def _record_loop(self):
|
def _record_loop(self):
|
||||||
@@ -171,31 +195,24 @@ class RunRecorder:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Gazebo Capture via xwd
|
|
||||||
if self._gazebo_wid and HAS_PIL:
|
if self._gazebo_wid and HAS_PIL:
|
||||||
try:
|
try:
|
||||||
# Capture window to stdout using xwd
|
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
['xwd', '-id', self._gazebo_wid, '-out', '/dev/stdout'],
|
['xwd', '-id', self._gazebo_wid, '-out', '/dev/stdout'],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE
|
stderr=subprocess.PIPE
|
||||||
)
|
)
|
||||||
if proc.returncode == 0:
|
if proc.returncode == 0:
|
||||||
# Parse XWD using PIL
|
|
||||||
img_pil = PIL.Image.open(io.BytesIO(proc.stdout))
|
img_pil = PIL.Image.open(io.BytesIO(proc.stdout))
|
||||||
# Convert to OpenCV (RGB -> BGR)
|
|
||||||
img_np = np.array(img_pil)
|
img_np = np.array(img_pil)
|
||||||
# PIL opens XWD as RGB usually, check mode
|
|
||||||
if img_pil.mode == 'RGB':
|
if img_pil.mode == 'RGB':
|
||||||
img_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
|
img_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
|
||||||
elif img_pil.mode == 'RGBA':
|
elif img_pil.mode == 'RGBA':
|
||||||
img_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGBA2BGR)
|
img_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGBA2BGR)
|
||||||
else:
|
else:
|
||||||
img_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR) # Fallback
|
img_bgr = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
|
||||||
|
|
||||||
self._write_gazebo_frame(img_bgr)
|
self._write_gazebo_frame(img_bgr)
|
||||||
except Exception:
|
except Exception:
|
||||||
# xwd failed or window closed
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elapsed = time.time() - t0
|
elapsed = time.time() - t0
|
||||||
@@ -224,7 +241,6 @@ class RunRecorder:
|
|||||||
|
|
||||||
def _write_gazebo_frame(self, frame):
|
def _write_gazebo_frame(self, frame):
|
||||||
h, w = frame.shape[:2]
|
h, w = frame.shape[:2]
|
||||||
# Ensure dimensions are even
|
|
||||||
if w % 2 != 0: w -= 1
|
if w % 2 != 0: w -= 1
|
||||||
if h % 2 != 0: h -= 1
|
if h % 2 != 0: h -= 1
|
||||||
frame = frame[:h, :w]
|
frame = frame[:h, :w]
|
||||||
@@ -235,7 +251,6 @@ class RunRecorder:
|
|||||||
path = str(self.run_dir / "gazebo.avi")
|
path = str(self.run_dir / "gazebo.avi")
|
||||||
self._gazebo_writer = cv2.VideoWriter(path, fourcc, self.fps, (w, h))
|
self._gazebo_writer = cv2.VideoWriter(path, fourcc, self.fps, (w, h))
|
||||||
elif (w, h) != self._gazebo_size:
|
elif (w, h) != self._gazebo_size:
|
||||||
# Handle resize? Just skip for now or resize
|
|
||||||
frame = cv2.resize(frame, self._gazebo_size)
|
frame = cv2.resize(frame, self._gazebo_size)
|
||||||
|
|
||||||
self._gazebo_writer.write(frame)
|
self._gazebo_writer.write(frame)
|
||||||
@@ -253,20 +268,17 @@ class RunRecorder:
|
|||||||
cv2.imwrite(str(path), frame)
|
cv2.imwrite(str(path), frame)
|
||||||
self._camera_snapshots.append(filename)
|
self._camera_snapshots.append(filename)
|
||||||
print(f"[REC] Snapshot: {filename}")
|
print(f"[REC] Snapshot: {filename}")
|
||||||
|
threading.Thread(target=self._upload_file, args=(path, filename), daemon=True).start()
|
||||||
|
|
||||||
def snapshot_tracker(self, label="tracker"):
|
def snapshot_tracker(self, label="tracker"):
|
||||||
"""Save a snapshot of the current flight tracker."""
|
|
||||||
if self._last_tracker_frame is not None:
|
if self._last_tracker_frame is not None:
|
||||||
filename = f"tracker_{label}.png"
|
filename = f"tracker_{label}.png"
|
||||||
path = self.run_dir / filename
|
path = self.run_dir / filename
|
||||||
cv2.imwrite(str(path), self._last_tracker_frame)
|
cv2.imwrite(str(path), self._last_tracker_frame)
|
||||||
print(f"[REC] Tracker snapshot: {filename}")
|
print(f"[REC] Tracker snapshot: {filename}")
|
||||||
|
threading.Thread(target=self._upload_file, args=(path, filename), daemon=True).start()
|
||||||
|
|
||||||
def save_summary(self, search_mode="", altitude=0, markers=None,
|
def save_summary(self, search_mode="", altitude=0, markers=None, landed=False, ugv_dispatched=False, ugv_target=None, extra=None):
|
||||||
landed=False, ugv_dispatched=False,
|
|
||||||
ugv_target=None, extra=None):
|
|
||||||
# Summary JSON file saving moved/removed as per request
|
|
||||||
# Still useful to see basic stats in terminal?
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
@@ -275,32 +287,49 @@ class RunRecorder:
|
|||||||
self._record_thread.join(timeout=3.0)
|
self._record_thread.join(timeout=3.0)
|
||||||
|
|
||||||
if self._last_tracker_frame is not None:
|
if self._last_tracker_frame is not None:
|
||||||
path = self.run_dir / "flight_path.png"
|
filename = "flight_path.png"
|
||||||
|
path = self.run_dir / filename
|
||||||
cv2.imwrite(str(path), self._last_tracker_frame)
|
cv2.imwrite(str(path), self._last_tracker_frame)
|
||||||
print(f"[REC] Flight path saved: {path}")
|
print(f"[REC] Flight path saved: {path}")
|
||||||
|
self._upload_file(path, filename)
|
||||||
|
|
||||||
if self._last_camera_frame is not None:
|
if self._last_camera_frame is not None:
|
||||||
path = self.run_dir / "camera_final.png"
|
filename = "camera_final.png"
|
||||||
|
path = self.run_dir / filename
|
||||||
cv2.imwrite(str(path), self._last_camera_frame)
|
cv2.imwrite(str(path), self._last_camera_frame)
|
||||||
|
self._upload_file(path, filename)
|
||||||
|
|
||||||
if self._tracker_writer:
|
if self._tracker_writer:
|
||||||
self._tracker_writer.release()
|
self._tracker_writer.release()
|
||||||
|
self._upload_file(self.run_dir / "flight_path.avi", "flight_path.avi")
|
||||||
if self._camera_writer:
|
if self._camera_writer:
|
||||||
self._camera_writer.release()
|
self._camera_writer.release()
|
||||||
|
self._upload_file(self.run_dir / "camera.avi", "camera.avi")
|
||||||
if self._gazebo_writer:
|
if self._gazebo_writer:
|
||||||
self._gazebo_writer.release()
|
self._gazebo_writer.release()
|
||||||
|
self._upload_file(self.run_dir / "gazebo.avi", "gazebo.avi")
|
||||||
|
|
||||||
self.stop_logging()
|
self.stop_logging()
|
||||||
self._log_file.close()
|
self._log_file.close()
|
||||||
|
self._upload_file(self._log_path, "log.txt")
|
||||||
|
|
||||||
duration = time.time() - self.start_time
|
duration = time.time() - self.start_time
|
||||||
mins = int(duration // 60)
|
mins = int(duration // 60)
|
||||||
secs = int(duration % 60)
|
secs = int(duration % 60)
|
||||||
|
|
||||||
|
if self.sim_id:
|
||||||
|
try:
|
||||||
|
requests.put(
|
||||||
|
f"{API_URL}/api/simulations/{self.sim_id}/time",
|
||||||
|
json={"search_time": self.search_duration, "total_time": duration},
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[REC] PUT time failed: {e}")
|
||||||
|
|
||||||
self._original_stdout.write(
|
self._original_stdout.write(
|
||||||
f"\n[REC] ═══════════════════════════════════════\n"
|
f"\n[REC] ═══════════════════════════════════════\n"
|
||||||
f"[REC] Run #{self.run_num} recorded\n"
|
f"[REC] Cloud Upload Complete (ID: {self.sim_id})\n"
|
||||||
f"[REC] Dir: {self.run_dir}\n"
|
|
||||||
f"[REC] Duration: {mins}m {secs}s\n"
|
f"[REC] Duration: {mins}m {secs}s\n"
|
||||||
f"[REC] Tracker: {self._tracker_frames} frames\n"
|
f"[REC] Tracker: {self._tracker_frames} frames\n"
|
||||||
f"[REC] Camera: {self._camera_frames} frames\n"
|
f"[REC] Camera: {self._camera_frames} frames\n"
|
||||||
@@ -308,7 +337,6 @@ class RunRecorder:
|
|||||||
f"[REC] ═══════════════════════════════════════\n"
|
f"[REC] ═══════════════════════════════════════\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class _TeeWriter:
|
class _TeeWriter:
|
||||||
def __init__(self, stream, log_file):
|
def __init__(self, stream, log_file):
|
||||||
self._stream = stream
|
self._stream = stream
|
||||||
|
|||||||
@@ -55,10 +55,10 @@ class CameraProcessor:
|
|||||||
return
|
return
|
||||||
|
|
||||||
processed = self.process_image(bgr)
|
processed = self.process_image(bgr)
|
||||||
self.raw_frames[name] = processed.copy()
|
self.raw_frames[name] = bgr.copy()
|
||||||
self.frames[name] = processed
|
self.frames[name] = processed
|
||||||
for fn in self.callbacks.get(name, []):
|
for fn in self.callbacks.get(name, []):
|
||||||
fn(name, processed)
|
fn(name, bgr.copy())
|
||||||
return cb
|
return cb
|
||||||
|
|
||||||
def process_image(self, image):
|
def process_image(self, image):
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ class ObjectDetector:
|
|||||||
self.aruco_params.minMarkerPerimeterRate = 0.01
|
self.aruco_params.minMarkerPerimeterRate = 0.01
|
||||||
self.aruco_params.maxMarkerPerimeterRate = 4.0
|
self.aruco_params.maxMarkerPerimeterRate = 4.0
|
||||||
self.aruco_params.adaptiveThreshWinSizeMin = 3
|
self.aruco_params.adaptiveThreshWinSizeMin = 3
|
||||||
self.aruco_params.adaptiveThreshWinSizeMax = 30
|
self.aruco_params.adaptiveThreshWinSizeMax = 53
|
||||||
self.aruco_params.adaptiveThreshWinSizeStep = 5
|
self.aruco_params.adaptiveThreshWinSizeStep = 10
|
||||||
self.aruco_params.adaptiveThreshConstant = 7
|
self.aruco_params.adaptiveThreshConstant = 7
|
||||||
self.aruco_params.minCornerDistanceRate = 0.01
|
self.aruco_params.minCornerDistanceRate = 0.01
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -140,14 +140,20 @@
|
|||||||
|
|
||||||
<model name="target_tag_1">
|
<model name="target_tag_1">
|
||||||
<static>true</static>
|
<static>true</static>
|
||||||
<pose>-5.0 -5.0 0.005 0 0 0</pose>
|
<pose>3.0 3.0 0.005 0 0 0</pose>
|
||||||
<link name="link">
|
<link name="link">
|
||||||
<visual name="v">
|
<visual name="v">
|
||||||
<geometry><box><size>0.5 0.5 0.01</size></box></geometry>
|
<geometry><box><size>0.5 0.5 0.01</size></box></geometry>
|
||||||
<material>
|
<material>
|
||||||
<ambient>1 1 1 1</ambient>
|
<ambient>1 1 1 1</ambient>
|
||||||
<diffuse>1 1 1 1</diffuse>
|
<diffuse>1 1 1 1</diffuse>
|
||||||
<pbr><metal><albedo_map>tags/aruco_DICT_4X4_50_1.png</albedo_map></metal></pbr>
|
<pbr>
|
||||||
|
<metal>
|
||||||
|
<albedo_map>tags/aruco_DICT_4X4_50_1.png</albedo_map>
|
||||||
|
<roughness>1.0</roughness>
|
||||||
|
<metalness>0.0</metalness>
|
||||||
|
</metal>
|
||||||
|
</pbr>
|
||||||
</material>
|
</material>
|
||||||
</visual>
|
</visual>
|
||||||
</link>
|
</link>
|
||||||
|
|||||||
@@ -161,7 +161,13 @@
|
|||||||
<material>
|
<material>
|
||||||
<ambient>1 1 1 1</ambient>
|
<ambient>1 1 1 1</ambient>
|
||||||
<diffuse>1 1 1 1</diffuse>
|
<diffuse>1 1 1 1</diffuse>
|
||||||
<pbr><metal><albedo_map>tags/aruco_DICT_4X4_50_1.png</albedo_map></metal></pbr>
|
<pbr>
|
||||||
|
<metal>
|
||||||
|
<albedo_map>tags/aruco_DICT_4X4_50_1.png</albedo_map>
|
||||||
|
<roughness>1.0</roughness>
|
||||||
|
<metalness>0.0</metalness>
|
||||||
|
</metal>
|
||||||
|
</pbr>
|
||||||
</material>
|
</material>
|
||||||
</visual>
|
</visual>
|
||||||
</link>
|
</link>
|
||||||
|
|||||||
Reference in New Issue
Block a user