GetImageFlux ============ .. py:module:: GetImageFlux .. autoapi-nested-parse:: GetImageFlux - Extended Source Photometry from Region Files Space Telescope Science Institute Synopsis -------- Calculate fluxes in source and background regions from FITS images using elliptical and annular apertures defined in a region table file. Computes both raw and background-subtracted fluxes for extended sources. Command Line Usage ------------------ :: GetImageFlux.py [-h] [-viz] [-auto_back] [-gap N] image1.fits [image2.fits ...] region_table.txt GetImageFlux.py [-viz] [-auto_back] [-gap N] -match match_file.txt region_table.txt **Required Arguments:** image1.fits, image2.fits, ... One or more FITS files to process. Each file should have a primary (flux) extension and optionally a MASK extension for bad pixel exclusion. region_table.txt A region file containing source region definitions. If ``-auto_back`` is used, this file only needs source regions. Otherwise, it must contain both source and background region rows (SourceBack='Source' and 'Back'). **Optional Arguments:** -h Display this help message and exit. -viz Create visualization plots for each source region. Plots are saved to the ``Figs_Flux/`` directory (created if necessary). Shows both source ellipse (red) and background annulus (magenta). -auto_back Automatically generate background annulus regions from a source-only input file. The background annulus is calculated to have the same area as the source ellipse. A new region file ``_with_back.txt`` is created and used for processing. -gap N Gap in arcseconds between the source outer edge and the background annulus inner radius. Default is 3.0 arcsec. Only used with ``-auto_back``. -match match_file.txt Use a match file (output from ``ImageMatch2Source.py``) to drive which source/image combinations are processed. The match file must have columns ``Source_name`` and ``filename``. When ``-match`` is used, no explicit image files are needed on the command line — they come from the match file's ``filename`` column. Only the matched sources are processed for each image, rather than all sources in the region table. -filter name Insert a filter identifier into the output filename. For example, ``-filter ha`` produces ``Flux_ha_.txt`` instead of ``Flux_.txt``. Useful to avoid overwriting results from different filters. Description ----------- This module performs aperture photometry on extended sources defined in a region table. For each source, it calculates: * Source flux from the defined elliptical region * Background flux from the corresponding annular region * Net flux (source minus scaled background) * An estimate of the flux error due to background uncertainty * Statistical measures (mean, median, mode, std) The net flux is computed as:: net_flux = source_flux - num_pixels_used * back_median where ``source_flux`` is the summed pixel values in the source aperture (containing both the real source signal and background), ``back_median`` is the median pixel value in the background annulus, and ``num_pixels_used`` is the number of unmasked pixels in the source aperture. The median is used because it is robust to contaminating sources within the background region. The flux error is estimated by splitting the background annulus into radial sub-annuli (default 4), computing the median in each, and taking the standard deviation of those medians. This measures how the background level varies with distance from the source, which is the dominant source of uncertainty for extended-source photometry. The per-pixel uncertainty is then scaled to the source aperture:: flux_err = num_pixels_used * std(sub_annulus_medians) The region table must contain the following columns: * Source_name : str - Identifier for the source * RA : float - Right Ascension in degrees * Dec : float - Declination in degrees * RegType : str - Region type ('ellipse' or 'annulus') * Major : float - Semi-major axis in arcseconds * Minor : float - Semi-minor axis in arcseconds * Theta : float - Position angle in degrees * SourceBack : str - 'Source' or 'Back' to identify region type Output ------ A single consolidated output file is written containing three row types for each source/image combination, identified by the ``SourceBack`` column: * **Source** -- photometry of the source region * **Back** -- photometry of the background region * **Net** -- background-subtracted values Key output columns: * ``flux`` -- total flux (DN) in the aperture; for Net rows this is background-subtracted * ``flux_err`` -- for Back rows, the per-pixel background uncertainty (std of radial sub-annulus medians); for Net rows, the total flux error (num_pixels_used * back_flux_err); 0 for Source rows * ``mean``, ``median`` -- per-pixel statistics; for Net rows, source minus background * ``std`` -- standard deviation of pixel values in the aperture * ``surface_brightness_per_arcsec2`` -- flux per square arcsecond * ``num_pixels_used`` -- number of unmasked pixels in the aperture * ``frac_in_image`` -- fraction of the aperture within the image (1.0 = fully contained) * ``Image`` -- which FITS file the measurement came from If ``-viz`` is specified, visualization plots are saved to: * ``Figs_Flux/_.png`` - Aperture visualization for each source If ``-auto_back`` is specified, a new region file is created: * ``_with_back.txt`` - Region file with auto-generated background regions Notes ----- **Source-only input format (for use with -auto_back):** When using ``-auto_back``, the input file only needs source definitions:: Source_name RA Dec RegType Major Minor Theta -------------- --------- ---------- ------- ------ ------ ----- SNR_N11L 73.702779 -66.429249 ellipse 35.00 35.00 0 SNR_N86 73.904167 -68.646389 ellipse 183.00 183.00 0 The background annulus is automatically calculated with: * Inner radius = max(Major, Minor) + gap (default gap = 3 arcsec) * Outer radius chosen so annulus area = source ellipse area **Full region table format (standard input):** Without ``-auto_back``, the input must have both source and background rows:: Source_name RA Dec RegType Major Minor Theta SourceBack -------------- --------- ---------- ------- ------ ------ ----- ---------- SNR_N11L 73.702779 -66.429249 ellipse 35.00 35.00 0 Source SNR_N11L 73.702779 -66.429249 annulus 105.00 70.00 0 Back SNR_N86 73.904167 -68.646389 ellipse 183.00 183.00 0 Source SNR_N86 73.904167 -68.646389 annulus 401.00 218.00 0 Back For 'ellipse' regions, Major and Minor are semi-axes of the ellipse. For 'annulus' regions, Major is the outer radius and Minor is the inner radius. History ------- 250808 ksl Initial coding 250122 ksl Added Sphinx documentation .. moduleauthor:: KSL Functions --------- .. autoapisummary:: GetImageFlux.add_net_rows GetImageFlux.background_uncertainty GetImageFlux.calculate_background_annulus GetImageFlux.circular_photometry GetImageFlux.create_bad_pixel_mask GetImageFlux.do_all GetImageFlux.do_many GetImageFlux.do_one GetImageFlux.elliptical_annulus_photometry GetImageFlux.elliptical_photometry GetImageFlux.elliptical_region_photometry GetImageFlux.generate_region_table GetImageFlux.get_pixel_scale GetImageFlux.load_fits_image GetImageFlux.print_photometry_summary GetImageFlux.results2table GetImageFlux.steer GetImageFlux.visualize_aperture_region Module Contents --------------- .. py:function:: add_net_rows(xtab) Add background-subtracted Net rows to a photometry table. For each source that has both Source and Back entries (matched by Source_name and Image), compute a Net row with background-subtracted flux and statistics. Rows are ordered as Source, Back, Net for each source in each image. The net flux is computed as:: net_flux = source_flux - num_pixels_used_source * back_median where ``source_flux`` is the total (summed) flux in the source aperture (which includes both the real source signal and the background contribution), ``back_median`` is the median pixel value in the background annulus, and ``num_pixels_used_source`` is the number of unmasked pixels that contributed to the source flux. The median is used rather than the mean because it is robust to contaminating sources within the background annulus. The flux error is propagated from the per-pixel background uncertainty measured by ``background_uncertainty()``:: net_flux_err = num_pixels_used_source * back_flux_err where ``back_flux_err`` is the standard deviation of the median pixel values measured in radial sub-annuli of the background region. Parameters ---------- xtab : astropy.table.Table Table containing Source and Back entries identified by the SourceBack column. Returns ------- astropy.table.Table Table with rows grouped as Source, Back, Net per source/image. The ``flux_err`` column contains: 0 for Source rows, the per-pixel background uncertainty for Back rows, and the propagated total flux error for Net rows. .. py:function:: background_uncertainty(pixel_coord, r_inner_pix, r_outer_pix, data, bad_pixel_mask, n_sub=4) Estimate background uncertainty by radially subsampling an annulus. Splits the background annulus into concentric sub-annuli and computes the median in each. The standard deviation of these medians measures how the background level varies with radius, which is the dominant source of uncertainty for extended source photometry. Parameters ---------- pixel_coord : tuple (x, y) pixel coordinates of the annulus center. r_inner_pix : float Inner radius of the background annulus in pixels. r_outer_pix : float Outer radius of the background annulus in pixels. data : numpy.ndarray 2D image data array. bad_pixel_mask : numpy.ndarray Boolean mask where True indicates bad pixels. n_sub : int, optional Number of radial sub-annuli. Default is 4. Returns ------- float Standard deviation of the sub-annulus medians (per-pixel background uncertainty). Returns 0.0 if fewer than 2 sub-annuli have valid data. .. py:function:: calculate_background_annulus(a_arcsec, b_arcsec, gap=3.0) Calculate background annulus radii to match source ellipse area. Given a source ellipse, calculates the inner and outer radii of a circular background annulus such that: - Inner radius = max(a, b) + gap (starts outside source with buffer) - Outer radius chosen so annulus area equals source ellipse area Parameters ---------- a_arcsec : float Semi-major axis of source ellipse in arcseconds. b_arcsec : float Semi-minor axis of source ellipse in arcseconds. gap : float, optional Gap between source outer edge and background inner radius in arcseconds. Default is 3.0. Returns ------- r_inner : float Inner radius of background annulus in arcseconds. r_outer : float Outer radius of background annulus in arcseconds. Notes ----- The area calculation: - Source ellipse area: A = π * a * b - Annulus area: A = π * (r_out² - r_in²) - Setting equal: r_out = sqrt(r_in² + a * b) Examples -------- >>> r_in, r_out = calculate_background_annulus(35.0, 35.0, gap=3.0) >>> print(f"Inner: {r_in:.1f}, Outer: {r_out:.1f}") Inner: 38.0, Outer: 51.7 .. py:function:: circular_photometry(fits_file, ra, dec, radius_arcsec, radius_in_arcsec=None, include_zero_mask=True, image_data=None) Perform circular aperture photometry. Convenience wrapper using elliptical functions with equal semi-axes. Parameters ---------- fits_file : str Path to the FITS file. ra : float Right Ascension in degrees. dec : float Declination in degrees. radius_arcsec : float Outer radius in arcseconds. radius_in_arcsec : float, optional Inner radius for annulus in arcseconds. If provided, creates a circular annulus. include_zero_mask : bool, optional Whether to mask zero-valued pixels. Default is True. image_data : dict, optional Pre-loaded image data from load_fits_image(). Default is None. Returns ------- dict Photometry results dictionary. See elliptical_photometry(). .. py:function:: create_bad_pixel_mask(data, value_range=None, specific_values=None) Create a boolean mask for bad pixels in an image. Identifies bad pixels based on NaN values, value range limits, and specific flagged values. Parameters ---------- data : numpy.ndarray 2D (or N-D) array of image data. value_range : tuple of (float, float), optional Mask pixels with values outside this (min, max) range. If None, no range masking is applied. specific_values : list, set, or tuple, optional Mask pixels matching these exact values (e.g., {0, -999, 9999}). Returns ------- numpy.ndarray Boolean mask where True indicates bad pixels. Examples -------- >>> mask = create_bad_pixel_mask(image_data, value_range=(-100, 100)) >>> masked_data = np.ma.array(image_data, mask=mask) .. py:function:: do_all(image_file, region_table, create_visualization=False, source_names=None) Process all sources in a region table for one image. Parameters ---------- image_file : str Path to the FITS image file. region_table : str Path to the region table file. create_visualization : bool, optional If True, create visualization plots for each source region. Plots are saved to ``Figs_Flux/`` directory. Default is False. source_names : list of str, optional If provided, only process sources whose Source_name is in this list. Used by ``-match`` mode to restrict processing to matched sources. Default is None (process all sources). Returns ------- astropy.table.Table or None Photometry results table with Source and Back rows and an 'Image' column, or None if no regions overlap the image. .. py:function:: do_many(xtab, image_file, create_visualization=False) Process all regions in a table for a given image. Iterates through the region table and performs photometry for each source and background region. When visualization is enabled, creates plots showing both the source region and background annulus. Parameters ---------- xtab : astropy.table.Table Region table with columns: Source_name, RA, Dec, RegType, Major, Minor, Theta, SourceBack. image_file : str Path to the FITS image file. create_visualization : bool, optional If True, create visualization plots for source regions showing both the source ellipse and background annulus. Default is False. Returns ------- list of dict List of photometry result dictionaries, one per region. Raises ------ ValueError If an unknown RegType is encountered. .. py:function:: do_one(source_name='SNR_N49', region_file='lmc_spec_ann_reg.txt', image_file='../LMC/LMC.ha.csub.fits.gz') Process a single source from a region file. Extracts photometry for one source and its background region, useful for testing and debugging. Parameters ---------- source_name : str, optional Name of the source to process. Default is 'SNR_N49'. region_file : str, optional Path to the region table file. image_file : str, optional Path to the FITS image file. Returns ------- list of dict Two-element list containing [source_results, background_results]. .. py:function:: elliptical_annulus_photometry(fits_file, ra, dec, a_out_arcsec, b_out_arcsec, a_in_arcsec, b_in_arcsec, theta_deg=0, include_zero_mask=True, image_data=None) Perform photometry in an elliptical annulus. Convenience wrapper for elliptical_photometry() for annular apertures. Parameters ---------- fits_file : str Path to the FITS file. ra : float Right Ascension in degrees. dec : float Declination in degrees. a_out_arcsec : float Semi-major axis of outer ellipse in arcseconds. b_out_arcsec : float Semi-minor axis of outer ellipse in arcseconds. a_in_arcsec : float Semi-major axis of inner ellipse in arcseconds. b_in_arcsec : float Semi-minor axis of inner ellipse in arcseconds. theta_deg : float, optional Position angle in degrees. Default is 0. include_zero_mask : bool, optional Whether to mask zero-valued pixels. Default is True. image_data : dict, optional Pre-loaded image data from load_fits_image(). Default is None. Returns ------- dict Photometry results dictionary. See elliptical_photometry(). .. py:function:: elliptical_photometry(fits_file, ra, dec, a_arcsec, b_arcsec, theta_deg=0, a_in_arcsec=None, b_in_arcsec=None, include_zero_mask=True, image_data=None) Perform elliptical aperture photometry at a sky position. Unified function that handles both simple elliptical apertures and elliptical annuli for background estimation. Parameters ---------- fits_file : str Path to the FITS file containing the image data. ra : float Right Ascension of the aperture center in degrees. dec : float Declination of the aperture center in degrees. a_arcsec : float Semi-major axis of outer ellipse in arcseconds. b_arcsec : float Semi-minor axis of outer ellipse in arcseconds. theta_deg : float, optional Position angle of ellipse in degrees, measured counter-clockwise from the positive x-axis. Default is 0. a_in_arcsec : float, optional Semi-major axis of inner ellipse in arcseconds. If provided along with b_in_arcsec, creates an elliptical annulus. b_in_arcsec : float, optional Semi-minor axis of inner ellipse in arcseconds. include_zero_mask : bool, optional Whether to mask pixels with zero values. Default is True. image_data : dict, optional Pre-loaded image data from load_fits_image(). If provided, the FITS file is not opened again. Default is None. Returns ------- dict Dictionary containing photometry results with keys: * flux : Total flux in aperture * surface_brightness_per_pixel : Flux per pixel * surface_brightness_per_arcsec2 : Flux per square arcsecond * area_pixels, area_arcsec2 : Aperture areas * num_pixels_total, num_pixels_used, num_pixels_masked : Pixel counts * mean, median, mode, std, min, max : Statistical measures * aperture_type : 'elliptical_aperture' or 'elliptical_annulus' * Aperture geometry parameters Notes ----- If the FITS file contains a 'MASK' extension, it will be used for bad pixel masking. Otherwise, a mask is created from NaN values and the specified value_range. Examples -------- >>> results = elliptical_photometry('image.fits', 73.7, -66.4, 35.0, 35.0) >>> print(f"Flux: {results['flux']:.2e}") .. py:function:: elliptical_region_photometry(fits_file, ra, dec, a_arcsec, b_arcsec, theta_deg=0, include_zero_mask=True, image_data=None) Perform photometry in a simple elliptical aperture. Convenience wrapper for elliptical_photometry() for simple apertures (no annulus). Parameters ---------- fits_file : str Path to the FITS file. ra : float Right Ascension in degrees. dec : float Declination in degrees. a_arcsec : float Semi-major axis in arcseconds. b_arcsec : float Semi-minor axis in arcseconds. theta_deg : float, optional Position angle in degrees. Default is 0. include_zero_mask : bool, optional Whether to mask zero-valued pixels. Default is True. image_data : dict, optional Pre-loaded image data from load_fits_image(). Default is None. Returns ------- dict Photometry results dictionary. See elliptical_photometry(). .. py:function:: generate_region_table(input_file, gap=3.0) Generate a complete region table with auto-generated background regions. Reads a source-only region file and generates corresponding background annulus regions for each source, with area matching the source region. Parameters ---------- input_file : str Path to input region file containing source regions only. Must have columns: Source_name, RA, Dec, RegType, Major, Minor, Theta. gap : float, optional Gap between source outer edge and background inner radius in arcseconds. Default is 3.0. Returns ------- output_file : str Path to the generated output file with both source and background regions. Named as ``_with_back.txt``. combined_table : astropy.table.Table Table containing both source and background region rows. Notes ----- The output table includes all columns from the input plus: - SourceBack : 'Source' or 'Back' to identify region type For each source row, a corresponding background row is created with: - RegType = 'annulus' - Major = outer radius of background annulus - Minor = inner radius of background annulus - SourceBack = 'Back' The output file is written in ascii.fixed_width_two_line format. Examples -------- >>> outfile, table = generate_region_table('sources.txt', gap=5.0) >>> print(f"Generated {outfile} with {len(table)} rows") .. py:function:: get_pixel_scale(fits_header) Calculate the pixel scale from a FITS header. Extracts the pixel scale using either CDELT keywords or the CD matrix from the WCS information in the header. Parameters ---------- fits_header : astropy.io.fits.Header FITS file header containing WCS information. Returns ------- float Pixel scale in arcseconds per pixel. Notes ----- The function first attempts to use CDELT1/CDELT2 keywords if present. If not available, it calculates the scale from the CD matrix via the WCS. The returned value is the mean of the two axes' scales. .. py:function:: load_fits_image(fits_file) Load a FITS image and return all derived quantities needed for photometry. Reads the FITS file once and extracts the data, header, bad pixel mask, WCS, and pixel scale. This avoids redundant I/O when processing multiple regions on the same image. Parameters ---------- fits_file : str Path to the FITS file containing the image data. Returns ------- dict Dictionary with keys: * data : numpy.ndarray - 2D image data from the primary extension * header : astropy.io.fits.Header - Primary extension header * bad_pixel_mask : numpy.ndarray - Boolean mask (True = bad pixel) * wcs : astropy.wcs.WCS - World Coordinate System object * pixel_scale : float - Pixel scale in arcseconds per pixel .. py:function:: print_photometry_summary(results) Print a formatted summary of photometry results. Parameters ---------- results : dict Photometry results dictionary from elliptical_photometry(). Returns ------- None Prints summary to stdout. .. py:function:: results2table(results_list) Convert a list of result dictionaries to an Astropy Table. Parameters ---------- results_list : list of dict List of photometry result dictionaries. Returns ------- astropy.table.Table Table with formatted columns. Float columns use '.2f' format, and None values are converted to -99.0. The ``flux_err`` column is placed immediately after ``flux``. .. py:function:: steer(argv) Parse command line arguments and execute photometry. Parameters ---------- argv : list of str Command line arguments (typically sys.argv). Returns ------- None Results are written to output files. Notes ----- Arguments are parsed as follows: * Arguments containing 'fits' are treated as image files * The first non-fits argument is treated as the region table * ``-h`` prints help and exits * ``-viz`` enables visualization output to Figs_Flux/ * ``-auto_back`` auto-generates background regions from source-only input * ``-gap N`` sets the gap between source and background (default 3 arcsec) * ``-match match_file`` uses a match file to drive source/image pairs * ``-filter name`` inserts filter name into output filename .. py:function:: visualize_aperture_region(fits_file, ra, dec, a_arcsec, b_arcsec, theta_deg=0, back_outer_arcsec=None, back_inner_arcsec=None, display_size_arcsec=None, output_filename=None, include_zero_mask=True, show_plot=True, source_name=None, image_data=None) Create a visualization showing source and background regions on image data. Generates a three-panel plot showing: (1) original data with source ellipse (red) and background annulus (magenta), (2) masked data, and (3) source region mask overlay. Parameters ---------- fits_file : str Path to the FITS file. ra : float Right Ascension of aperture center in degrees. dec : float Declination of aperture center in degrees. a_arcsec : float Semi-major axis of source ellipse in arcseconds. b_arcsec : float Semi-minor axis of source ellipse in arcseconds. theta_deg : float, optional Position angle in degrees. Default is 0. back_outer_arcsec : float, optional Outer radius of background annulus in arcseconds. back_inner_arcsec : float, optional Inner radius of background annulus in arcseconds. display_size_arcsec : float, optional Size of cutout region in arcseconds. Default is 2.5x the background outer radius (if provided) or 4x the source major axis. output_filename : str, optional Output filename for plot. Auto-generated if None. include_zero_mask : bool, optional Whether to mask zero-valued pixels in display. Default is True. show_plot : bool, optional Whether to display plot interactively. Default is True. source_name : str, optional Name of the source for the plot title. image_data : dict, optional Pre-loaded image data from load_fits_image(). If provided, the FITS file is not opened again. Default is None. Returns ------- str Path to the saved visualization image file.