pyetsimul 3.2.4__tar.gz → 3.3.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.
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/PKG-INFO +5 -6
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/README.md +3 -4
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/conf.py +2 -1
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/index.rst +3 -3
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/custom_gaze_model.md +8 -8
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/experiments/README.md +4 -1
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/experiments/data_validation.py +1 -1
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/core/eye_tracker.py +5 -2
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/stampe1993/stampe1993_gaze_model.py +60 -8
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/simulation/composed_variation.py +1 -2
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/types/geometry.py +14 -12
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyproject.toml +13 -11
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_angle2gaze.py +2 -1
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_eye_find_cr.py +2 -1
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_eye_find_cr_simple.py +4 -3
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_geometry_types_arithmetic.py +2 -2
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_glint_noise.py +10 -10
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_spherical_cornea_scaling.py +9 -9
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/uv.lock +28 -1
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/.github/workflows/publish.yml +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/.gitignore +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/.readthedocs.yaml +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/LICENSE +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/_static/images/eye_model_schematic.png +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/_static/images/eye_tracking_setup.png +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/api/core.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/api/evaluation.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/api/gaze_mapping.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/api/geometry.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/api/index.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/api/optics.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/api/simulation.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/api/types.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/api/visualization.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/getting_started.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/guides/custom_gaze_model.md +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/guides/custom_variations.md +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/theory/camera_and_lights.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/theory/coordinate_systems.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/theory/default_parameters.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/theory/evaluation_workflow.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/theory/eye_model.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/theory/gaze_estimation_models.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/docs/theory/optical_calculations.rst +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/basic_usage_demo.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/camera_distortion.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/cornea_comparison_figure.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/custom_gaze_model.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/custom_polynomial_example.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/data_generation_tutorial.ipynb +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/example.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/experiments/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/experiments/algorithm_comparison.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/experiments/config.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/experiments/data_generation.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/experiments/homography_normalization_demo.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/experiments/polynomial_demo.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/eye_anatomy.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/eyelid_openness_comparison.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/eyelink1000plus.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/glint_noise_demo.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/multi_eye_tracking_setup.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/polynomial_multi_glint.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/pupil_decentration.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/realistic_pupil.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/realistic_pupil_comparison.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/run_all_examples.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/setup_visualization.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/simple_data_generation.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/examples/stampe1993.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/camera_noise/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/camera_noise/glint_noise.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/core/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/core/camera.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/core/cornea.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/core/default_configs.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/core/eye.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/core/eye_operations.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/core/eyelid.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/core/light.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/core/pupil.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/core/pupil_decentration.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/evaluation/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/evaluation/algorithm_comparison.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/evaluation/analysis_utils.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/evaluation/calibration_analysis.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/evaluation/calibration_utils.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/evaluation/gaze_accuracy.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/eyelink1000plus/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/eyelink1000plus/eyelink1000plus_gaze_model.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/eyelink1000plus/href.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/homography_normalization/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/homography_normalization/gaussian_process.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/homography_normalization/homography_normalization_gaze_model.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/homography_normalization/homography_normalization_state.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/homography_normalization/homography_normalization_utils.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/polynomial/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/polynomial/polynomial_descriptor.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/polynomial/polynomial_features.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/polynomial/polynomial_gaze_model.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/polynomial/polynomial_state.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/polynomial/polynomials.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/stampe1993/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/geometry/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/geometry/conversions.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/geometry/intersections.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/geometry/listings_law.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/geometry/plane_detection.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/geometry/utils.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/log.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/optics/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/optics/glint_size.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/optics/pupil_imaging.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/optics/reflections.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/optics/refractions.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/simulation/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/simulation/anatomy.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/simulation/config.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/simulation/core.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/simulation/data_generation.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/simulation/data_loading.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/simulation/eye_position.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/simulation/generic.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/simulation/grid_base.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/simulation/target_position.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/types/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/types/algorithms.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/types/imaging.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/utils/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/utils/eye_surface_points.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/utils/eyelid_surface_points.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/utils/filename.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/utils/rotation_helpers.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/utils/surface_points.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/analysis_plots.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/camera_view.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/coordinate_utils.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/eye_anatomy.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/gaze_accuracy_plots.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/integrated_plots.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/interactive.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/interactive_controls.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/interactive_gaze_plot.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/plot_config.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/setup_plots.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/visualization/transforms.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/resources/Funded_by_EU_Eyes4ICU.png +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_camera_pan_tilt.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_camera_point_at.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_camera_project.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_camera_take_image.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_camera_unproject.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_eye_find_refraction.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_eye_get_pupil.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_eye_get_pupil_image.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_eye_look_at.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_eye_point_within_cornea.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_eye_pupil_size.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_eye_refract_ray_dual_surface.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_find_reflection_conic.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_find_reflection_sphere.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_find_refraction_conic.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_find_refraction_sphere.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_gaze2angle.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_intersect_ray_circle.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_intersect_ray_conic.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_intersect_ray_plane.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_intersect_ray_sphere.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_line_intersect_2d.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_lines_closest_point.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_listings_law.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_polynomial_validation.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_reflect_ray_circle.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_reflect_ray_conic.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_reflect_ray_sphere.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_refract_ray_conic.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/unit_tests/test_refract_ray_sphere.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/__init__.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/exploration_conic_vs_sphere.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/exploration_pupil_size_decentration.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/exploration_realistic_pupil.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/exploration_screen_and_observer_test.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/demo_calibration.pdf +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/demo_calibration.png +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/demo_observer_test.pdf +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/demo_observer_test.png +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/demo_screen_test.pdf +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/demo_screen_test.png +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/results_conic_vs_sphere.txt +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/results_pupil_size_decentration.txt +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/results_realistic_pupil.txt +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/results_screen_observer_test.txt +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/validation_conic_cornea.pdf +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/validation_conic_cornea.png +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/validation_pupil_decentration.pdf +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/validation_pupil_decentration.png +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/validation_realistic_pupil.pdf +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/output/validation_realistic_pupil.png +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/validation_conic_cornea.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/validation_pupil_decentration.py +0 -0
- {pyetsimul-3.2.4 → pyetsimul-3.3.0}/validation_and_scientific_exploitation/validation_realistic_pupil.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyetsimul
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: PyEtSimul: An Open-Source Python Software Framework for Eye Tracking Simulation
|
|
5
5
|
Project-URL: Homepage, https://github.com/mh-salari/pyetsimul
|
|
6
6
|
Project-URL: Repository, https://github.com/mh-salari/pyetsimul
|
|
7
7
|
Project-URL: Documentation, https://pyetsimul.readthedocs.io/
|
|
8
8
|
Project-URL: Issues, https://github.com/mh-salari/pyetsimul/issues
|
|
9
|
-
Author-email: Mohammadhossein Salari <
|
|
9
|
+
Author-email: Mohammadhossein Salari <mohammadhossein.salari@gmail.com>
|
|
10
10
|
License: GPL-3.0-or-later
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Keywords: eye-tracker,eye-tracking,gaze-estimation,simulation
|
|
@@ -27,7 +27,7 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|
[](https://pepy.tech/project/pyetsimul)
|
|
28
28
|
[](https://github.com/mh-salari/pyetsimul/blob/main/LICENSE)
|
|
29
29
|
[](https://pyetsimul.readthedocs.io/)
|
|
30
|
-
[](https://doi.org/10.1145/3806023)
|
|
31
31
|
|
|
32
32
|
PyEtSimul is an open-source Python framework for simulating video-based eye trackers
|
|
33
33
|
by generating synthetic eye features through geometric modeling. The framework allows
|
|
@@ -87,13 +87,12 @@ After installation, explore the various examples in the `examples/` directory to
|
|
|
87
87
|
|
|
88
88
|
## Citation
|
|
89
89
|
|
|
90
|
-
This work has been accepted for publication at ETRA 2026. The DOI will be added when the proceedings are published.
|
|
91
|
-
|
|
92
90
|
**Cite as:**
|
|
93
91
|
```
|
|
94
92
|
Salari, M., Niehorster, D. C., Hansen, D. W., & Bednarik, R. (2026).
|
|
95
93
|
PyEtSimul: An Open-Source Python Framework for Eye-Tracking Simulation.
|
|
96
|
-
|
|
94
|
+
Proceedings of the ACM on Human-Computer Interaction (PACMHCI), ETRA 2026. ACM.
|
|
95
|
+
DOI: 10.1145/3806023
|
|
97
96
|
```
|
|
98
97
|
|
|
99
98
|
**Also cite the original work:**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://pepy.tech/project/pyetsimul)
|
|
5
5
|
[](https://github.com/mh-salari/pyetsimul/blob/main/LICENSE)
|
|
6
6
|
[](https://pyetsimul.readthedocs.io/)
|
|
7
|
-
[](https://doi.org/10.1145/3806023)
|
|
8
8
|
|
|
9
9
|
PyEtSimul is an open-source Python framework for simulating video-based eye trackers
|
|
10
10
|
by generating synthetic eye features through geometric modeling. The framework allows
|
|
@@ -64,13 +64,12 @@ After installation, explore the various examples in the `examples/` directory to
|
|
|
64
64
|
|
|
65
65
|
## Citation
|
|
66
66
|
|
|
67
|
-
This work has been accepted for publication at ETRA 2026. The DOI will be added when the proceedings are published.
|
|
68
|
-
|
|
69
67
|
**Cite as:**
|
|
70
68
|
```
|
|
71
69
|
Salari, M., Niehorster, D. C., Hansen, D. W., & Bednarik, R. (2026).
|
|
72
70
|
PyEtSimul: An Open-Source Python Framework for Eye-Tracking Simulation.
|
|
73
|
-
|
|
71
|
+
Proceedings of the ACM on Human-Computer Interaction (PACMHCI), ETRA 2026. ACM.
|
|
72
|
+
DOI: 10.1145/3806023
|
|
74
73
|
```
|
|
75
74
|
|
|
76
75
|
**Also cite the original work:**
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"""Sphinx configuration for PyEtSimul documentation."""
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
|
+
from datetime import UTC, datetime
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
7
8
|
|
|
8
9
|
project = "PyEtSimul"
|
|
9
10
|
author = "Mohammadhossein Salari"
|
|
10
|
-
copyright = "2025,
|
|
11
|
+
copyright = f"2025-{datetime.now(tz=UTC).year}, {author}" # noqa: A001
|
|
11
12
|
|
|
12
13
|
extensions = [
|
|
13
14
|
"sphinx.ext.autodoc",
|
|
@@ -15,8 +15,8 @@ PyEtSimul Documentation
|
|
|
15
15
|
:target: https://github.com/mh-salari/pyetsimul/blob/main/LICENSE
|
|
16
16
|
:alt: License
|
|
17
17
|
|
|
18
|
-
.. |DOI| image:: https://img.shields.io/badge/DOI-
|
|
19
|
-
:target:
|
|
18
|
+
.. |DOI| image:: https://img.shields.io/badge/DOI-10.1145%2F3806023-blue
|
|
19
|
+
:target: https://doi.org/10.1145/3806023
|
|
20
20
|
:alt: DOI
|
|
21
21
|
|
|
22
22
|
**PyEtSimul** is an open-source Python framework for simulating video-based eye trackers
|
|
@@ -75,7 +75,7 @@ of gaze estimation algorithms across calibrated and uncalibrated settings.
|
|
|
75
75
|
by the same authors. If you use PyEtSimul in your research, please cite [2].
|
|
76
76
|
|
|
77
77
|
| [1] Bohme, M., Dorr, M., Graw, M., Martinetz, T., & Barth, E. (2008). A software framework for simulating eye trackers. In *Proceedings of the 2008 Symposium on Eye Tracking Research & Applications (ETRA '08)*, pp. 251-258. ACM. `DOI: 10.1145/1344471.1344529 <https://doi.org/10.1145/1344471.1344529>`_
|
|
78
|
-
| [2] Salari, M., Niehorster, D. C., Hansen, D. W., & Bednarik, R. (2026). PyEtSimul: An Open-Source Python Framework for Eye-Tracking Simulation.
|
|
78
|
+
| [2] Salari, M., Niehorster, D. C., Hansen, D. W., & Bednarik, R. (2026). PyEtSimul: An Open-Source Python Framework for Eye-Tracking Simulation. *Proceedings of the ACM on Human-Computer Interaction (PACMHCI)*, ETRA 2026. ACM. `DOI: 10.1145/3806023 <https://doi.org/10.1145/3806023>`_
|
|
79
79
|
|
|
80
80
|
.. note::
|
|
81
81
|
|
|
@@ -24,7 +24,7 @@ PyEtSimul gaze estimation models follow a simple pattern:
|
|
|
24
24
|
### Placeholder Code
|
|
25
25
|
|
|
26
26
|
```python
|
|
27
|
-
class MyAlgorithmState(AlgorithmState):
|
|
27
|
+
class MyAlgorithmState(AlgorithmState): # Inherit from pyetsimul.types.algorithms.AlgorithmState
|
|
28
28
|
"""
|
|
29
29
|
State for your custom gaze model.
|
|
30
30
|
TODO: Add fields to store your calibration parameters.
|
|
@@ -34,7 +34,7 @@ class MyAlgorithmState(AlgorithmState): # Inherit from pyetsimul.types.algorithm
|
|
|
34
34
|
"""Serialize the state to a dictionary for saving."""
|
|
35
35
|
# TODO: Add your custom parameters to the dictionary.
|
|
36
36
|
# Ensure they are JSON-serializable.
|
|
37
|
-
return {}
|
|
37
|
+
return {} # Return a dictionary of your state
|
|
38
38
|
|
|
39
39
|
@classmethod
|
|
40
40
|
def deserialize(cls, data: dict) -> "MyAlgorithmState":
|
|
@@ -58,7 +58,7 @@ class MyAlgorithmState(AlgorithmState): # Inherit from pyetsimul.types.algorithm
|
|
|
58
58
|
### Placeholder Code
|
|
59
59
|
|
|
60
60
|
```python
|
|
61
|
-
class MyGazeModel(EyeTracker):
|
|
61
|
+
class MyGazeModel(EyeTracker): # Inherit from pyetsimul.core.EyeTracker
|
|
62
62
|
"""
|
|
63
63
|
A template for a custom gaze estimation model.
|
|
64
64
|
TODO: Describe what your model does.
|
|
@@ -66,7 +66,7 @@ class MyGazeModel(EyeTracker): # Inherit from pyetsimul.core.EyeTracker
|
|
|
66
66
|
|
|
67
67
|
def __init__(self, **kwargs):
|
|
68
68
|
super().__init__(**kwargs)
|
|
69
|
-
self.algorithm_state = MyAlgorithmState()
|
|
69
|
+
self.algorithm_state = MyAlgorithmState() # Instance of your custom AlgorithmState
|
|
70
70
|
|
|
71
71
|
@property
|
|
72
72
|
def algorithm_name(self) -> str:
|
|
@@ -80,13 +80,13 @@ class MyGazeModel(EyeTracker): # Inherit from pyetsimul.core.EyeTracker
|
|
|
80
80
|
# TODO: Add any specific parameters your model needs for instantiation
|
|
81
81
|
) -> "MyGazeModel":
|
|
82
82
|
"""A factory method for clean instantiation."""
|
|
83
|
-
return cls()
|
|
83
|
+
return cls() # Return an instance of your model
|
|
84
84
|
|
|
85
85
|
# --- Block 3: Calibration Logic ---
|
|
86
86
|
# Purpose: Learn the mapping from eye features to world coordinates.
|
|
87
87
|
# This method modifies the internal state of the tracker, specifically `self.algorithm_state`.
|
|
88
88
|
# It does not return a value.
|
|
89
|
-
def calibrate(self, calibration_measurements: list[EyeMeasurement]) -> None:
|
|
89
|
+
def calibrate(self, calibration_measurements: list[EyeMeasurement]) -> None: # EyeMeasurement from pyetsimul.types
|
|
90
90
|
"""Calibrate the model by learning the mapping from eye features to gaze."""
|
|
91
91
|
# TODO: Implement your calibration logic here.
|
|
92
92
|
# This method should update `self.algorithm_state` with learned parameters.
|
|
@@ -96,7 +96,7 @@ class MyGazeModel(EyeTracker): # Inherit from pyetsimul.core.EyeTracker
|
|
|
96
96
|
# --- Block 4: Prediction Logic ---
|
|
97
97
|
# Purpose: Estimate gaze from a single eye measurement using the learned model.
|
|
98
98
|
# This method returns a `GazePrediction` object.
|
|
99
|
-
def predict_gaze(self, measurement: EyeMeasurement) -> GazePrediction:
|
|
99
|
+
def predict_gaze(self, measurement: EyeMeasurement) -> GazePrediction: # GazePrediction from pyetsimul.types
|
|
100
100
|
"""Predict gaze position from a single eye measurement."""
|
|
101
101
|
# TODO: Implement your gaze prediction logic here.
|
|
102
102
|
# Extract features from `measurement`.
|
|
@@ -105,7 +105,7 @@ class MyGazeModel(EyeTracker): # Inherit from pyetsimul.core.EyeTracker
|
|
|
105
105
|
|
|
106
106
|
# Return a GazePrediction object
|
|
107
107
|
return GazePrediction(
|
|
108
|
-
gaze_point=Point3D(0.0, 0.0, 0.0),
|
|
108
|
+
gaze_point=Point3D(0.0, 0.0, 0.0), # Point3D from pyetsimul.types
|
|
109
109
|
confidence=0.0,
|
|
110
110
|
algorithm_name=self.algorithm_name,
|
|
111
111
|
processing_time=0.0,
|
|
@@ -7,6 +7,7 @@ Create a custom parameter variation in 3 steps:
|
|
|
7
7
|
```python
|
|
8
8
|
from pyetsimul.simulation import GenericEyeVariation
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
class MyCustomVariation(GenericEyeVariation):
|
|
11
12
|
"""Custom variation that changes any eye parameter."""
|
|
12
13
|
|
|
@@ -31,6 +32,7 @@ class MyCustomVariation(GenericEyeVariation):
|
|
|
31
32
|
```python
|
|
32
33
|
from pyetsimul.core.cornea import ConicCornea
|
|
33
34
|
|
|
35
|
+
|
|
34
36
|
class ConicCorneaKVariation(GenericEyeVariation):
|
|
35
37
|
def __init__(self, k_range: list[float], num_steps: int = 10):
|
|
36
38
|
super().__init__("cornea.anterior_k", k_range, num_steps)
|
|
@@ -42,6 +44,7 @@ class ConicCorneaKVariation(GenericEyeVariation):
|
|
|
42
44
|
def apply_to_eye(self, eye, value: float) -> None:
|
|
43
45
|
eye.cornea.anterior_k = value
|
|
44
46
|
|
|
47
|
+
|
|
45
48
|
# Usage
|
|
46
49
|
variation = ConicCorneaKVariation([-0.05, -0.15], 5)
|
|
47
50
|
```
|
|
@@ -58,7 +61,7 @@ config = ExperimentConfig(
|
|
|
58
61
|
eyes=[Eye()], # Add your custom eye setup if needed
|
|
59
62
|
cameras=[Camera()],
|
|
60
63
|
lights=[Light()],
|
|
61
|
-
output_dir="outputs"
|
|
64
|
+
output_dir="outputs",
|
|
62
65
|
)
|
|
63
66
|
|
|
64
67
|
# Generate data
|
|
@@ -119,7 +119,7 @@ def validate_measurements(data: dict[str, Any]) -> bool:
|
|
|
119
119
|
return valid_measurements == total_measurements
|
|
120
120
|
|
|
121
121
|
|
|
122
|
-
def compare_values(actual: Any, expected: Any, field_name: str, tolerance: float = 1e-10) -> bool: # noqa: ANN401
|
|
122
|
+
def compare_values(actual: Any, expected: Any, field_name: str, tolerance: float = 1e-10) -> bool: # noqa: ANN401
|
|
123
123
|
"""Compare actual vs expected values with tolerance."""
|
|
124
124
|
if actual is None and expected is None:
|
|
125
125
|
return True
|
|
@@ -116,13 +116,16 @@ class EyeTracker(ABC):
|
|
|
116
116
|
failed_points = []
|
|
117
117
|
|
|
118
118
|
info(f"Collecting calibration data at {n_points} points...")
|
|
119
|
+
if len(self.cameras) > 1:
|
|
120
|
+
warning(
|
|
121
|
+
f"calibrate() uses only the first of {len(self.cameras)} configured cameras; "
|
|
122
|
+
"multi-camera calibration is not implemented."
|
|
123
|
+
)
|
|
119
124
|
|
|
120
125
|
for i, calib_point in enumerate(self.calib_points):
|
|
121
126
|
# Make eye look at calibration point
|
|
122
127
|
eye.look_at(calib_point, legacy=self.use_legacy_look_at)
|
|
123
128
|
|
|
124
|
-
# Take images from all cameras (currently uses first camera)
|
|
125
|
-
# TODO: Multi-camera support requires algorithm-specific implementation
|
|
126
129
|
if not self.cameras:
|
|
127
130
|
raise ValueError("No cameras available for calibration")
|
|
128
131
|
|
{pyetsimul-3.2.4 → pyetsimul-3.3.0}/pyetsimul/gaze_mapping/stampe1993/stampe1993_gaze_model.py
RENAMED
|
@@ -87,20 +87,69 @@ class Stampe1993GazeModel(PolynomialGazeModel):
|
|
|
87
87
|
)
|
|
88
88
|
|
|
89
89
|
def calibrate(self, calibration_measurements: list[EyeMeasurement]) -> None:
|
|
90
|
-
"""Two-stage calibration
|
|
91
|
-
|
|
90
|
+
"""Two-stage calibration matching Stampe (1993) / EyeLink 1000 Plus.
|
|
91
|
+
|
|
92
|
+
Fits the biquadratic polynomial on the **inner** cal points (centre + edges,
|
|
93
|
+
no corners), then a per-quadrant corner correction on the **outer** (corner)
|
|
94
|
+
cal points. This staged split reproduces the EyeLink 1000 Plus stored !CAL
|
|
95
|
+
polynomial coefficients.
|
|
96
|
+
"""
|
|
92
97
|
self.plane_info = detect_calibration_plane(self.calib_points)
|
|
93
98
|
info(summarize_plane_detection(self.calib_points, self.plane_info))
|
|
94
|
-
self._calibrate_same_xy(calibration_measurements)
|
|
95
99
|
|
|
96
|
-
#
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
# Identify inner (centre+edges) vs outer (corner) calibration points by their
|
|
101
|
+
# 2-D target positions: a "corner" target is off both cardinal axes relative
|
|
102
|
+
# to the calibration-grid centroid (large |Δx · Δy|). For HV9 this picks the
|
|
103
|
+
# 4 corners and leaves the 5 inner points (centre + 4 edges) for the polynomial fit.
|
|
104
|
+
calib_coords_2d = np.array([self.plane_info.extract_2d_coords(pt) for pt in self.calib_points])
|
|
105
|
+
centroid = calib_coords_2d.mean(axis=0)
|
|
106
|
+
off_axis = np.abs((calib_coords_2d[:, 0] - centroid[0]) * (calib_coords_2d[:, 1] - centroid[1]))
|
|
107
|
+
n = len(self.calib_points)
|
|
108
|
+
n_corners = min(4, n - 1)
|
|
109
|
+
if n_corners <= 0 or n <= n_corners:
|
|
110
|
+
inner_idx = list(range(n))
|
|
111
|
+
corner_idx: list[int] = []
|
|
112
|
+
else:
|
|
113
|
+
order = np.argsort(off_axis)
|
|
114
|
+
inner_idx = order[:-n_corners].tolist()
|
|
115
|
+
corner_idx = order[-n_corners:].tolist()
|
|
116
|
+
|
|
117
|
+
# Stage 1: polynomial fit on inner cal points only. Temporarily restrict
|
|
118
|
+
# self.calib_points + the measurement list so the parent fitter sees only the inner subset.
|
|
119
|
+
saved_calib_points = self.calib_points
|
|
120
|
+
try:
|
|
121
|
+
self.calib_points = [saved_calib_points[i] for i in inner_idx]
|
|
122
|
+
inner_measurements = [calibration_measurements[i] for i in inner_idx]
|
|
123
|
+
self._calibrate_same_xy(inner_measurements)
|
|
124
|
+
finally:
|
|
125
|
+
self.calib_points = saved_calib_points
|
|
126
|
+
|
|
127
|
+
# Stage 2: corner correction on the 4 outer cal points (1 per quadrant for HV9).
|
|
128
|
+
if corner_idx:
|
|
129
|
+
self._fit_corner_correction(
|
|
130
|
+
[calibration_measurements[i] for i in corner_idx],
|
|
131
|
+
indices=corner_idx,
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
# No corners (e.g. HV5 monocular geometry): zero out corner correction.
|
|
135
|
+
self.corner_coefficients = np.zeros((4, 2))
|
|
136
|
+
coords_array = np.array([self.plane_info.extract_2d_coords(pt) for pt in self.calib_points])
|
|
137
|
+
self.screen_center_2d = (float(coords_array[:, 0].mean()), float(coords_array[:, 1].mean()))
|
|
138
|
+
|
|
139
|
+
def _fit_corner_correction(
|
|
140
|
+
self,
|
|
141
|
+
calibration_measurements: list[EyeMeasurement],
|
|
142
|
+
indices: list[int] | None = None,
|
|
143
|
+
) -> None:
|
|
100
144
|
"""Fit per-quadrant corner correction coefficients from polynomial residuals.
|
|
101
145
|
|
|
102
146
|
The screen is divided into 4 quadrants relative to the calibration grid center.
|
|
103
147
|
Corner correction: X_final = X_poly + cx[q] * (X_poly - Xc) * (Y_poly - Yc)
|
|
148
|
+
|
|
149
|
+
When ``indices`` is provided, position ``i`` in ``calibration_measurements`` is
|
|
150
|
+
paired with target coordinates from ``self.calib_points[indices[i]]``. This lets
|
|
151
|
+
the caller pass only a subset (the outer cal points) while the grid centroid is
|
|
152
|
+
still computed from the full ``self.calib_points`` set.
|
|
104
153
|
"""
|
|
105
154
|
self.corner_coefficients = np.zeros((4, 2)) # [quadrant][cx, cy]
|
|
106
155
|
|
|
@@ -112,6 +161,9 @@ class Stampe1993GazeModel(PolynomialGazeModel):
|
|
|
112
161
|
cx_center, cy_center = self.screen_center_2d
|
|
113
162
|
info(f"\nCalibration grid center (2D): ({cx_center:.1f}, {cy_center:.1f}) mm")
|
|
114
163
|
|
|
164
|
+
if indices is None:
|
|
165
|
+
indices = list(range(len(calibration_measurements)))
|
|
166
|
+
|
|
115
167
|
# Group points by quadrant relative to calibration center
|
|
116
168
|
quadrant_data: dict[int, list[tuple[float, float, float, float]]] = {0: [], 1: [], 2: [], 3: []}
|
|
117
169
|
|
|
@@ -134,7 +186,7 @@ class Stampe1993GazeModel(PolynomialGazeModel):
|
|
|
134
186
|
gaze_2d = coefficient_matrix @ poly_features.features
|
|
135
187
|
poly_x, poly_y = gaze_2d[0], gaze_2d[1]
|
|
136
188
|
|
|
137
|
-
target_x, target_y = calib_coords_2d[i]
|
|
189
|
+
target_x, target_y = calib_coords_2d[indices[i]]
|
|
138
190
|
|
|
139
191
|
# Quadrant relative to calibration center
|
|
140
192
|
dx = poly_x - cx_center
|
|
@@ -94,5 +94,4 @@ class SequentialVariation(ParameterVariation):
|
|
|
94
94
|
"""Generate sequential values from all variations."""
|
|
95
95
|
for i, variation in enumerate(self.variations):
|
|
96
96
|
for value in variation.generate_values():
|
|
97
|
-
|
|
98
|
-
yield combination
|
|
97
|
+
yield {"variation_index": i, "variation_name": variation.param_name, "value": value}
|
|
@@ -4,6 +4,7 @@ Provides type-safe dataclasses that improve code readability and maintain
|
|
|
4
4
|
semantic meaning while supporting arithmetic operations and numpy integration.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import math
|
|
7
8
|
from dataclasses import dataclass
|
|
8
9
|
from typing import Self, Union, overload
|
|
9
10
|
|
|
@@ -49,7 +50,7 @@ class Point2D:
|
|
|
49
50
|
error_msg = f"{msg}: {error_msg}"
|
|
50
51
|
raise AssertionError(error_msg)
|
|
51
52
|
|
|
52
|
-
def __sub__(self, other: "Point2D | float
|
|
53
|
+
def __sub__(self, other: "Point2D | float") -> "Point2D":
|
|
53
54
|
"""Subtract point or scalar from point."""
|
|
54
55
|
if isinstance(other, Point2D):
|
|
55
56
|
return Point2D(self.x - other.x, self.y - other.y)
|
|
@@ -57,7 +58,7 @@ class Point2D:
|
|
|
57
58
|
return Point2D(self.x - other, self.y - other)
|
|
58
59
|
return NotImplemented
|
|
59
60
|
|
|
60
|
-
def __add__(self, other: "Point2D | float
|
|
61
|
+
def __add__(self, other: "Point2D | float") -> "Point2D":
|
|
61
62
|
"""Add two points/vectors or add scalar to point."""
|
|
62
63
|
if isinstance(other, Point2D):
|
|
63
64
|
return Point2D(self.x + other.x, self.y + other.y)
|
|
@@ -161,7 +162,7 @@ class Point3D:
|
|
|
161
162
|
return Point3D(self.x - other, self.y - other, self.z - other)
|
|
162
163
|
return NotImplemented
|
|
163
164
|
|
|
164
|
-
def __add__(self, other: "Vector3D | float
|
|
165
|
+
def __add__(self, other: "Vector3D | float") -> "Point3D":
|
|
165
166
|
"""Add a vector or scalar to a point to get a new point."""
|
|
166
167
|
if isinstance(other, Vector3D):
|
|
167
168
|
return Point3D(self.x + other.x, self.y + other.y, self.z + other.z)
|
|
@@ -278,7 +279,7 @@ class Vector3D:
|
|
|
278
279
|
return Vector3D.from_array(result)
|
|
279
280
|
return NotImplemented
|
|
280
281
|
|
|
281
|
-
def __add__(self, other: "Vector3D | float
|
|
282
|
+
def __add__(self, other: "Vector3D | float") -> "Vector3D":
|
|
282
283
|
"""Add two vectors or add scalar to vector."""
|
|
283
284
|
if isinstance(other, Vector3D):
|
|
284
285
|
return Vector3D(self.x + other.x, self.y + other.y, self.z + other.z)
|
|
@@ -286,7 +287,7 @@ class Vector3D:
|
|
|
286
287
|
return Vector3D(self.x + other, self.y + other, self.z + other)
|
|
287
288
|
return NotImplemented
|
|
288
289
|
|
|
289
|
-
def __sub__(self, other: "Vector3D | float
|
|
290
|
+
def __sub__(self, other: "Vector3D | float") -> "Vector3D":
|
|
290
291
|
"""Subtract vector or scalar from vector."""
|
|
291
292
|
if isinstance(other, Vector3D):
|
|
292
293
|
return Vector3D(self.x - other.x, self.y - other.y, self.z - other.z)
|
|
@@ -364,9 +365,10 @@ class Position3D:
|
|
|
364
365
|
def from_array(cls, arr: np.ndarray) -> "Position3D":
|
|
365
366
|
"""Create from 4D homogeneous array [x,y,z,1] or 3D array [x,y,z]."""
|
|
366
367
|
if arr.shape == (4,):
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
368
|
+
w = float(arr[3])
|
|
369
|
+
# w == 1 (point) or w == 0 (direction) → take xyz directly; otherwise de-homogenize.
|
|
370
|
+
if not math.isclose(w, 1.0) and not math.isclose(w, 0.0):
|
|
371
|
+
return cls(arr[0] / w, arr[1] / w, arr[2] / w)
|
|
370
372
|
return cls(arr[0], arr[1], arr[2])
|
|
371
373
|
if arr.shape == (3,):
|
|
372
374
|
return cls(arr[0], arr[1], arr[2])
|
|
@@ -422,7 +424,7 @@ class Position3D:
|
|
|
422
424
|
return Position3D(self.x - other, self.y - other, self.z - other)
|
|
423
425
|
return NotImplemented
|
|
424
426
|
|
|
425
|
-
def __add__(self, other: "Vector3D | Direction3D | Point3D | float
|
|
427
|
+
def __add__(self, other: "Vector3D | Direction3D | Point3D | float") -> "Position3D":
|
|
426
428
|
"""Add a vector, direction, point, or scalar to a position to get a new position."""
|
|
427
429
|
if isinstance(other, (Vector3D, Direction3D, Point3D)):
|
|
428
430
|
return Position3D(self.x + other.x, self.y + other.y, self.z + other.z)
|
|
@@ -438,7 +440,7 @@ class Position3D:
|
|
|
438
440
|
"""Multiply position by a scalar (reverse order)."""
|
|
439
441
|
return self.__mul__(scalar)
|
|
440
442
|
|
|
441
|
-
def __radd__(self, other: "Vector3D | Direction3D | Point3D | float
|
|
443
|
+
def __radd__(self, other: "Vector3D | Direction3D | Point3D | float") -> "Position3D":
|
|
442
444
|
"""Add position to a scalar (reverse order)."""
|
|
443
445
|
return self.__add__(other)
|
|
444
446
|
|
|
@@ -601,7 +603,7 @@ class Direction3D:
|
|
|
601
603
|
# For other operations, defer to numpy
|
|
602
604
|
return NotImplemented
|
|
603
605
|
|
|
604
|
-
def __add__(self, other: "Direction3D | float
|
|
606
|
+
def __add__(self, other: "Direction3D | float") -> "Direction3D":
|
|
605
607
|
"""Add two directions or add scalar to direction."""
|
|
606
608
|
if isinstance(other, Direction3D):
|
|
607
609
|
return Direction3D(self.x + other.x, self.y + other.y, self.z + other.z)
|
|
@@ -609,7 +611,7 @@ class Direction3D:
|
|
|
609
611
|
return Direction3D(self.x + other, self.y + other, self.z + other)
|
|
610
612
|
return NotImplemented
|
|
611
613
|
|
|
612
|
-
def __sub__(self, other: "Direction3D | float
|
|
614
|
+
def __sub__(self, other: "Direction3D | float") -> "Direction3D":
|
|
613
615
|
"""Subtract direction or scalar from direction."""
|
|
614
616
|
if isinstance(other, Direction3D):
|
|
615
617
|
return Direction3D(self.x - other.x, self.y - other.y, self.z - other.z)
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
name = "pyetsimul"
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "PyEtSimul: An Open-Source Python Software Framework for Eye Tracking Simulation"
|
|
9
|
-
authors = [{name = "Mohammadhossein Salari", email = "
|
|
9
|
+
authors = [{name = "Mohammadhossein Salari", email = "mohammadhossein.salari@gmail.com"}]
|
|
10
10
|
license = {text = "GPL-3.0-or-later"}
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
keywords = ["eye-tracking", "simulation", "eye-tracker", "gaze-estimation"]
|
|
@@ -38,6 +38,7 @@ tag-regex = "^(?P<version>\\d+\\.\\d+\\.\\d+)$"
|
|
|
38
38
|
|
|
39
39
|
[dependency-groups]
|
|
40
40
|
dev = [
|
|
41
|
+
"ruff>=0.13.0",
|
|
41
42
|
"jupyterlab>=4.4.7",
|
|
42
43
|
"pytest>=8.4.1",
|
|
43
44
|
"scipy-stubs>=1.16.2.0",
|
|
@@ -146,27 +147,28 @@ ignore = [
|
|
|
146
147
|
"TID252", # Flake8-tidy-imports - Prefer absolute imports over relative imports from parent modules
|
|
147
148
|
"FBT001", # Flake8-boolean-trap - Boolean-typed positional argument in function definition
|
|
148
149
|
"FBT002", # Flake8-boolean-trap - Boolean default positional argument in function definition
|
|
150
|
+
"PLR0911", # Pylint - Too many return statements
|
|
151
|
+
"C901", # McCabe - Function is too complex
|
|
152
|
+
"PLR0912", # Pylint - Too many branches
|
|
149
153
|
"PLR0913", # Pylint - Too many arguments
|
|
150
154
|
"PLR0914", # Pylint - Too many local variables
|
|
151
|
-
"PLR0904", # Pylint - Too many public methods
|
|
152
155
|
"PLR0915", # Pylint - Too many statements
|
|
153
156
|
"PLR0917", # Pylint - Too many positional arguments
|
|
154
157
|
"TRY003", # Tryceratops - Avoid specifying long messages outside the exception class
|
|
155
158
|
"EM101", # Flake8-errmsg - Exception must not use a string literal, assign to variable first
|
|
156
159
|
"EM102", # Flake8-errmsg - Exception must not use an f-string literal, assign to variable first
|
|
157
160
|
"D401", # Pydocstyle - First line of docstring should be in imperative mood
|
|
158
|
-
"
|
|
159
|
-
"PT011", # Flake8-pytest-style - pytest.raises() is too broad
|
|
160
|
-
"TD002", # Flake8-todos - Missing author in TODO
|
|
161
|
-
"TD003", # Flake8-todos - Missing issue link in TODO
|
|
162
|
-
"FIX002", # Flake8-fixme - Line contains TODO
|
|
163
|
-
"N803", # PEP8-naming - Argument name should be lowercase
|
|
164
|
-
"C901", # McCabe - Function is too complex
|
|
165
|
-
"PLR0912", # Pylint - Too many branches
|
|
166
|
-
"INP001", # Flake8-no-pep420 - Implicit namespace package (requires __init__.py)
|
|
161
|
+
"N803", # PEP8-naming - Argument name should be lowercase (math vars like X, Y, N are conventional in this codebase)
|
|
167
162
|
]
|
|
168
163
|
|
|
164
|
+
[tool.ruff.lint.pylint]
|
|
165
|
+
# NumPy array-protocol hooks: NumPy looks these up by exact name when a custom
|
|
166
|
+
# class is passed to np.asarray() / ufuncs. Not typos; not generic Python dunders.
|
|
167
|
+
allow-dunder-method-names = ["__array__", "__array_ufunc__"]
|
|
168
|
+
|
|
169
169
|
[tool.ruff.lint.per-file-ignores]
|
|
170
170
|
"unit_tests/**/*.py" = ["S101"]
|
|
171
171
|
"examples/run_all_examples.py" = ["S404", "S603"]
|
|
172
172
|
"pyetsimul/log.py" = ["ANN401"]
|
|
173
|
+
"docs/conf.py" = ["INP001"] # Sphinx config file, not a package member
|
|
174
|
+
"pyetsimul/core/eye.py" = ["PLR0904"] # Eye is a domain object with many natural operations (orientation, pupil, cornea, eyelid, fovea, refraction, serialization)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Unit tests for angle2gaze function."""
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
+
import pytest
|
|
4
5
|
|
|
5
6
|
from pyetsimul.geometry.conversions import angle2gaze
|
|
6
7
|
from pyetsimul.types import Direction3D, Point2D, RotationMatrix
|
|
@@ -51,4 +52,4 @@ def test_output_properties() -> None:
|
|
|
51
52
|
# The to_array() method should return a 4D homogeneous vector with the last component as 0
|
|
52
53
|
result_array = np.array(result)
|
|
53
54
|
assert result_array.shape == (4,)
|
|
54
|
-
assert result_array[3] == 0.0
|
|
55
|
+
assert result_array[3] == pytest.approx(0.0, abs=1e-9)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Unit tests for Eye.find_cr method."""
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
+
import pytest
|
|
4
5
|
|
|
5
6
|
from pyetsimul.core.camera import Camera
|
|
6
7
|
from pyetsimul.core.eye import Eye
|
|
@@ -104,4 +105,4 @@ def test_output_properties() -> None:
|
|
|
104
105
|
# Check types and shapes
|
|
105
106
|
arr = np.array(cr)
|
|
106
107
|
assert arr.shape == (4,)
|
|
107
|
-
assert arr[3] == 1.0 # Homogeneous coordinate
|
|
108
|
+
assert arr[3] == pytest.approx(1.0) # Homogeneous coordinate
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Unit tests for Eye.find_cr_simple method."""
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
+
import pytest
|
|
4
5
|
|
|
5
6
|
from pyetsimul.core.camera import Camera
|
|
6
7
|
from pyetsimul.core.eye import Eye
|
|
@@ -29,7 +30,7 @@ def test_basic_simple_corneal_reflex() -> None:
|
|
|
29
30
|
# Test 4D homogeneous coordinates
|
|
30
31
|
arr = np.array(cr)
|
|
31
32
|
assert arr.shape == (4,)
|
|
32
|
-
assert arr[3] == 1.0
|
|
33
|
+
assert arr[3] == pytest.approx(1.0)
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
def test_angled_positions() -> None:
|
|
@@ -52,7 +53,7 @@ def test_angled_positions() -> None:
|
|
|
52
53
|
# Test 4D homogeneous coordinates
|
|
53
54
|
arr = np.array(cr)
|
|
54
55
|
assert arr.shape == (4,)
|
|
55
|
-
assert arr[3] == 1.0
|
|
56
|
+
assert arr[3] == pytest.approx(1.0)
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
def test_reflex_outside_cornea_boundary() -> None:
|
|
@@ -85,4 +86,4 @@ def test_output_properties() -> None:
|
|
|
85
86
|
# Check types and shapes
|
|
86
87
|
arr = np.array(cr)
|
|
87
88
|
assert arr.shape == (4,)
|
|
88
|
-
assert arr[3] == 1.0 # Homogeneous coordinate
|
|
89
|
+
assert arr[3] == pytest.approx(1.0) # Homogeneous coordinate
|
|
@@ -183,7 +183,7 @@ class TestVector3D:
|
|
|
183
183
|
|
|
184
184
|
# Magnitude
|
|
185
185
|
mag = v.magnitude()
|
|
186
|
-
assert mag == 5.0
|
|
186
|
+
assert mag == pytest.approx(5.0)
|
|
187
187
|
|
|
188
188
|
# Normalize
|
|
189
189
|
normalized = v.normalize()
|
|
@@ -253,7 +253,7 @@ class TestPosition3D:
|
|
|
253
253
|
pos2 = Position3D(3, 4, 0)
|
|
254
254
|
|
|
255
255
|
distance = pos1.distance_to(pos2)
|
|
256
|
-
assert distance == 5.0
|
|
256
|
+
assert distance == pytest.approx(5.0)
|
|
257
257
|
|
|
258
258
|
|
|
259
259
|
class TestDirection3D:
|
|
@@ -55,19 +55,19 @@ def test_constant_offset_noise() -> None:
|
|
|
55
55
|
|
|
56
56
|
def test_config_validation() -> None:
|
|
57
57
|
"""Test that GlintNoiseConfig raises errors for invalid configurations."""
|
|
58
|
-
with pytest.raises(ValueError):
|
|
58
|
+
with pytest.raises(ValueError, match=r"requires 'std' to be specified"):
|
|
59
59
|
GlintNoiseConfig(noise_type="gaussian") # Missing std
|
|
60
60
|
|
|
61
|
-
with pytest.raises(ValueError):
|
|
61
|
+
with pytest.raises(ValueError, match=r"requires 'std' to be specified"):
|
|
62
62
|
GlintNoiseConfig(noise_type="uniform") # Missing std
|
|
63
63
|
|
|
64
|
-
with pytest.raises(ValueError):
|
|
64
|
+
with pytest.raises(ValueError, match=r"requires 'offset_x' and 'offset_y'"):
|
|
65
65
|
GlintNoiseConfig(noise_type="constant_offset", offset_x=1.0) # Missing offset_y
|
|
66
66
|
|
|
67
|
-
with pytest.raises(ValueError):
|
|
67
|
+
with pytest.raises(ValueError, match=r"requires 'offset_x' and 'offset_y'"):
|
|
68
68
|
GlintNoiseConfig(noise_type="constant_offset", offset_y=1.0) # Missing offset_x
|
|
69
69
|
|
|
70
|
-
with pytest.raises(ValueError):
|
|
70
|
+
with pytest.raises(ValueError, match=r"Unknown noise type"):
|
|
71
71
|
GlintNoiseConfig(noise_type="unknown_noise") # Unknown noise type
|
|
72
72
|
|
|
73
73
|
|
|
@@ -88,21 +88,21 @@ def test_advanced_noise() -> None:
|
|
|
88
88
|
def test_advanced_mode_validation() -> None:
|
|
89
89
|
"""Test validation for advanced mode parameters."""
|
|
90
90
|
# Missing covariance
|
|
91
|
-
with pytest.raises(ValueError):
|
|
91
|
+
with pytest.raises(ValueError, match=r"requires both 'mean' and 'covariance'"):
|
|
92
92
|
GlintNoiseConfig(mean=[0.0, 0.0])
|
|
93
93
|
|
|
94
94
|
# Missing mean
|
|
95
|
-
with pytest.raises(ValueError):
|
|
95
|
+
with pytest.raises(ValueError, match=r"requires both 'mean' and 'covariance'"):
|
|
96
96
|
GlintNoiseConfig(covariance=[[1.0, 0.0], [0.0, 1.0]])
|
|
97
97
|
|
|
98
98
|
# Wrong mean size
|
|
99
|
-
with pytest.raises(ValueError):
|
|
99
|
+
with pytest.raises(ValueError, match=r"'mean' must be a 2-element list"):
|
|
100
100
|
GlintNoiseConfig(mean=[0.0], covariance=[[1.0, 0.0], [0.0, 1.0]])
|
|
101
101
|
|
|
102
102
|
# Wrong covariance size
|
|
103
|
-
with pytest.raises(ValueError):
|
|
103
|
+
with pytest.raises(ValueError, match=r"'covariance' must be a 2x2 matrix"):
|
|
104
104
|
GlintNoiseConfig(mean=[0.0, 0.0], covariance=[[1.0]])
|
|
105
105
|
|
|
106
106
|
# Negative definite covariance
|
|
107
|
-
with pytest.raises(ValueError):
|
|
107
|
+
with pytest.raises(ValueError, match=r"positive semidefinite"):
|
|
108
108
|
GlintNoiseConfig(mean=[0.0, 0.0], covariance=[[-1.0, 0.0], [0.0, 1.0]])
|