dkist-processing-visp 3.3.0__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 +52 -21
- 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 -19
- dkist_processing_visp/models/tags.py +1 -0
- 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 +3 -1
- dkist_processing_visp/parsers/raster_step.py +4 -1
- dkist_processing_visp/parsers/spectrograph_configuration.py +75 -0
- dkist_processing_visp/parsers/time.py +15 -7
- 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 +13 -12
- 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 +34 -4
- 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 +2 -0
- dkist_processing_visp/tasks/write_l1.py +25 -5
- dkist_processing_visp/tests/conftest.py +99 -35
- dkist_processing_visp/tests/header_models.py +92 -20
- dkist_processing_visp/tests/local_trial_workflows/l0_cals_only.py +4 -23
- 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 +10 -29
- dkist_processing_visp/tests/local_trial_workflows/l0_to_l1.py +1 -21
- dkist_processing_visp/tests/local_trial_workflows/local_trial_helpers.py +98 -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 +8 -5
- dkist_processing_visp/tests/test_dark.py +4 -3
- 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 +4 -3
- 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 +98 -14
- dkist_processing_visp/tests/test_quality.py +2 -3
- 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 +36 -8
- dkist_processing_visp/tests/test_workflows.py +1 -0
- dkist_processing_visp/tests/test_write_l1.py +17 -3
- dkist_processing_visp/workflows/__init__.py +1 -0
- dkist_processing_visp/workflows/l0_processing.py +8 -2
- 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
- docs/conf.py +5 -1
- docs/gain_correction.rst +50 -42
- dkist_processing_visp/tasks/mixin/line_zones.py +0 -115
- dkist_processing_visp-3.3.0.dist-info/METADATA +0 -459
- dkist_processing_visp-3.3.0.dist-info/RECORD +0 -90
- {dkist_processing_visp-3.3.0.dist-info → dkist_processing_visp-5.1.1.dist-info}/WHEEL +0 -0
- {dkist_processing_visp-3.3.0.dist-info → dkist_processing_visp-5.1.1.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""ViSP science calibration task. See :doc:`this page </science_calibration>` for more information."""
|
|
2
|
+
|
|
2
3
|
from collections import defaultdict
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from functools import cached_property
|
|
@@ -11,6 +12,7 @@ from astropy.time import TimeDelta
|
|
|
11
12
|
from dkist_processing_common.codecs.fits import fits_access_decoder
|
|
12
13
|
from dkist_processing_common.codecs.fits import fits_array_decoder
|
|
13
14
|
from dkist_processing_common.codecs.fits import fits_hdulist_encoder
|
|
15
|
+
from dkist_processing_common.models.fits_access import MetadataKey
|
|
14
16
|
from dkist_processing_common.models.task_name import TaskName
|
|
15
17
|
from dkist_processing_common.tasks.mixin.quality import QualityMixin
|
|
16
18
|
from dkist_processing_math.arithmetic import divide_arrays_by_array
|
|
@@ -20,6 +22,7 @@ from dkist_processing_math.statistics import average_numpy_arrays
|
|
|
20
22
|
from dkist_processing_pac.optics.telescope import Telescope
|
|
21
23
|
from dkist_service_configuration.logging import logger
|
|
22
24
|
|
|
25
|
+
from dkist_processing_visp.models.fits_access import VispMetadataKey
|
|
23
26
|
from dkist_processing_visp.models.tags import VispTag
|
|
24
27
|
from dkist_processing_visp.parsers.visp_l0_fits_access import VispL0FitsAccess
|
|
25
28
|
from dkist_processing_visp.tasks.mixin.beam_access import BeamAccessMixin
|
|
@@ -48,6 +51,9 @@ class CalibrationCollection:
|
|
|
48
51
|
|
|
49
52
|
This is done by considering that state offset values computed by the GeometricCalibration task. Any sub-pixel
|
|
50
53
|
overlaps are rounded to the next integer that still guarantees overlap.
|
|
54
|
+
|
|
55
|
+
When "start pixels" are mentioned, those are pixels being counted from zero on a given axis in the positive direction.
|
|
56
|
+
When "end pixels" are mentioned, those are pixels being counted from the end of a given axis in the negative direction.
|
|
51
57
|
"""
|
|
52
58
|
logger.info("Computing beam overlap slices")
|
|
53
59
|
# This will be a flat list of (x, y) pairs for all modstates and beams
|
|
@@ -60,25 +66,33 @@ class CalibrationCollection:
|
|
|
60
66
|
logger.info(f"All x shifts: {all_x_shifts}")
|
|
61
67
|
logger.info(f"All y shifts: {all_y_shifts}")
|
|
62
68
|
|
|
63
|
-
# The amount we need to "slice in" from the
|
|
69
|
+
# The amount we need to "slice in" from the start of the array is equivalent to the absolute value of the most negative shift.
|
|
64
70
|
# The call to `np.ceil` ensures that the integer rounding doesn't allow non-overlap regions to leak in.
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
start_pixels_to_slice_x = int(np.ceil(abs(np.min(all_x_shifts))))
|
|
72
|
+
start_pixels_to_slice_y = int(np.ceil(abs(np.min(all_y_shifts))))
|
|
67
73
|
|
|
68
|
-
# The amount we need to "chop off" the end of the array is the most
|
|
74
|
+
# The amount we need to "chop off" the end of the array is the most positive shift.
|
|
69
75
|
#
|
|
70
76
|
# Here we rely on the fact that the fiducial array's shift is *always* (0, 0)
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
77
|
+
# (see `geometric.compute_modstate_offset`). Thus, if there are no negative shifts then the following lines
|
|
78
|
+
# will result in None. This is required for slicing because array[x:0] is no good. So if the max is 0 then we
|
|
79
|
+
# end up with array[x:None] which goes all the way to the end of the array.
|
|
74
80
|
#
|
|
75
|
-
# The call to `np.
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
# The call to `np.ceil` ensures that the integer rounding doesn't allow non-overlap regions to leak in.
|
|
82
|
+
# (because more negative slices will cut out more data).
|
|
83
|
+
end_pixels_to_slice_x = int(np.ceil(np.max(all_x_shifts))) or None
|
|
84
|
+
end_pixels_to_slice_y = int(np.ceil(np.max(all_y_shifts))) or None
|
|
85
|
+
|
|
86
|
+
# As the pixels to remove from the end of axes is given as a positive number, we need to make it negative for slicing.
|
|
87
|
+
if end_pixels_to_slice_x is not None:
|
|
88
|
+
end_pixels_to_slice_x *= -1
|
|
89
|
+
|
|
90
|
+
if end_pixels_to_slice_y is not None:
|
|
91
|
+
end_pixels_to_slice_y *= -1
|
|
79
92
|
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
# Construct the slices
|
|
94
|
+
x_slice = slice(start_pixels_to_slice_x, end_pixels_to_slice_x)
|
|
95
|
+
y_slice = slice(start_pixels_to_slice_y, end_pixels_to_slice_y)
|
|
82
96
|
|
|
83
97
|
return x_slice, y_slice
|
|
84
98
|
|
|
@@ -125,17 +139,17 @@ class ScienceCalibration(
|
|
|
125
139
|
None
|
|
126
140
|
|
|
127
141
|
"""
|
|
128
|
-
with self.
|
|
142
|
+
with self.telemetry_span("Loading calibration objects"):
|
|
129
143
|
calibrations = self.collect_calibration_objects()
|
|
130
144
|
|
|
131
|
-
with self.
|
|
145
|
+
with self.telemetry_span(
|
|
132
146
|
f"Processing Science Frames for "
|
|
133
147
|
f"{self.constants.num_map_scans} map scans and "
|
|
134
148
|
f"{self.constants.num_raster_steps} raster steps"
|
|
135
149
|
):
|
|
136
150
|
self.process_frames(calibrations=calibrations)
|
|
137
151
|
|
|
138
|
-
with self.
|
|
152
|
+
with self.telemetry_span("Computing and logging quality metrics"):
|
|
139
153
|
no_of_raw_science_frames: int = self.scratch.count_all(
|
|
140
154
|
tags=[
|
|
141
155
|
VispTag.input(),
|
|
@@ -156,7 +170,7 @@ class ScienceCalibration(
|
|
|
156
170
|
"""
|
|
157
171
|
dark_dict = defaultdict(dict)
|
|
158
172
|
background_dict = dict()
|
|
159
|
-
solar_dict =
|
|
173
|
+
solar_dict = dict()
|
|
160
174
|
angle_dict = dict()
|
|
161
175
|
state_offset_dict = defaultdict(dict)
|
|
162
176
|
spec_shift_dict = dict()
|
|
@@ -211,6 +225,18 @@ class ScienceCalibration(
|
|
|
211
225
|
)
|
|
212
226
|
)
|
|
213
227
|
|
|
228
|
+
# Solar
|
|
229
|
+
#######
|
|
230
|
+
solar_dict[VispTag.beam(beam)] = next(
|
|
231
|
+
self.read(
|
|
232
|
+
tags=[
|
|
233
|
+
VispTag.intermediate_frame(beam=beam),
|
|
234
|
+
VispTag.task_solar_gain(),
|
|
235
|
+
],
|
|
236
|
+
decoder=fits_array_decoder,
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
214
240
|
# Demod
|
|
215
241
|
#######
|
|
216
242
|
if self.constants.correct_for_polarization:
|
|
@@ -225,18 +251,6 @@ class ScienceCalibration(
|
|
|
225
251
|
)
|
|
226
252
|
|
|
227
253
|
for modstate in range(1, self.constants.num_modstates + 1):
|
|
228
|
-
# Solar
|
|
229
|
-
#######
|
|
230
|
-
solar_dict[VispTag.beam(beam)][VispTag.modstate(modstate)] = next(
|
|
231
|
-
self.read(
|
|
232
|
-
tags=[
|
|
233
|
-
VispTag.intermediate_frame(beam=beam, modstate=modstate),
|
|
234
|
-
VispTag.task_solar_gain(),
|
|
235
|
-
],
|
|
236
|
-
decoder=fits_array_decoder,
|
|
237
|
-
)
|
|
238
|
-
)
|
|
239
|
-
|
|
240
254
|
# State Offset
|
|
241
255
|
##############
|
|
242
256
|
state_offset_dict[VispTag.beam(beam)][VispTag.modstate(modstate)] = next(
|
|
@@ -274,9 +288,10 @@ class ScienceCalibration(
|
|
|
274
288
|
for raster_step in range(0, self.constants.num_raster_steps):
|
|
275
289
|
beam_storage = dict()
|
|
276
290
|
header_storage = dict()
|
|
291
|
+
nan_storage = dict()
|
|
277
292
|
for beam in range(1, self.constants.num_beams + 1):
|
|
278
293
|
apm_str = f"{map_scan = }, {raster_step = }, and {beam = }"
|
|
279
|
-
with self.
|
|
294
|
+
with self.telemetry_span(f"Basic corrections for {apm_str}"):
|
|
280
295
|
# Initialize array_stack and headers
|
|
281
296
|
if self.constants.correct_for_polarization:
|
|
282
297
|
logger.info(
|
|
@@ -285,6 +300,7 @@ class ScienceCalibration(
|
|
|
285
300
|
(
|
|
286
301
|
intermediate_array,
|
|
287
302
|
intermediate_header,
|
|
303
|
+
nan_mask,
|
|
288
304
|
) = self.process_polarimetric_modstates(
|
|
289
305
|
beam=beam,
|
|
290
306
|
raster_step=raster_step,
|
|
@@ -296,7 +312,11 @@ class ScienceCalibration(
|
|
|
296
312
|
logger.info(
|
|
297
313
|
f"Processing spectrographic observe frames from {apm_str}"
|
|
298
314
|
)
|
|
299
|
-
|
|
315
|
+
(
|
|
316
|
+
intermediate_array,
|
|
317
|
+
intermediate_header,
|
|
318
|
+
nan_mask,
|
|
319
|
+
) = self.correct_single_frame(
|
|
300
320
|
beam=beam,
|
|
301
321
|
modstate=1,
|
|
302
322
|
raster_step=raster_step,
|
|
@@ -307,20 +327,38 @@ class ScienceCalibration(
|
|
|
307
327
|
intermediate_header = self.compute_date_keys(intermediate_header)
|
|
308
328
|
beam_storage[VispTag.beam(beam)] = intermediate_array
|
|
309
329
|
header_storage[VispTag.beam(beam)] = intermediate_header
|
|
330
|
+
nan_storage[VispTag.beam(beam)] = nan_mask
|
|
310
331
|
|
|
311
|
-
with self.
|
|
332
|
+
with self.telemetry_span("Combining beams"):
|
|
312
333
|
calibrated = self.combine_beams(beam_storage, header_storage, calibrations)
|
|
313
334
|
|
|
314
335
|
if self.constants.correct_for_polarization:
|
|
315
|
-
with self.
|
|
336
|
+
with self.telemetry_span("Correcting telescope polarization"):
|
|
316
337
|
calibrated = self.telescope_polarization_correction(calibrated)
|
|
317
338
|
|
|
339
|
+
with self.telemetry_span("Combining NaN masks from beams"):
|
|
340
|
+
cut_combined_nan_mask = self.combine_and_cut_nan_masks(
|
|
341
|
+
list(nan_storage.values()), calibrations
|
|
342
|
+
)
|
|
343
|
+
|
|
318
344
|
# Save the final output files
|
|
319
|
-
with self.
|
|
345
|
+
with self.telemetry_span("Writing calibrated arrays"):
|
|
320
346
|
self.write_calibrated_array(
|
|
321
|
-
calibrated,
|
|
347
|
+
calibrated,
|
|
348
|
+
map_scan=map_scan,
|
|
349
|
+
calibrations=calibrations,
|
|
350
|
+
nan_mask=cut_combined_nan_mask,
|
|
322
351
|
)
|
|
323
352
|
|
|
353
|
+
@staticmethod
|
|
354
|
+
def combine_and_cut_nan_masks(
|
|
355
|
+
nan_masks: list[np.ndarray], calibrations: CalibrationCollection
|
|
356
|
+
) -> np.ndarray:
|
|
357
|
+
"""Combine two NaN masks into one, cropping the result based on pre-calculated shifts."""
|
|
358
|
+
combined_nan_mask = np.logical_or.reduce(nan_masks)
|
|
359
|
+
x_slice, y_slice = calibrations.beams_overlap_slice
|
|
360
|
+
return combined_nan_mask[x_slice, y_slice]
|
|
361
|
+
|
|
324
362
|
def process_polarimetric_modstates(
|
|
325
363
|
self,
|
|
326
364
|
beam: int,
|
|
@@ -328,7 +366,7 @@ class ScienceCalibration(
|
|
|
328
366
|
map_scan: int,
|
|
329
367
|
readout_exp_time: float,
|
|
330
368
|
calibrations: CalibrationCollection,
|
|
331
|
-
) -> tuple[np.ndarray, fits.Header]:
|
|
369
|
+
) -> tuple[np.ndarray, fits.Header, np.ndarray]:
|
|
332
370
|
"""
|
|
333
371
|
Process a single polarimetric beam as much as is possible.
|
|
334
372
|
|
|
@@ -340,11 +378,12 @@ class ScienceCalibration(
|
|
|
340
378
|
].shape
|
|
341
379
|
array_stack = np.zeros(array_shape + (self.constants.num_modstates,))
|
|
342
380
|
header_stack = []
|
|
381
|
+
nan_mask_stack = np.zeros(array_shape + (self.constants.num_modstates,))
|
|
343
382
|
|
|
344
|
-
with self.
|
|
383
|
+
with self.telemetry_span(f"Correcting {self.constants.num_modstates} modstates"):
|
|
345
384
|
for modstate in range(1, self.constants.num_modstates + 1):
|
|
346
385
|
# Correct the arrays
|
|
347
|
-
corrected_array, corrected_header = self.correct_single_frame(
|
|
386
|
+
corrected_array, corrected_header, nan_mask = self.correct_single_frame(
|
|
348
387
|
beam=beam,
|
|
349
388
|
modstate=modstate,
|
|
350
389
|
raster_step=raster_step,
|
|
@@ -355,15 +394,17 @@ class ScienceCalibration(
|
|
|
355
394
|
# Add this result to the 3D stack
|
|
356
395
|
array_stack[:, :, modstate - 1] = corrected_array
|
|
357
396
|
header_stack.append(corrected_header)
|
|
397
|
+
nan_mask_stack[:, :, modstate - 1] = nan_mask
|
|
358
398
|
|
|
359
|
-
with self.
|
|
399
|
+
with self.telemetry_span("Applying instrument polarization correction"):
|
|
360
400
|
intermediate_array = nd_left_matrix_multiply(
|
|
361
401
|
vector_stack=array_stack,
|
|
362
402
|
matrix_stack=calibrations.demod_matrices[VispTag.beam(beam)],
|
|
363
403
|
)
|
|
364
404
|
intermediate_header = self.compute_date_keys(header_stack)
|
|
365
405
|
|
|
366
|
-
|
|
406
|
+
# The modulator state NaN masks are stacked along axis=2 with axis=0 & 1 being the array axes of one modstate
|
|
407
|
+
return intermediate_array, intermediate_header, np.logical_or.reduce(nan_mask_stack, axis=2)
|
|
367
408
|
|
|
368
409
|
def combine_beams(
|
|
369
410
|
self,
|
|
@@ -437,6 +478,7 @@ class ScienceCalibration(
|
|
|
437
478
|
calibrated_object: VispL0FitsAccess,
|
|
438
479
|
map_scan: int,
|
|
439
480
|
calibrations: CalibrationCollection,
|
|
481
|
+
nan_mask: np.ndarray,
|
|
440
482
|
) -> None:
|
|
441
483
|
"""
|
|
442
484
|
Write out calibrated science frames.
|
|
@@ -455,6 +497,9 @@ class ScienceCalibration(
|
|
|
455
497
|
calibrations
|
|
456
498
|
Calibration collection
|
|
457
499
|
|
|
500
|
+
nan_mask
|
|
501
|
+
A mask containing the known areas where data does not exist for both beams
|
|
502
|
+
|
|
458
503
|
Returns
|
|
459
504
|
-------
|
|
460
505
|
None
|
|
@@ -470,7 +515,8 @@ class ScienceCalibration(
|
|
|
470
515
|
stokes_I_data = calibrated_object.data[:, :, 0]
|
|
471
516
|
for i, stokes_param in enumerate(self.constants.stokes_params):
|
|
472
517
|
stokes_data = calibrated_object.data[:, :, i]
|
|
473
|
-
|
|
518
|
+
nan_masked_data = np.where(nan_mask, np.nan, stokes_data)
|
|
519
|
+
final_data = self.re_dummy_data(nan_masked_data)
|
|
474
520
|
pol_header = self.add_L1_pol_headers(final_header, stokes_data, stokes_I_data)
|
|
475
521
|
self.write_cal_array(
|
|
476
522
|
data=final_data,
|
|
@@ -480,7 +526,8 @@ class ScienceCalibration(
|
|
|
480
526
|
map_scan=map_scan,
|
|
481
527
|
)
|
|
482
528
|
else: # Only write stokes I
|
|
483
|
-
|
|
529
|
+
nan_masked_data = np.where(nan_mask, np.nan, calibrated_object.data)
|
|
530
|
+
final_data = self.re_dummy_data(nan_masked_data)
|
|
484
531
|
self.write_cal_array(
|
|
485
532
|
data=final_data,
|
|
486
533
|
header=final_header,
|
|
@@ -497,7 +544,7 @@ class ScienceCalibration(
|
|
|
497
544
|
map_scan: int,
|
|
498
545
|
readout_exp_time: float,
|
|
499
546
|
calibrations: CalibrationCollection,
|
|
500
|
-
) -> tuple[np.ndarray, fits.Header]:
|
|
547
|
+
) -> tuple[np.ndarray, fits.Header, np.ndarray]:
|
|
501
548
|
"""
|
|
502
549
|
Apply basic corrections to a single frame.
|
|
503
550
|
|
|
@@ -539,7 +586,7 @@ class ScienceCalibration(
|
|
|
539
586
|
VispTag.readout_exp_time(readout_exp_time)
|
|
540
587
|
]
|
|
541
588
|
background_array = calibrations.background[VispTag.beam(beam)]
|
|
542
|
-
solar_gain_array = calibrations.solar_gain[VispTag.beam(beam)]
|
|
589
|
+
solar_gain_array = calibrations.solar_gain[VispTag.beam(beam)]
|
|
543
590
|
angle = calibrations.angle[VispTag.beam(beam)]
|
|
544
591
|
spec_shift = calibrations.spec_shift[VispTag.beam(beam)]
|
|
545
592
|
state_offset = calibrations.state_offset[VispTag.beam(beam)][VispTag.modstate(modstate)]
|
|
@@ -592,7 +639,40 @@ class ScienceCalibration(
|
|
|
592
639
|
self.corrections_remove_spec_geometry(geo_corrected_array, spec_shift)
|
|
593
640
|
)
|
|
594
641
|
|
|
595
|
-
|
|
642
|
+
nan_mask = self.generate_nan_mask(
|
|
643
|
+
solar_corrected_array=solar_corrected_array,
|
|
644
|
+
state_offset=state_offset,
|
|
645
|
+
angle=angle,
|
|
646
|
+
spec_shift=spec_shift,
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
return (
|
|
650
|
+
spectral_corrected_array,
|
|
651
|
+
observe_object.header,
|
|
652
|
+
nan_mask,
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
def generate_nan_mask(
|
|
656
|
+
self,
|
|
657
|
+
solar_corrected_array: np.ndarray,
|
|
658
|
+
state_offset: np.ndarray,
|
|
659
|
+
angle: float,
|
|
660
|
+
spec_shift: np.ndarray,
|
|
661
|
+
) -> np.ndarray:
|
|
662
|
+
"""Calculate the NaN mask through geometric correction to be applied to the final L1 arrays."""
|
|
663
|
+
# Using a bi-cubic polynomial (order = 3) best converges to the desired result in the underlying fits
|
|
664
|
+
geo_corrected_with_nan = next(
|
|
665
|
+
self.corrections_correct_geometry(
|
|
666
|
+
solar_corrected_array, state_offset, angle, mode="constant", order=3, cval=np.nan
|
|
667
|
+
)
|
|
668
|
+
)
|
|
669
|
+
# Interpolating with nearest neighbor (order = 0) prevents NaN values from "taking over" the whole array
|
|
670
|
+
spectral_corrected_with_nan = next(
|
|
671
|
+
self.corrections_remove_spec_geometry(
|
|
672
|
+
geo_corrected_with_nan, spec_shift, cval=np.nan, order=0
|
|
673
|
+
)
|
|
674
|
+
)
|
|
675
|
+
return np.isnan(spectral_corrected_with_nan)
|
|
596
676
|
|
|
597
677
|
def telescope_polarization_correction(
|
|
598
678
|
self,
|
|
@@ -652,7 +732,7 @@ class ScienceCalibration(
|
|
|
652
732
|
date_end = (Time(sorted_obj_list[-1].time_obs) + exp_time).isot
|
|
653
733
|
|
|
654
734
|
header = sorted_obj_list[0].header
|
|
655
|
-
header[
|
|
735
|
+
header[MetadataKey.time_obs] = date_beg
|
|
656
736
|
header["DATE-END"] = date_end
|
|
657
737
|
|
|
658
738
|
return header
|