Пример #1
0
def make_mf(
    maskname,
    instrument,
    filtname,
    npix,
    i_wl=None,
    peakmethod="fft",
    n_wl=3,
    theta_detector=0,
    cutoff=1e-4,
    hole_diam=0.8,
    fw_splodge=0.7,
    scaling=1,
    diag_plot=False,
    verbose=False,
    display=True,
    save_to=None,
    filename=None,
):
    """
    Summary:
    --------

    Compute the match filter mf which give the indices of the peak positions (mf.pvct)
    and the associated gains (mf.gvct) in the image. Contains also the u-v coordinates,
    wavelengths informations, holes mask positions (mf.xy_coords), centered mf (mf.cpvct,
    mf.gpvct), etc.

    Parameters:
    -----------
    `maskname`: str
        Name of the mask (number of holes),\n
    `instrument`: str
        Instrument used (default = jwst),\n
    `filtname`: str
        Name of the filter,\n
    `npix`: int
        Size of the image,\n
    `peakmethod` {str}:
        3 methods are used to sample the u-v space: 'fft' uses fft between individual holes to compute
        the expected splodge positions; 'square' compute the splodge in a square using the expected
        fraction of pixel to determine its weight; 'gauss' considers a gaussian splodge (with a gaussian
        weight) to get the same splodge side for each n(n-1)/2 baselines,\n
    `n_wl`: int
        number of wavelengths to use to simulate bandwidth,\n
    `theta_detector`: float
        Angle [deg] to rotate the mask compare to the detector (if the mask is not
        perfectly aligned with the detector, e.g.: VLT/VISIR) ,\n
    `cutoff`: float
        cutoff limit between noise and signal pixels in simulated transforms,\n
    `hole_diam`: float
        Diameter of a single aperture (0.8 for JWST),\n
    `fw_splodge` {float}:
        Relative size of the splodge used to compute multiple triangle indices and the fwhm
        of the 'gauss' technique,\n
    """
    from munch import munchify as dict2class

    # Get detector, filter and mask informations
    # ------------------------------------------
    pixelsize = get_pixel_size(instrument)  # Pixel size of the detector [rad]
    if pixelsize is np.nan:
        cprint("Error: Pixel size unknown for %s." % instrument, "red")
        return None
    # Wavelength of the filter (filt[0]: central, filt[1]: width)
    filt = get_wavelength(instrument, filtname)

    if instrument == "SPHERE-IFS":
        if isinstance(i_wl, (int, np.integer)):
            filt = [filt[i_wl], 0.001 * filt[i_wl]]
        else:
            filt = [np.mean(filt[i_wl[0] : i_wl[1]]), filt[i_wl[1]] - filt[i_wl[0]]]

    xy_coords = get_mask(instrument, maskname)  # mask coordinates

    x_mask = xy_coords[:, 0] * scaling
    y_mask = xy_coords[:, 1] * scaling

    x_mask_rot = x_mask * np.cos(np.deg2rad(theta_detector)) + y_mask * np.sin(
        np.deg2rad(theta_detector)
    )
    y_mask_rot = -x_mask * np.sin(np.deg2rad(theta_detector)) + y_mask * np.cos(
        np.deg2rad(theta_detector)
    )

    xy_coords_rot = []
    for i in range(len(x_mask)):
        xy_coords_rot.append([x_mask_rot[i], y_mask_rot[i]])
    xy_coords = np.array(xy_coords_rot)

    if display:
        import matplotlib.pyplot as plt

        _plot_mask_coord(xy_coords, maskname, instrument)
        if save_to is not None:
            figname = os.path.join(save_to, Path(filename).stem)
            plt.savefig(f"{figname}_{1}.pdf")

    n_holes = xy_coords.shape[0]

    index_mask = compute_index_mask(n_holes)
    n_baselines = index_mask.n_baselines
    n_bispect = index_mask.n_bispect

    ncp_i = int((n_holes - 1) * (n_holes - 2) / 2)
    if verbose:
        cprint("---------------------------", "cyan")
        cprint(
            "%s (%s): %i holes masks" % (instrument.upper(), filtname, n_holes), "cyan"
        )
        cprint("---------------------------", "cyan")
        cprint(
            "nbl = %i, nbs = %i, ncp_i = %i, ncov = %i"
            % (n_baselines, n_bispect, ncp_i, index_mask.n_cov),
            "cyan",
        )

    # Consider the filter to be made up of n_wl wavelengths
    wl = np.arange(n_wl) / n_wl * filt[1]
    wl = wl - np.mean(wl) + filt[0]

    Sum, Sum_c = 0, 0

    mf_ix = np.zeros([2, n_baselines], dtype=int)  # matched filter
    mf_ix_c = np.zeros([2, n_baselines], dtype=int)  # matched filter

    if verbose:
        print("\n- Calculating sampling of", n_holes, "holes array...")

    innerpix, innerpix_center = _compute_center_splodge(
        npix, pixelsize, filt, hole_diam=hole_diam
    )

    u, v = _compute_uv_coord(
        xy_coords, index_mask, filt, pixelsize, npix, round_uv_to_pixel=False
    )

    mf_pvct = mf_gvct = mfc_pvct = mfc_gvct = None

    for i in range(n_baselines):
        args = {
            "i": i,
            "npix": npix,
            "pixelsize": pixelsize,
            "innerpix": innerpix,
            "innerpix_center": innerpix_center,
        }

        if peakmethod == "fft":
            ind_peak = _peak_fft_method(
                xy_coords=xy_coords, wl=wl, index_mask=index_mask, **args
            )
        elif peakmethod == "square":
            ind_peak = _peak_square_method(u=u, v=v, **args)
        elif peakmethod == "unique":
            ind_peak = _peak_one_method(u=u, v=v, **args)
        elif peakmethod == "gauss":
            ind_peak = _peak_gauss_method(
                u=u,
                v=v,
                filt=filt,
                index_mask=index_mask,
                fw_splodge=fw_splodge,
                **args,
                hole_diam=hole_diam,
            )
        else:
            cprint(
                "Error: choose the extraction method 'gauss', 'fft' or 'square'.", "red"
            )
            return None

        # Compute the cutoff limit before saving the gain map
        pixelvector = np.where(ind_peak["flat"] >= cutoff)[0]
        pixelvector_c = np.where(ind_peak["centered"] >= cutoff)[0]

        # Now normalise the pixel gain, so that using the matched filter
        # on an ideal splodge is equivalent to just looking at the peak...
        if peakmethod == "gauss":
            pixelgain, pixelgain_c = _normalize_gain(
                ind_peak["gain_f"], ind_peak["gain_c"], pixelvector, pixelvector_c
            )
        else:
            pixelgain, pixelgain_c = _normalize_gain(
                ind_peak["flat"], ind_peak["centered"], pixelvector, pixelvector_c
            )

        mf_ix[0, i] = Sum
        Sum = Sum + len(pixelvector)
        mf_ix[1, i] = Sum

        mf_ix_c[0, i] = Sum_c
        Sum_c = Sum_c + len(pixelvector_c)
        mf_ix_c[1, i] = Sum_c

        if i == 0:
            mf_pvct = list(pixelvector)
            mf_gvct = list(pixelgain)
            mfc_pvct = list(pixelvector_c)
            mfc_gvct = list(pixelgain_c)
        else:
            mf_pvct.extend(list(pixelvector))
            mf_gvct.extend(list(pixelgain))
            mfc_pvct.extend(list(pixelvector_c))
            mfc_gvct.extend(list(pixelgain_c))

    mf = np.zeros(
        [npix, npix, n_baselines],
        dtype=[("norm", float), ("conj", float), ("norm_c", float), ("conj_c", float)],
    )

    for i in range(n_baselines):
        mf_tmp = np.zeros([npix, npix])
        mf_tmp_c = np.zeros([npix, npix])

        ind = mf_pvct[mf_ix[0, i] : mf_ix[1, i]]
        ind_c = mfc_pvct[mf_ix_c[0, i] : mf_ix_c[1, i]]

        mf_tmp.ravel()[ind] = mf_gvct[mf_ix[0, i] : mf_ix[1, i]]
        mf_tmp_c.ravel()[ind_c] = mfc_gvct[mf_ix_c[0, i] : mf_ix_c[1, i]]

        mf_tmp = mf_tmp.reshape([npix, npix])
        mf_tmp_c = mf_tmp_c.reshape([npix, npix])

        mf["norm"][:, :, i] = np.roll(mf_tmp, 0, axis=1)
        mf["norm_c"][:, :, i] = np.roll(mf_tmp_c, 0, axis=1)

        mf_temp_rot = np.roll(np.roll(np.rot90(np.rot90(mf_tmp)), 1, axis=0), 1, axis=1)
        mf_temp_rot_c = np.roll(
            np.roll(np.rot90(np.rot90(mf_tmp_c)), 1, axis=0), 1, axis=1
        )

        mf["conj"][:, :, i] = mf_temp_rot
        mf["conj_c"][:, :, i] = mf_temp_rot_c

        norm = np.sqrt(np.sum(mf["norm"][:, :, i] ** 2))

        mf["norm"][:, :, i] = mf["norm"][:, :, i] / norm
        mf["conj"][:, :, i] = mf["conj"][:, :, i] / norm

        mf["norm_c"][:, :, i] = mf["norm_c"][:, :, i] / norm
        mf["conj_c"][:, :, i] = mf["conj_c"][:, :, i] / norm

    rmat, imat = _make_overlap_mat(mf, n_baselines, display=diag_plot)

    mf_tot = np.sum(mf["norm"], axis=2) + np.sum(mf["conj"], axis=2)
    mf_tot_m = np.sum(mf["norm"], axis=2) - np.sum(mf["conj"], axis=2)

    im_uv = np.roll(np.fft.fftshift(mf_tot), 1, axis=1)

    if display:
        import matplotlib.pyplot as plt

        plt.figure(figsize=(9, 7))
        plt.title("(u-v) plan - mask %s" % (maskname), fontsize=14)
        plt.imshow(im_uv, origin="lower")
        plt.plot(npix // 2 + 1, npix // 2, "r+")
        plt.ylabel("Y [pix]")  # , fontsize=12)
        plt.xlabel("X [pix]")  # , fontsize=12)
        plt.tight_layout()

    out = {
        "cube": mf["norm"],
        "imat": imat,
        "rmat": rmat,
        "uv": im_uv,
        "tot": mf_tot,
        "tot_m": mf_tot_m,
        "pvct": mf_pvct,
        "gvct": mf_gvct,
        "cpvct": mfc_pvct,
        "cgvct": mfc_gvct,
        "ix": mf_ix,
        "u": u * filt[0],
        "v": v * filt[0],
        "wl": filt[0],
        "e_wl": filt[1],
        "pixelSize": pixelsize,
        "xy_coords": xy_coords,
    }

    return dict2class(out)
Пример #2
0
def extract_bs(
    cube,
    filename,
    maskname,
    filtname=None,
    targetname=None,
    instrum=None,
    bs_multi_tri=False,
    peakmethod="gauss",
    hole_diam=0.8,
    cutoff=1e-4,
    fw_splodge=0.7,
    naive_err=False,
    n_wl=3,
    n_blocks=0,
    theta_detector=0,
    scaling_uv=1,
    i_wl=None,
    unbias_v2=True,
    compute_cp_cov=True,
    expert_plot=False,
    save_to=None,
    verbose=False,
    display=True,
):
    """Compute the bispectrum (bs, v2, cp, etc.) from a data cube.

    Parameters:
    -----------

    `cube` {array}:
        Cleaned and checked data cube ready to extract NRM data,\n
    `filename` {array}:
        Name of the file containing the datacube (to keep track on it),\n
    `maskname` {str}:
        Name of the mask,\n
    `filtname` {str}:
        By default, checks the header to extract the filter, if not in header
        uses filtname instead (e.g.: F430M, F480M),\n
    `targetname` {str}:
        By default, checks the header to extract the target, if not in header
        uses target_name instead,\n
    `bs_multi_tri` {bool}:
        Use the multiple triangle technique to compute the bispectrum
        (default: False),\n
    `peakmethod` {str}:
        3 methods are used to sample to u-v space: 'fft' uses fft between individual holes to compute
        the expected splodge position; 'square' compute the splodge in a square using the expected
        fraction of pixel to determine its weight; 'gauss' considers a gaussian splodge (with a gaussian
        weight) to get the same splodge side for each n(n-1)/2 baselines,\n
    `fw_splodge` {float}:
        Relative size of the splodge used to compute multiple triangle indices and the fwhm
        of the 'gauss' technique,\n
    `naive_err` {bool}:
        If True, the uncertainties are computed using the std of the overall
        cvis or bs array. Otherwise, the uncertainties are computed using
        covariance matrices,\n
    `n_wl` {int}:
        Number of elements to sample the spectral filters (default: 3),\n
    `n_blocks` {float}:
        Number of separated blocks use to split the data cube and get more
        accurate uncertainties (default: 0, n_blocks = n_ps),\n
    `theta_detector`: {float}
        Angle [deg] to rotate the mask compare to the detector (if the mask is not
        perfectly aligned with the detector, e.g.: VLT/VISIR) ,\n
    `i_wl`: {int}
        Only used for IFU data (e.g.: IFS/SPHERE), select the desired spectral channel
        to retrieve the appropriate wavelength and mask positions, \n
    `unbias_v2`: {bool}
        If True, the squared visibilities are unbiased using the Fourier base, \n
    `targetname` {str}:
        Name of the target to save in oifits file (if not in header of the
        cube),\n
    `save_to` {str}:
        Name of the repository to save the figures,\n
    `verbose` {bool}:
        If True, print usefull informations during the process.\n
    `display` {bool}:
        If True, display all figures,\n

    Returns:
    --------
    `obs_result` {class object}:
        Return all interferometric observables (.vis2, .e_vis2, .cp, .e_cp, etc.), information relative
        to the used mask (.mask), the computed matrices and statistic (.matrix)
        and the important information (.infos). The .mask, .infos and .matrix are also class with
        various quantities (see .mask.__dict__.keys()).
    """
    if verbose:
        cprint("\n-- Starting extraction of observables --", "cyan")
    start_time = time.time()

    if save_to is not None:
        if not os.path.exists(save_to):
            os.mkdir(save_to)

    with fits.open(filename) as hdu:
        hdr = hdu[0].header

    infos = _check_input_infos(
        hdr, targetname=targetname, filtname=filtname, instrum=instrum, verbose=False
    )

    if "INSTRUME" not in hdr.keys():
        hdr["INSTRUME"] = infos["instrument"]
    # 1. Open the data cube and perform a series of roll (both axis) to avoid
    # grid artefact (negative fft values).
    # ------------------------------------------------------------------------
    ft_arr, n_ps, npix = _construct_ft_arr(cube)

    # Number of aperture in the mask
    try:
        n_holes = len(get_mask(infos.instrument, maskname))
    except TypeError:
        return None

    # 2. Determine the number of different baselines (bl), bispectrums (bs) or
    # covariance matrices (cov) and associates each holes as couple for bl or
    # triplet for bs (or cp) using compute_index_mask function (see ami_function.py).
    # ------------------------------------------------------------------------
    index_mask = compute_index_mask(n_holes)

    n_baselines = index_mask.n_baselines

    closing_tri = _format_closing_triangle(index_mask)

    # 3. Compute the match filter mf
    # ------------------------------------------------------------------------
    mf = make_mf(
        maskname,
        infos.instrument,
        infos.filtname,
        npix,
        peakmethod=peakmethod,
        fw_splodge=fw_splodge,
        n_wl=n_wl,
        cutoff=cutoff,
        hole_diam=hole_diam,
        scaling=scaling_uv,
        theta_detector=theta_detector,
        i_wl=i_wl,
        display=display,
        save_to=save_to,
        filename=filename,
    )

    ifig = 2
    if save_to is not None:
        figname = os.path.join(save_to, Path(filename).stem)
        plt.savefig(f"{figname}_{ifig}.pdf")

    ifig += 1

    if mf is None:
        return None

    # We store the principal results in the new dictionnary to be save at the end
    obs_result = {"u": mf.u, "v": mf.v, "wl": mf.wl, "e_wl": mf.e_wl}

    # 4. Compute indices for the multiple triangle technique (tri_pix function)
    # -------------------------------------------------------------------------
    l_B = np.sqrt(mf.u ** 2 + mf.v ** 2)  # Length of different bl [m]
    minbl = np.min(l_B)

    if n_holes >= 15:
        sampledisk_r = minbl / 2 / mf.wl * mf.pixelSize * npix * 0.9
    else:
        sampledisk_r = minbl / 2 / mf.wl * mf.pixelSize * npix * fw_splodge

    if bs_multi_tri:
        closing_tri_pix = tri_pix(npix, sampledisk_r, display=display, verbose=verbose)
    else:
        closing_tri_pix = None

    # 5. Display the power spectrum of the first frame to check the computed
    # positions of the peaks.
    # ------------------------------------------------------------------------
    if display:
        _show_complex_ps(ft_arr)
        if save_to is not None:
            plt.savefig(f"{figname}_{ifig}.pdf")
        ifig += 1

        _show_peak_position(ft_arr, n_baselines, mf, maskname, peakmethod)
        if save_to is not None:
            plt.savefig(f"{figname}_{ifig}.pdf")
        ifig += 1

    if verbose:
        print("\nFilename: %s" % filename)
        print("# of frames = %i" % n_ps)

    n_blocks = _set_good_nblocks(n_blocks, n_ps)

    # 6. Extract the complex quantities from the fft_arr (complex vis, bispectrum,
    # phase, etc.)
    # ------------------------------------------------------------------------
    fringe_peak = give_peak_info2d(mf, n_baselines, npix, npix)

    if verbose:
        print("\nCalculating V^2 and BS...")

    complex_bs = _compute_complex_bs(
        ft_arr,
        index_mask,
        fringe_peak,
        mf,
        dark_ps=None,
        closing_tri_pix=closing_tri_pix,
        bs_multi_tri=bs_multi_tri,
        verbose=verbose,
    )

    cvis_arr = complex_bs["vis_arr"]["complex"]
    v2_arr = complex_bs["vis_arr"]["squared"]
    bs_arr = complex_bs["bs_arr"]
    fluxes = complex_bs["fluxes"]

    # 7. Compute correlated noise and bias at the peak position
    # ---------------------------------------------------------
    bias, dark_bias, autocor_noise = _compute_corr_noise(
        complex_bs, ft_arr, fringe_peak
    )

    v2_arr_unbiased, bias_arr = _unbias_v2_arr(
        v2_arr, npix, fringe_peak, bias, dark_bias, autocor_noise, unbias=unbias_v2
    )

    # 8. Turn Arrays into means and covariance matrices
    # -------------------------------------------------
    v2_quantities = _compute_v2_quantities(v2_arr_unbiased, bias_arr, n_blocks)

    bs_quantities = _compute_bs_quantities(
        bs_arr, v2_quantities["v2"], fluxes, index_mask, n_blocks
    )

    bs_v2_cov = _compute_bs_v2_cov(
        bs_arr, v2_arr_unbiased, v2_quantities["v2"], bs_quantities["bs"], index_mask
    )

    if compute_cp_cov:
        cp_cov = _compute_cp_cov(
            bs_arr, bs_quantities["bs"], index_mask, disable=np.invert(verbose)
        )
    else:
        cp_cov = None

    # 9. Now normalize all extracted observables
    vis2_norm, obs_norm = _normalize_all_obs(
        bs_quantities,
        v2_quantities,
        cvis_arr,
        cp_cov,
        bs_v2_cov,
        fluxes,
        index_mask,
        infos,
        expert_plot=display,
    )
    if save_to is not None:
        plt.savefig(f"{figname}_{ifig}.pdf")
    ifig += 1

    obs_result["vis2"] = vis2_norm

    # 10. Now we compute the cp quantities and store them with the other observables
    obs_result = _compute_cp(obs_result, obs_norm, infos, expert_plot=display)

    if save_to is not None:
        plt.savefig(f"{figname}_{ifig}.pdf")
    ifig += 1

    if display:
        _show_norm_matrices(obs_norm, expert_plot=expert_plot)
        if save_to is not None:
            plt.savefig(f"{figname}_{ifig}.pdf")
        ifig += 1

    t3_coord, bl_cp = _compute_t3_coord(mf, index_mask)
    bl_v2 = np.sqrt(mf.u ** 2 + mf.v ** 2)
    obs_result["bl"] = bl_v2
    obs_result["bl_cp"] = bl_cp

    # 11. Now we compute the uncertainties using the covariance matrix (for v2)
    # and the variance matrix for the cp.
    obs_result = _compute_uncertainties(obs_result, obs_norm, naive_err=naive_err)

    # 12. Compute scaling error due to phase error (piston) between holes.
    fitmat = _compute_phs_piston(complex_bs, index_mask, display=expert_plot)
    phs_v2corr = _compute_phs_error(complex_bs, fitmat, index_mask, npix)
    obs_norm["phs_v2corr"] = phs_v2corr

    # 13. Compute the absolute oriention (North-up, East-left)
    # ------------------------------------------------------------------------
    pa = compute_pa(hdr, n_ps, display=display, verbose=verbose)

    # Compile informations in the storage infos class
    infos = _add_infos_header(infos, hdr, mf, pa, filename, maskname, npix)

    mask = {
        "bl2h_ix": index_mask.bl2h_ix,
        "bs2bl_ix": index_mask.bs2bl_ix,
        "closing_tri": closing_tri,
        "xycoord": mf.xy_coords,
        "n_holes": index_mask.n_holes,
        "n_baselines": index_mask.n_baselines,
        "t3_coord": t3_coord,
    }

    # Finally we store the computed matrices (cov, var, arr, etc,), the informations
    # and the mask parameters to the final output.
    obs_result["mask"] = mask
    obs_result["infos"] = infos
    obs_result["matrix"] = obs_norm

    t = time.time() - start_time
    m = t // 60

    if save_to is not None:
        produce_result_pdf(save_to, Path(filename).stem)

    if verbose:
        cprint("\nDone (exec time: %d min %2.1f s)." % (m, t - m * 60), color="magenta")
    return dict2class(obs_result)
Пример #3
0
def make_mf(maskname, instrument, filtname, npix,
            peakmethod='fft', n_wl=3, theta_detector=0,
            cutoff=1e-4, hole_diam=0.8, fw_splodge=0.7,
            verbose=False, diag_plot=False, display=True):
    """
    Summary:
    --------

    Compute the match filter mf which give the indices of the peak positions (mf.pvct)
    and the associated gains (mf.gvct) in the image. Contains also the u-v coordinates,
    wavelengths informations, holes mask positions (mf.xy_coords), centered mf (mf.cpvct,
    mf.gpvct), etc.

    Parameters:
    -----------
    `maskname`: str
        Name of the mask (number of holes),\n
    `instrument`: str
        Instrument used (default = jwst),\n
    `filtname`: str
        Name of the filter,\n
    `npix`: int
        Size of the image,\n
    `peakmethod` {str}:
        3 methods are used to sample the u-v space: 'fft' uses fft between individual holes to compute
        the expected splodge positions; 'square' compute the splodge in a square using the expected
        fraction of pixel to determine its weight; 'gauss' considers a gaussian splodge (with a gaussian
        weight) to get the same splodge side for each n(n-1)/2 baselines,\n
    `n_wl`: int
        number of wavelengths to use to simulate bandwidth,\n
    `theta_detector`: float
        Angle [deg] to rotate the mask compare to the detector (if the mask is not
        perfectly aligned with the detector, e.g.: VLT/VISIR) ,\n
    `cutoff`: float
        cutoff limit between noise and signal pixels in simulated transforms,\n
    `hole_diam`: float
        Diameter of a single aperture (0.8 for JWST),\n
    `fw_splodge` {float}:
        Relative size of the splodge used to compute multiple triangle indices and the fwhm
        of the 'gauss' technique,\n
    """

    # Get detector, filter and mask informations
    # ------------------------------------------
    pixelsize = get_pixel_size(instrument)  # Pixel size of the detector [rad]
    # Wavelength of the filter (filt[0]: central, filt[1]: width)
    filt = get_wavelength(instrument, filtname)
    xy_coords = get_mask(instrument, maskname)  # mask coordinates

    x_mask = xy_coords[:, 0]
    y_mask = xy_coords[:, 1]

    x_mask_rot = x_mask*np.cos(np.deg2rad(theta_detector)) + \
        y_mask*np.sin(np.deg2rad(theta_detector))
    y_mask_rot = -x_mask*np.sin(np.deg2rad(theta_detector)) + \
        y_mask*np.cos(np.deg2rad(theta_detector))

    xy_coords_rot = []
    for i in range(len(x_mask)):
        xy_coords_rot.append([x_mask_rot[i], y_mask_rot[i]])
    xy_coords = np.array(xy_coords_rot)

    if display:
        _plot_mask_coord(xy_coords, maskname, instrument)

    n_holes = xy_coords.shape[0]

    index_mask = compute_index_mask(n_holes)
    n_baselines = index_mask.n_baselines
    n_bispect = index_mask.n_bispect

    ncp_i = int((n_holes - 1)*(n_holes - 2)/2)
    if verbose:
        cprint('---------------------------', 'cyan')
        cprint('%s (%s): %i holes masks' %
               (instrument.upper(), filtname, n_holes), 'cyan')
        cprint('---------------------------', 'cyan')
        cprint('nbl = %i, nbs = %i, ncp_i = %i, ncov = %i' %
               (n_baselines, n_bispect, ncp_i, index_mask.n_cov), 'cyan')

    # Consider the filter to be made up of n_wl wavelengths
    wl = np.arange(n_wl)/n_wl*filt[1]
    wl = wl - np.mean(wl) + filt[0]

    Sum, Sum_c = 0, 0

    mf_ix = np.zeros([2, n_baselines], dtype=int)  # matched filter
    mf_ix_c = np.zeros([2, n_baselines], dtype=int)  # matched filter

    if verbose:
        print('\n- Calculating sampling of', n_holes, 'holes array...')

    innerpix, innerpix_center = _compute_center_splodge(npix, pixelsize, filt,
                                                        hole_diam=hole_diam)

    u, v = _compute_uv_coord(xy_coords, index_mask, filt, pixelsize, npix,
                             round_uv_to_pixel=False)

    mf_pvct = mf_gvct = mfc_pvct = mfc_gvct = None

    for i in range(n_baselines):
        args = {'i': i, 'npix': npix, 'pixelsize': pixelsize,
                'innerpix': innerpix, 'innerpix_center': innerpix_center}

        if peakmethod == 'fft':
            ind_peak = _peak_fft_method(xy_coords=xy_coords, wl=wl, index_mask=index_mask,
                                        **args)
        elif peakmethod == 'square':
            ind_peak = _peak_square_method(u=u, v=v, **args)
        elif peakmethod == 'one':
            ind_peak = _peak_one_method(u=u, v=v, **args)
        elif peakmethod == 'gauss':
            ind_peak = _peak_gauss_method(u=u, v=v, filt=filt, index_mask=index_mask,
                                          fw_splodge=fw_splodge, **args,
                                          hole_diam=hole_diam)
        else:
            cprint(
                "Error: choose the extraction method 'gauss', 'fft' or 'square'.", 'red')
            return None
        
        # Compute the cutoff limit before saving the gain map
        pixelvector = np.where(ind_peak['flat'] >= cutoff)[0]
        pixelvector_c = np.where(ind_peak['centered'] >= cutoff)[0]

        # Now normalise the pixel gain, so that using the matched filter
        # on an ideal splodge is equivalent to just looking at the peak...
        if peakmethod == 'gauss':
            pixelgain, pixelgain_c = _normalize_gain(ind_peak['gain_f'], ind_peak['gain_c'],
                                                     pixelvector, pixelvector_c)
        else:
            pixelgain, pixelgain_c = _normalize_gain(ind_peak['flat'], ind_peak['centered'],
                                                     pixelvector, pixelvector_c)

        mf_ix[0, i] = Sum
        Sum = Sum + len(pixelvector)
        mf_ix[1, i] = Sum

        mf_ix_c[0, i] = Sum_c
        Sum_c = Sum_c + len(pixelvector_c)
        mf_ix_c[1, i] = Sum_c

        if (i == 0):
            mf_pvct = list(pixelvector)
            mf_gvct = list(pixelgain)
            mfc_pvct = list(pixelvector_c)
            mfc_gvct = list(pixelgain_c)
        else:
            mf_pvct.extend(list(pixelvector))
            mf_gvct.extend(list(pixelgain))
            mfc_pvct.extend(list(pixelvector_c))
            mfc_gvct.extend(list(pixelgain_c))

    mf = np.zeros([npix, npix, n_baselines], dtype=[('norm', float), ('conj', float),
                                                    ('norm_c', float), ('conj_c', float)])

    for i in range(n_baselines):
        mf_tmp = np.zeros([npix, npix])
        mf_tmp_c = np.zeros([npix, npix])

        ind = mf_pvct[mf_ix[0, i]:mf_ix[1, i]]
        ind_c = mfc_pvct[mf_ix_c[0, i]:mf_ix_c[1, i]]

        mf_tmp.ravel()[ind] = mf_gvct[mf_ix[0, i]:mf_ix[1, i]]
        mf_tmp_c.ravel()[ind_c] = mfc_gvct[mf_ix_c[0, i]:mf_ix_c[1, i]]

        mf_tmp = mf_tmp.reshape([npix, npix])
        mf_tmp_c = mf_tmp_c.reshape([npix, npix])

        mf['norm'][:, :, i] = np.roll(mf_tmp, 0, axis=1)
        mf['norm_c'][:, :, i] = np.roll(mf_tmp_c, 0, axis=1)

        mf_temp_rot = np.roll(
            np.roll(np.rot90(np.rot90(mf_tmp)), 1, axis=0), 1, axis=1)
        mf_temp_rot_c = np.roll(
            np.roll(np.rot90(np.rot90(mf_tmp_c)), 1, axis=0), 1, axis=1)

        mf['conj'][:, :, i] = mf_temp_rot
        mf['conj_c'][:, :, i] = mf_temp_rot_c

        norm = np.sqrt(np.sum(mf['norm'][:, :, i]**2))

        mf['norm'][:, :, i] = mf['norm'][:, :, i]/norm
        mf['conj'][:, :, i] = mf['conj'][:, :, i]/norm

        mf['norm_c'][:, :, i] = mf['norm_c'][:, :, i]/norm
        mf['conj_c'][:, :, i] = mf['conj_c'][:, :, i]/norm

    rmat, imat = _make_overlap_mat(mf, n_baselines, display=diag_plot)

    mf_tot = np.sum(mf['norm'], axis=2) + np.sum(mf['conj'], axis=2)
    mf_tot_m = np.sum(mf['norm'], axis=2) - np.sum(mf['conj'], axis=2)

    im_uv = np.roll(np.fft.fftshift(mf_tot), 1, axis=1)

    if display:
        plt.figure(figsize=(6, 6))
        plt.title('(u-v) plan - mask %s' %
                  (maskname), fontsize=14)
        plt.imshow(im_uv, origin='lower')
        plt.plot(npix//2+1, npix//2, 'r+')
        plt.ylabel('Y [pix]')  # , fontsize=12)
        plt.xlabel('X [pix]')  # , fontsize=12)
        plt.tight_layout()

    out = {'cube': mf['norm'],
           'imat': imat,
           'rmat': rmat,
           'uv': im_uv,
           'tot': mf_tot,
           'tot_m': mf_tot_m,
           'pvct': mf_pvct,
           'gvct': mf_gvct,
           'cpvct': mfc_pvct,
           'cgvct': mfc_gvct,
           'ix': mf_ix,
           'u': u*filt[0],
           'v': v*filt[0],
           'wl': filt[0],
           'e_wl': filt[1],
           'pixelSize': pixelsize,
           'xy_coords': xy_coords
           }

    return dict2class(out)