apple_vision/feature_print/
mod.rs1use core::ffi::c_char;
5use core::ptr;
6use std::ffi::CString;
7use std::path::Path;
8
9use crate::error::{from_swift, VisionError};
10use crate::ffi;
11
12#[derive(Debug, Clone, PartialEq)]
18#[allow(clippy::derive_partial_eq_without_eq)]
19pub struct FeaturePrint {
20 pub element_type: i32,
22 pub element_count: usize,
24 pub data: Vec<u8>,
26}
27
28impl FeaturePrint {
29 #[must_use]
32 pub fn as_f32(&self) -> Option<Vec<f32>> {
33 if self.element_type != 1 {
34 return None;
35 }
36 let mut out = Vec::with_capacity(self.element_count);
37 for chunk in self.data.chunks_exact(4) {
38 let arr: [u8; 4] = chunk.try_into().ok()?;
39 out.push(f32::from_le_bytes(arr));
40 }
41 Some(out)
42 }
43
44 #[must_use]
47 pub fn as_f64(&self) -> Option<Vec<f64>> {
48 if self.element_type != 2 {
49 return None;
50 }
51 let mut out = Vec::with_capacity(self.element_count);
52 for chunk in self.data.chunks_exact(8) {
53 let arr: [u8; 8] = chunk.try_into().ok()?;
54 out.push(f64::from_le_bytes(arr));
55 }
56 Some(out)
57 }
58
59 pub fn l2_distance(&self, other: &Self) -> Result<f64, VisionError> {
67 if self.element_type != other.element_type || self.element_count != other.element_count {
68 return Err(VisionError::InvalidArgument(
69 "feature print element type / count mismatch".into(),
70 ));
71 }
72 let sumsq: f64 = match self.element_type {
73 1 => self
74 .as_f32()
75 .unwrap_or_default()
76 .iter()
77 .zip(other.as_f32().unwrap_or_default().iter())
78 .map(|(a, b)| f64::from(a - b).powi(2))
79 .sum(),
80 2 => self
81 .as_f64()
82 .unwrap_or_default()
83 .iter()
84 .zip(other.as_f64().unwrap_or_default().iter())
85 .map(|(a, b)| (a - b).powi(2))
86 .sum(),
87 _ => 0.0,
88 };
89 Ok(sumsq.sqrt())
90 }
91}
92
93pub fn generate_image_feature_print_in_path(
99 path: impl AsRef<Path>,
100) -> Result<Option<FeaturePrint>, VisionError> {
101 let path_str = path
102 .as_ref()
103 .to_str()
104 .ok_or_else(|| VisionError::InvalidArgument("non-UTF-8 path".into()))?;
105 let path_c = CString::new(path_str)
106 .map_err(|e| VisionError::InvalidArgument(format!("path NUL byte: {e}")))?;
107
108 let mut raw = ffi::FeaturePrintRaw {
109 element_type: 0,
110 element_count: 0,
111 bytes: ptr::null_mut(),
112 };
113 let mut err_msg: *mut c_char = ptr::null_mut();
114 let status = unsafe {
116 ffi::vn_generate_image_feature_print_in_path(path_c.as_ptr(), &mut raw, &mut err_msg)
117 };
118 if status != ffi::status::OK {
119 return Err(unsafe { from_swift(status, err_msg) });
121 }
122 if raw.bytes.is_null() || raw.element_count == 0 {
123 return Ok(None);
124 }
125 let bytes_per_elem = match raw.element_type {
126 1 => 4_usize,
127 2 => 8_usize,
128 _ => 0_usize,
129 };
130 let len = raw.element_count.saturating_mul(bytes_per_elem);
131 let slice = unsafe { core::slice::from_raw_parts(raw.bytes.cast::<u8>(), len) };
133 let data = slice.to_vec();
134 unsafe { ffi::vn_feature_print_free(&mut raw) };
136
137 Ok(Some(FeaturePrint {
138 element_type: raw.element_type,
139 element_count: raw.element_count,
140 data,
141 }))
142}