def bright_lines(features):
    im = features['im_no_fingers']
    h, w = im.shape
    if 'finger_period_row' in features:
        rh = int(round(features['finger_period_row']))
        cw = int(round(features['finger_period_col']))
    else:
        rh = int(round(features['finger_period']))
        cw = int(round(features['finger_period']))

    f_v = im - np.maximum(np.roll(im, shift=2 * cw, axis=1),
                          np.roll(im, shift=-2 * cw, axis=1))
    pixel_ops.ApplyThresholdLT_F32(f_v, f_v, 0.0, 0.0)

    # filter
    mask = (f_v > 0.02).astype(np.uint8)
    min_size = 0.0005 * h * w
    ip.remove_small_ccs(mask, min_size)
    f_v[mask == 0] = 0
    # features['_f_v'] = f_v.copy()

    f_h = im - np.maximum(np.roll(im, shift=2 * rh, axis=0),
                          np.roll(im, shift=-2 * rh, axis=0))
    pixel_ops.ApplyThresholdLT_F32(f_h, f_h, 0.0, 0.0)

    # filter
    mask = (f_h > 0.02).astype(np.uint8)
    min_size = 0.0005 * h * w
    ip.remove_small_ccs(mask, min_size)
    f_h[mask == 0] = 0
    # features['_f_h'] = f_h.copy()

    # normalize
    f_h /= 0.3
    f_v /= 0.3

    pixel_ops.ClipImage(f_h, 0.0, 1.0)
    pixel_ops.ClipImage(f_v, 0.0, 1.0)
    features['ov_lines_horizontal_u8'] = (f_h * 255).astype(np.uint8)
    features['ov_lines_vertical_u8'] = (f_v * 255).astype(np.uint8)

    features['bright_lines_horizontal'] = f_h.mean() * 100
    features['bright_lines_vertical'] = f_v.mean() * 100

    if False:
        view = ImageViewer(im)
        ImageViewer(f_v)
        ImageViewer(f_h)
        view.show()
        sys.exit()
def feature_extraction(im, features, skip_features=False):
    # median filter to remove noise
    im = cv2.medianBlur(im, 3)
    h, w = im.shape

    # normalize
    hist_features = {}
    ip.histogram_percentiles(im, hist_features)
    norm = im / hist_features['hist_percentile_99.9']
    pixel_ops.ClipImage(norm, 0, 1)

    # automatically determine if square or round
    is_round = True
    crop_props = None
    try:
        # try cropping using wafer alg
        # im = np.ascontiguousarray(im[::-1, :])
        crop_props = cropping.crop_wafer_cz(im, create_mask=True, output_error=False)

        # if round, rotation will likely be high
        if abs(crop_props['estimated_rotation']) < 5:
            # make sure most of foreground mask is actually foreground
            f = {}
            ip.histogram_percentiles(im, f)
            norm = im / f['hist_percentile_99.9']
            coverage = (norm[crop_props['mask'] == 0] > 0.5).mean()
            if coverage > 0.97:
                is_round = False
            if False:
                print coverage
                view = ImageViewer(norm)
                ImageViewer(crop_props['mask'])
                view.show()
    except:
        pass

    if False:
        print "Is round:", is_round
        view = ImageViewer(im)
        view.show()

    if is_round:
        # find center and radius
        find_slug(norm, features)
    else:
        # pre-crop
        cropped = cropping.correct_rotation(im, crop_props, pad=False, border_erode=parameters.BORDER_ERODE_CZ,
                                            fix_chamfer=False)
        features['bl_uncropped_u8'] = crop_props['mask']
        features['bl_cropped_u8'] = crop_props['mask']
        features['center_y'] = crop_props['center'][0]
        features['center_x'] = crop_props['center'][1]
        features['radius'] = crop_props['radius']
        features['corners'] = crop_props['corners']
        features['center'] = crop_props['center']
        features['crop_rotation'] = 0

        if False:
            view = ImageViewer(im)
            ImageViewer(cropped)
            ImageViewer(crop_props['mask'])
            view.show()

        im = np.ascontiguousarray(cropped, dtype=im.dtype)
        norm = im / hist_features['hist_percentile_99.9']

    # set corners (note: this is for consistency. in current implementation there is no cropping)
    features['corner_tl_x'] = 0
    features['corner_tl_y'] = 0
    features['corner_tr_x'] = w - 1
    features['corner_tr_y'] = 0
    features['corner_br_x'] = w - 1
    features['corner_br_y'] = h - 1
    features['corner_bl_x'] = 0
    features['corner_bl_y'] = h - 1

    if False:
        view = ImageViewer(norm)
        ImageViewer(features['bl_uncropped_u8'])
        view.show()

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

    # PL metrics
    hist = ip.histogram_percentiles(im, features, features['center_y'], features['center_x'],
                                    features['radius'])
    if False:
        # features['radius'] = features['radius']
        rgb = create_overlay(im, features)
        # ImageViewer(im)
        ImageViewer(rgb)
        plt.figure()
        plt.plot(hist)
        plt.show()

    # rds
    rds(norm, features)

    # dark/bright corners
    radial_profile(norm, features)

    # rings
    ring_strength(norm, features)
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.º 4
0
def plir2(im_sp, im_lp, features, spline_plir, spline_sp):
    pixel_ops.ApplyThresholdLT_F32(im_sp, im_sp, 1.0, 1.0)
    pixel_ops.ApplyThresholdLT_F32(im_lp, im_lp, 1.0, 1.0)
    if im_sp.shape != im_lp.shape:
        print im_sp.shape, im_lp.shape
        assert False

    im_sp = im_sp.astype(np.float64)
    im_lp = im_lp.astype(np.float64)

    if False:
        view = ImageViewer(im_sp)
        ImageViewer(im_lp)
        view.show()
        sys.exit()

    # register short and long pass images
    def register(signal1, signal2, debug=False):
        bandpass1 = ndimage.gaussian_filter1d(
            signal1, sigma=10) - ndimage.gaussian_filter1d(signal1, sigma=3)
        bandpass2 = ndimage.gaussian_filter1d(
            signal2, sigma=10) - ndimage.gaussian_filter1d(signal2, sigma=3)
        offsets = range(-10, 11)
        fits = [(np.roll(bandpass1, shift=s) * bandpass2).mean()
                for s in offsets]
        optimal_shift = offsets[np.argmax(fits)]

        if debug:
            plt.figure()
            plt.plot(signal1)
            plt.plot(signal2)
            plt.figure()
            plt.plot(offsets, fits)
            plt.figure()
            plt.plot(np.roll(bandpass1, shift=optimal_shift))
            plt.plot(bandpass2)
            plt.show()

        return optimal_shift

    c = np.argmax(im_sp.mean(axis=0))
    profile_sp = im_sp[:, c - 10:c + 11].mean(axis=1)
    profile_lp = im_lp[:, c - 10:c + 11].mean(axis=1)
    shift_v = register(profile_sp, profile_lp, debug=False)
    if False:
        print c, shift_v
        view = ImageViewer(im_lp)
        ImageViewer(im_sp)
        ImageViewer(np.roll(im_sp, shift=shift_v, axis=0))
        view.show()
        sys.exit()
    im_sp = np.roll(im_sp, shift=shift_v, axis=0)

    # compute PL (ratio of LP to SP)
    plir = im_lp / im_sp

    if False:
        t = stats.scoreatpercentile(plir, per=99)
        print plir.min(), t, plir.max()
        plir[plir > t] = t
        view = ImageViewer(im_lp)
        ImageViewer(im_sp)
        ImageViewer(plir)
        view.show()
        sys.exit()

    # Get cropping and rotation parameters (based on short pass)
    # normalisation - find the 99th percentile
    vals = im_sp[::4, ::4].flat
    vals = np.sort(vals)
    min_val = vals[int(0.025 * vals.shape[0])]
    max_val = vals[int(0.975 * vals.shape[0])]
    features['norm_range'] = max_val - min_val
    features['norm_lower'] = min_val
    im_normed_temp = (im_sp - min_val) / (max_val - min_val)
    rotated_temp = block_rotate(im_normed_temp, features)

    # get crop bounds crop
    block_crop(rotated_temp, features)
    rotation = features['crop_rotation']
    x1, x2, y1, y2 = features['_crop_bounds']

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

    if False:
        cropped_temp = rotated_temp[y1:y2, x1:x2]
        view = ImageViewer(im_sp)
        ImageViewer(rotated_temp)
        ImageViewer(cropped_temp)
        view.show()

    # correct and crop plir image (using params from short pass)
    if abs(rotation) > 0.01:
        h, w = plir.shape
        rot_mat = cv2.getRotationMatrix2D((w // 2, h // 2), rotation, 1.0)
        plir_rotated = cv2.warpAffine(plir,
                                      rot_mat, (w, h),
                                      flags=cv2.INTER_LINEAR,
                                      borderMode=cv2.BORDER_REPLICATE)
        sp_rotated = cv2.warpAffine(im_sp,
                                    rot_mat, (w, h),
                                    flags=cv2.INTER_LINEAR,
                                    borderMode=cv2.BORDER_REPLICATE)
        lp_rotated = cv2.warpAffine(im_lp,
                                    rot_mat, (w, h),
                                    flags=cv2.INTER_LINEAR,
                                    borderMode=cv2.BORDER_REPLICATE)
    else:
        plir_rotated = plir
        sp_rotated = im_sp
        lp_rotated = im_lp

    features['marker_loc'] = find_marker(sp_rotated[y1:y2, x1:x2])

    plir_cropped = plir_rotated[y1:y2, x1:x2]
    plir_cropped = np.ascontiguousarray(plir_cropped)

    # continue with plir
    _, upper = ip.get_percentile(plir_cropped, 0.005)
    pixel_ops.ClipImageF64(plir_cropped, 0, upper)

    if False:
        _, upper = ip.get_percentile(plir, 0.005)
        pixel_ops.ClipImageF64(plir, 0, upper)
        print plir_cropped.min(), plir_cropped.dtype
        view = ImageViewer(plir)
        ImageViewer(plir_cropped)
        view.show()
        sys.exit()

    tau_bulk_plir = interpolate.splev(plir_cropped.flatten(),
                                      spline_plir).reshape(plir_cropped.shape)
    _, upper = ip.get_percentile(tau_bulk_plir, 0.0001)
    pixel_ops.ClipImageF64(tau_bulk_plir, 0.1, upper)

    if False:
        ImageViewer(im_sp)
        ImageViewer(im_lp)
        plt.figure()
        plt.imshow(plir_cropped, cmap=cmaps.viridis)
        plt.colorbar()
        plt.figure()
        plt.imshow(tau_bulk_plir, cmap=cmaps.viridis)
        plt.colorbar()
        plt.show()
        sys.exit()

    # dislocation and impure processing for PL image
    cropped_sp = sp_rotated[y1:y2, x1:x2]
    cropped_lp = lp_rotated[y1:y2, x1:x2]

    # compute c-values
    c_vals = fit_c_vals(cropped_sp, tau_bulk_plir, spline_sp)
    doping = ndimage.gaussian_filter1d(c_vals, sigma=2, mode="reflect")

    if False:
        ImageViewer(cropped_sp)
        plt.figure()
        plt.plot(c_vals)
        plt.plot(doping)
        plt.show()

    features['_C_vals'] = doping.astype(np.float32)
    sp_dope = cropped_sp * np.r_[doping]
    sp_dope[sp_dope > spline_sp[0][-1]] = spline_sp[0][-1]
    tau_bulk_full = interpolate.splev(sp_dope.flatten(), spline_sp).astype(
        np.float32).reshape(cropped_sp.shape)

    # pixel_ops.ApplyThresholdLT_F32(tau_bulk_full, tau_bulk_full, 0.1, 0.1)
    _, upper_p = ip.get_percentile(tau_bulk_full, 0.0001)
    pixel_ops.ClipImage(tau_bulk_full, 0.1, upper_p)

    features['im_tau_bulk_f32'] = tau_bulk_full
    features['im_tau_bulk_u8'] = (ip.scale_image(tau_bulk_full) * 255).astype(
        np.uint8)
    features['im_cropped_sp_u8'] = (ip.scale_image(cropped_sp) * 255).astype(
        np.uint8)
    features['im_cropped_sp_u16'] = np.round(cropped_sp).astype(np.uint16)
    features['im_cropped_lp_u16'] = np.round(cropped_lp).astype(np.uint16)

    if False:
        if True:
            _, upper_p = ip.get_percentile(tau_bulk_full, 0.001)
            pixel_ops.ClipImage(tau_bulk_full, 0.1, upper_p)
        else:
            tau_bulk_full = np.log(tau_bulk_full)
        ImageViewer(cropped_sp)
        ImageViewer(sp_dope)
        ImageViewer(tau_bulk_plir)
        ImageViewer(tau_bulk_full)
        plt.figure()
        plt.plot(tau_bulk_plir.mean(axis=0))
        plt.plot(tau_bulk_full.mean(axis=0))
        if False:
            plt.figure()
            plt.plot(doping)
            plt.plot(c_vals)
        plt.show()

    return True
Exemplo n.º 5
0
def plir(im_sp, im_lp, im_pl, features, spline_plir, spline_plc):
    t_start = timeit.default_timer()

    pixel_ops.ApplyThresholdLT_F32(im_sp, im_sp, 1.0, 1.0)
    pixel_ops.ApplyThresholdLT_F32(im_lp, im_lp, 1.0, 1.0)
    pixel_ops.ApplyThresholdLT_F32(im_pl, im_pl, 1.0, 1.0)
    if im_sp.shape != im_lp.shape:
        print im_sp.shape, im_lp.shape
        assert False
    im_sp = im_sp.astype(np.float64)
    im_lp = im_lp.astype(np.float64)
    im_pl = im_pl.astype(np.float64)

    if False:
        view = ImageViewer(im_sp)
        ImageViewer(im_lp)
        ImageViewer(im_pl)
        view.show()
        sys.exit()

    # vertical registration
    c = np.argmax(im_sp.mean(axis=0))
    profile_sp = im_sp[:, c - 10:c + 11].mean(axis=1)
    profile_lp = im_lp[:, c - 10:c + 11].mean(axis=1)
    shift_v = register(profile_sp, profile_lp, debug=False)
    if False:
        print c, shift_v
        view = ImageViewer(im_lp)
        ImageViewer(im_sp)
        ImageViewer(np.roll(im_sp, shift=shift_v, axis=0))
        view.show()
        sys.exit()
    im_sp = np.roll(im_sp, shift=shift_v, axis=0)

    # compute plir (ratio of LP to SP)
    plir = im_lp / im_sp

    if False:
        t = stats.scoreatpercentile(plir, per=90)
        print plir.min(), t, plir.max()
        plir[plir > t] = t
        view = ImageViewer(im_lp)
        ImageViewer(im_sp)
        ImageViewer(plir)
        view.show()
        sys.exit()

    # Get cropping and rotation parameters (based on short pass)
    vals = im_sp[::2, ::2].flat
    vals = np.sort(vals)
    min_val = vals[int(0.025 * vals.shape[0])]
    max_val = vals[int(0.975 * vals.shape[0])]
    features['norm_range'] = max_val - min_val
    features['norm_lower'] = min_val
    im_normed_temp = (im_sp - min_val) / (max_val - min_val)
    rotated_temp = block_rotate(im_normed_temp, features)
    block_crop(rotated_temp, features)
    rotation = features['crop_rotation']
    x1, x2, y1, y2 = features['_crop_bounds']

    if False:
        cropped_temp = rotated_temp[y1:y2, x1:x2]
        view = ImageViewer(im_sp)
        ImageViewer(rotated_temp)
        ImageViewer(cropped_temp)
        view.show()

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

    # rotate all images
    if abs(rotation) > 0.01:
        h, w = plir.shape
        rot_mat = cv2.getRotationMatrix2D((w // 2, h // 2), rotation, 1.0)
        plir_rotated = cv2.warpAffine(plir,
                                      rot_mat, (w, h),
                                      flags=cv2.INTER_LINEAR,
                                      borderMode=cv2.BORDER_REPLICATE)
        sp_rotated = cv2.warpAffine(im_sp,
                                    rot_mat, (w, h),
                                    flags=cv2.INTER_LINEAR,
                                    borderMode=cv2.BORDER_REPLICATE)
        lp_rotated = cv2.warpAffine(im_lp,
                                    rot_mat, (w, h),
                                    flags=cv2.INTER_LINEAR,
                                    borderMode=cv2.BORDER_REPLICATE)
        h, w = im_pl.shape
        rot_mat = cv2.getRotationMatrix2D((w // 2, h // 2), rotation, 1.0)
        nf_rotated = cv2.warpAffine(im_pl,
                                    rot_mat, (w, h),
                                    flags=cv2.INTER_LINEAR,
                                    borderMode=cv2.BORDER_REPLICATE)
    else:
        plir_rotated = plir
        sp_rotated = im_sp
        lp_rotated = im_lp
        nf_rotated = im_pl

    # find marker location
    features['marker_loc'] = find_marker(sp_rotated[y1:y2, x1:x2])
    nf_to_sp_ratio = nf_rotated.shape[1] / float(sp_rotated.shape[1])
    features['marker_loc'] *= nf_to_sp_ratio

    # crop plir image
    cropped_plir = plir_rotated[y1:y2, x1:x2]
    cropped_plir = np.ascontiguousarray(cropped_plir)
    cropped_sp = sp_rotated[y1:y2, x1:x2]
    cropped_lp = lp_rotated[y1:y2, x1:x2]

    if False:
        _, upper = ip.get_percentile(cropped_plir, 0.005)
        pixel_ops.ClipImageF64(cropped_plir, 0, upper)
        print cropped_plir.min(), cropped_plir.dtype
        view = ImageViewer(plir)
        ImageViewer(cropped_plir)
        ImageViewer(cropped_sp)
        view.show()
        sys.exit()

    # convert plir image to bulk image
    tau_bulk_plir = interpolate.splev(cropped_plir.flatten(),
                                      spline_plir).reshape(cropped_plir.shape)
    _, upper = ip.get_percentile(tau_bulk_plir, 0.0001)
    pixel_ops.ClipImageF64(tau_bulk_plir, 0.1, upper)

    if False:
        ImageViewer(im_sp)
        ImageViewer(im_lp)
        plt.figure()
        plt.imshow(cropped_plir)
        plt.colorbar()
        plt.figure()
        plt.imshow(tau_bulk_plir)
        plt.colorbar()
        plt.show()
        sys.exit()

    # zoom bulk image to make the same size as NF
    if im_pl.shape != im_sp.shape:
        size_ratio_h = im_pl.shape[0] / float(im_sp.shape[0])
        size_ratio_w = im_pl.shape[1] / float(im_sp.shape[1])

        x1 = int(round(x1 * size_ratio_w))
        x2 = int(round(x2 * size_ratio_w))
        y1 = int(round(y1 * size_ratio_h))
        y2 = int(round(y2 * size_ratio_h))

        # correct and crop plir image (using params from short pass)
        cropped_nf = nf_rotated[y1:y2, x1:x2]

        # upsize low res bulk
        tau_bulk_plir = ndimage.zoom(tau_bulk_plir, zoom=2.0, order=1)

        # make sure same size
        height = min(tau_bulk_plir.shape[0], cropped_nf.shape[0])
        width = min(tau_bulk_plir.shape[1], cropped_nf.shape[1])
        tau_bulk_plir = tau_bulk_plir[:height, :width]
        cropped_nf = cropped_nf[:height, :width]
        assert tau_bulk_plir.shape == cropped_nf.shape
    else:
        cropped_nf = nf_rotated[y1:y2, x1:x2]

    if False:
        view = ImageViewer(tau_bulk_plir)
        ImageViewer(cropped_nf)
        view.show()
        sys.exit()

    if parameters.PLIR_INTERPOLATE_MARKER_WIDTH > 0 and features[
            'marker_loc'] > 0:
        # interpolate marker
        print features['marker_loc']
        locs = np.array([int(round(features['marker_loc']))], np.int32)
        cropped_nf = np.ascontiguousarray(cropped_nf, np.float32)
        pixel_ops.InterpolateBBs(cropped_nf, locs,
                                 parameters.PLIR_INTERPOLATE_MARKER_WIDTH)
        tau_bulk_plir = np.ascontiguousarray(tau_bulk_plir, np.float32)
        pixel_ops.InterpolateBBs(tau_bulk_plir, locs,
                                 parameters.PLIR_INTERPOLATE_MARKER_WIDTH)

        if False:
            view = ImageViewer(cropped_nf)
            ImageViewer(tau_bulk_plir)
            view.show()

    # correct for doping and transfer to bulk
    c_vals = fit_c_vals(cropped_nf, tau_bulk_plir, spline_plc)
    doping = ndimage.gaussian_filter1d(c_vals, sigma=2, mode="reflect")

    if False:
        ImageViewer(cropped_nf)
        plt.figure()
        plt.plot(c_vals)
        plt.plot(doping)
        plt.show()

    nf_dope = cropped_nf * np.r_[doping]
    nf_dope[nf_dope > spline_plc[0][-1]] = spline_plc[0][-1]

    tau_bulk_nf = interpolate.splev(nf_dope.flatten(), spline_plc).astype(
        np.float32).reshape(cropped_nf.shape)
    _, upper_p = ip.get_percentile(tau_bulk_nf, 0.0001)
    pixel_ops.ClipImage(tau_bulk_nf, 0.1, upper_p)

    features['_C_vals'] = doping.astype(np.float32)
    features['im_tau_bulk_f32'] = tau_bulk_nf
    features['im_tau_bulk_u8'] = (ip.scale_image(tau_bulk_nf) * 255).astype(
        np.uint8)
    features['im_cropped_nf_u8'] = (ip.scale_image(cropped_nf) * 255).astype(
        np.uint8)
    features['im_cropped_nf_u16'] = np.round(cropped_nf).astype(np.uint16)
    features['im_cropped_sp_u16'] = np.round(cropped_sp).astype(np.uint16)
    features['im_cropped_lp_u16'] = np.round(cropped_lp).astype(np.uint16)

    if False:
        print tau_bulk_nf.min(), tau_bulk_nf.max()
        _, upper_p = ip.get_percentile(tau_bulk_nf, 0.001)
        pixel_ops.ClipImage(tau_bulk_nf, 0.1, upper_p)
        # print interpolate.splev([0.0, 0.0245], spline_plc)
        #ImageViewer(cropped_nf * np.r_[doping])
        #ImageViewer(tau_bulk_nf)

        plt.figure()
        plt.plot(tau_bulk_plir.mean(axis=0))
        plt.plot(tau_bulk_nf.mean(axis=0))

        # plt.figure()
        # plt.hist(tau_bulk_full.flat, bins=100)
        if False:
            plt.figure()
            plt.plot(doping)
            plt.plot(c_vals)
            plt.figure()
            pl = np.mean(cropped_nf, axis=0)
            plt.plot(pl, label="PL")
            plt.legend()
        plt.show()

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

    return True
def finger_shape(features):
    if 'DEBUG' in features:
        DEBUG = features['DEBUG']
    else:
        DEBUG = False

    # use an image that has been normalised to [0, 1]
    im = features['_cropped_f32'] / features['hist_percentile_99.9']

    if parameters.CELL_BB_MID_POINTS:
        locs = np.round((features['_busbar_cols'][:-1] + features['_busbar_cols'][1:]) / 2.0).astype(np.int32)
        pixel_ops.InterpolateBBs(im, locs, 3)

    im_finger = im[features['_peak_row_nums']]
    # firing = np.zeros_like(im_finger)

    if False:
        view = ImageViewer(im)
        ImageViewer(im_finger)
        plt.figure()
        plt.plot(im_finger.mean(axis=0))
        view.show()
        sys.exit()

    TRAINING_MODE = False
    if TRAINING_MODE:
        import os
        fn = "finger_shape.csv"
        # bb_locs = features['_busbar_cols']
        bb_locs = np.r_[0, features['_busbar_cols'], im.shape[1] - 1]
        with open(fn, 'a') as f:
            def on_click(event):
                tb = plt.get_current_fig_manager().toolbar
                if event.xdata is None: return
                if tb.mode != '':
                    print 'Not in click mode - turn of pan or zoom'
                    return

                if event.button == 1:
                    classification = "good"
                elif event.button == 3:
                    classification = "bad"
                else:
                    return

                y = round(event.ydata)
                x = int(round(event.xdata))
                i = np.searchsorted(bb_locs, x)

                left, right = bb_locs[i - 1], bb_locs[i]
                assert left < x < right
                vals = im_finger[y, left:right]

                if x < im_finger.shape[1] // 2:
                    vals = vals[::-1]

                plt.figure()
                plt.plot(vals)
                plt.show()

                valstr = ','.join(["%0.02f" % (v) for v in vals])

                f.write("%s,%s\n" % (classification, valstr))

            fig = plt.figure()
            fig.canvas.mpl_connect('button_press_event', on_click)
            plt.imshow(im_finger, cmap=plt.cm.gray, interpolation='nearest')
            plt.show()
        return

    if len(features['_busbar_cols']) > 1:
        bb_locs = features['_busbar_cols']
    else:
        # only 1 busbar, so add left and right edges
        bb_locs = np.r_[0, features['_busbar_cols'], im.shape[1] - 1]

    S = 15
    # analyse each finger independently
    peak_broken = []
    peak_fine = []
    finger_rs = []
    finger_maes = []

    if False:
        locs = []
        for bb in range(len(bb_locs) - 1):
            locs.append((bb_locs[bb] + bb_locs[bb + 1]) // 2)
        im_finger2 = im_finger.copy()
        pixel_ops.InterpolateBBs(im_finger2, np.array(locs, np.int32), 4)
        view = ImageViewer(im_finger)
        ImageViewer(im_finger2)
        view.show()
        im_finger = im_finger2

    for bb in range(len(bb_locs) - 1):
        segment = im_finger[:, bb_locs[bb] + S:bb_locs[bb + 1] - S]
        if segment.shape[1] == 0:
            continue
        xs = np.linspace(-1.0, 1.0, num=segment.shape[1])
        for y in range(5, segment.shape[0] - 5):
            # fit a quadratic curve
            bbb = segment[y, :]
            params = np.polyfit(xs, bbb, 2)

            f = np.poly1d(params)
            ys = f(xs)

            # calculate the mean absolute error between the actual pixel values and the fitted parabola
            mae = np.abs(ys - bbb).mean() / bbb.mean()
            sigmoid = expit((mae - 0.02) / 0.001)

            # save curevature & goodness of fit
            finger_rs.append(params[0] * -1)
            finger_maes.append(mae)

            if sigmoid > 0.7:
                peak_broken.append(bbb.max())
            elif sigmoid < 0.3:
                peak_fine.append(bbb.max())

            # if True and bb == 0 and y == 5:
            if False and sigmoid > 0.7:
                print mae, sigmoid
                print bb, y
                im_fin = im_finger.copy()
                im_fin[y - 2, bb_locs[bb] + S:bb_locs[bb + 1] - S] = 0
                im_fin[y + 2, bb_locs[bb] + S:bb_locs[bb + 1] - S] = 0
                ImageViewer(im_fin)
                plt.figure()
                plt.plot(xs, bbb, label="x-profile")
                plt.plot(xs, ys, label="Fitted parabola")
                plt.legend()
                plt.show()

    if len(finger_rs) > 0:
        features['resistance_finger'] = np.median(finger_rs)
        features['resistance_finger_error'] = np.median(finger_maes)
    else:
        features['resistance_finger'] = 0
        features['resistance_finger_error'] = 0

    if len(peak_broken) > 0 or len(peak_fine) > 0:
        range_min = np.array(peak_broken + peak_fine).min()
        range_max = np.array(peak_broken + peak_fine).max()
        range_vals = np.linspace(range_min, range_max, num=100)

        peak_broken = np.array(peak_broken)
        peak_fine = np.array(peak_fine)
        broken_percent = peak_broken.shape[0] * 100 / float(peak_broken.shape[0] + peak_fine.shape[0])
        features['fingers_non_para'] = broken_percent

        # compute distribution of peak vals of bad fingers
        bad_fingers_dist = None
        bad_mode = None
        if len(peak_broken) > 5:
            f_bad_fingers = stats.gaussian_kde(peak_broken)
            bad_fingers_dist = f_bad_fingers(range_vals)
            bad_maxs = np.where((bad_fingers_dist > np.roll(bad_fingers_dist, 1)) &
                                (bad_fingers_dist > np.roll(bad_fingers_dist, -1)) &
                                (bad_fingers_dist > 2.0))[0]
            if len(bad_maxs) > 0:
                bad_mode = range_vals[bad_maxs[np.argmax(bad_fingers_dist[bad_maxs])]]

        # compute distribution of peak vals of good fingers
        good_fingers_dist = None
        good_maxs = []
        good_mode, good_mode_i = None, None
        if len(peak_fine) > 5:
            f_good_fingers = stats.gaussian_kde(peak_fine)
            good_fingers_dist = f_good_fingers(range_vals)
            good_maxs = np.where((good_fingers_dist > np.roll(good_fingers_dist, 1)) &
                                 (good_fingers_dist > np.roll(good_fingers_dist, -1)) &
                                 (good_fingers_dist > 2))[0]

            if len(good_maxs) > 0:
                good_mode_i = good_maxs[np.argmax(good_fingers_dist[good_maxs])]
                good_mode = range_vals[good_mode_i]

        if False:
            ImageViewer(im_finger)
            plt.figure()
            if good_fingers_dist is not None:
                plt.plot(range_vals, good_fingers_dist, 'g')
            if bad_fingers_dist is not None:
                plt.plot(range_vals, bad_fingers_dist, 'b')
            plt.show()
            sys.exit()

        if broken_percent >= 70:
            if DEBUG:
                print "0"
            # lots of broken, use fixed threshold
            threshold = 0.7
        elif broken_percent >= 33 and (good_mode is None or bad_mode < good_mode):
            if DEBUG:
                print "1"
            # significant broken and "non-broken" fingers are brighter than broken ones
            threshold = 0.7
        elif len(good_maxs) >= 2:
            # 2+ peaks in good dist.
            # - perhaps some good fingers are being classified as bad
            if broken_percent < 30 and (bad_mode is None or good_mode < bad_mode):
                if DEBUG:
                    print "2"
                # mostly good and broken fingers brighter
                # - use the highest peak
                i = good_mode_i
                while True:
                    if (good_fingers_dist[i] < 0.5 or i == good_fingers_dist.shape[0] - 1 or
                            (good_fingers_dist[i] < good_fingers_dist[i + 1] and
                                     good_fingers_dist[i] < 3)): break
                    i += 1
                threshold = range_vals[i]
            elif broken_percent < 20 and bad_mode is not None and bad_mode < good_mode:
                if DEBUG:
                    print "2B"
                # mostly good but broken fingers darker
                # - use the highest peak
                threshold = (range_vals[good_maxs[-2]] + range_vals[good_maxs[-1]]) / 2.0
            elif bad_mode is not None:
                if DEBUG:
                    print "3"
                # quite a few bad, or broken fingers darker
                # - use first peak
                i = good_maxs[0]
                while True:
                    if (good_fingers_dist[i] < 0.5 or i == good_fingers_dist.shape[0] - 1 or
                            (good_fingers_dist[i] < good_fingers_dist[i + 1] and
                                     good_fingers_dist[i] < 3)): break
                    i += 1
                threshold = min(range_vals[i], bad_mode - 0.1)
            else:
                threshold = good_mode
        elif (broken_percent <= 33 >= 1 and (bad_mode is None or good_mode < bad_mode) or
                      broken_percent < 10) and len(good_maxs):
            # - majority good fingers, single peak & broken fingers (if they exist) are brighter
            # - base the treshold on the distribution of good finger peaks
            if DEBUG:
                print "4"

            # find main mode in good dist
            i = good_maxs[0]
            while True:
                if (good_fingers_dist[i] < 0.5 or
                            i == good_fingers_dist.shape[0] - 1 or
                        (good_fingers_dist[i] < good_fingers_dist[i + 1] and
                                 good_fingers_dist[i] < 3)):
                    break
                i += 1

            threshold = min(range_vals[good_maxs[-1]] + 0.2, range_vals[i])
        else:
            if broken_percent > 33:
                # roughly 50/50. broken fingers are brighter
                # - use middle between good & bad
                if DEBUG:
                    print "5"
                threshold = (good_mode + bad_mode) / 2.0
            else:
                if DEBUG:
                    print "6"
                # majority good. assume the "bad" ones aren't actually bad
                if good_mode_i is not None:
                    i = good_mode_i
                else:
                    i = np.argmax(good_fingers_dist)
                while True:
                    if (good_fingers_dist[i] < 0.5 or
                                i == good_fingers_dist.shape[0] - 1 or
                            (good_fingers_dist[i] < good_fingers_dist[i + 1] and
                                     good_fingers_dist[i] < 3)):
                        break
                    i += 1

                threshold = range_vals[i]
    else:
        threshold = 1.0

    threshold -= parameters.BRIGHT_AREA_SENSITIVITY

    if False:
        print "Percent broken: ", broken_percent
        print "Threshold:", threshold

        view = ImageViewer(im)
        plt.figure()
        m1, m2 = 0, 0
        if good_fingers_dist is not None:
            plt.plot(range_vals, good_fingers_dist, 'g', label="Not broken")
            m1 = good_fingers_dist.max()
        if bad_fingers_dist is not None:
            plt.plot(range_vals, bad_fingers_dist, 'r', label="Broken")
            m2 = bad_fingers_dist.max()
        plt.vlines(threshold, 0, max(m1, m2))
        plt.legend()
        view.show()

    # highlight areas brighter than threshold
    # features['bright_line_threshold'] = threshold
    if threshold < 1.0:
        bright_lines = (im[features['_peak_row_nums']] - threshold) / (1.0 - threshold)
    else:
        bright_lines = np.zeros_like(im_finger)
    bright_lines *= 0.5
    pixel_ops.ClipImage(bright_lines, 0, 1)

    # don't want to highlight single lines, so apply vertical median filter
    rows_filtered = np.zeros_like(bright_lines)
    pixel_ops.FilterV(bright_lines, rows_filtered)
    rows_filtered[:2, :] = bright_lines[:2, :]
    rows_filtered[-2:, :] = bright_lines[-2:, :]

    if False:
        view = ImageViewer(im_finger)
        ImageViewer(bright_lines)
        ImageViewer(rows_filtered)
        view.show()
        sys.exit()

    bright_lines = rows_filtered

    # create a full size image of bright areas
    bright_area_full = np.zeros_like(im)
    pixel_ops.ExpandFingers(bright_area_full, bright_lines, features['_peak_row_nums'])
    bright_area_full = cv2.GaussianBlur(bright_area_full, sigmaX=3, ksize=(0, 0))
    bright_u8 = (bright_area_full * 255).astype(np.uint8)
    if 'ov_bright_area_u8' in features:
        assert False
        # features['ov_bright_area_u8'] = np.maximum(bright_u8, features['ov_bright_area_u8'])
    else:
        features['ov_bright_area_u8'] = bright_u8
    features['bright_area_strength'] = bright_lines.mean() * 100

    # bright area metrics
    mask_bright = (bright_area_full > 0.1).astype(np.uint8)
    im_pl = features['_cropped_f32']
    brightPL = pixel_ops.MaskMean_F32(im_pl, mask_bright, 1)
    darkPL = pixel_ops.MaskMean_F32(im_pl, mask_bright, 0)
    features['bright_area_mean_PL'] = brightPL
    features['bright_area_fraction'] = mask_bright.mean()
    features['bright_area_PL_intensity_ratio'] = brightPL / max(1, darkPL)

    # firing problems
    # firing_full = np.zeros_like(im)
    # pixel_ops.ExpandFingers(firing_full, firing, features['_peak_row_nums'])


    if False:
        view = ImageViewer(im_finger)
        ImageViewer(bright_area_full)
        # view = ImageViewer(firing_full)
        ImageViewer(features['ov_bright_area_u8'])
        view.show()
        sys.exit()
Exemplo n.º 7
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 finger_defect_features(features):
    breaks_full = features['mk_finger_break_u8']
    breaks = breaks_full[features['_finger_row_nums'], :]
    features['finger_break_count'] = breaks_full.sum()
    breaks_full = ndimage.binary_dilation(breaks_full, structure=ndimage.generate_binary_structure(2, 1), iterations=3)
    features['mk_finger_break_u8'] = breaks_full.astype(np.uint8)
    finger_im = features['_finger_im']
    edges_abs = features['_edges_abs']
    bb_locs = features['_busbar_cols']
    im = features['im_norm']

    if 'input_param_verbose' in features and features['input_param_verbose']:
        # compute feature based on finger break

        # create an edge/busbar mask
        cell_template = features['bl_cropped_u8'][features['_finger_row_nums'], :]
        cell_foreground = (cell_template != 4).astype(np.uint8)
        pixel_ops.FindCellMiddle(cell_foreground)

        # - position, distance to middle points, distance to furthest edge, strength
        ys, xs = np.where(breaks == 1)

        for i in range(len(ys)):
            y, x = ys[i], xs[i]

            row = cell_foreground[y, :]
            features['break%02d_y' % (i + 1)] = features['_finger_row_nums'][y]
            features['break%02d_x' % (i + 1)] = x
            features['break%02d_strength' % (i + 1)] = edges_abs[y, x]
            features['break%02d_middle_dist' % (i + 1)] = np.abs(np.where(row == 2)[0] - x).min()

            # find busbar/edge on left & right
            # TODO: not sure about logic when at busbar. guess this is worst case?
            edges = np.where(row == 1)[0]
            left = edges[np.where(edges < x)[0][-1]]
            right = edges[np.where(edges > x)[0][0]]
            if x - left > right - x:
                if breaks[y, left:x - 1].sum() > 0:
                    dist = -1
                else:
                    dist = x - left
            else:
                if breaks[y, x + 1:right].sum() > 0:
                    dist = -1
                else:
                    dist = right - x
            features['break%02d_edge_dist' % (i + 1)] = dist

    ################
    # BRIGHT LINES #
    ################
    # - if at least one break:
    #   - find strongest break. 0=dark side, 1=bright side
    bright_lines = np.zeros_like(finger_im)
    bbs = np.r_[0, bb_locs, finger_im.shape[1]]
    num_bright_lines = pixel_ops.BrokenFingerBrightLines(finger_im, bbs, edges_abs, breaks, bright_lines)
    features['bright_lines_count'] = num_bright_lines
    bright_lines = ndimage.uniform_filter(bright_lines, size=(1, 5))
    pixel_ops.ClipImage(bright_lines, 0.0, 1.0)

    # create a full size image of bright lines
    bright_lines_full = np.zeros_like(im)
    bright_lines_full[features['_finger_row_nums'], :] = bright_lines
    bright_lines_full = cv2.GaussianBlur(bright_lines_full, sigmaX=1, sigmaY=2, ksize=(0, 0))

    features['ov_bright_lines_u8'] = (bright_lines_full * 255).astype(np.uint8)
    features['bright_lines_strength'] = bright_lines.mean()
    features['bright_lines_length'] = (bright_lines > 0.5).sum()

    if False:
        print features['bright_lines_length']
        view = ImageViewer(im)
        # ImageViewer(normed)
        ImageViewer(bright_lines)
        ImageViewer(bright_lines > 0.5)
        # ImageViewer(breaks)
        ImageViewer(create_overlay(features))
        view.show()
        sys.exit()
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 finger_defects(features):
    # TODO: bright lines for top and bottom rows
    im = features['im_norm']
    row_nums = features['_finger_row_nums']

    ################
    # BRIGHT LINES #
    ################
    # highlight bright lines based on difference between lines above/below
    finger_im = im[row_nums, :].copy()

    # based on differences above & below
    rows_smooth = finger_im
    rows_filtered = np.zeros_like(rows_smooth)
    pixel_ops.FilterV(rows_smooth, rows_filtered)
    rows_filtered[:2, :] = finger_im[:2, :]
    rows_filtered[-2:, :] = finger_im[-2:, :]
    bright_lines = rows_smooth - rows_filtered

    bright_lines /= max(0.1, (0.5 - parameters.BRIGHT_LINES_MULTI_SENSITIVITY))

    # make less sensitive for multi
    bright_lines -= 0.25
    bright_lines /= 0.75

    bright_lines = ndimage.uniform_filter(bright_lines, size=(1, 5))
    pixel_ops.ClipImage(bright_lines, 0.0, 1.0)

    if False:
        plt.figure()
        plt.plot(finger_im[42, :])
        view = ImageViewer(finger_im)
        ImageViewer(im)
        ImageViewer(rows_filtered)
        ImageViewer(bright_lines)
        view.show()
        sys.exit()

    # get strength of each line (sum)
    # - need to insert lines between rows for computing CCs
    bl_cc = np.zeros((bright_lines.shape[0] * 2, bright_lines.shape[1]), np.uint8)
    bl_cc[::2, :] = bright_lines > 0
    ccs, num_ccs = ip.connected_components(bl_cc)
    ccs = ccs[::2, :]
    sums = ndimage.sum(bright_lines, ccs, range(num_ccs + 1))
    sums /= (float(bright_lines.shape[1]) / 100.0)
    line_strengths = np.take(sums, ccs)
    bright_lines[line_strengths < parameters.BRIGHT_LINES_MULTI_THRESHOLD] = 0
    features['bright_lines_count'] = (sums > parameters.BRIGHT_LINES_MULTI_THRESHOLD).sum()

    if False:
        print features['bright_lines_count']
        view = ImageViewer(bl_cc)
        ImageViewer(ccs)
        ImageViewer(line_strengths)
        ImageViewer(bright_lines)
        view.show()

    # create a full size image of bright lines
    bright_lines_full = np.zeros_like(im)
    bright_lines_full[row_nums, :] = bright_lines
    bright_lines_full = cv2.GaussianBlur(bright_lines_full, sigmaX=2, ksize=(0, 0))
    features['ov_bright_lines_u8'] = (bright_lines_full * 255).astype(np.uint8)
    features['bright_lines_strength'] = bright_lines.mean()
    features['bright_lines_length'] = (bright_lines > 0.5).sum()

    if False:
        im[row_nums, :] = 0
        view = ImageViewer(im)
        ImageViewer(finger_im)
        ImageViewer(bright_lines > 0.5)
        ImageViewer(bright_lines_full)
        view.show()
        sys.exit()

    if True:
        # want a 1-1 correspondence with bright lines, so:
        # - threshold based on sum of line
        # - add break to end with highest gradient
        f = np.array([[-1, -1, -1, -1, -1, 0, 1, 1, 1, 1, 1]], dtype=np.float32)
        edges_abs = np.abs(ndimage.correlate(finger_im, weights=f, mode="nearest"))
        cc_labels = np.where(sums > parameters.BRIGHT_LINES_MULTI_THRESHOLD)[0]
        max_pos = ndimage.maximum_position(edges_abs, ccs, cc_labels)
        breaks = np.zeros_like(finger_im, np.uint8)

        if len(max_pos) > 0:
            ys, xs = zip(*max_pos)
            breaks[np.array(ys, np.int32), np.array(xs, np.int32)] = 1
    else:
        ####################
        # BROKEN FINGERS 1 #
        ####################
        # - 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
        # - since there is a step 2, 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
        w = 11
        g = 3
        s = g + (w // 2)
        maxs = ndimage.maximum_filter(bright_lines, size=(1, w))
        mins = ndimage.minimum_filter(bright_lines, size=(1, w))
        diffs = np.maximum(np.roll(mins, s, axis=1) - np.roll(maxs, -s, axis=1),
                           np.roll(mins, -s, axis=1) - np.roll(maxs, s, axis=1))
        diffs = ndimage.gaussian_filter1d(diffs, sigma=3, axis=1)
        pixel_ops.ApplyThresholdLT_F32(diffs, diffs, 0.0, 0.0)
        local_maxes = np.logical_and((diffs > np.roll(diffs, 1, axis=1)),
                                     (diffs > np.roll(diffs, -1, axis=1)))
        diffs[~local_maxes] = 0
        f = np.array([[-1, -1, -1, -1, -1, 0, 1, 1, 1, 1, 1]], dtype=np.float32)
        edges_abs = np.abs(ndimage.correlate(finger_im, weights=f, mode="nearest"))
        breaks1 = ((diffs > parameters.BROKEN_FINGER_MULTI_THRESHOLD1) &
                   (edges_abs > parameters.BROKEN_FINGER_MULTI_EDGE_THRESHOLD))
        breaks1[:, :features['cell_edge_left']] = False
        breaks1[:, features['cell_edge_right']:] = False

        if False:
            view = ImageViewer(bright_lines)
            ImageViewer(diffs)
            ImageViewer(edges_abs)
            ImageViewer(breaks1)
            # ImageViewer(np.roll(mins, shift=s, axis=1) - np.roll(maxs, shift=-s, axis=1))
            view.show()
            sys.exit()

    # add to defect mask
    breaks_full = np.zeros_like(im)
    breaks_full[features["_finger_row_nums"], :] = breaks
    breaks_full = ndimage.binary_dilation(breaks_full, structure=ndimage.generate_binary_structure(2, 1), iterations=3)
    features['mk_finger_break_u8'] = breaks_full.astype(np.uint8)
    features['finger_break_count'] = breaks.sum()

    if False:
        view = ImageViewer(im)
        ImageViewer(bright_lines)
        # ImageViewer(break_strength)
        ImageViewer(breaks)
        ImageViewer(create_overlay(features))
        view.show()
        sys.exit()