use crate::types::{BitPackedTensor, Tensor, VsfType, WorldCoord};
use crate::vsf_builder::VsfBuilder;
#[derive(Debug, Clone)]
pub struct CfaPattern(VsfType);
impl CfaPattern {
pub fn new(pattern: Vec<u8>) -> Result<Self, String> {
if pattern.is_empty() {
return Err("CFA pattern cannot be empty".to_string());
}
for &byte in &pattern {
match byte {
b'R' | b'G' | b'B' | b'C' | b'Y' | b'W' | b'E' => {}
_ => return Err(format!("Invalid CFA colour code: {}", byte as char)),
}
}
Ok(CfaPattern(VsfType::t_u3(Tensor {
shape: vec![pattern.len()],
data: pattern,
})))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::t_u3(ref tensor) => {
for &byte in &tensor.data {
match byte {
b'R' | b'G' | b'B' | b'C' | b'Y' | b'W' | b'E' => {}
_ => return Err(format!("Invalid CFA colour code: {}", byte as char)),
}
}
Ok(CfaPattern(vsf))
}
_ => Err("Expected t_u3 type for CFA pattern".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct BlackLevel(VsfType);
impl BlackLevel {
pub fn new(level: f32) -> Result<Self, String> {
if level < 0.0 {
return Err("Black level cannot be negative".to_string());
}
Ok(BlackLevel(VsfType::f5(level)))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::f5(v) if v >= 0.0 => Ok(BlackLevel(vsf)),
VsfType::f5(_) => Err("Black level cannot be negative".to_string()),
_ => Err("Expected f5 type for black level".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct WhiteLevel(VsfType);
impl WhiteLevel {
pub fn new(level: f32) -> Result<Self, String> {
if level <= 0.0 {
return Err("White level must be positive".to_string());
}
Ok(WhiteLevel(VsfType::f5(level)))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::f5(v) if v > 0.0 => Ok(WhiteLevel(vsf)),
VsfType::f5(_) => Err("White level must be positive".to_string()),
_ => Err("Expected f5 type for white level".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct CalibrationHash(VsfType);
impl CalibrationHash {
pub fn new(algorithm: u8, hash: Vec<u8>) -> Result<Self, String> {
use crate::crypto_algorithms::{HASH_BLAKE3, HASH_SHA256, HASH_SHA512};
if hash.is_empty() {
return Err("Hash cannot be empty".to_string());
}
let vsf_type = match algorithm {
HASH_BLAKE3 => VsfType::hb(hash),
HASH_SHA256 | HASH_SHA512 => VsfType::hs(hash),
_ => return Err(format!("Unsupported hash algorithm: {}", algorithm as char)),
};
Ok(CalibrationHash(vsf_type))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::hb(ref hash) | VsfType::hs(ref hash) if !hash.is_empty() => {
Ok(CalibrationHash(vsf))
}
VsfType::hb(_) | VsfType::hs(_) => Err("Hash cannot be empty".to_string()),
_ => Err("Expected hash type for calibration hash".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct Magic9(VsfType);
impl Magic9 {
pub fn new(values: Vec<f32>) -> Result<Self, String> {
if values.len() != 9 {
return Err(format!(
"Magic 9 matrix must have exactly 9 elements, got {}",
values.len()
));
}
Ok(Magic9(VsfType::t_f5(Tensor {
shape: vec![3, 3],
data: values,
})))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::t_f5(ref tensor) => {
if tensor.data.len() != 9 {
return Err(format!(
"Magic 9 matrix must have exactly 9 elements, got {}",
tensor.data.len()
));
}
Ok(Magic9(vsf))
}
_ => Err("Expected t_f5 type for Magic 9 matrix".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct IsoSpeed(VsfType);
impl IsoSpeed {
pub fn new(iso: f32) -> Result<Self, String> {
if iso <= 0.0 {
return Err("ISO speed must be positive".to_string());
}
Ok(IsoSpeed(VsfType::f5(iso)))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::f5(v) if v > 0.0 => Ok(IsoSpeed(vsf)),
VsfType::f5(_) => Err("ISO speed must be positive".to_string()),
_ => Err("Expected f5 type for ISO speed".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct ShutterTime(VsfType);
impl ShutterTime {
pub fn new(seconds: f32) -> Result<Self, String> {
if seconds <= 0.0 {
return Err("Shutter time must be positive".to_string());
}
Ok(ShutterTime(VsfType::f5(seconds)))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::f5(v) if v > 0.0 => Ok(ShutterTime(vsf)),
VsfType::f5(_) => Err("Shutter time must be positive".to_string()),
_ => Err("Expected f5 type for shutter time".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct Aperture(VsfType);
impl Aperture {
pub fn new(f_number: f32) -> Result<Self, String> {
if f_number <= 0.0 {
return Err("Aperture f-number must be positive".to_string());
}
Ok(Aperture(VsfType::f5(f_number)))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::f5(v) if v > 0.0 => Ok(Aperture(vsf)),
VsfType::f5(_) => Err("Aperture f-number must be positive".to_string()),
_ => Err("Expected f5 type for aperture".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct FocalLength(VsfType);
impl FocalLength {
pub fn new(meters: f32) -> Result<Self, String> {
if meters <= 0.0 {
return Err("Focal length must be positive".to_string());
}
Ok(FocalLength(VsfType::f5(meters)))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::f5(v) if v > 0.0 => Ok(FocalLength(vsf)),
VsfType::f5(_) => Err("Focal length must be positive".to_string()),
_ => Err("Expected f5 type for focal length".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct ExposureCompensation(VsfType);
impl ExposureCompensation {
pub fn new(ev: f32) -> Result<Self, String> {
Ok(ExposureCompensation(VsfType::f5(ev))) }
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::f5(_) => Ok(ExposureCompensation(vsf)),
_ => Err("Expected f5 type for exposure compensation".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct FocusDistance(VsfType);
impl FocusDistance {
pub fn new(meters: f32) -> Result<Self, String> {
if meters < 0.0 {
return Err("Focus distance cannot be negative".to_string());
}
Ok(FocusDistance(VsfType::f5(meters)))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::f5(v) if v >= 0.0 => Ok(FocusDistance(vsf)),
VsfType::f5(_) => Err("Focus distance cannot be negative".to_string()),
_ => Err("Expected f5 type for focus distance".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct FlashFired(VsfType);
impl FlashFired {
pub fn new(fired: bool) -> Result<Self, String> {
Ok(FlashFired(VsfType::u0(fired)))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::u0(_) => Ok(FlashFired(vsf)),
_ => Err("Expected u0 type for flash fired".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct MeteringMode(VsfType);
impl MeteringMode {
pub fn new(mode: String) -> Result<Self, String> {
if mode.is_empty() {
return Err("Metering mode cannot be empty".to_string());
}
Ok(MeteringMode(VsfType::x(mode)))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::x(ref s) if !s.is_empty() => Ok(MeteringMode(vsf)),
VsfType::x(_) => Err("Metering mode cannot be empty".to_string()),
_ => Err("Expected x type for metering mode".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct Manufacturer(VsfType);
impl Manufacturer {
pub fn new(name: String) -> Result<Self, String> {
if name.is_empty() {
return Err("Manufacturer name cannot be empty".to_string());
}
Ok(Manufacturer(VsfType::x(name)))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::x(ref s) if !s.is_empty() => Ok(Manufacturer(vsf)),
VsfType::x(_) => Err("Manufacturer name cannot be empty".to_string()),
_ => Err("Expected x type for manufacturer".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct ModelName(VsfType);
impl ModelName {
pub fn new(name: String) -> Result<Self, String> {
if name.is_empty() {
return Err("Model name cannot be empty".to_string());
}
Ok(ModelName(VsfType::x(name)))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::x(ref s) if !s.is_empty() => Ok(ModelName(vsf)),
VsfType::x(_) => Err("Model name cannot be empty".to_string()),
_ => Err("Expected x type for model name".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct SerialNumber(VsfType);
impl SerialNumber {
pub fn new(serial: String) -> Result<Self, String> {
if serial.is_empty() {
return Err("Serial number cannot be empty".to_string());
}
Ok(SerialNumber(VsfType::x(serial)))
}
pub fn to_vsf_type(self) -> VsfType {
self.0
}
pub fn from_vsf_type(vsf: VsfType) -> Result<Self, String> {
match vsf {
VsfType::x(ref s) if !s.is_empty() => Ok(SerialNumber(vsf)),
VsfType::x(_) => Err("Serial number cannot be empty".to_string()),
_ => Err("Expected x type for serial number".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct RawMetadata {
pub cfa_pattern: Option<CfaPattern>,
pub black_level: Option<BlackLevel>,
pub white_level: Option<WhiteLevel>,
pub dark_frame_hash: Option<CalibrationHash>,
pub flat_field_hash: Option<CalibrationHash>,
pub bias_frame_hash: Option<CalibrationHash>,
pub vignette_correction_hash: Option<CalibrationHash>,
pub distortion_correction_hash: Option<CalibrationHash>,
pub magic_9: Option<Magic9>,
}
#[derive(Debug, Clone)]
pub struct CameraSettings {
pub make: Option<Manufacturer>,
pub model: Option<ModelName>,
pub serial_number: Option<SerialNumber>,
pub iso_speed: Option<IsoSpeed>,
pub shutter_time_s: Option<ShutterTime>,
pub aperture_f_number: Option<Aperture>,
pub focal_length_m: Option<FocalLength>,
pub exposure_compensation: Option<ExposureCompensation>,
pub focus_distance_m: Option<FocusDistance>,
pub flash_fired: Option<FlashFired>,
pub metering_mode: Option<MeteringMode>,
}
#[derive(Debug, Clone)]
pub struct LensInfo {
pub make: Option<Manufacturer>,
pub model: Option<ModelName>,
pub serial_number: Option<SerialNumber>,
pub min_focal_length_m: Option<FocalLength>,
pub max_focal_length_m: Option<FocalLength>,
pub min_aperture_f: Option<Aperture>, pub max_aperture_f: Option<Aperture>, }
#[derive(Debug, Clone, Default)]
pub struct RawMetadataBuilder {
pub cfa_pattern: Option<Vec<u8>>,
pub black_level: Option<f32>,
pub white_level: Option<f32>,
pub dark_frame_hash: Option<(u8, Vec<u8>)>,
pub flat_field_hash: Option<(u8, Vec<u8>)>,
pub bias_frame_hash: Option<(u8, Vec<u8>)>,
pub vignette_correction_hash: Option<(u8, Vec<u8>)>,
pub distortion_correction_hash: Option<(u8, Vec<u8>)>,
pub magic_9: Option<Vec<f32>>,
}
impl RawMetadataBuilder {
fn build(self) -> Result<Option<RawMetadata>, String> {
if self.cfa_pattern.is_none()
&& self.black_level.is_none()
&& self.white_level.is_none()
&& self.dark_frame_hash.is_none()
&& self.flat_field_hash.is_none()
&& self.bias_frame_hash.is_none()
&& self.vignette_correction_hash.is_none()
&& self.distortion_correction_hash.is_none()
&& self.magic_9.is_none()
{
return Ok(None);
}
Ok(Some(RawMetadata {
cfa_pattern: self.cfa_pattern.map(CfaPattern::new).transpose()?,
black_level: self.black_level.map(BlackLevel::new).transpose()?,
white_level: self.white_level.map(WhiteLevel::new).transpose()?,
dark_frame_hash: self
.dark_frame_hash
.map(|(alg, hash)| CalibrationHash::new(alg, hash))
.transpose()?,
flat_field_hash: self
.flat_field_hash
.map(|(alg, hash)| CalibrationHash::new(alg, hash))
.transpose()?,
bias_frame_hash: self
.bias_frame_hash
.map(|(alg, hash)| CalibrationHash::new(alg, hash))
.transpose()?,
vignette_correction_hash: self
.vignette_correction_hash
.map(|(alg, hash)| CalibrationHash::new(alg, hash))
.transpose()?,
distortion_correction_hash: self
.distortion_correction_hash
.map(|(alg, hash)| CalibrationHash::new(alg, hash))
.transpose()?,
magic_9: self.magic_9.map(Magic9::new).transpose()?,
}))
}
}
#[derive(Debug, Clone, Default)]
pub struct CameraBuilder {
pub make: Option<String>,
pub model: Option<String>,
pub serial_number: Option<String>,
pub iso_speed: Option<f32>,
pub shutter_time_s: Option<f32>,
pub aperture_f_number: Option<f32>,
pub focal_length_m: Option<f32>,
pub exposure_compensation: Option<f32>,
pub focus_distance_m: Option<f32>,
pub flash_fired: Option<bool>,
pub metering_mode: Option<String>,
}
impl CameraBuilder {
fn build(self) -> Result<Option<CameraSettings>, String> {
if self.make.is_none()
&& self.model.is_none()
&& self.serial_number.is_none()
&& self.iso_speed.is_none()
&& self.shutter_time_s.is_none()
&& self.aperture_f_number.is_none()
&& self.focal_length_m.is_none()
&& self.exposure_compensation.is_none()
&& self.focus_distance_m.is_none()
&& self.flash_fired.is_none()
&& self.metering_mode.is_none()
{
return Ok(None);
}
Ok(Some(CameraSettings {
make: self.make.map(Manufacturer::new).transpose()?,
model: self.model.map(ModelName::new).transpose()?,
serial_number: self.serial_number.map(SerialNumber::new).transpose()?,
iso_speed: self.iso_speed.map(IsoSpeed::new).transpose()?,
shutter_time_s: self.shutter_time_s.map(ShutterTime::new).transpose()?,
aperture_f_number: self.aperture_f_number.map(Aperture::new).transpose()?,
focal_length_m: self.focal_length_m.map(FocalLength::new).transpose()?,
exposure_compensation: self
.exposure_compensation
.map(ExposureCompensation::new)
.transpose()?,
focus_distance_m: self.focus_distance_m.map(FocusDistance::new).transpose()?,
flash_fired: self.flash_fired.map(FlashFired::new).transpose()?,
metering_mode: self.metering_mode.map(MeteringMode::new).transpose()?,
}))
}
}
#[derive(Debug, Clone, Default)]
pub struct LensBuilder {
pub make: Option<String>,
pub model: Option<String>,
pub serial_number: Option<String>,
pub min_focal_length_m: Option<f32>,
pub max_focal_length_m: Option<f32>,
pub min_aperture_f: Option<f32>,
pub max_aperture_f: Option<f32>,
}
impl LensBuilder {
fn build(self) -> Result<Option<LensInfo>, String> {
if self.make.is_none()
&& self.model.is_none()
&& self.serial_number.is_none()
&& self.min_focal_length_m.is_none()
&& self.max_focal_length_m.is_none()
&& self.min_aperture_f.is_none()
&& self.max_aperture_f.is_none()
{
return Ok(None);
}
Ok(Some(LensInfo {
make: self.make.map(Manufacturer::new).transpose()?,
model: self.model.map(ModelName::new).transpose()?,
serial_number: self.serial_number.map(SerialNumber::new).transpose()?,
min_focal_length_m: self.min_focal_length_m.map(FocalLength::new).transpose()?,
max_focal_length_m: self.max_focal_length_m.map(FocalLength::new).transpose()?,
min_aperture_f: self.min_aperture_f.map(Aperture::new).transpose()?,
max_aperture_f: self.max_aperture_f.map(Aperture::new).transpose()?,
}))
}
}
#[derive(Debug, Clone)]
pub struct RawImageBuilder {
image: BitPackedTensor,
pub raw: RawMetadataBuilder,
pub camera: CameraBuilder,
pub lens: LensBuilder,
}
impl RawImageBuilder {
pub fn new(image: BitPackedTensor) -> Self {
Self {
image,
raw: RawMetadataBuilder::default(),
camera: CameraBuilder::default(),
lens: LensBuilder::default(),
}
}
pub fn build(self) -> Result<Vec<u8>, String> {
let metadata = self.raw.build()?;
let camera = self.camera.build()?;
let lens = self.lens.build()?;
build_raw_image(self.image, metadata, camera, lens)
}
}
pub fn raw_image(bit_depth: u8, width: usize, height: usize, samples: Vec<u64>) -> VsfType {
let tensor = BitPackedTensor::pack(bit_depth, vec![width, height], &samples);
VsfType::p(tensor)
}
pub fn gps_track(coords: Vec<(f64, f64)>) -> Vec<WorldCoord> {
coords
.into_iter()
.map(|(lat, lon)| WorldCoord::from_lat_lon(lat, lon))
.collect()
}
pub fn gps_waypoint(lat: f64, lon: f64) -> WorldCoord {
WorldCoord::from_lat_lon(lat, lon)
}
pub fn geotagged_photo(
width: usize,
height: usize,
rgb_data: Vec<u8>,
lat: f64,
lon: f64,
) -> (VsfType, WorldCoord) {
let tensor = Tensor::new(vec![width, height, 3], rgb_data);
let img = VsfType::t_u3(tensor);
let loc = WorldCoord::from_lat_lon(lat, lon);
(img, loc)
}
pub fn build_raw_image(
image: BitPackedTensor,
metadata: Option<RawMetadata>,
camera: Option<CameraSettings>,
lens: Option<LensInfo>,
) -> Result<Vec<u8>, String> {
let mut builder = VsfBuilder::new();
let mut raw_items = vec![("image".to_string(), VsfType::p(image))];
if let Some(meta) = metadata {
if let Some(cfa) = meta.cfa_pattern {
raw_items.push(("cfa_pattern".to_string(), cfa.to_vsf_type()));
}
if let Some(black) = meta.black_level {
raw_items.push(("black_level".to_string(), black.to_vsf_type()));
}
if let Some(white) = meta.white_level {
raw_items.push(("white_level".to_string(), white.to_vsf_type()));
}
if let Some(hash) = meta.dark_frame_hash {
raw_items.push(("dark_frame_hash".to_string(), hash.to_vsf_type()));
}
if let Some(hash) = meta.flat_field_hash {
raw_items.push(("flat_field_hash".to_string(), hash.to_vsf_type()));
}
if let Some(hash) = meta.bias_frame_hash {
raw_items.push(("bias_frame_hash".to_string(), hash.to_vsf_type()));
}
if let Some(hash) = meta.vignette_correction_hash {
raw_items.push(("vignette_correction_hash".to_string(), hash.to_vsf_type()));
}
if let Some(hash) = meta.distortion_correction_hash {
raw_items.push(("distortion_correction_hash".to_string(), hash.to_vsf_type()));
}
if let Some(matrix) = meta.magic_9 {
raw_items.push(("magic_9".to_string(), matrix.to_vsf_type()));
}
}
if let Some(cam) = camera {
if let Some(make) = cam.make {
raw_items.push(("camera_make".to_string(), make.to_vsf_type()));
}
if let Some(model) = cam.model {
raw_items.push(("camera_model".to_string(), model.to_vsf_type()));
}
if let Some(serial) = cam.serial_number {
raw_items.push(("camera_serial".to_string(), serial.to_vsf_type()));
}
if let Some(iso) = cam.iso_speed {
raw_items.push(("iso_speed".to_string(), iso.to_vsf_type()));
}
if let Some(shutter) = cam.shutter_time_s {
raw_items.push(("shutter_time_s".to_string(), shutter.to_vsf_type()));
}
if let Some(aperture) = cam.aperture_f_number {
raw_items.push(("aperture_f_number".to_string(), aperture.to_vsf_type()));
}
if let Some(focal) = cam.focal_length_m {
raw_items.push(("focal_length_m".to_string(), focal.to_vsf_type()));
}
if let Some(comp) = cam.exposure_compensation {
raw_items.push(("exposure_compensation".to_string(), comp.to_vsf_type()));
}
if let Some(focus) = cam.focus_distance_m {
raw_items.push(("focus_distance_m".to_string(), focus.to_vsf_type()));
}
if let Some(flash) = cam.flash_fired {
raw_items.push(("flash_fired".to_string(), flash.to_vsf_type()));
}
if let Some(metering) = cam.metering_mode {
raw_items.push(("metering_mode".to_string(), metering.to_vsf_type()));
}
}
if let Some(l) = lens {
if let Some(make) = l.make {
raw_items.push(("lens_make".to_string(), make.to_vsf_type()));
}
if let Some(model) = l.model {
raw_items.push(("lens_model".to_string(), model.to_vsf_type()));
}
if let Some(serial) = l.serial_number {
raw_items.push(("lens_serial".to_string(), serial.to_vsf_type()));
}
if let Some(min_focal) = l.min_focal_length_m {
raw_items.push(("lens_min_focal_m".to_string(), min_focal.to_vsf_type()));
}
if let Some(max_focal) = l.max_focal_length_m {
raw_items.push(("lens_max_focal_m".to_string(), max_focal.to_vsf_type()));
}
if let Some(min_ap) = l.min_aperture_f {
raw_items.push(("lens_min_aperture".to_string(), min_ap.to_vsf_type()));
}
if let Some(max_ap) = l.max_aperture_f {
raw_items.push(("lens_max_aperture".to_string(), max_ap.to_vsf_type()));
}
}
builder = builder.add_section("raw", raw_items);
builder.build()
}
pub fn lumis_raw_capture(samples: Vec<u64>, iso: f32, shutter_s: f32) -> Result<Vec<u8>, String> {
let image = BitPackedTensor::pack(12, vec![4096, 3072], &samples);
build_raw_image(
image,
Some(RawMetadata {
cfa_pattern: Some(CfaPattern::new(vec![b'R', b'G', b'G', b'B'])?), black_level: Some(BlackLevel::new(64.0)?),
white_level: Some(WhiteLevel::new(4095.0)?),
dark_frame_hash: None,
flat_field_hash: None,
bias_frame_hash: None,
vignette_correction_hash: None,
distortion_correction_hash: None,
magic_9: None,
}),
Some(CameraSettings {
make: None,
model: None,
serial_number: None,
iso_speed: Some(IsoSpeed::new(iso)?),
shutter_time_s: Some(ShutterTime::new(shutter_s)?),
aperture_f_number: None,
focal_length_m: None,
exposure_compensation: None,
focus_distance_m: None,
flash_fired: Some(FlashFired::new(false)?),
metering_mode: None,
}),
None, )
}
pub const ENCODING_AV1: u8 = b'a';
pub fn compressed_image(av1_data: Vec<u8>) -> Result<Vec<u8>, String> {
VsfBuilder::new()
.provenance_only()
.add_section(
"image",
vec![("pixels".to_string(), VsfType::v(ENCODING_AV1, av1_data))],
)
.build()
}
pub struct ParsedCompressedImage {
pub encoding: u8,
pub data: Vec<u8>,
}
pub fn parse_compressed_image(data: &[u8]) -> Result<ParsedCompressedImage, String> {
use crate::file_format::{VsfHeader, VsfSection};
let (header, _) = VsfHeader::decode(data)?;
let image_field = header
.fields
.iter()
.find(|f| f.name == "image")
.ok_or("Required 'image' section not found")?;
let mut ptr = image_field.offset_bytes;
let section = VsfSection::parse(data, &mut ptr)?;
let pixels_field = section
.get_field("pixels")
.ok_or("Missing 'pixels' field in image section")?;
let value = pixels_field.values.first().ok_or("Empty 'pixels' field")?;
match value {
VsfType::v(encoding, pixel_data) => Ok(ParsedCompressedImage {
encoding: *encoding,
data: pixel_data.clone(),
}),
_ => Err("Expected v type for pixels field".to_string()),
}
}
pub struct ParsedRawImage {
pub image: BitPackedTensor,
pub metadata: Option<RawMetadata>,
pub camera: Option<CameraSettings>,
pub lens: Option<LensInfo>,
}
fn to_usize(vsf_type: &VsfType) -> Option<usize> {
match vsf_type {
VsfType::u(v, _) => Some(*v), VsfType::u0(b) => Some(*b as usize), VsfType::u3(v) => Some(*v as usize), VsfType::u4(v) => Some(*v as usize), VsfType::u5(v) => Some(*v as usize), VsfType::u6(v) => Some(*v as usize), VsfType::u7(v) => Some(*v as usize), _ => None,
}
}
pub fn parse_raw_image(data: &[u8]) -> Result<ParsedRawImage, String> {
use crate::crypto_algorithms::{HASH_BLAKE3, HASH_SHA256, HASH_SHA512};
use crate::file_format::{VsfHeader, VsfSection};
let (header, _) = VsfHeader::decode(data)?;
let raw_field = header
.fields
.iter()
.find(|f| f.name == "raw")
.ok_or("Required 'raw' section not found")?;
let mut ptr = raw_field.offset_bytes;
let section = VsfSection::parse(data, &mut ptr)?;
fn get_first_value<'a>(section: &'a VsfSection, name: &str) -> Option<&'a VsfType> {
section.get_field(name)?.values.first()
}
let image = match get_first_value(§ion, "image") {
Some(VsfType::p(tensor)) => tensor.clone(),
_ => return Err("Missing required 'image' field".to_string()),
};
let mut cfa_pattern: Option<Vec<u8>> = None;
let mut black_level: Option<f32> = None;
let mut white_level: Option<f32> = None;
let mut dark_frame_hash: Option<(u8, Vec<u8>)> = None;
let mut flat_field_hash: Option<(u8, Vec<u8>)> = None;
let mut bias_frame_hash: Option<(u8, Vec<u8>)> = None;
let mut vignette_correction_hash: Option<(u8, Vec<u8>)> = None;
let mut distortion_correction_hash: Option<(u8, Vec<u8>)> = None;
let mut magic_9: Option<Vec<f32>> = None;
let mut camera_make: Option<String> = None;
let mut camera_model: Option<String> = None;
let mut camera_serial: Option<String> = None;
let mut iso_speed: Option<f32> = None;
let mut shutter_time_s: Option<f32> = None;
let mut aperture_f_number: Option<f32> = None;
let mut focal_length_m: Option<f32> = None;
let mut exposure_compensation: Option<f32> = None;
let mut focus_distance_m: Option<f32> = None;
let mut flash_fired: Option<bool> = None;
let mut metering_mode: Option<String> = None;
let mut lens_make: Option<String> = None;
let mut lens_model: Option<String> = None;
let mut lens_serial: Option<String> = None;
let mut lens_min_focal_m: Option<f32> = None;
let mut lens_max_focal_m: Option<f32> = None;
let mut lens_min_aperture: Option<f32> = None;
let mut lens_max_aperture: Option<f32> = None;
fn parse_hash(value: &VsfType) -> Option<(u8, Vec<u8>)> {
match value {
VsfType::hb(v) => Some((HASH_BLAKE3, v.clone())),
VsfType::hs(v) => {
let algo = if v.len() == 32 {
HASH_SHA256
} else {
HASH_SHA512
};
Some((algo, v.clone()))
}
_ => None,
}
}
for field in §ion.fields {
let value = match field.values.first() {
Some(v) => v,
None => continue,
};
match field.name.as_str() {
"cfa_pattern" => {
if let VsfType::t_u3(tensor) = value {
cfa_pattern = Some(tensor.data.clone());
}
}
"black_level" => {
if let VsfType::f5(v) = value {
black_level = Some(*v);
}
}
"white_level" => {
if let VsfType::f5(v) = value {
white_level = Some(*v);
}
}
"dark_frame_hash" => dark_frame_hash = parse_hash(value),
"flat_field_hash" => flat_field_hash = parse_hash(value),
"bias_frame_hash" => bias_frame_hash = parse_hash(value),
"vignette_correction_hash" => vignette_correction_hash = parse_hash(value),
"distortion_correction_hash" => distortion_correction_hash = parse_hash(value),
"magic_9" => {
if let VsfType::t_f5(tensor) = value {
magic_9 = Some(tensor.data.clone());
}
}
"camera_make" => {
if let VsfType::x(v) = value {
camera_make = Some(v.clone());
}
}
"camera_model" => {
if let VsfType::x(v) = value {
camera_model = Some(v.clone());
}
}
"camera_serial" => {
if let VsfType::x(v) = value {
camera_serial = Some(v.clone());
}
}
"iso_speed" => {
if let VsfType::f5(v) = value {
iso_speed = Some(*v);
}
}
"shutter_time_s" => {
if let VsfType::f5(v) = value {
shutter_time_s = Some(*v);
}
}
"aperture_f_number" => {
if let VsfType::f5(v) = value {
aperture_f_number = Some(*v);
}
}
"focal_length_m" => {
if let VsfType::f5(v) = value {
focal_length_m = Some(*v);
}
}
"exposure_compensation" => {
if let VsfType::f5(v) = value {
exposure_compensation = Some(*v);
}
}
"focus_distance_m" => {
if let VsfType::f5(v) = value {
focus_distance_m = Some(*v);
}
}
"flash_fired" => flash_fired = to_usize(value).map(|v| v != 0),
"metering_mode" => {
if let VsfType::x(v) = value {
metering_mode = Some(v.clone());
}
}
"lens_make" => {
if let VsfType::x(v) = value {
lens_make = Some(v.clone());
}
}
"lens_model" => {
if let VsfType::x(v) = value {
lens_model = Some(v.clone());
}
}
"lens_serial" => {
if let VsfType::x(v) = value {
lens_serial = Some(v.clone());
}
}
"lens_min_focal_m" => {
if let VsfType::f5(v) = value {
lens_min_focal_m = Some(*v);
}
}
"lens_max_focal_m" => {
if let VsfType::f5(v) = value {
lens_max_focal_m = Some(*v);
}
}
"lens_min_aperture" => {
if let VsfType::f5(v) = value {
lens_min_aperture = Some(*v);
}
}
"lens_max_aperture" => {
if let VsfType::f5(v) = value {
lens_max_aperture = Some(*v);
}
}
_ => {} }
}
let raw_metadata = if cfa_pattern.is_some()
|| black_level.is_some()
|| white_level.is_some()
|| dark_frame_hash.is_some()
|| flat_field_hash.is_some()
|| bias_frame_hash.is_some()
|| vignette_correction_hash.is_some()
|| distortion_correction_hash.is_some()
|| magic_9.is_some()
{
Some(RawMetadata {
cfa_pattern: cfa_pattern.map(CfaPattern::new).transpose()?,
black_level: black_level.map(BlackLevel::new).transpose()?,
white_level: white_level.map(WhiteLevel::new).transpose()?,
dark_frame_hash: dark_frame_hash
.map(|(alg, hash)| CalibrationHash::new(alg, hash))
.transpose()?,
flat_field_hash: flat_field_hash
.map(|(alg, hash)| CalibrationHash::new(alg, hash))
.transpose()?,
bias_frame_hash: bias_frame_hash
.map(|(alg, hash)| CalibrationHash::new(alg, hash))
.transpose()?,
vignette_correction_hash: vignette_correction_hash
.map(|(alg, hash)| CalibrationHash::new(alg, hash))
.transpose()?,
distortion_correction_hash: distortion_correction_hash
.map(|(alg, hash)| CalibrationHash::new(alg, hash))
.transpose()?,
magic_9: magic_9.map(Magic9::new).transpose()?,
})
} else {
None
};
let camera_settings = if camera_make.is_some()
|| camera_model.is_some()
|| camera_serial.is_some()
|| iso_speed.is_some()
|| shutter_time_s.is_some()
|| aperture_f_number.is_some()
|| focal_length_m.is_some()
|| exposure_compensation.is_some()
|| focus_distance_m.is_some()
|| flash_fired.is_some()
|| metering_mode.is_some()
{
Some(CameraSettings {
make: camera_make.map(Manufacturer::new).transpose()?,
model: camera_model.map(ModelName::new).transpose()?,
serial_number: camera_serial.map(SerialNumber::new).transpose()?,
iso_speed: iso_speed.map(IsoSpeed::new).transpose()?,
shutter_time_s: shutter_time_s.map(ShutterTime::new).transpose()?,
aperture_f_number: aperture_f_number.map(Aperture::new).transpose()?,
focal_length_m: focal_length_m.map(FocalLength::new).transpose()?,
exposure_compensation: exposure_compensation
.map(ExposureCompensation::new)
.transpose()?,
focus_distance_m: focus_distance_m.map(FocusDistance::new).transpose()?,
flash_fired: flash_fired.map(FlashFired::new).transpose()?,
metering_mode: metering_mode.map(MeteringMode::new).transpose()?,
})
} else {
None
};
let lens_info = if lens_make.is_some()
|| lens_model.is_some()
|| lens_serial.is_some()
|| lens_min_focal_m.is_some()
|| lens_max_focal_m.is_some()
|| lens_min_aperture.is_some()
|| lens_max_aperture.is_some()
{
Some(LensInfo {
make: lens_make.map(Manufacturer::new).transpose()?,
model: lens_model.map(ModelName::new).transpose()?,
serial_number: lens_serial.map(SerialNumber::new).transpose()?,
min_focal_length_m: lens_min_focal_m.map(FocalLength::new).transpose()?,
max_focal_length_m: lens_max_focal_m.map(FocalLength::new).transpose()?,
min_aperture_f: lens_min_aperture.map(Aperture::new).transpose()?,
max_aperture_f: lens_max_aperture.map(Aperture::new).transpose()?,
})
} else {
None
};
Ok(ParsedRawImage {
image,
metadata: raw_metadata,
camera: camera_settings,
lens: lens_info,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto_algorithms::HASH_BLAKE3;
#[test]
fn test_text_document() {
let doc = VsfType::x("Hello, VSF!".to_string());
if let VsfType::x(s) = doc {
assert_eq!(s, "Hello, VSF!");
} else {
panic!("Expected string");
}
}
#[test]
fn test_raw_image_12bit() {
let samples = vec![2048u64; 100 * 50]; let img = raw_image(12, 100, 50, samples);
if let VsfType::p(tensor) = img {
assert_eq!(tensor.bit_depth, 12);
assert_eq!(tensor.shape, vec![100, 50]);
assert_eq!(tensor.len(), 100 * 50);
} else {
panic!("Expected bitpacked tensor");
}
}
#[test]
fn test_grayscale_image() {
let data = vec![128u8; 64 * 48];
let tensor = Tensor::new(vec![64, 48], data);
let img = VsfType::t_u3(tensor);
if let VsfType::t_u3(tensor) = img {
assert_eq!(tensor.shape, vec![64, 48]);
assert_eq!(tensor.data.len(), 64 * 48);
} else {
panic!("Expected u8 tensor");
}
}
#[test]
fn test_rgb_image() {
let data = vec![255u8; 64 * 48 * 3];
let tensor = Tensor::new(vec![64, 48, 3], data);
let img = VsfType::t_u3(tensor);
if let VsfType::t_u3(tensor) = img {
assert_eq!(tensor.shape, vec![64, 48, 3]);
assert_eq!(tensor.data.len(), 64 * 48 * 3);
} else {
panic!("Expected u8 tensor");
}
}
#[test]
fn test_gps_track() {
let track = gps_track(vec![
(40.7128, -74.0060), (51.5074, -0.1278), ]);
assert_eq!(track.len(), 2);
}
#[test]
fn test_gps_waypoint() {
let point = gps_waypoint(0.0, 0.0);
let (lat, lon) = point.to_lat_lon();
assert!(lat.abs() < 10.0, "Lat error: {}", lat.abs());
assert!(lon.abs() < 10.0, "Lon error: {}", lon.abs());
}
#[test]
fn test_geotagged_photo() {
let rgb_data = vec![0u8; 100 * 100 * 3];
let (img, loc) = geotagged_photo(100, 100, rgb_data, 0.0, 0.0);
if let VsfType::t_u3(tensor) = img {
assert_eq!(tensor.shape, vec![100, 100, 3]);
} else {
panic!("Expected RGB tensor");
}
let (lat, lon) = loc.to_lat_lon();
assert!(lat.abs() < 10.0);
assert!(lon.abs() < 10.0);
}
#[test]
fn test_complete_raw_image_minimal() {
let samples: Vec<u64> = vec![255; 64]; let image = BitPackedTensor::pack(8, vec![8, 8], &samples);
let result = build_raw_image(image, None, None, None);
assert!(result.is_ok());
let bytes = result.unwrap();
assert_eq!(&bytes[0..3], "RÅ".as_bytes());
assert_eq!(bytes[3], b'<');
assert!(bytes.len() > 50); }
#[test]
fn test_complete_raw_image_with_metadata() {
let samples: Vec<u64> = vec![255; 64]; let image = BitPackedTensor::pack(8, vec![8, 8], &samples);
let result = build_raw_image(
image,
Some(RawMetadata {
cfa_pattern: Some(CfaPattern::new(vec![b'R', b'G', b'G', b'B']).unwrap()),
black_level: Some(BlackLevel::new(64.0).unwrap()),
white_level: Some(WhiteLevel::new(255.0).unwrap()),
dark_frame_hash: Some(CalibrationHash::new(HASH_BLAKE3, vec![0xAB; 32]).unwrap()),
flat_field_hash: None,
bias_frame_hash: None,
vignette_correction_hash: None,
distortion_correction_hash: None,
magic_9: Some(
Magic9::new(vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]).unwrap(),
),
}),
Some(CameraSettings {
make: None,
model: None,
serial_number: None,
iso_speed: Some(IsoSpeed::new(800.0).unwrap()),
shutter_time_s: Some(ShutterTime::new(1. / 60.).unwrap()), aperture_f_number: Some(Aperture::new(2.8).unwrap()),
focal_length_m: Some(FocalLength::new(0.024).unwrap()), exposure_compensation: None,
focus_distance_m: None,
flash_fired: Some(FlashFired::new(false).unwrap()),
metering_mode: Some(MeteringMode::new("matrix".to_string()).unwrap()),
}),
None,
);
assert!(result.is_ok());
let bytes = result.unwrap();
assert_eq!(&bytes[0..3], "RÅ".as_bytes());
assert!(bytes.contains(&b'z'));
assert!(bytes.contains(&b'y'));
assert!(bytes.contains(&b'['));
assert!(bytes.contains(&b']'));
}
#[test]
fn test_lumis_raw_capture() {
let pixel_count = 4096 * 3072;
let samples: Vec<u64> = vec![2048; pixel_count];
let result = lumis_raw_capture(
samples,
800.0,
1. / 60., );
assert!(result.is_ok());
let bytes = result.unwrap();
assert_eq!(&bytes[0..3], "RÅ".as_bytes());
assert!(
bytes.len() > 18_000_000,
"File should be > 18MB with bitpacked pixels"
);
}
#[test]
fn test_roundtrip_minimal_raw() {
let samples: Vec<u64> = (0..16).collect(); let original_image = BitPackedTensor::pack(8, vec![4, 4], &samples);
let raw_bytes = build_raw_image(original_image.clone(), None, None, None).unwrap();
let parsed = parse_raw_image(&raw_bytes).unwrap();
assert_eq!(parsed.image.bit_depth, 8);
assert_eq!(parsed.image.shape, vec![4, 4]);
let original_samples = original_image.unpack().into_u64();
let parsed_samples = parsed.image.unpack().into_u64();
assert_eq!(parsed_samples, original_samples);
assert_eq!(parsed_samples, samples);
assert!(parsed.metadata.is_none());
assert!(parsed.camera.is_none());
assert!(parsed.lens.is_none());
}
#[test]
fn test_roundtrip_full_metadata() {
let samples: Vec<u64> = vec![200; 64]; let original_image = BitPackedTensor::pack(8, vec![8, 8], &samples);
let original_metadata = RawMetadata {
cfa_pattern: Some(CfaPattern::new(vec![b'R', b'G', b'G', b'B']).unwrap()), black_level: Some(BlackLevel::new(64.0).unwrap()),
white_level: Some(WhiteLevel::new(255.0).unwrap()),
dark_frame_hash: Some(CalibrationHash::new(HASH_BLAKE3, vec![0xAB; 32]).unwrap()),
flat_field_hash: Some(CalibrationHash::new(HASH_BLAKE3, vec![0xCD; 32]).unwrap()),
bias_frame_hash: None,
vignette_correction_hash: None,
distortion_correction_hash: None,
magic_9: Some(Magic9::new(vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]).unwrap()),
};
let original_camera = CameraSettings {
make: Some(Manufacturer::new("TestCam".to_string()).unwrap()),
model: Some(ModelName::new("Model X".to_string()).unwrap()),
serial_number: Some(SerialNumber::new("CAM123456".to_string()).unwrap()),
iso_speed: Some(IsoSpeed::new(800.0).unwrap()),
shutter_time_s: Some(ShutterTime::new(1. / 60.).unwrap()), aperture_f_number: Some(Aperture::new(2.8).unwrap()),
focal_length_m: Some(FocalLength::new(0.050).unwrap()), exposure_compensation: Some(ExposureCompensation::new(-0.5).unwrap()),
focus_distance_m: Some(FocusDistance::new(3.5).unwrap()),
flash_fired: Some(FlashFired::new(false).unwrap()),
metering_mode: Some(MeteringMode::new("matrix".to_string()).unwrap()),
};
let raw_bytes = build_raw_image(
original_image.clone(),
Some(original_metadata.clone()),
Some(original_camera.clone()),
None, )
.unwrap();
let parsed = parse_raw_image(&raw_bytes).unwrap();
assert_eq!(parsed.image.bit_depth, 8);
assert_eq!(parsed.image.shape, vec![8, 8]);
let parsed_samples = parsed.image.unpack().into_u64();
assert_eq!(parsed_samples, samples);
assert!(parsed.metadata.is_some());
let _meta = parsed.metadata.unwrap();
assert!(parsed.camera.is_some());
let _cam = parsed.camera.unwrap();
assert!(parsed.lens.is_none());
}
#[test]
fn test_preamble_structure() {
let samples: Vec<u64> = vec![100; 16]; let image = BitPackedTensor::pack(8, vec![4, 4], &samples);
let raw_bytes = build_raw_image(
image,
Some(RawMetadata {
cfa_pattern: Some(CfaPattern::new(vec![b'R', b'G', b'G', b'B']).unwrap()),
black_level: Some(BlackLevel::new(64.0).unwrap()),
white_level: Some(WhiteLevel::new(255.0).unwrap()),
dark_frame_hash: None,
flat_field_hash: None,
bias_frame_hash: None,
vignette_correction_hash: None,
distortion_correction_hash: None,
magic_9: None,
}),
None,
None,
)
.unwrap();
assert_eq!(&raw_bytes[0..3], "RÅ".as_bytes()); assert_eq!(raw_bytes[3], b'<');
let header_end = raw_bytes.iter().position(|&b| b == b'>').unwrap();
let section_start = raw_bytes[header_end..]
.iter()
.position(|&b| b == b'[')
.expect("Expected to find section start '['");
assert!(
section_start < 200,
"Section should start soon after header"
);
}
#[test]
fn test_builder_pattern_minimal() {
let samples: Vec<u64> = (0..16).collect();
let image = BitPackedTensor::pack(8, vec![4, 4], &samples);
let raw = RawImageBuilder::new(image);
let result = raw.build();
assert!(result.is_ok());
let bytes = result.unwrap();
assert_eq!(&bytes[0..3], "RÅ".as_bytes());
let parsed = parse_raw_image(&bytes).unwrap();
assert_eq!(parsed.image.bit_depth, 8);
assert_eq!(parsed.image.shape, vec![4, 4]);
}
#[test]
fn test_builder_pattern_camera_settings() {
let samples: Vec<u64> = vec![100; 64];
let image = BitPackedTensor::pack(8, vec![8, 8], &samples);
let mut raw = RawImageBuilder::new(image);
raw.camera.iso_speed = Some(800.0);
raw.camera.shutter_time_s = Some(1.0 / 60.0);
raw.camera.aperture_f_number = Some(2.8);
raw.camera.flash_fired = Some(false);
raw.camera.metering_mode = Some("matrix".to_string());
let result = raw.build();
assert!(result.is_ok());
let bytes = result.unwrap();
let parsed = parse_raw_image(&bytes).unwrap();
assert!(parsed.camera.is_some());
}
#[test]
fn test_builder_pattern_raw_metadata() {
let samples: Vec<u64> = vec![100; 64];
let image = BitPackedTensor::pack(8, vec![8, 8], &samples);
let mut raw = RawImageBuilder::new(image);
raw.raw.cfa_pattern = Some(vec![b'R', b'G', b'G', b'B']);
raw.raw.black_level = Some(64.0);
raw.raw.white_level = Some(4095.0);
raw.raw.dark_frame_hash = Some((HASH_BLAKE3, vec![0xAB; 32]));
let result = raw.build();
assert!(result.is_ok());
let bytes = result.unwrap();
let parsed = parse_raw_image(&bytes).unwrap();
assert!(parsed.metadata.is_some());
}
#[test]
fn test_builder_pattern_lens_info() {
let samples: Vec<u64> = vec![100; 64];
let image = BitPackedTensor::pack(8, vec![8, 8], &samples);
let mut raw = RawImageBuilder::new(image);
raw.lens.make = Some("Sony".to_string());
raw.lens.model = Some("FE 24-70mm F2.8 GM II".to_string());
raw.lens.serial_number = Some("ABC123456".to_string());
raw.lens.min_focal_length_m = Some(0.024); raw.lens.max_focal_length_m = Some(0.070); raw.lens.min_aperture_f = Some(22.0);
raw.lens.max_aperture_f = Some(2.8);
let result = raw.build();
assert!(result.is_ok());
let bytes = result.unwrap();
let parsed = parse_raw_image(&bytes).unwrap();
assert!(parsed.lens.is_some());
}
#[test]
fn test_builder_pattern_full() {
let samples: Vec<u64> = vec![2048; 64];
let image = BitPackedTensor::pack(12, vec![8, 8], &samples);
let mut raw = RawImageBuilder::new(image);
raw.raw.cfa_pattern = Some(vec![b'R', b'G', b'G', b'B']);
raw.raw.black_level = Some(64.0);
raw.raw.white_level = Some(4095.0);
raw.raw.magic_9 = Some(vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]);
raw.camera.iso_speed = Some(800.0);
raw.camera.shutter_time_s = Some(1.0 / 125.0);
raw.camera.aperture_f_number = Some(2.8);
raw.camera.focal_length_m = Some(0.050); raw.camera.exposure_compensation = Some(-0.5);
raw.camera.focus_distance_m = Some(3.5);
raw.camera.flash_fired = Some(false);
raw.camera.metering_mode = Some("spot".to_string());
raw.lens.make = Some("Sony".to_string());
raw.lens.model = Some("FE 50mm F1.2 GM".to_string());
let result = raw.build();
assert!(result.is_ok());
let bytes = result.unwrap();
let parsed = parse_raw_image(&bytes).unwrap();
assert_eq!(parsed.image.bit_depth, 12);
assert_eq!(parsed.image.shape, vec![8, 8]);
assert!(parsed.metadata.is_some());
assert!(parsed.camera.is_some());
assert!(parsed.lens.is_some());
}
#[test]
fn test_cfa_pattern_validation() {
let samples: Vec<u64> = vec![100; 16];
let image = BitPackedTensor::pack(8, vec![4, 4], &samples);
let valid_patterns = vec![
vec![b'R', b'G', b'G', b'B'], vec![b'G', b'R', b'B', b'G'], vec![b'B', b'G', b'G', b'R'], vec![b'C', b'Y', b'Y', b'G'], vec![b'R', b'G', b'B', b'E', b'W', b'C', b'Y', b'R', b'G'], ];
for cfa in valid_patterns {
let result = build_raw_image(
image.clone(),
Some(RawMetadata {
cfa_pattern: Some(CfaPattern::new(cfa.clone()).unwrap()),
black_level: None,
white_level: None,
dark_frame_hash: None,
flat_field_hash: None,
bias_frame_hash: None,
vignette_correction_hash: None,
distortion_correction_hash: None,
magic_9: None,
}),
None,
None,
);
assert!(
result.is_ok(),
"Valid CFA pattern {:?} should be accepted",
cfa
);
}
let invalid_patterns = vec![
vec![b'R', b'G', b'X', b'B'], vec![0, 1, 1, 2], vec![b'r', b'g', b'g', b'b'], ];
for cfa in invalid_patterns {
let cfa_result = CfaPattern::new(cfa.clone());
assert!(
cfa_result.is_err(),
"Invalid CFA pattern {:?} should be rejected",
cfa
);
}
}
}