dkist-processing-visp 5.7.22__tar.gz → 5.7.23__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. dkist_processing_visp-5.7.23/.snyk +7 -0
  2. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/CHANGELOG.rst +13 -0
  3. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/PKG-INFO +56 -56
  4. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/models/parameters.py +7 -0
  5. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/parsers/spectrograph_configuration.py +17 -23
  6. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/parse.py +3 -1
  7. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/conftest.py +1 -0
  8. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_parse.py +123 -31
  9. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp.egg-info/PKG-INFO +56 -56
  10. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp.egg-info/requires.txt +55 -55
  11. dkist_processing_visp-5.7.23/dkist_processing_visp.egg-info/scm_version.json +8 -0
  12. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/pyproject.toml +57 -57
  13. dkist_processing_visp-5.7.22/.snyk +0 -2
  14. dkist_processing_visp-5.7.22/dkist_processing_visp.egg-info/scm_version.json +0 -8
  15. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/.gitignore +0 -0
  16. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/.pre-commit-config.yaml +0 -0
  17. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/.readthedocs.yml +0 -0
  18. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/.rovodev/.review-agent.md +0 -0
  19. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/README.rst +0 -0
  20. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/SCIENCE_CHANGELOG.rst +0 -0
  21. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/bitbucket-pipelines.yml +0 -0
  22. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/changelog/.gitempty +0 -0
  23. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/__init__.py +0 -0
  24. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/config.py +0 -0
  25. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/fonts/Lato-Regular.ttf +0 -0
  26. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/models/__init__.py +0 -0
  27. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/models/constants.py +0 -0
  28. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/models/dataset_extras.py +0 -0
  29. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/models/fits_access.py +0 -0
  30. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/models/metric_code.py +0 -0
  31. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/models/tags.py +0 -0
  32. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/models/task_name.py +0 -0
  33. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/parsers/__init__.py +0 -0
  34. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/parsers/map_repeats.py +0 -0
  35. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/parsers/modulator_states.py +0 -0
  36. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/parsers/polarimeter_mode.py +0 -0
  37. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/parsers/raster_step.py +0 -0
  38. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/parsers/time.py +0 -0
  39. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/parsers/visp_l0_fits_access.py +0 -0
  40. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/parsers/visp_l1_fits_access.py +0 -0
  41. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/__init__.py +0 -0
  42. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/assemble_movie.py +0 -0
  43. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/background_light.py +0 -0
  44. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/dark.py +0 -0
  45. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/geometric.py +0 -0
  46. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/instrument_polarization.py +0 -0
  47. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/l1_output_data.py +0 -0
  48. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/lamp.py +0 -0
  49. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/make_movie_frames.py +0 -0
  50. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/mixin/__init__.py +0 -0
  51. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/mixin/beam_access.py +0 -0
  52. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/mixin/corrections.py +0 -0
  53. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/mixin/downsample.py +0 -0
  54. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/polcal_as_science.py +0 -0
  55. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/quality_metrics.py +0 -0
  56. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/science.py +0 -0
  57. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/solar.py +0 -0
  58. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/visp_base.py +0 -0
  59. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/wavelength_calibration.py +0 -0
  60. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/write_dataset_extras.py +0 -0
  61. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tasks/write_l1.py +0 -0
  62. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/README.rst +0 -0
  63. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/__init__.py +0 -0
  64. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/header_models.py +0 -0
  65. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/local_trial_workflows/__init__.py +0 -0
  66. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/local_trial_workflows/l0_cals_only.py +0 -0
  67. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/local_trial_workflows/l0_polcals_as_science.py +0 -0
  68. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/local_trial_workflows/l0_solar_gain_as_science.py +0 -0
  69. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/local_trial_workflows/l0_to_l1.py +0 -0
  70. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/local_trial_workflows/local_trial_helpers.py +0 -0
  71. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_assemble_movie.py +0 -0
  72. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_assemble_quality.py +0 -0
  73. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_background_light.py +0 -0
  74. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_dark.py +0 -0
  75. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_downsample.py +0 -0
  76. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_fits_access.py +0 -0
  77. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_geometric.py +0 -0
  78. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_instrument_polarization.py +0 -0
  79. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_lamp.py +0 -0
  80. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_make_movie_frames.py +0 -0
  81. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_map_repeats.py +0 -0
  82. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_parameters.py +0 -0
  83. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_polcal_as_science.py +0 -0
  84. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_quality.py +0 -0
  85. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_science.py +0 -0
  86. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_solar.py +0 -0
  87. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_trial_create_quality_report.py +0 -0
  88. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_visp_base.py +0 -0
  89. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_visp_constants.py +0 -0
  90. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_wavelength_calibration.py +0 -0
  91. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_workflows.py +0 -0
  92. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_write_dataset_extras.py +0 -0
  93. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/tests/test_write_l1.py +0 -0
  94. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/workflows/__init__.py +0 -0
  95. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/workflows/l0_processing.py +0 -0
  96. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/workflows/single_task_workflows.py +0 -0
  97. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp/workflows/trial_workflows.py +0 -0
  98. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp.egg-info/SOURCES.txt +0 -0
  99. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp.egg-info/dependency_links.txt +0 -0
  100. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp.egg-info/scm_file_list.json +85 -85
  101. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/dkist_processing_visp.egg-info/top_level.txt +0 -0
  102. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/Makefile +0 -0
  103. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/background_light.rst +0 -0
  104. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/changelog.rst +0 -0
  105. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/conf.py +0 -0
  106. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/dataset_extras.rst +0 -0
  107. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/gain_correction.rst +0 -0
  108. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/geometric.rst +0 -0
  109. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/index.rst +0 -0
  110. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/l0_to_l1_visp.rst +0 -0
  111. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/l0_to_l1_visp_full-trial.rst +0 -0
  112. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/landing_page.rst +0 -0
  113. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/make.bat +0 -0
  114. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/polarization_calibration.rst +0 -0
  115. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/requirements.txt +0 -0
  116. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/requirements_table.rst +0 -0
  117. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/science_calibration.rst +0 -0
  118. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/scientific_changelog.rst +0 -0
  119. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/docs/wavelength_calibration.rst +0 -0
  120. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/licenses/LICENSE.rst +0 -0
  121. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/science_towncrier.sh +0 -0
  122. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/setup.cfg +0 -0
  123. {dkist_processing_visp-5.7.22 → dkist_processing_visp-5.7.23}/towncrier_science.toml +0 -0
@@ -0,0 +1,7 @@
1
+ # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities
2
+ # https://docs.snyk.io/manage-risk/policies/the-.snyk-file#syntax-of-the-.snyk-file
3
+ ignore:
4
+ SNYK-PYTHON-APACHEAIRFLOWCORE-17879182:
5
+ - "*" :
6
+ reason: 'Patched in Airflow 3.3.0, but we are not ready to upgrade till 3.3.1 or later'
7
+ expires: '2027-07-07T00:00:00.000Z'
@@ -1,3 +1,16 @@
1
+ v5.7.23 (2026-07-07)
2
+ ====================
3
+
4
+ Bugfixes
5
+ --------
6
+
7
+ - Added tolerance to ``ReflectedLightAngleBud`` to handle small arm position differences between frames.
8
+ Previously, any variation in ``VSPARMPS`` across frames would cause pipeline parsing to fail with a ``ValueError``.
9
+ Now, ``ReflectedLightAngleBud`` subclasses ``TaskNearFloatBud`` and accepts a configurable tolerance via ``visp_reflected_light_angle_tolerance_deg``.
10
+ If the spread between the minimum and maximum ``REFLECTED_LIGHT_ANGLE_DEG`` values is within tolerance, the mean value is used.
11
+ If the spread exceeds the tolerance, a ``ValueError`` is still raised to flag genuine hardware problems. (`#327 <https://bitbucket.org/dkistdc/dkist-processing-visp/pull-requests/327>`__)
12
+
13
+
1
14
  v5.7.22 (2026-06-22)
2
15
  ====================
3
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dkist-processing-visp
3
- Version: 5.7.22
3
+ Version: 5.7.23
4
4
  Summary: Science processing code for the ViSP instrument on DKIST
5
5
  Author-email: NSO / AURA <dkistdc@nso.edu>
6
6
  License: BSD-3-Clause
@@ -79,28 +79,28 @@ Requires-Dist: SQLAlchemy==2.0.51; extra == "frozen"
79
79
  Requires-Dist: Send2Trash==2.1.0; extra == "frozen"
80
80
  Requires-Dist: a2wsgi==1.10.10; extra == "frozen"
81
81
  Requires-Dist: aioftp==0.27.2; extra == "frozen"
82
- Requires-Dist: aiohappyeyeballs==2.6.2; extra == "frozen"
82
+ Requires-Dist: aiohappyeyeballs==2.7.1; extra == "frozen"
83
83
  Requires-Dist: aiohttp==3.14.1; extra == "frozen"
84
84
  Requires-Dist: aiosignal==1.4.0; extra == "frozen"
85
85
  Requires-Dist: aiosmtplib==5.1.2; extra == "frozen"
86
86
  Requires-Dist: aiosqlite==0.21.0; extra == "frozen"
87
- Requires-Dist: alembic==1.18.4; extra == "frozen"
87
+ Requires-Dist: alembic==1.18.5; extra == "frozen"
88
88
  Requires-Dist: amqp==5.3.1; extra == "frozen"
89
89
  Requires-Dist: annotated-doc==0.0.4; extra == "frozen"
90
90
  Requires-Dist: annotated-types==0.7.0; extra == "frozen"
91
- Requires-Dist: anyio==4.14.0; extra == "frozen"
91
+ Requires-Dist: anyio==4.14.1; extra == "frozen"
92
92
  Requires-Dist: apache-airflow==3.2.1; extra == "frozen"
93
93
  Requires-Dist: apache-airflow-core==3.2.1; extra == "frozen"
94
- Requires-Dist: apache-airflow-providers-celery==3.20.0; extra == "frozen"
94
+ Requires-Dist: apache-airflow-providers-celery==3.21.0; extra == "frozen"
95
95
  Requires-Dist: apache-airflow-providers-common-compat==1.15.0; extra == "frozen"
96
- Requires-Dist: apache-airflow-providers-common-io==1.7.3; extra == "frozen"
96
+ Requires-Dist: apache-airflow-providers-common-io==1.8.0; extra == "frozen"
97
97
  Requires-Dist: apache-airflow-providers-common-sql==2.0.1; extra == "frozen"
98
- Requires-Dist: apache-airflow-providers-postgres==6.7.1; extra == "frozen"
98
+ Requires-Dist: apache-airflow-providers-postgres==6.8.0; extra == "frozen"
99
99
  Requires-Dist: apache-airflow-providers-smtp==3.0.1; extra == "frozen"
100
- Requires-Dist: apache-airflow-providers-standard==1.14.0; extra == "frozen"
100
+ Requires-Dist: apache-airflow-providers-standard==1.15.0; extra == "frozen"
101
101
  Requires-Dist: apache-airflow-task-sdk==1.2.1; extra == "frozen"
102
102
  Requires-Dist: appnope==0.1.4; extra == "frozen"
103
- Requires-Dist: argcomplete==3.6.3; extra == "frozen"
103
+ Requires-Dist: argcomplete==3.7.0; extra == "frozen"
104
104
  Requires-Dist: argon2-cffi==25.1.0; extra == "frozen"
105
105
  Requires-Dist: argon2-cffi-bindings==25.1.0; extra == "frozen"
106
106
  Requires-Dist: arrow==1.4.0; extra == "frozen"
@@ -109,7 +109,7 @@ Requires-Dist: asdf_standard==1.5.0; extra == "frozen"
109
109
  Requires-Dist: asgiref==3.11.1; extra == "frozen"
110
110
  Requires-Dist: asteval==1.0.9; extra == "frozen"
111
111
  Requires-Dist: astropy==7.0.2; extra == "frozen"
112
- Requires-Dist: astropy-iers-data==0.2026.6.22.1.23.34; extra == "frozen"
112
+ Requires-Dist: astropy-iers-data==0.2026.7.6.1.1.20; extra == "frozen"
113
113
  Requires-Dist: asttokens==3.0.1; extra == "frozen"
114
114
  Requires-Dist: async-lru==2.3.0; extra == "frozen"
115
115
  Requires-Dist: asyncpg==0.31.0; extra == "frozen"
@@ -118,14 +118,14 @@ Requires-Dist: babel==2.18.0; extra == "frozen"
118
118
  Requires-Dist: beautifulsoup4==4.15.0; extra == "frozen"
119
119
  Requires-Dist: billiard==4.2.4; extra == "frozen"
120
120
  Requires-Dist: bleach==6.4.0; extra == "frozen"
121
- Requires-Dist: boto3==1.43.34; extra == "frozen"
122
- Requires-Dist: botocore==1.43.34; extra == "frozen"
121
+ Requires-Dist: boto3==1.43.41; extra == "frozen"
122
+ Requires-Dist: botocore==1.43.41; extra == "frozen"
123
123
  Requires-Dist: cadwyn==7.1.0; extra == "frozen"
124
124
  Requires-Dist: celery==5.6.3; extra == "frozen"
125
125
  Requires-Dist: certifi==2026.6.17; extra == "frozen"
126
- Requires-Dist: cffi==2.0.0; extra == "frozen"
127
- Requires-Dist: charset-normalizer==3.4.7; extra == "frozen"
128
- Requires-Dist: click==8.4.1; extra == "frozen"
126
+ Requires-Dist: cffi==2.1.0; extra == "frozen"
127
+ Requires-Dist: charset-normalizer==3.4.9; extra == "frozen"
128
+ Requires-Dist: click==8.4.2; extra == "frozen"
129
129
  Requires-Dist: click-didyoumean==0.3.1; extra == "frozen"
130
130
  Requires-Dist: click-plugins==1.1.1.2; extra == "frozen"
131
131
  Requires-Dist: click-repl==0.3.0; extra == "frozen"
@@ -133,7 +133,7 @@ Requires-Dist: colorlog==6.10.1; extra == "frozen"
133
133
  Requires-Dist: comm==0.2.3; extra == "frozen"
134
134
  Requires-Dist: contourpy==1.3.3; extra == "frozen"
135
135
  Requires-Dist: cron_descriptor==2.1.0; extra == "frozen"
136
- Requires-Dist: croniter==6.2.2; extra == "frozen"
136
+ Requires-Dist: croniter==6.2.3; extra == "frozen"
137
137
  Requires-Dist: cryptography==49.0.0; extra == "frozen"
138
138
  Requires-Dist: cycler==0.12.1; extra == "frozen"
139
139
  Requires-Dist: debugpy==1.8.21; extra == "frozen"
@@ -145,15 +145,15 @@ Requires-Dist: dkist-processing-common==14.3.4; extra == "frozen"
145
145
  Requires-Dist: dkist-processing-core==7.3.0; extra == "frozen"
146
146
  Requires-Dist: dkist-processing-math==2.2.3; extra == "frozen"
147
147
  Requires-Dist: dkist-processing-pac==3.1.2; extra == "frozen"
148
- Requires-Dist: dkist-processing-visp==5.7.22; extra == "frozen"
148
+ Requires-Dist: dkist-processing-visp==5.7.23; extra == "frozen"
149
149
  Requires-Dist: dkist-service-configuration==4.3.0; extra == "frozen"
150
150
  Requires-Dist: dkist-spectral-lines==3.0.1; extra == "frozen"
151
151
  Requires-Dist: dkist_fits_specifications==4.28.0; extra == "frozen"
152
152
  Requires-Dist: dnspython==2.8.0; extra == "frozen"
153
153
  Requires-Dist: email-validator==2.3.0; extra == "frozen"
154
154
  Requires-Dist: executing==2.2.1; extra == "frozen"
155
- Requires-Dist: fastapi==0.138.0; extra == "frozen"
156
- Requires-Dist: fastapi-cli==0.0.27; extra == "frozen"
155
+ Requires-Dist: fastapi==0.139.0; extra == "frozen"
156
+ Requires-Dist: fastapi-cli==0.0.28; extra == "frozen"
157
157
  Requires-Dist: fastjsonschema==2.21.2; extra == "frozen"
158
158
  Requires-Dist: flower==2.0.1; extra == "frozen"
159
159
  Requires-Dist: fonttools==4.63.0; extra == "frozen"
@@ -164,18 +164,18 @@ Requires-Dist: globus-sdk==4.8.1; extra == "frozen"
164
164
  Requires-Dist: googleapis-common-protos==1.75.0; extra == "frozen"
165
165
  Requires-Dist: gqlclient==2.0.0; extra == "frozen"
166
166
  Requires-Dist: greenback==1.3.0; extra == "frozen"
167
- Requires-Dist: greenlet==3.5.2; extra == "frozen"
168
- Requires-Dist: grpcio==1.81.1; extra == "frozen"
167
+ Requires-Dist: greenlet==3.5.3; extra == "frozen"
168
+ Requires-Dist: grpcio==1.82.0; extra == "frozen"
169
169
  Requires-Dist: h11==0.16.0; extra == "frozen"
170
170
  Requires-Dist: httpcore==1.0.9; extra == "frozen"
171
171
  Requires-Dist: httptools==0.8.0; extra == "frozen"
172
172
  Requires-Dist: httpx==0.28.1; extra == "frozen"
173
- Requires-Dist: humanize==4.15.0; extra == "frozen"
173
+ Requires-Dist: humanize==4.16.0; extra == "frozen"
174
174
  Requires-Dist: idna==3.18; extra == "frozen"
175
175
  Requires-Dist: imageio-ffmpeg==0.6.0; extra == "frozen"
176
176
  Requires-Dist: importlib_metadata==9.0.0; extra == "frozen"
177
177
  Requires-Dist: ipykernel==7.3.0; extra == "frozen"
178
- Requires-Dist: ipython==9.14.1; extra == "frozen"
178
+ Requires-Dist: ipython==9.15.0; extra == "frozen"
179
179
  Requires-Dist: ipython_pygments_lexers==1.1.1; extra == "frozen"
180
180
  Requires-Dist: isoduration==20.11.0; extra == "frozen"
181
181
  Requires-Dist: itsdangerous==2.2.0; extra == "frozen"
@@ -193,7 +193,7 @@ Requires-Dist: jupyter_client==8.9.1; extra == "frozen"
193
193
  Requires-Dist: jupyter_core==5.9.1; extra == "frozen"
194
194
  Requires-Dist: jupyter_server==2.20.0; extra == "frozen"
195
195
  Requires-Dist: jupyter_server_terminals==0.5.4; extra == "frozen"
196
- Requires-Dist: jupyterlab==4.6.0; extra == "frozen"
196
+ Requires-Dist: jupyterlab==4.6.1; extra == "frozen"
197
197
  Requires-Dist: jupyterlab_pygments==0.3.0; extra == "frozen"
198
198
  Requires-Dist: jupyterlab_server==2.28.0; extra == "frozen"
199
199
  Requires-Dist: kiwisolver==1.5.0; extra == "frozen"
@@ -211,7 +211,7 @@ Requires-Dist: matplotlib==3.11.0; extra == "frozen"
211
211
  Requires-Dist: matplotlib-inline==0.2.2; extra == "frozen"
212
212
  Requires-Dist: mdurl==0.1.2; extra == "frozen"
213
213
  Requires-Dist: methodtools==0.4.7; extra == "frozen"
214
- Requires-Dist: mistune==3.3.1; extra == "frozen"
214
+ Requires-Dist: mistune==3.3.2; extra == "frozen"
215
215
  Requires-Dist: more-itertools==11.1.0; extra == "frozen"
216
216
  Requires-Dist: moviepy==2.1.2; extra == "frozen"
217
217
  Requires-Dist: msgspec==0.21.1; extra == "frozen"
@@ -226,30 +226,30 @@ Requires-Dist: notebook==7.6.0; extra == "frozen"
226
226
  Requires-Dist: notebook_shim==0.2.4; extra == "frozen"
227
227
  Requires-Dist: numpy==2.2.5; extra == "frozen"
228
228
  Requires-Dist: object-clerk==1.0.0; extra == "frozen"
229
- Requires-Dist: opentelemetry-api==1.42.1; extra == "frozen"
230
- Requires-Dist: opentelemetry-exporter-otlp==1.42.1; extra == "frozen"
231
- Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.42.1; extra == "frozen"
232
- Requires-Dist: opentelemetry-exporter-otlp-proto-grpc==1.42.1; extra == "frozen"
233
- Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.42.1; extra == "frozen"
234
- Requires-Dist: opentelemetry-instrumentation==0.63b1; extra == "frozen"
235
- Requires-Dist: opentelemetry-instrumentation-aiohttp-client==0.63b1; extra == "frozen"
236
- Requires-Dist: opentelemetry-instrumentation-asgi==0.63b1; extra == "frozen"
237
- Requires-Dist: opentelemetry-instrumentation-botocore==0.63b1; extra == "frozen"
238
- Requires-Dist: opentelemetry-instrumentation-celery==0.63b1; extra == "frozen"
239
- Requires-Dist: opentelemetry-instrumentation-dbapi==0.63b1; extra == "frozen"
240
- Requires-Dist: opentelemetry-instrumentation-fastapi==0.63b1; extra == "frozen"
241
- Requires-Dist: opentelemetry-instrumentation-pika==0.63b1; extra == "frozen"
242
- Requires-Dist: opentelemetry-instrumentation-psycopg2==0.63b1; extra == "frozen"
243
- Requires-Dist: opentelemetry-instrumentation-pymongo==0.63b1; extra == "frozen"
244
- Requires-Dist: opentelemetry-instrumentation-redis==0.63b1; extra == "frozen"
245
- Requires-Dist: opentelemetry-instrumentation-requests==0.63b1; extra == "frozen"
246
- Requires-Dist: opentelemetry-instrumentation-sqlalchemy==0.63b1; extra == "frozen"
247
- Requires-Dist: opentelemetry-instrumentation-system-metrics==0.63b1; extra == "frozen"
229
+ Requires-Dist: opentelemetry-api==1.43.0; extra == "frozen"
230
+ Requires-Dist: opentelemetry-exporter-otlp==1.43.0; extra == "frozen"
231
+ Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.43.0; extra == "frozen"
232
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc==1.43.0; extra == "frozen"
233
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.43.0; extra == "frozen"
234
+ Requires-Dist: opentelemetry-instrumentation==0.64b0; extra == "frozen"
235
+ Requires-Dist: opentelemetry-instrumentation-aiohttp-client==0.64b0; extra == "frozen"
236
+ Requires-Dist: opentelemetry-instrumentation-asgi==0.64b0; extra == "frozen"
237
+ Requires-Dist: opentelemetry-instrumentation-botocore==0.64b0; extra == "frozen"
238
+ Requires-Dist: opentelemetry-instrumentation-celery==0.64b0; extra == "frozen"
239
+ Requires-Dist: opentelemetry-instrumentation-dbapi==0.64b0; extra == "frozen"
240
+ Requires-Dist: opentelemetry-instrumentation-fastapi==0.64b0; extra == "frozen"
241
+ Requires-Dist: opentelemetry-instrumentation-pika==0.64b0; extra == "frozen"
242
+ Requires-Dist: opentelemetry-instrumentation-psycopg2==0.64b0; extra == "frozen"
243
+ Requires-Dist: opentelemetry-instrumentation-pymongo==0.64b0; extra == "frozen"
244
+ Requires-Dist: opentelemetry-instrumentation-redis==0.64b0; extra == "frozen"
245
+ Requires-Dist: opentelemetry-instrumentation-requests==0.64b0; extra == "frozen"
246
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy==0.64b0; extra == "frozen"
247
+ Requires-Dist: opentelemetry-instrumentation-system-metrics==0.64b0; extra == "frozen"
248
248
  Requires-Dist: opentelemetry-propagator-aws-xray==1.0.2; extra == "frozen"
249
- Requires-Dist: opentelemetry-proto==1.42.1; extra == "frozen"
250
- Requires-Dist: opentelemetry-sdk==1.42.1; extra == "frozen"
251
- Requires-Dist: opentelemetry-semantic-conventions==0.63b1; extra == "frozen"
252
- Requires-Dist: opentelemetry-util-http==0.63b1; extra == "frozen"
249
+ Requires-Dist: opentelemetry-proto==1.43.0; extra == "frozen"
250
+ Requires-Dist: opentelemetry-sdk==1.43.0; extra == "frozen"
251
+ Requires-Dist: opentelemetry-semantic-conventions==0.64b0; extra == "frozen"
252
+ Requires-Dist: opentelemetry-util-http==0.64b0; extra == "frozen"
253
253
  Requires-Dist: outcome==1.3.0.post0; extra == "frozen"
254
254
  Requires-Dist: packaging==26.2; extra == "frozen"
255
255
  Requires-Dist: pandas==3.0.3; extra == "frozen"
@@ -270,7 +270,7 @@ Requires-Dist: proglog==0.1.12; extra == "frozen"
270
270
  Requires-Dist: prometheus_client==0.25.0; extra == "frozen"
271
271
  Requires-Dist: prompt_toolkit==3.0.52; extra == "frozen"
272
272
  Requires-Dist: propcache==0.5.2; extra == "frozen"
273
- Requires-Dist: protobuf==6.33.6; extra == "frozen"
273
+ Requires-Dist: protobuf==7.35.1; extra == "frozen"
274
274
  Requires-Dist: psutil==7.2.2; extra == "frozen"
275
275
  Requires-Dist: psycopg2-binary==2.9.12; extra == "frozen"
276
276
  Requires-Dist: ptyprocess==0.7.0; extra == "frozen"
@@ -300,7 +300,7 @@ Requires-Dist: rfc3987-syntax==1.1.0; extra == "frozen"
300
300
  Requires-Dist: rich==15.0.0; extra == "frozen"
301
301
  Requires-Dist: rich-argparse==1.8.0; extra == "frozen"
302
302
  Requires-Dist: rich-toolkit==0.20.1; extra == "frozen"
303
- Requires-Dist: rpds-py==2026.5.1; extra == "frozen"
303
+ Requires-Dist: rpds-py==2026.6.3; extra == "frozen"
304
304
  Requires-Dist: s3transfer==0.19.0; extra == "frozen"
305
305
  Requires-Dist: scikit-image==0.25.2; extra == "frozen"
306
306
  Requires-Dist: scikit-learn==1.6.1; extra == "frozen"
@@ -329,25 +329,25 @@ Requires-Dist: threadpoolctl==3.6.0; extra == "frozen"
329
329
  Requires-Dist: tifffile==2026.6.1; extra == "frozen"
330
330
  Requires-Dist: tinycss2==1.5.1; extra == "frozen"
331
331
  Requires-Dist: tornado==6.5.7; extra == "frozen"
332
- Requires-Dist: tqdm==4.68.3; extra == "frozen"
332
+ Requires-Dist: tqdm==4.68.4; extra == "frozen"
333
333
  Requires-Dist: traitlets==5.15.1; extra == "frozen"
334
- Requires-Dist: typer==0.26.7; extra == "frozen"
334
+ Requires-Dist: typer==0.26.8; extra == "frozen"
335
335
  Requires-Dist: typing-inspection==0.4.2; extra == "frozen"
336
- Requires-Dist: typing_extensions==4.15.0; extra == "frozen"
336
+ Requires-Dist: typing_extensions==4.16.0; extra == "frozen"
337
337
  Requires-Dist: tzdata==2026.2; extra == "frozen"
338
- Requires-Dist: tzlocal==5.4.3; extra == "frozen"
338
+ Requires-Dist: tzlocal==5.4.4; extra == "frozen"
339
339
  Requires-Dist: uc-micro-py==2.0.0; extra == "frozen"
340
340
  Requires-Dist: uncertainties==3.2.3; extra == "frozen"
341
341
  Requires-Dist: universal_pathlib==0.3.10; extra == "frozen"
342
342
  Requires-Dist: uri-template==1.3.0; extra == "frozen"
343
343
  Requires-Dist: urllib3==2.7.0; extra == "frozen"
344
344
  Requires-Dist: uuid6==2025.0.1; extra == "frozen"
345
- Requires-Dist: uvicorn==0.49.0; extra == "frozen"
345
+ Requires-Dist: uvicorn==0.50.2; extra == "frozen"
346
346
  Requires-Dist: uvloop==0.22.1; extra == "frozen"
347
347
  Requires-Dist: vine==5.1.0; extra == "frozen"
348
348
  Requires-Dist: voluptuous==0.16.0; extra == "frozen"
349
349
  Requires-Dist: watchfiles==1.2.0; extra == "frozen"
350
- Requires-Dist: wcwidth==0.8.1; extra == "frozen"
350
+ Requires-Dist: wcwidth==0.8.2; extra == "frozen"
351
351
  Requires-Dist: webcolors==25.10.0; extra == "frozen"
352
352
  Requires-Dist: webencodings==0.5.1; extra == "frozen"
353
353
  Requires-Dist: websocket-client==1.9.0; extra == "frozen"
@@ -27,6 +27,13 @@ class VispParsingParameters(ParameterBase):
27
27
  "visp_max_cs_step_time_sec", start_date=datetime.now()
28
28
  )
29
29
 
30
+ @property
31
+ def reflected_light_angle_tolerance_deg(self) -> float:
32
+ """Max allowed variation in REFLECTED_LIGHT_ANGLE_DEG across frames before raising an error."""
33
+ return self._find_most_recent_past_value(
34
+ "visp_reflected_light_angle_tolerance_deg", start_date=datetime.now()
35
+ )
36
+
30
37
 
31
38
  class VispParameters(ParameterBase, ParameterWavelengthMixin, ParameterArmIdMixin):
32
39
  """Put all Visp parameters parsed from the input dataset document in a single property."""
@@ -1,8 +1,10 @@
1
1
  """Buds for parsing the incident and reflected light angles of the ViSP spectrograph."""
2
2
 
3
- from dkist_processing_common.models.flower_pot import SetStem
3
+ from statistics import mean
4
+
4
5
  from dkist_processing_common.models.flower_pot import SpilledDirt
5
6
  from dkist_processing_common.models.task_name import TaskName
7
+ from dkist_processing_common.parsers.near_bud import TaskNearFloatBud
6
8
  from dkist_processing_common.parsers.task import parse_header_ip_task_with_gains
7
9
  from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
8
10
 
@@ -56,42 +58,34 @@ class IncidentLightAngleBud(TaskUniqueBud):
56
58
  return convert_grating_angle_to_incident_light_angle(grating_angle)
57
59
 
58
60
 
59
- class ReflectedLightAngleBud(SetStem):
61
+ class ReflectedLightAngleBud(TaskNearFloatBud):
60
62
  """Bud that combines the incident light angle and arm position header values to compute the reflected light angle."""
61
63
 
62
- def __init__(self):
63
- super().__init__(stem_name=VispBudName.reflected_light_angle_deg.value)
64
- self.ip_task_types = [
65
- task.casefold() for task in [TaskName.observe.value, TaskName.solar_gain.value]
66
- ]
64
+ def __init__(self, tolerance_deg: float):
65
+ super().__init__(
66
+ constant_name=VispBudName.reflected_light_angle_deg.value,
67
+ metadata_key=VispMetadataKey.arm_position_deg,
68
+ ip_task_types=[TaskName.observe.value, TaskName.solar_gain.value],
69
+ tolerance=tolerance_deg,
70
+ task_type_parsing_function=parse_header_ip_task_with_gains,
71
+ )
67
72
 
68
73
  def setter(self, fits_obj: VispL0FitsAccess) -> float | type[SpilledDirt]:
69
74
  """
70
75
  Compute the reflected light angle.
71
76
 
72
- The reflected light angle is `-1 * fits_objs.grating_angle_deg + fits_obj.arm_position_deg`.
77
+ The reflected light angle is `-1 * fits_obj.grating_angle_deg + fits_obj.arm_position_deg`.
73
78
  """
74
- task = parse_header_ip_task_with_gains(fits_obj)
79
+ task = self.parsing_function(fits_obj)
75
80
 
76
81
  if task.casefold() in self.ip_task_types:
77
82
  incident_light_angle = convert_grating_angle_to_incident_light_angle(
78
83
  fits_obj.grating_angle_deg
79
84
  )
80
85
  arm_position = fits_obj.arm_position_deg
81
- reflected_angle = (
82
- convert_incident_light_angle_and_arm_position_to_reflected_light_angle(
83
- incident_light_angle=incident_light_angle,
84
- arm_position=arm_position,
85
- )
86
+ return convert_incident_light_angle_and_arm_position_to_reflected_light_angle(
87
+ incident_light_angle=incident_light_angle,
88
+ arm_position=arm_position,
86
89
  )
87
- return reflected_angle
88
90
 
89
91
  return SpilledDirt
90
-
91
- def getter(self) -> float:
92
- """Get the value for the reflected light angle and raise an error if it is not unique."""
93
- if len(self.value_set) > 1:
94
- raise ValueError(
95
- f"Multiple {self.stem_name} values found for key. Values: {self.value_set}"
96
- )
97
- return self.value_set.pop()
@@ -96,7 +96,9 @@ class ParseL0VispInputData(ParseL0InputDataBase):
96
96
  NonDarkNonPolcalTaskReadoutExpTimesBud(),
97
97
  DarkReadoutExpTimePickyBud(),
98
98
  IncidentLightAngleBud(),
99
- ReflectedLightAngleBud(),
99
+ ReflectedLightAngleBud(
100
+ tolerance_deg=self.parameters.reflected_light_angle_tolerance_deg
101
+ ),
100
102
  TaskUniqueBud(
101
103
  constant_name=VispBudName.grating_constant_inverse_mm.value,
102
104
  metadata_key=VispMetadataKey.grating_constant_inverse_mm,
@@ -184,6 +184,7 @@ class WavelengthParameter:
184
184
 
185
185
  @dataclass
186
186
  class VispInputDatasetParameterValues:
187
+ visp_reflected_light_angle_tolerance_deg: float = 0.03
187
188
  visp_max_cs_step_time_sec: float = 180.0
188
189
  visp_beam_border: int = 1000
189
190
  visp_background_on: bool = True
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from datetime import datetime
2
3
  from datetime import timezone
3
4
 
@@ -7,6 +8,7 @@ from dkist_data_simulator.dataset import key_function
7
8
  from dkist_processing_common._util.scratch import WorkflowFileSystem
8
9
  from dkist_processing_common.models.tags import Tag
9
10
 
11
+ from dkist_processing_visp.models.constants import VispBudName
10
12
  from dkist_processing_visp.models.parameters import VispParsingParameters
11
13
  from dkist_processing_visp.models.tags import VispTag
12
14
  from dkist_processing_visp.tasks.parse import ParseL0VispInputData
@@ -82,6 +84,11 @@ def num_modstates() -> int:
82
84
  return 2
83
85
 
84
86
 
87
+ @pytest.fixture
88
+ def reflected_light_angle_tolerance_param_value() -> float:
89
+ return 0.03
90
+
91
+
85
92
  def write_input_dark_frames_to_task(
86
93
  task,
87
94
  readout_exp_time: float,
@@ -222,7 +229,10 @@ def write_input_cal_frames_to_task(
222
229
  ):
223
230
  seq = CalibrationSequence()
224
231
 
225
- def write_frames_to_task(task):
232
+ def write_frames_to_task(task, arm_position_offsets=None):
233
+ if arm_position_offsets is None:
234
+ arm_position_offsets = [0.0]
235
+
226
236
  for readout_exp_time in required_dark_readout_exp_times:
227
237
  write_input_dark_frames_to_task(
228
238
  task=task,
@@ -239,17 +249,18 @@ def write_input_cal_frames_to_task(
239
249
  num_modstates=num_modstates,
240
250
  arm_id=testing_arm_id,
241
251
  )
242
- write_input_solar_frames_to_task(
243
- task=task,
244
- readout_exp_time=solar_readout_exp_time,
245
- exp_time=solar_exp_time,
246
- num_modstates=num_modstates,
247
- arm_id=testing_arm_id,
248
- ip_start_time=testing_solar_ip_start_time,
249
- grating_constant=testing_grating_constant,
250
- grating_angle=testing_grating_angle,
251
- arm_position=testing_arm_position,
252
- )
252
+ for offset in arm_position_offsets:
253
+ write_input_solar_frames_to_task(
254
+ task=task,
255
+ readout_exp_time=solar_readout_exp_time,
256
+ exp_time=solar_exp_time,
257
+ num_modstates=num_modstates,
258
+ arm_id=testing_arm_id,
259
+ ip_start_time=testing_solar_ip_start_time,
260
+ grating_constant=testing_grating_constant,
261
+ grating_angle=testing_grating_angle,
262
+ arm_position=testing_arm_position + offset,
263
+ )
253
264
  # Create a fake set of calibration sequence frames
254
265
  write_calibration_sequence_frames_to_task(
255
266
  task=task,
@@ -273,7 +284,12 @@ def write_input_cal_frames_to_task(
273
284
 
274
285
 
275
286
  @pytest.fixture
276
- def parse_task_with_no_data(tmp_path, recipe_run_id, assign_input_dataset_doc_to_task):
287
+ def parse_task_with_no_data(
288
+ tmp_path,
289
+ recipe_run_id,
290
+ assign_input_dataset_doc_to_task,
291
+ reflected_light_angle_tolerance_param_value,
292
+ ):
277
293
  """You've got to populate the data in the actual test."""
278
294
  with ParseL0VispInputData(
279
295
  recipe_run_id=recipe_run_id,
@@ -286,7 +302,11 @@ def parse_task_with_no_data(tmp_path, recipe_run_id, assign_input_dataset_doc_to
286
302
  )
287
303
  assign_input_dataset_doc_to_task(
288
304
  task,
289
- VispInputDatasetParameterValues(),
305
+ VispInputDatasetParameterValues(
306
+ visp_reflected_light_angle_tolerance_deg=(
307
+ reflected_light_angle_tolerance_param_value
308
+ )
309
+ ),
290
310
  parameter_class=VispParsingParameters,
291
311
  obs_ip_start_time=None,
292
312
  )
@@ -368,6 +388,13 @@ def test_parse_visp_input_data(
368
388
  ) == len(observe_exp_times)
369
389
 
370
390
 
391
+ @pytest.mark.parametrize(
392
+ "arm_position_offset_multipliers, expected_reflected_offset_multiplier",
393
+ [
394
+ pytest.param([0.0], 0.0, id="single_arm_position"),
395
+ pytest.param([0.0, 0.9], 0.45, id="two_arm_positions_within_tolerance"),
396
+ ],
397
+ )
371
398
  def test_parse_visp_input_data_constants(
372
399
  parse_task_with_no_data,
373
400
  write_input_cal_frames_to_task,
@@ -389,6 +416,9 @@ def test_parse_visp_input_data_constants(
389
416
  testing_grating_constant,
390
417
  testing_grating_angle,
391
418
  testing_arm_position,
419
+ reflected_light_angle_tolerance_param_value,
420
+ arm_position_offset_multipliers,
421
+ expected_reflected_offset_multiplier,
392
422
  ):
393
423
  """
394
424
  Given: A ParseVispInputData task
@@ -399,24 +429,32 @@ def test_parse_visp_input_data_constants(
399
429
  "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
400
430
  )
401
431
  task = parse_task_with_no_data
402
- write_input_cal_frames_to_task(task)
432
+ arm_position_offsets = [
433
+ multiplier * reflected_light_angle_tolerance_param_value
434
+ for multiplier in arm_position_offset_multipliers
435
+ ]
436
+ expected_reflected_offset = (
437
+ expected_reflected_offset_multiplier * reflected_light_angle_tolerance_param_value
438
+ )
439
+ write_input_cal_frames_to_task(task, arm_position_offsets=arm_position_offsets)
403
440
 
404
441
  num_maps_per_readout_exp_time = 1
405
442
  num_steps = 3
406
- for obs_readout_exp_time, obs_exp_time in zip(observe_readout_exp_times, observe_exp_times):
407
- write_input_observe_frames_to_task(
408
- task,
409
- num_maps=num_maps_per_readout_exp_time,
410
- num_modstates=num_modstates,
411
- num_steps=num_steps,
412
- readout_exp_time=obs_readout_exp_time,
413
- exp_time=obs_exp_time,
414
- arm_id=testing_arm_id,
415
- ip_start_time=testing_obs_ip_start_time,
416
- grating_constant=testing_grating_constant,
417
- grating_angle=testing_grating_angle,
418
- arm_position=testing_arm_position,
419
- )
443
+ for offset in arm_position_offsets:
444
+ for obs_readout_exp_time, obs_exp_time in zip(observe_readout_exp_times, observe_exp_times):
445
+ write_input_observe_frames_to_task(
446
+ task,
447
+ num_maps=num_maps_per_readout_exp_time,
448
+ num_modstates=num_modstates,
449
+ num_steps=num_steps,
450
+ readout_exp_time=obs_readout_exp_time,
451
+ exp_time=obs_exp_time,
452
+ arm_id=testing_arm_id,
453
+ ip_start_time=testing_obs_ip_start_time,
454
+ grating_constant=testing_grating_constant,
455
+ grating_angle=testing_grating_angle,
456
+ arm_position=testing_arm_position + offset,
457
+ )
420
458
 
421
459
  # When
422
460
  task()
@@ -430,14 +468,14 @@ def test_parse_visp_input_data_constants(
430
468
  assert task.constants._db_dict["INCIDENT_LIGHT_ANGLE_DEG"] == -1 * testing_grating_angle
431
469
  assert (
432
470
  task.constants._db_dict["REFLECTED_LIGHT_ANGLE_DEG"]
433
- == -1 * testing_grating_angle + testing_arm_position
471
+ == -1 * testing_grating_angle + testing_arm_position + expected_reflected_offset
434
472
  )
435
473
  assert task.constants._db_dict["GRATING_CONSTANT_INVERSE_MM"] == testing_grating_constant
436
474
  assert task.constants._db_dict["SOLAR_GAIN_IP_START_TIME"] == testing_solar_ip_start_time
437
475
  assert task.constants._db_dict["NUM_MODSTATES"] == num_modstates
438
476
  assert task.constants._db_dict["NUM_MAP_SCANS"] == num_maps_per_readout_exp_time * len(
439
477
  observe_readout_exp_times
440
- )
478
+ ) * len(arm_position_offsets)
441
479
  assert task.constants._db_dict["NUM_RASTER_STEPS"] == num_steps
442
480
  assert task.constants._db_dict["WAVELENGTH"] == 656.28
443
481
  assert task.constants._db_dict["DARK_EXPOSURE_TIMES"] == [dark_exp_time]
@@ -466,6 +504,60 @@ def test_parse_visp_input_data_constants(
466
504
  )
467
505
 
468
506
 
507
+ def test_reflected_light_angle_exceeds_tolerance_raises_error(
508
+ parse_task_with_no_data,
509
+ write_input_cal_frames_to_task,
510
+ observe_readout_exp_times,
511
+ observe_exp_times,
512
+ num_modstates,
513
+ mocker,
514
+ fake_gql_client,
515
+ testing_arm_id,
516
+ testing_obs_ip_start_time,
517
+ testing_grating_constant,
518
+ testing_grating_angle,
519
+ testing_arm_position,
520
+ reflected_light_angle_tolerance_param_value,
521
+ ):
522
+ """
523
+ Given: A ParseVispInputData task with observe frames whose arm positions differ by more than the tolerance
524
+ When: Calling the task instance
525
+ Then: A ValueError is raised
526
+ """
527
+ mocker.patch(
528
+ "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
529
+ )
530
+ task = parse_task_with_no_data
531
+ write_input_cal_frames_to_task(task)
532
+
533
+ for arm_position in [
534
+ testing_arm_position,
535
+ testing_arm_position + reflected_light_angle_tolerance_param_value * 2,
536
+ ]:
537
+ for obs_readout_exp_time, obs_exp_time in zip(observe_readout_exp_times, observe_exp_times):
538
+ write_input_observe_frames_to_task(
539
+ task,
540
+ num_maps=1,
541
+ num_modstates=num_modstates,
542
+ num_steps=3,
543
+ readout_exp_time=obs_readout_exp_time,
544
+ exp_time=obs_exp_time,
545
+ arm_id=testing_arm_id,
546
+ ip_start_time=testing_obs_ip_start_time,
547
+ grating_constant=testing_grating_constant,
548
+ grating_angle=testing_grating_angle,
549
+ arm_position=arm_position,
550
+ )
551
+
552
+ with pytest.raises(
553
+ ValueError,
554
+ match=re.escape(
555
+ f"{VispBudName.reflected_light_angle_deg.value} values are not close enough"
556
+ ),
557
+ ):
558
+ task()
559
+
560
+
469
561
  def test_parse_visp_values(
470
562
  parse_task_with_no_data,
471
563
  write_input_cal_frames_to_task,