trajectopy 6.0.1__tar.gz → 6.2.0__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 (222) hide show
  1. {trajectopy-6.0.1 → trajectopy-6.2.0}/PKG-INFO +1 -1
  2. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_gui_models.py +327 -242
  3. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_gui_smoke.py +648 -603
  4. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_project.py +302 -297
  5. trajectopy-6.2.0/trajectopy/__init__.py +245 -0
  6. trajectopy-6.2.0/trajectopy/__version__.py +1 -0
  7. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/app.py +18 -9
  8. trajectopy-6.2.0/trajectopy/gui/log.py +98 -0
  9. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/managers/plot_manager.py +2 -0
  10. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/managers/requests.py +257 -243
  11. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/managers/ui_manager.py +36 -2
  12. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/models/entries.py +504 -496
  13. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/models/result_model.py +83 -79
  14. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/models/selection.py +4 -0
  15. trajectopy-6.2.0/trajectopy/gui/models/table_model.py +489 -0
  16. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/models/trajectory_model.py +12 -4
  17. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/project_format.py +5 -1
  18. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/themes.py +33 -4
  19. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/utils.py +26 -7
  20. trajectopy-6.2.0/trajectopy/gui/views/entry_table_view.py +241 -0
  21. trajectopy-6.2.0/trajectopy/gui/views/log_panel.py +69 -0
  22. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/main_window.py +20 -0
  23. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/manual_alignment_window.py +2 -0
  24. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/menu_helpers.py +44 -0
  25. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/progress_window.py +1 -0
  26. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/result_context_menu.py +18 -1
  27. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/result_selection_window.py +2 -4
  28. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/result_table_view.py +1 -1
  29. trajectopy-6.2.0/trajectopy/gui/views/splash_screen.py +61 -0
  30. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/trajectory_context_menu.py +16 -4
  31. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/trajectory_table_view.py +59 -58
  32. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/user_guide.md +433 -425
  33. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy.spec +2 -2
  34. trajectopy-6.0.1/trajectopy/__init__.py +0 -172
  35. trajectopy-6.0.1/trajectopy/__version__.py +0 -1
  36. trajectopy-6.0.1/trajectopy/gui/models/table_model.py +0 -152
  37. trajectopy-6.0.1/trajectopy/gui/views/entry_table_view.py +0 -100
  38. {trajectopy-6.0.1 → trajectopy-6.2.0}/.gitattributes +0 -0
  39. {trajectopy-6.0.1 → trajectopy-6.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  40. {trajectopy-6.0.1 → trajectopy-6.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  41. {trajectopy-6.0.1 → trajectopy-6.2.0}/.github/workflows/ci.yml +0 -0
  42. {trajectopy-6.0.1 → trajectopy-6.2.0}/.github/workflows/release.yml +0 -0
  43. {trajectopy-6.0.1 → trajectopy-6.2.0}/.gitignore +0 -0
  44. {trajectopy-6.0.1 → trajectopy-6.2.0}/.pre-commit-config.yaml +0 -0
  45. {trajectopy-6.0.1 → trajectopy-6.2.0}/.pylintrc +0 -0
  46. {trajectopy-6.0.1 → trajectopy-6.2.0}/CITATION.cff +0 -0
  47. {trajectopy-6.0.1 → trajectopy-6.2.0}/LICENSE +0 -0
  48. {trajectopy-6.0.1 → trajectopy-6.2.0}/README.md +0 -0
  49. {trajectopy-6.0.1 → trajectopy-6.2.0}/example_data/KITTI_ORB.traj +0 -0
  50. {trajectopy-6.0.1 → trajectopy-6.2.0}/example_data/KITTI_SPTAM.traj +0 -0
  51. {trajectopy-6.0.1 → trajectopy-6.2.0}/example_data/KITTI_gt.traj +0 -0
  52. {trajectopy-6.0.1 → trajectopy-6.2.0}/example_scripts/align.py +0 -0
  53. {trajectopy-6.0.1 → trajectopy-6.2.0}/example_scripts/alignment.py +0 -0
  54. {trajectopy-6.0.1 → trajectopy-6.2.0}/example_scripts/ate.py +0 -0
  55. {trajectopy-6.0.1 → trajectopy-6.2.0}/example_scripts/deviation_plots.py +0 -0
  56. {trajectopy-6.0.1 → trajectopy-6.2.0}/example_scripts/rpe.py +0 -0
  57. {trajectopy-6.0.1 → trajectopy-6.2.0}/example_scripts/trajectory_plot.py +0 -0
  58. {trajectopy-6.0.1 → trajectopy-6.2.0}/installer/trajectopy.iss +0 -0
  59. {trajectopy-6.0.1 → trajectopy-6.2.0}/pyproject.toml +0 -0
  60. {trajectopy-6.0.1 → trajectopy-6.2.0}/requirements.txt +0 -0
  61. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/__init__.py +0 -0
  62. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/data/abs_dev.result +0 -0
  63. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/data/align_entry.result +0 -0
  64. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/data/generated_trajectory.traj +0 -0
  65. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/data/noisy_trajectory.traj +0 -0
  66. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/data/open_loop_trajectory.traj +0 -0
  67. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/data/rel_dev_meter.result +0 -0
  68. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/data/rel_dev_second.result +0 -0
  69. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_alignment.py +0 -0
  70. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_approximation.py +0 -0
  71. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_architecture.py +0 -0
  72. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_comparison.py +0 -0
  73. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_context_menus.py +0 -0
  74. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_correlation.py +0 -0
  75. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_fast_reading.py +0 -0
  76. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_gui_operations.py +0 -0
  77. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_inplace_contract.py +0 -0
  78. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_io.py +0 -0
  79. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_kml.py +0 -0
  80. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_manual_alignment.py +0 -0
  81. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_matching.py +0 -0
  82. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_merging.py +0 -0
  83. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_pipeline.py +0 -0
  84. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_positions_rotations.py +0 -0
  85. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_processing_edge_cases.py +0 -0
  86. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_processing_errors.py +0 -0
  87. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_readers_edge_cases.py +0 -0
  88. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_readers_errors.py +0 -0
  89. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_regressions.py +0 -0
  90. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_settings.py +0 -0
  91. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_settings_edge_cases.py +0 -0
  92. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_settings_serialization.py +0 -0
  93. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_settings_validation.py +0 -0
  94. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_sorting.py +0 -0
  95. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_trajectory.py +0 -0
  96. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_trajectory_validation.py +0 -0
  97. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_update_check.py +0 -0
  98. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/test_visualization.py +0 -0
  99. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/testdata.py +0 -0
  100. {trajectopy-6.0.1 → trajectopy-6.2.0}/test/util.py +0 -0
  101. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/LICENSE +0 -0
  102. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/__main__.py +0 -0
  103. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/cli/__init__.py +0 -0
  104. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/cli/main.py +0 -0
  105. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/core/__init__.py +0 -0
  106. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/core/parameters.py +0 -0
  107. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/core/positions.py +0 -0
  108. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/core/rotations.py +0 -0
  109. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/core/settings.py +0 -0
  110. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/core/trajectory.py +0 -0
  111. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/exceptions.py +0 -0
  112. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/__init__.py +0 -0
  113. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/managers/__init__.py +0 -0
  114. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/managers/file_manager.py +0 -0
  115. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/managers/project_manager.py +0 -0
  116. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/managers/trajectory_manager.py +0 -0
  117. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/models/__init__.py +0 -0
  118. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/models/property_model.py +0 -0
  119. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/project.py +0 -0
  120. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/resources/full-icon-poppins.png +0 -0
  121. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/resources/icon-bg.ico +0 -0
  122. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/resources/icon-bg.png +0 -0
  123. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/resources/icon.png +0 -0
  124. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/update_check.py +0 -0
  125. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/__init__.py +0 -0
  126. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/about_window.py +0 -0
  127. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/alignment_edit_window.py +0 -0
  128. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/dof_organizer.py +0 -0
  129. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/json_settings_view.py +0 -0
  130. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/kml_dialog.py +0 -0
  131. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/playback_window.py +0 -0
  132. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/preview_plot.py +0 -0
  133. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/preview_plot_3d.py +0 -0
  134. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/properties_panel.py +0 -0
  135. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/views/timeline_widget.py +0 -0
  136. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/gui/worker.py +0 -0
  137. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/io/__init__.py +0 -0
  138. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/io/ascii.py +0 -0
  139. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/io/header.py +0 -0
  140. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/io/kml.py +0 -0
  141. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/io/results_io.py +0 -0
  142. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/io/rosbag.py +0 -0
  143. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/io/rosmsg.py +0 -0
  144. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/io/trajectory_io.py +0 -0
  145. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/io/utils.py +0 -0
  146. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/__init__.py +0 -0
  147. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/alignment.py +0 -0
  148. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/approximation.py +0 -0
  149. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/evaluation.py +0 -0
  150. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/interpolation.py +0 -0
  151. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/lib/__init__.py +0 -0
  152. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/lib/alignment/__init__.py +0 -0
  153. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/lib/alignment/data.py +0 -0
  154. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/lib/alignment/direct.py +0 -0
  155. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/lib/alignment/egrad_interface.py +0 -0
  156. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/lib/alignment/equations.py +0 -0
  157. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/lib/alignment/estimation.py +0 -0
  158. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/lib/alignment/parameters.py +0 -0
  159. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/lib/approximation/__init__.py +0 -0
  160. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/lib/approximation/cubic_approximation.py +0 -0
  161. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/lib/approximation/line_approximation.py +0 -0
  162. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/lib/approximation/voxelizer.py +0 -0
  163. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/matching.py +0 -0
  164. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/merging.py +0 -0
  165. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/processing/sorting.py +0 -0
  166. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/py.typed +0 -0
  167. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/readers/__init__.py +0 -0
  168. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/readers/ascii.py +0 -0
  169. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/readers/rosbag.py +0 -0
  170. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/readers/rosmsg.py +0 -0
  171. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/readers/utils.py +0 -0
  172. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/results/__init__.py +0 -0
  173. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/results/alignment_result.py +0 -0
  174. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/results/ate_result.py +0 -0
  175. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/results/correlation.py +0 -0
  176. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/results/rpe_result.py +0 -0
  177. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/utils/__init__.py +0 -0
  178. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/utils/colors.py +0 -0
  179. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/utils/common.py +0 -0
  180. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/utils/definitions.py +0 -0
  181. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/utils/formatting.py +0 -0
  182. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/utils/geometry.py +0 -0
  183. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/utils/linalg.py +0 -0
  184. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/utils/numeric.py +0 -0
  185. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/utils/resources.py +0 -0
  186. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/utils/statistics.py +0 -0
  187. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/visualization/__init__.py +0 -0
  188. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/visualization/_svg_export.py +0 -0
  189. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/visualization/axes.py +0 -0
  190. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/visualization/figure.py +0 -0
  191. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/visualization/plots.py +0 -0
  192. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy/visualization/style.py +0 -0
  193. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/core/parameters.md +0 -0
  194. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/core/positions.md +0 -0
  195. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/core/rotations.md +0 -0
  196. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/core/settings.md +0 -0
  197. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/core/trajectory.md +0 -0
  198. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/index.md +0 -0
  199. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/io/kml.md +0 -0
  200. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/io/results_io.md +0 -0
  201. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/io/rosbag.md +0 -0
  202. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/io/trajectory_io.md +0 -0
  203. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/processing/alignment.md +0 -0
  204. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/processing/approximation.md +0 -0
  205. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/processing/evaluation.md +0 -0
  206. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/processing/interpolation.md +0 -0
  207. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/processing/matching.md +0 -0
  208. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/processing/merging.md +0 -0
  209. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/processing/sorting.md +0 -0
  210. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/results/alignment_result.md +0 -0
  211. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/results/ate_result.md +0 -0
  212. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/results/rpe_result.md +0 -0
  213. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/visualization/display.md +0 -0
  214. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/api/visualization/figures.md +0 -0
  215. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/assets/icon.png +0 -0
  216. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/file_formats.md +0 -0
  217. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/index.md +0 -0
  218. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/javascripts/mathjax.js +0 -0
  219. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/settings.md +0 -0
  220. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/docs/stylesheets/extra.css +0 -0
  221. {trajectopy-6.0.1 → trajectopy-6.2.0}/trajectopy-docs/mkdocs.yml +0 -0
  222. {trajectopy-6.0.1 → trajectopy-6.2.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: trajectopy
3
- Version: 6.0.1
3
+ Version: 6.2.0
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/
@@ -1,242 +1,327 @@
1
- """Table models keep the selection on the right entries, and sub-windows are freed and left open independently.
2
-
3
- Runs headless (``QT_QPA_PLATFORM=offscreen``) and is skipped if the GUI extras are not installed.
4
- """
5
-
6
- import os
7
- import unittest
8
-
9
- os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
10
-
11
- try:
12
- from PySide6 import QtWidgets
13
-
14
- from trajectopy.core.settings import PlotSettings
15
- from trajectopy.gui.managers.plot_manager import PlotManager
16
- from trajectopy.gui.managers.requests import (
17
- PlotRequest,
18
- PlotRequestType,
19
- TrajectoryModelRequest,
20
- TrajectoryModelRequestType,
21
- UIRequest,
22
- UIRequestType,
23
- )
24
- from trajectopy.gui.managers.ui_manager import UIManager
25
- from trajectopy.gui.models.entries import TrajectoryEntry
26
- from trajectopy.gui.models.selection import TrajectorySelection
27
- from trajectopy.gui.models.trajectory_model import TrajectoryTableModel
28
- from trajectopy.gui.views.trajectory_table_view import TrajectoryTableView
29
- except ImportError: # pragma: no cover - GUI extras not installed
30
- QtWidgets = None
31
-
32
- from test.testdata import noisy_trajectory
33
-
34
-
35
- def _entry(name: str, group_id: str | None = None) -> "TrajectoryEntry":
36
- trajectory = noisy_trajectory.copy()
37
- trajectory.name = name
38
- entry = TrajectoryEntry(full_filename="", trajectory=trajectory)
39
- if group_id is not None:
40
- entry.group_id = group_id
41
- return entry
42
-
43
-
44
- @unittest.skipIf(QtWidgets is None, "GUI dependencies not installed")
45
- class TestTableModelSelection(unittest.TestCase):
46
- @classmethod
47
- def setUpClass(cls) -> None:
48
- cls.app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
49
-
50
- def setUp(self) -> None:
51
- self.model = TrajectoryTableModel()
52
- self.view = TrajectoryTableView(parent=None, trajectory_table_model=self.model)
53
- self.entries = [_entry(f"T{i}") for i in range(4)]
54
- for entry in self.entries:
55
- self.model.add(entry)
56
-
57
- def selected_names(self) -> list[str]:
58
- return [entry.name for entry in self.view.selected_entries]
59
-
60
- def test_selection_stays_on_entry_when_row_above_is_removed(self):
61
- self.view.selectRow(1)
62
- self.model.remove([self.entries[0].entry_id])
63
- self.assertEqual(self.selected_names(), ["T1"])
64
-
65
- def test_removing_the_selected_entry_clears_the_selection(self):
66
- self.view.selectRow(2)
67
- self.model.remove([self.entries[2].entry_id])
68
- self.assertEqual(self.selected_names(), [])
69
- self.assertEqual([entry.name for entry in self.model.items], ["T0", "T1", "T3"])
70
-
71
- def test_selection_follows_entry_when_reordered(self):
72
- self.view.selectRow(1)
73
- order = [self.entries[i].entry_id for i in (3, 2, 1, 0)]
74
- self.model.sort_items(TrajectoryModelRequest(type=TrajectoryModelRequestType.SORT, index_list=order))
75
- self.assertEqual([entry.name for entry in self.model.items], ["T3", "T2", "T1", "T0"])
76
- self.assertEqual(self.selected_names(), ["T1"])
77
-
78
- def test_entries_missing_in_the_order_are_kept(self):
79
- order = [self.entries[2].entry_id, self.entries[0].entry_id]
80
- self.model.sort_items(TrajectoryModelRequest(type=TrajectoryModelRequestType.SORT, index_list=order))
81
- self.assertEqual([entry.name for entry in self.model.items], ["T2", "T0", "T1", "T3"])
82
-
83
- def test_remove_group_removes_only_that_group(self):
84
- grouped = [_entry("G0", group_id="group"), _entry("G1", group_id="group")]
85
- for entry in grouped:
86
- self.model.add(entry)
87
- self.view.selectRow(3)
88
- self.model.remove_group("group")
89
- self.assertEqual([entry.name for entry in self.model.items], ["T0", "T1", "T2", "T3"])
90
- self.assertEqual(self.selected_names(), ["T3"])
91
-
92
- def test_reset_clears_the_selection(self):
93
- self.view.selectRow(0)
94
- self.model.reset()
95
- self.assertEqual(self.model.rowCount(), 0)
96
- self.assertEqual(self.selected_names(), [])
97
-
98
-
99
- @unittest.skipIf(QtWidgets is None, "GUI dependencies not installed")
100
- class TestSubWindows(unittest.TestCase):
101
- @classmethod
102
- def setUpClass(cls) -> None:
103
- cls.app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
104
-
105
- def setUp(self) -> None:
106
- self.main_window = QtWidgets.QMainWindow()
107
- self.addCleanup(self.main_window.deleteLater)
108
- self.selection = TrajectorySelection(entries=[_entry("T0")])
109
-
110
- def _process_deferred_deletes(self) -> None:
111
- for _ in range(5):
112
- self.app.sendPostedEvents(None, 0) # 0: QEvent.DeferredDelete included
113
- self.app.processEvents()
114
-
115
- def test_closed_plot_windows_are_freed(self):
116
- manager = PlotManager(parent=self.main_window)
117
- request = PlotRequest(
118
- type=PlotRequestType.TRAJECTORIES, trajectory_selection=self.selection, plot_settings=PlotSettings()
119
- )
120
- for _ in range(3):
121
- manager.handle_request(request)
122
- manager._windows[-1].close()
123
- self._process_deferred_deletes()
124
- self.assertEqual(manager._windows, [])
125
- self.assertEqual(self.main_window.findChildren(QtWidgets.QMainWindow), [])
126
-
127
- def test_opening_a_window_leaves_the_others_open(self):
128
- manager = UIManager(parent=self.main_window)
129
- manager.handle_request(UIRequest(type=UIRequestType.TRAJ_SETTINGS, trajectory_selection=self.selection))
130
- manager.handle_request(UIRequest(type=UIRequestType.DOF_ORGANIZER, trajectory_selection=self.selection))
131
- windows = self.main_window.findChildren(QtWidgets.QMainWindow)
132
- self.assertEqual(len(windows), 2)
133
- self.assertTrue(all(window.isVisible() for window in windows))
134
- for window in windows:
135
- window.close()
136
- self._process_deferred_deletes()
137
- self.assertEqual(self.main_window.findChildren(QtWidgets.QMainWindow), [])
138
-
139
- def _visible_windows(self) -> list:
140
- return [window for window in self.main_window.findChildren(QtWidgets.QMainWindow) if window.isVisible()]
141
-
142
- def _close_all(self) -> None:
143
- for window in self.main_window.findChildren(QtWidgets.QMainWindow):
144
- window.close()
145
- self._process_deferred_deletes()
146
-
147
- def test_settings_of_a_trajectory_open_only_once(self):
148
- manager = UIManager(parent=self.main_window)
149
- request = UIRequest(type=UIRequestType.TRAJ_SETTINGS, trajectory_selection=self.selection)
150
- manager.handle_request(request)
151
- first = self._visible_windows()
152
- manager.handle_request(request)
153
-
154
- self.assertEqual(self._visible_windows(), first)
155
- self.assertEqual(len(first), 1)
156
-
157
- # once closed, it opens again
158
- self._close_all()
159
- manager.handle_request(request)
160
- self.assertEqual(len(self._visible_windows()), 1)
161
- self._close_all()
162
-
163
- def test_a_window_for_the_current_selection_replaces_the_open_one(self):
164
- manager = UIManager(parent=self.main_window)
165
- request = UIRequest(type=UIRequestType.DOF_ORGANIZER, trajectory_selection=self.selection)
166
- manager.handle_request(request)
167
- first = self._visible_windows()
168
- manager.handle_request(request)
169
- second = self._visible_windows()
170
-
171
- self.assertEqual(len(second), 1)
172
- self.assertIsNot(second[0], first[0])
173
- self._close_all()
174
-
175
- def test_applying_settings_keeps_values_outside_of_the_usual_range(self):
176
- from trajectopy.core.settings import ProcessingSettings
177
- from trajectopy.gui.views.json_settings_view import JSONViewer
178
-
179
- settings = ProcessingSettings()
180
- settings.alignment.preprocessing.max_samples = 250_000
181
- settings.alignment.preprocessing.time_start = -2.5
182
- viewer = JSONViewer(parent=self.main_window, settings=settings)
183
- viewer.apply_changes()
184
- viewer.close()
185
- self._process_deferred_deletes()
186
-
187
- self.assertEqual(settings.alignment.preprocessing.max_samples, 250_000)
188
- self.assertEqual(settings.alignment.preprocessing.time_start, -2.5)
189
-
190
-
191
- @unittest.skipIf(QtWidgets is None, "GUI dependencies not installed")
192
- class TestUniqueIds(unittest.TestCase):
193
- @classmethod
194
- def setUpClass(cls) -> None:
195
- cls.app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
196
-
197
- def test_a_result_file_opened_twice_gives_two_independent_entries(self):
198
- import tempfile
199
-
200
- from trajectopy.gui.models.entries import AbsoluteDeviationEntry
201
- from trajectopy.gui.models.result_model import ResultTableModel
202
- from trajectopy.processing.evaluation import ate
203
-
204
- with tempfile.TemporaryDirectory() as directory:
205
- filename = os.path.join(directory, "ate.result")
206
- AbsoluteDeviationEntry(deviations=ate(noisy_trajectory, noisy_trajectory, align=False)).to_file(filename)
207
- first, second = AbsoluteDeviationEntry.from_file(filename), AbsoluteDeviationEntry.from_file(filename)
208
-
209
- model = ResultTableModel()
210
- model.add(first)
211
- model.add(second)
212
- model.remove([second.entry_id])
213
-
214
- self.assertEqual(model.items, [first])
215
-
216
- def test_operation_results_keep_the_current_reference(self):
217
- import copy
218
-
219
- from trajectopy.gui.managers.requests import TrajectoryModelRequest, TrajectoryModelRequestType
220
-
221
- model = TrajectoryTableModel()
222
- first, second = _entry("first"), _entry("second")
223
- first.set_as_reference = True
224
- model.add(first)
225
- model.add(second)
226
- result = copy.deepcopy(first) # e.g. an EPSG change, computed while the reference is changed
227
- model.handle_request(
228
- TrajectoryModelRequest(
229
- type=TrajectoryModelRequestType.SET_REFERENCE, selection=TrajectorySelection(entries=[second])
230
- )
231
- )
232
- model.handle_request(
233
- TrajectoryModelRequest(
234
- type=TrajectoryModelRequestType.UPDATE, selection=TrajectorySelection(entries=[result])
235
- )
236
- )
237
-
238
- self.assertEqual([entry.set_as_reference for entry in model.items], [False, True])
239
-
240
-
241
- if __name__ == "__main__":
242
- unittest.main()
1
+ """Table models keep the selection on the right entries, and sub-windows are freed and left open independently.
2
+
3
+ Runs headless (``QT_QPA_PLATFORM=offscreen``) and is skipped if the GUI extras are not installed.
4
+ """
5
+
6
+ import os
7
+ import unittest
8
+
9
+ os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
10
+
11
+ try:
12
+ from PySide6 import QtCore, QtWidgets
13
+
14
+ from trajectopy.core.settings import PlotSettings
15
+ from trajectopy.gui.managers.plot_manager import PlotManager
16
+ from trajectopy.gui.managers.requests import (
17
+ PlotRequest,
18
+ PlotRequestType,
19
+ TrajectoryModelRequest,
20
+ TrajectoryModelRequestType,
21
+ UIRequest,
22
+ UIRequestType,
23
+ )
24
+ from trajectopy.gui.managers.ui_manager import UIManager
25
+ from trajectopy.gui.models.entries import TrajectoryEntry
26
+ from trajectopy.gui.models.selection import TrajectorySelection
27
+ from trajectopy.gui.models.trajectory_model import TrajectoryTableModel
28
+ from trajectopy.gui.views.trajectory_table_view import TrajectoryTableView
29
+ except ImportError: # pragma: no cover - GUI extras not installed
30
+ QtWidgets = None
31
+
32
+ from test.testdata import noisy_trajectory
33
+
34
+
35
+ def _entry(name: str, group_id: str | None = None) -> "TrajectoryEntry":
36
+ trajectory = noisy_trajectory.copy()
37
+ trajectory.name = name
38
+ entry = TrajectoryEntry(full_filename="", trajectory=trajectory)
39
+ if group_id is not None:
40
+ entry.group_id = group_id
41
+ return entry
42
+
43
+
44
+ @unittest.skipIf(QtWidgets is None, "GUI dependencies not installed")
45
+ class TestTableModelSelection(unittest.TestCase):
46
+ @classmethod
47
+ def setUpClass(cls) -> None:
48
+ cls.app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
49
+
50
+ def setUp(self) -> None:
51
+ self.model = TrajectoryTableModel()
52
+ self.view = TrajectoryTableView(parent=None, trajectory_table_model=self.model)
53
+ self.entries = [_entry(f"T{i}") for i in range(4)]
54
+ for entry in self.entries:
55
+ self.model.add(entry)
56
+
57
+ def selected_names(self) -> list[str]:
58
+ return [entry.name for entry in self.view.selected_entries]
59
+
60
+ def test_selection_stays_on_entry_when_row_above_is_removed(self):
61
+ self.view.select_entries([self.entries[1]])
62
+ self.model.remove([self.entries[0].entry_id])
63
+ self.assertEqual(self.selected_names(), ["T1"])
64
+
65
+ def test_removing_the_selected_entry_clears_the_selection(self):
66
+ self.view.select_entries([self.entries[2]])
67
+ self.model.remove([self.entries[2].entry_id])
68
+ self.assertEqual(self.selected_names(), [])
69
+ self.assertEqual([entry.name for entry in self.model.items], ["T0", "T1", "T3"])
70
+
71
+ def test_selection_follows_entry_when_reordered(self):
72
+ self.view.select_entries([self.entries[1]])
73
+ order = [self.entries[i].entry_id for i in (3, 2, 1, 0)]
74
+ self.model.sort_items(TrajectoryModelRequest(type=TrajectoryModelRequestType.SORT, index_list=order))
75
+ self.assertEqual([entry.name for entry in self.model.items], ["T3", "T2", "T1", "T0"])
76
+ self.assertEqual(self.selected_names(), ["T1"])
77
+
78
+ def test_entries_missing_in_the_order_are_kept(self):
79
+ order = [self.entries[2].entry_id, self.entries[0].entry_id]
80
+ self.model.sort_items(TrajectoryModelRequest(type=TrajectoryModelRequestType.SORT, index_list=order))
81
+ self.assertEqual([entry.name for entry in self.model.items], ["T2", "T0", "T1", "T3"])
82
+
83
+ def test_remove_group_removes_only_that_group(self):
84
+ grouped = [_entry("G0", group_id="group"), _entry("G1", group_id="group")]
85
+ for entry in grouped:
86
+ self.model.add(entry)
87
+ self.view.select_entries([self.entries[3]])
88
+ self.model.remove_group("group")
89
+ self.assertEqual([entry.name for entry in self.model.items], ["T0", "T1", "T2", "T3"])
90
+ self.assertEqual(self.selected_names(), ["T3"])
91
+
92
+ def test_derived_entries_are_grouped_after_their_source(self):
93
+ derived = self.entries[1].derive(noisy_trajectory.copy())
94
+ self.model.add(derived)
95
+ self.assertEqual(self.model.items.index(derived), 2)
96
+ self.assertEqual(self.model.rowCount(), 4) # T0, group of T1, T2, T3
97
+ group = self.model.index(1, 0)
98
+ self.assertEqual(self.model.group_at(group), self.entries[1].group_id)
99
+ self.assertEqual(self.model.rowCount(group), 2)
100
+ self.assertEqual(self.model.data(group), "T1 (2)")
101
+
102
+ def test_selecting_a_group_selects_its_entries(self):
103
+ self.model.add(self.entries[1].derive(noisy_trajectory.copy()))
104
+ group = self.model.index(1, 0)
105
+ self.view.selectionModel().select(
106
+ group, QtCore.QItemSelectionModel.SelectionFlag.Select | QtCore.QItemSelectionModel.SelectionFlag.Rows
107
+ )
108
+ self.assertEqual(len(self.view.selected_entries), 2)
109
+ self.assertEqual(set(self.view.selection.groups), {self.entries[1].group_id})
110
+
111
+ def test_group_rename_ungroup(self):
112
+ ids = {self.entries[0].entry_id, self.entries[2].entry_id}
113
+ self.view.select_entries([self.entries[2]])
114
+ self.model.group_entries(ids, "Run 1")
115
+ self.assertEqual([entry.name for entry in self.model.items], ["T0", "T2", "T1", "T3"])
116
+ self.assertEqual(list(self.model.groups().values()), ["Run 1"])
117
+ self.assertEqual(self.selected_names(), ["T2"]) # the selection follows the entry into the group
118
+
119
+ group_id = self.entries[0].group_id
120
+ self.model.rename_groups({group_id}, "Run 2")
121
+ self.assertEqual(self.model.data(self.model.index(0, 0)), "Run 2 (2)")
122
+
123
+ self.model.move_to_group({self.entries[3].entry_id}, group_id)
124
+ self.assertEqual([entry.name for entry in self.model.items], ["T0", "T2", "T3", "T1"])
125
+ self.assertEqual(self.entries[3].group_name, "Run 2")
126
+
127
+ self.model.ungroup_entries({self.entries[0].entry_id, self.entries[2].entry_id})
128
+ self.assertEqual(list(self.model.groups().values()), ["Run 2"]) # a named group stays with one entry
129
+ self.model.ungroup_entries({self.entries[3].entry_id})
130
+ self.assertEqual(self.model.groups(), {})
131
+ self.assertEqual(self.model.rowCount(), 4)
132
+
133
+ def test_dropping_entries(self):
134
+ self.model.group_entries({self.entries[0].entry_id, self.entries[1].entry_id}, "Run")
135
+ data = self.model.mimeData([self.model.entry_index(self.entries[3].entry_id)])
136
+ group = self.model.group_index(self.entries[0].group_id)
137
+ self.assertTrue(self.model.dropMimeData(data, QtCore.Qt.DropAction.MoveAction, -1, -1, group))
138
+ self.assertEqual(self.entries[3].group_id, self.entries[0].group_id)
139
+
140
+ # onto an entry outside of groups: a group with that entry
141
+ data = self.model.mimeData([self.model.entry_index(self.entries[3].entry_id)])
142
+ target = self.model.entry_index(self.entries[2].entry_id)
143
+ self.assertTrue(self.model.dropMimeData(data, QtCore.Qt.DropAction.MoveAction, -1, -1, target))
144
+ self.assertEqual(self.entries[3].group_id, self.entries[2].group_id)
145
+ self.assertEqual(len(self.model.groups()), 2)
146
+
147
+ # between the rows of the top level: out of the group
148
+ data = self.model.mimeData([self.model.entry_index(self.entries[3].entry_id)])
149
+ self.assertTrue(self.model.dropMimeData(data, QtCore.Qt.DropAction.MoveAction, 0, 0, QtCore.QModelIndex()))
150
+ self.assertEqual(len(self.model.groups()), 1)
151
+
152
+ def test_collapsed_groups_stay_collapsed(self):
153
+ self.model.group_entries({self.entries[0].entry_id, self.entries[1].entry_id}, "Run")
154
+ group = self.model.group_index(self.entries[0].group_id)
155
+ self.assertTrue(self.view.isExpanded(group))
156
+ self.view.collapse(group)
157
+ self.model.remove([self.entries[3].entry_id])
158
+ self.assertFalse(self.view.isExpanded(self.model.group_index(self.entries[0].group_id)))
159
+
160
+ def test_reset_clears_the_selection(self):
161
+ self.view.select_entries([self.entries[0]])
162
+ self.model.reset()
163
+ self.assertEqual(self.model.rowCount(), 0)
164
+ self.assertEqual(self.selected_names(), [])
165
+
166
+
167
+ @unittest.skipIf(QtWidgets is None, "GUI dependencies not installed")
168
+ class TestSubWindows(unittest.TestCase):
169
+ @classmethod
170
+ def setUpClass(cls) -> None:
171
+ cls.app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
172
+
173
+ def setUp(self) -> None:
174
+ self.main_window = QtWidgets.QMainWindow()
175
+ self.addCleanup(self.main_window.deleteLater)
176
+ self.selection = TrajectorySelection(entries=[_entry("T0")])
177
+
178
+ def _process_deferred_deletes(self) -> None:
179
+ for _ in range(5):
180
+ self.app.sendPostedEvents(None, 0) # 0: QEvent.DeferredDelete included
181
+ self.app.processEvents()
182
+
183
+ def test_closed_plot_windows_are_freed(self):
184
+ manager = PlotManager(parent=self.main_window)
185
+ request = PlotRequest(
186
+ type=PlotRequestType.TRAJECTORIES, trajectory_selection=self.selection, plot_settings=PlotSettings()
187
+ )
188
+ for _ in range(3):
189
+ manager.handle_request(request)
190
+ manager._windows[-1].close()
191
+ self._process_deferred_deletes()
192
+ self.assertEqual(manager._windows, [])
193
+ self.assertEqual(self.main_window.findChildren(QtWidgets.QMainWindow), [])
194
+
195
+ def test_opening_a_window_leaves_the_others_open(self):
196
+ manager = UIManager(parent=self.main_window)
197
+ manager.handle_request(UIRequest(type=UIRequestType.TRAJ_SETTINGS, trajectory_selection=self.selection))
198
+ manager.handle_request(UIRequest(type=UIRequestType.DOF_ORGANIZER, trajectory_selection=self.selection))
199
+ windows = self.main_window.findChildren(QtWidgets.QMainWindow)
200
+ self.assertEqual(len(windows), 2)
201
+ self.assertTrue(all(window.isVisible() for window in windows))
202
+ for window in windows:
203
+ window.close()
204
+ self._process_deferred_deletes()
205
+ self.assertEqual(self.main_window.findChildren(QtWidgets.QMainWindow), [])
206
+
207
+ def test_windows_open_where_the_main_window_is(self):
208
+ from trajectopy.gui.utils import center_window
209
+
210
+ available = self.main_window.screen().availableGeometry()
211
+ self.main_window.setGeometry(available.left() + 50, available.top() + 50, 600, 400)
212
+ self.main_window.show()
213
+ window = QtWidgets.QMainWindow(self.main_window)
214
+ window.resize(200, 100)
215
+ center_window(window)
216
+ self.assertEqual(window.frameGeometry().center(), self.main_window.frameGeometry().center())
217
+
218
+ self.main_window.move(available.left(), available.top()) # partly beyond the screen if centered
219
+ window.resize(available.width(), 100)
220
+ center_window(window)
221
+ self.assertEqual(window.frameGeometry().left(), available.left())
222
+ self.main_window.hide()
223
+
224
+ def _visible_windows(self) -> list:
225
+ return [window for window in self.main_window.findChildren(QtWidgets.QMainWindow) if window.isVisible()]
226
+
227
+ def _close_all(self) -> None:
228
+ for window in self.main_window.findChildren(QtWidgets.QMainWindow):
229
+ window.close()
230
+ self._process_deferred_deletes()
231
+
232
+ def test_settings_of_a_trajectory_open_only_once(self):
233
+ manager = UIManager(parent=self.main_window)
234
+ request = UIRequest(type=UIRequestType.TRAJ_SETTINGS, trajectory_selection=self.selection)
235
+ manager.handle_request(request)
236
+ first = self._visible_windows()
237
+ manager.handle_request(request)
238
+
239
+ self.assertEqual(self._visible_windows(), first)
240
+ self.assertEqual(len(first), 1)
241
+
242
+ # once closed, it opens again
243
+ self._close_all()
244
+ manager.handle_request(request)
245
+ self.assertEqual(len(self._visible_windows()), 1)
246
+ self._close_all()
247
+
248
+ def test_a_window_for_the_current_selection_replaces_the_open_one(self):
249
+ manager = UIManager(parent=self.main_window)
250
+ request = UIRequest(type=UIRequestType.DOF_ORGANIZER, trajectory_selection=self.selection)
251
+ manager.handle_request(request)
252
+ first = self._visible_windows()
253
+ manager.handle_request(request)
254
+ second = self._visible_windows()
255
+
256
+ self.assertEqual(len(second), 1)
257
+ self.assertIsNot(second[0], first[0])
258
+ self._close_all()
259
+
260
+ def test_applying_settings_keeps_values_outside_of_the_usual_range(self):
261
+ from trajectopy.core.settings import ProcessingSettings
262
+ from trajectopy.gui.views.json_settings_view import JSONViewer
263
+
264
+ settings = ProcessingSettings()
265
+ settings.alignment.preprocessing.max_samples = 250_000
266
+ settings.alignment.preprocessing.time_start = -2.5
267
+ viewer = JSONViewer(parent=self.main_window, settings=settings)
268
+ viewer.apply_changes()
269
+ viewer.close()
270
+ self._process_deferred_deletes()
271
+
272
+ self.assertEqual(settings.alignment.preprocessing.max_samples, 250_000)
273
+ self.assertEqual(settings.alignment.preprocessing.time_start, -2.5)
274
+
275
+
276
+ @unittest.skipIf(QtWidgets is None, "GUI dependencies not installed")
277
+ class TestUniqueIds(unittest.TestCase):
278
+ @classmethod
279
+ def setUpClass(cls) -> None:
280
+ cls.app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
281
+
282
+ def test_a_result_file_opened_twice_gives_two_independent_entries(self):
283
+ import tempfile
284
+
285
+ from trajectopy.gui.models.entries import AbsoluteDeviationEntry
286
+ from trajectopy.gui.models.result_model import ResultTableModel
287
+ from trajectopy.processing.evaluation import ate
288
+
289
+ with tempfile.TemporaryDirectory() as directory:
290
+ filename = os.path.join(directory, "ate.result")
291
+ AbsoluteDeviationEntry(deviations=ate(noisy_trajectory, noisy_trajectory, align=False)).to_file(filename)
292
+ first, second = AbsoluteDeviationEntry.from_file(filename), AbsoluteDeviationEntry.from_file(filename)
293
+
294
+ model = ResultTableModel()
295
+ model.add(first)
296
+ model.add(second)
297
+ model.remove([second.entry_id])
298
+
299
+ self.assertEqual(model.items, [first])
300
+
301
+ def test_operation_results_keep_the_current_reference(self):
302
+ import copy
303
+
304
+ from trajectopy.gui.managers.requests import TrajectoryModelRequest, TrajectoryModelRequestType
305
+
306
+ model = TrajectoryTableModel()
307
+ first, second = _entry("first"), _entry("second")
308
+ first.set_as_reference = True
309
+ model.add(first)
310
+ model.add(second)
311
+ result = copy.deepcopy(first) # e.g. an EPSG change, computed while the reference is changed
312
+ model.handle_request(
313
+ TrajectoryModelRequest(
314
+ type=TrajectoryModelRequestType.SET_REFERENCE, selection=TrajectorySelection(entries=[second])
315
+ )
316
+ )
317
+ model.handle_request(
318
+ TrajectoryModelRequest(
319
+ type=TrajectoryModelRequestType.UPDATE, selection=TrajectorySelection(entries=[result])
320
+ )
321
+ )
322
+
323
+ self.assertEqual([entry.set_as_reference for entry in model.items], [False, True])
324
+
325
+
326
+ if __name__ == "__main__":
327
+ unittest.main()