def plot_apertures(self, ax, offset=9, wcs=None): if wcs: cpix = self._ref_centroids_sky.to_pixel(wcs) xlim = ax.get_xlim() ylim = ax.get_ylim() if self._apertures_obj is not None: if wcs: apertures_obj = [ CircularAperture(cpix, r) for r in self.aperture_radii ] else: apertures_obj = self._apertures_obj [apt.plot(ax=ax, alpha=0.25) for apt in apertures_obj] for istar, (x, y) in enumerate(apertures_obj[0].positions): if (xlim[0] <= x <= xlim[1]) and (ylim[0] <= y <= ylim[1]): yoffset = offset if y < ylim[1] - 10 else -offset ax.text(x + offset, y + yoffset, istar) if self._apertures_sky is not None: if wcs: apertures_sky = CircularAnnulus(cpix, self.aperture_radii[-1], self.aperture_radii[-1] + 15) else: apertures_sky = self._apertures_sky apertures_sky.plot(ax=ax, alpha=0.25)
def ap_phot(sources, data, source_r=6., sky_in=15, sky_out=20, plot=False): global fig centroids = (sources['xcentroid'], sources['ycentroid']) source_aperture = CircularAperture(centroids, r=source_r) source_area = source_aperture.area() source_table = aperture_photometry(data, source_aperture) sky_aperture = CircularAnnulus(centroids, r_in=sky_in, r_out=sky_out) sky_area = sky_aperture.area() sky_table = aperture_photometry(data, sky_aperture) sky_subtracted_source = deepcopy(source_table) for i in range(np.shape(centroids)[1]): sky_value = sky_table[i][3] sky_per_pix = sky_value / sky_area sky_behind_source = sky_per_pix * source_area sky_subtracted_source[i][3] -= sky_behind_source if plot: fig = plt.figure(figsize=(17, 17)) plt.imshow(data, cmap='gray', origin='lower', vmin=0, vmax=1500) for i in range(np.shape(centroids)[1]): plt.annotate(str(source_table[i][0]), xy=(np.float64(source_table[i][1]) + 15., np.float64(source_table[i][2]) + 15.), color="white") source_aperture.plot(color='blue', lw=1.5, alpha=0.5) sky_aperture.plot(color="orange", lw=0.5, alpha=0.5) plt.tight_layout() plt.show() return sky_subtracted_source
def ref2image(data,positions,aper,fig_name): apertures = CircularAperture(positions, r=aper[0]) annulus_apertures = CircularAnnulus(positions, r_in=aper[1], r_out=aper[2]) fig = plt.figure(figsize=(20,20));fig.add_subplot(111) apertures.plot(color='blue',lw=2,alpha=1) annulus_apertures.plot(color='red',lw=2,alpha=0.5) norm = ImageNormalize(stretch=HistEqStretch(data)) plt.imshow(data, cmap='Greys', origin='lower', norm=norm) for i in range(len(positions)): plt.text(positions[i][0]+10,positions[i][1]+10,str(i+1),fontdict={'size':'50','color':'blue'}) plt.savefig(fig_name,dpi=150) plt.show()
def imageshow(data,positions,aper=[8,12,20],rim_size=50): min_x=int(np.min(positions.T[0]))-rim_size;max_x=int(np.max(positions.T[0]))+rim_size min_y=int(np.min(positions.T[1]))-rim_size;max_y=int(np.max(positions.T[1]))+rim_size data=data[min_y:max_y,min_x:max_x] positions=positions-np.array([min_x,min_y]) apertures = CircularAperture(positions, r=aper[0]) annulus_apertures = CircularAnnulus(positions, r_in=aper[1], r_out=aper[2]) fig = plt.figure(figsize=(20,20));fig.add_subplot(111) apertures.plot(color='blue',lw=2,alpha=1) annulus_apertures.plot(color='red',lw=2,alpha=0.5) norm = ImageNormalize(stretch=HistEqStretch(data)) plt.imshow(data, cmap='Greys', origin='lower', norm=norm) plt.show()
def aper_phot(img_data,positions, r = 10., r_in = 14., r_out = 18,bkg_sub=False,plot=False): """ :params: r: Aperture Radius :params: r_in: annulus aperture inside radius :params: r_out: annulus aperture outside radius :params: bkg_sub: True if background subtraction is needed :params: plot: True to plot :out: phot_table: Table with the values of the aperture photometry """ #Background subtraction if bkg_sub == True: sigma_clip = SigmaClip(sigma=3., iters=10) bkg_estimator = MedianBackground() bkg = Background2D(img_data, (50, 50), filter_size=(3, 3),sigma_clip=sigma_clip, bkg_estimator=bkg_estimator) data_sub = img_data - bkg.background else: data_sub = img_data #Aperture Photometry using a circular aperture and a circular annulus apertures = CircularAperture(positions, r=r) annulus_apertures = CircularAnnulus(positions,r_in = r_in,r_out = r_out) apers = [apertures,annulus_apertures] phot_table = aperture_photometry(data_sub, apers) bkg_mean = phot_table['aperture_sum_1'] / annulus_apertures.area() bkg_sum = bkg_mean * apertures.area() final_sum = phot_table['aperture_sum_0'] - bkg_sum phot_table['residual_aperture_sum'] = final_sum positions = np.array(positions) if plot == True: #Ploting norm = ImageNormalize(data_sub, interval=ZScaleInterval(),stretch=LinearStretch()) plt.imshow(data_sub, cmap='Greys', origin='lower',norm=norm) apertures.plot(color='blue', lw=1.5, alpha=0.5) annulus_apertures.plot(color='green',lw=1.5,alpha=0.5) plt.plot(positions[:,0],positions[:,1], ls='none', color = 'red', marker='.', ms=10, lw=1.5) plt.xlim(0, data_sub.shape[1]-1) plt.ylim(0, data_sub.shape[0]-1) plt.show() return phot_table
class AnnSky: def __init__(self, data, coord, r_in, r_out, method, subpixels=None): from photutils import CircularAnnulus self.ap = CircularAnnulus(coord, r_in=r_in, r_out=r_out) self.sec_data = SecImData(self.ap, data, method=method, subpixels=subpixels) # media sigma clip def stat(self, sigma_clip): from astropy.stats import sigma_clipped_stats mean, median, stddev = sigma_clipped_stats(self.sec_data, sigma=sigma_clip) return mean, median, stddev def plot(self, color='C0', ls='solid', lw=1): self.ap.plot(color=color, ls=ls, lw=lw)
def showStars(image): stars = findStars(image) # take just the 5 brightest objects stars.sort('flux') stars.reverse() list = stars[:3] positions = zip(list['xcentroid'], list['ycentroid']) apertures = CircularAperture(positions, r=6) annuli = CircularAnnulus(positions, r_in=18, r_out=24) phot_table = aperture_photometry(image, [apertures, annuli]) plt.clf() plt.imshow(image, cmap='gray', norm=LogNorm()) annuli.plot(color='green', lw=1, alpha=0.75) apertures.plot(color='blue', lw=1, alpha=0.75) plt.savefig('%s.png' % path, dpi=300) print list print phot_table
def photometryimg(positions, img, i): positionslist = positions.tolist() aperture = CircularAperture(positionslist, r=5) #2*FWHM annulus_aperture = CircularAnnulus(positionslist, r_in=7, r_out=9)#4-5*FWHM+2*FWHM apers = [aperture, annulus_aperture] displayimage(img, 1, i) ###画图1 aperture.plot(color='blue', lw=0.5) annulus_aperture.plot(color='red', lw=0.2) plt.pause(0.001) plt.clf() phot_table = aperture_photometry(img, apers) bkg_mean = phot_table['aperture_sum_1'] / annulus_aperture.area bkg_sum = bkg_mean * aperture.area final_sum = phot_table['aperture_sum_0'] - bkg_sum phot_table['residual_aperture_sum'] = final_sum posflux = np.column_stack((positions, phot_table['residual_aperture_sum'])) #return posflux magstar = 25 - 2.5*np.log10(abs(final_sum/1)) return posflux,magstar
def CircleMaskPhometry(data, location, index=2): Mimgdata = np.copy(data) Mlocatin = np.copy(location) Mapeture = CircularAperture(Mlocatin, r=6) Mannuals = CircularAnnulus(Mlocatin, r_in=8., r_out=11.) Eannuals_masks = Mannuals.to_mask(method='center') bkg_median = [] for mask in Eannuals_masks: Eannuals_data = mask.multiply(Mimgdata) Eannulus_data_1d = Eannuals_data[mask.data > 0] meandata, median_sigclip, _ = sigma_clipped_stats(Eannulus_data_1d) bkg_median.append(median_sigclip) bkg_median = np.array(bkg_median) phot = aperture_photometry(Mimgdata, Mapeture) phot['annulus_median'] = bkg_median phot['aper_bkg'] = bkg_median * Mapeture.area phot['aper_sum_bkgsub'] = phot['aperture_sum'] - phot['aper_bkg'] Mpositionflux = np.transpose( (phot['xcenter'], phot['ycenter'], phot['aper_sum_bkgsub'])) displayimage(Mimgdata, 3, index) Mapeture.plot(color='blue', lw=0.5) Mannuals.plot(color='red', lw=0.2) plt.pause(0.001) plt.clf() #Mannulus_data = Eannuals_masks[0].multiply(Mimgdata) #displayimage(Mannulus_data,3,index+1) flux_sum = phot['aper_sum_bkgsub'] magstar = 25 - 2.5 * np.log10(abs(flux_sum / 1)) return Mpositionflux, magstar
def run_counting(self): if self.images: self.find_sources_in_all_images() self.do_aperture_flux_measurement_in_all_images() print( "Choose the reference star on image that popped on the screen!" ) print( "Use left mouse button. If you made a mistake simply click on correct star." ) print("Close the window or hit Enter to proceed...\n") image = self.images[0] positions = self.sources_positions[0] object_aperture = CircularAperture(positions, self.aperture_radius) background_annulus = CircularAnnulus(positions, self._irad_annulus, self._orad_annulus) self.ax.imshow(image, norm=colors.PowerNorm(gamma=0.5), cmap='PuBu_r') object_aperture.plot(color='white', lw=1) background_annulus.plot(color='white', lw=1) self.interactive = True plt.show() # Continue when image is closed threshold_flux = self.fluxes_in_all_images[0][ self.reference_star_idx] counts = self.count_stars(threshold_flux) for f, c in zip(self.file_names, counts): print("{:5d} counts in central circle of image : {}".format( c, f)) print("\nAverage number of stars in central circle = {:.1f}\n". format(np.mean(counts)))
def check_adjustments(self): if self.images: print("Current adjustments are:\n") print("Aperture radius = {} pix".format(self.aperture_radius)) print("FWHM = {} pix".format(self.fwhm)) print("Threshold = {:.2f}\n".format(self.threshold)) #------------------------------------------------------------------- image = self.images[0] positions = None if len(self.images) == len(self.sources_positions): positions = self.sources_positions[0] else: positions = self.get_positions(image) # ------------------------------------------------------------------ object_aperture = CircularAperture(positions, self.aperture_radius) background_annulus = CircularAnnulus(positions, self._irad_annulus, self._orad_annulus) self.ax.imshow(image, norm=colors.PowerNorm(gamma=0.5), cmap='PuBu_r') object_aperture.plot(color='white', lw=1) background_annulus.plot(color='white', lw=1) self.interactive = False plt.show()
def find_zero_point(stack, fil, ext, catalog, data_path=None, show_fwhm=False, show_bkg=False, show_contours=False, r_annulus_in=3.5, r_annulus_out=4.5, log=None): ''' Function used to find the FWHM, zero point, and zero point error for a specific stack and filter. Initially finds all sources in specified catalog within the image and matches them to a 10+ sigma source in the image. Then makes a series of cuts to determine the standard stars to use when finding the zero point: * Only considers sources with a center count (in adu / pix / sec) between 100 and 300 - Plots radial profiles of these sources - Finds the median FWHM of the image from these sources (returns this value) * Removes sources too close to the edge * Removes sources that are too close to each other * Removes oblong sources (most likely galaxies or two very close stars) Checks isophotes of remaining sources to determine if the stack is aligned correctly. * Shows the user a sample cutout of a source that reflects the isophotes of the stack as a whole If not, then allows the user the option of checking them individual images and possibly remaking the stack with `revisit_stack` before continuing. If ``show_bkg`` is True, shows the remaining sources to be used when finding the zero point, color coded by the median background level. Finds the instrumental magnitudes of the remaining sources and compares to the magnitudes from the catalog and determines the zero point and zero point error. Returns these values along with the FWHM of the image. Parameters ---------- :param stack: str Name of the stack to use (include path to the stack). :param fil: str Name of the filter of the stack. :param ext: int Extension to use (only used if `revisit_stack` is called). :param catalog: str Name of catalog to query for magnitudes. :param data_path: str, optional Path to the original data (only used if `revisit_stack` is called). Default is ``None``. If ``None`` and `revisit_stack` is called, error will occur. :param show_fwhm: boolean, optional Set to ``True`` to view the radial profiles when finding the FWHM of the image. Default is ``False``. :param show_bkg: boolean, optional Set to ``True`` to view the final sources before finding the zero point, color coded by the median background level. Default is ``False``. :param show_contours: boolean, optional Set to ``True`` to view the contours when examining the isophotes. Also shows isophotes in `revisit_stack`. Default is ``False``. :param r_annulus_in: int, optional Radius for the inner annulus when performing photometry on the final sources to determine their instrumental magnitude. Default is ``3.5``. :param r_annulus_out: int, optional Radius for the oter annulus when performing photometry on the final sources to determine their instrumental magnitude. Default is ``4.5``. :param log: In-depth log. If no log is inputted, information is printed out instead of being written to ``log``. Default is ``None``. Returns ------- :return: float, float, float First float is the calculated zero point of the image. Second float is the calculated zero point error of the image. Third float is the calculated FWHM of the image. ''' if log is not None: log.info("Computing FWHM of sources in the stack %s" % stack) else: print("Computing FWHM of sources in the stack %s" % stack) with fits.open(stack) as hdr: header, data = hdr[0].header, hdr[0].data w = wcs.WCS(header) # Center in pixel coordinates, change to real coordinates real_coords = w.wcs_pix2world( np.array([[header['NAXIS1'] / 2, header['NAXIS2'] / 2]], np.float), 1) coords = SkyCoord(real_coords[0][0] * u.deg, real_coords[0][1] * u.deg, frame='fk5') Vizier.ROW_LIMIT = -1 # So that you get all the rows, not just the first 50 # Information about the sources from catalog found in the extension (full area, not radially) cat_ID, cat_ra, cat_dec, cat_mag, cat_err = find_catalog(catalog, fil) cat = Vizier.query_region(coords, width=13.65 * u.arcmin, catalog=cat_ID) if len(cat) == 0: log.info('No catalog sources at input coordinates! Exiting...') sys.exit() else: cat = cat[0] mask = (~cat[cat_ra].mask) & (~cat[cat_dec].mask) &\ (~cat[cat_mag].mask) & (~cat[cat_err].mask) cat = cat[mask] # Find the sources above 10 std in the image _, median, std = sigma_clipped_stats(data, sigma=3.0) daofind = DAOStarFinder( fwhm=7, threshold=10. * std) # Looking for best sources, so raise threshold way up dao_sources = daofind( np.asarray(data)) # DAOStarFinder sources == dao_sources if log is not None: log.info( "{0} objects found in {1} and {2} sources above 10 std found in the stack" .format(len(cat), catalog, len(dao_sources))) log.info( "DAOStarFinder found {0} sources that are 10 std above the background" .format(len(dao_sources))) else: print( "{0} objects found in {1} and {2} sources above 10 std found in the stack" .format(len(cat), catalog, len(dao_sources))) print( "DAOStarFinder found {0} sources that are 10 std above the background" .format(len(dao_sources))) ra_dao = [ ] # Change pixel coordinates to real coordinates for DAOStarFinder sources dec_dao = [] for i, j in enumerate(dao_sources): pixel_coords = np.array([[j['xcentroid'], j['ycentroid']]], np.float) dao_coords = w.wcs_pix2world(pixel_coords, 1) ra_dao.append(dao_coords[0][0]) dec_dao.append(dao_coords[0][1]) # Coordinates need to be in real, not pixel coords_dao = SkyCoord(ra_dao * u.deg, dec_dao * u.deg, frame='fk5') # Coordinates from sources coords_cat = SkyCoord(cat[cat_ra], cat[cat_dec], frame='fk5') # Coordinates from catalog # Match each catalog source to the closest dao_source idx, d2, d3 = coords_cat.match_to_catalog_sky( coords_dao) # Match smaller to larger if log is not None: log.info("{0} objects have been matched".format(len(idx))) else: print("{0} objects have been matched".format(len(idx))) # Next step: find the radial profiles from intermediate-brightness sources and then find the fwhm of the field step_size = 1 fwhm_orig = 4 coords = [ w.wcs_world2pix(np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0] for j in idx ] # Positions of DAO radii = np.arange(step_size, 2.5 * fwhm_orig, step_size) # Radii of apertures x_data = np.arange( 0, 2.5 * fwhm_orig, step_size) # Steps (starting at zero, to 2.5 * fwhm, by step_size) apers_area = [np.pi * (step_size**2)] # Circle area = pi*(r**2) for r in radii: apers_area.append(np.pi * ((r + step_size)**2 - r**2)) # Annulus area = pi*(r_out**2 - r_in**2) apertures = [CircularAperture(coords, r=step_size) ] # For circle aperture around center for r in radii: apertures.append(CircularAnnulus(coords, r_in=r, r_out=r + step_size)) # Annuli apertures phot_table = aperture_photometry( data, apertures ) # Each row is a source, with len(x_data) photometry measurements if show_fwhm: fig, ax = plt.subplots() sigmas = [] new_idx = [] for i, source in enumerate( phot_table ): # Find the sums per area (adu/pix/sec) for each of the sources sums = [source[i] / apers_area[i - 3] for i in range(3, len(source))] # Only consider moderately bright sources; too bright are saturated and too dim can have larger error values if 100 <= sums[0] <= 30000 and sums[len(sums) - 1] < 30000: try: # Try unless it takes too much time...if that happens, it's probably a bad fit and we don't want it w_fit = x_data f_fit = sums g, _ = curve_fit(fix_x0, w_fit, f_fit) if show_fwhm: # Add two plots: radial profile and Gaussian fit ax.plot(x_data, sums, 'o-', alpha=0.5, lw=2, markersize=4) # Plot the radial profiles ax.plot(x_data, fix_x0(x_data, *g), '^-', alpha=0.5, lw=2, markersize=4) sigmas.append( np.abs(g[1]) ) # Sigma can be either positive or negative; fix_x0 allows both new_idx.append( idx[i] ) # Append to a list that will be used later for future sources except RuntimeError: pass fwhm_values = [i * 2.35482 for i in sigmas] sigmas_post_clipping, _, _ = sigmaclip(sigmas, low=3, high=3) # Clip outlier sigma values med = np.median(sigmas_post_clipping) # Median sigma value fwhm = med * 2.35482 # FWHM to be used to find the zero point and make cuts if log is not None: log.info( "Median sigma: {0} Median FWHM: {1} Number of sources counted/total found in field: {2}/{3}" .format('%5.3f' % med, '%2.3f' % fwhm, len(sigmas_post_clipping), len(dao_sources))) else: print( "Median sigma: {0} Median FWHM: {1} Number of sources counted/total found in field: {2}/{3}" .format('%5.3f' % med, '%2.3f' % fwhm, len(sigmas_post_clipping), len(dao_sources))) clipped_fwhm_values = [i * 2.35482 for i in sigmas_post_clipping] if show_fwhm: ax.set_title("Radial profiles of sources 10 std above background" ) # Plot of radial profiles and Gaussian fits ax.set_xlabel("Radial distance from centroid of source (pixels)") ax.set_ylabel("Count (adu per second per pixel)") plt.show() fig, ax = plt.subplots( ) # Histogram of fwhm_values after sigma clipping plt.hist(clipped_fwhm_values, bins=30) ax.set_title("FWHM values for Gaussian-fit radial profiles") ax.set_xlabel("FWHM value for best-fit Gaussian (pixels)") plt.show() # Calculate the zero point if log is not None: log.info( "Computing the zero point of the stack {0} with filter {1} and FWHM of {2}" .format(stack, fil, '%2.3f' % fwhm)) else: print( "Computing the zero point of the stack {0} with filter {1} and FWHM of {2}" .format(stack, fil, '%2.3f' % fwhm)) orig_coords = [ w.wcs_world2pix(np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0] for j in new_idx ] # DAO pos # First cut: removing sources near the edge edge_error = 100 first_cut_coords, first_cut_idx, first_fwhm_values = [], [], [] for i, j in enumerate(orig_coords): if not (j[0] < edge_error or data.shape[0] - j[0] < edge_error or j[1] < edge_error or data.shape[0] - j[1] < edge_error): first_cut_coords.append(orig_coords[i]) first_cut_idx.append(new_idx[i]) first_fwhm_values.append(fwhm_values[i]) # Second cut: removing sources that are not isolated # 1: Make sure no two sources are within (r_annulus_out + r_annulus_in) * fwhm of each other # 2: If a second source is present in the annulus, the sigma clipping for the mask will cut out the additional # counts from these pixels so that the second source will be cut out and not interfere with the zero point # If the sources are close enough together, they will either be cut out here or will look oblong and be cut out next overlap = [] for i, j in enumerate(first_cut_coords): for k in range(i + 1, len(first_cut_coords)): # (r_ann_out + r_ann_in)*fwhm so that the annulus of one doesn't overlap the circular aperture of the other if np.sqrt((j[0] - first_cut_coords[k][0])**2 + (j[1] - first_cut_coords[k][1])**2) < \ (r_annulus_out + r_annulus_in) * fwhm: overlap.append(i) overlap.append(k) second_cut_coords, second_cut_idx, second_fwhm_values = [], [], [] for i, j in enumerate(first_cut_coords): if i not in overlap: second_cut_coords.append(j) second_cut_idx.append(first_cut_idx[i]) second_fwhm_values.append(first_fwhm_values[i]) # Third cut: removing sources that are not circular # Part 1: remove sources with a fwhm 1.2 * median fwhm of the field (1.2 determined by considering 5 stacks) third_cut_coords, third_cut_idx, third_fwhm_values = [], [], [] for i, j in enumerate(second_fwhm_values): if not (j > 1.2 * fwhm): third_cut_coords.append(second_cut_coords[i]) third_cut_idx.append(second_cut_idx[i]) third_fwhm_values.append(j) if log is not None: log.info( "Checking if the stack was performed correctly with {0} sources". format(len(third_cut_coords))) # Final check else: print("Checking if the stack was performed correctly with {0} sources". format(len(third_cut_coords))) # Final check d_x_y = 40 field_sigmas = cutouts_2d_gaussian(stack, third_cut_coords, d_x_y, fwhm, show_cutouts=show_contours) field_ratio = np.median(field_sigmas) if log is not None: log.info("Median field ratio (y_sigma/x_sigma): {0}".format( '%2.3f' % field_ratio)) else: print("Median field ratio (y_sigma/x_sigma): {0}".format('%2.3f' % field_ratio)) if not ((1 / 1.2) > field_ratio or 1.2 < field_ratio): if log is not None: log.info( "Good to go! Now calculating the zero point with {0} sources". format(len(third_cut_coords))) else: print( "Good to go! Now calculating the zero point with {0} sources". format(len(third_cut_coords))) else: # Stack is slightly off. Check to see what the issue is if log is not None: log.info("Stack seems to be off. Sample cutout shown") else: print("Stack seems to be off. Sample cutout shown") if show_contours: for i, j in enumerate(third_cut_coords): d = data[int(j[1]) - d_x_y:int(j[1]) + d_x_y, int(j[0]) - d_x_y:int(j[0]) + d_x_y] # Cutout of source x, y = np.meshgrid( np.linspace(0, np.shape(d)[1] - 1, np.shape(d)[1]), np.linspace(0, np.shape(d)[0] - 1, np.shape(d)[0])) initial_guess = (100, d_x_y, d_x_y, fwhm / 2.35482, fwhm / 2.35482, 0, 0 ) # Initial guess is IMPORTANT popt, pcov = curve_fit(twoD_Gaussian, (x, y), d.ravel(), p0=initial_guess) # Fit of 2D Gaussian if np.abs((popt[4] / popt[3]) - field_ratio) < 0.01: if log is not None: log.info( 'x sigma = {0} y sigma = {1} ratio = {2}'.format( '%7.3f' % popt[3], '%7.3f' % popt[4], '%7.3f' % (popt[4] / popt[3]))) else: print('x sigma = {0} y sigma = {1} ratio = {2}'.format( '%7.3f' % popt[3], '%7.3f' % popt[4], '%7.3f' % (popt[4] / popt[3]))) fitted_data = twoD_Gaussian((x, y), *popt) contours = np.arange(np.min(d), np.max(d), 30.) norm = simple_norm(data, 'sqrt', percent=99) plt.imshow(d, norm=norm) plt.colorbar() plt.contour(x, y, fitted_data.reshape(np.shape(d)), contours, colors='w') plt.show() break # Breaks out of loop if input( "Would you like to revisit the stack and look at isophotes within it? Type 'yes' or 'no': " ) == "yes": try: zp, zp_err, fwhm = revisit_stack(stack, fil, ext, third_cut_coords, fwhm, show_fwhm, show_bkg, show_contours, data_path, log) return zp, zp_err, fwhm except TypeError: pass elif log is not None: log.info( "User decided not to remake the stack even though isophotes are not circular" ) final_coords, final_idx = third_cut_coords, third_cut_idx # Final coordinates and idx to use # Annuli of DAO sources annulus_apertures = CircularAnnulus(final_coords, r_in=fwhm * r_annulus_in, r_out=fwhm * r_annulus_out) annulus_masks = annulus_apertures.to_mask( method='center') # Create masks to highlight pixels in annuli bkg_median = [] for mask in annulus_masks: annulus_data = mask.multiply(data) annulus_data_1d = annulus_data[mask.data > 0] _, median_sigclip, stddev = sigma_clipped_stats(annulus_data_1d) bkg_median.append(median_sigclip) bkg_median = np.array(bkg_median) if show_bkg: # Finds the sources for each upper median background limit and plots them over the image groupings = [[], [], [], [], [], [], [], []] for i, j in enumerate(bkg_median): if j < 0: groupings[0].append(i) elif j < 0.1: groupings[1].append(i) elif j < 0.25: groupings[2].append(i) elif j < 0.5: groupings[3].append(i) elif j < 0.75: groupings[4].append(i) elif j < 1: groupings[5].append(i) elif j < 2.5: groupings[6].append(i) else: groupings[7].append(i) positions = [[], [], [], [], [], [], [], []] for i, j in enumerate(final_idx): if i in groupings[0]: # Positions of DAO positions[0].append( w.wcs_world2pix( np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0]) elif i in groupings[1]: positions[1].append( w.wcs_world2pix( np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0]) elif i in groupings[2]: positions[2].append( w.wcs_world2pix( np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0]) elif i in groupings[3]: positions[3].append( w.wcs_world2pix( np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0]) elif i in groupings[4]: positions[4].append( w.wcs_world2pix( np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0]) elif i in groupings[5]: positions[5].append( w.wcs_world2pix( np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0]) elif i in groupings[6]: positions[6].append( w.wcs_world2pix( np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0]) elif i in groupings[7]: positions[7].append( w.wcs_world2pix( np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0]) colors = [ 'w', 'r', 'orange', 'lime', 'deepskyblue', 'b', 'purple', 'k' ] bkg_med_list = [] for i, j in enumerate(positions): if len(positions[i]) != 0: apertures = CircularAperture( positions[i], 2.5 * fwhm) # Aperture to perform photometry # Annuli of DAO sources annulus_apertures = CircularAnnulus(positions[i], r_in=fwhm * r_annulus_in, r_out=fwhm * r_annulus_out) annulus_masks = annulus_apertures.to_mask( method='center' ) # Create masks to highlight pixels in annuli bkg_median_1 = [] for mask in annulus_masks: annulus_data = mask.multiply(data) annulus_data_1d = annulus_data[mask.data > 0] _, median_sigclip, stddev = sigma_clipped_stats( annulus_data_1d) bkg_median_1.append(median_sigclip) bkg_median_1 = np.array(bkg_median_1) bkg_med_list.append(bkg_median_1) apertures.plot(color='white', lw=2) annulus_apertures.plot(color=colors[i], lw=2) if log is not None: log.info("Plotting the sources by median background value") log.info( "Number of sources per list (<0, 0.1, 0.25, 0.5, 0.75, 1, 2.5, >2.5)" ) else: print("Plotting the sources by median background value") print( "Number of sources per list (<0, 0.1, 0.25, 0.5, 0.75, 1, 2.5, >2.5)" ) for i in range(len(bkg_med_list)): if log is not None: log.info(len(bkg_med_list[i])) else: print(len(bkg_med_list[i])) norm = simple_norm(data, 'sqrt', percent=99) plt.suptitle( 'w < 0 | r < 0.1 | o < 0.25 | g < 0.5 | lb < 0.75 | db < 1 | p < 2.5 | b > 2.5' ) plt.imshow(data, norm=norm) plt.colorbar() plt.show( ) # Plot of matched sources with annuli, color coded by median background value in annulus mag_cat = [] # Moving past show_bkg magerr_cat = [] for i, j in enumerate(final_idx): mag_cat.append( cat[i][cat_mag]) # Reference magnitudes of catalog objects magerr_cat.append(cat[i][cat_err]) final_apertures = CircularAperture(final_coords, 2.5 * fwhm) # Aperture to perform photometry # Annuli of DAO sources final_ann_aps = CircularAnnulus(final_coords, r_in=fwhm * r_annulus_in, r_out=fwhm * r_annulus_out) final_ann_masks = final_ann_aps.to_mask( method='center') # Create masks to highlight pixels in annuli final_bkg_median = [] for mask in final_ann_masks: # For each masked annulus, find the median background value and add to list annulus_data = mask.multiply(data) annulus_data_1d = annulus_data[mask.data > 0] _, median_sigclip, stddev = sigma_clipped_stats(annulus_data_1d) final_bkg_median.append(median_sigclip) final_bkg_median = np.array(final_bkg_median) sigma_clip = SigmaClip( sigma=3) # This section is to find the error on the background bkg_estimator = MedianBackground() bkg = Background2D(data, (120, 120), filter_size=(3, 3), sigma_clip=sigma_clip, bkg_estimator=bkg_estimator) error = calc_total_error(data, bkg.background_rms, 1) phot_table = aperture_photometry( data, final_apertures, error=error) # Photometry, error accounted for bkg_aper = final_bkg_median * final_apertures.area flux = np.asarray(phot_table['aperture_sum']) - bkg_aper fluxerr = np.sqrt(np.asarray(phot_table['aperture_sum_err'])**2 + bkg_aper) ap = absphot.absphot() zpt, zpterr = ap.zpt_iteration(flux, fluxerr, mag_cat, magerr_cat) print(zpt, zpterr) mag_inst = -2.5 * np.log10( (np.asarray(phot_table['aperture_sum']) - bkg_aper)) # Instrumental magnitudes (and error) # Find zero point if log is not None: log.info( 'Calculating zero point from {0} stars with sigma clipping and a maximum std error of 0.1.' .format(len(mag_inst))) else: print( 'Calculating zero point from {0} stars with sigma clipping and a maximum std error of 0.1.' .format(len(mag_inst))) for i in np.flip(np.linspace(1, 3, num=10)): _, zp, zp_err = sigma_clipped_stats(mag_cat - mag_inst, sigma=i) if zp_err < 0.1: break if log is not None: log.info('zp = {0} +/- {1}'.format('%2.3f' % zp, '%2.3f' % zp_err)) else: print('zp = {0} +/- {1}'.format('%2.3f' % zp, '%2.3f' % zp_err)) # Online zp (vega): https://www.ukirt.hawaii.edu/instruments/wfcam/user_guide/performance.html return zp, zp_err, fwhm
""" 2.5 APERTURE AND ANNULUS DATA ---------------------------------""" # Requires object pixel coordinates and radii of aperture and annulus aperture = CircularAperture(obj_loc, r=ap_rad) annulus = CircularAnnulus(obj_loc, r_in=an_in, r_out=an_out) """ 2.6 DISPLAY THE APERTURE ON THE IMAGE TO CHECK CENTERING ------""" if images == True: # DISPLAY INDIVIDUAL TARGETS for im_plot in range(0, len(obj_loc)): plt.figure(figsize=(15,15)) plt.imshow(imgdta, cmap='nipy_spectral') aperture.plot(color='white', lw=2) annulus.plot(color='red', lw=2) xcent = int(obj_loc[im_plot][0]) ycent = int(obj_loc[im_plot][1]) plt.xlim(xcent-15, xcent+15) plt.ylim(ycent-15, ycent+15) # DISPLAY WHOLE REGION OF AG PEG AND STANDARD STARS plt.figure(figsize=(20, 20)) plt.imshow(imgdta, cmap='nipy_spectral') aperture.plot(color='white', lw=2) annulus.plot(color='red', lw=2) """ 2.7 OBJECT MAGNITUDE WITH RESPECT TO EACH STANDARD STAR """ # Counts of object and standard stars with associated errors. star_cts = counts(imgdta, aperture, annulus, 5)
def do_aperture_photometry(filename): fwhm,filename=iraf_fwhm() xpix,ypix=source_list(filename) #ast=AstrometryNet() #ast.api_key= 'iqmqwvazpvolmjmn' data,header=fits.getdata(filename,header=True) exposure_time=header['EXPOSURE'] sigma_clip = SigmaClip(sigma=3., maxiters=10) bkg_estimator = MedianBackground() bkg = Background2D(data, (10,10), filter_size=(3, 3),sigma_clip=sigma_clip, bkg_estimator=bkg_estimator) back=bkg.background # this is the background we need for the background subtraction. back2=np.median(bkg.background) mask = data == 0 unit = u.electron / u.s xdf_image = CCDData(data, unit=unit, meta=header, mask=mask) norm_image = ImageNormalize(vmin=1e-4, vmax=5e-2, stretch=LogStretch(), clip=False) xdf_image_clipped = np.clip(xdf_image, 1e-4, None) mean, median, std = sigma_clipped_stats(xdf_image.data, sigma=3.0, maxiters=20, mask=xdf_image.mask) print('Finding the sources') #daofind = DAOStarFinder(fwhm=fwhm, threshold=5*std) # 3 sigma above the background. #sources = daofind(data - median) #sources_findpeaks = find_peaks(xdf_image.data, mask=xdf_image.mask, threshold=30.*std, box_size=30, centroid_func=centroid_2dg) #print('We have found:',len(sources),' sources') #print(sources) #print(sources['xcentroid'], sources['ycentroid'],sources['fwhm']) #positions=sources['xcentroid'], sources['ycentroid'] positions=np.genfromtxt('co_ordinates_list_1.txt',unpack=True,usecols=(0,1)) #print(positions) radii=[ fwhm,2*fwhm, 4*fwhm, 6*fwhm] #positions=(sources['xcentroid'], sources['ycentroid']) apertures = [CircularAperture(positions, r=r) for r in radii] an_ap = CircularAnnulus(positions, r_in=8*fwhm, r_out=10*fwhm) #apers = [apertures, annulus_apertures] #bkg_sigma=mad_std(data) effective_gain=exposure_time error=calc_total_error(data,back,effective_gain) #error=0.1*data phot_table = aperture_photometry(data, apertures,error=error) phot_table2=aperture_photometry(data,an_ap) bkg_mean = phot_table2['aperture_sum'] / an_ap.area() bkg_sum = bkg_mean * an_ap.area() final_sum0=phot_table['aperture_sum_0']-bkg_sum final_sum1=phot_table['aperture_sum_1']-bkg_sum final_sum2=phot_table['aperture_sum_2']-bkg_sum final_sum3=phot_table['aperture_sum_3']-bkg_sum mag_0=-2.5*np.log10(final_sum0/exposure_time)+25 mag_1=-2.5*np.log10(final_sum1/exposure_time)+25 mag_2=-2.5*np.log10(final_sum2/exposure_time)+25 mag_3=-2.5*np.log10(final_sum3/exposure_time)+25 fig=plt.figure() plt.imshow(data,cmap='gray',origin='lower',vmin=50,vmax=400) colors=['red','green','yellow','blue'] for i in range(len(apertures)): apertures[i].plot(color=colors[i], alpha=0.7) an_ap.plot(color='green', alpha=0.7) plt.show() for i in range (len(phot_table)): print(mag_0[i],mag_1[i],mag_2[i],mag_3[i]) ''' mag=-2.5*np.log10(final_sum/30)+25 flux=final_sum flux_err=phot_table['aperture_sum_err_0'] mag_err=1.09*flux_err/flux x=[phot.value for phot in phot_table['xcenter']] y=[phot.value for phot in phot_table['ycenter']] #with open('result.dat', 'w') as f: #with open('out.txt', 'w') as f: for i in range(len(x)): print(x[i],y[i],'\t',mag[i],mag_err[i]) outfile=' ' for i in range (len(phot_table)): outfile+=x[i]+ " "+ y[i]+" "+ mag[i]+" " +mag_err[i] outfile+='\n' out=open('result.txt','w') out.write(outfile,overwrite=True) out.close() ''' '''
def measure_single_flux(img, positions, aperture_size, model='Moffat', alpha=None, gamma=None, fwhm=None, bkg=True, plot=False): '''Measure the flux for a single object The Background is subtracted from an annulus. No error is reported Parameters ---------- img : ndarray array with the image data position : tuple position (x,y) of the source aperture_size : float aperture size in units of FWHM alpha : float power index of the Moffat gamma : float other parameter for the Moffat bkg : bool determines if the background is subtracted ''' if model == 'Moffat': fwhm = 2 * gamma * np.sqrt(2**(1 / alpha) - 1) r = aperture_size * (fwhm) / 2 aperture = CircularAperture(positions, r=r) phot = aperture_photometry(img, aperture) r_in = 4 * fwhm / 2 r_out = np.sqrt(3 * r**2 + r_in**2) annulus_aperture = CircularAnnulus(positions, r_in=r_in, r_out=r_out) mask = annulus_aperture.to_mask(method='center') annulus_data = mask.multiply(img) annulus_data_1d = annulus_data[mask.data > 0] _, median_sigclip, _ = sigma_clipped_stats( annulus_data_1d[~np.isnan(annulus_data_1d)], sigma=3, maxiters=1) phot['bkg_median'] = np.array(median_sigclip) phot['bkg_local'] = phot['bkg_median'] * aperture.area if bkg: phot['flux'] = phot['aperture_sum'] - phot['bkg_local'] else: phot['flux'] = phot['aperture_sum'] if model == 'Moffat': correction = light_in_moffat(r, alpha, gamma) elif model == 'Gaussian': correction = light_in_gaussian(r, fwhm) else: raise ValueError(f'unkown model {model}') phot['flux'] /= correction if plot: from astropy.visualization import simple_norm norm = simple_norm(img, 'sqrt', percent=99) plt.imshow(img, norm=norm) aperture.plot(color='orange', lw=2) annulus_aperture.plot(color='red', lw=2) plt.show() #return phot return phot['flux'][0]
annuli.plot(color='blue', lw=2) plt.imshow(quadRU_data, origin='lower', norm=norm, cmap='BrBG', clim=(0, 1000)) plt.show() plt.clf() ##use CircularAnnulus annuli_c = CircularAnnulus(star_coords, r_in=5.5, r_out=7) print("Circular Annuli", annuli_c) phot_table_circ = aperture_photometry(quadRU_data, annuli_c, method='exact') print("phot_table_circ", phot_table_circ) if show_images: annuli_c.plot(color='blue', lw=2) plt.imshow(quadRU_data, origin='lower', norm=norm, cmap='BrBG', clim=(0, 1000)) plt.show() plt.clf() ##background subtraction ##ellip annuli background subtraction apers = [apertures, annuli, annuli_c] phot_table = aperture_photometry(quadRU_data, apers) background_mean_ellip = phot_table['aperture_sum_1'] / annuli.area() background_sum_ellip = background_mean_ellip * apertures.area()
def measure_sensit(fin=None, ext=0, im=None, pos=None, std=True, rmax=11, sky_rin=30, sky_rout=40, flux=None, exptime=None, head=None, showplot=True, verbose=False): """ comute the maximum S/N of an image in a similar way as the ESO pipeline does """ if fin != None: hdu = fits.open(fin) im = hdu[ext].data if head == None: head = hdu[ext].header hdu.close() # --- position to measure the S/N if pos == None: pos = 0.5 * np.asarray(im.shape) # print(pos) # from aper import aper # --- define circular apertures from 1 px to rmax px aperrads = np.arange(1, rmax) apers = [CircularAperture((pos[1], pos[0]), r=a) for a in aperrads] # --- estimate the background level and standard deviation # --- define a sky annulus annul = CircularAnnulus((pos[1], pos[0]), r_in=sky_rin, r_out=sky_rout) # --- make a mask corresponding to the annulus annul_mask = annul.to_mask( method='center' ) # in older versions of the photutils a [0] is reqiured here # --- apply the annulus mask to the image annul_data = annul_mask.multiply(im) # --- extract the data from that annulus bg = annul_data[annul_mask.data > 0] bgstd = np.nanstd(bg) bgmed = np.nanmedian(bg) counts = [ aperture_photometry(im, a, error=im * 0 + bgstd)['aperture_sum'][0] - bgmed * a.area for a in apers ] ecnts = [ aperture_photometry(im, a, error=im * 0 + bgstd)['aperture_sum_err'][0] for a in apers ] sn = np.array(counts) / np.array(ecnts) if verbose: print("Aper rad. | counts | error | S/N") for r in aperrads: print(r, counts[r - 1], ecnts[r - 1], sn[r - 1]) # sn = np.zeros(rmax) # for radius in range(1, rmax): # (mag, magerr, flux, fluxerr, sky, skyerr, badflag, # outstr) = aper(im, pos[0], pos[1], phpadu=1000.0, apr=radius, # skyrad=[30, 40], exact=True) # print(flux, fluxerr, flux/fluxerr) # sn[radius-1] = flux/fluxerr # --- best S/N and corresponding aperture radius snbest = np.max(sn) rbest = np.argmax(sn) + 1 # --- check if the file was a standard which allow computation of # sensitivity sens = -1 # --- in case the image is of a standard star try to determine the reached # sensitivity if std: try: if flux == None: flux = head["HIERARCH ESO QC JYVAL"] if exptime == None: if "HIERARCH ESO QC EXPTIME" in head: exptime = head["HIERARCH ESO QC EXPTIME"] elif "HIERARCH ESO SEQ TIME" in head: exptime = head["HIERARCH ESO SEQ TIME"] sens = flux * 1000.0 * 10.0 * np.sqrt(exptime / 3600.0) / snbest except: print( "MEASURE_SENSITIVITY: ERROR: Can not compute sensitivity because flux/time information missing" ) if showplot: fig = plt.figure(num=10, figsize=(8, 4)) ax0 = fig.add_subplot(1, 2, 1) ax0.plot(aperrads, sn) ax0.set_xlabel("aperture radius [px]") ax0.set_ylabel("S/N") ax1 = fig.add_subplot(1, 2, 2) pim = im - np.nanmin(im) ax1.imshow(pim, origin='lower', norm=LogNorm(), interpolation='nearest', cmap='gnuplot2', vmin=np.nanpercentile(pim, 0.1), vmax=np.nanmax(pim)) apers[rbest].plot(color='lime', lw=1) annul.plot(color='cyan', lw=1) ax1.set_xlabel('x [px]') ax1.set_ylabel('y [px]') plt.show() plt.close(10) if verbose: print("\n") print("MEASURE_SENSITIVITY:\n" + " - background level: " + "{:.2f}".format(bgmed) + "\n" + " - background STD: " + "{:.2f}".format(bgstd) + "\n" + " - best S/N: " + "{:.1f}".format(snbest) + "\n" + " - for radius [px]: " + str(rbest) + "\n" + " - counts: " + "{:.1f}".format(counts[rbest - 1]) + "\n" + " - ecounts: " + "{:.1f}".format(ecnts[rbest - 1])) if sens != -1: print(" - STD flux density [Jy]: " + "{:.2f}".format(flux) + "\n" + " - exposure time [s]: " + "{:.0f}".format(exptime) + "\n" + " - sensitivity: " + "{:.2f}".format(sens) + "\n") else: print( " - no sensitivty determined because flux/time info missing. ") return (snbest, sens)
def ap_an_phot(image, sources, source_ap, sky_ap, delta_ann=3.0, coords='xy', centroid=False, show_plot=False, **kargs): """Performs circular aperture fotometry on a image, given a table with the coordinates (x,y) or (ra,dec) of the sources. Background is estimated from a annular aperture and substracted Input: image: (str) name of the .fits file with the image sources: table with the Positions of the sources in the image. source_ap: Radius of the circular aperture in pixels sky_ap: inner radius of the annulus to calculate the sky. delta_ann: Width of the annulus to calculate the sky centroid: (Boolean) if True the source position centroid are recalculated. sky_coord (str) type of coordinates to use, either xy or radec. If xy sources table must have 'x' and 'y' cols. If ra dec sources table must to have 'ra' and 'dec' cols. show_plot: (Boolean) If True a plot of the aperture is displayed. Output: phot_table: Table with the resulting photometry """ data = fits.getdata(image) x, y = sources.colnames if coords == 'radec': sources = SkyCoord(sources[x], sources[y], **kargs) # convert to pixel coordinates header = fits.getheader(image) wcs = WCS(header) sources = Table(sources.to_pixel(wcs), names=[x, y]) elif coords == 'xy': warnings.warn('Image pixels start at 1 while python index stats at 0', AstropyUserWarning) # Check if the following correction is necessary # sources[x] -= 1 # sources[y] -= 1 if centroid: sources = centroid_sources(data, sources[x], sources[y], box_size=30) sources = Table(sources, names=[x, y]) # create apertures sources = [(a, b) for a, b in zip(sources[x], sources[y])] source_aperture = CircularAperture(sources, r=source_ap) sky_aperture = CircularAnnulus(sources, r_in=sky_ap, r_out=sky_ap + delta_ann) if show_plot: index = [str(i+1) for i in range(len(sources))] norm = simple_norm(data, 'sqrt', percent=99) plt.figure() plt.imshow(data, norm=norm) source_aperture.plot(color='white', lw=2) sky_aperture.plot(color='red', lw=2) for a, b in sources: plt.text(a, b, index, color="purple", fontsize=12) plt.show() # get background using sigma clipped median annulus_masks = sky_aperture.to_mask(method='center') bkg_median = [] for mask in annulus_masks: annulus_data = mask.multiply(data) annulus_data_1d = annulus_data[mask.data > 0] _, median_sigclip, _ = sigma_clipped_stats(annulus_data_1d) bkg_median.append(median_sigclip) bkg_median = np.array(bkg_median) phot_table = aperture_photometry(data, source_aperture) phot_table['annulus_median'] = bkg_median phot_table['aper_bkg'] = bkg_median * source_aperture.area() phot_table['aper_sum_bkgsub'] = phot_table['aperture_sum'] - \ phot_table['aper_bkg'] return phot_table
def fluxExtract(science,bias,dark,flats,hdr,plotap=False,rad=18,plotname=None): ' Returns final flux of source ' # Calibrate Image. Flatten? add bias and flat to input flagg = 0 # Defaul shape of sbig 2x2 binning xsize,ysize = 1266,1676 if xsize != np.shape(bias)[0]: print 'WARNING: check that size of science file matches assumptions' if flats == 0: flat = np.ones((xsize,ysize)) else: flat = flats[hdr['FILTER']] if dark == 0: dark = np.ones((xsize,ysize)) else: dark = dark[str(hdr['EXPTIME'])] if np.mean(bias) == 0: bias = np.zeros((xsize,ysize)) else: bias = bias # Size of science to trim calib frames (which are always full) # Science frames either full or sub: 400x400 around center () centx, centy = int(1663/2), int(1252/2) # use config file in future, x and y are switched from thesky vs pyfits??? subframe_size = np.shape(science) dx,dy = subframe_size[0]/2,subframe_size[0]/2 l,b,r,t = centx - dx, centy+dy, centx+dx, centy-dy # top and bottom switched data = (science - bias[t:b,l:r] - dark[t:b,l:r])/flat[t:b,l:r] if np.mean(data) < 0: flagg += 1 # Get source x,y position x,y = sourceFinder(science) positions = (x,y) if x==0: flagg += 1 if y==0: flagg += 1 # Define Apertures apertures = CircularAperture(positions, r=rad) annulus_apertures = CircularAnnulus(positions, r_in=rad+5, r_out=rad+20) # Get fluxes rawflux_table = aperture_photometry(data, apertures) bkgflux_table = aperture_photometry(data, annulus_apertures) bkg_mean = bkgflux_table['aperture_sum'] / annulus_apertures.area() bkg_sum = bkg_mean * apertures.area() final_sum = rawflux_table['aperture_sum'] - bkg_sum # Plot if plotap == True: plt.ioff() plt.figure(-999) plt.clf() plt.imshow(np.log(data), origin='lower') apertures.plot(color='red', lw=1.5, alpha=0.5) annulus_apertures.plot(color='orange', lw=1.5, alpha=0.5) plt.savefig(plotname) return final_sum[0],flagg,x,y,bkg_mean[0]
tp.lightcurve(info) position = (xnew,ynew) aperture = CircularAperture(position, r=radius) bkg_aperture = CircularAnnulus(position, r_in=15., r_out=20.) # perform the photometry; the default method is 'exact' phot = aperture_photometry(image, aperture) bkg = aperture_photometry(image, bkg_aperture) # calculate the mean background level (per pixel) in the annuli bkg_mean = bkg['aperture_sum'] / bkg_aperture.area() bkg_mean bkg_sum = bkg_mean * aperture.area() # plot the apertures plt.imshow(scale_image(image, scale='sqrt', percent=98.), origin='lower',cmap='gray') aperture.plot(color='blue') bkg_aperture.plot(color='cyan', hatch='//', alpha=0.8) plt.xlim(xnew-100,xnew+100) plt.ylim(ynew-100,ynew+100) phot['bkg_sum'] = bkg_sum
i = 0 for x, y in positions: label = "{:s}".format(radec['name'][i].replace( field, '').replace('_', '').replace('-', '')) plt.annotate( label, # this is the text (x, y), # this is the point to label textcoords="offset points", # how to position the text color='blue', xytext=(-10, 5), ha='center') # horizontal alignment i = i + 1 plt.imshow(science, norm=norm_science, cmap='Greys') apertures.plot(color='yellow', lw=1.5, alpha=0.7) annulus_apertures.plot(color='blue', lw=1.5, alpha=0.5) plt.savefig('stars_positions_' + field + '_' + filter_name + '.png', format='png', pdi=300) """ IRAF definitions of mag and mag_err flux = sum - area * msky mag = zmag - 2.5 * log10 (flux) + 2.5 * log10 (itime) merr = 1.0857 * err / flux err = sqrt (flux / gain + area * stdev**2 + area**2 * stdev**2 / nsky) """ # Implement a mask that track the bad pixels and/or saturated pixels # from the dq array, in order to exclude those regions from the data. #tic = time.perf_counter() bkg_median = [] bkg_stddev = []
#the aperture photometry bit. Defines an aperture with radius (not width, oops, I'll sort that) of star_w, and #an annulus around the star to pick up background noise. positions = (sources['xcentroid'], sources['ycentroid']) apertures = CircularAperture(positions, r=aperture_w / 2) annuli = CircularAnnulus(positions, r_in=10., r_out=12.) totals = [apertures, annuli] phot_table = aperture_photometry(region, totals) for col in phot_table.colnames: phot_table[col].info.format = '%.8g' #calculates the mean background from the annulus, then corrects the #aperture brightness for this bkg_mean = (phot_table['aperture_sum_1']) / annuli.area() bkg_sum = bkg_mean * apertures.area() final_sum = phot_table['aperture_sum_0'] - bkg_sum phot_table['final_sum'] = final_sum print phot_table['final_sum'] #plots it so I know what's going on (plus it's pretty, inferno is a great colourmap) plt.figure(figsize=(10, 10), dpi=80) plt.imshow(region, cmap='inferno', origin='left', aspect='equal', norm=LogNorm()) apertures.plot(color='green', lw=1.5, alpha=1) annuli.plot(color='white', lw=.5, alpha=1) plt.show()
g = [None] * 100 bkg = [None] * 100 for i in range(100): h[i] = p + p * i aperture1[i] = CircularAperture(positions, r=h[i]) apers1[i] = [aperture1[i]] f[i] = aperture_photometry(data, apers1[i], method='exact') bkg[i] = median_sigclip * aperture1[i].area g[i] = f[i]['aperture_sum_0'] - bkg[i] #plots figure with apertures plt.imshow(data, cmap='gray') plt.gca().invert_yaxis() aperture.plot(color='red', lw=2) annulus_aperture.plot(color='green', lw=2) plt.xlim(aa - c - 10, aa + c + 10) plt.ylim(bb - c - 10, bb + c + 10) plt.show() #plots curve of growth plt.plot(h, g, 'k-') plt.title('Curve of Growth for aperture') plt.xlabel('Radius (pix)') plt.ylabel('Counts') plt.show() print('End of aperture photometry, thank you!') else: print('End of CPM position measurement, thank you!')
def find_target_phot(stack, fil, fwhm, zp, zp_err, show_phot=False, log=None, log2=None, ra=None, dec=None, x=None, y=None): ''' Function to perform photometry on a target at a specific RA and Dec. Usually used after `find_zero_point` (this finds the ``zp``, ``zp_err``, and ``fwhm``). During this process, the user has the options to change the target's location and radii used in aperture photometry (if ``show_plots`` is ``True``, user can see cutouts of target and its radial profile). Attempts to find the magnitude of the target using aperture photometry. If magnitude cannot be found at a 3 sigma confidence interval, limiting magnitude is found at the same position. Limiting magnitude is found by adding fake sources of increasing magnitude at the target's position until it is more than 3 sigma above the background. :param stack: str Name of the stack to use (include path to the stack). :param fil: str Name of the filter of the stack. :param fwhm: float FWHM of image (most likely found through `find_zero_point`). :param zp: float Zero point of image (most likely found through `find_zero_point`). :param zp_err: float Error on the zero point of image (most likely found through `find_zero_point`). :param show_phot: boolean, optional Option to see the cutout of the source with apertures along with its radial profile. Default is ``False``. :param log: log, optional In-depth log. If no log is inputted, information is printed out instead of being written to ``log``. Default is ``None``. :param log2: log, optional Overview log. If no log is inputted, information is printed out instead of being written to ``log2``. Default is ``None``. :param ra: float, optional RA coordinate of target. Default is ``None``. :param dec: float, optional Dec coordinate of target. Default is ``None``. :param x: float, optional X pixel coordinate (not RA). Default is ``None``. :param y: float, optional Y pixel coordiante (not Dec). Default is ``None``. Returns ------- :return: float, float Three options: * Returns the magnitude of the target (in AB) and its error. * Returns tne limiting magnitude at the target's location (in AB) and its error. * Returns ``None``, ``None`` if a fake source of amplitude 5 (adu / sec / pixel) is not three sigma above the background. ''' with fits.open(stack) as hdr: header, data = hdr[0].header, hdr[0].data w = wcs.WCS(header) # Parse the WCS keywords in the primary HDU if ra is not None: coords = np.array([[ra, dec]], np.float) # Array of coordinates: [[RA, Dec]] in [deg, deg] pixel_coords = w.wcs_world2pix(coords, 1)[0] # Find the pixel coordinates in the image x = pixel_coords[0] y = pixel_coords[1] elif x is not None: coords = np.array([[x, y]], np.float) # Array of coordinates: [[RA, Dec]] in [deg, deg] pixel_coords = w.wcs_pix2world(coords, 1)[0] # Find the pixel coordinates in the image ra = pixel_coords[0] dec = pixel_coords[1] else: print("No coordinates inputted") d_x_y = 125 # Pixels above and below the target when showing image...125 corresponds to a 50" x 50" cutout if log is not None: log.info("Pixel coordinates: (%.3f, %.3f)" % (x, y)) else: print("Pixel coordinates: (%.3f, %.3f)" % (x, y)) if input('Would you like to choose the cutout size? Default is 125x125 pixels (50"x50"): ') == "yes": try: d_x_y = int(input("Choose the radius, in arcsec: "))*2.5 except TypeError: pass sigma_clip = SigmaClip(sigma=3) # This section is to find the error on the background bkg_estimator = MedianBackground() bkg = Background2D(data, (120, 120), filter_size=(3, 3), sigma_clip=sigma_clip, bkg_estimator=bkg_estimator) error = calc_total_error(data, bkg.background_rms, 1) # Used later as well for limiting magnitude if log is not None: log.info("You will now get to pick the position you would like. " "Decide what pixel coordinates you would like to use") log.info("Blue circle in image is the distance the radial profile extends to") else: print("You will now get to pick the position you would like. " "Decide what pixel coordinates you would like to use") print("Blue circle in image is the distance the radial profile extends to") correct_position = "no" while correct_position == "no": if show_phot: radial_profile(data, x, y, 0.7, fwhm, rad=6) # Radial profile plt.axvline(6*fwhm) print("Double click to set the new center. Do nothing if you are ok with current coordinates.") fig = plt.figure(2) new_coords = [] fig.canvas.mpl_connect('button_press_event', lambda event: onclick(event, new_coords=new_coords)) ap_in = CircularAperture((x, y), 1) # Aperture to perform photometry ap_out = CircularAperture((x, y), 6*fwhm) # Aperture to perform photometry ap_in.plot(color='r', lw=1) # Plot of target with apertures and annulus ap_out.plot(color='b', lw=2) norm = simple_norm(data, 'sqrt', percent=99) plt.imshow(data, norm=norm) plt.xlim(int(x) - d_x_y, int(x) + d_x_y) plt.ylim(int(y) - d_x_y, int(y) + d_x_y) plt.colorbar() plt.show() if len(new_coords) != 0: x, y = new_coords[len(new_coords) - 2], new_coords[len(new_coords) - 1] real_coords = w.wcs_pix2world(np.array([[x, y]], np.float), 1)[0] # Find the pixel coords in the image ra, dec = real_coords[0], real_coords[1] if input("Are you ok with current coordinates? RA = %.5f Dec = %.5f Type 'yes' or 'no': " % (ra, dec)) != "yes": pass else: correct_position = "yes" if log is not None: log.info("Coordinates chosen: (%.3f, %.3f) at RA = %.5f and Dec = %.5f" % (x, y, ra, dec)) else: print("Coordinates chosen: (%.3f, %.3f) at RA = %.5f and Dec = %.5f" % (x, y, ra, dec)) if log2 is not None: log2.info("Final coordinates: (%.3f, %.3f) at RA = %.5f and Dec = %.5f" % (x, y, ra, dec)) if log is not None: log.info("You will now get to choose the radii for the circular aperture and the r_in and r_out of the annulus") else: print("You will now get to choose the radii for the circular aperture and the r_in and r_out of the annulus") correct_radii = "no" while correct_radii == "no": if log is not None: log.info("Automatic radii picked by comparing to FWHM of field: rad = %.3f, r_in = %.3f, r_out = %.3f" % (2.5 * fwhm, 3.5 * fwhm, 4.5 * fwhm)) else: print("Automatic radii picked by comparing to FWHM of field: rad = %.3f, r_in = %.3f, r_out = %.3f" % (2.5 * fwhm, 3.5 * fwhm, 4.5 * fwhm)) if input("Would you like to use these radii? Type 'yes or 'no': ") == "yes": rad, r_in, r_out = 2.5*fwhm, 3.5*fwhm, 4.5*fwhm else: log.info("FWHM = %.3f" % fwhm) rad = float(input("Pick a radius (in pixels) for the circular aperture: ")) r_in = float(input("Pick an inner radius (in pixels) for the background annulus: ")) r_out = float(input("Pick an outer radius (in pixels) for the background annulus: ")) if r_in >= r_out: r_out = r_in + 1 if log is not None: log.info("You selected an invalid r_out value. Automatically set to %s" % r_out) else: print("You selected an invalid r_out value. Automatically set to %s" % r_out) aperture = CircularAperture((x, y), rad) # Aperture to perform photometry annulus_aperture = CircularAnnulus((x, y), r_in=r_in, r_out=r_out) # Annulus of target annulus_mask = annulus_aperture.to_mask(method='center') # Create masks to highlight pixels in annuli annulus_data = annulus_mask.multiply(data) annulus_data_1d = annulus_data[annulus_mask.data > 0] _, median_sigclip, stddev = sigma_clipped_stats(annulus_data_1d) phot_table = aperture_photometry(data, aperture, error=error) # Photometry, error accounted for bkg_aper = median_sigclip * aperture.area if show_phot: # Radial profile out to 6 * fwhm (large just to be safe) radial_profile(data, x, y, 0.7, fwhm, rad=6) plt.axvline(rad, c='r') plt.axvline(r_in) plt.axvline(r_out) plt.figure(2) aperture.plot(color='white', lw=2) # Plot of target with apertures and annulus annulus_aperture.plot(color='b', lw=2) norm = simple_norm(data, 'sqrt', percent=99) plt.imshow(data, norm=norm) plt.xlim(int(x) - d_x_y, int(x) + d_x_y) plt.ylim(int(y) - d_x_y, int(y) + d_x_y) plt.colorbar() plt.show() correct_radii = input("Are you ok with the previously selected radii? Type 'yes' or 'no': ") if log is not None: log.info("Radii chosen: %.3f, %.3f, %.3f" % (rad, r_in, r_out)) else: print("Radii chosen: %.3f, %.3f, %.3f" % (rad, r_in, r_out)) if log2 is not None: log2.info("Final radii: %.3f, %.3f, %.3f" % (rad, r_in, r_out)) three_sigma = 3 # Change according to what sigma you'd like to use if (phot_table['aperture_sum']) - bkg_aper > 0: mag = -2.5 * np.log10((phot_table['aperture_sum']) - bkg_aper) # Instrumental magnitude mag_err = 2.5 / np.log(10.) * (phot_table['aperture_sum_err'])/(phot_table['aperture_sum']) mag += zp # Apparent magnitude (Vega) mag_err = np.sqrt(mag_err ** 2 + zp_err ** 2) if log is not None: log.info("Magnitude of target = %.3f +/- %.3f AB mag" % (mag + AB_conversion(fil), mag_err)) # Final mag if log2 is not None: log2.info("Magnitude of target = %.3f +/- %.3f AB mag" % (mag + AB_conversion(fil), mag_err)) # Final mag else: print("Magnitude of target = %.3f +/- %.3f AB mag" % (mag + AB_conversion(fil), mag_err)) # Final magnitude if np.abs(mag_err) < 1/three_sigma: # Good! No limiting magnitude needed return float(mag + AB_conversion(fil)), float(mag_err) if log is not None: log.info("Target was not %s sigma above the background." % three_sigma) else: print("Target was not %s sigma above the background." % three_sigma) if log2 is not None: log2.info("Target not found") else: if log is not None: log.info("Target's aperture sum was less than background aperture sum. Target was not found") else: print("Target's aperture sum was less than background aperture sum. Target was not found") if log2 is not None: log2.info("Target not found") # Finding the limiting magnitude where the target was supposed to be located if log is not None: log.info("Now finding the limiting magnitude to %s sigma." % three_sigma) else: print("Now finding the limiting magnitude to %s sigma." % three_sigma) d_x_y = 50 d = data[int(y)-d_x_y:int(y)+d_x_y, int(x)-d_x_y:int(x)+d_x_y] # Cutout of source; upside down from normal x_mesh, y_mesh = np.meshgrid(np.linspace(0, np.shape(d)[1] - 1, np.shape(d)[1]), np.linspace(0, np.shape(d)[0] - 1, np.shape(d)[0])) amplitudes = np.arange(0, 5, 0.1) # Amplitudes to consider for i, j in enumerate(amplitudes): gauss_data = twoD_Gaussian((x_mesh, y_mesh), j, d_x_y, d_x_y, fwhm/2.35482, fwhm/2.35482, 0, 0) # Fake source norm = simple_norm(data, 'sqrt', percent=99) # Find this before changing data d += gauss_data.reshape(np.shape(d)) # Adding the Gaussian to the data # Photometry on source with resulting limiting magnitude aperture = CircularAperture((x, y), rad) # Aperture to perform photometry annulus_aperture = CircularAnnulus((x, y), r_in=r_in, r_out=r_out) # Annulus of target annulus_mask = annulus_aperture.to_mask(method='center') # Create masks to highlight pixels in annuli annulus_data = annulus_mask.multiply(data) annulus_data_1d = annulus_data[annulus_mask.data > 0] _, median_sigclip, stddev = sigma_clipped_stats(annulus_data_1d) phot_table = aperture_photometry(data, aperture, error=error) # Photometry, error accounted for (found earlier) bkg_aper = median_sigclip * aperture.area if (phot_table['aperture_sum']) - bkg_aper > 0: mag_err = 2.5 / np.log(10.) * (phot_table['aperture_sum_err']) / (phot_table['aperture_sum']) if np.abs(mag_err) < 1/three_sigma: if log is not None: log.info("Amplitude: %.3f Magnitude Error: %.3f" % (j, mag_err)) else: print("Amplitude: %.3f Magnitude Error: %.3f" % (j, mag_err)) if show_phot: plt.figure(2) d -= gauss_data.reshape(np.shape(d)) aperture.plot(color='white', lw=2) # Old data plot annulus_aperture.plot(color='b', lw=2) plt.imshow(data, norm=norm) plt.xlim(int(x) - d_x_y, int(x) + d_x_y) plt.ylim(int(y) - d_x_y, int(y) + d_x_y) plt.colorbar() plt.title("Target without fake source added in") plt.figure(3) d += gauss_data.reshape(np.shape(d)) aperture.plot(color='white', lw=2) # New data plot annulus_aperture.plot(color='b', lw=2) plt.imshow(data, norm=norm) plt.xlim(int(x) - d_x_y, int(x) + d_x_y) plt.ylim(int(y) - d_x_y, int(y) + d_x_y) plt.colorbar() plt.title("Target after fake source has been added in") # Radial profile...only need for good fake source (same process as used to find the FWHM) radial_profile(data, x, y, 0.7, fwhm, rad=r_out/fwhm) plt.axvline(rad, c='r') plt.axvline(r_in) plt.axvline(r_out) plt.show() mag = -2.5 * np.log10((phot_table['aperture_sum']) - bkg_aper) # Instrumental magnitude mag += zp # Apparent magnitude (Vega) mag_err = np.sqrt(mag_err ** 2 + zp_err ** 2) # Limiting magnitude at the position of the target if log is not None: log.info("Limiting magnitude = %.3f +/- %.3f AB mag" % (mag + AB_conversion(fil), mag_err)) else: print("Limiting magnitude = %.3f +/- %.3f AB mag" % (mag + AB_conversion(fil), mag_err)) if log2 is not None: log2.info("Limiting magnitude = %.3f +/- %.3f AB mag" % (mag + AB_conversion(fil), mag_err)) return float(mag + AB_conversion(fil)), float(mag_err) d -= gauss_data.reshape(np.shape(d)) # 'Reset' data for next run if limiting magnitude not found if log is not None: log.info("False source with amplitude of 5 (adu/sec/pixel) was not %s sigma above the background." % three_sigma) log.info("Limiting magnitude not found") else: print("False source with amplitude of 5 (adu/sec/pixel) was not %s sigma above the background." % three_sigma) print("Limiting magnitude not found") if log2 is not None: log2.info("Limiting magnitude not found") return None, None
overwrite=True, format='ascii.fast_csv') DAOcoord = (DAOfound['xcentroid'], DAOfound['ycentroid']) DAOannul = CircAn(positions=DAOcoord, r_in=4 * FWHM, r_out=6 * FWHM) # Save apertures as circular, 4 pixel radius, at each (X, Y) DAOapert = CircAp(DAOcoord, r=4.) #print('DAOapert\n ', DAOapert) DAOimgXY = np.array(DAOcoord) #print('DAOimgXY \n', DAOimgXY) plt.figure(figsize=(16, 12)) ax = plt.gca() im = plt.imshow(img, vmax=thresh * 4, origin='lower') DAOannul.plot(color='red', lw=2., alpha=0.7) divider = make_axes_locatable(ax) cax = divider.append_axes("right", size="3%", pad=0.05) plt.colorbar(im, cax=cax) plt.savefig(f_name[:-4] + '_DAOstarfinder_Annulus_result_all_stars.png', overwrite=True) #plt.show() #%% #print('Star ID msky sky_std nsky nrej') #for star_ID in range(2, 10) : # for debug img_uint16 = np.array(img * 65536.0, dtype=np.uint16) ronoise = hdu[0].header['RDNOISE'] #gain = 0.46599999070167542 # e/ADU gain = hdu[0].header['GAIN']
def photometry(fileid): """ Run photometry. .. codeauthor:: Rasmus Handberg <*****@*****.**> """ # Settings: ref_mag_limit = 17 # Lower limit on reference target brightness ref_target_dist_limit = 30 # Reference star must be further than this away to be included logger = logging.getLogger(__name__) tic = default_timer() # Use local copy of archive if configured to do so: config = load_config() # Get datafile dict from API: datafile = api.get_datafile(fileid) logger.debug("Datafile: %s", datafile) targetid = datafile['targetid'] photfilter = datafile['photfilter'] archive_local = config.get('photometry', 'archive_local', fallback=None) if archive_local is not None: datafile['archive_path'] = archive_local if not os.path.isdir(datafile['archive_path']): raise FileNotFoundError("ARCHIVE is not available") # Get the catalog containing the target and reference stars: # TODO: Include proper-motion to the time of observation catalog = api.get_catalog(targetid, output='table') target = catalog['target'][0] # Extract information about target: target_name = str(target['target_name']) target_coord = coords.SkyCoord(ra=target['ra'], dec=target['decl'], unit='deg', frame='icrs') # Folder to save output: # TODO: Change this! output_folder_root = config.get('photometry', 'output', fallback='.') output_folder = os.path.join(output_folder_root, target_name, '%04d' % fileid) os.makedirs(output_folder, exist_ok=True) # Also write any logging output to the formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') _filehandler = logging.FileHandler(os.path.join(output_folder, 'photometry.log'), mode='w') _filehandler.setFormatter(formatter) _filehandler.setLevel(logging.INFO) logger.addHandler(_filehandler) # The paths to the science image: filepath = os.path.join(datafile['archive_path'], datafile['path']) # TODO: Download datafile using API to local drive: # TODO: Is this a security concern? #if archive_local: # api.download_datafile(datafile, archive_local) # Translate photometric filter into table column: if photfilter == 'gp': ref_filter = 'g_mag' elif photfilter == 'rp': ref_filter = 'r_mag' elif photfilter == 'ip': ref_filter = 'i_mag' elif photfilter == 'zp': ref_filter = 'z_mag' elif photfilter == 'B': ref_filter = 'B_mag' elif photfilter == 'V': ref_filter = 'V_mag' else: logger.warning( "Could not find filter '%s' in catalogs. Using default gp filter.", photfilter) ref_filter = 'g_mag' references = catalog['references'] references.sort(ref_filter) # Load the image from the FITS file: image = load_image(filepath) # Calculate pixel-coordinates of references: row_col_coords = image.wcs.all_world2pix( np.array([[ref['ra'], ref['decl']] for ref in references]), 0) references['pixel_column'] = row_col_coords[:, 0] references['pixel_row'] = row_col_coords[:, 1] # Calculate the targets position in the image: target_pixel_pos = image.wcs.all_world2pix( [[target['ra'], target['decl']]], 0)[0] # Clean out the references: hsize = 10 x = references['pixel_column'] y = references['pixel_row'] references = references[ (np.sqrt((x - target_pixel_pos[0])**2 + (y - target_pixel_pos[1])**2) > ref_target_dist_limit) & (references[ref_filter] < ref_mag_limit) & (x > hsize) & (x < (image.shape[1] - 1 - hsize)) & (y > hsize) & (y < (image.shape[0] - 1 - hsize))] #============================================================================================== # BARYCENTRIC CORRECTION OF TIME #============================================================================================== ltt_bary = image.obstime.light_travel_time(target_coord, ephemeris='jpl') image.obstime = image.obstime.tdb + ltt_bary #============================================================================================== # BACKGROUND ESITMATION #============================================================================================== fig, ax = plt.subplots(1, 2, figsize=(20, 18)) plot_image(image.clean, ax=ax[0], scale='log', cbar='right', title='Image') plot_image(image.mask, ax=ax[1], scale='linear', cbar='right', title='Mask') fig.savefig(os.path.join(output_folder, 'original.png'), bbox_inches='tight') plt.close(fig) # Estimate image background: # Not using image.clean here, since we are redefining the mask anyway bkg = Background2D( image.clean, (128, 128), filter_size=(5, 5), #mask=image.mask | (image.clean > background_cutoff), sigma_clip=SigmaClip(sigma=3.0), bkg_estimator=SExtractorBackground(), exclude_percentile=50.0) image.background = bkg.background # Create background-subtracted image: image.subclean = image.clean - image.background # Plot background estimation: fig, ax = plt.subplots(1, 3, figsize=(20, 6)) plot_image(image.clean, ax=ax[0], scale='log', title='Original') plot_image(image.background, ax=ax[1], scale='log', title='Background') plot_image(image.subclean, ax=ax[2], scale='log', title='Background subtracted') fig.savefig(os.path.join(output_folder, 'background.png'), bbox_inches='tight') plt.close(fig) # TODO: Is this correct?! image.error = calc_total_error(image.clean, bkg.background_rms, 1.0) #============================================================================================== # DETECTION OF STARS AND MATCHING WITH CATALOG #============================================================================================== logger.info("References:\n%s", references) radius = 10 fwhm_guess = 6.0 fwhm_min = 3.5 fwhm_max = 13.5 # Extract stars sub-images: #stars = extract_stars( # NDData(data=image.subclean, mask=image.mask), # stars_for_epsf, # size=size #) # Set up 2D Gaussian model for fitting to reference stars: g2d = models.Gaussian2D(amplitude=1.0, x_mean=radius, y_mean=radius, x_stddev=fwhm_guess * gaussian_fwhm_to_sigma) g2d.amplitude.bounds = (0.1, 2.0) g2d.x_mean.bounds = (0.5 * radius, 1.5 * radius) g2d.y_mean.bounds = (0.5 * radius, 1.5 * radius) g2d.x_stddev.bounds = (fwhm_min * gaussian_fwhm_to_sigma, fwhm_max * gaussian_fwhm_to_sigma) g2d.y_stddev.tied = lambda model: model.x_stddev g2d.theta.fixed = True gfitter = fitting.LevMarLSQFitter() fwhms = np.full(len(references), np.NaN) for i, (x, y) in enumerate( zip(references['pixel_column'], references['pixel_row'])): x = int(np.round(x)) y = int(np.round(y)) x0, y0, width, height = x - radius, y - radius, 2 * radius, 2 * radius cutout = slice(y0 - 1, y0 + height), slice(x0 - 1, x0 + width) curr_star = image.subclean[cutout] / np.max(image.subclean[cutout]) npix = len(curr_star) ypos, xpos = np.mgrid[:npix, :npix] gfit = gfitter(g2d, x=xpos, y=ypos, z=curr_star) fwhms[i] = gfit.x_fwhm mask = ~np.isfinite(fwhms) | (fwhms <= fwhm_min) | (fwhms >= fwhm_max) masked_fwhms = np.ma.masked_array(fwhms, mask) fwhm = np.mean(sigma_clip(masked_fwhms, maxiters=20, sigma=2.0)) logger.info("FWHM: %f", fwhm) # Use DAOStarFinder to search the image for stars, and only use reference-stars where a # star was actually detected close to the references-star coordinate: cleanout_references = (len(references) > 50) if cleanout_references: daofind_tbl = DAOStarFinder(100, fwhm=fwhm, roundlo=-0.5, roundhi=0.5).find_stars(image.subclean, mask=image.mask) indx_good = np.zeros(len(references), dtype='bool') for k, ref in enumerate(references): dist = np.sqrt((daofind_tbl['xcentroid'] - ref['pixel_column'])**2 + (daofind_tbl['ycentroid'] - ref['pixel_row'])**2) if np.any(dist <= fwhm / 4): # Cutoff set somewhat arbitrary indx_good[k] = True references = references[indx_good] fig, ax = plt.subplots(1, 1, figsize=(20, 18)) plot_image(image.subclean, ax=ax, scale='log', cbar='right', title=target_name) ax.scatter(references['pixel_column'], references['pixel_row'], c='r', alpha=0.3) if cleanout_references: ax.scatter(daofind_tbl['xcentroid'], daofind_tbl['ycentroid'], c='g', alpha=0.3) ax.scatter(target_pixel_pos[0], target_pixel_pos[1], marker='+', c='r') fig.savefig(os.path.join(output_folder, 'positions.png'), bbox_inches='tight') plt.close(fig) #============================================================================================== # CREATE EFFECTIVE PSF MODEL #============================================================================================== # Make cutouts of stars using extract_stars: # Scales with FWHM size = int(np.round(29 * fwhm / 6)) if size % 2 == 0: size += 1 # Make sure it's a uneven number size = max(size, 15) # Never go below 15 pixels hsize = (size - 1) / 2 x = references['pixel_column'] y = references['pixel_row'] mask_near_edge = ((x > hsize) & (x < (image.shape[1] - 1 - hsize)) & (y > hsize) & (y < (image.shape[0] - 1 - hsize))) stars_for_epsf = Table() stars_for_epsf['x'] = x[mask_near_edge] stars_for_epsf['y'] = y[mask_near_edge] # Store which stars were used in ePSF in the table: logger.info("Number of stars used for ePSF: %d", len(stars_for_epsf)) references['used_for_epsf'] = mask_near_edge # Extract stars sub-images: stars = extract_stars(NDData(data=image.subclean, mask=image.mask), stars_for_epsf, size=size) # Plot the stars being used for ePSF: nrows = 5 ncols = 5 imgnr = 0 for k in range(int(np.ceil(len(stars_for_epsf) / (nrows * ncols)))): fig, ax = plt.subplots(nrows=nrows, ncols=ncols, figsize=(20, 20), squeeze=True) ax = ax.ravel() for i in range(nrows * ncols): if imgnr > len(stars_for_epsf) - 1: ax[i].axis('off') else: plot_image(stars[imgnr], ax=ax[i], scale='log', cmap='viridis') imgnr += 1 fig.savefig(os.path.join(output_folder, 'epsf_stars%02d.png' % (k + 1)), bbox_inches='tight') plt.close(fig) # Build the ePSF: epsf = EPSFBuilder(oversampling=1.0, maxiters=500, fitter=EPSFFitter(fit_boxsize=2 * fwhm), progress_bar=True)(stars)[0] logger.info('Successfully built PSF model') fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 15)) plot_image(epsf.data, ax=ax1, cmap='viridis') fwhms = [] for a, ax in ((0, ax3), (1, ax2)): # Collapse the PDF along this axis: profile = epsf.data.sum(axis=a) itop = profile.argmax() poffset = profile[itop] / 2 # Run a spline through the points, but subtract half of the peak value, and find the roots: # We have to use a cubic spline, since roots() is not supported for other splines # for some reason profile_intp = UnivariateSpline(np.arange(0, len(profile)), profile - poffset, k=3, s=0, ext=3) lr = profile_intp.roots() axis_fwhm = lr[1] - lr[0] fwhms.append(axis_fwhm) x_fine = np.linspace(-0.5, len(profile) - 0.5, 500) ax.plot(profile, 'k.-') ax.plot(x_fine, profile_intp(x_fine) + poffset, 'g-') ax.axvline(itop) ax.axvspan(lr[0], lr[1], facecolor='g', alpha=0.2) ax.set_xlim(-0.5, len(profile) - 0.5) # Let's make the final FWHM the largest one we found: fwhm = np.max(fwhms) logger.info("Final FWHM based on ePSF: %f", fwhm) #ax2.axvspan(itop - fwhm/2, itop + fwhm/2, facecolor='b', alpha=0.2) #ax3.axvspan(itop - fwhm/2, itop + fwhm/2, facecolor='b', alpha=0.2) ax4.axis('off') fig.savefig(os.path.join(output_folder, 'epsf.png'), bbox_inches='tight') plt.close(fig) #============================================================================================== # COORDINATES TO DO PHOTOMETRY AT #============================================================================================== coordinates = np.array([[ref['pixel_column'], ref['pixel_row']] for ref in references]) # Add the main target position as the first entry: if datafile.get('template') is None: coordinates = np.concatenate(([target_pixel_pos], coordinates), axis=0) #============================================================================================== # APERTURE PHOTOMETRY #============================================================================================== # Define apertures for aperture photometry: apertures = CircularAperture(coordinates, r=fwhm) annuli = CircularAnnulus(coordinates, r_in=1.5 * fwhm, r_out=2.5 * fwhm) apphot_tbl = aperture_photometry(image.subclean, [apertures, annuli], mask=image.mask, error=image.error) logger.debug("Aperture Photometry Table:\n%s", apphot_tbl) logger.info('Apperature Photometry Success') #============================================================================================== # PSF PHOTOMETRY #============================================================================================== # Are we fixing the postions? epsf.fixed.update({'x_0': False, 'y_0': False}) # Create photometry object: photometry = BasicPSFPhotometry(group_maker=DAOGroup(fwhm), bkg_estimator=SExtractorBackground(), psf_model=epsf, fitter=fitting.LevMarLSQFitter(), fitshape=size, aperture_radius=fwhm) psfphot_tbl = photometry(image=image.subclean, init_guesses=Table(coordinates, names=['x_0', 'y_0'])) logger.debug("PSF Photometry Table:\n%s", psfphot_tbl) logger.info('PSF Photometry Success') #============================================================================================== # TEMPLATE SUBTRACTION AND TARGET PHOTOMETRY #============================================================================================== if datafile.get('template') is not None: # Find the pixel-scale of the science image: pixel_area = proj_plane_pixel_area(image.wcs.celestial) pixel_scale = np.sqrt(pixel_area) * 3600 # arcsec/pixel #print(image.wcs.celestial.cunit) % Doesn't work? logger.info("Science image pixel scale: %f", pixel_scale) # Run the template subtraction, and get back # the science image where the template has been subtracted: diffimage = run_imagematch(datafile, target, star_coord=coordinates, fwhm=fwhm, pixel_scale=pixel_scale) # Include mask from original image: diffimage = np.ma.masked_array(diffimage, image.mask) # Create apertures around the target: apertures = CircularAperture(target_pixel_pos, r=fwhm) annuli = CircularAnnulus(target_pixel_pos, r_in=1.5 * fwhm, r_out=2.5 * fwhm) # Create two plots of the difference image: fig, ax = plt.subplots(1, 1, squeeze=True, figsize=(20, 20)) plot_image(diffimage, ax=ax, cbar='right', title=target_name) ax.plot(target_pixel_pos[0], target_pixel_pos[1], marker='+', color='r') fig.savefig(os.path.join(output_folder, 'diffimg.png'), bbox_inches='tight') apertures.plot(color='r') annuli.plot(color='k') ax.set_xlim(target_pixel_pos[0] - 50, target_pixel_pos[0] + 50) ax.set_ylim(target_pixel_pos[1] - 50, target_pixel_pos[1] + 50) fig.savefig(os.path.join(output_folder, 'diffimg_zoom.png'), bbox_inches='tight') plt.close(fig) # Run aperture photometry on subtracted image: target_apphot_tbl = aperture_photometry(diffimage, [apertures, annuli], mask=image.mask, error=image.error) # Run PSF photometry on template subtracted image: target_psfphot_tbl = photometry(diffimage, init_guesses=Table( target_pixel_pos, names=['x_0', 'y_0'])) # Combine the output tables from the target and the reference stars into one: apphot_tbl = vstack([target_apphot_tbl, apphot_tbl], join_type='exact') psfphot_tbl = vstack([target_psfphot_tbl, psfphot_tbl], join_type='exact') # Build results table: tab = references.copy() tab.insert_row( 0, { 'starid': 0, 'ra': target['ra'], 'decl': target['decl'], 'pixel_column': target_pixel_pos[0], 'pixel_row': target_pixel_pos[1] }) for key in ('pm_ra', 'pm_dec', 'gaia_mag', 'gaia_bp_mag', 'gaia_rp_mag', 'H_mag', 'J_mag', 'K_mag', 'g_mag', 'r_mag', 'i_mag', 'z_mag'): tab[0][key] = np.NaN # Subtract background estimated from annuli: flux_aperture = apphot_tbl['aperture_sum_0'] - ( apphot_tbl['aperture_sum_1'] / annuli.area()) * apertures.area() flux_aperture_error = np.sqrt(apphot_tbl['aperture_sum_err_0']**2 + (apphot_tbl['aperture_sum_err_1'] / annuli.area() * apertures.area())**2) # Add table columns with results: tab['flux_aperture'] = flux_aperture / image.exptime tab['flux_aperture_error'] = flux_aperture_error / image.exptime tab['flux_psf'] = psfphot_tbl['flux_fit'] / image.exptime tab['flux_psf_error'] = psfphot_tbl['flux_unc'] / image.exptime tab['pixel_column_psf_fit'] = psfphot_tbl['x_fit'] tab['pixel_row_psf_fit'] = psfphot_tbl['y_fit'] tab['pixel_column_psf_fit_error'] = psfphot_tbl['x_0_unc'] tab['pixel_row_psf_fit_error'] = psfphot_tbl['y_0_unc'] # Theck that we got valid photometry: if not np.isfinite(tab[0]['flux_psf']) or not np.isfinite( tab[0]['flux_psf_error']): raise Exception("Target magnitude is undefined.") #============================================================================================== # CALIBRATE #============================================================================================== # Convert PSF fluxes to magnitudes: mag_inst = -2.5 * np.log10(tab['flux_psf']) mag_inst_err = (2.5 / np.log(10)) * (tab['flux_psf_error'] / tab['flux_psf']) # Corresponding magnitudes in catalog: mag_catalog = tab[ref_filter] # Mask out things that should not be used in calibration: use_for_calibration = np.ones_like(mag_catalog, dtype='bool') use_for_calibration[0] = False # Do not use target for calibration use_for_calibration[~np.isfinite(mag_inst) | ~np.isfinite(mag_catalog)] = False # Just creating some short-hands: x = mag_catalog[use_for_calibration] y = mag_inst[use_for_calibration] yerr = mag_inst_err[use_for_calibration] # Fit linear function with fixed slope, using sigma-clipping: model = models.Linear1D(slope=1, fixed={'slope': True}) fitter = fitting.FittingWithOutlierRemoval(fitting.LinearLSQFitter(), sigma_clip, sigma=3.0) best_fit, sigma_clipped = fitter(model, x, y, weights=1.0 / yerr**2) # Extract zero-point and estimate its error: # I don't know why there is not an error-estimate attached directly to the Parameter? zp = -1 * best_fit.intercept.value # Negative, because that is the way zeropoints are usually defined zp_error = nanstd(y[~sigma_clipped] - best_fit(x[~sigma_clipped])) # Add calibrated magnitudes to the photometry table: tab['mag'] = mag_inst + zp tab['mag_error'] = np.sqrt(mag_inst_err**2 + zp_error**2) fig, ax = plt.subplots(1, 1) ax.errorbar(x, y, yerr=yerr, fmt='k.') ax.scatter(x[sigma_clipped], y[sigma_clipped], marker='x', c='r') ax.plot(x, best_fit(x), color='g', linewidth=3) ax.set_xlabel('Catalog magnitude') ax.set_ylabel('Instrumental magnitude') fig.savefig(os.path.join(output_folder, 'calibration.png'), bbox_inches='tight') plt.close(fig) #============================================================================================== # SAVE PHOTOMETRY #============================================================================================== # Descriptions of columns: tab['flux_aperture'].unit = u.count / u.second tab['flux_aperture_error'].unit = u.count / u.second tab['flux_psf'].unit = u.count / u.second tab['flux_psf_error'].unit = u.count / u.second tab['pixel_column'].unit = u.pixel tab['pixel_row'].unit = u.pixel tab['pixel_column_psf_fit'].unit = u.pixel tab['pixel_row_psf_fit'].unit = u.pixel tab['pixel_column_psf_fit_error'].unit = u.pixel tab['pixel_row_psf_fit_error'].unit = u.pixel # Meta-data: tab.meta['version'] = __version__ tab.meta['fileid'] = fileid tab.meta['template'] = None if datafile.get( 'template') is None else datafile['template']['fileid'] tab.meta['photfilter'] = photfilter tab.meta['fwhm'] = fwhm tab.meta['obstime-bmjd'] = float(image.obstime.mjd) tab.meta['zp'] = zp tab.meta['zp_error'] = zp_error # Filepath where to save photometry: photometry_output = os.path.join(output_folder, 'photometry.ecsv') # Write the final table to file: tab.write(photometry_output, format='ascii.ecsv', delimiter=',', overwrite=True) toc = default_timer() logger.info("Photometry took: %f seconds", toc - tic) return photometry_output