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)
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))
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)
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)
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)
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
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
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))
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
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
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)
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)
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)
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)
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)
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)
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
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)
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)
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)