1use hf_hub::api::sync::Api;
2use image::{DynamicImage, GenericImageView};
3use ort::{inputs, session::Session, value::TensorRef};
4use std::cmp::{max, min};
5
6#[derive(Debug)]
7pub struct Lama {
8 model: Session,
9}
10
11impl Lama {
12 pub fn new() -> anyhow::Result<Self> {
13 let api = Api::new()?;
14 let repo = api.model("mayocream/lama-manga-onnx".to_string());
15 let model_path = repo.get("lama-manga.onnx")?;
16
17 let model = Session::builder()?
18 .with_optimization_level(ort::session::builder::GraphOptimizationLevel::Level3)?
19 .commit_from_file(model_path)?;
20
21 Ok(Lama { model })
22 }
23
24 pub fn inference(
25 &mut self,
26 image: &DynamicImage,
27 mask: &DynamicImage,
28 ) -> anyhow::Result<DynamicImage> {
29 self.inference_tiled(image, mask, 512, 128)
32 }
33
34 pub fn inference_tiled(
42 &mut self,
43 image: &DynamicImage,
44 mask: &DynamicImage,
45 tile_size: u32,
46 overlap: u32,
47 ) -> anyhow::Result<DynamicImage> {
48 let (w, h) = image.dimensions();
49 let tile = max(32, tile_size); let ovl = min(overlap, tile.saturating_sub(1));
51 let stride = tile.saturating_sub(ovl);
52
53 let mut acc_r = vec![0f32; (w * h) as usize];
55 let mut acc_g = vec![0f32; (w * h) as usize];
56 let mut acc_b = vec![0f32; (w * h) as usize];
57 let mut acc_w = vec![0f32; (w * h) as usize];
58
59 let img_rgb = image.to_rgb8();
61 let mask_luma = mask.to_luma8();
63
64 let mut y0 = 0u32;
66 while y0 < h {
67 let mut x0 = 0u32;
68 while x0 < w {
69 let x1 = min(x0 + tile, w);
70 let y1 = min(y0 + tile, h);
71 let eff_w = x1 - x0;
72 let eff_h = y1 - y0;
73
74 let mut any_masked = false;
76 'mask_check: for yy in 0..eff_h {
77 for xx in 0..eff_w {
78 if mask_luma.get_pixel(x0 + xx, y0 + yy)[0] > 0 {
79 any_masked = true;
80 break 'mask_check;
81 }
82 }
83 }
84 if !any_masked {
85 x0 = x0.saturating_add(stride).min(w);
86 continue;
87 }
88
89 let (tile_img, tile_mask) =
91 extract_reflect_padded_tile(&img_rgb, &mask_luma, x0, y0, eff_w, eff_h, tile);
92
93 let tile_out = self.infer_tile_512(&tile_img, &tile_mask)?; let mut tile_out_crop = image::RgbImage::new(eff_w, eff_h);
98 for yy in 0..eff_h {
99 for xx in 0..eff_w {
100 tile_out_crop.put_pixel(xx, yy, *tile_out.get_pixel(xx, yy));
101 }
102 }
103
104 let weights = make_tile_weights(eff_w, eff_h, ovl);
106
107 for yy in 0..eff_h {
111 for xx in 0..eff_w {
112 let global_x = x0 + xx;
113 let global_y = y0 + yy;
114 let idx = (global_y * w + global_x) as usize;
115
116 let m = if mask_luma.get_pixel(global_x, global_y)[0] > 0 {
117 1.0f32
118 } else {
119 0.0f32
120 };
121
122 if m == 0.0 {
123 continue;
124 }
125
126 let wgt = weights[(yy * eff_w + xx) as usize] * m;
127 if wgt <= 0.0 {
128 continue;
129 }
130
131 let p = tile_out_crop.get_pixel(xx, yy);
132 acc_r[idx] += p[0] as f32 * wgt;
133 acc_g[idx] += p[1] as f32 * wgt;
134 acc_b[idx] += p[2] as f32 * wgt;
135 acc_w[idx] += wgt;
136 }
137 }
138
139 x0 = x0.saturating_add(stride).min(w);
140 }
141 y0 = y0.saturating_add(stride).min(h);
142 }
143
144 let mut out = img_rgb.clone();
146 for y in 0..h {
147 for x in 0..w {
148 let idx = (y * w + x) as usize;
149 if mask_luma.get_pixel(x, y)[0] == 0 {
150 continue; }
152 let wsum = acc_w[idx];
153 if wsum > 0.0 {
154 let r = (acc_r[idx] / wsum).clamp(0.0, 255.0) as u8;
155 let g = (acc_g[idx] / wsum).clamp(0.0, 255.0) as u8;
156 let b = (acc_b[idx] / wsum).clamp(0.0, 255.0) as u8;
157 out.put_pixel(x, y, image::Rgb([r, g, b]));
158 }
159 }
160 }
161
162 Ok(DynamicImage::ImageRgb8(out))
163 }
164}
165
166fn extract_reflect_padded_tile(
168 img: &image::RgbImage,
169 mask: &image::GrayImage,
170 x0: u32,
171 y0: u32,
172 eff_w: u32,
173 eff_h: u32,
174 tile: u32,
175) -> (image::RgbImage, image::GrayImage) {
176 let mut out_img = image::RgbImage::new(tile, tile);
177 let mut out_msk = image::GrayImage::new(tile, tile);
178
179 for yy in 0..eff_h {
181 for xx in 0..eff_w {
182 let src_x = x0 + xx;
183 let src_y = y0 + yy;
184 out_img.put_pixel(xx, yy, *img.get_pixel(src_x, src_y));
185 out_msk.put_pixel(xx, yy, *mask.get_pixel(src_x, src_y));
186 }
187 }
188
189 for yy in 0..eff_h {
191 for xx in eff_w..tile {
192 let rx = if eff_w == 0 {
193 0
194 } else {
195 eff_w - 1 - ((xx - eff_w) % eff_w)
196 };
197 let p = *out_img.get_pixel(rx, yy);
198 let m = *out_msk.get_pixel(rx, yy);
199 out_img.put_pixel(xx, yy, p);
200 out_msk.put_pixel(xx, yy, m);
201 }
202 }
203 for yy in eff_h..tile {
205 let sy = if eff_h == 0 {
206 0
207 } else {
208 eff_h - 1 - ((yy - eff_h) % eff_h)
209 };
210 for xx in 0..tile {
211 let p = *out_img.get_pixel(xx, sy);
212 let m = *out_msk.get_pixel(xx, sy);
213 out_img.put_pixel(xx, yy, p);
214 out_msk.put_pixel(xx, yy, m);
215 }
216 }
217
218 (out_img, out_msk)
219}
220
221fn make_tile_weights(w: u32, h: u32, overlap: u32) -> Vec<f32> {
224 use std::f32::consts::PI;
225 let mut weights = vec![1.0f32; (w * h) as usize];
226 let half = (overlap as f32) / 2.0;
227 if overlap == 0 {
228 return weights;
229 }
230
231 for y in 0..h {
232 for x in 0..w {
233 let dx = min(x, w - 1 - x) as f32;
234 let dy = min(y, h - 1 - y) as f32;
235 let d = dx.min(dy);
236 let wxy = if d >= half || half <= 1e-3 {
237 1.0
238 } else {
239 let t = (d / half).clamp(0.0, 1.0);
241 0.5 * (1.0 - (PI * (1.0 - t)).cos())
242 };
243 weights[(y * w + x) as usize] = wxy;
244 }
245 }
246 weights
247}
248
249impl Lama {
250 fn infer_tile_512(
253 &mut self,
254 tile_img: &image::RgbImage,
255 tile_mask: &image::GrayImage,
256 ) -> anyhow::Result<image::RgbImage> {
257 let (tw, th) = tile_img.dimensions();
258 let w = tw as usize;
260 let h = th as usize;
261
262 let mut image_data = ndarray::Array::zeros((1, 3, h, w));
263 for y in 0..th {
264 for x in 0..tw {
265 let p = tile_img.get_pixel(x, y);
266 let fx = x as usize;
267 let fy = y as usize;
268 image_data[[0, 0, fy, fx]] = (p[0] as f32) / 255.0;
269 image_data[[0, 1, fy, fx]] = (p[1] as f32) / 255.0;
270 image_data[[0, 2, fy, fx]] = (p[2] as f32) / 255.0;
271 }
272 }
273
274 let mut mask_data = ndarray::Array::zeros((1, 1, h, w));
275 for y in 0..th {
276 for x in 0..tw {
277 let m = tile_mask.get_pixel(x, y)[0];
278 let fx = x as usize;
279 let fy = y as usize;
280 mask_data[[0, 0, fy, fx]] = if m > 0 { 1.0f32 } else { 0.0f32 };
281 }
282 }
283
284 let inputs = inputs![
285 "image" => TensorRef::from_array_view(image_data.view())?,
286 "mask" => TensorRef::from_array_view(mask_data.view())?,
287 ];
288 let outputs = self.model.run(inputs)?;
289 let output = outputs["output"].try_extract_array::<f32>()?;
290 let output = output.view();
291
292 let mut out_img = image::RgbImage::new(tw, th);
293 for y in 0..th {
294 for x in 0..tw {
295 let r = (output[[0, 0, y as usize, x as usize]] * 255.0).clamp(0.0, 255.0) as u8;
296 let g = (output[[0, 1, y as usize, x as usize]] * 255.0).clamp(0.0, 255.0) as u8;
297 let b = (output[[0, 2, y as usize, x as usize]] * 255.0).clamp(0.0, 255.0) as u8;
298 out_img.put_pixel(x, y, image::Rgb([r, g, b]));
299 }
300 }
301 Ok(out_img)
302 }
303}