def raw_aperture_photometry(sci_path, rms_path, mask_path, ra, dec, apply_calibration=False): import photutils from astropy.coordinates import SkyCoord from astropy.io import fits from astropy.table import vstack from astropy.wcs import WCS ra = np.atleast_1d(ra) dec = np.atleast_1d(dec) coord = SkyCoord(ra, dec, unit='deg') with fits.open(sci_path, memmap=False) as shdu: header = shdu[0].header swcs = WCS(header) scipix = shdu[0].data with fits.open(rms_path, memmap=False) as rhdu: rmspix = rhdu[0].data with fits.open(mask_path, memmap=False) as mhdu: maskpix = mhdu[0].data apertures = photutils.SkyCircularAperture(coord, r=APERTURE_RADIUS) phot_table = photutils.aperture_photometry(scipix, apertures, error=rmspix, wcs=swcs) pixap = apertures.to_pixel(swcs) annulus_masks = pixap.to_mask(method='center') maskpix = [annulus_mask.cutout(maskpix) for annulus_mask in annulus_masks] magzp = header['MAGZP'] apcor = header[APER_KEY] # check for invalid photometry on masked pixels phot_table['flags'] = [ int(np.bitwise_or.reduce(m, axis=(0, 1))) for m in maskpix ] phot_table['zp'] = magzp + apcor phot_table['obsjd'] = header['OBSJD'] phot_table['filtercode'] = 'z' + header['FILTER'][-1] # rename some columns phot_table.rename_column('aperture_sum', 'flux') phot_table.rename_column('aperture_sum_err', 'fluxerr') return phot_table
def _get_apertures(grid): # NOTE: we assume all apertures have the same radius r = np.unique(grid.radiuses) assert r.shape[0] == 1, "Regions have different radiuses!" apertures = photutils.SkyCircularAperture( SkyCoord(ra=grid.centers_ra * u.deg, dec=grid.centers_dec * u.deg, frame='icrs'), r[0] * u.arcsec) return apertures
def test_recovered_flux_aperture(science_image): with fits.open(science_image) as hdul: header = hdul[0].header wcs = WCS(header) footprint = wcs.calc_footprint() # realize fakes randomly throughout the footprint rx = rng.uniform(size=N_FAKE) ry = rng.uniform(size=N_FAKE) # keep things bright mag = rng.uniform(low=15, high=18, size=N_FAKE) minra, mindec = footprint.min(axis=0) maxra, maxdec = footprint.max(axis=0) coord = SkyCoord(minra + (maxra - minra) * rx, mindec + (maxdec - mindec) * ry, unit='deg') fakes.inject_psf(science_image, mag, coord) with fits.open(science_image) as hdul: data = hdul[-2].data header = hdul[0].header table = hdul[-1].data # subtract off the background sigma_clip = SigmaClip(sigma=3.0) bkg = MedianBackground(sigma_clip) bkg_val = bkg.calc_background(data) bkgsub = data - bkg_val APERTURE_RADIUS = 3 * u.pixel for row in table: ra, dec, mag = row['fake_ra'], row['fake_dec'], row['fake_mag'] coord = SkyCoord(ra, dec, unit='deg') apertures = photutils.SkyCircularAperture(coord, r=APERTURE_RADIUS) phot_table = photutils.aperture_photometry(bkgsub, apertures, wcs=wcs) flux = phot_table['aperture_sum'][0] assert abs(-2.5 * np.log10(flux) + header['MAGZP'] + header['APCOR4'] - mag) < 0.05
def measure_backgrounds(cat_table, ref_im): """ Measure the background for all the sources in cat_table, using ref_im. Parameters ---------- cat_table: astropy Table the catalog in astropy table form, as loaded from a .gst.fits photometry catalog ref_im: imageHDU fits image which will be used to estimate the background Returns ------- measurements: list of float a metric for the background intensity at the star's position (photometry / area) mask: 2d ndarray of bool an array containing a bool for each pixel of the image, True if the pixel was ignored for the background calculations """ if not ref_im: # Return a dumb value return cat_table['F814W_CHI'] w = wcs.WCS(ref_im.header) shp = ref_im.data.shape inner_rad = 30 * units.pixel outer_rad = inner_rad + 20 * units.pixel mask_rad = inner_rad # More elaborate way using an actual image (do not care about the # filter for the moment, just take the image (which was given on the # command line) at face value) ra = cat_table['RA'] dec = cat_table['DEC'] c = astropy.coordinates.SkyCoord(ra * units.degree, dec * units.degree) # Annuli, of which the counts per surface area will be used as # background measurements annuli = pu.SkyCircularAnnulus(c, r_in=inner_rad, r_out=outer_rad) area = annuli.to_pixel(w).area() # A mask to make sure that no sources end up in the background # calculation circles = pu.SkyCircularAperture(c, mask_rad) source_masks = circles.to_pixel(w).to_mask() mask_union = np.zeros(shp) for i, ap_mask in enumerate(source_masks): data_slices = list(ap_mask.bbox.slices) # These slices go outside of the box sometimes! In that case we # will override the slices on both sides. # Default slices (go over the whole mask) mask_slices = [slice(None, None), slice(None, None)] # Adjust the slices for x and y if necessary for j in range(2): # DATA: . . a b c d e f # index - - 0 1 2 3 4 5 # --------------- # MASK: - + + + - # index 0 1 2 3 4 # --> DATA_SLICE [0:stop] # --> MASK_SLICE [2:] data_start = data_slices[j].start if data_start < 0: # Move the start from negative n to zero data_slices[j] = slice(0, data_slices[j].stop) # Move the start from 0 to positive n mask_slices[j] = slice(-data_start, mask_slices[j].stop) # --> we slice over the part of the small mask that # falls on the positive side of the axis data_stop = data_slices[j].stop if data_stop > shp[j]: overflow = data_stop - shp[j] # Move the stop 'overflow' to shp[j] data_slices[j] = slice(data_slices[j].start, shp[j]) # Move the stop to 'overflow' pixels from the end mask_slices[j] = slice(mask_slices[j].start, -overflow) # --> slice over the part that falls below the maximum mask_union[data_slices] += ap_mask.data[mask_slices] # Threshold mask_union = mask_union > 0 phot = pu.aperture_photometry(ref_im.data, annuli, wcs=w, mask=mask_union) return phot['aperture_sum'] / area, mask_union
outf.writeto('dendrograms_min1mJy_diff1mJy_mask_pruned.fits', clobber=True) for image, name in ((contfile,''), (radio_image,'KUband')): data = image[0].data.squeeze() mywcs = wcs.WCS(image[0].header).celestial keys = ['{1}cont_flux{0}arcsec'.format(rr.value, name) for rr in radii] columns = {k:[] for k in (keys)} log.info("Doing aperture photometry on {0}".format(name)) for ii, row in enumerate(ProgressBar(pruned_ppcat)): size = max(radii)*2.2 xc,yc = row['x_cen'], row['y_cen'] position = coordinates.SkyCoord(xc, yc, frame='fk5', unit=(u.deg,u.deg)) cutout = Cutout2D(data, position, size, mywcs, mode='partial') for rr in radii: aperture = photutils.SkyCircularAperture(positions=position, r=rr).to_pixel(cutout.wcs) aperture_data = photutils.aperture_photometry(data=cutout.data, apertures=aperture, method='exact') flux_jybeam = aperture_data[0]['aperture_sum'] #flux_jybeam_average = flux_jybeam / aperture.area() #flux_jysr_average = flux_jybeam_average / beam.sr.value #flux_jysr = flux_jysr_average * aperture.area() #flux_jy = (flux_jysr * (pixel_scale**2).to(u.sr).value) columns['{1}cont_flux{0}arcsec'.format(rr.value, name)].append(flux_jybeam/ppbeam) for k in columns: if k not in pruned_ppcat.keys(): pruned_ppcat.add_column(Column(name=k, data=columns[k])) for k in pruned_ppcat.colnames:
def aperture_photometry(calibratable, ra, dec, apply_calibration=False, assume_background_subtracted=False, use_cutout=False, direct_load=None): import photutils from astropy.coordinates import SkyCoord from astropy.io import fits from astropy.table import vstack from astropy.wcs import WCS ra = np.atleast_1d(ra) dec = np.atleast_1d(dec) coord = SkyCoord(ra, dec, unit='deg') if not use_cutout: wcs = calibratable.wcs apertures = photutils.SkyCircularAperture(coord, r=APERTURE_RADIUS) # something that is photometerable implements mask, background, and wcs if not assume_background_subtracted: pixels_bkgsub = calibratable.background_subtracted_image.data else: pixels_bkgsub = calibratable.data bkgrms = calibratable.rms_image.data mask = calibratable.mask_image.data phot_table = photutils.aperture_photometry(pixels_bkgsub, apertures, error=bkgrms, wcs=wcs) phot_table['zp'] = calibratable.header['MAGZP'] + calibratable.header[ 'APCOR4'] phot_table['obsjd'] = calibratable.header['OBSJD'] phot_table['filtercode'] = 'z' + calibratable.header['FILTER'][-1] pixap = apertures.to_pixel(wcs) annulus_masks = pixap.to_mask(method='center') maskpix = [ annulus_mask.cutout(mask.data) for annulus_mask in annulus_masks ] else: phot_table = [] maskpix = [] for s in coord: if direct_load is not None and 'sci' in direct_load: sci_path = direct_load['sci'] else: if assume_background_subtracted: sci_path = calibratable.local_path else: sci_path = calibratable.background_subtracted_image.local_path if direct_load is not None and 'mask' in direct_load: mask_path = direct_load['mask'] else: mask_path = calibratable.mask_image.local_path if direct_load is not None and 'rms' in direct_load: rms_path = direct_load['rms'] else: rms_path = calibratable.rms_image.local_path with fits.open(sci_path, memmap=True) as f: wcs = WCS(f[0].header) pixcoord = wcs.all_world2pix([[s.ra.deg, s.dec.deg]], 0)[0] pixx, pixy = pixcoord nx = calibratable.header['NAXIS1'] ny = calibratable.header['NAXIS2'] xmin = max(0, pixx - 1.5 * APERTURE_RADIUS.value) xmax = min(nx, pixx + 1.5 * APERTURE_RADIUS.value) ymin = max(0, pixy - 1.5 * APERTURE_RADIUS.value) ymax = min(ny, pixy + 1.5 * APERTURE_RADIUS.value) ixmin = int(np.floor(xmin)) ixmax = int(np.ceil(xmax)) iymin = int(np.floor(ymin)) iymax = int(np.ceil(ymax)) ap = photutils.CircularAperture([pixx - ixmin, pixy - iymin], APERTURE_RADIUS.value) # something that is photometerable implements mask, background, and wcs with fits.open(sci_path, memmap=True) as f: pixels_bkgsub = f[0].data[iymin:iymax, ixmin:ixmax] with fits.open(rms_path, memmap=True) as f: bkgrms = f[0].data[iymin:iymax, ixmin:ixmax] with fits.open(mask_path, memmap=True) as f: mask = f[0].data[iymin:iymax, ixmin:ixmax] pt = photutils.aperture_photometry(pixels_bkgsub, ap, error=bkgrms) annulus_mask = ap.to_mask(method='center') mp = annulus_mask.cutout(mask.data) maskpix.append(mp) phot_table.append(pt) phot_table = vstack(phot_table) if apply_calibration: magzp = calibratable.header['MAGZP'] apcor = calibratable.header[APER_KEY] phot_table['mag'] = -2.5 * np.log10( phot_table['aperture_sum']) + magzp + apcor phot_table['magerr'] = 1.0826 * phot_table[ 'aperture_sum_err'] / phot_table['aperture_sum'] # check for invalid photometry on masked pixels phot_table['flags'] = [ int(np.bitwise_or.reduce(m, axis=(0, 1))) for m in maskpix ] # rename some columns phot_table.rename_column('aperture_sum', 'flux') phot_table.rename_column('aperture_sum_err', 'fluxerr') return phot_table
def measure_backgrounds(cat_table, ref_im, mask_radius, ann_width, cat_filter): """ Measure the background for all the sources in cat_table, using ref_im. Parameters ---------- cat_table: astropy Table the catalog in astropy table form, as loaded from a .gst.fits photometry catalog ref_im: imageHDU fits image which will be used to estimate the background mask_radius : float radius (in pixels) of mask for catalog sources ann_width : float width of annulus (in pixels) for calculating background around each catalog source cat_filter : list or None If list: Two elements in which the first is a filter (e.g. 'F475W') and the second is a magnitude. Catalog entries with [filter]_VEGA > mag will not be masked. If None: all catalog entries will be considered. Returns ------- measurements: list of float a metric for the background intensity at the star's position (photometry / area) """ w = wcs.WCS(ref_im.header) shp = ref_im.data.shape inner_rad = mask_radius * units.pixel outer_rad = inner_rad + ann_width * units.pixel mask_rad = inner_rad # More elaborate way using an actual image (do not care about the # filter for the moment, just take the image (which was given on the # command line) at face value) ra = cat_table['RA'] dec = cat_table['DEC'] c = astropy.coordinates.SkyCoord(ra * units.degree, dec * units.degree) # Annuli, of which the counts per surface area will be used as # background measurements annuli = pu.SkyCircularAnnulus(c, r_in=inner_rad, r_out=outer_rad) area = annuli.to_pixel(w).area() # A mask to make sure that no sources end up in the background # calculation if cat_filter is None: circles = pu.SkyCircularAperture(c, mask_rad) else: circles = pu.SkyCircularAperture(c[ cat_table[cat_filter[0]+'_VEGA'] < float(cat_filter[1]) ], mask_rad) source_masks = circles.to_pixel(w).to_mask() mask_union = np.zeros(shp) for i, ap_mask in enumerate(source_masks): # the masks have a bounding box which we need to take into # account. Here we will calculate the overlap between the mask # and the image (important if the mask is near the edge, so that # the box might go outside of it). data_slices = list(ap_mask.bbox.slices) # These slices go outside of the box sometimes! In that case we # will override the slices on both sides. # Default slices (go over the whole mask) mask_slices = [slice(None, None), slice(None, None)] # Adjust the slices in each dimension for j in range(2): # DATA: . . a b c d e f # index - - 0 1 2 3 4 5 # --------------- # MASK: - + + + - # index 0 1 2 3 4 # --> DATA_SLICE [0:stop] # --> MASK_SLICE [2:] data_start = data_slices[j].start if data_start < 0: # Move the start from negative n to zero data_slices[j] = slice(0, data_slices[j].stop) # Move the start from 0 to positive n mask_slices[j] = slice(-data_start, mask_slices[j].stop) # --> we slice over the part of the small mask that # falls on the positive side of the axis data_stop = data_slices[j].stop if data_stop > shp[j]: overflow = data_stop - shp[j] # Move the stop 'overflow' to shp[j] data_slices[j] = slice(data_slices[j].start, shp[j]) # Move the stop to 'overflow' pixels from the end mask_slices[j] = slice(mask_slices[j].start, -overflow) # --> slice over the part that falls below the maximum mask_union[tuple(data_slices)] += ap_mask.data[tuple(mask_slices)] # Threshold mask_union = mask_union > 0 # also mask NaNs mask_union[np.isnan(ref_im.data)] = True # Save the masked reference image hdu = fits.PrimaryHDU(np.where(mask_union, 0, ref_im.data), header=ref_im.header) hdu.writeto('masked_reference_image.fits', overwrite=True) # Do the measurements phot = pu.aperture_photometry(ref_im.data, annuli, wcs=w, mask=mask_union) return phot['aperture_sum'] / area
def funcdrz_aperture_photometry_on_crop(drz, obs, tar, source='qso', fp_image='drz_crop.fits', radii=([1., 2., 3., 4., ])*u.arcsec, stretch='linear', vmin=None, vmax=None): """ measure aperture photometry of images """ s = drz.sources[source] # wcs from drz.fits w = wcs.WCS(fits.getheader(drz.directory+'drz.fits', 1)) # read crop fp = s.directory+fp_image fp_root = os.path.splitext(fp)[0] data = fits.getdata(fp) # define aperture pos_sky = ac.SkyCoord(s.ra, s.dec, frame='icrs', unit='deg') apt_sky = [pu.SkyCircularAperture(pos_sky, r=r) for r in radii] apt_pix = [apt_sky.to_pixel(wcs=w) for apt_sky in apt_sky] # transform from drz.fits pix coordinate to crop pix coordinate pos_pix_crop = apt_pix[0].positions[0] - np.array([s.x, s.y]) + np.array(data.shape[::-1])//2 apertures_pix_crop = copy.copy(apt_pix) for aperture in apertures_pix_crop: aperture.positions = np.array([pos_pix_crop]) phot = at.Table(pu.aperture_photometry(data, apertures_pix_crop)) phot['xcenter'].unit = None phot['ycenter'].unit = None # reorganize phot table phot.remove_column('id') phot.rename_column('xcenter', 'xc_drzcrop') phot.rename_column('ycenter', 'yc_drzcrop') phot['xc_drz'] = apt_pix[0].positions[0][0] phot['yc_drz'] = apt_pix[0].positions[0][1] phot['ra'] = s.ra phot['dec'] = s.dec phot['fn_image'] = fp_image cols = ['fn_image', 'ra', 'dec', 'xc_drz', 'yc_drz', 'xc_drzcrop', 'yc_drzcrop', ] if len(radii) == 1: cols += ['aperture_sum'] else: cols += ['aperture_sum_'+str(i) for i in range(len(radii))] phot = phot[cols] # write photometric results phot.write(fp_root+'_aphot.csv', overwrite=True) # write radii csv radii_df = pd.DataFrame({'tag': ['aperture_sum_'+str(i) for i in range(len(radii))], 'radii': [str(radii[i]) for i in range(len(radii))]}) radii_df = radii_df[['tag','radii']] radii_df.to_csv('radii.csv', index=False) # visual v = tinyfit.visual.visual(fp) v.plot(fn_out=fp_root+'_aphot.pdf', colorbar=True, vmin=vmin, vmax=vmax, stretch=stretch) for a in apertures_pix_crop: a.plot() plt.savefig(fp_root+'_aphot.pdf') plt.close()