Skip to main content

ultralytics_inference/
task.rs

1// Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
3//! Task definitions for YOLO models.
4//!
5//! This module defines the different tasks that YOLO models can perform,
6//! along with their associated capabilities and string representations.
7
8use std::fmt;
9use std::str::FromStr;
10
11/// YOLO model task types.
12///
13/// Each task type corresponds to a different computer vision problem
14/// that YOLO models can solve. The task type determines the expected
15/// model outputs and post-processing steps.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
17pub enum Task {
18    /// Object detection.
19    /// Predicts bounding boxes and class labels for objects in an image.
20    #[default]
21    Detect,
22    /// Instance segmentation.
23    /// Predicts bounding boxes, class labels, and pixel-level masks for objects.
24    Segment,
25    /// Pose estimation.
26    /// Predicts bounding boxes and skeletal keypoints for objects (e.g., humans).
27    Pose,
28    /// Image classification.
29    /// Predicts class probabilities for the entire image (no localization).
30    Classify,
31    /// Oriented bounding box detection (OBB).
32    /// Predicts rotated bounding boxes for objects, useful for aerial imagery etc.
33    Obb,
34    /// Semantic segmentation.
35    /// Assigns a class label to every pixel in the image.
36    Semantic,
37    /// Monocular depth estimation.
38    /// Predicts a per-pixel depth map (in meters) for the whole image.
39    Depth,
40}
41
42impl Task {
43    /// Get the string representation used in ONNX model metadata
44    /// (e.g. `"detect"`, `"segment"`).
45    #[must_use]
46    pub const fn as_str(&self) -> &'static str {
47        match self {
48            Self::Detect => "detect",
49            Self::Segment => "segment",
50            Self::Pose => "pose",
51            Self::Classify => "classify",
52            Self::Obb => "obb",
53            Self::Semantic => "semantic",
54            Self::Depth => "depth",
55        }
56    }
57
58    /// ONNX filename suffix for this task, used to construct `{family}n{suffix}.onnx`
59    /// (e.g. `yolo26n-seg.onnx`, `yolo11n-pose.onnx`, `yolov8n.onnx`).
60    ///
61    /// ```
62    /// use ultralytics_inference::Task;
63    /// assert_eq!(Task::Detect.model_suffix(), "");
64    /// assert_eq!(Task::Segment.model_suffix(), "-seg");
65    /// ```
66    #[must_use]
67    pub const fn model_suffix(&self) -> &'static str {
68        match self {
69            Self::Detect => "",
70            Self::Segment => "-seg",
71            Self::Pose => "-pose",
72            Self::Classify => "-cls",
73            Self::Obb => "-obb",
74            Self::Semantic => "-sem",
75            Self::Depth => "-depth",
76        }
77    }
78
79    /// Default nano `YOLO26` model filename for this task.
80    ///
81    /// Used by the CLI to auto-pick a model when `--model` is omitted but `--task` is set.
82    /// `YOLO26`, `YOLO11`, and `YOLOv8` variants are all auto-downloadable.
83    ///
84    /// ```
85    /// use ultralytics_inference::Task;
86    /// assert_eq!(Task::Detect.default_model(), "yolo26n.onnx");
87    /// assert_eq!(Task::Segment.default_model(), "yolo26n-seg.onnx");
88    /// ```
89    #[must_use]
90    pub fn default_model(&self) -> String {
91        format!("yolo26n{}.onnx", self.model_suffix())
92    }
93}
94
95impl fmt::Display for Task {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        f.write_str(self.as_str())
98    }
99}
100
101impl FromStr for Task {
102    type Err = TaskParseError;
103
104    fn from_str(s: &str) -> Result<Self, Self::Err> {
105        match s.to_lowercase().as_str() {
106            "detect" | "detection" => Ok(Self::Detect),
107            "segment" | "segmentation" => Ok(Self::Segment),
108            "pose" | "keypoint" | "keypoints" => Ok(Self::Pose),
109            "classify" | "classification" | "cls" => Ok(Self::Classify),
110            "obb" | "oriented" => Ok(Self::Obb),
111            "semantic" | "semantic_segmentation" | "semseg" => Ok(Self::Semantic),
112            "depth" | "depth_estimation" => Ok(Self::Depth),
113            _ => Err(TaskParseError(s.to_string())),
114        }
115    }
116}
117
118/// Error returned when parsing an invalid task string.
119#[derive(Debug, Clone)]
120pub struct TaskParseError(String);
121
122impl fmt::Display for TaskParseError {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        write!(
125            f,
126            "invalid task '{}', expected one of: detect, segment, pose, classify, obb, semantic, depth",
127            self.0
128        )
129    }
130}
131
132impl std::error::Error for TaskParseError {}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_task_from_str() {
140        assert_eq!("detect".parse::<Task>().unwrap(), Task::Detect);
141        assert_eq!("segment".parse::<Task>().unwrap(), Task::Segment);
142        assert_eq!("pose".parse::<Task>().unwrap(), Task::Pose);
143        assert_eq!("classify".parse::<Task>().unwrap(), Task::Classify);
144        assert_eq!("obb".parse::<Task>().unwrap(), Task::Obb);
145
146        assert_eq!("semantic".parse::<Task>().unwrap(), Task::Semantic);
147        assert_eq!("depth".parse::<Task>().unwrap(), Task::Depth);
148
149        // Alternative names
150        assert_eq!("detection".parse::<Task>().unwrap(), Task::Detect);
151        assert_eq!("segmentation".parse::<Task>().unwrap(), Task::Segment);
152        assert_eq!("keypoints".parse::<Task>().unwrap(), Task::Pose);
153        assert_eq!("cls".parse::<Task>().unwrap(), Task::Classify);
154        assert_eq!(
155            "semantic_segmentation".parse::<Task>().unwrap(),
156            Task::Semantic
157        );
158        assert_eq!("depth_estimation".parse::<Task>().unwrap(), Task::Depth);
159    }
160
161    #[test]
162    fn test_task_display() {
163        assert_eq!(Task::Detect.to_string(), "detect");
164        assert_eq!(Task::Segment.to_string(), "segment");
165        assert_eq!(Task::Semantic.to_string(), "semantic");
166    }
167
168    #[test]
169    fn test_task_suffix_and_default_model() {
170        let cases = [
171            (Task::Detect, "detect", "", "yolo26n.onnx"),
172            (Task::Segment, "segment", "-seg", "yolo26n-seg.onnx"),
173            (Task::Pose, "pose", "-pose", "yolo26n-pose.onnx"),
174            (Task::Classify, "classify", "-cls", "yolo26n-cls.onnx"),
175            (Task::Obb, "obb", "-obb", "yolo26n-obb.onnx"),
176            (Task::Semantic, "semantic", "-sem", "yolo26n-sem.onnx"),
177            (Task::Depth, "depth", "-depth", "yolo26n-depth.onnx"),
178        ];
179        for (task, name, suffix, model) in cases {
180            assert_eq!(task.as_str(), name);
181            assert_eq!(task.model_suffix(), suffix);
182            assert_eq!(task.default_model(), model);
183        }
184    }
185
186    #[test]
187    fn test_task_from_str_aliases_and_errors() {
188        assert_eq!("KEYPOINT".parse::<Task>().unwrap(), Task::Pose);
189        assert_eq!("oriented".parse::<Task>().unwrap(), Task::Obb);
190        assert_eq!("semseg".parse::<Task>().unwrap(), Task::Semantic);
191        assert_eq!("Classification".parse::<Task>().unwrap(), Task::Classify);
192        assert!("not_a_task".parse::<Task>().is_err());
193    }
194}