def plot_sky_masks(self, r1, r2, minp=0.0, maxp=99.9, cols=5, figsize=(11, 2.5)): aps = CircularAnnulus([self._stars.xcentroid, self._stars.ycentroid], r1, r2) fig, axs = subplots(int(ceil(self.nstars / cols)), cols, figsize=figsize, sharex=True, sharey=True) for m, ax in zip(aps.to_mask(), axs.flat): d = where(m.data.astype('bool'), m.cutout(self.reduced), nan) #m.multiply(self.reduced) ax.imshow(d, cmap=cm.gray_r, origin='image', norm=sn(d, stretch='linear', min_percent=minp, max_percent=maxp)) fig.tight_layout()
def photometry_custom(image_data, radpix, wcs): #aperture = CircularAperture((image_data.shape[0]/2,image_data.shape[0]/2), radpix[19]) position = [image_data.shape[0] / 2, image_data.shape[0] / 2] apertures = [CircularAperture(position, r=r) for r in radpix] annulus_aperture = CircularAnnulus(position, r_in=radpix[19], r_out=radpix[19] + radpix[16]) phot_table = aperture_photometry(image_data, apertures, wcs=wcs) annulus_masks = annulus_aperture.to_mask(method='center') annulus_data = annulus_masks.multiply(image_data) mask = annulus_masks.data annulus_data_1d = annulus_data[mask > 0] _, median_sigclip, _ = sigma_clipped_stats(annulus_data_1d) bkg_mean = median_sigclip #sky_aperture = to_sky(apertures,wcs) phot_array = np.zeros(20) bkg_sum_array = np.zeros(20) for i in range(0, 20): phot_array[i] = phot_table['aperture_sum_' + str(i)][0] bkg_sum_array[i] = bkg_mean * apertures[i].area final_sum = phot_array - bkg_sum_array scale = np.mean(proj_plane_pixel_scales(wcs)) phot_Jy_custom = final_sum print('Backgorud outer radius =', radpix[19] + radpix[16], 'pixels') print(phot_table) return (phot_Jy_custom)
def aper_phot(data, header, positions, aper, data_out, gain=1, rdnoise=0): exptime = header['EXPTIME'] apertures = CircularAperture(positions, r=aper[0]) annulus_apertures = CircularAnnulus(positions, r_in=aper[1], r_out=aper[2]) annulus_masks = annulus_apertures.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_intensity = np.array(bkg_median) * apertures.area() * gain phot = aperture_photometry(data, apertures) phot['signal_intensity'] = phot['aperture_sum'] * gain - bkg_intensity phot['SNR'] = phot['signal_intensity'] / (phot['signal_intensity'] + bkg_intensity + rdnoise**2)**0.5 phot['magnitude'] = -2.5 * np.log10( phot['signal_intensity'] / exptime) + 24 phot['delta_magnitude'] = 2.5 * np.log10(1 + 1 / phot['SNR']) for col in phot.colnames: phot[col].info.format = '%.4f' # for consistent table output imageshow(data, positions, aper=aper) return phot
def _fiber_fracflux(psf, x_centroid=None, y_centroid=None, fib_rad_pix=None): # not really sure if this edge case will ever happen ?? if (np.sum(psf) <= 0): return np.nan, np.nan, np.nan if fib_rad_pix is None: fib_rad_pix = 3.567 * (1.52 / 1.462) # pixels position = (x_centroid, y_centroid) aperture = CircularAperture(position, r=fib_rad_pix) annulus_aperture = CircularAnnulus(position, r_in=25.0, r_out=40.0) annulus_mask = annulus_aperture.to_mask(method='center') annulus_data = annulus_mask.multiply(psf) annulus_data_1d = annulus_data[annulus_mask.data > 0] _, bkg_median, std_bg = sigma_clipped_stats(annulus_data_1d) phot = aperture_photometry(psf, aperture) aper_bkg_tot = bkg_median * _get_area_from_ap(aperture) numerator = phot['aperture_sum'][0] - aper_bkg_tot # aper flux denominator = np.sum(psf) frac = numerator / denominator return frac, numerator, denominator
def _make_apertures(self, colpos, rowpos): """Create an apertures, bg annular, and annular mask object for aperture photometry. See https://photutils.readthedocs.io/en/stable/aperture.html#sigma-clipped-median-within-a-circular-annulus """ positions = np.transpose((colpos, rowpos)) # Radius of circular source region radius ap_radius = math.ceil(self._ap_fwhm_mult * self._search_fwhm) # BG outer annulus radius. By making this 1.5 times ap_radius # the BG annulus has an area 1.25 times the inner circle, so # we should get decent sampling. outer_radius = math.ceil(1.5 * ap_radius) self._logger.debug(f'Radius of circular aperture photometry is {ap_radius} pixels.') self._logger.debug(f'Local BG estimation in annulus outer radius {outer_radius} pixels, inner radius {ap_radius} pixels.') apertures = CircularAperture(positions, r=ap_radius) bg_apertures = CircularAnnulus(positions, r_in=ap_radius, r_out=outer_radius) bg_aperture_masks = bg_apertures.to_mask(method='center') return apertures, bg_apertures, bg_aperture_masks
def _aper_local_background(self): """ Estimate the local background and error using a circular annulus aperture. The local backround is the sigma-clipped median value in the annulus. The background error is the standard error of the median, sqrt(pi / 2N) * std. """ bkg_aper = CircularAnnulus( self.xypos, self.aperture_params['bkg_aperture_inner_radius'], self.aperture_params['bkg_aperture_outer_radius']) bkg_aper_masks = bkg_aper.to_mask(method='center') sigclip = SigmaClip(sigma=3) nvalues = [] bkg_median = [] bkg_std = [] for mask in bkg_aper_masks: bkg_data = mask.multiply(self.model.data.value) bkg_data_1d = bkg_data[mask.data > 0] values = sigclip(bkg_data_1d, masked=False) nvalues.append(values.size) bkg_median.append(np.median(values)) bkg_std.append(np.std(values)) nvalues = np.array(nvalues) bkg_median = np.array(bkg_median) # standard error of the median bkg_median_err = np.sqrt(np.pi / (2. * nvalues)) * np.array(bkg_std) bkg_median <<= self.model.data.unit bkg_median_err <<= self.model.data.unit return bkg_median, bkg_median_err
def nb_cogphot(nbima, nbvar, xc, yc, maxrad=15, growthlim=1.025, plots=False): from photutils import CircularAperture, CircularAnnulus from photutils import aperture_photometry from astropy.stats import sigma_clipped_stats as scl from astropy.convolution import convolve, Box2DKernel rad = np.arange(1, maxrad + 1) phot = np.zeros_like(rad, dtype=float) photerr = np.zeros_like(rad, dtype=float) growth = np.zeros_like(rad, dtype=float) skyaper = CircularAnnulus((xc - 1, yc - 1), r_in=maxrad, r_out=np.max([1.5 * maxrad, 20])) skymask = skyaper.to_mask(method='center') skydata = skymask.multiply(nbima)[skymask.data > 0] bkg_avg, bkg_med, _ = scl(skydata) for ii, r in enumerate(rad): aper = CircularAperture((xc - 1, yc - 1), r=r) phot[ii] = (aperture_photometry( nbima, aper))['aperture_sum'][0] - bkg_med * aper.area photerr[ii] = np.sqrt((aperture_photometry(nbvar, aper))['aperture_sum'][0]) if ii < 3: growth[ii] = 100 else: growth[ii] = phot[ii] / phot[ii - 1] rlim = np.argmin(growth > growthlim) - 1 fluxarr = phot[rlim] errarr = photerr[rlim] radarr = rad[rlim] if plots: fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(5, 5)) ax.imshow(convolve(nbima, Box2DKernel(5)), vmin=-2, vmax=30, origin='lower') #ax.imshow(tmpnbima, vmin=-2, vmax=30, origin='lower') circ = plt.Circle((xc - 1, yc - 1), rad[rlim], color='r', fill=False, lw=3) ax.add_artist(circ) ax.set_xlim(xc - 50, xc + 50) ax.set_ylim(yc - 50, yc + 50) plt.show() plt.plot(rad, phot) plt.show() return fluxarr, errarr, radarr
def do_photometry(image, source_table, aperture_radius=3, subtract_background=False, annulus_radii=(6, 8)): """Perform aperture photometry on the input data using the source locations specified in the source_table Parameters ---------- image : numpy.ndarray 2D image source_table : astropy.table.Table Table of source locations (i.e. output from photutils's DAOStarFinder) subtract_background : bool If True, use photutils to estimate and subtract background Returns ------- source_table : astropy.table.Table Updated source_table including photometry results """ # Create list of tuples giving source locations positions = [(tab['xcentroid'], tab['ycentroid']) for tab in source_table] apertures = CircularAperture(positions, r=aperture_radius) # If background is to be subtracted, calculate the sigma-clipped median # value of the pixels in the annuli around the sources if subtract_background: annulus_apertures = CircularAnnulus(positions, r_in=annulus_radii[0], r_out=annulus_radii[1]) annulus_masks = annulus_apertures.to_mask(method='center') bkg_median = [] for mask in annulus_masks: annulus_data = mask.multiply(image) 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) # Do the photometry and add to the input table phot_table = aperture_photometry(image, apertures) source_table['aperture_sum'] = phot_table['aperture_sum'] if subtract_background: # Add columns with that background subtraction info source_table['annulus_median'] = bkg_median source_table['aper_bkg'] = bkg_median * apertures.area source_table['aper_sum_bkgsub'] = source_table[ 'aperture_sum'] - source_table['aper_bkg'] return source_table
def do_Circ_phot(pos, FWHM, ap_min=3., ap_factor=1.5, rin=None, rout=None, \ sky_nsigma=3., sky_iter=10): ''' In rigorous manner, we should use error = sqrt (flux / epadu + area * stdev**2 + area**2 * stdev**2 / nsky) as in http://stsdas.stsci.edu/cgi-bin/gethelp.cgi?phot . Here flux == aperture_sum - sky, i.e., flux from image_reduc. stdev should include sky error AND ronoise. ''' if rin == None: rin = 4 * FWHM if rout == None: rout = 6 * FWHM N = len(pos) if pos.ndim == 1: N = 1 an = CircAn(pos, r_in=rin, r_out=rout) ap_size = np.max([ap_min, ap_factor*FWHM]) aperture = CircAp(pos, r=ap_size) flux = aperture.do_photometry(image_reduc, method='exact')[0] # do phot and get sum from aperture. [0] is sum and [1] is error. #For test: #N=len(pos_star_fit) #an = CircAn(pos_star_fit, r_in=4*FWHM_moffat, r_out=6*FWHM_moffat) #ap_size = 1.5*FWHM_moffat #aperture = CircAp(pos_star_fit, r=ap_size) #flux = aperture.do_photometry(image_reduc, method='exact')[0] flux_ss = np.zeros(N) error = np.zeros(N) for i in range(0, N): mask_an = (an.to_mask(method='center'))[i] # cf: test = mask_an.cutout(image_reduc) <-- will make cutout image. sky_an = mask_an.apply(image_reduc) all_sky = sky_an[np.nonzero(sky_an)] # only annulus region will be saved as np.ndarray msky, stdev, nsky, nrej = sky_fit(all_sky, method='Mode', mode_option='sex') area = aperture.area() flux_ss[i] = flux[i] - msky*area # sky subtracted flux error[i] = np.sqrt( flux_ss[i]/gain \ + area * stdev**2 \ + area**2 * stdev**2 / nsky ) # To know rejected number, uncomment the following. # plt.imshow(sky_an, cmap='gray_r', vmin=-20) # plt.colorbar() # plt.show() # mask_ap = (aperture.to_mask(method='exact'))[i] # star_ap = mask_ap.apply(image_reduc) # plt.imshow(star_ap) # plt.colorbar() # plt.show() # plt.cla() if pos.ndim > 1: print('\t[{x:7.2f}, {y:7.2f}], {nsky:3d} {nrej:3d} {msky:7.2f} {stdev:7.2f} {flux:7.1f} {ferr:3.1f}'.format(\ x=pos[i][0], y=pos[i][1], \ nsky=nsky, nrej=nrej, msky=msky, stdev=stdev,\ flux=flux_ss[i], ferr=error[i])) return flux_ss, error
def pmgstars_forced_phot(xcentroid, ycentroid, image, elg=False, bgs=False): assert(len(xcentroid) > 0) assert(len(ycentroid) > 0) # create the apertures # get the fluxes print('Attempting to do forced aperture photometry') # shouldn't happen... assert(not (elg and bgs)) if elg or bgs: par = common.gfa_misc_params() param = 'exp_kernel_filename' if elg else 'devauc_kernel_filename' fname = os.path.join(os.environ[par['meta_env_var']], par[param]) kern = fits.getdata(fname) # non-optimal to repeatedly read this... image = ndimage.convolve(image, kern, mode='constant') positions = list(zip(xcentroid, ycentroid)) # the 1.52/1.462 factor comes from David Schlegel's request # to have gfa_reduce fiber flux fraction related quantities # be referenced to a 1.52 asec diameter aperture, even though # the angular diameter corresponding to a 107 um fiber at the # GFA focal plane position is smaller (1.462 asec using GFA # platescale geometric mean); see SurveySpeed wiki page for 1.52 value radius = 3.567*(1.52/1.462) # pixels apertures = CircularAperture(positions, r=radius) annulus_apertures = CircularAnnulus(positions, r_in=60.0, r_out=65.0) annulus_masks = annulus_apertures.to_mask(method='center') bkg_median = [] for mask in annulus_masks: annulus_data = mask.multiply(image) annulus_data_1d = annulus_data[mask.data > 0] # this sigma_clipped_stats call is actually the slow part !! _, median_sigclip, std_bg = sigma_clipped_stats(annulus_data_1d) bkg_median.append(median_sigclip) bkg_median = np.array(bkg_median) phot = aperture_photometry(image, apertures) aper_bkg_tot = bkg_median*_get_area_from_ap(apertures[0]) aper_fluxes = np.array(phot['aperture_sum']) - aper_bkg_tot return aper_fluxes
def load_bkg_cutout(self, manual_mask=False, col_mask_start=0, col_mask_end=0, row_mask_start=0, row_mask_end=0): ''' Only need to manually mask the background if the number of bad pixels in the background annulus is more than half of the total; Otherwise *median absolute deviation*(or percentiles) should give a robust estimate of the background noise. imgpath = pobj.imgpath pixX = pobj.pixX pixY = pobj.pixY # bad_threshold = pobj.bad_threshold r_bkg_in = pobj.r_bkg_in r_bkg_out = pobj.r_bkg_out ''' imgpath = self.imgpath pixX = self.pixX pixY = self.pixY # bad_threshold = self.bad_threshold r_bkg_in = self.r_bkg_in r_bkg_out = self.r_bkg_out dt = fits.open(imgpath)[1].data positions = [(pixX, pixY)] annulus_aperture = CircularAnnulus(positions, r_in = r_bkg_in, r_out = r_bkg_out) annulus_masks = annulus_aperture.to_mask(method='center') annulus_data = annulus_masks[0].multiply(dt) bkg_fn = deepcopy(annulus_data) bad_bkg_mask = np.isnan(annulus_data) if manual_mask == True: bad_bkg_mask[r_bkg_out+row_mask_start:r_bkg_out+row_mask_end, r_bkg_out+col_mask_start:r_bkg_out+col_mask_end] = True nbad_bkg = np.sum(bad_bkg_mask) self.nbad_bkg = nbad_bkg setnan = annulus_masks[0].data==0 bkg_fn[setnan] = np.nan # bkgstd = np.nanstd(bkg_fn) temp = bkg_fn.ravel() temp = temp[~np.isnan(temp)] bkgstd = 0.5 * (np.percentile(temp, 84.13)-np.percentile(temp, 15.86)) # bkgstd = np.median(abs(temp - np.median(temp))) bkgmed = np.median(temp) self.bkgstd = bkgstd self.bkg_fn = bkg_fn self.bkgmed = bkgmed if self.verbose == True: print ('\t bkgstd pixel RMS in original diff-image cutout = %.2f DN'%(self.bkgstd)) print ('\t bkgmed pixel in original diff-image cutout = %.2f DN'%(self.bkgmed))
def psf_radial_reduce(img, reduction: Callable[[np.ndarray], float] = np.mean): # get center of image. xcenter, ycenter = centroid_quadratic(img) # last radius in pixel where ring is fully in image extent = np.min(img.shape)/2 radii = np.linspace(0.1, extent, int(extent)) values = [] for r_in, r_out in zip(radii, radii[1:]): aper = CircularAnnulus([xcenter, ycenter], r_in, r_out) mask = aper.to_mask('center') values.append(reduction(mask.get_values(img))) return radii[:-1], np.array(values)/np.max(img)
def pc_aper_phot(im, cat, one_aper=False, bg_sigclip=False): im = im.astype(float) par = common.pc_params() positions = list(zip(cat['xcentroid'], cat['ycentroid'])) radii = par['aper_phot_objrad'] if not one_aper else [par['aper_phot_objrad_best']] ann_radii = par['annulus_radii'] # should have 2 elements - inner and outer apertures = [CircularAperture(positions, r=r) for r in radii] annulus_apertures = CircularAnnulus(positions, r_in=ann_radii[0], r_out=ann_radii[1]) annulus_masks = annulus_apertures.to_mask(method='center') bkg_median = [] for mask in annulus_masks: annulus_data = mask.multiply(im) annulus_data_1d = annulus_data[mask.data > 0] if bg_sigclip: # this sigma_clipped_stats call is actually the slow part !! _, median_sigclip, std_bg = sigma_clipped_stats(annulus_data_1d) bkg_median.append(median_sigclip) else: bkg_median.append(np.median(annulus_data_1d)) bkg_median = np.array(bkg_median) phot = aperture_photometry(im, apertures) for i, aperture in enumerate(apertures): aper_bkg_tot = bkg_median*_get_area_from_ap(aperture) cat['aper_sum_bkgsub_' + str(i)] = phot['aperture_sum_' + str(i)] - aper_bkg_tot cat['aper_bkg_' + str(i)] = aper_bkg_tot cat['sky_annulus_area_pix'] = _get_area_from_ap(annulus_apertures) cat['sky_annulus_median'] = bkg_median flux_adu = np.zeros((len(cat), len(radii)), dtype=float) for i in range(len(radii)): flux_adu[:, i] = cat['aper_sum_bkgsub_' + str(i)] cat['flux_adu'] = flux_adu return cat
def ap_phot(image, star_tbl, read_noise, exposure, r=1.5, r_in=1.5, r_out=3.): ''' Given an image, go do some aperture photometry ''' from astropy.stats import sigma_clipped_stats from photutils import aperture_photometry, CircularAperture, CircularAnnulus from photutils.utils import calc_total_error # Build apertures from star_tbl positions = np.transpose([star_tbl['x'], star_tbl['y']]) apertures = CircularAperture(positions, r=r) annulus_apertures = CircularAnnulus(positions, r_in=r_in, r_out=r_out) annulus_masks = annulus_apertures.to_mask(method='center') # Get backgrounds in annuli bkg_median = [] for mask in annulus_masks: annulus_data = mask.multiply(image) annulus_data_1d = annulus_data[mask.data > 0] _, median_sigclip, _ = sigma_clipped_stats(annulus_data_1d) bkg_median.append(median_sigclip.value) bkg_median = np.array(bkg_median) * image.unit # Set error error = calc_total_error(image.value, read_noise / exposure.value, exposure.value) error *= image.unit # Perform aperture photometry result = aperture_photometry(image, apertures, error=error) result['annulus_median'] = bkg_median result['aper_bkg'] = bkg_median * apertures.area() result['aper_sum_bkgsub'] = result['aperture_sum'] - result['aper_bkg'] # To-do: fold an error on background level into the aperture photometry error for col in result.colnames: result[col].info.format = '%.8g' # for consistent table output # print("Aperture photometry complete") return result, apertures, annulus_apertures
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
class PhotutilsAperturePhotometry(Block): r""" Aperture photometry using the :code:`CircularAperture` and :code:`CircularAnnulus` of photutils_ with a wide range of apertures. By default annulus goes from 5 fwhm to 8 fwhm and apertures from 0.1 to 10 times the fwhm with 0.25 steps (leading to 40 apertures). The error (e.g. in ADU) is then computed following: .. math:: \sigma = \sqrt{S + (A_p + \frac{A_p}{A_n})(b + r^2 + \frac{gain^2}{2}) + scint } .. image:: images/aperture_phot.png :align: center :width: 110px with :math:`S` the flux (ADU) within an aperture of area :math:`A_p`, :math:`b` the background flux (ADU) within an annulus of area :math:`A_n`, :math:`r` the read-noise (ADU) and :math:`scint` is a scintillation term expressed as: .. math:: scint = \frac{S_fd^{2/3} airmass^{7/4} h}{16T} with :math:`S_f` a scintillation factor, :math:`d` the aperture diameter (m), :math:`h` the altitude (m) and :math:`T` the exposure time. The positions of individual stars are taken from :code:`Image.stars_coords` so one of the detection block should be used, placed before this one. For more details check https://photutils.readthedocs.io/en/stable/aperture.html |write| - ``Image.stars_coords`` - ``Image.apertures_area`` - ``Image.sky`` - ``Image.fluxes`` - ``Image.annulus_area`` - ``Image.annulus_rin`` - ``Image.annulus_rout`` - ``Image.apertures_radii`` - ``Image.fluxes`` |modify| Parameters ---------- apertures : ndarray or list, optional apertures in fraction of fwhm, by default None, i.e. np.arange(0.1, 8, 0.25) r_in : int, optional radius of the inner annulus in fraction of fwhm, by default 5 r_out : int, optional radius of the outer annulus in fraction of fwhm, by default 8 scale: bool or float: Multiplication factor applied to `apertures`. - if True: `apertures` multiplied by image.fwhm, varying for each image - if False: `apertures` not multiplied - if float: `apertures` multiplied `scale` and held fixed for all images """ def __init__(self, apertures=None, r_in=5, r_out=8, scale=True, sigclip=2., **kwargs): super().__init__(**kwargs) if apertures is None: self.apertures = np.arange(0.1, 8, 0.25) else: self.apertures = apertures self.annulus_inner_radius = r_in self.annulus_outer_radius = r_out self.annulus_final_rin = None self.annulus_final_rout = None self.aperture_final_r = None self.n_apertures = len(self.apertures) self.n_stars = None self.circular_apertures = None self.annulus_apertures = None self.annulus_masks = None self.circular_apertures_area = None self.annulus_area = None self.scale = scale self.sigclip = sigclip self._has_fix_scale = not isinstance(self.scale, bool) def set_apertures(self, stars_coords, fwhm=1): self.annulus_final_rin = self.annulus_inner_radius * fwhm self.annulus_final_rout = self.annulus_outer_radius * fwhm self.aperture_final_r = fwhm * self.apertures self.annulus_apertures = CircularAnnulus( stars_coords, r_in=self.annulus_final_rin, r_out=self.annulus_final_rout, ) if callable(self.annulus_apertures.area): self.annulus_area = self.annulus_apertures.area() else: self.annulus_area = self.annulus_apertures.area self.circular_apertures = [ CircularAperture(stars_coords, r=r) for r in self.aperture_final_r ] # Unresolved buf; sometimes circular_apertures.area is a method, sometimes a float if callable(self.circular_apertures[0].area): self.circular_apertures_area = [ ca.area() for ca in self.circular_apertures ] else: self.circular_apertures_area = [ ca.area for ca in self.circular_apertures ] self.annulus_masks = self.annulus_apertures.to_mask(method="center") self.n_stars = len(stars_coords) def run(self, image): try: if self._has_fix_scale: self.set_apertures(image.stars_coords, self.scale) elif self.scale: self.set_apertures(image.stars_coords, image.fwhm) else: self.set_apertures(image.stars_coords) except ZeroDivisionError: # temporary image.discard = True return None bkg_median = [] for mask in self.annulus_masks: annulus_data = mask.multiply(image.data) if annulus_data is not None: annulus_data_1d = annulus_data[mask.data > 0] _, median_sigma_clip, _ = sigma_clipped_stats( annulus_data_1d, sigma=self.sigclip) bkg_median.append(median_sigma_clip) else: bkg_median.append(0.) bkg_median = np.array(bkg_median) image.apertures_area = self.circular_apertures_area image.annulus_sky = bkg_median image.sky = bkg_median.mean() image.fluxes = np.zeros((self.n_apertures, self.n_stars)) image.annulus_area = self.annulus_area image.annulus_rin = self.annulus_final_rin image.annulus_rout = self.annulus_final_rout image.apertures_radii = self.aperture_final_r data = image.data.copy() data[data < 0] = 0 photometry = aperture_photometry(data, self.circular_apertures) fluxes = np.array([ photometry[f"aperture_sum_{a}"] - (bkg_median * self.circular_apertures_area[a]) for a in range(len(self.apertures)) ]) # dummy values if negative or nan fluxes[np.isnan(fluxes)] = 1 fluxes[fluxes < 0] = 1 image.fluxes = fluxes self.compute_error(image) image.header["sky"] = np.mean(image.sky) def compute_error(self, image): image.errors = np.zeros((self.n_apertures, self.n_stars)) for i, aperture_area in enumerate(self.circular_apertures_area): area = aperture_area * (1 + aperture_area / self.annulus_area) image.errors[i, :] = image.telescope.error( image.fluxes[i], area, image.sky, image.exposure, airmass=image.get("keyword_airmass"), ) def citations(self): return "astropy", "photutils"
async def __call__(self, image: Image) -> Image: """Do aperture photometry on given image. Args: image: Image to do aperture photometry on. Returns: Image with attached catalog. """ loop = asyncio.get_running_loop() # no pixel scale given? if image.pixel_scale is None: log.warning("No pixel scale provided by image.") return image # fetch catalog if image.catalog is None: log.warning("No catalog in image.") return image sources = image.catalog.copy() # get positions positions = [(x - 1, y - 1) for x, y in sources.iterrows("x", "y")] # perform aperture photometry for diameters of 1" to 8" for diameter in [1, 2, 3, 4, 5, 6, 7, 8]: # extraction radius in pixels radius = diameter / 2.0 / image.pixel_scale if radius < 1: continue # defines apertures aperture = CircularAperture(positions, r=radius) annulus_aperture = CircularAnnulus(positions, r_in=2 * radius, r_out=3 * radius) annulus_masks = annulus_aperture.to_mask(method="center") # loop annuli bkg_median = [] for m in annulus_masks: annulus_data = m.multiply(image.data) annulus_data_1d = annulus_data[m.data > 0] _, median_sigclip, _ = sigma_clipped_stats(annulus_data_1d) bkg_median.append(median_sigclip) # do photometry phot = await loop.run_in_executor( None, partial(aperture_photometry, image.data, aperture, mask=image.mask, error=image.uncertainty)) # calc flux bkg_median_np = np.array(bkg_median) aper_bkg = bkg_median_np * aperture.area sources["fluxaper%d" % diameter] = phot["aperture_sum"] - aper_bkg if "aperture_sum_err" in phot.columns: sources["fluxerr%d" % diameter] = phot["aperture_sum_err"] sources["bkgaper%d" % diameter] = bkg_median_np # copy image, set catalog and return it img = image.copy() img.catalog = sources return img
def compare_rate_images(subarray_file, fullframe_file, out_dir='./', subarray_threshold=100, fullframe_threshold=100, max_separation=3., aperture_radius=5, bkgd_in_radius=6, bkgd_out_radius=8, subarray_rate_file=None, fullframe_rate_file=None): """MAIN FUNCTiON FOR COMPARING SOURCE RATES IN RATE FILES Parameters ---------- subarray_file : str Fits file containing the subarray data to be compared fullframe_file : sr Fits file containing the full frame data to be compared out_dir : str Output directory into which products are saved subarray_threshold : float Number of sigma above the background needed to identify a source fullframe_threshold : float Number of sigma above the background needed to identify a source max_separation : float Maximum allowed separation between sources, in pixels, between the subarray and full frame data in order for a source to be considered a match. aperture_radius : int Aperture radius (in pixels) to use for photometry bkgd_in_radius : int Inner radius (in pixels) to use for photometry background subtraction bkgd_out_radius : int Outer radius (in pixels) to use for photometry background subtraction Returns ------- sub_sources : astropy.table.Table Table of source positions, equivalent full frame positions, and photometry results from the data """ # Read in data subarray = datamodels.open(subarray_file) fullframe = datamodels.open(fullframe_file) # Quick test to see if using the rate files rather than the cal files # makes any difference in the LW discrepancy between fullframe and subarray data #if subarray_rate_file is not None: # subarray_rate = datamodels.open(subarray_rate_file) #else: # subarray_rate = subarray #if fullframe_rate_file is not None: # fullframe_rate = datamodels.open(fullframe_rate_file) #else: # fullframe_rate = fullframe # Define coord transform functions sub_xy_to_radec = subarray.meta.wcs.get_transform('detector', 'world') ff_radec_to_xy = fullframe.meta.wcs.get_transform('world', 'detector') ff_xy_to_radec = fullframe.meta.wcs.get_transform('detector', 'world') # Check to be sure the two images overlap ff_min_x = 0 ff_min_y = 0 ff_max_x = fullframe.meta.subarray.xsize - 1 ff_max_y = fullframe.meta.subarray.ysize - 1 cornerx = [ 0, 0, subarray.meta.subarray.xsize, subarray.meta.subarray.xsize ] cornery = [ 0, subarray.meta.subarray.ysize, subarray.meta.subarray.ysize, 0 ] cornerra, cornerdec = sub_xy_to_radec(cornerx, cornery) ffcornerx, ffcornery = ff_radec_to_xy(cornerra, cornerdec) overlap = False for fcx, fcy in zip(ffcornerx, ffcornery): if ((fcx > ff_min_x) & (fcx < ff_max_x) & (fcy > ff_min_y) and (fcy < ff_max_y)): overlap = True if not overlap: print( "Suabrray file and full frame file do not overlap. Quitting.\n\n") return 0 # Locate sources sub_fwhm = get_fwhm(subarray.meta.instrument.filter) sub_source_image = os.path.join( out_dir, '{}_subarray_source_map_countrate_compare.png'.format( os.path.basename(subarray_file))) #sub_sources = find_sources(subarray.data, threshold=subarray_threshold, fwhm=sub_fwhm, show_sources=True, save_sources=True, plot_name=sub_source_image) sub_sources = find_sources(subarray_rate.data, threshold=subarray_threshold, fwhm=sub_fwhm, show_sources=True, save_sources=True, plot_name=sub_source_image) ff_fwhm = get_fwhm(fullframe.meta.instrument.filter) full_source_image = os.path.join( out_dir, '{}_fullframe_map_datamodels.png'.format( os.path.basename(fullframe_file))) #ff_sources = find_sources(fullframe.data, threshold=fullframe_threshold, fwhm=ff_fwhm, show_sources=True, save_sources=True, plot_name=full_source_image) ff_sources = find_sources(fullframe_rate.data, threshold=fullframe_threshold, fwhm=ff_fwhm, show_sources=True, save_sources=True, plot_name=full_source_image) if sub_sources is None: print("No subarray sources to compare.") return 0 if ff_sources is None: print("No full frame sources to compare") return 0 # Put subarray sources in full frame detector coordinates # 1. Transform to RA, Dec # 2. Use WCS of full frame file to put coordinates in full frame coords # Transform subarray x,y -> RA, Dec -> full frame x, y ra, dec = sub_xy_to_radec(sub_sources['xcentroid'], sub_sources['ycentroid']) ffx, ffy = ff_radec_to_xy(ra, dec) # Add RA, Dec and fullframe equivalent x,y to the subarray source catalog sub_sources['RA'] = ra sub_sources['Dec'] = dec sub_sources['fullframe_x'] = ffx sub_sources['fullframe_y'] = ffy # Find RA, Dec of sources in full frame catalog ffra, ffdec = ff_xy_to_radec(ff_sources['xcentroid'], ff_sources['ycentroid']) ff_sources['RA'] = ffra ff_sources['Dec'] = ffdec # Match catalogs ff_cat = SkyCoord(ra=ffra * u.degree, dec=ffdec * u.degree) sub_cat = SkyCoord(ra=ra * u.degree, dec=dec * u.degree) idx, d2d, d3d = sub_cat.match_to_catalog_3d(ff_cat) # Remove bad matches pscale = subarray.meta.wcsinfo.cdelt1 if pscale is None: pscale = np.abs(subarray.meta.wcsinfo.cd1_1) pix_scale = pscale * 3600. max_sep = max_separation * pix_scale * u.arcsec # Matches must be within a pixel sep_constraint = d2d < max_sep sub_catalog_matches = sub_sources[sep_constraint] ff_catalog_matches = ff_sources[idx[sep_constraint]] num_matched = len(ff_catalog_matches) print('Found {} matching sources in the two input files.'.format( num_matched)) if num_matched == 0: print("No matching sources found. Quitting.\n\n\n") return 0, 0 # What if we just do photometry on the sources in the subarray and their # calculated positions in the full frame? sub_pos = [] ff_pos = [] good_indexes = [] non_zero_sub_dq = [] non_zero_full_dq = [] for i, line in enumerate(sub_catalog_matches): if ((line['fullframe_x'] > 5) & (line['fullframe_x'] < 2039) & \ (line['fullframe_y'] > 5) & (line['fullframe_y'] < 2039)): sub_pos.append((line['xcentroid'], line['ycentroid'])) #ff_pos.append((line['fullframe_x'], line['fullframe_y'])) ff_pos.append((ff_catalog_matches[i]['xcentroid'], ff_catalog_matches[i]['ycentroid'])) good_indexes.append(i) # Make a note if there are any non-zero DQ flags within the apertures # During development, found some cases with NO_LIN_CORR and NO_FLAT_FIELD # that were screwing up photometry yc_sub = int(np.round(line['ycentroid'])) xc_sub = int(np.round(line['xcentroid'])) sub_dq = subarray.dq[yc_sub - 3:yc_sub + 4, xc_sub - 3:xc_sub + 4] if np.sum(sub_dq) > 0: non_zero_sub_dq.append(True) else: non_zero_sub_dq.append(False) yc = int(np.round(line['fullframe_x'])) xc = int(np.round(line['fullframe_y'])) sub_dq = fullframe.dq[yc - 1:yc + 2, xc - 1:xc + 2] if np.sum(sub_dq) > 0: non_zero_full_dq.append(True) else: non_zero_full_dq.append(False) print('Performing photometry on a total of {} sources.'.format( len(sub_pos))) # Now perform aperture photometry on the sources in the subarray # and full frame data. Keep the aperture small since the difference # in exposure time and SNR will be large #sub_pos = [(m['xcentroid'], m['ycentroid']) for m in sub_catalog_matches] #ff_pos = [(m['xcentroid'], m['ycentroid']) for m in ff_catalog_matches] sub_aperture = CircularAperture(sub_pos, r=aperture_radius) ff_aperture = CircularAperture(ff_pos, r=aperture_radius) sub_annulus = CircularAnnulus(sub_pos, r_in=bkgd_in_radius, r_out=bkgd_out_radius) full_annulus = CircularAnnulus(ff_pos, r_in=bkgd_in_radius, r_out=bkgd_out_radius) # Photometry #sub_phot_table = aperture_photometry(subarray.data, sub_aperture) #ff_phot_table = aperture_photometry(fullframe.data, ff_aperture) sub_phot_table = aperture_photometry(subarray_rate.data, sub_aperture) ff_phot_table = aperture_photometry(fullframe_rate.data, ff_aperture) sub_annulus_masks = sub_annulus.to_mask(method='center') full_annulus_masks = full_annulus.to_mask(method='center') #sub_bkg_median = median_background(sub_annulus_masks, subarray.data) sub_bkg_median = median_background(sub_annulus_masks, subarray_rate.data) sub_phot_table['annulus_median'] = sub_bkg_median sub_phot_table['aper_bkg'] = sub_bkg_median * sub_aperture.area sub_phot_table['aper_sum_bkgsub'] = sub_phot_table[ 'aperture_sum'] - sub_phot_table['aper_bkg'] #full_bkg_median = median_background(full_annulus_masks, fullframe.data) full_bkg_median = median_background(full_annulus_masks, fullframe_rate.data) ff_phot_table['annulus_median'] = full_bkg_median ff_phot_table['aper_bkg'] = full_bkg_median * ff_aperture.area ff_phot_table['aper_sum_bkgsub'] = ff_phot_table[ 'aperture_sum'] - ff_phot_table['aper_bkg'] # Compare photometry results delta_phot = ff_phot_table['aper_sum_bkgsub'].data - sub_phot_table[ 'aper_sum_bkgsub'].data delta_phot_perc = delta_phot / ff_phot_table['aper_sum_bkgsub'].data * 100. sub_phot_table['delta_from_fullframe'] = delta_phot sub_phot_table['delta_from_fullframe_percent'] = delta_phot_perc # Keep track of whether there are bad pixels in the apertures #sub_dq = np.zeros(len(sub_sources), dtype=bool) #sub_dq[good_indexes] = non_zero_sub_dq #sub_sources['sub_dq'] = sub_dq #full_dq = np.zeros(len(sub_sources), dtype=bool) #full_dq[good_indexes] = non_zero_full_dq #sub_sources['full_dq'] = full_dq sub_dq = np.zeros(len(sub_catalog_matches), dtype=bool) sub_dq[good_indexes] = non_zero_sub_dq sub_catalog_matches['sub_dq'] = sub_dq full_dq = np.zeros(len(sub_catalog_matches), dtype=bool) full_dq[good_indexes] = non_zero_full_dq sub_catalog_matches['full_dq'] = full_dq # Add photometry to the table #sub_phot_data = np.zeros(len(sub_sources)) #sub_phot_data[good_indexes] = sub_phot_table['aper_sum_bkgsub'].data #ff_phot_data = np.zeros(len(sub_sources)) #ff_phot_data[good_indexes] = ff_phot_table['aper_sum_bkgsub'].data #delta_phot_col = np.zeros(len(sub_sources)) #delta_phot_col[good_indexes] = delta_phot #delta_phot_perc_col = np.zeros(len(sub_sources)) #delta_phot_perc_col[good_indexes] = delta_phot_perc sub_phot_data = np.zeros(len(sub_catalog_matches)) sub_phot_data[good_indexes] = sub_phot_table['aper_sum_bkgsub'].data ff_phot_data = np.zeros(len(sub_catalog_matches)) ff_phot_data[good_indexes] = ff_phot_table['aper_sum_bkgsub'].data delta_phot_col = np.zeros(len(sub_catalog_matches)) delta_phot_col[good_indexes] = delta_phot delta_phot_perc_col = np.zeros(len(sub_catalog_matches)) delta_phot_perc_col[good_indexes] = delta_phot_perc #sub_sources['sub_phot'] = sub_phot_data #sub_sources['ff_phot'] = ff_phot_data #sub_sources['d_phot'] = delta_phot_col #sub_sources['d_phot_p'] = delta_phot_perc_col #sub_sources['xcentroid'].info.format = '7.3f' #sub_sources['ycentroid'].info.format = '7.3f' #sub_sources['fullframe_x'].info.format = '7.3f' #sub_sources['fullframe_y'].info.format = '7.3f' #sub_sources['sub_phot'].info.format = '7.3f' #sub_sources['ff_phot'].info.format = '7.3f' #sub_sources['d_phot'].info.format = '7.3f' #sub_sources['d_phot_p'].info.format = '7.3f' #print(sub_sources['xcentroid', 'ycentroid', 'fullframe_x', 'fullframe_y', 'sub_phot', 'ff_phot', 'd_phot_p', 'sub_dq', 'full_dq']) sub_catalog_matches['sub_phot'] = sub_phot_data sub_catalog_matches['ff_phot'] = ff_phot_data sub_catalog_matches['d_phot'] = delta_phot_col sub_catalog_matches['d_phot_p'] = delta_phot_perc_col sub_catalog_matches['xcentroid'].info.format = '7.3f' sub_catalog_matches['ycentroid'].info.format = '7.3f' sub_catalog_matches['fullframe_x'].info.format = '7.3f' sub_catalog_matches['fullframe_y'].info.format = '7.3f' sub_catalog_matches['sub_phot'].info.format = '7.3f' sub_catalog_matches['ff_phot'].info.format = '7.3f' sub_catalog_matches['d_phot'].info.format = '7.3f' sub_catalog_matches['d_phot_p'].info.format = '7.3f' final_sub_cat = sub_catalog_matches[good_indexes] print(final_sub_cat['xcentroid', 'ycentroid', 'fullframe_x', 'fullframe_y', 'sub_phot', 'ff_phot', 'd_phot_p', 'sub_dq', 'full_dq']) print('') # Save the complete table sub_base = os.path.basename(subarray_file).replace('.fits', '') full_base = os.path.basename(fullframe_file).replace('.fits', '') table_name = os.path.join( out_dir, 'photometry_comparison_{}_{}.txt'.format(sub_base, full_base)) ascii.write(final_sub_cat, table_name, overwrite=True) print('Photometry results saved to: {}'.format(table_name)) # Try filtering out sources where there is a pixel flagged in the full # frame or subarray DQ mask at the source location clean = [] for row in final_sub_cat: clean.append(row['sub_dq'] == False and row['full_dq'] == False) if np.sum(clean) > 0: clean_table = final_sub_cat[clean] print('Excluding sources with a pixel flagged in the DQ array:') med_clean_diff = np.around(np.median(clean_table['d_phot_p']), 1) print( 'Median photometry difference between subarray and full frame sources is: {}%\n\n\n' .format(med_clean_diff)) else: clean_table = None print('No sources without a flagged pixel in the DQ arrays.') return final_sub_cat, clean_table
mag_ann = np.zeros(N_star) merr_ann = np.zeros(N_star) # aperture sum apert_sum = APPHOT(img_uint16, DAOapert, method='exact')['aperture_sum'] ap_area = DAOapert.area() #print(apert_sum) apert_result = 'ID, Msky, sky_std, Sky count Pixel_N, Sky reject Pixel_N, mag_ann, merr_ann\n' for star_ID in range(0, N_stars)[10:12]: # since our `DAOannul` has many elements : mask_annul = (DAOannul.to_mask(method='center'))[star_ID] mask_apert = (DAOapert.to_mask(method='center'))[star_ID] # CAUTION!! YOU MUST USE 'center', NOT 'exact'!!! cutimg = mask_annul.cutout(img) #cutimg.tofile('{0!s}_DAOstarfinder_Star_Flux_pixel_value_starID_{1:04}.csv'.format(f_name[:-4], star_ID), sep=',') df_cutimg = pd.DataFrame(cutimg * 65536.0, dtype=np.uint16) df_cutimg.to_csv( '{0!s}_DAOstarfinder_Star_Flux_pixel_value_starID_{1:04}.csv'. format(f_name[:-4], star_ID)) cut_apert = mask_apert.cutout(img) #cutimg.tofile('{0!s}_DAOstarfinder_Star_Flux_pixel_value_starID_{1:04}.csv'.format(f_name[:-4], star_ID), sep=',') df_cut_apert = pd.DataFrame(cut_apert * 65536.0, dtype=np.uint16) df_cut_apert.to_csv( '{0!s}_DAOstarfinder_Star_apertruer_Flux_pixel_value_starID_{1:04}.csv'
def runPhotUtils(drcInfo, radius=4, suffix='_pu.dat', date=dateDef_): """ Input: drcInfo: a text file with the information from the headers of the fits files -- TARGNAME, FILTER1, FILTER2, EXPTIME, ORIENTAT, RA, DEC, and JDAN; these can be easily extracted using astropy.io.fits; one could also open the fits files in this code, but that seems more memory intensive than just opening the fits files once and outputting to a file. Variables to change: filternames, EEBand, ZPT, r_in, and r_out. Can replace _rad with _r(integer radius) if it helps you keep track of things. When running this in a loop on multiple targets, sometimes one could run out of memory space. This causes the computer to kill the program. When this happens, just remove the targets that have already been run from the drcInfo and run again. """ info = np.loadtxt(drcInfo, dtype=str) infoN = np.genfromtxt(drcInfo, dtype=str, names=True) nameCols = np.array(infoN.dtype.names) jdan = np.int(np.where(nameCols == 'JDAN')[0]) filt1 = np.int(np.where(nameCols == 'FILTER1')[0]) filt2 = np.int(np.where(nameCols == 'FILTER2')[0]) targN = np.int(np.where(nameCols == 'TARGNAME')[0]) fileNames = info[:, jdan] for ff in range(len(info)): image = drcDir + fileNames[ff] + ".fits" print(image) hdu = fits.open(image) sci = hdu[1].data hdu.close() data = sci.copy() f1 = info[:, filt1][ff] f2 = info[:, filt2][ff] targname = info[:, targN][ff] print(targname) # Would need to change the below numbers if # the data is in different filters. It could # be handy to have a table to call. if (f1 == 'F606W') or (f2 == 'F606W'): filt = 'F606W' if abs(radius - 4) <= 1e-3: EEband = 0.839 # 4 pixel elif abs(radius - 3) <= 1e-3: EEband = 0.795 ZPT = 26.667 elif (f1 == 'F814W') or (f2 == 'F814W'): filt = 'F814W' if abs(radius - 4) <= 1e-3: EEband = 0.830 # 4 pixels elif abs(radius - 3) <= 1e-3: EEband = 0.77 ZPT = 26.779 mean, median, std = sigma_clipped_stats(data, sigma=3.0, maxiters=10) daofind = DAOStarFinder(fwhm=2.5, threshold=5. * std) sources = daofind(data - median) loc = np.array([sources['xcentroid'], sources['ycentroid']]) positions = np.transpose(loc) apertures_rad = CircularAperture(positions, r=radius) rawflux_rad = aperture_photometry(data, apertures_rad) # Added this on Nov 2 rawflux_rad['roundness1'] = sources['roundness1'] rawflux_rad['roundness2'] = sources['roundness2'] rawflux_rad['sharpness'] = sources['sharpness'] annulus_apertures = CircularAnnulus(positions, r_in=9., r_out=12.) annulus_masks = annulus_apertures.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) rawflux_rad['annulus_median'] = bkg_median rawflux_rad['aper_bkg'] = bkg_median * apertures_rad.area rawflux_rad['final_phot'] = rawflux_rad['aperture_sum'] \ - rawflux_rad['aper_bkg'] mask_negative = (rawflux_rad['final_phot'] > 0) rawflux_pos_rad = rawflux_rad[mask_negative] final_phot = -2.5 * np.log10(rawflux_pos_rad['final_phot']/EEband) \ + ZPT rawflux_pos_rad['magr'] = final_phot rawflux_pos_rad['id'] = np.arange(0, len(rawflux_pos_rad), 1) s0 = ' ' header = s0.join(rawflux_pos_rad.dtype.names) saveDir = f2mag_dirs(targname, date=date, workDir='../') outName = saveDir + fileNames[ff] + '_' + filt + suffix np.savetxt(outName, rawflux_pos_rad, header=header) # An attempt to free up memory; Didn't seem to work? rawflux_pos_rad = None data = None sci = None print('Moving On.') return None
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
g_iraf, ge_iraf, gf_iraf, gsky_iraf = np.loadtxt('phot_test_g.txdump', usecols=(1,2,3,4), unpack=True) i_iraf, ie_iraf, if_iraf, isky_iraf = np.loadtxt('phot_test_i.txdump', usecols=(1,2,3,4), unpack=True) # now try python x, y = np.loadtxt(coords_file, usecols=(0,1), unpack=True) positions = np.array(zip(x,y)) hdu_g = fits.open(fits_g) hdu_i = fits.open(fits_i) apertures = CircularAperture(positions, r=8.) annulus_apertures = CircularAnnulus(positions, r_in=10., r_out=14.) print apertures.area() ap_mask = apertures.to_mask(method='subpixel', subpixels=7) dummy = np.ones_like(hdu_g[0].data) ann_mask = annulus_apertures.to_mask(method='center') ap_g = [m.apply(hdu_g[0].data) for i,m in enumerate(ap_mask)] ap_i = [m.apply(hdu_i[0].data) for i,m in enumerate(ap_mask)] area_g = [np.sum(m.apply(dummy)) for i,m in enumerate(ap_mask)] area_i = [np.sum(m.apply(dummy)) for i,m in enumerate(ap_mask)] print area_g, area_i # plt.imshow(ap_g[0], interpolation='nearest') # plt.show() ann_g = [m.apply(hdu_g[0].data, fill_value=-999.) for i,m in enumerate(ann_mask)] ann_i = [m.apply(hdu_i[0].data, fill_value=-999.) for i,m in enumerate(ann_mask)] flux_g = np.array([np.sum(a) for j,a in enumerate(ap_g)]) flux_i = np.array([np.sum(a) for j,a in enumerate(ap_i)]) bkg_med_g = np.array([sigma_clipped_stats(a, iters=0, mask_value=0.)[1] for j,a in enumerate(ann_g)])
sigma_clip = SigmaClip(sigma=3) bkg_estimator = MedianBackground() bkg = Background2D(sci_med, (60, 60), filter_size=(3, 3), sigma_clip=sigma_clip, bkg_estimator=bkg_estimator, mask=masked.mask, exclude_percentile=80) error = calc_total_error(sci_med, bkg.background_rms, 1) fwhm = np.float(input('FWHM of stars? ')) log.info('Using FWHM of ' + str(fwhm)) apertures = CircularAperture(positions, r=fwhm * 2.5) annulus_apertures = CircularAnnulus(positions, r_in=fwhm * 3, r_out=fwhm * 4) annulus_masks = annulus_apertures.to_mask(method='center') bkg_median = [] for mask in annulus_masks: annulus_data = mask.multiply(sci_med) 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) phot = aperture_photometry(sci_med, apertures, error=error) bkg_aper = bkg_median * apertures.area mag_inst = -2.5 * np.log10( (np.asarray(phot['aperture_sum']) - bkg_aper)) mag_inst_err = 2.5 / np.log(10.) * np.asarray( phot['aperture_sum_err']) / np.asarray(phot['aperture_sum']) log.info('Calculating magnitudes for ' + sci) for i, m in enumerate(mag_inst):
def growth_curve(data, x, y, model, rmax=30, alpha=None, plot=False, **kwargs): '''do a growth curve analysis on the given star measure the amount of light as a function of radius and tries to fit a Gaussian to the measured profile. Returns the FWHM of the Gaussian. Parameters ---------- model : str shape of the PSF. Must be either `gaussian` or `moffat`. rmax : float maximum radius for the growth curve ''' # ----------------------------------------------------------------- # determine background (we use same bkg_median for all apertures) # ----------------------------------------------------------------- r_in = 0.7 * rmax r_out = rmax annulus_aperture = CircularAnnulus((x, y), r_in=r_in, r_out=r_out) mask = annulus_aperture.to_mask(method='center') annulus_data = mask.multiply(data) annulus_data_1d = annulus_data[mask.data > 0] _, bkg_median, _ = sigma_clipped_stats( annulus_data_1d[~np.isnan(annulus_data_1d)], maxiters=None) if np.isnan(bkg_median): logger.warning('background contains NaN') if model == 'moffat': return np.array([np.inf, np.inf]), np.atleast_1d(np.inf) else: return np.array([np.inf]), np.atleast_1d(np.inf) # ----------------------------------------------------------------- # measure flux for different aperture radii # ----------------------------------------------------------------- radius = [] flux = [] r = 0.5 while True: if r > rmax: logger.debug(f'no convergence within a radius of {rmax:.2f}') break aperture = CircularAperture((x, y), r=r) phot = aperture_photometry(data, aperture) flux.append(phot['aperture_sum'][0] - aperture.area * bkg_median) radius.append(r) #if test_convergence(flux,**kwargs): # pass # break r += 0.5 radius = np.array(radius) flux = np.array(flux) flux = flux / flux[-1] if np.any(np.isnan(flux)): if model == 'moffat': return np.array([np.inf, np.inf]), np.atleast_1d(np.inf) else: return np.array([np.inf]), np.atleast_1d(np.inf) # ----------------------------------------------------------------- # fit moffat or gaussian # ----------------------------------------------------------------- if model == 'moffat': ''' guess = np.array([2,2]) func = light_in_moffat fit,sig = optimization.curve_fit(func, radius,flux , guess) alpha, gamma = fit[0], fit[1] fwhm = 2*gamma * np.sqrt(2**(1/alpha)-1) #print(f'alpha={alpha:.2f}, gamma={gamma:.2f}, fwhm={fwhm:.2f}') ''' func = light_in_moffat if alpha: model = _moffat_model(alpha=alpha) model.alpha.fixed = True else: model = _moffat_model() fitter = fitting.LevMarLSQFitter() fitted_line = fitter(model, radius, flux) alpha, gamma = fitted_line.parameters fit = [alpha, gamma] fwhm = 2 * gamma * np.sqrt(2**(1 / alpha) - 1) print(f'alpha={alpha:.2f}, gamma={gamma:.2f}, fwhm={fwhm:.2f}') elif model == 'gaussian': ''' guess =5 func = light_in_gaussian fit,sig = optimization.curve_fit(func, radius,flux , guess) fwhm = fit[0] ''' func = light_in_gaussian model = _gaussian_model() fitter = fitting.LevMarLSQFitter() fitted_line = fitter(model, radius, flux) fwhm = fitted_line.parameters fit = [fwhm] else: raise TypeError('model must be `moffat` or `gaussian`') if plot: from astropy.visualization import simple_norm fig = plt.figure(figsize=(6.9, 6.9 / 2)) ax1 = fig.add_subplot(121) ax2 = fig.add_subplot(122) norm = simple_norm(data, percent=99, clip=False) #, percent=99.) yslice = slice(int(x - rmax / 2), int(x + rmax / 2)) xslice = slice(int(y - rmax / 2), int(y + rmax / 2)) im1 = ax1.imshow(data[xslice, yslice], norm=norm, origin='lower', cmap='Greens') p = ax2.plot(radius, flux, label='observed') ax2.plot(radius, func(radius, *fit), label='fit', ls='--', color=p[0].get_color()) plt.xlabel('radius in px') plt.ylabel('light in aperture') plt.legend() plt.grid() return fit
def photom_av(ima, pos, radius, r_in=False, r_out=False, mode='median'): ''' Aperture photometry in an aperture located at pixel coordinates pos = ( (x0, y0), (x1, y1), ... ) with a radius=radius. When r_in and r_out are given, background is estimated in CircularAnnulus and subtracted. mode refers to how the background is estimated within the circlar annulus. Can be 'median' or 'mean' Photometry is calculating by median averaging the pixels within the aperture and multiplying by the number of pixels in the aperture (including fractions of pixels). ''' # Setting up the mask if hasattr(ima, 'mask'): if ima.mask.size == 1: mask = np.zeros(ima.shape, dtype=np.bool) | ima.mask else: mask = ima.mask.copy() else: mask = np.zeros(ima.shape, dtype=np.bool) ### Performing the actual photometry - identical for each method # Median averaging of flux in aperture # Setting up the aperture apertures = CircularAperture(pos, r = radius) ap_mask = apertures.to_mask(method='center') # Setting up arrays to store data nflx = len(ap_mask) flx = np.zeros(nflx, dtype=np.float) flux_max = np.zeros(nflx, dtype=np.float) flux_min = np.zeros(nflx, dtype=np.float) # Median averaging of flux for i, am in enumerate(ap_mask): fluxmask = ~mask & am.to_image(shape=mask.shape).astype(np.bool) flx[i] = np.median(ima[fluxmask]) flux_max[i] = np.max(ima[fluxmask]) flux_min[i] = np.min(ima[fluxmask]) # Aperture photometry on mask to see how many masked pixels are in the # aperture apm = aperture_photometry(mask.astype(int), apertures) # Number of unmasked pixels in aperture ap_area = Column(name = 'area_aper', data=apertures.area() - apm['aperture_sum'].data) # Flux in aperture using median av flux and fractional no. pixels in aperture flux_init = flx*ap_area ### Two different modes for analysing the background if ( r_in and r_out and mode in ('mean', 'median') ): ### This stuff is the same regardless of method # Setting up the annulus anulus_apertures = CircularAnnulus(pos, r_in=r_in, r_out=r_out) # Performing annulus photometry on the mask bkgm = aperture_photometry(mask.astype(int), anulus_apertures) # Number of masked pixels in bkg mbkg_area = Column(name = 'bpix_bkg', data=bkgm['aperture_sum']) # Number of non-masked pixels in aperture and bkg bkg_area = Column(name = 'area_bkg', data=anulus_apertures.area() - bkgm['aperture_sum']) ### This stuff is specific to the mean if mode == 'mean': # Perform the annulus photometry on the image bkg = aperture_photometry(ima, anulus_apertures, mask=mask) # Average bkg where this divides by only number of NONMASKED pixels # as the aperture photometry ignores the masked pixels bkga = Column(name='background', data=bkg['aperture_sum']/bkg_area) # Bkg subtracted flux flux = flux_init - bkga*ap_area # Adding that data ap.add_column(bkga) elif mode == 'median': # Number of pixels in the annulus, a different method aperture_mask = anulus_apertures.to_mask(method='center') nbkg = len(aperture_mask) # Background mask bkgm = np.zeros(nbkg, dtype=np.float) # Median averaging for i, am in enumerate(aperture_mask): bmask = ~mask & am.to_image(shape=mask.shape).astype(np.bool) bkgm[i] = np.median(ima[bmask]) flux = flux_init - bkgm*ap_area bkgm = Column(name = 'background', data = bkgm) return flux, apm, flx, ap_area, flux_max, flux_min #flux, no.masked pixels in ap, median av flux
def measure_flux(LineMaps, peak_tbl, alpha, Rv, Ebv, lines=None, aperture_size=1.5, background='local', extinction='MW'): '''measure flux for all lines in lines for each position in peak_tbl, the flux inside an aperture of `aperture_size` is measured for each line `lines` (if `LineMaps` has an extinsion). The background is estimated from an annulus with a sigma clipped median (both median and mean are reported). The [OIII] fluxes are Milky Way extinction corrected and the internal extinction is estimated if the Halpha and Hbeta lines are present. Parameters ---------- LineMaps : Galaxy Galaxy object with detected sources peak_tbl : astropy table Table with columns `x` and `y` (position of the sources) alpha : float power index of the moffat lines : list list of lines that are measured aperture_size : float size of the aperture in multiples of the fwhm background : no longer used extinction : no longer used ''' #del self.peaks_tbl['SkyCoord'] # convertion factor from arcsec to pixel (used for the PSF) input_unit = 1e-20 * u.erg / u.cm**2 / u.s ''' check the input parameters ''' # self must be of type Galaxy if not isinstance(LineMaps, ReadLineMaps): logger.warning('input should be of type ReadLineMaps') if background not in ['global', 'local', None]: raise TypeError(f'unknown Background estimation: {background}') # if no line is specified, we measure the flux in all line maps if not lines: lines = LineMaps.lines else: # make sure lines is a list lines = [lines] if not isinstance(lines, list) else lines for line in lines: if not hasattr(LineMaps, line): raise AttributeError( f'{LineMaps.name} has no attribute {line}') logger.info( f'measuring fluxes in {LineMaps.name} for {len(peak_tbl)} sources\naperture = {aperture_size} fwhm' ) ''' loop over all lines to measure the fluxes for all sources ''' out = {} for line in lines: logger.info(f'measuring fluxes in {line} line map') # select data and error (copy in case we need to modify it) data = getattr(LineMaps, f'{line}').copy() error = getattr(LineMaps, f'{line}_err').copy() try: v_disp = getattr(LineMaps, f'{line}_SIGMA') except: logger.warning('no maps with velocity dispersion for ' + line) v_disp = np.zeros(data.shape) # the fwhm varies slightly with wavelength wavelength = int(re.findall(r'\d{4}', line)[0]) PSF_correction = correct_PSF(wavelength) # calculate a global background map mask = np.isnan(data) ''' bkg = Background2D(data,(10,10), #filter_size=(15,15), sigma_clip= None,#SigmaClip(sigma=3.,maxiters=None), bkg_estimator=MedianBackground(), mask=mask).background bkg[mask] = np.nan from astropy.convolution import convolve, Gaussian2DKernel, Box2DKernel kernel = Box2DKernel(10) #Gaussian2DKernel(10) bkg_convolve = convolve(data,kernel,nan_treatment='interpolate',preserve_nan=True) # this is too slow and the masks ignore bright HA emitter etc. source_mask = np.zeros(self.shape,dtype=bool) for fwhm in np.unique(peak_tbl['fwhm']): source_part = peak_tbl[peak_tbl['fwhm']==fwhm] positions = np.transpose((source_part['x'], source_part['y'])) r = 4 * (fwhm-PSF_correction) / 2 aperture = CircularAperture(positions, r=r) for m in aperture.to_mask(method='center'): source_mask |= m.to_image(self.shape).astype(bool) ''' ''' loop over the individual pointings (they have different fwhm) ''' for fwhm in np.unique(peak_tbl['fwhm']): source_part = peak_tbl[peak_tbl['fwhm'] == fwhm] positions = np.transpose((source_part['x'], source_part['y'])) gamma = (fwhm - PSF_correction) / (2 * np.sqrt(2**(1 / alpha) - 1)) if aperture_size > 3: logger.warning('aperture > 3 FWHM') r = aperture_size * (fwhm - PSF_correction) / 2 aperture = CircularAperture(positions, r=r) # measure the flux for each source phot = aperture_photometry(data, aperture, error=error) # the local background subtraction estimates the background for # each source individually (annulus with 5 times the area of aperture) r_in = 4 * (fwhm - PSF_correction) / 2 r_out = np.sqrt(5 * r**2 + r_in**2) annulus_aperture = CircularAnnulus(positions, r_in=r_in, r_out=r_out) annulus_masks = annulus_aperture.to_mask(method='center') # background from annulus with sigma clipping bkg_median = [] bgk_mean = [] for mask in annulus_masks: # select the pixels inside the annulus and calulate sigma clipped median annulus_data = mask.multiply(data) annulus_data_1d = annulus_data[mask.data > 0] _, median_sigclip, _ = sigma_clipped_stats( annulus_data_1d[~np.isnan(annulus_data_1d)], sigma=3, maxiters=10, cenfunc='median') mean_sigclip, _, _ = sigma_clipped_stats( annulus_data_1d[~np.isnan(annulus_data_1d)], sigma=3, maxiters=10, cenfunc='mean') bkg_median.append(median_sigclip) bgk_mean.append(mean_sigclip) # save bkg_median in case we need it again and multiply background with size of the aperture phot['bkg_median'] = np.array(bkg_median) * aperture.area phot['bkg_mean'] = np.array(bgk_mean) * aperture.area ''' # background from annulus with masked sources ones = np.ones(self.shape) # calculate flux in annulus where other sources are masked bkg_phot = aperture_photometry(data,annulus_aperture,mask=source_mask) # calculate area of the annulus (parts can be masked) bkg_area = aperture_photometry(ones,annulus_aperture,mask=source_mask) # save bkg_median in case we need it again phot['bkg_median'] = bkg_phot['aperture_sum'] / bkg_area['aperture_sum'] # multiply background with size of the aperture phot['bkg_local'] = phot['bkg_median'] * aperture.area ''' phot[f'{line}_flux'] = phot['aperture_sum'] - phot['bkg_median'] phot[f'{line}_flux_raw'] = phot['aperture_sum'] # we don't subtract the background from OIII because there is none #if line == 'OIII5006_DAP': # phot[f'{line}_flux'] = phot['aperture_sum'] # correct for flux that is lost outside of the aperture phot[f'{line}_flux'] /= light_in_moffat(r, alpha, gamma) phot[f'{line}_flux_raw'] /= light_in_moffat(r, alpha, gamma) phot[f'{line}_flux_err'] = phot[ 'aperture_sum_err'] / light_in_moffat(r, alpha, gamma) phot['bkg_median'] /= light_in_moffat(r, alpha, gamma) phot['bkg_mean'] /= light_in_moffat(r, alpha, gamma) #print(f'{fwhm}: {light_in_moffat(r,alpha,gamma):.2f}') # calculate the average of the velocity dispersion aperture = CircularAperture(positions, r=4) SIGMA = aperture_photometry(v_disp, aperture) phot['SIGMA'] = SIGMA['aperture_sum'] / aperture.area # calculate stellar mass (for mass specific PN number, not used) #aperture = CircularAperture(positions, r=2) #stellar_mass = aperture_photometry(LineMaps.stellar_mass,aperture) #phot['stellar_mass'] = stellar_mass['aperture_sum'] / aperture.area # save fwhm in an additional column phot['fwhm'] = fwhm # concatenate new sources with output table if 'flux' in locals(): phot['id'] += np.amax(flux['id'], initial=0) flux = vstack([flux, phot]) else: flux = phot # for consistent table output for col in flux.colnames: flux[col].info.format = '%.8g' flux['fwhm'].info.format = '%.3g' out[line] = flux # we need an empty table for the next line del flux # so far we have an individual table for each emission line for line, v in out.items(): # find the wavelength for the extinction correction wavelength = re.findall(r'\d{4}', line) if len(wavelength) != 1: logger.error( 'line name must contain wavelength as 4 digit number in angstrom' ) wavelength = int(wavelength[0]) # first we create the output table with if 'flux' not in locals(): flux = v[['id', 'xcenter', 'ycenter', 'fwhm']] flux.rename_columns(['xcenter', 'ycenter'], ['x', 'y']) flux['x'] = flux[ 'x'].value # we don't want them to be in pixel units flux['y'] = flux['y'].value # flux[f'{line}_flux'] = v[f'{line}_flux'] flux[f'{line}_flux_err'] = v[f'{line}_flux_err'] flux[f'{line}_flux_raw'] = v[f'{line}_flux_raw'] flux[f'{line}_bkg_median'] = v[f'bkg_median'] flux[f'{line}_bkg_mean'] = v[f'bkg_mean'] # linemaps are already MW extinction corrected (OIII sum is not) # the new [OIII] fluxes use the DAP [OIII] errors and hence are already extinction corrected if line == 'OIII5006': rc = pyneb.RedCorr(R_V=3.1, E_BV=Ebv, law='CCM89') flux['OIII5006_flux'] *= rc.getCorr(5006) flux['OIII5006_flux_raw'] *= rc.getCorr(5006) flux['OIII5006_bkg_median'] *= rc.getCorr(5006) flux['OIII5006_bkg_mean'] *= rc.getCorr(5006) logger.info( f'lambda{wavelength}: Av={-2.5*np.log10(1/rc.getCorr(5006)):.2f}' ) # those columns are only needed for tests if False: flux[f'{line}_aperture_sum'] = v['aperture_sum'] flux[f'{line}_bkg_local'] = v['bkg_local'] flux[f'{line}_bkg_median'] = v['bkg_median'] #flux[f'{k}_bkg_convole'] = v['bkg_convolve'] flux[f'{line}_SIGMA'] = v['SIGMA'] # the internal extinction correction based on the balmer decrement # we do not calculate an error of E(B-V) and hance also do not account for this in the corrected errors if 'HB4861' in lines and 'HA6562' in lines: logger.info('correction for internal extinction with balmer decrement') rc = pyneb.RedCorr(R_V=3.1, law='CCM89') rc.setCorr(obs_over_theo=flux['HA6562_flux'] / flux['HB4861_flux'] / 2.86, wave1=6562.81, wave2=4861.33) rc.E_BV[(rc.E_BV < 0) | (flux['HB4861_flux'] < 3 * flux['HB4861_flux_err']) | (flux['HA6562_flux'] < 3 * flux['HA6562_flux_err'])] = 0 flux['EBV_balmer'] = rc.E_BV for line in lines: wavelength = int(re.findall(r'\d{4}', line)[0]) flux[f'{line}_flux_corr'] = flux[f'{line}_flux'] * rc.getCorr( wavelength) flux[f'{line}_flux_corr_err'] = flux[ f'{line}_flux_err'] * rc.getCorr(wavelength) flux[f'{line}_bkg_median_corr'] = flux[ f'{line}_bkg_median'] * rc.getCorr(wavelength) flux[f'{line}_bkg_mean_corr'] = flux[ f'{line}_bkg_mean'] * rc.getCorr(wavelength) logger.info('all flux measurements completed') return flux
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]
def find_fluxes(self, image_number, set): #get total shift from first image to this one x_shift, y_shift = self.get_total_shift(image_number, set) #add the shift onto the positions of the stars in the first image #to find their positions in this image x = self.catalogue['xcentroid'] + x_shift y = self.catalogue['ycentroid'] + y_shift positions = (x, y) # Shape and size of aperture object apertures = CircularAperture(positions, r=9) image_data = self.get_image(image_number, set)[0].data # Local background subtraction # Define size of background aperture annulus_apertures = CircularAnnulus(positions, r_in=10, r_out=15) apertures = CircularAperture(positions, r=9) apers = [apertures, annulus_apertures] # find counts in each aperture and annulus phot_table2 = aperture_photometry(image_data, apers) for col in phot_table2.colnames: phot_table2[ col].info.format = '%.8g' # for consistent table output # MEAN # Background sub using mean phot_table2['residual_aperture_sum_mean'] = phot_table2[ 'aperture_sum_1'] * 0 - 1 phot_table2['mean'] = phot_table2['aperture_sum_1'] * 0 - 1 for i in range(len(phot_table2)): x = positions[0][i] y = positions[1][i] #check that largest aperture does not exceed the boundaries of the image if Utilities.is_within_boundaries(x, y, len(image_data[0]), len(image_data), 15): # Calc the mean background in the second aperture ring bkg_mean = phot_table2['aperture_sum_1'][ i] / annulus_apertures.area phot_table2['mean'][i] = bkg_mean # Calc background level in each main aperture and subtract bkg_sum = bkg_mean * apertures.area final_sum = phot_table2['aperture_sum_0'][i] - bkg_sum phot_table2['residual_aperture_sum_mean'][i] = final_sum phot_table2[ 'residual_aperture_sum_mean'].info.format = '%.8g' # for consistent table output # MEDIAN # Background sub using median phot_table2['median'] = phot_table2['aperture_sum_1'] * 0 - 1 phot_table2['residual_aperture_sum_med'] = phot_table2[ 'aperture_sum_1'] * 0 - 1 #for each source for q in range(0, len(phot_table2)): x = positions[0][q] y = positions[1][q] xypos = (x, y) #check that largest aperture does not exceed the boundaries of the image if Utilities.is_within_boundaries(x, y, len(image_data[0]), len(image_data), 15): annulus = CircularAnnulus(xypos, r_in=Constants.inner_radius, r_out=Constants.outer_radius) ann_mask = annulus.to_mask(method='center') weighted_data = ann_mask.multiply(image_data) phot_table2['median'][q] = np.median( weighted_data[weighted_data != 0]) # Calc the median background in the second aperture ring bkg_med = phot_table2['median'][q] # Calc background level in each main aperture and subtract bkg_sum = bkg_med * apertures.area final_sum = phot_table2['aperture_sum_0'][q] - bkg_sum phot_table2['residual_aperture_sum_med'][q] = final_sum phot_table2[ 'residual_aperture_sum_med'].info.format = '%.8g' # for consistent table output return phot_table2
r2 = pyregion.open('regions_phys.reg') full_mask = r2.get_mask(hdu=hdu_g[0]) # full_mask_i = full_mask_g #r2.get_mask(hdu=hdu_i[0]) # plt.imshow(full_mask_g) # plt.show() median_g = 191.9227 median_i = 623.8584 positions = [(5819.6140, 5535.8705)] radii = [91., 136., 182., 227., 273., 318., 364., 409., 455., 500., 545., 591., 636., 682., 727., 772., 818.] apertures = [CircularAperture(positions, r=r) for r in radii] annulus = CircularAnnulus(positions, r_in=900., r_out=1000.) ann_mask = annulus.to_mask(method='exact') ann_g = ann_mask[0].apply(hdu_g[0].data) * ann_mask[0].apply(np.abs(full_mask-1)) ann_i = ann_mask[0].apply(hdu_i[0].data) * ann_mask[0].apply(np.abs(full_mask-1)) ann_keep = np.where(ann_g != 0) bkg_mean_g = np.median(ann_g[ann_keep]) bkg_mean_i = np.median(ann_i[ann_keep]) print bkg_mean_g, bkg_mean_i aper_mask = apertures[0].to_mask(method='exact') dbl_mask_aper = aper_mask[0].apply(hdu_g[0].data-bkg_mean_g, fill_value=-999.) * aper_mask[0].apply(np.abs(full_mask-1), fill_value=-999.) plt.imshow(dbl_mask_aper) plt.show() mean, median, std = sigma_clipped_stats(dbl_mask_aper, mask_value=-999.) print mean, median, std
def ap_phot(positions, data, radius, r_in=None, r_out=None): try: from astropy.stats import sigma_clipped_stats from photutils import aperture_photometry from photutils import CircularAperture, CircularAnnulus import numpy as np import os, sys if r_in == None or r_out == None: print('Warning - ap_phot - inner and outer annulus not set ') print('Setting to r-in = 10 r_out = 20') r_in = 10 r_out = 20 if not isinstance(positions, list): positions = list(positions) apertures = CircularAperture(positions, r=radius) annulus_apertures = CircularAnnulus(positions, r_in=r_in, r_out=r_out) annulus_masks = annulus_apertures.to_mask(method='center') # print(positions) if r_out >= data.shape[0] or r_out > data.shape[1]: print('Error - Apphot - Annulus size greater than image size') bkg_median = [] if not isinstance(annulus_masks, list): annulus_masks = list(annulus_masks) for mask in annulus_masks: annulus_data = mask.multiply(data) if annulus_data is None: if np.isnan(np.sum(annulus_data)): print('Error - annulus data is nan') print('Error - Annulus == None setting to zero') annulus_data = np.zeros( (int(2 * r_out + 1), int(2 * r_out + 1))) annulus_data_1d = annulus_data[mask.data > 0] annulus_data_1d_nonan = annulus_data_1d[~np.isnan(annulus_data_1d)] _, median_sigclip, _ = sigma_clipped_stats(annulus_data_1d_nonan, cenfunc=np.nanmedian, stdfunc=np.nanstd) bkg_median.append(median_sigclip) bkg_median = np.array(bkg_median) phot = aperture_photometry(data, apertures) phot = phot.to_pandas() phot['annulus_median'] = bkg_median phot['aper_bkg'] = bkg_median * np.pi * radius**2 phot['aper_sum_bkgsub'] = phot['aperture_sum'] - phot['aper_bkg'] aperture_sum = np.array(phot['aper_sum_bkgsub']) bkg_sum = np.array(phot['annulus_median']) aperture_sum[aperture_sum <= 0] = 0 except Exception as e: exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] print(exc_type, fname, exc_tb.tb_lineno, e) return aperture_sum, bkg_sum
def photom(ima, pos, radius, r_in=None, r_out=None, method='median'): ''' Aperture photometry in an aperture located at pixel coordinates pos = ( (x0, y0), (x1, y1), ... ) with a radius=radius. When r_in and r_out are given, background is estimated in CircularAnnulus and subtracted. method refers to how the background is estimated within the circlar annulus. Can be 'median' or 'mean' or 'mode' ''' ima_local = np.ma.asanyarray(ima.copy()) ima_local.fill_value = np.nan mask_ = ima_local.mask ima_ = ima_local.filled() ### Do photometry - identical for each method apertures = CircularAperture(pos, r = radius) ap = aperture_photometry(ima_, apertures, mask=mask_, method='exact') # Aperture photometry on mask to estimate # of masked pixels in aperture apm = aperture_photometry(mask_.astype(int), apertures, method='exact') ap.add_columns( [apertures.area()-apm['aperture_sum'], apm['aperture_sum']], names=['aperture_area', 'aperture_badpix']) ap.add_column(ap['aperture_sum'], index=3, name='Flux') if ( r_in == None or r_out == None or not method in ('mean', 'median', 'mode') ): # Quit here if background correction is not requested return ap annulus_apertures = CircularAnnulus(pos, r_in=r_in, r_out=r_out) annulus_masks = annulus_apertures.to_mask(method='center') bg_values = [] for annulus_mask in annulus_masks: bg_ima = annulus_mask.cutout(ima_) bg_mask = annulus_mask.cutout(mask_.astype(np.int)) bg_ima = np.ma.array(bg_ima, mask= bg_mask.astype(np.bool) | ~annulus_mask.data.astype(np.bool)) if method == 'mean': bg_val = bg_ima.mean() elif method == 'median': bg_val = np.ma.median(bg_ima) elif method == 'mode': kernel = gaussian_kde(bg_ima.data[~bg_ima.mask], bw_method='silverman') mode = bg_ima.mean() std = bg_ima.std() mode = minimize_scalar(lambda x: -kernel(x), bounds=(mode-3*std, mode+3*std), method='bounded') bg_val=mode.x[0] if False: median = np.ma.median(bg_ima) h, b = np.histogram(bg_ima.data[~bg_ima.mask], bins=15, normed=True) bc = 0.5*(b[1:]+ b[:-1]) plt.figure(33); plt.clf(); plt.ioff() fig, (ax0,ax1) = plt.subplots(ncols=2, nrows=1, num=33) ax0.plot(bc, h, 'x') x = np.linspace(bc.min(), bc.max(), 100) ax0.plot(x, kernel(x)) ax0.vlines(mode.x, ax0.get_ylim()[0], ax0.get_ylim()[1]) ax0.vlines(median, ax0.get_ylim()[0], ax0.get_ylim()[1]) ax1.imshow(bg_ima) plt.show() bg_values.append(bg_val) ap.add_column(Column(data=bg_values, name = 'background')) ap['Flux'] = ap['Flux'] - ap['aperture_area']*ap['background'] return ap, bg_ima
def runPhotUtils(targname,jdan,filt): if filt=='F606W': EEband = 0.839 ZPT = 26.667 fils = '_f606w/' elif filt=='F814W': EEband = 0.830 ZPT = 26.779 fils = '_f814w/' image = targname + fils + '/crClean/' + jdan + '_WJ2.fits' hdu = fits.open(image) sci = hdu[0].data hdr = hdu[0].header hdu.close() data = sci.copy() mean, median, std = sigma_clipped_stats(data, sigma=3.0, \ maxiters=10) daofind = DAOStarFinder(fwhm=2.5, threshold=5.*std) sources = daofind(data - median) loc = np.array([sources['xcentroid'], sources['ycentroid']]) positions = np.transpose(loc) apertures_r4 = CircularAperture(positions, r=4.) rawflux_r4 = aperture_photometry(data, apertures_r4) annulus_apertures = CircularAnnulus(positions, r_in=9., \ r_out=12.) annulus_masks = annulus_apertures.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) rawflux_r4['annulus_median'] = bkg_median rawflux_r4['aper_bkg'] = bkg_median*apertures_r4.area rawflux_r4['final_phot'] = rawflux_r4['aperture_sum'] \ - rawflux_r4['aper_bkg'] mask_negative = (rawflux_r4['final_phot'] > 0) rawflux_pos_r4 = rawflux_r4[mask_negative] final_phot = -2.5*np.log10(rawflux_pos_r4['final_phot']/EEband) \ +2.5*np.log10(hdr['exptime']) + ZPT rawflux_pos_r4['magr'] = final_phot s0 = ' ' header = s0.join(rawflux_pos_r4.dtype.names) saveDir = 'photUtils0820/hor1run0608/' outname = saveDir + jdan + '_' + filt + 'photU.dat' np.savetxt(outname,rawflux_pos_r4,header=header) return None
def do_aper_phot(data, catalog, extname, ivar_adu): # catalog should be the catalog with refined centroids # for **one CI camera** print('Attempting to do aperture photometry') positions = list(zip(catalog['xcentroid'], catalog['ycentroid'])) radii = aper_rad_pix(extname) apertures = [CircularAperture(positions, r=r) for r in radii] annulus_apertures = CircularAnnulus(positions, r_in=60.0, r_out=65.0) annulus_masks = annulus_apertures.to_mask(method='center') par = common.ci_misc_params() b_over_a = (1.0 if (extname == 'CIC') else par['nominal_mer_cd'] / par['nominal_sag_cd']) # the long axis of elliptical aperture (in terms of pixels) needs to # be in the CI pixel Y direction apertures_ell = [ EllipticalAperture(positions, a, a * b_over_a, theta=np.pi / 2) for a in radii ] # 107 um fiber diam, 9 um on a side for a pixel # fiber diam from Table 4.1 of https://arxiv.org/abs/1611.00037 rad_fiber_pix_sag = (107.0 / 9.0) / 2.0 deg_to_normal = 5.43 # [desi-commiss 522] if extname != 'CIC': rad_fiber_pix_mer = rad_fiber_pix_sag * np.sin(deg_to_normal / (180.0 / np.pi)) else: rad_fiber_pix_mer = rad_fiber_pix_sag aper_fib = EllipticalAperture(positions, rad_fiber_pix_sag, rad_fiber_pix_mer, theta=np.pi / 2) bkg_median = [] for mask in annulus_masks: annulus_data = mask.multiply(data) annulus_data_1d = annulus_data[mask.data > 0] # this sigma_clipped_stats call is actually the slow part !! _, median_sigclip, std_bg = sigma_clipped_stats(annulus_data_1d) bkg_median.append(median_sigclip) bkg_median = np.array(bkg_median) phot = aperture_photometry(data, apertures, error=aper_phot_unc_map(ivar_adu)) for i, aperture in enumerate(apertures): aper_bkg_tot = bkg_median * _get_area_from_ap(aperture) catalog['aper_sum_bkgsub_' + str(i)] = phot['aperture_sum_' + str(i)] - aper_bkg_tot catalog['aper_bkg_' + str(i)] = aper_bkg_tot catalog['aperture_sum_err_' + str(i)] = phot['aperture_sum_err_' + str(i)] ### del phot phot = aperture_photometry(data, apertures_ell, error=aper_phot_unc_map(ivar_adu)) for i, aperture in enumerate(apertures_ell): aper_bkg_tot = bkg_median * _get_area_from_ap(aperture) catalog['aper_ell_sum_bkgsub_' + str(i)] = phot['aperture_sum_' + str(i)] - aper_bkg_tot catalog['aper_ell_bkg_' + str(i)] = aper_bkg_tot catalog['aperture_ell_sum_err_' + str(i)] = phot['aperture_sum_err_' + str(i)] ### ### del phot phot = aperture_photometry(data, aper_fib, error=aper_phot_unc_map(ivar_adu)) aper_bkg_tot = bkg_median * _get_area_from_ap(aper_fib) catalog['aper_sum_bkgsub_fib'] = phot['aperture_sum'] - aper_bkg_tot catalog['aper_bkg_fib'] = aper_bkg_tot catalog['aperture_sum_err_fib'] = phot['aperture_sum_err'] #### # is .area() result a vector or scalar ?? catalog['sky_annulus_area_pix'] = _get_area_from_ap(annulus_apertures) catalog['sky_annulus_median'] = bkg_median
def runPhotUtils(drcInfo, radius=4, suffix='_pu.dat', date=dateDef_): """ Input: drcInfo: a text file with the information from the headers of the fits files -- TARGNAME, FILTER1, FILTER2, EXPTIME, ORIENTAT, RA, DEC, and JDAN; these can be easily extracted using astropy.io.fits; one could also open the fits files in this code, but that seems more memory intensive than just opening the fits files once and outputting to a file. Variables to change: filternames, EEBand, ZPT, r_in, and r_out. Can replace _rad with _r(integer radius) if it helps you keep track of things. When running this in a loop on multiple targets, sometimes one could run out of memory space. This causes the computer to kill the program. When this happens, just remove the targets that have already been run from the drcInfo and run again. """ info = np.loadtxt(drcInfo, dtype=str) infoN = np.genfromtxt(drcInfo, dtype=str, names=True) nameCols = np.array(infoN.dtype.names) jdan = np.int(np.where(nameCols == 'JDAN')[0]) filt1 = np.int(np.where(nameCols == 'FILTER1')[0]) filt2 = np.int(np.where(nameCols == 'FILTER2')[0]) targN = np.int(np.where(nameCols == 'TARGNAME')[0]) fileNames = info[:, jdan] for ff in range(len(info)): image = drcDir + fileNames[ff] + ".fits" print(image) hdu = fits.open(image) sci = hdu[1].data hdu.close() data = sci.copy() f1 = info[:, filt1][ff] f2 = info[:, filt2][ff] targname = info[:, targN][ff] print(targname) # Would need to change the below numbers if # the data is in different filters. It could # be handy to have a table to call. if (f1 == 'F606W') or (f2 == 'F606W'): filt = 'F606W' if abs(radius - 4) <= 1e-3: EEband = 0.839 # 4 pixel elif abs(radius - 3) <= 1e-3: EEband = 0.795 ZPT = 26.667 elif (f1 == 'F814W') or (f2 == 'F814W'): filt = 'F814W' if abs(radius - 4) <= 1e-3: EEband = 0.830 # 4 pixels elif abs(radius - 3) <= 1e-3: EEband = 0.77 ZPT = 26.779 saveDir = f2mag_dirs(targname, date=date, workDir='../') outName = saveDir + fileNames[ff] + '_' + filt mean, median, std = sigma_clipped_stats(data, sigma=3.0, maxiters=10) daofind = DAOStarFinder(fwhm=2., threshold=5. * std) sources = daofind(data - median) loc = np.array([sources['xcentroid'], sources['ycentroid']]) positions = np.transpose(loc) apertures_rad = CircularAperture(positions, r=radius) rawflux_rad = aperture_photometry(data, apertures_rad) # Added this on Nov 2 rawflux_rad['roundness1'] = sources['roundness1'] rawflux_rad['roundness2'] = sources['roundness2'] rawflux_rad['sharpness'] = sources['sharpness'] annulus_apertures = CircularAnnulus(positions, r_in=9., r_out=12.) annulus_masks = annulus_apertures.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) rawflux_rad['annulus_median'] = bkg_median rawflux_rad['aper_bkg'] = bkg_median * apertures_rad.area rawflux_rad['final_phot'] = rawflux_rad['aperture_sum'] \ - rawflux_rad['aper_bkg'] # Looking at the excess flux and trying to use it as a # star/galaxy indicator. Added Nov 10. apertures_r2 = CircularAperture(positions, r=radius + 2) rawflux_r2 = aperture_photometry(data, apertures_r2) rawflux_r2['aper_bkg'] = rawflux_rad['annulus_median'] \ * apertures_r2.area rawflux_r2['final_phot'] = rawflux_r2['aperture_sum'] \ - rawflux_r2['aper_bkg'] mask_negative = (rawflux_rad['final_phot'] > 0) \ & (rawflux_r2['final_phot'] > 0) rawflux_pos_rad = rawflux_rad[mask_negative] rawflux_pos_r2 = rawflux_r2[mask_negative] mag_rad = -2.5 * np.log10(rawflux_pos_rad['final_phot']) mag_r2 = -2.5 * np.log10(rawflux_pos_r2['final_phot']) delta_mag = mag_rad - mag_r2 mask_1 = (delta_mag > 0) & (delta_mag < 0.1) mean, median, std = sigma_clipped_stats(delta_mag[mask_1], sigma=3.0, maxiters=5) apcor = median mask_r2 = (delta_mag > 0) & (delta_mag < apcor + 1.5 * std) # Diagnostic plot to see if the flag is viable fig, ax = plt.subplots() ax.scatter(mag_r2[mask_r2], delta_mag[mask_r2], c='k', s=10) ax.axhline(apcor, ls='-', c='r') ax.set_xlim(-18, -8) ax.set_ylim(-0.2, 0.3) ax.set_xlabel('Mag [r={0}]'.format(radius + 2)) ax.set_ylabel(r'$\Delta$mag') plt.savefig(outName + '_64mask.png', dpi=600, bbox_inches='tight') plt.close() maskCol = np.zeros((len(mask_r2), 1)) for ii in range(len(mask_r2)): if mask_r2[ii]: maskCol[ii] = int(1) else: maskCol[ii] = int(0) rawflux_pos_rad['six_4_flag'] = maskCol final_phot = -2.5 * np.log10(rawflux_pos_rad['final_phot']/EEband) \ + ZPT rawflux_pos_rad['magr'] = final_phot rawflux_pos_rad['id'] = np.arange(0, len(rawflux_pos_rad), 1) s0 = ' ' header = s0.join(rawflux_pos_rad.dtype.names) np.savetxt(outName + suffix, rawflux_pos_rad, header=header) # An attempt to free up memory; Didn't seem to work? sources = None rawflux_rad = None rawflux_pos_rad = None data = None sci = None print('Moving On.') return None