lime-stable 2.0.dev2__tar.gz → 2.0.dev4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {lime_stable-2.0.dev2/src/lime_stable.egg-info → lime_stable-2.0.dev4}/PKG-INFO +2 -2
  2. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/pyproject.toml +2 -2
  3. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/archives/read_fits.py +2 -1
  4. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/archives/tables.py +3 -0
  5. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/changelog.txt +5 -1
  6. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/fitting/lines.py +3 -1
  7. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/fitting/redshift.py +3 -0
  8. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/io.py +18 -20
  9. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/lime.toml +1 -1
  10. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/observations.py +3 -1
  11. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/plotting/bokeh_plots.py +9 -1
  12. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/plotting/plots.py +23 -8
  13. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/plotting/plots_interactive.py +75 -65
  14. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/plotting/theme_lime.toml +3 -1
  15. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/transitions.py +77 -70
  16. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/workflow.py +250 -184
  17. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4/src/lime_stable.egg-info}/PKG-INFO +2 -2
  18. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime_stable.egg-info/requires.txt +1 -1
  19. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/tests/test_cube.py +2 -2
  20. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/tests/test_io.py +46 -0
  21. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/tests/test_spectrum.py +49 -5
  22. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/tests/test_tools.py +102 -4
  23. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/LICENSE.rst +0 -0
  24. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/MANIFEST.in +0 -0
  25. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/README.rst +0 -0
  26. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/setup.cfg +0 -0
  27. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/__init__.py +0 -0
  28. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/archives/__init__.py +0 -0
  29. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/fitting/__init__.py +0 -0
  30. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/inference/detection.py +0 -0
  31. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/inference/intensity_threshold.py +0 -0
  32. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/plotting/__init__.py +0 -0
  33. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/plotting/format.py +0 -0
  34. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/plotting/utils.py +0 -0
  35. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/resources/__init__.py +0 -0
  36. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/resources/lines_database_formatting.py +0 -0
  37. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/resources/lines_database_v2.0.0.txt +0 -0
  38. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/resources/logo.py +0 -0
  39. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/resources/types_params.txt +0 -0
  40. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/retrieve/__init__.py +0 -0
  41. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/retrieve/peaks.py +0 -0
  42. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime/tools.py +0 -0
  43. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime_stable.egg-info/SOURCES.txt +0 -0
  44. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime_stable.egg-info/dependency_links.txt +0 -0
  45. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/src/lime_stable.egg-info/top_level.txt +0 -0
  46. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/tests/test_astro.py +0 -0
  47. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/tests/test_line.py +0 -0
  48. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/tests/test_model.py +0 -0
  49. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/tests/test_read_fits.py +0 -0
  50. {lime_stable-2.0.dev2 → lime_stable-2.0.dev4}/tests/test_sample.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lime-stable
3
- Version: 2.0.dev2
3
+ Version: 2.0.dev4
4
4
  Summary: Line measuring algorithm for astronomical spectra
5
5
  Author-email: Vital Fernández <vgf@umich.edu>
6
6
  License-Expression: GPL-3.0-or-later
@@ -15,10 +15,10 @@ Requires-Dist: matplotlib~=3.10
15
15
  Requires-Dist: numpy~=2.2
16
16
  Requires-Dist: pandas~=2.2
17
17
  Requires-Dist: scipy~=1.15
18
+ Requires-Dist: aspect-stable~=0.3.1
18
19
  Requires-Dist: tomli>=2.0.0; python_version < "3.11"
19
20
  Provides-Extra: full
20
21
  Requires-Dist: asdf~=4.1; extra == "full"
21
- Requires-Dist: aspect-stable~=0.3.0; extra == "full"
22
22
  Requires-Dist: bokeh~=3.6; 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.0.dev2"
7
+ version = "2.0.dev4"
8
8
  readme = "README.rst"
9
9
  requires-python = ">=3.11"
10
10
  license = "GPL-3.0-or-later"
@@ -17,6 +17,7 @@ dependencies = ["astropy~=7.0",
17
17
  "numpy~=2.2",
18
18
  "pandas~=2.2",
19
19
  "scipy~=1.15",
20
+ "aspect-stable~=0.3.1",
20
21
  "tomli >= 2.0.0 ; python_version < '3.11'"]
21
22
 
22
23
  classifiers = ["Programming Language :: Python :: 3",
@@ -24,7 +25,6 @@ classifiers = ["Programming Language :: Python :: 3",
24
25
 
25
26
  [project.optional-dependencies]
26
27
  full = ["asdf~=4.1",
27
- "aspect-stable~=0.3.0",
28
28
  "bokeh~=3.6",
29
29
  "mplcursors~=0.6",
30
30
  "openpyxl~=3.1",
@@ -4,6 +4,7 @@ import numpy as np
4
4
  from astropy.io import fits
5
5
  from astropy.wcs import WCS
6
6
  import logging
7
+ from io import IOBase
7
8
 
8
9
  from lime.io import LiMe_Error
9
10
  from urllib.parse import urlparse
@@ -180,7 +181,7 @@ def check_fits_location(fits_address, lime_object=None, source=None):
180
181
  output = None, False
181
182
 
182
183
  # Streamlit BytesIO input
183
- elif type(fits_address).__name__ == 'UploadedFile':
184
+ elif (type(fits_address).__name__ == 'UploadedFile') or isinstance(fits_address, IOBase):
184
185
  output = fits_address, False
185
186
 
186
187
  # File address or url
@@ -122,6 +122,9 @@ def table_fluxes(lines_df, table_address, header_format_latex, table_type='pdf',
122
122
  except:
123
123
  print('\n-- PDF compilation failure')
124
124
 
125
+ elif table_type == 'tex':
126
+ pdf.pdfDoc.generate_tex(table_address.as_posix())
127
+
125
128
  return
126
129
 
127
130
 
@@ -105,4 +105,8 @@ LiMe Mayor update - 2.0.0 - XX/XX/XXXX
105
105
  - The central wavelenght bands are now calculated assuming a bands_vsigma=70 and n_sigma=4
106
106
  - The function lime.Spectrum.retrieve.spectrum() returns the spectrum axes as a recarray or saves it into a text file if the user provides a file address.
107
107
  - lime.Spectrum.from_file("fname", instrument='text') can now read the file from a text file following the format from the lime.Spectrum.retrieve.spectrum() function.
108
- - Bug the .plot.spectrum(include_fittings) does not show the combined profile
108
+ - Bug the .plot.spectrum(include_fittings) does not show the combined profile
109
+ - Rename term "_conf" to "_cfg" across function names and arguments for uniformity
110
+ - Fitting functions now have the update_default=True argument. In the default behaviour the default configuration updates the default one. If set to update_default=False only the obj_cfg is used if available else the default one.
111
+ - Creating a spectrum object where all the entries are masked now produces a critical warning instead of raising an error.
112
+ - By default line_bands
@@ -418,7 +418,9 @@ def sigma_corrections(line, idcs_line, wave_arr, R_arr, temperature):
418
418
  if np.isscalar(R_arr):
419
419
  line.sigma_instr = np.mean(wave_arr.compressed() / (R_arr * k_gFWHM))
420
420
  else:
421
- line.sigma_instr = np.mean(wave_arr[idcs_line].compressed() / (R_arr[idcs_line] * k_gFWHM))
421
+ mask_data = ~wave_arr.mask
422
+ line.sigma_instr = np.mean(wave_arr[mask_data] / (R_arr[idcs_line][mask_data] * k_gFWHM))
423
+ wave_arr[mask_data] / (R_arr[idcs_line][mask_data] * k_gFWHM)
422
424
  else:
423
425
  line.sigma_instr = np.nan
424
426
 
@@ -283,6 +283,9 @@ class RedshiftFitting:
283
283
  else:
284
284
  pred_arr, conf_arr = self._spec.infer.pred_arr, self._spec.infer.conf_arr
285
285
 
286
+ # Resolving power # TODO this should be read at another point...
287
+ res_power = self._spec.res_power if res_power is None else res_power
288
+
286
289
  # Set the type of fitting and the components to use
287
290
  if mode == 'key':
288
291
  components = components if components is not None else ['emission', 'doublet']
@@ -446,8 +446,8 @@ def save_frame(fname, dataframe, page='FRAME', parameters='all', header=None, co
446
446
  output_file.write(string_DF.encode('UTF-8'))
447
447
 
448
448
  # Pdf fluxes table
449
- elif file_type == '.pdf':
450
- table_fluxes(lines_log, log_path.parent / log_path.stem, header_format_latex=_LOG_COLUMNS_LATEX,
449
+ elif file_type == '.pdf' or file_type == '.tex':
450
+ table_fluxes(lines_log, log_path.parent / log_path.stem, table_type=file_type[1:], header_format_latex=_LOG_COLUMNS_LATEX,
451
451
  lines_notation=log.latex_label.values, **kwargs)
452
452
 
453
453
  # Log in a fits format
@@ -601,7 +601,8 @@ def check_file_dataframe(df_variable, variable_type=pd.DataFrame, ext='FRAME', s
601
601
  return output
602
602
 
603
603
 
604
- def check_fit_conf(fit_conf, default_key, group_key, group_list=None, fit_cfg_suffix='_line_fitting', line_detection=False):
604
+ def check_fit_conf(fit_conf, default_key, obj_key, update_default=True, group_list=None, fit_cfg_suffix='_line_fitting',
605
+ line_detection=False):
605
606
 
606
607
  # Check that there is an input configuration
607
608
  if fit_conf is not None:
@@ -634,33 +635,30 @@ def check_fit_conf(fit_conf, default_key, group_key, group_list=None, fit_cfg_su
634
635
 
635
636
  # Recover the configuration expected for the object
636
637
  default_cfg = input_cfg.get(f'{default_key}_line_fitting') if default_key is not None else None
637
- mask_cfg = input_cfg.get(f'{group_key}_line_fitting') if group_key is not None else None
638
+ custom_cfg = input_cfg.get(f'{obj_key}_line_fitting') if obj_key is not None else None
638
639
 
639
- # Case there are not leveled entries
640
- if (default_cfg is None) and (mask_cfg is None):
640
+ # Case there are not level entries
641
+ if (default_cfg is None) and (custom_cfg is None):
641
642
  output_cfg = input_cfg
642
643
 
643
644
  # Proceed to update the levels
644
645
  else:
645
646
 
646
647
  # Default configuration
647
- output_cfg = {} if default_cfg is None else default_cfg
648
- default_detect = output_cfg.get('line_detection')
648
+ default_cfg = {} if default_cfg is None else default_cfg
649
+ default_detect = default_cfg.get('line_detection', {})
649
650
 
650
- # Mask conf
651
- mask_conf = {} if mask_cfg is None else mask_cfg
652
- mask_detect = mask_conf.get('line_detection')
651
+ # Custom configuration
652
+ custom_cfg = {} if custom_cfg is None else custom_cfg
653
+ custom_detect = custom_cfg.get('line_detection', {})
653
654
 
654
- # Update the levels
655
- output_cfg = {**output_cfg, **mask_conf}
655
+ # Update default configuration if requested else use only custom
656
+ output_cfg = {**default_cfg, **custom_cfg} if update_default else (custom_cfg if custom_cfg else default_cfg)
656
657
 
657
- # If no line detection don't add it # TODO this is wrong lower should update upper
658
- if mask_detect is not None:
659
- output_cfg['line_detection'] = mask_detect
660
- elif default_detect is not None:
661
- output_cfg['line_detection'] = default_detect
662
- else:
663
- pass
658
+ # Update default detection if requested else use only custom
659
+ if line_detection:
660
+ output_cfg['line_detection'] = default_detect.update(custom_detect) if update_default else\
661
+ (custom_detect if custom_detect else default_detect)
664
662
 
665
663
  else:
666
664
  output_cfg = {}
@@ -1,3 +1,3 @@
1
1
  [metadata]
2
2
  name = 'lime-stable'
3
- version = "2.0.dev2"
3
+ version = "2.0.dev4"
@@ -142,7 +142,7 @@ def check_inputs_arrays(wave, flux, err_flux, pixel_mask, lime_object):
142
142
  # Check not all the pixels are masked
143
143
  if mask_check:
144
144
  if np.all(output_pixel_mask):
145
- raise LiMe_Error(f'All the input observation pixels are masked. Please check that only bad pixels entries'
145
+ _logger.critical(f'All the input observation pixels are masked. Please check that only bad pixels entries'
146
146
  f' are masked (in numpy arrays flux_arr[pixel_mask] = bad_entries)')
147
147
 
148
148
  return output_pixel_mask
@@ -1347,6 +1347,8 @@ class Sample(UserDict, OpenFits):
1347
1347
  else:
1348
1348
  obs = load_function_output
1349
1349
 
1350
+
1351
+
1350
1352
  return obs
1351
1353
 
1352
1354
  def __getitem__(self, id_key):
@@ -270,7 +270,7 @@ class BokehFigures:
270
270
 
271
271
  def spectrum(self, output_address=None, label=None, bands=None, rest_frame=False, log_scale=False,
272
272
  include_fits=True, include_cont=False, include_components=False, return_fig=False, fig_cfg=None, ax_cfg=None, maximize=False,
273
- detection_band=None, show_masks=True, show_categories=False):
273
+ detection_band=None, show_masks=True, show_categories=False, show_err=False):
274
274
 
275
275
 
276
276
  # Set figure format with the user inputs overwriting the default conf
@@ -298,6 +298,14 @@ class BokehFigures:
298
298
  if bands is not None:
299
299
  bokeh_bands(fig, bands, wave_plot, flux_plot, z_corr, self._spec.redshift)
300
300
 
301
+ # Show uncertainty
302
+ if show_err and (self._spec.err_flux is not None):
303
+ err_plot = self._spec.err_flux.data
304
+ fig.varea_step(x=wave_plot / z_corr,
305
+ y1=(flux_plot - err_plot) * z_corr,
306
+ y2=(flux_plot + err_plot) * z_corr,
307
+ step_mode="center", fill_alpha=0.2, color=theme.colors['err_area'])
308
+
301
309
  # Include the continuum
302
310
  if include_cont and self._spec.cont is not None:
303
311
  fig.line(wave_plot/z_corr, self._spec.cont*z_corr, legend_label="Continuum.",
@@ -654,7 +654,7 @@ def redshift_permu_evaluation(spectrum, z_infered, obs_wave_arr, theo_wave_arr,
654
654
  return
655
655
 
656
656
 
657
- def bands_filling_plot(axis, x, y, z_corr, idcs_mask, label, exclude_continua=False, color_dict=theme.colors):
657
+ def bands_filling_plot(axis, x, y, z_corr, idcs_mask, label, exclude_continua=False, color_dict=theme.colors, show_central=True):
658
658
 
659
659
  # Security check for low selection
660
660
  if len(x[idcs_mask[2]:idcs_mask[3]]) > 1:
@@ -673,8 +673,9 @@ def bands_filling_plot(axis, x, y, z_corr, idcs_mask, label, exclude_continua=Fa
673
673
  low_lim = m * x_interval + n
674
674
 
675
675
  # Central bands
676
- axis.fill_between(x_interval/z_corr, low_lim*z_corr, y_interval*z_corr,
677
- facecolor=color_dict['line_band'], step='mid', alpha=0.25)
676
+ if show_central:
677
+ axis.fill_between(x_interval/z_corr, low_lim*z_corr, y_interval*z_corr,
678
+ facecolor=color_dict['line_band'], step='mid', alpha=0.25)
678
679
 
679
680
  # Continua bands exclusion
680
681
  if exclude_continua is False:
@@ -767,8 +768,8 @@ class Plotter:
767
768
  def _line_matching_plot(self, axis, bands, x, y, z_corr, redshift):
768
769
 
769
770
  # Open the bands file the bands
770
- match_log = self._spec.retrieve.line_bands(ref_bands=bands, fit_conf=None, instrumental_correction=False,
771
- adjust_central_bands=False)
771
+ match_log = self._spec.retrieve.line_bands(ref_bands=bands, fit_cfg=None, instrumental_correction=False,
772
+ adjust_central_band=False)
772
773
  # Compute bands limits
773
774
  w3 = match_log.w3.values * (1 + redshift)
774
775
  w4 = match_log.w4.values * (1 + redshift)
@@ -782,6 +783,7 @@ class Plotter:
782
783
 
783
784
  # Loop through the detections and plot the names
784
785
  for i, line_label in enumerate(match_log.index):
786
+ print(line_label)
785
787
  line = Line(line_label, match_log)
786
788
 
787
789
  # Get the max flux on the region making the exception for 1 pixel bands
@@ -881,7 +883,7 @@ class SpectrumFigures(Plotter):
881
883
 
882
884
  """
883
885
 
884
- This function plots the spectrum flux versus wavelength.
886
+ This function plots the spectrum flux versus wavelength.z
885
887
 
886
888
  The user can include the line bands on the plot if added via the ``line_bands`` attribute.
887
889
 
@@ -1274,7 +1276,7 @@ class SpectrumFigures(Plotter):
1274
1276
  return in_fig
1275
1277
 
1276
1278
  def bands(self, label=None, output_address=None, ref_bands=None, include_fits=True, rest_frame=False, y_scale='auto', fig_cfg=None,
1277
- ax_cfg=None, in_fig=None, maximize=False):
1279
+ ax_cfg=None, in_fig=None, maximize=False, show_err=True):
1278
1280
 
1279
1281
  """
1280
1282
 
@@ -1333,6 +1335,12 @@ class SpectrumFigures(Plotter):
1333
1335
  # Check which line should be plotted
1334
1336
  line = parse_bands_arguments(label, log, ref_bands, norm_flux)
1335
1337
 
1338
+ # Check the observation has uncertainty to display
1339
+ if show_err and (self._spec.err_flux is None):
1340
+ if self._spec.err_flux is None:
1341
+ _logger.info('Input observation does not include uncertainty to display')
1342
+ show_err = False
1343
+
1336
1344
  # Proceed to plot
1337
1345
  if line is not None:
1338
1346
 
@@ -1375,7 +1383,14 @@ class SpectrumFigures(Plotter):
1375
1383
  where='mid', color=theme.colors['fg'], label=label_leg, linewidth=theme.plt['spectrum_width'])
1376
1384
 
1377
1385
  # Continuum bands
1378
- bands_filling_plot(in_ax[0], wave_plot, flux_plot, z_corr, idcs_bands, line, color_dict=theme.colors)
1386
+ bands_filling_plot(in_ax[0], wave_plot, flux_plot, z_corr, idcs_bands, line, color_dict=theme.colors,
1387
+ show_central=not show_err)
1388
+
1389
+ if show_err:
1390
+ err_plot = self._spec.err_flux.data
1391
+ in_ax[0].fill_between(x=wave_plot[idcs_bands[2]:idcs_bands[3]] / z_corr, y1=(flux_plot[idcs_bands[2]:idcs_bands[3]] - err_plot[idcs_bands[2]:idcs_bands[3]]) * z_corr,
1392
+ y2=(flux_plot[idcs_bands[2]:idcs_bands[3]] + err_plot[idcs_bands[2]:idcs_bands[3]]) * z_corr,
1393
+ step='mid', alpha=1, color=theme.colors['line_band'], ec=None)
1379
1394
 
1380
1395
  # Add the fitting results
1381
1396
  if include_fits:
@@ -20,23 +20,31 @@ from ..transitions import label_decomposition, Line
20
20
  _logger = logging.getLogger('LiMe')
21
21
 
22
22
 
23
- def establish_selection_lines(spec, input_log, band_vsigma=70, n_sigma=4, fit_conf=None,
24
- default_conf_prefix='default', obj_conf_prefix=None, adjust_central_bands=True,
25
- instrumental_correction=True, components_detection=False, line_list=None,
26
- particle_list=None, sig_digits=None, vacuum_waves=False, ref_bands=None, update_labels=True,
27
- update_latex=False):
23
+ def establish_selection_lines(spec, input_log, band_vsigma, n_sigma, adjust_central_band, instrumental_correction,
24
+ components_detection, composite_lines, fit_cfg, default_cfg_prefix, obj_cfg_prefix,
25
+ update_default, line_list, particle_list, decimals, vacuum_waves, ref_bands, update_labels,
26
+ update_latex, vacuum_label):
28
27
 
29
28
  # Use the default database crop for the observation if none provided
30
- ref_bands = spec.retrieve.line_bands(band_vsigma, n_sigma, None, None, None,
31
- adjust_central_bands, instrumental_correction, False, line_list,
32
- particle_list, sig_digits, vacuum_waves, ref_bands, update_labels, update_latex)
29
+ ref_bands = spec.retrieve.line_bands(band_vsigma=band_vsigma, n_sigma=n_sigma, adjust_central_band=adjust_central_band,
30
+ instrumental_correction=instrumental_correction, components_detection=False,
31
+ composite_lines=None, fit_cfg=None, default_cfg_prefix=None,
32
+ obj_cfg_prefix=None, update_default=True,
33
+ line_list=line_list, particle_list=particle_list, decimals=decimals,
34
+ vacuum_waves=vacuum_waves, ref_bands=ref_bands, update_labels=update_labels,
35
+ update_latex=update_latex, vacuum_label=vacuum_label)
36
+
33
37
 
34
38
  # Load input log or copy the reference one
35
39
  in_bands = check_file_dataframe(input_log, verbose=False)
36
40
  if in_bands is None:
37
- in_bands = spec.retrieve.line_bands(band_vsigma, n_sigma, fit_conf, default_conf_prefix, obj_conf_prefix,
38
- adjust_central_bands, instrumental_correction, components_detection, line_list,
39
- particle_list, sig_digits, vacuum_waves, ref_bands, update_labels, update_latex)
41
+ in_bands = spec.retrieve.line_bands(band_vsigma=band_vsigma, n_sigma=n_sigma, adjust_central_band=adjust_central_band,
42
+ instrumental_correction=instrumental_correction, components_detection=components_detection,
43
+ composite_lines=composite_lines, fit_cfg=fit_cfg, default_cfg_prefix=default_cfg_prefix,
44
+ obj_cfg_prefix=obj_cfg_prefix, update_default=update_default,
45
+ line_list=line_list, particle_list=particle_list, decimals=decimals,
46
+ vacuum_waves=vacuum_waves, ref_bands=ref_bands, update_labels=update_labels,
47
+ update_latex=update_latex, vacuum_label=vacuum_label)
40
48
  default_status = 1 if components_detection else 0
41
49
  else:
42
50
  default_status = 1
@@ -78,6 +86,11 @@ def establish_selection_lines(spec, input_log, band_vsigma=70, n_sigma=4, fit_co
78
86
  active_lines = active_lines[sorted_indexes].astype(bool)
79
87
  labels_arr = labels_arr[sorted_indexes]
80
88
 
89
+ # Set NaN entries in dataframe as None
90
+ if 'group_label' in log.columns:
91
+ idcs_nan = log.group_label.isnull()
92
+ log.loc[idcs_nan, 'group_label'] = 'none'
93
+
81
94
  return log, labels_arr, active_lines
82
95
 
83
96
 
@@ -180,17 +193,20 @@ class BandsInspection:
180
193
  self._color_bg = {True: theme.colors['inspection_positive'],
181
194
  False: theme.colors['inspection_negative']}
182
195
 
196
+ self._out_params = ["wavelength", "wave_vac", "w1", "w2", "w3", "w4", "w5", "w6",
197
+ "units_wave", "particle", "transition", "rel_int"]
198
+
183
199
  return
184
200
 
185
- def bands(self, bands_file, band_vsigma=70, n_sigma=4, fit_conf=None, default_conf_prefix='default', obj_conf_prefix=None,
186
- adjust_central_bands=True, instrumental_correction=True, components_detection=False, line_list=None,
187
- particle_list=None, sig_digits=None, vacuum_waves=False, ref_bands=None, update_labels=True,
188
- update_latex=False, y_scale='auto', n_cols=6, n_rows=None, col_row_scale=(1, 0.5),
189
- exclude_continua=False, n_pixels=10, fig_cfg=None, in_fig=None, maximize=False):
201
+ def bands(self, bands_file, band_vsigma=70, n_sigma=4, adjust_central_band=True, instrumental_correction=True,
202
+ components_detection=False, composite_lines=None, fit_cfg=None, default_cfg_prefix='default',
203
+ obj_cfg_prefix=None, update_default=True, line_list=None, particle_list=None, decimals=None,
204
+ vacuum_waves=False, ref_bands=None, update_labels=False, update_latex=False, vacuum_label=False,
205
+ y_scale='auto', n_cols=6, n_rows=None, col_row_scale=(1, 0.5),
206
+ exclude_continua=False, n_pixels=10, fig_cfg=None, in_fig=None, maximize=False):
190
207
 
191
208
 
192
209
  """
193
-
194
210
  This function launches an interactive plot from which to select the line bands on the observed spectrum. If this
195
211
  function is run a second time, the user selections won't be overwritten.
196
212
 
@@ -266,10 +282,11 @@ class BandsInspection:
266
282
  raise LiMe_Error(f'Input bands file directory does not exist ({self._log_address.parent.as_posix()})')
267
283
 
268
284
  # Establish the reference lines log to inspect the mask
269
- self.log, self.line_list, self.active_lines = establish_selection_lines(self._spec, self._log_address, band_vsigma, n_sigma, fit_conf,
270
- default_conf_prefix, obj_conf_prefix, adjust_central_bands,
271
- instrumental_correction, components_detection, line_list, particle_list,
272
- sig_digits, vacuum_waves, ref_bands, update_labels, update_latex)
285
+ self.log, self.line_list, self.active_lines = establish_selection_lines(self._spec, self._log_address,
286
+ band_vsigma, n_sigma, adjust_central_band, instrumental_correction,
287
+ components_detection, composite_lines, fit_cfg, default_cfg_prefix,
288
+ obj_cfg_prefix, update_default, line_list, particle_list, decimals,
289
+ vacuum_waves, ref_bands, update_labels, update_latex, vacuum_label)
273
290
 
274
291
  # Proceed if there are lines in the mask for the object spectrum wavelength range
275
292
  if len(self.log.index) > 0:
@@ -430,7 +447,7 @@ class BandsInspection:
430
447
  _logger.info(f'Unsuccessful line selection: {self.line}: w_low: {w_low}, w_high: {w_high}')
431
448
 
432
449
  # Save the log to the file
433
- save_or_clear_log(self.log, self._log_address, self.active_lines)
450
+ save_or_clear_log(self.log, self._log_address, self.active_lines, self._out_params)
434
451
 
435
452
  # Redraw the line measurement
436
453
  self._ax.clear()
@@ -470,7 +487,7 @@ class BandsInspection:
470
487
  self.active_lines[idx] = np.invert(self.active_lines[idx])
471
488
 
472
489
  # Save the log to the file
473
- save_or_clear_log(self.log, self._log_address, self.active_lines)
490
+ save_or_clear_log(self.log, self._log_address, self.active_lines, self._out_params)
474
491
 
475
492
  # Plot the line selection with the new Background
476
493
  self._ax.clear()
@@ -516,7 +533,7 @@ class BandsInspection:
516
533
  self._fig.canvas.draw()
517
534
 
518
535
  # Save the log to the file
519
- save_or_clear_log(self.log, self._log_address, self.active_lines)
536
+ save_or_clear_log(self.log, self._log_address, self.active_lines, self._out_params)
520
537
 
521
538
  return
522
539
 
@@ -549,7 +566,7 @@ class RedshiftInspectionSingle:
549
566
 
550
567
  def redshift(self, reference_lines, output_file_log=None, output_idcs=None, redshift_column='redshift',
551
568
  none_value=np.nan, unknown_value=0.0, legend_handle='levels', maximize=False, title_label=None,
552
- output_address=None, plt_cfg={}, ax_cfg={}, in_fig=None):
569
+ output_address=None, fig_cfg={}, ax_cfg={}, in_fig=None):
553
570
 
554
571
  # Assign the attributes
555
572
  self._obj_idcs = None
@@ -581,16 +598,12 @@ class RedshiftInspectionSingle:
581
598
  idcs_sorted = np.argsort(_waves_array)
582
599
  self._waves_array, self._latex_array = _waves_array[idcs_sorted], _latex_array[idcs_sorted]
583
600
 
584
- # Set figure format with the user inputs overwriting the default conf
585
- plt_cfg.setdefault('figure.figsize', (10, 6))
586
- plt_cfg.setdefault('axes.labelsize', 12)
587
- plt_cfg.setdefault('xtick.labelsize', 10)
588
- plt_cfg.setdefault('ytick.labelsize', 10)
601
+ # Set the plot format where the user's overwrites the default
602
+ size_conf = {'figure.figsize': (10, 6), 'axes.labelsize': 12, 'xtick.labelsize': 10, 'ytick.labelsize': 10}
603
+ size_conf = size_conf if fig_cfg is None else {**size_conf, **fig_cfg}
589
604
 
590
- norm_flux = self._sample.load_params.get('norm_flux')
591
- units_wave, units_flux = self._sample.load_params.get('units_wave'), self._sample.load_params.get('units_flux')
592
- PLT_CONF, self._AXES_CONF = self._figure_format(plt_cfg, ax_cfg, norm_flux=norm_flux, units_wave=units_wave,
593
- units_flux=units_flux)
605
+ PLT_CONF = theme.fig_defaults(size_conf)
606
+ self._AXES_CONF = theme.ax_defaults(ax_cfg, self._spec.units_wave, self._spec.units_flux, self._spec.norm_flux, fig_type=None)
594
607
 
595
608
  # Create and fill the figure
596
609
  with rc_context(PLT_CONF):
@@ -601,15 +614,12 @@ class RedshiftInspectionSingle:
601
614
  self._ax = self._fig.add_subplot(gs[0])
602
615
  self._ax.set(**self._AXES_CONF)
603
616
 
604
- # Line Selection axis
617
+ # Create the RadioButtons widget for the lines
605
618
  buttoms_ax = self._fig.add_subplot(gs[1])
606
- buttons_list = [r'$None$'] + list(self._latex_array) + [r'$Unknown$']
607
- radio = RadioButtons(buttoms_ax, buttons_list)
608
- for circle in radio.circles: # Make the buttons a bit rounder
609
- circle.set_height(0.025)
610
- circle.set_width(0.075)
611
- for r in radio.labels:
612
- r.set_fontsize(6)
619
+ labels_buttons = [r'$None$'] + list(self._latex_array) + [r'$Unknown$']
620
+ radio_props = {'s': [10] * len(labels_buttons)}
621
+ label_props = {'fontsize': [6] * len(labels_buttons)}
622
+ radio = RadioButtons(buttoms_ax, labels_buttons, radio_props=radio_props, label_props=label_props)
613
623
 
614
624
  # Plot the spectrum
615
625
  self._launch_plots_ZI()
@@ -828,9 +838,13 @@ class RedshiftInspection:
828
838
 
829
839
  def redshift(self, obj_idcs, reference_lines, output_file_log=None, output_idcs=None, redshift_column='redshift',
830
840
  initial_z=None, none_value=np.nan, unknown_value=0.0, legend_handle='levels', maximize=False, title=None,
831
- output_address=None, n_pixels=10, fig_cfg={}, ax_cfg={}, in_fig=None):
841
+ output_address=None, n_pixels=10, fig_cfg={}, ax_cfg={}, in_fig=None, **kwargs):
832
842
 
833
843
 
844
+ # Check if input tuple
845
+ if isinstance(obj_idcs, tuple):
846
+ obj_idcs = pd.MultiIndex.from_tuples([obj_idcs], names=self._sample.index.names)
847
+
834
848
  # Assign the attributes
835
849
  self._obj_idcs = obj_idcs if isinstance(obj_idcs, pd.MultiIndex) else self._sample.loc[obj_idcs].index
836
850
  self._column_log = redshift_column
@@ -840,6 +854,10 @@ class RedshiftInspection:
840
854
  self._legend_handle = legend_handle
841
855
  self._user_point = None
842
856
 
857
+ # Parameters for the load function
858
+ self._load_params = {**self._sample.load_params, **kwargs}
859
+ self._load_params['redshift'] = 0
860
+
843
861
  # Output Log params
844
862
  self._log_address = output_file_log
845
863
 
@@ -894,16 +912,12 @@ class RedshiftInspection:
894
912
  self._ax = self._fig.add_subplot(gs[0])
895
913
  self._ax.set(**self._AXES_CONF)
896
914
 
897
- # Line Selection axis
915
+ # Create the RadioButtons widget for the lines
898
916
  buttoms_ax = self._fig.add_subplot(gs[1])
899
- buttons_list = [r'$None$'] + list(self._latex_array) + [r'$Unknown$']
900
- radio = RadioButtons(buttoms_ax, buttons_list)
901
- for circle in radio.circles: # Make the buttons a bit rounder
902
- circle.set_height(0.025)
903
- circle.set_width(0.075)
904
- circle.set_edgecolor(theme.colors['fg']) # or any color like 'black', '#333333', etc.
905
- for r in radio.labels:
906
- r.set_fontsize(6)
917
+ labels_buttons = [r'$None$'] + list(self._latex_array) + [r'$Unknown$']
918
+ radio_props = {'s': [10] * len(labels_buttons)}
919
+ label_props = {'fontsize': [6] * len(labels_buttons)}
920
+ radio = RadioButtons(buttoms_ax, labels_buttons, radio_props=radio_props, label_props=label_props)
907
921
 
908
922
  # Plot the spectrum
909
923
  self._launch_plots_ZI()
@@ -915,9 +929,6 @@ class RedshiftInspection:
915
929
  # Plot on screen unless an output address is provided
916
930
  save_close_fig_swicth(output_address, 'tight', self._fig, maximise=maximize)
917
931
 
918
- # else:
919
- # _logger.warning(f'The sample does not have objects. The redshift check could not be done')
920
-
921
932
  return
922
933
 
923
934
  def _launch_plots_ZI(self):
@@ -955,8 +966,7 @@ class RedshiftInspection:
955
966
  for i, obj_idx in enumerate(self._obj_idcs):
956
967
 
957
968
  # Load the spectrum with a zero redshift
958
- load_params = {**self._sample.load_params, **{'redshift': 0}}
959
- spec = self._sample.load_function(self._sample.frame, obj_idx, self._sample.file_address, **load_params)
969
+ spec = self._sample.load_function(self._sample.frame, obj_idx, self._sample.file_address, **self._load_params)
960
970
 
961
971
  # Plot on the observed frame with reshift = 0
962
972
  wave_plot, flux_plot, z_corr, idcs_mask = frame_mask_switch(spec.wave, spec.flux, spec.redshift, 'observed')
@@ -977,8 +987,7 @@ class RedshiftInspection:
977
987
  for obj_idx in self._obj_idcs:
978
988
 
979
989
  # Load the spectrum
980
- load_params = {**self._sample.load_params, **{'redshift': 0}}
981
- spec = self._sample.load_function(self._sample.frame, obj_idx, self._sample.file_address, **load_params)
990
+ spec = self._sample.load_function(self._sample.frame, obj_idx, self._sample.file_address, **self._load_params)
982
991
 
983
992
  wavelength = spec.wave.data
984
993
  wavelength = wavelength[~np.isnan(wavelength)]
@@ -1053,13 +1062,12 @@ class RedshiftInspection:
1053
1062
  _redshift_pred = redshift_output
1054
1063
 
1055
1064
  # Store the new redshift
1056
- #
1065
+
1057
1066
  self._sample.loc[self._output_idcs, self._column_log] = _redshift_pred
1058
1067
 
1059
1068
  # Save to file if provided
1060
1069
  if self._log_address is not None:
1061
1070
  save_frame(self._log_address, self._sample.frame)
1062
-
1063
1071
  return
1064
1072
 
1065
1073
  def _button_ZI(self, line_selection):
@@ -1349,10 +1357,12 @@ class CubeInspection:
1349
1357
  # Buttons axis if provided
1350
1358
  if len(self.masks_dict) > 0:
1351
1359
  self._ax2 = self._fig.add_subplot(gs_image[1])
1352
- radio = RadioButtons(self._ax2, list(self.masks_dict.keys()))
1353
1360
 
1354
- for r in radio.labels:
1355
- r.set_fontsize(5)
1361
+ # Create the RadioButtons widget with the specified properties
1362
+ labels_buttons = list(self.masks_dict.keys())
1363
+ radio_props = {'s': [10] * len(labels_buttons)}
1364
+ label_props = {'fontsize': [5] * len(labels_buttons)}
1365
+ radio = RadioButtons(self._ax2, labels_buttons, radio_props=radio_props, label_props=label_props)
1356
1366
 
1357
1367
  # Plot the data
1358
1368
  self.data_plots()
@@ -1733,7 +1743,7 @@ class MaskInspection:
1733
1743
  return
1734
1744
 
1735
1745
 
1736
- class SpectrumCheck(Plotter, RedshiftInspection, BandsInspection):
1746
+ class SpectrumCheck(Plotter, RedshiftInspectionSingle, BandsInspection):
1737
1747
 
1738
1748
  def __init__(self, spectrum):
1739
1749
 
@@ -109,6 +109,7 @@ mask_marker = '#FF0000'
109
109
  inspection_positive = '#FFFFFF'
110
110
  inspection_negative = '#ff796c' # 'xkcd:salmon'
111
111
  fade_fg = "#FFE5B4"
112
+ err_area = "#00FF00"
112
113
 
113
114
  [colors.dark] # Dark theme
114
115
  bg = "#2B2B2B"
@@ -128,4 +129,5 @@ comps_map = 'PuRd'
128
129
  mask_marker = '#FF0000'
129
130
  inspection_positive = "#2B2B2B"
130
131
  inspection_negative = '#840000'
131
- fade_fg = "#73879B"
132
+ fade_fg = "#73879B"
133
+ err_area = "#00FF00"