def refine_worm(image, initial_area, candidate_edges): # find strong worm edges (roughly equivalent to the edges found by find_initial_worm, # which are in candidate_edges): smooth the image, do canny edge-finding, and # then keep only those edges near candidate_edges smooth_image = restoration.denoise_tv_bregman(image, 140).astype(numpy.float32) smoothed, gradient, sobel = canny.prepare_canny(smooth_image, 8, initial_area) local_maxima = canny.canny_local_maxima(gradient, sobel) candidate_edge_region = ndimage.binary_dilation(candidate_edges, iterations=4) strong_edges = local_maxima & candidate_edge_region # Now threshold the image to find dark blobs as our initial worm region # First, find areas in the initial region unlikely to be worm pixels mean, std = mcd.robust_mean_std(smooth_image[initial_area][::4], 0.85) non_worm = (smooth_image > mean - std) & initial_area # now fit a smoothly varying polynomial to the non-worm pixels in the initial # region of interest, and subtract that from the actual image to generate # an image with a flat illumination field background = polyfit.fit_polynomial(smooth_image, mask=non_worm, degree=2) minus_bg = smooth_image - background # now recalculate a threshold from the background-subtracted pixels mean, std = mcd.robust_mean_std(minus_bg[initial_area][::4], 0.85) initial_worm = (minus_bg < mean - std) & initial_area # Add any pixels near the strong edges to our candidate worm position initial_worm |= ndimage.binary_dilation(strong_edges, iterations=3) initial_worm = mask.fill_small_radius_holes(initial_worm, 5) # Now grow/shrink the initial_worm region so that as many of the strong # edges from the canny filter are in contact with the region edges as possible. ac = active_contour.EdgeClaimingAdvection(initial_worm, strong_edges, max_region_mask=initial_area) stopper = active_contour.StoppingCondition(ac, max_iterations=100) while stopper.should_continue(): ac.advect(iters=1) ac.smooth(iters=1, depth=2) worm_mask = mask.fill_small_radius_holes(ac.mask, 7) # Now, get edges from the image at a finer scale smoothed, gradient, sobel = canny.prepare_canny(smooth_image, 0.3, initial_area) local_maxima = canny.canny_local_maxima(gradient, sobel) strong_sum = strong_edges.sum() highp = 100 * (1 - 1.5*strong_sum/local_maxima.sum()) lowp = max(100 * (1 - 3*strong_sum/local_maxima.sum()), 0) low_worm, high_worm = numpy.percentile(gradient[local_maxima], [lowp, highp]) fine_edges = canny.canny_hysteresis(local_maxima, gradient, low_worm, high_worm) # Expand out the identified worm area to include any of these finer edges closed_edges = ndimage.binary_closing(fine_edges, structure=S) worm = ndimage.binary_propagation(worm_mask, mask=worm_mask|closed_edges, structure=S) worm = ndimage.binary_closing(worm, structure=S, iterations=2) worm = mask.fill_small_radius_holes(worm, 5) worm = ndimage.binary_opening(worm) worm = mask.get_largest_object(worm) # Last, smooth the shape a bit to reduce sharp corners, but not too much to # sand off the tail ac = active_contour.CurvatureMorphology(worm, max_region_mask=initial_area) ac.smooth(depth=2, iters=2) return strong_edges, ac.mask
def measure_fluorescence(image, worm_mask, well_mask=None): if well_mask is not None: restricted_mask = ndimage.binary_erosion(well_mask, iterations=15) background = polyfit.fit_polynomial(image[::4,::4], mask=restricted_mask[::4,::4], degree=2).astype(numpy.float32) background = ndimage.zoom(background, 4) background /= background[well_mask].mean() background[background <= 0.01] = 1 # we're going to divide by background, so prevent div/0 errors image = image.astype(numpy.float32) / background image[~well_mask] = 0 worm_pixels = image[worm_mask] low_px_mean, low_px_std = mcd.robust_mean_std(worm_pixels[worm_pixels < worm_pixels.mean()], 0.5) expression_thresh = low_px_mean + 2.5*low_px_std high_expression_thresh = low_px_mean + 6*low_px_std fluo_px = worm_pixels[worm_pixels > expression_thresh] high_fluo_px = worm_pixels[worm_pixels > high_expression_thresh] area = worm_mask.sum() integrated = worm_pixels.sum() median, percentile95 = numpy.percentile(worm_pixels, [50, 95]) expression_area = fluo_px.size expression_area_fraction = expression_area / area expression_mean = fluo_px.mean() high_expression_area = high_fluo_px.size high_expression_area_fraction = high_expression_area / area high_expression_mean = high_fluo_px.mean() high_expression_integrated = high_fluo_px.sum() expression_mask = (image > expression_thresh) & worm_mask high_expression_mask = (image > high_expression_thresh) & worm_mask return data_row(area, integrated, median, percentile95, expression_area, expression_area_fraction, expression_mean, high_expression_area, high_expression_area_fraction, high_expression_mean, high_expression_integrated), (image, background, expression_mask, high_expression_mask)
def measure_gfp_fluorescence(fluorescent_image, worm_mask): '''Given an image, measure the gfp flourescence TODO: Do we need a worm_frame? Figure out how to add columns to the tsv files ''' worm_pixels = fluorescent_image[worm_mask].copy() #Get interesting statistics out low_px_mean, low_px_std = mcd.robust_mean_std( worm_pixels[worm_pixels < worm_pixels.mean()], 0.5) expression_thresh = low_px_mean + 2.5 * low_px_std high_expression_thresh = low_px_mean + 6 * low_px_std fluo_px = worm_pixels[worm_pixels > expression_thresh] high_fluo_px = worm_pixels[worm_pixels > high_expression_thresh] area = worm_mask.sum() integrated = worm_pixels.sum() median, percentile95 = np.percentile(worm_pixels, [50, 95]) expression_area = fluo_px.size expression_area_fraction = expression_area / area expression_mean = fluo_px.mean() high_expression_area = high_fluo_px.size high_expression_area_fraction = high_expression_area / area high_expression_mean = high_fluo_px.mean() high_expression_integrated = high_fluo_px.sum() expression_mask = (fluorescent_image > expression_thresh) & worm_mask high_expression_mask = (fluorescent_image > high_expression_thresh) & worm_mask #create dictionary to store the statistics/measurements gfp_data = { 'warped_integrated_gfp': integrated, 'warped_median_gfp': median, 'warped_percentile95_gfp': percentile95, 'warped_expression_area_gfp': expression_area, 'warped_expression_areafraction_gfp': expression_area_fraction, 'warped_expression_mean_gfp': expression_mean, 'warped_high_expression_area_gfp': high_expression_area, 'warped_high_expression_area_fraction_gfp': high_expression_area_fraction, 'warped_high_expression_mean_gfp': high_expression_mean, 'warped_high_expression_integrated_gfp': high_expression_integrated, 'warped_total_size': np.sum(worm_mask) } #Generate nice looking pictures to save expression_area_mask = np.zeros(worm_mask.shape).astype('bool') high_expression_area_mask = np.zeros(worm_mask.shape).astype('bool') percentile95_mask = np.zeros(worm_mask.shape).astype('bool') expression_area_mask[fluorescent_image > expression_thresh] = True expression_area_mask[np.invert(worm_mask)] = False high_expression_area_mask[ fluorescent_image > high_expression_thresh] = True high_expression_area_mask[np.invert(worm_mask)] = False percentile95_mask[fluorescent_image > percentile95] = True percentile95_mask[np.invert(worm_mask)] = False colored_areas = extractFeatures.color_features( [expression_area_mask, high_expression_area_mask, percentile95_mask]) return (gfp_data, colored_areas)
def get_vignette_mask(image): """Convert a well-exposed image (ideally a brightfield image with ~uniform intensity) into a mask delimiting the image region from the dark, vignetted borders of the image. Returns: vignette_mask, which is True in the image regions. """ likely_vignette_pixels = image[image < numpy.percentile(image, 40)] mean, std = mcd.robust_mean_std(likely_vignette_pixels, 0.5) vignette_threshold = mean + 150 * std vignette_mask = image > vignette_threshold vignette_mask = ndimage.binary_fill_holes(vignette_mask) return vignette_mask
def subregion_measures(image, mask): """Measure region properties of a masked image, defining several sub-regions relevant to fluorescence images. Three sub-regions will be defined: expression: the region of a fluorescence image where there is apparent fluorescent protein expression (2.5 standard deviations above the background distribution of low-valued pixels in the masked region) high_expression: the region of apparent intense fluorescence (6 standard deviations above background) over_99: the region including the top 1% of pixels by intensity. Parameters: image, mask: numpy.ndarrays of same shape. Returns: data, region_masks data: [sum, mean, median, percentile_95, percentile_99, expression_sum, expression_mean, expression_median, expression_area_fraction, high_expression_sum, high_expression_mean, high_expression_median, high_expression_area_fraction, over_99_sum, over_99_mean, over_99_median] where the 'area_fraction' measurements represent the fraction of the mask area encompassed by the expression and high_expression areas. region_masks: [expression_mask, high_expression_mask, over_99_mask] where each is a mask defining the sub-region of the mask. """ if mask.dtype != bool: mask = mask > 0 pixels = image[mask] mask_measures = region_measures(pixels) mean = mask_measures[1] area = pixels.sum() low_mean, low_std = mcd.robust_mean_std(pixels[pixels < mean], 0.5) expression_mask = (image > (low_mean + 2.5 * low_std)) & mask expression_measures = region_measures(image[expression_mask])[:3] expression_area_fraction = expression_mask.sum() / area expression_measures.append(expression_area_fraction) high_expression_mask = (image > (low_mean + 6 * low_std)) & mask high_expression_measures = region_measures(image[high_expression_mask])[:3] high_expression_area_fraction = high_expression_mask.sum() / area high_expression_measures.append(high_expression_area_fraction) percentile_99 = mask_measures[-1] over_99_mask = (image > percentile_99) & mask over_99_measures = region_measures(image[over_99_mask])[:3] data = mask_measures + expression_measures + high_expression_measures + over_99_measures region_masks = [expression_mask, high_expression_mask, over_99_mask] return data, region_masks
def get_rough_mask(image): """Take an image with light wells and dark inter-well regions and construct a mask with 'True' values in all pixels belonging to a well and 'False' values elsewhere.""" # take every 3rd pixel in each direction and then flatten to a 1D array. image_sample = image[::3, ::3].flatten() # use that subsample to estimate the mean and variance of the background pixels mean, std = mcd.robust_mean_std(image_sample, subset_fraction=0.5) # find the non-background pixels well_mask = image > (mean + 12*std) well_mask = ndimage.binary_fill_holes(well_mask) well_mask = mask.remove_small_radius_objects(well_mask, max_radius=10) well_mask = mask.remove_edge_objects(well_mask) return well_mask
def get_vignette_mask(image, percent_vignetted=5): """Convert a well-exposed image (ideally a brightfield image with ~uniform intensity) into a mask delimiting the image region from the dark, vignetted borders of the image. percent_vignetted: percent (0-100) of pixels estimated to be in the vignetted region. 35% is a good estimate for images with a full circular vignette (e.g. 0.7x optocoupler); 5% is reasonable for images with only small vignetted areas (e.g. 1x optocoupler). Returns: vignette_mask, which is True in the image regions. """ bright_pixels = image[image > numpy.percentile(image[::4,::4], 1.5*percent_vignetted)] mean, std = mcd.robust_mean_std(bright_pixels[::10], 0.85) vignette_threshold = mean - 5 * std vignette_mask = image > vignette_threshold vignette_mask = ndimage.binary_closing(vignette_mask) vignette_mask = ndimage.binary_fill_holes(vignette_mask) vignette_mask = ndimage.binary_opening(vignette_mask, iterations=15) return vignette_mask
def measure_gfp_fluorescence(fluorescent_image, worm_mask, measurement_name=None): """Measure specific GFP fluorescent things for a particular GFP image and return a dictionary of the measurements Parameters: flourescent_image: numpy array of the flourescent image worm_mask: numpy array of the mask measurement_name: string of the descriptor that you want the columns to have i.e. if you were measuring gfp in the head then you might put measurement_name = 'head' and then in the output your dictionary will have keys such as 'integrated_gfp_head' instead of 'integrated_gfp'. This makes it easier to input into elegant right away and not overwrite any other previously made measurements Returns: gfp_measurements: dictionary of measurements and their values NOTE: currently gfp_measurements has set things to look for, but maybe in the future we can have this take functions to study. """ worm_pixels = fluorescent_image[worm_mask].copy() low_px_mean, low_px_std = mcd.robust_mean_std( worm_pixels[worm_pixels < worm_pixels.mean()], 0.5) expression_thresh = low_px_mean + 2.5 * low_px_std high_expression_thresh = low_px_mean + 6 * low_px_std fluo_px = worm_pixels[worm_pixels > expression_thresh] high_fluo_px = worm_pixels[worm_pixels > high_expression_thresh] area = worm_mask.sum() integrated = worm_pixels.sum() median, percentile95 = np.percentile(worm_pixels, [50, 95]) expression_area = fluo_px.size expression_area_fraction = expression_area / area expression_mean = fluo_px.mean() high_expression_area = high_fluo_px.size high_expression_area_fraction = high_expression_area / area high_expression_mean = high_fluo_px.mean() high_expression_integrated = high_fluo_px.sum() expression_mask = (fluorescent_image > expression_thresh) & worm_mask high_expression_mask = (fluorescent_image > high_expression_thresh) & worm_mask length = worm_mask.shape[0] gfp_measurements = { 'integrated_gfp': integrated, 'median_gfp': median, 'percentile95_gfp': percentile95, 'expressionarea_gfp': expression_area, 'expressionareafraction_gfp': expression_area_fraction, 'expressionmean_gfp': expression_mean, 'highexpressionarea_gfp': high_expression_area, 'highexpressionareafraction_gfp': high_expression_area_fraction, 'highexpressionmean_gfp': high_expression_mean, 'highexpressionintegrated_gfp': high_expression_integrated, 'area': area, 'length': length } if measurement_name is not None: for key in sorted(gfp_measurements.keys()): gfp_measurements[key + "_" + measurement_name] = gfp_measurements.pop(key) expression_area_mask = np.zeros(worm_mask.shape).astype('bool') high_expression_area_mask = np.zeros(worm_mask.shape).astype('bool') percentile95_mask = np.zeros(worm_mask.shape).astype('bool') expression_area_mask[fluorescent_image > expression_thresh] = True expression_area_mask[np.invert(worm_mask)] = False high_expression_area_mask[ fluorescent_image > high_expression_thresh] = True high_expression_area_mask[np.invert(worm_mask)] = False percentile95_mask[fluorescent_image > percentile95] = True percentile95_mask[np.invert(worm_mask)] = False #colored_areas = extractFeatures.color_features([expression_area_mask,high_expression_area_mask,percentile95_mask]) return gfp_measurements