def run_object(self, image_name, object_name, workspace):
        statistics = []
        input_image = workspace.image_set.get_image(image_name,
                                                    must_be_grayscale=True)
        objects = workspace.get_objects(object_name)
        pixels = input_image.pixel_data
        if input_image.has_mask:
            mask = input_image.mask
        else:
            mask = None
        labels = objects.segmented
        try:
            pixels = objects.crop_image_similarly(pixels)
        except ValueError:
            #
            # Recover by cropping the image to the labels
            #
            pixels, m1 = size_similarly(labels, pixels)
            if np.any(~m1):
                if mask is None:
                    mask = m1
                else:
                    mask, m2 = size_similarly(labels, mask)
                    mask[~m2] = False

        if mask is not None:
            labels = labels.copy()
            labels[~mask] = 0

        for name in self.moms.value.split(','):
            fn = MOM_TO_F[name]
            value = get_object_moment(pixels, fn)
            statistics += self.record_measurement(workspace, image_name,
                                                  object_name, name, value)
        return statistics
 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 = size_similarly(labels, pixel_data)
             labels[~m1] = 0
             if mask is not None:
                 mask, m2 = 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
Ejemplo n.º 3
0
    def run_object(self, image_name, object_name, bins, workspace):
        """Run, computing the area measurements for a single map of objects"""
        statistics = []
        image = workspace.image_set.get_image(image_name,
                                              must_be_grayscale=True)
        objects = workspace.get_objects(object_name)
        pixel_data = image.pixel_data
        if image.has_mask:
            mask = image.mask
        else:
            mask = None
        labels = objects.segmented
        try:
            pixel_data = objects.crop_image_similarly(pixel_data)
        except ValueError:
            #
            # Recover by cropping the image to the labels
            #
            pixel_data, m1 = size_similarly(labels, pixel_data)
            if np.any(~m1):
                if mask is None:
                    mask = m1
                else:
                    mask, m2 = size_similarly(labels, mask)
                    mask[~m2] = False

        if mask is not None:
            labels = labels.copy()
            labels[~mask] = 0

        hist = get_objects_histogram(pixel_data, labels, bins)
        if np.all(labels == 0):
            for b in range(0, bins):
                statistics += self.record_measurement(
                    workspace, image_name, object_name,
                    str(bins) + BINS + str(b), np.zeros((0, )))
        else:
            for b in range(0, bins):
                value = hist[:, b]
                statistics += self.record_measurement(
                    workspace, image_name, object_name,
                    str(bins) + BINS + str(b), value)

        return statistics
 def run_one(self, image_name, object_name, scale, angle, workspace):
     """Run, computing the area measurements for a single map of objects"""
     statistics = []
     image = workspace.image_set.get_image(image_name,
                                           must_be_grayscale=True)
     objects = workspace.get_objects(object_name)
     pixel_data = image.pixel_data
     if image.has_mask:
         mask = image.mask
     else:
         mask = None
     labels = objects.segmented
     try:
         pixel_data = objects.crop_image_similarly(pixel_data)
     except ValueError:
         #
         # Recover by cropping the image to the labels
         #
         pixel_data, m1 = size_similarly(labels, pixel_data)
         if np.any(~m1):
             if mask is None:
                 mask = m1
             else:
                 mask, m2 = size_similarly(labels, mask)
                 mask[~m2] = False
         
     if np.all(labels == 0):
         for name in F_HARALICK:
             statistics += self.record_measurement(
                 workspace, image_name, object_name, 
                 str(scale) + "_" + H_TO_A[angle], name, np.zeros((0,)))
     else:
         scale_i, scale_j = self.get_angle_ij(angle, scale)
             
         for name, value in zip(F_HARALICK, Haralick(pixel_data,
                                                     labels,
                                                     scale_i,
                                                     scale_j,
                                                     mask=mask).all()):
             statistics += self.record_measurement(
                 workspace, image_name, object_name, 
                 str(scale) + "_" + H_TO_A[angle], name, value)
     return statistics
    def filter_labels(self, labels_out, objects, workspace):
        """Filter labels out of the output

        Filter labels that are not in the segmented input labels. Optionally
        filter labels that are touching the edge.

        labels_out - the unfiltered output labels
        objects    - the objects thing, containing both segmented and
                     small_removed labels
        """
        segmented_labels = objects.segmented
        max_out = numpy.max(labels_out)
        if max_out > 0:
            segmented_labels, m1 = size_similarly(labels_out, segmented_labels)
            segmented_labels[~m1] = 0
            lookup = scipy.ndimage.maximum(
                segmented_labels, labels_out, list(range(max_out + 1))
            )
            lookup = numpy.array(lookup, int)
            lookup[0] = 0
            segmented_labels_out = lookup[labels_out]
        else:
            segmented_labels_out = labels_out.copy()
        if self.wants_discard_edge:
            image = workspace.image_set.get_image(self.image_name.value)
            if image.has_mask:
                mask_border = image.mask & ~scipy.ndimage.binary_erosion(image.mask)
                edge_labels = segmented_labels_out[mask_border]
            else:
                edge_labels = numpy.hstack(
                    (
                        segmented_labels_out[0, :],
                        segmented_labels_out[-1, :],
                        segmented_labels_out[:, 0],
                        segmented_labels_out[:, -1],
                    )
                )
            edge_labels = numpy.unique(edge_labels)
            #
            # Make a lookup table that translates edge labels to zero
            # but translates everything else to itself
            #
            lookup = numpy.arange(max(max_out, numpy.max(segmented_labels)) + 1)
            lookup[edge_labels] = 0
            #
            # Run the segmented labels through this to filter out edge
            # labels
            segmented_labels_out = lookup[segmented_labels_out]

        return segmented_labels_out
Ejemplo n.º 6
0
    def run_one(self, image_name, object_name, scale, workspace):
        statistics = []

        image = workspace.image_set.get_image(image_name,
                                              must_be_grayscale=True)

        objects = workspace.get_objects(object_name)
        labels = objects.segmented

        gray_levels = int(self.gray_levels.value)

        unique_labels = numpy.unique(labels)
        if unique_labels[0] == 0:
            unique_labels = unique_labels[1:]

        n_directions = 13 if objects.volumetric else 4

        if len(unique_labels) == 0:
            for direction in range(n_directions):
                for feature_name in F_HARALICK:
                    statistics += self.record_measurement(
                        image=image_name,
                        feature=feature_name,
                        obj=object_name,
                        result=numpy.zeros((0, )),
                        scale="{:d}_{:02d}".format(scale, direction),
                        workspace=workspace,
                        gray_levels="{:d}".format(gray_levels),
                    )

            return statistics

        # IMG-961: Ensure image and objects have the same shape.
        try:
            mask = (image.mask if image.has_mask else numpy.ones_like(
                image.pixel_data, dtype=bool))
            pixel_data = objects.crop_image_similarly(image.pixel_data)
        except ValueError:
            pixel_data, m1 = size_similarly(labels, image.pixel_data)

            if numpy.any(~m1):
                if image.has_mask:
                    mask, m2 = size_similarly(labels, image.mask)
                    mask[~m2] = False
                else:
                    mask = m1

        pixel_data[~mask] = 0
        # mahotas.features.haralick bricks itself when provided a dtype larger than uint8 (version 1.4.3)
        pixel_data = skimage.util.img_as_ubyte(pixel_data)
        if gray_levels != 256:
            pixel_data = skimage.exposure.rescale_intensity(
                pixel_data, in_range=(0, 255),
                out_range=(0, gray_levels - 1)).astype(numpy.uint8)
        props = skimage.measure.regionprops(labels, pixel_data)

        features = numpy.empty((n_directions, 13, len(unique_labels)))

        for index, prop in enumerate(props):
            label_data = prop["intensity_image"]
            try:
                features[:, :,
                         index] = mahotas.features.haralick(label_data,
                                                            distance=scale,
                                                            ignore_zeros=True)
            except ValueError:
                features[:, :, index] = numpy.nan

        for direction, direction_features in enumerate(features):
            for feature_name, feature in zip(F_HARALICK, direction_features):
                statistics += self.record_measurement(
                    image=image_name,
                    feature=feature_name,
                    obj=object_name,
                    result=feature,
                    scale="{:d}_{:02d}".format(scale, direction),
                    workspace=workspace,
                    gray_levels="{:d}".format(gray_levels),
                )

        return statistics
    def run(self, workspace):
        """Run the module on the current data set

        workspace - has the current image set, object set, measurements
                    and the parent frame for the application if the module
                    is allowed to display. If the module should not display,
                    workspace.frame is None.
        """
        #
        # The object set holds "objects". Each of these is a container
        # for holding up to three kinds of image labels.
        #
        object_set = workspace.object_set
        #
        # Get the primary objects (the centers to be removed).
        # Get the string value out of primary_object_name.
        #
        primary_objects = object_set.get_objects(
            self.primary_objects_name.value)
        #
        # Get the cleaned-up labels image
        #
        primary_labels = primary_objects.segmented
        #
        # Do the same with the secondary object
        secondary_objects = object_set.get_objects(
            self.secondary_objects_name.value)
        secondary_labels = secondary_objects.segmented
        #
        # If one of the two label images is smaller than the other, we
        # try to find the cropping mask and we apply that mask to the larger
        #
        try:
            if any([
                    p_size < s_size for p_size, s_size in zip(
                        primary_labels.shape, secondary_labels.shape)
            ]):
                #
                # Look for a cropping mask associated with the primary_labels
                # and apply that mask to resize the secondary labels
                #
                secondary_labels = primary_objects.crop_image_similarly(
                    secondary_labels)
                tertiary_image = primary_objects.parent_image
            elif any([
                    p_size > s_size for p_size, s_size in zip(
                        primary_labels.shape, secondary_labels.shape)
            ]):
                primary_labels = secondary_objects.crop_image_similarly(
                    primary_labels)
                tertiary_image = secondary_objects.parent_image
            elif secondary_objects.parent_image is not None:
                tertiary_image = secondary_objects.parent_image
            else:
                tertiary_image = primary_objects.parent_image
        except ValueError:
            # No suitable cropping - resize all to fit the secondary
            # labels which are the most critical.
            #
            primary_labels, _ = size_similarly(secondary_labels,
                                               primary_labels)
            if secondary_objects.parent_image is not None:
                tertiary_image = secondary_objects.parent_image
            else:
                tertiary_image = primary_objects.parent_image
                if tertiary_image is not None:
                    tertiary_image, _ = size_similarly(secondary_labels,
                                                       tertiary_image)
        # If size/shape differences were too extreme, raise an error.
        if primary_labels.shape != secondary_labels.shape:
            raise ValueError(
                "This module requires that the object sets have matching widths and matching heights.\n"
                "The %s and %s objects do not (%s vs %s).\n"
                "If they are paired correctly you may want to use the ResizeObjects module "
                "to make them the same size." % (
                    self.secondary_objects_name,
                    self.primary_objects_name,
                    secondary_labels.shape,
                    primary_labels.shape,
                ))

        #
        # Find the outlines of the primary image and use this to shrink the
        # primary image by one. This guarantees that there is something left
        # of the secondary image after subtraction
        #
        primary_outline = outline(primary_labels)
        tertiary_labels = secondary_labels.copy()
        if self.shrink_primary:
            primary_mask = numpy.logical_or(primary_labels == 0,
                                            primary_outline)
        else:
            primary_mask = primary_labels == 0
        tertiary_labels[primary_mask == False] = 0
        #
        # Get the outlines of the tertiary image
        #
        tertiary_outlines = outline(tertiary_labels) != 0
        #
        # Make the tertiary objects container
        #
        tertiary_objects = Objects()
        tertiary_objects.segmented = tertiary_labels
        tertiary_objects.parent_image = tertiary_image
        #
        # Relate tertiary objects to their parents & record
        #
        child_count_of_secondary, secondary_parents = secondary_objects.relate_children(
            tertiary_objects)
        if self.shrink_primary:
            child_count_of_primary, primary_parents = primary_objects.relate_children(
                tertiary_objects)
        else:
            # Primary and tertiary don't overlap.
            # Establish overlap between primary and secondary and commute
            _, secondary_of_primary = secondary_objects.relate_children(
                primary_objects)
            mask = secondary_of_primary != 0
            child_count_of_primary = numpy.zeros(mask.shape, int)
            child_count_of_primary[mask] = child_count_of_secondary[
                secondary_of_primary[mask] - 1]
            primary_parents = numpy.zeros(secondary_parents.shape,
                                          secondary_parents.dtype)
            primary_of_secondary = numpy.zeros(secondary_objects.count + 1,
                                               int)
            primary_of_secondary[secondary_of_primary] = numpy.arange(
                1,
                len(secondary_of_primary) + 1)
            primary_of_secondary[0] = 0
            primary_parents = primary_of_secondary[secondary_parents]
        #
        # Write out the objects
        #
        workspace.object_set.add_objects(tertiary_objects,
                                         self.subregion_objects_name.value)
        #
        # Write out the measurements
        #
        m = workspace.measurements
        #
        # The parent/child associations
        #
        for parent_objects_name, parents_of, child_count, relationship in (
            (
                self.primary_objects_name,
                primary_parents,
                child_count_of_primary,
                R_REMOVED,
            ),
            (
                self.secondary_objects_name,
                secondary_parents,
                child_count_of_secondary,
                R_PARENT,
            ),
        ):
            m.add_measurement(
                self.subregion_objects_name.value,
                FF_PARENT % parent_objects_name.value,
                parents_of,
            )
            m.add_measurement(
                parent_objects_name.value,
                FF_CHILDREN_COUNT % self.subregion_objects_name.value,
                child_count,
            )
            mask = parents_of != 0
            image_number = numpy.ones(numpy.sum(mask),
                                      int) * m.image_set_number
            child_object_number = numpy.argwhere(mask).flatten() + 1
            parent_object_number = parents_of[mask]
            m.add_relate_measurement(
                self.module_num,
                relationship,
                parent_objects_name.value,
                self.subregion_objects_name.value,
                image_number,
                parent_object_number,
                image_number,
                child_object_number,
            )

        object_count = tertiary_objects.count
        #
        # The object count
        #
        add_object_count_measurements(workspace.measurements,
                                      self.subregion_objects_name.value,
                                      object_count)
        #
        # The object locations
        #
        add_object_location_measurements(workspace.measurements,
                                         self.subregion_objects_name.value,
                                         tertiary_labels)

        if self.show_window:
            workspace.display_data.primary_labels = primary_labels
            workspace.display_data.secondary_labels = secondary_labels
            workspace.display_data.tertiary_labels = tertiary_labels
            workspace.display_data.tertiary_outlines = tertiary_outlines
Ejemplo n.º 8
0
    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 = numpy.max(labels)
        #
        # Resize the mask to cover the objects
        #
        mask, m1 = 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 = fixup_scipy_ndimage_result(
                scipy.ndimage.sum(
                    mask, labels, numpy.arange(1, nobjects + 1, dtype=numpy.int32)
                )
            )
            if self.overlap_choice == P_KEEP:
                keep = pixel_counts > 0
            else:
                total_pixels = fixup_scipy_ndimage_result(
                    scipy.ndimage.sum(
                        numpy.ones(labels.shape),
                        labels,
                        numpy.arange(1, nobjects + 1, dtype=numpy.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 = numpy.hstack(([False], keep))
            labels[~keep[labels]] = 0
        #
        # Renumber the labels matrix if requested
        #
        if self.retain_or_renumber == R_RENUMBER:
            unique_labels = numpy.unique(labels[labels != 0])
            indexer = numpy.zeros(nobjects + 1, int)
            indexer[unique_labels] = numpy.arange(1, len(unique_labels) + 1)
            labels = indexer[labels]
            parent_objects = unique_labels
        else:
            parent_objects = numpy.arange(1, nobjects + 1)
        #
        # Add the objects
        #
        remaining_objects = 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, FF_PARENT % object_name, parent_objects,
        )
        if numpy.max(original_objects.segmented) == 0:
            child_count = numpy.array([], int)
        else:
            child_count = fixup_scipy_ndimage_result(
                scipy.ndimage.sum(
                    labels,
                    original_objects.segmented,
                    numpy.arange(1, nobjects + 1, dtype=numpy.int32),
                )
            )
            child_count = (child_count > 0).astype(int)
        m.add_measurement(
            object_name, 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)
        add_object_count_measurements(m, remaining_object_name, remaining_object_count)
        add_object_location_measurements(m, remaining_object_name, labels)
        #
        # 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_one_tamura(self, image_name, object_name, workspace):
     """Run, computing the area measurements for a single map of objects"""
     statistics = []
     image = workspace.image_set.get_image(image_name,
                                           must_be_grayscale=True)
     objects = workspace.get_objects(object_name)
     pixel_data = image.pixel_data
     if image.has_mask:
         mask = image.mask
     else:
         mask = None
     labels = objects.segmented
     try:
         pixel_data = objects.crop_image_similarly(pixel_data)
     except ValueError:
         #
         # Recover by cropping the image to the labels
         #
         pixel_data, m1 = size_similarly(labels, pixel_data)
         if np.any(~m1):
             if mask is None:
                 mask = m1
             else:
                 mask, m2 = size_similarly(labels, mask)
                 mask[~m2] = False
         
     if np.all(labels == 0):
         for name in F_ALL:
             statistics += self.record_measurement(workspace, image_name, 
                                                   object_name, "", "%s_%s" % (F_TAMURA, name), 
                                                   np.zeros((0,)))
     else:
         labs=np.unique(labels)
         values=np.zeros([np.max(labs)+1,2])
         for l in labs:  
             if l!=0:
                 px = pixel_data
                 px[np.where(labels != l)] = 0.0
                 values[l,0]=self.contrast(px)
                 values[l,1]=self.directionality(px)
                 statistics += self.record_measurement(
                     workspace, image_name, object_name, 
                     "-", "%s_%s" % (F_TAMURA, F_2), values[:,0])               
                 statistics += self.record_measurement(
                     workspace, image_name, object_name, 
                     "-", "%s_%s" % (F_TAMURA, F_3), values[:,1])
         
         coars = np.zeros([np.max(labs)+1])
         coars_hist=np.zeros([np.max(labs)+1,HIST_COARS_BINS])
         for l in labs:  
             if l!=0:
                 px = pixel_data
                 px[np.where(labels != l)] = 0.0 
                 coars[l], coars_hist[l,:] = self.coarseness(px) 
                 statistics += self.record_measurement(
                     workspace, image_name, object_name, 
                     "-", "%s_%s" % (F_TAMURA, F_1), coars)  
         for b in range(0,HIST_COARS_BINS):
             value = coars_hist[1:,b] 
             name = "CoarsenessHist_%dBinsHist_Bin%d" % (HIST_COARS_BINS, b)
             statistics += self.record_measurement(
                 workspace, image_name, object_name, 
                 "-", "%s_%s" % (F_TAMURA, name) , value)     
             
     return statistics
Ejemplo n.º 10
0
    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 = numpy.max(labels)
        label_range = numpy.arange(labels_count, dtype=numpy.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 = 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 = centrosome.cpmorphology.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 = centrosome.cpmorphology.fill_labeled_holes(
                combined_skel, ~seed_center, size_fn
            )
        #
        # Reskeletonize to make true branchpoints at the ring boundaries
        #
        combined_skel = centrosome.cpmorphology.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(
            numpy.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 = centrosome.cpmorphology.branchpoints(combined_skel)
        #
        # Odd case: when four branches meet like this, branchpoints are not
        # assigned because they are arbitrary. So assign them.
        #
        # .  .
        #  B.
        #  .B
        # .  .
        #
        odd_case = (
            combined_skel[:-1, :-1]
            & combined_skel[1:, :-1]
            & combined_skel[:-1, 1:]
            & combined_skel[1, 1]
        )
        branch_points[:-1, :-1][odd_case] = True
        branch_points[1:, 1:][odd_case] = True
        #
        # Find the branching counts for the trunks (# of extra branches
        # emanating from a point other than the line it might be on).
        #
        branching_counts = centrosome.cpmorphology.branchings(combined_skel)
        branching_counts = numpy.array([0, 0, 0, 1, 2])[branching_counts]
        #
        # Only take branches within 1 of the outside skeleton
        #
        dilated_skel = scipy.ndimage.binary_dilation(
            outside_skel, centrosome.cpmorphology.eight_connect
        )
        branching_counts[~dilated_skel] = 0
        #
        # Find the endpoints
        #
        end_points = centrosome.cpmorphology.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(
                scipy.ndimage.sum(branching_counts, nearby_labels, label_range)
            ).astype(int)
        else:
            trunk_counts = numpy.zeros((0,), int)
        #
        # The branches are the branchpoints that lie outside the seed objects
        #
        if labels_count > 0:
            branch_counts = fix(
                scipy.ndimage.sum(branch_points, outside_labels, label_range)
            )
        else:
            branch_counts = numpy.zeros((0,), int)
        #
        # Save the endpoints
        #
        if labels_count > 0:
            end_counts = fix(scipy.ndimage.sum(end_points, outside_labels, label_range))
        else:
            end_counts = numpy.zeros((0,), int)
        #
        # Calculate the distances
        #
        total_distance = centrosome.cpmorphology.skeleton_length(
            dlabels * outside_skel, label_range
        )
        #
        # Save measurements
        #
        m = workspace.measurements
        assert isinstance(m, Measurements)
        feature = "_".join((C_OBJSKELETON, F_NUMBER_TRUNKS, skeleton_name))
        m.add_measurement(seed_objects_name, feature, trunk_counts)
        feature = "_".join((C_OBJSKELETON, F_NUMBER_NON_TRUNK_BRANCHES, skeleton_name))
        m.add_measurement(seed_objects_name, feature, branch_counts)
        feature = "_".join((C_OBJSKELETON, F_NUMBER_BRANCH_ENDS, skeleton_name))
        m.add_measurement(seed_objects_name, feature, end_counts)
        feature = "_".join((C_OBJSKELETON, F_TOTAL_OBJSKELETON_LENGTH, skeleton_name))
        m[seed_objects_name, feature] = total_distance
        #
        # Collect the graph information
        #
        if self.wants_objskeleton_graph:
            trunk_mask = (branching_counts > 0) & (nearby_labels != 0)
            intensity_image = workspace.image_set.get_image(
                self.intensity_image_name.value
            )
            edge_graph, vertex_graph = self.make_objskeleton_graph(
                combined_skel,
                dlabels,
                trunk_mask,
                branch_points & ~trunk_mask,
                end_points,
                intensity_image.pixel_data,
            )

            image_number = workspace.measurements.image_set_number

            edge_path, vertex_path = self.get_graph_file_paths(m, m.image_number)
            workspace.interaction_request(
                self,
                m.image_number,
                edge_path,
                edge_graph,
                vertex_path,
                vertex_graph,
                headless_ok=True,
            )

            if self.show_window:
                workspace.display_data.edge_graph = edge_graph
                workspace.display_data.vertex_graph = vertex_graph
                workspace.display_data.intensity_image = intensity_image.pixel_data
        #
        # Make the display image
        #
        if self.show_window or self.wants_branchpoint_image:
            branchpoint_image = numpy.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, :] *= 0.875
            branchpoint_image[dilated_labels != 0, :] += 0.1
            if self.show_window:
                workspace.display_data.branchpoint_image = branchpoint_image
            if self.wants_branchpoint_image:
                bi = Image(branchpoint_image, parent_image=skeleton_image)
                workspace.image_set.add(self.branchpoint_image_name.value, bi)