dkist-processing-vbi 1.34.8__tar.gz → 2.0.0__tar.gz

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 (93) hide show
  1. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/CHANGELOG.rst +11 -0
  2. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/PKG-INFO +30 -31
  3. dkist_processing_vbi-2.0.0/SCIENCE_CHANGELOG.rst +18 -0
  4. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/models/parameters.py +5 -0
  5. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tasks/assemble_movie.py +20 -7
  6. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tasks/gain.py +57 -3
  7. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/conftest.py +1 -0
  8. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_assemble_movie.py +86 -8
  9. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_gain.py +50 -1
  10. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi.egg-info/PKG-INFO +30 -31
  11. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi.egg-info/requires.txt +29 -30
  12. dkist_processing_vbi-2.0.0/dkist_processing_vbi.egg-info/scm_version.json +8 -0
  13. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/pyproject.toml +31 -32
  14. dkist_processing_vbi-1.34.8/SCIENCE_CHANGELOG.rst +0 -7
  15. dkist_processing_vbi-1.34.8/dkist_processing_vbi.egg-info/scm_version.json +0 -8
  16. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/.gitignore +0 -0
  17. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/.pre-commit-config.yaml +0 -0
  18. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/.readthedocs.yml +0 -0
  19. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/.rovodev/.review-agent.md +0 -0
  20. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/.snyk +0 -0
  21. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/README.rst +0 -0
  22. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/bitbucket-pipelines.yml +0 -0
  23. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/changelog/.gitempty +0 -0
  24. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/__init__.py +0 -0
  25. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/config.py +0 -0
  26. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/models/__init__.py +0 -0
  27. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/models/burst.py +0 -0
  28. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/models/constants.py +0 -0
  29. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/models/dataset_extras.py +0 -0
  30. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/models/filter.py +0 -0
  31. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/models/fits_access.py +0 -0
  32. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/models/tags.py +0 -0
  33. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/models/task_name.py +0 -0
  34. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/parsers/__init__.py +0 -0
  35. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/parsers/burst.py +0 -0
  36. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/parsers/mosaic_repeats.py +0 -0
  37. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/parsers/spatial_step_pattern.py +0 -0
  38. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/parsers/time.py +0 -0
  39. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/parsers/vbi_l0_fits_access.py +0 -0
  40. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/parsers/vbi_l1_fits_access.py +0 -0
  41. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tasks/__init__.py +0 -0
  42. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tasks/dark.py +0 -0
  43. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tasks/make_movie_frames.py +0 -0
  44. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tasks/parse.py +0 -0
  45. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tasks/process_summit_processed.py +0 -0
  46. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tasks/quality_metrics.py +0 -0
  47. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tasks/science.py +0 -0
  48. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tasks/vbi_base.py +0 -0
  49. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tasks/write_dataset_extras.py +0 -0
  50. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tasks/write_l1.py +0 -0
  51. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/__init__.py +0 -0
  52. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/local_trial_workflows/__init__.py +0 -0
  53. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/local_trial_workflows/l0_to_l1.py +0 -0
  54. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_dark.py +0 -0
  55. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_dataset_extras.py +0 -0
  56. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_fits_access.py +0 -0
  57. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_make_movie_frames.py +0 -0
  58. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_parse_l0.py +0 -0
  59. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_parse_summit.py +0 -0
  60. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_process_summit.py +0 -0
  61. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_quality_metrics.py +0 -0
  62. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_science.py +0 -0
  63. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_spatial_step_pattern.py +0 -0
  64. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_vbi_base.py +0 -0
  65. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_vbi_constants.py +0 -0
  66. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_workflows.py +0 -0
  67. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/tests/test_write_l1.py +0 -0
  68. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/workflows/__init__.py +0 -0
  69. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/workflows/l0_processing.py +0 -0
  70. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/workflows/summit_data_processing.py +0 -0
  71. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi/workflows/trial_workflows.py +0 -0
  72. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi.egg-info/SOURCES.txt +0 -0
  73. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi.egg-info/dependency_links.txt +0 -0
  74. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi.egg-info/scm_file_list.json +0 -0
  75. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/dkist_processing_vbi.egg-info/top_level.txt +0 -0
  76. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/Makefile +0 -0
  77. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/changelog.rst +0 -0
  78. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/conf.py +0 -0
  79. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/dataset_extras.rst +0 -0
  80. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/index.rst +0 -0
  81. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/l0_to_l1_vbi_no-speckle-full-trial.rst +0 -0
  82. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/l0_to_l1_vbi_no-speckle.rst +0 -0
  83. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/l0_to_l1_vbi_summit-calibrated-full-trial.rst +0 -0
  84. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/l0_to_l1_vbi_summit-calibrated.rst +0 -0
  85. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/landing_page.rst +0 -0
  86. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/make.bat +0 -0
  87. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/requirements.txt +0 -0
  88. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/requirements_table.rst +0 -0
  89. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/docs/scientific_changelog.rst +0 -0
  90. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/licenses/LICENSE.rst +0 -0
  91. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/science_towncrier.sh +0 -0
  92. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/setup.cfg +0 -0
  93. {dkist_processing_vbi-1.34.8 → dkist_processing_vbi-2.0.0}/towncrier_science.toml +0 -0
@@ -1,3 +1,14 @@
1
+ v2.0.0 (2026-09-03)
2
+ ===================
3
+
4
+ Features
5
+ --------
6
+
7
+ - Distinguish large, edge-connected gain occlusions from detector bad pixels, assigning occluded pixels a neutral gain of 1.0 and excluding them from bad-pixel correction. (`#231 <https://bitbucket.org/dkistdc/dkist-processing-vbi/pull-requests/231>`__)
8
+ - Update VBI movies to use percentile-clipped linear intensity scaling. (`#231 <https://bitbucket.org/dkistdc/dkist-processing-vbi/pull-requests/231>`__)
9
+ - Update VBI movies to display the observation's scanning mode. (`#231 <https://bitbucket.org/dkistdc/dkist-processing-vbi/pull-requests/231>`__)
10
+
11
+
1
12
  v1.34.8 (2026-08-25)
2
13
  ====================
3
14
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dkist-processing-vbi
3
- Version: 1.34.8
3
+ Version: 2.0.0
4
4
  Summary: Processing code for the VBI instrument on DKIST
5
5
  Author-email: NSO / AURA <dkistdc@nso.edu>
6
6
  License: BSD-3-Clause
@@ -80,7 +80,7 @@ Requires-Dist: alembic==1.19.1; extra == "frozen"
80
80
  Requires-Dist: amqp==5.3.1; extra == "frozen"
81
81
  Requires-Dist: annotated-doc==0.0.5; extra == "frozen"
82
82
  Requires-Dist: annotated-types==0.8.0; extra == "frozen"
83
- Requires-Dist: anyio==4.14.2; extra == "frozen"
83
+ Requires-Dist: anyio==4.15.0; extra == "frozen"
84
84
  Requires-Dist: apache-airflow==3.3.1; extra == "frozen"
85
85
  Requires-Dist: apache-airflow-core==3.3.1; extra == "frozen"
86
86
  Requires-Dist: apache-airflow-providers-celery==3.23.1; extra == "frozen"
@@ -101,7 +101,7 @@ Requires-Dist: asdf_standard==1.5.0; extra == "frozen"
101
101
  Requires-Dist: asgiref==3.12.1; extra == "frozen"
102
102
  Requires-Dist: asteval==1.0.10; extra == "frozen"
103
103
  Requires-Dist: astropy==7.0.2; extra == "frozen"
104
- Requires-Dist: astropy-iers-data==0.2026.8.24.0.24.29; extra == "frozen"
104
+ Requires-Dist: astropy-iers-data==0.2026.8.31.0.57.9; extra == "frozen"
105
105
  Requires-Dist: astropy_healpix==2.0.1; extra == "frozen"
106
106
  Requires-Dist: asttokens==3.0.2; extra == "frozen"
107
107
  Requires-Dist: async-lru==2.3.0; extra == "frozen"
@@ -111,15 +111,15 @@ Requires-Dist: babel==2.18.0; extra == "frozen"
111
111
  Requires-Dist: beautifulsoup4==4.15.0; extra == "frozen"
112
112
  Requires-Dist: billiard==4.2.4; extra == "frozen"
113
113
  Requires-Dist: bleach==6.4.0; extra == "frozen"
114
- Requires-Dist: boto3==1.43.79; extra == "frozen"
115
- Requires-Dist: botocore==1.43.79; extra == "frozen"
116
- Requires-Dist: cachetools==7.1.7; extra == "frozen"
114
+ Requires-Dist: boto3==1.43.88; extra == "frozen"
115
+ Requires-Dist: botocore==1.43.88; extra == "frozen"
116
+ Requires-Dist: cachetools==7.1.8; extra == "frozen"
117
117
  Requires-Dist: cadwyn==7.0.0; extra == "frozen"
118
118
  Requires-Dist: celery==5.6.3; extra == "frozen"
119
119
  Requires-Dist: certifi==2026.7.22; extra == "frozen"
120
120
  Requires-Dist: cffi==2.1.1; extra == "frozen"
121
121
  Requires-Dist: charset-normalizer==3.5.1; extra == "frozen"
122
- Requires-Dist: click==8.4.2; extra == "frozen"
122
+ Requires-Dist: click==8.5.0; extra == "frozen"
123
123
  Requires-Dist: click-didyoumean==0.3.1; extra == "frozen"
124
124
  Requires-Dist: click-plugins==1.1.1.2; extra == "frozen"
125
125
  Requires-Dist: click-repl==0.3.0; extra == "frozen"
@@ -129,7 +129,7 @@ Requires-Dist: comm==0.2.3; extra == "frozen"
129
129
  Requires-Dist: contourpy==1.3.3; extra == "frozen"
130
130
  Requires-Dist: cron_descriptor==2.1.0; extra == "frozen"
131
131
  Requires-Dist: croniter==6.2.4; extra == "frozen"
132
- Requires-Dist: cryptography==50.0.0; extra == "frozen"
132
+ Requires-Dist: cryptography==50.0.1; extra == "frozen"
133
133
  Requires-Dist: cycler==0.12.1; extra == "frozen"
134
134
  Requires-Dist: dask==2026.8.0; extra == "frozen"
135
135
  Requires-Dist: dask-image==2026.5.0; extra == "frozen"
@@ -142,7 +142,7 @@ Requires-Dist: dkist-processing-common==14.4.0; extra == "frozen"
142
142
  Requires-Dist: dkist-processing-core==7.5.0; extra == "frozen"
143
143
  Requires-Dist: dkist-processing-math==2.2.3; extra == "frozen"
144
144
  Requires-Dist: dkist-processing-pac==3.1.2; extra == "frozen"
145
- Requires-Dist: dkist-processing-vbi==1.34.8; extra == "frozen"
145
+ Requires-Dist: dkist-processing-vbi==2.0.0; extra == "frozen"
146
146
  Requires-Dist: dkist-service-configuration==4.3.0; extra == "frozen"
147
147
  Requires-Dist: dkist-spectral-lines==3.0.1; extra == "frozen"
148
148
  Requires-Dist: dkist_fits_specifications==4.28.1; extra == "frozen"
@@ -154,17 +154,17 @@ Requires-Dist: fastapi==0.136.3; extra == "frozen"
154
154
  Requires-Dist: fastapi-cli==0.0.32; extra == "frozen"
155
155
  Requires-Dist: fastjsonschema==2.22.2; extra == "frozen"
156
156
  Requires-Dist: flower==2.1.0; extra == "frozen"
157
- Requires-Dist: fonttools==4.63.0; extra == "frozen"
157
+ Requires-Dist: fonttools==4.64.0; extra == "frozen"
158
158
  Requires-Dist: fqdn==1.5.1; extra == "frozen"
159
159
  Requires-Dist: frozenlist==1.8.0; extra == "frozen"
160
160
  Requires-Dist: fsspec==2026.7.0; extra == "frozen"
161
161
  Requires-Dist: globus-sdk==4.9.0; extra == "frozen"
162
162
  Requires-Dist: google-crc32c==1.8.0; extra == "frozen"
163
- Requires-Dist: googleapis-common-protos==1.75.1; extra == "frozen"
163
+ Requires-Dist: googleapis-common-protos==1.75.2; extra == "frozen"
164
164
  Requires-Dist: gqlclient==2.0.0; extra == "frozen"
165
165
  Requires-Dist: greenback==1.3.0; extra == "frozen"
166
166
  Requires-Dist: greenlet==3.5.5; extra == "frozen"
167
- Requires-Dist: grpcio==1.83.0; extra == "frozen"
167
+ Requires-Dist: grpcio==1.83.1; extra == "frozen"
168
168
  Requires-Dist: h11==0.16.0; extra == "frozen"
169
169
  Requires-Dist: httpcore==1.0.9; extra == "frozen"
170
170
  Requires-Dist: httptools==0.8.0; extra == "frozen"
@@ -172,9 +172,9 @@ Requires-Dist: httpx==0.28.1; extra == "frozen"
172
172
  Requires-Dist: humanize==4.16.0; extra == "frozen"
173
173
  Requires-Dist: idna==3.19; extra == "frozen"
174
174
  Requires-Dist: imageio-ffmpeg==0.6.0; extra == "frozen"
175
- Requires-Dist: importlib_metadata==9.0.0; extra == "frozen"
175
+ Requires-Dist: importlib_metadata==9.0.1; extra == "frozen"
176
176
  Requires-Dist: ipykernel==7.3.0; extra == "frozen"
177
- Requires-Dist: ipython==9.16.1; extra == "frozen"
177
+ Requires-Dist: ipython==9.17.1; extra == "frozen"
178
178
  Requires-Dist: ipython_pygments_lexers==1.1.1; extra == "frozen"
179
179
  Requires-Dist: isoduration==20.11.0; extra == "frozen"
180
180
  Requires-Dist: itsdangerous==2.2.0; extra == "frozen"
@@ -187,20 +187,20 @@ Requires-Dist: jsonschema-specifications==2025.9.1; extra == "frozen"
187
187
  Requires-Dist: jupyter-events==0.12.1; extra == "frozen"
188
188
  Requires-Dist: jupyter-lsp==2.3.1; extra == "frozen"
189
189
  Requires-Dist: jupyter_builder==1.2.2; extra == "frozen"
190
- Requires-Dist: jupyter_client==8.9.1; extra == "frozen"
190
+ Requires-Dist: jupyter_client==8.10.0; extra == "frozen"
191
191
  Requires-Dist: jupyter_core==5.9.1; extra == "frozen"
192
- Requires-Dist: jupyter_server==2.20.0; extra == "frozen"
192
+ Requires-Dist: jupyter_server==2.21.0; extra == "frozen"
193
193
  Requires-Dist: jupyter_server_terminals==0.5.4; extra == "frozen"
194
194
  Requires-Dist: jupyterlab==4.6.3; extra == "frozen"
195
195
  Requires-Dist: jupyterlab_pygments==0.3.0; extra == "frozen"
196
196
  Requires-Dist: jupyterlab_server==2.28.0; extra == "frozen"
197
- Requires-Dist: kiwisolver==1.5.0; extra == "frozen"
197
+ Requires-Dist: kiwisolver==1.5.1; extra == "frozen"
198
198
  Requires-Dist: kombu==5.6.2; extra == "frozen"
199
199
  Requires-Dist: lark==1.3.1; extra == "frozen"
200
200
  Requires-Dist: lazy-loader==0.5; extra == "frozen"
201
201
  Requires-Dist: lazy-object-proxy==1.12.0; extra == "frozen"
202
202
  Requires-Dist: libcst==1.9.0; extra == "frozen"
203
- Requires-Dist: linkify-it-py==2.1.1; extra == "frozen"
203
+ Requires-Dist: linkify-it-py==2.2.0; extra == "frozen"
204
204
  Requires-Dist: lmfit==1.3.4; extra == "frozen"
205
205
  Requires-Dist: locket==1.0.0; extra == "frozen"
206
206
  Requires-Dist: lockfile==0.12.2; extra == "frozen"
@@ -264,27 +264,27 @@ Requires-Dist: pexpect==4.9.0; extra == "frozen"
264
264
  Requires-Dist: pika==1.4.4; extra == "frozen"
265
265
  Requires-Dist: pillow==10.4.0; extra == "frozen"
266
266
  Requires-Dist: pip==26.2.1; extra == "frozen"
267
- Requires-Dist: platformdirs==4.11.4; extra == "frozen"
267
+ Requires-Dist: platformdirs==4.11.7; extra == "frozen"
268
268
  Requires-Dist: pluggy==1.6.0; extra == "frozen"
269
269
  Requires-Dist: pooch==1.9.0; extra == "frozen"
270
270
  Requires-Dist: proglog==0.1.12; extra == "frozen"
271
271
  Requires-Dist: prometheus_client==0.26.0; extra == "frozen"
272
272
  Requires-Dist: prompt_toolkit==3.0.53; extra == "frozen"
273
273
  Requires-Dist: propcache==0.5.2; extra == "frozen"
274
- Requires-Dist: protobuf==7.36.0; extra == "frozen"
274
+ Requires-Dist: protobuf==7.36.1; extra == "frozen"
275
275
  Requires-Dist: psutil==7.2.2; extra == "frozen"
276
- Requires-Dist: psycopg==3.3.4; extra == "frozen"
277
- Requires-Dist: psycopg-binary==3.3.4; extra == "frozen"
276
+ Requires-Dist: psycopg==3.3.5; extra == "frozen"
277
+ Requires-Dist: psycopg-binary==3.3.5; extra == "frozen"
278
278
  Requires-Dist: psycopg2-binary==2.9.12; extra == "frozen"
279
279
  Requires-Dist: ptyprocess==0.7.0; extra == "frozen"
280
280
  Requires-Dist: pure_eval==0.2.3; extra == "frozen"
281
281
  Requires-Dist: pycparser==3.0; extra == "frozen"
282
- Requires-Dist: pydantic==2.13.4; extra == "frozen"
282
+ Requires-Dist: pydantic==2.13.5; extra == "frozen"
283
283
  Requires-Dist: pydantic-extra-types==2.11.1; extra == "frozen"
284
284
  Requires-Dist: pydantic-settings==2.15.0; extra == "frozen"
285
- Requires-Dist: pydantic_core==2.46.4; extra == "frozen"
285
+ Requires-Dist: pydantic_core==2.46.5; extra == "frozen"
286
286
  Requires-Dist: pyerfa==2.0.1.5; extra == "frozen"
287
- Requires-Dist: pygtrie==2.5.0; extra == "frozen"
287
+ Requires-Dist: pygtrie==2.6.1; extra == "frozen"
288
288
  Requires-Dist: pyparsing==3.3.2; extra == "frozen"
289
289
  Requires-Dist: python-daemon==3.1.2; extra == "frozen"
290
290
  Requires-Dist: python-dateutil==2.9.0.post0; extra == "frozen"
@@ -335,12 +335,11 @@ Requires-Dist: toolz==1.1.0; extra == "frozen"
335
335
  Requires-Dist: tornado==6.5.8; extra == "frozen"
336
336
  Requires-Dist: tqdm==4.70.0; extra == "frozen"
337
337
  Requires-Dist: traitlets==5.16.1; extra == "frozen"
338
- Requires-Dist: typer==0.27.1; extra == "frozen"
338
+ Requires-Dist: typer==0.27.2; extra == "frozen"
339
339
  Requires-Dist: typing-inspection==0.4.4; extra == "frozen"
340
340
  Requires-Dist: typing_extensions==4.16.0; extra == "frozen"
341
341
  Requires-Dist: tzdata==2026.3; extra == "frozen"
342
342
  Requires-Dist: tzlocal==5.4.4; extra == "frozen"
343
- Requires-Dist: uc-micro-py==2.0.0; extra == "frozen"
344
343
  Requires-Dist: uncertainties==3.2.3; extra == "frozen"
345
344
  Requires-Dist: universal_pathlib==0.3.10; extra == "frozen"
346
345
  Requires-Dist: uri-template==1.3.0; extra == "frozen"
@@ -351,13 +350,13 @@ Requires-Dist: uvloop==0.22.1; extra == "frozen"
351
350
  Requires-Dist: vine==5.1.0; extra == "frozen"
352
351
  Requires-Dist: voluptuous==0.16.0; extra == "frozen"
353
352
  Requires-Dist: watchfiles==1.2.0; extra == "frozen"
354
- Requires-Dist: wcwidth==0.8.2; extra == "frozen"
353
+ Requires-Dist: wcwidth==0.8.3; extra == "frozen"
355
354
  Requires-Dist: webcolors==25.10.0; extra == "frozen"
356
355
  Requires-Dist: webencodings==0.6.1; extra == "frozen"
357
- Requires-Dist: websocket-client==1.9.0; extra == "frozen"
358
- Requires-Dist: websockets==17.0.1; extra == "frozen"
356
+ Requires-Dist: websocket-client==1.9.2; extra == "frozen"
357
+ Requires-Dist: websockets==17.1; extra == "frozen"
359
358
  Requires-Dist: wirerope==1.0.0; extra == "frozen"
360
- Requires-Dist: wrapt==2.3.0; extra == "frozen"
359
+ Requires-Dist: wrapt==2.4.0; extra == "frozen"
361
360
  Requires-Dist: yamale==6.1.0; extra == "frozen"
362
361
  Requires-Dist: yarl==1.24.5; extra == "frozen"
363
362
  Requires-Dist: zarr==3.3.0; extra == "frozen"
@@ -0,0 +1,18 @@
1
+ v2.0.0 (2026-09-03)
2
+ ===================
3
+
4
+
5
+
6
+
7
+ - Large, edge-connected regions identified as bad in a gain frame can now be classified as observation-related occlusions using the configurable ``vbi_occlusion_minimum_connected_pixels`` threshold.
8
+ Occluded pixels are excluded from full-field gain normalization and the bad-pixel map, then assigned a neutral gain of 1.0.
9
+ This prevents unilluminated detector regions from being interpolated as if they were defective camera pixels and avoids invalid values when a correction neighborhood would otherwise contain only flagged pixels. (`#231 <https://bitbucket.org/dkistdc/dkist-processing-vbi/pull-requests/231>`__)
10
+
11
+
12
+ v1.1.0 (2022-12-15)
13
+ ===================
14
+
15
+
16
+
17
+
18
+ - Don't re-compress already compressed data that are processed at the summit. This ensures L1 files served by the Data Center are *exactly* the same as those processed on the summit. (`#39 <https://bitbucket.org/dkistdc/dkist-processing-vbi/pull-requests/39>`__)
@@ -16,6 +16,11 @@ class VbiParameters(ParameterBase):
16
16
  """Return the number of standard deviations used to identify bad pixels."""
17
17
  return self._find_most_recent_past_value("vbi_bad_pixel_standard_deviations")
18
18
 
19
+ @property
20
+ def occlusion_minimum_connected_pixels(self) -> int:
21
+ """Return the minimum edge-connected area classified as an occlusion."""
22
+ return self._find_most_recent_past_value("vbi_occlusion_minimum_connected_pixels")
23
+
19
24
  @property
20
25
  def movie_intensity_clipping_percentile(self) -> float:
21
26
  """Percentile to clip the movie intensity at."""
@@ -208,7 +208,7 @@ class AssembleVbiMovie(VbiTaskBase):
208
208
 
209
209
  def apply_colormap(self, array: np.ndarray) -> np.ndarray:
210
210
  """
211
- Convert floats to RGB colors using the ZScale normalization scheme.
211
+ Convert floats to RGB colors using a linear normalization scheme.
212
212
 
213
213
  Parameters
214
214
  ----------
@@ -217,13 +217,23 @@ class AssembleVbiMovie(VbiTaskBase):
217
217
  """
218
218
  # Clip the top and bottom 0.5% of data values to improve the contrast
219
219
  movie_intensity_clipping_percentile = self.parameters.movie_intensity_clipping_percentile
220
- vmin, vmax = np.nanpercentile(
221
- array, (movie_intensity_clipping_percentile, 100 - movie_intensity_clipping_percentile)
222
- )
223
- array = np.clip(a=array, a_min=vmin, a_max=vmax)
220
+ finite = np.isfinite(array)
221
+ scaled_array = np.full(array.shape, np.nan)
222
+ if np.any(finite):
223
+ vmin, vmax = np.percentile(
224
+ array[finite],
225
+ (movie_intensity_clipping_percentile, 100 - movie_intensity_clipping_percentile),
226
+ )
227
+ if vmax > vmin:
228
+ scaled_array[finite] = np.clip(
229
+ a=(array[finite] - vmin) / (vmax - vmin),
230
+ a_min=0,
231
+ a_max=1,
232
+ )
233
+ else:
234
+ scaled_array[finite] = 0.0
224
235
 
225
236
  color_mapper = colormaps.get_cmap("gray").with_extremes(bad="white")
226
- scaled_array = ZScaleInterval()(array)
227
237
  return color_mapper(scaled_array, bytes=True)[
228
238
  :, :, :-1
229
239
  ] # Drop the last (alpha) color dimension
@@ -436,8 +446,11 @@ class AssembleVbiMovie(VbiTaskBase):
436
446
  last_frame = self.movie_frames[-1]
437
447
  obs_part_id = self.metadata_store_input_dataset_observe_frames.inputDatasetPartId
438
448
  product_id = WriteL1Frame.compute_product_id(obs_part_id, "L1")
449
+ scanning_mode = (
450
+ "Center only" if first_frame.number_of_spatial_steps == 1 else "Field Scanning"
451
+ )
439
452
  return (
440
- f"Instrument: {self.instrument_string} -- Wavelength: {self.constants.wavelength:.3f} nm\n"
453
+ f"Instrument: {self.instrument_string} -- Wavelength: {self.constants.wavelength:.3f} nm -- Scanning Mode: {scanning_mode}\n"
441
454
  f"Product ID: {product_id} --- Experiment ID: {self.constants.experiment_id}\n"
442
455
  f"Start Time: {first_frame.time_obs} --- End Time: {last_frame.time_obs}"
443
456
  )
@@ -8,6 +8,7 @@ from dkist_processing_common.tasks.mixin.quality import QualityMixin
8
8
  from dkist_processing_math.arithmetic import subtract_array_from_arrays
9
9
  from dkist_processing_math.statistics import average_numpy_arrays
10
10
  from dkist_service_configuration.logging import logger
11
+ from scipy import ndimage
11
12
 
12
13
  from dkist_processing_vbi.models.tags import VbiTag
13
14
  from dkist_processing_vbi.tasks.vbi_base import VbiTaskBase
@@ -52,8 +53,9 @@ class GainCalibration(VbiTaskBase, QualityMixin):
52
53
 
53
54
  # We'll just stuff the un-normalized arrays in this dictionary to avoid dealing with tags, io, etc.
54
55
  # This is OK (tm) because this will be, at most, 9 4k x 4k arrays. This is a lot (~1G), but not too much.
55
- step_gain_dict: dict = {}
56
- step_bad_pixel_map_dict: dict = {}
56
+ step_gain_dict: dict[int, np.ndarray] = {}
57
+ step_bad_pixel_map_dict: dict[int, np.ndarray] = {}
58
+ step_occlusion_mask_dict: dict[int, np.ndarray] = {}
57
59
 
58
60
  with self.telemetry_span(
59
61
  f"Collecting and reducing gain arrays from {self.constants.num_spatial_steps} steps and {len(self.constants.solar_gain_exposure_times)} exp times",
@@ -102,10 +104,13 @@ class GainCalibration(VbiTaskBase, QualityMixin):
102
104
  averaged_gain_array=averaged_gain_array,
103
105
  dark_subtracted_gain_array=dark_subtracted_gain_array,
104
106
  )
107
+ occlusion_mask = self.identify_occlusion_pixels(bad_pixel_map)
108
+ step_occlusion_mask_dict[step] = occlusion_mask
109
+ bad_pixel_map = bad_pixel_map & ~occlusion_mask
105
110
  step_bad_pixel_map_dict[step] = bad_pixel_map
106
111
 
107
112
  logger.info(f"Recording processed gain image for {step = }")
108
- usable_pixel_mask = ~bad_pixel_map
113
+ usable_pixel_mask = ~(bad_pixel_map | occlusion_mask)
109
114
  self.total_usable_pix[step] = np.count_nonzero(usable_pixel_mask)
110
115
  self.total_counts[step] = np.sum(dark_subtracted_gain_array[usable_pixel_mask])
111
116
  step_gain_dict[step] = dark_subtracted_gain_array
@@ -113,6 +118,12 @@ class GainCalibration(VbiTaskBase, QualityMixin):
113
118
  with self.telemetry_span("normalizing gain arrays"):
114
119
  normalized_array_dict = self.normalize_fov(step_gain_dict)
115
120
 
121
+ with self.telemetry_span("neutralizing occluded gain values"):
122
+ normalized_array_dict = self.neutralize_occlusion_gain_values(
123
+ gain_array_dict=normalized_array_dict,
124
+ step_occlusion_mask_dict=step_occlusion_mask_dict,
125
+ )
126
+
116
127
  with self.telemetry_span("replacing invalid inverse-gain values"):
117
128
  normalized_array_dict = self.replace_invalid_gain_values(normalized_array_dict)
118
129
 
@@ -160,6 +171,49 @@ class GainCalibration(VbiTaskBase, QualityMixin):
160
171
 
161
172
  return step_gain_dict
162
173
 
174
+ def identify_occlusion_pixels(self, bad_pixel_map: np.ndarray) -> np.ndarray:
175
+ """Find large connected bad-pixel regions caused by edge occlusion."""
176
+ bad_pixel_plane = np.squeeze(bad_pixel_map)
177
+ # Give each 8-connected region a unique label
178
+ labels, _ = ndimage.label(
179
+ bad_pixel_plane,
180
+ structure=np.ones((3, 3), dtype=bool),
181
+ )
182
+ # Get the label number of every region that touches an edge
183
+ edge_labels = np.unique(
184
+ np.concatenate(
185
+ (
186
+ labels[0, :],
187
+ labels[-1, :],
188
+ labels[:, 0],
189
+ labels[:, -1],
190
+ )
191
+ )
192
+ )
193
+ edge_labels = edge_labels[edge_labels != 0]
194
+
195
+ occlusion_plane = np.zeros_like(bad_pixel_plane, dtype=bool)
196
+ component_sizes = np.bincount(labels.ravel())
197
+ minimum_connected_pixels = self.parameters.occlusion_minimum_connected_pixels
198
+ for label in edge_labels:
199
+ component_size = component_sizes[label]
200
+ if component_size >= minimum_connected_pixels:
201
+ # Add the pixels in this component to the occlusion plane
202
+ occlusion_plane |= labels == label
203
+ logger.info(f"Identified {component_size} edge-connected occlusion pixels")
204
+
205
+ return occlusion_plane.reshape(bad_pixel_map.shape)
206
+
207
+ @staticmethod
208
+ def neutralize_occlusion_gain_values(
209
+ gain_array_dict: dict[int, np.ndarray],
210
+ step_occlusion_mask_dict: dict[int, np.ndarray],
211
+ ) -> dict[int, np.ndarray]:
212
+ """Set occluded pixels to a neutral gain correction."""
213
+ for step, occlusion_mask in step_occlusion_mask_dict.items():
214
+ gain_array_dict[step][occlusion_mask] = 1.0
215
+ return gain_array_dict
216
+
163
217
  @staticmethod
164
218
  def replace_invalid_gain_values(
165
219
  gain_array_dict: dict[int, np.ndarray],
@@ -346,6 +346,7 @@ class VbiConstantsDb:
346
346
  class VbiInputDatasetParameterValues:
347
347
  vbi_bad_pixel_neighborhood_radius: int = 5
348
348
  vbi_bad_pixel_standard_deviations: float = 9.0
349
+ vbi_occlusion_minimum_connected_pixels: int = 1000
349
350
  vbi_movie_intensity_clipping_percentile: float = 0.5
350
351
  vbi_central_region_side_length: int = 300
351
352
 
@@ -3,6 +3,9 @@ import pytest
3
3
  from astropy.visualization.wcsaxes import WCSAxes
4
4
  from dkist_processing_common._util.scratch import WorkflowFileSystem
5
5
  from dkist_processing_common.codecs.fits import fits_hdulist_encoder
6
+ from dkist_processing_common.tests.mock_metadata_store import (
7
+ make_default_input_dataset_recipe_run_response,
8
+ )
6
9
  from imageio_ffmpeg import count_frames_and_secs
7
10
  from imageio_ffmpeg import read_frames
8
11
 
@@ -14,9 +17,22 @@ from dkist_processing_vbi.tests.conftest import VbiInputDatasetParameterValues
14
17
  from dkist_processing_vbi.tests.conftest import generate_214_l1_fits_frame
15
18
 
16
19
 
20
+ @pytest.fixture(
21
+ scope="function",
22
+ params=[1, 10],
23
+ ids=["1_mosaic_repeat", "10_mosaic_repeats"],
24
+ )
25
+ def num_mosaic_repeats(request):
26
+ return request.param
27
+
28
+
17
29
  @pytest.fixture(scope="function")
18
30
  def assemble_task_with_tagged_movie_frames(
19
- tmp_path, recipe_run_id, init_vbi_constants_db, num_mosaic_repeats
31
+ tmp_path,
32
+ recipe_run_id,
33
+ init_vbi_constants_db,
34
+ assign_input_dataset_doc_to_task,
35
+ num_mosaic_repeats,
20
36
  ):
21
37
  constants_db = VbiConstantsDb(NUM_MOSAIC_REPEATS=num_mosaic_repeats)
22
38
  init_vbi_constants_db(recipe_run_id, constants_db)
@@ -47,26 +63,20 @@ def assemble_task_with_tagged_movie_frames(
47
63
  ],
48
64
  encoder=fits_hdulist_encoder,
49
65
  )
66
+ assign_input_dataset_doc_to_task(task, VbiInputDatasetParameterValues())
50
67
  yield task
51
68
  task._purge()
52
69
 
53
70
 
54
- @pytest.mark.parametrize(
55
- "num_mosaic_repeats",
56
- [pytest.param(1, id="1_mosaic_repeat"), pytest.param(10, id="10_mosaic_repeats")],
57
- )
58
71
  def test_assemble_movie(
59
72
  assemble_task_with_tagged_movie_frames,
60
73
  mocker,
61
- assign_input_dataset_doc_to_task,
62
74
  fake_gql_client,
63
- num_mosaic_repeats,
64
75
  ):
65
76
  mocker.patch(
66
77
  "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
67
78
  )
68
79
  task = assemble_task_with_tagged_movie_frames
69
- assign_input_dataset_doc_to_task(task, VbiInputDatasetParameterValues())
70
80
  task()
71
81
  assert task.parameters.movie_intensity_clipping_percentile == 0.5
72
82
  movie_file = list(assemble_task_with_tagged_movie_frames.read(tags=[VbiTag.movie()]))
@@ -82,3 +92,71 @@ def test_assemble_movie(
82
92
  assert f"DKIST/VBI-RED Level 1 Quicklook -- Dataset {task.constants.dataset_id}" in figure_text
83
93
  assert isinstance(task.movie_axis, WCSAxes)
84
94
  assert all(frame.data is None for frame in task.movie_frames)
95
+
96
+
97
+ @pytest.mark.parametrize(
98
+ ("number_of_spatial_steps", "scanning_mode"),
99
+ [
100
+ pytest.param(1, "Center only", id="center_only"),
101
+ pytest.param(2, "Field Scanning", id="field_scanning"),
102
+ ],
103
+ )
104
+ def test_format_dataset_info_includes_scanning_mode(
105
+ assemble_task_with_tagged_movie_frames,
106
+ number_of_spatial_steps,
107
+ scanning_mode,
108
+ ):
109
+ """
110
+ Given: A set of movie frames with a specified number of spatial steps
111
+ When: Formatting the dataset info
112
+ Then: The scanning mode is correctly included in the dataset info string
113
+ """
114
+ task = assemble_task_with_tagged_movie_frames
115
+ task.metadata_store_input_dataset_recipe_run = make_default_input_dataset_recipe_run_response()
116
+ task.movie_frames = task.read_movie_frames()
117
+ first_frame = task.movie_frames[0]
118
+ first_frame.number_of_spatial_steps = number_of_spatial_steps
119
+
120
+ dataset_info = task.format_dataset_info(first_frame)
121
+
122
+ assert f"Scanning Mode: {scanning_mode}\n" in dataset_info
123
+
124
+
125
+ @pytest.mark.parametrize(
126
+ "non_finite_value",
127
+ [
128
+ pytest.param(np.nan, id="nan"),
129
+ pytest.param(np.inf, id="positive_infinity"),
130
+ pytest.param(-np.inf, id="negative_infinity"),
131
+ ],
132
+ )
133
+ def test_apply_colormap_ignores_non_finite_values_when_scaling(
134
+ assemble_task_with_tagged_movie_frames, non_finite_value
135
+ ):
136
+ """
137
+ Given: An array with non-finite values (NaN, positive infinity, negative infinity)
138
+ When: Applying the colormap to the array
139
+ Then: The non-finite values are ignored when scaling, and they are mapped to white
140
+ """
141
+ array = np.array([[0.0, 0.5, 1.0, non_finite_value]])
142
+
143
+ result = assemble_task_with_tagged_movie_frames.apply_colormap(array)
144
+
145
+ expected = np.array([[[0, 0, 0], [128, 128, 128], [255, 255, 255], [255, 255, 255]]])
146
+ np.testing.assert_array_equal(result, expected)
147
+
148
+
149
+ def test_apply_colormap_handles_array_without_finite_values(
150
+ assemble_task_with_tagged_movie_frames,
151
+ ):
152
+ """
153
+ Given: An array without any finite values (only NaN, positive infinity, negative infinity)
154
+ When: Applying the colormap to the array
155
+ Then: The result is an array of white pixels for all positions
156
+ """
157
+ array = np.array([[np.nan, np.inf, -np.inf]])
158
+
159
+ result = assemble_task_with_tagged_movie_frames.apply_colormap(array)
160
+
161
+ expected = np.full((1, 3, 3), 255)
162
+ np.testing.assert_array_equal(result, expected)
@@ -138,7 +138,11 @@ def test_gain_calibration(gain_calibration_task, mocker, fake_gql_client):
138
138
 
139
139
 
140
140
  def test_identify_bad_pixels(gain_calibration_task):
141
- """Pixels matching any bad-pixel condition are marked in the output map."""
141
+ """
142
+ Given: a set of gain frames with known bad pixels
143
+ When: the identify_bad_pixels method is called
144
+ Then: the bad pixels are correctly identified in the output map
145
+ """
142
146
  averaged_gain_array = np.full((100, 100), 100.0)
143
147
  dark_subtracted_gain_array = averaged_gain_array.copy()
144
148
 
@@ -160,3 +164,48 @@ def test_identify_bad_pixels(gain_calibration_task):
160
164
  expected_bad_pixel_map = np.zeros_like(averaged_gain_array, dtype=bool)
161
165
  expected_bad_pixel_map[0, :5] = True
162
166
  np.testing.assert_array_equal(bad_pixel_map, expected_bad_pixel_map)
167
+
168
+
169
+ @pytest.mark.parametrize(
170
+ "bad_pixel_map_shape",
171
+ [
172
+ pytest.param((200, 200), id="2d"),
173
+ pytest.param((1, 200, 200), id="leading_dimension"),
174
+ ],
175
+ )
176
+ def test_identify_occlusion_pixels(gain_calibration_task, bad_pixel_map_shape):
177
+ """
178
+ Given: a bad pixel map with known occluded regions
179
+ When: the identify_occlusion_pixels method is called
180
+ Then: the occluded regions are correctly identified in the output mask
181
+ """
182
+ bad_pixel_map = np.zeros(bad_pixel_map_shape, dtype=bool)
183
+ bad_pixel_map[..., 0:20, 75:125] = True # large edge region - occlusion
184
+ bad_pixel_map[..., 50:75, 80:120] = True # large interior region
185
+ bad_pixel_map[..., 150:160, 0:99] = True # small edge region
186
+
187
+ occlusion_mask = gain_calibration_task.identify_occlusion_pixels(bad_pixel_map)
188
+
189
+ expected_occlusion_mask = np.zeros_like(bad_pixel_map)
190
+ expected_occlusion_mask[..., 0:20, 75:125] = True
191
+ assert occlusion_mask.shape == bad_pixel_map_shape
192
+ np.testing.assert_array_equal(occlusion_mask, expected_occlusion_mask)
193
+
194
+
195
+ def test_neutralize_occlusion_gain_values():
196
+ """
197
+ Given: a gain array with known occluded regions
198
+ When: the neutralize_occlusion_gain_values method is called
199
+ Then: the occluded regions are set to 1.0 and the other values remain unchanged
200
+ """
201
+ gain_array = np.array([[[0.5, 0.75, 1.25], [1.5, 1.75, 2.0]]])
202
+ original_gain_array = gain_array.copy()
203
+ occlusion_mask = np.array([[[False, True, False], [True, False, False]]])
204
+
205
+ result = GainCalibration.neutralize_occlusion_gain_values(
206
+ gain_array_dict={1: gain_array},
207
+ step_occlusion_mask_dict={1: occlusion_mask},
208
+ )
209
+
210
+ np.testing.assert_array_equal(result[1][occlusion_mask], 1.0)
211
+ np.testing.assert_array_equal(result[1][~occlusion_mask], original_gain_array[~occlusion_mask])