def run(self, workspace): # # Get the measurements object - we put the measurements we # make in here # meas = workspace.measurements assert isinstance(meas, cpmeas.Measurements) # # We record some statistics which we will display later. # We format them so that Matplotlib can display them in a table. # The first row is a header that tells what the fields are. # statistics = [ [ "Feature", "Mean", "Median", "SD"] ] # # Put the statistics in the workspace display data so we # can get at them when we display # workspace.display_data.statistics = statistics # # Get the input image and object. You need to get the .value # because otherwise you'll get the setting object instead of # the string name. # input_image_name = self.input_image_name.value input_object_name = self.input_object_name.value ################################################################ # # GETTING AN IMAGE FROM THE IMAGE SET # # Get the image set. The image set has all of the images in it. # image_set = workspace.image_set # # Get the input image object. We want a grayscale image here. # The image set will convert a color image to a grayscale one # and warn the user. # input_image = image_set.get_image(input_image_name, must_be_grayscale = True) # # Get the pixels - these are a 2-d Numpy array. # pixels = input_image.pixel_data # ############################################################### # # GETTING THE LABELS MATRIX FROM THE OBJECT SET # # The object set has all of the objects in it. # object_set = workspace.object_set assert isinstance(object_set, cpo.ObjectSet) # # Get objects from the object set. The most useful array in # the objects is "objects.segmented" which is a labels matrix # in which each pixel has an integer value. # # The value, "0", is reserved for "background" - a pixel with # a zero value is not in an object. Each object has an object # number, starting at "1" and each pixel in the object is # labeled with that number. # # The other useful array is "objects.small_removed_segmented" which # is another labels matrix. There are objects that touch the edge of # the image and get filtered out and there are large objects that # get filtered out. Modules like "IdentifySecondaryObjects" may # want to associate pixels near the objects in the labels matrix to # those objects - the large and touching objects should compete with # the real ones, so you should use "objects.small_removed_segmented" # for those cases. # objects = object_set.get_objects(input_object_name) labels = objects.segmented # ########################################### # # 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 = objects.get_indices() # # 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) ############################################################### # # The module computes a measurement based on the image intensity # inside an object times a Zernike polynomial inscribed in the # minimum enclosing circle around the object. The details are # in the "measure_zernike" function. We call into the function with # an N and M which describe the polynomial. # for n, m in self.get_zernike_indexes(): # Compute the zernikes for each object, returned in an array zr, zi = self.measure_zernike( pixels, labels, indexes, centers, radius, n, m) # Get the name of the measurement feature for this zernike feature = self.get_measurement_name(n, m) # Add a measurement for this kind of object if m != 0: meas.add_measurement(input_object_name, feature, zr) # # Do the same with -m # feature = self.get_measurement_name(n, -m) meas.add_measurement(input_object_name, feature, zi) else: # For zero, the total is the sum of real and imaginary parts meas.add_measurement(input_object_name, feature, zr + zi) # # Record the statistics. # zmean = np.mean(zr) zmedian = np.median(zr) zsd = np.std(zr) statistics.append( [ feature, zmean, zmedian, zsd ] )
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 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