dkist-processing-common 12.9.0__tar.gz → 12.10.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.
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/CHANGELOG.rst +11 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/PKG-INFO +1 -1
- dkist_processing_common-12.10.0/dkist_processing_common/models/quality.py +351 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/l1_output_data.py +19 -2
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/mixin/quality/_base.py +0 -20
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/mixin/quality/_metrics.py +2 -161
- dkist_processing_common-12.10.0/dkist_processing_common/tasks/quality_metrics.py +503 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_assemble_quality.py +158 -49
- dkist_processing_common-12.10.0/dkist_processing_common/tests/test_quality.py +706 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_quality_mixin.py +97 -266
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common.egg-info/PKG-INFO +1 -1
- dkist_processing_common-12.9.0/dkist_processing_common/models/quality.py +0 -122
- dkist_processing_common-12.9.0/dkist_processing_common/tasks/quality_metrics.py +0 -316
- dkist_processing_common-12.9.0/dkist_processing_common/tests/test_quality.py +0 -292
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/.gitignore +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/.pre-commit-config.yaml +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/.readthedocs.yml +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/.snyk +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/README.rst +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/bitbucket-pipelines.yml +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/changelog/.gitempty +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/__init__.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/_util/__init__.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/_util/constants.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/_util/graphql.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/_util/scratch.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/_util/tags.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/codecs/__init__.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/codecs/array.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/codecs/asdf.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/codecs/basemodel.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/codecs/bytes.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/codecs/fits.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/codecs/iobase.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/codecs/json.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/codecs/path.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/codecs/quality.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/codecs/str.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/config.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/fonts/Lato-Regular.ttf +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/fonts/__init__.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/manual.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/__init__.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/constants.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/dkist_location.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/extras.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/fits_access.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/flower_pot.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/fried_parameter.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/graphql.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/input_dataset.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/message.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/message_queue_binding.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/metric_code.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/parameters.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/tags.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/task_name.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/telemetry.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/models/wavelength.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/__init__.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/average_bud.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/cs_step.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/dsps_repeat.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/experiment_id_bud.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/id_bud.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/l0_fits_access.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/l1_fits_access.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/lookup_bud.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/near_bud.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/observing_program_id_bud.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/proposal_id_bud.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/quality.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/retarder.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/single_value_single_key_flower.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/task.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/time.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/unique_bud.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/parsers/wavelength.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/__init__.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/assemble_movie.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/base.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/mixin/__init__.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/mixin/globus.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/mixin/interservice_bus.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/mixin/metadata_store.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/mixin/object_store.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/mixin/quality/__init__.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/output_data_base.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/parse_l0_input_data.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/teardown.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/transfer_input_data.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/trial_catalog.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/trial_output_data.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/write_extra.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/write_l1.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tasks/write_l1_base.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/__init__.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/conftest.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/mock_metadata_store.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_assemble_movie.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_base.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_codecs.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_constants.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_construct_dataset_extras.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_cs_step.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_dkist_location.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_fits_access.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_flower_pot.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_fried_parameter.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_input_dataset.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_interservice_bus.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_interservice_bus_mixin.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_manual_processing.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_output_data_base.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_parameters.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_parse_l0_input_data.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_publish_catalog_messages.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_scratch.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_stems.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_submit_dataset_metadata.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_tags.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_task_name.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_task_parsing.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_teardown.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_transfer_input_data.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_transfer_l1_output_data.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_trial_catalog.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_trial_output_data.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_workflow_task_base.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common/tests/test_write_l1.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common.egg-info/SOURCES.txt +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common.egg-info/dependency_links.txt +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common.egg-info/requires.txt +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/dkist_processing_common.egg-info/top_level.txt +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/docs/Makefile +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/docs/changelog.rst +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/docs/conf.py +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/docs/index.rst +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/docs/landing_page.rst +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/docs/make.bat +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/docs/requirements.txt +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/licenses/LICENSE.rst +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/pyproject.toml +0 -0
- {dkist_processing_common-12.9.0 → dkist_processing_common-12.10.0}/setup.cfg +0 -0
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
v12.10.0 (2026-04-02)
|
|
2
|
+
=====================
|
|
3
|
+
|
|
4
|
+
Features
|
|
5
|
+
--------
|
|
6
|
+
|
|
7
|
+
- Added capabilities to simplify the way that quality metrics get produced.
|
|
8
|
+
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>`__)
|
|
9
|
+
- Simplify the production of L0 quality metrics. (`#313 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/313>`__)
|
|
10
|
+
|
|
11
|
+
|
|
1
12
|
v12.9.0 (2026-04-01)
|
|
2
13
|
====================
|
|
3
14
|
|
|
@@ -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
|
-
|
|
150
|
-
|
|
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."""
|
|
@@ -12,10 +12,10 @@ from functools import partial
|
|
|
12
12
|
from typing import Any
|
|
13
13
|
from typing import Iterable
|
|
14
14
|
from typing import Literal
|
|
15
|
+
from typing import Sequence
|
|
15
16
|
|
|
16
17
|
import astropy.units as u
|
|
17
18
|
import numpy as np
|
|
18
|
-
from astropy.wcs import WCS
|
|
19
19
|
from dkist_processing_pac.fitter.fitter_parameters import CU_PARAMS
|
|
20
20
|
from dkist_processing_pac.fitter.fitter_parameters import GLOBAL_PARAMS
|
|
21
21
|
from dkist_processing_pac.fitter.fitter_parameters import TELESCOPE_PARAMS
|
|
@@ -186,7 +186,7 @@ class _SimplePlotQualityMixin:
|
|
|
186
186
|
return all_plot_data
|
|
187
187
|
|
|
188
188
|
@staticmethod
|
|
189
|
-
def _find_iqr_outliers(datetimes:
|
|
189
|
+
def _find_iqr_outliers(datetimes: Sequence[datetime], values: Sequence[float]) -> list[str]:
|
|
190
190
|
"""
|
|
191
191
|
Given a list of values, find values that fall more than (1.5 * iqr) outside the quartiles of the data.
|
|
192
192
|
|
|
@@ -314,90 +314,6 @@ class _SimplePlotQualityMixin:
|
|
|
314
314
|
warnings=None,
|
|
315
315
|
)
|
|
316
316
|
|
|
317
|
-
def quality_store_frame_average(
|
|
318
|
-
self,
|
|
319
|
-
datetimes: list[str],
|
|
320
|
-
values: list[float],
|
|
321
|
-
task_type: str,
|
|
322
|
-
modstate: int | None = None,
|
|
323
|
-
):
|
|
324
|
-
"""Collect and store datetime / value pairs for the individual frame averages."""
|
|
325
|
-
tags = [Tag.quality(MetricCode.frame_average)]
|
|
326
|
-
if modstate:
|
|
327
|
-
tags.append(Tag.modstate(modstate))
|
|
328
|
-
self._record_2d_plot_values(
|
|
329
|
-
x_values=datetimes,
|
|
330
|
-
y_values=values,
|
|
331
|
-
tags=tags,
|
|
332
|
-
series_name=modstate or 1,
|
|
333
|
-
task_type=task_type,
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
def quality_build_frame_average(self, task_type: str) -> dict:
|
|
337
|
-
"""Build frame average schema from stored data."""
|
|
338
|
-
# This will load data for all modstates, if present
|
|
339
|
-
series_data = self._load_2d_plot_values(
|
|
340
|
-
tags=Tag.quality(MetricCode.frame_average), task_type=task_type
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
# Build metric dict
|
|
344
|
-
if len(series_data) > 0:
|
|
345
|
-
datetimes, values = list(series_data.values())[0]
|
|
346
|
-
warnings = self._find_iqr_outliers(datetimes=datetimes, values=values)
|
|
347
|
-
return self._create_2d_plot_with_datetime_metric(
|
|
348
|
-
name=f"Average Across Frame - {task_type.upper()}",
|
|
349
|
-
description=f"Average intensity value across frames of task type {task_type}. One measurement is taken per frame in each task type.",
|
|
350
|
-
metric_code=MetricCode.frame_average,
|
|
351
|
-
facet=task_type.upper(),
|
|
352
|
-
xlabel="Time",
|
|
353
|
-
ylabel="Average Value (adu / sec)",
|
|
354
|
-
series_data=series_data,
|
|
355
|
-
series_name="Modstate",
|
|
356
|
-
warnings=self._format_warnings(warnings),
|
|
357
|
-
)
|
|
358
|
-
|
|
359
|
-
def quality_store_frame_rms(
|
|
360
|
-
self,
|
|
361
|
-
datetimes: list[str],
|
|
362
|
-
values: list[float],
|
|
363
|
-
task_type: str,
|
|
364
|
-
modstate: int | None = None,
|
|
365
|
-
):
|
|
366
|
-
"""Collect and store datetime / value pairs for the individual frame rms."""
|
|
367
|
-
tags = [Tag.quality(MetricCode.frame_rms)]
|
|
368
|
-
if modstate:
|
|
369
|
-
tags.append(Tag.modstate(modstate))
|
|
370
|
-
self._record_2d_plot_values(
|
|
371
|
-
x_values=datetimes,
|
|
372
|
-
y_values=values,
|
|
373
|
-
tags=tags,
|
|
374
|
-
series_name=modstate or 1,
|
|
375
|
-
task_type=task_type,
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
def quality_build_frame_rms(self, task_type: str) -> dict:
|
|
379
|
-
"""Build frame rms schema from stored data."""
|
|
380
|
-
# This will load data for all modstates, if present
|
|
381
|
-
series_data = self._load_2d_plot_values(
|
|
382
|
-
tags=Tag.quality(MetricCode.frame_rms), task_type=task_type
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
# Build metric dict
|
|
386
|
-
if len(series_data) > 0:
|
|
387
|
-
datetimes, values = list(series_data.values())[0]
|
|
388
|
-
warnings = self._find_iqr_outliers(datetimes=datetimes, values=values)
|
|
389
|
-
return self._create_2d_plot_with_datetime_metric(
|
|
390
|
-
name=f"Root Mean Square (RMS) Across Frame - {task_type.upper()}",
|
|
391
|
-
description=f"RMS value across frames of task type {task_type}. One measurement is taken per frame in each task type.",
|
|
392
|
-
metric_code=MetricCode.frame_rms,
|
|
393
|
-
facet=task_type.upper(),
|
|
394
|
-
xlabel="Time",
|
|
395
|
-
ylabel="RMS (adu / sec)",
|
|
396
|
-
series_data=series_data,
|
|
397
|
-
series_name="Modstate",
|
|
398
|
-
warnings=self._format_warnings(warnings),
|
|
399
|
-
)
|
|
400
|
-
|
|
401
317
|
def quality_store_noise(self, datetimes: list[str], values: list[float], stokes: str = "I"):
|
|
402
318
|
"""Collect and store datetime / value pairs for the noise data."""
|
|
403
319
|
self._record_2d_plot_values(
|
|
@@ -565,81 +481,6 @@ class _TableQualityMixin:
|
|
|
565
481
|
warnings=self._format_warnings(warnings),
|
|
566
482
|
)
|
|
567
483
|
|
|
568
|
-
def quality_store_dataset_average(self, task_type: str, frame_averages: list[float]):
|
|
569
|
-
"""
|
|
570
|
-
Collect and store dataset average.
|
|
571
|
-
|
|
572
|
-
Parameters
|
|
573
|
-
----------
|
|
574
|
-
task_type: task type as listed in the headers
|
|
575
|
-
frame_averages: average value of all pixels in each frame of the given task type
|
|
576
|
-
"""
|
|
577
|
-
data = {"task_type": task_type, "frame_averages": frame_averages}
|
|
578
|
-
self._record_values(values=data, tags=Tag.quality(MetricCode.dataset_average))
|
|
579
|
-
|
|
580
|
-
def quality_build_dataset_average(self) -> dict:
|
|
581
|
-
"""Build dataset average schema from stored data."""
|
|
582
|
-
dataset_averages = defaultdict(list)
|
|
583
|
-
# Loop over files that contain data for this metric
|
|
584
|
-
for path in self.read(tags=Tag.quality(MetricCode.dataset_average)):
|
|
585
|
-
with path.open() as f:
|
|
586
|
-
data = json.load(f)
|
|
587
|
-
# Add counts for the task type to its already existing counts
|
|
588
|
-
dataset_averages[data["task_type"]] += data["frame_averages"]
|
|
589
|
-
|
|
590
|
-
# Now, build metric from the counts dict
|
|
591
|
-
table_data = [[i[0], round(np.mean(i[1]), 2)] for i in dataset_averages.items()]
|
|
592
|
-
# Add header row
|
|
593
|
-
table_data.insert(0, ["Task Type", "Dataset Average (adu / sec)"])
|
|
594
|
-
return self._create_table_metric(
|
|
595
|
-
name="Average Across Dataset",
|
|
596
|
-
description="This metric is the calculated mean intensity value across data from an "
|
|
597
|
-
"instrument program task type used in the creation of an entire L1 "
|
|
598
|
-
"dataset.",
|
|
599
|
-
metric_code=MetricCode.dataset_average,
|
|
600
|
-
rows=table_data,
|
|
601
|
-
warnings=None,
|
|
602
|
-
)
|
|
603
|
-
|
|
604
|
-
def quality_store_dataset_rms(self, task_type: str, frame_rms: list[float]):
|
|
605
|
-
"""
|
|
606
|
-
Collect and store dataset average.
|
|
607
|
-
|
|
608
|
-
Parameters
|
|
609
|
-
----------
|
|
610
|
-
task_type: task type as listed in the headers
|
|
611
|
-
frame_rms: rms value of all pixels in each frame of the given task type
|
|
612
|
-
"""
|
|
613
|
-
data = {"task_type": task_type, "frame_rms": frame_rms}
|
|
614
|
-
self._record_values(values=data, tags=Tag.quality(MetricCode.dataset_rms))
|
|
615
|
-
|
|
616
|
-
def quality_build_dataset_rms(self) -> dict:
|
|
617
|
-
"""Build dataset rms schema from stored data."""
|
|
618
|
-
dataset_rms = {}
|
|
619
|
-
# Loop over files that contain data for this metric
|
|
620
|
-
for path in self.read(tags=Tag.quality(MetricCode.dataset_rms)):
|
|
621
|
-
with path.open() as f:
|
|
622
|
-
data = json.load(f)
|
|
623
|
-
# If the task type isn't in the dict, add it with counts set to zero
|
|
624
|
-
if not data["task_type"] in dataset_rms.keys():
|
|
625
|
-
dataset_rms[data["task_type"]] = []
|
|
626
|
-
# Add counts for the task type to its already existing counts
|
|
627
|
-
dataset_rms[data["task_type"]] += data["frame_rms"]
|
|
628
|
-
|
|
629
|
-
# Now, build metric from the counts dict
|
|
630
|
-
table_data = [[i[0], round(np.mean(i[1]), 2)] for i in dataset_rms.items()]
|
|
631
|
-
# Add header row
|
|
632
|
-
table_data.insert(0, ["Task Type", "Dataset RMS (adu / sec)"])
|
|
633
|
-
return self._create_table_metric(
|
|
634
|
-
name="Dataset RMS",
|
|
635
|
-
description="This metric is the calculated root mean square intensity value across data"
|
|
636
|
-
" from an instrument program task type used in the creation of an entire "
|
|
637
|
-
"L1 dataset.",
|
|
638
|
-
metric_code=MetricCode.dataset_rms,
|
|
639
|
-
rows=table_data,
|
|
640
|
-
warnings=None,
|
|
641
|
-
)
|
|
642
|
-
|
|
643
484
|
def quality_store_historical(self, name: str, value: Any, warning: str | None = None):
|
|
644
485
|
"""
|
|
645
486
|
Insert historical data into the schema used to record quality info.
|