dkist-processing-common 10.5.4__py3-none-any.whl → 12.1.0rc1__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.
- changelog/280.misc.rst +1 -0
- changelog/282.feature.2.rst +2 -0
- changelog/282.feature.rst +2 -0
- changelog/284.feature.rst +1 -0
- changelog/285.feature.rst +2 -0
- changelog/285.misc.rst +2 -0
- changelog/286.feature.rst +2 -0
- changelog/287.misc.rst +1 -0
- dkist_processing_common/__init__.py +1 -0
- dkist_processing_common/_util/constants.py +1 -0
- dkist_processing_common/_util/graphql.py +1 -0
- dkist_processing_common/_util/scratch.py +9 -9
- dkist_processing_common/_util/tags.py +1 -0
- dkist_processing_common/codecs/array.py +20 -0
- dkist_processing_common/codecs/asdf.py +9 -3
- dkist_processing_common/codecs/basemodel.py +22 -0
- dkist_processing_common/codecs/bytes.py +1 -0
- dkist_processing_common/codecs/fits.py +37 -9
- dkist_processing_common/codecs/iobase.py +1 -0
- dkist_processing_common/codecs/json.py +1 -0
- dkist_processing_common/codecs/path.py +1 -0
- dkist_processing_common/codecs/quality.py +1 -1
- dkist_processing_common/codecs/str.py +1 -0
- dkist_processing_common/config.py +64 -25
- dkist_processing_common/manual.py +6 -8
- dkist_processing_common/models/constants.py +373 -37
- dkist_processing_common/models/dkist_location.py +27 -0
- dkist_processing_common/models/fits_access.py +48 -0
- dkist_processing_common/models/flower_pot.py +231 -9
- dkist_processing_common/models/fried_parameter.py +41 -0
- dkist_processing_common/models/graphql.py +66 -75
- dkist_processing_common/models/input_dataset.py +117 -0
- dkist_processing_common/models/message.py +1 -1
- dkist_processing_common/models/message_queue_binding.py +1 -1
- dkist_processing_common/models/metric_code.py +2 -0
- dkist_processing_common/models/parameters.py +65 -28
- dkist_processing_common/models/quality.py +50 -5
- dkist_processing_common/models/tags.py +23 -21
- dkist_processing_common/models/task_name.py +3 -2
- dkist_processing_common/models/telemetry.py +28 -0
- dkist_processing_common/models/wavelength.py +3 -1
- dkist_processing_common/parsers/average_bud.py +46 -0
- dkist_processing_common/parsers/cs_step.py +13 -12
- dkist_processing_common/parsers/dsps_repeat.py +6 -4
- dkist_processing_common/parsers/experiment_id_bud.py +12 -4
- dkist_processing_common/parsers/id_bud.py +42 -27
- dkist_processing_common/parsers/l0_fits_access.py +5 -3
- dkist_processing_common/parsers/l1_fits_access.py +51 -23
- dkist_processing_common/parsers/lookup_bud.py +125 -0
- dkist_processing_common/parsers/near_bud.py +21 -20
- dkist_processing_common/parsers/observing_program_id_bud.py +24 -0
- dkist_processing_common/parsers/proposal_id_bud.py +13 -5
- dkist_processing_common/parsers/quality.py +2 -0
- dkist_processing_common/parsers/retarder.py +32 -0
- dkist_processing_common/parsers/single_value_single_key_flower.py +6 -1
- dkist_processing_common/parsers/task.py +8 -6
- dkist_processing_common/parsers/time.py +178 -72
- dkist_processing_common/parsers/unique_bud.py +21 -22
- dkist_processing_common/parsers/wavelength.py +5 -3
- dkist_processing_common/tasks/__init__.py +3 -2
- dkist_processing_common/tasks/assemble_movie.py +4 -3
- dkist_processing_common/tasks/base.py +59 -60
- dkist_processing_common/tasks/l1_output_data.py +54 -53
- dkist_processing_common/tasks/mixin/globus.py +24 -27
- dkist_processing_common/tasks/mixin/interservice_bus.py +1 -0
- dkist_processing_common/tasks/mixin/metadata_store.py +108 -243
- dkist_processing_common/tasks/mixin/object_store.py +22 -0
- dkist_processing_common/tasks/mixin/quality/__init__.py +1 -0
- dkist_processing_common/tasks/mixin/quality/_base.py +8 -1
- dkist_processing_common/tasks/mixin/quality/_metrics.py +166 -14
- dkist_processing_common/tasks/output_data_base.py +4 -3
- dkist_processing_common/tasks/parse_l0_input_data.py +277 -15
- dkist_processing_common/tasks/quality_metrics.py +9 -9
- dkist_processing_common/tasks/teardown.py +7 -7
- dkist_processing_common/tasks/transfer_input_data.py +67 -69
- dkist_processing_common/tasks/trial_catalog.py +77 -17
- dkist_processing_common/tasks/trial_output_data.py +16 -17
- dkist_processing_common/tasks/write_l1.py +102 -72
- dkist_processing_common/tests/conftest.py +32 -173
- dkist_processing_common/tests/mock_metadata_store.py +271 -0
- dkist_processing_common/tests/test_assemble_movie.py +4 -4
- dkist_processing_common/tests/test_assemble_quality.py +32 -4
- dkist_processing_common/tests/test_base.py +5 -19
- dkist_processing_common/tests/test_codecs.py +103 -12
- dkist_processing_common/tests/test_constants.py +15 -0
- dkist_processing_common/tests/test_dkist_location.py +15 -0
- dkist_processing_common/tests/test_fits_access.py +56 -19
- dkist_processing_common/tests/test_flower_pot.py +147 -5
- dkist_processing_common/tests/test_fried_parameter.py +27 -0
- dkist_processing_common/tests/test_input_dataset.py +78 -361
- dkist_processing_common/tests/test_interservice_bus.py +1 -0
- dkist_processing_common/tests/test_interservice_bus_mixin.py +1 -1
- dkist_processing_common/tests/test_manual_processing.py +33 -0
- dkist_processing_common/tests/test_output_data_base.py +5 -7
- dkist_processing_common/tests/test_parameters.py +71 -22
- dkist_processing_common/tests/test_parse_l0_input_data.py +115 -32
- dkist_processing_common/tests/test_publish_catalog_messages.py +2 -24
- dkist_processing_common/tests/test_quality.py +1 -0
- dkist_processing_common/tests/test_quality_mixin.py +255 -23
- dkist_processing_common/tests/test_scratch.py +2 -1
- dkist_processing_common/tests/test_stems.py +511 -168
- dkist_processing_common/tests/test_submit_dataset_metadata.py +3 -7
- dkist_processing_common/tests/test_tags.py +1 -0
- dkist_processing_common/tests/test_task_name.py +1 -1
- dkist_processing_common/tests/test_task_parsing.py +17 -7
- dkist_processing_common/tests/test_teardown.py +28 -24
- dkist_processing_common/tests/test_transfer_input_data.py +270 -125
- dkist_processing_common/tests/test_transfer_l1_output_data.py +2 -3
- dkist_processing_common/tests/test_trial_catalog.py +83 -8
- dkist_processing_common/tests/test_trial_output_data.py +46 -73
- dkist_processing_common/tests/test_workflow_task_base.py +8 -10
- dkist_processing_common/tests/test_write_l1.py +298 -76
- dkist_processing_common-12.1.0rc1.dist-info/METADATA +265 -0
- dkist_processing_common-12.1.0rc1.dist-info/RECORD +134 -0
- {dkist_processing_common-10.5.4.dist-info → dkist_processing_common-12.1.0rc1.dist-info}/WHEEL +1 -1
- docs/conf.py +1 -0
- docs/index.rst +1 -1
- docs/landing_page.rst +13 -0
- dkist_processing_common/tasks/mixin/input_dataset.py +0 -166
- dkist_processing_common-10.5.4.dist-info/METADATA +0 -175
- dkist_processing_common-10.5.4.dist-info/RECORD +0 -112
- {dkist_processing_common-10.5.4.dist-info → dkist_processing_common-12.1.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
These classes should not be directly mixed in to anything. They are pre-mixed into the top-level QualityMixin
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import copy
|
|
6
7
|
import json
|
|
7
8
|
import logging
|
|
@@ -12,14 +13,19 @@ from typing import Any
|
|
|
12
13
|
from typing import Iterable
|
|
13
14
|
from typing import Literal
|
|
14
15
|
|
|
16
|
+
import astropy.units as u
|
|
15
17
|
import numpy as np
|
|
18
|
+
from astropy.wcs import WCS
|
|
16
19
|
from dkist_processing_pac.fitter.fitter_parameters import CU_PARAMS
|
|
17
20
|
from dkist_processing_pac.fitter.fitter_parameters import GLOBAL_PARAMS
|
|
18
21
|
from dkist_processing_pac.fitter.fitter_parameters import TELESCOPE_PARAMS
|
|
19
22
|
from dkist_processing_pac.fitter.fitting_core import compare_I
|
|
20
23
|
from dkist_processing_pac.fitter.polcal_fitter import PolcalFitter
|
|
21
24
|
from pandas import DataFrame
|
|
25
|
+
from solar_wavelength_calibration.fitter.wavelength_fitter import FitResult
|
|
22
26
|
|
|
27
|
+
from dkist_processing_common.codecs.json import json_decoder
|
|
28
|
+
from dkist_processing_common.models.fried_parameter import r0_valid
|
|
23
29
|
from dkist_processing_common.models.metric_code import MetricCode
|
|
24
30
|
from dkist_processing_common.models.quality import EfficiencyHistograms
|
|
25
31
|
from dkist_processing_common.models.quality import ModulationMatrixHistograms
|
|
@@ -28,6 +34,7 @@ from dkist_processing_common.models.quality import PlotHistogram
|
|
|
28
34
|
from dkist_processing_common.models.quality import PlotRaincloud
|
|
29
35
|
from dkist_processing_common.models.quality import ReportMetric
|
|
30
36
|
from dkist_processing_common.models.quality import SimpleTable
|
|
37
|
+
from dkist_processing_common.models.quality import VerticalMultiPanePlot2D
|
|
31
38
|
from dkist_processing_common.models.tags import Tag
|
|
32
39
|
|
|
33
40
|
logger = logging.getLogger(__name__)
|
|
@@ -203,7 +210,7 @@ class _SimplePlotQualityMixin:
|
|
|
203
210
|
return warnings
|
|
204
211
|
|
|
205
212
|
def quality_store_ao_status_and_fried_parameter(
|
|
206
|
-
self, datetimes: list[str], values: list[list[bool
|
|
213
|
+
self, datetimes: list[str], values: list[list[bool | float]]
|
|
207
214
|
):
|
|
208
215
|
"""
|
|
209
216
|
Collect and store datetime / value pairs for the boolean AO status and Fried parameter.
|
|
@@ -213,23 +220,27 @@ class _SimplePlotQualityMixin:
|
|
|
213
220
|
Because of how L1Metric.has_metric works, empty lists will not be passed to this method.
|
|
214
221
|
However, because of how L1Metric.store_metric works, one or both values can be None.
|
|
215
222
|
"""
|
|
216
|
-
|
|
217
|
-
ao_not_none = [ao for ao in
|
|
223
|
+
ao_lock_values = [value[0] for value in values]
|
|
224
|
+
ao_not_none = [ao for ao in ao_lock_values if ao is not None]
|
|
218
225
|
if len(ao_not_none) != 0:
|
|
219
226
|
self._record_values(values=ao_not_none, tags=Tag.quality(MetricCode.ao_status))
|
|
220
227
|
fried_values = [value[1] for value in values]
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
]
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
ao_oob_values = [value[2] for value in values]
|
|
229
|
+
fried_values_to_plot = []
|
|
230
|
+
datetimes_to_plot = []
|
|
231
|
+
# For each set of input data, check if the r0 is considered valid based on all data
|
|
232
|
+
for i in range(len(fried_values)):
|
|
233
|
+
if r0_valid(
|
|
234
|
+
r0=fried_values[i],
|
|
235
|
+
ao_lock=ao_lock_values[i],
|
|
236
|
+
num_out_of_bounds_ao_values=ao_oob_values[i],
|
|
237
|
+
):
|
|
238
|
+
fried_values_to_plot.append(fried_values[i])
|
|
239
|
+
datetimes_to_plot.append(datetimes[i])
|
|
240
|
+
if len(fried_values_to_plot) != 0:
|
|
230
241
|
self._record_2d_plot_values(
|
|
231
|
-
x_values=
|
|
232
|
-
y_values=
|
|
242
|
+
x_values=datetimes_to_plot,
|
|
243
|
+
y_values=fried_values_to_plot,
|
|
233
244
|
tags=Tag.quality(MetricCode.fried_parameter),
|
|
234
245
|
)
|
|
235
246
|
|
|
@@ -1319,3 +1330,144 @@ class _PolcalQualityMixin:
|
|
|
1319
1330
|
base_str += " bins."
|
|
1320
1331
|
|
|
1321
1332
|
return base_str
|
|
1333
|
+
|
|
1334
|
+
|
|
1335
|
+
class _WavecalQualityMixin:
|
|
1336
|
+
"""Mixin class supporting the recording and building of wavecal-related metrics."""
|
|
1337
|
+
|
|
1338
|
+
def quality_store_wavecal_results(
|
|
1339
|
+
self,
|
|
1340
|
+
*,
|
|
1341
|
+
input_wavelength: u.Quantity,
|
|
1342
|
+
input_spectrum: np.ndarray,
|
|
1343
|
+
fit_result: FitResult,
|
|
1344
|
+
weights: None | np.ndarray = None,
|
|
1345
|
+
):
|
|
1346
|
+
"""
|
|
1347
|
+
Store the results of a wavelength solution fit.
|
|
1348
|
+
|
|
1349
|
+
Namely, save the:
|
|
1350
|
+
|
|
1351
|
+
* Input spectrum and wavelength
|
|
1352
|
+
* Best-fit combined atlas spectrum
|
|
1353
|
+
* Best-fit wavelength vector
|
|
1354
|
+
* Fit residuals
|
|
1355
|
+
|
|
1356
|
+
Note that the residuals are the *unweighed* residuals.
|
|
1357
|
+
"""
|
|
1358
|
+
weight_data = np.ones(input_wavelength.size) if weights is None else weights
|
|
1359
|
+
prepared_weights = fit_result.prepared_weights
|
|
1360
|
+
residuals = fit_result.minimizer_result.residual / prepared_weights
|
|
1361
|
+
residuals[~np.isfinite(residuals)] = 0.0
|
|
1362
|
+
normalized_residuals = residuals / input_spectrum
|
|
1363
|
+
|
|
1364
|
+
best_fit_atlas = fit_result.best_fit_atlas
|
|
1365
|
+
best_fit_wavelength = fit_result.best_fit_wavelength_vector
|
|
1366
|
+
|
|
1367
|
+
finite_idx = (
|
|
1368
|
+
np.isfinite(input_wavelength)
|
|
1369
|
+
* np.isfinite(input_spectrum)
|
|
1370
|
+
* np.isfinite(best_fit_wavelength)
|
|
1371
|
+
* np.isfinite(best_fit_atlas)
|
|
1372
|
+
* np.isfinite(normalized_residuals)
|
|
1373
|
+
* np.isfinite(weight_data)
|
|
1374
|
+
)
|
|
1375
|
+
|
|
1376
|
+
data = {
|
|
1377
|
+
"input_wavelength_nm": input_wavelength.to_value(u.nm)[finite_idx].tolist(),
|
|
1378
|
+
"input_spectrum": input_spectrum[finite_idx].tolist(),
|
|
1379
|
+
"best_fit_wavelength_nm": best_fit_wavelength[finite_idx].tolist(),
|
|
1380
|
+
"best_fit_atlas": best_fit_atlas[finite_idx].tolist(),
|
|
1381
|
+
"normalized_residuals": normalized_residuals[finite_idx].tolist(),
|
|
1382
|
+
"weights": None if weights is None else weight_data[finite_idx].tolist(),
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
self._record_values(values=data, tags=[Tag.quality(MetricCode.wavecal_fit)])
|
|
1386
|
+
|
|
1387
|
+
def quality_build_wavecal_results(self) -> dict:
|
|
1388
|
+
"""Build a ReportMetric containing a multi-pane plot showing the fit spectra and residuals."""
|
|
1389
|
+
data = next(self.read(tags=[Tag.quality(MetricCode.wavecal_fit)], decoder=json_decoder))
|
|
1390
|
+
|
|
1391
|
+
input_wave_list = data["input_wavelength_nm"]
|
|
1392
|
+
input_spectrum_list = data["input_spectrum"]
|
|
1393
|
+
best_fit_wave_list = data["best_fit_wavelength_nm"]
|
|
1394
|
+
best_fit_atlas_list = data["best_fit_atlas"]
|
|
1395
|
+
residuals_list = data["normalized_residuals"]
|
|
1396
|
+
weights = data["weights"]
|
|
1397
|
+
|
|
1398
|
+
fit_series = {
|
|
1399
|
+
"Best Fit Observations": [best_fit_wave_list, input_spectrum_list],
|
|
1400
|
+
"Input Spectrum": [input_wave_list, input_spectrum_list],
|
|
1401
|
+
"Best Fit Atlas": [best_fit_wave_list, best_fit_atlas_list],
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
# Set the colors and zorder here manually because the JSON-ization of the quality data means we can't be sure of
|
|
1405
|
+
# the order these will be plotted in and thus can't rely on the default color-cycler in `dkist-quality`.
|
|
1406
|
+
fit_plot_kwargs = {
|
|
1407
|
+
"Best Fit Observations": {
|
|
1408
|
+
"ls": "-",
|
|
1409
|
+
"lw": 4,
|
|
1410
|
+
"alpha": 0.8,
|
|
1411
|
+
"ms": 0,
|
|
1412
|
+
"color": "#FAA61C",
|
|
1413
|
+
"zorder": 2,
|
|
1414
|
+
},
|
|
1415
|
+
"Input Spectrum": {"ls": "-", "alpha": 0.4, "ms": 0, "color": "#1E317A", "zorder": 2.1},
|
|
1416
|
+
"Best Fit Atlas": {"color": "k", "ls": "-", "ms": 0, "zorder": 2.2},
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
fit_plot = Plot2D(
|
|
1420
|
+
xlabel="Wavelength [nm]",
|
|
1421
|
+
ylabel="Signal",
|
|
1422
|
+
series_data=fit_series,
|
|
1423
|
+
plot_kwargs=fit_plot_kwargs,
|
|
1424
|
+
sort_series=False,
|
|
1425
|
+
)
|
|
1426
|
+
|
|
1427
|
+
residuals_series = {"Residuals": [best_fit_wave_list, residuals_list]}
|
|
1428
|
+
residuals_plot_kwargs = {"Residuals": {"ls": "-", "color": "k", "ms": 0}}
|
|
1429
|
+
|
|
1430
|
+
y_min = np.nanpercentile(residuals_list, 2)
|
|
1431
|
+
y_max = np.nanpercentile(residuals_list, 98)
|
|
1432
|
+
y_range = y_max - y_min
|
|
1433
|
+
y_min -= 0.1 * y_range
|
|
1434
|
+
y_max += 0.1 * y_range
|
|
1435
|
+
residuals_plot = Plot2D(
|
|
1436
|
+
xlabel="Wavelength [nm]",
|
|
1437
|
+
ylabel=r"$\frac{\mathrm{Obs - Atlas}}{\mathrm{Obs}}$",
|
|
1438
|
+
series_data=residuals_series,
|
|
1439
|
+
plot_kwargs=residuals_plot_kwargs,
|
|
1440
|
+
ylim=(y_min, y_max),
|
|
1441
|
+
)
|
|
1442
|
+
|
|
1443
|
+
plot_list = [fit_plot, residuals_plot]
|
|
1444
|
+
height_ratios = [1.5, 1.0]
|
|
1445
|
+
if weights is not None:
|
|
1446
|
+
weight_series = {"Weights": [best_fit_wave_list, weights]}
|
|
1447
|
+
weight_plot_kwargs = {"Weights": {"ls": "-", "color": "k", "ms": 0}}
|
|
1448
|
+
weight_plot = Plot2D(
|
|
1449
|
+
xlabel="Wavelength [nm]",
|
|
1450
|
+
ylabel="Fit Weights",
|
|
1451
|
+
series_data=weight_series,
|
|
1452
|
+
plot_kwargs=weight_plot_kwargs,
|
|
1453
|
+
)
|
|
1454
|
+
plot_list.append(weight_plot)
|
|
1455
|
+
height_ratios.append(1.0)
|
|
1456
|
+
|
|
1457
|
+
full_plot = VerticalMultiPanePlot2D(
|
|
1458
|
+
top_to_bottom_plot_list=plot_list,
|
|
1459
|
+
match_x_axes=True,
|
|
1460
|
+
no_gap=True,
|
|
1461
|
+
top_to_bottom_height_ratios=height_ratios,
|
|
1462
|
+
)
|
|
1463
|
+
|
|
1464
|
+
metric = ReportMetric(
|
|
1465
|
+
name="Wavelength Calibration Results",
|
|
1466
|
+
description="These plots show the wavelength solution computed based on fits to a Solar FTS atlas. "
|
|
1467
|
+
"The top plot shows the input and best-fit spectra along with the best-fit atlas, which is "
|
|
1468
|
+
"a combination of Solar and Telluric spectra. The bottom plot shows the fit residuals.",
|
|
1469
|
+
metric_code=MetricCode.wavecal_fit,
|
|
1470
|
+
multi_plot_data=full_plot,
|
|
1471
|
+
)
|
|
1472
|
+
|
|
1473
|
+
return metric.model_dump()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Base class that supports common output data methods and paths."""
|
|
2
|
+
|
|
2
3
|
import logging
|
|
3
4
|
from abc import ABC
|
|
4
5
|
from abc import abstractmethod
|
|
@@ -19,7 +20,7 @@ class OutputDataBase(WorkflowTaskBase, ABC):
|
|
|
19
20
|
@cached_property
|
|
20
21
|
def destination_bucket(self) -> str:
|
|
21
22
|
"""Get the destination bucket."""
|
|
22
|
-
return self.
|
|
23
|
+
return self.metadata_store_recipe_run.configuration.destination_bucket
|
|
23
24
|
|
|
24
25
|
def format_object_key(self, path: Path) -> str:
|
|
25
26
|
"""
|
|
@@ -57,10 +58,10 @@ class TransferDataBase(OutputDataBase, ObjectStoreMixin, ABC):
|
|
|
57
58
|
|
|
58
59
|
def run(self) -> None:
|
|
59
60
|
"""Transfer the data and cleanup any folders."""
|
|
60
|
-
with self.
|
|
61
|
+
with self.telemetry_span("Transfer objects"):
|
|
61
62
|
self.transfer_objects()
|
|
62
63
|
|
|
63
|
-
with self.
|
|
64
|
+
with self.telemetry_span("Remove folder objects"):
|
|
64
65
|
self.remove_folder_objects()
|
|
65
66
|
|
|
66
67
|
@abstractmethod
|
|
@@ -20,7 +20,7 @@ that makes the rest of the pipeline easy to write.
|
|
|
20
20
|
In other words, we can find exactly the frame we need (tags) and, once we have it, we never need to look
|
|
21
21
|
at a different frame to get information (constants).
|
|
22
22
|
"""
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
import logging
|
|
25
25
|
from abc import ABC
|
|
26
26
|
from abc import abstractmethod
|
|
@@ -28,24 +28,33 @@ from typing import TypeVar
|
|
|
28
28
|
|
|
29
29
|
from dkist_processing_common.codecs.fits import fits_access_decoder
|
|
30
30
|
from dkist_processing_common.models.constants import BudName
|
|
31
|
+
from dkist_processing_common.models.fits_access import MetadataKey
|
|
31
32
|
from dkist_processing_common.models.flower_pot import FlowerPot
|
|
32
33
|
from dkist_processing_common.models.flower_pot import Stem
|
|
33
34
|
from dkist_processing_common.models.flower_pot import Thorn
|
|
34
35
|
from dkist_processing_common.models.tags import Tag
|
|
36
|
+
from dkist_processing_common.models.task_name import TaskName
|
|
37
|
+
from dkist_processing_common.parsers.average_bud import TaskAverageBud
|
|
35
38
|
from dkist_processing_common.parsers.experiment_id_bud import ContributingExperimentIdsBud
|
|
36
39
|
from dkist_processing_common.parsers.experiment_id_bud import ExperimentIdBud
|
|
40
|
+
from dkist_processing_common.parsers.lookup_bud import TaskTimeLookupBud
|
|
41
|
+
from dkist_processing_common.parsers.observing_program_id_bud import (
|
|
42
|
+
TaskContributingObservingProgramExecutionIdsBud,
|
|
43
|
+
)
|
|
37
44
|
from dkist_processing_common.parsers.proposal_id_bud import ContributingProposalIdsBud
|
|
38
45
|
from dkist_processing_common.parsers.proposal_id_bud import ProposalIdBud
|
|
46
|
+
from dkist_processing_common.parsers.task import parse_header_ip_task_with_gains
|
|
39
47
|
from dkist_processing_common.parsers.time import AverageCadenceBud
|
|
40
48
|
from dkist_processing_common.parsers.time import MaximumCadenceBud
|
|
41
49
|
from dkist_processing_common.parsers.time import MinimumCadenceBud
|
|
50
|
+
from dkist_processing_common.parsers.time import TaskDateBeginBud
|
|
42
51
|
from dkist_processing_common.parsers.time import TaskExposureTimesBud
|
|
43
52
|
from dkist_processing_common.parsers.time import TaskReadoutExpTimesBud
|
|
44
53
|
from dkist_processing_common.parsers.time import VarianceCadenceBud
|
|
54
|
+
from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
|
|
45
55
|
from dkist_processing_common.parsers.unique_bud import UniqueBud
|
|
46
56
|
from dkist_processing_common.tasks.base import WorkflowTaskBase
|
|
47
57
|
|
|
48
|
-
|
|
49
58
|
__all__ = [
|
|
50
59
|
"ParseL0InputDataBase",
|
|
51
60
|
"ParseDataBase",
|
|
@@ -58,10 +67,254 @@ logger = logging.getLogger(__name__)
|
|
|
58
67
|
S = TypeVar("S", bound=Stem)
|
|
59
68
|
|
|
60
69
|
|
|
70
|
+
def dataset_extra_bud_factory() -> list[S]:
|
|
71
|
+
"""Provide constant buds for use in dataset extras."""
|
|
72
|
+
return [
|
|
73
|
+
UniqueBud(constant_name=BudName.camera_id, metadata_key=MetadataKey.camera_id),
|
|
74
|
+
UniqueBud(constant_name=BudName.camera_name, metadata_key=MetadataKey.camera_name),
|
|
75
|
+
UniqueBud(
|
|
76
|
+
constant_name=BudName.camera_bit_depth, metadata_key=MetadataKey.camera_bit_depth
|
|
77
|
+
),
|
|
78
|
+
UniqueBud(
|
|
79
|
+
constant_name=BudName.hardware_binning_x, metadata_key=MetadataKey.hardware_binning_x
|
|
80
|
+
),
|
|
81
|
+
UniqueBud(
|
|
82
|
+
constant_name=BudName.hardware_binning_y, metadata_key=MetadataKey.hardware_binning_x
|
|
83
|
+
),
|
|
84
|
+
UniqueBud(
|
|
85
|
+
constant_name=BudName.software_binning_x, metadata_key=MetadataKey.software_binning_x
|
|
86
|
+
),
|
|
87
|
+
UniqueBud(
|
|
88
|
+
constant_name=BudName.software_binning_y, metadata_key=MetadataKey.software_binning_y
|
|
89
|
+
),
|
|
90
|
+
UniqueBud(
|
|
91
|
+
constant_name=BudName.hls_version,
|
|
92
|
+
metadata_key=MetadataKey.hls_version,
|
|
93
|
+
),
|
|
94
|
+
TaskContributingObservingProgramExecutionIdsBud(
|
|
95
|
+
constant_name=BudName.dark_observing_program_execution_ids,
|
|
96
|
+
ip_task_types=TaskName.dark,
|
|
97
|
+
),
|
|
98
|
+
TaskContributingObservingProgramExecutionIdsBud(
|
|
99
|
+
constant_name=BudName.solar_gain_observing_program_execution_ids,
|
|
100
|
+
ip_task_types=TaskName.solar_gain,
|
|
101
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
102
|
+
),
|
|
103
|
+
TaskContributingObservingProgramExecutionIdsBud(
|
|
104
|
+
constant_name=BudName.polcal_observing_program_execution_ids,
|
|
105
|
+
ip_task_types=TaskName.polcal,
|
|
106
|
+
),
|
|
107
|
+
TaskTimeLookupBud(
|
|
108
|
+
constant_name=BudName.dark_num_raw_frames_per_fpa,
|
|
109
|
+
key_metadata_key=MetadataKey.sensor_readout_exposure_time_ms,
|
|
110
|
+
value_metadata_key=MetadataKey.num_raw_frames_per_fpa,
|
|
111
|
+
ip_task_types=TaskName.dark,
|
|
112
|
+
),
|
|
113
|
+
TaskUniqueBud(
|
|
114
|
+
constant_name=BudName.solar_gain_num_raw_frames_per_fpa,
|
|
115
|
+
metadata_key=MetadataKey.num_raw_frames_per_fpa,
|
|
116
|
+
ip_task_types=TaskName.solar_gain,
|
|
117
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
118
|
+
),
|
|
119
|
+
TaskUniqueBud(
|
|
120
|
+
constant_name=BudName.polcal_num_raw_frames_per_fpa,
|
|
121
|
+
metadata_key=MetadataKey.num_raw_frames_per_fpa,
|
|
122
|
+
ip_task_types=TaskName.polcal,
|
|
123
|
+
),
|
|
124
|
+
TaskUniqueBud(
|
|
125
|
+
constant_name=BudName.solar_gain_telescope_tracking_mode,
|
|
126
|
+
metadata_key=MetadataKey.telescope_tracking_mode,
|
|
127
|
+
ip_task_types=TaskName.solar_gain,
|
|
128
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
129
|
+
),
|
|
130
|
+
TaskUniqueBud(
|
|
131
|
+
constant_name=BudName.polcal_telescope_tracking_mode,
|
|
132
|
+
metadata_key=MetadataKey.telescope_tracking_mode,
|
|
133
|
+
ip_task_types=TaskName.polcal,
|
|
134
|
+
),
|
|
135
|
+
TaskUniqueBud(
|
|
136
|
+
constant_name=BudName.solar_gain_coude_table_tracking_mode,
|
|
137
|
+
metadata_key=MetadataKey.coude_table_tracking_mode,
|
|
138
|
+
ip_task_types=TaskName.solar_gain,
|
|
139
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
140
|
+
),
|
|
141
|
+
TaskUniqueBud(
|
|
142
|
+
constant_name=BudName.polcal_coude_table_tracking_mode,
|
|
143
|
+
metadata_key=MetadataKey.coude_table_tracking_mode,
|
|
144
|
+
ip_task_types=TaskName.polcal,
|
|
145
|
+
),
|
|
146
|
+
TaskUniqueBud(
|
|
147
|
+
constant_name=BudName.solar_gain_telescope_scanning_mode,
|
|
148
|
+
metadata_key=MetadataKey.telescope_scanning_mode,
|
|
149
|
+
ip_task_types=TaskName.solar_gain,
|
|
150
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
151
|
+
),
|
|
152
|
+
TaskUniqueBud(
|
|
153
|
+
constant_name=BudName.polcal_telescope_scanning_mode,
|
|
154
|
+
metadata_key=MetadataKey.telescope_scanning_mode,
|
|
155
|
+
ip_task_types=TaskName.polcal,
|
|
156
|
+
),
|
|
157
|
+
TaskUniqueBud(
|
|
158
|
+
constant_name=BudName.dark_gos_level3_status,
|
|
159
|
+
metadata_key=MetadataKey.gos_level3_status,
|
|
160
|
+
ip_task_types=TaskName.dark,
|
|
161
|
+
),
|
|
162
|
+
TaskUniqueBud(
|
|
163
|
+
constant_name=BudName.solar_gain_gos_level3_status,
|
|
164
|
+
metadata_key=MetadataKey.gos_level3_status,
|
|
165
|
+
ip_task_types=TaskName.solar_gain,
|
|
166
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
167
|
+
),
|
|
168
|
+
TaskUniqueBud(
|
|
169
|
+
constant_name=BudName.dark_gos_level3_lamp_status,
|
|
170
|
+
metadata_key=MetadataKey.gos_level3_lamp_status,
|
|
171
|
+
ip_task_types=TaskName.dark,
|
|
172
|
+
),
|
|
173
|
+
TaskUniqueBud(
|
|
174
|
+
constant_name=BudName.solar_gain_gos_level3_lamp_status,
|
|
175
|
+
metadata_key=MetadataKey.gos_level3_lamp_status,
|
|
176
|
+
ip_task_types=TaskName.solar_gain,
|
|
177
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
178
|
+
),
|
|
179
|
+
TaskUniqueBud(
|
|
180
|
+
constant_name=BudName.dark_gos_polarizer_status,
|
|
181
|
+
metadata_key=MetadataKey.gos_polarizer_status,
|
|
182
|
+
ip_task_types=TaskName.dark,
|
|
183
|
+
),
|
|
184
|
+
TaskUniqueBud(
|
|
185
|
+
constant_name=BudName.solar_gain_gos_polarizer_status,
|
|
186
|
+
metadata_key=MetadataKey.gos_polarizer_status,
|
|
187
|
+
ip_task_types=TaskName.solar_gain,
|
|
188
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
189
|
+
),
|
|
190
|
+
TaskUniqueBud(
|
|
191
|
+
constant_name=BudName.dark_gos_polarizer_angle,
|
|
192
|
+
metadata_key=MetadataKey.gos_polarizer_angle,
|
|
193
|
+
ip_task_types=TaskName.dark,
|
|
194
|
+
),
|
|
195
|
+
TaskUniqueBud(
|
|
196
|
+
constant_name=BudName.solar_gain_gos_polarizer_angle,
|
|
197
|
+
metadata_key=MetadataKey.gos_polarizer_angle,
|
|
198
|
+
ip_task_types=TaskName.solar_gain,
|
|
199
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
200
|
+
),
|
|
201
|
+
TaskUniqueBud(
|
|
202
|
+
constant_name=BudName.dark_gos_retarder_status,
|
|
203
|
+
metadata_key=MetadataKey.gos_retarder_status,
|
|
204
|
+
ip_task_types=TaskName.dark,
|
|
205
|
+
),
|
|
206
|
+
TaskUniqueBud(
|
|
207
|
+
constant_name=BudName.solar_gain_gos_retarder_status,
|
|
208
|
+
metadata_key=MetadataKey.gos_retarder_status,
|
|
209
|
+
ip_task_types=TaskName.solar_gain,
|
|
210
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
211
|
+
),
|
|
212
|
+
TaskUniqueBud(
|
|
213
|
+
constant_name=BudName.dark_gos_retarder_angle,
|
|
214
|
+
metadata_key=MetadataKey.gos_retarder_angle,
|
|
215
|
+
ip_task_types=TaskName.dark,
|
|
216
|
+
),
|
|
217
|
+
TaskUniqueBud(
|
|
218
|
+
constant_name=BudName.solar_gain_gos_retarder_angle,
|
|
219
|
+
metadata_key=MetadataKey.gos_retarder_angle,
|
|
220
|
+
ip_task_types=TaskName.solar_gain,
|
|
221
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
222
|
+
),
|
|
223
|
+
TaskUniqueBud(
|
|
224
|
+
constant_name=BudName.dark_gos_level0_status,
|
|
225
|
+
metadata_key=MetadataKey.gos_level0_status,
|
|
226
|
+
ip_task_types=TaskName.dark,
|
|
227
|
+
),
|
|
228
|
+
TaskUniqueBud(
|
|
229
|
+
constant_name=BudName.solar_gain_gos_level0_status,
|
|
230
|
+
metadata_key=MetadataKey.gos_level0_status,
|
|
231
|
+
ip_task_types=TaskName.solar_gain,
|
|
232
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
233
|
+
),
|
|
234
|
+
TaskAverageBud(
|
|
235
|
+
constant_name=BudName.dark_average_light_level,
|
|
236
|
+
metadata_key=MetadataKey.light_level,
|
|
237
|
+
ip_task_types=TaskName.dark,
|
|
238
|
+
),
|
|
239
|
+
TaskAverageBud(
|
|
240
|
+
constant_name=BudName.solar_gain_average_light_level,
|
|
241
|
+
metadata_key=MetadataKey.light_level,
|
|
242
|
+
ip_task_types=TaskName.solar_gain,
|
|
243
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
244
|
+
),
|
|
245
|
+
TaskAverageBud(
|
|
246
|
+
constant_name=BudName.polcal_average_light_level,
|
|
247
|
+
metadata_key=MetadataKey.light_level,
|
|
248
|
+
ip_task_types=TaskName.polcal,
|
|
249
|
+
),
|
|
250
|
+
TaskAverageBud(
|
|
251
|
+
constant_name=BudName.dark_average_telescope_elevation,
|
|
252
|
+
metadata_key=MetadataKey.elevation,
|
|
253
|
+
ip_task_types=TaskName.dark,
|
|
254
|
+
),
|
|
255
|
+
TaskAverageBud(
|
|
256
|
+
constant_name=BudName.solar_gain_average_telescope_elevation,
|
|
257
|
+
metadata_key=MetadataKey.elevation,
|
|
258
|
+
ip_task_types=TaskName.solar_gain,
|
|
259
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
260
|
+
),
|
|
261
|
+
TaskAverageBud(
|
|
262
|
+
constant_name=BudName.polcal_average_telescope_elevation,
|
|
263
|
+
metadata_key=MetadataKey.elevation,
|
|
264
|
+
ip_task_types=TaskName.polcal,
|
|
265
|
+
),
|
|
266
|
+
TaskAverageBud(
|
|
267
|
+
constant_name=BudName.dark_average_coude_table_angle,
|
|
268
|
+
metadata_key=MetadataKey.table_angle,
|
|
269
|
+
ip_task_types=TaskName.dark,
|
|
270
|
+
),
|
|
271
|
+
TaskAverageBud(
|
|
272
|
+
constant_name=BudName.solar_gain_average_coude_table_angle,
|
|
273
|
+
metadata_key=MetadataKey.table_angle,
|
|
274
|
+
ip_task_types=TaskName.solar_gain,
|
|
275
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
276
|
+
),
|
|
277
|
+
TaskAverageBud(
|
|
278
|
+
constant_name=BudName.polcal_average_coude_table_angle,
|
|
279
|
+
metadata_key=MetadataKey.table_angle,
|
|
280
|
+
ip_task_types=TaskName.polcal,
|
|
281
|
+
),
|
|
282
|
+
TaskAverageBud(
|
|
283
|
+
constant_name=BudName.dark_average_telescope_azimuth,
|
|
284
|
+
metadata_key=MetadataKey.azimuth,
|
|
285
|
+
ip_task_types=TaskName.dark,
|
|
286
|
+
),
|
|
287
|
+
TaskAverageBud(
|
|
288
|
+
constant_name=BudName.solar_gain_average_telescope_azimuth,
|
|
289
|
+
metadata_key=MetadataKey.azimuth,
|
|
290
|
+
ip_task_types=TaskName.solar_gain,
|
|
291
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
292
|
+
),
|
|
293
|
+
TaskAverageBud(
|
|
294
|
+
constant_name=BudName.polcal_average_telescope_azimuth,
|
|
295
|
+
metadata_key=MetadataKey.azimuth,
|
|
296
|
+
ip_task_types=TaskName.polcal,
|
|
297
|
+
),
|
|
298
|
+
TaskDateBeginBud(
|
|
299
|
+
constant_name=BudName.dark_date_begin,
|
|
300
|
+
ip_task_types=TaskName.dark,
|
|
301
|
+
),
|
|
302
|
+
TaskDateBeginBud(
|
|
303
|
+
constant_name=BudName.solar_gain_date_begin,
|
|
304
|
+
ip_task_types=TaskName.solar_gain,
|
|
305
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
306
|
+
),
|
|
307
|
+
TaskDateBeginBud(
|
|
308
|
+
constant_name=BudName.polcal_date_begin,
|
|
309
|
+
ip_task_types=TaskName.polcal,
|
|
310
|
+
),
|
|
311
|
+
]
|
|
312
|
+
|
|
313
|
+
|
|
61
314
|
def default_constant_bud_factory() -> list[S]:
|
|
62
315
|
"""Provide default constant buds for use in common parsing tasks."""
|
|
63
|
-
return [
|
|
64
|
-
UniqueBud(constant_name=BudName.instrument
|
|
316
|
+
return dataset_extra_bud_factory() + [
|
|
317
|
+
UniqueBud(constant_name=BudName.instrument, metadata_key=MetadataKey.instrument),
|
|
65
318
|
ProposalIdBud(),
|
|
66
319
|
ContributingProposalIdsBud(),
|
|
67
320
|
ExperimentIdBud(),
|
|
@@ -70,8 +323,10 @@ def default_constant_bud_factory() -> list[S]:
|
|
|
70
323
|
MaximumCadenceBud(),
|
|
71
324
|
MinimumCadenceBud(),
|
|
72
325
|
VarianceCadenceBud(),
|
|
73
|
-
TaskExposureTimesBud(stem_name=BudName.dark_exposure_times
|
|
74
|
-
TaskReadoutExpTimesBud(
|
|
326
|
+
TaskExposureTimesBud(stem_name=BudName.dark_exposure_times, ip_task_types=TaskName.dark),
|
|
327
|
+
TaskReadoutExpTimesBud(
|
|
328
|
+
stem_name=BudName.dark_readout_exp_times, ip_task_types=TaskName.dark
|
|
329
|
+
),
|
|
75
330
|
]
|
|
76
331
|
|
|
77
332
|
|
|
@@ -100,21 +355,25 @@ class ParseDataBase(WorkflowTaskBase, ABC):
|
|
|
100
355
|
|
|
101
356
|
@property
|
|
102
357
|
@abstractmethod
|
|
103
|
-
def tags_for_input_frames(self) -> list[
|
|
358
|
+
def tags_for_input_frames(self) -> list[str]:
|
|
104
359
|
"""Define the tags for the data that will be parsed."""
|
|
105
360
|
|
|
361
|
+
def pre_run(self) -> None:
|
|
362
|
+
"""Execute pre-task setup."""
|
|
363
|
+
self.outer_loop_progress.total = self.scratch.count_all(tags=self.tags_for_input_frames)
|
|
364
|
+
|
|
106
365
|
def run(self) -> None:
|
|
107
366
|
"""Run method for this task."""
|
|
108
|
-
with self.
|
|
367
|
+
with self.telemetry_span("Check that input frames exist"):
|
|
109
368
|
self.check_input_frames()
|
|
110
369
|
|
|
111
|
-
with self.
|
|
370
|
+
with self.telemetry_span("Ingest all input files"):
|
|
112
371
|
tag_pot, constant_pot = self.make_flower_pots()
|
|
113
372
|
|
|
114
|
-
with self.
|
|
373
|
+
with self.telemetry_span("Update constants"):
|
|
115
374
|
self.update_constants(constant_pot)
|
|
116
375
|
|
|
117
|
-
with self.
|
|
376
|
+
with self.telemetry_span("Tag files"):
|
|
118
377
|
self.tag_petals(tag_pot)
|
|
119
378
|
|
|
120
379
|
def make_flower_pots(self) -> tuple[FlowerPot, FlowerPot]:
|
|
@@ -125,6 +384,7 @@ class ParseDataBase(WorkflowTaskBase, ABC):
|
|
|
125
384
|
constant_pot.stems += self.constant_buds
|
|
126
385
|
|
|
127
386
|
for fits_obj in self.input_frames:
|
|
387
|
+
self.outer_loop_progress.increment()
|
|
128
388
|
filepath = fits_obj.name
|
|
129
389
|
tag_pot.add_dirt(filepath, fits_obj)
|
|
130
390
|
constant_pot.add_dirt(filepath, fits_obj)
|
|
@@ -137,6 +397,8 @@ class ParseDataBase(WorkflowTaskBase, ABC):
|
|
|
137
397
|
return self.read(
|
|
138
398
|
tags=self.tags_for_input_frames,
|
|
139
399
|
decoder=fits_access_decoder,
|
|
400
|
+
checksum=False,
|
|
401
|
+
disable_image_compression=True,
|
|
140
402
|
fits_access_class=self.fits_parsing_class,
|
|
141
403
|
)
|
|
142
404
|
|
|
@@ -158,9 +420,9 @@ class ParseDataBase(WorkflowTaskBase, ABC):
|
|
|
158
420
|
None
|
|
159
421
|
"""
|
|
160
422
|
for stem in constant_pot:
|
|
161
|
-
with self.
|
|
162
|
-
if
|
|
163
|
-
#
|
|
423
|
+
with self.telemetry_span(f"Setting value of constant {stem.stem_name}"):
|
|
424
|
+
if not stem.can_be_picked:
|
|
425
|
+
# Nothing to do
|
|
164
426
|
continue
|
|
165
427
|
if stem.bud.value is Thorn:
|
|
166
428
|
# Must've been a picky bud that passed. We don't want to pick it because it has no value
|
|
@@ -183,7 +445,7 @@ class ParseDataBase(WorkflowTaskBase, ABC):
|
|
|
183
445
|
None
|
|
184
446
|
"""
|
|
185
447
|
for stem in tag_pot:
|
|
186
|
-
with self.
|
|
448
|
+
with self.telemetry_span(f"Applying {stem.stem_name} tag to files"):
|
|
187
449
|
for petal in stem.petals:
|
|
188
450
|
tag = Tag.format_tag(stem.stem_name, petal.value)
|
|
189
451
|
for path in petal.keys:
|