solarc-eclipse 0.6.2__tar.gz → 0.8.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.
- solarc_eclipse-0.8.0/LICENSE +5 -0
- solarc_eclipse-0.8.0/PKG-INFO +75 -0
- solarc_eclipse-0.8.0/README.md +17 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/__init__.py +5 -4
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/analysis.py +66 -27
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/data_processing.py +57 -17
- solarc_eclipse-0.8.0/euvst_response/extern/__init__.py +0 -0
- solarc_eclipse-0.8.0/euvst_response/extern/mpfit.py +2388 -0
- solarc_eclipse-0.8.0/euvst_response/fitting.py +639 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/main.py +240 -83
- solarc_eclipse-0.8.0/euvst_response/monte_carlo.py +350 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/radiometric.py +160 -38
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/synthesis.py +182 -56
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/utils.py +118 -14
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/pyproject.toml +21 -4
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/setup.py +2 -27
- solarc_eclipse-0.8.0/solarc_eclipse.egg-info/PKG-INFO +75 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/solarc_eclipse.egg-info/SOURCES.txt +2 -1
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/solarc_eclipse.egg-info/requires.txt +14 -0
- solarc_eclipse-0.6.2/LICENSE +0 -1
- solarc_eclipse-0.6.2/PKG-INFO +0 -412
- solarc_eclipse-0.6.2/README.md +0 -369
- solarc_eclipse-0.6.2/euvst_response/data/throughput/source.txt +0 -3
- solarc_eclipse-0.6.2/euvst_response/fitting.py +0 -144
- solarc_eclipse-0.6.2/euvst_response/monte_carlo.py +0 -207
- solarc_eclipse-0.6.2/solarc_eclipse.egg-info/PKG-INFO +0 -412
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/MANIFEST.in +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/cli.py +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/config.py +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/data/throughput/grating_reflection_efficiency.dat +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/data/throughput/primary_mirror_coating_reflectance.dat +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/data/throughput/throughput_aluminium_1000_angstrom.dat +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/data/throughput/throughput_aluminium_oxide_1000_angstrom.dat +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/data/throughput/throughput_carbon_1000_angstrom.dat +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/pinhole_diffraction.py +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/psf.py +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/euvst_response/synthesis_cli.py +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/setup.cfg +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/solarc_eclipse.egg-info/dependency_links.txt +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/solarc_eclipse.egg-info/entry_points.txt +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/solarc_eclipse.egg-info/not-zip-safe +0 -0
- {solarc_eclipse-0.6.2 → solarc_eclipse-0.8.0}/solarc_eclipse.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Copyright (c) James McKevitt
|
|
2
|
+
|
|
3
|
+
Publishing any results produced by this software, or any work derived from it, requires permission from the author. Contact James McKevitt (jm2@mssl.ucl.ac.uk) before publication.
|
|
4
|
+
|
|
5
|
+
The SOLAR-C SWG are also required to approve the publication of forward modelling results for SOLAR-C/EUVST. Contact James McKevitt (jm2@mssl.ucl.ac.uk) for more information.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: solarc-eclipse
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: ECLIPSE: Emission Calculation and Line Prediction for SOLAR-C EUVST
|
|
5
|
+
Home-page: https://github.com/jamesmckevitt/eclipse
|
|
6
|
+
Author: James McKevitt
|
|
7
|
+
Author-email: James McKevitt <jm2@mssl.ucl.ac.uk>
|
|
8
|
+
License: Copyright (c) James McKevitt
|
|
9
|
+
|
|
10
|
+
Publishing any results produced by this software, or any work derived from it, requires permission from the author. Contact James McKevitt (jm2@mssl.ucl.ac.uk) before publication.
|
|
11
|
+
|
|
12
|
+
The SOLAR-C SWG are also required to approve the publication of forward modelling results for SOLAR-C/EUVST. Contact James McKevitt (jm2@mssl.ucl.ac.uk) for more information.
|
|
13
|
+
Project-URL: Homepage, https://github.com/jamesmckevitt/eclipse
|
|
14
|
+
Project-URL: Repository, https://github.com/jamesmckevitt/eclipse
|
|
15
|
+
Project-URL: Documentation, https://solarc-eclipse.readthedocs.io
|
|
16
|
+
Classifier: Development Status :: 4 - Beta
|
|
17
|
+
Classifier: Intended Audience :: Science/Research
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy
|
|
26
|
+
Requires-Dist: astropy
|
|
27
|
+
Requires-Dist: ndcube
|
|
28
|
+
Requires-Dist: specutils
|
|
29
|
+
Requires-Dist: scipy
|
|
30
|
+
Requires-Dist: matplotlib
|
|
31
|
+
Requires-Dist: joblib
|
|
32
|
+
Requires-Dist: tqdm
|
|
33
|
+
Requires-Dist: dill
|
|
34
|
+
Requires-Dist: pyyaml
|
|
35
|
+
Requires-Dist: reproject
|
|
36
|
+
Requires-Dist: sunpy[all]
|
|
37
|
+
Requires-Dist: dask
|
|
38
|
+
Requires-Dist: psutil
|
|
39
|
+
Requires-Dist: mendeleev
|
|
40
|
+
Requires-Dist: h5py
|
|
41
|
+
Requires-Dist: fiasco
|
|
42
|
+
Provides-Extra: mpi
|
|
43
|
+
Requires-Dist: mpi4py; extra == "mpi"
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: pytest; extra == "dev"
|
|
46
|
+
Requires-Dist: black; extra == "dev"
|
|
47
|
+
Requires-Dist: flake8; extra == "dev"
|
|
48
|
+
Requires-Dist: mypy; extra == "dev"
|
|
49
|
+
Requires-Dist: ipywidgets; extra == "dev"
|
|
50
|
+
Provides-Extra: docs
|
|
51
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
52
|
+
Requires-Dist: mkdocstrings[python]; extra == "docs"
|
|
53
|
+
Requires-Dist: mkdocs-jupyter; extra == "docs"
|
|
54
|
+
Dynamic: author
|
|
55
|
+
Dynamic: home-page
|
|
56
|
+
Dynamic: license-file
|
|
57
|
+
Dynamic: requires-python
|
|
58
|
+
|
|
59
|
+
# ECLIPSE: Emission Calculation and Line Prediction for SOLAR-C EUVST
|
|
60
|
+
|
|
61
|
+
The ECLIPSE code (Emission Calculation and Line Prediction for SOLAR-C EUVST) is used to forward model the performance of the EUV spectrograph EUVST onboard the SOLAR-C spacecraft.
|
|
62
|
+
|
|
63
|
+
Contact: James McKevitt (jm2@mssl.ucl.ac.uk). See [LICENSE](LICENSE) for usage terms.
|
|
64
|
+
|
|
65
|
+
The instrument response generated by this code will be updated during instrument development, testing, and commissioning.
|
|
66
|
+
|
|
67
|
+
[](https://solarc-eclipse.readthedocs.io/en/latest/)
|
|
68
|
+
[](https://pypi.org/project/solarc-eclipse/)
|
|
69
|
+
[](https://doi.org/10.5281/zenodo.17543844)
|
|
70
|
+
|
|
71
|
+
Full documentation - installation, quick start, the synthesis and instrument response guides, and the analysis tutorial - is at **[solarc-eclipse.readthedocs.io](https://solarc-eclipse.readthedocs.io)**.
|
|
72
|
+
|
|
73
|
+
## Acknowledgements
|
|
74
|
+
|
|
75
|
+
The SOLAR-C/EUVST-SW instrument is an ESA-funded contribution to the JAXA-led SOLAR-C mission. The EUVST-LW (long wavelength) instrument is contributed by NASA. The ECLIPSE code is developed and maintained at Mullard Space Science Laboratory (UCL), and was made using Austrian Super Computing (ASC) infrastructure in collaboration with the University of Vienna.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# ECLIPSE: Emission Calculation and Line Prediction for SOLAR-C EUVST
|
|
2
|
+
|
|
3
|
+
The ECLIPSE code (Emission Calculation and Line Prediction for SOLAR-C EUVST) is used to forward model the performance of the EUV spectrograph EUVST onboard the SOLAR-C spacecraft.
|
|
4
|
+
|
|
5
|
+
Contact: James McKevitt (jm2@mssl.ucl.ac.uk). See [LICENSE](LICENSE) for usage terms.
|
|
6
|
+
|
|
7
|
+
The instrument response generated by this code will be updated during instrument development, testing, and commissioning.
|
|
8
|
+
|
|
9
|
+
[](https://solarc-eclipse.readthedocs.io/en/latest/)
|
|
10
|
+
[](https://pypi.org/project/solarc-eclipse/)
|
|
11
|
+
[](https://doi.org/10.5281/zenodo.17543844)
|
|
12
|
+
|
|
13
|
+
Full documentation - installation, quick start, the synthesis and instrument response guides, and the analysis tutorial - is at **[solarc-eclipse.readthedocs.io](https://solarc-eclipse.readthedocs.io)**.
|
|
14
|
+
|
|
15
|
+
## Acknowledgements
|
|
16
|
+
|
|
17
|
+
The SOLAR-C/EUVST-SW instrument is an ESA-funded contribution to the JAXA-led SOLAR-C mission. The EUVST-LW (long wavelength) instrument is contributed by NASA. The ECLIPSE code is developed and maintained at Mullard Space Science Laboratory (UCL), and was made using Austrian Super Computing (ASC) infrastructure in collaboration with the University of Vienna.
|
|
@@ -4,7 +4,7 @@ ECLIPSE: Emission Calculation and Line Prediction for SOLAR-C EUVST
|
|
|
4
4
|
This package provides tools for modeling the performance of the EUV spectrograph EUVST, on SOLAR-C.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "0.
|
|
7
|
+
__version__ = "0.8.0"
|
|
8
8
|
__author__ = "James McKevitt"
|
|
9
9
|
__email__ = "jm2@mssl.ucl.ac.uk"
|
|
10
10
|
|
|
@@ -13,11 +13,11 @@ from .config import Detector_SWC, Detector_EIS, Telescope_EUVST, Telescope_EIS,
|
|
|
13
13
|
from .utils import wl_to_vel, vel_to_wl, angle_to_distance, distance_to_angle
|
|
14
14
|
from .radiometric import (
|
|
15
15
|
intensity_to_photons, add_telescope_throughput, photons_to_pixel_counts,
|
|
16
|
-
|
|
16
|
+
apply_exposure, sample_photon_arrivals, add_poisson, apply_focusing_optics_psf, to_electrons,
|
|
17
17
|
to_dn, add_visible_stray_light, add_pinhole_visible_light
|
|
18
18
|
)
|
|
19
19
|
from .pinhole_diffraction import apply_euv_pinhole_diffraction, airy_disk_pattern
|
|
20
|
-
from .fitting import fit_cube_gauss, velocity_from_fit, width_from_fit, analyse
|
|
20
|
+
from .fitting import fit_cube_gauss, velocity_from_fit, width_from_fit, analyse, FitConfig, FitComponent
|
|
21
21
|
from .monte_carlo import simulate_once, monte_carlo
|
|
22
22
|
from .main import main
|
|
23
23
|
from .data_processing import load_atmosphere, create_uniform_intensity_cube
|
|
@@ -36,9 +36,10 @@ __all__ = [
|
|
|
36
36
|
"Simulation", "AluminiumFilter",
|
|
37
37
|
"wl_to_vel", "vel_to_wl", "angle_to_distance", "distance_to_angle",
|
|
38
38
|
"intensity_to_photons", "add_telescope_throughput", "photons_to_pixel_counts",
|
|
39
|
-
"
|
|
39
|
+
"apply_exposure", "sample_photon_arrivals", "add_poisson", "apply_focusing_optics_psf", "to_electrons",
|
|
40
40
|
"to_dn", "add_visible_stray_light", "add_pinhole_visible_light",
|
|
41
41
|
"fit_cube_gauss", "velocity_from_fit", "width_from_fit", "analyse",
|
|
42
|
+
"FitConfig", "FitComponent",
|
|
42
43
|
"simulate_once", "monte_carlo",
|
|
43
44
|
"main",
|
|
44
45
|
"load_atmosphere",
|
|
@@ -119,7 +119,8 @@ def get_parameter_combinations(results: Dict[str, Any]) -> List[Dict]:
|
|
|
119
119
|
def analyse_fit_statistics(
|
|
120
120
|
combination_results: Dict[str, Any],
|
|
121
121
|
rest_wavelength: u.Quantity,
|
|
122
|
-
data_type: str = "dn"
|
|
122
|
+
data_type: str = "dn",
|
|
123
|
+
fit_config=None,
|
|
123
124
|
) -> Dict[str, Any]:
|
|
124
125
|
"""
|
|
125
126
|
Analyze fit statistics to compute velocity and line width statistics.
|
|
@@ -132,35 +133,52 @@ def analyse_fit_statistics(
|
|
|
132
133
|
Rest wavelength for velocity conversion.
|
|
133
134
|
data_type : str, optional
|
|
134
135
|
Either "dn" or "photon" to specify which fit statistics to analyze.
|
|
136
|
+
fit_config : FitConfig, optional
|
|
137
|
+
Multi-component fitting configuration. When provided the primary-
|
|
138
|
+
component indices are used; otherwise indices 1 (centre) and
|
|
139
|
+
2 (sigma) are assumed (single-component default).
|
|
135
140
|
|
|
136
141
|
Returns
|
|
137
142
|
-------
|
|
138
143
|
dict
|
|
139
144
|
Dictionary containing velocity and width statistics.
|
|
140
145
|
"""
|
|
146
|
+
# Determine parameter indices for the primary component
|
|
147
|
+
if fit_config is not None and not fit_config.is_single:
|
|
148
|
+
idx_center = fit_config.idx_center
|
|
149
|
+
idx_sigma = fit_config.idx_sigma
|
|
150
|
+
else:
|
|
151
|
+
idx_center = 1
|
|
152
|
+
idx_sigma = 2
|
|
153
|
+
|
|
141
154
|
# Get fit statistics
|
|
142
155
|
fit_stats_key = f"{data_type}_fit_stats"
|
|
143
156
|
if fit_stats_key not in combination_results:
|
|
144
157
|
raise ValueError(f"No {fit_stats_key} found in combination results")
|
|
145
158
|
|
|
146
159
|
fit_stats = combination_results[fit_stats_key]
|
|
160
|
+
if fit_stats is None:
|
|
161
|
+
raise ValueError(
|
|
162
|
+
f"'{data_type}' signal was not fitted for this combination. "
|
|
163
|
+
f"Check the 'fit_signals' setting in your YAML config."
|
|
164
|
+
)
|
|
147
165
|
fit_truth_data = combination_results["ground_truth"]["fit_truth_data"]
|
|
148
166
|
fit_truth_units = combination_results["ground_truth"]["fit_truth_units"]
|
|
149
167
|
|
|
150
168
|
# Extract data and units
|
|
151
|
-
mean_data = fit_stats["mean_data"] # Shape: (nx, ny,
|
|
152
|
-
std_data = fit_stats["std_data"] # Shape: (nx, ny,
|
|
153
|
-
units = fit_stats["units"] # List of
|
|
169
|
+
mean_data = fit_stats["mean_data"] # Shape: (nx, ny, n_params)
|
|
170
|
+
std_data = fit_stats["std_data"] # Shape: (nx, ny, n_params)
|
|
171
|
+
units = fit_stats["units"] # List of n_params astropy units
|
|
154
172
|
|
|
155
|
-
# Get center statistics
|
|
156
|
-
center_mean_data = mean_data[...,
|
|
157
|
-
center_std_data = std_data[...,
|
|
158
|
-
center_unit = units[
|
|
173
|
+
# Get center statistics for the primary component
|
|
174
|
+
center_mean_data = mean_data[..., idx_center]
|
|
175
|
+
center_std_data = std_data[..., idx_center]
|
|
176
|
+
center_unit = units[idx_center]
|
|
159
177
|
|
|
160
|
-
# Get width statistics
|
|
161
|
-
width_mean_data = mean_data[...,
|
|
162
|
-
width_std_data = std_data[...,
|
|
163
|
-
width_unit = units[
|
|
178
|
+
# Get width statistics for the primary component
|
|
179
|
+
width_mean_data = mean_data[..., idx_sigma]
|
|
180
|
+
width_std_data = std_data[..., idx_sigma]
|
|
181
|
+
width_unit = units[idx_sigma]
|
|
164
182
|
|
|
165
183
|
# Create quantities
|
|
166
184
|
center_mean_q = center_mean_data * center_unit
|
|
@@ -177,7 +195,7 @@ def analyse_fit_statistics(
|
|
|
177
195
|
|
|
178
196
|
# Convert to velocities
|
|
179
197
|
v_mean = centers_to_velocity(center_mean_q, rest_wavelength)
|
|
180
|
-
v_true = centers_to_velocity(fit_truth_data[...,
|
|
198
|
+
v_true = centers_to_velocity(fit_truth_data[..., idx_center] * fit_truth_units[idx_center], rest_wavelength)
|
|
181
199
|
v_err = v_true - v_mean
|
|
182
200
|
|
|
183
201
|
# Convert center std to velocity std using differential: dv/dlambda = c/lambda
|
|
@@ -377,11 +395,12 @@ def summary_table(results: Dict[str, Any]) -> None:
|
|
|
377
395
|
|
|
378
396
|
def create_sunpy_maps_from_combo(
|
|
379
397
|
combination_results: Dict[str, Any],
|
|
380
|
-
cube_reb,
|
|
398
|
+
cube_reb=None,
|
|
381
399
|
rest_wavelength: u.Quantity = 195.119 * u.AA,
|
|
382
400
|
data_type: str = "dn",
|
|
383
401
|
precision_requirement: u.Quantity = 2.0 * u.km / u.s,
|
|
384
|
-
exposure_time_results: List[Dict[str, Any]] | None = None
|
|
402
|
+
exposure_time_results: List[Dict[str, Any]] | None = None,
|
|
403
|
+
fit_config=None,
|
|
385
404
|
) -> Dict[str, Any]:
|
|
386
405
|
"""
|
|
387
406
|
Create SunPy maps from combination results using the new fit statistics structure.
|
|
@@ -390,8 +409,9 @@ def create_sunpy_maps_from_combo(
|
|
|
390
409
|
----------
|
|
391
410
|
combination_results : dict
|
|
392
411
|
Results for a specific parameter combination from get_results_for_combination().
|
|
393
|
-
cube_reb : NDCube
|
|
412
|
+
cube_reb : NDCube, optional
|
|
394
413
|
NDCube with helioprojective WCS to use for all maps.
|
|
414
|
+
If not provided, the WCS stored in the combination results is used.
|
|
395
415
|
rest_wavelength : u.Quantity, optional
|
|
396
416
|
Rest wavelength for velocity conversion (default: 195.119 A for Fe XII).
|
|
397
417
|
data_type : str, optional
|
|
@@ -401,6 +421,9 @@ def create_sunpy_maps_from_combo(
|
|
|
401
421
|
exposure_time_results : list of dict, optional
|
|
402
422
|
List of results from get_results_for_combination() for different exposure times.
|
|
403
423
|
If provided, will create an exposure time map showing minimum exposure needed.
|
|
424
|
+
fit_config : FitConfig, optional
|
|
425
|
+
Multi-component fitting configuration. When provided, the primary-
|
|
426
|
+
component indices are used to extract centre and width parameters.
|
|
404
427
|
|
|
405
428
|
Returns
|
|
406
429
|
-------
|
|
@@ -426,19 +449,27 @@ def create_sunpy_maps_from_combo(
|
|
|
426
449
|
# Extract exposure time from parameters
|
|
427
450
|
exposure_time = result["parameters"]["simulation.expos"].to_value(u.s)
|
|
428
451
|
# Create analysis for this exposure
|
|
429
|
-
analysis = analyse_fit_statistics(result, rest_wavelength, data_type)
|
|
452
|
+
analysis = analyse_fit_statistics(result, rest_wavelength, data_type, fit_config=fit_config)
|
|
430
453
|
analysis_per_exp[exposure_time] = analysis
|
|
431
454
|
else:
|
|
432
455
|
analysis_per_exp = None
|
|
433
456
|
|
|
434
|
-
# Extract 2D helioprojective WCS from the cube
|
|
435
|
-
|
|
457
|
+
# Extract 2D helioprojective WCS from the cube or stored signal WCS
|
|
458
|
+
if cube_reb is not None:
|
|
459
|
+
wcs_2d = cube_reb.wcs.celestial.swapaxes(0, 1)
|
|
460
|
+
else:
|
|
461
|
+
wcs_2d = combination_results["first_signal_wcs"].celestial.swapaxes(0, 1)
|
|
436
462
|
|
|
437
463
|
# Get the data arrays - now only first iteration is saved
|
|
438
464
|
first_photon_signal = combination_results["first_photon_signal"] # Shape: (nx, ny, nwave)
|
|
439
465
|
first_dn_signal = combination_results["first_dn_signal"] # Shape: (nx, ny, nwave)
|
|
440
466
|
fit_stats_key = f"{data_type}_fit_stats"
|
|
441
467
|
fit_stats = combination_results[fit_stats_key] # Contains first_fit_data, mean_data, std_data, units
|
|
468
|
+
if fit_stats is None:
|
|
469
|
+
raise ValueError(
|
|
470
|
+
f"'{data_type}' signal was not fitted for this combination. "
|
|
471
|
+
f"Check the 'fit_signals' setting in your YAML config."
|
|
472
|
+
)
|
|
442
473
|
|
|
443
474
|
maps = {}
|
|
444
475
|
|
|
@@ -457,14 +488,22 @@ def create_sunpy_maps_from_combo(
|
|
|
457
488
|
maps['total_dn'] = sunpy.map.Map(total_dn_data.T, wcs_2d)
|
|
458
489
|
maps['total_dn'].meta['bunit'] = str(total_dn_unit)
|
|
459
490
|
|
|
491
|
+
# Determine parameter indices for the primary component
|
|
492
|
+
if fit_config is not None and not fit_config.is_single:
|
|
493
|
+
idx_center = fit_config.idx_center
|
|
494
|
+
idx_sigma = fit_config.idx_sigma
|
|
495
|
+
else:
|
|
496
|
+
idx_center = 1
|
|
497
|
+
idx_sigma = 2
|
|
498
|
+
|
|
460
499
|
# --- Get velocity and width analysis for this combination ---
|
|
461
|
-
analysis = analyse_fit_statistics(combination_results, rest_wavelength, data_type)
|
|
500
|
+
analysis = analyse_fit_statistics(combination_results, rest_wavelength, data_type, fit_config=fit_config)
|
|
462
501
|
|
|
463
502
|
# --- Velocity maps ---
|
|
464
|
-
# Velocity from first fit (
|
|
465
|
-
first_fit_data = fit_stats["first_fit_data"] # Shape: (nx, ny,
|
|
466
|
-
center_first_data = first_fit_data[...,
|
|
467
|
-
center_first_unit = fit_stats["units"][
|
|
503
|
+
# Velocity from first fit (primary component center)
|
|
504
|
+
first_fit_data = fit_stats["first_fit_data"] # Shape: (nx, ny, n_params)
|
|
505
|
+
center_first_data = first_fit_data[..., idx_center]
|
|
506
|
+
center_first_unit = fit_stats["units"][idx_center]
|
|
468
507
|
|
|
469
508
|
def centers_to_velocity(centers_data, centers_unit, lambda0):
|
|
470
509
|
"""Convert wavelength centers to velocities"""
|
|
@@ -492,9 +531,9 @@ def create_sunpy_maps_from_combo(
|
|
|
492
531
|
maps['velocity_err'].meta['bunit'] = str(analysis["v_err"].unit)
|
|
493
532
|
|
|
494
533
|
# --- Line width maps ---
|
|
495
|
-
# Line width from first fit (
|
|
496
|
-
width_first_data = first_fit_data[...,
|
|
497
|
-
width_first_unit = fit_stats["units"][
|
|
534
|
+
# Line width from first fit (primary component sigma)
|
|
535
|
+
width_first_data = first_fit_data[..., idx_sigma]
|
|
536
|
+
width_first_unit = fit_stats["units"][idx_sigma]
|
|
498
537
|
|
|
499
538
|
# Create quantity with proper units
|
|
500
539
|
width_quantity = width_first_data * width_first_unit
|
|
@@ -14,7 +14,18 @@ from specutils import Spectrum
|
|
|
14
14
|
from specutils.manipulation import FluxConservingResampler
|
|
15
15
|
from joblib import Parallel, delayed
|
|
16
16
|
from tqdm import tqdm
|
|
17
|
-
from .utils import tqdm_joblib, distance_to_angle
|
|
17
|
+
from .utils import tqdm_joblib, distance_to_angle, _fwhm_to_sigma
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _resample_batch(flat_chunk, unit, spectral_world, new_spec_grid, n_spec):
|
|
21
|
+
"""Resample a chunk of pixels (module-level for efficient pickling)."""
|
|
22
|
+
resampler = FluxConservingResampler(extrapolation_treatment="zero_fill")
|
|
23
|
+
batch_results = np.empty((flat_chunk.shape[0], n_spec))
|
|
24
|
+
for i in range(flat_chunk.shape[0]):
|
|
25
|
+
spec = Spectrum(flux=flat_chunk[i] * unit, spectral_axis=spectral_world)
|
|
26
|
+
res = resampler(spec, new_spec_grid)
|
|
27
|
+
batch_results[i] = res.flux.value
|
|
28
|
+
return batch_results
|
|
18
29
|
|
|
19
30
|
|
|
20
31
|
def load_atmosphere(pkl_file: str, metadata_line: str = None) -> tuple:
|
|
@@ -171,19 +182,15 @@ def resample_ndcube_spectral_axis(ndcube, spectral_axis, output_resolution, ncpu
|
|
|
171
182
|
# Create batch indices
|
|
172
183
|
batch_indices = [(i, min(i + batch_size, n_pixels)) for i in range(0, n_pixels, batch_size)]
|
|
173
184
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
resampler = FluxConservingResampler(extrapolation_treatment="zero_fill")
|
|
177
|
-
batch_results = np.empty((end_idx - start_idx, n_spec))
|
|
178
|
-
for i, pixel_idx in enumerate(range(start_idx, end_idx)):
|
|
179
|
-
spec = Spectrum(flux=flat_data[pixel_idx] * ndcube.unit, spectral_axis=spectral_world)
|
|
180
|
-
res = resampler(spec, new_spec_grid)
|
|
181
|
-
batch_results[i] = res.flux.value
|
|
182
|
-
return batch_results
|
|
185
|
+
# Pass .copy() slices so loky pickles only the small chunk, not the full array
|
|
186
|
+
unit = ndcube.unit
|
|
183
187
|
|
|
184
188
|
with tqdm_joblib(tqdm(total=len(batch_indices), desc="Resampling spectral axis", unit="batch", leave=False)):
|
|
185
189
|
results = Parallel(n_jobs=ncpu)(
|
|
186
|
-
delayed(_resample_batch)(
|
|
190
|
+
delayed(_resample_batch)(
|
|
191
|
+
flat_data[start:end].copy(), unit, spectral_world, new_spec_grid, n_spec
|
|
192
|
+
)
|
|
193
|
+
for start, end in batch_indices
|
|
187
194
|
)
|
|
188
195
|
resampled = np.vstack(results)
|
|
189
196
|
|
|
@@ -324,9 +331,11 @@ def create_uniform_intensity_cube(
|
|
|
324
331
|
det,
|
|
325
332
|
sim,
|
|
326
333
|
n_sigma_extent: float = 8.0,
|
|
334
|
+
n_slit_pixels: int = 1,
|
|
335
|
+
tel=None,
|
|
327
336
|
) -> NDCube:
|
|
328
337
|
"""
|
|
329
|
-
Create a
|
|
338
|
+
Create a 1 x ``n_slit_pixels`` pixel NDCube containing a Gaussian emission line.
|
|
330
339
|
|
|
331
340
|
The cube is built directly at the detector's spectral resolution and
|
|
332
341
|
assigned a helioprojective WCS consistent with the output of
|
|
@@ -347,14 +356,30 @@ def create_uniform_intensity_cube(
|
|
|
347
356
|
Simulation configuration (provides ``slit_width``).
|
|
348
357
|
n_sigma_extent : float, optional
|
|
349
358
|
Number of sigma either side of line centre to include in the
|
|
350
|
-
wavelength grid (default: 8).
|
|
359
|
+
wavelength grid (default: 8). Measured on the width the line will have
|
|
360
|
+
once the spectral PSF has been applied, if *tel* is given.
|
|
361
|
+
n_slit_pixels : int, optional
|
|
362
|
+
Number of (uniform) slit pixels to generate. Set to the
|
|
363
|
+
``offchip_bin_slit`` value so that subsequent ``rebin_slit_offchip``
|
|
364
|
+
sums ``n_slit_pixels`` independent noise realisations into a single
|
|
365
|
+
binned pixel (default: 1).
|
|
366
|
+
tel : Telescope_EUVST or Telescope_EIS, optional
|
|
367
|
+
Telescope configuration. When given, the grid is widened to hold the
|
|
368
|
+
line after spectral PSF broadening, adding the PSF width to the thermal
|
|
369
|
+
width in quadrature. Without this a narrow line gets a grid only a
|
|
370
|
+
couple of pixels wide, and convolving it with a PSF wider than the line
|
|
371
|
+
pushes flux off the ends of the grid. Default None, which sizes the
|
|
372
|
+
grid on the thermal width alone.
|
|
351
373
|
|
|
352
374
|
Returns
|
|
353
375
|
-------
|
|
354
376
|
NDCube
|
|
355
|
-
Shape ``(1,
|
|
377
|
+
Shape ``(1, n_slit_pixels, n_lambda)`` with unit ``erg / (s cm2 sr cm)`` and a
|
|
356
378
|
helioprojective + wavelength WCS.
|
|
357
379
|
"""
|
|
380
|
+
if n_slit_pixels < 1:
|
|
381
|
+
raise ValueError(f"n_slit_pixels must be >= 1, got {n_slit_pixels}")
|
|
382
|
+
|
|
358
383
|
# --- Spectral grid --------------------------------------------------
|
|
359
384
|
lam0 = rest_wavelength.to(u.cm)
|
|
360
385
|
|
|
@@ -364,7 +389,19 @@ def create_uniform_intensity_cube(
|
|
|
364
389
|
# Detector pixel pitch in cm
|
|
365
390
|
dlam = det.wvl_res.to(u.cm / u.pix) * u.pix # strip per-pixel to cm
|
|
366
391
|
|
|
367
|
-
|
|
392
|
+
# The grid has to hold the line as it will be *measured*, not as it leaves
|
|
393
|
+
# the Sun, so add the spectral PSF to the thermal width in quadrature.
|
|
394
|
+
# Widths add that way for Gaussians, and the PSF is often the broader of
|
|
395
|
+
# the two: at the default 20 km/s the line is 0.77 pixels against a PSF of
|
|
396
|
+
# 1.08. Always widening, rather than only when psf is set, keeps the grid
|
|
397
|
+
# independent of a value that is swept and is not known when the cube is
|
|
398
|
+
# built and cached.
|
|
399
|
+
sigma_total = sigma_lam
|
|
400
|
+
if tel is not None:
|
|
401
|
+
sigma_psf = _fwhm_to_sigma(tel.psf_params[1].to(u.pixel).value) * dlam
|
|
402
|
+
sigma_total = np.sqrt(sigma_lam**2 + sigma_psf**2)
|
|
403
|
+
|
|
404
|
+
half_range = n_sigma_extent * sigma_total
|
|
368
405
|
n_pix_half = int(np.ceil((half_range / dlam).decompose().value))
|
|
369
406
|
n_lam = 2 * n_pix_half + 1 # always odd, centred on rest wavelength
|
|
370
407
|
|
|
@@ -377,14 +414,17 @@ def create_uniform_intensity_cube(
|
|
|
377
414
|
u.erg / (u.s * u.cm**2 * u.sr * u.cm)
|
|
378
415
|
)
|
|
379
416
|
profile = A * np.exp(-0.5 * ((lam_grid - lam0) / sigma_lam) ** 2)
|
|
380
|
-
|
|
417
|
+
# Tile the profile along the slit axis. Every slit pixel holds the same
|
|
418
|
+
# intensity, but each is noised independently downstream, which is what
|
|
419
|
+
# rebin_slit_offchip needs in order to sum them.
|
|
420
|
+
data = np.tile(profile.value, (1, n_slit_pixels, 1)) # shape (1, n_slit_pixels, n_lam)
|
|
381
421
|
|
|
382
422
|
# --- WCS (matches reproject_ndcube output format) --------------------
|
|
383
423
|
# Axes: WAVE (cm), HPLT-TAN (arcsec), HPLN-TAN (arcsec)
|
|
384
424
|
wcs = WCS(naxis=3)
|
|
385
425
|
wcs.wcs.ctype = ["WAVE", "HPLT-TAN", "HPLN-TAN"]
|
|
386
426
|
wcs.wcs.cunit = ["cm", "arcsec", "arcsec"]
|
|
387
|
-
wcs.wcs.crpix = [(n_lam + 1) / 2, 1
|
|
427
|
+
wcs.wcs.crpix = [(n_lam + 1) / 2, (n_slit_pixels + 1) / 2, 1.0]
|
|
388
428
|
wcs.wcs.crval = [lam0.value, 0.0, 0.0]
|
|
389
429
|
wcs.wcs.cdelt = [
|
|
390
430
|
dlam.to_value(u.cm),
|
|
File without changes
|