def test_04_03_strel_disk(self): r = np.random.RandomState() r.seed(43) module = morph.Morph() module.functions[0].structuring_element.value = morph.SE_DISK strel = cpmorph.strel_disk(3.5) pixel_data = r.uniform(size=(20, 30)) > .5 expected = scind.binary_dilation(pixel_data, strel) result = self.execute(pixel_data, morph.F_DILATE, scale=7, module=module) np.testing.assert_array_equal(expected, result)
def binary_tteesstt(self, function_name, function, gray_out=False, scale=None, custom_repeats=None): np.random.seed(map(ord, function_name)) input = np.random.uniform(size=(20, 20)) > .7 output = self.execute(input, function_name, scale=scale, custom_repeats=custom_repeats) if scale is None: expected = function(input) else: footprint = cpmorph.strel_disk(float(scale) / 2.0) expected = function(input, footprint=footprint) if not gray_out: expected = expected > 0 self.assertTrue(np.all(output == expected)) else: self.assertTrue(np.all(np.abs(output - expected) < np.finfo(np.float32).eps))
def run(self, workspace): """Run the module workspace - the workspace contains: pipeline - instance of CellProfiler.Pipeline for this run image_set - the images in the image set being processed object_set - the objects (labeled masks) in this image set measurements - the measurements for this run frame - display within this frame (or None to not display) """ input = workspace.image_set.get_image(self.image_name.value, must_be_grayscale=True) pixels = input.pixel_data.copy() binary_image, local_thresh = self.threshold_image( self.image_name.value, workspace, wants_local_threshold=True) if self.binary != 'Grayscale': pixels = binary_image & input.mask else: if self.low_or_high == TH_BELOW_THRESHOLD: pixels[input.mask & ~binary_image] = 0 if self.shift.value: pixels[input.mask & binary_image] -= \ local_thresh if self.threshold_modifier == TM_GLOBAL \ else local_thresh[input.mask & binary_image] elif self.low_or_high == TH_ABOVE_THRESHOLD: undilated = input.mask & binary_image dilated = binary_dilation(undilated, strel_disk(self.dilation.value), mask=input.mask) pixels[dilated] = 0 else: raise NotImplementedError( """Threshold setting, "%s" is not "%s" or "%s".""" % (self.low_or_high.value, TH_BELOW_THRESHOLD, TH_ABOVE_THRESHOLD)) output = cpimage.Image(pixels, parent_image=input) workspace.image_set.add(self.thresholded_image_name.value, output) if self.show_window: workspace.display_data.input_pixel_data = input.pixel_data workspace.display_data.output_pixel_data = output.pixel_data statistics = workspace.display_data.statistics = [] workspace.display_data.col_labels = ("Feature", "Value") for column in self.get_measurement_columns(workspace.pipeline): value = workspace.measurements.get_current_image_measurement( column[1]) statistics += [(column[1].split('_')[1], str(value))]
def run(self,workspace): """Run the module workspace - the workspace contains: pipeline - instance of CellProfiler.Pipeline for this run image_set - the images in the image set being processed object_set - the objects (labeled masks) in this image set measurements - the measurements for this run frame - display within this frame (or None to not display) """ input = workspace.image_set.get_image(self.image_name.value, must_be_grayscale=True) pixels = input.pixel_data.copy() binary_image, local_thresh = self.threshold_image( self.image_name.value, workspace, wants_local_threshold=True) if self.binary != 'Grayscale': pixels = binary_image & input.mask else: if self.low_or_high == TH_BELOW_THRESHOLD: pixels[input.mask & ~ binary_image] = 0 if self.shift.value: pixels[input.mask & binary_image] -= \ local_thresh if self.threshold_modifier == TM_GLOBAL \ else local_thresh[input.mask & binary_image] elif self.low_or_high == TH_ABOVE_THRESHOLD: undilated = input.mask & binary_image dilated = binary_dilation(undilated, strel_disk(self.dilation.value), mask=input.mask) pixels[dilated] = 0 else: raise NotImplementedError( """Threshold setting, "%s" is not "%s" or "%s".""" % (self.low_or_high.value, TH_BELOW_THRESHOLD, TH_ABOVE_THRESHOLD)) output = cpimage.Image(pixels, parent_image=input) workspace.image_set.add(self.thresholded_image_name.value, output) if self.show_window: workspace.display_data.input_pixel_data = input.pixel_data workspace.display_data.output_pixel_data = output.pixel_data statistics = workspace.display_data.statistics = [] workspace.display_data.col_labels = ("Feature", "Value") for column in self.get_measurement_columns(workspace.pipeline): value = workspace.measurements.get_current_image_measurement(column[1]) statistics += [(column[1].split('_')[1], str(value))]
def run_function(self, function, pixel_data, mask): '''Apply the function once to the image, returning the result''' count = function.repeat_count function_name = function.function.value scale = function.scale.value custom_repeats = function.custom_repeats.value is_binary = pixel_data.dtype.kind == 'b' if function.structuring_element == SE_ARBITRARY: strel = np.array(function.strel.get_matrix()) elif function.structuring_element == SE_DISK: strel = morph.strel_disk(scale / 2.0) elif function.structuring_element == SE_DIAMOND: strel = morph.strel_diamond(scale / 2.0) elif function.structuring_element == SE_LINE: strel = morph.strel_line(scale, function.angle.value) elif function.structuring_element == SE_OCTAGON: strel = morph.strel_octagon(scale / 2.0) elif function.structuring_element == SE_PAIR: strel = morph.strel_pair(function.x_offset.value, function.y_offset.value) elif function.structuring_element == SE_PERIODIC_LINE: xoff = function.x_offset.value yoff = function.y_offset.value n = max(scale / 2.0 / np.sqrt(float(xoff * xoff + yoff * yoff)), 1) strel = morph.strel_periodicline(xoff, yoff, n) elif function.structuring_element == SE_RECTANGLE: strel = morph.strel_rectangle(function.width.value, function.height.value) else: strel = morph.strel_square(scale) if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SKELPE, F_SPUR, F_THICKEN, F_THIN, F_VBREAK) and not is_binary): # Apply a very crude threshold to the image for binary algorithms logger.warning("Warning: converting image to binary for %s\n" % function_name) pixel_data = pixel_data != 0 if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK, F_INVERT, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SKELPE, F_SPUR, F_THICKEN, F_THIN, F_VBREAK, F_OPENLINES) or (is_binary and function_name in (F_CLOSE, F_DILATE, F_ERODE, F_OPEN))): # All of these have an iterations argument or it makes no # sense to iterate if function_name == F_BRANCHPOINTS: return morph.branchpoints(pixel_data, mask) elif function_name == F_BRIDGE: return morph.bridge(pixel_data, mask, count) elif function_name == F_CLEAN: return morph.clean(pixel_data, mask, count) elif function_name == F_CLOSE: if mask is None: return scind.binary_closing(pixel_data, strel, iterations=count) else: return (scind.binary_closing( pixel_data & mask, strel, iterations=count) | (pixel_data & ~mask)) elif function_name == F_CONVEX_HULL: if mask is None: return morph.convex_hull_image(pixel_data) else: return morph.convex_hull_image(pixel_data & mask) elif function_name == F_DIAG: return morph.diag(pixel_data, mask, count) elif function_name == F_DILATE: return scind.binary_dilation(pixel_data, strel, iterations=count, mask=mask) elif function_name == F_DISTANCE: image = scind.distance_transform_edt(pixel_data) if function.rescale_values.value: image = image / np.max(image) return image elif function_name == F_ENDPOINTS: return morph.endpoints(pixel_data, mask) elif function_name == F_ERODE: return scind.binary_erosion(pixel_data, strel, iterations=count, mask=mask) elif function_name == F_FILL: return morph.fill(pixel_data, mask, count) elif function_name == F_FILL_SMALL: def small_fn(area, foreground): return (not foreground) and (area <= custom_repeats) return morph.fill_labeled_holes(pixel_data, mask, small_fn) elif function_name == F_HBREAK: return morph.hbreak(pixel_data, mask, count) elif function_name == F_INVERT: if is_binary: if mask is None: return ~pixel_data result = pixel_data.copy() result[mask] = ~result[mask] return result elif mask is None: return 1 - pixel_data else: result = pixel_data.copy() result[mask] = 1 - result[mask] return result elif function_name == F_LIFE: return morph.life(pixel_data, count) elif function_name == F_MAJORITY: return morph.majority(pixel_data, mask, count) elif function_name == F_OPEN: if mask is None: return scind.binary_opening(pixel_data, strel, iterations=count) else: return (scind.binary_opening( pixel_data & mask, strel, iterations=count) | (pixel_data & ~mask)) elif function_name == F_OPENLINES: return morph.openlines(pixel_data, linelength=custom_repeats, mask=mask) elif function_name == F_REMOVE: return morph.remove(pixel_data, mask, count) elif function_name == F_SHRINK: return morph.binary_shrink(pixel_data, count) elif function_name == F_SKEL: return morph.skeletonize(pixel_data, mask) elif function_name == F_SKELPE: return morph.skeletonize( pixel_data, mask, scind.distance_transform_edt(pixel_data) * poisson_equation(pixel_data)) elif function_name == F_SPUR: return morph.spur(pixel_data, mask, count) elif function_name == F_THICKEN: return morph.thicken(pixel_data, mask, count) elif function_name == F_THIN: return morph.thin(pixel_data, mask, count) elif function_name == F_VBREAK: return morph.vbreak(pixel_data, mask) else: raise NotImplementedError( "Unimplemented morphological function: %s" % function_name) else: for i in range(count): if function_name == F_BOTHAT: new_pixel_data = morph.black_tophat(pixel_data, mask=mask, footprint=strel) elif function_name == F_CLOSE: new_pixel_data = morph.closing(pixel_data, mask=mask, footprint=strel) elif function_name == F_DILATE: new_pixel_data = morph.grey_dilation(pixel_data, mask=mask, footprint=strel) elif function_name == F_ERODE: new_pixel_data = morph.grey_erosion(pixel_data, mask=mask, footprint=strel) elif function_name == F_OPEN: new_pixel_data = morph.opening(pixel_data, mask=mask, footprint=strel) elif function_name == F_TOPHAT: new_pixel_data = morph.white_tophat(pixel_data, mask=mask, footprint=strel) else: raise NotImplementedError( "Unimplemented morphological function: %s" % function_name) if np.all(new_pixel_data == pixel_data): break pixel_data = new_pixel_data return pixel_data
def run(self, workspace): '''Run the module on the image set''' seed_objects_name = self.seed_objects_name.value skeleton_name = self.image_name.value seed_objects = workspace.object_set.get_objects(seed_objects_name) labels = seed_objects.segmented labels_count = np.max(labels) label_range = np.arange(labels_count, dtype=np.int32) + 1 skeleton_image = workspace.image_set.get_image( skeleton_name, must_be_binary=True) skeleton = skeleton_image.pixel_data if skeleton_image.has_mask: skeleton = skeleton & skeleton_image.mask try: labels = skeleton_image.crop_image_similarly(labels) except: labels, m1 = cpo.size_similarly(skeleton, labels) labels[~m1] = 0 # # The following code makes a ring around the seed objects with # the skeleton trunks sticking out of it. # # Create a new skeleton with holes at the seed objects # First combine the seed objects with the skeleton so # that the skeleton trunks come out of the seed objects. # # Erode the labels once so that all of the trunk branchpoints # will be within the labels # # # Dilate the objects, then subtract them to make a ring # my_disk = morph.strel_disk(1.5).astype(int) dilated_labels = grey_dilation(labels, footprint=my_disk) seed_mask = dilated_labels > 0 combined_skel = skeleton | seed_mask closed_labels = grey_erosion(dilated_labels, footprint=my_disk) seed_center = closed_labels > 0 combined_skel = combined_skel & (~seed_center) # # Fill in single holes (but not a one-pixel hole made by # a one-pixel image) # if self.wants_to_fill_holes: def size_fn(area, is_object): return (~ is_object) and (area <= self.maximum_hole_size.value) combined_skel = morph.fill_labeled_holes( combined_skel, ~seed_center, size_fn) # # Reskeletonize to make true branchpoints at the ring boundaries # combined_skel = morph.skeletonize(combined_skel) # # The skeleton outside of the labels # outside_skel = combined_skel & (dilated_labels == 0) # # Associate all skeleton points with seed objects # dlabels, distance_map = propagate.propagate(np.zeros(labels.shape), dilated_labels, combined_skel, 1) # # Get rid of any branchpoints not connected to seeds # combined_skel[dlabels == 0] = False # # Find the branchpoints # branch_points = morph.branchpoints(combined_skel) # # Odd case: when four branches meet like this, branchpoints are not # assigned because they are arbitrary. So assign them. # # . . # B. # .B # . . # odd_case = (combined_skel[:-1, :-1] & combined_skel[1:, :-1] & combined_skel[:-1, 1:] & combined_skel[1, 1]) branch_points[:-1, :-1][odd_case] = True branch_points[1:, 1:][odd_case] = True # # Find the branching counts for the trunks (# of extra branches # eminating from a point other than the line it might be on). # branching_counts = morph.branchings(combined_skel) branching_counts = np.array([0, 0, 0, 1, 2])[branching_counts] # # Only take branches within 1 of the outside skeleton # dilated_skel = scind.binary_dilation(outside_skel, morph.eight_connect) branching_counts[~dilated_skel] = 0 # # Find the endpoints # end_points = morph.endpoints(combined_skel) # # We use two ranges for classification here: # * anything within one pixel of the dilated image is a trunk # * anything outside of that range is a branch # nearby_labels = dlabels.copy() nearby_labels[distance_map > 1.5] = 0 outside_labels = dlabels.copy() outside_labels[nearby_labels > 0] = 0 # # The trunks are the branchpoints that lie within one pixel of # the dilated image. # if labels_count > 0: trunk_counts = fix(scind.sum(branching_counts, nearby_labels, label_range)).astype(int) else: trunk_counts = np.zeros((0,), int) # # The branches are the branchpoints that lie outside the seed objects # if labels_count > 0: branch_counts = fix(scind.sum(branch_points, outside_labels, label_range)) else: branch_counts = np.zeros((0,), int) # # Save the endpoints # if labels_count > 0: end_counts = fix(scind.sum(end_points, outside_labels, label_range)) else: end_counts = np.zeros((0,), int) # # Calculate the distances # total_distance = morph.skeleton_length( dlabels * outside_skel, label_range) # # Save measurements # m = workspace.measurements assert isinstance(m, cpmeas.Measurements) feature = "_".join((C_NEURON, F_NUMBER_TRUNKS, skeleton_name)) m.add_measurement(seed_objects_name, feature, trunk_counts) feature = "_".join((C_NEURON, F_NUMBER_NON_TRUNK_BRANCHES, skeleton_name)) m.add_measurement(seed_objects_name, feature, branch_counts) feature = "_".join((C_NEURON, F_NUMBER_BRANCH_ENDS, skeleton_name)) m.add_measurement(seed_objects_name, feature, end_counts) feature = "_".join((C_NEURON, F_TOTAL_NEURITE_LENGTH, skeleton_name)) m[seed_objects_name, feature] = total_distance # # Collect the graph information # if self.wants_neuron_graph: trunk_mask = (branching_counts > 0) & (nearby_labels != 0) intensity_image = workspace.image_set.get_image( self.intensity_image_name.value) edge_graph, vertex_graph = self.make_neuron_graph( combined_skel, dlabels, trunk_mask, branch_points & ~trunk_mask, end_points, intensity_image.pixel_data) image_number = workspace.measurements.image_set_number edge_path, vertex_path = self.get_graph_file_paths(m, m.image_number) workspace.interaction_request( self, m.image_number, edge_path, edge_graph, vertex_path, vertex_graph, headless_ok=True) if self.show_window: workspace.display_data.edge_graph = edge_graph workspace.display_data.vertex_graph = vertex_graph workspace.display_data.intensity_image = intensity_image.pixel_data # # Make the display image # if self.show_window or self.wants_branchpoint_image: branchpoint_image = np.zeros((skeleton.shape[0], skeleton.shape[1], 3)) trunk_mask = (branching_counts > 0) & (nearby_labels != 0) branch_mask = branch_points & (outside_labels != 0) end_mask = end_points & (outside_labels != 0) branchpoint_image[outside_skel, :] = 1 branchpoint_image[trunk_mask | branch_mask | end_mask, :] = 0 branchpoint_image[trunk_mask, 0] = 1 branchpoint_image[branch_mask, 1] = 1 branchpoint_image[end_mask, 2] = 1 branchpoint_image[dilated_labels != 0, :] *= .875 branchpoint_image[dilated_labels != 0, :] += .1 if self.show_window: workspace.display_data.branchpoint_image = branchpoint_image if self.wants_branchpoint_image: bi = cpi.Image(branchpoint_image, parent_image=skeleton_image) workspace.image_set.add(self.branchpoint_image_name.value, bi)
def run(self, workspace): '''Run the module on the image set''' seed_objects_name = self.seed_objects_name.value skeleton_name = self.image_name.value seed_objects = workspace.object_set.get_objects(seed_objects_name) labels = seed_objects.segmented labels_count = np.max(labels) label_range = np.arange(labels_count, dtype=np.int32) + 1 skeleton_image = workspace.image_set.get_image(skeleton_name, must_be_binary=True) skeleton = skeleton_image.pixel_data if skeleton_image.has_mask: skeleton = skeleton & skeleton_image.mask try: labels = skeleton_image.crop_image_similarly(labels) except: labels, m1 = cpo.size_similarly(skeleton, labels) labels[~m1] = 0 # # The following code makes a ring around the seed objects with # the skeleton trunks sticking out of it. # # Create a new skeleton with holes at the seed objects # First combine the seed objects with the skeleton so # that the skeleton trunks come out of the seed objects. # # Erode the labels once so that all of the trunk branchpoints # will be within the labels # # # Dilate the objects, then subtract them to make a ring # my_disk = morph.strel_disk(1.5).astype(int) dilated_labels = grey_dilation(labels, footprint=my_disk) seed_mask = dilated_labels > 0 combined_skel = skeleton | seed_mask closed_labels = grey_erosion(dilated_labels, footprint=my_disk) seed_center = closed_labels > 0 combined_skel = combined_skel & (~seed_center) # # Fill in single holes (but not a one-pixel hole made by # a one-pixel image) # if self.wants_to_fill_holes: def size_fn(area, is_object): return (~is_object) and (area <= self.maximum_hole_size.value) combined_skel = morph.fill_labeled_holes(combined_skel, ~seed_center, size_fn) # # Reskeletonize to make true branchpoints at the ring boundaries # combined_skel = morph.skeletonize(combined_skel) # # The skeleton outside of the labels # outside_skel = combined_skel & (dilated_labels == 0) # # Associate all skeleton points with seed objects # dlabels, distance_map = propagate.propagate(np.zeros(labels.shape), dilated_labels, combined_skel, 1) # # Get rid of any branchpoints not connected to seeds # combined_skel[dlabels == 0] = False # # Find the branchpoints # branch_points = morph.branchpoints(combined_skel) # # Odd case: when four branches meet like this, branchpoints are not # assigned because they are arbitrary. So assign them. # # . . # B. # .B # . . # odd_case = (combined_skel[:-1, :-1] & combined_skel[1:, :-1] & combined_skel[:-1, 1:] & combined_skel[1, 1]) branch_points[:-1, :-1][odd_case] = True branch_points[1:, 1:][odd_case] = True # # Find the branching counts for the trunks (# of extra branches # emanating from a point other than the line it might be on). # branching_counts = morph.branchings(combined_skel) branching_counts = np.array([0, 0, 0, 1, 2])[branching_counts] # # Only take branches within 1 of the outside skeleton # dilated_skel = scind.binary_dilation(outside_skel, morph.eight_connect) branching_counts[~dilated_skel] = 0 # # Find the endpoints # end_points = morph.endpoints(combined_skel) # # We use two ranges for classification here: # * anything within one pixel of the dilated image is a trunk # * anything outside of that range is a branch # nearby_labels = dlabels.copy() nearby_labels[distance_map > 1.5] = 0 outside_labels = dlabels.copy() outside_labels[nearby_labels > 0] = 0 # # The trunks are the branchpoints that lie within one pixel of # the dilated image. # if labels_count > 0: trunk_counts = fix( scind.sum(branching_counts, nearby_labels, label_range)).astype(int) else: trunk_counts = np.zeros((0, ), int) # # The branches are the branchpoints that lie outside the seed objects # if labels_count > 0: branch_counts = fix( scind.sum(branch_points, outside_labels, label_range)) else: branch_counts = np.zeros((0, ), int) # # Save the endpoints # if labels_count > 0: end_counts = fix(scind.sum(end_points, outside_labels, label_range)) else: end_counts = np.zeros((0, ), int) # # Calculate the distances # total_distance = morph.skeleton_length(dlabels * outside_skel, label_range) # # Save measurements # m = workspace.measurements assert isinstance(m, cpmeas.Measurements) feature = "_".join((C_OBJSKELETON, F_NUMBER_TRUNKS, skeleton_name)) m.add_measurement(seed_objects_name, feature, trunk_counts) feature = "_".join( (C_OBJSKELETON, F_NUMBER_NON_TRUNK_BRANCHES, skeleton_name)) m.add_measurement(seed_objects_name, feature, branch_counts) feature = "_".join( (C_OBJSKELETON, F_NUMBER_BRANCH_ENDS, skeleton_name)) m.add_measurement(seed_objects_name, feature, end_counts) feature = "_".join( (C_OBJSKELETON, F_TOTAL_OBJSKELETON_LENGTH, skeleton_name)) m[seed_objects_name, feature] = total_distance # # Collect the graph information # if self.wants_objskeleton_graph: trunk_mask = (branching_counts > 0) & (nearby_labels != 0) intensity_image = workspace.image_set.get_image( self.intensity_image_name.value) edge_graph, vertex_graph = self.make_objskeleton_graph( combined_skel, dlabels, trunk_mask, branch_points & ~trunk_mask, end_points, intensity_image.pixel_data) image_number = workspace.measurements.image_set_number edge_path, vertex_path = self.get_graph_file_paths( m, m.image_number) workspace.interaction_request(self, m.image_number, edge_path, edge_graph, vertex_path, vertex_graph, headless_ok=True) if self.show_window: workspace.display_data.edge_graph = edge_graph workspace.display_data.vertex_graph = vertex_graph workspace.display_data.intensity_image = intensity_image.pixel_data # # Make the display image # if self.show_window or self.wants_branchpoint_image: branchpoint_image = np.zeros( (skeleton.shape[0], skeleton.shape[1], 3)) trunk_mask = (branching_counts > 0) & (nearby_labels != 0) branch_mask = branch_points & (outside_labels != 0) end_mask = end_points & (outside_labels != 0) branchpoint_image[outside_skel, :] = 1 branchpoint_image[trunk_mask | branch_mask | end_mask, :] = 0 branchpoint_image[trunk_mask, 0] = 1 branchpoint_image[branch_mask, 1] = 1 branchpoint_image[end_mask, 2] = 1 branchpoint_image[dilated_labels != 0, :] *= .875 branchpoint_image[dilated_labels != 0, :] += .1 if self.show_window: workspace.display_data.branchpoint_image = branchpoint_image if self.wants_branchpoint_image: bi = cpi.Image(branchpoint_image, parent_image=skeleton_image) workspace.image_set.add(self.branchpoint_image_name.value, bi)
def run(self, workspace): objects = workspace.object_set.get_objects(self.object_name.value) assert isinstance(objects, cpo.Objects) has_pixels = objects.areas > 0 labels = objects.small_removed_segmented kept_labels = objects.segmented neighbor_objects = workspace.object_set.get_objects( self.neighbors_name.value) assert isinstance(neighbor_objects, cpo.Objects) neighbor_labels = neighbor_objects.small_removed_segmented # # Need to add in labels touching border. # unedited_segmented = neighbor_objects.unedited_segmented touching_border = np.zeros(np.max(unedited_segmented) + 1, bool) touching_border[unedited_segmented[0, :]] = True touching_border[unedited_segmented[-1, :]] = True touching_border[unedited_segmented[:, 0]] = True touching_border[unedited_segmented[:, -1]] = True touching_border[0] = False touching_border_mask = touching_border[unedited_segmented] nobjects = np.max(labels) nkept_objects = objects.count nneighbors = np.max(neighbor_labels) if np.any(touching_border) and \ np.all(~ touching_border_mask[neighbor_labels != 0]): # Add the border labels if any were excluded touching_border_object_number = np.cumsum(touching_border) + \ np.max(neighbor_labels) touching_border_mask = touching_border_mask & (neighbor_labels == 0) neighbor_labels = neighbor_labels.copy().astype(np.int32) neighbor_labels[ touching_border_mask] = touching_border_object_number[ unedited_segmented[touching_border_mask]] _, object_numbers = objects.relate_labels(labels, kept_labels) if self.neighbors_are_objects: neighbor_numbers = object_numbers neighbor_has_pixels = has_pixels else: _, neighbor_numbers = neighbor_objects.relate_labels( neighbor_labels, neighbor_objects.segmented) neighbor_has_pixels = np.bincount(neighbor_labels.ravel())[1:] > 0 neighbor_count = np.zeros((nobjects, )) pixel_count = np.zeros((nobjects, )) first_object_number = np.zeros((nobjects, ), int) second_object_number = np.zeros((nobjects, ), int) first_x_vector = np.zeros((nobjects, )) second_x_vector = np.zeros((nobjects, )) first_y_vector = np.zeros((nobjects, )) second_y_vector = np.zeros((nobjects, )) angle = np.zeros((nobjects, )) percent_touching = np.zeros((nobjects, )) expanded_labels = None if self.distance_method == D_EXPAND: # Find the i,j coordinates of the nearest foreground point # to every background point i, j = scind.distance_transform_edt(labels == 0, return_distances=False, return_indices=True) # Assign each background pixel to the label of its nearest # foreground pixel. Assign label to label for foreground. labels = labels[i, j] expanded_labels = labels # for display distance = 1 # dilate once to make touching edges overlap scale = S_EXPANDED if self.neighbors_are_objects: neighbor_labels = labels.copy() elif self.distance_method == D_WITHIN: distance = self.distance.value scale = str(distance) elif self.distance_method == D_ADJACENT: distance = 1 scale = S_ADJACENT else: raise ValueError("Unknown distance method: %s" % self.distance_method.value) if nneighbors > (1 if self.neighbors_are_objects else 0): first_objects = [] second_objects = [] object_indexes = np.arange(nobjects, dtype=np.int32) + 1 # # First, compute the first and second nearest neighbors, # and the angles between self and the first and second # nearest neighbors # ocenters = centers_of_labels( objects.small_removed_segmented).transpose() ncenters = centers_of_labels( neighbor_objects.small_removed_segmented).transpose() areas = fix( scind.sum(np.ones(labels.shape), labels, object_indexes)) perimeter_outlines = outline(labels) perimeters = fix( scind.sum(np.ones(labels.shape), perimeter_outlines, object_indexes)) i, j = np.mgrid[0:nobjects, 0:nneighbors] distance_matrix = np.sqrt((ocenters[i, 0] - ncenters[j, 0])**2 + (ocenters[i, 1] - ncenters[j, 1])**2) # # order[:,0] should be arange(nobjects) # order[:,1] should be the nearest neighbor # order[:,2] should be the next nearest neighbor # if distance_matrix.shape[1] == 1: # a little buggy, lexsort assumes that a 2-d array of # second dimension = 1 is a 1-d array order = np.zeros(distance_matrix.shape, int) else: order = np.lexsort([distance_matrix]) first_neighbor = 1 if self.neighbors_are_objects else 0 first_object_index = order[:, first_neighbor] first_x_vector = ncenters[first_object_index, 1] - ocenters[:, 1] first_y_vector = ncenters[first_object_index, 0] - ocenters[:, 0] if nneighbors > first_neighbor + 1: second_object_index = order[:, first_neighbor + 1] second_x_vector = ncenters[second_object_index, 1] - ocenters[:, 1] second_y_vector = ncenters[second_object_index, 0] - ocenters[:, 0] v1 = np.array((first_x_vector, first_y_vector)) v2 = np.array((second_x_vector, second_y_vector)) # # Project the unit vector v1 against the unit vector v2 # dot = (np.sum(v1 * v2, 0) / np.sqrt(np.sum(v1**2, 0) * np.sum(v2**2, 0))) angle = np.arccos(dot) * 180. / np.pi # Make the structuring element for dilation strel = strel_disk(distance) # # A little bigger one to enter into the border with a structure # that mimics the one used to create the outline # strel_touching = strel_disk(distance + .5) # # Get the extents for each object and calculate the patch # that excises the part of the image that is "distance" # away i, j = np.mgrid[0:labels.shape[0], 0:labels.shape[1]] min_i, max_i, min_i_pos, max_i_pos = \ scind.extrema(i, labels, object_indexes) min_j, max_j, min_j_pos, max_j_pos = \ scind.extrema(j, labels, object_indexes) min_i = np.maximum(fix(min_i) - distance, 0).astype(int) max_i = np.minimum(fix(max_i) + distance + 1, labels.shape[0]).astype(int) min_j = np.maximum(fix(min_j) - distance, 0).astype(int) max_j = np.minimum(fix(max_j) + distance + 1, labels.shape[1]).astype(int) # # Loop over all objects # Calculate which ones overlap "index" # Calculate how much overlap there is of others to "index" # for object_number in object_numbers: if object_number == 0: # # No corresponding object in small-removed. This means # that the object has no pixels, e.g. not renumbered. # continue index = object_number - 1 patch = labels[min_i[index]:max_i[index], min_j[index]:max_j[index]] npatch = neighbor_labels[min_i[index]:max_i[index], min_j[index]:max_j[index]] # # Find the neighbors # patch_mask = patch == (index + 1) extended = scind.binary_dilation(patch_mask, strel) neighbors = np.unique(npatch[extended]) neighbors = neighbors[neighbors != 0] if self.neighbors_are_objects: neighbors = neighbors[neighbors != object_number] nc = len(neighbors) neighbor_count[index] = nc if nc > 0: first_objects.append(np.ones(nc, int) * object_number) second_objects.append(neighbors) if self.neighbors_are_objects: # # Find the # of overlapping pixels. Dilate the neighbors # and see how many pixels overlap our image. Use a 3x3 # structuring element to expand the overlapping edge # into the perimeter. # outline_patch = perimeter_outlines[ min_i[index]:max_i[index], min_j[index]:max_j[index]] == object_number extended = scind.binary_dilation( (patch != 0) & (patch != object_number), strel_touching) overlap = np.sum(outline_patch & extended) pixel_count[index] = overlap if sum([len(x) for x in first_objects]) > 0: first_objects = np.hstack(first_objects) reverse_object_numbers = np.zeros( max(np.max(object_numbers), np.max(first_objects)) + 1, int) reverse_object_numbers[object_numbers] = np.arange( len(object_numbers)) + 1 first_objects = reverse_object_numbers[first_objects] second_objects = np.hstack(second_objects) reverse_neighbor_numbers = np.zeros( max(np.max(neighbor_numbers), np.max(second_objects)) + 1, int) reverse_neighbor_numbers[neighbor_numbers] = np.arange( len(neighbor_numbers)) + 1 second_objects = reverse_neighbor_numbers[second_objects] to_keep = (first_objects > 0) & (second_objects > 0) first_objects = first_objects[to_keep] second_objects = second_objects[to_keep] else: first_objects = np.zeros(0, int) second_objects = np.zeros(0, int) if self.neighbors_are_objects: percent_touching = pixel_count * 100 / perimeters else: percent_touching = pixel_count * 100.0 / areas object_indexes = object_numbers - 1 neighbor_indexes = neighbor_numbers - 1 # # Have to recompute nearest # first_object_number = np.zeros(nkept_objects, int) second_object_number = np.zeros(nkept_objects, int) if nkept_objects > (1 if self.neighbors_are_objects else 0): di = (ocenters[object_indexes[:, np.newaxis], 0] - ncenters[neighbor_indexes[np.newaxis, :], 0]) dj = (ocenters[object_indexes[:, np.newaxis], 1] - ncenters[neighbor_indexes[np.newaxis, :], 1]) distance_matrix = np.sqrt(di * di + dj * dj) distance_matrix[~has_pixels, :] = np.inf distance_matrix[:, ~neighbor_has_pixels] = np.inf # # order[:,0] should be arange(nobjects) # order[:,1] should be the nearest neighbor # order[:,2] should be the next nearest neighbor # order = np.lexsort([distance_matrix ]).astype(first_object_number.dtype) if self.neighbors_are_objects: first_object_number[has_pixels] = order[has_pixels, 1] + 1 if nkept_objects > 2: second_object_number[has_pixels] = order[has_pixels, 2] + 1 else: first_object_number[has_pixels] = order[has_pixels, 0] + 1 if order.shape[1] > 1: second_object_number[has_pixels] = order[has_pixels, 1] + 1 else: object_indexes = object_numbers - 1 neighbor_indexes = neighbor_numbers - 1 first_objects = np.zeros(0, int) second_objects = np.zeros(0, int) # # Now convert all measurements from the small-removed to # the final number set. # neighbor_count = neighbor_count[object_indexes] neighbor_count[~has_pixels] = 0 percent_touching = percent_touching[object_indexes] percent_touching[~has_pixels] = 0 first_x_vector = first_x_vector[object_indexes] second_x_vector = second_x_vector[object_indexes] first_y_vector = first_y_vector[object_indexes] second_y_vector = second_y_vector[object_indexes] angle = angle[object_indexes] # # Record the measurements # assert (isinstance(workspace, cpw.Workspace)) m = workspace.measurements assert (isinstance(m, cpmeas.Measurements)) image_set = workspace.image_set features_and_data = [ (M_NUMBER_OF_NEIGHBORS, neighbor_count), (M_FIRST_CLOSEST_OBJECT_NUMBER, first_object_number), (M_FIRST_CLOSEST_DISTANCE, np.sqrt(first_x_vector**2 + first_y_vector**2)), (M_SECOND_CLOSEST_OBJECT_NUMBER, second_object_number), (M_SECOND_CLOSEST_DISTANCE, np.sqrt(second_x_vector**2 + second_y_vector**2)), (M_ANGLE_BETWEEN_NEIGHBORS, angle) ] if self.neighbors_are_objects: features_and_data.append((M_PERCENT_TOUCHING, percent_touching)) for feature_name, data in features_and_data: m.add_measurement(self.object_name.value, self.get_measurement_name(feature_name), data) if len(first_objects) > 0: m.add_relate_measurement( self.module_num, cpmeas.NEIGHBORS, self.object_name.value, self.object_name.value if self.neighbors_are_objects else self.neighbors_name.value, m.image_set_number * np.ones(first_objects.shape, int), first_objects, m.image_set_number * np.ones(second_objects.shape, int), second_objects) labels = kept_labels neighbor_count_image = np.zeros(labels.shape, int) object_mask = objects.segmented != 0 object_indexes = objects.segmented[object_mask] - 1 neighbor_count_image[object_mask] = neighbor_count[object_indexes] workspace.display_data.neighbor_count_image = neighbor_count_image if self.neighbors_are_objects: percent_touching_image = np.zeros(labels.shape) percent_touching_image[object_mask] = percent_touching[ object_indexes] workspace.display_data.percent_touching_image = percent_touching_image image_set = workspace.image_set if self.wants_count_image.value: neighbor_cm_name = self.count_colormap.value neighbor_cm = get_colormap(neighbor_cm_name) sm = matplotlib.cm.ScalarMappable(cmap=neighbor_cm) img = sm.to_rgba(neighbor_count_image)[:, :, :3] img[:, :, 0][~object_mask] = 0 img[:, :, 1][~object_mask] = 0 img[:, :, 2][~object_mask] = 0 count_image = cpi.Image(img, masking_objects=objects) image_set.add(self.count_image_name.value, count_image) else: neighbor_cm_name = cpprefs.get_default_colormap() neighbor_cm = matplotlib.cm.get_cmap(neighbor_cm_name) if self.neighbors_are_objects and self.wants_percent_touching_image: percent_touching_cm_name = self.touching_colormap.value percent_touching_cm = get_colormap(percent_touching_cm_name) sm = matplotlib.cm.ScalarMappable(cmap=percent_touching_cm) img = sm.to_rgba(percent_touching_image)[:, :, :3] img[:, :, 0][~object_mask] = 0 img[:, :, 1][~object_mask] = 0 img[:, :, 2][~object_mask] = 0 touching_image = cpi.Image(img, masking_objects=objects) image_set.add(self.touching_image_name.value, touching_image) else: percent_touching_cm_name = cpprefs.get_default_colormap() percent_touching_cm = matplotlib.cm.get_cmap( percent_touching_cm_name) if self.show_window: workspace.display_data.neighbor_cm_name = neighbor_cm_name workspace.display_data.percent_touching_cm_name = percent_touching_cm_name workspace.display_data.orig_labels = objects.segmented workspace.display_data.expanded_labels = expanded_labels workspace.display_data.object_mask = object_mask
def run(self, workspace): objects = workspace.object_set.get_objects(self.object_name.value) assert isinstance(objects, cpo.Objects) has_pixels = objects.areas > 0 labels = objects.small_removed_segmented kept_labels = objects.segmented neighbor_objects = workspace.object_set.get_objects( self.neighbors_name.value) assert isinstance(neighbor_objects, cpo.Objects) neighbor_labels = neighbor_objects.small_removed_segmented # # Need to add in labels touching border. # unedited_segmented = neighbor_objects.unedited_segmented touching_border = np.zeros(np.max(unedited_segmented) + 1, bool) touching_border[unedited_segmented[0, :]] = True touching_border[unedited_segmented[-1, :]] = True touching_border[unedited_segmented[:, 0]] = True touching_border[unedited_segmented[:, -1]] = True touching_border[0] = False touching_border_mask = touching_border[unedited_segmented] nobjects = np.max(labels) nkept_objects = objects.count nneighbors = np.max(neighbor_labels) if np.any(touching_border) and \ np.all(~ touching_border_mask[neighbor_labels!=0]): # Add the border labels if any were excluded touching_border_object_number = np.cumsum(touching_border) + \ np.max(neighbor_labels) touching_border_mask = touching_border_mask & (neighbor_labels == 0) neighbor_labels = neighbor_labels.copy().astype(np.int32) neighbor_labels[touching_border_mask] = touching_border_object_number[ unedited_segmented[touching_border_mask]] neighbor_has_pixels = np.bincount(neighbor_labels.ravel())[1:] > 0 _, object_numbers = objects.relate_labels(labels, kept_labels) if self.neighbors_are_objects: neighbor_numbers = object_numbers else: _, neighbor_numbers = neighbor_objects.relate_labels( neighbor_labels, neighbor_objects.segmented) neighbor_count = np.zeros((nobjects,)) pixel_count = np.zeros((nobjects,)) first_object_number = np.zeros((nobjects,),int) second_object_number = np.zeros((nobjects,),int) first_x_vector = np.zeros((nobjects,)) second_x_vector = np.zeros((nobjects,)) first_y_vector = np.zeros((nobjects,)) second_y_vector = np.zeros((nobjects,)) angle = np.zeros((nobjects,)) percent_touching = np.zeros((nobjects,)) expanded_labels = None if self.distance_method == D_EXPAND: # Find the i,j coordinates of the nearest foreground point # to every background point i,j = scind.distance_transform_edt(labels==0, return_distances=False, return_indices=True) # Assign each background pixel to the label of its nearest # foreground pixel. Assign label to label for foreground. labels = labels[i,j] expanded_labels = labels # for display distance = 1 # dilate once to make touching edges overlap scale = S_EXPANDED if self.neighbors_are_objects: neighbor_labels = labels.copy() elif self.distance_method == D_WITHIN: distance = self.distance.value scale = str(distance) elif self.distance_method == D_ADJACENT: distance = 1 scale = S_ADJACENT else: raise ValueError("Unknown distance method: %s" % self.distance_method.value) if nneighbors > (1 if self.neighbors_are_objects else 0): first_objects = [] second_objects = [] object_indexes = np.arange(nobjects, dtype=np.int32)+1 # # First, compute the first and second nearest neighbors, # and the angles between self and the first and second # nearest neighbors # ocenters = centers_of_labels( objects.small_removed_segmented).transpose() ncenters = centers_of_labels( neighbor_objects.small_removed_segmented).transpose() areas = fix(scind.sum(np.ones(labels.shape),labels, object_indexes)) perimeter_outlines = outline(labels) perimeters = fix(scind.sum( np.ones(labels.shape), perimeter_outlines, object_indexes)) i,j = np.mgrid[0:nobjects,0:nneighbors] distance_matrix = np.sqrt((ocenters[i,0] - ncenters[j,0])**2 + (ocenters[i,1] - ncenters[j,1])**2) # # order[:,0] should be arange(nobjects) # order[:,1] should be the nearest neighbor # order[:,2] should be the next nearest neighbor # if distance_matrix.shape[1] == 1: # a little buggy, lexsort assumes that a 2-d array of # second dimension = 1 is a 1-d array order = np.zeros(distance_matrix.shape, int) else: order = np.lexsort([distance_matrix]) first_neighbor = 1 if self.neighbors_are_objects else 0 first_object_index = order[:, first_neighbor] first_x_vector = ncenters[first_object_index,1] - ocenters[:,1] first_y_vector = ncenters[first_object_index,0] - ocenters[:,0] if nneighbors > first_neighbor+1: second_object_index = order[:, first_neighbor + 1] second_x_vector = ncenters[second_object_index,1] - ocenters[:,1] second_y_vector = ncenters[second_object_index,0] - ocenters[:,0] v1 = np.array((first_x_vector,first_y_vector)) v2 = np.array((second_x_vector,second_y_vector)) # # Project the unit vector v1 against the unit vector v2 # dot = (np.sum(v1*v2,0) / np.sqrt(np.sum(v1**2,0)*np.sum(v2**2,0))) angle = np.arccos(dot) * 180. / np.pi # Make the structuring element for dilation strel = strel_disk(distance) # # A little bigger one to enter into the border with a structure # that mimics the one used to create the outline # strel_touching = strel_disk(distance + .5) # # Get the extents for each object and calculate the patch # that excises the part of the image that is "distance" # away i,j = np.mgrid[0:labels.shape[0],0:labels.shape[1]] min_i, max_i, min_i_pos, max_i_pos =\ scind.extrema(i,labels,object_indexes) min_j, max_j, min_j_pos, max_j_pos =\ scind.extrema(j,labels,object_indexes) min_i = np.maximum(fix(min_i)-distance,0).astype(int) max_i = np.minimum(fix(max_i)+distance+1,labels.shape[0]).astype(int) min_j = np.maximum(fix(min_j)-distance,0).astype(int) max_j = np.minimum(fix(max_j)+distance+1,labels.shape[1]).astype(int) # # Loop over all objects # Calculate which ones overlap "index" # Calculate how much overlap there is of others to "index" # for object_number in object_numbers: if object_number == 0: # # No corresponding object in small-removed. This means # that the object has no pixels, e.g. not renumbered. # continue index = object_number - 1 patch = labels[min_i[index]:max_i[index], min_j[index]:max_j[index]] npatch = neighbor_labels[min_i[index]:max_i[index], min_j[index]:max_j[index]] # # Find the neighbors # patch_mask = patch==(index+1) extended = scind.binary_dilation(patch_mask,strel) neighbors = np.unique(npatch[extended]) neighbors = neighbors[neighbors != 0] if self.neighbors_are_objects: neighbors = neighbors[neighbors != object_number] nc = len(neighbors) neighbor_count[index] = nc if nc > 0: first_objects.append(np.ones(nc,int) * object_number) second_objects.append(neighbors) if self.neighbors_are_objects: # # Find the # of overlapping pixels. Dilate the neighbors # and see how many pixels overlap our image. Use a 3x3 # structuring element to expand the overlapping edge # into the perimeter. # outline_patch = perimeter_outlines[ min_i[index]:max_i[index], min_j[index]:max_j[index]] == object_number extended = scind.binary_dilation( (patch != 0) & (patch != object_number), strel_touching) overlap = np.sum(outline_patch & extended) pixel_count[index] = overlap if sum([len(x) for x in first_objects]) > 0: first_objects = np.hstack(first_objects) reverse_object_numbers = np.zeros( max(np.max(object_numbers), np.max(first_objects)) + 1, int) reverse_object_numbers[object_numbers] = np.arange(len(object_numbers)) + 1 first_objects = reverse_object_numbers[first_objects] second_objects = np.hstack(second_objects) reverse_neighbor_numbers = np.zeros( max(np.max(neighbor_numbers), np.max(second_objects)) + 1, int) reverse_neighbor_numbers[neighbor_numbers] = np.arange(len(neighbor_numbers)) + 1 second_objects= reverse_neighbor_numbers[second_objects] to_keep = (first_objects > 0) & (second_objects > 0) first_objects = first_objects[to_keep] second_objects = second_objects[to_keep] else: first_objects = np.zeros(0, int) second_objects = np.zeros(0, int) if self.neighbors_are_objects: percent_touching = pixel_count * 100 / perimeters else: percent_touching = pixel_count * 100.0 / areas object_indexes = object_numbers - 1 neighbor_indexes = neighbor_numbers - 1 # # Have to recompute nearest # first_object_number = np.zeros(nkept_objects, int) second_object_number = np.zeros(nkept_objects, int) if nkept_objects > (1 if self.neighbors_are_objects else 0): di = (ocenters[object_indexes[:, np.newaxis], 0] - ncenters[neighbor_indexes[np.newaxis, :], 0]) dj = (ocenters[object_indexes[:, np.newaxis], 1] - ncenters[neighbor_indexes[np.newaxis, :], 1]) distance_matrix = np.sqrt(di*di + dj*dj) distance_matrix[~ has_pixels, :] = np.inf distance_matrix[:, ~neighbor_has_pixels] = np.inf # # order[:,0] should be arange(nobjects) # order[:,1] should be the nearest neighbor # order[:,2] should be the next nearest neighbor # order = np.lexsort([distance_matrix]).astype( first_object_number.dtype) if self.neighbors_are_objects: first_object_number[has_pixels] = order[has_pixels,1] + 1 if nkept_objects > 2: second_object_number[has_pixels] = order[has_pixels,2] + 1 else: first_object_number[has_pixels] = order[has_pixels,0] + 1 if order.shape[1] > 1: second_object_number[has_pixels] = order[has_pixels,1] + 1 else: object_indexes = object_numbers - 1 neighbor_indexes = neighbor_numbers - 1 first_objects = np.zeros(0, int) second_objects = np.zeros(0, int) # # Now convert all measurements from the small-removed to # the final number set. # neighbor_count = neighbor_count[object_indexes] neighbor_count[~ has_pixels] = 0 percent_touching = percent_touching[object_indexes] percent_touching[~ has_pixels] = 0 first_x_vector = first_x_vector[object_indexes] second_x_vector = second_x_vector[object_indexes] first_y_vector = first_y_vector[object_indexes] second_y_vector = second_y_vector[object_indexes] angle = angle[object_indexes] # # Record the measurements # assert(isinstance(workspace, cpw.Workspace)) m = workspace.measurements assert(isinstance(m, cpmeas.Measurements)) image_set = workspace.image_set features_and_data = [ (M_NUMBER_OF_NEIGHBORS, neighbor_count), (M_FIRST_CLOSEST_OBJECT_NUMBER, first_object_number), (M_FIRST_CLOSEST_DISTANCE, np.sqrt(first_x_vector**2+first_y_vector**2)), (M_SECOND_CLOSEST_OBJECT_NUMBER, second_object_number), (M_SECOND_CLOSEST_DISTANCE, np.sqrt(second_x_vector**2+second_y_vector**2)), (M_ANGLE_BETWEEN_NEIGHBORS, angle)] if self.neighbors_are_objects: features_and_data.append((M_PERCENT_TOUCHING, percent_touching)) for feature_name, data in features_and_data: m.add_measurement(self.object_name.value, self.get_measurement_name(feature_name), data) if len(first_objects) > 0: m.add_relate_measurement( self.module_num, cpmeas.NEIGHBORS, self.object_name.value, self.object_name.value if self.neighbors_are_objects else self.neighbors_name.value, m.image_set_number * np.ones(first_objects.shape, int), first_objects, m.image_set_number * np.ones(second_objects.shape, int), second_objects) labels = kept_labels neighbor_count_image = np.zeros(labels.shape,int) object_mask = objects.segmented != 0 object_indexes = objects.segmented[object_mask]-1 neighbor_count_image[object_mask] = neighbor_count[object_indexes] workspace.display_data.neighbor_count_image = neighbor_count_image if self.neighbors_are_objects: percent_touching_image = np.zeros(labels.shape) percent_touching_image[object_mask] = percent_touching[object_indexes] workspace.display_data.percent_touching_image = percent_touching_image image_set = workspace.image_set if self.wants_count_image.value: neighbor_cm_name = self.count_colormap.value neighbor_cm = get_colormap(neighbor_cm_name) sm = matplotlib.cm.ScalarMappable(cmap = neighbor_cm) img = sm.to_rgba(neighbor_count_image)[:,:,:3] img[:,:,0][~ object_mask] = 0 img[:,:,1][~ object_mask] = 0 img[:,:,2][~ object_mask] = 0 count_image = cpi.Image(img, masking_objects = objects) image_set.add(self.count_image_name.value, count_image) else: neighbor_cm_name = cpprefs.get_default_colormap() neighbor_cm = matplotlib.cm.get_cmap(neighbor_cm_name) if self.neighbors_are_objects and self.wants_percent_touching_image: percent_touching_cm_name = self.touching_colormap.value percent_touching_cm = get_colormap(percent_touching_cm_name) sm = matplotlib.cm.ScalarMappable(cmap = percent_touching_cm) img = sm.to_rgba(percent_touching_image)[:,:,:3] img[:,:,0][~ object_mask] = 0 img[:,:,1][~ object_mask] = 0 img[:,:,2][~ object_mask] = 0 touching_image = cpi.Image(img, masking_objects = objects) image_set.add(self.touching_image_name.value, touching_image) else: percent_touching_cm_name = cpprefs.get_default_colormap() percent_touching_cm = matplotlib.cm.get_cmap(percent_touching_cm_name) if self.show_window: workspace.display_data.neighbor_cm_name = neighbor_cm_name workspace.display_data.percent_touching_cm_name = percent_touching_cm_name workspace.display_data.orig_labels = objects.segmented workspace.display_data.expanded_labels = expanded_labels workspace.display_data.object_mask = object_mask
def run_function(self, function, pixel_data, mask): '''Apply the function once to the image, returning the result''' count = function.repeat_count function_name = function.function.value scale = function.scale.value custom_repeats = function.custom_repeats.value is_binary = pixel_data.dtype.kind == 'b' if function.structuring_element == SE_ARBITRARY: strel = np.array(function.strel.get_matrix()) elif function.structuring_element == SE_DISK: strel = morph.strel_disk(scale / 2.0) elif function.structuring_element == SE_DIAMOND: strel = morph.strel_diamond(scale / 2.0) elif function.structuring_element == SE_LINE: strel = morph.strel_line(scale, function.angle.value) elif function.structuring_element == SE_OCTAGON: strel = morph.strel_octagon(scale / 2.0) elif function.structuring_element == SE_PAIR: strel = morph.strel_pair(function.x_offset.value, function.y_offset.value) elif function.structuring_element == SE_PERIODIC_LINE: xoff = function.x_offset.value yoff = function.y_offset.value n = max(scale / 2.0 / np.sqrt(float(xoff * xoff + yoff * yoff)), 1) strel = morph.strel_periodicline( xoff, yoff, n) elif function.structuring_element == SE_RECTANGLE: strel = morph.strel_rectangle( function.width.value, function.height.value) else: strel = morph.strel_square(scale) if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SKELPE, F_SPUR, F_THICKEN, F_THIN, F_VBREAK) and not is_binary): # Apply a very crude threshold to the image for binary algorithms logger.warning("Warning: converting image to binary for %s\n" % function_name) pixel_data = pixel_data != 0 if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK, F_INVERT, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SKELPE, F_SPUR, F_THICKEN, F_THIN, F_VBREAK, F_OPENLINES) or (is_binary and function_name in (F_CLOSE, F_DILATE, F_ERODE, F_OPEN))): # All of these have an iterations argument or it makes no # sense to iterate if function_name == F_BRANCHPOINTS: return morph.branchpoints(pixel_data, mask) elif function_name == F_BRIDGE: return morph.bridge(pixel_data, mask, count) elif function_name == F_CLEAN: return morph.clean(pixel_data, mask, count) elif function_name == F_CLOSE: if mask is None: return scind.binary_closing(pixel_data, strel, iterations=count) else: return (scind.binary_closing(pixel_data & mask, strel, iterations=count) | (pixel_data & ~ mask)) elif function_name == F_CONVEX_HULL: if mask is None: return morph.convex_hull_image(pixel_data) else: return morph.convex_hull_image(pixel_data & mask) elif function_name == F_DIAG: return morph.diag(pixel_data, mask, count) elif function_name == F_DILATE: return scind.binary_dilation(pixel_data, strel, iterations=count, mask=mask) elif function_name == F_DISTANCE: image = scind.distance_transform_edt(pixel_data) if function.rescale_values.value: image = image / np.max(image) return image elif function_name == F_ENDPOINTS: return morph.endpoints(pixel_data, mask) elif function_name == F_ERODE: return scind.binary_erosion(pixel_data, strel, iterations=count, mask=mask) elif function_name == F_FILL: return morph.fill(pixel_data, mask, count) elif function_name == F_FILL_SMALL: def small_fn(area, foreground): return (not foreground) and (area <= custom_repeats) return morph.fill_labeled_holes(pixel_data, mask, small_fn) elif function_name == F_HBREAK: return morph.hbreak(pixel_data, mask, count) elif function_name == F_INVERT: if is_binary: if mask is None: return ~ pixel_data result = pixel_data.copy() result[mask] = ~result[mask] return result elif mask is None: return 1 - pixel_data else: result = pixel_data.copy() result[mask] = 1 - result[mask] return result elif function_name == F_LIFE: return morph.life(pixel_data, count) elif function_name == F_MAJORITY: return morph.majority(pixel_data, mask, count) elif function_name == F_OPEN: if mask is None: return scind.binary_opening(pixel_data, strel, iterations=count) else: return (scind.binary_opening(pixel_data & mask, strel, iterations=count) | (pixel_data & ~ mask)) elif function_name == F_OPENLINES: return morph.openlines(pixel_data, linelength=custom_repeats, mask=mask) elif function_name == F_REMOVE: return morph.remove(pixel_data, mask, count) elif function_name == F_SHRINK: return morph.binary_shrink(pixel_data, count) elif function_name == F_SKEL: return morph.skeletonize(pixel_data, mask) elif function_name == F_SKELPE: return morph.skeletonize( pixel_data, mask, scind.distance_transform_edt(pixel_data) * poisson_equation(pixel_data)) elif function_name == F_SPUR: return morph.spur(pixel_data, mask, count) elif function_name == F_THICKEN: return morph.thicken(pixel_data, mask, count) elif function_name == F_THIN: return morph.thin(pixel_data, mask, count) elif function_name == F_VBREAK: return morph.vbreak(pixel_data, mask) else: raise NotImplementedError("Unimplemented morphological function: %s" % function_name) else: for i in range(count): if function_name == F_BOTHAT: new_pixel_data = morph.black_tophat(pixel_data, mask=mask, footprint=strel) elif function_name == F_CLOSE: new_pixel_data = morph.closing(pixel_data, mask=mask, footprint=strel) elif function_name == F_DILATE: new_pixel_data = morph.grey_dilation(pixel_data, mask=mask, footprint=strel) elif function_name == F_ERODE: new_pixel_data = morph.grey_erosion(pixel_data, mask=mask, footprint=strel) elif function_name == F_OPEN: new_pixel_data = morph.opening(pixel_data, mask=mask, footprint=strel) elif function_name == F_TOPHAT: new_pixel_data = morph.white_tophat(pixel_data, mask=mask, footprint=strel) else: raise NotImplementedError("Unimplemented morphological function: %s" % function_name) if np.all(new_pixel_data == pixel_data): break pixel_data = new_pixel_data return pixel_data
def run(self, workspace): objects = workspace.object_set.get_objects(self.object_name.value) dimensions = len(objects.shape) assert isinstance(objects, Objects) has_pixels = objects.areas > 0 labels = objects.small_removed_segmented kept_labels = objects.segmented neighbor_objects = workspace.object_set.get_objects( self.neighbors_name.value) neighbor_labels = neighbor_objects.small_removed_segmented neighbor_kept_labels = neighbor_objects.segmented assert isinstance(neighbor_objects, Objects) if not self.wants_excluded_objects.value: # Remove labels not present in kept segmentation while preserving object IDs. mask = neighbor_kept_labels > 0 neighbor_labels[~mask] = 0 nobjects = numpy.max(labels) nkept_objects = len(objects.indices) nneighbors = numpy.max(neighbor_labels) _, object_numbers = objects.relate_labels(labels, kept_labels) if self.neighbors_are_objects: neighbor_numbers = object_numbers neighbor_has_pixels = has_pixels else: _, neighbor_numbers = neighbor_objects.relate_labels( neighbor_labels, neighbor_objects.small_removed_segmented) neighbor_has_pixels = numpy.bincount( neighbor_labels.ravel())[1:] > 0 neighbor_count = numpy.zeros((nobjects, )) pixel_count = numpy.zeros((nobjects, )) first_object_number = numpy.zeros((nobjects, ), int) second_object_number = numpy.zeros((nobjects, ), int) first_x_vector = numpy.zeros((nobjects, )) second_x_vector = numpy.zeros((nobjects, )) first_y_vector = numpy.zeros((nobjects, )) second_y_vector = numpy.zeros((nobjects, )) angle = numpy.zeros((nobjects, )) percent_touching = numpy.zeros((nobjects, )) expanded_labels = None if self.distance_method == D_EXPAND: # Find the i,j coordinates of the nearest foreground point # to every background point if dimensions == 2: i, j = scipy.ndimage.distance_transform_edt( labels == 0, return_distances=False, return_indices=True) # Assign each background pixel to the label of its nearest # foreground pixel. Assign label to label for foreground. labels = labels[i, j] else: k, i, j = scipy.ndimage.distance_transform_edt( labels == 0, return_distances=False, return_indices=True) labels = labels[k, i, j] expanded_labels = labels # for display distance = 1 # dilate once to make touching edges overlap scale = S_EXPANDED if self.neighbors_are_objects: neighbor_labels = labels.copy() elif self.distance_method == D_WITHIN: distance = self.distance.value scale = str(distance) elif self.distance_method == D_ADJACENT: distance = 1 scale = S_ADJACENT else: raise ValueError("Unknown distance method: %s" % self.distance_method.value) if nneighbors > (1 if self.neighbors_are_objects else 0): first_objects = [] second_objects = [] object_indexes = numpy.arange(nobjects, dtype=numpy.int32) + 1 # # First, compute the first and second nearest neighbors, # and the angles between self and the first and second # nearest neighbors # ocenters = centers_of_labels( objects.small_removed_segmented).transpose() ncenters = centers_of_labels( neighbor_objects.small_removed_segmented).transpose() areas = fix( scipy.ndimage.sum(numpy.ones(labels.shape), labels, object_indexes)) perimeter_outlines = outline(labels) perimeters = fix( scipy.ndimage.sum(numpy.ones(labels.shape), perimeter_outlines, object_indexes)) # # order[:,0] should be arange(nobjects) # order[:,1] should be the nearest neighbor # order[:,2] should be the next nearest neighbor # order = numpy.zeros((nobjects, min(nneighbors, 3)), dtype=numpy.uint32) j = numpy.arange(nneighbors) # (0, 1, 2) unless there are less than 3 neighbors partition_keys = tuple(range(min(nneighbors, 3))) for i in range(nobjects): dr = numpy.sqrt((ocenters[i, 0] - ncenters[j, 0])**2 + (ocenters[i, 1] - ncenters[j, 1])**2) order[i, :] = numpy.argpartition(dr, partition_keys)[:3] first_neighbor = 1 if self.neighbors_are_objects else 0 first_object_index = order[:, first_neighbor] first_x_vector = ncenters[first_object_index, 1] - ocenters[:, 1] first_y_vector = ncenters[first_object_index, 0] - ocenters[:, 0] if nneighbors > first_neighbor + 1: second_object_index = order[:, first_neighbor + 1] second_x_vector = ncenters[second_object_index, 1] - ocenters[:, 1] second_y_vector = ncenters[second_object_index, 0] - ocenters[:, 0] v1 = numpy.array((first_x_vector, first_y_vector)) v2 = numpy.array((second_x_vector, second_y_vector)) # # Project the unit vector v1 against the unit vector v2 # dot = numpy.sum(v1 * v2, 0) / numpy.sqrt( numpy.sum(v1**2, 0) * numpy.sum(v2**2, 0)) angle = numpy.arccos(dot) * 180.0 / numpy.pi # Make the structuring element for dilation if dimensions == 2: strel = strel_disk(distance) else: strel = skimage.morphology.ball(distance) # # A little bigger one to enter into the border with a structure # that mimics the one used to create the outline # if dimensions == 2: strel_touching = strel_disk(distance + 0.5) else: strel_touching = skimage.morphology.ball(distance + 0.5) # # Get the extents for each object and calculate the patch # that excises the part of the image that is "distance" # away if dimensions == 2: i, j = numpy.mgrid[0:labels.shape[0], 0:labels.shape[1]] minimums_i, maximums_i, _, _ = scipy.ndimage.extrema( i, labels, object_indexes) minimums_j, maximums_j, _, _ = scipy.ndimage.extrema( j, labels, object_indexes) minimums_i = numpy.maximum(fix(minimums_i) - distance, 0).astype(int) maximums_i = numpy.minimum( fix(maximums_i) + distance + 1, labels.shape[0]).astype(int) minimums_j = numpy.maximum(fix(minimums_j) - distance, 0).astype(int) maximums_j = numpy.minimum( fix(maximums_j) + distance + 1, labels.shape[1]).astype(int) else: k, i, j = numpy.mgrid[0:labels.shape[0], 0:labels.shape[1], 0:labels.shape[2]] minimums_k, maximums_k, _, _ = scipy.ndimage.extrema( k, labels, object_indexes) minimums_i, maximums_i, _, _ = scipy.ndimage.extrema( i, labels, object_indexes) minimums_j, maximums_j, _, _ = scipy.ndimage.extrema( j, labels, object_indexes) minimums_k = numpy.maximum(fix(minimums_k) - distance, 0).astype(int) maximums_k = numpy.minimum( fix(maximums_k) + distance + 1, labels.shape[0]).astype(int) minimums_i = numpy.maximum(fix(minimums_i) - distance, 0).astype(int) maximums_i = numpy.minimum( fix(maximums_i) + distance + 1, labels.shape[1]).astype(int) minimums_j = numpy.maximum(fix(minimums_j) - distance, 0).astype(int) maximums_j = numpy.minimum( fix(maximums_j) + distance + 1, labels.shape[2]).astype(int) # # Loop over all objects # Calculate which ones overlap "index" # Calculate how much overlap there is of others to "index" # for object_number in object_numbers: if object_number == 0: # # No corresponding object in small-removed. This means # that the object has no pixels, e.g., not renumbered. # continue index = object_number - 1 if dimensions == 2: patch = labels[minimums_i[index]:maximums_i[index], minimums_j[index]:maximums_j[index], ] npatch = neighbor_labels[ minimums_i[index]:maximums_i[index], minimums_j[index]:maximums_j[index], ] else: patch = labels[minimums_k[index]:maximums_k[index], minimums_i[index]:maximums_i[index], minimums_j[index]:maximums_j[index], ] npatch = neighbor_labels[ minimums_k[index]:maximums_k[index], minimums_i[index]:maximums_i[index], minimums_j[index]:maximums_j[index], ] # # Find the neighbors # patch_mask = patch == (index + 1) if distance <= 5: extended = scipy.ndimage.binary_dilation(patch_mask, strel) else: extended = (scipy.signal.fftconvolve( patch_mask, strel, mode="same") > 0.5) neighbors = numpy.unique(npatch[extended]) neighbors = neighbors[neighbors != 0] if self.neighbors_are_objects: neighbors = neighbors[neighbors != object_number] nc = len(neighbors) neighbor_count[index] = nc if nc > 0: first_objects.append(numpy.ones(nc, int) * object_number) second_objects.append(neighbors) # # Find the # of overlapping pixels. Dilate the neighbors # and see how many pixels overlap our image. Use a 3x3 # structuring element to expand the overlapping edge # into the perimeter. # if dimensions == 2: outline_patch = (perimeter_outlines[ minimums_i[index]:maximums_i[index], minimums_j[index]:maximums_j[index], ] == object_number ) else: outline_patch = (perimeter_outlines[ minimums_k[index]:maximums_k[index], minimums_i[index]:maximums_i[index], minimums_j[index]:maximums_j[index], ] == object_number ) if self.neighbors_are_objects: extendme = (patch != 0) & (patch != object_number) if distance <= 5: extended = scipy.ndimage.binary_dilation( extendme, strel_touching) else: extended = (scipy.signal.fftconvolve( extendme, strel_touching, mode="same") > 0.5) else: if distance <= 5: extended = scipy.ndimage.binary_dilation( (npatch != 0), strel_touching) else: extended = (scipy.signal.fftconvolve( (npatch != 0), strel_touching, mode="same") > 0.5) overlap = numpy.sum(outline_patch & extended) pixel_count[index] = overlap if sum([len(x) for x in first_objects]) > 0: first_objects = numpy.hstack(first_objects) reverse_object_numbers = numpy.zeros( max(numpy.max(object_numbers), numpy.max(first_objects)) + 1, int) reverse_object_numbers[object_numbers] = ( numpy.arange(len(object_numbers)) + 1) first_objects = reverse_object_numbers[first_objects] second_objects = numpy.hstack(second_objects) reverse_neighbor_numbers = numpy.zeros( max(numpy.max(neighbor_numbers), numpy.max(second_objects)) + 1, int) reverse_neighbor_numbers[neighbor_numbers] = ( numpy.arange(len(neighbor_numbers)) + 1) second_objects = reverse_neighbor_numbers[second_objects] to_keep = (first_objects > 0) & (second_objects > 0) first_objects = first_objects[to_keep] second_objects = second_objects[to_keep] else: first_objects = numpy.zeros(0, int) second_objects = numpy.zeros(0, int) percent_touching = pixel_count * 100 / perimeters object_indexes = object_numbers - 1 neighbor_indexes = neighbor_numbers - 1 # # Have to recompute nearest # first_object_number = numpy.zeros(nkept_objects, int) second_object_number = numpy.zeros(nkept_objects, int) if nkept_objects > (1 if self.neighbors_are_objects else 0): di = (ocenters[object_indexes[:, numpy.newaxis], 0] - ncenters[neighbor_indexes[numpy.newaxis, :], 0]) dj = (ocenters[object_indexes[:, numpy.newaxis], 1] - ncenters[neighbor_indexes[numpy.newaxis, :], 1]) distance_matrix = numpy.sqrt(di * di + dj * dj) distance_matrix[~has_pixels, :] = numpy.inf distance_matrix[:, ~neighbor_has_pixels] = numpy.inf # # order[:,0] should be arange(nobjects) # order[:,1] should be the nearest neighbor # order[:,2] should be the next nearest neighbor # order = numpy.lexsort([distance_matrix ]).astype(first_object_number.dtype) if self.neighbors_are_objects: first_object_number[has_pixels] = order[has_pixels, 1] + 1 if nkept_objects > 2: second_object_number[has_pixels] = order[has_pixels, 2] + 1 else: first_object_number[has_pixels] = order[has_pixels, 0] + 1 if order.shape[1] > 1: second_object_number[has_pixels] = order[has_pixels, 1] + 1 else: object_indexes = object_numbers - 1 neighbor_indexes = neighbor_numbers - 1 first_objects = numpy.zeros(0, int) second_objects = numpy.zeros(0, int) # # Now convert all measurements from the small-removed to # the final number set. # neighbor_count = neighbor_count[object_indexes] neighbor_count[~has_pixels] = 0 percent_touching = percent_touching[object_indexes] percent_touching[~has_pixels] = 0 first_x_vector = first_x_vector[object_indexes] second_x_vector = second_x_vector[object_indexes] first_y_vector = first_y_vector[object_indexes] second_y_vector = second_y_vector[object_indexes] angle = angle[object_indexes] # # Record the measurements # assert isinstance(workspace, Workspace) m = workspace.measurements assert isinstance(m, Measurements) image_set = workspace.image_set features_and_data = [ (M_NUMBER_OF_NEIGHBORS, neighbor_count), (M_FIRST_CLOSEST_OBJECT_NUMBER, first_object_number), ( M_FIRST_CLOSEST_DISTANCE, numpy.sqrt(first_x_vector**2 + first_y_vector**2), ), (M_SECOND_CLOSEST_OBJECT_NUMBER, second_object_number), ( M_SECOND_CLOSEST_DISTANCE, numpy.sqrt(second_x_vector**2 + second_y_vector**2), ), (M_ANGLE_BETWEEN_NEIGHBORS, angle), (M_PERCENT_TOUCHING, percent_touching), ] for feature_name, data in features_and_data: m.add_measurement(self.object_name.value, self.get_measurement_name(feature_name), data) if len(first_objects) > 0: m.add_relate_measurement( self.module_num, NEIGHBORS, self.object_name.value, self.object_name.value if self.neighbors_are_objects else self.neighbors_name.value, m.image_set_number * numpy.ones(first_objects.shape, int), first_objects, m.image_set_number * numpy.ones(second_objects.shape, int), second_objects, ) labels = kept_labels neighbor_count_image = numpy.zeros(labels.shape, int) object_mask = objects.segmented != 0 object_indexes = objects.segmented[object_mask] - 1 neighbor_count_image[object_mask] = neighbor_count[object_indexes] workspace.display_data.neighbor_count_image = neighbor_count_image percent_touching_image = numpy.zeros(labels.shape) percent_touching_image[object_mask] = percent_touching[object_indexes] workspace.display_data.percent_touching_image = percent_touching_image image_set = workspace.image_set if self.wants_count_image.value: neighbor_cm_name = self.count_colormap.value neighbor_cm = get_colormap(neighbor_cm_name) sm = matplotlib.cm.ScalarMappable(cmap=neighbor_cm) img = sm.to_rgba(neighbor_count_image)[:, :, :3] img[:, :, 0][~object_mask] = 0 img[:, :, 1][~object_mask] = 0 img[:, :, 2][~object_mask] = 0 count_image = Image(img, masking_objects=objects) image_set.add(self.count_image_name.value, count_image) else: neighbor_cm_name = "Blues" neighbor_cm = matplotlib.cm.get_cmap(neighbor_cm_name) if self.wants_percent_touching_image: percent_touching_cm_name = self.touching_colormap.value percent_touching_cm = get_colormap(percent_touching_cm_name) sm = matplotlib.cm.ScalarMappable(cmap=percent_touching_cm) img = sm.to_rgba(percent_touching_image)[:, :, :3] img[:, :, 0][~object_mask] = 0 img[:, :, 1][~object_mask] = 0 img[:, :, 2][~object_mask] = 0 touching_image = Image(img, masking_objects=objects) image_set.add(self.touching_image_name.value, touching_image) else: percent_touching_cm_name = "Oranges" percent_touching_cm = matplotlib.cm.get_cmap( percent_touching_cm_name) if self.show_window: workspace.display_data.neighbor_cm_name = neighbor_cm_name workspace.display_data.percent_touching_cm_name = percent_touching_cm_name workspace.display_data.orig_labels = objects.segmented workspace.display_data.neighbor_labels = neighbor_labels workspace.display_data.expanded_labels = expanded_labels workspace.display_data.object_mask = object_mask workspace.display_data.dimensions = dimensions