lime-stable 2.0.dev7__tar.gz → 2.0.dev8__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.dev7/src/lime_stable.egg-info → lime_stable-2.0.dev8}/PKG-INFO +1 -1
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/pyproject.toml +1 -1
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/archives/read_fits.py +44 -38
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/fitting/lines.py +2 -15
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/io.py +4 -1
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/lime.toml +1 -1
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/observations.py +12 -5
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/plotting/plots.py +4 -40
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/plotting/plots_interactive.py +1 -1
- lime_stable-2.0.dev8/src/lime/retrieve/line_bands.py +226 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/transitions.py +67 -60
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/workflow.py +9 -300
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8/src/lime_stable.egg-info}/PKG-INFO +1 -1
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime_stable.egg-info/SOURCES.txt +1 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/tests/test_model.py +1 -1
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/tests/test_read_fits.py +33 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/tests/test_spectrum.py +13 -6
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/tests/test_tools.py +4 -4
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/LICENSE.rst +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/MANIFEST.in +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/README.rst +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/setup.cfg +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/__init__.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/archives/__init__.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/archives/tables.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/changelog.txt +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/fitting/__init__.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/fitting/redshift.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/inference/detection.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/inference/intensity_threshold.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/plotting/__init__.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/plotting/bokeh_plots.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/plotting/format.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/plotting/theme_lime.toml +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/plotting/utils.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/resources/__init__.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/resources/lines_database_formatting.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/resources/lines_database_v2.0.0.txt +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/resources/logo.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/resources/types_params.txt +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/retrieve/__init__.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/retrieve/peaks.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime/tools.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime_stable.egg-info/dependency_links.txt +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime_stable.egg-info/requires.txt +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/src/lime_stable.egg-info/top_level.txt +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/tests/test_astro.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/tests/test_cube.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/tests/test_io.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/tests/test_line.py +0 -0
- {lime_stable-2.0.dev7 → lime_stable-2.0.dev8}/tests/test_sample.py +0 -0
|
@@ -257,35 +257,44 @@ def check_fits_instructions(fits_source, online_provider=False):
|
|
|
257
257
|
else:
|
|
258
258
|
fits_reader = None
|
|
259
259
|
|
|
260
|
-
# # Check for url location for surveys function
|
|
261
|
-
# if online_provider:
|
|
262
|
-
# if hasattr(UrlFitsSurvey, fits_source):
|
|
263
|
-
# url_locator = getattr(UrlFitsSurvey, fits_source)
|
|
264
|
-
# else:
|
|
265
|
-
# raise LiMe_Error(f'Input {fits_source} does not have a url manager for LiMe could not be created.')
|
|
266
|
-
# else:
|
|
267
|
-
# url_locator = None
|
|
268
|
-
|
|
269
260
|
return fits_reader
|
|
270
261
|
|
|
271
|
-
def load_txt(text_address):
|
|
262
|
+
def load_txt(text_address, **kwargs):
|
|
272
263
|
|
|
273
264
|
# Columns
|
|
274
|
-
out_array = np.loadtxt(text_address)
|
|
265
|
+
out_array = np.loadtxt(text_address, **kwargs)
|
|
275
266
|
|
|
276
|
-
#
|
|
277
|
-
|
|
278
|
-
|
|
267
|
+
# File address
|
|
268
|
+
if not type(text_address).__name__ == "UploadedFile":
|
|
269
|
+
with open(text_address, "r") as f:
|
|
270
|
+
lines = f.readlines()
|
|
279
271
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if not line.startswith("#") or (line.startswith("# LiMe")):
|
|
284
|
-
break
|
|
272
|
+
# Uploaded file
|
|
273
|
+
else:
|
|
274
|
+
lines = text_address.getvalue().decode("utf-8").splitlines()
|
|
285
275
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
276
|
+
# Reverse loop over the lines
|
|
277
|
+
params_dict = {}
|
|
278
|
+
for line in reversed(lines):
|
|
279
|
+
line = line.strip()
|
|
280
|
+
if not line.startswith("#") or line.startswith("# LiMe"):
|
|
281
|
+
break
|
|
282
|
+
key, value = line[1:].split(":", 1)
|
|
283
|
+
params_dict[key.strip()] = value.strip()
|
|
284
|
+
|
|
285
|
+
# # Transform foot comments as dictionary data
|
|
286
|
+
# params_dict = {}
|
|
287
|
+
# with open(text_address, "r") as f:
|
|
288
|
+
#
|
|
289
|
+
# # Reverse loop while the lines start by a "#"
|
|
290
|
+
# for line in reversed(f.readlines()):
|
|
291
|
+
# line = line.strip()
|
|
292
|
+
# if not line.startswith("#") or (line.startswith("# LiMe")):
|
|
293
|
+
# break
|
|
294
|
+
#
|
|
295
|
+
# # Extract key-value pairs
|
|
296
|
+
# key, value = line[1:].split(":", 1) # Split at the first ':'
|
|
297
|
+
# params_dict[key.strip()] = value.strip()
|
|
289
298
|
|
|
290
299
|
return out_array, params_dict
|
|
291
300
|
|
|
@@ -362,10 +371,10 @@ class OpenFits:
|
|
|
362
371
|
|
|
363
372
|
return
|
|
364
373
|
|
|
365
|
-
def parse_data_from_file(self, file_address, pixel_mask
|
|
374
|
+
def parse_data_from_file(self, file_address, pixel_mask, **kwargs):
|
|
366
375
|
|
|
367
376
|
# Read the fits data
|
|
368
|
-
wave_array, flux_array, err_array, header_list, fits_params = self.fits_reader(file_address)
|
|
377
|
+
wave_array, flux_array, err_array, header_list, fits_params = self.fits_reader(file_address, **kwargs)
|
|
369
378
|
pixel_mask = pixel_mask if pixel_mask is not None else fits_params['pixel_mask']
|
|
370
379
|
|
|
371
380
|
# Mask requested entries
|
|
@@ -441,10 +450,10 @@ class OpenFits:
|
|
|
441
450
|
return fits_args
|
|
442
451
|
|
|
443
452
|
@staticmethod
|
|
444
|
-
def text(file_address):
|
|
453
|
+
def text(file_address, **kwargs):
|
|
445
454
|
|
|
446
455
|
# Read text file dividing the columns into the spectrum axis and the comments as its parameters
|
|
447
|
-
data_arr, params_dict = load_txt(file_address)
|
|
456
|
+
data_arr, params_dict = load_txt(file_address, **kwargs)
|
|
448
457
|
|
|
449
458
|
# Unpack the columns into the spectrum axes
|
|
450
459
|
wave_array, flux_array = data_arr[:, 0], data_arr[:, 1]
|
|
@@ -456,16 +465,13 @@ class OpenFits:
|
|
|
456
465
|
# Convert strings to expected format
|
|
457
466
|
params_dict['redshift'] = float(params_dict['redshift']) if 'redshift' in params_dict else None
|
|
458
467
|
params_dict['norm_flux'] = float(params_dict['norm_flux']) if 'norm_flux' in params_dict else None
|
|
459
|
-
params_dict['id_label'] = params_dict['id_label'] if '
|
|
468
|
+
params_dict['id_label'] = params_dict['id_label'] if 'id_label' in params_dict else None
|
|
460
469
|
params_dict['pixel_mask'] = mask_array
|
|
461
470
|
|
|
462
|
-
# metadata['units_wave'] = au.Unit(metadata['units_wave']) if 'units_wave' in metadata else au.Unit('AA')
|
|
463
|
-
# metadata['units_flux'] = au.Unit(metadata['units_flux']) if 'units_flux' in metadata else au.Unit('FLAM')
|
|
464
|
-
|
|
465
471
|
return wave_array, flux_array, err_array, None, params_dict
|
|
466
472
|
|
|
467
473
|
@staticmethod
|
|
468
|
-
def nirspec(fits_address, data_ext_list=1, hdr_ext_list=(0, 1),
|
|
474
|
+
def nirspec(fits_address, data_ext_list=1, hdr_ext_list=(0, 1), **kwargs):
|
|
469
475
|
|
|
470
476
|
"""
|
|
471
477
|
|
|
@@ -503,7 +509,7 @@ class OpenFits:
|
|
|
503
509
|
return wave_array, flux_array, err_array, header_list, params_dict
|
|
504
510
|
|
|
505
511
|
@staticmethod
|
|
506
|
-
def isis(fits_address, data_ext_list=0, hdr_ext_list=0,
|
|
512
|
+
def isis(fits_address, data_ext_list=0, hdr_ext_list=0, **kwargs):
|
|
507
513
|
|
|
508
514
|
"""
|
|
509
515
|
|
|
@@ -548,7 +554,7 @@ class OpenFits:
|
|
|
548
554
|
return wave_array, flux_array, err_array, header_list, params_dict
|
|
549
555
|
|
|
550
556
|
@staticmethod
|
|
551
|
-
def osiris(fits_address, data_ext_list=0, hdr_ext_list=0,
|
|
557
|
+
def osiris(fits_address, data_ext_list=0, hdr_ext_list=0, **kwargs):
|
|
552
558
|
|
|
553
559
|
"""
|
|
554
560
|
|
|
@@ -593,7 +599,7 @@ class OpenFits:
|
|
|
593
599
|
return wave_array, flux_array, err_array, header_list, params_dict
|
|
594
600
|
|
|
595
601
|
@staticmethod
|
|
596
|
-
def sdss(fits_address, data_ext_list=(1, 2), hdr_ext_list=(0),
|
|
602
|
+
def sdss(fits_address, data_ext_list=(1, 2), hdr_ext_list=(0), **kwargs):
|
|
597
603
|
|
|
598
604
|
"""
|
|
599
605
|
|
|
@@ -645,7 +651,7 @@ class OpenFits:
|
|
|
645
651
|
return wave_array, flux_array, err_array, header_list, params_dict
|
|
646
652
|
|
|
647
653
|
@staticmethod
|
|
648
|
-
def manga(fits_address, data_ext_list=('WAVE', 'FLUX', 'IVAR'), hdr_ext_list=('FLUX'),
|
|
654
|
+
def manga(fits_address, data_ext_list=('WAVE', 'FLUX', 'IVAR'), hdr_ext_list=('FLUX'), **kwargs):
|
|
649
655
|
|
|
650
656
|
"""
|
|
651
657
|
|
|
@@ -692,7 +698,7 @@ class OpenFits:
|
|
|
692
698
|
return wave_array, flux_cube, err_cube, header_list, fits_params
|
|
693
699
|
|
|
694
700
|
@staticmethod
|
|
695
|
-
def muse(fits_address, data_ext_list=(1, 2), hdr_ext_list=1,
|
|
701
|
+
def muse(fits_address, data_ext_list=(1, 2), hdr_ext_list=1, **kwargs):
|
|
696
702
|
|
|
697
703
|
"""
|
|
698
704
|
|
|
@@ -737,7 +743,7 @@ class OpenFits:
|
|
|
737
743
|
return wave_array, flux_cube, err_cube, header_list, fits_params
|
|
738
744
|
|
|
739
745
|
@staticmethod
|
|
740
|
-
def megara(fits_address, data_ext_list=0, hdr_ext_list=(0, 1),
|
|
746
|
+
def megara(fits_address, data_ext_list=0, hdr_ext_list=(0, 1), **kwargs):
|
|
741
747
|
|
|
742
748
|
"""
|
|
743
749
|
|
|
@@ -781,7 +787,7 @@ class OpenFits:
|
|
|
781
787
|
return wave_array, flux_cube, err_cube, header_list, fits_params
|
|
782
788
|
|
|
783
789
|
@staticmethod
|
|
784
|
-
def miri(fits_address, data_ext_list=(1,2), hdr_ext_list=(1),
|
|
790
|
+
def miri(fits_address, data_ext_list=(1,2), hdr_ext_list=(1), **kwargs):
|
|
785
791
|
|
|
786
792
|
"""
|
|
787
793
|
|
|
@@ -608,7 +608,6 @@ class ProfileModelCompiler:
|
|
|
608
608
|
line.eqw = np.full(self.n_comps, np.nan)
|
|
609
609
|
line.eqw_err = np.full(self.n_comps, np.nan)
|
|
610
610
|
line.FWHM_p = np.full(self.n_comps, np.nan)
|
|
611
|
-
# line.sigma_thermal = np.full(self.n_comps, np.nan)
|
|
612
611
|
|
|
613
612
|
# Check for negative -0.0 # TODO this needs a better place # FIXME -0.0 error
|
|
614
613
|
if np.signbit(line.sigma_err[i]):
|
|
@@ -825,12 +824,6 @@ class LineFitting:
|
|
|
825
824
|
line.intg_flux = areasArray.mean()
|
|
826
825
|
line.intg_flux_err = areasArray.std()
|
|
827
826
|
|
|
828
|
-
# # Compute the integrated signal to noise # TODO is this an issue for absorptions
|
|
829
|
-
# amp_ref = line.peak_flux - line.cont
|
|
830
|
-
# if emission_check:
|
|
831
|
-
# if amp_ref < 0:
|
|
832
|
-
# amp_ref = line.peak_flux
|
|
833
|
-
|
|
834
827
|
# Compute SN_r
|
|
835
828
|
line.snr_line = signal_to_noise_rola(line.peak_flux - line.cont, line.cont_err, line.n_pixels)
|
|
836
829
|
line.snr_cont = line.cont/line.cont_err
|
|
@@ -842,20 +835,14 @@ class LineFitting:
|
|
|
842
835
|
else:
|
|
843
836
|
line._narrow_check = False
|
|
844
837
|
|
|
845
|
-
#
|
|
846
|
-
# idx_0 = compute_FWHM0(peakIdx, emis_flux, -1, cont_arr, emission_check)
|
|
847
|
-
# idx_f = compute_FWHM0(peakIdx, emis_flux, 1, cont_arr, emission_check)
|
|
848
|
-
#
|
|
849
|
-
# # Velocity calculations
|
|
850
|
-
# velocArray = c_KMpS * (emis_wave[idx_0:idx_f] - line.peak_wave) / line.peak_wave
|
|
851
|
-
# self.velocity_profile_calc(line, velocArray, emis_flux[idx_0:idx_f], cont_arr[idx_0:idx_f], emission_check)
|
|
838
|
+
# Velocity calculations
|
|
852
839
|
if (line.n_pixels >= min_array_dim) and (line._narrow_check is False):
|
|
853
840
|
self.velocity_profile_calc(line, peakIdx, emis_wave, emis_flux, cont_arr, emission_check, min_array_dim=min_array_dim)
|
|
854
841
|
|
|
855
842
|
# Pixel velocity # TODO we are not using this one
|
|
856
843
|
line.pixel_vel = c_KMpS * line.pixelWidth/line.peak_wave
|
|
857
844
|
|
|
858
|
-
# Equivalent width computation (it must be an 1d array to avoid conflict in blended lines)
|
|
845
|
+
# Equivalent width computation (it must be an 1d array to avoid conflict in blended lines)
|
|
859
846
|
lineContinuumMatrix = cont_arr + normalNoise
|
|
860
847
|
eqwMatrix = areasArray / lineContinuumMatrix.mean(axis=1)
|
|
861
848
|
|
|
@@ -569,10 +569,13 @@ def results_to_log(line, log, norm_flux):
|
|
|
569
569
|
|
|
570
570
|
# Converting None entries to str (9 = group_label)
|
|
571
571
|
if j == 9:
|
|
572
|
+
|
|
572
573
|
if param_value is None:
|
|
573
574
|
param_value = 'none'
|
|
574
575
|
|
|
575
|
-
|
|
576
|
+
if line.sub_comps[i] is not None:
|
|
577
|
+
param_value = line.sub_comps[i].group_label
|
|
578
|
+
|
|
576
579
|
log.at[comp, param] = param_value
|
|
577
580
|
|
|
578
581
|
return
|
|
@@ -411,7 +411,8 @@ class Spectrum:
|
|
|
411
411
|
return spec
|
|
412
412
|
|
|
413
413
|
@classmethod
|
|
414
|
-
def from_file(cls, file_address, instrument,
|
|
414
|
+
def from_file(cls, file_address, instrument, redshift=None, norm_flux=None, crop_waves=None, res_power=None,
|
|
415
|
+
units_wave=None, units_flux=None, pixel_mask=None, id_label=None, wcs=None, **kwargs):
|
|
415
416
|
|
|
416
417
|
"""
|
|
417
418
|
|
|
@@ -446,13 +447,19 @@ class Spectrum:
|
|
|
446
447
|
cls._fitsMgr = OpenFits(file_address, instrument, cls.__name__)
|
|
447
448
|
|
|
448
449
|
# Load the scientific data from the file
|
|
449
|
-
fits_args = cls._fitsMgr.parse_data_from_file(cls._fitsMgr.file_address,
|
|
450
|
+
fits_args = cls._fitsMgr.parse_data_from_file(cls._fitsMgr.file_address, pixel_mask, **kwargs)
|
|
450
451
|
|
|
451
|
-
# Update the
|
|
452
|
-
|
|
452
|
+
# Update the file parameters with the user parameters
|
|
453
|
+
input_args = dict(redshift=redshift, norm_flux=norm_flux, crop_waves=crop_waves, res_power=res_power,
|
|
454
|
+
units_wave=units_wave, units_flux=units_flux, id_label=id_label, wcs=wcs)
|
|
455
|
+
|
|
456
|
+
if cls._fitsMgr.spectrum_check:
|
|
457
|
+
input_args.pop('wcs')
|
|
458
|
+
|
|
459
|
+
input_args = {**fits_args, **{k: v for k, v in input_args.items() if v is not None}}
|
|
453
460
|
|
|
454
461
|
# Create the LiMe object
|
|
455
|
-
return cls(**
|
|
462
|
+
return cls(**input_args)
|
|
456
463
|
|
|
457
464
|
@classmethod
|
|
458
465
|
def from_survey(cls, target_id, survey, mask_flux_entries=None, **kwargs):
|
|
@@ -657,9 +657,10 @@ def redshift_permu_evaluation(spectrum, z_infered, obs_wave_arr, theo_wave_arr,
|
|
|
657
657
|
return
|
|
658
658
|
|
|
659
659
|
|
|
660
|
-
def bands_filling_plot(axis, x, y, z_corr, idcs_mask, label, exclude_continua=
|
|
660
|
+
def bands_filling_plot(axis, x, y, z_corr, idcs_mask, label, exclude_continua=True, color_dict=theme.colors, show_central=True):
|
|
661
661
|
|
|
662
662
|
# Security check for low selection
|
|
663
|
+
# TODO check this error crashing
|
|
663
664
|
if y[idcs_mask[2]:idcs_mask[3]].size > 1:
|
|
664
665
|
|
|
665
666
|
# Lower limit for the filled region
|
|
@@ -768,8 +769,8 @@ class Plotter:
|
|
|
768
769
|
def _line_matching_plot(self, axis, bands, x, y, z_corr, redshift):
|
|
769
770
|
|
|
770
771
|
# Open the bands file the bands
|
|
771
|
-
match_log =
|
|
772
|
-
|
|
772
|
+
match_log = check_file_dataframe(bands)
|
|
773
|
+
|
|
773
774
|
# Compute bands limits
|
|
774
775
|
w3 = match_log.w3.values * (1 + redshift)
|
|
775
776
|
w4 = match_log.w4.values * (1 + redshift)
|
|
@@ -1008,43 +1009,6 @@ class SpectrumFigures(Plotter):
|
|
|
1008
1009
|
in_ax.fill_between(wave_plot/z_corr, low_limit*z_corr, high_limit*z_corr, alpha=0.2,
|
|
1009
1010
|
color=theme.colors['fade_fg'])
|
|
1010
1011
|
|
|
1011
|
-
# # Include the detection bands
|
|
1012
|
-
# if detection_band is not None:
|
|
1013
|
-
#
|
|
1014
|
-
# detec_obj = getattr(self._spec.infer, detection_band)
|
|
1015
|
-
#
|
|
1016
|
-
# if detec_obj.confidence is not None:
|
|
1017
|
-
#
|
|
1018
|
-
# # Boundaries array for confidence intervals
|
|
1019
|
-
# bounds = np.array([0.0, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])
|
|
1020
|
-
#
|
|
1021
|
-
# # Adjust color map to match lower detection limit to fg color
|
|
1022
|
-
# cmap = plt.get_cmap(theme.colors['mask_map'])
|
|
1023
|
-
# cmaplist = [cmap(i) for i in range(cmap.N)]
|
|
1024
|
-
# cmaplist[0] = theme.colors['fg']
|
|
1025
|
-
# cmap = colors.LinearSegmentedColormap.from_list('mcm', cmaplist, bounds.size-1)
|
|
1026
|
-
# norm = colors.BoundaryNorm(bounds * 100, cmap.N)
|
|
1027
|
-
#
|
|
1028
|
-
# # Iterate through the confidence intervals and plot the step spectrum
|
|
1029
|
-
# for i in range(1, len(bounds)):
|
|
1030
|
-
# if i > 1:
|
|
1031
|
-
# idcs = detec_obj(bounds[i-1]*100, confidence_max=bounds[i]*100)
|
|
1032
|
-
# wave_nan, flux_nan = np.full(wave_plot.size, np.nan), np.full(flux_plot.size, np.nan)
|
|
1033
|
-
# wave_nan[idcs], flux_nan[idcs] = wave_plot[idcs] / z_corr, flux_plot[idcs] * z_corr
|
|
1034
|
-
#
|
|
1035
|
-
# in_ax.step(wave_nan, flux_nan, label=label, where='mid', color=cmap(i-1))
|
|
1036
|
-
#
|
|
1037
|
-
# # Color bar
|
|
1038
|
-
# sm = cm.ScalarMappable(cmap=cmap, norm=norm)
|
|
1039
|
-
# sm.set_array([])
|
|
1040
|
-
# cbar = plt.colorbar(sm, ax=in_ax)
|
|
1041
|
-
# cbar.set_label('Detection confidence %', rotation=270, labelpad=35)
|
|
1042
|
-
#
|
|
1043
|
-
#
|
|
1044
|
-
# else:
|
|
1045
|
-
# _logger.warning(f'The line detection bands confidence has not been calculated. They are not included'
|
|
1046
|
-
# f' on plot.')
|
|
1047
|
-
|
|
1048
1012
|
# Show components
|
|
1049
1013
|
if show_categories and self._spec.infer.pred_arr is not None:
|
|
1050
1014
|
|
|
@@ -1536,7 +1536,7 @@ class CubeInspection:
|
|
|
1536
1536
|
self._ax0.set_ylim(self.axlim_dict['image_ylim'])
|
|
1537
1537
|
self._ax1.set_xlim(self.axlim_dict['spec_xlim'])
|
|
1538
1538
|
|
|
1539
|
-
if
|
|
1539
|
+
if self.maintain_y_zoom:
|
|
1540
1540
|
self._ax1.set_ylim(self.axlim_dict['spec_ylim'])
|
|
1541
1541
|
|
|
1542
1542
|
else:
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from lime.io import LiMe_Error
|
|
5
|
+
from lime.transitions import Line
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
_logger = logging.getLogger('LiMe')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def pars_bands_conf(spec, bands, fit_conf, composite_lines, automatic_grouping=True):
|
|
12
|
+
|
|
13
|
+
# Use the input groups
|
|
14
|
+
if automatic_grouping is False:
|
|
15
|
+
|
|
16
|
+
# Get the the grouped lines
|
|
17
|
+
groups_dict = {} if fit_conf is False else {comp: group_label
|
|
18
|
+
for comp, group_label in fit_conf.items()
|
|
19
|
+
if comp.endswith(('_b', '_m'))}
|
|
20
|
+
|
|
21
|
+
# Limit the selection to the user lines
|
|
22
|
+
if composite_lines is not None:
|
|
23
|
+
groups_dict = {line: comps for line, comps in groups_dict.items() if line in composite_lines}
|
|
24
|
+
|
|
25
|
+
# Automatic group review
|
|
26
|
+
else:
|
|
27
|
+
|
|
28
|
+
# Check the input dataframe is sorted
|
|
29
|
+
if not np.all(np.diff(bands.wavelength) >= 0):
|
|
30
|
+
_logger.warning(f'The input bands table is not sorted. This can cause issues in the bands generation:'
|
|
31
|
+
f'\n{bands["wavelength"]}')
|
|
32
|
+
|
|
33
|
+
# Get list all the line groups and their lines
|
|
34
|
+
if fit_conf:
|
|
35
|
+
|
|
36
|
+
line_list, group_lines = [], []
|
|
37
|
+
group_names, group_blended_check = [], []
|
|
38
|
+
for comp, group_label in fit_conf.items():
|
|
39
|
+
if comp.endswith(('_b', '_m')):
|
|
40
|
+
|
|
41
|
+
# Homogeneous group
|
|
42
|
+
if '_m' not in group_label:
|
|
43
|
+
lines_i = group_label.split('+')
|
|
44
|
+
groups_i = [True] * (len(lines_i) - 1) if comp[-2:] == '_b' else [False] * (len(lines_i) - 1)
|
|
45
|
+
|
|
46
|
+
# Mixed group (merged child line in the blended parent group)
|
|
47
|
+
else:
|
|
48
|
+
lines_i, groups_i = [], []
|
|
49
|
+
for i, line in enumerate(group_label.split('+')):
|
|
50
|
+
if line[-2:] != '_m': # Single line
|
|
51
|
+
lines_i.append(line)
|
|
52
|
+
groups_i.append(True)
|
|
53
|
+
else: # Merged line
|
|
54
|
+
sub_group_label = fit_conf.get(line)
|
|
55
|
+
if sub_group_label:
|
|
56
|
+
items = sub_group_label.split('+')
|
|
57
|
+
lines_i += items
|
|
58
|
+
groups_i += [False] * len(items)
|
|
59
|
+
else:
|
|
60
|
+
raise LiMe_Error(f'The merged line: "{line}" in grouped line: "{comp}={group_label}" '
|
|
61
|
+
f'is not specified.\nPlease define a "{line}=LineA+LineB" '
|
|
62
|
+
f'in your configuration file.')
|
|
63
|
+
|
|
64
|
+
# Convert the sub_group_type to the relation
|
|
65
|
+
groups_i = np.array(groups_i)
|
|
66
|
+
groups_i = groups_i[:-1] != groups_i[1:]
|
|
67
|
+
|
|
68
|
+
# Add the group is all lines (sorted) in current wavelength range
|
|
69
|
+
idcs_i = bands.index.get_indexer(lines_i)
|
|
70
|
+
if np.all(idcs_i > -1):
|
|
71
|
+
group_names.append(comp)
|
|
72
|
+
group_lines.append(bands.loc[bands.index.isin(lines_i)].index.to_numpy())
|
|
73
|
+
group_blended_check.append(groups_i)
|
|
74
|
+
line_list += lines_i
|
|
75
|
+
|
|
76
|
+
# Sort the input lines using line banbs table
|
|
77
|
+
line_list = bands.loc[bands.index.isin(line_list)].index
|
|
78
|
+
sub_bands = bands.loc[line_list]
|
|
79
|
+
lambda_arr = sub_bands['wavelength'].to_numpy()
|
|
80
|
+
|
|
81
|
+
# Array to keep track of lines which have been assigned:
|
|
82
|
+
assigned_lines = np.zeros(line_list.size).astype(bool)
|
|
83
|
+
|
|
84
|
+
# Compare the observed line groups
|
|
85
|
+
groups_dict = {}
|
|
86
|
+
if line_list.size > 1:
|
|
87
|
+
|
|
88
|
+
# Get limits of the bands on the spectrum wavelength range
|
|
89
|
+
w3_arr = np.searchsorted(spec.wave_rest.data, bands.loc[line_list, 'w3'].to_numpy())
|
|
90
|
+
w4_arr = np.searchsorted(spec.wave_rest.data, bands.loc[line_list, 'w4'].to_numpy())
|
|
91
|
+
|
|
92
|
+
# Generate binary matrix with the line bands location
|
|
93
|
+
wave_matrix = np.zeros((lambda_arr.size, spec.wave_rest.data.size))
|
|
94
|
+
cols = np.arange(wave_matrix.shape[1])
|
|
95
|
+
wave_matrix[(cols >= w3_arr[:, None]) & (cols <= w4_arr[:, None])] = 1
|
|
96
|
+
|
|
97
|
+
# Compute the decision matrix with the common pixels
|
|
98
|
+
decision_matrix = wave_matrix @ wave_matrix.T
|
|
99
|
+
|
|
100
|
+
# pixels_width = wave_matrix.sum(axis=1)
|
|
101
|
+
# blended_matrix = decision_matrix < np.ceil(pixels_width/3)[:, None]
|
|
102
|
+
# math_dict = dict(zip(line_arr, np.arange(line_arr.size)))
|
|
103
|
+
|
|
104
|
+
# Loop through the input groups to confirm the best match
|
|
105
|
+
for i, group in enumerate(group_names):
|
|
106
|
+
|
|
107
|
+
# Diagnostic to establish the relation between the lines
|
|
108
|
+
threshold = 2
|
|
109
|
+
w3_arr = np.searchsorted(spec.wave_rest.data, bands.loc[group_lines[i], 'w3'].to_numpy())
|
|
110
|
+
w4_arr = np.searchsorted(spec.wave_rest.data, bands.loc[group_lines[i], 'w4'].to_numpy())
|
|
111
|
+
mu = (w3_arr + w4_arr) / 2
|
|
112
|
+
sigma = (w4_arr - w3_arr) / 6
|
|
113
|
+
delta_mu = np.diff(mu)
|
|
114
|
+
sigma_avg = np.sqrt((sigma[:-1] ** 2 + sigma[1:] ** 2) / 2)
|
|
115
|
+
R = delta_mu / sigma_avg
|
|
116
|
+
resolvable = R > threshold
|
|
117
|
+
|
|
118
|
+
match_group = True if np.all(resolvable == group_blended_check[i]) else False
|
|
119
|
+
|
|
120
|
+
# Before saving group check that there are no more lines grouped in the observation
|
|
121
|
+
if match_group:
|
|
122
|
+
idcs_i = sub_bands.index.get_indexer(group_lines[i])
|
|
123
|
+
if np.all(np.sum(decision_matrix[idcs_i, :] > 0, axis=1) == idcs_i.size):
|
|
124
|
+
groups_dict[group] = fit_conf[group]
|
|
125
|
+
assigned_lines[idcs_i] = True
|
|
126
|
+
|
|
127
|
+
# Invalid
|
|
128
|
+
else:
|
|
129
|
+
_logger.warning(f'The user requested automatic_grouping for the line transitions but the "fit_conf" is empty')
|
|
130
|
+
|
|
131
|
+
# Applyt the requested group changes
|
|
132
|
+
rename_dict, exclude_list= {}, []
|
|
133
|
+
group_dict, w3_dict, w4_dict = {}, {}, {}
|
|
134
|
+
for new_label, group_label in groups_dict.items():
|
|
135
|
+
|
|
136
|
+
component_list = np.unique([Line(x).core for x in group_lines[group_names.index(new_label)] if '_k-' not in x])
|
|
137
|
+
old_label = component_list[0]
|
|
138
|
+
|
|
139
|
+
# Only apply corrections if components are present
|
|
140
|
+
idcs_comps = bands.index.isin(component_list)
|
|
141
|
+
if np.sum(idcs_comps) == component_list.size:
|
|
142
|
+
|
|
143
|
+
# Save the modifications
|
|
144
|
+
rename_dict[old_label] = new_label
|
|
145
|
+
exclude_list += list(component_list)
|
|
146
|
+
w3_dict[new_label] = bands.loc[idcs_comps, 'w3'].min()
|
|
147
|
+
w4_dict[new_label] = bands.loc[idcs_comps, 'w4'].max()
|
|
148
|
+
group_dict[new_label] = group_label
|
|
149
|
+
|
|
150
|
+
# Check the line or the same group is already there
|
|
151
|
+
else:
|
|
152
|
+
low, high = spec.wave_rest.compressed()[[0, -1]]
|
|
153
|
+
check_arr = np.array([(low < Line(label).wavelength < high)[0] for label in component_list])
|
|
154
|
+
|
|
155
|
+
# Lines outside wavelength range
|
|
156
|
+
if np.all(~check_arr):
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
# Check if lines outside range
|
|
160
|
+
else:
|
|
161
|
+
if 'group_label' in bands.columns:
|
|
162
|
+
if not np.any(groups_dict[new_label] == bands.group_label):
|
|
163
|
+
_logger.info(f'Line component "{old_label}" for configuration entry: '
|
|
164
|
+
f'"{new_label}={groups_dict[new_label]}" not found in lines table')
|
|
165
|
+
else:
|
|
166
|
+
_logger.info(f'Missing line(s) "{np.setxor1d(bands.loc[idcs_comps].index.to_numpy(), component_list)}" '
|
|
167
|
+
f'for configuration entry: '
|
|
168
|
+
f'"{new_label}={groups_dict[new_label]}" in reference lines table')
|
|
169
|
+
|
|
170
|
+
# Warn in case some of the bands dont match the database:
|
|
171
|
+
if not set(exclude_list).issubset(bands.index):
|
|
172
|
+
_logger.info(f' The following blended or merged lines were not found on the input lines database:\n'
|
|
173
|
+
f' - {list(set(exclude_list) - set(bands.index))}\n'
|
|
174
|
+
f' - It is recommended that the merged/blended components follow the reference transitions labels.\n')
|
|
175
|
+
|
|
176
|
+
# Change the latex labels
|
|
177
|
+
for old_label, new_label in rename_dict.items():
|
|
178
|
+
line = Line(new_label, band=bands, fit_conf=groups_dict, update_latex=True)
|
|
179
|
+
bands.loc[old_label, 'latex_label'] = line.latex_label[0] if line.merged_check else '+'.join(line.latex_label)
|
|
180
|
+
|
|
181
|
+
# Change the indexes
|
|
182
|
+
bands.rename(index=dict(rename_dict), inplace=True)
|
|
183
|
+
|
|
184
|
+
# Remove components columns
|
|
185
|
+
bands.drop(exclude_list, errors='ignore', inplace=True)
|
|
186
|
+
|
|
187
|
+
# Add the group_label values
|
|
188
|
+
if 'group_label' not in bands.columns:
|
|
189
|
+
bands['group_label'] = 'none'
|
|
190
|
+
bands['group_label'] = pd.Series(bands.index.map(group_dict), index=bands.index).fillna(bands['group_label'])
|
|
191
|
+
|
|
192
|
+
# Change velocity limits
|
|
193
|
+
bands['w3'] = pd.Series(bands.index.map(w3_dict), index=bands.index).fillna(bands['w3'])
|
|
194
|
+
bands['w4'] = pd.Series(bands.index.map(w4_dict), index=bands.index).fillna(bands['w4'])
|
|
195
|
+
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
# if np.all(idcs_i > -1):
|
|
199
|
+
|
|
200
|
+
# # Logic for single, merged and blended lines
|
|
201
|
+
# shared_pixels = decision_matrix[idcs_i[:-1], idcs_i[1:]]
|
|
202
|
+
# if np.any(shared_pixels > 0):
|
|
203
|
+
# match_group =False
|
|
204
|
+
# # line_pixels = np.max(pixels_width[idcs_i])
|
|
205
|
+
# # diag_arr = resolvable
|
|
206
|
+
# #
|
|
207
|
+
# # obs_type = ['_b'] if np.all(diag_arr) else ['_m'] if np.all(~diag_arr) else None
|
|
208
|
+
# else:
|
|
209
|
+
# match_group = False
|
|
210
|
+
|
|
211
|
+
# Compare observed group versus user group
|
|
212
|
+
|
|
213
|
+
# else:
|
|
214
|
+
#
|
|
215
|
+
# # Lines not assigned before:
|
|
216
|
+
# if np.all(assigned_lines[idcs_i] == False):
|
|
217
|
+
#
|
|
218
|
+
# # Group consists in blended merged lines: Assigned single merged
|
|
219
|
+
# if (group_name[-2:] == '_m') and np.any(diag_arr[:-1] & diag_arr[1:]):
|
|
220
|
+
# output_groups[group_name] = group_label
|
|
221
|
+
# assigned_lines[idcs_i] = True
|
|
222
|
+
# # Get groups of common entries
|
|
223
|
+
# from scipy.sparse import csr_matrix, csgraph
|
|
224
|
+
# _, auto_labels = csgraph.connected_components(csgraph=csr_matrix(decision_matrix > 1), directed=False)
|
|
225
|
+
# for labels, group in zip(line_arr, auto_labels):
|
|
226
|
+
# print(f"{labels} {group}")
|