dgenerate-ultralytics-headless 8.3.137__py3-none-any.whl → 8.3.139__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.139.dist-info}/METADATA +1 -1
  2. {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.139.dist-info}/RECORD +36 -36
  3. tests/test_python.py +6 -1
  4. tests/test_solutions.py +183 -8
  5. ultralytics/__init__.py +1 -1
  6. ultralytics/cfg/__init__.py +1 -1
  7. ultralytics/data/base.py +1 -1
  8. ultralytics/data/build.py +4 -3
  9. ultralytics/data/loaders.py +2 -2
  10. ultralytics/engine/exporter.py +5 -5
  11. ultralytics/engine/model.py +2 -2
  12. ultralytics/engine/predictor.py +3 -10
  13. ultralytics/engine/results.py +2 -209
  14. ultralytics/engine/trainer.py +1 -1
  15. ultralytics/engine/validator.py +1 -1
  16. ultralytics/hub/auth.py +2 -2
  17. ultralytics/hub/utils.py +8 -3
  18. ultralytics/models/yolo/classify/predict.py +11 -0
  19. ultralytics/models/yolo/obb/val.py +1 -1
  20. ultralytics/models/yolo/world/train.py +1 -1
  21. ultralytics/models/yolo/yoloe/val.py +3 -3
  22. ultralytics/solutions/similarity_search.py +3 -6
  23. ultralytics/solutions/streamlit_inference.py +1 -1
  24. ultralytics/utils/__init__.py +159 -1
  25. ultralytics/utils/callbacks/hub.py +5 -4
  26. ultralytics/utils/checks.py +25 -18
  27. ultralytics/utils/downloads.py +7 -5
  28. ultralytics/utils/export.py +1 -1
  29. ultralytics/utils/metrics.py +90 -5
  30. ultralytics/utils/plotting.py +1 -1
  31. ultralytics/utils/torch_utils.py +3 -0
  32. ultralytics/utils/triton.py +1 -1
  33. {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.139.dist-info}/WHEEL +0 -0
  34. {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.139.dist-info}/entry_points.txt +0 -0
  35. {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.139.dist-info}/licenses/LICENSE +0 -0
  36. {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.139.dist-info}/top_level.txt +0 -0
@@ -13,8 +13,7 @@ import numpy as np
13
13
  import torch
14
14
 
15
15
  from ultralytics.data.augment import LetterBox
16
- from ultralytics.utils import LOGGER, SimpleClass, ops
17
- from ultralytics.utils.checks import check_requirements
16
+ from ultralytics.utils import LOGGER, DataExportMixin, SimpleClass, ops
18
17
  from ultralytics.utils.plotting import Annotator, colors, save_one_box
19
18
  from ultralytics.utils.torch_utils import smart_inference_mode
20
19
 
@@ -184,7 +183,7 @@ class BaseTensor(SimpleClass):
184
183
  return self.__class__(self.data[idx], self.orig_shape)
185
184
 
186
185
 
187
- class Results(SimpleClass):
186
+ class Results(SimpleClass, DataExportMixin):
188
187
  """
189
188
  A class for storing and manipulating inference results.
190
189
 
@@ -828,212 +827,6 @@ class Results(SimpleClass):
828
827
 
829
828
  return results
830
829
 
831
- def to_df(self, normalize=False, decimals=5):
832
- """
833
- Converts detection results to a Pandas Dataframe.
834
-
835
- This method converts the detection results into Pandas Dataframe format. It includes information
836
- about detected objects such as bounding boxes, class names, confidence scores, and optionally
837
- segmentation masks and keypoints.
838
-
839
- Args:
840
- normalize (bool): Whether to normalize the bounding box coordinates by the image dimensions.
841
- If True, coordinates will be returned as float values between 0 and 1.
842
- decimals (int): Number of decimal places to round the output values to.
843
-
844
- Returns:
845
- (DataFrame): A Pandas Dataframe containing all the information in results in an organized way.
846
-
847
- Examples:
848
- >>> results = model("path/to/image.jpg")
849
- >>> for result in results:
850
- >>> df_result = result.to_df()
851
- >>> print(df_result)
852
- """
853
- import pandas as pd # scope for faster 'import ultralytics'
854
-
855
- return pd.DataFrame(self.summary(normalize=normalize, decimals=decimals))
856
-
857
- def to_csv(self, normalize=False, decimals=5, *args, **kwargs):
858
- """
859
- Converts detection results to a CSV format.
860
-
861
- This method serializes the detection results into a CSV format. It includes information
862
- about detected objects such as bounding boxes, class names, confidence scores, and optionally
863
- segmentation masks and keypoints.
864
-
865
- Args:
866
- normalize (bool): Whether to normalize the bounding box coordinates by the image dimensions.
867
- If True, coordinates will be returned as float values between 0 and 1.
868
- decimals (int): Number of decimal places to round the output values to.
869
- *args (Any): Variable length argument list to be passed to pandas.DataFrame.to_csv().
870
- **kwargs (Any): Arbitrary keyword arguments to be passed to pandas.DataFrame.to_csv().
871
-
872
-
873
- Returns:
874
- (str): CSV containing all the information in results in an organized way.
875
-
876
- Examples:
877
- >>> results = model("path/to/image.jpg")
878
- >>> for result in results:
879
- >>> csv_result = result.to_csv()
880
- >>> print(csv_result)
881
- """
882
- return self.to_df(normalize=normalize, decimals=decimals).to_csv(*args, **kwargs)
883
-
884
- def to_xml(self, normalize=False, decimals=5, *args, **kwargs):
885
- """
886
- Converts detection results to XML format.
887
-
888
- This method serializes the detection results into an XML format. It includes information
889
- about detected objects such as bounding boxes, class names, confidence scores, and optionally
890
- segmentation masks and keypoints.
891
-
892
- Args:
893
- normalize (bool): Whether to normalize the bounding box coordinates by the image dimensions.
894
- If True, coordinates will be returned as float values between 0 and 1.
895
- decimals (int): Number of decimal places to round the output values to.
896
- *args (Any): Variable length argument list to be passed to pandas.DataFrame.to_xml().
897
- **kwargs (Any): Arbitrary keyword arguments to be passed to pandas.DataFrame.to_xml().
898
-
899
- Returns:
900
- (str): An XML string containing all the information in results in an organized way.
901
-
902
- Examples:
903
- >>> results = model("path/to/image.jpg")
904
- >>> for result in results:
905
- >>> xml_result = result.to_xml()
906
- >>> print(xml_result)
907
- """
908
- check_requirements("lxml")
909
- df = self.to_df(normalize=normalize, decimals=decimals)
910
- return '<?xml version="1.0" encoding="utf-8"?>\n<root></root>' if df.empty else df.to_xml(*args, **kwargs)
911
-
912
- def to_html(self, normalize=False, decimals=5, index=False, *args, **kwargs):
913
- """
914
- Converts detection results to HTML format.
915
-
916
- This method serializes the detection results into an HTML format. It includes information
917
- about detected objects such as bounding boxes, class names, confidence scores, and optionally
918
- segmentation masks and keypoints.
919
-
920
- Args:
921
- normalize (bool): Whether to normalize the bounding box coordinates by the image dimensions.
922
- If True, coordinates will be returned as float values between 0 and 1.
923
- decimals (int): Number of decimal places to round the output values to.
924
- index (bool): Whether to include the DataFrame index in the HTML output.
925
- *args (Any): Variable length argument list to be passed to pandas.DataFrame.to_html().
926
- **kwargs (Any): Arbitrary keyword arguments to be passed to pandas.DataFrame.to_html().
927
-
928
- Returns:
929
- (str): An HTML string containing all the information in results in an organized way.
930
-
931
- Examples:
932
- >>> results = model("path/to/image.jpg")
933
- >>> for result in results:
934
- >>> html_result = result.to_html()
935
- >>> print(html_result)
936
- """
937
- df = self.to_df(normalize=normalize, decimals=decimals)
938
- return "<table></table>" if df.empty else df.to_html(index=index, *args, **kwargs)
939
-
940
- def tojson(self, normalize=False, decimals=5):
941
- """Deprecated version of to_json()."""
942
- LOGGER.warning("'result.tojson()' is deprecated, replace with 'result.to_json()'.")
943
- return self.to_json(normalize, decimals)
944
-
945
- def to_json(self, normalize=False, decimals=5):
946
- """
947
- Converts detection results to JSON format.
948
-
949
- This method serializes the detection results into a JSON-compatible format. It includes information
950
- about detected objects such as bounding boxes, class names, confidence scores, and optionally
951
- segmentation masks and keypoints.
952
-
953
- Args:
954
- normalize (bool): Whether to normalize the bounding box coordinates by the image dimensions.
955
- If True, coordinates will be returned as float values between 0 and 1.
956
- decimals (int): Number of decimal places to round the output values to.
957
-
958
- Returns:
959
- (str): A JSON string containing the serialized detection results.
960
-
961
- Examples:
962
- >>> results = model("path/to/image.jpg")
963
- >>> for result in results:
964
- >>> json_result = result.to_json()
965
- >>> print(json_result)
966
-
967
- Notes:
968
- - For classification tasks, the JSON will contain class probabilities instead of bounding boxes.
969
- - For object detection tasks, the JSON will include bounding box coordinates, class names, and
970
- confidence scores.
971
- - If available, segmentation masks and keypoints will also be included in the JSON output.
972
- - The method uses the `summary` method internally to generate the data structure before
973
- converting it to JSON.
974
- """
975
- import json
976
-
977
- return json.dumps(self.summary(normalize=normalize, decimals=decimals), indent=2)
978
-
979
- def to_sql(self, table_name="results", normalize=False, decimals=5, db_path="results.db"):
980
- """
981
- Converts detection results to an SQL-compatible format.
982
-
983
- This method serializes the detection results into a format compatible with SQL databases.
984
- It includes information about detected objects such as bounding boxes, class names, confidence scores,
985
- and optionally segmentation masks, keypoints or oriented bounding boxes.
986
-
987
- Args:
988
- table_name (str): Name of the SQL table where the data will be inserted.
989
- normalize (bool): Whether to normalize the bounding box coordinates by the image dimensions.
990
- If True, coordinates will be returned as float values between 0 and 1.
991
- decimals (int): Number of decimal places to round the bounding boxes values to.
992
- db_path (str): Path to the SQLite database file.
993
-
994
- Examples:
995
- >>> results = model("path/to/image.jpg")
996
- >>> for result in results:
997
- >>> result.to_sql()
998
- """
999
- import json
1000
- import sqlite3
1001
-
1002
- # Convert results to a list of dictionaries
1003
- data = self.summary(normalize=normalize, decimals=decimals)
1004
- if len(data) == 0:
1005
- LOGGER.warning("No results to save to SQL. Results dict is empty.")
1006
- return
1007
-
1008
- # Connect to the SQLite database
1009
- conn = sqlite3.connect(db_path)
1010
- cursor = conn.cursor()
1011
-
1012
- # Create table if it doesn't exist
1013
- columns = (
1014
- "id INTEGER PRIMARY KEY AUTOINCREMENT, class_name TEXT, confidence REAL, box TEXT, masks TEXT, kpts TEXT"
1015
- )
1016
- cursor.execute(f"CREATE TABLE IF NOT EXISTS {table_name} ({columns})")
1017
-
1018
- # Insert data into the table
1019
- for item in data:
1020
- cursor.execute(
1021
- f"INSERT INTO {table_name} (class_name, confidence, box, masks, kpts) VALUES (?, ?, ?, ?, ?)",
1022
- (
1023
- item.get("name"),
1024
- item.get("confidence"),
1025
- json.dumps(item.get("box", {})),
1026
- json.dumps(item.get("segments", {})),
1027
- json.dumps(item.get("keypoints", {})),
1028
- ),
1029
- )
1030
-
1031
- # Commit and close the connection
1032
- conn.commit()
1033
- conn.close()
1034
-
1035
- LOGGER.info(f"Detection results successfully written to SQL table '{table_name}' in database '{db_path}'.")
1036
-
1037
830
 
1038
831
  class Boxes(BaseTensor):
1039
832
  """
@@ -578,7 +578,7 @@ class BaseTrainer:
578
578
  try:
579
579
  if self.args.task == "classify":
580
580
  data = check_cls_dataset(self.args.data)
581
- elif self.args.data.split(".")[-1] in {"yaml", "yml"} or self.args.task in {
581
+ elif self.args.data.rsplit(".", 1)[-1] in {"yaml", "yml"} or self.args.task in {
582
582
  "detect",
583
583
  "segment",
584
584
  "pose",
@@ -175,7 +175,7 @@ class BaseValidator:
175
175
  self.args.batch = model.metadata.get("batch", 1) # export.py models default to batch-size 1
176
176
  LOGGER.info(f"Setting batch={self.args.batch} input of shape ({self.args.batch}, 3, {imgsz}, {imgsz})")
177
177
 
178
- if str(self.args.data).split(".")[-1] in {"yaml", "yml"}:
178
+ if str(self.args.data).rsplit(".", 1)[-1] in {"yaml", "yml"}:
179
179
  self.data = check_det_dataset(self.args.data)
180
180
  elif self.args.task == "classify":
181
181
  self.data = check_cls_dataset(self.args.data, split=self.args.split)
ultralytics/hub/auth.py CHANGED
@@ -37,7 +37,7 @@ class Auth:
37
37
  verbose (bool): Enable verbose logging.
38
38
  """
39
39
  # Split the input API key in case it contains a combined key_model and keep only the API key part
40
- api_key = api_key.split("_")[0]
40
+ api_key = api_key.split("_", 1)[0]
41
41
 
42
42
  # Set API key attribute as value passed or SETTINGS API key if none passed
43
43
  self.api_key = api_key or SETTINGS.get("api_key", "")
@@ -77,7 +77,7 @@ class Auth:
77
77
  for attempts in range(max_attempts):
78
78
  LOGGER.info(f"{PREFIX}Login. Attempt {attempts + 1} of {max_attempts}")
79
79
  input_key = getpass.getpass(f"Enter API key from {API_KEY_URL} ")
80
- self.api_key = input_key.split("_")[0] # remove model id if present
80
+ self.api_key = input_key.split("_", 1)[0] # remove model id if present
81
81
  if self.authenticate():
82
82
  return True
83
83
  raise ConnectionError(emojis(f"{PREFIX}Failed to authenticate ❌"))
ultralytics/hub/utils.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
3
  import os
4
- import platform
5
4
  import random
6
5
  import threading
7
6
  import time
@@ -18,6 +17,7 @@ from ultralytics.utils import (
18
17
  IS_PIP_PACKAGE,
19
18
  LOGGER,
20
19
  ONLINE,
20
+ PYTHON_VERSION,
21
21
  RANK,
22
22
  SETTINGS,
23
23
  TESTS_RUNNING,
@@ -27,6 +27,7 @@ from ultralytics.utils import (
27
27
  get_git_origin_url,
28
28
  )
29
29
  from ultralytics.utils.downloads import GITHUB_ASSETS_NAMES
30
+ from ultralytics.utils.torch_utils import get_cpu_info
30
31
 
31
32
  HUB_API_ROOT = os.environ.get("ULTRALYTICS_HUB_API", "https://api.ultralytics.com")
32
33
  HUB_WEB_ROOT = os.environ.get("ULTRALYTICS_HUB_WEB", "https://hub.ultralytics.com")
@@ -191,7 +192,9 @@ class Events:
191
192
  self.metadata = {
192
193
  "cli": Path(ARGV[0]).name == "yolo",
193
194
  "install": "git" if IS_GIT_DIR else "pip" if IS_PIP_PACKAGE else "other",
194
- "python": ".".join(platform.python_version_tuple()[:2]), # i.e. 3.10
195
+ "python": PYTHON_VERSION.rsplit(".", 1)[0], # i.e. 3.13
196
+ "CPU": get_cpu_info(),
197
+ # "GPU": get_gpu_info(index=0) if cuda else None,
195
198
  "version": __version__,
196
199
  "env": ENVIRONMENT,
197
200
  "session_id": round(random.random() * 1e15),
@@ -205,12 +208,13 @@ class Events:
205
208
  and (IS_PIP_PACKAGE or get_git_origin_url() == "https://github.com/ultralytics/ultralytics.git")
206
209
  )
207
210
 
208
- def __call__(self, cfg):
211
+ def __call__(self, cfg, device=None):
209
212
  """
210
213
  Attempt to add a new event to the events list and send events if the rate limit is reached.
211
214
 
212
215
  Args:
213
216
  cfg (IterableSimpleNamespace): The configuration object containing mode and task information.
217
+ device (torch.device | str): The device type (e.g., 'cpu', 'cuda').
214
218
  """
215
219
  if not self.enabled:
216
220
  # Events disabled, do nothing
@@ -222,6 +226,7 @@ class Events:
222
226
  **self.metadata,
223
227
  "task": cfg.task,
224
228
  "model": cfg.model if cfg.model in GITHUB_ASSETS_NAMES else "custom",
229
+ "device": str(device),
225
230
  }
226
231
  if cfg.mode == "export":
227
232
  params["format"] = cfg.format
@@ -4,6 +4,7 @@ import cv2
4
4
  import torch
5
5
  from PIL import Image
6
6
 
7
+ from ultralytics.data.augment import classify_transforms
7
8
  from ultralytics.engine.predictor import BasePredictor
8
9
  from ultralytics.engine.results import Results
9
10
  from ultralytics.utils import DEFAULT_CFG, ops
@@ -51,6 +52,16 @@ class ClassificationPredictor(BasePredictor):
51
52
  self.args.task = "classify"
52
53
  self._legacy_transform_name = "ultralytics.yolo.data.augment.ToTensor"
53
54
 
55
+ def setup_source(self, source):
56
+ """Sets up source and inference mode and classify transforms."""
57
+ super().setup_source(source)
58
+ updated = (
59
+ self.model.model.transforms.transforms[0].size != max(self.imgsz)
60
+ if hasattr(self.model.model, "transforms")
61
+ else True
62
+ )
63
+ self.transforms = self.model.model.transforms if not updated else classify_transforms(self.imgsz)
64
+
54
65
  def preprocess(self, img):
55
66
  """Convert input images to model-compatible tensor format with appropriate normalization."""
56
67
  if not isinstance(img, torch.Tensor):
@@ -252,7 +252,7 @@ class OBBValidator(DetectionValidator):
252
252
  merged_results = defaultdict(list)
253
253
  LOGGER.info(f"Saving merged predictions with DOTA format to {pred_merged_txt}...")
254
254
  for d in data:
255
- image_id = d["image_id"].split("__")[0]
255
+ image_id = d["image_id"].split("__", 1)[0]
256
256
  pattern = re.compile(r"\d+___\d+")
257
257
  x, y = (int(c) for c in re.findall(pattern, d["image_id"])[0].split("___"))
258
258
  bbox, score, cls = d["rbox"], d["score"], d["category_id"] - 1
@@ -16,7 +16,7 @@ def on_pretrain_routine_end(trainer):
16
16
  """Callback to set up model classes and text encoder at the end of the pretrain routine."""
17
17
  if RANK in {-1, 0}:
18
18
  # Set class names for evaluation
19
- names = [name.split("/")[0] for name in list(trainer.test_loader.dataset.data["names"].values())]
19
+ names = [name.split("/", 1)[0] for name in list(trainer.test_loader.dataset.data["names"].values())]
20
20
  de_parallel(trainer.ema.ema).set_classes(names, cache_clip_model=False)
21
21
 
22
22
 
@@ -47,7 +47,7 @@ class YOLOEDetectValidator(DetectionValidator):
47
47
  (torch.Tensor): Visual prompt embeddings with shape (1, num_classes, embed_dim).
48
48
  """
49
49
  assert isinstance(model, YOLOEModel)
50
- names = [name.split("/")[0] for name in list(dataloader.dataset.data["names"].values())]
50
+ names = [name.split("/", 1)[0] for name in list(dataloader.dataset.data["names"].values())]
51
51
  visual_pe = torch.zeros(len(names), model.model[-1].embed, device=self.device)
52
52
  cls_visual_num = torch.zeros(len(names))
53
53
 
@@ -140,7 +140,7 @@ class YOLOEDetectValidator(DetectionValidator):
140
140
  if trainer is not None:
141
141
  self.device = trainer.device
142
142
  model = trainer.ema.ema
143
- names = [name.split("/")[0] for name in list(self.dataloader.dataset.data["names"].values())]
143
+ names = [name.split("/", 1)[0] for name in list(self.dataloader.dataset.data["names"].values())]
144
144
 
145
145
  if load_vp:
146
146
  LOGGER.info("Validate using the visual prompt.")
@@ -164,7 +164,7 @@ class YOLOEDetectValidator(DetectionValidator):
164
164
  model = attempt_load_weights(model, device=self.device, inplace=True)
165
165
  model.eval().to(self.device)
166
166
  data = check_det_dataset(refer_data or self.args.data)
167
- names = [name.split("/")[0] for name in list(data["names"].values())]
167
+ names = [name.split("/", 1)[0] for name in list(data["names"].values())]
168
168
 
169
169
  if load_vp:
170
170
  LOGGER.info("Validate using the visual prompt.")
@@ -30,12 +30,9 @@ class VisualAISearch(BaseSolution):
30
30
  """Initializes the VisualAISearch class with the FAISS index file and CLIP model."""
31
31
  super().__init__(**kwargs)
32
32
  check_requirements(["git+https://github.com/ultralytics/CLIP.git", "faiss-cpu"])
33
- import clip
34
- import faiss
35
-
36
- self.faiss = faiss
37
- self.clip = clip
38
33
 
34
+ self.faiss = __import__("faiss")
35
+ self.clip = __import__("clip")
39
36
  self.faiss_index = "faiss.index"
40
37
  self.data_path_npy = "paths.npy"
41
38
  self.model_name = "ViT-B/32"
@@ -51,7 +48,7 @@ class VisualAISearch(BaseSolution):
51
48
  safe_download(url=f"{ASSETS_URL}/images.zip", unzip=True, retry=3)
52
49
  self.data_dir = Path("images")
53
50
 
54
- self.model, self.preprocess = clip.load(self.model_name, device=self.device)
51
+ self.model, self.preprocess = self.clip.load(self.model_name, device=self.device)
55
52
 
56
53
  self.index = None
57
54
  self.image_paths = []
@@ -130,7 +130,7 @@ class Inference:
130
130
  # Add dropdown menu for model selection
131
131
  available_models = [x.replace("yolo", "YOLO") for x in GITHUB_ASSETS_STEMS if x.startswith("yolo11")]
132
132
  if self.model_path: # If user provided the custom model, insert model without suffix as *.pt is added later
133
- available_models.insert(0, self.model_path.split(".pt")[0])
133
+ available_models.insert(0, self.model_path.split(".pt", 1)[0])
134
134
  selected_model = self.st.sidebar.selectbox("Model", available_models)
135
135
 
136
136
  with self.st.spinner("Model is downloading..."):
@@ -187,6 +187,164 @@ class TQDM(rich.tqdm if TQDM_RICH else tqdm.tqdm):
187
187
  return super().__iter__()
188
188
 
189
189
 
190
+ class DataExportMixin:
191
+ """
192
+ Mixin class for exporting validation metrics or prediction results in various formats.
193
+
194
+ This class provides utilities to export performance metrics (e.g., mAP, precision, recall) or prediction results
195
+ from classification, object detection, segmentation, or pose estimation tasks into various formats, Pandas DataFrame
196
+ CSV, XML, HTML, JSON and SQLite (SQL)
197
+
198
+ Methods:
199
+ to_df(): Convert summary to a Pandas DataFrame.
200
+ to_csv(): Export results as a CSV string.
201
+ to_xml(): Export results as an XML string (requires `lxml`).
202
+ to_html(): Export results as an HTML table.
203
+ to_json(): Export results as a JSON string.
204
+ tojson(): Deprecated alias for `to_json()`.
205
+ to_sql(): Export results to an SQLite database.
206
+
207
+ Examples:
208
+ >>> model = YOLO("yolov8n.pt")
209
+ >>> results = model("image.jpg")
210
+ >>> df = results.to_df()
211
+ >>> print(df)
212
+ >>> csv_data = results.to_csv()
213
+ >>> results.to_sql(table_name="yolo_results")
214
+ """
215
+
216
+ def to_df(self, normalize=False, decimals=5):
217
+ """
218
+ Create a pandas DataFrame from the prediction results summary or validation metrics.
219
+
220
+ Args:
221
+ normalize (bool, optional): Normalize numerical values for easier comparison. Defaults to False.
222
+ decimals (int, optional): Decimal places to round floats. Defaults to 5.
223
+
224
+ Returns:
225
+ (DataFrame): DataFrame containing the summary data.
226
+ """
227
+ import pandas as pd # scope for faster 'import ultralytics'
228
+
229
+ return pd.DataFrame(self.summary())
230
+
231
+ def to_csv(self, normalize=False, decimals=5):
232
+ """
233
+ Export results to CSV string format.
234
+
235
+ Args:
236
+ normalize (bool, optional): Normalize numeric values. Defaults to False.
237
+ decimals (int, optional): Decimal precision. Defaults to 5.
238
+
239
+ Returns:
240
+ (str): CSV content as string.
241
+ """
242
+ return self.to_df(normalize=normalize, decimals=decimals).to_csv()
243
+
244
+ def to_xml(self, normalize=False, decimals=5):
245
+ """
246
+ Export results to XML format.
247
+
248
+ Args:
249
+ normalize (bool, optional): Normalize numeric values. Defaults to False.
250
+ decimals (int, optional): Decimal precision. Defaults to 5.
251
+
252
+ Returns:
253
+ (str): XML string.
254
+
255
+ Note:
256
+ Requires `lxml` package to be installed.
257
+ """
258
+ from ultralytics.utils.checks import check_requirements
259
+
260
+ check_requirements("lxml")
261
+ df = self.to_df(normalize=normalize, decimals=decimals)
262
+ return '<?xml version="1.0" encoding="utf-8"?>\n<root></root>' if df.empty else df.to_xml()
263
+
264
+ def to_html(self, normalize=False, decimals=5, index=False):
265
+ """
266
+ Export results to HTML table format.
267
+
268
+ Args:
269
+ normalize (bool, optional): Normalize numeric values. Defaults to False.
270
+ decimals (int, optional): Decimal precision. Defaults to 5.
271
+ index (bool, optional): Whether to include index column in the HTML table. Defaults to False.
272
+
273
+ Returns:
274
+ (str): HTML representation of the results.
275
+ """
276
+ df = self.to_df(normalize=normalize, decimals=decimals)
277
+ return "<table></table>" if df.empty else df.to_html(index=index)
278
+
279
+ def tojson(self, normalize=False, decimals=5):
280
+ """Deprecated version of to_json()."""
281
+ LOGGER.warning("'result.tojson()' is deprecated, replace with 'result.to_json()'.")
282
+ return self.to_json(normalize, decimals)
283
+
284
+ def to_json(self, normalize=False, decimals=5):
285
+ """
286
+ Export results to JSON format.
287
+
288
+ Args:
289
+ normalize (bool, optional): Normalize numeric values. Defaults to False.
290
+ decimals (int, optional): Decimal precision. Defaults to 5.
291
+
292
+ Returns:
293
+ (str): JSON-formatted string of the results.
294
+ """
295
+ return self.to_df(normalize=normalize, decimals=decimals).to_json(orient="records", indent=2)
296
+
297
+ def to_sql(self, normalize=False, decimals=5, table_name="results", db_path="results.db"):
298
+ """
299
+ Save results to an SQLite database.
300
+
301
+ Args:
302
+ normalize (bool, optional): Normalize numeric values. Defaults to False.
303
+ decimals (int, optional): Decimal precision. Defaults to 5.
304
+ table_name (str, optional): Name of the SQL table. Defaults to "results".
305
+ db_path (str, optional): SQLite database file path. Defaults to "results.db".
306
+ """
307
+ if not hasattr(self, "summary"):
308
+ LOGGER.warning("SQL export is only supported for detection results with `summary()`.")
309
+ return
310
+
311
+ import sqlite3
312
+
313
+ df = self.to_df(normalize, decimals)
314
+ if df.empty:
315
+ LOGGER.warning("No results to save to SQL. DataFrame is empty.")
316
+ return
317
+
318
+ conn = sqlite3.connect(db_path)
319
+ cursor = conn.cursor()
320
+ cursor.execute(
321
+ f"""CREATE TABLE IF NOT EXISTS {table_name} (
322
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
323
+ class_name TEXT,
324
+ confidence REAL,
325
+ box TEXT,
326
+ masks TEXT,
327
+ kpts TEXT
328
+ )"""
329
+ )
330
+
331
+ for _, row in df.iterrows():
332
+ cursor.execute(
333
+ f"INSERT INTO {table_name} (class_name, confidence, box, masks, kpts) VALUES (?, ?, ?, ?, ?)",
334
+ (
335
+ row.get("name"),
336
+ row.get("confidence"),
337
+ json.dumps(row.get("box", {})),
338
+ json.dumps(row.get("segments", {})),
339
+ json.dumps(row.get("keypoints", {})),
340
+ ),
341
+ )
342
+
343
+ conn.commit()
344
+ conn.close()
345
+ LOGGER.info(f"Results saved to SQL table '{table_name}' in '{db_path}'.")
346
+
347
+
190
348
  class SimpleClass:
191
349
  """
192
350
  A simple base class for creating objects with string representations of their attributes.
@@ -1387,7 +1545,7 @@ def deprecation_warn(arg, new_arg=None):
1387
1545
  def clean_url(url):
1388
1546
  """Strip auth from URL, i.e. https://url.com/file.txt?auth -> https://url.com/file.txt."""
1389
1547
  url = Path(url).as_posix().replace(":/", "://") # Pathlib turns :// -> :/, as_posix() for Windows
1390
- return unquote(url).split("?")[0] # '%2F' to '/', split https://url.com/file.txt?auth
1548
+ return unquote(url).split("?", 1)[0] # '%2F' to '/', split https://url.com/file.txt?auth
1391
1549
 
1392
1550
 
1393
1551
  def url2file(url):
@@ -73,22 +73,23 @@ def on_train_end(trainer):
73
73
 
74
74
  def on_train_start(trainer):
75
75
  """Run events on train start."""
76
- events(trainer.args)
76
+ events(trainer.args, trainer.device)
77
77
 
78
78
 
79
79
  def on_val_start(validator):
80
80
  """Run events on validation start."""
81
- events(validator.args)
81
+ if not validator.training:
82
+ events(validator.args, validator.device)
82
83
 
83
84
 
84
85
  def on_predict_start(predictor):
85
86
  """Run events on predict start."""
86
- events(predictor.args)
87
+ events(predictor.args, predictor.device)
87
88
 
88
89
 
89
90
  def on_export_start(exporter):
90
91
  """Run events on export start."""
91
- events(exporter.args)
92
+ events(exporter.args, exporter.device)
92
93
 
93
94
 
94
95
  callbacks = (