dkist-processing-common 12.10.0__tar.gz → 13.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/CHANGELOG.rst +17 -0
  2. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/PKG-INFO +1 -1
  3. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/task.py +1 -1
  4. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/time.py +103 -50
  5. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_stems.py +118 -9
  6. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common.egg-info/PKG-INFO +1 -1
  7. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/.gitignore +0 -0
  8. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/.pre-commit-config.yaml +0 -0
  9. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/.readthedocs.yml +0 -0
  10. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/.snyk +0 -0
  11. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/README.rst +0 -0
  12. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/bitbucket-pipelines.yml +0 -0
  13. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/changelog/.gitempty +0 -0
  14. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/__init__.py +0 -0
  15. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/_util/__init__.py +0 -0
  16. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/_util/constants.py +0 -0
  17. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/_util/graphql.py +0 -0
  18. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/_util/scratch.py +0 -0
  19. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/_util/tags.py +0 -0
  20. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/codecs/__init__.py +0 -0
  21. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/codecs/array.py +0 -0
  22. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/codecs/asdf.py +0 -0
  23. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/codecs/basemodel.py +0 -0
  24. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/codecs/bytes.py +0 -0
  25. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/codecs/fits.py +0 -0
  26. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/codecs/iobase.py +0 -0
  27. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/codecs/json.py +0 -0
  28. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/codecs/path.py +0 -0
  29. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/codecs/quality.py +0 -0
  30. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/codecs/str.py +0 -0
  31. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/config.py +0 -0
  32. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/fonts/Lato-Regular.ttf +0 -0
  33. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/fonts/__init__.py +0 -0
  34. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/manual.py +0 -0
  35. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/__init__.py +0 -0
  36. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/constants.py +0 -0
  37. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/dkist_location.py +0 -0
  38. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/extras.py +0 -0
  39. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/fits_access.py +0 -0
  40. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/flower_pot.py +0 -0
  41. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/fried_parameter.py +0 -0
  42. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/graphql.py +0 -0
  43. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/input_dataset.py +0 -0
  44. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/message.py +0 -0
  45. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/message_queue_binding.py +0 -0
  46. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/metric_code.py +0 -0
  47. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/parameters.py +0 -0
  48. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/quality.py +0 -0
  49. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/tags.py +0 -0
  50. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/task_name.py +0 -0
  51. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/telemetry.py +0 -0
  52. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/models/wavelength.py +0 -0
  53. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/__init__.py +0 -0
  54. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/average_bud.py +0 -0
  55. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/cs_step.py +0 -0
  56. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/dsps_repeat.py +0 -0
  57. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/experiment_id_bud.py +0 -0
  58. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/id_bud.py +0 -0
  59. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/l0_fits_access.py +0 -0
  60. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/l1_fits_access.py +0 -0
  61. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/lookup_bud.py +0 -0
  62. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/near_bud.py +0 -0
  63. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/observing_program_id_bud.py +0 -0
  64. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/proposal_id_bud.py +0 -0
  65. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/quality.py +0 -0
  66. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/retarder.py +0 -0
  67. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/single_value_single_key_flower.py +0 -0
  68. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/unique_bud.py +0 -0
  69. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/parsers/wavelength.py +0 -0
  70. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/__init__.py +0 -0
  71. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/assemble_movie.py +0 -0
  72. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/base.py +0 -0
  73. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/l1_output_data.py +0 -0
  74. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/mixin/__init__.py +0 -0
  75. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/mixin/globus.py +0 -0
  76. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/mixin/interservice_bus.py +0 -0
  77. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/mixin/metadata_store.py +0 -0
  78. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/mixin/object_store.py +0 -0
  79. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/mixin/quality/__init__.py +0 -0
  80. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/mixin/quality/_base.py +0 -0
  81. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/mixin/quality/_metrics.py +0 -0
  82. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/output_data_base.py +0 -0
  83. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/parse_l0_input_data.py +0 -0
  84. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/quality_metrics.py +0 -0
  85. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/teardown.py +0 -0
  86. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/transfer_input_data.py +0 -0
  87. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/trial_catalog.py +0 -0
  88. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/trial_output_data.py +0 -0
  89. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/write_extra.py +0 -0
  90. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/write_l1.py +0 -0
  91. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tasks/write_l1_base.py +0 -0
  92. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/__init__.py +0 -0
  93. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/conftest.py +0 -0
  94. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/mock_metadata_store.py +0 -0
  95. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_assemble_movie.py +0 -0
  96. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_assemble_quality.py +0 -0
  97. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_base.py +0 -0
  98. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_codecs.py +0 -0
  99. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_constants.py +0 -0
  100. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_construct_dataset_extras.py +0 -0
  101. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_cs_step.py +0 -0
  102. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_dkist_location.py +0 -0
  103. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_fits_access.py +0 -0
  104. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_flower_pot.py +0 -0
  105. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_fried_parameter.py +0 -0
  106. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_input_dataset.py +0 -0
  107. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_interservice_bus.py +0 -0
  108. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_interservice_bus_mixin.py +0 -0
  109. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_manual_processing.py +0 -0
  110. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_output_data_base.py +0 -0
  111. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_parameters.py +0 -0
  112. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_parse_l0_input_data.py +0 -0
  113. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_publish_catalog_messages.py +0 -0
  114. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_quality.py +0 -0
  115. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_quality_mixin.py +0 -0
  116. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_scratch.py +0 -0
  117. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_submit_dataset_metadata.py +0 -0
  118. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_tags.py +0 -0
  119. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_task_name.py +0 -0
  120. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_task_parsing.py +0 -0
  121. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_teardown.py +0 -0
  122. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_transfer_input_data.py +0 -0
  123. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_transfer_l1_output_data.py +0 -0
  124. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_trial_catalog.py +0 -0
  125. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_trial_output_data.py +0 -0
  126. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_workflow_task_base.py +0 -0
  127. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common/tests/test_write_l1.py +0 -0
  128. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common.egg-info/SOURCES.txt +0 -0
  129. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common.egg-info/dependency_links.txt +0 -0
  130. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common.egg-info/requires.txt +0 -0
  131. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/dkist_processing_common.egg-info/top_level.txt +0 -0
  132. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/docs/Makefile +0 -0
  133. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/docs/changelog.rst +0 -0
  134. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/docs/conf.py +0 -0
  135. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/docs/index.rst +0 -0
  136. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/docs/landing_page.rst +0 -0
  137. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/docs/make.bat +0 -0
  138. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/docs/requirements.txt +0 -0
  139. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/licenses/LICENSE.rst +0 -0
  140. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/pyproject.toml +0 -0
  141. {dkist_processing_common-12.10.0 → dkist_processing_common-13.0.0}/setup.cfg +0 -0
@@ -1,3 +1,20 @@
1
+ v13.0.0 (2026-04-03)
2
+ ====================
3
+
4
+ Bugfixes
5
+ --------
6
+
7
+ - Update `*CadenceBuds` to correctly account for polarimetric data. Previously, the potentially different cadence
8
+ between modulation states was not considered when computing the cadence of L0 observations. (`#310 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/310>`__)
9
+
10
+
11
+ Misc
12
+ ----
13
+
14
+ - *All* children of `L0FitsAccess` are now required to define the `modulator_state` attribute. If an instrument is not
15
+ polarimetric, setting this attribute to -1 is recommended. (`#310 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/310>`__)
16
+
17
+
1
18
  v12.10.0 (2026-04-02)
2
19
  =====================
3
20
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dkist-processing-common
3
- Version: 12.10.0
3
+ Version: 13.0.0
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
@@ -13,7 +13,7 @@ from dkist_processing_common.parsers.single_value_single_key_flower import (
13
13
  )
14
14
 
15
15
 
16
- def passthrough_header_ip_task(fits_obj: Type[FitsAccessBase]) -> str:
16
+ def passthrough_header_ip_task(fits_obj: FitsAccessBase) -> str:
17
17
  """
18
18
  Simply read the IP task directly from the header.
19
19
 
@@ -1,8 +1,10 @@
1
1
  """Time parser."""
2
2
 
3
+ from collections import defaultdict
3
4
  from datetime import datetime
4
5
  from datetime import timezone
5
6
  from enum import StrEnum
7
+ from typing import Any
6
8
  from typing import Callable
7
9
  from typing import Type
8
10
 
@@ -110,83 +112,134 @@ class TaskDatetimeBudBase(ListStem):
110
112
  return tuple(sorted(self.value_list))
111
113
 
112
114
 
113
- class CadenceBudBase(TaskDatetimeBudBase):
114
- """Base class for all Cadence Buds."""
115
+ class CadenceBudBase(ListStem):
116
+ """
117
+ Base class for all Cadence Buds.
118
+
119
+ Despite subclassing `ListStem`, this class does not use the `list` storage provided by that class. Instead, a dictionary
120
+ mapping modulation state to a list of times is used, which allows us to compute cadence values properly for polarimetric
121
+ data.
122
+
123
+ Two functions must be provided: one for reducing all timestamps for a given modstate, and one for reducing the individual
124
+ modstate values into the final cadence value. In other words, we first compute the requested "cadence" value individually
125
+ for each modstate, and then combine the value for all mostates into a single value that will be used for the constant.
126
+ """
115
127
 
116
- def __init__(self, constant_name: str):
128
+ def __init__(
129
+ self,
130
+ constant_name: str,
131
+ diff_reducing_function: Callable[[np.ndarray], np.floating[Any]],
132
+ modstate_reducing_function: Callable[[list[np.floating[Any]]], np.floating[Any]],
133
+ ):
117
134
  super().__init__(
118
135
  stem_name=constant_name,
119
- metadata_key=MetadataKey.time_obs,
120
- ip_task_types=TaskName.observe,
121
136
  )
137
+ self.header_parsing_function = passthrough_header_ip_task
138
+ self.time_objs_metadata_key = MetadataKey.time_obs.name
139
+ self.ip_task_type = TaskName.observe.value.casefold()
140
+ self.mapping = defaultdict(list)
141
+ self.diff_reduce_fcn = diff_reducing_function
142
+ self.mod_reduce_fcn = modstate_reducing_function
143
+
144
+ def setter(self, fits_obj: L0FitsAccess) -> str | Type[SpilledDirt]:
145
+ """
146
+ Ingest a single `FitsAccess` object.
122
147
 
148
+ The object's timestamp is appended to the list of values associated with the object's modstate.
149
+ """
150
+ if self.header_parsing_function(fits_obj).casefold() == self.ip_task_type:
151
+ modstate = fits_obj.modulator_state
123
152
 
124
- class AverageCadenceBud(CadenceBudBase):
125
- """Class for the average cadence Bud."""
153
+ timestamp = (
154
+ datetime.fromisoformat(getattr(fits_obj, self.time_objs_metadata_key))
155
+ .replace(tzinfo=timezone.utc)
156
+ .timestamp()
157
+ )
158
+ self.mapping[modstate].append(timestamp)
126
159
 
127
- def __init__(self):
128
- super().__init__(constant_name=BudName.average_cadence)
160
+ # Return values are important here; We have to return *something* if we're recording data we want to use.
161
+ # This string is that.
162
+ return "not used"
163
+
164
+ # And we have to return `SpilledDirt` if we didn't ingest this fits object
165
+ return SpilledDirt
129
166
 
130
- def getter(self) -> np.float64:
167
+ def getter(self) -> np.floating[Any]:
131
168
  """
132
- Return the mean cadence between frames.
169
+ Compute the cadence logic defined by the diff and modstate reducing functions.
133
170
 
134
- Returns
135
- -------
136
- The mean value of the cadences of the input frames
171
+ For each modstate:
172
+ #. Sort the individual timestamps
173
+ #. Compute the `~numpy.diff` of this sorted list
174
+ #. Apply the ``diff_reduce_fcn`` to this diff array
175
+
176
+ Finally, apply the ``mod_reduce_fcn`` to the above results for all modstates to produce the final answer.
137
177
  """
138
- return np.mean(np.diff(super().getter()))
178
+ per_modstate_values = [
179
+ self.diff_reduce_fcn(np.diff(sorted(time_list))) for time_list in self.mapping.values()
180
+ ]
181
+ final_value = self.mod_reduce_fcn(per_modstate_values)
182
+ return final_value
139
183
 
140
184
 
141
- class MaximumCadenceBud(CadenceBudBase):
142
- """Class for the maximum cadence bud."""
185
+ class AverageCadenceBud(CadenceBudBase):
186
+ """
187
+ Class for the average cadence Bud.
188
+
189
+ The final value is the average across modstates of the average time difference within each modstate.
190
+ """
143
191
 
144
192
  def __init__(self):
145
- super().__init__(constant_name=BudName.maximum_cadence)
193
+ super().__init__(
194
+ constant_name=BudName.average_cadence,
195
+ diff_reducing_function=np.mean,
196
+ modstate_reducing_function=np.mean,
197
+ )
146
198
 
147
- def getter(self) -> np.float64:
148
- """
149
- Return the maximum cadence between frames.
150
-
151
- Returns
152
- -------
153
- The maximum cadence between frames
154
- """
155
- return np.max(np.diff(super().getter()))
156
199
 
200
+ class MaximumCadenceBud(CadenceBudBase):
201
+ """
202
+ Class for the maximum cadence bud.
157
203
 
158
- class MinimumCadenceBud(CadenceBudBase):
159
- """Class for the minimum cadence bud."""
204
+ The final value is the max across modstates of the max time difference within each modstate.
205
+ """
160
206
 
161
207
  def __init__(self):
162
- super().__init__(constant_name=BudName.minimum_cadence)
163
-
164
- def getter(self) -> np.float64:
165
- """
166
- Return the minimum cadence between frames.
208
+ super().__init__(
209
+ constant_name=BudName.maximum_cadence,
210
+ diff_reducing_function=np.max,
211
+ modstate_reducing_function=np.max,
212
+ )
167
213
 
168
- Returns
169
- -------
170
- The minimum cadence between frames
171
- """
172
- return np.min(np.diff(super().getter()))
173
214
 
215
+ class MinimumCadenceBud(CadenceBudBase):
216
+ """
217
+ Class for the minimum cadence bud.
174
218
 
175
- class VarianceCadenceBud(CadenceBudBase):
176
- """Class for the variance cadence Bud."""
219
+ The final value is the minimum across modstates of the minimum time difference within each modstate.
220
+ """
177
221
 
178
222
  def __init__(self):
179
- super().__init__(constant_name=BudName.variance_cadence)
223
+ super().__init__(
224
+ constant_name=BudName.minimum_cadence,
225
+ diff_reducing_function=np.min,
226
+ modstate_reducing_function=np.min,
227
+ )
180
228
 
181
- def getter(self) -> np.float64:
182
- """
183
- Return the cadence variance between frames.
184
229
 
185
- Returns
186
- -------
187
- Return the variance of the cadences over the input frames
188
- """
189
- return np.var(np.diff(super().getter()))
230
+ class VarianceCadenceBud(CadenceBudBase):
231
+ """
232
+ Class for the variance cadence Bud.
233
+
234
+ The final value is the average across modstates of the variance time difference within each modstate.
235
+ """
236
+
237
+ def __init__(self):
238
+ super().__init__(
239
+ constant_name=BudName.variance_cadence,
240
+ diff_reducing_function=np.var,
241
+ modstate_reducing_function=np.mean,
242
+ )
190
243
 
191
244
 
192
245
  class TaskDateBeginBud(TaskDatetimeBudBase):
@@ -1,6 +1,7 @@
1
1
  import collections
2
2
  from enum import StrEnum
3
3
  from itertools import chain
4
+ from typing import Generator
4
5
 
5
6
  import pytest
6
7
  from astropy.io import fits
@@ -20,6 +21,7 @@ from dkist_processing_common.parsers.dsps_repeat import TotalDspsRepeatsBud
20
21
  from dkist_processing_common.parsers.experiment_id_bud import ContributingExperimentIdsBud
21
22
  from dkist_processing_common.parsers.experiment_id_bud import ExperimentIdBud
22
23
  from dkist_processing_common.parsers.id_bud import TaskContributingIdsBud
24
+ from dkist_processing_common.parsers.l0_fits_access import AbstractProperty
23
25
  from dkist_processing_common.parsers.lookup_bud import TaskTimeLookupBud
24
26
  from dkist_processing_common.parsers.lookup_bud import TimeLookupBud
25
27
  from dkist_processing_common.parsers.near_bud import NearFloatBud
@@ -75,9 +77,11 @@ class FitsReaderMetadataKey(StrEnum):
75
77
  gos_polarizer_status = "GOSPOL"
76
78
  wavelength = "LINEWAV"
77
79
  roundable_time = "RTIME"
80
+ modulator_state = "MODSTATE"
78
81
 
79
82
 
80
83
  class FitsReader(FitsAccessBase):
84
+
81
85
  def __init__(self, hdu, name):
82
86
  super().__init__(hdu, name)
83
87
  self.thing_id: int = self.header.get(FitsReaderMetadataKey.thing_id)
@@ -112,10 +116,17 @@ class FitsReader(FitsAccessBase):
112
116
  self.gos_polarizer_status: str = self.header.get(FitsReaderMetadataKey.gos_polarizer_status)
113
117
  self.wavelength: str = self.header.get(FitsReaderMetadataKey.wavelength)
114
118
  self.roundable_time: float = self.header.get(FitsReaderMetadataKey.roundable_time, 0.0)
119
+ self.modulator_state: int = -1
120
+
121
+
122
+ class ModFitsReader(FitsReader):
123
+ def __init__(self, hdu, name):
124
+ super().__init__(hdu, name)
125
+ self.modulator_state: int = self.header.get(FitsReaderMetadataKey.modulator_state)
115
126
 
116
127
 
117
128
  @pytest.fixture()
118
- def basic_header_objs():
129
+ def basic_header_objs() -> Generator[FitsReader, None, None]:
119
130
  header_dict = {
120
131
  "thing0": fits.header.Header(
121
132
  {
@@ -223,7 +234,7 @@ def basic_header_objs():
223
234
 
224
235
 
225
236
  @pytest.fixture
226
- def task_with_gains_header_objs():
237
+ def task_with_gains_header_objs() -> Generator[FitsReader, None, None]:
227
238
  header_dict = {
228
239
  "lamp_gain": fits.header.Header({"DKIST004": "gain", "GOSLVL3": "lamp", "GOSLAMP": "on"}),
229
240
  "solar_gain": fits.header.Header({"DKIST004": "gain", "GOSLVL3": "clear"}),
@@ -238,7 +249,7 @@ def retarder_name():
238
249
 
239
250
 
240
251
  @pytest.fixture
241
- def task_with_polcal_header_objs(retarder_name):
252
+ def task_with_polcal_header_objs(retarder_name) -> Generator[FitsReader, None, None]:
242
253
  header_dict = {
243
254
  "polcal_dark": fits.header.Header(
244
255
  {"DKIST004": "polcal", "GOSLVL0": "DarkShutter", "GOSPOL": "clear", "GOSRET": "clear"}
@@ -254,7 +265,7 @@ def task_with_polcal_header_objs(retarder_name):
254
265
 
255
266
 
256
267
  @pytest.fixture()
257
- def bad_header_objs():
268
+ def bad_header_objs() -> Generator[FitsReader, None, None]:
258
269
  bad_headers = {
259
270
  "thing0": fits.header.Header(
260
271
  {
@@ -309,7 +320,7 @@ def bad_header_objs():
309
320
 
310
321
 
311
322
  @pytest.fixture
312
- def bad_polcal_header_objs():
323
+ def bad_polcal_header_objs() -> Generator[FitsReader, None, None]:
313
324
  # I.e., GOSRET has multiple values
314
325
  header_dict = {
315
326
  "thing1": fits.header.Header({"DKIST004": "polcal", "GOSRET": "clear"}),
@@ -319,6 +330,52 @@ def bad_polcal_header_objs():
319
330
  return (FitsReader.from_header(header, name=path) for path, header in header_dict.items())
320
331
 
321
332
 
333
+ @pytest.fixture
334
+ def modulated_cadence_header_objs() -> Generator[FitsReader, None, None]:
335
+ header_dict = {
336
+ "thing1": fits.Header(
337
+ {"MODSTATE": 1, "DATE-OBS": "2000-01-01T00:00:00", "DKIST004": "observe"}
338
+ ),
339
+ "thing2": fits.Header(
340
+ {"MODSTATE": 1, "DATE-OBS": "2000-01-01T00:00:10", "DKIST004": "observe"}
341
+ ),
342
+ "thing3": fits.Header(
343
+ {"MODSTATE": 1, "DATE-OBS": "2000-01-01T00:00:22", "DKIST004": "observe"}
344
+ ),
345
+ "thing4": fits.Header(
346
+ {"MODSTATE": 2, "DATE-OBS": "2000-01-02T00:00:00", "DKIST004": "observe"}
347
+ ),
348
+ "thing5": fits.Header(
349
+ {"MODSTATE": 2, "DATE-OBS": "2000-01-02T00:00:05", "DKIST004": "observe"}
350
+ ),
351
+ "thing6": fits.Header(
352
+ {"MODSTATE": 2, "DATE-OBS": "2000-01-02T00:00:11", "DKIST004": "observe"}
353
+ ),
354
+ "thing7": fits.Header(
355
+ {"MODSTATE": 3, "DATE-OBS": "2000-01-03T00:00:00", "DKIST004": "observe"}
356
+ ),
357
+ "thing8": fits.Header(
358
+ {"MODSTATE": 3, "DATE-OBS": "2000-01-03T00:00:03", "DKIST004": "observe"}
359
+ ),
360
+ "thing9": fits.Header(
361
+ {"MODSTATE": 3, "DATE-OBS": "2000-01-03T00:00:07", "DKIST004": "observe"}
362
+ ),
363
+ "thing10": fits.Header(
364
+ {"MODSTATE": 3, "DATE-OBS": "2000-12-31T00:00:00", "DKIST004": "gain"}
365
+ ),
366
+ }
367
+ return (ModFitsReader.from_header(header, name=path) for path, header in header_dict.items())
368
+
369
+
370
+ @pytest.fixture
371
+ def non_observe_header_objs() -> Generator[FitsReader, None, None]:
372
+ header_dict = {
373
+ "thing1": fits.Header({"DKIST004": "gain"}),
374
+ "thing2": fits.Header({"DKIST004": "dark"}),
375
+ }
376
+ return (ModFitsReader.from_header(header, name=path) for path, header in header_dict.items())
377
+
378
+
322
379
  def test_unique_bud(basic_header_objs):
323
380
  """
324
381
  Given: A set of headers with a constant value header key
@@ -874,7 +931,7 @@ def test_dsps_flower(basic_header_objs):
874
931
  assert petals[1].keys == ["thing3"]
875
932
 
876
933
 
877
- def test_average_cadence_bud(basic_header_objs):
934
+ def test_average_cadence_bud(basic_header_objs, modulated_cadence_header_objs):
878
935
  """
879
936
  Given: A set of filepaths and associated headers with DATE-OBS keywords
880
937
  When: Ingesting with the AverageCadenceBud
@@ -889,8 +946,15 @@ def test_average_cadence_bud(basic_header_objs):
889
946
  # Because there are 3 observe frames in `basic_header_objs` spaced 1, and 2 seconds apart.
890
947
  assert bud_obj.bud.value == 1.5
891
948
 
949
+ pol_bud_obj = AverageCadenceBud()
950
+ for fo in modulated_cadence_header_objs:
951
+ key = fo.name
952
+ pol_bud_obj.update(key, fo)
953
+
954
+ assert pol_bud_obj.bud.value == 20 / 3
955
+
892
956
 
893
- def test_max_cadence_bud(basic_header_objs):
957
+ def test_max_cadence_bud(basic_header_objs, modulated_cadence_header_objs):
894
958
  """
895
959
  Given: A set of filepaths and associated headers with DATE-OBS keywords
896
960
  When: Ingesting with the MaxCadenceBud
@@ -905,8 +969,15 @@ def test_max_cadence_bud(basic_header_objs):
905
969
  # Because there are 3 observe frames in `basic_header_objs` spaced 1, and 2 seconds apart.
906
970
  assert bud_obj.bud.value == 2
907
971
 
972
+ pol_bud_obj = MaximumCadenceBud()
973
+ for fo in modulated_cadence_header_objs:
974
+ key = fo.name
975
+ pol_bud_obj.update(key, fo)
976
+
977
+ assert pol_bud_obj.bud.value == 12
978
+
908
979
 
909
- def test_minimum_cadence_bud(basic_header_objs):
980
+ def test_minimum_cadence_bud(basic_header_objs, modulated_cadence_header_objs):
910
981
  """
911
982
  Given: A set of filepaths and associated headers with DATE-OBS keywords
912
983
  When: Ingesting with the MinimumCadenceBud
@@ -921,8 +992,15 @@ def test_minimum_cadence_bud(basic_header_objs):
921
992
  # Because there are 3 observe frames in `basic_header_objs` spaced 1, and 2 seconds apart.
922
993
  assert bud_obj.bud.value == 1
923
994
 
995
+ pol_bud_obj = MinimumCadenceBud()
996
+ for fo in modulated_cadence_header_objs:
997
+ key = fo.name
998
+ pol_bud_obj.update(key, fo)
999
+
1000
+ assert pol_bud_obj.bud.value == 3
924
1001
 
925
- def test_variance_cadence_bud(basic_header_objs):
1002
+
1003
+ def test_variance_cadence_bud(basic_header_objs, modulated_cadence_header_objs):
926
1004
  """
927
1005
  Given: A set of filepaths and associated headers with DATE-OBS keywords
928
1006
  When: Ingesting with the VarianceCadenceBud
@@ -937,6 +1015,37 @@ def test_variance_cadence_bud(basic_header_objs):
937
1015
  # Because there are 3 observe frames in `basic_header_objs` spaced 1, and 2 seconds apart.
938
1016
  assert bud_obj.bud.value == 0.25
939
1017
 
1018
+ pol_bud_obj = VarianceCadenceBud()
1019
+ for fo in modulated_cadence_header_objs:
1020
+ key = fo.name
1021
+ pol_bud_obj.update(key, fo)
1022
+
1023
+ assert pol_bud_obj.bud.value == 0.5
1024
+
1025
+
1026
+ def test_cadence_buds_no_observes(non_observe_header_objs):
1027
+ """
1028
+ Given: A set of filepaths and headers that contain no "observe" frames
1029
+ When: Ingesting these headers with the *CadenceBuds and checking if there are values to be picked
1030
+ Then: None of the Buds "can_be_picked"
1031
+ """
1032
+ avg_bud = AverageCadenceBud()
1033
+ min_bud = MinimumCadenceBud()
1034
+ max_bud = MaximumCadenceBud()
1035
+ var_bud = VarianceCadenceBud()
1036
+
1037
+ for fo in non_observe_header_objs:
1038
+ key = fo.name
1039
+ avg_bud.update(key, fo)
1040
+ min_bud.update(key, fo)
1041
+ max_bud.update(key, fo)
1042
+ var_bud.update(key, fo)
1043
+
1044
+ assert not avg_bud.can_be_picked
1045
+ assert not min_bud.can_be_picked
1046
+ assert not max_bud.can_be_picked
1047
+ assert not var_bud.can_be_picked
1048
+
940
1049
 
941
1050
  def test_task_date_begin_bud(basic_header_objs):
942
1051
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dkist-processing-common
3
- Version: 12.10.0
3
+ Version: 13.0.0
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