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}
38
39impl Task {
40    /// Get the string representation used in ONNX model metadata
41    /// (e.g. `"detect"`, `"segment"`).
42    #[must_use]
43    pub const fn as_str(&self) -> &'static str {
44        match self {
45            Self::Detect => "detect",
46            Self::Segment => "segment",
47            Self::Pose => "pose",
48            Self::Classify => "classify",
49            Self::Obb => "obb",
50            Self::Semantic => "semantic",
51        }
52    }
53
54    /// ONNX filename suffix for this task, used to construct `{family}n{suffix}.onnx`
55    /// (e.g. `yolo26n-seg.onnx`, `yolo11n-pose.onnx`, `yolov8n.onnx`).
56    ///
57    /// ```
58    /// use ultralytics_inference::Task;
59    /// assert_eq!(Task::Detect.model_suffix(), "");
60    /// assert_eq!(Task::Segment.model_suffix(), "-seg");
61    /// ```
62    #[must_use]
63    pub const fn model_suffix(&self) -> &'static str {
64        match self {
65            Self::Detect => "",
66            Self::Segment => "-seg",
67            Self::Pose => "-pose",
68            Self::Classify => "-cls",
69            Self::Obb => "-obb",
70            Self::Semantic => "-sem",
71        }
72    }
73
74    /// Default nano `YOLO26` model filename for this task.
75    ///
76    /// Used by the CLI to auto-pick a model when `--model` is omitted but `--task` is set.
77    /// `YOLO26`, `YOLO11`, and `YOLOv8` variants are all auto-downloadable.
78    ///
79    /// ```
80    /// use ultralytics_inference::Task;
81    /// assert_eq!(Task::Detect.default_model(), "yolo26n.onnx");
82    /// assert_eq!(Task::Segment.default_model(), "yolo26n-seg.onnx");
83    /// ```
84    #[must_use]
85    pub fn default_model(&self) -> String {
86        format!("yolo26n{}.onnx", self.model_suffix())
87    }
88
89    /// Returns `true` when the task outputs bounding boxes: Detect, Segment, Pose, and Obb.
90    #[must_use]
91    pub const fn has_boxes(&self) -> bool {
92        matches!(self, Self::Detect | Self::Segment | Self::Pose | Self::Obb)
93    }
94
95    /// Returns `true` only for the Segment task, which outputs per-instance segmentation masks.
96    #[must_use]
97    pub const fn has_masks(&self) -> bool {
98        matches!(self, Self::Segment)
99    }
100
101    /// Returns `true` only for the Pose task, which outputs skeletal keypoints.
102    #[must_use]
103    pub const fn has_keypoints(&self) -> bool {
104        matches!(self, Self::Pose)
105    }
106
107    /// Returns `true` only for the Classify task, which outputs global class probabilities.
108    #[must_use]
109    pub const fn has_probs(&self) -> bool {
110        matches!(self, Self::Classify)
111    }
112
113    /// Returns `true` only for the Obb task, which outputs oriented (rotated) bounding boxes.
114    #[must_use]
115    pub const fn has_obb(&self) -> bool {
116        matches!(self, Self::Obb)
117    }
118
119    /// Returns `true` only for the `Semantic` task, which outputs a per-pixel class label map.
120    #[must_use]
121    pub const fn has_semantic_mask(&self) -> bool {
122        matches!(self, Self::Semantic)
123    }
124}
125
126impl fmt::Display for Task {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        f.write_str(self.as_str())
129    }
130}
131
132impl FromStr for Task {
133    type Err = TaskParseError;
134
135    fn from_str(s: &str) -> Result<Self, Self::Err> {
136        match s.to_lowercase().as_str() {
137            "detect" | "detection" => Ok(Self::Detect),
138            "segment" | "segmentation" => Ok(Self::Segment),
139            "pose" | "keypoint" | "keypoints" => Ok(Self::Pose),
140            "classify" | "classification" | "cls" => Ok(Self::Classify),
141            "obb" | "oriented" => Ok(Self::Obb),
142            "semantic" | "semantic_segmentation" | "semseg" => Ok(Self::Semantic),
143            _ => Err(TaskParseError(s.to_string())),
144        }
145    }
146}
147
148/// Error returned when parsing an invalid task string.
149#[derive(Debug, Clone)]
150pub struct TaskParseError(String);
151
152impl fmt::Display for TaskParseError {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        write!(
155            f,
156            "invalid task '{}', expected one of: detect, segment, pose, classify, obb, semantic",
157            self.0
158        )
159    }
160}
161
162impl std::error::Error for TaskParseError {}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_task_from_str() {
170        assert_eq!("detect".parse::<Task>().unwrap(), Task::Detect);
171        assert_eq!("segment".parse::<Task>().unwrap(), Task::Segment);
172        assert_eq!("pose".parse::<Task>().unwrap(), Task::Pose);
173        assert_eq!("classify".parse::<Task>().unwrap(), Task::Classify);
174        assert_eq!("obb".parse::<Task>().unwrap(), Task::Obb);
175
176        assert_eq!("semantic".parse::<Task>().unwrap(), Task::Semantic);
177
178        // Alternative names
179        assert_eq!("detection".parse::<Task>().unwrap(), Task::Detect);
180        assert_eq!("segmentation".parse::<Task>().unwrap(), Task::Segment);
181        assert_eq!("keypoints".parse::<Task>().unwrap(), Task::Pose);
182        assert_eq!("cls".parse::<Task>().unwrap(), Task::Classify);
183        assert_eq!(
184            "semantic_segmentation".parse::<Task>().unwrap(),
185            Task::Semantic
186        );
187    }
188
189    #[test]
190    fn test_task_display() {
191        assert_eq!(Task::Detect.to_string(), "detect");
192        assert_eq!(Task::Segment.to_string(), "segment");
193        assert_eq!(Task::Semantic.to_string(), "semantic");
194    }
195
196    #[test]
197    fn test_task_capabilities() {
198        assert!(Task::Detect.has_boxes());
199        assert!(!Task::Detect.has_masks());
200        assert!(Task::Segment.has_masks());
201        assert!(Task::Pose.has_keypoints());
202        assert!(Task::Classify.has_probs());
203        assert!(Task::Obb.has_obb());
204        assert!(Task::Semantic.has_semantic_mask());
205        assert!(!Task::Detect.has_semantic_mask());
206    }
207
208    #[test]
209    fn test_task_suffix_and_default_model() {
210        let cases = [
211            (Task::Detect, "detect", "", "yolo26n.onnx"),
212            (Task::Segment, "segment", "-seg", "yolo26n-seg.onnx"),
213            (Task::Pose, "pose", "-pose", "yolo26n-pose.onnx"),
214            (Task::Classify, "classify", "-cls", "yolo26n-cls.onnx"),
215            (Task::Obb, "obb", "-obb", "yolo26n-obb.onnx"),
216            (Task::Semantic, "semantic", "-sem", "yolo26n-sem.onnx"),
217        ];
218        for (task, name, suffix, model) in cases {
219            assert_eq!(task.as_str(), name);
220            assert_eq!(task.model_suffix(), suffix);
221            assert_eq!(task.default_model(), model);
222        }
223    }
224
225    #[test]
226    fn test_task_from_str_aliases_and_errors() {
227        assert_eq!("KEYPOINT".parse::<Task>().unwrap(), Task::Pose);
228        assert_eq!("oriented".parse::<Task>().unwrap(), Task::Obb);
229        assert_eq!("semseg".parse::<Task>().unwrap(), Task::Semantic);
230        assert_eq!("Classification".parse::<Task>().unwrap(), Task::Classify);
231        assert!("not_a_task".parse::<Task>().is_err());
232    }
233}