|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| use std::path::PathBuf;
|
| use std::time::{Duration, Instant};
|
|
|
| use dinovision::dinov3::Config;
|
| use dinovision::inference::{self, Weights};
|
| use dinovision::pca::COMPONENTS;
|
| use dinovision::source::{FrameSource, TestPattern};
|
|
|
|
|
|
|
| fn upscale(grid_rgb: &[f32], grid: usize, out: usize) -> Vec<u8> {
|
| let mut px = vec![0u8; out * out * 3];
|
| for y in 0..out {
|
| let gy = (y * grid) / out;
|
| for x in 0..out {
|
| let gx = (x * grid) / out;
|
| let src = (gy * grid + gx) * COMPONENTS;
|
| let dst = (y * out + x) * 3;
|
| for c in 0..3 {
|
| px[dst + c] = (grid_rgb[src + c].clamp(0.0, 1.0) * 255.0) as u8;
|
| }
|
| }
|
| }
|
| px
|
| }
|
|
|
|
|
| fn write_side_by_side(path: &PathBuf, left: &[u8], right: &[u8], size: usize) {
|
| let mut joined = vec![0u8; size * size * 6];
|
| for y in 0..size {
|
| let src = y * size * 3;
|
| let dst = y * size * 6;
|
| joined[dst..dst + size * 3].copy_from_slice(&left[src..src + size * 3]);
|
| joined[dst + size * 3..dst + size * 6].copy_from_slice(&right[src..src + size * 3]);
|
| }
|
| image::RgbImage::from_raw(size as u32 * 2, size as u32, joined)
|
| .expect("image dimensions do not match buffer")
|
| .save(path)
|
| .expect("failed to write image");
|
| }
|
|
|
| fn main() {
|
| env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
|
|
| let mut args = std::env::args().skip(1);
|
| let out_path = PathBuf::from(args.next().unwrap_or_else(|| "preview.png".into()));
|
| let weights = match args.next().map(PathBuf::from) {
|
| Some(p) if p.exists() => {
|
| log::info!("using weights from {}", p.display());
|
| Weights::SafeTensors(p)
|
| }
|
| Some(p) => panic!("{} does not exist", p.display()),
|
| None => Weights::Synthetic,
|
| };
|
|
|
| let config = Config::vits16().at_resolution(256);
|
| let gpu = dinovision::init_context(None).expect("failed to initialize GPU context");
|
| let worker = inference::spawn(gpu, config.clone(), weights, None, 1, dinovision::inference::Display::PcaColour);
|
|
|
|
|
|
|
|
|
| let mut source: Box<dyn FrameSource> = match std::env::var("DINOVISION_SOURCE").as_deref() {
|
| #[cfg(feature = "capture")]
|
| Ok("screen") => match dinovision::source::ScreenCapture::new(config.image_size, 0) {
|
| Ok(s) => Box::new(s),
|
| Err(e) => panic!("screen capture unavailable: {e}"),
|
| },
|
| #[cfg(not(feature = "capture"))]
|
| Ok("screen") => panic!("rebuild with --features capture for screen capture"),
|
| _ => Box::new(TestPattern::new(config.image_size)),
|
| };
|
| let mut last_frame: Vec<u8> = Vec::new();
|
|
|
|
|
|
|
| let deadline = Instant::now() + Duration::from_secs(180);
|
| let mut completed = 0;
|
| while completed < 5 && Instant::now() < deadline {
|
| if worker.is_ready() {
|
| let rgb = source.next_frame().unwrap();
|
| last_frame = rgb.to_vec();
|
| let patches = dinovision::preprocess::patches_from_rgb8(rgb, &config);
|
| worker.submit(patches);
|
| }
|
| std::thread::sleep(Duration::from_millis(20));
|
| completed = worker.generation();
|
| }
|
|
|
| let grid = worker
|
| .latest()
|
| .expect("inference produced no result before the deadline");
|
| log::info!(
|
| "{} completed encodes, last took {:.1} ms ({}x{} grid)",
|
| completed,
|
| grid.latency_ms,
|
| grid.grid,
|
| grid.grid
|
| );
|
|
|
| let size = config.image_size;
|
| let features = upscale(grid.patch_rgb(), grid.grid, size);
|
| write_side_by_side(&out_path, &last_frame, &features, size);
|
|
|
|
|
|
|
| let spread = {
|
| let mut lo = [255u8; 3];
|
| let mut hi = [0u8; 3];
|
| for px in features.chunks_exact(3) {
|
| for c in 0..3 {
|
| lo[c] = lo[c].min(px[c]);
|
| hi[c] = hi[c].max(px[c]);
|
| }
|
| }
|
| (0..3).map(|c| hi[c] as i32 - lo[c] as i32).max().unwrap()
|
| };
|
| println!("wrote {} (source | features)", out_path.display());
|
| println!("colour spread: {spread}/255");
|
| if spread < 16 {
|
| println!("WARNING: the feature view is nearly flat — projection may have collapsed");
|
| }
|
| }
|
|
|