def create_overlay(features):
    im_u8 = features['im_cropped_u8']
    im_rgb = np.empty((im_u8.shape[0], im_u8.shape[1], 3), np.float32)
    im_rgb[:, :, :] = im_u8[:, :, np.newaxis]

    if True:
        # splotches
        if False and "ov_splotches_u8" in features:
            splotches = features["ov_splotches_u8"]
            im_rgb[:, :, 2] += splotches
            im_rgb[:, :, 1] -= 0.5 * splotches
            im_rgb[:, :, 0] -= 0.5 * splotches

        # bright lines/areas
        if "ov_bright_area_u8" in features:
            broken_fingers = features["ov_bright_area_u8"]  # *2
            im_rgb[:, :, 0] -= broken_fingers
            im_rgb[:, :, 1] += broken_fingers
            im_rgb[:, :, 2] -= broken_fingers

        if "ov_bright_lines_u8" in features:
            broken_fingers = features["ov_bright_lines_u8"]  # *2
            im_rgb[:, :, 0] -= broken_fingers
            im_rgb[:, :, 1] += broken_fingers
            im_rgb[:, :, 2] -= broken_fingers

    # cracks
    if "mk_cracks_u8" in features:
        im_rgb = ip.overlay_mask(im_rgb, features['mk_cracks_u8'], 'r')

    if True:
        if 'mk_finger_break_u8' in features:
            im_rgb = ip.overlay_mask(im_rgb, features['mk_finger_break_u8'], 'b')

        if 'ov_dark_middle_u8' in features:
            impure = features["ov_dark_middle_u8"] // 2
            im_rgb[:, :, 0] += impure
            im_rgb[:, :, 1] += impure
            im_rgb[:, :, 2] -= impure

        if 'ov_dark_areas_u8' in features:
            impure = features["ov_dark_areas_u8"]
            im_rgb[:, :, 0] += impure
            im_rgb[:, :, 1] -= impure
            im_rgb[:, :, 2] += impure

        if "mk_dark_spots_outline_u8" in features:
            im_rgb = ip.overlay_mask(im_rgb, features['mk_dark_spots_outline_u8'], colour='r')

    im_rgb[im_rgb < 0] = 0
    im_rgb[im_rgb > 255] = 255
    im_rgb = im_rgb.astype(np.uint8)

    return im_rgb
def create_overlay(features):
    normed = features['im_cropped_u8']

    orig = normed.astype(np.int32)

    if False:
        view = ImageViewer(normed)
        view.show()

    b = orig
    g = orig
    r = orig

    if features['_cell_type'] == 'mono':
        pass
    elif features['_cell_type'] == 'multi':
        foreground = features['ov_dislocations_u8']
        b = orig + foreground
        g = orig - foreground
        r = orig - foreground

        impure = features['ov_impure2_u8']
        b -= impure
        g -= impure
        r += impure
    else:
        assert False

    r = np.clip(r, 0, 255)
    g = np.clip(g, 0, 255)
    b = np.clip(b, 0, 255)
    rgb = np.empty((normed.shape[0], normed.shape[1], 3), np.uint8)
    rgb[:, :, 0] = r.astype(np.uint8)
    rgb[:, :, 1] = g.astype(np.uint8)
    rgb[:, :, 2] = b.astype(np.uint8)

    # cracks
    if "mk_cracks_u8" in features:
        rgb = ip.overlay_mask(rgb, features['mk_cracks_u8'], 'r')

    if features['_cell_type'] == 'mono':
        # dark spots
        rgb = ip.overlay_mask(rgb, features['mk_dark_spots_outline_u8'], 'b')
        # dark areas
        if 'ov_dark_areas_u8' in features:
            dark = features["ov_dark_areas_u8"]
            rgb[:, :, 0] += dark
            rgb[:, :, 1] -= dark
            rgb[:, :, 2] += dark

    return rgb
def dark_spots(features):
    im = features['im_no_fingers']
    h, w = im.shape

    im_mini = im[::6, ::6]
    im_mini_med = cv2.medianBlur(im_mini, ksize=5)
    im_mini_smooth = cv2.GaussianBlur(im_mini_med, ksize=(0, 0), sigmaX=1)
    background = cv2.resize(im_mini_smooth, (w, h))
    dark_areas = background - im
    pixel_ops.ApplyThresholdLT_F32(dark_areas, dark_areas, 0.0, 0.0)

    foreground_mask = ((features['bl_cropped_u8'] == 0) |
                       (features['bl_cropped_u8'] == 4))
    structure = ndimage.generate_binary_structure(2, 1)
    foreground_mask = ndimage.binary_erosion(foreground_mask,
                                             structure=structure,
                                             iterations=3)
    dark_areas[~foreground_mask] = 0

    DARK_SPOT_SENSITIVITY = 0.08
    dark_spots = (dark_areas > DARK_SPOT_SENSITIVITY).astype(np.uint8)
    min_size = int(h * w * 0.0001)
    ip.remove_small_ccs(dark_spots, min_size)

    dark_spots_outline = ndimage.binary_dilation(
        dark_spots, structure=structure, iterations=2) - dark_spots
    features['mk_dark_spots_filled_u8'] = dark_spots
    features['mk_dark_spots_outline_u8'] = dark_spots_outline

    if False:
        view = ImageViewer(im)
        ImageViewer(background)
        ImageViewer(dark_spots)
        ImageViewer(ip.overlay_mask(im, dark_spots_outline))
        view.show()
def create_overlay(features):
    if 'im_cropped_u8' not in features:
        return None

    im_u8 = features['im_cropped_u8']
    im_rgb = np.empty((im_u8.shape[0], im_u8.shape[1], 3), np.float32)
    im_rgb[:, :, :] = im_u8[:, :, np.newaxis]

    # bright lines
    if "ov_lines_horizontal_u8" in features:
        horizontal = features["ov_lines_horizontal_u8"]
        im_rgb[:, :, 2] += horizontal
        im_rgb[:, :, 1] -= 0.5 * horizontal
        im_rgb[:, :, 0] -= 0.5 * horizontal

    if "ov_lines_vertical_u8" in features:
        vertical = features["ov_lines_vertical_u8"]
        im_rgb[:, :, 0] -= vertical
        im_rgb[:, :, 1] += 0.5 * vertical
        im_rgb[:, :, 2] -= 0.5 * vertical

    if "mk_cracks_u8" in features:
        im_rgb = ip.overlay_mask(im_rgb, features['mk_cracks_u8'], 'r')

    if "mk_dark_spots_outline_u8" in features:
        im_rgb = ip.overlay_mask(im_rgb, features['mk_dark_spots_outline_u8'],
                                 'g')

    if 'ov_dark_middle_u8' in features:
        impure = features["ov_dark_middle_u8"] // 2
        im_rgb[:, :, 0] += impure
        im_rgb[:, :, 1] -= impure
        im_rgb[:, :, 2] += impure

    im_rgb[im_rgb < 0] = 0
    im_rgb[im_rgb > 255] = 255
    im_rgb = im_rgb.astype(np.uint8)

    return im_rgb
def create_overlay(features):
    im_u8 = features['im_cropped_u8']
    im_rgb = np.empty((im_u8.shape[0], im_u8.shape[1], 3), np.float32)
    im_rgb[:, :, :] = im_u8[:, :, np.newaxis]

    if 'mk_cracks_u8' in features:
        im_rgb = ip.overlay_mask(im_rgb, features['mk_cracks_u8'], 'r')

    im_rgb[im_rgb < 0] = 0
    im_rgb[im_rgb > 255] = 255
    im_rgb = im_rgb.astype(np.uint8)

    return im_rgb
def create_overlay(im, features):
    h, w = im.shape
    if 'center_y' in features:
        crop_mask = np.zeros_like(im, np.uint8)
        y = int(round(features['center_y']))
        x = int(round(features['center_x']))
        r = int(round(features['radius']))
        rr, cc = draw.circle_perimeter(y, x, r)
        mask = ((rr < 0) | (rr >= h) | (cc < 0) | (cc >= w))
        rr = rr[~mask]
        cc = cc[~mask]
        crop_mask[rr, cc] = 1
        rgb = ip.overlay_mask(im, crop_mask)
    else:
        rgb = im

    return rgb
def dark_spots(features):
    im = features['im_no_fingers']

    # shrink to standard size
    h, w = 300, 300
    im_small = cv2.resize(im, (h, w))

    dark_areas = np.zeros_like(im_small)
    pixel_ops.DarkSpots(im_small, dark_areas, 8)

    candidates = (dark_areas > parameters.DARK_SPOT_MIN_STRENGTH).astype(np.uint8)
    ip.remove_small_ccs(candidates, parameters.DARK_SPOT_MIN_SIZE)

    candidates = cv2.resize(candidates, (im.shape[1], im.shape[0]))
    candidates[features['mask_busbar_filled']] = 0

    dark_spots_outline = ndimage.binary_dilation(candidates, iterations=3).astype(np.uint8) - \
                         ndimage.binary_dilation(candidates, iterations=1).astype(np.uint8)
    features['mk_dark_spots_outline_u8'] = dark_spots_outline
    features['mk_dark_spots_filled_u8'] = candidates
    features['dark_spots_area_fraction'] = candidates.mean()
    dark_areas_no_noise = dark_areas - parameters.DARK_SPOT_MIN_STRENGTH
    pixel_ops.ApplyThresholdLT_F32(dark_areas_no_noise, dark_areas_no_noise, 0.0, 0.0)
    features['dark_spots_strength'] = dark_areas_no_noise.mean() * 10000
    features['dark_spots_count'] = ip.connected_components(candidates)[1]

    if False:
        print features['dark_spots_area_fraction']
        print features['dark_spots_strength']
        print features['dark_spots_count']
        rgb = ip.overlay_mask(im, dark_spots_outline)
        view = ImageViewer(rgb)
        ImageViewer(dark_areas)
        ImageViewer(dark_areas_no_noise)
        ImageViewer(candidates)
        view.show()
def fill_corners(im, features, edge, dist):
    h, w = im.shape
    if 'radius' in features:
        radius = int(round(features['radius']))
        y2 = int(round(features['center_y']))
        x2 = int(round(features['center_x']))
    elif 'wafer_radius' in features:
        radius = int(round(features['wafer_radius']))
        y2 = int(round(features['wafer_middle_y']))
        x2 = int(round(features['wafer_middle_x']))
    else:
        print "ERROR: No radius found"
        assert False

    h2 = h // 2
    w2 = w // 2

    # pixels to sample intensities along corners
    ys, xs = draw.circle_perimeter(y2, x2, radius - edge)
    mask = ((ys >= 0) & (ys < h) & (xs >= 0) & (xs < w))
    ys = ys[mask]
    xs = xs[mask]
    corner_filled = im.copy()
    corner_avg = 0

    if False:
        im[ys, xs] = im.max() * 1.1
        view = ImageViewer(im)
        view.show()

    # top left
    mask = ((ys < h2) & (xs < w2))
    if mask.sum() > 0:
        corner_val = im[ys[mask], xs[mask]].mean()
        corner_avg += corner_val
        corner_filled[:h2, :w2][dist[:h2, :w2] > radius - edge] = corner_val

    # top right
    mask = ((ys < h2) & (xs > w2))
    if mask.sum() > 0:
        corner_val = im[ys[mask], xs[mask]].mean()
        corner_avg += corner_val
        corner_filled[:h2, w2:][dist[:h2, w2:] > radius - edge] = corner_val

    # bottom left
    mask = ((ys > h2) & (xs < w2))
    if mask.sum() > 0:
        corner_val = im[ys[mask], xs[mask]].mean()
        corner_avg += corner_val
        corner_filled[h2:, :w2][dist[h2:, :w2] > radius - edge] = corner_val

    # bottom right
    mask = ((ys > h2) & (xs > w2))
    if mask.sum() > 0:
        corner_val = im[ys[mask], xs[mask]].mean()
        corner_avg += corner_val
        corner_filled[h2:, w2:][dist[h2:, w2:] > radius - edge] = corner_val

    corner_avg /= 4.0

    # edges
    corner_filled[:, :edge] = np.c_[corner_filled[:, edge]]
    corner_filled[:, -edge:] = np.c_[corner_filled[:, -edge]]
    corner_filled[:edge, :] = np.r_[corner_filled[edge, :]]
    corner_filled[-edge:, :] = np.r_[corner_filled[-edge, :]]

    if False:
        r_mask = np.zeros_like(im, np.uint8)
        r_mask[ys, xs] = 1
        rgb = ip.overlay_mask(im, r_mask)
        view = ImageViewer(rgb)
        ImageViewer(corner_filled)
        view.show()
        sys.exit()

    return corner_filled, corner_avg
def efficiency_analysis(features):
    im = features['im_no_fingers']
    im_peaks = features['im_norm'][features['_peak_row_nums'], :]
    bbs = features['_busbar_cols']

    # make sure no zero values
    im_peaks = im_peaks.copy()
    pixel_ops.ApplyThresholdLT_F32(im_peaks, im_peaks, 0.01, 0.01)
    im = im.copy()
    pixel_ops.ApplyThresholdLT_F32(im, im, 0.01, 0.01)

    # IDEA:
    # two defect mask:
    # 1. existing one (highlights anything dark)
    # 2. do a dark line (local mins). mask out & interpolate.
    #    this one won't pick up grain boundaries
    # - slider blends between them

    #################
    # DEFECT MASK 1 #
    #################
    # - very sensitive to all dark areas

    # need to interpolate values along edges/busbars so defects in these regions can be found
    xs = np.arange(im_peaks.shape[0])
    cols = [features['cell_edge_left'], features['cell_edge_right']]
    cols += list(np.where(features['mask_busbar_edges'][0, :])[0])
    for c in cols:
        ys = im_peaks[:, c].copy()
        ys[ys >
           features['_bright_area_thresh']] = features['_bright_area_thresh']
        params_op = optimize.fmin_powell(line_error, [0.0, ys.mean()],
                                         disp=0,
                                         args=(xs, ys),
                                         xtol=0.05,
                                         ftol=0.05)
        vals = xs * params_op[0] + params_op[1]

        im_peaks[:, c] = vals

        if False:
            print features['_bright_area_thresh']
            mask = np.zeros_like(im, np.uint8)
            mask[:, c] = 1
            ImageViewer(ip.overlay_mask(im, mask))
            plt.figure()
            plt.plot(ys)
            plt.plot(vals)
            plt.show()

    # max of actual val and: interpolate vertical line at:
    # - left & right edge
    # - left & right of each BB

    # make monotonic (don't care about local mins)
    bb_mono = between_bb_mono(im_peaks, bbs)
    background1 = bb_mono
    background1 = cv2.GaussianBlur(background1,
                                   ksize=(0, 0),
                                   sigmaX=3,
                                   borderType=cv2.BORDER_REPLICATE)

    # relative difference
    foreground1 = background1 / im_peaks
    foreground1 -= 1
    pixel_ops.ClipImage(foreground1, 0, 3.0)
    foreground1[:, features['mask_busbar_filled'][0, :]] = 0

    # expand to full size
    full_size = np.zeros(im.shape, np.float32)
    pixel_ops.ExpandFingers(full_size, foreground1, features['_peak_row_nums'])
    foreground1 = full_size
    background_full = np.zeros(im.shape, np.float32)
    pixel_ops.ExpandFingers(background_full, background1,
                            features['_peak_row_nums'])

    if False:
        view = ImageViewer(im)
        ImageViewer(foreground1)
        ImageViewer(background1)
        view.show()

    #################
    # DEFECT MASK 2 #
    #################
    # - less likely to include dark grains
    # - mask out local mins and areas of high gradient, and the interpolate background
    flatten_cols = np.r_[im.mean(axis=0)]
    im_flattened = im - flatten_cols
    flatten_rows = np.c_[im_flattened.mean(axis=1)]
    im_flattened -= flatten_rows
    im_flattened[features['mask_busbar_filled']] = 0
    im_smoothed = cv2.GaussianBlur(im_flattened, ksize=(0, 0), sigmaX=2)

    # local mins
    dark_lines = np.zeros_like(im_flattened, np.uint8)
    pixel_ops.LocalMins(im_smoothed, dark_lines)
    s = ndimage.generate_binary_structure(2, 1)
    dark_lines = ndimage.binary_dilation(dark_lines, structure=s)

    # high gradient
    edgesH = cv2.Sobel(im_smoothed, cv2.CV_32F, 1, 0)
    edgesV = cv2.Sobel(im_smoothed, cv2.CV_32F, 0, 1)
    edges = cv2.magnitude(edgesH, edgesV)

    # combine
    defect_candidates = ((edges > 0.2) | dark_lines)

    if False:
        view = ImageViewer(im_flattened)
        ImageViewer(ip.overlay_mask(im_flattened, dark_lines))
        ImageViewer(ip.overlay_mask(im_flattened, edges > 0.2))
        ImageViewer(ip.overlay_mask(im_flattened, defect_candidates))
        view.show()
        sys.exit()

    background2 = interpolate_background(im_flattened,
                                         defect_candidates.astype(np.uint8))
    background2 += flatten_rows
    background2 += flatten_cols

    # relative difference
    foreground2 = background2 / im
    foreground2 -= 1
    pixel_ops.ClipImage(foreground2, 0, 3.0)
    foreground2[features['mask_busbar_filled']] = 0

    if False:
        view = ImageViewer(im)
        ImageViewer(background2)
        ImageViewer(foreground2)
        view.show()
        sys.exit()

    if False:
        features['_defect1'] = foreground1
        features['_defect2'] = foreground2

    # discrimination function
    x1, y1 = 0.0, 0.5
    x2, y2 = 0.4, 0.0
    foreground = (-1 * ((y2 - y1) * foreground2 -
                        (x2 - x1) * foreground1 + x2 * y1 - y2 * x1) /
                  np.sqrt((y2 - y1)**2 + (x2 - x1)**2))

    foreground += 0.1 + parameters.CELL_DISLOCATION_SENSITIVITY
    foreground *= 0.25
    pixel_ops.ClipImage(foreground, 0, 1)

    features['ov_dislocations_u8'] = (foreground * 255).astype(np.uint8)

    if False:
        view = ImageViewer(im)
        ImageViewer(foreground1)
        ImageViewer(foreground2)
        ImageViewer(foreground)
        #ImageViewer(background_full)
        # ImageViewer(foreground > 0.2)
        view.show()
        sys.exit()

    ################
    # IMPURE AREAS #
    ################

    def FindImpure(imp_profile, imp_profile_r, defects, debug=False):
        if False:
            ImageViewer(im)
            plt.figure()
            plt.plot(imp_profile)
            plt.plot(defects)
            plt.show()

        imp_ratio = (imp_profile / imp_profile_r).astype(np.float32)
        imp_ratio[imp_ratio > 0.9] = 0.9
        imp_ratio /= 0.9
        global_min = np.argmin(imp_ratio)
        local_mins = np.where((imp_ratio < np.roll(imp_ratio, 1))
                              & (imp_ratio < np.roll(imp_ratio, -1)))[0]

        # make monotonic from global minimum
        imp_mono = imp_ratio.copy()
        flip = False
        if global_min > len(imp_mono) // 2:
            flip = True
            imp_mono = imp_mono[::-1]
            global_min = np.argmin(imp_mono)
        rest = np.ascontiguousarray(imp_mono[global_min:])
        rest_mono = np.empty_like(rest)
        pixel_ops.MakeMonotonic(rest, rest_mono)
        imp_mono[global_min:] = rest_mono
        # if edge_dist < 0.075:
        #     imp_mono[:global_min] = imp_ratio[global_min]
        if flip:
            imp_mono = imp_mono[::-1]
            global_min = np.argmin(imp_mono)

        # for a real impure area, the global minimum should be close to an edge
        edge_dist = min(global_min,
                        len(imp_ratio) - global_min) / float(len(imp_ratio))
        edge_spot = int(0.03 * len(imp_mono))
        imp_at_edge = min(imp_mono[edge_spot], imp_mono[-edge_spot])
        imp_1d = np.ones_like(imp_mono)

        # check the defect content in the "impure" area
        if (imp_mono < 1.0).sum() == 0:
            defect_strength = 0
        else:
            # defect_strength = defects[imp_mono < 1.0].mean()
            defect_strength = np.median(defects[imp_mono < 1.0])

        if (edge_dist < 0.075 and  # lowest point close to edge
                imp_at_edge < 0.8 and  # edge is dark
                defect_strength < 0.1 and  # not too many defects
                # (global_min == local_mins[0] or global_min == local_mins[-1]) and # no local mins closer to edge
            (imp_mono - imp_ratio).mean() < 0.035):  # no large non-impure dips
            imp_1d = imp_mono.copy()
            imp_1d -= 0.3
            if global_min < len(imp_profile) // 2:
                imp_1d[:global_min] = imp_1d[global_min]
            else:
                imp_1d[global_min:] = imp_1d[global_min]

        if debug:
            print edge_dist, edge_dist < 0.075
            print imp_at_edge, imp_at_edge < 0.8
            print defect_strength, defect_strength < 0.1
            # print global_min, local_mins[0], (global_min == local_mins[0] or global_min == local_mins[-1])
            print(imp_mono -
                  imp_ratio).mean(), (imp_mono - imp_ratio).mean() < 0.035
            ImageViewer(im)
            plt.figure()
            plt.plot(imp_profile, 'r')
            plt.plot(imp_mono, 'g')
            plt.plot(imp_ratio, 'b')
            # plt.plot((0, len(signal_var)), (0.1, 0.1))
            plt.plot(defects, 'c')
            plt.plot(imp_1d, 'c')

            plt.show()

        # find impure edge width
        THRESH = 0.5
        if imp_1d[0] < THRESH:
            edge_width = np.where(imp_1d < THRESH)[0][-1] / float(
                len(imp_profile))
            left = True
        elif imp_1d[-1] < THRESH:
            edge_width = (len(imp_profile) -
                          np.where(imp_1d < THRESH)[0][0]) / float(
                              len(imp_profile))
            left = False
        else:
            edge_width = 0
            left = True

        return imp_1d, edge_width, left

    if False:
        # ignore defect areas
        defect_mask = foreground > 0.2
        mx = ma.masked_array(im, mask=defect_mask)

        if False:
            view = ImageViewer(im)
            ImageViewer(defect_mask)
            view.show()
    '''
    # left/right edges
    cols = np.apply_along_axis(stats.scoreatpercentile, 0, im, per=75)
    cols[cols > 1.0] = 1.0
    imp_profile = ndimage.gaussian_filter1d(cols, sigma=3, mode="nearest")
    imp_profile_r = imp_profile[::-1]
    if len(bbs) >= 2:
        mid = (bbs[0] + bbs[-1]) // 2
    else:
        mid = im.shape[1] // 2
    imp_profile_r = np.roll(imp_profile_r, mid - (len(imp_profile) // 2))
    col_defect = foreground.mean(axis=0)
    imp_h, ew_h, e_left = FindImpure(imp_profile, imp_profile_r, col_defect, debug=False)
    '''

    # top/bottom edges
    rows = np.apply_along_axis(stats.scoreatpercentile, 1, im, per=75)
    rows[rows > 1.0] = 1.0
    imp_profile = ndimage.gaussian_filter1d(rows, sigma=3, mode="nearest")
    imp_profile_r = imp_profile[::-1]
    row_defect = foreground.mean(axis=1)
    imp_v, ew_v, e_top = FindImpure(imp_profile,
                                    imp_profile_r,
                                    row_defect,
                                    debug=False)

    features['impure_edge_width'] = ew_v
    if e_top:
        features['impure_edge_side'] = 0
    else:
        features['impure_edge_side'] = 2

    impure = np.ones_like(im)
    impure[:, :] = np.c_[imp_v]

    if False:
        print features['impure_edge_width'], features['impure_edge_side']
        view = ImageViewer(impure)
        view.show()
        sys.exit()

    imp_cutoff = 0.55
    pixel_ops.ApplyThresholdGT_F32(impure, impure, imp_cutoff, imp_cutoff)
    impure /= imp_cutoff

    impure_overlay = np.log10(2 - impure)

    if False:
        plt.figure()
        plt.plot(impure[:, 0], 'r')
        plt.plot(np.log(impure[:, 0] + 1), 'g')
        plt.plot(np.log10(impure[:, 0] + 1), 'b')
        plt.show()

        view = ImageViewer(impure)
        view.show()

    pixel_ops.ClipImage(impure_overlay, 0, 1)
    features['ov_impure2_u8'] = (impure_overlay * 255).astype(np.uint8)

    ###########
    # METRICS #
    ###########
    num_pixels = im.shape[0] * im.shape[1]

    # impure
    impure_thresh = 0.9  # 0.8
    dislocation_thresh = 0.1
    num_impure_pixels = pixel_ops.CountThresholdLT_F32(impure, impure_thresh)
    features['impure_area_fraction'] = (num_impure_pixels / float(num_pixels))

    if features['impure_area_fraction'] > 0.001:
        pure_mask = (impure > impure_thresh) & (foreground <
                                                dislocation_thresh)
        features['impure_strength'] = (im[pure_mask].mean() /
                                       im[impure < impure_thresh].mean()) - 1
    else:
        features['impure_strength'] = 0
        features['impure_area_fraction'] = 0

    features['impure_strength2'] = features['impure_strength'] * features[
        'impure_area_fraction'] * 100

    # dislocations
    num_dislocations = pixel_ops.CountThresholdGT_F32(foreground,
                                                      dislocation_thresh)
    features['dislocation_area_fraction'] = (num_dislocations /
                                             float(num_pixels))
    features['_foreground'] = foreground
    features['_dislocation_thresh'] = dislocation_thresh
    features['_impure'] = impure
    features['_impure_thresh'] = impure_thresh

    # find the average distance between dislocation pixels
    ys, xs = np.where(foreground > dislocation_thresh)
    points = np.c_[ys, xs]
    num_samples = 1500
    if len(ys) > 0:
        points = points[::max(1, points.shape[0] // num_samples), :]
        pixel_dists = distance.pdist(points)
        avg_dist = np.mean(pixel_dists)
        avg_dist = (avg_dist - 0.3 * im.shape[0]) / (0.5 * im.shape[0])
    else:
        avg_dist = 0
    features['dislocation_density'] = 1.0 - avg_dist

    if len(ys) > 0:
        dis_vals = foreground[ys, xs]
        dislocation_strength = math.sqrt((dis_vals**2).mean())
        features['dislocation_severity_A'] = dislocation_strength

        # create a second defect strength.
        ratio2 = background_full / im
        ratio2 = np.clip(ratio2, 1, 5)
        ratio2[:, features['mask_busbar_filled'][0, :]] = 1
        features['dislocation_severity_B'] = ratio2[ys, xs].mean()

        if False:
            view = ImageViewer(background_full)
            ImageViewer(im)
            ImageViewer(ratio2)
            view.show()
    else:
        features['dislocation_severity_A'] = 0
        features['dislocation_severity_B'] = 0

    if False:
        view = ImageViewer(im)
        foreground[foreground < 0] = 0
        ImageViewer(foreground)
        ImageViewer(background_full)
        view.show()
        sys.exit()

    # dislocation histogram features
    foreground_bins = np.zeros((5), np.float32)
    pixel_ops.BackgrounHistogram(foreground, foreground_bins)
    foreground_bins = (foreground_bins / num_pixels) * 100
    features['dislocation_hist_01'] = foreground_bins[0]
    features['dislocation_hist_02'] = foreground_bins[1]
    features['dislocation_hist_03'] = foreground_bins[2]
    features['dislocation_hist_04'] = foreground_bins[3]
    features['dislocation_hist_05'] = foreground_bins[4]
    features['dislocation_strength'] = features[
        'dislocation_area_fraction'] * features['dislocation_severity_A']

    if False:
        # print features['foreground_hist_01'], features['foreground_hist_02'], features['foreground_hist_03'],
        # print features['foreground_hist_04'], features['foreground_hist_05']
        view = ImageViewer(im)
        ImageViewer(foreground)
        ImageViewer(impure)
        ImageViewer(create_overlay(features))
        view.show()
Exemplo n.º 10
0
def block_foreground(im, features):
    per_col = features['_col_60']
    im_col = np.dot(np.ones((im.shape[0], 1), np.float32),
                    per_col.reshape(1, per_col.shape[0]))

    per_row = features['_row_90']
    im_row = np.dot(per_row.reshape(im.shape[0], 1),
                    np.ones((1, im.shape[1]), np.float32))

    background = ip.fast_smooth(np.minimum(im_col, im_row), sigma=5)
    foreground = background - im
    pixel_ops.ApplyThresholdLT_F32(foreground, foreground, 0, 0)
    pixel_ops.ApplyThresholdLT_F32(background, foreground, 0.3, 0)

    if False:
        view = ImageViewer(im, vmin=0, vmax=1)
        ImageViewer(im_col, vmin=0, vmax=1)
        ImageViewer(im_row, vmin=0, vmax=1)
        ImageViewer(background, vmin=0, vmax=1)
        ImageViewer(foreground, vmin=0, vmax=1)
        view.show()
        sys.exit()

    # skeletonized version of defects
    local_mins = np.zeros_like(foreground, np.uint8)
    f = cv2.GaussianBlur(foreground * -1, ksize=(0, 0), sigmaX=2)
    pixel_ops.LocalMins(f, local_mins)
    dis = ((local_mins == 1) & (foreground > 0.1)).astype(np.uint8)
    ys, xs = np.where(dis)
    pixel_ops.FastThin(dis, ys.copy(), xs.copy(), ip.thinning_lut)
    ip.remove_small_ccs(dis, 10)

    if False:
        crossing = np.zeros_like(dis)
        pixel_ops.ComputeCrossings(dis, crossing)

        junctions = crossing > 2
        struct = ndimage.generate_binary_structure(2, 2)
        junctions_d = ndimage.binary_dilation(junctions, struct)
        branches = dis.copy()
        branches[junctions_d] = 0

        # find branches that touch an end point
        ccs, num_ccs = ip.connected_components(branches)
        spurs = np.zeros_like(dis)
        for cc in set(ccs[crossing == 1]):
            if cc == 0: continue
            spurs[ccs == cc] = 1

        # sys.exit()
        remove = spurs.copy()
        ip.remove_small_ccs(remove, 10)
        removed = spurs - remove

        pruned = dis - removed
        crossing = np.zeros_like(dis)
        pruned = pruned.astype(np.uint8)
        pixel_ops.ComputeCrossings(pruned, crossing)
        pruned[crossing == 1] = 0

        dis = pruned

        rgb = ip.overlay_mask(im, dis, colour='b')
        ip.save_image("brick_lines_skeleton.png", dis)
        ip.save_image("brick_lines_overlay.png", rgb)
        view = ImageViewer(foreground, vmin=0, vmax=1)
        ImageViewer(rgb)
        view.show()
        sys.exit()

    # create a height-based profile of dislocation levels
    # - crop impure areas at top and bottom,
    imp = np.where(per_row < 0.5)[0]
    mid = len(per_row) // 2
    upper_half = imp[imp < mid]
    if len(upper_half) > 0:
        top = upper_half.max()
    else:
        top = 0
    lower_half = imp[imp > mid]
    if len(lower_half) > 0:
        bottom = lower_half.min()
    else:
        bottom = len(per_row)

    if False:
        plt.figure()
        plt.plot(per_row)
        plt.vlines([top, bottom], ymin=0, ymax=1)
        plt.show()
        sys.exit()
    foreground_pure = foreground[top:bottom, :]
    dislocation_profile = foreground_pure.mean(axis=0)
    dislocation_profile[per_col < 0.6] = 0
    dislocation_profile = ndimage.gaussian_filter1d(dislocation_profile,
                                                    sigma=5)
    features['_dis_avg_height'] = dislocation_profile

    if False:
        view = ImageViewer(im, vmin=0, vmax=1)
        # ImageViewer(im_col, scale=0.5, vmin=0, vmax=1)
        # ImageViewer(im_row, scale=0.5, vmin=0, vmax=1)
        ImageViewer(background, vmin=0, vmax=1)
        ImageViewer(foreground, vmin=0, vmax=1)
        ImageViewer(foreground_pure, vmin=0, vmax=1)
        plt.figure()
        plt.plot(dislocation_profile)

        view.show()
        sys.exit()

    return foreground
def finger_defects(features):
    im = features['im_norm']
    h, w = im.shape
    row_nums = features['_finger_row_nums']
    finger_im = im[row_nums, :]  # .copy()
    mask = features['bl_cropped_u8'][row_nums, :]
    bb_locs = features['_busbar_cols']

    # make a little more robust by averaging 3 locations:
    # - finger, and a little above/below
    offset = 1  # int(features['finger_period'] / 2.0)
    off_up = im[row_nums - offset, :]
    off_down = im[row_nums + offset, :]
    finger_im = (finger_im + off_up + off_down) / 3.0
    features['_finger_im'] = finger_im

    if False:
        view = ImageViewer(im)
        ImageViewer(finger_im)
        # ImageViewer(finger_im2)
        view.show()

    # make monotonic (don't care about local mins)
    mono_lr = np.empty_like(finger_im)
    mono_rl = np.empty_like(finger_im)
    pixel_ops.MakeMonotonicBBs(finger_im, mono_lr, bb_locs)
    pixel_ops.MakeMonotonicBBs(np.ascontiguousarray(finger_im[:, ::-1]), mono_rl,
                               np.ascontiguousarray(w - bb_locs[::-1]))
    mono_rl = mono_rl[:, ::-1]
    mono = np.minimum(mono_lr, mono_rl)

    if False:
        view = ImageViewer(im)
        # ImageViewer(normed)
        # ImageViewer(mono_lr)
        # ImageViewer(mono_rl)
        ImageViewer(mono)
        view.show()

    ####################
    # BROKEN FINGERS 1 #
    ####################
    # Detect intensity changes along a finger
    # - works best away from edges, and can handle breaks aligned in a column
    # 1. at a break, the min on bright side should be greater than max on dark side
    # 2. sharp local gradient, not a region with a steady (but high) gradient

    finger_filled = mono.copy()

    # 1 - brighter areas
    s = 25
    offset = s // 2 + 3
    maxs = ndimage.maximum_filter(finger_filled, size=(1, s), mode="nearest")
    pixel_ops.ApplyThresholdLT_F32(maxs, maxs, 0.1, 0.1)
    mins = ndimage.minimum_filter(finger_filled, size=(1, s), mode="nearest")
    d1 = np.roll(mins, offset, axis=1) / np.roll(maxs, -offset, axis=1)
    d2 = np.roll(mins, -offset, axis=1) / np.roll(maxs, offset, axis=1)
    break_strength = np.maximum(d1, d2)

    # 2 - sharp local drop
    f = np.array([[-1, -1, -1, -1, -1, 0, 1, 1, 1, 1, 1]], dtype=np.float32)
    edges_abs = np.abs(ndimage.correlate(mono, weights=f, mode="nearest"))
    edges_local = edges_abs - np.maximum(np.roll(edges_abs, 6, axis=1), np.roll(edges_abs, -6, axis=1))
    break_strength += edges_local
    features['_edges_abs'] = edges_abs

    if False:
        print parameters.BROKEN_FINGER_THRESHOLD1
        print parameters.BROKEN_FINGER_EDGE_THRESHOLD
        view = ImageViewer(finger_filled)
        # ImageViewer(maxs)
        # ImageViewer(mins)
        # ImageViewer(d1)
        # ImageViewer(d2)
        # ImageViewer(edges_local)
        ImageViewer(break_strength)
        view.show()
        sys.exit()

    # find local maxes
    breaks1 = ((break_strength >= np.roll(break_strength, 1, axis=1)) &
               (break_strength >= np.roll(break_strength, -1, axis=1)) &
               (break_strength > parameters.BROKEN_FINGER_THRESHOLD1)).astype(np.uint8)

    # For this detector, ignore breaks near edges, corners & busbars
    mask_background = mask == 8  # edges
    mask2 = ndimage.binary_dilation(mask_background, np.ones((1, s + 4), np.uint8))
    mask3 = ndimage.binary_dilation(mask2, np.ones((3, 1), np.uint8))
    corners = mask2.sum(axis=0) > 2
    mask2[:, corners] = mask3[:, corners]
    bb_mask = features['mask_busbar_filled'][row_nums, :]
    bb_mask = ndimage.binary_dilation(bb_mask, np.ones((1, 11), np.uint8))
    mask2 = (mask2 | bb_mask)
    breaks1[mask2] = 0

    if False:
        view = ImageViewer(im)
        ImageViewer(break_strength)
        ImageViewer(breaks1)
        view.show()
        sys.exit()

    ####################
    # BROKEN FINGERS 2 #
    ####################
    # - find breaks by comparing with fingers above/below (using bright lines image)
    # - the advantage of this approach is that it can find breaks near edges
    #   and busbars
    # - doesn't need to be very sensitive -- just find breaks near borders, which should be strong
    # - note that there also needs to be a gradient at that spot, otherwise we get
    #   some phantom breaks
    rows_filtered = np.zeros_like(mono)
    pixel_ops.FilterV(mono, rows_filtered)
    rows_filtered[:2, :] = finger_im[:2, :]
    rows_filtered[-2:, :] = finger_im[-2:, :]
    bright_lines = mono - rows_filtered

    mask_background = ((mask == 1) | (mask == 8))
    bright_lines[mask_background] = False
    pixel_ops.ClipImage(bright_lines, 0.0, 1.0)
    pixel_ops.ApplyThresholdLT_F32(mono, bright_lines, 0.4, 0.0)

    if False:
        view = ImageViewer(mono)
        ImageViewer(mask)
        ImageViewer(bright_lines)
        view.show()
        sys.exit()

    # filter bright lines
    min_length = w // 20
    cc_sums = np.zeros_like(bright_lines)
    breaks2 = np.zeros_like(bright_lines, np.uint8)
    pixel_ops.BrightLineBreaks(bright_lines, cc_sums, breaks2, 0.04,
                               parameters.BROKEN_FINGER_THRESHOLD2, min_length)

    if False:
        for r in range(cc_sums.shape[0]):
            if cc_sums[r, :].sum() == 0:
                continue
            print r
            plt.figure()
            plt.plot(bright_lines[r, :])
            plt.plot((cc_sums[r, :] > 0) * bright_lines[r, :].max())
            plt.figure()
            plt.plot(mono[r, :])
            plt.plot((cc_sums[r, :] > 0) * mono[r, :].max())
            plt.show()

    if False:
        view = ImageViewer(im)
        ImageViewer(bright_lines)
        ImageViewer(cc_sums)
        ImageViewer(breaks2)
        view.show()

    ###########
    # COMBINE #
    ###########
    # if there are 2+ close together, only keep one with strongest gradient

    if False:
        # check break source
        # green == independent lines (breaks1)
        # red == relative lines (breaks2)
        breaks_full = np.zeros_like(im)
        breaks_full[features["_finger_row_nums"], :] = breaks1
        breaks_full = ndimage.binary_dilation(breaks_full, structure=ndimage.generate_binary_structure(2, 1),
                                              iterations=3)
        rgb = ip.overlay_mask(im, breaks_full, 'g')

        breaks_full = np.zeros_like(im)
        breaks_full[features["_finger_row_nums"], :] = breaks2
        breaks_full = ndimage.binary_dilation(breaks_full, structure=ndimage.generate_binary_structure(2, 1),
                                              iterations=3)
        rgb = ip.overlay_mask(rgb, breaks_full, 'r')

        view = ImageViewer(rgb)
        view.show()
        sys.exit()

    breaks = (breaks1 | breaks2).astype(np.uint8)
    break_count = ndimage.correlate(breaks, weights=np.ones((1, 15), np.uint8), mode='constant')  # .astype(np.uinut8)
    pixel_ops.CombineBreaks(breaks, break_count, edges_abs)

    # ignore breaks in cell edge or background
    cell_template = features['bl_cropped_u8'][features['_finger_row_nums']]
    breaks[((cell_template == 1) | (cell_template == 8))] = 0

    # create break mask
    breaks_full = np.zeros_like(im, np.uint8)
    breaks_full[features["_finger_row_nums"], :] = breaks
    features['mk_finger_break_u8'] = breaks_full

    if False:
        print breaks_full.sum()
        view = ImageViewer(im)
        ImageViewer(finger_filled)
        ImageViewer(break_count)
        ImageViewer(breaks)
        view.show()
        sys.exit()
def create_overlay(features):
    h, w = features['im_cropped_u8'].shape

    if True and 'ov_impure2_u8' in features:
        impure = features['ov_impure2_u8']
    else:
        impure = np.zeros_like(features['im_cropped_u8'], np.uint8)

    if 'ov_dislocations_u8' in features:
        foreground = features['ov_dislocations_u8']  # .astype(np.float32) / 255.
    else:
        foreground = np.zeros_like(features['im_cropped_u8'], np.uint8)

    orig = features['im_cropped_u8'].astype(np.int32)

    if True:
        # foreground
        b = orig + foreground
        g = orig - foreground
        r = orig - foreground
    else:
        b = orig.copy()
        g = orig.copy()
        r = orig.copy()

    if True:
        # splotches
        if "ov_splotches_u8" in features:
            splotches = features["ov_splotches_u8"]  # * 2
            b += splotches
            g -= splotches
            r -= splotches

    if True:
        # show impure as red
        b -= impure
        g -= impure
        r += impure

    if False:
        # show green line at boundary between impure and pure
        if features['impure_edge_width'] > 0:
            features['impure_edge_side'] = int(features['impure_edge_side'])
            if features['impure_edge_side'] in [0, 2]:
                pixels = int(round(features['impure_edge_width'] * orig.shape[0]))
            else:
                pixels = int(round(features['impure_edge_width'] * orig.shape[1]))
            if features['impure_edge_side'] == 0:
                r[pixels, :] = 255
                g[pixels, :] = 0
                b[pixels, :] = 0
            elif features['impure_edge_side'] == 2:
                r[-pixels, :] = 255
                g[-pixels, :] = 0
                b[-pixels, :] = 0
            elif features['impure_edge_side'] == 1:
                r[:, -pixels] = 255
                g[:, -pixels] = 0
                b[:, -pixels] = 0
            elif features['impure_edge_side'] == 3:
                r[:, pixels] = 255
                g[:, pixels] = 0
                b[:, pixels] = 0

    # bright lines
    if True and "ov_bright_lines_u8" in features:
        broken_fingers = features["ov_bright_lines_u8"] * 2
        r -= broken_fingers
        g += broken_fingers
        b -= broken_fingers

    if "ov_bright_area_u8" in features:
        broken_fingers = features["ov_bright_area_u8"] * 3
        r -= broken_fingers
        g += broken_fingers
        b -= broken_fingers

    r = np.clip(r, 0, 255)
    g = np.clip(g, 0, 255)
    b = np.clip(b, 0, 255)

    rgb = np.empty((h, w, 3), np.uint8)
    rgb[:, :, 0] = r.astype(np.uint8)
    rgb[:, :, 1] = g.astype(np.uint8)
    rgb[:, :, 2] = b.astype(np.uint8)

    # cracks
    if "mk_cracks_u8" in features:
        im_rgb = ip.overlay_mask(rgb, features['mk_cracks_u8'], 'r')

    # broken fingers
    if True and "mk_finger_break_u8" in features:
        rgb = ip.overlay_mask(rgb, features['mk_finger_break_u8'], 'b')

    return rgb
def find_fingers_perc(im, features):
    """
    Find the period and locations of the grid fingers
    """

    # rows
    row_profile = np.median(im, axis=1)
    row_profile /= row_profile.max()
    grid_rows_mask = np.logical_and(row_profile < np.roll(row_profile, 1),
                                    row_profile < np.roll(row_profile, -1))
    peaks = np.where(row_profile > 0.2)[0]
    start, stop = peaks[0], peaks[-1]
    grid_rows_mask[:start] = False
    grid_rows_mask[stop:] = False
    grid_rows = np.where(grid_rows_mask)[0]

    # compute the period
    local_mins = grid_rows[5:-5]
    if len(local_mins) == 0:
        raise CellTemplateException

    period = (local_mins[-1] - local_mins[0]) / float(len(local_mins) - 1)
    features['mask_grid_rows'] = grid_rows_mask
    features['_finger_row_nums'] = grid_rows
    features['finger_period_row'] = period
    features['_peak_row_nums'] = (grid_rows[:-1] + (period // 2)).astype(
        np.int32)

    # cols
    col_profile = np.median(im, axis=0)
    col_profile /= col_profile.max()
    grid_cols_mask = ((col_profile < np.roll(col_profile, 1)) &
                      (col_profile < np.roll(col_profile, -1)) &
                      (col_profile > 0.3))
    peaks = np.where(col_profile > 0.2)[0]
    start, stop = peaks[0], peaks[-1]
    grid_cols_mask[:start] = False
    grid_cols_mask[stop:] = False
    grid_cols = np.where(grid_cols_mask)[0]

    # compute the period
    local_mins = grid_cols[5:-5]
    if len(local_mins) == 0:
        raise CellTemplateException

    period = (local_mins[-1] - local_mins[0]) / float(len(local_mins) - 1)
    features['mask_grid_cols'] = grid_cols_mask
    features['_finger_col_nums'] = grid_cols
    features['finger_period_col'] = period

    if False:
        print "Row period: {}".format(features['finger_period_row'])
        print "Col period: {}".format(features['finger_period_col'])
        ImageViewer(im)

        mask = np.zeros_like(im, np.uint8)
        mask[:, grid_cols_mask] = 1
        mask[grid_rows_mask, :] = 1
        rgb = ip.overlay_mask(im, mask)
        ImageViewer(rgb)

        plt.figure()
        plt.plot(grid_rows, row_profile[grid_rows], 'o')
        plt.plot(row_profile)
        plt.figure()
        plt.plot(grid_cols, col_profile[grid_cols], 'o')
        plt.plot(col_profile)
        plt.show()
        sys.exit()
def find_busbars(im, features):
    h, w = im.shape

    if parameters.CELL_NO_BBS:
        features['bb_detection_mode'] = -1
        features['_busbar_cols'] = np.array([], np.int32)
        features['busbar_width'] = 0
        features['mask_busbar_edges'] = np.zeros_like(im, dtype=np.uint8)
        features['mask_busbar_filled'] = np.zeros_like(im, dtype=np.uint8)
        return

    ####################
    # BUSBAR LOCATIONS #
    ####################
    def pass_symmetry_test(bb_locs, debug=False):
        # check for symmetry in bb locations
        num_bb = len(bb_locs)
        if num_bb == 2:
            dist = abs(bb_locs[0] - (w - bb_locs[1]))
        elif num_bb == 3:
            dist1 = abs(w // 2 - bb_locs[1])
            dist2 = abs(bb_locs[0] - (w - bb_locs[2]))
            dist = (dist1 + dist2) / 2.0
        elif num_bb == 4:
            dist1 = abs(bb_locs[0] - (w - bb_locs[3]))
            dist2 = abs(bb_locs[1] - (w - bb_locs[2]))
            dist = (dist1 + dist2) / 2.0
        elif num_bb == 5:
            dist1 = abs(w // 2 - bb_locs[2])
            dist2 = abs(bb_locs[0] - (w - bb_locs[4]))
            dist3 = abs(bb_locs[1] - (w - bb_locs[3]))
            dist = (dist1 + dist2 + dist3) / 3.0
        else:
            dist = 100

        if num_bb >= 3:
            spacing = bb_locs[1:] - bb_locs[:-1]
            spacing_ratio = spacing.max() / float(spacing.min())
        else:
            spacing_ratio = 1

        if debug:
            print dist, spacing_ratio

        return (dist <= 15) and (spacing_ratio < 2)

    # attempt 1: columns without much variation
    features['bb_detection_mode'] = None
    col_var = ndimage.gaussian_filter1d(features['_col_var'],
                                        sigma=2,
                                        mode="constant")
    col_var = ndimage.gaussian_filter1d(col_var, sigma=1)
    dip_strength = np.minimum(np.roll(col_var, 10), np.roll(col_var,
                                                            -10)) - col_var
    threshold = parameters.CELL_BB_MIN
    e = len(col_var) // 25
    col_var[:e] = 0
    col_var[-e:] = 0
    busbar_locations = np.where((col_var < threshold)
                                & (col_var < np.roll(col_var, 1))
                                & (dip_strength > 0.07)
                                & (col_var < np.roll(col_var, -1)))[0]

    # ignore anything 0.2 higher than lowest
    # - for multi cells, impure areas can cause low variation, so skip this test
    if len(busbar_locations) > 0 and features['_alg_mode'] != 'multi cell':
        busbar_locations = busbar_locations[(
            col_var[busbar_locations] - col_var[busbar_locations].min()) < 0.2]
        busbar_locations.sort()

    # group anything very close together
    if len(busbar_locations) > 0:
        diffs = busbar_locations[1:] - busbar_locations[:-1]
        for i in np.where(diffs < 10)[0]:
            middle = (busbar_locations[i] + busbar_locations[i + 1]) // 2
            busbar_locations[i] = middle
            busbar_locations[i + 1] = -1
        busbar_locations = busbar_locations[busbar_locations >= 0]

    if False:
        print busbar_locations, pass_symmetry_test(
            busbar_locations, debug=True), parameters.CELL_BB_MIN

        ImageViewer(im)
        plt.figure()
        plt.plot(col_var)
        plt.plot(dip_strength)
        plt.vlines(busbar_locations, ymin=0, ymax=1)
        plt.show()
        # sys.exit()

    if pass_symmetry_test(busbar_locations):
        features['bb_detection_mode'] = 1

    if features['bb_detection_mode'] is None:
        # attempt 2: local minimums of intensity profile
        if im.shape[0] > 600:
            sigma = 5
            offset = 10
        else:
            sigma = 2.5
            offset = 5
        col_mean = ndimage.gaussian_filter1d(im.mean(axis=0),
                                             sigma=sigma,
                                             mode="constant")
        col_mean /= col_mean.max()
        dip_strength = np.minimum(np.roll(col_mean, offset),
                                  np.roll(col_mean, -offset)) - col_mean
        dip_strength[dip_strength < 0] = 0
        p10 = int(len(dip_strength) * 0.09)
        dip_strength[:p10] = 0
        dip_strength[-p10:] = 0

        dip_thresh = dip_strength.std() * parameters.CELL_BB_MODE_2_STD

        busbar_locations = np.where((col_mean < np.roll(col_mean, 1))
                                    & (col_mean < np.roll(col_mean, -1)) &
                                    (((dip_strength > 0.05) & (col_mean < 0.6))
                                     | (dip_strength > dip_thresh)))[0]

        # ignore anything too close to edge
        busbar_locations = busbar_locations[busbar_locations > 20]
        busbar_locations = busbar_locations[busbar_locations < w - 20]
        busbar_locations.sort()

        if False:
            print busbar_locations, dip_thresh, pass_symmetry_test(
                busbar_locations)
            ImageViewer(im)
            plt.figure()
            plt.plot(col_mean)
            plt.plot(dip_strength)
            plt.plot(busbar_locations, col_mean[busbar_locations], 'o')
            plt.show()
            # sys.exit()

        if pass_symmetry_test(busbar_locations):
            features['bb_detection_mode'] = 2

    if features['bb_detection_mode'] is None:
        # attempt 3:  gradient of intensity profile
        dips = np.abs(
            np.minimum(np.roll(col_mean, shift=15), np.roll(
                col_mean, shift=-15)) - col_mean)
        e = len(col_mean) // 10
        dips[:e] = 0
        dips[-e:] = 0

        # find strongest dips
        local_maxs = np.where(
            np.logical_and(dips > np.roll(dips, shift=1),
                           dips > np.roll(dips, shift=-1)))[0]

        # if dip near middle -> 3 busbars, else 2
        w2 = len(dips) // 2
        if dips[w2 - 10:w2 + 10].max() > 0.02:
            num_bb = 3
        else:
            num_bb = 2
        busbar_locations = local_maxs[np.argsort(dips[local_maxs])[-num_bb:]]
        busbar_locations.sort()

        if False:
            print busbar_locations
            ImageViewer(im)
            plt.figure()
            plt.plot(col_mean)
            plt.plot(dips)
            plt.plot(busbar_locations, col_mean[busbar_locations], 'o')
            plt.show()
            sys.exit()

        if pass_symmetry_test(busbar_locations):
            features['bb_detection_mode'] = 3

    if features['bb_detection_mode'] is None:
        # attempt 4
        # - peaks in interior of
        e = len(col_mean) // 10
        peaks = col_mean - np.maximum(np.roll(col_mean, shift=15),
                                      np.roll(col_mean, shift=-15))
        local_maxs = np.where(
            np.logical_and(peaks > np.roll(peaks, shift=1),
                           peaks > np.roll(peaks, shift=-1)))[0]
        local_maxs = local_maxs[local_maxs > e]
        local_maxs = local_maxs[local_maxs < len(col_mean) - e]
        busbar_locations = local_maxs[peaks[local_maxs] > 0.04]

        if False:
            print busbar_locations
            ImageViewer(im)
            plt.figure()
            plt.plot(peaks)
            plt.plot(local_maxs, peaks[local_maxs], 'o')
            plt.figure()
            plt.plot(col_mean)
            plt.plot(busbar_locations, col_mean[busbar_locations], 'o')
            plt.show()

        if pass_symmetry_test(busbar_locations):
            features['bb_detection_mode'] = 4

    if features['bb_detection_mode'] is None:
        if False:
            plt.figure()
            plt.plot(im.mean(axis=0))
            plt.show()

        raise MissingBusbarsException

    if False:
        print "Number of busbars: %d" % (len(busbar_locations))
        print features['bb_detection_mode']
        mask = np.zeros_like(im, np.uint8)
        mask[:, busbar_locations] = 1
        rgb = ip.overlay_mask(im, mask)
        view = ImageViewer(rgb)
        view.show()

    # fine tune: see if we get a better fit by shifting left or right
    bw_max = int(w * 0.025)
    bl = busbar_locations[0]
    bb_profile = np.median(im[:, bl - bw_max:bl + bw_max + 1], axis=0)
    bb_profile -= bb_profile.min()
    bb_profile /= bb_profile.max()
    for bb in range(1, len(busbar_locations)):
        bl = busbar_locations[bb]
        bb_profileX = np.median(im[:, bl - bw_max:bl + bw_max + 1], axis=0)
        bb_profileX -= bb_profileX.min()
        bb_profileX /= bb_profileX.max()

        # find offset
        shifts = np.arange(-5, 6)
        differences = [
            np.abs(bb_profile - np.roll(bb_profileX, s)).mean() for s in shifts
        ]
        best_shift = shifts[np.argmin(differences)]
        busbar_locations[bb] -= best_shift

        if False:
            print best_shift
            plt.figure()
            plt.plot(shifts, differences)
            plt.figure()
            plt.plot(bb_profile)
            plt.plot(bb_profileX)
            plt.plot(np.roll(bb_profileX, best_shift), '--')
            plt.show()

    features['_busbar_cols'] = busbar_locations

    ################
    # BUSBAR WIDTH #
    ################

    # create median busbar
    bb_stack = np.empty((h, bw_max * 2 + 1, len(busbar_locations)), np.float32)
    for e, bl in enumerate(busbar_locations):
        bb_stack[:, :, e] = im[:, bl - bw_max:bl + bw_max + 1]
    bb_template = np.median(bb_stack, axis=2)
    features['_bb_template'] = bb_template

    if False:
        plt.figure()
        for e, bl in enumerate(busbar_locations):
            plt.plot(im[:, bl - bw_max:bl + bw_max + 1].mean(axis=0))

        view = ImageViewer(bb_stack[:, :, 2])
        ImageViewer(bb_template)
        ImageViewer(im)
        view.show()
        sys.exit()

    if int(parameters.CELL_BB_WIDTH_MODE) == 1:
        # base on column variation
        im_peaks = bb_template[features['_peak_row_nums'], :]
        im_fingers = bb_template[features['_finger_row_nums'][:-1], :]
        diff = (im_peaks - im_fingers)
        col_var = np.median(diff, axis=0)  # diff.mean(axis=0)
        col_var /= min(col_var[0], col_var[-1])

        low_var = np.where(col_var < 0.8)[0]
        if len(low_var) < 2:
            raise MissingBusbarsException
        left, right = np.where(col_var < 0.8)[0][[0, -1]]

        if False:
            print col_var[(left + right) // 2]
            ImageViewer(diff)
            ImageViewer(bb_template)
            plt.figure()
            plt.plot(col_var)
            plt.vlines([left, right], 0, 1)
            plt.show()

        if col_var[(left + right) // 2] >= 0.8:
            raise MissingBusbarsException
    elif int(parameters.CELL_BB_WIDTH_MODE) == 2:
        # base on column intensity
        cols = bb_template.mean(axis=0)
        cols /= cols.max()
        dark_area = np.where(cols < parameters.CELL_BB_THRESH)[0]
        if len(dark_area) < 5:
            raise MissingBusbarsException
        left, right = dark_area[[0, -1]]

        if False:
            # ImageViewer(diff)
            ImageViewer(bb_template)
            plt.figure()
            plt.plot(cols)
            plt.vlines([left, right], 0, 1)
            plt.show()
    elif int(parameters.CELL_BB_WIDTH_MODE) == 3:
        left = (bb_template.shape[1] // 2) - parameters.CELL_BB_WIDTH
        right = (bb_template.shape[1] // 2) + parameters.CELL_BB_WIDTH

        if False:
            print left, right
            cols = bb_template.mean(axis=0)
            cols /= cols.max()
            ImageViewer(bb_template)
            plt.figure()
            plt.plot(cols)
            plt.vlines([left, right], 0, 1)
            plt.show()
    else:
        print "ERROR: Unknown busbar detection mode"

    features['busbar_width'] = right - left

    # create a mask, and use it to insert 1's for H&V filtering
    busbar_mask = np.zeros_like(im, dtype=np.uint8)
    busbar_filled = np.zeros_like(im, dtype=np.bool)
    for e, bl in enumerate(busbar_locations):
        busbar_mask[:, bl - bw_max + left] = 1
        busbar_mask[:, bl - bw_max + right] = 1
        busbar_filled[:, bl - bw_max + left:bl - bw_max + right + 1] = True

    if False:
        print features['busbar_width']
        overlay = ip.overlay_mask(im, busbar_mask)
        view = ImageViewer(overlay)
        ImageViewer(busbar_filled)
        view.show()
        sys.exit()

    features['mask_busbar_edges'] = busbar_mask
    features['mask_busbar_filled'] = busbar_filled
def mono_cracks(features):
    struct = ndimage.generate_binary_structure(2, 1)
    im = features['im_norm']
    im_no_rows = features['im_no_fingers']
    h, w = im.shape

    # apply a filter that enhances thin dark lines
    smoothed = cv2.GaussianBlur(im_no_rows,
                                ksize=(0, 0),
                                sigmaX=1.0,
                                borderType=cv2.BORDER_REPLICATE)
    dark_lines = np.zeros(smoothed.shape, np.float32)
    pixel_ops.CrackEnhance2(smoothed, dark_lines)

    if False:
        view = ImageViewer(im_no_rows)
        ImageViewer(smoothed)
        ImageViewer(dark_lines)
        view.show()
        sys.exit()

    # ignore background
    r = features['wafer_radius']
    pixel_ops.ApplyThresholdGT_F32(features['im_center_dist_im'], dark_lines,
                                   r, 0)

    # HOUGH PARAMS
    LINE_THRESH = 0.07
    MIN_LINE_LEN = 0.017  # 0.025
    LINE_GAP = 4
    ANGLE_TOL = 15
    dark_lines_binary = (dark_lines > LINE_THRESH).astype(np.uint8)
    line_length = int(round(im.shape[0] * MIN_LINE_LEN))
    lines = probabilistic_hough(
        dark_lines_binary,
        threshold=20,
        line_length=line_length,
        line_gap=LINE_GAP,
        theta=np.deg2rad(np.r_[np.arange(45 - ANGLE_TOL, 45 + ANGLE_TOL + 1),
                               np.arange(135 - ANGLE_TOL, 135 + ANGLE_TOL +
                                         1)]))

    line_im = np.zeros_like(dark_lines)
    edge_dist = int(round(im.shape[0] * 0.035))
    coms = []
    middle_y, middle_x = features['wafer_middle_y'], features['wafer_middle_x']
    for line in lines:
        r0, c0, r1, c1 = line[0][1], line[0][0], line[1][1], line[1][0]
        rs, cs = draw.line(r0, c0, r1, c1)
        line_im[rs, cs] = 1
        coms.append(cs.mean())

        # connect to edge
        # first end
        rs_end1, cs_end1 = rs[:edge_dist], cs[:edge_dist]
        rs_ex1 = rs_end1 + (rs_end1[0] - rs_end1[-1])
        cs_ex1 = cs_end1 + (cs_end1[0] - cs_end1[-1])
        center_dists = np.sqrt((cs_ex1 - middle_x)**2 + (rs_ex1 - middle_y)**2)
        mask = ((rs_ex1 >= 0) & (rs_ex1 < h) & (cs_ex1 >= 0) & (cs_ex1 < w) &
                (center_dists < r))
        # make sure some pixels have been mask (i.e. outside of cell) and is dark or short
        # print im_no_rows[rs_ex1[mask], cs_ex1[mask]].mean(), mask.sum()
        if mask.sum() < len(rs_ex1) and (
                im_no_rows[rs_ex1[mask], cs_ex1[mask]].mean() < 0.45
                or mask.sum() < 9):
            line_im[rs_ex1[mask], cs_ex1[mask]] = 2

        # second end
        rs_end2, cs_end2 = rs[-edge_dist:], cs[-edge_dist:]
        rs_ex2 = rs_end2 + (rs_end2[-1] - rs_end2[0])
        cs_ex2 = cs_end2 + (cs_end2[-1] - cs_end2[0])
        center_dists = np.sqrt((cs_ex2 - middle_x)**2 + (rs_ex2 - middle_y)**2)
        mask = ((rs_ex2 >= 0) & (rs_ex2 < h) & (cs_ex2 >= 0) & (cs_ex2 < w) &
                (center_dists < r))
        # make sure some pixels have been mask (i.e. outside of cell) and is dark or short
        if mask.sum() < len(cs_ex2) and (
                im_no_rows[rs_ex2[mask], cs_ex2[mask]].mean() < 0.45
                or mask.sum() < 9):
            line_im[rs_ex2[mask], cs_ex2[mask]] = 2

    # join cracks that straddle BBs
    bb_cols = np.r_[0, features['_busbar_cols'], w - 1]
    for i1, i2 in itertools.combinations(range(len(lines)), 2):
        c1 = min(coms[i1], coms[i2])
        c2 = max(coms[i1], coms[i2])
        # make sure on different sides of BB (compare midpoints)
        straddle = False
        for bb in range(len(bb_cols) - 2):
            if bb_cols[bb] < c1 < bb_cols[bb + 1] < c2 < bb_cols[bb + 2]:
                straddle = True
                break
        if not straddle:
            continue

        # make sure similar orientation & offset
        def orientation(r0, c0, r1, c1):
            orien = math.degrees(math.atan2(r1 - r0, c1 - c0))
            if orien < 0: orien += 180
            if orien > 180: orien -= 180
            return orien

        or1 = orientation(lines[i1][0][1], lines[i1][0][0], lines[i1][1][1],
                          lines[i1][1][0])
        or2 = orientation(lines[i2][0][1], lines[i2][0][0], lines[i2][1][1],
                          lines[i2][1][0])
        or_diff = abs(or2 - or1)
        if or_diff > 5:
            continue

        # find line between closest points
        if coms[i1] < coms[i2]:
            line1, line2 = lines[i1], lines[i2]
        else:
            line1, line2 = lines[i2], lines[i1]
        joining_line = draw.line(line1[1][1], line1[1][0], line2[0][1],
                                 line2[0][0])
        if len(joining_line[0]) > 0.05 * w:
            continue
        line_im[joining_line] = 3

    if False:
        view = ImageViewer(im_no_rows)
        ImageViewer(dark_lines)
        ImageViewer(dark_lines_binary)
        ImageViewer(line_im)
        view.show()
        sys.exit()

    # clean up lines
    line_im = ndimage.binary_closing(line_im, struct,
                                     iterations=2).astype(np.uint8)
    ys, xs = np.where(line_im)
    pixel_ops.FastThin(line_im,
                       ys.copy().astype(np.int32),
                       xs.copy().astype(np.int32), ip.thinning_lut)
    line_im = ndimage.binary_dilation(line_im, struct)

    # filter by "strength", which is a combination of darkness and length
    ccs, num_ccs = ip.connected_components(line_im)
    pixel_ops.ApplyThresholdGT_F32(dark_lines, dark_lines, 0.3, 0.3)
    if False:
        strength = ndimage.sum(dark_lines,
                               labels=ccs,
                               index=np.arange(num_ccs + 1))
    else:
        # median will be more robust than mean (dark spots can lead to false positives)
        median_vals = ndimage.median(dark_lines,
                                     labels=ccs,
                                     index=np.arange(num_ccs + 1))
        lengths = np.zeros(num_ccs + 1, np.int32)
        pixel_ops.CCSizes(ccs, lengths)
        strength = median_vals * lengths
    strength[0] = 0
    strongest_candidates = np.argsort(strength)[::-1]
    strongest_candidates = strongest_candidates[
        strength[strongest_candidates] > parameters.CELL_CRACK_STRENGTH]

    if False:
        # print strongest_candidates
        strength_map = np.take(strength, ccs)
        candidates = (strength_map > parameters.CELL_CRACK_STRENGTH).astype(
            np.uint8)
        view = ImageViewer(strength_map)
        ImageViewer(ip.overlay_mask(im, candidates, 'r'))
        view.show()
        sys.exit()

    # filter each candidate using other features
    mask_cracks = np.zeros_like(im, np.uint8)
    locs = ndimage.find_objects(ccs)
    crack_count = 0
    for cc_label in strongest_candidates:
        e = cc_label - 1
        y1, y2 = max(0, locs[e][0].start - 3), min(h, locs[e][0].stop + 3)
        x1, x2 = max(0, locs[e][1].start - 3), min(w, locs[e][1].stop + 3)
        crack = (ccs[y1:y2, x1:x2] == cc_label)
        im_win = im[y1:y2, x1:x2]
        ys, xs = np.where(crack)
        ys = ys + y1
        xs = xs + x1
        com_y = ys.mean()
        com_x = xs.mean()

        if False:
            view = ImageViewer(crack)
            ImageViewer(im_win)
            view.show()

        # remove cracks along corner edge by checking if center of mass
        #  is same distance from middle as cell radius, and parallel to edge
        center_dists = np.sqrt((ys - (h / 2.0))**2 + (xs - (w / 2.0))**2)
        center_dist = center_dists.mean()
        dist_range = center_dists.max() - center_dists.min()
        r = features['wafer_radius'] - features['cell_edge_tb']
        center_ratio = (min(center_dist, r) / max(center_dist, r))
        if center_ratio > 0.98 and dist_range < 10:
            continue

        # keep cracks that have passed the tests & compute properties
        crack_count += 1
        mask_cracks[y1:y2, x1:x2][crack] = cz_wafer.DEFECT_CRACK

        if ('input_param_verbose' not in features
                or features['input_param_verbose'] or crack_count < 6):
            cz_wafer.crack_properties(ys, xs, crack_count, features,
                                      mask_cracks)

        if crack_count >= parameters.MAX_NUM_CRACKS:
            break

    if False:
        # view = ImageViewer(ccs)
        print "Crack pixels: ", mask_cracks.sum()
        view = ImageViewer(ccs)
        ImageViewer(ip.overlay_mask(im, mask_cracks, 'r'))
        view.show()
        sys.exit()

    features['mk_cracks_u8'] = mask_cracks
    features['defect_count'] = crack_count
    if crack_count > 0:
        features['defect_present'] = 1
    else:
        features['defect_present'] = 0

    # thin before finding length
    crack_skel = mask_cracks.copy()
    ys, xs = np.where(mask_cracks)
    pixel_ops.FastThin(crack_skel,
                       ys.copy().astype(np.int32),
                       xs.copy().astype(np.int32), ip.thinning_lut)
    features['defect_length'] = crack_skel.sum()
    features['_crack_skel'] = crack_skel