Python refactor and simplification (#12624)

* Python code cleanup

* Auto-format by Ultralytics actions

---------

Co-authored-by: UltralyticsAssistant <web@ultralytics.com>
This commit is contained in:
Glenn JocherandUltralyticsAssistant authored and GitHub committed 2024-01-13 22:34:05 +01:00
1 parent de64179cd4
commit 22e9afef74
11 files changed
+155 -172

No files matched your search

+1 -1
View File
@@ -546,7 +546,7 @@ def export_tfjs(file, int8, prefix=colorstr("TensorFlow.js:")):
"--quantize_uint8" if int8 else "", "--quantize_uint8" if int8 else "",
"--output_node_names=Identity,Identity_1,Identity_2,Identity_3", "--output_node_names=Identity,Identity_1,Identity_2,Identity_3",
str(f_pb), str(f_pb),
str(f), f,
] ]
subprocess.run([arg for arg in args if arg], check=True) subprocess.run([arg for arg in args if arg], check=True)
+11 -5
View File
@@ -859,11 +859,17 @@ class Detections:
def tolist(self): def tolist(self):
# return a list of Detections objects, i.e. 'for result in results.tolist():' # return a list of Detections objects, i.e. 'for result in results.tolist():'
r = range(self.n) # iterable r = range(self.n) # iterable
x = [Detections([self.ims[i]], [self.pred[i]], [self.files[i]], self.times, self.names, self.s) for i in r] return [
# for d in x: Detections(
# for k in ['ims', 'pred', 'xyxy', 'xyxyn', 'xywh', 'xywhn']: [self.ims[i]],
# setattr(d, k, getattr(d, k)[0]) # pop out of list [self.pred[i]],
return x [self.files[i]],
self.times,
self.names,
self.s,
)
for i in r
]
def print(self): def print(self):
LOGGER.info(self.__str__()) LOGGER.info(self.__str__())
+11 -21
View File
@@ -682,10 +682,7 @@ def main(opt, callbacks=Callbacks()):
) )
# Delete the items in meta dictionary whose first value is False # Delete the items in meta dictionary whose first value is False
del_ = [] del_ = [item for item, value_ in meta.items() if value_[0] is False]
for item in meta.keys():
if meta[item][0] is False:
del_.append(item)
hyp_GA = hyp.copy() # Make a copy of hyp dictionary hyp_GA = hyp.copy() # Make a copy of hyp dictionary
for item in del_: for item in del_:
del meta[item] # Remove the item from meta dictionary del meta[item] # Remove the item from meta dictionary
@@ -696,9 +693,7 @@ def main(opt, callbacks=Callbacks()):
upper_limit = np.array([meta[k][2] for k in hyp_GA.keys()]) upper_limit = np.array([meta[k][2] for k in hyp_GA.keys()])
# Create gene_ranges list to hold the range of values for each gene in the population # Create gene_ranges list to hold the range of values for each gene in the population
gene_ranges = [] gene_ranges = [(lower_limit[i], upper_limit[i]) for i in range(len(upper_limit))]
for i in range(len(upper_limit)):
gene_ranges.append((lower_limit[i], upper_limit[i]))
# Initialize the population with initial_values or random values # Initialize the population with initial_values or random values
initial_values = [] initial_values = []
@@ -723,14 +718,11 @@ def main(opt, callbacks=Callbacks()):
# Generate random values within the search space for the rest of the population # Generate random values within the search space for the rest of the population
if initial_values is None: if initial_values is None:
population = [generate_individual(gene_ranges, len(hyp_GA)) for i in range(pop_size)] population = [generate_individual(gene_ranges, len(hyp_GA)) for _ in range(pop_size)]
else: elif pop_size > 1:
if pop_size > 1: population = [generate_individual(gene_ranges, len(hyp_GA)) for _ in range(pop_size - len(initial_values))]
population = [ for initial_value in initial_values:
generate_individual(gene_ranges, len(hyp_GA)) for i in range(pop_size - len(initial_values)) population = [initial_value] + population
]
for initial_value in initial_values:
population = [initial_value] + population
# Run the genetic algorithm for a fixed number of generations # Run the genetic algorithm for a fixed number of generations
list_keys = list(hyp_GA.keys()) list_keys = list(hyp_GA.keys())
@@ -738,10 +730,8 @@ def main(opt, callbacks=Callbacks()):
if generation >= 1: if generation >= 1:
save_dict = {} save_dict = {}
for i in range(len(population)): for i in range(len(population)):
little_dict = {} little_dict = {list_keys[j]: float(population[i][j]) for j in range(len(population[i]))}
for j in range(len(population[i])): save_dict[f"gen{str(generation)}number{str(i)}"] = little_dict
little_dict[list_keys[j]] = float(population[i][j])
save_dict["gen" + str(generation) + "number" + str(i)] = little_dict
with open(save_dir / "evolve_population.yaml", "w") as outfile: with open(save_dir / "evolve_population.yaml", "w") as outfile:
yaml.dump(save_dict, outfile, default_flow_style=False) yaml.dump(save_dict, outfile, default_flow_style=False)
@@ -771,7 +761,7 @@ def main(opt, callbacks=Callbacks()):
# Select the fittest individuals for reproduction using adaptive tournament selection # Select the fittest individuals for reproduction using adaptive tournament selection
selected_indices = [] selected_indices = []
for i in range(pop_size - elite_size): for _ in range(pop_size - elite_size):
# Adaptive tournament size # Adaptive tournament size
tournament_size = max( tournament_size = max(
max(2, tournament_size_min), max(2, tournament_size_min),
@@ -788,7 +778,7 @@ def main(opt, callbacks=Callbacks()):
selected_indices.extend(elite_indices) selected_indices.extend(elite_indices)
# Create the next generation through crossover and mutation # Create the next generation through crossover and mutation
next_generation = [] next_generation = []
for i in range(pop_size): for _ in range(pop_size):
parent1_index = selected_indices[random.randint(0, pop_size - 1)] parent1_index = selected_indices[random.randint(0, pop_size - 1)]
parent2_index = selected_indices[random.randint(0, pop_size - 1)] parent2_index = selected_indices[random.randint(0, pop_size - 1)]
# Adaptive crossover rate # Adaptive crossover rate
+1 -3
View File
@@ -24,9 +24,7 @@ def is_url(url, check=True):
def gsutil_getsize(url=""): def gsutil_getsize(url=""):
# gs://bucket/file size https://cloud.google.com/storage/docs/gsutil/commands/du # gs://bucket/file size https://cloud.google.com/storage/docs/gsutil/commands/du
output = subprocess.check_output(["gsutil", "du", url], shell=True, encoding="utf-8") output = subprocess.check_output(["gsutil", "du", url], shell=True, encoding="utf-8")
if output: return int(output.split()[0]) if output else 0
return int(output.split()[0])
return 0
def url_getsize(url="https://ultralytics.com/images/bus.jpg"): def url_getsize(url="https://ultralytics.com/images/bus.jpg"):
+1 -1
View File
@@ -188,7 +188,7 @@ class Profile(contextlib.ContextDecorator):
def __init__(self, t=0.0, device: torch.device = None): def __init__(self, t=0.0, device: torch.device = None):
self.t = t self.t = t
self.device = device self.device = device
self.cuda = True if (device and str(device)[:4] == "cuda") else False self.cuda = bool(device and str(device).startswith("cuda"))
def __enter__(self): def __enter__(self):
self.start = self.time() self.start = self.time()
+11 -14
View File
@@ -67,9 +67,7 @@ def _json_default(value):
value = value.item() value = value.item()
except ValueError: # "only one element tensors can be converted to Python scalars" except ValueError: # "only one element tensors can be converted to Python scalars"
pass pass
if isinstance(value, float): return value if isinstance(value, float) else str(value)
return value
return str(value)
class Loggers: class Loggers:
@@ -250,12 +248,12 @@ class Loggers:
f.write(s + ("%20.5g," * n % tuple([epoch] + vals)).rstrip(",") + "\n") f.write(s + ("%20.5g," * n % tuple([epoch] + vals)).rstrip(",") + "\n")
if self.ndjson_console or self.ndjson_file: if self.ndjson_console or self.ndjson_file:
json_data = json.dumps(dict(epoch=epoch, **x), default=_json_default) json_data = json.dumps(dict(epoch=epoch, **x), default=_json_default)
if self.ndjson_console: if self.ndjson_console:
print(json_data) print(json_data)
if self.ndjson_file: if self.ndjson_file:
file = self.save_dir / "results.ndjson" file = self.save_dir / "results.ndjson"
with open(file, "a") as f: with open(file, "a") as f:
print(json_data, file=f) print(json_data, file=f)
if self.tb: if self.tb:
for k, v in x.items(): for k, v in x.items():
@@ -370,10 +368,7 @@ class GenericLogger:
if clearml and "clearml" in self.include: if clearml and "clearml" in self.include:
try: try:
# Hyp is not available in classification mode # Hyp is not available in classification mode
if "hyp" not in opt: hyp = {} if "hyp" not in opt else opt.hyp
hyp = {}
else:
hyp = opt.hyp
self.clearml = ClearmlLogger(opt, hyp) self.clearml = ClearmlLogger(opt, hyp)
except Exception: except Exception:
self.clearml = None self.clearml = None
@@ -427,7 +422,9 @@ class GenericLogger:
if self.tb: if self.tb:
log_tensorboard_graph(self.tb, model, imgsz) log_tensorboard_graph(self.tb, model, imgsz)
def log_model(self, model_path, epoch=0, metadata={}): def log_model(self, model_path, epoch=0, metadata=None):
if metadata is None:
metadata = {}
# Log model to all loggers # Log model to all loggers
if self.wandb: if self.wandb:
art = wandb.Artifact(name=f"run_{wandb.run.id}_model", type="model", metadata=metadata) art = wandb.Artifact(name=f"run_{wandb.run.id}_model", type="model", metadata=metadata)
+23 -21
View File
@@ -31,7 +31,7 @@ def construct_dataset(clearml_info_string):
"More than one yaml file was found in the dataset root, cannot determine which one contains " "More than one yaml file was found in the dataset root, cannot determine which one contains "
"the dataset definition this way." "the dataset definition this way."
) )
elif len(yaml_filenames) == 0: elif not yaml_filenames:
raise ValueError( raise ValueError(
"No yaml definition found in dataset root path, check that there is a correct yaml file " "No yaml definition found in dataset root path, check that there is a correct yaml file "
"inside the dataset root path." "inside the dataset root path."
@@ -45,7 +45,7 @@ def construct_dataset(clearml_info_string):
{"train", "test", "val", "nc", "names"} {"train", "test", "val", "nc", "names"}
), "The right keys were not found in the yaml file, make sure it at least has the following keys: ('train', 'test', 'val', 'nc', 'names')" ), "The right keys were not found in the yaml file, make sure it at least has the following keys: ('train', 'test', 'val', 'nc', 'names')"
data_dict = dict() data_dict = {}
data_dict["train"] = ( data_dict["train"] = (
str((dataset_root_path / dataset_definition["train"]).resolve()) if dataset_definition["train"] else None str((dataset_root_path / dataset_definition["train"]).resolve()) if dataset_definition["train"] else None
) )
@@ -96,7 +96,7 @@ class ClearmlLogger:
self.data_dict = None self.data_dict = None
if self.clearml: if self.clearml:
self.task = Task.init( self.task = Task.init(
project_name=opt.project if not str(opt.project).startswith("runs/") else "YOLOv5", project_name="YOLOv5" if str(opt.project).startswith("runs/") else opt.project,
task_name=opt.name if opt.name != "exp" else "Training", task_name=opt.name if opt.name != "exp" else "Training",
tags=["YOLOv5"], tags=["YOLOv5"],
output_uri=True, output_uri=True,
@@ -202,24 +202,26 @@ class ClearmlLogger:
class_names (dict): dict containing mapping of class int to class name class_names (dict): dict containing mapping of class int to class name
image (Tensor): A torch tensor containing the actual image data image (Tensor): A torch tensor containing the actual image data
""" """
if len(self.current_epoch_logged_images) < self.max_imgs_to_log_per_epoch and self.current_epoch >= 0: if (
# Log every bbox_interval times and deduplicate for any intermittend extra eval runs len(self.current_epoch_logged_images) < self.max_imgs_to_log_per_epoch
if self.current_epoch % self.bbox_interval == 0 and image_path not in self.current_epoch_logged_images: and self.current_epoch >= 0
im = np.ascontiguousarray(np.moveaxis(image.mul(255).clamp(0, 255).byte().cpu().numpy(), 0, 2)) and (self.current_epoch % self.bbox_interval == 0 and image_path not in self.current_epoch_logged_images)
annotator = Annotator(im=im, pil=True) ):
for i, (conf, class_nr, box) in enumerate(zip(boxes[:, 4], boxes[:, 5], boxes[:, :4])): im = np.ascontiguousarray(np.moveaxis(image.mul(255).clamp(0, 255).byte().cpu().numpy(), 0, 2))
color = colors(i) annotator = Annotator(im=im, pil=True)
for i, (conf, class_nr, box) in enumerate(zip(boxes[:, 4], boxes[:, 5], boxes[:, :4])):
color = colors(i)
class_name = class_names[int(class_nr)] class_name = class_names[int(class_nr)]
confidence_percentage = round(float(conf) * 100, 2) confidence_percentage = round(float(conf) * 100, 2)
label = f"{class_name}: {confidence_percentage}%" label = f"{class_name}: {confidence_percentage}%"
if conf > conf_threshold: if conf > conf_threshold:
annotator.rectangle(box.cpu().numpy(), outline=color) annotator.rectangle(box.cpu().numpy(), outline=color)
annotator.box_label(box.cpu().numpy(), label=label, color=color) annotator.box_label(box.cpu().numpy(), label=label, color=color)
annotated_image = annotator.result() annotated_image = annotator.result()
self.task.get_logger().report_image( self.task.get_logger().report_image(
title="Bounding Boxes", series=image_path.name, iteration=self.current_epoch, image=annotated_image title="Bounding Boxes", series=image_path.name, iteration=self.current_epoch, image=annotated_image
) )
self.current_epoch_logged_images.add(image_path) self.current_epoch_logged_images.add(image_path)
+56 -64
View File
@@ -166,34 +166,33 @@ class CometLogger:
def _get_experiment(self, mode, experiment_id=None): def _get_experiment(self, mode, experiment_id=None):
if mode == "offline": if mode == "offline":
return (
comet_ml.ExistingOfflineExperiment(
previous_experiment=experiment_id,
**self.default_experiment_kwargs,
)
if experiment_id is not None
else comet_ml.OfflineExperiment(
**self.default_experiment_kwargs,
)
)
try:
if experiment_id is not None: if experiment_id is not None:
return comet_ml.ExistingOfflineExperiment( return comet_ml.ExistingExperiment(
previous_experiment=experiment_id, previous_experiment=experiment_id,
**self.default_experiment_kwargs, **self.default_experiment_kwargs,
) )
return comet_ml.OfflineExperiment( return comet_ml.Experiment(**self.default_experiment_kwargs)
**self.default_experiment_kwargs,
except ValueError:
logger.warning(
"COMET WARNING: "
"Comet credentials have not been set. "
"Comet will default to offline logging. "
"Please set your credentials to enable online logging."
) )
return self._get_experiment("offline", experiment_id)
else:
try:
if experiment_id is not None:
return comet_ml.ExistingExperiment(
previous_experiment=experiment_id,
**self.default_experiment_kwargs,
)
return comet_ml.Experiment(**self.default_experiment_kwargs)
except ValueError:
logger.warning(
"COMET WARNING: "
"Comet credentials have not been set. "
"Comet will default to offline logging. "
"Please set your credentials to enable online logging."
)
return self._get_experiment("offline", experiment_id)
return return
@@ -242,10 +241,7 @@ class CometLogger:
path = data_config.get("path") path = data_config.get("path")
if path and path.startswith(COMET_PREFIX): if path and path.startswith(COMET_PREFIX):
path = data_config["path"].replace(COMET_PREFIX, "") path = data_config["path"].replace(COMET_PREFIX, "")
data_dict = self.download_dataset_artifact(path) return self.download_dataset_artifact(path)
return data_dict
self.log_asset(self.opt.data, metadata={"type": "data-config-file"}) self.log_asset(self.opt.data, metadata={"type": "data-config-file"})
return check_dataset(data_file) return check_dataset(data_file)
@@ -269,24 +265,22 @@ class CometLogger:
self.log_image(native_scale_image, name=image_name) self.log_image(native_scale_image, name=image_name)
self.logged_image_names.append(image_name) self.logged_image_names.append(image_name)
metadata = [] metadata = [
for cls, *xyxy in filtered_labels.tolist(): {
metadata.append( "label": f"{self.class_names[int(cls)]}-gt",
{ "score": 100,
"label": f"{self.class_names[int(cls)]}-gt", "box": {"x": xyxy[0], "y": xyxy[1], "x2": xyxy[2], "y2": xyxy[3]},
"score": 100, }
"box": {"x": xyxy[0], "y": xyxy[1], "x2": xyxy[2], "y2": xyxy[3]}, for cls, *xyxy in filtered_labels.tolist()
} ]
) metadata.extend(
for *xyxy, conf, cls in filtered_detections.tolist(): {
metadata.append( "label": f"{self.class_names[int(cls)]}",
{ "score": conf * 100,
"label": f"{self.class_names[int(cls)]}", "box": {"x": xyxy[0], "y": xyxy[1], "x2": xyxy[2], "y2": xyxy[3]},
"score": conf * 100, }
"box": {"x": xyxy[0], "y": xyxy[1], "x2": xyxy[2], "y2": xyxy[3]}, for *xyxy, conf, cls in filtered_detections.tolist()
} )
)
self.metadata_dict[image_name] = metadata self.metadata_dict[image_name] = metadata
self.logged_images_count += 1 self.logged_images_count += 1
@@ -398,9 +392,8 @@ class CometLogger:
for path in paths: for path in paths:
self.log_asset(str(path)) self.log_asset(str(path))
if self.upload_dataset: if self.upload_dataset and not self.resume:
if not self.resume: self.upload_dataset_artifact()
self.upload_dataset_artifact()
return return
@@ -477,23 +470,22 @@ class CometLogger:
return return
def on_val_end(self, nt, tp, fp, p, r, f1, ap, ap50, ap_class, confusion_matrix): def on_val_end(self, nt, tp, fp, p, r, f1, ap, ap50, ap_class, confusion_matrix):
if self.comet_log_per_class_metrics: if self.comet_log_per_class_metrics and self.num_classes > 1:
if self.num_classes > 1: for i, c in enumerate(ap_class):
for i, c in enumerate(ap_class): class_name = self.class_names[c]
class_name = self.class_names[c] self.experiment.log_metrics(
self.experiment.log_metrics( {
{ "mAP@.5": ap50[i],
"mAP@.5": ap50[i], "mAP@.5:.95": ap[i],
"mAP@.5:.95": ap[i], "precision": p[i],
"precision": p[i], "recall": r[i],
"recall": r[i], "f1": f1[i],
"f1": f1[i], "true_positives": tp[i],
"true_positives": tp[i], "false_positives": fp[i],
"false_positives": fp[i], "support": nt[c],
"support": nt[c], },
}, prefix=class_name,
prefix=class_name, )
)
if self.comet_log_confusion_matrix: if self.comet_log_confusion_matrix:
epoch = self.experiment.curr_epoch epoch = self.experiment.curr_epoch
+16 -18
View File
@@ -4,7 +4,7 @@ from urllib.parse import urlparse
try: try:
import comet_ml import comet_ml
except (ModuleNotFoundError, ImportError): except ImportError:
comet_ml = None comet_ml = None
import yaml import yaml
@@ -109,14 +109,13 @@ def check_comet_weights(opt):
if comet_ml is None: if comet_ml is None:
return return
if isinstance(opt.weights, str): if isinstance(opt.weights, str) and opt.weights.startswith(COMET_PREFIX):
if opt.weights.startswith(COMET_PREFIX): api = comet_ml.API()
api = comet_ml.API() resource = urlparse(opt.weights)
resource = urlparse(opt.weights) experiment_path = f"{resource.netloc}{resource.path}"
experiment_path = f"{resource.netloc}{resource.path}" experiment = api.get(experiment_path)
experiment = api.get(experiment_path) download_model_checkpoint(opt, experiment)
download_model_checkpoint(opt, experiment) return True
return True
return None return None
@@ -136,15 +135,14 @@ def check_comet_resume(opt):
if comet_ml is None: if comet_ml is None:
return return
if isinstance(opt.resume, str): if isinstance(opt.resume, str) and opt.resume.startswith(COMET_PREFIX):
if opt.resume.startswith(COMET_PREFIX): api = comet_ml.API()
api = comet_ml.API() resource = urlparse(opt.resume)
resource = urlparse(opt.resume) experiment_path = f"{resource.netloc}{resource.path}"
experiment_path = f"{resource.netloc}{resource.path}" experiment = api.get(experiment_path)
experiment = api.get(experiment_path) set_opt_parameters(opt, experiment)
set_opt_parameters(opt, experiment) download_model_checkpoint(opt, experiment)
download_model_checkpoint(opt, experiment)
return True return True
return None return None
+23 -22
View File
@@ -65,28 +65,23 @@ class WandbLogger:
self.max_imgs_to_log = 16 self.max_imgs_to_log = 16
self.data_dict = None self.data_dict = None
if self.wandb: if self.wandb:
self.wandb_run = ( self.wandb_run = wandb.run or wandb.init(
wandb.init( config=opt,
config=opt, resume="allow",
resume="allow", project="YOLOv5" if opt.project == "runs/train" else Path(opt.project).stem,
project="YOLOv5" if opt.project == "runs/train" else Path(opt.project).stem, entity=opt.entity,
entity=opt.entity, name=opt.name if opt.name != "exp" else None,
name=opt.name if opt.name != "exp" else None, job_type=job_type,
job_type=job_type, id=run_id,
id=run_id, allow_val_change=True,
allow_val_change=True,
)
if not wandb.run
else wandb.run
) )
if self.wandb_run: if self.wandb_run and self.job_type == "Training":
if self.job_type == "Training": if isinstance(opt.data, dict):
if isinstance(opt.data, dict): # This means another dataset manager has already processed the dataset info (e.g. ClearML)
# This means another dataset manager has already processed the dataset info (e.g. ClearML) # and they will have stored the already processed dict in opt.data
# and they will have stored the already processed dict in opt.data self.data_dict = opt.data
self.data_dict = opt.data self.setup_training(opt)
self.setup_training(opt)
def setup_training(self, opt): def setup_training(self, opt):
""" """
@@ -133,7 +128,7 @@ class WandbLogger:
best_model (boolean) -- Boolean representing if the current checkpoint is the best yet. best_model (boolean) -- Boolean representing if the current checkpoint is the best yet.
""" """
model_artifact = wandb.Artifact( model_artifact = wandb.Artifact(
"run_" + wandb.run.id + "_model", f"run_{wandb.run.id}_model",
type="model", type="model",
metadata={ metadata={
"original_url": str(path), "original_url": str(path),
@@ -146,7 +141,13 @@ class WandbLogger:
) )
model_artifact.add_file(str(path / "last.pt"), name="last.pt") model_artifact.add_file(str(path / "last.pt"), name="last.pt")
wandb.log_artifact( wandb.log_artifact(
model_artifact, aliases=["latest", "last", "epoch " + str(self.current_epoch), "best" if best_model else ""] model_artifact,
aliases=[
"latest",
"last",
f"epoch {str(self.current_epoch)}",
"best" if best_model else "",
],
) )
LOGGER.info(f"Saving model artifact on epoch {epoch + 1}") LOGGER.info(f"Saving model artifact on epoch {epoch + 1}")
+1 -2
View File
@@ -35,7 +35,7 @@ def ap_per_class_box_and_mask(
tp_m, conf, pred_cls, target_cls, plot=plot, save_dir=save_dir, names=names, prefix="Mask" tp_m, conf, pred_cls, target_cls, plot=plot, save_dir=save_dir, names=names, prefix="Mask"
)[2:] )[2:]
results = { return {
"boxes": { "boxes": {
"p": results_boxes[0], "p": results_boxes[0],
"r": results_boxes[1], "r": results_boxes[1],
@@ -51,7 +51,6 @@ def ap_per_class_box_and_mask(
"ap_class": results_masks[4], "ap_class": results_masks[4],
}, },
} }
return results
class Metric: class Metric: