def plot_gal_params( hdu: fits.HDUList, ras: Union[list, np.ndarray, float], decs: Union[list, np.ndarray, float], a: Union[list, np.ndarray, float], b: Union[list, np.ndarray, float], theta: Union[list, np.ndarray, float], colour: str = 'white', show_centre: bool = False, label: str = None, world: bool = True, world_axes: bool = True, **kwargs): """ :param hdu: :param ras: In degrees. :param decs: In degrees. :param a: In degrees. :param b: In degrees. :param theta: In degrees, apparently. :param colour: :param offset: :param show_centre: :return: """ # TODO: Rename parameters to reflect acceptance of pixel coordinates. _, pix_scale = ff.get_pixel_scale(hdu) n_y, n_x = hdu[0].data.shape header = hdu[0].header wcs_image = wcs.WCS(header=header) if world: xs, ys = wcs_image.all_world2pix(ras, decs, 0) else: xs = np.array(ras) ys = np.array(decs) theta = np.array(theta) # Convert to photutils angle format theta = u.world_angle_se_to_pu(theta, rot_angle=ff.get_rotation_angle(header)) a = u.dequantify(a) b = u.dequantify(b) for i, x in enumerate(xs): u.debug_print(2, "plotting.plot_gal_params(): x, ys[i] ==", x, ys[i]) if a[i] != 0 and b[i] != 0: if world_axes: ellipse = photutils.EllipticalAperture((x, ys[i]), a=a[i] / pix_scale, b=b[i] / pix_scale, theta=theta[i]) else: ellipse = photutils.EllipticalAperture((x, ys[i]), a=a[i], b=b[i], theta=theta[i]) ellipse.plot(**kwargs) line_label = None else: line_label = label if show_centre: plt.plot((0.0, n_x), (ys[i], ys[i]), c=colour, label=line_label) plt.plot((x, x), (0.0, n_y), c=colour)
def plot_gal_params(hdu: fits.HDUList, ras: Union[list, np.ndarray, float], decs: Union[list, np.ndarray, float], a: Union[list, np.ndarray, float], b: Union[list, np.ndarray, float], theta: Union[list, np.ndarray, float], colour: str = 'white', show_centre: bool = False, label: str = None, world: bool = True, world_axes: bool = True, line_style='-'): """ :param hdu: :param ras: In degrees. :param decs: In degrees. :param a: In degrees. :param b: In degrees. :param theta: In degrees, apparently. :param colour: :param offset: :param show_centre: :return: """ # TODO: Rename parameters to reflect acceptance of pixel coordinates. _, pix_scale = ff.get_pixel_scale(hdu) n_y, n_x = hdu[0].data.shape header = hdu[0].header wcs_image = wcs.WCS(header=header) if world: xs, ys = wcs_image.all_world2pix(ras, decs, 0) else: xs = np.array(ras) ys = np.array(decs) theta = np.array(theta) theta = -theta + ff.get_rotation_angle(header) # Convert to radians theta = theta * np.pi / 180 for i, x in enumerate(xs): if a[i] != 0 and b[i] != 0: if world_axes: ellipse = photutils.EllipticalAperture((x, ys[i]), a=a[i] / pix_scale, b=b[i] / pix_scale, theta=theta[i]) else: ellipse = photutils.EllipticalAperture((x, ys[i]), a=a[i], b=b[i], theta=theta[i]) ellipse.plot(color=colour, label=label, ls=line_style) line_label = None else: line_label = label if show_centre: plt.plot((0.0, n_x), (ys[i], ys[i]), c=colour, label=line_label) plt.plot((x, x), (0.0, n_y), c=colour)
def ellipses(ax, ap_center, a, b, theta, ap_radii, color="w"): """Plot apertures onto an image. inputs ------ ax: matplotlib.Axes instance with pixel stamp already plotted ap_centers: array-like ra and dec pixel coordinates ap_radii: array-like radii of apertures in pixel coordinates """ #plot_center = np.array([ap_center[1], ap_center[0]]) for rad in ap_radii: logging.debug("rad %f", rad) ap = photutils.EllipticalAperture(ap_center, a * rad, b * rad, theta=theta) ap.plot(ax=ax, color=color, linewidth=2) #kwargs={"color":color,"linewidth":2})
def photometry(self, ZP, ifilter, radius=3., show=False, outfile=None): """ Perform photometry Fills self.photom in place Half-light radii: https://iopscience.iop.org/article/10.1086/444475/pdf Args: ZP (float): Zero point magnitude ifilter (str): Filter name to be used in the anaysis radius (float, optional): Scaling for semimajor/minor axes for Elliptical apertures show: outfile: """ # Init if self.segm is None: raise ValueError("segm not set!") if self.hdu is None: self.load_image() # Zero point if isinstance(ZP, str): ZP = self.header[ZP] self.cat = photutils.segmentation.SourceCatalog( self.hdu.data - self.bkg.background, self.segm, background=self.bkg.background) # Apertures apertures = [] for obj in self.cat: position = np.transpose((obj.xcentroid, obj.ycentroid)) a = obj.semimajor_sigma.value * radius b = obj.semiminor_sigma.value * radius theta = obj.orientation.to(units.rad).value apertures.append( photutils.EllipticalAperture(position, a, b, theta=theta)) self.apertures = apertures # Magnitudes self.filter = ifilter self.photom = Table(self.cat.to_table()).to_pandas() self.photom[ifilter] = -2.5 * np.log10( self.photom['segment_flux']) + ZP # Add in ones lost in the pandas conversion Kron for key in ['kron_radius']: self.photom[key] = getattr(self.cat, key).value # pixel # Plot? if show or outfile is not None: norm = ImageNormalize(stretch=SqrtStretch()) fig = plt.figure(figsize=(6, 6)) # fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 12.5)) plt.clf() ax1 = plt.gca() ax1.imshow(self.hdu.data, origin='lower', cmap='Greys_r', norm=norm) ax1.set_title('Data') # for aperture in apertures: aperture.plot(axes=ax1, color='white', lw=1.5) if outfile is not None: # This must come first plt.savefig(outfile, dpi=300) if show: plt.show()
of the galaxy in the data. Then, make an elliptical aperture for each edge listed for this galaxy. The apertures should have a b/a equal to the convolved axis ratio we calculated above. Make sure to check the theta to make sure it matches the convention we expect. ''' # find (x,y) location of galaxy in the arry pixY, pixX = self.get_pix(wcs) # initialize aperture list apertureList = [] # for each radius, make an elliptical aperture. # make sure to keep the sub-pixel shifts (galaxy centers not exactly # on a single pixel) for r in self.edges: apertureList.append(photutils.EllipticalAperture(positions= (self.pscale+pixY%1, self.pscale+pixX%1), a=r, b=r*self.qConv, theta=math.radians(paIm)-math.radians(self.pa))) # return the whole list to use later return apertureList def calcPhotometry_detectionband(self, filterName, photflam, data, wcs, weight, seg, paIm, SNthresh=10.): '''Calculates aperture photometry for the detection band. The detection band is a different function from the rest of the bands because we'll use it to determine the total number of annuli. Essentially, we want to use the full list of apertures we calculated above, but *stop calculating* after we reach a given S/N threshold. We use a threshold of 10 in Suess+19, but this can be tuned depending on the use. Each annulus is 1 PSF FWHM wide.''' # make sure the catalog photometry is good for this object if self.flag != 1:
def rff_plot(self, rpet_scale=2, rhalf_scale=1): assert_data(["galfit/fit", "galfit/residual"], self.data) assert_properties(["rff", "rff_in", "rff_out"], self.data["galfit"]) fig, axs = plt.subplots(1, 3, figsize=(15, 5)) self._plot_normalized(self.data["img"], axs[0]) self._plot_normalized(self.data["galfit/fit"], axs[1]) self._plot_normalized(self.data["galfit/residual"], axs[2]) # Define values needed for calculation pxscale = self.data.attrs["pxscale"] rpet = self.data["statmorph"].attrs["rpetro_circ"] / pxscale rhalf = self.data["galfit"].attrs["rad"] / pxscale xc = self.data["galfit"].attrs["xc"] yc = self.data["galfit"].attrs["yc"] e = self.data["galfit"].attrs["e"] pa = self.data["galfit"].attrs["theta"] th = (pa - 90) * np.pi / 180 rff = self.data["galfit"].attrs["rff"] rff_in = self.data["galfit"].attrs["rff_in"] rff_out = self.data["galfit"].attrs["rff_out"] n = self.data["galfit"].attrs["sersic_n"] ap_pet1 = phot.CircularAperture((xc, yc), rpet_scale * rpet) # ap_pet2 = phot.CircularAperture( (xc, yc), rpet) ap_ser = phot.EllipticalAperture((xc, yc), rhalf_scale * rhalf, rhalf_scale * rhalf * e, theta=th) for i in [1, 2]: ap_pet1.plot(axes=axs[i], color="y", ls="-") # ap_pet2.plot(axes=axs[i], color="orange", ls="--") ap_ser.plot(axes=axs[i], color="red", ls="-") axs[2].annotate(f"RFF = {rff:2.3f}", xy=(0.05, 0.03), xycoords="axes fraction", ha="left", va="bottom", c="w", size=14, fontweight=800) axs[2].annotate(f"Inner RFF = {rff_in:2.3f}", xy=(0.05, 0.97), xycoords="axes fraction", ha="left", va="top", c="w", size=14, fontweight=800) axs[2].annotate(f"Outer RFF = {rff_out:2.3f}", xy=(0.05, 0.90), xycoords="axes fraction", ha="left", va="top", c="w", size=14, fontweight=800) axs[1].annotate(f"n = {n:2.1f}", xy=(0.05, 0.03), xycoords="axes fraction", ha="left", va="bottom", c="w", size=14, fontweight=800) plt.subplots_adjust(wspace=0.02) return fig, axs
for r in r_list: for subpix, method, label in subpix_list: line = "| ellipses r={0:2d} {1:8s} |".format(int(r), label) t0 = time.time() flux, fluxerr, flag = sep.sum_ellipse(data, x, y, a, b, theta, r, subpix=subpix) t1 = time.time() t_sep = (t1 - t0) * 1.e6 / naper / nloop line += " {0:7.2f} us/aper |".format(t_sep) if HAVE_PHOTUTILS: apertures = photutils.EllipticalAperture((x, y), a * r, b * r, theta) t0 = time.time() res = photutils.aperture_photometry(data, apertures, method=method, subpixels=subpix) t1 = time.time() t_pu = (t1 - t0) * 1.e6 / naper line += " {0:7.2f} us/aper | {1:6.2f} |".format(t_pu, t_pu / t_sep) print(line)
def get_rff(self, region="all", rpet_scale=2, rhalf_scale=1, overwrite=False): """Calculate the residual flux fraction, defined in Hoyos et al. (2011, 2012) RFF = (I(res) - 0.8 sky_rms )/I(model). We calculate RFF within a Petrosian radius, calculated by statmorph. There are 3 modes: 1. Total RFF within KRpet 2. RFF within inner KR_0.5 3. RFF within outer KR_0.5 and Kxrpet INPUTS: region: region of RFF calculation (all, inner, outer) rpet_scale: number of Petrosian radii wihin which RFF is found rhalf_scale: number of Sersic radii defining inner region for inner/outer overwrite: overwrite existing RFF meausrement for this region? """ # Check all required data exists assert_properties(["pxscale"], self.data) assert_data(["img", "mask", "galfit", "statmorph"], self.data) # Check if RFF is already computed kwords = {"all": "rff", "inner": "rff_in", "outer": "rff_out"} kword = kwords[region] if not clear_overwrite(kword, self.data["galfit"].attrs): return # Define values needed for calculation pxscale = self.data.attrs["pxscale"] rpet = self.data["statmorph"].attrs["rpetro_circ"] / pxscale xc = self.data["galfit"].attrs["xc"] yc = self.data["galfit"].attrs["yc"] mask = self.data["mask"] res = self.data["galfit/residual"] model = self.data["galfit/fit"] # 1. Calculate standard deviation of residual bg __, bgmed, bgdev = sigma_clipped_stats(res, mask=self.data["mask"]) res = res - bgmed # 2. Define aperture based on RFF region ap_pet = phot.CircularAperture((xc, yc), rpet_scale * rpet) if region != "all": e = self.data["galfit"].attrs["e"] pa = self.data["galfit"].attrs["theta"] th = (pa - 90) * np.pi / 180 rhalf = self.data["galfit"].attrs["rad"] / pxscale ap_ser = phot.EllipticalAperture((xc, yc), rhalf, rhalf * e, theta=th) if region == "inner": ap = ap_ser elif region == "outer": ap = ap_pet ap_mask = ap_ser.to_mask().to_image(mask.shape) mask = np.logical_or(mask, ap_mask) elif region == "all": ap = ap_pet # 3. Calculate residual and model flux resflux = ap.do_photometry(np.abs(res), mask=mask)[0][0] modflux = ap.do_photometry(model, mask=mask)[0][0] # 4. Number of pixels in the sum: numpix = ap.area - ap.do_photometry(mask)[0][0] bgflux = np.sqrt(2 / np.pi) * bgdev * numpix # 5. Compute RFF rff = (resflux - bgflux) / modflux self.data["galfit"].attrs[kword] = rff