results.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. # Ultralytics YOLO 🚀, AGPL-3.0 license
  2. """
  3. Ultralytics Results, Boxes and Masks classes for handling inference results
  4. Usage: See https://docs.ultralytics.com/modes/predict/
  5. """
  6. from copy import deepcopy
  7. from functools import lru_cache
  8. from pathlib import Path
  9. import numpy as np
  10. import torch
  11. from ultralytics.data.augment import LetterBox
  12. from ultralytics.utils import LOGGER, SimpleClass, deprecation_warn, ops
  13. from ultralytics.utils.plotting import Annotator, colors, save_one_box
  14. class BaseTensor(SimpleClass):
  15. """
  16. Base tensor class with additional methods for easy manipulation and device handling.
  17. """
  18. def __init__(self, data, orig_shape) -> None:
  19. """Initialize BaseTensor with data and original shape.
  20. Args:
  21. data (torch.Tensor | np.ndarray): Predictions, such as bboxes, masks and keypoints.
  22. orig_shape (tuple): Original shape of image.
  23. """
  24. assert isinstance(data, (torch.Tensor, np.ndarray))
  25. self.data = data
  26. self.orig_shape = orig_shape
  27. @property
  28. def shape(self):
  29. """Return the shape of the data tensor."""
  30. return self.data.shape
  31. def cpu(self):
  32. """Return a copy of the tensor on CPU memory."""
  33. return self if isinstance(self.data, np.ndarray) else self.__class__(self.data.cpu(), self.orig_shape)
  34. def numpy(self):
  35. """Return a copy of the tensor as a numpy array."""
  36. return self if isinstance(self.data, np.ndarray) else self.__class__(self.data.numpy(), self.orig_shape)
  37. def cuda(self):
  38. """Return a copy of the tensor on GPU memory."""
  39. return self.__class__(torch.as_tensor(self.data).cuda(), self.orig_shape)
  40. def to(self, *args, **kwargs):
  41. """Return a copy of the tensor with the specified device and dtype."""
  42. return self.__class__(torch.as_tensor(self.data).to(*args, **kwargs), self.orig_shape)
  43. def __len__(self): # override len(results)
  44. """Return the length of the data tensor."""
  45. return len(self.data)
  46. def __getitem__(self, idx):
  47. """Return a BaseTensor with the specified index of the data tensor."""
  48. return self.__class__(self.data[idx], self.orig_shape)
  49. class Results(SimpleClass):
  50. """
  51. A class for storing and manipulating inference results.
  52. Args:
  53. orig_img (numpy.ndarray): The original image as a numpy array.
  54. path (str): The path to the image file.
  55. names (dict): A dictionary of class names.
  56. boxes (torch.tensor, optional): A 2D tensor of bounding box coordinates for each detection.
  57. masks (torch.tensor, optional): A 3D tensor of detection masks, where each mask is a binary image.
  58. probs (torch.tensor, optional): A 1D tensor of probabilities of each class for classification task.
  59. keypoints (List[List[float]], optional): A list of detected keypoints for each object.
  60. Attributes:
  61. orig_img (numpy.ndarray): The original image as a numpy array.
  62. orig_shape (tuple): The original image shape in (height, width) format.
  63. boxes (Boxes, optional): A Boxes object containing the detection bounding boxes.
  64. masks (Masks, optional): A Masks object containing the detection masks.
  65. probs (Probs, optional): A Probs object containing probabilities of each class for classification task.
  66. keypoints (Keypoints, optional): A Keypoints object containing detected keypoints for each object.
  67. speed (dict): A dictionary of preprocess, inference, and postprocess speeds in milliseconds per image.
  68. names (dict): A dictionary of class names.
  69. path (str): The path to the image file.
  70. _keys (tuple): A tuple of attribute names for non-empty attributes.
  71. """
  72. def __init__(self, orig_img, path, names, boxes=None, masks=None, probs=None, keypoints=None) -> None:
  73. """Initialize the Results class."""
  74. self.orig_img = orig_img
  75. self.orig_shape = orig_img.shape[:2]
  76. self.boxes = Boxes(boxes, self.orig_shape) if boxes is not None else None # native size boxes
  77. self.masks = Masks(masks, self.orig_shape) if masks is not None else None # native size or imgsz masks
  78. self.probs = Probs(probs) if probs is not None else None
  79. self.keypoints = Keypoints(keypoints, self.orig_shape) if keypoints is not None else None
  80. self.speed = {'preprocess': None, 'inference': None, 'postprocess': None} # milliseconds per image
  81. self.names = names
  82. self.path = path
  83. self.save_dir = None
  84. self._keys = ('boxes', 'masks', 'probs', 'keypoints')
  85. def __getitem__(self, idx):
  86. """Return a Results object for the specified index."""
  87. r = self.new()
  88. for k in self.keys:
  89. setattr(r, k, getattr(self, k)[idx])
  90. return r
  91. def __len__(self):
  92. """Return the number of detections in the Results object."""
  93. for k in self.keys:
  94. return len(getattr(self, k))
  95. def update(self, boxes=None, masks=None, probs=None):
  96. """Update the boxes, masks, and probs attributes of the Results object."""
  97. if boxes is not None:
  98. ops.clip_boxes(boxes, self.orig_shape) # clip boxes
  99. self.boxes = Boxes(boxes, self.orig_shape)
  100. if masks is not None:
  101. self.masks = Masks(masks, self.orig_shape)
  102. if probs is not None:
  103. self.probs = probs
  104. def cpu(self):
  105. """Return a copy of the Results object with all tensors on CPU memory."""
  106. r = self.new()
  107. for k in self.keys:
  108. setattr(r, k, getattr(self, k).cpu())
  109. return r
  110. def numpy(self):
  111. """Return a copy of the Results object with all tensors as numpy arrays."""
  112. r = self.new()
  113. for k in self.keys:
  114. setattr(r, k, getattr(self, k).numpy())
  115. return r
  116. def cuda(self):
  117. """Return a copy of the Results object with all tensors on GPU memory."""
  118. r = self.new()
  119. for k in self.keys:
  120. setattr(r, k, getattr(self, k).cuda())
  121. return r
  122. def to(self, *args, **kwargs):
  123. """Return a copy of the Results object with tensors on the specified device and dtype."""
  124. r = self.new()
  125. for k in self.keys:
  126. setattr(r, k, getattr(self, k).to(*args, **kwargs))
  127. return r
  128. def new(self):
  129. """Return a new Results object with the same image, path, and names."""
  130. return Results(orig_img=self.orig_img, path=self.path, names=self.names)
  131. @property
  132. def keys(self):
  133. """Return a list of non-empty attribute names."""
  134. return [k for k in self._keys if getattr(self, k) is not None]
  135. def plot(
  136. self,
  137. conf=True,
  138. line_width=None,
  139. font_size=None,
  140. font='Arial.ttf',
  141. pil=False,
  142. img=None,
  143. im_gpu=None,
  144. kpt_radius=5,
  145. kpt_line=True,
  146. labels=True,
  147. boxes=True,
  148. masks=True,
  149. probs=True,
  150. **kwargs # deprecated args TODO: remove support in 8.2
  151. ):
  152. """
  153. Plots the detection results on an input RGB image. Accepts a numpy array (cv2) or a PIL Image.
  154. Args:
  155. conf (bool): Whether to plot the detection confidence score.
  156. line_width (float, optional): The line width of the bounding boxes. If None, it is scaled to the image size.
  157. font_size (float, optional): The font size of the text. If None, it is scaled to the image size.
  158. font (str): The font to use for the text.
  159. pil (bool): Whether to return the image as a PIL Image.
  160. img (numpy.ndarray): Plot to another image. if not, plot to original image.
  161. im_gpu (torch.Tensor): Normalized image in gpu with shape (1, 3, 640, 640), for faster mask plotting.
  162. kpt_radius (int, optional): Radius of the drawn keypoints. Default is 5.
  163. kpt_line (bool): Whether to draw lines connecting keypoints.
  164. labels (bool): Whether to plot the label of bounding boxes.
  165. boxes (bool): Whether to plot the bounding boxes.
  166. masks (bool): Whether to plot the masks.
  167. probs (bool): Whether to plot classification probability
  168. Returns:
  169. (numpy.ndarray): A numpy array of the annotated image.
  170. Example:
  171. ```python
  172. from PIL import Image
  173. from ultralytics import YOLO
  174. model = YOLO('yolov8n.pt')
  175. results = model('bus.jpg') # results list
  176. for r in results:
  177. im_array = r.plot() # plot a BGR numpy array of predictions
  178. im = Image.fromarray(im_array[..., ::-1]) # RGB PIL image
  179. im.show() # show image
  180. im.save('results.jpg') # save image
  181. ```
  182. """
  183. if img is None and isinstance(self.orig_img, torch.Tensor):
  184. img = (self.orig_img[0].detach().permute(1, 2, 0).cpu().contiguous() * 255).to(torch.uint8).numpy()
  185. # Deprecation warn TODO: remove in 8.2
  186. if 'show_conf' in kwargs:
  187. deprecation_warn('show_conf', 'conf')
  188. conf = kwargs['show_conf']
  189. assert isinstance(conf, bool), '`show_conf` should be of boolean type, i.e, show_conf=True/False'
  190. if 'line_thickness' in kwargs:
  191. deprecation_warn('line_thickness', 'line_width')
  192. line_width = kwargs['line_thickness']
  193. assert isinstance(line_width, int), '`line_width` should be of int type, i.e, line_width=3'
  194. names = self.names
  195. pred_boxes, show_boxes = self.boxes, boxes
  196. pred_masks, show_masks = self.masks, masks
  197. pred_probs, show_probs = self.probs, probs
  198. annotator = Annotator(
  199. deepcopy(self.orig_img if img is None else img),
  200. line_width,
  201. font_size,
  202. font,
  203. pil or (pred_probs is not None and show_probs), # Classify tasks default to pil=True
  204. example=names)
  205. # Plot Segment results
  206. if pred_masks and show_masks:
  207. if im_gpu is None:
  208. img = LetterBox(pred_masks.shape[1:])(image=annotator.result())
  209. im_gpu = torch.as_tensor(img, dtype=torch.float16, device=pred_masks.data.device).permute(
  210. 2, 0, 1).flip(0).contiguous() / 255
  211. idx = pred_boxes.cls if pred_boxes else range(len(pred_masks))
  212. annotator.masks(pred_masks.data, colors=[colors(x, True) for x in idx], im_gpu=im_gpu)
  213. # Plot Detect results
  214. if pred_boxes and show_boxes:
  215. for d in reversed(pred_boxes):
  216. c, conf, id = int(d.cls), float(d.conf) if conf else None, None if d.id is None else int(d.id.item())
  217. name = ('' if id is None else f'id:{id} ') + names[c]
  218. label = (f'{name} {conf:.2f}' if conf else name) if labels else None
  219. annotator.box_label(d.xyxy.squeeze(), label, color=colors(c, True))
  220. # Plot Classify results
  221. if pred_probs is not None and show_probs:
  222. text = ',\n'.join(f'{names[j] if names else j} {pred_probs.data[j]:.2f}' for j in pred_probs.top5)
  223. x = round(self.orig_shape[0] * 0.03)
  224. annotator.text([x, x], text, txt_color=(255, 255, 255)) # TODO: allow setting colors
  225. # Plot Pose results
  226. if self.keypoints is not None:
  227. for k in reversed(self.keypoints.data):
  228. annotator.kpts(k, self.orig_shape, radius=kpt_radius, kpt_line=kpt_line)
  229. return annotator.result()
  230. def verbose(self):
  231. """
  232. Return log string for each task.
  233. """
  234. log_string = ''
  235. probs = self.probs
  236. boxes = self.boxes
  237. if len(self) == 0:
  238. return log_string if probs is not None else f'{log_string}(no detections), '
  239. if probs is not None:
  240. log_string += f"{', '.join(f'{self.names[j]} {probs.data[j]:.2f}' for j in probs.top5)}, "
  241. if boxes:
  242. for c in boxes.cls.unique():
  243. n = (boxes.cls == c).sum() # detections per class
  244. log_string += f"{n} {self.names[int(c)]}{'s' * (n > 1)}, "
  245. return log_string
  246. def save_txt(self, txt_file, save_conf=False):
  247. """
  248. Save predictions into txt file.
  249. Args:
  250. txt_file (str): txt file path.
  251. save_conf (bool): save confidence score or not.
  252. """
  253. boxes = self.boxes
  254. masks = self.masks
  255. probs = self.probs
  256. kpts = self.keypoints
  257. texts = []
  258. if probs is not None:
  259. # Classify
  260. [texts.append(f'{probs.data[j]:.2f} {self.names[j]}') for j in probs.top5]
  261. elif boxes:
  262. # Detect/segment/pose
  263. for j, d in enumerate(boxes):
  264. c, conf, id = int(d.cls), float(d.conf), None if d.id is None else int(d.id.item())
  265. line = (c, *d.xywhn.view(-1))
  266. if masks:
  267. seg = masks[j].xyn[0].copy().reshape(-1) # reversed mask.xyn, (n,2) to (n*2)
  268. line = (c, *seg)
  269. if kpts is not None:
  270. kpt = torch.cat((kpts[j].xyn, kpts[j].conf[..., None]), 2) if kpts[j].has_visible else kpts[j].xyn
  271. line += (*kpt.reshape(-1).tolist(), )
  272. line += (conf, ) * save_conf + (() if id is None else (id, ))
  273. texts.append(('%g ' * len(line)).rstrip() % line)
  274. if texts:
  275. Path(txt_file).parent.mkdir(parents=True, exist_ok=True) # make directory
  276. with open(txt_file, 'a') as f:
  277. f.writelines(text + '\n' for text in texts)
  278. def save_crop(self, save_dir, file_name=Path('im.jpg')):
  279. """
  280. Save cropped predictions to `save_dir/cls/file_name.jpg`.
  281. Args:
  282. save_dir (str | pathlib.Path): Save path.
  283. file_name (str | pathlib.Path): File name.
  284. """
  285. if self.probs is not None:
  286. LOGGER.warning('WARNING ⚠️ Classify task do not support `save_crop`.')
  287. return
  288. if isinstance(save_dir, str):
  289. save_dir = Path(save_dir)
  290. if isinstance(file_name, str):
  291. file_name = Path(file_name)
  292. for d in self.boxes:
  293. save_one_box(d.xyxy,
  294. self.orig_img.copy(),
  295. file=save_dir / self.names[int(d.cls)] / f'{file_name.stem}.jpg',
  296. BGR=True)
  297. def tojson(self, normalize=False):
  298. """Convert the object to JSON format."""
  299. if self.probs is not None:
  300. LOGGER.warning('Warning: Classify task do not support `tojson` yet.')
  301. return
  302. import json
  303. # Create list of detection dictionaries
  304. results = []
  305. data = self.boxes.data.cpu().tolist()
  306. h, w = self.orig_shape if normalize else (1, 1)
  307. for i, row in enumerate(data): # xyxy, track_id if tracking, conf, class_id
  308. box = {'x1': row[0] / w, 'y1': row[1] / h, 'x2': row[2] / w, 'y2': row[3] / h}
  309. conf = row[-2]
  310. class_id = int(row[-1])
  311. name = self.names[class_id]
  312. result = {'name': name, 'class': class_id, 'confidence': conf, 'box': box}
  313. if self.boxes.is_track:
  314. result['track_id'] = int(row[-3]) # track ID
  315. if self.masks:
  316. x, y = self.masks.xy[i][:, 0], self.masks.xy[i][:, 1] # numpy array
  317. result['segments'] = {'x': (x / w).tolist(), 'y': (y / h).tolist()}
  318. if self.keypoints is not None:
  319. x, y, visible = self.keypoints[i].data[0].cpu().unbind(dim=1) # torch Tensor
  320. result['keypoints'] = {'x': (x / w).tolist(), 'y': (y / h).tolist(), 'visible': visible.tolist()}
  321. results.append(result)
  322. # Convert detections to JSON
  323. return json.dumps(results, indent=2)
  324. class Boxes(BaseTensor):
  325. """
  326. A class for storing and manipulating detection boxes.
  327. Args:
  328. boxes (torch.Tensor | numpy.ndarray): A tensor or numpy array containing the detection boxes,
  329. with shape (num_boxes, 6) or (num_boxes, 7). The last two columns contain confidence and class values.
  330. If present, the third last column contains track IDs.
  331. orig_shape (tuple): Original image size, in the format (height, width).
  332. Attributes:
  333. xyxy (torch.Tensor | numpy.ndarray): The boxes in xyxy format.
  334. conf (torch.Tensor | numpy.ndarray): The confidence values of the boxes.
  335. cls (torch.Tensor | numpy.ndarray): The class values of the boxes.
  336. id (torch.Tensor | numpy.ndarray): The track IDs of the boxes (if available).
  337. xywh (torch.Tensor | numpy.ndarray): The boxes in xywh format.
  338. xyxyn (torch.Tensor | numpy.ndarray): The boxes in xyxy format normalized by original image size.
  339. xywhn (torch.Tensor | numpy.ndarray): The boxes in xywh format normalized by original image size.
  340. data (torch.Tensor): The raw bboxes tensor (alias for `boxes`).
  341. Methods:
  342. cpu(): Move the object to CPU memory.
  343. numpy(): Convert the object to a numpy array.
  344. cuda(): Move the object to CUDA memory.
  345. to(*args, **kwargs): Move the object to the specified device.
  346. """
  347. def __init__(self, boxes, orig_shape) -> None:
  348. """Initialize the Boxes class."""
  349. if boxes.ndim == 1:
  350. boxes = boxes[None, :]
  351. n = boxes.shape[-1]
  352. assert n in (6, 7), f'expected `n` in [6, 7], but got {n}' # xyxy, track_id, conf, cls
  353. super().__init__(boxes, orig_shape)
  354. self.is_track = n == 7
  355. self.orig_shape = orig_shape
  356. @property
  357. def xyxy(self):
  358. """Return the boxes in xyxy format."""
  359. return self.data[:, :4]
  360. @property
  361. def conf(self):
  362. """Return the confidence values of the boxes."""
  363. return self.data[:, -2]
  364. @property
  365. def cls(self):
  366. """Return the class values of the boxes."""
  367. return self.data[:, -1]
  368. @property
  369. def id(self):
  370. """Return the track IDs of the boxes (if available)."""
  371. return self.data[:, -3] if self.is_track else None
  372. @property
  373. @lru_cache(maxsize=2) # maxsize 1 should suffice
  374. def xywh(self):
  375. """Return the boxes in xywh format."""
  376. return ops.xyxy2xywh(self.xyxy)
  377. @property
  378. @lru_cache(maxsize=2)
  379. def xyxyn(self):
  380. """Return the boxes in xyxy format normalized by original image size."""
  381. xyxy = self.xyxy.clone() if isinstance(self.xyxy, torch.Tensor) else np.copy(self.xyxy)
  382. xyxy[..., [0, 2]] /= self.orig_shape[1]
  383. xyxy[..., [1, 3]] /= self.orig_shape[0]
  384. return xyxy
  385. @property
  386. @lru_cache(maxsize=2)
  387. def xywhn(self):
  388. """Return the boxes in xywh format normalized by original image size."""
  389. xywh = ops.xyxy2xywh(self.xyxy)
  390. xywh[..., [0, 2]] /= self.orig_shape[1]
  391. xywh[..., [1, 3]] /= self.orig_shape[0]
  392. return xywh
  393. @property
  394. def boxes(self):
  395. """Return the raw bboxes tensor (deprecated)."""
  396. LOGGER.warning("WARNING ⚠️ 'Boxes.boxes' is deprecated. Use 'Boxes.data' instead.")
  397. return self.data
  398. class Masks(BaseTensor):
  399. """
  400. A class for storing and manipulating detection masks.
  401. Attributes:
  402. segments (list): Deprecated property for segments (normalized).
  403. xy (list): A list of segments in pixel coordinates.
  404. xyn (list): A list of normalized segments.
  405. Methods:
  406. cpu(): Returns the masks tensor on CPU memory.
  407. numpy(): Returns the masks tensor as a numpy array.
  408. cuda(): Returns the masks tensor on GPU memory.
  409. to(device, dtype): Returns the masks tensor with the specified device and dtype.
  410. """
  411. def __init__(self, masks, orig_shape) -> None:
  412. """Initialize the Masks class with the given masks tensor and original image shape."""
  413. if masks.ndim == 2:
  414. masks = masks[None, :]
  415. super().__init__(masks, orig_shape)
  416. @property
  417. @lru_cache(maxsize=1)
  418. def segments(self):
  419. """Return segments (normalized). Deprecated; use xyn property instead."""
  420. LOGGER.warning(
  421. "WARNING ⚠️ 'Masks.segments' is deprecated. Use 'Masks.xyn' for segments (normalized) and 'Masks.xy' for segments (pixels) instead."
  422. )
  423. return self.xyn
  424. @property
  425. @lru_cache(maxsize=1)
  426. def xyn(self):
  427. """Return normalized segments."""
  428. return [
  429. ops.scale_coords(self.data.shape[1:], x, self.orig_shape, normalize=True)
  430. for x in ops.masks2segments(self.data)]
  431. @property
  432. @lru_cache(maxsize=1)
  433. def xy(self):
  434. """Return segments in pixel coordinates."""
  435. return [
  436. ops.scale_coords(self.data.shape[1:], x, self.orig_shape, normalize=False)
  437. for x in ops.masks2segments(self.data)]
  438. @property
  439. def masks(self):
  440. """Return the raw masks tensor. Deprecated; use data attribute instead."""
  441. LOGGER.warning("WARNING ⚠️ 'Masks.masks' is deprecated. Use 'Masks.data' instead.")
  442. return self.data
  443. class Keypoints(BaseTensor):
  444. """
  445. A class for storing and manipulating detection keypoints.
  446. Attributes:
  447. xy (torch.Tensor): A collection of keypoints containing x, y coordinates for each detection.
  448. xyn (torch.Tensor): A normalized version of xy with coordinates in the range [0, 1].
  449. conf (torch.Tensor): Confidence values associated with keypoints if available, otherwise None.
  450. Methods:
  451. cpu(): Returns a copy of the keypoints tensor on CPU memory.
  452. numpy(): Returns a copy of the keypoints tensor as a numpy array.
  453. cuda(): Returns a copy of the keypoints tensor on GPU memory.
  454. to(device, dtype): Returns a copy of the keypoints tensor with the specified device and dtype.
  455. """
  456. def __init__(self, keypoints, orig_shape) -> None:
  457. """Initializes the Keypoints object with detection keypoints and original image size."""
  458. if keypoints.ndim == 2:
  459. keypoints = keypoints[None, :]
  460. super().__init__(keypoints, orig_shape)
  461. self.has_visible = self.data.shape[-1] == 3
  462. @property
  463. @lru_cache(maxsize=1)
  464. def xy(self):
  465. """Returns x, y coordinates of keypoints."""
  466. return self.data[..., :2]
  467. @property
  468. @lru_cache(maxsize=1)
  469. def xyn(self):
  470. """Returns normalized x, y coordinates of keypoints."""
  471. xy = self.xy.clone() if isinstance(self.xy, torch.Tensor) else np.copy(self.xy)
  472. xy[..., 0] /= self.orig_shape[1]
  473. xy[..., 1] /= self.orig_shape[0]
  474. return xy
  475. @property
  476. @lru_cache(maxsize=1)
  477. def conf(self):
  478. """Returns confidence values of keypoints if available, else None."""
  479. return self.data[..., 2] if self.has_visible else None
  480. class Probs(BaseTensor):
  481. """
  482. A class for storing and manipulating classification predictions.
  483. Attributes:
  484. top1 (int): Index of the top 1 class.
  485. top5 (list[int]): Indices of the top 5 classes.
  486. top1conf (torch.Tensor): Confidence of the top 1 class.
  487. top5conf (torch.Tensor): Confidences of the top 5 classes.
  488. Methods:
  489. cpu(): Returns a copy of the probs tensor on CPU memory.
  490. numpy(): Returns a copy of the probs tensor as a numpy array.
  491. cuda(): Returns a copy of the probs tensor on GPU memory.
  492. to(): Returns a copy of the probs tensor with the specified device and dtype.
  493. """
  494. def __init__(self, probs, orig_shape=None) -> None:
  495. super().__init__(probs, orig_shape)
  496. @property
  497. @lru_cache(maxsize=1)
  498. def top1(self):
  499. """Return the index of top 1."""
  500. return int(self.data.argmax())
  501. @property
  502. @lru_cache(maxsize=1)
  503. def top5(self):
  504. """Return the indices of top 5."""
  505. return (-self.data).argsort(0)[:5].tolist() # this way works with both torch and numpy.
  506. @property
  507. @lru_cache(maxsize=1)
  508. def top1conf(self):
  509. """Return the confidence of top 1."""
  510. return self.data[self.top1]
  511. @property
  512. @lru_cache(maxsize=1)
  513. def top5conf(self):
  514. """Return the confidences of top 5."""
  515. return self.data[self.top5]