def augment_img(aug, suffix, img_path, count=1):
    """Iteratively augment a single image with a given augmentation function.
    Generates transformed labels for each augmentation

    Parameters:
        aug      Augmentation function from albumentations
        suffix   String suffix appended to each augmentation file and label
        img_path Filesystem path to the image to augment
        count    Number of augmentations to perform
    """
    img = cv2.imread(img_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

    base_name = img_path[:-4]
    label_path = get_label_path(img_path)

    boxes, field_ids = parse_label(label_path)

    i = 0
    while i < count:
        aug_path = f"{base_name.replace('images', 'aug-images')}_{suffix}-{i}.png"
        new_txt_path = get_label_path(aug_path)
        os.makedirs(os.path.dirname(new_txt_path), exist_ok=True)
        os.makedirs(os.path.dirname(aug_path), exist_ok=True)

        if os.path.exists(aug_path) and os.path.exists(new_txt_path):
            i += 1
            continue

        try:
            result = aug(image=img, bboxes=boxes, classes=field_ids)
        except IndexError:
            continue
        aug_img = result["image"]

        new_bboxes = [" ".join(map(str, bbox)) for bbox in result["bboxes"]]

        # Check if bounding boxes have been removed due to visibility criteria
        if len(new_bboxes) != len(boxes):
            continue

        img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
        cv2.imwrite(aug_path, aug_img)

        with open(new_txt_path, "w+") as out:
            for box_i, bbox_str in enumerate(new_bboxes):
                out.write(f"{field_ids[box_i]} {bbox_str}\n")
        i += 1
    def augment(self, imgs_per_class, major_aug, min_visibility=0.75):
        """Augment all images in the image folder, adding the augmentations to the folder.

        Parameters:
            imgs_per_class   Target number of images. If there are more samples in the folder
                             than this number for a class, no augmentation will be performed.
            major_aug        A boolean variable determining if 'major' transformations will be used.
            min_visibility   Minimum visibility of the resultant bounding boxes after augmentation.
                             This is a value in (0.0, 1.0] relative to the area of the bounding box.
        """
        incr_factors = self.get_incr_factors(imgs_per_class)

        bbox_params = alb.BboxParams("yolo",
                                     min_visibility=min_visibility,
                                     label_fields=["classes"])
        aug = multi_aug(get_augmentations(), major_aug, bbox_params)

        pbar = tqdm(desc="Augmenting training images",
                    total=sum(incr_factors.values()))
        for img, count in incr_factors.items():
            augment_img(aug, "compose", img, count=count)
            new_imgs = {
                f"{img[:-4].replace('images', 'aug-images')}_compose-{i}.png"
                for i in range(count)
            }
            self.img_folder.imgs.update(new_imgs)
            self.img_folder.labels.update(
                {get_label_path(img)
                 for img in new_imgs})
            pbar.update(count)

        pbar.close()
 def make_img_dict(self):
     """Get a dictionary of image paths and their corresponding classes."""
     img_dict = dict()
     for img in self.imgs:
         classes = self.get_classes(get_label_path(img))
         if len(classes) != 0:
             img_dict[img] = classes
     return img_dict
    def filter_images(self):
        """Remove non-labeled images from the folder."""
        labeled_imgs = set()

        for img in self.imgs:
            label_path = get_label_path(img)
            if os.path.exists(label_path):
                labeled_imgs.add(img)
        self.imgs = labeled_imgs
    def __init__(
        self,
        img_list,
        img_size=416,
        multiscale=True,
        normalized_labels=True,
    ):

        self.img_files = img_list

        self.label_files = [get_label_path(path) for path in self.img_files]

        self.img_size = img_size
        self.multiscale = multiscale
        self.normalized_labels = normalized_labels
        self.min_size = self.img_size - 3 * 32
        self.max_size = self.img_size + 3 * 32
        self.batch_count = 0
    def label(self, classes, ground_truth_func):
        """Label images in the folder in accordance to a provided ground truth function.

        Ground truth function should return a list of labels, each in a tuple format with
        class name and normalized bounding box dimensions in Darknet format.

        The input class list should be ordered and consistent with the ground truth labels.
        """
        for img in self.imgs:
            labels = ground_truth_func(img, classes)
            if len(labels) == 0:
                return
            text_label = open(get_label_path(img), "w+")
            for i, (class_display_name, x_cent, y_cent, w,
                    h) in enumerate(labels):
                class_num = classes.index(class_display_name)
                if i > 0:
                    text_label.write("\n")
                text_label.write(f"{class_num} {x_cent} {y_cent} {w} {h}")
            text_label.close()
 def get_labels(self):
     """Get a set of labels corresponding to images in the folder."""
     return {get_label_path(img) for img in self.imgs}
def make_results_df(config, img_folder, detections_by_img, total_epochs):
    metrics = [
        "file",
        "actual",
        "detected",
        "conf",
        "conf_std",
        "hit",
    ]

    results = pd.DataFrame(columns=metrics)
    classes = utils.load_classes(config["class_list"])

    for path, detections in detections_by_img.items():
        ground_truths = img_folder.get_classes(utils.get_label_path(path))
        detection_pairs = list()
        if detections is not None:
            region_detections, regions_std = yoloutils.group_average_bb(
                detections, total_epochs, config["iou_thres"])

            # evaluate.save_image(region_detections, path, config, classes)
            if len(region_detections) == 1:
                detected_class = int(region_detections.numpy()[0][-1])
                if detected_class in ground_truths:
                    label = detected_class
                elif len(ground_truths) == 1:
                    label = ground_truths[0]
                else:
                    label = None
                detection_pairs = [(label, region_detections[0])]
            else:
                test_img = LabeledSet([path], len(classes))
                detection_pairs = evaluate.match_detections(
                    test_img, region_detections.unsqueeze(0), config)

        for (truth, box) in detection_pairs:
            if box is None:
                continue
            obj_conf, class_conf, pred_class = box.numpy()[4:]
            obj_std, class_std = regions_std[round(float(class_conf), 3)]

            row = {
                "file": path,
                "detected": classes[int(pred_class)],
                "actual": classes[int(truth)] if truth is not None else "",
                "conf": obj_conf * class_conf,
                "conf_std": math.sqrt(obj_std**2 + class_std**2),
            }
            row["hit"] = row["actual"] == row["detected"]

            results = results.append(row, ignore_index=True)

            if truth is not None:
                ground_truths.remove(int(truth))

        # Add rows for those missing detections
        for truth in ground_truths:
            row = {
                "file": path,
                "detected": "",
                "actual": classes[int(truth)],
                "conf": 0.0,
                "hit": False,
                "conf_std": 0.0,
            }

            results = results.append(row, ignore_index=True)
    return results
def simple_benchmark_avg(img_folder,
                         prefix,
                         start,
                         end,
                         total_epochs,
                         config,
                         roll=False):
    """Deprecated version of benchmark averaging, meant for single object
    detection within an image. Used for a fair comparison baseline on old models
    """

    loader = DataLoader(
        img_folder,
        batch_size=1,
        shuffle=False,
        num_workers=config["n_cpu"],
    )

    results = pd.DataFrame(
        columns=["file", "confs", "actual", "detected", "conf", "hit"])
    results.set_index("file")

    classes = utils.load_classes(config["class_list"])

    if roll:
        checkpoints_i = list(range(max(1, end - total_epochs + 1), end + 1))
    else:
        checkpoints_i = list(
            sorted(
                set(
                    np.linspace(start,
                                end,
                                total_epochs,
                                dtype=np.dtype(np.int16)))))

    single = total_epochs == 1

    if not single:
        print("Benchmarking on epochs", checkpoints_i)

    for n in tqdm(checkpoints_i, "Benchmarking epochs", disable=single):
        ckpt = get_checkpoint(config["checkpoints"], prefix, n)

        model_def = yoloutils.parse_model_config(config["model_config"])
        model = models.get_eval_model(model_def, config["img_size"], ckpt)

        for (img_paths, input_imgs) in loader:
            path = img_paths[0]
            if path not in results.file:
                actual_class = classes[img_folder.get_classes(
                    utils.get_label_path(path))[0]]
                results.loc[path] = [
                    path, dict(), actual_class, None, None, None
                ]

            detections = evaluate.detect(input_imgs, config["conf_thres"],
                                         model)

            confs = results.loc[path]["confs"]

            for detection in detections:
                if detection is None:
                    continue
                (_, _, _, _, _, cls_conf, cls_pred) = detection.numpy()[0]

                if cls_pred not in confs.keys():
                    confs[cls_pred] = [cls_conf]

                else:
                    confs[cls_pred].append(cls_conf)

    for _, row in results.iterrows():
        best_class = None
        best_conf = float("-inf")

        for class_name, confs in row["confs"].items():
            avg_conf = sum(confs) / len(checkpoints_i)

            if avg_conf > best_conf:
                best_conf = avg_conf
                best_class = class_name

        if best_class is not None:
            row["detected"] = classes[int(best_class)]
            row["conf"] = best_conf
            row["hit"] = row["actual"] == row["detected"]
        else:
            row["detected"] = ""
            row["conf"] = 0.0
            row["hit"] = False

    return results