dgenerate-ultralytics-headless 8.3.138__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dgenerate-ultralytics-headless
3
- Version: 8.3.138
3
+ Version: 8.3.139
4
4
  Summary: Automatically built Ultralytics package with python-opencv-headless dependency instead of python-opencv
5
5
  Author-email: Glenn Jocher <glenn.jocher@ultralytics.com>, Jing Qiu <jing.qiu@ultralytics.com>
6
6
  Maintainer-email: Ultralytics <hello@ultralytics.com>
@@ -1,4 +1,4 @@
1
- dgenerate_ultralytics_headless-8.3.138.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
1
+ dgenerate_ultralytics_headless-8.3.139.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
2
2
  tests/__init__.py,sha256=xnMhv3O_DF1YrW4zk__ZywQzAaoTDjPKPoiI1Ktss1w,670
3
3
  tests/conftest.py,sha256=rsIAipRKfrVNoTaJ1LdpYue8AbcJ_fr3d3WIlM_6uXY,2982
4
4
  tests/test_cli.py,sha256=vXUC_EK0fa87JRhHsCOZf7AJQ5_Jm1sL8u-yhmsaQh0,5851
@@ -6,9 +6,9 @@ tests/test_cuda.py,sha256=L_2xp2TH-pInsdI8UrbZ5onRtHQGdUVoPXnyX6Ot4_U,7950
6
6
  tests/test_engine.py,sha256=aGqZ8P7QO5C_nOa1b4FOyk92Ysdk5WiP-ST310Vyxys,4962
7
7
  tests/test_exports.py,sha256=dhZn86LdbapW15RthQF870LGxDjC1MUZhlGdBgPmgIQ,9716
8
8
  tests/test_integrations.py,sha256=dQteeRsRVuT_p5-T88-7jqT65Zm9iAXkyKg-KQ1_TQ8,6341
9
- tests/test_python.py,sha256=KWsncKpeDdRmjRftmJpsMl7bBLI3TG_I7Lb4kuemZzQ,25618
10
- tests/test_solutions.py,sha256=4_Ce7VmsAoALN79a72nEpRgIJqx1oGnCY7yre9a-5vk,12671
11
- ultralytics/__init__.py,sha256=dNL32IT6IwReXAZE3Gp8ZeYZDxmgnF_xLn60OR9wXzg,730
9
+ tests/test_python.py,sha256=C1T9nODEyw1AUtpwmpTYO3-yx5ceQj1pfuWX1o7jXpU,25734
10
+ tests/test_solutions.py,sha256=Vscth8_3n9yGPQv2nrcloQYnPjB7V_oDdDKIb1pfUHI,12863
11
+ ultralytics/__init__.py,sha256=evr9ZL63t1w8IbSYB-xf-mDRjQo4ZpAHAtsrfWUghoU,730
12
12
  ultralytics/assets/bus.jpg,sha256=wCAZxJecGR63Od3ZRERe9Aja1Weayrb9Ug751DS_vGM,137419
13
13
  ultralytics/assets/zidane.jpg,sha256=Ftc4aeMmen1O0A3o6GCDO9FlfBslLpTAw0gnetx7bts,50427
14
14
  ultralytics/cfg/__init__.py,sha256=mpvLR68Iff4J59zYGhysSl8VwIVVzV_VMOYeVdqnYj4,39544
@@ -119,10 +119,10 @@ ultralytics/data/scripts/get_coco.sh,sha256=UuJpJeo3qQpTHVINeOpmP0NYmg8PhEFE3A8J
119
119
  ultralytics/data/scripts/get_coco128.sh,sha256=qmRQl_hOKrsdHrTrnyQuFIH01oDz3lfaz138OgGfLt8,650
120
120
  ultralytics/data/scripts/get_imagenet.sh,sha256=hr42H16bM47iT27rgS7MpEo-GeOZAYUQXgr0B2cwn48,1705
121
121
  ultralytics/engine/__init__.py,sha256=lm6MckFYCPTbqIoX7w0s_daxdjNeBeKW6DXppv1-QUM,70
122
- ultralytics/engine/exporter.py,sha256=tJTl6AkJ-OCgE1pstsu2iOSHqidkuqejPetNCg8S64k,70841
122
+ ultralytics/engine/exporter.py,sha256=BZWa7Mnl1BPvbPiD-RJs6M5Bca4sm3_MQgjoHesvXEs,70949
123
123
  ultralytics/engine/model.py,sha256=BtC5KYNrdfhryrS7b6ZXDIsmtObEeIDTePCv1gO4br4,52952
124
124
  ultralytics/engine/predictor.py,sha256=rZ5mIPeejkxUerpTfUf_1rSAklOR3THqoejlil4C04w,21651
125
- ultralytics/engine/results.py,sha256=MhbyMCwgslmtV53fqii4UJUaLQ4gKTKdkXi7vvmJDAE,79628
125
+ ultralytics/engine/results.py,sha256=2sNNhAc2zaIRaQBXl_36gAKK31V8tgNDcgC4ZPiGqKI,70072
126
126
  ultralytics/engine/trainer.py,sha256=xdgNAgq6umJ6915tiCK3U22NeY7w1HnvmAhXlwS_hYI,38955
127
127
  ultralytics/engine/tuner.py,sha256=zEW1UpLlZ6N4xbvS7MxICkshRlaFgLNfuADA0VfRpao,12629
128
128
  ultralytics/engine/validator.py,sha256=f9UUv3QqQStLrO1nojrHkdS58qYQxKXaoIQQria6WyA,17054
@@ -234,11 +234,11 @@ ultralytics/trackers/utils/__init__.py,sha256=lm6MckFYCPTbqIoX7w0s_daxdjNeBeKW6D
234
234
  ultralytics/trackers/utils/gmc.py,sha256=843LlmqWuXdUULBNpxVCZlil-_2QG-UwvscUCFbpGjA,14541
235
235
  ultralytics/trackers/utils/kalman_filter.py,sha256=A0CqOnnaKH6kr0XwuHzyHmIU6aJAjJYxF9jVlNBKZHo,21326
236
236
  ultralytics/trackers/utils/matching.py,sha256=7eIufSdeN7cXuFMjvcfvz0Ldq84m4YKZl5IGxBR8IIo,7169
237
- ultralytics/utils/__init__.py,sha256=9RF8KyUHd_YyovvZzlcnZzxx-jkxBLrxfXfkFVj64Iw,52882
237
+ ultralytics/utils/__init__.py,sha256=4U7xwGn3zbnmTm_P8pnySaY0l_yovbh6PvXJkD9P6r4,58774
238
238
  ultralytics/utils/autobatch.py,sha256=kg05q2qKg74y_Uq2vvr01i3KhLfpVR7sT0IXBt3_kyI,4921
239
239
  ultralytics/utils/autodevice.py,sha256=OKZfTbswg6SlsYGCGMqROkA-451CXGG47oeyC5Q1kFM,7232
240
240
  ultralytics/utils/benchmarks.py,sha256=lDNNnLeLUzmqKrqrqlCOiau-q7A-gcLooZP2dbxCu-U,30214
241
- ultralytics/utils/checks.py,sha256=GJbfMl608ihHSjN78Xjjky10Cglv-CYsxJ7Ra7HeH1U,33204
241
+ ultralytics/utils/checks.py,sha256=F02ASeClT_HbYaLQEvddL5ZFRursRWSTNTrSG0EWixQ,33671
242
242
  ultralytics/utils/dist.py,sha256=aytW0JEkcA5ZTZucV92ot7Bn-apiej8aLk3QNWicjAc,4103
243
243
  ultralytics/utils/downloads.py,sha256=G1nd7c7Gwjf58nZzDVpXDtoFtzhZYbjKBnwbZVMWRG0,22333
244
244
  ultralytics/utils/errors.py,sha256=vY9h2evFSrHnZdHJVVrmm8Zzw4qVDLyo9DeYW5g0dFk,1573
@@ -246,7 +246,7 @@ ultralytics/utils/export.py,sha256=Rr5R3GdJBapJJt1XHkH6VQwYN52-L_7wGiRDCgnb7BY,8
246
246
  ultralytics/utils/files.py,sha256=0K4O1cgqRiXaDw7EQK13TqA5SME_RrvfDVQSPetNr5w,8042
247
247
  ultralytics/utils/instance.py,sha256=UOEsXR9V-bXNRk6BTonASBEgeMqvzzAk4S7VdXZJUAM,18090
248
248
  ultralytics/utils/loss.py,sha256=Woc_rj7ptCyezHdylEygXMeSEgivYu_B9jJHD4UwxWE,37607
249
- ultralytics/utils/metrics.py,sha256=n8guPEADBMRNpeXNShEX-fxVv9xck8S4QaOIiaW_kl0,56037
249
+ ultralytics/utils/metrics.py,sha256=8x4S7y-rBKRkM47f_o7jfMHA1Bz8SDq3t-R1FXlQNEM,59267
250
250
  ultralytics/utils/ops.py,sha256=YFwPrKlPcgEmgAWqnJVR0Ccx5NQgp5e3P-YYHwVSP0k,34779
251
251
  ultralytics/utils/patches.py,sha256=_dhIU_eDklQE-aWIjpyjPHl_wOwZoGuIUQnXgdSwk_A,5020
252
252
  ultralytics/utils/plotting.py,sha256=oFq19c3tRng-dKHEH-j-S_wLG4CZ_mk8wqE_Gab2H8A,47221
@@ -265,8 +265,8 @@ ultralytics/utils/callbacks/neptune.py,sha256=yYUgEgSv6L39sSev6vjwhAWU3DlPDsbSDV
265
265
  ultralytics/utils/callbacks/raytune.py,sha256=A8amUGpux7dYES-L1iSeMoMXBySGWCD1aUqT7vcG-pU,1284
266
266
  ultralytics/utils/callbacks/tensorboard.py,sha256=jgYnym3cUQFAgN1GzTyO7l3jINtfAh8zhrllDvnLuVQ,5339
267
267
  ultralytics/utils/callbacks/wb.py,sha256=iDRFXI4IIDm8R5OI89DMTmjs8aHLo1HRCLkOFKdaMG4,7507
268
- dgenerate_ultralytics_headless-8.3.138.dist-info/METADATA,sha256=nEE7PWdXEk2qcDj95YSk9gnmx_YRfC-qzikx69OBVYY,38296
269
- dgenerate_ultralytics_headless-8.3.138.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
270
- dgenerate_ultralytics_headless-8.3.138.dist-info/entry_points.txt,sha256=YM_wiKyTe9yRrsEfqvYolNO5ngwfoL4-NwgKzc8_7sI,93
271
- dgenerate_ultralytics_headless-8.3.138.dist-info/top_level.txt,sha256=XP49TwiMw4QGsvTLSYiJhz1xF_k7ev5mQ8jJXaXi45Q,12
272
- dgenerate_ultralytics_headless-8.3.138.dist-info/RECORD,,
268
+ dgenerate_ultralytics_headless-8.3.139.dist-info/METADATA,sha256=39D1VzPdrUmxMBKSS9idrLRY8BcpB39cBXjRzPLyc0A,38296
269
+ dgenerate_ultralytics_headless-8.3.139.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
270
+ dgenerate_ultralytics_headless-8.3.139.dist-info/entry_points.txt,sha256=YM_wiKyTe9yRrsEfqvYolNO5ngwfoL4-NwgKzc8_7sI,93
271
+ dgenerate_ultralytics_headless-8.3.139.dist-info/top_level.txt,sha256=XP49TwiMw4QGsvTLSYiJhz1xF_k7ev5mQ8jJXaXi45Q,12
272
+ dgenerate_ultralytics_headless-8.3.139.dist-info/RECORD,,
tests/test_python.py CHANGED
@@ -198,7 +198,12 @@ def test_track_stream():
198
198
 
199
199
  def test_val():
200
200
  """Test the validation mode of the YOLO model."""
201
- YOLO(MODEL).val(data="coco8.yaml", imgsz=32)
201
+ metrics = YOLO(MODEL).val(data="coco8.yaml", imgsz=32)
202
+ metrics.to_df()
203
+ metrics.to_csv()
204
+ metrics.to_xml()
205
+ metrics.to_html()
206
+ metrics.to_json()
202
207
 
203
208
 
204
209
  def test_train_scratch():
tests/test_solutions.py CHANGED
@@ -300,6 +300,7 @@ def test_streamlit_handle_video_upload_creates_file():
300
300
  os.remove("ultralytics.mp4")
301
301
 
302
302
 
303
+ @pytest.mark.skipif(IS_RASPBERRYPI, reason="Disabled due to slow performance on Raspberry Pi.")
303
304
  def test_similarity_search_app_init():
304
305
  """Test SearchApp initializes with required attributes."""
305
306
  app = solutions.SearchApp(device="cpu")
@@ -307,6 +308,7 @@ def test_similarity_search_app_init():
307
308
  assert hasattr(app, "run")
308
309
 
309
310
 
311
+ @pytest.mark.skipif(IS_RASPBERRYPI, reason="Disabled due to slow performance on Raspberry Pi.")
310
312
  def test_similarity_search_complete(tmp_path):
311
313
  """Test VisualAISearch end-to-end with sample image and query."""
312
314
  from PIL import Image
ultralytics/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
- __version__ = "8.3.138"
3
+ __version__ = "8.3.139"
4
4
 
5
5
  import os
6
6
 
@@ -981,6 +981,7 @@ class Exporter:
981
981
  custom_input_op_name_np_data_path=np_data,
982
982
  enable_batchmatmul_unfold=True, # fix lower no. of detected objects on GPU delegate
983
983
  output_signaturedefs=True, # fix error with Attention block group convolution
984
+ disable_group_convolution=self.args.format == "tfjs", # fix TF.js error with group convolution
984
985
  optimization_for_gpu_delegate=True,
985
986
  )
986
987
  YAML.save(f / "metadata.yaml", self.metadata) # add metadata.yaml
@@ -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
  """
@@ -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.
@@ -388,21 +388,28 @@ def check_requirements(requirements=ROOT.parent / "requirements.txt", exclude=()
388
388
  pkgs.append(r)
389
389
 
390
390
  @Retry(times=2, delay=1)
391
- def attempt_install(packages, commands):
392
- """Attempt pip install command with retries on failure."""
393
- return subprocess.check_output(f"pip install --no-cache-dir {packages} {commands}", shell=True).decode()
391
+ def attempt_install(packages, commands, use_uv):
392
+ """Attempt package installation with uv if available, falling back to pip."""
393
+ if use_uv:
394
+ # Note requires --break-system-packages on ARM64 dockerfile
395
+ cmd = f"uv pip install --system --no-cache-dir {packages} {commands} --index-strategy=unsafe-best-match --break-system-packages --prerelease=allow"
396
+ else:
397
+ cmd = f"pip install --no-cache-dir {packages} {commands}"
398
+ return subprocess.check_output(cmd, shell=True).decode()
394
399
 
395
400
  s = " ".join(f'"{x}"' for x in pkgs) # console string
396
401
  if s:
397
402
  if install and AUTOINSTALL: # check environment variable
403
+ # Note uv fails on arm64 macOS and Raspberry Pi runners
404
+ uv = not ARM64 and subprocess.run(["command", "-v", "uv"], capture_output=True, shell=True).returncode == 0
398
405
  n = len(pkgs) # number of packages updates
399
406
  LOGGER.info(f"{prefix} Ultralytics requirement{'s' * (n > 1)} {pkgs} not found, attempting AutoUpdate...")
400
407
  try:
401
408
  t = time.time()
402
409
  assert ONLINE, "AutoUpdate skipped (offline)"
403
- LOGGER.info(attempt_install(s, cmds))
410
+ LOGGER.info(attempt_install(s, cmds, use_uv=uv))
404
411
  dt = time.time() - t
405
- LOGGER.info(f"{prefix} AutoUpdate success ✅ {dt:.1f}s, installed {n} package{'s' * (n > 1)}: {pkgs}")
412
+ LOGGER.info(f"{prefix} AutoUpdate success ✅ {dt:.1f}s")
406
413
  LOGGER.warning(
407
414
  f"{prefix} {colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n"
408
415
  )
@@ -8,7 +8,7 @@ from pathlib import Path
8
8
  import numpy as np
9
9
  import torch
10
10
 
11
- from ultralytics.utils import LOGGER, SimpleClass, TryExcept, checks, plt_settings
11
+ from ultralytics.utils import LOGGER, DataExportMixin, SimpleClass, TryExcept, checks, plt_settings
12
12
 
13
13
  OKS_SIGMA = (
14
14
  np.array([0.26, 0.25, 0.25, 0.35, 0.35, 0.79, 0.79, 0.72, 0.72, 0.62, 0.62, 1.07, 1.07, 0.87, 0.87, 0.89, 0.89])
@@ -865,7 +865,7 @@ class Metric(SimpleClass):
865
865
  ]
866
866
 
867
867
 
868
- class DetMetrics(SimpleClass):
868
+ class DetMetrics(SimpleClass, DataExportMixin):
869
869
  """
870
870
  Utility class for computing detection metrics such as precision, recall, and mean average precision (mAP).
871
871
 
@@ -961,8 +961,29 @@ class DetMetrics(SimpleClass):
961
961
  """Return dictionary of computed performance metrics and statistics."""
962
962
  return self.box.curves_results
963
963
 
964
+ def summary(self, **kwargs):
965
+ """Returns per-class detection metrics with shared scalar values included."""
966
+ scalars = {
967
+ "box-map": self.box.map,
968
+ "box-map50": self.box.map50,
969
+ "box-map75": self.box.map75,
970
+ }
971
+ per_class = {
972
+ "box-p": self.box.p,
973
+ "box-r": self.box.r,
974
+ "box-f1": self.box.f1,
975
+ }
976
+ return [
977
+ {
978
+ "class_name": self.names[i] if hasattr(self, "names") and i in self.names else str(i),
979
+ **{k: v[i] for k, v in per_class.items()},
980
+ **scalars,
981
+ }
982
+ for i in range(len(next(iter(per_class.values()), [])))
983
+ ]
964
984
 
965
- class SegmentMetrics(SimpleClass):
985
+
986
+ class SegmentMetrics(SimpleClass, DataExportMixin):
966
987
  """
967
988
  Calculates and aggregates detection and segmentation metrics over a given set of classes.
968
989
 
@@ -1097,6 +1118,29 @@ class SegmentMetrics(SimpleClass):
1097
1118
  """Return dictionary of computed performance metrics and statistics."""
1098
1119
  return self.box.curves_results + self.seg.curves_results
1099
1120
 
1121
+ def summary(self, **kwargs):
1122
+ """Returns per-class segmentation metrics with shared scalar values included (box + mask)."""
1123
+ scalars = {
1124
+ "box-map": self.box.map,
1125
+ "box-map50": self.box.map50,
1126
+ "box-map75": self.box.map75,
1127
+ "mask-map": self.seg.map,
1128
+ "mask-map50": self.seg.map50,
1129
+ "mask-map75": self.seg.map75,
1130
+ }
1131
+ per_class = {
1132
+ "box-p": self.box.p,
1133
+ "box-r": self.box.r,
1134
+ "box-f1": self.box.f1,
1135
+ "mask-p": self.seg.p,
1136
+ "mask-r": self.seg.r,
1137
+ "mask-f1": self.seg.f1,
1138
+ }
1139
+ return [
1140
+ {"class_name": self.names[i], **{k: v[i] for k, v in per_class.items()}, **scalars}
1141
+ for i in range(len(next(iter(per_class.values()), [])))
1142
+ ]
1143
+
1100
1144
 
1101
1145
  class PoseMetrics(SegmentMetrics):
1102
1146
  """
@@ -1229,8 +1273,31 @@ class PoseMetrics(SegmentMetrics):
1229
1273
  """Return dictionary of computed performance metrics and statistics."""
1230
1274
  return self.box.curves_results + self.pose.curves_results
1231
1275
 
1276
+ def summary(self, **kwargs):
1277
+ """Returns per-class pose metrics with shared scalar values included (box + pose)."""
1278
+ scalars = {
1279
+ "box-map": self.box.map,
1280
+ "box-map50": self.box.map50,
1281
+ "box-map75": self.box.map75,
1282
+ "pose-map": self.pose.map,
1283
+ "pose-map50": self.pose.map50,
1284
+ "pose-map75": self.pose.map75,
1285
+ }
1286
+ per_class = {
1287
+ "box-p": self.box.p,
1288
+ "box-r": self.box.r,
1289
+ "box-f1": self.box.f1,
1290
+ "pose-p": self.pose.p,
1291
+ "pose-r": self.pose.r,
1292
+ "pose-f1": self.pose.f1,
1293
+ }
1294
+ return [
1295
+ {"class_name": self.names[i], **{k: v[i] for k, v in per_class.items()}, **scalars}
1296
+ for i in range(len(next(iter(per_class.values()), [])))
1297
+ ]
1298
+
1232
1299
 
1233
- class ClassifyMetrics(SimpleClass):
1300
+ class ClassifyMetrics(SimpleClass, DataExportMixin):
1234
1301
  """
1235
1302
  Class for computing classification metrics including top-1 and top-5 accuracy.
1236
1303
 
@@ -1286,8 +1353,12 @@ class ClassifyMetrics(SimpleClass):
1286
1353
  """Return a list of curves for accessing specific metrics curves."""
1287
1354
  return []
1288
1355
 
1356
+ def summary(self, **kwargs):
1357
+ """Returns a single-row summary for classification metrics (top1/top5)."""
1358
+ return [{"classify-top1": self.top1, "classify-top5": self.top5}]
1359
+
1289
1360
 
1290
- class OBBMetrics(SimpleClass):
1361
+ class OBBMetrics(SimpleClass, DataExportMixin):
1291
1362
  """
1292
1363
  Metrics for evaluating oriented bounding box (OBB) detection.
1293
1364
 
@@ -1316,6 +1387,7 @@ class OBBMetrics(SimpleClass):
1316
1387
  self.names = names
1317
1388
  self.box = Metric()
1318
1389
  self.speed = {"preprocess": 0.0, "inference": 0.0, "loss": 0.0, "postprocess": 0.0}
1390
+ self.task = "obb"
1319
1391
 
1320
1392
  def process(self, tp, conf, pred_cls, target_cls, on_plot=None):
1321
1393
  """
@@ -1383,3 +1455,16 @@ class OBBMetrics(SimpleClass):
1383
1455
  def curves_results(self):
1384
1456
  """Return a list of curves for accessing specific metrics curves."""
1385
1457
  return []
1458
+
1459
+ def summary(self, **kwargs):
1460
+ """Returns per-class detection metrics with shared scalar values included."""
1461
+ scalars = {
1462
+ "box-map": self.box.map,
1463
+ "box-map50": self.box.map50,
1464
+ "box-map75": self.box.map75,
1465
+ }
1466
+ per_class = {"box-p": self.box.p, "box-r": self.box.r, "box-f1": self.box.f1}
1467
+ return [
1468
+ {"class_name": self.names[i], **{k: v[i] for k, v in per_class.items()}, **scalars}
1469
+ for i in range(len(next(iter(per_class.values()), [])))
1470
+ ]