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 cell_structure(cropped, features):
    # create a map of distance from each pixel to center
    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'])
    features['im_center_dist_im'] = r
    features['im_center_theta_im'] = theta
    if False:
        # in multi mode, don't estimate radius
        features['wafer_radius'] = features['_cell_diag'] + 1
    else:
        features['im_center_dist_rot'] = r
        features['im_center_theta_rot'] = theta

    # determine properties of the cell pattern
    if features['_fingers_grid']:
        find_fingers_perc(cropped, features)
    else:
        find_fingers(cropped, features)
    cell_edge_width(cropped, features)
    find_busbars(cropped, features)
    create_cell_mask(cropped, features)
def ring_strength(im, features):
    DEBUG = False

    # remove a lot of the defects by taking the max of a few positions at equal distance
    h, w = im.shape
    if 'im_center_dist_rot' in features:
        # being called by wafers alg
        dist = features['im_center_dist_rot']
        theta = features['im_center_theta_rot']
        center_x = int(round(features['wafer_middle_x']))
        center_y = int(round(features['wafer_middle_y']))
        radius = int(features['wafer_radius'] - 10)
    else:
        dist, theta = np.empty_like(im, np.float32), np.empty_like(im, np.float32)
        pixel_ops.CenterDistance(dist, theta, features['center_y'], features['center_x'])
        center_x = int(round(features['center_x']))
        center_y = int(round(features['center_y']))
        radius = int(features['radius'] - 10)

    corner_filled, corner_avg = fill_corners(im, features, 10, dist)

    if False:
        view = ImageViewer(im)
        ImageViewer(corner_filled)
        view.show()
        sys.exit()

    maxes = corner_filled.copy()
    rotated = np.empty_like(im)
    for r in [-4.0, -2.0, 2.0, 4.0]:
        rot_mat = cv2.getRotationMatrix2D((center_x, center_y), r, 1.0)
        cv2.warpAffine(corner_filled, rot_mat, (w, h), flags=cv2.INTER_LINEAR,
                       borderMode=cv2.BORDER_CONSTANT, dst=rotated, borderValue=0)
        maxes = np.maximum(maxes, rotated)

    if False:
        view = ImageViewer(im)
        ImageViewer(maxes)
        view.show()
        sys.exit()

    # A spiral smooth
    # - get coordinates that start at the middle and rotate outwards
    dist = np.round(dist).astype(np.int32)
    dist_flat = dist.flat
    theta_flat = theta.flat

    # first sort by distance from center
    args = np.argsort(dist_flat)
    dist_flat = dist_flat[args]
    theta_flat = theta_flat[args]

    # for pixels at an equal distance, sort by theta
    boundaires = np.where((dist_flat - np.roll(dist_flat, 1)) > 0)[0]
    for i in range(len(boundaires) - 1):
        start = boundaires[i]
        stop = boundaires[i + 1]
        args_t = np.argsort(theta_flat[start:stop])

        args[start:stop] = args[start:stop][args_t]

    # apply smoothing to flattened, ordered image
    im1D = maxes.flatten()
    im1D = im1D[args]
    if False:
        im_smooth = ndimage.gaussian_filter1d(im1D, sigma=30)
    else:
        # faster: smooth downsized
        im_smooth = ndimage.gaussian_filter1d(im1D[::3], sigma=10)
        zoom = len(im1D) / float(len(im_smooth))
        im_smooth = ndimage.zoom(im_smooth, zoom=zoom, order=0)
        assert len(im_smooth) == len(im1D)

    im_rings = im_smooth[np.argsort(args)].reshape((h, w))
    if False:
        im_rings, _ = cz_wafer.fill_corners_edges(im_rings, features, 4, corner_fill=corner_avg)

    if False:
        view = ImageViewer(im)
        ImageViewer(im_rings)
        view.show()
        sys.exit()

    if DEBUG:
        plt.figure()

    rotations = range(0, 361, 10)
    dip_profiles = np.zeros((len(rotations), radius), np.float32)
    circle_strengths = []
    for e, r in enumerate(rotations):
        ys, xs = draw.line(center_y, center_x,
                           center_y + int(radius * math.cos(math.radians(r))),
                           center_x + int(radius * math.sin(math.radians(r))))
        mask = ((ys >= 0) & (xs >= 0) & (ys < h) & (xs < w))
        ys, xs = ys[mask], xs[mask]
        if DEBUG:
            im[ys, xs] = 0

        profile = im_rings[ys, xs]
        sample_r = dist[ys[-1], xs[-1]]

        # resample to standard length
        rs = np.linspace(0, sample_r, num=len(profile), endpoint=True)
        f = interpolate.interp1d(rs, profile)
        profile = f(np.arange(sample_r))

        if parameters.RING_SIGMA1 > 0:
            profile = ndimage.gaussian_filter1d(profile, sigma=parameters.RING_SIGMA1)
        if parameters.RING_SIGMA2 > 0:
            profile_upper = ndimage.gaussian_filter1d(profile, sigma=parameters.RING_SIGMA2)

        # interpolate peaks
        peaks = np.where((profile_upper > np.roll(profile_upper, 1)) &
                         (profile_upper > np.roll(profile_upper, -1)))[0]
        if len(peaks) < 2:
            dip_profiles[e, :len(profile)] = 0
        else:
            f = interpolate.interp1d(peaks, profile_upper[peaks])
            xs = np.arange(peaks[0], peaks[-1])
            f_upper = profile.copy()
            f_upper[xs] = f(xs)

            # find dips
            dip_shape = f_upper - profile

            # ignore middle (small artifacts near middle have disproportionally high radius)
            dip_shape[:100] = 0
            dip_profiles[e, :len(profile)] = dip_shape

            # a second strategy for telling difference between slugs with 1 small dark
            #  and lots/large rings
            zeros = np.where(dip_shape == 0)[0]
            gaps = np.where(zeros[1:] - zeros[:-1] > 1)[0]
            big_dips = []
            for g in gaps:
                start, stop = zeros[g], zeros[g + 1]
                dip_strength = 1000.0 * dip_shape[start:stop].sum() / float(len(profile))
                if dip_strength > 0.5:
                    big_dips.append(dip_strength)
            circle_strengths.append(np.array(big_dips).sum())

        if DEBUG:
            plt.plot(profile)
            plt.plot(dip_profiles[e, :])
            plt.plot(f_upper, '--')

    path_xs = np.zeros(dip_profiles.shape[0], np.int32)
    path_strength = np.zeros_like(dip_profiles, np.float32)
    pixel_ops.strongest_path(dip_profiles, path_strength, path_xs, 15)
    path_vals = dip_profiles[np.arange(dip_profiles.shape[0]), path_xs]

    if False:
        dip_profiles[np.arange(dip_profiles.shape[0]), path_xs] = dip_profiles.max() * 1.1
        view = ImageViewer(dip_profiles)
        ImageViewer(path_strength)
        plt.figure()
        plt.plot(path_strength[-1, :])
        plt.figure()
        plt.plot(path_vals)
        view.show()
        sys.exit()

    # a path might have a few peaks due to non-ring artifacts.
    # - ignore some of the highest areas
    path2 = path_vals.copy()
    for i in range(parameters.NUM_PEAKS):
        m = np.argmax(path2)
        path2[max(0, m - 2):min(path2.shape[0], m + 3)] = 0
    path2[[0, -1]] = 0
    # plt.figure()
    # plt.plot(path_vals)
    # plt.plot(path2)
    # plt.show()

    features['circle_strength'] = 100 * path2.max()
    features['circle_strength_2'] = np.median(circle_strengths) * 10
    # print features['circle_strength_2']

    if DEBUG:
        # plt.plot(dip_profiles.sum(axis=0))
        print features['circle_strength']
        ImageViewer(im)
        ImageViewer(im_rings)
        dip_profiles[np.arange(dip_profiles.shape[0]), path_xs] = dip_profiles.max() * 1.1
        ImageViewer(dip_profiles)
        plt.figure()
        plt.plot(path_vals)
        plt.plot(path2)
        plt.show()

    return im_rings
def find_slug(im, features):
    h, w = im.shape
    h2, w2 = h // 2, w // 2

    # highlight edges in each quadrant
    edgesH = cv2.Sobel(im, cv2.CV_32F, 0, 1)
    edgesV = cv2.Sobel(im, cv2.CV_32F, 1, 0)
    corner_edges = np.zeros_like(im)
    corner_edges[:h2, :w2] = edgesH[:h2, :w2] + edgesV[:h2, :w2]
    corner_edges[:h2, -w2:] = edgesH[:h2, -w2:] - edgesV[:h2, -w2:]
    corner_edges[-h2:, -w2:] = -1 * edgesH[-h2:, -w2:] - edgesV[-h2:, -w2:]
    corner_edges[-h2:, :w2] = -1 * edgesH[-h2:, :w2] + edgesV[-h2:, :w2]

    # find points on the corners
    left = corner_edges[:, :w2]
    ys = np.arange(left.shape[0])
    xs = np.argmax(left, axis=1)
    mask = corner_edges[ys, xs] > 0.4
    ys = ys[mask]
    xs = xs[mask]
    right = corner_edges[:, w2:]
    ys2 = np.arange(right.shape[0])
    xs2 = w2 + np.argmax(right, axis=1)
    mask = corner_edges[ys2, xs2] > 0.4
    ys2 = ys2[mask]
    xs2 = xs2[mask]
    ys = np.r_[ys, ys2]
    xs = np.r_[xs, xs2]

    if False:
        ImageViewer(corner_edges)
        plt.figure()
        plt.imshow(im, cmap="gray")
        plt.plot(xs, ys, "o")
        plt.show()
        sys.exit()

    t1 = default_timer()

    # user Hough transform to vote on most likely center/radius
    # - assume true center is within 150 pixels of image middle

    # phrase 1: rough fit
    MAX_OFFSET = 200
    step = 3
    acc_ys = np.arange(h2 - MAX_OFFSET, h2 + MAX_OFFSET + 1, step)
    acc_xs = np.arange(w2 - MAX_OFFSET, w2 + MAX_OFFSET + 1, step)
    diag = math.sqrt(h2 ** 2 + w2 ** 2)
    min_r = int(0.5 * diag)
    max_r = int(diag)
    acc = np.zeros((acc_ys.shape[0], acc_xs.shape[0], max_r - min_r), np.int32)
    pixel_ops.CircleHoughAcc2(ys, xs, acc_ys, acc_xs, acc, min_r, max_r)
    acc = ndimage.gaussian_filter(acc.astype(np.float32), sigma=(1, 1, 0))
    i, j, r = ndimage.maximum_position(acc)
    middle_y, middle_x, radius = acc_ys[i], acc_xs[j], r + min_r

    if True:
        # phrase 2: fine tune
        acc_ys = np.arange(middle_y - (2 * step), middle_y + (2 * step) + 1)
        acc_xs = np.arange(middle_x - (2 * step), middle_x + (2 * step) + 1)
        min_r = int(radius - 10)
        max_r = int(radius + 10)
        acc = np.zeros((acc_ys.shape[0], acc_xs.shape[0], max_r - min_r), np.int32)
        pixel_ops.CircleHoughAcc2(ys, xs, acc_ys, acc_xs, acc, min_r, max_r)
        acc = ndimage.gaussian_filter(acc.astype(np.float32), sigma=(1, 1, 0))
        i, j, r = ndimage.maximum_position(acc)

        middle_y, middle_x, radius = acc_ys[i], acc_xs[j], r + min_r

    features['center_y'] = middle_y
    features['center_x'] = middle_x
    features['radius'] = radius
    features['crop_rotation'] = 0
    features['crop_left'] = 0
    features['crop_right'] = im.shape[1] - 1
    features['crop_top'] = 0
    features['crop_bottom'] = im.shape[0] - 1

    mask = np.zeros_like(im, np.uint8)
    r, theta = np.empty_like(im, np.float32), np.empty_like(im, np.float32)
    pixel_ops.CenterDistance(r, theta, middle_y, middle_x)
    pixel_ops.ApplyThresholdGT_F32_U8(r, mask, radius, 1)

    features['bl_uncropped_u8'] = mask
    features['bl_cropped_u8'] = mask

    if False:
        print default_timer() - t1
        rgb = create_overlay(im, features)
        view = ImageViewer(rgb)
        # ImageViewer(mask)
        view.show()
        sys.exit()
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 '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