def run(self) -> None: """ Run method of the module. Selects the pixel values in the column and row at each pixel position, computes the mean or median value while excluding pixels within the radius of the mask, and subtracts the mean or median value from each pixel separately. Returns ------- NoneType None """ pixscale = self.m_image_in_port.get_attribute('PIXSCALE') im_shape = self.m_image_in_port.get_shape()[-2:] def _subtract_line(image_in, mask): image_tmp = np.copy(image_in) image_tmp[mask == 0.] = np.nan if self.m_combine == 'mean': row_mean = np.nanmean(image_tmp, axis=1) col_mean = np.nanmean(image_tmp, axis=0) x_grid, y_grid = np.meshgrid(col_mean, row_mean) subtract = (x_grid+y_grid)/2. elif self.m_combine == 'median': subtract = np.zeros(im_shape) col_median = np.nanmedian(image_tmp, axis=0) col_2d = np.tile(col_median, (im_shape[1], 1)) image_tmp -= col_2d image_tmp[mask == 0.] = np.nan row_median = np.nanmedian(image_tmp, axis=1) row_2d = np.tile(row_median, (im_shape[0], 1)) row_2d = np.rot90(row_2d) # 90 deg rotation in clockwise direction subtract = col_2d + row_2d return image_in - subtract if self.m_mask: size = (self.m_mask/pixscale, None) else: size = (None, None) mask = create_mask(im_shape, size) self.apply_function_to_images(_subtract_line, self.m_image_in_port, self.m_image_out_port, 'Running LineSubtractionModule', func_args=(mask, )) history = f'combine = {self.m_combine}' self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history('LineSubtractionModule', history) self.m_image_out_port.close_port()
def run(self) -> None: """ Run method of the module. Selects the pixel values in the column and row at each pixel position, computes the mean or median value while excluding pixels within the radius of the mask, and subtracts the mean or median value from each pixel separately. Returns ------- NoneType None """ pixscale = self.m_image_in_port.get_attribute('PIXSCALE') im_shape = self.m_image_in_port.get_shape()[-2:] if self.m_mask: size = (self.m_mask/pixscale, None) else: size = (None, None) mask = create_mask(im_shape, size) self.apply_function_to_images(subtract_line, self.m_image_in_port, self.m_image_out_port, 'Background subtraction', func_args=(mask, self.m_combine, im_shape)) history = f'combine = {self.m_combine}' self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history('LineSubtractionModule', history) self.m_image_out_port.close_port()
def _objective(arg): sys.stdout.write('.') sys.stdout.flush() pos_y = arg[0] pos_x = arg[1] mag = arg[2] sep_ang = cartesian_to_polar(center, pos_y, pos_x) fake = fake_planet(images=images, psf=psf, parang=parang, position=(sep_ang[0], sep_ang[1]), magnitude=mag, psf_scaling=self.m_psf_scaling) mask = create_mask(fake.shape[-2:], (self.m_cent_size, self.m_edge_size)) if self.m_reference_in_port is None: _, im_res = pca_psf_subtraction(images=fake*mask, angles=-1.*parang+self.m_extra_rot, pca_number=self.m_pca_number, pca_sklearn=None, im_shape=None, indices=None) else: im_reshape = np.reshape(fake*mask, (im_shape[0], im_shape[1]*im_shape[2])) _, im_res = pca_psf_subtraction(images=im_reshape, angles=-1.*parang+self.m_extra_rot, pca_number=self.m_pca_number, pca_sklearn=sklearn_pca, im_shape=im_shape, indices=None) res_stack = combine_residuals(method=self.m_residuals, res_rot=im_res) self.m_res_out_port.append(res_stack, data_dim=3) chi_square = merit_function(residuals=res_stack[0, ], merit=self.m_merit, aperture=aperture, sigma=self.m_sigma) position = rotate_coordinates(center, (pos_y, pos_x), -self.m_extra_rot) res = np.asarray([position[1], position[0], sep_ang[0]*pixscale, (sep_ang[1]-self.m_extra_rot) % 360., mag, chi_square]) self.m_flux_position_port.append(res, data_dim=2) return chi_square
def _objective(arg): sys.stdout.write('.') sys.stdout.flush() pos_y = arg[0] pos_x = arg[1] mag = arg[2] sep = math.sqrt((pos_y - center[0])**2 + (pos_x - center[1])**2) ang = math.atan2(pos_y - center[0], pos_x - center[1]) * 180. / math.pi - 90. fake = fake_planet(images=images, psf=psf, parang=parang, position=(sep, ang), magnitude=mag, psf_scaling=self.m_psf_scaling) im_shape = (fake.shape[-2], fake.shape[-1]) mask = create_mask(im_shape, [self.m_cent_size, self.m_edge_size]) _, im_res = pca_psf_subtraction(images=fake * mask, angles=-1. * parang + self.m_extra_rot, pca_number=self.m_pca_number) stack = combine_residuals(method=self.m_residuals, res_rot=im_res) self.m_res_out_port.append(stack, data_dim=3) merit = merit_function(residuals=stack, function=self.m_merit, variance="poisson", aperture=self.m_aperture, sigma=self.m_sigma) position = rotate_coordinates(center, (pos_y, pos_x), -self.m_extra_rot) res = np.asarray((position[1], position[0], sep * pixscale, (ang - self.m_extra_rot) % 360., mag, merit)) self.m_flux_position_port.append(res, data_dim=2) return merit
def run(self) -> None: """ Run method of the module. Computes the similarity between frames based on the Mean Squared Error (MSE), the Pearson Correlation Coefficient (PCC), or the Structural Similarity (SSIM). Returns ------- NoneType None """ # get image number and image shapes nimages = self.m_image_in_port.get_shape()[0] im_shape = self.m_image_in_port.get_shape()[1:] cpu = self._m_config_port.get_attribute('CPU') pixscale = self.m_image_in_port.get_attribute('PIXSCALE') # convert arcsecs to pixels self.m_mask_radii = (math.floor(self.m_mask_radii[0] / pixscale), math.floor(self.m_mask_radii[1] / pixscale)) self.m_window_size = int(self.m_window_size / pixscale) # overlay the same mask over all images mask = create_mask(im_shape, self.m_mask_radii) images = self.m_image_in_port.get_all() # close the port during the calculations self.m_image_out_port.close_port() if self.m_temporal_median == 'constant': temporal_median = np.median(images, axis=0) else: temporal_median = False if self.m_method == 'SSIM': images = crop_image(images, None, int(self.m_mask_radii[1])) temporal_median = crop_image(temporal_median, None, int(self.m_mask_radii[1])) else: images *= mask # compare images and store similarity similarities = np.zeros(nimages) pool = mp.Pool(cpu) async_results = [] for i in range(nimages): async_results.append(pool.apply_async(FrameSimilarityModule._similarity, args=(images, i, self.m_method, self.m_window_size, temporal_median))) pool.close() start_time = time.time() # wait for all processes to finish while mp.active_children(): # number of finished processes nfinished = sum([i.ready() for i in async_results]) progress(nfinished, nimages, 'Running FrameSimilarityModule', start_time) # check if new processes have finished every 5 seconds time.sleep(5) # get the results for every async_result object for async_result in async_results: reference, similarity = async_result.get() similarities[reference] = similarity pool.terminate() # reopen the port after the calculation self.m_image_out_port.open_port() self.m_image_out_port.add_attribute(f'{self.m_method}', similarities, static=False) self.m_image_out_port.close_port()
def PCArun(self) -> None: """ Run method of the module. An artificial planet is injected (based on the noise level) at a given separation and position angle. The amount of self-subtraction is then determined and the contrast limit is calculated for a given sigma level or false positive fraction. A correction for small sample statistics is applied for both cases. Note that if the sigma level is fixed, the false positive fraction changes with separation, following the Student's t-distribution (see Mawet et al. 2014 for details). Returns ------- NoneType None """ images = self.m_image_in_port.get_all() psf = self.m_psf_in_port.get_all() if psf.shape[0] != 1 and psf.shape[0] != images.shape[0]: raise ValueError( f'The number of frames in psf_in_tag {psf.shape} does not match with ' f'the number of frames in image_in_tag {images.shape}. The ' f'DerotateAndStackModule can be used to average the PSF frames ' f'(without derotating) before applying the ContrastCurveModule.' ) cpu = self._m_config_port.get_attribute('CPU') working_place = self._m_config_port.get_attribute('WORKING_PLACE') parang = self.m_image_in_port.get_attribute('PARANG') pixscale = self.m_image_in_port.get_attribute('PIXSCALE') self.m_image_in_port.close_port() self.m_psf_in_port.close_port() if self.m_cent_size is not None: self.m_cent_size /= pixscale if self.m_edge_size is not None: self.m_edge_size /= pixscale self.m_aperture /= pixscale pos_r = np.arange(self.m_separation[0] / pixscale, self.m_separation[1] / pixscale, self.m_separation[2] / pixscale) pos_t = np.arange(self.m_angle[0] + self.m_extra_rot, self.m_angle[1] + self.m_extra_rot, self.m_angle[2]) if self.m_cent_size is None: index_del = np.argwhere(pos_r - self.m_aperture <= 0.) else: index_del = np.argwhere( pos_r - self.m_aperture <= self.m_cent_size) pos_r = np.delete(pos_r, index_del) if self.m_edge_size is None or self.m_edge_size > images.shape[1] / 2.: index_del = np.argwhere( pos_r + self.m_aperture >= images.shape[1] / 2.) else: index_del = np.argwhere( pos_r + self.m_aperture >= self.m_edge_size) pos_r = np.delete(pos_r, index_del) positions = [] for sep in pos_r: for ang in pos_t: positions.append((sep, ang)) result = [] async_results = [] # Create temporary files tmp_im_str = os.path.join(working_place, 'tmp_images.npy') tmp_psf_str = os.path.join(working_place, 'tmp_psf.npy') np.save(tmp_im_str, images) np.save(tmp_psf_str, psf) mask = create_mask(images.shape[-2:], (self.m_cent_size, self.m_edge_size)) _, im_res = pca_psf_subtraction(images=images * mask, angles=-1. * parang + self.m_extra_rot, pca_number=self.m_pca_number) noise = combine_residuals(method=self.m_residuals, res_rot=im_res) pool = mp.Pool(cpu) for pos in positions: async_results.append( pool.apply_async( contrast_limit, args=(tmp_im_str, tmp_psf_str, noise, mask, parang, self.m_psf_scaling, self.m_extra_rot, self.m_pca_number, self.m_threshold, self.m_aperture, self.m_residuals, self.m_snr_inject, pos))) pool.close() start_time = time.time() # wait for all processes to finish while mp.active_children(): # number of finished processes nfinished = sum([i.ready() for i in async_results]) progress(nfinished, len(positions), 'Calculating detection limits...', start_time) # check if new processes have finished every 5 seconds time.sleep(5) if nfinished != len(positions): sys.stdout.write( '\r ') sys.stdout.write('\rCalculating detection limits... [DONE]\n') sys.stdout.flush() # get the results for every async_result object for item in async_results: result.append(item.get()) pool.terminate() os.remove(tmp_im_str) os.remove(tmp_psf_str) result = np.asarray(result) # Sort the results first by separation and then by angle indices = np.lexsort((result[:, 1], result[:, 0])) result = result[indices] result = result.reshape((pos_r.size, pos_t.size, 4)) mag_mean = np.nanmean(result, axis=1)[:, 2] mag_var = np.nanvar(result, axis=1)[:, 2] res_fpf = result[:, 0, 3] limits = np.column_stack( (pos_r * pixscale, mag_mean, mag_var, res_fpf)) self.m_image_in_port._check_status_and_activate() self.m_contrast_out_port._check_status_and_activate() self.m_contrast_out_port.set_all(limits, data_dim=2) history = f'{self.m_threshold[0]} = {self.m_threshold[1]}' self.m_contrast_out_port.add_history('ContrastCurveModule', history) self.m_contrast_out_port.copy_attributes(self.m_image_in_port) self.m_contrast_out_port.close_port()
def run(self): """ Run method of the module. Masks and normalizes the images. Returns ------- NoneType None """ self.m_image_out_port.del_all_data() self.m_image_out_port.del_all_attributes() if self.m_mask_out_port is not None: self.m_mask_out_port.del_all_data() self.m_mask_out_port.del_all_attributes() # Get PIXSCALE and MEMORY attributes pixscale = self.m_image_in_port.get_attribute("PIXSCALE") memory = self._m_config_port.get_attribute("MEMORY") # Get the number of images and split into batches to comply with memory constraints im_shape = self.m_image_in_port.get_shape() nimages = im_shape[0] frames = memory_frames(memory, nimages) # Convert m_cent_size and m_edge_size from arcseconds to pixels if self.m_cent_size is not None: self.m_cent_size /= pixscale if self.m_edge_size is not None: self.m_edge_size /= pixscale # Create 2D disk mask which will be applied to every frame mask = create_mask((int(im_shape[-2]), int(im_shape[-1])), [self.m_cent_size, self.m_edge_size]).astype(bool) # Keep track of the normalization vectors in case we are normalizing the images (if # we are not normalizing, this list will remain empty) norms = list() # Run the PSFpreparationModule for each subset of frames for i, _ in enumerate(frames[:-1]): # Print progress to command line progress(i, len(frames[:-1]), "Running PSFpreparationModule...") # Get the images and ensure they have the correct 3D shape with the following # three dimensions: (batch_size, height, width) images = self.m_image_in_port[frames[i]:frames[i + 1], ] if images.ndim == 2: warnings.warn( "The input data has 2 dimensions whereas 3 dimensions are required. " "An extra dimension has been added.") images = images[np.newaxis, ...] # Apply the mask, i.e., set all pixels to 0 where the mask is False images[:, ~mask] = 0. # If desired, normalize the images using the Frobenius norm if self.m_norm: im_norm = np.linalg.norm(images, ord="fro", axis=(1, 2)) images /= im_norm[:, np.newaxis, np.newaxis] norms.append(im_norm) # Write processed images to output port self.m_image_out_port.append(images, data_dim=3) # Store information about mask if self.m_mask_out_port is not None: self.m_mask_out_port.set_all(mask) self.m_mask_out_port.copy_attributes(self.m_image_in_port) # Copy attributes from input port self.m_image_out_port.copy_attributes(self.m_image_in_port) # If the norms list is not empty (i.e., if we have computed the norm for every image), # we can also save the corresponding norm vector as an additional attribute if norms: self.m_image_out_port.add_attribute(name="norm", value=np.hstack(norms), static=False) # Save cent_size and edge_size as attributes to the output port if self.m_cent_size is not None: self.m_image_out_port.add_attribute(name="cent_size", value=self.m_cent_size * pixscale, static=True) if self.m_edge_size is not None: self.m_image_out_port.add_attribute(name="edge_size", value=self.m_edge_size * pixscale, static=True) sys.stdout.write("Running PSFpreparationModule... [DONE]\n") sys.stdout.flush()
def run(self) -> None: """ Run method of the module. The posterior distributions of the separation, position angle, and flux contrast are sampled with the affine invariant Markov chain Monte Carlo (MCMC) ensemble sampler emcee. At each step, a negative copy of the PSF template is injected and the likelihood function is evaluated at the approximate position of the planet. Returns ------- NoneType None """ ndim = 3 cpu = self._m_config_port.get_attribute('CPU') pixscale = self.m_image_in_port.get_attribute('PIXSCALE') parang = self.m_image_in_port.get_attribute('PARANG') images = self.m_image_in_port.get_all() psf = self.m_psf_in_port.get_all() im_shape = self.m_image_in_port.get_shape()[-2:] self.m_image_in_port.close_port() self.m_psf_in_port.close_port() if psf.shape[0] != 1 and psf.shape[0] != images.shape[0]: raise ValueError('The number of frames in psf_in_tag does not match with the number of ' 'frames in image_in_tag. The DerotateAndStackModule can be used to ' 'average the PSF frames (without derotating) before applying the ' 'MCMCsamplingModule.') if self.m_mask[0] is not None: self.m_mask = (self.m_mask[0]/pixscale, self.m_mask[1]) if self.m_mask[1] is not None: self.m_mask = (self.m_mask[0], self.m_mask[1]/pixscale) # create the mask and get the unmasked image indices mask = create_mask(im_shape[-2:], self.m_mask) indices = np.where(mask.reshape(-1) != 0.)[0] if isinstance(self.m_aperture, float): yx_pos = polar_to_cartesian(images, self.m_param[0]/pixscale, self.m_param[1]) aperture = (int(round(yx_pos[0])), int(round(yx_pos[1])), self.m_aperture/pixscale) elif isinstance(self.m_aperture, tuple): aperture = (self.m_aperture[1], self.m_aperture[0], self.m_aperture[2]/pixscale) initial = np.zeros((self.m_nwalkers, ndim)) initial[:, 0] = self.m_param[0] + np.random.normal(0, self.m_sigma[0], self.m_nwalkers) initial[:, 1] = self.m_param[1] + np.random.normal(0, self.m_sigma[1], self.m_nwalkers) initial[:, 2] = self.m_param[2] + np.random.normal(0, self.m_sigma[2], self.m_nwalkers) print('Sampling the posterior distributions with MCMC...') with Pool(processes=cpu) as pool: sampler = emcee.EnsembleSampler(self.m_nwalkers, ndim, lnprob, pool=pool, args=([self.m_bounds, images, psf, mask, parang, self.m_psf_scaling, pixscale, self.m_pca_number, self.m_extra_rot, aperture, indices, self.m_merit, self.m_residuals])) sampler.run_mcmc(initial, self.m_nsteps, progress=True) self.m_image_in_port._check_status_and_activate() self.m_chain_out_port._check_status_and_activate() self.m_chain_out_port.set_all(sampler.get_chain()) history = f'walkers = {self.m_nwalkers}, steps = {self.m_nsteps}' self.m_chain_out_port.copy_attributes(self.m_image_in_port) self.m_chain_out_port.add_history('MCMCsamplingModule', history) mean_accept = np.mean(sampler.acceptance_fraction) print(f'Mean acceptance fraction: {mean_accept:.3f}') self.m_chain_out_port.add_attribute('ACCEPTANCE', mean_accept, static=True) try: autocorr = emcee.autocorr.integrated_time(sampler.get_chain()) print(f'Integrated autocorrelation time ={autocorr}') except emcee.autocorr.AutocorrError: autocorr = [np.nan, np.nan, np.nan] print('The chain is too short to reliably estimate the autocorrelation time. [WARNING]') self.m_chain_out_port.add_attribute('AUTOCORR_0', autocorr[0], static=True) self.m_chain_out_port.add_attribute('AUTOCORR_1', autocorr[1], static=True) self.m_chain_out_port.add_attribute('AUTOCORR_2', autocorr[2], static=True) self.m_chain_out_port.close_port()
def pixel_variance(var_type: str, images: np.ndarray, parang: np.ndarray, cent_size: Optional[float], edge_size: Optional[float], pca_number: int, residuals: str, aperture: Tuple[int, int, float], sigma: float) -> float: """ Function to calculate the variance of the noise. After the PSF subtraction, images are rotated in opposite direction of the regular derotation, therefore dispersing any companion or disk signal. The noise is measured within an annulus. Parameters ---------- var_type : str Variance type ('gaussian' or 'hessian'). images : numpy.ndarray Input images (3D). parang : numpy.ndarray Parallactic angles. cent_size : float, None Radius of the central mask (pix). No mask is used when set to None. edge_size : float, None Outer radius (pix) beyond which pixels are masked. No outer mask is used when set to None. pca_number : int Number of principal components (PCs) used for the PSF subtraction. residuals : str Method for combining the residuals ('mean', 'median', 'weighted', or 'clipped'). aperture : tuple(int, int, float) Aperture position (y, x) and radius (pix). sigma : float, None Standard deviation (pix) of the Gaussian kernel which is used to smooth the images. Returns ------- float Variance of the pixel values. Either the variance of the pixel values ('gaussian') or the variance of the determinant of the Hessian ('hessian'). """ mask = create_mask(images.shape[-2:], (cent_size, edge_size)) _, im_res_derot = pca_psf_subtraction(images * mask, parang, pca_number) res_noise = combine_residuals(residuals, im_res_derot) sep_ang = cartesian_to_polar(center_subpixel(res_noise), aperture[0], aperture[1]) if var_type == 'gaussian': selected = select_annulus(res_noise[0, ], sep_ang[0] - aperture[2], sep_ang[0] + aperture[2]) elif var_type == 'hessian': hessian_rr, hessian_rc, hessian_cc = hessian_matrix( image=res_noise[0, ], sigma=sigma, mode='constant', cval=0., order='rc') hes_det = (hessian_rr * hessian_cc) - (hessian_rc * hessian_rc) selected = select_annulus(hes_det, sep_ang[0] - aperture[2], sep_ang[0] + aperture[2]) return float(np.var(selected))
def run(self) -> None: """ Run method of the module. The posterior distributions of the separation, position angle, and flux contrast are sampled with the affine invariant Markov chain Monte Carlo (MCMC) ensemble sampler emcee. At each step, a negative copy of the PSF template is injected and the likelihood function is evaluated at the approximate position of the planet. Returns ------- NoneType None """ ndim = 3 cpu = self._m_config_port.get_attribute('CPU') pixscale = self.m_image_in_port.get_attribute('PIXSCALE') parang = self.m_image_in_port.get_attribute('PARANG') images = self.m_image_in_port.get_all() psf = self.m_psf_in_port.get_all() if psf.shape[0] != 1 and psf.shape[0] != images.shape[0]: raise ValueError('The number of frames in psf_in_tag does not match with the number of ' 'frames in image_in_tag. The DerotateAndStackModule can be used to ' 'average the PSF frames (without derotating) before applying the ' 'MCMCsamplingModule.') im_shape = self.m_image_in_port.get_shape()[-2:] if self.m_mask[0] is not None: self.m_mask = (self.m_mask[0]/pixscale, self.m_mask[1]) if self.m_mask[1] is not None: self.m_mask = (self.m_mask[0], self.m_mask[1]/pixscale) # create the mask and get the unmasked image indices mask = create_mask(im_shape[-2:], self.m_mask) indices = np.where(mask.reshape(-1) != 0.)[0] if isinstance(self.m_aperture, float): yx_pos = polar_to_cartesian(images, self.m_param[0]/pixscale, self.m_param[1]) aperture = (int(round(yx_pos[0])), int(round(yx_pos[1])), self.m_aperture/pixscale) elif isinstance(self.m_aperture, tuple): aperture = (self.m_aperture[1], self.m_aperture[0], self.m_aperture[2]/pixscale) initial = np.zeros((self.m_nwalkers, ndim)) initial[:, 0] = self.m_param[0] + np.random.normal(0, self.m_sigma[0], self.m_nwalkers) initial[:, 1] = self.m_param[1] + np.random.normal(0, self.m_sigma[1], self.m_nwalkers) initial[:, 2] = self.m_param[2] + np.random.normal(0, self.m_sigma[2], self.m_nwalkers) # if self.m_merit == 'gaussian': # variance = self.gaussian_variance(images*mask, psf, parang, aperture) # else: # variance = None sampler = emcee.EnsembleSampler(nwalkers=self.m_nwalkers, dim=ndim, lnpostfn=lnprob, a=self.m_scale, args=([self.m_bounds, images, psf, mask, parang, self.m_psf_scaling, pixscale, self.m_pca_number, self.m_extra_rot, aperture, indices, self.m_merit, self.m_residuals]), threads=cpu) start_time = time.time() for i, _ in enumerate(sampler.sample(p0=initial, iterations=self.m_nsteps)): progress(i, self.m_nsteps, 'Running MCMCsamplingModule...', start_time) sys.stdout.write('Running MCMCsamplingModule... [DONE]\n') sys.stdout.flush() self.m_chain_out_port.set_all(sampler.chain) history = f'walkers = {self.m_nwalkers}, steps = {self.m_nsteps}' self.m_chain_out_port.copy_attributes(self.m_image_in_port) self.m_chain_out_port.add_history('MCMCsamplingModule', history) self.m_chain_out_port.close_port() print(f'Mean acceptance fraction: {np.mean(sampler.acceptance_fraction):.3f}') try: autocorr = emcee.autocorr.integrated_time(sampler.flatchain, low=10, high=None, step=1, c=10, full_output=False, axis=0, fast=False) print('Integrated autocorrelation time =', autocorr) except emcee.autocorr.AutocorrError: print('The chain is too short to reliably estimate the autocorrelation time. [WARNING]')
def run(self) -> None: """ Run method of the module. Masks and normalizes the images. Returns ------- NoneType None """ # Get the PIXSCALE and MEMORY attributes pixscale = self.m_image_in_port.get_attribute('PIXSCALE') memory = self._m_config_port.get_attribute('MEMORY') # Get the numnber of dimensions and shape ndim = self.m_image_in_port.get_ndim() im_shape = self.m_image_in_port.get_shape() if ndim == 3: # Number of images nimages = im_shape[-3] # Split into batches to comply with memory constraints frames = memory_frames(memory, nimages) elif ndim == 4: # Process all wavelengths per exposure at once frames = np.linspace(0, im_shape[-3], im_shape[-3] + 1) if self.m_norm and ndim == 4: warnings.warn( 'The \'norm\' parameter does not support 4D datasets and will therefore ' 'be ignored.') # Convert m_cent_size and m_edge_size from arcseconds to pixels if self.m_cent_size is not None: self.m_cent_size /= pixscale if self.m_edge_size is not None: self.m_edge_size /= pixscale # Create 2D disk mask which will be applied to every frame mask = create_mask((int(im_shape[-2]), int(im_shape[-1])), (self.m_cent_size, self.m_edge_size)).astype(bool) # Keep track of the normalization vectors in case we are normalizing the images (if # we are not normalizing, this list will remain empty) norms = list() start_time = time.time() # Run the PSFpreparationModule for each subset of frames for i in range(frames[:-1].size): # Print progress to command line progress(i, len(frames[:-1]), 'Preparing images for PSF subtraction...', start_time) if ndim == 3: # Get the images and ensure they have the correct 3D shape with the following # three dimensions: (batch_size, height, width) images = self.m_image_in_port[frames[i]:frames[i + 1], ] if images.ndim == 2: warnings.warn( 'The input data has 2 dimensions whereas 3 dimensions are ' 'required. An extra dimension has been added.') images = images[np.newaxis, ...] elif ndim == 4: # Process all wavelengths per exposure at once images = self.m_image_in_port[:, i, ] # Apply the mask, i.e., set all pixels to 0 where the mask is False images[:, ~mask] = 0. # If desired, normalize the images using the Frobenius norm if self.m_norm and ndim == 3: im_norm = np.linalg.norm(images, ord='fro', axis=(1, 2)) images /= im_norm[:, np.newaxis, np.newaxis] norms.append(im_norm) # Write processed images to output port if ndim == 3: self.m_image_out_port.append(images, data_dim=3) elif ndim == 4: self.m_image_out_port.append(images, data_dim=4) # Store information about mask if self.m_mask_out_port is not None: self.m_mask_out_port.set_all(mask) self.m_mask_out_port.copy_attributes(self.m_image_in_port) # Copy attributes from input port self.m_image_out_port.copy_attributes(self.m_image_in_port) # If the norms list is not empty (i.e., if we have computed the norm for every image), # we can also save the corresponding norm vector as an additional attribute if norms: self.m_image_out_port.add_attribute(name='norm', value=np.hstack(norms), static=False) # Save cent_size and edge_size as attributes to the output port if self.m_cent_size is not None: self.m_image_out_port.add_attribute(name='cent_size', value=self.m_cent_size * pixscale, static=True) if self.m_edge_size is not None: self.m_image_out_port.add_attribute(name='edge_size', value=self.m_edge_size * pixscale, static=True)
def run(self): """ Run method of the module. Shifts the reference PSF to the location of the fake planet with an additional correction for the parallactic angle and writes the stack with images with the injected planet signal. :return: None """ if not isinstance(self.m_param, tuple) or len(self.m_param) != 3: raise TypeError( "The param argument should contain a tuple with the approximate " "separation (arcsec), position angle (deg), and contrast (mag)." ) if not isinstance(self.m_bounds, tuple) or len(self.m_bounds) != 3: raise TypeError( "The bounds argument should contain a tuple with three tuples for " "the boundaries of the separation (arcsec), position angle (deg), and " "contrast (mag).") if not isinstance(self.m_sigma, tuple) or len(self.m_sigma) != 3: raise TypeError( "The sigma argument should contain a tuple with the standard " "deviation of the separation (arcsec), position angle (deg), " "and contrast (mag) that is used to sample the starting position " "of the walkers.") ndim = 3 cpu = self._m_config_port.get_attribute("CPU") pixscale = self.m_image_in_port.get_attribute("PIXSCALE") parang = self.m_image_in_port.get_attribute("PARANG") images = self.m_image_in_port.get_all() psf = self.m_psf_in_port.get_all() if psf.ndim == 3 and psf.shape[0] != images.shape[0]: raise ValueError( 'The number of frames in psf_in_tag does not match with the number of ' 'frames in image_in_tag. The DerotateAndStackModule can be used to ' 'average the PSF frames (without derotating) before applying the ' 'MCMCsamplingModule.') im_shape = image_size_port(self.m_image_in_port) if self.m_mask[0] is not None: self.m_mask[0] /= pixscale if self.m_mask[1] is not None: self.m_mask[1] /= pixscale # create the mask and get the unmasked image indices mask = create_mask(im_shape[-2:], self.m_mask) indices = np.where(mask.reshape(-1) != 0.)[0] self.aperture_dict(images) initial = np.zeros((self.m_nwalkers, ndim)) initial[:, 0] = self.m_param[0] + np.random.normal( 0, self.m_sigma[0], self.m_nwalkers) initial[:, 1] = self.m_param[1] + np.random.normal( 0, self.m_sigma[1], self.m_nwalkers) initial[:, 2] = self.m_param[2] + np.random.normal( 0, self.m_sigma[2], self.m_nwalkers) if self.m_variance == "gaussian": student_t = self.gaussian_noise(images * mask, psf, parang, self.m_aperture) variance = (self.m_variance, student_t) else: variance = (self.m_variance, None) sampler = emcee.EnsembleSampler( nwalkers=self.m_nwalkers, dim=ndim, lnpostfn=lnprob, a=self.m_scale, args=([ self.m_bounds, images, psf, mask, parang, self.m_psf_scaling, pixscale, self.m_pca_number, self.m_extra_rot, self.m_aperture, indices, self.m_prior, variance, self.m_residuals ]), threads=cpu) for i, _ in enumerate( sampler.sample(p0=initial, iterations=self.m_nsteps)): progress(i, self.m_nsteps, "Running MCMCsamplingModule...") sys.stdout.write("Running MCMCsamplingModule... [DONE]\n") sys.stdout.flush() self.m_chain_out_port.set_all(sampler.chain) history = "walkers = " + str(self.m_nwalkers) + ", steps = " + str( self.m_nsteps) self.m_chain_out_port.add_history_information("MCMCsamplingModule", history) self.m_chain_out_port.copy_attributes_from_input_port( self.m_image_in_port) self.m_chain_out_port.close_port() print("Mean acceptance fraction: {0:.3f}".format( np.mean(sampler.acceptance_fraction))) try: autocorr = emcee.autocorr.integrated_time(sampler.flatchain, low=10, high=None, step=1, c=10, full_output=False, axis=0, fast=False) print("Integrated autocorrelation time =", autocorr) except emcee.autocorr.AutocorrError: print( "The chain is too short to reliably estimate the autocorrelation time. [WARNING]" )
def run(self): """ Run method of the module. An artificial planet is injected (based on the noise level) at a given separation and position angle. The amount of self-subtraction is then determined and the contrast limit is calculated for a given sigma level or false positive fraction. A correction for small sample statistics is applied for both cases. Note that if the sigma level is fixed, the false positive fraction changes with separation, following the Student's t-distribution (see Mawet et al. 2014 for details). Returns ------- NoneType None """ images = self.m_image_in_port.get_all() psf = self.m_psf_in_port.get_all() if psf.shape[0] != 1 and psf.shape[0] != images.shape[0]: raise ValueError('The number of frames in psf_in_tag {0} does not match with the ' 'number of frames in image_in_tag {1}. The DerotateAndStackModule can ' 'be used to average the PSF frames (without derotating) before ' 'applying the ContrastCurveModule.'.format(psf.shape, images.shape)) cpu = self._m_config_port.get_attribute("CPU") parang = self.m_image_in_port.get_attribute("PARANG") pixscale = self.m_image_in_port.get_attribute("PIXSCALE") if self.m_cent_size is not None: self.m_cent_size /= pixscale if self.m_edge_size is not None: self.m_edge_size /= pixscale self.m_aperture /= pixscale pos_r = np.arange(self.m_separation[0]/pixscale, self.m_separation[1]/pixscale, self.m_separation[2]/pixscale) pos_t = np.arange(self.m_angle[0]+self.m_extra_rot, self.m_angle[1]+self.m_extra_rot, self.m_angle[2]) if self.m_cent_size is None: index_del = np.argwhere(pos_r-self.m_aperture <= 0.) else: index_del = np.argwhere(pos_r-self.m_aperture <= self.m_cent_size) pos_r = np.delete(pos_r, index_del) if self.m_edge_size is None or self.m_edge_size > images.shape[1]/2.: index_del = np.argwhere(pos_r+self.m_aperture >= images.shape[1]/2.) else: index_del = np.argwhere(pos_r+self.m_aperture >= self.m_edge_size) pos_r = np.delete(pos_r, index_del) sys.stdout.write("Running ContrastCurveModule...\r") sys.stdout.flush() positions = [] for sep in pos_r: for ang in pos_t: positions.append((sep, ang)) # Create a queue object which will contain the results queue = mp.Queue() result = [] jobs = [] working_place = self._m_config_port.get_attribute("WORKING_PLACE") # Create temporary files tmp_im_str = os.path.join(working_place, "tmp_images.npy") tmp_psf_str = os.path.join(working_place, "tmp_psf.npy") np.save(tmp_im_str, images) np.save(tmp_psf_str, psf) mask = create_mask(images.shape[-2:], [self.m_cent_size, self.m_edge_size]) _, im_res = pca_psf_subtraction(images=images*mask, angles=-1.*parang+self.m_extra_rot, pca_number=self.m_pca_number) noise = combine_residuals(method=self.m_residuals, res_rot=im_res) for i, pos in enumerate(positions): process = mp.Process(target=contrast_limit, args=(tmp_im_str, tmp_psf_str, noise, mask, parang, self.m_psf_scaling, self.m_extra_rot, self.m_pca_number, self.m_threshold, self.m_aperture, self.m_residuals, self.m_snr_inject, pos, queue), name=(str(os.path.basename(__file__)) + '_radius=' + str(np.round(pos[0]*pixscale, 1)) + '_angle=' + str(np.round(pos[1], 1)))) jobs.append(process) for i, job in enumerate(jobs): job.start() if (i+1)%cpu == 0: # Start *cpu* number of processes. Wait for them to finish and start again *cpu* # number of processes. for k in jobs[i+1-cpu:(i+1)]: k.join() elif (i+1) == len(jobs) and (i+1)%cpu != 0: # Wait for the last processes to finish if number of processes is not a multiple # of *cpu* for k in jobs[(i + 1 - (i+1)%cpu):]: k.join() progress(i, len(jobs), "Running ContrastCurveModule...") # Send termination sentinel to queue queue.put(None) while True: item = queue.get() if item is None: break else: result.append(item) os.remove(tmp_im_str) os.remove(tmp_psf_str) result = np.asarray(result) # Sort the results first by separation and then by angle indices = np.lexsort((result[:, 1], result[:, 0])) result = result[indices] result = result.reshape((pos_r.size, pos_t.size, 4)) mag_mean = np.nanmean(result, axis=1)[:, 2] mag_var = np.nanvar(result, axis=1)[:, 2] res_fpf = result[:, 0, 3] limits = np.column_stack((pos_r*pixscale, mag_mean, mag_var, res_fpf)) self.m_contrast_out_port.set_all(limits, data_dim=2) sys.stdout.write("\rRunning ContrastCurveModule... [DONE]\n") sys.stdout.flush() history = str(self.m_threshold[0])+" = "+str(self.m_threshold[1]) self.m_contrast_out_port.add_history("ContrastCurveModule", history) self.m_contrast_out_port.copy_attributes(self.m_image_in_port) self.m_contrast_out_port.close_port()