dkist-processing-common 12.6.2__tar.gz → 12.6.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/CHANGELOG.rst +19 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/PKG-INFO +2 -2
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/cs_step.py +69 -12
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/l0_fits_access.py +23 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/write_extra.py +7 -3
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/conftest.py +65 -7
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_construct_dataset_extras.py +13 -0
- dkist_processing_common-12.6.3/dkist_processing_common/tests/test_cs_step.py +111 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_fits_access.py +28 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_stems.py +25 -15
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common.egg-info/PKG-INFO +2 -2
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common.egg-info/requires.txt +1 -1
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/pyproject.toml +1 -1
- dkist_processing_common-12.6.2/dkist_processing_common/tests/test_cs_step.py +0 -76
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/.gitignore +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/.pre-commit-config.yaml +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/.readthedocs.yml +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/.snyk +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/README.rst +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/bitbucket-pipelines.yml +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/changelog/.gitempty +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/__init__.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/_util/__init__.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/_util/constants.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/_util/graphql.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/_util/scratch.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/_util/tags.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/codecs/__init__.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/codecs/array.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/codecs/asdf.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/codecs/basemodel.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/codecs/bytes.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/codecs/fits.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/codecs/iobase.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/codecs/json.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/codecs/path.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/codecs/quality.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/codecs/str.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/config.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/fonts/Lato-Regular.ttf +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/fonts/__init__.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/manual.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/__init__.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/constants.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/dkist_location.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/extras.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/fits_access.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/flower_pot.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/fried_parameter.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/graphql.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/input_dataset.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/message.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/message_queue_binding.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/metric_code.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/parameters.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/quality.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/tags.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/task_name.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/telemetry.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/models/wavelength.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/__init__.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/average_bud.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/dsps_repeat.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/experiment_id_bud.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/id_bud.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/l1_fits_access.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/lookup_bud.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/near_bud.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/observing_program_id_bud.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/proposal_id_bud.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/quality.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/retarder.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/single_value_single_key_flower.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/task.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/time.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/unique_bud.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/parsers/wavelength.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/__init__.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/assemble_movie.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/base.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/l1_output_data.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/mixin/__init__.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/mixin/globus.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/mixin/interservice_bus.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/mixin/metadata_store.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/mixin/object_store.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/mixin/quality/__init__.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/mixin/quality/_base.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/mixin/quality/_metrics.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/output_data_base.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/parse_l0_input_data.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/quality_metrics.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/teardown.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/transfer_input_data.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/trial_catalog.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/trial_output_data.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/write_l1.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tasks/write_l1_base.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/__init__.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/mock_metadata_store.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_assemble_movie.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_assemble_quality.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_base.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_codecs.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_constants.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_dkist_location.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_flower_pot.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_fried_parameter.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_input_dataset.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_interservice_bus.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_interservice_bus_mixin.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_manual_processing.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_output_data_base.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_parameters.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_parse_l0_input_data.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_publish_catalog_messages.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_quality.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_quality_mixin.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_scratch.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_submit_dataset_metadata.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_tags.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_task_name.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_task_parsing.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_teardown.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_transfer_input_data.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_transfer_l1_output_data.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_trial_catalog.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_trial_output_data.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_workflow_task_base.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common/tests/test_write_l1.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common.egg-info/SOURCES.txt +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common.egg-info/dependency_links.txt +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/dkist_processing_common.egg-info/top_level.txt +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/docs/Makefile +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/docs/changelog.rst +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/docs/conf.py +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/docs/index.rst +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/docs/landing_page.rst +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/docs/make.bat +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/docs/requirements.txt +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/licenses/LICENSE.rst +0 -0
- {dkist_processing_common-12.6.2 → dkist_processing_common-12.6.3}/setup.cfg +0 -0
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
v12.6.3 (2026-02-25)
|
|
2
|
+
====================
|
|
3
|
+
|
|
4
|
+
Bugfixes
|
|
5
|
+
--------
|
|
6
|
+
|
|
7
|
+
- Fix `CSStepFrameFlower` and `NumFramesPerCSStepBud` to correctly handle the multiple modstates present in each CS step. (`#306 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/306>`__)
|
|
8
|
+
- Add correct HEADVERS, HEAD_URL, and CAL_URL keys to all dataset extras. (`#307 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/307>`__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
Misc
|
|
12
|
+
----
|
|
13
|
+
|
|
14
|
+
- Add `modulator_state` as an expected property on `L0FitsAccess` to be clear about the expected interface in instrument-specific
|
|
15
|
+
subclasses. In the base `L0FitsAccess` class this property will raise a `NotImplementedError`. (`#306 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/306>`__)
|
|
16
|
+
- Add `CSStepModstate` object, which adds modulation state awareness to the `CSStep` object used in parsing. (`#306 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/306>`__)
|
|
17
|
+
- Move to `dkist-fits-specifications` v4.22.0, which includes HEADVERS in the schema for dataset extras. (`#307 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/307>`__)
|
|
18
|
+
|
|
19
|
+
|
|
1
20
|
v12.6.2 (2026-02-19)
|
|
2
21
|
====================
|
|
3
22
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dkist-processing-common
|
|
3
|
-
Version: 12.6.
|
|
3
|
+
Version: 12.6.3
|
|
4
4
|
Summary: Common task classes used by the DKIST science data processing pipelines
|
|
5
5
|
Author-email: NSO / AURA <dkistdc@nso.edu>
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -15,7 +15,7 @@ Requires-Python: >=3.13
|
|
|
15
15
|
Description-Content-Type: text/x-rst
|
|
16
16
|
Requires-Dist: asdf<4.0.0,>=3.5.0
|
|
17
17
|
Requires-Dist: astropy>=7.0.0
|
|
18
|
-
Requires-Dist: dkist-fits-specifications<5.0,>=4.
|
|
18
|
+
Requires-Dist: dkist-fits-specifications<5.0,>=4.22.0
|
|
19
19
|
Requires-Dist: dkist-header-validator<6.0,>=5.3.0
|
|
20
20
|
Requires-Dist: dkist-processing-core==7.0.2
|
|
21
21
|
Requires-Dist: dkist-processing-pac<4.0,>=3.1
|
|
@@ -7,6 +7,7 @@ from collections import Counter
|
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
from datetime import timezone
|
|
10
|
+
from enum import StrEnum
|
|
10
11
|
from typing import Type
|
|
11
12
|
|
|
12
13
|
from dkist_processing_common.models.constants import BudName
|
|
@@ -27,8 +28,8 @@ class CSStep:
|
|
|
27
28
|
This class allows for an easy way to quickly compare fits access objects to determine if they come from the
|
|
28
29
|
same Calibration Sequence (CS) step. Each step in a CS is defined by the configuration of the GOS, namely the
|
|
29
30
|
status of the polarizer and retarder (in or out) and the angle of each. Because some CS schemes call for some
|
|
30
|
-
GOS configurations to repeat a check is also made against the observation time for each object; the
|
|
31
|
-
|
|
31
|
+
GOS configurations to repeat, a check is also made against the observation time for each object; the value of
|
|
32
|
+
``max_cs_time_sec`` defines a maximum difference in time where two exposures are considered different CS steps
|
|
32
33
|
regardless of GOS configuration.
|
|
33
34
|
|
|
34
35
|
This class can also be sorted. In this case, only the observation time is taken into account.
|
|
@@ -60,9 +61,10 @@ class CSStep:
|
|
|
60
61
|
self.max_cs_time_sec = max_cs_time_sec
|
|
61
62
|
|
|
62
63
|
self.fits_obj_repr = repr(fits_obj)
|
|
64
|
+
self.angle_round_ndigits = angle_round_ndigits
|
|
63
65
|
|
|
64
66
|
def __repr__(self):
|
|
65
|
-
return f"{self.__class__.__name__}({self.fits_obj_repr}, max_cs_time_sec={self.max_cs_time_sec!r})"
|
|
67
|
+
return f"{self.__class__.__name__}({self.fits_obj_repr}, max_cs_time_sec={self.max_cs_time_sec!r}, angle_round_ndigits={self.angle_round_ndigits})"
|
|
66
68
|
|
|
67
69
|
def __str__(self):
|
|
68
70
|
return "CS step with Pol = {}:{}, Ret = {}:{}, Dark = {}. Taken at {}".format(
|
|
@@ -76,8 +78,8 @@ class CSStep:
|
|
|
76
78
|
|
|
77
79
|
def __eq__(self, other: object) -> bool:
|
|
78
80
|
"""Two steps are equal if they have the same GOS configuration and are taken within some package-defined time of each other."""
|
|
79
|
-
if not isinstance(other,
|
|
80
|
-
raise TypeError(f"Cannot compare
|
|
81
|
+
if not isinstance(other, type(self)):
|
|
82
|
+
raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
|
|
81
83
|
|
|
82
84
|
for item in ["pol_in", "pol_theta", "ret_in", "ret_theta", "dark_in"]:
|
|
83
85
|
if getattr(self, item) != getattr(other, item):
|
|
@@ -98,6 +100,55 @@ class CSStep:
|
|
|
98
100
|
return hash((self.pol_in, self.pol_theta, self.ret_in, self.ret_theta, self.dark_in))
|
|
99
101
|
|
|
100
102
|
|
|
103
|
+
class CSStepModstate(CSStep):
|
|
104
|
+
"""
|
|
105
|
+
Subclass of `CSStep` that is also aware of the modulation state associated with a given fits object.
|
|
106
|
+
|
|
107
|
+
This is used for grouping files based on CS step and modstate, which is useful when determining the number of frames
|
|
108
|
+
in a given CS step.
|
|
109
|
+
|
|
110
|
+
The equality and hash functionality of `CSStep` is extended to modulation state, and sorting remains only based on
|
|
111
|
+
observe time.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(
|
|
115
|
+
self,
|
|
116
|
+
fits_obj: L0FitsAccess,
|
|
117
|
+
max_cs_time_sec: float,
|
|
118
|
+
angle_round_ndigits: int = 1,
|
|
119
|
+
):
|
|
120
|
+
super().__init__(
|
|
121
|
+
fits_obj=fits_obj,
|
|
122
|
+
max_cs_time_sec=max_cs_time_sec,
|
|
123
|
+
angle_round_ndigits=angle_round_ndigits,
|
|
124
|
+
)
|
|
125
|
+
self.modstate = fits_obj.modulator_state
|
|
126
|
+
|
|
127
|
+
def __repr__(self):
|
|
128
|
+
return f"{self.__class__.__name__}({self.fits_obj_repr}, max_cs_time_sec={self.max_cs_time_sec!r}, angle_round_ndigits={self.angle_round_ndigits})"
|
|
129
|
+
|
|
130
|
+
def __str__(self):
|
|
131
|
+
return (
|
|
132
|
+
"CS step with Pol = {}:{}, Ret = {}:{}, Dark = {}. Modstate = {}. Taken at {}".format(
|
|
133
|
+
self.pol_in,
|
|
134
|
+
self.pol_theta,
|
|
135
|
+
self.ret_in,
|
|
136
|
+
self.ret_theta,
|
|
137
|
+
self.dark_in,
|
|
138
|
+
self.modstate,
|
|
139
|
+
self.obs_time.isoformat(),
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def __eq__(self, other: object) -> bool:
|
|
144
|
+
return super().__eq__(other) and self.modstate == other.modstate
|
|
145
|
+
|
|
146
|
+
def __hash__(self) -> int:
|
|
147
|
+
return hash(
|
|
148
|
+
(self.pol_in, self.pol_theta, self.ret_in, self.ret_theta, self.dark_in, self.modstate)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
101
152
|
class CSStepFlower(Stem):
|
|
102
153
|
"""
|
|
103
154
|
Identify which CS Step a header belongs to.
|
|
@@ -167,7 +218,7 @@ class CSStepFrameFlower(Stem):
|
|
|
167
218
|
self.max_cs_step_time_sec = max_cs_step_time_sec
|
|
168
219
|
self.CS_step_dict = defaultdict(list)
|
|
169
220
|
|
|
170
|
-
def setter(self, fits_obj: L0FitsAccess) ->
|
|
221
|
+
def setter(self, fits_obj: L0FitsAccess) -> CSStepModstate | Type[SpilledDirt]:
|
|
171
222
|
"""
|
|
172
223
|
Set the CS Step for this fits object.
|
|
173
224
|
|
|
@@ -183,7 +234,10 @@ class CSStepFrameFlower(Stem):
|
|
|
183
234
|
if fits_obj.ip_task_type != "polcal":
|
|
184
235
|
return SpilledDirt
|
|
185
236
|
|
|
186
|
-
cs_step =
|
|
237
|
+
cs_step = CSStepModstate(
|
|
238
|
+
fits_obj,
|
|
239
|
+
max_cs_time_sec=self.max_cs_step_time_sec,
|
|
240
|
+
)
|
|
187
241
|
self.CS_step_dict[cs_step].append(cs_step)
|
|
188
242
|
return cs_step
|
|
189
243
|
|
|
@@ -204,8 +258,8 @@ class CSStepFrameFlower(Stem):
|
|
|
204
258
|
"""
|
|
205
259
|
cs_step_for_key = self.key_to_petal_dict[key]
|
|
206
260
|
|
|
207
|
-
# Use sorted's `key` here to guarantee we never hit `
|
|
208
|
-
# because they come from the same CS Step.
|
|
261
|
+
# Use sorted's `key` here to guarantee we never hit `CSStepModstate.__eq__`, which would consider all of these
|
|
262
|
+
# frames equal because they come from the same CS Step.
|
|
209
263
|
frames_in_cs_step_list = sorted(
|
|
210
264
|
self.CS_step_dict[cs_step_for_key], key=lambda cs: cs.obs_time
|
|
211
265
|
)
|
|
@@ -275,7 +329,7 @@ class NumFramesPerCSStepBud(ListStem):
|
|
|
275
329
|
super().__init__(stem_name=BudName.num_frames_per_cs_step)
|
|
276
330
|
self.max_cs_step_time_sec = max_cs_step_time_sec
|
|
277
331
|
|
|
278
|
-
def setter(self, fits_obj: L0FitsAccess) ->
|
|
332
|
+
def setter(self, fits_obj: L0FitsAccess) -> CSStepModstate | Type[SpilledDirt]:
|
|
279
333
|
"""
|
|
280
334
|
Return the CS Step for this fits object.
|
|
281
335
|
|
|
@@ -290,13 +344,16 @@ class NumFramesPerCSStepBud(ListStem):
|
|
|
290
344
|
"""
|
|
291
345
|
if fits_obj.ip_task_type != "polcal":
|
|
292
346
|
return SpilledDirt
|
|
293
|
-
return
|
|
347
|
+
return CSStepModstate(
|
|
348
|
+
fits_obj,
|
|
349
|
+
max_cs_time_sec=self.max_cs_step_time_sec,
|
|
350
|
+
)
|
|
294
351
|
|
|
295
352
|
def getter(self) -> int:
|
|
296
353
|
"""
|
|
297
354
|
Return number of frames per CS step.
|
|
298
355
|
|
|
299
|
-
We assume that each copy of the same `
|
|
356
|
+
We assume that each copy of the same `CSStepModstate` object represents a single frame.
|
|
300
357
|
|
|
301
358
|
Returns
|
|
302
359
|
-------
|
|
@@ -6,6 +6,27 @@ from dkist_processing_common.models.fits_access import MetadataKey
|
|
|
6
6
|
from dkist_processing_common.parsers.l1_fits_access import L1FitsAccess
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
class AbstractProperty:
|
|
10
|
+
"""
|
|
11
|
+
A class for defining properties expected to exist in subclasses, but that don't exist on the current class.
|
|
12
|
+
|
|
13
|
+
Use like this
|
|
14
|
+
|
|
15
|
+
.. code::
|
|
16
|
+
|
|
17
|
+
class BaseClass:
|
|
18
|
+
required_property = AbstractProperty()
|
|
19
|
+
|
|
20
|
+
Then accessing `BaseClass().required_property` will result in a `NotImplementedError`.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Inspired by https://stackoverflow.com/questions/56997102/python-notimplementederror-for-instance-attributes
|
|
24
|
+
def __get__(self, *args, **kwargs):
|
|
25
|
+
raise NotImplementedError(
|
|
26
|
+
"This property needs to be defined manually in an instrument subclass"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
9
30
|
class L0FitsAccess(L1FitsAccess):
|
|
10
31
|
"""
|
|
11
32
|
Class defining a fits access object for L0 input data.
|
|
@@ -20,6 +41,8 @@ class L0FitsAccess(L1FitsAccess):
|
|
|
20
41
|
A boolean indicating whether to 'squeeze' out dimensions of size 1
|
|
21
42
|
"""
|
|
22
43
|
|
|
44
|
+
modulator_state: int = AbstractProperty()
|
|
45
|
+
|
|
23
46
|
def __init__(
|
|
24
47
|
self,
|
|
25
48
|
hdu: fits.ImageHDU | fits.PrimaryHDU | fits.CompImageHDU,
|
|
@@ -9,6 +9,7 @@ from typing import Literal
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
from astropy.io import fits
|
|
11
11
|
from astropy.time import Time
|
|
12
|
+
from dkist_fits_specifications import __version__ as spec_version
|
|
12
13
|
from dkist_fits_specifications.utils.formatter import reformat_dataset_extra_header
|
|
13
14
|
from dkist_header_validator.spec_validators import spec_extras_validator
|
|
14
15
|
|
|
@@ -150,6 +151,8 @@ class WriteL1DatasetExtras(WriteL1Base, ABC):
|
|
|
150
151
|
start_datetime = datetime.fromisoformat(task_specific_date_begin)
|
|
151
152
|
end_datetime = datetime.fromisoformat(header_data.end_time)
|
|
152
153
|
|
|
154
|
+
inst_name = self.constants.instrument.lower()
|
|
155
|
+
calvers = self.version_from_module_name()
|
|
153
156
|
dataset_extra_header = {
|
|
154
157
|
DatasetExtraHeaderSection.common: {
|
|
155
158
|
"BUNIT": "count",
|
|
@@ -181,10 +184,11 @@ class WriteL1DatasetExtras(WriteL1Base, ABC):
|
|
|
181
184
|
"RECIPEID": self.metadata_store_recipe_run.recipeInstance.recipeId,
|
|
182
185
|
"RINSTID": self.metadata_store_recipe_run.recipeInstanceId,
|
|
183
186
|
"FILENAME": header_data.filename,
|
|
184
|
-
"
|
|
187
|
+
"HEADVERS": spec_version,
|
|
188
|
+
"HEAD_URL": f"{self.docs_base_url}/projects/data-products/en/v{spec_version}",
|
|
185
189
|
"INFO_URL": self.docs_base_url,
|
|
186
|
-
"CAL_URL": "",
|
|
187
|
-
"CALVERS":
|
|
190
|
+
"CAL_URL": f"{self.docs_base_url}/projects/{inst_name}/en/v{calvers}/{self.workflow_name}.html",
|
|
191
|
+
"CALVERS": calvers,
|
|
188
192
|
"IDSPARID": (
|
|
189
193
|
parameters.inputDatasetPartId
|
|
190
194
|
if (parameters := self.metadata_store_input_dataset_parameters)
|
|
@@ -7,6 +7,7 @@ from collections import defaultdict
|
|
|
7
7
|
from copy import deepcopy
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
from datetime import timedelta
|
|
10
|
+
from enum import StrEnum
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from random import choice
|
|
12
13
|
from random import randint
|
|
@@ -196,7 +197,9 @@ class CalibrationSequenceDataset(Spec122Dataset):
|
|
|
196
197
|
instrument="visp",
|
|
197
198
|
angle_max_random_perturbation: float = 0.049,
|
|
198
199
|
num_frames_per_CS_step: int = 5,
|
|
200
|
+
num_modstates: int = 8,
|
|
199
201
|
):
|
|
202
|
+
self.num_modstates = num_modstates
|
|
200
203
|
self.num_frames_per_CS_step = num_frames_per_CS_step
|
|
201
204
|
self.angle_max_random_perturbation = angle_max_random_perturbation
|
|
202
205
|
# Make up a Calibration sequence. Mostly random except for two clears and two darks at start and end, which
|
|
@@ -224,7 +227,10 @@ class CalibrationSequenceDataset(Spec122Dataset):
|
|
|
224
227
|
]
|
|
225
228
|
|
|
226
229
|
self.num_steps = len(self.pol_theta)
|
|
227
|
-
dataset_shape = (
|
|
230
|
+
dataset_shape = (
|
|
231
|
+
self.num_steps * self.num_modstates,
|
|
232
|
+
self.num_frames_per_CS_step,
|
|
233
|
+
) + array_shape[1:]
|
|
228
234
|
super().__init__(
|
|
229
235
|
dataset_shape,
|
|
230
236
|
array_shape,
|
|
@@ -240,7 +246,11 @@ class CalibrationSequenceDataset(Spec122Dataset):
|
|
|
240
246
|
|
|
241
247
|
@property
|
|
242
248
|
def cs_step(self) -> int:
|
|
243
|
-
return self.index // self.num_frames_per_CS_step
|
|
249
|
+
return self.index // (self.num_modstates * self.num_frames_per_CS_step)
|
|
250
|
+
|
|
251
|
+
@key_function("VISP_011")
|
|
252
|
+
def modstate(self, key: str) -> int:
|
|
253
|
+
return ((self.index // self.num_frames_per_CS_step) % self.num_modstates) + 1
|
|
244
254
|
|
|
245
255
|
@key_function("PAC__004")
|
|
246
256
|
def polarizer_status(self, key: str) -> str:
|
|
@@ -284,6 +294,17 @@ class NonPolCalDataset(Spec122Dataset):
|
|
|
284
294
|
self.add_constant_key("PAC__008", "DarkShutter")
|
|
285
295
|
|
|
286
296
|
|
|
297
|
+
class ModstateFitsAccess(L0FitsAccess):
|
|
298
|
+
def __init__(
|
|
299
|
+
self,
|
|
300
|
+
hdu: fits.ImageHDU | fits.PrimaryHDU | fits.CompImageHDU,
|
|
301
|
+
name: str | None = None,
|
|
302
|
+
auto_squeeze: bool = True,
|
|
303
|
+
):
|
|
304
|
+
super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
|
|
305
|
+
self.modulator_state: int = self.header["VSPSTNUM"]
|
|
306
|
+
|
|
307
|
+
|
|
287
308
|
@pytest.fixture(scope="session")
|
|
288
309
|
def cs_step_angle_round_ndigits() -> int:
|
|
289
310
|
return 1
|
|
@@ -296,6 +317,11 @@ def angle_random_max_perturbation(cs_step_angle_round_ndigits) -> float:
|
|
|
296
317
|
return 10**-cs_step_angle_round_ndigits / 2 - 10 ** -(cs_step_angle_round_ndigits + 2)
|
|
297
318
|
|
|
298
319
|
|
|
320
|
+
@pytest.fixture(scope="session")
|
|
321
|
+
def num_modstates() -> int:
|
|
322
|
+
return 4
|
|
323
|
+
|
|
324
|
+
|
|
299
325
|
@pytest.fixture(scope="session")
|
|
300
326
|
def num_frames_per_cs_step() -> int:
|
|
301
327
|
return 3
|
|
@@ -303,13 +329,14 @@ def num_frames_per_cs_step() -> int:
|
|
|
303
329
|
|
|
304
330
|
@pytest.fixture(scope="session")
|
|
305
331
|
def grouped_cal_sequence_fits_objs(
|
|
306
|
-
angle_random_max_perturbation, num_frames_per_cs_step
|
|
307
|
-
) -> dict[int, list[
|
|
332
|
+
angle_random_max_perturbation, num_modstates, num_frames_per_cs_step
|
|
333
|
+
) -> dict[int, list[ModstateFitsAccess]]:
|
|
308
334
|
ds = CalibrationSequenceDataset(
|
|
309
335
|
array_shape=(1, 2, 2),
|
|
310
336
|
time_delta=2.0,
|
|
311
337
|
angle_max_random_perturbation=angle_random_max_perturbation,
|
|
312
338
|
num_frames_per_CS_step=num_frames_per_cs_step,
|
|
339
|
+
num_modstates=num_modstates,
|
|
313
340
|
)
|
|
314
341
|
header_list = [
|
|
315
342
|
spec122_validator.validate_and_translate_to_214_l0(d.header(), return_type=fits.HDUList)[
|
|
@@ -319,9 +346,40 @@ def grouped_cal_sequence_fits_objs(
|
|
|
319
346
|
]
|
|
320
347
|
expected_cs_dict = defaultdict(list)
|
|
321
348
|
for i in range(ds.num_steps):
|
|
322
|
-
for
|
|
323
|
-
|
|
349
|
+
for m in range(ds.num_modstates):
|
|
350
|
+
for j in range(ds.num_frames_per_CS_step):
|
|
351
|
+
expected_cs_dict[i].append(ModstateFitsAccess.from_header(header_list.pop(0)))
|
|
352
|
+
|
|
353
|
+
# Make sure we used all the dataset frames
|
|
354
|
+
assert len(header_list) == 0
|
|
355
|
+
return expected_cs_dict
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@pytest.fixture(scope="session")
|
|
359
|
+
def grouped_cal_sequence_modstate_fits_objs(
|
|
360
|
+
angle_random_max_perturbation, num_modstates, num_frames_per_cs_step
|
|
361
|
+
) -> dict[int, dict[int, list[ModstateFitsAccess]]]:
|
|
362
|
+
ds = CalibrationSequenceDataset(
|
|
363
|
+
array_shape=(1, 2, 2),
|
|
364
|
+
time_delta=2.0,
|
|
365
|
+
angle_max_random_perturbation=angle_random_max_perturbation,
|
|
366
|
+
num_frames_per_CS_step=num_frames_per_cs_step,
|
|
367
|
+
num_modstates=num_modstates,
|
|
368
|
+
)
|
|
369
|
+
header_list = [
|
|
370
|
+
spec122_validator.validate_and_translate_to_214_l0(d.header(), return_type=fits.HDUList)[
|
|
371
|
+
0
|
|
372
|
+
].header
|
|
373
|
+
for d in ds
|
|
374
|
+
]
|
|
375
|
+
expected_cs_dict = defaultdict(lambda: defaultdict(list))
|
|
376
|
+
for i in range(ds.num_steps):
|
|
377
|
+
for m in range(ds.num_modstates):
|
|
378
|
+
for j in range(ds.num_frames_per_CS_step):
|
|
379
|
+
expected_cs_dict[i][m].append(ModstateFitsAccess.from_header(header_list.pop(0)))
|
|
324
380
|
|
|
381
|
+
# Make sure we used all the dataset frames
|
|
382
|
+
assert len(header_list) == 0
|
|
325
383
|
return expected_cs_dict
|
|
326
384
|
|
|
327
385
|
|
|
@@ -341,7 +399,7 @@ def non_polcal_fits_objs() -> list[L0FitsAccess]:
|
|
|
341
399
|
@pytest.fixture(scope="session")
|
|
342
400
|
def max_cs_step_time_sec() -> float:
|
|
343
401
|
"""Max CS step time in seconds"""
|
|
344
|
-
return
|
|
402
|
+
return 30.0
|
|
345
403
|
|
|
346
404
|
|
|
347
405
|
####################################
|
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import pytest
|
|
8
8
|
from astropy.io import fits
|
|
9
|
+
from dkist_fits_specifications import __version__ as spec_version
|
|
9
10
|
|
|
10
11
|
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
11
12
|
from dkist_processing_common.codecs.fits import fits_array_decoder
|
|
@@ -385,6 +386,18 @@ def test_construct_dataset_extras(
|
|
|
385
386
|
"DEMODULATION MATRICES",
|
|
386
387
|
"POLCAL AS SCIENCE",
|
|
387
388
|
]
|
|
389
|
+
assert header["INFO_URL"] == task.docs_base_url
|
|
390
|
+
assert header["HEADVERS"] == spec_version
|
|
391
|
+
assert (
|
|
392
|
+
header["HEAD_URL"]
|
|
393
|
+
== f"{task.docs_base_url}/projects/data-products/en/v{spec_version}"
|
|
394
|
+
)
|
|
395
|
+
calvers = task.version_from_module_name()
|
|
396
|
+
assert header["CALVERS"] == calvers
|
|
397
|
+
assert (
|
|
398
|
+
header["CAL_URL"]
|
|
399
|
+
== f"{task.docs_base_url}/projects/{task.constants.instrument.lower()}/en/v{calvers}/{task.workflow_name}.html"
|
|
400
|
+
)
|
|
388
401
|
if header["IPTASK"] == "POLCAL":
|
|
389
402
|
assert "POLANGLE" not in header
|
|
390
403
|
else:
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from dkist_processing_common.parsers.cs_step import CSStep
|
|
7
|
+
from dkist_processing_common.parsers.cs_step import CSStepModstate
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_equality_correct(
|
|
11
|
+
grouped_cal_sequence_fits_objs, max_cs_step_time_sec, cs_step_angle_round_ndigits
|
|
12
|
+
):
|
|
13
|
+
"""
|
|
14
|
+
Given: A set of PolCal headers
|
|
15
|
+
When: Converting them to CSStep objects and comparing them
|
|
16
|
+
Then: All headers belonging to the same step produce CSStep objects that are equal and are not equal to CSStep objects
|
|
17
|
+
from other steps
|
|
18
|
+
"""
|
|
19
|
+
num_steps = len(grouped_cal_sequence_fits_objs)
|
|
20
|
+
for step in range(num_steps):
|
|
21
|
+
for i in range(num_steps):
|
|
22
|
+
objs_equal = CSStep(
|
|
23
|
+
grouped_cal_sequence_fits_objs[step][0], max_cs_step_time_sec
|
|
24
|
+
) == CSStep(grouped_cal_sequence_fits_objs[i][0], max_cs_step_time_sec)
|
|
25
|
+
assert objs_equal == (step == i)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_csstepmodstate_equality_correct(
|
|
29
|
+
grouped_cal_sequence_modstate_fits_objs,
|
|
30
|
+
num_modstates,
|
|
31
|
+
max_cs_step_time_sec,
|
|
32
|
+
cs_step_angle_round_ndigits,
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Given: A set of PolCal headers
|
|
36
|
+
When: Converting them to CSStepModstate objects and comparing them
|
|
37
|
+
Then: All headers belonging to the same step and modstate produce CSStepModstate objects that are equal and that are
|
|
38
|
+
not equal to CSStepModstate objects from other steps and modstates
|
|
39
|
+
"""
|
|
40
|
+
num_steps = len(grouped_cal_sequence_modstate_fits_objs)
|
|
41
|
+
for step, modstate_dict in grouped_cal_sequence_modstate_fits_objs.items():
|
|
42
|
+
for modstate in range(num_modstates):
|
|
43
|
+
for ss in range(num_steps):
|
|
44
|
+
for mm in range(num_modstates):
|
|
45
|
+
objs_equal = CSStepModstate(
|
|
46
|
+
grouped_cal_sequence_modstate_fits_objs[step][modstate][0],
|
|
47
|
+
max_cs_step_time_sec,
|
|
48
|
+
) == CSStepModstate(
|
|
49
|
+
grouped_cal_sequence_modstate_fits_objs[ss][mm][0],
|
|
50
|
+
max_cs_step_time_sec,
|
|
51
|
+
)
|
|
52
|
+
assert objs_equal == (ss == step and mm == modstate)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.mark.parametrize(
|
|
56
|
+
"step_class",
|
|
57
|
+
[pytest.param(CSStep, id="CSStep"), pytest.param(CSStepModstate, id="CSStepModstate")],
|
|
58
|
+
)
|
|
59
|
+
def test_not_equal_non_CS_Step_type(
|
|
60
|
+
grouped_cal_sequence_fits_objs, step_class, max_cs_step_time_sec
|
|
61
|
+
):
|
|
62
|
+
"""
|
|
63
|
+
Given: A PolCal header and resulting CSStep[Modstate] object
|
|
64
|
+
When: Testing equality with a non CSStep object
|
|
65
|
+
Then: An error is raised
|
|
66
|
+
"""
|
|
67
|
+
cs_step = step_class(grouped_cal_sequence_fits_objs[0][0], max_cs_step_time_sec)
|
|
68
|
+
with pytest.raises(
|
|
69
|
+
TypeError,
|
|
70
|
+
match=f"Cannot compare <class 'dkist_processing_common.parsers.cs_step.{step_class.__name__}'> with <class 'int'>",
|
|
71
|
+
):
|
|
72
|
+
_ = cs_step == 1
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.parametrize(
|
|
76
|
+
"step_class",
|
|
77
|
+
[pytest.param(CSStep, id="CSStep"), pytest.param(CSStepModstate, id="CSStepModstate")],
|
|
78
|
+
)
|
|
79
|
+
def test_order_correct(grouped_cal_sequence_fits_objs, step_class, max_cs_step_time_sec):
|
|
80
|
+
"""
|
|
81
|
+
Given: A set of PolCal headers
|
|
82
|
+
When: Converting them to CSStep[Modstate] objects and ordering them
|
|
83
|
+
Then: The step objects are correctly ordered by observe time
|
|
84
|
+
"""
|
|
85
|
+
cs_step_list = [
|
|
86
|
+
step_class(header, max_cs_step_time_sec)
|
|
87
|
+
for header in sum(grouped_cal_sequence_fits_objs.values(), [])
|
|
88
|
+
]
|
|
89
|
+
random.shuffle(cs_step_list) # Just to mix it up a bit
|
|
90
|
+
time_list = [c.obs_time for c in cs_step_list]
|
|
91
|
+
cs_sort_idx = np.argsort(cs_step_list)
|
|
92
|
+
time_sort_idx = np.argsort(time_list)
|
|
93
|
+
assert np.array_equal(cs_sort_idx, time_sort_idx)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@pytest.mark.parametrize(
|
|
97
|
+
"step_class",
|
|
98
|
+
[pytest.param(CSStep, id="CSStep"), pytest.param(CSStepModstate, id="CSStepModstate")],
|
|
99
|
+
)
|
|
100
|
+
def test_repr(grouped_cal_sequence_fits_objs, step_class):
|
|
101
|
+
"""
|
|
102
|
+
Given: An `L0FitsAccess` object
|
|
103
|
+
When: Using that object to instantiate `CSStep[Modstate]`
|
|
104
|
+
Then: The repr of the instance is correct
|
|
105
|
+
"""
|
|
106
|
+
fits_obj = grouped_cal_sequence_fits_objs[0][0]
|
|
107
|
+
cs_step_obj = step_class(fits_obj, max_cs_time_sec=10, angle_round_ndigits=5)
|
|
108
|
+
assert (
|
|
109
|
+
repr(cs_step_obj)
|
|
110
|
+
== f"{step_class.__name__}({fits_obj!r}, max_cs_time_sec=10, angle_round_ndigits=5)"
|
|
111
|
+
)
|
|
@@ -145,6 +145,17 @@ class FitsAccessWithNaxisKeys(FitsAccessBase):
|
|
|
145
145
|
self.naxis2 = self.header[MetadataKeyWithNaxisKeys.naxis2]
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
class FitsAccessWithModstate(L0FitsAccess):
|
|
149
|
+
def __init__(
|
|
150
|
+
self,
|
|
151
|
+
hdu: fits.ImageHDU | fits.PrimaryHDU | fits.CompImageHDU,
|
|
152
|
+
name: str | None = None,
|
|
153
|
+
auto_squeeze: bool = True,
|
|
154
|
+
):
|
|
155
|
+
super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
|
|
156
|
+
self.modulator_state = 5
|
|
157
|
+
|
|
158
|
+
|
|
148
159
|
def test_metadata_keys_in_access_bases(hdu_with_no_data):
|
|
149
160
|
"""
|
|
150
161
|
Given: a set of metadata key names in the MetadataKey sting enumeration
|
|
@@ -348,3 +359,20 @@ def test_from_header_naxis_preserved(header_type):
|
|
|
348
359
|
assert fits_obj.naxis == 2
|
|
349
360
|
assert fits_obj.naxis1 == 123
|
|
350
361
|
assert fits_obj.naxis2 == 456
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def test_l0fitsaccess_modulator_state(hdu_with_no_data):
|
|
365
|
+
"""
|
|
366
|
+
Given: Instances of both the base `L0FitsAccess` class and a subclass of the same that defines the `modulator_state` property
|
|
367
|
+
When: Accessing the `modulator_state` property on both classes
|
|
368
|
+
Then: The base `L0FitsAccess` instance raises the appropriate error and the subclass returns the correct value
|
|
369
|
+
"""
|
|
370
|
+
l0_obj = L0FitsAccess(hdu=hdu_with_no_data)
|
|
371
|
+
with pytest.raises(
|
|
372
|
+
NotImplementedError,
|
|
373
|
+
match="This property needs to be defined manually in an instrument subclass",
|
|
374
|
+
):
|
|
375
|
+
_ = l0_obj.modulator_state
|
|
376
|
+
|
|
377
|
+
child_obj = FitsAccessWithModstate(hdu=hdu_with_no_data)
|
|
378
|
+
assert child_obj.modulator_state == 5
|
|
@@ -501,14 +501,15 @@ def test_cs_step_flower(grouped_cal_sequence_fits_objs, non_polcal_fits_objs, ma
|
|
|
501
501
|
|
|
502
502
|
assert len(list(cs_step_flower.petals)) == len(list(grouped_cal_sequence_fits_objs.keys()))
|
|
503
503
|
for step_petal in cs_step_flower.petals:
|
|
504
|
-
assert sorted(step_petal.keys) ==
|
|
505
|
-
f"step_{step_petal.value}_file_{i}" for i in range(len(step_petal.keys))
|
|
506
|
-
|
|
504
|
+
assert sorted(step_petal.keys) == sorted(
|
|
505
|
+
[f"step_{step_petal.value}_file_{i}" for i in range(len(step_petal.keys))]
|
|
506
|
+
)
|
|
507
507
|
|
|
508
508
|
|
|
509
509
|
def test_cs_step_frame_flower(
|
|
510
|
-
|
|
510
|
+
grouped_cal_sequence_modstate_fits_objs,
|
|
511
511
|
non_polcal_fits_objs,
|
|
512
|
+
num_modstates,
|
|
512
513
|
num_frames_per_cs_step,
|
|
513
514
|
max_cs_step_time_sec,
|
|
514
515
|
):
|
|
@@ -517,24 +518,31 @@ def test_cs_step_frame_flower(
|
|
|
517
518
|
When: Updating the CSStepFrameFlower with all headers
|
|
518
519
|
Then: The flower correctly organizes the PolCal frames and ignores the non-PolCal frames
|
|
519
520
|
"""
|
|
520
|
-
cs_step_frame_flower = CSStepFrameFlower(
|
|
521
|
+
cs_step_frame_flower = CSStepFrameFlower(
|
|
522
|
+
max_cs_step_time_sec=max_cs_step_time_sec,
|
|
523
|
+
)
|
|
521
524
|
assert cs_step_frame_flower.stem_name == StemName.cs_step_frame
|
|
522
525
|
|
|
523
|
-
num_cs_steps = len(
|
|
524
|
-
for step,
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
526
|
+
num_cs_steps = len(grouped_cal_sequence_modstate_fits_objs)
|
|
527
|
+
for step, modstate_dict in grouped_cal_sequence_modstate_fits_objs.items():
|
|
528
|
+
for m, objs in modstate_dict.items():
|
|
529
|
+
file_names = [f"step_{step}_mod_{m}_file_{i}" for i in range(len(objs))]
|
|
530
|
+
# Reverse the lists here just to make sure we're not relying on pre-sorted inputs
|
|
531
|
+
for o, k in zip(objs[::-1], file_names[::-1]):
|
|
532
|
+
cs_step_frame_flower.update(k, o)
|
|
529
533
|
|
|
530
534
|
for o in non_polcal_fits_objs:
|
|
531
535
|
cs_step_frame_flower.update("non_polcal", o)
|
|
532
536
|
|
|
533
537
|
assert len(list(cs_step_frame_flower.petals)) == num_frames_per_cs_step
|
|
534
538
|
for frame_petal in cs_step_frame_flower.petals:
|
|
535
|
-
assert sorted(frame_petal.keys) ==
|
|
536
|
-
|
|
537
|
-
|
|
539
|
+
assert sorted(frame_petal.keys) == sorted(
|
|
540
|
+
[
|
|
541
|
+
f"step_{step}_mod_{m}_file_{frame_petal.value}"
|
|
542
|
+
for step in range(num_cs_steps)
|
|
543
|
+
for m in range(num_modstates)
|
|
544
|
+
]
|
|
545
|
+
)
|
|
538
546
|
|
|
539
547
|
|
|
540
548
|
def test_num_cs_step_bud(
|
|
@@ -569,7 +577,9 @@ def test_num_frames_per_cs_step_bud(
|
|
|
569
577
|
When: Updating the bud with the headers
|
|
570
578
|
Then: The bud correctly parses the number of frames per CS step
|
|
571
579
|
"""
|
|
572
|
-
num_frames_per_cs_bud_obj = NumFramesPerCSStepBud(
|
|
580
|
+
num_frames_per_cs_bud_obj = NumFramesPerCSStepBud(
|
|
581
|
+
max_cs_step_time_sec=max_cs_step_time_sec,
|
|
582
|
+
)
|
|
573
583
|
num_cs_steps = len(grouped_cal_sequence_fits_objs)
|
|
574
584
|
for i, (step, objs) in enumerate(grouped_cal_sequence_fits_objs.items()):
|
|
575
585
|
if skip_frame and i == num_cs_steps - 1:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dkist-processing-common
|
|
3
|
-
Version: 12.6.
|
|
3
|
+
Version: 12.6.3
|
|
4
4
|
Summary: Common task classes used by the DKIST science data processing pipelines
|
|
5
5
|
Author-email: NSO / AURA <dkistdc@nso.edu>
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -15,7 +15,7 @@ Requires-Python: >=3.13
|
|
|
15
15
|
Description-Content-Type: text/x-rst
|
|
16
16
|
Requires-Dist: asdf<4.0.0,>=3.5.0
|
|
17
17
|
Requires-Dist: astropy>=7.0.0
|
|
18
|
-
Requires-Dist: dkist-fits-specifications<5.0,>=4.
|
|
18
|
+
Requires-Dist: dkist-fits-specifications<5.0,>=4.22.0
|
|
19
19
|
Requires-Dist: dkist-header-validator<6.0,>=5.3.0
|
|
20
20
|
Requires-Dist: dkist-processing-core==7.0.2
|
|
21
21
|
Requires-Dist: dkist-processing-pac<4.0,>=3.1
|