def random_morphological_transform(label):
    """ Randomly erode/dilate the label

        @param label: a [H x W] numpy array of {0, 1}
    """

    num_tries = 0
    valid_transform = False
    while not valid_transform:

        if num_tries >= cfg.TRAIN.max_augmentation_tries:
            print('Morph: Exhausted number of augmentation tries...')
            return label

        # Sample whether we do erosion or dilation, and kernel size for that
        x_min, y_min, x_max, y_max = util_.mask_to_tight_box(label)
        sidelength = np.mean([x_max - x_min, y_max - y_min])

        morphology_kernel_size = 0
        num_ksize_tries = 0
        while morphology_kernel_size == 0:
            if num_ksize_tries >= 50:  # 50 tries for this
                print(
                    'Morph: Exhausted number of augmentation tries... Sidelength: {sidelength}'
                )
                return label

            dilation_percentage = np.random.beta(
                cfg.TRAIN.label_dilation_alpha, cfg.TRAIN.label_dilation_beta)
            morphology_kernel_size = int(
                round(sidelength * dilation_percentage))

            num_ksize_tries += 1

        iterations = np.random.randint(1, cfg.TRAIN.morphology_max_iters + 1)

        # Erode/dilate the mask
        kernel = cv2.getStructuringElement(
            cv2.MORPH_ELLIPSE,
            (morphology_kernel_size, morphology_kernel_size))
        if np.random.rand() < 0.5:
            morphed_label = cv2.erode(label, kernel, iterations=iterations)
        else:
            morphed_label = cv2.dilate(label, kernel, iterations=iterations)

        # Make sure there the mass is reasonable
        if (np.count_nonzero(morphed_label) / morphed_label.size > 0.001) and \
           (np.count_nonzero(morphed_label) / morphed_label.size < 0.98):
            valid_transform = True

        num_tries += 1

    return morphed_label
def crop_rois(rgb, initial_masks, depth):

    N, H, W = initial_masks.shape
    crop_size = cfg.TRAIN.SYN_CROP_SIZE
    padding_percentage = 0.25

    mask_ids = torch.unique(initial_masks[0])
    if mask_ids[0] == 0:
        mask_ids = mask_ids[1:]
    num = mask_ids.shape[0]
    rgb_crops = torch.zeros((num, 3, crop_size, crop_size), device=cfg.device)
    rois = torch.zeros((num, 4), device=cfg.device)
    mask_crops = torch.zeros((num, crop_size, crop_size), device=cfg.device)
    if depth is not None:
        depth_crops = torch.zeros((num, 3, crop_size, crop_size), device=cfg.device)
    else:
        depth_crops = None

    for index, mask_id in enumerate(mask_ids):
        mask = (initial_masks[0] == mask_id).float() # Shape: [H x W]
        x_min, y_min, x_max, y_max = util_.mask_to_tight_box(mask)
        x_padding = int(torch.round((x_max - x_min).float() * padding_percentage).item())
        y_padding = int(torch.round((y_max - y_min).float() * padding_percentage).item())

        # pad and be careful of boundaries
        x_min = max(x_min - x_padding, 0)
        x_max = min(x_max + x_padding, W-1)
        y_min = max(y_min - y_padding, 0)
        y_max = min(y_max + y_padding, H-1)
        rois[index, 0] = x_min
        rois[index, 1] = y_min
        rois[index, 2] = x_max
        rois[index, 3] = y_max

        # crop
        rgb_crop = rgb[0, :, y_min:y_max+1, x_min:x_max+1] # [3 x crop_H x crop_W]
        mask_crop = mask[y_min:y_max+1, x_min:x_max+1] # [crop_H x crop_W]
        if depth is not None:
            depth_crop = depth[0, :, y_min:y_max+1, x_min:x_max+1] # [3 x crop_H x crop_W]

        # resize
        new_size = (crop_size, crop_size)
        rgb_crop = F.upsample_bilinear(rgb_crop.unsqueeze(0), new_size)[0] # Shape: [3 x new_H x new_W]
        rgb_crops[index] = rgb_crop
        mask_crop = F.upsample_nearest(mask_crop.unsqueeze(0).unsqueeze(0), new_size)[0,0] # Shape: [new_H, new_W]
        mask_crops[index] = mask_crop
        if depth is not None:
            depth_crop = F.upsample_bilinear(depth_crop.unsqueeze(0), new_size)[0] # Shape: [3 x new_H x new_W]
            depth_crops[index] = depth_crop

    return rgb_crops, mask_crops, rois, depth_crops
def random_cut(label):
    """ Randomly cut part of mask

        @param label: a [H x W] numpy array of {0, 1}
    """

    H, W = label.shape

    num_tries = 0
    valid_transform = False
    while not valid_transform:

        if num_tries >= cfg.TRAIN.max_augmentation_tries:
            print('Cut: Exhausted number of augmentation tries...')
            return label

        cut_label = label.copy()

        # Sample cut percentage
        cut_percentage = np.random.uniform(cfg.TRAIN.cut_percentage_min,
                                           cfg.TRAIN.cut_percentage_max)
        x_min, y_min, x_max, y_max = util_.mask_to_tight_box(label)
        if np.random.rand() < 0.5:  # choose width

            sidelength = x_max - x_min
            if np.random.rand() < 0.5:  # from the left
                x = int(round(cut_percentage * sidelength)) + x_min
                cut_label[y_min:y_max + 1, x_min:x] = 0
            else:  # from the right
                x = x_max - int(round(cut_percentage * sidelength))
                cut_label[y_min:y_max + 1, x:x_max + 1] = 0

        else:  # choose height

            sidelength = y_max - y_min
            if np.random.rand() < 0.5:  # from the top
                y = int(round(cut_percentage * sidelength)) + y_min
                cut_label[y_min:y, x_min:x_max + 1] = 0
            else:  # from the bottom
                y = y_max - int(round(cut_percentage * sidelength))
                cut_label[y:y_max + 1, x_min:x_max + 1] = 0

        # Make sure the mass is reasonable
        if (np.count_nonzero(cut_label) / cut_label.size > 0.001) and \
           (np.count_nonzero(cut_label) / cut_label.size < 0.98):
            valid_transform = True

        num_tries += 1

    return cut_label
def random_translation(label):
    """ Randomly translate mask

        @param label: a [H x W] numpy array of {0, 1}
    """

    num_tries = 0
    valid_transform = False
    while not valid_transform:

        if num_tries >= cfg.TRAIN.max_augmentation_tries:
            print('Translate: Exhausted number of augmentation tries...')
            return label

        # Get tight bbox of mask
        x_min, y_min, x_max, y_max = util_.mask_to_tight_box(label)
        sidelength = max(x_max - x_min, y_max - y_min)

        # sample translation pixels
        translation_percentage = np.random.beta(cfg.TRAIN.translation_alpha,
                                                cfg.TRAIN.translation_beta)
        translation_percentage = max(translation_percentage,
                                     cfg.TRAIN.translation_percentage_min)
        translation_max = int(round(translation_percentage * sidelength))
        translation_max = max(translation_max,
                              1)  # To make sure things don't error out

        tx = np.random.randint(-translation_max, translation_max)
        ty = np.random.randint(-translation_max, translation_max)

        translated_label = translate(label,
                                     tx,
                                     ty,
                                     interpolation=cv2.INTER_NEAREST)

        # Make sure the mass is reasonable
        if (np.count_nonzero(translated_label) / translated_label.size > 0.001) and \
           (np.count_nonzero(translated_label) / translated_label.size < 0.98):
            valid_transform = True

        num_tries += 1

    return translated_label
    def pad_crop_resize(self, img, label, depth):
        """ Crop the image around the label mask, then resize to 224x224
        """

        H, W, _ = img.shape

        # sample an object to crop
        K = np.max(label)
        while True:
            if K > 0:
                idx = np.random.randint(1, K + 1)
            else:
                idx = 0
            foreground = (label == idx).astype(np.float32)

            # get tight box around label/morphed label
            x_min, y_min, x_max, y_max = util_.mask_to_tight_box(foreground)
            cx = (x_min + x_max) / 2
            cy = (y_min + y_max) / 2

            # make bbox square
            x_delta = x_max - x_min
            y_delta = y_max - y_min
            if x_delta > y_delta:
                y_min = cy - x_delta / 2
                y_max = cy + x_delta / 2
            else:
                x_min = cx - y_delta / 2
                x_max = cx + y_delta / 2

            sidelength = x_max - x_min
            padding_percentage = np.random.uniform(
                cfg.TRAIN.min_padding_percentage,
                cfg.TRAIN.max_padding_percentage)
            padding = int(round(sidelength * padding_percentage))
            if padding == 0:
                padding = 25

            # Pad and be careful of boundaries
            x_min = max(int(x_min - padding), 0)
            x_max = min(int(x_max + padding), W - 1)
            y_min = max(int(y_min - padding), 0)
            y_max = min(int(y_max + padding), H - 1)

            # crop
            if (y_min == y_max) or (x_min == x_max):
                continue

            img_crop = img[y_min:y_max + 1, x_min:x_max + 1]
            label_crop = label[y_min:y_max + 1, x_min:x_max + 1]
            roi = [x_min, y_min, x_max, y_max]
            if depth is not None:
                depth_crop = depth[y_min:y_max + 1, x_min:x_max + 1]
            break

        # resize
        s = cfg.TRAIN.SYN_CROP_SIZE
        img_crop = cv2.resize(img_crop, (s, s))
        label_crop = cv2.resize(label_crop, (s, s),
                                interpolation=cv2.INTER_NEAREST)
        if depth is not None:
            depth_crop = cv2.resize(depth_crop, (s, s),
                                    interpolation=cv2.INTER_NEAREST)
        else:
            depth_crop = None

        return img_crop, label_crop, depth_crop
def random_add(label):
    """ Randomly add part of mask 

        @param label: a [H x W] numpy array of {0, 1}
    """
    H, W = label.shape

    num_tries = 0
    valid_transform = False
    while not valid_transform:
        if num_tries >= cfg.TRAIN.max_augmentation_tries:
            print('Add: Exhausted number of augmentation tries...')
            return label

        added_label = label.copy()

        # Sample add percentage
        add_percentage = np.random.uniform(cfg.TRAIN.add_percentage_min,
                                           cfg.TRAIN.add_percentage_max)
        x_min, y_min, x_max, y_max = util_.mask_to_tight_box(label)

        # Sample translation from center
        translation_percentage_x = np.random.uniform(0, 2 * add_percentage)
        tx = int(round((x_max - x_min) * translation_percentage_x))
        translation_percentage_y = np.random.uniform(0, 2 * add_percentage)
        ty = int(round((y_max - y_min) * translation_percentage_y))

        if np.random.rand() < 0.5:  # choose x direction

            sidelength = x_max - x_min
            ty = np.random.choice(
                [-1, 1]
            ) * ty  # mask will be moved to the left/right. up/down doesn't matter

            if np.random.rand() < 0.5:  # mask copied from the left.
                x = int(round(add_percentage * sidelength)) + x_min
                try:
                    temp = added_label[y_min + ty:y_max + 1 + ty,
                                       x_min - tx:x - tx]
                    added_label[y_min + ty:y_max + 1 + ty,
                                x_min - tx:x - tx] = np.logical_or(
                                    temp, added_label[y_min:y_max + 1,
                                                      x_min:x])
                except ValueError as e:  # indices were out of bounds
                    num_tries += 1
                    continue
            else:  # mask copied from the right
                x = x_max - int(round(add_percentage * sidelength))
                try:
                    temp = added_label[y_min + ty:y_max + 1 + ty,
                                       x + tx:x_max + 1 + tx]
                    added_label[y_min + ty:y_max + 1 + ty,
                                x + tx:x_max + 1 + tx] = np.logical_or(
                                    temp, added_label[y_min:y_max + 1,
                                                      x:x_max + 1])
                except ValueError as e:  # indices were out of bounds
                    num_tries += 1
                    continue

        else:  # choose y direction

            sidelength = y_max - y_min
            tx = np.random.choice([
                -1, 1
            ]) * tx  # mask will be moved up/down. lef/right doesn't matter

            if np.random.rand() < 0.5:  # from the top
                y = int(round(add_percentage * sidelength)) + y_min
                try:
                    temp = added_label[y_min - ty:y - ty,
                                       x_min + tx:x_max + 1 + tx]
                    added_label[y_min - ty:y - ty,
                                x_min + tx:x_max + 1 + tx] = np.logical_or(
                                    temp, added_label[y_min:y,
                                                      x_min:x_max + 1])
                except ValueError as e:  # indices were out of bounds
                    num_tries += 1
                    continue
            else:  # from the bottom
                y = y_max - int(round(add_percentage * sidelength))
                try:
                    temp = added_label[y + ty:y_max + 1 + ty,
                                       x_min + tx:x_max + 1 + tx]
                    added_label[y + ty:y_max + 1 + ty,
                                x_min + tx:x_max + 1 + tx] = np.logical_or(
                                    temp, added_label[y:y_max + 1,
                                                      x_min:x_max + 1])
                except ValueError as e:  # indices were out of bounds
                    num_tries += 1
                    continue

        # Make sure the mass is reasonable
        if (np.count_nonzero(added_label) / added_label.size > 0.001) and \
           (np.count_nonzero(added_label) / added_label.size < 0.98):
            valid_transform = True

        num_tries += 1

    return added_label
def random_ellipses(label):
    """ Randomly add/drop a few ellipses in the mask
        This is adapted from the DexNet 2.0 code.
        Their code: https://github.com/BerkeleyAutomation/gqcnn/blob/75040b552f6f7fb264c27d427b404756729b5e88/gqcnn/sgd_optimizer.py

        @param label: a [H x W] numpy array of {0, 1}
    """
    H, W = label.shape

    num_tries = 0
    valid_transform = False
    while not valid_transform:

        if num_tries >= cfg.TRAIN.max_augmentation_tries:
            print('Ellipse: Exhausted number of augmentation tries...')
            return label

        new_label = label.copy()

        # Sample number of ellipses to include/dropout
        num_ellipses = np.random.poisson(cfg.TRAIN.num_ellipses_mean)

        # Sample ellipse centers by sampling from Gaussian at object center
        pixel_indices = util_.build_matrix_of_indices(H, W)
        h_idx, w_idx = np.where(new_label)
        mu = np.mean(pixel_indices[h_idx, w_idx, :],
                     axis=0)  # Shape: [2]. y_center, x_center
        sigma = 2 * np.cov(pixel_indices[h_idx, w_idx, :].T)  # Shape: [2 x 2]
        if np.any(np.isnan(mu)) or np.any(np.isnan(sigma)):
            print(mu, sigma, h_idx, w_idx)
        ellipse_centers = np.random.multivariate_normal(
            mu, sigma, size=num_ellipses)  # Shape: [num_ellipses x 2]
        ellipse_centers = np.round(ellipse_centers).astype(int)

        # Sample ellipse radii and angles
        x_min, y_min, x_max, y_max = util_.mask_to_tight_box(new_label)
        scale_factor = max(
            x_max - x_min, y_max -
            y_min) * cfg.TRAIN.ellipse_size_percentage  # Mean of gamma r.v.
        x_radii = np.random.gamma(cfg.TRAIN.ellipse_gamma_base_shape *
                                  scale_factor,
                                  cfg.TRAIN.ellipse_gamma_base_scale,
                                  size=num_ellipses)
        y_radii = np.random.gamma(cfg.TRAIN.ellipse_gamma_base_shape *
                                  scale_factor,
                                  cfg.TRAIN.ellipse_gamma_base_scale,
                                  size=num_ellipses)
        angles = np.random.randint(0, 360, size=num_ellipses)

        # Dropout ellipses
        for i in range(num_ellipses):
            center = ellipse_centers[i, :]
            x_radius = np.round(x_radii[i]).astype(int)
            y_radius = np.round(y_radii[i]).astype(int)
            angle = angles[i]

            # include or dropout the ellipse
            mask = np.zeros_like(new_label)
            mask = cv2.ellipse(mask,
                               tuple(center[::-1]), (x_radius, y_radius),
                               angle=angle,
                               startAngle=0,
                               endAngle=360,
                               color=1,
                               thickness=-1)
            if np.random.rand() < 0.5:
                new_label[mask == 1] = 0  # Drop out ellipse
            else:
                new_label[mask == 1] = 1  # Add ellipse

        # Make sure the mass is reasonable
        if (np.count_nonzero(new_label) / new_label.size > 0.001) and \
           (np.count_nonzero(new_label) / new_label.size < 0.98):
            valid_transform = True

        num_tries += 1

    return new_label