def run_one_gabor(self, image_name, object_name, scale, workspace): objects = workspace.get_objects(object_name) labels = objects.segmented object_count = np.max(labels) if object_count > 0: image = workspace.image_set.get_image(image_name, must_be_grayscale=True) pixel_data = image.pixel_data labels = objects.segmented if image.has_mask: mask = image.mask else: mask = None try: pixel_data = objects.crop_image_similarly(pixel_data) if mask is not None: mask = objects.crop_image_similarly(mask) labels[~mask] = 0 except ValueError: pixel_data, m1 = cpo.size_similarly(labels, pixel_data) labels[~m1] = 0 if mask is not None: mask, m2 = cpo.size_similarly(labels, mask) labels[~m2] = 0 labels[~mask] = 0 pixel_data = normalized_per_object(pixel_data, labels) best_score = np.zeros((object_count,)) for angle in range(self.gabor_angles.value): theta = np.pi * angle / self.gabor_angles.value g = gabor(pixel_data, labels, scale, theta) score_r = fix(scind.sum(g.real, labels, np.arange(object_count, dtype=np.int32)+ 1)) score_i = fix(scind.sum(g.imag, labels, np.arange(object_count, dtype=np.int32)+ 1)) score = np.sqrt(score_r**2+score_i**2) best_score = np.maximum(best_score, score) else: best_score = np.zeros((0,)) statistics = self.record_measurement(workspace, image_name, object_name, scale, F_GABOR, best_score) return statistics
def record_measurement(self, workspace, object_name, feature_name, result): """Record the result of a measurement in the workspace's measurements""" data = fix(result) workspace.add_measurement(object_name, "%s_%s" % (AREA_SHAPE, feature_name), data) if workspace.frame is not None and np.any(np.isfinite(data)) > 0: data = data[np.isfinite(data)] workspace.display_data.statistics.append( (object_name, feature_name, "%.2f" % np.mean(data), "%.2f" % np.median(data), "%.2f" % np.std(data)) )
def __init__(self, name): self.name = name self.labels = workspace.object_set.get_objects(name).segmented self.nobjects = np.max(self.labels) if self.nobjects != 0: self.range = np.arange(1, np.max(self.labels) + 1) self.labels = self.labels.copy() self.labels[~im.mask] = 0 self.current_mean = fix(scind.mean(im.pixel_data, self.labels, self.range)) self.start_mean = np.maximum(self.current_mean, np.finfo(float).eps)
def run(self, workspace): parents = workspace.object_set.get_objects(self.parent_name.value) children = workspace.object_set.get_objects(self.sub_object_name.value) child_count, parents_of = parents.relate_children(children) m = workspace.measurements if self.wants_per_parent_means.value: parent_indexes = np.arange(np.max(parents.segmented))+1 for feature_name in m.get_feature_names(self.sub_object_name.value): if not self.should_aggregate_feature(feature_name): continue data = m.get_current_measurement(self.sub_object_name.value, feature_name) if data is not None: if len(parents_of) > 0: means = fix(scind.mean(data.astype(float), parents_of, parent_indexes)) else: means = np.zeros((0,)) mean_feature_name = FF_MEAN%(self.sub_object_name.value, feature_name) m.add_measurement(self.parent_name.value, mean_feature_name, means) m.add_measurement(self.sub_object_name.value, FF_PARENT%(self.parent_name.value), parents_of) m.add_measurement(self.parent_name.value, FF_CHILDREN_COUNT%(self.sub_object_name.value), child_count) parent_names = self.get_parent_names() for parent_name in parent_names: if self.find_parent_child_distances in (D_BOTH, D_CENTROID): self.calculate_centroid_distances(workspace, parent_name) if self.find_parent_child_distances in (D_BOTH, D_MINIMUM): self.calculate_minimum_distances(workspace, parent_name) if workspace.frame is not None: figure = workspace.create_or_find_figure(title="RelateObjects, image cycle #%d"%( workspace.measurements.image_set_number),subplots=(2,2)) figure.subplot_imshow_labels(0,0,parents.segmented, title = self.parent_name.value) figure.subplot_imshow_labels(1,0,children.segmented, title = self.sub_object_name.value, sharex = figure.subplot(0,0), sharey = figure.subplot(0,0)) parent_labeled_children = np.zeros(children.segmented.shape, int) parent_labeled_children[children.segmented > 0] = \ parents_of[children.segmented[children.segmented > 0]-1] figure.subplot_imshow_labels(0,1,parent_labeled_children, "%s labeled by %s"% (self.sub_object_name.value, self.parent_name.value), sharex = figure.subplot(0,0), sharey = figure.subplot(0,0))
def record_measurement(self, workspace, image_name, object_name, feature_name, result): """Record the result of a measurement in the workspace's measurements""" data = fix(result) data[~np.isfinite(data)] = 0 workspace.add_measurement(object_name, "%s_%s_%s" % (HISTOGRAM, feature_name, image_name), data) statistics = [[image_name, object_name, feature_name, "%f"%(d) if len(data) else "-"] for d in data] return statistics
def record_measurement(self, workspace, image_name, object_name, scale, feature_name, result): """Record the result of a measurement in the workspace's measurements""" data = fix(result) data[~np.isfinite(data)] = 0 workspace.add_measurement( object_name, "%s_%s_%s_%s" % (TEXTURE, feature_name,image_name, str(scale)), data) statistics = [[image_name, object_name, "%s %s"%(aggregate_name, feature_name), scale, "%.2f"%fn(data) if len(data) else "-"] for aggregate_name, fn in (("min",np.min), ("max",np.max), ("mean",np.mean), ("median",np.median), ("std dev",np.std))] return statistics
def run(self, workspace): '''Run the module on an image set''' object_name = self.object_name.value remaining_object_name = self.remaining_objects.value original_objects = workspace.object_set.get_objects(object_name) if self.mask_choice == MC_IMAGE: mask = workspace.image_set.get_image(self.masking_image.value, must_be_binary = True) mask = mask.pixel_data else: masking_objects = workspace.object_set.get_objects( self.masking_objects.value) mask = masking_objects.segmented > 0 if self.wants_inverted_mask: mask = ~mask # # Load the labels # labels = original_objects.segmented.copy() nobjects = np.max(labels) # # Resize the mask to cover the objects # mask, m1 = cpo.size_similarly(labels, mask) mask[~m1] = False # # Apply the mask according to the overlap choice. # if nobjects == 0: pass elif self.overlap_choice == P_MASK: labels = labels * mask else: pixel_counts = fix(scind.sum(mask, labels, np.arange(1, nobjects+1,dtype=np.int32))) if self.overlap_choice == P_KEEP: keep = pixel_counts > 0 else: total_pixels = fix(scind.sum(np.ones(labels.shape), labels, np.arange(1, nobjects+1,dtype=np.int32))) if self.overlap_choice == P_REMOVE: keep = pixel_counts == total_pixels elif self.overlap_choice == P_REMOVE_PERCENTAGE: fraction = self.overlap_fraction.value keep = pixel_counts / total_pixels >= fraction else: raise NotImplementedError("Unknown overlap-handling choice: %s", self.overlap_choice.value) keep = np.hstack(([False], keep)) labels[~ keep[labels]] = 0 # # Renumber the labels matrix if requested # if self.retain_or_renumber == R_RENUMBER: unique_labels = np.unique(labels[labels!=0]) indexer = np.zeros(nobjects+1, int) indexer[unique_labels] = np.arange(1, len(unique_labels)+1) labels = indexer[labels] parent_objects = unique_labels else: parent_objects = np.arange(1, nobjects+1) # # Add the objects # remaining_objects = cpo.Objects() remaining_objects.segmented = labels remaining_objects.unedited_segmented = original_objects.unedited_segmented workspace.object_set.add_objects(remaining_objects, remaining_object_name) # # Add measurements # m = workspace.measurements m.add_measurement(remaining_object_name, I.FF_PARENT % object_name, parent_objects) if np.max(original_objects.segmented) == 0: child_count = np.array([],int) else: child_count = fix(scind.sum(labels, original_objects.segmented, np.arange(1, nobjects+1,dtype=np.int32))) child_count = (child_count > 0).astype(int) m.add_measurement(object_name, I.FF_CHILDREN_COUNT % remaining_object_name, child_count) if self.retain_or_renumber == R_RETAIN: remaining_object_count = nobjects else: remaining_object_count = len(unique_labels) I.add_object_count_measurements(m, remaining_object_name, remaining_object_count) I.add_object_location_measurements(m, remaining_object_name, labels) # # Add an outline if asked to do so # if self.wants_outlines.value: outline_image = cpi.Image(outline(labels) > 0, parent_image = original_objects.parent_image) workspace.image_set.add(self.outlines_name.value, outline_image) # # Save the input, mask and output images for display # if self.show_window: workspace.display_data.original_labels = original_objects.segmented workspace.display_data.final_labels = labels workspace.display_data.mask = mask
def calculate_minimum_distances(self, workspace, parent_name): '''Calculate the distance from child center to parent perimeter''' meas = workspace.measurements assert isinstance(meas,cpmeas.Measurements) sub_object_name = self.sub_object_name.value parents = workspace.object_set.get_objects(parent_name) children = workspace.object_set.get_objects(sub_object_name) parents_of = self.get_parents_of(workspace, parent_name) if len(parents_of) == 0: dist = np.zeros((0,)) elif np.all(parents_of == 0): dist = np.array([np.NaN] * len(parents_of)) else: mask = parents_of > 0 ccenters = centers_of_labels(children.segmented).transpose() ccenters = ccenters[mask,:] parents_of_masked = parents_of[mask] - 1 pperim = outline(parents.segmented) # # Get a list of all points on the perimeter # perim_loc = np.argwhere(pperim != 0) # # Get the label # for each point # perim_idx = pperim[perim_loc[:,0],perim_loc[:,1]] # # Sort the points by label # # idx = np.lexsort((perim_loc[:,1],perim_loc[:,0],perim_idx)) perim_loc = perim_loc[idx,:] perim_idx = perim_idx[idx] # # Get counts and indexes to each run of perimeter points # counts = fix(scind.sum(np.ones(len(perim_idx)),perim_idx, np.arange(1,perim_idx[-1]+1))).astype(np.int32) indexes = np.cumsum(counts) - counts # # For the children, get the index and count of the parent # ccounts = counts[parents_of_masked] cindexes = indexes[parents_of_masked] # # Now make an array that has an element for each of that child's # perimeter points # clabel = np.zeros(np.sum(ccounts), int) # # cfirst is the eventual first index of each child in the # clabel array # cfirst = np.cumsum(ccounts) - ccounts clabel[cfirst[1:]] += 1 clabel = np.cumsum(clabel) # # Make an index that runs from 0 to ccounts for each # child label. # cp_index = np.arange(len(clabel)) - cfirst[clabel] # # then add cindexes to get an index to the perimeter point # cp_index += cindexes[clabel] # # Now, calculate the distance from the centroid of each label # to each perimeter point in the parent. # dist = np.sqrt(np.sum((perim_loc[cp_index,:] - ccenters[clabel,:])**2,1)) # # Finally, find the minimum distance per child # min_dist = fix(scind.minimum(dist, clabel, np.arange(len(ccounts)))) # # Account for unparented children # dist = np.array([np.NaN] * len(mask)) dist[mask] = min_dist meas.add_measurement(sub_object_name, FF_MINIMUM % parent_name, dist)
def run_on_objects(self,object_name, workspace): """Run, computing the area measurements for a single map of objects""" objects = workspace.get_objects(object_name) assert isinstance(objects, cpo.Objects) # # Do the ellipse-related measurements # i, j, l = objects.ijv.transpose() centers, eccentricity, major_axis_length, minor_axis_length, \ theta, compactness =\ ellipse_from_second_moments_ijv(i, j, 1, l, objects.indices, True) del i del j del l self.record_measurement(workspace, object_name, F_ECCENTRICITY, eccentricity) self.record_measurement(workspace, object_name, F_MAJOR_AXIS_LENGTH, major_axis_length) self.record_measurement(workspace, object_name, F_MINOR_AXIS_LENGTH, minor_axis_length) self.record_measurement(workspace, object_name, F_ORIENTATION, theta * 180 / np.pi) self.record_measurement(workspace, object_name, F_COMPACTNESS, compactness) is_first = False if len(objects.indices) == 0: nobjects = 0 else: nobjects = np.max(objects.indices) mcenter_x = np.zeros(nobjects) mcenter_y = np.zeros(nobjects) mextent = np.zeros(nobjects) mperimeters = np.zeros(nobjects) msolidity = np.zeros(nobjects) euler = np.zeros(nobjects) max_radius = np.zeros(nobjects) median_radius = np.zeros(nobjects) mean_radius = np.zeros(nobjects) min_feret_diameter = np.zeros(nobjects) max_feret_diameter = np.zeros(nobjects) zernike_numbers = self.get_zernike_numbers() zf = {} for n,m in zernike_numbers: zf[(n,m)] = np.zeros(nobjects) if nobjects > 0: chulls, chull_counts = convex_hull_ijv(objects.ijv, objects.indices) for labels, indices in objects.get_labels(): to_indices = indices-1 distances = distance_to_edge(labels) mcenter_y[to_indices], mcenter_x[to_indices] =\ maximum_position_of_labels(distances, labels, indices) max_radius[to_indices] = fix(scind.maximum( distances, labels, indices)) mean_radius[to_indices] = fix(scind.mean( distances, labels, indices)) median_radius[to_indices] = median_of_labels( distances, labels, indices) # # The extent (area / bounding box area) # mextent[to_indices] = calculate_extents(labels, indices) # # The perimeter distance # mperimeters[to_indices] = calculate_perimeters(labels, indices) # # Solidity # msolidity[to_indices] = calculate_solidity(labels, indices) # # Euler number # euler[to_indices] = euler_number(labels, indices) # # Zernike features # zf_l = cpmz.zernike(zernike_numbers, labels, indices) for (n,m), z in zip(zernike_numbers, zf_l.transpose()): zf[(n,m)][to_indices] = z # # Form factor # ff = 4.0 * np.pi * objects.areas / mperimeters**2 # # Feret diameter # min_feret_diameter, max_feret_diameter = \ feret_diameter(chulls, chull_counts, objects.indices) else: ff = np.zeros(0) for f, m in ([(F_AREA, objects.areas), (F_CENTER_X, mcenter_x), (F_CENTER_Y, mcenter_y), (F_EXTENT, mextent), (F_PERIMETER, mperimeters), (F_SOLIDITY, msolidity), (F_FORM_FACTOR, ff), (F_EULER_NUMBER, euler), (F_MAXIMUM_RADIUS, max_radius), (F_MEAN_RADIUS, mean_radius), (F_MEDIAN_RADIUS, median_radius), (F_MIN_FERET_DIAMETER, min_feret_diameter), (F_MAX_FERET_DIAMETER, max_feret_diameter)] + [(self.get_zernike_name((n,m)), zf[(n,m)]) for n,m in zernike_numbers]): self.record_measurement(workspace, object_name, f, m)
def measure_zernike(self, pixels, labels, n, m): # I'll put some documentation in here to explain what it does. # If someone ever wants to call it, their editor might display # the documentation. '''Measure the intensity of the image with Zernike (N, M) pixels - the intensity image to be measured labels - the labels matrix that labels each object with an integer n, m - the Zernike coefficients. See http://en.wikipedia.org/wiki/Zernike_polynomials for an explanation of the Zernike polynomials ''' # # The strategy here is to operate on the whole array instead # of operating on one object at a time. The most important thing # is to avoid having to run the Python interpreter once per pixel # in the image and the second most important is to avoid running # it per object in case there are hundreds of objects. # # We play lots of indexing tricks here to operate on the whole image. # I'll try to explain some - hopefully, you can reuse. # # You could move the calculation of the minimum enclosing circle # outside of this function. The function gets called more than # 10 times, so the same calculation is performed 10 times. # It would make the code a little more confusing, so I'm leaving # it as-is. ########################################### # # The minimum enclosing circle (MEC) is the smallest circle that # will fit around the object. We get the centers and radii of # all of the objects at once. You'll see how that lets us # compute the X and Y position of each pixel in a label all at # one go. # # First, get an array that lists the whole range of indexes in # the labels matrix. # indexes = np.arange(1, np.max(labels)+1,dtype=np.int32) # # Then ask for the minimum_enclosing_circle for each object named # in those indexes. MEC returns the i and j coordinate of the center # and the radius of the circle and that defines the circle entirely. # centers, radius = minimum_enclosing_circle(labels, indexes) center_x = centers[:, 1] center_y = centers[:, 0] # # Make up fake values for 0 (the background). This lets us do our # indexing tricks. Really, we're going to ignore the background, # but we want to do the indexing without ignoring the background # because that's easier. # center_x = np.hstack([[0], center_x]) center_y = np.hstack([[0], center_y]) radius = np.hstack([[1], radius]) # # Now get one array that's the y coordinate of each pixel and one # that's the x coordinate. This might look stupid and wasteful, # but these "arrays" are never actually realized and made into # real memory. # y, x = np.mgrid[0:labels.shape[0], 0:labels.shape[1]] # # Get the x and y coordinates relative to the object centers. # This uses Numpy broadcasting. For each pixel, we use the # value in the labels matrix as an index into the appropriate # one-dimensional array. So we get the value for that object. # y -= center_y[labels] x -= center_x[labels] # # Zernikes take x and y values from zero to one. We scale the # integer coordinate values by dividing them by the radius of # the circle. Again, we use the indexing trick to look up the # values for each object. # y = y.astype(float) / radius[labels] x = x.astype(float) / radius[labels] # ################################# # # ZERNIKE POLYNOMIALS # # Now we can get Zernike polynomials per-pixel where each pixel # value is calculated according to its object's MEC. # # We use a mask of all of the non-zero labels so the calculation # runs a little faster. # zernike_polynomial = construct_zernike_polynomials( x, y, np.array([ [ n, m ]])) # # Multiply the Zernike polynomial by the image to dissect # the image by the Zernike basis set. # output_pixels = pixels * zernike_polynomial[:,:,0] # # The zernike polynomial is a complex number. We get a power # spectrum here to combine the real and imaginary parts # output_pixels = np.sqrt(output_pixels * output_pixels.conjugate()) # # Finally, we use Scipy to sum the intensities. Scipy has different # versions with different quirks. The "fix" function takes all # of that into account. # # The sum function calculates the sum of the pixel values for # each pixel in an object, using the labels matrix to name # the pixels in an object # result = fix(scind.sum(output_pixels.real, labels, indexes)) # # And we're done! Did you like it? Did you get it? # return result
def run(self, workspace): assert isinstance(workspace, cpw.Workspace) image = workspace.image_set.get_image(self.image_name.value, must_be_grayscale = True) img = image.pixel_data mask = image.mask objects = workspace.object_set.get_objects(self.primary_objects.value) global_threshold = None if self.method == M_DISTANCE_N: has_threshold = False elif self.threshold_method == cpthresh.TM_BINARY_IMAGE: binary_image = workspace.image_set.get_image(self.binary_image.value, must_be_binary = True) local_threshold = np.ones(img.shape) * np.max(img) + np.finfo(float).eps local_threshold[binary_image.pixel_data] = np.min(img) - np.finfo(float).eps global_threshold = cellprofiler.cpmath.otsu.otsu(img[mask], self.threshold_range.min, self.threshold_range.max) has_threshold = True else: local_threshold,global_threshold = self.get_threshold(img, mask, None, workspace) has_threshold = True if has_threshold: thresholded_image = img > local_threshold # # Get the following labels: # * all edited labels # * labels touching the edge, including small removed # labels_in = objects.unedited_segmented.copy() labels_touching_edge = np.hstack( (labels_in[0,:], labels_in[-1,:], labels_in[:,0], labels_in[:,-1])) labels_touching_edge = np.unique(labels_touching_edge) is_touching = np.zeros(np.max(labels_in)+1, bool) is_touching[labels_touching_edge] = True is_touching = is_touching[labels_in] labels_in[(~ is_touching) & (objects.segmented == 0)] = 0 # # Stretch the input labels to match the image size. If there's no # label matrix, then there's no label in that area. # if tuple(labels_in.shape) != tuple(img.shape): tmp = np.zeros(img.shape, labels_in.dtype) i_max = min(img.shape[0], labels_in.shape[0]) j_max = min(img.shape[1], labels_in.shape[1]) tmp[:i_max, :j_max] = labels_in[:i_max, :j_max] labels_in = tmp if self.method in (M_DISTANCE_B, M_DISTANCE_N): if self.method == M_DISTANCE_N: distances,(i,j) = scind.distance_transform_edt(labels_in == 0, return_indices = True) labels_out = np.zeros(labels_in.shape,int) dilate_mask = distances <= self.distance_to_dilate.value labels_out[dilate_mask] =\ labels_in[i[dilate_mask],j[dilate_mask]] else: labels_out, distances = propagate(img, labels_in, thresholded_image, 1.0) labels_out[distances>self.distance_to_dilate.value] = 0 labels_out[labels_in > 0] = labels_in[labels_in>0] if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out # # Create the final output labels by removing labels in the # output matrix that are missing from the segmented image # segmented_labels = objects.segmented segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) elif self.method == M_PROPAGATION: labels_out, distance = propagate(img, labels_in, thresholded_image, self.regularization_factor.value) if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out.copy() segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) elif self.method == M_WATERSHED_G: # # First, apply the sobel filter to the image (both horizontal # and vertical). The filter measures gradient. # sobel_image = np.abs(scind.sobel(img)) # # Combine the image mask and threshold to mask the watershed # watershed_mask = np.logical_or(thresholded_image, labels_in > 0) watershed_mask = np.logical_and(watershed_mask, mask) # # Perform the first watershed # labels_out = watershed(sobel_image, labels_in, np.ones((3,3),bool), mask=watershed_mask) if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out.copy() segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) elif self.method == M_WATERSHED_I: # # invert the image so that the maxima are filled first # and the cells compete over what's close to the threshold # inverted_img = 1-img # # Same as above, but perform the watershed on the original image # watershed_mask = np.logical_or(thresholded_image, labels_in > 0) watershed_mask = np.logical_and(watershed_mask, mask) # # Perform the watershed # labels_out = watershed(inverted_img, labels_in, np.ones((3,3),bool), mask=watershed_mask) if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) if self.wants_discard_edge and self.wants_discard_primary: # # Make a new primary object # lookup = scind.maximum(segmented_out, objects.segmented, range(np.max(objects.segmented)+1)) lookup = fix(lookup) lookup[0] = 0 lookup[lookup != 0] = np.arange(np.sum(lookup != 0)) + 1 segmented_labels = lookup[objects.segmented] segmented_out = lookup[segmented_out] new_objects = cpo.Objects() new_objects.segmented = segmented_labels if objects.has_unedited_segmented: new_objects.unedited_segmented = objects.unedited_segmented if objects.has_small_removed_segmented: new_objects.small_removed_segmented = objects.small_removed_segmented new_objects.parent_image = objects.parent_image primary_outline = outline(segmented_labels) if self.wants_primary_outlines: out_img = cpi.Image(primary_outline.astype(bool), parent_image = image) workspace.image_set.add(self.new_primary_outlines_name.value, out_img) else: primary_outline = outline(objects.segmented) secondary_outline = outline(segmented_out) if workspace.frame != None: object_area = np.sum(segmented_out > 0) object_pct = 100 * object_area / np.product(segmented_out.shape) my_frame=workspace.create_or_find_figure(title="IdentifySecondaryObjects, image cycle #%d"%( workspace.measurements.image_set_number),subplots=(2,2)) title = "Input image, cycle #%d"%(workspace.image_set.number+1) my_frame.subplot_imshow_grayscale(0, 0, img, title) my_frame.subplot_imshow_labels(1, 0, segmented_out, "Labeled image", sharex = my_frame.subplot(0,0), sharey = my_frame.subplot(0,0)) outline_img = np.dstack((img, img, img)) cpmi.draw_outline(outline_img, secondary_outline > 0, cpprefs.get_secondary_outline_color()) my_frame.subplot_imshow(0, 1, outline_img, "Outlined image", normalize=False, sharex = my_frame.subplot(0,0), sharey = my_frame.subplot(0,0)) primary_img = np.dstack((img, img, img)) cpmi.draw_outline(primary_img, primary_outline > 0, cpprefs.get_primary_outline_color()) cpmi.draw_outline(primary_img, secondary_outline > 0, cpprefs.get_secondary_outline_color()) my_frame.subplot_imshow(1, 1, primary_img, "Primary and output outlines", normalize=False, sharex = my_frame.subplot(0,0), sharey = my_frame.subplot(0,0)) if global_threshold is not None: my_frame.status_bar.SetFields( ["Threshold: %.3f" % global_threshold, "Area covered by objects: %.1f %%" % object_pct]) else: my_frame.status_bar.SetFields( ["Area covered by objects: %.1f %%" % object_pct]) # # Add the objects to the object set # objects_out = cpo.Objects() objects_out.unedited_segmented = small_removed_segmented_out objects_out.small_removed_segmented = small_removed_segmented_out objects_out.segmented = segmented_out objects_out.parent_image = image objname = self.objects_name.value workspace.object_set.add_objects(objects_out, objname) if self.use_outlines.value: out_img = cpi.Image(secondary_outline.astype(bool), parent_image = image) workspace.image_set.add(self.outlines_name.value, out_img) object_count = np.max(segmented_out) # # Add the background measurements if made # measurements = workspace.measurements if has_threshold: if isinstance(local_threshold,np.ndarray): ave_threshold = np.mean(local_threshold) else: ave_threshold = local_threshold measurements.add_measurement(cpmeas.IMAGE, cpmi.FF_FINAL_THRESHOLD%(objname), np.array([ave_threshold], dtype=float)) measurements.add_measurement(cpmeas.IMAGE, cpmi.FF_ORIG_THRESHOLD%(objname), np.array([global_threshold], dtype=float)) wv = cpthresh.weighted_variance(img, mask, local_threshold) measurements.add_measurement(cpmeas.IMAGE, cpmi.FF_WEIGHTED_VARIANCE%(objname), np.array([wv],dtype=float)) entropies = cpthresh.sum_of_entropies(img, mask, local_threshold) measurements.add_measurement(cpmeas.IMAGE, cpmi.FF_SUM_OF_ENTROPIES%(objname), np.array([entropies],dtype=float)) cpmi.add_object_count_measurements(measurements, objname, object_count) cpmi.add_object_location_measurements(measurements, objname, segmented_out) # # Relate the secondary objects to the primary ones and record # the relationship. # children_per_parent, parents_of_children = \ objects.relate_children(objects_out) measurements.add_measurement(self.primary_objects.value, cpmi.FF_CHILDREN_COUNT%objname, children_per_parent) measurements.add_measurement(objname, cpmi.FF_PARENT%self.primary_objects.value, parents_of_children) # # If primary objects were created, add them # if self.wants_discard_edge and self.wants_discard_primary: workspace.object_set.add_objects(new_objects, self.new_primary_objects_name.value) cpmi.add_object_count_measurements(measurements, self.new_primary_objects_name.value, np.max(new_objects.segmented)) cpmi.add_object_location_measurements(measurements, self.new_primary_objects_name.value, new_objects.segmented) for parent_objects, parent_name, child_objects, child_name in ( (objects, self.primary_objects.value, new_objects, self.new_primary_objects_name.value), (new_objects, self.new_primary_objects_name.value, objects_out, objname)): children_per_parent, parents_of_children = \ parent_objects.relate_children(child_objects) measurements.add_measurement(parent_name, cpmi.FF_CHILDREN_COUNT%child_name, children_per_parent) measurements.add_measurement(child_name, cpmi.FF_PARENT%parent_name, parents_of_children)
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) # # 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) # # 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) # # Add an image number column to both and change vertex index # to vertex number (one-based) # image_number = workspace.measurements.image_set_number vertex_graph = np.rec.fromarrays( (np.ones(len(vertex_graph)) * image_number, np.arange(1, len(vertex_graph) + 1), vertex_graph['i'], vertex_graph['j'], vertex_graph['labels'], vertex_graph['kind']), names = ("image_number", "vertex_number", "i", "j", "labels", "kind")) edge_graph = np.rec.fromarrays( (np.ones(len(edge_graph)) * image_number, edge_graph["v1"], edge_graph["v2"], edge_graph["length"], edge_graph["total_intensity"]), names = ("image_number", "v1", "v2", "length", "total_intensity")) path = self.directory.get_absolute_path(m) edge_file = m.apply_metadata(self.edge_file_name.value) edge_path = os.path.abspath(os.path.join(path, edge_file)) vertex_file = m.apply_metadata(self.vertex_file_name.value) vertex_path = os.path.abspath(os.path.join(path, vertex_file)) d = self.get_dictionary(workspace.image_set_list) for file_path, table, fmt in ( (edge_path, edge_graph, "%d,%d,%d,%d,%.4f"), (vertex_path, vertex_graph, "%d,%d,%d,%d,%d,%s")): # # Delete files first time through / otherwise append # if not d.has_key(file_path): d[file_path] = True if os.path.exists(file_path): if workspace.frame is not None: import wx if wx.MessageBox( "%s already exists. Do you want to overwrite it?" % file_path, "Warning: overwriting file", style = wx.YES_NO, parent = workspace.frame) != wx.YES: raise ValueError("Can't overwrite %s" % file_path) os.remove(file_path) fd = open(file_path, 'wt') header = ','.join(table.dtype.names) fd.write(header + '\n') else: fd = open(file_path, 'at') np.savetxt(fd, table, fmt) fd.close() if workspace.frame is not None: workspace.display_data.edge_graph = edge_graph workspace.display_data.vertex_graph = vertex_graph # # Make the display image # if workspace.frame is not None 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 workspace.frame: 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 measure_TAS(self, pixels, labels, n, m): # I'll put some documentation in here to explain what it does. # If someone ever wants to call it, their editor might display # the documentation. '''Measure the intensity of the image with Zernike (N, M) pixels - the intensity image to be measured labels - the labels matrix that labels each object with an integer n, m - the Zernike coefficients. See http://en.wikipedia.org/wiki/Zernike_polynomials for an explanation of the Zernike polynomials ''' # # The strategy here is to operate on the whole array instead # of operating on one object at a time. The most important thing # is to avoid having to run the Python interpreter once per pixel # in the image and the second most important is to avoid running # it per object in case there are hundreds of objects. # # We play lots of indexing tricks here to operate on the whole image. # I'll try to explain some - hopefully, you can reuse. # # You could move the calculation of the minimum enclosing circle # outside of this function. The function gets called more than # 10 times, so the same calculation is performed 10 times. # It would make the code a little more confusing, so I'm leaving # it as-is. ########################################### # # The minimum enclosing circle (MEC) is the smallest circle that # will fit around the object. We get the centers and radii of # all of the objects at once. You'll see how that lets us # compute the X and Y position of each pixel in a label all at # one go. # # First, get an array that lists the whole range of indexes in # the labels matrix. # if len(labels) == 0: n_objects = 0 else: n_objects = np.max(labels) if n_objects == 0: result = np.zeros((0, )) else: indexes = np.arange(1, np.max(labels) + 1, dtype=np.int32) # Calculate mean of pixels above int 30 mean = np.mean(pixels[pixels > 0.118]) # Set ranges rangelow = mean + n rangehigh = mean + m # Threshold image, create mask mask = np.logical_and(pixels < rangehigh, pixels > rangelow) thresholded_image = np.zeros(np.shape(pixels)) thresholded_image[mask] = 1 # Apply convolution to get the sum of sorrounding pixels # First define a weight array w = np.array([[1, 1, 1], [1, 0, 1], [1, 1, 1]]) # Create a new array of sums sums = scind.convolve(thresholded_image, w, mode='constant') # remove 0 pixels from sums sums[~mask] = 9 # Get the histogram result = fix( scind.histogram(sums.astype(int), 0, 9, 10, labels=labels, index=indexes)) #print np.shape(result) result = np.vstack(result).T.astype(np.float64) #result = np.random.rand(2,9).T result = result / np.sum(result, axis=0) #result = result[0:9] #result = (result/np.sum(result)).astype(np.float64) # # And we're done! Did you like it? Did you get it? # return result[0:9]
def calculate_minimum_distances(self, workspace, parent_name): '''Calculate the distance from child center to parent perimeter''' meas = workspace.measurements assert isinstance(meas, cpmeas.Measurements) sub_object_name = self.sub_object_name.value parents = workspace.object_set.get_objects(parent_name) children = workspace.object_set.get_objects(sub_object_name) parents_of = self.get_parents_of(workspace, parent_name) if len(parents_of) == 0: dist = np.zeros((0, )) elif np.all(parents_of == 0): dist = np.array([np.NaN] * len(parents_of)) else: mask = parents_of > 0 ccenters = centers_of_labels(children.segmented).transpose() ccenters = ccenters[mask, :] parents_of_masked = parents_of[mask] - 1 pperim = outline(parents.segmented) # # Get a list of all points on the perimeter # perim_loc = np.argwhere(pperim != 0) # # Get the label # for each point # perim_idx = pperim[perim_loc[:, 0], perim_loc[:, 1]] # # Sort the points by label # # idx = np.lexsort((perim_loc[:, 1], perim_loc[:, 0], perim_idx)) perim_loc = perim_loc[idx, :] perim_idx = perim_idx[idx] # # Get counts and indexes to each run of perimeter points # counts = fix( scind.sum(np.ones(len(perim_idx)), perim_idx, np.arange(1, perim_idx[-1] + 1))).astype(np.int32) indexes = np.cumsum(counts) - counts # # For the children, get the index and count of the parent # ccounts = counts[parents_of_masked] cindexes = indexes[parents_of_masked] # # Now make an array that has an element for each of that child's # perimeter points # clabel = np.zeros(np.sum(ccounts), int) # # cfirst is the eventual first index of each child in the # clabel array # cfirst = np.cumsum(ccounts) - ccounts clabel[cfirst[1:]] += 1 clabel = np.cumsum(clabel) # # Make an index that runs from 0 to ccounts for each # child label. # cp_index = np.arange(len(clabel)) - cfirst[clabel] # # then add cindexes to get an index to the perimeter point # cp_index += cindexes[clabel] # # Now, calculate the distance from the centroid of each label # to each perimeter point in the parent. # dist = np.sqrt( np.sum((perim_loc[cp_index, :] - ccenters[clabel, :])**2, 1)) # # Finally, find the minimum distance per child # min_dist = fix(scind.minimum(dist, clabel, np.arange(len(ccounts)))) # # Account for unparented children # dist = np.array([np.NaN] * len(mask)) dist[mask] = min_dist meas.add_measurement(sub_object_name, FF_MINIMUM % parent_name, dist)
def run(self, workspace): parents = workspace.object_set.get_objects(self.parent_name.value) children = workspace.object_set.get_objects(self.sub_object_name.value) child_count, parents_of = parents.relate_children(children) m = workspace.measurements assert isinstance(m, cpmeas.Measurements) if self.wants_per_parent_means.value: parent_indexes = np.arange(np.max(parents.segmented)) + 1 for feature_name in m.get_feature_names( self.sub_object_name.value): if not self.should_aggregate_feature(feature_name): continue data = m.get_current_measurement(self.sub_object_name.value, feature_name) if data is not None: if len(parents_of) > 0: means = fix( scind.mean(data.astype(float), parents_of, parent_indexes)) else: means = np.zeros((0, )) mean_feature_name = FF_MEAN % (self.sub_object_name.value, feature_name) m.add_measurement(self.parent_name.value, mean_feature_name, means) m.add_measurement(self.sub_object_name.value, FF_PARENT % (self.parent_name.value), parents_of) m.add_measurement(self.parent_name.value, FF_CHILDREN_COUNT % (self.sub_object_name.value), child_count) group_index = m.get_current_image_measurement(cpmeas.GROUP_INDEX) group_indexes = np.ones(np.sum(parents_of != 0), int) * group_index good_parents = parents_of[parents_of != 0] good_children = np.argwhere(parents_of != 0).flatten() + 1 if np.any(good_parents): m.add_relate_measurement(self.module_num, R_PARENT, self.parent_name.value, self.sub_object_name.value, group_indexes, good_parents, group_indexes, good_children) m.add_relate_measurement(self.module_num, R_CHILD, self.sub_object_name.value, self.parent_name.value, group_indexes, good_children, group_indexes, good_parents) parent_names = self.get_parent_names() for parent_name in parent_names: if self.find_parent_child_distances in (D_BOTH, D_CENTROID): self.calculate_centroid_distances(workspace, parent_name) if self.find_parent_child_distances in (D_BOTH, D_MINIMUM): self.calculate_minimum_distances(workspace, parent_name) if workspace.frame is not None: figure = workspace.create_or_find_figure( title="RelateObjects, image cycle #%d" % (workspace.measurements.image_set_number), subplots=(2, 2)) figure.subplot_imshow_labels(0, 0, parents.segmented, title=self.parent_name.value) figure.subplot_imshow_labels(1, 0, children.segmented, title=self.sub_object_name.value, sharex=figure.subplot(0, 0), sharey=figure.subplot(0, 0)) parent_labeled_children = np.zeros(children.segmented.shape, int) parent_labeled_children[children.segmented > 0] = \ parents_of[children.segmented[children.segmented > 0]-1] figure.subplot_imshow_labels( 0, 1, parent_labeled_children, "%s labeled by %s" % (self.sub_object_name.value, self.parent_name.value), sharex=figure.subplot(0, 0), sharey=figure.subplot(0, 0))
def run(self, workspace): if workspace.frame is not None: statistics = [("Image","Object","Feature","Mean","Median","STD")] workspace.display_data.statistics = statistics for image_name in [img.name for img in self.images]: image = workspace.image_set.get_image(image_name.value, must_be_grayscale=True) for object_name in [obj.name for obj in self.objects]: # Need to refresh image after each iteration... img = image.pixel_data if image.has_mask: masked_image = img.copy() masked_image[~image.mask] = 0 else: masked_image = img objects = workspace.object_set.get_objects(object_name.value) nobjects = objects.count integrated_intensity = np.zeros((nobjects,)) integrated_intensity_edge = np.zeros((nobjects,)) mean_intensity = np.zeros((nobjects,)) mean_intensity_edge = np.zeros((nobjects,)) std_intensity = np.zeros((nobjects,)) std_intensity_edge = np.zeros((nobjects,)) min_intensity = np.zeros((nobjects,)) min_intensity_edge = np.zeros((nobjects,)) max_intensity = np.zeros((nobjects,)) max_intensity_edge = np.zeros((nobjects,)) mass_displacement = np.zeros((nobjects,)) lower_quartile_intensity = np.zeros((nobjects,)) median_intensity = np.zeros((nobjects,)) upper_quartile_intensity = np.zeros((nobjects,)) cmi_x = np.zeros((nobjects,)) cmi_y = np.zeros((nobjects,)) max_x = np.zeros((nobjects,)) max_y = np.zeros((nobjects,)) for labels, lindexes in objects.get_labels(): lindexes = lindexes[lindexes != 0] labels, img = cpo.crop_labels_and_image(labels, img) _, masked_image = cpo.crop_labels_and_image(labels, masked_image) outlines = cpmo.outline(labels) if image.has_mask: _, mask = cpo.crop_labels_and_image(labels, image.mask) masked_labels = labels.copy() masked_labels[~mask] = 0 masked_outlines = outlines.copy() masked_outlines[~mask] = 0 else: masked_labels = labels masked_outlines = outlines lmask = masked_labels > 0 & np.isfinite(img) # Ignore NaNs, Infs has_objects = np.any(lmask) if has_objects: emask = masked_outlines > 0 limg = img[lmask] eimg = img[emask] llabels = labels[lmask] elabels = labels[emask] mesh_y, mesh_x = np.mgrid[0:masked_image.shape[0], 0:masked_image.shape[1]] mesh_x = mesh_x[lmask] mesh_y = mesh_y[lmask] lcount = fix(nd.sum(np.ones(len(limg)), llabels, lindexes)) ecount = fix(nd.sum(np.ones(len(eimg)), elabels, lindexes)) integrated_intensity[lindexes-1] = \ fix(nd.sum(limg, llabels, lindexes)) integrated_intensity_edge[lindexes-1] = \ fix(nd.sum(eimg, elabels, lindexes)) mean_intensity[lindexes-1] = \ integrated_intensity[lindexes-1] / lcount mean_intensity_edge[lindexes-1] = \ integrated_intensity_edge[lindexes-1] / ecount std_intensity[lindexes-1] = np.sqrt( fix(nd.mean((limg - mean_intensity[llabels-1])**2, llabels, lindexes))) std_intensity_edge[lindexes-1] = np.sqrt( fix(nd.mean((eimg - mean_intensity_edge[elabels-1])**2, elabels, lindexes))) min_intensity[lindexes-1] = fix(nd.minimum(limg, llabels, lindexes)) min_intensity_edge[lindexes-1] = fix( nd.minimum(eimg, elabels, lindexes)) max_intensity[lindexes-1] = fix( nd.maximum(limg, llabels, lindexes)) max_intensity_edge[lindexes-1] = fix( nd.maximum(eimg, elabels, lindexes)) # Compute the position of the intensity maximum max_position = np.array( fix(nd.maximum_position(limg, llabels, lindexes)), dtype=int ) max_position = np.reshape( max_position, ( max_position.shape[0], ) ) max_x[lindexes-1] = mesh_x[ max_position ] max_y[lindexes-1] = mesh_y[ max_position ] # The mass displacement is the distance between the center # of mass of the binary image and of the intensity image. The # center of mass is the average X or Y for the binary image # and the sum of X or Y * intensity / integrated intensity cm_x = fix(nd.mean(mesh_x, llabels, lindexes)) cm_y = fix(nd.mean(mesh_y, llabels, lindexes)) i_x = fix(nd.sum(mesh_x * limg, llabels, lindexes)) i_y = fix(nd.sum(mesh_y * limg, llabels, lindexes)) cmi_x[lindexes-1] = i_x / integrated_intensity[lindexes-1] cmi_y[lindexes-1] = i_y / integrated_intensity[lindexes-1] diff_x = cm_x - cmi_x[lindexes-1] diff_y = cm_y - cmi_y[lindexes-1] mass_displacement[lindexes-1] = \ np.sqrt(diff_x * diff_x+diff_y*diff_y) # # Sort the intensities by label, then intensity. # For each label, find the index above and below # the 25%, 50% and 75% mark and take the weighted # average. # order = np.lexsort((limg, llabels)) areas = lcount.astype(int) indices = np.cumsum(areas) - areas for dest, fraction in ( (lower_quartile_intensity, 1.0/4.0), (median_intensity, 1.0/2.0), (upper_quartile_intensity, 3.0/4.0)): qindex = indices.astype(float) + areas * fraction qfraction = qindex - np.floor(qindex) qindex = qindex.astype(int) qmask = qindex < indices + areas-1 qi = qindex[qmask] qf = qfraction[qmask] dest[lindexes[qmask]-1] = ( limg[order[qi]] * (1 - qf) + limg[order[qi + 1]] * qf) # # In some situations (e.g. only 3 points), there may # not be an upper bound. # qmask = (~qmask) & (areas > 0) dest[lindexes[qmask]-1] = limg[order[qindex[qmask]]] m = workspace.measurements for category, feature_name, measurement in \ ((INTENSITY, INTEGRATED_INTENSITY, integrated_intensity), (INTENSITY, MEAN_INTENSITY, mean_intensity), (INTENSITY, STD_INTENSITY, std_intensity), (INTENSITY, MIN_INTENSITY, min_intensity), (INTENSITY, MAX_INTENSITY, max_intensity), (INTENSITY, INTEGRATED_INTENSITY_EDGE, integrated_intensity_edge), (INTENSITY, MEAN_INTENSITY_EDGE, mean_intensity_edge), (INTENSITY, STD_INTENSITY_EDGE, std_intensity_edge), (INTENSITY, MIN_INTENSITY_EDGE, min_intensity_edge), (INTENSITY, MAX_INTENSITY_EDGE, max_intensity_edge), (INTENSITY, MASS_DISPLACEMENT, mass_displacement), (INTENSITY, LOWER_QUARTILE_INTENSITY, lower_quartile_intensity), (INTENSITY, MEDIAN_INTENSITY, median_intensity), (INTENSITY, UPPER_QUARTILE_INTENSITY, upper_quartile_intensity), (C_LOCATION, LOC_CMI_X, cmi_x), (C_LOCATION, LOC_CMI_Y, cmi_y), (C_LOCATION, LOC_MAX_X, max_x), (C_LOCATION, LOC_MAX_Y, max_y)): measurement_name = "%s_%s_%s"%(category,feature_name, image_name.value) m.add_measurement(object_name.value,measurement_name, measurement) if workspace.frame is not None and len(measurement) > 0: statistics.append((image_name.value, object_name.value, feature_name, np.round(np.mean(measurement),3), np.round(np.median(measurement),3), np.round(np.std(measurement),3)))
def run(self, workspace): objects = workspace.object_set.get_objects(self.object_name.value) assert isinstance(objects, cpo.Objects) 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 nobjects = np.max(labels) nneighbors = np.max(neighbor_labels) nkept_objects = objects.count _, 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, )) 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] 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: 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) # # 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]) if self.neighbors_are_objects: first_object_number = order[:, 1] + 1 if nkept_objects > 2: second_object_number = order[:, 2] + 1 else: first_object_number = order[:, 0] + 1 if nneighbors > 1: second_object_number = order[:, 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] percent_touching = percent_touching[object_indexes] 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 assert (isinstance(image_set, cpi.ImageSet)) 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.group_index * np.ones(first_objects.shape, int), first_objects, m.group_index * 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 = get_colormap(self.count_colormap.value) 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 = matplotlib.cm.get_cmap( cpprefs.get_default_colormap()) if self.neighbors_are_objects and self.wants_percent_touching_image: percent_touching_cm = get_colormap(self.touching_colormap.value) 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 = matplotlib.cm.get_cmap( cpprefs.get_default_colormap()) workspace.display_data.neighbor_cm = neighbor_cm workspace.display_data.percent_touching_cm = percent_touching_cm workspace.display_data.orig_labels = objects.segmented workspace.display_data.labels = labels workspace.display_data.object_mask = object_mask
def run(self, workspace): assert isinstance(workspace, cpw.Workspace) image_name = self.image_name.value image = workspace.image_set.get_image(image_name, must_be_grayscale=True) workspace.display_data.statistics = [] img = image.pixel_data mask = image.mask objects = workspace.object_set.get_objects(self.primary_objects.value) global_threshold = None if self.method == M_DISTANCE_N: has_threshold = False else: thresholded_image = self.threshold_image(image_name, workspace) has_threshold = True # # Get the following labels: # * all edited labels # * labels touching the edge, including small removed # labels_in = objects.unedited_segmented.copy() labels_touching_edge = np.hstack( (labels_in[0, :], labels_in[-1, :], labels_in[:, 0], labels_in[:, -1])) labels_touching_edge = np.unique(labels_touching_edge) is_touching = np.zeros(np.max(labels_in) + 1, bool) is_touching[labels_touching_edge] = True is_touching = is_touching[labels_in] labels_in[(~is_touching) & (objects.segmented == 0)] = 0 # # Stretch the input labels to match the image size. If there's no # label matrix, then there's no label in that area. # if tuple(labels_in.shape) != tuple(img.shape): tmp = np.zeros(img.shape, labels_in.dtype) i_max = min(img.shape[0], labels_in.shape[0]) j_max = min(img.shape[1], labels_in.shape[1]) tmp[:i_max, :j_max] = labels_in[:i_max, :j_max] labels_in = tmp if self.method in (M_DISTANCE_B, M_DISTANCE_N): if self.method == M_DISTANCE_N: distances, (i, j) = scind.distance_transform_edt( labels_in == 0, return_indices=True) labels_out = np.zeros(labels_in.shape, int) dilate_mask = distances <= self.distance_to_dilate.value labels_out[dilate_mask] =\ labels_in[i[dilate_mask],j[dilate_mask]] else: labels_out, distances = propagate(img, labels_in, thresholded_image, 1.0) labels_out[distances > self.distance_to_dilate.value] = 0 labels_out[labels_in > 0] = labels_in[labels_in > 0] if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out # # Create the final output labels by removing labels in the # output matrix that are missing from the segmented image # segmented_labels = objects.segmented segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) elif self.method == M_PROPAGATION: labels_out, distance = propagate(img, labels_in, thresholded_image, self.regularization_factor.value) if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out.copy() segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) elif self.method == M_WATERSHED_G: # # First, apply the sobel filter to the image (both horizontal # and vertical). The filter measures gradient. # sobel_image = np.abs(scind.sobel(img)) # # Combine the image mask and threshold to mask the watershed # watershed_mask = np.logical_or(thresholded_image, labels_in > 0) watershed_mask = np.logical_and(watershed_mask, mask) # # Perform the first watershed # labels_out = watershed(sobel_image, labels_in, np.ones((3, 3), bool), mask=watershed_mask) if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out.copy() segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) elif self.method == M_WATERSHED_I: # # invert the image so that the maxima are filled first # and the cells compete over what's close to the threshold # inverted_img = 1 - img # # Same as above, but perform the watershed on the original image # watershed_mask = np.logical_or(thresholded_image, labels_in > 0) watershed_mask = np.logical_and(watershed_mask, mask) # # Perform the watershed # labels_out = watershed(inverted_img, labels_in, np.ones((3, 3), bool), mask=watershed_mask) if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) if self.wants_discard_edge and self.wants_discard_primary: # # Make a new primary object # lookup = scind.maximum(segmented_out, objects.segmented, range(np.max(objects.segmented) + 1)) lookup = fix(lookup) lookup[0] = 0 lookup[lookup != 0] = np.arange(np.sum(lookup != 0)) + 1 segmented_labels = lookup[objects.segmented] segmented_out = lookup[segmented_out] new_objects = cpo.Objects() new_objects.segmented = segmented_labels if objects.has_unedited_segmented: new_objects.unedited_segmented = objects.unedited_segmented if objects.has_small_removed_segmented: new_objects.small_removed_segmented = objects.small_removed_segmented new_objects.parent_image = objects.parent_image primary_outline = outline(segmented_labels) if self.wants_primary_outlines: out_img = cpi.Image(primary_outline.astype(bool), parent_image=image) workspace.image_set.add(self.new_primary_outlines_name.value, out_img) else: primary_outline = outline(objects.segmented) secondary_outline = outline(segmented_out) # # Add the objects to the object set # objects_out = cpo.Objects() objects_out.unedited_segmented = small_removed_segmented_out objects_out.small_removed_segmented = small_removed_segmented_out objects_out.segmented = segmented_out objects_out.parent_image = image objname = self.objects_name.value workspace.object_set.add_objects(objects_out, objname) if self.use_outlines.value: out_img = cpi.Image(secondary_outline.astype(bool), parent_image=image) workspace.image_set.add(self.outlines_name.value, out_img) object_count = np.max(segmented_out) # # Add measurements # measurements = workspace.measurements cpmi.add_object_count_measurements(measurements, objname, object_count) cpmi.add_object_location_measurements(measurements, objname, segmented_out) # # Relate the secondary objects to the primary ones and record # the relationship. # children_per_parent, parents_of_children = \ objects.relate_children(objects_out) measurements.add_measurement(self.primary_objects.value, cpmi.FF_CHILDREN_COUNT % objname, children_per_parent) measurements.add_measurement( objname, cpmi.FF_PARENT % self.primary_objects.value, parents_of_children) # # If primary objects were created, add them # if self.wants_discard_edge and self.wants_discard_primary: workspace.object_set.add_objects( new_objects, self.new_primary_objects_name.value) cpmi.add_object_count_measurements( measurements, self.new_primary_objects_name.value, np.max(new_objects.segmented)) cpmi.add_object_location_measurements( measurements, self.new_primary_objects_name.value, new_objects.segmented) for parent_objects, parent_name, child_objects, child_name in ( (objects, self.primary_objects.value, new_objects, self.new_primary_objects_name.value), (new_objects, self.new_primary_objects_name.value, objects_out, objname)): children_per_parent, parents_of_children = \ parent_objects.relate_children(child_objects) measurements.add_measurement( parent_name, cpmi.FF_CHILDREN_COUNT % child_name, children_per_parent) measurements.add_measurement(child_name, cpmi.FF_PARENT % parent_name, parents_of_children) if self.show_window: object_area = np.sum(segmented_out > 0) workspace.display_data.object_pct = \ 100 * object_area / np.product(segmented_out.shape) workspace.display_data.img = img workspace.display_data.segmented_out = segmented_out workspace.display_data.primary_outline = primary_outline workspace.display_data.secondary_outline = secondary_outline workspace.display_data.global_threshold = global_threshold workspace.display_data.object_count = object_count
def measure_zernike(self, pixels, labels, indexes, centers, radius, n, m): # I'll put some documentation in here to explain what it does. # If someone ever wants to call it, their editor might display # the documentation. '''Measure the intensity of the image with Zernike (N, M) pixels - the intensity image to be measured labels - the labels matrix that labels each object with an integer indexes - the label #s in the image centers - the centers of the minimum enclosing circle for each object radius - the radius of the minimum enclosing circle for each object n, m - the Zernike coefficients. See http://en.wikipedia.org/wiki/Zernike_polynomials for an explanation of the Zernike polynomials ''' # # The strategy here is to operate on the whole array instead # of operating on one object at a time. The most important thing # is to avoid having to run the Python interpreter once per pixel # in the image and the second most important is to avoid running # it per object in case there are hundreds of objects. # # We play lots of indexing tricks here to operate on the whole image. # I'll try to explain some - hopefully, you can reuse. center_x = centers[:, 1] center_y = centers[:, 0] # # Make up fake values for 0 (the background). This lets us do our # indexing tricks. Really, we're going to ignore the background, # but we want to do the indexing without ignoring the background # because that's easier. # center_x = np.hstack([[0], center_x]) center_y = np.hstack([[0], center_y]) radius = np.hstack([[1], radius]) # # Now get one array that's the y coordinate of each pixel and one # that's the x coordinate. This might look stupid and wasteful, # but these "arrays" are never actually realized and made into # real memory. # y, x = np.mgrid[0:labels.shape[0], 0:labels.shape[1]] # # Get the x and y coordinates relative to the object centers. # This uses Numpy broadcasting. For each pixel, we use the # value in the labels matrix as an index into the appropriate # one-dimensional array. So we get the value for that object. # y -= center_y[labels] x -= center_x[labels] # # Zernikes take x and y values from zero to one. We scale the # integer coordinate values by dividing them by the radius of # the circle. Again, we use the indexing trick to look up the # values for each object. # y = y.astype(float) / radius[labels] x = x.astype(float) / radius[labels] # ################################# # # ZERNIKE POLYNOMIALS # # Now we can get Zernike polynomials per-pixel where each pixel # value is calculated according to its object's MEC. # # We use a mask of all of the non-zero labels so the calculation # runs a little faster. # zernike_polynomial = construct_zernike_polynomials( x, y, np.array([[n, m]]), labels > 0) # # For historical reasons, CellProfiler didn't multiply by the per/zernike # normalizing factor: 2*n + 2 / E / pi where E is 2 if m is zero and 1 # if m is one. We do it here to aid with the reconstruction # zernike_polynomial *= (2 * n + 2) / (2 if m == 0 else 1) / np.pi # # Multiply the Zernike polynomial by the image to dissect # the image by the Zernike basis set. # output_pixels = pixels * zernike_polynomial[:, :, 0] # # Finally, we use Scipy to sum the intensities. Scipy has different # versions with different quirks. The "fix" function takes all # of that into account. # # The sum function calculates the sum of the pixel values for # each pixel in an object, using the labels matrix to name # the pixels in an object # zr = fix(scind.sum(output_pixels.real, labels, indexes)) zi = fix(scind.sum(output_pixels.imag, labels, indexes)) # # And we're done! Did you like it? Did you get it? # return zr, zi
def measure_zernike(self, pixels, labels, n, m): # I'll put some documentation in here to explain what it does. # If someone ever wants to call it, their editor might display # the documentation. '''Measure the intensity of the image with Zernike (N, M) pixels - the intensity image to be measured labels - the labels matrix that labels each object with an integer n, m - the Zernike coefficients. See http://en.wikipedia.org/wiki/Zernike_polynomials for an explanation of the Zernike polynomials ''' # # The strategy here is to operate on the whole array instead # of operating on one object at a time. The most important thing # is to avoid having to run the Python interpreter once per pixel # in the image and the second most important is to avoid running # it per object in case there are hundreds of objects. # # We play lots of indexing tricks here to operate on the whole image. # I'll try to explain some - hopefully, you can reuse. # # You could move the calculation of the minimum enclosing circle # outside of this function. The function gets called more than # 10 times, so the same calculation is performed 10 times. # It would make the code a little more confusing, so I'm leaving # it as-is. ########################################### # # The minimum enclosing circle (MEC) is the smallest circle that # will fit around the object. We get the centers and radii of # all of the objects at once. You'll see how that lets us # compute the X and Y position of each pixel in a label all at # one go. # # First, get an array that lists the whole range of indexes in # the labels matrix. # indexes = np.arange(1, np.max(labels) + 1, dtype=np.int32) # # Then ask for the minimum_enclosing_circle for each object named # in those indexes. MEC returns the i and j coordinate of the center # and the radius of the circle and that defines the circle entirely. # centers, radius = minimum_enclosing_circle(labels, indexes) center_x = centers[:, 1] center_y = centers[:, 0] # # Make up fake values for 0 (the background). This lets us do our # indexing tricks. Really, we're going to ignore the background, # but we want to do the indexing without ignoring the background # because that's easier. # center_x = np.hstack([[0], center_x]) center_y = np.hstack([[0], center_y]) radius = np.hstack([[1], radius]) # # Now get one array that's the y coordinate of each pixel and one # that's the x coordinate. This might look stupid and wasteful, # but these "arrays" are never actually realized and made into # real memory. # y, x = np.mgrid[0:labels.shape[0], 0:labels.shape[1]] # # Get the x and y coordinates relative to the object centers. # This uses Numpy broadcasting. For each pixel, we use the # value in the labels matrix as an index into the appropriate # one-dimensional array. So we get the value for that object. # y -= center_y[labels] x -= center_x[labels] # # Zernikes take x and y values from zero to one. We scale the # integer coordinate values by dividing them by the radius of # the circle. Again, we use the indexing trick to look up the # values for each object. # y = y.astype(float) / radius[labels] x = x.astype(float) / radius[labels] # ################################# # # ZERNIKE POLYNOMIALS # # Now we can get Zernike polynomials per-pixel where each pixel # value is calculated according to its object's MEC. # # We use a mask of all of the non-zero labels so the calculation # runs a little faster. # zernike_polynomial = construct_zernike_polynomials( x, y, np.array([[n, m]])) # # Multiply the Zernike polynomial by the image to dissect # the image by the Zernike basis set. # output_pixels = pixels * zernike_polynomial[:, :, 0] # # The zernike polynomial is a complex number. We get a power # spectrum here to combine the real and imaginary parts # output_pixels = np.sqrt(output_pixels * output_pixels.conjugate()) # # Finally, we use Scipy to sum the intensities. Scipy has different # versions with different quirks. The "fix" function takes all # of that into account. # # The sum function calculates the sum of the pixel values for # each pixel in an object, using the labels matrix to name # the pixels in an object # result = fix(scind.sum(output_pixels.real, labels, indexes)) # # And we're done! Did you like it? Did you get it? # return result
def make_neuron_graph(self, skeleton, skeleton_labels, trunks, branchpoints, endpoints, image): '''Make a table that captures the graph relationship of the skeleton skeleton - binary skeleton image + outline of seed objects skeleton_labels - labels matrix of skeleton trunks - binary image with trunk points as 1 branchpoints - binary image with branchpoints as 1 endpoints - binary image with endpoints as 1 image - image for intensity measurement returns two tables. Table 1: edge table The edge table is a numpy record array with the following named columns in the following order: v1: index into vertex table of first vertex of edge v2: index into vertex table of second vertex of edge length: # of intermediate pixels + 2 (for two vertices) total_intensity: sum of intensities along the edge Table 2: vertex table The vertex table is a numpy record array: i: I coordinate of the vertex j: J coordinate of the vertex label: the vertex's label kind: kind of vertex = "T" for trunk, "B" for branchpoint or "E" for endpoint. ''' i,j = np.mgrid[0:skeleton.shape[0], 0:skeleton.shape[1]] # # Give each point of interest a unique number # points_of_interest = trunks | branchpoints | endpoints number_of_points = np.sum(points_of_interest) # # Make up the vertex table # tbe = np.zeros(points_of_interest.shape, '|S1') tbe[trunks] = 'T' tbe[branchpoints] = 'B' tbe[endpoints] = 'E' i_idx = i[points_of_interest] j_idx = j[points_of_interest] poe_labels = skeleton_labels[points_of_interest] tbe = tbe[points_of_interest] vertex_table = { self.VF_I: i_idx, self.VF_J: j_idx, self.VF_LABELS: poe_labels, self.VF_KIND: tbe } # # First, break the skeleton by removing the branchpoints, endpoints # and trunks # broken_skeleton = skeleton & (~points_of_interest) # # Label the broken skeleton: this labels each edge differently # edge_labels, nlabels = morph.label_skeleton(skeleton) # # Reindex after removing the points of interest # edge_labels[points_of_interest] = 0 if nlabels > 0: indexer = np.arange(nlabels+1) unique_labels = np.sort(np.unique(edge_labels)) nlabels = len(unique_labels)-1 indexer[unique_labels] = np.arange(len(unique_labels)) edge_labels = indexer[edge_labels] # # find magnitudes and lengths for all edges # magnitudes = fix(scind.sum(image, edge_labels, np.arange(1, nlabels+1,dtype=np.int32))) lengths = fix(scind.sum(np.ones(edge_labels.shape), edge_labels, np.arange(1, nlabels+1,dtype=np.int32))).astype(int) else: magnitudes = np.zeros(0) lengths = np.zeros(0, int) # # combine the edge labels and indexes of points of interest with padding # edge_mask = edge_labels != 0 all_labels = np.zeros(np.array(edge_labels.shape)+2, int) all_labels[1:-1,1:-1][edge_mask] = edge_labels[edge_mask] + number_of_points all_labels[i_idx+1, j_idx+1] = np.arange(1, number_of_points+1) # # Collect all 8 neighbors for each point of interest # p1 = np.zeros(0,int) p2 = np.zeros(0,int) for i_off, j_off in ((0,0), (0,1), (0,2), (1,0), (1,2), (2,0), (2,1), (2,2)): p1 = np.hstack((p1, np.arange(1, number_of_points+1))) p2 = np.hstack((p2, all_labels[i_idx+i_off,j_idx+j_off])) # # Get rid of zeros which are background # p1 = p1[p2 != 0] p2 = p2[p2 != 0] # # Find point_of_interest -> point_of_interest connections. # p1_poi = p1[(p2 <= number_of_points) & (p1 < p2)] p2_poi = p2[(p2 <= number_of_points) & (p1 < p2)] # # Make sure matches are labeled the same # same_labels = (skeleton_labels[i_idx[p1_poi-1], j_idx[p1_poi-1]] == skeleton_labels[i_idx[p2_poi-1], j_idx[p2_poi-1]]) p1_poi = p1_poi[same_labels] p2_poi = p2_poi[same_labels] # # Find point_of_interest -> edge # p1_edge = p1[p2 > number_of_points] edge = p2[p2 > number_of_points] # # Now, each value that p2_edge takes forms a group and all # p1_edge whose p2_edge are connected together by the edge. # Possibly they touch each other without the edge, but we will # take the minimum distance connecting each pair to throw out # the edge. # edge, p1_edge, p2_edge = morph.pairwise_permutations(edge, p1_edge) indexer = edge - number_of_points - 1 lengths = lengths[indexer] magnitudes = magnitudes[indexer] # # OK, now we make the edge table. First poi<->poi. Length = 2, # magnitude = magnitude at each point # poi_length = np.ones(len(p1_poi)) * 2 poi_magnitude = (image[i_idx[p1_poi-1], j_idx[p1_poi-1]] + image[i_idx[p2_poi-1], j_idx[p2_poi-1]]) # # Now the edges... # poi_edge_length = lengths + 2 poi_edge_magnitude = (image[i_idx[p1_edge-1], j_idx[p1_edge-1]] + image[i_idx[p2_edge-1], j_idx[p2_edge-1]] + magnitudes) # # Put together the columns # v1 = np.hstack((p1_poi, p1_edge)) v2 = np.hstack((p2_poi, p2_edge)) lengths = np.hstack((poi_length, poi_edge_length)) magnitudes = np.hstack((poi_magnitude, poi_edge_magnitude)) # # Sort by p1, p2 and length in order to pick the shortest length # indexer = np.lexsort((lengths, v1, v2)) v1 = v1[indexer] v2 = v2[indexer] lengths = lengths[indexer] magnitudes = magnitudes[indexer] if len(v1) > 0: to_keep = np.hstack(([True], (v1[1:] != v1[:-1]) | (v2[1:] != v2[:-1]))) v1 = v1[to_keep] v2 = v2[to_keep] lengths = lengths[to_keep] magnitudes = magnitudes[to_keep] # # Put it all together into a table # edge_table = { self.EF_V1: v1, self.EF_V2: v2, self.EF_LENGTH: lengths, self.EF_TOTAL_INTENSITY: magnitudes } return edge_table, vertex_table
def run_on_image_setting(self, workspace, image): assert isinstance(workspace, cpw.Workspace) image_set = workspace.image_set measurements = workspace.measurements im = image_set.get_image(image.image_name.value, must_be_grayscale=True) # # Downsample the image and mask # new_shape = np.array(im.pixel_data.shape) if image.subsample_size.value < 1: new_shape = new_shape * image.subsample_size.value i, j = (np.mgrid[0:new_shape[0], 0:new_shape[1]].astype(float) / image.subsample_size.value) pixels = scind.map_coordinates(im.pixel_data, (i, j), order=1) mask = scind.map_coordinates(im.mask.astype(float), (i, j)) > .9 else: pixels = im.pixel_data mask = im.mask # # Remove background pixels using a greyscale tophat filter # if image.image_sample_size.value < 1: back_shape = new_shape * image.image_sample_size.value i, j = (np.mgrid[0:back_shape[0], 0:back_shape[1]].astype(float) / image.image_sample_size.value) back_pixels = scind.map_coordinates(pixels, (i, j), order=1) back_mask = scind.map_coordinates(mask.astype(float), (i, j)) > .9 else: back_pixels = pixels back_mask = mask radius = image.element_size.value back_pixels = morph.grey_erosion(back_pixels, radius, back_mask) back_pixels = morph.grey_dilation(back_pixels, radius, back_mask) if image.image_sample_size.value < 1: i, j = np.mgrid[0:new_shape[0], 0:new_shape[1]].astype(float) # # Make sure the mapping only references the index range of # back_pixels. # i *= float(back_shape[0] - 1) / float(new_shape[0] - 1) j *= float(back_shape[1] - 1) / float(new_shape[1] - 1) back_pixels = scind.map_coordinates(back_pixels, (i, j), order=1) pixels -= back_pixels pixels[pixels < 0] = 0 # # For each object, build a little record # class ObjectRecord(object): def __init__(self, name): self.name = name self.labels = workspace.object_set.get_objects(name).segmented self.nobjects = np.max(self.labels) if self.nobjects != 0: self.range = np.arange(1, np.max(self.labels) + 1) self.labels = self.labels.copy() self.labels[~im.mask] = 0 self.current_mean = fix( scind.mean(im.pixel_data, self.labels, self.range)) self.start_mean = np.maximum(self.current_mean, np.finfo(float).eps) object_records = [ ObjectRecord(ob.objects_name.value) for ob in image.objects ] # # Transcribed from the Matlab module: granspectr function # # CALCULATES GRANULAR SPECTRUM, ALSO KNOWN AS SIZE DISTRIBUTION, # GRANULOMETRY, AND PATTERN SPECTRUM, SEE REF.: # J.Serra, Image Analysis and Mathematical Morphology, Vol. 1. Academic Press, London, 1989 # Maragos,P. "Pattern spectrum and multiscale shape representation", IEEE Transactions on Pattern Analysis and Machine Intelligence, 11, N 7, pp. 701-716, 1989 # L.Vincent "Granulometries and Opening Trees", Fundamenta Informaticae, 41, No. 1-2, pp. 57-90, IOS Press, 2000. # L.Vincent "Morphological Area Opening and Closing for Grayscale Images", Proc. NATO Shape in Picture Workshop, Driebergen, The Netherlands, pp. 197-208, 1992. # I.Ravkin, V.Temov "Bit representation techniques and image processing", Applied Informatics, v.14, pp. 41-90, Finances and Statistics, Moskow, 1988 (in Russian) # THIS IMPLEMENTATION INSTEAD OF OPENING USES EROSION FOLLOWED BY RECONSTRUCTION # ng = image.granular_spectrum_length.value startmean = np.mean(pixels[mask]) ero = pixels.copy() # Mask the test image so that masked pixels will have no effect # during reconstruction # ero[~mask] = 0 currentmean = startmean startmean = max(startmean, np.finfo(float).eps) footprint = np.array([[False, True, False], [True, True, True], [False, True, False]]) statistics = [image.image_name.value] for i in range(1, ng + 1): prevmean = currentmean ero = morph.grey_erosion(ero, mask=mask, footprint=footprint) rec = morph.grey_reconstruction(ero, pixels, footprint) currentmean = np.mean(rec[mask]) gs = (prevmean - currentmean) * 100 / startmean statistics += ["%.2f" % gs] feature = image.granularity_feature(i) measurements.add_image_measurement(feature, gs) # # Restore the reconstructed image to the shape of the # original image so we can match against object labels # orig_shape = im.pixel_data.shape i, j = np.mgrid[0:orig_shape[0], 0:orig_shape[1]].astype(float) # # Make sure the mapping only references the index range of # back_pixels. # i *= float(new_shape[0] - 1) / float(orig_shape[0] - 1) j *= float(new_shape[1] - 1) / float(orig_shape[1] - 1) rec = scind.map_coordinates(rec, (i, j), order=1) # # Calculate the means for the objects # for object_record in object_records: assert isinstance(object_record, ObjectRecord) if object_record.nobjects > 0: new_mean = fix( scind.mean(rec, object_record.labels, object_record.range)) gss = ((object_record.current_mean - new_mean) * 100 / object_record.start_mean) object_record.current_mean = new_mean else: gss = np.zeros((0, )) measurements.add_measurement(object_record.name, feature, gss) return statistics
def run(self, workspace): assert isinstance(workspace, cpw.Workspace) image_name = self.image_name.value image = workspace.image_set.get_image(image_name, must_be_grayscale = True) workspace.display_data.statistics = [] img = image.pixel_data mask = image.mask objects = workspace.object_set.get_objects(self.primary_objects.value) global_threshold = None if self.method == M_DISTANCE_N: has_threshold = False else: thresholded_image = self.threshold_image(image_name, workspace) has_threshold = True # # Get the following labels: # * all edited labels # * labels touching the edge, including small removed # labels_in = objects.unedited_segmented.copy() labels_touching_edge = np.hstack( (labels_in[0,:], labels_in[-1,:], labels_in[:,0], labels_in[:,-1])) labels_touching_edge = np.unique(labels_touching_edge) is_touching = np.zeros(np.max(labels_in)+1, bool) is_touching[labels_touching_edge] = True is_touching = is_touching[labels_in] labels_in[(~ is_touching) & (objects.segmented == 0)] = 0 # # Stretch the input labels to match the image size. If there's no # label matrix, then there's no label in that area. # if tuple(labels_in.shape) != tuple(img.shape): tmp = np.zeros(img.shape, labels_in.dtype) i_max = min(img.shape[0], labels_in.shape[0]) j_max = min(img.shape[1], labels_in.shape[1]) tmp[:i_max, :j_max] = labels_in[:i_max, :j_max] labels_in = tmp if self.method in (M_DISTANCE_B, M_DISTANCE_N): if self.method == M_DISTANCE_N: distances,(i,j) = scind.distance_transform_edt(labels_in == 0, return_indices = True) labels_out = np.zeros(labels_in.shape,int) dilate_mask = distances <= self.distance_to_dilate.value labels_out[dilate_mask] =\ labels_in[i[dilate_mask],j[dilate_mask]] else: labels_out, distances = propagate(img, labels_in, thresholded_image, 1.0) labels_out[distances>self.distance_to_dilate.value] = 0 labels_out[labels_in > 0] = labels_in[labels_in>0] if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out # # Create the final output labels by removing labels in the # output matrix that are missing from the segmented image # segmented_labels = objects.segmented segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) elif self.method == M_PROPAGATION: labels_out, distance = propagate(img, labels_in, thresholded_image, self.regularization_factor.value) if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out.copy() segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) elif self.method == M_WATERSHED_G: # # First, apply the sobel filter to the image (both horizontal # and vertical). The filter measures gradient. # sobel_image = np.abs(scind.sobel(img)) # # Combine the image mask and threshold to mask the watershed # watershed_mask = np.logical_or(thresholded_image, labels_in > 0) watershed_mask = np.logical_and(watershed_mask, mask) # # Perform the first watershed # labels_out = watershed(sobel_image, labels_in, np.ones((3,3),bool), mask=watershed_mask) if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out.copy() segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) elif self.method == M_WATERSHED_I: # # invert the image so that the maxima are filled first # and the cells compete over what's close to the threshold # inverted_img = 1-img # # Same as above, but perform the watershed on the original image # watershed_mask = np.logical_or(thresholded_image, labels_in > 0) watershed_mask = np.logical_and(watershed_mask, mask) # # Perform the watershed # labels_out = watershed(inverted_img, labels_in, np.ones((3,3),bool), mask=watershed_mask) if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) if self.wants_discard_edge and self.wants_discard_primary: # # Make a new primary object # lookup = scind.maximum(segmented_out, objects.segmented, range(np.max(objects.segmented)+1)) lookup = fix(lookup) lookup[0] = 0 lookup[lookup != 0] = np.arange(np.sum(lookup != 0)) + 1 segmented_labels = lookup[objects.segmented] segmented_out = lookup[segmented_out] new_objects = cpo.Objects() new_objects.segmented = segmented_labels if objects.has_unedited_segmented: new_objects.unedited_segmented = objects.unedited_segmented if objects.has_small_removed_segmented: new_objects.small_removed_segmented = objects.small_removed_segmented new_objects.parent_image = objects.parent_image primary_outline = outline(segmented_labels) if self.wants_primary_outlines: out_img = cpi.Image(primary_outline.astype(bool), parent_image = image) workspace.image_set.add(self.new_primary_outlines_name.value, out_img) else: primary_outline = outline(objects.segmented) secondary_outline = outline(segmented_out) # # Add the objects to the object set # objects_out = cpo.Objects() objects_out.unedited_segmented = small_removed_segmented_out objects_out.small_removed_segmented = small_removed_segmented_out objects_out.segmented = segmented_out objects_out.parent_image = image objname = self.objects_name.value workspace.object_set.add_objects(objects_out, objname) if self.use_outlines.value: out_img = cpi.Image(secondary_outline.astype(bool), parent_image = image) workspace.image_set.add(self.outlines_name.value, out_img) object_count = np.max(segmented_out) # # Add measurements # measurements = workspace.measurements cpmi.add_object_count_measurements(measurements, objname, object_count) cpmi.add_object_location_measurements(measurements, objname, segmented_out) # # Relate the secondary objects to the primary ones and record # the relationship. # children_per_parent, parents_of_children = \ objects.relate_children(objects_out) measurements.add_measurement(self.primary_objects.value, cpmi.FF_CHILDREN_COUNT%objname, children_per_parent) measurements.add_measurement(objname, cpmi.FF_PARENT%self.primary_objects.value, parents_of_children) image_numbers = np.ones(len(parents_of_children), int) *\ measurements.image_set_number mask = parents_of_children > 0 measurements.add_relate_measurement( self.module_num, R_PARENT, self.primary_objects.value, self.objects_name.value, image_numbers[mask], parents_of_children[mask], image_numbers[mask], np.arange(1, len(parents_of_children) + 1)[mask]) # # If primary objects were created, add them # if self.wants_discard_edge and self.wants_discard_primary: workspace.object_set.add_objects(new_objects, self.new_primary_objects_name.value) cpmi.add_object_count_measurements(measurements, self.new_primary_objects_name.value, np.max(new_objects.segmented)) cpmi.add_object_location_measurements(measurements, self.new_primary_objects_name.value, new_objects.segmented) for parent_objects, parent_name, child_objects, child_name in ( (objects, self.primary_objects.value, new_objects, self.new_primary_objects_name.value), (new_objects, self.new_primary_objects_name.value, objects_out, objname)): children_per_parent, parents_of_children = \ parent_objects.relate_children(child_objects) measurements.add_measurement(parent_name, cpmi.FF_CHILDREN_COUNT%child_name, children_per_parent) measurements.add_measurement(child_name, cpmi.FF_PARENT%parent_name, parents_of_children) if self.show_window: object_area = np.sum(segmented_out > 0) workspace.display_data.object_pct = \ 100 * object_area / np.product(segmented_out.shape) workspace.display_data.img = img workspace.display_data.segmented_out = segmented_out workspace.display_data.primary_outline = primary_outline workspace.display_data.secondary_outline = secondary_outline workspace.display_data.global_threshold = global_threshold workspace.display_data.object_count = object_count
def do_measurements(self, workspace, image_name, object_name, center_object_name, center_choice, bin_count_settings, dd): '''Perform the radial measurements on the image set workspace - workspace that holds images / objects image_name - make measurements on this image object_name - make measurements on these objects center_object_name - use the centers of these related objects as the centers for radial measurements. None to use the objects themselves. center_choice - the user's center choice for this object: C_SELF, C_CENTERS_OF_OBJECTS or C_EDGES_OF_OBJECTS. bin_count_settings - the bin count settings group d - a dictionary for saving reusable partial results returns one statistics tuple per ring. ''' assert isinstance(workspace, cpw.Workspace) assert isinstance(workspace.object_set, cpo.ObjectSet) bin_count = bin_count_settings.bin_count.value wants_scaled = bin_count_settings.wants_scaled.value maximum_radius = bin_count_settings.maximum_radius.value image = workspace.image_set.get_image(image_name, must_be_grayscale=True) objects = workspace.object_set.get_objects(object_name) labels, pixel_data = cpo.crop_labels_and_image(objects.segmented, image.pixel_data) nobjects = np.max(objects.segmented) measurements = workspace.measurements assert isinstance(measurements, cpmeas.Measurements) if nobjects == 0: for bin in range(1, bin_count + 1): for feature in (F_FRAC_AT_D, F_MEAN_FRAC, F_RADIAL_CV): feature_name = ((feature + FF_GENERIC) % (image_name, bin, bin_count)) measurements.add_measurement( object_name, "_".join([M_CATEGORY, feature_name]), np.zeros(0)) if not wants_scaled: measurement_name = "_".join( [M_CATEGORY, feature, image_name, FF_OVERFLOW]) measurements.add_measurement(object_name, measurement_name, np.zeros(0)) return [(image_name, object_name, "no objects", "-", "-", "-", "-") ] name = (object_name if center_object_name is None else "%s_%s" % (object_name, center_object_name)) if dd.has_key(name): normalized_distance, i_center, j_center, good_mask = dd[name] else: d_to_edge = distance_to_edge(labels) if center_object_name is not None: # # Use the center of the centering objects to assign a center # to each labeled pixel using propagation # center_objects = workspace.object_set.get_objects( center_object_name) center_labels, cmask = cpo.size_similarly( labels, center_objects.segmented) pixel_counts = fix( scind.sum( np.ones(center_labels.shape), center_labels, np.arange(1, np.max(center_labels) + 1, dtype=np.int32))) good = pixel_counts > 0 i, j = (centers_of_labels(center_labels) + .5).astype(int) ig = i[good] jg = j[good] lg = np.arange(1, len(i) + 1)[good] if center_choice == C_CENTERS_OF_OTHER: # # Reduce the propagation labels to the centers of # the centering objects # center_labels = np.zeros(center_labels.shape, int) center_labels[ig, jg] = lg cl, d_from_center = propagate(np.zeros(center_labels.shape), center_labels, labels != 0, 1) # # Erase the centers that fall outside of labels # cl[labels == 0] = 0 # # If objects are hollow or crescent-shaped, there may be # objects without center labels. As a backup, find the # center that is the closest to the center of mass. # missing_mask = (labels != 0) & (cl == 0) missing_labels = np.unique(labels[missing_mask]) if len(missing_labels): all_centers = centers_of_labels(labels) missing_i_centers, missing_j_centers = \ all_centers[:, missing_labels-1] di = missing_i_centers[:, np.newaxis] - ig[np.newaxis, :] dj = missing_j_centers[:, np.newaxis] - jg[np.newaxis, :] missing_best = lg[np.argsort((di * di + dj * dj, ))[:, 0]] best = np.zeros(np.max(labels) + 1, int) best[missing_labels] = missing_best cl[missing_mask] = best[labels[missing_mask]] # # Now compute the crow-flies distance to the centers # of these pixels from whatever center was assigned to # the object. # iii, jjj = np.mgrid[0:labels.shape[0], 0:labels.shape[1]] di = iii[missing_mask] - i[cl[missing_mask] - 1] dj = jjj[missing_mask] - j[cl[missing_mask] - 1] d_from_center[missing_mask] = np.sqrt(di * di + dj * dj) else: # Find the point in each object farthest away from the edge. # This does better than the centroid: # * The center is within the object # * The center tends to be an interesting point, like the # center of the nucleus or the center of one or the other # of two touching cells. # i, j = maximum_position_of_labels(d_to_edge, labels, objects.indices) center_labels = np.zeros(labels.shape, int) center_labels[i, j] = labels[i, j] # # Use the coloring trick here to process touching objects # in separate operations # colors = color_labels(labels) ncolors = np.max(colors) d_from_center = np.zeros(labels.shape) cl = np.zeros(labels.shape, int) for color in range(1, ncolors + 1): mask = colors == color l, d = propagate(np.zeros(center_labels.shape), center_labels, mask, 1) d_from_center[mask] = d[mask] cl[mask] = l[mask] good_mask = cl > 0 if center_choice == C_EDGES_OF_OTHER: # Exclude pixels within the centering objects # when performing calculations from the centers good_mask = good_mask & (center_labels == 0) i_center = np.zeros(cl.shape) i_center[good_mask] = i[cl[good_mask] - 1] j_center = np.zeros(cl.shape) j_center[good_mask] = j[cl[good_mask] - 1] normalized_distance = np.zeros(labels.shape) if wants_scaled: total_distance = d_from_center + d_to_edge normalized_distance[good_mask] = ( d_from_center[good_mask] / (total_distance[good_mask] + .001)) else: normalized_distance[good_mask] = \ d_from_center[good_mask] / maximum_radius dd[name] = [normalized_distance, i_center, j_center, good_mask] ngood_pixels = np.sum(good_mask) good_labels = labels[good_mask] bin_indexes = (normalized_distance * bin_count).astype(int) bin_indexes[bin_indexes > bin_count] = bin_count labels_and_bins = (good_labels - 1, bin_indexes[good_mask]) histogram = coo_matrix((pixel_data[good_mask], labels_and_bins), (nobjects, bin_count + 1)).toarray() sum_by_object = np.sum(histogram, 1) sum_by_object_per_bin = np.dstack([sum_by_object] * (bin_count + 1))[0] fraction_at_distance = histogram / sum_by_object_per_bin number_at_distance = coo_matrix( (np.ones(ngood_pixels), labels_and_bins), (nobjects, bin_count + 1)).toarray() object_mask = number_at_distance > 0 sum_by_object = np.sum(number_at_distance, 1) sum_by_object_per_bin = np.dstack([sum_by_object] * (bin_count + 1))[0] fraction_at_bin = number_at_distance / sum_by_object_per_bin mean_pixel_fraction = fraction_at_distance / (fraction_at_bin + np.finfo(float).eps) masked_fraction_at_distance = masked_array(fraction_at_distance, ~object_mask) masked_mean_pixel_fraction = masked_array(mean_pixel_fraction, ~object_mask) # Anisotropy calculation. Split each cell into eight wedges, then # compute coefficient of variation of the wedges' mean intensities # in each ring. # # Compute each pixel's delta from the center object's centroid i, j = np.mgrid[0:labels.shape[0], 0:labels.shape[1]] imask = i[good_mask] > i_center[good_mask] jmask = j[good_mask] > j_center[good_mask] absmask = (abs(i[good_mask] - i_center[good_mask]) > abs(j[good_mask] - j_center[good_mask])) radial_index = (imask.astype(int) + jmask.astype(int) * 2 + absmask.astype(int) * 4) statistics = [] for bin in range(bin_count + (0 if wants_scaled else 1)): bin_mask = (good_mask & (bin_indexes == bin)) bin_pixels = np.sum(bin_mask) bin_labels = labels[bin_mask] bin_radial_index = radial_index[bin_indexes[good_mask] == bin] labels_and_radii = (bin_labels - 1, bin_radial_index) radial_values = coo_matrix( (pixel_data[bin_mask], labels_and_radii), (nobjects, 8)).toarray() pixel_count = coo_matrix((np.ones(bin_pixels), labels_and_radii), (nobjects, 8)).toarray() mask = pixel_count == 0 radial_means = masked_array(radial_values / pixel_count, mask) radial_cv = np.std(radial_means, 1) / np.mean(radial_means, 1) radial_cv[np.sum(~mask, 1) == 0] = 0 for measurement, feature, overflow_feature in ( (fraction_at_distance[:, bin], MF_FRAC_AT_D, OF_FRAC_AT_D), (mean_pixel_fraction[:, bin], MF_MEAN_FRAC, OF_MEAN_FRAC), (np.array(radial_cv), MF_RADIAL_CV, OF_RADIAL_CV)): if bin == bin_count: measurement_name = overflow_feature % image_name else: measurement_name = feature % (image_name, bin + 1, bin_count) measurements.add_measurement(object_name, measurement_name, measurement) radial_cv.mask = np.sum(~mask, 1) == 0 bin_name = str(bin + 1) if bin < bin_count else "Overflow" statistics += [(image_name, object_name, bin_name, str(bin_count), round(np.mean(masked_fraction_at_distance[:, bin]), 4), round(np.mean(masked_mean_pixel_fraction[:, bin]), 4), round(np.mean(radial_cv), 4))] return statistics
def measure_zernike(self, pixels, labels, indexes, centers, radius, n, m): # I'll put some documentation in here to explain what it does. # If someone ever wants to call it, their editor might display # the documentation. '''Measure the intensity of the image with Zernike (N, M) pixels - the intensity image to be measured labels - the labels matrix that labels each object with an integer indexes - the label #s in the image centers - the centers of the minimum enclosing circle for each object radius - the radius of the minimum enclosing circle for each object n, m - the Zernike coefficients. See http://en.wikipedia.org/wiki/Zernike_polynomials for an explanation of the Zernike polynomials ''' # # The strategy here is to operate on the whole array instead # of operating on one object at a time. The most important thing # is to avoid having to run the Python interpreter once per pixel # in the image and the second most important is to avoid running # it per object in case there are hundreds of objects. # # We play lots of indexing tricks here to operate on the whole image. # I'll try to explain some - hopefully, you can reuse. center_x = centers[:, 1] center_y = centers[:, 0] # # Make up fake values for 0 (the background). This lets us do our # indexing tricks. Really, we're going to ignore the background, # but we want to do the indexing without ignoring the background # because that's easier. # center_x = np.hstack([[0], center_x]) center_y = np.hstack([[0], center_y]) radius = np.hstack([[1], radius]) # # Now get one array that's the y coordinate of each pixel and one # that's the x coordinate. This might look stupid and wasteful, # but these "arrays" are never actually realized and made into # real memory. # y, x = np.mgrid[0:labels.shape[0], 0:labels.shape[1]] # # Get the x and y coordinates relative to the object centers. # This uses Numpy broadcasting. For each pixel, we use the # value in the labels matrix as an index into the appropriate # one-dimensional array. So we get the value for that object. # y -= center_y[labels] x -= center_x[labels] # # Zernikes take x and y values from zero to one. We scale the # integer coordinate values by dividing them by the radius of # the circle. Again, we use the indexing trick to look up the # values for each object. # y = y.astype(float) / radius[labels] x = x.astype(float) / radius[labels] # ################################# # # ZERNIKE POLYNOMIALS # # Now we can get Zernike polynomials per-pixel where each pixel # value is calculated according to its object's MEC. # # We use a mask of all of the non-zero labels so the calculation # runs a little faster. # zernike_polynomial = construct_zernike_polynomials( x, y, np.array([ [ n, m ]]), labels > 0) # # For historical reasons, CellProfiler didn't multiply by the per/zernike # normalizing factor: 2*n + 2 / E / pi where E is 2 if m is zero and 1 # if m is one. We do it here to aid with the reconstruction # zernike_polynomial *= (2*n + 2) / (2 if m == 0 else 1) / np.pi # # Multiply the Zernike polynomial by the image to dissect # the image by the Zernike basis set. # output_pixels = pixels * zernike_polynomial[:,:,0] # # Finally, we use Scipy to sum the intensities. Scipy has different # versions with different quirks. The "fix" function takes all # of that into account. # # The sum function calculates the sum of the pixel values for # each pixel in an object, using the labels matrix to name # the pixels in an object # zr = fix(scind.sum(output_pixels.real, labels, indexes)) zi = fix(scind.sum(output_pixels.imag, labels, indexes)) # # And we're done! Did you like it? Did you get it? # return zr, zi
def run(self, workspace): '''Run the algorithm on one image set''' # # Get the image as a binary image # image_set = workspace.image_set image = image_set.get_image(self.image_name.value, must_be_binary=True) mask = image.pixel_data if image.has_mask: mask = mask & image.mask angle_count = self.angle_count.value # # We collect the i,j and angle of pairs of points that # are 3-d adjacent after erosion. # # i - the i coordinate of each point found after erosion # j - the j coordinate of each point found after erosion # a - the angle of the structuring element for each point found # i = np.zeros(0, int) j = np.zeros(0, int) a = np.zeros(0, int) ig, jg = np.mgrid[0:mask.shape[0], 0:mask.shape[1]] this_idx = 0 for angle_number in range(angle_count): angle = float(angle_number) * np.pi / float(angle_count) strel = self.get_diamond(angle) erosion = binary_erosion(mask, strel) # # Accumulate the count, i, j and angle for all foreground points # in the erosion # this_count = np.sum(erosion) i = np.hstack((i, ig[erosion])) j = np.hstack((j, jg[erosion])) a = np.hstack((a, np.ones(this_count, float) * angle)) # # Find connections based on distances, not adjacency # first, second = self.find_adjacent_by_distance(i, j, a) # # Do all connected components. # if len(first) > 0: ij_labels = all_connected_components(first, second) + 1 nlabels = np.max(ij_labels) label_indexes = np.arange(1, nlabels + 1) # # Compute the measurements # center_x = fix(mean_of_labels(j, ij_labels, label_indexes)) center_y = fix(mean_of_labels(i, ij_labels, label_indexes)) # # The angles are wierdly complicated because of the wrap-around. # You can imagine some horrible cases, like a circular patch of # "worm" in which all angles are represented or a gentle "U" # curve. # # For now, I'm going to use the following heuristic: # # Compute two different "angles". The angles of one go # from 0 to 180 and the angles of the other go from -90 to 90. # Take the variance of these from the mean and # choose the representation with the lowest variance. # # An alternative would be to compute the variance at each possible # dividing point. Another alternative would be to actually trace through # the connected components - both overkill for such an inconsequential # measurement I hope. # angles = fix(mean_of_labels(a, ij_labels, label_indexes)) vangles = fix( mean_of_labels((a - angles[ij_labels - 1])**2, ij_labels, label_indexes)) aa = a.copy() aa[a > np.pi / 2] -= np.pi aangles = fix(mean_of_labels(aa, ij_labels, label_indexes)) vaangles = fix( mean_of_labels((aa - aangles[ij_labels - 1])**2, ij_labels, label_indexes)) aangles[aangles < 0] += np.pi angles[vaangles < vangles] = aangles[vaangles < vangles] # # Squish the labels to 2-d. The labels for overlaps are arbitrary. # labels = np.zeros(mask.shape, int) labels[i, j] = ij_labels else: center_x = np.zeros(0, int) center_y = np.zeros(0, int) angles = np.zeros(0) nlabels = 0 label_indexes = np.zeros(0, int) labels = np.zeros(mask.shape, int) m = workspace.measurements assert isinstance(m, cpmeas.Measurements) object_name = self.object_name.value m.add_measurement(object_name, I.M_LOCATION_CENTER_X, center_x) m.add_measurement(object_name, I.M_LOCATION_CENTER_Y, center_y) m.add_measurement(object_name, M_ANGLE, angles * 180 / np.pi) m.add_measurement(object_name, I.M_NUMBER_OBJECT_NUMBER, label_indexes) m.add_image_measurement(I.FF_COUNT % object_name, nlabels) # # Make the objects # object_set = workspace.object_set assert isinstance(object_set, cpo.ObjectSet) objects = cpo.Objects() objects.segmented = labels objects.parent_image = image object_set.add_objects(objects, object_name) if self.show_window: workspace.display_data.i = center_y workspace.display_data.j = center_x workspace.display_data.angle = angles workspace.display_data.mask = mask workspace.display_data.labels = labels workspace.display_data.count = nlabels
def run(self, workspace): '''Run the algorithm on one image set''' # # Get the image as a binary image # image_set = workspace.image_set image = image_set.get_image(self.image_name.value, must_be_binary = True) mask = image.pixel_data if image.has_mask: mask = mask & image.mask angle_count = self.angle_count.value # # We collect the i,j and angle of pairs of points that # are 3-d adjacent after erosion. # # i - the i coordinate of each point found after erosion # j - the j coordinate of each point found after erosion # a - the angle of the structuring element for each point found # i = np.zeros(0, int) j = np.zeros(0, int) a = np.zeros(0, int) ig, jg = np.mgrid[0:mask.shape[0], 0:mask.shape[1]] this_idx = 0 for angle_number in range(angle_count): angle = float(angle_number) * np.pi / float(angle_count) strel = self.get_diamond(angle) erosion = binary_erosion(mask, strel) # # Accumulate the count, i, j and angle for all foreground points # in the erosion # this_count = np.sum(erosion) i = np.hstack((i, ig[erosion])) j = np.hstack((j, jg[erosion])) a = np.hstack((a, np.ones(this_count, float) * angle)) # # Find connections based on distances, not adjacency # first, second = self.find_adjacent_by_distance(i,j,a) # # Do all connected components. # if len(first) > 0: ij_labels = all_connected_components(first, second) + 1 nlabels = np.max(ij_labels) label_indexes = np.arange(1, nlabels + 1) # # Compute the measurements # center_x = fix(mean_of_labels(j, ij_labels, label_indexes)) center_y = fix(mean_of_labels(i, ij_labels, label_indexes)) # # The angles are wierdly complicated because of the wrap-around. # You can imagine some horrible cases, like a circular patch of # "worm" in which all angles are represented or a gentle "U" # curve. # # For now, I'm going to use the following heuristic: # # Compute two different "angles". The angles of one go # from 0 to 180 and the angles of the other go from -90 to 90. # Take the variance of these from the mean and # choose the representation with the lowest variance. # # An alternative would be to compute the variance at each possible # dividing point. Another alternative would be to actually trace through # the connected components - both overkill for such an inconsequential # measurement I hope. # angles = fix(mean_of_labels(a, ij_labels, label_indexes)) vangles = fix(mean_of_labels((a - angles[ij_labels-1])**2, ij_labels, label_indexes)) aa = a.copy() aa[a > np.pi / 2] -= np.pi aangles = fix(mean_of_labels(aa, ij_labels, label_indexes)) vaangles = fix(mean_of_labels((aa-aangles[ij_labels-1])**2, ij_labels, label_indexes)) aangles[aangles < 0] += np.pi angles[vaangles < vangles] = aangles[vaangles < vangles] # # Squish the labels to 2-d. The labels for overlaps are arbitrary. # labels = np.zeros(mask.shape, int) labels[i, j] = ij_labels else: center_x = np.zeros(0, int) center_y = np.zeros(0, int) angles = np.zeros(0) nlabels = 0 label_indexes = np.zeros(0, int) labels = np.zeros(mask.shape, int) m = workspace.measurements assert isinstance(m, cpmeas.Measurements) object_name = self.object_name.value m.add_measurement(object_name, I.M_LOCATION_CENTER_X, center_x) m.add_measurement(object_name, I.M_LOCATION_CENTER_Y, center_y) m.add_measurement(object_name, M_ANGLE, angles * 180 / np.pi) m.add_measurement(object_name, I.M_NUMBER_OBJECT_NUMBER, label_indexes) m.add_image_measurement(I.FF_COUNT % object_name, nlabels) # # Make the objects # object_set = workspace.object_set assert isinstance(object_set, cpo.ObjectSet) objects = cpo.Objects() objects.segmented = labels objects.parent_image = image object_set.add_objects(objects, object_name) if self.show_window: workspace.display_data.i = center_y workspace.display_data.j = center_x workspace.display_data.angle = angles workspace.display_data.mask = mask workspace.display_data.labels = labels workspace.display_data.count = nlabels
def run(self, workspace): '''Run the module on an image set''' object_name = self.object_name.value remaining_object_name = self.remaining_objects.value original_objects = workspace.object_set.get_objects(object_name) if self.mask_choice == MC_IMAGE: mask = workspace.image_set.get_image(self.masking_image.value, must_be_binary=True) mask = mask.pixel_data else: masking_objects = workspace.object_set.get_objects( self.masking_objects.value) mask = masking_objects.segmented > 0 if self.wants_inverted_mask: mask = ~mask # # Load the labels # labels = original_objects.segmented.copy() nobjects = np.max(labels) # # Resize the mask to cover the objects # mask, m1 = cpo.size_similarly(labels, mask) mask[~m1] = False # # Apply the mask according to the overlap choice. # if nobjects == 0: pass elif self.overlap_choice == P_MASK: labels = labels * mask else: pixel_counts = fix( scind.sum(mask, labels, np.arange(1, nobjects + 1, dtype=np.int32))) if self.overlap_choice == P_KEEP: keep = pixel_counts > 0 else: total_pixels = fix( scind.sum(np.ones(labels.shape), labels, np.arange(1, nobjects + 1, dtype=np.int32))) if self.overlap_choice == P_REMOVE: keep = pixel_counts == total_pixels elif self.overlap_choice == P_REMOVE_PERCENTAGE: fraction = self.overlap_fraction.value keep = pixel_counts / total_pixels >= fraction else: raise NotImplementedError( "Unknown overlap-handling choice: %s", self.overlap_choice.value) keep = np.hstack(([False], keep)) labels[~keep[labels]] = 0 # # Renumber the labels matrix if requested # if self.retain_or_renumber == R_RENUMBER: unique_labels = np.unique(labels[labels != 0]) indexer = np.zeros(nobjects + 1, int) indexer[unique_labels] = np.arange(1, len(unique_labels) + 1) labels = indexer[labels] parent_objects = unique_labels else: parent_objects = np.arange(1, nobjects + 1) # # Add the objects # remaining_objects = cpo.Objects() remaining_objects.segmented = labels remaining_objects.unedited_segmented = original_objects.unedited_segmented workspace.object_set.add_objects(remaining_objects, remaining_object_name) # # Add measurements # m = workspace.measurements m.add_measurement(remaining_object_name, I.FF_PARENT % object_name, parent_objects) if np.max(original_objects.segmented) == 0: child_count = np.array([], int) else: child_count = fix( scind.sum(labels, original_objects.segmented, np.arange(1, nobjects + 1, dtype=np.int32))) child_count = (child_count > 0).astype(int) m.add_measurement(object_name, I.FF_CHILDREN_COUNT % remaining_object_name, child_count) if self.retain_or_renumber == R_RETAIN: remaining_object_count = nobjects else: remaining_object_count = len(unique_labels) I.add_object_count_measurements(m, remaining_object_name, remaining_object_count) I.add_object_location_measurements(m, remaining_object_name, labels) # # Add an outline if asked to do so # if self.wants_outlines.value: outline_image = cpi.Image( outline(labels) > 0, parent_image=original_objects.parent_image) workspace.image_set.add(self.outlines_name.value, outline_image) # # Save the input, mask and output images for display # if self.show_window: workspace.display_data.original_labels = original_objects.segmented workspace.display_data.final_labels = labels workspace.display_data.mask = mask
def run_on_image_setting(self, workspace, image): assert isinstance(workspace, cpw.Workspace) image_set = workspace.image_set measurements = workspace.measurements im = image_set.get_image(image.image_name.value, must_be_grayscale=True) # # Downsample the image and mask # new_shape = np.array(im.pixel_data.shape) if image.subsample_size.value < 1: new_shape = new_shape * image.subsample_size.value i,j = (np.mgrid[0:new_shape[0],0:new_shape[1]].astype(float) / image.subsample_size.value) pixels = scind.map_coordinates(im.pixel_data,(i,j),order=1) mask = scind.map_coordinates(im.mask.astype(float), (i,j)) > .9 else: pixels = im.pixel_data mask = im.mask # # Remove background pixels using a greyscale tophat filter # if image.image_sample_size.value < 1: back_shape = new_shape * image.image_sample_size.value i,j = (np.mgrid[0:back_shape[0],0:back_shape[1]].astype(float) / image.image_sample_size.value) back_pixels = scind.map_coordinates(pixels,(i,j), order=1) back_mask = scind.map_coordinates(mask.astype(float), (i,j)) > .9 else: back_pixels = pixels back_mask = mask radius = image.element_size.value back_pixels = morph.grey_erosion(back_pixels, radius, back_mask) back_pixels = morph.grey_dilation(back_pixels, radius, back_mask) if image.image_sample_size.value < 1: i,j = np.mgrid[0:new_shape[0],0:new_shape[1]].astype(float) # # Make sure the mapping only references the index range of # back_pixels. # i *= float(back_shape[0]-1)/float(new_shape[0]-1) j *= float(back_shape[1]-1)/float(new_shape[1]-1) back_pixels = scind.map_coordinates(back_pixels,(i,j), order=1) pixels -= back_pixels pixels[pixels < 0] = 0 # # For each object, build a little record # class ObjectRecord(object): def __init__(self, name): self.name = name self.labels = workspace.object_set.get_objects(name).segmented self.nobjects = np.max(self.labels) if self.nobjects != 0: self.range = np.arange(1, np.max(self.labels)+1) self.labels = self.labels.copy() self.labels[~ im.mask] = 0 self.current_mean = fix( scind.mean(im.pixel_data, self.labels, self.range)) self.start_mean = np.maximum( self.current_mean, np.finfo(float).eps) object_records = [ObjectRecord(ob.objects_name.value) for ob in image.objects ] # # Transcribed from the Matlab module: granspectr function # # CALCULATES GRANULAR SPECTRUM, ALSO KNOWN AS SIZE DISTRIBUTION, # GRANULOMETRY, AND PATTERN SPECTRUM, SEE REF.: # J.Serra, Image Analysis and Mathematical Morphology, Vol. 1. Academic Press, London, 1989 # Maragos,P. "Pattern spectrum and multiscale shape representation", IEEE Transactions on Pattern Analysis and Machine Intelligence, 11, N 7, pp. 701-716, 1989 # L.Vincent "Granulometries and Opening Trees", Fundamenta Informaticae, 41, No. 1-2, pp. 57-90, IOS Press, 2000. # L.Vincent "Morphological Area Opening and Closing for Grayscale Images", Proc. NATO Shape in Picture Workshop, Driebergen, The Netherlands, pp. 197-208, 1992. # I.Ravkin, V.Temov "Bit representation techniques and image processing", Applied Informatics, v.14, pp. 41-90, Finances and Statistics, Moskow, 1988 (in Russian) # THIS IMPLEMENTATION INSTEAD OF OPENING USES EROSION FOLLOWED BY RECONSTRUCTION # ng = image.granular_spectrum_length.value startmean = np.mean(pixels[mask]) ero = pixels.copy() # Mask the test image so that masked pixels will have no effect # during reconstruction # ero[~mask] = 0 currentmean = startmean startmean = max(startmean, np.finfo(float).eps) footprint = np.array([[False,True,False], [True ,True,True], [False,True,False]]) statistics = [ image.image_name.value] for i in range(1,ng+1): prevmean = currentmean ero = morph.grey_erosion(ero, mask = mask, footprint=footprint) rec = morph.grey_reconstruction(ero, pixels, footprint) currentmean = np.mean(rec[mask]) gs = (prevmean - currentmean) * 100 / startmean statistics += [ "%.2f"%gs] feature = image.granularity_feature(i) measurements.add_image_measurement(feature, gs) # # Restore the reconstructed image to the shape of the # original image so we can match against object labels # orig_shape = im.pixel_data.shape i,j = np.mgrid[0:orig_shape[0],0:orig_shape[1]].astype(float) # # Make sure the mapping only references the index range of # back_pixels. # i *= float(new_shape[0]-1)/float(orig_shape[0]-1) j *= float(new_shape[1]-1)/float(orig_shape[1]-1) rec = scind.map_coordinates(rec,(i,j), order=1) # # Calculate the means for the objects # for object_record in object_records: assert isinstance(object_record, ObjectRecord) if object_record.nobjects > 0: new_mean = fix(scind.mean(rec, object_record.labels, object_record.range)) gss = ((object_record.current_mean - new_mean) * 100 / object_record.start_mean) object_record.current_mean = new_mean else: gss = np.zeros((0,)) measurements.add_measurement(object_record.name, feature, gss) return statistics
def run(self, workspace): parents = workspace.object_set.get_objects(self.parent_name.value) children = workspace.object_set.get_objects(self.sub_object_name.value) child_count, parents_of = parents.relate_children(children) m = workspace.measurements assert isinstance(m, cpmeas.Measurements) if self.wants_per_parent_means.value: parent_indexes = np.arange(np.max(parents.segmented))+1 for feature_name in m.get_feature_names(self.sub_object_name.value): if not self.should_aggregate_feature(feature_name): continue data = m.get_current_measurement(self.sub_object_name.value, feature_name) if data is not None and len(data) > 0: if len(parents_of) > 0: means = fix(scind.mean(data.astype(float), parents_of, parent_indexes)) else: means = np.zeros((0,)) else: # No child measurements - all NaN means = np.ones(len(parents_of)) * np.nan mean_feature_name = FF_MEAN%(self.sub_object_name.value, feature_name) m.add_measurement(self.parent_name.value, mean_feature_name, means) m.add_measurement(self.sub_object_name.value, FF_PARENT%(self.parent_name.value), parents_of) m.add_measurement(self.parent_name.value, FF_CHILDREN_COUNT%(self.sub_object_name.value), child_count) good_parents = parents_of[parents_of != 0] image_numbers = np.ones(len(good_parents), int) * m.image_set_number good_children = np.argwhere(parents_of != 0).flatten() + 1 if np.any(good_parents): m.add_relate_measurement(self.module_num, R_PARENT, self.parent_name.value, self.sub_object_name.value, image_numbers, good_parents, image_numbers, good_children) m.add_relate_measurement(self.module_num, R_CHILD, self.sub_object_name.value, self.parent_name.value, image_numbers, good_children, image_numbers, good_parents) parent_names = self.get_parent_names() for parent_name in parent_names: if self.find_parent_child_distances in (D_BOTH, D_CENTROID): self.calculate_centroid_distances(workspace, parent_name) if self.find_parent_child_distances in (D_BOTH, D_MINIMUM): self.calculate_minimum_distances(workspace, parent_name) if self.show_window: workspace.display_data.parent_labels = parents.segmented workspace.display_data.parent_count = parents.count workspace.display_data.child_labels = children.segmented workspace.display_data.parents_of = parents_of
def do_measurements(self, workspace, image_name, object_name, center_object_name, center_choice, bin_count_settings, dd): '''Perform the radial measurements on the image set workspace - workspace that holds images / objects image_name - make measurements on this image object_name - make measurements on these objects center_object_name - use the centers of these related objects as the centers for radial measurements. None to use the objects themselves. center_choice - the user's center choice for this object: C_SELF, C_CENTERS_OF_OBJECTS or C_EDGES_OF_OBJECTS. bin_count_settings - the bin count settings group d - a dictionary for saving reusable partial results returns one statistics tuple per ring. ''' assert isinstance(workspace, cpw.Workspace) assert isinstance(workspace.object_set, cpo.ObjectSet) bin_count = bin_count_settings.bin_count.value wants_scaled = bin_count_settings.wants_scaled.value maximum_radius = bin_count_settings.maximum_radius.value image = workspace.image_set.get_image(image_name, must_be_grayscale=True) objects = workspace.object_set.get_objects(object_name) labels, pixel_data = cpo.crop_labels_and_image(objects.segmented, image.pixel_data) nobjects = np.max(objects.segmented) measurements = workspace.measurements assert isinstance(measurements, cpmeas.Measurements) if nobjects == 0: for bin in range(1, bin_count+1): for feature in (F_FRAC_AT_D, F_MEAN_FRAC, F_RADIAL_CV): feature_name = ( (feature + FF_GENERIC) % (image_name, bin, bin_count)) measurements.add_measurement( object_name, "_".join([M_CATEGORY, feature_name]), np.zeros(0)) if not wants_scaled: measurement_name = "_".join([M_CATEGORY, feature, image_name, FF_OVERFLOW]) measurements.add_measurement( object_name, measurement_name, np.zeros(0)) return [(image_name, object_name, "no objects","-","-","-","-")] name = (object_name if center_object_name is None else "%s_%s"%(object_name, center_object_name)) if dd.has_key(name): normalized_distance, i_center, j_center, good_mask = dd[name] else: d_to_edge = distance_to_edge(labels) if center_object_name is not None: # # Use the center of the centering objects to assign a center # to each labeled pixel using propagation # center_objects=workspace.object_set.get_objects(center_object_name) center_labels, cmask = cpo.size_similarly( labels, center_objects.segmented) pixel_counts = fix(scind.sum( np.ones(center_labels.shape), center_labels, np.arange(1, np.max(center_labels)+1,dtype=np.int32))) good = pixel_counts > 0 i,j = (centers_of_labels(center_labels) + .5).astype(int) if center_choice == C_CENTERS_OF_OTHER: # # Reduce the propagation labels to the centers of # the centering objects # ig = i[good] jg = j[good] lg = np.arange(1, len(i)+1)[good] center_labels = np.zeros(center_labels.shape, int) center_labels[ig,jg] = lg cl,d_from_center = propagate(np.zeros(center_labels.shape), center_labels, labels != 0, 1) # # Erase the centers that fall outside of labels # cl[labels == 0] = 0 # # If objects are hollow or crescent-shaped, there may be # objects without center labels. As a backup, find the # center that is the closest to the center of mass. # missing_mask = (labels != 0) & (cl == 0) missing_labels = np.unique(labels[missing_mask]) if len(missing_labels): all_centers = centers_of_labels(labels) missing_i_centers, missing_j_centers = \ all_centers[:, missing_labels-1] di = missing_i_centers[:, np.newaxis] - ig[np.newaxis, :] dj = missing_j_centers[:, np.newaxis] - jg[np.newaxis, :] missing_best = lg[np.lexsort((di*di + dj*dj, ))[:, 0]] best = np.zeros(np.max(labels) + 1, int) best[missing_labels] = missing_best cl[missing_mask] = best[labels[missing_mask]] # # Now compute the crow-flies distance to the centers # of these pixels from whatever center was assigned to # the object. # iii, jjj = np.mgrid[0:labels.shape[0], 0:labels.shape[1]] di = iii[missing_mask] - i[cl[missing_mask] - 1] dj = jjj[missing_mask] - j[cl[missing_mask] - 1] d_from_center[missing_mask] = np.sqrt(di*di + dj*dj) else: # Find the point in each object farthest away from the edge. # This does better than the centroid: # * The center is within the object # * The center tends to be an interesting point, like the # center of the nucleus or the center of one or the other # of two touching cells. # i,j = maximum_position_of_labels(d_to_edge, labels, objects.indices) center_labels = np.zeros(labels.shape, int) center_labels[i,j] = labels[i,j] # # Use the coloring trick here to process touching objects # in separate operations # colors = color_labels(labels) ncolors = np.max(colors) d_from_center = np.zeros(labels.shape) cl = np.zeros(labels.shape, int) for color in range(1,ncolors+1): mask = colors == color l,d = propagate(np.zeros(center_labels.shape), center_labels, mask, 1) d_from_center[mask] = d[mask] cl[mask] = l[mask] good_mask = cl > 0 if center_choice == C_EDGES_OF_OTHER: # Exclude pixels within the centering objects # when performing calculations from the centers good_mask = good_mask & (center_labels == 0) i_center = np.zeros(cl.shape) i_center[good_mask] = i[cl[good_mask]-1] j_center = np.zeros(cl.shape) j_center[good_mask] = j[cl[good_mask]-1] normalized_distance = np.zeros(labels.shape) if wants_scaled: total_distance = d_from_center + d_to_edge normalized_distance[good_mask] = (d_from_center[good_mask] / (total_distance[good_mask] + .001)) else: normalized_distance[good_mask] = \ d_from_center[good_mask] / maximum_radius dd[name] = [normalized_distance, i_center, j_center, good_mask] ngood_pixels = np.sum(good_mask) good_labels = labels[good_mask] bin_indexes = (normalized_distance * bin_count).astype(int) bin_indexes[bin_indexes > bin_count] = bin_count labels_and_bins = (good_labels-1,bin_indexes[good_mask]) histogram = coo_matrix((pixel_data[good_mask], labels_and_bins), (nobjects, bin_count+1)).toarray() sum_by_object = np.sum(histogram, 1) sum_by_object_per_bin = np.dstack([sum_by_object]*(bin_count + 1))[0] fraction_at_distance = histogram / sum_by_object_per_bin number_at_distance = coo_matrix((np.ones(ngood_pixels),labels_and_bins), (nobjects, bin_count+1)).toarray() object_mask = number_at_distance > 0 sum_by_object = np.sum(number_at_distance, 1) sum_by_object_per_bin = np.dstack([sum_by_object]*(bin_count+1))[0] fraction_at_bin = number_at_distance / sum_by_object_per_bin mean_pixel_fraction = fraction_at_distance / (fraction_at_bin + np.finfo(float).eps) masked_fraction_at_distance = masked_array(fraction_at_distance, ~object_mask) masked_mean_pixel_fraction = masked_array(mean_pixel_fraction, ~object_mask) # Anisotropy calculation. Split each cell into eight wedges, then # compute coefficient of variation of the wedges' mean intensities # in each ring. # # Compute each pixel's delta from the center object's centroid i,j = np.mgrid[0:labels.shape[0], 0:labels.shape[1]] imask = i[good_mask] > i_center[good_mask] jmask = j[good_mask] > j_center[good_mask] absmask = (abs(i[good_mask] - i_center[good_mask]) > abs(j[good_mask] - j_center[good_mask])) radial_index = (imask.astype(int) + jmask.astype(int)*2 + absmask.astype(int)*4) statistics = [] for bin in range(bin_count + (0 if wants_scaled else 1)): bin_mask = (good_mask & (bin_indexes == bin)) bin_pixels = np.sum(bin_mask) bin_labels = labels[bin_mask] bin_radial_index = radial_index[bin_indexes[good_mask] == bin] labels_and_radii = (bin_labels-1, bin_radial_index) radial_values = coo_matrix((pixel_data[bin_mask], labels_and_radii), (nobjects, 8)).toarray() pixel_count = coo_matrix((np.ones(bin_pixels), labels_and_radii), (nobjects, 8)).toarray() mask = pixel_count==0 radial_means = masked_array(radial_values / pixel_count, mask) radial_cv = np.std(radial_means,1) / np.mean(radial_means, 1) radial_cv[np.sum(~mask,1)==0] = 0 for measurement, feature, overflow_feature in ( (fraction_at_distance[:,bin], MF_FRAC_AT_D, OF_FRAC_AT_D), (mean_pixel_fraction[:,bin], MF_MEAN_FRAC, OF_MEAN_FRAC), (np.array(radial_cv), MF_RADIAL_CV, OF_RADIAL_CV)): if bin == bin_count: measurement_name = overflow_feature % image_name else: measurement_name = feature % (image_name, bin+1, bin_count) measurements.add_measurement(object_name, measurement_name, measurement) radial_cv.mask = np.sum(~mask,1)==0 bin_name = str(bin+1) if bin < bin_count else "Overflow" statistics += [(image_name, object_name, bin_name, str(bin_count), round(np.mean(masked_fraction_at_distance[:,bin]),4), round(np.mean(masked_mean_pixel_fraction[:, bin]),4), round(np.mean(radial_cv),4))] return statistics
def run(self, workspace): if self.show_window: workspace.display_data.col_labels = ( "Image","Object","Feature","Mean","Median","STD") workspace.display_data.statistics = statistics = [] for image_name in [img.name for img in self.images]: image = workspace.image_set.get_image(image_name.value, must_be_grayscale=True) for object_name in [obj.name for obj in self.objects]: # Need to refresh image after each iteration... img = image.pixel_data if image.has_mask: masked_image = img.copy() masked_image[~image.mask] = 0 else: masked_image = img objects = workspace.object_set.get_objects(object_name.value) nobjects = objects.count integrated_intensity = np.zeros((nobjects,)) integrated_intensity_edge = np.zeros((nobjects,)) mean_intensity = np.zeros((nobjects,)) mean_intensity_edge = np.zeros((nobjects,)) std_intensity = np.zeros((nobjects,)) std_intensity_edge = np.zeros((nobjects,)) min_intensity = np.zeros((nobjects,)) min_intensity_edge = np.zeros((nobjects,)) max_intensity = np.zeros((nobjects,)) max_intensity_edge = np.zeros((nobjects,)) mass_displacement = np.zeros((nobjects,)) lower_quartile_intensity = np.zeros((nobjects,)) median_intensity = np.zeros((nobjects,)) mad_intensity = np.zeros((nobjects,)) upper_quartile_intensity = np.zeros((nobjects,)) cmi_x = np.zeros((nobjects,)) cmi_y = np.zeros((nobjects,)) max_x = np.zeros((nobjects,)) max_y = np.zeros((nobjects,)) for labels, lindexes in objects.get_labels(): lindexes = lindexes[lindexes != 0] labels, img = cpo.crop_labels_and_image(labels, img) _, masked_image = cpo.crop_labels_and_image(labels, masked_image) outlines = cpmo.outline(labels) if image.has_mask: _, mask = cpo.crop_labels_and_image(labels, image.mask) masked_labels = labels.copy() masked_labels[~mask] = 0 masked_outlines = outlines.copy() masked_outlines[~mask] = 0 else: masked_labels = labels masked_outlines = outlines lmask = masked_labels > 0 & np.isfinite(img) # Ignore NaNs, Infs has_objects = np.any(lmask) if has_objects: limg = img[lmask] llabels = labels[lmask] mesh_y, mesh_x = np.mgrid[0:masked_image.shape[0], 0:masked_image.shape[1]] mesh_x = mesh_x[lmask] mesh_y = mesh_y[lmask] lcount = fix(nd.sum(np.ones(len(limg)), llabels, lindexes)) integrated_intensity[lindexes-1] = \ fix(nd.sum(limg, llabels, lindexes)) mean_intensity[lindexes-1] = \ integrated_intensity[lindexes-1] / lcount std_intensity[lindexes-1] = np.sqrt( fix(nd.mean((limg - mean_intensity[llabels-1])**2, llabels, lindexes))) min_intensity[lindexes-1] = fix(nd.minimum(limg, llabels, lindexes)) max_intensity[lindexes-1] = fix( nd.maximum(limg, llabels, lindexes)) # Compute the position of the intensity maximum max_position = np.array( fix(nd.maximum_position(limg, llabels, lindexes)), dtype=int ) max_position = np.reshape( max_position, ( max_position.shape[0], ) ) max_x[lindexes-1] = mesh_x[ max_position ] max_y[lindexes-1] = mesh_y[ max_position ] # The mass displacement is the distance between the center # of mass of the binary image and of the intensity image. The # center of mass is the average X or Y for the binary image # and the sum of X or Y * intensity / integrated intensity cm_x = fix(nd.mean(mesh_x, llabels, lindexes)) cm_y = fix(nd.mean(mesh_y, llabels, lindexes)) i_x = fix(nd.sum(mesh_x * limg, llabels, lindexes)) i_y = fix(nd.sum(mesh_y * limg, llabels, lindexes)) cmi_x[lindexes-1] = i_x / integrated_intensity[lindexes-1] cmi_y[lindexes-1] = i_y / integrated_intensity[lindexes-1] diff_x = cm_x - cmi_x[lindexes-1] diff_y = cm_y - cmi_y[lindexes-1] mass_displacement[lindexes-1] = \ np.sqrt(diff_x * diff_x+diff_y*diff_y) # # Sort the intensities by label, then intensity. # For each label, find the index above and below # the 25%, 50% and 75% mark and take the weighted # average. # order = np.lexsort((limg, llabels)) areas = lcount.astype(int) indices = np.cumsum(areas) - areas for dest, fraction in ( (lower_quartile_intensity, 1.0/4.0), (median_intensity, 1.0/2.0), (upper_quartile_intensity, 3.0/4.0)): qindex = indices.astype(float) + areas * fraction qfraction = qindex - np.floor(qindex) qindex = qindex.astype(int) qmask = qindex < indices + areas-1 qi = qindex[qmask] qf = qfraction[qmask] dest[lindexes[qmask]-1] = ( limg[order[qi]] * (1 - qf) + limg[order[qi + 1]] * qf) # # In some situations (e.g. only 3 points), there may # not be an upper bound. # qmask = (~qmask) & (areas > 0) dest[lindexes[qmask]-1] = limg[order[qindex[qmask]]] # # Once again, for the MAD # madimg = np.abs(limg - median_intensity[llabels-1]) order = np.lexsort((madimg, llabels)) qindex = indices.astype(float) + areas / 2.0 qfraction = qindex - np.floor(qindex) qindex = qindex.astype(int) qmask = qindex < indices + areas-1 qi = qindex[qmask] qf = qfraction[qmask] mad_intensity[lindexes[qmask]-1] = ( madimg[order[qi]] * (1 - qf) + madimg[order[qi + 1]] * qf) qmask = (~qmask) & (areas > 0) mad_intensity[lindexes[qmask]-1] = madimg[order[qindex[qmask]]] emask = masked_outlines > 0 eimg = img[emask] elabels = labels[emask] has_edge = len(eimg) > 0 if has_edge: ecount = fix(nd.sum( np.ones(len(eimg)), elabels, lindexes)) integrated_intensity_edge[lindexes-1] = \ fix(nd.sum(eimg, elabels, lindexes)) mean_intensity_edge[lindexes-1] = \ integrated_intensity_edge[lindexes-1] / ecount std_intensity_edge[lindexes-1] = \ np.sqrt(fix(nd.mean( (eimg - mean_intensity_edge[elabels-1])**2, elabels, lindexes))) min_intensity_edge[lindexes-1] = fix( nd.minimum(eimg, elabels, lindexes)) max_intensity_edge[lindexes-1] = fix( nd.maximum(eimg, elabels, lindexes)) m = workspace.measurements for category, feature_name, measurement in \ ((INTENSITY, INTEGRATED_INTENSITY, integrated_intensity), (INTENSITY, MEAN_INTENSITY, mean_intensity), (INTENSITY, STD_INTENSITY, std_intensity), (INTENSITY, MIN_INTENSITY, min_intensity), (INTENSITY, MAX_INTENSITY, max_intensity), (INTENSITY, INTEGRATED_INTENSITY_EDGE, integrated_intensity_edge), (INTENSITY, MEAN_INTENSITY_EDGE, mean_intensity_edge), (INTENSITY, STD_INTENSITY_EDGE, std_intensity_edge), (INTENSITY, MIN_INTENSITY_EDGE, min_intensity_edge), (INTENSITY, MAX_INTENSITY_EDGE, max_intensity_edge), (INTENSITY, MASS_DISPLACEMENT, mass_displacement), (INTENSITY, LOWER_QUARTILE_INTENSITY, lower_quartile_intensity), (INTENSITY, MEDIAN_INTENSITY, median_intensity), (INTENSITY, MAD_INTENSITY, mad_intensity), (INTENSITY, UPPER_QUARTILE_INTENSITY, upper_quartile_intensity), (C_LOCATION, LOC_CMI_X, cmi_x), (C_LOCATION, LOC_CMI_Y, cmi_y), (C_LOCATION, LOC_MAX_X, max_x), (C_LOCATION, LOC_MAX_Y, max_y)): measurement_name = "%s_%s_%s"%(category,feature_name, image_name.value) m.add_measurement(object_name.value,measurement_name, measurement) if self.show_window and len(measurement) > 0: statistics.append((image_name.value, object_name.value, feature_name, np.round(np.mean(measurement),3), np.round(np.median(measurement),3), np.round(np.std(measurement),3)))
def run_image_pair_objects(self, workspace, first_image_name, second_image_name, object_name): '''Calculate per-object correlations between intensities in two images''' first_image = workspace.image_set.get_image(first_image_name, must_be_grayscale=True) second_image = workspace.image_set.get_image(second_image_name, must_be_grayscale=True) objects = workspace.object_set.get_objects(object_name) # # Crop both images to the size of the labels matrix # labels = objects.segmented try: first_pixels = objects.crop_image_similarly(first_image.pixel_data) first_mask = objects.crop_image_similarly(first_image.mask) except ValueError: first_pixels, m1 = cpo.size_similarly(labels, first_image.pixel_data) first_mask, m1 = cpo.size_similarly(labels, first_image.mask) first_mask[~m1] = False try: second_pixels = objects.crop_image_similarly( second_image.pixel_data) second_mask = objects.crop_image_similarly(second_image.mask) except ValueError: second_pixels, m1 = cpo.size_similarly(labels, second_image.pixel_data) second_mask, m1 = cpo.size_similarly(labels, second_image.mask) second_mask[~m1] = False mask = ((labels > 0) & first_mask & second_mask) first_pixels = first_pixels[mask] second_pixels = second_pixels[mask] labels = labels[mask] if len(labels) == 0: n_objects = 0 else: n_objects = np.max(labels) if n_objects == 0: corr = np.zeros((0, )) else: # # The correlation is sum((x-mean(x))(y-mean(y)) / # ((n-1) * std(x) *std(y))) # lrange = np.arange(n_objects, dtype=np.int32) + 1 area = fix(scind.sum(np.ones_like(labels), labels, lrange)) mean1 = fix(scind.mean(first_pixels, labels, lrange)) mean2 = fix(scind.mean(second_pixels, labels, lrange)) # # Calculate the standard deviation times the population. # std1 = np.sqrt( fix( scind.sum((first_pixels - mean1[labels - 1])**2, labels, lrange))) std2 = np.sqrt( fix( scind.sum((second_pixels - mean2[labels - 1])**2, labels, lrange))) x = first_pixels - mean1[labels - 1] # x - mean(x) y = second_pixels - mean2[labels - 1] # y - mean(y) corr = fix( scind.sum(x * y / (std1[labels - 1] * std2[labels - 1]), labels, lrange)) corr[~np.isfinite(corr)] = 0 measurement = ("Correlation_Correlation_%s_%s" % (first_image_name, second_image_name)) workspace.measurements.add_measurement(object_name, measurement, corr) if n_objects == 0: return [[ first_image_name, second_image_name, object_name, "Mean correlation", "-" ], [ first_image_name, second_image_name, object_name, "Median correlation", "-" ], [ first_image_name, second_image_name, object_name, "Min correlation", "-" ], [ first_image_name, second_image_name, object_name, "Max correlation", "-" ]] else: return [[ first_image_name, second_image_name, object_name, "Mean correlation", "%.2f" % np.mean(corr) ], [ first_image_name, second_image_name, object_name, "Median correlation", "%.2f" % np.median(corr) ], [ first_image_name, second_image_name, object_name, "Min correlation", "%.2f" % np.min(corr) ], [ first_image_name, second_image_name, object_name, "Max correlation", "%.2f" % np.max(corr) ]]
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) # # 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) # # 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 do_measurements(self, workspace, image_name, object_name, center_object_name, bin_count, dd): '''Perform the radial measurements on the image set workspace - workspace that holds images / objects image_name - make measurements on this image object_name - make measurements on these objects center_object_name - use the centers of these related objects as the centers for radial measurements. None to use the objects themselves. bin_count - bin the object into this many concentric rings d - a dictionary for saving reusable partial results returns one statistics tuple per ring. ''' assert isinstance(workspace, cpw.Workspace) assert isinstance(workspace.object_set, cpo.ObjectSet) image = workspace.image_set.get_image(image_name, must_be_grayscale=True) objects = workspace.object_set.get_objects(object_name) labels, pixel_data = cpo.crop_labels_and_image(objects.segmented, image.pixel_data) nobjects = np.max(objects.segmented) measurements = workspace.measurements assert isinstance(measurements, cpmeas.Measurements) if nobjects == 0: for bin in range(1, bin_count+1): for feature in (FF_FRAC_AT_D, FF_MEAN_FRAC, FF_RADIAL_CV): measurements.add_measurement(object_name, M_CATEGORY + "_" + feature % (image_name, bin, bin_count), np.zeros(0)) return [(image_name, object_name, "no objects","-","-","-","-")] name = (object_name if center_object_name is None else "%s_%s"%(object_name, center_object_name)) if dd.has_key(name): normalized_distance, i_center, j_center, good_mask = dd[name] else: d_to_edge = distance_to_edge(labels) if center_object_name is not None: center_objects=workspace.object_set.get_objects(center_object_name) center_labels, cmask = cpo.size_similarly( labels, center_objects.segmented) pixel_counts = fix(scind.sum(np.ones(center_labels.shape), center_labels, np.arange(1, np.max(center_labels)+1,dtype=np.int32))) good = pixel_counts > 0 i,j = (centers_of_labels(center_labels) + .5).astype(int) ig = i[good] jg = j[good] center_labels = np.zeros(center_labels.shape, int) center_labels[ig,jg] = labels[ig,jg] ## TODO: This is incorrect when objects are annular. Retrieves label# = 0 cl,d_from_center = propagate(np.zeros(center_labels.shape), center_labels, labels != 0, 1) else: # Find the point in each object farthest away from the edge. # This does better than the centroid: # * The center is within the object # * The center tends to be an interesting point, like the # center of the nucleus or the center of one or the other # of two touching cells. # i,j = maximum_position_of_labels(d_to_edge, labels, objects.indices) center_labels = np.zeros(labels.shape, int) center_labels[i,j] = labels[i,j] # # Use the coloring trick here to process touching objects # in separate operations # colors = color_labels(labels) ncolors = np.max(colors) d_from_center = np.zeros(labels.shape) cl = np.zeros(labels.shape, int) for color in range(1,ncolors+1): mask = colors == color l,d = propagate(np.zeros(center_labels.shape), center_labels, mask, 1) d_from_center[mask] = d[mask] cl[mask] = l[mask] good_mask = cl > 0 i_center = np.zeros(cl.shape) i_center[good_mask] = i[cl[good_mask]-1] j_center = np.zeros(cl.shape) j_center[good_mask] = j[cl[good_mask]-1] normalized_distance = np.zeros(labels.shape) total_distance = d_from_center + d_to_edge normalized_distance[good_mask] = (d_from_center[good_mask] / (total_distance[good_mask] + .001)) dd[name] = [normalized_distance, i_center, j_center, good_mask] ngood_pixels = np.sum(good_mask) good_labels = objects.segmented[good_mask] bin_indexes = (normalized_distance * bin_count).astype(int) labels_and_bins = (good_labels-1,bin_indexes[good_mask]) histogram = coo_matrix((image.pixel_data[good_mask], labels_and_bins), (nobjects, bin_count)).toarray() sum_by_object = np.sum(histogram, 1) sum_by_object_per_bin = np.dstack([sum_by_object]*bin_count)[0] fraction_at_distance = histogram / sum_by_object_per_bin number_at_distance = coo_matrix((np.ones(ngood_pixels),labels_and_bins), (nobjects, bin_count)).toarray() object_mask = number_at_distance > 0 sum_by_object = np.sum(number_at_distance, 1) sum_by_object_per_bin = np.dstack([sum_by_object]*bin_count)[0] fraction_at_bin = number_at_distance / sum_by_object_per_bin mean_pixel_fraction = fraction_at_distance / (fraction_at_bin + np.finfo(float).eps) masked_fraction_at_distance = masked_array(fraction_at_distance, ~object_mask) masked_mean_pixel_fraction = masked_array(mean_pixel_fraction, ~object_mask) # Anisotropy calculation. Split each cell into eight wedges, then # compute coefficient of variation of the wedges' mean intensities # in each ring. # # Compute each pixel's delta from the center object's centroid i,j = np.mgrid[0:labels.shape[0], 0:labels.shape[1]] imask = i[good_mask] > i_center[good_mask] jmask = j[good_mask] > j_center[good_mask] absmask = (abs(i[good_mask] - i_center[good_mask]) > abs(j[good_mask] - j_center[good_mask])) radial_index = (imask.astype(int) + jmask.astype(int)*2 + absmask.astype(int)*4) statistics = [] for bin in range(bin_count): bin_mask = (good_mask & (bin_indexes == bin)) bin_pixels = np.sum(bin_mask) bin_labels = labels[bin_mask] bin_radial_index = radial_index[bin_indexes[good_mask] == bin] labels_and_radii = (bin_labels-1, bin_radial_index) radial_values = coo_matrix((pixel_data[bin_mask], labels_and_radii), (nobjects, 8)).toarray() pixel_count = coo_matrix((np.ones(bin_pixels), labels_and_radii), (nobjects, 8)).toarray() mask = pixel_count==0 radial_means = masked_array(radial_values / pixel_count, mask) radial_cv = np.std(radial_means,1) / np.mean(radial_means, 1) radial_cv[np.sum(~mask,1)==0] = 0 for measurement, feature in ((fraction_at_distance[:,bin], MF_FRAC_AT_D), (mean_pixel_fraction[:,bin], MF_MEAN_FRAC), (np.array(radial_cv), MF_RADIAL_CV)): measurements.add_measurement(object_name, feature % (image_name, bin+1, bin_count), measurement) radial_cv.mask = np.sum(~mask,1)==0 statistics += [(image_name, object_name, str(bin+1), str(bin_count), round(np.mean(masked_fraction_at_distance[:,bin]),4), round(np.mean(masked_mean_pixel_fraction[:, bin]),4), round(np.mean(radial_cv),4))] return statistics
def make_neuron_graph(self, skeleton, skeleton_labels, trunks, branchpoints, endpoints, image): '''Make a table that captures the graph relationship of the skeleton skeleton - binary skeleton image + outline of seed objects skeleton_labels - labels matrix of skeleton trunks - binary image with trunk points as 1 branchpoints - binary image with branchpoints as 1 endpoints - binary image with endpoints as 1 image - image for intensity measurement returns two tables. Table 1: edge table The edge table is a numpy record array with the following named columns in the following order: v1: index into vertex table of first vertex of edge v2: index into vertex table of second vertex of edge length: # of intermediate pixels + 2 (for two vertices) total_intensity: sum of intensities along the edge Table 2: vertex table The vertex table is a numpy record array: i: I coordinate of the vertex j: J coordinate of the vertex label: the vertex's label kind: kind of vertex = "T" for trunk, "B" for branchpoint or "E" for endpoint. ''' i, j = np.mgrid[0:skeleton.shape[0], 0:skeleton.shape[1]] # # Give each point of interest a unique number # points_of_interest = trunks | branchpoints | endpoints number_of_points = np.sum(points_of_interest) # # Make up the vertex table # tbe = np.zeros(points_of_interest.shape, '|S1') tbe[trunks] = 'T' tbe[branchpoints] = 'B' tbe[endpoints] = 'E' i_idx = i[points_of_interest] j_idx = j[points_of_interest] poe_labels = skeleton_labels[points_of_interest] tbe = tbe[points_of_interest] vertex_table = { self.VF_I: i_idx, self.VF_J: j_idx, self.VF_LABELS: poe_labels, self.VF_KIND: tbe } # # First, break the skeleton by removing the branchpoints, endpoints # and trunks # broken_skeleton = skeleton & (~points_of_interest) # # Label the broken skeleton: this labels each edge differently # edge_labels, nlabels = morph.label_skeleton(skeleton) # # Reindex after removing the points of interest # edge_labels[points_of_interest] = 0 if nlabels > 0: indexer = np.arange(nlabels + 1) unique_labels = np.sort(np.unique(edge_labels)) nlabels = len(unique_labels) - 1 indexer[unique_labels] = np.arange(len(unique_labels)) edge_labels = indexer[edge_labels] # # find magnitudes and lengths for all edges # magnitudes = fix( scind.sum(image, edge_labels, np.arange(1, nlabels + 1, dtype=np.int32))) lengths = fix( scind.sum(np.ones(edge_labels.shape), edge_labels, np.arange(1, nlabels + 1, dtype=np.int32))).astype(int) else: magnitudes = np.zeros(0) lengths = np.zeros(0, int) # # combine the edge labels and indexes of points of interest with padding # edge_mask = edge_labels != 0 all_labels = np.zeros(np.array(edge_labels.shape) + 2, int) all_labels[1:-1, 1:-1][edge_mask] = edge_labels[edge_mask] + number_of_points all_labels[i_idx + 1, j_idx + 1] = np.arange(1, number_of_points + 1) # # Collect all 8 neighbors for each point of interest # p1 = np.zeros(0, int) p2 = np.zeros(0, int) for i_off, j_off in ((0, 0), (0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1), (2, 2)): p1 = np.hstack((p1, np.arange(1, number_of_points + 1))) p2 = np.hstack((p2, all_labels[i_idx + i_off, j_idx + j_off])) # # Get rid of zeros which are background # p1 = p1[p2 != 0] p2 = p2[p2 != 0] # # Find point_of_interest -> point_of_interest connections. # p1_poi = p1[(p2 <= number_of_points) & (p1 < p2)] p2_poi = p2[(p2 <= number_of_points) & (p1 < p2)] # # Make sure matches are labeled the same # same_labels = ( skeleton_labels[i_idx[p1_poi - 1], j_idx[p1_poi - 1]] == skeleton_labels[i_idx[p2_poi - 1], j_idx[p2_poi - 1]]) p1_poi = p1_poi[same_labels] p2_poi = p2_poi[same_labels] # # Find point_of_interest -> edge # p1_edge = p1[p2 > number_of_points] edge = p2[p2 > number_of_points] # # Now, each value that p2_edge takes forms a group and all # p1_edge whose p2_edge are connected together by the edge. # Possibly they touch each other without the edge, but we will # take the minimum distance connecting each pair to throw out # the edge. # edge, p1_edge, p2_edge = morph.pairwise_permutations(edge, p1_edge) indexer = edge - number_of_points - 1 lengths = lengths[indexer] magnitudes = magnitudes[indexer] # # OK, now we make the edge table. First poi<->poi. Length = 2, # magnitude = magnitude at each point # poi_length = np.ones(len(p1_poi)) * 2 poi_magnitude = (image[i_idx[p1_poi - 1], j_idx[p1_poi - 1]] + image[i_idx[p2_poi - 1], j_idx[p2_poi - 1]]) # # Now the edges... # poi_edge_length = lengths + 2 poi_edge_magnitude = (image[i_idx[p1_edge - 1], j_idx[p1_edge - 1]] + image[i_idx[p2_edge - 1], j_idx[p2_edge - 1]] + magnitudes) # # Put together the columns # v1 = np.hstack((p1_poi, p1_edge)) v2 = np.hstack((p2_poi, p2_edge)) lengths = np.hstack((poi_length, poi_edge_length)) magnitudes = np.hstack((poi_magnitude, poi_edge_magnitude)) # # Sort by p1, p2 and length in order to pick the shortest length # indexer = np.lexsort((lengths, v1, v2)) v1 = v1[indexer] v2 = v2[indexer] lengths = lengths[indexer] magnitudes = magnitudes[indexer] if len(v1) > 0: to_keep = np.hstack( ([True], (v1[1:] != v1[:-1]) | (v2[1:] != v2[:-1]))) v1 = v1[to_keep] v2 = v2[to_keep] lengths = lengths[to_keep] magnitudes = magnitudes[to_keep] # # Put it all together into a table # edge_table = { self.EF_V1: v1, self.EF_V2: v2, self.EF_LENGTH: lengths, self.EF_TOTAL_INTENSITY: magnitudes } return edge_table, vertex_table
def run(self, workspace): assert isinstance(workspace, cpw.Workspace) image = workspace.image_set.get_image(self.image_name.value, must_be_grayscale=True) img = image.pixel_data mask = image.mask objects = workspace.object_set.get_objects(self.primary_objects.value) global_threshold = None if self.method == M_DISTANCE_N: has_threshold = False elif self.threshold_method == cpthresh.TM_BINARY_IMAGE: binary_image = workspace.image_set.get_image( self.binary_image.value, must_be_binary=True) local_threshold = np.ones( img.shape) * np.max(img) + np.finfo(float).eps local_threshold[ binary_image.pixel_data] = np.min(img) - np.finfo(float).eps global_threshold = cellprofiler.cpmath.otsu.otsu( img[mask], self.threshold_range.min, self.threshold_range.max) has_threshold = True else: local_threshold, global_threshold = self.get_threshold( img, mask, None, workspace) has_threshold = True if has_threshold: thresholded_image = img > local_threshold # # Get the following labels: # * all edited labels # * labels touching the edge, including small removed # labels_in = objects.unedited_segmented.copy() labels_touching_edge = np.hstack( (labels_in[0, :], labels_in[-1, :], labels_in[:, 0], labels_in[:, -1])) labels_touching_edge = np.unique(labels_touching_edge) is_touching = np.zeros(np.max(labels_in) + 1, bool) is_touching[labels_touching_edge] = True is_touching = is_touching[labels_in] labels_in[(~is_touching) & (objects.segmented == 0)] = 0 # # Stretch the input labels to match the image size. If there's no # label matrix, then there's no label in that area. # if tuple(labels_in.shape) != tuple(img.shape): tmp = np.zeros(img.shape, labels_in.dtype) i_max = min(img.shape[0], labels_in.shape[0]) j_max = min(img.shape[1], labels_in.shape[1]) tmp[:i_max, :j_max] = labels_in[:i_max, :j_max] labels_in = tmp if self.method in (M_DISTANCE_B, M_DISTANCE_N): if self.method == M_DISTANCE_N: distances, (i, j) = scind.distance_transform_edt( labels_in == 0, return_indices=True) labels_out = np.zeros(labels_in.shape, int) dilate_mask = distances <= self.distance_to_dilate.value labels_out[dilate_mask] =\ labels_in[i[dilate_mask],j[dilate_mask]] else: labels_out, distances = propagate(img, labels_in, thresholded_image, 1.0) labels_out[distances > self.distance_to_dilate.value] = 0 labels_out[labels_in > 0] = labels_in[labels_in > 0] if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out # # Create the final output labels by removing labels in the # output matrix that are missing from the segmented image # segmented_labels = objects.segmented segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) elif self.method == M_PROPAGATION: labels_out, distance = propagate(img, labels_in, thresholded_image, self.regularization_factor.value) if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out.copy() segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) elif self.method == M_WATERSHED_G: # # First, apply the sobel filter to the image (both horizontal # and vertical). The filter measures gradient. # sobel_image = np.abs(scind.sobel(img)) # # Combine the image mask and threshold to mask the watershed # watershed_mask = np.logical_or(thresholded_image, labels_in > 0) watershed_mask = np.logical_and(watershed_mask, mask) # # Perform the first watershed # labels_out = watershed(sobel_image, labels_in, np.ones((3, 3), bool), mask=watershed_mask) if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out.copy() segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) elif self.method == M_WATERSHED_I: # # invert the image so that the maxima are filled first # and the cells compete over what's close to the threshold # inverted_img = 1 - img # # Same as above, but perform the watershed on the original image # watershed_mask = np.logical_or(thresholded_image, labels_in > 0) watershed_mask = np.logical_and(watershed_mask, mask) # # Perform the watershed # labels_out = watershed(inverted_img, labels_in, np.ones((3, 3), bool), mask=watershed_mask) if self.fill_holes: small_removed_segmented_out = fill_labeled_holes(labels_out) else: small_removed_segmented_out = labels_out segmented_out = self.filter_labels(small_removed_segmented_out, objects, workspace) if self.wants_discard_edge and self.wants_discard_primary: # # Make a new primary object # lookup = scind.maximum(segmented_out, objects.segmented, range(np.max(objects.segmented) + 1)) lookup = fix(lookup) lookup[0] = 0 lookup[lookup != 0] = np.arange(np.sum(lookup != 0)) + 1 segmented_labels = lookup[objects.segmented] segmented_out = lookup[segmented_out] new_objects = cpo.Objects() new_objects.segmented = segmented_labels if objects.has_unedited_segmented: new_objects.unedited_segmented = objects.unedited_segmented if objects.has_small_removed_segmented: new_objects.small_removed_segmented = objects.small_removed_segmented new_objects.parent_image = objects.parent_image primary_outline = outline(segmented_labels) if self.wants_primary_outlines: out_img = cpi.Image(primary_outline.astype(bool), parent_image=image) workspace.image_set.add(self.new_primary_outlines_name.value, out_img) else: primary_outline = outline(objects.segmented) secondary_outline = outline(segmented_out) if workspace.frame != None: object_area = np.sum(segmented_out > 0) object_pct = 100 * object_area / np.product(segmented_out.shape) my_frame = workspace.create_or_find_figure( title="IdentifySecondaryObjects, image cycle #%d" % (workspace.measurements.image_set_number), subplots=(2, 2)) title = "Input image, cycle #%d" % (workspace.image_set.number + 1) my_frame.subplot_imshow_grayscale(0, 0, img, title) my_frame.subplot_imshow_labels(1, 0, segmented_out, "Labeled image", sharex=my_frame.subplot(0, 0), sharey=my_frame.subplot(0, 0)) outline_img = np.dstack((img, img, img)) cpmi.draw_outline(outline_img, secondary_outline > 0, cpprefs.get_secondary_outline_color()) my_frame.subplot_imshow(0, 1, outline_img, "Outlined image", normalize=False, sharex=my_frame.subplot(0, 0), sharey=my_frame.subplot(0, 0)) primary_img = np.dstack((img, img, img)) cpmi.draw_outline(primary_img, primary_outline > 0, cpprefs.get_primary_outline_color()) cpmi.draw_outline(primary_img, secondary_outline > 0, cpprefs.get_secondary_outline_color()) my_frame.subplot_imshow(1, 1, primary_img, "Primary and output outlines", normalize=False, sharex=my_frame.subplot(0, 0), sharey=my_frame.subplot(0, 0)) if global_threshold is not None: my_frame.status_bar.SetFields([ "Threshold: %.3f" % global_threshold, "Area covered by objects: %.1f %%" % object_pct ]) else: my_frame.status_bar.SetFields( ["Area covered by objects: %.1f %%" % object_pct]) # # Add the objects to the object set # objects_out = cpo.Objects() objects_out.unedited_segmented = small_removed_segmented_out objects_out.small_removed_segmented = small_removed_segmented_out objects_out.segmented = segmented_out objects_out.parent_image = image objname = self.objects_name.value workspace.object_set.add_objects(objects_out, objname) if self.use_outlines.value: out_img = cpi.Image(secondary_outline.astype(bool), parent_image=image) workspace.image_set.add(self.outlines_name.value, out_img) object_count = np.max(segmented_out) # # Add the background measurements if made # measurements = workspace.measurements if has_threshold: if isinstance(local_threshold, np.ndarray): ave_threshold = np.mean(local_threshold) else: ave_threshold = local_threshold measurements.add_measurement( cpmeas.IMAGE, cpmi.FF_FINAL_THRESHOLD % (objname), np.array([ave_threshold], dtype=float)) measurements.add_measurement( cpmeas.IMAGE, cpmi.FF_ORIG_THRESHOLD % (objname), np.array([global_threshold], dtype=float)) wv = cpthresh.weighted_variance(img, mask, local_threshold) measurements.add_measurement(cpmeas.IMAGE, cpmi.FF_WEIGHTED_VARIANCE % (objname), np.array([wv], dtype=float)) entropies = cpthresh.sum_of_entropies(img, mask, local_threshold) measurements.add_measurement(cpmeas.IMAGE, cpmi.FF_SUM_OF_ENTROPIES % (objname), np.array([entropies], dtype=float)) cpmi.add_object_count_measurements(measurements, objname, object_count) cpmi.add_object_location_measurements(measurements, objname, segmented_out) # # Relate the secondary objects to the primary ones and record # the relationship. # children_per_parent, parents_of_children = \ objects.relate_children(objects_out) measurements.add_measurement(self.primary_objects.value, cpmi.FF_CHILDREN_COUNT % objname, children_per_parent) measurements.add_measurement( objname, cpmi.FF_PARENT % self.primary_objects.value, parents_of_children) # # If primary objects were created, add them # if self.wants_discard_edge and self.wants_discard_primary: workspace.object_set.add_objects( new_objects, self.new_primary_objects_name.value) cpmi.add_object_count_measurements( measurements, self.new_primary_objects_name.value, np.max(new_objects.segmented)) cpmi.add_object_location_measurements( measurements, self.new_primary_objects_name.value, new_objects.segmented) for parent_objects, parent_name, child_objects, child_name in ( (objects, self.primary_objects.value, new_objects, self.new_primary_objects_name.value), (new_objects, self.new_primary_objects_name.value, objects_out, objname)): children_per_parent, parents_of_children = \ parent_objects.relate_children(child_objects) measurements.add_measurement( parent_name, cpmi.FF_CHILDREN_COUNT % child_name, children_per_parent) measurements.add_measurement(child_name, cpmi.FF_PARENT % parent_name, parents_of_children)
def run_image_pair_objects(self, workspace, first_image_name, second_image_name, object_name): '''Calculate per-object correlations between intensities in two images''' first_image = workspace.image_set.get_image(first_image_name, must_be_grayscale=True) second_image = workspace.image_set.get_image(second_image_name, must_be_grayscale=True) objects = workspace.object_set.get_objects(object_name) # # Crop both images to the size of the labels matrix # labels = objects.segmented try: first_pixels = objects.crop_image_similarly(first_image.pixel_data) first_mask = objects.crop_image_similarly(first_image.mask) except ValueError: first_pixels, m1 = cpo.size_similarly(labels, first_image.pixel_data) first_mask, m1 = cpo.size_similarly(labels, first_image.mask) first_mask[~m1] = False try: second_pixels = objects.crop_image_similarly(second_image.pixel_data) second_mask = objects.crop_image_similarly(second_image.mask) except ValueError: second_pixels, m1 = cpo.size_similarly(labels, second_image.pixel_data) second_mask, m1 = cpo.size_similarly(labels, second_image.mask) second_mask[~m1] = False mask = ((labels > 0) & first_mask & second_mask) first_pixels = first_pixels[mask] second_pixels = second_pixels[mask] labels = labels[mask] if len(labels)==0: n_objects = 0 else: n_objects = np.max(labels) if n_objects == 0: corr = np.zeros((0,)) else: # # The correlation is sum((x-mean(x))(y-mean(y)) / # ((n-1) * std(x) *std(y))) # lrange = np.arange(n_objects,dtype=np.int32)+1 area = fix(scind.sum(np.ones_like(labels), labels, lrange)) mean1 = fix(scind.mean(first_pixels, labels, lrange)) mean2 = fix(scind.mean(second_pixels, labels, lrange)) # # Calculate the standard deviation times the population. # std1 = np.sqrt(fix(scind.sum((first_pixels-mean1[labels-1])**2, labels, lrange))) std2 = np.sqrt(fix(scind.sum((second_pixels-mean2[labels-1])**2, labels, lrange))) x = first_pixels - mean1[labels-1] # x - mean(x) y = second_pixels - mean2[labels-1] # y - mean(y) corr = fix(scind.sum(x * y / (std1[labels-1] * std2[labels-1]), labels, lrange)) corr[~ np.isfinite(corr)] = 0 measurement = ("Correlation_Correlation_%s_%s" % (first_image_name, second_image_name)) workspace.measurements.add_measurement(object_name, measurement, corr) if n_objects == 0: return [[first_image_name, second_image_name, object_name, "Mean correlation","-"], [first_image_name, second_image_name, object_name, "Median correlation","-"], [first_image_name, second_image_name, object_name, "Min correlation","-"], [first_image_name, second_image_name, object_name, "Max correlation","-"]] else: return [[first_image_name, second_image_name, object_name, "Mean correlation","%.2f"%np.mean(corr)], [first_image_name, second_image_name, object_name, "Median correlation","%.2f"%np.median(corr)], [first_image_name, second_image_name, object_name, "Min correlation","%.2f"%np.min(corr)], [first_image_name, second_image_name, object_name, "Max correlation","%.2f"%np.max(corr)]]
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) # # 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) # # 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 # # 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] if np.any(touching_border) and \ np.all(~ touching_border_mask[neighbor_labels]): # 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[touching_border_mask] = touching_border_object_number[ unedited_segmented[touching_border_mask]] nobjects = np.max(labels) nneighbors = np.max(neighbor_labels) nkept_objects = objects.count _, 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[:, ~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 nneighbors > 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 measure_TAS(self, pixels, labels, n, m): # I'll put some documentation in here to explain what it does. # If someone ever wants to call it, their editor might display # the documentation. '''Measure the intensity of the image with Zernike (N, M) pixels - the intensity image to be measured labels - the labels matrix that labels each object with an integer n, m - the Zernike coefficients. See http://en.wikipedia.org/wiki/Zernike_polynomials for an explanation of the Zernike polynomials ''' # # The strategy here is to operate on the whole array instead # of operating on one object at a time. The most important thing # is to avoid having to run the Python interpreter once per pixel # in the image and the second most important is to avoid running # it per object in case there are hundreds of objects. # # We play lots of indexing tricks here to operate on the whole image. # I'll try to explain some - hopefully, you can reuse. # # You could move the calculation of the minimum enclosing circle # outside of this function. The function gets called more than # 10 times, so the same calculation is performed 10 times. # It would make the code a little more confusing, so I'm leaving # it as-is. ########################################### # # The minimum enclosing circle (MEC) is the smallest circle that # will fit around the object. We get the centers and radii of # all of the objects at once. You'll see how that lets us # compute the X and Y position of each pixel in a label all at # one go. # # First, get an array that lists the whole range of indexes in # the labels matrix. # if len(labels)==0: n_objects = 0 else: n_objects = np.max(labels) if n_objects==0: result = np.zeros((0,)) else: indexes = np.arange(1, np.max(labels)+1,dtype=np.int32) # Calculate mean of pixels above int 30 mean=np.mean(pixels[pixels>0.118]) # Set ranges rangelow = mean + n rangehigh = mean + m # Threshold image, create mask mask=np.logical_and(pixels<rangehigh,pixels>rangelow) thresholded_image=np.zeros(np.shape(pixels)) thresholded_image[mask]=1 # Apply convolution to get the sum of sorrounding pixels # First define a weight array w=np.array([[1,1,1],[1,0,1],[1,1,1]]) # Create a new array of sums sums=scind.convolve(thresholded_image,w,mode='constant') # remove 0 pixels from sums sums[~mask]=9 # Get the histogram result = fix(scind.histogram(sums.astype(int),0,9,10,labels=labels, index=indexes)) #print np.shape(result) result = np.vstack(result).T.astype(np.float64) #result = np.random.rand(2,9).T result = result/np.sum(result,axis=0) #result = result[0:9] #result = (result/np.sum(result)).astype(np.float64) # # And we're done! Did you like it? Did you get it? # return result[0:9]