1use std::fmt;
9use std::str::FromStr;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
17pub enum Task {
18 #[default]
21 Detect,
22 Segment,
25 Pose,
28 Classify,
31 Obb,
34 Semantic,
37}
38
39impl Task {
40 #[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 #[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 #[must_use]
85 pub fn default_model(&self) -> String {
86 format!("yolo26n{}.onnx", self.model_suffix())
87 }
88
89 #[must_use]
91 pub const fn has_boxes(&self) -> bool {
92 matches!(self, Self::Detect | Self::Segment | Self::Pose | Self::Obb)
93 }
94
95 #[must_use]
97 pub const fn has_masks(&self) -> bool {
98 matches!(self, Self::Segment)
99 }
100
101 #[must_use]
103 pub const fn has_keypoints(&self) -> bool {
104 matches!(self, Self::Pose)
105 }
106
107 #[must_use]
109 pub const fn has_probs(&self) -> bool {
110 matches!(self, Self::Classify)
111 }
112
113 #[must_use]
115 pub const fn has_obb(&self) -> bool {
116 matches!(self, Self::Obb)
117 }
118
119 #[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#[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 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}