helio-foxes 1.0.2__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 (45) hide show
  1. helio_foxes-1.0.2/LICENSE +21 -0
  2. helio_foxes-1.0.2/PKG-INFO +656 -0
  3. helio_foxes-1.0.2/README.md +604 -0
  4. helio_foxes-1.0.2/data/__init__.py +0 -0
  5. helio_foxes-1.0.2/data/aia_normalization.py +525 -0
  6. helio_foxes-1.0.2/data/align_aia_sxr.py +278 -0
  7. helio_foxes-1.0.2/data/build_dataset.py +125 -0
  8. helio_foxes-1.0.2/data/clean_aia.py +103 -0
  9. helio_foxes-1.0.2/data/combine_sxr.py +212 -0
  10. helio_foxes-1.0.2/data/convert_aia.py +116 -0
  11. helio_foxes-1.0.2/data/split_train_val_test.py +101 -0
  12. helio_foxes-1.0.2/data/sxr_normalization.py +106 -0
  13. helio_foxes-1.0.2/download/__init__.py +0 -0
  14. helio_foxes-1.0.2/download/download_sdo.py +268 -0
  15. helio_foxes-1.0.2/download/download_sxr.py +89 -0
  16. helio_foxes-1.0.2/download/hugging_face_data_download.py +344 -0
  17. helio_foxes-1.0.2/forecasting/__init__.py +0 -0
  18. helio_foxes-1.0.2/forecasting/dataset.py +385 -0
  19. helio_foxes-1.0.2/forecasting/evaluation.py +1104 -0
  20. helio_foxes-1.0.2/forecasting/inference.py +838 -0
  21. helio_foxes-1.0.2/forecasting/model.py +1470 -0
  22. helio_foxes-1.0.2/helio_foxes/__init__.py +13 -0
  23. helio_foxes-1.0.2/helio_foxes/api.py +1167 -0
  24. helio_foxes-1.0.2/helio_foxes.egg-info/PKG-INFO +656 -0
  25. helio_foxes-1.0.2/helio_foxes.egg-info/SOURCES.txt +43 -0
  26. helio_foxes-1.0.2/helio_foxes.egg-info/dependency_links.txt +1 -0
  27. helio_foxes-1.0.2/helio_foxes.egg-info/requires.txt +23 -0
  28. helio_foxes-1.0.2/helio_foxes.egg-info/top_level.txt +5 -0
  29. helio_foxes-1.0.2/pyproject.toml +37 -0
  30. helio_foxes-1.0.2/setup.cfg +4 -0
  31. helio_foxes-1.0.2/tests/test_bias_penalty.py +120 -0
  32. helio_foxes-1.0.2/tests/test_coupled_checkpoint_compatibility.py +124 -0
  33. helio_foxes-1.0.2/tests/test_dataset_integrity.py +130 -0
  34. helio_foxes-1.0.2/tests/test_evaluation_patch_maps.py +61 -0
  35. helio_foxes-1.0.2/tests/test_harp_api.py +161 -0
  36. helio_foxes-1.0.2/tests/test_harp_uncertainty.py +230 -0
  37. helio_foxes-1.0.2/tests/test_inference.py +136 -0
  38. helio_foxes-1.0.2/tests/test_public_api.py +548 -0
  39. helio_foxes-1.0.2/tests/test_sxr_api.py +234 -0
  40. helio_foxes-1.0.2/tests/test_training_class_weights.py +105 -0
  41. helio_foxes-1.0.2/tests/test_training_strategies.py +48 -0
  42. helio_foxes-1.0.2/training/__init__.py +0 -0
  43. helio_foxes-1.0.2/training/callbacks.py +861 -0
  44. helio_foxes-1.0.2/training/strategies.py +20 -0
  45. helio_foxes-1.0.2/training/train.py +401 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Frontier Development Lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,656 @@
1
+ Metadata-Version: 2.4
2
+ Name: helio-foxes
3
+ Version: 1.0.2
4
+ Summary: FOXES: Framework for Operational X-ray Emission Synthesis
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 Frontier Development Lab
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Requires-Python: >=3.10
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: numpy
31
+ Requires-Dist: torch
32
+ Requires-Dist: pytorch-lightning
33
+ Requires-Dist: pandas
34
+ Requires-Dist: tqdm
35
+ Requires-Dist: PyYAML
36
+ Requires-Dist: matplotlib
37
+ Requires-Dist: drms
38
+ Requires-Dist: astropy<8,>=7.2
39
+ Requires-Dist: sunpy<8,>=6
40
+ Requires-Dist: h5netcdf
41
+ Requires-Dist: h5py
42
+ Requires-Dist: itipy
43
+ Requires-Dist: aiapy<0.11,>=0.6.4
44
+ Provides-Extra: evaluation
45
+ Requires-Dist: scikit-learn; extra == "evaluation"
46
+ Requires-Dist: seaborn; extra == "evaluation"
47
+ Requires-Dist: plotly; extra == "evaluation"
48
+ Provides-Extra: training
49
+ Requires-Dist: wandb; extra == "training"
50
+ Requires-Dist: torchvision; extra == "training"
51
+ Dynamic: license-file
52
+
53
+ ---
54
+ license: mit
55
+ task_categories:
56
+ - image-to-image
57
+ - time-series-forecasting
58
+ tags:
59
+ - solar
60
+ - heliophysics
61
+ - astronomy
62
+ - AIA
63
+ - SDO
64
+ - EUV
65
+ - SXR
66
+ - GOES
67
+ - flare
68
+ - space-weather
69
+ pretty_name: "FOXES: Framework for Operational X-ray Emission Synthesis"
70
+ size_categories:
71
+ - 100K<n<1M
72
+ language:
73
+ - en
74
+ datasets:
75
+ - griffingoodwin04/FOXES-Data
76
+ ---
77
+
78
+ # FOXES: A Framework For Operational X-ray Emission Synthesis
79
+
80
+ <p align="center">
81
+ <img src="foxes_logo.png" alt="FOXES Logo" width="300"/>
82
+ </p>
83
+
84
+ This repository contains the code and resources for **FOXES**, a project developed as part of the _**Frontier Development Lab**_'s Heliolab 2025!
85
+
86
+ Model / Data:
87
+
88
+ https://huggingface.co/spaces/griffingoodwin04/FOXES-model
89
+
90
+ https://huggingface.co/datasets/griffingoodwin04/FOXES-Data
91
+
92
+ ### Original FOXES abstract
93
+ The solar soft X-ray (SXR) irradiance is a long-standing proxy of solar activity, used for the classification of flare strength. As a result, the flare class, along with the SXR light curve, are routinely used as the primary input to many forecasting methods, from coronal mass ejection speed to energetic particle output. However, the SXR irradiance lacks spatial information leading to dubious classification during periods of high activity, and is applicable only for observations from Earth orbit, hindering forecasting for other places in the heliosphere. This work introduces the Framework for Operational X-ray Emission Synthesis (FOXES), a Vision Transformer-based approach for translating Extreme Ultraviolet (EUV) spatially-resolved observations into SXR irradiance predictions. The model produces two outputs: (1) a global SXR flux prediction and (2) per-patch flux contributions, which offer a spatially resolved interpretation of where the model attributes SXR emission. This paves the way for EUV-based flare detection to be extended beyond Earth's line of sight, allowing for a more comprehensive and reliable flare catalog to support robust, scalable, and real-time forecasting, extending our monitoring into a true multiviewpoint system.
94
+
95
+ **Team**: Griffin Goodwin, Alison March, Jayant Biradar, Christopher Schirninger, Robert Jarolim, Angelos Vourlidas, Viacheslav Sadykov, Lorien Pratt
96
+
97
+ ---
98
+
99
+ ## The Original FOXES Model
100
+
101
+ The original model predicts a raw SXR flux contribution for each image patch.
102
+ Its three trained checkpoints use **global**, **local**, or **inverted**
103
+ attention. The model converts patch logits to flux and clamps very small
104
+ contributions to a positive floor (`1e-15` W/m²), which can make the maps
105
+ relatively sparse. In the evaluations run so far, global RMSE and MSE were
106
+ good, but the spatial maps for weak flares and quiet-Sun images were less
107
+ convincing. This motivated the updated model below.
108
+
109
+ ## The Updated (Post Publication) FOXES Model: Local + Global Context
110
+
111
+ The maintained model in this repository extends the released FOXES patch
112
+ predictor. Local attention builds a feature for each AIA image patch. Patch
113
+ flux starts from a positive, image-wide baseline divided across the patches;
114
+ each patch's output scales its share. A global query then summarizes the full
115
+ AIA image and uses that context, together with each patch's local features,
116
+ to adjust its flux contribution. The corrected patch fluxes sum to the
117
+ full-disk SXR prediction.
118
+
119
+ This local + global design produces **less sparse, visually better spatial
120
+ flux maps** in the examples examined so far. It also predicts a global
121
+ Gaussian uncertainty and a map of each patch's contribution to that variance.
122
+ The uncertainty map is an attribution of global uncertainty; there are no patch-level SXR labels
123
+ to establish calibrated local error bars.
124
+
125
+ The tradeoff is **somewhat worse global SXR prediction performance** than the
126
+ original released FOXES model in the evaluations run so far. The visual map
127
+ improvement is qualitative.
128
+ The newly added local + global checkpoints listed below have **not yet been
129
+ validated**; these earlier observations should not be read as evaluation
130
+ results for either new checkpoint file.
131
+
132
+ ---
133
+
134
+ ## Repository Structure
135
+
136
+ This repository covers the full loop: getting data, training, running
137
+ inference, and evaluating a FOXES model.
138
+
139
+ ```text
140
+ FOXES
141
+ ├── download
142
+ │ ├── hugging_face_data_download.py # Recommended: HF Hub -> .npy (streamed, or from local parquet)
143
+ │ ├── hf_download_config.yaml # Config for hugging_face_data_download.py
144
+ │ ├── download_sdo.py # Advanced: raw AIA download from JSOC (needs data/build_dataset.py after)
145
+ │ ├── sdo_download_config.yaml # Config for download_sdo.py
146
+ │ ├── download_sxr.py # Advanced: raw GOES SXR download via SunPy Fido (needs data/build_dataset.py after)
147
+ │ └── sxr_download_config.yaml # Config for download_sxr.py
148
+ ├── data
149
+ │ ├── build_dataset.py # Runs the full raw -> processed pipeline below in one command
150
+ │ ├── build_dataset_config.yaml # Config for build_dataset.py
151
+ │ ├── clean_aia.py # Drop AIA FITS files with a bad DATE-OBS timestamp
152
+ │ ├── convert_aia.py # Raw AIA FITS -> paired 512x512 .npy stacks (itipy)
153
+ │ ├── combine_sxr.py # Combine raw multi-satellite GOES files into per-satellite CSVs
154
+ │ ├── align_aia_sxr.py # Match AIA timestamps to GOES CSVs -> per-timestamp SXR .npy
155
+ │ ├── split_train_val_test.py # Split processed AIA/SXR into train/val/test (use for training a new model only)
156
+ │ └── sxr_normalization.py # Compute log-space mean/std over SXR .npy files for training
157
+ ├── forecasting
158
+ │ ├── dataset.py # AIAGOESDataset / AIAGOESDataModule: loads paired AIA + SXR .npy files
159
+ │ ├── model.py # Released deterministic and current local + global Gaussian models
160
+ │ ├── inference.py # Run a checkpoint over a folder of data; writes predictions.csv
161
+ │ ├── inference_config.yaml # Config for inference.py
162
+ │ ├── evaluation.py # Compute metrics and generate evaluation plots
163
+ │ ├── evaluation_config.yaml # Config for evaluation.py
164
+ │ └── trained_weights_and_normalization/ # Tracked original weights; local newer weights may also be staged here
165
+ ├── training
166
+ │ ├── train.py # Train released deterministic or current local + global model
167
+ │ ├── train_config.yaml # Points to the maintained coupled Gaussian config
168
+ │ ├── train_coupled_gaussian.yaml
169
+ │ ├── train_deterministic.yaml
170
+ │ └── callbacks.py # W&B prediction, attention, and spatial-map callbacks
171
+ └── requirements.txt # Python dependencies
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Setup
177
+
178
+ ### Use FOXES from a Python UI
179
+
180
+ Install this checkout in the UI environment:
181
+
182
+ ```bash
183
+ pip install -e .
184
+ ```
185
+
186
+ Load a checkpoint once when the UI starts, then reuse it for each image:
187
+
188
+ ```python
189
+ import helio_foxes
190
+
191
+ model = helio_foxes.load_model("/path/to/model.ckpt", device="auto")
192
+ processor = helio_foxes.ObservationProcessor(email="you@example.com")
193
+ observation = processor.fetch_observation("2023-08-01T00:00:00Z")
194
+ print(observation.data.shape) # (7, 512, 512)
195
+ result = helio_foxes.predict(observation, model=model)
196
+
197
+ print(result.flux) # full-disk SXR flux, W/m²
198
+ print(result.flux_map) # patch contributions, W/m²
199
+ print(result.harp_regions) # HARP numbers, locations, and flux sums, W/m²
200
+ print(result.sxr) # measured GOES XRS-A/XRS-B fluxes and sample time
201
+ ax = helio_foxes.plot_flux_map(result) # optional: vmin=1e-10, vmax=1e-6
202
+ ax.figure.savefig("flux_map.png")
203
+ ```
204
+ When plotting a prediction, the title automatically shows the predicted FOXES
205
+ flux and the observed SXR-B mean when available, in W/m². For Gaussian models,
206
+ it also shows the model's full-disk uncertainty beside the predicted flux as
207
+ `± value`, computed as the square root of the summed patch variances. This is
208
+ a model estimate, not a calibrated error bar.
209
+
210
+ `ObservationProcessor` calls `AIAProcessor`, `HARPProcessor`, and `SXRProcessor`
211
+ for the same requested timestamp. All three sources are fetched by default.
212
+ AIA and HARPs share the crop geometry and JSOC email; GOES SXR needs no email.
213
+ Prediction adds a `flux_sum` in W/m² and a canonical lowercase `harpnum` to
214
+ each region in `result.harp_regions`. Each result includes `top_pct`,
215
+ `left_pct`, `width_pct`, and `height_pct` for its location in the image.
216
+ Original JSOC fields, including `LON_MIN`, `LON_MAX`, `LAT_MIN`, and `LAT_MAX`,
217
+ are preserved. The observation's queried rows are not modified.
218
+
219
+ ```python
220
+ for harp in result.harp_regions or []:
221
+ print(harp["harpnum"], harp["flux_sum"], "W/m²")
222
+ ```
223
+
224
+ Plotting reuses these regions and the same flux summation rule:
225
+
226
+ ```python
227
+ ax = helio_foxes.plot_flux_map(result, show_harp_boxes=True)
228
+ ```
229
+
230
+ For Gaussian models, each HARP result also contains `flux_sum_uncertainty`
231
+ in W/m²: square the patch uncertainties, sum within the same box as the flux,
232
+ then take the square root. Empty boxes have zero uncertainty; deterministic
233
+ models return `None`. These values follow the model's additive variance
234
+ attribution; they have not been calibrated as regional error bars.
235
+
236
+ Uncertainty appears on a third label line as `± value` when available.
237
+ Hide it with `show_harp_uncertainty=False`:
238
+
239
+ ```python
240
+ ax = helio_foxes.plot_flux_map(result, show_harp_boxes=True, show_harp_uncertainty=False)
241
+ for harp in result.harp_regions or []:
242
+ print(harp["harpnum"], harp["flux_sum"], harp["flux_sum_uncertainty"])
243
+ ```
244
+
245
+ Each HARP box is labeled with its number and flux sum in W/m². Labels use a
246
+ fixed font size; set `harp_label_fontsize` to change it. A standard
247
+ Matplotlib annotation places each label diagonally outside a box corner with
248
+ a three-point gap in both x and y. The entire label extends outward from
249
+ the corner. The side
250
+ toward the image center is preferred. A simple overlap pass tries the other
251
+ corners before four-point nudges, keeping automatic positions outside their
252
+ HARP box and inside the map. Shifted
253
+ labels get connector lines. `harp_label_max_shift=80` limits movement to 80
254
+ points from a corner; lower it to keep labels closer, or set it
255
+ to zero for labels fixed at their preferred corner. If no free position is found within the limit, the
256
+ label stays at its starting position. Crowded cases may need manual offsets
257
+ or a higher `min_harp_flux`. Labels are not automatically hidden. This runs
258
+ once when plotting; replot after substantial size changes.
259
+
260
+ For crowded maps, set individual labels' starting positions. Offsets are in points
261
+ from the chosen corner: positive x moves right, positive y moves up. Manually
262
+ offset labels get matching connector lines:
263
+
264
+ ```python
265
+ ax = helio_foxes.plot_flux_map(
266
+ result, show_harp_boxes=True,
267
+ harp_label_offsets={1234: (15, 30), 5678: (-20, -30)},
268
+ )
269
+ ```
270
+
271
+ Box outlines and labels share a fixed
272
+ color by flux rank (strongest: sky blue, second: orange, third: green).
273
+
274
+ Expand HARP boxes when fetching observations so prediction sums and plot
275
+ outlines use the same expanded region:
276
+
277
+ ```python
278
+ processor = helio_foxes.ObservationProcessor(email=email, harp_bbox_expansion=0.1)
279
+ observation = processor.fetch_observation(timestamp)
280
+ result = helio_foxes.predict(observation, model=model)
281
+ ```
282
+
283
+ `harp_bbox_expansion` defaults to zero. `0.1` adds 10% of each box's width and
284
+ height on each side (20% more total width and height), clipped to the image.
285
+ Original JSOC heliographic bounds are preserved. For direct HARP queries, use
286
+ `helio_foxes.HARPProcessor(email=email, bbox_expansion=0.1)`. The expansion is carried
287
+ in observation metadata for HARP queries deferred until plotting.
288
+
289
+ Set `min_harp_flux` to hide regions below an absolute flux sum in W/m²:
290
+
291
+ ```python
292
+ ax = helio_foxes.plot_flux_map(result, show_harp_boxes=True, min_harp_flux=1e-8)
293
+ ```
294
+
295
+ The default threshold is `1e-7` W/m²; use `min_harp_flux=0` to show all regions.
296
+ Filtering only affects the plot;
297
+ `result.harp_regions` retains all predicted regional sums.
298
+
299
+ Set `include_aia=False`, `include_harps=False`, or `include_sxr=False` on
300
+ `ObservationProcessor` to disable a source. Override any setting for one
301
+ observation with `processor.fetch_observation(timestamp, include_sxr=False)`
302
+ (and similarly for AIA/HARPs). With AIA disabled, `observation.data` is `None`,
303
+ and prediction requires fetching AIA first. Each component processor handles
304
+ only its own source.
305
+ If the observation did not fetch HARPs, plotting with `show_harp_boxes=True`
306
+ queries them using the prediction metadata and its `jsoc_email` argument.
307
+ Pass `harp_regions=regions` to the plot to use an explicit set of regions.
308
+
309
+ `harp_regions=None` means no query was requested; `[]` means no usable HARP
310
+ regions were returned, including query failures. Plotting reuses an empty
311
+ result without repeating the query. HARP data is supplementary, so an empty
312
+ HARP result does not discard the AIA observation.
313
+
314
+ `observation.metadata` is preserved as `result.metadata`. It records the
315
+ requested UTC timestamp, channel order, original processed image shape, and
316
+ crop padding. The timestamp is the requested time, not the individual AIA
317
+ exposure times. The JSOC email stays on the processor or plotting call.
318
+
319
+ For array-based workflows, `helio_foxes.AIAProcessor(email=email).fetch_date(timestamp)`
320
+ returns a NumPy stack. Supply its time once to `helio_foxes.predict(image, model=model,
321
+ timestamp=timestamp)` to enable automatic HARP queries from the result. This
322
+ assumes the standard AIA channel order and crop padding of 0.1. For a different
323
+ crop, wrap the array in `helio_foxes.Observation` with explicit
324
+ `helio_foxes.ObservationMetadata`. Plotting a plain flux-map array also continues to
325
+ accept an explicit `timestamp`.
326
+
327
+ Each sum includes positive patch contributions whose centers fall inside the
328
+ HARP box. Boxes can overlap, so their sums need not add to the full-disk flux.
329
+
330
+ ### Measured GOES SXR
331
+
332
+ The observation's `sxr` dict is also carried into `result.sxr`; it is a measured
333
+ comparison value and is not used as model input. XRS-B is the 1–8 Å band to
334
+ compare with the predicted `result.flux`; XRS-A is 0.5–4 Å. Fluxes are in W/m².
335
+
336
+ ```python
337
+ processor = helio_foxes.ObservationProcessor(
338
+ email="you@example.com", include_sxr=True, sxr_tolerance=120,
339
+ sxr_satellite=None, # optionally restrict to e.g. 18
340
+ )
341
+ observation = processor.fetch_observation("2023-08-01T00:00:00Z")
342
+ result = helio_foxes.predict(observation, model=model)
343
+ if result.sxr is not None:
344
+ print("Predicted:", result.flux, "Observed:", result.sxr["xrsb_flux"])
345
+ print(result.sxr["satellites"], result.sxr["timestamp"])
346
+ print(result.sxr["measurements"]) # individual contributing satellite values
347
+ print(result.sxr["time_offset_seconds"]) # sample minus requested UTC time
348
+
349
+ # Fetch just SXR, using the same datetime/ISO string format:
350
+ sxr = helio_foxes.SXRProcessor(tolerance=120, satellite=18).fetch_date(timestamp)
351
+ # Or through the observation fetcher:
352
+ sxr_only = processor.fetch_observation(timestamp, include_aia=False, include_harps=False)
353
+ ```
354
+
355
+ `SXRProcessor` downloads the GOES science `avg1m` product and finds the nearest
356
+ usable timestamp within `tolerance` seconds (120 by default; time ties favor
357
+ the earlier sample). At that timestamp, `xrsb_flux` is the arithmetic mean of
358
+ available satellites' finite positive XRS-B values with zero XRS-B quality
359
+ flags. This follows `data/align_aia_sxr.py`: average in physical W/m² before
360
+ the training transform applies log normalization. `result.flux` also uses
361
+ physical W/m², so no normalization is needed for comparison.
362
+
363
+ `satellites` names the contributors and `measurements` retains their individual
364
+ values. Each satellite counts once; measurements at different timestamps are
365
+ never averaged together. Restricting `satellite` yields a single-satellite
366
+ result. XRS-A is averaged over valid short-band measurements from the XRS-B
367
+ contributors, or `None` if unavailable. Bad XRS-A values do not exclude good
368
+ XRS-B measurements from the model comparison. The returned `timestamp` is the
369
+ actual sample time, while `observation.metadata.timestamp` remains the requested
370
+ time. Daily files are filtered to the requested window and deleted after use.
371
+ No interpolation or additional legacy scaling is applied to the reprocessed
372
+ science fluxes. `sxr=None` means fetching was disabled or no usable measurement
373
+ was available, including network or processing failures; these failures log a
374
+ warning and do not discard the AIA observation. The science archive may lag
375
+ recent observations. Check the calibration used for older training datasets
376
+ before interpreting measured-versus-predicted differences. Unlike the bulk
377
+ dataset converter, this fetcher does not fill missing fluxes using log-space
378
+ interpolation; only valid measured samples contribute.
379
+
380
+ GOES netCDF reading requires `h5netcdf` and `h5py`, both declared package
381
+ dependencies. If an existing environment reports either missing, run
382
+ `python -m pip install h5netcdf h5py` using that environment's Python. In a
383
+ notebook, run `%pip install h5netcdf h5py` and restart the kernel.
384
+
385
+ To install:
386
+ ```bash
387
+ python -m pip install -e .
388
+ ```
389
+ `AIAProcessor.fetch_date` writes the seven raw AIA FITS frames to a temporary
390
+ directory, processes them with ITI, and returns the stack in memory.
391
+ `ObservationProcessor.fetch_observation` uses this same processing path. The raw files are
392
+ removed when the call finishes, including when a download is incomplete.
393
+ The requested timestamp is interpreted as UTC. An ISO string with an offset
394
+ is converted to UTC; a timestamp without an offset is assumed to be UTC.
395
+ If you provide `aia_norm_path` to `AIAProcessor`, do not also pass that
396
+ artifact to `load_model`, since that would normalize the stack twice.
397
+
398
+ The input uses channel order `[94, 131, 171, 193, 211, 304, 335]` and must
399
+ already have the same preprocessing as the checkpoint's training data. If that
400
+ training used an AIA normalization artifact, pass `aia_norm_path` to
401
+ `helio_foxes.load_model`. `result.intervals` and `result.patch_uncertainty_map` are
402
+ available for the coupled Gaussian model. Checkpoints are passed by path and
403
+ are not bundled into the Python package. Pass a path to a **full checkpoint
404
+ file** available on the UI machine.
405
+
406
+ ### 1) Clone
407
+ ```bash
408
+ git clone https://github.com/griffin-goodwin/FOXES.git
409
+ cd FOXES
410
+ ```
411
+
412
+ ### 2) Create an environment
413
+
414
+ **Option A — pip:**
415
+ ```bash
416
+ conda create -n foxes python=3.14 -y
417
+ conda activate foxes
418
+ pip install -r requirements.txt
419
+ ```
420
+
421
+ ---
422
+
423
+ ## Running the Model
424
+
425
+ FOXES is run in four steps: **get data**, **inference** (run a checkpoint over
426
+ your data), **evaluation** (score the predictions and generate plots), and
427
+ optionally **training** your own model. All are driven by a YAML config — edit
428
+ the config, then run the script.
429
+
430
+ The checked-in configs use paths relative to the repository root:
431
+ `./foxes_data/` for downloaded or processed data and `./foxes_outputs/` for
432
+ predictions and plots. Run the commands below from the repository root, or
433
+ replace those paths with locations on your machine. For inference, set
434
+ `data.checkpoint_path` to a full checkpoint file available on your machine.
435
+
436
+
437
+ ### 0) Get data
438
+
439
+ **Recommended:** stream the released dataset straight from Hugging Face Hub
440
+ into the paired `.npy` layout inference expects — no separate processing step:
441
+
442
+ ```bash
443
+ python download/hugging_face_data_download.py --config download/hf_download_config.yaml
444
+ ```
445
+
446
+ Edit `download/hf_download_config.yaml` first to set `aia_dir`/`sxr_dir`, which
447
+ splits to pull, and whether to subsample.
448
+
449
+ If you've already downloaded the HF parquet files locally instead of
450
+ streaming, set `local_parquet_dir` in that same config to the root folder
451
+ containing your per-split subdirs (`train/`, `validation/` or `val/`, `test/`)
452
+ — streaming and the HF Hub login are skipped entirely in that case.
453
+
454
+ **Advanced:** to acquire and process raw data yourself instead, edit and run
455
+ the two download configs, then the one dataset-build config, in order:
456
+
457
+ ```bash
458
+ # 1) Raw AIA FITS from JSOC (requires a registered email)
459
+ python download/download_sdo.py --config download/sdo_download_config.yaml
460
+
461
+ # 2) Raw GOES XRS data via SunPy Fido
462
+ python download/download_sxr.py --config download/sxr_download_config.yaml
463
+
464
+ # 3) Clean + convert AIA, combine + align SXR -> paired .npy (see data/build_dataset_config.yaml)
465
+ python data/build_dataset.py --config data/build_dataset_config.yaml
466
+ ```
467
+
468
+ `data/build_dataset.py` runs the full raw-to-processed pipeline in one command
469
+ (clean AIA → convert AIA → combine GOES → align AIA/SXR); each step can be
470
+ skipped via the `steps:` block in its config if you've already run it.
471
+ Inference/evaluation just need the flat output of that — training needs two
472
+ more things, both off by default and only relevant if you're training:
473
+ - `steps.split: true` — splits `aia.processed_dir`/`output.sxr_dir` into
474
+ `train/`/`val/`/`test/` subfolders (date ranges or a month-based default;
475
+ see the `split:` block in the config).
476
+ - `sxr_normalization.compute: true` — computes SXR normalization stats from
477
+ the train split (requires `steps.split` to have run first).
478
+
479
+ To regenerate only the SXR normalization artifact for an existing split (the
480
+ training convention is `log10(SXR + 1e-8)`), run:
481
+
482
+ ```bash
483
+ python data/sxr_normalization.py \
484
+ --sxr-dir /data/SXR_processed/train \
485
+ --output /data/SXR_processed/normalized_sxr.npy
486
+ ```
487
+
488
+ ### 1) Data format
489
+
490
+ Point `inference.py` at a folder of paired `.npy` files, one file per timestamp:
491
+
492
+ ```text
493
+ /your/aia_dir/
494
+ ├── 2023-08-01T00:00:00.npy # (7, 512, 512) float32 — one channel per AIA wavelength
495
+ ├── 2023-08-01T00:01:00.npy
496
+ └── ...
497
+
498
+ /your/sxr_dir/ # only needed if you have ground truth to compare against
499
+ ├── 2023-08-01T00:00:00.npy # scalar xrsb_flux value
500
+ └── ...
501
+ ```
502
+
503
+ Timestamps are matched by filename between `aia_dir` and `sxr_dir` — there's no
504
+ required subfolder name (no `train/`, `val/`, or `test/`). Just point the config
505
+ at whichever folder holds the data you want to run.
506
+
507
+ If you don't have ground-truth SXR data (e.g. scoring new/live data), set
508
+ `prediction_only: "true"` in the config and `data.sxr_dir` is ignored entirely.
509
+
510
+ ### 2) Run inference
511
+
512
+ The three original released checkpoints are included under
513
+ `forecasting/trained_weights_and_normalization/`, differing only in the
514
+ self-attention mask they were trained with. Each checkpoint carries its own
515
+ mask, so switch between those original models with `checkpoint_path`:
516
+
517
+ | Checkpoint | `mask_mode` | Description |
518
+ | --- | --- | --- |
519
+ | `inverted-attention-mask.ckpt` | `inverted` | **Original released FOXES model.** Each patch attends to *distant* patches (the flipped local-attention mask it was actually trained with). |
520
+ | `localized-attention-mask.ckpt` | `local` | True local attention — the opposite of the original model: each patch attends only to its own neighborhood. |
521
+ | `no-attention-mask.ckpt` | `none` | Global ViT — standard full/global attention, no masking at all. |
522
+
523
+ This working copy also has two newer local + global Gaussian checkpoint files
524
+ under `updated_architecture_checkpoints/`. Their
525
+ architecture and normalization details below come from the local checkpoint
526
+ metadata; **neither has yet been validated for prediction quality, spatial
527
+ maps, or uncertainty calibration**.
528
+
529
+ | Local checkpoint | Architecture recorded in checkpoint | Status |
530
+ | --- | --- | --- |
531
+ | `updated_architecture_checkpoints/ComplexModel.ckpt` | 12 layers, 8 attention heads, local mask, global patch adjustment | Local file; unvalidated; remote availability unconfirmed |
532
+ | `updated_architecture_checkpoints/SlightlyLighterModel.ckpt` | 4 layers, 4 attention heads, local mask, global patch adjustment | Local file; unvalidated; remote availability unconfirmed |
533
+
534
+
535
+ Both newer checkpoints contain SXR normalization values matching the locally
536
+ staged `updated_architecture_checkpoints/normalized_sxr_updated_FOXES.npy`.
537
+ The original three checkpoints contain values matching the locally staged
538
+ `normalized_sxr_FOXES_paper.npy` instead.
539
+
540
+ Edit `forecasting/inference_config.yaml`:
541
+
542
+ ```yaml
543
+ data:
544
+ aia_dir: "/path/to/your/aia_data"
545
+ sxr_dir: "/path/to/your/sxr_data" # omit/ignore if prediction_only
546
+ sxr_norm_path: null # optional: set to the matching local normalization file
547
+ checkpoint_path: "forecasting/trained_weights_and_normalization/inverted-attention-mask.ckpt"
548
+
549
+ output_path: "/path/to/predictions.csv"
550
+
551
+ device: auto # auto selects CUDA, Apple MPS, or CPU; set mps to require MPS
552
+
553
+ # Optional: run a reproducible random subset of eligible images.
554
+ sampling:
555
+ count: 1000 # or use fraction: 0.1; leave both null for all images
556
+ seed: 42
557
+ ```
558
+
559
+ Then run:
560
+
561
+ ```bash
562
+ python forecasting/inference.py --config forecasting/inference_config.yaml
563
+ ```
564
+
565
+ This writes `output_path` (a CSV of timestamp/prediction/groundtruth). Per-patch
566
+ **flux contribution maps** and per-image **attention weights** are saved
567
+ automatically alongside it whenever `flux_path` / `weight_path` are set in the
568
+ config — set `model_params.no_flux: true` or `model_params.no_weights: true` to
569
+ skip either.
570
+
571
+ ### 3) Evaluate
572
+
573
+ Edit `forecasting/evaluation_config.yaml` to point at the predictions
574
+ CSV and data directories, then run:
575
+
576
+ ```bash
577
+ python forecasting/evaluation.py --config forecasting/evaluation_config.yaml
578
+ ```
579
+
580
+ This computes metrics (MSE, MAE, R²) and generates plots under
581
+ `evaluation.output_dir`. When the CSV contains Gaussian uncertainty columns,
582
+ evaluation also writes `metrics/uncertainty_metrics.csv` with NLL, 68%/95%
583
+ coverage, sharpness, and standardized-residual diagnostics for the full dataset
584
+ and each Quiet/C/M/X class. The corresponding reliability curve and residual
585
+ histogram are saved as `plots/uncertainty_calibration.png`.
586
+
587
+ Set `plotting.inference_patch_dir` to inference's patch output folder to save
588
+ one figure per matching CSV timestamp under `plots/patch_maps/`. Each figure
589
+ shows available flux and square-root variance contribution maps. Set
590
+ `plotting.aia_dir` and `plotting.aia_wavelength` to place the matching AIA
591
+ channel beside them. All panels use the lower-left image origin;
592
+ `plotting.max_patch_maps` can limit large runs. Flux and uncertainty panels
593
+ share one logarithmic scale from the smallest positive to largest value
594
+ across all matching patch maps. Zero values are masked on the log scale.
595
+ Set `plotting.patch_map_class_source` to `groundtruth` or `predictions` to save
596
+ figures under `plots/patch_maps/Below-B/`, `B/`, `C/`, `M/`, or `X/` using the
597
+ same flux boundaries as evaluation metrics. Leave it `null` for a flat folder.
598
+ Evaluation shows progress while scanning the maps for the shared color scale
599
+ and while saving the figures. Existing PNGs are skipped when evaluation is
600
+ rerun, so an interrupted plot run resumes with the remaining figures.
601
+
602
+ ### 4) Train your own model (optional)
603
+
604
+ Unlike inference, training expects `aia_dir`/`sxr_dir` each to have `train/`,
605
+ `val/`, and `test/` subfolders of paired `.npy` files — exactly what
606
+ `data/build_dataset.py` and the Hugging Face download path produce.
607
+
608
+ The maintained local + global Gaussian model uses one global query and a
609
+ weighted final-prediction Huber loss. Edit
610
+ `training/train_coupled_gaussian.yaml` for its architecture, mean loss,
611
+ uncertainty settings, data paths, and W&B settings. For the released
612
+ deterministic model, use `training/train_deterministic.yaml`.
613
+
614
+ To encourage predictions above and below the one-to-one line to balance, set
615
+ `mean_loss.bias_penalty_weight` in either training config. The default is `0`;
616
+ `0.1` is a starting point for a sweep. The added loss is the weight times the
617
+ mean of the **squared signed bias within each GOES class present in the batch**,
618
+ using normalized log10 flux. Each represented class contributes equally; the
619
+ existing Huber loss retains its configured class weights. Compare
620
+ `val_class/<class>/bias_dex` with validation MSE and RMSE before choosing a
621
+ weight. A class contributes to the penalty only in batches where it appears.
622
+
623
+ Optional `vit_architecture.global_attention_heads`,
624
+ `global_head_hidden_dim`, and `global_attention_dropout` change only the
625
+ coupled model's global head. Omit them to use the architecture defaults saved
626
+ in the maintained checkpoint.
627
+
628
+ ```bash
629
+ python training/train.py --config training/train_coupled_gaussian.yaml
630
+ ```
631
+
632
+ Training logs to Weights & Biases and saves checkpoints to
633
+ `data.checkpoints_dir`.
634
+
635
+ ---
636
+
637
+ ## Citation
638
+ If you use this code or data in your work, please cite:
639
+
640
+ ```bibtex
641
+ @software{FOXES,
642
+ title = {{FOXES: A Framework For Operational X-ray Emission Synthesis}},
643
+ institution = {Frontier Development Lab (FDL)},
644
+ repository-code = {https://github.com/griffin-goodwin/FOXES},
645
+ version = {v1.0},
646
+ year = {2026}
647
+ }
648
+ ```
649
+
650
+ ## Acknowledgement
651
+ This work is a research product of Heliolab (heliolab.ai), an initiative of the Frontier Development Lab (FDL.ai). FDL is a public–private partnership between NASA, Trillium Technologies (trillium.tech), and commercial AI partners including Google Cloud and NVIDIA.
652
+ Heliolab was designed, delivered, and managed by Trillium Technologies Inc., a research and development company focused on intelligent systems and collaborative communities for Heliophysics, planetary stewardship and space exploration.
653
+ We gratefully acknowledge Google Cloud for extensive computational resources and NVIDIA Corporation.
654
+ This material is based upon work supported by NASA under award number No. 80GSFC23CA040. Any opinions, findings, conclusions or recommendations expressed are those of the authors and do not necessarily reflect the views of the National Aeronautics and Space Administration.
655
+
656
+ Large language models were used as brainstorming tools to discuss possible training strategies and methodological considerations. The authors retained full responsibility for all research decisions, interpretations, and conclusions presented in this work.