onboard-methane-detection 0.1.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 (21) hide show
  1. onboard_methane_detection-0.1.0/LICENSE.md +57 -0
  2. onboard_methane_detection-0.1.0/MANIFEST.in +1 -0
  3. onboard_methane_detection-0.1.0/PKG-INFO +234 -0
  4. onboard_methane_detection-0.1.0/README.md +156 -0
  5. onboard_methane_detection-0.1.0/onboard_methane_detection/__init__.py +17 -0
  6. onboard_methane_detection-0.1.0/onboard_methane_detection/inference/__init__.py +0 -0
  7. onboard_methane_detection-0.1.0/onboard_methane_detection/inference/pipeline.py +67 -0
  8. onboard_methane_detection-0.1.0/onboard_methane_detection/inference/utils.py +129 -0
  9. onboard_methane_detection-0.1.0/onboard_methane_detection/mag1c_sas_base.py +204 -0
  10. onboard_methane_detection-0.1.0/onboard_methane_detection/onground/__init__.py +0 -0
  11. onboard_methane_detection-0.1.0/onboard_methane_detection/onground/utils.py +256 -0
  12. onboard_methane_detection-0.1.0/onboard_methane_detection/processing/__init__.py +0 -0
  13. onboard_methane_detection-0.1.0/onboard_methane_detection/processing/pipeline.py +69 -0
  14. onboard_methane_detection-0.1.0/onboard_methane_detection/processing/utils.py +173 -0
  15. onboard_methane_detection-0.1.0/onboard_methane_detection.egg-info/PKG-INFO +234 -0
  16. onboard_methane_detection-0.1.0/onboard_methane_detection.egg-info/SOURCES.txt +19 -0
  17. onboard_methane_detection-0.1.0/onboard_methane_detection.egg-info/dependency_links.txt +1 -0
  18. onboard_methane_detection-0.1.0/onboard_methane_detection.egg-info/requires.txt +2 -0
  19. onboard_methane_detection-0.1.0/onboard_methane_detection.egg-info/top_level.txt +1 -0
  20. onboard_methane_detection-0.1.0/pyproject.toml +37 -0
  21. onboard_methane_detection-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,57 @@
1
+ The majority of code is licensed under:
2
+ MIT License
3
+
4
+ Copyright (c) 2025 Jonáš Herec, Zaitra s.r.o.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+
24
+ However, the onboard_methane_detection/mag1c_sas_base.py file and onboard_methane_detection/onground/utils.py contains Mag1c edited code and use files from Mag1c, which is licensed under the BSD 3-Clause License.
25
+ The original code can be found at https://github.com/markusfoote/mag1c
26
+ It is licensed under this license and copyright:
27
+ BSD 3-Clause License
28
+
29
+ Copyright (c) 2019,
30
+ Scientific Computing and Imaging Institute and
31
+ Utah Remote Sensing Applications Lab
32
+ All rights reserved.
33
+
34
+ Redistribution and use in source and binary forms, with or without
35
+ modification, are permitted provided that the following conditions are met:
36
+
37
+ 1. Redistributions of source code must retain the above copyright notice, this
38
+ list of conditions and the following disclaimer.
39
+
40
+ 2. Redistributions in binary form must reproduce the above copyright notice,
41
+ this list of conditions and the following disclaimer in the documentation
42
+ and/or other materials provided with the distribution.
43
+
44
+ 3. Neither the name of the copyright holder nor the names of its
45
+ contributors may be used to endorse or promote products derived from
46
+ this software without specific prior written permission.
47
+
48
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
49
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
50
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
51
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
52
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
53
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
54
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
55
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
56
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
57
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1 @@
1
+ exclude showcase.ipynb
@@ -0,0 +1,234 @@
1
+ Metadata-Version: 2.4
2
+ Name: onboard-methane-detection
3
+ Version: 0.1.0
4
+ Summary: Onboard methane detection
5
+ Author-email: Jonáš Herec <jonas.herec@zaitra.io>
6
+ License: The majority of code is licensed under:
7
+ MIT License
8
+
9
+ Copyright (c) 2025 Jonáš Herec, Zaitra s.r.o.
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ However, the onboard_methane_detection/mag1c_sas_base.py file and onboard_methane_detection/onground/utils.py contains Mag1c edited code and use files from Mag1c, which is licensed under the BSD 3-Clause License.
30
+ The original code can be found at https://github.com/markusfoote/mag1c
31
+ It is licensed under this license and copyright:
32
+ BSD 3-Clause License
33
+
34
+ Copyright (c) 2019,
35
+ Scientific Computing and Imaging Institute and
36
+ Utah Remote Sensing Applications Lab
37
+ All rights reserved.
38
+
39
+ Redistribution and use in source and binary forms, with or without
40
+ modification, are permitted provided that the following conditions are met:
41
+
42
+ 1. Redistributions of source code must retain the above copyright notice, this
43
+ list of conditions and the following disclaimer.
44
+
45
+ 2. Redistributions in binary form must reproduce the above copyright notice,
46
+ this list of conditions and the following disclaimer in the documentation
47
+ and/or other materials provided with the distribution.
48
+
49
+ 3. Neither the name of the copyright holder nor the names of its
50
+ contributors may be used to endorse or promote products derived from
51
+ this software without specific prior written permission.
52
+
53
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
54
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
55
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
56
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
57
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
58
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
59
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
60
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
61
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
62
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
63
+
64
+ Project-URL: Homepage, https://github.com/zaitra/onboard-methane-detection
65
+ Project-URL: Repository, https://github.com/zaitra/onboard-methane-detection
66
+ Keywords: methane,detection,hyperspectral,remote-sensing,satellite
67
+ Classifier: Programming Language :: Python :: 3
68
+ Classifier: License :: OSI Approved :: MIT License
69
+ Classifier: License :: OSI Approved :: BSD License
70
+ Classifier: Operating System :: OS Independent
71
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
72
+ Requires-Python: >=3.9
73
+ Description-Content-Type: text/markdown
74
+ License-File: LICENSE.md
75
+ Requires-Dist: numpy
76
+ Requires-Dist: onnxruntime
77
+ Dynamic: license-file
78
+
79
+ # Onboard Methane Detection
80
+
81
+ This library provides an end-to-end pipeline for processing hyperspectral imagery to detect methane emissions. It includes tools for band selection, methane spectrum generation, preprocessing via Mag1c SAS, and model inference.
82
+
83
+ ## Installation
84
+
85
+ ```bash
86
+ pip install onboard-methane-detection
87
+ ```
88
+
89
+ ### Dependencies and Performance
90
+
91
+ Onboard Methane Detection is lightweight and depends only on NumPy for processing. ONNX Runtime is required only if you want inference. You can omit ONNX Runtime by using the lightweight build (see `Lightweight Onboard Package Build` below).
92
+
93
+ Important: NumPy can be very slow without an acceleration library, so make sure one is installed on your device. We tested with [OpenBLAS](https://github.com/OpenMathLib/OpenBLAS); the build was about 3.9 MB on ARM Cortex-A53 when compiled without LAPACK. After installing OpenBLAS, reinstall NumPy via `pip` so it can be detected and used.
94
+
95
+ ## End-to-End Example
96
+
97
+ The following example demonstrates a complete workflow, divided into distinct stages: on-ground preparation, and onboard session execution. The same example is available in
98
+ <a href="https://colab.research.google.com/github/zaitra/onboard-methane-detection/blob/main/showcase.ipynb">showcase.ipynb <img src="https://colab.research.google.com/assets/colab-badge.svg" height=16px></a>.
99
+
100
+ ### On-Ground
101
+
102
+ ```python
103
+ # Core imports for on-ground processing
104
+ import numpy as np
105
+ from onboard_methane_detection import (
106
+ select_the_bands_by_transmittance,
107
+ generate_methane_spectrum,
108
+ select_rgb_bands
109
+ )
110
+
111
+ # 1. Define or load sensor specifications
112
+ # wavelengths = [...] # List of all available sensor wavelengths
113
+ # fwhms = [...] # List of corresponding FWHMs
114
+
115
+ # 2. Select RGB bands
116
+ rgb_wavelengths, rgb_indices = select_rgb_bands(wavelengths)
117
+
118
+ # 3. Select bands specifically for Methane detection
119
+ # First, generate the baseline CH4 spectrum across all sensor bands
120
+ ch4_spectrum_full = generate_methane_spectrum(wavelengths, fwhms)
121
+
122
+ # Then, select the most informative bands based on transmittance
123
+ selected_wvs_ch4, selected_ch4_spectrum = select_the_bands_by_transmittance(
124
+ wavelengths, ch4_spectrum_full, N=50, strategy="highest-variance"
125
+ )
126
+ selected_indices_ch4 = [wavelengths.tolist().index(w) for w in selected_wvs_ch4]
127
+
128
+ # 4. Export artifacts for the onboard inference session
129
+ np.save('rgb_indices.npy', np.array(rgb_indices))
130
+ np.save('selected_indices_ch4.npy', np.array(selected_indices_ch4))
131
+ np.save('selected_ch4_spectrum.npy', selected_ch4_spectrum)
132
+ ```
133
+
134
+ ### Onboard
135
+
136
+ ```python
137
+ import numpy as np
138
+
139
+ # Core imports for onboard execution
140
+ from onboard_methane_detection import (
141
+ mag1c_sas,
142
+ initialize_model,
143
+ normalize_image,
144
+ model_inference
145
+ )
146
+
147
+ # 1. Load exported artifacts (indices and spectrum) from On-Ground preparation
148
+ rgb_indices = np.load('rgb_indices.npy')
149
+ selected_indices_ch4 = np.load('selected_indices_ch4.npy')
150
+ selected_ch4_spectrum = np.load('selected_ch4_spectrum.npy')
151
+
152
+ # 2. Session Initialization
153
+ # Initialize the inference model before the processing loop begins
154
+ session, input_name = initialize_model(dynamic_output_size=True)
155
+
156
+ # 3. Processing Images (Onboard Loop)
157
+ # acquired_images: list of images in shape (C, H, W)
158
+
159
+ for image in acquired_images:
160
+ # Extract RGB channels using previously found indices
161
+ rgb_image = image[rgb_indices, :, :]
162
+
163
+ # Extract the CH4 detection bands from the image
164
+ methane_bands_image = image[selected_indices_ch4, :, :]
165
+
166
+ # Apply Mag1c SAS preprocessing.
167
+ # Optional tiling args (`tiling`, `tile_size`) can be used if the image is too large.
168
+ mag1c_output = mag1c_sas(methane_bands_image, selected_ch4_spectrum, tiling=False)
169
+
170
+ # Concatenate Mag1c SAS output with local RGB context.
171
+ # Correct channel order is R, G, B, Mag1c-SAS.
172
+ model_input = np.concatenate([rgb_image, mag1c_output], axis=0)
173
+
174
+ # Normalize input: sensor_or_factors can be "emit", "aviris_ng",
175
+ # or a custom array of 4 division factors for a different sensor.
176
+ model_input = normalize_image(model_input, sensor_or_factors='emit')
177
+
178
+ # Run model inference.
179
+ # Tiling arguments can also be provided here if the image is too large.
180
+ predictions = model_inference(session, input_name, model_input, use_tiling=False, logits_to_probs=True)
181
+
182
+ print("Processed image and generated predictions.")
183
+ ```
184
+
185
+ ## Lightweight Onboard Package Build
186
+
187
+ For deployment on resource-constrained satellite hardware, you may want to build a minimal version of this library containing only the necessary modules. A deployment script `build_package_for_deployment.py` is provided for this purpose. The lightweight build depends only on NumPy, and adds ONNX Runtime only if you include `inference`.
188
+
189
+ ### Usage
190
+
191
+ Run the script to generate a lightweight installable package:
192
+
193
+ ```bash
194
+ # Base-only build (no optional subpackages):
195
+ # Includes only `compute_base_mag1c_SAS` (Mag1c-SAS core math).
196
+ python build_package_for_deployment.py --output satellite_pkg
197
+
198
+ # Add optional subpackages as needed:
199
+ python build_package_for_deployment.py --parts processing inference --output satellite_pkg
200
+ ```
201
+
202
+ ### What Gets Included
203
+
204
+ The build script always includes the base Mag1c-SAS implementation and then optionally adds subpackages. You can omit `--parts` entirely to keep the package as small as possible.
205
+
206
+ - Base (always included):
207
+ - Exports `compute_base_mag1c_SAS(...)` which expects batched, flattened radiance data shaped like `(B, N, C)`.
208
+ - Import path: `from onboard_methane_detection import compute_base_mag1c_SAS`
209
+ - This is the minimal dependency footprint and is suitable if you already handle reshaping/masking/sampling in your own pipeline.
210
+ - `processing` (optional):
211
+ - Adds `mag1c_sas(image, methane_spectrum, ...)`, a more convenient Mag1c-SAS wrapper for CHW hyperspectral cubes.
212
+ - Automatically handles masking invalid pixels, reshaping CHW -> `(B, N, C)`, sampling pixels for covariance estimation, and reshaping the output back.
213
+ - `inference` (optional):
214
+ - Adds LinkNet ONNX inference utilities: `initialize_model(...)`, `normalize_image(...)`, and `model_inference(...)`.
215
+ - `model_inference(...)` can use tiling, but tiling utilities live in `processing`, so include both `inference` and `processing` if you want tiled inference.
216
+ - Use this when you want to run the segmentation model onboard.
217
+ - `onground` (optional):
218
+ - Adds on-ground preparation helpers: `generate_methane_spectrum(...)`, `select_the_bands_by_transmittance(...)`, and `select_rgb_bands(...)`.
219
+ - Use this to generate the methane spectrum template and select bands/indices to export for the onboard session.
220
+
221
+ ### Arguments
222
+
223
+ - `--parts`: A space-separated list of optional subpackages to include in the build. Available options are `inference`, `processing`, and `onground`. If you want the smallest possible package, omit `--parts` entirely (base-only Mag1c-SAS).
224
+ - `--output`: The destination directory where the lightweight package will be generated (default: `satellite_package`).
225
+ - `--source`: The directory of the source package (default: `onboard_methane_detection`).
226
+
227
+ Once the package is generated, you can install it on your target hardware using:
228
+
229
+ ```bash
230
+ pip install ./satellite_pkg
231
+ ```
232
+
233
+ # Acknowledgments
234
+ A huge thank you to the creators of [Mag1c](https://github.com/markusfoote/mag1c). This project uses core files and edited code from their original repository, and it wouldn't have been possible without their foundational work!
@@ -0,0 +1,156 @@
1
+ # Onboard Methane Detection
2
+
3
+ This library provides an end-to-end pipeline for processing hyperspectral imagery to detect methane emissions. It includes tools for band selection, methane spectrum generation, preprocessing via Mag1c SAS, and model inference.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install onboard-methane-detection
9
+ ```
10
+
11
+ ### Dependencies and Performance
12
+
13
+ Onboard Methane Detection is lightweight and depends only on NumPy for processing. ONNX Runtime is required only if you want inference. You can omit ONNX Runtime by using the lightweight build (see `Lightweight Onboard Package Build` below).
14
+
15
+ Important: NumPy can be very slow without an acceleration library, so make sure one is installed on your device. We tested with [OpenBLAS](https://github.com/OpenMathLib/OpenBLAS); the build was about 3.9 MB on ARM Cortex-A53 when compiled without LAPACK. After installing OpenBLAS, reinstall NumPy via `pip` so it can be detected and used.
16
+
17
+ ## End-to-End Example
18
+
19
+ The following example demonstrates a complete workflow, divided into distinct stages: on-ground preparation, and onboard session execution. The same example is available in
20
+ <a href="https://colab.research.google.com/github/zaitra/onboard-methane-detection/blob/main/showcase.ipynb">showcase.ipynb <img src="https://colab.research.google.com/assets/colab-badge.svg" height=16px></a>.
21
+
22
+ ### On-Ground
23
+
24
+ ```python
25
+ # Core imports for on-ground processing
26
+ import numpy as np
27
+ from onboard_methane_detection import (
28
+ select_the_bands_by_transmittance,
29
+ generate_methane_spectrum,
30
+ select_rgb_bands
31
+ )
32
+
33
+ # 1. Define or load sensor specifications
34
+ # wavelengths = [...] # List of all available sensor wavelengths
35
+ # fwhms = [...] # List of corresponding FWHMs
36
+
37
+ # 2. Select RGB bands
38
+ rgb_wavelengths, rgb_indices = select_rgb_bands(wavelengths)
39
+
40
+ # 3. Select bands specifically for Methane detection
41
+ # First, generate the baseline CH4 spectrum across all sensor bands
42
+ ch4_spectrum_full = generate_methane_spectrum(wavelengths, fwhms)
43
+
44
+ # Then, select the most informative bands based on transmittance
45
+ selected_wvs_ch4, selected_ch4_spectrum = select_the_bands_by_transmittance(
46
+ wavelengths, ch4_spectrum_full, N=50, strategy="highest-variance"
47
+ )
48
+ selected_indices_ch4 = [wavelengths.tolist().index(w) for w in selected_wvs_ch4]
49
+
50
+ # 4. Export artifacts for the onboard inference session
51
+ np.save('rgb_indices.npy', np.array(rgb_indices))
52
+ np.save('selected_indices_ch4.npy', np.array(selected_indices_ch4))
53
+ np.save('selected_ch4_spectrum.npy', selected_ch4_spectrum)
54
+ ```
55
+
56
+ ### Onboard
57
+
58
+ ```python
59
+ import numpy as np
60
+
61
+ # Core imports for onboard execution
62
+ from onboard_methane_detection import (
63
+ mag1c_sas,
64
+ initialize_model,
65
+ normalize_image,
66
+ model_inference
67
+ )
68
+
69
+ # 1. Load exported artifacts (indices and spectrum) from On-Ground preparation
70
+ rgb_indices = np.load('rgb_indices.npy')
71
+ selected_indices_ch4 = np.load('selected_indices_ch4.npy')
72
+ selected_ch4_spectrum = np.load('selected_ch4_spectrum.npy')
73
+
74
+ # 2. Session Initialization
75
+ # Initialize the inference model before the processing loop begins
76
+ session, input_name = initialize_model(dynamic_output_size=True)
77
+
78
+ # 3. Processing Images (Onboard Loop)
79
+ # acquired_images: list of images in shape (C, H, W)
80
+
81
+ for image in acquired_images:
82
+ # Extract RGB channels using previously found indices
83
+ rgb_image = image[rgb_indices, :, :]
84
+
85
+ # Extract the CH4 detection bands from the image
86
+ methane_bands_image = image[selected_indices_ch4, :, :]
87
+
88
+ # Apply Mag1c SAS preprocessing.
89
+ # Optional tiling args (`tiling`, `tile_size`) can be used if the image is too large.
90
+ mag1c_output = mag1c_sas(methane_bands_image, selected_ch4_spectrum, tiling=False)
91
+
92
+ # Concatenate Mag1c SAS output with local RGB context.
93
+ # Correct channel order is R, G, B, Mag1c-SAS.
94
+ model_input = np.concatenate([rgb_image, mag1c_output], axis=0)
95
+
96
+ # Normalize input: sensor_or_factors can be "emit", "aviris_ng",
97
+ # or a custom array of 4 division factors for a different sensor.
98
+ model_input = normalize_image(model_input, sensor_or_factors='emit')
99
+
100
+ # Run model inference.
101
+ # Tiling arguments can also be provided here if the image is too large.
102
+ predictions = model_inference(session, input_name, model_input, use_tiling=False, logits_to_probs=True)
103
+
104
+ print("Processed image and generated predictions.")
105
+ ```
106
+
107
+ ## Lightweight Onboard Package Build
108
+
109
+ For deployment on resource-constrained satellite hardware, you may want to build a minimal version of this library containing only the necessary modules. A deployment script `build_package_for_deployment.py` is provided for this purpose. The lightweight build depends only on NumPy, and adds ONNX Runtime only if you include `inference`.
110
+
111
+ ### Usage
112
+
113
+ Run the script to generate a lightweight installable package:
114
+
115
+ ```bash
116
+ # Base-only build (no optional subpackages):
117
+ # Includes only `compute_base_mag1c_SAS` (Mag1c-SAS core math).
118
+ python build_package_for_deployment.py --output satellite_pkg
119
+
120
+ # Add optional subpackages as needed:
121
+ python build_package_for_deployment.py --parts processing inference --output satellite_pkg
122
+ ```
123
+
124
+ ### What Gets Included
125
+
126
+ The build script always includes the base Mag1c-SAS implementation and then optionally adds subpackages. You can omit `--parts` entirely to keep the package as small as possible.
127
+
128
+ - Base (always included):
129
+ - Exports `compute_base_mag1c_SAS(...)` which expects batched, flattened radiance data shaped like `(B, N, C)`.
130
+ - Import path: `from onboard_methane_detection import compute_base_mag1c_SAS`
131
+ - This is the minimal dependency footprint and is suitable if you already handle reshaping/masking/sampling in your own pipeline.
132
+ - `processing` (optional):
133
+ - Adds `mag1c_sas(image, methane_spectrum, ...)`, a more convenient Mag1c-SAS wrapper for CHW hyperspectral cubes.
134
+ - Automatically handles masking invalid pixels, reshaping CHW -> `(B, N, C)`, sampling pixels for covariance estimation, and reshaping the output back.
135
+ - `inference` (optional):
136
+ - Adds LinkNet ONNX inference utilities: `initialize_model(...)`, `normalize_image(...)`, and `model_inference(...)`.
137
+ - `model_inference(...)` can use tiling, but tiling utilities live in `processing`, so include both `inference` and `processing` if you want tiled inference.
138
+ - Use this when you want to run the segmentation model onboard.
139
+ - `onground` (optional):
140
+ - Adds on-ground preparation helpers: `generate_methane_spectrum(...)`, `select_the_bands_by_transmittance(...)`, and `select_rgb_bands(...)`.
141
+ - Use this to generate the methane spectrum template and select bands/indices to export for the onboard session.
142
+
143
+ ### Arguments
144
+
145
+ - `--parts`: A space-separated list of optional subpackages to include in the build. Available options are `inference`, `processing`, and `onground`. If you want the smallest possible package, omit `--parts` entirely (base-only Mag1c-SAS).
146
+ - `--output`: The destination directory where the lightweight package will be generated (default: `satellite_package`).
147
+ - `--source`: The directory of the source package (default: `onboard_methane_detection`).
148
+
149
+ Once the package is generated, you can install it on your target hardware using:
150
+
151
+ ```bash
152
+ pip install ./satellite_pkg
153
+ ```
154
+
155
+ # Acknowledgments
156
+ A huge thank you to the creators of [Mag1c](https://github.com/markusfoote/mag1c). This project uses core files and edited code from their original repository, and it wouldn't have been possible without their foundational work!
@@ -0,0 +1,17 @@
1
+ from .mag1c_sas_base import compute_base_mag1c_SAS
2
+ # Optional imports.
3
+ try:
4
+ from .processing.pipeline import mag1c_sas
5
+ except ImportError:
6
+ pass
7
+
8
+ try:
9
+ from .inference.utils import initialize_model, normalize_image
10
+ from .inference.pipeline import model_inference
11
+ except ImportError:
12
+ pass
13
+
14
+ try:
15
+ from .onground.utils import select_the_bands_by_transmittance, generate_methane_spectrum, select_rgb_bands
16
+ except ImportError:
17
+ pass
@@ -0,0 +1,67 @@
1
+ import numpy as np
2
+ from .utils import run_inference_full, pad_to_32, reverse_32_padding
3
+ from ..processing.utils import tile_image, stitch_tiles
4
+
5
+ def model_inference(session, input_name, image, use_tiling=True, tile_size=512, logits_to_probs=False):
6
+ """
7
+ Pipeline for performing model inference on a CHW image with optional tiling.
8
+
9
+ Args:
10
+ session: Initialized ONNX inference session.
11
+ input_name: Input node name for the ONNX session.
12
+ image: Input image of shape (C, H, W) or (B, C, H, W).
13
+ use_tiling: If True, process the image in tiles. If False, process the whole image at once.
14
+ tile_size: Size of tiles (default 512).
15
+ logits_to_probs: If True, apply sigmoid to convert logits to probabilities.
16
+
17
+ Returns:
18
+ Model inference result of shape (1, H, W).
19
+ """
20
+ is_chw = image.ndim == 3
21
+ if is_chw:
22
+ image = np.expand_dims(image, axis=0)
23
+
24
+ if not use_tiling:
25
+ padded_image, pad_info = pad_to_32(image)
26
+ result = _process_tile_inference(session, input_name, padded_image, logits_to_probs)
27
+ result_unpadded = reverse_32_padding(result, pad_info)
28
+ return result_unpadded
29
+
30
+ # Tile the image
31
+ tiles, tiling_info = tile_image(image, tile_size)
32
+
33
+ # Process each tile
34
+ result_tiles = []
35
+ for tile in tiles:
36
+ tile_result = _process_tile_inference(session, input_name, tile, logits_to_probs)
37
+ result_tiles.append(tile_result)
38
+
39
+ # Stitch results back together
40
+ # Update tiling_info for single-channel output
41
+ orig_bchw = tiling_info['original_shape']
42
+ orig_h, orig_w = orig_bchw[-2:]
43
+ tiling_info['original_shape'] = (1, orig_h, orig_w)
44
+
45
+ result = stitch_tiles(result_tiles, tiling_info)
46
+ return result
47
+
48
+
49
+ def _process_tile_inference(session, input_name, image_batched, logits_to_probs):
50
+ """
51
+ Process a single tile/image through model inference.
52
+
53
+ Args:
54
+ session: Initialized ONNX inference session.
55
+ input_name: Input node name for the ONNX session.
56
+ image_batched: Input image of shape (B, C, H, W).
57
+ logits_to_probs: If True, apply sigmoid to convert logits to probabilities.
58
+
59
+ Returns:
60
+ Model inference result of shape (1, H, W).
61
+ """
62
+ image_batched = image_batched.astype(np.float32)
63
+
64
+ # Run inference
65
+ result = run_inference_full(session, input_name, image_batched, logits_to_probs=logits_to_probs)
66
+
67
+ return result
@@ -0,0 +1,129 @@
1
+ import os
2
+ import numpy as np
3
+ import onnxruntime as ort
4
+
5
+
6
+ def initialize_model(model_path: str = None, dynamic_output_size: bool = False) -> tuple[ort.InferenceSession, str]:
7
+ """
8
+ Initialize and return the ONNX inference session and input name.
9
+ """
10
+ if model_path is None:
11
+ model_filename = "linknet_exported_dynamic.onnx" if dynamic_output_size else "linknet_exported.onnx"
12
+ print(f"Using model: {model_filename}")
13
+ model_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), model_filename)
14
+ session = ort.InferenceSession(model_path)
15
+ input_name = session.get_inputs()[0].name
16
+ return session, input_name
17
+
18
+
19
+ def run_sole_inference(session: ort.InferenceSession, input_name: str, image: np.ndarray) -> np.ndarray:
20
+ """
21
+ Run the inference session with the given image.
22
+ """
23
+ outputs = session.run(None, {input_name: image})
24
+ return outputs[0]
25
+
26
+
27
+ def postprocess_result(result: np.ndarray, logits_to_probs: bool = False) -> np.ndarray:
28
+ """
29
+ Postprocess the inference results, converting logits to probabilities if requested.
30
+ """
31
+ if logits_to_probs:
32
+ result = 1 / (1 + np.exp(-result)) # Sigmoid function
33
+ return result[0] # Remove batch dimension if present
34
+
35
+
36
+ def normalize_image(image: np.ndarray, sensor_or_factors: str | list | np.ndarray = 'emit') -> np.ndarray:
37
+ """
38
+ Normalize a CHW image and return BCHW.
39
+
40
+ Args:
41
+ image: Numpy array of shape (C, H, W). If BCHW is provided, it is used as-is.
42
+ sensor_or_factors: 'emit', 'aviris_ng', or a list/array of length 4 containing custom division factors.
43
+ """
44
+ if isinstance(sensor_or_factors, str):
45
+ if sensor_or_factors.lower() == 'emit':
46
+ factors = np.array([98.91703491210939, 112.33824462890625, 119.84940185546876, 2436.562744140625], dtype=image.dtype)
47
+ elif sensor_or_factors.lower() == 'aviris_ng':
48
+ factors = np.array([247.29258728, 280.84561157, 299.62350464, 1218.28137207], dtype=image.dtype)
49
+ else:
50
+ raise ValueError("String must be 'emit' or 'aviris_ng'")
51
+ elif isinstance(sensor_or_factors, (list, np.ndarray)) and len(sensor_or_factors) == 4:
52
+ factors = np.array(sensor_or_factors, dtype=image.dtype)
53
+ else:
54
+ raise ValueError("Must be 'emit', 'aviris_ng', or a custom list/array of length 4")
55
+
56
+ if image.ndim == 3:
57
+ image_bchw = image[None, :, :, :]
58
+ elif image.ndim == 4:
59
+ image_bchw = image
60
+ else:
61
+ raise ValueError("Image must be CHW or BCHW")
62
+
63
+ factors = factors[None, :, None, None]
64
+ normalized = image_bchw / factors
65
+ return np.clip(normalized, 0, 2)
66
+
67
+
68
+ def run_inference_full(session: ort.InferenceSession, input_name: str, image: np.ndarray, logits_to_probs: bool = False) -> np.ndarray:
69
+ """
70
+ Load an ONNX model and perform inference on a numpy image.
71
+
72
+ Args:
73
+ session: Initialized ONNX inference session.
74
+ input_name: Input node name for the ONNX session.
75
+ image: Input numpy image array of shape (B, C, H, W) where:
76
+ - B is batch size
77
+ - C is number of channels (4)
78
+ - H is image height
79
+ - W is image width
80
+ logits_to_probs: If True, apply sigmoid to convert logits to probabilities
81
+ (for binary segmentation).
82
+
83
+ Returns:
84
+ Inference output as numpy array of shape (B, 1, H, W).
85
+ Contains probabilities if logits_to_probs=True, else raw logits.
86
+ """
87
+ result = run_sole_inference(session, input_name, image)
88
+ return postprocess_result(result, logits_to_probs)
89
+
90
+
91
+ def pad_to_32(image: np.ndarray) -> tuple[np.ndarray, dict]:
92
+ """
93
+ Pad a BCHW image so its spatial dimensions (H, W) are divisible by 32.
94
+ Returns the padded image and a dictionary with padding information.
95
+ """
96
+ b, c, orig_h, orig_w = image.shape
97
+ pad_h = (32 - (orig_h % 32)) % 32
98
+ pad_w = (32 - (orig_w % 32)) % 32
99
+
100
+ pad_top = pad_h // 2
101
+ pad_bottom = pad_h - pad_top
102
+ pad_left = pad_w // 2
103
+ pad_right = pad_w - pad_left
104
+
105
+ padded_image = np.pad(image, ((0, 0), (0, 0), (pad_top, pad_bottom), (pad_left, pad_right)), mode='constant')
106
+
107
+ pad_info = {
108
+ 'orig_h': orig_h,
109
+ 'orig_w': orig_w,
110
+ 'pad_top': pad_top,
111
+ 'pad_left': pad_left,
112
+ 'pad_h': pad_h,
113
+ 'pad_w': pad_w
114
+ }
115
+ return padded_image, pad_info
116
+
117
+
118
+ def reverse_32_padding(result_image: np.ndarray, pad_info: dict) -> np.ndarray:
119
+ """
120
+ Crop the padded inference result back to its original shape.
121
+ Assumes result_image is of shape (1, H, W).
122
+ """
123
+ if pad_info['pad_h'] > 0 or pad_info['pad_w'] > 0:
124
+ pad_top = pad_info['pad_top']
125
+ pad_left = pad_info['pad_left']
126
+ orig_h = pad_info['orig_h']
127
+ orig_w = pad_info['orig_w']
128
+ return result_image[:, pad_top:pad_top+orig_h, pad_left:pad_left+orig_w]
129
+ return result_image