use crate::{
Answer, AnswerValue, Boolean, BoxQuestion, Error, ErrorKind, Provider, Region, Request, Result,
ask,
};
use indexmap::IndexMap;
#[derive(Clone, Debug)]
pub struct LocateOptions {
pub where_: String,
pub min_presence: f64,
pub min_box_confidence: f64,
}
impl Default for LocateOptions {
fn default() -> Self {
Self {
where_: "the image".into(),
min_presence: 0.5,
min_box_confidence: 0.0,
}
}
}
#[derive(Clone, Debug)]
pub struct Located {
pub found: Option<bool>,
pub region: Option<Region>,
pub presence: Answer<AnswerValue>,
pub box_answer: Answer<AnswerValue>,
}
pub async fn locate(
provider: &dyn Provider,
request: &Request,
target: &str,
options: LocateOptions,
) -> Result<Located> {
if crate::question::is_blank(target) {
return Err(Error::new(
ErrorKind::InvalidArgument,
"target must be a non-empty description",
));
}
let presence = Boolean::new(format!("Is there {target} in {}?", options.where_))?
.with_min_confidence(options.min_presence)?;
let region = BoxQuestion::new(format!("{target} in {}", options.where_))?
.with_min_confidence(options.min_box_confidence)?;
let questions = IndexMap::from([
("present".into(), presence.into()),
("box".into(), region.into()),
]);
let request = request.with_questions(questions)?;
let mut answers = ask(provider, &request).await?;
let presence = answers
.shift_remove("present")
.ok_or_else(|| Error::new(ErrorKind::InvalidResponse, "missing presence answer"))?;
let box_answer = answers
.shift_remove("box")
.ok_or_else(|| Error::new(ErrorKind::InvalidResponse, "missing box answer"))?;
let found = match presence.value() {
Some(AnswerValue::Boolean(value)) => Some(*value),
_ => None,
};
let region = if found == Some(true) {
match box_answer.value() {
Some(AnswerValue::Box(region)) => Some(*region),
_ => None,
}
} else {
None
};
Ok(Located {
found,
region,
presence,
box_answer,
})
}