lime-stable 2.0.5__tar.gz → 2.2.dev1__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.0.5/src/lime_stable.egg-info → lime_stable-2.2.dev1}/PKG-INFO +2 -2
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/pyproject.toml +2 -2
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/archives/read_fits.py +64 -17
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/fitting/redshift.py +1 -6
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/io.py +27 -6
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/lime.toml +1 -1
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/observations.py +165 -30
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/plotting/bokeh_plots.py +153 -44
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/plotting/plots.py +98 -23
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/plotting/plots_interactive.py +5 -5
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/plotting/theme_lime.toml +18 -2
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/resources/generator_db.py +32 -8
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/retrieve/line_bands.py +33 -7
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/tools.py +9 -2
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/transitions.py +250 -67
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/workflow.py +138 -143
- {lime_stable-2.0.5 → lime_stable-2.2.dev1/src/lime_stable.egg-info}/PKG-INFO +2 -2
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime_stable.egg-info/requires.txt +1 -1
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/LICENSE.rst +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/MANIFEST.in +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/README.md +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/setup.cfg +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/__init__.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/archives/__init__.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/archives/tables.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/changelog.txt +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/fitting/__init__.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/fitting/lines.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/inference/detection.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/inference/intensity_threshold.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/plotting/__init__.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/plotting/format.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/plotting/utils.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/resources/__init__.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/resources/generator_logo.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/resources/lines_database_v2.0.0.txt +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/resources/types_params.txt +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/retrieve/__init__.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime/rsrc_manager.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime_stable.egg-info/SOURCES.txt +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime_stable.egg-info/dependency_links.txt +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/src/lime_stable.egg-info/top_level.txt +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/tests/test_astro.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/tests/test_cube.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/tests/test_io.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/tests/test_line.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/tests/test_model.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/tests/test_plots.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/tests/test_read_fits.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/tests/test_redshift.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/tests/test_resources.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/tests/test_sample.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/tests/test_spectrum.py +0 -0
- {lime_stable-2.0.5 → lime_stable-2.2.dev1}/tests/test_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lime-stable
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.dev1
|
|
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
|
|
@@ -18,7 +18,7 @@ Requires-Dist: scipy~=1.16
|
|
|
18
18
|
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
19
19
|
Provides-Extra: full
|
|
20
20
|
Requires-Dist: asdf~=4.1; extra == "full"
|
|
21
|
-
Requires-Dist: aspect-stable~=0.
|
|
21
|
+
Requires-Dist: aspect-stable~=0.7.dev1; extra == "full"
|
|
22
22
|
Requires-Dist: bokeh~=3.8; extra == "full"
|
|
23
23
|
Requires-Dist: mplcursors~=0.6; extra == "full"
|
|
24
24
|
Requires-Dist: openpyxl~=3.1; extra == "full"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lime-stable"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.2.dev1"
|
|
8
8
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
license = {text = "GPL-3.0-or-later"}
|
|
@@ -24,7 +24,7 @@ dependencies = ["astropy~=7.1",
|
|
|
24
24
|
|
|
25
25
|
[project.optional-dependencies]
|
|
26
26
|
full = ["asdf~=4.1",
|
|
27
|
-
"aspect-stable~=0.
|
|
27
|
+
"aspect-stable~=0.7.dev1",
|
|
28
28
|
"bokeh~=3.8",
|
|
29
29
|
"mplcursors~=0.6",
|
|
30
30
|
"openpyxl~=3.1",
|
|
@@ -265,6 +265,7 @@ def check_fits_instructions(fits_source, online_provider=False):
|
|
|
265
265
|
fits_reader = getattr(fits_manager, fits_source)
|
|
266
266
|
else:
|
|
267
267
|
source_type = 'instrument' if online_provider is False else 'survey'
|
|
268
|
+
# TODO show instruments supported
|
|
268
269
|
raise LiMe_Error(f'Input {source_type} "{fits_source}" is not recognized. LiMe observation cannot be created.')
|
|
269
270
|
|
|
270
271
|
else:
|
|
@@ -375,6 +376,40 @@ def load_fits(fits_address, data_ext_list=None, hdr_ext_list=None, url_check=Fal
|
|
|
375
376
|
return data_list, header_list
|
|
376
377
|
|
|
377
378
|
|
|
379
|
+
def join_spectra_matrix(wave, flux, err=None):
|
|
380
|
+
|
|
381
|
+
# Make sure the wavelength array is increasing
|
|
382
|
+
# for i in range(wave.shape[0]):
|
|
383
|
+
# if wave[i, 0] > wave[i, -1]:
|
|
384
|
+
# wave[i] = wave[i, ::-1]
|
|
385
|
+
# flux[i] = flux[i, ::-1]
|
|
386
|
+
# err[i] = err[i, ::-1] if err is not None else None
|
|
387
|
+
|
|
388
|
+
# Make sure the passes are sorted:
|
|
389
|
+
key = np.nanmean(wave, axis=1)
|
|
390
|
+
order = np.argsort(key)
|
|
391
|
+
wave, flux, err = wave[order], flux[order], err[order] if err is not None else None
|
|
392
|
+
|
|
393
|
+
# Get dimensions
|
|
394
|
+
n_passes, n_pix = wave.shape
|
|
395
|
+
|
|
396
|
+
join_wl = 0.5 * (wave[:-1, -1] + wave[1:, 0])
|
|
397
|
+
cut_idx = np.sum(wave[:-1] <= join_wl[:, None], axis=1)
|
|
398
|
+
cut_idx = np.append(cut_idx, n_pix)
|
|
399
|
+
|
|
400
|
+
pix = np.arange(n_pix)
|
|
401
|
+
mask = pix < cut_idx[:, None]
|
|
402
|
+
|
|
403
|
+
wave_1d = wave[mask]
|
|
404
|
+
flux_1d = flux[mask]
|
|
405
|
+
err_1d = err[mask] if err is not None else None
|
|
406
|
+
|
|
407
|
+
lengths = np.sum(mask, axis=1)
|
|
408
|
+
starts = np.concatenate(([0], np.cumsum(lengths[:-1])))
|
|
409
|
+
|
|
410
|
+
return wave_1d, flux_1d, err_1d
|
|
411
|
+
|
|
412
|
+
|
|
378
413
|
class OpenFits:
|
|
379
414
|
|
|
380
415
|
def __init__(self, file_address, file_source=None, load_function=None, lime_object=None):
|
|
@@ -785,30 +820,42 @@ class OpenFits:
|
|
|
785
820
|
|
|
786
821
|
"""
|
|
787
822
|
|
|
788
|
-
#
|
|
823
|
+
# Read the array
|
|
789
824
|
data_list, header_list = load_fits(fits_address, data_ext_list, hdr_ext_list, url_check=False)
|
|
825
|
+
|
|
826
|
+
# Warning in the case of empty observartion
|
|
827
|
+
if len(data_list[0]['WAVELENGTH'].squeeze()) == 0:
|
|
828
|
+
_logger.critical(f'Input COS observation does not have scientific data ({fits_address}).')
|
|
829
|
+
|
|
830
|
+
# One single array with the data
|
|
790
831
|
if data_list[0]['WAVELENGTH'].squeeze().ndim == 1:
|
|
791
832
|
wave_arr = data_list[0]['WAVELENGTH'].squeeze()
|
|
792
833
|
flux_arr = data_list[0]['FLUX'].squeeze()
|
|
793
834
|
err_arr = data_list[0]['ERROR'].squeeze()
|
|
794
835
|
|
|
836
|
+
# Multiple passes
|
|
795
837
|
else:
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
#
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
#
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
838
|
+
|
|
839
|
+
wave_arr, flux_arr, err_arr = join_spectra_matrix(data_list[0]['WAVELENGTH'],
|
|
840
|
+
data_list[0]['FLUX'],
|
|
841
|
+
data_list[0]['ERROR'])
|
|
842
|
+
|
|
843
|
+
# # Get common middle index for joining the spectra
|
|
844
|
+
# # wave_matrix = data_list[0]['WAVELENGTH'][::-1]
|
|
845
|
+
# idcs_common = np.nonzero(data_list[0]['WAVELENGTH'][1, :] > data_list[0]['WAVELENGTH'][0, 0])[0]
|
|
846
|
+
# center_idx = idcs_common.shape[0] // 2
|
|
847
|
+
#
|
|
848
|
+
# # Create empty containers
|
|
849
|
+
# wave_arr = np.empty(data_list[0]['WAVELENGTH'].size - center_idx, data_list[0]['WAVELENGTH'].dtype) # TODO check for additional extension to join the spectra
|
|
850
|
+
# flux_arr = np.empty(data_list[0]['FLUX'].size - center_idx, data_list[0]['FLUX'].dtype) # dtype=data_list[0]['FLUX'].dtype)
|
|
851
|
+
# err_arr = np.empty(data_list[0]['ERROR'].size - center_idx, data_list[0]['ERROR'].dtype) # dtype=data_list[0]['ERROR'].dtype)
|
|
852
|
+
#
|
|
853
|
+
# # Fill with the array data
|
|
854
|
+
# arr_size = data_list[0]['WAVELENGTH'].shape[1]
|
|
855
|
+
# for key_arr, cont_arr in zip(['WAVELENGTH', 'FLUX', 'ERROR'], [wave_arr, flux_arr, err_arr]):
|
|
856
|
+
# cont_arr[0:arr_size - center_idx] = data_list[0][key_arr][1][0:arr_size - center_idx]
|
|
857
|
+
# cont_arr[arr_size - center_idx:] = data_list[0][key_arr][0]
|
|
858
|
+
# # print(key_arr, np.any(np.isnan(cont_arr)))
|
|
812
859
|
|
|
813
860
|
# Spectrum properties
|
|
814
861
|
params_dict = SPECTRUM_FITS_PARAMS['cos']
|
|
@@ -149,9 +149,6 @@ def redshift_key_method(spec, bands, z_min, z_max, delta_z, pred_arr, components
|
|
|
149
149
|
if spec.res_power is not None:
|
|
150
150
|
res_power = spec.res_power
|
|
151
151
|
else:
|
|
152
|
-
# wave_arr / np.r_[np.diff(wave_arr), np.diff(wave_arr)[-1]]
|
|
153
|
-
# alpha_lambda = np.diff(wave_arr)
|
|
154
|
-
# res_power = wave_arr / np.r_[alpha_lambda, alpha_lambda[-1]]
|
|
155
152
|
delta_lambda = np.ediff1d(wave_arr, to_end=0)
|
|
156
153
|
delta_lambda[-1] = delta_lambda[-2]
|
|
157
154
|
res_power = wave_arr / delta_lambda
|
|
@@ -166,8 +163,6 @@ def redshift_key_method(spec, bands, z_min, z_max, delta_z, pred_arr, components
|
|
|
166
163
|
z_arr = np.arange(z_min, z_max + 0.5 * delta_z, delta_z)
|
|
167
164
|
|
|
168
165
|
# Parameters for the brute analysis
|
|
169
|
-
# z_arr = np.linspace(z_min, z_max, z_nsteps)
|
|
170
|
-
# z_arr = np.arange(z_min, z_max, step=0.005)
|
|
171
166
|
wave_matrix = np.tile(wave_arr, (theo_lambda.size, 1))
|
|
172
167
|
flux_sum = np.zeros(z_arr.size)
|
|
173
168
|
|
|
@@ -197,7 +192,7 @@ def redshift_key_method(spec, bands, z_min, z_max, delta_z, pred_arr, components
|
|
|
197
192
|
|
|
198
193
|
if plot_results and (z_infer is not None):
|
|
199
194
|
gauss_arr_max = compute_gaussian_ridges(z_infer, theo_lambda, wave_matrix, 1, band_vsigma, res_power)
|
|
200
|
-
redshift_key_evaluation(spec, method, z_infer, mask, gauss_arr_max, z_arr, flux_sum)
|
|
195
|
+
redshift_key_evaluation(spec, method, z_infer, mask, gauss_arr_max, z_arr, flux_sum, theo_lambda)
|
|
201
196
|
|
|
202
197
|
return z_infer
|
|
203
198
|
|
|
@@ -141,7 +141,6 @@ def parse_lime_cfg(toml_cfg, fit_cfg_suffix='_line_fitting'):
|
|
|
141
141
|
return toml_cfg
|
|
142
142
|
|
|
143
143
|
|
|
144
|
-
# Function to load configuration file
|
|
145
144
|
def load_cfg(file_address, fit_cfg_suffix='_line_fitting'):
|
|
146
145
|
|
|
147
146
|
"""
|
|
@@ -206,7 +205,6 @@ def load_cfg(file_address, fit_cfg_suffix='_line_fitting'):
|
|
|
206
205
|
return cfg_lime
|
|
207
206
|
|
|
208
207
|
|
|
209
|
-
# Function to save SpecSyzer configuration file
|
|
210
208
|
def save_cfg(output_file, param_dict, section_name=None, clear_section=False):
|
|
211
209
|
|
|
212
210
|
"""
|
|
@@ -221,9 +219,32 @@ def save_cfg(output_file, param_dict, section_name=None, clear_section=False):
|
|
|
221
219
|
|
|
222
220
|
# TODO review convert numpy arrays and floats64
|
|
223
221
|
if toml_check:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
222
|
+
|
|
223
|
+
# Section dict or the default dictionary
|
|
224
|
+
output_data = param_dict if section_name is None else {section_name: param_dict}
|
|
225
|
+
|
|
226
|
+
# If the file does not exist create a new file
|
|
227
|
+
if not output_path.is_file():
|
|
228
|
+
with open(output_file, "w") as f:
|
|
229
|
+
toml.dump(output_data, f)
|
|
230
|
+
|
|
231
|
+
# Load the file and add the new section
|
|
232
|
+
else:
|
|
233
|
+
with open(output_path, 'r') as f:
|
|
234
|
+
full_config = toml.load(f)
|
|
235
|
+
|
|
236
|
+
# Update the section data
|
|
237
|
+
full_config.update(output_data)
|
|
238
|
+
# if section_name is not None:
|
|
239
|
+
# full_config.update(output_data)
|
|
240
|
+
#
|
|
241
|
+
# # Add the new data
|
|
242
|
+
# else:
|
|
243
|
+
# full_config.update(output_data)
|
|
244
|
+
|
|
245
|
+
# Save the new data
|
|
246
|
+
with open(output_file, "w") as f:
|
|
247
|
+
toml.dump(full_config, f)
|
|
227
248
|
|
|
228
249
|
else:
|
|
229
250
|
raise LiMe_Error(f'toml library is not installed. Toml files cannot be saved')
|
|
@@ -538,7 +559,7 @@ def save_frame(fname, dataframe, page='FRAME', parameters='all', header=None, co
|
|
|
538
559
|
|
|
539
560
|
lineLogHDU = log_to_HDU(lines_log, ext_name=page, column_dtypes=column_dtypes, header_dict=header)
|
|
540
561
|
|
|
541
|
-
if log_path.is_file(): # TODO this strategy is slow for many
|
|
562
|
+
if log_path.is_file(): # TODO this strategy is slow for many configuration
|
|
542
563
|
try:
|
|
543
564
|
fits.update(log_path, data=lineLogHDU.data, header=lineLogHDU.header, extname=lineLogHDU.name, verify=True)
|
|
544
565
|
except KeyError:
|
|
@@ -7,6 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
from astropy.io import fits
|
|
8
8
|
from collections import UserDict
|
|
9
9
|
|
|
10
|
+
from lime.fitting.lines import profiles_computation, linear_continuum_computation
|
|
10
11
|
from lime.tools import extract_fluxes, normalize_fluxes, ProgressBar, check_units, extract_wcs_header, \
|
|
11
12
|
parse_unit_convertion
|
|
12
13
|
|
|
@@ -157,6 +158,7 @@ def check_spectra_arrays(observation):
|
|
|
157
158
|
|
|
158
159
|
return
|
|
159
160
|
|
|
161
|
+
|
|
160
162
|
def check_redshift_norm(redshift, norm_flux, flux_array, units_flux, norm_factor=1, min_flux_scale=0.001, max_flux_scale=1e50):
|
|
161
163
|
|
|
162
164
|
if (redshift is None) or np.isnan(redshift) or np.isinf(redshift):
|
|
@@ -226,10 +228,10 @@ def check_sample_levels(levels, necessary_levels=("id", "file")):
|
|
|
226
228
|
return
|
|
227
229
|
|
|
228
230
|
|
|
229
|
-
def cropping_spectrum(crop_waves, input_wave, input_flux, input_err, pixel_mask):
|
|
231
|
+
def cropping_spectrum(crop_waves, crop_flux, input_wave, input_flux, input_err, pixel_mask):
|
|
230
232
|
|
|
231
233
|
if crop_waves is not None:
|
|
232
|
-
|
|
234
|
+
# TODO warning message
|
|
233
235
|
idx_min = np.searchsorted(input_wave, crop_waves[0]) if crop_waves[0] != 0 else 0
|
|
234
236
|
idx_max = np.searchsorted(input_wave, crop_waves[1]) if crop_waves[1] != -1 else None
|
|
235
237
|
|
|
@@ -256,6 +258,23 @@ def cropping_spectrum(crop_waves, input_wave, input_flux, input_err, pixel_mask)
|
|
|
256
258
|
if pixel_mask is not None:
|
|
257
259
|
pixel_mask = pixel_mask[idcs_crop[0]:idcs_crop[1]]
|
|
258
260
|
|
|
261
|
+
if crop_flux is not None:
|
|
262
|
+
|
|
263
|
+
# Validate percentiles
|
|
264
|
+
for perctl, name in zip(crop_flux, ("Min percentile", "Max percentile")):
|
|
265
|
+
if not np.isscalar(perctl):
|
|
266
|
+
raise TypeError(f"{name} must be a scalar.")
|
|
267
|
+
if not (0.0 <= perctl <= 100):
|
|
268
|
+
raise ValueError(f"{name} must be in the range [0, 100].")
|
|
269
|
+
|
|
270
|
+
if crop_flux[0] >= crop_flux[1]:
|
|
271
|
+
raise ValueError(f"The lower percentile limit ({crop_flux[0]}) must be must be smaller than upper limit "
|
|
272
|
+
f"({crop_flux[1]}).")
|
|
273
|
+
|
|
274
|
+
# Clip the flux to the limits
|
|
275
|
+
lo, hi = np.percentile(input_flux[~pixel_mask], crop_flux)
|
|
276
|
+
input_flux[~pixel_mask] = np.clip(input_flux[~pixel_mask], lo, hi)
|
|
277
|
+
|
|
259
278
|
return input_wave, input_flux, input_err, pixel_mask
|
|
260
279
|
|
|
261
280
|
|
|
@@ -416,7 +435,8 @@ class Spectrum:
|
|
|
416
435
|
_fitsMgr = None
|
|
417
436
|
|
|
418
437
|
def __init__(self, input_wave=None, input_flux=None, input_err=None, redshift=None, norm_flux=None, crop_waves=None,
|
|
419
|
-
res_power=None, units_wave='AA', units_flux='FLAM', pixel_mask=None, id_label=None,
|
|
438
|
+
crop_flux=None, res_power=None, units_wave='AA', units_flux='FLAM', pixel_mask=None, id_label=None,
|
|
439
|
+
review_inputs=True):
|
|
420
440
|
|
|
421
441
|
# Class attributes
|
|
422
442
|
self.label = None
|
|
@@ -447,7 +467,7 @@ class Spectrum:
|
|
|
447
467
|
|
|
448
468
|
# Review and assign the attibutes data
|
|
449
469
|
if review_inputs:
|
|
450
|
-
self._set_attributes(input_wave, input_flux, input_err, redshift, norm_flux, crop_waves, res_power,
|
|
470
|
+
self._set_attributes(input_wave, input_flux, input_err, redshift, norm_flux, crop_waves, crop_flux, res_power,
|
|
451
471
|
units_wave, units_flux, pixel_mask, id_label)
|
|
452
472
|
|
|
453
473
|
return
|
|
@@ -503,11 +523,6 @@ class Spectrum:
|
|
|
503
523
|
# Class attributes
|
|
504
524
|
spec.label = label
|
|
505
525
|
spec.flux = cube.flux[:, idx_j, idx_i]
|
|
506
|
-
# from matplotlib import pyplot as plt
|
|
507
|
-
# fig, ax = plt.subplots()
|
|
508
|
-
# ax.imshow(cube.err_flux[500, :, :].data)
|
|
509
|
-
# plt.show()
|
|
510
|
-
|
|
511
526
|
spec.err_flux = None if cube.err_flux is None else cube.err_flux[:, idx_j, idx_i]
|
|
512
527
|
spec.norm_flux = cube.norm_flux
|
|
513
528
|
spec.redshift = cube.redshift
|
|
@@ -526,7 +541,7 @@ class Spectrum:
|
|
|
526
541
|
return spec
|
|
527
542
|
|
|
528
543
|
@classmethod
|
|
529
|
-
def from_file(cls, fname, instrument, redshift=None, norm_flux=None, crop_waves=None, res_power=None,
|
|
544
|
+
def from_file(cls, fname, instrument, redshift=None, norm_flux=None, crop_waves=None, crop_flux=None, res_power=None,
|
|
530
545
|
units_wave=None, units_flux=None, pixel_mask=None, id_label=None, wcs=None, **kwargs):
|
|
531
546
|
|
|
532
547
|
"""
|
|
@@ -623,8 +638,8 @@ class Spectrum:
|
|
|
623
638
|
fits_args = cls._fitsMgr.parse_data_from_file(cls._fitsMgr.file_address, pixel_mask, **kwargs)
|
|
624
639
|
|
|
625
640
|
# Update the file parameters with the user parameters
|
|
626
|
-
input_args = dict(redshift=redshift, norm_flux=norm_flux, crop_waves=crop_waves,
|
|
627
|
-
|
|
641
|
+
input_args = dict(redshift=redshift, norm_flux=norm_flux, crop_waves=crop_waves, crop_flux=crop_flux,
|
|
642
|
+
res_power=res_power, units_wave=units_wave, units_flux=units_flux, id_label=id_label, wcs=wcs)
|
|
628
643
|
|
|
629
644
|
if cls._fitsMgr.spectrum_check:
|
|
630
645
|
input_args.pop('wcs')
|
|
@@ -675,8 +690,8 @@ class Spectrum:
|
|
|
675
690
|
# Create the LiMe object
|
|
676
691
|
return cls(**fits_args)
|
|
677
692
|
|
|
678
|
-
def _set_attributes(self, input_wave, input_flux, input_err, redshift, norm_flux, crop_waves,
|
|
679
|
-
units_flux, pixel_mask, label):
|
|
693
|
+
def _set_attributes(self, input_wave, input_flux, input_err, redshift, norm_flux, crop_waves, crop_flux, res_power,
|
|
694
|
+
units_wave, units_flux, pixel_mask, label):
|
|
680
695
|
|
|
681
696
|
# Class attributes
|
|
682
697
|
self.label = label
|
|
@@ -691,8 +706,8 @@ class Spectrum:
|
|
|
691
706
|
self.redshift, self.norm_flux = check_redshift_norm(redshift, norm_flux, input_flux, self.units_flux)
|
|
692
707
|
|
|
693
708
|
# Crop the input spectrum if necessary
|
|
694
|
-
input_wave, input_flux, input_err, pixel_mask = cropping_spectrum(crop_waves, input_wave, input_flux,
|
|
695
|
-
pixel_mask)
|
|
709
|
+
input_wave, input_flux, input_err, pixel_mask = cropping_spectrum(crop_waves, crop_flux, input_wave, input_flux,
|
|
710
|
+
input_err, pixel_mask)
|
|
696
711
|
|
|
697
712
|
# Normalization and masking
|
|
698
713
|
self.wave, self.wave_rest, self.flux, self.err_flux = spec_normalization_masking(input_wave, input_flux,
|
|
@@ -925,6 +940,116 @@ class Spectrum:
|
|
|
925
940
|
|
|
926
941
|
return
|
|
927
942
|
|
|
943
|
+
def save_spectrum(self, fname=None, line_label=None, ref_frame=None, split_components=False, **kwargs):
|
|
944
|
+
|
|
945
|
+
# Headers for the default list
|
|
946
|
+
headers = np.array(["wave", "flux", "err_flux", "pixel_mask"])
|
|
947
|
+
|
|
948
|
+
# Use the observation frame if none is provided
|
|
949
|
+
frame = self.frame if ref_frame is None else ref_frame
|
|
950
|
+
|
|
951
|
+
# By default report complete spectrum
|
|
952
|
+
idcs = (0, None)
|
|
953
|
+
|
|
954
|
+
# If a line is provided get indexes for the bands limits
|
|
955
|
+
line_measured = False
|
|
956
|
+
if line_label is not None:
|
|
957
|
+
if frame is not None:
|
|
958
|
+
if line_label in frame.index:
|
|
959
|
+
bands_limits = frame.loc[line_label, 'w1':'w6']
|
|
960
|
+
idcs_bands = np.searchsorted(self._spec.wave.data, bands_limits * (1 + self._spec.redshift))
|
|
961
|
+
idcs = (idcs_bands[0], idcs_bands[5])
|
|
962
|
+
line_measured = True
|
|
963
|
+
else:
|
|
964
|
+
_logger.warning(f'Line {line_label} not found on observation frame')
|
|
965
|
+
else:
|
|
966
|
+
_logger.warning(f'No lines measured on object')
|
|
967
|
+
|
|
968
|
+
# Compute the bands
|
|
969
|
+
if line_measured:
|
|
970
|
+
|
|
971
|
+
# Declare line object and the components and its components from the frame
|
|
972
|
+
line = Line.from_transition(line_label, data_frame=frame)
|
|
973
|
+
line_list = line.list_comps
|
|
974
|
+
|
|
975
|
+
# Compute the linear components
|
|
976
|
+
gaussian_arr = profiles_computation(line_list, frame, 1 + self._spec.redshift, line.profile,
|
|
977
|
+
x_array=self._spec.wave.data[idcs[0]: idcs[1]])
|
|
978
|
+
linear_arr = linear_continuum_computation(line_list, frame, 1 + self._spec.redshift, x_array=self._spec.wave.data[idcs[0]: idcs[1]])
|
|
979
|
+
|
|
980
|
+
# Determine which component you want to extract:
|
|
981
|
+
if split_components is False:
|
|
982
|
+
gaussian_arr = gaussian_arr.sum(axis=1) + linear_arr[:, 0]
|
|
983
|
+
gaussian_arr = gaussian_arr.reshape(-1, 1)
|
|
984
|
+
line_hdrs = [line_label]
|
|
985
|
+
else:
|
|
986
|
+
gaussian_arr = gaussian_arr + linear_arr[:, 0][:, np.newaxis]
|
|
987
|
+
line_hdrs = line_list
|
|
988
|
+
|
|
989
|
+
# Add the line list to the headers
|
|
990
|
+
headers = np.append(headers, line_hdrs)
|
|
991
|
+
|
|
992
|
+
# Container for the data
|
|
993
|
+
out_arr = np.full((self.wave.data[idcs[0]: idcs[1]].size, len(headers)), np.nan)
|
|
994
|
+
|
|
995
|
+
# Fill the array:
|
|
996
|
+
out_arr[:, 0] = self.wave.data[idcs[0]: idcs[1]]
|
|
997
|
+
out_arr[:, 1] = self.flux.data[idcs[0]: idcs[1]] * self.norm_flux
|
|
998
|
+
|
|
999
|
+
# Err array if it exists
|
|
1000
|
+
if self.err_flux is not None:
|
|
1001
|
+
out_arr[:, 2] = self.err_flux[idcs[0]: idcs[1]].data * self.norm_flux
|
|
1002
|
+
|
|
1003
|
+
# Pixel mask if any is invalid
|
|
1004
|
+
if np.any(self.wave.mask):
|
|
1005
|
+
out_arr[:, 3] = self.wave[idcs[0]: idcs[1]].mask
|
|
1006
|
+
|
|
1007
|
+
# Add the components
|
|
1008
|
+
if line_measured:
|
|
1009
|
+
for i, line_comp in enumerate(line_hdrs):
|
|
1010
|
+
out_arr[:, 4 + i] = gaussian_arr[:, i]
|
|
1011
|
+
|
|
1012
|
+
# Crop array if some columns are missing
|
|
1013
|
+
nan_columns = np.zeros(out_arr.shape[1]).astype(bool)
|
|
1014
|
+
nan_columns[:4] = np.all(np.isnan(out_arr[:, :4]), axis=0)
|
|
1015
|
+
out_arr = out_arr[:, ~nan_columns]
|
|
1016
|
+
|
|
1017
|
+
# Headers
|
|
1018
|
+
headers = headers[~nan_columns]
|
|
1019
|
+
|
|
1020
|
+
# Formatting for the data
|
|
1021
|
+
spec_hdrs_list = ['%.18e', '%.18e', '%.18e', '%d']
|
|
1022
|
+
spec_hdrs_list = spec_hdrs_list + ['%.18e'] * len(line_hdrs) if line_measured else spec_hdrs_list
|
|
1023
|
+
array_fmt = np.array(spec_hdrs_list)
|
|
1024
|
+
array_fmt = list(array_fmt[~nan_columns])
|
|
1025
|
+
|
|
1026
|
+
# Update defaults with user-provided values
|
|
1027
|
+
default_kwargs = {"fmt": array_fmt, "delimiter": ' '}
|
|
1028
|
+
default_kwargs.update(kwargs)
|
|
1029
|
+
|
|
1030
|
+
# Create header
|
|
1031
|
+
if default_kwargs.get('header') is None:
|
|
1032
|
+
default_kwargs['header'] = default_kwargs['delimiter'].join(headers)
|
|
1033
|
+
|
|
1034
|
+
# Dictionary with parameters
|
|
1035
|
+
if 'footer' not in default_kwargs:
|
|
1036
|
+
footer_dict = {'LiMe': f"v{lime_cfg['metadata']['version']}",
|
|
1037
|
+
'units_wave': self.units_wave, 'units_flux': self.units_flux,
|
|
1038
|
+
'redshift': self.redshift, 'norm_flux': self.norm_flux, 'id_label': self.label}
|
|
1039
|
+
footer_str = "\n".join(f"{key}:{value}" for key, value in footer_dict.items())
|
|
1040
|
+
default_kwargs['footer'] = footer_str
|
|
1041
|
+
|
|
1042
|
+
# Return a recarray with the spectrum data
|
|
1043
|
+
if fname is None:
|
|
1044
|
+
output = np.core.records.fromarrays([out_arr[:, i] for i in range(out_arr.shape[1])], names=list(headers))
|
|
1045
|
+
|
|
1046
|
+
# Save to a file
|
|
1047
|
+
else:
|
|
1048
|
+
np.savetxt(fname, out_arr, **default_kwargs)
|
|
1049
|
+
output = None
|
|
1050
|
+
|
|
1051
|
+
return output
|
|
1052
|
+
|
|
928
1053
|
def update_redshift(self, redshift):
|
|
929
1054
|
|
|
930
1055
|
"""
|
|
@@ -982,20 +1107,23 @@ class Spectrum:
|
|
|
982
1107
|
|
|
983
1108
|
return
|
|
984
1109
|
|
|
985
|
-
def
|
|
986
|
-
|
|
987
|
-
raise LiMe_Error(f'The line_detection functionality has been moved an rebranded. Please use:\n'
|
|
988
|
-
f'Spectrum.infer.peaks_troughs()')
|
|
989
|
-
|
|
990
|
-
def clear_data(self):
|
|
1110
|
+
def clear_data(self, line_data=True, cont_data=True):
|
|
991
1111
|
|
|
992
1112
|
"""
|
|
993
|
-
Clear the spectrum’s measurements frame.
|
|
1113
|
+
Clear the spectrum’s line measurements frame and fitted continuum.
|
|
994
1114
|
|
|
995
1115
|
This method removes all entries from the internal ``frame`` attribute,
|
|
996
1116
|
effectively resetting the stored measurements while preserving the
|
|
997
1117
|
dataframe structure (columns and metadata).
|
|
998
1118
|
|
|
1119
|
+
Parameters
|
|
1120
|
+
----------
|
|
1121
|
+
line_data : bool, optional
|
|
1122
|
+
Clear the spectrum’s line measurements frame. The default value is true.
|
|
1123
|
+
|
|
1124
|
+
cont_data : bool, optional
|
|
1125
|
+
Clear the spectrum’s fitted continuum. The default value is true.
|
|
1126
|
+
|
|
999
1127
|
Returns
|
|
1000
1128
|
-------
|
|
1001
1129
|
None
|
|
@@ -1005,6 +1133,7 @@ class Spectrum:
|
|
|
1005
1133
|
-----
|
|
1006
1134
|
- The operation is equivalent to reassigning ``self.frame = self.frame[0:0]``,
|
|
1007
1135
|
which clears all rows but keeps column definitions intact.
|
|
1136
|
+
- The Spectrum.cont and cont_std variables are set to None.
|
|
1008
1137
|
- Use this method to reset the spectrum’s measurement results before
|
|
1009
1138
|
reprocessing or refitting without recreating the object.
|
|
1010
1139
|
|
|
@@ -1017,7 +1146,12 @@ class Spectrum:
|
|
|
1017
1146
|
(0, 10)
|
|
1018
1147
|
"""
|
|
1019
1148
|
|
|
1020
|
-
|
|
1149
|
+
if line_data:
|
|
1150
|
+
self.frame = self.frame[0:0]
|
|
1151
|
+
|
|
1152
|
+
if cont_data:
|
|
1153
|
+
self.cont = None
|
|
1154
|
+
self.cont_std = None
|
|
1021
1155
|
|
|
1022
1156
|
return
|
|
1023
1157
|
|
|
@@ -1105,9 +1239,10 @@ class Cube:
|
|
|
1105
1239
|
_fitsMgr = None
|
|
1106
1240
|
|
|
1107
1241
|
def __init__(self, input_wave=None, input_flux=None, input_err=None, redshift=None, norm_flux=None, crop_waves=None,
|
|
1108
|
-
res_power=None, units_wave='AA', units_flux='FLAM', pixel_mask=None, id_label=None,
|
|
1242
|
+
crop_flux=None, res_power=None, units_wave='AA', units_flux='FLAM', pixel_mask=None, id_label=None,
|
|
1243
|
+
wcs=None):
|
|
1109
1244
|
|
|
1110
|
-
# Review the
|
|
1245
|
+
# Review the inputs
|
|
1111
1246
|
pixel_mask = check_inputs_arrays(input_wave, input_flux, input_err, pixel_mask, self)
|
|
1112
1247
|
|
|
1113
1248
|
# Class attributes
|
|
@@ -1133,8 +1268,8 @@ class Cube:
|
|
|
1133
1268
|
self.redshift, self.norm_flux = check_redshift_norm(redshift, norm_flux, input_flux, self.units_flux)
|
|
1134
1269
|
|
|
1135
1270
|
# Start cropping the input spectrum if necessary
|
|
1136
|
-
input_wave, input_flux, input_err, pixel_mask = cropping_spectrum(crop_waves, input_wave, input_flux,
|
|
1137
|
-
pixel_mask)
|
|
1271
|
+
input_wave, input_flux, input_err, pixel_mask = cropping_spectrum(crop_waves, crop_flux, input_wave, input_flux,
|
|
1272
|
+
input_err, pixel_mask)
|
|
1138
1273
|
|
|
1139
1274
|
# Spectrum normalization, redshift and mask calculation
|
|
1140
1275
|
self.wave, self.wave_rest, self.flux, self.err_flux = spec_normalization_masking(input_wave, input_flux,
|
|
@@ -1336,7 +1471,7 @@ class Cube:
|
|
|
1336
1471
|
>>> cube.spatial_masking("H1_4861A", param="SN_line", contour_pctls=[80, 90, 95])
|
|
1337
1472
|
"""
|
|
1338
1473
|
|
|
1339
|
-
# Check the function
|
|
1474
|
+
# Check the function inputs
|
|
1340
1475
|
contour_pctls = np.atleast_1d(contour_pctls)
|
|
1341
1476
|
if not np.all(np.diff(contour_pctls) > 0):
|
|
1342
1477
|
raise LiMe_Error(f'The mask percentiles ({contour_pctls}) must be in increasing order')
|
|
@@ -1561,7 +1696,7 @@ class Cube:
|
|
|
1561
1696
|
--------
|
|
1562
1697
|
Extract a single spaxel spectrum from a cube:
|
|
1563
1698
|
|
|
1564
|
-
>>> spec = cube.
|
|
1699
|
+
>>> spec = cube.get_spectrum(25, 30)
|
|
1565
1700
|
|
|
1566
1701
|
"""
|
|
1567
1702
|
|