lime-stable 2.2.dev5__tar.gz → 2.4.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.
- {lime_stable-2.2.dev5/src/lime_stable.egg-info → lime_stable-2.4.0}/PKG-INFO +4 -5
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/pyproject.toml +9 -6
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/archives/tables.py +1 -1
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/changelog.txt +24 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/fitting/lines.py +46 -29
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/fitting/redshift.py +0 -1
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/io.py +2 -2
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/lime.toml +1 -1
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/observations.py +145 -3
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/plotting/plots.py +131 -30
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/plotting/plots_interactive.py +11 -10
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/resources/lines_database_v2.0.6.txt +4 -1
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/retrieve/line_bands.py +2 -52
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/tools.py +4 -5
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/transitions.py +103 -21
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/workflow.py +127 -88
- {lime_stable-2.2.dev5 → lime_stable-2.4.0/src/lime_stable.egg-info}/PKG-INFO +4 -5
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime_stable.egg-info/SOURCES.txt +1 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime_stable.egg-info/requires.txt +1 -4
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/tests/test_io.py +1 -1
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/tests/test_line.py +39 -6
- lime_stable-2.4.0/tests/test_model.py +268 -0
- lime_stable-2.4.0/tests/test_plots.py +318 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/tests/test_spectrum.py +241 -73
- lime_stable-2.4.0/tests/test_tables.py +294 -0
- lime_stable-2.2.dev5/tests/test_model.py +0 -110
- lime_stable-2.2.dev5/tests/test_plots.py +0 -46
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/LICENSE.rst +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/MANIFEST.in +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/README.md +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/setup.cfg +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/__init__.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/archives/__init__.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/archives/read_fits.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/fitting/__init__.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/inference/detection.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/inference/intensity_threshold.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/plotting/__init__.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/plotting/bokeh_plots.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/plotting/format.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/plotting/theme_lime.toml +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/plotting/utils.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/resources/__init__.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/resources/generator_db.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/resources/generator_logo.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/resources/lines_database_v2.0.0.txt +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/resources/types_params.txt +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/retrieve/__init__.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime/rsrc_manager.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime_stable.egg-info/dependency_links.txt +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/src/lime_stable.egg-info/top_level.txt +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/tests/test_astro.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/tests/test_cube.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/tests/test_read_fits.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/tests/test_redshift.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/tests/test_resources.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/tests/test_sample.py +0 -0
- {lime_stable-2.2.dev5 → lime_stable-2.4.0}/tests/test_tools.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lime-stable
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Line measuring algorithm for astronomical spectra
|
|
5
5
|
Author-email: Vital Fernández <vgf@stsci.edu>
|
|
6
6
|
License: GPL-3.0-or-later
|
|
7
7
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.
|
|
9
|
-
Requires-Python: >=3.
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
License-File: LICENSE.rst
|
|
12
12
|
Requires-Dist: astropy~=7.1
|
|
@@ -15,10 +15,9 @@ Requires-Dist: matplotlib~=3.10
|
|
|
15
15
|
Requires-Dist: numpy~=2.3
|
|
16
16
|
Requires-Dist: pandas~=2.2
|
|
17
17
|
Requires-Dist: scipy~=1.16
|
|
18
|
-
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
19
18
|
Provides-Extra: full
|
|
20
19
|
Requires-Dist: asdf~=4.1; extra == "full"
|
|
21
|
-
Requires-Dist: aspect-stable~=0.
|
|
20
|
+
Requires-Dist: aspect-stable~=0.8; extra == "full"
|
|
22
21
|
Requires-Dist: bokeh~=3.8; extra == "full"
|
|
23
22
|
Requires-Dist: mplcursors~=0.6; extra == "full"
|
|
24
23
|
Requires-Dist: openpyxl~=3.1; extra == "full"
|
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lime-stable"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.4.0"
|
|
8
8
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
9
|
-
requires-python = ">=3.
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
10
|
license = {text = "GPL-3.0-or-later"}
|
|
11
11
|
classifiers = ["License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
12
|
-
"Programming Language :: Python :: 3.
|
|
12
|
+
"Programming Language :: Python :: 3.12"]
|
|
13
13
|
|
|
14
14
|
authors = [{name = "Vital Fernández", email = "vgf@stsci.edu"}]
|
|
15
15
|
description = "Line measuring algorithm for astronomical spectra"
|
|
@@ -19,12 +19,11 @@ dependencies = ["astropy~=7.1",
|
|
|
19
19
|
"matplotlib~=3.10",
|
|
20
20
|
"numpy~=2.3",
|
|
21
21
|
"pandas~=2.2",
|
|
22
|
-
"scipy~=1.16"
|
|
23
|
-
"tomli >= 2.0.0 ; python_version < '3.11'"]
|
|
22
|
+
"scipy~=1.16"]
|
|
24
23
|
|
|
25
24
|
[project.optional-dependencies]
|
|
26
25
|
full = ["asdf~=4.1",
|
|
27
|
-
"aspect-stable~=0.
|
|
26
|
+
"aspect-stable~=0.8",
|
|
28
27
|
"bokeh~=3.8",
|
|
29
28
|
"mplcursors~=0.6",
|
|
30
29
|
"openpyxl~=3.1",
|
|
@@ -48,3 +47,7 @@ mpl-results-path = 'tests/outputs'
|
|
|
48
47
|
mpl-results-always = false
|
|
49
48
|
markers = ["mpl: matplotlib image comparison tests"]
|
|
50
49
|
addopts = "-p no:asdf_schema_tester"
|
|
50
|
+
|
|
51
|
+
[tool.coverage.run]
|
|
52
|
+
omit = ["src/lime/resources/generator_db.py",
|
|
53
|
+
"src/lime/resources/generator_logo.py"]
|
|
@@ -122,4 +122,28 @@ LiMe minor update - 2.0.4 - 12/05/2025
|
|
|
122
122
|
- Further development for the functions involving redshift a line fitting using aspect
|
|
123
123
|
- The spatial masking functions should not require the 'PARAM', 'PARAMIDX', 'PARAMVAL' and 'NUMSPAXE' in the .fits headers to plot the mask overlays
|
|
124
124
|
|
|
125
|
+
LiMe medium update - 2.4.0 - 26/06/2024
|
|
126
|
+
- Added new ultraviolet and infrared lines to the database `lines_database_v2.0.6.txt`
|
|
127
|
+
- Major updates to `observations.py`: expanded spectrum observation handling
|
|
128
|
+
- Major updates to `workflow.py`: significant refactoring of the fitting workflow
|
|
129
|
+
- The Transition and Line classes are been update to have an `origin`, `redshift` and `atomic_data` as attributes. This will be documented in a future major update
|
|
130
|
+
- Changes to the matplotlib and bokeh plot functions to use common theme structure
|
|
131
|
+
- The `Spectrum.retrieve.lines_frame` has been update to include an origin as an input. This will be documented in a future major update
|
|
132
|
+
- The `Spectrum.retrieve.lines_frame` documentation added to the API.
|
|
133
|
+
- Added SDSS complete spectrum example script (`3_complete_spectrum_SDSS.py`)
|
|
134
|
+
- Added line groups prediction example script (`3_line_groups_prediction.py`)
|
|
135
|
+
- Updated `fitting/redshift.py` function to correct a bug issue with non-line measurements
|
|
136
|
+
- Updated `archives/read_fits.py`: FITS reading improvements
|
|
137
|
+
- Updated `pyproject.toml` updated dependencies to include ASPECT features
|
|
138
|
+
- The computation of the line continua now takes into account the covariance of the fitting. The nominal values remain constant but the uncertainty predicted is smaller
|
|
139
|
+
- Expanded the tests for the latex/pdf table making functions from the `test_tables.py` script
|
|
140
|
+
- Expanded the tests to compare the line measurements with synthetic lines for more profile parameters (including the line continuum levels)
|
|
141
|
+
- Improvement of the descriptions in the measurements documentation page and correction of wrong terms
|
|
142
|
+
- Updated plot baselines across multiple tests
|
|
143
|
+
- Added paper citation on the documentation
|
|
144
|
+
- Added more integration with aspect functions (next medium release will focus on documenting these updgrades)
|
|
145
|
+
- Adding releases on the github page to mark medium / major updates
|
|
146
|
+
- Bump minimum python to 3.12 version for uniform requirements with readthedocs compilation
|
|
147
|
+
|
|
148
|
+
|
|
125
149
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
3
4
|
import numpy as np
|
|
4
5
|
from lmfit.models import Model
|
|
5
6
|
from lmfit import fit_report
|
|
@@ -91,6 +92,7 @@ def const_cont_model(cont_array, prefix='cont_', allow_scale=False):
|
|
|
91
92
|
# freeze at 1.0 -> truly constant
|
|
92
93
|
return m, params
|
|
93
94
|
|
|
95
|
+
|
|
94
96
|
def linear_model(x, m_cont, n_cont):
|
|
95
97
|
"""Linear line formulation"""
|
|
96
98
|
return m_cont * x + n_cont
|
|
@@ -252,9 +254,9 @@ def power_area(line, idx, n_steps):
|
|
|
252
254
|
def exp_area(line, idx, n_steps):
|
|
253
255
|
|
|
254
256
|
amp = np.random.normal(line.amp[idx], line.amp_err[idx], n_steps)
|
|
255
|
-
alpha = np.random.normal(line.alpha[idx], line.
|
|
257
|
+
alpha = np.random.normal(line.alpha[idx], line.alpha_err[idx], n_steps)
|
|
256
258
|
|
|
257
|
-
return 2
|
|
259
|
+
return 2 * amp / alpha
|
|
258
260
|
|
|
259
261
|
|
|
260
262
|
def pseudo_power_area(line, idx, n_steps):
|
|
@@ -938,9 +940,18 @@ class LineFitting:
|
|
|
938
940
|
line.measurements.peak_wave = emis_wave[peakIdx]
|
|
939
941
|
line.measurements.peak_flux = emis_flux[peakIdx]
|
|
940
942
|
line.measurements.pixelWidth = np.diff(emis_wave).mean()
|
|
941
|
-
|
|
943
|
+
|
|
944
|
+
# Continuum level and uncertainty at peak
|
|
942
945
|
line.measurements.cont = cont_arr[peakIdx]
|
|
943
|
-
|
|
946
|
+
|
|
947
|
+
if self.cov_linear is None:
|
|
948
|
+
line.measurements.cont_err = np.sqrt(((emis_wave[-1] - emis_wave[peakIdx]) / (emis_wave[-1]-emis_wave[0]) * emis_err[0]) ** 2
|
|
949
|
+
+ ((emis_wave[peakIdx] - emis_wave[0]) / (emis_wave[-1]-emis_wave[0]) * emis_err[-1]) ** 2)
|
|
950
|
+
else:
|
|
951
|
+
line.measurements.cont_err = np.sqrt(self.cov_linear[0, 0] * emis_wave[peakIdx] ** 2 + self.cov_linear[1, 1]
|
|
952
|
+
+ 2 * self.cov_linear[0, 1] * emis_wave[peakIdx])
|
|
953
|
+
|
|
954
|
+
# y_val_err = np.sqrt(((emis_wave[-1] - emis_wave[peakIdx]) / (emis_wave[-1]-emis_wave[0]) * emis_err[0]) ** 2 + ((emis_wave[peakIdx] - emis_wave[0]) / (emis_wave[-1]-emis_wave[0]) * emis_err[-1]) ** 2)
|
|
944
955
|
|
|
945
956
|
# Warning if continuum above or below line peak/through
|
|
946
957
|
if emission_check and (cont_arr[peakIdx] > emis_flux[peakIdx]):
|
|
@@ -1003,61 +1014,67 @@ class LineFitting:
|
|
|
1003
1014
|
match user_cont_source:
|
|
1004
1015
|
|
|
1005
1016
|
case 'adjacent':
|
|
1006
|
-
|
|
1007
|
-
# Check for zero err
|
|
1008
1017
|
err_cont = self._spec.err_flux[idcs_cont].compressed() if self._spec.err_flux is not None else None
|
|
1009
|
-
err_cont = err_cont if np.any(err_cont) else None
|
|
1018
|
+
# err_cont = err_cont if np.any(err_cont) else None
|
|
1010
1019
|
|
|
1011
1020
|
# Fit the model, including uncertainties
|
|
1012
|
-
params,
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
self.line.measurements.
|
|
1019
|
-
|
|
1021
|
+
params, self.cov_linear = curve_fit(linear_model, xdata=self._spec.wave[idcs_cont].compressed(),
|
|
1022
|
+
ydata=self._spec.flux[idcs_cont].compressed(), sigma=err_cont,
|
|
1023
|
+
absolute_sigma=True, check_finite=False)
|
|
1024
|
+
|
|
1025
|
+
self.line.measurements.m_cont = params[0]
|
|
1026
|
+
self.line.measurements.n_cont = params[1]
|
|
1027
|
+
self.line.measurements.m_cont_err_intg, self.line.measurements.n_cont_err_intg = np.sqrt(np.diag(self.cov_linear))
|
|
1028
|
+
|
|
1020
1029
|
cont_arr = self._spec.wave * self.line.measurements.m_cont + self.line.measurements.n_cont
|
|
1021
1030
|
|
|
1022
1031
|
case 'central':
|
|
1023
|
-
|
|
1024
1032
|
x, y = self._spec.wave[idcs_emis].compressed(), self._spec.flux[idcs_emis].compressed()
|
|
1025
1033
|
err = self._spec.err_flux[idcs_emis].compressed() if self._spec.err_flux is not None else [0, 0]
|
|
1026
1034
|
|
|
1027
|
-
|
|
1035
|
+
dx = x[-1] - x[0]
|
|
1036
|
+
self.line.measurements.m_cont = (y[-1] - y[0]) / dx
|
|
1028
1037
|
self.line.measurements.n_cont = y[0] - self.line.measurements.m_cont * x[0]
|
|
1029
1038
|
|
|
1030
|
-
self.line.measurements.m_cont_err_intg = np.sqrt((err[0]
|
|
1031
|
-
self.line.measurements.n_cont_err_intg = np.sqrt(err[0] ** 2 + (
|
|
1039
|
+
self.line.measurements.m_cont_err_intg = np.sqrt((err[0] / dx) ** 2 + (err[-1] / dx) ** 2)
|
|
1040
|
+
self.line.measurements.n_cont_err_intg = np.sqrt((err[0] * x[-1] / dx) ** 2 + (err[-1] * x[0] / dx) ** 2)
|
|
1041
|
+
|
|
1032
1042
|
cont_arr = self._spec.wave * self.line.measurements.m_cont + self.line.measurements.n_cont
|
|
1033
1043
|
|
|
1034
|
-
case 'fit':
|
|
1035
1044
|
|
|
1045
|
+
case 'fit':
|
|
1036
1046
|
x, y = self._spec.wave[idcs_cont].compressed(), self._spec.cont[idcs_cont].compressed()
|
|
1037
1047
|
err = self._spec.err_flux[idcs_cont].compressed() if self._spec.err_flux is not None else [0, 0]
|
|
1038
1048
|
|
|
1039
|
-
|
|
1049
|
+
dx = x[-1] - x[0]
|
|
1050
|
+
self.line.measurements.m_cont = (y[-1] - y[0]) / dx
|
|
1040
1051
|
self.line.measurements.n_cont = y[0] - self.line.measurements.m_cont * x[0]
|
|
1041
1052
|
|
|
1042
|
-
self.line.measurements.m_cont_err_intg = np.sqrt((err[0]
|
|
1043
|
-
self.line.measurements.n_cont_err_intg = np.sqrt(err[0] ** 2 + (
|
|
1053
|
+
self.line.measurements.m_cont_err_intg = np.sqrt((err[0] / dx) ** 2 + (err[-1] / dx) ** 2)
|
|
1054
|
+
self.line.measurements.n_cont_err_intg = np.sqrt((err[0] * x[-1] / dx) ** 2 + (err[-1] * x[0] / dx) ** 2)
|
|
1055
|
+
|
|
1056
|
+
# TODO here with the uncertainty from the spectrum fitted
|
|
1044
1057
|
cont_arr = self._spec.cont
|
|
1045
1058
|
|
|
1046
1059
|
case _:
|
|
1047
1060
|
raise LiMe_Error(f'Continuum source "{user_cont_source}" is not recognized. '
|
|
1048
1061
|
f'Please use "central", "adjacent" and "fit".')
|
|
1049
1062
|
|
|
1050
|
-
#
|
|
1051
|
-
|
|
1052
|
-
|
|
1063
|
+
# Bands constant pixel error
|
|
1064
|
+
if err_from_bands:
|
|
1065
|
+
err_arr = self._spec.wave * 0 + np.std(self._spec.flux[idcs_cont] - (self.line.measurements.m_cont * self._spec.wave[idcs_cont] + self.line.measurements.n_cont), ddof=2)
|
|
1066
|
+
|
|
1067
|
+
# Spectrum error
|
|
1068
|
+
else:
|
|
1069
|
+
err_arr = self._spec.err_flux
|
|
1053
1070
|
|
|
1054
|
-
return cont_arr
|
|
1071
|
+
return cont_arr, err_arr
|
|
1055
1072
|
|
|
1056
1073
|
def pixel_error_calculation(self, idcs_continua, user_error_from_bands):
|
|
1057
|
-
|
|
1074
|
+
# TODO delete this one
|
|
1058
1075
|
# Constant pixel error array from adjacent bands
|
|
1059
1076
|
if user_error_from_bands:
|
|
1060
|
-
return np.ma.array(np.full(self._spec.wave.shape, np.std(self._spec.flux[idcs_continua] - (self.line.measurements.m_cont * self._spec.wave[idcs_continua] + self.line.measurements.n_cont))), mask=
|
|
1077
|
+
return np.ma.array(np.full(self._spec.wave.shape, np.std(self._spec.flux[idcs_continua] - (self.line.measurements.m_cont * self._spec.wave[idcs_continua] + self.line.measurements.n_cont), ddof=2)), mask=self._spec.wave.mask)
|
|
1061
1078
|
|
|
1062
1079
|
# Pixel array
|
|
1063
1080
|
else:
|
|
@@ -134,7 +134,6 @@ def redshift_key_method(spec, bands, z_min, z_max, delta_z, pred_arr, components
|
|
|
134
134
|
|
|
135
135
|
# If there is only one line return nan
|
|
136
136
|
if not (method_flux and not detection_only):
|
|
137
|
-
# match np.count_nonzero(np.diff(np.r_[False, idcs_lines])):
|
|
138
137
|
match comp_counter(idcs_lines):
|
|
139
138
|
case 0:
|
|
140
139
|
return None # No components
|
|
@@ -205,7 +205,7 @@ def load_cfg(file_address, fit_cfg_suffix='_line_fitting'):
|
|
|
205
205
|
return cfg_lime
|
|
206
206
|
|
|
207
207
|
|
|
208
|
-
def save_cfg(output_file, param_dict, section_name=None, clear_section=False):
|
|
208
|
+
def save_cfg(output_file, param_dict, section_name=None, clear_section=False, overwrite_file=False):
|
|
209
209
|
|
|
210
210
|
"""
|
|
211
211
|
This function safes the input dictionary into a configuration file. If no section is provided the input dictionary
|
|
@@ -224,7 +224,7 @@ def save_cfg(output_file, param_dict, section_name=None, clear_section=False):
|
|
|
224
224
|
output_data = param_dict if section_name is None else {section_name: param_dict}
|
|
225
225
|
|
|
226
226
|
# If the file does not exist create a new file
|
|
227
|
-
if not output_path.is_file():
|
|
227
|
+
if not output_path.is_file() or overwrite_file:
|
|
228
228
|
with open(output_file, "w") as f:
|
|
229
229
|
toml.dump(output_data, f)
|
|
230
230
|
|
|
@@ -161,12 +161,17 @@ def check_spectra_arrays(observation):
|
|
|
161
161
|
|
|
162
162
|
def check_redshift_norm(redshift, norm_flux, flux_array, units_flux, norm_factor=1, min_flux_scale=0.001, max_flux_scale=1e50):
|
|
163
163
|
|
|
164
|
-
if
|
|
164
|
+
if redshift is None:
|
|
165
165
|
_logger.warning(f'No redshift provided for the spectrum. Assuming local universe observation (z = 0)')
|
|
166
166
|
redshift = 0
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
_logger.warning(f'
|
|
168
|
+
elif not np.isfinite(redshift):
|
|
169
|
+
_logger.warning(f'The redshift value is no numeric: z={redshift}. Setting the value to z = 0)')
|
|
170
|
+
redshift = 0
|
|
171
|
+
|
|
172
|
+
else:
|
|
173
|
+
if redshift < 0:
|
|
174
|
+
_logger.warning(f'Input spectrum redshift has a negative value: z = {redshift}')
|
|
170
175
|
|
|
171
176
|
if norm_flux is None:
|
|
172
177
|
if units_flux.scale == 1:
|
|
@@ -950,6 +955,82 @@ class Spectrum:
|
|
|
950
955
|
|
|
951
956
|
def save_spectrum(self, fname=None, line_label=None, ref_frame=None, split_components=False, **kwargs):
|
|
952
957
|
|
|
958
|
+
"""Save or return the spectrum data, with the option to include the fitted profiles if available.
|
|
959
|
+
|
|
960
|
+
Exports the spectrum wavelength, flux, flux uncertainty, and pixel mask to a
|
|
961
|
+
space-delimited text file. If a ``line_label`` is provided and that line has
|
|
962
|
+
been measured, the output is cropped to the line band limits (``w1``–``w6``)
|
|
963
|
+
and the fitted profile(s) are appended as additional columns. The file footer
|
|
964
|
+
encodes key metadata (LiMe version, units, redshift, normalisation factor,
|
|
965
|
+
and object label) so the spectrum can be fully reconstructed using lime.Spectrum.from_file("file_address.txt", intrument="text")
|
|
966
|
+
|
|
967
|
+
If ``fname`` is ``None`` the data are returned as a NumPy record array instead
|
|
968
|
+
of being written to disk.
|
|
969
|
+
|
|
970
|
+
Parameters
|
|
971
|
+
----------
|
|
972
|
+
fname : str or path-like, optional
|
|
973
|
+
Destination file path. If ``None`` (default) the spectrum is returned as
|
|
974
|
+
a :class:`numpy.recarray` rather than saved.
|
|
975
|
+
line_label : str, optional
|
|
976
|
+
Transition label (e.g. ``'H1_6563A'``) used to crop the output to the
|
|
977
|
+
band limits of that line and to append its fitted profile column(s). The
|
|
978
|
+
label must be present in ``ref_frame`` (or ``self.frame`` if
|
|
979
|
+
``ref_frame`` is ``None``). If not found a warning is logged and the full
|
|
980
|
+
spectrum is saved without profile columns.
|
|
981
|
+
ref_frame : pandas.DataFrame, optional
|
|
982
|
+
Lines measurement frame to use for band limits and profile parameters.
|
|
983
|
+
Defaults to ``self.frame`` when ``None``.
|
|
984
|
+
split_components : bool, optional
|
|
985
|
+
When ``True`` and a ``line_label`` is supplied, each kinematic component
|
|
986
|
+
of the profile is written as a separate column (labelled by component
|
|
987
|
+
name). When ``False`` (default) all components are summed into a single
|
|
988
|
+
column labelled with ``line_label``.
|
|
989
|
+
**kwargs
|
|
990
|
+
Additional keyword arguments forwarded to :func:`numpy.savetxt`. Common
|
|
991
|
+
overrides include ``delimiter``, ``header``, and ``footer``. Defaults
|
|
992
|
+
are ``fmt`` (per-column format strings) and ``delimiter=' '``; any value
|
|
993
|
+
supplied here takes precedence.
|
|
994
|
+
|
|
995
|
+
Returns
|
|
996
|
+
-------
|
|
997
|
+
numpy.recarray or None
|
|
998
|
+
If ``fname`` is ``None``, returns a record array whose field names match
|
|
999
|
+
the output column headers (``wave``, ``flux``, ``err_flux``,
|
|
1000
|
+
``pixel_mask``, and optionally one or more profile columns). Columns that
|
|
1001
|
+
are entirely ``NaN`` (e.g. ``err_flux`` when no uncertainty array is
|
|
1002
|
+
attached) are dropped from the output. Returns ``None`` when writing to
|
|
1003
|
+
a file.
|
|
1004
|
+
|
|
1005
|
+
Notes
|
|
1006
|
+
-----
|
|
1007
|
+
* Wavelengths are stored in the **observed** frame (rest-frame values
|
|
1008
|
+
multiplied by ``1 + self.redshift``).
|
|
1009
|
+
* Flux values are denormalised before saving (multiplied by
|
|
1010
|
+
``self.norm_flux``).
|
|
1011
|
+
* Profile columns include the linear continuum contribution so that summing
|
|
1012
|
+
them reconstructs the full fitted model over the band.
|
|
1013
|
+
* The footer written by default uses a ``key:value`` format; do not supply a
|
|
1014
|
+
custom ``footer`` kwarg unless you intend to replace this metadata block.
|
|
1015
|
+
|
|
1016
|
+
Examples
|
|
1017
|
+
--------
|
|
1018
|
+
Save the full spectrum to disk:
|
|
1019
|
+
|
|
1020
|
+
>>> spec.save_spectrum('my_spectrum.txt')
|
|
1021
|
+
|
|
1022
|
+
Return the band around ``H1_6563A`` as a record array:
|
|
1023
|
+
|
|
1024
|
+
>>> rec = spec.save_spectrum(line_label='H1_6563A')
|
|
1025
|
+
>>> rec.dtype.names
|
|
1026
|
+
('wave', 'flux', 'err_flux', 'H1_6563A')
|
|
1027
|
+
|
|
1028
|
+
Save with individual kinematic components as separate columns:
|
|
1029
|
+
|
|
1030
|
+
>>> spec.save_spectrum('ha_components.txt', line_label='H1_6563A', split_components=True)
|
|
1031
|
+
"""
|
|
1032
|
+
|
|
1033
|
+
|
|
953
1034
|
# Headers for the default list
|
|
954
1035
|
headers = np.array(["wave", "flux", "err_flux", "pixel_mask"])
|
|
955
1036
|
|
|
@@ -1164,6 +1245,67 @@ class Spectrum:
|
|
|
1164
1245
|
|
|
1165
1246
|
return
|
|
1166
1247
|
|
|
1248
|
+
def __repr__(self):
|
|
1249
|
+
|
|
1250
|
+
# Label
|
|
1251
|
+
label_str = self.label if self.label is not None else 'Spectrum'
|
|
1252
|
+
|
|
1253
|
+
# Redshift and normalization
|
|
1254
|
+
z_str = f'{self.redshift:.6f}' if self.redshift is not None else 'None'
|
|
1255
|
+
norm_str = f'{self.norm_flux:.3e}' if self.norm_flux is not None else 'None'
|
|
1256
|
+
|
|
1257
|
+
# Wavelength range and resolution
|
|
1258
|
+
if self.wave is not None:
|
|
1259
|
+
wmin_str = f'{self.wave.data.min():.2f}'
|
|
1260
|
+
wmax_str = f'{self.wave.data.max():.2f}'
|
|
1261
|
+
delta = np.diff(self.wave.data)
|
|
1262
|
+
if np.all(np.isclose(delta, delta[0])):
|
|
1263
|
+
res_str = f'constant ({delta[0]:.3f} {self.units_wave})'
|
|
1264
|
+
else:
|
|
1265
|
+
res_str = f'variable (min={delta.min():.3f}, max={delta.max():.3f} {self.units_wave})'
|
|
1266
|
+
else:
|
|
1267
|
+
wmin_str = wmax_str = res_str = 'None'
|
|
1268
|
+
|
|
1269
|
+
# Resolving power
|
|
1270
|
+
if self.res_power is None:
|
|
1271
|
+
res_power_str = 'None'
|
|
1272
|
+
elif np.ndim(self.res_power) == 0:
|
|
1273
|
+
res_power_str = f'{self.res_power:.1f} (scalar)'
|
|
1274
|
+
else:
|
|
1275
|
+
res_power_arr = np.asarray(self.res_power)
|
|
1276
|
+
if np.all(np.isclose(res_power_arr, res_power_arr[0])):
|
|
1277
|
+
res_power_str = f'{res_power_arr[0]:.1f} (constant)'
|
|
1278
|
+
else:
|
|
1279
|
+
res_power_str = f'variable (min={res_power_arr.min():.1f}, max={res_power_arr.max():.1f})'
|
|
1280
|
+
|
|
1281
|
+
# Checks
|
|
1282
|
+
has_mask = (self.wave is not None) and np.any(self.wave.mask)
|
|
1283
|
+
has_err = self.err_flux is not None
|
|
1284
|
+
|
|
1285
|
+
return (
|
|
1286
|
+
f'\n{"=" * 60}\n'
|
|
1287
|
+
f' {label_str}\n'
|
|
1288
|
+
f'{"=" * 60}\n'
|
|
1289
|
+
f' Units\n'
|
|
1290
|
+
f' Wavelength : {self.units_wave}\n'
|
|
1291
|
+
f' Flux : {self.units_flux}\n'
|
|
1292
|
+
f'{"-" * 60}\n'
|
|
1293
|
+
f' Observation\n'
|
|
1294
|
+
f' Redshift : {z_str}\n'
|
|
1295
|
+
f' Norm flux : {norm_str}\n'
|
|
1296
|
+
f'{"-" * 60}\n'
|
|
1297
|
+
f' Wavelength grid\n'
|
|
1298
|
+
f' wmin : {wmin_str} {self.units_wave}\n'
|
|
1299
|
+
f' wmax : {wmax_str} {self.units_wave}\n'
|
|
1300
|
+
f' Resolution : {res_str}\n'
|
|
1301
|
+
f' Res. power : {res_power_str}\n'
|
|
1302
|
+
f'{"-" * 60}\n'
|
|
1303
|
+
f' Checks\n'
|
|
1304
|
+
f' Masked pixels : {"yes" if has_mask else "no"}\n'
|
|
1305
|
+
f' Uncertainty array : {"yes" if has_err else "no"}\n'
|
|
1306
|
+
f'{"=" * 60}\n'
|
|
1307
|
+
)
|
|
1308
|
+
|
|
1167
1309
|
|
|
1168
1310
|
class Cube:
|
|
1169
1311
|
|