trajectopy 4.2.4__tar.gz → 4.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. {trajectopy-4.2.4 → trajectopy-4.3.1}/PKG-INFO +1 -1
  2. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_data/report_settings.json +2 -2
  3. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_scripts/ate.py +2 -1
  4. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_merging.py +9 -3
  5. trajectopy-4.3.1/trajectopy/__version__.py +1 -0
  6. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/core/settings.py +4 -2
  7. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/core/trajectory.py +14 -0
  8. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/managers/file_manager.py +2 -2
  9. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/managers/requests.py +0 -2
  10. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/managers/trajectory_manager.py +8 -80
  11. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/utils.py +38 -16
  12. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/json_settings_view.py +4 -0
  13. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/trajectory_context_menu.py +51 -283
  14. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/lib/alignment/data.py +1 -1
  15. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/settings.md +8 -6
  16. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/user_guide.md +2 -1
  17. trajectopy-4.2.4/trajectopy/__version__.py +0 -1
  18. {trajectopy-4.2.4 → trajectopy-4.3.1}/.gitattributes +0 -0
  19. {trajectopy-4.2.4 → trajectopy-4.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  20. {trajectopy-4.2.4 → trajectopy-4.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  21. {trajectopy-4.2.4 → trajectopy-4.3.1}/.gitignore +0 -0
  22. {trajectopy-4.2.4 → trajectopy-4.3.1}/.pre-commit-config.yaml +0 -0
  23. {trajectopy-4.2.4 → trajectopy-4.3.1}/.pylintrc +0 -0
  24. {trajectopy-4.2.4 → trajectopy-4.3.1}/CITATION.cff +0 -0
  25. {trajectopy-4.2.4 → trajectopy-4.3.1}/LICENSE +0 -0
  26. {trajectopy-4.2.4 → trajectopy-4.3.1}/README.md +0 -0
  27. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_data/KITTI_ORB.traj +0 -0
  28. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_data/KITTI_SPTAM.traj +0 -0
  29. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_data/KITTI_gt.traj +0 -0
  30. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_data/default.mplstyle +0 -0
  31. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_data/matplotlib_settings.json +0 -0
  32. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_scripts/align.py +0 -0
  33. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_scripts/alignment.py +0 -0
  34. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_scripts/mpl_plot.py +0 -0
  35. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_scripts/multi_report.py +0 -0
  36. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_scripts/rpe.py +0 -0
  37. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_scripts/single_report.py +0 -0
  38. {trajectopy-4.2.4 → trajectopy-4.3.1}/example_scripts/trajectory_plot.py +0 -0
  39. {trajectopy-4.2.4 → trajectopy-4.3.1}/pyproject.toml +0 -0
  40. {trajectopy-4.2.4 → trajectopy-4.3.1}/requirements.txt +0 -0
  41. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/__init__.py +0 -0
  42. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/data/abs_dev.result +0 -0
  43. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/data/align_entry.result +0 -0
  44. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/data/generated_trajectory.traj +0 -0
  45. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/data/noisy_trajectory.traj +0 -0
  46. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/data/open_loop_trajectory.traj +0 -0
  47. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/data/rel_dev_meter.result +0 -0
  48. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/data/rel_dev_second.result +0 -0
  49. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_alignment.py +0 -0
  50. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_approximation.py +0 -0
  51. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_comparison.py +0 -0
  52. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_io.py +0 -0
  53. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_matching.py +0 -0
  54. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_pipeline.py +0 -0
  55. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_positions_rotations.py +0 -0
  56. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_processing_edge_cases.py +0 -0
  57. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_processing_errors.py +0 -0
  58. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_readers_edge_cases.py +0 -0
  59. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_readers_errors.py +0 -0
  60. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_settings.py +0 -0
  61. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_settings_edge_cases.py +0 -0
  62. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_settings_validation.py +0 -0
  63. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_sorting.py +0 -0
  64. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_trajectory.py +0 -0
  65. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/test_trajectory_validation.py +0 -0
  66. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/testdata.py +0 -0
  67. {trajectopy-4.2.4 → trajectopy-4.3.1}/test/util.py +0 -0
  68. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/LICENSE +0 -0
  69. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/__init__.py +0 -0
  70. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/__main__.py +0 -0
  71. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/cli/__init__.py +0 -0
  72. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/cli/main.py +0 -0
  73. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/core/__init__.py +0 -0
  74. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/core/positions.py +0 -0
  75. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/core/rotations.py +0 -0
  76. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/exceptions.py +0 -0
  77. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/__init__.py +0 -0
  78. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/app.py +0 -0
  79. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/managers/__init__.py +0 -0
  80. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/managers/plot_manager.py +0 -0
  81. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/managers/session_manager.py +0 -0
  82. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/managers/ui_manager.py +0 -0
  83. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/models/__init__.py +0 -0
  84. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/models/entries.py +0 -0
  85. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/models/property_model.py +0 -0
  86. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/models/result_model.py +0 -0
  87. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/models/selection.py +0 -0
  88. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/models/table_model.py +0 -0
  89. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/models/trajectory_model.py +0 -0
  90. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/resources/full-icon-poppins.png +0 -0
  91. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/resources/icon-bg.ico +0 -0
  92. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/resources/icon-bg.png +0 -0
  93. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/resources/icon.png +0 -0
  94. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/themes.py +0 -0
  95. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/__init__.py +0 -0
  96. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/about_window.py +0 -0
  97. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/alignment_edit_window.py +0 -0
  98. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/dof_organizer.py +0 -0
  99. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/main_window.py +0 -0
  100. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/mpl_plot_tabs.py +0 -0
  101. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/playback_window.py +0 -0
  102. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/progress_window.py +0 -0
  103. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/properties_window.py +0 -0
  104. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/result_context_menu.py +0 -0
  105. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/result_selection_window.py +0 -0
  106. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/result_table_view.py +0 -0
  107. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/timeline_widget.py +0 -0
  108. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/gui/views/trajectory_table_view.py +0 -0
  109. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/__init__.py +0 -0
  110. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/alignment.py +0 -0
  111. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/approximation.py +0 -0
  112. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/evaluation.py +0 -0
  113. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/interpolation.py +0 -0
  114. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/lib/__init__.py +0 -0
  115. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/lib/alignment/__init__.py +0 -0
  116. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/lib/alignment/direct.py +0 -0
  117. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/lib/alignment/egrad_interface.py +0 -0
  118. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/lib/alignment/equations.py +0 -0
  119. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/lib/alignment/estimation.py +0 -0
  120. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/lib/alignment/parameters.py +0 -0
  121. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/lib/approximation/__init__.py +0 -0
  122. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/lib/approximation/cubic_approximation.py +0 -0
  123. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/lib/approximation/line_approximation.py +0 -0
  124. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/lib/approximation/voxelizer.py +0 -0
  125. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/matching.py +0 -0
  126. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/merging.py +0 -0
  127. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/processing/sorting.py +0 -0
  128. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/py.typed +0 -0
  129. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/readers/__init__.py +0 -0
  130. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/readers/ascii.py +0 -0
  131. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/readers/rosbag.py +0 -0
  132. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/readers/rosmsg.py +0 -0
  133. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/readers/utils.py +0 -0
  134. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/results/__init__.py +0 -0
  135. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/results/alignment_result.py +0 -0
  136. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/results/ate_result.py +0 -0
  137. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/results/rpe_result.py +0 -0
  138. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/utils/__init__.py +0 -0
  139. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/utils/common.py +0 -0
  140. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/utils/definitions.py +0 -0
  141. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/visualization/__init__.py +0 -0
  142. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/visualization/assets/icon.png +0 -0
  143. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/visualization/assets/style.css +0 -0
  144. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/visualization/default.mplstyle +0 -0
  145. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/visualization/mpl_plots.py +0 -0
  146. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/visualization/plotly_plots.py +0 -0
  147. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/visualization/plotly_reports.py +0 -0
  148. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/visualization/report_data.py +0 -0
  149. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/visualization/templates/generic.html +0 -0
  150. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/visualization/templates/multi_template.html +0 -0
  151. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy/visualization/templates/single_template.html +0 -0
  152. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/core/positions.md +0 -0
  153. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/core/rotations.md +0 -0
  154. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/core/settings.md +0 -0
  155. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/core/trajectory.md +0 -0
  156. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/index.md +0 -0
  157. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/processing/alignment.md +0 -0
  158. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/processing/approximation.md +0 -0
  159. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/processing/evaluation.md +0 -0
  160. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/processing/interpolation.md +0 -0
  161. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/processing/matching.md +0 -0
  162. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/processing/merging.md +0 -0
  163. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/processing/parameters.md +0 -0
  164. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/processing/sorting.md +0 -0
  165. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/results/alignment_result.md +0 -0
  166. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/results/ate_result.md +0 -0
  167. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/results/rpe_result.md +0 -0
  168. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/visualization/mpl_plots.md +0 -0
  169. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/visualization/plotly_plots.md +0 -0
  170. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/api/visualization/plotly_reports.md +0 -0
  171. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/assets/icon.png +0 -0
  172. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/file_formats.md +0 -0
  173. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/index.md +0 -0
  174. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/javascripts/mathjax.js +0 -0
  175. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/docs/stylesheets/extra.css +0 -0
  176. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy-docs/mkdocs.yml +0 -0
  177. {trajectopy-4.2.4 → trajectopy-4.3.1}/trajectopy.spec +0 -0
  178. {trajectopy-4.2.4 → trajectopy-4.3.1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trajectopy
3
- Version: 4.2.4
3
+ Version: 4.3.1
4
4
  Summary: Trajectory Evaluation in Python
5
5
  Project-URL: Homepage, https://gereon-t.github.io/trajectopy/
6
6
  Project-URL: Documentation, https://gereon-t.github.io/trajectopy/
@@ -19,9 +19,9 @@
19
19
  "scatter_smooth": false,
20
20
  "scatter_smooth_window": 5,
21
21
  "scatter_plot_on_map": false,
22
- "scatter_mapbox_style": "open-street-map",
22
+ "scatter_mapbox_style": "carto-positron",
23
23
  "scatter_mapbox_zoom": 15,
24
- "scatter_mapbox_token": "pk.eyJ1IjoiZ2VyZW9uLXQiLCJhIjoiY2xwdGp6Nnh5MGZlejJqcnptemdlZnJicyJ9.fbQAv2C5pw7ctmzP_qX1pg",
24
+ "scatter_mapbox_token": "",
25
25
  "pos_x_name": "x",
26
26
  "pos_y_name": "y",
27
27
  "pos_z_name": "z",
@@ -28,7 +28,8 @@ def main():
28
28
  # default settings
29
29
  processing_settings = ProcessingSettings()
30
30
 
31
- ate_result = ate(other=gt_traj, trajectory=est_traj, processing_settings=processing_settings)
31
+ # align = True is the default, but can be disabled if you want to evaluate the unaligned trajectories
32
+ ate_result = ate(other=gt_traj, trajectory=est_traj, processing_settings=processing_settings, align=True)
32
33
  console.print(dict_to_table(ate_result.property_dict))
33
34
 
34
35
 
@@ -59,7 +59,8 @@ class TestMerging(unittest.TestCase):
59
59
  self.assertIsInstance(merged, Trajectory)
60
60
 
61
61
  # Verify length is sum of input trajectories
62
- expected_length = len(self.traj1) + len(self.traj2)
62
+ unique_timestamps = np.unique(np.concatenate([self.traj1.timestamps, self.traj2.timestamps]))
63
+ expected_length = len(unique_timestamps)
63
64
  self.assertEqual(len(merged), expected_length)
64
65
 
65
66
  # Verify name
@@ -81,7 +82,10 @@ class TestMerging(unittest.TestCase):
81
82
  merged = merge_trajectories([self.traj1, self.traj2, self.traj3])
82
83
 
83
84
  # Verify all trajectories are included
84
- expected_length = len(self.traj1) + len(self.traj2) + len(self.traj3)
85
+ unique_timestamps = np.unique(
86
+ np.concatenate([self.traj1.timestamps, self.traj2.timestamps, self.traj3.timestamps])
87
+ )
88
+ expected_length = len(unique_timestamps)
85
89
  self.assertEqual(len(merged), expected_length)
86
90
 
87
91
  # Timestamps should still be sorted
@@ -104,9 +108,11 @@ class TestMerging(unittest.TestCase):
104
108
 
105
109
  merged = merge_trajectories([traj1_no_rot, traj2_no_rot])
106
110
 
111
+ unique_timestamps = np.unique(np.concatenate([traj1_no_rot.timestamps, traj2_no_rot.timestamps]))
112
+
107
113
  # Should work without rotations (creates identity rotations)
108
114
  self.assertIsNotNone(merged)
109
- self.assertEqual(len(merged), len(traj1_no_rot) + len(traj2_no_rot))
115
+ self.assertEqual(len(merged), len(unique_timestamps))
110
116
 
111
117
  def test_merge_trajectories_preserves_epsg(self):
112
118
  """Test that EPSG code is preserved in merging."""
@@ -0,0 +1 @@
1
+ __version__ = "4.3.1"
@@ -543,6 +543,7 @@ class AlignmentSettings(Settings):
543
543
  Combines preprocessing, estimation, and stochastic settings for trajectory alignment.
544
544
 
545
545
  Attributes:
546
+ enabled: Whether alignment is enabled.
546
547
  preprocessing: Preprocessing filter settings
547
548
  estimation_settings: Parameter estimation settings
548
549
  stochastics: Stochastic model (observation uncertainty) settings
@@ -552,6 +553,7 @@ class AlignmentSettings(Settings):
552
553
  Defaults to 1e-4.
553
554
  """
554
555
 
556
+ enabled: bool = True
555
557
  preprocessing: AlignmentPreprocessing = field(default_factory=AlignmentPreprocessing)
556
558
  estimation_settings: AlignmentEstimationSettings = field(default_factory=AlignmentEstimationSettings)
557
559
  stochastics: AlignmentStochastics = field(default_factory=AlignmentStochastics)
@@ -978,7 +980,7 @@ class ReportSettings(Settings):
978
980
  scatter_smooth: Smooth scatter plot color data. Defaults to False.
979
981
  scatter_smooth_window: Window size for scatter smoothing. Defaults to 5.
980
982
  scatter_plot_on_map: Plot trajectories on mapbox map (requires valid EPSG). Defaults to False.
981
- scatter_mapbox_style: Mapbox style (some require token). Defaults to "open-street-map".
983
+ scatter_mapbox_style: Mapbox style (some require token). Defaults to "carto-positron".
982
984
  scatter_mapbox_zoom: Mapbox zoom level. Defaults to 15.
983
985
  scatter_mapbox_token: Mapbox access token (optional). Defaults to "".
984
986
  pos_x_name: Label for X position coordinate. Defaults to "x".
@@ -1024,7 +1026,7 @@ class ReportSettings(Settings):
1024
1026
  scatter_smooth_window: int = 5
1025
1027
 
1026
1028
  scatter_plot_on_map: bool = False
1027
- scatter_mapbox_style: str = "open-street-map"
1029
+ scatter_mapbox_style: str = "carto-positron"
1028
1030
  scatter_mapbox_zoom: int = 15
1029
1031
  scatter_mapbox_token: str = ""
1030
1032
 
@@ -73,6 +73,20 @@ class Trajectory:
73
73
 
74
74
  self.timestamps = np.arange(0, len(positions)) if timestamps is None else timestamps
75
75
 
76
+ # ensure strictly increasing timestamps
77
+ unique_timestamps, unique_indices = np.unique(self.timestamps, return_index=True)
78
+
79
+ if len(unique_timestamps) != len(self.timestamps):
80
+ logger.warning(
81
+ "Timestamps contain duplicates. Only the first occurrence of each unique timestamp will be kept. "
82
+ "This may lead to loss of data if duplicates are present."
83
+ )
84
+ self.timestamps = unique_timestamps
85
+ self.positions.xyz = self.positions.xyz[unique_indices, :]
86
+
87
+ if self.rotations is not None:
88
+ self.rotations = Rotations.from_quat(self.rotations.as_quat()[unique_indices, :])
89
+
76
90
  if velocity_xyz is not None and len(velocity_xyz) == len(self.positions):
77
91
  self._velocity_xyz = velocity_xyz
78
92
  else:
@@ -61,9 +61,9 @@ class FileManager(QObject):
61
61
 
62
62
  @show_progress
63
63
  @Slot(FileRequest)
64
- def handle_request(self, request: FileRequest) -> None:
64
+ def handle_request(self, request: FileRequest):
65
65
  """Logic for handling a request."""
66
- self._request_executor.submit(generic_request_handler, self, request, True).result()
66
+ return self._request_executor.submit(generic_request_handler, self, request, True)
67
67
 
68
68
  def shutdown_executor(self) -> None:
69
69
  """Best-effort cleanup for the persistent Python worker thread."""
@@ -55,8 +55,6 @@ class SessionManagerRequestType(Enum):
55
55
 
56
56
  class TrajectoryManagerRequestType(Enum):
57
57
  SWITCH_SORTING = auto()
58
- COMPARE_ABS = auto()
59
- COMPARE_REL = auto()
60
58
  ALIGN = auto()
61
59
  ADAPT_FIRST_POSE = auto()
62
60
  APPLY_ALIGNMENT = auto()
@@ -48,15 +48,6 @@ class TrajectoryEntryPair:
48
48
  request: TrajectoryManagerRequest
49
49
  reference_entry: TrajectoryEntry | None = None
50
50
 
51
- def __post_init__(self) -> None:
52
- if self.request.type in [
53
- TrajectoryManagerRequestType.ATE,
54
- TrajectoryManagerRequestType.RPE,
55
- ]: # pipeline requests use matching setting from trajectory
56
- return
57
-
58
- self.entry.settings.matching.method = self.request.matching_method
59
-
60
51
 
61
52
  class TrajectoryManager(QObject):
62
53
  """
@@ -144,16 +135,6 @@ class TrajectoryManager(QObject):
144
135
  apply_to_reference=True,
145
136
  ),
146
137
  TrajectoryManagerRequestType.AVERAGE: self.operation_average_trajectories,
147
- TrajectoryManagerRequestType.COMPARE_ABS: lambda: self.handle_trajectory_operation(
148
- operation=self.operation_compare_abs,
149
- inplace=False,
150
- apply_to_reference=False,
151
- ),
152
- TrajectoryManagerRequestType.COMPARE_REL: lambda: self.handle_trajectory_operation(
153
- operation=self.operation_compare_rel,
154
- inplace=False,
155
- apply_to_reference=False,
156
- ),
157
138
  TrajectoryManagerRequestType.MERGE: self.operation_merge_trajectories,
158
139
  TrajectoryManagerRequestType.SORT: lambda: self.handle_trajectory_operation(
159
140
  operation=self.operation_sort, inplace=False, apply_to_reference=True
@@ -215,8 +196,9 @@ class TrajectoryManager(QObject):
215
196
  None.
216
197
  """
217
198
  self.request = request
218
- self._request_executor.submit(generic_request_handler, self, request, False).result()
219
- self.update_view.emit()
199
+ future = self._request_executor.submit(generic_request_handler, self, request, False)
200
+ future.add_done_callback(lambda ft: self.update_view.emit())
201
+ return future
220
202
 
221
203
  def shutdown_executor(self) -> None:
222
204
  """Best-effort cleanup for the persistent Python worker thread."""
@@ -475,64 +457,6 @@ class TrajectoryManager(QObject):
475
457
 
476
458
  self.emit_add_trajectory_signal(new_trajectory_entry)
477
459
 
478
- @staticmethod
479
- def operation_compare_abs(entry_pair: TrajectoryEntryPair) -> tuple[ResultEntry]:
480
- """
481
- Compares the selected trajectory to the reference trajectory.
482
-
483
- Args:
484
- entry_pair (TrajectoryEntryPair): The pair of trajectories to compare.
485
-
486
- Returns:
487
- ResultEntry: The result of the comparison.
488
- """
489
- if (reference_entry := entry_pair.reference_entry) is None:
490
- raise ValueError("No reference trajectory selected.")
491
-
492
- traj_test, traj_ref = matching.match_trajectories(
493
- trajectory=entry_pair.entry.trajectory,
494
- other=reference_entry.trajectory,
495
- matching_settings=entry_pair.entry.settings.matching,
496
- )
497
-
498
- if len(traj_ref) != len(traj_test):
499
- raise ValueError("Something went wrong during matching.")
500
-
501
- comparison_result = evaluation._compare_trajectories_absolute(trajectory=traj_test, other=traj_ref)
502
-
503
- return (AbsoluteDeviationEntry(deviations=comparison_result),)
504
-
505
- @staticmethod
506
- def operation_compare_rel(entry_pair: TrajectoryEntryPair) -> tuple[ResultEntry]:
507
- """
508
- Compares the selected trajectory to the reference trajectory using relative comparison.
509
-
510
- Args:
511
- entry_pair (TrajectoryEntryPair): The pair of trajectories to compare.
512
-
513
- Returns:
514
- ResultEntry: The result of the comparison.
515
- """
516
- if (reference_entry := entry_pair.reference_entry) is None:
517
- raise ValueError("No reference trajectory selected.")
518
-
519
- traj_test, traj_ref = matching.match_trajectories(
520
- trajectory=entry_pair.entry.trajectory,
521
- other=reference_entry.trajectory,
522
- matching_settings=entry_pair.entry.settings.matching,
523
- )
524
-
525
- if len(traj_ref) != len(traj_test):
526
- raise ValueError("Something went wrong during matching.")
527
-
528
- comparison_result = evaluation._compare_trajectories_relative(
529
- trajectory=traj_test,
530
- other=traj_ref,
531
- relative_comparison_settings=entry_pair.entry.settings.relative_comparison,
532
- )
533
-
534
- return (RelativeDeviationEntry(deviations=comparison_result),)
535
-
536
460
  @staticmethod
537
461
  def operation_interpolate_to_grid(
538
462
  entry_pair: TrajectoryEntryPair,
@@ -824,7 +748,7 @@ class TrajectoryManager(QObject):
824
748
  entry_pair (TrajectoryEntryPair): The pair of trajectories to compare.
825
749
 
826
750
  Returns:
827
- ResultEntry: The result of the comparison.
751
+ ResultEntry: The result of the comparison and the alignment information.
828
752
  """
829
753
  if (reference_entry := entry_pair.reference_entry) is None:
830
754
  raise ValueError("No reference trajectory selected.")
@@ -834,8 +758,12 @@ class TrajectoryManager(QObject):
834
758
  other=reference_entry.trajectory,
835
759
  processing_settings=entry_pair.entry.settings,
836
760
  return_alignment=True,
761
+ align=entry_pair.entry.settings.alignment.enabled,
837
762
  )
838
763
 
764
+ if not entry_pair.entry.settings.alignment.enabled:
765
+ return (AbsoluteDeviationEntry(deviations=ate_result),)
766
+
839
767
  return (AbsoluteDeviationEntry(deviations=ate_result), AlignmentEntry(alignment_result=alignment_result))
840
768
 
841
769
  @staticmethod
@@ -61,8 +61,6 @@ def handle_drag_move(event: QtGui.QDragMoveEvent | None) -> None:
61
61
  def show_progress(func):
62
62
  """
63
63
  Decorator to show progress bar while executing a function
64
-
65
- This should be used for functions that take a long time to execute.
66
64
  """
67
65
 
68
66
  @wraps(func)
@@ -71,12 +69,6 @@ def show_progress(func):
71
69
  progress_window = getattr(manager, "_progress_window", None)
72
70
 
73
71
  if progress_window is not None:
74
- # Cross-thread: invoke the progress window slot directly via
75
- # QMetaObject.invokeMethod with BlockingQueuedConnection.
76
- # This guarantees the progress window is shown before work
77
- # begins, even in PyInstaller-packaged executables where
78
- # PySide6 signal dispatch across threads can be unreliable
79
- # on the first call.
80
72
  QMetaObject.invokeMethod(
81
73
  progress_window,
82
74
  "handle_show_request",
@@ -86,15 +78,45 @@ def show_progress(func):
86
78
  manager.operation_started.emit()
87
79
 
88
80
  try:
89
- return func(*args, **kwargs)
81
+ result = func(*args, **kwargs)
82
+ # Check if the result is a concurrent.futures.Future
83
+ import concurrent.futures
84
+
85
+ if isinstance(result, concurrent.futures.Future):
86
+
87
+ def on_done(future):
88
+ # We must emit signals from the manager's thread
89
+ # QMetaObject.invokeMethod handles this nicely if we call a slot,
90
+ # but we can also just emit the signal directly since Qt queued connections will route it.
91
+ manager.operation_finished.emit()
92
+ if progress_window is not None:
93
+ QMetaObject.invokeMethod(
94
+ progress_window,
95
+ "handle_close_request",
96
+ Qt.ConnectionType.BlockingQueuedConnection,
97
+ )
98
+ # If there's an exception, we might want to log it
99
+ try:
100
+ future.result()
101
+ except Exception as e:
102
+ import logging
103
+
104
+ logging.getLogger(__name__).exception("Error in background task: %s", e)
105
+
106
+ result.add_done_callback(on_done)
107
+ return result
108
+ else:
109
+ return result
90
110
  finally:
91
- manager.operation_finished.emit()
92
- if progress_window is not None:
93
- QMetaObject.invokeMethod(
94
- progress_window,
95
- "handle_close_request",
96
- Qt.ConnectionType.BlockingQueuedConnection,
97
- )
111
+ # Only emit finished immediately if it's NOT a Future
112
+ if not isinstance(result, concurrent.futures.Future):
113
+ manager.operation_finished.emit()
114
+ if progress_window is not None:
115
+ QMetaObject.invokeMethod(
116
+ progress_window,
117
+ "handle_close_request",
118
+ Qt.ConnectionType.BlockingQueuedConnection,
119
+ )
98
120
 
99
121
  return wrapper
100
122
 
@@ -214,6 +214,10 @@ class JSONViewer(QtWidgets.QMainWindow):
214
214
  group_label = QtWidgets.QLabel(self.scroll_area_widget_contents)
215
215
  group_label.setStyleSheet("color: #888888;")
216
216
 
217
+ if parent_name == "root":
218
+ # make first level keys more prominent
219
+ group_label.setStyleSheet("color: #888888; font-weight: bold; font-size: 14px;")
220
+
217
221
  group_label.setObjectName(f"label-{parent_name}-{key}")
218
222
  group_label.setText(_translate("MainWindow", format_key(key)))
219
223
  self.form_layout.setWidget(self.form_item_cnt, QtWidgets.QFormLayout.ItemRole.LabelRole, group_label)