def normalise(cropped, features): # normalize using median of mid-point between fingers # - don't want to use percentiles, as bright fingers act as outliers # and reduce contrast everywhere else if '_peak_row_nums' in features: foreground = cropped[features['_peak_row_nums'], :] fg_mask = features['bl_cropped_u8'][features['_peak_row_nums'], :] fg_vals = foreground[fg_mask == 0] fg_median = np.median(fg_vals) norm = cropped / fg_median else: f = {} ip.histogram_percentiles(cropped, f) norm = cropped / f['hist_percentile_99'] features['im_norm'] = norm # create an 8-bit display version display = cropped / features['hist_percentile_99.9'] pixel_ops.ApplyThresholdGT_F32(display, display, 1.0, 1.0) features['im_cropped_u8'] = np.round(display * 255).astype(np.uint8)
def feature_extraction(im, features, skip_crop=False): t_start = timeit.default_timer() # rotation & cropping rotated = cropping.correct_stripe_rotation(im, features, already_cropped=skip_crop) cropped = cropping.crop_stripe(im, rotated, features, already_cropped=skip_crop) h, w = cropped.shape if False: view = ImageViewer(im) ImageViewer(cropped) view.show() features['_cropped_f32'] = cropped features['im_cropped_u16'] = cropped.astype(np.uint16) ip.histogram_percentiles(cropped, features) im_norm = cropped / features['hist_percentile_99'] features['im_norm'] = im_norm pixel_ops.ApplyThresholdGT_F32(im_norm, im_norm, 1.0, 1.0) features['im_cropped_u8'] = np.round(im_norm * 255).astype(np.uint8) # TODO: finger/background mask features['bl_cropped_u8'] = np.zeros_like(im_norm, np.uint8) if False: view = ImageViewer(im) ImageViewer(features['im_cropped_u16']) ImageViewer(im_norm) view.show() if 'input_param_skip_features' in features and int( features['input_param_skip_features']) == 1: return features['_fingers_grid'] = False features['_busbar_cols'] = np.array([], np.int32) features['busbar_width'] = 0 features['cell_edge_left'] = 10 features['cell_edge_right'] = im_norm.shape[1] - 10 features['mask_busbar_edges'] = np.zeros_like(im_norm, dtype=np.uint8) features['mask_busbar_filled'] = np.zeros_like(im_norm, dtype=np.uint8) features['wafer_middle_y'] = h // 2 features['wafer_middle_x'] = w // 2 features['wafer_radius'] = h features['_bright_area_thresh'] = 1 features['cell_edge_tb'] = 0 # assume cropped already. cell.find_fingers(im_norm, features) cell.remove_cell_template(im_norm, features) # TODO: add background to mask features['bl_cropped_u8'] = np.zeros_like(im_norm, np.uint8) features['bl_cropped_u8'][features['_finger_row_nums'], :] = 4 features['bl_cropped_u8'][ im_norm < parameters.STRIPE_BACKGROUND_THRESH] = 1 if False: view = ImageViewer(im_norm) ImageViewer(features['bl_cropped_u8']) view.show() if features['_cell_type'] == "multi": efficiency_analysis(features) cell.multi_cracks(features) features['ov_dislocations_u8'][:, :10] = 0 features['ov_dislocations_u8'][:, -10:] = 0 elif features['_cell_type'] == "mono": # calculate distance from wafer middle r, theta = np.empty_like(im_norm, np.float32), np.empty_like( im_norm, np.float32) pixel_ops.CenterDistance(r, theta, features['wafer_middle_y'], features['wafer_middle_x']) features['im_center_dist_im'] = r features['im_center_theta_im'] = theta cell.mono_cracks(features) mono_cell.dark_areas(features) mono_cell.dark_spots(features) else: print "ERROR -- Unknown mode: %s" % features['_cell_type'] # compute runtime t_stop = timeit.default_timer() features['runtime'] = t_stop - t_start return
def efficiency_analysis(features): im = features['im_no_fingers'] im_peaks = features['im_norm'][features['_peak_row_nums'], :] bbs = features['_busbar_cols'] # make sure no zero values im_peaks = im_peaks.copy() pixel_ops.ApplyThresholdLT_F32(im_peaks, im_peaks, 0.01, 0.01) im = im.copy() pixel_ops.ApplyThresholdLT_F32(im, im, 0.01, 0.01) # IDEA: # two defect mask: # 1. existing one (highlights anything dark) # 2. do a dark line (local mins). mask out & interpolate. # this one won't pick up grain boundaries # - slider blends between them ################# # DEFECT MASK 1 # ################# # - very sensitive to all dark areas # need to interpolate values along edges/busbars so defects in these regions can be found xs = np.arange(im_peaks.shape[0]) cols = [features['cell_edge_left'], features['cell_edge_right']] cols += list(np.where(features['mask_busbar_edges'][0, :])[0]) for c in cols: ys = im_peaks[:, c].copy() ys[ys > features['_bright_area_thresh']] = features['_bright_area_thresh'] params_op = optimize.fmin_powell(line_error, [0.0, ys.mean()], disp=0, args=(xs, ys), xtol=0.05, ftol=0.05) vals = xs * params_op[0] + params_op[1] im_peaks[:, c] = vals if False: print features['_bright_area_thresh'] mask = np.zeros_like(im, np.uint8) mask[:, c] = 1 ImageViewer(ip.overlay_mask(im, mask)) plt.figure() plt.plot(ys) plt.plot(vals) plt.show() # max of actual val and: interpolate vertical line at: # - left & right edge # - left & right of each BB # make monotonic (don't care about local mins) bb_mono = between_bb_mono(im_peaks, bbs) background1 = bb_mono background1 = cv2.GaussianBlur(background1, ksize=(0, 0), sigmaX=3, borderType=cv2.BORDER_REPLICATE) # relative difference foreground1 = background1 / im_peaks foreground1 -= 1 pixel_ops.ClipImage(foreground1, 0, 3.0) foreground1[:, features['mask_busbar_filled'][0, :]] = 0 # expand to full size full_size = np.zeros(im.shape, np.float32) pixel_ops.ExpandFingers(full_size, foreground1, features['_peak_row_nums']) foreground1 = full_size background_full = np.zeros(im.shape, np.float32) pixel_ops.ExpandFingers(background_full, background1, features['_peak_row_nums']) if False: view = ImageViewer(im) ImageViewer(foreground1) ImageViewer(background1) view.show() ################# # DEFECT MASK 2 # ################# # - less likely to include dark grains # - mask out local mins and areas of high gradient, and the interpolate background flatten_cols = np.r_[im.mean(axis=0)] im_flattened = im - flatten_cols flatten_rows = np.c_[im_flattened.mean(axis=1)] im_flattened -= flatten_rows im_flattened[features['mask_busbar_filled']] = 0 im_smoothed = cv2.GaussianBlur(im_flattened, ksize=(0, 0), sigmaX=2) # local mins dark_lines = np.zeros_like(im_flattened, np.uint8) pixel_ops.LocalMins(im_smoothed, dark_lines) s = ndimage.generate_binary_structure(2, 1) dark_lines = ndimage.binary_dilation(dark_lines, structure=s) # high gradient edgesH = cv2.Sobel(im_smoothed, cv2.CV_32F, 1, 0) edgesV = cv2.Sobel(im_smoothed, cv2.CV_32F, 0, 1) edges = cv2.magnitude(edgesH, edgesV) # combine defect_candidates = ((edges > 0.2) | dark_lines) if False: view = ImageViewer(im_flattened) ImageViewer(ip.overlay_mask(im_flattened, dark_lines)) ImageViewer(ip.overlay_mask(im_flattened, edges > 0.2)) ImageViewer(ip.overlay_mask(im_flattened, defect_candidates)) view.show() sys.exit() background2 = interpolate_background(im_flattened, defect_candidates.astype(np.uint8)) background2 += flatten_rows background2 += flatten_cols # relative difference foreground2 = background2 / im foreground2 -= 1 pixel_ops.ClipImage(foreground2, 0, 3.0) foreground2[features['mask_busbar_filled']] = 0 if False: view = ImageViewer(im) ImageViewer(background2) ImageViewer(foreground2) view.show() sys.exit() if False: features['_defect1'] = foreground1 features['_defect2'] = foreground2 # discrimination function x1, y1 = 0.0, 0.5 x2, y2 = 0.4, 0.0 foreground = (-1 * ((y2 - y1) * foreground2 - (x2 - x1) * foreground1 + x2 * y1 - y2 * x1) / np.sqrt((y2 - y1)**2 + (x2 - x1)**2)) foreground += 0.1 + parameters.CELL_DISLOCATION_SENSITIVITY foreground *= 0.25 pixel_ops.ClipImage(foreground, 0, 1) features['ov_dislocations_u8'] = (foreground * 255).astype(np.uint8) if False: view = ImageViewer(im) ImageViewer(foreground1) ImageViewer(foreground2) ImageViewer(foreground) #ImageViewer(background_full) # ImageViewer(foreground > 0.2) view.show() sys.exit() ################ # IMPURE AREAS # ################ def FindImpure(imp_profile, imp_profile_r, defects, debug=False): if False: ImageViewer(im) plt.figure() plt.plot(imp_profile) plt.plot(defects) plt.show() imp_ratio = (imp_profile / imp_profile_r).astype(np.float32) imp_ratio[imp_ratio > 0.9] = 0.9 imp_ratio /= 0.9 global_min = np.argmin(imp_ratio) local_mins = np.where((imp_ratio < np.roll(imp_ratio, 1)) & (imp_ratio < np.roll(imp_ratio, -1)))[0] # make monotonic from global minimum imp_mono = imp_ratio.copy() flip = False if global_min > len(imp_mono) // 2: flip = True imp_mono = imp_mono[::-1] global_min = np.argmin(imp_mono) rest = np.ascontiguousarray(imp_mono[global_min:]) rest_mono = np.empty_like(rest) pixel_ops.MakeMonotonic(rest, rest_mono) imp_mono[global_min:] = rest_mono # if edge_dist < 0.075: # imp_mono[:global_min] = imp_ratio[global_min] if flip: imp_mono = imp_mono[::-1] global_min = np.argmin(imp_mono) # for a real impure area, the global minimum should be close to an edge edge_dist = min(global_min, len(imp_ratio) - global_min) / float(len(imp_ratio)) edge_spot = int(0.03 * len(imp_mono)) imp_at_edge = min(imp_mono[edge_spot], imp_mono[-edge_spot]) imp_1d = np.ones_like(imp_mono) # check the defect content in the "impure" area if (imp_mono < 1.0).sum() == 0: defect_strength = 0 else: # defect_strength = defects[imp_mono < 1.0].mean() defect_strength = np.median(defects[imp_mono < 1.0]) if (edge_dist < 0.075 and # lowest point close to edge imp_at_edge < 0.8 and # edge is dark defect_strength < 0.1 and # not too many defects # (global_min == local_mins[0] or global_min == local_mins[-1]) and # no local mins closer to edge (imp_mono - imp_ratio).mean() < 0.035): # no large non-impure dips imp_1d = imp_mono.copy() imp_1d -= 0.3 if global_min < len(imp_profile) // 2: imp_1d[:global_min] = imp_1d[global_min] else: imp_1d[global_min:] = imp_1d[global_min] if debug: print edge_dist, edge_dist < 0.075 print imp_at_edge, imp_at_edge < 0.8 print defect_strength, defect_strength < 0.1 # print global_min, local_mins[0], (global_min == local_mins[0] or global_min == local_mins[-1]) print(imp_mono - imp_ratio).mean(), (imp_mono - imp_ratio).mean() < 0.035 ImageViewer(im) plt.figure() plt.plot(imp_profile, 'r') plt.plot(imp_mono, 'g') plt.plot(imp_ratio, 'b') # plt.plot((0, len(signal_var)), (0.1, 0.1)) plt.plot(defects, 'c') plt.plot(imp_1d, 'c') plt.show() # find impure edge width THRESH = 0.5 if imp_1d[0] < THRESH: edge_width = np.where(imp_1d < THRESH)[0][-1] / float( len(imp_profile)) left = True elif imp_1d[-1] < THRESH: edge_width = (len(imp_profile) - np.where(imp_1d < THRESH)[0][0]) / float( len(imp_profile)) left = False else: edge_width = 0 left = True return imp_1d, edge_width, left if False: # ignore defect areas defect_mask = foreground > 0.2 mx = ma.masked_array(im, mask=defect_mask) if False: view = ImageViewer(im) ImageViewer(defect_mask) view.show() ''' # left/right edges cols = np.apply_along_axis(stats.scoreatpercentile, 0, im, per=75) cols[cols > 1.0] = 1.0 imp_profile = ndimage.gaussian_filter1d(cols, sigma=3, mode="nearest") imp_profile_r = imp_profile[::-1] if len(bbs) >= 2: mid = (bbs[0] + bbs[-1]) // 2 else: mid = im.shape[1] // 2 imp_profile_r = np.roll(imp_profile_r, mid - (len(imp_profile) // 2)) col_defect = foreground.mean(axis=0) imp_h, ew_h, e_left = FindImpure(imp_profile, imp_profile_r, col_defect, debug=False) ''' # top/bottom edges rows = np.apply_along_axis(stats.scoreatpercentile, 1, im, per=75) rows[rows > 1.0] = 1.0 imp_profile = ndimage.gaussian_filter1d(rows, sigma=3, mode="nearest") imp_profile_r = imp_profile[::-1] row_defect = foreground.mean(axis=1) imp_v, ew_v, e_top = FindImpure(imp_profile, imp_profile_r, row_defect, debug=False) features['impure_edge_width'] = ew_v if e_top: features['impure_edge_side'] = 0 else: features['impure_edge_side'] = 2 impure = np.ones_like(im) impure[:, :] = np.c_[imp_v] if False: print features['impure_edge_width'], features['impure_edge_side'] view = ImageViewer(impure) view.show() sys.exit() imp_cutoff = 0.55 pixel_ops.ApplyThresholdGT_F32(impure, impure, imp_cutoff, imp_cutoff) impure /= imp_cutoff impure_overlay = np.log10(2 - impure) if False: plt.figure() plt.plot(impure[:, 0], 'r') plt.plot(np.log(impure[:, 0] + 1), 'g') plt.plot(np.log10(impure[:, 0] + 1), 'b') plt.show() view = ImageViewer(impure) view.show() pixel_ops.ClipImage(impure_overlay, 0, 1) features['ov_impure2_u8'] = (impure_overlay * 255).astype(np.uint8) ########### # METRICS # ########### num_pixels = im.shape[0] * im.shape[1] # impure impure_thresh = 0.9 # 0.8 dislocation_thresh = 0.1 num_impure_pixels = pixel_ops.CountThresholdLT_F32(impure, impure_thresh) features['impure_area_fraction'] = (num_impure_pixels / float(num_pixels)) if features['impure_area_fraction'] > 0.001: pure_mask = (impure > impure_thresh) & (foreground < dislocation_thresh) features['impure_strength'] = (im[pure_mask].mean() / im[impure < impure_thresh].mean()) - 1 else: features['impure_strength'] = 0 features['impure_area_fraction'] = 0 features['impure_strength2'] = features['impure_strength'] * features[ 'impure_area_fraction'] * 100 # dislocations num_dislocations = pixel_ops.CountThresholdGT_F32(foreground, dislocation_thresh) features['dislocation_area_fraction'] = (num_dislocations / float(num_pixels)) features['_foreground'] = foreground features['_dislocation_thresh'] = dislocation_thresh features['_impure'] = impure features['_impure_thresh'] = impure_thresh # find the average distance between dislocation pixels ys, xs = np.where(foreground > dislocation_thresh) points = np.c_[ys, xs] num_samples = 1500 if len(ys) > 0: points = points[::max(1, points.shape[0] // num_samples), :] pixel_dists = distance.pdist(points) avg_dist = np.mean(pixel_dists) avg_dist = (avg_dist - 0.3 * im.shape[0]) / (0.5 * im.shape[0]) else: avg_dist = 0 features['dislocation_density'] = 1.0 - avg_dist if len(ys) > 0: dis_vals = foreground[ys, xs] dislocation_strength = math.sqrt((dis_vals**2).mean()) features['dislocation_severity_A'] = dislocation_strength # create a second defect strength. ratio2 = background_full / im ratio2 = np.clip(ratio2, 1, 5) ratio2[:, features['mask_busbar_filled'][0, :]] = 1 features['dislocation_severity_B'] = ratio2[ys, xs].mean() if False: view = ImageViewer(background_full) ImageViewer(im) ImageViewer(ratio2) view.show() else: features['dislocation_severity_A'] = 0 features['dislocation_severity_B'] = 0 if False: view = ImageViewer(im) foreground[foreground < 0] = 0 ImageViewer(foreground) ImageViewer(background_full) view.show() sys.exit() # dislocation histogram features foreground_bins = np.zeros((5), np.float32) pixel_ops.BackgrounHistogram(foreground, foreground_bins) foreground_bins = (foreground_bins / num_pixels) * 100 features['dislocation_hist_01'] = foreground_bins[0] features['dislocation_hist_02'] = foreground_bins[1] features['dislocation_hist_03'] = foreground_bins[2] features['dislocation_hist_04'] = foreground_bins[3] features['dislocation_hist_05'] = foreground_bins[4] features['dislocation_strength'] = features[ 'dislocation_area_fraction'] * features['dislocation_severity_A'] if False: # print features['foreground_hist_01'], features['foreground_hist_02'], features['foreground_hist_03'], # print features['foreground_hist_04'], features['foreground_hist_05'] view = ImageViewer(im) ImageViewer(foreground) ImageViewer(impure) ImageViewer(create_overlay(features)) view.show()
def feature_extraction(im, features, crop=True, skip_features=False): h, w = im.shape if im.dtype != np.float32: im = im.astype(np.float32) if crop: # cropping rotation_corrected = block_rotate(im, features) cropped_u16 = block_crop(rotation_corrected, features) bounds = features['_crop_bounds'] else: rotation_corrected = im cropped_u16 = im bounds = (0, w - 1, 0, h - 1) features['_crop_bounds'] = bounds features['crop_rotation'] = 0 # get original coordinates of the block corners find_corners(im, features) features['_rotation_corrected'] = rotation_corrected if False: view = ImageViewer(im) ImageViewer(cropped_u16) view.show() sys.exit() # normalisation vals = cropped_u16[::2, ::2].flat vals = np.sort(vals) min_val = vals[int(0.01 * vals.shape[0])] max_val = vals[int(0.99 * vals.shape[0])] features['norm_range'] = max_val - min_val features['norm_lower'] = min_val im_normed = (cropped_u16 - min_val) / (max_val - min_val) pixel_ops.ApplyThresholdLT_F32(im_normed, im_normed, 0.0, 0.0) cropped = im_normed croped_u8 = im_normed.copy() pixel_ops.ApplyThresholdGT_F32(croped_u8, croped_u8, 1.0, 1.0) features['im_cropped_u8'] = (croped_u8 * 255).astype(np.uint8) features['im_cropped_u16'] = cropped_u16.astype(np.uint16) if skip_features or ('input_param_skip_features' in features and int(features['input_param_skip_features']) == 1): return if False: view = ImageViewer(im) ImageViewer(cropped, vmin=0, vmax=1) view.show() sys.exit() # compute some row/column percentiles col_sorted = np.sort(cropped[::4, :], axis=0) features['_col_90'] = np.ascontiguousarray( col_sorted[int(round(0.9 * 0.25 * cropped.shape[0])), :]) features['_col_60'] = np.ascontiguousarray( col_sorted[int(round(0.6 * 0.25 * cropped.shape[0])), :]) row_sorted = np.sort(cropped[:, ::4], axis=1) features['_row_90'] = np.ascontiguousarray( row_sorted[:, int(round(0.9 * 0.25 * cropped.shape[1]))]) # background background = block_background(cropped, features) # foreground foreground = block_foreground(cropped, features) # normalise background background /= background.max() # calculate metrics robust_dislocations(cropped, background, features) # dislocation area DIS_THRESH = 0.3 dislocation_area = ( pixel_ops.CountThresholdGT_F32(foreground, DIS_THRESH) / float(foreground.shape[0] * foreground.shape[1])) impure_area = 1 - (pixel_ops.CountThresholdGT_F32(background, 0.5) / float(foreground.shape[0] * foreground.shape[1])) # edge width l4 = background.shape[1] // 4 profile = background[:, l4:-l4].mean(axis=1) fg = np.where(profile > parameters.BRICK_EDGE_THRESH)[0] if len(fg) > 0: left_width, right = fg[[0, -1]] right_width = len(profile) - right - 1 edge_width = max(left_width, right_width) if edge_width < 0.05 * len(profile): edge_width = 0 else: edge_width = 100 features['edge_width'] = edge_width if False: print edge_width ImageViewer(cropped) plt.figure() plt.plot(profile) plt.show() if False: dislocations = np.zeros_like(foreground, dtype=np.uint8) pixel_ops.ApplyThresholdGT_F32_U8(foreground, dislocations, DIS_THRESH, 1) print features['defect_robust_area_fraction'], impure_area view = ImageViewer(im) ImageViewer(dislocations) ImageViewer(foreground) ImageViewer(background, vmin=0, vmax=1) view.show() # sys.exit() imp_cutoff = 0.55 pixel_ops.ApplyThresholdGT_F32(background, background, imp_cutoff, imp_cutoff) background /= imp_cutoff background = np.log10(2 - background) dis_cutoff = 0.1 foreground -= dis_cutoff foreground = np.clip(foreground, 0, 1) foreground *= 0.5 features['ov_impure_u8'] = (background * 255).astype(np.uint8) features['ov_defects_u8'] = (foreground * 255).astype(np.uint8) features['_bounds'] = bounds pixel_ops.ClipImage(im_normed, 0, 1) features['dislocation_area_fraction'] = dislocation_area features['impure_area_fraction'] = impure_area return features
def firing_defects(features): im = features['im_norm'] finger_rows = features['_finger_row_nums'] # im_fingers = features['_finger_im'] im_smooth = cv2.GaussianBlur(im, ksize=(0, 0), sigmaX=1) im_fingers = im_smooth[finger_rows, :] if False: view = ImageViewer(im) # view = ImageViewer(im_smooth) ImageViewer(im_fingers) view.show() sys.exit() # find depth of local minimums S = 5 dips = np.minimum(np.roll(im_fingers, S, axis=1), np.roll(im_fingers, -S, axis=1)) - im_fingers pixel_ops.ApplyThresholdLT_F32(dips, dips, 0, 0) dips[:, features['mask_busbar_filled'][0, :]] = 0 dips[:, :features['cell_edge_left']] = 0 dips[:, features['cell_edge_right']:] = 0 # TODO: add upper bound as well locs = ((im_fingers < np.roll(im_fingers, 1, axis=1)) & (im_fingers < np.roll(im_fingers, -1, axis=1)) & (dips > 0.02) & (dips < 0.05)).astype(np.float32) # count num dips in a region. ignore areas with only 1 w = im.shape[1] // 10 weights = np.ones(w, np.int32) local_count = ndimage.convolve1d(locs, weights, axis=1, mode="constant") dips_filtered = dips.copy() dips_filtered[local_count <= 1] = 0 if False: view = ImageViewer(im) ImageViewer(dips) # ImageViewer(locs) # ImageViewer(local_count) # ImageViewer(dips_filtered) view.show() im_dots = cv2.GaussianBlur(dips_filtered, ksize=(0, 0), sigmaX=10, sigmaY=2) # im_dots -= 0.001 if False: view = ImageViewer(dips_filtered) view = ImageViewer(im_dots) view.show() splotch = np.empty_like(im) pixel_ops.ExpandFingers(splotch, im_dots, finger_rows) splotch = ip.fast_smooth(splotch, sigma=10) features["_firing"] = splotch.copy() splotch *= 100.0 * (1.0 + parameters.FIRING_SENSITIVITY) pixel_ops.ClipImage(splotch, 0.0, 1.0) pixel_ops.ApplyThresholdGT_F32(features['im_center_dist_im'], splotch, features['wafer_radius'], 0) features["ov_splotches_u8"] = (splotch * 255).astype(np.uint8) # metrics if splotch.max() >= 0.2: features['firing_area_strength'] = splotch.mean() * 100 mask_firing = (splotch > 0.2).astype(np.uint8) im_pl = features['_cropped_f32'] firingPL = pixel_ops.MaskMean_F32(im_pl, mask_firing, 1) goodPL = pixel_ops.MaskMean_F32(im_pl, mask_firing, 0) features['firing_area_mean_PL'] = firingPL features['firing_area_fraction'] = mask_firing.mean() features['firing_area_PL_intensity_ratio'] = firingPL / max(1, goodPL) else: features['firing_area_strength'] = 0.0 features['firing_area_mean_PL'] = 0.0 features['firing_area_fraction'] = 0.0 features['firing_area_PL_intensity_ratio'] = 0.0 if False: print features['firing_area_strength'] view = ImageViewer(im) ImageViewer(splotch, vmin=0, vmax=1) ImageViewer(mask_firing) f2 = {'im_cropped_u8': features['im_cropped_u8'], 'ov_splotches_u8': features['ov_splotches_u8']} ImageViewer(create_overlay(f2)) view.show() sys.exit()
def dark_areas(features): # dark areas (not dark spots) im = features['im_norm'] h, w = im.shape row_nums = features['_peak_row_nums'] cell_mask = features['bl_cropped_u8'][row_nums, :] bb_locs = features['_busbar_cols'] # fill edges & corners foreground = im[row_nums].copy() edge = features['cell_edge_tb'] rr, cc = draw.circle_perimeter(features['wafer_middle_y'], features['wafer_middle_x'], int(round(features['wafer_radius'])) - edge) mask = (cc >= 0) & (cc < w) & np.in1d(rr, row_nums) lut = np.zeros(h, np.int32) lut[row_nums] = np.arange(len(row_nums)) rr = rr[mask] cc = cc[mask] rr = np.take(lut, rr) pixel_ops.FillCorners(foreground, rr.astype(np.int32), cc.astype(np.int32)) foreground[:, :edge] = np.c_[foreground[:, edge]] foreground[:, -edge:] = np.c_[foreground[:, -edge]] # create background # 1. make monotonic between edges & BBs mono_lr = np.empty_like(foreground) mono_rl = np.empty_like(foreground) pixel_ops.MakeMonotonicBBs(foreground, mono_lr, bb_locs) pixel_ops.MakeMonotonicBBs(np.ascontiguousarray(foreground[:, ::-1]), mono_rl, np.ascontiguousarray(w - bb_locs[::-1])) mono_rl = mono_rl[:, ::-1] mono = np.minimum(mono_lr, mono_rl) background = mono da1 = background - foreground # fill BBs and flatten background2 = background.copy() pixel_ops.InterpolateBBs(background2, features['_busbar_cols'], features['busbar_width']) cols = background2.mean(axis=0) background2 -= np.r_[cols] rows = background2.mean(axis=1) background2 -= np.c_[rows] da2 = -1 * background2 pixel_ops.ApplyThresholdLT_F32(da2, da2, 0.0, 0.0) if False: view = ImageViewer(foreground) ImageViewer(background) ImageViewer(background2) ImageViewer(da1) ImageViewer(da2) ImageViewer(da1 + da2) # plt.figure() # for x in range(0, background.shape[1], 50): # plt.plot(background[:, x], label="Col %d"%(x)) # plt.legend() view.show() sys.exit() dark_areas = da1 + da2 dark_areas -= (0.1 - parameters.DARK_AREA_SENSITIVITY) dark_areas[(cell_mask == 1) | (cell_mask == 8)] = 0 # corners pixel_ops.ClipImage(dark_areas, 0.0, 1.0) dark_areas_full = np.empty_like(im) pixel_ops.ExpandFingers(dark_areas_full, dark_areas, features['_peak_row_nums']) if False: pixel_ops.ApplyThresholdGT_F32(features['im_center_dist_im'], dark_areas_full, features['wafer_radius'], 0) print features['wafer_radius'] view = ImageViewer(dark_areas) ImageViewer(dark_areas_full) ImageViewer(features['im_center_dist_im']) view.show() dark_areas_full = cv2.GaussianBlur(dark_areas_full, ksize=(0, 0), sigmaX=1) # metrics mask_dark = (dark_areas_full > 0.2).astype(np.uint8) features['dark_area_strength'] = dark_areas_full.mean() * 100 im_pl = features['_cropped_f32'] darkPL = pixel_ops.MaskMean_F32(im_pl, mask_dark, 1) brightPL = pixel_ops.MaskMean_F32(im_pl, mask_dark, 0) features['dark_area_mean_PL'] = darkPL features['dark_area_fraction'] = mask_dark.mean() features['dark_area_PL_intensity_ratio'] = brightPL / max(1, darkPL) features['ov_dark_areas_u8'] = (dark_areas_full * 255).astype(np.uint8) if False: print features['dark_area_fraction'], features['dark_area_strength'] # plt.figure() # plt.plot(cols) # plt.plot(cols_mono) view = ImageViewer(im) ImageViewer(foreground) ImageViewer(dark_areas) ImageViewer(mask_dark) ImageViewer(dark_areas_full) view.show()
def bright_areas(features): im = features['im_norm'] row_nums = features['_finger_row_nums'] im_fingers = features['im_norm'][row_nums, :] vals = np.sort(im_fingers.flat) count = vals.shape[0] p01 = vals[int(round(0.01 * count))] p95 = vals[int(round(0.95 * count))] p99 = vals[int(round(0.999 * count))] # find highest peak counts, edges = np.histogram(vals, bins=50, density=True) i_max = np.argmax(counts) val_mode = (edges[i_max] + edges[i_max + 1]) / 2.0 if p99 - p95 > 0.2: # thresh 1: assuming a symmetric distribution, mode+spread on low ed thresh1 = val_mode + (val_mode - p01) * 2 # thresh 2: look for a distribution that drops very low below_02 = np.where(counts[i_max:] < 0.025)[0] if len(below_02) > 0: i = i_max + below_02[0] thresh2 = (edges[i] + edges[i + 1]) / 2.0 thresh = min(thresh1, thresh2) else: thresh = thresh1 else: thresh = im_fingers.max() thresh -= parameters.BRIGHT_AREA_MULTI_SENSITIVITY bright_areas = im_fingers - thresh bright_areas /= 0.5 pixel_ops.ApplyThresholdLT_F32(bright_areas, bright_areas, 0.0, 0.0) pixel_ops.ApplyThresholdGT_F32(bright_areas, bright_areas, 1.0, 1.0) # create a full size image of bright lines bright_lines_full = np.zeros_like(im) bright_lines_full[row_nums, :] = bright_areas bright_lines_full = cv2.GaussianBlur(bright_lines_full, sigmaX=1, sigmaY=2, ksize=(0, 0)) if 'ov_bright_area_u8' in features: features['ov_bright_area_u8'] = np.maximum((bright_lines_full * 255).astype(np.uint8), features['ov_bright_area_u8']) else: features['ov_bright_area_u8'] = (bright_lines_full * 255).astype(np.uint8) features['bright_area_strength'] = bright_areas.mean() * 100 features['bright_area_fraction'] = (pixel_ops.CountThresholdGT_F32(bright_areas, 0.1)) / float( im.shape[0] * im.shape[1]) features['_bright_area_thresh'] = thresh if False: print features['bright_area_strength'] print pixel_ops.CountThresholdGT_F32(bright_areas, 0.1) * 100 / float(im.shape[0] * im.shape[1]) print features['bright_area_fraction'] view = ImageViewer(im) ImageViewer(im_fingers) ImageViewer(bright_areas) plt.figure() plt.hist(im_fingers.flatten(), bins=50, normed=True) plt.plot((edges[:-1] + edges[1:]) / 2.0, counts) plt.vlines(thresh, 0, counts.max()) view.show()
def mono_cracks(features): struct = ndimage.generate_binary_structure(2, 1) im = features['im_norm'] im_no_rows = features['im_no_fingers'] h, w = im.shape # apply a filter that enhances thin dark lines smoothed = cv2.GaussianBlur(im_no_rows, ksize=(0, 0), sigmaX=1.0, borderType=cv2.BORDER_REPLICATE) dark_lines = np.zeros(smoothed.shape, np.float32) pixel_ops.CrackEnhance2(smoothed, dark_lines) if False: view = ImageViewer(im_no_rows) ImageViewer(smoothed) ImageViewer(dark_lines) view.show() sys.exit() # ignore background r = features['wafer_radius'] pixel_ops.ApplyThresholdGT_F32(features['im_center_dist_im'], dark_lines, r, 0) # HOUGH PARAMS LINE_THRESH = 0.07 MIN_LINE_LEN = 0.017 # 0.025 LINE_GAP = 4 ANGLE_TOL = 15 dark_lines_binary = (dark_lines > LINE_THRESH).astype(np.uint8) line_length = int(round(im.shape[0] * MIN_LINE_LEN)) lines = probabilistic_hough( dark_lines_binary, threshold=20, line_length=line_length, line_gap=LINE_GAP, theta=np.deg2rad(np.r_[np.arange(45 - ANGLE_TOL, 45 + ANGLE_TOL + 1), np.arange(135 - ANGLE_TOL, 135 + ANGLE_TOL + 1)])) line_im = np.zeros_like(dark_lines) edge_dist = int(round(im.shape[0] * 0.035)) coms = [] middle_y, middle_x = features['wafer_middle_y'], features['wafer_middle_x'] for line in lines: r0, c0, r1, c1 = line[0][1], line[0][0], line[1][1], line[1][0] rs, cs = draw.line(r0, c0, r1, c1) line_im[rs, cs] = 1 coms.append(cs.mean()) # connect to edge # first end rs_end1, cs_end1 = rs[:edge_dist], cs[:edge_dist] rs_ex1 = rs_end1 + (rs_end1[0] - rs_end1[-1]) cs_ex1 = cs_end1 + (cs_end1[0] - cs_end1[-1]) center_dists = np.sqrt((cs_ex1 - middle_x)**2 + (rs_ex1 - middle_y)**2) mask = ((rs_ex1 >= 0) & (rs_ex1 < h) & (cs_ex1 >= 0) & (cs_ex1 < w) & (center_dists < r)) # make sure some pixels have been mask (i.e. outside of cell) and is dark or short # print im_no_rows[rs_ex1[mask], cs_ex1[mask]].mean(), mask.sum() if mask.sum() < len(rs_ex1) and ( im_no_rows[rs_ex1[mask], cs_ex1[mask]].mean() < 0.45 or mask.sum() < 9): line_im[rs_ex1[mask], cs_ex1[mask]] = 2 # second end rs_end2, cs_end2 = rs[-edge_dist:], cs[-edge_dist:] rs_ex2 = rs_end2 + (rs_end2[-1] - rs_end2[0]) cs_ex2 = cs_end2 + (cs_end2[-1] - cs_end2[0]) center_dists = np.sqrt((cs_ex2 - middle_x)**2 + (rs_ex2 - middle_y)**2) mask = ((rs_ex2 >= 0) & (rs_ex2 < h) & (cs_ex2 >= 0) & (cs_ex2 < w) & (center_dists < r)) # make sure some pixels have been mask (i.e. outside of cell) and is dark or short if mask.sum() < len(cs_ex2) and ( im_no_rows[rs_ex2[mask], cs_ex2[mask]].mean() < 0.45 or mask.sum() < 9): line_im[rs_ex2[mask], cs_ex2[mask]] = 2 # join cracks that straddle BBs bb_cols = np.r_[0, features['_busbar_cols'], w - 1] for i1, i2 in itertools.combinations(range(len(lines)), 2): c1 = min(coms[i1], coms[i2]) c2 = max(coms[i1], coms[i2]) # make sure on different sides of BB (compare midpoints) straddle = False for bb in range(len(bb_cols) - 2): if bb_cols[bb] < c1 < bb_cols[bb + 1] < c2 < bb_cols[bb + 2]: straddle = True break if not straddle: continue # make sure similar orientation & offset def orientation(r0, c0, r1, c1): orien = math.degrees(math.atan2(r1 - r0, c1 - c0)) if orien < 0: orien += 180 if orien > 180: orien -= 180 return orien or1 = orientation(lines[i1][0][1], lines[i1][0][0], lines[i1][1][1], lines[i1][1][0]) or2 = orientation(lines[i2][0][1], lines[i2][0][0], lines[i2][1][1], lines[i2][1][0]) or_diff = abs(or2 - or1) if or_diff > 5: continue # find line between closest points if coms[i1] < coms[i2]: line1, line2 = lines[i1], lines[i2] else: line1, line2 = lines[i2], lines[i1] joining_line = draw.line(line1[1][1], line1[1][0], line2[0][1], line2[0][0]) if len(joining_line[0]) > 0.05 * w: continue line_im[joining_line] = 3 if False: view = ImageViewer(im_no_rows) ImageViewer(dark_lines) ImageViewer(dark_lines_binary) ImageViewer(line_im) view.show() sys.exit() # clean up lines line_im = ndimage.binary_closing(line_im, struct, iterations=2).astype(np.uint8) ys, xs = np.where(line_im) pixel_ops.FastThin(line_im, ys.copy().astype(np.int32), xs.copy().astype(np.int32), ip.thinning_lut) line_im = ndimage.binary_dilation(line_im, struct) # filter by "strength", which is a combination of darkness and length ccs, num_ccs = ip.connected_components(line_im) pixel_ops.ApplyThresholdGT_F32(dark_lines, dark_lines, 0.3, 0.3) if False: strength = ndimage.sum(dark_lines, labels=ccs, index=np.arange(num_ccs + 1)) else: # median will be more robust than mean (dark spots can lead to false positives) median_vals = ndimage.median(dark_lines, labels=ccs, index=np.arange(num_ccs + 1)) lengths = np.zeros(num_ccs + 1, np.int32) pixel_ops.CCSizes(ccs, lengths) strength = median_vals * lengths strength[0] = 0 strongest_candidates = np.argsort(strength)[::-1] strongest_candidates = strongest_candidates[ strength[strongest_candidates] > parameters.CELL_CRACK_STRENGTH] if False: # print strongest_candidates strength_map = np.take(strength, ccs) candidates = (strength_map > parameters.CELL_CRACK_STRENGTH).astype( np.uint8) view = ImageViewer(strength_map) ImageViewer(ip.overlay_mask(im, candidates, 'r')) view.show() sys.exit() # filter each candidate using other features mask_cracks = np.zeros_like(im, np.uint8) locs = ndimage.find_objects(ccs) crack_count = 0 for cc_label in strongest_candidates: e = cc_label - 1 y1, y2 = max(0, locs[e][0].start - 3), min(h, locs[e][0].stop + 3) x1, x2 = max(0, locs[e][1].start - 3), min(w, locs[e][1].stop + 3) crack = (ccs[y1:y2, x1:x2] == cc_label) im_win = im[y1:y2, x1:x2] ys, xs = np.where(crack) ys = ys + y1 xs = xs + x1 com_y = ys.mean() com_x = xs.mean() if False: view = ImageViewer(crack) ImageViewer(im_win) view.show() # remove cracks along corner edge by checking if center of mass # is same distance from middle as cell radius, and parallel to edge center_dists = np.sqrt((ys - (h / 2.0))**2 + (xs - (w / 2.0))**2) center_dist = center_dists.mean() dist_range = center_dists.max() - center_dists.min() r = features['wafer_radius'] - features['cell_edge_tb'] center_ratio = (min(center_dist, r) / max(center_dist, r)) if center_ratio > 0.98 and dist_range < 10: continue # keep cracks that have passed the tests & compute properties crack_count += 1 mask_cracks[y1:y2, x1:x2][crack] = cz_wafer.DEFECT_CRACK if ('input_param_verbose' not in features or features['input_param_verbose'] or crack_count < 6): cz_wafer.crack_properties(ys, xs, crack_count, features, mask_cracks) if crack_count >= parameters.MAX_NUM_CRACKS: break if False: # view = ImageViewer(ccs) print "Crack pixels: ", mask_cracks.sum() view = ImageViewer(ccs) ImageViewer(ip.overlay_mask(im, mask_cracks, 'r')) view.show() sys.exit() features['mk_cracks_u8'] = mask_cracks features['defect_count'] = crack_count if crack_count > 0: features['defect_present'] = 1 else: features['defect_present'] = 0 # thin before finding length crack_skel = mask_cracks.copy() ys, xs = np.where(mask_cracks) pixel_ops.FastThin(crack_skel, ys.copy().astype(np.int32), xs.copy().astype(np.int32), ip.thinning_lut) features['defect_length'] = crack_skel.sum() features['_crack_skel'] = crack_skel