From 9fb255a99dff208a74280ce9253c816a5164e415 Mon Sep 17 00:00:00 2001 From: Sourcery AI <> Date: Fri, 1 Jul 2022 03:19:53 +0000 Subject: [PATCH] 'Refactored by Sourcery' --- setup.py | 3 +- test/test_models.py | 45 ++++------- test/test_models_anchor_utils.py | 3 +- test/test_runtime_ort.py | 8 +- tools/eval_metric.py | 5 +- tools/run_clang_format.py | 5 +- yolort/data/_helper.py | 9 +-- yolort/data/coco.py | 12 +-- yolort/data/coco_eval.py | 27 +++---- yolort/data/data_module.py | 8 +- yolort/data/distributed.py | 16 ++-- yolort/data/transforms.py | 10 +-- yolort/models/_checkpoint.py | 9 +-- yolort/models/_utils.py | 4 +- yolort/models/anchor_utils.py | 2 +- yolort/models/backbone_utils.py | 5 +- yolort/models/box_head.py | 8 +- yolort/models/darknetv4.py | 29 ++++--- yolort/models/darknetv6.py | 18 +++-- yolort/models/path_aggregation_network.py | 19 ++--- yolort/models/transform.py | 8 +- yolort/models/yolo.py | 18 ++--- yolort/models/yolov5.py | 30 +++---- yolort/relay/head_helper.py | 6 +- yolort/relay/ir_visualizer.py | 2 +- yolort/relay/trace_wrapper.py | 2 +- yolort/relay/trt_inference.py | 5 +- yolort/runtime/ort_helper.py | 5 +- yolort/runtime/y_onnxruntime.py | 16 ++-- yolort/runtime/y_tensorrt.py | 4 +- yolort/trainer/lightning_task.py | 2 +- yolort/utils/annotations_converter.py | 4 +- yolort/utils/image_utils.py | 21 ++--- yolort/utils/logger.py | 14 +--- yolort/utils/visualizer.py | 16 ++-- yolort/v5/helper.py | 18 +++-- yolort/v5/models/common.py | 23 +++--- yolort/v5/models/experimental.py | 5 +- yolort/v5/models/yolo.py | 2 +- yolort/v5/utils/augmentations.py | 10 +-- yolort/v5/utils/autoanchor.py | 2 +- yolort/v5/utils/callbacks.py | 5 +- yolort/v5/utils/datasets.py | 9 +-- yolort/v5/utils/general.py | 41 +++++----- yolort/v5/utils/loss.py | 10 +-- yolort/v5/utils/metrics.py | 98 +++++++++++------------ yolort/v5/utils/plots.py | 11 ++- yolort/v5/utils/torch_utils.py | 19 +++-- 48 files changed, 279 insertions(+), 372 deletions(-) diff --git a/setup.py b/setup.py index c259fac5..2c2befbd 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ Adapted from TorchVision, see: https://github.com/pytorch/vision/blob/master/setup.py """ + import os import subprocess from pathlib import Path @@ -27,7 +28,7 @@ if os.getenv("BUILD_VERSION"): version = os.getenv("BUILD_VERSION") elif sha != "Unknown": - version += "+" + sha[:7] + version += f"+{sha[:7]}" def write_version_file(): diff --git a/test/test_models.py b/test/test_models.py index 2c4820ac..1d86f8d0 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -45,8 +45,7 @@ def get_export_import_copy(m): buffer = io.BytesIO() torch.jit.save(m, buffer) buffer.seek(0) - imported = torch.jit.load(buffer) - return imported + return torch.jit.load(buffer) m_import = get_export_import_copy(m) with freeze_rng_state(): @@ -98,40 +97,33 @@ class TestModel: @staticmethod def _get_in_channels(width_multiple, use_p6): grow_widths = [256, 512, 768, 1024] if use_p6 else [256, 512, 1024] - in_channels = [int(gw * width_multiple) for gw in grow_widths] - return in_channels + return [int(gw * width_multiple) for gw in grow_widths] @staticmethod def _get_strides(use_p6: bool): - if use_p6: - strides = [8, 16, 32, 64] - else: - strides = [8, 16, 32] - return strides + return [8, 16, 32, 64] if use_p6 else [8, 16, 32] @staticmethod def _get_anchor_grids(use_p6: bool): - if use_p6: - anchor_grids = [ + return ( + [ [19, 27, 44, 40, 38, 94], [96, 68, 86, 152, 180, 137], [140, 301, 303, 264, 238, 542], [436, 615, 739, 380, 925, 792], ] - else: - anchor_grids = [ + if use_p6 + else [ [10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326], ] - return anchor_grids + ) def _compute_anchors(self, height, width, use_p6: bool): strides = self._get_strides(use_p6) anchors_num = len(strides) - anchors_shape = [] - for s in strides: - anchors_shape.append((height // s, width // s)) + anchors_shape = [(height // s, width // s) for s in strides] return anchors_num, anchors_shape def _get_feature_shapes(self, height, width, width_multiple=0.5, use_p6=False): @@ -147,8 +139,7 @@ def _get_feature_maps(self, batch_size, height, width, width_multiple=0.5, use_p width_multiple=width_multiple, use_p6=use_p6, ) - feature_maps = [torch.rand(batch_size, *f_shape) for f_shape in feature_shapes] - return feature_maps + return [torch.rand(batch_size, *f_shape) for f_shape in feature_shapes] def _get_head_outputs(self, batch_size, height, width, width_multiple=0.5, use_p6=False): feature_shapes = self._get_feature_shapes( @@ -160,9 +151,7 @@ def _get_head_outputs(self, batch_size, height, width, width_multiple=0.5, use_p num_outputs = self.num_outputs head_shapes = [(batch_size, 3, *f_shape[1:], num_outputs) for f_shape in feature_shapes] - head_outputs = [torch.rand(*h_shape) for h_shape in head_shapes] - - return head_outputs + return [torch.rand(*h_shape) for h_shape in head_shapes] def _init_test_backbone_with_pan( self, @@ -176,14 +165,13 @@ def _init_test_backbone_with_pan( backbone_name = f"darknet_{model_size}_{version.replace('.', '_')}" backbone_arch = eval(f"darknet_{'tan' if use_tan else 'pan'}_backbone") assert backbone_arch in [darknet_pan_backbone, darknet_tan_backbone] - model = backbone_arch( + return backbone_arch( backbone_name, depth_multiple, width_multiple, version=version, use_p6=use_p6, ) - return model @pytest.mark.parametrize( "depth_multiple, width_multiple, version, use_p6, use_tan", @@ -226,8 +214,7 @@ def test_backbone_with_pan( def _init_test_anchor_generator(self, use_p6=False): strides = self._get_strides(use_p6) anchor_grids = self._get_anchor_grids(use_p6) - anchor_generator = AnchorGenerator(strides, anchor_grids) - return anchor_generator + return AnchorGenerator(strides, anchor_grids) @pytest.mark.parametrize( "width_multiple, use_p6", @@ -256,8 +243,7 @@ def _init_test_yolo_head(self, width_multiple=0.5, use_p6=False): num_anchors = len(strides) num_classes = self.num_classes - box_head = YOLOHead(in_channels, num_anchors, strides, num_classes) - return box_head + return YOLOHead(in_channels, num_anchors, strides, num_classes) def test_yolo_head(self): N, H, W = 4, 416, 352 @@ -277,8 +263,7 @@ def _init_test_postprocessors(self, strides): score_thresh = 0.5 nms_thresh = 0.45 detections_per_img = 100 - postprocessors = PostProcess(strides, score_thresh, nms_thresh, detections_per_img) - return postprocessors + return PostProcess(strides, score_thresh, nms_thresh, detections_per_img) @pytest.mark.parametrize("use_p6", [False, True]) def test_postprocessors(self, use_p6): diff --git a/test/test_models_anchor_utils.py b/test/test_models_anchor_utils.py index 803de860..fdc15d7d 100644 --- a/test/test_models_anchor_utils.py +++ b/test/test_models_anchor_utils.py @@ -8,8 +8,7 @@ class TestAnchorGenerator: def get_features(self, images): s0, s1 = images.shape[-2:] - features = [torch.rand(2, 8, s0 // 5, s1 // 5)] - return features + return [torch.rand(2, 8, s0 // 5, s1 // 5)] def test_anchor_generator(self): images = torch.rand(2, 3, 10, 10) diff --git a/test/test_runtime_ort.py b/test/test_runtime_ort.py index 12f1b47d..f43998c2 100644 --- a/test/test_runtime_ort.py +++ b/test/test_runtime_ort.py @@ -36,7 +36,7 @@ def run_model(self, model, inputs_list): # validate the exported model with onnx runtime for test_inputs in inputs_list: with torch.no_grad(): - if isinstance(test_inputs, Tensor) or isinstance(test_inputs, list): + if isinstance(test_inputs, (Tensor, list)): test_inputs = (test_inputs,) test_outputs = model(*test_inputs) if isinstance(test_outputs, Tensor): @@ -55,15 +55,13 @@ def ort_validate(self, onnx_io, inputs, outputs): # Inference on ONNX Runtime ort_outs = y_runtime.predict(inputs) - for i in range(0, len(outputs)): + for i in range(len(outputs)): torch.testing.assert_allclose(outputs[i], ort_outs[i], rtol=1e-03, atol=1e-05) def get_image(self, img_name): img_path = Path(__file__).parent.resolve() / "assets" / img_name - image = read_image(str(img_path)) / 255 - - return image + return read_image(str(img_path)) / 255 def get_test_images(self): return self.get_image("bus.jpg"), self.get_image("zidane.jpg") diff --git a/tools/eval_metric.py b/tools/eval_metric.py index 0ab6adae..d82acc69 100644 --- a/tools/eval_metric.py +++ b/tools/eval_metric.py @@ -168,7 +168,7 @@ def evaluate(model, data_loader, coco_evaluator, device, print_freq, use_wandb, ) header = "Test:" for images, targets in metric_logger.log_every(data_loader, print_freq, header): - images = list(image.to(device) for image in images) + images = [image.to(device) for image in images] if torch.cuda.is_available(): torch.cuda.synchronize() @@ -185,8 +185,7 @@ def evaluate(model, data_loader, coco_evaluator, device, print_freq, use_wandb, # gather the stats from all processes metric_logger.synchronize_between_processes() - results = coco_evaluator.compute() - return results + return coco_evaluator.compute() def cli_main(): diff --git a/tools/run_clang_format.py b/tools/run_clang_format.py index 46bb143d..cb2bf256 100644 --- a/tools/run_clang_format.py +++ b/tools/run_clang_format.py @@ -113,8 +113,7 @@ def __init__(self, message, exc=None): def run_clang_format_diff_wrapper(args, file): try: - ret = run_clang_format_diff(args, file) - return ret + return run_clang_format_diff(args, file) except DiffError: raise except Exception as e: @@ -293,7 +292,7 @@ def main(): colored_stdout = sys.stdout.isatty() colored_stderr = sys.stderr.isatty() - version_invocation = [args.clang_format_executable, str("--version")] + version_invocation = [args.clang_format_executable, "--version"] try: subprocess.check_call(version_invocation, stdout=DEVNULL) except subprocess.CalledProcessError as e: diff --git a/yolort/data/_helper.py b/yolort/data/_helper.py index ede98717..d3395056 100644 --- a/yolort/data/_helper.py +++ b/yolort/data/_helper.py @@ -24,7 +24,7 @@ def create_small_table(small_dict): str: the table as a string. """ keys, values = tuple(zip(*small_dict.items())) - table = tabulate( + return tabulate( [values], headers=keys, tablefmt="pipe", @@ -32,7 +32,6 @@ def create_small_table(small_dict): stralign="center", numalign="center", ) - return table def get_coco_api_from_dataset(dataset): @@ -65,8 +64,8 @@ def prepare_coco128( data_path.mkdir(parents=True, exist_ok=True) zip_path = data_path / "coco128.zip" - coco128_url = "https://github.com/zhiqwang/yolov5-rt-stack/releases/download/v0.3.0/coco128.zip" if not zip_path.is_file(): + coco128_url = "https://github.com/zhiqwang/yolov5-rt-stack/releases/download/v0.3.0/coco128.zip" logger.info(f"Downloading coco128 datasets form {coco128_url}") torch.hub.download_url_to_file(coco128_url, zip_path, hash_prefix="a67d2887") @@ -106,7 +105,7 @@ def get_dataloader(data_root: str, mode: str = "val", batch_size: int = 4): # We adopt the sequential sampler in order to repeat the experiment sampler = torch.utils.data.SequentialSampler(dataset) - loader = torch.utils.data.DataLoader( + return torch.utils.data.DataLoader( dataset, batch_size, sampler=sampler, @@ -114,5 +113,3 @@ def get_dataloader(data_root: str, mode: str = "val", batch_size: int = 4): collate_fn=collate_fn, num_workers=0, ) - - return loader diff --git a/yolort/data/coco.py b/yolort/data/coco.py index 3e693ad4..18958eb9 100644 --- a/yolort/data/coco.py +++ b/yolort/data/coco.py @@ -64,8 +64,7 @@ def __call__(self, image, target): if anno and "keypoints" in anno[0]: keypoints = [obj["keypoints"] for obj in anno] keypoints = torch.as_tensor(keypoints, dtype=torch.float32) - num_keypoints = keypoints.shape[0] - if num_keypoints: + if num_keypoints := keypoints.shape[0]: keypoints = keypoints.view(num_keypoints, -1, 3) keep = (boxes[:, 3] > boxes[:, 1]) & (boxes[:, 2] > boxes[:, 0]) @@ -76,9 +75,7 @@ def __call__(self, image, target): if keypoints is not None: keypoints = keypoints[keep] - target = {} - target["boxes"] = boxes - target["labels"] = classes + target = {"boxes": boxes, "labels": classes} if self.return_masks: target["masks"] = masks target["image_id"] = image_id @@ -109,7 +106,6 @@ def convert_coco_poly_to_mask(segmentations, height, width): mask = mask.any(dim=2) masks.append(mask) if masks: - masks = torch.stack(masks, dim=0) + return torch.stack(masks, dim=0) else: - masks = torch.zeros((0, height, width), dtype=torch.uint8) - return masks + return torch.zeros((0, height, width), dtype=torch.uint8) diff --git a/yolort/data/coco_eval.py b/yolort/data/coco_eval.py index bb23a7c8..168ec28e 100644 --- a/yolort/data/coco_eval.py +++ b/yolort/data/coco_eval.py @@ -61,7 +61,7 @@ def __init__( dist_sync_fn=dist_sync_fn, ) self._logger = logging.getLogger(__name__) - if isinstance(coco_gt, str) or isinstance(coco_gt, PosixPath): + if isinstance(coco_gt, (str, PosixPath)): with contextlib.redirect_stdout(io.StringIO()): coco_gt = COCO(coco_gt) elif isinstance(coco_gt, COCO): @@ -70,10 +70,10 @@ def __init__( raise NotImplementedError(f"Currently not supports type {type(coco_gt)}") self.coco_gt = coco_gt - if eval_type == "yolov5": - self.category_id_maps = coco_gt.getCatIds() - elif eval_type == "torchvision": + if eval_type == "torchvision": self.category_id_maps = list(range(coco_gt.getCatIds()[-1] + 1)) + elif eval_type == "yolov5": + self.category_id_maps = coco_gt.getCatIds() else: raise NotImplementedError(f"Currently not supports eval type {eval_type}") @@ -116,8 +116,7 @@ def compute(self): # Summarize coco_eval.summarize() - results = self.derive_coco_results() - return results + return self.derive_coco_results() def derive_coco_results(self, class_names: Optional[List[str]] = None): """ @@ -149,7 +148,10 @@ def derive_coco_results(self, class_names: Optional[List[str]] = None): metric: float(self.coco_eval.stats[idx] * 100 if self.coco_eval.stats[idx] >= 0 else "nan") for idx, metric in enumerate(metrics) } - self._logger.info(f"Evaluation results for {self.iou_type}:\n" + create_small_table(results)) + self._logger.info( + f"Evaluation results for {self.iou_type}:\n{create_small_table(results)}" + ) + if not np.isfinite(sum(results.values())): self._logger.info("Some metrics cannot be computed and is shown as NaN.") @@ -181,9 +183,9 @@ def derive_coco_results(self, class_names: Optional[List[str]] = None): headers=["category", "AP"] * (N_COLS // 2), numalign="left", ) - self._logger.info(f"Per-category {self.iou_type} AP:\n" + table) + self._logger.info(f"Per-category {self.iou_type} AP:\n{table}") - results.update({"AP-" + name: ap for name, ap in results_per_category}) + results |= {f"AP-{name}": ap for name, ap in results_per_category} return results def prepare(self, predictions, iou_type): @@ -229,10 +231,7 @@ def merge(img_ids, eval_imgs): for p in all_img_ids: merged_img_ids.extend(p) - merged_eval_imgs = [] - for p in all_eval_imgs: - merged_eval_imgs.append(p) - + merged_eval_imgs = list(all_eval_imgs) merged_img_ids = np.array(merged_img_ids) merged_eval_imgs = np.concatenate(merged_eval_imgs, 2) @@ -285,7 +284,7 @@ def evaluate(self): # loop through images, area range, max detection number catIds = p.catIds if p.useCats else [-1] - if p.iouType == "segm" or p.iouType == "bbox": + if p.iouType in ["segm", "bbox"]: computeIoU = self.computeIoU elif p.iouType == "keypoints": computeIoU = self.computeOks diff --git a/yolort/data/data_module.py b/yolort/data/data_module.py index 55510da5..07ceaaa5 100644 --- a/yolort/data/data_module.py +++ b/yolort/data/data_module.py @@ -54,15 +54,13 @@ def train_dataloader(self) -> None: drop_last=True, ) - loader = torch.utils.data.DataLoader( + return torch.utils.data.DataLoader( self._train_dataset, batch_sampler=batch_sampler, collate_fn=collate_fn, num_workers=self.num_workers, ) - return loader - def val_dataloader(self) -> None: """ VOCDetection and COCODetection @@ -73,7 +71,7 @@ def val_dataloader(self) -> None: # Creating data loaders sampler = torch.utils.data.SequentialSampler(self._val_dataset) - loader = torch.utils.data.DataLoader( + return torch.utils.data.DataLoader( self._val_dataset, self.batch_size, sampler=sampler, @@ -82,8 +80,6 @@ def val_dataloader(self) -> None: num_workers=self.num_workers, ) - return loader - class COCODetectionDataModule(DetectionDataModule): def __init__( diff --git a/yolort/data/distributed.py b/yolort/data/distributed.py index afd5a174..97b93e6e 100644 --- a/yolort/data/distributed.py +++ b/yolort/data/distributed.py @@ -33,9 +33,11 @@ def all_gather(data): # receiving Tensor from all ranks # we pad the tensor because torch all_gather does not support # gathering tensors of different shapes - tensor_list = [] - for _ in size_list: - tensor_list.append(torch.empty((max_size,), dtype=torch.uint8, device="cuda")) + tensor_list = [ + torch.empty((max_size,), dtype=torch.uint8, device="cuda") + for _ in size_list + ] + if local_size != max_size: padding = torch.empty(size=(max_size - local_size,), dtype=torch.uint8, device="cuda") tensor = torch.cat((tensor, padding), dim=0) @@ -58,6 +60,8 @@ def is_dist_avail_and_initialized(): def get_world_size(): - if not is_dist_avail_and_initialized(): - return 1 - return torch.distributed.get_world_size() + return ( + torch.distributed.get_world_size() + if is_dist_avail_and_initialized() + else 1 + ) diff --git a/yolort/data/transforms.py b/yolort/data/transforms.py index cfc98cb9..75f3fec5 100644 --- a/yolort/data/transforms.py +++ b/yolort/data/transforms.py @@ -306,9 +306,8 @@ def forward( image = self._brightness(image) contrast_before = r[1] < 0.5 - if contrast_before: - if r[2] < self.p: - image = self._contrast(image) + if contrast_before and r[2] < self.p: + image = self._contrast(image) if r[3] < self.p: image = self._saturation(image) @@ -316,9 +315,8 @@ def forward( if r[4] < self.p: image = self._hue(image) - if not contrast_before: - if r[5] < self.p: - image = self._contrast(image) + if not contrast_before and r[5] < self.p: + image = self._contrast(image) if r[6] < self.p: channels = get_image_num_channels(image) diff --git a/yolort/models/_checkpoint.py b/yolort/models/_checkpoint.py index 0dbbcef1..db600801 100644 --- a/yolort/models/_checkpoint.py +++ b/yolort/models/_checkpoint.py @@ -46,23 +46,18 @@ def load_from_ultralytics(checkpoint_path: str, version: str = "r6.0"): depth_multiple = checkpoint_yolov5.yaml["depth_multiple"] width_multiple = checkpoint_yolov5.yaml["width_multiple"] - use_p6 = False - if len(strides) == 4: - use_p6 = True - + use_p6 = len(strides) == 4 if use_p6: inner_block_maps = {"0": "11", "1": "12", "3": "15", "4": "16", "6": "19", "7": "20"} layer_block_maps = {"0": "23", "1": "24", "2": "26", "3": "27", "4": "29", "5": "30", "6": "32"} p6_block_maps = {"0": "9", "1": "10"} head_ind = 33 - head_name = "m" else: inner_block_maps = {"0": "9", "1": "10", "3": "13", "4": "14"} layer_block_maps = {"0": "17", "1": "18", "2": "20", "3": "21", "4": "23"} p6_block_maps = None head_ind = 24 - head_name = "m" - + head_name = "m" convert_yolo_checkpoint = CheckpointConverter( depth_multiple, width_multiple, diff --git a/yolort/models/_utils.py b/yolort/models/_utils.py index a43d612c..cd4ed96c 100644 --- a/yolort/models/_utils.py +++ b/yolort/models/_utils.py @@ -35,9 +35,7 @@ def encode_single(reference_boxes: Tensor, anchors: Tensor) -> Tensor: pred_xy = reference_boxes[:, :2] * 2.0 - 0.5 pred_wh = (reference_boxes[:, 2:4] * 2) ** 2 * anchors - pred_boxes = torch.cat((pred_xy, pred_wh), 1) - - return pred_boxes + return torch.cat((pred_xy, pred_wh), 1) def decode_single( diff --git a/yolort/models/anchor_utils.py b/yolort/models/anchor_utils.py index 086709dc..f760d4d9 100644 --- a/yolort/models/anchor_utils.py +++ b/yolort/models/anchor_utils.py @@ -60,7 +60,7 @@ def _generate_shifts( return shifts def forward(self, feature_maps: List[Tensor]) -> Tuple[List[Tensor], List[Tensor]]: - grid_sizes = list([feature_map.shape[-2:] for feature_map in feature_maps]) + grid_sizes = [feature_map.shape[-2:] for feature_map in feature_maps] dtype, device = feature_maps[0].dtype, feature_maps[0].device grids = self._generate_grids(grid_sizes, dtype=dtype, device=device) shifts = self._generate_shifts(grid_sizes, dtype=dtype, device=device) diff --git a/yolort/models/backbone_utils.py b/yolort/models/backbone_utils.py index 0c0f730c..8ee0188d 100644 --- a/yolort/models/backbone_utils.py +++ b/yolort/models/backbone_utils.py @@ -92,11 +92,12 @@ def darknet_pan_backbone( are ["r3.1", "r4.0", "r6.0"]. Default: "r6.0". use_p6 (bool): Whether to use P6 layers. """ - assert version in [ + assert version in { "r3.1", "r4.0", "r6.0", - ], "Currently only supports version 'r3.1', 'r4.0' and 'r6.0'." + }, "Currently only supports version 'r3.1', 'r4.0' and 'r6.0'." + last_channel = 768 if use_p6 else 1024 backbone = darknet.__dict__[backbone_name]( diff --git a/yolort/models/box_head.py b/yolort/models/box_head.py index cd46c61e..0b1ded7d 100644 --- a/yolort/models/box_head.py +++ b/yolort/models/box_head.py @@ -52,17 +52,13 @@ def get_result_from_head(self, features: Tensor, idx: int) -> Tensor: This is equivalent to self.head[idx](features), but torchscript doesn't support this yet """ - num_blocks = 0 - for m in self.head: - num_blocks += 1 + num_blocks = sum(1 for _ in self.head) if idx < 0: idx += num_blocks - i = 0 out = features - for module in self.head: + for i, module in enumerate(self.head): if i == idx: out = module(features) - i += 1 return out def forward(self, x: List[Tensor]) -> List[Tensor]: diff --git a/yolort/models/darknetv4.py b/yolort/models/darknetv4.py index e9cd00ed..84509e79 100644 --- a/yolort/models/darknetv4.py +++ b/yolort/models/darknetv4.py @@ -62,10 +62,11 @@ def __init__( ) -> None: super().__init__() - assert version in ["r3.1", "r4.0"], ( + assert version in {"r3.1", "r4.0"}, ( "Currently the module version used in DarkNetV4 is r3.1 or r4.0", ) + if block is None: block = _block[version] @@ -89,14 +90,23 @@ def __init__( for depth_gain, out_channel in zip(stages_repeats, stages_out_channels): depth_gain = max(round(depth_gain * depth_multiple), 1) out_channel = _make_divisible(out_channel * width_multiple, round_nearest) - layers.append(Conv(input_channel, out_channel, k=3, s=2, version=version)) - layers.append(block(out_channel, out_channel, n=depth_gain)) + layers.extend( + ( + Conv(input_channel, out_channel, k=3, s=2, version=version), + block(out_channel, out_channel, n=depth_gain), + ) + ) + input_channel = out_channel # building last CSP blocks last_channel = _make_divisible(last_channel * width_multiple, round_nearest) - layers.append(Conv(input_channel, last_channel, k=3, s=2, version=version)) - layers.append(SPP(last_channel, last_channel, k=(5, 9, 13), version=version)) + layers.extend( + ( + Conv(input_channel, last_channel, k=3, s=2, version=version), + SPP(last_channel, last_channel, k=(5, 9, 13), version=version), + ) + ) self.features = nn.Sequential(*layers) self.avgpool = nn.AdaptiveAvgPool2d(1) @@ -109,8 +119,8 @@ def __init__( for m in self.modules(): if isinstance(m, nn.Conv2d): - pass # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') - elif isinstance(m, nn.BatchNorm2d): + continue + if isinstance(m, nn.BatchNorm2d): m.eps = 1e-3 m.momentum = 0.03 elif isinstance(m, (nn.Hardswish, nn.LeakyReLU, nn.ReLU, nn.ReLU6)): @@ -146,9 +156,8 @@ def _darknet_v4_conf(arch: str, pretrained: bool, progress: bool, *args: Any, ** model_url = model_urls[arch] if model_url is None: raise NotImplementedError(f"pretrained {arch} is not supported as of now") - else: - state_dict = load_state_dict_from_url(model_url, progress=progress) - model.load_state_dict(state_dict) + state_dict = load_state_dict_from_url(model_url, progress=progress) + model.load_state_dict(state_dict) return model diff --git a/yolort/models/darknetv6.py b/yolort/models/darknetv6.py index 59807f83..6d8b3826 100644 --- a/yolort/models/darknetv6.py +++ b/yolort/models/darknetv6.py @@ -85,8 +85,13 @@ def __init__( for depth_gain, out_channel in zip(stages_repeats, stages_out_channels): depth_gain = max(round(depth_gain * depth_multiple), 1) out_channel = _make_divisible(out_channel * width_multiple, round_nearest) - layers.append(Conv(input_channel, out_channel, k=3, s=2, version=version)) - layers.append(block(out_channel, out_channel, n=depth_gain)) + layers.extend( + ( + Conv(input_channel, out_channel, k=3, s=2, version=version), + block(out_channel, out_channel, n=depth_gain), + ) + ) + input_channel = out_channel # building last CSP blocks @@ -106,8 +111,8 @@ def __init__( for m in self.modules(): if isinstance(m, nn.Conv2d): - pass # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') - elif isinstance(m, nn.BatchNorm2d): + continue + if isinstance(m, nn.BatchNorm2d): m.eps = 1e-3 m.momentum = 0.03 elif isinstance(m, (nn.Hardswish, nn.LeakyReLU, nn.ReLU, nn.ReLU6)): @@ -137,9 +142,8 @@ def _darknet_v6_conf(arch: str, pretrained: bool, progress: bool, *args: Any, ** model_url = model_urls[arch] if model_url is None: raise NotImplementedError(f"pretrained {arch} is not supported as of now") - else: - state_dict = load_state_dict_from_url(model_url, progress=progress) - model.load_state_dict(state_dict) + state_dict = load_state_dict_from_url(model_url, progress=progress) + model.load_state_dict(state_dict) return model diff --git a/yolort/models/path_aggregation_network.py b/yolort/models/path_aggregation_network.py index 18992b44..bbe3fb36 100644 --- a/yolort/models/path_aggregation_network.py +++ b/yolort/models/path_aggregation_network.py @@ -108,7 +108,7 @@ def __init__( if version == "r6.0": init_block = SPP(in_channels[-1], in_channels[-1], k=(5, 9, 13)) - elif version in ["r3.1", "r4.0"]: + elif version in {"r3.1", "r4.0"}: init_block = block(in_channels[-1], in_channels[-1], n=depth_gain, shortcut=False) else: raise NotImplementedError(f"Version {version} is not implemented yet.") @@ -169,15 +169,13 @@ def get_result_from_inner_blocks(self, x: Tensor, idx: int) -> Tensor: This is equivalent to self.inner_blocks[idx](x), but torchscript doesn't support this yet """ - num_blocks = len(self.inner_blocks) if idx < 0: + num_blocks = len(self.inner_blocks) idx += num_blocks - i = 0 out = x - for module in self.inner_blocks: + for i, module in enumerate(self.inner_blocks): if i == idx: out = module(x) - i += 1 return out def get_result_from_layer_blocks(self, x: Tensor, idx: int) -> Tensor: @@ -185,15 +183,13 @@ def get_result_from_layer_blocks(self, x: Tensor, idx: int) -> Tensor: This is equivalent to self.layer_blocks[idx](x), but torchscript doesn't support this yet """ - num_blocks = len(self.layer_blocks) if idx < 0: + num_blocks = len(self.layer_blocks) idx += num_blocks - i = 0 out = x - for module in self.layer_blocks: + for i, module in enumerate(self.layer_blocks): if i == idx: out = module(x) - i += 1 return out def forward(self, x: Dict[str, Tensor]) -> List[Tensor]: @@ -225,11 +221,8 @@ def forward(self, x: Dict[str, Tensor]) -> List[Tensor]: inners.insert(0, last_inner) - # Ascending the feature pyramid - results = [] last_inner = self.get_result_from_layer_blocks(inners[0], 0) - results.append(last_inner) - + results = [last_inner] for idx in range(num_features - 1): last_inner = self.get_result_from_layer_blocks(last_inner, 2 * idx + 1) last_inner = torch.cat([last_inner, inners[idx + 1]], dim=1) diff --git a/yolort/models/transform.py b/yolort/models/transform.py index 8e923f18..dfe029de 100644 --- a/yolort/models/transform.py +++ b/yolort/models/transform.py @@ -176,7 +176,7 @@ def forward( """ device = images[0].device - images = [img for img in images] + images = list(images) if targets is not None: # make a copy of targets to avoid modifying it in-place # once torchscript supports dict comprehension @@ -184,9 +184,7 @@ def forward( # targets = [{k: v for k,v in t.items()} for t in targets] targets_copy: List[Dict[str, Tensor]] = [] for t in targets: - data: Dict[str, Tensor] = {} - for k, v in t.items(): - data[k] = v.to(device=device) + data: Dict[str, Tensor] = {k: v.to(device=device) for k, v in t.items()} targets_copy.append(data) targets = targets_copy @@ -353,7 +351,7 @@ def postprocess( return result def __repr__(self): - format_string = self.__class__.__name__ + "(" + format_string = f"{self.__class__.__name__}(" _indent = "\n " format_string += f"{_indent}Resize(min_size={self.min_size}, max_size={self.max_size})" format_string += "\n)" diff --git a/yolort/models/yolo.py b/yolort/models/yolo.py index fdca11e8..9f192d66 100644 --- a/yolort/models/yolo.py +++ b/yolort/models/yolo.py @@ -133,10 +133,7 @@ def eager_outputs( losses: Dict[str, Tensor], detections: List[Dict[str, Tensor]], ) -> Tuple[Dict[str, Tensor], List[Dict[str, Tensor]]]: - if self.training: - return losses - - return detections + return losses if self.training else detections def forward( self, @@ -174,13 +171,12 @@ def forward( # compute the detections detections = self.post_process(head_outputs, grids, shifts) - if torch.jit.is_scripting(): - if not self._has_warned: - warnings.warn("YOLO always returns a (Losses, Detections) tuple in scripting.") - self._has_warned = True - return losses, detections - else: + if not torch.jit.is_scripting(): return self.eager_outputs(losses, detections) + if not self._has_warned: + warnings.warn("YOLO always returns a (Losses, Detections) tuple in scripting.") + self._has_warned = True + return losses, detections @classmethod def load_from_yolov5( @@ -847,7 +843,6 @@ def yolov5_darknet_tan_s_r40( progress (bool): If True, displays a progress bar of the download to stderr """ backbone_name = "darknet_s_r4_0" - weights_name = "yolov5_darknet_tan_s_r40_coco" depth_multiple = 0.33 width_multiple = 0.5 version = "r4.0" @@ -856,6 +851,7 @@ def yolov5_darknet_tan_s_r40( model = YOLO(backbone, num_classes, **kwargs) if pretrained: + weights_name = "yolov5_darknet_tan_s_r40_coco" if model_urls.get(weights_name, None) is None: raise ValueError(f"No checkpoint is available for model {weights_name}") state_dict = load_state_dict_from_url(model_urls[weights_name], progress=progress) diff --git a/yolort/models/yolov5.py b/yolort/models/yolov5.py index 6b7d6168..46d58a38 100644 --- a/yolort/models/yolov5.py +++ b/yolort/models/yolov5.py @@ -157,17 +157,10 @@ def forward( if self.training: # compute the losses - if torch.jit.is_scripting(): - losses = outputs[0] - else: - losses = outputs + losses = outputs[0] if torch.jit.is_scripting() else outputs else: # Rescale coordinate - if torch.jit.is_scripting(): - result = outputs[1] - else: - result = outputs - + result = outputs[1] if torch.jit.is_scripting() else outputs if torchvision._is_tracing(): im_shape = _get_shape_onnx(samples.tensors) else: @@ -175,13 +168,12 @@ def forward( detections = self.transform.postprocess(result, im_shape, original_image_sizes) - if torch.jit.is_scripting(): - if not self._has_warned: - warnings.warn("YOLOv5 always returns a (Losses, Detections) tuple in scripting.") - self._has_warned = True - return losses, detections - else: + if not torch.jit.is_scripting(): return self.eager_outputs(losses, detections) + if not self._has_warned: + warnings.warn("YOLOv5 always returns a (Losses, Detections) tuple in scripting.") + self._has_warned = True + return losses, detections @torch.jit.unused def eager_outputs( @@ -189,10 +181,7 @@ def eager_outputs( losses: Dict[str, Tensor], detections: List[Dict[str, Tensor]], ) -> Tuple[Dict[str, Tensor], List[Dict[str, Tensor]]]: - if self.training: - return losses - - return detections + return losses if self.training else detections @torch.no_grad() def predict(self, x: Any, image_loader: Optional[Callable] = None) -> List[Dict[str, Tensor]]: @@ -282,11 +271,10 @@ def load_from_yolov5( fill_color (int): fill value for padding. Default: 114 """ model = YOLO.load_from_yolov5(checkpoint_path, **kwargs) - yolov5 = cls( + return cls( model=model, size=size, size_divisible=size_divisible, fixed_shape=fixed_shape, fill_color=fill_color, ) - return yolov5 diff --git a/yolort/relay/head_helper.py b/yolort/relay/head_helper.py index 6de1fcf4..7c0b9ac7 100644 --- a/yolort/relay/head_helper.py +++ b/yolort/relay/head_helper.py @@ -30,8 +30,7 @@ def __init__( def forward(self, x): x = self.model(x) - out = self.post_process(x) - return out + return self.post_process(x) class FakePostProcess(nn.Module): @@ -83,8 +82,7 @@ def forward(self, x: Tensor): i = i.unsqueeze(1) i = i.float() classes_keep = classes_keep.float() - out = torch.concat([i, boxes_keep, classes_keep, scores_keep], 1) - return out + return torch.concat([i, boxes_keep, classes_keep, scores_keep], 1) class NonMaxSupressionOp(torch.autograd.Function): diff --git a/yolort/relay/ir_visualizer.py b/yolort/relay/ir_visualizer.py index 76d0ad42..32702206 100644 --- a/yolort/relay/ir_visualizer.py +++ b/yolort/relay/ir_visualizer.py @@ -252,5 +252,5 @@ def is_relevant_type(t): if kind in ("ListType", "OptionalType"): return is_relevant_type(t.getElementType()) if kind == "TupleType": - return any([is_relevant_type(tt) for tt in t.elements()]) + return any(is_relevant_type(tt) for tt in t.elements()) return False diff --git a/yolort/relay/trace_wrapper.py b/yolort/relay/trace_wrapper.py index b6956c63..09699c0c 100644 --- a/yolort/relay/trace_wrapper.py +++ b/yolort/relay/trace_wrapper.py @@ -9,7 +9,7 @@ def dict_to_tuple(out_dict: Dict[str, Tensor]) -> Tuple: """ Convert the model output dictionary to tuple format. """ - if "masks" in out_dict.keys(): + if "masks" in out_dict: return ( out_dict["boxes"], out_dict["scores"], diff --git a/yolort/relay/trt_inference.py b/yolort/relay/trt_inference.py index b259b957..23fbeca1 100644 --- a/yolort/relay/trt_inference.py +++ b/yolort/relay/trt_inference.py @@ -53,10 +53,7 @@ def forward(self, inputs: Tensor) -> Tuple[Tensor, Tensor]: Args: inputs (Tensor): batched images, of shape [batch_size x 3 x H x W] """ - # Compute the detections - outputs = self.model(inputs) - - return outputs + return self.model(inputs) @torch.no_grad() def to_onnx( diff --git a/yolort/runtime/ort_helper.py b/yolort/runtime/ort_helper.py index ec5aa7d4..e49e8793 100644 --- a/yolort/runtime/ort_helper.py +++ b/yolort/runtime/ort_helper.py @@ -158,10 +158,7 @@ def _set_input_names(self): if self._batch_size == 1: return ["image"] - input_names = [] - for i in range(self._batch_size): - input_names.append(f"image{i + 1}") - return input_names + return [f"image{i + 1}" for i in range(self._batch_size)] def _set_output_names(self): if self._skip_preprocess: diff --git a/yolort/runtime/y_onnxruntime.py b/yolort/runtime/y_onnxruntime.py index ac92a99f..ed6e8805 100644 --- a/yolort/runtime/y_onnxruntime.py +++ b/yolort/runtime/y_onnxruntime.py @@ -51,7 +51,7 @@ def _set_providers(self): ort_device = ort.get_device() providers = None - enable_gpu = True if self.device == "cuda" else False + enable_gpu = self.device == "cuda" if ort_device == "GPU" and enable_gpu: providers = ["CUDAExecutionProvider"] logger.info("Set inference device to GPU") @@ -65,8 +65,7 @@ def _set_providers(self): @requires_module("onnxruntime") def _build_runtime(self): - runtime = ort.InferenceSession(self.engine_path, providers=self._providers) - return runtime + return ort.InferenceSession(self.engine_path, providers=self._providers) def default_loader(self, img_path: str) -> np.ndarray: """ @@ -96,9 +95,12 @@ def __call__(self, inputs: List[np.ndarray]): predictions (Tuple[List[float], List[int], List[float, float]]): stands for scores, labels and boxes respectively. """ - inputs = dict((self._runtime.get_inputs()[i].name, inpt) for i, inpt in enumerate(inputs)) - predictions = self._runtime.run(output_names=None, input_feed=inputs) - return predictions + inputs = { + self._runtime.get_inputs()[i].name: inpt + for i, inpt in enumerate(inputs) + } + + return self._runtime.run(output_names=None, input_feed=inputs) def predict(self, x: Any, image_loader: Optional[Callable] = None) -> List[Dict[str, np.ndarray]]: """ @@ -131,7 +133,7 @@ def collate_images(self, samples: Any, image_loader: Callable) -> List[np.ndarra return [samples] if contains_any_tensor(samples, dtype=np.ndarray): - return [sample for sample in samples] + return list(samples) if isinstance(samples, str): samples = [samples] diff --git a/yolort/runtime/y_tensorrt.py b/yolort/runtime/y_tensorrt.py index 6fc21f82..a4ab3905 100644 --- a/yolort/runtime/y_tensorrt.py +++ b/yolort/runtime/y_tensorrt.py @@ -200,9 +200,7 @@ def forward(self, inputs: List[Tensor]): # Rescale coordinate im_shape = torch.tensor(samples.tensors.shape[-2:]) - detections = self.transform.postprocess(results, im_shape, original_image_sizes) - - return detections + return self.transform.postprocess(results, im_shape, original_image_sizes) def predict(self, x: Any, image_loader: Optional[Callable] = None) -> List[Dict[str, Tensor]]: """ diff --git a/yolort/trainer/lightning_task.py b/yolort/trainer/lightning_task.py index c8cec1e1..693d0f79 100644 --- a/yolort/trainer/lightning_task.py +++ b/yolort/trainer/lightning_task.py @@ -99,7 +99,7 @@ def test_step(self, batch, batch_idx): The test step. """ images, targets = batch - images = list(image.to(next(self.parameters()).device) for image in images) + images = [image.to(next(self.parameters()).device) for image in images] preds = self.model(images) results = self.evaluator(preds, targets) # log step metric diff --git a/yolort/utils/annotations_converter.py b/yolort/utils/annotations_converter.py index 6f4eaf65..ea7b9e5d 100644 --- a/yolort/utils/annotations_converter.py +++ b/yolort/utils/annotations_converter.py @@ -80,14 +80,14 @@ def _get_metadata(metalabels: Union[str, List[str]]): @staticmethod def _get_licenses(set_license): if set_license: - licenses = [ + return [ { "id": 1, "name": "GNU General Public License v3.0", "url": "https://github.com/zhiqwang/yolov5-rt-stack/blob/main/LICENSE", } ] - return licenses + return None diff --git a/yolort/utils/image_utils.py b/yolort/utils/image_utils.py index 42448c48..84c0e51b 100644 --- a/yolort/utils/image_utils.py +++ b/yolort/utils/image_utils.py @@ -105,8 +105,7 @@ def get_image_from_url(url: str, flags: int = 1) -> np.ndarray: data = requests.get(url) buffer = BytesIO(data.content) bytes_as_np_array = np.frombuffer(buffer.read(), dtype=np.uint8) - image = cv2.imdecode(bytes_as_np_array, flags) - return image + return cv2.imdecode(bytes_as_np_array, flags) @requires_module("cv2") @@ -129,8 +128,7 @@ def read_image_to_tensor(image: np.ndarray, is_half: bool = False) -> Tensor: def load_names(category_path): names = [] with open(category_path, "r") as f: - for line in f: - names.append(line.strip()) + names.extend(line.strip() for line in f) return names @@ -140,7 +138,7 @@ def overlay_boxes(detections, path, time_consume, args): img = cv2.imread(path) if args.save_img else None - for i, pred in enumerate(detections): # detections per image + for pred in detections: det_logs = "" save_path = Path(args.output_dir).joinpath(Path(path).name) txt_path = Path(args.output_dir).joinpath(Path(path).stem) @@ -353,7 +351,7 @@ def overlay_bbox(image, bboxes_list, color=None, thickness=2, font_scale=0.3, wi if with_mask: image = cv2.addWeighted(image, 1.0, mask, 0.6, 0) - if len(bbox) == 5 or len(bbox) == 6: + if len(bbox) in {5, 6}: cv2.putText( image, txt, @@ -383,8 +381,7 @@ def merge_images_with_boundary(images_list, row_col_num=(1, -1)): if not isinstance(images_list, list): images_list = [images_list] - images_merged = merge_images(images_list, row_col_num) - return images_merged + return merge_images(images_list, row_col_num) @requires_module("cv2") @@ -408,9 +405,9 @@ def merge_images(images_list, row_col_num): cv2.rectangle(image, (0, 0), (image.shape[1], image.shape[0]), (255, 0, 255)) if row_col_num[1] < 0 or num_images < row: - images_merged = np.hstack(images_list) + return np.hstack(images_list) elif row_col_num[0] < 0 or num_images < col: - images_merged = np.vstack(images_list) + return np.vstack(images_list) else: assert row * col >= num_images, "Images overboundary, not enough windows to display all images!" @@ -423,6 +420,4 @@ def merge_images(images_list, row_col_num): merge_col = np.hstack(images_list[start:end]) merge_imgs_col.append(merge_col) - images_merged = np.vstack(merge_imgs_col) - - return images_merged + return np.vstack(merge_imgs_col) diff --git a/yolort/utils/logger.py b/yolort/utils/logger.py index 866c189c..a33ecf8c 100644 --- a/yolort/utils/logger.py +++ b/yolort/utils/logger.py @@ -99,9 +99,7 @@ def __getattr__(self, attr): raise AttributeError(f"'{type(self).__name__}' object has no attribute '{attr}'") def __str__(self): - loss_str = [] - for name, meter in self.meters.items(): - loss_str.append("{}: {}".format(name, str(meter))) + loss_str = [f"{name}: {str(meter)}" for name, meter in self.meters.items()] return self.delimiter.join(loss_str) def synchronize_between_processes(self): @@ -112,14 +110,13 @@ def add_meter(self, name, meter): self.meters[name] = meter def log_every(self, iterable, print_freq, header=None): - i = 0 if not header: header = "" start_time = time.time() end = time.time() iter_time = SmoothedValue(fmt="{avg:.4f}") data_time = SmoothedValue(fmt="{avg:.4f}") - space_fmt = ":" + str(len(str(len(iterable)))) + "d" + space_fmt = f":{len(str(len(iterable)))}d" if torch.cuda.is_available(): log_msg = self.delimiter.join( [ @@ -144,7 +141,7 @@ def log_every(self, iterable, print_freq, header=None): ] ) MB = 1024.0 * 1024.0 - for obj in iterable: + for i, obj in enumerate(iterable): data_time.update(time.time() - end) yield obj iter_time.update(time.time() - end) @@ -174,7 +171,6 @@ def log_every(self, iterable, print_freq, header=None): data=str(data_time), ) ) - i += 1 end = time.time() total_time = time.time() - start_time total_time_str = str(datetime.timedelta(seconds=int(total_time))) @@ -190,9 +186,7 @@ def is_dist_avail_and_initialized(): def get_rank(): - if not is_dist_avail_and_initialized(): - return 0 - return dist.get_rank() + return dist.get_rank() if is_dist_avail_and_initialized() else 0 def is_main_process(): diff --git a/yolort/utils/visualizer.py b/yolort/utils/visualizer.py index 47a41759..8d82c4bf 100644 --- a/yolort/utils/visualizer.py +++ b/yolort/utils/visualizer.py @@ -221,7 +221,7 @@ def draw_text( if font_size is None: font_size = max(self.line_width - 1, 1) # font thickness w, h = cv2.getTextSize(text, 0, fontScale=self.line_width / 3, thickness=font_size)[0] - outside = pt1[1] - h - 3 >= 0 # text fits outside box + outside = pt1[1] - h >= 3 pt2 = pt1[0] + w, pt1[1] - h - 3 if outside else pt1[1] + h + 3 cv2.rectangle(self.output, pt1, pt2, color, -1, cv2.LINE_AA) # filled cv2.putText( @@ -240,10 +240,7 @@ def _convert_boxes(self, boxes: Union[Tensor, np.ndarray]): """ Convert different format of boxes to an Nx4 array. """ - if isinstance(boxes, Tensor): - return boxes.cpu().detach().numpy() - else: - return boxes + return boxes.cpu().detach().numpy() if isinstance(boxes, Tensor) else boxes def _create_text_labels( self, @@ -274,10 +271,11 @@ def _create_colors(self, labels: Optional[List[int]] = None): """ Generate colors that match the labels. """ - colors = None - if labels is not None: - colors = [self.assigned_colors(label, bgr=self.is_bgr) for label in labels] - return colors + return ( + [self.assigned_colors(label, bgr=self.is_bgr) for label in labels] + if labels is not None + else None + ) def _change_color_brightness( self, diff --git a/yolort/v5/helper.py b/yolort/v5/helper.py index 27ebdcc8..c9619db8 100644 --- a/yolort/v5/helper.py +++ b/yolort/v5/helper.py @@ -30,10 +30,11 @@ def add_yolov5_context(): def get_yolov5_size(depth_multiple, width_multiple): - if depth_multiple == 0.33 and width_multiple == 0.25: - return "n" - if depth_multiple == 0.33 and width_multiple == 0.5: - return "s" + if depth_multiple == 0.33: + if width_multiple == 0.25: + return "n" + if width_multiple == 0.5: + return "s" if depth_multiple == 0.67 and width_multiple == 0.75: return "m" if depth_multiple == 1.0 and width_multiple == 1.0: @@ -72,10 +73,11 @@ def load_yolov5_model(checkpoint_path: str, fuse: bool = False): # Compatibility updates for sub_module in model.modules(): - if isinstance(sub_module, Detect): - if not isinstance(sub_module.anchor_grid, list): # new Detect Layer compatibility - delattr(sub_module, "anchor_grid") - setattr(sub_module, "anchor_grid", [torch.zeros(1)] * sub_module.nl) + if isinstance(sub_module, Detect) and not isinstance( + sub_module.anchor_grid, list + ): + delattr(sub_module, "anchor_grid") + setattr(sub_module, "anchor_grid", [torch.zeros(1)] * sub_module.nl) if isinstance(sub_module, nn.Upsample) and not hasattr(sub_module, "recompute_scale_factor"): sub_module.recompute_scale_factor = None # torch 1.11.0 compatibility diff --git a/yolort/v5/models/common.py b/yolort/v5/models/common.py index 637bb013..36fd8b28 100644 --- a/yolort/v5/models/common.py +++ b/yolort/v5/models/common.py @@ -228,15 +228,20 @@ def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True, version="r4.0"): def forward(self, x: Tensor) -> Tensor: y = focus_transform(x) - out = self.conv(y) - - return out + return self.conv(y) def focus_transform(x: Tensor) -> Tensor: """x(b,c,w,h) -> y(b,4c,w/2,h/2)""" - y = torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1) - return y + return torch.cat( + [ + x[..., ::2, ::2], + x[..., 1::2, ::2], + x[..., ::2, 1::2], + x[..., 1::2, 1::2], + ], + 1, + ) def space_to_depth(x: Tensor) -> Tensor: @@ -244,8 +249,7 @@ def space_to_depth(x: Tensor) -> Tensor: N, C, H, W = x.size() x = x.reshape(N, C, H // 2, 2, W // 2, 2) x = x.permute(0, 5, 3, 1, 2, 4) - y = x.reshape(N, C * 4, H // 2, W // 2) - return y + return x.reshape(N, C * 4, H // 2, W // 2) class Concat(nn.Module): @@ -257,10 +261,7 @@ def __init__(self, dimension: int = 1): # torchscript does not yet support *args, so we overload method # allowing it to take either a List[Tensor] or single Tensor def forward(self, x: List[Tensor]) -> Tensor: - if isinstance(x, Tensor): - prev_features = [x] - else: - prev_features = x + prev_features = [x] if isinstance(x, Tensor) else x return torch.cat(prev_features, self.d) diff --git a/yolort/v5/models/experimental.py b/yolort/v5/models/experimental.py index dd5c5b2a..59ce8e28 100644 --- a/yolort/v5/models/experimental.py +++ b/yolort/v5/models/experimental.py @@ -77,9 +77,6 @@ def __init__(self): super().__init__() def forward(self, x, augment=False, profile=False, visualize=False): - y = [] - for module in self: - y.append(module(x, augment, profile, visualize)[0]) - + y = [module(x, augment, profile, visualize)[0] for module in self] y = torch.cat(y, 1) # nms ensemble return y, None # inference, train output diff --git a/yolort/v5/models/yolo.py b/yolort/v5/models/yolo.py index 38ae7a32..ecc6e66d 100644 --- a/yolort/v5/models/yolo.py +++ b/yolort/v5/models/yolo.py @@ -216,7 +216,7 @@ def _profile_one_layer(self, m, x, dt): m(x.copy() if c else x) dt.append((time_sync() - t) * 100) if m == self.model[0]: - LOGGER.info(f"{'time (ms)':>10s} {'GFLOPs':>10s} {'params':>10s} {'module'}") + LOGGER.info(f"{'time (ms)':>10s} {'GFLOPs':>10s} {'params':>10s} module") LOGGER.info(f"{dt[-1]:10.2f} {o:10.2f} {m.np:10.0f} {m.type}") if c: LOGGER.info(f"{sum(dt):10.2f} {'-':>10s} {'-':>10s} Total") diff --git a/yolort/v5/utils/augmentations.py b/yolort/v5/utils/augmentations.py index 887d09aa..446053cb 100644 --- a/yolort/v5/utils/augmentations.py +++ b/yolort/v5/utils/augmentations.py @@ -192,15 +192,7 @@ def random_perspective( else: # affine im = cv2.warpAffine(im, M[:2], dsize=(width, height), borderValue=(114, 114, 114)) - # Visualize - # import matplotlib.pyplot as plt - # ax = plt.subplots(1, 2, figsize=(12, 6))[1].ravel() - # ax[0].imshow(im[:, :, ::-1]) # base - # ax[1].imshow(im2[:, :, ::-1]) # warped - - # Transform label coordinates - n = len(targets) - if n: + if n := len(targets): use_segments = any(x.any() for x in segments) new = np.zeros((n, 4)) if use_segments: # warp segments diff --git a/yolort/v5/utils/autoanchor.py b/yolort/v5/utils/autoanchor.py index 8f4408de..697ef76a 100644 --- a/yolort/v5/utils/autoanchor.py +++ b/yolort/v5/utils/autoanchor.py @@ -115,7 +115,7 @@ def print_results(k, verbose=True): f"metric_all={x.mean():.3f}/{best.mean():.3f}-mean/best, " f"past_thr={x[x > thr].mean():.3f}-mean: " ) - for i, x in enumerate(k): + for x in k: s += "%i,%i, " % (round(x[0]), round(x[1])) if verbose: LOGGER.info(s[:-2]) diff --git a/yolort/v5/utils/callbacks.py b/yolort/v5/utils/callbacks.py index 5a59f667..8333fafb 100644 --- a/yolort/v5/utils/callbacks.py +++ b/yolort/v5/utils/callbacks.py @@ -51,10 +51,7 @@ def get_registered_actions(self, hook=None): Args: hook The name of the hook to check, defaults to all """ - if hook: - return self._callbacks[hook] - else: - return self._callbacks + return self._callbacks[hook] if hook else self._callbacks def run(self, hook, *args, **kwargs): """ diff --git a/yolort/v5/utils/datasets.py b/yolort/v5/utils/datasets.py index 5666a5b3..2b379fce 100644 --- a/yolort/v5/utils/datasets.py +++ b/yolort/v5/utils/datasets.py @@ -105,12 +105,11 @@ def __next__(self): while not ret_val: self.count += 1 self.cap.release() - if self.count == self.num_files: # last video + if self.count == self.num_files: raise StopIteration - else: - path = self.files[self.count] - self.new_video(path) - ret_val, img_origin = self.cap.read() + path = self.files[self.count] + self.new_video(path) + ret_val, img_origin = self.cap.read() self.frame += 1 source_bar = f"video {self.count + 1}/{self.num_files} ({self.frame}/{self.frames}) {path}: " diff --git a/yolort/v5/utils/general.py b/yolort/v5/utils/general.py index 3b7f2900..2dbd8850 100644 --- a/yolort/v5/utils/general.py +++ b/yolort/v5/utils/general.py @@ -47,8 +47,10 @@ def set_logging(name=None, verbose=True): # Sets level and returns logger rank = int(os.getenv("RANK", -1)) # rank in world for Multi-GPU trainings logging.basicConfig( - format="%(message)s", level=logging.INFO if (verbose and rank in (-1, 0)) else logging.WARNING + format="%(message)s", + level=logging.INFO if verbose and rank in {-1, 0} else logging.WARNING, ) + return logging.getLogger(name) @@ -130,7 +132,9 @@ def intersect_dicts(dict1, dict2, exclude=()): return { k: v for k, v in dict1.items() - if k in dict2 and not any(x in k for x in exclude) and v.shape == dict2[k].shape + if k in dict2 + and all(x not in k for x in exclude) + and v.shape == dict2[k].shape } @@ -145,8 +149,7 @@ def user_config_dir(dir="Ultralytics", env_var="YOLOV5_CONFIG_DIR"): Return path of user configuration directory. Prefer environment variable if exists. Make dir if required. """ - env = os.getenv(env_var) - if env: + if env := os.getenv(env_var): path = Path(env) # use environment variable else: cfg = { @@ -167,17 +170,16 @@ def is_writeable(dir, test=False): Return True if directory has write permissions, test opening a file with write permissions if test=True """ - if test: # method 1 - file = Path(dir) / "tmp.txt" - try: - with open(file, "w"): # open file with write permissions - pass - file.unlink() # remove file - return True - except OSError: - return False - else: # method 2 + if not test: return os.access(dir, os.R_OK) # possible issues on Windows + file = Path(dir) / "tmp.txt" + try: + with open(file, "w"): # open file with write permissions + pass + file.unlink() # remove file + return True + except OSError: + return False def is_pip(): @@ -280,7 +282,7 @@ def check_file(file, suffix=""): check_suffix(file, suffix) # optional file = str(file) # convert to str() - if Path(file).is_file() or file == "": + if Path(file).is_file() or not file: # return the file if the file exists return file @@ -310,9 +312,7 @@ def check_file(file, suffix=""): def url2file(url): # Convert URL to filename, i.e. https://url.com/file.txt?auth -> file.txt url = str(Path(url)).replace(":/", "://") # Pathlib turns :// -> :/ - # '%2F' to '/', split https://url.com/file.txt?auth - file = Path(urllib.parse.unquote(url)).name.split("?")[0] - return file + return Path(urllib.parse.unquote(url)).name.split("?")[0] def make_divisible(x, divisor): @@ -383,9 +383,8 @@ def labels_to_class_weights(labels, nc=80): def labels_to_image_weights(labels, nc=80, class_weights=np.ones(80)): # Produces image weights based on class_weights and image contents class_counts = np.array([np.bincount(x[:, 0].astype(np.int), minlength=nc) for x in labels]) - image_weights = (class_weights.reshape(1, nc) * class_counts).sum(1) # index = random.choices(range(n), weights=image_weights, k=1) # weight image sample - return image_weights + return (class_weights.reshape(1, nc) * class_counts).sum(1) def xyxy2xywh(x): @@ -707,7 +706,7 @@ def apply_classifier(x, model, img, im0): # Classes pred_cls1 = d[:, 5].long() ims = [] - for j, a in enumerate(d): # per item + for a in d: cutout = im0[i][int(a[1]) : int(a[3]), int(a[0]) : int(a[2])] im = cv2.resize(cutout, (224, 224)) # BGR # cv2.imwrite('example%i.jpg' % j, cutout) diff --git a/yolort/v5/utils/loss.py b/yolort/v5/utils/loss.py index 3dacd839..e17221a8 100644 --- a/yolort/v5/utils/loss.py +++ b/yolort/v5/utils/loss.py @@ -103,11 +103,7 @@ def __init__(self, model, autobalance=False, hyperparameters=None): self.sort_obj_iou = False device = next(model.parameters()).device # get model device # hyperparameters - if hyperparameters is not None: - h = hyperparameters - else: - h = model.hyp - + h = hyperparameters if hyperparameters is not None else model.hyp # Define criteria BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h["cls_pw"]], device=device)) BCEobj = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h["obj_pw"]], device=device)) @@ -153,9 +149,7 @@ def __call__(self, p, targets): # target obj tobj = torch.zeros_like(pi[..., 0], device=device) - # number of targets - n = b.shape[0] - if n: + if n := b.shape[0]: # prediction subset corresponding to targets ps = pi[b, a, gj, gi] diff --git a/yolort/v5/utils/metrics.py b/yolort/v5/utils/metrics.py index cfb78827..6dd4d69c 100644 --- a/yolort/v5/utils/metrics.py +++ b/yolort/v5/utils/metrics.py @@ -51,27 +51,26 @@ def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, save_dir=".", names if n_p == 0 or n_l == 0: continue - else: - # Accumulate FPs and TPs - fpc = (1 - tp[i]).cumsum(0) - tpc = tp[i].cumsum(0) - - # Recall - recall = tpc / (n_l + 1e-16) # recall curve - # negative x, xp because xp decreases - r[ci] = np.interp(-px, -conf[i], recall[:, 0], left=0) - - # Precision - # precision curve - precision = tpc / (tpc + fpc) - # p at pr_score - p[ci] = np.interp(-px, -conf[i], precision[:, 0], left=1) - - # AP from recall-precision curve - for j in range(tp.shape[1]): - ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j]) - if plot and j == 0: - py.append(np.interp(px, mrec, mpre)) # precision at mAP@0.5 + # Accumulate FPs and TPs + fpc = (1 - tp[i]).cumsum(0) + tpc = tp[i].cumsum(0) + + # Recall + recall = tpc / (n_l + 1e-16) # recall curve + # negative x, xp because xp decreases + r[ci] = np.interp(-px, -conf[i], recall[:, 0], left=0) + + # Precision + # precision curve + precision = tpc / (tpc + fpc) + # p at pr_score + p[ci] = np.interp(-px, -conf[i], precision[:, 0], left=1) + + # AP from recall-precision curve + for j in range(tp.shape[1]): + ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j]) + if plot and j == 0: + py.append(np.interp(px, mrec, mpre)) # precision at mAP@0.5 # Compute F1 (harmonic mean of precision and recall) f1 = 2 * p * r / (p + r + 1e-16) @@ -238,38 +237,35 @@ def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, eps= union = w1 * h1 + w2 * h2 - inter + eps iou = inter / union - if GIoU or DIoU or CIoU: - # convex (smallest enclosing box) width - cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1) - # convex height - ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1) - # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1 - if CIoU or DIoU: - # convex diagonal squared - c2 = cw**2 + ch**2 + eps - rho2 = ( - (b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 - + - # center distance squared - (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2 - ) / 4 - if DIoU: - # DIoU - return iou - rho2 / c2 - # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47 - elif CIoU: - v = (4 / math.pi**2) * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2) - with torch.no_grad(): - alpha = v / (v - iou + (1 + eps)) - return iou - (rho2 / c2 + v * alpha) # CIoU - else: # GIoU https://arxiv.org/pdf/1902.09630.pdf - # convex area - c_area = cw * ch + eps - # GIoU - return iou - (c_area - union) / c_area - else: + if not GIoU and not DIoU and not CIoU: # IoU return iou + # convex (smallest enclosing box) width + cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1) + # convex height + ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1) + # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1 + if CIoU or DIoU: + # convex diagonal squared + c2 = cw**2 + ch**2 + eps + rho2 = ( + (b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + + + # center distance squared + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2 + ) / 4 + if DIoU: + # DIoU + return iou - rho2 / c2 + v = (4 / math.pi**2) * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2) + with torch.no_grad(): + alpha = v / (v - iou + (1 + eps)) + return iou - (rho2 / c2 + v * alpha) # CIoU + else: # GIoU https://arxiv.org/pdf/1902.09630.pdf + # convex area + c_area = cw * ch + eps + # GIoU + return iou - (c_area - union) / c_area def box_iou(box1, box2): diff --git a/yolort/v5/utils/plots.py b/yolort/v5/utils/plots.py index ba85ab54..5f72ffcd 100644 --- a/yolort/v5/utils/plots.py +++ b/yolort/v5/utils/plots.py @@ -143,7 +143,7 @@ def box_label(self, box, label="", color=(128, 128, 128), txt_color=(255, 255, 2 if label: tf = max(self.lw - 1, 1) # font thickness w, h = cv2.getTextSize(label, 0, fontScale=self.lw / 3, thickness=tf)[0] - outside = p1[1] - h - 3 >= 0 # label fits outside box + outside = p1[1] - h >= 3 p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3 cv2.rectangle(self.im, p1, p2, color, -1, cv2.LINE_AA) # filled cv2.putText( @@ -226,8 +226,11 @@ def output_to_target(output): # Convert model output to target format [batch_id, class_id, x, y, w, h, conf] targets = [] for i, o in enumerate(output): - for *box, conf, cls in o.cpu().numpy(): - targets.append([i, cls, *list(*xyxy2xywh(np.array(box)[None])), conf]) + targets.extend( + [i, cls, *list(*xyxy2xywh(np.array(box)[None])), conf] + for *box, conf, cls in o.cpu().numpy() + ) + return np.array(targets) @@ -483,7 +486,7 @@ def plot_results(file="path/to/results.csv", dir=""): ax = ax.ravel() files = list(save_dir.glob("results*.csv")) assert len(files), f"No results.csv files found in {save_dir.resolve()}, nothing to plot." - for fi, f in enumerate(files): + for f in files: try: data = pd.read_csv(f) s = [x.strip() for x in data.columns] diff --git a/yolort/v5/utils/torch_utils.py b/yolort/v5/utils/torch_utils.py index 282d9c50..331c0261 100644 --- a/yolort/v5/utils/torch_utils.py +++ b/yolort/v5/utils/torch_utils.py @@ -155,7 +155,7 @@ def profile(input, ops, n=10, device=None): s_in = tuple(x.shape) if isinstance(x, torch.Tensor) else "list" s_out = tuple(y.shape) if isinstance(y, torch.Tensor) else "list" # parameters - p = sum(list(x.numel() for x in m.parameters())) if isinstance(m, nn.Module) else 0 + p = sum(x.numel() for x in m.parameters()) if isinstance(m, nn.Module) else 0 print(f"{p:12}{flops:12.4g}{mem:>14.3f}{tf:14.4g}{tb:14.4g}{str(s_in):>24s}{str(s_out):>24s}") results.append([p, flops, mem, tf, tb, s_in, s_out]) except Exception as e: @@ -263,7 +263,7 @@ def model_info(model, verbose=False, img_size=640): % (i, name, p.requires_grad, p.numel(), list(p.shape), p.mean(), p.std()) ) - try: # FLOPs + try: from thop import profile stride = max(int(model.stride.max()), 32) if hasattr(model, "stride") else 32 @@ -277,7 +277,7 @@ def model_info(model, verbose=False, img_size=640): img_size = img_size if isinstance(img_size, list) else [img_size, img_size] # 640x640 GFLOPs fs = ", %.1f GFLOPs" % (flops * img_size[0] / stride * img_size[1] / stride) - except (ImportError, Exception): + except Exception: fs = "" LOGGER.info( @@ -291,13 +291,12 @@ def scale_img(img, ratio=1.0, same_shape=False, gs=32): """ if ratio == 1.0: return img - else: - h, w = img.shape[2:] - s = (int(h * ratio), int(w * ratio)) # new size - img = F.interpolate(img, size=s, mode="bilinear", align_corners=False) # resize - if not same_shape: # pad/crop img - h, w = (math.ceil(x * ratio / gs) * gs for x in (h, w)) - return F.pad(img, [0, w - s[1], 0, h - s[0]], value=0.447) # value = imagenet mean + h, w = img.shape[2:] + s = (int(h * ratio), int(w * ratio)) # new size + img = F.interpolate(img, size=s, mode="bilinear", align_corners=False) # resize + if not same_shape: # pad/crop img + h, w = (math.ceil(x * ratio / gs) * gs for x in (h, w)) + return F.pad(img, [0, w - s[1], 0, h - s[0]], value=0.447) # value = imagenet mean def copy_attr(a, b, include=(), exclude=()):