OIFITS Handling

Reading

FunctionDescription
readoifits(file)Read an OIFITS file into an OIdata struct
readoifits_multiepochs(file)Read multi-epoch OIFITS data
list_oifits_targets(file)List all target names in an OIFITS file
filter_data(data; kwargs...)Filter data by baseline, wavelength, etc.
set_data_filter(data; kwargs...)Set persistent data filters
readfits(file)Read a FITS image into a matrix
writefits(data, file)Write a matrix to a FITS image
oifits_prep(data; kwargs...)Inflate error bars (additive/relative floors, multiplicative scaling)
updatefits_aspro(in, out, pixsize)Add ASPRO-compatible WCS headers to a FITS image
OITOOLS.OIdataType
OIdata{T<:AbstractFloat}

Central data container produced by readoifits. All observable arrays are flat vectors of length nobs; UV coordinates are stored in a 2×nuv matrix.

Observable arrays

  • v2, v2_err — squared visibilities and errors
  • v2_baseline, v2_lam, v2_dlam, v2_mjd, v2_flag — spatial frequency B/λ (cycles/rad), λ (m), Δλ (m), MJD, flag
  • t3phi, t3phi_err, t3amp, t3amp_err — closure phases (deg) and triple amplitudes
  • t3_baseline — geometric mean of the three legs' B/λ (cycles/rad); t3_maxbaseline — longest leg, same units
  • t3_lam, t3_dlam, t3_mjd, t3_flag
  • visamp, visamp_err, visphi, visphi_err — complex visibility amplitude and phase (deg)
  • vis_baseline (B/λ, cycles/rad), vis_lam, vis_dlam, vis_mjd, vis_flag
  • flux, flux_err, flux_lam, flux_dlam, flux_mjd, flux_flag
  • flux_sta_index — station index for each flux point; 0 means calibrated (OI_FLUX CALSTAT=C)
  • flux_calibratedtrue if OI_FLUX has CALSTAT="C" (calibrated source spectrum / SED)

UV plane

  • uv2×nuv matrix of (u, v) spatial frequencies as baseline/λ. This is dimensionless — cycles per radian, not cycles/m. Plots divide by 1e6 and label Mλ; multiply by λ to recover the projected baseline in metres.
  • uv_lam, uv_dlam, uv_mjd, uv_baseline. uv_mjd is the array $MJD resolves from and is stored in T, so at the default Float32 it holds an MJD only to ~5.6 min; the per-table v2_mjd, t3_mjd, vis_mjd and flux_mjd are Float64 whatever T is.
  • indx_v2, indx_vis — index of each V²/vis point into the UV array
  • indx_t3_1, indx_t3_2, indx_t3_3 — UV indices for the three legs of each triangle

Station / telescope metadata

  • sta_name, tel_name, sta_index — station names, telescope names, station indices
  • v2_sta_index2×nv2 matrix; t3_sta_index3×nt3 matrix; vis_sta_index2×nvis

Sizes

  • nv2, nt3amp, nt3phi, nvisamp, nvisphi, nflux, nuv

Correlation matrices (from OI_CORR; empty sparse matrices when absent)

  • v2_corr, v2_corr_idx — V² correlation matrix and per-point 1-based index
  • t3amp_corr, t3amp_corr_idx, t3phi_corr, t3phi_corr_idx
  • visamp_corr, visamp_corr_idx, visphi_corr, visphi_corr_idx
  • flux_corr, flux_corr_idx
  • *_corr_idx[i] == 0 means point i has no associated correlation row.

Other

  • mean_mjd::Float64 — mean MJD of the bin (always Float64 regardless of T)
  • filename — path to the originating OIFITS file
source
OITOOLS.readoifitsFunction
readoifits(oifitsfile; kwargs...) -> Array{OIdata{T}, 2}

Read an OIFITS file and return a 2-D array of OIdata{T} indexed as [nwavbin, ntimebin]. The simplest call returns a 1×1 array containing all data in a single bin.

Keyword arguments

Target selection

  • targetname — select a single target by name. Default: all targets combined.

Spectral / temporal binning

  • spectralbin — vector of [λ_min, λ_max] windows in metres. Default: single bin spanning all wavelengths.
  • temporalbin — vector of [mjd_min, mjd_max] windows. Default: single bin spanning all epochs.
  • polychromatic — if true, derive one spectral bin per instrument channel using midpoints between adjacent channel centres as boundaries. Overrides spectralbin.
  • merge_oi_wavelength — if true and polychromatic=true, check whether all OIWAVELENGTH tables share the same spectral channels (matching `effbandoverlap) and, if so, deduplicate them to avoid redundant bins. Errors if tables are incompatible. Ignored whenspectralbinis explicitly provided. Default:false`.
  • splitting — force multi-bin mode even when bin vectors equal [[]].
  • get_specbin_file — auto-derive spectral bins from the file. Default: true.
  • get_timebin_file — auto-derive temporal bins from the file. Default: true.

Observable selection

  • use_vis, use_v2, use_t3, use_flux — load each observable type. Default: all true.

Quality filtering

  • filter_bad_data — apply quality cuts on load. Default: true.
  • force_full_vis — require both visamp and visphi to be valid. Default: false.
  • force_full_t3 — require both t3amp and t3phi. Default: false.
  • cutoff_minv2, cutoff_maxv2 — V² range cut. Default: (-1, 2.0).
  • cutoff_mint3amp, cutoff_maxt3amp — T3amp range cut. Default: (-1.0, 1.5).
  • filter_v2_snr_threshold — minimum |V²/σ|. Default: 0.01.
  • special_filter_diffvis — differential visibility mode: keep only vis points common across all spectral bins.

UV deduplication

  • redundance_remove — merge UV points closer than uvtol. Default: true.
  • uvtol — merge radius in cycles/rad (i.e. B/λ). Default: 200.0.

Numeric precision

  • T — element type for all numeric arrays. Default: Float32 (half the memory of Float64, and enough precision for interferometric observables). Pass T=Float64 if a downstream computation needs it.

Output

  • warn — print warnings about non-standard files. Default: true.
  • verbose — print summary of loaded tables. Default: true.

Example

data = readoifits("mystar.oifits")                         # all data, single bin
data = readoifits("mystar.oifits"; polychromatic=true)     # one bin per channel
data = readoifits("multi.oifits"; polychromatic=true, merge_oi_wavelength=true) # merge compatible tables
data = readoifits("mystar.oifits"; T=Float64)              # double precision
data = readoifits("multi.oifits"; targetname="Betelgeuse") # one target
source
OITOOLS.readoifits_multiepochsFunction
readoifits_multiepochs(oifitsfiles; polychromatic=false, kwargs...) -> Matrix{OIdata{T}}

Read a list of OIFITS files, one per epoch, and return a Matrix{OIdata{T}} of size (nwav, nepochs).

  • Without polychromatic: each file yields a single spectral bin → 1 × nepochs
  • With polychromatic=true: each file is split into wavelength channels → nwav × nepochs

Mean MJD per epoch is available via data[1,i].mean_mjd.

Prints a summary line per file. Passes filter_bad_data, force_full_t3, and polychromatic through to readoifits.

source
OITOOLS.filter_dataFunction
filter_data(data, indexes_to_discard) -> OIdata

Return a deep copy of data with the specified points removed. indexes_to_discard must be the five-element vector [uv_bad, vis_bad, v2_bad, t3_bad, flux_bad] returned by set_data_filter. UV points that become unreferenced after removing observables are pruned automatically and all index arrays are remapped.

Example

idx = set_data_filter(data[1,1]; filter_bad_data=true, baseline_range=[5e6, 300e6])
clean = filter_data(data[1,1], idx)

The default discards nothing. (Before 0.11 the default was Int64[], which raised a BoundsError on indexes_to_discard[2] for any dataset containing observables, so it could never actually be used.)

source
OITOOLS.set_data_filterFunction
set_data_filter(data; kwargs...) -> [uv_bad, vis_bad, v2_bad, t3_bad, flux_bad]

Compute lists of indices to discard from a loaded OIdata bin without modifying it. Pass the result to filter_data to obtain a filtered copy.

Keyword arguments

  • wav_range — wavelength window(s) in metres, e.g. [1.6e-6, 1.8e-6] or a vector of windows [[1.6e-6,1.8e-6],[2.0e-6,2.4e-6]]. Default: keep all.
  • mjd_range — MJD window(s), same format. Default: keep all.
  • baseline_range[min, max] spatial frequency B/λ in cycles/rad (the same units as uv_baseline, so e.g. [5e6, 300e6] is 5–300 Mλ). Default: keep all.
  • filter_bad_data — apply quality cuts (flags, NaN, SNR, amplitude range). Default: false.
  • filter_vis, filter_v2, filter_t3amp, filter_t3phi, filter_flux — enable cuts per observable type.
  • cutoff_minv2, cutoff_maxv2 — V² range cut. Default: (-1, 2.0).
  • cutoff_mint3amp, cutoff_maxt3amp — T3 amplitude range cut. Default: (-1.0, 1.5).
  • filter_v2_snr_threshold — minimum |V²/σ| to keep. Default: 0.01.
  • force_full_vis — require both amplitude and phase to be valid (default: either).
  • force_full_t3 — require both T3amp and T3phi to be valid (default: either).
  • special_filter_diffvis — enable differential visibility filtering mode.
  • uv_bad — pre-supplied list of UV indices to remove.
  • filter_visphi, filter_visamp — enable visibility phase/amplitude filtering.

Returns [uv_bad, vis_bad, v2_bad, t3_bad, flux_bad] — five Vector{Int64} of indices to discard.

source

Writing

FunctionDescription
oifits_check(file)Validate an OIFITS file
oifits_merge(files, outfile)Merge multiple OIFITS files
oifits_filter(infile, outfile)Filter an OIFITS file
oifits_fix_tdim(file)Drop the redundant TDIM cards cfitsio writes on scalar columns (needed for astropy/PMOIRED to read the file)
OITOOLS.oifits_fix_tdimFunction
oifits_fix_tdim(file) -> Int

Remove the redundant TDIMn cards that cfitsio writes for one-dimensional columns, and return how many were removed.

Files written through OIFITS.write (i.e. by simulate, simulate_from_oifits, ...) carry a TDIMn = (1) card for every scalar column. The card is legal but redundant, and readers that honour it — astropy, and therefore PMOIRED — return such columns with shape (nrow, 1) instead of (nrow,). PMOIRED then fails to load the file with

TypeError: cannot use 'numpy.ndarray' as a set element

Multi-element columns (VIS2DATA, FLAG, STA_INDEX, ...) keep their TDIM. The file is modified in place.

source

PMOIRED compatibility

FunctionDescription
pmoired_to_dict(s)Convert a PMOIRED model string directly to a Julia Dict
pmoired_to_julia(s)Convert a PMOIRED model string to Julia source code (returns String)
pmoired_to_julia_file(infile, outfile)Convert a PMOIRED notebook snippet file
dict_to_pmoired(d)Render an OITOOLS model dict as PMOIRED source (returns String)
dict_to_pmoired_file(d, outfile)Write an OITOOLS model dict out as PMOIRED source
OITOOLS.pmoired_to_dictFunction
pmoired_to_dict(s) -> Dict{String,Any}

Convert a PMOIRED Python dict literal string directly to a Julia Dict. This is a convenience wrapper around pmoired_to_julia that evaluates the resulting Julia string, so users don't need eval(Meta.parse(...)).

model_dict = pmoired_to_dict("{'star,ud': 3.2, 'ring,f': '1 - \$star,f'}")
source
OITOOLS.pmoired_to_juliaFunction
pmoired_to_julia(s) -> String

Convert a PMOIRED Python dict literal string to an equivalent Julia Dict literal string. Handles nested dicts, expression strings with $ references, single-quoted strings, Python boolean/None literals, and list literals.

Transformation rules applied: { ... } -> Dict( ... ) 'key': value -> "key" => value (outside string literals) : -> => (outside string literals, single colon only) 'plain string' -> "plain string" 'expr with $ref' -> raw"expr with $ref" (raw string prevents interpolation) True / False -> true / false None -> nothing

Example

# Python / PMOIRED:
param = {
    'star,ud':    3.2,
    'star,f':     0.7,
    'ring,udout': '$star,ud * 8',
    'ring,f':     '1 - $star,f',
    'ring,incl':  30.0,
}
fitOnly = ['star,ud', 'star,f', 'ring,udout', 'ring,f']
# Generated Julia:
param = Dict(
    "star,ud"    => 3.2,
    "star,f"     => 0.7,
    "ring,udout" => raw"$star,ud * 8",
    "ring,f"     => raw"1 - $star,f",
    "ring,incl"  => 30.0,
)
fitOnly = ["star,ud", "star,f", "ring,udout", "ring,f"]
source
OITOOLS.pmoired_to_julia_fileFunction
pmoired_to_julia_file(infile, outfile)

Read a Python/PMOIRED notebook snippet from infile, convert it, write to outfile. Lines that look like param = {...} or fitOnly = [...] are converted; other lines are passed through.

source
OITOOLS.dict_to_pmoiredFunction
dict_to_pmoired(model_dict; check=true) -> String

Render an OITOOLS model dictionary as PMOIRED source — the inverse of pmoired_to_dict.

Keys are emitted in sorted order so the output is reproducible and diffable. Julia true, false and nothing become True, False and None; string values are quoted with single quotes, so $-expressions survive unchanged (both packages use the same syntax).

With check=true (the default) a warning is emitted for keys that have no PMOIRED equivalent — the ldlin/ldquad/ldpow/resolved geometries are OITOOLS additions. The model is still written; the warning exists because the failure is otherwise silent on the PMOIRED side.

See also pmoired_to_dict, dict_to_pmoired_file.

source

Utilities

FunctionDescription
recenter(x; mask, max)Recenter an image by circular shift to centroid or peak
sexagesimal_to_degrees(s)Parse "-46 28 00.5" or "12:34:56" to decimal degrees (sign applies to the whole value)
OITOOLS.recenterFunction
recenter(x; mask=[], max=false)

Recenter an image by circular shifting so the centroid (or peak if max=true) is at the image center. Works on 1D (vectorized square image) or 2D arrays. If mask is provided, the centroid is computed from the mask instead.

source
OITOOLS.sexagesimal_to_degreesFunction
sexagesimal_to_degrees(s) -> Float64

Parse a sexagesimal coordinate string into a single decimal value. Fields may be separated by whitespace or colons, and one, two or three of them may be given.

The sign applies to the whole quantity, not just the leading field: "-00 30 00" is -0.5, and "-46 28 00.57" is -46.4668. Reducing the parsed fields with a dot product against [1, 1/60, 1/3600] — which is what callers used to do — gets both of those wrong, because it negates only the degrees term.

julia> sexagesimal_to_degrees("-46 28 00.5731825")
-46.46682588402778
source

SIMBAD

FunctionDescription
simbad_target(name)Full SIMBAD record in one request: coordinates, proper motion, parallax, radial velocity, spectral type, object type, magnitudes
ra_dec_from_simbad(name)RA and Dec alone, in decimal degrees
magnitudes_from_simbad(name)Photometric magnitudes alone, B through N
simbad_tap(adql)Any ADQL query against SIMBAD's TAP service; returns column names and rows
SIMBAD_BANDSThe ten bands simbad_target reports, in panel order
SIMBAD_TAP_URLThe TAP endpoint queries go to

simbad_target is the one to reach for when planning. Proper motion places the target at the epoch actually being observed, parallax turns an angular diameter into a physical one, and the spectral type is what a surface-brightness relation needs to predict a diameter before anything is measured:

t = simbad_target("Vega")
t.main_id           # "* alf Lyr"
t.ra, t.dec         # 279.2347, 38.7837   — degrees
t.plx               # 130.23 mas
t.sptype            # "A0V"
t.mags["K"]         # 0.129
t.mags["N"]         # NaN — SIMBAD has no N magnitude for this target

Coordinates come back in decimal degrees, and a value SIMBAD does not hold is NaN (or "" for a string) rather than an error: not knowing a parallax is a fact about the target, and has to be distinguishable from the query having failed. mags always carries all ten SIMBAD_BANDS as keys, so a panel iterating them never has to test for presence as well as for NaN.

It is also the cheapest call. Astrometry and photometry arrive together from a single ADQL query, which is what SIMBAD asks clients to do; ra_dec_from_simbad and magnitudes_from_simbad are wrappers around it, so asking for both costs two requests where simbad_target costs one. The transport is Downloads, a standard library — no Python is involved.

Failures are reported apart from one another, because they call for different actions: the service being unreachable, the service refusing the query, and the query succeeding with no rows. Only the last is a problem with the name you asked for.

simbad_tap is the escape hatch for anything else in the SIMBAD schema. It returns the header and the rows as Strings, exactly as the service sent them:

cols, rows = simbad_tap("SELECT TOP 5 main_id, plx_value FROM basic WHERE plx_value > 500")
OITOOLS.simbad_targetFunction
simbad_target(name; timeout = 30) -> NamedTuple

Everything SIMBAD holds that an observation needs, in one request: (; name, main_id, ra, dec, pmra, pmdec, plx, rv, sptype, otype, mags).

ra/dec are DEGREES, proper motions mas/yr, parallax mas, radial velocity km/s. Missing values are NaN and a missing string is "" — SIMBAD not knowing a target's parallax is a fact about the target, not a failure, and it has to be distinguishable from the query failing. mags always has all ten SIMBAD_BANDS as keys, NaN where there is no measurement.

Beyond coordinates and magnitudes this returns what ASPRO also reads, and for the same reasons: proper motion places the target at the epoch actually being observed, parallax turns an angular diameter into a physical one, and the spectral type is what a surface-brightness relation needs to predict a diameter before anything is measured.

name is matched against SIMBAD's identifier table, so anything SIMBAD lists works — "Vega", "alf Lyr", "HD 172167".

t = simbad_target("Vega")
t.ra, t.dec        # 279.2347, 38.7837 degrees
t.mags["K"]        # 0.129

This is the one to call when more than one field is wanted: ra_dec_from_simbad and magnitudes_from_simbad are thin wrappers around it, so asking for both costs two requests where this costs one.

source
OITOOLS.ra_dec_from_simbadFunction
ra_dec_from_simbad(name) -> (ra_deg, dec_deg)

Resolve a target name through SIMBAD and return its J2000 right ascension and declination in decimal degrees.

Call simbad_target instead if the magnitudes or the parallax are wanted too; this issues its own request.

source
OITOOLS.magnitudes_from_simbadFunction
magnitudes_from_simbad(name) -> Dict{String,Float64}

Photometric magnitudes for a target, keyed by SIMBAD_BANDS. Bands SIMBAD has no measurement for are NaN, never 0.0.

A network or query failure throws. That distinction is the point: a target with no K magnitude and a query that never ran are different answers, and reporting them the same way is how "this star has no K magnitude" comes to mean "SIMBAD was down".

source
OITOOLS.simbad_tapFunction
simbad_tap(adql; url = SIMBAD_TAP_URL, timeout = 30) -> (colnames, rows)

Run one ADQL query against SIMBAD's TAP service and return its column names and rows, all as Strings.

cols, rows = simbad_tap("SELECT TOP 5 main_id, ra, dec FROM basic WHERE plx_value > 500")

This is the general escape hatch; simbad_target is the query OITOOLS actually needs. The full schema is at https://simbad.cds.unistra.fr/simbad/tap/tapsearch.html.

Three failures are reported differently on purpose, because they call for different actions: the service being unreachable (network), the service refusing the query (ADQL), and the query succeeding with no rows (the target does not exist). Only the last is the caller's data problem.

source
OITOOLS.SIMBAD_BANDSConstant
SIMBAD_BANDS

Photometric bands simbad_target reports, in the order a panel should show them.

B through N. The near-infrared ones are what an interferometric observation is actually made in, and V is what the adaptive optics guides on, so both matter and for different reasons.

source
OITOOLS.SIMBAD_TAP_URLConstant
SIMBAD_TAP_URL

The TAP endpoint queries are sent to. Overridable per call through simbad_tap's url keyword, which is what the mirror at simbad.u-strasbg.fr is for.

source