Skip to content
Snippets Groups Projects
camera_model.rs 5.44 KiB
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::str::FromStr;

use crate::Error;

pub struct CamModel {
    pub tstw: Vec<f32>,
    pub num_ctrl_pts: usize,
    pub ctrl_pts: Vec<f32>,
    pub weights: Vec<f32>,
    pub coefs: Vec<f32>,
    pub tonemap: Vec<f32>,
}

pub fn load_cam_model<P: AsRef<Path>>(cam_model: P, chans: usize) -> Result<CamModel, Error> {
    let (num_ctrl_pts, ctrl_pts) = load_ctrl_pts(&cam_model, chans)?;
    let (weights, coefs) = load_weights_coefs(&cam_model, chans, num_ctrl_pts)?;

    Ok(CamModel {
        tstw: load_tstw(&cam_model, chans)?,
        num_ctrl_pts,
        ctrl_pts,
        weights,
        coefs,
        tonemap: load_tonemap(&cam_model, chans)?,
    })
}

fn load_tstw<P: AsRef<Path>>(cam_model: &P, chans: usize) -> Result<Vec<f32>, Error> {
    let f = File::open(Path::join(cam_model.as_ref(), "raw2jpg_transform.txt"))?;
    let f = BufReader::new(f);

    // TODO: I don't understand this file and what this computation is doing, but I can't find any
    // source even going back to the original repo
    // NOTE: I've replaced the "wb_index" with the value it's provided in the HPVM repo (6)
    let wb_base = 5 + 5 * (6 - 1);

    let lines = f.lines().skip(wb_base).take(chans);

    let mut tstw = vec![0.0; chans * chans];
    let mut tstw_chunked = tstw.chunks_mut(chans).collect::<Vec<_>>();
    let tstw_ref: &mut [&mut [f32]] = tstw_chunked.as_mut_slice();

    for (i, line) in lines.enumerate() {
        let line = line?;
        let words = line.split_whitespace();
        let mut count = 0;
        for (j, word) in words.enumerate() {
            // The HPVM version computes this data structure and then transposes it separately, but
            // doing the transposition here since it makes more sense
            tstw_ref[j][i] = f32::from_str(word)?;
            count += 1;
        }
        assert_eq!(count, chans, "Missing tstw channels");
    }

    Ok(tstw)
}

fn load_ctrl_pts<P: AsRef<Path>>(cam_model: &P, chans: usize) -> Result<(usize, Vec<f32>), Error> {
    let f = File::open(Path::join(cam_model.as_ref(), "raw2jpg_ctrlPoints.txt"))?;
    let mut f = BufReader::new(f);

    let mut first_line = String::new();
    let _ = f.read_line(&mut first_line)?;
    let num_ctrl_pts = usize::from_str(first_line.trim())?;

    let mut ctrl_pts = vec![0.0; num_ctrl_pts * chans];
    let mut ctrl_pts_chunked = ctrl_pts.chunks_mut(chans).collect::<Vec<_>>();
    let result: &mut [&mut [f32]] = ctrl_pts_chunked.as_mut_slice();

    for (i, line) in f.lines().enumerate() {
        if i >= num_ctrl_pts {
            break;
        }

        let line = line?;
        let words = line.split_whitespace();
        let mut count = 0;
        for (j, word) in words.enumerate() {
            result[i][j] = f32::from_str(word)?;
            count += 1;
        }
        assert_eq!(count, chans, "Missing control point channel");
    }

    Ok((num_ctrl_pts, ctrl_pts))
}

fn load_weights_coefs<P: AsRef<Path>>(
    cam_model: &P,
    chans: usize,
    num_ctrl_pts: usize,
) -> Result<(Vec<f32>, Vec<f32>), Error> {
    let f = File::open(Path::join(cam_model.as_ref(), "raw2jpg_coefs.txt"))?;
    let mut f = BufReader::new(f);

    let mut first_line = String::new();
    let _ = f.read_line(&mut first_line)?;
    let lines = usize::from_str(first_line.trim())?;

    assert_eq!(lines, num_ctrl_pts + 4);

    let mut weights = vec![0.0; num_ctrl_pts * chans];
    let mut weights_chunked = weights.chunks_mut(chans).collect::<Vec<_>>();
    let weights_dst: &mut [&mut [f32]] = weights_chunked.as_mut_slice();

    let mut coefs = vec![0.0; 4 * chans];
    let mut coefs_chunked = coefs.chunks_mut(chans).collect::<Vec<_>>();
    let coefs_dst: &mut [&mut [f32]] = coefs_chunked.as_mut_slice();

    for (i, line) in f.lines().enumerate() {
        let line = line?;
        let words = line.split_whitespace();

        if i < num_ctrl_pts {
            let mut count = 0;
            for (j, word) in words.enumerate() {
                weights_dst[i][j] = f32::from_str(word)?;
                count += 1;
            }
            assert_eq!(count, chans, "Missing weight channel");
        } else if i < num_ctrl_pts + 4 {
            let mut count = 0;
            for (j, word) in words.enumerate() {
                coefs_dst[i - num_ctrl_pts][j] = f32::from_str(word)?;
                count += 1;
            }
            assert_eq!(count, chans, "Missing coefficient channel");
        } else {
            break;
        }
    }

    Ok((weights, coefs))
}

fn load_tonemap<P: AsRef<Path>>(cam_model: &P, chans: usize) -> Result<Vec<f32>, Error> {
    let f = File::open(Path::join(cam_model.as_ref(), "raw2jpg_respFcns.txt"))?;
    let mut f = BufReader::new(f);

    let mut first_line = String::new();
    let _ = f.read_line(&mut first_line)?;
    // TODO: What does the first line mean? Should we use it for something?

    let mut tonemap = vec![0.0; 256 * chans];
    let mut tonemap_chunked = tonemap.chunks_mut(chans).collect::<Vec<_>>();
    let result: &mut [&mut [f32]] = tonemap_chunked.as_mut_slice();

    for (i, line) in f.lines().enumerate() {
        if i >= 256 {
            break;
        }

        let line = line?;
        let words = line.split_whitespace();
        let mut count = 0;
        for (j, word) in words.enumerate() {
            result[i][j] = f32::from_str(word)?;
            count += 1;
        }
        assert_eq!(count, chans, "Missing tonemap channels");
    }

    Ok(tonemap)
}