dgenerate-ultralytics-headless 8.3.221__py3-none-any.whl → 8.3.223__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 (29) hide show
  1. {dgenerate_ultralytics_headless-8.3.221.dist-info → dgenerate_ultralytics_headless-8.3.223.dist-info}/METADATA +2 -2
  2. {dgenerate_ultralytics_headless-8.3.221.dist-info → dgenerate_ultralytics_headless-8.3.223.dist-info}/RECORD +29 -27
  3. tests/test_python.py +5 -5
  4. ultralytics/__init__.py +1 -1
  5. ultralytics/cfg/datasets/ImageNet.yaml +1 -1
  6. ultralytics/cfg/datasets/lvis.yaml +5 -5
  7. ultralytics/cfg/datasets/open-images-v7.yaml +1 -1
  8. ultralytics/data/base.py +1 -1
  9. ultralytics/data/utils.py +1 -1
  10. ultralytics/engine/exporter.py +46 -110
  11. ultralytics/engine/model.py +1 -1
  12. ultralytics/engine/trainer.py +1 -1
  13. ultralytics/models/rtdetr/val.py +1 -1
  14. ultralytics/models/yolo/classify/train.py +2 -2
  15. ultralytics/nn/autobackend.py +1 -1
  16. ultralytics/nn/modules/head.py +5 -30
  17. ultralytics/utils/__init__.py +4 -4
  18. ultralytics/utils/benchmarks.py +3 -1
  19. ultralytics/utils/export/__init__.py +4 -239
  20. ultralytics/utils/export/engine.py +240 -0
  21. ultralytics/utils/export/imx.py +39 -28
  22. ultralytics/utils/export/tensorflow.py +221 -0
  23. ultralytics/utils/metrics.py +2 -2
  24. ultralytics/utils/nms.py +4 -2
  25. ultralytics/utils/plotting.py +1 -1
  26. {dgenerate_ultralytics_headless-8.3.221.dist-info → dgenerate_ultralytics_headless-8.3.223.dist-info}/WHEEL +0 -0
  27. {dgenerate_ultralytics_headless-8.3.221.dist-info → dgenerate_ultralytics_headless-8.3.223.dist-info}/entry_points.txt +0 -0
  28. {dgenerate_ultralytics_headless-8.3.221.dist-info → dgenerate_ultralytics_headless-8.3.223.dist-info}/licenses/LICENSE +0 -0
  29. {dgenerate_ultralytics_headless-8.3.221.dist-info → dgenerate_ultralytics_headless-8.3.223.dist-info}/top_level.txt +0 -0
@@ -6,6 +6,7 @@ import subprocess
6
6
  import types
7
7
  from pathlib import Path
8
8
 
9
+ import numpy as np
9
10
  import torch
10
11
 
11
12
  from ultralytics.nn.modules import Detect, Pose
@@ -13,6 +14,32 @@ from ultralytics.utils import LOGGER
13
14
  from ultralytics.utils.tal import make_anchors
14
15
  from ultralytics.utils.torch_utils import copy_attr
15
16
 
17
+ # Configuration for Model Compression Toolkit (MCT) quantization
18
+ MCT_CONFIG = {
19
+ "YOLO11": {
20
+ "detect": {
21
+ "layer_names": ["sub", "mul_2", "add_14", "cat_21"],
22
+ "weights_memory": 2585350.2439,
23
+ "n_layers": 238,
24
+ },
25
+ "pose": {
26
+ "layer_names": ["sub", "mul_2", "add_14", "cat_22", "cat_23", "mul_4", "add_15"],
27
+ "weights_memory": 2437771.67,
28
+ "n_layers": 257,
29
+ },
30
+ "classify": {"layer_names": [], "weights_memory": np.inf, "n_layers": 112},
31
+ },
32
+ "YOLOv8": {
33
+ "detect": {"layer_names": ["sub", "mul", "add_6", "cat_17"], "weights_memory": 2550540.8, "n_layers": 168},
34
+ "pose": {
35
+ "layer_names": ["add_7", "mul_2", "cat_19", "mul", "sub", "add_6", "cat_18"],
36
+ "weights_memory": 2482451.85,
37
+ "n_layers": 187,
38
+ },
39
+ "classify": {"layer_names": [], "weights_memory": np.inf, "n_layers": 73},
40
+ },
41
+ }
42
+
16
43
 
17
44
  class FXModel(torch.nn.Module):
18
45
  """
@@ -200,30 +227,13 @@ def torch2imx(
200
227
  tpc = get_target_platform_capabilities(tpc_version="4.0", device_type="imx500")
201
228
 
202
229
  bit_cfg = mct.core.BitWidthConfig()
203
- if "C2PSA" in model.__str__(): # YOLO11
204
- if model.task == "detect":
205
- layer_names = ["sub", "mul_2", "add_14", "cat_21"]
206
- weights_memory = 2585350.2439
207
- n_layers = 238 # 238 layers for fused YOLO11n
208
- elif model.task == "pose":
209
- layer_names = ["sub", "mul_2", "add_14", "cat_22", "cat_23", "mul_4", "add_15"]
210
- weights_memory = 2437771.67
211
- n_layers = 257 # 257 layers for fused YOLO11n-pose
212
- else: # YOLOv8
213
- if model.task == "detect":
214
- layer_names = ["sub", "mul", "add_6", "cat_17"]
215
- weights_memory = 2550540.8
216
- n_layers = 168 # 168 layers for fused YOLOv8n
217
- elif model.task == "pose":
218
- layer_names = ["add_7", "mul_2", "cat_19", "mul", "sub", "add_6", "cat_18"]
219
- weights_memory = 2482451.85
220
- n_layers = 187 # 187 layers for fused YOLO11n-pose
230
+ mct_config = MCT_CONFIG["YOLO11" if "C2PSA" in model.__str__() else "YOLOv8"][model.task]
221
231
 
222
232
  # Check if the model has the expected number of layers
223
- if len(list(model.modules())) != n_layers:
233
+ if len(list(model.modules())) != mct_config["n_layers"]:
224
234
  raise ValueError("IMX export only supported for YOLOv8n and YOLO11n models.")
225
235
 
226
- for layer_name in layer_names:
236
+ for layer_name in mct_config["layer_names"]:
227
237
  bit_cfg.set_manual_activation_bit_width([mct.core.common.network_editors.NodeNameFilter(layer_name)], 16)
228
238
 
229
239
  config = mct.core.CoreConfig(
@@ -232,7 +242,7 @@ def torch2imx(
232
242
  bit_width_config=bit_cfg,
233
243
  )
234
244
 
235
- resource_utilization = mct.core.ResourceUtilization(weights_memory=weights_memory)
245
+ resource_utilization = mct.core.ResourceUtilization(weights_memory=mct_config["weights_memory"])
236
246
 
237
247
  quant_model = (
238
248
  mct.gptq.pytorch_gradient_post_training_quantization( # Perform Gradient-Based Post Training Quantization
@@ -255,13 +265,14 @@ def torch2imx(
255
265
  )[0]
256
266
  )
257
267
 
258
- quant_model = NMSWrapper(
259
- model=quant_model,
260
- score_threshold=conf or 0.001,
261
- iou_threshold=iou,
262
- max_detections=max_det,
263
- task=model.task,
264
- )
268
+ if model.task != "classify":
269
+ quant_model = NMSWrapper(
270
+ model=quant_model,
271
+ score_threshold=conf or 0.001,
272
+ iou_threshold=iou,
273
+ max_detections=max_det,
274
+ task=model.task,
275
+ )
265
276
 
266
277
  f = Path(str(file).replace(file.suffix, "_imx_model"))
267
278
  f.mkdir(exist_ok=True)
@@ -0,0 +1,221 @@
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import numpy as np
8
+ import torch
9
+
10
+ from ultralytics.nn.modules import Detect, Pose
11
+ from ultralytics.utils import LOGGER
12
+ from ultralytics.utils.downloads import attempt_download_asset
13
+ from ultralytics.utils.files import spaces_in_path
14
+ from ultralytics.utils.tal import make_anchors
15
+
16
+
17
+ def tf_wrapper(model: torch.nn.Module) -> torch.nn.Module:
18
+ """A wrapper to add TensorFlow compatible inference methods to Detect and Pose layers."""
19
+ for m in model.modules():
20
+ if not isinstance(m, Detect):
21
+ continue
22
+ import types
23
+
24
+ m._inference = types.MethodType(_tf_inference, m)
25
+ if type(m) is Pose:
26
+ m.kpts_decode = types.MethodType(tf_kpts_decode, m)
27
+ return model
28
+
29
+
30
+ def _tf_inference(self, x: list[torch.Tensor]) -> tuple[torch.Tensor]:
31
+ """Decode boxes and cls scores for tf object detection."""
32
+ shape = x[0].shape # BCHW
33
+ x_cat = torch.cat([xi.view(x[0].shape[0], self.no, -1) for xi in x], 2)
34
+ box, cls = x_cat.split((self.reg_max * 4, self.nc), 1)
35
+ if self.dynamic or self.shape != shape:
36
+ self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x, self.stride, 0.5))
37
+ self.shape = shape
38
+ grid_h, grid_w = shape[2], shape[3]
39
+ grid_size = torch.tensor([grid_w, grid_h, grid_w, grid_h], device=box.device).reshape(1, 4, 1)
40
+ norm = self.strides / (self.stride[0] * grid_size)
41
+ dbox = self.decode_bboxes(self.dfl(box) * norm, self.anchors.unsqueeze(0) * norm[:, :2])
42
+ return torch.cat((dbox, cls.sigmoid()), 1)
43
+
44
+
45
+ def tf_kpts_decode(self, bs: int, kpts: torch.Tensor) -> torch.Tensor:
46
+ """Decode keypoints for tf pose estimation."""
47
+ ndim = self.kpt_shape[1]
48
+ # required for TFLite export to avoid 'PLACEHOLDER_FOR_GREATER_OP_CODES' bug
49
+ # Precompute normalization factor to increase numerical stability
50
+ y = kpts.view(bs, *self.kpt_shape, -1)
51
+ grid_h, grid_w = self.shape[2], self.shape[3]
52
+ grid_size = torch.tensor([grid_w, grid_h], device=y.device).reshape(1, 2, 1)
53
+ norm = self.strides / (self.stride[0] * grid_size)
54
+ a = (y[:, :, :2] * 2.0 + (self.anchors - 0.5)) * norm
55
+ if ndim == 3:
56
+ a = torch.cat((a, y[:, :, 2:3].sigmoid()), 2)
57
+ return a.view(bs, self.nk, -1)
58
+
59
+
60
+ def onnx2saved_model(
61
+ onnx_file: str,
62
+ output_dir: Path,
63
+ int8: bool = False,
64
+ images: np.ndarray = None,
65
+ disable_group_convolution: bool = False,
66
+ prefix="",
67
+ ):
68
+ """
69
+ Convert a ONNX model to TensorFlow SavedModel format via ONNX.
70
+
71
+ Args:
72
+ onnx_file (str): ONNX file path.
73
+ output_dir (Path): Output directory path for the SavedModel.
74
+ int8 (bool, optional): Enable INT8 quantization. Defaults to False.
75
+ images (np.ndarray, optional): Calibration images for INT8 quantization in BHWC format.
76
+ disable_group_convolution (bool, optional): Disable group convolution optimization. Defaults to False.
77
+ prefix (str, optional): Logging prefix. Defaults to "".
78
+
79
+ Returns:
80
+ (keras.Model): Converted Keras model.
81
+
82
+ Note:
83
+ Requires onnx2tf package. Downloads calibration data if INT8 quantization is enabled.
84
+ Removes temporary files and renames quantized models after conversion.
85
+ """
86
+ # Pre-download calibration file to fix https://github.com/PINTO0309/onnx2tf/issues/545
87
+ onnx2tf_file = Path("calibration_image_sample_data_20x128x128x3_float32.npy")
88
+ if not onnx2tf_file.exists():
89
+ attempt_download_asset(f"{onnx2tf_file}.zip", unzip=True, delete=True)
90
+ np_data = None
91
+ if int8:
92
+ tmp_file = output_dir / "tmp_tflite_int8_calibration_images.npy" # int8 calibration images file
93
+ if images is not None:
94
+ output_dir.mkdir()
95
+ np.save(str(tmp_file), images) # BHWC
96
+ np_data = [["images", tmp_file, [[[[0, 0, 0]]]], [[[[255, 255, 255]]]]]]
97
+
98
+ import onnx2tf # scoped for after ONNX export for reduced conflict during import
99
+
100
+ LOGGER.info(f"{prefix} starting TFLite export with onnx2tf {onnx2tf.__version__}...")
101
+ keras_model = onnx2tf.convert(
102
+ input_onnx_file_path=onnx_file,
103
+ output_folder_path=str(output_dir),
104
+ not_use_onnxsim=True,
105
+ verbosity="error", # note INT8-FP16 activation bug https://github.com/ultralytics/ultralytics/issues/15873
106
+ output_integer_quantized_tflite=int8,
107
+ custom_input_op_name_np_data_path=np_data,
108
+ enable_batchmatmul_unfold=True and not int8, # fix lower no. of detected objects on GPU delegate
109
+ output_signaturedefs=True, # fix error with Attention block group convolution
110
+ disable_group_convolution=disable_group_convolution, # fix error with group convolution
111
+ )
112
+
113
+ # Remove/rename TFLite models
114
+ if int8:
115
+ tmp_file.unlink(missing_ok=True)
116
+ for file in output_dir.rglob("*_dynamic_range_quant.tflite"):
117
+ file.rename(file.with_name(file.stem.replace("_dynamic_range_quant", "_int8") + file.suffix))
118
+ for file in output_dir.rglob("*_integer_quant_with_int16_act.tflite"):
119
+ file.unlink() # delete extra fp16 activation TFLite files
120
+ return keras_model
121
+
122
+
123
+ def keras2pb(keras_model, file: Path, prefix=""):
124
+ """
125
+ Convert a Keras model to TensorFlow GraphDef (.pb) format.
126
+
127
+ Args:
128
+ keras_model(tf_keras): Keras model to convert to frozen graph format.
129
+ file (Path): Output file path (suffix will be changed to .pb).
130
+ prefix (str, optional): Logging prefix. Defaults to "".
131
+
132
+ Note:
133
+ Creates a frozen graph by converting variables to constants for inference optimization.
134
+ """
135
+ import tensorflow as tf
136
+ from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
137
+
138
+ LOGGER.info(f"\n{prefix} starting export with tensorflow {tf.__version__}...")
139
+ m = tf.function(lambda x: keras_model(x)) # full model
140
+ m = m.get_concrete_function(tf.TensorSpec(keras_model.inputs[0].shape, keras_model.inputs[0].dtype))
141
+ frozen_func = convert_variables_to_constants_v2(m)
142
+ frozen_func.graph.as_graph_def()
143
+ tf.io.write_graph(graph_or_graph_def=frozen_func.graph, logdir=str(file.parent), name=file.name, as_text=False)
144
+
145
+
146
+ def tflite2edgetpu(tflite_file: str | Path, output_dir: str | Path, prefix: str = ""):
147
+ """
148
+ Convert a TensorFlow Lite model to Edge TPU format using the Edge TPU compiler.
149
+
150
+ Args:
151
+ tflite_file (str | Path): Path to the input TensorFlow Lite (.tflite) model file.
152
+ output_dir (str | Path): Output directory path for the compiled Edge TPU model.
153
+ prefix (str, optional): Logging prefix. Defaults to "".
154
+
155
+ Note:
156
+ Requires the Edge TPU compiler to be installed. The function compiles the TFLite model
157
+ for optimal performance on Google's Edge TPU hardware accelerator.
158
+ """
159
+ import subprocess
160
+
161
+ cmd = (
162
+ "edgetpu_compiler "
163
+ f'--out_dir "{output_dir}" '
164
+ "--show_operations "
165
+ "--search_delegate "
166
+ "--delegate_search_step 30 "
167
+ "--timeout_sec 180 "
168
+ f'"{tflite_file}"'
169
+ )
170
+ LOGGER.info(f"{prefix} running '{cmd}'")
171
+ subprocess.run(cmd, shell=True)
172
+
173
+
174
+ def pb2tfjs(pb_file: str, output_dir: str, half: bool = False, int8: bool = False, prefix: str = ""):
175
+ """
176
+ Convert a TensorFlow GraphDef (.pb) model to TensorFlow.js format.
177
+
178
+ Args:
179
+ pb_file (str): Path to the input TensorFlow GraphDef (.pb) model file.
180
+ output_dir (str): Output directory path for the converted TensorFlow.js model.
181
+ half (bool, optional): Enable FP16 quantization. Defaults to False.
182
+ int8 (bool, optional): Enable INT8 quantization. Defaults to False.
183
+ prefix (str, optional): Logging prefix. Defaults to "".
184
+
185
+ Note:
186
+ Requires tensorflowjs package. Uses tensorflowjs_converter command-line tool for conversion.
187
+ Handles spaces in file paths and warns if output directory contains spaces.
188
+ """
189
+ import subprocess
190
+
191
+ import tensorflow as tf
192
+ import tensorflowjs as tfjs
193
+
194
+ LOGGER.info(f"\n{prefix} starting export with tensorflowjs {tfjs.__version__}...")
195
+
196
+ gd = tf.Graph().as_graph_def() # TF GraphDef
197
+ with open(pb_file, "rb") as file:
198
+ gd.ParseFromString(file.read())
199
+ outputs = ",".join(gd_outputs(gd))
200
+ LOGGER.info(f"\n{prefix} output node names: {outputs}")
201
+
202
+ quantization = "--quantize_float16" if half else "--quantize_uint8" if int8 else ""
203
+ with spaces_in_path(pb_file) as fpb_, spaces_in_path(output_dir) as f_: # exporter can not handle spaces in path
204
+ cmd = (
205
+ "tensorflowjs_converter "
206
+ f'--input_format=tf_frozen_model {quantization} --output_node_names={outputs} "{fpb_}" "{f_}"'
207
+ )
208
+ LOGGER.info(f"{prefix} running '{cmd}'")
209
+ subprocess.run(cmd, shell=True)
210
+
211
+ if " " in output_dir:
212
+ LOGGER.warning(f"{prefix} your model may not work correctly with spaces in path '{output_dir}'.")
213
+
214
+
215
+ def gd_outputs(gd):
216
+ """Return TensorFlow GraphDef model output node names."""
217
+ name_list, input_list = [], []
218
+ for node in gd.node: # tensorflow.core.framework.node_def_pb2.NodeDef
219
+ name_list.append(node.name)
220
+ input_list.extend(node.input)
221
+ return sorted(f"{x}:0" for x in list(set(name_list) - set(input_list)) if not x.startswith("NoOp"))
@@ -663,7 +663,7 @@ def plot_pr_curve(
663
663
  for i, y in enumerate(py.T):
664
664
  ax.plot(px, y, linewidth=1, label=f"{names[i]} {ap[i, 0]:.3f}") # plot(recall, precision)
665
665
  else:
666
- ax.plot(px, py, linewidth=1, color="grey") # plot(recall, precision)
666
+ ax.plot(px, py, linewidth=1, color="gray") # plot(recall, precision)
667
667
 
668
668
  ax.plot(px, py.mean(1), linewidth=3, color="blue", label=f"all classes {ap[:, 0].mean():.3f} mAP@0.5")
669
669
  ax.set_xlabel("Recall")
@@ -708,7 +708,7 @@ def plot_mc_curve(
708
708
  for i, y in enumerate(py):
709
709
  ax.plot(px, y, linewidth=1, label=f"{names[i]}") # plot(confidence, metric)
710
710
  else:
711
- ax.plot(px, py.T, linewidth=1, color="grey") # plot(confidence, metric)
711
+ ax.plot(px, py.T, linewidth=1, color="gray") # plot(confidence, metric)
712
712
 
713
713
  y = smooth(py.mean(0), 0.1)
714
714
  ax.plot(px, y, linewidth=3, color="blue", label=f"all classes {y.max():.2f} at {px[y.argmax()]:.3f}")
ultralytics/utils/nms.py CHANGED
@@ -231,9 +231,11 @@ class TorchNMS:
231
231
  upper_mask = row_idx < col_idx
232
232
  ious = ious * upper_mask
233
233
  # Zeroing these scores ensures the additional indices would not affect the final results
234
- scores[~((ious >= iou_threshold).sum(0) <= 0)] = 0
234
+ scores_ = scores[sorted_idx]
235
+ scores_[~((ious >= iou_threshold).sum(0) <= 0)] = 0
236
+ scores[sorted_idx] = scores_ # update original tensor for NMSModel
235
237
  # NOTE: return indices with fixed length to avoid TFLite reshape error
236
- pick = torch.topk(scores, scores.shape[0]).indices
238
+ pick = torch.topk(scores_, scores_.shape[0]).indices
237
239
  return sorted_idx[pick]
238
240
 
239
241
  @staticmethod
@@ -722,7 +722,7 @@ def plot_images(
722
722
  convert tensor inputs to numpy arrays for processing.
723
723
 
724
724
  Channel Support:
725
- - 1 channel: Greyscale
725
+ - 1 channel: Grayscale
726
726
  - 2 channels: Third channel added as zeros
727
727
  - 3 channels: Used as-is (standard RGB)
728
728
  - 4+ channels: Cropped to first 3 channels