Reorganized xyz_spectra crate a bit.

This way the executable code can be worked with directly, instead
of via the python file that generates the rust code.

Also introduced some small optimizations.
This commit is contained in:
Nathan Vegdahl 2018-07-01 14:29:19 -07:00
parent 989914b878
commit ef7084e694
3 changed files with 1308 additions and 20350 deletions

View File

@ -3,6 +3,8 @@
# This file is originally from the supplemental material of the paper
# "Physically Meaningful Rendering using Tristimulus Colours" by Meng et al.
# It has been adapted by Nathan Vegdahl to generate Rust instead of C.
# Only the data tables are generated, and should be put in spectra_tables.rs
# The executable code lives in lib.rs.
import numpy as np
import scipy
@ -32,137 +34,6 @@ have_matplotlib = True
#except:
# have_matplotlib = False
SPEC_FUNC = """// This file is auto-generated by generate_spectra_rust.py
#![allow(dead_code)]
#![cfg_attr(rustfmt, rustfmt_skip)]
use std::f32;
/// Evaluate the spectrum for xyz at the given wavelength.
pub fn spectrum_xyz_to_p(lambda: f32, xyz: (f32, f32, f32)) -> f32
{
assert!(lambda >= SPECTRUM_SAMPLE_MIN);
assert!(lambda <= SPECTRUM_SAMPLE_MAX);
let norm = {
let norm = 1.0 / (xyz.0 + xyz.1 + xyz.2);
if norm < f32::MAX {
norm
} else {
return 0.0;
}
};
let xyy = (
xyz.0 * norm,
xyz.1 * norm,
xyz.1,
);
// rotate to align with grid
let uv = spectrum_xy_to_uv((xyy.0, xyy.1));
if uv.0 < 0.0 || uv.0 >= SPECTRUM_GRID_WIDTH as f32 || uv.1 < 0.0 || uv.1 >= SPECTRUM_GRID_HEIGHT as f32 {
return 0.0;
}
let uvi = (uv.0 as i32, uv.1 as i32);
assert!(uvi.0 < SPECTRUM_GRID_WIDTH);
assert!(uvi.1 < SPECTRUM_GRID_HEIGHT);
let cell_idx: i32 = uvi.0 + SPECTRUM_GRID_WIDTH * uvi.1;
assert!(cell_idx < SPECTRUM_GRID_WIDTH * SPECTRUM_GRID_HEIGHT);
assert!(cell_idx >= 0);
let cell = &SPECTRUM_GRID[cell_idx as usize];
let inside: bool = cell.inside;
let idx = &cell.idx;
let num: i32 = cell.num_points;
// if the cell has no points, nothing we can do, so return 0
if num == 0 {
return 0.0;
}
// get linearly interpolated spectral power for the corner vertices:
// this clamping is only necessary if lambda is not sure to be >= SPECTRUM_SAMPLE_MIN and <= SPECTRUM_SAMPLE_MAX:
let sb: f32 = /*(SPECTRUM_NUM_SAMPLES as f32 - 1e-4).min(0.0.max(*/ (lambda - SPECTRUM_SAMPLE_MIN) / (SPECTRUM_SAMPLE_MAX - SPECTRUM_SAMPLE_MIN) * (SPECTRUM_NUM_SAMPLES as f32 - 1.0)/*))*/;
assert!(sb >= 0.0);
assert!(sb <= SPECTRUM_NUM_SAMPLES as f32);
let mut p = [0.0f32; 6];
let sb0: i32 = sb as i32;
let sb1: i32 = if (sb + 1.0) < SPECTRUM_NUM_SAMPLES as f32 { sb as i32 + 1 } else { SPECTRUM_NUM_SAMPLES - 1 };
let sbf: f32 = sb as f32 - sb0 as f32;
for i in 0..(num as usize) {
assert!(idx[i] >= 0);
assert!(sb0 < SPECTRUM_NUM_SAMPLES);
assert!(sb1 < SPECTRUM_NUM_SAMPLES);
let spectrum = &SPECTRUM_DATA_POINTS[idx[i] as usize].spectrum;
p[i] = spectrum[sb0 as usize] * (1.0 - sbf) + spectrum[sb1 as usize] * sbf;
}
let mut interpolated_p: f32 = 0.0;
if inside { // fast path for normal inner quads:
let uv2 = (uv.0 - uvi.0 as f32, uv.1 - uvi.1 as f32);
assert!(uv2.0 >= 0.0 && uv2.0 <= 1.0);
assert!(uv2.1 >= 0.0 && uv2.1 <= 1.0);
// the layout of the vertices in the quad is:
// 2 3
// 0 1
interpolated_p = p[0] * (1.0 - uv2.0) * (1.0 - uv2.1) +
p[2] * (1.0 - uv2.0) * uv2.1 +
p[3] * uv2.0 * uv2.1 +
p[1] * uv2.0 * (1.0 - uv2.1);
} else { // need to go through triangulation :(
// we get the indices in such an order that they form a triangle fan around idx[0].
// compute barycentric coordinates of our xy* point for all triangles in the fan:
let ex: f32 = uv.0 - SPECTRUM_DATA_POINTS[idx[0] as usize].uv.0;
let ey: f32 = uv.1 - SPECTRUM_DATA_POINTS[idx[0] as usize].uv.1;
let mut e0x: f32 = SPECTRUM_DATA_POINTS[idx[1] as usize].uv.0 - SPECTRUM_DATA_POINTS[idx[0] as usize].uv.0;
let mut e0y: f32 = SPECTRUM_DATA_POINTS[idx[1] as usize].uv.1 - SPECTRUM_DATA_POINTS[idx[0] as usize].uv.1;
let mut uu: f32 = e0x * ey - ex * e0y;
for i in 0..(num as usize - 1) {
let (e1x, e1y): (f32, f32) = if i as i32 == (num - 2) { // close the circle
(SPECTRUM_DATA_POINTS[idx[1] as usize].uv.0 - SPECTRUM_DATA_POINTS[idx[0] as usize].uv.0,
SPECTRUM_DATA_POINTS[idx[1] as usize].uv.1 - SPECTRUM_DATA_POINTS[idx[0] as usize].uv.1)
} else {
(SPECTRUM_DATA_POINTS[idx[i+2] as usize].uv.0 - SPECTRUM_DATA_POINTS[idx[0] as usize].uv.0,
SPECTRUM_DATA_POINTS[idx[i+2] as usize].uv.1 - SPECTRUM_DATA_POINTS[idx[0] as usize].uv.1)
};
let vv: f32 = ex * e1y - e1x * ey;
// TODO: with some sign magic, this division could be deferred to the last iteration!
let area: f32 = e0x * e1y - e1x * e0y;
// normalise
let u: f32 = uu / area;
let v: f32 = vv / area;
let w: f32 = 1.0 - u - v;
// outside spectral locus (quantized version at least) or outside grid
if u < 0.0 || v < 0.0 || w < 0.0 {
uu = -vv;
e0x = e1x;
e0y = e1y;
continue;
}
// This seems to be the triangle we've been looking for.
interpolated_p = p[0] * w + p[i + 1] * v + p[if i as i32 == (num - 2) { 1 } else { i + 2 }] * u;
break;
}
}
// now we have a spectrum which corresponds to the xy chromaticities of the input. need to scale according to the
// input brightness X+Y+Z now:
return interpolated_p / norm;
}
"""
# ------------------------------------------------------------------------------
# Color matching functions.
# Note: The load function assumes a CSV input, where each row
@ -906,7 +777,9 @@ def write_output(data_points, grid, grid_res, filename):
num_spec_samples = Cmf.num_bins()
spec_bin_size = Cmf.bin_size()
f.write(SPEC_FUNC)
f.write('// This file is auto-generated by generate_spectra_tables.py\n')
f.write('#![allow(dead_code)]\n')
f.write('#![cfg_attr(rustfmt, rustfmt_skip)]')
f.write('\n\n')
f.write('/// This is 1 over the integral over either CMF.\n')
f.write('/// Spectra can be mapped so that xyz=(1,1,1) is converted to constant 1 by\n')
@ -915,69 +788,49 @@ def write_output(data_points, grid, grid_res, filename):
f.write('\n\n')
f.write('// Basic info on the spectrum grid.\n')
f.write('const SPECTRUM_GRID_WIDTH: i32 = {};\n'.format(grid_res[0]))
f.write('const SPECTRUM_GRID_HEIGHT: i32 = {};\n'.format(grid_res[1]))
f.write('pub(crate) const SPECTRUM_GRID_WIDTH: i32 = {};\n'.format(grid_res[0]))
f.write('pub(crate) const SPECTRUM_GRID_HEIGHT: i32 = {};\n'.format(grid_res[1]))
f.write('\n')
f.write('// The spectra here have these properties.\n')
f.write('pub const SPECTRUM_SAMPLE_MIN: f32 = {};\n'.format(lambda_min))
f.write('pub const SPECTRUM_SAMPLE_MAX: f32 = {};\n'.format(lambda_max))
f.write('const SPECTRUM_BIN_SIZE: f32 = {};\n'.format(spec_bin_size))
f.write('const SPECTRUM_NUM_SAMPLES: i32 = {};\n'.format(num_spec_samples))
f.write('pub(crate) const SPECTRUM_BIN_SIZE: f32 = {};\n'.format(spec_bin_size))
f.write('pub(crate) const SPECTRUM_NUM_SAMPLES: i32 = {};\n'.format(num_spec_samples))
f.write('\n')
# Conversion routines xy->xystar and xy->uv and back.
f.write('// xy* color space.\n')
f.write('const SPECTRUM_MAT_XY_TO_XYSTAR: [f32; 6] = [\n')
f.write('pub(crate) const SPECTRUM_MAT_XY_TO_XYSTAR: [f32; 6] = [\n')
f.write(' {m[0]}, {m[1]}, {m[2]},\n {m[3]}, {m[4]}, {m[5]}\n'
.format(m=Transform.mat_xy_to_xystar[:2,:].flatten().tolist()[0]))
f.write('];\n')
f.write('const SPECTRUM_MAT_XYSTAR_TO_XY: [f32; 6] = [\n')
f.write('pub(crate) const SPECTRUM_MAT_XYSTAR_TO_XY: [f32; 6] = [\n')
f.write(' {m[0]}, {m[1]}, {m[2]},\n {m[3]}, {m[4]}, {m[5]}\n'
.format(m=Transform.mat_xystar_to_xy[:2,:].flatten().tolist()[0]))
f.write('];\n')
f.write('// uv color space.\n')
f.write('const SPECTRUM_MAT_XY_TO_UV: [f32; 6] = [\n')
f.write('pub(crate) const SPECTRUM_MAT_XY_TO_UV: [f32; 6] = [\n')
f.write(' {m[0]}, {m[1]}, {m[2]},\n {m[3]}, {m[4]}, {m[5]}\n'
.format(m=Transform.mat_xy_to_uv[:2,:].flatten().tolist()[0]))
f.write('];\n')
f.write('const SPECTRUM_MAT_UV_TO_XY: [f32; 6] = [\n')
f.write('pub(crate) const SPECTRUM_MAT_UV_TO_XY: [f32; 6] = [\n')
f.write(' {m[0]}, {m[1]}, {m[2]},\n {m[3]}, {m[4]}, {m[5]}\n'
.format(m=Transform.mat_uv_to_xy[:2,:].flatten().tolist()[0]))
f.write('];\n')
f.write('// apply a 3x2 matrix to a 2D color.\n')
f.write('fn spectrum_apply_3x2(matrix: &[f32; 6], src: (f32, f32)) -> (f32, f32) {\n')
f.write(' (matrix[0] * src.0 + matrix[1] * src.1 + matrix[2],\n')
f.write(' matrix[3] * src.0 + matrix[4] * src.1 + matrix[5])\n')
f.write('}\n')
f.write('// Concrete conversion routines.\n')
f.write('fn spectrum_xy_to_xystar(xy: (f32, f32)) -> (f32, f32) {\n')
f.write(' spectrum_apply_3x2(&SPECTRUM_MAT_XY_TO_XYSTAR, xy)\n')
f.write('}\n')
f.write('fn spectrum_xystar_to_xy(xystar: (f32, f32)) -> (f32, f32) {\n')
f.write(' spectrum_apply_3x2(&SPECTRUM_MAT_XYSTAR_TO_XY, xystar)\n')
f.write('}\n')
f.write('fn spectrum_xy_to_uv(xy: (f32, f32)) -> (f32, f32) {\n')
f.write(' spectrum_apply_3x2(&SPECTRUM_MAT_XY_TO_UV, xy)\n')
f.write('}\n')
f.write('fn spectrum_uv_to_xy(uv: (f32, f32)) -> (f32, f32) {\n')
f.write(' spectrum_apply_3x2(&SPECTRUM_MAT_UV_TO_XY, uv)\n')
f.write('}\n')
f.write('// Grid cells. Laid out in row-major format.\n')
f.write('// num_points = 0 for cells without data points.\n')
f.write('#[derive(Copy, Clone)]\n')
f.write('struct SpectrumGridCell {\n')
f.write(' inside: bool,\n')
f.write(' num_points: i32,\n')
f.write('pub(crate) struct SpectrumGridCell {\n')
f.write(' pub inside: bool,\n')
f.write(' pub num_points: i32,\n')
max_num_idx = 0
for c in grid:
if len(c.indices) > max_num_idx:
max_num_idx = len(c.indices)
f.write('\tidx: [i32; {}],\n'.format(max_num_idx))
f.write(' pub idx: [i32; {}],\n'.format(max_num_idx))
f.write('}\n\n')
# Count grid cells
@ -986,7 +839,7 @@ def write_output(data_points, grid, grid_res, filename):
grid_cell_count += 1
# Write grid cells
f.write('const SPECTRUM_GRID: [SpectrumGridCell; {}] = [\n'.format(grid_cell_count))
f.write('pub(crate) const SPECTRUM_GRID: [SpectrumGridCell; {}] = [\n'.format(grid_cell_count))
cell_strings = []
for (x, y) in [(x,y) for y in range(grid_res[1]) for x in range(grid_res[0])]:
cell = grid[y * grid_res[0] + x]
@ -1006,18 +859,12 @@ def write_output(data_points, grid, grid_res, filename):
f.write('\n];\n\n')
f.write('// Grid data points.\n')
f.write('#[derive(Copy)]\n')
f.write('struct SpectrumDataPoint {\n')
f.write(' xystar: (f32, f32),\n')
f.write(' uv: (f32, f32),\n')
f.write(' spectrum: [f32; {}], // X+Y+Z = 1\n'.format(num_spec_samples))
f.write('#[derive(Copy, Clone)]\n')
f.write('pub(crate) struct SpectrumDataPoint {\n')
f.write(' pub xystar: (f32, f32),\n')
f.write(' pub uv: (f32, f32),\n')
f.write(' pub spectrum: [f32; {}], // X+Y+Z = 1\n'.format(num_spec_samples))
f.write('}\n\n')
f.write("impl Clone for SpectrumDataPoint {\n"
" fn clone(&self) -> SpectrumDataPoint {\n"
" *self\n"
" }\n"
"}\n\n"
)
data_point_strings = []
data_point_count = 0
for p in data_points:
@ -1030,36 +877,24 @@ def write_output(data_points, grid, grid_res, filename):
" spectrum: [{spec}],\n"
" }}".format(p=p, spec=spec_str)
)
f.write('const SPECTRUM_DATA_POINTS: [SpectrumDataPoint; {}] = [\n'.format(data_point_count))
f.write('pub(crate) const SPECTRUM_DATA_POINTS: [SpectrumDataPoint; {}] = [\n'.format(data_point_count))
f.write(',\n'.join(data_point_strings))
f.write('\n];\n\n')
f.write('// Color matching functions.\n')
f.write('const CMF_WAVELENGTH: [f32; {}] = [\n'.format(len(Cmf.wavelength())))
f.write('pub(crate) const CMF_WAVELENGTH: [f32; {}] = [\n'.format(len(Cmf.wavelength())))
f.write(' {}\n'.format(', '.join(str(v) for v in Cmf.wavelength())))
f.write('];\n')
f.write('const CMF_X: [f32; {}] = [\n'.format(len(Cmf.x_bar())))
f.write('pub(crate) const CMF_X: [f32; {}] = [\n'.format(len(Cmf.x_bar())))
f.write(' {}\n'.format(', '.join(str(v) for v in Cmf.x_bar())))
f.write('];\n')
f.write('const CMF_Y: [f32; {}] = [\n'.format(len(Cmf.y_bar())))
f.write('pub(crate) const CMF_Y: [f32; {}] = [\n'.format(len(Cmf.y_bar())))
f.write(' {}\n'.format(', '.join(str(v) for v in Cmf.y_bar())))
f.write('];\n')
f.write('const CMF_Z: [f32; {}] = [\n'.format(len(Cmf.z_bar())))
f.write('pub(crate) const CMF_Z: [f32; {}] = [\n'.format(len(Cmf.z_bar())))
f.write(' {}\n'.format(', '.join(str(v) for v in Cmf.z_bar())))
f.write('];\n\n')
f.write('fn xyz_from_spectrum(spectrum: &[f32]) -> (f32, f32, f32) {\n')
f.write(' let mut xyz = (0.0, 0.0, 0.0);\n')
f.write(' for i in 0..(SPECTRUM_NUM_SAMPLES as usize) {\n')
f.write(' xyz.0 += spectrum[i] * CMF_X[i];\n')
f.write(' xyz.1 += spectrum[i] * CMF_Y[i];\n')
f.write(' xyz.2 += spectrum[i] * CMF_Z[i];\n')
f.write(' }\n')
f.write(' xyz.0 *= SPECTRUM_BIN_SIZE;\n')
f.write(' xyz.1 *= SPECTRUM_BIN_SIZE;\n')
f.write(' xyz.2 *= SPECTRUM_BIN_SIZE;\n')
f.write(' return xyz;\n')
f.write('}\n\n')
print(' ... done')
@ -1230,7 +1065,7 @@ if __name__ == "__main__":
compute_spectra(data_points)
write_output(data_points, grid, grid_res,
#'spectra_{}_{}.rs'.format(os.path.splitext(args.cmf)[0], args.scale))
'spectra_xyz.rs')
'spectra_tables.rs')
# Finally, plot all spectra.
if args.plot:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff