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_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 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 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