dkist-processing-dlnirsp 0.32.9__py3-none-any.whl → 0.33.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.
Files changed (35) hide show
  1. changelog/120.feature.rst +2 -0
  2. changelog/121.misc.rst +2 -0
  3. changelog/124.feature.rst +2 -0
  4. changelog/124.science.rst +5 -0
  5. dkist_processing_dlnirsp/models/constants.py +6 -0
  6. dkist_processing_dlnirsp/models/parameters.py +33 -3
  7. dkist_processing_dlnirsp/parsers/task.py +2 -25
  8. dkist_processing_dlnirsp/parsers/time.py +2 -2
  9. dkist_processing_dlnirsp/tasks/__init__.py +1 -2
  10. dkist_processing_dlnirsp/tasks/movie.py +1121 -0
  11. dkist_processing_dlnirsp/tasks/parse.py +13 -8
  12. dkist_processing_dlnirsp/tasks/solar.py +129 -30
  13. dkist_processing_dlnirsp/tests/conftest.py +43 -6
  14. dkist_processing_dlnirsp/tests/local_trial_workflows/l0_polcals_as_science.py +21 -18
  15. dkist_processing_dlnirsp/tests/local_trial_workflows/l0_solar_gain_as_science.py +21 -18
  16. dkist_processing_dlnirsp/tests/local_trial_workflows/l0_to_l1.py +21 -18
  17. dkist_processing_dlnirsp/tests/local_trial_workflows/local_trial_dev_mockers.py +1 -1
  18. dkist_processing_dlnirsp/tests/test_dlnirsp_constants.py +2 -0
  19. dkist_processing_dlnirsp/tests/test_movie.py +141 -0
  20. dkist_processing_dlnirsp/tests/test_parameters.py +8 -0
  21. dkist_processing_dlnirsp/tests/test_parse.py +10 -0
  22. dkist_processing_dlnirsp/tests/test_science.py +0 -9
  23. dkist_processing_dlnirsp/tests/test_solar.py +114 -17
  24. dkist_processing_dlnirsp/workflows/l0_processing.py +6 -8
  25. dkist_processing_dlnirsp/workflows/trial_workflow.py +7 -7
  26. {dkist_processing_dlnirsp-0.32.9.dist-info → dkist_processing_dlnirsp-0.33.0rc1.dist-info}/METADATA +40 -23
  27. {dkist_processing_dlnirsp-0.32.9.dist-info → dkist_processing_dlnirsp-0.33.0rc1.dist-info}/RECORD +31 -29
  28. docs/gain.rst +7 -3
  29. dkist_processing_dlnirsp/tasks/assemble_movie.py +0 -150
  30. dkist_processing_dlnirsp/tasks/make_movie_frames.py +0 -156
  31. dkist_processing_dlnirsp/tests/test_assemble_movie.py +0 -169
  32. dkist_processing_dlnirsp/tests/test_make_movie_frames.py +0 -98
  33. {dkist_processing_dlnirsp-0.32.9.dist-info → dkist_processing_dlnirsp-0.33.0rc1.dist-info}/WHEEL +0 -0
  34. {dkist_processing_dlnirsp-0.32.9.dist-info → dkist_processing_dlnirsp-0.33.0rc1.dist-info}/entry_points.txt +0 -0
  35. {dkist_processing_dlnirsp-0.32.9.dist-info → dkist_processing_dlnirsp-0.33.0rc1.dist-info}/top_level.txt +0 -0
@@ -1,156 +0,0 @@
1
- """Task for turning output science frames into movie frames."""
2
-
3
- import numpy as np
4
- from astropy.io import fits
5
- from astropy.visualization import ZScaleInterval
6
- from dkist_processing_common.codecs.fits import fits_array_encoder
7
- from dkist_processing_common.codecs.fits import fits_hdu_decoder
8
- from dkist_service_configuration.logging import logger
9
-
10
- from dkist_processing_dlnirsp.models.tags import DlnirspTag
11
- from dkist_processing_dlnirsp.tasks.dlnirsp_base import DlnirspTaskBase
12
-
13
- __all__ = ["MakeDlnirspMovieFrames"]
14
-
15
-
16
- class MakeDlnirspMovieFrames(DlnirspTaskBase):
17
- """Task class for making individual movie frames that will be used for the browse movie."""
18
-
19
- def run(self) -> None:
20
- """
21
- Construct movie frames from the set of output science frames.
22
-
23
- For now just make a single movie frame for every output frame. If the data are polarimetric then all 4
24
- Stokes images will be put together in a grid.
25
- """
26
- if self.constants.correct_for_polarization:
27
- logger.info("Making polarimetric movie frames")
28
- else:
29
- logger.info("Making spectrographic movie frames")
30
-
31
- self.make_movie_frames()
32
-
33
- def make_movie_frames(self):
34
- """Make a movie frame for every output frame."""
35
- for mosaic_num in range(self.constants.num_mosaic_repeats):
36
- for dither_step in range(self.constants.num_dither_steps):
37
- for X_tile in range(1, self.constants.num_mosaic_tiles_x + 1):
38
- for Y_tile in range(1, self.constants.num_mosaic_tiles_y + 1):
39
- if self.constants.correct_for_polarization:
40
- data, header = self.make_single_polarimetric_frame(
41
- mosaic_num=mosaic_num,
42
- dither_step=dither_step,
43
- X_tile_num=X_tile,
44
- Y_tile_num=Y_tile,
45
- )
46
- else:
47
- data, header = self.make_single_spectrographic_frame(
48
- mosaic_num=mosaic_num,
49
- dither_step=dither_step,
50
- X_tile_num=X_tile,
51
- Y_tile_num=Y_tile,
52
- )
53
-
54
- self.write(
55
- data=data,
56
- header=header,
57
- tags=[
58
- DlnirspTag.movie_frame(),
59
- DlnirspTag.mosaic_num(mosaic_num),
60
- DlnirspTag.dither_step(dither_step),
61
- DlnirspTag.mosaic_tile_x(X_tile),
62
- DlnirspTag.mosaic_tile_y(Y_tile),
63
- ],
64
- encoder=fits_array_encoder,
65
- )
66
-
67
- def make_single_polarimetric_frame(
68
- self, mosaic_num: int, dither_step: int, X_tile_num: int, Y_tile_num: int
69
- ) -> tuple[np.ndarray, fits.Header]:
70
- """
71
- Extract and grid together all 4 Stokes frames for a given instrument loop tuple.
72
-
73
- The data are also scaled for visual appeal.
74
- """
75
- stokes_dict = dict()
76
- for stokes in self.constants.stokes_params:
77
- tags = [
78
- DlnirspTag.frame(),
79
- DlnirspTag.calibrated(),
80
- DlnirspTag.mosaic_num(mosaic_num),
81
- DlnirspTag.dither_step(dither_step),
82
- DlnirspTag.mosaic_tile_x(X_tile_num),
83
- DlnirspTag.mosaic_tile_y(Y_tile_num),
84
- ]
85
- hdu = next(self.read(tags=tags, decoder=fits_hdu_decoder))
86
- data_2D = self.grab_wavelength_slice(hdu.data)
87
- stokes_dict[stokes] = self.scale_for_rendering(data_2D)
88
-
89
- # Don't care which hdu we get
90
- header = hdu.header
91
- movie_array = self.grid_movie_frame(
92
- top_left=stokes_dict["I"],
93
- top_right=stokes_dict["Q"],
94
- bottom_left=stokes_dict["U"],
95
- bottom_right=stokes_dict["V"],
96
- )
97
- return movie_array, header
98
-
99
- def make_single_spectrographic_frame(
100
- self, mosaic_num: int, dither_step: int, X_tile_num: int, Y_tile_num: int
101
- ) -> tuple[np.ndarray, fits.Header]:
102
- """Load a output spectrographic frame, extract a single wavelength, and scale for visual appeal."""
103
- tags = [
104
- DlnirspTag.frame(),
105
- DlnirspTag.calibrated(),
106
- DlnirspTag.mosaic_num(mosaic_num),
107
- DlnirspTag.dither_step(dither_step),
108
- DlnirspTag.mosaic_tile_x(X_tile_num),
109
- DlnirspTag.mosaic_tile_y(Y_tile_num),
110
- ]
111
- hdu = next(self.read(tags=tags, decoder=fits_hdu_decoder))
112
-
113
- data_2D = self.grab_wavelength_slice(hdu.data)
114
- scaled_data = self.scale_for_rendering(data_2D)
115
-
116
- movie_array = self.scale_for_rendering(scaled_data)
117
-
118
- return movie_array, hdu.header
119
-
120
- @staticmethod
121
- def grab_wavelength_slice(data: np.ndarray):
122
- """Convert a 3D IFU image into a 2D movie frame by extracting a certain wavelength region."""
123
- wavelength_dim_size = data.shape[0]
124
- middle_index = int(np.mean(range(wavelength_dim_size)))
125
- return data[middle_index, :, :]
126
-
127
- @staticmethod
128
- def scale_for_rendering(data: np.ndarray):
129
- """
130
- Scale the output frame data using a normalization function to facilitate display as a movie frame.
131
-
132
- Non-number pixels (nan, inf, -inf) are set to black.
133
- """
134
- bad_idx = ~np.isfinite(data)
135
- data[bad_idx] = np.nanmedian(data)
136
- zscale = ZScaleInterval()
137
- scaled_data = zscale(data)
138
- scaled_data[bad_idx] = 0.0
139
- return scaled_data
140
-
141
- @staticmethod
142
- def grid_movie_frame(
143
- top_left: np.ndarray,
144
- top_right: np.ndarray,
145
- bottom_left: np.ndarray,
146
- bottom_right: np.ndarray,
147
- ) -> np.ndarray:
148
- """Combine multiple arrays into a 2x2 grid."""
149
- result = np.concatenate(
150
- (
151
- np.concatenate((top_left, top_right), axis=1),
152
- np.concatenate((bottom_left, bottom_right), axis=1),
153
- ),
154
- axis=0,
155
- )
156
- return result
@@ -1,169 +0,0 @@
1
- import numpy as np
2
- import pytest
3
- from dkist_processing_common._util.scratch import WorkflowFileSystem
4
- from moviepy.video.io.VideoFileClip import VideoFileClip
5
-
6
- from dkist_processing_dlnirsp.models.tags import DlnirspTag
7
- from dkist_processing_dlnirsp.tasks.assemble_movie import AssembleDlnirspMovie
8
- from dkist_processing_dlnirsp.tests.conftest import DlnirspTestingConstants
9
- from dkist_processing_dlnirsp.tests.conftest import MovieFrameHeaders
10
- from dkist_processing_dlnirsp.tests.conftest import tag_on_mosaic_dither_loops
11
- from dkist_processing_dlnirsp.tests.conftest import write_frames_to_task
12
-
13
-
14
- def write_movie_frames_to_task(
15
- task,
16
- dither_mode_on: bool,
17
- num_mosaics: int,
18
- num_X_tiles: int,
19
- num_Y_tiles: int,
20
- array_shape: tuple[int, int],
21
- ) -> int:
22
- dataset = MovieFrameHeaders(
23
- dither_mode_on=dither_mode_on,
24
- num_mosaics=num_mosaics,
25
- num_X_tiles=num_X_tiles,
26
- num_Y_tiles=num_Y_tiles,
27
- array_shape=array_shape,
28
- )
29
-
30
- num_written_frames = write_frames_to_task(
31
- task=task,
32
- frame_generator=dataset,
33
- extra_tags=[
34
- DlnirspTag.movie_frame(),
35
- ],
36
- tag_func=tag_on_mosaic_dither_loops,
37
- data_func=make_movie_frame_data,
38
- )
39
- return num_written_frames
40
-
41
-
42
- def make_movie_frame_data(frame: MovieFrameHeaders) -> np.ndarray:
43
- shape = frame.array_shape[1:]
44
- data = np.zeros(shape)
45
- total_frames = frame.dataset_shape[0]
46
- current_index = frame.index + 1
47
- horizontal_frac = int((current_index / total_frames) * shape[0])
48
- data[:horizontal_frac, :] = 1.0
49
-
50
- return data
51
-
52
-
53
- @pytest.fixture
54
- def assemble_movie_task(recipe_run_id, dither_mode_on, tmp_path, link_constants_db):
55
- num_mosaics = 4
56
- num_X_tiles = 3
57
- num_Y_tiles = 2
58
-
59
- constants = DlnirspTestingConstants(
60
- NUM_MOSAIC_REPEATS=num_mosaics,
61
- NUM_MOSAIC_TILES_X=num_X_tiles,
62
- NUM_MOSAIC_TILES_Y=num_Y_tiles,
63
- NUM_DITHER_STEPS=int(dither_mode_on) + 1,
64
- )
65
- link_constants_db(recipe_run_id, constants)
66
-
67
- with AssembleDlnirspMovie(
68
- recipe_run_id=recipe_run_id,
69
- workflow_name="workflow_name",
70
- workflow_version="workflow_version",
71
- ) as task:
72
- task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path, recipe_run_id=recipe_run_id)
73
-
74
- yield task, num_mosaics, num_X_tiles, num_Y_tiles
75
- task._purge()
76
-
77
-
78
- @pytest.mark.parametrize("dither_mode_on", [pytest.param(True, id="dither_mode_on")])
79
- @pytest.mark.parametrize(
80
- "is_polarimetric, requires_shrinking",
81
- [
82
- pytest.param(True, True, id="polarimetric_shrink"),
83
- pytest.param(False, False, id="spectrographic_noshrink"),
84
- pytest.param(True, False, id="polarimetric_noshrink"),
85
- ],
86
- )
87
- def test_assemble_movie(
88
- assemble_movie_task,
89
- dither_mode_on,
90
- is_polarimetric,
91
- requires_shrinking,
92
- mocker,
93
- fake_gql_client,
94
- ):
95
- """
96
- Given: An AssembleDlnirspMovie task and associated MOVIE_FRAME files
97
- When: Running the task
98
- Then: A movie is generated with the correct size
99
- """
100
- mocker.patch(
101
- "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
102
- )
103
- task, num_mosaics, num_X_tiles, num_Y_tiles = assemble_movie_task
104
-
105
- if requires_shrinking:
106
- movie_shape = (1080 * 2, 1920 * 2) # Intentionally "backward" from normal
107
- expected_shape = (1080, 1920)[::-1]
108
- else:
109
- movie_shape = (1920 // 2 - 1, 1080 // 2 - 1) # Weird aspect ratio
110
- expected_shape = (1920, 1080)[::-1] # Because AssembleMovie will force even dimensions
111
- write_movie_frames_to_task(
112
- task,
113
- dither_mode_on=dither_mode_on,
114
- num_mosaics=num_mosaics,
115
- num_X_tiles=num_X_tiles,
116
- num_Y_tiles=num_Y_tiles,
117
- array_shape=movie_shape,
118
- )
119
-
120
- task()
121
-
122
- movie_file = list(task.read(tags=[DlnirspTag.movie()]))
123
- assert len(movie_file) == 1
124
- assert movie_file[0].exists()
125
- clip = VideoFileClip(str(movie_file[0]))
126
- assert tuple(clip.size) == expected_shape
127
-
128
- # import os
129
- # os.system(f"cp {movie_file[0]} foo.mp4")
130
-
131
-
132
- @pytest.mark.parametrize(
133
- "dither_mode_on",
134
- [pytest.param(True, id="dither_mode_on"), pytest.param(False, id="dither_mode_off")],
135
- )
136
- def test_tags_for_images(assemble_movie_task, dither_mode_on):
137
- """
138
- Given: A `AssembleDlnirspMovie` task with constants describing the number of L1 frames
139
- When: Computing the tags for a certain movie image
140
- Then: The correct tags are returned
141
- """
142
- task, num_mosaics, num_X_tiles, num_Y_tiles = assemble_movie_task
143
- num_dither = int(dither_mode_on) + 1
144
-
145
- assert task.num_images == num_mosaics * num_dither * num_X_tiles * num_Y_tiles
146
-
147
- expected_mosaic_values = sum(
148
- [[i for _ in range(num_X_tiles * num_Y_tiles * num_dither)] for i in range(num_mosaics)], []
149
- )
150
- expected_dither_values = (
151
- sum([[d] * num_X_tiles * num_Y_tiles for d in range(num_dither)], []) * num_mosaics
152
- )
153
- expected_X_tile_values = (
154
- sum([[i for _ in range(num_Y_tiles)] for i in range(1, num_X_tiles + 1)] * num_mosaics, [])
155
- * num_dither
156
- )
157
- expected_Y_tile_values = (
158
- list(range(1, num_Y_tiles + 1)) * num_X_tiles * num_mosaics * num_dither
159
- )
160
-
161
- for n in range(task.num_images):
162
- assert sorted(task.tags_for_image_n(n)) == sorted(
163
- [
164
- DlnirspTag.mosaic_num(expected_mosaic_values[n]),
165
- DlnirspTag.dither_step(expected_dither_values[n]),
166
- DlnirspTag.mosaic_tile_x(expected_X_tile_values[n]),
167
- DlnirspTag.mosaic_tile_y(expected_Y_tile_values[n]),
168
- ]
169
- )
@@ -1,98 +0,0 @@
1
- import pytest
2
- from astropy.io import fits
3
- from dkist_processing_common._util.scratch import WorkflowFileSystem
4
-
5
- from dkist_processing_dlnirsp.models.tags import DlnirspTag
6
- from dkist_processing_dlnirsp.tasks.make_movie_frames import MakeDlnirspMovieFrames
7
- from dkist_processing_dlnirsp.tests.conftest import DlnirspTestingConstants
8
- from dkist_processing_dlnirsp.tests.conftest import write_calibrated_frames_to_task
9
-
10
-
11
- @pytest.fixture
12
- def make_movie_task(recipe_run_id, is_polarimetric, link_constants_db, tmp_path):
13
- dither_mode_on = True
14
- num_mosaics = 3
15
- num_X_tiles = 2
16
- num_Y_tiles = 1
17
-
18
- constants_db = DlnirspTestingConstants(
19
- NUM_MOSAIC_REPEATS=num_mosaics,
20
- NUM_MOSAIC_TILES_X=num_X_tiles,
21
- NUM_MOSAIC_TILES_Y=num_Y_tiles,
22
- NUM_DITHER_STEPS=int(dither_mode_on) + 1,
23
- POLARIMETER_MODE="Full Stokes" if is_polarimetric else "Stokes I",
24
- )
25
- link_constants_db(recipe_run_id, constants_db)
26
-
27
- with MakeDlnirspMovieFrames(
28
- recipe_run_id=recipe_run_id,
29
- workflow_name="workflow_name",
30
- workflow_version="workflow_version",
31
- ) as task:
32
- task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path, recipe_run_id=recipe_run_id)
33
-
34
- yield task, num_mosaics, dither_mode_on, num_X_tiles, num_Y_tiles
35
- task._purge()
36
-
37
-
38
- @pytest.mark.parametrize(
39
- "is_polarimetric",
40
- [pytest.param(True, id="polarimetric"), pytest.param(False, id="spectrographic")],
41
- )
42
- def test_make_movie_frames(
43
- make_movie_task, is_polarimetric, link_constants_db, mocker, fake_gql_client
44
- ):
45
- """
46
- Given: A Make Movie Frames task and some calibrated Science frames
47
- When: Running the task
48
- Then: The correct number of movie frames are produced and they have the correct shape (depending on polarimetric or not)
49
- """
50
- mocker.patch(
51
- "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
52
- )
53
- task, num_mosaics, dither_mode_on, num_X_tiles, num_Y_tiles = make_movie_task
54
-
55
- num_dither = int(dither_mode_on) + 1
56
- wave_size = 3
57
- X_size = 4
58
- Y_size = 5
59
- write_calibrated_frames_to_task(
60
- task,
61
- num_mosaics=num_mosaics,
62
- num_X_tiles=num_X_tiles,
63
- num_Y_tiles=num_Y_tiles,
64
- is_polarimetric=is_polarimetric,
65
- array_shape=(wave_size, X_size, Y_size),
66
- dither_mode_on=dither_mode_on,
67
- )
68
-
69
- task()
70
-
71
- if is_polarimetric:
72
- expected_movie_frame_shape = (X_size * 2, Y_size * 2)
73
- else:
74
- expected_movie_frame_shape = (X_size, Y_size)
75
-
76
- all_movie_frames = list(task.read([DlnirspTag.movie_frame()]))
77
- assert len(all_movie_frames) == num_dither * num_mosaics * num_X_tiles * num_Y_tiles
78
-
79
- for mosaic in range(num_mosaics):
80
- for dither in range(num_dither):
81
- for X_tile in range(1, num_X_tiles + 1):
82
- for Y_tile in range(1, num_Y_tiles + 1):
83
- single_frame = list(
84
- task.read(
85
- [
86
- DlnirspTag.movie_frame(),
87
- DlnirspTag.mosaic_num(mosaic),
88
- DlnirspTag.dither_step(dither),
89
- DlnirspTag.mosaic_tile_x(X_tile),
90
- DlnirspTag.mosaic_tile_y(Y_tile),
91
- ]
92
- )
93
- )
94
- assert len(single_frame) == 1
95
- hdul = fits.open(single_frame[0])
96
- assert len(hdul) == 1
97
- data = hdul[0].data
98
- assert data.shape == expected_movie_frame_shape