sarpro 0.3.2

A high-performance Sentinel-1 Synthetic Aperture Radar (SAR) GRD product to image processor.
Documentation
use std::path::PathBuf;
use gdal::raster::ResampleAlg;
use tracing::info;
use sarpro::core::processing::save::{
    save_processed_image, save_processed_multiband_image_sequential,
};
use sarpro::io::sentinel1::TargetCrsArg;
use sarpro::io::SafeReader;
use sarpro::types::{BitDepth, OutputFormat, ProcessingOperation, SyntheticRgbMode};
use sarpro::{AutoscaleStrategy, BitDepthArg, InputFormat, Polarization, PolarizationOperation};
use sarpro::core::params::ProcessingOptions;

use crate::cli::errors::AppError;

pub fn process_single_file(
    input: &PathBuf,
    output: &PathBuf,
    format: OutputFormat,
    bit_depth: BitDepthArg,
    input_format: InputFormat,
    polarization: Polarization,
    autoscale: AutoscaleStrategy,
    size: &str,
    batch_mode: bool,
    pad: bool,
    target_crs: Option<&str>,
    resample_alg: Option<&str>,
    synrgb_mode: SyntheticRgbMode,
    _proc_opts: &ProcessingOptions,
) -> Result<(), Box<dyn std::error::Error>> {
    // autoscale now passed as typed enum directly

    let target_size = if size == "original" {
        None
    } else {
        let parsed_size = size.parse::<usize>().map_err(|_| AppError::InvalidSize {
            size: size.to_string(),
        })?;

        if parsed_size == 0 {
            return Err(AppError::ZeroSize { size: parsed_size }.into());
        }

        Some(parsed_size)
    };

    let reader = if batch_mode {
        match input_format {
            InputFormat::Safe => {
                // Map resample algorithm
                let resample = match resample_alg {
                    Some("nearest") => Some(ResampleAlg::NearestNeighbour),
                    Some("bilinear") => Some(ResampleAlg::Bilinear),
                    Some("cubic") => Some(ResampleAlg::Cubic),
                    Some("lanczos") => Some(ResampleAlg::Lanczos),
                    _ => None,
                };
                // Map target CRS string into internal enum; resolution happens inside reader
                let target_arg: Option<TargetCrsArg> = match target_crs {
                    Some(t) if t.eq_ignore_ascii_case("none") => Some(TargetCrsArg::None),
                    Some(t) if t.eq_ignore_ascii_case("auto") => Some(TargetCrsArg::Auto),
                    Some(t) => Some(TargetCrsArg::Custom(t.to_string())),
                    None => None,
                };
                match SafeReader::open_with_warnings_with_options(
                    input,
                    polarization,
                    target_arg,
                    resample,
                    target_size,
                )? {
                Some(reader) => reader,
                None => {
                    tracing::warn!("Skipping unsupported product type: {:?}", input);
                    return Ok(());
                }
            }
        }
        }
    } else {
        match input_format {
            InputFormat::Safe => {
                let resample_alg = match resample_alg {
                    Some("nearest") => Some(ResampleAlg::NearestNeighbour),
                    Some("bilinear") => Some(ResampleAlg::Bilinear),
                    Some("cubic") => Some(ResampleAlg::Cubic),
                    Some("lanczos") => Some(ResampleAlg::Lanczos),
                    _ => None,
                };
                let target_arg: Option<TargetCrsArg> = match target_crs {
                    Some(t) if t.eq_ignore_ascii_case("none") => Some(TargetCrsArg::None),
                    Some(t) if t.eq_ignore_ascii_case("auto") => Some(TargetCrsArg::Auto),
                    Some(t) => Some(TargetCrsArg::Custom(t.to_string())),
                    None => None,
                };
                SafeReader::open_with_options(
                    input,
                    polarization,
                    target_arg,
                    resample_alg,
                    target_size,
                )?
            }
        }
    };

    let bit_depth_enum = match bit_depth {
        BitDepthArg::U8 => BitDepth::U8,
        BitDepthArg::U16 => BitDepth::U16,
    };

    match polarization {
        Polarization::Vv | Polarization::Vh | Polarization::Hh | Polarization::Hv => {
            let processed = match polarization {
                Polarization::Vv => reader.vv_data()?,
                Polarization::Vh => reader.vh_data()?,
                Polarization::Hh => reader.hh_data()?,
                Polarization::Hv => reader.hv_data()?,
                _ => unreachable!(),
            };

            let bytes = processed.len() * std::mem::size_of::<f32>();
            info!(
                "Memory usage (approx): {:.2} MB",
                bytes as f64 / 1024.0 / 1024.0
            );

            save_processed_image(
                &processed,
                output.as_path(),
                format,
                bit_depth_enum,
                target_size,
                Some(reader.metadata()),
                pad,
                autoscale,
                ProcessingOperation::SingleBand,
            )
        }
        Polarization::Multiband => {
            if reader.vv_data().is_ok() && reader.vh_data().is_ok() {
                let vv_processed = reader.vv_data()?;
                let vh_processed = reader.vh_data()?;

                let total_bytes =
                    (vv_processed.len() + vh_processed.len()) * std::mem::size_of::<f32>();
                info!(
                    "Memory usage (Multiband VV/VH): {:.2} MB",
                    total_bytes as f64 / 1024.0 / 1024.0
                );

                save_processed_multiband_image_sequential(
                    &vv_processed,
                    &vh_processed,
                    output.as_path(),
                    format,
                    bit_depth_enum,
                    target_size,
                    Some(reader.metadata()),
                    pad,
                    autoscale,
                    ProcessingOperation::MultibandVvVh,
                    synrgb_mode,
                )
            } else if reader.hh_data().is_ok() && reader.hv_data().is_ok() {
                let hh_processed = reader.hh_data()?;
                let hv_processed = reader.hv_data()?;

                let total_bytes =
                    (hh_processed.len() + hv_processed.len()) * std::mem::size_of::<f32>();
                info!(
                    "Memory usage (Multiband HH/HV): {:.2} MB",
                    total_bytes as f64 / 1024.0 / 1024.0
                );

                save_processed_multiband_image_sequential(
                    &hh_processed,
                    &hv_processed,
                    output.as_path(),
                    format,
                    bit_depth_enum,
                    target_size,
                    Some(reader.metadata()),
                    pad,
                    autoscale,
                    ProcessingOperation::MultibandHhHv,
                    synrgb_mode,
                )
            } else {
                let available = reader.get_available_polarizations();
                return Err(AppError::IncompleDataPair {
                    operation: "multiband".to_string(),
                    available,
                }
                .into());
            }
        }
        Polarization::OP(_) => {
            let processed = if reader.vv_data().is_ok() && reader.vh_data().is_ok() {
                match polarization {
                    Polarization::OP(PolarizationOperation::Sum) => reader.sum_data()?,
                    Polarization::OP(PolarizationOperation::Diff) => reader.difference_data()?,
                    Polarization::OP(PolarizationOperation::Ratio) => reader.ratio_data()?,
                    Polarization::OP(PolarizationOperation::NDiff) => {
                        reader.normalized_diff_data()?
                    }
                    Polarization::OP(PolarizationOperation::LogRatio) => reader.log_ratio_data()?,
                    _ => unreachable!(),
                }
            } else if reader.hh_data().is_ok() && reader.hv_data().is_ok() {
                match polarization {
                    Polarization::OP(PolarizationOperation::Sum) => reader.sum_hh_hv_data()?,
                    Polarization::OP(PolarizationOperation::Diff) => {
                        reader.difference_hh_hv_data()?
                    }
                    Polarization::OP(PolarizationOperation::Ratio) => reader.ratio_hh_hv_data()?,
                    Polarization::OP(PolarizationOperation::NDiff) => {
                        reader.normalized_diff_hh_hv_data()?
                    }
                    Polarization::OP(PolarizationOperation::LogRatio) => {
                        reader.log_ratio_hh_hv_data()?
                    }
                    _ => unreachable!(),
                }
            } else {
                let available = reader.get_available_polarizations();
                return Err(AppError::IncompleDataPair {
                    operation: polarization.to_string(),
                    available,
                }
                .into());
            };

            let bytes = processed.len() * std::mem::size_of::<f32>();
            info!(
                "Memory usage (approx): {:.2} MB",
                bytes as f64 / 1024.0 / 1024.0
            );

            save_processed_image(
                &processed,
                output.as_path(),
                format,
                bit_depth_enum,
                target_size,
                Some(reader.metadata()),
                pad,
                autoscale,
                ProcessingOperation::PolarOp(match polarization {
                    Polarization::OP(op) => op,
                    _ => unreachable!(),
                }),
            )
        }
    }
}