Skip to content

Commit 9c5825b

Browse files
committed
1. Handle vehicle body attitude in static_camera: True case (not yet handled in _is_valid_estimate, TODO added there). 2. Handle MAVLink GPS_INPUT yaw north correctly (36000 instead of 0 cdeg). 3. Adjust GPS_INPUT usec field for estimation delay.
1 parent 850270c commit 9c5825b

File tree

2 files changed

+66
-22
lines changed

2 files changed

+66
-22
lines changed

gisnav/nodes/base_node.py

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -416,14 +416,24 @@ def _r_guess(self) -> Optional[np.ndarray]:
416416
Should be roughly same as rotation matrix stored in :py:attr:`._pose_guess`, even though it is derived via
417417
a different route. If gimbal is not stabilized to its set position, the rotation matrix will be different.
418418
"""
419+
static_camera = self.get_parameter('misc.static_camera').get_parameter_value().bool_value
419420
if self._bridge.gimbal_set_attitude is None:
420-
self.get_logger().warn('Gimbal set attitude not available, will not provide pose guess.')
421-
return None
422-
423-
assert_type(self._bridge.gimbal_set_attitude, Attitude)
424-
gimbal_set_attitude = self._bridge.gimbal_set_attitude.to_esd() # Need coordinates in image frame, not NED
425-
426-
return gimbal_set_attitude.r
421+
if not static_camera:
422+
self.get_logger().warn('Gimbal set attitude not available, will not provide pose guess.')
423+
return None
424+
else:
425+
if self._bridge.attitude is not None:
426+
attitude = self._bridge.attitude.as_rotation()
427+
attitude *= Rotation.from_euler('XYZ', [0, -np.pi/2, 0])
428+
return Attitude(attitude.as_quat()).to_esd().r
429+
else:
430+
self.get_logger().warn('Vehicle attitude not available, will not provide pose guess for static '
431+
'camera.')
432+
return None
433+
else:
434+
assert_type(self._bridge.gimbal_set_attitude, Attitude)
435+
gimbal_set_attitude = self._bridge.gimbal_set_attitude.to_esd() # Need coordinates in image frame, not NED
436+
return gimbal_set_attitude.r
427437

428438
@property
429439
def _wms_results_pending(self) -> bool:
@@ -524,8 +534,10 @@ def _estimation_inputs(self) -> Optional[Tuple[InputData, ContextualMapData]]:
524534
ground_elevation_ellipsoid=self._bridge.ground_elevation_ellipsoid,
525535
)
526536

537+
static_camera = self.get_parameter('misc.static_camera').get_parameter_value().bool_value
538+
527539
# Get cropped and rotated map
528-
if self._bridge.gimbal_set_attitude is not None:
540+
if self._bridge.gimbal_set_attitude is not None and not static_camera:
529541
camera_yaw = self._bridge.gimbal_set_attitude.yaw
530542
assert_type(camera_yaw, float)
531543
assert -np.pi <= camera_yaw <= np.pi, f'Unexpected gimbal yaw value: {camera_yaw} ([-pi, pi] expected).'
@@ -772,12 +784,26 @@ def _guess_fov_center(self, origin: Position) -> Optional[GeoPoint]:
772784
:param origin: Camera position
773785
:return: Center of the projected FOV, or None if not available
774786
"""
787+
static_camera = self.get_parameter('misc.static_camera').get_parameter_value().bool_value
788+
775789
if self._bridge.gimbal_set_attitude is None:
776-
self.get_logger().warn('Gimbal set attitude not available, cannot project gimbal FOV.')
777-
return None
790+
if not static_camera:
791+
self.get_logger().warn('Gimbal set attitude not available, cannot project gimbal FOV.')
792+
return None
793+
else:
794+
if self._bridge.attitude is not None:
795+
attitude = self._bridge.attitude.as_rotation()
796+
attitude *= Rotation.from_euler('XYZ', [0, -np.pi/2, 0])
797+
gimbal_set_attitude = Attitude(attitude.as_quat()).to_esd().r
798+
else:
799+
self.get_logger().warn('Vehicle attitude not available, will not provide pose guess for static '
800+
'camera.')
801+
return None
802+
else:
803+
assert_type(self._bridge.gimbal_set_attitude, Attitude)
804+
gimbal_set_attitude = self._bridge.gimbal_set_attitude.to_esd() # Need coordinates in image frame, not NED
778805

779-
assert_type(self._bridge.gimbal_set_attitude, Attitude)
780-
gimbal_set_attitude = self._bridge.gimbal_set_attitude.to_esd() # Need coordinates in image frame, not NED
806+
assert gimbal_set_attitude is not None
781807

782808
if self._bridge.camera_data is None:
783809
self.get_logger().warn('Camera data not available, could not create a mock pose to generate a FOV guess.')
@@ -1132,7 +1158,7 @@ def _is_valid_estimate(self, fixed_camera: FixedCamera, input_data: InputData) -
11321158
"""
11331159
static_camera = self.get_parameter('misc.static_camera').get_parameter_value().bool_value
11341160
if static_camera:
1135-
# TODO add vehicle body roll & pitch
1161+
# TODO add vehicle body roll & pitch from input data
11361162
r_guess = Attitude(Rotation.from_rotvec([0, -np.pi/2, 0]).as_quat()).to_esd().as_rotation()
11371163

11381164
if input_data.r_guess is None and not static_camera:
@@ -1176,21 +1202,33 @@ def _camera_pitch_too_high(self, max_pitch: Union[int, float]) -> bool:
11761202
Used to determine whether camera pitch setting is too high up from nadir to make matching against a map
11771203
not worthwhile.
11781204
1205+
.. note::
1206+
Uses actual vehicle attitude (instead of gimbal set attitude) if static_camera ROS param is True
1207+
11791208
:param max_pitch: The limit for the pitch in degrees from nadir over which it will be considered too high
11801209
:return: True if pitch is too high
11811210
"""
11821211
assert_type(max_pitch, get_args(Union[int, float]))
1183-
if self._bridge.gimbal_set_attitude is not None:
1212+
static_camera = self.get_parameter('misc.static_camera').get_parameter_value().bool_value
1213+
pitch = None
1214+
if self._bridge.gimbal_set_attitude is not None and not static_camera:
11841215
# +90 degrees to re-center from FRD frame to nadir-facing camera as origin for max pitch comparison
11851216
pitch = np.degrees(self._bridge.gimbal_set_attitude.pitch) + 90
1186-
if pitch > max_pitch:
1187-
self.get_logger().warn(f'Camera pitch {pitch} is above limit {max_pitch}.')
1188-
return True
11891217
else:
1190-
static_camera = self.get_parameter('misc.static_camera').get_parameter_value().bool_value
11911218
if not static_camera:
11921219
self.get_logger().warn('Gimbal attitude was not available, assuming camera pitch too high.')
11931220
return True
1221+
else:
1222+
if self._bridge.attitude is None:
1223+
self.get_logger().warn('Vehicle attitude was not available, assuming static camera pitch too high.')
1224+
return True
1225+
else:
1226+
pitch = self._bridge.attitude.pitch
1227+
1228+
assert pitch is not None
1229+
if pitch > max_pitch:
1230+
self.get_logger().warn(f'Camera pitch {pitch} is above limit {max_pitch}.')
1231+
return True
11941232

11951233
return False
11961234

gisnav/nodes/mock_gps_node.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,12 @@ def _generate_gps_input(self, fixed_camera: FixedCamera) -> dict:
117117
`GPS_INPUT_IGNORE_FLAGS <https://mavlink.io/en/messages/common.html#GPS_INPUT_IGNORE_FLAGS>`_
118118
"""
119119
position = fixed_camera.position
120-
gps_time = GPSTime.from_datetime(datetime.now())
120+
gps_time = GPSTime.from_datetime(datetime.now()) # TODO: use usec here, not now
121121

122122
msg = {}
123-
msg['usec'] = int(time.time() * 1e3)
123+
124+
# Adjust UTC epoch timestamp for estimation delay
125+
msg['usec'] = int(time.time_ns() / 1e3) - (self._bridge.synchronized_time - fixed_camera.timestamp)
124126
msg['gps_id'] = 0
125127
msg['ignore_flags'] = 56 # vel_horiz + vel_vert + speed_accuracy
126128
msg['time_week'] = gps_time.week_number
@@ -131,14 +133,18 @@ def _generate_gps_input(self, fixed_camera: FixedCamera) -> dict:
131133
msg['alt'] = position.z_amsl # float(position.z_ellipsoid) # ArduPilot Gazebo SITL expects AMSL
132134
msg['horiz_accuracy'] = 10.0 # position.eph
133135
msg['vert_accuracy'] = 3.0 # position.epv
134-
msg['speed_accuracy'] = np.nan
136+
msg['speed_accuracy'] = np.nan # should be in ignore_flags
135137
msg['hdop'] = 0.0
136138
msg['vdop'] = 0.0
137139
msg['vn'] = np.nan # should be in ignore_flags
138140
msg['ve'] = np.nan # should be in ignore_flags
139141
msg['vd'] = np.nan # should be in ignore_flags
140142
msg['satellites_visible'] = np.iinfo(np.uint8).max
141-
msg['yaw'] = int(np.degrees(position.attitude.yaw % (2 * np.pi)) * 100)
143+
144+
# TODO check yaw sign (NED or ENU?)
145+
yaw = int(np.degrees(position.attitude.yaw % (2 * np.pi)) * 100)
146+
yaw = 36000 if yaw == 0 else yaw # MAVLink definition 0 := not available
147+
msg['yaw'] = yaw
142148

143149
return msg
144150

0 commit comments

Comments
 (0)