def _test(verbose=True, debug=False, plot=True, warnings=True, *args, **kwargs): """Test procedures. Parameters ---------- debug: boolean swamps the console namespace with local variables. Default ``False`` """ import matplotlib.pyplot as plt from numpy import linspace, loadtxt from radis.phys.convert import cm2nm, nm2cm from radis.test.utils import getTestFile # Test even resampling w_nm, I_nm = loadtxt(getTestFile("spectrum.txt")).T w_cm, I_cm = resample_even( nm2cm(w_nm), I_nm, resfactor=2, energy_threshold=1e-3, print_conservation=verbose, ) if plot: plt.figure() plt.xlabel("Wavelength (nm)") plt.ylabel("Intensity") plt.plot(w_nm, I_nm, "-ok", label="original") plt.plot(cm2nm(w_cm), I_cm, "-or", label="resampled") plt.legend() # Test resampling w_crop = linspace(376, 381, 100) I_crop = resample(w_nm, I_nm, w_crop, energy_threshold=0.01) if plot: plt.figure() plt.xlabel("Wavelength (nm)") plt.ylabel("Intensity") plt.plot(w_nm, I_nm, "-ok", label="original") plt.plot(w_crop, I_crop, "-or", label="resampled") plt.legend() if debug: globals().update(locals()) if warnings: warn("Testing resampling: no quantitative tests defined yet") return True # no standard tests yet
def calc_radiance(wavenumber, emissivity, Tgas, unit="mW/sr/cm2/nm"): """Derive radiance (mW/cm2/sr/nm) from the emissivity. Returns ------- radiance: mW/sr/cm2/nm """ radiance = emissivity * planck(cm2nm(wavenumber), Tgas, unit=unit) return radiance
def calc_radiance(wavenumber, emissivity, Tgas, unit='mW/sr/cm2/nm'): ''' Derive radiance (mW/cm2/sr/nm) from the emissivity Returns ------- radiance: mW/sr/cm2/nm ''' radiance = emissivity * planck(cm2nm(wavenumber), Tgas, unit=unit) return radiance
def _test(verbose=True, debug=False, plot=True, warnings=True, *args, **kwargs): ''' Test procedures Parameters ---------- debug: boolean swamps the console namespace with local variables. Default False ''' from neq.test.utils import getTestFile from radis.phys.convert import nm2cm, cm2nm import matplotlib.pyplot as plt from numpy import loadtxt, linspace # Test even resampling w_nm, I_nm = loadtxt(getTestFile('spectrum.txt')).T w_cm, I_cm = resample_even(nm2cm(w_nm), I_nm, resfactor=2, energy_threshold=1e-3, print_conservation=verbose) if plot: plt.figure() plt.xlabel('Wavelength (nm)') plt.ylabel('Intensity') plt.plot(w_nm, I_nm, '-ok', label='original') plt.plot(cm2nm(w_cm), I_cm, '-or', label='resampled') plt.legend() # Test resampling w_crop = linspace(376, 381,100) I_crop = resample(w_nm, I_nm, w_crop, energy_threshold=0.01) if plot: plt.figure() plt.xlabel('Wavelength (nm)') plt.ylabel('Intensity') plt.plot(w_nm, I_nm, '-ok', label='original') plt.plot(w_crop, I_crop, '-or', label='resampled') plt.legend() if debug: globals().update(locals()) if warnings: warn('Testing resampling: no quantitative tests defined yet') return True # no standard tests yet
def get_x(w): ''' w (input) is supposed to be in vacuum wavenumbers in the lines database ''' # Convert wavelength / wavenumber if wunit == 'cm-1': x = w elif wunit == 'nm': x = cm2nm(w) # Correct if requested medium is air if medium == 'air': x = vacuum2air(x) elif medium == 'vacuum': pass else: raise ValueError('Unknown medium: {0}'.format(medium)) else: raise ValueError('Unknown wunit: {0}'.format(wunit)) return x
def get_x(w): """w (input) is supposed to be in vacuum wavenumbers in the lines database.""" # Convert wavelength / wavenumber if wunit == "cm-1": x = w elif wunit == "nm": x = cm2nm(w) # Correct if requested medium is air if medium == "air": x = vacuum2air(x) elif medium == "vacuum": pass else: raise ValueError("Unknown medium: {0}".format(medium)) else: raise ValueError("Unknown wunit: {0}".format(wunit)) return x
def calc_radiance(wavenumber, emissivity, Tgas, unit="mW/sr/cm2/nm"): """Derive radiance (:math:`mW/cm^2/sr/nm`) from the emissivity. .. math:: I(\lambda) = \epsilon(\lambda) \cdot L_0(\lambda, T) Returns ------- radiance: array in :math:`mW/sr/cm2/nm` unless ``unit`` is different See Also -------- :py:func:`~radis.phys.blackbody.planck` """ radiance = emissivity * planck(cm2nm(wavenumber), Tgas, unit=unit) return radiance
def crop(s, wmin=None, wmax=None, wunit=None, inplace=False): # type: (Spectrum, float, float, str, str, bool) -> Spectrum """Crop spectrum to ``wmin-wmax`` range in ``wunit`` Parameters ---------- s: Spectrum object object to crop wmin, wmax: float, or None boundaries of spectral range (in ``wunit``) wunit: ``'nm'``, ``'cm-1'``, ``'nm_vac'`` which waveunit to use for ``wmin, wmax``. If ``default``: use the default Spectrum wavespace defined with :meth:`~radis.spectrum.spectrum.Spectrum.get_waveunit`. Other Parameters ---------------- inplace: bool if ``True``, modifiy ``s`` directly. Else, returns a copy. Returns ------- s_crop: Spectrum a cropped Spectrum. if using ``inplace``, then ``s_crop`` and ``s`` are still the same object Examples -------- :: crop(s, 420, 480, 'nm', 'air') Or in ``cm-1``:: crop(s, 2000, 2300, 'cm-1') """ # Check inputs if wmin is None and wmax is None: raise ValueError("Choose at least `wmin=` or `wmax=`") if wunit is None: raise ValueError("Please precise unit for wmin and wmax with `unit=`") assert wunit in ["nm", "cm-1"] if (wmin is not None and wmax is not None) and wmin >= wmax: raise ValueError( "wmin should be < wmax (Got: {0:.2f}, {1:.2f})".format(wmin, wmax)) if len(s._q) > 0 and len(s._q_conv) > 0: raise NotImplementedError( "Cant crop this Spectrum as there are both convoluted " + "and not convoluted quantities stored") # Could bring unexpected errors... For instance, if cropping both # with slit and without slit quantities to the same waverange, # reapplying the slit in 'valid' mode would reduce the wavelength range # of the convoluted quantities # Implementation: better ask User to drop some of the quantities themselves if not inplace: s = s.copy() # Convert wmin, wmax to Spectrum wavespace (stored_waveunit) # (deal with cases where wavelength are given in 'air' or 'vacuum') # TODO @dev: rewrite with wunit='cm-1', 'nm_air', 'nm_vac' stored_waveunit = s.get_waveunit() wmin0, wmax0 = wmin, wmax if stored_waveunit == "cm-1": # convert wmin, wmax to wavenumber if wunit == "nm": if wmax0: wmin = nm_air2cm(wmax0) # note: min/max inverted if wmin0: wmax = nm_air2cm(wmin0) # note: min/max inverted elif wunit == "nm_vac": if wmax0: wmin = nm2cm(wmax0) # note: min/max inverted if wmin0: wmax = nm2cm(wmin0) # note: min/max inverted elif wunit == "cm-1": pass else: raise ValueError(wunit) elif stored_waveunit == "nm": # convert wmin, wmax to wavelength air if wunit == "nm": pass elif wunit == "nm_vac": if wmin0: wmin = vacuum2air(wmin0) if wmax0: wmax = vacuum2air(wmax0) elif wunit == "cm-1": if wmax0: wmin = cm2nm_air(wmax0) # note: min/max inverted if wmin0: wmax = cm2nm_air(wmin0) # note: min/max inverted else: raise ValueError(wunit) elif stored_waveunit == "nm_vac": # convert wmin, wmax to wavelength vacuum if wunit == "nm": if wmin0: wmin = air2vacuum(wmin0) if wmax0: wmax = air2vacuum(wmax0) elif wunit == "nm_vac": pass elif wunit == "cm-1": if wmax0: wmin = cm2nm(wmax0) # note: min/max inverted if wmin0: wmax = cm2nm(wmin0) # note: min/max inverted else: raise ValueError(wunit) else: raise ValueError(stored_waveunit) # Crop non convoluted if len(s._q) > 0: b = ones_like(s._q["wavespace"], dtype=bool) if wmin: b *= wmin <= s._q["wavespace"] if wmax: b *= s._q["wavespace"] <= wmax for k, v in s._q.items(): s._q[k] = v[b] # Crop convoluted if len(s._q_conv) > 0: b = ones_like(s._q_conv["wavespace"], dtype=bool) if wmin: b *= wmin <= s._q_conv["wavespace"] if wmax: b *= s._q_conv["wavespace"] <= wmax for k, v in s._q_conv.items(): s._q_conv[k] = v[b] return s
def test_optically_thick_limit_1iso(verbose=True, plot=True, *args, **kwargs): ''' Test that we find Planck in the optically thick limit In particular, this test will fail if : - linestrength are not properly calculated - at noneq, linestrength and emission integrals are mixed up The test should be run for 1 and several isotopes, because different calculations paths are used internally, and this can lead to different errors. Also, this test is used to run with DEBUG_MODE = True, which will check that isotopes and molecule ids are what we expect in all the groupby() loops that make the production code very fast. Notes ----- switched from large band calculation with [HITRAN-2016]_ to a calculation with the embedded [HITEMP-2010]_ fragment (shorter range, but no need to download files) ''' if plot: # Make sure matplotlib is interactive so that test are not stuck in pytest plt.ion() # Force DEBUG_MODE DEBUG_MODE = radis.DEBUG_MODE radis.DEBUG_MODE = True try: # wavelength_min = 4500 # wavelength_max = 4800 wavelength_min = cm2nm(2284.6) wavelength_max = cm2nm(2284.2) P = 0.017 # bar wstep = 0.001 # cm-1 Tgas = 1200 # %% Generate some CO2 emission spectra # -------------- sf = SpectrumFactory( wavelength_min=wavelength_min, wavelength_max=wavelength_max, molecule='CO2', mole_fraction=1, path_length=0.05, cutoff=1e-25, broadening_max_width=1, export_populations=False, #'vib', export_lines=False, isotope=1, use_cached=True, wstep=wstep, pseudo_continuum_threshold=0, pressure=P, verbose=False) # sf.fetch_databank('astroquery') sf.warnings['NegativeEnergiesWarning'] = 'ignore' sf.load_databank('HITEMP-CO2-TEST') pb = ProgressBar(3, active=verbose) s_eq = sf.eq_spectrum(Tgas=Tgas, mole_fraction=1, name='Equilibrium') pb.update(1) s_2T = sf.non_eq_spectrum(Tvib=Tgas, Trot=Tgas, mole_fraction=1, name='Noneq (2T)') pb.update(2) s_4T = sf.non_eq_spectrum(Tvib=(Tgas, Tgas, Tgas), Trot=Tgas, mole_fraction=1, name='Noneq (4T)') pb.update(3) s_plck = sPlanck( wavelength_min=2000, #=wavelength_min, wavelength_max= 5000, #=wavelength_max - wstep, # there is a border effect on last point T=Tgas) pb.done() # %% Post process: # MAke optically thick, and compare with Planck for s in [s_eq, s_2T, s_4T]: s.rescale_path_length(1e6) if plot: nfig = 'test_opt_thick_limit_1iso {0}'.format(s.name) plt.figure(nfig).clear() s.plot(wunit='nm', nfig=nfig, lw=4) s_plck.plot(wunit='nm', nfig=nfig, Iunit='mW/cm2/sr/nm', lw=2) plt.legend() if verbose: printm( "Residual between opt. thick CO2 spectrum ({0}) and Planck: {1:.2g}" .format( s.name, get_residual(s, s_plck, 'radiance_noslit', ignore_nan=True))) # assert get_residual(s, s_plck, 'radiance_noslit', ignore_nan=True) < 1e-3 assert get_residual(s, s_plck, 'radiance_noslit', ignore_nan=True) < 0.9e-4 if verbose: printm('Tested optically thick limit is Planck (1 isotope): OK') finally: # Reset DEBUG_MODE radis.DEBUG_MODE = DEBUG_MODE
def plot_slit(w, I=None, waveunit='', plot_unit='same', Iunit=None, warnings=True): ''' Plot slit, calculate and display FWHM, and calculate effective FWHM. FWHM is calculated from the limits of the range above the half width, while FWHM is the equivalent width of a triangular slit with the same area Parameters ---------- w, I: arrays or (str, None) if str, open file directly waveunit: 'nm', 'cm-1' or '' unit of input w plot_unit: 'nm, 'cm-1' or 'same' change plot unit (and FWHM units) Iunit: str, or None give Iunit warnings: boolean if True, test if slit is correctly centered and output a warning if it is not. Default True ''' try: from neq.plot.toolbar import add_tools # TODO: move in publib add_tools() # includes a Ruler to measure slit except: pass # Check input if isinstance(w, string_types) and I is None: w, I = np.loadtxt(w).T assert len(w) == len(I) if np.isnan(I).sum() > 0: warn('Slit function has nans') w = w[~np.isnan(I)] I = I[~np.isnan(I)] assert len(I) > 0 # cast units waveunit = cast_waveunit(waveunit, force_match=False) plot_unit = cast_waveunit(plot_unit, force_match=False) if plot_unit == 'same': plot_unit = waveunit # Convert wavespace unit if needed elif waveunit == 'cm-1' and plot_unit == 'nm': # wavelength > wavenumber w = cm2nm(w) waveunit = 'nm' elif waveunit == 'nm' and plot_unit == 'cm-1': # wavenumber > wavelength w = nm2cm(w) waveunit = 'cm-1' else: # same units pass # raise ValueError('Unknown plot unit: {0}'.format(plot_unit)) # Recalculate FWHM FWHM, xmin, xmax = get_FWHM(w, I, return_index=True) FWHM_eff = get_effective_FWHM(w, I) # Get labels if plot_unit == 'nm': xlabel = 'Wavelength (nm)' elif plot_unit == 'cm-1': xlabel = 'Wavenumber (cm-1)' elif plot_unit == '': xlabel = 'Wavespace' else: raise ValueError('Unknown unit for plot_unit: {0}'.format(plot_unit)) ylabel = 'Slit function' if Iunit is not None: ylabel += ' ({0})'.format(Iunit) fig, ax = plt.subplots() ax.plot(w, I, 'o', color='lightgrey') ax.plot(w, I, '-k', label='FWHM: {0:.3f} {1}'.format(FWHM, plot_unit)+\ '\nEff. FWHM: {0:.3f} {1}'.format(FWHM_eff, plot_unit)+\ '\nArea: {0:.3f}'.format(abs(np.trapz(I, x=w))), ) # Vertical lines on center, and FWHM plt.axvline(w[len(w) // 2], ls='-', lw=2, color='lightgrey') # center plt.axvline(w[(xmin + xmax) // 2], ls='--', color='k', lw=0.5) # maximum (should be center) plt.axvline(w[xmin], ls='--', color='k', lw=0.5) # FWHM min plt.axvline(w[xmax], ls='--', color='k', lw=0.5) # FWHM max plt.axhline(I.max() / 2, ls='--', color='k', lw=0.5) # half maximum ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) plt.legend(loc='best', prop={'size': 16}) # extend axis: fig.tight_layout() xlmin, xlmax = ax.get_xlim() ax.set_xlim((xlmin - 0.5, xlmax + 0.5)) if warnings: if w[(xmin + xmax) // 2] != w[len(w) // 2]: warn('Slit function doesnt seem centered (center measured with FWHM)'+\ ' is not the array center. This can induce offsets!') if I[0] != 0 or I[-1] != 0: warn('Slit function should have zeros on both sides') return fig, ax
def get_slit_function(slit_function, unit='nm', norm_by='area', shape='triangular', center_wavespace=None, return_unit='same', wstep=None, plot=False, resfactor=2, *args, **kwargs): ''' Import or generate slit function in correct wavespace Give a file path to import, or a float / tuple to generate arbitrary shapes Warning with units: read about unit and return_unit parameters. See :meth:`~radis.spectrum.spectrum.Spectrum.apply_slit` and :func:`~radis.tools.slit.convolve_with_slit` for more info Parameters ---------- slit_function: tuple, or str If float: generate slit function with FWHM of `slit_function` (in `unit`) If .txt file: import experimental slit function (in `unit`): format must be 2-columns with wavelengths and intensity (doesn't have to be normalized) unit: 'nm' or 'cm-1' unit of slit_function FWHM, or unit of imported file norm_by: 'area', 'max', or None how to normalize. `area` conserves energy. With `max` the slit is normalized so that its maximum is one (that is what is done in Specair: it changes the outptut spectrum unit, e.g. from 'mW/cm2/sr/µm' to 'mW/cm2/sr') None doesnt normalize. Default 'area' shape: 'triangular', 'trapezoidal', 'gaussian' which shape to use when generating a slit. Default 'triangular' center_wavespace: float, or None center of slit when generated (in unit). Not used if slit is imported. return_unit: 'nm', 'cm-1', or 'same' if not 'same', slit is converted to the given wavespace. wstep: float which discretization step to use (in return_unit) when generating a slit function. Not used if importing Other Parameters ---------------- resfactor: int resolution increase when resampling from nm to cm-1, or the other way round. Default 2. energy_threshold: float tolerance fraction. Only used when importing experimental slit as the theoretical slit functions are directly generated in spectrum wavespace Default 1e-3 (0.1%) Returns ------- wslit, Islit: array wslit is in `return_unit` . Islit is normalized according to `norm_by` Examples -------- >>> wslit, Islit = get_slit_function(1, 'nm', shape='triangular', center_wavespace=600, wstep=0.01) Returns a triangular slit function of FWHM = 1 nm, centered on 600 nm, with a step of 0.01 nm >>> wslit, Islit = get_slit_function(1, 'nm', shape='triangular', center_wavespace=600, return_unit='cm-1', wstep=0.01) Returns a triangular slit function expressed in cm-1, with a FWHM = 1 nm (converted in equivalent width in cm-1 at 600 nm), centered on 600 nm, with a step of 0.01 cm-1 (!) Notes ----- In norm_by 'max' mode, slit is normalized by slit max. In RADIS, this is done in the spectrum wavespace (to avoid errors that would be caused by interpolating the spectrum). A problem arise if the spectrum wavespace is different from the slit wavespace: typically, slit is in 'nm' but a spectrum calculated by RADIS is stored in 'cm-1': in that case, the convoluted spectrum is multiplied by /int(Islit*dν) instead of /int(Islit*dλ). The output unit is then [radiance]*[spec_unit] instead of [radiance]*[slit_unit], i.e, typically, [mW/cm2/sr/nm]*[cm-1] instead of [mW/cm2/sr/nm]*[nm]=[mW/cm2/sr] While this remains true if the units are taken into account, this is not the expected behaviour. For instance, Specair users are used to having a FWHM(nm) factor between spectra convolved with slit normalized by max and slit normalized by area. The norm_by='max' behaviour adds a correction factor `/int(Islit*dλ)/int(Islit*dν)` to maintain an output spectrum in [radiance]*[slit_unit] See Also -------- :meth:`~radis.spectrum.spectrum.Spectrum.apply_slit`, :func:`~radis.tools.slit.convolve_with_slit` ''' if 'waveunit' in kwargs: assert return_unit == 'same' # default return_unit = kwargs.pop('waveunit') warn(DeprecationWarning('waveunit renamed return_unit')) if 'slit_unit' in kwargs: assert unit == 'nm' # default unit = kwargs.pop('slit_unit') warn(DeprecationWarning('slit_unit renamed unit')) energy_threshold = kwargs.pop('energy_threshold', 1e-3) # type: float # tolerance fraction # when resampling (only used in experimental slit as the) # theoretical slit functions are directly generated in # spectrum wavespace def check_input_gen(): if center_wavespace is None: raise ValueError('center_wavespace has to be given when generating '+\ 'slit function') if wstep is None: raise ValueError('wstep has to be given when generating '+\ 'slit function') # Cast units if return_unit == 'same': return_unit = unit unit = cast_waveunit(unit) return_unit = cast_waveunit(return_unit) scale_slit = 1 # in norm_by=max mode, used to keep units in [Iunit]*return_unit in [Iunit]*unit # not used in norm_by=area mode # First get the slit in return_unit space if is_float(slit_function ): # Generate slit function (directly in return_unit space) check_input_gen() # ... first get FWHM in return_unit (it is in `unit` right now) FWHM = slit_function if return_unit == 'cm-1' and unit == 'nm': # center_wavespace ~ nm, FWHM ~ nm FWHM = dnm2dcm(FWHM, center_wavespace) # wavelength > wavenumber center_wavespace = nm2cm(center_wavespace) if norm_by == 'max': scale_slit = slit_function / FWHM # [unit/return_unit] elif return_unit == 'nm' and unit == 'cm-1': # center_wavespace ~ cm-1, FWHM ~ cm-1 FWHM = dcm2dnm(FWHM, center_wavespace) # wavenumber > wavelength center_wavespace = cm2nm(center_wavespace) if norm_by == 'max': scale_slit = slit_function / FWHM # [unit/return_unit] else: pass # correct unit already # Now FWHM is in 'return_unit' # ... now, build it (in our wavespace) if __debug__: printdbg( 'get_slit_function: {0} FWHM {1:.2f}{2}, center {3:.2f}{2}, norm_by {4}' .format(shape, FWHM, return_unit, center_wavespace, norm_by)) if shape == 'triangular': wslit, Islit = triangular_slit(FWHM, wstep, center=center_wavespace, bplot=plot, norm_by=norm_by, waveunit=return_unit, scale=scale_slit, *args, **kwargs) # Insert other slit shapes here # ... elif shape == 'gaussian': wslit, Islit = gaussian_slit(FWHM, wstep, center=center_wavespace, bplot=plot, norm_by=norm_by, waveunit=return_unit, scale=scale_slit, *args, **kwargs) elif shape == 'trapezoidal': raise TypeError( 'A (top, base) tuple must be given with a trapezoidal slit') else: raise TypeError( 'Slit function ({0}) not in known slit shapes: {1}'.format( shape, SLIT_SHAPES)) elif isinstance(slit_function, tuple): check_input_gen() try: top, base = slit_function except: raise TypeError( 'Wrong format for slit function: {0}'.format(slit_function)) if shape == 'trapezoidal': pass elif shape == 'triangular': # it's the default warn( 'Triangular slit given with a tuple: we used trapezoidal slit instead' ) shape = 'trapezoidal' else: raise TypeError( 'A (top, base) tuple must be used with a trapezoidal slit') # ... first get FWHM in our wavespace unit if return_unit == 'cm-1' and unit == 'nm': # center_wavespace ~ nm, FWHM ~ nm top = dnm2dcm(top, center_wavespace) # wavelength > wavenumber base = dnm2dcm(base, center_wavespace) # wavelength > wavenumber center_wavespace = nm2cm(center_wavespace) if norm_by == 'max': scale_slit = sum(slit_function) / (top + base ) # [unit/return_unit] elif return_unit == 'nm' and unit == 'cm-1': # center_wavespace ~ cm-1, FWHM ~ cm-1 top = dcm2dnm(top, center_wavespace) # wavenumber > wavelength base = dcm2dnm(base, center_wavespace) # wavenumber > wavelength center_wavespace = cm2nm(center_wavespace) if norm_by == 'max': scale_slit = sum(slit_function) / (top + base ) # [unit/return_unit] else: pass # correct unit already FWHM = (top + base) / 2 # ... now, build it (in our wavespace) if __debug__: printdbg( 'get_slit_function: {0}, FWHM {1:.2f}{2}, center {3:.2f}{2}, norm_by {4}' .format(shape, FWHM, return_unit, center_wavespace, norm_by)) wslit, Islit = trapezoidal_slit(top, base, wstep, center=center_wavespace, bplot=plot, norm_by=norm_by, waveunit=return_unit, scale=scale_slit, *args, **kwargs) elif isinstance(slit_function, string_types): # import it if __debug__: printdbg( 'get_slit_function: {0} in {1}, norm_by {2}, return in {3}'. format(slit_function, unit, norm_by, return_unit)) wslit, Islit = import_experimental_slit( slit_function, norm_by=norm_by, # norm is done later anyway waveunit=unit, bplot=False, # we will plot after resampling *args, **kwargs) # ... get unit # Normalize if norm_by == 'area': # normalize by the area # I_slit /= np.trapz(I_slit, x=w_slit) Iunit = '1/{0}'.format(unit) elif norm_by == 'max': # set maximum to 1 Iunit = '1' elif norm_by is None: Iunit = None else: raise ValueError( 'Unknown normalization type: `norm_by` = {0}'.format(norm_by)) # ... check it looks correct unq, counts = np.unique(wslit, return_counts=True) dup = counts > 1 if dup.sum() > 0: raise ValueError( 'Not all wavespace points are unique: slit function ' + 'format may be wrong. Duplicates for w={0}'.format(unq[dup])) # ... resample if needed if return_unit == 'cm-1' and unit == 'nm': # wavelength > wavenumber wold, Iold = wslit, Islit wslit, Islit = resample_even(nm2cm(wslit), Islit, resfactor=resfactor, energy_threshold=energy_threshold, print_conservation=True) scale_slit = trapz(Iold, wold) / trapz(Islit, wslit) # [unit/return_unit] renormalize = True elif return_unit == 'nm' and unit == 'cm-1': # wavenumber > wavelength wold, Iold = wslit, Islit wslit, Islit = resample_even(cm2nm(wslit), Islit, resfactor=resfactor, energy_threshold=energy_threshold, print_conservation=True) scale_slit = trapz(Iold, wold) / trapz(Islit, wslit) # [unit/return_unit] renormalize = True else: # return_unit == unit renormalize = False # Note: if wstep dont match with quantity it's alright as it gets # interpolated in the `convolve_with_slit` function # re-Normalize if needed (after changing units) if renormalize: if __debug__: printdbg('get_slit_function: renormalize') if norm_by == 'area': # normalize by the area Islit /= abs(np.trapz(Islit, x=wslit)) Iunit = '1/{0}'.format(return_unit) elif norm_by == 'max': # set maximum to 1 Islit /= abs(np.max(Islit)) Islit *= scale_slit Iunit = '1' if scale_slit != 1: Iunit += 'x{0}'.format(scale_slit) # elif norm_by == 'max2': # set maximum to 1 # removed this mode for simplification # Islit /= abs(np.max(Islit)) elif norm_by is None: Iunit = None else: raise ValueError( 'Unknown normalization type: `norm_by` = {0}'.format( norm_by)) if plot: # (plot after resampling / renormalizing) # Plot slit plot_slit(wslit, Islit, waveunit=return_unit, Iunit=Iunit) else: raise TypeError('Unexpected type for slit function: {0}'.format( type(slit_function))) return wslit, Islit
def crop(s, wmin=None, wmax=None, wunit=None, medium=None, inplace=False): # type: (Spectrum, float, float, str, str, bool) -> Spectrum ''' Crop spectrum to ``wmin-wmax`` range in ``wunit`` Parameters ---------- s: Spectrum object object to crop wmin, wmax: float, or None boundaries of spectral range (in ``wunit``) wunit: ``'nm'``, ``'cm-1'`` which waveunit to use for ``wmin, wmax``. Default ``default``: just use the Spectrum wavespace. medium: 'air', vacuum' necessary if cropping in 'nm' Other Parameters ---------------- inplace: bool if ``True``, modifiy ``s`` directly. Else, returns a copy. Returns ------- s_crop: Spectrum a cropped Spectrum. if using ``inplace``, then ``s_crop`` and ``s`` are still the same object Examples -------- :: crop(s, 420, 480, 'nm', 'air') Or in ``cm-1``:: crop(s, 2000, 2300, 'cm-1') ''' # Check inputs if wmin is None and wmax is None: raise ValueError('Choose at least `wmin=` or `wmax=`') if wunit is None: raise ValueError('Please precise unit for wmin and wmax with `unit=`') assert wunit in ['nm', 'cm-1'] if wunit == 'nm' and medium is None: raise ValueError("Precise wavelength medium with medium='air' or "+\ "medium='vacuum'") if (wmin is not None and wmax is not None) and wmin >= wmax: raise ValueError( 'wmin should be < wmax (Got: {0:.2f}, {1:.2f})'.format(wmin, wmax)) if len(s._q) > 0 and len(s._q_conv) > 0: raise NotImplementedError('Cant crop this Spectrum as there are both convoluted '+\ 'and not convoluted quantities stored') # Could bring unexpected errors... For instance, if cropping both # with slit and without slit quantities to the same waverange, # reapplying the slit in 'valid' mode would reduce the wavelength range # of the convoluted quantities # Implementation: better ask User to drop some of the quantities themselves if not inplace: s = s.copy() # Convert wmin, wmax to Spectrum wavespace # (deal with cases where wavelength are given in 'air' or 'vacuum') # TODO @dev: rewrite with wunit='cm-1', 'nm_air', 'nm_vac' waveunit = s.get_waveunit() wmin0, wmax0 = wmin, wmax if wunit == 'nm' and waveunit == 'cm-1': if medium == 'air': if wmax0: wmin = nm_air2cm(wmax0) # reverted if wmin0: wmax = nm_air2cm(wmin0) # reverted else: if wmax0: wmin = nm2cm(wmax0) # reverted if wmin0: wmax = nm2cm(wmin0) # reverted elif wunit == 'cm-1' and waveunit == 'nm': if s.get_medium() == 'air': if wmax0: wmin = cm2nm_air(wmax0) # nm in air if wmin0: wmax = cm2nm_air(wmin0) # nm in air else: if wmax0: wmin = cm2nm(wmax0) # get nm in vacuum if wmin0: wmax = cm2nm(wmin0) # get nm in vacuum elif wunit == 'nm' and waveunit == 'nm': if s.get_medium() == 'air' and medium == 'vacuum': # convert from given medium ('vacuum') to spectrum medium ('air') if wmin0: wmin = vacuum2air(wmin0) if wmax0: wmax = vacuum2air(wmax0) elif s.get_medium() == 'vacuum' and medium == 'air': # the other way around if wmin0: wmin = air2vacuum(wmin0) if wmax0: wmax = air2vacuum(wmax0) else: assert wunit == waveunit # correct wmin, wmax # Crop non convoluted if len(s._q) > 0: b = ones_like(s._q['wavespace'], dtype=bool) if wmin: b *= (wmin <= s._q['wavespace']) if wmax: b *= (s._q['wavespace'] <= wmax) for k, v in s._q.items(): s._q[k] = v[b] # Crop convoluted if len(s._q_conv) > 0: b = ones_like(s._q_conv['wavespace'], dtype=bool) if wmin: b *= (wmin <= s._q_conv['wavespace']) if wmax: b *= (s._q_conv['wavespace'] <= wmax) for k, v in s._q_conv.items(): s._q_conv[k] = v[b] return s