chiron 0.1.0

Graphical and command-line interface tools for Biomechanics Foundation
Documentation
use grid::Grid;
use std::fmt::Display;
use std::path::PathBuf;
use std::str::FromStr;

use crate::args::{file_arg, output_arg};
use clap::{ArgMatches, Command};
use colored::Colorize;

use c3dio::file_formats::sto;
use c3dio::prelude::*;

pub(super) fn force_command() -> Command {
    Command::new("forces")
        .about("Prints the force data from a C3D file")
        .arg(file_arg().required(true))
        .arg(output_arg().required(true))
}

pub(super) fn process_forces_command(sub_matches: ArgMatches) {
    let file = sub_matches.get_one::<String>("FILE").unwrap();
    let output = sub_matches.get_one::<String>("OUTPUT").unwrap();
    let format = match output.split('.').last() {
        Some(format) => {
            let format = ForceOutputFileTypes::from_str(format.trim().to_lowercase().as_str());
            match format {
                Ok(format) => format,
                Err(e) => {
                    println!("{}", e.red());
                    return;
                }
            }
        }
        None => {
            println!(
                "{}",
                "No output format was provided, please provide a file with an extension".red()
            );
            return;
        }
    };
    println!("Opening {}", file.green());
    match C3d::load(file) {
        Ok(c3d) => {
            println!("Converting to {}", format.to_string().bright_yellow());
            let write_attempt = match format {
                ForceOutputFileTypes::Sto => {
                    let sto = build_sto(&c3d);
                    match sto {
                        Some(sto) => sto.write(PathBuf::from(output)),
                        None => Err(C3dWriteError::InvalidForcePlatformInfo(
                            "Could not build sto file from c3d file".to_string(),
                        )),
                    }
                }
            };
            match write_attempt {
                Ok(_) => println!("Wrote {}", output.green()),
                Err(e) => println!("{}", e.to_string().red()),
            }
        }
        Err(e) => println!("{}", e.to_string().red()),
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
enum ForceOutputFileTypes {
    Sto,
}

impl FromStr for ForceOutputFileTypes {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.trim().to_lowercase().as_str() {
            "sto" => Ok(ForceOutputFileTypes::Sto),
            _ => Err(format!("{} is not a valid output format", s)),
        }
    }
}

impl Display for ForceOutputFileTypes {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ForceOutputFileTypes::Sto => write!(f, "sto"),
        }
    }
}

fn build_sto(c3d: &C3d) -> Option<Sto> {
    let mut column_names = Vec::new();
    let mut data = Grid::new(c3d.analog.analog.size().0, 0);
    if c3d.forces.len() > 0 {
        for (i, plate) in c3d.forces.iter().enumerate() {
            for channel in plate.channels.into_iter() {
                let channel = channel as usize;
                if c3d.analog.labels.len() >= channel {
                    column_names.push(c3d.analog.labels[channel - 1].clone());
                } else {
                    column_names.push(format!("column_{}", channel));
                }
                data.push_col(c3d.analog.iter_col(channel - 1).cloned().collect());
            }
            let origin = plate.origin.as_ref();
            if origin.len() >= i + 1 {
                column_names.push(format!("EC{}X", i + 1));
                column_names.push(format!("EC{}Y", i + 1));
                column_names.push(format!("EC{}Z", i + 1));
                data.push_col(vec![origin[0] as f64; data.size().0]);
                data.push_col(vec![origin[1] as f64; data.size().0]);
                data.push_col(vec![origin[2] as f64; data.size().0]);
            }
        }
        Some(Sto {
            file_description: None,
            version: 1,
            in_degrees: false,
            first_frame: c3d.points.first_frame as usize,
            column_names,
            data_rate: c3d.analog.rate,
            data,
        })
    } else {
        println!("{}", "No force data was found in the C3D file".red());
        None
    }
}