Skip to content

Commit 2ada0da

Browse files
author
Saumya Saksena
committed
Add tello app
1 parent af7b842 commit 2ada0da

File tree

1 file changed

+244
-0
lines changed

1 file changed

+244
-0
lines changed

tello_vision/app.py

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
"""Main application for Tello Vision.
2+
3+
Integrates drone control, detection, and visualization.
4+
"""
5+
6+
import argparse
7+
import time
8+
from pathlib import Path
9+
10+
import cv2
11+
import numpy as np
12+
import yaml
13+
14+
from .detectors.base_detector import BaseDetector
15+
from .tello_controller import TelloController
16+
from .visualizer import Visualizer
17+
18+
19+
class TelloVisionApp:
20+
"""Main application for Tello drone with vision capabilities."""
21+
22+
def __init__(self, config_path: str = "config.yaml"):
23+
"""Initialize application.
24+
25+
Args:
26+
config_path: Path to configuration file
27+
"""
28+
# Load configuration
29+
with open(config_path, "r") as f:
30+
self.config = yaml.safe_load(f)
31+
32+
# Initialize components
33+
self.drone = TelloController(self.config["drone"])
34+
35+
# Initialize detector
36+
detector_config = self.config["detector"]
37+
backend = detector_config["backend"]
38+
backend_config = detector_config[backend]
39+
40+
self.detector = BaseDetector.create_detector(backend, backend_config)
41+
42+
# Initialize visualizer
43+
self.visualizer = Visualizer(self.config["visualization"])
44+
45+
# Processing config
46+
self.processing_config = self.config["processing"]
47+
48+
# State
49+
self.running = False
50+
self.frame_count = 0
51+
self.fps = 0.0
52+
self.last_fps_time = time.time()
53+
54+
# Output directory
55+
self.output_dir = Path(self.processing_config["output_dir"])
56+
self.output_dir.mkdir(parents=True, exist_ok=True)
57+
58+
def initialize(self) -> bool:
59+
"""Initialize all components.
60+
61+
Returns:
62+
True if initialization successful
63+
"""
64+
print("=" * 50)
65+
print("Tello Vision - Modern Instance Segmentation")
66+
print("=" * 50)
67+
68+
# Load detector model
69+
print("\n[1/3] Loading detection model...")
70+
self.detector.load_model()
71+
self.detector.warmup()
72+
73+
# Connect to drone
74+
print("\n[2/3] Connecting to drone...")
75+
if not self.drone.connect():
76+
print("Failed to connect to drone!")
77+
return False
78+
79+
# Setup keyboard controls
80+
print("\n[3/3] Setting up controls...")
81+
self.drone.setup_keyboard_controls(self.config["controls"])
82+
83+
print("\n✓ Initialization complete!")
84+
print("\nControls:")
85+
for action, key in self.config["controls"].items():
86+
print(f" {action}: {key}")
87+
88+
return True
89+
90+
def process_frame(self, frame: np.ndarray) -> np.ndarray:
91+
"""Process a single frame with detection and visualization.
92+
93+
Args:
94+
frame: Input frame from drone
95+
96+
Returns:
97+
Processed frame with visualizations
98+
"""
99+
# Skip frames if configured
100+
frame_skip = self.processing_config.get("frame_skip", 0)
101+
if frame_skip > 0 and self.frame_count % (frame_skip + 1) != 0:
102+
return frame
103+
104+
# Run detection
105+
result = self.detector.detect(frame)
106+
107+
# Filter by target classes if configured
108+
target_classes = self.config["detector"].get("target_classes", [])
109+
if target_classes:
110+
result = result.filter_by_class(target_classes)
111+
112+
# Draw detections
113+
frame = self.visualizer.draw_detections(frame, result)
114+
115+
# Draw stats if enabled
116+
if self.processing_config.get("display_stats", True):
117+
stats = self.drone.get_stats_text()
118+
stats.append(f"Detections: {result.count}")
119+
stats.append(f"Inference: {result.inference_time*1000:.1f}ms")
120+
frame = self.visualizer.draw_stats(frame, stats)
121+
122+
# Draw FPS if enabled
123+
if self.processing_config.get("display_fps", True):
124+
frame = self.visualizer.draw_fps(frame, self.fps)
125+
126+
# Record if enabled
127+
if self.drone.is_recording:
128+
self.drone.write_frame(frame)
129+
130+
return frame
131+
132+
def update_fps(self) -> None:
133+
"""Update FPS counter."""
134+
self.frame_count += 1
135+
136+
current_time = time.time()
137+
elapsed = current_time - self.last_fps_time
138+
139+
if elapsed >= 1.0:
140+
self.fps = self.frame_count / elapsed
141+
self.frame_count = 0
142+
self.last_fps_time = current_time
143+
144+
# Update drone stats
145+
self.drone.update_stats()
146+
147+
def run(self) -> None:
148+
"""Run the main application loop."""
149+
self.running = True
150+
151+
print("\n" + "=" * 50)
152+
print("Starting video stream...")
153+
print("Press 'tab' to takeoff, 'backspace' to land")
154+
print("Press 'p' to quit")
155+
print("=" * 50 + "\n")
156+
157+
window_name = self.processing_config.get("window_name", "Tello Vision")
158+
159+
try:
160+
while self.running:
161+
# Get frame from drone
162+
frame = self.drone.get_frame()
163+
164+
if frame is None:
165+
continue
166+
167+
# Process frame
168+
processed_frame = self.process_frame(frame)
169+
170+
# Display if enabled
171+
if self.processing_config.get("display_window", True):
172+
cv2.imshow(window_name, processed_frame)
173+
174+
# Update FPS
175+
self.update_fps()
176+
177+
# Check for quit
178+
key = cv2.waitKey(1) & 0xFF
179+
if key == ord("p") or key == 27: # 'p' or ESC
180+
break
181+
elif key == ord("r"):
182+
self.toggle_recording()
183+
elif key == 13: # Enter
184+
self.take_photo(processed_frame)
185+
186+
except KeyboardInterrupt:
187+
print("\nInterrupted by user")
188+
189+
finally:
190+
self.shutdown()
191+
192+
def toggle_recording(self) -> None:
193+
"""Toggle video recording."""
194+
if not self.drone.is_recording:
195+
timestamp = time.strftime("%Y%m%d_%H%M%S")
196+
output_path = str(self.output_dir / f"tello_video_{timestamp}.mp4")
197+
self.drone.start_recording(output_path)
198+
else:
199+
self.drone.stop_recording()
200+
201+
def take_photo(self, frame: np.ndarray) -> None:
202+
"""Save current frame as photo."""
203+
timestamp = time.strftime("%Y%m%d_%H%M%S")
204+
output_path = str(self.output_dir / f"tello_photo_{timestamp}.jpg")
205+
cv2.imwrite(output_path, frame)
206+
print(f"Photo saved: {output_path}")
207+
208+
def shutdown(self) -> None:
209+
"""Shutdown application and cleanup."""
210+
print("\nShutting down...")
211+
self.running = False
212+
213+
# Cleanup
214+
cv2.destroyAllWindows()
215+
self.drone.disconnect()
216+
217+
print("Shutdown complete")
218+
219+
220+
def main():
221+
"""Main entry point."""
222+
parser = argparse.ArgumentParser(
223+
description="Tello Vision - Modern instance segmentation for DJI Tello"
224+
)
225+
parser.add_argument(
226+
"--config", type=str, default="config.yaml", help="Path to configuration file"
227+
)
228+
parser.add_argument(
229+
"--no-drone",
230+
action="store_true",
231+
help="Run without connecting to drone (test mode)",
232+
)
233+
234+
args = parser.parse_args()
235+
236+
# Create and run app
237+
app = TelloVisionApp(args.config)
238+
239+
if app.initialize():
240+
app.run()
241+
242+
243+
if __name__ == "__main__":
244+
main()

0 commit comments

Comments
 (0)