Beispiel #1
0
def _peak_gauss_method(
    i,
    npix,
    u,
    v,
    filt,
    index_mask,
    pixelsize,
    innerpix,
    innerpix_center,
    fw_splodge=0.7,
    hole_diam=0.8,
):
    mf = np.zeros([npix, npix])
    n_holes = index_mask.n_holes

    l_B = np.sqrt(u**2 + v**2)
    minbl = np.min(l_B) * filt[0]
    if n_holes >= 15:
        sampledisk_r = minbl / 2 / filt[0] * pixelsize * npix * 0.9
    else:
        sampledisk_r = minbl / 2.0 / filt[0] * pixelsize * npix * fw_splodge

    xspot = float(np.round(v[i] * pixelsize * npix + npix / 2.0))
    yspot = float(np.round(u[i] * pixelsize * npix + npix / 2.0))
    mf = plot_circle(mf, xspot, yspot, sampledisk_r, display=False)
    mf = np.roll(mf, npix // 2, axis=0)
    mf = np.roll(mf, npix // 2, axis=1)

    X = [np.arange(npix), np.arange(npix), 1]
    splodge_fwhm = hole_diam / filt[0] * pixelsize * npix / 1.9
    param = {
        "A": 1,
        "x0": -npix // 2 + yspot,
        "y0": -npix // 2 + xspot,
        "fwhm_x": splodge_fwhm,
        "fwhm_y": splodge_fwhm,
        "theta": 0,
    }
    gauss = gauss_2d_asym(X, param)
    gauss = np.roll(gauss, npix // 2, axis=0)
    gauss = np.roll(gauss, npix // 2, axis=1)
    mfg = gauss / np.sum(gauss)
    mf_gain_flat = mfg.ravel()
    mf_gain_centered = norm_max(np.fft.fftshift(mfg).ravel())
    mf_flat = norm_max(mf.ravel())
    mf_centered = norm_max(np.fft.fftshift(mf).ravel())

    mf_flat[innerpix] = 0.0
    mf_gain_flat[innerpix] = 0.0
    mf_centered[innerpix_center] = 0.0
    mf_gain_centered[innerpix_center] = 0.0

    mf = {
        "flat": mf_flat,
        "centered": mf_centered,
        "gain_f": mf_gain_flat,
        "gain_c": mf_gain_centered,
    }
    return dict2class(mf)
Beispiel #2
0
def check_seeing_cond(list_nrm):
    """ Extract the seeing conditions, parang, averaged vis2
    and cp of a list of nrm classes extracted with extract_bs
    function (bispect.py).

    Output
    ------
    If output is **res**, access to parallactic angle by `res.infos.pa`, or
    `res.infos.seeing` for the seeing across multiple nrm data (files).

    """
    l_seeing, l_vis2, l_cp, l_pa, l_mjd = [], [], [], [], []

    hdr = fits.open(list_nrm[0].infos.filename)[0].header
    for nrm in list_nrm:
        hdr = fits.open(nrm.infos.filename)[0].header
        pa = np.mean(sphere_parang(hdr))
        seeing = nrm.infos.seeing
        mjd = hdr['MJD-OBS']
        l_vis2.append(np.mean(nrm.vis2))
        l_cp.append(np.mean(nrm.cp))
        l_seeing.append(seeing)
        l_pa.append(pa)
        l_mjd.append(mjd)

    res = {
        'pa': l_pa,
        'seeing': l_seeing,
        'vis2': l_vis2,
        'cp': l_cp,
        'mjd': l_mjd,
        'target': hdr['OBJECT']
    }

    return dict2class(sanitize_array(res))
Beispiel #3
0
def regress_noc(x, y, weights):
    """Python version of IDL regress_noc."""
    sx = x.shape
    sy = y.shape
    nterm = sx[0]  # # OF TERMS
    npts = sy[0]  # # OF OBSERVATIONS

    if (len(weights) != sy[0]) or (len(sx) != 2) or (sy[0] != sx[1]):
        cprint("Incompatible arrays to compute slope error.", "red")

    xwy = np.dot(x, (weights * y))
    wx = np.zeros([npts, nterm])
    for i in range(npts):
        wx[i, :] = x[:, i] * weights[i]
    xwx = np.dot(x, wx)
    cov = np.linalg.inv(xwx)
    coeff = np.dot(cov, xwy)
    yfit = np.dot(x.T, coeff)
    if npts != nterm:
        MSE = np.sum(weights * (yfit - y) ** 2) / (npts - nterm)

    var_yfit = np.zeros(npts)

    for i in range(npts):
        var_yfit[i] = np.dot(np.dot(x[:, i].T, cov), x[:, i])  # Neter et al pg 233

    dic = {"coeff": coeff, "cov": cov, "yfit": yfit, "MSE": MSE, "var_yfit": var_yfit}
    return dict2class(dic)
Beispiel #4
0
def _peak_gauss_method(i,
                       npix,
                       u,
                       v,
                       filt,
                       index_mask,
                       pixelsize,
                       innerpix,
                       innerpix_center,
                       fw_splodge=0.7,
                       hole_diam=0.8):
    mf = np.zeros([npix, npix])
    n_holes = index_mask.n_holes

    l_B = np.sqrt(u**2 + v**2)
    minbl = np.min(l_B) * filt[0]
    if n_holes >= 15:
        sampledisk_r = minbl / 2 / filt[0] * pixelsize * npix * 0.9
    else:
        sampledisk_r = minbl / 2. / \
            filt[0] * pixelsize * npix * fw_splodge

    xspot = float(np.round((v[i] * pixelsize * npix + npix / 2.)))
    yspot = float(np.round((u[i] * pixelsize * npix + npix / 2.)))
    mf = plot_circle(mf, xspot, yspot, sampledisk_r, display=False)
    mf = np.roll(mf, npix // 2, axis=0)
    mf = np.roll(mf, npix // 2, axis=1)

    X = [np.arange(npix), np.arange(npix), 1]
    splodge_fwhm = hole_diam / filt[0] * pixelsize * npix / 1.9
    param = {
        'A': 1,
        'x0': -npix // 2 + yspot,
        'y0': -npix // 2 + xspot,
        'fwhm_x': splodge_fwhm,
        'fwhm_y': splodge_fwhm,
        'theta': 0
    }
    gauss = gauss_2d_asym(X, param)
    gauss = np.roll(gauss, npix // 2, axis=0)
    gauss = np.roll(gauss, npix // 2, axis=1)
    mfg = gauss / np.sum(gauss)
    mf_gain_flat = mfg.ravel()
    mf_gain_centered = norm_max(np.fft.fftshift(mfg).ravel())
    mf_flat = norm_max(mf.ravel())
    mf_centered = norm_max(np.fft.fftshift(mf).ravel())

    mf_flat[innerpix] = 0.
    mf_gain_flat[innerpix] = 0.
    mf_centered[innerpix_center] = 0.
    mf_gain_centered[innerpix_center] = 0.

    mf = {
        'flat': mf_flat,
        'centered': mf_centered,
        'gain_f': mf_gain_flat,
        'gain_c': mf_gain_centered
    }
    return dict2class(mf)
Beispiel #5
0
def _check_input_infos(hdr, targetname=None, filtname=None, instrum=None, verbose=True):
    """Extract informations from the header and fill the missing values with the
    input arguments. Return the infos class containing important informations of
    the input header (keys: target, seeing, instrument, ...)
    """
    target = hdr.get("OBJECT")
    filt = hdr.get("FILTER")
    instrument = hdr.get("INSTRUME", instrum)
    mod = hdr.get("HIERARCH ESO DET ID")

    if (mod == "IFS") & (instrument == "SPHERE"):
        instrument = instrument + "-" + mod

    # Check the target name
    if (target is None) or (target == "STD"):
        if targetname is not None:
            target = targetname
            if verbose:
                cprint(
                    "Warning: OBJECT is not in the header, targetname is used (%s)."
                    % targetname,
                    "green",
                )
        else:
            cprint("Warning: target name not found (header or as input).", "green")

    # Check the filter used
    if filt is None:
        if filtname is not None:
            filt = filtname
            if verbose:
                cprint(
                    "Warning: FILTER is not in the header, filtname is used (%s)."
                    % filtname,
                    "green",
                )

    # Check the instrument used
    if instrument is None:
        raise OSError("instrum not found (in the header or as input).")

    # Origin files
    orig = hdr.get("ORIGFILE", "SimulatedData")
    if orig == "SimulatedData":
        orig = hdr.get("ARCFILE", "SimulatedData")

    # Seeing informations
    seeing_start = float(hdr.get("HIERARCH ESO TEL AMBI FWHM START", 0))
    seeing_end = float(hdr.get("HIERARCH ESO TEL AMBI FWHM END", 0))
    seeing = np.mean([seeing_start, seeing_end])

    infos = {
        "filtname": filt,
        "target": target,
        "instrument": instrument,
        "orig": orig,
        "seeing": seeing,
    }
    return dict2class(infos)
Beispiel #6
0
def _check_input_infos(hdr, targetname=None, filtname=None, verbose=True):
    """ Extract informations from the header and fill the missing values with the
    input arguments. Return the infos class containing important informations of 
    the input header (keys: target, seeing, instrument, ...)
    """
    target = hdr.get('OBJECT')
    filt = hdr.get('FILTER')
    instrument = hdr.get('INSTRUME')
    # Check the target name
    if (target is None) or (target == 'STD'):
        if (targetname is not None):
            target = targetname
            if verbose:
                cprint(
                    "Warning: OBJECT is not in the header, targetname is used (%s)."
                    % targetname, "green")
        else:
            cprint("Error: target name not found (header or as input).", "red")

    # Check the filter used
    if (filt is None):
        if (filtname is not None):
            filt = filtname
            if verbose:
                cprint(
                    "Warning: FILTER is not in the header, filtname is used (%s)."
                    % filtname, "green")
        else:
            cprint("Error: filter not found (in the header or as input).",
                   "red")

    # Check the instrument used
    if instrument is None:
        cprint("Error: INSTRUME not found (header).", "red")

    # Origin files
    orig = hdr.get("ORIGFILE", 'SimulatedData')
    if orig == 'SimulatedData':
        orig = hdr.get("ARCFILE", 'SimulatedData')

    # Seeing informations
    seeing_start = float(hdr.get('HIERARCH ESO TEL AMBI FWHM START', 0))
    seeing_end = float(hdr.get('HIERARCH ESO TEL AMBI FWHM END', 0))
    seeing = np.mean([seeing_start, seeing_end])

    infos = {
        'filtname': filt,
        'target': target,
        'instrument': instrument,
        'orig': orig,
        'seeing': seeing
    }
    return dict2class(infos)
def load_bs_hdf5(filename):
    """Load hdf5 file and format as class like object (same
    format as `amical.extract_bs()`
    """
    from munch import munchify as dict2class
    import h5py

    dict_bs = {"matrix": {}, "infos": {"hdr": {}}, "mask": {}}
    with h5py.File(filename, "r") as hf2:
        obs = hf2["obs"]
        for o in obs:
            dict_bs[o] = np.array(obs.get(o))

        matrix = hf2["matrix"]
        for key in matrix:
            dict_bs["matrix"][key] = np.array(matrix.get(key))

        if len(dict_bs["matrix"]["cp_cov"]) == 1:
            dict_bs["matrix"]["cp_cov"] = None

        mask = hf2["mask"]
        for key in mask:
            if key not in ["u1", "u2", "v1", "v2"]:
                dict_bs["mask"][key] = np.array(mask.get(key))

        t3_coord = {
            "u1": np.array(mask.get("u1")),
            "u2": np.array(mask.get("u2")),
            "v1": np.array(mask.get("v1")),
            "v2": np.array(mask.get("v2")),
        }

        dict_bs["mask"]["t3_coord"] = t3_coord

        infos = hf2["infos"]
        for key in hf2["infos"].attrs.keys():
            dict_bs["infos"][key] = infos.attrs[key]

        hdr = hf2["hdr"]
        for key in hdr.attrs.keys():
            dict_bs["infos"]["hdr"][key] = hdr.attrs[key]

        bs_save = dict2class(dict_bs)
    return bs_save
Beispiel #8
0
def load_bs_hdf5(filename):
    """ Load hdf5 file and format as class like object (same
    format as `amical.extract_bs()`
    """
    hf2 = h5py.File(filename, 'r')

    dict_bs = {'matrix': {}, 'infos': {'hdr': {}}, 'mask': {}}

    obs = hf2['obs']
    for o in obs:
        dict_bs[o] = obs[o].value

    matrix = hf2['matrix']
    for key in matrix:
        dict_bs['matrix'][key] = matrix[key].value

    if len(dict_bs['matrix']['cp_cov']) == 1:
        dict_bs['matrix']['cp_cov'] = None

    mask = hf2['mask']
    for key in mask:
        if key not in ['u1', 'u2', 'v1', 'v2']:
            dict_bs['mask'][key] = mask[key].value

    t3_coord = {
        'u1': mask['u1'].value,
        'u2': mask['u2'].value,
        'v1': mask['v1'].value,
        'v2': mask['v2'].value,
    }

    dict_bs['mask']['t3_coord'] = t3_coord

    infos = hf2['infos']
    for key in infos:
        dict_bs['infos'][key] = infos[key].value

    hdr = hf2['hdr']
    for key in hdr:
        dict_bs['infos']['hdr'][key] = hdr[key].value

    bs_save = dict2class(dict_bs)
    return bs_save
Beispiel #9
0
def _apply_flag(dict_calibrated, unit="arcsec"):
    """Apply flag and convert to appropriete units."""

    from munch import munchify as dict2class

    wl = dict_calibrated["OI_WAVELENGTH"]["EFF_WAVE"]
    uv_scale = {
        "m": 1,
        "rad": 1 / wl,
        "arcsec": 1 / wl / rad2mas(1e-3),
        "lambda": 1 / wl / 1e6,
    }

    U = dict_calibrated["OI_VIS2"]["UCOORD"] * uv_scale[unit]
    V = dict_calibrated["OI_VIS2"]["VCOORD"] * uv_scale[unit]

    flag_v2 = np.invert(dict_calibrated["OI_VIS2"]["FLAG"])
    V2 = dict_calibrated["OI_VIS2"]["VIS2DATA"][flag_v2]
    e_V2 = dict_calibrated["OI_VIS2"]["VIS2ERR"][flag_v2] * 1
    sp_freq_vis = dict_calibrated["OI_VIS2"]["BL"][flag_v2] * uv_scale[unit]
    flag_cp = np.invert(dict_calibrated["OI_T3"]["FLAG"])
    cp = dict_calibrated["OI_T3"]["T3PHI"][flag_cp]
    e_cp = dict_calibrated["OI_T3"]["T3PHIERR"][flag_cp]
    sp_freq_cp = dict_calibrated["OI_T3"]["BL"][flag_cp] * uv_scale[unit]
    bmax = 1.2 * np.max(np.sqrt(U**2 + V**2))

    cal_flagged = dict2class({
        "U": U,
        "V": V,
        "bmax": bmax,
        "vis2": V2,
        "e_vis2": e_V2,
        "cp": cp,
        "e_cp": e_cp,
        "sp_freq_vis": sp_freq_vis,
        "sp_freq_cp": sp_freq_cp,
        "wl": wl[0],
        "band": dict_calibrated["info"]["FILT"],
    })

    return cal_flagged
def check_seeing_cond(list_nrm):  # pragma: no cover
    """Extract the seeing conditions, parang, averaged vis2
    and cp of a list of nrm classes extracted with extract_bs
    function (bispect.py).

    Output
    ------
    If output is **res**, access to parallactic angle by `res.infos.pa`, or
    `res.infos.seeing` for the seeing across multiple nrm data (files).

    """
    from astropy.io import fits
    from munch import munchify as dict2class

    l_seeing, l_vis2, l_cp, l_pa, l_mjd = [], [], [], [], []

    with fits.open(list_nrm[0].infos.filename) as fd:
        hdr = fd[0].header
    for nrm in list_nrm:
        with fits.open(nrm.infos.filename) as fd:
            hdr = fd[0].header
        pa = np.mean(sphere_parang(hdr))
        seeing = nrm.infos.seeing
        mjd = hdr["MJD-OBS"]
        l_vis2.append(np.mean(nrm.vis2))
        l_cp.append(np.mean(nrm.cp))
        l_seeing.append(seeing)
        l_pa.append(pa)
        l_mjd.append(mjd)

    res = {
        "pa": l_pa,
        "seeing": l_seeing,
        "vis2": l_vis2,
        "cp": l_cp,
        "mjd": l_mjd,
        "target": hdr["OBJECT"],
    }

    return dict2class(sanitize_array(res))
Beispiel #11
0
def _apply_flag(dict_calibrated, unit='arcsec'):
    """ Apply flag and convert to appropriete units."""

    wl = dict_calibrated['OI_WAVELENGTH']['EFF_WAVE']
    uv_scale = {
        'm': 1,
        'rad': 1 / wl,
        'arcsec': 1 / wl / rad2mas(1e-3),
        'lambda': 1 / wl / 1e6
    }

    U = dict_calibrated['OI_VIS2']['UCOORD'] * uv_scale[unit]
    V = dict_calibrated['OI_VIS2']['VCOORD'] * uv_scale[unit]

    flag_v2 = np.invert(dict_calibrated['OI_VIS2']['FLAG'])
    V2 = dict_calibrated['OI_VIS2']['VIS2DATA'][flag_v2]
    e_V2 = dict_calibrated['OI_VIS2']['VIS2ERR'][flag_v2] * 1
    sp_freq_vis = dict_calibrated['OI_VIS2']['BL'][flag_v2] * uv_scale[unit]
    flag_cp = np.invert(dict_calibrated['OI_T3']['FLAG'])
    cp = dict_calibrated['OI_T3']['T3PHI'][flag_cp]
    e_cp = dict_calibrated['OI_T3']['T3PHIERR'][flag_cp]
    sp_freq_cp = dict_calibrated['OI_T3']['BL'][flag_cp] * uv_scale[unit]
    bmax = 1.2 * np.max(np.sqrt(U**2 + V**2))

    cal_flagged = dict2class({
        'U': U,
        'V': V,
        'bmax': bmax,
        'vis2': V2,
        'e_vis2': e_V2,
        'cp': cp,
        'e_cp': e_cp,
        'sp_freq_vis': sp_freq_vis,
        'sp_freq_cp': sp_freq_cp,
        'wl': wl[0],
        'band': dict_calibrated['info']['FILT']
    })

    return cal_flagged
Beispiel #12
0
    def __init__(self, filename):
        self.fn = os.path.basename(filename)
        # read in multi-oifits extensions, make it one attribute of the class
        self.oi = dict2class(oifits.load(filename))
        self.stats = Stats()
        # Populate the geometry sub-class
        self.geometry = Geometry()
        staxyz = self.oi.OI_ARRAY.STAXYZ
        self.geometry.ctrs_inst = np.delete(
            staxyz, -1, 1
        )  # remove the Z column of all zeros
        self.geometry.ctrs_eqt = self.oi.OI_ARRAY.CTRS_EQT
        self.geometry.t3_bl = self.oi.OI_T3.BL
        self.geometry.t3_idx = self.oi.OI_T3.STA_INDEX
        self.geometry.vis_bl = self.oi.OI_VIS.BL
        self.geometry.vis_idx = self.oi.OI_VIS.STA_INDEX
        self.geometry.quad_idx, self.geometry.quad_bl = self.calc_quads()

        # construct strings (useful for labeling plots)
        bl_idx = self.geometry.vis_idx
        tri_idx = self.geometry.t3_idx
        bl_strings = []
        for idx in bl_idx:
            bl_strings.append(str(idx[0]) + '_' + str(idx[1]))
        tri_strings = []
        for idx in tri_idx:
            tri_strings.append(str(idx[0]) + '_' + str(idx[1]) + '_' + str(idx[2]))
        self.geometry.vis_idx_strings = bl_strings
        self.geometry.t3_idx_strings = tri_strings
        # If this is a multi-int oifits, calculate observable statistics
        # and add as attributes in the Stats subclass
        # otherwise access directly
        # judge based on shape
        if len(self.oi.OI_T3.T3PHI.shape) == 2:
            self.multi = True
            self.calc_stats()
        else:
            self.multi = False  # not a multi-integration OIFITS
Beispiel #13
0
def loadc(filename):
    """Same as load but provide an easy usable output as a class format (output.v2, or output.cp)."""
    from munch import munchify as dict2class

    dic = load(filename)
    res = {}
    # Extract infos
    res["target"] = dic["info"].get("OBJECT")

    res["calib"] = dic["info"].get("CALIB")
    res["seeing"] = dic["info"].get("SEEING")
    res["mjd"] = dic["info"].get("MJD")
    if res["mjd"] is None:
        res["mjd"] = dic["info"].get("MJD-OBS")

    # Extract wavelength
    res["wl"] = dic["OI_WAVELENGTH"]["EFF_WAVE"]
    res["e_wl"] = dic["OI_WAVELENGTH"]["EFF_BAND"]

    # Extract squared visibilities
    res["vis2"] = dic["OI_VIS2"]["VIS2DATA"]
    res["e_vis2"] = dic["OI_VIS2"]["VIS2ERR"]
    res["u"] = dic["OI_VIS2"]["UCOORD"]
    res["v"] = dic["OI_VIS2"]["VCOORD"]
    res["bl"] = dic["OI_VIS2"]["BL"]
    res["flag_vis"] = dic["OI_VIS2"]["FLAG"]

    # Extract closure phases
    res["cp"] = dic["OI_T3"]["T3PHI"]
    res["e_cp"] = dic["OI_T3"]["T3PHIERR"]
    res["u1"] = dic["OI_T3"]["U1COORD"]
    res["v1"] = dic["OI_T3"]["V1COORD"]
    res["u2"] = dic["OI_T3"]["U2COORD"]
    res["v2"] = dic["OI_T3"]["V2COORD"]
    res["bl_cp"] = dic["OI_T3"]["BL"]
    res["flag_cp"] = dic["OI_T3"]["FLAG"]

    return dict2class(res)
Beispiel #14
0
def wrap_raw(bs):
    """
    Wrap extraction product to save it as oifits

    `bs` : {munch.Munch}
        Object returned by amical.extract_bs() with raw observables,\n

    Returns
    --------
    `fake_cal` : {munch.Munch}
        Object that stores the raw observables in a format compatible with the
        output from amical.calibrate() and the input for `amical.save()`,\n
    """
    from munch import munchify as dict2class

    u1 = bs.u[bs.mask.bs2bl_ix[0, :]]
    v1 = bs.v[bs.mask.bs2bl_ix[0, :]]
    u2 = bs.u[bs.mask.bs2bl_ix[1, :]]
    v2 = bs.v[bs.mask.bs2bl_ix[1, :]]

    fake_cal = {
        "vis2": bs.vis2,
        "e_vis2": bs.e_vis2,
        "cp": bs.cp,
        "e_cp": bs.e_cp,
        "u": bs.u,
        "v": bs.v,
        "wl": bs.wl,
        "u1": u1,
        "v1": v1,
        "u2": u2,
        "v2": v2,
        "raw_t": bs,
        "raw_c": bs,
    }

    return dict2class(fake_cal)
Beispiel #15
0
def loadc(filename):
    """ Same as load but provide an easy usable output as a class format (output.v2, or output.cp). """
    dic = load(filename)
    res = {}
    # Extract infos
    res['target'] = dic['info']['OBJECT']

    res['calib'] = dic['info'].get('CALIB')
    res['seeing'] = dic['info'].get('SEEING')
    res['mjd'] = dic['info'].get('MJD')
    if res['mjd'] is None:
        res['mjd'] = dic['info'].get('MJD-OBS')

    # Extract wavelength
    res['wl'] = dic['OI_WAVELENGTH']['EFF_WAVE']
    res['e_wl'] = dic['OI_WAVELENGTH']['EFF_BAND']

    # Extract squared visibilities
    res['vis2'] = dic['OI_VIS2']['VIS2DATA']
    res['e_vis2'] = dic['OI_VIS2']['VIS2ERR']
    res['u'] = dic['OI_VIS2']['UCOORD']
    res['v'] = dic['OI_VIS2']['VCOORD']
    res['bl'] = dic['OI_VIS2']['BL']
    res['flag_vis'] = dic['OI_VIS2']['FLAG']

    # Extract closure phases
    res['cp'] = dic['OI_T3']['T3PHI']
    res['e_cp'] = dic['OI_T3']['T3PHIERR']
    res['u1'] = dic['OI_T3']['U1COORD']
    res['v1'] = dic['OI_T3']['V1COORD']
    res['u2'] = dic['OI_T3']['U2COORD']
    res['v2'] = dic['OI_T3']['V2COORD']
    res['bl_cp'] = dic['OI_T3']['BL']
    res['flag_cp'] = dic['OI_T3']['FLAG']

    return dict2class(res)
Beispiel #16
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)
Beispiel #17
0
def calibrate(res_t,
              res_c,
              clip=False,
              sig_thres=2,
              apply_phscorr=False,
              apply_atmcorr=True,
              AddindepCpErr=False,
              display=False):
    """ Calibrate v2 and cp from a science target and its calibrator.

    Parameters
    ----------
    `res_t` : {dict}
        Dictionnary containing extracted NRM data of science target (see bispect.py),\n
    `res_c` : {list or dict}
        Dictionnary or a list of dictionnary containing extracted NRM data of calibrator target,\n
    `clip` : {bool}
        If True, sigma clipping is performed over the calibrator files (if any) to reject bad
        observables due to seeing conditions, centering, etc.,\n
    `sig_thres` : {float}
        Threshold of the sigma clipping (default: 2-sigma around the median is used),\n    
    `apply_phscorr` : {bool}, optional
        If True, apply a phasor correction from seeing and wind shacking issues, by default False.\n
    `display`: {bool}
        If True, plot figures.


    Returns
    -------
    `cal`: {class}
        Class of calibrated data, keys are: `v2`, `e_v2` (squared visibities and errors),
        `cp`, `e_cp` (closure phase and errors), `visamp`, `e_visamp` (visibility
        ampliture and errors), `visphi`, `e_visphi` (visibility phase and errors), `u`, `v`
        (u-v coordinates), `wl` (wavelength), `raw_t` and `raw_c` (dictionnary of extracted raw
        NRM data, inputs of this function).
    """

    if type(res_c) is not list:
        res_c = [res_c]

    calib_tab = average_calib_files(res_c,
                                    sig_thres=sig_thres,
                                    display=display)

    if clip:
        cmn_v2_c, cmn_cp_c, std_v2_c, std_cp_c = (calib_tab.f_v2_clip,
                                                  calib_tab.f_cp_clip,
                                                  calib_tab.std_vis2_clip,
                                                  calib_tab.std_cp_clip)
    else:
        cmn_v2_c, cmn_cp_c, std_v2_c, std_cp_c = (calib_tab.f_v2,
                                                  calib_tab.f_cp,
                                                  calib_tab.std_vis2,
                                                  calib_tab.std_cp)

    v2_corr_t = 1
    if apply_atmcorr:
        v2_corr_t = _calc_correction_atm_vis2(res_t)

    if apply_phscorr:
        v2_corr_t *= res_t.matrix.phs_v2corr

    # Raw V2 target (corrected from atm correction and phasors.)
    v2_t = res_t.vis2 / v2_corr_t
    e_v2_t = res_t.e_vis2 / v2_corr_t
    #  e_v2_c = res_c[0].e_vis2/v2_corr_t

    # Raw CP target
    cp_t = res_t.cp
    e_cp_t = res_t.e_cp

    # Calibration by the weighted averages and taking into accound the std of the calibrators.
    # ---------------------------------------------
    vis2_calib = v2_t / cmn_v2_c
    cp_calib = cp_t - cmn_cp_c

    #
    n_holes = res_t.mask.n_holes
    if AddindepCpErr:
        err_scale = np.sqrt(n_holes / 3.)
    else:
        err_scale = 1

    # Quadratic added error due to calibrator dispersion (the average is weightened (see wtmn from amical.tools)).
    weightened_error = True
    if weightened_error:
        e_vis2_calib = np.sqrt(e_v2_t**2 / cmn_v2_c**2 +
                               std_v2_c**2 * v2_t**2 / cmn_v2_c**4)
    else:
        e_vis2_calib = np.sqrt(e_v2_t**2 + std_v2_c**2)

    e_cp_calib = np.sqrt(e_cp_t**2 + std_cp_c**2) * err_scale

    u1 = res_t.u[res_t.mask.bs2bl_ix[0, :]]
    v1 = res_t.v[res_t.mask.bs2bl_ix[0, :]]
    u2 = res_t.u[res_t.mask.bs2bl_ix[1, :]]
    v2 = res_t.v[res_t.mask.bs2bl_ix[1, :]]

    cal = {
        'vis2': vis2_calib,
        'e_vis2': e_vis2_calib,
        'cp': cp_calib,
        'e_cp': e_cp_calib,
        'u': res_t.u,
        'v': res_t.v,
        'wl': res_t.wl,
        'u1': u1,
        'v1': v1,
        'u2': u2,
        'v2': v2,
        'raw_t': res_t,
        'raw_c': res_c
    }
    return dict2class(cal)
Beispiel #18
0
def average_calib_files(list_nrm, sig_thres=2, display=False):
    """ Average NRM data extracted from multiple calibrator files. Additionaly,
    perform sigma-clipping to reject suspicious dataset.

    Parameters:
    -----------
    `list_nrm` : {list}
        List of classes containing extracted NRM data (see bispect.py) of multiple calibrator files,\n
    `sig_thres` : {float}
        Threshold of the sigma clipping (default: 2-sigma around the median is used),\n    
     """
    nfiles = len(list_nrm)
    l_pa = np.zeros(nfiles)
    cp_vs_file, e_cp_vs_file = [], []
    vis2_vs_file, e_vis2_vs_file = [], []

    # Fill array containing each vis2 and cp across files.
    for n in range(nfiles):
        nrm = list_nrm[n]
        hdu = fits.open(nrm.infos.filename)
        hdr = hdu[0].header
        try:
            # todo: Check parallactic angle param of a real NIRISS header.
            l_pa[n] = hdr['PARANG']
        except KeyError:
            l_pa[n] = 0

        cp = nrm.cp
        e_cp = nrm.e_cp
        vis2 = nrm.vis2
        e_vis2 = nrm.e_vis2

        cp_vs_file.append(cp)
        e_cp_vs_file.append(e_cp)
        vis2_vs_file.append(vis2)
        e_vis2_vs_file.append(e_vis2)

    bl = list_nrm[0].bl

    cp_vs_file = np.array(cp_vs_file)
    e_cp_vs_file = np.array(e_cp_vs_file)
    vis2_vs_file = np.array(vis2_vs_file)
    e_vis2_vs_file = np.array(e_vis2_vs_file)

    zero_uncer = (e_vis2_vs_file == 0)
    e_vis2_vs_file[zero_uncer] = np.max(e_vis2_vs_file)

    cmn_vis2, std_vis2 = wtmn(vis2_vs_file, e_vis2_vs_file)
    cmn_cp, std_cp = wtmn(cp_vs_file, e_cp_vs_file)

    # Apply sigma clipping on the averages
    cmn_vis2_clip, std_vis2_clip = _apply_sig_clip(vis2_vs_file,
                                                   e_vis2_vs_file,
                                                   sig_thres=sig_thres,
                                                   var='V2',
                                                   display=display)
    cmn_cp_clip, std_cp_clip = _apply_sig_clip(cp_vs_file,
                                               e_cp_vs_file,
                                               sig_thres=sig_thres,
                                               ymax=10,
                                               var='CP',
                                               display=display)

    res = {
        'f_v2_clip': np.array(cmn_vis2_clip),
        'f_v2': np.array(cmn_vis2),
        'std_vis2_clip': np.array(std_vis2_clip),
        'std_vis2': np.array(std_vis2),
        'f_cp_clip': np.array(cmn_cp_clip),
        'f_cp': np.array(cmn_cp),
        'std_cp_clip': np.array(std_cp_clip),
        'std_cp': np.array(std_cp),
        'bl': bl,
        'pa': l_pa
    }

    return dict2class(res)
Beispiel #19
0
def compute_index_mask(n_holes, verbose=False):
    """
    This function generates index arrays for an N-hole mask.

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

    `n_holes`: int
       number of holes in the array.

    Returns:
    --------

    `n_baselines`: int
        The number of different baselines (n_holes*(n_holes-1)/2),\n
    `n_bispect`: int
        The number of bispectrum elements (n_holes*(n_holes-1)*(n_holes-2)/6),\n
    `n_cov`: int
        The number of bispectrum covariance
        (n_holes*(n_holes-1)*(n_holes-2)*(n_holes-3)/4),\n
    `h2bl_ix`: numpy.array
        Holes to baselines index,\n
    `bl2h_ix`: numpy.array
                Baselines to holes index,\n
    `bs2bl_ix`: numpy.array
        Bispectrum to baselines index,\n
    `bl2bs_ix`    : numpy.array
        Baselines to bispectrum index,\n
    `bscov2bs_ix`: numpy.array,
        Bispectrum covariance to bispectrum index.

    """
    from munch import munchify as dict2class

    n_baselines = int(n_holes * (n_holes - 1) / 2)
    n_bispect = int(n_holes * (n_holes - 1) * (n_holes - 2) / 6)
    n_cov = int(n_holes * (n_holes - 1) * (n_holes - 2) * (n_holes - 3) / 4)

    # Given a pair of holes i,j h2bl_ix(i,j) gives the number of the baseline
    h2bl_ix = np.zeros([n_holes, n_holes], dtype=int)
    count = 0
    for i in range(n_holes - 1):
        for j in np.arange(i + 1, n_holes):
            h2bl_ix[i, j] = int(count)
            count = count + 1

    if verbose:
        print(h2bl_ix.T)  # transpose to display as IDL

    # Given a baseline, bl2h_ix gives the 2 holes that go to make it up
    bl2h_ix = np.zeros([2, n_baselines], dtype=int)

    count = 0
    for i in range(n_holes - 1):
        for j in np.arange(i + 1, n_holes):
            bl2h_ix[0, count] = int(i)
            bl2h_ix[1, count] = int(j)
            count = count + 1

    if verbose:
        print(bl2h_ix.T)  # transpose to display as IDL

    # Given a point in the bispectrum, bs2bl_ix gives the 3 baselines which
    # make the triangle. bl2bs_ix gives the index of all points in the
    # bispectrum containing a given baseline.

    bs2bl_ix = np.zeros([3, n_bispect], dtype=int)
    temp = np.zeros([n_baselines], dtype=int)  # N_baselines * a count variable

    if verbose:
        print("Indexing bispectrum...")

    bl2bs_ix = np.zeros([n_baselines, n_holes - 2], dtype=int)
    count = 0

    for i in range(n_holes - 2):
        for j in np.arange(i + 1, n_holes - 1):
            for k in np.arange(j + 1, n_holes):
                bs2bl_ix[0, count] = int(h2bl_ix[i, j])
                bs2bl_ix[1, count] = int(h2bl_ix[j, k])
                bs2bl_ix[2, count] = int(h2bl_ix[i, k])
                bl2bs_ix[bs2bl_ix[0, count], temp[bs2bl_ix[0, count]]] = count
                bl2bs_ix[bs2bl_ix[1, count], temp[bs2bl_ix[1, count]]] = count
                bl2bs_ix[bs2bl_ix[2, count], temp[bs2bl_ix[2, count]]] = count
                temp[bs2bl_ix[0, count]] = temp[bs2bl_ix[0, count]] + 1
                temp[bs2bl_ix[1, count]] = temp[bs2bl_ix[1, count]] + 1
                temp[bs2bl_ix[2, count]] = temp[bs2bl_ix[2, count]] + 1
                count += 1

    if verbose:
        print(bl2bs_ix.T)  # transpose to display as IDL
        print("Indexing the bispectral covariance...")

    bscov2bs_ix = np.zeros([2, n_cov], dtype=int)

    count = 0

    for i in range(n_bispect - 1):
        for j in np.arange(i + 1, n_bispect):
            if (
                (bs2bl_ix[0, i] == bs2bl_ix[0, j])
                or (bs2bl_ix[1, i] == bs2bl_ix[0, j])
                or (bs2bl_ix[2, i] == bs2bl_ix[0, j])
                or (bs2bl_ix[0, i] == bs2bl_ix[1, j])
                or (bs2bl_ix[1, i] == bs2bl_ix[1, j])
                or (bs2bl_ix[2, i] == bs2bl_ix[1, j])
                or (bs2bl_ix[0, i] == bs2bl_ix[2, j])
                or (bs2bl_ix[1, i] == bs2bl_ix[2, j])
                or (bs2bl_ix[2, i] == bs2bl_ix[2, j])
            ):
                bscov2bs_ix[0, count] = i
                bscov2bs_ix[1, count] = j
                count += 1

    if verbose:
        print(bscov2bs_ix.T)

    indices_mask = dict2class(
        {
            "n_baselines": n_baselines,
            "n_bispect": n_bispect,
            "n_cov": n_cov,
            "h2bl_ix": h2bl_ix,
            "bl2h_ix": bl2h_ix,
            "bs2bl_ix": bs2bl_ix,
            "bl2bs_ix": bl2bs_ix,
            "bscov2bs_ix": bscov2bs_ix,
            "n_holes": n_holes,
        }
    )
    return indices_mask
Beispiel #20
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)
Beispiel #21
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)
Beispiel #22
0
def populate_NRM(nrm_t, method='med'):
    """ 
    modelled on calib_NRM() but no calibration done because it's for a single object.
    Instead it just populates the appropriate dictionary.  
    So nomenclature looks funny with _5, etc., 
    Funny-looking clumsy straight handoffs to internal variable nmaes,...
    # RAC 3/3021
    If method='multi', preserve observables in each slice (integration) in the output class.
    Multi-slice observable arrays will have read-in shape (len(observable),nslices).
    Errors of multi-slice observables will be all zero (for now)
    Otherwise, take median or mean (assumed if method not 'med' or 'multi').

    """

    visamp_in = nrm_t.fa
    visphi_in = nrm_t.fp
    vis2_in = visamp_in**2

    if method == 'multi':
        vis2 = vis2_in.T
        e_vis2 = np.zeros(vis2.shape)
    elif method == 'med':
        vis2 = np.median(vis2_in, axis=0)  # V2
        e_vis2 = np.std(vis2_in, axis=0)  # Error on V2
    else:
        vis2 = np.mean(vis2_in, axis=0)  # V2
        e_vis2 = np.std(vis2_in, axis=0)  # Error on V2

    if method == 'multi':
        visamp = visamp_in.T
        e_visamp = np.zeros(visamp.shape)
    elif method == 'med':
        visamp = np.median(visamp_in, axis=0)  # Vis. amp
        e_visamp = np.std(visamp_in, axis=0)  # Error on Vis. amp
    else:
        visamp = np.mean(visamp_in, axis=0)  # Vis. amp
        e_visamp = np.std(visamp_in, axis=0)  # Error on Vis. amp

    if method == 'multi':
        visphi = visphi_in.T
        e_visphi = np.zeros(visphi.shape)
    elif method == 'med':
        visphi = np.median(visphi_in, axis=0)  # Vis. phase
        e_visphi = np.std(visphi_in, axis=0)
    else:
        visphi = np.mean(visphi_in, axis=0)  # Vis. phase
        e_visphi = np.std(visphi_in, axis=0)  # Error on Vis. phase

    shift2pi = np.zeros(nrm_t.cp.shape)
    shift2pi[nrm_t.cp >= 6] = 2*np.pi
    shift2pi[nrm_t.cp <= -6] = -2*np.pi

    nrm_t.cp -= shift2pi

    cp_in = nrm_t.cp
    cpamp_in = nrm_t.ca

    if method == 'multi':
        cp = cp_in.T
        e_cp = np.zeros(cp.shape)
    elif method == 'med':
        cp = np.median(cp_in, axis=0)
        e_cp = np.std(cp_in, axis=0)
    else:
        cp = np.mean(cp_in, axis=0)
        e_cp = np.std(cp_in, axis=0)

    if method == 'multi':
        cpamp = cpamp_in.T
        e_cpamp = np.zeros(cpamp.shape)
    elif method == 'med':
        cpamp = np.median(cpamp_in, axis=0)
        e_cpamp = np.std(cpamp_in, axis=0)
    else:
        cpamp = np.mean(cpamp_in, axis=0)
        e_cpamp = np.std(cpamp_in, axis=0)

    output = {'vis2': vis2,
              'e_vis2': e_vis2,
              'visamp': visamp,
              'e_visamp': e_visamp,
              'visphi': visphi,
              'e_visphi': e_visphi,
              'cp': cp,
              'e_cp': e_cp,
              'cpamp': cpamp,
              'e_cpamp': e_cpamp
              }

    return dict2class(output)