def feature_extraction(im, features):
    t_start = timeit.default_timer()

    # crop
    crop_props = crop(im)
    features['corners'] = crop_props['corners']
    #print crop_props.keys()
    #features['crop_top'] = crop_props['crop_top']
    # features['corner_tl_x'] = crop_props['corners'][0][1]
    # features['corner_tl_y'] = crop_props['corners'][0][0]
    # features['corner_tr_x'] = crop_props['corners'][1][1]
    # features['corner_tr_y'] = crop_props['corners'][1][0]
    # features['corner_br_x'] = crop_props['corners'][2][1]
    # features['corner_br_y'] = crop_props['corners'][2][0]
    # features['corner_bl_x'] = crop_props['corners'][3][1]
    # features['corner_bl_y'] = crop_props['corners'][3][0]
    features['wafer_radius'] = crop_props['radius']
    features['_wafer_middle_orig'] = crop_props['center']
    features['crop_rotation'] = crop_props['estimated_rotation']
    cropped = cropping.correct_rotation(im, crop_props, pad=False, border_erode=parameters.BORDER_ERODE_CZ,
                                        fix_chamfer=False)
    if not cropped.flags['C_CONTIGUOUS']:
        cropped = np.ascontiguousarray(cropped)

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

    # histogram features
    h, w = cropped.shape
    ip.histogram_percentiles(cropped, features, h // 2, w // 2, features['wafer_radius'])

    # normalise image
    min_val = features['hist_percentile_01'] / float(features['hist_percentile_99'])
    norm_upper = features['hist_percentile_99']
    norm_lower = min(0.2, min_val)
    normed = ((cropped / norm_upper) - norm_lower) / (1 - norm_lower)

    # calculate distance from wafer rotation middle
    r, theta = np.empty_like(normed, np.float32), np.empty_like(normed, np.float32)
    pixel_ops.CenterDistance(r, theta, h // 2, w // 2)
    features['im_center_dist_im'] = r

    # create mask: 1=background
    wafer_mask = np.zeros_like(cropped, np.uint8)
    pixel_ops.ApplyThresholdGT_F32_U8(features['im_center_dist_im'], wafer_mask, features['wafer_radius'], 1)
    features['bl_cropped_u8'] = wafer_mask

    features['im_cropped_u8'] = (np.clip(normed, 0.0, 1.0) * 255).astype(np.uint8)
    if cropped.dtype.type is np.uint16:
        features['im_cropped_u16'] = cropped
    else:
        features['im_cropped_u16'] = cropped.astype(np.uint16)

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

    return crop_props
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)
Esempio n. 3
0
def analyse_module(features):
    im = np.ascontiguousarray(features["_im_ratio_cropped"])
    h, w = im.shape
    # mask out rows and columns
    border = 20
    border_cols = features['_divisions_cols'] - features['_divisions_cols'][0]
    for c in border_cols:
        im[:, max(c - border, 0):min(c + border + 1, w)] = 0
    border_rows = features['_divisions_rows'] - features['_divisions_rows'][0]
    for r in border_rows:
        im[max(r - border, 0):min(r + border + 1, h), :] = 0

    # scale so max is around
    scale = ((2**15) / im.max())
    im *= scale

    f = {}
    hist = ip.histogram_percentiles(im, f, skip_zero=True)
    hist = hist[:f['hist_percentile_99.9']]
    hist_norm = hist / hist.max()
    lower = np.where(hist_norm > 0.02)[0][0]
    upper = 2 * f['hist_peak'] - lower
    high_vals = (hist[upper:].sum() / float(hist.sum()))
    features['module_bright_area_fraction'] = high_vals

    if False:
        print "%s: %0.01f%%" % (features['fn'], high_vals)
        ip.print_metrics(f)
        plt.figure()
        plt.xlabel("PL/EL ratio")
        plt.ylabel("Count")
        plt.title("Above threshold: %0.02f%%" % high_vals)
        xs = np.arange(len(hist)) / float(scale)
        plt.plot(xs, hist)
        plt.vlines([upper / float(scale)], ymin=0, ymax=hist.max())
        if False:
            plt.savefig(
                os.path.join(r"C:\Users\Neil\Desktop\M1\hist",
                             features['fn'] + '_1.png'))
            im = features["_im_ratio_cropped"]
            im[im > f['hist_percentile_99']] = f['hist_percentile_99']
            ip.save_image(
                os.path.join(r"C:\Users\Neil\Desktop\M1\hist",
                             features['fn'] + '_0.png'), im)
        else:
            plt.show()
            view = ImageViewer(im[::3, ::3])
            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 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 feature_extraction(im, features, already_cropped=False):
    t_start = timeit.default_timer()

    if already_cropped:
        # features['crop_rotation'] = 0
        # features['cell_rotated'] = False
        rotated = cropping.correct_cell_rotation(
            im, features, already_cropped=already_cropped)
        cropped = cropping.crop_cell(rotated,
                                     im,
                                     features,
                                     width=None,
                                     already_cropped=True)
    else:
        # rotation & cropping
        rotated = cropping.correct_cell_rotation(im, features)
        cropped = cropping.crop_cell(rotated, im, features, width=None)

    features['im_cropped_u16'] = cropped.astype(np.uint16)
    h, w = cropped.shape

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

    # determine properties of the cell pattern
    cell.cell_structure(cropped, features)

    if False:
        view = ImageViewer(cropped)
        ImageViewer(features['bl_cropped_u8'])
        view.show()
        sys.exit()

    # normalise
    ip.histogram_percentiles(cropped,
                             features,
                             center_y=h // 2,
                             center_x=w // 2,
                             radius=features['wafer_radius'])
    cell.normalise(cropped, features)
    norm = features['im_norm']

    if False:
        view = ImageViewer(cropped)
        ImageViewer(norm)
        view.show()
        sys.exit()

    # full-size cell with no fingers/busbars
    cell.remove_cell_template(norm, features)

    if False:
        view = ImageViewer(norm)
        # ImageViewer(im_peaks)
        ImageViewer(features['im_no_fingers'])
        ImageViewer(features['im_no_figners_bbs'])
        view.show()
        sys.exit()

    if 'input_param_skip_features' not in features or int(
            features['input_param_skip_features']) != 1:
        wafer_features(features)
        bright_lines(features)
        if False:
            dark_spots(features)
        else:
            cz_cell.dark_spots(features)
        if True:
            temp = parameters.CELL_CRACK_STRENGTH
            parameters.CELL_CRACK_STRENGTH = 3.0
            cell.mono_cracks(features)
            parameters.CELL_CRACK_STRENGTH = temp

    # undo rotation
    if parameters.ORIGINAL_ORIENTATION and features['cell_rotated']:
        for feature in features.keys():
            if ((feature.startswith('im_') or feature.startswith('mask_')
                 or feature.startswith('map_') or feature.startswith('ov_')
                 or feature.startswith('bl_') or feature.startswith('mk_'))
                    and features[feature].ndim == 2):
                features[feature] = features[feature].T[:, ::-1]

    # compute runtime
    t_stop = timeit.default_timer()
    features['runtime'] = t_stop - t_start
def feature_extraction(im, features, skip_crop=False):
    t_start = timeit.default_timer()

    # rotation & cropping
    rotated = cropping.correct_cell_rotation(im, features, already_cropped=skip_crop)
    cropped = cropping.crop_cell(rotated, im, features, width=None, already_cropped=skip_crop)

    features['_cropped_f32'] = cropped
    features['im_cropped_u16'] = cropped.astype(np.uint16)
    h, w = cropped.shape

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

    # find fingers, busbars, etc
    cell.cell_structure(cropped, features)

    if False:
        view = ImageViewer(im)
        ImageViewer(cropped)
        ImageViewer(features['bl_cropped_u8'])
        view.show()
        sys.exit()

    # normalise
    ip.histogram_percentiles(cropped, features, center_y=h // 2, center_x=w // 2, radius=features['wafer_radius'])
    cell.normalise(cropped, features)
    norm = features['im_norm']

    if False:
        view = ImageViewer(cropped)
        ImageViewer(norm)
        view.show()
        sys.exit()

    # remove mini busbars
    if parameters.CELL_BB_MID_POINTS:
        locs = np.round((features['_busbar_cols'][:-1] + features['_busbar_cols'][1:]) / 2.0).astype(np.int32)
        norm_no_min = norm.copy()
        pixel_ops.InterpolateBBs(norm_no_min, locs, 3)
        if False:
            print locs
            view = ImageViewer(norm)
            ImageViewer(norm_no_min)
            view.show()
            sys.exit()
        norm = norm_no_min
        features['im_norm'] = norm_no_min

    # full-size cell with no fingers/busbars
    cell.remove_cell_template(norm, features)

    if False:
        view = ImageViewer(norm)
        # ImageViewer(im_peaks)
        ImageViewer(features['im_no_fingers'])
        ImageViewer(features['im_no_figners_bbs'])
        view.show()
        sys.exit()

    if 'input_param_skip_features' not in features or int(features['input_param_skip_features']) != 1:
        # find cell features
        wafer_features(features)
        cell.mono_cracks(features)
        finger_defects(features)
        firing_defects(features)
        finger_shape(features)
        dark_areas(features)
        dark_spots(features)
        if 'input_param_no_post_processing' not in features or int(features['input_param_no_post_processing']) != 1:
            feature_combination(features)

        finger_defect_features(features)

    if False:
        # disable until we can do more tuning with Hunter and Juergen
        # - also should be merged with finger_shape
        emitter_gridline_r(norm, features)

    # undo rotation
    if parameters.ORIGINAL_ORIENTATION and features['cell_rotated']:
        for feature in features.keys():
            if ((feature.startswith('im_') or feature.startswith('mask_') or
                     feature.startswith('map_') or feature.startswith('ov_') or
                     feature.startswith('bl_') or feature.startswith('mk_')) and features[feature].ndim == 2):
                features[feature] = features[feature].T[:, ::-1]

    # compute runtime
    t_stop = timeit.default_timer()
    features['runtime'] = t_stop - t_start
def resolution(im, features):
    f = {}
    ip.histogram_percentiles(im, f)
    im /= f['hist_percentile_99.9']
    im_smooth = cv2.GaussianBlur(im, ksize=(0, 0), sigmaX=1, borderType=cv2.BORDER_REPLICATE)

    # find peaks
    circle_r = 3
    footprint = np.zeros((circle_r * 2 + 1, circle_r * 2 + 1), np.uint8)
    ys, xs = pixel_ops.circle_perimeter(3, 3, 3)
    footprint[ys, xs] = 1
    ring_maxes = ndimage.maximum_filter(im, footprint=footprint)
    peak_strength = im - ring_maxes

    peaks = np.zeros_like(im, np.uint8)
    pixel_ops.LocalMaxs(im_smooth, peaks)

    marker_locs = np.where((peak_strength > 0.1) & (peaks == 1))
    marker_im = np.zeros_like(peaks)
    marker_im[marker_locs[0], marker_locs[1]] = 1
    struct = ndimage.generate_binary_structure(2, 1)
    marker_im = ndimage.binary_dilation(marker_im, structure=struct)

    # hough transform to find lines
    min_degree = math.asin(1 / float(im.shape[1]))
    increments = math.pi / min_degree
    angles = np.linspace(-np.pi / 2.0, np.pi / 2.0, increments)
    h, theta, d = hough_line(marker_im, angles)

    # find top line candidates
    lines = []
    hspace, angles, dists = hough_line_peaks(h, theta, d, min_distance=5, min_angle=5)
    for i in range(min(6, len(angles))):
        angle = angles[i]
        dist = dists[i]
        y0 = dist / np.sin(angle)
        y1 = (dist - im.shape[1] * np.cos(angle)) / np.sin(angle)
        x0 = 0
        x1 = im.shape[1] - 1
        lines.append((y0, y1, x0, x1))

    if False:
        plt.figure()
        plt.imshow(marker_im, cmap=cm.gray)
        for (y0, y1, x0, x1) in lines:
            plt.plot((x0, x1), (y0, y1), '-r')
        plt.show()

    # sort by vertical position, and discard top and bottom
    lines.sort(key=lambda x: x[0])
    lines = lines[2:-2]

    # pick strongest (interects most peaks)
    strenghs = []
    for (y0, y1, x0, x1) in lines:
        ys, xs = draw.line(int(round(y0)), x0, int(round(y1)), x1)
        y_mask = ((ys >= 0) & (ys < im.shape[0]))
        ys, xs = ys[y_mask], xs[y_mask]
        strenghs.append(marker_im[ys, xs].sum())
    (y0, y1, x0, x1) = lines[np.argmax(strenghs)]
    line_length = math.sqrt(im.shape[1] ** 2 + (y1 - y0) ** 2)
    # compute distance between pixels (> 1 for non horizontal/vertical lines)
    inter_pixel_dist = line_length / im.shape[1]
    ys, xs = draw.line(int(round(y0)), x0, int(round(y1)), x1)
    y_mask = ((ys >= 0) & (ys < im.shape[0]))
    ys, xs = ys[y_mask], xs[y_mask]
    profile = im[ys, xs]

    if False:
        print inter_pixel_dist
        plt.figure()
        plt.imshow(im, cmap=cm.gray)
        plt.plot((x0, x1), (y0, y1), '-r')
        plt.figure()
        plt.plot(profile)
        plt.show()

    # find average dist between profile peaks
    profile_peaks = np.where((profile > np.roll(profile, 1)) &
                             (profile > np.roll(profile, -1)))[0]
    profile_peaks = profile_peaks[2:-2]
    peaks_dists = profile_peaks[1:] - profile_peaks[:-1]

    # remove outliers (due to missed peak or spurious peak)
    median_dist = np.median(peaks_dists)
    peaks_dists = peaks_dists[((peaks_dists >= median_dist - 1) &
                               (peaks_dists <= median_dist + 1))]

    pixels_per_mm = peaks_dists.mean()
    pixels_per_mm *= inter_pixel_dist

    if False:
        print peaks_dists
        print pixels_per_mm
        plt.figure()
        plt.plot(profile)
        plt.plot(profile_peaks, profile[profile_peaks], 'o')
        plt.show()

    features['pixels_per_mm'] = pixels_per_mm
def feature_extraction(im, features, already_cropped=False):
    t_start = timeit.default_timer()

    # rotation & cropping
    rotated = cropping.correct_cell_rotation(im, features, already_cropped=already_cropped)
    cropped = cropping.crop_cell(rotated, im, features, width=None, already_cropped=already_cropped)

    features['_cropped_f32'] = cropped
    features['im_cropped_u16'] = cropped.astype(np.uint16)
    h, w = cropped.shape

    if False:
        plt.figure()
        plt.plot(cropped.mean(axis=0))
        view = ImageViewer(im)
        ImageViewer(rotated)
        ImageViewer(cropped)
        view.show()

    # find fingers, busbars, etc
    cell.cell_structure(cropped, features)

    if False:
        view = ImageViewer(cropped)
        ImageViewer(features['bl_cropped_u8'])
        view.show()
        sys.exit()

    # normalise
    ip.histogram_percentiles(cropped, features, center_y=h // 2, center_x=w // 2, radius=features['wafer_radius'])
    cell.normalise(cropped, features)
    norm = features['im_norm']

    if False:
        view = ImageViewer(cropped)
        ImageViewer(norm)
        view.show()
        sys.exit()

    # full-size cell with no fingers/busbars
    cell.remove_cell_template(norm, features)

    if False:
        view = ImageViewer(norm)
        # ImageViewer(im_peaks)
        ImageViewer(features['im_no_fingers'])
        ImageViewer(features['im_no_figners_bbs'])
        view.show()
        sys.exit()

    if False:
        import os, glob
        folder = r"C:\Users\Neil\Desktop\crack_interp"
        fns = glob.glob(os.path.join(folder, "*.*"))
        fn_out = os.path.join(folder, "flat_%03d.png" % len(fns))

        im_out = (255 * ip.scale_image(features['im_no_figners_bbs'])).astype(np.uint8)
        ip.save_image(fn_out, im_out)

    if 'input_param_skip_features' not in features or int(features['input_param_skip_features']) != 1:
        # feature extraction
        finger_defects(features)
        bright_areas(features)
        efficiency_analysis(features)
        cell.multi_cracks(features)
        # firing_defects(features)

    # undo rotation
    if parameters.ORIGINAL_ORIENTATION and features['cell_rotated']:
        for feature in features.keys():
            if ((feature.startswith('im_') or feature.startswith('mask_') or
                     feature.startswith('map_') or feature.startswith('ov_') or
                     feature.startswith('bl_') or feature.startswith('mk_')) and features[feature].ndim == 2):
                features[feature] = features[feature].T[:, ::-1]
        if 'impure_edge_side' in features:
            if features['impure_edge_side'] == 0:
                features['impure_edge_side'] = 1
            elif features['impure_edge_side'] == 2:
                features['impure_edge_side'] = 3
            elif features['impure_edge_side'] == 1:
                features['impure_edge_side'] = 2
            elif features['impure_edge_side'] == 3:
                features['impure_edge_side'] = 0

    # compute runtime
    t_stop = timeit.default_timer()
    features['runtime'] = t_stop - t_start
def feature_extraction(im, features, already_cropped=False):
    t_start = timeit.default_timer()

    if 'input_param_num_stripes' in features:
        num_stripes = features['input_param_num_stripes']
    else:
        num_stripes = 6

    features['num_rows'] = 1
    features['num_cols'] = num_stripes

    if 'input_param_multi' in features:
        multi = int(features['input_param_multi']) == 1
    else:
        multi = False

    # rotation & cropping
    rotated = cropping.correct_cell_rotation(im,
                                             features,
                                             already_cropped=already_cropped)
    cropped = cropping.crop_cell(rotated,
                                 im,
                                 features,
                                 width=None,
                                 already_cropped=already_cropped)

    # stripe corners
    corner_tr_x = features['corner_tr_x']
    corner_tr_y = features['corner_tr_y']
    corner_tl_x = features['corner_tl_x']
    corner_tl_y = features['corner_tl_y']
    corner_br_x = features['corner_br_x']
    corner_br_y = features['corner_br_y']
    corner_bl_x = features['corner_bl_x']
    corner_bl_y = features['corner_bl_y']
    if features['cell_rotated']:
        x_diff_l = corner_bl_x - corner_tl_x
        y_diff_l = corner_bl_y - corner_tl_y
        x_diff_r = corner_br_x - corner_tr_x
        y_diff_r = corner_br_y - corner_tr_y
        for i in range(num_stripes):
            p_start = i / float(num_stripes)
            p_stop = (i + 1) / float(num_stripes)
            features["%02d_corner_tl_y" % (i + 1)] = int(
                round(corner_tl_y + (p_start * y_diff_l)))
            features["%02d_corner_tl_x" % (i + 1)] = int(
                round(corner_tl_x + (p_start * x_diff_l)))
            features["%02d_corner_bl_y" % (i + 1)] = int(
                round(corner_tl_y + (p_stop * y_diff_l)))
            features["%02d_corner_bl_x" % (i + 1)] = int(
                round(corner_tl_x + (p_stop * x_diff_l)))

            features["%02d_corner_tr_y" % (i + 1)] = int(
                round(corner_tr_y + (p_start * y_diff_r)))
            features["%02d_corner_tr_x" % (i + 1)] = int(
                round(corner_tr_x + (p_start * x_diff_r)))
            features["%02d_corner_br_y" % (i + 1)] = int(
                round(corner_tr_y + (p_stop * y_diff_r)))
            features["%02d_corner_br_x" % (i + 1)] = int(
                round(corner_tr_x + (p_stop * x_diff_r)))
    else:
        x_diff_t = corner_tr_x - corner_tl_x
        y_diff_t = corner_tr_y - corner_tl_y
        x_diff_b = corner_br_x - corner_bl_x
        y_diff_b = corner_br_y - corner_bl_y
        for i in range(num_stripes):
            p_start = i / float(num_stripes)
            p_stop = (i + 1) / float(num_stripes)
            features["%02d_corner_tl_y" % (i + 1)] = int(
                round(corner_tl_y + (p_start * y_diff_t)))
            features["%02d_corner_tl_x" % (i + 1)] = int(
                round(corner_tl_x + (p_start * x_diff_t)))
            features["%02d_corner_tr_y" % (i + 1)] = int(
                round(corner_tl_y + (p_stop * y_diff_t)))
            features["%02d_corner_tr_x" % (i + 1)] = int(
                round(corner_tl_x + (p_stop * x_diff_t)))

            features["%02d_corner_bl_y" % (i + 1)] = int(
                round(corner_bl_y + (p_start * y_diff_b)))
            features["%02d_corner_bl_x" % (i + 1)] = int(
                round(corner_bl_x + (p_start * x_diff_b)))
            features["%02d_corner_br_y" % (i + 1)] = int(
                round(corner_bl_y + (p_stop * y_diff_b)))
            features["%02d_corner_br_x" % (i + 1)] = int(
                round(corner_bl_x + (p_stop * x_diff_b)))

    features['im_cropped_u16'] = cropped.astype(np.uint16)
    h, w = cropped.shape

    corner_mask = np.ones_like(cropped, np.uint8)
    r, theta = np.empty_like(cropped,
                             np.float32), np.empty_like(cropped, np.float32)
    pixel_ops.CenterDistance(r, theta, features['wafer_middle_y'],
                             features['wafer_middle_x'])
    pixel_ops.ApplyThresholdGT_F32_U8(r, corner_mask, features['wafer_radius'],
                                      0)

    if False:
        print features['cell_rotated']
        plt.figure()
        plt.plot(cropped.mean(axis=0))
        view = ImageViewer(im)
        ImageViewer(rotated)
        ImageViewer(cropped)
        ImageViewer(corner_mask)
        view.show()

    ip.histogram_percentiles(cropped,
                             features,
                             center_y=h // 2,
                             center_x=w // 2,
                             radius=features['wafer_radius'])
    cell.normalise(cropped, features)

    # find cell structure
    f = features.copy()
    cell.cell_structure(cropped, f)
    features['bl_cropped_u8'] = f['bl_cropped_u8']
    ip.histogram_percentiles(cropped,
                             f,
                             center_y=h // 2,
                             center_x=w // 2,
                             radius=features['wafer_radius'])
    cell.normalise(cropped, f)
    cell.remove_cell_template(f['im_norm'], f)

    if 'input_param_skip_features' not in features or int(
            features['input_param_skip_features']) != 1:
        if multi:
            # efficiency analysis
            multi_cell.bright_areas(f)
            multi_cell.efficiency_analysis(f)

            # save results
            features['impure_area_fraction'] = f['impure_area_fraction']
            features['dislocation_area_fraction'] = f[
                'dislocation_area_fraction']

            im_dislocations = f['_foreground']
            dislocation_thresh = f['_dislocation_thresh']
            im_impure = f['_impure']
            impure_thresh = f['_impure_thresh']
        else:
            # cracks
            cell.mono_cracks(f)
            features['mk_cracks_u8'] = f['mk_cracks_u8']
            features['defect_count'] = f['defect_count']
            features['defect_present'] = f['defect_present']
            features['defect_length'] = f['defect_length']
            crack_skel = f['_crack_skel']

        # extract stats from each stripe
        stripe_width = w // num_stripes
        for s in range(num_stripes):
            s1 = int(round(s * stripe_width))
            s2 = int(round(min(w, (s + 1) * stripe_width)))
            stripe = cropped[:, s1:s2]
            mask = corner_mask[:, s1:s2]

            vals = stripe[mask == 1].flat

            features["%02d_hist_harmonic_mean" %
                     (s + 1)] = 1.0 / (1.0 / np.maximum(0.01, vals)).mean()
            features["%02d_hist_median" % (s + 1)] = np.median(vals)
            features["%02d_hist_mean" % (s + 1)] = np.mean(vals)
            features["%02d_hist_percentile_01" %
                     (s + 1)] = stats.scoreatpercentile(vals, 1)
            features["%02d_hist_percentile_99" %
                     (s + 1)] = stats.scoreatpercentile(vals, 99)
            features["%02d_hist_std" % (s + 1)] = np.std(vals)
            features["%02d_hist_cov" %
                     (s + 1)] = features["%02d_hist_std" % (s + 1)] / max(
                         1, features["%02d_hist_mean" % (s + 1)])

            if 'input_param_no_stripe_images' not in features or int(
                    features['input_param_no_stripe_images']) != 1:
                features['im_%02d_u16' % (s + 1)] = stripe.astype(np.uint16)
                features['bl_%02d_cropped_u8' %
                         (s + 1)] = features['bl_cropped_u8'][:, s1:s2]

            if False:
                view = ImageViewer(stripe)
                ImageViewer(mask)
                view.show()

            if multi:
                impure_stripe = im_impure[:, s1:s2]
                dis_stripe = im_dislocations[:, s1:s2]
                features["%02d_dislocation" %
                         (s + 1)] = (dis_stripe > dislocation_thresh).mean()
                features["%02d_impure" %
                         (s + 1)] = (impure_stripe < impure_thresh).mean()
            else:
                crack_stripe = features['mk_cracks_u8'][:, s1:s2]

                if 'input_param_no_stripe_images' not in features or int(
                        features['input_param_no_stripe_images']) != 1:
                    features['mk_%02d_cracks_u8' %
                             (s + 1)] = np.ascontiguousarray(crack_stripe,
                                                             dtype=np.uint8)

                skel_stripe = crack_skel[:, s1:s2]
                features["%02d_defect_length" % (s + 1)] = skel_stripe.sum()
                if features["%02d_defect_length" % (s + 1)] > 0:
                    _, num_ccs = ip.connected_components(crack_stripe)
                    features["%02d_defect_count" % (s + 1)] = num_ccs
                    features["%02d_defect_present" %
                             (s + 1)] = 1 if num_ccs > 0 else 0
                else:
                    features["%02d_defect_count" % (s + 1)] = 0
                    features["%02d_defect_present" % (s + 1)] = 0

    # undo rotation
    if parameters.ORIGINAL_ORIENTATION and features['cell_rotated']:
        features['num_rows'], features['num_cols'] = features[
            'num_cols'], features['num_rows']
        for feature in features.keys():
            if ((feature.startswith('im_') or feature.startswith('ov_')
                 or feature.startswith('bl_') or feature.startswith('mk_'))
                    and features[feature].ndim == 2):
                features[feature] = features[feature].T[:, ::-1]

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