def filter_using_image(self, workspace, mask): '''Filter out connections using local intensity minima between objects workspace - the workspace for the image set mask - mask of background points within the minimum distance ''' # # NOTE: This is an efficient implementation and an improvement # in accuracy over the Matlab version. It would be faster and # more accurate to eliminate the line-connecting and instead # do the following: # * Distance transform to get the coordinates of the closest # point in an object for points in the background that are # at most 1/2 of the max distance between objects. # * Take the intensity at this closest point and similarly # label the background point if the background intensity # is at least the minimum intensity fraction # * Assume there is a connection between objects if, after this # labeling, there are adjacent points in each object. # # As it is, the algorithm duplicates the Matlab version but suffers # for cells whose intensity isn't high in the centroid and clearly # suffers when two cells touch at some point that's off of the line # between the two. # objects = workspace.object_set.get_objects(self.objects_name.value) labels = objects.segmented image = self.get_image(workspace) if self.show_window: # Save the image for display workspace.display_data.image = image # # Do a distance transform into the background to label points # in the background with their closest foreground object # i, j = scind.distance_transform_edt(labels == 0, return_indices=True, return_distances=False) confluent_labels = labels[i, j] confluent_labels[~mask] = 0 if self.where_algorithm == CA_CLOSEST_POINT: # # For the closest point method, find the intensity at # the closest point in the object (which will be the point itself # for points in the object). # object_intensity = image[i, j] * self.minimum_intensity_fraction.value confluent_labels[object_intensity > image] = 0 count, index, c_j = morph.find_neighbors(confluent_labels) if len(c_j) == 0: # Nobody touches - return the labels matrix return labels # # Make a row of i matching the touching j # c_i = np.zeros(len(c_j)) # # Eliminate labels without matches # label_numbers = np.arange(1, len(count) + 1)[count > 0] index = index[count > 0] count = count[count > 0] # # Get the differences between labels so we can use a cumsum trick # to increment to the next label when they change # label_numbers[1:] = label_numbers[1:] - label_numbers[:-1] c_i[index] = label_numbers c_i = np.cumsum(c_i).astype(int) if self.where_algorithm == CA_CENTROIDS: # # Only connect points > minimum intensity fraction # center_i, center_j = morph.centers_of_labels(labels) indexes, counts, i, j = morph.get_line_pts(center_i[c_i - 1], center_j[c_i - 1], center_i[c_j - 1], center_j[c_j - 1]) # # The indexes of the centroids at pt1 # last_indexes = indexes + counts - 1 # # The minimum of the intensities at pt0 and pt1 # centroid_intensities = np.minimum( image[i[indexes], j[indexes]], image[i[last_indexes], j[last_indexes]]) # # Assign label numbers to each point so we can use # scipy.ndimage.minimum. The label numbers are indexes into # "connections" above. # pt_labels = np.zeros(len(i), int) pt_labels[indexes[1:]] = 1 pt_labels = np.cumsum(pt_labels) minima = scind.minimum(image[i, j], pt_labels, np.arange(len(indexes))) minima = morph.fixup_scipy_ndimage_result(minima) # # Filter the connections using the image # mif = self.minimum_intensity_fraction.value i = c_i[centroid_intensities * mif <= minima] j = c_j[centroid_intensities * mif <= minima] else: i = c_i j = c_j # # Add in connections from self to self # unique_labels = np.unique(labels) i = np.hstack((i, unique_labels)) j = np.hstack((j, unique_labels)) # # Run "all_connected_components" to get a component # for # objects identified as same. # new_indexes = morph.all_connected_components(i, j) new_labels = np.zeros(labels.shape, int) new_labels[labels != 0] = new_indexes[labels[labels != 0]] return new_labels
def filter_using_image(self, workspace, mask): '''Filter out connections using local intensity minima between objects workspace - the workspace for the image set mask - mask of background points within the minimum distance ''' # # NOTE: This is an efficient implementation and an improvement # in accuracy over the Matlab version. It would be faster and # more accurate to eliminate the line-connecting and instead # do the following: # * Distance transform to get the coordinates of the closest # point in an object for points in the background that are # at most 1/2 of the max distance between objects. # * Take the intensity at this closest point and similarly # label the background point if the background intensity # is at least the minimum intensity fraction # * Assume there is a connection between objects if, after this # labeling, there are adjacent points in each object. # # As it is, the algorithm duplicates the Matlab version but suffers # for cells whose intensity isn't high in the centroid and clearly # suffers when two cells touch at some point that's off of the line # between the two. # objects = workspace.object_set.get_objects(self.objects_name.value) labels = objects.segmented image = self.get_image(workspace) if self.show_window: # Save the image for display workspace.display_data.image = image # # Do a distance transform into the background to label points # in the background with their closest foreground object # i, j = scind.distance_transform_edt(labels==0, return_indices=True, return_distances=False) confluent_labels = labels[i,j] confluent_labels[~mask] = 0 if self.where_algorithm == CA_CLOSEST_POINT: # # For the closest point method, find the intensity at # the closest point in the object (which will be the point itself # for points in the object). # object_intensity = image[i,j] * self.minimum_intensity_fraction.value confluent_labels[object_intensity > image] = 0 count, index, c_j = morph.find_neighbors(confluent_labels) if len(c_j) == 0: # Nobody touches - return the labels matrix return labels # # Make a row of i matching the touching j # c_i = np.zeros(len(c_j)) # # Eliminate labels without matches # label_numbers = np.arange(1,len(count)+1)[count > 0] index = index[count > 0] count = count[count > 0] # # Get the differences between labels so we can use a cumsum trick # to increment to the next label when they change # label_numbers[1:] = label_numbers[1:] - label_numbers[:-1] c_i[index] = label_numbers c_i = np.cumsum(c_i).astype(int) if self.where_algorithm == CA_CENTROIDS: # # Only connect points > minimum intensity fraction # center_i, center_j = morph.centers_of_labels(labels) indexes, counts, i, j = morph.get_line_pts( center_i[c_i-1], center_j[c_i-1], center_i[c_j-1], center_j[c_j-1]) # # The indexes of the centroids at pt1 # last_indexes = indexes+counts-1 # # The minimum of the intensities at pt0 and pt1 # centroid_intensities = np.minimum( image[i[indexes],j[indexes]], image[i[last_indexes], j[last_indexes]]) # # Assign label numbers to each point so we can use # scipy.ndimage.minimum. The label numbers are indexes into # "connections" above. # pt_labels = np.zeros(len(i), int) pt_labels[indexes[1:]] = 1 pt_labels = np.cumsum(pt_labels) minima = scind.minimum(image[i,j], pt_labels, np.arange(len(indexes))) minima = morph.fixup_scipy_ndimage_result(minima) # # Filter the connections using the image # mif = self.minimum_intensity_fraction.value i = c_i[centroid_intensities * mif <= minima] j = c_j[centroid_intensities * mif <= minima] else: i = c_i j = c_j # # Add in connections from self to self # unique_labels = np.unique(labels) i = np.hstack((i, unique_labels)) j = np.hstack((j, unique_labels)) # # Run "all_connected_components" to get a component # for # objects identified as same. # new_indexes = morph.all_connected_components(i, j) new_labels = np.zeros(labels.shape, int) new_labels[labels != 0] = new_indexes[labels[labels != 0]] return new_labels
def run(self, workspace): objects = workspace.object_set.get_objects(self.objects_name.value) assert isinstance(objects, cpo.Objects) labels = objects.segmented half_window_size = self.window_size.value threshold = self.threshold.value output_labels = objects.segmented.copy() max_objects = np.max(output_labels) indices = np.arange(max_objects+1) # Get object slices object_slices = morph.fixup_scipy_ndimage_result(scind.measurements.find_objects(output_labels)) # Calculate perimeters perimeters = morph.calculate_perimeters(output_labels, indices) # Find the neighbors neighbors = np.zeros((max_objects+1, max_objects+1), bool) # neighbors[i,j] = True when object j is a neighbor of object i. lmax = scind.grey_dilation(output_labels, footprint=np.ones((3,3), bool)) # lower pixel values will be replaced by adjacent larger pixel values lbig = output_labels.copy() lbig[lbig == 0] = np.iinfo(output_labels.dtype).max # set the background to be large so that it is ignored next lmin = scind.grey_erosion(lbig, footprint=np.ones((3,3), bool)) # larger pixel values will be replaced by adjacent smaller pixel values for i in range(1, max_objects+1): object_bounds = (object_slices[i-1][0],object_slices[i-1][1]) object_map = output_labels[object_bounds] == i # The part of the slice that contains the object lower_neighbors = np.unique(lmin[object_bounds][object_map]) higher_neighbors = np.unique(lmax[object_bounds][object_map]) for neighbor_list in [lower_neighbors, higher_neighbors]: for j in range(0, len(neighbor_list)): neighbors[i,neighbor_list[j]] = True neighbors[i,i] = False # Generate window dimensions for each location (necessary for the edges) dim1_window = np.ones(output_labels.shape, int) * half_window_size dim2_window = np.ones(output_labels.shape, int) * half_window_size for i in range(0, half_window_size): dim1_window[i:output_labels.shape[0]-i,0:output_labels.shape[1]] += 1 dim2_window[0:output_labels.shape[0],i:output_labels.shape[1]-i] += 1 # Loop over all objects for k in range(1, max_objects+1): if (perimeters[k] == 0) or not np.max(neighbors[k]): # Has been removed by a merge or has no neighbors continue k_bounds_array = object_slices[k-1] k_dim1_bounds = k_bounds_array[0].indices(output_labels.shape[0]) k_dim2_bounds = k_bounds_array[1].indices(output_labels.shape[1]) # Loop over all neighbors of object k l = 0 while l < max_objects: l += 1 if k == l or (perimeters[l] == 0) or not neighbors[k,l]: # Has been removed by a merge or is not a neighbor of object k continue l_bounds_array = object_slices[l-1] l_dim1_bounds = l_bounds_array[0].indices(output_labels.shape[0]) l_dim2_bounds = l_bounds_array[1].indices(output_labels.shape[1]) kl_dim1_min_bound = min(k_dim1_bounds[0], l_dim1_bounds[0]) kl_dim1_max_bound = max(k_dim1_bounds[1], l_dim1_bounds[1]) kl_dim2_min_bound = min(k_dim2_bounds[0], l_dim2_bounds[0]) kl_dim2_max_bound = max(k_dim2_bounds[1], l_dim2_bounds[1]) kl_bounds = (slice(kl_dim1_min_bound, kl_dim1_max_bound), slice(kl_dim2_min_bound, kl_dim2_max_bound)) kl_shape = (kl_dim1_max_bound - kl_dim1_min_bound, kl_dim2_max_bound - kl_dim2_min_bound) # # Find the vertices of object pair k, l # vertices = np.zeros(kl_shape, bool) isAdjacent = np.zeros(output_labels.shape, bool) isBorder = np.zeros(output_labels.shape, bool) for i in range(-1,2): ki_min = 0 ki_max = 0 li_min = 0 li_max = 0 if k_dim1_bounds[0] + i < 0: ki_min = -i else: ki_min = k_dim1_bounds[0] if l_dim1_bounds[0] + i < 0: li_min = -i else: li_min = l_dim1_bounds[0] if k_dim1_bounds[1] + i > output_labels.shape[0]: ki_max = output_labels.shape[0] - i else: ki_max = k_dim1_bounds[1] if l_dim1_bounds[1] + i > output_labels.shape[0]: li_max = output_labels.shape[0] - i else: li_max = l_dim1_bounds[1] ki_slice = slice(ki_min, ki_max) li_slice = slice(li_min, li_max) ki_test_slice = slice(ki_min+i, ki_max+i) li_test_slice = slice(li_min+i, li_max+i) for j in range(-1,2): if i == j == 0: continue kj_min = 0 kj_max = 0 lj_min = 0 lj_max = 0 if k_dim2_bounds[0] + j < 0: kj_min = -j else: kj_min = k_dim2_bounds[0] if l_dim2_bounds[0] + j < 0: lj_min = -j else: lj_min = l_dim2_bounds[0] if k_dim2_bounds[1] + j > output_labels.shape[0]: kj_max = output_labels.shape[0] - j else: kj_max = k_dim2_bounds[1] if l_dim2_bounds[1] + j > output_labels.shape[0]: lj_max = output_labels.shape[0] - j else: lj_max = l_dim2_bounds[1] if (kj_min == kj_max or ki_min == ki_max) and (lj_min == lj_max or li_min == li_max): continue kj_slice = slice(kj_min, kj_max) lj_slice = slice(lj_min, lj_max) kj_test_slice = slice(kj_min+j, kj_max+j) lj_test_slice = slice(lj_min+j, lj_max+j) k_bounds = (ki_slice, kj_slice) l_bounds = (li_slice, lj_slice) k_test_bounds = (ki_test_slice, kj_test_slice) l_test_bounds = (li_test_slice, lj_test_slice) kl_mod_bounds = (slice(min(ki_min, li_min), max(ki_max, li_max)), slice(min(kj_min, lj_min), max(kj_max, lj_max))) isAdjacentSlice = np.zeros(output_labels.shape, bool) isAdjacentSlice[k_bounds] = np.logical_and(output_labels[k_bounds] == k, output_labels[k_test_bounds] == l) isAdjacentSlice[l_bounds] = np.logical_or(isAdjacentSlice[l_bounds], np.logical_and(output_labels[l_bounds] == l, output_labels[l_test_bounds] == k)) isAdjacent[kl_mod_bounds] = np.logical_or(isAdjacent[kl_mod_bounds], isAdjacentSlice[kl_mod_bounds]) isBorderSlice = np.zeros(output_labels.shape, bool) isBorderSlice[k_bounds] = np.logical_and(output_labels[k_bounds] == k, np.logical_and(output_labels[k_test_bounds] != k, output_labels[k_test_bounds] != l)) isBorderSlice[l_bounds] = np.logical_or(isBorderSlice[l_bounds], np.logical_and(output_labels[l_bounds] == l, np.logical_and(output_labels[l_test_bounds] != k, output_labels[l_test_bounds] != l))) isBorder[kl_mod_bounds] = np.logical_or(isBorder[kl_mod_bounds], isBorderSlice[kl_mod_bounds]) vertices = np.logical_and(isAdjacent[kl_bounds], isBorder[kl_bounds]) # # Calculate the maximum vertex score for the pair # '''+1 for every non-I/J labeled pixel in the window +1 more for every non-I/J labeled pixel adjacent to it Divided by the score that would result if the objects were flat e.g. (vertex is starred) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 1 1 *1* 2 2 0 1 1 1 1 2 2 2 1 1 1 1 2 2 2 1 1 1 2 2 2 2 If it were flat, it would be 7 * 3 = 21, so the divisor 7 * 6 = 42. In the case that the window is rectangular (such as at the image edge), we split the difference. Generalized: Vertex Score = (Sum of NotIJ) / [A * B - 0.5 * (A + B)] ''' sum_not_IJ = np.zeros(kl_shape, int) for i in range(0, kl_shape[0]): for j in range(0,kl_shape[1]): if not vertices[i,j]: continue window_bounds = (slice(i - half_window_size + kl_dim1_min_bound, i + half_window_size + kl_dim1_min_bound), slice(j - half_window_size + kl_dim2_min_bound, j + half_window_size + kl_dim2_min_bound)) window_slice = output_labels[window_bounds] sum_not_IJ[i,j] = count_nonzero(np.logical_and(window_slice != k, window_slice != l)) # Should be np.count_nonzero, but it doesn't exist in the version that comes with # CellProfiler 2.0 r11710 vertex_scores = sum_not_IJ / (dim1_window[kl_bounds] * dim2_window[kl_bounds] - 0.5 * (dim1_window[kl_bounds] + dim2_window[kl_bounds])) merged = np.zeros(kl_shape, int) merged[output_labels[kl_bounds] == k] = 1 merged[output_labels[kl_bounds] == l] = 1 merged_perimeter = morph.calculate_perimeters(merged, [1]) seam_length = (perimeters[k] + perimeters[l] - merged_perimeter) / 2 min_external_perimeter = min(perimeters[k], perimeters[l]) - seam_length adjusted_min_external_perimeter = min_external_perimeter / np.pi seam_fraction = seam_length / (adjusted_min_external_perimeter + seam_length) max_vertex_score = np.max(vertex_scores) score = (max_vertex_score + seam_fraction) / 2 # # Join object pair with score above the threshold # if score >= threshold: output_labels[output_labels == l] = k # Calculate new perimeters perimeters[k] = merged_perimeter perimeters[l] = 0 # Reconfigure neighbors neighbors[k] = np.logical_or(neighbors[k], neighbors[l]) for x in range(1, max_objects+1): # Is there an array method to do this? if neighbors[x,l]: neighbors[x,k] = True neighbors[0:max_objects,l] = False # Recalculate bounds k_dim1_bounds = kl_bounds[0].indices(output_labels.shape[0]) k_dim2_bounds = kl_bounds[1].indices(output_labels.shape[1]) # Reset l to 0 so that we check all the neighbors against this new object l = 0 else: pass output_objects = cpo.Objects() output_objects.segmented = output_labels if objects.has_small_removed_segmented: output_objects.small_removed_segmented = \ copy_labels(objects.small_removed_segmented, output_labels) if objects.has_unedited_segmented: output_objects.unedited_segmented = \ copy_labels(objects.unedited_segmented, output_labels) output_objects.parent_image = objects.parent_image workspace.object_set.add_objects(output_objects, self.output_objects_name.value) measurements = workspace.measurements add_object_count_measurements(measurements, self.output_objects_name.value, np.max(output_objects.segmented)) add_object_location_measurements(measurements, self.output_objects_name.value, output_objects.segmented) # # Relate the output objects to the input ones and record # the relationship. # children_per_parent, parents_of_children = \ objects.relate_children(output_objects) measurements.add_measurement(self.objects_name.value, FF_CHILDREN_COUNT % self.output_objects_name.value, children_per_parent) measurements.add_measurement(self.output_objects_name.value, FF_PARENT%self.objects_name.value, parents_of_children) if self.wants_outlines: outlines = cellprofiler.cpmath.outline.outline(output_labels) outline_image = cpi.Image(outlines.astype(bool)) workspace.image_set.add(self.outlines_name.value, outline_image) if workspace.frame is not None: workspace.display_data.orig_labels = objects.segmented workspace.display_data.output_labels = output_objects.segmented