Source code for petrofit.petrosian.core

import numpy as np

from scipy.interpolate import interp1d

from matplotlib import pyplot as plt

from ..utils import closest_value_index, get_interpolated_values, pixel_to_angular, mpl_tick_frame
from ..photometry import radial_elliptical_aperture

__all__ = [
    'calculate_petrosian', 'calculate_petrosian_r',
    'calculate_concentration_index', 'Petrosian', 'fraction_to_r',
]


[docs] def calculate_petrosian(area_list, flux_list, area_err=None, flux_err=None): """ Given a list of aperture areas and associated fluxes, and their errors, computes the petrosian curve and its 1-sigma errors. Parameters ---------- area_list : numpy.array Array of aperture areas. flux_list : numpy.array Array of photometric flux values. area_err : numpy.array Array of errors in the aperture areas. If None and `flux_err` is provided, errors are calculated with `area_err` is set to zero. flux_err : numpy.array Array of errors in the flux values. If None and `area_err` is provided, errors are calculated with `flux_err` is set to zero. Returns ------- petrosian_list : numpy.array Array of petrosian values at each value of `area_list` petrosian_err : numpy.array Array of 1-sigma errors in the Petrosian values. """ # Convert input to array area_list = np.array(area_list) flux_list = np.array(flux_list) # Validate input assert len(area_list) == len(flux_list) assert len(area_list) > 2, "At least 3 data points are needed to compute Petrosian." assert np.all(np.diff(area_list) >= 0) # Compute the difference between consecutive elements in the area and flux arrays area_diff = np.diff(area_list) flux_diff = np.diff(flux_list) # Compute the average surface brightness within each aperture I_avg_within_r = flux_list[1:] / area_list[1:] # Compute the surface brightness at the edge of each aperture zero_mask = np.where(area_diff > 0) I_at_r = np.zeros_like(area_diff) I_at_r[zero_mask] = flux_diff[zero_mask] / area_diff[zero_mask] # Compute the Petrosian value at each radius petrosian_list = I_at_r / I_avg_within_r # Append the first Petrosian value to the beginning of the array petrosian_list = np.insert(petrosian_list, 0, np.nan) # Match input _, unique_indices = np.unique(area_list, return_inverse=True) petrosian_list = petrosian_list[unique_indices] if area_err is not None or flux_err is not None: # Convert input to array area_err = np.zeros_like(area_list) if area_err is None else np.array(area_err) flux_err = np.zeros_like(flux_list) if flux_err is None else np.array(flux_err) # Validate input assert len(area_err) == len(area_list) assert len(flux_err) == len(flux_list) # Compute the errors in the area and flux differences area_diff_err = np.sqrt(area_err[:-1] ** 2 + area_err[1:] ** 2) flux_diff_err = np.sqrt(flux_err[:-1] ** 2 + flux_err[1:] ** 2) # Compute the error in the surface brightness at the edge of each aperture I_at_r_err = np.zeros_like(area_diff) I_at_r_err[zero_mask] = abs(I_at_r[zero_mask]) * np.sqrt( (flux_diff_err[zero_mask] / flux_diff[zero_mask]) ** 2\ + (area_diff_err[zero_mask] / area_diff[zero_mask]) ** 2) # Compute the error in the average surface brightness within each aperture I_avg_within_r_err = abs(I_avg_within_r) * np.sqrt( (flux_err[1:] / flux_list[1:]) ** 2 + (area_err[1:] / area_list[1:]) ** 2) # Compute the error in the Petrosian value at each radius petrosian_err = np.zeros_like(area_diff) petrosian_err[zero_mask] = abs(petrosian_list[1:][zero_mask]) * np.sqrt( (I_at_r_err[zero_mask] / I_at_r[zero_mask]) ** 2 + \ (I_avg_within_r_err[zero_mask] / I_avg_within_r[zero_mask]) ** 2) # Append the first Petrosian error to the beginning of the array petrosian_err = np.insert(petrosian_err, 0, np.nan) # Match input petrosian_err = petrosian_err[unique_indices] return petrosian_list, petrosian_err return petrosian_list, None
[docs] def calculate_petrosian_r(r_list, petrosian_list, petrosian_err=None, eta=0.2, interp_kind='cubic', interp_num=5000): """ Calculate petrosian radius from photometric values using interpolation. The Petrosian radius is defined as the radius at which the petrosian profile equals eta. Parameters ---------- r_list : numpy.array Array of radii in pixels. petrosian_list : numpy.array Array of petrosian values at each value of `area_list` petrosian_err : numpy.array Array of 1-sigma errors in the Petrosian values. eta : float, default=0.2 Eta is the petrosian value which defines the `r_petrosian`. interp_kind : str or int, optional Specifies the kind of interpolation as a string or as an integer specifying the order of the spline interpolator to use. If set to `None`, the radius is computed descretely. The string has to be one of ‘linear’, ‘nearest’, ‘nearest-up’, ‘zero’, ‘slinear’, ‘quadratic’, ‘cubic’, ‘previous’, or ‘next’. ‘zero’, ‘slinear’, ‘quadratic’ and ‘cubic’ refer to a spline interpolation of zeroth, first, second or third order; ‘previous’ and ‘next’ simply return the previous or next value of the point; ‘nearest-up’ and ‘nearest’ differ when interpolating half-integers (e.g. 0.5, 1.5) in that ‘nearest-up’ rounds up and ‘nearest’ rounds down. Default is ‘linear’. interp_num : int Number of interpolation function sampling radii. Returns ------- r_petro : float or numpy.nan Petrosian radius r_petro_err : float or numpy.nan 1-sigma error in r_petro. Computed if petrosian_err is provided. """ # Convert input to array r_list = np.array(r_list) petrosian_list = np.array(petrosian_list) if petrosian_err is not None: petrosian_err = np.array(petrosian_err) # replace the first element with r=0 and petrosian=1 # This is because the petrosian for the first radius in r_list can not be # computed, we instead replace that value with r=0, since that value is known to equal 0. r_list[0] = 0 petrosian_list[0] = 1 # Interpolate values r_list_new, petrosian_list_new = get_interpolated_values(r_list, petrosian_list, kind=interp_kind, num=interp_num) idx = closest_value_index(eta, petrosian_list_new) r_petro = np.nan if idx is None else r_list_new[idx] if petrosian_err is None or np.isnan(r_petro): return r_petro, np.nan # Compute Errors # -------------- # Convert input to array petrosian_err[0] = 0 # Compute upper 1-sigma r_petro r_list_upper, petrosian_list_upper = get_interpolated_values(r_list, petrosian_list + petrosian_err, kind=interp_kind, num=interp_num) idx = closest_value_index(eta, petrosian_list_upper) r_petro_upper = np.nan if idx is None else r_list_upper[idx] # Compute lower 1-sigma r_petro r_list_lower, petrosian_list_lower = get_interpolated_values(r_list, petrosian_list - petrosian_err, kind=interp_kind, num=interp_num) idx = closest_value_index(eta, petrosian_list_lower) r_petro_lower = np.nan if idx is None else r_list_lower[idx] # Estimate error r_petro_err = (r_petro_upper - r_petro_lower) / 2. return r_petro, r_petro_err
[docs] def fraction_to_r(fraction, r_list, flux_list, r_petrosian, flux_err=None, r_petrosian_err=None, epsilon=2., epsilon_fraction=0.99, interp_kind='cubic', interp_num=5000): """ Given photometric values and `r_total_flux`, calculate radius which encloses a specified fraction of the total flux. Parameters ---------- fraction : float Fraction of flux total flux enclosed in target radius. r_list : numpy.array Array of radii in pixels. flux_list : numpy.array Array of photometric flux values. r_petrosian : float Petrosian radius flux_err : int or numpy.array, optional Array of errors in the flux values. If None, erros are not computed even if `r_petro_err` is provided. r_petrosian_err : float, optional 1-sigma error in r_petro. If set to `None` and `flux_err` is provided, `r_petro_err` is assumed to be 0 when computing errors. epsilon : float, default=2. Epsilon value (used to determine `r_epsilon`). N.B: `r_epsilon = r_petrosian` * epsilon` epsilon_fraction: float, default=0.99 Fraction of total flux that is recovered by `r_petrosian * epsilon`. interp_kind : str or int, optional Specifies the kind of interpolation used on the curve of growth (i.e `r_list` vs `flux_list`) interp_num : int Number of interpolation function sampling radii. Returns ------- r_fraction_flux : float or numpy.nan Radius which encloses a specified fraction of the total flux (determined by the Petrosian profile). r_fraction_flux_error : float or numpy.nan Error in `r_fraction_flux`. """ r_list = np.array(r_list) flux_list = np.array(flux_list) if flux_err is not None: flux_err = np.array(flux_err) if r_petrosian_err is not None: r_petrosian_err = np.array(r_petrosian_err) r_epsilon = r_petrosian * epsilon if r_epsilon > max(r_list): return np.nan, np.nan f = interp1d(r_list, flux_list, kind='cubic' if interp_kind is None else interp_kind) # Flux in r_epsilon epsilon_flux = f(r_epsilon) # Flux value corrsponding to fraction fractional_flux = epsilon_flux * (fraction / epsilon_fraction) # Get interp flux list and find r_frac: r_list_new, flux_list_new = get_interpolated_values(r_list, flux_list, kind=interp_kind, num=interp_num) idx = closest_value_index(fractional_flux, flux_list_new, growing=True) r_frac = np.nan if idx is None else r_list_new[idx] if np.isnan(r_frac) or flux_err is None: return r_frac, np.nan # Compute Errors # -------------- r_petrosian_err = 0 if r_petrosian_err is None or np.isnan(r_petrosian_err) else r_petrosian_err r_epsilon_err = r_petrosian_err # Compute the fractional_flux_err error f_lower = interp1d(r_list, flux_list - flux_err, kind='cubic' if interp_kind is None else interp_kind) f_upper = interp1d(r_list, flux_list + flux_err, kind='cubic' if interp_kind is None else interp_kind) fractional_flux_err_lower = f_lower(r_epsilon - r_epsilon_err) fractional_flux_err_upper = f_upper(r_epsilon + r_epsilon_err) fractional_flux_err = (fractional_flux_err_upper - fractional_flux_err_lower) / 2. # Find r_frac in the range of fractional_flux +/- fractional_flux_err fractional_flux_upper = fractional_flux + fractional_flux_err fractional_flux_lower = fractional_flux - fractional_flux_err r_list_lower, flux_list_lower = get_interpolated_values(r_list, flux_list - flux_err) idx = closest_value_index(fractional_flux_upper, flux_list_lower, growing=True) r_frac_upper = np.nan if idx is None else r_list_lower[idx] r_list_upper, flux_list_upper = get_interpolated_values(r_list, flux_list + flux_err) idx = closest_value_index(fractional_flux_lower, flux_list_upper, growing=True) r_frac_lower = np.nan if idx is None else r_list_upper[idx] r_frac_err = (r_frac_upper - r_frac_lower) / 2. return r_frac, r_frac_err
[docs] def calculate_concentration_index(fraction_1, fraction_2, r_list, flux_list, r_petrosian, flux_err=None, r_petrosian_err=None, epsilon=2., epsilon_fraction=0.99, interp_kind='cubic', interp_num=5000): """ Calculates Petrosian concentration index. ``concentration_index = C_1_2 = 5 * np.log10( r(fraction_2) / r(fraction_1) )`` Parameters ---------- fraction_1 : float Fraction of total light enclosed by the radius in the denominator. fraction_2 : float Fraction of total light enclosed by the radius in the numerator. r_list : numpy.array Array of radii in pixels. flux_list : numpy.array Array of photometric flux values. r_petrosian : float Petrosian radius flux_err : int or numpy.array, optional Array of errors in the flux values. If None, erros are not computed even if `r_petro_err` is provided. r_petrosian_err : float, optional 1-sigma error in r_petro. If set to `None` and `flux_err` is provided, `r_petro_err` is assumed to be 0 when computing errors. epsilon : float, default=2. Epsilon value (used to determine `r_epsilon`). N.B: `r_epsilon = r_petrosian` * epsilon` epsilon_fraction: float, default=0.99 Fraction of total flux that is recovered by `r_petrosian * epsilon`. interp_kind : str or int, optional Specifies the kind of interpolation used on the curve of growth (i.e `r_list` vs `flux_list`) interp_num : int Number of interpolation function sampling radii. Returns ------- r_fraction_1, r_fraction_2, concentration_index * r_fraction_1 : float or np.nan Radius containing `fraction_1` of the total flux. * r_fraction_2: float or np.nan Radius containing `fraction_2` of the total flux. * concentration_index : float or np.nan Concentration index """ r1 = fraction_to_r(fraction_1, r_list, flux_list, r_petrosian, flux_err=flux_err, r_petrosian_err=r_petrosian_err, epsilon=epsilon, epsilon_fraction=epsilon_fraction, interp_kind=interp_kind, interp_num=interp_num) r2 = fraction_to_r(fraction_2, r_list, flux_list, r_petrosian, flux_err=flux_err, r_petrosian_err=r_petrosian_err, epsilon=epsilon, epsilon_fraction=epsilon_fraction, interp_kind=interp_kind, interp_num=interp_num) r1 = r1[0] r2 = r2[0] if np.any(np.isnan(np.array([r1, r2]))): return [np.nan, np.nan, np.nan] return r1, r2, 5 * np.log10(r2 / r1)
[docs] class Petrosian: """ Class that computes and plots Petrosian properties. """ _eta = None _epsilon = None _epsilon_fraction = None _total_flux_fraction = None _r_plot_alpha = 0.7 def __init__(self, r_list, area_list, flux_list, area_err=None, flux_err=None, eta=0.2, epsilon=2., epsilon_fraction=0.99, total_flux_fraction=0.99, verbose=False, interp_kind='cubic', interp_num=5000): """ Petrosian properties. Parameters ---------- r_list : numpy.array Array of radii in pixels. area_list : numpy.array Array of aperture areas. flux_list : numpy.array Array of photometric flux values. area_err : numpy.array Array of errors in the aperture areas. If None and `flux_err` is provided, errors are calculated with `area_err` set to zero. flux_err : numpy.array Array of errors in the flux values. If None and `area_err` is provided, errors are calculated with `flux_err` is set to zero. eta : float, default=0.2 Eta is the petrosian value which defines the `r_petrosian`. epsilon : float Epsilon value (used to determine `r_total_flux`). N.B: `r_total_flux` = `r_petrosian` * `epsilon` epsilon_fraction: float, default=0.99 Fraction of total flux recovered by `r_petrosian * epsilon`. total_flux_fraction : float, default=0.99 Fraction of Sersic flux that defines the Petrosian total flux. Sersic total flux is the flux at infinity, thus a smaller fraction must be used to define the total flux when analysing images. `total_flux_fraction` can also be adjusted if the image has low signal-to-noise or if the profile extends too far out (for example profiles with high Sersic indices). verbose : bool Prints info interp_kind : str or int, optional Specifies the kind of interpolation used on the curve of growth (i.e `r_list` vs `flux_list`) interp_num : int Number of interpolation function sampling radii. """ self.verbose = verbose self._r_list = np.array(r_list) self._area_list = np.array(area_list) self._flux_list = np.array(flux_list) self._area_err = None if area_err is None else np.array(area_err) self._flux_err = None if flux_err is None else np.array(flux_err) self._validate_input_arrays() self.epsilon = float(epsilon) self.eta = float(eta) self.epsilon_fraction = float(epsilon_fraction) self.total_flux_fraction = float(total_flux_fraction) self.petrosian_list = None self.petrosian_err = None self.has_petrosian_err = None self.interp_kind = interp_kind self.interp_num = interp_num self._update_petrosian() def _validate_input_arrays(self): """ Check if input arrays are equal in size and have at least 3 data points """ assert len(self.r_list) > 2, "At least 3 data points are needed to compute Petrosian." assert len(self.r_list.shape) == 1, "Input arrays should be one dimensional." assert self.r_list.shape == self.flux_list.shape if self.flux_err is not None: assert self.flux_list.shape == self.flux_err.shape if self.area_err is not None: assert self.area_list.shape == self.area_err.shape def _update_petrosian(self): """ Updates the Petrosian properties based on current parameters. """ self.petrosian_list, self.petrosian_err = calculate_petrosian(self.area_list, self.flux_list, area_err=self.area_err, flux_err=self.flux_err) self.has_petrosian_err = self.petrosian_err is not None def _calculate_petrosian_r(self): """ Calculates the Petrosian radius based on the current Petrosian profile. """ return calculate_petrosian_r(self.r_list, self.petrosian_list, petrosian_err=self.petrosian_err, eta=self.eta, interp_kind=self.interp_kind, interp_num=self.interp_num) def _calculate_fraction_to_r(self, fraction): """ Calculates the radius containing a given fraction of the total flux. """ return fraction_to_r(fraction, self.r_list, self.flux_list, self.r_petrosian, flux_err=self.flux_err, r_petrosian_err=self.r_petrosian_err, epsilon=self.epsilon, epsilon_fraction=self.epsilon_fraction, interp_kind=self.interp_kind, interp_num=self.interp_num)
[docs] def concentration_index(self, fraction_1=0.2, fraction_2=0.8): """ Calculates Petrosian concentration index. ``concentration_index = C_1_2 = 5 * np.log10( r(fraction_2) / r(fraction_1) )`` Parameters ---------- fraction_1 : float Fraction of total light enclosed by the radius in the denominator. fraction_2 : float Fraction of total light enclosed by the radius in the numerator. Returns ------- r_fraction_1, r_fraction_2, concentration_index * r_fraction_1 : float or np.nan Radius containing `fraction_1` of the total flux. * r_fraction_2: float or np.nan Radius containing `fraction_2` of the total flux. * concentration_index : float or np.nan Concentration index """ return calculate_concentration_index(fraction_1, fraction_2, self.r_list, self.flux_list, self.r_petrosian, flux_err=self.flux_err, r_petrosian_err=self.r_petrosian_err, epsilon=self.epsilon, epsilon_fraction=self.epsilon_fraction, interp_kind=self.interp_kind, interp_num=self.interp_num)
@property def r_list(self): return self._r_list @property def area_list(self): return self._area_list @property def area_err(self): return self._area_err @property def flux_list(self): return self._flux_list @property def flux_err(self): return self._flux_err @property def epsilon(self): """ Epsilon value (used to determine `r_total_flux`). N.B: ``r_total_flux = r_petrosian * epsilon`` """ return self._epsilon @epsilon.setter def epsilon(self, value): self._epsilon = value @property def eta(self): """Eta is the Petrosian value which defines the `r_petrosian`""" return self._eta @eta.setter def eta(self, value): self._eta = value @property def epsilon_fraction(self): """Fraction of total flux recovered by `epsilon`""" return self._epsilon_fraction @epsilon_fraction.setter def epsilon_fraction(self, value): self._epsilon_fraction = value @property def total_flux_fraction(self): """ Fraction of Sersic flux that defines the Petrosian total flux. Sersic total flux is the flux at infinity, thus a smaller fraction must be used to define the total flux when analysing images. `total_flux_fraction` can also be adjusted if the image has low signal-to-noise or if the profile extends too far out (for example profiles with high Sersic indices). """ return self._total_flux_fraction @total_flux_fraction.setter def total_flux_fraction(self, value): self._total_flux_fraction = value @property def r_petrosian(self): """ The Petrosian radius is defined as the radius at which the Petrosian profile equals eta. """ r_p, _ = self._calculate_petrosian_r() return r_p @property def r_petrosian_err(self): """Estimated 1-sigma r_petrosian Error.""" _, r_p_err = self._calculate_petrosian_r() return r_p_err @property def r_epsilon(self): """r_epsilon = r_petrosian * epsilon""" return self.r_petrosian * self.epsilon @property def r_epsilon_in_range(self): """True if `r_epsilon` is in input radii list""" return self.r_list.min() <= self.r_epsilon <= self.r_list.max() @property def r_total_flux(self): """The radius containing the total flux""" r_frac, r_frac_err = self._calculate_fraction_to_r(self.total_flux_fraction) return r_frac @property def r_total_flux_err(self): """The radius containing the total flux""" r_frac, r_frac_err = self._calculate_fraction_to_r(self.total_flux_fraction) return r_frac_err @property def total_flux(self): """Returns the flux at `r_total_flux` or np.nan if out of range""" r_total_flux = self.r_total_flux if not min(self.r_list) <= r_total_flux <= max(self.r_list): return np.nan f = interp1d(self.r_list, self.flux_list, kind=self.interp_kind) total_flux = f(r_total_flux) return float(total_flux) @property def total_flux_err(self): """ Returns the photometric uncertainty at `r_total_flux` or np.nan if out of range. Note that this includes errors in estimating `r_total_flux`. """ r_total_flux = self.r_total_flux r_total_flux_err = self.r_total_flux_err if self.flux_err is None or np.isnan(self.total_flux): return np.nan f = interp1d(self.r_list, self.flux_err, kind='nearest') flux_err = f(self.r_total_flux) return np.sqrt(flux_err ** 2 + r_total_flux_err ** 2) @property def half_flux(self): """Returns the flux at `r_half_flux` or np.nan if out of range""" r_half_flux = self.r_half_light if not min(self.r_list) <= r_half_flux <= max(self.r_list): return np.nan f = interp1d(self.r_list, self.flux_list, kind=self.interp_kind) total_flux = f(r_half_flux) return float(total_flux) @property def half_flux_err(self): """ Returns the photometric uncertainty at `r_half_flux` or np.nan if out of range. Note that this includes errors in estimating `r_half_flux`. """ r_half_flux_err = self.r_half_flux_err if self.flux_err is None or np.isnan(self.half_flux): return np.nan f = interp1d(self.r_list, self.flux_err, kind='nearest') flux_err = f(self.r_half_flux) return np.sqrt(flux_err ** 2 + r_half_flux_err ** 2) @property def r_half_light(self): """R_e or Radius containing half of the total Petrosian flux""" r_e, r_e_err = self._calculate_fraction_to_r(0.5) return r_e @property def r_half_light_err(self): """Error estimate on r_e""" r_e, r_e_err = self._calculate_fraction_to_r(0.5) return r_e_err # Alias for r_half_light @property def r_50(self): return self.r_half_light @property def r_50_err(self): return self.r_half_light_err
[docs] def fraction_flux_to_r(self, fraction=0.5): """Given a fraction, compute the radius containing that fraction of the Petrosian total flux""" r_frac, r_frac_err = self._calculate_fraction_to_r(fraction) return r_frac
[docs] def fraction_flux_to_r_err(self, fraction=0.5): """Given a fraction, compute the radius containing that fraction of the Petrosian total flux""" r_frac, r_frac_err = self._calculate_fraction_to_r(fraction) return r_frac_err
[docs] def r_half_light_arcsec(self, wcs): """ Given a wcs, compute the radius containing half of the total Petrosian flux in arcsec. """ r_half_light = self.r_half_light if not np.isnan(r_half_light): return pixel_to_angular(r_half_light, wcs).value return np.nan
[docs] def r_total_flux_arcsec(self, wcs): """ Given a wcs, compute the radius containing the total Petrosian flux in arcsec. """ r_total_flux = self.r_total_flux if not np.isnan(r_total_flux): return pixel_to_angular(r_total_flux, wcs).value return np.nan
@property def c2080(self): """``c2080 = 5 * np.log10(r_80 / r_20)``""" return self.concentration_index(fraction_1=0.2, fraction_2=0.8)[-1] @property def c5090(self): """``c5090 = 5 * np.log10(r_90 / r_50)``""" return self.concentration_index(fraction_1=0.5, fraction_2=0.9)[-1] def _plot_radii(self, ax, radius_unit='pix'): radius_unit = '' if radius_unit is None else str(radius_unit) r_petrosian = self.r_petrosian if not np.isnan(r_petrosian): ax.axvline(r_petrosian, linestyle='--', color='black', alpha=self._r_plot_alpha, label=r"$R_{{p}}(\eta_{{{}}})={:0.4f}$ {}".format(self.eta, r_petrosian, radius_unit)) r_total_flux = self.r_total_flux if not np.isnan(r_total_flux): total_flux_fraction = int(self.total_flux_fraction * 100) ax.axvline(r_total_flux, linestyle='--', c='tab:red', alpha=self._r_plot_alpha, label="$R_{{total}}(L_{{{}}}) = {:0.4f}$ {}".format(total_flux_fraction, r_total_flux, radius_unit)) r_half_light = self.r_half_light if not np.isnan(r_half_light): ax.axvline(r_half_light, linestyle='--', c='tab:blue', alpha=self._r_plot_alpha, label="$R_{{50}}(L_{{50}}) = {:0.4f}$ {}".format(r_half_light, radius_unit))
[docs] def plot(self, plot_r=True, title='Petrosian Profile', radius_unit='pix', ax=None, color='tab:blue', err_alpha=0.2, err_capsize=3, show_legend=True, legend_fontsize=None, ax_fontsize=None, tick_fontsize=None): """ Plots the Petrosian profile. Parameters ---------- plot_r : bool, optional If True, plots the total flux and half-light radii. Default is True. title : str, optional Title for the plot. Default is 'Petrosian Profile'. radius_unit : str, optional Unit for the radius. Default is 'pix'. ax : matplotlib.axis, optional Matplotlib axis object to plot on. If None, creates a new axis. color : string Matplotlib color for profile. err_alpha : float, optional Transparency for the error region. Default is 0.2. err_capsize : int, optional Cap size for the error bars. Default is 3. show_legend : bool, optional If True, displays the legend. Default is True. legend_fontsize : int or float, optional Font size for the legend. If None, uses default font size. ax_fontsize : int or float, optional Font size for the axis labels. If None, uses default font size. tick_fontsize : int or float, optional Font size for the tick labels. If None, uses default font size. Returns ------- ax : matplotlib.axis Matplotlib axis object with the plot. """ radius_unit = '' if radius_unit is None else str(radius_unit) if ax is None: ax = plt.gca() ax.errorbar(self.r_list, self.petrosian_list, yerr=self.petrosian_err, marker='o', capsize=err_capsize, label="Data", color=color) if err_alpha is not None and self.has_petrosian_err and err_alpha > 0: ax.fill_between(self.r_list, self.petrosian_list - self.petrosian_err, self.petrosian_list + self.petrosian_err, alpha=err_alpha, color=color) r_petrosian = self.r_petrosian r_petrosian_err = self.r_petrosian_err if plot_r: r_color = 'black' ax.axhline(self.eta, linestyle='--', color=r_color, alpha=self._r_plot_alpha) if not np.isnan(r_petrosian): ax.axvline(r_petrosian, linestyle='--', color=r_color, alpha=self._r_plot_alpha, label=r"$R_{{p}}(\eta_{{{}}})={:0.4f}$ {}".format(self.eta, r_petrosian, radius_unit)) if not np.isnan(r_petrosian_err): ax.errorbar(r_petrosian, self.eta, xerr=r_petrosian_err, zorder=6, marker='o', capsize=5, lw=3, color='tab:orange') else: ax.scatter(r_petrosian, self.eta, zorder=6, marker='o', color='tab:orange') ax.axhline(0, c='black') ax.set_title(title, fontsize=ax_fontsize) ax.set_xlabel("Aperture Radius" + " [{}]".format(radius_unit) if radius_unit else "", fontsize=ax_fontsize) ax.set_ylabel(r"Petrosian Index $\eta(r)$", fontsize=ax_fontsize) mpl_tick_frame(minorticks=True, tick_fontsize=tick_fontsize) ax.set_xlim(0, None) if show_legend: ax.legend(fontsize=legend_fontsize) return ax
[docs] def plot_cog(self, plot_r=True, title='Curve of Growth', radius_unit='pix', flux_unit='', ax=None, color='tab:blue', err_alpha=0.2, err_capsize=3, show_legend=True, legend_fontsize=None, ax_fontsize=None, tick_fontsize=None): """ Plots the Curve of Growth (COG) for the Petrosian profile. Parameters ---------- plot_r : bool, optional If True, plots radii of interest including Petrosian radius. Default is True. title : str, optional Title for the plot. Default is 'Curve of Growth'. radius_unit : str, optional Unit for the radius. Default is 'pix'. flux_unit : str, optional Unit for the cumulative flux. Default is '' ax : matplotlib.axis, optional Matplotlib axis object to plot on. If None, creates a new axis. color : string Matplotlib color for profile. err_alpha : float, optional Transparency for the error region. Default is 0.2. err_capsize : int, optional Cap size for the error bars. Default is 3. show_legend : bool, optional If True, displays the legend. Default is True. legend_fontsize : int or float, optional Font size for the legend. If None, uses default font size. ax_fontsize : int or float, optional Font size for the axis labels. If None, uses default font size. tick_fontsize : int or float, optional Font size for the tick labels. If None, uses default font size. Returns ------- ax : matplotlib.axis Matplotlib axis object with the plot. """ radius_unit = '' if radius_unit is None else str(radius_unit) if ax is None: ax = plt.gca() ax.errorbar(self.r_list, self.flux_list, yerr=self.flux_err, marker='o', capsize=err_capsize, c=color, label="Data") if err_alpha is not None and self.has_petrosian_err and err_alpha > 0: ax.fill_between(self.r_list, self.flux_list - self.flux_err, self.flux_list + self.flux_err, alpha=err_alpha, color=color) if plot_r: r_half_light = self.r_half_light half_flux = self.half_flux if not np.isnan(r_half_light) and not np.isnan(half_flux): ax.axvline(r_half_light, linestyle='--', c='black', alpha=self._r_plot_alpha, label="$R_{{50}}(L_{{50}}) = {:0.4f}$ {}".format(r_half_light, radius_unit)) ax.axhline(half_flux, linestyle='--', c='black', alpha=self._r_plot_alpha) ax.scatter(r_half_light, half_flux, zorder=6, marker='o', color='tab:orange') r_total_flux = self.r_total_flux total_flux = self.total_flux if not np.isnan(r_total_flux) and not np.isnan(total_flux): total_flux_fraction = int(self.total_flux_fraction * 100) ax.axvline(r_total_flux, linestyle='-', c='black', alpha=self._r_plot_alpha, label="$R_{{total}}(L_{{{}}}) = {:0.4f}$ {}".format(total_flux_fraction, r_total_flux, radius_unit)) ax.axhline(total_flux, linestyle='-', c='black', alpha=self._r_plot_alpha) ax.scatter(r_total_flux, total_flux, zorder=6, marker='o', color='tab:orange') ax.set_title(title, fontsize=ax_fontsize) ax.set_xlabel("Aperture Radius" + " [{}]".format(radius_unit) if radius_unit else "", fontsize=ax_fontsize) ax.set_ylabel(r"$L(\leq r)$" + (" [{}]".format(flux_unit) if flux_unit else ''), fontsize=ax_fontsize) mpl_tick_frame(minorticks=True, tick_fontsize=tick_fontsize) ax.set_xlim(0, None) ax.set_ylim(0, None) if show_legend: ax.legend(fontsize=legend_fontsize) return ax
[docs] def imshow(self, position=(0, 0), elong=1., theta=0., color=None, lw=None): """ Make 2D plots of elliptical apertures with radii of `r_half_light`, `r_total_flux`, `r_20` and `r_80`. Parameters ---------- position : tuple (x, y) center of the apertures. elong : float Elongation of the aperture. theta : float The orientation of the aperture in rad. color : str Color override that will change the color of the apertures in the plot. lw : float Line width (thickness) of the plotted apertures. """ labels = ['r_half_light', 'r_total_flux', 'r_20', 'r_80'] radii = [self.r_half_light, self.r_total_flux, self._calculate_fraction_to_r(.2)[0], self._calculate_fraction_to_r(.8)[0]] colors = ['r', 'r', 'b', 'b'] linestyles = ['dashed', 'solid', 'dotted', 'dashdot'] for label, r, default_color, ls in zip(labels, radii, colors, linestyles): if not np.isnan(r) and r > 0: radial_elliptical_aperture(position, r, elong, theta).plot( label=label, linestyle=ls, color=color if color else default_color, lw=lw) plt.scatter(*position, marker='+', color=color if color else 'red')