Skip to main content

tflite_c/
tensor.rs

1//! Borrowed views into a [`crate::Interpreter`]'s input and output tensors.
2
3use std::ffi::CStr;
4use std::marker::PhantomData;
5use std::os::raw::c_int;
6use std::sync::Arc;
7
8use crate::error::{Error, Result};
9use crate::ffi::{TfLiteQuantizationParams, TfLiteTensor, TfLiteType};
10use crate::library::TfLiteLibrary;
11
12/// Read-only borrow of a tensor owned by a [`crate::Interpreter`].
13///
14/// `Tensor<'a>` cannot outlive the interpreter it came from — the borrow
15/// checker enforces this by attaching a phantom lifetime to the underlying
16/// pointer.
17pub struct Tensor<'a> {
18    raw: *const TfLiteTensor,
19    lib: Arc<TfLiteLibrary>,
20    _marker: PhantomData<&'a ()>,
21}
22
23/// Read-write borrow of a tensor owned by a [`crate::Interpreter`].
24///
25/// Like [`Tensor`] but additionally exposes [`TensorMut::data_mut`] for
26/// filling input buffers in place.
27pub struct TensorMut<'a> {
28    raw: *mut TfLiteTensor,
29    lib: Arc<TfLiteLibrary>,
30    _marker: PhantomData<&'a mut ()>,
31}
32
33impl<'a> Tensor<'a> {
34    /// Construct from a raw pointer; intended for use by the interpreter only.
35    ///
36    /// # Safety
37    /// The caller must guarantee that `raw` is a valid `TfLiteTensor*` that
38    /// outlives `'a` and is not being mutated concurrently.
39    pub(crate) unsafe fn from_raw(raw: *const TfLiteTensor, lib: Arc<TfLiteLibrary>) -> Self {
40        Self { raw, lib, _marker: PhantomData }
41    }
42
43    /// Tensor shape as a list of per-axis dimensions.
44    pub fn dims(&self) -> Vec<i32> {
45        common_dims(self.raw, &self.lib)
46    }
47
48    /// Element type of the tensor.
49    pub fn dtype(&self) -> TfLiteType {
50        common_dtype(self.raw, &self.lib)
51    }
52
53    /// Tensor name as reported by the model, if any.
54    pub fn name(&self) -> Option<&str> {
55        common_name(self.raw, &self.lib)
56    }
57
58    /// Total size of the tensor's data buffer in bytes.
59    pub fn byte_size(&self) -> usize {
60        common_byte_size(self.raw, &self.lib)
61    }
62
63    /// Affine quantization parameters (`scale`, `zero_point`).
64    pub fn quantization(&self) -> TfLiteQuantizationParams {
65        common_quantization(self.raw, &self.lib)
66    }
67
68    /// Raw byte view of the tensor's data buffer.
69    ///
70    /// Returns an empty slice if the C API reports a NULL data pointer.
71    pub fn data(&self) -> &[u8] {
72        common_data(self.raw, &self.lib)
73    }
74
75    /// Typed `&[f32]` view; errors if the tensor is not `Float32`.
76    pub fn as_slice_f32(&self) -> Result<&[f32]> {
77        typed_slice::<f32>(self.raw, &self.lib, TfLiteType::Float32)
78    }
79
80    /// Typed `&[i8]` view; errors if the tensor is not `Int8`.
81    pub fn as_slice_i8(&self) -> Result<&[i8]> {
82        typed_slice::<i8>(self.raw, &self.lib, TfLiteType::Int8)
83    }
84
85    /// Typed `&[u8]` view; errors if the tensor is not `UInt8`.
86    pub fn as_slice_u8(&self) -> Result<&[u8]> {
87        typed_slice::<u8>(self.raw, &self.lib, TfLiteType::UInt8)
88    }
89
90    /// Typed `&[i32]` view; errors if the tensor is not `Int32`.
91    pub fn as_slice_i32(&self) -> Result<&[i32]> {
92        typed_slice::<i32>(self.raw, &self.lib, TfLiteType::Int32)
93    }
94
95    /// Read the tensor as a fresh `Vec<f32>`, dequantizing `Int8`/`UInt8`
96    /// using the tensor's quantization parameters.
97    ///
98    /// The dequantization formula is `real = (q - zero_point) * scale`.
99    /// `Float32` tensors are copied straight through.
100    pub fn to_vec_f32(&self) -> Result<Vec<f32>> {
101        match self.dtype() {
102            TfLiteType::Float32 => self.as_slice_f32().map(<[f32]>::to_vec),
103            TfLiteType::Int8 => {
104                let raw = self.as_slice_i8()?;
105                Ok(dequantize_i8(raw, self.quantization()))
106            }
107            TfLiteType::UInt8 => {
108                let raw = self.as_slice_u8()?;
109                Ok(dequantize_u8(raw, self.quantization()))
110            }
111            actual => Err(Error::DtypeMismatch { expected: TfLiteType::Float32, actual }),
112        }
113    }
114}
115
116impl<'a> TensorMut<'a> {
117    /// Construct from a raw pointer; intended for use by the interpreter only.
118    ///
119    /// # Safety
120    /// The caller must guarantee that `raw` is a valid `TfLiteTensor*` that
121    /// outlives `'a` and that the unique-borrow invariant is upheld.
122    pub(crate) unsafe fn from_raw(raw: *mut TfLiteTensor, lib: Arc<TfLiteLibrary>) -> Self {
123        Self { raw, lib, _marker: PhantomData }
124    }
125
126    /// Tensor shape as a list of per-axis dimensions.
127    pub fn dims(&self) -> Vec<i32> {
128        common_dims(self.raw, &self.lib)
129    }
130
131    /// Element type of the tensor.
132    pub fn dtype(&self) -> TfLiteType {
133        common_dtype(self.raw, &self.lib)
134    }
135
136    /// Tensor name as reported by the model, if any.
137    pub fn name(&self) -> Option<&str> {
138        common_name(self.raw, &self.lib)
139    }
140
141    /// Total size of the tensor's data buffer in bytes.
142    pub fn byte_size(&self) -> usize {
143        common_byte_size(self.raw, &self.lib)
144    }
145
146    /// Affine quantization parameters (`scale`, `zero_point`).
147    pub fn quantization(&self) -> TfLiteQuantizationParams {
148        common_quantization(self.raw, &self.lib)
149    }
150
151    /// Read-only byte view of the tensor's data buffer.
152    pub fn data(&self) -> &[u8] {
153        common_data(self.raw, &self.lib)
154    }
155
156    /// Mutable byte view of the tensor's data buffer.
157    ///
158    /// Errors with [`Error::NullData`] if the C API reports a NULL data
159    /// pointer (which can happen if the tensor has not been allocated yet).
160    pub fn data_mut(&mut self) -> Result<&mut [u8]> {
161        // SAFETY: `raw` is non-null per the interpreter's bounds check, and
162        // `TensorMut` requires exclusive access to the tensor.
163        unsafe {
164            let ptr = (self.lib.tensor_data)(self.raw) as *mut u8;
165            if ptr.is_null() {
166                return Err(Error::NullData);
167            }
168            let len = (self.lib.tensor_byte_size)(self.raw);
169            Ok(std::slice::from_raw_parts_mut(ptr, len))
170        }
171    }
172}
173
174// --- Shared helpers (deliberately free functions to avoid trait dispatch) -------
175
176fn common_dims(raw: *const TfLiteTensor, lib: &TfLiteLibrary) -> Vec<i32> {
177    // SAFETY: `raw` is non-null per the interpreter's bounds check.
178    unsafe {
179        let n = (lib.tensor_num_dims)(raw);
180        if n <= 0 {
181            return Vec::new();
182        }
183        (0..n).map(|i| (lib.tensor_dim)(raw, i as c_int)).collect()
184    }
185}
186
187fn common_dtype(raw: *const TfLiteTensor, lib: &TfLiteLibrary) -> TfLiteType {
188    // SAFETY: `raw` is non-null per the interpreter's bounds check.
189    unsafe { (lib.tensor_type)(raw) }
190}
191
192fn common_name<'a>(raw: *const TfLiteTensor, lib: &TfLiteLibrary) -> Option<&'a str> {
193    // SAFETY: `raw` is non-null per the interpreter's bounds check; the
194    // returned C string is owned by TFLite and lives at least as long as
195    // the tensor handle, which is upheld by the borrow checker via the
196    // caller's `'a` lifetime.
197    unsafe {
198        let ptr = (lib.tensor_name)(raw);
199        if ptr.is_null() {
200            return None;
201        }
202        CStr::from_ptr(ptr).to_str().ok()
203    }
204}
205
206fn common_byte_size(raw: *const TfLiteTensor, lib: &TfLiteLibrary) -> usize {
207    // SAFETY: `raw` is non-null per the interpreter's bounds check.
208    unsafe { (lib.tensor_byte_size)(raw) }
209}
210
211fn common_quantization(
212    raw: *const TfLiteTensor,
213    lib: &TfLiteLibrary,
214) -> TfLiteQuantizationParams {
215    // SAFETY: `raw` is non-null per the interpreter's bounds check.
216    unsafe { (lib.tensor_quantization_params)(raw) }
217}
218
219fn common_data<'a>(raw: *const TfLiteTensor, lib: &TfLiteLibrary) -> &'a [u8] {
220    // SAFETY: `raw` is non-null per the interpreter's bounds check.
221    unsafe {
222        let ptr = (lib.tensor_data)(raw) as *const u8;
223        if ptr.is_null() {
224            return &[];
225        }
226        let len = (lib.tensor_byte_size)(raw);
227        std::slice::from_raw_parts(ptr, len)
228    }
229}
230
231fn typed_slice<'a, T: Copy>(
232    raw: *const TfLiteTensor,
233    lib: &TfLiteLibrary,
234    expected: TfLiteType,
235) -> Result<&'a [T]> {
236    let actual = common_dtype(raw, lib);
237    if actual != expected {
238        return Err(Error::DtypeMismatch { expected, actual });
239    }
240    // SAFETY: We have just confirmed the tensor's element type matches `T`'s
241    // representation. `byte_size` reports the total data length in bytes.
242    unsafe {
243        let ptr = (lib.tensor_data)(raw) as *const T;
244        if ptr.is_null() {
245            return Err(Error::NullData);
246        }
247        let bytes = (lib.tensor_byte_size)(raw);
248        let len = bytes / std::mem::size_of::<T>();
249        Ok(std::slice::from_raw_parts(ptr, len))
250    }
251}
252
253/// Dequantize an int8 buffer into f32 using affine quantization.
254///
255/// `real = (q - zero_point) * scale`. Exposed at crate-private scope so it
256/// can be unit-tested without a live TFLite library.
257pub(crate) fn dequantize_i8(raw: &[i8], params: TfLiteQuantizationParams) -> Vec<f32> {
258    let zp = params.zero_point as f32;
259    let scale = params.scale;
260    raw.iter().map(|&q| (q as f32 - zp) * scale).collect()
261}
262
263/// Dequantize a uint8 buffer into f32 using affine quantization.
264pub(crate) fn dequantize_u8(raw: &[u8], params: TfLiteQuantizationParams) -> Vec<f32> {
265    let zp = params.zero_point as f32;
266    let scale = params.scale;
267    raw.iter().map(|&q| (q as f32 - zp) * scale).collect()
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    #[test]
275    fn dequantize_i8_matches_formula() {
276        // scale=0.5, zero_point=-10 → real = (q + 10) * 0.5
277        let params = TfLiteQuantizationParams { scale: 0.5, zero_point: -10 };
278        let raw: Vec<i8> = vec![-10, 0, 10, 20];
279        let got = dequantize_i8(&raw, params);
280        assert_eq!(got, vec![0.0, 5.0, 10.0, 15.0]);
281    }
282
283    #[test]
284    fn dequantize_i8_with_zero_scale_is_all_zero() {
285        let params = TfLiteQuantizationParams { scale: 0.0, zero_point: 42 };
286        let got = dequantize_i8(&[1, 2, 3, 4], params);
287        assert_eq!(got, vec![0.0, 0.0, 0.0, 0.0]);
288    }
289
290    #[test]
291    fn dequantize_u8_matches_formula() {
292        // scale=0.25, zero_point=128 → real = (q - 128) * 0.25
293        let params = TfLiteQuantizationParams { scale: 0.25, zero_point: 128 };
294        let raw: Vec<u8> = vec![128, 132, 124, 0];
295        let got = dequantize_u8(&raw, params);
296        assert_eq!(got, vec![0.0, 1.0, -1.0, -32.0]);
297    }
298
299    #[test]
300    fn dequantize_handles_empty_input() {
301        let params = TfLiteQuantizationParams { scale: 1.0, zero_point: 0 };
302        assert!(dequantize_i8(&[], params).is_empty());
303        assert!(dequantize_u8(&[], params).is_empty());
304    }
305}