Skip to main content

oxihuman_export/
hemoglobin_map_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5pub struct HemoglobinMap {
6    pub width: u32,
7    pub height: u32,
8    pub oxy: Vec<f32>,
9    pub deoxy: Vec<f32>,
10}
11
12pub fn new_hemoglobin_map(w: u32, h: u32) -> HemoglobinMap {
13    let n = (w * h) as usize;
14    HemoglobinMap {
15        width: w,
16        height: h,
17        oxy: vec![0.0; n],
18        deoxy: vec![0.0; n],
19    }
20}
21
22fn idx(m: &HemoglobinMap, x: u32, y: u32) -> usize {
23    (y * m.width + x) as usize
24}
25
26pub fn hemo_map_set(m: &mut HemoglobinMap, x: u32, y: u32, oxy: f32, deoxy: f32) {
27    let i = idx(m, x, y);
28    m.oxy[i] = oxy;
29    m.deoxy[i] = deoxy;
30}
31
32pub fn hemo_map_get(m: &HemoglobinMap, x: u32, y: u32) -> (f32, f32) {
33    let i = idx(m, x, y);
34    (m.oxy[i], m.deoxy[i])
35}
36
37pub fn hemo_map_oxygen_saturation(m: &HemoglobinMap, x: u32, y: u32) -> f32 {
38    let (oxy, deoxy) = hemo_map_get(m, x, y);
39    let total = oxy + deoxy;
40    if total < 1e-8 {
41        0.0
42    } else {
43        oxy / total
44    }
45}
46
47pub fn hemo_map_to_bytes(m: &HemoglobinMap) -> Vec<u8> {
48    m.oxy
49        .iter()
50        .zip(m.deoxy.iter())
51        .flat_map(|(&oxy, &deoxy)| {
52            let o = (oxy.clamp(0.0, 1.0) * 255.0) as u8;
53            let d = (deoxy.clamp(0.0, 1.0) * 255.0) as u8;
54            [o, d, 0u8, 255u8]
55        })
56        .collect()
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn test_new_hemoglobin_map() {
65        /* correct size */
66        let m = new_hemoglobin_map(4, 4);
67        assert_eq!(m.oxy.len(), 16);
68    }
69
70    #[test]
71    fn test_set_get() {
72        /* round-trip */
73        let mut m = new_hemoglobin_map(2, 2);
74        hemo_map_set(&mut m, 0, 0, 0.9, 0.1);
75        let (oxy, deoxy) = hemo_map_get(&m, 0, 0);
76        assert!((oxy - 0.9).abs() < 1e-6);
77        assert!((deoxy - 0.1).abs() < 1e-6);
78    }
79
80    #[test]
81    fn test_oxygen_saturation_half() {
82        /* equal oxy and deoxy => 0.5 */
83        let mut m = new_hemoglobin_map(2, 2);
84        hemo_map_set(&mut m, 0, 0, 0.5, 0.5);
85        assert!((hemo_map_oxygen_saturation(&m, 0, 0) - 0.5).abs() < 1e-6);
86    }
87
88    #[test]
89    fn test_oxygen_saturation_zero() {
90        /* zero total => 0 */
91        let m = new_hemoglobin_map(2, 2);
92        assert!((hemo_map_oxygen_saturation(&m, 0, 0)).abs() < 1e-6);
93    }
94
95    #[test]
96    fn test_to_bytes_length() {
97        /* 4 bytes per pixel */
98        let m = new_hemoglobin_map(3, 3);
99        let bytes = hemo_map_to_bytes(&m);
100        assert_eq!(bytes.len(), 3 * 3 * 4);
101    }
102}