dkist-processing-common 13.0.0rc2__tar.gz → 13.0.1__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 (146) hide show
  1. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/CHANGELOG.rst +37 -0
  2. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/PKG-INFO +2 -2
  3. dkist_processing_common-13.0.1/dkist_processing_common/models/quality.py +351 -0
  4. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/l1_output_data.py +19 -2
  5. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/mixin/quality/_base.py +0 -20
  6. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/mixin/quality/_metrics.py +2 -161
  7. dkist_processing_common-13.0.1/dkist_processing_common/tasks/quality_metrics.py +503 -0
  8. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_assemble_quality.py +158 -49
  9. dkist_processing_common-13.0.1/dkist_processing_common/tests/test_quality.py +706 -0
  10. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_quality_mixin.py +97 -266
  11. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common.egg-info/PKG-INFO +2 -2
  12. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common.egg-info/SOURCES.txt +0 -2
  13. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common.egg-info/requires.txt +1 -1
  14. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/pyproject.toml +1 -1
  15. dkist_processing_common-13.0.0rc2/changelog/310.bugfix.rst +0 -2
  16. dkist_processing_common-13.0.0rc2/changelog/310.misc.rst +0 -2
  17. dkist_processing_common-13.0.0rc2/dkist_processing_common/models/quality.py +0 -122
  18. dkist_processing_common-13.0.0rc2/dkist_processing_common/tasks/quality_metrics.py +0 -316
  19. dkist_processing_common-13.0.0rc2/dkist_processing_common/tests/test_quality.py +0 -292
  20. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/.gitignore +0 -0
  21. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/.pre-commit-config.yaml +0 -0
  22. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/.readthedocs.yml +0 -0
  23. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/.snyk +0 -0
  24. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/README.rst +0 -0
  25. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/bitbucket-pipelines.yml +0 -0
  26. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/changelog/.gitempty +0 -0
  27. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/__init__.py +0 -0
  28. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/_util/__init__.py +0 -0
  29. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/_util/constants.py +0 -0
  30. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/_util/graphql.py +0 -0
  31. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/_util/scratch.py +0 -0
  32. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/_util/tags.py +0 -0
  33. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/codecs/__init__.py +0 -0
  34. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/codecs/array.py +0 -0
  35. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/codecs/asdf.py +0 -0
  36. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/codecs/basemodel.py +0 -0
  37. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/codecs/bytes.py +0 -0
  38. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/codecs/fits.py +0 -0
  39. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/codecs/iobase.py +0 -0
  40. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/codecs/json.py +0 -0
  41. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/codecs/path.py +0 -0
  42. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/codecs/quality.py +0 -0
  43. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/codecs/str.py +0 -0
  44. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/config.py +0 -0
  45. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/fonts/Lato-Regular.ttf +0 -0
  46. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/fonts/__init__.py +0 -0
  47. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/manual.py +0 -0
  48. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/__init__.py +0 -0
  49. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/constants.py +0 -0
  50. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/dkist_location.py +0 -0
  51. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/extras.py +0 -0
  52. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/fits_access.py +0 -0
  53. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/flower_pot.py +0 -0
  54. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/fried_parameter.py +0 -0
  55. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/graphql.py +0 -0
  56. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/input_dataset.py +0 -0
  57. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/message.py +0 -0
  58. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/message_queue_binding.py +0 -0
  59. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/metric_code.py +0 -0
  60. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/parameters.py +0 -0
  61. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/tags.py +0 -0
  62. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/task_name.py +0 -0
  63. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/telemetry.py +0 -0
  64. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/models/wavelength.py +0 -0
  65. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/__init__.py +0 -0
  66. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/average_bud.py +0 -0
  67. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/cs_step.py +0 -0
  68. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/dsps_repeat.py +0 -0
  69. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/experiment_id_bud.py +0 -0
  70. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/id_bud.py +0 -0
  71. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/l0_fits_access.py +0 -0
  72. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/l1_fits_access.py +0 -0
  73. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/lookup_bud.py +0 -0
  74. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/near_bud.py +0 -0
  75. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/observing_program_id_bud.py +0 -0
  76. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/proposal_id_bud.py +0 -0
  77. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/quality.py +0 -0
  78. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/retarder.py +0 -0
  79. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/single_value_single_key_flower.py +0 -0
  80. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/task.py +0 -0
  81. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/time.py +0 -0
  82. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/unique_bud.py +0 -0
  83. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/parsers/wavelength.py +0 -0
  84. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/__init__.py +0 -0
  85. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/assemble_movie.py +0 -0
  86. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/base.py +0 -0
  87. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/mixin/__init__.py +0 -0
  88. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/mixin/globus.py +0 -0
  89. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/mixin/interservice_bus.py +0 -0
  90. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/mixin/metadata_store.py +0 -0
  91. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/mixin/object_store.py +0 -0
  92. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/mixin/quality/__init__.py +0 -0
  93. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/output_data_base.py +0 -0
  94. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/parse_l0_input_data.py +0 -0
  95. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/teardown.py +0 -0
  96. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/transfer_input_data.py +0 -0
  97. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/trial_catalog.py +0 -0
  98. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/trial_output_data.py +0 -0
  99. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/write_extra.py +0 -0
  100. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/write_l1.py +0 -0
  101. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tasks/write_l1_base.py +0 -0
  102. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/__init__.py +0 -0
  103. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/conftest.py +0 -0
  104. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/mock_metadata_store.py +0 -0
  105. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_assemble_movie.py +0 -0
  106. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_base.py +0 -0
  107. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_codecs.py +0 -0
  108. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_constants.py +0 -0
  109. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_construct_dataset_extras.py +0 -0
  110. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_cs_step.py +0 -0
  111. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_dkist_location.py +0 -0
  112. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_fits_access.py +0 -0
  113. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_flower_pot.py +0 -0
  114. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_fried_parameter.py +0 -0
  115. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_input_dataset.py +0 -0
  116. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_interservice_bus.py +0 -0
  117. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_interservice_bus_mixin.py +0 -0
  118. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_manual_processing.py +0 -0
  119. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_output_data_base.py +0 -0
  120. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_parameters.py +0 -0
  121. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_parse_l0_input_data.py +0 -0
  122. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_publish_catalog_messages.py +0 -0
  123. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_scratch.py +0 -0
  124. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_stems.py +0 -0
  125. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_submit_dataset_metadata.py +0 -0
  126. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_tags.py +0 -0
  127. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_task_name.py +0 -0
  128. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_task_parsing.py +0 -0
  129. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_teardown.py +0 -0
  130. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_transfer_input_data.py +0 -0
  131. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_transfer_l1_output_data.py +0 -0
  132. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_trial_catalog.py +0 -0
  133. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_trial_output_data.py +0 -0
  134. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_workflow_task_base.py +0 -0
  135. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common/tests/test_write_l1.py +0 -0
  136. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common.egg-info/dependency_links.txt +0 -0
  137. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/dkist_processing_common.egg-info/top_level.txt +0 -0
  138. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/docs/Makefile +0 -0
  139. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/docs/changelog.rst +0 -0
  140. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/docs/conf.py +0 -0
  141. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/docs/index.rst +0 -0
  142. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/docs/landing_page.rst +0 -0
  143. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/docs/make.bat +0 -0
  144. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/docs/requirements.txt +0 -0
  145. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/licenses/LICENSE.rst +0 -0
  146. {dkist_processing_common-13.0.0rc2 → dkist_processing_common-13.0.1}/setup.cfg +0 -0
@@ -1,3 +1,40 @@
1
+ v13.0.1 (2026-04-07)
2
+ ====================
3
+
4
+ Misc
5
+ ----
6
+
7
+ - Use dkist-fits-specifications v4.24.0, which changes some Cryo-NIRSP dataset extra keys to be not instrument required. (`#324 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/324>`__)
8
+
9
+
10
+ v13.0.0 (2026-04-03)
11
+ ====================
12
+
13
+ Bugfixes
14
+ --------
15
+
16
+ - Update `*CadenceBuds` to correctly account for polarimetric data. Previously, the potentially different cadence
17
+ between modulation states was not considered when computing the cadence of L0 observations. (`#310 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/310>`__)
18
+
19
+
20
+ Misc
21
+ ----
22
+
23
+ - *All* children of `L0FitsAccess` are now required to define the `modulator_state` attribute. If an instrument is not
24
+ polarimetric, setting this attribute to -1 is recommended. (`#310 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/310>`__)
25
+
26
+
27
+ v12.10.0 (2026-04-02)
28
+ =====================
29
+
30
+ Features
31
+ --------
32
+
33
+ - Added capabilities to simplify the way that quality metrics get produced.
34
+ The central feature is the `QualityMetric` `BaseModel` which can be used to fully define any quality metric. (`#313 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/313>`__)
35
+ - Simplify the production of L0 quality metrics. (`#313 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/313>`__)
36
+
37
+
1
38
  v12.9.0 (2026-04-01)
2
39
  ====================
3
40
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dkist-processing-common
3
- Version: 13.0.0rc2
3
+ Version: 13.0.1
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
@@ -15,7 +15,7 @@ Requires-Python: >=3.13
15
15
  Description-Content-Type: text/x-rst
16
16
  Requires-Dist: asdf<4.0.0,>=3.5.0
17
17
  Requires-Dist: astropy>=7.0.0
18
- Requires-Dist: dkist-fits-specifications<5.0,>=4.23.0
18
+ Requires-Dist: dkist-fits-specifications<5.0,>=4.24.0
19
19
  Requires-Dist: dkist-header-validator<6.0,>=5.3.0
20
20
  Requires-Dist: dkist-processing-core==7.1.0
21
21
  Requires-Dist: dkist-processing-pac<4.0,>=3.1
@@ -0,0 +1,351 @@
1
+ """Support classes used to create a quality report."""
2
+
3
+ from datetime import datetime
4
+ from typing import Annotated
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel
8
+ from pydantic import BeforeValidator
9
+ from pydantic import ConfigDict
10
+ from pydantic import Field
11
+ from pydantic import FiniteFloat
12
+ from pydantic import PlainSerializer
13
+ from pydantic import field_validator
14
+ from pydantic import model_serializer
15
+ from pydantic import model_validator
16
+ from pydantic.alias_generators import to_camel
17
+ from pydantic_core.core_schema import SerializationInfo
18
+ from pydantic_core.core_schema import ValidationInfo
19
+
20
+ ###########################################################
21
+ # Old Quality Models #
22
+ # #
23
+ # These will gradually be replaced by #
24
+ # New Quality Models (below) #
25
+ ###########################################################
26
+
27
+
28
+ class Plot2D(BaseModel):
29
+ """Support class use to hold the data for creating a 2D plot in the quality report."""
30
+
31
+ xlabel: str
32
+ ylabel: str
33
+ series_data: dict[str, list[list[Any]]]
34
+ series_name: str | None = None
35
+ ylabel_horizontal: bool = False
36
+ ylim: tuple[float, float] | None = None
37
+ plot_kwargs: dict[str, dict[str, Any]] = Field(default_factory=dict)
38
+ sort_series: bool = True
39
+
40
+
41
+ class VerticalMultiPanePlot2D(BaseModel):
42
+ """
43
+ Support class to hold a multi-pane plot with plots stacked vertically.
44
+
45
+ This type of metric is really geared towards plots that share an X axis and have no gap between them. If you just
46
+ want two separate plots it's probably better to use a list of `Plot2D` objects.
47
+ """
48
+
49
+ top_to_bottom_plot_list: list[Plot2D]
50
+ match_x_axes: bool = True
51
+ no_gap: bool = True
52
+ top_to_bottom_height_ratios: list[float] | None = None
53
+
54
+ @field_validator("top_to_bottom_height_ratios")
55
+ @classmethod
56
+ def ensure_same_number_of_height_ratios_and_plots(
57
+ cls, height_ratios: list[float] | None, info: ValidationInfo
58
+ ) -> list[float]:
59
+ """
60
+ Make sure that the number of height ratios is the same as the number of plots.
61
+
62
+ Also populates default, same-size ratios if no ratios were given.
63
+ """
64
+ try:
65
+ plot_list = info.data["top_to_bottom_plot_list"]
66
+ except KeyError:
67
+ # The plot list didn't validate for some reason. We're about to error anyway.
68
+ return [1.0]
69
+
70
+ num_plots = len(plot_list)
71
+ if height_ratios is None:
72
+ return [1.0] * num_plots
73
+
74
+ if len(height_ratios) != num_plots:
75
+ raise ValueError(
76
+ f"The number of items in `top_to_bottom_height_ratios` list ({len(height_ratios)}) is not "
77
+ f"the same as the number of plots ({num_plots})"
78
+ )
79
+
80
+ return height_ratios
81
+
82
+
83
+ class SimpleTable(BaseModel):
84
+ """Support class to hold a simple table to be inserted into the quality report."""
85
+
86
+ rows: list[list[Any]]
87
+ header_row: bool = True
88
+ header_column: bool = False
89
+
90
+
91
+ class ModulationMatrixHistograms(BaseModel):
92
+ """Support class for holding the big ol' grid of histograms that represent the modulation matrix fits."""
93
+
94
+ modmat_list: list[list[list[float]]]
95
+
96
+
97
+ class EfficiencyHistograms(BaseModel):
98
+ """Support class for holding 4 histograms that correspond to efficiencies of the 4 stokes components."""
99
+
100
+ efficiency_list: list[list[float]]
101
+
102
+
103
+ class PlotHistogram(BaseModel):
104
+ """Support class to hold 1D data for plotting a histogram."""
105
+
106
+ xlabel: str
107
+ series_data: dict[str, list[float]]
108
+ series_name: str | None = None
109
+ vertical_lines: dict[str, float] | None
110
+
111
+
112
+ class PlotRaincloud(BaseModel):
113
+ """Support class to hold data series for fancy-ass violin plots."""
114
+
115
+ xlabel: str
116
+ ylabel: str
117
+ categorical_column_name: str
118
+ distribution_column_name: str
119
+ dataframe_json: str
120
+ hue_column_name: str | None
121
+ ylabel_horizontal: bool | None
122
+
123
+
124
+ class ReportMetric(BaseModel):
125
+ """A Quality Report is made up of a list of metrics with the schema defined by this class."""
126
+
127
+ name: str
128
+ description: str
129
+ metric_code: str
130
+ facet: str | None = None
131
+ statement: str | list[str] | None = None
132
+ plot_data: Plot2D | list[Plot2D] | None = None
133
+ multi_plot_data: VerticalMultiPanePlot2D | None = None
134
+ histogram_data: PlotHistogram | list[PlotHistogram] | None = None
135
+ table_data: SimpleTable | list[SimpleTable] | None = None
136
+ modmat_data: ModulationMatrixHistograms | None = None
137
+ efficiency_data: EfficiencyHistograms | None = None
138
+ raincloud_data: PlotRaincloud | None = None
139
+ warnings: list[str] | None = None
140
+
141
+
142
+ ###########################################################
143
+ # New Quality Models #
144
+ # #
145
+ # These define the interface with dkist-quality #
146
+ # These will eventually get moved to dkist-quality #
147
+ ###########################################################
148
+
149
+
150
+ class QualityModel(BaseModel):
151
+ """BaseModel for quality data."""
152
+
153
+ model_config = ConfigDict(
154
+ # only accept snake_case field names
155
+ validate_by_name=True,
156
+ # re-validate the field if its value gets changed after creation
157
+ validate_assignment=True,
158
+ )
159
+
160
+
161
+ def datetime_or_iso_dict_input(v: Any) -> Any:
162
+ """
163
+ Extend pydantic `datetime` capability.
164
+
165
+ Adds the ability to accept an 'iso dict' of the {"iso_date": <value>} form
166
+ """
167
+ # If appropriate, extract value from {"iso_date": <value>} before passing to the normal datetime handler
168
+ if isinstance(v, dict) and "iso_date" in v:
169
+ return v["iso_date"]
170
+ return v
171
+
172
+
173
+ # NOTE: This is a temporary data type that can be eliminated once all metric conversion is complete
174
+ DatetimeIsoDict = Annotated[
175
+ # this is a datetime within the pydantic BaseModel
176
+ datetime,
177
+ # accept 'iso dict' input in addition to normal datetime inputs
178
+ BeforeValidator(datetime_or_iso_dict_input),
179
+ # always output the datetime as an 'iso dict'
180
+ PlainSerializer(lambda dt: {"iso_date": dt.isoformat()}, return_type=dict[str, str]),
181
+ ]
182
+
183
+
184
+ class TimeSeriesData(QualityModel):
185
+ """
186
+ Time Series Data.
187
+
188
+ The context, e.g. task_type or modstate, is maintained outside of this structure.
189
+ """
190
+
191
+ # by default, a tuple accepts a list as input
192
+ # the use of tuples is intentional because list mutations do not trigger validation
193
+ # the impact is that TimeSeriesData should not be instantiated until
194
+ # after the tuple input has been fully accumulated
195
+ # when `mode="json"`, the output of a tuple results in a list
196
+ # when `mode="python"`, the output of a tuple results in a tuple
197
+ x_values: tuple[DatetimeIsoDict, ...]
198
+ # NOTE: FiniteFloat will fail fast for `nan` and `inf`
199
+ y_values: tuple[FiniteFloat, ...]
200
+
201
+ @model_validator(mode="after")
202
+ def tuple_validation(self):
203
+ """Validate tuple lengths."""
204
+ axes_are_different_lengths = len(self.x_values) != len(self.y_values)
205
+ if axes_are_different_lengths:
206
+ raise ValueError(
207
+ f"Cannot store TimeSeriesData with different length axes. "
208
+ f"{len(self.x_values)=}, {len(self.y_values)=}"
209
+ )
210
+ # Can't have this AND default to empty tuple
211
+ axes_are_zero_length = not self.x_values or not self.y_values
212
+ if axes_are_zero_length:
213
+ raise ValueError(
214
+ f"Cannot store TimeSeriesData with 0 length axes. "
215
+ f"{len(self.x_values)=}, {len(self.y_values)=}"
216
+ )
217
+ return self
218
+
219
+ @model_serializer(mode="wrap")
220
+ def custom_dump(self, handler, info: SerializationInfo):
221
+ """Serialize output for QRM compatibility."""
222
+ data = handler(self)
223
+ # change structure if mode="json" for QRM compatibility
224
+ if info.mode == "json":
225
+ # dump as list[list[Any]] with DatetimeIsoDict format for x_values
226
+ return [list(data["x_values"]), list(data["y_values"])]
227
+ return data
228
+
229
+ @model_validator(mode="before")
230
+ @classmethod
231
+ def handle_json(cls, input_value: Any):
232
+ """
233
+ Transform json input from list[list[Any]] to dict[Any, Any].
234
+
235
+ the dict is expected to be dict[str, tuple[Any, ...]], which gets validated by normal BaseModel configurations.
236
+ """
237
+ if isinstance(input_value, dict):
238
+ return input_value
239
+
240
+ if (
241
+ isinstance(input_value, (list, tuple))
242
+ and len(input_value) == 2
243
+ and isinstance(input_value[0], (list, tuple))
244
+ and isinstance(input_value[1], (list, tuple))
245
+ ):
246
+ return {"x_values": input_value[0], "y_values": input_value[1]}
247
+
248
+ return input_value
249
+
250
+
251
+ class XYData(QualityModel):
252
+ """One or more time series for a single x-y plot."""
253
+
254
+ xlabel: str
255
+ ylabel: str
256
+ # the key is the name assigned to the series, e.g. within the legend
257
+ # the key cannot be None, because `null` is not a valid json key
258
+ series_data: dict[str, TimeSeriesData] = Field(default_factory=dict)
259
+ # TODO FUTURE - convert `series_name` to `legend_title` to better align with matplotlib terminology
260
+ series_name: str | None = None
261
+ ylabel_horizontal: bool | None = None
262
+ ylim: tuple[float | None, float | None] | None = None
263
+ plot_kwargs: dict[str, dict[str, Any]] = Field(default_factory=dict)
264
+ sort_series: bool | None = True
265
+
266
+
267
+ class TableData(QualityModel):
268
+ """Tabular Data."""
269
+
270
+ # by default, a tuple accepts a list as input
271
+ # the use of tuples is intentional because list mutations do not trigger validation
272
+ # the impact is that TableData should not be instantiated until
273
+ # after the tuple input has been fully accumulated
274
+ # when `mode="json"`, the output of a tuple results in a list
275
+ # when `mode="python"`, the output of a tuple results in a tuple
276
+ # Note: numeric values are represented as strings for table data
277
+ rows: tuple[tuple[str, ...], ...]
278
+ header_row: bool | None = True
279
+ header_col: bool | None = False
280
+
281
+ @model_validator(mode="after")
282
+ def tuple_validation(self):
283
+ """Validate tuple lengths."""
284
+ row_count = len(self.rows)
285
+ if row_count == 0:
286
+ raise ValueError(f"Invalid table. No rows.")
287
+ header_column_count = len(self.rows[0])
288
+ if header_column_count == 0:
289
+ raise ValueError(f"Invalid table. No columns.")
290
+ row_column_counts = [len(row) for row in self.rows[1:]]
291
+ column_count_mismatch = any(
292
+ col_count != header_column_count for col_count in row_column_counts
293
+ )
294
+ if column_count_mismatch:
295
+ raise ValueError(
296
+ f"Mismatch in column count. {header_column_count=}, {row_column_counts=}"
297
+ )
298
+ return self
299
+
300
+
301
+ class MultiPlotData(QualityModel):
302
+ """Multiple Plot Data."""
303
+
304
+ # TBD
305
+ pass
306
+
307
+
308
+ class HistogramData(QualityModel):
309
+ """Histogram Data."""
310
+
311
+ # TBD
312
+ pass
313
+
314
+
315
+ class ModMatData(QualityModel):
316
+ """Modulation Matrix Data."""
317
+
318
+ # TBD
319
+ pass
320
+
321
+
322
+ class RaincloudData(QualityModel):
323
+ """Raincloud Data."""
324
+
325
+ # TBD
326
+ pass
327
+
328
+
329
+ class EfficiencyData(QualityModel):
330
+ """Efficiency Data."""
331
+
332
+ # TBD
333
+ pass
334
+
335
+
336
+ class QualityMetric(QualityModel):
337
+ """Quality Metric."""
338
+
339
+ name: str
340
+ description: str
341
+ metric_code: str
342
+ facet: str | None = None
343
+ statement: list[str] | None = None
344
+ plot_data: list[XYData] | None = None
345
+ multi_plot_data: list[MultiPlotData] | None = None
346
+ histogram_data: list[HistogramData] | None = None
347
+ table_data: list[TableData] | None = None
348
+ modmad_data: list[ModMatData] | None = None
349
+ raincloud_data: list[RaincloudData] | None = None
350
+ efficiency_data: list[EfficiencyData] | None = None
351
+ warnings: list[str] | None = None
@@ -5,11 +5,13 @@ from abc import ABC
5
5
  from pathlib import Path
6
6
  from typing import Iterable
7
7
 
8
+ from dkist_processing_common.codecs.basemodel import basemodel_decoder
8
9
  from dkist_processing_common.codecs.quality import quality_data_encoder
9
10
  from dkist_processing_common.models.message import CatalogFrameMessage
10
11
  from dkist_processing_common.models.message import CatalogFrameMessageBody
11
12
  from dkist_processing_common.models.message import CatalogObjectMessage
12
13
  from dkist_processing_common.models.message import CatalogObjectMessageBody
14
+ from dkist_processing_common.models.quality import QualityMetric
13
15
  from dkist_processing_common.models.tags import Tag
14
16
  from dkist_processing_common.tasks.mixin.globus import GlobusMixin
15
17
  from dkist_processing_common.tasks.mixin.interservice_bus import InterserviceBusMixin
@@ -146,8 +148,13 @@ class AssembleQualityData(L1OutputDataBase, QualityMixin):
146
148
 
147
149
  def run(self):
148
150
  """Run method for the task."""
149
- with self.telemetry_span("Assembling quality data"):
150
- quality_data = self.quality_assemble_data(polcal_label_list=self.polcal_label_list)
151
+ # this is the new way, which will eventually supplant the old `quality_assemble_data`
152
+ with self.telemetry_span("Assembling generic quality data"):
153
+ quality_data = self.assemble_quality_data()
154
+
155
+ # this is the old way, which is being replaced by the new `assemble_quality_data`
156
+ with self.telemetry_span("Assembling quality data by Metric"):
157
+ quality_data += self.quality_assemble_data(polcal_label_list=self.polcal_label_list)
151
158
 
152
159
  with self.telemetry_span(
153
160
  f"Saving quality data with {len(quality_data)} metrics to the file system"
@@ -159,6 +166,16 @@ class AssembleQualityData(L1OutputDataBase, QualityMixin):
159
166
  relative_path=f"{self.constants.dataset_id}_quality_data.json",
160
167
  )
161
168
 
169
+ def assemble_quality_data(self) -> list[dict]:
170
+ """Assemble Quality Data."""
171
+ metrics = []
172
+ tags = [Tag.quality("GENERIC")]
173
+ for metric in self.read(tags=tags, decoder=basemodel_decoder, model=QualityMetric):
174
+ # `metrics` needs to be list[dict] for compatibility with existing `quality_assemble_data`
175
+ # once all metric conversion is complete, `metrics` should be list[QualityMetric] instead
176
+ metrics.append(metric.model_dump(mode="json"))
177
+ return metrics
178
+
162
179
 
163
180
  class SubmitDatasetMetadata(L1OutputDataBase):
164
181
  """
@@ -28,7 +28,6 @@ class QualityMixin(
28
28
  """Assemble the quality data by checking for the existence of each metric."""
29
29
  report = []
30
30
  report += self.quality_task_independent_metrics()
31
- report += self.quality_task_dependent_metrics()
32
31
 
33
32
  polcal_labels = polcal_label_list or []
34
33
  report += self.quality_polcal_metrics(polcal_labels)
@@ -43,15 +42,6 @@ class QualityMixin(
43
42
  result.append(metric_func())
44
43
  return result
45
44
 
46
- def quality_task_dependent_metrics(self) -> list[dict]:
47
- """Encapsulate task dependent metric parsing."""
48
- result = []
49
- for metric_name, metric_func in self.quality_metrics_task_dependence.items():
50
- for task_type in self.quality_task_types:
51
- if self._quality_metric_exists(metric_name=metric_name, task_type=task_type):
52
- result.append(metric_func(task_type=task_type))
53
- return result
54
-
55
45
  def quality_polcal_metrics(self, label_list: list) -> list[dict]:
56
46
  """Encapsulate polcal metric parsing."""
57
47
  result = []
@@ -82,22 +72,12 @@ class QualityMixin(
82
72
  "SENSITIVITY": self.quality_build_sensitivity,
83
73
  "HEALTH_STATUS": self.quality_build_health_status,
84
74
  "TASK_TYPES": self.quality_build_task_type_counts,
85
- "DATASET_AVERAGE": self.quality_build_dataset_average,
86
- "DATASET_RMS": self.quality_build_dataset_rms,
87
75
  "HISTORICAL": self.quality_build_historical,
88
76
  "AO_STATUS": self.quality_build_ao_status,
89
77
  "RANGE": self.quality_build_range,
90
78
  "WAVECAL_FIT": self.quality_build_wavecal_results,
91
79
  }
92
80
 
93
- @property
94
- def quality_metrics_task_dependence(self) -> dict:
95
- """Return a dict of quality metrics which are dependent on the task."""
96
- return {
97
- "FRAME_AVERAGE": self.quality_build_frame_average,
98
- "FRAME_RMS": self.quality_build_frame_rms,
99
- }
100
-
101
81
  @property
102
82
  def quality_metrics_polcal(self) -> dict:
103
83
  """Return a dict of polcal quality metrics."""