dkist-processing-common 13.0.2__tar.gz → 13.0.3rc2__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.
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/CHANGELOG.rst +0 -9
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/PKG-INFO +2 -2
- dkist_processing_common-13.0.3rc2/changelog/326.feature.rst +1 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/quality.py +1 -2
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/quality.py +2 -2
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/mixin/quality/_base.py +0 -4
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/mixin/quality/_metrics.py +0 -161
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/quality_metrics.py +235 -110
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_assemble_quality.py +56 -18
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_quality.py +486 -64
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_quality_mixin.py +4 -232
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common.egg-info/PKG-INFO +2 -2
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common.egg-info/SOURCES.txt +1 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common.egg-info/requires.txt +1 -1
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/pyproject.toml +1 -1
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/.gitignore +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/.pre-commit-config.yaml +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/.readthedocs.yml +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/.snyk +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/README.rst +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/bitbucket-pipelines.yml +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/changelog/.gitempty +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/__init__.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/_util/__init__.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/_util/constants.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/_util/graphql.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/_util/scratch.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/_util/tags.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/codecs/__init__.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/codecs/array.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/codecs/asdf.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/codecs/basemodel.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/codecs/bytes.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/codecs/fits.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/codecs/iobase.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/codecs/json.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/codecs/path.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/codecs/quality.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/codecs/str.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/config.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/fonts/Lato-Regular.ttf +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/fonts/__init__.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/manual.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/__init__.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/constants.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/dkist_location.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/extras.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/fits_access.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/flower_pot.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/fried_parameter.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/graphql.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/input_dataset.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/message.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/message_queue_binding.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/metric_code.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/parameters.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/tags.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/task_name.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/telemetry.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/models/wavelength.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/__init__.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/average_bud.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/cs_step.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/dsps_repeat.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/experiment_id_bud.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/id_bud.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/l0_fits_access.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/l1_fits_access.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/lookup_bud.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/near_bud.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/observing_program_id_bud.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/proposal_id_bud.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/retarder.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/single_value_single_key_flower.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/task.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/time.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/unique_bud.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/parsers/wavelength.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/__init__.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/assemble_movie.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/base.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/l1_output_data.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/mixin/__init__.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/mixin/globus.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/mixin/interservice_bus.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/mixin/metadata_store.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/mixin/object_store.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/mixin/quality/__init__.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/output_data_base.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/parse_l0_input_data.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/teardown.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/transfer_input_data.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/trial_catalog.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/trial_output_data.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/write_extra.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/write_l1.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tasks/write_l1_base.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/__init__.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/conftest.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/mock_metadata_store.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_assemble_movie.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_base.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_codecs.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_constants.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_construct_dataset_extras.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_cs_step.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_dkist_location.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_fits_access.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_flower_pot.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_fried_parameter.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_input_dataset.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_interservice_bus.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_interservice_bus_mixin.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_manual_processing.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_output_data_base.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_parameters.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_parse_l0_input_data.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_publish_catalog_messages.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_scratch.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_stems.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_submit_dataset_metadata.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_tags.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_task_name.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_task_parsing.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_teardown.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_transfer_input_data.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_transfer_l1_output_data.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_trial_catalog.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_trial_output_data.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_workflow_task_base.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common/tests/test_write_l1.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common.egg-info/dependency_links.txt +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/dkist_processing_common.egg-info/top_level.txt +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/docs/Makefile +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/docs/changelog.rst +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/docs/conf.py +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/docs/index.rst +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/docs/landing_page.rst +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/docs/make.bat +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/docs/requirements.txt +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/licenses/LICENSE.rst +0 -0
- {dkist_processing_common-13.0.2 → dkist_processing_common-13.0.3rc2}/setup.cfg +0 -0
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
v13.0.2 (2026-04-08)
|
|
2
|
-
====================
|
|
3
|
-
|
|
4
|
-
Misc
|
|
5
|
-
----
|
|
6
|
-
|
|
7
|
-
- Upgrade `dkist-processing-core` to version 7.2.0 which includes an `apache-airflow` version upgrade to 3.2.0. (`#325 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/325>`__)
|
|
8
|
-
|
|
9
|
-
|
|
10
1
|
v13.0.1 (2026-04-07)
|
|
11
2
|
====================
|
|
12
3
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dkist-processing-common
|
|
3
|
-
Version: 13.0.
|
|
3
|
+
Version: 13.0.3rc2
|
|
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
|
|
@@ -17,7 +17,7 @@ Requires-Dist: asdf<4.0.0,>=3.5.0
|
|
|
17
17
|
Requires-Dist: astropy>=7.0.0
|
|
18
18
|
Requires-Dist: dkist-fits-specifications<5.0,>=4.24.0
|
|
19
19
|
Requires-Dist: dkist-header-validator<6.0,>=5.3.0
|
|
20
|
-
Requires-Dist: dkist-processing-core==7.
|
|
20
|
+
Requires-Dist: dkist-processing-core==7.1.0
|
|
21
21
|
Requires-Dist: dkist-processing-pac<4.0,>=3.1
|
|
22
22
|
Requires-Dist: dkist-service-configuration<5.0,>=4.3.0
|
|
23
23
|
Requires-Dist: dkist-spectral-lines<4.0,>=3.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Simplify the production of L1 quality metrics.
|
|
@@ -13,7 +13,6 @@ from pydantic import PlainSerializer
|
|
|
13
13
|
from pydantic import field_validator
|
|
14
14
|
from pydantic import model_serializer
|
|
15
15
|
from pydantic import model_validator
|
|
16
|
-
from pydantic.alias_generators import to_camel
|
|
17
16
|
from pydantic_core.core_schema import SerializationInfo
|
|
18
17
|
from pydantic_core.core_schema import ValidationInfo
|
|
19
18
|
|
|
@@ -276,7 +275,7 @@ class TableData(QualityModel):
|
|
|
276
275
|
# Note: numeric values are represented as strings for table data
|
|
277
276
|
rows: tuple[tuple[str, ...], ...]
|
|
278
277
|
header_row: bool | None = True
|
|
279
|
-
|
|
278
|
+
header_column: bool | None = False
|
|
280
279
|
|
|
281
280
|
@model_validator(mode="after")
|
|
282
281
|
def tuple_validation(self):
|
|
@@ -31,5 +31,5 @@ class L1QualityFitsAccess(L1FitsAccess):
|
|
|
31
31
|
self.date_begin: str = self.header["DATE-BEG"]
|
|
32
32
|
self.light_level: float = self.header["LIGHTLVL"]
|
|
33
33
|
self.health_status: str = self.header["DSHEALTH"]
|
|
34
|
-
self.ao_status: int = self.header.get("AO_LOCK", None)
|
|
35
|
-
self.num_out_of_bounds_ao_values: int = self.header.get("OOBSHIFT", None)
|
|
34
|
+
self.ao_status: int | None = self.header.get("AO_LOCK", None)
|
|
35
|
+
self.num_out_of_bounds_ao_values: int | None = self.header.get("OOBSHIFT", None)
|
|
@@ -66,14 +66,10 @@ class QualityMixin(
|
|
|
66
66
|
def quality_metrics_no_task_dependence(self) -> dict:
|
|
67
67
|
"""Return a dict of the quality metrics with no task dependence."""
|
|
68
68
|
return {
|
|
69
|
-
"FRIED_PARAMETER": self.quality_build_fried_parameter,
|
|
70
|
-
"LIGHT_LEVEL": self.quality_build_light_level,
|
|
71
69
|
"NOISE": self.quality_build_noise,
|
|
72
70
|
"SENSITIVITY": self.quality_build_sensitivity,
|
|
73
|
-
"HEALTH_STATUS": self.quality_build_health_status,
|
|
74
71
|
"TASK_TYPES": self.quality_build_task_type_counts,
|
|
75
72
|
"HISTORICAL": self.quality_build_historical,
|
|
76
|
-
"AO_STATUS": self.quality_build_ao_status,
|
|
77
73
|
"RANGE": self.quality_build_range,
|
|
78
74
|
"WAVECAL_FIT": self.quality_build_wavecal_results,
|
|
79
75
|
}
|
|
@@ -25,7 +25,6 @@ from pandas import DataFrame
|
|
|
25
25
|
from solar_wavelength_calibration.fitter.wavelength_fitter import FitResult
|
|
26
26
|
|
|
27
27
|
from dkist_processing_common.codecs.json import json_decoder
|
|
28
|
-
from dkist_processing_common.models.fried_parameter import r0_valid
|
|
29
28
|
from dkist_processing_common.models.metric_code import MetricCode
|
|
30
29
|
from dkist_processing_common.models.quality import EfficiencyHistograms
|
|
31
30
|
from dkist_processing_common.models.quality import ModulationMatrixHistograms
|
|
@@ -41,24 +40,6 @@ logger = logging.getLogger(__name__)
|
|
|
41
40
|
|
|
42
41
|
|
|
43
42
|
class _SimpleQualityMixin:
|
|
44
|
-
@staticmethod
|
|
45
|
-
def _create_statement_metric(
|
|
46
|
-
name: str,
|
|
47
|
-
description: str,
|
|
48
|
-
metric_code: str,
|
|
49
|
-
statement: str,
|
|
50
|
-
warnings: str | None = None,
|
|
51
|
-
facet: str | None = None,
|
|
52
|
-
) -> dict:
|
|
53
|
-
metric = ReportMetric(
|
|
54
|
-
name=name,
|
|
55
|
-
description=description,
|
|
56
|
-
metric_code=metric_code,
|
|
57
|
-
facet=facet,
|
|
58
|
-
statement=statement,
|
|
59
|
-
warnings=warnings,
|
|
60
|
-
)
|
|
61
|
-
return metric.model_dump()
|
|
62
43
|
|
|
63
44
|
def quality_store_range(self, name: str, warnings: list[str]):
|
|
64
45
|
"""
|
|
@@ -209,111 +190,6 @@ class _SimplePlotQualityMixin:
|
|
|
209
190
|
)
|
|
210
191
|
return warnings
|
|
211
192
|
|
|
212
|
-
def quality_store_ao_status_and_fried_parameter(
|
|
213
|
-
self, datetimes: list[str], values: list[list[bool | float]]
|
|
214
|
-
):
|
|
215
|
-
"""
|
|
216
|
-
Collect and store datetime / value pairs for the boolean AO status and Fried parameter.
|
|
217
|
-
|
|
218
|
-
Store all non-None AO lock status values, but only store Fried parameter values if AO lock status is True.
|
|
219
|
-
|
|
220
|
-
Because of how L1Metric.has_metric works, empty lists will not be passed to this method.
|
|
221
|
-
However, because of how L1Metric.store_metric works, one or both values can be None.
|
|
222
|
-
"""
|
|
223
|
-
ao_lock_values = [value[0] for value in values]
|
|
224
|
-
ao_not_none = [ao for ao in ao_lock_values if ao is not None]
|
|
225
|
-
if len(ao_not_none) != 0:
|
|
226
|
-
self._record_values(values=ao_not_none, tags=Tag.quality(MetricCode.ao_status))
|
|
227
|
-
fried_values = [value[1] for value in values]
|
|
228
|
-
ao_oob_values = [value[2] for value in values]
|
|
229
|
-
fried_values_to_plot = []
|
|
230
|
-
datetimes_to_plot = []
|
|
231
|
-
# For each set of input data, check if the r0 is considered valid based on all data
|
|
232
|
-
for i in range(len(fried_values)):
|
|
233
|
-
if r0_valid(
|
|
234
|
-
r0=fried_values[i],
|
|
235
|
-
ao_lock=ao_lock_values[i],
|
|
236
|
-
num_out_of_bounds_ao_values=ao_oob_values[i],
|
|
237
|
-
):
|
|
238
|
-
fried_values_to_plot.append(fried_values[i])
|
|
239
|
-
datetimes_to_plot.append(datetimes[i])
|
|
240
|
-
if len(fried_values_to_plot) != 0:
|
|
241
|
-
self._record_2d_plot_values(
|
|
242
|
-
x_values=datetimes_to_plot,
|
|
243
|
-
y_values=fried_values_to_plot,
|
|
244
|
-
tags=Tag.quality(MetricCode.fried_parameter),
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
def quality_build_ao_status(self) -> dict:
|
|
248
|
-
"""
|
|
249
|
-
Build ao status schema from stored data.
|
|
250
|
-
|
|
251
|
-
Because of how quality_task_independent_metrics in the QualityMixin works, this method is not called if no data is on disk.
|
|
252
|
-
"""
|
|
253
|
-
ao_status = []
|
|
254
|
-
# Loop over files that contain data for this metric
|
|
255
|
-
for path in self.read(tags=Tag.quality(MetricCode.ao_status)):
|
|
256
|
-
with path.open() as f:
|
|
257
|
-
ao_status += json.load(f)
|
|
258
|
-
percentage = round(100 * np.count_nonzero(ao_status) / len(ao_status), 1)
|
|
259
|
-
return self._create_statement_metric(
|
|
260
|
-
name="Adaptive Optics Status",
|
|
261
|
-
description="This metric shows the percentage of frames in which the adaptive optics "
|
|
262
|
-
"system was running and locked",
|
|
263
|
-
metric_code=MetricCode.ao_status,
|
|
264
|
-
statement=f"The adaptive optics system was running and locked for {percentage}% of the "
|
|
265
|
-
f"observed frames",
|
|
266
|
-
warnings=None,
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
def quality_build_fried_parameter(self) -> dict:
|
|
270
|
-
"""
|
|
271
|
-
Build fried parameter schema from stored data.
|
|
272
|
-
|
|
273
|
-
Because of how quality_task_independent_metrics in the QualityMixin works, this method is not called if no data is on disk.
|
|
274
|
-
"""
|
|
275
|
-
# Merge all recorded quality values
|
|
276
|
-
series_data = self._load_2d_plot_values(tags=Tag.quality(MetricCode.fried_parameter))
|
|
277
|
-
values = list(series_data.values())[0][1]
|
|
278
|
-
return self._create_2d_plot_with_datetime_metric(
|
|
279
|
-
name="Fried Parameter",
|
|
280
|
-
description="This metric quantifies the stability of the atmosphere during an "
|
|
281
|
-
"observation and directly impacts the data quality through a phenomenon "
|
|
282
|
-
"known as atmospheric seeing. One measurement is taken per L1 frame. "
|
|
283
|
-
"Only measurements taken while the AO system is locked are valid.",
|
|
284
|
-
metric_code=MetricCode.fried_parameter,
|
|
285
|
-
xlabel="Time",
|
|
286
|
-
ylabel="Fried Parameter (m)",
|
|
287
|
-
ylim=(0.0, 0.2),
|
|
288
|
-
series_data=series_data,
|
|
289
|
-
statement=f"Average valid Fried Parameter measurements for L1 dataset: "
|
|
290
|
-
f"{round(np.mean(values), 2)} ± {round(np.std(values), 2)} m",
|
|
291
|
-
warnings=None,
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
def quality_store_light_level(self, datetimes: list[str], values: list[float]):
|
|
295
|
-
"""Collect and store datetime / value pairs for the light level."""
|
|
296
|
-
self._record_2d_plot_values(
|
|
297
|
-
x_values=datetimes, y_values=values, tags=Tag.quality(MetricCode.light_level)
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
def quality_build_light_level(self) -> dict:
|
|
301
|
-
"""Build light_level schema from stored data."""
|
|
302
|
-
series_data = self._load_2d_plot_values(tags=Tag.quality(MetricCode.light_level))
|
|
303
|
-
values = list(series_data.values())[0][1]
|
|
304
|
-
return self._create_2d_plot_with_datetime_metric(
|
|
305
|
-
name="Light Level",
|
|
306
|
-
description="The telescope light level, as measured by the Telescope Acquisition Camera, at the start of "
|
|
307
|
-
"data acquisition of each frame.",
|
|
308
|
-
metric_code=MetricCode.light_level,
|
|
309
|
-
xlabel="Time",
|
|
310
|
-
ylabel="Light Level (adu)",
|
|
311
|
-
series_data=series_data,
|
|
312
|
-
statement=f"Average Light Level for L1 dataset: "
|
|
313
|
-
f"{round(np.mean(values), 2)} ± {round(np.std(values), 2)} adu",
|
|
314
|
-
warnings=None,
|
|
315
|
-
)
|
|
316
|
-
|
|
317
193
|
def quality_store_noise(self, datetimes: list[str], values: list[float], stokes: str = "I"):
|
|
318
194
|
"""Collect and store datetime / value pairs for the noise data."""
|
|
319
195
|
self._record_2d_plot_values(
|
|
@@ -390,43 +266,6 @@ class _TableQualityMixin:
|
|
|
390
266
|
)
|
|
391
267
|
return metric.model_dump()
|
|
392
268
|
|
|
393
|
-
def quality_store_health_status(self, values: list[str]):
|
|
394
|
-
"""
|
|
395
|
-
Collect and store health status data.
|
|
396
|
-
|
|
397
|
-
Parameters
|
|
398
|
-
----------
|
|
399
|
-
values: statuses as listed in the headers
|
|
400
|
-
"""
|
|
401
|
-
self._record_values(values=values, tags=Tag.quality(MetricCode.health_status))
|
|
402
|
-
|
|
403
|
-
def quality_build_health_status(self) -> dict:
|
|
404
|
-
"""Build health status schema from stored data."""
|
|
405
|
-
values = []
|
|
406
|
-
for path in self.read(tags=Tag.quality(MetricCode.health_status)):
|
|
407
|
-
with path.open() as f:
|
|
408
|
-
data = json.load(f)
|
|
409
|
-
values += data
|
|
410
|
-
statuses, counts = np.unique(values, return_counts=True)
|
|
411
|
-
statuses = [s.lower() for s in statuses]
|
|
412
|
-
# JSON serialization does not work with numpy types
|
|
413
|
-
counts = [int(c) for c in counts]
|
|
414
|
-
warnings = []
|
|
415
|
-
if any(s in statuses for s in ["bad", "ill", "unknown"]):
|
|
416
|
-
warnings.append(
|
|
417
|
-
"Data sourced from components with a health status of 'ill', 'bad', or 'unknown'."
|
|
418
|
-
)
|
|
419
|
-
table_data = [list(z) for z in zip(statuses, counts)]
|
|
420
|
-
table_data.insert(0, ["Status", "Count"])
|
|
421
|
-
return self._create_table_metric(
|
|
422
|
-
name="Data Source Health",
|
|
423
|
-
description="This metric contains the worst health status of the data source during "
|
|
424
|
-
"data acquisition. One reading is taken per L1 frame.",
|
|
425
|
-
metric_code=MetricCode.health_status,
|
|
426
|
-
rows=table_data,
|
|
427
|
-
warnings=self._format_warnings(warnings),
|
|
428
|
-
)
|
|
429
|
-
|
|
430
269
|
def quality_store_task_type_counts(
|
|
431
270
|
self, task_type: str, total_frames: int, frames_not_used: int = 0
|
|
432
271
|
):
|
|
@@ -3,19 +3,20 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
from collections import defaultdict
|
|
5
5
|
from datetime import datetime
|
|
6
|
-
from inspect import signature
|
|
7
6
|
from itertools import chain
|
|
8
7
|
from pathlib import Path
|
|
9
|
-
from typing import Callable
|
|
10
8
|
from typing import Generator
|
|
11
9
|
from typing import Iterable
|
|
12
10
|
from typing import Type
|
|
13
11
|
|
|
14
12
|
import numpy as np
|
|
15
13
|
from pydantic import FiniteFloat
|
|
14
|
+
from pydantic import model_validator
|
|
16
15
|
|
|
17
16
|
from dkist_processing_common.codecs.basemodel import basemodel_encoder
|
|
17
|
+
from dkist_processing_common.codecs.fits import fits_access_decoder
|
|
18
18
|
from dkist_processing_common.models.fits_access import FitsAccessBase
|
|
19
|
+
from dkist_processing_common.models.fried_parameter import r0_valid
|
|
19
20
|
from dkist_processing_common.models.metric_code import MetricCode
|
|
20
21
|
from dkist_processing_common.models.quality import QualityMetric
|
|
21
22
|
from dkist_processing_common.models.quality import QualityModel
|
|
@@ -261,11 +262,6 @@ class QualityL0Metrics(WorkflowTaskBase, QualityMixin):
|
|
|
261
262
|
metric_code=MetricCode.dataset_average,
|
|
262
263
|
table_data=[table_data],
|
|
263
264
|
)
|
|
264
|
-
# NOTE 1: QualityValueEncoder
|
|
265
|
-
# NOTE 1: a BaseModel float field automatically converts np.float32 to python float
|
|
266
|
-
# NOTE 2: QualityDataEncoder
|
|
267
|
-
# NOTE 2: a BaseModel datetime field automatically converts to iso 8601 str with mode="json"
|
|
268
|
-
# NOTE 2: a BaseModel FiniteFloat field inherently rejects `nan` and `inf` values
|
|
269
265
|
self.write(metric, tags=Tag.quality("GENERIC"), encoder=basemodel_encoder)
|
|
270
266
|
|
|
271
267
|
def compute_and_write_dataset_rms(
|
|
@@ -291,11 +287,6 @@ class QualityL0Metrics(WorkflowTaskBase, QualityMixin):
|
|
|
291
287
|
metric_code=MetricCode.dataset_rms,
|
|
292
288
|
table_data=[table_data],
|
|
293
289
|
)
|
|
294
|
-
# NOTE 1: QualityValueEncoder
|
|
295
|
-
# NOTE 1: a BaseModel float field automatically converts np.float32 to python float
|
|
296
|
-
# NOTE 2: QualityDataEncoder
|
|
297
|
-
# NOTE 2: a BaseModel datetime field automatically converts to iso 8601 str with mode="json"
|
|
298
|
-
# NOTE 2: a BaseModel FiniteFloat field inherently rejects `nan` and `inf` values
|
|
299
290
|
self.write(metric, tags=Tag.quality("GENERIC"), encoder=basemodel_encoder)
|
|
300
291
|
|
|
301
292
|
def compute_and_write_frame_average(
|
|
@@ -341,12 +332,6 @@ class QualityL0Metrics(WorkflowTaskBase, QualityMixin):
|
|
|
341
332
|
plot_data=[xy_data],
|
|
342
333
|
warnings=warnings,
|
|
343
334
|
)
|
|
344
|
-
# NOTE 1: QualityValueEncoder
|
|
345
|
-
# NOTE 1: a BaseModel float field automatically converts np.float32 to python float
|
|
346
|
-
# NOTE 2: QualityDataEncoder
|
|
347
|
-
# NOTE 2: a BaseModel datetime field automatically converts to iso 8601 str with mode="json"
|
|
348
|
-
# NOTE 2: a BaseModel FiniteFloat field inherently rejects `nan` and `inf` values
|
|
349
|
-
# write one file per task type
|
|
350
335
|
self.write(metric, tags=Tag.quality("GENERIC"), encoder=basemodel_encoder)
|
|
351
336
|
|
|
352
337
|
def compute_and_write_frame_rms(
|
|
@@ -392,112 +377,252 @@ class QualityL0Metrics(WorkflowTaskBase, QualityMixin):
|
|
|
392
377
|
plot_data=[xy_data],
|
|
393
378
|
warnings=warnings,
|
|
394
379
|
)
|
|
395
|
-
# NOTE 1: QualityValueEncoder
|
|
396
|
-
# NOTE 1: a BaseModel float field automatically converts np.float32 to python float
|
|
397
|
-
# NOTE 2: QualityDataEncoder
|
|
398
|
-
# NOTE 2: a BaseModel datetime field automatically converts to iso 8601 str with mode="json"
|
|
399
|
-
# NOTE 2: a BaseModel FiniteFloat field inherently rejects `nan` and `inf` values
|
|
400
|
-
# write one file per task type
|
|
401
380
|
self.write(metric, tags=Tag.quality("GENERIC"), encoder=basemodel_encoder)
|
|
402
381
|
|
|
403
382
|
|
|
404
|
-
class
|
|
405
|
-
"""
|
|
406
|
-
Class for collecting L1 quality metric data while frames are being opened before storing on disk.
|
|
407
|
-
|
|
408
|
-
Parameters
|
|
409
|
-
----------
|
|
410
|
-
storage_method
|
|
411
|
-
The callable used to execute the storage
|
|
412
|
-
value_source
|
|
413
|
-
The source of the value being stored
|
|
414
|
-
value_function
|
|
415
|
-
The function to return the values
|
|
416
|
-
"""
|
|
383
|
+
class L1QualityData(QualityModel):
|
|
384
|
+
"""Intermediate L1 Quality Data gleaned from collection of L1QualityFitsAccess."""
|
|
417
385
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
386
|
+
# by default, a tuple accepts a list as input
|
|
387
|
+
# the use of tuples is intentional because list mutations do not trigger validation
|
|
388
|
+
# the impact is that L1QualityData should not be instantiated until
|
|
389
|
+
# after the tuple input has been fully accumulated
|
|
390
|
+
# when `mode="json"`, the output of a tuple results in a list
|
|
391
|
+
# when `mode="python"`, the output of a tuple results in a tuple
|
|
392
|
+
datetimes: tuple[datetime, ...]
|
|
393
|
+
# NOTE: FiniteFloat will fail fast for `nan` and `inf`
|
|
394
|
+
light_level_values: tuple[FiniteFloat, ...]
|
|
395
|
+
health_status_values: tuple[str, ...]
|
|
396
|
+
# NOTE: FiniteFloat will fail fast for `nan` and `inf`
|
|
397
|
+
fried_parameter_values: tuple[FiniteFloat, ...]
|
|
398
|
+
ao_lock_values: tuple[bool | None, ...]
|
|
399
|
+
ao_oob_values: tuple[int | None, ...]
|
|
400
|
+
|
|
401
|
+
@model_validator(mode="after")
|
|
402
|
+
def tuple_validation(self):
|
|
403
|
+
"""Validate tuple lengths."""
|
|
404
|
+
error_message = [f"{len(self.datetimes)=}"]
|
|
405
|
+
datetime_len = len(self.datetimes)
|
|
406
|
+
if len(self.light_level_values) != datetime_len:
|
|
407
|
+
error_message.append(f"{len(self.light_level_values)=}")
|
|
408
|
+
if len(self.health_status_values) != datetime_len:
|
|
409
|
+
error_message.append(f"{len(self.health_status_values)=}")
|
|
410
|
+
if len(self.fried_parameter_values) != datetime_len:
|
|
411
|
+
error_message.append(f"{len(self.fried_parameter_values)=}")
|
|
412
|
+
if len(self.ao_lock_values) != datetime_len:
|
|
413
|
+
error_message.append(f"{len(self.ao_lock_values)=}")
|
|
414
|
+
if len(self.ao_oob_values) != datetime_len:
|
|
415
|
+
error_message.append(f"{len(self.ao_oob_values)=}")
|
|
416
|
+
if len(error_message) > 1:
|
|
417
|
+
raise ValueError(
|
|
418
|
+
f"One or more dimensions have wrong length: {', '.join(error_message)}. "
|
|
419
|
+
)
|
|
420
|
+
return self
|
|
433
421
|
|
|
434
|
-
If a value_function was provided, apply it to the given source attribute and append to
|
|
435
|
-
self.values. Otherwise, append the attribute value itself to self.values. If multiple
|
|
436
|
-
sources provided, append a list of the source attributes to self.values.
|
|
437
422
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
frame
|
|
441
|
-
The input frame
|
|
423
|
+
class QualityL1Metrics(WorkflowTaskBase, QualityMixin):
|
|
424
|
+
"""Task class supporting the generation of quality metrics for the L1 data."""
|
|
442
425
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
self.
|
|
426
|
+
def run(self) -> None:
|
|
427
|
+
"""Run method for this task."""
|
|
428
|
+
with self.telemetry_span("Reading L1 frames"):
|
|
429
|
+
l1_quality_data = self.collect_l1_quality_data()
|
|
430
|
+
|
|
431
|
+
with self.telemetry_span("Writing L1 quality data"):
|
|
432
|
+
self.compute_and_write_l1_quality_metrics(l1_quality_data)
|
|
433
|
+
|
|
434
|
+
def collect_l1_quality_data(self) -> L1QualityData:
|
|
435
|
+
"""Collect data used to compute L1 quality metrics."""
|
|
436
|
+
frames = self.read(
|
|
437
|
+
tags=[Tag.calibrated(), Tag.frame()],
|
|
438
|
+
decoder=fits_access_decoder,
|
|
439
|
+
fits_access_class=L1QualityFitsAccess,
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# accumulate specified L1QualityFitsAccess values into lists, sorted by datetime
|
|
443
|
+
rows = sorted(
|
|
444
|
+
(
|
|
445
|
+
(
|
|
446
|
+
# DATE-BEG
|
|
447
|
+
datetime.fromisoformat(frame.time_obs),
|
|
448
|
+
# LIGHTLVL
|
|
449
|
+
frame.light_level,
|
|
450
|
+
# DSHEALTH
|
|
451
|
+
frame.health_status,
|
|
452
|
+
# ATMOS_R0
|
|
453
|
+
frame.fried_parameter,
|
|
454
|
+
# AO_LOCK
|
|
455
|
+
frame.ao_status,
|
|
456
|
+
# OOBSHIFT
|
|
457
|
+
frame.num_out_of_bounds_ao_values,
|
|
458
|
+
)
|
|
459
|
+
for frame in frames
|
|
460
|
+
),
|
|
461
|
+
# DATE-BEG
|
|
462
|
+
key=lambda row: row[0],
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
return L1QualityData(
|
|
466
|
+
datetimes=tuple(row[0] for row in rows),
|
|
467
|
+
light_level_values=tuple(row[1] for row in rows),
|
|
468
|
+
health_status_values=tuple(row[2] for row in rows),
|
|
469
|
+
fried_parameter_values=tuple(row[3] for row in rows),
|
|
470
|
+
ao_lock_values=tuple(row[4] for row in rows),
|
|
471
|
+
ao_oob_values=tuple(row[5] for row in rows),
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
def compute_and_write_l1_quality_metrics(self, l1_quality_data: L1QualityData) -> None:
|
|
475
|
+
"""Write L1 metrics to disk."""
|
|
476
|
+
self.compute_and_write_ao_status(l1_quality_data)
|
|
477
|
+
self.compute_and_write_fried_parameter(l1_quality_data)
|
|
478
|
+
self.compute_and_write_health_status(l1_quality_data)
|
|
479
|
+
self.compute_and_write_light_level(l1_quality_data)
|
|
480
|
+
|
|
481
|
+
def compute_and_write_ao_status(self, l1_quality_data: L1QualityData) -> None:
|
|
482
|
+
"""Write AO_STATUS quality metric."""
|
|
483
|
+
ao_not_none = [ao for ao in l1_quality_data.ao_lock_values if ao is not None]
|
|
484
|
+
|
|
485
|
+
# don't write anything if there is no data
|
|
486
|
+
if not ao_not_none:
|
|
450
487
|
return
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
488
|
+
|
|
489
|
+
percentage = round(100 * np.count_nonzero(ao_not_none) / len(ao_not_none), 1)
|
|
490
|
+
|
|
491
|
+
metric = QualityMetric(
|
|
492
|
+
name="Adaptive Optics Status",
|
|
493
|
+
description="This metric shows the percentage of frames in which the adaptive optics "
|
|
494
|
+
"system was running and locked",
|
|
495
|
+
metric_code=MetricCode.ao_status,
|
|
496
|
+
statement=[
|
|
497
|
+
f"The adaptive optics system was running and locked for {percentage}% of the observed frames"
|
|
498
|
+
],
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
self.write(metric, tags=Tag.quality("GENERIC"), encoder=basemodel_encoder)
|
|
502
|
+
|
|
503
|
+
def compute_and_write_fried_parameter(self, l1_quality_data: L1QualityData) -> None:
|
|
504
|
+
"""Write FRIED_PARAMETER quality metric."""
|
|
505
|
+
datetimes = l1_quality_data.datetimes
|
|
506
|
+
fried_parameter_values = l1_quality_data.fried_parameter_values
|
|
507
|
+
ao_lock_values = l1_quality_data.ao_lock_values
|
|
508
|
+
ao_oob_values = l1_quality_data.ao_oob_values
|
|
509
|
+
|
|
510
|
+
fried_values_to_plot = []
|
|
511
|
+
datetimes_to_plot = []
|
|
512
|
+
# For each set of input data, check if the r0 is considered valid based on all data
|
|
513
|
+
for i in range(len(fried_parameter_values)):
|
|
514
|
+
if r0_valid(
|
|
515
|
+
r0=fried_parameter_values[i],
|
|
516
|
+
ao_lock=ao_lock_values[i],
|
|
517
|
+
num_out_of_bounds_ao_values=ao_oob_values[i],
|
|
518
|
+
):
|
|
519
|
+
datetimes_to_plot.append(datetimes[i])
|
|
520
|
+
fried_values_to_plot.append(fried_parameter_values[i])
|
|
521
|
+
|
|
522
|
+
if len(fried_values_to_plot) == 0:
|
|
523
|
+
# don't write anything if there is no data
|
|
454
524
|
return
|
|
455
|
-
self.values.append(getattr(frame, self.value_source))
|
|
456
525
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
526
|
+
time_series = TimeSeriesData(
|
|
527
|
+
x_values=tuple(datetimes_to_plot),
|
|
528
|
+
y_values=tuple(fried_values_to_plot),
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
series_data = {"": time_series}
|
|
532
|
+
|
|
533
|
+
xy_data = XYData(
|
|
534
|
+
xlabel="Time",
|
|
535
|
+
ylabel="Fried Parameter (m)",
|
|
536
|
+
ylim=(0.0, 0.2),
|
|
537
|
+
series_data=series_data,
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
statement = (
|
|
541
|
+
f"Average valid Fried Parameter measurements for L1 dataset: "
|
|
542
|
+
f"{round(np.mean(fried_values_to_plot), 2)} ± {round(np.std(fried_values_to_plot), 2)} m"
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
metric = QualityMetric(
|
|
546
|
+
name="Fried Parameter",
|
|
547
|
+
description="This metric quantifies the stability of the atmosphere during an "
|
|
548
|
+
"observation and directly impacts the data quality through a phenomenon "
|
|
549
|
+
"known as atmospheric seeing. One measurement is taken per L1 frame. "
|
|
550
|
+
"Only measurements taken while the AO system is locked are valid.",
|
|
551
|
+
metric_code=MetricCode.fried_parameter,
|
|
552
|
+
plot_data=[xy_data],
|
|
553
|
+
statement=[statement],
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
self.write(metric, tags=Tag.quality("GENERIC"), encoder=basemodel_encoder)
|
|
557
|
+
|
|
558
|
+
def compute_and_write_health_status(self, l1_quality_data: L1QualityData) -> None:
|
|
559
|
+
"""Write HEALTH_STATUS quality metric."""
|
|
560
|
+
# don't need to check for None - l1_quality_data.health_status_values does not allow None
|
|
561
|
+
health_status_values = l1_quality_data.health_status_values
|
|
562
|
+
|
|
563
|
+
# don't write anything if there is no data
|
|
564
|
+
if not health_status_values:
|
|
472
565
|
return
|
|
473
|
-
self.storage_method(values=v)
|
|
474
566
|
|
|
567
|
+
statuses, counts = np.unique(health_status_values, return_counts=True)
|
|
568
|
+
# individual elements to lower case, with side effect of switch from numpy.ndarray to list
|
|
569
|
+
statuses = [s.lower() for s in statuses]
|
|
570
|
+
# individual elements from int to str, with side effect of switch from numpy.ndarray to list
|
|
571
|
+
counts = [str(c) for c in counts]
|
|
475
572
|
|
|
476
|
-
|
|
477
|
-
|
|
573
|
+
header = ("Status", "Count")
|
|
574
|
+
rows = [header]
|
|
575
|
+
rows.extend(tuple(z) for z in zip(statuses, counts))
|
|
576
|
+
table_data = TableData(rows=tuple(rows))
|
|
478
577
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
value_source=["ao_status", "fried_parameter", "num_out_of_bounds_ao_values"],
|
|
487
|
-
),
|
|
488
|
-
]
|
|
578
|
+
metric = QualityMetric(
|
|
579
|
+
name="Data Source Health",
|
|
580
|
+
description="This metric contains the worst health status of the data source during "
|
|
581
|
+
"data acquisition. One reading is taken per L1 frame.",
|
|
582
|
+
metric_code=MetricCode.health_status,
|
|
583
|
+
table_data=[table_data],
|
|
584
|
+
)
|
|
489
585
|
|
|
490
|
-
|
|
491
|
-
|
|
586
|
+
if any(s in statuses for s in ["bad", "ill", "unknown"]):
|
|
587
|
+
metric.warnings = [
|
|
588
|
+
"Data sourced from components with a health status of 'ill', 'bad', or 'unknown'."
|
|
589
|
+
]
|
|
492
590
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
591
|
+
self.write(metric, tags=Tag.quality("GENERIC"), encoder=basemodel_encoder)
|
|
592
|
+
|
|
593
|
+
def compute_and_write_light_level(self, l1_quality_data: L1QualityData) -> None:
|
|
594
|
+
"""Write LIGHT_LEVEL quality metric."""
|
|
595
|
+
# don't need to check for None - l1_quality_data.light_level_values does not allow None
|
|
596
|
+
datetimes = l1_quality_data.datetimes
|
|
597
|
+
light_level_values = l1_quality_data.light_level_values
|
|
598
|
+
|
|
599
|
+
# don't write anything if there is no data
|
|
600
|
+
if not light_level_values:
|
|
601
|
+
return
|
|
602
|
+
|
|
603
|
+
time_series = TimeSeriesData(
|
|
604
|
+
x_values=datetimes,
|
|
605
|
+
y_values=light_level_values,
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
series_data = {"": time_series}
|
|
609
|
+
|
|
610
|
+
xy_data = XYData(
|
|
611
|
+
xlabel="Time",
|
|
612
|
+
ylabel="Light Level (adu)",
|
|
613
|
+
series_data=series_data,
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
statement = f"Average Light Level for L1 dataset: "
|
|
617
|
+
f"{round(np.mean(light_level_values), 2)} ± {round(np.std(light_level_values), 2)} adu"
|
|
499
618
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
619
|
+
metric = QualityMetric(
|
|
620
|
+
name="Light Level",
|
|
621
|
+
description="The telescope light level, as measured by the Telescope Acquisition Camera, "
|
|
622
|
+
"at the start of data acquisition of each frame.",
|
|
623
|
+
metric_code=MetricCode.light_level,
|
|
624
|
+
plot_data=[xy_data],
|
|
625
|
+
statement=[statement],
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
self.write(metric, tags=Tag.quality("GENERIC"), encoder=basemodel_encoder)
|