def normalise(cropped, features):
    # normalize using median of mid-point between fingers
    #  - don't want to use percentiles, as bright fingers act as outliers
    #    and reduce contrast everywhere else
    if '_peak_row_nums' in features:
        foreground = cropped[features['_peak_row_nums'], :]
        fg_mask = features['bl_cropped_u8'][features['_peak_row_nums'], :]
        fg_vals = foreground[fg_mask == 0]
        fg_median = np.median(fg_vals)
        norm = cropped / fg_median
    else:
        f = {}
        ip.histogram_percentiles(cropped, f)
        norm = cropped / f['hist_percentile_99']

    features['im_norm'] = norm

    # create an 8-bit display version
    display = cropped / features['hist_percentile_99.9']
    pixel_ops.ApplyThresholdGT_F32(display, display, 1.0, 1.0)
    features['im_cropped_u8'] = np.round(display * 255).astype(np.uint8)
def feature_extraction(im, features, skip_crop=False):
    t_start = timeit.default_timer()

    # rotation & cropping
    rotated = cropping.correct_stripe_rotation(im,
                                               features,
                                               already_cropped=skip_crop)
    cropped = cropping.crop_stripe(im,
                                   rotated,
                                   features,
                                   already_cropped=skip_crop)
    h, w = cropped.shape

    if False:
        view = ImageViewer(im)
        ImageViewer(cropped)
        view.show()

    features['_cropped_f32'] = cropped
    features['im_cropped_u16'] = cropped.astype(np.uint16)
    ip.histogram_percentiles(cropped, features)
    im_norm = cropped / features['hist_percentile_99']
    features['im_norm'] = im_norm
    pixel_ops.ApplyThresholdGT_F32(im_norm, im_norm, 1.0, 1.0)
    features['im_cropped_u8'] = np.round(im_norm * 255).astype(np.uint8)

    # TODO: finger/background mask
    features['bl_cropped_u8'] = np.zeros_like(im_norm, np.uint8)

    if False:
        view = ImageViewer(im)
        ImageViewer(features['im_cropped_u16'])
        ImageViewer(im_norm)
        view.show()

    if 'input_param_skip_features' in features and int(
            features['input_param_skip_features']) == 1:
        return

    features['_fingers_grid'] = False
    features['_busbar_cols'] = np.array([], np.int32)
    features['busbar_width'] = 0
    features['cell_edge_left'] = 10
    features['cell_edge_right'] = im_norm.shape[1] - 10
    features['mask_busbar_edges'] = np.zeros_like(im_norm, dtype=np.uint8)
    features['mask_busbar_filled'] = np.zeros_like(im_norm, dtype=np.uint8)
    features['wafer_middle_y'] = h // 2
    features['wafer_middle_x'] = w // 2
    features['wafer_radius'] = h
    features['_bright_area_thresh'] = 1
    features['cell_edge_tb'] = 0  # assume cropped already.
    cell.find_fingers(im_norm, features)
    cell.remove_cell_template(im_norm, features)

    # TODO: add background to mask
    features['bl_cropped_u8'] = np.zeros_like(im_norm, np.uint8)
    features['bl_cropped_u8'][features['_finger_row_nums'], :] = 4
    features['bl_cropped_u8'][
        im_norm < parameters.STRIPE_BACKGROUND_THRESH] = 1
    if False:
        view = ImageViewer(im_norm)
        ImageViewer(features['bl_cropped_u8'])
        view.show()

    if features['_cell_type'] == "multi":
        efficiency_analysis(features)
        cell.multi_cracks(features)
        features['ov_dislocations_u8'][:, :10] = 0
        features['ov_dislocations_u8'][:, -10:] = 0
    elif features['_cell_type'] == "mono":
        # calculate distance from wafer middle
        r, theta = np.empty_like(im_norm, np.float32), np.empty_like(
            im_norm, np.float32)
        pixel_ops.CenterDistance(r, theta, features['wafer_middle_y'],
                                 features['wafer_middle_x'])
        features['im_center_dist_im'] = r
        features['im_center_theta_im'] = theta

        cell.mono_cracks(features)
        mono_cell.dark_areas(features)
        mono_cell.dark_spots(features)
    else:
        print "ERROR -- Unknown mode: %s" % features['_cell_type']

    # compute runtime
    t_stop = timeit.default_timer()
    features['runtime'] = t_stop - t_start

    return
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()
Beispiel #4
0
def feature_extraction(im, features, crop=True, skip_features=False):
    h, w = im.shape

    if im.dtype != np.float32:
        im = im.astype(np.float32)

    if crop:
        # cropping
        rotation_corrected = block_rotate(im, features)
        cropped_u16 = block_crop(rotation_corrected, features)
        bounds = features['_crop_bounds']
    else:
        rotation_corrected = im
        cropped_u16 = im
        bounds = (0, w - 1, 0, h - 1)
        features['_crop_bounds'] = bounds
        features['crop_rotation'] = 0

    # get original coordinates of the block corners
    find_corners(im, features)
    features['_rotation_corrected'] = rotation_corrected

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

    # normalisation
    vals = cropped_u16[::2, ::2].flat
    vals = np.sort(vals)
    min_val = vals[int(0.01 * vals.shape[0])]
    max_val = vals[int(0.99 * vals.shape[0])]
    features['norm_range'] = max_val - min_val
    features['norm_lower'] = min_val
    im_normed = (cropped_u16 - min_val) / (max_val - min_val)
    pixel_ops.ApplyThresholdLT_F32(im_normed, im_normed, 0.0, 0.0)

    cropped = im_normed
    croped_u8 = im_normed.copy()
    pixel_ops.ApplyThresholdGT_F32(croped_u8, croped_u8, 1.0, 1.0)
    features['im_cropped_u8'] = (croped_u8 * 255).astype(np.uint8)
    features['im_cropped_u16'] = cropped_u16.astype(np.uint16)

    if skip_features or ('input_param_skip_features' in features
                         and int(features['input_param_skip_features']) == 1):
        return

    if False:
        view = ImageViewer(im)
        ImageViewer(cropped, vmin=0, vmax=1)
        view.show()
        sys.exit()

    # compute some row/column percentiles
    col_sorted = np.sort(cropped[::4, :], axis=0)
    features['_col_90'] = np.ascontiguousarray(
        col_sorted[int(round(0.9 * 0.25 * cropped.shape[0])), :])
    features['_col_60'] = np.ascontiguousarray(
        col_sorted[int(round(0.6 * 0.25 * cropped.shape[0])), :])
    row_sorted = np.sort(cropped[:, ::4], axis=1)
    features['_row_90'] = np.ascontiguousarray(
        row_sorted[:, int(round(0.9 * 0.25 * cropped.shape[1]))])

    # background
    background = block_background(cropped, features)

    # foreground
    foreground = block_foreground(cropped, features)

    # normalise background
    background /= background.max()

    # calculate metrics
    robust_dislocations(cropped, background, features)

    # dislocation area
    DIS_THRESH = 0.3
    dislocation_area = (
        pixel_ops.CountThresholdGT_F32(foreground, DIS_THRESH) /
        float(foreground.shape[0] * foreground.shape[1]))
    impure_area = 1 - (pixel_ops.CountThresholdGT_F32(background, 0.5) /
                       float(foreground.shape[0] * foreground.shape[1]))

    # edge width
    l4 = background.shape[1] // 4
    profile = background[:, l4:-l4].mean(axis=1)
    fg = np.where(profile > parameters.BRICK_EDGE_THRESH)[0]
    if len(fg) > 0:
        left_width, right = fg[[0, -1]]
        right_width = len(profile) - right - 1
        edge_width = max(left_width, right_width)
        if edge_width < 0.05 * len(profile):
            edge_width = 0
    else:
        edge_width = 100
    features['edge_width'] = edge_width

    if False:
        print edge_width
        ImageViewer(cropped)
        plt.figure()
        plt.plot(profile)
        plt.show()

    if False:
        dislocations = np.zeros_like(foreground, dtype=np.uint8)
        pixel_ops.ApplyThresholdGT_F32_U8(foreground, dislocations, DIS_THRESH,
                                          1)

        print features['defect_robust_area_fraction'], impure_area

        view = ImageViewer(im)
        ImageViewer(dislocations)
        ImageViewer(foreground)
        ImageViewer(background, vmin=0, vmax=1)
        view.show()
        # sys.exit()

    imp_cutoff = 0.55
    pixel_ops.ApplyThresholdGT_F32(background, background, imp_cutoff,
                                   imp_cutoff)
    background /= imp_cutoff
    background = np.log10(2 - background)

    dis_cutoff = 0.1
    foreground -= dis_cutoff
    foreground = np.clip(foreground, 0, 1)
    foreground *= 0.5

    features['ov_impure_u8'] = (background * 255).astype(np.uint8)
    features['ov_defects_u8'] = (foreground * 255).astype(np.uint8)
    features['_bounds'] = bounds
    pixel_ops.ClipImage(im_normed, 0, 1)
    features['dislocation_area_fraction'] = dislocation_area
    features['impure_area_fraction'] = impure_area

    return features
def firing_defects(features):
    im = features['im_norm']
    finger_rows = features['_finger_row_nums']
    # im_fingers = features['_finger_im']
    im_smooth = cv2.GaussianBlur(im, ksize=(0, 0), sigmaX=1)
    im_fingers = im_smooth[finger_rows, :]

    if False:
        view = ImageViewer(im)
        # view = ImageViewer(im_smooth)
        ImageViewer(im_fingers)
        view.show()
        sys.exit()

    # find depth of local minimums
    S = 5
    dips = np.minimum(np.roll(im_fingers, S, axis=1), np.roll(im_fingers, -S, axis=1)) - im_fingers
    pixel_ops.ApplyThresholdLT_F32(dips, dips, 0, 0)
    dips[:, features['mask_busbar_filled'][0, :]] = 0
    dips[:, :features['cell_edge_left']] = 0
    dips[:, features['cell_edge_right']:] = 0

    # TODO: add upper bound as well
    locs = ((im_fingers < np.roll(im_fingers, 1, axis=1)) &
            (im_fingers < np.roll(im_fingers, -1, axis=1)) &
            (dips > 0.02) & (dips < 0.05)).astype(np.float32)

    # count num dips in a region. ignore areas with only 1
    w = im.shape[1] // 10
    weights = np.ones(w, np.int32)
    local_count = ndimage.convolve1d(locs, weights, axis=1, mode="constant")
    dips_filtered = dips.copy()
    dips_filtered[local_count <= 1] = 0

    if False:
        view = ImageViewer(im)
        ImageViewer(dips)
        # ImageViewer(locs)
        # ImageViewer(local_count)
        # ImageViewer(dips_filtered)
        view.show()

    im_dots = cv2.GaussianBlur(dips_filtered, ksize=(0, 0), sigmaX=10, sigmaY=2)
    # im_dots -= 0.001

    if False:
        view = ImageViewer(dips_filtered)
        view = ImageViewer(im_dots)
        view.show()

    splotch = np.empty_like(im)
    pixel_ops.ExpandFingers(splotch, im_dots, finger_rows)
    splotch = ip.fast_smooth(splotch, sigma=10)
    features["_firing"] = splotch.copy()
    splotch *= 100.0 * (1.0 + parameters.FIRING_SENSITIVITY)
    pixel_ops.ClipImage(splotch, 0.0, 1.0)
    pixel_ops.ApplyThresholdGT_F32(features['im_center_dist_im'],
                                   splotch, features['wafer_radius'], 0)
    features["ov_splotches_u8"] = (splotch * 255).astype(np.uint8)

    # metrics
    if splotch.max() >= 0.2:
        features['firing_area_strength'] = splotch.mean() * 100
        mask_firing = (splotch > 0.2).astype(np.uint8)
        im_pl = features['_cropped_f32']
        firingPL = pixel_ops.MaskMean_F32(im_pl, mask_firing, 1)
        goodPL = pixel_ops.MaskMean_F32(im_pl, mask_firing, 0)
        features['firing_area_mean_PL'] = firingPL
        features['firing_area_fraction'] = mask_firing.mean()
        features['firing_area_PL_intensity_ratio'] = firingPL / max(1, goodPL)
    else:
        features['firing_area_strength'] = 0.0
        features['firing_area_mean_PL'] = 0.0
        features['firing_area_fraction'] = 0.0
        features['firing_area_PL_intensity_ratio'] = 0.0

    if False:
        print features['firing_area_strength']
        view = ImageViewer(im)
        ImageViewer(splotch, vmin=0, vmax=1)
        ImageViewer(mask_firing)
        f2 = {'im_cropped_u8': features['im_cropped_u8'],
              'ov_splotches_u8': features['ov_splotches_u8']}
        ImageViewer(create_overlay(f2))
        view.show()
        sys.exit()
def dark_areas(features):
    # dark areas (not dark spots)
    im = features['im_norm']
    h, w = im.shape
    row_nums = features['_peak_row_nums']
    cell_mask = features['bl_cropped_u8'][row_nums, :]
    bb_locs = features['_busbar_cols']

    # fill edges & corners
    foreground = im[row_nums].copy()
    edge = features['cell_edge_tb']
    rr, cc = draw.circle_perimeter(features['wafer_middle_y'], features['wafer_middle_x'],
                                   int(round(features['wafer_radius'])) - edge)
    mask = (cc >= 0) & (cc < w) & np.in1d(rr, row_nums)
    lut = np.zeros(h, np.int32)
    lut[row_nums] = np.arange(len(row_nums))
    rr = rr[mask]
    cc = cc[mask]
    rr = np.take(lut, rr)
    pixel_ops.FillCorners(foreground, rr.astype(np.int32), cc.astype(np.int32))
    foreground[:, :edge] = np.c_[foreground[:, edge]]
    foreground[:, -edge:] = np.c_[foreground[:, -edge]]

    # create background
    # 1. make monotonic between edges & BBs
    mono_lr = np.empty_like(foreground)
    mono_rl = np.empty_like(foreground)
    pixel_ops.MakeMonotonicBBs(foreground, mono_lr, bb_locs)
    pixel_ops.MakeMonotonicBBs(np.ascontiguousarray(foreground[:, ::-1]), mono_rl,
                               np.ascontiguousarray(w - bb_locs[::-1]))
    mono_rl = mono_rl[:, ::-1]
    mono = np.minimum(mono_lr, mono_rl)
    background = mono
    da1 = background - foreground

    # fill BBs and flatten
    background2 = background.copy()
    pixel_ops.InterpolateBBs(background2, features['_busbar_cols'], features['busbar_width'])
    cols = background2.mean(axis=0)
    background2 -= np.r_[cols]
    rows = background2.mean(axis=1)
    background2 -= np.c_[rows]

    da2 = -1 * background2
    pixel_ops.ApplyThresholdLT_F32(da2, da2, 0.0, 0.0)

    if False:
        view = ImageViewer(foreground)
        ImageViewer(background)
        ImageViewer(background2)
        ImageViewer(da1)
        ImageViewer(da2)
        ImageViewer(da1 + da2)
        # plt.figure()
        # for x in range(0, background.shape[1], 50):
        #    plt.plot(background[:, x], label="Col %d"%(x))
        # plt.legend()
        view.show()
        sys.exit()

    dark_areas = da1 + da2
    dark_areas -= (0.1 - parameters.DARK_AREA_SENSITIVITY)
    dark_areas[(cell_mask == 1) | (cell_mask == 8)] = 0  # corners
    pixel_ops.ClipImage(dark_areas, 0.0, 1.0)
    dark_areas_full = np.empty_like(im)
    pixel_ops.ExpandFingers(dark_areas_full, dark_areas, features['_peak_row_nums'])

    if False:
        pixel_ops.ApplyThresholdGT_F32(features['im_center_dist_im'], dark_areas_full,
                                       features['wafer_radius'], 0)
        print features['wafer_radius']
        view = ImageViewer(dark_areas)
        ImageViewer(dark_areas_full)
        ImageViewer(features['im_center_dist_im'])
        view.show()

    dark_areas_full = cv2.GaussianBlur(dark_areas_full, ksize=(0, 0), sigmaX=1)

    # metrics
    mask_dark = (dark_areas_full > 0.2).astype(np.uint8)
    features['dark_area_strength'] = dark_areas_full.mean() * 100
    im_pl = features['_cropped_f32']
    darkPL = pixel_ops.MaskMean_F32(im_pl, mask_dark, 1)
    brightPL = pixel_ops.MaskMean_F32(im_pl, mask_dark, 0)
    features['dark_area_mean_PL'] = darkPL
    features['dark_area_fraction'] = mask_dark.mean()
    features['dark_area_PL_intensity_ratio'] = brightPL / max(1, darkPL)

    features['ov_dark_areas_u8'] = (dark_areas_full * 255).astype(np.uint8)

    if False:
        print features['dark_area_fraction'], features['dark_area_strength']
        # plt.figure()
        # plt.plot(cols)
        # plt.plot(cols_mono)
        view = ImageViewer(im)
        ImageViewer(foreground)
        ImageViewer(dark_areas)
        ImageViewer(mask_dark)
        ImageViewer(dark_areas_full)
        view.show()
def bright_areas(features):
    im = features['im_norm']
    row_nums = features['_finger_row_nums']
    im_fingers = features['im_norm'][row_nums, :]

    vals = np.sort(im_fingers.flat)
    count = vals.shape[0]
    p01 = vals[int(round(0.01 * count))]
    p95 = vals[int(round(0.95 * count))]
    p99 = vals[int(round(0.999 * count))]

    # find highest peak
    counts, edges = np.histogram(vals, bins=50, density=True)
    i_max = np.argmax(counts)
    val_mode = (edges[i_max] + edges[i_max + 1]) / 2.0

    if p99 - p95 > 0.2:
        # thresh 1: assuming a symmetric distribution, mode+spread on low ed
        thresh1 = val_mode + (val_mode - p01) * 2

        # thresh 2: look for a distribution that drops very low
        below_02 = np.where(counts[i_max:] < 0.025)[0]
        if len(below_02) > 0:
            i = i_max + below_02[0]
            thresh2 = (edges[i] + edges[i + 1]) / 2.0
            thresh = min(thresh1, thresh2)
        else:
            thresh = thresh1
    else:
        thresh = im_fingers.max()

    thresh -= parameters.BRIGHT_AREA_MULTI_SENSITIVITY

    bright_areas = im_fingers - thresh
    bright_areas /= 0.5
    pixel_ops.ApplyThresholdLT_F32(bright_areas, bright_areas, 0.0, 0.0)
    pixel_ops.ApplyThresholdGT_F32(bright_areas, bright_areas, 1.0, 1.0)

    # create a full size image of bright lines
    bright_lines_full = np.zeros_like(im)
    bright_lines_full[row_nums, :] = bright_areas
    bright_lines_full = cv2.GaussianBlur(bright_lines_full, sigmaX=1, sigmaY=2, ksize=(0, 0))
    if 'ov_bright_area_u8' in features:
        features['ov_bright_area_u8'] = np.maximum((bright_lines_full * 255).astype(np.uint8),
                                                   features['ov_bright_area_u8'])
    else:
        features['ov_bright_area_u8'] = (bright_lines_full * 255).astype(np.uint8)
    features['bright_area_strength'] = bright_areas.mean() * 100
    features['bright_area_fraction'] = (pixel_ops.CountThresholdGT_F32(bright_areas, 0.1)) / float(
        im.shape[0] * im.shape[1])
    features['_bright_area_thresh'] = thresh

    if False:
        print features['bright_area_strength']
        print pixel_ops.CountThresholdGT_F32(bright_areas, 0.1) * 100 / float(im.shape[0] * im.shape[1])
        print features['bright_area_fraction']
        view = ImageViewer(im)
        ImageViewer(im_fingers)
        ImageViewer(bright_areas)
        plt.figure()
        plt.hist(im_fingers.flatten(), bins=50, normed=True)
        plt.plot((edges[:-1] + edges[1:]) / 2.0, counts)
        plt.vlines(thresh, 0, counts.max())
        view.show()
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