use chrono::{DateTime, Duration, FixedOffset};
use fitsio::hdu::FitsHdu;
use num_traits::ToPrimitive;
use std::fmt;
use std::path::Path;
use self::error::MetafitsError;
use crate::antenna::*;
use crate::baseline::*;
use crate::coarse_channel::*;
use crate::fits_read::*;
use crate::rfinput::*;
use crate::types::*;
use crate::voltage_files::*;
use crate::*;
pub mod error;
pub(crate) mod ffi;
#[cfg(test)]
pub(crate) mod ffi_test;
#[cfg(test)]
mod test;
#[cfg(any(feature = "python", feature = "python-stubgen"))]
use pyo3::prelude::*;
#[cfg(any(feature = "python", feature = "python-stubgen"))]
mod python;
#[cfg(feature = "python-stubgen")]
use pyo3_stub_gen_derive::gen_stub_pyclass;
#[cfg_attr(feature = "python-stubgen", gen_stub_pyclass)]
#[cfg_attr(
any(feature = "python", feature = "python-stubgen"),
pyclass(get_all, set_all, from_py_object)
)]
#[derive(Clone, Debug)]
pub struct MetafitsContext {
pub mwa_version: Option<MWAVersion>,
pub obs_id: u32,
pub sched_start_gps_time_ms: u64,
pub sched_end_gps_time_ms: u64,
pub sched_start_unix_time_ms: u64,
pub sched_end_unix_time_ms: u64,
pub sched_start_utc: DateTime<FixedOffset>,
pub sched_end_utc: DateTime<FixedOffset>,
pub sched_start_mjd: f64,
pub sched_end_mjd: f64,
pub sched_duration_ms: u64,
pub dut1: Option<f64>,
pub ra_tile_pointing_degrees: f64,
pub dec_tile_pointing_degrees: f64,
pub ra_phase_center_degrees: Option<f64>,
pub dec_phase_center_degrees: Option<f64>,
pub az_deg: f64,
pub alt_deg: f64,
pub za_deg: f64,
pub az_rad: f64,
pub alt_rad: f64,
pub za_rad: f64,
pub sun_alt_deg: Option<f64>,
pub sun_distance_deg: Option<f64>,
pub moon_distance_deg: Option<f64>,
pub jupiter_distance_deg: Option<f64>,
pub lst_deg: f64,
pub lst_rad: f64,
pub hour_angle_string: String,
pub grid_name: String,
pub grid_number: i32,
pub creator: String,
pub project_id: String,
pub obs_name: String,
pub mode: MWAMode,
pub geometric_delays_applied: GeometricDelaysApplied,
pub cable_delays_applied: CableDelaysApplied,
pub calibration_delays_and_gains_applied: bool,
pub signal_chain_corrections_applied: bool,
pub corr_fine_chan_width_hz: u32,
pub corr_int_time_ms: u64,
pub corr_raw_scale_factor: f32,
pub num_corr_fine_chans_per_coarse: usize,
pub volt_fine_chan_width_hz: u32,
pub num_volt_fine_chans_per_coarse: usize,
pub receivers: Vec<usize>,
pub num_receivers: usize,
pub delays: Vec<u32>,
pub num_delays: usize,
pub calibrator: bool,
pub calibrator_source: String,
pub global_analogue_attenuation_db: f64,
pub quack_time_duration_ms: u64,
pub good_time_unix_ms: u64,
pub good_time_gps_ms: u64,
pub num_ants: usize,
pub antennas: Vec<Antenna>,
pub num_rf_inputs: usize,
pub rf_inputs: Vec<Rfinput>,
pub num_ant_pols: usize,
pub num_metafits_timesteps: usize,
pub metafits_timesteps: Vec<TimeStep>,
pub num_metafits_coarse_chans: usize,
pub metafits_coarse_chans: Vec<CoarseChannel>,
pub num_metafits_fine_chan_freqs: usize,
pub metafits_fine_chan_freqs_hz: Vec<f64>,
pub obs_bandwidth_hz: u32,
pub coarse_chan_width_hz: u32,
pub centre_freq_hz: u32,
pub num_baselines: usize,
pub baselines: Vec<Baseline>,
pub num_visibility_pols: usize,
pub metafits_filename: String,
pub oversampled: bool,
pub deripple_applied: bool,
pub deripple_param: String,
pub best_cal_fit_id: Option<u32>,
pub best_cal_obs_id: Option<u32>,
pub best_cal_code_ver: Option<String>,
pub best_cal_fit_timestamp: Option<String>,
pub best_cal_creator: Option<String>,
pub best_cal_fit_iters: Option<u16>,
pub best_cal_fit_iter_limit: Option<u16>,
pub signal_chain_corrections: Option<Vec<SignalChainCorrection>>,
pub num_signal_chain_corrections: usize,
pub calibration_fits: Option<Vec<CalibrationFit>>,
pub num_calibration_fits: usize,
pub metafits_voltage_beams: Option<Vec<VoltageBeam>>,
pub num_metafits_voltage_beams: usize,
pub num_metafits_coherent_beams: usize,
pub num_metafits_incoherent_beams: usize,
}
impl MetafitsContext {
pub fn new<P: AsRef<Path>>(
metafits: P,
mwa_version: Option<MWAVersion>,
) -> Result<Self, MwalibError> {
if !fits_read::is_fitsio_reentrant() {
return Err(MwalibError::Fits(FitsError::CfitsioIsNotReentrant));
}
Self::new_inner(metafits.as_ref(), mwa_version)
}
fn new_inner(metafits: &Path, mwa_version: Option<MWAVersion>) -> Result<Self, MwalibError> {
let mut new_context = MetafitsContext::new_internal(metafits)?;
new_context.mwa_version = match mwa_version {
None => match new_context.mode {
MWAMode::Hw_Lfiles => Some(MWAVersion::CorrLegacy),
MWAMode::Voltage_Start | MWAMode::Voltage_Buffer => {
Some(MWAVersion::VCSLegacyRecombined)
}
MWAMode::Mwax_Correlator => Some(MWAVersion::CorrMWAXv2),
MWAMode::Mwax_Vcs | MWAMode::Mwax_Buffer => Some(MWAVersion::VCSMWAXv2),
MWAMode::Mwax_Beamformer => Some(MWAVersion::BeamformerMWAXv2),
MWAMode::Mwax_Corr_Bf => Some(MWAVersion::CorrBeamformerMWAXv2),
_ => {
return Err(MwalibError::Metafits(
MetafitsError::UnableToDetermineMWAVersionFromMode(new_context.mode),
))
}
},
m => m,
};
if matches!(
new_context.mwa_version,
Some(MWAVersion::VCSLegacyRecombined)
) {
new_context.rf_inputs.sort_by_key(|k| k.vcs_order);
}
if new_context.mwa_version == Some(MWAVersion::VCSMWAXv2) {
new_context.volt_fine_chan_width_hz = new_context.coarse_chan_width_hz;
new_context.num_volt_fine_chans_per_coarse = 1;
}
new_context.populate_expected_coarse_channels(new_context.mwa_version.unwrap())?;
new_context.metafits_fine_chan_freqs_hz = CoarseChannel::get_fine_chan_centres_array_hz(
new_context.mwa_version.unwrap(),
&new_context.metafits_coarse_chans,
match new_context.mwa_version.unwrap() {
MWAVersion::VCSLegacyRecombined | MWAVersion::VCSMWAXv2 => {
new_context.volt_fine_chan_width_hz
}
MWAVersion::CorrLegacy
| MWAVersion::CorrOldLegacy
| MWAVersion::CorrMWAXv2
| MWAVersion::CorrBeamformerMWAXv2
| MWAVersion::BeamformerMWAXv2 => new_context.corr_fine_chan_width_hz,
},
match new_context.mwa_version.unwrap() {
MWAVersion::VCSLegacyRecombined | MWAVersion::VCSMWAXv2 => {
new_context.num_volt_fine_chans_per_coarse
}
MWAVersion::CorrLegacy
| MWAVersion::CorrOldLegacy
| MWAVersion::CorrMWAXv2
| MWAVersion::CorrBeamformerMWAXv2
| MWAVersion::BeamformerMWAXv2 => new_context.num_corr_fine_chans_per_coarse,
},
);
new_context.num_metafits_fine_chan_freqs = new_context.metafits_fine_chan_freqs_hz.len();
new_context.populate_expected_timesteps(new_context.mwa_version.unwrap())?;
Ok(new_context)
}
pub(crate) fn new_internal<T: AsRef<std::path::Path>>(
metafits: T,
) -> Result<Self, MwalibError> {
let metafits_filename = metafits
.as_ref()
.to_str()
.expect("Metafits filename is not UTF-8 compliant")
.to_string();
let mut metafits_fptr = fits_open!(&metafits)?;
let metafits_hdu = fits_open_hdu!(&mut metafits_fptr, 0)?;
let metafits_tile_table_hdu = fits_open_hdu_by_name!(&mut metafits_fptr, "TILEDATA")?;
let metafits_cal_hdu_result = fits_open_hdu_by_name!(&mut metafits_fptr, "CALIBDATA");
let metafits_signalchain_hdu_result =
fits_open_hdu_by_name!(&mut metafits_fptr, "SIGCHAINDATA");
let metafits_beams_hdu_result = fits_open_hdu_by_name!(&mut metafits_fptr, "VOLTAGEBEAMS");
let metafits_beam_alt_az_hdu_result =
fits_open_hdu_by_name!(&mut metafits_fptr, "BEAMALTAZ");
let obsid = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "GPSTIME")?;
let oversampled: bool = matches!(
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "OVERSAMP")?.unwrap_or(0),
1
);
let quack_time_duration_ms: u64 = {
let qt: f64 = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "QUACKTIM")?;
(qt * 1000.).round() as _
};
let good_time_unix_ms: u64 = {
let gt: f64 = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "GOODTIME")?;
(gt * 1000.).round() as _
};
let metafits_observation_bandwidth_hz: u32 = {
let bw: f64 = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "BANDWDTH")?;
(bw * 1e6).round() as _
};
let (metafits_coarse_chan_vec, metafits_coarse_chan_width_hz) =
CoarseChannel::get_metafits_coarse_channel_info(
&mut metafits_fptr,
&metafits_hdu,
metafits_observation_bandwidth_hz,
)?;
let metafits_coarse_chans: Vec<CoarseChannel> =
Vec::with_capacity(metafits_coarse_chan_vec.len());
let num_metafits_coarse_chans: usize = 0;
let num_rf_inputs: usize =
get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "NINPUTS")?;
let num_antennas = num_rf_inputs / 2;
let signal_chain_corrections: Option<Vec<SignalChainCorrection>>;
let num_signal_chain_corrections: usize;
match metafits_signalchain_hdu_result {
Ok(metafits_signalchain_hdu) => {
let sig_chain_corrs = signal_chain_correction::populate_signal_chain_corrections(
&mut metafits_fptr,
&metafits_signalchain_hdu,
)?;
num_signal_chain_corrections = sig_chain_corrs.len();
signal_chain_corrections = Some(sig_chain_corrs);
}
Err(_) => {
num_signal_chain_corrections = 0;
signal_chain_corrections = None;
}
}
let mut rf_inputs: Vec<Rfinput> = Rfinput::populate_rf_inputs(
num_rf_inputs,
&mut metafits_fptr,
&metafits_tile_table_hdu,
MWALIB_MWA_COAX_V_FACTOR,
metafits_coarse_chan_vec.len(),
&signal_chain_corrections,
)?;
assert_eq!(num_rf_inputs, rf_inputs.len());
rf_inputs.sort_by_key(|k| k.subfile_order);
let antennas: Vec<Antenna> = Antenna::populate_antennas(&rf_inputs);
let num_antenna_pols = 2;
let baselines = Baseline::populate_baselines(num_antennas);
let num_visibility_pols = 4;
let num_baselines = (num_antennas * (num_antennas + 1)) / 2;
let centre_freq_hz: u32 = {
let cf: f64 = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "FREQCENT")?;
(cf * 1e6).round() as _
};
let scheduled_start_utc_string: String =
get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "DATE-OBS")?;
let scheduled_start_utc_string_with_offset: String = scheduled_start_utc_string + "+00:00";
let scheduled_start_utc =
DateTime::parse_from_rfc3339(&scheduled_start_utc_string_with_offset)
.expect("Unable to parse DATE-OBS into a date time");
let scheduled_start_mjd: f64 =
get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "MJD")?;
let scheduled_duration_ms: u64 = {
let ex: u64 = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "EXPOSURE")?;
ex * 1000
};
let dut1: Option<f64> = get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "DUT1")?;
let num_metafits_timesteps: usize = 0;
let metafits_timesteps: Vec<TimeStep> = Vec::new();
let scheduled_end_utc = scheduled_start_utc
+ Duration::try_milliseconds(scheduled_duration_ms as i64)
.expect("scheduled_duration_ms is out of range");
let scheduled_end_mjd =
scheduled_start_mjd + (scheduled_duration_ms as f64 / 1000. / 86400.);
let scheduled_start_gpstime_ms: u64 = obsid as u64 * 1000;
let scheduled_end_gpstime_ms: u64 = scheduled_start_gpstime_ms + scheduled_duration_ms;
let scheduled_start_unix_time_ms: u64 = good_time_unix_ms - quack_time_duration_ms;
let scheduled_end_unix_time_ms: u64 = scheduled_start_unix_time_ms + scheduled_duration_ms;
let good_time_gps_ms: u64 = scheduled_start_gpstime_ms + quack_time_duration_ms;
let ra_tile_pointing_degrees: f64 =
get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "RA")?;
let dec_tile_pointing_degrees: f64 =
get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "DEC")?;
let ra_phase_center_degrees: Option<f64> =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "RAPHASE")?;
let dec_phase_center_degrees: Option<f64> =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "DECPHASE")?;
let azimuth_degrees: f64 =
get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "AZIMUTH")?;
let altitude_degrees: f64 =
get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "ALTITUDE")?;
let zenith_angle_degrees: f64 = 90.0 - altitude_degrees;
let sun_altitude_degrees: Option<f64> =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "SUN-ALT")?;
let sun_distance_degrees: Option<f64> =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "SUN-DIST")?;
let moon_distance_degrees: Option<f64> =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "MOONDIST")?;
let jupiter_distance_degrees: Option<f64> =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "JUP-DIST")?;
let lst_degrees: f64 = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "LST")?;
let hour_angle_string = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "HA")?;
let grid_name: String =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "GRIDNAME")?
.unwrap_or_else(|| String::from("NOGRID"));
let grid_number =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "GRIDNUM")?.unwrap_or(0);
let creator = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "CREATOR")?;
let project_id = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "PROJECT")?;
let observation_name =
get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "FILENAME")?;
let mode: MWAMode = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "MODE")?;
let geometric_delays_applied: GeometricDelaysApplied =
match get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "GEODEL")? {
Some(g) => match num_traits::FromPrimitive::from_i32(g) {
Some(gda) => gda,
None => {
return Err(MwalibError::Parse {
key: String::from("GEODEL"),
fits_filename: metafits_filename,
hdu_num: 0,
source_file: String::from(file!()),
source_line: line!(),
})
}
},
None => GeometricDelaysApplied::No,
};
let cable_delays_applied: CableDelaysApplied =
match get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "CABLEDEL")? {
Some(g) => match num_traits::FromPrimitive::from_i32(g) {
Some(gda) => gda,
None => {
return Err(MwalibError::Parse {
key: String::from("CABLEDEL"),
fits_filename: metafits_filename,
hdu_num: 0,
source_file: String::from(file!()),
source_line: line!(),
})
}
},
None => CableDelaysApplied::NoCableDelaysApplied,
};
let calibration_delays_and_gains_applied: bool = matches!(
(get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "CALIBDEL")?).unwrap_or(0),
1
);
let signal_chain_corrections_applied: bool = matches!(
(get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "SIGCHDEL")?).unwrap_or(0),
1
);
let integration_time_ms: u64 = {
let it: f64 = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "INTTIME")?;
(it * 1000.) as _
};
let receivers_string: String =
get_required_fits_key_long_string!(&mut metafits_fptr, &metafits_hdu, "RECVRS")?;
let corr_raw_scale_factor: f32 =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "RAWSCALE")?.unwrap_or(1.0);
let receivers: Vec<usize> = receivers_string
.replace(&['\'', '&'][..], "")
.split(',')
.map(|s| s.parse().unwrap())
.collect();
let num_receivers = receivers.len();
let delays_string: String =
get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "DELAYS")?;
let delays: Vec<u32> = delays_string
.replace(&['\'', '&'][..], "")
.split(',')
.map(|s| s.parse().unwrap())
.collect();
let num_delays = delays.len();
let calibration_string: String =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "CALIBRAT")?
.unwrap_or_else(|| String::from("F"));
let calibrator: bool = calibration_string == "T";
let calibrator_source: String =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "CALIBSRC")?
.unwrap_or_else(|| String::from(""));
let global_analogue_attenuation_db: f64 =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "ATTEN_DB")?.unwrap_or(0.0);
let dr_flag =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "DR_FLAG")?.unwrap_or(0);
let deripple =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "DERIPPLE")?.unwrap_or(0);
let deripple_applied: bool = dr_flag == 1 || deripple == 1;
let deripple_param: String =
get_optional_fits_key!(&mut metafits_fptr, &metafits_hdu, "DR_PARAM")?
.unwrap_or_else(|| String::from(""));
let num_metafits_fine_chan_freqs: usize = 0;
let metafits_fine_chan_freqs: Vec<f64> = Vec::new();
let corr_fine_chan_width_hz: u32 = {
let fc: f64 = get_required_fits_key!(&mut metafits_fptr, &metafits_hdu, "FINECHAN")?;
(fc * 1000.).round() as _
};
let num_corr_fine_chans_per_coarse =
(metafits_coarse_chan_width_hz / corr_fine_chan_width_hz) as usize;
let volt_fine_chan_width_hz: u32 = 10_000;
let num_volt_fine_chans_per_coarse =
(metafits_coarse_chan_width_hz / volt_fine_chan_width_hz) as usize;
let calibration_fits: Option<Vec<CalibrationFit>>;
let num_calibration_fits: usize;
match &metafits_cal_hdu_result {
Ok(metafits_calibdata_hdu) => {
let cal_fits = calibration_fit::populate_calibration_fits(
&mut metafits_fptr,
metafits_calibdata_hdu,
&rf_inputs,
num_metafits_coarse_chans,
)?;
num_calibration_fits = cal_fits.len();
calibration_fits = Some(cal_fits);
}
Err(_) => {
num_calibration_fits = 0;
calibration_fits = None;
}
}
let best_cal_fit_id: Option<u32>;
let best_cal_obs_id: Option<u32>;
let best_cal_code_ver: Option<String>;
let best_cal_fit_timestamp: Option<String>;
let best_cal_creator: Option<String>;
let best_cal_fit_iters: Option<u16>;
let best_cal_fit_iter_limit: Option<u16>;
match metafits_cal_hdu_result {
Ok(metafits_cal_hdu) => {
best_cal_fit_id =
get_optional_fits_key!(&mut metafits_fptr, &metafits_cal_hdu, "CALFITID")?;
best_cal_obs_id =
get_optional_fits_key!(&mut metafits_fptr, &metafits_cal_hdu, "CALOBSID")?;
best_cal_code_ver =
get_optional_fits_key!(&mut metafits_fptr, &metafits_cal_hdu, "CALCDVER")?;
best_cal_fit_timestamp =
get_optional_fits_key!(&mut metafits_fptr, &metafits_cal_hdu, "CALDTIME")?;
best_cal_creator =
get_optional_fits_key!(&mut metafits_fptr, &metafits_cal_hdu, "CALCRTR")?;
best_cal_fit_iters =
get_optional_fits_key!(&mut metafits_fptr, &metafits_cal_hdu, "CALITERS")?;
best_cal_fit_iter_limit =
get_optional_fits_key!(&mut metafits_fptr, &metafits_cal_hdu, "CALITLIM")?
.and_then(|val: f32| val.to_u16());
}
Err(_) => {
best_cal_fit_id = None;
best_cal_obs_id = None;
best_cal_code_ver = None;
best_cal_fit_timestamp = None;
best_cal_creator = None;
best_cal_fit_iters = None;
best_cal_fit_iter_limit = None;
}
};
let beams: Option<Vec<VoltageBeam>>;
let num_beams: usize;
let num_coherent_beams: usize;
let num_incoherent_beams: usize;
match &metafits_beams_hdu_result {
Ok(metafits_beams_hdu) => {
let beamaltaz_hdu: Option<&FitsHdu> = metafits_beam_alt_az_hdu_result.as_ref().ok();
let beams_vec = voltage_beam::populate_voltage_beams(
&mut metafits_fptr,
metafits_beams_hdu,
beamaltaz_hdu,
&metafits_coarse_chans,
&antennas,
)?;
num_beams = beams_vec.len();
beams = Some(beams_vec);
num_coherent_beams = beams
.as_ref()
.unwrap()
.iter()
.filter(|b| b.coherent == true)
.count();
num_incoherent_beams = beams
.as_ref()
.unwrap()
.iter()
.filter(|b| b.coherent == false)
.count();
}
Err(_) => {
num_beams = 0;
num_coherent_beams = 0;
num_incoherent_beams = 0;
beams = None;
}
}
Ok(MetafitsContext {
mwa_version: None,
obs_id: obsid,
sched_start_gps_time_ms: scheduled_start_gpstime_ms,
sched_end_gps_time_ms: scheduled_end_gpstime_ms,
sched_start_unix_time_ms: scheduled_start_unix_time_ms,
sched_end_unix_time_ms: scheduled_end_unix_time_ms,
sched_start_utc: scheduled_start_utc,
sched_end_utc: scheduled_end_utc,
sched_start_mjd: scheduled_start_mjd,
sched_end_mjd: scheduled_end_mjd,
sched_duration_ms: scheduled_duration_ms,
dut1,
ra_tile_pointing_degrees,
dec_tile_pointing_degrees,
ra_phase_center_degrees,
dec_phase_center_degrees,
az_deg: azimuth_degrees,
alt_deg: altitude_degrees,
za_deg: zenith_angle_degrees,
az_rad: azimuth_degrees.to_radians(),
alt_rad: altitude_degrees.to_radians(),
za_rad: zenith_angle_degrees.to_radians(),
sun_alt_deg: sun_altitude_degrees,
sun_distance_deg: sun_distance_degrees,
moon_distance_deg: moon_distance_degrees,
jupiter_distance_deg: jupiter_distance_degrees,
lst_deg: lst_degrees,
lst_rad: lst_degrees.to_radians(),
hour_angle_string,
grid_name,
grid_number,
creator,
project_id,
obs_name: observation_name,
mode,
geometric_delays_applied,
cable_delays_applied,
calibration_delays_and_gains_applied,
signal_chain_corrections_applied,
corr_fine_chan_width_hz,
corr_int_time_ms: integration_time_ms,
corr_raw_scale_factor,
num_corr_fine_chans_per_coarse,
volt_fine_chan_width_hz,
num_volt_fine_chans_per_coarse,
receivers,
num_receivers,
delays,
num_delays,
calibrator,
calibrator_source,
global_analogue_attenuation_db,
quack_time_duration_ms,
good_time_unix_ms,
good_time_gps_ms,
num_ants: num_antennas,
antennas,
num_rf_inputs,
rf_inputs,
num_ant_pols: num_antenna_pols,
num_metafits_timesteps,
metafits_timesteps,
num_metafits_coarse_chans,
metafits_coarse_chans,
num_metafits_fine_chan_freqs,
metafits_fine_chan_freqs_hz: metafits_fine_chan_freqs,
obs_bandwidth_hz: metafits_observation_bandwidth_hz,
coarse_chan_width_hz: metafits_coarse_chan_width_hz,
centre_freq_hz,
num_baselines,
baselines,
num_visibility_pols,
metafits_filename,
oversampled,
deripple_applied,
deripple_param,
best_cal_fit_id,
best_cal_obs_id,
best_cal_code_ver,
best_cal_fit_timestamp,
best_cal_creator,
best_cal_fit_iters,
best_cal_fit_iter_limit,
signal_chain_corrections,
num_signal_chain_corrections,
calibration_fits,
num_calibration_fits,
metafits_voltage_beams: beams,
num_metafits_voltage_beams: num_beams,
num_metafits_coherent_beams: num_coherent_beams,
num_metafits_incoherent_beams: num_incoherent_beams,
})
}
pub(crate) fn populate_expected_coarse_channels(
&mut self,
mwa_version: MWAVersion,
) -> Result<(), MwalibError> {
let mut metafits_fptr = fits_open!(&self.metafits_filename)?;
let metafits_hdu = fits_open_hdu!(&mut metafits_fptr, 0)?;
let (metafits_coarse_chan_vec, metafits_coarse_chan_width_hz) =
CoarseChannel::get_metafits_coarse_channel_info(
&mut metafits_fptr,
&metafits_hdu,
self.obs_bandwidth_hz,
)?;
self.metafits_coarse_chans
.extend(CoarseChannel::populate_coarse_channels(
mwa_version,
&metafits_coarse_chan_vec,
metafits_coarse_chan_width_hz,
None,
None,
)?);
self.num_metafits_coarse_chans = self.metafits_coarse_chans.len();
Ok(())
}
pub(crate) fn populate_expected_timesteps(
&mut self,
mwa_version: MWAVersion,
) -> Result<(), MwalibError> {
self.metafits_timesteps.extend(TimeStep::populate_timesteps(
self,
mwa_version,
self.sched_start_gps_time_ms,
self.sched_duration_ms,
self.sched_start_gps_time_ms,
self.sched_start_unix_time_ms,
));
self.num_metafits_timesteps = self.metafits_timesteps.len();
Ok(())
}
pub fn generate_expected_volt_filename(
&self,
metafits_timestep_index: usize,
metafits_coarse_chan_index: usize,
) -> Result<String, VoltageFileError> {
if metafits_timestep_index >= self.num_metafits_timesteps {
return Err(VoltageFileError::InvalidTimeStepIndex(
self.num_metafits_timesteps - 1,
));
}
if metafits_coarse_chan_index >= self.num_metafits_coarse_chans {
return Err(VoltageFileError::InvalidCoarseChanIndex(
self.num_metafits_coarse_chans - 1,
));
}
let obs_id = self.obs_id;
let gpstime = self.metafits_timesteps[metafits_timestep_index].gps_time_ms / 1000;
let chan = format!(
"{:03}",
self.metafits_coarse_chans[metafits_coarse_chan_index].rec_chan_number
);
let out_string = match self.mwa_version.unwrap() {
MWAVersion::VCSLegacyRecombined => format!("{}_{}_ch{}.dat", obs_id, gpstime, chan),
MWAVersion::VCSMWAXv2 => format!("{}_{}_{}.sub", obs_id, gpstime, chan),
_ => {
return Err(VoltageFileError::InvalidMwaVersion {
mwa_version: self.mwa_version.unwrap(),
})
}
};
Ok(out_string)
}
}
impl fmt::Display for MetafitsContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"MetafitsContext (
obsid: {obsid},
mode: {mode},
If Correlator Mode:
fine channel resolution: {fcw} kHz,
integration time: {int_time:.2} s
num fine channels/coarse: {nfcpc},
If Voltage Mode:
fine channel resolution: {vfcw} kHz,
num fine channels/coarse: {nvfcpc},
Geometric delays applied : {geodel},
Cable length corrections applied : {cabledel},
Calibration delays & gains applied: {calibdel},
Signal chain corrections applied : {sigchdel},
Creator: {creator},
Project ID: {project_id},
Observation Name: {obs_name},
Receivers: {receivers},
Delays: {delays},
Calibration: {calib},
Calibrator Source: {calsrc},
Global attenuation: {atten} dB,
Scheduled start (UNIX) {sched_start_unix},
Scheduled end (UNIX) {sched_end_unix},
Scheduled start (GPS) {sched_start_gps},
Scheduled end (GPS) {sched_end_gps},
Scheduled start (utc) {sched_start_utc},
Scheduled end (utc) {sched_end_utc},
Scheduled start (MJD) {sched_start_mjd},
Scheduled end (MJD) {sched_end_mjd},
Scheduled duration {sched_duration} s,
Quack time: {quack_duration} s,
Good UNIX start time: {good_time},
Num timesteps: {nts},
Timesteps: {ts},
Num coarse channels: {ncc},
Coarse Channels: {cc},
Oversampled coarse chans: {os},
Deripple applied: {dr} ({dr_param}),
Num fine channels: {nfc},
Fine Channels (MHz): {fc},
R.A. (tile_pointing): {rtpc} degrees,
Dec. (tile_pointing): {dtpc} degrees,
R.A. (phase center): {rppc} degrees,
Dec. (phase center): {dppc} degrees,
Azimuth: {az} degrees,
Altitude: {alt} degrees,
Sun altitude: {sun_alt} degrees,
Sun distance: {sun_dis} degrees,
Moon distance: {moon_dis} degrees,
Jupiter distance: {jup_dis} degrees,
LST: {lst} degrees,
Hour angle: {ha} degrees,
Grid name: {grid},
Grid number: {grid_n},
num antennas: {n_ants},
antennas: {ants},
rf_inputs: {rfs},
num antenna pols: {n_aps},
num baselines: {n_bls},
baselines: {bl01} v {bl02} to {bll1} v {bll2}
num auto-correlations: {n_ants},
num cross-correlations: {n_ccs},
visibility raw scale fact {crsf},
num visibility pols: {n_vps},
visibility pols: {vp0}, {vp1}, {vp2}, {vp3},
metafits FREQCENT key: {freqcent} MHz,
best calibrator info (if present)
..fit_id: {bcal_fit_id},
..obs_id: {bcal_obs_id},
..code_ver: {bcal_code_ver},
..fit_timestamp: {bcal_fit_timestamp},
..creator: {bcal_creator},
num signal chain corrs: {n_scc},
Signal chain corrs: {scc},
num calibration fits: {ncfits},
metafits filename: {meta},
)",
obsid = self.obs_id,
creator = self.creator,
project_id = self.project_id,
obs_name = self.obs_name,
receivers = pretty_print_vec(&self.receivers, 32),
delays = pretty_print_vec(&self.delays, 32),
atten = self.global_analogue_attenuation_db,
sched_start_unix = self.sched_start_unix_time_ms as f64 / 1e3,
sched_end_unix = self.sched_end_unix_time_ms as f64 / 1e3,
sched_start_gps = self.sched_start_gps_time_ms as f64 / 1e3,
sched_end_gps = self.sched_end_gps_time_ms as f64 / 1e3,
sched_start_utc = self.sched_start_utc,
sched_end_utc = self.sched_end_utc,
sched_start_mjd = self.sched_start_mjd,
sched_end_mjd = self.sched_end_mjd,
sched_duration = self.sched_duration_ms as f64 / 1e3,
quack_duration = self.quack_time_duration_ms as f64 / 1e3,
good_time = self.good_time_unix_ms as f64 / 1e3,
ts = pretty_print_vec(&self.metafits_timesteps, 10),
nts = self.metafits_timesteps.len(),
cc = pretty_print_vec(&self.metafits_coarse_chans, 24),
ncc = self.metafits_coarse_chans.len(),
os = self.oversampled,
dr = self.deripple_applied,
dr_param = match self.deripple_applied {
true => self.deripple_param.to_string(),
false => String::from("N/A"),
},
nfc = self.metafits_fine_chan_freqs_hz.len(),
fc = pretty_print_vec(
&self
.metafits_fine_chan_freqs_hz
.iter()
.map(|f| (f / 1000000.) as f32)
.collect::<Vec<f32>>(),
8
),
rtpc = format!("{:.3}", self.ra_tile_pointing_degrees),
dtpc = format!("{:.3}", self.dec_tile_pointing_degrees),
rppc = match self.ra_phase_center_degrees {
Some(s) => format!("{:.3}", s),
None => String::from("None"),
},
dppc = match self.dec_phase_center_degrees {
Some(s) => format!("{:.3}", s),
None => String::from("None"),
},
az = self.az_deg,
alt = self.alt_deg,
sun_alt = match self.sun_alt_deg {
Some(s) => s.to_string(),
None => String::from("None"),
},
sun_dis = match self.sun_distance_deg {
Some(s) => s.to_string(),
None => String::from("None"),
},
moon_dis = match self.moon_distance_deg {
Some(s) => s.to_string(),
None => String::from("None"),
},
jup_dis = match self.jupiter_distance_deg {
Some(s) => s.to_string(),
None => String::from("None"),
},
lst = self.lst_deg,
ha = self.hour_angle_string,
grid = self.grid_name,
grid_n = self.grid_number,
calib = self.calibrator,
calsrc = self.calibrator_source,
n_ants = self.num_ants,
ants = pretty_print_vec(&self.antennas, 256),
rfs = pretty_print_vec(&self.rf_inputs, 10),
n_aps = self.num_ant_pols,
n_bls = self.num_baselines,
bl01 = self.baselines[0].ant1_index,
bl02 = self.baselines[0].ant2_index,
bll1 = self.baselines[self.num_baselines - 1].ant1_index,
bll2 = self.baselines[self.num_baselines - 1].ant2_index,
n_ccs = self.num_baselines - self.num_ants,
n_vps = self.num_visibility_pols,
vp0 = VisPol::XX,
vp1 = VisPol::XY,
vp2 = VisPol::YX,
vp3 = VisPol::YY,
freqcent = self.centre_freq_hz as f64 / 1e6,
mode = self.mode,
geodel = self.geometric_delays_applied,
cabledel = self.cable_delays_applied,
calibdel = self.calibration_delays_and_gains_applied,
sigchdel = self.signal_chain_corrections_applied,
vfcw = self.volt_fine_chan_width_hz as f64 / 1e3,
nvfcpc = self.num_volt_fine_chans_per_coarse,
fcw = self.corr_fine_chan_width_hz as f64 / 1e3,
nfcpc = self.num_corr_fine_chans_per_coarse,
int_time = self.corr_int_time_ms as f64 / 1e3,
crsf = self.corr_raw_scale_factor,
n_scc = self.num_signal_chain_corrections,
scc = pretty_print_opt_vec(&self.signal_chain_corrections, 2),
ncfits = self.num_calibration_fits,
bcal_fit_id = match self.best_cal_fit_id {
Some(b) => b.to_string(),
None => String::from("None"),
},
bcal_obs_id = match self.best_cal_obs_id {
Some(b) => b.to_string(),
None => String::from("None"),
},
bcal_code_ver = match &self.best_cal_code_ver {
Some(b) => b.to_string(),
None => String::from("None"),
},
bcal_fit_timestamp = match &self.best_cal_fit_timestamp {
Some(b) => b.to_string(),
None => String::from("None"),
},
bcal_creator = match &self.best_cal_creator {
Some(b) => b.to_string(),
None => String::from("None"),
},
meta = self.metafits_filename,
)
}
}