Skip to content

Commit 56a2028

Browse files
committed
1. Fix alt_ellipsoid property in px4_micrortps.py. PX4 VehicleGlobalPosition message in SITL simulation seems to give an invalid value so it is calculated from altitude AMSL using egm96 geoid instead. 2. Rename _camera_pitch_too_high to _camera_roll_or_pitch_too_high and check for roll as well. This is relevant for the static camera case in the ArduPilot gazebo-iris simulation which is implemented here. This is less relevant for the roll-stabilized gimbal on the PX4 simulated Typhoon H480 model but a TODO is added for that as well.
1 parent 7b1a71d commit 56a2028

File tree

3 files changed

+54
-12
lines changed

3 files changed

+54
-12
lines changed

gisnav/autopilots/px4_micrortps.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from sensor_msgs.msg import CameraInfo, Image
99
from px4_msgs.msg import VehicleAttitude, VehicleLocalPosition, VehicleGlobalPosition, GimbalDeviceAttitudeStatus, \
1010
GimbalDeviceSetAttitude
11+
from pygeodesy.geoids import GeoidPGM
1112

1213
from gisnav.assertions import assert_type
1314
from gisnav.data import TimePair, Attitude
@@ -59,6 +60,8 @@ def __init__(self, node: rclpy.node.Node, camera_info_topic: str, image_topic: s
5960
self._gimbal_attitude = None
6061
self._time_sync = None
6162

63+
self._egm96 = GeoidPGM('/usr/share/GeographicLib/geoids/egm96-5.pgm', kind=-3)
64+
6265
# region Properties
6366
@property
6467
def _vehicle_local_position(self) -> Optional[VehicleLocalPosition]:
@@ -113,6 +116,30 @@ def _time_sync(self) -> Optional[TimePair]:
113116
def _time_sync(self, value: Optional[TimePair]) -> None:
114117
assert_type(value, get_args(Optional[TimePair]))
115118
self.__time_sync = value
119+
120+
@property
121+
def _egm96(self) -> GeoidPGM:
122+
"""Geoid for getting alt_ellipsoid from alt (AMSL) altitude
123+
124+
It looks like the PX4 SITL simulation VehcileGlobalPosition provides an invalid alt_ellipsoid so this is used
125+
to calculate it from alt (AMSL) instead.
126+
127+
.. code-block: bash
128+
:caption: geographiclib needed
129+
130+
sudo apt install geographiclib-tools
131+
sudo geographiclib-get-geoids egm96-5
132+
133+
.. seealso::
134+
`Avoiding Pitfalls Related to Ellipsoid Height and Height Above Mean Sea Level
135+
<https://wiki.ros.org/mavros#mavros.2FPlugins.Avoiding_Pitfalls_Related_to_Ellipsoid_Height_and_Height_Above_Mean_Sea_Level>`_
136+
"""
137+
return self.__egm96
138+
139+
@_egm96.setter
140+
def _egm96(self, value: GeoidPGM) -> None:
141+
assert_type(value, GeoidPGM)
142+
self.__egm96 = value
116143
# endregion
117144

118145
# region Private
@@ -288,11 +315,21 @@ def altitude_amsl(self) -> Optional[float]:
288315
def altitude_ellipsoid(self) -> Optional[float]:
289316
"""Vehicle altitude in meters above WGS 84 ellipsoid or None if not available
290317
318+
.. note::
319+
Looks like the PX4 SITL returns an invalid alt_ellipsoid in the VehicleGlobalPosition message:
320+
``alt_ellipsoid: -1844674428928.0``. Ellipsoid altitude is therefore calculated from altitude AMSL using
321+
egm96 geoid instead.
322+
291323
.. seealso::
292324
:py:attr:`.altitude_amsl`
293325
"""
294-
if self._vehicle_global_position is not None and hasattr(self._vehicle_global_position, 'alt_ellipsoid'):
295-
return self._vehicle_global_position.alt_ellipsoid
326+
# alt_ellipsoid invalid -- commented out
327+
#if self._vehicle_global_position is not None and hasattr(self._vehicle_global_position, 'alt_ellipsoid'):
328+
# return self._vehicle_global_position.alt_ellipsoid
329+
#else:
330+
# return None
331+
if self.altitude_amsl is not None and self.global_position is not None:
332+
return self.altitude_amsl + self._egm96.height(self.global_position.lat, self.global_position.lon)
296333
else:
297334
return None
298335

gisnav/data.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,8 +558,10 @@ def _estimate_position(self, ground_elevation: Optional[float], ground_elevation
558558
# Not a valid position estimate
559559
return None
560560

561+
561562
lon, lat = t_wgs84.squeeze()[1::-1]
562563
alt = t_wgs84[2]
564+
563565
position = Position(
564566
xy=GeoPoint(lon, lat, crs), # lon-lat order
565567
z_ground=alt,

gisnav/nodes/base_node.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,7 @@ def _should_request_new_map(self, bbox: GeoSquare) -> bool:
923923
use_gimbal_projection = self.get_parameter('map_update.gimbal_projection').get_parameter_value().bool_value
924924
if use_gimbal_projection:
925925
max_pitch = self.get_parameter('map_update.max_pitch').get_parameter_value().integer_value
926-
if self._camera_pitch_too_high(max_pitch):
926+
if self._camera_roll_or_pitch_too_high(max_pitch):
927927
self.get_logger().warn(f'Camera pitch not available or above maximum {max_pitch}. Will not update map.')
928928
return False
929929

@@ -1091,7 +1091,8 @@ def _should_estimate(self, img: np.ndarray) -> bool:
10911091
"""Determines whether :meth:`._estimate` should be called
10921092
10931093
Match should be attempted if (1) a reference map has been retrieved, (2) there are no pending match results,
1094-
(3) camera pitch is not too high (e.g. facing horizon instead of nadir), and (4) drone is not flying too low.
1094+
(3) camera roll or pitch is not too high (e.g. facing horizon instead of nadir), and (4) drone is not flying
1095+
too low.
10951096
10961097
:param img: Image from which to estimate pose
10971098
:return: True if pose estimation be attempted
@@ -1106,10 +1107,11 @@ def _should_estimate(self, img: np.ndarray) -> bool:
11061107
self.get_logger().debug(f'Previous pose estimation results pending. Skipping pose estimation.')
11071108
return False
11081109

1109-
# Check condition (3) - whether camera pitch is too large
1110+
# Check condition (3) - whether camera roll/pitch is too large
11101111
max_pitch = self.get_parameter('misc.max_pitch').get_parameter_value().integer_value
1111-
if self._camera_pitch_too_high(max_pitch):
1112-
self.get_logger().warn(f'Camera pitch not available or above limit {max_pitch}. Skipping pose estimation.')
1112+
if self._camera_roll_or_pitch_too_high(max_pitch):
1113+
self.get_logger().warn(f'Camera roll or pitch not available or above limit {max_pitch}. Skipping pose '
1114+
f'estimation.')
11131115
return False
11141116

11151117
# Check condition (4) - whether vehicle altitude is too low
@@ -1205,11 +1207,11 @@ def _is_valid_estimate(self, fixed_camera: FixedCamera, input_data: InputData) -
12051207
# endregion
12061208

12071209
# region Shared Logic
1208-
def _camera_pitch_too_high(self, max_pitch: Union[int, float]) -> bool:
1209-
"""Returns True if (set) camera pitch exceeds given limit OR camera pitch is unknown
1210+
def _camera_roll_or_pitch_too_high(self, max_pitch: Union[int, float]) -> bool:
1211+
"""Returns True if (set) camera roll or pitch exceeds given limit OR camera pitch is unknown
12101212
1211-
Used to determine whether camera pitch setting is too high up from nadir to make matching against a map
1212-
not worthwhile.
1213+
Used to determine whether camera roll or pitch is too high up from nadir to make matching against a map
1214+
not worthwhile. Checks roll for static camera, but assumes zero roll for 2-axis gimbal (static_camera: False).
12131215
12141216
.. note::
12151217
Uses actual vehicle attitude (instead of gimbal set attitude) if static_camera ROS param is True
@@ -1221,6 +1223,7 @@ def _camera_pitch_too_high(self, max_pitch: Union[int, float]) -> bool:
12211223
static_camera = self.get_parameter('misc.static_camera').get_parameter_value().bool_value
12221224
pitch = None
12231225
if self._bridge.gimbal_set_attitude is not None and not static_camera:
1226+
# TODO: do not assume zero roll here - camera attitude handling needs refactoring
12241227
# +90 degrees to re-center from FRD frame to nadir-facing camera as origin for max pitch comparison
12251228
pitch = np.degrees(self._bridge.gimbal_set_attitude.pitch) + 90
12261229
else:
@@ -1232,7 +1235,7 @@ def _camera_pitch_too_high(self, max_pitch: Union[int, float]) -> bool:
12321235
self.get_logger().warn('Vehicle attitude was not available, assuming static camera pitch too high.')
12331236
return True
12341237
else:
1235-
pitch = self._bridge.attitude.pitch
1238+
pitch = max(self._bridge.attitude.pitch, self._bridge.attitude.roll)
12361239

12371240
assert pitch is not None
12381241
if pitch > max_pitch:

0 commit comments

Comments
 (0)