def sources_mask(shape, x, y, a, b, theta, mask=None, scale=1.0): """Create a mask to cover all sources.""" image = np.zeros(shape, dtype=bool) sep.mask_ellipse(image, x, y, a, b, theta, r=scale) if mask is not None: image |= np.array(mask) return image
def spec_from_ellipse(cube, varcube = None, x0 = 0., y0 = 0., a = 1., b = 1., theta = 0., r = 1.): """ Get the spectrum within an elliptical region Args: cube (Spectral Cube): A datacube object varcube (Spectral Cube, optional): Variance cube x0, y0 (float, optional): Centroid of ellipse a, b (float, optional): semi-major and semi-minor axes theta (float, optional): rotation angle of the semi-major axis from the positive x axis. r (float, optional): Scaling factor for a and b. If not 1, a = r*a and b = r*b. Returns: spec (OneDSpectrum): The extracted spectrum. var (OneDSpectrum): Variance in spectrum. Only returned if varcube is supplied. """ #TODO: Use photutils aperture object for this. # Create 2D mask first mask = np.zeros(cube.shape[1:], dtype=np.bool) sep.mask_ellipse(mask, x0, y0, a, b, theta,r) return spec_from_mask(cube, mask, varcube=varcube)
def test_mask_ellipse(): arr = np.zeros((20, 20), dtype=np.bool) # should mask 5 pixels: sep.mask_ellipse(arr, 10., 10., 1.0, 1.0, 0.0, r=1.001) assert arr.sum() == 5 # should mask 13 pixels: sep.mask_ellipse(arr, 10., 10., 1.0, 1.0, 0.0, r=2.001) assert arr.sum() == 13
def test_mask_ellipse_alt(): """mask_ellipse with cxx, cyy, cxy parameters.""" arr = np.zeros((20, 20), dtype=np.bool) # should mask 5 pixels: sep.mask_ellipse(arr, 10., 10., cxx=1.0, cyy=1.0, cxy=0.0, r=1.001) assert arr.sum() == 5 # should mask 13 pixels: sep.mask_ellipse(arr, 10., 10., cxx=1.0, cyy=1.0, cxy=0.0, r=2.001) assert arr.sum() == 13
def test_mask_ellipse_dep(): """Deprecated version of mask_ellipse""" arr = np.zeros((20, 20), dtype=np.bool) # should mask 5 pixels: sep.mask_ellipse(arr, 10., 10., cxx=1.0, cyy=1.0, cxy=0.0, scale=1.001) assert arr.sum() == 5 # should mask 13 pixels: sep.mask_ellipse(arr, 10., 10., cxx=1.0, cyy=1.0, cxy=0.0, scale=2.001) assert arr.sum() == 13
def sep_ellipse_mask(sources, image_shape, scale=5.0): logger.info('building ellipse mask') mask = np.zeros(image_shape, dtype=bool) sep.mask_ellipse(mask, sources['x'], sources['y'], sources['a'], sources['b'], sources['theta'], scale) logger.info('{:.2f}% of patch masked'.format(100 * mask.sum()/mask.size)) return mask
def sep_ellipse_mask(sources, image_shape, scale=5.0): logger.info('building ellipse mask') mask = np.zeros(image_shape, dtype=bool) sep.mask_ellipse(mask, sources['x'], sources['y'], sources['a'], sources['b'], sources['theta'], scale) logger.info('{:.2f}% of image masked'.format(100 * mask.sum() / mask.size)) return mask
def gaia_star_mask(img, wcs, pix=0.168, mask_a=694.7, mask_b=4.04, size_buffer=1.4, gaia_bright=18.0, factor_b=1.3, factor_f=1.9): """Find stars using Gaia and mask them out if necessary. Using the stars found in the GAIA TAP catalog, we build a bright star mask following similar procedure in Coupon et al. (2017). We separate the GAIA stars into bright (G <= 18.0) and faint (G > 18.0) groups, and apply different parameters to build the mask. """ gaia_stars = image_gaia_stars(img, wcs, pixel=pix, mask_a=mask_a, mask_b=mask_b, verbose=False, visual=False, size_buffer=size_buffer) # Make a mask image msk_star = np.zeros(img.shape).astype('uint8') if gaia_stars is not None: gaia_b = gaia_stars[gaia_stars['phot_g_mean_mag'] <= gaia_bright] sep.mask_ellipse(msk_star, gaia_b['x_pix'], gaia_b['y_pix'], gaia_b['rmask_arcsec'] / factor_b / pix, gaia_b['rmask_arcsec'] / factor_b / pix, 0.0, r=1.0) gaia_f = gaia_stars[gaia_stars['phot_g_mean_mag'] > gaia_bright] sep.mask_ellipse(msk_star, gaia_f['x_pix'], gaia_f['y_pix'], gaia_f['rmask_arcsec'] / factor_f / pix, gaia_f['rmask_arcsec'] / factor_f / pix, 0.0, r=1.0) return gaia_stars, msk_star return None, msk_star
def make_HSC_detect_mask(bin_msk, img, objects, segmap, r=10.0, radius=1.5, threshold=0.01): '''Make HSC detection and bright star mask, based on HSC binary mask flags. Parameters: ----------- bin_msk: 2-D np.array, can be loaded from HSC image cutouts objects: table, returned from sep.extract_obj segmap: 2-D np.array, returned from sep.extract_obj r: float, blow-up parameter radius: float, convolution radius threshold: float, threshold of making mask after convolution Returns: ----------- HSC_detect_mask: 2-D boolean np.array See also: ----------------- convert_HSC_binary_mask(bin_msk) ''' import sep TDmask = convert_HSC_binary_mask(bin_msk) cen_mask = np.zeros(bin_msk.shape, dtype=np.bool) cen_obj = objects[segmap[int(bin_msk.shape[0] / 2.), int(bin_msk.shape[1] / 2.)] - 1] fraction_radius = sep.flux_radius(img, cen_obj['x'], cen_obj['y'], 10 * cen_obj['a'], 0.5)[0] ba = np.divide(cen_obj['b'], cen_obj['a']) sep.mask_ellipse(cen_mask, cen_obj['x'], cen_obj['y'], fraction_radius, fraction_radius * ba, cen_obj['theta'], r=r) from astropy.convolution import convolve, Gaussian2DKernel HSC_mask = (TDmask[:, :, 5]).astype(bool) * ( ~cen_mask) + TDmask[:, :, 9].astype(bool) # Convolve the image with a Gaussian kernel with the width of 1.5 pixel cvl = convolve(HSC_mask.astype('float'), Gaussian2DKernel(radius)) HSC_detect_mask = cvl >= threshold return HSC_detect_mask
def iraf_star_mask(img, threshold, fwhm, mask=None, bw=500, bh=500, fw=4, fh=4, zeropoint=27.0, mag_lim=24.0): """Detect all stellar objects using DAOFind and IRAFStarFinder.""" bkg_star = sep.Background(img, mask=mask, bw=bw, bh=bh, fw=fw, fh=fh) dao_finder = DAOStarFinder(fwhm=fwhm, threshold=threshold * bkg_star.globalrms) irf_finder = IRAFStarFinder(fwhm=fwhm, threshold=threshold * bkg_star.globalrms) stars_dao = dao_finder(img - bkg_star.globalback) stars_irf = irf_finder(img - bkg_star.globalback) msk_star = np.zeros(img.shape).astype('uint8') if len(stars_irf) > 0: stars_irf_use = stars_irf[(-2.5 * np.log10(stars_irf['flux']) + zeropoint) <= mag_lim] sep.mask_ellipse(msk_star, stars_irf_use['xcentroid'], stars_irf_use['ycentroid'], fwhm, fwhm, 0.0, r=1.0) else: stars_irf_use = None if len(stars_dao) > 0: stars_dao_use = stars_dao[(-2.5 * np.log10(stars_dao['flux']) + zeropoint) <= mag_lim] sep.mask_ellipse(msk_star, stars_dao_use['xcentroid'], stars_dao_use['ycentroid'], fwhm, fwhm, 0.0, r=1.0) else: stars_dao_use = None return stars_dao_use, stars_irf_use, msk_star
def get_trace_source(self, x, y, a=1, b=1, theta=0): """ ccdpixels -> traceindex The method get the RGBA values within an ellipe centered in `x` and `y` with a major and minor axes length `a` and `b` and angle `theta`. The index of the traces that maximize the color overlap is then returned. Parameters ---------- x, y: [float, float] x and y central position of the ellipses a, b: [float, float] -optional- major and minor axis lengths theta: [float] -optional- rotation angle (in rad) of the ellipse. Returns ------- traceindex """ try: from sep import mask_ellipse except: raise ImportError( "You need sep (Python verion of Sextractor) to run this method => sudo pip install sep" ) masking = np.zeros(self._mapshape, dtype="bool") mask_ellipse(masking, x * self.subpixelization, y * self.subpixelization, a * self.subpixelization, b * self.subpixelization, theta=theta) if not np.any(masking): return np.asarray([]) sum_mask = np.sum(np.sum( [(self._facecolors.flatten() == v) for v in np.concatenate(self._maskimage.T.T[masking])], axis=0).reshape(self._facecolors.shape), axis=1) return np.asarray(self.trace_indexes)[sum_mask == sum_mask.max( )] if sum_mask.max() > 0 else np.asarray([])
def get_mask(self, from_sources=None, **kwargs): """ get data mask Parameters ---------- from_source: [None/bool/DataFrame] -optional- A mask will be extracted from the given source. (This uses, sep.mask_ellipse) Accepted format: - None or False: ignored. - True: this uses the self.sources - DataFrame: this will using this dataframe as source. this dataframe must contain: x,y,a,b,theta => accepted kwargs: 'r' the scale (diameter) of the ellipse (5 default) # # anything else, self.mask is returned # # Returns ------- 2D array (True where should be masked out) """ # Source mask if from_sources is not None and from_sources is not False: if type(from_sources)== bool and from_sources: from_sources = self.sources elif type(from_sources) is not pandas.DataFrame: raise ValueError("cannot parse the given from_source could be bool, or DataFrame") from sep import mask_ellipse ellipsemask = np.asarray(np.zeros(self.shape),dtype="bool") # -- Apply the mask to falsemask mask_ellipse(ellipsemask, *from_sources[["x","y","a","b","theta"]].astype("float").values.T, r=kwargs.get('r',5) ) return ellipsemask return self.mask
def bg_subtract2(im, obj, flux, fluxerr, r=None): if r is None: a = obj['a'] b = obj['b'] else: a = r b = r theta = max(min(obj['theta'], np.pi / 2.00001), -np.pi / 2.00001) outer = np.zeros(im.shape, bool) sep.mask_ellipse(outer, obj['x'], obj['y'], a, b, theta, r=5) inner = np.zeros(im.shape, bool) sep.mask_ellipse(inner, obj['x'], obj['y'], a, b, theta, r=3) area = np.zeros(im.shape, bool) sep.mask_ellipse(area, obj['x'], obj['y'], a, b, theta) area = area.sum() mask = outer * ~inner bgap = im * mask bgap = bgap[bgap != 0] mms = sigma_clipped_stats(bgap) flux -= mms[1] * area fluxerr = np.sqrt(fluxerr**2 + area * mms[2] * (1 + area / len(bgap))) return flux, fluxerr
def make_obj_mask(cat, img_shape, grow_r=1.0): """ Use SEP to build a mask based on objects in input catalog. Parameters ---------- cat : astropy.table.Table Source catalog form SEP. img_shape : array-like The shape of the image to be masked. grow_r : float, optional Fraction to grow the objects sizes. Returns ------- mask : 2D ndarray Mask with same shape as img_shape. """ mask = np.zeros(img_shape, dtype='uint8') sep.mask_ellipse(mask, cat['x'], cat['y'], cat['a'], cat['b'], cat['theta'], grow_r) return mask
def sources_mask(shape, x, y, a, b, theta, mask=None, scale=1.0): """Create a mask to cover all sources. Parameters ---------- shape: int or tuple of ints Shape of the new array. x,y: array_like Center of ellipse(s). a, b, theta: array_like (optional) Parameters defining the extent of the ellipe(s). mask: numpy.ndarray (optional) An optional mask. Default: `None` scale: array_like (optional) Scale factor of ellipse(s). Default: 1.0 """ image = np.zeros(shape, dtype=bool) sep.mask_ellipse(image, x, y, a, b, theta, r=scale) if mask is not None: image |= np.array(mask) return image
def findsources(image,cube,check=False,output='.',spectra=False,helio=0,nsig=2., minarea=10.,regmask=None,clean=True,outspec='Spectra',marz=False, rphot=False, sname='MUSE'): """ Take a detection image (collapse of a cube), or median of an RGB, or whatever you want (but aligned to the cube) and run sourceextractor Use SEP utilities http://sep.readthedocs.org/en/stable/ image -> fits file of image to process check -> if true write a bunch of check mages output -> where to dump the output cube -> the cube used to extract spectra spectra -> if True, extract spectra in VACUUM wave!! helio -> pass additional heliocentric correction nsig -> number of skyrms used for source id minarea -> minimum area for extraction regmask -> ds9 region file (image) of regions to be masked before extraction [e.g. edges] clean -> clean souces outspec -> where to store output spectra marz -> write spectra in also marz format (spectra needs to be true). If set to numerical value, this is used as an r-band magnitude limit. rphot -> perform r-band aperture photometry and add r-band magnitudes to the catalogue sname -> prefix for the source names. Default = MUSE """ import sep from astropy.io import fits from astropy import wcs from astropy import coordinates from astropy import units as u from astropy import table import numpy as np import os try: from mypython.ifu import muse_utils as utl from mypython.fits import pyregmask as msk except ImportError: from mypython import ifu from ifu import muse_utils as utl from mypython import fits from fits import pyregmask as msk from astropy.io import fits from shutil import copyfile import glob #open image img=fits.open(image) try: header=img[1].header except: header= img[0].header imgwcs = wcs.WCS(header) try: #this is ok for narrow band images data=img[1].data except: #white cubex images data=img[0].data data=data.byteswap(True).newbyteorder() #grab effective dimension nex,ney=data.shape #close fits img.close() #create bad pixel mask if(regmask): Mask=msk.PyMask(ney,nex,regmask,header=img[0].header) for ii in range(Mask.nreg): Mask.fillmask(ii) if(ii == 0): badmask=Mask.mask else: badmask+=Mask.mask badmask=1.*badmask else: badmask=np.zeros((nex,ney)) if(check): print('Dumping badmask') hdumain = fits.PrimaryHDU(badmask,header=header) hdulist = fits.HDUList([hdumain]) hdulist.writeto(output+"/badmask.fits",overwrite=True) #check background level, but do not subtract it print('Checking background levels') bkg = sep.Background(data,mask=badmask) print('Residual background level ', bkg.globalback) print('Residual background rms ', bkg.globalrms) if(check): print('Dumping sky...') #dump sky properties back = bkg.back() rms = bkg.rms() hdumain = fits.PrimaryHDU(back,header=header) hdubk = fits.ImageHDU(back) hdurms = fits.ImageHDU(rms) hdulist = fits.HDUList([hdumain,hdubk,hdurms]) hdulist.writeto(output+"/skyprop.fits",overwrite=True) #extracting sources at nsigma thresh = nsig * bkg.globalrms # segmap = np.zeros((header["NAXIS1"],header["NAXIS2"])) objects, segmap=sep.extract(data,thresh,segmentation_map=True, minarea=minarea,clean=clean,mask=badmask,deblend_cont=0.0001) print("Extracted {} objects... ".format(len(objects))) if(spectra): if not os.path.exists(outspec): os.makedirs(outspec) if((check) | (spectra)): #create a detection mask alla cubex srcmask=np.zeros((1,data.shape[0],data.shape[1])) nbj=1 print('Generating spectra...') #loop over detections for obj in objects: #init mask tmpmask=np.zeros((data.shape[0],data.shape[1]),dtype=np.bool) tmpmask3d=np.zeros((1,data.shape[0],data.shape[1]),dtype=np.bool) #fill this mask sep.mask_ellipse(tmpmask,obj['x'],obj['y'],obj['a'],obj['b'],obj['theta'],r=2) tmpmask3d[0,:,:]=tmpmask[:,:] srcmask=srcmask+tmpmask3d*nbj if(spectra): savename="{}/id{}.fits".format(outspec,nbj) if not os.path.exists(savename): utl.cube2spec(cube,obj['x'],obj['y'],None,write=savename, shape='mask',helio=helio,mask=tmpmask3d,tovac=True) else: print("{} already exists. Skipping it...".format(savename)) #go to next nbj=nbj+1 if(check): print('Dumping source mask...') hdumain = fits.PrimaryHDU(srcmask,header=header) hdubk = fits.ImageHDU(srcmask) hdulist = fits.HDUList([hdumain,hdubk]) hdulist.writeto(output+"/source.fits",overwrite=True) print('Dumping segmentation map') hdumain = fits.PrimaryHDU(segmap,header=header) hdubk = fits.ImageHDU(segmap) hdulist = fits.HDUList([hdumain,hdubk]) hdulist.writeto(output+"/segmap.fits",overwrite=True) #Generate source names using coordinates and name prefix ra, dec = imgwcs.wcs_pix2world(objects['x'], objects['y'],0) coord = coordinates.FK5(ra*u.degree, dec*u.degree) rastr = coord.ra.to_string(u.hour, precision=2, sep='') decstr = coord.dec.to_string(u.degree, precision=1, sep='', alwayssign=True) name = [sname+'J{0}{1}'.format(rastr[k], decstr[k]) for k in range(len(rastr))] ids = np.arange(len(name)) #write source catalogue print('Writing catalogue..') tab = table.Table(objects) tab.add_column(table.Column(name),0,name='name') tab.add_column(table.Column(ids),0,name='ID') tab.write(output+'/catalogue.fits',overwrite=True) #cols = fits.ColDefs(objects) #cols.add_col(fits.Column(name, format='A')) #tbhdu = fits.BinTableHDU.from_columns(cols) #tbhdu.writeto(output+'/catalogue.fits',clobber=True) #rband photometry if (rphot): if not os.path.exists(output+'/Image_R.fits'): rimg, rvar, rwcsimg = utl.cube2img(cube, filt=129, write=output+'/Image_R.fits') phot_r = sourcephot(output+'/catalogue.fits', output+'/Image_R.fits', output+'/segmap.fits', image) phot_r.add_column(table.Column(name),1,name='name') tbhdu = fits.open(output+'/catalogue.fits')[1] tbhdu2 = fits.BinTableHDU(phot_r) hdulist = fits.HDUList([fits.PrimaryHDU(), tbhdu, tbhdu2]) hdulist.writeto(output+'/catalogue.fits',overwrite=True) if((marz) & (spectra)): #if marz is True but no magnitude limit set, create marz file for whole catalogue if marz==True: marz_file(image, output+'/catalogue.fits', outspec, output) else: #create folder and catalogue with just sources brighter than mag limit if os.path.exists(output + '/spectra_r' + str(marz)): files = glob.glob(output + '/spectra_r' + str(marz) +'/*') for f in files: os.remove(f) else: os.mkdir(output + '/spectra_r' + str(marz)) mag = phot_r['MAGSEG'] #add in x y pixels from original catalogue x, y = tbhdu.data['x'], tbhdu.data['y'] phot_r['x'], phot_r['y'] = x, y #add in ra,dec img = fits.open(image) mywcs = wcs.WCS(img[0].header) ra, dec = mywcs.all_pix2world(x,y,0) phot_r['RA'] = ra phot_r['dec'] = dec for i in range(len(mag)): if mag[i] < marz: copyfile((output + '/spectra/id' + str(i+1) + '.fits'), (output + '/spectra_r' + str(marz) + '/id' + str(i+1) + '.fits')) #Write photometry catalog with objects below magnitude limit excluded phot_r.remove_rows(phot_r['MAGSEG'] > marz) catalogue_lim_name = (output + '/catalogue_r' + str(marz) +'.fits') if os.path.exists(catalogue_lim_name): os.remove(catalogue_lim_name) phot_r.write(catalogue_lim_name) outspec = output + '/spectra_r' + str(marz) marz_file(image, output+'/catalogue_r' + str(marz) +'.fits', outspec, output, r_lim=marz) print('All done') return objects
def sourcephot(catalogue,image,segmap,detection,instrument='MUSE',dxp=0.,dyp=0., noise=[False],zpab=False, kn=2.5, circap=1.0): """ Get a source catalogue from findsources and a fits image with ZP and compute magnitudes in that filter catalogue -> source cat from findsources image -> fits image with ZP in header segmap -> fits of segmentation map detection -> the detection image, used to compute Kron radius instrument -> if not MUSE, map positions from detection to image dxp,dyp -> shifts in pixel of image to register MUSE and image astrometry noise -> if set to a noise model, use equation noise[0]*noise[1]*npix**noise[2] to compute the error zpab -> if ZPAB (zeropoint AB) not stored in header, must be supplied kn -> factor to be used when scaling Kron apertures [sextractor default 2.5] circap -> radius in arcsec for aperture photmetry to be used when Kron aperture fails """ from astropy.io import fits import numpy as np import sep import matplotlib.pyplot as plt from astropy.table import Table from astropy import wcs #grab root name rname=((image.split('/')[-1]).split('.fits'))[0] print ('Working on {}'.format(rname)) #open the catalogue/fits cat=fits.open(catalogue) img=fits.open(image) seg=fits.open(segmap) det=fits.open(detection) #grab reference wcs from detection image try: wref=wcs.WCS(det[1].header) except: wref = wcs.WCS(det[0].header) psref=wref.pixel_scale_matrix[1,1]*3600. print ('Reference pixel size {}'.format(psref)) #if not handling MUSE, special cases for format of data if('MUSE' not in instrument): #handle instrument cases if('LRIS' in instrument): #data imgdata=img[1].data #place holder for varaince as will use noise model below vardata=imgdata*0+1 vardata=vardata.byteswap(True).newbyteorder() #grab wcs image wimg=wcs.WCS(img[1].header) psimg=wimg.pixel_scale_matrix[1,1]*3600. #store the ZP if(zpab): img[0].header['ZPAB']=zpab else: print('Instrument not supported!!') exit() else: #for muse, keep eveything the same imgdata=img[0].data vardata=img[1].data psimg=psref #grab flux and var dataflx=np.nan_to_num(imgdata.byteswap(True).newbyteorder()) datavar=np.nan_to_num(vardata.byteswap(True).newbyteorder()) # import pdb; pdb.set_trace() #grab detection and seg mask try: detflx=np.nan_to_num(det[1].data.byteswap(True).newbyteorder()) except: detflx = np.nan_to_num(det[0].data.byteswap(True).newbyteorder()) #go back to 1d if(len(seg[0].data.shape)>2): segmask=(np.nan_to_num(seg[0].data.byteswap(True).newbyteorder()))[0,:,:] else: segmask=(np.nan_to_num(seg[0].data.byteswap(True).newbyteorder())) #if needed, map the segmap to new image with transformation if('MUSE' not in instrument): #allocate space for transformed segmentation map segmasktrans=np.zeros(dataflx.shape) print("Remapping segmentation map to new image...") #loop over original segmap and map to trasformed one #Just use nearest pixel, and keep only 1 when multiple choices for xx in range(segmask.shape[0]): for yy in range(segmask.shape[1]): #go to world radec=wref.wcs_pix2world([[yy,xx]],0) #back to new instrument pixel newxy=wimg.wcs_world2pix(radec,0) #apply shift to register WCS newxy[0][1]=newxy[0][1]+dyp newxy[0][0]=newxy[0][0]+dxp segmasktrans[newxy[0][1],newxy[0][0]]=segmask[xx,yy] #grow buffer as needed by individual instruments #This accounts for resampling to finer pixel size if('LRIS' in instrument): segmasktrans[newxy[0][1]+1,newxy[0][0]+1]=segmask[xx,yy] segmasktrans[newxy[0][1]-1,newxy[0][0]-1]=segmask[xx,yy] segmasktrans[newxy[0][1]+1,newxy[0][0]-1]=segmask[xx,yy] segmasktrans[newxy[0][1]-1,newxy[0][0]+1]=segmask[xx,yy] segmasktrans[newxy[0][1]+1,newxy[0][0]]=segmask[xx,yy] segmasktrans[newxy[0][1]-1,newxy[0][0]]=segmask[xx,yy] segmasktrans[newxy[0][1],newxy[0][0]-1]=segmask[xx,yy] segmasktrans[newxy[0][1],newxy[0][0]+1]=segmask[xx,yy] #dump the transformed segmap for checking hdumain = fits.PrimaryHDU(segmasktrans,header=img[1].header) hdulist = fits.HDUList(hdumain) hdulist.writeto("{}_segremap.fits".format(rname),clobber=True) else: #no transformation needed segmasktrans=segmask #source to extract nsrc=len(cat[1].data) print('Extract photometry for {} sources'.format(nsrc)) phot = Table(names=('ID', 'MAGAP', 'MAGAP_ERR','FXAP', 'FXAP_ERR', 'RAD', 'MAGSEG', 'MAGSEG_ERR', 'FXSEG', 'FXSEG_ERR','ZP'), dtype=('i4','f4','f4','f4','f4','f4','f4','f4','f4','f4','f4')) #create check aperture mask checkaperture=np.zeros(dataflx.shape) print('Computing photometry for objects...') #loop over each source for idobj in range(nsrc): ######### #Find positions etc and transform as appropriate ######### #extract MUSE source paramaters x= cat[1].data['x'][idobj] y= cat[1].data['y'][idobj] a= cat[1].data['a'][idobj] b= cat[1].data['b'][idobj] theta= cat[1].data['theta'][idobj] #compute kron radius on MUSE detection image #Kron rad in units of a,b tmpdata=np.copy(detflx) tmpmask=np.copy(segmask) #mask all other sources to avoid overlaps but keep desired one pixels=np.where(tmpmask == idobj+1) tmpmask[pixels]=0 #compute kron radius [pixel of reference image] kronrad, flg = sep.kron_radius(tmpdata,x,y,a,b,theta,6.0,mask=tmpmask) #plt.imshow(np.log10(tmpdata+1),origin='low') #plt.show() #exit() #now check if size is sensible in units of MUSE data rmin = 2.0 #MUSE pix use_circle = kronrad * np.sqrt(a*b) < rmin #use circular aperture of 2" in muse pixel unit rcircap = circap/psref #now use info to compute photometry and apply #spatial transformation if needed if('MUSE' not in instrument): #map centre of aperture - +1 reference #go to world radec=wref.wcs_pix2world([[x,y]],1) #back to new instrument pixel newxy=wimg.wcs_world2pix(radec,1) #apply shift to register WCS xphot=newxy[0][0]+dxp yphot=newxy[0][1]+dyp #scale radii to new pixel size rminphot=rcircap*psref/psimg aphot=a*psref/psimg bphot=b*psref/psimg #Kron radius in units of a,b else: #for muse, transfer to same units xphot=x yphot=y rminphot=rcircap aphot=a bphot=b ##### #Compute local sky ##### skyreg=kn*kronrad*np.sqrt(aphot*bphot)+15 if (yphot-skyreg < 0.0): yphot=skyreg if (xphot-skyreg < 0.0): xphot=skyreg if (yphot+skyreg > segmasktrans.shape[0]-1): yphot=segmasktrans.shape[0]-1-skyreg if (xphot+skyreg > segmasktrans.shape[1]-1): xphot=segmasktrans.shape[1]-1-skyreg #print(int(yphot-skyreg),int(yphot+skyreg),int(xphot-skyreg),int(xphot+skyreg)) cutskymask=segmasktrans[int(yphot-skyreg):int(yphot+skyreg),int(xphot-skyreg):int(xphot+skyreg)] cutskydata=dataflx[int(yphot-skyreg):int(yphot+skyreg),int(xphot-skyreg):int(xphot+skyreg)] skymedian=np.nan_to_num(np.median(cutskydata[np.where(cutskymask < 1.0)])) #print skymedian #plt.imshow(cutskymask,origin='low') #plt.show() #if(idobj > 30): # exit() ######### #Now grab the Kron mag computed using detection image ######### #mask all other objects to avoid blending tmpdata=np.copy(dataflx) #apply local sky subtraction tmpdata=tmpdata-skymedian tmpvar=np.copy(datavar) tmpmask=np.copy(segmasktrans) pixels=np.where(tmpmask == idobj+1) tmpmask[pixels]=0 #plt.imshow(tmpmask,origin='low') #plt.show() #exit() #circular aperture if(use_circle): #flux in circular aperture flux_kron, err, flg = sep.sum_circle(tmpdata,xphot,yphot,rminphot,mask=tmpmask) #propagate variance fluxvar, err, flg = sep.sum_circle(tmpvar,xphot,yphot,rminphot,mask=tmpmask) #store Rused in arcsec rused=rminphot*psimg #update check aperture tmpcheckaper=np.zeros(dataflx.shape,dtype=bool) sep.mask_ellipse(tmpcheckaper,xphot,yphot,1.,1.,0.,r=rminphot) checkaperture=checkaperture+tmpcheckaper*(idobj+1) #kron apertures else: #kron flux flux_kron, err, flg = sep.sum_ellipse(tmpdata,xphot, yphot, aphot, bphot, theta, kn*kronrad, mask=tmpmask) #propagate variance fluxvar, err, flg = sep.sum_ellipse(tmpvar,xphot,yphot, aphot, bphot, theta, kn*kronrad, mask=tmpmask) #translate in radius rused=kn*kronrad*psimg*np.sqrt(aphot*bphot) #update check aperture tmpcheckaper=np.zeros(dataflx.shape,dtype=bool) sep.mask_ellipse(tmpcheckaper,xphot,yphot,aphot,bphot,theta,r=kn*kronrad) checkaperture=checkaperture+tmpcheckaper*(idobj+1) #compute error for aperture if(noise[0]): #use model appix=np.where(tmpcheckaper > 0) errflux_kron=noise[0]*noise[1]*len(appix[0])**noise[2] else: #propagate variance errflux_kron=np.sqrt(fluxvar) #go to mag if(flux_kron > 0): mag_aper=-2.5*np.log10(flux_kron)+img[0].header['ZPAB'] errmg_aper=2.5*np.log10(1.+errflux_kron/flux_kron) else: mag_aper=99.0 errmg_aper=99.0 #find out if non detections if(errflux_kron >= flux_kron): errmg_aper=9 mag_aper=-2.5*np.log10(2.*errflux_kron)+img[0].header['ZPAB'] ####### #grab the photometry in the segmentation map ##### #This may not work well for other instruments #if images are not well aligned pixels=np.where(segmasktrans == idobj+1) #add flux in pixels tmpdata=np.copy(dataflx) #apply sky sub tmpdata=tmpdata-skymedian flux_seg=np.sum(tmpdata[pixels]) #compute noise from model or adding variance if(noise[0]): #from model errfx_seg=noise[0]*noise[1]*len(pixels[0])**noise[2] else: #add variance in pixels to compute error errfx_seg=np.sqrt(np.sum(datavar[pixels])) #go to mag with calibrations if(flux_seg > 0): mag_seg=-2.5*np.log10(flux_seg)+img[0].header['ZPAB'] errmg_seg=2.5*np.log10(1.+errfx_seg/flux_seg) else: mag_seg=99.0 errmg_seg=99.0 #find out if non detections if(errfx_seg >= flux_seg): errmg_seg=9 mag_seg=-2.5*np.log10(2.*errfx_seg)+img[0].header['ZPAB'] #stash by id phot.add_row((idobj+1,mag_aper,errmg_aper,flux_kron,errflux_kron,rused,mag_seg,errmg_seg, flux_seg,errfx_seg,img[0].header['ZPAB'])) #dump the aperture check image hdumain = fits.PrimaryHDU(checkaperture,header=img[1].header) hdulist = fits.HDUList(hdumain) hdulist.writeto("{}_aper.fits".format(rname),clobber=True) #close cat.close() img.close() seg.close() det.close() return phot
def findsources(image, cube, varima=None, check=False, output='./', spectra=False, helio=0, nsig=2., minarea=10., deblend_cont=0.0001, regmask=None, invregmask=False, fitsmask=None, clean=True, outspec='Spectra', marz=False, rphot=False, detphot=False, sname='MUSE'): """ Take a detection image (collapse of a cube), or median of an RGB, or whatever you want (but aligned to the cube) and run sourceextractor Use SEP utilities http://sep.readthedocs.org/en/stable/ image -> fits file of image to process cube -> the cube used to extract spectra varima -> the noise image corresponding to the science image (std), optional check -> if true write a bunch of check mages output -> where to dump the output spectra -> if True, extract spectra in VACUUM wave!! helio -> pass additional heliocentric correction nsig -> number of skyrms used for source id minarea -> minimum area for extraction regmask -> ds9 region file (image) of regions to be masked before extraction [e.g. edges] invregmask -> if True invert the mask (region defines good area) fitsmask -> Fits file with good mask, overrides regmask clean -> clean souces outspec -> where to store output spectra marz -> write spectra in also marz format (spectra needs to be true). If set to numerical value, this is used as an r-band magnitude limit. detphot -> perform aperture phtometry on the detection image and add magnitues to the catalogue rphot -> perform r-band aperture photometry and add r-band magnitudes to the catalogue sname -> prefix for the source names. Default = MUSE """ import sep from astropy.io import fits from astropy import wcs from astropy import coordinates from astropy import units as u from astropy import table import numpy as np import os from mypython.ifu import muse_utils as utl from mypython.fits import pyregmask as msk from shutil import copyfile import glob #open image img = fits.open(image) header = img[0].header imgwcs = wcs.WCS(header) try: #this is ok for narrow band images data = img[1].data except: #white cubex images data = img[0].data data = data.byteswap(True).newbyteorder() #grab effective dimension nex, ney = data.shape #close fits img.close() if (varima): var = fits.open(varima) try: datavar = var[1].data except: datavar = var[0].data datavar = datavar.byteswap(True).newbyteorder() #grab effective dimension stdx, stdy = datavar.shape #close fits var.close() if (stdx != nex) or (stdy != ney): print( "The noise image does not have the same dimensions as the science image" ) return -1 #create bad pixel mask if (fitsmask): print("Using FITS image for badmask") hdumsk = fits.open(fitsmask) try: badmask = hdumsk[1].data except: badmask = hdumsk[0].data badmask = badmask.byteswap(True).newbyteorder() elif (regmask): print("Using region file for badmask") Mask = msk.PyMask(ney, nex, regmask, header=img[0].header) for ii in range(Mask.nreg): Mask.fillmask(ii) if (ii == 0): badmask = Mask.mask else: badmask += Mask.mask badmask = 1. * badmask else: badmask = np.zeros((nex, ney)) if (regmask) and (invregmask) and not (fitsmask): badmask = 1 - badmask if (check): print('Dumping badmask') hdumain = fits.PrimaryHDU(badmask, header=header) hdulist = fits.HDUList([hdumain]) hdulist.writeto(output + "/badmask.fits", overwrite=True) #check background level, but do not subtract it print('Checking background levels') bkg = sep.Background(data, mask=badmask) print('Residual background level ', bkg.globalback) print('Residual background rms ', bkg.globalrms) if (check): print('Dumping sky...') #dump sky properties back = bkg.back() rms = bkg.rms() hdumain = fits.PrimaryHDU(back, header=header) hdubk = fits.ImageHDU(back) hdurms = fits.ImageHDU(rms) hdulist = fits.HDUList([hdumain, hdubk, hdurms]) hdulist.writeto(output + "/skyprop.fits", overwrite=True) if (varima): #Use nsigma threshold and a pixel by pixel effective treshold based on variance map thresh = nsig objects, segmap = sep.extract(data, thresh, var=datavar, segmentation_map=True, minarea=minarea, clean=clean, mask=badmask, deblend_cont=deblend_cont, deblend_nthresh=32) else: #extracting sources at nsigma, use constant threshold thresh = nsig * bkg.globalrms objects, segmap = sep.extract(data, thresh, segmentation_map=True, minarea=minarea, clean=clean, mask=badmask, deblend_cont=deblend_cont, deblend_nthresh=32) print("Extracted {} objects... ".format(len(objects))) ids = np.arange(len(objects)) + 1 if (spectra): if not os.path.exists(outspec): os.makedirs(outspec) if ((check) | (spectra)): #create a detection mask a'la cubex srcmask = np.zeros((data.shape[0], data.shape[1])) print('Generating spectra...') #loop over detections for nbj in ids: obj = objects[nbj - 1] #init mask tmpmask = np.zeros((data.shape[0], data.shape[1]), dtype=np.bool) #fill this mask sep.mask_ellipse(tmpmask, obj['x'], obj['y'], obj['a'], obj['b'], obj['theta'], r=2) #add in global mask srcmask = srcmask + tmpmask * nbj #verify conflicts, resolve using segmentation map if np.nanmax(srcmask) > nbj: blended = (srcmask > nbj) srcmask[blended] = segmap[blended] #Now loop again and extract spectra if required if (spectra): #Verify that the source mask has the same number of objects as the object list if not len(np.unique(srcmask[srcmask > 0])) == len(objects): print( "Mismatch between number of objects and number of spectra to extract." ) for nbj in ids: savename = "{}/id{}.fits".format(outspec, nbj) tmpmask3d = np.zeros((1, data.shape[0], data.shape[1])) tmpmask3d[0, :, :] = srcmask[:, :] tmpmask3d[tmpmask3d != nbj] = 0 tmpmask3d[tmpmask3d > 0] = 1 tmpmask3d = np.array(tmpmask3d, dtype=np.bool) utl.cube2spec(cube, None, None, None, write=savename, shape='mask', helio=helio, mask=tmpmask3d, tovac=True) if (check): print('Dumping source mask...') hdumain = fits.PrimaryHDU(srcmask, header=header) hdubk = fits.ImageHDU(srcmask) hdulist = fits.HDUList([hdumain, hdubk]) hdulist.writeto(output + "/source.fits", overwrite=True) print('Dumping segmentation map') hdumain = fits.PrimaryHDU(segmap, header=header) hdubk = fits.ImageHDU(segmap) hdulist = fits.HDUList([hdumain, hdubk]) hdulist.writeto(output + "/segmap.fits", overwrite=True) #Generate source names using coordinates and name prefix ra, dec = imgwcs.wcs_pix2world(objects['x'], objects['y'], 0) coord = coordinates.FK5(ra * u.degree, dec * u.degree) rastr = coord.ra.to_string(u.hour, precision=2, sep='', pad=True) decstr = coord.dec.to_string(u.degree, precision=1, sep='', alwayssign=True, pad=True) name = [ sname + 'J{0}{1}'.format(rastr[k], decstr[k]) for k in range(len(rastr)) ] #Generate a column to be used to flag the sources to be used in the analysis #True for all sources at this point use_source = np.ones_like(name, dtype=bool) #write source catalogue print('Writing catalogue..') tab = table.Table(objects) tab.add_column(table.Column(dec), 0, name='DEC') tab.add_column(table.Column(ra), 0, name='RA') tab.add_column(table.Column(name), 0, name='name') tab.add_column(table.Column(ids), 0, name='ID') tab.add_column(table.Column(use_source), name='use_source') tab.write(output + '/catalogue.fits', overwrite=True) if (detphot): #Run source photometry on the extraction image whiteimg, whitevar, whitewcsimg = utl.cube2img(cube, write=output + '/Image_white.fits') phot_det = sourcephot(output + '/catalogue.fits', output + '/Image_white.fits', output + '/segmap.fits', image, zpab=28.35665) phot_det.add_column(table.Column(name), 1, name='name') tbhdu = fits.open(output + '/catalogue.fits') tbhdu.append(fits.BinTableHDU(phot_det)) tbhdu[-1].header['PHOTBAND'] = 'Detection' tbhdu.writeto(output + '/catalogue.fits', overwrite=True) #rband photometry if (rphot): rimg, rvar, rwcsimg = utl.cube2img(cube, filt=129, write=output + '/Image_R.fits') phot_r = sourcephot(output + '/catalogue.fits', output + '/Image_R.fits', output + '/segmap.fits', image) phot_r.add_column(table.Column(name), 1, name='name') tbhdu = fits.open(output + '/catalogue.fits') tbhdu.append(fits.BinTableHDU(phot_r)) tbhdu[-1].header['PHOTBAND'] = 'SDSS_r' tbhdu.writeto(output + '/catalogue.fits', overwrite=True) if ((marz) & (spectra)): #if marz is True but no magnitude limit set, create marz file for whole catalogue if marz > 10 and (rphot): #Requires testing hdu = fits.open(output + '/catalogue.fits') hdu[1].data['use_source'][hdu[2].data['MAGAP'] > marz] = False hdu.writeto(output + '/catalogue.fits', overwrite=True) marz_file(output + '/catalogue.fits', outspec, output, r_lim=marz) else: marz_file(output + '/catalogue.fits', outspec, output) print('All done') return objects
def findsources(image, cube, check=False, output='./', spectra=False, helio=0, nsig=2., minarea=10., regmask=None, clean=True, outspec='Spectra'): """ Take a detection image (collapse of a cube), or median of an RGB, or whatever you want (but aligned to the cube) and run sourceextractor Use SEP utilities http://sep.readthedocs.org/en/stable/ image -> fits file of image to process check -> if true write a bunch of check mages output -> where to dump the output cube -> the cube used to extract spectra spectra -> if True, extract spectra in VACUUM wave!! helio -> pass additional heliocentric correction nsig -> number of skyrms used for source id minarea -> minimum area for extraction regmask -> ds9 region file (image) of regions to be masked before extraction [e.g. edges] clean -> clean souces outspec -> where to store output spectra """ import sep from astropy.io import fits import numpy as np import os from mypython.ifu import muse_utils as utl from mypython.fits import pyregmask as msk #open image img = fits.open(image) header = img[0].header try: #this is ok for narrow band images data = img[1].data except: #white cubex images data = img[0].data data = data.byteswap(True).newbyteorder() #grab effective dimension nex, ney = data.shape #close fits img.close() #create bad pixel mask if (regmask): Mask = msk.PyMask(ney, nex, regmask) for ii in range(Mask.nreg): Mask.fillmask(ii) if (ii == 0): badmask = Mask.mask else: badmask += Mask.mask badmask = 1. * badmask else: badmask = np.zeros((nex, ney)) if (check): print('Dumping badmask') hdumain = fits.PrimaryHDU(badmask, header=header) hdulist = fits.HDUList([hdumain]) hdulist.writeto(output + "/badmask.fits", clobber=True) #check background level, but do not subtract it print 'Checking background levels' bkg = sep.Background(data, mask=badmask) print 'Residual background level ', bkg.globalback print 'Residual background rms ', bkg.globalrms if (check): print 'Dumping sky...' #dump sky properties back = bkg.back() rms = bkg.rms() hdumain = fits.PrimaryHDU(back, header=header) hdubk = fits.ImageHDU(back) hdurms = fits.ImageHDU(rms) hdulist = fits.HDUList([hdumain, hdubk, hdurms]) hdulist.writeto(output + "/skyprop.fits", clobber=True) #extracting sources at nsigma thresh = nsig * bkg.globalrms segmap = np.zeros((header["NAXIS1"], header["NAXIS2"])) objects, segmap = sep.extract(data, thresh, segmentation_map=True, minarea=minarea, clean=clean, mask=badmask) print "Extracted {} objects... ".format(len(objects)) if (spectra): if not os.path.exists(outspec): os.makedirs(outspec) if ((check) | (spectra)): #create a detection mask alla cubex srcmask = np.zeros((1, data.shape[0], data.shape[1])) nbj = 1 print('Generating spectra...') #loop over detections for obj in objects: #init mask tmpmask = np.zeros((data.shape[0], data.shape[1]), dtype=np.bool) tmpmask3d = np.zeros((1, data.shape[0], data.shape[1]), dtype=np.bool) #fill this mask sep.mask_ellipse(tmpmask, obj['x'], obj['y'], obj['a'], obj['b'], obj['theta'], r=2) tmpmask3d[0, :, :] = tmpmask[:, :] srcmask = srcmask + tmpmask3d * nbj if (spectra): savename = "{}/id{}.fits".format(outspec, nbj) utl.cube2spec(cube, obj['x'], obj['y'], None, write=savename, shape='mask', helio=helio, mask=tmpmask3d, tovac=True) #go to next nbj = nbj + 1 if (check): print 'Dumping source mask...' hdumain = fits.PrimaryHDU(srcmask, header=header) hdubk = fits.ImageHDU(srcmask) hdulist = fits.HDUList([hdumain, hdubk]) hdulist.writeto(output + "/source.fits", clobber=True) print 'Dumping segmentation map' hdumain = fits.PrimaryHDU(segmap, header=header) hdubk = fits.ImageHDU(segmap) hdulist = fits.HDUList([hdumain, hdubk]) hdulist.writeto(output + "/segmap.fits", clobber=True) #write source catalogue print 'Writing catalogue..' cols = fits.ColDefs(objects) tbhdu = fits.BinTableHDU.from_columns(cols) tbhdu.writeto(output + '/catalogue.fits', clobber=True) print 'All done' return objects
def make_binary_mask(img, w, segmap, radius=10.0, threshold=0.01, gaia=True, factor_b=1.2, sep_objcat=None, sep_mag=18.0, sep_zp=27.0, sep_blowup=15, show_fig=True): '''Make binary mask for a given segmentation map. We convolve the segmentation map using a Gaussian kernal to expand the size of mask. Parameters: ---------- img: 2-D numpy array, image data w: wcs of the input image segmap: 2-D numpy array, segmentation map given by `extract_obj()` radius: float, the width of Gaussian kernel threshold: float, it can change the size of mask. Lower threshold, larger mask. Returns: ------- binary_mask: 2-D numpy boolean array. ''' # Remove the central object seg_nocen = imtools.seg_remove_cen_obj(segmap) seg_conv = copy.deepcopy(seg_nocen) seg_conv[seg_nocen > 0] = 1 # Convolve the image with a Gaussian kernel with the width of 10 pixel # This is actually pretty slow, because the image is very large. seg_conv = convolve(seg_conv.astype('float'), Gaussian2DKernel(radius)) seg_mask = seg_conv >= threshold if sep_objcat is not None: t = Table(sep_objcat) cen_inx = segmap[int(img.shape[0] / 2.), int(img.shape[1] / 2.)] - 1 cen_obj = sep_objcat[cen_inx] t.remove_row(cen_inx) t.sort('flux') t.reverse() bright_objs = t[sep_zp - 2.5 * np.log10(t['flux']) < sep_mag] print('The number of bright objects: ', len(bright_objs)) for skyobj in bright_objs: sep.mask_ellipse(seg_mask, skyobj['x'], skyobj['y'], skyobj['a'], skyobj['b'], skyobj['theta'], r=sep_blowup) if gaia is False: if show_fig: from .display import display_single, IMG_CMAP, SEG_CMAP display_single(seg_mask.astype(int), cmap=SEG_CMAP) return seg_mask else: # Combine this mask with Gaia star mask gaia_mask = imtools.gaia_star_mask(img, w, gaia_bright=18, factor_f=10000, factor_b=factor_b)[1].astype('bool') # If gaia mask overlaps with the center of target galaxy: #if gaia_mask[int(img.shape[0] / 2.), int(img.shape[1] / 2.)] is True: from scipy.ndimage import measurements lw, num = measurements.label(gaia_mask) indx = lw[int(img.shape[0] / 2.), int(img.shape[1] / 2.)] gaia_mask[lw == indx] = False if show_fig: from .display import display_single, IMG_CMAP, SEG_CMAP display_single((seg_mask + gaia_mask).astype(int), cmap=SEG_CMAP) binary_mask = seg_mask + gaia_mask return binary_mask
def findsources(image,cube,check=False,output='./',spectra=False,helio=0,nsig=2., minarea=10.,regmask=None,clean=True,outspec='Spectra'): """ Take a detection image (collapse of a cube), or median of an RGB, or whatever you want (but aligned to the cube) and run sourceextractor Use SEP utilities http://sep.readthedocs.org/en/stable/ image -> fits file of image to process check -> if true write a bunch of check mages output -> where to dump the output cube -> the cube used to extract spectra spectra -> if True, extract spectra in VACUUM wave!! helio -> pass additional heliocentric correction nsig -> number of skyrms used for source id minarea -> minimum area for extraction regmask -> ds9 region file (image) of regions to be masked before extraction [e.g. edges] clean -> clean souces outspec -> where to store output spectra """ import sep from astropy.io import fits import numpy as np import os from mypython.ifu import muse_utils as utl from mypython.fits import pyregmask as msk #open image img=fits.open(image) header=img[0].header try: #this is ok for narrow band images data=img[1].data except: #white cubex images data=img[0].data data=data.byteswap(True).newbyteorder() #close fits img.close() #create bad pixel mask if(regmask): Mask=msk.PyMask(header["NAXIS1"],header["NAXIS2"],regmask) for ii in range(Mask.nreg): Mask.fillmask(ii) if(ii == 0): badmask=Mask.mask else: badmask+=Mask.mask badmask=1.*badmask else: badmask=np.zeros((header["NAXIS1"],header["NAXIS2"])) if(check): print('Dumping badmask') hdumain = fits.PrimaryHDU(badmask,header=header) hdulist = fits.HDUList([hdumain]) hdulist.writeto(output+"/badmask.fits",clobber=True) #check background level, but do not subtract it print 'Checking background levels' bkg = sep.Background(data,mask=badmask) print 'Residual background level ', bkg.globalback print 'Residual background rms ', bkg.globalrms if(check): print 'Dumping sky...' #dump sky properties back = bkg.back() rms = bkg.rms() hdumain = fits.PrimaryHDU(back,header=header) hdubk = fits.ImageHDU(back) hdurms = fits.ImageHDU(rms) hdulist = fits.HDUList([hdumain,hdubk,hdurms]) hdulist.writeto(output+"/skyprop.fits",clobber=True) #extracting sources at nsigma thresh = nsig * bkg.globalrms segmap = np.zeros((header["NAXIS1"],header["NAXIS2"])) objects,segmap=sep.extract(data,thresh,segmentation_map=True, minarea=minarea,clean=clean,mask=badmask) print "Extracted {} objects... ".format(len(objects)) if(spectra): if not os.path.exists(outspec): os.makedirs(outspec) if((check) | (spectra)): #create a detection mask alla cubex srcmask=np.zeros((1,data.shape[0],data.shape[1])) nbj=1 print('Generating spectra...') #loop over detections for obj in objects: #init mask tmpmask=np.zeros((data.shape[0],data.shape[1]),dtype=np.bool) tmpmask3d=np.zeros((1,data.shape[0],data.shape[1]),dtype=np.bool) #fill this mask sep.mask_ellipse(tmpmask,obj['x'],obj['y'],obj['a'],obj['b'],obj['theta'],r=2) tmpmask3d[0,:,:]=tmpmask[:,:] srcmask=srcmask+tmpmask3d*nbj if(spectra): savename="{}/id{}.fits".format(outspec,nbj) utl.cube2spec(cube,obj['x'],obj['y'],None,write=savename, shape='mask',helio=helio,mask=tmpmask3d,tovac=True) #go to next nbj=nbj+1 if(check): print 'Dumping source mask...' hdumain = fits.PrimaryHDU(srcmask,header=header) hdubk = fits.ImageHDU(srcmask) hdulist = fits.HDUList([hdumain,hdubk]) hdulist.writeto(output+"/source.fits",clobber=True) print 'Dumping segmentation map' hdumain = fits.PrimaryHDU(segmap,header=header) hdubk = fits.ImageHDU(segmap) hdulist = fits.HDUList([hdumain,hdubk]) hdulist.writeto(output+"/segmap.fits",clobber=True) #write source catalogue print 'Writing catalogue..' cols = fits.ColDefs(objects) tbhdu = fits.BinTableHDU.from_columns(cols) tbhdu.writeto(output+'/catalogue.fits',clobber=True) print 'All done' return objects
def sourcephot(catalogue,image,segmap,detection,instrument='MUSE',dxp=0.,dyp=0., noise=[False],zpab=False, kn=2.5, circap=1.0): """ Get a source catalogue from findsources and a fits image with ZP and compute magnitudes in that filter catalogue -> source cat from findsources image -> fits image with ZP in header segmap -> fits of segmentation map detection -> the detection image, used to compute Kron radius instrument -> if not MUSE, map positions from detection to image dxp,dyp -> shifts in pixel of image to register MUSE and image astrometry noise -> if set to a noise model, use equation noise[0]*noise[1]*npix**noise[2] to compute the error zpab -> if ZPAB (zeropoint AB) not stored in header, must be supplied kn -> factor to be used when scaling Kron apertures [sextractor default 2.5] circap -> radius in arcsec for aperture photmetry to be used when Kron aperture fails """ from astropy.io import fits import numpy as np import sep import matplotlib.pyplot as plt from astropy.table import Table from astropy import wcs #grab root name rname=((image.split('/')[-1]).split('.fits'))[0] print ('Working on {}'.format(rname)) #open the catalogue/fits cat=fits.open(catalogue) img=fits.open(image) seg=fits.open(segmap) det=fits.open(detection) #grab reference wcs from detection image wref=wcs.WCS(det[0].header) psref=wref.pixel_scale_matrix[1,1]*3600. print ('Reference pixel size {}'.format(psref)) #if not handling MUSE, special cases for format of data if('MUSE' not in instrument): #handle instrument cases if('LRIS' in instrument): #data imgdata=img[1].data #place holder for varaince as will use noise model below vardata=imgdata*0+1 vardata=vardata.byteswap(True).newbyteorder() #grab wcs image wimg=wcs.WCS(img[1].header) psimg=wimg.pixel_scale_matrix[1,1]*3600. #store the ZP if(zpab): img[0].header['ZPAB']=zpab else: print 'Instrument not supported!!' exit() else: #for muse, keep eveything the same imgdata=img[0].data vardata=img[1].data psimg=psref #grab flux and var dataflx=np.nan_to_num(imgdata.byteswap(True).newbyteorder()) datavar=np.nan_to_num(vardata.byteswap(True).newbyteorder()) #grab detection and seg mask detflx=np.nan_to_num(det[0].data.byteswap(True).newbyteorder()) #go back to 1d segmask=(np.nan_to_num(seg[0].data.byteswap(True).newbyteorder()))[0,:,:] #if needed, map the segmap to new image with transformation if('MUSE' not in instrument): #allocate space for transformed segmentation map segmasktrans=np.zeros(dataflx.shape) print "Remapping segmentation map to new image..." #loop over original segmap and map to trasformed one #Just use nearest pixel, and keep only 1 when multiple choices for xx in range(segmask.shape[0]): for yy in range(segmask.shape[1]): #go to world radec=wref.wcs_pix2world([[yy,xx]],0) #back to new instrument pixel newxy=wimg.wcs_world2pix(radec,0) #apply shift to register WCS newxy[0][1]=newxy[0][1]+dyp newxy[0][0]=newxy[0][0]+dxp segmasktrans[newxy[0][1],newxy[0][0]]=segmask[xx,yy] #grow buffer as needed by individual instruments #This accounts for resampling to finer pixel size if('LRIS' in instrument): segmasktrans[newxy[0][1]+1,newxy[0][0]+1]=segmask[xx,yy] segmasktrans[newxy[0][1]-1,newxy[0][0]-1]=segmask[xx,yy] segmasktrans[newxy[0][1]+1,newxy[0][0]-1]=segmask[xx,yy] segmasktrans[newxy[0][1]-1,newxy[0][0]+1]=segmask[xx,yy] segmasktrans[newxy[0][1]+1,newxy[0][0]]=segmask[xx,yy] segmasktrans[newxy[0][1]-1,newxy[0][0]]=segmask[xx,yy] segmasktrans[newxy[0][1],newxy[0][0]-1]=segmask[xx,yy] segmasktrans[newxy[0][1],newxy[0][0]+1]=segmask[xx,yy] #dump the transformed segmap for checking hdumain = fits.PrimaryHDU(segmasktrans,header=img[1].header) hdulist = fits.HDUList(hdumain) hdulist.writeto("{}_segremap.fits".format(rname),clobber=True) else: #no transformation needed segmasktrans=segmask #source to extract nsrc=len(cat[1].data) print('Extract photometry for {} sources'.format(nsrc)) phot = Table(names=('ID', 'MAGAP', 'MAGAP_ERR','FXAP', 'FXAP_ERR', 'RAD', 'MAGSEG', 'MAGSEG_ERR', 'FXSEG', 'FXSEG_ERR','ZP'), dtype=('i4','f4','f4','f4','f4','f4','f4','f4','f4','f4','f4')) #create check aperture mask checkaperture=np.zeros(dataflx.shape) print('Computing photometry for objects...') #loop over each source for idobj in range(nsrc): ######### #Find positions etc and transform as appropriate ######### #extract MUSE source paramaters x= cat[1].data['x'][idobj] y= cat[1].data['y'][idobj] a= cat[1].data['a'][idobj] b= cat[1].data['b'][idobj] theta= cat[1].data['theta'][idobj] #compute kron radius on MUSE detection image #Kron rad in units of a,b tmpdata=np.copy(detflx) tmpmask=np.copy(segmask) #mask all other sources to avoid overlaps but keep desired one pixels=np.where(tmpmask == idobj+1) tmpmask[pixels]=0 #compute kron radius [pixel of reference image] kronrad, flg = sep.kron_radius(tmpdata,x,y,a,b,theta,6.0,mask=tmpmask) #plt.imshow(np.log10(tmpdata+1),origin='low') #plt.show() #exit() #now check if size is sensible in units of MUSE data rmin = 2.0 #MUSE pix use_circle = kronrad * np.sqrt(a*b) < rmin #use circular aperture of 2" in muse pixel unit rcircap = circap/psref #now use info to compute photometry and apply #spatial transformation if needed if('MUSE' not in instrument): #map centre of aperture - +1 reference #go to world radec=wref.wcs_pix2world([[x,y]],1) #back to new instrument pixel newxy=wimg.wcs_world2pix(radec,1) #apply shift to register WCS xphot=newxy[0][0]+dxp yphot=newxy[0][1]+dyp #scale radii to new pixel size rminphot=rcircap*psref/psimg aphot=a*psref/psimg bphot=b*psref/psimg #Kron radius in units of a,b else: #for muse, transfer to same units xphot=x yphot=y rminphot=rcircap aphot=a bphot=b ##### #Compute local sky ##### skyreg=kn*kronrad*np.sqrt(aphot*bphot)+15 cutskymask=segmasktrans[yphot-skyreg:yphot+skyreg,xphot-skyreg:xphot+skyreg] cutskydata=dataflx[yphot-skyreg:yphot+skyreg,xphot-skyreg:xphot+skyreg] skymedian=np.nan_to_num(np.median(cutskydata[np.where(cutskymask < 1.0)])) #print skymedian #plt.imshow(cutskymask,origin='low') #plt.show() #if(idobj > 30): # exit() ######### #Now grab the Kron mag computed using detection image ######### #mask all other objects to avoid blending tmpdata=np.copy(dataflx) #apply local sky subtraction tmpdata=tmpdata-skymedian tmpvar=np.copy(datavar) tmpmask=np.copy(segmasktrans) pixels=np.where(tmpmask == idobj+1) tmpmask[pixels]=0 #plt.imshow(tmpmask,origin='low') #plt.show() #exit() #circular aperture if(use_circle): #flux in circular aperture flux_kron, err, flg = sep.sum_circle(tmpdata,xphot,yphot,rminphot,mask=tmpmask) #propagate variance fluxvar, err, flg = sep.sum_circle(tmpvar,xphot,yphot,rminphot,mask=tmpmask) #store Rused in arcsec rused=rminphot*psimg #update check aperture tmpcheckaper=np.zeros(dataflx.shape,dtype=bool) sep.mask_ellipse(tmpcheckaper,xphot,yphot,1.,1.,0.,r=rminphot) checkaperture=checkaperture+tmpcheckaper*(idobj+1) #kron apertures else: #kron flux flux_kron, err, flg = sep.sum_ellipse(tmpdata,xphot, yphot, aphot, bphot, theta, kn*kronrad, mask=tmpmask) #propagate variance fluxvar, err, flg = sep.sum_ellipse(tmpvar,xphot,yphot, aphot, bphot, theta, kn*kronrad, mask=tmpmask) #translate in radius rused=kn*kronrad*psimg*np.sqrt(aphot*bphot) #update check aperture tmpcheckaper=np.zeros(dataflx.shape,dtype=bool) sep.mask_ellipse(tmpcheckaper,xphot,yphot,aphot,bphot,theta,r=kn*kronrad) checkaperture=checkaperture+tmpcheckaper*(idobj+1) #compute error for aperture if(noise[0]): #use model appix=np.where(tmpcheckaper > 0) errflux_kron=noise[0]*noise[1]*len(appix[0])**noise[2] else: #propagate variance errflux_kron=np.sqrt(fluxvar) #go to mag if(flux_kron > 0): mag_aper=-2.5*np.log10(flux_kron)+img[0].header['ZPAB'] errmg_aper=2.5*np.log10(1.+errflux_kron/flux_kron) else: mag_aper=99.0 errmg_aper=99.0 #find out if non detections if(errflux_kron >= flux_kron): errmg_aper=9 mag_aper=-2.5*np.log10(2.*errflux_kron)+img[0].header['ZPAB'] ####### #grab the photometry in the segmentation map ##### #This may not work well for other instruments #if images are not well aligned pixels=np.where(segmasktrans == idobj+1) #add flux in pixels tmpdata=np.copy(dataflx) #apply sky sub tmpdata=tmpdata-skymedian flux_seg=np.sum(tmpdata[pixels]) #compute noise from model or adding variance if(noise[0]): #from model errfx_seg=noise[0]*noise[1]*len(pixels[0])**noise[2] else: #add variance in pixels to compute error errfx_seg=np.sqrt(np.sum(datavar[pixels])) #go to mag with calibrations if(flux_seg > 0): mag_seg=-2.5*np.log10(flux_seg)+img[0].header['ZPAB'] errmg_seg=2.5*np.log10(1.+errfx_seg/flux_seg) else: mag_seg=99.0 errmg_seg=99.0 #find out if non detections if(errfx_seg >= flux_seg): errmg_seg=9 mag_seg=-2.5*np.log10(2.*errfx_seg)+img[0].header['ZPAB'] #stash by id phot.add_row((idobj+1,mag_aper,errmg_aper,flux_kron,errflux_kron,rused,mag_seg,errmg_seg, flux_seg,errfx_seg,img[0].header['ZPAB'])) #dump the aperture check image hdumain = fits.PrimaryHDU(checkaperture,header=img[1].header) hdulist = fits.HDUList(hdumain) hdulist.writeto("{}_aper.fits".format(rname),clobber=True) #close cat.close() img.close() seg.close() det.close() return phot
def evaluate_sky(img, sigma=1.5, radius=10, pixel_scale=0.168, central_mask_radius=7.0, threshold=0.005, deblend_cont=0.001, deblend_nthresh=20, clean_param=1.0, show_fig=True, show_hist=True, f_factor=None): '''Evaluate the mean sky value. Parameters: ---------- img: 2-D numpy array, the input image show_fig: bool. If True, it will show you the masked sky image. show_hist: bool. If True, it will show you the histogram of the sky value. Returns: ------- median: median of background pixels, in original unit std: standard deviation, in original unit ''' import sep import copy from slug.imutils import extract_obj, make_binary_mask from astropy.convolution import convolve, Gaussian2DKernel b = 35 # Box size f = 5 # Filter width bkg = sep.Background(img, maskthresh=0, bw=b, bh=b, fw=f, fh=f) # first time objects, segmap = extract_obj(img - bkg.globalback, b=35, f=5, sigma=sigma, minarea=20, pixel_scale=pixel_scale, deblend_nthresh=deblend_nthresh, deblend_cont=deblend_cont, clean_param=clean_param, show_fig=False) seg_sky = copy.deepcopy(segmap) seg_sky[segmap > 0] = 1 seg_sky = seg_sky.astype(bool) # Blow up the mask for obj in objects: sep.mask_ellipse(seg_sky, obj['x'], obj['y'], obj['a'], obj['b'], obj['theta'], r=radius) bkg_mask_1 = seg_sky data = copy.deepcopy(img - bkg.globalback) data[bkg_mask_1 == 1] = 0 # Second time obj_lthre, seg_lthre = extract_obj(data, b=35, f=5, sigma=sigma + 1, minarea=5, pixel_scale=pixel_scale, deblend_nthresh=deblend_nthresh, deblend_cont=deblend_cont, clean_param=clean_param, show_fig=False) seg_sky = copy.deepcopy(seg_lthre) seg_sky[seg_lthre > 0] = 1 seg_sky = seg_sky.astype(bool) # Blow up the mask for obj in obj_lthre: sep.mask_ellipse(seg_sky, obj['x'], obj['y'], obj['a'], obj['b'], obj['theta'], r=radius/2) bkg_mask_2 = seg_sky bkg_mask = (bkg_mask_1 + bkg_mask_2).astype(bool) cen_obj = objects[segmap[int(bkg_mask.shape[0] / 2.), int(bkg_mask.shape[1] / 2.)] - 1] fraction_radius = sep.flux_radius(img, cen_obj['x'], cen_obj['y'], 10*cen_obj['a'], 0.5)[0] ba = np.divide(cen_obj['b'], cen_obj['a']) if fraction_radius < int(bkg_mask.shape[0] / 8.): sep.mask_ellipse(bkg_mask, cen_obj['x'], cen_obj['y'], fraction_radius, fraction_radius * ba, cen_obj['theta'], r=central_mask_radius) elif fraction_radius < int(bkg_mask.shape[0] / 4.): sep.mask_ellipse(bkg_mask, cen_obj['x'], cen_obj['y'], fraction_radius, fraction_radius * ba, cen_obj['theta'], r=1.2) # Estimate sky from histogram of binned image import copy from scipy import stats from astropy.stats import sigma_clip from astropy.nddata import block_reduce data = copy.deepcopy(img) data[bkg_mask] = np.nan if f_factor is None: f_factor = round(6 / pixel_scale) rebin = block_reduce(data, f_factor) sample = rebin.flatten() if show_fig: display_single(rebin) plt.savefig('./{}-bkg.png'.format(np.random.randint(1000)), dpi=100, bbox_inches='tight') temp = sigma_clip(sample) sample = temp.data[~temp.mask] kde = stats.gaussian_kde(sample) print(f_factor) mean = np.nanmean(sample) / f_factor**2 median = np.nanmedian(sample) / f_factor**2 std = np.nanstd(sample, ddof=1) / f_factor / np.sqrt(len(sample)) xlim = np.std(sample, ddof=1) * 7 x = np.linspace(-xlim + np.median(sample), xlim + np.median(sample), 100) offset = x[np.argmax(kde.evaluate(x))] / f_factor**2 print('mean', mean) print('median', median) print('std', std) bkg_global = sep.Background(img, mask=bkg_mask, maskthresh=0, bw=f_factor, bh=f_factor, fw=f_factor/2, fh=f_factor/2) print("#SEP sky: Mean Sky / RMS Sky = %10.5f / %10.5f" % (bkg_global.globalback, bkg_global.globalrms)) if show_hist: fig, ax = plt.subplots(figsize=(8,6)) ax.plot(x, kde.evaluate(x), linestyle='dashed', c='black', lw=2, label='KDE') ax.hist(sample, bins=x, normed=1); ax.legend(loc='best', frameon=False, fontsize=20) ax.set_xlabel('Pixel Value', fontsize=20) ax.set_ylabel('Normed Number', fontsize=20) ax.tick_params(labelsize=20) ylim = ax.get_ylim() ax.text(-0.1 * f_factor + np.median(sample), 0.9 * (ylim[1] - ylim[0]) + ylim[0], r'$\mathrm{offset}='+str(round(offset, 6))+'$', fontsize=20) ax.text(-0.1 * f_factor + np.median(sample), 0.8 * (ylim[1] - ylim[0]) + ylim[0], r'$\mathrm{median}='+str(round(median, 6))+'$', fontsize=20) ax.text(-0.1 * f_factor + np.median(sample), 0.7 * (ylim[1] - ylim[0]) + ylim[0], r'$\mathrm{std}='+str(round(std, 6))+'$', fontsize=20) plt.vlines(np.median(sample), 0, ylim[1], linestyle='--') return median, std, sample
# 2.Break the data into detections. Generate a catalogue of them. Get the propertie of each catalogue. thresh = eval(dic['thresh1']) * bkg.globalrms objs = sep.extract(DataSubtracted, thresh, minarea=eval(dic['MinDetectPixel1']), conv=KernelDict[dic['ConvKernel1']], deblend_nthresh=eval(dic['NDeblendThreshold1']), deblend_cont=eval(dic['DeblendCont1'])) print 'Objects list created successfully.' # ps:sep.extract() function will return an structured array of the properties for each object. To list each property, type objs['thresh']. # Delete the central galaxy from the array. The central galaxy is difined to be the object that is closest to the center of the image.(Useful to SDSS Montage image, need some update in the future for images of other origion.) x = objs['x'].copy() y = objs['y'].copy() # ??_?? k = (y[:]-height)**2 + (x[:]-width)**2 objs = delete(objs,argmin(k)) # 3.Generate mask. mask = zeros(shape(data), dtype = uint8)# mask_ellipse's argument must be in ubyte (uint8) format. sep.mask_ellipse(mask, objs['x'], objs['y'], objs['a'], objs['b'], objs['theta'], eval(dic['MaskScalingFactor1'])) # Masked pixels are 1, and unmasked pixels are 0. sep.mask_ellipse(mask,eval(dic['GalaxyCenterY']),eval(dic['GalaxyCenterX']),1,1,0,7) # Add central mask. print 'Mask image created successfully.' # 4.Compute an masked data array. DataMasked = data - 5000*mask #ObjIndex = nonzero(mask) # find the index of all the objects. #for i in range(len(ObjIndex[0])): # DataMasked[ObjIndex[0][i],ObjIndex[1][i]] = 0 print 'Masked image created successfully' sss = '_bw='+dic['BoxWidth1']+'_thr='+dic['thresh1']+'_sf='+dic['MaskScalingFactor1']+'.fits' # 5. Go through the pipeline for the central subregion if the SubregionHeight is not set to None. # Cut a subregion from the original data.
def findsources(image,cube,check=False,output='./',spectra=False,helio=0.0): """ Take a detection image (collapse of a cube), or median of an RGB, or whatever you want (but aligned to the cube) and run sourceextractor Use SEP utilities http://sep.readthedocs.org/en/v0.4.x/ image -> fits file of image to process check -> if true write a bunch of check mages output -> where to dump the output cube -> the cube used to extract spectra spectra -> if True, extract spectra helio -> pass additional heliocentric correction """ import sep from astropy.io import fits import numpy as np import os from mypython.ifu import muse_utils as utl #open image img=fits.open(image) header=img[0].header data=img[1].data data=data.byteswap(True).newbyteorder() #close fits img.close() #check bacground level, but do not subtract it print 'Checking background levels' bkg = sep.Background(data) print 'Residual background level ', bkg.globalback print 'Residual background rms ', bkg.globalrms if(check): print 'Dumping sky...' #dump sky properties back = bkg.back() rms = bkg.rms() hdumain = fits.PrimaryHDU(back,header=header) hdubk = fits.ImageHDU(back) hdurms = fits.ImageHDU(rms) hdulist = fits.HDUList([hdumain,hdubk,hdurms]) hdulist.writeto(output+"/skyprop.fits",clobber=True) #extracting sources thresh = 2. * bkg.globalrms objects = sep.extract(data,thresh, minarea=10, clean=0.0) print "Extracted {} objects... ".format(len(objects)) if(spectra): if not os.path.exists("Spectra"): os.makedirs("Spectra") if((check) | (spectra)): #create a detection mask srcmask=np.zeros((data.shape[0],data.shape[1])) nbj=1 #loop over detections for obj in objects: #init mask tmpmask=np.zeros((data.shape[0],data.shape[1]),dtype=np.bool) #fill this mask sep.mask_ellipse(tmpmask,obj['x'],obj['y'],obj['a'],obj['b'],obj['theta'],r=2) srcmask=srcmask+tmpmask*nbj if(spectra): savename="Spectra/id{}.fits".format(nbj) utl.cube2spec(cube,obj['x'],obj['y'],None,write=savename, shape='mask',helio=helio,mask=tmpmask) #go to next nbj=nbj+1 if(check): print 'Dumping source mask...' hdumain = fits.PrimaryHDU(srcmask,header=header) hdubk = fits.ImageHDU(srcmask) hdulist = fits.HDUList([hdumain,hdubk]) hdulist.writeto(output+"/source.fits",clobber=True) #write source catalogue print 'Writing catalogue..' cols = fits.ColDefs(objects) tbhdu = fits.BinTableHDU.from_columns(cols) tbhdu.writeto(output+'/catalogue.fits',clobber=True) print 'All done' return objects