dkist-processing-common 10.8.2__tar.gz → 10.8.3__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 (128) hide show
  1. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/CHANGELOG.rst +15 -0
  2. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/PKG-INFO +1 -1
  3. dkist_processing_common-10.8.3/dkist_processing_common/models/fried_parameter.py +41 -0
  4. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/quality.py +1 -0
  5. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/mixin/quality/_metrics.py +19 -14
  6. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/quality_metrics.py +1 -1
  7. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/write_l1.py +9 -2
  8. dkist_processing_common-10.8.3/dkist_processing_common/tests/test_fried_parameter.py +27 -0
  9. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_quality_mixin.py +32 -22
  10. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_write_l1.py +33 -8
  11. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common.egg-info/PKG-INFO +1 -1
  12. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common.egg-info/SOURCES.txt +2 -0
  13. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/.gitignore +0 -0
  14. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/.pre-commit-config.yaml +0 -0
  15. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/.readthedocs.yml +0 -0
  16. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/.snyk +0 -0
  17. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/README.rst +0 -0
  18. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/bitbucket-pipelines.yml +0 -0
  19. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/changelog/.gitempty +0 -0
  20. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/__init__.py +0 -0
  21. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/_util/__init__.py +0 -0
  22. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/_util/constants.py +0 -0
  23. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/_util/graphql.py +0 -0
  24. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/_util/scratch.py +0 -0
  25. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/_util/tags.py +0 -0
  26. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/codecs/__init__.py +0 -0
  27. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/codecs/asdf.py +0 -0
  28. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/codecs/bytes.py +0 -0
  29. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/codecs/fits.py +0 -0
  30. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/codecs/iobase.py +0 -0
  31. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/codecs/json.py +0 -0
  32. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/codecs/path.py +0 -0
  33. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/codecs/quality.py +0 -0
  34. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/codecs/str.py +0 -0
  35. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/config.py +0 -0
  36. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/fonts/Lato-Regular.ttf +0 -0
  37. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/fonts/__init__.py +0 -0
  38. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/manual.py +0 -0
  39. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/__init__.py +0 -0
  40. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/constants.py +0 -0
  41. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/fits_access.py +0 -0
  42. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/flower_pot.py +0 -0
  43. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/graphql.py +0 -0
  44. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/message.py +0 -0
  45. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/message_queue_binding.py +0 -0
  46. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/metric_code.py +0 -0
  47. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/parameters.py +0 -0
  48. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/quality.py +0 -0
  49. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/tags.py +0 -0
  50. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/task_name.py +0 -0
  51. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/models/wavelength.py +0 -0
  52. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/__init__.py +0 -0
  53. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/cs_step.py +0 -0
  54. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/dsps_repeat.py +0 -0
  55. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/experiment_id_bud.py +0 -0
  56. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/id_bud.py +0 -0
  57. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/l0_fits_access.py +0 -0
  58. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/l1_fits_access.py +0 -0
  59. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/near_bud.py +0 -0
  60. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/proposal_id_bud.py +0 -0
  61. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/retarder.py +0 -0
  62. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/single_value_single_key_flower.py +0 -0
  63. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/task.py +0 -0
  64. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/time.py +0 -0
  65. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/unique_bud.py +0 -0
  66. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/parsers/wavelength.py +0 -0
  67. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/__init__.py +0 -0
  68. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/assemble_movie.py +0 -0
  69. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/base.py +0 -0
  70. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/l1_output_data.py +0 -0
  71. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/mixin/__init__.py +0 -0
  72. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/mixin/globus.py +0 -0
  73. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/mixin/input_dataset.py +0 -0
  74. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/mixin/interservice_bus.py +0 -0
  75. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/mixin/metadata_store.py +0 -0
  76. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/mixin/object_store.py +0 -0
  77. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/mixin/quality/__init__.py +0 -0
  78. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/mixin/quality/_base.py +0 -0
  79. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/output_data_base.py +0 -0
  80. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/parse_l0_input_data.py +0 -0
  81. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/teardown.py +0 -0
  82. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/transfer_input_data.py +0 -0
  83. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/trial_catalog.py +0 -0
  84. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tasks/trial_output_data.py +0 -0
  85. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/__init__.py +0 -0
  86. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/conftest.py +0 -0
  87. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_assemble_movie.py +0 -0
  88. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_assemble_quality.py +0 -0
  89. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_base.py +0 -0
  90. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_codecs.py +0 -0
  91. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_constants.py +0 -0
  92. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_cs_step.py +0 -0
  93. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_fits_access.py +0 -0
  94. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_flower_pot.py +0 -0
  95. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_input_dataset.py +0 -0
  96. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_interservice_bus.py +0 -0
  97. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_interservice_bus_mixin.py +0 -0
  98. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_manual_processing.py +0 -0
  99. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_output_data_base.py +0 -0
  100. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_parameters.py +0 -0
  101. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_parse_l0_input_data.py +0 -0
  102. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_publish_catalog_messages.py +0 -0
  103. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_quality.py +0 -0
  104. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_scratch.py +0 -0
  105. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_stems.py +0 -0
  106. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_submit_dataset_metadata.py +0 -0
  107. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_tags.py +0 -0
  108. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_task_name.py +0 -0
  109. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_task_parsing.py +0 -0
  110. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_teardown.py +0 -0
  111. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_transfer_input_data.py +0 -0
  112. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_transfer_l1_output_data.py +0 -0
  113. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_trial_catalog.py +0 -0
  114. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_trial_output_data.py +0 -0
  115. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common/tests/test_workflow_task_base.py +0 -0
  116. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common.egg-info/dependency_links.txt +0 -0
  117. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common.egg-info/requires.txt +0 -0
  118. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/dkist_processing_common.egg-info/top_level.txt +0 -0
  119. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/docs/Makefile +0 -0
  120. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/docs/changelog.rst +0 -0
  121. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/docs/conf.py +0 -0
  122. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/docs/index.rst +0 -0
  123. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/docs/landing_page.rst +0 -0
  124. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/docs/make.bat +0 -0
  125. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/docs/requirements.txt +0 -0
  126. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/licenses/LICENSE.rst +0 -0
  127. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/pyproject.toml +0 -0
  128. {dkist_processing_common-10.8.2 → dkist_processing_common-10.8.3}/setup.cfg +0 -0
@@ -1,3 +1,18 @@
1
+ v10.8.3 (2025-05-28)
2
+ ====================
3
+
4
+ Features
5
+ --------
6
+
7
+ - Further constrain when the ATMOS_R0 header key is written in L1 data based on the following metrics:
8
+
9
+ * the ATMOS_R0 value in the L0 data must be less than or equal to 30cm
10
+ * the AO_LOCK value in the L0 data must be True
11
+ * the OOBSHIFT value in the L0 data must be less than 100
12
+
13
+ The wavefront sensor has about 1500 subapertures from which x and y shifts are measured. When the adaptive optics loop is locked, we count how many of these shifts exceed a threshold considered to be out of bounds. The r0 measurement gets less accurate as more of these shifts are out of bounds and so this is another way to detect outliers in the r0 value. (`#250 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/250>`__)
14
+
15
+
1
16
  v10.8.2 (2025-05-27)
2
17
  ====================
3
18
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dkist-processing-common
3
- Version: 10.8.2
3
+ Version: 10.8.3
4
4
  Summary: Common task classes used by the DKIST science data processing pipelines
5
5
  Author-email: NSO / AURA <dkistdc@nso.edu>
6
6
  License: BSD-3-Clause
@@ -0,0 +1,41 @@
1
+ """Helper methods to handle fried parameter / r0 validity."""
2
+
3
+
4
+ def r0_valid(
5
+ r0: float | None = None,
6
+ ao_lock: bool | None = None,
7
+ num_out_of_bounds_ao_values: int | None = None,
8
+ ) -> bool:
9
+ """
10
+ Determine if the r0 value should be considered valid based on the following conditions.
11
+
12
+ * ATMOS_R0 does not exist in the header.
13
+ * the value of ATMOS_R0 is greater than 0.3m
14
+ * the AO is not locked
15
+ * the value of OOBSHIFT is greater than 100
16
+
17
+ When the adaptive optics system is not locked, the ATMOS_R0 keyword is still filled with the output of the
18
+ Fried parameter calculation. The inputs are not valid in this instance and the value should be removed.
19
+
20
+ Sometimes, due to timing differences between the calculation of the Fried parameter and the AO lock status being
21
+ updated, non-physical values can be recorded for ATMOS_R0 right on the edge of an AO_LOCK state change. To
22
+ combat this, any remaining R0 values greater than 30cm (which is beyond the realm of physical possibility for
23
+ solar observations) are also removed.
24
+
25
+ In addition, the number of AO out-of-bound values is given in the keyword OOBSHIFT and the AO team advises
26
+ that values under 100 are when the r0 value is considered reliable. If the OOBSHIFT key doesn't exist, this check
27
+ should be ignored.
28
+ """
29
+ if r0 is None:
30
+ return False
31
+
32
+ if r0 > 0.3:
33
+ return False
34
+
35
+ if ao_lock is not True:
36
+ return False
37
+
38
+ if num_out_of_bounds_ao_values is not None and num_out_of_bounds_ao_values > 100:
39
+ return False
40
+
41
+ return True
@@ -31,3 +31,4 @@ class L1QualityFitsAccess(L1FitsAccess):
31
31
  self.light_level: float = self.header["LIGHTLVL"]
32
32
  self.health_status: str = self.header["DSHEALTH"]
33
33
  self.ao_status: int = self.header.get("AO_LOCK", None)
34
+ self.num_out_of_bounds_ao_values: int = self.header.get("OOBSHIFT", None)
@@ -20,6 +20,7 @@ from dkist_processing_pac.fitter.fitting_core import compare_I
20
20
  from dkist_processing_pac.fitter.polcal_fitter import PolcalFitter
21
21
  from pandas import DataFrame
22
22
 
23
+ from dkist_processing_common.models.fried_parameter import r0_valid
23
24
  from dkist_processing_common.models.metric_code import MetricCode
24
25
  from dkist_processing_common.models.quality import EfficiencyHistograms
25
26
  from dkist_processing_common.models.quality import ModulationMatrixHistograms
@@ -203,7 +204,7 @@ class _SimplePlotQualityMixin:
203
204
  return warnings
204
205
 
205
206
  def quality_store_ao_status_and_fried_parameter(
206
- self, datetimes: list[str], values: list[list[bool, float]]
207
+ self, datetimes: list[str], values: list[list[bool | float]]
207
208
  ):
208
209
  """
209
210
  Collect and store datetime / value pairs for the boolean AO status and Fried parameter.
@@ -213,23 +214,27 @@ class _SimplePlotQualityMixin:
213
214
  Because of how L1Metric.has_metric works, empty lists will not be passed to this method.
214
215
  However, because of how L1Metric.store_metric works, one or both values can be None.
215
216
  """
216
- ao_values = [value[0] for value in values]
217
- ao_not_none = [ao for ao in ao_values if ao is not None]
217
+ ao_lock_values = [value[0] for value in values]
218
+ ao_not_none = [ao for ao in ao_lock_values if ao is not None]
218
219
  if len(ao_not_none) != 0:
219
220
  self._record_values(values=ao_not_none, tags=Tag.quality(MetricCode.ao_status))
220
221
  fried_values = [value[1] for value in values]
221
- fried_values_not_none_ao_locked = [
222
- r0 for ao, r0 in zip(ao_values, fried_values) if (ao is True) and (r0 is not None)
223
- ]
224
- datetimes_fried_not_none_ao_locked = [
225
- dt
226
- for dt, ao, r0 in zip(datetimes, ao_values, fried_values)
227
- if (ao is True) and (r0 is not None)
228
- ]
229
- if len(fried_values_not_none_ao_locked) != 0:
222
+ ao_oob_values = [value[2] for value in values]
223
+ fried_values_to_plot = []
224
+ datetimes_to_plot = []
225
+ # For each set of input data, check if the r0 is considered valid based on all data
226
+ for i in range(len(fried_values)):
227
+ if r0_valid(
228
+ r0=fried_values[i],
229
+ ao_lock=ao_lock_values[i],
230
+ num_out_of_bounds_ao_values=ao_oob_values[i],
231
+ ):
232
+ fried_values_to_plot.append(fried_values[i])
233
+ datetimes_to_plot.append(datetimes[i])
234
+ if len(fried_values_to_plot) != 0:
230
235
  self._record_2d_plot_values(
231
- x_values=datetimes_fried_not_none_ao_locked,
232
- y_values=fried_values_not_none_ao_locked,
236
+ x_values=datetimes_to_plot,
237
+ y_values=fried_values_to_plot,
233
238
  tags=Tag.quality(MetricCode.fried_parameter),
234
239
  )
235
240
 
@@ -296,7 +296,7 @@ class QualityL1Metrics(WorkflowTaskBase, QualityMixin):
296
296
  L1Metric(storage_method=self.quality_store_health_status, value_source="health_status"),
297
297
  L1Metric(
298
298
  storage_method=self.quality_store_ao_status_and_fried_parameter,
299
- value_source=["ao_status", "fried_parameter"],
299
+ value_source=["ao_status", "fried_parameter", "num_out_of_bounds_ao_values"],
300
300
  ),
301
301
  ]
302
302
 
@@ -29,6 +29,7 @@ from sunpy.coordinates import Helioprojective
29
29
 
30
30
  from dkist_processing_common.codecs.fits import fits_access_decoder
31
31
  from dkist_processing_common.codecs.fits import fits_hdulist_encoder
32
+ from dkist_processing_common.models.fried_parameter import r0_valid
32
33
  from dkist_processing_common.models.tags import Tag
33
34
  from dkist_processing_common.models.wavelength import WavelengthRange
34
35
  from dkist_processing_common.parsers.l0_fits_access import L0FitsAccess
@@ -373,8 +374,14 @@ class WriteL1Frame(WorkflowTaskBase, MetadataStoreMixin, ABC):
373
374
  """
374
375
  # Replace header values in place
375
376
  header = self.replace_header_values(header=header, data=data)
376
- # Remove r0 value if AO not locked
377
- header = self.remove_invalid_r0_values(header=header)
377
+ # Remove r0 value if r0 conditions are not met
378
+ r0_is_valid = r0_valid(
379
+ r0=header["ATMOS_R0"],
380
+ ao_lock=header.get("AO_LOCK", None),
381
+ num_out_of_bounds_ao_values=header.get("OOBSHIFT", None),
382
+ )
383
+ if not r0_is_valid:
384
+ header.pop("ATMOS_R0", None)
378
385
  # Add the stats table
379
386
  header = self.add_stats_headers(header=header, data=data)
380
387
  # Add the datacenter table
@@ -0,0 +1,27 @@
1
+ import pytest
2
+
3
+ from dkist_processing_common.models.fried_parameter import r0_valid
4
+
5
+
6
+ @pytest.mark.parametrize(
7
+ "r0, ao_lock, oob_shift, should_r0_exist",
8
+ [
9
+ pytest.param(0.2, True, 17, True, id="AO_LOCK_True_good_R0_good_oob"),
10
+ pytest.param(1, True, 17, False, id="AO_LOCK_True_bad_R0_good_oob"),
11
+ pytest.param(0.2, False, 17, False, id="AO_LOCK_False_good_R0_good_oob"),
12
+ pytest.param(1, False, 17, False, id="AO_LOCK_False_bad_R0_good_oob"),
13
+ pytest.param(0.2, True, 150, False, id="AO_LOCK_True_good_R0_bad_oob"),
14
+ pytest.param(1, True, 150, False, id="AO_LOCK_True_bad_R0_bad_oob"),
15
+ pytest.param(0.2, False, 150, False, id="AO_LOCK_False_good_R0_bad_oob"),
16
+ pytest.param(1, False, 150, False, id="AO_LOCK_False_bad_R0_bad_oob"),
17
+ pytest.param(0.2, None, 17, False, id="AO_LOCK_missing"),
18
+ pytest.param(0.2, True, None, True, id="OOBSHIFT_missing"),
19
+ ],
20
+ )
21
+ def test_check_r0_valid(r0, ao_lock, oob_shift, should_r0_exist):
22
+ """
23
+ :Given: values for r0, the ao_lock status, and the ao out of bound shift value
24
+ :When: checking for a valid state to use r0
25
+ :Then: valid conditions are marked True, invalid conditions marked False
26
+ """
27
+ assert r0_valid(r0, ao_lock, oob_shift) == should_r0_exist
@@ -39,9 +39,9 @@ def quality_task(tmp_path, recipe_run_id):
39
39
  @pytest.fixture
40
40
  def plot_data():
41
41
  datetimes_a = ["2021-01-01T01:01:01", "2021-01-01T02:01:01"]
42
- values_a = [3, 4]
42
+ values_a = [0.1, 0.2]
43
43
  datetimes_b = ["2020-01-01T01:01:01", "2020-01-01T02:01:01"]
44
- values_b = [1, 2]
44
+ values_b = [0.15, 0.25]
45
45
  return datetimes_a, values_a, datetimes_b, values_b
46
46
 
47
47
 
@@ -198,8 +198,9 @@ def test_store_ao_status_and_fried_parameter(quality_task, ao_values):
198
198
  """
199
199
  task = quality_task
200
200
  datetimes = ["2020-01-01T01:01:01", "2020-01-01T02:01:01"]
201
- fried_values = [3.0, 4.0]
202
- combined_values = [[ao, r0] for ao, r0 in zip(ao_values, fried_values)]
201
+ fried_values = [0.1, 0.2]
202
+ oob_values = [25, 50]
203
+ combined_values = [[ao, r0, oob] for ao, r0, oob in zip(ao_values, fried_values, oob_values)]
203
204
  task.quality_store_ao_status_and_fried_parameter(datetimes=datetimes, values=combined_values)
204
205
  path = list(task.read(tags=Tag.quality("AO_STATUS")))
205
206
  assert len(path) == 1
@@ -220,10 +221,11 @@ def test_store_ao_status_and_fried_parameter(quality_task, ao_values):
220
221
  @pytest.mark.parametrize(
221
222
  "combined_values",
222
223
  [
223
- pytest.param([[True, 1], [None, 2]], id="AO_some_none"),
224
- pytest.param([[True, 1], [True, None]], id="Fried_some_none"),
225
- pytest.param([[None, 1], [None, 2]], id="AO_all_none"),
226
- pytest.param([[True, None], [True, None]], id="Fried_all_none"),
224
+ pytest.param([[True, 0.1, 25], [None, 0.2, 25]], id="AO_some_none"),
225
+ pytest.param([[True, 0.1, 25], [True, None, 25]], id="Fried_some_none"),
226
+ pytest.param([[None, 0.1, 25], [None, 0.2, 25]], id="AO_all_none"),
227
+ pytest.param([[True, None, 25], [True, None, 25]], id="Fried_all_none"),
228
+ pytest.param([[True, 0.1, None], [True, 0.2, None]], id="Out_of_bounds_all_none"),
227
229
  ],
228
230
  )
229
231
  def test_store_ao_status_and_fried_parameter_with_nones(quality_task, combined_values):
@@ -231,13 +233,14 @@ def test_store_ao_status_and_fried_parameter_with_nones(quality_task, combined_v
231
233
  datetimes = ["2020-01-01T01:01:01", "2020-01-01T02:01:01"]
232
234
  task.quality_store_ao_status_and_fried_parameter(datetimes=datetimes, values=combined_values)
233
235
  path = list(task.read(tags=Tag.quality("AO_STATUS")))
234
- ao_values = [ao for ao, r0 in combined_values]
235
- fried_values = [r0 for ao, r0 in combined_values]
236
+ ao_values = [ao for ao, r0, oob in combined_values]
237
+ fried_values = [r0 for ao, r0, oob in combined_values]
238
+ ao_out_of_bounds = [oob for ao, r0, oob in combined_values]
236
239
  if not all(ao is None for ao in ao_values):
237
240
  assert len(path) == 1
238
241
  with path[0].open() as f:
239
242
  data = json.load(f)
240
- assert len(data) == sum(1 for ao, r0 in combined_values if ao is not None)
243
+ assert len(data) == sum(1 for ao, r0, oob in combined_values if ao is not None)
241
244
  else:
242
245
  assert len(path) == 0
243
246
  path = list(task.read(tags=Tag.quality("FRIED_PARAMETER")))
@@ -246,7 +249,7 @@ def test_store_ao_status_and_fried_parameter_with_nones(quality_task, combined_v
246
249
  with path[0].open() as f:
247
250
  data = json.load(f)
248
251
  assert len(data["y_values"]) == sum(
249
- 1 for ao, r0 in combined_values if ao is True and r0 is not None
252
+ 1 for ao, r0, oob in combined_values if ao is True and r0 is not None
250
253
  )
251
254
  else:
252
255
  assert len(path) == 0
@@ -263,7 +266,8 @@ def test_build_ao_status(quality_task, plot_data):
263
266
  datetimes = datetimes_a + datetimes_b
264
267
  fried_values = values_a + values_b
265
268
  ao_values = [False, True, True, True]
266
- combined_values = [[ao, r0] for ao, r0 in zip(ao_values, fried_values)]
269
+ oob_values = [25, 50, None, 50]
270
+ combined_values = [[ao, r0, oob] for ao, r0, oob in zip(ao_values, fried_values, oob_values)]
267
271
  task.quality_store_ao_status_and_fried_parameter(datetimes=datetimes, values=combined_values)
268
272
  metric = task.quality_build_ao_status()
269
273
  assert metric["name"] == "Adaptive Optics Status"
@@ -284,9 +288,15 @@ def test_build_fried_parameter(quality_task, plot_data):
284
288
  task = quality_task
285
289
  datetimes_a, fried_values_a, datetimes_b, fried_values_b = plot_data
286
290
  ao_values_a = [True, True]
287
- combined_values_a = [[ao, r0] for ao, r0 in zip(ao_values_a, fried_values_a)]
291
+ oob_values_a = [25, 50]
292
+ combined_values_a = [
293
+ [ao, r0, oob] for ao, r0, oob in zip(ao_values_a, fried_values_a, oob_values_a)
294
+ ]
288
295
  ao_values_b = [True, True]
289
- combined_values_b = [[ao, r0] for ao, r0 in zip(ao_values_b, fried_values_b)]
296
+ oob_values_b = [25, 50]
297
+ combined_values_b = [
298
+ [ao, r0, oob] for ao, r0, oob in zip(ao_values_b, fried_values_b, oob_values_b)
299
+ ]
290
300
  task.quality_store_ao_status_and_fried_parameter(
291
301
  datetimes=datetimes_a, values=combined_values_a
292
302
  )
@@ -307,14 +317,14 @@ def test_build_fried_parameter(quality_task, plot_data):
307
317
  "2021-01-01T02:01:01",
308
318
  ]
309
319
  ]
310
- assert metric["plot_data"]["series_data"][""][1] == [1, 2, 3, 4]
320
+ assert metric["plot_data"]["series_data"][""][1] == [0.15, 0.25, 0.1, 0.2]
311
321
  assert metric["name"] == "Fried Parameter"
312
322
  assert metric["metric_code"] == "FRIED_PARAMETER"
313
323
  assert metric["facet"] is None
314
324
  assert metric["warnings"] is None
315
325
  assert (
316
326
  metric["statement"]
317
- == "Average valid Fried Parameter measurements for L1 dataset: 2.5 ± 1.12 m"
327
+ == "Average valid Fried Parameter measurements for L1 dataset: 0.18 ± 0.06 m"
318
328
  )
319
329
 
320
330
 
@@ -340,12 +350,12 @@ def test_build_light_level(quality_task, plot_data):
340
350
  "2021-01-01T02:01:01",
341
351
  ]
342
352
  ]
343
- assert metric["plot_data"]["series_data"][""][1] == [1, 2, 3, 4]
353
+ assert metric["plot_data"]["series_data"][""][1] == [0.15, 0.25, 0.1, 0.2]
344
354
  assert metric["name"] == "Light Level"
345
355
  assert metric["metric_code"] == "LIGHT_LEVEL"
346
356
  assert metric["facet"] is None
347
357
  assert metric["warnings"] is None
348
- assert metric["statement"] == f"Average Light Level for L1 dataset: 2.5 ± 1.12 adu"
358
+ assert metric["statement"] == f"Average Light Level for L1 dataset: 0.18 ± 0.06 adu"
349
359
 
350
360
 
351
361
  def test_build_frame_average(quality_task, plot_data):
@@ -461,7 +471,7 @@ def test_build_noise(quality_task, plot_data):
461
471
  "2021-01-01T02:01:01",
462
472
  ]
463
473
  ]
464
- assert metric["plot_data"]["series_data"]["I"][1] == [1, 2, 3, 4]
474
+ assert metric["plot_data"]["series_data"]["I"][1] == [0.15, 0.25, 0.1, 0.2]
465
475
  assert metric["name"] == "Noise Estimation"
466
476
  assert metric["metric_code"] == "NOISE"
467
477
  assert metric["facet"] is None
@@ -493,7 +503,7 @@ def test_build_sensitivity(quality_task, plot_data):
493
503
  "2021-01-01T02:01:01",
494
504
  ]
495
505
  ]
496
- assert metric["plot_data"]["series_data"]["I"][1] == [1, 2, 3, 4]
506
+ assert metric["plot_data"]["series_data"]["I"][1] == [0.15, 0.25, 0.1, 0.2]
497
507
  assert metric["name"] == f"Sensitivity"
498
508
  assert metric["metric_code"] == "SENSITIVITY"
499
509
  assert metric["facet"] is None
@@ -692,7 +702,7 @@ def test_build_report(quality_task, plot_data):
692
702
  task.quality_store_task_type_counts(task_type="dark", total_frames=100, frames_not_used=7)
693
703
  task.quality_store_task_type_counts(task_type="gain", total_frames=100, frames_not_used=0)
694
704
  task.quality_store_ao_status_and_fried_parameter(
695
- datetimes=datetimes, values=[[True, values[0]], [True, values[1]]]
705
+ datetimes=datetimes, values=[[True, values[0], values[0]], [True, values[1], values[1]]]
696
706
  )
697
707
  task.quality_store_light_level(datetimes=datetimes, values=values)
698
708
  task.quality_store_frame_average(
@@ -201,9 +201,32 @@ def write_l1_task_with_empty_waveband(recipe_run_id, tmp_path, request):
201
201
  @pytest.fixture(
202
202
  scope="function",
203
203
  params=[
204
- pytest.param({"AO_LOCK": True}, id="AO_LOCK_True"),
205
- pytest.param({"AO_LOCK": False}, id="AO_LOCK_False"),
206
- pytest.param({}, id="AO_LOCK_missing"),
204
+ pytest.param(
205
+ {"AO_LOCK": True, "ATMOS_R0": 0.2, "OOBSHIFT": 17}, id="AO_LOCK_True_good_R0_good_oob"
206
+ ),
207
+ pytest.param(
208
+ {"AO_LOCK": True, "ATMOS_R0": 1, "OOBSHIFT": 17}, id="AO_LOCK_True_bad_R0_good_oob"
209
+ ),
210
+ pytest.param(
211
+ {"AO_LOCK": False, "ATMOS_R0": 0.2, "OOBSHIFT": 17}, id="AO_LOCK_False_good_R0_good_oob"
212
+ ),
213
+ pytest.param(
214
+ {"AO_LOCK": False, "ATMOS_R0": 1, "OOBSHIFT": 17}, id="AO_LOCK_False_bad_R0_good_oob"
215
+ ),
216
+ pytest.param(
217
+ {"AO_LOCK": True, "ATMOS_R0": 0.2, "OOBSHIFT": 150}, id="AO_LOCK_True_good_R0_bad_oob"
218
+ ),
219
+ pytest.param(
220
+ {"AO_LOCK": True, "ATMOS_R0": 1, "OOBSHIFT": 150}, id="AO_LOCK_True_bad_R0_bad_oob"
221
+ ),
222
+ pytest.param(
223
+ {"AO_LOCK": False, "ATMOS_R0": 0.2, "OOBSHIFT": 150}, id="AO_LOCK_False_good_R0_bad_oob"
224
+ ),
225
+ pytest.param(
226
+ {"AO_LOCK": False, "ATMOS_R0": 1, "OOBSHIFT": 150}, id="AO_LOCK_False_bad_R0_bad_oob"
227
+ ),
228
+ pytest.param({"ATMOS_R0": 0.2, "OOBSHIFT": 17}, id="AO_LOCK_missing"),
229
+ pytest.param({"ATMOS_R0": 0.2, "AO_LOCK": True}, id="OOBSHIFT_missing"),
207
230
  ],
208
231
  )
209
232
  def write_l1_task_no_data(request, recipe_run_id, tmp_path, complete_common_header):
@@ -216,10 +239,10 @@ def write_l1_task_no_data(request, recipe_run_id, tmp_path, complete_common_head
216
239
  ):
217
240
  task.scratch = WorkflowFileSystem(recipe_run_id=recipe_run_id, scratch_base_path=tmp_path)
218
241
  header = complete_common_header
219
- header.pop("AO_LOCK", None) # If it's not required, shouldn't be here in the first place??
242
+ header.pop("AO_LOCK", None)
243
+ header.pop("ATMOS_R0", None)
244
+ header.pop("OOBSHIFT", None)
220
245
  header.update(request.param)
221
- fried_parameter = 0.2
222
- header["ATMOS_R0"] = fried_parameter
223
246
  hdu = fits.PrimaryHDU(data=np.random.random(size=(1, 1, 1)) * 1, header=header)
224
247
  hdul = fits.HDUList([hdu])
225
248
  task.write(
@@ -231,7 +254,9 @@ def write_l1_task_no_data(request, recipe_run_id, tmp_path, complete_common_head
231
254
  encoder=fits_hdulist_encoder,
232
255
  )
233
256
  task.constants._update(asdict(FakeConstantDb()))
234
- yield task, header, fried_parameter
257
+ fried_parameter = request.param["ATMOS_R0"]
258
+ oob_shift = request.param.get("OOBSHIFT")
259
+ yield task, header, fried_parameter, oob_shift
235
260
  task._purge()
236
261
 
237
262
 
@@ -650,7 +675,7 @@ def test_check_r0_ao_lock(write_l1_task_no_data):
650
675
  :When: writing, check if the AO lock is on
651
676
  :Then: write the r0 value if AO lock on, don't write if AO lock off
652
677
  """
653
- task, header, r0 = write_l1_task_no_data
678
+ task, header, r0, _ = write_l1_task_no_data
654
679
  header_after_check = task.remove_invalid_r0_values(header=header)
655
680
  if header.get("AO_LOCK"):
656
681
  assert header_after_check["ATMOS_R0"] == header["ATMOS_R0"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dkist-processing-common
3
- Version: 10.8.2
3
+ Version: 10.8.3
4
4
  Summary: Common task classes used by the DKIST science data processing pipelines
5
5
  Author-email: NSO / AURA <dkistdc@nso.edu>
6
6
  License: BSD-3-Clause
@@ -35,6 +35,7 @@ dkist_processing_common/models/__init__.py
35
35
  dkist_processing_common/models/constants.py
36
36
  dkist_processing_common/models/fits_access.py
37
37
  dkist_processing_common/models/flower_pot.py
38
+ dkist_processing_common/models/fried_parameter.py
38
39
  dkist_processing_common/models/graphql.py
39
40
  dkist_processing_common/models/message.py
40
41
  dkist_processing_common/models/message_queue_binding.py
@@ -91,6 +92,7 @@ dkist_processing_common/tests/test_constants.py
91
92
  dkist_processing_common/tests/test_cs_step.py
92
93
  dkist_processing_common/tests/test_fits_access.py
93
94
  dkist_processing_common/tests/test_flower_pot.py
95
+ dkist_processing_common/tests/test_fried_parameter.py
94
96
  dkist_processing_common/tests/test_input_dataset.py
95
97
  dkist_processing_common/tests/test_interservice_bus.py
96
98
  dkist_processing_common/tests/test_interservice_bus_mixin.py