#![cfg(feature = "image")]
use offgrid::*;
fn params() -> Option<ImageModelParams> {
let var = |k: &str| std::env::var(k).ok().map(Into::into);
Some(ImageModelParams {
diffusion_model: Some(var("OFFGRID_TEST_IMAGE_MODEL")?),
vae: Some(var("OFFGRID_TEST_IMAGE_VAE")?),
text_encoder: Some(var("OFFGRID_TEST_IMAGE_TEXT_ENCODER")?),
..Default::default()
})
}
fn request(prompt: &str) -> ImageRequest {
ImageRequest {
prompt: prompt.into(),
width: 512,
height: 512,
steps: Some(8),
cfg_scale: Some(1.0),
seed: Some(42),
..Default::default()
}
}
#[test]
fn generates_png_deterministically_and_cancels() {
let Some(params) = params() else { return };
let mut model = ImageModel::load(¶ms).expect("image model loads");
let mut events = Vec::new();
let img = model
.generate(&request("A red apple on a white table"), |p| {
events.push(p);
true
})
.unwrap();
assert_eq!((img.width, img.height, img.rgb.len()), (512, 512, 512 * 512 * 3));
let sampled: Vec<u32> = events
.iter()
.filter_map(|p| if let ImageProgress::Sampling { step, .. } = p { Some(*step) } else { None })
.collect();
assert_eq!(sampled.last(), Some(&8), "{events:?}");
assert!(events.iter().any(|p| matches!(p, ImageProgress::Loading { .. })), "{events:?}");
let last_sampling = events.iter().rposition(|p| matches!(p, ImageProgress::Sampling { .. })).unwrap();
assert!(events[last_sampling + 1..].iter().all(|p| matches!(p, ImageProgress::Decoding { .. })), "{events:?}");
let distinct: std::collections::HashSet<&[u8]> = img.rgb.as_chunks::<3>().0.iter().map(|px| &px[..]).collect();
assert!(distinct.len() > 1000, "{} distinct colours", distinct.len());
let png = img.to_png();
let decoder = png::Decoder::new(std::io::Cursor::new(&png));
let reader = decoder.read_info().unwrap();
assert_eq!((reader.info().width, reader.info().height), (512, 512));
let again = model.generate(&request("A red apple on a white table"), |_| true).unwrap();
assert_eq!(again, img, "same seed, same image");
let err = model
.generate(&request("A blue boat"), |p| !matches!(p, ImageProgress::Sampling { step: 2.., .. }))
.unwrap_err();
assert!(matches!(err, Error::Cancelled), "{err}");
let after = model.generate(&request("A blue boat"), |_| true).unwrap();
assert_eq!(after.rgb.len(), 512 * 512 * 3, "model still works after a cancel");
let bad = ImageRequest { width: 500, ..request("x") };
assert!(matches!(model.generate(&bad, |_| true), Err(Error::InvalidArgument(_))));
}
#[test]
fn generates_while_chatting() {
let (Some(params), Ok(chat_path)) = (params(), std::env::var("OFFGRID_TEST_MODEL")) else { return };
let chat_model = Model::load(chat_path, &ModelParams::default()).unwrap();
let mut chat = ChatSession::new(chat_model, &SessionParams { n_ctx: 2048, ..Default::default() }).unwrap();
let mut model = ImageModel::load(¶ms).unwrap();
let painter = std::thread::spawn(move || model.generate(&request("A lighthouse at dusk"), |_| true));
let req = ChatRequest {
max_tokens: Some(20),
sampling: SamplingParams { temperature: 0.0, ..Default::default() },
..ChatRequest::new(vec![ChatMessage::user("What is the capital of France? One word.")])
};
let answer = chat.complete(&req, |_| true).unwrap();
assert!(answer.content.contains("Paris"), "{}", answer.content);
let img = painter.join().unwrap().unwrap();
assert_eq!(img.rgb.len(), 512 * 512 * 3);
}
#[test]
fn refines_an_init_image() {
let Some(params) = params() else { return };
let mut model = ImageModel::load(¶ms).unwrap();
let base = model.generate(&request("A red apple on a white table"), |_| true).unwrap();
assert_eq!(Image::from_png(&base.to_png()).unwrap(), base, "PNG round trip");
let refine = ImageRequest {
init_image: Some(base.clone()),
strength: Some(0.6),
width: 1024, ..request("A green apple on a white table")
};
let json = serde_json::to_string(&refine).unwrap();
let back: ImageRequest = serde_json::from_str(&json).unwrap();
assert_eq!(back.init_image.as_ref(), Some(&base), "base64 PNG in JSON");
let refined = model.generate(&refine, |_| true).unwrap();
let fresh = model.generate(&request("A green apple on a white table"), |_| true).unwrap();
assert_eq!((refined.width, refined.height), (512, 512));
let diff = |a: &Image, b: &Image| {
a.rgb.iter().zip(&b.rgb).map(|(&x, &y)| (x as i32 - y as i32).unsigned_abs() as u64).sum::<u64>() as f64
/ a.rgb.len() as f64
};
let (kept, new) = (diff(&refined, &base), diff(&fresh, &base));
println!("mean abs diff to the start image: refined {kept:.1}, fresh {new:.1}");
assert!(kept < new * 0.7, "refined {kept:.1} vs fresh {new:.1}");
}
#[test]
fn text_encoder_on_cpu_and_vae_tiling() {
let Some(params) = params() else { return };
let base = ImageModel::load(¶ms).unwrap().generate(&request("A red apple on a white table"), |_| true).unwrap();
let tight = ImageModelParams { text_encoder_on_cpu: true, vae_tiling: true, ..params };
let img = ImageModel::load(&tight).unwrap().generate(&request("A red apple on a white table"), |_| true).unwrap();
assert_eq!((img.width, img.height), (512, 512));
let diff = img.rgb.iter().zip(&base.rgb).map(|(&a, &b)| (a as i32 - b as i32).unsigned_abs() as u64).sum::<u64>()
as f64
/ img.rgb.len() as f64;
println!("mean abs diff to the default setup: {diff:.2}");
assert!(diff < 8.0, "{diff}");
}