def elliptical_mask(self, scale=2): ell = EllipticalAperture( self.centroid, scale * self.semimajor_axis, scale * self.semiminor_axis, np.deg2rad(self.theta) ) ell_mask = ell.to_mask()[0].to_image(self.image.shape).astype(bool) return ell_mask
def apply_ellipse_cutout(self, array): """Applies an elliptical cutout to any given array according to galaxy specifications. This method uses the `astropy` Elliptical Aperture module to make an elliptical cutout of the galaxy by taking into account the Petrosian effective radius, inclination, and b/a ratio for a galaxy. A cutout from the centre of the array is then returned with this requirement applied. The primary reason for working with cutouts is speed of analysis. Args: array (array): Any array, which is an emission map with a modified mask in our use. Returns: array (array): An elliptical cutout of the array """ if self.ellipical_mask_loaded == False: x, y = self.hamap.shape ellipical_aperture = EllipticalAperture( [(x - 1) / 2, (y - 1) / 2], np.ceil(self.eff_rad), np.ceil(self.eff_rad * self.elpetro_ba), self.theta) self.mask_ellipical = ellipical_aperture.to_mask(method='exact')[0] self.ellipical_mask_loaded = True return self.mask_ellipical.cutout(array)
def _set_aperture_elements(self): """ Set internal aperture elements. ``self._ap_rect``, ``self.ap_el_1``, ``self.ap_el_2`` and their ``_in`` counterparts are always made by ``np.atleast_2d(self.position)``, so their results are always in the ``N x 2`` shape. """ if hasattr(self, 'a'): w = self.w a = self.a b = self.b h = self.h theta = self.theta elif hasattr(self, 'a_in'): # annulus w = self.w a = self.a_out b = self.b_out h = self.h_out theta = self.theta else: raise ValueError('Cannot determine the aperture shape.') # positions only accepted in the shape of (N, 2), so shape[0] # gives the number of positions: pos = np.atleast_2d(self.positions) self.offset = np.array([w * np.cos(theta) / 2, w * np.sin(theta) / 2]) offsets = np.repeat(np.array([ self.offset, ]), pos.shape[0], 0) # aperture elements for aperture, # OUTER aperture elements for annulus: self._ap_rect = RectangularAperture(positions=pos, w=w, h=h, theta=theta) self._ap_el_1 = EllipticalAperture(positions=pos - offsets, a=a, b=b, theta=theta) self._ap_el_2 = EllipticalAperture(positions=pos + offsets, a=a, b=b, theta=theta) if hasattr(self, 'a_in'): # inner components of annulus self._ap_rect_in = RectangularAperture(positions=pos, w=self.w, h=self.h_in, theta=self.theta) self._ap_el_1_in = EllipticalAperture(positions=pos - offsets, a=self.a_in, b=self.b_in, theta=self.theta) self._ap_el_2_in = EllipticalAperture(positions=pos + offsets, a=self.a_in, b=self.b_in, theta=self.theta)
def ap_phot_plot(xcen, ycen, image, e1, e2, theta, zp): apertures = EllipticalAperture([(xcen, ycen)], e1, e2, theta) phot_table = aperture_photometry(image, apertures) phot_table['aperture_sum'].info.format = '%.8g' fig1 = plt.figure() apertures.plot(color='white', lw=2) plt.imshow(image) plt.show() plt.close() mag = -2.5 * np.log10(phot_table['aperture_sum'][0]) + zp return phot_table['aperture_sum'][0], mag
def elliptical_mask(self, scale=2, use_sersic_model=False): if use_sersic_model: pars = self.fit_params ell = EllipticalAperture([pars['x_0'], pars['y_0']], scale * pars['r_eff'], scale * pars['r_eff'] * (1 - pars['ellip']), np.deg2rad(pars['theta'])) else: ell = EllipticalAperture(self.centroid, scale * self.semimajor_axis, scale * self.semiminor_axis, self.theta.to('radian').value) ell_mask = ell.to_mask().to_image(self.image.shape).astype(bool) return ell_mask
def radial_elliptical_aperture(position, r, elong=1., theta=0.): """ Helper function given a radius, elongation and theta, will make an elliptical aperture. Parameters ---------- position : tuple (x, y) coords for center of aperture. r : int or float Semi-major radius of the aperture. elong : float Elongation. theta : float Orientation in rad. Returns ------- EllipticalAperture """ a, b = r, r / elong return EllipticalAperture(position, a, b, theta=theta)
def transform_xy_ellipse(centerX, centerY, aAxis, bAxis, cubeObj): """ Update rectangle data widgets and image object attributes :param float centerX: center x coordinate :param float centerY: center y coordinate :param float a: long axis value :param float b: short axis value :param object cubeObj: current data from the cube :return list fValues: flux values list for each wavelength :return list wValues: wavelenght list for each slice :return Aperture_Photometry aperture: aperture of the ellipse """ fValues = [] #Because it gets all the flux on a pixel, it needs to get the area of it rather #the sum of it pixelArea = (cubeObj.cubeRAValue * 3600.) * (cubeObj.cubeDValue * 3600.) position = [(centerX, centerY)] aperture = EllipticalAperture(position, aAxis / 2, bAxis / 2) for i in range(cubeObj.maxSlice): phot_table = aperture_photometry(cubeObj.data_cube[i], aperture, method='exact') fValues.append(phot_table['aperture_sum'][0] * pixelArea) wValues = [ ((w + 1) - cubeObj.cubeZCPix) * cubeObj.cubeWValue + cubeObj.cubeZCRVal for w in range(len(fValues)) ] return fValues, wValues, aperture
def create_aperture(aperture): """ Function to create a circular or elliptical aperture. :param aperture: Dictionary with the aperture properties. The aperture 'type' can be 'circular' or 'elliptical' (str). Both types of apertures require a position, 'pos_x' and 'pos_y' (float), where the aperture is placed. The circular aperture requires a 'radius' (in pixels, float) and the elliptical aperture requires a 'semimajor' and 'semiminor' axis (in pixels, float), and an 'angle' (deg). The rotation angle in degrees of the semimajor axis from the positive x axis. The rotation angle increases counterclockwise. :type aperture: dict :return: Aperture object. :rtype: photutils.aperture.circle.CircularAperture or photutils.aperture.circle.EllipticalAperture """ if aperture['type'] == "circular": phot_ap = CircularAperture((aperture['pos_x'], aperture['pos_y']), aperture['radius']) elif aperture['type'] == "elliptical": phot_ap = EllipticalAperture((aperture['pos_x'], aperture['pos_y']), aperture['semimajor'], aperture['semiminor'], math.radians(aperture['angle'])) else: raise ValueError("Aperture type not recognized.") return phot_ap
def mark_ellipse(cat, nsaid): pass # ellipse showing R24 index = np.where(cat.NEDname == nsaid) #print('start of mark_ellipse',nsaid, index) radii = ['GAL_R24','GAL_HR17'] colors = ['cyan','magenta'] apertures = [] for i,sma in enumerate(radii): #print('in loop',sma,index) rad = cat[sma][index][0] #print(sma, rad) if rad > 0.1: t = cat['GAL_PA'][index][0] if t < 0: theta = np.radians(180. + t+90) else: theta = np.radians(t+90) # orientation in radians apertures.append(EllipticalAperture((cat['GAL_XC'][index][0],cat['GAL_YC'][index][0]),\ cat[sma][index][0]/pixel_scale, \ cat[sma][index][0]*cat['GAL_BA'][index][0]/pixel_scale,\ theta = theta)) for i,aperture in enumerate(apertures): aperture.plot(color=colors[i],lw=1.5)
def profile_analyseplus(x,data,aperture,mask=None): flux=[] sma=aperture['sma'] epsilon=aperture['ellipticity'] theta=(aperture['PA']+90.)*np.pi/180. aperturestd = EllipticalAperture(positions=aperture['posi'],a=sma,b=sma*(1-epsilon),theta=theta) rawflux_table = aperture_photometry(data, aperturestd, mask=mask) stdflux=rawflux_table['aperture_sum'][0] for x1 in x: apertures = EllipticalAperture(positions=aperture['posi'],a=x1,b=x1*(1-epsilon),theta=theta) rawflux_table = aperture_photometry(data, apertures, mask=mask) flux.append(rawflux_table['aperture_sum'][0]) antieerFunc=interp1d(flux,x) HLR=antieerFunc(0.5*stdflux) concentration=5*np.log(antieerFunc(0.7*stdflux)/antieerFunc(0.3*stdflux)) result = { 'concentration':concentration, 'HLR':HLR, } return result
def measure_photo(self): """ sample.extract(), from EllipseSample documentation: Extract sample data by scanning an elliptical path over the image array. Returns ------- result : 2D `~numpy.ndarray` The rows of the array contain the angles, radii, and extracted intensity values, respectively. """ if self.photomorph == 'ellipse': sample = EllipseSample(self.im, sma=self.params['sma'], astep=0, sclip=0.3, nclip=0, linear_growth=False, geometry=self.geom, integrmode='bilinear') self.ellip_data = sample.extract() self.mean_intens = np.mean(self.ellip_data[2]) elif self.photomorph == 'annulus': aper = EllipticalAnnulus((self.params['x0'], self.params['y0']), self.params['sma']-self.beam_size_pix/2., self.params['sma']+self.beam_size_pix/2., (self.params['sma']+self.beam_size_pix/2.) \ *(1. - self.params['eps']), self.params['pa']) annulus_mask = aper.to_mask() self.ellip_data = annulus_mask.multiply(self.im) self.sum_intens = np.sum( self.ellip_data) / self.beam_size_pix**2 #in Jy self.mean_intens = np.mean(self.ellip_data) # also: aperture_photometry(self.image_cube[self.chan] / self.beam_size_pix**2, aper) elif self.photomorph == 'circle': aper = CircularAperture((self.params['x0'], self.params['y0']), self.params['sma']) circle_mask = aper.to_mask() self.ellip_data = circle_mask.multiply(self.im) self.sum_intens = np.sum(self.ellip_data) / self.beam_size_pix**2 self.mean_intens = np.mean(self.ellip_data) elif self.photomorph == 'ellipse_area': aper = EllipticalAperture( (self.params['x0'], self.params['y0']), self.params['sma'], self.params['sma'] * (1. - self.params['eps']), self.params['pa']) ellipse_mask = aper.to_mask() self.ellip_data = ellipse_mask.multiply(self.im) self.sum_intens = np.sum(self.ellip_data) / self.beam_size_pix**2 self.mean_intens = np.mean(self.ellip_data)
def make_blank_image(fitsimage, mask_detection=True, detections=None, mask_aperture=2, **kwargs): # remove all the detections, return blank image image = fitsimage.image.copy() if mask_detection: if detections is None: detections = source_finder(fitsimage, **kwargs) ra = np.array([detections['ra']]) dec = np.array([detections['dec']]) skycoords = SkyCoord(ra.flatten(), dec.flatten(), unit='deg') pixel_coords = np.array( list(zip(*skycoord_to_pixel(skycoords, fitsimage.wcs)))) for i, coord in enumerate(pixel_coords): aper = EllipticalAperture(coord, mask_aperture, mask_aperture, 0) mask = aper.to_mask().to_image(shape=fitsimage.imagesize) > 0 image[mask] = fitsimage.std return image
def _fractionTotalFLuxEllipse(a: float, image: np.ndarray, b: float, theta: float, centre: List[float], totalsum: float) -> float: '''Function calculates the fraction of the total flux for the given elliptical parameters Parameters ---------- a : float Semi-major axis or width. Must be positive image : np.ndarray image for which flux will be counted b : float Ellipses semi-minor axis or hight theta : float Rotation of ellipse w.r.t the positive x axis anticlockwise centre : List[float] centre of the ellipse. totalsum : float total flux of the object. Returns ------- The fraction of the total flux of the object as defined by the input parameters minus 50% so that this function can be used to find a root at 50% total flux : float ''' apCur = EllipticalAperture(centre, a, b, theta) curSum = apCur.do_photometry(image, method="exact")[0][0] return (curSum / totalsum) - 0.5
def find_centroid(data): """ find the centroid again after the image was rotated """ sigma_clip = SigmaClip(sigma=3., iters=10) bkg_estimator = MedianBackground() bkg = Background2D(data, (25, 25), filter_size=(3, 3), sigma_clip=sigma_clip, bkg_estimator=bkg_estimator) threshold = bkg.background + (3. * bkg.background_rms) sigma = 2.0 * gaussian_fwhm_to_sigma # FWHM = 2. kernel = Gaussian2DKernel(sigma, x_size=3, y_size=3) kernel.normalize() segm = detect_sources(data, threshold, npixels=5, filter_kernel=kernel) props = source_properties(data, segm) tbl = properties_table(props) my_min = 100000. x_shape = np.float(data.shape[0]) y_shape = np.float(data.shape[1]) r = 3. # approximate isophotal extent apertures = [] for prop in props: position = (prop.xcentroid.value, prop.ycentroid.value) #print(position) a = prop.semimajor_axis_sigma.value * r b = prop.semiminor_axis_sigma.value * r theta = prop.orientation.value apertures.append(EllipticalAperture(position, a, b, theta=theta)) my_dist = np.sqrt((prop.xcentroid.value - x_shape / 2.)**2 + (prop.ycentroid.value - y_shape / 2.)**2) if (my_dist < my_min): my_label = prop.id - 1 my_min = my_dist mytheta = props[my_label].orientation.value mysize = np.int(np.round(r * props[my_label].semimajor_axis_sigma.value)) my_x = props[my_label].xcentroid.value my_y = props[my_label].ycentroid.value return my_x, my_y, mytheta, mysize
def elliptic_aperture_pix(filename, position, ellipticity, theta, r_end=9, plot=True): #Opening FITS, getting data and header ima = Image(os.getcwd() + '/DATA/' + filename + '.fits') hdr = ima.data_header data = ima.data.copy() #Galaxy center position and position for apertures center = (hdr['CRPIX1'], hdr['CRPIX2']) position = position + center #Defining radius for circular apertures radius = 50 * np.arange(1, r_end) aperture = [ EllipticalAperture(position, i, i * ellipticity, theta) for i in radius ] if plot == True: #Plot galaxy image with apertures fig, ax = plt.subplots(sharex=True, sharey=True) ima.data[ima.data < 0] = 0 ima.plot(ax, scale='log', vmin=0, vmax=0.9 * np.amax(ima.data), colorbar='v') for i in range(len(aperture)): aperture[i].plot(ax, color='white', lw=2) ax.set_title(r'Original image - NGC 3614') #plt.savefig('Image_apertures', dpi = 200) #Table with apertures data (flux sum) phot_table = aperture_photometry(data, aperture) for col in phot_table.colnames: phot_table[col].info.format = '%.8g' print(phot_table) return data, position, aperture
def maximize_intens(self, par): if self.photomorph == 'ellipse': geom = EllipseGeometry( par[0], par[1], par[2], par[3], par[4], ) sample = EllipseSample(self.im, sma=geom.sma, astep=0, sclip=0.3, nclip=0, linear_growth=False, geometry=geom, integrmode='bilinear') data2min = np.mean(sample.extract()[2]) elif self.photomorph == 'annulus': aper = EllipticalAnnulus( (par[0], par[1]), par[2] - self.beam_size_pix / 2., par[2] + self.beam_size_pix / 2., (par[2] + self.beam_size_pix / 2.) * (1. - par[3]), par[4]) annulus_mask = aper.to_mask() data2min = annulus_mask.multiply(self.im) elif self.photomorph == 'circle': aper = CircularAperture((par[0], par[1]), par[2]) circular_mask = aper.to_mask() data2min = circular_mask.multiply(self.im) elif self.photomorph == 'ellipse_area': aper = EllipticalAperture((par[0], par[1]), par[2], par[2] * (1. - par[3]), par[4]) ellipse_mask = aper.to_mask() data2min = ellipse_mask.multiply(self.im) if self.q2min == 'sum': tominimize = np.sum(data2min) elif self.q2min == 'mean': tominimize = np.mean(data2min) return -tominimize
def burst_drift(npy, fslice=':', tslice=':'): #Load npy array burst = np.load(npy) #Subband in Frequency subfac = 16 rb_sub = np.nanmean(R3H.reshape(-1, subfac, R3H.shape[1]), axis=1) rb_sub_zm = rb_sub[fslice, tslice] ynorm = (rb_sub_zm.sum(0) / np.max(rb_sub_zm.sum(0))) x = np.linspace(0, len(ynorm) - 1, len(ynorm)) #Smooth sav = ss.savgol_filter(rb_sub_zm, 49, 6) #Calculate 2D ACF acf = ss.correlate(sav, sav) #Provide the initial Ellipse to be fitted #Calculate Ellipse Geometry geometry = EllipseGeometry(x0=acf.shape[1] / 2, y0=acf.shape[0] / 2, sma=20, eps=0.4, pa=60 * np.pi / 180.) #Show Initial Guess Ellipsee aper = EllipticalAperture((geometry.x0, geometry.y0), geometry.sma, geometry.sma * (1 - geometry.eps), geometry.pa) #Plot Initial Guess Ellipse on ACF #plt.imshow(acf, aspect = 'auto') #aper.plot(color='white') #Fit Ellipse to 2D ACF ellipse = Ellipse(acf, geometry) isolist = ellipse.fit_image() model_image = build_ellipse_model(acf.shape, isolist) residual = acf - model_image fig = plt.figure(figsize=(40, 10)) ax1 = fig.add_subplot(131) plt.gca().invert_yaxis() x = np.linspace(0, acf.shape[1] - 1, acf.shape[1]) y = lambda x: (0.809) * x + 37.65 plt.plot(x, y(x), color='white', label='Drift Rate = -33 MHz/ms') plt.imshow(sav, interpolation=None) plt.yticks(np.arange(0, sav.shape[0], 60), [1000, 900, 800, 700, 600]) plt.xticks(np.arange(0, sav.shape[1], 48), [0, 2, 4, 6, 8, 10]) plt.ylabel('Frequency (MHz)') plt.xlabel('Time (ms)') plt.title('Dynamic Spectrum R3H') plt.legend() ax2 = fig.add_subplot(132) plt.imshow(acf) #, origin='lower') plt.ylabel('Frequency Shift (MHz)') plt.xlabel('Time Shfit (ms)') plt.yticks(np.arange(0, acf.shape[0], 79), [1000, 600, 200, 0, -200, -600, -1000]) plt.xticks(np.arange(0, acf.shape[1], 49), [-10, -8, -6, -4, -3, 0, 2, 4, 6, 8, 10]) #plt.plot(x, y(x)) plt.title('2D ACF R3H') smas = np.linspace(10, 100, 8) for sma in smas: iso = isolist.get_closest(sma) x, y, = iso.sampled_coordinates() plt.plot(x, y, color='white') #ax3.imshow(model_image, origin='lower') #ax3.set_title('Ellipse Model') #ax3.imshow(residual, origin='lower') ax3 = fig.add_subplot(133) #plt.set_aspect('auto') x = np.linspace(0, len(ynorm) - 1, len(ynorm)) plt.plot(x, ss.savgol_filter(ynorm, 19, 6)) plt.title('Timeseries R3H') plt.ylabel('Normalized Intensity') plt.xlabel('Time (ms)') plt.xticks(np.arange(0, sav.shape[1], 48), [0, 2, 4, 6, 8, 10]) #ax3.set_title('Residual') fig.savefig('Drift Rate R3H') return
def spetial_points_calc(x0, y0, sma, eps, pa, n_points, wcs, dist_center, xys_sky_PV): """ Calculates the points for the three interesting points in the elliptical fits from which you can extract important information from. This are where phi is 0, 90 and 180 Parameters ---------- x0, y0, sma, eps, pa: float ellipse parameters wcs: wcs of the image n_points: int number of points of the pv line. The higher the more precises the value of the x_phis are dist_center: float this are the values set to x_phi90, the distances of the centers of the ellipses """ vla4b_sky = SkyCoord(*mf.default_params['vla4b_deg'], unit='deg') ellipse_pixel, ellipse_sky = ellipse_points_calc(x0, y0, sma, eps, pa, n_points, wcs) ellipse_sky_cat = concatenate(ellipse_sky) xys_sky_PV_cat = concatenate(xys_sky_PV) aper = EllipticalAperture((x0, y0), sma, sma * (1. - eps), pa) aper_sky = aper.to_sky(wcs) ell_center_sky = aper_sky.positions sma_arcsec = aper_sky.a.value sminora_arcsec = aper_sky.b.value xys_PV_inrange = np.array([ xys for xys in xys_sky_PV_cat if (xys.separation(ell_center_sky).arcsec <= 1.1 * sma_arcsec) and ( xys.separation(ell_center_sky).arcsec >= 0.5 * sminora_arcsec) ]) xys_180_PV = np.array([ xys for xys in xys_PV_inrange if vla4b_sky.separation(xys).arcsec < vla4b_sky.separation(ell_center_sky).arcsec ]) xys_0_PV = np.array([ xys for xys in xys_PV_inrange if vla4b_sky.separation(xys).arcsec > vla4b_sky.separation(ell_center_sky).arcsec ]) distances_180 = np.array( [[xys.separation(ell).arcsec for ell in ellipse_sky_cat] for xys in xys_180_PV]) distances_0 = np.array( [[xys.separation(ell).arcsec for ell in ellipse_sky_cat] for xys in xys_0_PV]) idx_180 = np.where(distances_180 == np.min(distances_180)) idx_0 = np.where(distances_0 == np.min(distances_0)) p180_sky = ellipse_sky_cat[idx_180[1]] p0_sky = ellipse_sky_cat[idx_0[1]] xp_phi180 = p180_sky.separation(vla4b_sky).arcsec xp_phi90 = dist_center xp_phi0 = p0_sky.separation(vla4b_sky).arcsec return [[ellipse_pixel, ellipse_sky], [p180_sky, p0_sky], [xp_phi180, xp_phi90, xp_phi0]]
def _prep_2dacf(time_smooth, freq_smooth, time_res, subband_freq_res, diagnostic=True): #Calculate 2d acf acf2d = ss.correlate(savts_2d_c, savsp_2d_c) #Cap spiked central values in acf cap = np.mean( acf2d[len(acf2d.sum(1)) // 2 + 10:len(acf2d.sum(1)) // 2 + 10, len(acf2d.sum(0)) // 2 + 10:len(acf2d.sum(0)) // 2 + 10]) acf2d_cap = np.where(acf2d > cap, cap, acf2d) #Smooth Data w Savitzky Golay filter (t - time) (s - spectrum) twinlen = acf2d.shape[1] // 4 if twinlen % 2 == 0: twinlen = twinlen + 1 swinlen = acf2d.shape[0] // 4 if swinlen % 2 == 0: swinlen = swinlen + 1 polyo = 6 savacft = ss.savgol_filter(acf2d, twinlen, polyo, axis=1) savacff = ss.savgol_filter(acf2d, swinlen, polyo, axis=0) print('ACF Time Window Length: ', twinlen) print('ACF Freq Window Length: ', swinlen) #Calculate initial guess parameters spectrum acf time savt = savacft.sum(0) / np.max(savacft.sum(0)) maximt = np.max(savt) stdt = np.std(savt) meant = np.where(savt == maximt)[0][0] xt = np.linspace(0, len(savt), len(savt)) #Fit 1D Gaussian to Spectrum fittert = modeling.fitting.LevMarLSQFitter() modelt = modeling.models.Gaussian1D(amplitude=maximt, mean=meant, stddev=stdt) fitted_modelt = fittert(modelt, xt, savt) #Calculate initial guess parameters spectrum acf freq savsp = savacff.sum(1) / np.max(savacff.sum(1)) maximsp = np.max(savsp) stdsp = np.std(savsp) meansp = np.where(savsp == maximsp)[0][0] xsp = np.linspace(0, len(savsp), len(savsp)) #Fit 1D Gaussian to Spectrum fittersp = modeling.fitting.LevMarLSQFitter() modelsp = modeling.models.Gaussian1D(amplitude=maximsp, mean=meansp, stddev=stdsp) fitted_modelsp = fittersp(modelsp, xsp, savsp) #Get Ellipse Ratio sigmat = fitted_modelt.stddev.value sigmaf = fitted_modelsp.stddev.value #Up to sig 3 -- MAKE EDIT TO ADJUST ITERATIVELY IN FIT FUNCTION sigt1 = sigmat sigf1 = sigmaf sigt2 = sigmat * 2 sigf2 = sigmaf * 2 sigt3 = sigmat * 3 sigf3 = sigmaf * 3 sigmat = sigmat * 0.3 sigmaf = sigmaf * 0.3 #Sigmas form a rectangle, get slope of the rectangle diagonal to estimate semi major axis PA hyp = np.sqrt(sigmat**2 + sigmaf**2) estpa = np.arccos(sigmat / hyp) #in radians #Estimate ellipticity (eps) with sigma ratio oppestpa = np.arccos(sigmaf / hyp) estsmajax = np.tan(oppestpa) * (hyp / 2) estsminax = hyp / 2 esteps = 1 - (estsminax / estsmajax) print(estsmajax, estsminax) print('Estimated Ellipticity: ', esteps) print('Estmated Semimajor Axis: ', estsmajax) print('Estimated PA: ', estpa) print('Initial guess ellipse applied!') #Provide the initial ellipse to be fitted #Calculate ellipse geometry geometry = EllipseGeometry(x0 = acf2d.shape[1]/2, \ y0 = acf2d.shape[0]/2, sma = estsmajax, eps = esteps, pa = estpa) #Show initial guess ellipse aper = EllipticalAperture((geometry.x0, geometry.y0), \ geometry.sma, geometry.sma*(1-geometry.eps),geometry.pa) if diagnostic == True: fig = plt.figure() plt.imshow(acf2d, aspect='auto') aper.plot(color='white') plt.title('Diagnostic - Pre-fit Estimate') plt.show() fig.savefig('Diagnostic_' + str(burst_npy) + '.png') print('Now for the fit...') return acf2d, geometry, aper, sigmat, sigmaf, sigf3
b = (1. - cat.ELLIPTICITY[objectID][0]) * a print('ellipticity = ', cat.ELLIPTICITY[objectID][0]) print('b/a = ', 1. - cat.ELLIPTICITY[objectID][0]) flux_r = np.zeros(len(a), 'f') flux_ha = np.zeros(len(a), 'f') if args.mask: maskdat = fits.getdata(args.mask) maskdat = np.array(maskdat, 'bool') for i in range(len(a)): if args.verbose: print('defining elliptical aperture ' + str(i) + ' ap size = ', str(a[i])) ap = EllipticalAperture(position, a[i], b[i], theta) #,ai,bi,theta) for ai,bi in zip(a,b)] if args.mask: phot_table_r = aperture_photometry(imdat_r, ap, mask=maskdat) phot_table_ha = aperture_photometry(imdat_ha, ap, mask=maskdat) else: phot_table_r = aperture_photometry(imdat_r, ap) phot_table_ha = aperture_photometry(imdat_ha, ap) if args.verbose: print('measuring flux in aperture ' + str(i) + ' ap size = ', str(a[i])) flux_r[i] = phot_table_r['aperture_sum'][0] flux_ha[i] = phot_table_ha['aperture_sum'][0] # plot image with outer ellipse
def fit_ellipse_for_source( friendid=None, detectid=None, coords=None, shotid=None, subcont=True, convolve_image=False, pixscale=pixscale, imsize=imsize, wave_range=None, ): if detectid is not None: global deth5 detectid_obj = detectid if detectid_obj <= 2190000000: det_info = deth5.root.Detections.read_where("detectid == detectid_obj")[0] linewidth = det_info["linewidth"] wave_obj = det_info["wave"] redshift = wave_obj / (1216) - 1 else: det_info = conth5.root.Detections.read_where("detectid == detectid_obj")[0] redshift = 0 wave_obj = 4500 coords_obj = SkyCoord(det_info["ra"], det_info["dec"], unit="deg") shotid_obj = det_info["shotid"] fwhm = surveyh5.root.Survey.read_where("shotid == shotid_obj")["fwhm_virus"][0] amp = det_info["multiframe"] if wave_range is not None: wave_range_obj = wave_range else: if detectid_obj <= 2190000000: wave_range_obj = [wave_obj - 2 * linewidth, wave_obj + 2 * linewidth] else: wave_range_obj = [4100, 4200] if coords is not None: coords_obj = coords if shotid is not None: shotid_obj = shotid fwhm = surveyh5.root.Survey.read_where("shotid == shotid_obj")[ "fwhm_virus" ][0] try: hdu = make_narrowband_image( coords=coords_obj, shotid=shotid_obj, wave_range=wave_range_obj, imsize=imsize * u.arcsec, pixscale=pixscale * u.arcsec, subcont=subcont, convolve_image=convolve_image, include_error=True, ) except: print("Could not make narrowband image for {}".format(detectid)) return np.nan, np.nan elif friendid is not None: global friend_cat sel = friend_cat["friendid"] == friendid group = friend_cat[sel] coords_obj = SkyCoord(ra=group["icx"][0] * u.deg, dec=group["icy"][0] * u.deg) wave_obj = group["icz"][0] redshift = wave_obj / (1216) - 1 linewidth = group["linewidth"][0] shotid_obj = group["shotid"][0] fwhm = group["fwhm"][0] amp = group["multiframe"][0] if wave_range is not None: wave_range_obj = wave_range else: wave_range_obj = [wave_obj - 2 * linewidth, wave_obj + 2 * linewidth] if shotid is not None: shotid_obj = shotid fwhm = surveyh5.root.Survey.read_where("shotid == shotid_obj")[ "fwhm_virus" ][0] try: hdu = make_narrowband_image( coords=coords_obj, shotid=shotid_obj, wave_range=wave_range_obj, imsize=imsize * u.arcsec, pixscale=pixscale * u.arcsec, subcont=subcont, convolve_image=convolve_image, include_error=True, ) except: print("Could not make narrowband image for {}".format(friendid)) return None elif coords is not None: coords_obj = coords if wave_range is not None: wave_range_obj = wave_range else: print( "You need to supply wave_range=[wave_start, wave_end] for collapsed image" ) if shotid is not None: shotid_obj = shotid fwhm = surveyh5.root.Survey.read_where("shotid == shotid_obj")[ "fwhm_virus" ][0] else: print("Enter the shotid to use (eg. 20200123003)") hdu = make_narrowband_image( coords=coords_obj, shotid=shotid_obj, wave_range=wave_range_obj, imsize=imsize * u.arcsec, pixscale=pixscale * u.arcsec, subcont=subcont, convolve_image=convolve_image, include_error=True, ) else: print("You must provide a detectid, friendid or coords/wave_range/shotid") return np.nan, np.nan w = wcs.WCS(hdu[0].header) if friendid is not None: sel_friend_group = friend_cat["friendid"] == friendid group = friend_cat[sel_friend_group] eps = 1 - group["a2"][0] / group["b2"][0] pa = group["pa"][0] * np.pi / 180.0 - 90 sma = group["a"][0] * 3600 / pixscale coords = SkyCoord(ra=group["icx"][0] * u.deg, dec=group["icy"][0] * u.deg) wave_obj = group["icz"][0] redshift = wave_obj / (1216) - 1 linewidth = np.nanmedian(group["linewidth"]) shotid_obj = group["shotid"][0] fwhm = group["fwhm"][0] geometry = EllipseGeometry( x0=w.wcs.crpix[0], y0=w.wcs.crpix[0], sma=sma, eps=eps, pa=pa ) else: geometry = EllipseGeometry( x0=w.wcs.crpix[0], y0=w.wcs.crpix[0], sma=20, eps=0.2, pa=20.0 ) geometry = EllipseGeometry( x0=w.wcs.crpix[0], y0=w.wcs.crpix[0], sma=20, eps=0.2, pa=20.0 ) # geometry.find_center(hdu.data) # aper = EllipticalAperture((geometry.x0, geometry.y0), geometry.sma, # geometry.sma*(1 - geometry.eps), geometry.pa) # plt.imshow(hdu.data, origin='lower') # aper.plot(color='white') ellipse = Ellipse(hdu[0].data) isolist = ellipse.fit_image() iso_tab = isolist.to_table() if len(iso_tab) == 0: geometry.find_center(hdu[0].data, verbose=False, threshold=0.5) ellipse = Ellipse(hdu[0].data, geometry) isolist = ellipse.fit_image() iso_tab = isolist.to_table() if len(iso_tab) == 0: return np.nan, np.nan, np.nan try: # compute iso's manually in steps of 3 pixels ellipse = Ellipse(hdu[0].data) # reset ellipse iso_list = [] for sma in np.arange(1, 60, 2): iso = ellipse.fit_isophote(sma) if np.isnan(iso.intens): # print('break at {}'.format(sma)) break else: iso_list.append(iso) isolist = IsophoteList(iso_list) iso_tab = isolist.to_table() except: return np.nan, np.nan, np.nan try: model_image = build_ellipse_model(hdu[0].data.shape, isolist) residual = hdu[0].data - model_image except: return np.nan, np.nan, np.nan sma = iso_tab["sma"] * pixscale const_arcsec_to_kpc = cosmo.kpc_proper_per_arcmin(redshift).value / 60.0 def arcsec_to_kpc(sma): dist = const_arcsec_to_kpc * sma return dist def kpc_to_arcsec(dist): sma = dist / const_arcsec_to_kpc return sma dist_kpc = ( sma * u.arcsec.to(u.arcmin) * u.arcmin * cosmo.kpc_proper_per_arcmin(redshift) ) dist_arcsec = kpc_to_arcsec(dist_kpc) # print(shotid_obj, fwhm) # s_exp1d = models.Exponential1D(amplitude=0.2, tau=-50) alpha = 3.5 s_moffat = models.Moffat1D( amplitude=1, gamma=(0.5 * fwhm) / np.sqrt(2 ** (1.0 / alpha) - 1.0), x_0=0.0, alpha=alpha, fixed={"amplitude": False, "x_0": True, "gamma": True, "alpha": True}, ) s_init = models.Exponential1D(amplitude=0.2, tau=-50) fit = fitting.LevMarLSQFitter() s_r = fit(s_init, dist_kpc, iso_tab["intens"]) # Fitting can be done using the uncertainties as weights. # To get the standard weighting of 1/unc^2 for the case of # Gaussian errors, the weights to pass to the fitting are 1/unc. # fitted_line = fit(line_init, x, y, weights=1.0/yunc) # s_r = fit(s_init, dist_kpc, iso_tab['intens'])#, weights=iso_tab['intens']/iso_tab['intens_err'] ) print(s_r) try: r_n = -1.0 * s_r.tau # _0 #* const_arcsec_to_kpc except: r_n = np.nan # r_n = -1. * s_r.tau_0 try: sel_iso = np.where(dist_kpc >= 2 * r_n)[0][0] except: sel_iso = -1 aper = EllipticalAperture( (isolist.x0[sel_iso], isolist.y0[sel_iso]), isolist.sma[sel_iso], isolist.sma[sel_iso] * (1 - isolist.eps[sel_iso]), isolist.pa[sel_iso], ) phottable = aperture_photometry(hdu[0].data, aper, error=hdu[1].data) flux = phottable["aperture_sum"][0] * 10 ** -17 * u.erg / (u.cm ** 2 * u.s) flux_err = phottable["aperture_sum_err"][0] * 10 ** -17 * u.erg / (u.cm ** 2 * u.s) lum_dist = cosmo.luminosity_distance(redshift).to(u.cm) lum = flux * 4.0 * np.pi * lum_dist ** 2 lum_err = flux_err * 4.0 * np.pi * lum_dist ** 2 if detectid: name = detectid elif friendid: name = friendid # Get Image data from Elixer catlib = catalogs.CatalogLibrary() try: cutout = catlib.get_cutouts( position=coords_obj, side=imsize, aperture=None, dynamic=False, filter=["r", "g", "f606W"], first=True, allow_bad_image=False, allow_web=True, )[0] except: print("Could not get imaging for " + str(name)) zscale = ZScaleInterval(contrast=0.5, krej=1.5) vmin, vmax = zscale.get_limits(values=hdu[0].data) fig = plt.figure(figsize=(20, 12)) fig.suptitle( "{} ra={:3.2f}, dec={:3.2f}, wave={:5.2f}, z={:3.2f}, mf={}".format( name, coords_obj.ra.value, coords_obj.dec.value, wave_obj, redshift, amp ), fontsize=22, ) ax1 = fig.add_subplot(231, projection=w) plt.imshow(hdu[0].data, vmin=vmin, vmax=vmax) plt.xlabel("RA") plt.ylabel("Dec") plt.colorbar() plt.title("Image summed across 4*linewidth") ax2 = fig.add_subplot(232, projection=w) plt.imshow(model_image, vmin=vmin, vmax=vmax) plt.xlabel("RA") plt.ylabel("Dec") plt.colorbar() plt.title("model") ax3 = fig.add_subplot(233, projection=w) plt.imshow(residual, vmin=vmin, vmax=vmax) plt.xlabel("RA") plt.ylabel("Dec") plt.colorbar() plt.title("residuals (image-model)") # fig = plt.figure(figsize=(10,5)) im_zscale = ZScaleInterval(contrast=0.5, krej=2.5) im_vmin, im_vmax = im_zscale.get_limits(values=cutout["cutout"].data) ax4 = fig.add_subplot(234, projection=cutout["cutout"].wcs) plt.imshow( cutout["cutout"].data, vmin=im_vmin, vmax=im_vmax, origin="lower", cmap=plt.get_cmap("gray"), interpolation="none", ) plt.text( 0.8, 0.9, cutout["instrument"] + cutout["filter"], transform=ax4.transAxes, fontsize=20, color="w", ) plt.contour(hdu[0].data, transform=ax4.get_transform(w)) plt.xlabel("RA") plt.ylabel("Dec") aper.plot( color="white", linestyle="dashed", linewidth=2, transform=ax4.get_transform(w) ) ax5 = fig.add_subplot(235) plt.errorbar( dist_kpc.value, iso_tab["intens"], yerr=iso_tab["intens_err"] * iso_tab["intens"], linestyle="none", marker="o", label="Lya SB profile", ) plt.plot(dist_kpc, s_r(dist_kpc), color="r", label="Lya exp SB model", linewidth=2) plt.xlabel("Semi-major axis (kpc)") # plt.xlabel('Semi-major axis (arcsec)') plt.ylabel("Flux ({})".format(10 ** -17 * (u.erg / (u.s * u.cm ** 2)))) plt.text(0.4, 0.7, "r_n={:3.2f}".format(r_n), transform=ax5.transAxes, fontsize=16) plt.text( 0.4, 0.6, "L_lya={:3.2e}".format(lum), transform=ax5.transAxes, fontsize=16 ) secax = ax5.secondary_xaxis("top", functions=(kpc_to_arcsec, kpc_to_arcsec)) secax.set_xlabel("Semi-major axis (arcsec)") # secax.set_xlabel('Semi-major axis (kpc)') plt.xlim(0, 100) # plt.plot(sma, s_r(sma), label='moffat psf') # plt.plot(dist_kpc.value, s1(kpc_to_arcsec(dist_kpc.value)), # linestyle='dashed', linewidth=2, # color='green', label='PSF seeing:{:3.2f}'.format(fwhm)) # These two are the exact same # s1 = models.Moffat1D() # s1.amplitude = iso_tab['intens'][0] # alpha=3.5 # s1.gamma = 0.5*(fwhm*const_arcsec_to_kpc)/ np.sqrt(2 ** (1.0 / alpha) - 1.0) # s1.alpha = alpha # plt.plot(r_1d, moffat_1d, color='orange') # plt.plot(dist_kpc.value, (s1(dist_kpc.value)), # linestyle='dashed', linewidth=2, # color='blue', label='PSF seeing:{:3.2f}'.format(fwhm)) E = Extract() E.load_shot(shotid_obj) moffat_psf = E.moffat_psf(seeing=fwhm, boxsize=imsize, scale=pixscale) moffat_shape = np.shape(moffat_psf) xcen = int(moffat_shape[1] / 2) ycen = int(moffat_shape[2] / 2) moffat_1d = ( moffat_psf[0, xcen:-1, ycen] / moffat_psf[0, xcen, ycen] * iso_tab["intens"][0] ) r_1d = moffat_psf[1, xcen:-1, ycen] E.close() plt.plot( arcsec_to_kpc(pixscale * np.arange(80)), iso_tab["intens"][0] * (moffat_psf[0, 80:-1, 80] / moffat_psf[0, 80, 80]), linestyle="dashed", color="green", label="PSF seeing:{:3.2f}".format(fwhm), ) plt.legend() if friendid is not None: ax6 = fig.add_subplot(236, projection=cutout["cutout"].wcs) plot_friends(friendid, friend_cat, cutout, ax=ax6, label=False) plt.savefig("fit2d_{}.png".format(name)) # filename = 'param_{}.txt'.format(name) # np.savetxt(filename, (r_n.value, lum.value)) return r_n, lum, lum_err
time = (t1 - t0).total_seconds() ts.append(time) time = sum(ts) / 5 rows.append((r, time)) df = pd.DataFrame(rows, columns=["r", "time"]) path = os.path.dirname(__file__) df.to_csv(os.path.join(path, "python_aperture_size.csv"), index=False) ## Ellipse rows = [] for r in trange(1, 201, 5): ap = EllipticalAperture((255.5, 255.5), r, r, 20) ts = [] for i in range(5): t0 = datetime.now() t = aperture_photometry(data, ap, method="exact") t1 = datetime.now() time = (t1 - t0).total_seconds() ts.append(time) time = sum(ts) / 5 rows.append((r, time)) df = pd.DataFrame(rows, columns=["r", "time"]) path = os.path.dirname(__file__)
def _create_apertures(self, image, image_stars_coordinates, data_shape): self.info('Creating apertures started') apertures = [] sigma_values_table = [] sigma_value = namedtuple('sigma_value', ['mean', 'median', 'std']) masks = [self._create_mask( star_coo, data_shape) for star_coo in image_stars_coordinates] for i, mask in enumerate(masks): mean, median, std = sigma_clipped_stats( image.data, mask=mask, sigma=self.config_section.get('sigma_clipped_sigma'), iters=self.config_section.get('sigma_clipped_iters')) self.info('Sigma clipped stats: mean={}, median={}, std={}'.format( mean, median, std)) if (self.config_section.get('calculate_center') or self.config_section.get('calculate_aperture')): star_properties = self._find_star_properties(np.copy(image.data), median, mask, image_stars_coordinates[i]) if self.config_section.get('calculate_center'): position = (star_properties['xcentroid'].value, star_properties['ycentroid'].value) else: position = image_stars_coordinates[i] self.info( 'Calculate center is off, original coordinates have been used') self.info('Coordinates={}'.format(position)) if self.config_section.get('calculate_aperture'): self.info('Calculating apertures has been started') a = star_properties['semimajor_axis_sigma'] * self.config_section.get( 'r_aperture_multi') b = star_properties['semiminor_axis_sigma'] * self.config_section.get( 'r_aperture_multi') theta = star_properties['orientation'] self.info('Found aperture parameters: a={}, b={}, theta={}'.format( a, b, theta)) aperture = EllipticalAperture( position, a=a.value, b=b.value, theta=theta.value) annulus = EllipticalAnnulus(position, a_in=a.value + self.config_section.get( 'r_annulus_in'), a_out=a.value + self.config_section.get( 'r_annulus_out'), b_out=b.value + self.config_section.get( 'r_annulus_out'), theta=theta.value) else: self.info('Aperture calculate is off') self.info( 'Aperture paratmeters from configuration file have been used') aperture = CircularAperture( position, r=self.config_section.get('r_aperture')) annulus = CircularAnnulus(position, r_in=self.config_section.get( 'r_aperture') + self.config_section.get( 'r_annulus_in'), r_out=self.config_section.get( 'r_aperture') + self.config_section.get( 'r_annulus_out')) apertures.append([aperture, annulus, std]) sigma_values_table.append(sigma_value(mean, median, std)) return apertures, sigma_values_table
def get_elliptical_aperture(data,isophote,mask=None,skyp=2): lenofell=len(isophote) sky_mean, sky_median, sky_std = sigma_clipped_stats(data, sigma=3.0, iters=5) datap=data-sky_median sky=sky_median DeltaSKY=0 ellip=isophote['col6'] ex_0=isophote['col10'] ey_0=isophote['col12'] ePA=isophote['col8'] eSMA=isophote['col1'] fluxList=[] aperarea=[] for loop in range(lenofell): a=eSMA[loop] b=a*( 1 - (ellip[loop]) ) posi = [ex_0[loop], ey_0[loop]] apertures = EllipticalAperture(positions=posi,a=a,b=b,theta=(ePA[loop]+90)) rawflux_table = aperture_photometry(datap, apertures, mask=mask) aperturesMaskedArea = aperture_photometry(mask.astype(int), apertures)['aperture_sum'] aperarea.append((apertures.area()-aperturesMaskedArea[0])) fluxList.append(rawflux_table['aperture_sum'][0]) print ("{0} percent done".format(100*loop/lenofell) ) for loop in range(skyp) : antieerFunc=interp1d(fluxList,eSMA) aper1=1.4*antieerFunc(0.9*fluxList[lenofell-1]) deltaSKY=0 mass=0 if(aper1 < 0.5*eSMA[lenofell-1]): aper1=0.5*eSMA[lenofell-1] if(aper1 > 0.75*eSMA[lenofell-1]): aper1=0.5*eSMA[lenofell-1] bignumber=0 for loop2 in range(lenofell) : if eSMA[loop2] > aper1 : bignumber=loop2 break loop3=bignumber+1 while (loop3 < lenofell) : deltaSKy=(fluxList[loop3]-fluxList[bignumber])/(aperarea[loop3]-aperarea[bignumber]) deltaSKY += deltaSKy*(pow(loop3,3)) loop3+=1 mass += pow(loop3,3) Dsky=deltaSKY/mass DeltaSKY += 0.3*Dsky for loop3 in range(lenofell) : fluxList[loop3] += (-0.3)*Dsky*(aperarea[loop3]) print ("{0} percent done".format(100*loop/lenofell) ) fig = plt.figure(figsize=(8, 6)) plt.plot(eSMA, fluxList, label='Curve of Growth') plt.axvline(x=1.4*antieerFunc(0.95*fluxList[lenofell-1]), linestyle='--', color='b', label='standard aperture') plt.xlabel('Semi-major axis length [pixel]', fontsize=16) plt.legend(loc='lower right') plt.show() sky += DeltaSKY antieerFunc=interp1d(fluxList,eSMA) for loop in range(lenofell) : if eSMA[loop] > (1.4*antieerFunc(0.95*fluxList[lenofell-1])) : std2=loop break a2=eSMA[std2] b2=a2*( 1 - (ellip[std2]) ) posi2 = [ex_0[std2], ey_0[std2]] aperturestd = EllipticalAperture(positions=posi2,a=a2,b=b2,theta=(ePA[std2]+90)) stdflux=fluxList[std2] plt.figure(figsize=(10, 10)) norm = ImageNormalize(stretch=LogStretch()) plt.imshow(data, cmap='Greys', origin='lower', norm=norm) ax = plt.gca() ellipseAp = patches.Ellipse(xy=posi2,width=2*a2,height=2*b2,angle=(ePA[std2]+90),color='r',fill=False) ax.add_patch(ellipseAp) plt.show() result = { 'aperture': aperturestd , 'flux' : stdflux , 'sky': sky, 'fluxList': fluxList, 'stdn':std2 } return result
def do_aper_phot(data, catalog, extname, ivar_adu): # catalog should be the catalog with refined centroids # for **one CI camera** print('Attempting to do aperture photometry') positions = list(zip(catalog['xcentroid'], catalog['ycentroid'])) radii = aper_rad_pix(extname) apertures = [CircularAperture(positions, r=r) for r in radii] annulus_apertures = CircularAnnulus(positions, r_in=60.0, r_out=65.0) annulus_masks = annulus_apertures.to_mask(method='center') par = common.ci_misc_params() b_over_a = (1.0 if (extname == 'CIC') else par['nominal_mer_cd'] / par['nominal_sag_cd']) # the long axis of elliptical aperture (in terms of pixels) needs to # be in the CI pixel Y direction apertures_ell = [ EllipticalAperture(positions, a, a * b_over_a, theta=np.pi / 2) for a in radii ] # 107 um fiber diam, 9 um on a side for a pixel # fiber diam from Table 4.1 of https://arxiv.org/abs/1611.00037 rad_fiber_pix_sag = (107.0 / 9.0) / 2.0 deg_to_normal = 5.43 # [desi-commiss 522] if extname != 'CIC': rad_fiber_pix_mer = rad_fiber_pix_sag * np.sin(deg_to_normal / (180.0 / np.pi)) else: rad_fiber_pix_mer = rad_fiber_pix_sag aper_fib = EllipticalAperture(positions, rad_fiber_pix_sag, rad_fiber_pix_mer, theta=np.pi / 2) bkg_median = [] for mask in annulus_masks: annulus_data = mask.multiply(data) annulus_data_1d = annulus_data[mask.data > 0] # this sigma_clipped_stats call is actually the slow part !! _, median_sigclip, std_bg = sigma_clipped_stats(annulus_data_1d) bkg_median.append(median_sigclip) bkg_median = np.array(bkg_median) phot = aperture_photometry(data, apertures, error=aper_phot_unc_map(ivar_adu)) for i, aperture in enumerate(apertures): aper_bkg_tot = bkg_median * _get_area_from_ap(aperture) catalog['aper_sum_bkgsub_' + str(i)] = phot['aperture_sum_' + str(i)] - aper_bkg_tot catalog['aper_bkg_' + str(i)] = aper_bkg_tot catalog['aperture_sum_err_' + str(i)] = phot['aperture_sum_err_' + str(i)] ### del phot phot = aperture_photometry(data, apertures_ell, error=aper_phot_unc_map(ivar_adu)) for i, aperture in enumerate(apertures_ell): aper_bkg_tot = bkg_median * _get_area_from_ap(aperture) catalog['aper_ell_sum_bkgsub_' + str(i)] = phot['aperture_sum_' + str(i)] - aper_bkg_tot catalog['aper_ell_bkg_' + str(i)] = aper_bkg_tot catalog['aperture_ell_sum_err_' + str(i)] = phot['aperture_sum_err_' + str(i)] ### ### del phot phot = aperture_photometry(data, aper_fib, error=aper_phot_unc_map(ivar_adu)) aper_bkg_tot = bkg_median * _get_area_from_ap(aper_fib) catalog['aper_sum_bkgsub_fib'] = phot['aperture_sum'] - aper_bkg_tot catalog['aper_bkg_fib'] = aper_bkg_tot catalog['aperture_sum_err_fib'] = phot['aperture_sum_err'] #### # is .area() result a vector or scalar ?? catalog['sky_annulus_area_pix'] = _get_area_from_ap(annulus_apertures) catalog['sky_annulus_median'] = bkg_median
def mask_obj(img, snr=3.0, exp_sz= 1.2, plt_show = True): threshold = detect_threshold(img, snr=snr) center_img = len(img)/2 sigma = 3.0 * gaussian_fwhm_to_sigma# FWHM = 3. kernel = Gaussian2DKernel(sigma, x_size=5, y_size=5) kernel.normalize() segm = detect_sources(img, threshold, npixels=10, filter_kernel=kernel) npixels = 20 segm_deblend = deblend_sources(img, segm, npixels=npixels, filter_kernel=kernel, nlevels=15, contrast=0.001) #Number of objects segm_deblend.data.max() fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12.5, 10)) import copy, matplotlib my_cmap = copy.copy(matplotlib.cm.get_cmap('gist_heat')) # copy the default cmap my_cmap.set_bad('black') vmin = 1.e-3 vmax = 2.1 ax1.imshow(img, origin='lower', cmap=my_cmap, norm=LogNorm(), vmin=vmin, vmax=vmax) ax1.set_title('Data') ax2.imshow(segm_deblend, origin='lower', cmap=segm_deblend.cmap(random_state=12345)) ax2.set_title('Segmentation Image') plt.close() columns = ['id', 'xcentroid', 'ycentroid', 'source_sum', 'area'] cat = source_properties(img, segm_deblend) tbl = cat.to_table(columns=columns) tbl['xcentroid'].info.format = '.2f' # optional format tbl['ycentroid'].info.format = '.2f' print(tbl) from photutils import EllipticalAperture cat = source_properties(img, segm_deblend) segm_deblend_size = segm_deblend.areas apertures = [] for obj in cat: size = segm_deblend_size[obj.id] print 'obj.id', obj.id position = (obj.xcentroid.value, obj.ycentroid.value) a_o = obj.semimajor_axis_sigma.value b_o = obj.semiminor_axis_sigma.value size_o = np.pi * a_o * b_o r = np.sqrt(size/size_o)*exp_sz a, b = a_o*r, b_o*r theta = obj.orientation.value apertures.append(EllipticalAperture(position, a, b, theta=theta)) dis_sq = [np.sqrt((apertures[i].positions[0][0]-center_img)**2+(apertures[i].positions[0][1]-center_img)**2) for i in range(len(apertures))] dis_sq = np.asarray(dis_sq) c_index= np.where(dis_sq == dis_sq.min())[0][0] #from astropy.visualization.mpl_normalize import ImageNormalize #norm = ImageNormalize(stretch=SqrtStretch()) fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12.5, 10)) ax1.imshow(img, origin='lower', cmap=my_cmap, norm=LogNorm(), vmin=vmin, vmax=vmax) ax1.set_title('Data') ax2.imshow(segm_deblend, origin='lower', cmap=segm_deblend.cmap(random_state=12345)) ax2.set_title('Segmentation Image') for i in range(len(apertures)): aperture = apertures[i] aperture.plot(color='white', lw=1.5, ax=ax1) aperture.plot(color='white', lw=1.5, ax=ax2) if plt_show == True: plt.show() else: plt.close() from regions import PixCoord, EllipsePixelRegion from astropy.coordinates import Angle obj_masks = [] # In the script, the objects are 1, emptys are 0. for i in range(len(apertures)): aperture = apertures[i] x, y = aperture.positions[0] center = PixCoord(x=x, y=y) theta = Angle(aperture.theta/np.pi*180.,'deg') reg = EllipsePixelRegion(center=center, width=aperture.a*2, height=aperture.b*2, angle=theta) patch = reg.as_artist(facecolor='none', edgecolor='red', lw=2) fig, axi = plt.subplots(1, 1, figsize=(10, 12.5)) axi.add_patch(patch) mask_set = reg.to_mask(mode='center') mask = mask_set.to_image((len(img),len(img))) axi.imshow(mask, origin='lower') plt.close() obj_masks.append(mask) return obj_masks
def ap_phot(xcen, ycen, image, e1, e2, theta, zp): apertures = EllipticalAperture([(xcen, ycen)], e1, e2, theta) phot_table = aperture_photometry(image, apertures) phot_table['aperture_sum'].info.format = '%.8g' mag = -2.5 * np.log10(phot_table['aperture_sum'][0]) + zp return phot_table['aperture_sum'][0], mag
def plot_ellipse(self, first_time): self.ax_text.clear() if not first_time: self.ax.patches[1].remove() else: pass if self.photomorph == 'ellipse': aper = EllipticalAperture( (self.params['x0'], self.params['y0']), self.params['sma'], self.params['sma'] * (1. - self.params['eps']), self.params['pa']) self.ax_text.text( 0., 0.5, r"Mean intens path: {:.3} Jy/beam".format(self.mean_intens), fontsize=10, color='k', verticalalignment='top', transform=self.ax_text.transAxes, ) elif self.photomorph == 'annulus': aper = EllipticalAnnulus((self.params['x0'], self.params['y0']), self.params['sma']-self.beam_size_pix/2., self.params['sma']+self.beam_size_pix/2., (self.params['sma']+self.beam_size_pix/2.) \ *(1. - self.params['eps']), self.params['pa']) self.ax_text.text( 0., 0.5, r"Flux density annulus: {:.3} Jy".format(self.sum_intens), fontsize=10, color='k', verticalalignment='top', transform=self.ax_text.transAxes, ) self.ax_text.text( 0., 1, r"Mean intens path: {:.3} Jy/beam".format(self.mean_intens), fontsize=10, color='k', verticalalignment='top', transform=self.ax_text.transAxes, ) elif self.photomorph == 'circle': aper = CircularAperture((self.params['x0'], self.params['y0']), self.params['sma']) self.ax_text.text( 0., 0.5, r"Flux density circle: {:.3} Jy".format(self.sum_intens), fontsize=10, color='k', verticalalignment='top', transform=self.ax_text.transAxes, ) self.ax_text.text( 0., 1, r"Mean intens: {:.3} Jy/beam".format(self.mean_intens), fontsize=10, color='k', verticalalignment='top', transform=self.ax_text.transAxes, ) elif self.photomorph == 'ellipse_area': aper = EllipticalAperture( (self.params['x0'], self.params['y0']), self.params['sma'], self.params['sma'] * (1. - self.params['eps']), self.params['pa']) self.ax_text.text( 0., 0.5, r"Flux density circle: {:.3} Jy".format(self.sum_intens), fontsize=10, color='k', verticalalignment='top', transform=self.ax_text.transAxes, ) self.ax_text.text( 0., 1, r"Mean intens: {:.3} Jy/beam".format(self.mean_intens), fontsize=10, color='k', verticalalignment='top', transform=self.ax_text.transAxes, ) aper.plot(self.ax, color=self.color_ref_ellip) self.ax_text.set_axis_off() self.color_ref_ellip = 'r'
def segmentation_photometry(path_file_abs, bkg_sigma=3.0, source_snr=3.0, fwhm_kernel=2.0, x_size_kernel=3, y_size_kernel=3, clobber=False): """ aperture photometry from source segmentation make_source_mask not yet available in photutils v0.2.2, this version manually creates a source mask for determining background """ import os import copy import glob import pickle import numpy as np from scipy import ndimage import matplotlib #matplotlib.rcParams['text.usetex'] = True #matplotlib.rcParams['text.latex.unicode'] = True #from matplotlib.backends.backend_pdf import PdfPages import matplotlib.pyplot as plt from astropy.io import fits, ascii from astropy.convolution import Gaussian2DKernel from astropy.stats import sigma_clipped_stats, gaussian_fwhm_to_sigma # from astropy.table import Table from astropy.visualization import (LogStretch, mpl_normalize) # from astropy.extern.six.moves import StringIO from photutils import (detect_threshold, EllipticalAperture, source_properties, properties_table) from photutils.detection import detect_sources from photutils.utils import random_cmap # create preliminary mask #from photutils import make_source_mask #masterMask = make_source_mask(master, snr=2, npixels=5, dilate_size=11) # if LEDoff was used, get threshold from LEDoff/background # path_dataset = os.path.dirname(path_file_abs) + os.path.sep # filenameCombined = '\t'.join( # os.listdir(os.path.join(datasetDirLocal, 'master'))) # if 'master_ledoff_subtracted' in filename: # print('Using master_ledoff') # # path_file_abs = os.path.join(datasetDir, 'master', filename) # hdu = fits.open(path_file_abs)[0] # data_subtracted = hdu.data # # calculate threadhold # ledoff_pred = np.mean(data_subtracted) * \ # np.ones(data_subtracted.shape) # mse = mean_squared_error(data_subtracted, ledoff_pred) # rmse = np.sqrt(mse) # threshold = 7.0 * rmse # threshold_value = threshold # if no LEDoff was used, background subtraction is needed # there should exist no file named "subtracted" # if 'master.fit' in filenameCombined \ # or 'master_normalised.fit' in filenameCombined: #filenamedir = os.path.basename(path_file_abs) #print(filenamedir) #New stuff made by Parker f_dir, filename = os.path.split(path_file_abs) ff = os.path.splitext(filename)[0] new_dir = f_dir + '/' + ff if not os.path.exists(new_dir): os.makedirs(new_dir) dir_save = new_dir print("The photometry files will be saved in ", dir_save) filenames_combined = '\t'.join(os.listdir(dir_save)) if clobber == False \ and 'segm.obj' in filenames_combined \ and 'props.obj' in filenames_combined \ and 'props.csv' in filenames_combined\ and 'props.ecsv' in filenames_combined: print('Photometry properties table already exists. Reading objects...') segm = pickle.load( open(glob.glob(os.path.join(dir_save, '*segm.obj*'))[0], 'rb')) props = pickle.load( open(glob.glob(os.path.join(dir_save, '*props.obj*'))[0], 'rb')) return [segm, props] if 'master' in path_file_abs: if 'normalised' in path_file_abs: print('Performing photometry to ' + 'normalised master object image {}...'.format(path_file_abs)) else: print('Performing photometry to ' + 'un-normalised master image {}...'.format(path_file_abs)) else: print('Warning: Photometry being performed to ' + 'a single exposure {}...'.format(path_file_abs)) hdu = fits.open(path_file_abs)[0] data = hdu.data header = hdu.header if 'EXPREQ' in header: exptime = header['EXPREQ'] elif 'EXPTIME' in header: exptime = header['EXPTIME'] else: print('Exposure time not found in header. Cannot determine magnitude.') exptime = np.nan # === Iteratively determine background level === # assuming background is homogenous, estimate background by sigma clipping # if background noise varies across image, generate 2D background instead print('Determining background noise level...' '') [mean, median, std] = sigma_clipped_stats(data, sigma=bkg_sigma, iters=5) threshold = median + (std * 2.0) segm = detect_sources(data, threshold, npixels=5) # turn segm into a mask mask = segm.data.astype(np.bool) # dilate the source mask to ensure complete masking of detected sources dilate_structure = np.ones((5, 5)) mask_dilated = ndimage.binary_dilation(mask, structure=dilate_structure) # get sigma clipping stats of background, without sources that are masekd [bkg_mean, bkg_median, bkg_std] = sigma_clipped_stats(data, sigma=bkg_sigma, mask=mask_dilated, iters=3) # === Detect sources by segmentation === print('Determining threshold for source detection...') # determine threshold for source detection # in current implementation, if all inputs are present, the formula is # threshold = background + (background_error * snr) threshold = detect_threshold(data, background=bkg_median, error=bkg_std, snr=source_snr) print('Preparing 2D Gaussian kernal...') sigma_kernel = fwhm_kernel * gaussian_fwhm_to_sigma kernel = Gaussian2DKernel(sigma_kernel, x_size=x_size_kernel, y_size=y_size_kernel) # normalise kernel # The kernel models are normalized per default, ∫∞−∞f(x)dx=1∫−∞∞f(x)dx=1. # But because of the limited kernel array size, the normalization # for kernels with an infinite response can differ from one. kernel.normalize() # obtain a SegmentationImage object with the same shape as the data, # where sources are labeled by different positive integer values. # A value of zero is always reserved for the background. # if the threshold includes the background level as above, then the image # input into detect_sources() should not be background subtracted. print('Segmentation processing...') segm = detect_sources(data, threshold, npixels=5, filter_kernel=kernel) print('Segmentation labels are: ', repr(segm.labels)) # === Measure regional source properties === # source_properties() assumes that the data have been background-subtracted. # Background is the background level that was previously present # in the input data. # The input background does not get subtracted from the input data, # which should already be background-subtracted. print('Extracting source properties...') props = source_properties(data - bkg_median, segm, background=bkg_median) # add flux and instrumental magnitude to properties # flux = source_sum / exptime # instrumental magnitude = -2.5 * log10(flux) for i in range(len(props)): # source_sum is by definition background-subtracted already props[i].flux = props[i].source_sum / exptime props[i].mag_instr = -2.5 * np.log10(props[i].flux) # make plots and save to images # define approximate isophotal ellipses for each object apertures = [] r = 2.8 # approximate isophotal extent for prop in props: position = (prop.xcentroid.value, prop.ycentroid.value) a = prop.semimajor_axis_sigma.value * r b = prop.semiminor_axis_sigma.value * r theta = prop.orientation.value apertures.append(EllipticalAperture(position, a, b, theta=theta)) # create a table of properties try: props_table = properties_table(props) except: print('No source detected in {}'.format(path_file_abs)) return [None, None] props_table['flux'] = [props[i].flux for i in range(len(props))] props_table['mag_instr'] = [props[i].mag_instr for i in range(len(props))] # add custom columns to the table: mag_instru and flux # plot centroid and segmentation using approximate elliptical apertures norm = mpl_normalize.ImageNormalize(stretch=LogStretch()) rand_cmap = random_cmap(segm.max + 1, random_state=12345) #[fig1, (ax1, ax2)] = plt.subplots(1, 2, figsize = (12, 6)) #ax1.imshow(data, origin='lower', cmap=plt.cm.gray, norm=norm) #ax1.plot( # props_table['xcentroid'], props_table['ycentroid'], # ls='none', color='blue', marker='+', ms=10, lw=1.5) #ax2.imshow(segm, origin='lower', cmap=rand_cmap) #for aperture in apertures: # aperture.plot(ax=ax1, lw=1.0, alpha=1.0, color='red') # aperture.plot(ax=ax2, lw=1.0, alpha=1.0, color='red') # plot using actual segmentation outlines (to be improved) #[fig2, ax3] = plt.subplots(figsize = (6, 6)) #ax3.imshow(data, origin='lower', cmap=plt.cm.gray, norm=norm) #segm_outline = np.array(segm.outline_segments(), dtype=float) #segm_outline[segm_outline<1] = np.nan # get a copy of the gray color map #segm_outline_cmap = copy.copy(plt.cm.get_cmap('autumn')) # set how the colormap handles 'bad' values #segm_outline_cmap.set_bad(alpha=0) #ax3.imshow(segm_outline, origin='lower', cmap=segm_outline_cmap) # === save === # Save segm, porps to object files, and also save props to table file. print('Saving segmentation and source propdderties to {}...'.format( dir_save)) try: # if filename ends with fits, remove it in the filename if filename[-5:] == '.fits': dir_save_prefix = os.path.join(dir_save, filename[0:-5]) else: dir_save_prefix = os.path.join(dir_save, filename) # Enhanced CSV allows preserving table meta-data such as # column data types and units. # In this way a data table can be stored and read back as ASCII # with no loss of information. ascii.write(props_table, dir_save_prefix + '-phot_props.ecsv', format='ecsv') # csv for readability in MS excel ascii.write(props_table, dir_save_prefix + '-phot_props.csv', format='csv') # dump segmentation and properties to object files in binary mode file_segm = open(dir_save_prefix + '-phot_segm.obj', 'wb') pickle.dump(segm, file_segm) file_props = open(dir_save_prefix + '-phot_props.obj', 'wb') pickle.dump(props, file_props) # save figures #fig1.savefig(dir_save_prefix + '-phot_segm_fig1.png', dpi=600) #pp1 = PdfPages(dir_save_prefix + '-phot_segm_fig1.pdf') #pp1.savefig(fig1) #pp1.close() #fig2.savefig(dir_save_prefix + '-phot_segm_fig2.png', dpi=600) #pp2 = PdfPages(dir_save_prefix + '-phot_segm_fig2.pdf') #pp2.savefig(fig2) #pp2.close() print('Segmentation, properties objects, tables, and images saved to', dir_save) except: print('Unable to write to disk, check permissions.') return [segm, props]
def isophote_data() : from photutils.isophote import EllipseGeometry from photutils import EllipticalAperture from photutils.isophote import Ellipse test = 'a2744/cutouts/a2744_ID_5830_f160w.fits' (data, dim, photfnu, R_e, redshift, sma, smb, pa) = open_cutout(test) (noise, _, _, _, _, _, _, _) = open_cutout('a2744/cutouts/a2744_ID_5830_f160w_noise.fits') plt.display_image_simple(data, cmap=cm.viridis) xlen, ylen = dim[1], dim[0] geometry = EllipseGeometry(x0=xlen/2, y0=ylen/2, sma=20, eps=0.5, pa=70*np.pi/180) aper = EllipticalAperture((geometry.x0, geometry.y0), geometry.sma, geometry.sma*(1 - geometry.eps), geometry.pa) # pyp.imshow(data, origin='lower') # aper.plot(color='white') ellipse = Ellipse(data, geometry) isolist = ellipse.fit_image() # print(isolist.tflux_e) isophotes = True if isophotes : plt.display_isophotes(data, isolist, cmap=cm.viridis) # from photutils.isophote import build_ellipse_model # model = build_ellipse_model(data.shape, isolist) # residual = data - model # plt.display_image_simple(residual, norm=None) annuli = True if annuli : from photutils import EllipticalAnnulus from photutils import aperture_photometry center = (isolist[0].x0, isolist[0].y0) # print(center) last = np.where(isolist.stop_code == 0)[0][-1] isolist = isolist[last] pa = isolist.pa # print(pa*180/np.pi) a_outs = np.arange(1e-5, isolist.sma, isolist.sma/11) b_outs = a_outs*(1-isolist.eps) for i in range(len(a_outs) - 1) : a_in = a_outs[i] a_out = a_outs[i+1] b_in = b_outs[i] b_out = b_outs[i+1] # print(a_in, a_out, b_in, b_out) aper = EllipticalAnnulus(center, a_in, a_out, b_out, b_in=b_in, theta=isolist.pa) phot_table = aperture_photometry(data, aper, error=noise) flux = phot_table['aperture_sum'][0] flux_err = phot_table['aperture_sum_err'][0] # print(flux) # print(flux_err) annulus_mask = aper.to_mask() annulus_data = annulus_mask.multiply(data) # plt.display_image_simple(annulus_data, cmap=cm.viridis, norm=None) # print(np.sum(annulus_data)) err_table = aperture_photometry(noise, aper) flux_err_alt = err_table['aperture_sum'][0] # print(flux_err) err_data = annulus_mask.multiply(noise) # print(np.sum(err_data)) # print(flux/flux_err) print(flux/flux_err_alt) # print() return
def segmentation_photometry(path_image_abs, path_error_abs = None, logger = None, bkg_sigma = 1.5, source_snr = 1.05, fwhm_kernel = 25, x_size_kernel = 100, y_size_kernel = 80, dump_pickle = False, clobber = True): """ given a fits file (master image), this function calculates photometry by source segmentation. make_source_mask not yet available in photutils v0.2.2, this version manually creates a source mask for determining background. """ def msg(string, msgtype = None): if logger == None: print(string) else: print(string) if msgtype == 'info': logger.info(string) if msgtype == 'error': logger.error(string) if msgtype == 'warning': logger.warning(string) filename = os.path.basename(path_image_abs) dir_save = os.path.dirname(path_image_abs) filenames_combined = '\t'.join(os.listdir(dir_save)) if clobber == False \ and filename[0:-5]+'-segm.obj' in filenames_combined \ and filename[0:-5]+'-props.obj' in filenames_combined \ and filename[0:-5]+'-centroid_outline.png' in filenames_combined \ and filename[0:-5]+'-centroid_outline.pdf' in filenames_combined \ and filename[0:-5]+'-segmentation.png' in filenames_combined \ and filename[0:-5]+'-segmentation.pdf' in filenames_combined: msg('Photometry properties table already exists. ' + 'Reading pickles...', msgtype='info') try: segm = pickle.load(open(glob.glob(os.path.join( dir_save, filename[0:-5]+'-segm.obj*'))[0], 'rb')) props_list = pickle.load(open(glob.glob(os.path.join( dir_save, filename[0:-5]+'-props.obj*'))[0], 'rb')) return [segm, props_list] except: # pickle file corrupt or empty, proceed pass elif clobber == False \ and filename[0:-5]+'-logstretch.png' in filenames_combined \ and filename[0:-5]+'-logstretch.pdf' in filenames_combined: msg('Non-detection from previous results.', msgtype='info') return [None, []] # image type notifications if 'master' in path_image_abs: if 'normalised' in path_image_abs: msg('Performing photometry to ' + 'normalised master object image {}...'.format(path_image_abs), msgtype='info') else: msg('Performing photometry to ' + 'un-normalised master image {}...'.format(path_image_abs), msgtype='info') elif 'reduced' in path_image_abs: msg('Performing photometry to ' + 'reduced image frame {}...'.format(path_image_abs), msgtype='info') else: msg('Warning: Photometry being performed to ' + 'a single exposure {}...'.format(path_image_abs), msgtype='warning') # read in data try: hdu = fits.open(path_image_abs)['FPC'] data = hdu.data except: hdu = fits.open(path_image_abs)[0] data = hdu.data # read in error in data msg('Reading master error image {}...' .format(path_error_abs)) try: hdu_error = fits.open(path_error_abs)[0] data_error = hdu_error.data except: data_error = np.zeros(data.shape) msg('No master error image available for {}' .format(path_image_abs)) header = hdu.header if 'EXPREQ' in header: exptime = header['EXPREQ'] elif 'EXPTIME' in header: exptime = header['EXPTIME'] else: msg('Exposure time not found in header. ' + 'Cannot determine magnitude.', msgtype='error') exptime = np.nan # === Iteratively determine background level === # assuming backcground is homogenous, estimate background by sigma clipping # if background noise varies across image, generate 2D background instead # using the Background function msg('Determining background noise level...', msgtype='info') [mean, median, std] = sigma_clipped_stats(data, sigma=bkg_sigma, iters=3) threshold = median + (std * 4) segm = detect_sources(data, threshold, npixels=5) # turn segm into a mask mask = segm.data.astype(np.bool) # dilate the source mask to ensure complete masking of detected sources dilate_structure = np.ones((5, 5)) mask_dilated = ndimage.binary_dilation(mask, structure=dilate_structure) # get sigma clipping stats of background, without sources that are masekd [bkg_mean, bkg_median, bkg_std] = sigma_clipped_stats( data, sigma=bkg_sigma, mask=mask_dilated, iters = 3) # === Detect sources by segmentation === msg('Determining threshold for source detection...', msgtype='info') # determine threshold for source detection # in current implementation, if all inputs are present, the formula is # threshold = background + (background_error * snr) threshold = detect_threshold(data, background = bkg_median, error = data_error+bkg_std, snr = source_snr) # calculate total error including poisson statistics try: # this is for v0.3 and above msg('Calculating total errors including background and Poisson...', msgtype='info') err_tot = calc_total_error(data, bkg_error= data_error+bkg_std, effective_gain=0.37) gain = None # in version earlier than 0.3, this function is not available except: # error must be of the same shape as the data array # this is for v0.2.2 err_tot = data_error + bkg_std gain = 0.37 msg('Preparing 2D Gaussian kernal...', msgtype='info') sigma_kernel = fwhm_kernel * gaussian_fwhm_to_sigma kernel = Gaussian2DKernel(sigma_kernel, x_size = x_size_kernel, y_size = y_size_kernel) # normalise kernel # The kernel models are normalized per default, ∫∞−∞f(x)dx=1∫−∞∞f(x)dx=1. # But because of the limited kernel array size, the normalization # for kernels with an infinite response can differ from one. kernel.normalize() # obtain a SegmentationImage object with the same shape as the data, # where sources are labeled by different positive integer values. # A value of zero is always reserved for the background. # if the threshold includes the background level as above, then the image # input into detect_sources() should not be background subtracted. msg('Segmentation processing...', msgtype='info') segm = detect_sources(data, threshold, npixels=5, filter_kernel=kernel) msg('Segmentation labels are: ' + repr(segm.labels), msgtype='info') # === Measure regional source properties === # source_properties() assumes that the data have been background-subtracted. # Background is the background level that was previously present # in the input data. # The input background does not get subtracted from the input data, # which should already be background-subtracted. msg('Extracting source properties...', msgtype='info') if gain is None: # gain is no longer supported in v0.3 and included in total error array props_list = source_properties(data-bkg_median, segm, background = bkg_median, error = err_tot) else: # still in v0.2.2 props_list = source_properties(data-bkg_median, segm, background = bkg_median, error = err_tot, effective_gain = gain) # add more properties that are not automatically calculated for i in range(len(props_list)): # source_sum is by definition background-subtracted already props_list[i].flux = props_list[i].source_sum/exptime props_list[i].flux_err = props_list[i].source_sum_err/exptime # flux = source_sum / exptime # instrumental magnitude = -2.5 * log10(flux) props_list[i].mag_instr = -2.5 * np.log10(props_list[i].flux) props_list[i].mag_instr_err = -2.5 / props_list[i].flux / np.log(10) \ * props_list[i].flux_err # assuming fwhm of a circule gaussian of the same cross section area props_list[i].fwhm = gaussian_sigma_to_fwhm * np.sqrt( props_list[i].semimajor_axis_sigma.value * props_list[i].semiminor_axis_sigma.value) # make plots and save to images # define approximate isophotal ellipses for each object apertures = [] r = 5 # approximate isophotal extent for props in props_list: position = (props.xcentroid.value, props.ycentroid.value) a = props.semimajor_axis_sigma.value * r b = props.semiminor_axis_sigma.value * r theta = props.orientation.value apertures.append(EllipticalAperture(position, a, b, theta=theta)) # === plot and save === # if filename ends with fits, remove it in the filename if filename[-5:] == '.fits': path_save_prefix = os.path.join(dir_save, filename[0:-5]) else: path_save_prefix = os.path.join(dir_save, filename) norm_log = mpl_normalize.ImageNormalize(vmin=0, vmax=2000, stretch = LogStretch()) if len(props_list) > 0: # Save segm, porps to object files, and also save props to table file. msg('Saving segmentation and source properties to {}...' .format(dir_save), msgtype='info') # at least one source was detected # create a table of properties props_table = properties_table(props_list) # add custom columns to the table: mag_instru and flux props_table['flux'] = [props_list[i].flux for i in range(len(props_list))] props_table['flux_err'] = [props_list[i].flux_err for i in range(len(props_list))] props_table['mag_instr'] = [props_list[i].mag_instr for i in range(len(props_list))] props_table['mag_instr_err'] = [props_list[i].mag_instr_err for i in range(len(props_list))] props_table['fwhm'] = [props_list[i].fwhm for i in range(len(props_list))] # plot centroid and segmentation outline [fig1, ax1] = plt.subplots(figsize=(4, 3)) ax1.imshow(data, origin='lower', cmap=plt.cm.gray, norm=norm_log) ax1.plot(props_table['xcentroid'], props_table['ycentroid'], linestyle='none', color='red', marker='+', markersize=2, markeredgewidth=0.1, alpha=1) segm_outline = np.array(segm.outline_segments(), dtype=float) segm_outline[segm_outline<1] = np.nan # get a copy of the gray color map segm_outline_cmap = plt.cm.winter # set how the colormap handles 'bad' values segm_outline_cmap.set_bad(alpha=0) ax1.imshow(segm_outline, origin='lower', cmap=segm_outline_cmap, alpha=1) ax1.get_xaxis().set_visible(False) ax1.get_yaxis().set_visible(False) fig1.tight_layout() # segmentation image and aperture using approximate elliptical apertures [fig2, ax2] = plt.subplots(figsize=(4, 3)) rand_cmap = random_cmap(segm.max + 1, random_state=8) ax2.imshow(segm, origin='lower', cmap=rand_cmap) ax2.plot(props_table['xcentroid'], props_table['ycentroid'], linestyle='none', color='red', marker='+', markersize=2, markeredgewidth=0.1, alpha=1) for aperture in apertures: aperture.plot(ax=ax2, lw=0.1, alpha=1, color='lime') ax2.axis('off') ax2.get_xaxis().set_visible(False) ax2.get_yaxis().set_visible(False) fig2.tight_layout() try: # Enhanced CSV allows preserving table meta-data such as # column data types and units. # In this way a data table can be stored and read back as ASCII # with no loss of information. ascii.write(props_table, path_save_prefix + '-props.ecsv', format = 'ecsv') # csv for readability in MS excel ascii.write(props_table, path_save_prefix + '-props.csv', format = 'csv') # save figures fig1.savefig(path_save_prefix + '-centroid_outline.png', bbox_inches='tight', pad_inches=0, dpi=1200) fig2.savefig(path_save_prefix + '-segmentation.png', bbox_inches='tight', pad_inches=0, dpi=2000) pp1 = PdfPages(path_save_prefix + '-centroid_outline.pdf') pp1.savefig(fig1, dpi=1200) pp1.close() pp2 = PdfPages(path_save_prefix + '-segmentation.pdf') pp2.savefig(fig2, dpi=2000) pp2.close() if dump_pickle: # dump segmentation and properties to objects in binary mode file_segm = open(path_save_prefix + '-segm.obj', 'wb') pickle.dump(segm, file_segm) file_props = open(path_save_prefix + '-props.obj', 'wb') pickle.dump(props_list, file_props) msg('Segmentation, properties objects, tables, and images ' + 'saved to {}'.format(dir_save), msgtype='info') except: msg('Unable to write to disk, check permissions.', msgtype='error') # memory leak? try: plt.close('all') del (hdu, hdu_error, data, data_error, header, mask, mask_dilated, err_tot, kernel, apertures, norm_log, props_table, segm_outline, segm_outline_cmap, rand_cmap, fig1, ax1, fig2, ax2, pp1, pp2, file_segm, file_props) except: pass return [segm, props_list] else: msg('No source detected in {}'.format(path_image_abs), msgtype='warning') # save log scale stretched image, if no source was detected [fig0, ax0] = plt.subplots(figsize=(4, 3)) ax0.imshow(data, origin='lower', cmap=plt.cm.gray, norm=norm_log) ax0.get_xaxis().set_visible(False) ax0.get_yaxis().set_visible(False) try: fig0.savefig(path_save_prefix + '-logstretch.png', bbox_inches='tight', pad_inches=0, dpi=1200) pp0 = PdfPages(path_save_prefix + '-logstretch.pdf') pp0.savefig(fig0, dpi=1200) pp0.close() except: msg('Unable to write to disk, check permissions.', msgtype='error') return [None, []]