def _sample_flux_snr(distances, fwhm, plsc, n_injections, flux_min, flux_max, nproc=10, random_seed=42, wavelengths=None, spectrum=None, mode='median', scaling='temp-standard', svd_mode='randsvd'): """ Sensible flux intervals depend on a combination of factors, # of frames, range of rotation, correlation, glare intensity. """ if GARRAY.ndim == 3: frsize = int(GARRAY.shape[1]) elif GARRAY.ndim == 4: frsize = int(GARRAY.shape[2]) ninj = n_injections random_state = np.random.RandomState(random_seed) flux_dist_theta_all = list() snrs_list = list() fluxes_list = list() n_ks = 3 for i, d in enumerate(distances): yy, xx = get_annulus_segments((frsize, frsize), d, 1, 1)[0] num_patches = yy.shape[0] fluxes_dist = random_state.uniform(flux_min[i], flux_max[i], size=ninj) inds_inj = random_state.randint(0, num_patches, size=ninj) for j in range(ninj): injx = xx[inds_inj[j]] injy = yy[inds_inj[j]] injx -= frame_center(GARRAY[0])[1] injy -= frame_center(GARRAY[0])[0] dist = np.sqrt(injx**2 + injy**2) theta = np.mod(np.arctan2(injy, injx) / np.pi * 180, 360) flux_dist_theta_all.append((fluxes_dist[j], dist, theta)) # multiprocessing (pool) for each distance res = pool_map(nproc, _get_adi_snrs, GARRPSF, GARRPA, fwhm, plsc, iterable(flux_dist_theta_all), wavelengths, spectrum, mode, n_ks, scaling, svd_mode) for i in range(len(distances)): flux_dist = [] snr_dist = [] for j in range(ninj): flux_dist.append(res[j + (ninj * i)][0]) snr_dist.append(res[j + (ninj * i)][1]) fluxes_list.append(flux_dist) snrs_list.append(snr_dist) return fluxes_list, snrs_list
def create_synt_cube(cube, psf, ang, plsc, dist=None, theta=None, flux=None, random_seed=42, verbose=False): """ """ centy_fr, centx_fr = frame_center(cube[0]) random_state = np.random.RandomState(random_seed) if theta is None: theta = random_state.randint(0, 360) posy = dist * np.sin(np.deg2rad(theta)) + centy_fr posx = dist * np.cos(np.deg2rad(theta)) + centx_fr if verbose: print('Theta:', theta) print('Flux_inj:', flux) cubefc = cube_inject_companions(cube, psf, ang, flevel=flux, plsc=plsc, rad_dists=[dist], n_branches=1, theta=theta, verbose=verbose) return cubefc, posx, posy
def prepare_patches(cube, angle_list, xy, fwhm, patch_size_px, delta_rot=0.5, normalization='slice', imlib='opencv', interpolation='bilinear', debug=False): """ Prepare patches for SODINN-PW. """ centy_fr, centx_fr = frame_center(cube[0]) angle_list = check_pa_vector(angle_list) xy_dist = dist(centy_fr, centx_fr, xy[1], xy[0]) res = _pairwise_diff_residuals(cube, angle_list, ann_center=xy_dist, fwhm=fwhm, delta_rot=delta_rot, debug=False) res_der = cube_derotate(res, angle_list, imlib=imlib, interpolation=interpolation) res_der_crop = cube_crop_frames(res_der, patch_size_px, xy=xy, verbose=False, force=True) patches = normalize_01_pw(res_der_crop, normalization) if debug: print('dist : {}'.format(xy_dist)) plot_frames( tuple(patches), axis=False, colorbar=False, ) return patches
def get_indices_annulus(shape, inrad, outrad, mask=None, maskrad=None, verbose=False): """ mask is a list of tuples X,Y # TODO: documentation """ framemp = np.zeros(shape) if mask is not None: if not isinstance(mask, list): raise TypeError('Mask should be a list of tuples') if maskrad is None: raise ValueError('Fwhm not given') for xy in mask: # patch_size/2 diameter aperture cir = circle(xy[1], xy[0], maskrad, shape) framemp[cir] = 1 annulus_width = outrad - inrad cy, cx = frame_center(framemp) yy, xx = np.mgrid[:framemp.shape[0], :framemp.shape[1]] circ = np.sqrt((xx - cx)**2 + (yy - cy)**2) donut_mask = (circ <= (inrad + annulus_width)) & (circ >= inrad) y, x = np.where(donut_mask) if mask is not None: npix = y.shape[0] ymask, xmask = np.where(framemp) # masked pixels where == 1 inds = [] for i, tup in enumerate(zip(y, x)): if tup in zip(ymask, xmask): inds.append(i) y = np.delete(y, inds) x = np.delete(x, inds) if verbose: print(y.shape[0], 'pixels in annulus') return y, x
def test_frame_center(): frames = 39 nlambda = 2 res44 = (2.0, 2.0) # replaced (1.5, 1.5) considering new convention res55 = (2.0, 2.0) # 2D assert frame_center(np.zeros((4, 4))) == res44 assert frame_center(np.zeros((5, 5))) == res55 # 3D assert frame_center(np.zeros((frames, 4, 4))) == res44 assert frame_center(np.zeros((frames, 5, 5))) == res55 # 4D assert frame_center(np.zeros((nlambda, frames, 4, 4))) == res44 assert frame_center(np.zeros((nlambda, frames, 5, 5))) == res55
def make_mlar_samples_ann_signal(input_array, angle_list, psf, n_samples, cevr_thresh, n_ks, force_klen, inrad, outrad, patch_size, flux_low, flux_high, plsc=0.01, normalize='slice', nproc=1, nproc2=1, interp='bilinear', lr_mode='eigen', mode='mlar', kss_window=None, tss_window=None, random_seed=42, verbose=False): """ n_samples : For ONEs, half_n_samples SVDs mask is a list of tuples X,Y inputarr is a 3d array or list of 3d arrays orig_zero_patches : percentage of original zero patches """ dist_flux_p1 = flux_low dist_flux_p2 = flux_high collapse_func = np.mean scaling = None # 'temp-mean' random_state = np.random.RandomState(random_seed) # making ones, injecting companions. The other half of n_samples if verbose: print("Creating the ONEs samples") frsize = int(input_array.shape[1]) cube = input_array # if frsize > outrad + outrad + patch_size + 2: # frsize = int(outrad + outrad + patch_size + 2) # cube = cube_crop_frames(input_array, frsize, force=True, # verbose=False) width = outrad - inrad yy, xx = get_annulus_segments((frsize, frsize), inrad, width, 1)[0] num_patches = yy.shape[0] k_list = get_cumexpvar(cube, 'annular', inrad, outrad, patch_size, k_list=None, cevr_thresh=cevr_thresh, n_ks=n_ks, match_len=force_klen, verbose=False) n_req_inject = n_samples if mode == 'mlar': # 4D: n_samples/2, n_k_list, patch_size, patch_size X_ones_array = np.empty((n_req_inject, len(k_list), patch_size, patch_size)) elif mode == 'tmlar': nfr = cube.shape[0] X_ones_array = np.empty((n_req_inject, nfr, patch_size, patch_size)) elif mode == 'tmlar4d': nfr = cube.shape[0] X_ones_array = np.empty((n_req_inject, nfr, len(k_list), patch_size, patch_size)) if verbose: print("{} injections".format(n_req_inject)) if not dist_flux_p2 > dist_flux_p1: err_msg = 'dist_flux_p2 must be larger than dist_flux_p1' raise ValueError(err_msg) fluxes = random_state.uniform(dist_flux_p1, dist_flux_p2, size=n_req_inject) fluxes = np.sort(fluxes) inds_inj = random_state.randint(0, num_patches, size=n_req_inject) dists = [] thetas = [] for m in range(n_req_inject): injx = xx[inds_inj[m]] injy = yy[inds_inj[m]] injx -= frame_center(cube[0])[1] injy -= frame_center(cube[0])[0] dist = np.sqrt(injx**2+injy**2) theta = np.mod(np.arctan2(injy, injx) / np.pi * 180, 360) dists.append(dist) thetas.append(theta) if not nproc: nproc = int((cpu_count()/4)) if nproc == 1: for m in range(n_req_inject): cufc, cox, coy = create_synt_cube(cube, psf, angle_list, plsc, theta=thetas[m], flux=fluxes[m], dist=dists[m], verbose=False) cox = int(np.round(cox)) coy = int(np.round(coy)) cube_residuals = svd_decomp(cufc, angle_list, patch_size, inrad, outrad, scaling, k_list, collapse_func, neg_ang=False, lr_mode=lr_mode, nproc=nproc2, interp=interp, mode=mode) # one patch residuals per injection X_ones_array[m] = cube_crop_frames(np.asarray(cube_residuals), patch_size, xy=(cox, coy), verbose=False, force=True) elif nproc > 1: if lr_mode in ['cupy', 'randcupy', 'eigencupy']: raise RuntimeError('CUPY does not play well with multiproc') if get_start_method() == 'fork' and lr_mode in ['pytorch', 'eigenpytorch', 'randpytorch']: raise RuntimeError("Cannot use pytorch and multiprocessing " "outside main (i.e. from a jupyter cell). " "See: http://pytorch.org/docs/0.3.1/notes/" "multiprocessing.html.") flux_dist_theta = zip(fluxes, dists, thetas) res = pool_map(nproc, _inject_FC, cube, psf, angle_list, plsc, inrad, outrad, iterable(flux_dist_theta), k_list, scaling, collapse_func, patch_size, lr_mode, interp, mode) for m in range(n_req_inject): X_ones_array[m] = res[m] # Moving-subsampling move_subsampling = 'median' if mode == 'mlar' and kss_window is not None: X_ones_array = cube_move_subsample(X_ones_array, kss_window, axis=1, mode=move_subsampling) elif mode == 'tmlar' and tss_window is not None: X_ones_array = cube_move_subsample(X_ones_array, kss_window, axis=1, mode=move_subsampling) elif mode == 'tmlar4d': if tss_window is not None: X_ones_array = cube_move_subsample(X_ones_array, tss_window, axis=1, mode=move_subsampling) if kss_window is not None: X_ones_array = cube_move_subsample(X_ones_array, kss_window, axis=2, mode=move_subsampling) if normalize is not None: if mode == 'tmlar4d': for i in range(X_ones_array.shape[0]): X_ones_array[i] = normalize_01(X_ones_array[i], normalize) else: X_ones_array = normalize_01(X_ones_array, normalize) return X_ones_array.astype('float32')
def noise_per_annulus(array, separation, fwhm, init_rad=None, wedge=(0, 360), verbose=False, debug=False, mask=None): """ Measures the noise as the standard deviation of apertures defined in each annulus with a given separation. Parameters ---------- array : array_like Input frame. separation : float Separation in pixels of the centers of the annuli measured from the center of the frame. fwhm : float FWHM in pixels. init_rad : float Initial radial distance to be used. If None then the init_rad = FWHM. wedge : tuple of floats, optional Initial and Final angles for using a wedge. For example (-90,90) only considers the right side of an image. Be careful when using small wedges, this leads to computing a standard deviation of very small samples (<10 values). verbose : bool, optional If True prints information. debug : bool, optional If True plots the positioning of the apertures. Returns ------- noise : array_like Vector with the noise value per annulus. vector_radd : array_like Vector with the radial distances values. """ # dprint(fwhm) def find_coords(rad, sep, init_angle, fin_angle): angular_range = fin_angle - init_angle npoints = (np.deg2rad(angular_range) * rad) / sep #(2*np.pi*rad)/sep ang_step = angular_range / npoints #360/npoints x = [] y = [] for i in range(int(npoints)): newx = rad * np.cos(np.deg2rad(ang_step * i + init_angle)) newy = rad * np.sin(np.deg2rad(ang_step * i + init_angle)) x.append(newx) y.append(newy) return np.array(y), np.array(x) ### if array.ndim != 2: raise TypeError('Input array is not a frame or 2d array') if not isinstance(wedge, tuple): raise TypeError('Wedge must be a tuple with the initial and final ' 'angles') init_angle, fin_angle = wedge centery, centerx = frame_center(array) n_annuli = int(np.floor((centery) / separation)) - 1 noise = [] vector_radd = [] if verbose: print('{} annuli'.format(n_annuli)) if init_rad is None: init_rad = fwhm if debug: _, ax = plt.subplots(figsize=(6, 6)) ax.imshow(array, origin='lower', interpolation='nearest', alpha=0.5, cmap='gray') for i in range(n_annuli): y = centery + init_rad + separation * i rad = dist(centery, centerx, y, centerx) yy, xx = find_coords(rad, fwhm, init_angle, fin_angle) yy += centery xx += centerx apertures = photutils.CircularAperture((xx, yy), fwhm / 2) mask = np.bool_(mask) # quick2D(mask) if mask: fluxes = photutils.aperture_photometry(array, apertures, mask=mask) else: fluxes = photutils.aperture_photometry(array, apertures) fluxes = np.array(fluxes['aperture_sum']) noise_ann = np.std(fluxes) noise.append(noise_ann) vector_radd.append(rad) # if debug: # if i == 1: # plt.plot(fluxes) # plt.figure() # plt.hist(fluxes, bins=50) if debug: for j in range(xx.shape[0]): # Circle takes coordinates as (X,Y) aper = plt.Circle((xx[j], yy[j]), radius=fwhm / 2, color='r', fill=False, alpha=0.8) ax.add_patch(aper) cent = plt.Circle((xx[j], yy[j]), radius=0.8, color='r', fill=True, alpha=0.5) ax.add_patch(cent) if verbose: print('Radius(px) = {}, Noise = {:.3f} '.format(rad, noise_ann)) return np.array(noise), np.array(vector_radd)
def __init__(self, cube, psf, distances, angles, fwhm, plsc, wavelengths=None, spectrum=None, n_injections=30, algo='pca', min_snr=1, max_snr=3, inter_extrap=False, svd_mode='randsvd', inter_extrap_dist=None, random_seed=42, n_proc=2): """ Initialization of the flux estimator object. """ global GARRAY global GARRPSF global GARRWL global GARRPA GARRAY = cube GARRPSF = psf GARRPA = angles GARRWL = wavelengths check_array(cube, dim=(3, 4), msg='cube') check_array(psf, dim=(2, 3), msg='psf') check_array(angles, dim=1, msg='angles') check_array(distances, dim=1, msg='distances') if isinstance(min_snr, (tuple, list)): if not len(min_snr) == len(distances): raise ValueError('`min_snr` length does not match `distances`') elif isinstance(min_snr, (int, float)): min_snr = [min_snr] * len(distances) else: raise TypeError('`min_snr` must be a float/int or a list/tuple') if isinstance(max_snr, (tuple, list)): if not len(max_snr) == len(distances): raise ValueError('`max_snr` length does not match `distances`') elif isinstance(max_snr, (int, float)): max_snr = [max_snr] * len(distances) else: raise TypeError('`max_snr` must be a float/int or a list/tuple') if cube.ndim == 4: if wavelengths is None: raise ValueError('`wavelengths` must be provided when `cube` ' 'is a 4d array') if spectrum is None: raise ValueError( '`spectrum` must be provided when `cube` is a ' '4d array') check_array(wavelengths, dim=1, msg='wavelengths') check_array(spectrum, dim=1, msg='spectrum') cy, cx = frame_center(cube) maxd = cy - 5 * fwhm if not max(distances) <= maxd: raise ValueError('`distances` contains a value that is too ' 'high wrt the frame size. Values must be ' 'smaller than {:.2f}'.format(maxd)) self.starttime = time_ini() self.min_fluxes = None self.max_fluxes = None self.radprof = None self.sampled_fluxes = None self.sampled_snrs = None self.estimated_fluxes_low = None self.estimated_fluxes_high = None self.distances = distances self.angles = angles self.fwhm = fwhm self.plsc = plsc if cube.ndim == 4: self.scaling = 'temp-standard' elif cube.ndim == 3: self.scaling = None self.wavelengths = wavelengths self.spectrum = spectrum self.n_injections = n_injections self.algo = algo self.svd_mode = svd_mode self.min_snr = min_snr self.max_snr = max_snr self.random_seed = random_seed self.n_proc = n_proc self.inter_extrap = inter_extrap self.inter_extrap_dist = inter_extrap_dist self.n_dist = range(len(self.distances)) self.fluxes_list = list() self.snrs_list = list()
def _get_adi_snrs(psf, angle_list, fwhm, plsc, flux_dist_theta_all, wavelengths=None, spectrum=None, mode='pca', n_ks=3, scaling='temp-standard', svd_mode='randsvd', debug=False): """ Get the mean S/N (at 3 equidistant positions) for a given flux and distance, on a residual frame. """ theta = flux_dist_theta_all[2] flux = flux_dist_theta_all[0] dist = flux_dist_theta_all[1] if GARRAY.ndim == 3: spectrum = 1 elif GARRAY.ndim == 4: # grey spectrum (same flux in all wls) if spectrum is None: spectrum = np.ones((GARRAY.shape[0])) snrs = [] # 3 equidistant azimuthal positions, 1 or several K values for ang in [theta, theta + 120, theta + 240]: cube_fc, pos = cube_inject_companions(GARRAY, psf, angle_list, flevel=flux * spectrum, plsc=plsc, rad_dists=[dist], theta=ang, verbose=False, full_output=True) posy, posx = pos[0] fr_temp = _compute_residual_frame(cube_fc, angle_list, dist, fwhm, wavelengths, mode, n_ks, svd_mode, scaling, 'median', 'opencv', 'bilinear') # handling the case of mode='median' if isinstance(fr_temp, np.ndarray): fr_temp = [fr_temp] snrs_ks = [] for i in range(len(fr_temp)): res = snr(fr_temp[i], source_xy=(posx, posy), fwhm=fwhm, exclude_negative_lobes=True) snrs_ks.append(res) maxsnr_ks = max(snrs_ks) if np.isinf(maxsnr_ks) or np.isnan(maxsnr_ks) or maxsnr_ks < 0: maxsnr_ks = 0.01 snrs.append(maxsnr_ks) if debug: print(' ') cy, cx = frame_center(GARRAY[0]) label = 'Flux: {:.1f}, Max S/N: {:.2f}'.format(flux, maxsnr_ks) hp.plot_frames(tuple(np.array(fr_temp)), axis=False, horsp=0.05, colorbar=False, circle=((posx, posy), (cx, cy)), circle_radius=(5, dist), label=label, dpi=60) # max of mean S/N at 3 equidistant positions snr_value = np.max(snrs) return flux, snr_value
def find_AGPM_or_star(self, file_list, rel_AGPM_pos_xy=(50.5, 6.5), size=101, verbose=True, debug=False): """ added by Iain to prevent dust grains being picked up as the AGPM This method will find the location of the AGPM or star (even when sky frames are mixed with science frames), by using the known relative distance of the AGPM from the frame center in all VLT/NaCO datasets. It then creates a subset square image around the expected location and applies a low pass filter + max search method and returns the (y,x) location of the AGPM/star Parameters ---------- file_list : list of str List containing all science cube names rel_AGPM_pos_xy : tuple, float relative location of the AGPM from the frame center in pixels, should be left unchanged. This is used to calculate how many pixels in x and y the AGPM is from the center and can be applied to almost all datasets with VLT/NaCO as the AGPM is always in the same approximate position size : int pixel dimensions of the square to sample for the AGPM/star (ie size = 100 is 100 x 100 pixels) verbose : bool If True extra messages are shown debug : bool, False by default Enters pdb once the location has been found Returns ---------- [ycom, xcom] : location of AGPM or star """ sci_cube = open_fits(self.inpath + file_list[0]) # opens first sci/sky cube nz, ny, nx = sci_cube.shape # gets size of it. science and sky cubes have same shape. assumes all cubes are the same ny and nx (they should be!) cy, cx = frame_center(sci_cube, verbose=verbose) # find central pixel coordinates # then the position will be that plus the relative shift in y and x rel_shift_x = rel_AGPM_pos_xy[ 0] # 50.5 is pixels from frame center to AGPM in x in an example data set, thus providing the relative shift rel_shift_y = rel_AGPM_pos_xy[ 1] # 6.5 is pixels from frame center to AGPM in y in an example data set, thus providing the relative shift # the center of the square to apply the low pass filter to - is the approximate position of the AGPM/star based on previous observations y_tmp = cy + rel_shift_y x_tmp = cx + rel_shift_x median_all_cubes = np.zeros([len(file_list), ny, nx]) # makes empty array for sc, fits_name in enumerate(file_list): # loops over all images tmp = open_fits(self.inpath + fits_name, verbose=debug) # opens the cube median_all_cubes[sc] = tmp[ -1] # takes the last entry (the median) and adds it to the empty array median_frame = np.median(median_all_cubes, axis=0) # median of all median frames # define a square of 100 x 100 with the center being the approximate AGPM/star position median_frame, cornery, cornerx = get_square(median_frame, size=size, y=y_tmp, x=x_tmp, position=True, verbose=True) # apply low pass filter median_frame = frame_filter_lowpass(median_frame, median_size=7, mode='median') median_frame = frame_filter_lowpass(median_frame, mode='gauss', fwhm_size=5) # find coordiates of max flux in the square ycom_tmp, xcom_tmp = np.unravel_index(np.argmax(median_frame), median_frame.shape) # AGPM/star is the bottom-left corner coordinates plus the location of the max in the square ycom = cornery + ycom_tmp xcom = cornerx + xcom_tmp if verbose: print('The location of the AGPM/star is', 'ycom =', ycom, 'xcom =', xcom) if debug: pdb.set_trace() return [ycom, xcom]
def do_negfc(self, do_firstguess=True, guess_xy=None, mcmc_negfc=True, inject_neg=True, ncomp=1, algo='pca_annular', nwalkers_ini=120, niteration_min=25, niteration_limit=10000, delta_rot=(0.5, 3), weights=False, coronagraph=False, overwrite=True, save_plot=True, verbose=True): """ Module for estimating the location and flux of a planet. A sub-folder 'negfc' is created for storing all output files. Using a first guess from the (x,y) coordinates in pixels for the planet, we can estimate a preliminary guess for the position and flux for each planet using a Nelder-Mead minimization. Saves the r, theta and flux If using MCMC, runs an affine invariant MCMC sampling algorithm in order to determine the position and the flux of the planet using the 'Negative Fake Companion' technique. The result of this procedure is a chain with the samples from the posterior distributions of each of the 3 parameters. Finally, we can inject a negative flux of the planet found in MCMC into the original dataset and apply post processing to determine the residuals in the data. Parameters: *********** do_firstguess : bool whether to determine a first guess for the position and the flux of a planet (not NEGFC) guess_xy : tuple if do_firstguess=True, this estimate of the source (x,y) location to be provided to firstguess(). Note Python's zero-based indexing mcmc_negfc : bool whether to run MCMC NEGFC sampling (computationally intensive) inject_neg : bool whether to inject negative flux of the planet and post process the data without signal from the planet ncomp : int, default 1 number of prinicple components to subtract algo : 'pca_annulus', 'pca_annular', 'pca'. default 'pca_annular' select which routine to be used to model and subtract the stellar PSF nwalkers_ini : int, default 120 for MCMC, the number of Goodman & Weare 'walkers' niteration_min : int, default 25 for MCMC, the simulation will run at least this number of steps per walker niteration_limit : int, default 10000 for MCMC, stops simulation if this many steps run without having reached the convergence criterion delta_rot : tuple same as for postprocessing module. Threshold rotation angle for PCA annular weights : bool should only be used on unsaturated datasets, where the flux of the star can be measured in each image of the cube. Applies a correction to each frame to account for variability of the adaptive optics, and hence flux from the star (reference Christiaens et al. 2021 2021MNRAS.502.6117C) coronagraph : bool for MCMC and injecting a negative companion, True if the observation utilised a coronagraph (AGPM). The known radial transmission of the NACO+AGPM coronagraph will be used overwrite : bool, default True whether to run a module and overwrite results if they already exist save_plot : bool, default True for firstguess the chi2 vs. flux plot is saved. For MCMC results are pickled and saved to the outpath along with corner plots verbose : bool prints more output and interediate files when True """ print("======= Starting NEGFC....=======") if guess_xy is None and do_firstguess is True: raise ValueError("Enter an approximate location into guess_xy!") if weights is True and coronagraph is True: raise ValueError("Dataset cannot be both non-coronagraphic and coronagraphic!!") outpath_sub = self.outpath + "negfc/" if not isdir(outpath_sub): os.system("mkdir " + outpath_sub) if verbose: print('Input path is {}'.format(self.inpath)) print('Output path is {}'.format(outpath_sub)) source = self.dataset_dict['source'] tn_shift = 0.568 # Launhardt et al. 2020, true North offset for NACO ADI_cube_name = '{}_master_cube.fits' # template name for input master cube derot_ang_name = 'derot_angles.fits' # template name for corresponding input derotation angles psfn_name = "master_unsat_psf_norm.fits" # normalised PSF ADI_cube = open_fits(self.inpath + ADI_cube_name.format(source), verbose=verbose) derot_angles = open_fits(self.inpath + derot_ang_name, verbose=verbose) + tn_shift psfn = open_fits(self.inpath + psfn_name, verbose=verbose) ref_cube = None if algo == 'pca_annular': label_pca = 'pca_annular' algo = pca_annular elif algo == 'pca_annulus': label_pca = 'pca_annulus' algo = pca_annulus elif algo == 'pca': label_pca = 'pca' algo = pca else: raise ValueError("Invalid algorithm. Select either pca_annular, pca_annulus or pca!") opt_npc = ncomp ap_rad = 1 * self.fwhm f_range = np.geomspace(0.1, 201, 40) asize = 3 * self.fwhm if weights: nfr = ADI_cube.shape[0] # number of frames star_flux = np.zeros([nfr]) # for storing the star flux found in each frame crop_sz_tmp = min(int(10 * self.fwhm), ADI_cube.shape[1] - 2) # crop around star, either 10*FWHM or size - 2 if crop_sz_tmp % 2 == 0: # if it's not even, crop crop_sz_tmp -= 1 for ii in range(nfr): _, star_flux[ii], _ = normalize_psf(ADI_cube[ii], fwhm=self.fwhm, size=crop_sz_tmp, full_output=True) # get star flux in 1*FWHM weights = star_flux / np.median(star_flux) star_flux = np.median(star_flux) # for use after MCMC when turning the chain into contrast else: weights = None flux_psf_name = "master_unsat-stellarpsf_fluxes.fits" # flux in a FWHM aperture found in calibration star_flux = open_fits(self.inpath + flux_psf_name, verbose=verbose)[1] # scaled fwhm flux if coronagraph: # radial transmission of the coronagraph, 2 columns (pixels from centre, off-axis transmission) # data provided by Valentin Christiaens. First entry in both columns was not zero, but VIP adds it in anyway transmission = np.array([[0, 3.5894626e-10, 5.0611424e-01, 1.0122285e+00, 1.5183427e+00, 2.0244570e+00, 2.5305712e+00, 3.0366855e+00, 3.5427995e+00, 4.0489140e+00, 4.5550284e+00, 5.0611424e+00, 5.5672565e+00, 6.0733705e+00, 6.5794849e+00, 7.0855989e+00, 7.5917134e+00, 8.6039419e+00, 9.1100569e+00, 9.6161709e+00, 1.0628398e+01, 1.1134513e+01, 1.2146742e+01, 1.2652856e+01, 1.3665085e+01, 1.4677314e+01, 1.6195656e+01, 1.7207884e+01, 1.8220114e+01, 1.9738455e+01, 2.1256796e+01, 2.2775141e+01, 2.4293484e+01, 2.6317940e+01, 2.8342396e+01, 3.0366854e+01, 3.2897423e+01, 3.4921883e+01, 3.7452454e+01, 4.0489140e+01, 4.3525822e+01, 4.6562508e+01, 5.0105309e+01, 5.4154221e+01, 5.7697018e+01, 6.2252052e+01, 6.6807076e+01, 7.1868225e+01], [0, 6.7836474e-05, 3.3822558e-03, 1.7766271e-02, 5.2646037e-02, 1.1413762e-01, 1.9890217e-01, 2.9460809e-01, 3.8605216e-01, 4.6217495e-01, 5.1963091e-01, 5.6185508e-01, 5.9548348e-01, 6.2670821e-01, 6.5912777e-01, 6.9335037e-01, 7.2783405e-01, 7.8866738e-01, 8.1227022e-01, 8.3128709e-01, 8.5912752e-01, 8.6968899e-01, 8.8677746e-01, 8.9409947e-01, 9.0848678e-01, 9.2426234e-01, 9.4704604e-01, 9.5787460e-01, 9.6538281e-01, 9.7379774e-01, 9.8088801e-01, 9.8751044e-01, 9.9255627e-01, 9.9640906e-01, 9.9917024e-01, 1.0009050e+00, 1.0021056e+00, 1.0026742e+00, 1.0027454e+00, 1.0027291e+00, 1.0023015e+00, 1.0016677e+00, 1.0009446e+00, 1.0000550e+00, 9.9953103e-01, 9.9917012e-01, 9.9915260e-01, 9.9922234e-01]]) else: transmission = None if (not isfile(outpath_sub + label_pca + "_npc{}_simplex_results.fits".format(opt_npc)) or overwrite) and do_firstguess: # find r, theta based on the provided estimate location cy, cx = frame_center(ADI_cube[0]) dy_pl = guess_xy[0][1] - cy dx_pl = guess_xy[0][0] - cx r_pl = np.sqrt(np.power(dx_pl, 2) + np.power(dy_pl, 2)) # pixel distance to the guess location theta_pl = (np.rad2deg(np.arctan2(dy_pl, dx_pl))) % 360 # theta (angle) to the guess location print("Estimated (r, theta) before first guess = ({:.1f},{:.1f})".format(r_pl, theta_pl)) ini_state = firstguess(ADI_cube, derot_angles, psfn, ncomp=opt_npc, plsc=self.pixel_scale, planets_xy_coord=guess_xy, fwhm=self.fwhm, annulus_width=12, aperture_radius=ap_rad, cube_ref=ref_cube, svd_mode='lapack', scaling=None, fmerit='stddev', imlib='opencv', interpolation='lanczos4', collapse='median', p_ini=None, transmission=transmission, weights=weights, algo=algo, f_range=f_range, simplex=True, simplex_options=None, plot=save_plot, verbose=verbose, save=save_plot) # when p_ini is set to None, it gets the value of planets_xy_coord ini_state = np.array([ini_state[0][0], ini_state[1][0], ini_state[2][0]]) write_fits(outpath_sub + label_pca + "_npc{}_simplex_results.fits".format(opt_npc), ini_state, verbose=verbose) # saves r, theta and flux. No print statement as firstguess() does that for us if (not isfile(outpath_sub + "MCMC_results") or overwrite) and mcmc_negfc: ini_state = open_fits(outpath_sub + label_pca + "_npc{}_simplex_results.fits".format(opt_npc), verbose=verbose) delta_theta_min = np.rad2deg(np.arctan(4./ r_pl)) # at least the angle corresponding to 2 azimuthal pixels delta_theta = max(delta_theta_min, 5.) bounds = [(max(r_pl - asize / 2., 1), r_pl + asize / 2.), # radius (theta_pl - delta_theta, theta_pl + delta_theta), # angle (0, 5 * abs(ini_state[2]))] if ini_state[0] < bounds[0][0] or ini_state[0] > bounds[0][1] or ini_state[1] < bounds[1][0] or \ ini_state[1] > bounds[1][1] or ini_state[2] < bounds[2][0] or ini_state[2] > bounds[2][1]: print("!!! WARNING: simplex results not in original bounds - NEGFC simplex MIGHT HAVE FAILED !!!") ini_state = np.array([r_pl, theta_pl, abs(ini_state[2])]) if verbose is True: verbosity = 2 print('MCMC NEGFC sampling is about to begin...') else: verbosity = 0 final_chain = mcmc_negfc_sampling(ADI_cube, derot_angles, psfn, ncomp=opt_npc, plsc=self.pixel_scale, initial_state=ini_state, fwhm=self.fwhm, weights=weights, annulus_width=12, aperture_radius=ap_rad, cube_ref=ref_cube, svd_mode='lapack', scaling=None, fmerit='stddev', imlib='opencv', interpolation='lanczos4', transmission=transmission, collapse='median', nwalkers=nwalkers_ini, bounds=bounds, a=2.0, ac_c=50, mu_sigma=(0, 1), burnin=0.3, rhat_threshold=1.01, rhat_count_threshold=1, conv_test='ac', # use autocorrelation 'ac' to ensure sufficient sampling. sample around # the area of best likelihood to make distribution niteration_min=niteration_min, niteration_limit=niteration_limit, niteration_supp=0, check_maxgap=50, nproc=self.nproc, algo=algo, output_dir=outpath_sub, output_file="MCMC_results", display=False, verbosity=verbosity, save=save_plot) final_chain[:, :, 2] = final_chain[:, :, 2] / star_flux # dividing by the star flux converts to a contrast show_walk_plot(final_chain, save=save_plot, output_dir=outpath_sub) show_corner_plot(final_chain, burnin=0.5, save=save_plot, output_dir=outpath_sub) # determine the highly probable value for each model parameter and the 1-sigma confidence interval isamples_flat = final_chain[:,int(final_chain.shape[1]//(1/0.3)):,:].reshape((-1,3)) # 0.3 is the burnin vals, err = confidence(isamples_flat, cfd=68.27, bins=100, gaussian_fit=False, weights=weights, verbose=verbose, save=True, output_dir=outpath_sub, filename='confidence.txt', plsc=self.pixel_scale) labels = ['r', 'theta', 'f'] mcmc_res = np.zeros([3,3]) # pull the values and confidence interval out for saving for i in range(3): mcmc_res[i,0] = vals[labels[i]] mcmc_res[i,1] = err[labels[i]][0] mcmc_res[i,2] = err[labels[i]][1] write_fits(outpath_sub + 'mcmc_results.fits', mcmc_res) # now gaussian fit gvals, gerr = confidence(isamples_flat, cfd=68.27, bins=100, gaussian_fit=True, weights=weights, verbose=verbose, save=True, output_dir=outpath_sub,filename='confidence_gauss.txt', plsc=self.pixel_scale) mcmc_res = np.zeros([3,2]) for i in range(3): mcmc_res[i,0] = gvals[i] mcmc_res[i,1] = gerr[i] write_fits(outpath_sub + 'mcmc_results_gauss.fits', mcmc_res) if inject_neg: pca_res = np.zeros([ADI_cube.shape[1], ADI_cube.shape[2]]) pca_res_emp = pca_res.copy() planet_params = open_fits(outpath_sub+'mcmc_results.fits') flux_psf_name = "master_unsat-stellarpsf_fluxes.fits" star_flux = open_fits(self.inpath + flux_psf_name, verbose=verbose)[1] ADI_cube_emp = cube_inject_companions(ADI_cube, psfn, derot_angles, flevel=-planet_params[2, 0] * star_flux, plsc=self.pixel_scale, rad_dists=[planet_params[0, 0]], n_branches=1, theta=planet_params[1, 0], imlib='opencv', interpolation='lanczos4', verbose=verbose, transmission=transmission) write_fits(outpath_sub+'ADI_cube_empty.fits', ADI_cube_emp) # the cube with the negative flux injected if algo == pca_annular: radius_int = int(np.floor(r_pl-asize/2)) # asize is 3 * FWHM, rounds down. To skip the inner region # crop the cube to just larger than the annulus to improve the speed of PCA crop_sz = int(2*np.ceil(r_pl+asize+1)) # rounds up if not crop_sz % 2: # make sure the crop is odd crop_sz += 1 if crop_sz < ADI_cube.shape[1] and crop_sz < ADI_cube.shape[2]: # crop if crop_sz is smaller than cube pad = int((ADI_cube.shape[1]-crop_sz)/2) crop_cube = cube_crop_frames(ADI_cube, crop_sz, verbose=verbose) else: crop_cube = ADI_cube # dont crop if the cube is already smaller pca_res_tmp = pca_annular(crop_cube, derot_angles, cube_ref=ref_cube, radius_int=radius_int, fwhm=self.fwhm, asize=asize, delta_rot=delta_rot, ncomp=opt_npc, svd_mode='lapack', scaling=None, imlib='opencv', interpolation='lanczos4', nproc=self.nproc, min_frames_lib=max(opt_npc, 10), verbose=verbose, full_output=False) pca_res = np.pad(pca_res_tmp, pad, mode='constant', constant_values=0) write_fits(outpath_sub + 'pca_annular_res_npc{}.fits'.format(opt_npc), pca_res) # emp if crop_sz < ADI_cube_emp.shape[1] and crop_sz < ADI_cube_emp.shape[2]: pad = int((ADI_cube_emp.shape[1]-crop_sz)/2) crop_cube = cube_crop_frames(ADI_cube_emp, crop_sz, verbose=verbose) else: crop_cube = ADI_cube_emp del ADI_cube_emp del ADI_cube pca_res_tmp = pca_annular(crop_cube, derot_angles, cube_ref=ref_cube, radius_int=radius_int, fwhm=self.fwhm, asize=asize, delta_rot=delta_rot, ncomp=opt_npc, svd_mode='lapack', scaling=None, imlib='opencv', interpolation='lanczos4', nproc=self.nproc, min_frames_lib=max(opt_npc, 10), verbose=verbose, full_output=False) # pad again now pca_res_emp = np.pad(pca_res_tmp, pad, mode='constant', constant_values=0) write_fits(outpath_sub+'pca_annular_res_empty_npc{}.fits'.format(opt_npc), pca_res_emp)
def dark_subtract(self, verbose=True, debug=True): sci_list = [] with open(self.outpath + "sci_list.txt", "r") as f: tmp = f.readlines() for line in tmp: sci_list.append(line.split('\n')[0]) sky_list = [] with open(self.outpath + "sky_list.txt", "r") as f: tmp = f.readlines() for line in tmp: sky_list.append(line.split('\n')[0]) unsat_list = [] with open(self.outpath + "unsat_list.txt", "r") as f: tmp = f.readlines() for line in tmp: unsat_list.append(line.split('\n')[0]) unsat_dark_list = [] with open(self.outpath + "unsat_dark_list.txt", "r") as f: tmp = f.readlines() for line in tmp: unsat_dark_list.append(line.split('\n')[0]) flat_list = [] with open(self.outpath + "flat_list.txt", "r") as f: tmp = f.readlines() for line in tmp: flat_list.append(line.split('\n')[0]) flat_dark_list = [] with open(self.outpath + "flat_dark_list.txt", "r") as f: tmp = f.readlines() for line in tmp: flat_dark_list.append(line.split('\n')[0]) sci_dark_list = [] with open(self.outpath + "sci_dark_list.txt", "r") as f: tmp = f.readlines() for line in tmp: sci_dark_list.append(line.split('\n')[0]) pixel_scale = fits_info.pixel_scale tmp = np.zeros([3, self.com_sz, self.com_sz]) #creating master dark cubes for fd, fd_name in enumerate(flat_dark_list): tmp_tmp = open_fits(self.inpath + fd_name, header=False, verbose=debug) tmp[fd] = frame_crop(tmp_tmp, self.com_sz, force=True, verbose=debug) write_fits(self.outpath + 'flat_dark_cube.fits', tmp) if verbose: print('Flat dark cubes have been cropped and saved') for sd, sd_name in enumerate(sci_dark_list): tmp_tmp = open_fits(self.inpath + sd_name, header=False, verbose=debug) n_dim = tmp_tmp.ndim if sd == 0: if n_dim == 2: tmp = np.array([ frame_crop(tmp_tmp, self.com_sz, force=True, verbose=debug) ]) else: tmp = cube_crop_frames(tmp_tmp, self.com_sz, force=True, verbose=debug) else: if n_dim == 2: tmp = np.append(tmp, [ frame_crop( tmp_tmp, self.com_sz, force=True, verbose=debug) ], axis=0) else: tmp = np.append(tmp, cube_crop_frames(tmp_tmp, self.com_sz, force=True, verbose=debug), axis=0) write_fits(self.outpath + 'sci_dark_cube.fits', tmp) if verbose: print('Sci dark cubes have been cropped and saved') #create an if stament for if the size is larger than sz and less than if less than crop by nx-1 for sd, sd_name in enumerate(unsat_dark_list): tmp_tmp = open_fits(self.inpath + sd_name, header=False, verbose=debug) tmp = np.zeros([ len(sci_dark_list) * tmp_tmp.shape[0], tmp_tmp.shape[1], tmp_tmp.shape[2] ]) n_dim = tmp_tmp.ndim if sd == 0: if n_dim == 2: ny, nx = tmp_tmp.shape if nx <= self.com_sz: tmp = np.array([ frame_crop(tmp_tmp, nx - 1, force=True, verbose=debug) ]) else: tmp = np.array( [frame_crop(tmp_tmp, self.com_sz, verbose=debug)]) else: nz, ny, nx = tmp_tmp.shape if nx <= self.com_sz: tmp = cube_crop_frames(tmp_tmp, nx - 1, force=True, verbose=debug) else: tmp = cube_crop_frames(tmp_tmp, self.com_sz, force=True, verbose=debug) else: if n_dim == 2: ny, nx = tmp_tmp.shape if nx <= self.com_sz: tmp = np.append(tmp, [ frame_crop( tmp_tmp, nx - 1, force=True, verbose=debug) ], axis=0) else: tmp = np.append(tmp, [ frame_crop(tmp_tmp, self.com_sz, force=True, verbose=debug) ], axis=0) else: nz, ny, nx = tmp_tmp.shape if nx <= self.com_sz: tmp = cube_crop_frames(tmp, nx - 1, force=True, verbose=debug) tmp = np.append(tmp, cube_crop_frames(tmp_tmp, nx - 1, force=True, verbose=debug), axis=0) else: tmp = np.append(tmp, cube_crop_frames(tmp_tmp, self.com_sz, force=True, verbose=debug), axis=0) tmp = np.zeros([ len(sci_dark_list) * tmp_tmp.shape[0], tmp_tmp.shape[1], tmp_tmp.shape[2] ]) write_fits(self.outpath + 'unsat_dark_cube.fits', tmp) if verbose: print('Unsat dark cubes have been cropped and saved') #defining the anulus (this is where we avoid correcting around the star) cy, cx = find_agpm_list(self, sci_list) self.agpm_pos = (cx, cy) if verbose: print(' The location of the AGPM has been calculated', 'cy = ', cy, 'cx = ', cx) agpm_dedge = min(self.agpm_pos[0], self.agpm_pos[1], self.com_sz - self.agpm_pos[0], self.com_sz - self.agpm_pos[1]) mask_arr = np.ones([self.com_sz, self.com_sz]) cy, cx = frame_center(mask_arr) mask_inner_rad = int(3.0 / pixel_scale) # 3arcsec min to avoid star emission mask_width = agpm_dedge - mask_inner_rad - 1 mask_AGPM_com = get_annulus_segments(mask_arr, mask_inner_rad, mask_width, mode='mask')[0] mask_AGPM_com = frame_shift( mask_AGPM_com, self.agpm_pos[1] - cy, self.agpm_pos[0] - cx) # agpm is not centered in the frame so shift the mask if verbose: print('AGPM mask has been defined') if debug: tmp = open_fits(self.outpath + sci_list[0]) #plot_frames(tmp[-1], circle = self.agpm_pos) #now begin the dark subtraction useing PCA npc_dark = 1 #val found this value gives the best result. tmp_tmp = np.zeros([len(flat_list), self.com_sz, self.com_sz]) tmp_tmp_tmp = open_fits(self.outpath + 'flat_dark_cube.fits') for fl, flat_name in enumerate(flat_list): tmp = open_fits(self.inpath + flat_name, header=False, verbose=debug) tmp_tmp[fl] = frame_crop(tmp, self.com_sz, force=True, verbose=debug) tmp_tmp = cube_subtract_sky_pca(tmp_tmp, tmp_tmp_tmp, mask_AGPM_com, ref_cube=None, ncomp=npc_dark) write_fits(self.outpath + '1_crop_flat_cube.fits', tmp_tmp) if verbose: print('Dark has been subtracted from Flats') if debug: #plot the median of dark cube median of cube before subtraction median after subtraction tmp_tmp_tmp = np.median(tmp_tmp_tmp, axis=0) #flat_dark cube median tmp = tmp #flat before subtraction tmp_tmp = np.median(tmp_tmp, axis=0) #median flat after dark subtract plot_frames((tmp_tmp_tmp, tmp, tmp_tmp)) tmp_tmp_tmp = open_fits(self.outpath + 'sci_dark_cube.fits') for sc, fits_name in enumerate(sci_list): tmp = open_fits(self.inpath + fits_name, header=False, verbose=debug) tmp = cube_crop_frames(tmp, self.com_sz, force=True, verbose=debug) tmp_tmp = cube_subtract_sky_pca(tmp, tmp_tmp_tmp, mask_AGPM_com, ref_cube=None, ncomp=npc_dark) write_fits(self.outpath + '1_crop_' + fits_name, tmp_tmp) if verbose: print('Dark has been subtracted from Sci') if debug: #plot the median of dark cube median of cube before subtraction median after subtraction tmp_tmp_tmp = np.median(tmp_tmp_tmp, axis=0) tmp = np.median(tmp, axis=0) tmp_tmp = np.median(tmp_tmp, axis=0) plot_frames((tmp_tmp_tmp, tmp, tmp_tmp)) tmp_tmp_tmp = open_fits(self.outpath + 'sci_dark_cube.fits') for sc, fits_name in enumerate(sky_list): tmp = open_fits(self.inpath + fits_name, header=False, verbose=debug) tmp = cube_crop_frames(tmp, self.com_sz, force=True, verbose=debug) tmp_tmp = cube_subtract_sky_pca(tmp, tmp_tmp_tmp, mask_AGPM_com, ref_cube=None, ncomp=npc_dark) write_fits(self.outpath + '1_crop_' + fits_name, tmp_tmp) if verbose: print('Dark has been subtracted from Sky') if debug: #plot the median of dark cube median of cube before subtraction median after subtraction tmp_tmp_tmp = np.median(tmp_tmp_tmp, axis=0) tmp = np.median(tmp, axis=0) tmp_tmp = np.median(tmp_tmp, axis=0) plot_frames((tmp_tmp_tmp, tmp, tmp_tmp)) tmp_tmp_tmp = open_fits(self.outpath + 'master_unsat_dark.fits') # no need to crop the unsat frame at the same size as the sci images if they are smaller for un, fits_name in enumerate(unsat_list): tmp = open_fits(self.inpath + fits_name, header=False) #tmp = cube_crop_frames(tmp,nx_unsat_crop) if tmp.shape[2] > self.com_sz: nx_unsat_crop = self.com_sz tmp_tmp = cube_crop_frames(tmp - tmp_tmp_tmp, nx_unsat_crop, force=True, verbose=debug) elif tmp.shape[2] % 2 == 0: nx_unsat_crop = tmp.shape[2] - 1 tmp_tmp = cube_crop_frames(tmp - tmp_tmp_tmp, nx_unsat_crop, force=True, verbose=debug) else: nx_unsat_crop = tmp.shape[2] tmp_tmp = tmp - tmp_tmp_tmp write_fits(self.outpath + '1_crop_unsat_' + fits_name, tmp_tmp) if verbose: print('unsat frames have been cropped') if debug: #plot the median of dark cube median of cube before subtraction median after subtraction tmp_tmp_tmp = np.median(tmp_tmp_tmp, axis=0) tmp = np.median(tmp, axis=0) tmp_tmp = np.median(tmp_tmp, axis=0) plot_frames((tmp_tmp_tmp, tmp, tmp_tmp))