[READ-ONLY] Mirror of https://github.com/mrgnw/spatial-maker.
0

Configure Feed

Select the types of activity you want to include in your feed.

v0.3.1: Fix DIBR stereo artifacts (z-buffer, horizontal fill), fix model finder matching .tar.gz, JSON ffprobe parsing, download progress output

+60 -45
spatial-maker-v0.3.1-aarch64-apple-darwin.tar.gz

This is a binary file and will not be displayed.

+21
src/model.rs
··· 111 111 if let Ok(entries) = std::fs::read_dir(dir) { 112 112 for entry in entries.flatten() { 113 113 let name = entry.file_name().to_string_lossy().to_string(); 114 + if name.ends_with(".tar.gz") || name.ends_with(".downloading") { 115 + continue; 116 + } 114 117 if name.contains("DepthAnything") || name.contains("depth_anything") { 115 118 let lower_size = encoder_size.to_lowercase(); 116 119 let name_lower = name.to_lowercase(); ··· 190 193 where 191 194 F: FnMut(u64, u64), 192 195 { 196 + eprintln!("Downloading model: {} ({} MB)...", metadata.name, metadata.size_mb); 193 197 tracing::info!("Downloading model: {} from {}", metadata.name, metadata.url); 194 198 195 199 let response = reqwest::get(&metadata.url) ··· 212 216 let mut stream = response.bytes_stream(); 213 217 use futures_util::StreamExt; 214 218 219 + let mut last_pct: u64 = 0; 215 220 while let Some(chunk) = stream.next().await { 216 221 let chunk = chunk.map_err(|e| SpatialError::Other(format!("Download interrupted: {}", e)))?; 217 222 file.write_all(&chunk) ··· 220 225 downloaded += chunk.len() as u64; 221 226 if let Some(ref mut f) = progress_fn { 222 227 f(downloaded, total_bytes); 228 + } 229 + if total_bytes > 0 { 230 + let pct = downloaded * 100 / total_bytes; 231 + if pct != last_pct { 232 + last_pct = pct; 233 + eprint!("\rDownloading... {}%", pct); 234 + } 223 235 } 224 236 } 237 + eprintln!(); 225 238 drop(file); 226 239 227 240 let parent = destination 228 241 .parent() 229 242 .ok_or_else(|| SpatialError::IoError("Invalid destination path".to_string()))?; 230 243 244 + eprintln!("Extracting..."); 231 245 let output = std::process::Command::new("tar") 232 246 .args(&["xzf"]) 233 247 .arg(&temp_path) ··· 242 256 } 243 257 244 258 let _ = tokio::fs::remove_file(&temp_path).await; 259 + 260 + if !destination.exists() { 261 + return Err(SpatialError::ModelError(format!( 262 + "Extraction succeeded but model not found at {:?}", 263 + destination 264 + ))); 265 + } 245 266 } else { 246 267 let mut file = tokio::fs::File::create(destination) 247 268 .await
+39 -45
src/stereo.rs
··· 12 12 let height = img_rgb.height() as usize; 13 13 14 14 let mut right_rgb: ImageBuffer<Rgb<u8>, Vec<u8>> = ImageBuffer::new(width as u32, height as u32); 15 - 16 - let bg = Rgb([64u8, 64u8, 64u8]); 17 - for pixel in right_rgb.pixels_mut() { 18 - *pixel = bg; 19 - } 15 + let mut depth_buffer = vec![f32::NEG_INFINITY; width * height]; 16 + let mut filled = vec![false; width * height]; 20 17 21 18 for y in 0..height { 22 19 for x in 0..width { ··· 25 22 let x_right = x as i32 - disparity; 26 23 27 24 if x_right >= 0 && x_right < width as i32 { 28 - if let Some(pixel) = img_rgb.get_pixel_checked(x as u32, y as u32) { 29 - right_rgb.put_pixel(x_right as u32, y as u32, *pixel); 25 + let idx = y * width + x_right as usize; 26 + if depth_val > depth_buffer[idx] { 27 + depth_buffer[idx] = depth_val; 28 + filled[idx] = true; 29 + if let Some(pixel) = img_rgb.get_pixel_checked(x as u32, y as u32) { 30 + right_rgb.put_pixel(x_right as u32, y as u32, *pixel); 31 + } 30 32 } 31 33 } 32 34 } 33 35 } 34 36 35 - fill_disocclusions(&mut right_rgb); 37 + fill_disocclusions(&mut right_rgb, &filled, width, height); 36 38 37 39 let left_image = image.clone(); 38 40 let right_image = DynamicImage::ImageRgb8(right_rgb); ··· 65 67 } 66 68 } 67 69 68 - fn fill_disocclusions(image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>) { 69 - let width = image.width() as usize; 70 - let height = image.height() as usize; 71 - let bg = Rgb([64u8, 64u8, 64u8]); 72 - 70 + fn fill_disocclusions( 71 + image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>, 72 + filled: &[bool], 73 + width: usize, 74 + height: usize, 75 + ) { 73 76 let original = image.clone(); 74 77 75 78 for y in 0..height { 76 79 for x in 0..width { 77 - let pixel = original.get_pixel(x as u32, y as u32); 78 - if pixel[0] == bg[0] && pixel[1] == bg[1] && pixel[2] == bg[2] { 79 - if let Some(nearest) = find_nearest_valid(&original, x, y, bg) { 80 - image.put_pixel(x as u32, y as u32, nearest); 81 - } 80 + if filled[y * width + x] { 81 + continue; 82 82 } 83 - } 84 - } 85 - } 86 - 87 - fn find_nearest_valid( 88 - image: &ImageBuffer<Rgb<u8>, Vec<u8>>, 89 - cx: usize, 90 - cy: usize, 91 - bg: Rgb<u8>, 92 - ) -> Option<Rgb<u8>> { 93 - let width = image.width() as usize; 94 - let height = image.height() as usize; 95 - 96 - for radius in 1..=20 { 97 - for dy in -(radius as i32)..=(radius as i32) { 98 - for dx in -(radius as i32)..=(radius as i32) { 99 - if dx.abs() != radius as i32 && dy.abs() != radius as i32 { 100 - continue; 83 + // scan left to find nearest filled pixel on same row 84 + let mut left_pixel = None; 85 + for lx in (0..x).rev() { 86 + if filled[y * width + lx] { 87 + left_pixel = Some(*original.get_pixel(lx as u32, y as u32)); 88 + break; 101 89 } 102 - let nx = (cx as i32 + dx) as usize; 103 - let ny = (cy as i32 + dy) as usize; 104 - if nx < width && ny < height { 105 - let pixel = image.get_pixel(nx as u32, ny as u32); 106 - if pixel[0] != bg[0] || pixel[1] != bg[1] || pixel[2] != bg[2] { 107 - return Some(*pixel); 108 - } 90 + } 91 + // scan right 92 + let mut right_pixel = None; 93 + for rx in (x + 1)..width { 94 + if filled[y * width + rx] { 95 + right_pixel = Some(*original.get_pixel(rx as u32, y as u32)); 96 + break; 109 97 } 110 98 } 99 + 100 + let fill = match (left_pixel, right_pixel) { 101 + (Some(l), Some(_)) => l, // prefer left (background side in DIBR) 102 + (Some(l), None) => l, 103 + (None, Some(r)) => r, 104 + (None, None) => continue, 105 + }; 106 + image.put_pixel(x as u32, y as u32, fill); 111 107 } 112 108 } 113 - 114 - None 115 109 }