ultralytics 8.3.191__py3-none-any.whl → 8.3.193__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 (34) hide show
  1. ultralytics/__init__.py +1 -1
  2. ultralytics/cfg/__init__.py +7 -5
  3. ultralytics/cfg/datasets/SKU-110K.yaml +1 -1
  4. ultralytics/cfg/datasets/xView.yaml +1 -1
  5. ultralytics/data/utils.py +1 -1
  6. ultralytics/engine/exporter.py +5 -4
  7. ultralytics/engine/model.py +4 -4
  8. ultralytics/engine/predictor.py +7 -3
  9. ultralytics/engine/trainer.py +5 -5
  10. ultralytics/engine/tuner.py +227 -40
  11. ultralytics/models/yolo/classify/train.py +2 -2
  12. ultralytics/models/yolo/classify/val.py +1 -1
  13. ultralytics/models/yolo/detect/val.py +1 -1
  14. ultralytics/models/yolo/pose/val.py +1 -1
  15. ultralytics/models/yolo/segment/val.py +14 -14
  16. ultralytics/models/yolo/world/train.py +1 -1
  17. ultralytics/models/yolo/yoloe/train.py +3 -4
  18. ultralytics/models/yolo/yoloe/val.py +3 -3
  19. ultralytics/nn/__init__.py +2 -4
  20. ultralytics/nn/autobackend.py +2 -2
  21. ultralytics/nn/tasks.py +2 -51
  22. ultralytics/utils/__init__.py +5 -1
  23. ultralytics/utils/checks.py +2 -1
  24. ultralytics/utils/plotting.py +2 -2
  25. ultralytics/utils/tal.py +2 -2
  26. ultralytics/utils/torch_utils.py +7 -6
  27. ultralytics/utils/tqdm.py +50 -74
  28. ultralytics/utils/tuner.py +1 -1
  29. {ultralytics-8.3.191.dist-info → ultralytics-8.3.193.dist-info}/METADATA +1 -1
  30. {ultralytics-8.3.191.dist-info → ultralytics-8.3.193.dist-info}/RECORD +34 -34
  31. {ultralytics-8.3.191.dist-info → ultralytics-8.3.193.dist-info}/WHEEL +0 -0
  32. {ultralytics-8.3.191.dist-info → ultralytics-8.3.193.dist-info}/entry_points.txt +0 -0
  33. {ultralytics-8.3.191.dist-info → ultralytics-8.3.193.dist-info}/licenses/LICENSE +0 -0
  34. {ultralytics-8.3.191.dist-info → ultralytics-8.3.193.dist-info}/top_level.txt +0 -0
@@ -63,7 +63,7 @@ class SegmentationValidator(DetectionValidator):
63
63
  (Dict[str, Any]): Preprocessed batch.
64
64
  """
65
65
  batch = super().preprocess(batch)
66
- batch["masks"] = batch["masks"].to(self.device).float()
66
+ batch["masks"] = batch["masks"].to(self.device, non_blocking=True).float()
67
67
  return batch
68
68
 
69
69
  def init_metrics(self, model: torch.nn.Module) -> None:
@@ -133,8 +133,17 @@ class SegmentationValidator(DetectionValidator):
133
133
  (Dict[str, Any]): Prepared batch with processed annotations.
134
134
  """
135
135
  prepared_batch = super()._prepare_batch(si, batch)
136
- midx = [si] if self.args.overlap_mask else batch["batch_idx"] == si
137
- prepared_batch["masks"] = batch["masks"][midx]
136
+ nl = len(prepared_batch["cls"])
137
+ if self.args.overlap_mask:
138
+ masks = batch["masks"][si]
139
+ index = torch.arange(1, nl + 1, device=masks.device).view(nl, 1, 1)
140
+ masks = (masks == index).float()
141
+ else:
142
+ masks = batch["masks"][batch["batch_idx"] == si]
143
+ if nl and self.process is ops.process_mask_native:
144
+ masks = F.interpolate(masks[None], prepared_batch["imgsz"], mode="bilinear", align_corners=False)[0]
145
+ masks = masks.gt_(0.5)
146
+ prepared_batch["masks"] = masks
138
147
  return prepared_batch
139
148
 
140
149
  def _process_batch(self, preds: dict[str, torch.Tensor], batch: dict[str, Any]) -> dict[str, np.ndarray]:
@@ -158,20 +167,11 @@ class SegmentationValidator(DetectionValidator):
158
167
  >>> correct_preds = validator._process_batch(preds, batch)
159
168
  """
160
169
  tp = super()._process_batch(preds, batch)
161
- gt_cls, gt_masks = batch["cls"], batch["masks"]
170
+ gt_cls = batch["cls"]
162
171
  if len(gt_cls) == 0 or len(preds["cls"]) == 0:
163
172
  tp_m = np.zeros((len(preds["cls"]), self.niou), dtype=bool)
164
173
  else:
165
- pred_masks = preds["masks"]
166
- if self.args.overlap_mask:
167
- nl = len(gt_cls)
168
- index = torch.arange(nl, device=gt_masks.device).view(nl, 1, 1) + 1
169
- gt_masks = gt_masks.repeat(nl, 1, 1) # shape(1,640,640) -> (n,640,640)
170
- gt_masks = torch.where(gt_masks == index, 1.0, 0.0)
171
- if gt_masks.shape[1:] != pred_masks.shape[1:]:
172
- gt_masks = F.interpolate(gt_masks[None], pred_masks.shape[1:], mode="bilinear", align_corners=False)[0]
173
- gt_masks = gt_masks.gt_(0.5)
174
- iou = mask_iou(gt_masks.view(gt_masks.shape[0], -1), pred_masks.view(pred_masks.shape[0], -1))
174
+ iou = mask_iou(batch["masks"].flatten(1), preds["masks"].flatten(1))
175
175
  tp_m = self.match_predictions(preds["cls"], gt_cls, iou).cpu().numpy()
176
176
  tp.update({"tp_m": tp_m}) # update tp with mask IoU
177
177
  return tp
@@ -171,7 +171,7 @@ class WorldTrainer(DetectionTrainer):
171
171
 
172
172
  # Add text features
173
173
  texts = list(itertools.chain(*batch["texts"]))
174
- txt_feats = torch.stack([self.text_embeddings[text] for text in texts]).to(self.device)
174
+ txt_feats = torch.stack([self.text_embeddings[text] for text in texts]).to(self.device, non_blocking=True)
175
175
  txt_feats = txt_feats / txt_feats.norm(p=2, dim=-1, keepdim=True)
176
176
  batch["txt_feats"] = txt_feats.reshape(len(batch["texts"]), -1, txt_feats.shape[-1])
177
177
  return batch
@@ -197,7 +197,7 @@ class YOLOETrainerFromScratch(YOLOETrainer, WorldTrainerFromScratch):
197
197
  batch = DetectionTrainer.preprocess_batch(self, batch)
198
198
 
199
199
  texts = list(itertools.chain(*batch["texts"]))
200
- txt_feats = torch.stack([self.text_embeddings[text] for text in texts]).to(self.device)
200
+ txt_feats = torch.stack([self.text_embeddings[text] for text in texts]).to(self.device, non_blocking=True)
201
201
  txt_feats = txt_feats.reshape(len(batch["texts"]), -1, txt_feats.shape[-1])
202
202
  batch["txt_feats"] = txt_feats
203
203
  return batch
@@ -251,8 +251,7 @@ class YOLOEPEFreeTrainer(YOLOEPETrainer, YOLOETrainerFromScratch):
251
251
 
252
252
  def preprocess_batch(self, batch):
253
253
  """Preprocess a batch of images for YOLOE training, adjusting formatting and dimensions as needed."""
254
- batch = DetectionTrainer.preprocess_batch(self, batch)
255
- return batch
254
+ return DetectionTrainer.preprocess_batch(self, batch)
256
255
 
257
256
  def set_text_embeddings(self, datasets, batch: int):
258
257
  """
@@ -318,5 +317,5 @@ class YOLOEVPTrainer(YOLOETrainerFromScratch):
318
317
  def preprocess_batch(self, batch):
319
318
  """Preprocess a batch of images for YOLOE training, moving visual prompts to the appropriate device."""
320
319
  batch = super().preprocess_batch(batch)
321
- batch["visuals"] = batch["visuals"].to(self.device)
320
+ batch["visuals"] = batch["visuals"].to(self.device, non_blocking=True)
322
321
  return batch
@@ -102,7 +102,7 @@ class YOLOEDetectValidator(DetectionValidator):
102
102
  """Preprocess batch data, ensuring visuals are on the same device as images."""
103
103
  batch = super().preprocess(batch)
104
104
  if "visuals" in batch:
105
- batch["visuals"] = batch["visuals"].to(batch["img"].device)
105
+ batch["visuals"] = batch["visuals"].to(batch["img"].device, non_blocking=True)
106
106
  return batch
107
107
 
108
108
  def get_vpe_dataloader(self, data: dict[str, Any]) -> torch.utils.data.DataLoader:
@@ -186,9 +186,9 @@ class YOLOEDetectValidator(DetectionValidator):
186
186
  self.device = select_device(self.args.device, verbose=False)
187
187
 
188
188
  if isinstance(model, (str, Path)):
189
- from ultralytics.nn.tasks import attempt_load_weights
189
+ from ultralytics.nn.tasks import load_checkpoint
190
190
 
191
- model = attempt_load_weights(model, device=self.device)
191
+ model, _ = load_checkpoint(model, device=self.device) # model, ckpt
192
192
  model.eval().to(self.device)
193
193
  data = check_det_dataset(refer_data or self.args.data)
194
194
  names = [name.split("/", 1)[0] for name in list(data["names"].values())]
@@ -5,18 +5,16 @@ from .tasks import (
5
5
  ClassificationModel,
6
6
  DetectionModel,
7
7
  SegmentationModel,
8
- attempt_load_one_weight,
9
- attempt_load_weights,
10
8
  guess_model_scale,
11
9
  guess_model_task,
10
+ load_checkpoint,
12
11
  parse_model,
13
12
  torch_safe_load,
14
13
  yaml_model_load,
15
14
  )
16
15
 
17
16
  __all__ = (
18
- "attempt_load_one_weight",
19
- "attempt_load_weights",
17
+ "load_checkpoint",
20
18
  "parse_model",
21
19
  "yaml_model_load",
22
20
  "guess_model_task",
@@ -203,9 +203,9 @@ class AutoBackend(nn.Module):
203
203
  model = model.fuse(verbose=verbose)
204
204
  model = model.to(device)
205
205
  else: # pt file
206
- from ultralytics.nn.tasks import attempt_load_one_weight
206
+ from ultralytics.nn.tasks import load_checkpoint
207
207
 
208
- model, _ = attempt_load_one_weight(model, device=device, fuse=fuse) # load model, ckpt
208
+ model, _ = load_checkpoint(model, device=device, fuse=fuse) # load model, ckpt
209
209
 
210
210
  # Common PyTorch model processing
211
211
  if hasattr(model, "kpt_shape"):
ultralytics/nn/tasks.py CHANGED
@@ -1483,61 +1483,12 @@ def torch_safe_load(weight, safe_only=False):
1483
1483
  return ckpt, file
1484
1484
 
1485
1485
 
1486
- def attempt_load_weights(weights, device=None, inplace=True, fuse=False):
1487
- """
1488
- Load an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a.
1489
-
1490
- Args:
1491
- weights (str | List[str]): Model weights path(s).
1492
- device (torch.device, optional): Device to load model to.
1493
- inplace (bool): Whether to do inplace operations.
1494
- fuse (bool): Whether to fuse model.
1495
-
1496
- Returns:
1497
- (torch.nn.Module): Loaded model.
1498
- """
1499
- ensemble = Ensemble()
1500
- for w in weights if isinstance(weights, list) else [weights]:
1501
- ckpt, w = torch_safe_load(w) # load ckpt
1502
- args = {**DEFAULT_CFG_DICT, **ckpt["train_args"]} if "train_args" in ckpt else None # combined args
1503
- model = (ckpt.get("ema") or ckpt["model"]).float() # FP32 model
1504
-
1505
- # Model compatibility updates
1506
- model.args = args # attach args to model
1507
- model.pt_path = w # attach *.pt file path to model
1508
- model.task = getattr(model, "task", guess_model_task(model))
1509
- if not hasattr(model, "stride"):
1510
- model.stride = torch.tensor([32.0])
1511
-
1512
- # Append
1513
- ensemble.append((model.fuse().eval() if fuse and hasattr(model, "fuse") else model.eval()).to(device))
1514
-
1515
- # Module updates
1516
- for m in ensemble.modules():
1517
- if hasattr(m, "inplace"):
1518
- m.inplace = inplace
1519
- elif isinstance(m, torch.nn.Upsample) and not hasattr(m, "recompute_scale_factor"):
1520
- m.recompute_scale_factor = None # torch 1.11.0 compatibility
1521
-
1522
- # Return model
1523
- if len(ensemble) == 1:
1524
- return ensemble[-1]
1525
-
1526
- # Return ensemble
1527
- LOGGER.info(f"Ensemble created with {weights}\n")
1528
- for k in "names", "nc", "yaml":
1529
- setattr(ensemble, k, getattr(ensemble[0], k))
1530
- ensemble.stride = ensemble[int(torch.argmax(torch.tensor([m.stride.max() for m in ensemble])))].stride
1531
- assert all(ensemble[0].nc == m.nc for m in ensemble), f"Models differ in class counts {[m.nc for m in ensemble]}"
1532
- return ensemble
1533
-
1534
-
1535
- def attempt_load_one_weight(weight, device=None, inplace=True, fuse=False):
1486
+ def load_checkpoint(weight, device=None, inplace=True, fuse=False):
1536
1487
  """
1537
1488
  Load a single model weights.
1538
1489
 
1539
1490
  Args:
1540
- weight (str): Model weight path.
1491
+ weight (str | Path): Model weight path.
1541
1492
  device (torch.device, optional): Device to load model to.
1542
1493
  inplace (bool): Whether to do inplace operations.
1543
1494
  fuse (bool): Whether to fuse model.
@@ -49,7 +49,7 @@ MACOS_VERSION = platform.mac_ver()[0] if MACOS else None
49
49
  NOT_MACOS14 = not (MACOS and MACOS_VERSION.startswith("14."))
50
50
  ARM64 = platform.machine() in {"arm64", "aarch64"} # ARM64 booleans
51
51
  PYTHON_VERSION = platform.python_version()
52
- TORCH_VERSION = torch.__version__
52
+ TORCH_VERSION = str(torch.__version__) # Normalize torch.__version__ (PyTorch>1.9 returns TorchVersion objects)
53
53
  TORCHVISION_VERSION = importlib.metadata.version("torchvision") # faster than importing torchvision
54
54
  IS_VSCODE = os.environ.get("TERM_PROGRAM", False) == "vscode"
55
55
  RKNN_CHIPS = frozenset(
@@ -132,6 +132,10 @@ os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" # suppress verbose TF compiler warning
132
132
  os.environ["TORCH_CPP_LOG_LEVEL"] = "ERROR" # suppress "NNPACK.cpp could not initialize NNPACK" warnings
133
133
  os.environ["KINETO_LOG_LEVEL"] = "5" # suppress verbose PyTorch profiler output when computing FLOPs
134
134
 
135
+ # Precompiled type tuples for faster isinstance() checks
136
+ FLOAT_OR_INT = (float, int)
137
+ STR_OR_PATH = (str, Path)
138
+
135
139
 
136
140
  class DataExportMixin:
137
141
  """
@@ -36,6 +36,7 @@ from ultralytics.utils import (
36
36
  PYTHON_VERSION,
37
37
  RKNN_CHIPS,
38
38
  ROOT,
39
+ TORCH_VERSION,
39
40
  TORCHVISION_VERSION,
40
41
  USER_CONFIG_DIR,
41
42
  WINDOWS,
@@ -464,7 +465,7 @@ def check_torchvision():
464
465
  }
465
466
 
466
467
  # Check major and minor versions
467
- v_torch = ".".join(torch.__version__.split("+", 1)[0].split(".")[:2])
468
+ v_torch = ".".join(TORCH_VERSION.split("+", 1)[0].split(".")[:2])
468
469
  if v_torch in compatibility_table:
469
470
  compatible_versions = compatibility_table[v_torch]
470
471
  v_torchvision = ".".join(TORCHVISION_VERSION.split("+", 1)[0].split(".")[:2])
@@ -893,7 +893,7 @@ def plot_results(
893
893
  assert len(files), f"No results.csv files found in {save_dir.resolve()}, nothing to plot."
894
894
  for f in files:
895
895
  try:
896
- data = pl.read_csv(f)
896
+ data = pl.read_csv(f, infer_schema_length=None)
897
897
  s = [x.strip() for x in data.columns]
898
898
  x = data.select(data.columns[0]).to_numpy().flatten()
899
899
  for i, j in enumerate(index):
@@ -971,7 +971,7 @@ def plot_tune_results(csv_file: str = "tune_results.csv"):
971
971
 
972
972
  # Scatter plots for each hyperparameter
973
973
  csv_file = Path(csv_file)
974
- data = pl.read_csv(csv_file)
974
+ data = pl.read_csv(csv_file, infer_schema_length=None)
975
975
  num_metrics_columns = 1
976
976
  keys = [x.strip() for x in data.columns][num_metrics_columns:]
977
977
  x = data.to_numpy()
ultralytics/utils/tal.py CHANGED
@@ -3,12 +3,12 @@
3
3
  import torch
4
4
  import torch.nn as nn
5
5
 
6
- from . import LOGGER
6
+ from . import LOGGER, TORCH_VERSION
7
7
  from .checks import check_version
8
8
  from .metrics import bbox_iou, probiou
9
9
  from .ops import xywhr2xyxyxyxy
10
10
 
11
- TORCH_1_10 = check_version(torch.__version__, "1.10.0")
11
+ TORCH_1_10 = check_version(TORCH_VERSION, "1.10.0")
12
12
 
13
13
 
14
14
  class TaskAlignedAssigner(nn.Module):
@@ -27,6 +27,7 @@ from ultralytics.utils import (
27
27
  LOGGER,
28
28
  NUM_THREADS,
29
29
  PYTHON_VERSION,
30
+ TORCH_VERSION,
30
31
  TORCHVISION_VERSION,
31
32
  WINDOWS,
32
33
  colorstr,
@@ -35,15 +36,15 @@ from ultralytics.utils.checks import check_version
35
36
  from ultralytics.utils.patches import torch_load
36
37
 
37
38
  # Version checks (all default to version>=min_version)
38
- TORCH_1_9 = check_version(torch.__version__, "1.9.0")
39
- TORCH_1_13 = check_version(torch.__version__, "1.13.0")
40
- TORCH_2_0 = check_version(torch.__version__, "2.0.0")
41
- TORCH_2_4 = check_version(torch.__version__, "2.4.0")
39
+ TORCH_1_9 = check_version(TORCH_VERSION, "1.9.0")
40
+ TORCH_1_13 = check_version(TORCH_VERSION, "1.13.0")
41
+ TORCH_2_0 = check_version(TORCH_VERSION, "2.0.0")
42
+ TORCH_2_4 = check_version(TORCH_VERSION, "2.4.0")
42
43
  TORCHVISION_0_10 = check_version(TORCHVISION_VERSION, "0.10.0")
43
44
  TORCHVISION_0_11 = check_version(TORCHVISION_VERSION, "0.11.0")
44
45
  TORCHVISION_0_13 = check_version(TORCHVISION_VERSION, "0.13.0")
45
46
  TORCHVISION_0_18 = check_version(TORCHVISION_VERSION, "0.18.0")
46
- if WINDOWS and check_version(torch.__version__, "==2.4.0"): # reject version 2.4.0 on Windows
47
+ if WINDOWS and check_version(TORCH_VERSION, "==2.4.0"): # reject version 2.4.0 on Windows
47
48
  LOGGER.warning(
48
49
  "Known issue with torch==2.4.0 on Windows with CPU, recommend upgrading to torch>=2.4.1 to resolve "
49
50
  "https://github.com/ultralytics/ultralytics/issues/15049"
@@ -165,7 +166,7 @@ def select_device(device="", batch=0, newline=False, verbose=True):
165
166
  if isinstance(device, torch.device) or str(device).startswith(("tpu", "intel")):
166
167
  return device
167
168
 
168
- s = f"Ultralytics {__version__} 🚀 Python-{PYTHON_VERSION} torch-{torch.__version__} "
169
+ s = f"Ultralytics {__version__} 🚀 Python-{PYTHON_VERSION} torch-{TORCH_VERSION} "
169
170
  device = str(device).lower()
170
171
  for remove in "cuda:", "none", "(", ")", "[", "]", "'", " ":
171
172
  device = device.replace(remove, "") # to string, 'cuda:0' -> '0' and '(0, 1)' -> '0,1'
ultralytics/utils/tqdm.py CHANGED
@@ -88,11 +88,11 @@ class TQDM:
88
88
  mininterval: float = 0.1,
89
89
  disable: bool | None = None,
90
90
  unit: str = "it",
91
- unit_scale: bool = False,
91
+ unit_scale: bool = True,
92
92
  unit_divisor: int = 1000,
93
- bar_format: str | None = None,
93
+ bar_format: str | None = None, # kept for API compatibility; not used for formatting
94
94
  initial: int = 0,
95
- **kwargs, # Accept unused args for compatibility
95
+ **kwargs,
96
96
  ) -> None:
97
97
  """
98
98
  Initialize the TQDM progress bar with specified configuration options.
@@ -138,11 +138,8 @@ class TQDM:
138
138
  self.mininterval = max(mininterval, self.NONINTERACTIVE_MIN_INTERVAL) if self.noninteractive else mininterval
139
139
  self.initial = initial
140
140
 
141
- # Set bar format based on whether we have a total
142
- if self.total:
143
- self.bar_format = bar_format or "{desc}: {percent:.0f}% {bar} {n}/{total} {rate} {elapsed}<{remaining}"
144
- else:
145
- self.bar_format = bar_format or "{desc}: {bar} {n} {rate} {elapsed}"
141
+ # Kept for API compatibility (unused for f-string formatting)
142
+ self.bar_format = bar_format
146
143
 
147
144
  self.file = file or sys.stdout
148
145
 
@@ -151,48 +148,31 @@ class TQDM:
151
148
  self.last_print_n = self.initial
152
149
  self.last_print_t = time.time()
153
150
  self.start_t = time.time()
154
- self.last_rate = 0
151
+ self.last_rate = 0.0
155
152
  self.closed = False
153
+ self.is_bytes = unit_scale and unit in ("B", "bytes")
154
+ self.scales = (
155
+ [(1073741824, "GB/s"), (1048576, "MB/s"), (1024, "KB/s")]
156
+ if self.is_bytes
157
+ else [(1e9, f"G{self.unit}/s"), (1e6, f"M{self.unit}/s"), (1e3, f"K{self.unit}/s")]
158
+ )
156
159
 
157
- # Display initial bar if we have total and not disabled
158
160
  if not self.disable and self.total and not self.noninteractive:
159
161
  self._display()
160
162
 
161
163
  def _format_rate(self, rate: float) -> str:
162
- """Format rate with proper units and reasonable precision."""
164
+ """Format rate with units."""
163
165
  if rate <= 0:
164
166
  return ""
167
+ fallback = f"{rate:.1f}B/s" if self.is_bytes else f"{rate:.1f}{self.unit}/s"
168
+ return next((f"{rate / t:.1f}{u}" for t, u in self.scales if rate >= t), fallback)
165
169
 
166
- # For bytes with scaling, use binary units
167
- if self.unit in ("B", "bytes") and self.unit_scale:
168
- return next(
169
- (
170
- f"{rate / threshold:.1f}{unit}"
171
- for threshold, unit in [
172
- (1073741824, "GB/s"),
173
- (1048576, "MB/s"),
174
- (1024, "KB/s"),
175
- ]
176
- if rate >= threshold
177
- ),
178
- f"{rate:.1f}B/s",
179
- )
180
- # For other scalable units, use decimal units
181
- if self.unit_scale and self.unit in ("it", "items", ""):
182
- for threshold, prefix in [(1000000, "M"), (1000, "K")]:
183
- if rate >= threshold:
184
- return f"{rate / threshold:.1f}{prefix}{self.unit}/s"
185
-
186
- # Default formatting
187
- precision = ".1f" if rate >= 1 else ".2f"
188
- return f"{rate:{precision}}{self.unit}/s"
189
-
190
- def _format_num(self, num: int) -> str:
170
+ def _format_num(self, num: int | float) -> str:
191
171
  """Format number with optional unit scaling."""
192
- if not self.unit_scale or self.unit not in ("B", "bytes"):
172
+ if not self.unit_scale or not self.is_bytes:
193
173
  return str(num)
194
174
 
195
- for unit in ["", "K", "M", "G", "T"]:
175
+ for unit in ("", "K", "M", "G", "T"):
196
176
  if abs(num) < self.unit_divisor:
197
177
  return f"{num:3.1f}{unit}B" if unit else f"{num:.0f}B"
198
178
  num /= self.unit_divisor
@@ -224,8 +204,7 @@ class TQDM:
224
204
  """Check if display should update."""
225
205
  if self.noninteractive:
226
206
  return False
227
-
228
- return True if self.total and self.n >= self.total else dt >= self.mininterval
207
+ return (self.total is not None and self.n >= self.total) or (dt >= self.mininterval)
229
208
 
230
209
  def _display(self, final: bool = False) -> None:
231
210
  """Display progress bar."""
@@ -240,8 +219,8 @@ class TQDM:
240
219
  return
241
220
 
242
221
  # Calculate rate (avoid crazy numbers)
243
- if dt > self.MIN_RATE_CALC_INTERVAL: # Only calculate rate if enough time has passed
244
- rate = dn / dt
222
+ if dt > self.MIN_RATE_CALC_INTERVAL:
223
+ rate = dn / dt if dt else 0.0
245
224
  # Smooth rate for reasonable values, use raw rate for very high values
246
225
  if rate < self.MAX_SMOOTHED_RATE:
247
226
  self.last_rate = self.RATE_SMOOTHING_FACTOR * rate + (1 - self.RATE_SMOOTHING_FACTOR) * self.last_rate
@@ -249,8 +228,8 @@ class TQDM:
249
228
  else:
250
229
  rate = self.last_rate
251
230
 
252
- # At completion, use the overall rate for more accurate display
253
- if self.n >= (self.total or float("inf")) and self.total and self.total > 0:
231
+ # At completion, use overall rate
232
+ if self.total and self.n >= self.total:
254
233
  overall_elapsed = current_time - self.start_t
255
234
  if overall_elapsed > 0:
256
235
  rate = self.n / overall_elapsed
@@ -260,44 +239,41 @@ class TQDM:
260
239
  self.last_print_t = current_time
261
240
  elapsed = current_time - self.start_t
262
241
 
263
- # Calculate remaining time
242
+ # Remaining time
264
243
  remaining_str = ""
265
- if self.total and 0 < self.n < self.total and rate > 0:
266
- remaining_str = self._format_time((self.total - self.n) / rate)
244
+ if self.total and 0 < self.n < self.total and elapsed > 0:
245
+ est_rate = rate or (self.n / elapsed)
246
+ remaining_str = f"<{self._format_time((self.total - self.n) / est_rate)}"
267
247
 
268
- # Build progress components
248
+ # Numbers and percent
269
249
  if self.total:
270
250
  percent = (self.n / self.total) * 100
271
- # For bytes with unit scaling, avoid repeating units: show "5.4/5.4MB" not "5.4MB/5.4MB"
272
- n = self._format_num(self.n)
273
- total = self._format_num(self.total)
274
- if self.unit_scale and self.unit in ("B", "bytes"):
275
- n = n.rstrip("KMGTPB") # Remove unit suffix from current
251
+ n_str = self._format_num(self.n)
252
+ t_str = self._format_num(self.total)
253
+ if self.is_bytes:
254
+ # Collapse suffix only when identical (e.g. "5.4/5.4MB")
255
+ if n_str[-2] == t_str[-2]:
256
+ n_str = n_str.rstrip("KMGTPB") # Remove unit suffix from current if different than total
276
257
  else:
277
- percent = 0
278
- n = self._format_num(self.n)
279
- total = "?"
258
+ percent = 0.0
259
+ n_str, t_str = self._format_num(self.n), "?"
280
260
 
281
261
  elapsed_str = self._format_time(elapsed)
262
+ rate_str = self._format_rate(rate) or (self._format_rate(self.n / elapsed) if elapsed > 0 else "")
282
263
 
283
- # Use different format for completion
284
- if self.total and self.n >= self.total:
285
- format_str = self.bar_format.replace("<{remaining}", "")
264
+ bar = self._generate_bar()
265
+
266
+ # Compose progress line via f-strings (two shapes: with/without total)
267
+ if self.total:
268
+ if self.is_bytes and self.n >= self.total:
269
+ # Completed bytes: show only final size
270
+ progress_str = f"{self.desc}: {percent:.0f}% {bar} {t_str} {rate_str} {elapsed_str}"
271
+ else:
272
+ progress_str = (
273
+ f"{self.desc}: {percent:.0f}% {bar} {n_str}/{t_str} {rate_str} {elapsed_str}{remaining_str}"
274
+ )
286
275
  else:
287
- format_str = self.bar_format
288
-
289
- # Format progress string
290
- progress_str = format_str.format(
291
- desc=self.desc,
292
- percent=percent,
293
- bar=self._generate_bar(),
294
- n=n,
295
- total=total,
296
- rate=self._format_rate(rate) or (self._format_rate(self.n / elapsed) if elapsed > 0 else ""),
297
- remaining=remaining_str,
298
- elapsed=elapsed_str,
299
- unit=self.unit,
300
- )
276
+ progress_str = f"{self.desc}: {bar} {n_str} {rate_str} {elapsed_str}"
301
277
 
302
278
  # Write to output
303
279
  try:
@@ -335,7 +311,7 @@ class TQDM:
335
311
  if self.closed:
336
312
  return
337
313
 
338
- self.closed = True # Set before final display
314
+ self.closed = True
339
315
 
340
316
  if not self.disable:
341
317
  # Final display
@@ -129,7 +129,7 @@ def run_ray_tune(
129
129
  {**train_args, **{"exist_ok": train_args.pop("resume", False)}}, # resume w/ same tune_dir
130
130
  ),
131
131
  name=train_args.pop("name", "tune"), # runs/{task}/{tune_dir}
132
- ).resolve() # must be absolute dir
132
+ ) # must be absolute dir
133
133
  tune_dir.mkdir(parents=True, exist_ok=True)
134
134
  if tune.Tuner.can_restore(tune_dir):
135
135
  LOGGER.info(f"{colorstr('Tuner: ')} Resuming tuning run {tune_dir}...")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ultralytics
3
- Version: 8.3.191
3
+ Version: 8.3.193
4
4
  Summary: Ultralytics YOLO 🚀 for SOTA object detection, multi-object tracking, instance segmentation, pose estimation and image classification.
5
5
  Author-email: Glenn Jocher <glenn.jocher@ultralytics.com>, Jing Qiu <jing.qiu@ultralytics.com>
6
6
  Maintainer-email: Ultralytics <hello@ultralytics.com>