triplclust_rs 0.4.0

Rust version of C. Dalitz's triplclust clustering algorithm
Documentation
//! This is helper code to load and wrangle copies of data generated by the original
//! triplclust algorithm for testing and validating the behavior of this code.
//! I'm not docstringing all of this.
use numpy::ndarray::{Array1, Array2};
use rustc_hash::{FxHashMap, FxHashSet};
use std::env::var;
use std::fs::read_to_string;
use std::path::{Path, PathBuf};

#[derive(Debug)]
pub enum LoadError {
    FailedIO(std::io::Error),
    BadFloatParse(std::num::ParseFloatError),
    BadIntParse(std::num::ParseIntError),
    NoManifest(std::env::VarError),
}

impl From<std::io::Error> for LoadError {
    fn from(value: std::io::Error) -> Self {
        Self::FailedIO(value)
    }
}

impl From<std::num::ParseFloatError> for LoadError {
    fn from(value: std::num::ParseFloatError) -> Self {
        Self::BadFloatParse(value)
    }
}
impl From<std::num::ParseIntError> for LoadError {
    fn from(value: std::num::ParseIntError) -> Self {
        Self::BadIntParse(value)
    }
}

impl From<std::env::VarError> for LoadError {
    fn from(value: std::env::VarError) -> Self {
        Self::NoManifest(value)
    }
}

impl std::fmt::Display for LoadError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::FailedIO(err) => {
                write!(f, "Failed to load cloud from data file with error: {}", err)
            }
            Self::BadFloatParse(err) => {
                write!(f, "Failed to parse data in cloud file with error: {}", err)
            }
            Self::BadIntParse(err) => {
                write!(f, "Failed to parse data in cloud file with error: {}", err)
            }
            Self::NoManifest(err) => {
                write!(f, "Could not find cargo manifest directory: {}", err)
            }
        }
    }
}

impl std::error::Error for LoadError {}

pub fn get_test_data_path() -> Result<PathBuf, LoadError> {
    let manifest: PathBuf = var("CARGO_MANIFEST_DIR")?.into();
    let crates_path = manifest.parent().expect("Cargo manifest has no parent?");
    let workspace_root = crates_path.parent().expect("Cargo manifest has no parent?");
    Ok(workspace_root.join("data"))
}

pub fn load_cloud_from_dat(path: &Path) -> Result<Array2<f64>, LoadError> {
    let data = read_to_string(path)?;
    let mut cloud = Array2::<f64>::zeros((data.lines().fold(0, |x, _| x + 1) - 3, 3));
    for (ridx, row) in data.lines().enumerate() {
        if ridx < 3 {
            continue;
        }
        let entries = row.split(" ");
        for (cidx, entry) in entries.enumerate() {
            cloud[(ridx - 3, cidx)] = entry.parse()?;
        }
    }

    Ok(cloud)
}

pub fn load_test_data() -> Result<Array2<f64>, LoadError> {
    let path = get_test_data_path()?.join("test.dat");
    load_cloud_from_dat(&path)
}

pub fn load_cdist_data() -> Result<Array1<f64>, LoadError> {
    let path = get_test_data_path()?.join("debug_cdist.csv");
    let data = read_to_string(path)?;
    let mut array = Array1::<f64>::zeros(data.lines().fold(0, |x, _| x + 1));
    for (idx, line) in data.lines().enumerate() {
        array[idx] = line.split(",").collect::<Vec<&str>>()[0].parse()?;
    }
    Ok(array)
}

pub fn load_test_results() -> Result<
    (
        f64,
        f64,
        f64,
        f64,
        usize,
        Array2<f64>,
        Vec<Vec<i32>>,
        FxHashSet<i32>,
    ),
    LoadError,
> {
    let path = get_test_data_path()?.join("result.csv");
    let spath = get_test_data_path()?.join("debug_smoothed.csv");
    let smooth_data = read_to_string(spath)?;
    let spoints = smooth_data.lines().fold(0, |x, _| x + 1) - 1;
    let mut smooth_array = Array2::<f64>::zeros((spoints, 3));
    for (ridx, row) in smooth_data.lines().enumerate() {
        if ridx < 1 {
            continue;
        }
        for (cidx, entry) in row.split(",").enumerate() {
            smooth_array[(ridx - 1, cidx)] = entry.parse()?;
        }
    }
    let data = read_to_string(path)?;
    let mut results_labels = vec![];
    let mut unique_labels = FxHashSet::<i32>::default();
    let mut dnn = 0.0;
    let mut smooth_radius = 0.0;
    let mut dist_scale = 0.0;
    let mut cluster_threshold = 0.0;
    let mut n_removed = 0;
    let mut label_map = FxHashMap::<i32, i32>::default();
    // Mapping of triplclust_rs labels to triplclust labels
    // determined by visual inspection... not the best
    label_map.insert(0, 3);
    label_map.insert(1, 0);
    label_map.insert(2, 1);
    label_map.insert(3, 2);
    label_map.insert(-1, -1);
    for (ridx, row) in data.lines().enumerate() {
        if ridx < 7 {
            let entries = row.split(" ").collect::<Vec<&str>>();
            match ridx {
                0 => dnn = entries[3].parse()?,
                1 => smooth_radius = entries[4].parse()?,
                2 => dist_scale = entries[4].parse()?,
                3 => cluster_threshold = entries[4].parse()?,
                4 => n_removed = entries[5].parse()?,
                _ => (),
            };
            continue;
        }
        let entries = row.split(",");
        results_labels.push(vec![]);
        for (cidx, entry) in entries.enumerate() {
            if cidx < 3 {
                continue;
            } else {
                if entry.contains(";") {
                    let labels = entry.split(";").collect::<Vec<&str>>();
                    for label in labels {
                        let tr_label = match label_map.get(&label.parse()?) {
                            Some(l) => l,
                            None => panic!(),
                        };
                        results_labels[ridx - 7].push(*tr_label);
                        unique_labels.insert(*tr_label);
                    }
                } else {
                    let tr_label = match label_map.get(&entry.parse()?) {
                        Some(l) => l,
                        None => panic!(),
                    };
                    results_labels[ridx - 7].push(*tr_label);
                    unique_labels.insert(*tr_label);
                }
            }
        }
    }
    Ok((
        dnn,
        smooth_radius,
        dist_scale,
        cluster_threshold,
        n_removed,
        smooth_array,
        results_labels,
        unique_labels,
    ))
}

pub fn load_o16_event_pointcloud_data() -> Result<Array2<f64>, LoadError> {
    let path = get_test_data_path()?.join("o16_event41470.csv");
    let data = read_to_string(path)?;
    let mut cloud = Array2::<f64>::zeros((data.lines().fold(0, |x, _| x + 1), 3));
    for (ridx, row) in data.lines().enumerate() {
        let entries = row.split(" ");
        for (cidx, entry) in entries.enumerate() {
            cloud[(ridx, cidx)] = entry.parse()?;
        }
    }

    Ok(cloud)
}