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()
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
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()
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()