Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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 
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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