def create_overlay(features): im_u8 = features['im_cropped_u8'] im_rgb = np.empty((im_u8.shape[0], im_u8.shape[1], 3), np.float32) im_rgb[:, :, :] = im_u8[:, :, np.newaxis] if True: # splotches if False and "ov_splotches_u8" in features: splotches = features["ov_splotches_u8"] im_rgb[:, :, 2] += splotches im_rgb[:, :, 1] -= 0.5 * splotches im_rgb[:, :, 0] -= 0.5 * splotches # bright lines/areas if "ov_bright_area_u8" in features: broken_fingers = features["ov_bright_area_u8"] # *2 im_rgb[:, :, 0] -= broken_fingers im_rgb[:, :, 1] += broken_fingers im_rgb[:, :, 2] -= broken_fingers if "ov_bright_lines_u8" in features: broken_fingers = features["ov_bright_lines_u8"] # *2 im_rgb[:, :, 0] -= broken_fingers im_rgb[:, :, 1] += broken_fingers im_rgb[:, :, 2] -= broken_fingers # cracks if "mk_cracks_u8" in features: im_rgb = ip.overlay_mask(im_rgb, features['mk_cracks_u8'], 'r') if True: if 'mk_finger_break_u8' in features: im_rgb = ip.overlay_mask(im_rgb, features['mk_finger_break_u8'], 'b') if 'ov_dark_middle_u8' in features: impure = features["ov_dark_middle_u8"] // 2 im_rgb[:, :, 0] += impure im_rgb[:, :, 1] += impure im_rgb[:, :, 2] -= impure if 'ov_dark_areas_u8' in features: impure = features["ov_dark_areas_u8"] im_rgb[:, :, 0] += impure im_rgb[:, :, 1] -= impure im_rgb[:, :, 2] += impure if "mk_dark_spots_outline_u8" in features: im_rgb = ip.overlay_mask(im_rgb, features['mk_dark_spots_outline_u8'], colour='r') im_rgb[im_rgb < 0] = 0 im_rgb[im_rgb > 255] = 255 im_rgb = im_rgb.astype(np.uint8) return im_rgb
def create_overlay(features): normed = features['im_cropped_u8'] orig = normed.astype(np.int32) if False: view = ImageViewer(normed) view.show() b = orig g = orig r = orig if features['_cell_type'] == 'mono': pass elif features['_cell_type'] == 'multi': foreground = features['ov_dislocations_u8'] b = orig + foreground g = orig - foreground r = orig - foreground impure = features['ov_impure2_u8'] b -= impure g -= impure r += impure else: assert False r = np.clip(r, 0, 255) g = np.clip(g, 0, 255) b = np.clip(b, 0, 255) rgb = np.empty((normed.shape[0], normed.shape[1], 3), np.uint8) rgb[:, :, 0] = r.astype(np.uint8) rgb[:, :, 1] = g.astype(np.uint8) rgb[:, :, 2] = b.astype(np.uint8) # cracks if "mk_cracks_u8" in features: rgb = ip.overlay_mask(rgb, features['mk_cracks_u8'], 'r') if features['_cell_type'] == 'mono': # dark spots rgb = ip.overlay_mask(rgb, features['mk_dark_spots_outline_u8'], 'b') # dark areas if 'ov_dark_areas_u8' in features: dark = features["ov_dark_areas_u8"] rgb[:, :, 0] += dark rgb[:, :, 1] -= dark rgb[:, :, 2] += dark return rgb
def dark_spots(features): im = features['im_no_fingers'] h, w = im.shape im_mini = im[::6, ::6] im_mini_med = cv2.medianBlur(im_mini, ksize=5) im_mini_smooth = cv2.GaussianBlur(im_mini_med, ksize=(0, 0), sigmaX=1) background = cv2.resize(im_mini_smooth, (w, h)) dark_areas = background - im pixel_ops.ApplyThresholdLT_F32(dark_areas, dark_areas, 0.0, 0.0) foreground_mask = ((features['bl_cropped_u8'] == 0) | (features['bl_cropped_u8'] == 4)) structure = ndimage.generate_binary_structure(2, 1) foreground_mask = ndimage.binary_erosion(foreground_mask, structure=structure, iterations=3) dark_areas[~foreground_mask] = 0 DARK_SPOT_SENSITIVITY = 0.08 dark_spots = (dark_areas > DARK_SPOT_SENSITIVITY).astype(np.uint8) min_size = int(h * w * 0.0001) ip.remove_small_ccs(dark_spots, min_size) dark_spots_outline = ndimage.binary_dilation( dark_spots, structure=structure, iterations=2) - dark_spots features['mk_dark_spots_filled_u8'] = dark_spots features['mk_dark_spots_outline_u8'] = dark_spots_outline if False: view = ImageViewer(im) ImageViewer(background) ImageViewer(dark_spots) ImageViewer(ip.overlay_mask(im, dark_spots_outline)) view.show()
def create_overlay(features): if 'im_cropped_u8' not in features: return None im_u8 = features['im_cropped_u8'] im_rgb = np.empty((im_u8.shape[0], im_u8.shape[1], 3), np.float32) im_rgb[:, :, :] = im_u8[:, :, np.newaxis] # bright lines if "ov_lines_horizontal_u8" in features: horizontal = features["ov_lines_horizontal_u8"] im_rgb[:, :, 2] += horizontal im_rgb[:, :, 1] -= 0.5 * horizontal im_rgb[:, :, 0] -= 0.5 * horizontal if "ov_lines_vertical_u8" in features: vertical = features["ov_lines_vertical_u8"] im_rgb[:, :, 0] -= vertical im_rgb[:, :, 1] += 0.5 * vertical im_rgb[:, :, 2] -= 0.5 * vertical if "mk_cracks_u8" in features: im_rgb = ip.overlay_mask(im_rgb, features['mk_cracks_u8'], 'r') if "mk_dark_spots_outline_u8" in features: im_rgb = ip.overlay_mask(im_rgb, features['mk_dark_spots_outline_u8'], 'g') if 'ov_dark_middle_u8' in features: impure = features["ov_dark_middle_u8"] // 2 im_rgb[:, :, 0] += impure im_rgb[:, :, 1] -= impure im_rgb[:, :, 2] += impure im_rgb[im_rgb < 0] = 0 im_rgb[im_rgb > 255] = 255 im_rgb = im_rgb.astype(np.uint8) return im_rgb
def create_overlay(features): im_u8 = features['im_cropped_u8'] im_rgb = np.empty((im_u8.shape[0], im_u8.shape[1], 3), np.float32) im_rgb[:, :, :] = im_u8[:, :, np.newaxis] if 'mk_cracks_u8' in features: im_rgb = ip.overlay_mask(im_rgb, features['mk_cracks_u8'], 'r') im_rgb[im_rgb < 0] = 0 im_rgb[im_rgb > 255] = 255 im_rgb = im_rgb.astype(np.uint8) return im_rgb
def create_overlay(im, features): h, w = im.shape if 'center_y' in features: crop_mask = np.zeros_like(im, np.uint8) y = int(round(features['center_y'])) x = int(round(features['center_x'])) r = int(round(features['radius'])) rr, cc = draw.circle_perimeter(y, x, r) mask = ((rr < 0) | (rr >= h) | (cc < 0) | (cc >= w)) rr = rr[~mask] cc = cc[~mask] crop_mask[rr, cc] = 1 rgb = ip.overlay_mask(im, crop_mask) else: rgb = im return rgb
def dark_spots(features): im = features['im_no_fingers'] # shrink to standard size h, w = 300, 300 im_small = cv2.resize(im, (h, w)) dark_areas = np.zeros_like(im_small) pixel_ops.DarkSpots(im_small, dark_areas, 8) candidates = (dark_areas > parameters.DARK_SPOT_MIN_STRENGTH).astype(np.uint8) ip.remove_small_ccs(candidates, parameters.DARK_SPOT_MIN_SIZE) candidates = cv2.resize(candidates, (im.shape[1], im.shape[0])) candidates[features['mask_busbar_filled']] = 0 dark_spots_outline = ndimage.binary_dilation(candidates, iterations=3).astype(np.uint8) - \ ndimage.binary_dilation(candidates, iterations=1).astype(np.uint8) features['mk_dark_spots_outline_u8'] = dark_spots_outline features['mk_dark_spots_filled_u8'] = candidates features['dark_spots_area_fraction'] = candidates.mean() dark_areas_no_noise = dark_areas - parameters.DARK_SPOT_MIN_STRENGTH pixel_ops.ApplyThresholdLT_F32(dark_areas_no_noise, dark_areas_no_noise, 0.0, 0.0) features['dark_spots_strength'] = dark_areas_no_noise.mean() * 10000 features['dark_spots_count'] = ip.connected_components(candidates)[1] if False: print features['dark_spots_area_fraction'] print features['dark_spots_strength'] print features['dark_spots_count'] rgb = ip.overlay_mask(im, dark_spots_outline) view = ImageViewer(rgb) ImageViewer(dark_areas) ImageViewer(dark_areas_no_noise) ImageViewer(candidates) view.show()
def fill_corners(im, features, edge, dist): h, w = im.shape if 'radius' in features: radius = int(round(features['radius'])) y2 = int(round(features['center_y'])) x2 = int(round(features['center_x'])) elif 'wafer_radius' in features: radius = int(round(features['wafer_radius'])) y2 = int(round(features['wafer_middle_y'])) x2 = int(round(features['wafer_middle_x'])) else: print "ERROR: No radius found" assert False h2 = h // 2 w2 = w // 2 # pixels to sample intensities along corners ys, xs = draw.circle_perimeter(y2, x2, radius - edge) mask = ((ys >= 0) & (ys < h) & (xs >= 0) & (xs < w)) ys = ys[mask] xs = xs[mask] corner_filled = im.copy() corner_avg = 0 if False: im[ys, xs] = im.max() * 1.1 view = ImageViewer(im) view.show() # top left mask = ((ys < h2) & (xs < w2)) if mask.sum() > 0: corner_val = im[ys[mask], xs[mask]].mean() corner_avg += corner_val corner_filled[:h2, :w2][dist[:h2, :w2] > radius - edge] = corner_val # top right mask = ((ys < h2) & (xs > w2)) if mask.sum() > 0: corner_val = im[ys[mask], xs[mask]].mean() corner_avg += corner_val corner_filled[:h2, w2:][dist[:h2, w2:] > radius - edge] = corner_val # bottom left mask = ((ys > h2) & (xs < w2)) if mask.sum() > 0: corner_val = im[ys[mask], xs[mask]].mean() corner_avg += corner_val corner_filled[h2:, :w2][dist[h2:, :w2] > radius - edge] = corner_val # bottom right mask = ((ys > h2) & (xs > w2)) if mask.sum() > 0: corner_val = im[ys[mask], xs[mask]].mean() corner_avg += corner_val corner_filled[h2:, w2:][dist[h2:, w2:] > radius - edge] = corner_val corner_avg /= 4.0 # edges corner_filled[:, :edge] = np.c_[corner_filled[:, edge]] corner_filled[:, -edge:] = np.c_[corner_filled[:, -edge]] corner_filled[:edge, :] = np.r_[corner_filled[edge, :]] corner_filled[-edge:, :] = np.r_[corner_filled[-edge, :]] if False: r_mask = np.zeros_like(im, np.uint8) r_mask[ys, xs] = 1 rgb = ip.overlay_mask(im, r_mask) view = ImageViewer(rgb) ImageViewer(corner_filled) view.show() sys.exit() return corner_filled, corner_avg
def efficiency_analysis(features): im = features['im_no_fingers'] im_peaks = features['im_norm'][features['_peak_row_nums'], :] bbs = features['_busbar_cols'] # make sure no zero values im_peaks = im_peaks.copy() pixel_ops.ApplyThresholdLT_F32(im_peaks, im_peaks, 0.01, 0.01) im = im.copy() pixel_ops.ApplyThresholdLT_F32(im, im, 0.01, 0.01) # IDEA: # two defect mask: # 1. existing one (highlights anything dark) # 2. do a dark line (local mins). mask out & interpolate. # this one won't pick up grain boundaries # - slider blends between them ################# # DEFECT MASK 1 # ################# # - very sensitive to all dark areas # need to interpolate values along edges/busbars so defects in these regions can be found xs = np.arange(im_peaks.shape[0]) cols = [features['cell_edge_left'], features['cell_edge_right']] cols += list(np.where(features['mask_busbar_edges'][0, :])[0]) for c in cols: ys = im_peaks[:, c].copy() ys[ys > features['_bright_area_thresh']] = features['_bright_area_thresh'] params_op = optimize.fmin_powell(line_error, [0.0, ys.mean()], disp=0, args=(xs, ys), xtol=0.05, ftol=0.05) vals = xs * params_op[0] + params_op[1] im_peaks[:, c] = vals if False: print features['_bright_area_thresh'] mask = np.zeros_like(im, np.uint8) mask[:, c] = 1 ImageViewer(ip.overlay_mask(im, mask)) plt.figure() plt.plot(ys) plt.plot(vals) plt.show() # max of actual val and: interpolate vertical line at: # - left & right edge # - left & right of each BB # make monotonic (don't care about local mins) bb_mono = between_bb_mono(im_peaks, bbs) background1 = bb_mono background1 = cv2.GaussianBlur(background1, ksize=(0, 0), sigmaX=3, borderType=cv2.BORDER_REPLICATE) # relative difference foreground1 = background1 / im_peaks foreground1 -= 1 pixel_ops.ClipImage(foreground1, 0, 3.0) foreground1[:, features['mask_busbar_filled'][0, :]] = 0 # expand to full size full_size = np.zeros(im.shape, np.float32) pixel_ops.ExpandFingers(full_size, foreground1, features['_peak_row_nums']) foreground1 = full_size background_full = np.zeros(im.shape, np.float32) pixel_ops.ExpandFingers(background_full, background1, features['_peak_row_nums']) if False: view = ImageViewer(im) ImageViewer(foreground1) ImageViewer(background1) view.show() ################# # DEFECT MASK 2 # ################# # - less likely to include dark grains # - mask out local mins and areas of high gradient, and the interpolate background flatten_cols = np.r_[im.mean(axis=0)] im_flattened = im - flatten_cols flatten_rows = np.c_[im_flattened.mean(axis=1)] im_flattened -= flatten_rows im_flattened[features['mask_busbar_filled']] = 0 im_smoothed = cv2.GaussianBlur(im_flattened, ksize=(0, 0), sigmaX=2) # local mins dark_lines = np.zeros_like(im_flattened, np.uint8) pixel_ops.LocalMins(im_smoothed, dark_lines) s = ndimage.generate_binary_structure(2, 1) dark_lines = ndimage.binary_dilation(dark_lines, structure=s) # high gradient edgesH = cv2.Sobel(im_smoothed, cv2.CV_32F, 1, 0) edgesV = cv2.Sobel(im_smoothed, cv2.CV_32F, 0, 1) edges = cv2.magnitude(edgesH, edgesV) # combine defect_candidates = ((edges > 0.2) | dark_lines) if False: view = ImageViewer(im_flattened) ImageViewer(ip.overlay_mask(im_flattened, dark_lines)) ImageViewer(ip.overlay_mask(im_flattened, edges > 0.2)) ImageViewer(ip.overlay_mask(im_flattened, defect_candidates)) view.show() sys.exit() background2 = interpolate_background(im_flattened, defect_candidates.astype(np.uint8)) background2 += flatten_rows background2 += flatten_cols # relative difference foreground2 = background2 / im foreground2 -= 1 pixel_ops.ClipImage(foreground2, 0, 3.0) foreground2[features['mask_busbar_filled']] = 0 if False: view = ImageViewer(im) ImageViewer(background2) ImageViewer(foreground2) view.show() sys.exit() if False: features['_defect1'] = foreground1 features['_defect2'] = foreground2 # discrimination function x1, y1 = 0.0, 0.5 x2, y2 = 0.4, 0.0 foreground = (-1 * ((y2 - y1) * foreground2 - (x2 - x1) * foreground1 + x2 * y1 - y2 * x1) / np.sqrt((y2 - y1)**2 + (x2 - x1)**2)) foreground += 0.1 + parameters.CELL_DISLOCATION_SENSITIVITY foreground *= 0.25 pixel_ops.ClipImage(foreground, 0, 1) features['ov_dislocations_u8'] = (foreground * 255).astype(np.uint8) if False: view = ImageViewer(im) ImageViewer(foreground1) ImageViewer(foreground2) ImageViewer(foreground) #ImageViewer(background_full) # ImageViewer(foreground > 0.2) view.show() sys.exit() ################ # IMPURE AREAS # ################ def FindImpure(imp_profile, imp_profile_r, defects, debug=False): if False: ImageViewer(im) plt.figure() plt.plot(imp_profile) plt.plot(defects) plt.show() imp_ratio = (imp_profile / imp_profile_r).astype(np.float32) imp_ratio[imp_ratio > 0.9] = 0.9 imp_ratio /= 0.9 global_min = np.argmin(imp_ratio) local_mins = np.where((imp_ratio < np.roll(imp_ratio, 1)) & (imp_ratio < np.roll(imp_ratio, -1)))[0] # make monotonic from global minimum imp_mono = imp_ratio.copy() flip = False if global_min > len(imp_mono) // 2: flip = True imp_mono = imp_mono[::-1] global_min = np.argmin(imp_mono) rest = np.ascontiguousarray(imp_mono[global_min:]) rest_mono = np.empty_like(rest) pixel_ops.MakeMonotonic(rest, rest_mono) imp_mono[global_min:] = rest_mono # if edge_dist < 0.075: # imp_mono[:global_min] = imp_ratio[global_min] if flip: imp_mono = imp_mono[::-1] global_min = np.argmin(imp_mono) # for a real impure area, the global minimum should be close to an edge edge_dist = min(global_min, len(imp_ratio) - global_min) / float(len(imp_ratio)) edge_spot = int(0.03 * len(imp_mono)) imp_at_edge = min(imp_mono[edge_spot], imp_mono[-edge_spot]) imp_1d = np.ones_like(imp_mono) # check the defect content in the "impure" area if (imp_mono < 1.0).sum() == 0: defect_strength = 0 else: # defect_strength = defects[imp_mono < 1.0].mean() defect_strength = np.median(defects[imp_mono < 1.0]) if (edge_dist < 0.075 and # lowest point close to edge imp_at_edge < 0.8 and # edge is dark defect_strength < 0.1 and # not too many defects # (global_min == local_mins[0] or global_min == local_mins[-1]) and # no local mins closer to edge (imp_mono - imp_ratio).mean() < 0.035): # no large non-impure dips imp_1d = imp_mono.copy() imp_1d -= 0.3 if global_min < len(imp_profile) // 2: imp_1d[:global_min] = imp_1d[global_min] else: imp_1d[global_min:] = imp_1d[global_min] if debug: print edge_dist, edge_dist < 0.075 print imp_at_edge, imp_at_edge < 0.8 print defect_strength, defect_strength < 0.1 # print global_min, local_mins[0], (global_min == local_mins[0] or global_min == local_mins[-1]) print(imp_mono - imp_ratio).mean(), (imp_mono - imp_ratio).mean() < 0.035 ImageViewer(im) plt.figure() plt.plot(imp_profile, 'r') plt.plot(imp_mono, 'g') plt.plot(imp_ratio, 'b') # plt.plot((0, len(signal_var)), (0.1, 0.1)) plt.plot(defects, 'c') plt.plot(imp_1d, 'c') plt.show() # find impure edge width THRESH = 0.5 if imp_1d[0] < THRESH: edge_width = np.where(imp_1d < THRESH)[0][-1] / float( len(imp_profile)) left = True elif imp_1d[-1] < THRESH: edge_width = (len(imp_profile) - np.where(imp_1d < THRESH)[0][0]) / float( len(imp_profile)) left = False else: edge_width = 0 left = True return imp_1d, edge_width, left if False: # ignore defect areas defect_mask = foreground > 0.2 mx = ma.masked_array(im, mask=defect_mask) if False: view = ImageViewer(im) ImageViewer(defect_mask) view.show() ''' # left/right edges cols = np.apply_along_axis(stats.scoreatpercentile, 0, im, per=75) cols[cols > 1.0] = 1.0 imp_profile = ndimage.gaussian_filter1d(cols, sigma=3, mode="nearest") imp_profile_r = imp_profile[::-1] if len(bbs) >= 2: mid = (bbs[0] + bbs[-1]) // 2 else: mid = im.shape[1] // 2 imp_profile_r = np.roll(imp_profile_r, mid - (len(imp_profile) // 2)) col_defect = foreground.mean(axis=0) imp_h, ew_h, e_left = FindImpure(imp_profile, imp_profile_r, col_defect, debug=False) ''' # top/bottom edges rows = np.apply_along_axis(stats.scoreatpercentile, 1, im, per=75) rows[rows > 1.0] = 1.0 imp_profile = ndimage.gaussian_filter1d(rows, sigma=3, mode="nearest") imp_profile_r = imp_profile[::-1] row_defect = foreground.mean(axis=1) imp_v, ew_v, e_top = FindImpure(imp_profile, imp_profile_r, row_defect, debug=False) features['impure_edge_width'] = ew_v if e_top: features['impure_edge_side'] = 0 else: features['impure_edge_side'] = 2 impure = np.ones_like(im) impure[:, :] = np.c_[imp_v] if False: print features['impure_edge_width'], features['impure_edge_side'] view = ImageViewer(impure) view.show() sys.exit() imp_cutoff = 0.55 pixel_ops.ApplyThresholdGT_F32(impure, impure, imp_cutoff, imp_cutoff) impure /= imp_cutoff impure_overlay = np.log10(2 - impure) if False: plt.figure() plt.plot(impure[:, 0], 'r') plt.plot(np.log(impure[:, 0] + 1), 'g') plt.plot(np.log10(impure[:, 0] + 1), 'b') plt.show() view = ImageViewer(impure) view.show() pixel_ops.ClipImage(impure_overlay, 0, 1) features['ov_impure2_u8'] = (impure_overlay * 255).astype(np.uint8) ########### # METRICS # ########### num_pixels = im.shape[0] * im.shape[1] # impure impure_thresh = 0.9 # 0.8 dislocation_thresh = 0.1 num_impure_pixels = pixel_ops.CountThresholdLT_F32(impure, impure_thresh) features['impure_area_fraction'] = (num_impure_pixels / float(num_pixels)) if features['impure_area_fraction'] > 0.001: pure_mask = (impure > impure_thresh) & (foreground < dislocation_thresh) features['impure_strength'] = (im[pure_mask].mean() / im[impure < impure_thresh].mean()) - 1 else: features['impure_strength'] = 0 features['impure_area_fraction'] = 0 features['impure_strength2'] = features['impure_strength'] * features[ 'impure_area_fraction'] * 100 # dislocations num_dislocations = pixel_ops.CountThresholdGT_F32(foreground, dislocation_thresh) features['dislocation_area_fraction'] = (num_dislocations / float(num_pixels)) features['_foreground'] = foreground features['_dislocation_thresh'] = dislocation_thresh features['_impure'] = impure features['_impure_thresh'] = impure_thresh # find the average distance between dislocation pixels ys, xs = np.where(foreground > dislocation_thresh) points = np.c_[ys, xs] num_samples = 1500 if len(ys) > 0: points = points[::max(1, points.shape[0] // num_samples), :] pixel_dists = distance.pdist(points) avg_dist = np.mean(pixel_dists) avg_dist = (avg_dist - 0.3 * im.shape[0]) / (0.5 * im.shape[0]) else: avg_dist = 0 features['dislocation_density'] = 1.0 - avg_dist if len(ys) > 0: dis_vals = foreground[ys, xs] dislocation_strength = math.sqrt((dis_vals**2).mean()) features['dislocation_severity_A'] = dislocation_strength # create a second defect strength. ratio2 = background_full / im ratio2 = np.clip(ratio2, 1, 5) ratio2[:, features['mask_busbar_filled'][0, :]] = 1 features['dislocation_severity_B'] = ratio2[ys, xs].mean() if False: view = ImageViewer(background_full) ImageViewer(im) ImageViewer(ratio2) view.show() else: features['dislocation_severity_A'] = 0 features['dislocation_severity_B'] = 0 if False: view = ImageViewer(im) foreground[foreground < 0] = 0 ImageViewer(foreground) ImageViewer(background_full) view.show() sys.exit() # dislocation histogram features foreground_bins = np.zeros((5), np.float32) pixel_ops.BackgrounHistogram(foreground, foreground_bins) foreground_bins = (foreground_bins / num_pixels) * 100 features['dislocation_hist_01'] = foreground_bins[0] features['dislocation_hist_02'] = foreground_bins[1] features['dislocation_hist_03'] = foreground_bins[2] features['dislocation_hist_04'] = foreground_bins[3] features['dislocation_hist_05'] = foreground_bins[4] features['dislocation_strength'] = features[ 'dislocation_area_fraction'] * features['dislocation_severity_A'] if False: # print features['foreground_hist_01'], features['foreground_hist_02'], features['foreground_hist_03'], # print features['foreground_hist_04'], features['foreground_hist_05'] view = ImageViewer(im) ImageViewer(foreground) ImageViewer(impure) ImageViewer(create_overlay(features)) view.show()
def block_foreground(im, features): per_col = features['_col_60'] im_col = np.dot(np.ones((im.shape[0], 1), np.float32), per_col.reshape(1, per_col.shape[0])) per_row = features['_row_90'] im_row = np.dot(per_row.reshape(im.shape[0], 1), np.ones((1, im.shape[1]), np.float32)) background = ip.fast_smooth(np.minimum(im_col, im_row), sigma=5) foreground = background - im pixel_ops.ApplyThresholdLT_F32(foreground, foreground, 0, 0) pixel_ops.ApplyThresholdLT_F32(background, foreground, 0.3, 0) if False: view = ImageViewer(im, vmin=0, vmax=1) ImageViewer(im_col, vmin=0, vmax=1) ImageViewer(im_row, vmin=0, vmax=1) ImageViewer(background, vmin=0, vmax=1) ImageViewer(foreground, vmin=0, vmax=1) view.show() sys.exit() # skeletonized version of defects local_mins = np.zeros_like(foreground, np.uint8) f = cv2.GaussianBlur(foreground * -1, ksize=(0, 0), sigmaX=2) pixel_ops.LocalMins(f, local_mins) dis = ((local_mins == 1) & (foreground > 0.1)).astype(np.uint8) ys, xs = np.where(dis) pixel_ops.FastThin(dis, ys.copy(), xs.copy(), ip.thinning_lut) ip.remove_small_ccs(dis, 10) if False: crossing = np.zeros_like(dis) pixel_ops.ComputeCrossings(dis, crossing) junctions = crossing > 2 struct = ndimage.generate_binary_structure(2, 2) junctions_d = ndimage.binary_dilation(junctions, struct) branches = dis.copy() branches[junctions_d] = 0 # find branches that touch an end point ccs, num_ccs = ip.connected_components(branches) spurs = np.zeros_like(dis) for cc in set(ccs[crossing == 1]): if cc == 0: continue spurs[ccs == cc] = 1 # sys.exit() remove = spurs.copy() ip.remove_small_ccs(remove, 10) removed = spurs - remove pruned = dis - removed crossing = np.zeros_like(dis) pruned = pruned.astype(np.uint8) pixel_ops.ComputeCrossings(pruned, crossing) pruned[crossing == 1] = 0 dis = pruned rgb = ip.overlay_mask(im, dis, colour='b') ip.save_image("brick_lines_skeleton.png", dis) ip.save_image("brick_lines_overlay.png", rgb) view = ImageViewer(foreground, vmin=0, vmax=1) ImageViewer(rgb) view.show() sys.exit() # create a height-based profile of dislocation levels # - crop impure areas at top and bottom, imp = np.where(per_row < 0.5)[0] mid = len(per_row) // 2 upper_half = imp[imp < mid] if len(upper_half) > 0: top = upper_half.max() else: top = 0 lower_half = imp[imp > mid] if len(lower_half) > 0: bottom = lower_half.min() else: bottom = len(per_row) if False: plt.figure() plt.plot(per_row) plt.vlines([top, bottom], ymin=0, ymax=1) plt.show() sys.exit() foreground_pure = foreground[top:bottom, :] dislocation_profile = foreground_pure.mean(axis=0) dislocation_profile[per_col < 0.6] = 0 dislocation_profile = ndimage.gaussian_filter1d(dislocation_profile, sigma=5) features['_dis_avg_height'] = dislocation_profile if False: view = ImageViewer(im, vmin=0, vmax=1) # ImageViewer(im_col, scale=0.5, vmin=0, vmax=1) # ImageViewer(im_row, scale=0.5, vmin=0, vmax=1) ImageViewer(background, vmin=0, vmax=1) ImageViewer(foreground, vmin=0, vmax=1) ImageViewer(foreground_pure, vmin=0, vmax=1) plt.figure() plt.plot(dislocation_profile) view.show() sys.exit() return foreground
def finger_defects(features): im = features['im_norm'] h, w = im.shape row_nums = features['_finger_row_nums'] finger_im = im[row_nums, :] # .copy() mask = features['bl_cropped_u8'][row_nums, :] bb_locs = features['_busbar_cols'] # make a little more robust by averaging 3 locations: # - finger, and a little above/below offset = 1 # int(features['finger_period'] / 2.0) off_up = im[row_nums - offset, :] off_down = im[row_nums + offset, :] finger_im = (finger_im + off_up + off_down) / 3.0 features['_finger_im'] = finger_im if False: view = ImageViewer(im) ImageViewer(finger_im) # ImageViewer(finger_im2) view.show() # make monotonic (don't care about local mins) mono_lr = np.empty_like(finger_im) mono_rl = np.empty_like(finger_im) pixel_ops.MakeMonotonicBBs(finger_im, mono_lr, bb_locs) pixel_ops.MakeMonotonicBBs(np.ascontiguousarray(finger_im[:, ::-1]), mono_rl, np.ascontiguousarray(w - bb_locs[::-1])) mono_rl = mono_rl[:, ::-1] mono = np.minimum(mono_lr, mono_rl) if False: view = ImageViewer(im) # ImageViewer(normed) # ImageViewer(mono_lr) # ImageViewer(mono_rl) ImageViewer(mono) view.show() #################### # BROKEN FINGERS 1 # #################### # Detect intensity changes along a finger # - works best away from edges, and can handle breaks aligned in a column # 1. at a break, the min on bright side should be greater than max on dark side # 2. sharp local gradient, not a region with a steady (but high) gradient finger_filled = mono.copy() # 1 - brighter areas s = 25 offset = s // 2 + 3 maxs = ndimage.maximum_filter(finger_filled, size=(1, s), mode="nearest") pixel_ops.ApplyThresholdLT_F32(maxs, maxs, 0.1, 0.1) mins = ndimage.minimum_filter(finger_filled, size=(1, s), mode="nearest") d1 = np.roll(mins, offset, axis=1) / np.roll(maxs, -offset, axis=1) d2 = np.roll(mins, -offset, axis=1) / np.roll(maxs, offset, axis=1) break_strength = np.maximum(d1, d2) # 2 - sharp local drop f = np.array([[-1, -1, -1, -1, -1, 0, 1, 1, 1, 1, 1]], dtype=np.float32) edges_abs = np.abs(ndimage.correlate(mono, weights=f, mode="nearest")) edges_local = edges_abs - np.maximum(np.roll(edges_abs, 6, axis=1), np.roll(edges_abs, -6, axis=1)) break_strength += edges_local features['_edges_abs'] = edges_abs if False: print parameters.BROKEN_FINGER_THRESHOLD1 print parameters.BROKEN_FINGER_EDGE_THRESHOLD view = ImageViewer(finger_filled) # ImageViewer(maxs) # ImageViewer(mins) # ImageViewer(d1) # ImageViewer(d2) # ImageViewer(edges_local) ImageViewer(break_strength) view.show() sys.exit() # find local maxes breaks1 = ((break_strength >= np.roll(break_strength, 1, axis=1)) & (break_strength >= np.roll(break_strength, -1, axis=1)) & (break_strength > parameters.BROKEN_FINGER_THRESHOLD1)).astype(np.uint8) # For this detector, ignore breaks near edges, corners & busbars mask_background = mask == 8 # edges mask2 = ndimage.binary_dilation(mask_background, np.ones((1, s + 4), np.uint8)) mask3 = ndimage.binary_dilation(mask2, np.ones((3, 1), np.uint8)) corners = mask2.sum(axis=0) > 2 mask2[:, corners] = mask3[:, corners] bb_mask = features['mask_busbar_filled'][row_nums, :] bb_mask = ndimage.binary_dilation(bb_mask, np.ones((1, 11), np.uint8)) mask2 = (mask2 | bb_mask) breaks1[mask2] = 0 if False: view = ImageViewer(im) ImageViewer(break_strength) ImageViewer(breaks1) view.show() sys.exit() #################### # BROKEN FINGERS 2 # #################### # - find breaks by comparing with fingers above/below (using bright lines image) # - the advantage of this approach is that it can find breaks near edges # and busbars # - doesn't need to be very sensitive -- just find breaks near borders, which should be strong # - note that there also needs to be a gradient at that spot, otherwise we get # some phantom breaks rows_filtered = np.zeros_like(mono) pixel_ops.FilterV(mono, rows_filtered) rows_filtered[:2, :] = finger_im[:2, :] rows_filtered[-2:, :] = finger_im[-2:, :] bright_lines = mono - rows_filtered mask_background = ((mask == 1) | (mask == 8)) bright_lines[mask_background] = False pixel_ops.ClipImage(bright_lines, 0.0, 1.0) pixel_ops.ApplyThresholdLT_F32(mono, bright_lines, 0.4, 0.0) if False: view = ImageViewer(mono) ImageViewer(mask) ImageViewer(bright_lines) view.show() sys.exit() # filter bright lines min_length = w // 20 cc_sums = np.zeros_like(bright_lines) breaks2 = np.zeros_like(bright_lines, np.uint8) pixel_ops.BrightLineBreaks(bright_lines, cc_sums, breaks2, 0.04, parameters.BROKEN_FINGER_THRESHOLD2, min_length) if False: for r in range(cc_sums.shape[0]): if cc_sums[r, :].sum() == 0: continue print r plt.figure() plt.plot(bright_lines[r, :]) plt.plot((cc_sums[r, :] > 0) * bright_lines[r, :].max()) plt.figure() plt.plot(mono[r, :]) plt.plot((cc_sums[r, :] > 0) * mono[r, :].max()) plt.show() if False: view = ImageViewer(im) ImageViewer(bright_lines) ImageViewer(cc_sums) ImageViewer(breaks2) view.show() ########### # COMBINE # ########### # if there are 2+ close together, only keep one with strongest gradient if False: # check break source # green == independent lines (breaks1) # red == relative lines (breaks2) breaks_full = np.zeros_like(im) breaks_full[features["_finger_row_nums"], :] = breaks1 breaks_full = ndimage.binary_dilation(breaks_full, structure=ndimage.generate_binary_structure(2, 1), iterations=3) rgb = ip.overlay_mask(im, breaks_full, 'g') breaks_full = np.zeros_like(im) breaks_full[features["_finger_row_nums"], :] = breaks2 breaks_full = ndimage.binary_dilation(breaks_full, structure=ndimage.generate_binary_structure(2, 1), iterations=3) rgb = ip.overlay_mask(rgb, breaks_full, 'r') view = ImageViewer(rgb) view.show() sys.exit() breaks = (breaks1 | breaks2).astype(np.uint8) break_count = ndimage.correlate(breaks, weights=np.ones((1, 15), np.uint8), mode='constant') # .astype(np.uinut8) pixel_ops.CombineBreaks(breaks, break_count, edges_abs) # ignore breaks in cell edge or background cell_template = features['bl_cropped_u8'][features['_finger_row_nums']] breaks[((cell_template == 1) | (cell_template == 8))] = 0 # create break mask breaks_full = np.zeros_like(im, np.uint8) breaks_full[features["_finger_row_nums"], :] = breaks features['mk_finger_break_u8'] = breaks_full if False: print breaks_full.sum() view = ImageViewer(im) ImageViewer(finger_filled) ImageViewer(break_count) ImageViewer(breaks) view.show() sys.exit()
def create_overlay(features): h, w = features['im_cropped_u8'].shape if True and 'ov_impure2_u8' in features: impure = features['ov_impure2_u8'] else: impure = np.zeros_like(features['im_cropped_u8'], np.uint8) if 'ov_dislocations_u8' in features: foreground = features['ov_dislocations_u8'] # .astype(np.float32) / 255. else: foreground = np.zeros_like(features['im_cropped_u8'], np.uint8) orig = features['im_cropped_u8'].astype(np.int32) if True: # foreground b = orig + foreground g = orig - foreground r = orig - foreground else: b = orig.copy() g = orig.copy() r = orig.copy() if True: # splotches if "ov_splotches_u8" in features: splotches = features["ov_splotches_u8"] # * 2 b += splotches g -= splotches r -= splotches if True: # show impure as red b -= impure g -= impure r += impure if False: # show green line at boundary between impure and pure if features['impure_edge_width'] > 0: features['impure_edge_side'] = int(features['impure_edge_side']) if features['impure_edge_side'] in [0, 2]: pixels = int(round(features['impure_edge_width'] * orig.shape[0])) else: pixels = int(round(features['impure_edge_width'] * orig.shape[1])) if features['impure_edge_side'] == 0: r[pixels, :] = 255 g[pixels, :] = 0 b[pixels, :] = 0 elif features['impure_edge_side'] == 2: r[-pixels, :] = 255 g[-pixels, :] = 0 b[-pixels, :] = 0 elif features['impure_edge_side'] == 1: r[:, -pixels] = 255 g[:, -pixels] = 0 b[:, -pixels] = 0 elif features['impure_edge_side'] == 3: r[:, pixels] = 255 g[:, pixels] = 0 b[:, pixels] = 0 # bright lines if True and "ov_bright_lines_u8" in features: broken_fingers = features["ov_bright_lines_u8"] * 2 r -= broken_fingers g += broken_fingers b -= broken_fingers if "ov_bright_area_u8" in features: broken_fingers = features["ov_bright_area_u8"] * 3 r -= broken_fingers g += broken_fingers b -= broken_fingers r = np.clip(r, 0, 255) g = np.clip(g, 0, 255) b = np.clip(b, 0, 255) rgb = np.empty((h, w, 3), np.uint8) rgb[:, :, 0] = r.astype(np.uint8) rgb[:, :, 1] = g.astype(np.uint8) rgb[:, :, 2] = b.astype(np.uint8) # cracks if "mk_cracks_u8" in features: im_rgb = ip.overlay_mask(rgb, features['mk_cracks_u8'], 'r') # broken fingers if True and "mk_finger_break_u8" in features: rgb = ip.overlay_mask(rgb, features['mk_finger_break_u8'], 'b') return rgb
def find_fingers_perc(im, features): """ Find the period and locations of the grid fingers """ # rows row_profile = np.median(im, axis=1) row_profile /= row_profile.max() grid_rows_mask = np.logical_and(row_profile < np.roll(row_profile, 1), row_profile < np.roll(row_profile, -1)) peaks = np.where(row_profile > 0.2)[0] start, stop = peaks[0], peaks[-1] grid_rows_mask[:start] = False grid_rows_mask[stop:] = False grid_rows = np.where(grid_rows_mask)[0] # compute the period local_mins = grid_rows[5:-5] if len(local_mins) == 0: raise CellTemplateException period = (local_mins[-1] - local_mins[0]) / float(len(local_mins) - 1) features['mask_grid_rows'] = grid_rows_mask features['_finger_row_nums'] = grid_rows features['finger_period_row'] = period features['_peak_row_nums'] = (grid_rows[:-1] + (period // 2)).astype( np.int32) # cols col_profile = np.median(im, axis=0) col_profile /= col_profile.max() grid_cols_mask = ((col_profile < np.roll(col_profile, 1)) & (col_profile < np.roll(col_profile, -1)) & (col_profile > 0.3)) peaks = np.where(col_profile > 0.2)[0] start, stop = peaks[0], peaks[-1] grid_cols_mask[:start] = False grid_cols_mask[stop:] = False grid_cols = np.where(grid_cols_mask)[0] # compute the period local_mins = grid_cols[5:-5] if len(local_mins) == 0: raise CellTemplateException period = (local_mins[-1] - local_mins[0]) / float(len(local_mins) - 1) features['mask_grid_cols'] = grid_cols_mask features['_finger_col_nums'] = grid_cols features['finger_period_col'] = period if False: print "Row period: {}".format(features['finger_period_row']) print "Col period: {}".format(features['finger_period_col']) ImageViewer(im) mask = np.zeros_like(im, np.uint8) mask[:, grid_cols_mask] = 1 mask[grid_rows_mask, :] = 1 rgb = ip.overlay_mask(im, mask) ImageViewer(rgb) plt.figure() plt.plot(grid_rows, row_profile[grid_rows], 'o') plt.plot(row_profile) plt.figure() plt.plot(grid_cols, col_profile[grid_cols], 'o') plt.plot(col_profile) plt.show() sys.exit()
def find_busbars(im, features): h, w = im.shape if parameters.CELL_NO_BBS: features['bb_detection_mode'] = -1 features['_busbar_cols'] = np.array([], np.int32) features['busbar_width'] = 0 features['mask_busbar_edges'] = np.zeros_like(im, dtype=np.uint8) features['mask_busbar_filled'] = np.zeros_like(im, dtype=np.uint8) return #################### # BUSBAR LOCATIONS # #################### def pass_symmetry_test(bb_locs, debug=False): # check for symmetry in bb locations num_bb = len(bb_locs) if num_bb == 2: dist = abs(bb_locs[0] - (w - bb_locs[1])) elif num_bb == 3: dist1 = abs(w // 2 - bb_locs[1]) dist2 = abs(bb_locs[0] - (w - bb_locs[2])) dist = (dist1 + dist2) / 2.0 elif num_bb == 4: dist1 = abs(bb_locs[0] - (w - bb_locs[3])) dist2 = abs(bb_locs[1] - (w - bb_locs[2])) dist = (dist1 + dist2) / 2.0 elif num_bb == 5: dist1 = abs(w // 2 - bb_locs[2]) dist2 = abs(bb_locs[0] - (w - bb_locs[4])) dist3 = abs(bb_locs[1] - (w - bb_locs[3])) dist = (dist1 + dist2 + dist3) / 3.0 else: dist = 100 if num_bb >= 3: spacing = bb_locs[1:] - bb_locs[:-1] spacing_ratio = spacing.max() / float(spacing.min()) else: spacing_ratio = 1 if debug: print dist, spacing_ratio return (dist <= 15) and (spacing_ratio < 2) # attempt 1: columns without much variation features['bb_detection_mode'] = None col_var = ndimage.gaussian_filter1d(features['_col_var'], sigma=2, mode="constant") col_var = ndimage.gaussian_filter1d(col_var, sigma=1) dip_strength = np.minimum(np.roll(col_var, 10), np.roll(col_var, -10)) - col_var threshold = parameters.CELL_BB_MIN e = len(col_var) // 25 col_var[:e] = 0 col_var[-e:] = 0 busbar_locations = np.where((col_var < threshold) & (col_var < np.roll(col_var, 1)) & (dip_strength > 0.07) & (col_var < np.roll(col_var, -1)))[0] # ignore anything 0.2 higher than lowest # - for multi cells, impure areas can cause low variation, so skip this test if len(busbar_locations) > 0 and features['_alg_mode'] != 'multi cell': busbar_locations = busbar_locations[( col_var[busbar_locations] - col_var[busbar_locations].min()) < 0.2] busbar_locations.sort() # group anything very close together if len(busbar_locations) > 0: diffs = busbar_locations[1:] - busbar_locations[:-1] for i in np.where(diffs < 10)[0]: middle = (busbar_locations[i] + busbar_locations[i + 1]) // 2 busbar_locations[i] = middle busbar_locations[i + 1] = -1 busbar_locations = busbar_locations[busbar_locations >= 0] if False: print busbar_locations, pass_symmetry_test( busbar_locations, debug=True), parameters.CELL_BB_MIN ImageViewer(im) plt.figure() plt.plot(col_var) plt.plot(dip_strength) plt.vlines(busbar_locations, ymin=0, ymax=1) plt.show() # sys.exit() if pass_symmetry_test(busbar_locations): features['bb_detection_mode'] = 1 if features['bb_detection_mode'] is None: # attempt 2: local minimums of intensity profile if im.shape[0] > 600: sigma = 5 offset = 10 else: sigma = 2.5 offset = 5 col_mean = ndimage.gaussian_filter1d(im.mean(axis=0), sigma=sigma, mode="constant") col_mean /= col_mean.max() dip_strength = np.minimum(np.roll(col_mean, offset), np.roll(col_mean, -offset)) - col_mean dip_strength[dip_strength < 0] = 0 p10 = int(len(dip_strength) * 0.09) dip_strength[:p10] = 0 dip_strength[-p10:] = 0 dip_thresh = dip_strength.std() * parameters.CELL_BB_MODE_2_STD busbar_locations = np.where((col_mean < np.roll(col_mean, 1)) & (col_mean < np.roll(col_mean, -1)) & (((dip_strength > 0.05) & (col_mean < 0.6)) | (dip_strength > dip_thresh)))[0] # ignore anything too close to edge busbar_locations = busbar_locations[busbar_locations > 20] busbar_locations = busbar_locations[busbar_locations < w - 20] busbar_locations.sort() if False: print busbar_locations, dip_thresh, pass_symmetry_test( busbar_locations) ImageViewer(im) plt.figure() plt.plot(col_mean) plt.plot(dip_strength) plt.plot(busbar_locations, col_mean[busbar_locations], 'o') plt.show() # sys.exit() if pass_symmetry_test(busbar_locations): features['bb_detection_mode'] = 2 if features['bb_detection_mode'] is None: # attempt 3: gradient of intensity profile dips = np.abs( np.minimum(np.roll(col_mean, shift=15), np.roll( col_mean, shift=-15)) - col_mean) e = len(col_mean) // 10 dips[:e] = 0 dips[-e:] = 0 # find strongest dips local_maxs = np.where( np.logical_and(dips > np.roll(dips, shift=1), dips > np.roll(dips, shift=-1)))[0] # if dip near middle -> 3 busbars, else 2 w2 = len(dips) // 2 if dips[w2 - 10:w2 + 10].max() > 0.02: num_bb = 3 else: num_bb = 2 busbar_locations = local_maxs[np.argsort(dips[local_maxs])[-num_bb:]] busbar_locations.sort() if False: print busbar_locations ImageViewer(im) plt.figure() plt.plot(col_mean) plt.plot(dips) plt.plot(busbar_locations, col_mean[busbar_locations], 'o') plt.show() sys.exit() if pass_symmetry_test(busbar_locations): features['bb_detection_mode'] = 3 if features['bb_detection_mode'] is None: # attempt 4 # - peaks in interior of e = len(col_mean) // 10 peaks = col_mean - np.maximum(np.roll(col_mean, shift=15), np.roll(col_mean, shift=-15)) local_maxs = np.where( np.logical_and(peaks > np.roll(peaks, shift=1), peaks > np.roll(peaks, shift=-1)))[0] local_maxs = local_maxs[local_maxs > e] local_maxs = local_maxs[local_maxs < len(col_mean) - e] busbar_locations = local_maxs[peaks[local_maxs] > 0.04] if False: print busbar_locations ImageViewer(im) plt.figure() plt.plot(peaks) plt.plot(local_maxs, peaks[local_maxs], 'o') plt.figure() plt.plot(col_mean) plt.plot(busbar_locations, col_mean[busbar_locations], 'o') plt.show() if pass_symmetry_test(busbar_locations): features['bb_detection_mode'] = 4 if features['bb_detection_mode'] is None: if False: plt.figure() plt.plot(im.mean(axis=0)) plt.show() raise MissingBusbarsException if False: print "Number of busbars: %d" % (len(busbar_locations)) print features['bb_detection_mode'] mask = np.zeros_like(im, np.uint8) mask[:, busbar_locations] = 1 rgb = ip.overlay_mask(im, mask) view = ImageViewer(rgb) view.show() # fine tune: see if we get a better fit by shifting left or right bw_max = int(w * 0.025) bl = busbar_locations[0] bb_profile = np.median(im[:, bl - bw_max:bl + bw_max + 1], axis=0) bb_profile -= bb_profile.min() bb_profile /= bb_profile.max() for bb in range(1, len(busbar_locations)): bl = busbar_locations[bb] bb_profileX = np.median(im[:, bl - bw_max:bl + bw_max + 1], axis=0) bb_profileX -= bb_profileX.min() bb_profileX /= bb_profileX.max() # find offset shifts = np.arange(-5, 6) differences = [ np.abs(bb_profile - np.roll(bb_profileX, s)).mean() for s in shifts ] best_shift = shifts[np.argmin(differences)] busbar_locations[bb] -= best_shift if False: print best_shift plt.figure() plt.plot(shifts, differences) plt.figure() plt.plot(bb_profile) plt.plot(bb_profileX) plt.plot(np.roll(bb_profileX, best_shift), '--') plt.show() features['_busbar_cols'] = busbar_locations ################ # BUSBAR WIDTH # ################ # create median busbar bb_stack = np.empty((h, bw_max * 2 + 1, len(busbar_locations)), np.float32) for e, bl in enumerate(busbar_locations): bb_stack[:, :, e] = im[:, bl - bw_max:bl + bw_max + 1] bb_template = np.median(bb_stack, axis=2) features['_bb_template'] = bb_template if False: plt.figure() for e, bl in enumerate(busbar_locations): plt.plot(im[:, bl - bw_max:bl + bw_max + 1].mean(axis=0)) view = ImageViewer(bb_stack[:, :, 2]) ImageViewer(bb_template) ImageViewer(im) view.show() sys.exit() if int(parameters.CELL_BB_WIDTH_MODE) == 1: # base on column variation im_peaks = bb_template[features['_peak_row_nums'], :] im_fingers = bb_template[features['_finger_row_nums'][:-1], :] diff = (im_peaks - im_fingers) col_var = np.median(diff, axis=0) # diff.mean(axis=0) col_var /= min(col_var[0], col_var[-1]) low_var = np.where(col_var < 0.8)[0] if len(low_var) < 2: raise MissingBusbarsException left, right = np.where(col_var < 0.8)[0][[0, -1]] if False: print col_var[(left + right) // 2] ImageViewer(diff) ImageViewer(bb_template) plt.figure() plt.plot(col_var) plt.vlines([left, right], 0, 1) plt.show() if col_var[(left + right) // 2] >= 0.8: raise MissingBusbarsException elif int(parameters.CELL_BB_WIDTH_MODE) == 2: # base on column intensity cols = bb_template.mean(axis=0) cols /= cols.max() dark_area = np.where(cols < parameters.CELL_BB_THRESH)[0] if len(dark_area) < 5: raise MissingBusbarsException left, right = dark_area[[0, -1]] if False: # ImageViewer(diff) ImageViewer(bb_template) plt.figure() plt.plot(cols) plt.vlines([left, right], 0, 1) plt.show() elif int(parameters.CELL_BB_WIDTH_MODE) == 3: left = (bb_template.shape[1] // 2) - parameters.CELL_BB_WIDTH right = (bb_template.shape[1] // 2) + parameters.CELL_BB_WIDTH if False: print left, right cols = bb_template.mean(axis=0) cols /= cols.max() ImageViewer(bb_template) plt.figure() plt.plot(cols) plt.vlines([left, right], 0, 1) plt.show() else: print "ERROR: Unknown busbar detection mode" features['busbar_width'] = right - left # create a mask, and use it to insert 1's for H&V filtering busbar_mask = np.zeros_like(im, dtype=np.uint8) busbar_filled = np.zeros_like(im, dtype=np.bool) for e, bl in enumerate(busbar_locations): busbar_mask[:, bl - bw_max + left] = 1 busbar_mask[:, bl - bw_max + right] = 1 busbar_filled[:, bl - bw_max + left:bl - bw_max + right + 1] = True if False: print features['busbar_width'] overlay = ip.overlay_mask(im, busbar_mask) view = ImageViewer(overlay) ImageViewer(busbar_filled) view.show() sys.exit() features['mask_busbar_edges'] = busbar_mask features['mask_busbar_filled'] = busbar_filled
def mono_cracks(features): struct = ndimage.generate_binary_structure(2, 1) im = features['im_norm'] im_no_rows = features['im_no_fingers'] h, w = im.shape # apply a filter that enhances thin dark lines smoothed = cv2.GaussianBlur(im_no_rows, ksize=(0, 0), sigmaX=1.0, borderType=cv2.BORDER_REPLICATE) dark_lines = np.zeros(smoothed.shape, np.float32) pixel_ops.CrackEnhance2(smoothed, dark_lines) if False: view = ImageViewer(im_no_rows) ImageViewer(smoothed) ImageViewer(dark_lines) view.show() sys.exit() # ignore background r = features['wafer_radius'] pixel_ops.ApplyThresholdGT_F32(features['im_center_dist_im'], dark_lines, r, 0) # HOUGH PARAMS LINE_THRESH = 0.07 MIN_LINE_LEN = 0.017 # 0.025 LINE_GAP = 4 ANGLE_TOL = 15 dark_lines_binary = (dark_lines > LINE_THRESH).astype(np.uint8) line_length = int(round(im.shape[0] * MIN_LINE_LEN)) lines = probabilistic_hough( dark_lines_binary, threshold=20, line_length=line_length, line_gap=LINE_GAP, theta=np.deg2rad(np.r_[np.arange(45 - ANGLE_TOL, 45 + ANGLE_TOL + 1), np.arange(135 - ANGLE_TOL, 135 + ANGLE_TOL + 1)])) line_im = np.zeros_like(dark_lines) edge_dist = int(round(im.shape[0] * 0.035)) coms = [] middle_y, middle_x = features['wafer_middle_y'], features['wafer_middle_x'] for line in lines: r0, c0, r1, c1 = line[0][1], line[0][0], line[1][1], line[1][0] rs, cs = draw.line(r0, c0, r1, c1) line_im[rs, cs] = 1 coms.append(cs.mean()) # connect to edge # first end rs_end1, cs_end1 = rs[:edge_dist], cs[:edge_dist] rs_ex1 = rs_end1 + (rs_end1[0] - rs_end1[-1]) cs_ex1 = cs_end1 + (cs_end1[0] - cs_end1[-1]) center_dists = np.sqrt((cs_ex1 - middle_x)**2 + (rs_ex1 - middle_y)**2) mask = ((rs_ex1 >= 0) & (rs_ex1 < h) & (cs_ex1 >= 0) & (cs_ex1 < w) & (center_dists < r)) # make sure some pixels have been mask (i.e. outside of cell) and is dark or short # print im_no_rows[rs_ex1[mask], cs_ex1[mask]].mean(), mask.sum() if mask.sum() < len(rs_ex1) and ( im_no_rows[rs_ex1[mask], cs_ex1[mask]].mean() < 0.45 or mask.sum() < 9): line_im[rs_ex1[mask], cs_ex1[mask]] = 2 # second end rs_end2, cs_end2 = rs[-edge_dist:], cs[-edge_dist:] rs_ex2 = rs_end2 + (rs_end2[-1] - rs_end2[0]) cs_ex2 = cs_end2 + (cs_end2[-1] - cs_end2[0]) center_dists = np.sqrt((cs_ex2 - middle_x)**2 + (rs_ex2 - middle_y)**2) mask = ((rs_ex2 >= 0) & (rs_ex2 < h) & (cs_ex2 >= 0) & (cs_ex2 < w) & (center_dists < r)) # make sure some pixels have been mask (i.e. outside of cell) and is dark or short if mask.sum() < len(cs_ex2) and ( im_no_rows[rs_ex2[mask], cs_ex2[mask]].mean() < 0.45 or mask.sum() < 9): line_im[rs_ex2[mask], cs_ex2[mask]] = 2 # join cracks that straddle BBs bb_cols = np.r_[0, features['_busbar_cols'], w - 1] for i1, i2 in itertools.combinations(range(len(lines)), 2): c1 = min(coms[i1], coms[i2]) c2 = max(coms[i1], coms[i2]) # make sure on different sides of BB (compare midpoints) straddle = False for bb in range(len(bb_cols) - 2): if bb_cols[bb] < c1 < bb_cols[bb + 1] < c2 < bb_cols[bb + 2]: straddle = True break if not straddle: continue # make sure similar orientation & offset def orientation(r0, c0, r1, c1): orien = math.degrees(math.atan2(r1 - r0, c1 - c0)) if orien < 0: orien += 180 if orien > 180: orien -= 180 return orien or1 = orientation(lines[i1][0][1], lines[i1][0][0], lines[i1][1][1], lines[i1][1][0]) or2 = orientation(lines[i2][0][1], lines[i2][0][0], lines[i2][1][1], lines[i2][1][0]) or_diff = abs(or2 - or1) if or_diff > 5: continue # find line between closest points if coms[i1] < coms[i2]: line1, line2 = lines[i1], lines[i2] else: line1, line2 = lines[i2], lines[i1] joining_line = draw.line(line1[1][1], line1[1][0], line2[0][1], line2[0][0]) if len(joining_line[0]) > 0.05 * w: continue line_im[joining_line] = 3 if False: view = ImageViewer(im_no_rows) ImageViewer(dark_lines) ImageViewer(dark_lines_binary) ImageViewer(line_im) view.show() sys.exit() # clean up lines line_im = ndimage.binary_closing(line_im, struct, iterations=2).astype(np.uint8) ys, xs = np.where(line_im) pixel_ops.FastThin(line_im, ys.copy().astype(np.int32), xs.copy().astype(np.int32), ip.thinning_lut) line_im = ndimage.binary_dilation(line_im, struct) # filter by "strength", which is a combination of darkness and length ccs, num_ccs = ip.connected_components(line_im) pixel_ops.ApplyThresholdGT_F32(dark_lines, dark_lines, 0.3, 0.3) if False: strength = ndimage.sum(dark_lines, labels=ccs, index=np.arange(num_ccs + 1)) else: # median will be more robust than mean (dark spots can lead to false positives) median_vals = ndimage.median(dark_lines, labels=ccs, index=np.arange(num_ccs + 1)) lengths = np.zeros(num_ccs + 1, np.int32) pixel_ops.CCSizes(ccs, lengths) strength = median_vals * lengths strength[0] = 0 strongest_candidates = np.argsort(strength)[::-1] strongest_candidates = strongest_candidates[ strength[strongest_candidates] > parameters.CELL_CRACK_STRENGTH] if False: # print strongest_candidates strength_map = np.take(strength, ccs) candidates = (strength_map > parameters.CELL_CRACK_STRENGTH).astype( np.uint8) view = ImageViewer(strength_map) ImageViewer(ip.overlay_mask(im, candidates, 'r')) view.show() sys.exit() # filter each candidate using other features mask_cracks = np.zeros_like(im, np.uint8) locs = ndimage.find_objects(ccs) crack_count = 0 for cc_label in strongest_candidates: e = cc_label - 1 y1, y2 = max(0, locs[e][0].start - 3), min(h, locs[e][0].stop + 3) x1, x2 = max(0, locs[e][1].start - 3), min(w, locs[e][1].stop + 3) crack = (ccs[y1:y2, x1:x2] == cc_label) im_win = im[y1:y2, x1:x2] ys, xs = np.where(crack) ys = ys + y1 xs = xs + x1 com_y = ys.mean() com_x = xs.mean() if False: view = ImageViewer(crack) ImageViewer(im_win) view.show() # remove cracks along corner edge by checking if center of mass # is same distance from middle as cell radius, and parallel to edge center_dists = np.sqrt((ys - (h / 2.0))**2 + (xs - (w / 2.0))**2) center_dist = center_dists.mean() dist_range = center_dists.max() - center_dists.min() r = features['wafer_radius'] - features['cell_edge_tb'] center_ratio = (min(center_dist, r) / max(center_dist, r)) if center_ratio > 0.98 and dist_range < 10: continue # keep cracks that have passed the tests & compute properties crack_count += 1 mask_cracks[y1:y2, x1:x2][crack] = cz_wafer.DEFECT_CRACK if ('input_param_verbose' not in features or features['input_param_verbose'] or crack_count < 6): cz_wafer.crack_properties(ys, xs, crack_count, features, mask_cracks) if crack_count >= parameters.MAX_NUM_CRACKS: break if False: # view = ImageViewer(ccs) print "Crack pixels: ", mask_cracks.sum() view = ImageViewer(ccs) ImageViewer(ip.overlay_mask(im, mask_cracks, 'r')) view.show() sys.exit() features['mk_cracks_u8'] = mask_cracks features['defect_count'] = crack_count if crack_count > 0: features['defect_present'] = 1 else: features['defect_present'] = 0 # thin before finding length crack_skel = mask_cracks.copy() ys, xs = np.where(mask_cracks) pixel_ops.FastThin(crack_skel, ys.copy().astype(np.int32), xs.copy().astype(np.int32), ip.thinning_lut) features['defect_length'] = crack_skel.sum() features['_crack_skel'] = crack_skel