Locate line peaks in array coordinates (from 0 to naxis1-1).

    Parameters
    ----------
    sp : 1d numpy array
        Spectrum to be wavelength calibrated.
    times_sigma_threshold : float
        Times robust sigma above the median to detect line peaks.
    minimum_threshold : float or None
            Minimum value of the threshold.
    nwinwidth_initial : int
        Width of the window where each peak must be found using
        the initial method (approximate)
    nwinwidth_refined : int
        Width of the window where each peak location will be
        refined.
    npix_avoid_border : int
            Number of pixels at the borders of the spectrum where peaks
            are not considered. If zero, the actual number will be
            given by nwinwidth_initial.
    nbrightlines : int or list of integers
        Maximum number of brightest lines to be employed in the
        wavelength calibration. If this value is 0, all the detected
        lines will be employed.
    sigma_gaussian_filtering : float
        Sigma of the gaussian filter to be applied to the spectrum in
        order to avoid problems with saturated lines. This filtering is
        skipped when this parameter is <= 0.
    minimum_gaussian_filtering : float
        Minimum pixel value to employ gaussian filtering. This value is
        employed only when sigma_gaussian_filtering is > 0.
    plottile : string
        Plot title.
    geometry : tuple (4 integers) or None
        x, y, dx, dy values employed to set the Qt backend geometry.
    debugplot : int
        Determines whether intermediate computations and/or plots
        are displayed. The valid codes are defined in
        numina.array.display.pause_debugplot.

    Returns
    -------
    fxpeaks : 1d numpy array
        Refined location of peaks in array index scale, i.e, from 0
        to naxis1 - 1.
    sxpeaks : 1d numpy array
        Line peak widths.

def find_fxpeaks(sp,
                 times_sigma_threshold,
                 minimum_threshold,
                 nwinwidth_initial,
                 nwinwidth_refined,
                 npix_avoid_border,
                 nbrightlines,
                 sigma_gaussian_filtering,
                 minimum_gaussian_filtering,
                 plottitle=None,
                 geometry=None,
                 debugplot=0):
    """Locate line peaks in array coordinates (from 0 to naxis1-1).

    Parameters
    ----------
    sp : 1d numpy array
        Spectrum to be wavelength calibrated.
    times_sigma_threshold : float
        Times robust sigma above the median to detect line peaks.
    minimum_threshold : float or None
            Minimum value of the threshold.
    nwinwidth_initial : int
        Width of the window where each peak must be found using
        the initial method (approximate)
    nwinwidth_refined : int
        Width of the window where each peak location will be
        refined.
    npix_avoid_border : int
            Number of pixels at the borders of the spectrum where peaks
            are not considered. If zero, the actual number will be
            given by nwinwidth_initial.
    nbrightlines : int or list of integers
        Maximum number of brightest lines to be employed in the
        wavelength calibration. If this value is 0, all the detected
        lines will be employed.
    sigma_gaussian_filtering : float
        Sigma of the gaussian filter to be applied to the spectrum in
        order to avoid problems with saturated lines. This filtering is
        skipped when this parameter is <= 0.
    minimum_gaussian_filtering : float
        Minimum pixel value to employ gaussian filtering. This value is
        employed only when sigma_gaussian_filtering is > 0.
    plottile : string
        Plot title.
    geometry : tuple (4 integers) or None
        x, y, dx, dy values employed to set the Qt backend geometry.
    debugplot : int
        Determines whether intermediate computations and/or plots
        are displayed. The valid codes are defined in
        numina.array.display.pause_debugplot.

    Returns
    -------
    fxpeaks : 1d numpy array
        Refined location of peaks in array index scale, i.e, from 0
        to naxis1 - 1.
    sxpeaks : 1d numpy array
        Line peak widths.

    """

    # spectrum dimension
    naxis1 = sp.shape[0]

    # apply gaussian filtering when requested
    if sigma_gaussian_filtering > 0:
        spf = ndimage.filters.gaussian_filter(
            sp,
            sigma=sigma_gaussian_filtering
        )
        lpreserve = sp < minimum_gaussian_filtering
        spf[lpreserve] = sp[lpreserve]
    else:
        spf = np.copy(sp)

    # threshold to search for peaks
    q50 = np.percentile(spf, q=50)
    sigma_g = robust_std(spf)
    threshold = q50 + times_sigma_threshold * sigma_g
    if abs(debugplot) >= 10:
        print("median....:", q50)
        print("robuts std:", sigma_g)
        print("threshold.:", threshold)
    if minimum_threshold > threshold:
        threshold = minimum_threshold
    if abs(debugplot) >= 10:
        print("minimum threshold:", minimum_threshold)
        print("final threshold..:", threshold)

    # initial location of the peaks (integer values)
    ixpeaks = find_peaks_spectrum(spf,
                                  nwinwidth=nwinwidth_initial,
                                  threshold=threshold)

    # select a maximum number of brightest lines in each region
    if len(nbrightlines) == 1 and nbrightlines[0] == 0:
        pass
    else:
        if abs(debugplot) >= 10:
            print('nbrightlines =', nbrightlines)
            print('ixpeaks in whole spectrum:\n', ixpeaks)
        region_size = (naxis1-1)/len(nbrightlines)
        ixpeaks_filtered = np.array([], dtype=int)
        for iregion, nlines_in_region in enumerate(nbrightlines):
            if nlines_in_region > 0:
                imin = int(iregion * region_size)
                imax = int((iregion + 1) * region_size)
                if iregion > 0:
                    imin += 1
                ixpeaks_region = \
                    ixpeaks[np.logical_and(ixpeaks >= imin, ixpeaks <= imax)]
                if len(ixpeaks_region) > 0:
                    peak_fluxes = spf[ixpeaks_region]
                    spos = peak_fluxes.argsort()
                    ixpeaks_tmp = ixpeaks_region[spos[-nlines_in_region:]]
                    ixpeaks_tmp.sort()  # in-place sort
                    if abs(debugplot) >= 10:
                        print('ixpeaks in region........:\n', ixpeaks_tmp)
                    ixpeaks_filtered = np.concatenate((ixpeaks_filtered,
                                                       ixpeaks_tmp))
        ixpeaks = ixpeaks_filtered
        if abs(debugplot) >= 10:
            print('ixpeaks filtered.........:\n', ixpeaks)

    # remove peaks too close to any of the borders of the spectrum
    if npix_avoid_border > 0:
        lok_ini = ixpeaks >= npix_avoid_border
        lok_end = ixpeaks <= naxis1 - 1 - npix_avoid_border
        ixpeaks = ixpeaks[lok_ini * lok_end]

    # refined location of the peaks (float values)
    fxpeaks, sxpeaks = refine_peaks_spectrum(spf, ixpeaks,
                                             nwinwidth=nwinwidth_refined,
                                             method="gaussian")

    # print peak location and width of fitted lines
    if abs(debugplot) >= 10:
        print(">>> Number of lines found:", len(fxpeaks))
        print("# line_number, channel, width")
        for i, (fx, sx) in enumerate(zip(fxpeaks, sxpeaks)):
            print(i, fx+1, sx)

    # display median spectrum and peaks
    if abs(debugplot) % 10 != 0:
        xplot = np.arange(1, naxis1 + 1, dtype=float)
        ax = ximplotxy(xplot, sp, show=False, geometry=geometry,
                       **{'label': 'original spectrum'})
        ax.set_xlabel('pixel (from 1 to NAXIS1)')
        ax.set_ylabel('counts')
        if plottitle is not None:
            ax.set_title(plottitle)
        if sigma_gaussian_filtering > 0:
            ax.plot(xplot, spf, label="filtered spectrum")
        ymin = sp.min()
        ymax = sp.max()
        dy = ymax - ymin
        ymin -= dy/20.
        ymax += dy/20.
        ax.set_ylim([ymin, ymax])
        # display threshold
        ax.axhline(y=threshold, color="black", linestyle="dotted",
                   label="detection threshold")
        # mark peak location
        ax.plot(ixpeaks + 1, spf[ixpeaks], 'bo', label="initial location")
        ax.plot(fxpeaks + 1, spf[ixpeaks], 'go', label="refined location")
        # legend
        ax.legend(numpoints=1)
        # show plot
        pause_debugplot(debugplot, pltshow=True)

    return fxpeaks, sxpeaks