dkist-processing-visp 2.20.14__py3-none-any.whl → 5.1.1__py3-none-any.whl
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_visp/__init__.py +1 -0
- dkist_processing_visp/config.py +1 -0
- dkist_processing_visp/models/constants.py +61 -20
- dkist_processing_visp/models/fits_access.py +20 -0
- dkist_processing_visp/models/metric_code.py +10 -0
- dkist_processing_visp/models/parameters.py +129 -24
- dkist_processing_visp/models/tags.py +22 -1
- dkist_processing_visp/models/task_name.py +1 -0
- dkist_processing_visp/parsers/map_repeats.py +1 -0
- dkist_processing_visp/parsers/modulator_states.py +1 -0
- dkist_processing_visp/parsers/polarimeter_mode.py +4 -2
- dkist_processing_visp/parsers/raster_step.py +4 -1
- dkist_processing_visp/parsers/spectrograph_configuration.py +75 -0
- dkist_processing_visp/parsers/time.py +24 -14
- dkist_processing_visp/parsers/visp_l0_fits_access.py +19 -8
- dkist_processing_visp/parsers/visp_l1_fits_access.py +1 -0
- dkist_processing_visp/tasks/__init__.py +1 -0
- dkist_processing_visp/tasks/assemble_movie.py +1 -0
- dkist_processing_visp/tasks/background_light.py +2 -1
- dkist_processing_visp/tasks/dark.py +5 -4
- dkist_processing_visp/tasks/geometric.py +132 -20
- dkist_processing_visp/tasks/instrument_polarization.py +128 -18
- dkist_processing_visp/tasks/l1_output_data.py +203 -0
- dkist_processing_visp/tasks/lamp.py +53 -93
- dkist_processing_visp/tasks/make_movie_frames.py +8 -6
- dkist_processing_visp/tasks/mixin/beam_access.py +1 -0
- dkist_processing_visp/tasks/mixin/corrections.py +54 -4
- dkist_processing_visp/tasks/mixin/downsample.py +1 -0
- dkist_processing_visp/tasks/parse.py +50 -17
- dkist_processing_visp/tasks/quality_metrics.py +5 -4
- dkist_processing_visp/tasks/science.py +126 -46
- dkist_processing_visp/tasks/solar.py +896 -456
- dkist_processing_visp/tasks/visp_base.py +4 -3
- dkist_processing_visp/tasks/write_l1.py +38 -10
- dkist_processing_visp/tests/conftest.py +145 -47
- dkist_processing_visp/tests/header_models.py +157 -20
- dkist_processing_visp/tests/local_trial_workflows/l0_cals_only.py +21 -78
- dkist_processing_visp/tests/local_trial_workflows/l0_polcals_as_science.py +421 -0
- dkist_processing_visp/tests/local_trial_workflows/l0_solar_gain_as_science.py +387 -0
- dkist_processing_visp/tests/local_trial_workflows/l0_to_l1.py +18 -75
- dkist_processing_visp/tests/local_trial_workflows/local_trial_helpers.py +346 -14
- dkist_processing_visp/tests/test_assemble_movie.py +2 -3
- dkist_processing_visp/tests/test_assemble_quality.py +89 -4
- dkist_processing_visp/tests/test_background_light.py +51 -44
- dkist_processing_visp/tests/test_dark.py +4 -3
- dkist_processing_visp/tests/test_downsample.py +1 -0
- dkist_processing_visp/tests/test_fits_access.py +43 -0
- dkist_processing_visp/tests/test_geometric.py +45 -4
- dkist_processing_visp/tests/test_instrument_polarization.py +72 -9
- dkist_processing_visp/tests/test_lamp.py +22 -26
- dkist_processing_visp/tests/test_make_movie_frames.py +4 -4
- dkist_processing_visp/tests/test_map_repeats.py +3 -1
- dkist_processing_visp/tests/test_parameters.py +122 -21
- dkist_processing_visp/tests/test_parse.py +164 -18
- dkist_processing_visp/tests/test_quality.py +3 -4
- dkist_processing_visp/tests/test_science.py +113 -15
- dkist_processing_visp/tests/test_solar.py +318 -99
- dkist_processing_visp/tests/test_visp_constants.py +38 -8
- dkist_processing_visp/tests/test_workflows.py +1 -0
- dkist_processing_visp/tests/test_write_l1.py +22 -3
- dkist_processing_visp/workflows/__init__.py +1 -0
- dkist_processing_visp/workflows/l0_processing.py +10 -3
- dkist_processing_visp/workflows/trial_workflows.py +8 -2
- dkist_processing_visp-5.1.1.dist-info/METADATA +552 -0
- dkist_processing_visp-5.1.1.dist-info/RECORD +94 -0
- {dkist_processing_visp-2.20.14.dist-info → dkist_processing_visp-5.1.1.dist-info}/WHEEL +1 -1
- docs/conf.py +5 -1
- docs/gain_correction.rst +52 -44
- docs/science_calibration.rst +7 -0
- dkist_processing_visp/tasks/mixin/line_zones.py +0 -115
- dkist_processing_visp-2.20.14.dist-info/METADATA +0 -196
- dkist_processing_visp-2.20.14.dist-info/RECORD +0 -89
- {dkist_processing_visp-2.20.14.dist-info → dkist_processing_visp-5.1.1.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,6 @@ from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
|
6
6
|
from dkist_processing_common.codecs.fits import fits_array_encoder
|
|
7
7
|
from dkist_processing_common.models.tags import Tag
|
|
8
8
|
from dkist_processing_common.models.task_name import TaskName
|
|
9
|
-
from dkist_processing_common.tests.conftest import FakeGQLClient
|
|
10
9
|
|
|
11
10
|
from dkist_processing_visp.models.tags import VispTag
|
|
12
11
|
from dkist_processing_visp.tasks.quality_metrics import VispL0QualityMetrics
|
|
@@ -92,7 +91,7 @@ def visp_l1_quality_task(tmp_path, pol_mode, recipe_run_id, init_visp_constants_
|
|
|
92
91
|
with VispL1QualityMetrics(
|
|
93
92
|
recipe_run_id=recipe_run_id, workflow_name="science_calibration", workflow_version="VX.Y"
|
|
94
93
|
) as task:
|
|
95
|
-
task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path)
|
|
94
|
+
task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path, recipe_run_id=recipe_run_id)
|
|
96
95
|
|
|
97
96
|
yield task, num_map_scans, num_raster_steps, num_stokes
|
|
98
97
|
task._purge()
|
|
@@ -132,7 +131,7 @@ def test_l0_quality_task(
|
|
|
132
131
|
|
|
133
132
|
|
|
134
133
|
@pytest.mark.parametrize("pol_mode", ["observe_polarimetric", "observe_intensity"])
|
|
135
|
-
def test_l1_quality_task(visp_l1_quality_task, pol_mode, mocker):
|
|
134
|
+
def test_l1_quality_task(visp_l1_quality_task, pol_mode, mocker, fake_gql_client):
|
|
136
135
|
"""
|
|
137
136
|
Given: A VispL1QualityMetrics task
|
|
138
137
|
When: Calling the task instance
|
|
@@ -140,7 +139,7 @@ def test_l1_quality_task(visp_l1_quality_task, pol_mode, mocker):
|
|
|
140
139
|
and a single noise measurement and datetime is recorded for L1 file for each Stokes Q, U, and V
|
|
141
140
|
"""
|
|
142
141
|
mocker.patch(
|
|
143
|
-
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=
|
|
142
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
|
|
144
143
|
)
|
|
145
144
|
# When
|
|
146
145
|
task, num_maps, num_steps, num_stokes = visp_l1_quality_task
|
|
@@ -12,8 +12,8 @@ from dkist_header_validator import spec122_validator
|
|
|
12
12
|
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
13
13
|
from dkist_processing_common.codecs.fits import fits_array_encoder
|
|
14
14
|
from dkist_processing_common.codecs.fits import fits_hdu_decoder
|
|
15
|
+
from dkist_processing_common.models.fits_access import MetadataKey
|
|
15
16
|
from dkist_processing_common.models.tags import Tag
|
|
16
|
-
from dkist_processing_common.tests.conftest import FakeGQLClient
|
|
17
17
|
|
|
18
18
|
from dkist_processing_visp.models.tags import VispStemName
|
|
19
19
|
from dkist_processing_visp.models.tags import VispTag
|
|
@@ -144,7 +144,7 @@ def dummy_calibration_collection():
|
|
|
144
144
|
|
|
145
145
|
dark_dict = {VispTag.beam(beam): {VispTag.readout_exp_time(0.04): np.zeros(intermediate_shape)}}
|
|
146
146
|
background_dict = {VispTag.beam(beam): np.zeros(intermediate_shape)}
|
|
147
|
-
solar_dict = {VispTag.beam(beam):
|
|
147
|
+
solar_dict = {VispTag.beam(beam): np.ones(intermediate_shape)}
|
|
148
148
|
angle_dict = {VispTag.beam(beam): 0.0}
|
|
149
149
|
spec_dict = {VispTag.beam(beam): np.zeros(intermediate_shape[1])}
|
|
150
150
|
offset_dict = {VispTag.beam(beam): {VispTag.modstate(modstate): np.zeros(2)}}
|
|
@@ -184,7 +184,7 @@ def headers_with_dates() -> tuple[list[fits.Header], str, int, int]:
|
|
|
184
184
|
]
|
|
185
185
|
random.shuffle(headers) # Shuffle to make sure they're not already in time order
|
|
186
186
|
for h in headers:
|
|
187
|
-
h[
|
|
187
|
+
h[MetadataKey.fpa_exposure_time_ms] = exp_time # Exposure time, in ms
|
|
188
188
|
|
|
189
189
|
return headers, start_time, exp_time, time_delta
|
|
190
190
|
|
|
@@ -252,10 +252,14 @@ def calibration_collection_with_full_overlap_slice() -> CalibrationCollection:
|
|
|
252
252
|
|
|
253
253
|
@pytest.mark.parametrize(
|
|
254
254
|
"background_on",
|
|
255
|
-
[pytest.param(True, id="
|
|
255
|
+
[pytest.param(True, id="background_on"), pytest.param(False, id="background_off")],
|
|
256
256
|
)
|
|
257
257
|
def test_science_calibration_task(
|
|
258
|
-
science_calibration_task,
|
|
258
|
+
science_calibration_task,
|
|
259
|
+
background_on,
|
|
260
|
+
assign_input_dataset_doc_to_task,
|
|
261
|
+
mocker,
|
|
262
|
+
fake_gql_client,
|
|
259
263
|
):
|
|
260
264
|
"""
|
|
261
265
|
Given: A ScienceCalibration task
|
|
@@ -264,7 +268,7 @@ def test_science_calibration_task(
|
|
|
264
268
|
"""
|
|
265
269
|
|
|
266
270
|
mocker.patch(
|
|
267
|
-
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=
|
|
271
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
|
|
268
272
|
)
|
|
269
273
|
|
|
270
274
|
# When
|
|
@@ -304,7 +308,8 @@ def test_science_calibration_task(
|
|
|
304
308
|
task=task, num_modstates=num_modstates, data_shape=intermediate_shape, offsets=offsets
|
|
305
309
|
)
|
|
306
310
|
write_dummy_intermediate_solar_cals_to_task(
|
|
307
|
-
task=task,
|
|
311
|
+
task=task,
|
|
312
|
+
data_shape=intermediate_shape,
|
|
308
313
|
)
|
|
309
314
|
write_demod_matrices_to_task(task=task, num_modstates=num_modstates)
|
|
310
315
|
write_input_observe_frames_to_task(
|
|
@@ -360,10 +365,10 @@ def test_science_calibration_task(
|
|
|
360
365
|
assert header["VSPMAP"] == map_scan
|
|
361
366
|
|
|
362
367
|
# Check that WCS keys were updated
|
|
363
|
-
if offsets[1, 0, 0]
|
|
364
|
-
assert header["CRPIX2"] == input_header["CRPIX2"] - np.ceil(offsets[1, 0, 0])
|
|
365
|
-
if offsets[1, 0, 1]
|
|
366
|
-
assert header["CRPIX1"] == input_header["CRPIX1"] - np.ceil(offsets[1, 0, 1])
|
|
368
|
+
if offsets[1, 0, 0] < 0:
|
|
369
|
+
assert header["CRPIX2"] == input_header["CRPIX2"] - np.ceil(-offsets[1, 0, 0])
|
|
370
|
+
if offsets[1, 0, 1] < 0:
|
|
371
|
+
assert header["CRPIX1"] == input_header["CRPIX1"] - np.ceil(-offsets[1, 0, 1])
|
|
367
372
|
|
|
368
373
|
quality_files = task.read(tags=[Tag.quality("TASK_TYPES")])
|
|
369
374
|
for file in quality_files:
|
|
@@ -416,7 +421,7 @@ def test_readout_normalization_correct(
|
|
|
416
421
|
)
|
|
417
422
|
|
|
418
423
|
# When:
|
|
419
|
-
corrected_array, _ = task.correct_single_frame(
|
|
424
|
+
corrected_array, _, _ = task.correct_single_frame(
|
|
420
425
|
beam=1,
|
|
421
426
|
modstate=1,
|
|
422
427
|
raster_step=1,
|
|
@@ -509,7 +514,7 @@ def test_compute_date_keys_compressed_headers(
|
|
|
509
514
|
[[1.0, 2.0], [11.0, 10.0], [3.0, 2.0]], # Beam 2
|
|
510
515
|
]
|
|
511
516
|
),
|
|
512
|
-
[slice(
|
|
517
|
+
[slice(0, -11, None), slice(0, -10, None)],
|
|
513
518
|
),
|
|
514
519
|
(
|
|
515
520
|
np.array(
|
|
@@ -518,7 +523,7 @@ def test_compute_date_keys_compressed_headers(
|
|
|
518
523
|
[[-1.0, -2.0], [-11.0, -10.0], [-3.0, -2.0]], # Beam 2
|
|
519
524
|
]
|
|
520
525
|
),
|
|
521
|
-
[slice(
|
|
526
|
+
[slice(11, None, None), slice(10, None, None)],
|
|
522
527
|
),
|
|
523
528
|
(
|
|
524
529
|
np.array(
|
|
@@ -527,7 +532,7 @@ def test_compute_date_keys_compressed_headers(
|
|
|
527
532
|
[[1.0, 2.0], [-11.0, 10.0], [-3.0, -2.0]], # Beam 2
|
|
528
533
|
]
|
|
529
534
|
),
|
|
530
|
-
[slice(
|
|
535
|
+
[slice(11, -10, None), slice(2, -10, None)],
|
|
531
536
|
),
|
|
532
537
|
],
|
|
533
538
|
ids=["All positive", "All negative", "Positive and negative"],
|
|
@@ -577,3 +582,96 @@ def test_combine_beams(
|
|
|
577
582
|
expected = np.ones((10, 10, 4)) * 2.5
|
|
578
583
|
|
|
579
584
|
np.testing.assert_array_equal(data, expected)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
@pytest.mark.parametrize(
|
|
588
|
+
"shifts",
|
|
589
|
+
# Shifts have shape (num_beams, num_modstates, 2)
|
|
590
|
+
# So the inner-most lists below (e.g., [5.0, 6.0]) correspond to [x_shift, y_shit]
|
|
591
|
+
[
|
|
592
|
+
np.array(
|
|
593
|
+
[
|
|
594
|
+
[[0.0, 0.0], [10.0, 2.0], [5.0, 6.0]], # Beam 1
|
|
595
|
+
[[1.0, 2.0], [-11.0, 10.0], [-3.0, -2.0]], # Beam 2
|
|
596
|
+
]
|
|
597
|
+
),
|
|
598
|
+
],
|
|
599
|
+
ids=["Positive and negative"],
|
|
600
|
+
)
|
|
601
|
+
def test_combine_and_cut_nan_masks(
|
|
602
|
+
science_calibration_task, calibration_collection_with_geo_shifts, shifts
|
|
603
|
+
):
|
|
604
|
+
"""
|
|
605
|
+
Given: A ScienceCalibration task and NaN masks, along with geometric shifts
|
|
606
|
+
When: Combining the two NaN masks
|
|
607
|
+
Then: The final mask has NaN values in the correct place and is correctly cropped
|
|
608
|
+
"""
|
|
609
|
+
nan_1_location = [0, 1]
|
|
610
|
+
nan_2_location = [50, 50]
|
|
611
|
+
nan_3_location = [4, 1]
|
|
612
|
+
nan_4_location = [55, 63]
|
|
613
|
+
nan_mask_shape = (100, 100)
|
|
614
|
+
nan_mask_1 = np.zeros(shape=nan_mask_shape)
|
|
615
|
+
nan_mask_1[nan_1_location[0], nan_1_location[1]] = np.nan
|
|
616
|
+
nan_mask_1[nan_2_location[0], nan_2_location[1]] = np.nan
|
|
617
|
+
nan_mask_2 = np.zeros(shape=nan_mask_shape)
|
|
618
|
+
nan_mask_2[nan_3_location[0], nan_3_location[1]] = np.nan
|
|
619
|
+
nan_mask_2[nan_4_location[0], nan_4_location[1]] = np.nan
|
|
620
|
+
task, _, _, _, _, _ = science_calibration_task
|
|
621
|
+
combined_nan_mask = task.combine_and_cut_nan_masks(
|
|
622
|
+
nan_masks=[nan_mask_1, nan_mask_2], calibrations=calibration_collection_with_geo_shifts
|
|
623
|
+
)
|
|
624
|
+
beam_1_shifts = shifts[0]
|
|
625
|
+
beam_2_shifts = shifts[1]
|
|
626
|
+
beam_1_x_shifts = [i[0] for i in beam_1_shifts]
|
|
627
|
+
beam_2_x_shifts = [i[0] for i in beam_2_shifts]
|
|
628
|
+
beam_1_y_shifts = [i[1] for i in beam_1_shifts]
|
|
629
|
+
beam_2_y_shifts = [i[1] for i in beam_2_shifts]
|
|
630
|
+
x_shifts = beam_1_x_shifts + beam_2_x_shifts
|
|
631
|
+
y_shifts = beam_1_y_shifts + beam_2_y_shifts
|
|
632
|
+
assert combined_nan_mask.shape == (
|
|
633
|
+
nan_mask_shape[0] - (max(x_shifts) - min(x_shifts)),
|
|
634
|
+
nan_mask_shape[1] - (max(y_shifts) - min(y_shifts)),
|
|
635
|
+
)
|
|
636
|
+
# Check that one NaN value from each original mask is present in the combined mask and in the correct place
|
|
637
|
+
assert (
|
|
638
|
+
combined_nan_mask[
|
|
639
|
+
nan_2_location[0] - int(abs(min(x_shifts))), nan_2_location[1] - int(abs(min(y_shifts)))
|
|
640
|
+
]
|
|
641
|
+
== True
|
|
642
|
+
)
|
|
643
|
+
assert (
|
|
644
|
+
combined_nan_mask[
|
|
645
|
+
nan_4_location[0] - int(abs(min(x_shifts))), nan_4_location[1] - int(abs(min(y_shifts)))
|
|
646
|
+
]
|
|
647
|
+
== True
|
|
648
|
+
)
|
|
649
|
+
assert np.sum(combined_nan_mask) == 2 # only two NaN values are in the final mask
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def test_generate_nan_mask(science_calibration_task, dummy_calibration_collection):
|
|
653
|
+
"""
|
|
654
|
+
Given: a calibration collection
|
|
655
|
+
When: calculating the NaN mask to use
|
|
656
|
+
Then: the mask takes up some, but not all, of the frame size
|
|
657
|
+
"""
|
|
658
|
+
task, _, _, _, _, _ = science_calibration_task
|
|
659
|
+
calibration_collection, _, _ = dummy_calibration_collection
|
|
660
|
+
beam = 1
|
|
661
|
+
modstate = 1
|
|
662
|
+
solar_gain_array = calibration_collection.solar_gain[VispTag.beam(beam)]
|
|
663
|
+
angle = calibration_collection.angle[VispTag.beam(beam)]
|
|
664
|
+
spec_shift = calibration_collection.spec_shift[VispTag.beam(beam)]
|
|
665
|
+
state_offset = calibration_collection.state_offset[VispTag.beam(beam)][
|
|
666
|
+
VispTag.modstate(modstate)
|
|
667
|
+
]
|
|
668
|
+
nan_mask = task.generate_nan_mask(
|
|
669
|
+
solar_corrected_array=np.random.random(size=solar_gain_array.shape),
|
|
670
|
+
state_offset=state_offset,
|
|
671
|
+
angle=angle,
|
|
672
|
+
spec_shift=spec_shift,
|
|
673
|
+
)
|
|
674
|
+
# Some of the mask is marked as NaN but not all
|
|
675
|
+
assert np.sum(nan_mask) < np.size(nan_mask)
|
|
676
|
+
# Ensure that only zeroes and ones are in the mask
|
|
677
|
+
assert set(np.unique(nan_mask)) == {0, 1}
|