lime-stable 2.0.dev4__py3-none-any.whl → 2.0.dev7__py3-none-any.whl

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/__init__.py CHANGED
@@ -42,7 +42,7 @@ _logger.debug(f'Launching LiMe {__version__} in Python {__python_version__}')
42
42
  from lime.observations import Spectrum, Sample, Cube
43
43
  from lime.io import *
44
44
  from lime.tools import *
45
- from lime.transitions import Line, label_decomposition, bands_from_frame
45
+ from lime.transitions import Line, label_decomposition, bands_from_measurements
46
46
  from lime.archives.read_fits import OpenFits, show_instrument_cfg
47
47
  from lime.plotting.plots import theme
48
48
  from lime.workflow import line_bands
@@ -154,7 +154,7 @@ def check_fits_source(fits_source, lime_object=None, load_function=None):
154
154
  else:
155
155
 
156
156
  if load_function is None:
157
- raise LiMe_Error(f'Please introduce fits file instrument or a load function to import the fits file as a '
157
+ _logger.warning(f'Please introduce fits file instrument or a load function to import the fits file as a '
158
158
  f'LiMe observation')
159
159
 
160
160
  return fits_source, spectrum_type
lime/changelog.txt CHANGED
@@ -109,4 +109,5 @@ LiMe Mayor update - 2.0.0 - XX/XX/XXXX
109
109
  - Rename term "_conf" to "_cfg" across function names and arguments for uniformity
110
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
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
112
+ - By default line_bands.
113
+ - Added bands_from_log function
lime/fitting/lines.py CHANGED
@@ -180,7 +180,6 @@ def pp_FWHM(line, idx):
180
180
 
181
181
 
182
182
  def gaussian_area(line, idx, n_steps):
183
-
184
183
  amp = np.random.normal(line.amp[idx], line.amp_err[idx], n_steps)
185
184
  sigma = np.random.normal(line.sigma[idx], line.sigma_err[idx], n_steps)
186
185
 
@@ -470,8 +469,6 @@ class ProfileModelCompiler:
470
469
  self.model.prefix = f'line0_'
471
470
 
472
471
  # Fix or not the continuum
473
- # m_cont_conf = _SLOPE_FIX_PAR if line._cont_from_adjacent else _SLOPE_FREE_PAR
474
- # n_cont_conf = _INTER_FIX_PAR if line._cont_from_adjacent else _INTER_FREE_PAR
475
472
  self.define_param(0, line, 'm_cont', line.m_cont, _SLOPE_FIX_PAR, user_conf)
476
473
  self.define_param(0, line, 'n_cont', line.n_cont, _INTER_FIX_PAR, user_conf)
477
474
 
@@ -616,8 +613,11 @@ class ProfileModelCompiler:
616
613
  # Check for negative -0.0 # TODO this needs a better place # FIXME -0.0 error
617
614
  if np.signbit(line.sigma_err[i]):
618
615
  line.sigma_err[i] = np.nan
619
- if self.output.errorbars:
620
- _logger.warning(f'Negative value for profile sigma at {line.label}')
616
+ _logger.warning(f'Negative scale value for amplitude at {comp_label}')
617
+
618
+ if np.signbit(line.amp_err[i]):
619
+ line.amp_err[i] = np.nan
620
+ _logger.warning(f'Negative scale value for amplitude at {comp_label}')
621
621
 
622
622
  # Compute the profile areas
623
623
  profile_flux_dist = AREA_FUNCTIONS[line._p_shape[i]](line, i, 1000)
@@ -52,8 +52,8 @@ class LineFinder:
52
52
 
53
53
  return
54
54
 
55
- def peaks_troughs(self, bands, sigma_threshold=3, emission_type=True, width_tol=5, band_modification=None,
56
- continuum_array=None, continuum_std=None, plot_steps=False, log_scale=False):
55
+ def peaks_troughs(self, bands, sigma_threshold=3, emission_type=True, width_tol=5,
56
+ continuum_array=None, continuum_std=None, plot_steps=False, **kwargs):
57
57
 
58
58
  """
59
59
 
@@ -82,9 +82,6 @@ class LineFinder:
82
82
  :param width_tol: Minimum number of pixels between peaks/troughs. The default value is 5.
83
83
  :type width_tol: float, optional
84
84
 
85
- :param band_modification: Method to adjust the line band with. The default value is None.
86
- :type band_modification: str, optional
87
-
88
85
  :param ml_detection: Machine learning algorithm to detect lines. The default value is None.
89
86
  :type ml_detection: str, optional
90
87
 
@@ -105,101 +102,36 @@ class LineFinder:
105
102
 
106
103
  # Match peaks with theoretical lines
107
104
  bands = check_file_dataframe(bands)
108
- matched_DF = self.label_peaks(idcs_peaks, bands, width_tol=width_tol, band_modification=band_modification,
109
- line_type=emission_type)
105
+ matched_DF = self.label_peaks(idcs_peaks, bands, width_tol=width_tol, line_type=emission_type)
110
106
 
111
107
  # Plot the results
112
108
  if plot_steps:
113
- plot_peaks_troughs(self._spec, idcs_peaks, limit_threshold, continuum_array, matched_DF, log_scale)
109
+ plot_peaks_troughs(self._spec, idcs_peaks, limit_threshold, continuum_array, matched_DF, **kwargs)
114
110
 
115
111
  return matched_DF
116
112
 
117
113
 
118
- def label_peaks(self, peak_table, mask_df, line_type='emission', width_tol=5, band_modification=None, detect_check=False):
119
-
120
- # TODO auto param should be changed to boolean
121
- # Establish the type of input values for the peak indexes, first numpy array
122
- if isinstance(peak_table, np.ndarray):
123
- idcsLinePeak = peak_table
124
-
125
- # Specutils table
126
- else:
127
- # Query the lines from the astropy finder archives #
128
- if len(peak_table) != 0:
129
- idcsLineType = peak_table['emission_type'] == line_type
130
- idcsLinePeak = np.array(peak_table[idcsLineType]['line_center_index'])
131
- else:
132
- idcsLinePeak = np.array([])
114
+ def label_peaks(self, idcs_peaks, matched_DF, line_type='emission', width_tol=5):
133
115
 
134
116
  # Security check in case no lines detected
135
- if len(idcsLinePeak) == 0:
136
- return pd.DataFrame(columns=mask_df.columns)
137
-
138
- # Exclude bands not withing the regime:
139
- w0, wf = self._spec.wave_rest.data[~self._spec.wave_rest.mask][0], self._spec.wave_rest.data[~self._spec.wave_rest.mask][-1]
140
- idcs_selection = (mask_df.w3 > w0) & (mask_df.w4 < wf)
141
-
142
- # Prepare dataframe to stored the matched lines
143
- matched_DF = mask_df.loc[idcs_selection].copy()
144
- matched_DF['signal_peak'] = np.nan
145
-
146
- # Theoretical wave values
147
- waveTheory = label_decomposition(matched_DF.index.values, params_list=['wavelength'])[0]
148
- matched_DF['wavelength'] = waveTheory
149
-
150
- # Match the lines with the theoretical emission
151
- tolerance = np.diff(self._spec.wave_rest).mean() * width_tol
152
- matched_DF['observation'] = 'not_detected'
153
- unidentifiedLine = dict.fromkeys(matched_DF.columns.values, np.nan)
154
-
155
- # Get the wavelength peaks
156
- wave_peaks = self._spec.wave_rest[idcsLinePeak]
157
-
158
- for i in np.arange(wave_peaks.size):
159
-
160
- idx_array = np.where(np.isclose(a=waveTheory.astype(np.float64), b=wave_peaks[i], atol=tolerance))
161
-
162
- if len(idx_array[0]) == 0:
163
- unknownLineLabel = 'xy_{:.0f}A'.format(np.round(wave_peaks[i]))
164
-
165
- # Scheme to avoid repeated lines
166
- if (unknownLineLabel not in matched_DF.index) and detect_check:
167
- newRow = unidentifiedLine.copy()
168
- newRow.update({'wavelength': wave_peaks[i], 'w3': wave_peaks[i] - 5, 'w4': wave_peaks[i] + 5,
169
- 'observation': 'not_identified'})
170
- matched_DF.loc[unknownLineLabel] = newRow
171
-
172
- else:
173
-
174
- row_index = matched_DF.index[matched_DF.wavelength == waveTheory[idx_array[0][0]]]
175
- matched_DF.loc[row_index, 'observation'] = 'detected'
176
- matched_DF.loc[row_index, 'signal_peak'] = idcsLinePeak[i]
177
- theoLineLabel = row_index[0]
178
-
179
- blended_check = True if '_b' in theoLineLabel else False
180
- minSeparation = 4 if blended_check else 2
117
+ if len(idcs_peaks) == 0:
118
+ return pd.DataFrame(columns=matched_DF.columns)
181
119
 
182
- # Width is only computed for blended lines
183
- if band_modification is not None:
184
- if band_modification == 'auto':
185
- if blended_check is False:
186
- emission_check = True if line_type == 'emission' else False
187
- idx_min = compute_line_width(idcsLinePeak[i], self._spec.flux, delta_i=-1, min_delta=minSeparation, emission_check=emission_check)
188
- idx_max = compute_line_width(idcsLinePeak[i], self._spec.flux, delta_i=1, min_delta=minSeparation, emission_check=emission_check)
189
- matched_DF.loc[row_index, 'w3'] = self._spec.wave_rest[idx_min]
190
- matched_DF.loc[row_index, 'w4'] = self._spec.wave_rest[idx_max]
120
+ # Add theoretical wavelength values if necessary
121
+ if 'wavelength' not in matched_DF.columns:
122
+ matched_DF['wavelength'] = label_decomposition(matched_DF.index.values, params_list=['wavelength'])[0]
191
123
 
192
- # Include_only_detected
193
- idcs_unknown = matched_DF['observation'] == 'not_detected'
194
- matched_DF.drop(index=matched_DF.loc[idcs_unknown].index.values, inplace=True)
124
+ # Get bands limits indexes
125
+ idcs_w3 = np.searchsorted(self._spec.wave_rest, matched_DF.w3)
126
+ idcs_w4 = np.searchsorted(self._spec.wave_rest, matched_DF.w4)
195
127
 
196
- # Sort by wavelength
197
- matched_DF.sort_values('wavelength', inplace=True)
198
- matched_DF.drop(columns=['wavelength', 'observation'], inplace=True)
128
+ # Get the bands matching
129
+ band_contains_peak = (idcs_peaks[None, :] > idcs_w3[:, None]) & (idcs_peaks[None, :] < idcs_w4[:, None])
130
+ idcs_matched_bands = band_contains_peak.any(axis=1)
131
+ idcs_matched_peaks = idcs_peaks[band_contains_peak.argmax(axis=1)[idcs_matched_bands]]
199
132
 
200
- # Security check all bands are within selection
201
- wave_peak_matched = self._spec.wave_rest[matched_DF.signal_peak.to_numpy().astype(int)]
202
- idcs_valid = (matched_DF.w3 < wave_peak_matched) & (matched_DF.w4 > wave_peak_matched)
203
- matched_DF = matched_DF.loc[idcs_valid]
133
+ # Crop the bands to the detection
134
+ matched_DF.loc[idcs_matched_bands, 'observation'] = 'detected'
135
+ matched_DF.loc[idcs_matched_bands, 'signal_peak'] = idcs_matched_peaks
204
136
 
205
- return matched_DF
137
+ return matched_DF.loc[idcs_matched_bands]
lime/lime.toml CHANGED
@@ -1,3 +1,3 @@
1
1
  [metadata]
2
2
  name = 'lime-stable'
3
- version = "2.0.dev4"
3
+ version = "2.0.dev7"
@@ -102,14 +102,7 @@ def bokeh_bands(fig, bands, x, y, z_corr, redshift):
102
102
 
103
103
  # Loop through the detections and plot the names
104
104
  for i in np.arange(latex.size):
105
- if idcs_band_limits[0, i] != idcs_band_limits[0, i]: # Y limit for the label check if same pixel
106
- max_region = np.max(y[idcs_band_limits[0, i]:idcs_band_limits[0, i]])
107
- else:
108
- max_region = y[idcs_band_limits[0, i]]
109
-
110
- label = 'Matched line' if i == 0 else '_'
111
105
  fig.add_layout(BoxAnnotation(left=w3_obs[i]/z_corr, right=w4_obs[i]/z_corr, fill_alpha=0.3, fill_color=theme.colors['match_line']))
112
- # axis.text(wave_array[i] * (1 + redshift) / z_corr, max_region * 0.9 * z_corr, latex[i], rotation=270)
113
106
 
114
107
  return
115
108
 
@@ -124,16 +117,25 @@ def bands_filling_bokeh(fig, x, y, z_corr, idcs_mask, label, exclude_continua=Fa
124
117
  low_lim = 0 if np.isnan(low_lim) else low_lim
125
118
 
126
119
  # Central bands
120
+ print('1',x[idcs_mask[2]:idcs_mask[3]]/z_corr)
121
+ print('1',y[idcs_mask[2]:idcs_mask[3]]*z_corr)
122
+ print('1',low_lim*z_corr)
127
123
  fig.varea_step(x=x[idcs_mask[2]:idcs_mask[3]]/z_corr, y1=low_lim*z_corr, y2=y[idcs_mask[2]:idcs_mask[3]]*z_corr,
128
124
  step_mode="center", fill_alpha=0.25, color=color_dict['line_band'])
129
125
 
130
126
  # Continua bands exclusion
131
127
  if exclude_continua is False:
128
+ print('2', x[idcs_mask[0]:idcs_mask[1]]/z_corr)
129
+ print('2', low_lim*z_corr)
130
+ print('2', y[idcs_mask[0]:idcs_mask[1]]*z_corr)
132
131
  fig.varea_step(x=x[idcs_mask[0]:idcs_mask[1]]/z_corr,
133
132
  y1=low_lim*z_corr,
134
133
  y2=y[idcs_mask[0]:idcs_mask[1]]*z_corr,
135
134
  step_mode="center", fill_alpha=0.25, color=color_dict['cont_band'])
136
135
 
136
+ print('3', x[idcs_mask[4]:idcs_mask[5]]/z_corr)
137
+ print('3', low_lim*z_corr)
138
+ print('3', y[idcs_mask[4]:idcs_mask[5]]*z_corr)
137
139
  fig.varea_step(x=x[idcs_mask[4]:idcs_mask[5]]/z_corr,
138
140
  y1=low_lim*z_corr,
139
141
  y2=y[idcs_mask[4]:idcs_mask[5]]*z_corr,
@@ -193,8 +195,8 @@ class BokehFigures:
193
195
 
194
196
  return
195
197
 
196
- def bands(self, label, output_address=None, ref_bands=None, include_fits=True, rest_frame=False, log_scale=True, fig_cfg=None,
197
- ax_cfg=None, return_fig=False):
198
+ def bands(self, label, output_address=None, bands=None, include_fits=True, rest_frame=False, log_scale=True,
199
+ exclude_continua=True, fig_cfg=None, ax_cfg=None, return_fig=False):
198
200
 
199
201
 
200
202
  # Unpack variables
@@ -205,7 +207,7 @@ class BokehFigures:
205
207
  legend_check = True if label is not None else False
206
208
 
207
209
  # Check which line should be plotted
208
- line = parse_bands_arguments(label, log, ref_bands, norm_flux)
210
+ line = parse_bands_arguments(label, log, bands, norm_flux)
209
211
 
210
212
  # Proceed to plot
211
213
  if line is not None:
@@ -232,16 +234,13 @@ class BokehFigures:
232
234
  # Create figure with default utils if not provided
233
235
  fig = figure(tools=PLT_CONF.get('tools', "pan,wheel_zoom,box_zoom,reset,save"), y_axis_type=scale_str)
234
236
 
235
- # # Create figure with default utils if not provided
236
- # fig = figure(tools=PLT_CONF.get('tools', "pan,wheel_zoom,box_zoom,reset,save"))
237
-
238
237
  # Spectrum data source
239
238
  source = ColumnDataSource(data={"x": wave_plot[idcs_bands[0]:idcs_bands[5]] / z_corr,
240
239
  "y": flux_plot[idcs_bands[0]:idcs_bands[5]] * z_corr})
241
240
  fig.step("x", "y", source=source, color=theme.colors['fg'], line_width=1, mode='center')
242
241
 
243
242
  # Fille the bands
244
- bands_filling_bokeh(fig, wave_plot, flux_plot, z_corr, idcs_bands, line)
243
+ bands_filling_bokeh(fig, wave_plot, flux_plot, z_corr, idcs_bands, line, exclude_continua=exclude_continua)
245
244
 
246
245
  # Plot labels
247
246
  fig.xaxis.axis_label = AXES_CONF['xlabel']
@@ -250,6 +249,9 @@ class BokehFigures:
250
249
  # Adjust the format of the plot
251
250
  update_bokeh_figure(fig, PLT_CONF)
252
251
 
252
+ # Hide the legend if there are line profiles
253
+ fig.legend.visible = legend_check
254
+
253
255
  # Save or display the plot
254
256
  if return_fig:
255
257
  return fig
@@ -258,11 +260,14 @@ class BokehFigures:
258
260
  save(fig, filename=output_address)
259
261
 
260
262
  else:
261
- # output_notebook()
262
263
  show(fig)
263
264
 
265
+ else:
266
+ _logger.info(f'The input line {label} could not be found')
267
+
264
268
  return
265
269
 
270
+
266
271
  def grid(self, output_address=None, rest_frame=True, log_scale=False, n_cols=6, n_rows=None, col_row_scale=(2, 1.5),
267
272
  include_fits=True, in_fig=None, fig_cfg=None, ax_cfg=None, maximize=False):
268
273
 
@@ -410,6 +415,9 @@ class BokehFigures:
410
415
  # Adjust the format of the plot
411
416
  update_bokeh_figure(fig, PLT_CONF)
412
417
 
418
+ # Hide the legend if there are line profiles
419
+ fig.legend.visible = legend_check
420
+
413
421
  # Save or display the plot
414
422
  if return_fig:
415
423
  return fig
lime/plotting/format.py CHANGED
@@ -54,6 +54,7 @@ class Themer:
54
54
  self.conf = None # All the formating data
55
55
  self.style = None # Label of the active style
56
56
  self.active_conf = None # Dictionary with the active figure configuration and library
57
+ self.default_lib = 'matplotlib'
57
58
 
58
59
  # LiMe plots personalization
59
60
  self.colors = None # Features individual colors
@@ -67,6 +68,8 @@ class Themer:
67
68
  self.conf = conf.copy()
68
69
  self.set_style(style)
69
70
 
71
+
72
+
70
73
  return
71
74
 
72
75
 
@@ -163,7 +166,7 @@ class Themer:
163
166
  return ax_cfg
164
167
 
165
168
 
166
- def set_style(self, style=None, scale=None, colors_conf=None, library=None):
169
+ def set_style(self, style=None, scale=None, colors_conf=None, library='matplotlib'):
167
170
 
168
171
  # Set the default style
169
172
  # self.style = ['default']
@@ -173,6 +176,7 @@ class Themer:
173
176
  # self.style += [style] if isinstance(style, str) else style
174
177
  self.style = 'default' if style is None else style
175
178
  self.scale = ['default'] if style is None else [scale]
179
+ self.default_lib = library
176
180
 
177
181
  # Set the library defaults
178
182
  self.active_conf = {'matplotlib': self.conf['matplotlib']['default'].copy(),
lime/plotting/plots.py CHANGED
@@ -159,7 +159,10 @@ def _auto_flux_scale(axis, y, y_scale, scale_dict=theme.plt):
159
159
  ratio = np.abs(y_max/y_min)
160
160
  if (ratio > 25) or (ratio < 0.06):
161
161
  if neg_check:
162
- y_scale = {'value': 'symlog', 'linthresh': min(np.ceil(np.abs(y_min)), np.min(y[y>0]))}
162
+ if np.sum(y>0) > 1:
163
+ y_scale = {'value': 'symlog', 'linthresh': min(np.ceil(np.abs(y_min)), np.min(y[y>0]))}
164
+ else:
165
+ y_scale = {'value': 'symlog', 'linthresh': np.ceil(np.abs(y_min))}
163
166
  else:
164
167
  y_scale = {'value': 'log'}
165
168
  else:
@@ -657,7 +660,7 @@ def redshift_permu_evaluation(spectrum, z_infered, obs_wave_arr, theo_wave_arr,
657
660
  def bands_filling_plot(axis, x, y, z_corr, idcs_mask, label, exclude_continua=False, color_dict=theme.colors, show_central=True):
658
661
 
659
662
  # Security check for low selection
660
- if len(x[idcs_mask[2]:idcs_mask[3]]) > 1:
663
+ if y[idcs_mask[2]:idcs_mask[3]].size > 1:
661
664
 
662
665
  # Lower limit for the filled region
663
666
  if exclude_continua is False:
@@ -666,10 +669,10 @@ def bands_filling_plot(axis, x, y, z_corr, idcs_mask, label, exclude_continua=Fa
666
669
  x_interval = x[idcs_mask[2]:idcs_mask[3]]
667
670
  y_interval = y[idcs_mask[2]:idcs_mask[3]]
668
671
  else:
669
- m = (y[idcs_mask[3]] - y[idcs_mask[2]])/(x[idcs_mask[3]] - x[idcs_mask[2]])
670
- n = y[idcs_mask[2]] - m * x[idcs_mask[2]]
671
672
  x_interval = x[idcs_mask[2]:idcs_mask[3]]
672
673
  y_interval = y[idcs_mask[2]:idcs_mask[3]]
674
+ m = (y_interval[-1] - y_interval[0])/(x_interval[-1] - x_interval[0])
675
+ n = y_interval[0] - m * x_interval[0]
673
676
  low_lim = m * x_interval + n
674
677
 
675
678
  # Central bands
@@ -690,7 +693,7 @@ def bands_filling_plot(axis, x, y, z_corr, idcs_mask, label, exclude_continua=Fa
690
693
  return
691
694
 
692
695
 
693
- def plot_peaks_troughs(spec, peak_idcs, detect_limit, continuum, match_bands, log_scale=False):
696
+ def plot_peaks_troughs(spec, peak_idcs, detect_limit, continuum, match_bands, **kwargs):
694
697
 
695
698
  norm_flux = spec.norm_flux
696
699
  wave = spec.wave
@@ -724,9 +727,6 @@ def plot_peaks_troughs(spec, peak_idcs, detect_limit, continuum, match_bands, lo
724
727
  ax.scatter(wave_plot[idcs_mask], flux_plot[idcs_mask], label='Masked pixels', marker='x',
725
728
  color=theme.colors['mask_marker'])
726
729
 
727
- if log_scale:
728
- ax.set_yscale('log')
729
-
730
730
  ax.legend()
731
731
  ax.update(AXES_CONF)
732
732
  plt.tight_layout()
@@ -783,7 +783,6 @@ class Plotter:
783
783
 
784
784
  # Loop through the detections and plot the names
785
785
  for i, line_label in enumerate(match_log.index):
786
- print(line_label)
787
786
  line = Line(line_label, match_log)
788
787
 
789
788
  # Get the max flux on the region making the exception for 1 pixel bands
@@ -792,7 +791,7 @@ class Plotter:
792
791
 
793
792
  x_text = line.wavelength * (1 + redshift)/z_corr if not line.blended_check else line.wavelength[0] * (1 + redshift)/z_corr
794
793
  y_text = max_region * 0.9 * z_corr
795
- text = line.latex_label[0]
794
+ text = line.label
796
795
 
797
796
  axis.text(x_text, y_text, text, rotation=270)
798
797
  axis.axvspan(x[idx_w3]/z_corr, x[idx_w4]/z_corr, label='Matched line' if i == 0 else '_', alpha=0.30,
@@ -1009,84 +1008,79 @@ class SpectrumFigures(Plotter):
1009
1008
  in_ax.fill_between(wave_plot/z_corr, low_limit*z_corr, high_limit*z_corr, alpha=0.2,
1010
1009
  color=theme.colors['fade_fg'])
1011
1010
 
1012
- # Include the detection bands
1013
- if detection_band is not None:
1014
-
1015
- detec_obj = getattr(self._spec.infer, detection_band)
1016
-
1017
- if detec_obj.confidence is not None:
1018
-
1019
- # Boundaries array for confidence intervals
1020
- bounds = np.array([0.0, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])
1021
-
1022
- # Adjust color map to match lower detection limit to fg color
1023
- cmap = plt.get_cmap(theme.colors['mask_map'])
1024
- cmaplist = [cmap(i) for i in range(cmap.N)]
1025
- cmaplist[0] = theme.colors['fg']
1026
- cmap = colors.LinearSegmentedColormap.from_list('mcm', cmaplist, bounds.size-1)
1027
- norm = colors.BoundaryNorm(bounds * 100, cmap.N)
1028
-
1029
- # Iterate through the confidence intervals and plot the step spectrum
1030
- for i in range(1, len(bounds)):
1031
- if i > 1:
1032
- idcs = detec_obj(bounds[i-1]*100, confidence_max=bounds[i]*100)
1033
- wave_nan, flux_nan = np.full(wave_plot.size, np.nan), np.full(flux_plot.size, np.nan)
1034
- wave_nan[idcs], flux_nan[idcs] = wave_plot[idcs] / z_corr, flux_plot[idcs] * z_corr
1035
-
1036
- in_ax.step(wave_nan, flux_nan, label=label, where='mid', color=cmap(i-1))
1037
-
1038
- # Color bar
1039
- sm = cm.ScalarMappable(cmap=cmap, norm=norm)
1040
- sm.set_array([])
1041
- cbar = plt.colorbar(sm, ax=in_ax)
1042
- cbar.set_label('Detection confidence %', rotation=270, labelpad=35)
1043
-
1044
-
1045
- else:
1046
- _logger.warning(f'The line detection bands confidence has not been calculated. They are not included'
1047
- f' on plot.')
1048
-
1049
- if show_categories:
1050
- # Define the bins you want
1051
- bins = [40, 60, 80, 100]
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
+ # Show components
1049
+ if show_categories and self._spec.infer.pred_arr is not None:
1052
1050
 
1053
1051
  # Use np.histogram to get the counts in each bin
1054
- if self._spec.infer.pred_arr is not None:
1055
- if aspect_check:
1056
- categories = np.sort(np.unique(self._spec.infer.pred_arr))
1057
- legend_scatter = []
1058
-
1059
- for category in categories:
1060
- if category != 0:
1061
-
1062
- # Get category properties
1063
- feature_name = self._spec.infer.model_mgr.medium.number_feature_dict[category]
1064
- feature_color = aspect.cfg['colors'][feature_name]
1065
- idcs_feature = self._spec.infer.pred_arr == category
1066
- legend_scatter.append(mlines.Line2D([], [], marker='o', color='w',
1067
- markerfacecolor=feature_color, markersize=8, label=feature_name))
1068
-
1069
- # Count the pixels for each category
1070
- counts, _ = np.histogram(self._spec.infer.conf_arr[idcs_feature], bins=bins)
1071
- for idx_conf, count_conf in enumerate(counts):
1072
- if count_conf > 0:
1073
-
1074
- # Get indeces matching the detections
1075
- idcs_count = np.where((bins[idx_conf] < self._spec.infer.conf_arr[idcs_feature]) &
1076
- (self._spec.infer.conf_arr[idcs_feature] <= bins[idx_conf + 1]))[0]
1077
- idcs_nonnan = np.where(idcs_feature)[0][idcs_count] # Returns indices where mask is True
1078
-
1079
- # Generate nan arrays with the data to avoid filling non detections
1080
- wave_nan, flux_nan = np.full(wave_plot.size, np.nan), np.full(flux_plot.size, np.nan)
1081
- wave_nan[idcs_nonnan] = wave_plot[idcs_nonnan] / z_corr
1082
- flux_nan[idcs_nonnan] = flux_plot[idcs_nonnan] * z_corr
1083
-
1084
- # Plot with the corresponding colors and linestyle
1085
- in_ax.step(wave_nan, flux_nan, label=feature_name, where='mid', color=feature_color,
1086
- linestyle=category_conf_styles[idx_conf])
1087
-
1088
- else:
1089
- _logger.warning('Aspect needs to be installed to display the spectrum components')
1052
+ categories = np.sort(np.unique(self._spec.infer.pred_arr))
1053
+ legend_scatter = []
1054
+
1055
+ for category in categories:
1056
+ if category != 0:
1057
+
1058
+ # Get category properties
1059
+ feature_name = self._spec.infer.model_mgr.medium.number_feature_dict[category]
1060
+ feature_color = aspect.cfg['colors'][feature_name]
1061
+ idcs_feature = self._spec.infer.pred_arr == category
1062
+ legend_scatter.append(mlines.Line2D([], [], marker='o', color='w',
1063
+ markerfacecolor=feature_color, markersize=8, label=feature_name))
1064
+
1065
+ # Count the pixels for each category
1066
+ bins = [40, 60, 80, 100]
1067
+ counts, _ = np.histogram(self._spec.infer.conf_arr[idcs_feature], bins=bins)
1068
+ for idx_conf, count_conf in enumerate(counts):
1069
+ if count_conf > 0:
1070
+
1071
+ # Get indeces matching the detections
1072
+ idcs_count = np.where((bins[idx_conf] < self._spec.infer.conf_arr[idcs_feature]) &
1073
+ (self._spec.infer.conf_arr[idcs_feature] <= bins[idx_conf + 1]))[0]
1074
+ idcs_nonnan = np.where(idcs_feature)[0][idcs_count] # Returns indices where mask is True
1075
+
1076
+ # Generate nan arrays with the data to avoid filling non detections
1077
+ wave_nan, flux_nan = np.full(wave_plot.size, np.nan), np.full(flux_plot.size, np.nan)
1078
+ wave_nan[idcs_nonnan] = wave_plot[idcs_nonnan] / z_corr
1079
+ flux_nan[idcs_nonnan] = flux_plot[idcs_nonnan] * z_corr
1080
+
1081
+ # Plot with the corresponding colors and linestyle
1082
+ in_ax.step(wave_nan, flux_nan, label=feature_name, where='mid', color=feature_color,
1083
+ linestyle=category_conf_styles[idx_conf])
1090
1084
 
1091
1085
  # Legend category
1092
1086
  legend_category = in_ax.legend(handles=legend_scatter, edgecolor=theme.colors['fg'])
@@ -1275,8 +1269,8 @@ class SpectrumFigures(Plotter):
1275
1269
 
1276
1270
  return in_fig
1277
1271
 
1278
- def bands(self, label=None, output_address=None, ref_bands=None, include_fits=True, rest_frame=False, y_scale='auto', fig_cfg=None,
1279
- ax_cfg=None, in_fig=None, maximize=False, show_err=True):
1272
+ def bands(self, label=None, bands=None, output_address=None, include_fits=True, rest_frame=False, y_scale='auto', fig_cfg=None,
1273
+ ax_cfg=None, in_fig=None, maximize=False, show_err=False, exclude_continua=False):
1280
1274
 
1281
1275
  """
1282
1276
 
@@ -1333,7 +1327,7 @@ class SpectrumFigures(Plotter):
1333
1327
  display_check = True if in_fig is None else False
1334
1328
 
1335
1329
  # Check which line should be plotted
1336
- line = parse_bands_arguments(label, log, ref_bands, norm_flux)
1330
+ line = parse_bands_arguments(label, bands, log, norm_flux)
1337
1331
 
1338
1332
  # Check the observation has uncertainty to display
1339
1333
  if show_err and (self._spec.err_flux is None):
@@ -1378,13 +1372,13 @@ class SpectrumFigures(Plotter):
1378
1372
  idcs_bands = line.index_bands(self._spec.wave, self._spec.redshift, just_band_edges=True)
1379
1373
 
1380
1374
  # Plot the spectrum
1381
- label_leg = line.latex_label if (line.latex_label is not None and include_fits is False) else None
1375
+ label_leg = line.latex_label[0] if (line.latex_label[0] is not None and include_fits is False) else None
1382
1376
  in_ax[0].step(wave_plot[idcs_bands[0]:idcs_bands[5]] / z_corr, flux_plot[idcs_bands[0]:idcs_bands[5]] * z_corr,
1383
1377
  where='mid', color=theme.colors['fg'], label=label_leg, linewidth=theme.plt['spectrum_width'])
1384
1378
 
1385
1379
  # Continuum bands
1386
1380
  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)
1381
+ show_central=not show_err, exclude_continua=exclude_continua)
1388
1382
 
1389
1383
  if show_err:
1390
1384
  err_plot = self._spec.err_flux.data
lime/plotting/utils.py CHANGED
@@ -7,21 +7,21 @@ from lime.io import _PARENT_BANDS
7
7
  _logger = logging.getLogger('LiMe')
8
8
 
9
9
 
10
- def parse_bands_arguments(label, log, ref_bands, norm_flux):
10
+ def parse_bands_arguments(label, bands, log, norm_flux):
11
11
 
12
12
  line = None
13
13
  if label is None and (log.index.size > 0):
14
14
  label = log.index[-1]
15
15
  line = Line.from_log(label, log, norm_flux)
16
16
 
17
+ # The user provided a reference band to check the region use it
18
+ elif label is not None and bands is not None:
19
+ line = Line(label, bands)
20
+
17
21
  # Line has been measured before
18
22
  elif label is not None and (log.index.size > 0):
19
23
  line = Line.from_log(label, log, norm_flux)
20
24
 
21
- # The user provided a reference band to check the region use it
22
- elif label is not None and ref_bands is not None:
23
- line = Line(label, ref_bands)
24
-
25
25
  elif label is not None and label in _PARENT_BANDS.index:
26
26
  line = Line(label, band=_PARENT_BANDS.loc[label, 'w1':'w6'].to_numpy())
27
27
 
lime/tools.py CHANGED
@@ -54,7 +54,7 @@ def int_to_roman(num):
54
54
  return roman_num
55
55
 
56
56
 
57
- def pd_get(df, row, column, default=None, transform=None):
57
+ def pd_get(df, row, column, default=None, transform=None, nan_to_none=False):
58
58
 
59
59
  # Fast get from dataframe
60
60
  try:
@@ -66,6 +66,12 @@ def pd_get(df, row, column, default=None, transform=None):
66
66
  if transform is not None:
67
67
  cell = default if cell == transform else cell
68
68
 
69
+ # Transform nan to None
70
+ if nan_to_none and (cell is not None):
71
+ if isinstance(cell, float):
72
+ # print('JODER', cell)
73
+ cell = None if np.isnan(cell) else cell
74
+
69
75
  return cell
70
76
 
71
77
 
lime/transitions.py CHANGED
@@ -4,7 +4,7 @@ import numpy as np
4
4
  import pandas as pd
5
5
  from numpy.core.fromnumeric import argmin
6
6
 
7
- from .io import _PARENT_BANDS, _LOG_EXPORT, _LOG_COLUMNS, check_file_dataframe, LiMe_Error
7
+ from .io import _PARENT_BANDS, _LOG_EXPORT, _LOG_COLUMNS, check_file_dataframe, LiMe_Error, load_frame
8
8
  from pandas import DataFrame
9
9
  from .tools import pd_get, au
10
10
 
@@ -185,46 +185,53 @@ def air_to_vacuum_function(wave_array, units_wave='AA'):
185
185
  return wave_array / (1 + 1e-6 * (287.6155 + 1.62887 * sigma2 + 0.01360 * np.square(sigma2)))
186
186
 
187
187
 
188
- def check_units_from_wave(line, str_ion, str_wave, lines_df):
188
+ def check_units_from_wave(line, str_ion, str_wave, bands, ref_bands=None):
189
189
 
190
- # Try to recover from dataframe
191
- if lines_df is not None:
190
+ # First the input database
191
+ if bands is not None:
192
192
 
193
193
  # Check literal unit
194
- wave = pd_get(lines_df, line, 'wavelength')
195
- units = pd_get(lines_df, line, 'units_wave')
194
+ wave = pd_get(bands, line, 'wavelength', nan_to_none=True)
195
+ units = pd_get(bands, line, 'units_wave', nan_to_none=True)
196
196
 
197
197
  # Check core element
198
198
  if wave is None:
199
199
  core_element = f'{str_ion}_{str_wave}'
200
- wave = pd_get(lines_df, core_element, 'wavelength')
201
- units = pd_get(lines_df, core_element, 'units_wave')
200
+ wave = pd_get(bands, core_element, 'wavelength', nan_to_none=True)
201
+ units = pd_get(bands, core_element, 'units_wave', nan_to_none=True)
202
202
 
203
203
  # Convert to units
204
204
  units = au.Unit(units) if units is not None else units
205
+ # units = None if (units is None or np.isnan(units)) else au.Unit(units)
205
206
 
206
207
  else:
207
208
  wave, units = None, None
208
209
 
209
- # Otherwise decipher from label
210
+ # Second the reference database
211
+ if (units is None) or (wave is None):
212
+ ref_bands = ref_bands if ref_bands is not None else _PARENT_BANDS
213
+ wave = pd_get(ref_bands, line, 'wavelength', nan_to_none=True)
214
+ units = pd_get(ref_bands, line, 'units_wave', nan_to_none=True)
215
+
216
+ # Convert to units
217
+ units = au.Unit(units) if units is not None else units
218
+
219
+ # Third decipher from label
210
220
  if (units is None) or (wave is None):
211
221
 
212
222
  # First check for Angstroms
213
223
  if str_wave[-1] == 'A':
214
- units_label = au.Unit('AA')
215
- wave_label = float(str_wave[:-1])
224
+ units = au.Unit('AA')
225
+ wave = float(str_wave[:-1])
216
226
 
217
227
  else:
218
228
  au_unit = au.Unit(str_wave)
219
- units_label = au_unit.bases[0]
220
- wave_label = au_unit.scale
221
-
222
- else:
223
- units_label, wave_label = None, None
229
+ units = au_unit.bases[0]
230
+ wave = au_unit.scale
224
231
 
225
- # Give preferences to the tabel values
226
- wave = wave_label if wave is None else wave
227
- units = units_label if units is None else units
232
+ # # Give preferences to the tabel values
233
+ # wave = wave_label if wave is None else wave
234
+ # units = units_label if units is None else units
228
235
 
229
236
  return wave, units
230
237
 
@@ -400,7 +407,7 @@ def latex_from_label(label, particle=None, wave=None, units_wave=None, kinem=Non
400
407
  #
401
408
 
402
409
 
403
- def label_composition(line_list, ref_df=None, default_profile=None):
410
+ def label_composition(line_list, bands=None, default_profile=None, ref_bands=None):
404
411
 
405
412
  # Empty containers for the label componentes
406
413
  n_comps = len(line_list)
@@ -429,7 +436,7 @@ def label_composition(line_list, ref_df=None, default_profile=None):
429
436
  particle[i] = Particle.from_label(line_items[0])
430
437
 
431
438
  # Wavelength properties
432
- wavelength[i], units_wave[i] = check_units_from_wave(line, line_items[0], line_items[1], ref_df)
439
+ wavelength[i], units_wave[i] = check_units_from_wave(line, line_items[0], line_items[1], bands, ref_bands)
433
440
 
434
441
  # Split the optional components: "H1_1216A_t-rec_k-0_p-g" -> {'t': 'rec', 'k': '0', 'p': 'g'} # TODO better do that with optional_comps
435
442
  comp_conf = {optC[0]: optC[2:] for optC in line_items[2:]}
@@ -452,8 +459,8 @@ def label_composition(line_list, ref_df=None, default_profile=None):
452
459
  trans = comp_conf.get('t', None)
453
460
 
454
461
  # If none is provided check from the table
455
- if (trans is None) and (ref_df is not None):
456
- trans = pd_get(ref_df, line, 'transition')
462
+ if (trans is None) and (bands is not None):
463
+ trans = pd_get(bands, line, 'transition')
457
464
 
458
465
  # Else assume default
459
466
  if trans is None:
@@ -607,27 +614,48 @@ def format_line_mask_option(entry_value, wave_array):
607
614
  return formatted_value
608
615
 
609
616
 
610
- def bands_from_frame(frame, lines_level='line', sort=True):
617
+ def bands_from_measurements(frame, sample_levels=['id', 'line'], sort=True, remove_empty_columns=False, index_dict=None,
618
+ bands_hdrs=('wavelength', 'w1', 'w2', 'w3', 'w4', 'w5', 'w6', 'group_label', 'units_wave', 'particle',
619
+ 'transition')):
611
620
 
612
- # Empty container to store the data
613
- headers = ['wavelength', 'w1', 'w2', 'w3', 'w4', 'w5', 'w6', 'group_label', 'latex_label', 'units_wave', 'particle', 'transition']
614
- bands = pd.DataFrame(columns=headers)
621
+ # Load the frame if necessary
622
+ frame = check_file_dataframe(frame, sample_levels=sample_levels)
615
623
 
616
624
  # Single frame
617
625
  if not isinstance(frame.index, pd.MultiIndex):
618
- print('bicho')
626
+
627
+ # Make dataframe with single lines
628
+ idcs_single = frame.group_label == 'none'
629
+ bands = frame.reindex(index=frame.loc[idcs_single].index, columns=bands_hdrs)
630
+
631
+ # Get the indexes of grouped lines
632
+ group_labels_arr = frame.loc[~idcs_single].group_label
633
+ unique_values, unique_indices = np.unique(group_labels_arr, return_index=True)
634
+ group_labels_arr = group_labels_arr.iloc[np.sort(unique_indices)]
635
+
636
+ # Make dataframe with grouped lines and add blended suffix
637
+ bands_group = frame.reindex(index=group_labels_arr.index, columns=bands_hdrs)
638
+ blended_arr = bands_group.loc[~bands_group.index.to_series().str.endswith('_m')].index.to_numpy()
639
+ bands_group.rename(index={key: f"{key}_b" for key in blended_arr}, inplace=True)
640
+
641
+ # Combine the dataframes
642
+ bands = pd.concat([bands, bands_group], axis=0)
619
643
 
620
644
  # Multi-index
621
645
  else:
622
646
 
623
- line_list = frame.index.get_level_values(lines_level).unique()
647
+ line_list = frame.index.get_level_values(sample_levels).unique()
648
+
649
+ idcs_single = frame.group_label == 'none'
650
+ bands = frame.reindex(index=frame.loc[idcs_single].index, columns=bands_hdrs)
651
+
624
652
 
625
653
  # Loop through the lines
626
654
  for i, line_label in enumerate(line_list):
627
655
 
628
656
  # Exclude kinematic components:
629
657
  if '_k-' not in line_label:
630
- df_line = frame.xs(line_label, level=lines_level, drop_level=False)
658
+ df_line = frame.xs(line_label, level=sample_levels, drop_level=False)
631
659
  group_list = df_line.group_label.unique()
632
660
 
633
661
  for j, group in enumerate(group_list):
@@ -673,12 +701,16 @@ def bands_from_frame(frame, lines_level='line', sort=True):
673
701
  bands.loc[line.label, 'latex_label'] = line.latex_label
674
702
  bands.loc[line.label, 'units_wave'] = line.units_wave[line._ref_idx]
675
703
  bands.loc[line.label, 'particle'] = line.particle[line._ref_idx]
676
- # bands.loc[line.label, 'transition'] = line.transition
677
-
678
704
 
679
705
  if sort:
680
706
  bands.sort_values(by=['wavelength', 'group_label'], inplace=True)
681
707
 
708
+ if remove_empty_columns:
709
+ bands = bands.dropna(axis=1, how='all')
710
+
711
+ if index_dict is not None:
712
+ bands.rename(index=index_dict, inplace=True)
713
+
682
714
  return bands
683
715
 
684
716
 
@@ -734,7 +766,7 @@ class Particle:
734
766
  class Line:
735
767
 
736
768
  def __init__(self, label, band=None, fit_conf=None, profile=None, cont_from_bands=True, z_line=None,
737
- update_latex=False, interpret=True):
769
+ update_latex=False, ref_bands=None, interpret=True):
738
770
 
739
771
  # Label attributes
740
772
  self.label = label
@@ -796,7 +828,7 @@ class Line:
796
828
 
797
829
  # Interpret the line from the user reference
798
830
  if interpret:
799
- self._from_label(label, band, fit_conf, update_latex)
831
+ self._from_label(label, band, fit_conf, update_latex, ref_bands)
800
832
 
801
833
  return
802
834
 
@@ -808,7 +840,7 @@ class Line:
808
840
 
809
841
  return self.label
810
842
 
811
- def _from_label(self, label, band=None, fit_conf=None, update_latex=False):
843
+ def _from_label(self, label, band=None, fit_conf=None, update_latex=False, ref_bands=None):
812
844
 
813
845
  # If band is not provided use default database
814
846
  if band is None:
@@ -830,8 +862,8 @@ class Line:
830
862
  self._modularity_component(modularity_comp, fit_conf, band)
831
863
 
832
864
  # Review the components of the line
833
- ref_bands_df = band if isinstance(band, DataFrame) else None
834
- items = label_composition(self.list_comps, ref_df=ref_bands_df, default_profile=self.profile_comp)
865
+ items = label_composition(self.list_comps, bands=band if isinstance(band, DataFrame) else None,
866
+ default_profile=self.profile_comp)
835
867
  self.particle, self.wavelength, self.units_wave, self.kinem, self.profile_comp, self.transition_comp = items
836
868
 
837
869
  # Quick elements for the line profile
lime/workflow.py CHANGED
@@ -13,7 +13,9 @@ from lime.transitions import Line, air_to_vacuum_function, label_decomposition
13
13
  from lime.io import check_file_dataframe, check_file_array_mask, log_to_HDU, results_to_log, load_frame, LiMe_Error, check_fit_conf, _PARENT_BANDS
14
14
  from lime.fitting.redshift import RedshiftFitting
15
15
  from lime import __version__
16
- from itertools import combinations
16
+ from scipy.sparse import csr_matrix, csgraph
17
+
18
+
17
19
 
18
20
  try:
19
21
  import aspect
@@ -526,7 +528,7 @@ def get_merged_blended_lines(spec, bands, in_cfg, composite_lines):
526
528
  #
527
529
  # return output_dict
528
530
 
529
- def pars_bands_conf(spec, bands, ref_bands, fit_conf, composite_lines):
531
+ def pars_bands_conf(spec, bands, ref_bands, fit_conf, composite_lines, automatic_grouping=False):
530
532
 
531
533
  # Get the the grouped lines
532
534
  comps_dict = {} if fit_conf is False else {comp: group_label
@@ -537,6 +539,31 @@ def pars_bands_conf(spec, bands, ref_bands, fit_conf, composite_lines):
537
539
  if composite_lines is not None:
538
540
  comps_dict = {line: comps for line, comps in comps_dict.items() if line in composite_lines}
539
541
 
542
+ if automatic_grouping:
543
+
544
+ # Get candidate lines for grouping
545
+ line_arr = [item for v in comps_dict.values() for item in v.split('+')]
546
+ line_arr = bands.loc[bands.index.isin(line_arr)].index
547
+ lambda_arr = bands.loc[line_arr, 'wavelength'].to_numpy()
548
+
549
+ # Generate the line - wavelength matrix with the line pixels width
550
+ wave_matrix = np.zeros((lambda_arr.size, spec.wave_rest.data.size))
551
+ w3_arr = np.searchsorted(spec.wave_rest.data, bands.loc[line_arr, 'w3'].to_numpy())
552
+ w4_arr = np.searchsorted(spec.wave_rest.data, bands.loc[line_arr, 'w4'].to_numpy())
553
+ cols = np.arange(wave_matrix.shape[1])
554
+ wave_matrix[(cols >= w3_arr[:, None]) & (cols <= w4_arr[:, None])] = 1
555
+ pixels_width = wave_matrix.sum(axis=1)
556
+
557
+ # Get the decision matrix with the matching intervals
558
+ decision_matrix = wave_matrix @ wave_matrix.T
559
+ blended_matrix = decision_matrix < np.ceil(pixels_width/3)[:, None]
560
+ math_dict = dict(zip(line_arr, np.arange(line_arr.size)))
561
+
562
+ # Get groups of common entries
563
+ _, auto_labels = csgraph.connected_components(csgraph=csr_matrix(decision_matrix > 1), directed=False)
564
+ for labels, group in zip(line_arr, auto_labels):
565
+ print(f"{labels} {group}")
566
+
540
567
  # Loop through the lines on the list and review which changes are necessary
541
568
  rename_dict, exclude_list= {}, []
542
569
  group_dict, w3_dict, w4_dict = {}, {}, {}
@@ -811,7 +838,8 @@ class SpecTreatment(LineFitting, RedshiftFitting):
811
838
  self._n_lines = 0
812
839
 
813
840
  def bands(self, label, bands=None, fit_cfg=None, min_method='least_squares', profile='g-emi', cont_from_bands=True,
814
- err_from_bands=None, temp=10000.0, default_cfg_prefix='default', obj_cfg_prefix=None, update_default=True):
841
+ err_from_bands=None, temp=10000.0, default_cfg_prefix='default', obj_cfg_prefix=None, update_default=True,
842
+ ref_bands=None):
815
843
 
816
844
  """
817
845
 
@@ -883,7 +911,7 @@ class SpecTreatment(LineFitting, RedshiftFitting):
883
911
  cont_from_bands = True if cont_from_bands is None else cont_from_bands
884
912
 
885
913
  # Interpret the input line
886
- self.line = Line(label, bands, input_conf, profile, cont_from_bands)
914
+ self.line = Line(label, bands, input_conf, profile, cont_from_bands, ref_bands=ref_bands)
887
915
 
888
916
  # Check the line selection is valid
889
917
  idcs_selection = review_bands(self._spec, self.line, user_cont_from_bands=cont_from_bands, user_err_from_bands=err_from_bands)
@@ -925,7 +953,7 @@ class SpecTreatment(LineFitting, RedshiftFitting):
925
953
 
926
954
  def frame(self, bands, fit_cfg=None, min_method='least_squares', profile='g-emi', cont_from_bands=None, err_from_bands=None,
927
955
  temp=10000.0, line_list=None, default_cfg_prefix='default', obj_cfg_prefix=None, update_default=True, line_detection=False,
928
- plot_fit=False, progress_output='bar'):
956
+ plot_fit=False, progress_output='bar', ref_bands=None):
929
957
 
930
958
  """
931
959
 
@@ -1053,8 +1081,7 @@ class SpecTreatment(LineFitting, RedshiftFitting):
1053
1081
  # Fit the lines
1054
1082
  self.bands(line, bands, input_conf, min_method, profile,
1055
1083
  cont_from_bands=cont_from_bands, err_from_bands=err_from_bands,
1056
- temp=temp,
1057
- obj_cfg_prefix=None, default_cfg_prefix=None)
1084
+ temp=temp, obj_cfg_prefix=None, default_cfg_prefix=None, ref_bands=ref_bands)
1058
1085
 
1059
1086
  if plot_fit:
1060
1087
  self._spec.plot.bands()
@@ -1069,7 +1096,7 @@ class SpecTreatment(LineFitting, RedshiftFitting):
1069
1096
  return
1070
1097
 
1071
1098
  def continuum(self, degree_list, emis_threshold, abs_threshold=None, smooth_length=None, plot_steps=False,
1072
- log_scale=False):
1099
+ **kwargs):
1073
1100
 
1074
1101
  """
1075
1102
 
@@ -1134,7 +1161,7 @@ class SpecTreatment(LineFitting, RedshiftFitting):
1134
1161
  if plot_steps:
1135
1162
  ax_cfg = {'title':f'Continuum fitting, iteration ({i+1}/{len(degree_list)})'}
1136
1163
  self._spec.plot._continuum_iteration(input_wave, input_flux, cont_fit, input_flux_s, mask_cont, low_lim,
1137
- high_lim, emis_threshold[i], ax_cfg, log_scale=log_scale)
1164
+ high_lim, emis_threshold[i], ax_cfg, **kwargs)
1138
1165
 
1139
1166
  # Include the standard deviation of the spectrum for the unmasked pixels
1140
1167
  self._spec.cont = np.ma.masked_array(cont_fit, self._spec.flux.mask)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lime-stable
3
- Version: 2.0.dev4
3
+ Version: 2.0.dev7
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
@@ -19,7 +19,7 @@ Requires-Dist: aspect-stable~=0.3.1
19
19
  Requires-Dist: tomli>=2.0.0; python_version < "3.11"
20
20
  Provides-Extra: full
21
21
  Requires-Dist: asdf~=4.1; extra == "full"
22
- Requires-Dist: bokeh~=3.6; extra == "full"
22
+ Requires-Dist: bokeh~=3.7; extra == "full"
23
23
  Requires-Dist: mplcursors~=0.6; extra == "full"
24
24
  Requires-Dist: openpyxl~=3.1; extra == "full"
25
25
  Requires-Dist: PyLaTeX~=1.4; extra == "full"
@@ -1,26 +1,26 @@
1
- lime/__init__.py,sha256=XSdB2p1Pa7xRN2kUGBkjQ1DLBlf8PEdpRP6e7fmnA-I,1272
2
- lime/changelog.txt,sha256=odmxX7BukbjvY3HLcWe3_kwvY3Lgf8fgG7xdy43B_Bk,9620
1
+ lime/__init__.py,sha256=i7iYJDoAYCsApb35OG33Y0repT6FpgLCsFMTk0Nu0rM,1279
2
+ lime/changelog.txt,sha256=e02y8w_romtBvZLt7wrDxfL0Kry5ccTUkaajX_XwHbs,9657
3
3
  lime/io.py,sha256=9bH51m6DAYXJBLDXVQLmaOB0xw6_XoJiMoZlXDQ7Up4,32708
4
- lime/lime.toml,sha256=27Au6pwBvRINCnatm4AzhCoYZOSwCf5cUouHKe0c6hw,52
4
+ lime/lime.toml,sha256=YZ4ISkDoWKzFX_o2XTfOzUGpZQvFYzH0ee1dkLaEcV4,52
5
5
  lime/observations.py,sha256=2XV1V7FsQYwi1plcGPHATNeLn-XccUhcKtzjds3D3YU,64552
6
- lime/tools.py,sha256=WbCHcRipTDmYtVGShziL060tmq3PhXCHUzKrKf1xwQA,33922
7
- lime/transitions.py,sha256=5fZt3ogpfhRU-I1kS0LSlnKb3SsJdhF1IX7kkA0kWLA,43881
8
- lime/workflow.py,sha256=nLbPkPKUFQQVCADOO62V4NtNq2lbJgSOaeGtE9Jioqc,64046
6
+ lime/tools.py,sha256=5E7_YeXeLrzFo-2h5EAI8J-cPaK4i6g6OJ1uL2jXJ7o,34136
7
+ lime/transitions.py,sha256=-TgVCqL9R0-i4o8jHQGkGllHrMyP-Bo9DqoWfVNxHe4,45634
8
+ lime/workflow.py,sha256=x3es6Sgq9wI785WdTsIuOWnWvKdSYdnJHN_MSkIhcxM,65417
9
9
  lime/archives/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- lime/archives/read_fits.py,sha256=rYySOtmDTGaebwJUBrgwN0O_8qj6LBuUIiPlVBgUOg4,37945
10
+ lime/archives/read_fits.py,sha256=TIJtP8iGokjE9Nj1ALYb7MHE2lUxZGHuz6cx5SZBb5U,37944
11
11
  lime/archives/tables.py,sha256=sn3dINNorARuhEA_NsUKnhz8pT5d9u_sNBNKgv3EtuA,14242
12
12
  lime/fitting/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- lime/fitting/lines.py,sha256=Rh3UkMWe6V9UI2zyffEClx-hpcdo-nRjAGX6qlC5shU,41336
13
+ lime/fitting/lines.py,sha256=X1QI9ZqpLfxvfVsOMWMDmwLxXhR_gNvIYBdFA7A2GD8,41288
14
14
  lime/fitting/redshift.py,sha256=JBYQlESr63PrF3gRYVT-QLQr2yMi9YZKaeNxI8gFvgY,11639
15
15
  lime/inference/detection.py,sha256=m_Yz-53RGEvQQa6rgS_-nU2DwiRG45yuZIuneAvs0Dc,425
16
- lime/inference/intensity_threshold.py,sha256=2VW-uEQb6dLvxpNdC_l6myvjfX3YpczTAoLms6ws23k,8532
16
+ lime/inference/intensity_threshold.py,sha256=9LqSjJQajfrM4SOHSqJWnverKFnrdrjnV472w56T79U,5047
17
17
  lime/plotting/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- lime/plotting/bokeh_plots.py,sha256=lprs6omdNkeu60iIT-mLlGA6Nn9NomL8qosSbfplsPc,23257
19
- lime/plotting/format.py,sha256=bawpN8G_zHS8ujRapdaBvaYzjBgESssysL70MV8s_9M,7211
20
- lime/plotting/plots.py,sha256=J75hgxXimJPOMef79dbEbSUEsNoDO14-1BEbH-Zb2QM,87610
18
+ lime/plotting/bokeh_plots.py,sha256=xJlFS90NW-kQLSBDrfYd3tkjixyHK0DUs1vm5ExmjOQ,23444
19
+ lime/plotting/format.py,sha256=KM2UjhfJ7j2iEOIRTn8JfpLNmSRSlUFTHqB5EKxTq80,7296
20
+ lime/plotting/plots.py,sha256=LruFJvee0NcZ8wkMeb_-bMqonwLsgB6cF92aPqZRAOk,87496
21
21
  lime/plotting/plots_interactive.py,sha256=wX_ckpv_lM6YgZZFEfx36dfGt1DpTB5e-R3DyDkWgMY,73357
22
22
  lime/plotting/theme_lime.toml,sha256=moyNfRDx0x0Gma3azW_DNfLQoKd1qnBRiX0DAkioBbs,3396
23
- lime/plotting/utils.py,sha256=LzSDvEb1IrPGmr2nlrq_8jl34al_0yiLSz3t4y6-Kdo,2081
23
+ lime/plotting/utils.py,sha256=tPBXCvMlg7_onLWN9O2I7FoAurzkfF0Qe-JVYSLb1Xs,2069
24
24
  lime/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  lime/resources/lines_database_formatting.py,sha256=vhdanmwQ9ih0eERRAOCUeIQoc-VmTdHv7GseWxDavoA,4357
26
26
  lime/resources/lines_database_v2.0.0.txt,sha256=grhl32eDMtHHnXOw-LVmSCPbYjqUgccJ8ba3d3vDnHI,30855
@@ -28,8 +28,8 @@ lime/resources/logo.py,sha256=buuJuwCjw0JFFtqvvIBpjb-LCuFgBwRFqnVOqCmSam8,3296
28
28
  lime/resources/types_params.txt,sha256=HgK-rr_cHnjNR3oLJomdYt6UvKwBn-FMWt40MkCR0wM,10111
29
29
  lime/retrieve/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  lime/retrieve/peaks.py,sha256=zERD2-GO-AtNrx8xwGqNbml0pB0nTCH4Oro-9fr69GU,8713
31
- lime_stable-2.0.dev4.dist-info/licenses/LICENSE.rst,sha256=B_kw459IXvKujLR27bhVUwXmTq9AM72DdCA1xGgBMKI,35608
32
- lime_stable-2.0.dev4.dist-info/METADATA,sha256=_Yl-LUpWXHMmwO3BvF338kqZ-w7mPKIOFQMTXfgftKQ,3931
33
- lime_stable-2.0.dev4.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
34
- lime_stable-2.0.dev4.dist-info/top_level.txt,sha256=F6pWR5Cgjf9EkXNBZlUSKFKcPG8vPzM08QwYFfwpsZc,5
35
- lime_stable-2.0.dev4.dist-info/RECORD,,
31
+ lime_stable-2.0.dev7.dist-info/licenses/LICENSE.rst,sha256=B_kw459IXvKujLR27bhVUwXmTq9AM72DdCA1xGgBMKI,35608
32
+ lime_stable-2.0.dev7.dist-info/METADATA,sha256=pvUxdCfhIskIQOvSpTUiTH4EQdn8ejTi_WiPfedg4ag,3931
33
+ lime_stable-2.0.dev7.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
34
+ lime_stable-2.0.dev7.dist-info/top_level.txt,sha256=F6pWR5Cgjf9EkXNBZlUSKFKcPG8vPzM08QwYFfwpsZc,5
35
+ lime_stable-2.0.dev7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.1)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5