def create_aperture_from_ds9_region(region_file): ''' Loads a ds9 region ('reg') file saved in physical format and returns an astropy aperture instance. ''' # f = open(region_file,'rb') for i in range(3): f.readline() region_text = f.read() pdb.set_trace() regions = region_text.split('\n') src_apertures,bkg_apertures = [],[] for region in regions[:-1]: # This is a poor example of string handling, but it works. value_text = region.split('(')[1].split(')')[0] pos_x,pos_y,r_in,r_out = np.array(value_text.split(','),dtype = float) src_apertures.append(photutils.CircularAperture((pos_x,pos_y), r=r_in)) bkg_aperture.append(photutils.CircularAnnulus((pos_x,pos_y), r_in=r_in, r_out=r_out)) return src_apertures,bkg_apertures
def _aperture_phot(self, x, y, data, radsize=1, sky_inner=5, skywidth=5, method="subpixel", subpixels=4): """Perform sky subtracted aperture photometry, uses photutils functions, photutil must be installed Parameters ---------- radsize: int Size of the radius sky_inner: int Inner radius of the sky annulus skywidth: int Width of the sky annulus method: string Pixel sampling method to use subpixels: int How many subpixels to use Notes ----- background is taken from sky annulus pixels, check into masking bad pixels """ if not photutils_installed: print("Install photutils to enable") else: apertures = photutils.CircularAperture((x, y), radsize) rawflux_table = photutils.aperture_photometry( data, apertures, subpixels=1, method="center") outer = sky_inner + skywidth annulus_apertures = photutils.CircularAnnulus( (x, y), r_in=sky_inner, r_out=outer) bkgflux_table = photutils.aperture_photometry( data, annulus_apertures) # to calculate the mean local background, divide the circular annulus aperture sums # by the area fo the circular annuls. The bkg sum with the circular aperture is then # then mean local background tims the circular apreture area. aperture_area = apertures.area() annulus_area = annulus_apertures.area() bkg_sum = ( bkgflux_table['aperture_sum'] * aperture_area / annulus_area)[0] skysub_flux = rawflux_table['aperture_sum'][0] - bkg_sum return ( float(rawflux_table['aperture_sum'][0]), bkg_sum, skysub_flux)
def perform_aperture_photometry(xcen, ycen, filename, aperture_radii = np.arange(2, 15), bkg_r_in=16., bkg_r_out=20): ofile = fits.open(filename) apertures = [pu.CircularAperture((xcen, ycen), r=r) for r in aperture_radii] annulus_apertures = pu.CircularAnnulus((xcen, ycen), r_in=bkg_r_in, r_out=bkg_r_out) apertures.append(annulus_apertures) phot_table = pu.aperture_photometry(ofile[1].data, apertures ) bkg_colname = phot_table.colnames[-1] bkg_mean = phot_table[bkg_colname] / annulus_apertures.area() for aper, icol in zip(apertures, phot_table.colnames[3:]): bkg_sum = bkg_mean*aper.area() phot_table.add_column(phot_table[icol]-bkg_sum, name='{}_bkg_sub'.format(icol)) return phot_table, apertures
def __init__(self, name='', pos=(0, 0), loupe=None, aperture_radius=5, background_radii=[15, 25], subtract_background=False): ''' Initialize an interactive aperture. Parameters ---------- name : str A simple name for the aperture pos : tuple A two-element list of (x,y)? pixel positions for the center loupe : a loupe.Loupe object A organizing Loupe in which this aperture is embedded aperture_radius : float A radius for the aperture background_radii : list of two floats Inner and outer radii for a background-estimation annulus subtract_background : bool Should we do background subtraction? ''' # initialize the CircularAperture as normal photutils.CircularAperture.__init__(self, pos, aperture_radius) # create a background annulus r_in, r_out = background_radii r_out = np.maximum(r_out, r_in + 1) self.background_aperture = photutils.CircularAnnulus(pos, r_in, r_out) self.subtract_background = subtract_background # keep track of this aperture's name self.name = name # keep track of the loupe this aperture is a part of self.loupe = loupe # keep track of the flux in this aperture with an undefined flux self.flux = np.nan
def starphot(imdata, position, radius, r_in, r_out): """ sources: http://photutils.readthedocs.io/en/stable/ photutils/aperture.html ARGS: imdata: Numpy array containing the star. position: [x,y] coordinates where x corresponds to the second index of imdata radius: Radius of the circular aperture used to compute the flux of the star. r_in: Inner radius of the annulus used to compute the background mean. r_out: Outer radius of the annulus used to compute the background mean. Returns [flux, background variance, background mean] """ try: statmask = photutils.make_source_mask(imdata, snr=5, npixels=5, dilate_size=10) except TypeError: return None bkg_annulus = photutils.CircularAnnulus(position, r_in, r_out) bkg_phot_table = photutils.aperture_photometry(imdata, bkg_annulus, method='subpixel', mask=statmask) bkg_mean_per_pixel = bkg_phot_table['aperture_sum'] / bkg_annulus.area() src_aperture = photutils.CircularAperture(position, radius) src_phot_table = photutils.aperture_photometry(imdata, src_aperture, method='subpixel') signal = src_phot_table['aperture_sum'] - bkg_mean_per_pixel*\ src_aperture.area() #noise_squared = signal + bkg_mean_per_pixel*src_aperture.area() mean, median, std = sigma_clipped_stats(imdata, sigma=3.0, iters=5, mask=statmask) noise_squared = std**2 return float(str(signal.data[0])), noise_squared,\ float(str(bkg_mean_per_pixel.data[0]))
def _perform(self): """ Returns an Argument() with the parameters that depend on this operation. """ self.log.info(f"Running {self.__class__.__name__} action") exptime = self.action.args.meta.get('exptime') pixel_scale = self.cfg['Telescope'].getfloat('pixel_scale', 1) thresh = self.cfg['Extract'].getint('extract_threshold', 9) minarea = self.cfg['Extract'].getint('extract_minarea', 7) mina = self.cfg['Extract'].getfloat('fwhm_mina', 1) minb = self.cfg['Extract'].getfloat('fwhm_minb', 1) faint_limit_pct = self.cfg['Extract'].getfloat( 'faint_limit_percentile', 0) bright_limit_pct = self.cfg['Extract'].getfloat( 'bright_limit_percentile', 100) radius_limit = self.cfg['Extract'].getfloat('radius_limit_pix', 4000) bsub = self.action.args.ccddata.data - self.action.args.background.background seperr = self.action.args.ccddata.uncertainty.array sepmask = self.action.args.ccddata.mask # Define quick utility function def run_sep(bsub, seperr, sepmask, thresh, minarea): try: objects = sep.extract(bsub, err=seperr, mask=sepmask, thresh=float(thresh), minarea=minarea) return objects except Exception as e: if str(e)[:27] == 'internal pixel buffer full:': return None else: raise SEPError(str(e)) objects = None while objects is None: try: self.log.info(f'Invoking SEP with threshold: {thresh}') objects = run_sep(bsub, seperr, sepmask, thresh, minarea) thresh += 9 except SEPError as e: self.log.error('Source extractor failed:') self.log.error(e) return self.action.args t = Table(objects) t['flux'] /= exptime # Add radius (of star from center of image) to table ny, nx = bsub.shape r = np.sqrt((t['x'] - nx / 2.)**2 + (t['y'] - ny / 2.)**2) t.add_column(Column(data=r.data, name='r', dtype=np.float)) # Add FWHM to table coef = 2 * np.sqrt(2 * np.log(2)) fwhm = np.sqrt((coef * t['a'])**2 + (coef * t['b'])**2) t.add_column(Column(data=fwhm.data, name='FWHM', dtype=np.float)) # Add ellipticities to table ellipticities = t['a'] / t['b'] t.add_column( Column(data=ellipticities.data, name='ellipticity', dtype=np.float)) # Filter out stars based on bright and faint limits faint_limit = np.percentile(t['flux'], faint_limit_pct) bright_limit = np.percentile(t['flux'], bright_limit_pct) self.log.info( f' Faintest {faint_limit_pct:.1f}% flux {faint_limit:f}') self.log.info( f' Brightest {bright_limit_pct:.1f}% flux {bright_limit:f}') filtered = (t['a'] < mina) | (t['b'] < minb) | (t['flag'] > 0) | ( t['flux'] > bright_limit) | (t['flux'] < faint_limit) | (t['r'] > radius_limit) self.log.debug(f' Removing {np.sum(filtered):d}/{len(filtered):d}'\ f' extractions from FWHM calculation') self.log.debug( f" {np.sum( (t['a'] < mina) )} removed for fwhm_mina limit") self.log.debug( f" {np.sum( (t['b'] < minb) )} removed for fwhm_minb limit") self.log.debug( f" {np.sum( (t['flag'] > 0) )} removed for source extractor flags" ) self.log.debug( f" {np.sum( (t['flux'] < faint_limit) )} removed for faint limit" ) self.log.debug( f" {np.sum( (t['flux'] > bright_limit) )} removed for bright limit" ) self.action.args.meta['n_objects'] = len(t[~filtered]) self.log.info( f' Found {self.action.args.meta.get("n_objects"):d} stars') self.action.args.objects = t[~filtered] self.action.args.objects.sort('flux') self.action.args.objects.reverse() if self.action.args.meta.get("n_objects") == 0: self.log.warning('No stars found') return self.action.args else: FWHM_pix = np.median(t['FWHM'][~filtered]) FWHM_mode_bin = pixel_scale * 0.25 FWHM_pix_mode = mode( t['FWHM'][~filtered] / FWHM_mode_bin) * FWHM_mode_bin self.log.info( f' Median FWHM = {FWHM_pix:.1f} pix ({FWHM_pix*pixel_scale:.2f} arcsec)' ) self.log.info( f' Mode FWHM = {FWHM_pix_mode:.1f} pix ({FWHM_pix_mode*pixel_scale:.2f} arcsec)' ) ellipticity = np.median(t['ellipticity'][~filtered]) ellipticity_mode_bin = 0.05 ellipticity_mode = mode( t['ellipticity'][~filtered] / ellipticity_mode_bin) * ellipticity_mode_bin self.log.info(f' Median ellipticity = {ellipticity:.2f}') self.log.info(f' Mode ellipticity = {ellipticity_mode:.2f}') self.action.args.meta['fwhm'] = FWHM_pix_mode self.action.args.meta['ellipticity'] = ellipticity_mode ## Do photutils photometry measurement positions = [(det['x'], det['y']) for det in self.action.args.objects] ap_radius = self.cfg['Photometry'].getfloat('aperture_radius', 2) * FWHM_pix star_apertures = photutils.CircularAperture(positions, ap_radius) sky_apertures = photutils.CircularAnnulus( positions, r_in=int(np.ceil(1.5 * ap_radius)), r_out=int(np.ceil(2.0 * ap_radius))) phot_table = photutils.aperture_photometry( self.action.args.ccddata, [star_apertures, sky_apertures]) phot_table['sky'] = phot_table['aperture_sum_1'] / sky_apertures.area() med_sky = np.median(phot_table['sky']) self.log.info(f' Median Sky = {med_sky.value:.0f} e-/pix') self.action.args.meta['sky_background'] = med_sky.value self.action.args.objects.add_column(phot_table['sky']) bkg_sum = phot_table['aperture_sum_1'] / sky_apertures.area( ) * star_apertures.area() final_sum = (phot_table['aperture_sum_0'] - bkg_sum) final_uncert = (bkg_sum + final_sum)**0.5 * u.electron**0.5 phot_table['apflux'] = final_sum / exptime self.action.args.objects.add_column(phot_table['apflux']) phot_table['apuncert'] = final_uncert / exptime self.action.args.objects.add_column(phot_table['apuncert']) phot_table['snr'] = final_sum / final_uncert self.action.args.objects.add_column(phot_table['snr']) where_bad = (final_sum <= 0) self.log.info(f' {np.sum(where_bad)} stars rejected for flux < 0') self.action.args.objects = self.action.args.objects[~where_bad] self.action.args.meta['n_fluxes'] = len(self.action.args.objects) self.log.info( f' Fluxes for {self.action.args.meta["n_fluxes"]:d} stars') return self.action.args
def aperture(image, dt_expstart, fcoords): """ The main function for doing aperture photometry on individual FITS files app = -1 means the results are for a PSF fit instead of aperture photometry Arguments: image = Image data as 2D numpy.ndarray dt_expstart = UTC timestamp of start of exposure as datetime.datetime object. fcoords = Text file with pixel coordinates for stars in first frame. Format: targx targy compx compy compx compy """ global iap, pguess_old, nstars, svec dnorm = 500. rann1 = 18. dann = 2. rann2 = rann1 + dann # variable apertures fail for data on 2014-07-01 # restricting apertures to 4 pix for now # STH, 2014-07-01 # app_min = 1. # app_max = 19. app_min = 3. app_max = 8. dapp = 1. app_sizes = np.arange(app_min,app_max,dapp) # If first time through, read in "guesses" for locations of stars # TODO: just read a csv if iap == 0: var = np.loadtxt(fcoords) xvec = var[:,0] yvec = var[:,1] nstars = len(xvec) else: xvec = svec[:,0] yvec = svec[:,1] # Find locations of stars # dxx0 is size of bounding box around star location. # Box edge is 2*dxx0 # TODO: make dxx0 = prelim guess of fwhm dxx0 = 10. for i in range(nstars): xx0 = [xvec[i], yvec[i]] xbounds = (xx0[0]-dxx0,xx0[0]+dxx0) ybounds = (xx0[1]-dxx0,xx0[1]+dxx0) # TODO: when next released by photutils, use: # centroid = photutils.detection.morphology.centroid_2dg() res = sco.minimize(center, xx0, method='L-BFGS-B', bounds=(xbounds,ybounds),jac=der_center) xx0=res.x xvec[i] = xx0[0] yvec[i] = xx0[1] # Calculate sky around stars anns = [photutils.CircularAnnulus(rann1, rann2)] * len(xvec) sky = photutils.aperture_photometry(image, xvec, yvec, anns, method='exact',subpixels=10) # Do psf fits to stars. Results are stored in arrays fwhm, pflux, psky, psf_x, and psf_y fwhm = np.zeros(nstars) # Make stacked array of star positions from aperture photometry svec = np.dstack((xvec,yvec))[0] # Make stacked array of star positions from PSF fitting # pvec = np.dstack((psf_x,psf_y))[0] pvec = svec iap = iap + 1 starr = [] apvec = [] app=-1.0 # Get time of observation from the header #date = hdr['DATE-OBS'] # for Argos files #utc = hdr['UTC'] # for Argos files # date = hdr['UTC-DATE'] # for Pro-EM files # utc = hdr['UTC-BEG'] # for Pro-EM files # times = date + " " + utc # t = Time(times, format='iso', scale='utc') # TODO: astropy 0.3.2 fully supports datetime objects # When we can upgrade photutils and thus numpy, use datetime objects. try: t = Time(dt_expstart, scale='utc') # ValueError if we haven't upgraded astropy. except ValueError: t = Time(dt_expstart.isoformat(), format='isot', scale='utc') # Calculate Julian Date of observation jd = t.jd # TODO: As of 2014-06-01, photutils allows vectors of apertures. Use them. STH for radius in app_sizes: app = [photutils.CircularAperture(radius)] flux = [photutils.aperture_photometry(image, x, y, app, method='exact',subpixels=10) for (x, y) in zip(xvec, yvec)] skyc = sky*radius**2/(rann2**2 - rann1**2) fluxc = flux - skyc starr.append([fluxc,skyc,fwhm]) apvec.append(radius) starr = np.array(starr) apvec = np.array(apvec) return jd,svec,pvec,apvec,starr
def fit_gaussian(self, plot=True, verbose=False, save=None): """ Perform a fit of a 2D gaussian. Input: - plot: (optional) bool. If True, makes a plot of the image with the contours of the gaussian - verbose: (optional) bool. If True, prints the verbose of mpdfit - additional optional keywords can be 'amp', 'centerx', 'centery', 'sigmax','sigmay','fwhm' or 'theta' to set the value of the first guess of the fit. theta must be between 0 and 90 - save: (optional) string with the name to save a pdf of the fit (only valid if plot=True) and a ds9 reg file (still to be implemented) Output: - fit_result: a dictionary with the parameters of the best fit. The entries are 'AMP' 'X' 'FWHMX' 'Y' 'FWHMY' 'FWHM' 'THETA' 'ell' - fit_error: a dictionary with the parameters of the error on the previous parameters (same entries) - chi2: value of the chi square - chi2_reduced: value of the reduced chi squared """ for i in self.good_frames: if verbose: print('Processing image {0:d}'.format(i)) current_image = self.cube[i, :, :] - self.bck current_ma = np.ma.masked_array(current_image - self.bck, mask=self.mask) sky_med = np.median(current_ma) sky_rms = np.std(current_ma) if sky_med > 5: print('Warning, the sky level is high: {0:5.1f} ADU'.format( sky_med)) if sky_rms > 5: print('Warning, the background noise is high: {0:5.1f} ADU'. format(sky_rms)) # We first set a default guess filtered_image = gaussian_filter(current_image, 2) argmax = np.argmax(filtered_image) ymax, xmax = np.unravel_index(argmax, current_image.shape) amp = np.max(current_image) guess_dico = { 'amp': amp, 'centerx': xmax, 'centery': ymax, 'sigx': self.theoretical_sig, 'sigy': self.theoretical_sig, 'theta': 0. } # We also set default boundaries parinfo = [ { 'fixed': 0, 'limited': [1, 1], 'limits': [0., 2 * amp] }, # Force the amplitude to be >0 { 'fixed': 0, 'limited': [1, 1], 'limits': [7, self.nx - 7] }, # We restrain the center to be 1px { 'fixed': 0, 'limited': [1, 1], 'limits': [7, self.ny - 7] }, # away from the edge { 'fixed': 0, 'limited': [1, 1], 'limits': [self.theoretical_sig, 1.4 * self.theoretical_sig] }, # sigma_x between 0.5 and 10px { 'fixed': 0, 'limited': [1, 1], 'limits': [self.theoretical_sig, 1.4 * self.theoretical_sig] }, # sigma_y between 0.5 and 10px { 'fixed': 0, 'limited': [1, 1], 'limits': [0, 180.] } ] # We limit theta beween 0 and 90 deg fa = { 'x': self.x_array, 'y': self.y_array, 'z': current_image, 'err': np.ones_like(current_image) * sky_rms } guess = [ guess_dico['amp'], guess_dico['centerx'], guess_dico['centery'], guess_dico['sigx'], guess_dico['sigy'], guess_dico['theta'] ] m = mpfit.mpfit(self.gauss2D_fit_erf, guess, functkw=fa, parinfo=parinfo, quiet=1) # quiet=(not verbose)*1) if m.status == 0: print( 'Fit failed for frame {0:d}. Try to help the minimizer by providing a better first guess' .format(i)) else: residuals = self.gauss2D_fit_erf( m.params, x=self.x_array, y=self.y_array, z=current_image, err=np.ones_like(current_image) * sky_rms)[1].reshape( current_image.shape) self.residuals[i, :, :] = residuals #+self.bck #+sky_med self.fit_result['CHI2'][i] = np.sum(residuals**2) self.fit_result['CHI2_r'][ i] = self.fit_result['CHI2'][i] / m.dof self.fit_result['AMP'][i] = m.params[0] self.fit_result['X'][i] = m.params[1] self.fit_result['Y'][i] = m.params[2] sig = np.array([m.params[3], m.params[4]]) sig_error = np.array([m.perror[3], m.perror[4]]) error_ell = 4 / (np.sum(sig)**2) * np.sqrt( np.sum((sig * sig_error)**2)) fwhm = sig2fwhm(sig) fwhm_error = sig2fwhm(sig_error) self.fit_result['FWHMX'][i] = fwhm[0] self.fit_result['FWHMY'][i] = fwhm[1] self.fit_result['FWHM'][i] = np.mean(fwhm) self.fit_result['THETA'][i] = m.params[5] self.fit_result['ell'][i] = (sig[1] - sig[0]) / np.mean(sig) self.fit_error['AMP'][i] = m.perror[0] self.fit_error['X'][i] = m.perror[1] self.fit_error['Y'][i] = m.perror[2] self.fit_error['FWHMX'][i] = fwhm_error[0] self.fit_error['FWHMY'][i] = fwhm_error[1] self.fit_error['FWHM'][i] = np.mean(fwhm_error) self.fit_error['THETA'][i] = m.perror[5] self.fit_error['ell'][i] = error_ell annulus_aperture = photutils.CircularAnnulus((self.fit_result['X'][i],self.fit_result['Y'][i]), \ r_in=self.theoretical_fwhm,\ r_out=2*self.theoretical_fwhm) phot_table_annulus = photutils.aperture_photometry(residuals**2, \ annulus_aperture,error=np.ones_like(current_image)*sky_rms) self.fit_result['annulus_rms'][i] = np.sqrt( phot_table_annulus['aperture_sum'] [0]) / phot_table_annulus['aperture_sum_err'][0] circular_aperture = photutils.CircularAperture((self.fit_result['X'][i],self.fit_result['Y'][i]), \ r=2.5*self.theoretical_fwhm) phot_table_circle = photutils.aperture_photometry(residuals**2, \ circular_aperture,error=np.ones_like(current_image)*sky_rms) self.fit_result['circle_rms'][i] = np.sqrt( phot_table_circle['aperture_sum'] [0]) / phot_table_circle['aperture_sum_err'][0] mask_aperture = np.zeros_like(current_image, dtype=bool) mask_R = np.copy(mask_aperture) #right mask_R[self.x_array > self.fit_result['X'][i]] = True mask_L = np.copy(mask_aperture) #left mask_L[self.x_array < self.fit_result['X'][i]] = True mask_T = np.copy(mask_aperture) #top mask_T[self.y_array > self.fit_result['Y'][i]] = True mask_B = np.copy(mask_aperture) # bottom mask_B[self.y_array < self.fit_result['Y'][i]] = True phot_table_T = photutils.aperture_photometry(residuals**2, annulus_aperture,\ error=np.ones_like(current_image)*sky_rms,mask=mask_T) self.fit_result['T_rms'][i] = np.sqrt( phot_table_T['aperture_sum'] [0]) / phot_table_T['aperture_sum_err'][0] phot_table_B = photutils.aperture_photometry(residuals**2, annulus_aperture,\ error=np.ones_like(current_image)*sky_rms,mask=mask_B) self.fit_result['B_rms'][i] = np.sqrt( phot_table_B['aperture_sum'] [0]) / phot_table_B['aperture_sum_err'][0] phot_table_L = photutils.aperture_photometry(residuals**2, annulus_aperture,\ error=np.ones_like(current_image)*sky_rms,mask=mask_L) self.fit_result['L_rms'][i] = np.sqrt( phot_table_L['aperture_sum'] [0]) / phot_table_L['aperture_sum_err'][0] phot_table_R = photutils.aperture_photometry(residuals**2, annulus_aperture,\ error=np.ones_like(current_image)*sky_rms,mask=mask_R) self.fit_result['R_rms'][i] = np.sqrt( phot_table_R['aperture_sum'] [0]) / phot_table_R['aperture_sum_err'][0] self.fit_result['asymmetry'][i] = np.max([ np.abs(self.fit_result['T_rms'][i] - self.fit_result['B_rms'][i]), np.abs(self.fit_result['L_rms'][i] - self.fit_result['T_rms'][i]) ]) if verbose: print('X={0:4.2f}+/-{1:4.2f} Y={2:4.2f}+/-{3:4.2f} FWHM={4:3.2f}+/-{5:4.2f} ell={6:4.2f}+/-{7:4.2f}'.format(self.fit_result['X'][i],\ self.fit_error['X'][i],self.fit_result['Y'][i],self.fit_error['Y'][i],self.fit_result['FWHM'][i],self.fit_error['FWHM'][i],self.fit_result['ell'][i],self.fit_error['ell'][i],)) print('AMP={0:4.2e}+/-{1:3.2e} theta={2:3.1f}+/-{3:3.1f}deg SKY={4:4.2f}+/-{5:4.2f}'.format(self.fit_result['AMP'][i],\ self.fit_error['AMP'][i],self.fit_result['THETA'][i],self.fit_error['THETA'][i],sky_med,sky_rms)) print('DOF={0:d} CHI2={1:.1f} CHI2_r={2:.1f}'.format( m.dof, self.fit_result['CHI2'][i], self.fit_result['CHI2_r'][i])) if plot: plt.close(1) fig = plt.figure(1, figsize=(7.5, 3)) gs = gridspec.GridSpec(1, 3, height_ratios=[1], width_ratios=[1, 1, 0.06]) gs.update(left=0.1, right=0.9, bottom=0.1, top=0.93, wspace=0.2, hspace=0.03) ax1 = plt.subplot(gs[0, 0]) # Area for the first plot ax2 = plt.subplot(gs[0, 1]) # Area for the second plot ax3 = plt.subplot(gs[0, 2]) # Area for the second plot im = ax1.imshow(current_image, cmap='CMRmap', origin='lower', interpolation='nearest', extent=[ np.min(self.x_array), np.max(self.x_array), np.min(self.y_array), np.max(self.y_array) ], vmin=np.nanmin(current_ma), vmax=np.nanmax(current_ma)) ax1.set_xlabel('X in px') ax1.set_ylabel('Y in px') ax1.contour( self.x_array, self.y_array, sky_med + Gaussian2D( m.params[0], m.params[1], m.params[2], m.params[3], m.params[4], np.radians(m.params[5]))( self.x_array, self.y_array), 3, colors='w') ax1.grid(True, c='w') im2 = ax2.imshow( residuals, cmap='CMRmap', origin='lower', interpolation='nearest', extent=[ np.min(self.x_array), np.max(self.x_array), np.min(self.y_array), np.max(self.y_array) ], vmin=np.nanmin(current_image), vmax=np.nanmax(current_ma) ) #, extent=[np.min(self.x_array),np.max(self.x_array),np.min(self.y_array),np.max(y_array)]) ax2.set_xlabel('X in px') ax2.grid(True, c='w') fig.colorbar(im, cax=ax3) if save is not None: fig.savefig(save + '_{0:d}.pdf'.format(i)) plt.figure(1) plt.clf() plt.plot(self.fit_result['CHI2_r'], label='chi2 r') plt.plot(self.fit_result['annulus_rms'], label='annulus') plt.plot(self.fit_result['asymmetry'], label='asymmetry') plt.plot(self.fit_result['circle_rms'], label='circle') plt.legend(frameon=False, loc='best') return
def aper_phot(self, x, y, data): """Perform aperture photometry, uses photutils functions, photutils must be available """ sigma = 0. # no centering amp = 0. # no centering if not photutils_installed: print("Install photutils to enable") else: if self.aperphot_pars["center"][0]: center = True delta = 10 popt = self.gauss_center(x, y, data, delta=delta) if 5 > popt.count(0) > 1: # an error occurred in centering warnings.warn( "Problem fitting center, using original coordinates") else: amp, x, y, sigma, offset = popt radius = int(self.aperphot_pars["radius"][0]) width = int(self.aperphot_pars["width"][0]) inner = int(self.aperphot_pars["skyrad"][0]) subsky = bool(self.aperphot_pars["subsky"][0]) outer = inner + width apertures = photutils.CircularAperture((x, y), radius) rawflux_table = photutils.aperture_photometry( data, apertures, subpixels=1, method="center") if subsky: annulus_apertures = photutils.CircularAnnulus( (x, y), r_in=inner, r_out=outer) bkgflux_table = photutils.aperture_photometry( data, annulus_apertures) # to calculate the mean local background, divide the circular annulus aperture sums # by the area fo teh circular annuls. The bkg sum with the circular aperture is then # then mean local background tims the circular apreture area. aperture_area = apertures.area() annulus_area = annulus_apertures.area() bkg_sum = float( (bkgflux_table['aperture_sum'] * aperture_area / annulus_area)[0]) total_flux = rawflux_table['aperture_sum'][0] - bkg_sum sky_per_pix = float( bkgflux_table['aperture_sum'] / annulus_area) else: total_flux = float(rawflux_table['aperture_sum'][0]) # compute the magnitude of the sky corrected flux magzero = float(self.aperphot_pars["zmag"][0]) mag = magzero - 2.5 * (np.log10(total_flux)) pheader = ( "x\ty\tradius\tflux\tmag(zpt={0:0.2f})\tsky\t".format(magzero)).expandtabs(15) if center: pheader += ("fwhm") pstr = "\n{0:.2f}\t{1:0.2f}\t{2:d}\t{3:0.2f}\t{4:0.2f}\t{5:0.2f}\t{6:0.2f}".format( x + 1, y + 1, radius, total_flux, mag, sky_per_pix, math_helper.gfwhm(sigma)).expandtabs(15) else: pstr = "\n{0:0.2f}\t{1:0.2f}\t{2:d}\t{3:0.2f}\t{4:0.2f}\t{5:0.2f}".format( x + 1, y + 1, radius, total_flux, mag, sky_per_pix,).expandtabs(15) print(pheader + pstr) logging.info(pheader + pstr)
def radial_profile(self, x, y, data=None, form=None, fig=None): """Display the radial profile plot (intensity vs radius) for the object. Background may be subtracted and centering can be done with a 2D Gaussian fit """ if not photutils_installed: print("Install photutils to enable") else: if data is None: data = self._data if not form: form = getattr(models, self.radial_profile_pars["fittype"][0]) getdata = bool(self.radial_profile_pars["getdata"][0]) subtract_background = bool( self.radial_profile_pars["background"][0]) # cut the data down to size datasize = int(self.radial_profile_pars["rplot"][0]) - 1 delta = 10 # chunk size in pixels to find center # center on image using a 2d gaussian if self.radial_profile_pars["center"][0]: # pull out a small chunk to get the center defined amp, centerx, centery, sigma, sigmay = self.gauss_center( x, y, data, delta=delta) else: centery = y centerx = x icenterx = int(centerx) icentery = int(centery) # just grab the data box we want from the image data_chunk = data[icentery - datasize:icentery + datasize, icenterx - datasize:icenterx + datasize] y, x = np.indices((data_chunk.shape)) r = np.sqrt((x - datasize)**2 + (y - datasize)**2) r = r.astype(np.int) # add up the flux in integer bins tbin = np.bincount(r.ravel(), data_chunk.ravel()) nr = np.arange(len(tbin)) # Get a background measurement if subtract_background: inner = self.radial_profile_pars["skyrad"][0] width = self.radial_profile_pars["width"][0] annulus_apertures = photutils.CircularAnnulus( (centerx, centery), r_in=inner, r_out=inner + width) bkgflux_table = photutils.aperture_photometry( data, annulus_apertures) # to calculate the mean local background, divide the circular # annulus aperture sums by the area fo the circular annulus. # The bkg sum with the circular aperture is then # then mean local background tims the circular apreture area. annulus_area = annulus_apertures.area() sky_per_pix = float(bkgflux_table['aperture_sum'] / annulus_area) tbin -= np.bincount(r.ravel()) * sky_per_pix if getdata: print("Sky per pixel: {0} using\ (rad={1}->{2})".format(sky_per_pix, inner, inner + width)) if fig is None: fig = plt.figure(self._figure_name) fig.clf() fig.add_subplot(111) ax = fig.gca() title = self.radial_profile_pars["title"][0] ax.set_xlabel(self.radial_profile_pars["xlabel"][0]) ax.set_ylabel(self.radial_profile_pars["ylabel"][0]) if getdata: info = "\nat (x,y)={0:d},{1:d}\nradii:{2}\nflux:{3}".format( int(centerx + 1), int(centery + 1), nr, tbin) print(info) logging.info(info) #finish the plot if bool(self.radial_profile_pars["pointmode"][0]): ax.plot(nr, tbin, self.radial_profile_pars["marker"][0]) else: ax.plot(nr, tbin) ax.set_title(title) ax.set_ylim(0, ) #over plot a gaussian fit to the data if bool(self.radial_profile_pars["fitplot"][0]): print("Fit overlay not yet implemented") if 'nbagg' in get_backend().lower(): fig.canvas.draw() else: plt.draw() plt.pause(0.001)
def aperture_photometry_data(star, input_filename, r_stellar, r_inner, r_outer, central_pixel_x, central_pixel_y, wavelength, data, data_unc): #Openeing Scuba2 data and error file data = observation_file(input_filename, sq_pix_size, wavelength) data_unc = observation_file_unc(input_filename, sq_pix_size, wavelength) #Getting stellar central postion from fits file header fitsFile = fits.open(input_filename) cx = central_pixel_x #print ('cx =', cx) cy = central_pixel_y #print ('cy =', cy) central_positions = (cx-1, cy-1) #Aperture Photometry stellar_aperture = photutils.CircularAperture(central_positions, r_stellar) #Stellar Aperture sky_annlus = photutils.CircularAnnulus(central_positions, r_inner, r_outer) #Sky Annulus error = data_unc apertures = [stellar_aperture, sky_annlus] aperture_photometry = photutils.aperture_photometry(data, apertures, error=error) #Carrying out Aperture Photometry #Adding Object name Aperture Photometry Output Table aperture_photometry['Object'] = star #x and y centers of the annuli are already automatically in the aperture photometry tables #Calculating Final Mean Stellar Flux and Unc. bkg_mean = aperture_photometry['aperture_sum_1'] / sky_annlus.area() #Caclulating BG flux bkg_sum = bkg_mean * stellar_aperture.area() stellar_flux = aperture_photometry['aperture_sum_0'] - bkg_sum #Final Mean Flux (Background reduced) aperture_photometry['final_stellar_flux_sum'] = stellar_flux #Source Unc aperture_photometry_unc = np.sqrt( ((aperture_photometry['aperture_sum_err_0'])**2) + ( (((stellar_aperture.area()/sky_annlus.area())*(aperture_photometry['aperture_sum_err_1']))**2) #Rescaling the same as bkg_sum ) ) #Add Calibration Error!!!!!!!! #np.sqrt((((aperture_photometry['aperture_sum_err_0'])**2) + (((aperture_photometry['aperture_sum_err_1'])*(stellar_aperture.area()/sky_annlus.area()))**2))) #Uncertainty on Final Mean Flux #Add Calibration Error!!!!!!!! #Callibration unc of 5% for PACS data Cal_unc = 0.05 * stellar_flux #Total combined unc Tot_unc = ( np.sqrt( ((aperture_photometry_unc/stellar_flux)**2) + ((Cal_unc/stellar_flux)**2) ) ) * stellar_flux aperture_photometry['final_stellar_flux_uncertainty'] = Tot_unc #print(aperture_photometry) return aperture_photometry
# Approximate positions xapprx, yapprx = (607,645) # Centroiding xc,yc = cntrd(target_i,xapprx,yapprx,fwhm=6) print xc,yc # Setting aperture radius and sky annuli ap_rad = 10.0 sky_in = 13.0 sky_out= 20.0 # Defining aperture radius and sky annulus region aperture = ph.CircularAperture( (xc,yc), r=ap_rad) sky_ann = ph.CircularAnnulus ( (xc,yc), r_in=sky_in, r_out=sky_out) # Defining sky per pixel n_pxls_src = np.pi*ap_rad**2 n_pxls_sky = np.pi*(sky_out**2 - sky_in**2) sky_per_pixel = sky_raw['aperture_sum'] / n_pxls_sky bg_flx_in_src = sky_per_pixel * n_pxls_src print sky_per_pixel # Defining source flux src_flux = src_raw['aperture_sum'] - bg_flx_in_src print src_flux # Exposure time from FITS header headerB=pf.getheader('D43.fits') headerV=pf.getheader('D44.fits')
def add_hubble_PSF(origpath, original, psf_flux, santinidir=None, median_combine=False, psfdir=None, RA=None, DEC=None, GALFIT_tmpdir=None, save_psf=False, field_path=None): """ Args: origpath: Path to original images. psf_flux: Desired flux of the PSF that is beeing added to the original image. obj_line: Dictionary containing the following SDSS parameters keywords: run, rerun, camcol, field, colc, rowc, dr7ObjID. tabledir: Path to Santini table. This is needed to read the positions of stars if median_combine is True. median_combine:Bool specifying wheter a single Hubble PSF is used (median_combine=False) or a position dependent PSF is extracted by median combining stars (median_combine=True). psfdir: Path to Hubble PSF. This is necessary if median_combine is set to False. RA/DEC: Coordinates of the galaxy in degrees. This is used to select stars from the neighbourhood if median_combine is set to True. GALFIT_tmpdir: Path to directory where temporary files for GALFIT are saved. GALFIT is only run if median_combine is set to True and all the temporary files will be deleted after they have been used. save_psf: True if PSF should be saved for each image. field_path: Path to field where stars should be extracted from (to create a PSF) """ # If the PSF used for the fake AGN has to be saved (save_psf==True): psf_save_path = conf.run_case + '/psf_used/' pixel_scale = 0.06 fwhm = 0.18 if median_combine: #path_to_field = conf.run_case+"/hlsp_candels_hst_wfc3_gs-tot_f160w_v1.0_drz.fits" path_to_field = field_path if not os.path.isdir(GALFIT_tmpdir): os.makedirs(GALFIT_tmpdir) table = astrotable.Table.read(santinidir) df = table.to_pandas() catalog = df[['RAdeg', 'DECdeg', 'Hmag', 'H_SNR', 'Class_star_1']] field_hdu = fits.open(path_to_field)[0] w = wcs.WCS(field_hdu.header) pixel_coordinates = w.wcs_world2pix(RA, DEC, 0, ra_dec_order=True) pixel_y = pixel_coordinates[1] pixel_x = pixel_coordinates[0] # Filter out hihg SNR stars from the neighborhood of the galaxy. star_mask = df['Class_star_1'] > 0.9 df_stars = df[star_mask] SNR_mask = df_stars['H_SNR'] > 5.0 df_bright = df_stars[SNR_mask] numpy_ra = np.array(df_bright['RAdeg']) numpy_dec = np.array(df_bright['DECdeg']) [x_crds, y_crds] = w.wcs_world2pix(numpy_ra, numpy_dec, 0, ra_dec_order=True) neighbor_mask_size = 4000 neighbor_mask = np.array(np.ones(len(numpy_ra), dtype=bool)) for i in range(0, len(numpy_ra)): if np.abs(x_crds[i]-pixel_x) > neighbor_mask_size/2 or \ np.abs(y_crds[i]-pixel_y) > neighbor_mask_size/2: neighbor_mask[i] = False df_selected = df_bright[neighbor_mask] x_coordinates = x_crds[neighbor_mask] y_coordinates = y_crds[neighbor_mask] h_mags = np.array(df_selected['Hmag']) print str(len(x_coordinates)) + ' stars selected.' csize = 40 # get median combined PSF psf_unscaled = empirical_PSF(field_hdu.data, x_coordinates, y_coordinates, csize, fwhm / pixel_scale, GALFIT_tmpdir, h_mags, phot_zeropoint=25.9463, pixel_scale=pixel_scale) files_to_delete = glob.glob(GALFIT_tmpdir + '*') for f in files_to_delete: os.remove(f) if psf_unscaled is None: return None else: # else <==> median_combine==False psf_unscaled = fits.getdata(psfdir) csize = 40 if median_combine: # compute statistics to subtract the background psf_centroid = [csize, csize] try: statmask = photutils.make_source_mask(psf_unscaled, snr=5, npixels=5, dilate_size=10) except TypeError: return None bkg_annulus = photutils.CircularAnnulus(psf_centroid, 25, 40) bkg_phot_table = photutils.aperture_photometry(psf_unscaled, bkg_annulus, method='subpixel', mask=statmask) bkg_mean_per_pixel = bkg_phot_table['aperture_sum'] / bkg_annulus.area( ) src_aperture = photutils.CircularAperture(psf_centroid, 25) src_phot_table = photutils.aperture_photometry(psf_unscaled, src_aperture, method='subpixel') flux_photutils = src_phot_table['aperture_sum'] - bkg_mean_per_pixel * \ src_aperture.area() scale_factor = psf_flux / flux_photutils psf = scale_factor * (psf_unscaled - bkg_mean_per_pixel) #psf = scale_factor * psf_unscaled else: scale_factor = psf_flux / psf_unscaled.sum() psf = scale_factor * psf_unscaled if save_psf: hdu = fits.PrimaryHDU(psf_unscaled - bkg_mean_per_pixel) hdu.writeto(psf_save_path + os.path.basename(origpath), overwrite=True) # add scaled PSF to the center of the galaxy center = [original.shape[1] // 2, original.shape[0] // 2] centroid_galaxy = find_centroid(original) centroid_PSF = find_centroid(psf) composite_image = np.copy(original) gal_x = int(centroid_galaxy[0]) gal_y = int(centroid_galaxy[1]) ps_x = int(centroid_PSF[0]) ps_y = int(centroid_PSF[1]) # Put the PS on top of the galaxy at its centroid. for x in range(0, psf.shape[1]): for y in range(0, psf.shape[0]): x_rel = gal_x - ps_x + x y_rel = gal_y - ps_y + y if x_rel >= 0 and y_rel >= 0 and x_rel < original.shape[1] and \ y_rel < original.shape[0]: composite_image[y_rel, x_rel] += psf[y, x] return composite_image
def add_sdss_PSF(origpath, original, psf_flux, obj_line, SDSStool_path, psFields_path, sexdir=None, median_combine=False, save_psf=False): """ Args: origpath: Path to original images. psf_flux: Desired flux of the PSF that is beeing added to the original image. obj_line: Dictionary containing the following SDSS parameters keywords: run, rerun, camcol, field, colc, rowc, dr7ObjID. SDSStool_path: Path to SDSS tool (stand alone code) executable. psFields_path: Path to PSF meda data (psFields). sexdir: Path where temporary SExtractor files should be saved. This directory is emptied after SExtractor has done its job. """ SDSS_psf_dir = '%s/psf/SDSS' % conf.run_case GALFIT_psf_dir = '%s/psf/GALFIT' % conf.run_case filter_string = conf.filter_ if not os.path.exists(SDSS_psf_dir): os.makedirs(SDSS_psf_dir) if not os.path.exists(GALFIT_psf_dir): os.makedirs(GALFIT_psf_dir) obj_id = obj_line['dr7ObjID'].item() SDSS_psf_filename = '%s/%s-%s.fits' % (SDSS_psf_dir, obj_id, filter_string) GALFIT_psf_filename = '%s/%s-%s.fits' % (GALFIT_psf_dir, obj_id, filter_string) if not os.path.exists(GALFIT_psf_filename): if not os.path.exists(SDSS_psf_filename): run_sdss_psftool(obj_line, SDSS_psf_filename, SDSStool_path, psFields_path) # Fit the SDSS tool PSF with 3 gaussians to get rid of the noise. psf = galfit.fit_PSF_GALFIT(SDSS_psf_filename, GALFIT_psf_dir) if psf is None: print('Error in Galfit fit') return None else: psf = galfit.open_GALFIT_results(GALFIT_psf_filename, 'model') # median combine stars to get the PSF image if median_combine==True if median_combine: if not os.path.isdir(sexdir): os.makedirs(sexdir) # Try to get the conversion from nanomaggies to counts. It the # information is not available, just use one single value. try: nmgy_per_count = fits.getheader(origpath)['NMGY'] except KeyError: nmgy_per_count = 0.0238446 field_data = get_field(obj_line) # Get the SDSS field. If the data is not available, return None such # that this objid is skipped in roou.py if field_data is None: return None hdu_output = fits.PrimaryHDU(field_data / nmgy_per_count) hdulist_output = fits.HDUList([hdu_output]) hdulist_output.writeto(sexdir + 'field_ADU.fits', overwrite=True) # Some SDSS parameters for SExtractor. exptime = 53.9 threshold = 5 saturation_limit = 4000 gain = 4.73 pixel_scale = 0.396 fwhm = 1.4 zeropoint = calc_zeropoint(exptime, nmgy_per_count) sex_edge = 26 SExtractor_params = { 'exptime': exptime, 'threshold': threshold, 'saturation_level': saturation_limit, 'gain': gain, 'pixel_scale': pixel_scale, 'fwhm': fwhm, 'magzero': zeropoint } x_coordinates, y_coordinates, fluxes, starboolean = \ get_stars_from_field(sexdir, 'field_ADU.fits', SExtractor_params, field_data.shape, sex_edge, mindist=40) if not starboolean: files_to_delete = glob.glob(sexdir + '*') for f in files_to_delete: os.remove(f) return None csize = 20 fluxes = fluxes * nmgy_per_count fluxmask = fluxes > 0 mag_guesses = [] for f in fluxes: mag_guesses.append( mag_from_counts(f / nmgy_per_count, exptime, zeropoint)) mag_guesses = np.array(mag_guesses) # get median combined PSF psf_unscaled = empirical_PSF(field_data, x_coordinates[fluxmask], y_coordinates[fluxmask], csize, fwhm / pixel_scale, sexdir, mag_guesses[fluxmask], zeropoint, pixel_scale) if psf_unscaled is None: files_to_delete = glob.glob(sexdir + '*') for f in files_to_delete: os.remove(f) return None # compute statistics to subtract the background psf_centroid = [csize, csize] try: statmask = photutils.make_source_mask(psf_unscaled, snr=5, npixels=5, dilate_size=10) except TypeError: files_to_delete = glob.glob(sexdir + '*') for f in files_to_delete: os.remove(f) return None bkg_annulus = photutils.CircularAnnulus(psf_centroid, 3 * fwhm / pixel_scale, 20) bkg_phot_table = photutils.aperture_photometry(psf_unscaled, bkg_annulus, method='subpixel', mask=statmask) bkg_mean_per_pixel = bkg_phot_table['aperture_sum'] / bkg_annulus.area( ) src_aperture = photutils.CircularAperture(psf_centroid, 3*fwhm / \ pixel_scale) src_phot_table = photutils.aperture_photometry(psf_unscaled, src_aperture, method='subpixel') flux_photutils = src_phot_table['aperture_sum'] - bkg_mean_per_pixel * \ src_aperture.area() scale_factor = psf_flux / flux_photutils psf = scale_factor * (psf_unscaled - bkg_mean_per_pixel) files_to_delete = glob.glob(sexdir + '*') for f in files_to_delete: os.remove(f) # else <==> median_combine==False else: # Use the 3 gaussian fit of the PSF generated by the SDSS PSF tool. psf = psf / psf.sum() psf = psf * psf_flux center = [original.shape[1] // 2, original.shape[0] // 2] centroid_galaxy = find_centroid(original) centroid_PSF = find_centroid(psf) composite_image = np.copy(original) gal_x = int(centroid_galaxy[0]) gal_y = int(centroid_galaxy[1]) ps_x = int(centroid_PSF[0]) ps_y = int(centroid_PSF[1]) # Put the PS on top of the galaxy at its centroid. for x in range(0, psf.shape[1]): for y in range(0, psf.shape[0]): x_rel = gal_x - ps_x + x y_rel = gal_y - ps_y + y if x_rel >= 0 and y_rel >= 0 and x_rel < original.shape[1] and \ y_rel < original.shape[0]: composite_image[y_rel, x_rel] += psf[y, x] return composite_image
def _perform(self): """ Returns an Argument() with the parameters that depends on this operation. """ self.log.info(f"Running {self.__class__.__name__} action") exptime = float(self.action.args.kd.get('EXPTIME')) # Photutils StarFinder # extract_fwhm = self.cfg['Extract'].getfloat('extract_fwhm', 5) # thresh = self.cfg['Extract'].getint('extract_threshold', 9) # pixel_scale = self.cfg['Telescope'].getfloat('pixel_scale', 1) # pd = self.action.args.kd.pixeldata[0] # mean, median, std = stats.sigma_clipped_stats(pd.data, sigma=3.0) # # daofind = photutils.DAOStarFinder(fwhm=extract_fwhm, threshold=thresh*std) # starfind = photutils.IRAFStarFinder(thresh*std, extract_fwhm) # sources = starfind(pd.data) # self.log.info(f' Found {len(sources):d} stars') # self.log.info(sources.keys()) # # self.action.args.objects = sources # self.action.args.objects.sort('flux') # self.action.args.objects.reverse() # self.action.args.n_objects = len(sources) # if self.action.args.n_objects == 0: # self.log.warning('No stars found') # self.action.args.fwhm = np.nan # self.action.args.ellipticity = np.nan # else: # FWHM_pix = np.median(sources['fwhm']) # roundness = np.median(sources['roundness']) # self.log.info(f' Median FWHM = {FWHM_pix:.1f} pix ({FWHM_pix*pixel_scale:.2f} arcsec)') # self.log.info(f' roundness = {roundness:.2f}') # self.action.args.fwhm = FWHM_pix # self.action.args.ellipticity = np.nan # Source Extractor pixel_scale = self.cfg['Telescope'].getfloat('pixel_scale', 1) thresh = self.cfg['Extract'].getint('extract_threshold', 9) minarea = self.cfg['Extract'].getint('extract_minarea', 7) mina = self.cfg['Extract'].getfloat('fwhm_mina', 1) minb = self.cfg['Extract'].getfloat('fwhm_minb', 1) bsub = self.action.args.kd.pixeldata[ 0].data - self.action.args.background[0].background try: objects = sep.extract( bsub, err=self.action.args.kd.pixeldata[0].uncertainty.array, mask=self.action.args.kd.pixeldata[0].mask, thresh=float(thresh), minarea=minarea) except Exception as e: self.log.error('Source extractor failed') self.log.error(e) return self.action.args t = Table(objects) t['flux'] /= exptime ny, nx = bsub.shape r = np.sqrt((t['x'] - nx / 2.)**2 + (t['y'] - ny / 2.)**2) t.add_column(Column(data=r.data, name='r', dtype=np.float)) coef = 2 * np.sqrt(2 * np.log(2)) fwhm = np.sqrt((coef * t['a'])**2 + (coef * t['b'])**2) t.add_column(Column(data=fwhm.data, name='FWHM', dtype=np.float)) ellipticities = t['a'] / t['b'] t.add_column( Column(data=ellipticities.data, name='ellipticity', dtype=np.float)) filtered = (t['a'] < mina) | (t['b'] < minb) | (t['flag'] > 0) self.log.debug(f' Removing {np.sum(filtered):d}/{len(filtered):d}'\ f' extractions from FWHM calculation') self.action.args.n_objects = len(t[~filtered]) self.log.info(f' Found {self.action.args.n_objects:d} stars') self.action.args.objects = t[~filtered] self.action.args.objects.sort('flux') self.action.args.objects.reverse() if self.action.args.n_objects == 0: self.log.warning('No stars found') else: FWHM_pix = np.median(t['FWHM'][~filtered]) ellipticity = np.median(t['ellipticity'][~filtered]) self.log.info( f' Median FWHM = {FWHM_pix:.1f} pix ({FWHM_pix*pixel_scale:.2f} arcsec)' ) self.log.info(f' ellipticity = {ellipticity:.2f}') self.action.args.fwhm = FWHM_pix self.action.args.ellipticity = ellipticity ## Do photutils photometry measurement positions = [(det['x'], det['y']) for det in self.action.args.objects] ap_radius = self.cfg['Photometry'].getfloat('aperture_radius', 2) * FWHM_pix star_apertures = photutils.CircularAperture(positions, ap_radius) sky_apertures = photutils.CircularAnnulus( positions, r_in=int(np.ceil(1.5 * ap_radius)), r_out=int(np.ceil(2.0 * ap_radius))) phot_table = photutils.aperture_photometry( self.action.args.kd.pixeldata[0], [star_apertures, sky_apertures]) phot_table['sky'] = phot_table['aperture_sum_1'] / sky_apertures.area() med_sky = np.median(phot_table['sky']) self.log.info(f' Median Sky = {med_sky.value:.0f} e-/pix') self.action.args.sky_background = med_sky.value self.action.args.objects.add_column(phot_table['sky']) bkg_sum = phot_table['aperture_sum_1'] / sky_apertures.area( ) * star_apertures.area() final_sum = (phot_table['aperture_sum_0'] - bkg_sum) final_uncert = (bkg_sum + final_sum)**0.5 * u.electron**0.5 phot_table['apflux'] = final_sum / exptime self.action.args.objects.add_column(phot_table['apflux']) phot_table['apuncert'] = final_uncert / exptime self.action.args.objects.add_column(phot_table['apuncert']) phot_table['snr'] = final_sum / final_uncert self.action.args.objects.add_column(phot_table['snr']) where_bad = (final_sum <= 0) self.log.info(f' {np.sum(where_bad)} stars rejected for flux < 0') self.action.args.objects = self.action.args.objects[~where_bad] # where_low_snr = (self.action.args.objects['snr'] <= 5) # self.log.info(f' {np.sum(where_low_snr)} stars rejected for SNR < 5') # self.action.args.objects = self.action.args.objects[~where_low_snr] self.log.info(f' Fluxes for {self.action.args.n_objects:d} stars') return self.action.args
def _perform(self): """ Returns an Argument() with the parameters that depends on this operation. """ self.log.info(f"Running {self.__class__.__name__} action") nx, ny = self.action.args.kd.pixeldata[0].shape ## Do photometry measurement self.log.debug(f' Add pixel positions to catalog') x, y = self.action.args.wcs.all_world2pix(self.action.args.calibration_catalog['raMean'], self.action.args.calibration_catalog['decMean'], 1) self.action.args.calibration_catalog.add_column(Column(data=x, name='x')) self.action.args.calibration_catalog.add_column(Column(data=y, name='y')) buffer = 10 in_image = (self.action.args.calibration_catalog['x'] > buffer)\ & (self.action.args.calibration_catalog['x'] < nx-buffer)\ & (self.action.args.calibration_catalog['y'] > buffer)\ & (self.action.args.calibration_catalog['y'] < ny-buffer) self.log.debug(f' Only {np.sum(in_image)} stars are within image pixel boundaries') self.action.args.calibration_catalog = self.action.args.calibration_catalog[in_image] positions = [p for p in zip(self.action.args.calibration_catalog['x'], self.action.args.calibration_catalog['y'])] self.log.debug(f' Attemping shape measurement for {len(positions)} stars') fwhm = list() elliptiticty = list() orientation = list() xcentroid = list() ycentroid = list() for i, entry in enumerate(self.action.args.calibration_catalog): xi = int(x[i]) yi = int(y[i]) if xi > 10 and xi < nx-10 and yi > 10 and yi < ny-10: im = self.action.args.kd.pixeldata[0].data[yi-10:yi+10,xi-10:xi+10] properties = photutils.data_properties(im) fwhm.append(2.355*(properties.semimajor_axis_sigma.value**2\ + properties.semiminor_axis_sigma.value**2)**0.5) orientation.append(properties.orientation.to(u.deg).value) elliptiticty.append(properties.elongation) xcentroid.append(properties.xcentroid.value) ycentroid.append(properties.ycentroid.value) else: fwhm.append(np.nan) orientation.append(np.nan) elliptiticty.append(np.nan) xcentroid.append(np.nan) ycentroid.append(np.nan) wnan = np.isnan(fwhm) nmasked = np.sum(wnan) self.log.info(f" Measured {len(fwhm)-nmasked} indivisual FWHM values") self.action.args.calibration_catalog.add_column(MaskedColumn( data=fwhm, name='FWHM', mask=wnan)) self.action.args.calibration_catalog.add_column(MaskedColumn( data=orientation, name='orientation', mask=wnan)) self.action.args.calibration_catalog.add_column(MaskedColumn( data=elliptiticty, name='elliptiticty', mask=wnan)) self.action.args.calibration_catalog.add_column(MaskedColumn( data=xcentroid, name='xcentroid', mask=wnan)) self.action.args.calibration_catalog.add_column(MaskedColumn( data=ycentroid, name='ycentroid', mask=wnan)) self.log.debug(f' Attemping aperture photometry for {len(positions)} stars') FWHM_pix = self.action.args.fwhm if self.action.args.fwhm is not None else 8 ap_radius = int(2.0*FWHM_pix) star_apertures = photutils.CircularAperture(positions, ap_radius) sky_apertures = photutils.CircularAnnulus(positions, r_in=int(np.ceil(1.5*ap_radius)), r_out=int(np.ceil(2.0*ap_radius))) self.log.debug(f' Running photutils.aperture_photometry') phot_table = photutils.aperture_photometry( self.action.args.kd.pixeldata[0].data, [star_apertures, sky_apertures]) self.log.debug(f' Subtracting sky flux') phot_table['sky'] = phot_table['aperture_sum_1'] / sky_apertures.area() med_sky = np.nanmedian(phot_table['sky']) self.log.info(f' Median Sky = {med_sky:.0f} e-/pix') self.action.args.sky_background = med_sky self.action.args.calibration_catalog.add_column(phot_table['sky']) bkg_sum = phot_table['aperture_sum_1'] / sky_apertures.area() * star_apertures.area() final_sum = (phot_table['aperture_sum_0'] - bkg_sum)/self.action.args.kd.exptime() phot_table['flux'] = final_sum self.action.args.calibration_catalog.add_column(phot_table['flux']) wzero = (self.action.args.calibration_catalog['flux'] < 0) self.log.debug(f' Masking {np.sum(wzero)} stars with <0 flux') self.action.args.calibration_catalog['flux'].mask = wzero # Estimate flux from catalog magnitude self.log.debug(f' Estimate flux from catalog magnitude') d_telescope = self.cfg['Telescope'].getfloat('d_primary_mm', 508) d_obstruction = self.cfg['Telescope'].getfloat('d_obstruction_mm', 127) A = 3.14*(d_telescope/2/1000)**2 - 3.14*(d_obstruction/2/1000)**2 # m^2 self.action.args.f0 = estimate_f0(A, band=self.action.args.band) # photons / sec catflux = self.action.args.f0\ * 10**(-self.action.args.calibration_catalog[f'{self.action.args.band}MeanApMag']/2.5) self.action.args.calibration_catalog.add_column(Column(data=catflux*u.photon/u.second, name='catflux')) self.log.debug(f' Fit, clipping brightest 5% and faintest 5% of stars') bad = (self.action.args.calibration_catalog['flux'].mask == True) nclip = int(np.floor(0.05*len(self.action.args.calibration_catalog[~bad]))) fitted_line = sigma_clipping_line_fit(self.action.args.calibration_catalog[~bad]['catflux'][nclip:-nclip].data, self.action.args.calibration_catalog[~bad]['flux'][nclip:-nclip].data, intercept_fixed=True) self.log.info(f" Slope (e-/photon) = {fitted_line.slope.value:.3g}") self.action.args.zero_point_fit = fitted_line deltas = self.action.args.calibration_catalog['flux'].data\ - fitted_line(self.action.args.calibration_catalog['catflux'].data) mean, med, std = stats.sigma_clipped_stats(deltas) self.log.info(f" Fit StdDev = {std:.2g}") return self.action.args
def photometry(ax, image, pos, ap_size=10, r_in=20, r_out=30, back_photo=True): ''' Create apertures around specified list of stars Parameters: ----------- ax : axis class axis of a subplot. image : ndarray Image for aperture photometry to be performed on. pos : 2darray A list of positions in x,y pixel coordinates for each star. Needs to be as [[0,0],[1,1]...] ap_size : int The radius of a circular aperture in pixel size r_in : int The inner radius of background aperture in pixel size (Ignore if back_photo=False) r_out : int The outer radius of background aperture in pixel size (Ignore if back_photo=False) back_photo : bool Set to True if want to return an array of background values, False to ignore anything to do with background Returns ------- photometry of each star : array average background pixel value around each star : array (If back_photo=True) plots image with the aperture and centroids located for each star ''' nstars = np.arange(0, np.shape(pos)[0], 1) name_stars = [] for i in nstars: name_stars.append('Star {}'.format(i)) #print name_stars aperture = photutils.CircularAperture(pos, r=ap_size) if back_photo == True: back_aperture = photutils.CircularAnnulus(pos, r_in, r_out) pos = np.array(pos) #plt.imshow(image,origin='lower') for i in range(len(name_stars)): circle1 = plt.Circle((pos[i, 0], pos[i, 1]), ap_size, color='black', fill=False, zorder=100) ax.add_artist(circle1) plt.axhline(pos[i, 1], xmin=pos[i, 0] / 100. - .01, xmax=pos[i, 0] / 100. + .02, color='black') plt.axvline(pos[i, 0], ymin=pos[i, 1] / 100. - .01, ymax=pos[i, 1] / 100. + .02, color='black') plt.text(pos[i, 0] + ap_size + 1, pos[i, 1], name_stars[i], zorder=11) #print pos[i,1] #print pos[i,0] if back_photo == True: circle2 = plt.Circle((pos[i, 0], pos[i, 1]), r_in, color='cyan', fill=False, zorder=10) circle3 = plt.Circle((pos[i, 0], pos[i, 1]), r_out, color='cyan', fill=False, zorder=10) ax.add_artist(circle2) ax.add_artist(circle3) #aperture.plot(origin=(0,0),indices=None,ax=ax,fill=False) #plt.ion() phot_table = photutils.aperture_photometry(image, aperture) flux_values = phot_table[ 'aperture_sum'].data #gets a list of the total flux in specified aperture for each star if back_photo == True: back_table = photutils.aperture_photometry(image, back_aperture) area = np.pi * (r_out**2 - r_in**2) a = np.ones((np.shape(image)[0], np.shape(image)[1])) area = photutils.aperture_photometry(a, back_aperture) back_values = back_table['aperture_sum'].data / area[ 'aperture_sum'].data return flux_values, back_values * np.pi * ap_size**2 else: return flux_values
def forced_phot(image, wcs, zp, catalog_alt, catalog_az, catalog_mag, catalog_id, nside=8, phot_params=None, do_background=False, return_table=False, mjd=0.): """ Generate a map of the extinction on the sky Parameters ---------- image : array The image to find the extinction map from wcs : wcs-like object WCS describing the image. Note WCS RA,Dec will be interpreted as Az,Alt. zp : float The zeropoint of the image catalog_alt : array Altitude of stars expected to be in the image (degrees) catalog_az : array Azimuth of stars expected in the image (degrees) catalog_mag : array Magnitudes of stars expected in the image nside : int Healpixel nside to set resoltion of output map phot_params : dict (None) Dictionary holding common photometry kwargs. Loads defaults from default_phot_params if None. do_background : bool (False) Do a 2D background model subtraction. Skipped by default because astropy is really slow. return_table : bool (False) If True, return the astropy table with the photometry, otherwise, return the extinction map. Output ------ extinction : array A healpixel array (where latitude and longitude are altitude and azimuth). Pixels with no stars are filled with the healpixel mask value. Non-masked pixels have the measured extinction in magnitudes. """ if phot_params is None: phot_params = default_phot_params() # Find the healpixel for each catalog star lat = np.radians(90. - catalog_alt) catalog_hp = hp.ang2pix(nside, lat, np.radians(catalog_az)) order = np.argsort(catalog_hp) catalog_alt = catalog_alt[order] catalog_az = catalog_az[order] catalog_mag = catalog_mag[order] catalog_id = catalog_id[order] catalog_hp = catalog_hp[order] catalog_x, catalog_y = wcs.all_world2pix(catalog_az, catalog_alt, 0) good_transform = ~np.isnan(catalog_x) # Find the x,y positions of helpix centers lat, lon = hp.pix2ang(nside, np.arange(hp.nside2npix(nside))) hp_alt = np.degrees(np.pi / 2. - lat) hp_az = np.degrees(lon) hp_x, hp_y = wcs.all_world2pix(hp_az, hp_alt, 0) # Run the photometry at the expected catalog positions if do_background: sigma_clip = phu.SigmaClip(sigma=phot_params['bk_clip_sigma'], iters=phot_params['bk_iter']) bkg_estimator = phu.MedianBackground() bkg = phu.Background2D( image, (phot_params['background_size'], phot_params['background_size']), filter_size=(phot_params['bk_filter_size'], phot_params['bk_filter_size']), sigma_clip=sigma_clip, bkg_estimator=bkg_estimator) bk_img = image - bkg.background else: bk_img = image positions = list(zip(catalog_x[good_transform], catalog_y[good_transform])) apertures = phu.CircularAperture(positions, r=phot_params['apper_r']) annulus_apertures = phu.CircularAnnulus(positions, r_in=phot_params['ann_r_in'], r_out=phot_params['ann_r_out']) apers = [apertures, annulus_apertures] phot_table = phu.aperture_photometry(bk_img, apers) bkg_mean = phot_table['aperture_sum_1'] / annulus_apertures.area() bkg_sum = bkg_mean * apertures.area() final_sum = phot_table['aperture_sum_0'] - bkg_sum phot_table['residual_aperture_sum'] = final_sum phot_table['catalog_id'] = catalog_id[good_transform] phot_table['residual_aperture_mag'] = -2.5 * np.log10(final_sum) + zp detected = np.where(final_sum > 0) mag_difference = phot_table['residual_aperture_mag'][ detected] - catalog_mag[good_transform][detected] phot_table['mag_difference'] = np.zeros( phot_table['residual_aperture_mag'].data.size, dtype=float) phot_table['mjd'] = np.zeros(phot_table['residual_aperture_mag'].data.size, dtype=float) phot_table['mag_difference'][detected] = mag_difference phot_table['mjd'] = mjd bins = np.arange(hp.nside2npix(nside) + 1) - 0.5 result, be, bn = binned_statistic(catalog_hp[good_transform][detected], mag_difference, bins=bins) if return_table: return phot_table, result else: return result