disco.core.fits_utils

This module provides all FITS-level I/O functions and astrometric utilities used by the CLI pipeline and, indirectly, by the GUI backend. It handles beam convolution kernels, robust centroid detection, parameter auto-detection, radial profile extraction, WCS conversions, and Gaia DR3 proper-motion retrieval.

The availability of astroquery is detected at import time and stored in the module-level flag _ASTROQUERY_AVAILABLE. When astroquery is absent, the Gaia-related functions are no-ops returning (None, None, None).


Beam Utilities

disco.core.fits_utils.get_alma_beam(sigma_maj, sigma_min, bpa_rad, size=15)[source]

Construct a 2D elliptical Gaussian beam kernel on a grid of half-width size.

Parameters:
  • sigma_maj (float) – Major-axis Gaussian sigma in pixels.

  • sigma_min (float) – Minor-axis Gaussian sigma in pixels.

  • bpa_rad (float) – Beam position angle in radians.

  • size (int) – Half-width of the kernel (kernel shape is (2*size+1, 2*size+1)). Default: 15.

Returns:

Normalised 2D kernel array.

Return type:

numpy.ndarray

disco.core.fits_utils.deconvolve_beams(bmaj_t, bmin_t, pa_t, bmaj_i, bmin_i, pa_i)[source]

Compute the convolution kernel required to convolve an image with beam \((b_{\rm maj,i}, b_{\rm min,i}, \phi_i)\) to the target beam \((b_{\rm maj,t}, b_{\rm min,t}, \phi_t)\).

The deconvolution is performed analytically via covariance matrix subtraction: \(\Sigma_c = \Sigma_t - \Sigma_i\), followed by eigendecomposition to recover the convolution kernel axes.

Parameters:
  • bmaj_t (float) – Target beam major axis in arcseconds.

  • bmin_t (float) – Target beam minor axis in arcseconds.

  • pa_t (float) – Target beam position angle in degrees.

  • bmaj_i (float) – Input beam major axis in arcseconds.

  • bmin_i (float) – Input beam minor axis in arcseconds.

  • pa_i (float) – Input beam position angle in degrees.

Returns:

(bmaj_c, bmin_c, pa_c) — the convolution kernel major axis (arcsec), minor axis (arcsec), and position angle (degrees). Returns (None, None, None) if the covariance difference has a negative eigenvalue (target beam smaller than input beam).

Return type:

tuple[float, float, float] or tuple[None, None, None]

disco.core.fits_utils.make_gaussian_kernel_casa(bmaj_c, bmin_c, pa_c, pixel_scale)[source]

Build a 2D elliptical Gaussian convolution kernel in pixel space from beam parameters in arcseconds.

The kernel size is \(\lceil 5\sigma_{\rm maj} \rceil\) pixels (odd-sized). Normalised to unit sum.

Parameters:
  • bmaj_c (float) – Kernel major axis FWHM in arcseconds.

  • bmin_c (float) – Kernel minor axis FWHM in arcseconds.

  • pa_c (float) – Kernel position angle in degrees.

  • pixel_scale (float) – Pixel scale in arcseconds per pixel.

Returns:

Normalised 2D kernel array.

Return type:

numpy.ndarray


Centroid and Parameter Detection

disco.core.fits_utils.find_center_robust(data, pixel_scale, header)[source]

Robustly estimate the disk centroid by Gaussian-smoothing a central crop of the image and computing the centre of mass of the thresholded (> 20% of peak) emission region. If the filled region is significantly larger than the detected region (indicating a cavity), the geometric bounding-box midpoint is used instead.

The search radius is fixed at 3 arcsec from the array centre.

Parameters:
  • data (numpy.ndarray) – 2D image array.

  • pixel_scale (float) – Pixel scale in arcseconds per pixel.

  • header (dict) – FITS header dictionary (used to read BMAJ).

Returns:

(cx, cy) — pixel coordinates of the estimated centroid.

Return type:

tuple[float, float]

disco.core.fits_utils.auto_detect_parameters(data, header, pixel_scale, cx, cy)[source]

Automatically estimate the inner radius, outer radius, and beam major axis from the image data and FITS header.

The outer radius is determined by scanning the azimuthally-averaged (non-deprojected) radial profile for the last bin exceeding \(3\sigma_{\rm edge}\), with a gap tolerance of 0.3 arcsec.

Parameters:
  • data (numpy.ndarray) – 2D image array.

  • header (dict) – FITS header.

  • pixel_scale (float) – Arcsec per pixel.

  • cx (float) – Centroid x-coordinate in pixels.

  • cy (float) – Centroid y-coordinate in pixels.

Returns:

(rmin, rout, bmaj) in arcseconds.

Return type:

tuple[float, float, float]

disco.core.fits_utils.refine_center_local(data, header, pixel_scale, cx_init, cy_init)[source]

Refine a centroid estimate by Gaussian-smoothing a small crop around the initial position and computing the centre of mass of the thresholded emission. The search radius is \(0.6\,b_{\rm maj}\) or a minimum of 0.15 arcsec. If the refined position moves by more than the search radius, the initial estimate is returned unchanged.

Parameters:
  • data (numpy.ndarray) – 2D image array.

  • header (dict) – FITS header.

  • pixel_scale (float) – Arcsec per pixel.

  • cx_init (float) – Initial centroid x in pixels.

  • cy_init (float) – Initial centroid y in pixels.

Returns:

(cx, cy) refined centroid in pixels.

Return type:

tuple[float, float]


Profile Extraction

disco.core.fits_utils.extract_profile(data, header, incl, pa, pixel_scale, cx, cy, limit_arcsec)[source]

Extract the azimuthally-averaged brightness temperature radial profile from a FITS image, given the disk geometry.

The procedure is:

  1. A \(1000 \times 1000\) deprojected image is generated by bilinear resampling (map_coordinates, order 3) applying the inclination and position angle transform.

  2. The deprojected image is resampled into polar coordinates (\(R \in [0, 500\,\text{pix}]\), \(\theta \in [-180°, 180°]\)).

  3. The azimuthal mean and standard deviation are computed.

  4. The uncertainty profile accounts for the effective number of independent beams per annulus.

  5. If a valid rest frequency and beam geometry are available, the profile is converted to brightness temperature \(T_b\) [K].

Parameters:
  • data (numpy.ndarray) – 2D image array (in mJy/beam or equivalent).

  • header (dict) – FITS header (used for BMAJ, BMIN, RESTFRQ).

  • incl (float) – Inclination in degrees.

  • pa (float) – Position angle in degrees.

  • pixel_scale (float) – Arcsec per pixel.

  • cx (float) – Centroid x in pixels.

  • cy (float) – Centroid y in pixels.

  • limit_arcsec (float) – Maximum radius for the returned profile.

Returns:

(r_arcsec, tb_prof, tb_err) — radius array, brightness temperature profile, and 1-sigma uncertainty, all clipped to limit_arcsec.

Return type:

tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]

disco.core.fits_utils.measure_rout_deproj(data, header, pixel_scale, cx, cy, incl, pa, rmin=0.0)[source]

Estimate the outer disk radius from the deprojected azimuthally-averaged profile. The profile is computed in pixel bins of width one pixel scale. The outer radius is defined as the last bin where the smoothed profile exceeds 2 × RMS, with a gap tolerance of 0.5 arcsec, plus a margin of \(\max(b_{\rm maj}, 3\,\delta_{\rm pix}, 0.03\,\text{arcsec})\).

Parameters:
  • data (numpy.ndarray) – 2D image array.

  • header (dict) – FITS header.

  • pixel_scale (float) – Arcsec per pixel.

  • cx (float) – Centroid x in pixels.

  • cy (float) – Centroid y in pixels.

  • incl (float) – Inclination in degrees.

  • pa (float) – Position angle in degrees.

  • rmin (float) – Inner radius in arcseconds (bins below this are skipped).

Returns:

Estimated outer radius in arcseconds, clipped to [0.10, 8.0].

Return type:

float

disco.core.fits_utils.save_debug_deproj_center(image, cx, cy, incl, pa, rout_arcsec, pixel_scale, out_png, title)[source]

Save a diagnostic PNG showing the deprojected image with the optimised centroid (cross marker) and outer radius (dashed cyan circle) overlaid. Generated when --debug on is specified.

Parameters:
  • image (numpy.ndarray) – 2D image array.

  • cx (float) – Centroid x in pixels.

  • cy (float) – Centroid y in pixels.

  • incl (float) – Inclination in degrees.

  • pa (float) – Position angle in degrees.

  • rout_arcsec (float) – Outer radius in arcseconds.

  • pixel_scale (float) – Arcsec per pixel.

  • out_png (str) – Output file path.

  • title (str) – Plot title string.


WCS and Coordinate Utilities

disco.core.fits_utils.deg_to_sex(deg)[source]

Convert a decimal degree value to a sexagesimal string of the form ±DDD:MM:SS.sss.

Parameters:

deg (float) – Decimal degrees.

Returns:

Sexagesimal string.

Return type:

str

disco.core.fits_utils.pixel_to_icrs(header, cx, cy)[source]

Convert pixel coordinates to ICRS (RA, Dec) using the FITS WCS. Uses astropy.wcs.WCS.celestial to handle cubes with degenerate axes.

Parameters:
  • header (dict) – FITS header containing WCS keywords.

  • cx (float) – Column (x) coordinate in pixels.

  • cy (float) – Row (y) coordinate in pixels.

Returns:

(ra_deg, dec_deg) in decimal degrees (ICRS).

Return type:

tuple[float, float]

disco.core.fits_utils.icrs_to_pixel(header, ra_deg, dec_deg)[source]

Convert ICRS sky coordinates to pixel coordinates using the FITS WCS.

Parameters:
  • header (dict) – FITS header.

  • ra_deg (float) – Right ascension in decimal degrees.

  • dec_deg (float) – Declination in decimal degrees.

Returns:

(x, y) pixel coordinates.

Return type:

tuple[float, float]

disco.core.fits_utils.get_obs_epoch(header)[source]

Extract the observation epoch from the FITS header. Reads DATE-OBS (ISO-T format) or MJD-OBS (Modified Julian Date).

Parameters:

header (dict) – FITS header.

Returns:

Observation epoch as an astropy.time.Time object, or None if neither keyword is present or parseable.

Return type:

astropy.time.Time or None


Gaia Proper Motion

disco.core.fits_utils.query_gaia_proper_motion(ra_deg, dec_deg, search_radius_arcsec=3.0)[source]

Query the Gaia DR3 catalogue (gaiadr3.gaia_source) for the nearest source within search_radius_arcsec arcseconds of the supplied position. Returns the proper motion of the nearest source with valid pmra and pmdec values.

Requires astroquery to be installed. Returns (None, None, None) if the library is unavailable or no match is found.

Parameters:
  • ra_deg (float) – Right ascension in decimal degrees (ICRS).

  • dec_deg (float) – Declination in decimal degrees (ICRS).

  • search_radius_arcsec (float) – Cone search radius. Default: 3.0.

Returns:

(pmra_masyr, pmdec_masyr, separation_arcsec) for the nearest matched Gaia source, or (None, None, None).

Return type:

tuple[float, float, float] or tuple[None, None, None]

disco.core.fits_utils.apply_proper_motion_correction(ra_deg, dec_deg, pmra_masyr, pmdec_masyr, dt_yr)[source]

Apply a proper-motion correction to a sky position, propagating it by dt_yr years.

\[\Delta\alpha = \frac{\mu_\alpha^* \, \Delta t}{\cos\delta \times 3.6 \times 10^6}, \qquad \Delta\delta = \frac{\mu_\delta \, \Delta t}{3.6 \times 10^6}\]
Parameters:
  • ra_deg (float) – Original right ascension in decimal degrees.

  • dec_deg (float) – Original declination in decimal degrees.

  • pmra_masyr (float) – Proper motion in RA (mas/yr), including cos(δ) factor.

  • pmdec_masyr (float) – Proper motion in Dec (mas/yr).

  • dt_yr (float) – Time baseline in years.

Returns:

(ra_corrected_deg, dec_corrected_deg).

Return type:

tuple[float, float]