dkist-processing-cryonirsp 1.10.0rc1__py3-none-any.whl → 1.14.9rc1__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 (92) hide show
  1. changelog/232.misc.rst +1 -0
  2. dkist_processing_cryonirsp/__init__.py +1 -0
  3. dkist_processing_cryonirsp/codecs/fits.py +1 -0
  4. dkist_processing_cryonirsp/config.py +1 -0
  5. dkist_processing_cryonirsp/models/beam_boundaries.py +1 -0
  6. dkist_processing_cryonirsp/models/constants.py +8 -30
  7. dkist_processing_cryonirsp/models/exposure_conditions.py +6 -5
  8. dkist_processing_cryonirsp/models/fits_access.py +40 -0
  9. dkist_processing_cryonirsp/models/parameters.py +1 -0
  10. dkist_processing_cryonirsp/models/tags.py +1 -0
  11. dkist_processing_cryonirsp/models/task_name.py +1 -0
  12. dkist_processing_cryonirsp/parsers/check_for_gains.py +1 -0
  13. dkist_processing_cryonirsp/parsers/cryonirsp_l0_fits_access.py +40 -48
  14. dkist_processing_cryonirsp/parsers/cryonirsp_l1_fits_access.py +1 -0
  15. dkist_processing_cryonirsp/parsers/exposure_conditions.py +14 -13
  16. dkist_processing_cryonirsp/parsers/map_repeats.py +1 -0
  17. dkist_processing_cryonirsp/parsers/measurements.py +29 -16
  18. dkist_processing_cryonirsp/parsers/modstates.py +5 -1
  19. dkist_processing_cryonirsp/parsers/optical_density_filters.py +1 -0
  20. dkist_processing_cryonirsp/parsers/polarimetric_check.py +18 -7
  21. dkist_processing_cryonirsp/parsers/scan_step.py +12 -4
  22. dkist_processing_cryonirsp/parsers/time.py +7 -7
  23. dkist_processing_cryonirsp/parsers/wavelength.py +6 -1
  24. dkist_processing_cryonirsp/tasks/__init__.py +1 -0
  25. dkist_processing_cryonirsp/tasks/assemble_movie.py +1 -0
  26. dkist_processing_cryonirsp/tasks/bad_pixel_map.py +6 -5
  27. dkist_processing_cryonirsp/tasks/beam_boundaries_base.py +9 -10
  28. dkist_processing_cryonirsp/tasks/ci_beam_boundaries.py +1 -0
  29. dkist_processing_cryonirsp/tasks/ci_science.py +1 -0
  30. dkist_processing_cryonirsp/tasks/cryonirsp_base.py +1 -0
  31. dkist_processing_cryonirsp/tasks/dark.py +5 -4
  32. dkist_processing_cryonirsp/tasks/gain.py +7 -6
  33. dkist_processing_cryonirsp/tasks/instrument_polarization.py +16 -15
  34. dkist_processing_cryonirsp/tasks/l1_output_data.py +1 -0
  35. dkist_processing_cryonirsp/tasks/linearity_correction.py +1 -0
  36. dkist_processing_cryonirsp/tasks/make_movie_frames.py +3 -2
  37. dkist_processing_cryonirsp/tasks/mixin/corrections.py +1 -0
  38. dkist_processing_cryonirsp/tasks/mixin/shift_measurements.py +1 -0
  39. dkist_processing_cryonirsp/tasks/parse.py +66 -61
  40. dkist_processing_cryonirsp/tasks/quality_metrics.py +15 -14
  41. dkist_processing_cryonirsp/tasks/science_base.py +8 -6
  42. dkist_processing_cryonirsp/tasks/sp_beam_boundaries.py +2 -1
  43. dkist_processing_cryonirsp/tasks/sp_geometric.py +11 -10
  44. dkist_processing_cryonirsp/tasks/sp_science.py +1 -0
  45. dkist_processing_cryonirsp/tasks/sp_solar_gain.py +15 -12
  46. dkist_processing_cryonirsp/tasks/sp_wavelength_calibration.py +9 -9
  47. dkist_processing_cryonirsp/tasks/write_l1.py +36 -7
  48. dkist_processing_cryonirsp/tests/conftest.py +6 -7
  49. dkist_processing_cryonirsp/tests/header_models.py +40 -3
  50. dkist_processing_cryonirsp/tests/local_trial_workflows/l0_cals_only.py +11 -31
  51. dkist_processing_cryonirsp/tests/local_trial_workflows/l0_to_l1.py +11 -30
  52. dkist_processing_cryonirsp/tests/local_trial_workflows/linearize_only.py +3 -3
  53. dkist_processing_cryonirsp/tests/local_trial_workflows/local_trial_helpers.py +3 -2
  54. dkist_processing_cryonirsp/tests/test_assemble_movie.py +4 -5
  55. dkist_processing_cryonirsp/tests/test_assemble_qualilty.py +5 -1
  56. dkist_processing_cryonirsp/tests/test_bad_pixel_maps.py +3 -4
  57. dkist_processing_cryonirsp/tests/test_ci_beam_boundaries.py +3 -4
  58. dkist_processing_cryonirsp/tests/test_ci_science.py +3 -4
  59. dkist_processing_cryonirsp/tests/test_corrections.py +3 -3
  60. dkist_processing_cryonirsp/tests/test_cryo_base.py +3 -5
  61. dkist_processing_cryonirsp/tests/test_cryo_constants.py +1 -2
  62. dkist_processing_cryonirsp/tests/test_dark.py +5 -6
  63. dkist_processing_cryonirsp/tests/test_fits_access.py +44 -0
  64. dkist_processing_cryonirsp/tests/test_gain.py +5 -6
  65. dkist_processing_cryonirsp/tests/test_instrument_polarization.py +9 -6
  66. dkist_processing_cryonirsp/tests/test_linearity_correction.py +4 -3
  67. dkist_processing_cryonirsp/tests/test_make_movie_frames.py +2 -3
  68. dkist_processing_cryonirsp/tests/test_parameters.py +3 -4
  69. dkist_processing_cryonirsp/tests/test_parse.py +14 -8
  70. dkist_processing_cryonirsp/tests/test_quality.py +2 -3
  71. dkist_processing_cryonirsp/tests/test_sp_beam_boundaries.py +4 -4
  72. dkist_processing_cryonirsp/tests/test_sp_geometric.py +3 -4
  73. dkist_processing_cryonirsp/tests/test_sp_make_movie_frames.py +2 -3
  74. dkist_processing_cryonirsp/tests/test_sp_science.py +3 -4
  75. dkist_processing_cryonirsp/tests/test_sp_solar.py +5 -4
  76. dkist_processing_cryonirsp/tests/test_sp_wavelength_calibration.py +4 -5
  77. dkist_processing_cryonirsp/tests/test_trial_create_quality_report.py +1 -1
  78. dkist_processing_cryonirsp/tests/test_workflows.py +1 -0
  79. dkist_processing_cryonirsp/tests/test_write_l1.py +12 -16
  80. dkist_processing_cryonirsp/workflows/__init__.py +1 -0
  81. dkist_processing_cryonirsp/workflows/ci_l0_processing.py +6 -5
  82. dkist_processing_cryonirsp/workflows/sp_l0_processing.py +6 -5
  83. dkist_processing_cryonirsp/workflows/trial_workflows.py +9 -8
  84. dkist_processing_cryonirsp-1.14.9rc1.dist-info/METADATA +552 -0
  85. dkist_processing_cryonirsp-1.14.9rc1.dist-info/RECORD +115 -0
  86. docs/conf.py +1 -0
  87. docs/wavelength_calibration.rst +1 -1
  88. changelog/167.feature.rst +0 -1
  89. dkist_processing_cryonirsp-1.10.0rc1.dist-info/METADATA +0 -458
  90. dkist_processing_cryonirsp-1.10.0rc1.dist-info/RECORD +0 -113
  91. {dkist_processing_cryonirsp-1.10.0rc1.dist-info → dkist_processing_cryonirsp-1.14.9rc1.dist-info}/WHEEL +0 -0
  92. {dkist_processing_cryonirsp-1.10.0rc1.dist-info → dkist_processing_cryonirsp-1.14.9rc1.dist-info}/top_level.txt +0 -0
@@ -5,15 +5,14 @@ import pytest
5
5
  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.codecs.fits import fits_hdulist_encoder
8
- from dkist_processing_common.tests.conftest import FakeGQLClient
9
8
 
10
9
  from dkist_processing_cryonirsp.codecs.fits import cryo_fits_array_decoder
11
10
  from dkist_processing_cryonirsp.models.exposure_conditions import AllowableOpticalDensityFilterNames
12
11
  from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
13
12
  from dkist_processing_cryonirsp.models.tags import CryonirspTag
14
13
  from dkist_processing_cryonirsp.tasks.sp_solar_gain import SPSolarGainCalibration
15
- from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
16
14
  from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
15
+ from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
17
16
  from dkist_processing_cryonirsp.tests.conftest import generate_214_l0_fits_frame
18
17
  from dkist_processing_cryonirsp.tests.header_models import CryonirspHeadersValidSPSolarGainFrames
19
18
 
@@ -178,14 +177,16 @@ def solar_gain_calibration_task_with_no_data(tmp_path, recipe_run_id, init_cryon
178
177
 
179
178
 
180
179
  @pytest.mark.parametrize("fringe_correction", [False, True])
181
- def test_solar_gain_task(solar_gain_calibration_task_that_completes, mocker, fringe_correction):
180
+ def test_solar_gain_task(
181
+ solar_gain_calibration_task_that_completes, mocker, fake_gql_client, fringe_correction
182
+ ):
182
183
  """
183
184
  Given: A set of raw solar gain images and necessary intermediate calibrations
184
185
  When: Running the solargain task
185
186
  Then: The task completes and the outputs are correct
186
187
  """
187
188
  mocker.patch(
188
- "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
189
+ "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
189
190
  )
190
191
 
191
192
  task, true_solar_single_beam = solar_gain_calibration_task_that_completes
@@ -8,12 +8,11 @@ from dkist_processing_common._util.scratch import WorkflowFileSystem
8
8
  from dkist_processing_common.codecs.fits import fits_array_encoder
9
9
  from dkist_processing_common.codecs.fits import fits_hdulist_encoder
10
10
  from dkist_processing_common.codecs.json import json_decoder
11
- from dkist_processing_common.tests.conftest import FakeGQLClient
12
11
 
13
12
  from dkist_processing_cryonirsp.models.tags import CryonirspTag
14
13
  from dkist_processing_cryonirsp.tasks.sp_wavelength_calibration import SPWavelengthCalibration
15
- from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
16
14
  from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
15
+ from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
17
16
  from dkist_processing_cryonirsp.tests.conftest import generate_fits_frame
18
17
  from dkist_processing_cryonirsp.tests.header_models import CryonirspHeadersValidSPSolarGainFrames
19
18
 
@@ -41,7 +40,7 @@ def sp_dispersion_axis_correction_task(
41
40
  GRATING_POSITION_DEG=grating_position_deg,
42
41
  GRATING_LITTROW_ANGLE_DEG=grating_littrow_angle,
43
42
  GRATING_CONSTANT=grating_constant,
44
- SOLAR_GAIN_IP_START_TIME="2021-01-01T00:00:00",
43
+ SOLAR_GAIN_START_TIME="2021-01-01T00:00:00",
45
44
  )
46
45
  init_cryonirsp_constants_db(recipe_run_id, constants_db)
47
46
  with SPWavelengthCalibration(
@@ -114,14 +113,14 @@ def sp_dispersion_axis_correction_task(
114
113
  task._purge()
115
114
 
116
115
 
117
- def test_sp_dispersion_axis_correction(sp_dispersion_axis_correction_task, mocker):
116
+ def test_sp_dispersion_axis_correction(sp_dispersion_axis_correction_task, mocker, fake_gql_client):
118
117
  """
119
118
  Given: A SPDispersionAxisCorrection task
120
119
  When: Calling the task instance
121
120
  Then: There are the expected number of intermediate fit frames with the correct tags applied and the values have been correctly fit
122
121
  """
123
122
  mocker.patch(
124
- "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
123
+ "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
125
124
  )
126
125
 
127
126
  task, original_header = sp_dispersion_axis_correction_task
@@ -49,7 +49,7 @@ def create_trial_quality_report_task(
49
49
  )
50
50
  task.write(
51
51
  quality_data_warning_only,
52
- tags=CryonirspTag.quality_data(),
52
+ tags=[CryonirspTag.output(), CryonirspTag.quality_data()],
53
53
  encoder=quality_data_encoder,
54
54
  relative_path=f"{task.constants.dataset_id}_quality_data.json",
55
55
  )
@@ -1,4 +1,5 @@
1
1
  """Test integrity of workflows."""
2
+
2
3
  from dkist_processing_core.build_utils import validate_workflows
3
4
 
4
5
  from dkist_processing_cryonirsp import workflows
@@ -9,7 +9,6 @@ from dkist_fits_specifications import __version__ as spec_version
9
9
  from dkist_header_validator import spec214_validator
10
10
  from dkist_processing_common.codecs.fits import fits_hdulist_encoder
11
11
  from dkist_processing_common.codecs.json import json_encoder
12
- from dkist_processing_common.tests.conftest import FakeGQLClient
13
12
 
14
13
  from dkist_processing_cryonirsp.models.tags import CryonirspTag
15
14
  from dkist_processing_cryonirsp.tasks.write_l1 import CIWriteL1Frame
@@ -164,14 +163,16 @@ def write_dummy_sp_dispersion_intermediate(task: SPWriteL1Frame) -> None:
164
163
  "arm_id",
165
164
  [pytest.param("CI", id="CI"), pytest.param("SP", id="SP")],
166
165
  )
167
- def test_write_l1_frame(write_l1_task, mocker, arm_id, num_stokes_params, num_map_scans, num_meas):
166
+ def test_write_l1_frame(
167
+ write_l1_task, mocker, fake_gql_client, arm_id, num_stokes_params, num_map_scans, num_meas
168
+ ):
168
169
  """
169
170
  :Given: a write L1 task
170
171
  :When: running the task
171
172
  :Then: the correct header keys are written
172
173
  """
173
174
  mocker.patch(
174
- "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
175
+ "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
175
176
  )
176
177
  task, stokes_params, original_headers = write_l1_task
177
178
  task()
@@ -221,6 +222,14 @@ def test_write_l1_frame(write_l1_task, mocker, arm_id, num_stokes_params, num_ma
221
222
  axis_num += 1
222
223
  stokes_axis_tests(axis_num, header)
223
224
 
225
+ # Timing header keys
226
+ if task.constants.correct_for_polarization:
227
+ assert header["CADENCE"] == 20 * num_meas
228
+ assert header["XPOSURE"] == 30
229
+ else:
230
+ assert header["CADENCE"] == 10 * num_meas
231
+ assert header["XPOSURE"] == 15
232
+
224
233
  # Other general tests
225
234
  general_header_tests(axis_num, header, task, arm_id)
226
235
 
@@ -423,16 +432,3 @@ def general_header_tests(
423
432
  assert isinstance(header["HLSVERS"], str)
424
433
  assert header["BUNIT"] == ""
425
434
  assert header.comments["BUNIT"] == "Values are relative to disk center. See calibration docs."
426
- assert header["NSPECLNS"] == 1
427
- assert header["WAVEBAND"] == "He I (1083.0 nm)"
428
- assert header["SPECLN01"] == "He I (1083.0 nm)"
429
- with pytest.raises(KeyError):
430
- header["SPECLN02"]
431
-
432
- # wavelength range tests
433
- if arm_id == "SP":
434
- assert round(header["WAVEMIN"], 2) == 1082
435
- assert round(header["WAVEMAX"], 2) == 1084
436
- if arm_id == "CI":
437
- assert header["WAVEMIN"] == 1082.5
438
- assert header["WAVEMAX"] == 1083.5
@@ -1,2 +1,3 @@
1
1
  """Workflow package."""
2
+
2
3
  from dkist_processing_cryonirsp.config import dkist_processing_cryonirsp_configurations
@@ -1,4 +1,5 @@
1
1
  """Cryo CI raw data processing workflow."""
2
+
2
3
  from dkist_processing_common.tasks import PublishCatalogAndQualityMessages
3
4
  from dkist_processing_common.tasks import QualityL1Metrics
4
5
  from dkist_processing_common.tasks import SubmitDatasetMetadata
@@ -11,9 +12,7 @@ from dkist_processing_core import Workflow
11
12
  from dkist_processing_cryonirsp.tasks import AssembleCryonirspMovie
12
13
  from dkist_processing_cryonirsp.tasks import BadPixelMapCalibration
13
14
  from dkist_processing_cryonirsp.tasks import CIBeamBoundariesCalibration
14
- from dkist_processing_cryonirsp.tasks import (
15
- CIInstrumentPolarizationCalibration,
16
- )
15
+ from dkist_processing_cryonirsp.tasks import CIInstrumentPolarizationCalibration
17
16
  from dkist_processing_cryonirsp.tasks import CIScienceCalibration
18
17
  from dkist_processing_cryonirsp.tasks import CISolarGainCalibration
19
18
  from dkist_processing_cryonirsp.tasks import CIWriteL1Frame
@@ -68,9 +67,11 @@ l0_pipeline.add_node(
68
67
  # Output flow
69
68
  l0_pipeline.add_node(
70
69
  task=SubmitDatasetMetadata,
71
- upstreams=[CIWriteL1Frame, CIAssembleQualityData, AssembleCryonirspMovie],
70
+ upstreams=[CIWriteL1Frame, AssembleCryonirspMovie],
71
+ )
72
+ l0_pipeline.add_node(
73
+ task=TransferL1Data, upstreams=[CIWriteL1Frame, AssembleCryonirspMovie, CIAssembleQualityData]
72
74
  )
73
- l0_pipeline.add_node(task=TransferL1Data, upstreams=[CIWriteL1Frame, AssembleCryonirspMovie])
74
75
  l0_pipeline.add_node(
75
76
  task=PublishCatalogAndQualityMessages, upstreams=[SubmitDatasetMetadata, TransferL1Data]
76
77
  )
@@ -1,4 +1,5 @@
1
1
  """Cryo SP raw data processing workflow."""
2
+
2
3
  from dkist_processing_common.tasks import PublishCatalogAndQualityMessages
3
4
  from dkist_processing_common.tasks import QualityL1Metrics
4
5
  from dkist_processing_common.tasks import SubmitDatasetMetadata
@@ -19,9 +20,7 @@ from dkist_processing_cryonirsp.tasks import MakeCryonirspMovieFrames
19
20
  from dkist_processing_cryonirsp.tasks import ParseL0CryonirspRampData
20
21
  from dkist_processing_cryonirsp.tasks import SPBeamBoundariesCalibration
21
22
  from dkist_processing_cryonirsp.tasks import SPGeometricCalibration
22
- from dkist_processing_cryonirsp.tasks import (
23
- SPInstrumentPolarizationCalibration,
24
- )
23
+ from dkist_processing_cryonirsp.tasks import SPInstrumentPolarizationCalibration
25
24
  from dkist_processing_cryonirsp.tasks import SPScienceCalibration
26
25
  from dkist_processing_cryonirsp.tasks import SPSolarGainCalibration
27
26
  from dkist_processing_cryonirsp.tasks import SPWavelengthCalibration
@@ -76,9 +75,11 @@ l0_pipeline.add_node(
76
75
  # Output flow
77
76
  l0_pipeline.add_node(
78
77
  task=SubmitDatasetMetadata,
79
- upstreams=[SPWriteL1Frame, SPAssembleQualityData, AssembleCryonirspMovie],
78
+ upstreams=[SPWriteL1Frame, AssembleCryonirspMovie],
79
+ )
80
+ l0_pipeline.add_node(
81
+ task=TransferL1Data, upstreams=[SPWriteL1Frame, AssembleCryonirspMovie, SPAssembleQualityData]
80
82
  )
81
- l0_pipeline.add_node(task=TransferL1Data, upstreams=[SPWriteL1Frame, AssembleCryonirspMovie])
82
83
  l0_pipeline.add_node(
83
84
  task=PublishCatalogAndQualityMessages, upstreams=[SubmitDatasetMetadata, TransferL1Data]
84
85
  )
@@ -1,4 +1,5 @@
1
1
  """Workflows for trial runs (i.e., not Production)."""
2
+
2
3
  from dkist_processing_common.tasks import CreateTrialAsdf
3
4
  from dkist_processing_common.tasks import CreateTrialDatasetInventory
4
5
  from dkist_processing_common.tasks import CreateTrialQualityReport
@@ -12,9 +13,7 @@ from dkist_processing_core import Workflow
12
13
  from dkist_processing_cryonirsp.tasks import AssembleCryonirspMovie
13
14
  from dkist_processing_cryonirsp.tasks import BadPixelMapCalibration
14
15
  from dkist_processing_cryonirsp.tasks import CIBeamBoundariesCalibration
15
- from dkist_processing_cryonirsp.tasks import (
16
- CIInstrumentPolarizationCalibration,
17
- )
16
+ from dkist_processing_cryonirsp.tasks import CIInstrumentPolarizationCalibration
18
17
  from dkist_processing_cryonirsp.tasks import CIScienceCalibration
19
18
  from dkist_processing_cryonirsp.tasks import CISolarGainCalibration
20
19
  from dkist_processing_cryonirsp.tasks import CIWriteL1Frame
@@ -27,9 +26,7 @@ from dkist_processing_cryonirsp.tasks import MakeCryonirspMovieFrames
27
26
  from dkist_processing_cryonirsp.tasks import ParseL0CryonirspRampData
28
27
  from dkist_processing_cryonirsp.tasks import SPBeamBoundariesCalibration
29
28
  from dkist_processing_cryonirsp.tasks import SPGeometricCalibration
30
- from dkist_processing_cryonirsp.tasks import (
31
- SPInstrumentPolarizationCalibration,
32
- )
29
+ from dkist_processing_cryonirsp.tasks import SPInstrumentPolarizationCalibration
33
30
  from dkist_processing_cryonirsp.tasks import SPScienceCalibration
34
31
  from dkist_processing_cryonirsp.tasks import SPSolarGainCalibration
35
32
  from dkist_processing_cryonirsp.tasks import SPWavelengthCalibration
@@ -93,7 +90,9 @@ full_trial_ci_pipeline.add_node(
93
90
  )
94
91
  full_trial_ci_pipeline.add_node(task=CreateTrialAsdf, upstreams=CIWriteL1Frame, pip_extras=["asdf"])
95
92
  full_trial_ci_pipeline.add_node(
96
- task=CreateTrialQualityReport, upstreams=CIAssembleQualityData, pip_extras=["quality"]
93
+ task=CreateTrialQualityReport,
94
+ upstreams=CIAssembleQualityData,
95
+ pip_extras=["quality", "inventory"],
97
96
  )
98
97
 
99
98
  # Output flow
@@ -171,7 +170,9 @@ full_trial_sp_pipeline.add_node(
171
170
  )
172
171
  full_trial_sp_pipeline.add_node(task=CreateTrialAsdf, upstreams=SPWriteL1Frame, pip_extras=["asdf"])
173
172
  full_trial_sp_pipeline.add_node(
174
- task=CreateTrialQualityReport, upstreams=SPAssembleQualityData, pip_extras=["quality"]
173
+ task=CreateTrialQualityReport,
174
+ upstreams=SPAssembleQualityData,
175
+ pip_extras=["quality", "inventory"],
175
176
  )
176
177
 
177
178
  # Output flow