yscv-eval 0.1.7

Evaluation metrics (mAP, MOTA, HOTA) and dataset adapters
Documentation
use std::collections::HashMap;
use std::fs;
use std::io::ErrorKind;
use std::path::Path;

use crate::{DetectionDatasetFrame, EvalError};

pub(crate) fn read_optional_text_file(path: &Path) -> Result<Option<String>, EvalError> {
    match fs::read_to_string(path) {
        Ok(text) => Ok(Some(text)),
        Err(err) if err.kind() == ErrorKind::NotFound => Ok(None),
        Err(err) => Err(EvalError::DatasetIo {
            path: path.display().to_string(),
            message: err.to_string(),
        }),
    }
}

pub(crate) fn ensure_detection_frame_index(
    frames: &mut Vec<DetectionDatasetFrame>,
    image_index_by_id: &mut HashMap<String, usize>,
    image_id: &str,
) -> usize {
    if let Some(frame_idx) = image_index_by_id.get(image_id) {
        return *frame_idx;
    }
    let frame_idx = frames.len();
    image_index_by_id.insert(image_id.to_string(), frame_idx);
    frames.push(DetectionDatasetFrame {
        ground_truth: Vec::new(),
        predictions: Vec::new(),
    });
    frame_idx
}

pub(crate) fn ensure_frame_slot<T>(frames: &mut Vec<Vec<T>>, frame_idx: usize) {
    if frames.len() <= frame_idx {
        frames.resize_with(frame_idx + 1, Vec::new);
    }
}

pub(crate) fn parse_image_id_manifest(
    text: &str,
    format: &'static str,
) -> Result<Vec<String>, EvalError> {
    use std::collections::HashSet;
    let mut image_ids = Vec::new();
    let mut seen = HashSet::new();
    for (line_idx, raw_line) in text.lines().enumerate() {
        let line_no = line_idx + 1;
        let line = raw_line.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        if line.split_whitespace().count() != 1 {
            return Err(EvalError::InvalidDatasetFormat {
                format,
                message: format!("manifest line {line_no} must contain exactly one image id"),
            });
        }
        if !seen.insert(line.to_string()) {
            return Err(EvalError::InvalidDatasetFormat {
                format,
                message: format!("duplicate image id `{line}` in manifest at line {line_no}"),
            });
        }
        image_ids.push(line.to_string());
    }
    Ok(image_ids)
}