def create_exclusion_cube(img, image_cube, dark_pixels, convex_pixels, axis, convex_threshold): timeStart("get slopes") slopes = get_slopes(img, axis=axis) timeEnd("get slopes") Debug.save_image("ridges", "slopes", slopes) exclusion_cube = np.zeros(image_cube.shape, dtype=bool) exclusion_cube[:,:,0] = dark_pixels | convex_pixels | slopes \ | (image_cube[:,:,0] < -convex_threshold) Debug.save_image("ridges", "exclusion_cube_base", exclusion_cube[:, :, 0]) num_scales = image_cube.shape[2] for i in range(1, num_scales): # each layer of the exclusion cube contains the previous layer # plus all convex pixels in the current image_cube layer exclusion_cube[:,:,i] = (exclusion_cube[:,:,i-1] \ | (image_cube[:,:,i] < -convex_threshold)) Debug.save_image("ridges", "exclusion_cube-" + pad(i), exclusion_cube[:, :, i]) return exclusion_cube
def get_meanlines(in_file, out_file, roi_file, scale=1, debug_dir=False): from lib.debug import Debug if debug_dir: Debug.set_directory(debug_dir) from lib.timer import timeStart, timeEnd from lib.load_image import get_image from lib.geojson_io import get_features, save_features from lib.polygon_mask import mask_image from lib.meanline_detection import detect_meanlines, meanlines_to_geojson timeStart("get meanlines") timeStart("read image") image = get_image(in_file) timeEnd("read image") roi_polygon = get_features(roi_file)["geometry"]["coordinates"][0] timeStart("mask image") masked_image = mask_image(image, roi_polygon) timeEnd("mask image") meanlines = detect_meanlines(masked_image, scale=scale) timeStart("convert to geojson") meanlines_as_geojson = meanlines_to_geojson(meanlines) timeEnd("convert to geojson") timeStart("saving as geojson") save_features(meanlines_as_geojson, out_file) timeEnd("saving as geojson") timeEnd("get meanlines")
def local_min(image, min_distance=2): if np.amax(image) <= 1: image = np.uint8(image * 255) selem = np.ones((2 * min_distance + 1, 2 * min_distance + 1)) timeStart("morphological erosion") img = erosion(image, selem) timeEnd("morphological erosion") return image == img
def extract_ridge_data(img, sobel_axis, dog_axis, footprint, dark_pixels, convex_pixels, sigma_list, convex_threshold, low_threshold): ''' Returns ------- ridges : 2D boolean array True at every pixel considered to be a ridge. max_values : 2D float array The maximum values across all sigma scales of image_cube. max_scales : 2D int array The scales at which the image_cube took on those maximum values. ''' num_scales = len(sigma_list) - 1 timeStart("create difference of gaussian image cube at %s scales" % num_scales) image_cube = create_image_cube(img, sigma_list, axis=dog_axis) timeEnd("create difference of gaussian image cube at %s scales" % num_scales) timeStart("create exclusion cube") exclusion = create_exclusion_cube(img, image_cube=image_cube, dark_pixels=dark_pixels, convex_pixels=convex_pixels, axis=sobel_axis, convex_threshold=convex_threshold) timeEnd("create exclusion cube") timeStart("find image cube maxima") maxima = find_valid_maxima(image_cube, footprint, exclusion, low_threshold) timeEnd("find image cube maxima") # set all non-maxima points in image_cube to 0 timeStart("suppress non-maxima") image_cube[~maxima] = 0 timeEnd("suppress non-maxima") timeStart("collapse cubes") # ridges is a 2D array that is true everywhere # that maxima has at least one true value in any scale ridges = np.amax(maxima, axis=-1) max_values = np.amax(image_cube, axis=-1) max_scales = np.argmax(image_cube, axis=-1) timeEnd("collapse cubes") return ridges, max_values, max_scales
def get_endpoint_data(features): """ Given a parsed GeoJson of segment data, it returns a raw data dictionary of the start and endpoints of all segments as well as averages and std deviations on the y axis. """ all_x = [] all_y = [] ids = [] average_y = [] std_deviation_y = [] startpoints = [] endpoints = [] timeStart("get coordinates") for feature in features["features"]: coordinates = np.array( feature["geometry"]["coordinates"] ) # turn the list of coords into a fancy 2D numpy array all_x.append( coordinates[:, 0] ) # numpy arrays are indexed [row, column], so [:, 0] means "all rows, 0th column" all_y.append(coordinates[:, 1]) ids.append(feature["id"]) timeEnd("get coordinates") for values in xrange(len(all_y)): average_y.append(np.mean(all_y[values])) std_deviation_y.append(np.std(all_y[values])) for starts in xrange(len(all_y)): x1 = all_x[starts][0] y1 = all_y[starts][0] x2 = all_x[starts][len(all_x[starts]) - 1] y2 = all_y[starts][len(all_y[starts]) - 1] startpoint = [x1, y1] endpoint = [x2, y2] startpoints.append(startpoint) endpoints.append(endpoint) return { "ids": ids, "startpoints": startpoints, "endpoints": endpoints, "average_y": average_y, "std_deviation_y": std_deviation_y }
def get_intersections(in_file, out_file, roi_file, debug_dir=False): if debug_dir: from lib.dir import ensure_dir_exists ensure_dir_exists(debug_dir) from lib.timer import timeStart, timeEnd from lib.intersection_detection import find_intersections from lib.load_image import get_image from lib.load_geojson import get_features from lib.polygon_mask import mask_image from lib.geojson_io import save_features from skimage.io import imsave timeStart("get intersections") timeStart("read image") grayscale_image = get_image(in_file) timeEnd("read image") roi_polygon = get_features(roi_file)["geometry"]["coordinates"][0] timeStart("mask image") masked_image = mask_image(grayscale_image, roi_polygon) timeEnd("mask image") intersections = find_intersections(masked_image.filled(False), figure=False) timeStart("saving to " + out_file) intersections_as_geojson = intersections.asGeoJSON() save_features(intersections_as_geojson, out_file) timeEnd("saving to " + out_file) if debug_dir: debug_filepath = debug_dir + "/intersections.png" timeStart("saving to " + debug_filepath) intersections_as_image = intersections.asImage().astype(float) imsave(debug_filepath, intersections_as_image) timeEnd("saving to " + debug_filepath) timeEnd("get intersections")
def get_roi(in_file, out_file, scale=1, debug_dir=False): if debug_dir: from lib.dir import ensure_dir_exists ensure_dir_exists(debug_dir) from lib.timer import timeStart, timeEnd from lib.load_image import get_image from lib.roi_detection import get_roi, corners_to_geojson from lib.geojson_io import save_features timeStart("ROI") timeStart("read image") image = get_image(in_file) timeEnd("read image") corners = get_roi(image, scale=scale) timeStart("convert to geojson") corners_as_geojson = corners_to_geojson(corners) timeEnd("convert to geojson") if debug_dir: from lib.polygon_mask import mask_image from scipy import misc roi_polygon = corners_as_geojson["geometry"]["coordinates"][0] timeStart("mask image") masked_image = mask_image(image, roi_polygon) timeEnd("mask image") misc.imsave(debug_dir+"/masked_image.png", masked_image.filled(0)) if out_file: timeStart("saving as geojson") save_features(corners_as_geojson, out_file) timeEnd("saving as geojson") else: print corners_as_geojson timeEnd("ROI")
def binary_image(image, markers_trace=None, markers_background=None, min_trace_size=6, min_background_size=4): ''' Creates a binary image. Parameters ------------ image : numpy array Can either be a color (3-D) or grayscale (2-D) image. Returns --------- image_bin : 2-D Boolean numpy array A 2-D array with the same shape as the input image. Foreground pixels are True, and background pixels are False. ''' if image.ndim != 2: image = color.rgb2gray(image) if markers_background is None: markers_background = get_background_markers(image) if markers_trace is None: markers_trace = get_trace_markers(image, markers_background) timeStart("watershed segmentation") image_bin = watershed_segmentation(image, markers_trace, markers_background) timeEnd("watershed segmentation") #image_bin = image_bin & (~ fill_corners(canny(image))) timeStart("remove small segments and edges") image_bin = remove_small_segments_and_edges(image_bin, min_trace_size, min_background_size) timeEnd("remove small segments and edges") return image_bin
def resize_image(in_file, out_file, scale): timeStart("load image") image = get_image(in_file) timeEnd("load image") timeStart("resize image") resized_image = imresize(image, scale) timeEnd("resize image") timeStart("save image") imsave(out_file, resized_image) timeEnd("save image")
def get_thresholded_image(in_file, out_file): from lib.timer import timeStart, timeEnd from lib.otsu_threshold_image import otsu_threshold_image from lib.load_image import get_image from scipy import misc timeStart("read image") grayscale_image = get_image(in_file) timeEnd("read image") timeStart("threshold image") thresholded_image = otsu_threshold_image(grayscale_image) timeEnd("threshold image") timeStart("save image") misc.imsave(out_file, thresholded_image) timeEnd("save image")
def get_segments(in_file, out_file, intersections_file): from lib.timer import timeStart, timeEnd from lib.load_image import get_image from lib.load_geojson import get_features from lib.segment_detection import get_segments from lib.segment_detection import save_segments_as_geojson timeStart("get segments") timeStart("read image") image = get_image(in_file) timeEnd("read image") intersections = get_features(intersections_file) timeStart("calculate segments") segments = get_segments(image, intersections) timeEnd("calculate segments") save_segments_as_geojson(segments, out_file) timeEnd("get segments")
def flatten_background(img, prob_background=1, num_blocks=None, block_dims=None, return_background=False, img_gray=None): ''' Finds the pixel intensity at every location in the image below which the pixel is likely part of the dark background. Pixels darker than this threshold are replaced by the value of the threshold at its location. This eliminates unusually dark regions. Parameters ------------ img : 2-D numpy array The grayscale image. Can be either floats on the interval [0,1] or ints on the interval [0,255]. prob_background : float, optional The minimum probability (estimated) that a pixel below the threshold is part of the backround. Must be <= 1. Lower numbers will result in higher thresholds. num_blocks : int, optional The number of blocks of the image to use to create the threshold. block_dims : tuple or numpy array The dimensions of the rectangular blocks. Dimensions should be less than the dimensions of the image. If left unspecified, the blocks will be squares with area approximately equal to two times the area of the image, divided by num_blocks. Returns -------- flattened : 2-D numpy array An image equal to the input grayscale image where pixels are above the brightness of the background threshold and equal to the threshold everywhere else. background : 2-D numpy array of bools The pixels below the background threshold. ''' # Default number of blocks assumes 500x500 blocks are a good size if num_blocks is None: num_blocks = int(np.ceil(2 * img.size / 250000)) timeStart("calculate background threshold with %s blocks" % num_blocks) get_background_thresh = make_background_thresh_fun(prob_background) background_level = threshold(img, get_background_thresh, num_blocks, block_dims, smoothing=0.003) timeEnd("calculate background threshold with %s blocks" % num_blocks) timeStart("select dark pixels") dark_pixels = img < background_level timeEnd("select dark pixels") timeStart("raise dark pixels") flattened = np.where(dark_pixels, background_level, img) timeEnd("raise dark pixels") local_min_gray = local_min(img_gray) Debug.save_image("threshold", "local_min_gray", local_min_gray) timeStart("union dark pixels, minima, and mask regions") background = dark_pixels | local_min_gray | img.mask timeEnd("union dark pixels, minima, and mask regions") Debug.save_image("threshold", "background", background) ''' background_with_mask barely differs from background, but it somehow differs enough to cause the pipeline to find different numbers of intersections and segments. The differences seem to be at the boundary of the mask, so I suspect they only result in segments that will end up being deleted anyway. TODO: Come back and check this after adding automated deletion of segments that pass outside the ROI (assuming this isn't already a thing). bennlich 9/21 ''' # local_min_mask = local_min(img) # Debug.save_image("threshold", "local_min_mask", local_min_mask) # timeStart("union dark pixels, minima, and mask regions") # background_with_mask = dark_pixels | local_min_mask | img.mask # timeEnd("union dark pixels, minima, and mask regions") # Debug.save_image("threshold", "background_with_mask", background_with_mask) if return_background is False: return flattened else: return (flattened, background)
def analyze_image(in_file, out_dir, stats_file=False, scale=1, debug_dir=False, fix_seed=False): from lib.dir import ensure_dir_exists from lib.debug import Debug from lib.stats_recorder import Record if debug_dir: Debug.set_directory(debug_dir) if fix_seed: Debug.set_seed(1234567890) if stats_file: Record.activate() ensure_dir_exists(out_dir) from lib.timer import timeStart, timeEnd from lib.load_image import get_grayscale_image, image_as_float from skimage.morphology import medial_axis from lib.roi_detection import get_roi, corners_to_geojson from lib.polygon_mask import mask_image from lib.meanline_detection import detect_meanlines, meanlines_to_geojson from lib.threshold import flatten_background from lib.ridge_detection import find_ridges from lib.binarization import binary_image from lib.intersection_detection import find_intersections from lib.trace_segmentation import get_segments, segments_to_geojson from lib.geojson_io import save_features, save_json from lib.utilities import encode_labeled_image_as_rgb from scipy import misc import numpy as np paths = { "roi": out_dir + "/roi.json", "meanlines": out_dir + "/meanlines.json", "intersections": out_dir + "/intersections.json", "intersections_raster": out_dir + "/intersections_raster.png", "segments": out_dir + "/segments.json", "segment_regions": out_dir + "/segment_regions.png", "segment_assignments": out_dir + "/segment_assignments.json" } timeStart("get all metadata") timeStart("read image") img_gray = image_as_float(get_grayscale_image(in_file)) timeEnd("read image") print "\n--ROI--" timeStart("get region of interest") corners = get_roi(img_gray, scale=scale) timeEnd("get region of interest") timeStart("convert roi to geojson") corners_as_geojson = corners_to_geojson(corners) timeEnd("convert roi to geojson") timeStart("saving roi as geojson") save_features(corners_as_geojson, paths["roi"]) timeEnd("saving roi as geojson") print "\n--MASK IMAGE--" roi_polygon = corners_as_geojson["geometry"]["coordinates"][0] timeStart("mask image") masked_image = mask_image(img_gray, roi_polygon) timeEnd("mask image") Debug.save_image("main", "masked_image", masked_image.filled(0)) if Record.active: non_masked_values = 255 * masked_image.compressed() bins = np.arange(257) image_hist, _ = np.histogram(non_masked_values, bins=bins) Record.record("roi_intensity_hist", image_hist.tolist()) print "\n--MEANLINES--" meanlines = detect_meanlines(masked_image, corners, scale=scale) timeStart("convert meanlines to geojson") meanlines_as_geojson = meanlines_to_geojson(meanlines) timeEnd("convert meanlines to geojson") timeStart("saving meanlines as geojson") save_features(meanlines_as_geojson, paths["meanlines"]) timeEnd("saving meanlines as geojson") print "\n--FLATTEN BACKGROUND--" img_dark_removed, background = \ flatten_background(masked_image, prob_background=0.95, return_background=True, img_gray=img_gray) Debug.save_image("main", "flattened_background", img_dark_removed) masked_image = None print "\n--RIDGES--" timeStart("get horizontal and vertical ridges") ridges_h, ridges_v = find_ridges(img_dark_removed, background) ridges = ridges_h | ridges_v timeEnd("get horizontal and vertical ridges") print "\n--THRESHOLDING--" timeStart("get binary image") img_bin = binary_image(img_dark_removed, markers_trace=ridges, markers_background=background) timeEnd("get binary image") img_dark_removed = None background = None print "\n--SKELETONIZE--" timeStart("get medial axis skeleton and distance transform") img_skel, dist = medial_axis(img_bin, return_distance=True) timeEnd("get medial axis skeleton and distance transform") Debug.save_image("skeletonize", "skeleton", img_skel) print "\n--INTERSECTIONS--" intersections = find_intersections(img_bin, img_skel, dist, figure=False) timeStart("convert to geojson") intersection_json = intersections.asGeoJSON() timeEnd("convert to geojson") timeStart("saving intersections as geojson") save_features(intersection_json, paths["intersections"]) timeEnd("saving intersections as geojson") timeStart("convert to image") intersection_image = intersections.asImage() timeEnd("convert to image") Debug.save_image("intersections", "intersections", intersection_image) timeStart("save intersections raster") misc.imsave(paths["intersections_raster"], intersection_image) timeEnd("save intersections raster") print "\n--SEGMENTS--" timeStart("get segments") segments, labeled_regions = \ get_segments(img_gray, img_bin, img_skel, dist, intersection_image, ridges_h, ridges_v, figure=True) timeEnd("get segments") timeStart("encode labels as rgb values") rgb_segments = encode_labeled_image_as_rgb(labeled_regions) timeEnd("encode labels as rgb values") timeStart("save segment regions") misc.imsave(paths["segment_regions"], rgb_segments) timeEnd("save segment regions") timeStart("convert centerlines to geojson") segments_as_geojson = segments_to_geojson(segments) timeEnd("convert centerlines to geojson") timeStart("saving centerlines as geojson") save_features(segments_as_geojson, paths["segments"]) timeEnd("saving centerlines as geojson") #return (img_gray, ridges, img_bin, intersections, img_seg) # return segments # detect center lines # connect segments # output data time_elapsed = timeEnd("get all metadata") Record.record("time_elapsed", float("%.2f" % time_elapsed)) if (stats_file): Record.export_as_json(stats_file) # TODO: refactor this into some sort of status module. # For now, since this is our only problematic status, # it's hard to know what to generalize. Eventually # we might want to flag several different statuses # for specific conditions. max_segments_reasonable = 11000 if (len(segments) > max_segments_reasonable): print "STATUS>>>problematic<<<" else: print "STATUS>>>complete<<<"
def find_ridges(img, dark_pixels, min_sigma=0.7071, max_sigma=30, sigma_ratio=1.9, min_ridge_length=15, low_threshold=0.002, high_threshold=0.006, convex_threshold=0.00015, figures=True): ''' The values for min_sigma, max_sigma, and sigma_ratio are hardcoded, but they ought to be a function of the scale parameter. They're related to the minimum and maximum expected trace width in pixels. If max_sigma is too small, the algorithm misses ridges of thick traces. Need to do more thinking about how this function works. ''' # num_scales is the number of scales at which to compute a difference of gaussians # the following line in words: the number of times you need to multiply # min_sigma by sigma_ratio to get max_sigma num_scales = int(log(float(max_sigma) / min_sigma, sigma_ratio)) + 1 # a geometric progression of standard deviations for gaussian kernels sigma_list = create_sigma_list(min_sigma, sigma_ratio, np.arange(num_scales + 1)) # convex_pixels is an image of regions with positive second derivative timeStart("get convex pixels") convex_pixels = get_convex_pixels(img, convex_threshold) timeEnd("get convex pixels") Debug.save_image("ridges", "convex_pixels", convex_pixels) timeStart("find horizontal ridges") footprint_h = np.ones((3, 1, 3), dtype=bool) ridges_h, max_values_h, max_scales_h = \ extract_ridge_data(img, sobel_axis=1, dog_axis=0, footprint=footprint_h, dark_pixels=dark_pixels, convex_pixels=convex_pixels, sigma_list=sigma_list, convex_threshold=convex_threshold, low_threshold=low_threshold) timeEnd("find horizontal ridges") timeStart("find vertical ridges") footprint_v = np.ones((1, 3, 3), dtype=bool) ridges_v, max_values_v, max_scales_v = \ extract_ridge_data(img, sobel_axis=0, dog_axis=1, footprint=footprint_v, dark_pixels=dark_pixels, convex_pixels=convex_pixels, sigma_list=sigma_list, convex_threshold=convex_threshold, low_threshold=low_threshold) timeEnd("find vertical ridges") # Horizontal ridges need to be prominent ridges_h = ridges_h & (max_values_h >= high_threshold) # Vertical ridges need to either be prominent or highly connected ridges_v = (ridges_v & ((max_values_v >= high_threshold) | remove_small_objects( ridges_v, min_ridge_length, connectivity=2))) timeStart("aggregate information about maxima of horizontal ridges") sigmas_h = create_sigma_list(min_sigma, sigma_ratio, max_scales_h) ridge_data_h = compile_ridge_data(sigmas_h, ridges_h, max_values_h) timeEnd("aggregate information about maxima of horizontal ridges") timeStart("prioritize horizontal regions") horizontal_regions = get_ridge_region_horiz(ridge_data_h, img.shape) ridges_v = ridges_v & (horizontal_regions == 0) timeEnd("prioritize horizontal regions") Debug.save_image("ridges", "vertical_ridges", ridges_v) Debug.save_image("ridges", "horizontal_ridges", ridges_h) if figures == True: return (ridges_h, ridges_v) else: # Aggregate information about maxima of vertical ridges sigmas_v = create_sigma_list(min_sigma, sigma_ratio, max_scales_v) ridge_data_v = compile_ridge_data(sigmas_v, ridges_v, max_values_v) return (ridge_data_h, ridge_data_v)
def img_seg_to_seg_objects(img_seg, num_segments, ridges_h, ridges_v, img_gray): ''' Creates segment objects from an array of labeled pixels. Parameters ------------ img_seg : 2-D numpy array of ints An array with each pixel labeled according to its segment. Returns -------- segments : list of segments A list containing all the trace segments. ''' # segment_coordinates becomes a list of segments, where # each segment is a list of all of its pixel coordinates segment_coordinates = [[] for i in xrange(num_segments)] timeStart("get segment coordinates") it = np.nditer(img_seg, flags=['multi_index']) while not it.finished: if it[0] == 0: it.iternext() continue segment_idx = int(it[0] - 1) segment_coordinates[segment_idx].append(np.array(it.multi_index)) it.iternext() # if the number of segments is small relative # to the size of the image, the commented algorithm below # is faster than that above # timeStart("nonzero") # segment_coords = np.nonzero(img_seg) # timeEnd("nonzero") # coords = np.column_stack(segment_coords) # timeStart("nzvals") # nzvals = img_seg[segment_coords[0], segment_coords[1]] # timeEnd("nzvals") # timeStart("index coords") # segment_coordinates = [ coords[nzvals == k] for k in range(1, num_segments + 1) ] # timeEnd("index coords") timeEnd("get segment coordinates") segments = {} timeStart("create segment objects") for (segment_idx, pixel_coords) in enumerate(segment_coordinates): segment_id = segment_idx + 1 pixel_coords = np.array(pixel_coords) values = get_image_values(img_gray, pixel_coords) ridge_line = get_ridge_line(ridges_h, ridges_v, pixel_coords) new_segment = segment(coords=pixel_coords, values=np.array(values), id=segment_id, ridge_line=ridge_line) if (new_segment.has_center_line): center_line_values = get_image_values( img_gray, new_segment.center_line.coords) new_segment.add_center_line_values(center_line_values) segments[segment_idx] = new_segment timeEnd("create segment objects") return segments
def get_segments(img_gray, img_bin, img_skel, dist, img_intersections, ridges_h, ridges_v, figure=False): timeStart("canny edge detection") image_canny = canny(img_gray) timeEnd("canny edge detection") Debug.save_image("segments", "edges", image_canny) # Strange: just noticed that none of the below had # ever been getting used for as long as this file # has existed. # timeStart("fill canny corners") # filled_corners_canny = fill_corners(image_canny) # timeEnd("fill canny corners") # Debug.save_image("segments", "edges_with_corners", filled_corners_canny) # timeStart("subtract canny corners from image") # img_bin = img_bin & (~ filled_corners_canny) # timeEnd("subtract canny corners from image") # Debug.save_image("segments", "binary_image_minus_edges", img_bin) timeStart("sobel filter") image_sobel = sobel(img_gray) timeEnd("sobel filter") Debug.save_image("segments", "slopes", image_sobel) timeStart("otsu threshold") steep_slopes = image_sobel > threshold_otsu(image_sobel) timeEnd("otsu threshold") Debug.save_image("segments", "steep_slopes", steep_slopes) timeStart("binary erosion") steep_slopes = binary_erosion(steep_slopes, square(3, dtype=bool)) timeEnd("binary erosion") Debug.save_image("segments", "eroded_steep_slopes", steep_slopes) timeStart("subtract regions from skeleton") segments_bin = (img_skel & (~img_intersections) & (~image_canny) & (~steep_slopes)) timeEnd("subtract regions from skeleton") Debug.save_image( "segments", "skeleton_minus_intersections_minus_edges_minus_steep_slopes", segments_bin) timeStart("reverse medial axis") # regrow segment regions from segment skeletons rmat = reverse_medial_axis(segments_bin, dist) timeEnd("reverse medial axis") Debug.save_image("segments", "reverse_medial_axis", rmat) # maybe, instead of running medial_axis again, do nearest-neighbor interp timeStart("get distance transform") # the pixels values of rmat_dist correspond to the distance # between each pixel and its nearest foreground/background boundary _, rmat_dist = medial_axis(rmat, return_distance=True) timeEnd("get distance transform") Debug.save_image("segments", "distance_transform", rmat_dist) timeStart("label segment skeletons") image_segments, num_segments = label(segments_bin, np.ones((3, 3))) timeEnd("label segment skeletons") Debug.save_image("segments", "labeled_skeleton", image_segments) timeStart("watershed") image_segments = watershed(-rmat_dist, image_segments, mask=rmat) timeEnd("watershed") Debug.save_image("segments", "watershed", image_segments) # subtract 1 for the background segment num_traces = num_segments - 1 print "found %s segments" % num_traces Record.record("num_segments", num_traces) segments = img_seg_to_seg_objects(image_segments, num_segments, ridges_h, ridges_v, img_gray) if Debug.active: from lib.segment_coloring import gray2prism # try to assign different gray values to neighboring segments traces_colored = (image_segments + num_traces * (image_segments % 4)) / float(4 * num_traces) # store a background mask background = traces_colored == 0 background = np.dstack((background, background, background)) # convert gray values to colors traces_colored = gray2prism(traces_colored) # make background pixels black traces_colored[background] = 0 Debug.save_image("segments", "segment_regions", traces_colored) if Record.active: timeStart("calculate histograms of segment sizes") # last bin includes both left and right boundaries, so add 1 to right-most bin segment_bins = np.arange(num_segments + 1) segment_sizes, _ = np.histogram(image_segments.flatten(), bins=segment_bins) # trim off the "background" segment segment_sizes = segment_sizes[1:] max_segment_size = np.amax(segment_sizes) # there are no size 0 segments segment_size_bins = np.arange(1, max_segment_size + 1) hist_of_sizes, _ = np.histogram(segment_sizes, bins=segment_size_bins) Record.record("segment_region_hist", hist_of_sizes.tolist()) timeEnd("calculate histograms of segment sizes") timeStart("calculate histogram of centerlines") centerlines = [ seg.center_line for seg in segments.itervalues() if seg.has_center_line ] Record.record("num_segments_with_centerlines", len(centerlines)) centerline_lengths = map(lambda line: len(line.coords), centerlines) max_centerline_length = np.amax(centerline_lengths) # there are no size 0 centerlines centerline_bins = np.arange(1, max_centerline_length + 1) hist_of_centerlines, _ = np.histogram(centerline_lengths, bins=centerline_bins) Record.record("segment_centerline_hist", hist_of_centerlines.tolist()) timeEnd("calculate histogram of centerlines") if figure == False: return segments else: return (segments, image_segments)
def analyze_image(in_file, out_dir, stats_file=False, scale=1, debug_dir=False, fix_seed=False): from lib.dir import ensure_dir_exists from lib.debug import Debug from lib.stats_recorder import Record if debug_dir: Debug.set_directory(debug_dir) if fix_seed: Debug.set_seed(1234567890) if stats_file: Record.activate() ensure_dir_exists(out_dir) from lib.timer import timeStart, timeEnd from lib.load_image import get_grayscale_image, image_as_float from lib.roi_detection import get_roi, corners_to_geojson from lib.polygon_mask import mask_image from lib.meanline_detection import detect_meanlines, meanlines_to_geojson from lib.geojson_io import save_features paths = { "roi": out_dir + "/roi.json", "meanlines": out_dir + "/meanlines.json", "intersections": out_dir + "/intersections.json", "segments": out_dir + "/segments.json", "segment_assignments": out_dir + "/segment_assignments.json" } timeStart("get roi and meanlines") timeStart("read image") img_gray = image_as_float(get_grayscale_image(in_file)) timeEnd("read image") print "\n--ROI--" timeStart("get region of interest") corners = get_roi(img_gray, scale=scale) timeEnd("get region of interest") timeStart("convert roi to geojson") corners_as_geojson = corners_to_geojson(corners) timeEnd("convert roi to geojson") timeStart("saving roi as geojson") save_features(corners_as_geojson, paths["roi"]) timeEnd("saving roi as geojson") print "\n--MASK IMAGE--" roi_polygon = corners_as_geojson["geometry"]["coordinates"][0] timeStart("mask image") masked_image = mask_image(img_gray, roi_polygon) timeEnd("mask image") Debug.save_image("main", "masked_image", masked_image.filled(0)) print "\n--MEANLINES--" meanlines = detect_meanlines(masked_image, corners, scale=scale) timeStart("convert meanlines to geojson") meanlines_as_geojson = meanlines_to_geojson(meanlines) timeEnd("convert meanlines to geojson") timeStart("saving meanlines as geojson") save_features(meanlines_as_geojson, paths["meanlines"]) timeEnd("saving meanlines as geojson") timeEnd("get roi and meanlines") if (stats_file): Record.export_as_json(stats_file)
def threshold(img, threshold_function, num_blocks, block_dims=None, smoothing=0.003): ''' Get a smoothly varying threshold from an image by applying the threshold function to multiple randomly positioned blocks of the image and using a 2-D smoothing spline to set the threshold across the image. Parameters ------------ img : 2-D numpy array The grayscale image. threshold_function : a function The threshold function should take a grayscale image as an input and output an int or float. num_blocks : int The number of blocks within the image to which to apply the threshold function. A higher number will provide better coverage across the image but will take longer to evaluate. block_dims : tuple or numpy array, optional The dimensions of the rectangular blocks. Dimensions should be less than the dimensions of the image. If left unspecified, the blocks will be squares with area approximately equal to two times the area of the image, divided by num_blocks. smoothing : float, optional A parameter to adjust the smoothness of the 2-D smoothing spline. A higher number increases the smoothness of the output. An input of zero is equivalent to interpolation. Returns --------- th_new : 2-D numpy array The threshold. The array is the same shape as the original input image. ''' img_dims = img.shape if num_blocks >= 16: spline_order = 3 else: spline_order = int(np.sqrt(num_blocks) - 1) if spline_order == 0: return (np.ones_like(img) * threshold_function(img)) if (type(img) is MaskedArray): mask = img.mask else: mask = np.zeros(img_dims, dtype=bool) candidate_coords = np.transpose(np.nonzero(~mask)) if block_dims is None: block_dim = int(round(np.sqrt(2 * img.size / num_blocks))) block_dims = (block_dim, block_dim) timeStart("select block centers") points = best_candidate_sample(candidate_coords, num_blocks) timeEnd("select block centers") def get_threshold_for_block(center): block = get_block(img, center, block_dims) if (type(block) is MaskedArray): return threshold_function(block.compressed()) else: return threshold_function(block) timeStart("calculate thresholds for blocks of size %s" % block_dim) thresholds = np.asarray( [get_threshold_for_block(center) for center in points]) timeEnd("calculate thresholds for blocks of size %s" % block_dim) timeStart("fit 2-D spline") # Maybe consider using lower-order spline for large images # (if large indices create problems for cubic functions) fit = spline2d(points[:, 0], points[:, 1], thresholds, bbox=[0, img_dims[0], 0, img_dims[1]], kx=spline_order, ky=spline_order, s=num_blocks * smoothing) th_new = fit(x=np.arange(img_dims[0]), y=np.arange(img_dims[1])) th_new = fix_border(th_new, points) timeEnd("fit 2-D spline") return th_new