frinZ 4.4.0

fringe search for Yamaguchi Interferometer and Japanese VLBI Network
Documentation
use crate::args::Args;
use crate::header::parse_header;
use crate::plot::plot_uv_tracks;
use crate::read::{read_sector_header, read_visibility_data};
use crate::utils::{radec2azalt, uvw_cal};
use byteorder::{LittleEndian, ReadBytesExt};
use chrono::{Duration, TimeZone, Timelike, Utc};
use std::error::Error;
use std::fs::{self, File};
use std::io::{Cursor, Read};
use std::path::Path;

const MIN_ELEVATION_DEG: f32 = 5.0;

pub fn run_uv_plot(args: &Args, uv_mode: i32) -> Result<(), Box<dyn Error>> {
    if uv_mode != 0 && uv_mode != 1 {
        return Err("--uv accepts only 0 (planar) or 1 (3D)".into());
    }
    let input_path = args
        .input
        .as_ref()
        .ok_or("Error: --uv requires an --input file.")?;

    let mut file = File::open(input_path)?;
    let mut buffer = Vec::new();
    file.read_to_end(&mut buffer)?;

    let mut cursor = Cursor::new(buffer.as_slice());
    let header = parse_header(&mut cursor)?;

    let include_vertical = uv_mode != 0;

    let start_sector = args.skip.max(0);
    if start_sector >= header.number_of_sector {
        return Err(format!(
            "Skip value ({}) exceeds available sectors ({}).",
            args.skip, header.number_of_sector
        )
        .into());
    }

    let available_sectors = header.number_of_sector - start_sector;
    let sectors_to_use = if args.length > 0 {
        args.length.min(available_sectors)
    } else {
        available_sectors
    };

    if sectors_to_use <= 0 {
        return Err("No sectors available after applying --skip/--length.".into());
    }

    let mut data_cursor = Cursor::new(buffer.as_slice());
    let (_, first_time, effective_integ_time) =
        read_visibility_data(&mut data_cursor, &header, 1, 0, start_sector, false, &[])?;

    if effective_integ_time <= 0.0 {
        return Err("Effective integration time is zero; cannot compute UV coverage.".into());
    }

    let mut sector_cursor = Cursor::new(buffer.as_slice());
    let sector_headers = read_sector_header(
        &mut sector_cursor,
        &header,
        sectors_to_use,
        start_sector,
        0,
        false,
    )?;

    let mut observation_times = Vec::with_capacity(sector_headers.len());
    for header_bytes in sector_headers {
        let mut sector_reader = Cursor::new(header_bytes);
        let correlation_time_sec = sector_reader.read_i32::<LittleEndian>()?;
        let obs_time = Utc
            .timestamp_opt(correlation_time_sec as i64, 0)
            .single()
            .ok_or_else(|| format!("Invalid timestamp seconds: {}", correlation_time_sec))?;
        observation_times.push(obs_time);
    }

    if observation_times.is_empty() {
        return Err("Failed to read sector timestamps for UV plotting.".into());
    }

    let mut observed_uv: Vec<(f32, f32)> = Vec::with_capacity(observation_times.len());
    let mut observed_baseline: Vec<(f32, f32)> = Vec::with_capacity(observation_times.len());
    for obs_time in &observation_times {
        let (u, v, _w, _du_dt, _dv_dt) = uvw_cal(
            header.station1_position,
            header.station2_position,
            *obs_time,
            header.source_position_ra,
            header.source_position_dec,
            include_vertical,
        );
        let u_f = u as f32;
        let v_f = v as f32;
        let ut_hour = obs_time.hour() as f32
            + obs_time.minute() as f32 / 60.0
            + obs_time.second() as f32 / 3600.0;
        observed_uv.push((u_f, v_f));
        observed_baseline.push((ut_hour, (u_f * u_f + v_f * v_f).sqrt()));
    }

    let ant1_position = [
        header.station1_position[0] as f32,
        header.station1_position[1] as f32,
        header.station1_position[2] as f32,
    ];
    let ant2_position = [
        header.station2_position[0] as f32,
        header.station2_position[1] as f32,
        header.station2_position[2] as f32,
    ];

    let day_start = first_time
        .date_naive()
        .and_hms_opt(0, 0, 0)
        .map(|naive| Utc.from_utc_datetime(&naive))
        .unwrap_or(first_time);
    let day_end = day_start + Duration::hours(24);

    let mut accessible_uv: Vec<(f32, f32)> = Vec::new();
    let mut accessible_baseline: Vec<(f32, f32)> = Vec::new();
    let mut current_time = day_start;
    let step = Duration::minutes(3);
    while current_time <= day_end {
        let (_, el1, _) = radec2azalt(
            ant1_position,
            current_time,
            header.source_position_ra as f32,
            header.source_position_dec as f32,
        );
        let (_, el2, _) = radec2azalt(
            ant2_position,
            current_time,
            header.source_position_ra as f32,
            header.source_position_dec as f32,
        );

        if el1 >= MIN_ELEVATION_DEG && el2 >= MIN_ELEVATION_DEG {
            let (u, v, _w, _du_dt, _dv_dt) = uvw_cal(
                header.station1_position,
                header.station2_position,
                current_time,
                header.source_position_ra,
                header.source_position_dec,
                include_vertical,
            );
            let u_f = u as f32;
            let v_f = v as f32;
            let ut_hour = current_time.hour() as f32
                + current_time.minute() as f32 / 60.0
                + current_time.second() as f32 / 3600.0;
            accessible_uv.push((u_f, v_f));
            accessible_baseline.push((ut_hour, (u_f * u_f + v_f * v_f).sqrt()));
        }

        current_time += step;
    }

    let parent_dir = input_path.parent().unwrap_or_else(|| Path::new(""));
    let output_dir = parent_dir.join("frinZ").join("uv");
    fs::create_dir_all(&output_dir)?;

    let base_stem = input_path
        .file_stem()
        .and_then(|s| s.to_str())
        .ok_or("Invalid input filename")?;
    let output_path = output_dir.join(format!("{}_uv.png", base_stem));

    let bandwidth_hz = header.sampling_speed as f64 / 2.0;
    let center_frequency_hz = header.observing_frequency + bandwidth_hz / 2.0;
    if center_frequency_hz <= 0.0 {
        return Err("Center frequency is non-positive; cannot scale spatial frequency.".into());
    }

    plot_uv_tracks(
        &output_path,
        &accessible_uv,
        &observed_uv,
        &accessible_baseline,
        &observed_baseline,
        center_frequency_hz,
        uv_mode,
    )?;

    println!("Generated UV coverage plot at {:?}", output_path);

    Ok(())
}