Skip to content
This repository was archived by the owner on Nov 13, 2025. It is now read-only.

Commit 66b0654

Browse files
authored
Complete autonomous landing module (#262)
* Complete autonomous landing module * remove typing import * remove largest area and nearest to center * Remove command queue and add a constant to config.yaml * modify test file * Add testing using config.yaml * Pass linters * Update test_auto_landing_worker.py * Formatting * pass -> continue
1 parent 67554d8 commit 66b0654

File tree

4 files changed

+348
-17
lines changed

4 files changed

+348
-17
lines changed

config.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,6 @@ cluster_estimation:
8282
communications:
8383
timeout: 60.0 # seconds
8484
worker_period: 0.5 # seconds
85+
86+
auto_landing:
87+
enabled: true

modules/auto_landing/auto_landing.py

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,21 @@
44
"""
55

66
import math
7+
from enum import Enum
78

89
from .. import merged_odometry_detections
910
from ..common.modules.logger import logger
1011

1112

13+
class DetectionSelectionStrategy(Enum):
14+
"""
15+
Strategies for selecting which detection to use when multiple targets are detected.
16+
"""
17+
18+
FIRST_DETECTION = "first_detection" # Use first detection in list (original behavior)
19+
HIGHEST_CONFIDENCE = "highest_confidence" # Choose detection with highest confidence
20+
21+
1222
class AutoLandingInformation:
1323
"""
1424
Information necessary for the LANDING_TARGET MAVLink command.
@@ -44,17 +54,21 @@ def create(
4454
im_h: float,
4555
im_w: float,
4656
local_logger: logger.Logger,
57+
selection_strategy: DetectionSelectionStrategy = DetectionSelectionStrategy.FIRST_DETECTION,
4758
) -> "tuple [bool, AutoLanding | None ]":
4859
"""
4960
fov_x: The horizontal camera field of view in degrees.
5061
fov_y: The vertical camera field of view in degrees.
5162
im_w: Width of image.
5263
im_h: Height of image.
64+
selection_strategy: Strategy for selecting which detection to use when multiple targets are detected.
5365
5466
Returns an AutoLanding object.
5567
"""
5668

57-
return True, AutoLanding(cls.__create_key, fov_x, fov_y, im_h, im_w, local_logger)
69+
return True, AutoLanding(
70+
cls.__create_key, fov_x, fov_y, im_h, im_w, local_logger, selection_strategy
71+
)
5872

5973
def __init__(
6074
self,
@@ -64,6 +78,7 @@ def __init__(
6478
im_h: float,
6579
im_w: float,
6680
local_logger: logger.Logger,
81+
selection_strategy: DetectionSelectionStrategy,
6782
) -> None:
6883
"""
6984
Private constructor, use create() method.
@@ -75,10 +90,41 @@ def __init__(
7590
self.im_h = im_h
7691
self.im_w = im_w
7792
self.__logger = local_logger
93+
self.__selection_strategy = selection_strategy
94+
95+
def _select_detection(self, detections: list) -> int | None:
96+
"""
97+
Select which detection to use based on the configured strategy.
98+
99+
Returns the index of the selected detection, or None if no suitable detection found.
100+
"""
101+
if not detections:
102+
return None
103+
104+
if (
105+
len(detections) == 1
106+
or self.__selection_strategy == DetectionSelectionStrategy.FIRST_DETECTION
107+
):
108+
return 0
109+
110+
if self.__selection_strategy == DetectionSelectionStrategy.HIGHEST_CONFIDENCE:
111+
# Find detection with highest confidence
112+
max_confidence = 0
113+
selected_index = 0
114+
115+
for i, detection in enumerate(detections):
116+
if detection.confidence > max_confidence:
117+
max_confidence = detection.confidence
118+
selected_index = i
119+
120+
return selected_index
121+
122+
# Default fallback
123+
return 0
78124

79125
def run(
80126
self, odometry_detections: merged_odometry_detections.MergedOdometryDetections
81-
) -> "tuple[bool, AutoLandingInformation]":
127+
) -> "tuple[bool, AutoLandingInformation | None]":
82128
"""
83129
Calculates the x and y angles in radians of the bounding box based on its center.
84130
@@ -87,8 +133,26 @@ def run(
87133
Returns an AutoLandingInformation object.
88134
"""
89135

90-
# TODO: Devise better algorithm to pick which detection to land at if several are detected
91-
x_center, y_center = odometry_detections.detections[0].get_centre()
136+
# Check if we have any detections
137+
if not odometry_detections.detections:
138+
self.__logger.warning("No detections available for auto-landing", True)
139+
return False, None
140+
141+
# Select which detection to use
142+
selected_index = self._select_detection(odometry_detections.detections)
143+
if selected_index is None:
144+
self.__logger.error("Failed to select detection for auto-landing", True)
145+
return False, None
146+
147+
selected_detection = odometry_detections.detections[selected_index]
148+
149+
# Log selection information
150+
self.__logger.info(
151+
f"Selected detection {selected_index + 1}/{len(odometry_detections.detections)} using strategy: {self.__selection_strategy.value}",
152+
True,
153+
)
154+
155+
x_center, y_center = selected_detection.get_centre()
92156

93157
angle_x = (x_center - self.im_w / 2) * (self.fov_x * (math.pi / 180)) / self.im_w
94158
angle_y = (y_center - self.im_h / 2) * (self.fov_y * (math.pi / 180)) / self.im_h

modules/auto_landing/auto_landing_worker.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import os
66
import pathlib
7+
import queue
78
import time
89

910
from utilities.workers import queue_proxy_wrapper
@@ -18,16 +19,23 @@ def auto_landing_worker(
1819
im_h: float,
1920
im_w: float,
2021
period: float,
22+
detection_strategy: auto_landing.DetectionSelectionStrategy,
2123
input_queue: queue_proxy_wrapper.QueueProxyWrapper,
2224
output_queue: queue_proxy_wrapper.QueueProxyWrapper,
2325
controller: worker_controller.WorkerController,
2426
) -> None:
2527
"""
26-
Worker process.
28+
Worker process for auto-landing operations.
2729
28-
period: Wait time in seconds.
29-
input_queue and output_queue are data queues.
30-
controller is how the main process communicates to this worker process.
30+
fov_x: Horizontal field of view in degrees.
31+
fov_y: Vertical field of view in degrees.
32+
im_h: Image height in pixels.
33+
im_w: Image width in pixels.
34+
period: Wait time in seconds between processing cycles.
35+
detection_strategy: Strategy for selecting detection when multiple targets are present.
36+
input_queue: Queue for receiving merged odometry detections.
37+
output_queue: Queue for sending auto-landing information.
38+
controller: Worker controller for pause/exit management.
3139
"""
3240

3341
worker_name = pathlib.Path(__file__).stem
@@ -42,25 +50,33 @@ def auto_landing_worker(
4250

4351
local_logger.info("Logger initialized", True)
4452

45-
result, auto_lander = auto_landing.AutoLanding.create(fov_x, fov_y, im_h, im_w, local_logger)
46-
53+
# Create auto-landing instance
54+
result, auto_lander = auto_landing.AutoLanding.create(
55+
fov_x, fov_y, im_h, im_w, local_logger, detection_strategy
56+
)
4757
if not result:
48-
local_logger.error("Worker failed to create class object", True)
58+
local_logger.error("Worker failed to create AutoLanding object", True)
4959
return
5060

5161
# Get Pylance to stop complaining
5262
assert auto_lander is not None
5363

64+
local_logger.info("Auto-landing worker initialized successfully", True)
65+
5466
while not controller.is_exit_requested():
5567
controller.check_pause()
5668

57-
input_data = input_queue.queue.get()
58-
if input_data is None:
69+
# Process detections if available
70+
input_data = None
71+
try:
72+
input_data = input_queue.queue.get_nowait()
73+
except queue.Empty:
74+
# No data available, continue
5975
continue
6076

61-
result, value = auto_lander.run(input_data)
62-
if not result:
63-
continue
77+
if input_data is not None:
78+
result, landing_info = auto_lander.run(input_data)
79+
if result and landing_info:
80+
output_queue.queue.put(landing_info)
6481

65-
output_queue.queue.put(value)
6682
time.sleep(period)

0 commit comments

Comments
 (0)