napari-track-edit 1.0.0a2__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.
- napari_track_edit-1.0.0a2/.github/CODEOWNERS +9 -0
- napari_track_edit-1.0.0a2/.github/dependabot.yml +10 -0
- napari_track_edit-1.0.0a2/.github/workflows/benchmark-report.yml +63 -0
- napari_track_edit-1.0.0a2/.github/workflows/benchmark_pr.py +142 -0
- napari_track_edit-1.0.0a2/.github/workflows/benchmarks.yml +141 -0
- napari_track_edit-1.0.0a2/.github/workflows/deploy.yml +35 -0
- napari_track_edit-1.0.0a2/.github/workflows/docs.yml +47 -0
- napari_track_edit-1.0.0a2/.github/workflows/make_bundle_app.yml +17 -0
- napari_track_edit-1.0.0a2/.github/workflows/test.yml +112 -0
- napari_track_edit-1.0.0a2/.gitignore +96 -0
- napari_track_edit-1.0.0a2/.pre-commit-config.yaml +23 -0
- napari_track_edit-1.0.0a2/CITATION.cff +37 -0
- napari_track_edit-1.0.0a2/DEVELOPER.md +20 -0
- napari_track_edit-1.0.0a2/LICENSE +28 -0
- napari_track_edit-1.0.0a2/PKG-INFO +134 -0
- napari_track_edit-1.0.0a2/README.md +75 -0
- napari_track_edit-1.0.0a2/assets/all_objects.png +0 -0
- napari_track_edit-1.0.0a2/assets/all_vs_lineages.png +0 -0
- napari_track_edit-1.0.0a2/assets/editing.png +0 -0
- napari_track_edit-1.0.0a2/assets/editing_menu.png +0 -0
- napari_track_edit-1.0.0a2/assets/editing_selection_tab.png +0 -0
- napari_track_edit-1.0.0a2/assets/groups.png +0 -0
- napari_track_edit-1.0.0a2/assets/lineages.png +0 -0
- napari_track_edit-1.0.0a2/assets/logo.png +0 -0
- napari_track_edit-1.0.0a2/assets/main_widget_startup.png +0 -0
- napari_track_edit-1.0.0a2/assets/manual_editing.png +0 -0
- napari_track_edit-1.0.0a2/assets/napari-track-edit_tutorial.md +319 -0
- napari_track_edit-1.0.0a2/assets/napari-track-edit_tutorial.pdf +0 -0
- napari_track_edit-1.0.0a2/assets/sample1.png +0 -0
- napari_track_edit-1.0.0a2/assets/sample2.png +0 -0
- napari_track_edit-1.0.0a2/assets/save_and_export.png +0 -0
- napari_track_edit-1.0.0a2/assets/size_feature.png +0 -0
- napari_track_edit-1.0.0a2/assets/tracking_parameters.png +0 -0
- napari_track_edit-1.0.0a2/assets/tracking_result2D.png +0 -0
- napari_track_edit-1.0.0a2/assets/tracking_result3D.png +0 -0
- napari_track_edit-1.0.0a2/assets/tracks_list.png +0 -0
- napari_track_edit-1.0.0a2/assets/tree_vs_size.png +0 -0
- napari_track_edit-1.0.0a2/docs/Makefile +20 -0
- napari_track_edit-1.0.0a2/docs/source/conf.py +26 -0
- napari_track_edit-1.0.0a2/docs/source/editing.rst +103 -0
- napari_track_edit-1.0.0a2/docs/source/getting_started.rst +259 -0
- napari_track_edit-1.0.0a2/docs/source/images/add_edge.png +0 -0
- napari_track_edit-1.0.0a2/docs/source/images/add_node.png +0 -0
- napari_track_edit-1.0.0a2/docs/source/images/break_edge.png +0 -0
- napari_track_edit-1.0.0a2/docs/source/images/delete_node.png +0 -0
- napari_track_edit-1.0.0a2/docs/source/images/results_demo_720p.mp4 +0 -0
- napari_track_edit-1.0.0a2/docs/source/images/swap_edges.png +0 -0
- napari_track_edit-1.0.0a2/docs/source/index.rst +32 -0
- napari_track_edit-1.0.0a2/docs/source/key_bindings.rst +123 -0
- napari_track_edit-1.0.0a2/docs/source/motile.rst +54 -0
- napari_track_edit-1.0.0a2/docs/source/tree_view.rst +25 -0
- napari_track_edit-1.0.0a2/docs/source/view_external_tracks.rst +65 -0
- napari_track_edit-1.0.0a2/justfile +11 -0
- napari_track_edit-1.0.0a2/meetings/2026-04-29/generate_timeline.py +182 -0
- napari_track_edit-1.0.0a2/meetings/2026-04-29/meeting-notes.md +46 -0
- napari_track_edit-1.0.0a2/meetings/2026-04-29/timeline.png +0 -0
- napari_track_edit-1.0.0a2/meetings/2026-04-29/timeline.svg +2922 -0
- napari_track_edit-1.0.0a2/meetings/2026-04-29/timeline.yaml +86 -0
- napari_track_edit-1.0.0a2/meetings/2026-04-29/tracksdata_merged.png +0 -0
- napari_track_edit-1.0.0a2/pyproject.toml +184 -0
- napari_track_edit-1.0.0a2/scripts/hela_example_tracks.csv +8602 -0
- napari_track_edit-1.0.0a2/scripts/load_external_points.py +38 -0
- napari_track_edit-1.0.0a2/scripts/run_hela.py +36 -0
- napari_track_edit-1.0.0a2/scripts/view_external_tracks.py +48 -0
- napari_track_edit-1.0.0a2/setup.cfg +4 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/__init__.py +6 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/__main__.py +66 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/_version.py +24 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/_wgpu_setup.py +32 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/application_menus/__init__.py +1 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/application_menus/editing_selection_menu.py +312 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/application_menus/feature_widget.py +407 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/application_menus/group_widget.py +23 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/application_menus/layer_dropdown.py +239 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/application_menus/main_app.py +140 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/application_menus/menu_manager.py +250 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/application_menus/track_list_widget.py +31 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/application_menus/tracking_from_scratch_widget.py +115 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/application_menus/tracking_widget.py +22 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/application_menus/visualization_widget.py +401 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/application_menus/welcome_widget.py +175 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/__init__.py +7 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/colormap.py +499 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/dims_utils.py +120 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/keybindings_config.py +197 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/lazy_array_wrapper.py +93 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/node_type.py +15 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/__init__.py +0 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/layers/__init__.py +0 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/layers/click_utils.py +57 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/layers/contour_labels.py +378 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/layers/out_of_slice_points.py +131 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/layers/track_graph.py +204 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/layers/track_labels.py +645 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/layers/track_points.py +417 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/layers/tracks_layer_group.py +224 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/ortho_views.py +463 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/table/__init__.py +0 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/table/custom_table_widget.py +607 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/tree_view/__init__.py +0 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/tree_view/flip_axes_widget.py +29 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/tree_view/navigation_widget.py +187 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/tree_view/tree_plot_fpl.py +1267 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/tree_view/tree_view_feature_widget.py +131 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/tree_view/tree_view_mode_widget.py +59 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/tree_view/tree_view_options_widget.py +53 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/tree_view/tree_widget.py +518 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views/tree_view/tree_widget_utils.py +432 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views_coordinator/__init__.py +0 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views_coordinator/groups.py +510 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views_coordinator/node_selection_history.py +283 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views_coordinator/tracks_list.py +554 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views_coordinator/tracks_viewer.py +814 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/data_views/views_coordinator/user_dialogs.py +178 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/download_progress.py +70 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/example_data.py +493 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/__init__.py +1 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/geff_io.py +75 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/menus/__init__.py +0 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/menus/csv_dimension_widget.py +54 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/menus/csv_import_widget.py +97 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/menus/export_dialog.py +382 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/menus/geff_import_utils.py +62 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/menus/geff_import_widget.py +105 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/menus/import_dialog.py +482 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/menus/prop_map_widget.py +585 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/menus/scale_widget.py +119 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/menus/segmentation_widgets.py +443 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/import_export/sql_io.py +402 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/launcher.py +8 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/motile/__init__.py +0 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/motile/backend/__init__.py +3 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/motile/backend/motile_run.py +416 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/motile/backend/solve.py +650 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/motile/backend/solver_params.py +90 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/motile/menus/__init__.py +1 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/motile/menus/motile_widget.py +226 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/motile/menus/param_values.py +76 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/motile/menus/params_editor.py +309 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/motile/menus/params_viewer.py +131 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/motile/menus/run_editor.py +252 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/motile/menus/run_viewer.py +159 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit/napari.yaml +91 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit.egg-info/PKG-INFO +134 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit.egg-info/SOURCES.txt +205 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit.egg-info/dependency_links.txt +1 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit.egg-info/entry_points.txt +5 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit.egg-info/requires.txt +37 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit.egg-info/scm_file_list.json +200 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit.egg-info/scm_version.json +8 -0
- napari_track_edit-1.0.0a2/src/napari_track_edit.egg-info/top_level.txt +1 -0
- napari_track_edit-1.0.0a2/tests/application_menus/test_editing_menu.py +198 -0
- napari_track_edit-1.0.0a2/tests/application_menus/test_feature_widget.py +468 -0
- napari_track_edit-1.0.0a2/tests/application_menus/test_layer_dropdown.py +422 -0
- napari_track_edit-1.0.0a2/tests/application_menus/test_main_app.py +64 -0
- napari_track_edit-1.0.0a2/tests/application_menus/test_menu_manager.py +246 -0
- napari_track_edit-1.0.0a2/tests/application_menus/test_selection_widget.py +161 -0
- napari_track_edit-1.0.0a2/tests/application_menus/test_track_list_widget.py +15 -0
- napari_track_edit-1.0.0a2/tests/application_menus/test_tracking_from_scratch_widget.py +116 -0
- napari_track_edit-1.0.0a2/tests/application_menus/test_tracking_widget.py +81 -0
- napari_track_edit-1.0.0a2/tests/application_menus/test_welcome_widget.py +29 -0
- napari_track_edit-1.0.0a2/tests/benchmarks/bench_data_model.py +39 -0
- napari_track_edit-1.0.0a2/tests/benchmarks/bench_ui_actions.py +290 -0
- napari_track_edit-1.0.0a2/tests/benchmarks/conftest.py +62 -0
- napari_track_edit-1.0.0a2/tests/benchmarks/synthetic_data.py +307 -0
- napari_track_edit-1.0.0a2/tests/conftest.py +334 -0
- napari_track_edit-1.0.0a2/tests/data_views/conftest.py +28 -0
- napari_track_edit-1.0.0a2/tests/data_views/test_center_view.py +673 -0
- napari_track_edit-1.0.0a2/tests/data_views/test_color_feature.py +435 -0
- napari_track_edit-1.0.0a2/tests/data_views/test_colormap.py +866 -0
- napari_track_edit-1.0.0a2/tests/data_views/test_dims_utils.py +152 -0
- napari_track_edit-1.0.0a2/tests/data_views/test_force_operations.py +511 -0
- napari_track_edit-1.0.0a2/tests/data_views/test_keybindings.py +82 -0
- napari_track_edit-1.0.0a2/tests/data_views/test_lazy_array_wrapper.py +201 -0
- napari_track_edit-1.0.0a2/tests/data_views/test_link_clipping_planes.py +125 -0
- napari_track_edit-1.0.0a2/tests/data_views/test_ortho_views.py +354 -0
- napari_track_edit-1.0.0a2/tests/data_views/test_track_labels.py +670 -0
- napari_track_edit-1.0.0a2/tests/data_views/test_track_points.py +500 -0
- napari_track_edit-1.0.0a2/tests/data_views/test_visualization_widget.py +504 -0
- napari_track_edit-1.0.0a2/tests/data_views/views/layers/test_click_utils.py +200 -0
- napari_track_edit-1.0.0a2/tests/data_views/views/layers/test_out_of_slice_points.py +186 -0
- napari_track_edit-1.0.0a2/tests/data_views/views/layers/test_paint_atoms.py +201 -0
- napari_track_edit-1.0.0a2/tests/data_views/views/layers/test_paint_event_parsing.py +561 -0
- napari_track_edit-1.0.0a2/tests/data_views/views/layers/test_track_graph.py +47 -0
- napari_track_edit-1.0.0a2/tests/data_views/views/table/test_table_widget.py +295 -0
- napari_track_edit-1.0.0a2/tests/data_views/views/tree_view/test_navigation_widget.py +606 -0
- napari_track_edit-1.0.0a2/tests/data_views/views/tree_view/test_tree_widget.py +595 -0
- napari_track_edit-1.0.0a2/tests/data_views/views/tree_view/test_tree_widget_utils.py +207 -0
- napari_track_edit-1.0.0a2/tests/data_views/views_coordinator/test_groups.py +559 -0
- napari_track_edit-1.0.0a2/tests/data_views/views_coordinator/test_merge_horizontally.py +162 -0
- napari_track_edit-1.0.0a2/tests/data_views/views_coordinator/test_selection_history.py +351 -0
- napari_track_edit-1.0.0a2/tests/data_views/views_coordinator/test_tracks_list.py +914 -0
- napari_track_edit-1.0.0a2/tests/data_views/views_coordinator/test_tracks_viewer.py +1182 -0
- napari_track_edit-1.0.0a2/tests/import_export/hela_example_tracks.csv +8602 -0
- napari_track_edit-1.0.0a2/tests/import_export/test_export_dialog.py +763 -0
- napari_track_edit-1.0.0a2/tests/import_export/test_geff_scale_widget.py +103 -0
- napari_track_edit-1.0.0a2/tests/import_export/test_import_dialog.py +1203 -0
- napari_track_edit-1.0.0a2/tests/import_export/test_segmentation_widgets.py +581 -0
- napari_track_edit-1.0.0a2/tests/import_export/test_sql_io.py +545 -0
- napari_track_edit-1.0.0a2/tests/motile/backend/test_motile_run.py +159 -0
- napari_track_edit-1.0.0a2/tests/motile/backend/test_solver.py +164 -0
- napari_track_edit-1.0.0a2/tests/motile/menus/test_motile_widget.py +297 -0
- napari_track_edit-1.0.0a2/tests/motile/menus/test_params_editor.py +244 -0
- napari_track_edit-1.0.0a2/tests/motile/menus/test_run_editor.py +229 -0
- napari_track_edit-1.0.0a2/tests/motile/menus/test_run_viewer.py +235 -0
- napari_track_edit-1.0.0a2/tests/test_cli.py +71 -0
- napari_track_edit-1.0.0a2/tests/test_example_data.py +332 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Caroline is the default reviewer for all code
|
|
2
|
+
* @cmalinmayor
|
|
3
|
+
|
|
4
|
+
# Anniek is the tree widget expert
|
|
5
|
+
/src/napari_track_edit/data_views/views/tree_view/* @anniekstok
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Anniek is also the import/export expert
|
|
9
|
+
/src/napari_track_edit/import_export/* @anniekstok
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
name: Benchmark
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
deployments: write
|
|
11
|
+
contents: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
benchmark:
|
|
15
|
+
name: Report benchmarks on gh-pages
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
env:
|
|
18
|
+
UV_NO_SYNC: "1"
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v7
|
|
21
|
+
# The Ubuntu runner image ships broken Microsoft apt repos whose signature
|
|
22
|
+
# is periodically invalid ("Clearsigned file isn't valid, got 'NOSPLIT'"),
|
|
23
|
+
# which fails the apt-get update inside setup-qt-libs. We don't need them.
|
|
24
|
+
- name: Remove broken Microsoft apt sources (ubuntu runner image)
|
|
25
|
+
run: |
|
|
26
|
+
sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list \
|
|
27
|
+
/etc/apt/sources.list.d/azure-cli.list \
|
|
28
|
+
/etc/apt/sources.list.d/azure-cli.sources
|
|
29
|
+
- uses: tlambert03/setup-qt-libs@v1
|
|
30
|
+
|
|
31
|
+
# wgpu (via pygfx/fastplotlib, used by the tree view) needs a real Vulkan/GL
|
|
32
|
+
# adapter; Xvfb (below) only provides X11/GLX, not Vulkan. lavapipe/llvmpipe
|
|
33
|
+
# give it a software one, matching how pygfx/wgpu-py run their own Linux CI.
|
|
34
|
+
- name: Install llvmpipe and lavapipe for offscreen wgpu rendering
|
|
35
|
+
run: |
|
|
36
|
+
sudo apt-get update -y -qq
|
|
37
|
+
sudo apt-get install -y libegl1-mesa-dev libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
|
|
38
|
+
|
|
39
|
+
- name: Set up Python
|
|
40
|
+
uses: astral-sh/setup-uv@v7
|
|
41
|
+
with:
|
|
42
|
+
python-version: "3.13"
|
|
43
|
+
enable-cache: true
|
|
44
|
+
|
|
45
|
+
- name: Install dependencies
|
|
46
|
+
run: uv sync --no-dev --group testing
|
|
47
|
+
|
|
48
|
+
# tests/benchmarks is ignored by default pytest config; `-o addopts=` clears
|
|
49
|
+
# that. GUI benchmarks need a real GL context, hence aganders3/headless-gui.
|
|
50
|
+
- name: Run benchmarks
|
|
51
|
+
uses: aganders3/headless-gui@v2
|
|
52
|
+
with:
|
|
53
|
+
run: uv run pytest tests/benchmarks -o addopts= --benchmark-json output.json
|
|
54
|
+
|
|
55
|
+
- name: Store benchmark results
|
|
56
|
+
uses: benchmark-action/github-action-benchmark@v1
|
|
57
|
+
with:
|
|
58
|
+
name: napari_track_edit benchmarks (pytest-benchmark)
|
|
59
|
+
tool: "pytest"
|
|
60
|
+
output-file-path: output.json
|
|
61
|
+
benchmark-data-dir-path: benchmarks
|
|
62
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
63
|
+
auto-push: true
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Compare two pytest-benchmark JSON files and produce a markdown table.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python benchmark_pr.py <old.json> <new.json> <output.md> [header]
|
|
5
|
+
|
|
6
|
+
The table reports mean ± standard deviation for human readability, but the
|
|
7
|
+
pass/fail gate compares the *median*. Median is robust to a single outlier round
|
|
8
|
+
in either direction: `min` is biased down by an accidentally-fast run, `mean` up
|
|
9
|
+
by an unlucky-slow one (a transient OS/GC spike). With >=3 rounds the median
|
|
10
|
+
ignores one such spike, which is what keeps the noisy single-action benchmarks
|
|
11
|
+
from tripping the gate spuriously.
|
|
12
|
+
|
|
13
|
+
Exits with code 1 if any benchmark's median regresses by more than
|
|
14
|
+
REGRESSION_THRESHOLD percent.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
import pandas as pd
|
|
22
|
+
|
|
23
|
+
REGRESSION_THRESHOLD = 50 # percent
|
|
24
|
+
# Benchmarks with a median below this floor are dominated by timer resolution and OS
|
|
25
|
+
# scheduling jitter rather than the code being measured, so a "regression" on them is
|
|
26
|
+
# noise, not signal: skip the gate (still shown in the report) unless the benchmark is
|
|
27
|
+
# slow on at least one side, which means it should have been caught by the floor already.
|
|
28
|
+
NOISE_FLOOR_SECONDS = 0.02
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def load_stats(path):
|
|
32
|
+
with open(path) as f:
|
|
33
|
+
data = json.load(f)
|
|
34
|
+
|
|
35
|
+
commit = data["commit_info"]["id"]
|
|
36
|
+
|
|
37
|
+
rows = []
|
|
38
|
+
for d in data["benchmarks"]:
|
|
39
|
+
s = d["stats"]
|
|
40
|
+
rows.append(
|
|
41
|
+
{
|
|
42
|
+
"Benchmark": d["name"],
|
|
43
|
+
"median": s["median"],
|
|
44
|
+
"mean": s["mean"],
|
|
45
|
+
"stddev": s["stddev"],
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return commit, pd.DataFrame(rows)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _fmt(mean, std):
|
|
53
|
+
return f"{mean:.5f} ± {std:.5f}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _write(out_file, report):
|
|
57
|
+
with open(out_file, "w") as f:
|
|
58
|
+
f.write(report)
|
|
59
|
+
print(report) # noqa: T201
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def make_report(old_path, new_path, out_file, header=None):
|
|
63
|
+
new_commit, new_df = load_stats(new_path)
|
|
64
|
+
|
|
65
|
+
# No baseline available (e.g. the first PR introducing benchmarks, or a run on
|
|
66
|
+
# a base commit that predates the suite). Report HEAD-only numbers and pass.
|
|
67
|
+
if not os.path.exists(old_path) or os.path.getsize(old_path) == 0:
|
|
68
|
+
df = pd.DataFrame(
|
|
69
|
+
{
|
|
70
|
+
"Benchmark": new_df["Benchmark"],
|
|
71
|
+
f"Median (s) HEAD {new_commit}": new_df["median"].map("{:.5f}".format),
|
|
72
|
+
f"Mean ± SD (s) HEAD {new_commit}": [
|
|
73
|
+
_fmt(m, s)
|
|
74
|
+
for m, s in zip(new_df["mean"], new_df["stddev"], strict=True)
|
|
75
|
+
],
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
# disable_numparse: see note on the comparison table below.
|
|
79
|
+
report = df.to_markdown(index=False, disable_numparse=True)
|
|
80
|
+
note = "_No baseline found; showing HEAD results only (no comparison)._"
|
|
81
|
+
report = f"{note}\n\n{report}"
|
|
82
|
+
if header:
|
|
83
|
+
report = f"## {header}\n\n{report}"
|
|
84
|
+
_write(out_file, report)
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
old_commit, old_df = load_stats(old_path)
|
|
88
|
+
|
|
89
|
+
# Merge on benchmark name (drops benchmarks present on only one side).
|
|
90
|
+
merged = old_df.merge(new_df, on="Benchmark", suffixes=("_old", "_new"))
|
|
91
|
+
|
|
92
|
+
# Gate on the median (see module docstring).
|
|
93
|
+
pct_change = (
|
|
94
|
+
100 * (merged["median_new"] - merged["median_old"]) / merged["median_old"]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# A benchmark below the floor on both sides is dominated by timer resolution and OS
|
|
98
|
+
# scheduling jitter rather than the code being measured, so a "regression" on it is
|
|
99
|
+
# noise, not signal: exclude it from the gate (still shown in the report).
|
|
100
|
+
above_floor = (merged["median_old"] >= NOISE_FLOOR_SECONDS) | (
|
|
101
|
+
merged["median_new"] >= NOISE_FLOOR_SECONDS
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Median columns first: they drive the gate, so "Median Change" is verifiable
|
|
105
|
+
# straight from them. Mean ± SD follows as a noise indicator (a wide SD or a mean
|
|
106
|
+
# far from the median flags a spiky benchmark whose median we're rightly trusting).
|
|
107
|
+
df = pd.DataFrame(
|
|
108
|
+
{
|
|
109
|
+
"Benchmark": merged["Benchmark"],
|
|
110
|
+
f"Median (s) BASE {old_commit}": merged["median_old"].map("{:.5f}".format),
|
|
111
|
+
f"Median (s) HEAD {new_commit}": merged["median_new"].map("{:.5f}".format),
|
|
112
|
+
"Median Change": pct_change.map("{:+.2f}%".format),
|
|
113
|
+
f"Mean ± SD (s) BASE {old_commit}": [
|
|
114
|
+
_fmt(m, s)
|
|
115
|
+
for m, s in zip(merged["mean_old"], merged["stddev_old"], strict=True)
|
|
116
|
+
],
|
|
117
|
+
f"Mean ± SD (s) HEAD {new_commit}": [
|
|
118
|
+
_fmt(m, s)
|
|
119
|
+
for m, s in zip(merged["mean_new"], merged["stddev_new"], strict=True)
|
|
120
|
+
],
|
|
121
|
+
}
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# disable_numparse: keep our fixed-precision strings exactly (tabulate otherwise
|
|
125
|
+
# re-parses numeric-looking cells and strips trailing zeros, so medians would show
|
|
126
|
+
# inconsistent precision next to the mean ± SD column).
|
|
127
|
+
report = df.to_markdown(index=False, disable_numparse=True)
|
|
128
|
+
if header:
|
|
129
|
+
report = f"## {header}\n\n{report}"
|
|
130
|
+
_write(out_file, report)
|
|
131
|
+
|
|
132
|
+
# Fail if any benchmark above the noise floor regressed beyond threshold
|
|
133
|
+
if (above_floor & (pct_change > REGRESSION_THRESHOLD)).any():
|
|
134
|
+
print( # noqa: T201
|
|
135
|
+
f"\nFAILED: median regression exceeds {REGRESSION_THRESHOLD}% threshold"
|
|
136
|
+
)
|
|
137
|
+
sys.exit(1)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
header = sys.argv[4] if len(sys.argv) > 4 else None
|
|
142
|
+
make_report(sys.argv[1], sys.argv[2], sys.argv[3], header)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
name: benchmarks
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request: {}
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
|
|
7
|
+
concurrency:
|
|
8
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
9
|
+
cancel-in-progress: true
|
|
10
|
+
|
|
11
|
+
# Needed for peter-evans/commit-comment to post the benchmark report on the commit.
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
benchmark:
|
|
17
|
+
name: Benchmark (${{ matrix.platform }})
|
|
18
|
+
runs-on: ${{ matrix.platform }}
|
|
19
|
+
if: github.event_name == 'pull_request'
|
|
20
|
+
# Healthy runs finish in ~10 minutes; without this a hang (e.g. a wgpu/tree-widget
|
|
21
|
+
# deadlock) burns the full 360-minute default before GitHub cancels it.
|
|
22
|
+
timeout-minutes: 30
|
|
23
|
+
strategy:
|
|
24
|
+
fail-fast: false
|
|
25
|
+
matrix:
|
|
26
|
+
platform: [ubuntu-latest, macos-latest, windows-latest]
|
|
27
|
+
env:
|
|
28
|
+
# Stop `uv run` from auto-resyncing (which would drop the `testing` group).
|
|
29
|
+
# Each benchmark step below runs its own explicit `uv sync` after checkout so
|
|
30
|
+
# base and head are each measured against *their own* locked dependencies.
|
|
31
|
+
UV_NO_SYNC: "1"
|
|
32
|
+
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v7
|
|
35
|
+
with:
|
|
36
|
+
fetch-depth: 50
|
|
37
|
+
# The Ubuntu runner image ships broken Microsoft apt repos whose signature
|
|
38
|
+
# is periodically invalid ("Clearsigned file isn't valid, got 'NOSPLIT'"),
|
|
39
|
+
# which fails the apt-get update inside setup-qt-libs. We don't need them.
|
|
40
|
+
- name: Remove broken Microsoft apt sources (ubuntu runner image)
|
|
41
|
+
if: runner.os == 'Linux'
|
|
42
|
+
run: |
|
|
43
|
+
sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list \
|
|
44
|
+
/etc/apt/sources.list.d/azure-cli.list \
|
|
45
|
+
/etc/apt/sources.list.d/azure-cli.sources
|
|
46
|
+
- uses: tlambert03/setup-qt-libs@v1
|
|
47
|
+
|
|
48
|
+
# wgpu (via pygfx/fastplotlib, used by the tree view) needs a real Vulkan/GL
|
|
49
|
+
# adapter; Xvfb (below) only provides X11/GLX, not Vulkan. lavapipe/llvmpipe
|
|
50
|
+
# give it a software one, matching how pygfx/wgpu-py run their own Linux CI.
|
|
51
|
+
- name: Install llvmpipe and lavapipe for offscreen wgpu rendering
|
|
52
|
+
if: runner.os == 'Linux'
|
|
53
|
+
run: |
|
|
54
|
+
sudo apt-get update -y -qq
|
|
55
|
+
sudo apt-get install -y libegl1-mesa-dev libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
|
|
56
|
+
|
|
57
|
+
- name: Set up Python
|
|
58
|
+
uses: astral-sh/setup-uv@v7
|
|
59
|
+
with:
|
|
60
|
+
python-version: "3.13"
|
|
61
|
+
enable-cache: true
|
|
62
|
+
|
|
63
|
+
- name: Set Windows resolution
|
|
64
|
+
if: runner.os == 'Windows'
|
|
65
|
+
run: Set-DisplayResolution -Width 1920 -Height 1080 -Force
|
|
66
|
+
shell: powershell
|
|
67
|
+
|
|
68
|
+
- name: Install Windows OpenGL
|
|
69
|
+
if: runner.os == 'Windows'
|
|
70
|
+
run: |
|
|
71
|
+
git clone --depth 1 https://github.com/pyvista/gl-ci-helpers.git
|
|
72
|
+
powershell gl-ci-helpers/appveyor/install_opengl.ps1
|
|
73
|
+
if (Test-Path -Path "C:\Windows\system32\opengl32.dll" -PathType Leaf) {Exit 0} else {Exit 1}
|
|
74
|
+
shell: powershell
|
|
75
|
+
|
|
76
|
+
# Same-runner base-vs-head comparison (running baseline fresh every PR) turned
|
|
77
|
+
# out not to fix cross-run noise either -- swings as large as the ones it was
|
|
78
|
+
# meant to prevent still showed up within a single same-runner run. Since running
|
|
79
|
+
# baseline live buys no real stability, cache it per base-SHA instead: the first
|
|
80
|
+
# PR against a given base commit produces it, every later PR against that same
|
|
81
|
+
# base restores it instead of re-running -- half the benchmark cost with no
|
|
82
|
+
# measurement downside.
|
|
83
|
+
- name: Retrieve cached baseline if available
|
|
84
|
+
uses: actions/cache/restore@v5
|
|
85
|
+
id: cache_baseline
|
|
86
|
+
with:
|
|
87
|
+
path: baseline.json
|
|
88
|
+
key: benchmark-${{ matrix.platform }}-${{ github.event.pull_request.base.sha }}
|
|
89
|
+
|
|
90
|
+
# The benchmark suite lives under tests/benchmarks, which the default pytest
|
|
91
|
+
# config ignores; `-o addopts=` clears that so it is collected here. The GUI
|
|
92
|
+
# benchmarks need a real GL context, hence aganders3/headless-gui.
|
|
93
|
+
#
|
|
94
|
+
# Runs its own `uv sync` after checkout so base is measured against its OWN
|
|
95
|
+
# locked dependencies, not head's. This is what makes dependency-bump PRs
|
|
96
|
+
# (funtracks/tracksdata/...) measure the real base-vs-head delta: without this,
|
|
97
|
+
# base code would run against head's deps -- an apples-to-oranges comparison
|
|
98
|
+
# that reports fictional regressions whenever a dependency changes a shared
|
|
99
|
+
# contract. `git checkout -f` discards any uv.lock churn from a previous sync.
|
|
100
|
+
# Tolerant on purpose: the base commit may predate the benchmark suite, in which
|
|
101
|
+
# case there is no baseline and the report falls back to HEAD-only numbers.
|
|
102
|
+
- name: Run baseline benchmark if not in cache
|
|
103
|
+
if: steps.cache_baseline.outputs.cache-hit != 'true'
|
|
104
|
+
continue-on-error: true
|
|
105
|
+
uses: aganders3/headless-gui@v2
|
|
106
|
+
with:
|
|
107
|
+
run: |
|
|
108
|
+
git checkout -f ${{ github.event.pull_request.base.sha }}
|
|
109
|
+
uv sync --no-dev --group testing
|
|
110
|
+
uv run pytest tests/benchmarks -o addopts= --benchmark-json baseline.json
|
|
111
|
+
|
|
112
|
+
- name: Cache baseline results
|
|
113
|
+
uses: actions/cache/save@v5
|
|
114
|
+
if: steps.cache_baseline.outputs.cache-hit != 'true' && hashFiles('baseline.json') != ''
|
|
115
|
+
with:
|
|
116
|
+
path: baseline.json
|
|
117
|
+
key: benchmark-${{ matrix.platform }}-${{ github.event.pull_request.base.sha }}
|
|
118
|
+
|
|
119
|
+
- name: Run benchmark on PR head
|
|
120
|
+
uses: aganders3/headless-gui@v2
|
|
121
|
+
with:
|
|
122
|
+
run: |
|
|
123
|
+
git checkout -f ${{ github.event.pull_request.head.sha }}
|
|
124
|
+
uv sync --no-dev --group testing
|
|
125
|
+
uv run pytest tests/benchmarks -o addopts= --benchmark-json pr.json
|
|
126
|
+
|
|
127
|
+
- name: Generate report
|
|
128
|
+
id: report
|
|
129
|
+
run: |
|
|
130
|
+
uv run --with pandas --with tabulate python .github/workflows/benchmark_pr.py baseline.json pr.json report.md "Benchmark: ${{ matrix.platform }}"
|
|
131
|
+
continue-on-error: true
|
|
132
|
+
|
|
133
|
+
- name: Comment on commit with report
|
|
134
|
+
uses: peter-evans/commit-comment@v4
|
|
135
|
+
if: github.event.pull_request.head.repo.fork == false
|
|
136
|
+
with:
|
|
137
|
+
body-path: report.md
|
|
138
|
+
|
|
139
|
+
- name: Fail if regression detected
|
|
140
|
+
if: steps.report.outcome == 'failure'
|
|
141
|
+
run: exit 1
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Deploy
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["*"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build-n-publish:
|
|
10
|
+
name: Build and publish Python 🐍 distributions 📦 to PyPI
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
if: contains(github.ref, 'tags')
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write
|
|
15
|
+
contents: write
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
- uses: actions/setup-python@v7
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.x"
|
|
21
|
+
- name: Build a binary wheel and a source tarball
|
|
22
|
+
run: |
|
|
23
|
+
python -m pip install -U pip
|
|
24
|
+
python -m pip install -U setuptools setuptools_scm build
|
|
25
|
+
python -m build --sdist --wheel --outdir dist/
|
|
26
|
+
- name: Publish to PyPI
|
|
27
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
28
|
+
with:
|
|
29
|
+
skip-existing: true
|
|
30
|
+
- name: Create release
|
|
31
|
+
uses: softprops/action-gh-release@v3
|
|
32
|
+
with:
|
|
33
|
+
draft: ${{ contains(github.ref_name, '-dev') }}
|
|
34
|
+
prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-a') || contains(github.ref_name, '-b') }}
|
|
35
|
+
generate_release_notes: true
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Docs build
|
|
2
|
+
|
|
3
|
+
# build the documentation whenever there are new commits on main
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
# security: restrict permissions for CI jobs.
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
# Build the documentation and upload the static HTML files as an artifact.
|
|
16
|
+
build:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
|
+
- name: Set up Python 3.11
|
|
21
|
+
uses: astral-sh/setup-uv@v7
|
|
22
|
+
with:
|
|
23
|
+
python-version: '3.11'
|
|
24
|
+
enable-cache: true
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: uv sync
|
|
27
|
+
- name: Build docs
|
|
28
|
+
run: |
|
|
29
|
+
cd docs/
|
|
30
|
+
uv run make html
|
|
31
|
+
- uses: actions/upload-pages-artifact@v5
|
|
32
|
+
with:
|
|
33
|
+
path: docs/build/html
|
|
34
|
+
|
|
35
|
+
# Deploy the artifact to GitHub pages.
|
|
36
|
+
deploy:
|
|
37
|
+
needs: build
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
permissions:
|
|
40
|
+
pages: write
|
|
41
|
+
id-token: write
|
|
42
|
+
environment:
|
|
43
|
+
name: github-pages
|
|
44
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
45
|
+
steps:
|
|
46
|
+
- id: deployment
|
|
47
|
+
uses: actions/deploy-pages@v5
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: bundleApp
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types:
|
|
6
|
+
- released
|
|
7
|
+
- published
|
|
8
|
+
# motile_tracker_bundle_app will be triggered when the release is changed from draft to published
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
packaging:
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
uses: funkelab/motile_tracker_bundled_app/.github/workflows/bundle_app.yml@main
|
|
15
|
+
secrets: inherit
|
|
16
|
+
with:
|
|
17
|
+
event_name: ${{ github.event_name }}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"] # Push events to matching v*, i.e. v1.0, v20.15.10
|
|
7
|
+
pull_request: {}
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
test:
|
|
16
|
+
name: ${{ matrix.platform }} py${{ matrix.python-version }} ${{ matrix.resolution }}
|
|
17
|
+
runs-on: ${{ matrix.platform }}
|
|
18
|
+
# Healthy runs finish in 5-12 minutes; without this a hang (e.g. a wgpu/tree-widget
|
|
19
|
+
# deadlock) burns the full 360-minute default before GitHub cancels it.
|
|
20
|
+
timeout-minutes: 30
|
|
21
|
+
env:
|
|
22
|
+
# disable resyncing on uv run
|
|
23
|
+
UV_NO_SYNC: "1"
|
|
24
|
+
strategy:
|
|
25
|
+
fail-fast: false
|
|
26
|
+
# OS-specific bugs are almost always Qt/GL-driver related and Python-version-
|
|
27
|
+
# agnostic, while version-specific bugs are language/stdlib related and OS-
|
|
28
|
+
# agnostic -- the two axes rarely interact, so we don't need their full cross:
|
|
29
|
+
# - lowest-direct (oldest allowed deps) on all platforms, but only at our
|
|
30
|
+
# floor Python (3.11, see requires-python) -- those dependency versions
|
|
31
|
+
# predate 3.12/3.13 anyway, so testing them there too is redundant.
|
|
32
|
+
# - highest deps on all platforms, but only at our newest Python (3.13).
|
|
33
|
+
# - the remaining Python version (3.12) only on ubuntu (cheapest runner), as
|
|
34
|
+
# a spot-check rather than a full platform cross.
|
|
35
|
+
matrix:
|
|
36
|
+
include:
|
|
37
|
+
- platform: ubuntu-latest
|
|
38
|
+
python-version: "3.11"
|
|
39
|
+
resolution: lowest-direct
|
|
40
|
+
- platform: macos-latest
|
|
41
|
+
python-version: "3.11"
|
|
42
|
+
resolution: lowest-direct
|
|
43
|
+
- platform: windows-latest
|
|
44
|
+
python-version: "3.11"
|
|
45
|
+
resolution: lowest-direct
|
|
46
|
+
- platform: ubuntu-latest
|
|
47
|
+
python-version: "3.12"
|
|
48
|
+
resolution: highest
|
|
49
|
+
- platform: ubuntu-latest
|
|
50
|
+
python-version: "3.13"
|
|
51
|
+
resolution: highest
|
|
52
|
+
- platform: macos-latest
|
|
53
|
+
python-version: "3.13"
|
|
54
|
+
resolution: highest
|
|
55
|
+
- platform: windows-latest
|
|
56
|
+
python-version: "3.13"
|
|
57
|
+
resolution: highest
|
|
58
|
+
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v7
|
|
61
|
+
# The Ubuntu runner image ships broken Microsoft apt repos whose signature
|
|
62
|
+
# is periodically invalid ("Clearsigned file isn't valid, got 'NOSPLIT'"),
|
|
63
|
+
# which fails the apt-get update inside setup-qt-libs. We don't need them.
|
|
64
|
+
- name: Remove broken Microsoft apt sources (ubuntu runner image)
|
|
65
|
+
if: runner.os == 'Linux'
|
|
66
|
+
run: |
|
|
67
|
+
sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list \
|
|
68
|
+
/etc/apt/sources.list.d/azure-cli.list \
|
|
69
|
+
/etc/apt/sources.list.d/azure-cli.sources
|
|
70
|
+
- uses: tlambert03/setup-qt-libs@v1
|
|
71
|
+
|
|
72
|
+
# wgpu (via pygfx/fastplotlib, used by the tree view) needs a real Vulkan/GL
|
|
73
|
+
# adapter; Xvfb (below) only provides X11/GLX, not Vulkan. lavapipe/llvmpipe
|
|
74
|
+
# give it a software one, matching how pygfx/wgpu-py run their own Linux CI.
|
|
75
|
+
- name: Install llvmpipe and lavapipe for offscreen wgpu rendering
|
|
76
|
+
if: runner.os == 'Linux'
|
|
77
|
+
run: |
|
|
78
|
+
sudo apt-get update -y -qq
|
|
79
|
+
sudo apt-get install -y libegl1-mesa-dev libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
|
|
80
|
+
|
|
81
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
82
|
+
uses: astral-sh/setup-uv@v7
|
|
83
|
+
with:
|
|
84
|
+
python-version: ${{ matrix.python-version }}
|
|
85
|
+
enable-cache: true
|
|
86
|
+
|
|
87
|
+
- name: Install dependencies
|
|
88
|
+
run: uv sync --no-dev --group testing --resolution ${{ matrix.resolution }}
|
|
89
|
+
|
|
90
|
+
- name: Set Windows resolution
|
|
91
|
+
if: runner.os == 'Windows'
|
|
92
|
+
run: Set-DisplayResolution -Width 1920 -Height 1080 -Force
|
|
93
|
+
shell: powershell
|
|
94
|
+
|
|
95
|
+
- name: Install Windows OpenGL
|
|
96
|
+
if: runner.os == 'Windows'
|
|
97
|
+
run: |
|
|
98
|
+
git clone --depth 1 https://github.com/pyvista/gl-ci-helpers.git
|
|
99
|
+
powershell gl-ci-helpers/appveyor/install_opengl.ps1
|
|
100
|
+
if (Test-Path -Path "C:\Windows\system32\opengl32.dll" -PathType Leaf) {Exit 0} else {Exit 1}
|
|
101
|
+
shell: powershell
|
|
102
|
+
|
|
103
|
+
- name: Test
|
|
104
|
+
uses: aganders3/headless-gui@v2
|
|
105
|
+
with:
|
|
106
|
+
run: uv run pytest --color=yes --cov=napari_track_edit --cov-report=xml --cov-report=term-missing .
|
|
107
|
+
|
|
108
|
+
- name: Coverage
|
|
109
|
+
uses: codecov/codecov-action@v7
|
|
110
|
+
with:
|
|
111
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
112
|
+
slug: live-image-tracking-tools/napari-track-edit
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
scripts/
|
|
2
|
+
uv.lock
|
|
3
|
+
pixi.lock
|
|
4
|
+
|
|
5
|
+
# Byte-compiled / optimized / DLL files
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*$py.class
|
|
9
|
+
|
|
10
|
+
# C extensions
|
|
11
|
+
*.so
|
|
12
|
+
|
|
13
|
+
# Backup files
|
|
14
|
+
*~
|
|
15
|
+
|
|
16
|
+
# Distribution / packaging
|
|
17
|
+
.Python
|
|
18
|
+
env/
|
|
19
|
+
build/
|
|
20
|
+
develop-eggs/
|
|
21
|
+
dist/
|
|
22
|
+
downloads/
|
|
23
|
+
eggs/
|
|
24
|
+
.eggs/
|
|
25
|
+
lib/
|
|
26
|
+
lib64/
|
|
27
|
+
parts/
|
|
28
|
+
sdist/
|
|
29
|
+
var/
|
|
30
|
+
*.egg-info/
|
|
31
|
+
.installed.cfg
|
|
32
|
+
*.egg
|
|
33
|
+
|
|
34
|
+
# PyInstaller
|
|
35
|
+
# Usually these files are written by a python script from a template
|
|
36
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
37
|
+
*.manifest
|
|
38
|
+
*.spec
|
|
39
|
+
|
|
40
|
+
# Installer logs
|
|
41
|
+
pip-log.txt
|
|
42
|
+
pip-delete-this-directory.txt
|
|
43
|
+
|
|
44
|
+
# Unit test / coverage reports
|
|
45
|
+
htmlcov/
|
|
46
|
+
.tox/
|
|
47
|
+
.coverage
|
|
48
|
+
.coverage.*
|
|
49
|
+
.cache
|
|
50
|
+
nosetests.xml
|
|
51
|
+
coverage.xml
|
|
52
|
+
*,cover
|
|
53
|
+
.hypothesis/
|
|
54
|
+
.napari_cache
|
|
55
|
+
|
|
56
|
+
# Translations
|
|
57
|
+
*.mo
|
|
58
|
+
*.pot
|
|
59
|
+
|
|
60
|
+
# Django stuff:
|
|
61
|
+
*.log
|
|
62
|
+
local_settings.py
|
|
63
|
+
|
|
64
|
+
# Flask instance folder
|
|
65
|
+
instance/
|
|
66
|
+
|
|
67
|
+
# Sphinx documentation
|
|
68
|
+
docs/_build/
|
|
69
|
+
|
|
70
|
+
# MkDocs documentation
|
|
71
|
+
/site/
|
|
72
|
+
|
|
73
|
+
# PyBuilder
|
|
74
|
+
target/
|
|
75
|
+
|
|
76
|
+
# Pycharm and VSCode
|
|
77
|
+
.idea/
|
|
78
|
+
venv/
|
|
79
|
+
.vscode/
|
|
80
|
+
|
|
81
|
+
# IPython Notebook
|
|
82
|
+
.ipynb_checkpoints
|
|
83
|
+
|
|
84
|
+
# pyenv
|
|
85
|
+
.python-version
|
|
86
|
+
|
|
87
|
+
# OS
|
|
88
|
+
.DS_Store
|
|
89
|
+
|
|
90
|
+
# written by setuptools_scm
|
|
91
|
+
**/_version.py
|
|
92
|
+
|
|
93
|
+
# Claude
|
|
94
|
+
claude.md
|
|
95
|
+
.claude
|
|
96
|
+
TESTING_STRATEGY.md
|