Spaces:
Running
Running
| import math | |
| import numpy as np | |
| import cv2 | |
| def _seg2bmap(seg, width=None, height=None): | |
| """ | |
| From a segmentation, compute a binary boundary map with 1 pixel wide | |
| boundaries. The boundary pixels are offset by 1/2 pixel towards the | |
| origin from the actual segment boundary. | |
| Arguments: | |
| seg : Segments labeled from 1..k. | |
| width : Width of desired bmap <= seg.shape[1] | |
| height : Height of desired bmap <= seg.shape[0] | |
| Returns: | |
| bmap (ndarray): Binary boundary map. | |
| David Martin <dmartin@eecs.berkeley.edu> | |
| January 2003 | |
| """ | |
| seg = seg.astype(bool) | |
| seg[seg > 0] = 1 | |
| assert np.atleast_3d(seg).shape[2] == 1 | |
| width = seg.shape[1] if width is None else width | |
| height = seg.shape[0] if height is None else height | |
| h, w = seg.shape[:2] | |
| ar1 = float(width) / float(height) | |
| ar2 = float(w) / float(h) | |
| assert not (width > w | height > h | abs(ar1 - ar2) > | |
| 0.01), "Can" "t convert %dx%d seg to %dx%d bmap." % (w, h, width, height) | |
| e = np.zeros_like(seg) | |
| s = np.zeros_like(seg) | |
| se = np.zeros_like(seg) | |
| e[:, :-1] = seg[:, 1:] | |
| s[:-1, :] = seg[1:, :] | |
| se[:-1, :-1] = seg[1:, 1:] | |
| b = seg ^ e | seg ^ s | seg ^ se | |
| b[-1, :] = seg[-1, :] ^ e[-1, :] | |
| b[:, -1] = seg[:, -1] ^ s[:, -1] | |
| b[-1, -1] = 0 | |
| if w == width and h == height: | |
| bmap = b | |
| else: | |
| bmap = np.zeros((height, width)) | |
| for x in range(w): | |
| for y in range(h): | |
| if b[y, x]: | |
| j = 1 + math.floor((y - 1) + height / h) | |
| i = 1 + math.floor((x - 1) + width / h) | |
| bmap[j, i] = 1 | |
| return bmap | |
| # https://github.com/davisvideochallenge/davis2017-evaluation/blob/master/davis2017/metrics.py#L6 | |
| def db_eval_iou(annotation, segmentation, void_pixels=None): | |
| """ Compute region similarity as the Jaccard Index. | |
| Arguments: | |
| annotation (ndarray): binary annotation map. | |
| segmentation (ndarray): binary segmentation map. | |
| void_pixels (ndarray): optional mask with void pixels | |
| Return: | |
| jaccard (float): region similarity | |
| """ | |
| assert annotation.shape == segmentation.shape, \ | |
| f'Annotation({annotation.shape}) and segmentation:{segmentation.shape} dimensions do not match.' | |
| annotation = annotation.astype(bool) | |
| segmentation = segmentation.astype(bool) | |
| if void_pixels is not None: | |
| assert annotation.shape == void_pixels.shape, \ | |
| f'Annotation({annotation.shape}) and void pixels:{void_pixels.shape} dimensions do not match.' | |
| void_pixels = void_pixels.astype(bool) | |
| else: | |
| void_pixels = np.zeros_like(segmentation) | |
| # Intersection between all sets | |
| inters = np.sum((segmentation & annotation) & np.logical_not(void_pixels), axis=(-2, -1)) | |
| union = np.sum((segmentation | annotation) & np.logical_not(void_pixels), axis=(-2, -1)) | |
| j = inters / union | |
| if j.ndim == 0: | |
| j = 0 if np.isclose(union, 0) else j | |
| else: | |
| j[np.isclose(union, 0)] = 0 | |
| return j | |
| def db_eval_boundary(annotation, segmentation, void_pixels=None, bound_th=0.008): | |
| assert annotation.shape == segmentation.shape | |
| if void_pixels is not None: | |
| assert annotation.shape == void_pixels.shape | |
| if annotation.ndim == 3: | |
| n_frames = annotation.shape[0] | |
| f_res = np.zeros(n_frames) | |
| for frame_id in range(n_frames): | |
| void_pixels_frame = None if void_pixels is None else void_pixels[frame_id, :, :, ] | |
| f_res[frame_id] = f_measure(segmentation[frame_id, :, :, ], annotation[frame_id, :, :], void_pixels_frame, bound_th=bound_th) | |
| elif annotation.ndim == 2: | |
| f_res = f_measure(segmentation, annotation, void_pixels, bound_th=bound_th) | |
| else: | |
| raise ValueError(f'db_eval_boundary does not support tensors with {annotation.ndim} dimensions') | |
| return f_res | |
| def f_measure(foreground_mask, gt_mask, void_pixels=None, bound_th=0.008): | |
| """ | |
| Compute mean,recall and decay from per-frame evaluation. | |
| Calculates precision/recall for boundaries between foreground_mask and | |
| gt_mask using morphological operators to speed it up. | |
| Arguments: | |
| foreground_mask (ndarray): binary segmentation image. | |
| gt_mask (ndarray): binary annotated image. | |
| void_pixels (ndarray): optional mask with void pixels | |
| Returns: | |
| F (float): boundaries F-measure | |
| """ | |
| assert np.atleast_3d(foreground_mask).shape[2] == 1 | |
| if void_pixels is not None: | |
| void_pixels = void_pixels.astype(bool) | |
| else: | |
| void_pixels = np.zeros_like(foreground_mask).astype(bool) | |
| bound_pix = bound_th if bound_th >= 1 else \ | |
| np.ceil(bound_th * np.linalg.norm(foreground_mask.shape)) | |
| # Get the pixel boundaries of both masks | |
| fg_boundary = _seg2bmap(foreground_mask * np.logical_not(void_pixels)) | |
| gt_boundary = _seg2bmap(gt_mask * np.logical_not(void_pixels)) | |
| from skimage.morphology import disk | |
| # fg_dil = binary_dilation(fg_boundary, disk(bound_pix)) | |
| fg_dil = cv2.dilate(fg_boundary.astype(np.uint8), disk(bound_pix).astype(np.uint8)) | |
| # gt_dil = binary_dilation(gt_boundary, disk(bound_pix)) | |
| gt_dil = cv2.dilate(gt_boundary.astype(np.uint8), disk(bound_pix).astype(np.uint8)) | |
| # Get the intersection | |
| gt_match = gt_boundary * fg_dil | |
| fg_match = fg_boundary * gt_dil | |
| # Area of the intersection | |
| n_fg = np.sum(fg_boundary) | |
| n_gt = np.sum(gt_boundary) | |
| # % Compute precision and recall | |
| if n_fg == 0 and n_gt > 0: | |
| precision = 1 | |
| recall = 0 | |
| elif n_fg > 0 and n_gt == 0: | |
| precision = 0 | |
| recall = 1 | |
| elif n_fg == 0 and n_gt == 0: | |
| precision = 1 | |
| recall = 1 | |
| else: | |
| precision = np.sum(fg_match) / float(n_fg) | |
| recall = np.sum(gt_match) / float(n_gt) | |
| # Compute F measure | |
| if precision + recall == 0: | |
| F = 0 | |
| else: | |
| F = 2 * precision * recall / (precision + recall) | |
| return F | |