def measure_rms(coord, data, img_wcs, annulus_radius=0.1 * u.arcsecond): pixel_scale = np.abs( img_wcs.pixel_scale_matrix.diagonal().prod())**0.5 * u.deg annulus_radius_pix = (annulus_radius.to(u.degree) / pixel_scale).decompose() annulus_width = 15 #pix center_coord_pix = coord.to_pixel(img_wcs) cutout = Cutout2D(data, center_coord_pix, annulus_radius * 2.5, img_wcs, mode='partial') cutout_center = regions.PixCoord(cutout.center_cutout[0], cutout.center_cutout[1]) innerann_reg = regions.CirclePixelRegion(cutout_center, annulus_radius_pix.value) outerann_reg = regions.CirclePixelRegion( cutout_center, annulus_radius_pix.value + annulus_width) innerann_mask = innerann_reg.to_mask() annulus_mask = mask(outerann_reg, cutout) - mask(innerann_reg, cutout) # Calculate the SNR and aperture flux sums pixels_in_annulus = cutout.data[annulus_mask.astype('bool')] bg_rms = median_abs_deviation(pixels_in_annulus) return bg_rms
def mlla_nondet(): MLLA_nondet = Table.read( '/home/jotter/nrao/summer_research_2018/tables/IR_nondet_may21_full.fits' ) b3_fl = fits.open( '/home/jotter/nrao/images/Orion_SourceI_B3_continuum_r0.5.clean0.05mJy.allbaselines.huge.deepmask.image.tt0.pbcor.fits' ) header = b3_fl[0].header img_wcs = WCS(header) data = b3_fl[0].data beam = radio_beam.Beam.from_fits_header(header) pixel_scale = np.abs( img_wcs.pixel_scale_matrix.diagonal().prod())**0.5 * u.deg #ppbeam = (beam.sr/(pixel_scale**2)).decompose().value MLLA_coord = SkyCoord(ra=MLLA_nondet['RAJ2000'], dec=MLLA_nondet['DEJ2000'], unit=u.degree) annulus_radius = 0.1 * u.arcsecond annulus_radius_pix = (annulus_radius.to(u.degree) / pixel_scale).decompose() annulus_width = 15 #pix ulim_fluxes = [] for ind in range(len(MLLA_coord)): center_coord = MLLA_coord[ind] center_coord_pix = center_coord.to_pixel(img_wcs) cutout = Cutout2D(data, center_coord_pix, annulus_radius * 2.5, img_wcs, mode='partial') cutout_center = regions.PixCoord(cutout.center_cutout[0], cutout.center_cutout[1]) innerann_reg = regions.CirclePixelRegion(cutout_center, annulus_radius_pix.value) outerann_reg = regions.CirclePixelRegion( cutout_center, annulus_radius_pix.value + annulus_width) innerann_mask = innerann_reg.to_mask() annulus_mask = mask(outerann_reg, cutout) - mask(innerann_reg, cutout) # Calculate the SNR and aperture flux sums pixels_in_annulus = cutout.data[annulus_mask.astype('bool')] bg_rms = median_abs_deviation(pixels_in_annulus) ulim_fluxes.append(3 * bg_rms) ulim_fluxes = np.array(ulim_fluxes) * 1000 * u.mJy #in mJy MLLA_nondet['B3_flux_ulim'] = ulim_fluxes MLLA_nondet.write( '/home/jotter/nrao/summer_research_2018/tables/IR_nondet_may21_full_ulim.fits', overwrite=True)
def roi_to_reg(roi): """ Function to convert a ROI to a region """ if isinstance(roi, CircularROI): return regions.CirclePixelRegion(center=(roi.xc, roi.yc, radius=roi.radius)) elif isinstance(roi, PointROI): return regions.PointRegion(center=(roi.x, roi.y)) elif isinstance(roi, PolygonalROI): return regions.PolygonPixelRegion(vertices=list(zip(roi.vx, roi.vy))) else: raise NotImplementedError("ROI {0} not recognized".format(roi))
def create_region(rtype, args, dx, dy): if rtype in ["Rectangle", "Box"]: xctr, yctr, xw, yw = args x = xctr+dx y = yctr+dy center = regions.PixCoord(x=x, y=y) reg = regions.RectanglePixelRegion(center=center, width=xw, height=yw) bounds = [x-0.5*xw, x+0.5*xw, y-0.5*yw, y+0.5*yw] elif rtype == "Circle": xctr, yctr, radius = args x = xctr+dx y = yctr+dy center = regions.PixCoord(x=x, y=y) reg = regions.CirclePixelRegion(center=center, radius=radius) bounds = [x-radius, x+radius, y-radius, y+radius] elif rtype == "Polygon": x = np.array(args[0])+dx y = np.array(args[1])+dy vertices = regions.PixCoord(x=x, y=y) reg = regions.PolygonPixelRegion(vertices=vertices) bounds = [x.min(), x.max(), y.min(), y.max()] else: raise NotImplementedError return reg, bounds
def measure_fluxes_gaussfit_allsrcs(data, img, data_name, name_start=27, name_end=-21, ref_name='B3'): cat = Table.read(data) RA_name = 'gauss_x_' + ref_name ind = np.where(np.isnan(cat[RA_name]) == False) cat = cat[ind] col_names = fnmatch.filter(cat.colnames, 'ap_flux_r*') col_names = [cn[8:] for cn in col_names] fl = fits.open(img) header = fl[0].header img_data = fl[0].data.squeeze() mywcs = WCS(header).celestial beam = radio_beam.Beam.from_fits_header(header) pixel_scale = np.abs( mywcs.pixel_scale_matrix.diagonal().prod())**0.5 * u.deg ppbeam = (beam.sr / (pixel_scale**2)).decompose().value save_dir = '/lustre/aoc/students/jotter/gauss_diags/diff_imgs/' + ref_name + '/' + data_name + '/' if not os.path.exists(save_dir): os.makedirs(save_dir) reg_file = '/users/jotter/summer_research_2018/final_regs/misc_regs/' + data_name + '_reg_file_' + ref_name + '_apflux.reg' with open(reg_file, 'w') as fh: fh.write("fk5\n") for ind in range(len(cat)): fh.write( 'ellipse({x_cen}, {y_cen}, {maj}, {minr}, {ang}) #text={{{ID}}}\n' .format(x_cen=cat['gauss_x_' + ref_name][ind], y_cen=cat['gauss_y_' + ref_name][ind], maj=(cat['FWHM_major_' + ref_name][ind] * u.arcsec.to(u.degree)), minr=(cat['FWHM_minor_' + ref_name][ind] * u.arcsec.to(u.degree)), ang=cat['position_angle_' + ref_name][ind], ID=str(cat['D_ID'][ind]))) rad = Angle(1, 'arcsecond') #radius for region list regs = [] for ind in range(len(cat)): reg = regions.CircleSkyRegion(center=SkyCoord( cat['gauss_x_' + ref_name][ind] * u.degree, cat['gauss_y_' + ref_name][ind] * u.degree), radius=rad, meta={'text': str(cat['D_ID'][ind])}) reg_pix = reg.to_pixel(mywcs) if reg_pix.center.x > 0 and reg_pix.center.x < len(img_data[0]): if reg_pix.center.y > 0 and reg_pix.center.y < len(img_data): if np.isnan(img_data[int(reg_pix.center.x), int(reg_pix.center.y)]) == False: regs.append(reg) cat_r = Angle(0.5, 'arcsecond') #radius in gaussian fitting gauss_cat = gaussfit_catalog( img, regs, cat_r, savepath=save_dir) #output is nested dictionary structure gauss_fit_tab = Table( names=('D_ID', 'FWHM_major_' + data_name, 'major_err_' + data_name, 'FWHM_minor_' + data_name, 'minor_err_' + data_name, 'pa_' + data_name, 'pa_err_' + data_name, 'g_amplitude_' + data_name, 'amp_err_' + data_name), dtype=('i4', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8')) for key in gauss_cat: gauss_fit_tab.add_row( (key, gauss_cat[key]['fwhm_major'], gauss_cat[key]['e_fwhm_major'], gauss_cat[key]['fwhm_minor'], gauss_cat[key]['e_fwhm_minor'], gauss_cat[key]['pa'], gauss_cat[key]['e_pa'], gauss_cat[key]['amplitude'], gauss_cat[key]['e_amplitude'])) #fill table ap_flux_arr = [] circ_flux_arr = [] ap_flux_err_arr = [] circ_flux_err_arr = [] bg_median_arr = [] bg_ap_arr = [] bg_circ_arr = [] RA = [] DEC = [] RA_err = [] DEC_err = [] for row in range(len(cat)): gauss_ind = np.where(gauss_fit_tab['D_ID'] == cat['D_ID'][row])[0] if len(gauss_ind) > 0: pix_major_fwhm = ( (cat['FWHM_major_' + ref_name][row] * u.arcsec).to(u.degree) / pixel_scale).decompose() pix_minor_fwhm = ( (cat['FWHM_minor_' + ref_name][row] * u.arcsec).to(u.degree) / pixel_scale).decompose() center_coord = SkyCoord(cat['gauss_x_' + ref_name][row], cat['gauss_y_' + ref_name][row], frame='icrs', unit=(u.deg, u.deg)) center_coord_pix = center_coord.to_pixel(mywcs) center_coord_pix_reg = regions.PixCoord(center_coord_pix[0], center_coord_pix[1]) position_angle = cat['position_angle_' + ref_name][row] * u.deg ellipse_reg = regions.EllipsePixelRegion(center_coord_pix_reg, pix_major_fwhm * 2., pix_minor_fwhm * 2., angle=position_angle) size = pix_major_fwhm * 2.1 ap_mask = ellipse_reg.to_mask() cutout_mask = ap_mask.cutout(img_data) aperture_flux = np.sum(cutout_mask[ap_mask.data == 1]) / ppbeam npix = len(cutout_mask[ap_mask.data == 1]) ap_flux_arr.append(aperture_flux) #now creating annulus around source to measure background and ap flux error annulus_width = 15 annulus_radius = 0.1 * u.arcsecond annulus_radius_pix = (annulus_radius.to(u.degree) / pixel_scale).decompose() # Cutout section of the image we care about, to speed up computation time size = 2.5 * annulus_radius cutout = Cutout2D(img_data, center_coord_pix, size, mywcs, mode='partial') #cutout of outer circle cutout_center = regions.PixCoord(cutout.center_cutout[0], cutout.center_cutout[1]) # Define the aperture regions needed for SNR innerann_reg = regions.CirclePixelRegion(cutout_center, annulus_radius_pix) outerann_reg = regions.CirclePixelRegion( cutout_center, annulus_radius_pix + annulus_width) # Make masks from aperture regions annulus_mask = mask(outerann_reg, cutout) - mask( innerann_reg, cutout) # Calculate the SNR and aperture flux sums pixels_in_annulus = cutout.data[annulus_mask.astype( 'bool')] #pixels within annulus bg_rms = rms(pixels_in_annulus) ap_bg_rms = bg_rms / np.sqrt( npix / ppbeam) #rms/sqrt(npix/ppbeam) - rms error per beam bg_median = np.median(pixels_in_annulus) pix_bg = bg_median * npix / ppbeam ap_flux_err_arr.append(ap_bg_rms) bg_median_arr.append(bg_median) bg_ap_arr.append(pix_bg) #now measure circle flux: radius = 0.1 * u.arcsecond radius_pix = annulus_radius_pix circle_reg = regions.CirclePixelRegion(center_coord_pix_reg, radius_pix) circ_ap_mask = circle_reg.to_mask() circ_cutout_mask = circ_ap_mask.cutout(img_data) cutout_mask = ap_mask.cutout(img_data) circ_aperture_flux = np.sum( circ_cutout_mask[circ_ap_mask.data == 1]) / ppbeam circ_npix = len(circ_cutout_mask[circ_ap_mask.data == 1]) circ_bg_rms = bg_rms / np.sqrt(circ_npix / ppbeam) circ_flux_arr.append(circ_aperture_flux) circ_flux_err_arr.append(circ_bg_rms) bg_circ_arr.append(bg_median * circ_npix / ppbeam) RA.append(cat['gauss_x_' + ref_name][row]) DEC.append(cat['gauss_y_' + ref_name][row]) RA_err.append(cat['x_err_' + ref_name][row]) DEC_err.append(cat['y_err_' + ref_name][row]) cols = [ 'ap_flux_', 'ap_flux_err_', 'bg_median_', 'bg_ap_', 'circ_flux_', 'circ_flux_err_', 'bg_circ_' ] arrs = [ ap_flux_arr, ap_flux_err_arr, bg_median_arr, bg_ap_arr, circ_flux_arr, circ_flux_err_arr, bg_circ_arr ] cols2 = ['RA_', 'DEC_', 'RA_err_', 'DEC_err_'] #seperate bc different naming arrs2 = [RA, DEC, RA_err, DEC_err] for j in range(len(cols)): gauss_fit_tab[cols[j] + data_name] = arrs[j] for c in range(len(cols2)): gauss_fit_tab[cols2[c] + ref_name] = arrs2[c] gauss_fit_tab.write('/lustre/aoc/students/jotter/dendro_catalogs/' + data_name + '_500klplus_allsrcs_catalog_' + ref_name + '_ref.txt', format='ascii', overwrite=True)
def measure_fluxes_gaussfit(data, directory, data_name, name_start=27, name_end=-21, ref_name='B6'): #This function takes a directory full of images and adds them all to a catalog with flux measurements and fits gaussians. 'data' is the catalog to get positions from, 'ref_name' is the name of the reference data (for positions), 'directory' is the directory with all the new images. cat = Table.read(data) RA_names = ['gauss_x_B3', 'gauss_x_B6', 'gauss_x_B7_hr'] inds = [] for fn in RA_names: ind = np.where(np.isnan(cat[fn]) == False) inds.append(ind) detected = reduce( np.intersect1d, inds ) #sources detected in B3, B6, B7 - only make measurements on these sources cat = cat[detected] imgs = glob.glob(directory + '*r-2.clean0.1mJy.500klplus.deepmask*') img_names = [img[len(directory) + name_start:name_end] for img in imgs] col_names = fnmatch.filter(cat.colnames, 'ap_flux_r*') col_names = [cn[8:] for cn in col_names] for j, img in enumerate(imgs): name = img_names[j] if len(name) > 54: name = name[0:53] if name not in col_names: fl = fits.open(img) header = fl[0].header try: img_data = fl[0].data.squeeze() except TypeError: print("error! img: " + img) continue mywcs = WCS(header).celestial beam = radio_beam.Beam.from_fits_header(header) pixel_scale = np.abs( mywcs.pixel_scale_matrix.diagonal().prod())**0.5 * u.deg ppbeam = (beam.sr / (pixel_scale**2)).decompose().value save_dir = '/lustre/aoc/students/jotter/gauss_diags/diff_imgs/' + ref_name + '/' + name + '/' if not os.path.exists(save_dir): os.makedirs(save_dir) reg_file = '/users/jotter/summer_research_2018/final_regs/' + name + '_reg_file_B6_apflux.reg' with open(reg_file, 'w') as fh: fh.write("fk5\n") for ind in range(len(cat)): fh.write( 'ellipse({x_cen}, {y_cen}, {maj}, {minr}, {ang}) #text={{{ID}}}\n' .format(x_cen=cat['gauss_x_' + ref_name][ind], y_cen=cat['gauss_y_' + ref_name][ind], maj=(cat['FWHM_major_' + ref_name][ind] * u.arcsec.to(u.degree)), minr=(cat['FWHM_minor_' + ref_name][ind] * u.arcsec.to(u.degree)), ang=cat['position_angle_' + ref_name][ind], ID=str(cat['D_ID'][ind]))) rad = Angle(1, 'arcsecond') #radius for region list regs = [] for ind in range(len(cat)): reg = regions.CircleSkyRegion(center=SkyCoord( cat['gauss_x_' + ref_name][ind] * u.degree, cat['gauss_y_' + ref_name][ind] * u.degree), radius=rad, meta={ 'text': str(cat['D_ID'][ind]) }) reg_pix = reg.to_pixel(mywcs) if reg_pix.center.x > 0 and reg_pix.center.x < len( img_data[0]): if reg_pix.center.y > 0 and reg_pix.center.y < len( img_data): regs.append(reg) cat_r = Angle(0.5, 'arcsecond') #radius in gaussian fitting gauss_cat = gaussfit_catalog( img, regs, cat_r, savepath=save_dir) #output is nested dictionary structure gauss_fit_tab = Table( names=('D_ID', 'FWHM_major_' + name, 'major_err_' + name, 'FWHM_minor_' + name, 'minor_err_' + name, 'pa_' + name, 'pa_err_' + name, 'g_amplitude_' + name, 'amp_err_' + name), dtype=('i4', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8')) for key in gauss_cat: gauss_fit_tab.add_row( (key, gauss_cat[key]['fwhm_major'], gauss_cat[key]['e_fwhm_major'], gauss_cat[key]['fwhm_minor'], gauss_cat[key]['e_fwhm_minor'], gauss_cat[key]['pa'], gauss_cat[key]['e_pa'], gauss_cat[key]['amplitude'], gauss_cat[key]['e_amplitude'])) #fill table cat = join(cat, gauss_fit_tab, keys='D_ID', join_type='left') ap_flux_arr = [] circ_flux_arr = [] ap_flux_err_arr = [] circ_flux_err_arr = [] bg_median_arr = [] bg_ap_arr = [] bg_circ_arr = [] for row in range(len(cat)): pix_major_fwhm = ((cat['FWHM_major_' + ref_name][row] * u.arcsec).to(u.degree) / pixel_scale).decompose() pix_minor_fwhm = ((cat['FWHM_minor_' + ref_name][row] * u.arcsec).to(u.degree) / pixel_scale).decompose() center_coord = SkyCoord(cat['gauss_x_' + ref_name][row], cat['gauss_y_' + ref_name][row], frame='icrs', unit=(u.deg, u.deg)) center_coord_pix = center_coord.to_pixel(mywcs) center_coord_pix_reg = regions.PixCoord( center_coord_pix[0], center_coord_pix[1]) position_angle = cat['position_angle_' + ref_name][row] * u.deg print(center_coord_pix_reg, pix_major_fwhm, pix_minor_fwhm) ellipse_reg = regions.EllipsePixelRegion(center_coord_pix_reg, pix_major_fwhm * 2., pix_minor_fwhm * 2., angle=position_angle) size = pix_major_fwhm * 2.1 ap_mask = ellipse_reg.to_mask() cutout_mask = ap_mask.cutout(img_data) aperture_flux = np.sum(cutout_mask[ap_mask.data == 1]) / ppbeam npix = len(cutout_mask[ap_mask.data == 1]) ap_flux_arr.append(aperture_flux) #now creating annulus around source to measure background and ap flux error annulus_width = 15 annulus_radius = 0.1 * u.arcsecond annulus_radius_pix = (annulus_radius.to(u.degree) / pixel_scale).decompose() # Cutout section of the image we care about, to speed up computation time size = 2.5 * annulus_radius cutout = Cutout2D(img_data, center_coord_pix, size, mywcs, mode='partial') #cutout of outer circle cutout_center = regions.PixCoord(cutout.center_cutout[0], cutout.center_cutout[1]) # Define the aperture regions needed for SNR innerann_reg = regions.CirclePixelRegion( cutout_center, annulus_radius_pix) outerann_reg = regions.CirclePixelRegion( cutout_center, annulus_radius_pix + annulus_width) # Make masks from aperture regions annulus_mask = mask(outerann_reg, cutout) - mask( innerann_reg, cutout) # Calculate the SNR and aperture flux sums pixels_in_annulus = cutout.data[annulus_mask.astype( 'bool')] #pixels within annulus bg_rms = rms(pixels_in_annulus) ap_bg_rms = bg_rms / np.sqrt( npix / ppbeam) #rms/sqrt(npix/ppbeam) - rms error per beam bg_median = np.median(pixels_in_annulus) pix_bg = bg_median * npix / ppbeam ap_flux_err_arr.append(ap_bg_rms) bg_median_arr.append(bg_median) bg_ap_arr.append(pix_bg) #now measure circle flux: radius = 0.1 * u.arcsecond radius_pix = annulus_radius_pix circle_reg = regions.CirclePixelRegion(center_coord_pix_reg, radius_pix) circ_ap_mask = circle_reg.to_mask() circ_cutout_mask = circ_ap_mask.cutout(img_data) cutout_mask = ap_mask.cutout(img_data) circ_aperture_flux = np.sum( circ_cutout_mask[circ_ap_mask.data == 1]) / ppbeam circ_npix = len(circ_cutout_mask[circ_ap_mask.data == 1]) circ_bg_rms = bg_rms / np.sqrt(circ_npix / ppbeam) circ_flux_arr.append(circ_aperture_flux) circ_flux_err_arr.append(circ_bg_rms) bg_circ_arr.append(bg_median * circ_npix / ppbeam) cols = [ 'ap_flux_', 'ap_flux_err_', 'bg_median_', 'bg_ap_', 'circ_flux_', 'circ_flux_err_', 'bg_circ_' ] arrs = [ ap_flux_arr, ap_flux_err_arr, bg_median_arr, bg_ap_arr, circ_flux_arr, circ_flux_err_arr, bg_circ_arr ] for j in range(len(cols)): cat[cols[j] + name] = arrs[j] cat.write('/lustre/aoc/students/jotter/dendro_catalogs/' + data_name + '_500klplus_allsrcs_catalog_' + ref_name + '_ref.txt', format='ascii', overwrite=True)
def measure_ap_fluxes(data, directory, data_name, name_start=27, name_end=-21, ref_name='B6'): #This function takes a directory full of images and adds them all to a catalog with flux measurements. 'data' is the catalog to get positions and sizes from, 'ref_name' is the name of the reference data (for positions), 'directory' is the directory with all the new images. #data = '/lustre/aoc/students/jotter/dendro_catalogs/IR_matched_catalog_B7.fits' #directory = '/lustre/aoc/students/jotter/directory/OrionB3/' cat = Table.read(data) RA_names = ['gauss_x_B3', 'gauss_x_B6', 'gauss_x_B7_hr'] inds = [] for fn in RA_names: ind = np.where(np.isnan(cat[fn]) == False) inds.append(ind) detected = reduce( np.intersect1d, inds ) #sources detected in B3, B6, B7 - only make measurements on these sources cat = cat[detected] imgs = glob.glob(directory + '*') img_names = [img[len(directory) + name_start:name_end] for img in imgs] col_names = fnmatch.filter(cat.colnames, 'ap_flux_r*') col_names = [cn[8:] for cn in col_names] for j, img in enumerate(imgs): name = img_names[j] if len(name) > 54: name = name[0:53] if name not in col_names: fl = fits.open(img) header = fl[0].header try: img_data = fl[0].data.squeeze() except TypeError: print("ERROR! img: " + img) continue mywcs = WCS(header).celestial beam = radio_beam.Beam.from_fits_header(header) pixel_scale = np.abs( mywcs.pixel_scale_matrix.diagonal().prod())**0.5 * u.deg ppbeam = (beam.sr / (pixel_scale**2)).decompose().value ap_flux_arr = [] circ_flux_arr = [] ap_flux_err_arr = [] circ_flux_err_arr = [] bg_median_arr = [] bg_ap_arr = [] bg_circ_arr = [] for row in range(len(cat)): pix_major_fwhm = ((cat['FWHM_major_' + ref_name][row] * u.arcsec).to(u.degree) / pixel_scale).decompose() pix_minor_fwhm = ((cat['FWHM_minor_' + ref_name][row] * u.arcsec).to(u.degree) / pixel_scale).decompose() center_coord = SkyCoord(cat['gauss_x_' + ref_name][row], cat['gauss_y_' + ref_name][row], frame='icrs', unit=(u.deg, u.deg)) center_coord_pix = center_coord.to_pixel(mywcs) center_coord_pix_reg = regions.PixCoord( center_coord_pix[0], center_coord_pix[1]) position_angle = cat['position_angle_' + ref_name][row] * u.deg ellipse_reg = regions.EllipsePixelRegion(center_coord_pix_reg, pix_major_fwhm * 2., pix_minor_fwhm * 2., angle=position_angle) size = pix_major_fwhm * 2.1 ap_mask = ellipse_reg.to_mask() cutout_mask = ap_mask.cutout(img_data) aperture_flux = np.sum(cutout_mask[ap_mask.data == 1]) / ppbeam npix = len(cutout_mask[ap_mask.data == 1]) ap_flux_arr.append(aperture_flux) #now creating annulus around source to measure background and ap flux error annulus_width = 15 annulus_radius = 0.1 * u.arcsecond annulus_radius_pix = (annulus_radius.to(u.degree) / pixel_scale).decompose() # Cutout section of the image we care about, to speed up computation time size = 2.5 * annulus_radius cutout = Cutout2D(img_data, center_coord_pix, size, mywcs, mode='partial') #cutout of outer circle cutout_center = regions.PixCoord(cutout.center_cutout[0], cutout.center_cutout[1]) # Define the aperture regions needed for SNR innerann_reg = regions.CirclePixelRegion( cutout_center, annulus_radius_pix) outerann_reg = regions.CirclePixelRegion( cutout_center, annulus_radius_pix + annulus_width) # Make masks from aperture regions annulus_mask = mask(outerann_reg, cutout) - mask( innerann_reg, cutout) # Calculate the SNR and aperture flux sums pixels_in_annulus = cutout.data[annulus_mask.astype( 'bool')] #pixels within annulus bg_rms = rms(pixels_in_annulus) ap_bg_rms = bg_rms / np.sqrt( npix / ppbeam) #rms/sqrt(npix/ppbeam) - rms error per beam bg_median = np.median(pixels_in_annulus) pix_bg = bg_median * npix / ppbeam ap_flux_err_arr.append(ap_bg_rms) bg_median_arr.append(bg_median) bg_ap_arr.append(pix_bg) #now measure circle flux: radius = 0.1 * u.arcsecond radius_pix = annulus_radius_pix circle_reg = regions.CirclePixelRegion(center_coord_pix_reg, radius_pix) circ_ap_mask = circle_reg.to_mask() circ_cutout_mask = circ_ap_mask.cutout(img_data) cutout_mask = ap_mask.cutout(img_data) circ_aperture_flux = np.sum( circ_cutout_mask[circ_ap_mask.data == 1]) / ppbeam circ_npix = len(circ_cutout_mask[circ_ap_mask.data == 1]) circ_bg_rms = bg_rms / np.sqrt(circ_npix / ppbeam) circ_flux_arr.append(circ_aperture_flux) circ_flux_err_arr.append(circ_bg_rms) bg_circ_arr.append(bg_median * circ_npix / ppbeam) cols = [ 'ap_flux_', 'ap_flux_err_', 'bg_median_', 'bg_ap_', 'circ_flux_', 'circ_flux_err_', 'bg_circ_' ] arrs = [ ap_flux_arr, ap_flux_err_arr, bg_median_arr, bg_ap_arr, circ_flux_arr, circ_flux_err_arr, bg_circ_arr ] for j in range(len(cols)): cat[cols[j] + name] = arrs[j] cat.write('/lustre/aoc/students/jotter/dendro_catalogs/' + data_name + '_diff_imgs_catalog_' + ref_name + '_ref.txt', format='ascii', overwrite=True)
def b6b7_catalog(B6_img, B6_name, B7_img, B7_name, cat_name, nonconv_B6_img=None, nonconv_B7_img=None): #creates catalog from one image in each band #B3_names, B6_names, B7_names only used for gaussian diag directory names ref_data_name = '/home/jotter/nrao/summer_research_2018/tables/ref_catalog_may21.fits' ref_data = Table.read(ref_data_name) ref_arrs = [ref_data['B6_detect'], ref_data['B7_detect']] band_imgs = [B6_img, B7_img] band_names = ['B6', 'B7'] band_img_names = [B6_name, B7_name] band_tables = [] for b in range(len(band_imgs)): #first loop through different bands name = band_names[b] img_name = band_img_names[b] img = band_imgs[b] fl = fits.open(img) header = fl[0].header img_data = fl[0].data.squeeze() img_wcs = WCS(header).celestial beam = radio_beam.Beam.from_fits_header(header) pixel_scale = np.abs(img_wcs.pixel_scale_matrix.diagonal().prod())**0.5 * u.deg ppbeam = (beam.sr/(pixel_scale**2)).decompose().value if name == 'B6' and nonconv_B6_img is not None: fl = fits.open(nonconv_B6_img) header = fl[0].header nonconv_beam = radio_beam.Beam.from_fits_header(header) ppbeam = (nonconv_beam.sr/(pixel_scale**2)).decompose().value if name == 'B7' and nonconv_B7_img is not None: fl = fits.open(nonconv_B7_img) header = fl[0].header nonconv_beam = radio_beam.Beam.from_fits_header(header) ppbeam = (nonconv_beam.sr/(pixel_scale**2)).decompose().value #now get ready to fit gaussians #start by setting up save directory for images gauss_save_dir = '/home/jotter/nrao/gauss_diags_may21/'+img_name+'/' if not os.path.exists(gauss_save_dir): os.makedirs(gauss_save_dir) #now make region list rad = Angle(1, 'arcsecond') #radius used in region list regs = [] src_inds = np.where(ref_arrs[b] == True)[0] print(len(src_inds)) for ind in src_inds: reg = regions.CircleSkyRegion(center=SkyCoord(ref_data['RA_B3'][ind]*u.degree, ref_data['DEC_B3'][ind]*u.degree), radius=rad, meta={'text':str(ref_data['B3_Seq'][ind])}) reg_pix = reg.to_pixel(img_wcs) if reg_pix.center.x > 0 and reg_pix.center.x < len(img_data[0]): if reg_pix.center.y > 0 and reg_pix.center.y < len(img_data): if np.isnan(img_data[int(reg_pix.center.x), int(reg_pix.center.y)]) == False: regs.append(reg) cat_r = Angle(0.5, 'arcsecond')/2 #radius for gaussian fitting print('ok') gauss_cat = gaussfit_catalog(img, regs, cat_r, savepath=gauss_save_dir, max_offset_in_beams = 1, max_radius_in_beams = 5) #table does not have all columns yet, add others later img_table = Table(names=('Seq_B3', 'fwhm_maj_'+name, 'fwhm_maj_err_'+name, 'fwhm_min_'+name, 'fwhm_min_err_'+name, 'pa_'+name, 'pa_err_'+name, 'gauss_amp_'+name, 'gauss_amp_err_'+name,'RA_'+name,'RA_err_'+name, 'DEC_'+name, 'DEC_err_'+name, ), dtype=('i4', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8')) for key in gauss_cat: img_table.add_row((key, gauss_cat[key]['fwhm_major'], gauss_cat[key]['e_fwhm_major'], gauss_cat[key]['fwhm_minor'], gauss_cat[key]['e_fwhm_minor'], gauss_cat[key]['pa'], gauss_cat[key]['e_pa'], gauss_cat[key]['amplitude'], gauss_cat[key]['e_amplitude'], gauss_cat[key]['center_x'], gauss_cat[key]['e_center_x'], gauss_cat[key]['center_y'], gauss_cat[key]['e_center_y'])) #now measure deconvovled sizes and aperture flux measurements for each source ap_flux_arr = [] ap_flux_err_arr = [] fwhm_maj_deconv_arr = [] fwhm_maj_deconv_err_arr = [] fwhm_min_deconv_arr = [] fwhm_min_deconv_err_arr = [] pa_deconv_arr = [] pa_deconv_err_arr = [] snr_arr = [] rms_arr = [] for row in range(len(img_table)): #now loop through sources in reference data and make measurements ref_ind = np.where(ref_data['B3_Seq'] == img_table['Seq_B3'][row])[0] if len(ref_ind > 0): #now measuring deconvolved sizes measured_source_size = radio_beam.Beam(major=img_table['fwhm_maj_'+name][row]*u.arcsec, minor=img_table['fwhm_min_'+name][row]*u.arcsec, pa=(img_table['pa_'+name][row]-90)*u.degree) try: deconv_size = measured_source_size.deconvolve(beam) fwhm_maj_deconv_arr.append(deconv_size.major.value) fwhm_min_deconv_arr.append(deconv_size.minor.value) fwhm_maj_deconv_err_arr.append(img_table['fwhm_maj_err_'+name][row]) #same error as non deconvolved fwhm_min_deconv_err_arr.append(img_table['fwhm_min_err_'+name][row]) pa_deconv_arr.append(deconv_size.pa.to(u.deg).value) pa_deconv_err_arr.append(img_table['pa_err_'+name][row]) except ValueError: fwhm_maj_deconv_arr.append(np.nan) fwhm_min_deconv_arr.append(np.nan) fwhm_maj_deconv_err_arr.append(np.nan) fwhm_min_deconv_err_arr.append(np.nan) pa_deconv_arr.append(np.nan) pa_deconv_err_arr.append(np.nan) pix_major_fwhm = ((img_table['fwhm_maj_'+name][row]*u.arcsec).to(u.degree)/pixel_scale).decompose() pix_minor_fwhm = ((img_table['fwhm_min_'+name][row]*u.arcsec).to(u.degree)/pixel_scale).decompose() center_coord = SkyCoord(img_table['RA_'+name][row], img_table['DEC_'+name][row], frame='icrs', unit=(u.deg, u.deg)) center_coord_pix = center_coord.to_pixel(img_wcs) center_coord_pix_reg = regions.PixCoord(center_coord_pix[0], center_coord_pix[1]) pos_ang = (img_table['pa_'+name][row]-90)*u.deg #must subtract 90 to be consistent ellipse_reg = regions.EllipsePixelRegion(center_coord_pix_reg, pix_major_fwhm.value*2, pix_minor_fwhm.value*2, angle=pos_ang) ap_mask = ellipse_reg.to_mask() cutout_mask = ap_mask.cutout(img_data) aperture_flux = np.nansum(cutout_mask[ap_mask.data==1])/ppbeam npix = len(cutout_mask[ap_mask.data==1]) #now make annulus for measuring background and error annulus_width = 15 #pixels annulus_radius = img_table['fwhm_maj_'+name][row]*u.arcsecond#+0.05*u.arcsecond annulus_radius_pix = (annulus_radius.to(u.degree)/pixel_scale).decompose() #cutout image cutout = Cutout2D(img_data, center_coord_pix, annulus_radius*2.5, img_wcs, mode='partial') cutout_center = regions.PixCoord(cutout.center_cutout[0], cutout.center_cutout[1]) #define aperture regions for SNR innerann_reg = regions.CirclePixelRegion(cutout_center, annulus_radius_pix.value) outerann_reg = regions.CirclePixelRegion(cutout_center, annulus_radius_pix.value+annulus_width) #Make masks from aperture regions annulus_mask = mask(outerann_reg, cutout) - mask(innerann_reg, cutout) # Calculate the SNR and aperture flux sums pixels_in_annulus = cutout.data[annulus_mask.astype('bool')] bg_rms = median_abs_deviation(pixels_in_annulus) print(img_table['Seq_B3'][row]) print('BG RMS: %f' % (bg_rms)) ap_bg_rms = bg_rms/np.sqrt(npix/ppbeam) #rms/sqrt(npix/ppbeam) - rms error per beam bg_median = np.nanmedian(pixels_in_annulus) pix_bg = bg_median*npix/ppbeam ap_flux_bgcorrect = aperture_flux - pix_bg ap_flux_correct = ap_flux_bgcorrect + ap_flux_bgcorrect*(1 - special.erf(2*np.sqrt(np.log(2)))) #flux correction for summing within 2*fwhm ap_flux_err_arr.append(ap_bg_rms) ap_flux_arr.append(ap_flux_correct) snr_arr.append(img_table['gauss_amp_'+name][row]/bg_rms) rms_arr.append(bg_rms) cols = ['ap_flux_'+name, 'ap_flux_err_'+name, 'fwhm_maj_deconv_'+name, 'fwhm_maj_deconv_err_'+name, 'fwhm_min_deconv_'+name, 'fwhm_min_deconv_err_'+name, 'pa_deconv_'+name, 'pa_deconv_err_'+name, 'SNR_'+name, 'RMS_'+name] arrs = [ap_flux_arr, ap_flux_err_arr, fwhm_maj_deconv_arr, fwhm_maj_deconv_err_arr, fwhm_min_deconv_arr, fwhm_min_deconv_err_arr, pa_deconv_arr, pa_deconv_err_arr, snr_arr, rms_arr] for c in range(len(cols)): img_table.add_column(Column(np.array(arrs[c])), name=cols[c]) img_table.add_column(Column(np.array(fwhm_maj_deconv_arr)/np.array(fwhm_min_deconv_arr)), name='ar_deconv_'+name) band_tables.append(img_table) #list of tables for each image B6B7 = join(band_tables[0], band_tables[1], keys='Seq_B3', join_type='outer') B6B7.write('/home/jotter/nrao/summer_research_2018/tables/'+cat_name+'.fits', overwrite=True)
def reject(imfile, catfile, threshold): """Reject noisy detections. Parameters ---------- imfile : str The path to the radio image file catfile : str The path to the source catalog, as obtained from detect.py threshold : float The signal-to-noise threshold below which sources are rejected """ # Extract information from filename outfile = os.path.basename(catfile).split('cat_')[1].split('.dat')[0] region = outfile.split('region')[1].split('_band')[0] band = outfile.split('band')[1].split('_val')[0] min_value = outfile.split('val')[1].split('_delt')[0] min_delta = outfile.split('delt')[1].split('_pix')[0] min_npix = outfile.split('pix')[1] print("\nSource rejection for region {} in band {}".format(region, band)) print("Loading image file") contfile = fits.open(imfile) data = contfile[0].data.squeeze() mywcs = wcs.WCS(contfile[0].header).celestial catalog = Table(Table.read(catfile, format='ascii'), masked=True) beam = radio_beam.Beam.from_fits_header(contfile[0].header) pixel_scale = np.abs( mywcs.pixel_scale_matrix.diagonal().prod())**0.5 * u.deg ppbeam = (beam.sr / (pixel_scale**2)).decompose().value data = data / ppbeam # Remove existing region files if os.path.isfile('./reg/reg_' + outfile + '_annulus.reg'): os.remove('./reg/reg_' + outfile + '_annulus.reg') if os.path.isfile('./reg/reg_' + outfile + '_filtered.reg'): os.remove('./reg/reg_' + outfile + '_filtered.reg') # Load in manually accepted and rejected sources override_accepted = [] override_rejected = [] if os.path.isfile('./.override/accept_' + outfile + '.txt'): override_accepted = np.loadtxt('./.override/accept_' + outfile + '.txt').astype('int') if os.path.isfile('./.override/reject_' + outfile + '.txt'): override_rejected = np.loadtxt('./.override/reject_' + outfile + '.txt').astype('int') print("\nManually accepted sources: ", set(override_accepted)) print("Manually rejected sources: ", set(override_rejected)) print('\nCalculating RMS values within aperture annuli') pb = ProgressBar(len(catalog)) data_cube = [] masks = [] rejects = [] snr_vals = [] mean_backgrounds = [] for i in range(len(catalog)): x_cen = catalog['x_cen'][i] * u.deg y_cen = catalog['y_cen'][i] * u.deg major_fwhm = catalog['major_fwhm'][i] * u.deg minor_fwhm = catalog['minor_fwhm'][i] * u.deg position_angle = catalog['position_angle'][i] * u.deg dend_flux = catalog['dend_flux_band{}'.format(band)][i] annulus_width = 1e-5 * u.deg center_distance = 1e-5 * u.deg # Define some ellipse properties in pixel coordinates position = coordinates.SkyCoord(x_cen, y_cen, frame='icrs', unit=(u.deg, u.deg)) pix_position = np.array(position.to_pixel(mywcs)) pix_major_fwhm = major_fwhm / pixel_scale pix_minor_fwhm = minor_fwhm / pixel_scale # Cutout section of the image we care about, to speed up computation time size = (center_distance + annulus_width + major_fwhm) * 2.2 cutout = Cutout2D(data, position, size, mywcs, mode='partial') cutout_center = regions.PixCoord(cutout.center_cutout[0], cutout.center_cutout[1]) # Define the aperture regions needed for SNR ellipse_reg = regions.EllipsePixelRegion( cutout_center, pix_major_fwhm * 2., pix_minor_fwhm * 2., angle=position_angle ) # Make sure you're running the dev version of regions, otherwise the position angles will be in radians! innerann_reg = regions.CirclePixelRegion( cutout_center, center_distance / pixel_scale + pix_major_fwhm) outerann_reg = regions.CirclePixelRegion( cutout_center, center_distance / pixel_scale + pix_major_fwhm + annulus_width / pixel_scale) # Make masks from aperture regions ellipse_mask = mask(ellipse_reg, cutout) annulus_mask = mask(outerann_reg, cutout) - mask(innerann_reg, cutout) # Plot annulus and ellipse regions data_cube.append(cutout.data) masks.append([annulus_mask, ellipse_mask]) # Calculate the SNR and aperture flux sums bg_rms = rms(cutout.data[annulus_mask.astype('bool')]) peak_flux = np.max(cutout.data[ellipse_mask.astype('bool')]) flux_rms_ratio = peak_flux / bg_rms snr_vals.append(flux_rms_ratio) # Reject bad sources below some SNR threshold rejected = False if flux_rms_ratio <= threshold: rejected = True # Process manual overrides if catalog['_idx'][i] in override_accepted: rejected = False if catalog['_idx'][i] in override_rejected: rejected = True rejects.append(int(rejected)) # Add non-rejected source ellipses to a new region file fname = './reg/reg_' + outfile + '_filtered.reg' with open(fname, 'a') as fh: if os.stat(fname).st_size == 0: fh.write("icrs\n") if not rejected: fh.write("ellipse({}, {}, {}, {}, {}) # text={{{}}}\n".format( x_cen.value, y_cen.value, major_fwhm.value, minor_fwhm.value, position_angle.value, i)) pb.update() # Plot the grid of sources plot_grid(data_cube, masks, rejects, snr_vals, catalog['_idx']) plt.suptitle( 'region={}, band={}, min_value={}, min_delta={}, min_npix={}, threshold={:.4f}' .format(region, band, min_value, min_delta, min_npix, threshold)) plt.show(block=False) # Get overrides from user print( 'Manual overrides example: type "r319, a605" to manually reject source #319 and accept source #605.' ) overrides = input( "\nType manual override list, or press enter to continue:\n").split( ', ') accepted_list = [ s[1:] for s in list(filter(lambda x: x.startswith('a'), overrides)) ] rejected_list = [ s[1:] for s in list(filter(lambda x: x.startswith('r'), overrides)) ] # Save the manually accepted and rejected sources fname = './.override/accept_' + outfile + '.txt' with open(fname, 'a') as fh: for num in accepted_list: fh.write('\n' + str(num)) fname = './.override/reject_' + outfile + '.txt' with open(fname, 'a') as fh: for num in rejected_list: fh.write('\n' + str(num)) print( "Manual overrides written to './.override/' and saved to source catalog. New overrides will be displayed the next time the rejection script is run." ) # Process the new overrides, to be saved into the catalog rejects = np.array(rejects) acc = np.array([a[-2:] for a in accepted_list], dtype=int) rej = np.array([r[-2:] for r in rejected_list], dtype=int) rejects[acc] = 0 rejects[rej] = 1 # Save the catalog with new columns for SNR catalog.add_column(Column(snr_vals), name='snr_band' + band) catalog.add_column(np.invert(catalog.mask['snr_band' + band]).astype(int), name='detected_band' + band) catalog.add_column(Column(rejects), name='rejected') catalog.write('./cat/cat_' + outfile + '_filtered.dat', format='ascii')
def fit_source(srcID, img, img_name, band, fit_bg=False, bg_stddev_x=30, bg_stddev_y=30, bg_mean_x=0, bg_mean_y=0, zoom=1, max_offset_in_beams=1, max_radius_in_beams=5, nonconv_img=None, mask_size=1.5): #this function fits a given source, and the background #srcID : int #name of source to fit in catalogs #img : fits file #fits file with source to fit #img_name : str #name of image for the directory where the fit plots will go #band : str #band of image to fit ('B3', 'B6', or 'B7') #fit_bg : bool #if False, do not fit background gaussian #bg_stddev_x : float #eyeballed estimate of stddev of the background source in pixels #bg_stddev_y : float #same as above in y direction #bg_mean_x/y : float #pixels away from center (origin) in x/y direction for background gaussian mean guess #zoom : float #amount of zoom, values greater than 1 are zoom ins #ref_data_name = '/home/jotter/nrao/summer_research_2018/tables/dendro_ref_catalog_edited.fits' ref_data_name = '/home/jotter/nrao/summer_research_2018/tables/ref_catalog_may21_b7.fits' #ref_data_name = '/lustre/cv/observers/cv-12578/orion_disks/summer_research_2018/tables/ref_catalog_may21.fits' ref_data = Table.read(ref_data_name) fl = fits.open(img) header = fl[0].header img_data = fl[0].data.squeeze() img_wcs = WCS(header).celestial beam = radio_beam.Beam.from_fits_header(header) pixel_scale = np.abs( img_wcs.pixel_scale_matrix.diagonal().prod())**0.5 * u.deg ppbeam = (beam.sr / (pixel_scale**2)).decompose().value if nonconv_img is not None: flnonconv = fits.open(nonconv_img) nonconv_header = flnonconv[0].header nonconv_beam = radio_beam.Beam.from_fits_header(nonconv_header) ppbeam = (nonconv_beam.sr / (pixel_scale**2)).decompose().value #now get ready to fit gaussians #start by setting up save directory for images gauss_save_dir = '/home/jotter/nrao/gauss_diags_may21/fitbg/' + img_name + '/' #gauss_save_dir = f'/lustre/cv/observers/cv-12578/orion_disks/gauss_diags_may21/{img_name}/' print('saving plots to ' + gauss_save_dir) if not os.path.exists(gauss_save_dir): os.makedirs(gauss_save_dir) #now make region rad = Angle(1, 'arcsecond') #radius used in region list #src_ind = np.where(ref_data['D_ID']==srcID)[0] src_ind = np.where(ref_data['B3_Seq'] == srcID)[0] ra = ref_data['RA_B3'][src_ind].data[0] dec = ref_data['DEC_B3'][src_ind].data[0] center_reg = SkyCoord(ra, dec, unit='deg', frame='icrs') reg = regions.CircleSkyRegion( center=center_reg, radius=1 * u.arcsecond, meta={ 'text': str(ref_data['B3_Seq'][src_ind].data[0]) + '_xstddev_' + str(bg_stddev_x) + '_ystddev_' + str(bg_stddev_y) }) region_list = [] #valid_inds = np.where(np.isnan(ref_data[band+'_detect']) == False)[0] for ind in range(len(ref_data)): #valid_inds: if ref_data['B3_Seq'][ind] == srcID: continue ra_i = ref_data['RA_B3'][ind] dec_i = ref_data['DEC_B3'][ind] region_i = regions.CircleSkyRegion(center=SkyCoord(ra_i, dec_i, unit='deg', frame='icrs'), radius=1 * u.arcsecond) region_list.append(region_i) #print(region_list) #print(reg) cat_r = Angle(0.5, 'arcsecond') / zoom #radius for gaussian fitting if fit_bg == True: gauss_cat, fitim_bg = bg_gaussfit( img, reg, region_list, cat_r, bg_stddev_x=bg_stddev_x, bg_stddev_y=bg_stddev_y, bg_mean_x=bg_mean_x, bg_mean_y=bg_mean_y, savepath=gauss_save_dir, max_offset_in_beams=max_offset_in_beams, max_offset_in_beams_bg=10, max_radius_in_beams=max_radius_in_beams, mask_size=mask_size) #print('gauss_cat length ',len(gauss_cat)) #k = list(gauss_cat.keys())[0] #if gauss_cat[k]['success'] == False: # gauss_cat = gaussfit_cutoutim(img, fitim_bg, reg, region_list, cat_r, savepath=gauss_save_dir, max_offset_in_beams = max_offset_in_beams, max_radius_in_beams = max_radius_in_beams) # success = gauss_cat[k]['success'] # print(F'ALTERNATIVE FIT SUCCESS: {success}') else: gauss_cat = gaussfit_catalog(img, [reg], cat_r, savepath=gauss_save_dir, max_offset_in_beams=max_offset_in_beams, max_radius_in_beams=max_radius_in_beams) img_table = Table(names=('Seq', 'fwhm_maj_' + band, 'fwhm_maj_err_' + band, 'fwhm_min_' + band, 'fwhm_min_err_' + band, 'pa_' + band, 'pa_err_' + band, 'gauss_amp_' + band, 'gauss_amp_err_' + band, 'RA_' + band, 'RA_err_' + band, 'DEC_' + band, 'DEC_err_' + band), dtype=('i4', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8', 'f8')) for key in gauss_cat: img_table.add_row( (srcID, gauss_cat[key]['fwhm_major'], gauss_cat[key]['e_fwhm_major'], gauss_cat[key]['fwhm_minor'], gauss_cat[key]['e_fwhm_minor'], gauss_cat[key]['pa'], gauss_cat[key]['e_pa'], gauss_cat[key]['amplitude'], gauss_cat[key]['e_amplitude'], gauss_cat[key]['center_x'], gauss_cat[key]['e_center_x'], gauss_cat[key]['center_y'], gauss_cat[key]['e_center_y'])) #now measure deconvovled sizes and aperture flux measurements for each source ap_flux_arr = [] ap_flux_err_arr = [] fwhm_maj_deconv_arr = [] fwhm_maj_deconv_err_arr = [] fwhm_min_deconv_arr = [] fwhm_min_deconv_err_arr = [] pa_deconv_arr = [] pa_deconv_err_arr = [] snr_arr = [] for row in range( len(img_table) ): #now loop through sources in reference data and make measurements ref_ind = np.where(ref_data['B3_Seq'] == img_table['Seq'][row])[0] if True == True: #len(ref_ind > 0): measured_source_size = radio_beam.Beam( major=img_table['fwhm_maj_' + band][row] * u.arcsec, minor=img_table['fwhm_min_' + band][row] * u.arcsec, pa=(img_table['pa_' + band][row] - 90) * u.deg) try: deconv_size = measured_source_size.deconvolve(beam) fwhm_maj_deconv_arr.append(deconv_size.major.value) fwhm_min_deconv_arr.append(deconv_size.minor.value) fwhm_maj_deconv_err_arr.append(img_table['fwhm_maj_err_' + band][row]) fwhm_min_deconv_err_arr.append(img_table['fwhm_min_err_' + band][row]) pa_deconv_arr.append(deconv_size.pa.value) pa_deconv_err_arr.append(img_table['pa_err_' + band][row]) except ValueError: fwhm_maj_deconv_arr.append(np.nan) fwhm_min_deconv_arr.append(np.nan) fwhm_maj_deconv_err_arr.append(np.nan) fwhm_min_deconv_err_arr.append(np.nan) pa_deconv_arr.append(np.nan) pa_deconv_err_arr.append(np.nan) pix_major_fwhm = ( (img_table['fwhm_maj_' + band][row] * u.arcsec).to(u.degree) / pixel_scale).decompose() pix_minor_fwhm = ( (img_table['fwhm_min_' + band][row] * u.arcsec).to(u.degree) / pixel_scale).decompose() center_coord = SkyCoord(img_table['RA_' + band][row], img_table['DEC_' + band][row], frame='icrs', unit=(u.deg, u.deg)) center_coord_pix = center_coord.to_pixel(img_wcs) center_coord_pix_reg = regions.PixCoord(center_coord_pix[0], center_coord_pix[1]) pos_ang = (img_table['pa_' + band][row] - 90) * u.deg ellipse_reg = regions.EllipsePixelRegion(center_coord_pix_reg, pix_major_fwhm.value * 2, pix_minor_fwhm.value * 2, angle=pos_ang) size = pix_major_fwhm * 2.1 ap_mask = ellipse_reg.to_mask() cutout_mask = ap_mask.cutout(img_data) aperture_flux = np.nansum(cutout_mask[ap_mask.data == 1]) / ppbeam npix = len(cutout_mask[ap_mask.data == 1]) #now make annulus for measuring background and error annulus_width = 15 #pixels annulus_radius = img_table[ 'fwhm_maj_' + band][row] * u.arcsecond #0.1*u.arcsecond annulus_radius_pix = (annulus_radius.to(u.degree) / pixel_scale).decompose() #cutout image cutout = Cutout2D(img_data, center_coord_pix, annulus_radius * 2.5, img_wcs, mode='partial') cutout_center = regions.PixCoord(cutout.center_cutout[0], cutout.center_cutout[1]) #define aperture regions for SNR innerann_reg = regions.CirclePixelRegion(cutout_center, annulus_radius_pix.value) outerann_reg = regions.CirclePixelRegion( cutout_center, annulus_radius_pix.value + annulus_width) #Make masks from aperture regions annulus_mask = mask(outerann_reg, cutout) - mask( innerann_reg, cutout) # Calculate the SNR and aperture flux sums pixels_in_annulus = cutout.data[annulus_mask.astype( 'bool')] #pixels within annulus bg_rms = median_abs_deviation(pixels_in_annulus) ap_bg_rms = bg_rms / np.sqrt( npix / ppbeam) #rms/sqrt(npix/ppbeam) - rms error per beam bg_median = np.median(pixels_in_annulus) pix_bg = bg_median * npix / ppbeam #flux corrections ap_flux_bgcorrect = aperture_flux - pix_bg ap_flux_correct = ap_flux_bgcorrect + ap_flux_bgcorrect * ( 1 - special.erf(2 * np.sqrt(np.log(2))) ) #flux correction for summing within 2*fwhm print( f'Background: {pix_bg}, Gauss amp: {img_table["gauss_amp_"+band][row]}' ) print(f'peak pixel: {np.nanmax(cutout_mask[ap_mask.data==1])}') ap_flux_err_arr.append(ap_bg_rms) ap_flux_arr.append(ap_flux_correct) snr_arr.append(img_table['gauss_amp_' + band][row] / bg_rms) cols = [ 'ap_flux_' + band, 'ap_flux_err_' + band, 'fwhm_maj_deconv_' + band, 'fwhm_maj_deconv_err_' + band, 'fwhm_min_deconv_' + band, 'fwhm_min_deconv_err_' + band, 'pa_deconv_' + band, 'pa_deconv_err_' + band, 'SNR_' + band ] arrs = [ ap_flux_arr, ap_flux_err_arr, fwhm_maj_deconv_arr, fwhm_maj_deconv_err_arr, fwhm_min_deconv_arr, fwhm_min_deconv_err_arr, pa_deconv_arr, pa_deconv_err_arr, snr_arr ] for c in range(len(cols)): img_table.add_column(Column(np.array(arrs[c])), name=cols[c]) img_table.add_column(Column(img_table['fwhm_maj_deconv_' + band] / img_table['fwhm_min_deconv_' + band]), name='ar_deconv_' + band) return img_table
def flux(region): # import the catalog file, get names of bands filename = glob('./cat/mastercat_region{}*'.format(region))[0] catalog = Table(Table.read(filename, format='ascii'), masked=True) catalog.sort('_idx') bands = np.array(filename.split('bands_')[1].split('.dat')[0].split('_'), dtype=int) n_bands = len(bands) n_rows = len(catalog) ellipse_npix_col = MaskedColumn(length=len(catalog), name='ellipse_npix', mask=True) circ1_npix_col = MaskedColumn(length=len(catalog), name='circ1_npix', mask=True) circ2_npix_col = MaskedColumn(length=len(catalog), name='circ2_npix', mask=True) circ3_npix_col = MaskedColumn(length=len(catalog), name='circ3_npix', mask=True) n_rejected = 0 for i in range(n_bands): band = bands[i] # Load image file for this band print("\nLoading image file for region {} in band {} (Image {}/{})". format(region, band, i + 1, n_bands)) imfile = grabfileinfo(region, band)[0] contfile = fits.open(imfile) data = contfile[0].data.squeeze() # Set up wcs, beam, and pixel scale for this image mywcs = wcs.WCS(contfile[0].header).celestial beam = radio_beam.Beam.from_fits_header(contfile[0].header) pixel_scale = np.abs( mywcs.pixel_scale_matrix.diagonal().prod())**0.5 * u.deg ppbeam = (beam.sr / (pixel_scale**2)).decompose().value print('ppbeam: ', ppbeam) data = data / ppbeam # Set up columns for each aperture peak_flux_col = MaskedColumn(length=len(catalog), name='peak_flux_band{}'.format(band), mask=True) annulus_median_col = MaskedColumn( length=len(catalog), name='annulus_median_band{}'.format(band), mask=True) annulus_rms_col = MaskedColumn(length=len(catalog), name='annulus_rms_band{}'.format(band), mask=True) ellipse_flux_col = MaskedColumn( length=len(catalog), name='ellipse_flux_band{}'.format(band), mask=True) circ1_flux_col = MaskedColumn(length=len(catalog), name='circ1_flux_band{}'.format(band), mask=True) circ2_flux_col = MaskedColumn(length=len(catalog), name='circ2_flux_band{}'.format(band), mask=True) circ3_flux_col = MaskedColumn(length=len(catalog), name='circ3_flux_band{}'.format(band), mask=True) ellipse_rms_col = MaskedColumn(length=len(catalog), name='ellipse_rms_band{}'.format(band), mask=True) circ1_rms_col = MaskedColumn(length=len(catalog), name='circ1_rms_band{}'.format(band), mask=True) circ2_rms_col = MaskedColumn(length=len(catalog), name='circ2_rms_band{}'.format(band), mask=True) circ3_rms_col = MaskedColumn(length=len(catalog), name='circ3_rms_band{}'.format(band), mask=True) circ1_r, circ2_r, circ3_r = 5e-6 * u.deg, 1e-5 * u.deg, 1.5e-5 * u.deg print('Photometering sources') pb = ProgressBar(len(catalog[np.where(catalog['rejected'] == 0)])) masks = [] datacube = [] rejects = [] snr_vals = [] names = [] # Iterate over sources, extracting ellipse parameters for j in range(n_rows): if catalog['rejected'][j] == 1: continue source = catalog[j] x_cen = source['x_cen'] * u.deg y_cen = source['y_cen'] * u.deg major = source['major_fwhm'] * u.deg minor = source['minor_fwhm'] * u.deg pa = source['position_angle'] * u.deg annulus_width = 1e-5 * u.deg center_distance = 1e-5 * u.deg # Convert to pixel coordinates position = coordinates.SkyCoord(x_cen, y_cen, frame='icrs', unit=(u.deg, u.deg)) pix_position = np.array(position.to_pixel(mywcs)) pix_major = major / pixel_scale pix_minor = minor / pixel_scale # Create cutout size = np.max([ circ3_r.value, major.value + center_distance.value + annulus_width.value ]) * 2.2 * u.deg try: cutout = Cutout2D(data, position, size, mywcs, mode='partial') except NoOverlapError: catalog['rejected'][j] = 1 pb.update() continue cutout_center = regions.PixCoord(cutout.center_cutout[0], cutout.center_cutout[1]) datacube.append(cutout.data) # create all aperture shapes ellipse_reg = regions.EllipsePixelRegion(cutout_center, pix_major * 2., pix_minor * 2., angle=pa) circ1_reg = regions.CirclePixelRegion(cutout_center, circ1_r / pixel_scale) circ2_reg = regions.CirclePixelRegion(cutout_center, circ2_r / pixel_scale) circ3_reg = regions.CirclePixelRegion(cutout_center, circ3_r / pixel_scale) innerann_reg = regions.CirclePixelRegion( cutout_center, center_distance / pixel_scale + pix_major) outerann_reg = regions.CirclePixelRegion( cutout_center, center_distance / pixel_scale + pix_major + annulus_width / pixel_scale) annulus_mask = mask(outerann_reg, cutout) - mask( innerann_reg, cutout) # get flux information from regions ellipse_flux, ellipse_rms, peak_flux, ellipse_mask, ellipse_npix = apsum( ellipse_reg, cutout) circ1_flux, circ1_rms, _, circ1_mask, circ1_npix = apsum( circ1_reg, cutout) circ2_flux, circ2_rms, _, circ2_mask, circ2_npix = apsum( circ2_reg, cutout) circ3_flux, circ3_rms, _, circ3_mask, circ3_npix = apsum( circ3_reg, cutout) annulus_rms = rms(cutout.data[annulus_mask.astype('bool')]) annulus_median = np.median( cutout.data[annulus_mask.astype('bool')]) # Add grid plot mask to list masklist = [ ellipse_mask, annulus_mask, circ1_mask, circ2_mask, circ3_mask ] masks.append(masklist) # add fluxes to appropriate columns peak_flux_col[j] = peak_flux ellipse_flux_col[j], ellipse_rms_col[j] = ellipse_flux, ellipse_rms circ1_flux_col[j], circ1_rms_col[j] = circ1_flux, circ1_rms circ2_flux_col[j], circ2_rms_col[j] = circ2_flux, circ2_rms circ3_flux_col[j], circ3_rms_col[j] = circ3_flux, circ3_rms ellipse_npix_col[j] = ellipse_npix circ1_npix_col[j] = circ1_npix circ2_npix_col[j] = circ2_npix circ3_npix_col[j] = circ3_npix annulus_median_col[j] = annulus_median annulus_rms_col[j] = annulus_rms catalog['snr_band' + str(band)][j] = peak_flux / annulus_rms snr_vals.append(peak_flux / annulus_rms) names.append(catalog['_idx'][j]) # Secondary rejection rejected = 0 lowest_flux = np.min( [ellipse_flux, circ1_flux, circ2_flux, circ3_flux]) #if lowest_flux <= annulus_median*ellipse_npix or lowest_flux < 0: if lowest_flux < 0: catalog['rejected'][j] = 1 n_rejected += 1 rejected = 1 rejects.append(rejected) pb.update() # Plot the grid of sources plot_grid(datacube, masks, rejects, snr_vals, names) plt.suptitle('region={}, band={}'.format(region, band)) plt.show(block=False) # add columns to catalog catalog.add_columns([ peak_flux_col, ellipse_flux_col, ellipse_rms_col, circ1_flux_col, circ1_rms_col, circ2_flux_col, circ2_rms_col, circ3_flux_col, circ3_rms_col, ]) catalog.add_columns([ ellipse_npix_col, circ1_npix_col, circ2_npix_col, circ3_npix_col, annulus_median_col, annulus_rms_col ]) print("\n{} sources flagged for secondary rejection".format(n_rejected)) # save catalog catalog = catalog[sorted(catalog.colnames)] catalog.write(filename.split('.dat')[0] + '_photometered.dat', format='ascii') print("\nMaster catalog saved as '{}'".format( filename.split('.dat')[0] + '_photometered.dat'))
def ginga_canvas_object_to_astropy_region(obj): """ Convert a Ginga canvas object to an AstroPy region object. Parameters ---------- obj : subclass of `~ginga.canvas.CanvasObject` The Ginga canvas object to be converted Returns ------- r : subclass of `~regions.PixelRegion` The corresponding AstroPy region object """ if not HAVE_REGIONS: raise ValueError( "Please install the Astropy 'regions' package to use this function" ) dc = get_canvas_types() r = None if isinstance(obj, (dc.Circle, )): r = regions.CirclePixelRegion(center=regions.PixCoord(x=obj.x, y=obj.y), radius=obj.radius) elif isinstance(obj, (dc.Ellipse, )): r = regions.EllipsePixelRegion(center=regions.PixCoord(x=obj.x, y=obj.y), width=obj.xradius * 2, height=obj.yradius * 2, angle=obj.rot_deg * u.deg) elif isinstance(obj, (dc.Text, )): r = regions.TextPixelRegion(center=regions.PixCoord(x=obj.x, y=obj.y), text=obj.text) r.visual['textangle'] = str(obj.rot_deg) elif isinstance(obj, (dc.Point, )): r = regions.PointPixelRegion(center=regions.PixCoord(x=obj.x, y=obj.y)) style = pt_ginga.get(obj.style, '*') r.visual['symbol'] = style elif isinstance(obj, (dc.Line, )): r = regions.LinePixelRegion(start=regions.PixCoord(x=obj.x1, y=obj.y1), end=regions.PixCoord(x=obj.x2, y=obj.y2)) elif isinstance(obj, (dc.Box, )): r = regions.RectanglePixelRegion(center=regions.PixCoord(x=obj.x, y=obj.y), width=obj.xradius * 2, height=obj.yradius * 2, angle=obj.rot_deg * u.deg) elif isinstance(obj, (dc.Polygon, )): x, y = np.asarray(obj.points).T r = regions.PolygonPixelRegion(vertices=regions.PixCoord(x=x, y=y)) elif isinstance(obj, (dc.Annulus, )) and obj.atype == 'circle': rin = obj.radius rout = rin + obj.width r = regions.CircleAnnulusPixelRegion(center=regions.PixCoord(x=obj.x, y=obj.y), inner_radius=rin, outer_radius=rout) elif isinstance(obj, (dc.Annulus2R, )) and obj.atype == 'ellipse': r = regions.EllipseAnnulusPixelRegion( center=regions.PixCoord(x=obj.x, y=obj.y), inner_width=obj.xradius * 2, inner_height=obj.yradius * 2, outer_width=obj.xradius * 2 + obj.xwidth * 2, outer_height=obj.yradius * 2 + obj.ywidth * 2, angle=obj.rot_deg * u.deg) elif isinstance(obj, (dc.Annulus2R, )) and obj.atype == 'box': r = regions.RectangleAnnulusPixelRegion( center=regions.PixCoord(x=obj.x, y=obj.y), inner_width=obj.xradius * 2, inner_height=obj.yradius * 2, outer_width=obj.xradius * 2 + obj.xwidth * 2, outer_height=obj.yradius * 2 + obj.ywidth * 2, angle=obj.rot_deg * u.deg) else: raise ValueError("Don't know how to convert this object") # Set visual styling attributes r.visual['color'] = obj.color if hasattr(obj, 'font'): r.visual['font'] = obj.font if obj.fontsize is not None: r.visual['fontsize'] = str(obj.fontsize) if hasattr(obj, 'linewidth'): r.visual['linewidth'] = obj.linewidth if hasattr(obj, 'fill'): r.visual['fill'] = obj.fill = r.visual.get('fill', False) # Limited support for other metadata r.meta['edit'] = 1 if obj.editable else 0 meta = obj.get_data() if meta is not None and meta.get('name', None) is not None: r.meta['name'] = meta.get('name') return r
def reject_sources(name, catname, datname, min_snr=5, max_size=None, max_size_ID=None, flux_type='peak'): #catname: name of catalog of sources, with required columns 'gauss_x_'+name, 'gauss_y_'+name, 'FWHM_major_'+name, 'FWHM_minor_'+name, and 'position_angle_'+name #datname: name of data fits image #min_snr: minimum SNR value, all sources with SNR below this will be rejected #max_size: maximum major axis radius for ellipse around source, in sigma #max_size_ID: alternatively, give the index of a source to set that source's radius to the maximum #flux_type: if 'peak', chooses the brightest pixel, if 'percentile', flux measured by average of top 10% of pixels catalog = fits.getdata(catname) catalog = Table(catalog) bad_inds = np.where(np.isnan(catalog['ap_flux_' + name]) == True) catalog.remove_rows(bad_inds) fl = fits.open(datname) data = fl[0].data.squeeze() header = fl[0].header mywcs = WCS(header).celestial sigma_to_FWHM = 2 * np.sqrt(2 * np.log(2)) pixel_scale = np.abs(mywcs.pixel_scale_matrix.diagonal().prod() )**0.5 * u.deg #for conversion to pixels snr_vals = [] cutout_images = [] masks = [] bg_arr = [] bg_arr2 = [] reject = np.full(len(catalog), False) for i in range(len(catalog)): x_cen = catalog['gauss_x_' + name][i] * u.deg y_cen = catalog['gauss_y_' + name][i] * u.deg major_fwhm = (catalog['FWHM_major_' + name][i] * u.arcsec).to(u.degree) minor_fwhm = (catalog['FWHM_minor_' + name][i] * u.arcsec).to(u.degree) position_angle = catalog['position_angle_' + name][i] * u.deg annulus_width = 15 center_pad = 10 #pad between ellipse and inner radius # Define some ellipse properties in pixel coordinates position = SkyCoord(x_cen, y_cen, frame='icrs', unit=(u.deg, u.deg)) pix_position = np.array(position.to_pixel(mywcs)) pix_major_fwhm = major_fwhm / pixel_scale pix_minor_fwhm = minor_fwhm / pixel_scale # Cutout section of the image we care about, to speed up computation time size = ( (center_pad + annulus_width) * pixel_scale + major_fwhm ) * 2.2 #2.2 is arbitrary to get entire annulus and a little extra cutout = Cutout2D(data, position, size, mywcs, mode='partial') #cutout of outer circle cutout_center = regions.PixCoord( cutout.center_cutout[0], cutout.center_cutout[1]) #center of the cutout in pixel coords # Define the aperture regions needed for SNR ellipse_reg = regions.EllipsePixelRegion(cutout_center, pix_major_fwhm * 2., pix_minor_fwhm * 2., angle=position_angle) innerann_reg = regions.CirclePixelRegion(cutout_center, center_pad + pix_major_fwhm) outerann_reg = regions.CirclePixelRegion( cutout_center, center_pad + pix_major_fwhm + annulus_width) # Make masks from aperture regions ellipse_mask = mask(ellipse_reg, cutout) annulus_mask = mask(outerann_reg, cutout) - mask(innerann_reg, cutout) # Calculate the SNR and aperture flux sums pixels_in_annulus = cutout.data[annulus_mask.astype( 'bool')] #pixels within annulus pixels_in_ellipse = cutout.data[ellipse_mask.astype( 'bool')] #pixels in ellipse bg_rms = rms(pixels_in_annulus) bg_mean = np.mean(pixels_in_annulus) bg_median = np.median(pixels_in_annulus) if flux_type == 'peak': peak_flux = catalog['peak_flux_' + name][i] if flux_type == 'percentile': top_percent = np.nanpercentile(pixels_in_ellipse, 90) peak_flux = np.mean( pixels_in_ellipse[pixels_in_ellipse > top_percent]) snr = peak_flux / bg_rms catalog['ap_flux_err_' + name][i] = bg_rms bg_arr.append(bg_mean) bg_arr2.append(bg_median) if snr < min_snr: #if low snr, reject reject[i] = True if max_size_ID is not None: if catalog['major_sigma'][i] > catalog['major_sigma'][ max_size_ID] + 0.01 / 3600: #if too big a source, reject reject[i] = True if max_size is not None: if catalog['major_sigma'][ i] > max_size: #if too big a source, reject reject[i] = True snr_vals.append(snr) cutout_images.append(cutout.data) masks.append(ellipse_mask + annulus_mask) catalog['bg_mean_' + name] = bg_arr catalog['bg_median_' + name] = bg_arr2 plot_grid(cutout_images, masks, reject, snr_vals, catalog['_idx_' + name]) plt.show(block=False) line_remove = input( 'enter id values for sources to exclude from the catalog, seperated by whitespace: ' ) man_input_rem = np.array(line_remove.split(), dtype='int') id_ind_true = np.where( np.in1d(catalog['_idx_' + name], man_input_rem) == True) reject[id_ind_true] = True line_keep = input( 'enter id values for removed sources to include from the catalog, seperated by whitespace: ' ) man_input_keep = np.array(line_keep.split(), dtype='int') id_ind_false = np.where( np.in1d(catalog['_idx_' + name], man_input_keep) == True) reject[id_ind_false] = False rej_ind = np.where(reject == True) catalog.remove_rows(rej_ind) return catalog