def show_example(self): data, label = self.random_model_label(1) label = label.flatten() _data = data[0].reshape(21, 21) pos = np.array(label[0]).flatten() plt.imshow(_data, cmap="Greys_r") t0_c2dg = time.time() pred_data = [data[0].reshape(1, 21, 21, 1)] t0_nn = time.time() pred = self.model(pred_data, training=False).numpy() tf_nn = time.time() - t0_nn plt.plot(*label[::-1], "x", c="k", label="true") plt.plot(*pred.T[::-1], "x", label="NNCentroid") t0_c2dg = time.time() c2dg = centroid_2dg(_data) tf_c2dg = time.time() - t0_c2dg plt.plot(*c2dg, "x", label="centroid_2dg") plt.legend() ax = plt.gca() plt.text( 0.05, 0.05, "distance:\nNNCentroid: {:.1e} ({:.0f}x faster)\n2DGaussian: {:.1e}" .format( np.sqrt(np.sum((label - pred)**2)), tf_c2dg / tf_nn, np.sqrt(np.sum((label - c2dg[::-1])**2)), ), fontsize=11, horizontalalignment='left', verticalalignment='bottom', transform=ax.transAxes, c="w")
def get_pix(cube, cen=None, geom='3x3', normalize=True): """ assumes the cube has shape (n,k,k), where n is the number of frames and k is the width of each (square) frame, and that k > 5. """ def fix_nan(im): im[np.isnan(im)] = np.median(im[~np.isnan(im)]) if not cen: med_im = np.median(cube, axis=0) fix_nan(med_im) cx, cy = centroid_2dg(med_im) cx, cy = list(map(int, list(map(round, [cx, cy])))) print("centroid: {}, {}".format(cx, cy)) else: cx, cy = list(map(int, cen)) if geom == '3x3': i = 1 elif geom == '5x5': i = 2 else: raise ValueError("geometry not supported") x0 = cx - i x1 = cx + i + 1 y0 = cy - i y1 = cy + i + 1 sub_cube = cube[:, x0:x1, y0:y1] pixels = sub_cube.reshape(cube.shape[0], sub_cube.shape[1]**2) if normalize: for i in pixels: i /= i.sum() return pixels
def centroid_2dg(self): """ This function ... :return: """ #from ..tools import plotting #plotting.plot_box(self._data) return centroid_2dg(self._data)
def _baricenterCalculator(self, reference_image): ''' Calculate the peak position of the image args: reference_image = camera frame returns: cy, cx = y and x coord ''' counts, bin_edges = np.histogram(reference_image) thr = 5 * bin_edges[np.where(counts == max(counts))] idx = np.where(reference_image < thr) img = reference_image.copy() img[idx] = 0 cx, cy = centroids.centroid_2dg(img) return np.int(cy), np.int(cx)
def centroid_2dg(self, cutoff=None): """ This function ... :param cutoff: sigma level for cutting off the data :return: """ # Create cutoff mask if cutoff: mask = self.create_sigma_mask(cutoff, invert=True) else: mask = None #from ..tools import plotting #plotting.plot_box(self._data) return centroid_2dg(self._data, mask=mask)
def _newThr(self, img): ''' Calculate the peak position of the image ''' counts, bin_edges = np.histogram(img) edges = (bin_edges[2:] + bin_edges[1:len(bin_edges) - 1]) / 2 thr = 5 * edges[np.where(counts == max(counts))] idx = np.where(img < thr) img[idx] = 0 # cy, cx = scin.measurements.center_of_mass(img) cx, cy = centroids.centroid_2dg(img) baricenterCoord = [np.int(round(cy)), np.int(round(cx))] pick = [ baricenterCoord[0] - 25, baricenterCoord[0] + 25, baricenterCoord[1] - 75, baricenterCoord[1] + 75 ] return pick
def compute_eff_radii(image, plot=False): max_pix_rad = np.min(image.shape) // 2 radius = np.arange(3, max_pix_rad, 3) fake_image = np.zeros_like(image) fake_image[max_pix_rad // 2:(3 * max_pix_rad) // 2, max_pix_rad // 2:(3 * max_pix_rad) // 2] = image[max_pix_rad // 2:(3 * max_pix_rad) // 2, max_pix_rad // 2:(3 * max_pix_rad) // 2] com_x, com_y = centroid_2dg(fake_image) aperture_sum = [] for rad_i in radius: aperture = CircularAperture((com_x, com_y), r=rad_i) phot_table = aperture_photometry(image, aperture) aperture_sum.append(phot_table['aperture_sum'].value) aperture_sum = np.array(aperture_sum).squeeze() norm_aperture_sum = aperture_sum / aperture_sum[-1] half_mass_rad = np.interp(0.5, norm_aperture_sum, radius) half_mass_aperture = CircularAperture((com_x, com_y), r=half_mass_rad) two_half_mass_aperture = CircularAperture((com_x, com_y), r=2 * half_mass_rad) half_mass_table = aperture_photometry(image, half_mass_aperture) two_half_mass_table = aperture_photometry(image, two_half_mass_aperture) if plot: fig = plt.figure() plt.imshow(np.log10(image)) plt.colorbar(label='log(image)') plt.plot(com_x, com_y, '+', markersize=8, color='c') half_mass_aperture.plot(color='r', lw=2, label=r'Half mass rad') two_half_mass_aperture.plot(color='orange', lw=2, label=r'Two half mass rad') plt.annotate( 'Tot mass={:.2}\nHalf mass={:.2}\nTwoHalf mass={:.2}'.format( float(aperture_sum[-1]), float(half_mass_table['aperture_sum'].value), float(two_half_mass_table['aperture_sum'].value)), xy=(.1, 1), xycoords='axes fraction', va='bottom') plt.legend() plt.close() return half_mass_aperture, two_half_mass_aperture, fig else: return half_mass_aperture, two_half_mass_aperture
def optimizephot(obj, img): ix, iy = obj cenx, ceny = centroids.centroid_2dg(img[iy - 10:iy + 10, ix - 10:ix + 10]) cenx += ix - 10 ceny += iy - 10 print("CENTROIDED LOCATION:", cenx, ceny) data = np.ma.asanyarray(img) weights = np.ones(data.shape) init_con = np.median(data) init_amp = np.max(data[int(ceny) - 5:int(ceny) + 5, int(cenx) - 5:int(cenx) + 5]) g_init = centroids.GaussianConst2D(constant=init_con, amplitude=init_amp, x_mean=cenx, y_mean=ceny, x_stddev=1, y_stddev=1, theta=0, fixed={ 'x_mean': True, 'y_mean': True }) fitter = LevMarLSQFitter() y, x = np.indices(data.shape) gfit = fitter(g_init, x, y, data, weights=weights) print(gfit) ap = np.round(1.6 * np.sqrt(gfit.x_stddev**2. + gfit.y_stddev**2), 1) #Mighell 1999ASPC..189...50M print("OPTIMAL APERTURE (unscaled):", ap) if ap > 15. or ap <= 1.: print("~~~~~~~~ POSSIBLE BAD APERTURE FIT - CHECK IMAGE ~~~~~~~~") print() amp = gfit.amplitude return cenx, ceny, ap, amp
def centroid(data, coord, box_size=30, wcs=None): if is_pix_coord(coord): coord = [np.float(coord[0]), np.float(coord[1])] print 'Centroiding at (%.2f,%.2f)' % (coord[0], coord[1]), elif is_sky_coord(coord): if wcs: try: x, y = wcs2pix(coord, wcs) print 'Centroiding at (%s,%s) -> (%.2f,%.2f)' % ( coord[0], coord[1], x, y) coord = (x, y) except: raise ValueError('Cannot parse coordinates with input wcs') else: raise InputError( "Must provide wcs object to parse sky coordinates") else: raise ValueError('Cannot parse coordinates') ## make cutout mask #print coord _, _, _, slices = cutout_footprint(data, coord, box_size=box_size) mask = np.ones_like(data, dtype=bool) mask[slices] = False xc, yc = centroid_2dg(data, mask=mask) print ' -> (%f, %f)' % (xc, yc), if wcs: ra, dec = pix2wcs([xc, yc], wcs) print ' [%s, %s]' % (ra, dec) tbl = Table([[xc], [yc], [ra], [dec]], names=['xcen', 'ycen', 'ra', 'dec']) return tbl else: print tbl = Table([[xc], [yc]], names=['xcen', 'ycen']) return tbl
def Center_2DGaussian(IMG, results, options): """ Compute the pixel location of the galaxy center by fitting a 2d Gaussian as implimented by the photutils package. """ current_center = {'x': IMG.shape[1]/2, 'y': IMG.shape[0]/2} if 'ap_guess_center' in options: current_center = deepcopy(options['ap_guess_center']) logging.info('%s: Center initialized by user: %s' % (options['ap_name'], str(current_center))) if 'ap_set_center' in options: logging.info('%s: Center set by user: %s' % (options['ap_name'], str(options['ap_set_center']))) return IMG, {'center': deepcopy(options['ap_set_center'])} # Create mask to focus centering algorithm on the center of the image ranges = [[max(0,int(current_center['x'] - 50*results['psf fwhm'])), min(IMG.shape[1],int(current_center['x'] + 50*results['psf fwhm']))], [max(0,int(current_center['y'] - 50*results['psf fwhm'])), min(IMG.shape[0],int(current_center['y'] + 50*results['psf fwhm']))]] centralize_mask = np.ones(IMG.shape, dtype = bool) centralize_mask[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]] = False try: x, y = centroid_2dg(IMG - results['background'], mask = centralize_mask) current_center = {'x': x, 'y': y} except: logging.warning('%s: 2D Gaussian center finding failed! using image center (or guess).' % options['ap_name']) # Plot center value for diagnostic purposes if 'ap_doplot' in options and options['ap_doplot']: plt.imshow(np.clip(IMG - results['background'],a_min = 0, a_max = None), origin = 'lower', cmap = 'Greys_r', norm = ImageNormalize(stretch=LogStretch())) plt.plot([current_center['x']],[current_center['y']], marker = 'x', markersize = 10, color = 'y') plt.savefig('%scenter_vis_%s.jpg' % (options['ap_plotpath'] if 'ap_plotpath' in options else '', options['ap_name'])) plt.close() logging.info('%s Center found: x %.1f, y %.1f' % (options['ap_name'], x, y)) return IMG, {'center': current_center, 'auxfile center': 'center x: %.2f pix, y: %.2f pix' % (current_center['x'], current_center['y'])}
def calculate_local_center(self, cutout): center_box_slices = (slice(6,15), slice(6,15)) cen = centroid_2dg(cutout[center_box_slices]) return cen + 6
def psf_convolution(self, model): """Convolve the cropped image with the appropriate PSF for the JWST detector being simulated. Parameters ---------- model : jwst.datamodels.ImageModel Data model instance containing the cropped image Returns ------- model : jwst.datamodels.ImageModel Data model with image convolved by the PSF """ # The jwst_psf and the mosaic_psf must have the same array size # and the same pixel scale. First deal with the pixel scale. # Rescale one of the PSFs if necessary, in order to get matching pixel scales. # Since the matching kernel is going to be convolved with the mosaic image, # then it seems like we should match the PSFs at the mosaic pixel scale. if not np.isclose(self.outscale1, self.mosaic_metadata['pix_scale1'], atol=0., rtol=0.01): orig_jwst = copy.deepcopy(self.jwst_psf) self.jwst_psf = resize_psf(self.jwst_psf, self.outscale1, self.mosaic_metadata['pix_scale1'], order=3) resized_y_dim, resized_x_dim = self.jwst_psf.shape if ((resized_y_dim % 2 == 0) or (resized_x_dim % 2 == 0)): if resized_y_dim % 2 == 0: new_y_dim = resized_y_dim + 1 else: new_y_dim = resized_y_dim if resized_x_dim % 2 == 0: new_x_dim = resized_x_dim + 1 else: new_x_dim = resized_x_dim # Add a column/row to the resized array, jwst_psf_padded = np.zeros((new_y_dim, new_x_dim)) jwst_psf_padded[0:resized_y_dim, 0:resized_x_dim] = self.jwst_psf # Rather than zeros, make the top row/leftmost column a # copy of the row/column next to it if new_y_dim > resized_y_dim: jwst_psf_padded[-1, 0:resized_x_dim] = self.jwst_psf[-1, :] if new_x_dim > resized_x_dim: jwst_psf_padded[0:resized_y_dim, -1] = self.jwst_psf[:, -1] if ((new_y_dim > resized_y_dim) and (new_x_dim > resized_x_dim)): jwst_psf_padded[-1, -1] = self.jwst_psf[-1, 1] # Shift the source to be centered in the center pixel centerx, centery = centroid_2dg(jwst_psf_padded) jwst_shifted = shift(jwst_psf_padded, [0.5, 0.5], order=1) centerx, centery = centroid_2dg(jwst_shifted) self.jwst_psf = jwst_shifted else: jwst_psf_padded = self.jwst_psf jwst_shape = self.jwst_psf.shape mosaic_shape = self.mosaic_psf.shape if ((jwst_shape[0] % 2 != mosaic_shape[0] % 2) or (jwst_shape[1] % 2 != mosaic_shape[1] % 2)): raise ValueError(( "ERROR: Mosaic PSF and JWST PSF have different shapes in terms " "of odd/even numbers of rows and/or columns. Try adding or subtracting " "rows/columns to the mosaic PSF. Mosaic PSF shape: {}, JWST PSF shape: {}" .format(mosaic_shape, jwst_shape))) # Now crop either the resized JWST PSF or the mosaic PSF in # order to get them both to the same array size self.logger.info("Crop PSFs to have the same array size") self.jwst_psf, self.mosaic_psf = tools.same_array_size( self.jwst_psf, self.mosaic_psf) # Now we make a matching kernel. The mosaic can then be # convolved with this kernel in order to adjust the PSFs to match # those from JWST. self.logger.info("Create matching kernel") kernel = self.matching_kernel(self.mosaic_psf, self.jwst_psf, window_type='TukeyWindow', alpha=1.5, beta=1.5) if self.save_intermediates: self.logger.info( 'Save JWST psf and matching psf in outgoing_and_matching_kernel.fits' ) ha = fits.PrimaryHDU(orig_jwst) h0 = fits.ImageHDU(self.jwst_psf) h1 = fits.ImageHDU(self.mosaic_psf) h2 = fits.ImageHDU(kernel) hlist = fits.HDUList([ha, h0, h1, h2]) outfile = os.path.join( self.outdir, '{}_outgoing_and_matching_kernel.fits'.format( self.output_base)) hlist.writeto(outfile, overwrite=True) self.logger.info( 'Convolve image cropped from mosaic with the matching PSF kernel') start_time = datetime.datetime.now() convolved_mosaic = fftconvolve(model.data, kernel, mode='same') end_time = datetime.datetime.now() delta_time = end_time - start_time self.logger.info("Convolution took {} seconds".format( delta_time.seconds)) model.data = convolved_mosaic if self.save_intermediates: self.logger.info( 'Saving convolved mosaic as convolved_mosaic.fits') h0 = fits.PrimaryHDU(convolved_mosaic) hlist = fits.HDUList([h0]) outfile = os.path.join( self.outdir, '{}_convolved_mosaic.fits'.format(self.output_base)) hlist.writeto(outfile, overwrite=True) return model
def Center_2DGaussian(IMG, results, options): """Find galaxy center with a 2D gaussian fit to the image.. Compute the pixel location of the galaxy center by fitting a 2d Gaussian as implimented by the photutils package. Parameters ----------------- ap_guess_center : dict, default None user provided starting point for center fitting. Center should be formatted as: .. code-block:: python {'x':float, 'y': float} , where the floats are the center coordinates in pixels. If not given, Autoprof will default to a guess of the image center. ap_set_center : dict, default None user provided fixed center for rest of calculations. Center should be formatted as: .. code-block:: python {'x':float, 'y': float} , where the floats are the center coordinates in pixels. If not given, Autoprof will default to a guess of the image center. ap_centeringring : int, default 50 Size of ring to use when finding galaxy center, in units of PSF. Larger rings will give the 2D fit more data to work with and allow for the starting position to be further from the true galaxy center. Smaller rings will include fewer spurious objects, and can stop the 2D fit from being distracted by larger nearby objects/galaxies. Notes ---------- :References: - 'background' - 'psf fwhm' Returns ------- IMG : ndarray Unaltered galaxy image results : dict .. code-block:: python {'center': {'x': , # x coordinate of the center (pix) 'y': }, # y coordinate of the center (pix) 'auxfile center': # optional, message for aux file to record galaxy center (string) } """ current_center = {"x": IMG.shape[1] / 2, "y": IMG.shape[0] / 2} if "ap_guess_center" in options: current_center = deepcopy(options["ap_guess_center"]) logging.info("%s: Center initialized by user: %s" % (options["ap_name"], str(current_center))) if "ap_set_center" in options: logging.info("%s: Center set by user: %s" % (options["ap_name"], str(options["ap_set_center"]))) return IMG, {"center": deepcopy(options["ap_set_center"])} # Create mask to focus centering algorithm on the center of the image ranges = [ [ max( 0, int(current_center["x"] - (options["ap_centeringring"] if "ap_centeringring" in options else 50) * results["psf fwhm"]), ), min( IMG.shape[1], int(current_center["x"] + (options["ap_centeringring"] if "ap_centeringring" in options else 50) * results["psf fwhm"]), ), ], [ max( 0, int(current_center["y"] - (options["ap_centeringring"] if "ap_centeringring" in options else 50) * results["psf fwhm"]), ), min( IMG.shape[0], int(current_center["y"] + (options["ap_centeringring"] if "ap_centeringring" in options else 50) * results["psf fwhm"]), ), ], ] centralize_mask = np.ones(IMG.shape, dtype=bool) centralize_mask[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]] = False try: x, y = centroid_2dg(IMG - results["background"], mask=centralize_mask) current_center = {"x": x, "y": y} except: logging.warning( "%s: 2D Gaussian center finding failed! using image center (or guess)." % options["ap_name"]) # Plot center value for diagnostic purposes if "ap_doplot" in options and options["ap_doplot"]: plt.imshow( np.clip(IMG - results["background"], a_min=0, a_max=None), origin="lower", cmap="Greys_r", norm=ImageNormalize(stretch=LogStretch()), ) plt.plot( [current_center["x"]], [current_center["y"]], marker="x", markersize=10, color="y", ) plt.savefig("%scenter_vis_%s.jpg" % ( options["ap_plotpath"] if "ap_plotpath" in options else "", options["ap_name"], )) plt.close() logging.info("%s Center found: x %.1f, y %.1f" % (options["ap_name"], x, y)) return IMG, { "center": current_center, "auxfile center": "center x: %.2f pix, y: %.2f pix" % (current_center["x"], current_center["y"]), }
def dataquality(cubeslist, maskslist): """ Perform bunch of QA checks to asses the final data quality of reduction """ import os import numpy as np from astropy.io import fits from mypython.fits import pyregmask as msk from mypython.ifu import muse_utils as mutil from mypython.ifu import muse_source as msrc from matplotlib.backends.backend_pdf import PdfPages import matplotlib.pyplot as plt import matplotlib.cm as cm from astropy.stats import sigma_clipped_stats try: from photutils import CircularAperture, aperture_photometry,\ data_properties, properties_table, centroids except: print("To run checks need photutils package") return print("Perform QA checks...") #make QA folder if not os.path.exists('QA'): os.makedirs('QA') #cube names cname = "COMBINED_CUBE.fits" iname = "COMBINED_IMAGE.fits" #first identify bright sources in final white image catsrc = msrc.findsources(iname, cname, check=True, output='QA', nsig=5, minarea=20) #make rsdss images if not (os.path.isfile('QA/sdssr.fits')): mutil.cube2img(cname, write='QA/sdssr.fits', wrange=None, helio=0, filt=129) rsdssall = fits.open('QA/sdssr.fits') segmask = fits.open('QA/segmap.fits') whiteref = fits.open(iname) #select round and bright objects shapesrc = catsrc['a'] / catsrc['b'] roundsrc = catsrc[np.where((shapesrc < 1.1) & (catsrc['cflux'] > 50))] imgfield = fits.open(iname) rms = np.std(imgfield[0].data) #perform aperture photometry on rband image - data already skysub positions = [roundsrc['x'], roundsrc['y']] apertures = CircularAperture(positions, r=10.) phot_table = aperture_photometry(rsdssall[1].data, apertures) phot_table_white = aperture_photometry(whiteref[0].data, apertures) rmag = -2.5 * np.log10( phot_table['aperture_sum']) + rsdssall[0].header['ZPAB'] wmag = -2.5 * np.log10(phot_table_white['aperture_sum']) #find FWHM on rband image fwhm = np.zeros(len(rmag)) for ii in range(len(rmag)): subdata=rsdssall[1].data[roundsrc['y'][ii]-10:roundsrc['y'][ii]+10,\ roundsrc['x'][ii]-10:roundsrc['x'][ii]+10] tdfit = centroids.fit_2dgaussian(subdata, error=None, mask=None) fwhm[ii] = 2.3548 * 0.5 * (tdfit.x_stddev + tdfit.y_stddev ) * rsdssall[0].header['PC2_2'] * 3600. #find rms of cube - mask sources and add edge buffer maskwbuffer = np.copy(segmask[1].data) maskwbuffer[0:30, :] = 9999 maskwbuffer[-31:-1, :] = 9999 maskwbuffer[:, 0:30] = 9999 maskwbuffer[:, -31:-1] = 9999 cwrms, crms = mutil.cubestat(cname, mask=maskwbuffer) #open diagnostic output with PdfPages('QA/QAfile.pdf') as pdf: ########################### #display field with r mag # ########################### plt.figure(figsize=(10, 10)) plt.imshow(imgfield[0].data, origin='low', clim=[-0.5 * rms, 0.5 * rms], cmap='gray_r') #mark round soruces plt.scatter(roundsrc['x'], roundsrc['y'], color='red') for ii in range(len(rmag)): plt.text(roundsrc['x'][ii], roundsrc['y'][ii], " " + str(rmag[ii]), color='red') plt.title('Round sources with SDSS r mag') pdf.savefig() # saves the current figure into a pdf page plt.close() ########################### #display FWHM # ########################### plt.figure(figsize=(10, 10)) plt.scatter(rmag, fwhm, color='red') plt.xlabel('Source rmag') plt.ylabel('FWHM (arcsec)') plt.title('Median FWHM {}'.format(np.median(fwhm))) pdf.savefig() # saves the current figure into a pdf page plt.close() ########################### #check centroid # ########################### plt.figure(figsize=(10, 10)) #loop on exposures for tmpc in open(cubeslist, 'r'): thisob = tmpc.split('/')[1] thisexp = tmpc.split('_')[3] wname = '../{}/Proc/DATACUBE_FINAL_LINEWCS_{}_white2.fits'.format( thisob, thisexp) wfits = fits.open(wname) #now loop on sources delta_x = np.zeros(len(rmag)) delta_y = np.zeros(len(rmag)) for ii in range(len(rmag)): subdata=wfits[0].data[roundsrc['y'][ii]-10:roundsrc['y'][ii]+10,\ roundsrc['x'][ii]-10:roundsrc['x'][ii]+10] x1, y1 = centroids.centroid_2dg(subdata) delta_x[ii] = 10.5 - x1 delta_y[ii] = 10.5 - y1 #plot for this subunit plt.scatter(delta_x * rsdssall[0].header['PC2_2'] * 3600., delta_y * rsdssall[0].header['PC2_2'] * 3600.) plt.xlabel('Delta x (arcsec)') plt.ylabel('Delta y (arcsec)') plt.title('Check exposure aligment') pdf.savefig() # saves the current figure into a pdf page plt.close() ########################### #check fluxing # ########################### #make a check on fluxing plt.figure(figsize=(10, 10)) #loop on exposures for tmpc in open(cubeslist, 'r'): thisob = tmpc.split('/')[1] thisexp = tmpc.split('_')[3] wname = '../{}/Proc/DATACUBE_FINAL_LINEWCS_{}_white2.fits'.format( thisob, thisexp) wfits = fits.open(wname) phot_this_white = aperture_photometry(wfits[0].data, apertures) wmag_this = -2.5 * np.log10(phot_this_white['aperture_sum']) #plot for this subunit ii = np.argsort(rmag) dd = wmag - wmag_this plt.plot(rmag[ii], dd[ii], label=thisob + thisexp) plt.xlabel('SDSS R mag') plt.ylabel('Delta White Mag') plt.title('Check exposure photometry') plt.legend() pdf.savefig() # saves the current figure into a pdf page plt.close() #display rms stats + compute stats over cubes plt.figure(figsize=(10, 10)) plt.semilogy(cwrms, crms, label='Coadd') for tmpc in open(cubeslist, 'r'): cwrms_this, crms_this = mutil.cubestat(tmpc.strip(), mask=maskwbuffer) plt.semilogy(cwrms_this, crms_this, label=tmpc.split('/')[1] + tmpc.split('_')[3]) plt.xlabel('Wave (A)') plt.ylabel('RMS (SB units)') plt.legend() plt.title('RMS in cubex') pdf.savefig() # saves the current figure into a pdf page plt.close()
def deblend_stars(image, ew_map=None, sigma_thresh=3., fwhm_pix=3., npixels=15, nlevels=32, contrast=0.01, size_thresh=2., dist_to_center_thresh=10., area_thresh=200., ew_thresh=20., output_dir='', save_fits=False): """ This method estimates foreground stars over a given image. Additional constrains can be applied by providing an EW(Ha) map, preventing giant HII regions to be classified as stars. --------------------------------------------------------------------------- input params: - image: (2D array) Flux image used to deblend the sources - ew_map (optional): (2D array) EW(Ha) map used to further constrain the sources - sigma_thresh (float, default=3.0): number of stddev for estimating the signal threshold - fwhm_pix (float, default=3.0): FWHM for Gaussian smoothing prior to source detection - npixels (int, default=15): The number of connected pixels, each greater than threshold, that an object must have to be detected. - nlevels (int, default=15): The number of multi-thresholding levels to use - contrast (float, default=0.01) - size_thres (float, defualt=2.) - - - output_dir (optional, defatult=''): (str) directory where the results will be saved - save_filts (optional, default=False): Set True for saving the stellar masks """ if ew_map is None: ew = np.zeros_like(image) rows, columns = image.shape centerx, centery = rows // 2, columns // 2 XX, YY = np.meshgrid(np.arange(columns), np.arange(rows)) # Estimating the image signal threshold threshold = detect_threshold(image, nsigma=sigma_thresh) # Kernel smoothing # sigma_pixels = FWHM_pixels / conversion_factor kernel_sigma = fwhm_pix / (2.0 * np.sqrt(2.0 * np.log(2.0))) kernel = Gaussian2DKernel(kernel_sigma, x_size=int(fwhm_pix), y_size=int(fwhm_pix)) kernel.normalize() # Source detection segm = detect_sources(image, threshold, npixels, filter_kernel=kernel) # Source deblending segm_deblend = deblend_sources(image, segm, npixels=npixels, filter_kernel=kernel, nlevels=nlevels, contrast=0.01) deblended_areas = segm_deblend.areas stellar_masks = [] if deblended_areas.size > 1: mask = segm_deblend.data cmap = segm_deblend.make_cmap() plt.figure() plt.imshow(segm_deblend, origin='lower', cmap=cmap) plt.colorbar() plt.contour(image, levels=20, colors='k', origin='lower') for ii in range(deblended_areas.size): plt.annotate(r'Pixel area: {}'.format(deblended_areas[ii]), xy=(.02, .95 - ii * 0.05), xycoords='axes fraction', ha='left', va='top', color=cmap(ii + 1), fontsize=9) plt.savefig(output_dir + 'deblending_map.png') plt.close() for ii in range(deblended_areas.size): source = mask == ii + 1 source_area = deblended_areas[ii] source_image = np.copy(image) source_image[~source] = 0 pos_x, pos_y = centroid_2dg(source_image) dist_to_center = np.sqrt((pos_x - centerx)**2 + (pos_y - centery)**2) amplitude_guess = source_image.max() initial_guess = [amplitude_guess, pos_x, pos_y, 3, 3, 0] try: popt, pcov = curve_fit( gaussian2d, [XX, YY], source_image.ravel(), p0=initial_guess, # bounds=(0, [amplitude_guess*2, 80, 80, 5, 5]) ) except: print('Error fitting gaussian to data') continue popt = np.abs(popt) sigmax, sigmay = popt[3], popt[4] gaussian = gaussian2d([XX, YY], *popt).reshape(image.shape) level = gaussian2d([ popt[1] + 3 * max([popt[3], 2]), popt[2] + 3 * max([popt[4], 2]) ], *popt) central_pixels = gaussian2d([popt[1] + sigmax, popt[2] + sigmay], *popt) star_mask = gaussian > level central_star_mask = gaussian > central_pixels ew_source = ew[central_star_mask] median_ew = np.nanmedian(ew_source) if np.isnan(median_ew): median_ew = 0. ellipticity = sigmax / sigmay # chi2 = np.sum(chi2[star_mask]) if (sigmax < size_thresh) & (sigmay < size_thresh): # &(ellipticity<1.5)&(ellipticity>0.5) if (dist_to_center < dist_to_center_thresh) & ( source_area < area_thresh) & (median_ew <= ew_thresh): is_star = True stellar_masks.append(star_mask) elif (dist_to_center > dist_to_center_thresh) & (median_ew <= ew_thresh): is_star = True stellar_masks.append(star_mask) else: is_star = False else: is_star = False plt.figure(figsize=(4, 4)) plt.subplot(221) plt.plot(XX[0, :], np.sum(source_image, axis=0), 'k') plt.plot(XX[0, :], np.sum(gaussian, axis=0), 'r') plt.annotate(r'$\sigma_x$={:5.3}'.format(sigmax), xy=(.1, .9), xycoords='axes fraction', ha='left', va='top') plt.subplot(224) plt.plot(np.sum(source_image, axis=1), YY[:, 0], 'k') plt.plot(np.sum(gaussian, axis=1), YY[:, 0], 'r') plt.annotate(r'$\sigma_y$={:5.3}'.format(sigmay), xy=(.9, .9), xycoords='axes fraction', ha='right', va='top') plt.subplot(223) plt.imshow(image, cmap='gist_earth', aspect='auto', origin='lower') if is_star: plt.plot(pos_x, pos_y, '*', c='lime', markersize=10) else: plt.plot(pos_x, pos_y, '+', c='lime', markersize=10) plt.contour(gaussian, colors='r', levels=level, linewidths=2) plt.annotate(r'$\sigma_x/\sigma_y$={:5.3}'.format(ellipticity), xy=(.05, .99), xycoords='axes fraction', ha='left', va='top', fontsize=8) plt.annotate(r'$EW(H\alpha)_{50}$' + '={:5.3}'.format(median_ew), xy=(.05, .90), xycoords='axes fraction', ha='left', va='top', fontsize=8) plt.savefig(output_dir + 'detection_' + str(ii) + '.png') plt.close() # plt.xlim(pos_x-15,pos_x+15) # plt.ylim(pos_y-15,pos_y+15) stellar_masks = np.array(stellar_masks) if stellar_masks.size > 0: print('-->', stellar_masks.shape[0], ' stars detected') total_mask = np.zeros_like(image, dtype=bool) for i in range(stellar_masks.shape[0]): total_mask = total_mask | np.array(stellar_masks[i, :, :], dtype=bool) if save_fits: fits_path = output_dir + 'stellar_mask.fits' hdr = fits.Header() hdr['COMMENT'] = "Stellar masks" image_list = [] image_list.append(fits.PrimaryHDU(header=hdr)) for ii in range(stellar_masks.shape[0]): image_list.append( fits.ImageHDU(np.array(stellar_masks[ii, :, :], dtype=int))) image_list.append(np.array(total_mask, dtype=int)) hdu = fits.HDUList(image_list) hdu.writeto(fits_path, overwrite=True) hdu.close() print('File saved as: ' + fits_path) return total_mask, stellar_masks.shape[0] else: return np.zeros_like(image, dtype=bool), 0
def centroider(target, sources, output_plots=False, gif=False, restore=False, box_w=8): matplotlib.use('TkAgg') plt.ioff() t1 = time.time() pines_path = pines_dir_check() short_name = short_name_creator(target) kernel = Gaussian2DKernel(x_stddev=1) #For fixing nans in cutouts. #If restore == True, read in existing output and return. if restore: centroid_df = pd.read_csv( pines_path / ('Objects/' + short_name + '/sources/target_and_references_centroids.csv'), converters={ 'X Centroids': eval, 'Y Centroids': eval }) print('Restoring centroider output from {}.'.format( pines_path / ('Objects/' + short_name + '/sources/target_and_references_centroids.csv'))) print('') return centroid_df #Create subdirectories in sources folder to contain output plots. if output_plots: subdirs = glob( str(pines_path / ('Objects/' + short_name + '/sources')) + '/*/') #Delete any source directories that are already there. for name in subdirs: shutil.rmtree(name) #Create new source directories. for name in sources['Name']: source_path = ( pines_path / ('Objects/' + short_name + '/sources/' + name + '/')) os.mkdir(source_path) #Read in extra shifts, in case the master image wasn't used for source detection. extra_shift_path = pines_path / ('Objects/' + short_name + '/sources/extra_shifts.txt') extra_shifts = pd.read_csv(extra_shift_path, delimiter=' ', names=['Extra X shift', 'Extra Y shift']) extra_x_shift = extra_shifts['Extra X shift'][0] extra_y_shift = extra_shifts['Extra Y shift'][0] np.seterr( divide='ignore', invalid='ignore' ) #Suppress some warnings we don't care about in median combining. #Get list of reduced files for target. reduced_path = pines_path / ('Objects/' + short_name + '/reduced') reduced_filenames = natsort.natsorted( [x.name for x in reduced_path.glob('*red.fits')]) reduced_files = np.array([reduced_path / i for i in reduced_filenames]) #Declare a new dataframe to hold the centroid information for all sources we want to track. columns = [] columns.append('Filename') columns.append('Seeing') columns.append('Time (JD UTC)') columns.append('Airmass') #Add x/y positions and cenroid flags for every tracked source for i in range(0, len(sources)): columns.append(sources['Name'][i] + ' Image X') columns.append(sources['Name'][i] + ' Image Y') columns.append(sources['Name'][i] + ' Cutout X') columns.append(sources['Name'][i] + ' Cutout Y') columns.append(sources['Name'][i] + ' Centroid Warning') centroid_df = pd.DataFrame(index=range(len(reduced_files)), columns=columns) log_path = pines_path / ('Logs/') log_dates = np.array( natsort.natsorted( [x.name.split('_')[0] for x in log_path.glob('*.txt')])) #Make sure we have logs for all the nights of these data. Need them to account for image shifts. nights = list(set([i.name.split('.')[0] for i in reduced_files])) for i in nights: if i not in log_dates: print('ERROR: {} not in {}. Download it from the PINES server.'. format(i + '_log.txt', log_path)) pdb.set_trace() shift_tolerance = 2.0 #Number of pixels that the measured centroid can be away from the expected position in either x or y before trying other centroiding algorithms. for i in range(len(sources)): #Get the initial source position. x_pos = sources['Source Detect X'][i] y_pos = sources['Source Detect Y'][i] print('') print( 'Getting centroids for {}, ({:3.1f}, {:3.1f}) in source detection image. Source {} of {}.' .format(sources['Name'][i], x_pos, y_pos, i + 1, len(sources))) if output_plots: print('Saving centroid plots to {}.'.format( pines_path / ('Objects/' + short_name + '/sources/' + sources['Name'][i] + '/'))) pbar = ProgressBar() for j in pbar(range(len(reduced_files))): centroid_df[sources['Name'][i] + ' Centroid Warning'][j] = 0 file = reduced_files[j] image = fits.open(file)[0].data #Get the measured image shift for this image. log = pines_log_reader(log_path / (file.name.split('.')[0] + '_log.txt')) log_ind = np.where(log['Filename'] == file.name.split('_')[0] + '.fits')[0][0] x_shift = float(log['X shift'][log_ind]) y_shift = float(log['Y shift'][log_ind]) #Save the filename for readability. Save the seeing for use in variable aperture photometry. Save the time for diagnostic plots. if i == 0: centroid_df['Filename'][j] = file.name.split('_')[0] + '.fits' centroid_df['Seeing'][j] = log['X seeing'][log_ind] time_str = fits.open(file)[0].header['DATE-OBS'] #Correct some formatting issues that can occur in Mimir time stamps. if time_str.split(':')[-1] == '60.00': time_str = time_str[0:14] + str( int(time_str.split(':')[-2]) + 1) + ':00.00' elif time_str.split(':')[-1] == '010.00': time_str = time_str[0:17] + time_str.split(':')[-1][1:] centroid_df['Time (JD UTC)'][j] = julian.to_jd( datetime.datetime.strptime(time_str, '%Y-%m-%dT%H:%M:%S.%f')) centroid_df['Airmass'][j] = log['Airmass'][log_ind] nan_flag = False #Flag indicating if you should not trust the log's shifts. Set to true if x_shift/y_shift are 'nan' or > 30 pixels. #If bad shifts were measured for this image, skip. if log['Shift quality flag'][log_ind] == 1: continue if np.isnan(x_shift) or np.isnan(y_shift): x_shift = 0 y_shift = 0 nan_flag = True #If there are clouds, shifts could have been erroneously high...just zero them? if abs(x_shift) > 200: #x_shift = 0 nan_flag = True if abs(y_shift) > 200: #y_shift = 0 nan_flag = True #Apply the shift. NOTE: This relies on having accurate x_shift and y_shift values from the log. #If they're incorrect, the cutout will not be in the right place. #x_pos = sources['Source Detect X'][i] - x_shift + extra_x_shift #y_pos = sources['Source Detect Y'][i] + y_shift - extra_y_shift x_pos = sources['Source Detect X'][i] - (x_shift - extra_x_shift) y_pos = sources['Source Detect Y'][i] + (y_shift - extra_y_shift) #TODO: Make all this its own function. #Cutout around the expected position and interpolate over any NaNs (which screw up source detection). cutout = interpolate_replace_nans( image[int(y_pos - box_w):int(y_pos + box_w) + 1, int(x_pos - box_w):int(x_pos + box_w) + 1], kernel=Gaussian2DKernel(x_stddev=0.5)) #interpolate_replace_nans struggles with edge pixels, so shave off edge_shave pixels in each direction of the cutout. edge_shave = 1 cutout = cutout[edge_shave:len(cutout) - edge_shave, edge_shave:len(cutout) - edge_shave] vals, lower, upper = sigmaclip( cutout, low=1.5, high=2.5) #Get sigma clipped stats on the cutout med = np.nanmedian(vals) std = np.nanstd(vals) try: centroid_x_cutout, centroid_y_cutout = centroid_2dg( cutout - med) #Perform centroid detection on the cutout. except: pdb.set_trace() centroid_x = centroid_x_cutout + int( x_pos ) - box_w + edge_shave #Translate the detected centroid from the cutout coordinates back to the full-frame coordinates. centroid_y = centroid_y_cutout + int(y_pos) - box_w + edge_shave # if i == 0: # qp(cutout) # plt.plot(centroid_x_cutout, centroid_y_cutout, 'rx') # # qp(image) # # plt.plot(centroid_x, centroid_y, 'rx') # pdb.set_trace() #If the shifts in the log are not 'nan' or > 200 pixels, check if the measured shifts are within shift_tolerance pixels of the expected position. # If they aren't, try alternate centroiding methods to try and find it. #Otherwise, use the shifts as measured with centroid_1dg. PINES_watchdog likely failed while observing, and we don't expect the centroids measured here to actually be at the expected position. if not nan_flag: #Try a 2D Gaussian detection. if (abs(centroid_x - x_pos) > shift_tolerance) or ( abs(centroid_y - y_pos) > shift_tolerance): centroid_x_cutout, centroid_y_cutout = centroid_2dg( cutout - med) centroid_x = centroid_x_cutout + int(x_pos) - box_w centroid_y = centroid_y_cutout + int(y_pos) - box_w #If that fails, try a COM detection. if (abs(centroid_x - x_pos) > shift_tolerance) or ( abs(centroid_y - y_pos) > shift_tolerance): centroid_x_cutout, centroid_y_cutout = centroid_com( cutout - med) centroid_x = centroid_x_cutout + int(x_pos) - box_w centroid_y = centroid_y_cutout + int(y_pos) - box_w #If that fails, try masking source and interpolate over any bad pixels that aren't in the bad pixel mask, then redo 1D gaussian detection. if (abs(centroid_x - x_pos) > shift_tolerance) or ( abs(centroid_y - y_pos) > shift_tolerance): mask = make_source_mask(cutout, nsigma=4, npixels=5, dilate_size=3) vals, lo, hi = sigmaclip(cutout[~mask]) bad_locs = np.where((mask == False) & ( (cutout > hi) | (cutout < lo))) cutout[bad_locs] = np.nan cutout = interpolate_replace_nans( cutout, kernel=Gaussian2DKernel(x_stddev=0.5)) centroid_x_cutout, centroid_y_cutout = centroid_1dg( cutout - med) centroid_x = centroid_x_cutout + int(x_pos) - box_w centroid_y = centroid_y_cutout + int(y_pos) - box_w #Try a 2D Gaussian detection on the interpolated cutout if (abs(centroid_x - x_pos) > shift_tolerance) or ( abs(centroid_y - y_pos) > shift_tolerance): centroid_x_cutout, centroid_y_cutout = centroid_2dg( cutout - med) centroid_x = centroid_x_cutout + int( x_pos) - box_w centroid_y = centroid_y_cutout + int( y_pos) - box_w #Try a COM on the interpolated cutout. if (abs(centroid_x - x_pos) > shift_tolerance ) or (abs(centroid_y - y_pos) > shift_tolerance): centroid_x_cutout, centroid_y_cutout = centroid_com( cutout) centroid_x = centroid_x_cutout + int( x_pos) - box_w centroid_y = centroid_y_cutout + int( y_pos) - box_w #Last resort: try cutting off the edge of the cutout. Edge pixels can experience poor interpolation, and this sometimes helps. if (abs(centroid_x - x_pos) > shift_tolerance) or ( abs(centroid_y - y_pos) > shift_tolerance): cutout = cutout[1:-1, 1:-1] centroid_x_cutout, centroid_y_cutout = centroid_1dg( cutout - med) centroid_x = centroid_x_cutout + int( x_pos) - box_w + 1 centroid_y = centroid_y_cutout + int( y_pos) - box_w + 1 #Try with a 2DG if (abs(centroid_x - x_pos) > shift_tolerance) or ( abs(centroid_y - y_pos) > shift_tolerance): centroid_x_cutout, centroid_y_cutout = centroid_2dg( cutout - med) centroid_x = centroid_x_cutout + int( x_pos) - box_w + 1 centroid_y = centroid_y_cutout + int( y_pos) - box_w + 1 #If ALL that fails, report the expected position as the centroid. if (abs(centroid_x - x_pos) > shift_tolerance) or ( abs(centroid_y - y_pos) > shift_tolerance): print( 'WARNING: large centroid deviation measured, returning predicted position' ) print('') centroid_df[ sources['Name'][i] + ' Centroid Warning'][j] = 1 centroid_x = x_pos centroid_y = y_pos #pdb.set_trace() #Check that your measured position is actually on the detector. if (centroid_x < 0) or (centroid_y < 0) or (centroid_x > 1023) or ( centroid_y > 1023): #Try a quick mask/interpolation of the cutout. mask = make_source_mask(cutout, nsigma=3, npixels=5, dilate_size=3) vals, lo, hi = sigmaclip(cutout[~mask]) bad_locs = np.where((mask == False) & ((cutout > hi) | (cutout < lo))) cutout[bad_locs] = np.nan cutout = interpolate_replace_nans( cutout, kernel=Gaussian2DKernel(x_stddev=0.5)) centroid_x, centroid_y = centroid_2dg(cutout - med) centroid_x += int(x_pos) - box_w centroid_y += int(y_pos) - box_w if (centroid_x < 0) or (centroid_y < 0) or ( centroid_x > 1023) or (centroid_y > 1023): print( 'WARNING: large centroid deviation measured, returning predicted position' ) print('') centroid_df[sources['Name'][i] + ' Centroid Warning'][j] = 1 centroid_x = x_pos centroid_y = y_pos #pdb.set_trace() #Check to make sure you didn't measure nan's. if np.isnan(centroid_x): centroid_x = x_pos print( 'NaN returned from centroid algorithm, defaulting to target position in source_detct_image.' ) if np.isnan(centroid_y): centroid_y = y_pos print( 'NaN returned from centroid algorithm, defaulting to target position in source_detct_image.' ) #Record the image and relative cutout positions. centroid_df[sources['Name'][i] + ' Image X'][j] = centroid_x centroid_df[sources['Name'][i] + ' Image Y'][j] = centroid_y centroid_df[sources['Name'][i] + ' Cutout X'][j] = centroid_x_cutout centroid_df[sources['Name'][i] + ' Cutout Y'][j] = centroid_y_cutout if output_plots: #Plot lock_x = int(centroid_df[sources['Name'][i] + ' Image X'][0]) lock_y = int(centroid_df[sources['Name'][i] + ' Image Y'][0]) norm = ImageNormalize(data=cutout, interval=ZScaleInterval()) plt.imshow(image, origin='lower', norm=norm) plt.plot(centroid_x, centroid_y, 'rx') ap = CircularAperture((centroid_x, centroid_y), r=5) ap.plot(lw=2, color='b') plt.ylim(lock_y - 30, lock_y + 30 - 1) plt.xlim(lock_x - 30, lock_x + 30 - 1) plt.title('CENTROID DIAGNOSTIC PLOT\n' + sources['Name'][i] + ', ' + reduced_files[j].name + ' (image ' + str(j + 1) + ' of ' + str(len(reduced_files)) + ')', fontsize=10) plt.text(centroid_x, centroid_y + 0.5, '(' + str(np.round(centroid_x, 1)) + ', ' + str(np.round(centroid_y, 1)) + ')', color='r', ha='center') plot_output_path = ( pines_path / ('Objects/' + short_name + '/sources/' + sources['Name'][i] + '/' + str(j).zfill(4) + '.jpg')) plt.gca().set_axis_off() plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0) plt.margins(0, 0) plt.gca().xaxis.set_major_locator(plt.NullLocator()) plt.gca().yaxis.set_major_locator(plt.NullLocator()) plt.savefig(plot_output_path, bbox_inches='tight', pad_inches=0, dpi=150) plt.close() if gif: gif_path = (pines_path / ('Objects/' + short_name + '/sources/' + sources['Name'][i] + '/')) gif_maker(path=gif_path, fps=10) output_filename = pines_path / ( 'Objects/' + short_name + '/sources/target_and_references_centroids.csv') #centroid_df.to_csv(pines_path/('Objects/'+short_name+'/sources/target_and_references_centroids.csv')) print('Saving centroiding output to {}.'.format(output_filename)) with open(output_filename, 'w') as f: for j in range(len(centroid_df)): #Write the header line. if j == 0: f.write('{:<17s}, '.format('Filename')) f.write('{:<15s}, '.format('Time (JD UTC)')) f.write('{:<6s}, '.format('Seeing')) f.write('{:<7s}, '.format('Airmass')) for i in range(len(sources['Name'])): n = sources['Name'][i] if i != len(sources['Name']) - 1: f.write( '{:<23s}, {:<23s}, {:<24s}, {:<24s}, {:<34s}, '. format(n + ' Image X', n + ' Image Y', n + ' Cutout X', n + ' Cutout Y', n + ' Centroid Warning')) else: f.write( '{:<23s}, {:<23s}, {:<24s}, {:<24s}, {:<34s}\n'. format(n + ' Image X', n + ' Image Y', n + ' Cutout X', n + ' Cutout Y', n + ' Centroid Warning')) #Write in the data lines. try: f.write('{:<17s}, '.format(centroid_df['Filename'][j])) f.write('{:<15.7f}, '.format(centroid_df['Time (JD UTC)'][j])) f.write('{:<6.1f}, '.format(float(centroid_df['Seeing'][j]))) f.write('{:<7.2f}, '.format(centroid_df['Airmass'][j])) except: pdb.set_trace() for i in range(len(sources['Name'])): n = sources['Name'][i] if i != len(sources['Name']) - 1: format_string = '{:<23.4f}, {:<23.4f}, {:<24.4f}, {:<24.4f}, {:<34d}, ' else: format_string = '{:<23.4f}, {:<23.4f}, {:<24.4f}, {:<24.4f}, {:<34d}\n' f.write( format_string.format( centroid_df[n + ' Image X'][j], centroid_df[n + ' Image Y'][j], centroid_df[n + ' Cutout X'][j], centroid_df[n + ' Cutout Y'][j], centroid_df[n + ' Centroid Warning'][j])) np.seterr(divide='warn', invalid='warn') print('') print('centroider runtime: {:.2f} minutes.'.format( (time.time() - t1) / 60)) print('') return centroid_df