oxihuman_export/
beard_export.rs1#![allow(dead_code)]
4
5pub struct BeardStrand {
6 pub root: [f32; 3],
7 pub length_mm: f32,
8 pub diameter_um: f32,
9 pub color_rgb: [f32; 3],
10}
11
12pub fn new_beard_strand(root: [f32; 3], length: f32) -> BeardStrand {
13 BeardStrand {
14 root,
15 length_mm: length,
16 diameter_um: 80.0,
17 color_rgb: [0.15, 0.10, 0.08],
18 }
19}
20
21pub fn beard_strand_to_csv_line(s: &BeardStrand) -> String {
22 format!(
23 "{:.4},{:.4},{:.4},{:.3},{:.2},{:.4},{:.4},{:.4}",
24 s.root[0],
25 s.root[1],
26 s.root[2],
27 s.length_mm,
28 s.diameter_um,
29 s.color_rgb[0],
30 s.color_rgb[1],
31 s.color_rgb[2]
32 )
33}
34
35pub fn beard_strands_to_csv(strands: &[BeardStrand]) -> String {
36 let mut out = String::from("rx,ry,rz,length_mm,diameter_um,cr,cg,cb\n");
37 for s in strands {
38 out.push_str(&beard_strand_to_csv_line(s));
39 out.push('\n');
40 }
41 out
42}
43
44pub fn beard_mean_length(strands: &[BeardStrand]) -> f32 {
45 if strands.is_empty() {
46 return 0.0;
47 }
48 strands.iter().map(|s| s.length_mm).sum::<f32>() / strands.len() as f32
49}
50
51pub fn beard_coverage_density(strands: &[BeardStrand], area_cm2: f32) -> f32 {
52 if area_cm2 <= 0.0 {
53 return 0.0;
54 }
55 strands.len() as f32 / area_cm2
56}
57
58pub fn beard_count(strands: &[BeardStrand]) -> usize {
59 strands.len()
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn test_new_beard_strand() {
68 let s = new_beard_strand([0.0; 3], 10.0);
70 assert!((s.length_mm - 10.0).abs() < 1e-6);
71 assert!((s.diameter_um - 80.0).abs() < 1e-6);
72 }
73
74 #[test]
75 fn test_to_csv_line() {
76 let s = new_beard_strand([1.0, 2.0, 3.0], 10.0);
78 let line = beard_strand_to_csv_line(&s);
79 assert!(line.contains("1.0000"));
80 }
81
82 #[test]
83 fn test_strands_to_csv_header() {
84 let csv = beard_strands_to_csv(&[]);
86 assert!(csv.contains("length_mm"));
87 }
88
89 #[test]
90 fn test_mean_length() {
91 let strands = vec![
93 new_beard_strand([0.0; 3], 8.0),
94 new_beard_strand([0.0; 3], 12.0),
95 ];
96 assert!((beard_mean_length(&strands) - 10.0).abs() < 1e-6);
97 }
98
99 #[test]
100 fn test_density() {
101 let strands: Vec<BeardStrand> = (0..10).map(|_| new_beard_strand([0.0; 3], 5.0)).collect();
103 assert!((beard_coverage_density(&strands, 5.0) - 2.0).abs() < 1e-6);
104 }
105
106 #[test]
107 fn test_count() {
108 let strands: Vec<BeardStrand> = (0..7).map(|_| new_beard_strand([0.0; 3], 5.0)).collect();
110 assert_eq!(beard_count(&strands), 7);
111 }
112}