Beispiel #1
0
def estimate_local_background(x, y, cube, small_bin=3, big_bin=30):
    """
    Estimation of a local background spectrum. For a given position, the background spectrum is defined as the median taken in a ``big_bin x big_bin`` pixels box from which the ``small_bin x small_bin`` center pixels have been excluded.

    Parameters
    ----------
    x : int
        Abscisse position, in pixels
    y : int
        Ordinate position, in pixels
    cube : :class:`~ORCS:orcs.process.SpectralCube`
        SpectralCube instance where we are looking at the source
    small_bin : int
        (Optional) Size of the inner region. Default = 3
    big_bin : int
        (Optional) Size of the outter region. Default = 30

    Returns
    -------
    bkg_spec : 1D :class:`~numpy:numpy.ndarray`
        The corresponding spectrum
    """
    big_box = centered_square_region(x, y, b=big_bin)
    small_box = centered_square_region(x, y, b=small_bin)
    mask = np.zeros((cube.dimx, cube.dimy))
    mask[big_box] = 1
    mask[small_box] = 0
    _, bkg_spec = cube.extract_integrated_spectrum(np.nonzero(mask),
                                                   median=True,
                                                   mean_flux=True,
                                                   silent=True)
    return bkg_spec
Beispiel #2
0
def extract_point_source(x, y, cube, small_bin=3, medium_bin=None, big_bin=30):
    """
    Basic way to extract a point source spectra with the local background subtracted.
    For a given position xy, we sum the spectra extracted in a squared region of size small_bin**2 centered on x,y, and subtract from it the median spectra from a squared region of size big_bin**2 centered on x,y excluding the medium_bin**2 central area.

    Parameters
    ----------
    x : int
        Abscisse position, in pixels
    y : int
        Ordinate position, in pixels
    cube : :class:`~ORCS:orcs.process.SpectralCube`
        SpectralCube instance where we are looking at the source
    small_bin : int
        (Optional) Size of the inner region. Default = 3
    medium_bin : int
        (Optional) Size of the middle region. Default = small_bin
    big_bin : int
        (Optional) Size of the outter region. Default = 30

    Returns
    -------
    a : 1D :class:`~numpy:numpy.ndarray`
        The axis of the spectrum
    spec : 1D :class:`~numpy:numpy.ndarray`
        The source spectrum, background subtracted
    """
    small_box = centered_square_region(x, y, b=small_bin)
    if medium_bin is None:
        medium_bin = small_bin
    bkg_spec = estimate_local_background(x, y, cube, medium_bin, big_bin)
    a, s, n = cube.extract_integrated_spectrum(small_box,
                                               silent=True,
                                               return_spec_nb=True)
    return a, s - n * bkg_spec
Beispiel #3
0
def fit_source(xpos, ypos, cube, v_guess = None, return_v_guess=False, v_min=-800., v_max=100., **kwargs):
    """
    Fit a spectrum from a position in the cube.
    The spectrum is extracted inside a 3x3 pixels box, a guess of the velocity if performed, and the spectrum is fitted using :func:`fit_spectrum`

    Parameters
    ----------
    xpos : int
        abscisse of the source in the cube
    ypos : int
        ordonate of the source in the cube
    cube : :class:`~ORCS:orcs.process.SpectralCube`
        The cube in which we want to extract and fit the spectrum.
    v_guess : float
        (Optional) If None, a guess is performed using :func:`guess_source_velocity`
    return_v_guess : bool, Default = False
        (Optional) If True, returns the guessed velocity value.
    v_min : float
        (Optional) v_min used by :func:`guess_source_velocity`. Default = -800
    v_max : float
        (Optional) v_max used by :func:`guess_source_velocity`. Default = 100
    kwargs : dict
        Additional keyword arguments to be passed to :func:`fit_spectrum`
    """
    xpos = int(xpos)
    ypos = int(ypos)
    a,s = extract_point_source(xpos, ypos, cube)
    if v_guess is None:
        v_guess = guess_source_velocity(s, cube, v_min = v_min, v_max = v_max)
    if v_guess != None:
        small_box = centered_square_region(xpos, ypos, 3)
        theta_orig = np.nanmean(cube.get_theta_map()[small_box])
        fit_params = fit_spectrum(s, theta_orig, v_guess, cube, **kwargs)
        if return_v_guess:
            return [v_guess, fit_params]
        else:
            return fit_params
    else:
        return []
Beispiel #4
0
def analyse_source(source, cube, plot=False, return_fit_params=False):
    """
    Convenience method to spatially analyse a source.
    A 30x30 pixels 'flux map' is build from the sum of a few frames around each detected lines for the source.

    Two analysis are then performed:

    * Aperture photometry from the center of the source, to estimate a flux growth function and fit it with a custom erf function.
    * A Gaussian 2D fit of the PSF on the flux map



    This method can be used in a parallel process.
    Parameters
    ----------
    source : :class:`~pandas:pandas.Series`
        A row from a :class:`~pandas:pandas.DataFrame` containing detected sources. Should have columns ``xpos``, ``ypos`` (Not astropy convention), ``velocity``, ``*_detected`` whare * is a line name, containing True or False for each line.
    cube : :class:`~ORCS:orcs.process.SpectralCube`
        SpectralCube instance where we are looking at the source
    plot : bool, Default = False
        (Optional) If True, the two fits are plotted
    return_fit_params : bool, Default = False
        (Optional) If True, returns the full fits parameters

    Returns
    -------
    res : dict
        A dictionnary containing all the relevant fitted quantities.

        +-----------------------+---------------------------------------------------------------------------------------------------+
        |Parameter              |Description                                                                                        |
        +=======================+===================================================================================================+
        |flux_map_ks_pvalue     |Estimates the 'randomness' of the flux map, i.e if it's just noise or if we actually have a signal |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |flux_r                 |Flux at different radius *r*                                                                       |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |flux_err_r             |Flux error varying with *r*                                                                        |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |erf_amplitude          |Amplitude estimated from erf fit                                                                   |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |erf_amplitude_err      |Amplitude error                                                                                    |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |erf_xfwhm              |x-axis fwhm from erf fit                                                                           |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |erf_yfwhm              |y-axis fwhm from erf fit                                                                           |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |erf_fwhm               |Fwhm defined as *r* at which half of the max flux is reached                                       |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |flux_fraction_3        |Ratio between flux measured at 3 pixels from the center and max flux                               |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |model_flux_fraction_15 |Ratio between estimated flux at 15 pixels from the center and max flux                             |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |modeled_flux_r         |Modeled flux varying with *r*                                                                      |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |psf_snr                |Ratio between amplitude of the 2D fit and noise in the flux map                                    |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |psf_amplitude          |Amplitude of the 2D fit                                                                            |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |psf_xfwhm              |x-axis fwhm from 2D fit                                                                            |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |psf_yfwhm              |y-axis fwhm from 2D fit                                                                            |
        +-----------------------+---------------------------------------------------------------------------------------------------+
        |psf_ks_pvalue          |Randomness of the residuals map                                                                    |
        +-----------------------+---------------------------------------------------------------------------------------------------+



    """
    result = {}
    try:
        from astropy.stats import sigma_clipped_stats, gaussian_sigma_to_fwhm
        from sitelle.constants import SN2_LINES, SN3_LINES
        from sitelle.region import centered_square_region
        from orb.utils.spectrum import line_shift
        from orb.core import Lines

        filter_name = cube.params.filter_name

        if filter_name == 'SN2':
            LINES = SN2_LINES
        elif filter_name == 'SN3':
            LINES = SN3_LINES
        else:
            raise ValueError(filter_name)

        ## We build a flux map of the detected lines
        try:
            detected_lines = [
                line_name for line_name in LINES
                if source['%s_detected' %
                          line_name.lower().replace('[', '').replace(']', '')]
            ]
        except KeyError as e:
            raise ValueError('No columns *_detected in the source')
        if detected_lines == []:
            return pd.Series(result)

        x, y = source.as_matrix(['xpos', 'ypos']).astype(int)
        big_box = centered_square_region(x, y, 30)
        medium_box = centered_square_region(15, 15, 5)
        small_box = centered_square_region(15, 15, 3)
        data = cube._extract_spectra_from_region(big_box, silent=True)
        mask = np.ones((30, 30))
        mask[medium_box] = 0
        bkg_spec = np.nanmedian(data[np.nonzero(mask)], axis=0)
        data -= bkg_spec

        axis = cube.params.base_axis
        spec = np.nansum(data[small_box], axis=0)

        line_pos = np.atleast_1d(
            Lines().get_line_cm1(detected_lines) +
            line_shift(source['velocity'],
                       Lines().get_line_cm1(detected_lines),
                       wavenumber=True))
        pos_min = line_pos - cube.params.line_fwhm
        pos_max = line_pos + cube.params.line_fwhm
        pos_index = np.array([[
            np.argmin(np.abs(axis - pos_min[i])),
            np.argmin(np.abs(axis - pos_max[i]))
        ] for i in range(pos_min.shape[0])])

        bandpass_size = 0
        flux_map = np.zeros(data.shape[:-1])
        for line_detection in pos_index:
            bandpass_size += line_detection[1] - line_detection[0]
            flux_map += np.nansum(data[:, :,
                                       line_detection[0]:line_detection[1]],
                                  axis=-1)

        _, _, std_map = sigma_clipped_stats(data, axis=-1)
        flux_noise_map = np.sqrt(bandpass_size) * std_map

        #Test for randomness of the flux_map
        from scipy import stats
        result['flux_map_ks_pvalue'] = stats.kstest(
            (flux_map / flux_noise_map).flatten(), 'norm').pvalue

        #Fit of the growth function
        from photutils import RectangularAperture
        from scipy.special import erf
        from scipy.optimize import curve_fit

        try:
            _x0 = source['xcentroid'] - x + 15.
            _y0 = source['ycentroid'] - y + 15.
        except:
            _x0 = source['xpos'] - x + 15.
            _y0 = source['ypos'] - y + 15.

        flux_r = [0.]
        flux_err_r = [np.nanmin(flux_noise_map)]

        r_max = 15
        r_range = np.arange(1, r_max + 1)
        for r in r_range:
            #             aper = CircularAperture((_x0,_y0), r)
            aper = RectangularAperture((_x0, _y0), r, r, 0)
            flux_r.append(aper.do_photometry(flux_map)[0][0])
            flux_err_r.append(
                np.sqrt(aper.do_photometry(flux_noise_map**2)[0][0]))

        flux_r = np.atleast_1d(flux_r)
        flux_err_r = np.atleast_1d(flux_err_r)

        result['flux_r'] = flux_r
        result['flux_err_r'] = flux_err_r
        try:

            def model(r, x0, y0, sx, sy, A):
                return A * erf((r / 2. - x0) / (2 * sx * np.sqrt(2))) * erf(
                    (r / 2. - y0) / (2 * sy * np.sqrt(2)))

            R = np.arange(r_max + 1)
            p, cov = curve_fit(model,
                               R,
                               flux_r,
                               p0=[0, 0, 1.5, 1.5,
                                   flux_map.max()],
                               bounds=([-2, -2, -np.inf, -np.inf, -np.inf],
                                       [2, 2, np.inf, np.inf, np.inf]),
                               sigma=flux_err_r,
                               absolute_sigma=True,
                               maxfev=10000)
            if (p[2] < 0) != (p[3] < 0):
                if p[-1] < 0:
                    p[-1] = -p[-1]
                    if p[2] < 0:
                        p[2] = -p[2]
                    elif p[3] < 0:
                        p[3] = -p[3]
            if plot:
                f, ax = plt.subplots()
                ax.plot(R, model(R, *p), label='Fit')
                ax.errorbar(R, flux_r, flux_err_r, label='Flux')
                ax.set_ylabel('Flux')
                ax.set_xlabel('Radius from source')
                ax.legend()

            from scipy.optimize import bisect
            fwhm = bisect(lambda x: model(x, *p) - p[-1] / 2, 0.1, 10)
            result['erf_amplitude'] = p[-1]
            result['erf_amplitude_err'] = np.sqrt(np.diag(cov))[-1]
            result['erf_xfwhm'] = gaussian_sigma_to_fwhm * p[2]
            result['erf_yfwhm'] = gaussian_sigma_to_fwhm * p[3]
            result['erf_ks_pvalue'] = stats.kstest(
                (flux_r - model(R, *p)) / flux_err_r, 'norm').pvalue
            result['erf_fwhm'] = fwhm

            result['flux_fraction_3'] = flux_r[3] / p[-1]
            result['model_flux_fraction_15'] = model(R, *
                                                     p)[r_range[-1]] / p[-1]

            result['modeled_flux_r'] = model(R, *p)

        except Exception as e:
            print(e)
            pass

        ## 2D fit of the PSF
        from astropy.modeling import models, fitting

        fitter = fitting.LevMarLSQFitter()
        X, Y = np.mgrid[:30, :30]

        flux_std = np.nanmean(flux_noise_map)

        gauss_model = models.Gaussian2D(amplitude=np.nanmax(flux_map /
                                                            flux_std),
                                        x_mean=_y0,
                                        y_mean=_x0)
        gauss_model.bounds['x_mean'] = (14, 16)
        gauss_model.bounds['y_mean'] = (14, 16)
        gauss_fit = fitter(gauss_model, X, Y, flux_map / flux_std)

        if plot is True:
            f, ax = plt.subplots(1, 3, figsize=(8, 3))
            v_min = np.nanmin(flux_map)
            v_max = np.nanmax(flux_map)
            plot_map(flux_map, ax=ax[0], cmap='RdBu_r', vmin=v_min, vmax=v_max)
            ax[0].set_title("Data")
            plot_map(gauss_fit(X, Y) * flux_std,
                     ax=ax[1],
                     cmap='RdBu_r',
                     vmin=v_min,
                     vmax=v_max)
            ax[1].set_title("Model")
            plot_map(flux_map - gauss_fit(X, Y) * flux_std,
                     ax=ax[2],
                     cmap='RdBu_r',
                     vmin=v_min,
                     vmax=v_max)
            ax[2].set_title("Residual")

        result['psf_snr'] = gauss_fit.amplitude[0]
        result['psf_amplitude'] = flux_std * gauss_fit.amplitude[
            0] * 2 * np.pi * gauss_fit.x_stddev * gauss_fit.y_stddev
        result['psf_xfwhm'] = gauss_fit.x_fwhm
        result['psf_yfwhm'] = gauss_fit.y_fwhm
        normalized_res = (flux_map -
                          gauss_fit(X, Y) * flux_std) / flux_noise_map
        result['psf_ks_pvalue'] = stats.kstest(normalized_res.flatten(),
                                               'norm').pvalue
        if return_fit_params:
            return pd.Series(result), p, gauss_fit
        else:
            return pd.Series(result)
    except Exception as e:
        print e
        return pd.Series(result)
Beispiel #5
0
def fit_SN3(source, cube, v_guess = None, lines=None, return_fit_params = False, kwargs_spec={}, kwargs_bkg = {}, debug=False):
    """
    Function specialized to fit sources found in the SN3 cube. The background spectrum is not fitted, due to the presence of sky lines that would bias the estimation of the velocity.
    | It looks very similar to :func:`fit_SN2` and the code should be refactored.
    It differs however in the philosophy behind the velocity estimation : this method has been designed to be performed after a SN2 fit, from which we already estimated the velocity of the source. Thus no guess is performed here.

    The method can be used in a parallel process.
    The SNR of the spec is estimated using the :func:`stats_without_lines` method.

    Parameters
    ---------
    source : :class:`~pandas:pandas.Series`
        A row from a :class:`~pandas:pandas.DataFrame` containing detected sources. Should have columns ``xpos``, ``ypos``, assumed to correspond to the SN2 pixel coordinates.
    cube : :class:`~ORCS:orcs.process.SpectralCube`
        The cube taken in SN3 filter.
    v_guess : float
        (Optional) If None, looking for the element ``source['v_guess']``
    lines : list of str
        (Optinal) Names of the lines to fit. If None, **SN3_LINES** are used, except if no v_guess has been found; then we assume it's only a Hbeta line at very fast velocity.
    return_fit_params : bool, Default = False
        (Optional) If True, returns the full parameters of the fit.
    kwargs_spec : dict
        (Optional) Additional keyword arguments to be used by :func:`fit_spectrum` when fitting the source spectrum.
    kwargs_bkg : dict
        (Optional) Additional keyword arguments to be used by :func:`fit_spectrum` when fitting the background spectrum.
    debug : bool, Default = False
        (Optional) If True, the velocity guess is verbose.

    Returns
    -------
    fit_res : dict
        A dict containg a lot of information about the fit.

        +--------------------+--------------------------------------------------------------------+
        | Parameter          | Description                                                        |
        +====================+====================================================================+
        | err                | estimated noise value on the spectra                               |
        +--------------------+--------------------------------------------------------------------+
        | guess_snr          | SNR guess                                                          |
        +--------------------+--------------------------------------------------------------------+
        | exit_status        | code to identify cause of crash                                    |
        +--------------------+--------------------------------------------------------------------+
        | v_guess            | guessed velocity in km/s                                           |
        +--------------------+--------------------------------------------------------------------+
        | chi2               | chi2 computed on the residual                                      |
        +--------------------+--------------------------------------------------------------------+
        | rchi2              | reduced chi2 computed on the residual                              |
        +--------------------+--------------------------------------------------------------------+
        | ks_pvalue          | ks test computed on the residuals                                  |
        +--------------------+--------------------------------------------------------------------+
        | logGBF             | log Gaussian Bayes Factor on the residuals                         |
        +--------------------+--------------------------------------------------------------------+
        | rchi2              | reduced chi2 computed on the residual                              |
        +--------------------+--------------------------------------------------------------------+
        | broadening         | broadening estimation of the lines                                 |
        +--------------------+--------------------------------------------------------------------+
        | broadening_err     | error on the brodeaning estimation                                 |
        +--------------------+--------------------------------------------------------------------+
        | velocity           | estimated velocity                                                 |
        +--------------------+--------------------------------------------------------------------+
        | velocity_err       | error on the fitted velocity                                       |
        +--------------------+--------------------------------------------------------------------+
        | flux_*             | flux estimation for * line, where * is the line name               |
        +--------------------+--------------------------------------------------------------------+
        | flux_*_err         | error on the flux estimation for * line, where * is the line name  |
        +--------------------+--------------------------------------------------------------------+
        | snr_*              | Estimated SNR of the * line                                        |
        +--------------------+--------------------------------------------------------------------+


    See Also
    --------
    :func:`fit_SN2`
    """
    fit_res = {}
    try:
        x, y = map(int, source[['xpos', 'ypos']])

        big_box = centered_square_region(x,y,30)
        medium_box_bkg = centered_square_region(15,15,15)
        data = cube._extract_spectra_from_region(big_box, silent=True)
        mask = np.ones((30, 30))
        mask[medium_box_bkg] = 0
        bkg_spec = np.nanmedian(data[np.nonzero(mask)], axis=0)

        medium_box = centered_square_region(15,15,5)
        small_box = centered_square_region(15,15, 3)
        mask = np.ones((30, 30))
        mask[medium_box] = 0
        data -= np.nanmedian(data[np.nonzero(mask)], axis=0)

        a = cube.params.base_axis
        imin,imax = np.searchsorted(a, cube.params.filter_range)
        s = np.nansum(data[small_box], axis=0)

        theta_orig = np.nanmean(cube.get_theta_map()[centered_square_region(x,y,3)])

        try: # Spec FIT
            mean,_,err = stats_without_lines(s[imin+5:imax-5], a[imin+5:imax-5],
                                          SN3_LINES, -1300., 700.)
            fit_res['err'] = err
            fit_res['guess_snr'] = np.nanmax((s[imin+5:imax-5] - mean) / err)

            if lines is None:
                lines = SN3_LINES
            if v_guess is None:
                if 'v_guess' in source.keys():
                    v_guess = source['v_guess']
            fit_res['v_guess'] = v_guess
            if 'fmodel' not in kwargs_spec:
                kwargs_spec['fmodel'] = 'sinc'
            if 'pos_def' not in kwargs_spec:
                kwargs_spec['pos_def']=['1']
            if 'signal_range' not in kwargs_spec:
                kwargs_spec['signal_range'] = cube.params.filter_range
            kwargs_spec['pos_cov'] = v_guess

            cube._prepare_input_params(lines, nofilter=True, **kwargs_spec)
            fit_params = fit_lines_in_spectrum(cube.params, cube.inputparams, cube.fit_tol,
                                                s, theta_orig,
                                                snr_guess=err,  debug=debug)
#             _,_,fit_params = cube.fit_lines_in_integrated_region(centered_square_region(x,y,3), SN2_LINES,
#                                                               nofilter=True, snr_guess=err,
#                                                               subtract_spectrum=sub_spec, **kwargs_spec)
            if fit_params == []:
                fit_res['exit_status'] = 2
            else:
                fit_res['exit_status'] = 0
                keys_to_keep = ['chi2', 'rchi2', 'ks_pvalue', 'logGBF']
                fit_res.update({k:v for (k,v) in fit_params.items() if k in keys_to_keep})
                fit_res['broadening'] = fit_params['broadening'][0]
                fit_res['broadening_err'] = fit_params['broadening_err'][0]
                fit_res['velocity'] = fit_params['velocity'][0]
                fit_res['velocity_err'] = fit_params['velocity_err'][0]

                # !! has to be in the same order than in fit_spectrum function
                for j, l in enumerate(lines):
                    line_name = l.lower().replace('[', '').replace(']', '')
                    fit_res['flux_%s'%line_name] = fit_params['flux'][j]
                    fit_res['flux_%s_err'%line_name] = fit_params['flux_err'][j]
                    fit_res['snr_%s'%line_name] = fit_params['snr'][j]
        except Exception as e:
            pass
    except Exception as e:
        print e
        fit_res['exit_status'] = 3
    if return_fit_params:
        return pd.Series(fit_res), fit_params
    else:
        return pd.Series(fit_res)
Beispiel #6
0
def fit_SN2(source, cube, v_guess = None, v_min = -800., v_max = 0., lines=None, return_fit_params = False, kwargs_spec={}, kwargs_bkg = {}, debug=False):
    """
    Function specialized to fit sources found in the SN2 cube. The background spectrum is fitted as well.
    Can be used in a parallel process.
    The SNR of the spec is estimated using the :func:`stats_without_lines` method.
    Only the velocity of the background is estimated, as the flux is biased by an unkown amount of absorption.

    Parameters
    ---------
    source : :class:`~pandas:pandas.Series`
        A row from a :class:`~pandas:pandas.DataFrame` containing detected sources. Should have columns ``xpos``, ``ypos``, assumed to correspond to the SN2 pixel coordinates.
    cube : :class:`~ORCS:orcs.process.SpectralCube`
        The cube taken in SN2 filter.
    v_guess : float
        (Optional) If None, a guess is performed using :func:`guess_source_velocity` and :func:`refine_velocity_guess`
    v_min : float
        (Optional) v_min used by :func:`guess_source_velocity`. Default = -800
    v_max : float
        (Optional) v_max used by :func:`guess_source_velocity`. Default = 0
    lines : list of str
        (Optinal) Names of the lines to fit. If None, **SN2_LINES** are used, except if no v_guess has been found; then we assume it's only a Hbeta line at very fast velocity.
    return_fit_params : bool, Default = False
        (Optional) If True, returns the full parameters of the fit.
    kwargs_spec : dict
        (Optional) Additional keyword arguments to be used by :func:`fit_spectrum` when fitting the source spectrum.
    kwargs_bkg : dict
        (Optional) Additional keyword arguments to be used by :func:`fit_spectrum` when fitting the background spectrum.
    debug : bool, Default = False
        (Optional) If True, the velocity guess is verbose.

    Returns
    -------
    fit_res : dict
        A dict containg a lot of information about the fit.

        +--------------------+--------------------------------------------------------------------+
        | Parameter          | Description                                                        |
        +====================+====================================================================+
        | err                | estimated noise value on the spectra                               |
        +--------------------+--------------------------------------------------------------------+
        | guess_snr          | SNR guess                                                          |
        +--------------------+--------------------------------------------------------------------+
        | exit_status        | code to identify cause of crash                                    |
        +--------------------+--------------------------------------------------------------------+
        | v_guess            | guessed velocity in km/s                                           |
        +--------------------+--------------------------------------------------------------------+
        | chi2               | chi2 computed on the residual                                      |
        +--------------------+--------------------------------------------------------------------+
        | rchi2              | reduced chi2 computed on the residual                              |
        +--------------------+--------------------------------------------------------------------+
        | ks_pvalue          | ks test computed on the residuals                                  |
        +--------------------+--------------------------------------------------------------------+
        | logGBF             | log Gaussian Bayes Factor on the residuals                         |
        +--------------------+--------------------------------------------------------------------+
        | rchi2              | reduced chi2 computed on the residual                              |
        +--------------------+--------------------------------------------------------------------+
        | broadening         | broadening estimation of the lines                                 |
        +--------------------+--------------------------------------------------------------------+
        | broadening_err     | error on the brodeaning estimation                                 |
        +--------------------+--------------------------------------------------------------------+
        | velocity           | estimated velocity                                                 |
        +--------------------+--------------------------------------------------------------------+
        | velocity_err       | error on the fitted velocity                                       |
        +--------------------+--------------------------------------------------------------------+
        | flux_*             | flux estimation for * line, where * is the line name               |
        +--------------------+--------------------------------------------------------------------+
        | flux_*_err         | error on the flux estimation for * line, where * is the line name  |
        +--------------------+--------------------------------------------------------------------+
        | snr_*              | Estimated SNR of the * line                                        |
        +--------------------+--------------------------------------------------------------------+
        | bkg_v_guess        | guess on the background spectrum velocity                          |
        +--------------------+--------------------------------------------------------------------+
        | bkg_exit_status    | code to identify cause of crash                                    |
        +--------------------+--------------------------------------------------------------------+
        | bkg_velocity       | estimated velocity of the background spectrum                      |
        +--------------------+--------------------------------------------------------------------+
        | bkg_velocity_err   | error on the background velocity estimation                        |
        +--------------------+--------------------------------------------------------------------+


    See Also
    --------
    :func:`fit_SN3`
    """
    fit_res = {}
    try: # Try catch to avoid a stupid crash during a long loop over many sources
        x, y = map(int, source[['xpos', 'ypos']])

        big_box = centered_square_region(x,y,30)
        medium_box_bkg = centered_square_region(15,15,15)
        small_box = centered_square_region(15,15, 3)
        data = cube._extract_spectra_from_region(big_box, silent=True)
        mask = np.ones((30, 30))
        mask[medium_box_bkg] = 0
        bkg_spec = np.nanmedian(data[np.nonzero(mask)], axis=0)

        medium_box = centered_square_region(15,15,5)
        mask = np.ones((30, 30))
        mask[medium_box] = 0
        data -= np.nanmedian(data[np.nonzero(mask)], axis=0)

        a = cube.params.base_axis
        imin,imax = np.searchsorted(a, cube.params.filter_range)
        s = np.nansum(data[small_box], axis=0)

        theta_orig = np.nanmean(cube.get_theta_map()[centered_square_region(x,y,3)])


        try: # Spec FIT
            spec = s[imin+5:imax-5]
            axis = a[imin+5:imax-5]
            mean,_,err = stats_without_lines(spec, axis,
                                          SN2_LINES, -1300., 700.)
            fit_res['err'] = err
            fit_res['guess_snr'] = np.nanmax((spec - mean) / err)

            if lines is None:
                lines = SN2_LINES
            if v_guess is None:
                if 'v_guess' in source.keys():
                    v_guess = source['v_guess']
                    if (v_guess > v_max) | (v_guess < v_min):
                        lines = ['Hbeta']
            if v_guess is None:
                v_guess, l = guess_source_velocity(s, cube, v_min = v_min, v_max = v_max, debug=debug, return_line=True)
            if v_guess is None:
                fit_res['exit_status'] = 1
                v_guess, l = guess_source_velocity(s, cube, v_min = -np.inf, v_max = np.inf, lines=['Hbeta'], debug=debug, return_line=True)
                lines = ['Hbeta']
            try:
                coeff = np.nanmax(spec)
                v_guess = refine_velocity_guess(spec/coeff, axis, v_guess, l)
            except Exception as e:
                pass
            fit_res['v_guess'] = v_guess
            if 'fmodel' not in kwargs_spec:
                kwargs_spec['fmodel'] = 'sinc'
            kwargs_spec['pos_def']=['1']
            if 'signal_range' not in kwargs_spec:
                kwargs_spec['signal_range'] = cube.params.filter_range
            kwargs_spec['pos_cov'] = v_guess

            cube._prepare_input_params(lines, nofilter=True, **kwargs_spec)
            fit_params = fit_lines_in_spectrum(cube.params, cube.inputparams, cube.fit_tol,s, theta_orig,snr_guess=err,  debug=debug)
            # fit_params = cube._fit_lines_in_spectrum(s, theta_orig, snr_guess=err)
#             _,_,fit_params = cube.fit_lines_in_integrated_region(centered_square_region(x,y,3), SN2_LINES,
#                                                               nofilter=True, snr_guess=err,
#                                                               subtract_spectrum=sub_spec, **kwargs_spec)

            if fit_params == []:
                fit_res['exit_status'] = 2
            else:
                fit_res['exit_status'] = 0
                keys_to_keep = ['chi2', 'rchi2', 'ks_pvalue', 'logGBF']
                fit_res.update({k:v for (k,v) in fit_params.items() if k in keys_to_keep})
                fit_res['broadening'] = fit_params['broadening'][0]
                fit_res['broadening_err'] = fit_params['broadening_err'][0]
                fit_res['velocity'] = fit_params['velocity'][0]
                fit_res['velocity_err'] = fit_params['velocity_err'][0]

                # !! has to be in the same order than in fit_spectrum function
                for j, l in enumerate(lines):
                    line_name = l.lower().replace('[', '').replace(']', '')
                    fit_res['flux_%s'%line_name] = fit_params['flux'][j]
                    fit_res['flux_%s_err'%line_name] = fit_params['flux_err'][j]
                    fit_res['snr_%s'%line_name] = fit_params['snr'][j]
        except Exception as e:
            print e
            pass
        try: #BKG velocity
            bkg_err = np.nanstd(np.concatenate([bkg_spec[:imin-40], bkg_spec[imax+40:]]))
            kwargs_bkg.update({'fmodel':'gaussian'})
            kwargs_bkg.update({'fwhm_def':['1']})
            kwargs_bkg.update({'signal_range':(19500,20500)})

            lines = ['[OIII]5007']
            v_guess, l = guess_source_velocity(bkg_spec, cube, lines=lines, force=True, return_line=True)
            try:
                coeff = np.nanmax(bkg_spec)
                v_guess = refine_velocity_guess(bkg_spec/coeff, a, v_guess, l)
            except Exception as e:
                pass
            fit_res['bkg_v_guess'] = v_guess
            kwargs_bkg['pos_cov'] = v_guess
            kwargs_bkg['pos_def'] = ['1']
            cube.inputparams = {}
            cube._prepare_input_params(lines, nofilter = True, **kwargs_bkg)
            fit_params_bkg = fit_lines_in_spectrum(cube.params, cube.inputparams, cube.fit_tol,
                                                bkg_spec, theta_orig,
                                                snr_guess=bkg_err,  debug=debug)

            if fit_params_bkg == []:
                fit_res['bkg_exit_status'] = 2
            else:
                fit_res['bkg_exit_status'] = 0
                fit_res['bkg_velocity'] = fit_params_bkg['velocity'][0]
                fit_res['bkg_velocity_err'] = fit_params_bkg['velocity_err'][0]

        except Exception as e:
            print e
            pass
    except Exception as e:
        print e
        fit_res['exit_status'] = 3
    if return_fit_params:
        return pd.Series(fit_res), fit_params
    else:
        return pd.Series(fit_res)