def _snr_optimize(arg): pos_x, pos_y = arg _, _, snr, _ = false_alarm(image=image, x_pos=pos_x, y_pos=pos_y, size=self.m_aperture, ignore=self.m_ignore) return -snr
def _fpf_minimize(arg): pos_x, pos_y = arg try: _, _, _, fpf = false_alarm(image=image, x_pos=pos_x, y_pos=pos_y, size=self.m_aperture, ignore=self.m_ignore) except ValueError: fpf = float('inf') return fpf
def gaussian_noise(self, images, psf, parang, aperture): """ Function to compute the (constant) variance for the likelihood function when the variance parameter is set to gaussian (see Mawet et al. 2014). The planet is first removed from the dataset with the values specified as *param* in the constructor of the instance. Parameters ---------- images : numpy.ndarray Input images. psf : numpy.ndarray PSF template. parang : numpy.ndarray Parallactic angles (deg). aperture : dict Properties of the circular aperture. The radius is recommended to be larger than or equal to 0.5*lambda/D. Returns ------- float Variance. """ pixscale = self.m_image_in_port.get_attribute("PIXSCALE") fake = fake_planet(images=images, psf=psf, parang=parang, position=(self.m_param[0]/pixscale, self.m_param[1]), magnitude=self.m_param[2], psf_scaling=self.m_psf_scaling) _, res_arr = pca_psf_subtraction(images=fake, angles=-1.*parang+self.m_extra_rot, pca_number=self.m_pca_number) stack = combine_residuals(method=self.m_residuals, res_rot=res_arr) _, noise, _, _ = false_alarm(image=stack[0, ], x_pos=aperture['pos_x'], y_pos=aperture['pos_y'], size=aperture['radius'], ignore=False) return noise**2
def contrast_limit(path_images, path_psf, noise, mask, parang, psf_scaling, extra_rot, pca_number, threshold, aperture, residuals, snr_inject, position): """ Function for calculating the contrast limit at a specified position for a given sigma level or false positive fraction, both corrected for small sample statistics. Parameters ---------- path_images : str System location of the stack of images (3D). path_psf : str System location of the PSF template for the fake planet (3D). Either a single image or a stack of images equal in size to science data. noise : numpy.ndarray Residuals of the PSF subtraction (3D) without injection of fake planets. Used to measure the noise level with a correction for small sample statistics. mask : numpy.ndarray Mask (2D). parang : numpy.ndarray Derotation angles (deg). psf_scaling : float Additional scaling factor of the planet flux (e.g., to correct for a neutral density filter). Should have a positive value. extra_rot : float Additional rotation angle of the images in clockwise direction (deg). pca_number : int Number of principal components used for the PSF subtraction. threshold : tuple(str, float) Detection threshold for the contrast curve, either in terms of "sigma" or the false positive fraction (FPF). The value is a tuple, for example provided as ("sigma", 5.) or ("fpf", 1e-6). Note that when sigma is fixed, the false positive fraction will change with separation. Also, sigma only corresponds to the standard deviation of a normal distribution at large separations (i.e., large number of samples). aperture : float Aperture radius (pix) for the calculation of the false positive fraction. residuals : str Method used for combining the residuals ("mean", "median", "weighted", or "clipped"). position : tuple(float, float) The separation (pix) and position angle (deg) of the fake planet. snr_inject : float Signal-to-noise ratio of the injected planet signal that is used to measure the amount of self-subtraction. Returns ------- NoneType None """ images = np.load(path_images) psf = np.load(path_psf) if threshold[0] == "sigma": sigma = threshold[1] # Calculate the FPF for a given sigma level fpf = student_t(t_input=threshold, radius=position[0], size=aperture, ignore=False) elif threshold[0] == "fpf": fpf = threshold[1] # Calculate the sigma level for a given FPF sigma = student_t(t_input=threshold, radius=position[0], size=aperture, ignore=False) else: raise ValueError("Threshold type not recognized.") # Cartesian coordinates of the fake planet xy_fake = polar_to_cartesian(images, position[0], position[1] - extra_rot) # Determine the noise level _, t_noise, _, _ = false_alarm(image=noise[0, ], x_pos=xy_fake[0], y_pos=xy_fake[1], size=aperture, ignore=False) # Aperture properties im_center = center_subpixel(images) ap_dict = { 'type': 'circular', 'pos_x': im_center[1], 'pos_y': im_center[0], 'radius': aperture } # Measure the flux of the star phot_table = aperture_photometry(psf_scaling * psf[0, ], create_aperture(ap_dict), method='exact') star = phot_table['aperture_sum'][0] # Magnitude of the injected planet flux_in = snr_inject * t_noise mag = -2.5 * math.log10(flux_in / star) # Inject the fake planet fake = fake_planet(images=images, psf=psf, parang=parang, position=(position[0], position[1]), magnitude=mag, psf_scaling=psf_scaling) # Run the PSF subtraction _, im_res = pca_psf_subtraction(images=fake * mask, angles=-1. * parang + extra_rot, pca_number=pca_number) # Stack the residuals im_res = combine_residuals(method=residuals, res_rot=im_res) # Measure the flux of the fake planet flux_out, _, _, _ = false_alarm(image=im_res[0, ], x_pos=xy_fake[0], y_pos=xy_fake[1], size=aperture, ignore=False) # Calculate the amount of self-subtraction attenuation = flux_out / flux_in # Calculate the detection limit contrast = sigma * t_noise / (attenuation * star) # The flux_out can be negative, for example if the aperture includes self-subtraction regions if contrast > 0.: contrast = -2.5 * math.log10(contrast) else: contrast = np.nan # Separation [pix], position antle [deg], contrast [mag], FPF return position[0], position[1], contrast, fpf
def run(self) -> None: """ Run method of the module. Calculates the SNR and FPF for a specified position in a post- processed image with the Student's t-test (Mawet et al. 2014). This approach assumes Gaussian noise but accounts for small sample statistics. Returns ------- NoneType None """ def _snr_optimize(arg): pos_x, pos_y = arg _, _, snr, _ = false_alarm(image=image, x_pos=pos_x, y_pos=pos_y, size=self.m_aperture, ignore=self.m_ignore) return -snr self.m_snr_out_port.del_all_data() self.m_snr_out_port.del_all_attributes() pixscale = self.m_image_in_port.get_attribute('PIXSCALE') self.m_aperture /= pixscale nimages = self.m_image_in_port.get_shape()[0] bounds = ((self.m_position[0]+self.m_bounds[0][0], self.m_position[0]+self.m_bounds[0][1]), (self.m_position[1]+self.m_bounds[1][0], self.m_position[1]+self.m_bounds[1][1])) start_time = time.time() for j in range(nimages): progress(j, nimages, 'Calculating S/N and FPF...', start_time) image = self.m_image_in_port[j, ] center = center_subpixel(image) if self.m_optimize: result = minimize(fun=_snr_optimize, x0=[self.m_position[0], self.m_position[1]], method='SLSQP', bounds=bounds, tol=None, options={'ftol': self.m_tolerance}) _, _, snr, fpf = false_alarm(image=image, x_pos=result.x[0], y_pos=result.x[1], size=self.m_aperture, ignore=self.m_ignore) x_pos, y_pos = result.x[0], result.x[1] else: _, _, snr, fpf = false_alarm(image=image, x_pos=self.m_position[0], y_pos=self.m_position[1], size=self.m_aperture, ignore=self.m_ignore) x_pos, y_pos = self.m_position[0], self.m_position[1] sep_ang = cartesian_to_polar(center, y_pos, x_pos) result = np.column_stack((x_pos, y_pos, sep_ang[0]*pixscale, sep_ang[1], snr, fpf)) self.m_snr_out_port.append(result, data_dim=2) history = f'aperture [arcsec] = {self.m_aperture*pixscale:.2f}' self.m_snr_out_port.copy_attributes(self.m_image_in_port) self.m_snr_out_port.add_history('FalsePositiveModule', history) self.m_snr_out_port.close_port()
def paco_contrast_limit(path_images, path_psf, noise, parang, psf_rad, psf_scaling, res_scaling, pixscale, extra_rot, threshold, aperture, snr_inject, position, algorithm): """ Function for calculating the contrast limit at a specified position for a given sigma level or false positive fraction, both corrected for small sample statistics. Parameters ---------- path_images : str System location of the stack of images (3D). path_psf : str System location of the PSF template for the fake planet (3D). Either a single image or a stack of images equal in size to science data. noise : numpy.ndarray Residuals of the PSF subtraction (3D) without injection of fake planets. Used to measure the noise level with a correction for small sample statistics. parang : numpy.ndarray Derotation angles (deg). psf_scaling : float Additional scaling factor of the planet flux (e.g., to correct for a neutral density filter). Should have a positive value. extra_rot : float Additional rotation angle of the images in clockwise direction (deg). threshold : tuple(str, float) Detection threshold for the contrast curve, either in terms of "sigma" or the false positive fraction (FPF). The value is a tuple, for example provided as ("sigma", 5.) or ("fpf", 1e-6). Note that when sigma is fixed, the false positive fraction will change with separation. Also, sigma only corresponds to the standard deviation of a normal distribution at large separations (i.e., large number of samples). aperture : float Aperture radius (pix) for the calculation of the false positive fraction. position : tuple(float, float) The separation (pix) and position angle (deg) of the fake planet. snr_inject : float Signal-to-noise ratio of the injected planet signal that is used to measure the amount of self-subtraction. Returns ------- NoneType None """ images = np.load(path_images) psf = np.load(path_psf) if threshold[0] == "sigma": sigma = threshold[1] # Calculate the FPF for a given sigma level fpf = student_t(t_input=threshold, radius=position[0], size=aperture, ignore=False) elif threshold[0] == "fpf": fpf = threshold[1] # Calculate the sigma level for a given FPF sigma = student_t(t_input=threshold, radius=position[0], size=aperture, ignore=False) else: raise ValueError("Threshold type not recognized.") # Cartesian coordinates of the fake planet xy_fake = polar_to_cartesian(images, position[0], position[1] - extra_rot) # Determine the noise level _, t_noise, _, _ = false_alarm(image=noise, x_pos=xy_fake[0], y_pos=xy_fake[1], size=aperture, ignore=False) # Aperture properties im_center = center_subpixel(images) # Measure the flux of the star ap_phot = CircularAperture((im_center[1], im_center[0]), aperture) phot_table = aperture_photometry(psf_scaling * psf[0, ], ap_phot, method='exact') star = phot_table['aperture_sum'][0] # Magnitude of the injected planet flux_in = snr_inject * t_noise mag = -2.5 * math.log10(flux_in / star) # Inject the fake planet fake = fake_planet(images=images, psf=psf, parang=parang, position=(position[0], position[1]), magnitude=mag, psf_scaling=psf_scaling) path_fake_planet = os.path.split(path_images)[0] = "/" np.save(path_fake_planet + "injected.npy", fake) # Run the PSF subtraction #_, im_res = pca_psf_subtraction(images=fake*mask, # angles=-1.*parang+extra_rot, # pca_number=pca_number) # Stack the residuals #im_res = combine_residuals(method=residuals, res_rot=im_res) # Measure the flux of the fake planet #flux_out, _, _, _ = false_alarm(image=im_res[0, ], # x_pos=xy_fake[0], # y_pos=xy_fake[1], # size=aperture, # ignore=False) # Setup PACO if algorithm == "fastpaco": fp = FastPACO(image_file=path_fake_planet + "injected.npy", angles=parang, psf=psf, psf_rad=psf_rad, px_scale=pixscale, res_scale=res_scaling, verbose=False) elif algorithm == "fullpaco": fp = FullPACO(image_file=path_fake_planet + "injected.npy", angles=parang, psf=psf, psf_rad=psf_rad, px_scale=pixscale, res_scale=res_scaling, verbose=False) # Run PACO # Restrict to 1 processor since this module is called from a child process a, b = fp.PACO(cpu=1) # Should do something with these? snr = b / np.sqrt(a) flux_residual = b / a flux_out, _, _, _ = false_alarm(image=flux_residual, x_pos=xy_fake[0], y_pos=xy_fake[1], size=aperture, ignore=False) # Iterative, unbiased flux estimation # This doesn't seem to give the correct results yet? #if self.m_flux_calc: # phi0s = fp.thresholdDetection(snr,self.m_threshold) # init = np.array([flux[int(phi0[0]),int(phi0[1])] for phi0 in phi0s]) # ests = np.array(fp.fluxEstimate(phi0s,self.m_eps,init)) # Calculate the amount of self-subtraction attenuation = flux_out / flux_in # Calculate the detection limit contrast = sigma * t_noise / (attenuation * star) # The flux_out can be negative, for example if the aperture includes self-subtraction regions if contrast > 0.: contrast = -2.5 * math.log10(contrast) else: contrast = np.nan # Separation [pix], position antle [deg], contrast [mag], FPF return (position[0], position[1], contrast, fpf)
def run(self): """ Run method of the module. Calculates the SNR and FPF for a specified position in a post- processed image with the Student's t-test (Mawet et al. 2014). This approach assumes Gaussian noise but accounts for small sample statistics. Returns ------- NoneType None """ def _fpf_minimize(arg): pos_x, pos_y = arg try: _, _, _, fpf = false_alarm(image=image, x_pos=pos_x, y_pos=pos_y, size=self.m_aperture, ignore=self.m_ignore) except ValueError: fpf = float('inf') return fpf self.m_snr_out_port.del_all_data() self.m_snr_out_port.del_all_attributes() pixscale = self.m_image_in_port.get_attribute('PIXSCALE') self.m_aperture /= pixscale nimages = self.m_image_in_port.get_shape()[0] start_time = time.time() for j in range(nimages): progress(j, nimages, 'Running FalsePositiveModule...', start_time) image = self.m_image_in_port[j, ] center = center_subpixel(image) if self.m_optimize: result = minimize(fun=_fpf_minimize, x0=[self.m_position[0], self.m_position[1]], method='Nelder-Mead', tol=None, options={ 'xatol': self.m_tolerance, 'fatol': float('inf') }) _, _, snr, fpf = false_alarm(image=image, x_pos=result.x[0], y_pos=result.x[1], size=self.m_aperture, ignore=self.m_ignore) x_pos, y_pos = result.x[0], result.x[1] else: _, _, snr, fpf = false_alarm(image=image, x_pos=self.m_position[0], y_pos=self.m_position[1], size=self.m_aperture, ignore=self.m_ignore) x_pos, y_pos = self.m_position[0], self.m_position[1] sep_ang = cartesian_to_polar(center, x_pos, y_pos) result = np.column_stack( (x_pos, y_pos, sep_ang[0] * pixscale, sep_ang[1], snr, fpf)) self.m_snr_out_port.append(result, data_dim=2) sys.stdout.write('Running FalsePositiveModule... [DONE]\n') sys.stdout.flush() history = f'aperture [arcsec] = {self.m_aperture*pixscale:.2f}' self.m_snr_out_port.copy_attributes(self.m_image_in_port) self.m_snr_out_port.add_history('FalsePositiveModule', history) self.m_snr_out_port.close_port()