From fc314227ed20998c0fd50ed6cdf8a30be497dada Mon Sep 17 00:00:00 2001 From: Adam Shapiro Date: Fri, 18 Oct 2024 17:12:36 -0400 Subject: [PATCH 1/5] Added README note about Windows Python installation. --- python/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/README.md b/python/README.md index f277a20f..ffa7c568 100644 --- a/python/README.md +++ b/python/README.md @@ -88,6 +88,10 @@ FusionEngine message specification. ### Usage Instructions +> Note for Windows users: You must install Python using an installer from +> https://www.python.org/downloads/windows/ and select "Add python.exe to PATH". (Do not install Python using Microsoft +> Store.) + #### Install From PyPI 1. Install Python (3.6 or later) and pip. From d63b57d275467b9fedb65f4b314af00e8bbd9c6f Mon Sep 17 00:00:00 2001 From: Adam Shapiro Date: Fri, 18 Oct 2024 17:13:37 -0400 Subject: [PATCH 2/5] Return the Analyzer instance from main(). This allows p1_display main() to be integrated within another application, which can then directly access the Analyzer instance to call additional functions, access the DataReader, etc. --- python/fusion_engine_client/analysis/analyzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/fusion_engine_client/analysis/analyzer.py b/python/fusion_engine_client/analysis/analyzer.py index c8717f30..6585e652 100755 --- a/python/fusion_engine_client/analysis/analyzer.py +++ b/python/fusion_engine_client/analysis/analyzer.py @@ -3042,7 +3042,7 @@ def main(): analyzer.generate_index(auto_open=not options.no_index) _logger.info("Output stored in '%s'." % os.path.abspath(output_dir)) - + return analyzer if __name__ == "__main__": main() From a482191c1b0b59a5030a5c6aa50228867edf8db2 Mon Sep 17 00:00:00 2001 From: Adam Shapiro Date: Fri, 18 Oct 2024 17:13:58 -0400 Subject: [PATCH 3/5] Pass in optional arguments list to p1_display main(). --- python/fusion_engine_client/analysis/analyzer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/fusion_engine_client/analysis/analyzer.py b/python/fusion_engine_client/analysis/analyzer.py index 6585e652..56f1750a 100755 --- a/python/fusion_engine_client/analysis/analyzer.py +++ b/python/fusion_engine_client/analysis/analyzer.py @@ -2812,7 +2812,7 @@ def _assign_colors(cls, elements, num_colors=None): return {e: colors[i % len(colors)] for i, e in enumerate(elements)} -def main(): +def main(args=None): parser = ArgumentParser(description="""\ Load and display information stored in a FusionEngine binary file. """) @@ -2908,7 +2908,7 @@ def main(): '-v', '--verbose', action='count', default=0, help="Print verbose/trace debugging messages.") - options = parser.parse_args() + options = parser.parse_args(args=args) # Configure logging. if options.verbose >= 1: From 05a88ff933ea26c974e9d684d54facea57e6f11f Mon Sep 17 00:00:00 2001 From: Adam Shapiro Date: Fri, 18 Oct 2024 17:14:41 -0400 Subject: [PATCH 4/5] Return calculated ENU displacement from plot_stationary_position_error(). --- python/fusion_engine_client/analysis/analyzer.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/python/fusion_engine_client/analysis/analyzer.py b/python/fusion_engine_client/analysis/analyzer.py index 56f1750a..56378593 100755 --- a/python/fusion_engine_client/analysis/analyzer.py +++ b/python/fusion_engine_client/analysis/analyzer.py @@ -858,18 +858,18 @@ def plot_stationary_position_error(self, truth_lla_deg): @param truth_lla_deg The truth LLA location (in degrees/meters). """ truth_ecef_m = np.array(geodetic2ecef(*truth_lla_deg, deg=True)) - self._plot_pose_displacement(title='Position Error', center_ecef_m=truth_ecef_m) + return self._plot_pose_displacement(title='Position Error', center_ecef_m=truth_ecef_m) def plot_pose_displacement(self): """! @brief Generate a topocentric (top-down) plot of position displacement, as well as plot of displacement over time. """ - self._plot_pose_displacement() + return self._plot_pose_displacement() def _plot_pose_displacement(self, title='Pose Displacement', center_ecef_m=None): if self.output_dir is None: - return + return None # Read the pose data. result = self.reader.read(message_types=[PoseMessage], source_ids=self.default_source_id, **self.params) @@ -877,13 +877,13 @@ def _plot_pose_displacement(self, title='Pose Displacement', center_ecef_m=None) if len(pose_data.p1_time) == 0: self.logger.info('No pose data available. Skipping displacement plots.') - return + return None # Remove invalid solutions. valid_idx = np.logical_and(~np.isnan(pose_data.p1_time), pose_data.solution_type != SolutionType.Invalid) if not np.any(valid_idx): self.logger.info('No valid position solutions detected. Skipping displacement plots.') - return + return None time = pose_data.p1_time[valid_idx] - float(self.t0) solution_type = pose_data.solution_type[valid_idx] @@ -906,6 +906,8 @@ def _plot_pose_displacement(self, title='Pose Displacement', center_ecef_m=None) self._plot_displacement(source=title, title=axis_title, time=time, solution_type=solution_type, displacement_enu_m=displacement_enu_m, std_enu_m=std_enu_m) + return displacement_enu_m + def plot_relative_position(self): """! @brief Generate a topocentric (top-down) plot of relative position vs base station, as well as plot of relative From 295a961ac73d87e1ef428206615655747869e284 Mon Sep 17 00:00:00 2001 From: Adam Shapiro Date: Fri, 18 Oct 2024 17:15:03 -0400 Subject: [PATCH 5/5] Constrain the 3D displacemen plot Y axis to min 0. --- python/fusion_engine_client/analysis/analyzer.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/python/fusion_engine_client/analysis/analyzer.py b/python/fusion_engine_client/analysis/analyzer.py index 56378593..3ef47a18 100755 --- a/python/fusion_engine_client/analysis/analyzer.py +++ b/python/fusion_engine_client/analysis/analyzer.py @@ -814,6 +814,7 @@ def _plot_displacement(self, source, time, solution_type, displacement_enu_m, st time_figure.update_layout(title_text=extra_text) # Plot the data. + max_3d_diff_m = [0.0] def _plot_data(name, idx, marker_style=None): style = {'mode': 'markers', 'marker': {'size': 8}, 'showlegend': True, 'legendgroup': name, 'hoverlabel': {'namelength': -1}} @@ -828,7 +829,9 @@ def _plot_data(name, idx, marker_style=None): topo_figure.add_trace(go.Scattergl(x=displacement_enu_m[0, idx], y=displacement_enu_m[1, idx], name=name, text=text, **style), 1, 1) - time_figure.add_trace(go.Scattergl(x=time[idx], y=np.linalg.norm(displacement_enu_m[:, idx], axis=0), + displacement_3d_m = np.linalg.norm(displacement_enu_m[:, idx], axis=0) + max_3d_diff_m[0] = max(max_3d_diff_m[0], np.nanmax(displacement_3d_m)) + time_figure.add_trace(go.Scattergl(x=time[idx], y=displacement_3d_m, name=name, text=text, **style), 1, 1) style['showlegend'] = False time_figure.add_trace(go.Scattergl(x=time[idx], y=displacement_enu_m[0, idx], name=name, @@ -847,6 +850,12 @@ def _plot_data(name, idx, marker_style=None): for type, info in _SOLUTION_TYPE_MAP.items(): _plot_data(info.name, solution_type == type, marker_style=info.style) + # Set the 3D displacement Y-axis limits to start at 0 and encompass the data. + max_y = 1.2 * max_3d_diff_m[0] + if max_y == 0.0: + max_y = 1.0 + time_figure['layout']['yaxis1'].update(range=[0, max_y]) + name = source.replace(' ', '_').lower() self._add_figure(name=f"{name}_top_down", figure=topo_figure, title=f"{source}: Top-Down (Topocentric)") self._add_figure(name=f"{name}_vs_time", figure=time_figure, title=f"{source}: vs. Time")