metrale_model_layers/vision_item.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: The unit of vision input handed to the model.
4//!
5//! Owner: model-layers (vision input).
6//! Invariants: none beyond the types.
7
8/// 2026-09-25: One vision item: a still image, or a video, ready for the encoder.
9///
10/// `groups` holds the temporal groups: one for a still image (`image`), and
11/// `grid_t` for a video, each group a full `grid_h × grid_w` patch plane built
12/// from `temporal_patch_size` consecutive frames. All groups share the item's
13/// one grid, so the encoder takes each group as an image of that grid.
14///
15/// The group count lives in the item rather than in a parallel vector, so the
16/// pad run (`pad_count`) and the encoder grids both read it from `t_len`.
17#[derive(Debug, Clone, PartialEq)]
18pub struct VisionItem {
19 /// 2026-09-25: Per temporal group: `[grid_h * grid_w, C * temporal_patch_size * patch^2]`.
20 pub groups: Vec<Vec<f32>>,
21 /// 2026-09-25: Pre-merge patch grid, shared by all of this item's groups.
22 pub grid_h: usize,
23 pub grid_w: usize,
24}
25
26impl VisionItem {
27 /// 2026-09-25: A still image: one temporal group.
28 pub fn image(pixels: Vec<f32>, grid_h: usize, grid_w: usize) -> Self {
29 Self {
30 groups: vec![pixels],
31 grid_h,
32 grid_w,
33 }
34 }
35
36 /// 2026-09-25: Temporal extent, in groups; at least 1.
37 pub fn t_len(&self) -> usize {
38 self.groups.len().max(1)
39 }
40
41 /// 2026-09-25: Merged tokens this item occupies in the prompt: `t_len` times
42 /// the spatially merged grid. The chat request builder uses it as the
43 /// length of the item's pad run.
44 pub fn pad_count(&self, spatial_merge_size: usize) -> usize {
45 let sms = spatial_merge_size.max(1);
46 self.t_len() * (self.grid_h / sms) * (self.grid_w / sms)
47 }
48}