use std::fmt;
use std::str::FromStr;
pub(crate) fn handle_deprecated_precision(
quantize: Option<Quantization>,
half: Option<bool>,
) -> Option<Quantization> {
if quantize.is_some() {
return quantize;
}
half.map_or(quantize, |enabled| {
crate::warn!(
"'half' is deprecated and will be removed in the future. Use 'quantize' instead."
);
enabled.then_some(Quantization::Fp16)
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Quantization {
Int8,
Fp16,
Fp32,
W8a16,
W8a32,
}
impl Quantization {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Int8 => "8",
Self::Fp16 => "16",
Self::Fp32 => "32",
Self::W8a16 => "w8a16",
Self::W8a32 => "w8a32",
}
}
}
impl fmt::Display for Quantization {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Quantization {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value.to_ascii_lowercase().as_str() {
"8" | "int8" | "w8a8" => Ok(Self::Int8),
"16" | "fp16" | "w16a16" => Ok(Self::Fp16),
"32" | "fp32" | "w32a32" => Ok(Self::Fp32),
"w8a16" => Ok(Self::W8a16),
"w8a32" => Ok(Self::W8a32),
_ => Err(format!(
"'quantize={value}' is invalid. Valid 'quantize' values are 8, 16, 32, \
'int8', 'fp16', 'fp32', 'w8a8', 'w16a16', 'w8a16', or 'w8a32'. \
See https://docs.ultralytics.com/modes/export#quantization-options"
)),
}
}
}
#[derive(Debug, Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct InferenceConfig {
pub confidence_threshold: f32,
pub iou_threshold: f32,
pub max_det: usize,
pub imgsz: Option<(usize, usize)>,
pub batch: Option<usize>,
pub num_threads: usize,
pub quantize: Option<Quantization>,
#[doc(hidden)]
pub half: bool,
pub device: Option<crate::Device>,
pub save: bool,
pub save_frames: bool,
pub rect: bool,
pub classes: Option<Vec<usize>>,
pub cuda_preprocess: bool,
}
impl Default for InferenceConfig {
fn default() -> Self {
Self {
confidence_threshold: Self::DEFAULT_CONF,
iou_threshold: Self::DEFAULT_IOU,
max_det: Self::DEFAULT_MAX_DET,
imgsz: None,
batch: None,
num_threads: 0, quantize: Self::DEFAULT_QUANTIZE,
half: Self::DEFAULT_HALF,
device: None,
save: Self::DEFAULT_SAVE,
save_frames: Self::DEFAULT_SAVE_FRAMES,
rect: Self::DEFAULT_RECT,
classes: None,
cuda_preprocess: Self::DEFAULT_CUDA_PREPROCESS,
}
}
}
impl InferenceConfig {
pub const DEFAULT_CONF: f32 = 0.25;
pub const DEFAULT_IOU: f32 = 0.7;
pub const DEFAULT_MAX_DET: usize = 300;
pub const DEFAULT_QUANTIZE: Option<Quantization> = None;
#[doc(hidden)]
pub const DEFAULT_HALF: bool = false;
pub const DEFAULT_SAVE: bool = true;
pub const DEFAULT_SAVE_FRAMES: bool = false;
pub const DEFAULT_RECT: bool = true;
pub const DEFAULT_IMGSZ: (usize, usize) = (640, 640);
pub const DEFAULT_OBB_IMGSZ: (usize, usize) = (1024, 1024);
pub const DEFAULT_CUDA_PREPROCESS: bool = true;
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn with_batch(mut self, batch: usize) -> Self {
self.batch = Some(batch);
self
}
#[must_use]
pub const fn with_confidence(mut self, threshold: f32) -> Self {
self.confidence_threshold = threshold;
self
}
#[must_use]
pub const fn with_iou(mut self, threshold: f32) -> Self {
self.iou_threshold = threshold;
self
}
#[must_use]
pub const fn with_max_det(mut self, max: usize) -> Self {
self.max_det = max;
self
}
#[must_use]
pub const fn with_imgsz(mut self, height: usize, width: usize) -> Self {
self.imgsz = Some((height, width));
self
}
#[must_use]
pub const fn with_threads(mut self, threads: usize) -> Self {
self.num_threads = threads;
self
}
#[must_use]
pub const fn with_quantize(mut self, quantize: Quantization) -> Self {
self.quantize = Some(quantize);
self
}
#[doc(hidden)]
#[must_use]
pub const fn with_half(mut self, half: bool) -> Self {
self.half = half;
self
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn normalize_precision(&mut self) {
self.quantize = handle_deprecated_precision(self.quantize, self.half.then_some(true));
self.half = false;
}
#[must_use]
pub const fn with_cuda_preprocess(mut self, enabled: bool) -> Self {
self.cuda_preprocess = enabled;
self
}
#[must_use]
pub const fn with_device(mut self, device: crate::Device) -> Self {
self.device = Some(device);
self
}
#[must_use]
pub const fn with_save(mut self, save: bool) -> Self {
self.save = save;
self
}
#[must_use]
pub const fn with_save_frames(mut self, save_frames: bool) -> Self {
self.save_frames = save_frames;
self
}
#[must_use]
pub const fn with_rect(mut self, rect: bool) -> Self {
self.rect = rect;
self
}
#[must_use]
pub fn with_classes(mut self, classes: Vec<usize>) -> Self {
self.classes = Some(classes);
self
}
#[must_use]
pub fn keep_class(&self, class_id: usize) -> bool {
self.classes.as_ref().is_none_or(|c| c.contains(&class_id))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_default() {
let config = InferenceConfig::default();
assert!((config.confidence_threshold - InferenceConfig::DEFAULT_CONF).abs() < f32::EPSILON);
assert!((config.iou_threshold - InferenceConfig::DEFAULT_IOU).abs() < f32::EPSILON);
assert_eq!(config.max_det, 300);
}
#[test]
fn test_config_builder() {
let config = InferenceConfig::new()
.with_confidence(0.5)
.with_iou(0.6)
.with_max_det(300)
.with_imgsz(640, 640)
.with_threads(8);
assert!((config.confidence_threshold - 0.5).abs() < f32::EPSILON);
assert!((config.iou_threshold - 0.6).abs() < f32::EPSILON);
assert_eq!(config.max_det, 300);
assert_eq!(config.imgsz, Some((640, 640)));
assert_eq!(config.num_threads, 8);
}
#[test]
fn test_keep_class() {
let config = InferenceConfig::default();
assert!(config.keep_class(0));
assert!(config.keep_class(100));
let config_filtered = InferenceConfig::new().with_classes(vec![1, 3]);
assert!(config_filtered.keep_class(1));
assert!(config_filtered.keep_class(3));
assert!(!config_filtered.keep_class(0));
assert!(!config_filtered.keep_class(2));
}
#[test]
fn test_remaining_builders() {
let config = InferenceConfig::new()
.with_batch(4)
.with_quantize(Quantization::Fp16)
.with_cuda_preprocess(false)
.with_device(crate::Device::Cpu)
.with_save(false)
.with_save_frames(true)
.with_rect(false);
assert_eq!(config.batch, Some(4));
assert_eq!(config.quantize, Some(Quantization::Fp16));
assert!(!config.cuda_preprocess);
assert_eq!(config.device, Some(crate::Device::Cpu));
assert!(!config.save);
assert!(config.save_frames);
assert!(!config.rect);
}
#[test]
fn test_default_constants() {
let c = InferenceConfig::default();
assert_eq!(c.max_det, InferenceConfig::DEFAULT_MAX_DET);
assert_eq!(c.save, InferenceConfig::DEFAULT_SAVE);
assert_eq!(c.rect, InferenceConfig::DEFAULT_RECT);
assert!(c.batch.is_none());
assert!(c.device.is_none());
assert!(c.classes.is_none());
assert_eq!(c.quantize, InferenceConfig::DEFAULT_QUANTIZE);
}
#[test]
fn test_quantization_aliases() {
for (value, expected) in [
("8", Quantization::Int8),
("int8", Quantization::Int8),
("w8a8", Quantization::Int8),
("16", Quantization::Fp16),
("fp16", Quantization::Fp16),
("w16a16", Quantization::Fp16),
("32", Quantization::Fp32),
("fp32", Quantization::Fp32),
("w32a32", Quantization::Fp32),
("w8a16", Quantization::W8a16),
("W8A32", Quantization::W8a32),
] {
assert_eq!(value.parse::<Quantization>().unwrap(), expected);
}
assert_eq!(Quantization::Int8.to_string(), "8");
assert!("4".parse::<Quantization>().is_err());
}
#[test]
fn test_deprecated_half_mapping() {
let mut config = InferenceConfig::new().with_half(true);
config.normalize_precision();
assert_eq!(config.quantize, Some(Quantization::Fp16));
assert!(!config.half);
let mut config = InferenceConfig::new()
.with_half(true)
.with_quantize(Quantization::Fp32);
config.normalize_precision();
assert_eq!(config.quantize, Some(Quantization::Fp32));
assert!(!config.half);
}
}