def lawn_maker(bf_files, super_vignette): ''' Find the bacterial lawn in one worm image. ''' lawn_masks = [] for a_bf_file in bf_files: # Prepare a worm image for use in lawn-finding. renormalized_image = freeimage.read(a_bf_file) renormalized_image = cv2.medianBlur(renormalized_image, 3) renormalized_image = imageOperations.renormalize_image(renormalized_image) # Remove extraneous edges and out-of-lawn junk by finding the lawn and also applying an "ultra-vignette" mask. ultra_vignette = scipy.ndimage.morphology.binary_erosion(super_vignette, iterations = 10) my_edges = skimage.feature.canny(renormalized_image, sigma = 0.02) my_edges[np.invert(ultra_vignette)] = False my_edges = scipy.ndimage.morphology.binary_dilation(my_edges, iterations = 10) my_lawn = scipy.ndimage.morphology.binary_fill_holes(my_edges) try: my_lawn = zplib_image_mask.get_largest_object(my_lawn).astype('bool') my_lawn = scipy.ndimage.morphology.binary_erosion(my_lawn, iterations = 10) my_lawn = zplib_image_mask.get_largest_object(my_lawn).astype('bool') except: my_lawn = np.zeros(my_lawn.shape).astype('bool') lawn_masks.append(my_lawn) my_lawn = np.max(np.array(lawn_masks), axis = 0) return my_lawn
def stop_editing(self, save_work=True): # Convert outline to mask outline = zplib_image_mask.get_largest_object( self.rw.layers[2].image.data > 0) new_mask = zplib_image_mask.fill_small_area_holes( outline, 300000).astype('uint8') new_mask = new_mask > 0 #new_mask[new_mask>0] = -1 self.rw.layers[2].image.set(data=(new_mask > 0).astype('bool')) self.rw.layers[2].tint = (1.0, 1.0, 1.0, 1.0) #update spline data m, skel, centerline, center_dist, med_axis, tb = skeleton.skel_and_centerline( new_mask) current_page_idx = self.rw.flipbook.current_page_idx self.rw.layers[1:] = [skel, centerline, center_dist] self.rw.flipbook.pages[current_page_idx].spline_data = tb self.rw.flipbook.pages[current_page_idx].dist_data = center_dist self.rw.flipbook.pages[current_page_idx].med_axis = med_axis self.rw.qt_object.layer_stack_painter_dock_widget.hide() """if self.current_page[1].data.any(): if not pathlib.Path(str(self.working_file).replace('mask.png','mask_oldwf.png')).exists(): self.working_file.rename(str(self.working_file).replace('mask.png','mask_oldwf.png')) freeimage.write(new_mask, self.working_file)""" self.editing = False #self.working_file = None # Do this after saving out since need the working file above self.edit.setText('Edit Mask')
def pose_from_mask(mask, smoothing=2): """Calculate worm pose splines from mask image. Parameter: mask: mask image (the largest object in the mask will be used) smoothing: smoothing factor to apply to splines produced (see docstring for zplib.interpolate.smooth_spline). If 0, no smoothing will be applied. Returns: center_tck, width_tck splines defining the (x, y) position of the centerline and the distance from the centerline to the edge (called "widths" but more like "half- widths" more accurately) as a function of position along the centerline. If there was no object in the mask, returns None for each. """ mask = mask > 0 # get largest object allowing diagonal connectivity mask = zpmask.get_largest_object(mask, structure=numpy.ones((3,3))) # crop image for faster medial axis transform slices = ndimage.find_objects(mask) if len(slices) == 0: # mask is completely empty return None, None sx, sy = slices[0] cropped = mask[sx, sy] centerline, widths = _get_centerline(cropped) if len(centerline) < 10: return None, None center_tck, width_tck = _get_splines(centerline, widths) # adjust x, y coords to account for the cropping c = center_tck[1] c += sx.start, sy.start if smoothing > 0: center_tck = interpolate.smooth_spline(center_tck, num_points=int(center_tck[0][-1]), smoothing=smoothing) width_tck = interpolate.smooth_spline(width_tck, num_points=100, smoothing=smoothing) return center_tck, width_tck
def find_initial_worm(small_image, well_mask): # plan here is to find known good worm edges with Canny using a stringent threshold, then # relax the threshold in the vicinity of the good edges. # back off another pixel from the well edge to avoid gradient from the edge shrunk_mask = ndimage.binary_erosion(well_mask, structure=S) smoothed, gradient, sobel = canny.prepare_canny(small_image, 2, shrunk_mask) local_maxima = canny.canny_local_maxima(gradient, sobel) # Calculate stringent and medium-stringent thresholds. The stringent threshold # is the 200th-brightest edge pixel, and the medium is the 450th-brightest pixel highp = 100 * (1-200/local_maxima.sum()) highp = max(highp, 94) mediump = 100 * (1-450/local_maxima.sum()) mediump = max(mediump, 94) low_worm, medium_worm, high_worm = numpy.percentile(gradient[local_maxima], [94, mediump, highp]) stringent_worm = canny.canny_hysteresis(local_maxima, gradient, low_worm, high_worm) # Expand out 20 pixels from the stringent worm edges to make our search space stringent_area = ndimage.binary_dilation(stringent_worm, mask=well_mask, iterations=20) # now use the relaxed threshold but only in the stringent area relaxed_worm = canny.canny_hysteresis(local_maxima, gradient, low_worm, medium_worm) & stringent_area # join very close-by objects, and remove remaining small objects candidate_worm = ndimage.binary_dilation(relaxed_worm, structure=S) candidate_worm = ndimage.binary_erosion(candidate_worm) candidate_worm = mask.remove_small_area_objects(candidate_worm, 30, structure=S) # Now figure out the biggest blob of nearby edges, and call that the worm region glommed_candidate = ndimage.binary_dilation(candidate_worm, structure=S, iterations=2) glommed_candidate = ndimage.binary_erosion(glommed_candidate, iterations=2) # get just outline, not any regions filled-in due to closing glommed_candidate ^= ndimage.binary_erosion(glommed_candidate) glommed_candidate = mask.get_largest_object(glommed_candidate, structure=S) worm_area = ndimage.binary_dilation(glommed_candidate, mask=well_mask, structure=S, iterations=12) worm_area = mask.fill_small_radius_holes(worm_area, max_radius=15) candidate_edges = relaxed_worm & candidate_worm & worm_area return candidate_edges, worm_area
def save_mask_lcc(experiment_root): experiment_root = pathlib.Path(experiment_root) mask_root = experiment_root / 'derived_data' / 'mask' for position_mask_root in sorted(mask_root.iterdir()): for mask_file in sorted(position_mask_root.iterdir()): mask_image = freeimage.read(str(mask_file)) > 0 new_mask = mask.get_largest_object(mask_image).astype(numpy.uint8) freeimage.write(new_mask*255, str(mask_file))
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 clean_dust_and_holes(dusty_pic): ''' Picks out the largest object in dusty_mask and fills in its holes, returning cleaned_mask. ''' my_dtype = dusty_pic.dtype dust_mask = np.invert(zplib_image_mask.get_largest_object(dusty_pic)) dusty_pic[dust_mask] = 0 cleaned_mask = simple_floor(zplib_image_mask.fill_small_area_holes(dusty_pic, 90000).astype(my_dtype), 1) return cleaned_mask
def parse_aggregate_centerlines(aggregate_mask): centerlines = [] temp_mask = aggregate_mask.copy() for layer in numpy.moveaxis(temp_mask, -1, 0): while layer.any(): new_centerline = zplib_image_mask.get_largest_object(layer>0,structure=numpy.ones((3,3))) new_centerline[new_centerline>0] = -1 layer[new_centerline>0] = 0 centerlines.append(new_centerline) return centerlines
def calculate_iou(prediction, ground_truth, plot_val=False, save_val=False, save_dir=None, pyr=False): """Find intersection over union for 2 images Prediction and ground_truth are boolean arrays """ pred_orig = freeimage.read(prediction) gt_orig = freeimage.read(ground_truth) folder, worm_id = ground_truth.parts[-2:] worm_id = worm_id.split(' ')[0] #get rid of stuff touching the edges if pyr: pred = mask.remove_edge_objects(pred_orig) gt = mask.remove_edge_objects(gt_orig) #find the worm in the mask (largest object presumably) pred_image = mask.get_largest_object(pred) ground_truth = mask.get_largest_object(gt ) else: #find the worm in the mask (largest object presumably) pred_image = mask.get_largest_object(pred_orig) ground_truth = mask.get_largest_object(gt_orig) #find intersect and union intersect = (pred_image & ground_truth) union = (pred_image|ground_truth) #plot for validation if save_val: #maybe change this to not be this save_file = save_dir+"/"+folder+"_"+worm_id+"_val.png" #if willie: # save_file = '/home/nicolette/Documents/lab_stuff/worm_segmentation_validation/Willie_iou/'+folder+"_"+worm_id+"_val.png" #else: # save_file = '/home/nicolette/Documents/lab_stuff/MATLAB_scripts/binarySeg_v2/figFolder/background_sub_pyr/iou/'+prediction.stem+"_val.png" print("saving: "+save_file) plot_validation(pred_orig, gt_orig, intersect, union, save_val=True, save_file=save_file) if plot_val: plot_validation(pred_orig, gt_orig, intersect, union) return intersect.sum()/union.sum()
def alternate_lawn_maker(bf_files, super_vignette): ''' Make a lawn just by intensity when for some reason the normal lawn maker fails... ''' lawn_masks = [] for a_bf_file in bf_files: renormalized_image = read_corrected_bf(a_bf_file) lawn_cutoff = np.percentile(renormalized_image, 5) lawn_mask = np.zeros(renormalized_image.shape).astype('bool') lawn_mask[renormalized_image < lawn_cutoff] = True ultra_vignette = scipy.ndimage.morphology.binary_erosion(super_vignette, iterations = 10) lawn_mask[np.invert(ultra_vignette)] = False lawn_mask = scipy.ndimage.morphology.binary_dilation(lawn_mask, iterations = 3) lawn_mask = scipy.ndimage.morphology.binary_fill_holes(lawn_mask) lawn_mask = zplib_image_mask.get_largest_object(lawn_mask).astype('bool') lawn_mask = scipy.ndimage.morphology.binary_erosion(lawn_mask, iterations = 3) lawn_mask = zplib_image_mask.get_largest_object(lawn_mask).astype('bool') lawn_masks.append(lawn_mask) lawn_mask = np.max(np.array(lawn_masks), axis = 0) return lawn_mask
def queryModelMask(self, image, delta=None): if self.model is None: return delta = self.queryModelDelta(image) if delta is None else delta.astype(numpy.float32) if self.input_antimask is not None: delta[self.input_antimask] = 0 threshold = numpy.percentile(numpy.abs(delta), 97.5).astype(numpy.float32) mask = delta >= threshold mask = zplib_image_mask.get_largest_object(mask) # mask[~zplib_image_mask.get_largest_object(mask)] = 0 # Remove dust from mask return mask
def clean_dust_and_holes(dusty_pic): ''' Picks out the largest object in dusty_mask and fills in its holes, returning cleaned_mask. ''' my_dtype = dusty_pic.dtype dust_mask = np.invert(zplib_image_mask.get_largest_object(dusty_pic)) dusty_pic[dust_mask] = 0 cleaned_mask = simple_floor( zplib_image_mask.fill_small_area_holes(dusty_pic, 90000).astype(my_dtype), 1) return cleaned_mask
def worm_candidate(my_edges, center_pixel, iterations): ''' Find a candidate worm. ''' harder_eggs = scipy.ndimage.morphology.binary_dilation(my_edges, iterations = iterations) harder_eggs = scipy.ndimage.morphology.binary_fill_holes(harder_eggs) harder_eggs = scipy.ndimage.morphology.binary_erosion(harder_eggs, iterations = iterations) if np.bincount(np.ndarray.flatten(harder_eggs)).shape[0] == 1: harder_eggs[center_pixel[0], center_pixel[1]] = True the_worm = zplib_image_mask.get_largest_object(harder_eggs).astype('bool') return the_worm
def skeletonize_mask(raster_worm, verbose_mode): ''' Given a masked worm in raster format, return a skeletonized version of it. ''' if verbose_mode: print('Skeletonizing mask.') zero_one_mask = np.zeros(raster_worm.shape) zero_one_mask[raster_worm > 0] = 1 zero_one_mask = zplib_image_mask.get_largest_object(zero_one_mask) my_skeleton = skimage.morphology.skeletonize(zero_one_mask) skeleton_mask = np.zeros(raster_worm.shape).astype('uint8') skeleton_mask[my_skeleton] = -1 return skeleton_mask
def make_mega_lawn(worm_subdirectory, super_vignette): ''' Make a mega lawn mask for use with all images of a worm. This avoids problems with detecting the relatively faint edge of the lawn, which depends on the exact focus plane and the brightness of the lamp. ''' # Parallelized edge detection. my_bf_files = [worm_subdirectory + os.path.sep + a_bf for a_bf in os.listdir(worm_subdirectory) if a_bf[-6:] == 'bf.png'] my_workers = min(multiprocessing.cpu_count() - 1, 60) chunk_size = int(np.ceil(len(my_bf_files)/my_workers)) bf_chunks = [my_bf_files[x:x + chunk_size] for x in range(0, len(my_bf_files), chunk_size)] with concurrent.futures.ProcessPoolExecutor(max_workers = my_workers) as executor: chunk_masks = [executor.submit(lawn_maker, bf_chunks[i], super_vignette.copy()) for i in range(0, len(bf_chunks))] concurrent.futures.wait(chunk_masks) chunk_masks = [a_job.result() for a_job in chunk_masks] # Make mega lawn from edge detection. mega_lawn = np.max(np.array(chunk_masks), axis = 0) mega_lawn = scipy.ndimage.morphology.binary_fill_holes(mega_lawn).astype('bool') mega_lawn = zplib_image_mask.get_largest_object(mega_lawn).astype('uint8') mega_lawn[mega_lawn > 0] = -1 # Parallelized thresholding. with concurrent.futures.ProcessPoolExecutor(max_workers = my_workers) as executor: chunk_masks = [executor.submit(alternate_lawn_maker, bf_chunks[i], super_vignette.copy()) for i in range(0, len(bf_chunks))] concurrent.futures.wait(chunk_masks) chunk_masks = [a_job.result() for a_job in chunk_masks] # Make alternative mega lawn from thresholding intensity. alt_mega_lawn = np.max(np.array(chunk_masks), axis = 0) alt_mega_lawn = scipy.ndimage.morphology.binary_fill_holes(alt_mega_lawn).astype('bool') alt_mega_lawn = zplib_image_mask.get_largest_object(alt_mega_lawn).astype('uint8') alt_mega_lawn[alt_mega_lawn > 0] = -1 # Select proper mega lawn. if np.bincount(np.ndarray.flatten(mega_lawn))[-1] < 0.8*np.bincount(np.ndarray.flatten(alt_mega_lawn))[-1]: mega_lawn = alt_mega_lawn freeimage.write(mega_lawn, worm_subdirectory + os.path.sep + 'great_lawn.png') return mega_lawn
def get_mask(self, position_root, derived_root, timepoint, annotations): mask_file = derived_root / 'mask' / position_root.name / f'{timepoint} {self.mask_name}.png' if not mask_file.exists(): print( f'No mask file found for {position_root.name} at {timepoint}.') return None else: mask = freeimage.read(mask_file) if mask.sum() == 0: print( f'No worm region defined for {position_root.name} at {timepoint}' ) return None else: mask = zpl_mask.get_largest_object(mask, structure=numpy.ones( (3, 3))) return mask
def fill_colored_outline(an_image, outline_only = False, egg_mode = False): ''' Fill out a colored outline and return a mask. ''' # Get rid of the alpha channel. an_image = an_image.copy() an_image[:, :, 3] = 0 # Get pixels with disproportionately more red. my_mask = np.abs(np.max(an_image[:, :, 1:], axis = 2).astype('float64') - an_image[:, :, 0].astype('float64')).astype('uint16') > 0 if not egg_mode: my_mask = zplib_image_mask.get_largest_object(my_mask) # Fill holes. if not outline_only: my_mask = zplib_image_mask.fill_small_area_holes(my_mask, 300000).astype('uint8') my_mask = my_mask.astype('uint8') my_mask[my_mask >= 1] = -1 return my_mask
def find_lawn_in_image(image, optocoupler, return_model=False): """Find a lawn in an image use Gaussian mixture modeling (GMM) This lawn maker models an image (i.e. its pixel intensities) as as mixture of two Gaussian densities. Each corresponds to either the background & lawn. Parameters: image: numpy ndarray of the image to find the lawn from optocoupler: optocoupler magnification (as a float) used for the specified image return model: if True, return the GMM fit to the images. Returns: lawn_mask or (lawn_mask, gmm_model) lawn mask: bool ndarray gmm_model: if return_model is True, also return the fitted GMM model """ filtered_image = ndimage.filters.median_filter(image, size=(3, 3), mode='constant') vignette_mask = process_images.vignette_mask(optocoupler, image.shape) img_data = filtered_image[vignette_mask] gmm = mixture.GaussianMixture(n_components=2) gmm.fit(numpy.expand_dims(img_data, 1)) # Calculate boundary point for label classification as intensity threshold gmm_support = numpy.linspace(0, 2**16 - 1, 2**16) labels = gmm.predict(numpy.reshape(gmm_support, (-1, 1))) thr = numpy.argmax(numpy.abs(numpy.diff(labels))) lawn_mask = (filtered_image < thr) & vignette_mask # Smooth the lawn mask by eroding, grabbing the largest object, and dilating back lawn_mask = ndimage.morphology.binary_erosion(lawn_mask, iterations=10) lawn_mask = zpl_mask.get_largest_object(lawn_mask) lawn_mask = ndimage.morphology.binary_fill_holes(lawn_mask) lawn_mask = ndimage.morphology.binary_dilation(lawn_mask, iterations=10) if return_model: return lawn_mask, gmm else: return lawn_mask
def lawn_distribution(lawn_folder): ''' Find the distribution of lawn sizes in a folder. ''' def size_object(a_mask): ''' Find the two points farthest apart in a mask and return their distance from each other. ''' locations = np.array(np.ma.nonzero(a_mask)).transpose() random_point = locations[0] point_a = locations[np.linalg.norm(locations - random_point, axis = 1).argmax()] point_b = locations[np.linalg.norm(locations - point_a, axis = 1).argmax()] my_distance = np.linalg.norm(point_a - point_b) return my_distance my_lawns = [freeimage.read(lawn_folder + os.path.sep + an_image).astype('bool') for an_image in os.listdir(lawn_folder) if an_image != 'vignette_mask.png'] my_lawns = [zplib_image_mask.get_largest_object(my_lawn).astype('bool') for my_lawn in my_lawns] my_vignette = freeimage.read(lawn_folder + os.path.sep + 'vignette_mask.png').astype('bool') lawn_sizes = [size_object(my_lawn) for my_lawn in my_lawns] field_size = size_object(my_vignette) return (lawn_sizes, field_size)
def clean_mask(img): '''Clean spurious edges/unwanted things from the masks Parameters: ------------ img: array_like (cast to booleans) shape (n,m) Binary image of the worm mask Returns: ----------- mask: array_like (cast to booleans) shape (n,m) Binary image of the worm mask without spurious edges ''' #clean up holes in the mask img = mask.fill_small_radius_holes(img, 2) #dilate/erode to get rid of spurious edges for the mask curve_morph = active_contour.CurvatureMorphology(mask=img) #TODO: multiple iterations of erode curve_morph.erode() curve_morph.dilate() curve_morph.smooth(iters=2) return mask.get_largest_object(curve_morph.mask)
def backup_worm_find(my_edges, bg_center): ''' If my edge detection doesn't find something close enough to the background mask, take more drastic action. ''' def worm_candidate(my_edges, center_pixel, iterations): ''' Find a candidate worm. ''' harder_eggs = scipy.ndimage.morphology.binary_dilation(my_edges, iterations = iterations) harder_eggs = scipy.ndimage.morphology.binary_fill_holes(harder_eggs) harder_eggs = scipy.ndimage.morphology.binary_erosion(harder_eggs, iterations = iterations) if np.bincount(np.ndarray.flatten(harder_eggs)).shape[0] == 1: harder_eggs[center_pixel[0], center_pixel[1]] = True the_worm = zplib_image_mask.get_largest_object(harder_eggs).astype('bool') return the_worm # Get the eggs that have been edge-detected as closed shapes. center_pixel = (my_edges.shape[0]//2, my_edges.shape[1]//2) easy_eggs = scipy.ndimage.morphology.binary_fill_holes(my_edges) if np.bincount(np.ndarray.flatten(easy_eggs)).shape[0] == 1: easy_eggs[center_pixel[0], center_pixel[1]] = True easy_worm = zplib_image_mask.get_largest_object(easy_eggs).astype('bool') # Get the more difficult eggs and the worm itself. worm_candidates = [easy_worm] worm_candidates.extend([worm_candidate(my_edges, center_pixel, i) for i in range(3, 11)]) worm_centers = np.array([np.mean(np.array(np.where(worm_candidate > 0)), axis = 1) for worm_candidate in worm_candidates]) worm_distances = [np.linalg.norm(worm_center - bg_center) for worm_center in worm_centers] the_worm = None done_yet = False for i in range(0, len(worm_candidates)): if not done_yet: if worm_distances[i] < 20: done_yet = True the_worm = worm_candidates[i] the_worm = scipy.ndimage.morphology.binary_fill_holes(the_worm) return the_worm
def stop_editing(self, save_work=True): # Convert outline to mask outline = zplib_image_mask.get_largest_object( self.rw.layers[1].image.data > 0) new_mask = zplib_image_mask.fill_small_area_holes( outline, 300000).astype('uint8') new_mask[new_mask > 0] = -1 self.rw.layers[1].image.set(data=(new_mask > 0).astype('bool')) self.rw.layers[1].tint = (1.0, 1.0, 1.0, 1.0) self.rw.qt_object.layer_stack_painter_dock_widget.hide() if self.current_page[1].data.any(): if not pathlib.Path( str(self.working_file).replace('mask.png', 'mask_oldwf.png')).exists(): self.working_file.rename( str(self.working_file).replace('mask.png', 'mask_oldwf.png')) freeimage.write(new_mask, self.working_file) self.editing = False self.working_file = None # Do this after saving out since need the working file above self.edit.setText('Edit Mask')
def find_eggs_worm(worm_image, super_vignette, mega_lawn, worm_sigma): ''' Solve the segmentation problem that has been giving me hives for months in a weekend... ''' # Prepare for edge detection by renormalizing and denoising image. worm_image = worm_image.copy() worm_image = cv2.medianBlur(worm_image, 3) worm_image = imageOperations.renormalize_image(worm_image) renormalized_image = worm_image # Find non-lawn and vignetting artifacts, and prepare to remove lawn and vignette edges. ultra_vignette = scipy.ndimage.morphology.binary_erosion(super_vignette, iterations = 10) # Do the actual edge detection in search of the worm. my_edges = skimage.feature.canny(renormalized_image, sigma = worm_sigma) my_edges[np.invert(ultra_vignette)] = False my_edges[np.invert(mega_lawn)] = False # Get the eggs that have been edge-detected as closed shapes. center_pixel = (worm_image.shape[0]//2, worm_image.shape[1]//2) easy_eggs = scipy.ndimage.morphology.binary_fill_holes(my_edges) (labels, region_indices, areas) = zplib_image_mask.get_areas(easy_eggs) keep_labels = areas > (1/(0.7**2))*270 keep_labels = np.concatenate(([0], keep_labels)) easy_eggs = keep_labels[labels].astype('bool') if np.bincount(np.ndarray.flatten(easy_eggs)).shape[0] == 1: easy_eggs[center_pixel[0], center_pixel[1]] = True easy_worm = zplib_image_mask.get_largest_object(easy_eggs).astype('bool') # Get the more difficult eggs and the worm itself. harder_eggs = scipy.ndimage.morphology.binary_dilation(my_edges, iterations = 3) harder_eggs = scipy.ndimage.morphology.binary_fill_holes(harder_eggs) harder_eggs = scipy.ndimage.morphology.binary_erosion(harder_eggs, iterations = 3) harder_eggs[easy_eggs] = False (labels, region_indices, areas) = zplib_image_mask.get_areas(harder_eggs) keep_labels = areas > (1/(0.7**2))*270 keep_labels = np.concatenate(([0], keep_labels)) harder_eggs = keep_labels[labels].astype('bool') if np.bincount(np.ndarray.flatten(harder_eggs)).shape[0] == 1: harder_eggs[center_pixel[0], center_pixel[1]] = True the_worm = zplib_image_mask.get_largest_object(harder_eggs).astype('bool') if the_worm[the_worm].shape[0] < easy_worm[easy_worm].shape[0]: the_worm = easy_worm if the_worm[the_worm].shape[0] > 200000: lawn_edges = skimage.feature.canny(renormalized_image, sigma = 0.05) lawn_edges[np.invert(ultra_vignette)] = False lawn_edges = scipy.ndimage.morphology.binary_dilation(lawn_edges, iterations = 10) my_lawn = scipy.ndimage.morphology.binary_fill_holes(lawn_edges) my_lawn = zplib_image_mask.get_largest_object(my_lawn).astype('bool') my_lawn = scipy.ndimage.morphology.binary_erosion(my_lawn, iterations = 10) lawn_transform = scipy.ndimage.morphology.distance_transform_edt(my_lawn) my_lawn_edge = np.zeros(my_lawn.shape).astype('bool') my_lawn_edge[lawn_transform > 0] = True my_lawn_edge[lawn_transform > 6] = False # Do the actual edge detection in search of the worm. my_edges = skimage.feature.canny(renormalized_image, sigma = 2.0) my_edges[np.invert(ultra_vignette)] = False my_edges[np.invert(mega_lawn)] = False my_edges[my_lawn_edge] = False # Get the eggs that have been edge-detected as closed shapes. center_pixel = (worm_image.shape[0]//2, worm_image.shape[1]//2) easy_eggs = scipy.ndimage.morphology.binary_fill_holes(my_edges) (labels, region_indices, areas) = zplib_image_mask.get_areas(easy_eggs) keep_labels = areas > (1/(0.7**2))*270 keep_labels = np.concatenate(([0], keep_labels)) easy_eggs = keep_labels[labels].astype('bool') if np.bincount(np.ndarray.flatten(easy_eggs)).shape[0] == 1: easy_eggs[center_pixel[0], center_pixel[1]] = True easy_worm = zplib_image_mask.get_largest_object(easy_eggs).astype('bool') # Get the more difficult eggs and the worm itself. harder_eggs = scipy.ndimage.morphology.binary_dilation(my_edges, iterations = 3) harder_eggs = scipy.ndimage.morphology.binary_fill_holes(harder_eggs) harder_eggs = scipy.ndimage.morphology.binary_erosion(harder_eggs, iterations = 3) harder_eggs[easy_eggs] = False (labels, region_indices, areas) = zplib_image_mask.get_areas(harder_eggs) keep_labels = areas > (1/(0.7**2))*270 keep_labels = np.concatenate(([0], keep_labels)) harder_eggs = keep_labels[labels].astype('bool') if np.bincount(np.ndarray.flatten(harder_eggs)).shape[0] == 1: harder_eggs[center_pixel[0], center_pixel[1]] = True the_worm = zplib_image_mask.get_largest_object(harder_eggs).astype('bool') if the_worm[the_worm].shape[0] < easy_worm[easy_worm].shape[0]: the_worm = easy_worm # Clean up worm and eggs. the_worm = scipy.ndimage.morphology.binary_fill_holes(the_worm) harder_eggs[the_worm] = False easy_eggs[the_worm] = False # Combine the two groups of eggs. all_eggs = np.zeros(harder_eggs.shape).astype('bool') all_eggs[harder_eggs] = True all_eggs[easy_eggs] = True return (all_eggs, the_worm, my_edges)
def ensure_human(human_dir, work_dir, data_dir): ''' Make sure that the human data directory, human_dir, has everything it needs from the working directory (namely properly corrected bf images and rough background subtraction masks). ''' def test_and_copy(test_file, found_file): ''' Check for test_file file in human_dir, then copy them over from found_file in work_dir if needed. ''' if not os.path.isfile(test_file): print('Missing ' + test_file.split(' ')[-1] + ' at ' + test_file + '.') if os.path.isfile(found_file): print('\tFound corresponding ' + found_file.split(' ')[-1] + ' at ' + found_file + '.') print('\tCopying file...') shutil.copyfile(found_file, test_file) if os.path.isfile(test_file): print('\tSuccess!') else: raise BaseException('\tCOPYING FAILED.') else: raise BaseException('\tCouldn\'t find corresponding file.') return points_list = [] for a_subdir in os.listdir(human_dir): human_subdir = human_dir + os.path.sep + a_subdir for a_file in os.listdir(human_subdir): if a_file.split('.')[-1] == 'png': file_split = a_file.split(' ') points_list.append(a_subdir + os.path.sep + file_split[0]) points_list = sorted(list(set(points_list))) for a_point in points_list: # Set up some variables and copy metadata. (a_subdir, the_point) = a_point.split(os.path.sep) human_subdir = human_dir + os.path.sep + a_subdir working_subdir = work_dir + os.path.sep + a_subdir.split(' ')[-1] data_subdir = data_dir + os.path.sep + a_subdir.split(' ')[-1] base_metadata = human_subdir + os.path.sep + 'position_metadata.json' found_metadata = data_subdir + os.path.sep + 'position_metadata.json' test_and_copy(base_metadata, found_metadata) # Clean up human mask. base_test = human_subdir + os.path.sep + the_point base_found = working_subdir + os.path.sep + the_point test_and_copy(base_test + ' ' + 'hmask.png', base_test + ' ' + 'outline.png') print('Cleaning up ' + base_test + ' ' + 'hmask.png' + '.') old_mask = freeimage.read(base_test + ' ' + 'hmask.png') my_mask = np.zeros(old_mask.shape[:2]).astype('uint8') if len(old_mask.shape) == 3: my_mask[np.max(old_mask[:, :, :3], axis = 2).astype('float64') > 0] = -1 elif len(old_mask.shape) == 2: my_mask[old_mask > 0] = -1 else: raise ValueError(base_test + ' ' + 'hmask.png' + 'does not have proper dimensions.') my_mask[np.invert(zplib_image_mask.get_largest_object(my_mask) > 0)] = 0 freeimage.write(my_mask, base_test + ' ' + 'hmask.png') # Clean up old stuff. if os.path.isfile(base_test + ' ' + 'bf.png'): os.remove(base_test + ' ' + 'bf.png') if os.path.isfile(base_test + ' ' + 'outline.png'): os.remove(base_test + ' ' + 'outline.png') # Copy over other test masks. test_and_copy(base_test + ' ' + 'bf.png', base_found + ' ' + 'bf.png') test_and_copy(base_test + ' ' + 'mask.png', base_found + ' ' + 'mask.png') return
def process_position_directory(position_directory, channels_to_process, save_directory=None, remake_masks=False, enable_gpu=False, model_path=None): """Process a position directory by starting a server for the segmenter and feeding it images to process on the fly. Parameters position_directory - str/pathlib.Path to position directory channels_to_process - list of str image identifiers to process into masks; save_directory - optional str/pathlib.Path where masks will be saved remake_masks - optional bool flag to denote whether the segmenter should remake masks for images that have already been processed enable_gpu - optional bool flag to turn on GPU compatibility; as of matconvnet-1.0-beta25, there's a bug in matconvnet such that mex code compiled with gpu support doesn't work when the computer doesn't have a gpu; thus, for cluster computing and working on other computers, set this to False to use the alternate build of the segmentation code TODO: Refactor to use load_data.scan_experiment_dir?? Clean up save_directory business - should this even be exposed? """ position_directory = pathlib.Path(position_directory) if save_directory is None: experiment_directory = position_directory.parent position = position_directory.name save_directory = experiment_directory / 'derived_data' / 'mask' / position else: save_directory = pathlib.Path(save_directory) # Make sure that the save directory exists try: save_directory.mkdir() except FileExistsError: pass if model_path is None: model_path = base_dir / DEFAULT_MODEL if not enable_gpu: # TODO force no gpu #if 'centos' in platform.platform()? build_name = 'processImageBatch_nogpu' else: build_name = 'processImageBatch' exe_path = ( base_dir / f'matlab_compiled/{build_name}/for_redistribution_files_only/{build_name}' ) try: position_annotations = load_data.read_annotation_file( position_directory.parent / 'annotations' / (position_directory.stem + '.pickle')) except: print(position_directory.stem, ' does not have annotation') return # Enumerate files to process files_to_process = [] for image_file in sorted(list(position_directory.iterdir())): if image_file.suffix[1:] in ['png', 'tiff']: MASK_FILE_EXISTS = (save_directory / image_file.name).exists() IS_GOOD_CHANNEL = image_file.stem.split( ' ')[-1] in channels_to_process #IS_ALIVE = position_annotations[1][image_file.stem.split(' ')[0]]['stage'] != 'dead' #IS_ADULT = position_annotations[1][image_file.stem.split(' ')[0]]['stage'] == 'adult' #if (remake_masks or not MASK_FILE_EXISTS) and IS_GOOD_CHANNEL and IS_ADULT: if (remake_masks or not MASK_FILE_EXISTS) and IS_GOOD_CHANNEL: files_to_process.append(image_file) print("Files to process: ", files_to_process[0]) if len(files_to_process) == 0: print(f'No files to process for position {position_directory.name}') return # Write out tempfile with (position_directory / 'image_manifest.txt').open('w') as manifest_file: for my_file in files_to_process: manifest_file.write(str(my_file) + '\n') manifest_file.write(str(save_directory / my_file.name) + '\n') print('Starting batch segmentation') subprocess.run([ str(exe_path), str(position_directory / 'image_manifest.txt'), str(int(enable_gpu)), str(model_path) ], env=exe_env) # Post-process written images to get just the largest component with (position_directory / 'image_manifest.txt').open('r') as mf_stream: mask_files = map(str.rstrip, mf_stream.readlines()[1::2]) for mask_file in mask_files: if pathlib.Path(mask_file).exists(): mask_image = freeimage.read(mask_file) > 0 new_mask = mask.get_largest_object(mask_image).astype(np.uint8) freeimage.write(new_mask * 255, mask_file) else: print(f'No mask region found for {mask_file}')
def find_worm_from_fluorescence(image, low_pct=99.2, high_pct=99.99, max_hole_radius=12): low_thresh, high_thresh = numpy.percentile(image, [low_pct, high_pct]) worm_mask = mask.hysteresis_threshold(image, low_thresh, high_thresh) worm_mask = mask.fill_small_radius_holes(worm_mask, max_hole_radius) worm_mask = mask.get_largest_object(worm_mask) return worm_mask
def ensure_human(human_dir, work_dir, data_dir): ''' Make sure that the human data directory, human_dir, has everything it needs from the working directory (namely properly corrected bf images and rough background subtraction masks). ''' def test_and_copy(test_file, found_file): ''' Check for test_file file in human_dir, then copy them over from found_file in work_dir if needed. ''' if not os.path.isfile(test_file): print('Missing ' + test_file.split(' ')[-1] + ' at ' + test_file + '.') if os.path.isfile(found_file): print('\tFound corresponding ' + found_file.split(' ')[-1] + ' at ' + found_file + '.') print('\tCopying file...') shutil.copyfile(found_file, test_file) if os.path.isfile(test_file): print('\tSuccess!') else: raise BaseException('\tCOPYING FAILED.') else: raise BaseException('\tCouldn\'t find corresponding file.') return points_list = [] for a_subdir in os.listdir(human_dir): human_subdir = human_dir + os.path.sep + a_subdir for a_file in os.listdir(human_subdir): if a_file.split('.')[-1] == 'png': file_split = a_file.split(' ') points_list.append(a_subdir + os.path.sep + file_split[0]) points_list = sorted(list(set(points_list))) for a_point in points_list: # Set up some variables and copy metadata. (a_subdir, the_point) = a_point.split(os.path.sep) human_subdir = human_dir + os.path.sep + a_subdir working_subdir = work_dir + os.path.sep + a_subdir.split(' ')[-1] data_subdir = data_dir + os.path.sep + a_subdir.split(' ')[-1] base_metadata = human_subdir + os.path.sep + 'position_metadata.json' found_metadata = data_subdir + os.path.sep + 'position_metadata.json' test_and_copy(base_metadata, found_metadata) # Clean up human mask. base_test = human_subdir + os.path.sep + the_point base_found = working_subdir + os.path.sep + the_point test_and_copy(base_test + ' ' + 'hmask.png', base_test + ' ' + 'outline.png') print('Cleaning up ' + base_test + ' ' + 'hmask.png' + '.') old_mask = freeimage.read(base_test + ' ' + 'hmask.png') my_mask = np.zeros(old_mask.shape[:2]).astype('uint8') if len(old_mask.shape) == 3: my_mask[ np.max(old_mask[:, :, :3], axis=2).astype('float64') > 0] = -1 elif len(old_mask.shape) == 2: my_mask[old_mask > 0] = -1 else: raise ValueError(base_test + ' ' + 'hmask.png' + 'does not have proper dimensions.') my_mask[np.invert( zplib_image_mask.get_largest_object(my_mask) > 0)] = 0 freeimage.write(my_mask, base_test + ' ' + 'hmask.png') # Clean up old stuff. if os.path.isfile(base_test + ' ' + 'bf.png'): os.remove(base_test + ' ' + 'bf.png') if os.path.isfile(base_test + ' ' + 'outline.png'): os.remove(base_test + ' ' + 'outline.png') # Copy over other test masks. test_and_copy(base_test + ' ' + 'bf.png', base_found + ' ' + 'bf.png') test_and_copy(base_test + ' ' + 'mask.png', base_found + ' ' + 'mask.png') return