OIFITS Handling
Reading
| Function | Description |
|---|---|
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.OIdata — Type
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 errorsv2_baseline,v2_lam,v2_dlam,v2_mjd,v2_flag— spatial frequency B/λ (cycles/rad), λ (m), Δλ (m), MJD, flagt3phi,t3phi_err,t3amp,t3amp_err— closure phases (deg) and triple amplitudest3_baseline— geometric mean of the three legs' B/λ (cycles/rad);t3_maxbaseline— longest leg, same unitst3_lam,t3_dlam,t3_mjd,t3_flagvisamp,visamp_err,visphi,visphi_err— complex visibility amplitude and phase (deg)vis_baseline(B/λ, cycles/rad),vis_lam,vis_dlam,vis_mjd,vis_flagflux,flux_err,flux_lam,flux_dlam,flux_mjd,flux_flagflux_sta_index— station index for each flux point;0means calibrated (OI_FLUX CALSTAT=C)flux_calibrated—trueif OI_FLUX has CALSTAT="C" (calibrated source spectrum / SED)
UV plane
uv—2×nuvmatrix 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_mjdis the array$MJDresolves from and is stored inT, so at the defaultFloat32it holds an MJD only to ~5.6 min; the per-tablev2_mjd,t3_mjd,vis_mjdandflux_mjdareFloat64whateverTis.indx_v2,indx_vis— index of each V²/vis point into the UV arrayindx_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 indicesv2_sta_index—2×nv2matrix;t3_sta_index—3×nt3matrix;vis_sta_index—2×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 indext3amp_corr,t3amp_corr_idx,t3phi_corr,t3phi_corr_idxvisamp_corr,visamp_corr_idx,visphi_corr,visphi_corr_idxflux_corr,flux_corr_idx*_corr_idx[i] == 0means pointihas no associated correlation row.
Other
mean_mjd::Float64— mean MJD of the bin (alwaysFloat64regardless ofT)filename— path to the originating OIFITS file
OITOOLS.readoifits — Function
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— iftrue, derive one spectral bin per instrument channel using midpoints between adjacent channel centres as boundaries. Overridesspectralbin.merge_oi_wavelength— iftrueandpolychromatic=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: alltrue.
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 thanuvtol. 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 ofFloat64, and enough precision for interferometric observables). PassT=Float64if 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 targetOITOOLS.readoifits_multiepochs — Function
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.
OITOOLS.filter_data — Function
filter_data(data, indexes_to_discard) -> OIdataReturn 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.)
OITOOLS.set_data_filter — Function
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 asuv_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.
Writing
| Function | Description |
|---|---|
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_tdim — Function
oifits_fix_tdim(file) -> IntRemove 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 elementMulti-element columns (VIS2DATA, FLAG, STA_INDEX, ...) keep their TDIM. The file is modified in place.
PMOIRED compatibility
| Function | Description |
|---|---|
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_dict — Function
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'}")OITOOLS.pmoired_to_julia — Function
pmoired_to_julia(s) -> StringConvert 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"]OITOOLS.pmoired_to_julia_file — Function
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.
OITOOLS.dict_to_pmoired — Function
dict_to_pmoired(model_dict; check=true) -> StringRender 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.
OITOOLS.dict_to_pmoired_file — Function
dict_to_pmoired_file(model_dict, outfile; check=true)Write model_dict to outfile as PMOIRED source. Returns outfile. Inverse of pmoired_to_julia_file.
Utilities
| Function | Description |
|---|---|
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.recenter — Function
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.
OITOOLS.sexagesimal_to_degrees — Function
sexagesimal_to_degrees(s) -> Float64Parse 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.46682588402778SIMBAD
| Function | Description |
|---|---|
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_BANDS | The ten bands simbad_target reports, in panel order |
SIMBAD_TAP_URL | The 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 targetCoordinates 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_target — Function
simbad_target(name; timeout = 30) -> NamedTupleEverything 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.129This 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.
OITOOLS.ra_dec_from_simbad — Function
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.
OITOOLS.magnitudes_from_simbad — Function
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".
OITOOLS.simbad_tap — Function
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.
OITOOLS.SIMBAD_BANDS — Constant
SIMBAD_BANDSPhotometric 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.
OITOOLS.SIMBAD_TAP_URL — Constant
SIMBAD_TAP_URLThe 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.