Example #1
0
            def __init__(self, index, operation):
                self.__index = index
                self.__operation = operation
                self.__operand_choice = Choice(
                    self.operand_choice_text(),
                    MC_ALL,
                    doc="""Indicate whether the operand is an image or object measurement.""",
                )

                self.__operand_objects = LabelName(
                    self.operand_objects_text(),
                    "None",
                    doc="""Choose the objects you want to measure for this operation.""",
                )

                self.__operand_measurement = Measurement(
                    self.operand_measurement_text(),
                    self.object_fn,
                    doc="""\
Enter the category that was used to create the measurement. You
will be prompted to add additional information depending on
the type of measurement that is requested.""",
                )

                self.__multiplicand = Float(
                    "Multiply the above operand by",
                    1,
                    doc="""Enter the number by which you would like to multiply the above operand.""",
                )

                self.__exponent = Float(
                    "Raise the power of above operand by",
                    1,
                    doc="""Enter the power by which you would like to raise the above operand.""",
                )
Example #2
0
    def create_settings(self):
        self.image_name = ImageSubscriber(
            "Select the input image",
            "None",
            doc=
            """Choose the name of the image to display in the object selection user interface.""",
        )

        self.objects_name = LabelName(
            "Name the objects to be identified",
            "Cells",
            doc="""\
What do you want to call the objects that you identify using this module? You can use this name to
refer to your objects in subsequent modules.""",
        )
    def create_settings(self):
        self.objects_x = LabelSubscriber(
            "Select initial object set",
            "None",
            doc="""Select an object set which you want to add objects to.""",
        )

        self.objects_y = LabelSubscriber(
            "Select object set to combine",
            "None",
            doc=
            """Select an object set which you want to add to the initial set.""",
        )

        self.merge_method = Choice(
            "Select how to handle overlapping objects",
            choices=["Merge", "Preserve", "Discard", "Segment"],
            doc="""\
When combining sets of objects, it is possible that both sets had an object in the
same location. Use this setting to choose how to handle objects which overlap with
eachother.
        
- Selecting "Merge" will make overlapping objects combine into a single object, taking
  on the label of the object from the initial set. When an added object would overlap
  with multiple objects from the initial set, each pixel of the added object will be
  assigned to the closest object from the initial set. This is primarily useful when
  the same objects appear in both sets.
        
- Selecting "Preserve" will protect the initial object set. Any overlapping regions
  from the second set will be ignored in favour of the object from the initial set.
        
- Selecting "Discard" will only add objects which do not have any overlap with objects
  in the initial object set.
        
- Selecting "Segment" will combine both object sets and attempt to re-draw segmentation to
  separate objects which overlapped. Note: This is less reliable when more than
  two objects were overlapping. If two object sets genuinely occupy the same space
  it may be better to consider them seperately.
         """,
        )

        self.output_object = LabelName(
            "Name the combined object set",
            "CombinedObjects",
            doc="""\
Enter the name for the combined object set. These objects will be available for use in
subsequent modules.""",
        )
    def create_settings(self):
        """Create the settings for the module

        Create the settings for the module during initialization.
        """
        self.secondary_objects_name = LabelSubscriber(
            "Select the larger identified objects",
            "None",
            doc="""\
Select the larger identified objects. This will usually be an object
previously identified by an **IdentifySecondaryObjects** module.""",
        )

        self.primary_objects_name = LabelSubscriber(
            "Select the smaller identified objects",
            "None",
            doc="""\
Select the smaller identified objects. This will usually be an object
previously identified by an **IdentifyPrimaryObjects** module.""",
        )

        self.subregion_objects_name = LabelName(
            "Name the tertiary objects to be identified",
            "Cytoplasm",
            doc="""\
Enter a name for the new tertiary objects. The tertiary objects
will consist of the smaller object subtracted from the larger object.""",
        )

        self.shrink_primary = Binary(
            "Shrink smaller object prior to subtraction?",
            True,
            doc="""\
Select *Yes* to shrink the smaller objects by 1 pixel before
subtracting them from the larger objects. this approach will ensure that
there is always a tertiary object produced, even if it is only 1 pixel wide.
If you need alternate amounts of shrinking, use the **ExpandOrShrink**
module prior to **IdentifyTertiaryObjects**.

Select *No* to subtract the objects directly, which will ensure that
no pixels are shared between the primary/secondary/tertiary objects and
hence measurements for all three sets of objects will not use the same
pixels multiple times. However, this may result in the creation of
objects with no area. Measurements can still be made on such objects,
but the results will be zero or not-a-number (NaN).
""" % globals(),
        )
Example #5
0
    def add_additional_object(self):
        group = SettingsGroup()

        group.append(
            "object_name",
            LabelSubscriber("Select additional object to relabel", "None"),
        )

        group.append(
            "target_name", LabelName("Name the relabeled objects", "FilteredGreen"),
        )

        group.append(
            "remover",
            RemoveSettingButton(
                "", "Remove this additional object", self.additional_objects, group
            ),
        )

        group.append("divider", Divider(line=False))

        self.additional_objects.append(group)
Example #6
0
    def create_settings(self):
        self.object_name = LabelSubscriber(
            "Select the input objects",
            "None",
            doc="Select the objects that you want to expand or shrink.",
        )

        self.output_object_name = LabelName(
            "Name the output objects",
            "ShrunkenNuclei",
            doc="Enter a name for the resulting objects.",
        )

        self.operation = Choice(
            "Select the operation",
            O_ALL,
            doc="""\
Choose the operation that you want to perform:

-  *{O_SHRINK_INF}:* Remove all pixels but one from filled objects.
   Thin objects with holes to loops unless the “fill” option is checked.
   Objects are never lost using this module (shrinking stops when an
   object becomes a single pixel).
-  *{O_EXPAND_INF}:* Expand objects, assigning every pixel in the
   image to an object. Background pixels are assigned to the nearest
   object.
-  *{O_DIVIDE}:* Remove pixels from an object that are adjacent to
   another object’s pixels unless doing so would change the object’s
   Euler number (break an object in two, remove the object completely or
   open a hole in an object).
-  *{O_SHRINK}:* Remove pixels around the perimeter of an object unless
   doing so would change the object’s Euler number (break the object in
   two, remove the object completely or open a hole in the object). You
   can specify the number of times perimeter pixels should be removed.
   Processing stops automatically when there are no more pixels to
   remove. Objects are never lost using this module (shrinking
   stops when an object becomes a single pixel).
-  *{O_EXPAND}:* Expand each object by adding background pixels
   adjacent to the image. You can choose the number of times to expand.
   Processing stops automatically if there are no more background
   pixels.
-  *{O_SKELETONIZE}:* Erode each object to its skeleton.
-  *{O_SPUR}:* Remove or reduce the length of spurs in a skeletonized
   image. The algorithm reduces spur size by the number of pixels
   indicated in the setting *Number of pixels by which to expand or
   shrink*.
""".format(
                **{
                    "O_DIVIDE": O_DIVIDE,
                    "O_EXPAND": O_EXPAND,
                    "O_EXPAND_INF": O_EXPAND_INF,
                    "O_SHRINK": O_SHRINK,
                    "O_SHRINK_INF": O_SHRINK_INF,
                    "O_SKELETONIZE": O_SKELETONIZE,
                    "O_SPUR": O_SPUR,
                }),
        )

        self.iterations = Integer(
            "Number of pixels by which to expand or shrink",
            1,
            minval=1,
            doc="""\
*(Used only if "{O_SHRINK}", "{O_EXPAND}", or "{O_SPUR}" is selected)*

Specify the number of pixels to add or remove from object borders.
""".format(**{
                "O_EXPAND": O_EXPAND,
                "O_SHRINK": O_SHRINK,
                "O_SPUR": O_SPUR
            }),
        )

        self.wants_fill_holes = Binary(
            "Fill holes in objects so that all objects shrink to a single point?",
            False,
            doc="""\
*(Used only if one of the “Shrink” options selected)*

Select *{YES}* to ensure that each object will shrink to a single
point, by filling the holes in each object.

Select *{NO}* to preserve the Euler number. In this case, the shrink
algorithm preserves each object’s Euler number, which means that it will
erode an object with a hole to a ring in order to keep the hole. An
object with two holes will be shrunk to two rings connected by a line in
order to keep from breaking up the object or breaking the hole.
""".format(**{
                "NO": "No",
                "YES": "Yes"
            }),
        )
    def create_settings(self):
        """Create the settings for the module

        Create the settings for the module during initialization.
        """
        self.image_name = ImageSubscriber(
            "Select the input image",
            "None",
            doc="""\
The name of a binary image from a previous module. **IdentifyDeadWorms**
will use this image to establish the foreground and background for the
fitting operation. You can use **ApplyThreshold** to threshold a
grayscale image and create the binary mask. You can also use a module
such as **IdentifyPrimaryObjects** to label each worm and then use
**ConvertObjectsToImage** to make the result a mask.
""",
        )

        self.object_name = LabelName(
            "Name the dead worm objects to be identified",
            "DeadWorms",
            doc="""\
This is the name for the dead worm objects. You can refer
to this name in subsequent modules such as
**IdentifySecondaryObjects**""",
        )

        self.worm_width = Integer(
            "Worm width",
            10,
            minval=1,
            doc="""\
This is the width (the short axis), measured in pixels,
of the diamond used as a template when
matching against the worm. It should be less than the width
of a worm.""",
        )

        self.worm_length = Integer(
            "Worm length",
            100,
            minval=1,
            doc="""\
This is the length (the long axis), measured in pixels,
of the diamond used as a template when matching against the
worm. It should be less than the length of a worm""",
        )

        self.angle_count = Integer(
            "Number of angles",
            32,
            minval=1,
            doc="""\
This is the number of different angles at which the template will be
tried. For instance, if there are 12 angles, the template will be
rotated by 0°, 15°, 30°, 45° … 165°. The shape is bilaterally symmetric;
that is, you will get the same shape after rotating it by 180°.
""",
        )

        self.wants_automatic_distance = Binary(
            "Automatically calculate distance parameters?",
            True,
            doc="""\
This setting determines whether or not **IdentifyDeadWorms**
automatically calculates the parameters used to determine whether two
found-worm centers belong to the same worm.

Select "*Yes*" to have **IdentifyDeadWorms** automatically calculate
the distance from the worm length and width. Select "*No*" to set the
distances manually.
"""
            % globals(),
        )

        self.space_distance = Float(
            "Spatial distance",
            5,
            minval=1,
            doc="""\
*(Used only if not automatically calculating distance parameters)*

Enter the distance for calculating the worm centers, in units of pixels.
The worm centers must be at least many pixels apart for the centers to
be considered two separate worms.
""",
        )

        self.angular_distance = Float(
            "Angular distance",
            30,
            minval=1,
            doc="""\
*(Used only if automatically calculating distance parameters)*

**IdentifyDeadWorms** calculates the worm centers at different angles.
Two worm centers are considered to represent different worms if their
angular distance is larger than this number. The number is measured in
degrees.
""",
        )
    def create_settings(self):
        super(RelateObjects, self).create_settings()

        self.x_name.text = "Parent objects"

        self.x_name.doc = """\
Parent objects are defined as those objects which encompass the child object.
For example, when relating speckles to the nuclei that contain them,
the nuclei are the parents.
        """

        self.y_name = LabelSubscriber(
            "Child objects",
            doc="""\
Child objects are defined as those objects contained within the parent object. For example, when relating
speckles to the nuclei that contains them, the speckles are the children.
            """,
        )

        self.find_parent_child_distances = Choice(
            "Calculate child-parent distances?",
            D_ALL,
            doc="""\
Choose the method to calculate distances of each child to its parent.
For example, these measurements can tell you whether nuclear speckles
are located more closely to the center of the nucleus or to the nuclear
periphery.

-  *{D_NONE}:* Do not calculate any distances. This saves computation time.
-  *{D_MINIMUM}:* The distance from the centroid of the child object to
   the closest perimeter point on the parent object.
-  *{D_CENTROID}:* The distance from the centroid of the child object
   to the centroid of the parent.
-  *{D_BOTH}:* Calculate both the *{D_MINIMUM}* and *{D_CENTROID}*
   distances.""".format(
                **{
                    "D_NONE": D_NONE,
                    "D_MINIMUM": D_MINIMUM,
                    "D_CENTROID": D_CENTROID,
                    "D_BOTH": D_BOTH,
                }),
        )

        self.wants_step_parent_distances = Binary(
            "Calculate distances to other parents?",
            False,
            doc="""\
*(Used only if calculating distances)*

Select "*{YES}*" to calculate the distances of the child objects to some
other objects. These objects must be either parents or children of your
parent object in order for this module to determine the distances. For
instance, you might find “Nuclei” using **IdentifyPrimaryObjects**, find
“Cells” using **IdentifySecondaryObjects** and find “Cytoplasm” using
**IdentifyTertiaryObjects**. You can use **Relate** to relate speckles
to cells and then measure distances to nuclei and cytoplasm. You could
not use **RelateObjects** to relate speckles to cytoplasm and then
measure distances to nuclei, because nuclei are neither a direct parent
nor child of cytoplasm.""".format(**{"YES": "Yes"}),
        )

        self.step_parent_names = []

        self.add_step_parent(can_delete=False)

        self.add_step_parent_button = DoSomething("", "Add another parent",
                                                  self.add_step_parent)

        self.wants_per_parent_means = Binary(
            "Calculate per-parent means for all child measurements?",
            False,
            doc="""\
Select "*{YES}*" to calculate the per-parent mean values of every upstream
measurement made with the children objects and store them as a
measurement for the parent; the nomenclature of this new measurement is
“Mean_<child>_<category>_<feature>”. This module
must be placed *after* all **Measure** modules that make measurements
of the children objects.""".format(**{"YES": "Yes"}),
        )

        self.wants_child_objects_saved = Binary(
            "Do you want to save the children with parents as a new object set?",
            False,
            doc="""\
Select "*{YES}*" to save the children objects that do have parents as new
object set. Objects with no parents will be discarded""".format(
                **{"YES": "Yes"}),
        )

        self.output_child_objects_name = LabelName(
            "Name the output object",
            "RelateObjects",
            doc="""\
Enter the name you want to call the object produced by this module. """,
        )
    def create_settings(self):
        """Create your settings by subclassing this function

        create_settings is called at the end of initialization.
        """
        self.grid_name = GridSubscriber(
            "Select the defined grid",
            "None",
            doc=
            """Select the name of a grid created by a previous **DefineGrid** module.""",
        )

        self.output_objects_name = LabelName(
            "Name the objects to be identified",
            "Wells",
            doc="""\
Enter the name of the grid objects identified by this module. These objects
will be available for further measurement and processing in subsequent modules.""",
        )

        self.shape_choice = Choice(
            "Select object shapes and locations",
            [
                SHAPE_RECTANGLE, SHAPE_CIRCLE_FORCED, SHAPE_CIRCLE_NATURAL,
                SHAPE_NATURAL
            ],
            doc="""\
Use this setting to choose the method to be used to determine the grid
objects’ shapes and locations:

-  *%(SHAPE_RECTANGLE)s:* Each object will be created as a rectangle,
   completely occupying the entire grid compartment (rectangle). This
   option creates the rectangular objects based solely on the grid’s
   specifications, not on any previously identified guiding objects.
-  *%(SHAPE_CIRCLE_FORCED)s:* Each object will be created as a circle,
   centered in the middle of each grid compartment. This option places
   the circular objects’ locations based solely on the grid’s
   specifications, not on any previously identified guiding objects. The
   radius of all circles in a grid will be constant for the entire grid
   in each image cycle, and can be determined automatically for each
   image cycle based on the average radius of previously identified
   guiding objects for that image cycle, or instead it can be specified
   as a single radius for all circles in all grids in the entire
   analysis run.
-  *%(SHAPE_CIRCLE_NATURAL)s:* Each object will be created as a
   circle, and each circle’s location within its grid compartment will
   be determined based on the location of any previously identified
   guiding objects within that grid compartment. Thus, if a guiding
   object lies within a particular grid compartment, that object’s
   center will be the center of the created circular object. If no
   guiding objects lie within a particular grid compartment, the
   circular object is placed within the center of that grid compartment.
   If more than one guiding object lies within the grid compartment,
   they will be combined and the centroid of this combined object will
   be the location of the created circular object. Note that guiding
   objects whose centers are close to the grid edge are ignored.
-  *%(SHAPE_NATURAL)s:* Within each grid compartment, the object will
   be identified based on combining all of the parts of guiding objects,
   if any, that fall within the grid compartment. Note that guiding
   objects whose centers are close to the grid edge are ignored. If a
   guiding object does not exist within a grid compartment, an object
   consisting of one single pixel in the middle of the grid compartment
   will be created.
""" % globals(),
        )

        self.diameter_choice = Choice(
            "Specify the circle diameter automatically?",
            [AM_AUTOMATIC, AM_MANUAL],
            doc="""\
*(Used only if "Circle" is selected as object shape)*

There are two methods for selecting the circle diameter:

-  *%(AM_AUTOMATIC)s:* Uses the average diameter of previously
   identified guiding objects as the diameter.
-  *%(AM_MANUAL)s:* Lets you specify the diameter directly, as a
   number.
""" % globals(),
        )

        self.diameter = Integer(
            "Circle diameter",
            20,
            minval=2,
            doc="""\
*(Used only if "Circle" is selected as object shape and diameter is
specified manually)*

Enter the diameter to be used for each grid circle, in pixels.
{dist}
""".format(dist=HELP_ON_MEASURING_DISTANCES),
        )

        self.guiding_object_name = LabelSubscriber(
            "Select the guiding objects",
            "None",
            doc="""\
*(Used only if "Circle" is selected as object shape and diameter is
specified automatically, or if "Natural Location" is selected as the
object shape)*

Select the names of previously identified objects that will be used to
guide the shape and/or location of the objects created by this module,
depending on the method chosen.
""",
        )
Example #10
0
    def create_settings(self):
        """Create your settings by subclassing this function

        create_settings is called at the end of initialization.

        You should create the setting variables for your module here:
            # Ask the user for the input image
            self.image_name = .ImageSubscriber(...)
            # Ask the user for the name of the output image
            self.output_image = .ImageName(...)
            # Ask the user for a parameter
            self.smoothing_size = .Float(...)
        """
        self.object_name = LabelSubscriber(
            "Select the objects to be edited",
            "None",
            doc="""\
Choose a set of previously identified objects
for editing, such as those produced by one of the
**Identify** modules (e.g., "*IdentifyPrimaryObjects*", "*IdentifySecondaryObjects*" etc.).""",
        )

        self.filtered_objects = LabelName(
            "Name the edited objects",
            "EditedObjects",
            doc="""\
Enter the name for the objects that remain
after editing. These objects will be available for use by
subsequent modules.""",
        )

        self.allow_overlap = Binary(
            "Allow overlapping objects?",
            False,
            doc="""\
**EditObjectsManually** can allow you to edit an object so that it
overlaps another or it can prevent you from overlapping one object with
another. Objects such as worms or the neurites of neurons may cross each
other and might need to be edited with overlapping allowed, whereas a
monolayer of cells might be best edited with overlapping off.
Select "*Yes*" to allow overlaps or select "*No*" to prevent them.
""" % globals(),
        )

        self.renumber_choice = Choice(
            "Numbering of the edited objects",
            [R_RENUMBER, R_RETAIN],
            doc="""\
Choose how to number the objects that remain after editing, which
controls how edited objects are associated with their predecessors:

-  *%(R_RENUMBER)s:* The module will number the objects that remain
   using consecutive numbers. This is a good choice if you do not plan
   to use measurements from the original objects and you only want to
   use the edited objects in downstream modules; the objects that remain
   after editing will not have gaps in numbering where removed objects
   are missing.
-  *%(R_RETAIN)s:* This option will retain each object’s original
   number so that the edited object’s number matches its original
   number. This allows any measurements you make from the edited objects
   to be directly aligned with measurements you might have made of the
   original, unedited objects (or objects directly associated with
   them).
""" % globals(),
        )

        self.wants_image_display = Binary(
            "Display a guiding image?",
            True,
            doc="""\
Select "*Yes*" to display an image and outlines of the objects.

Select "*No*" if you do not want a guide image while editing.
""" % globals(),
        )

        self.image_name = ImageSubscriber(
            "Select the guiding image",
            "None",
            doc="""\
*(Used only if a guiding image is desired)*

This is the image that will appear when editing objects. Choose an image
supplied by a previous module.
""",
        )
Example #11
0
    def create_settings(self):
        """Create the settings that control this module"""
        self.object_name = LabelSubscriber(
            "Select objects to be masked",
            "None",
            doc="""\
Select the objects that will be masked (that is, excluded in whole or in
part based on the other settings in the module). You can choose from any
objects created by a previous object processing module, such as
**IdentifyPrimaryObjects**, **IdentifySecondaryObjects** or
**IdentifyTertiaryObjects**.
""",
        )

        self.remaining_objects = LabelName(
            "Name the masked objects",
            "MaskedNuclei",
            doc="""\
Enter a name for the objects that remain after
the masking operation. You can refer to the masked objects in
subsequent modules by this name.
""",
        )

        self.mask_choice = Choice(
            "Mask using a region defined by other objects or by binary image?",
            [MC_OBJECTS, MC_IMAGE],
            doc="""\
You can mask your objects by defining a region using objects you
previously identified in your pipeline (*%(MC_OBJECTS)s*) or by
defining a region based on the white regions in a binary image
previously loaded or created in your pipeline (*%(MC_IMAGE)s*).
"""
            % globals(),
        )

        self.masking_objects = LabelSubscriber(
            "Select the masking object",
            "None",
            doc="""\
*(Used only if mask is to be made from objects)*

Select the objects that will be used to define the masking region. You
can choose from any objects created by a previous object processing
module, such as **IdentifyPrimaryObjects**,
**IdentifySecondaryObjects**, or **IdentifyTertiaryObjects**.
""",
        )

        self.masking_image = ImageSubscriber(
            "Select the masking image",
            "None",
            doc="""\
*(Used only if mask is to be made from an image)*

Select an image that was either loaded or created by a previous module.
The image should be a binary image where the white portion of the image
is the region(s) you will use for masking. Binary images can be loaded
from disk using the **NamesAndTypes** module by selecting “Binary mask”
for the image type. You can also create a binary image from a grayscale
image using **ApplyThreshold**.
""",
        )

        self.wants_inverted_mask = Binary(
            "Invert the mask?",
            False,
            doc="""\
This option reverses the foreground/background relationship of the mask.

-  Select "*No*" for the mask to be composed of the foreground (white
   portion) of the masking image or the area within the masking objects.
-  Select "*Yes*" for the mask to instead be composed of the
   *background* (black portions) of the masking image or the area
   *outside* the masking objects.
   """
            % globals(),
        )

        self.overlap_choice = Choice(
            "Handling of objects that are partially masked",
            [P_MASK, P_KEEP, P_REMOVE, P_REMOVE_PERCENTAGE],
            doc="""\
An object might partially overlap the mask region, with pixels both
inside and outside the region. **MaskObjects** can handle this in one
of three ways:

-  *%(P_MASK)s:* Choosing this option will reduce the size of partially
   overlapping objects. The part of the object that overlaps the masking
   region will be retained. The part of the object that is outside of the
   masking region will be removed.
-  *%(P_KEEP)s:* If you choose this option, **MaskObjects** will keep
   the whole object if any part of it overlaps the masking region.
-  *%(P_REMOVE)s:* Objects that are partially outside of the masking
   region will be completely removed if you choose this option.
-  *%(P_REMOVE_PERCENTAGE)s:* Determine whether to remove or keep an
   object depending on how much of the object overlaps the masking
   region. **MaskObjects** will keep an object if at least a certain
   fraction (which you enter below) of the object falls within the
   masking region. **MaskObjects** completely removes the object if too
   little of it overlaps the masking region."""
            % globals(),
        )

        self.overlap_fraction = Float(
            "Fraction of object that must overlap",
            0.5,
            minval=0,
            maxval=1,
            doc="""\
*(Used only if removing based on overlap)*

Specify the minimum fraction of an object that must overlap the masking
region for that object to be retained. For instance, if the fraction is
0.75, then 3/4 of an object must be within the masking region for that
object to be retained.
""",
        )

        self.retain_or_renumber = Choice(
            "Numbering of resulting objects",
            [R_RENUMBER, R_RETAIN],
            doc="""\
Choose how to number the objects that remain after masking, which
controls how remaining objects are associated with their predecessors:

-  *%(R_RENUMBER)s:* The objects that remain will be renumbered using
   consecutive numbers. This is a good choice if you do not plan to use
   measurements from the original objects; your object measurements for
   the masked objects will not have gaps (where removed objects are
   missing).
-  *%(R_RETAIN)s:* The original labels for the objects will be
   retained. This allows any measurements you make from the masked
   objects to be directly aligned with measurements you might have made
   of the original, unmasked objects (or objects directly associated
   with them).
"""
            % globals(),
        )
    def create_settings(self):
        super(IdentifySecondaryObjects, self).create_settings()

        self.x_name.text = "Select the input objects"

        self.x_name.doc = """\
What did you call the objects you want to use as primary objects ("seeds") to identify a secondary
object around each one? By definition, each primary object must be associated with exactly one
secondary object and completely contained within it."""

        self.y_name.text = "Name the objects to be identified"

        self.y_name.doc = "Enter the name that you want to call the objects identified by this module."

        self.method = Choice(
            "Select the method to identify the secondary objects",
            [
                M_PROPAGATION, M_WATERSHED_G, M_WATERSHED_I, M_DISTANCE_N,
                M_DISTANCE_B
            ],
            M_PROPAGATION,
            doc="""\
There are several methods available to find the dividing lines between
secondary objects that touch each other:

-  *{M_PROPAGATION:s}:* This method will find dividing lines between
   clumped objects where the image stained for secondary objects shows a
   change in staining (i.e., either a dimmer or a brighter line).
   Smoother lines work better, but unlike the Watershed method, small
   gaps are tolerated. This method is considered an improvement on the
   traditional *Watershed* method. The dividing lines between objects
   are determined by a combination of the distance to the nearest
   primary object and intensity gradients. This algorithm uses local
   image similarity to guide the location of boundaries between cells.
   Boundaries are preferentially placed where the image’s local
   appearance changes perpendicularly to the boundary (*Jones et al,
   2005*).

   |image0| The {M_PROPAGATION:s} algorithm is the default approach for secondary object
   creation. Each primary object is a "seed" for its corresponding
   secondary object, guided by the input
   image and limited to the foreground region as determined by the chosen
   thresholding method. λ is a regularization parameter; see the help for
   the setting for more details. Propagation of secondary object labels is
   by the shortest path to an adjacent primary object from the starting
   (“seeding”) primary object. The seed-to-pixel distances are calculated
   as the sum of absolute differences in a 3x3 (8-connected) image
   neighborhood, combined with λ via sqrt(differences\ :sup:`2` +
   λ\ :sup:`2`).
-  *{M_WATERSHED_G:s}:* This method uses the watershed algorithm
   (*Vincent and Soille, 1991*) to assign pixels to the primary objects
   which act as seeds for the watershed. In this variant, the watershed
   algorithm operates on the Sobel transformed image which computes an
   intensity gradient. This method works best when the image intensity
   drops off or increases rapidly near the boundary between cells.
-  *{M_WATERSHED_I:s}:* This method is similar to the above, but it
   uses the inverted intensity of the image for the watershed. The areas
   of lowest intensity will be detected as the boundaries between cells. This
   method works best when there is a saddle of relatively low intensity
   at the cell-cell boundary.
-  *Distance:* In this method, the edges of the primary objects are
   expanded a specified distance to create the secondary objects. For
   example, if nuclei are labeled but there is no stain to help locate
   cell edges, the nuclei can simply be expanded in order to estimate
   the cell’s location. This is often called the “doughnut” or “annulus”
   or “ring” approach for identifying the cytoplasm. There are two
   methods that can be used:

   -  *{M_DISTANCE_N:s}*: In this method, the image of the secondary
      staining is not used at all; the expanded objects are the final
      secondary objects.
   -  *{M_DISTANCE_B:s}*: Thresholding of the secondary staining image
      is used to eliminate background regions from the secondary
      objects. This allows the extent of the secondary objects to be
      limited to a certain distance away from the edge of the primary
      objects without including regions of background.

References
^^^^^^^^^^

Jones TR, Carpenter AE, Golland P (2005) “Voronoi-Based Segmentation of
Cells on Image Manifolds”, *ICCV Workshop on Computer Vision for
Biomedical Image Applications*, 535-543. (`link1`_)

Vincent L, Soille P (1991) "Watersheds in Digital Spaces: An Efficient
Algorithm Based on Immersion Simulations", *IEEE Transactions on Pattern
Analysis and Machine Intelligence*, Vol. 13, No. 6, 583-598 (`link2`_)

.. _link1: http://people.csail.mit.edu/polina/papers/JonesCarpenterGolland_CVBIA2005.pdf
.. _link2: http://www.cse.msu.edu/~cse902/S03/watershed.pdf

.. |image0| image:: {TECH_NOTE_ICON}
""".format(
                **{
                    "M_PROPAGATION": M_PROPAGATION,
                    "M_WATERSHED_G": M_WATERSHED_G,
                    "M_WATERSHED_I": M_WATERSHED_I,
                    "M_DISTANCE_N": M_DISTANCE_N,
                    "M_DISTANCE_B": M_DISTANCE_B,
                    "TECH_NOTE_ICON": _help.TECH_NOTE_ICON,
                }),
        )

        self.image_name = ImageSubscriber(
            "Select the input image",
            "None",
            doc="""\
The selected image will be used to find the edges of the secondary
objects. For *{M_DISTANCE_N:s}* this will not affect object
identification, only the module's display.
""".format(**{"M_DISTANCE_N": M_DISTANCE_N}),
        )

        self.distance_to_dilate = Integer(
            "Number of pixels by which to expand the primary objects",
            10,
            minval=1,
            doc="""\
*(Used only if "{M_DISTANCE_B:s}" or "{M_DISTANCE_N:s}" method is selected)*

This option allows you to define the number of pixels by which the primary objects
will be expanded. This option becomes useful in situations when no staining was
used to define cell cytoplasm but the cell edges must be defined for further
measurements.
""".format(**{
                "M_DISTANCE_N": M_DISTANCE_N,
                "M_DISTANCE_B": M_DISTANCE_B
            }),
        )

        self.regularization_factor = Float(
            "Regularization factor",
            0.05,
            minval=0,
            doc="""\
*(Used only if "{M_PROPAGATION:s}" method is selected)*

The regularization factor λ can be anywhere in the range 0 to
infinity. This method takes two factors into account when deciding
where to draw the dividing line between two touching secondary
objects: the distance to the nearest primary object, and the intensity
of the secondary object image. The regularization factor controls the
balance between these two considerations:

-  A λ value of 0 means that the distance to the nearest primary object
   is ignored and the decision is made entirely on the intensity
   gradient between the two competing primary objects.
-  Larger values of λ put more and more weight on the distance between
   the two objects. This relationship is such that small changes in λ
   will have fairly different results (e.g., 0.01 vs 0.001). However, the
   intensity image is almost completely ignored at λ much greater than
   1.
-  At infinity, the result will look like {M_DISTANCE_B:s}, masked to
   the secondary staining image.
""".format(**{
                "M_PROPAGATION": M_PROPAGATION,
                "M_DISTANCE_B": M_DISTANCE_B
            }),
        )

        self.wants_discard_edge = Binary(
            "Discard secondary objects touching the border of the image?",
            False,
            doc="""\
Select *{YES:s}* to discard secondary objects that touch the image
border. Select *{NO:s}* to retain objects regardless of whether they
touch the image edge or not.

Note: the objects are discarded with respect to downstream measurement
modules, but they are retained in memory as “Unedited objects”; this
allows them to be considered in downstream modules that modify the
segmentation.
""".format(**{
                "YES": "Yes",
                "NO": "No"
            }),
        )

        self.fill_holes = Binary(
            "Fill holes in identified objects?",
            True,
            doc="""\
Select *{YES:s}* to fill any holes inside objects.

Please note that if an object is located within a hole and this option is
enabled, the object will be lost when the hole is filled in.
""".format(**{"YES": "Yes"}),
        )

        self.wants_discard_primary = Binary(
            "Discard the associated primary objects?",
            False,
            doc="""\
*(Used only if discarding secondary objects touching the image
border)*

It might be appropriate to discard the primary object for any
secondary object that touches the edge of the image.

Select *{YES:s}* to create a new set of objects that are identical to
the original set of primary objects, minus the objects for which the
associated secondary object touches the image edge.
""".format(**{"YES": "Yes"}),
        )

        self.new_primary_objects_name = LabelName(
            "Name the new primary objects",
            "FilteredNuclei",
            doc="""\
*(Used only if associated primary objects are discarded)*

You can name the primary objects that remain after the discarding step.
These objects will all have secondary objects that do not touch the edge
of the image. Note that any primary object whose secondary object
touches the edge will be retained in memory as an “unedited object”;
this allows them to be considered in downstream modules that modify the
segmentation.""",
        )

        self.threshold_setting_version = Integer(
            "Threshold setting version",
            value=self.threshold.variable_revision_number)

        self.threshold.create_settings()

        self.threshold.threshold_smoothing_scale.value = 0
    def create_settings(self):
        self.objects_name = LabelSubscriber(
            "Select the input objects",
            "None",
            doc="""\
Select the objects you would like to split or merge (that is,
whose object numbers you want to reassign). You can
use any objects that were created in previous modules, such as
**IdentifyPrimaryObjects** or **IdentifySecondaryObjects**.""",
        )

        self.output_objects_name = LabelName(
            "Name the new objects",
            "RelabeledNuclei",
            doc="""\
Enter a name for the objects that have been split or merged (that is,
whose numbers have been reassigned).
You can use this name in subsequent modules that take objects as inputs.""",
        )

        self.relabel_option = Choice(
            "Operation",
            [OPTION_MERGE, OPTION_SPLIT],
            doc="""\
You can choose one of the following options:

-  *%(OPTION_MERGE)s:* Assign adjacent or nearby objects the same label
   based on certain criteria. It can be useful, for example, to merge
   together touching objects that were incorrectly split into two pieces
   by an **Identify** module.
-  *%(OPTION_SPLIT)s:* Assign a unique number to separate objects that
   currently share the same label. This can occur if you applied certain
   operations in the **Morph** module to objects.""" % globals(),
        )

        self.merge_option = Choice(
            "Merging method",
            [UNIFY_DISTANCE, UNIFY_PARENT],
            doc="""\
*(Used only with the "%(OPTION_MERGE)s" option)*

You can merge objects in one of two ways:

-  *%(UNIFY_DISTANCE)s:* All objects within a certain pixel radius from
   each other will be merged.
-  *%(UNIFY_PARENT)s:* All objects which share the same parent
   relationship to another object will be merged. This is not to be
   confused with using the **RelateObjects** module, in which the
   related objects remain as individual objects. See **RelateObjects**
   for more details.""" % globals(),
        )

        self.merging_method = Choice(
            "Output object type",
            [UM_DISCONNECTED, UM_CONVEX_HULL],
            doc="""\
*(Used only with the "%(UNIFY_PARENT)s" merging method)*

**SplitOrMergeObjects** can either merge the child objects and keep them
disconnected or it can find the smallest convex polygon (the convex
hull) that encloses all of a parent’s child objects. The convex hull
will be truncated to include only those pixels in the parent - in that
case it may not truly be convex. Choose *%(UM_DISCONNECTED)s* to leave
the children as disconnected pieces. Choose *%(UM_CONVEX_HULL)s* to
create an output object that is the convex hull around them all.""" %
            globals(),
        )

        self.parent_object = Choice(
            "Select the parent object",
            ["None"],
            choices_fn=self.get_parent_choices,
            doc="""\
Select the parent object that will be used to merge the child objects.
Please note the following:

-  You must have established a parent-child relationship between the
   objects using a prior **RelateObjects** module.
-  Primary objects and their associated secondary objects are already in
   a one-to-one parent-child relationship, so it makes no sense to merge
   them here.""",
        )

        self.distance_threshold = Integer(
            "Maximum distance within which to merge objects",
            0,
            minval=0,
            doc="""\
*(Used only with the "%(OPTION_MERGE)s" option and the "%(UNIFY_DISTANCE)s"
method)*

Objects that are less than or equal to the distance you enter here, in
pixels, will be merged. If you choose zero (the default), only objects
that are touching will be merged. Note that *%(OPTION_MERGE)s* will
not actually connect or bridge the two objects by adding any new pixels;
it simply assigns the same object number to the portions of the object.
The new, merged object may therefore consist of two or more unconnected
components. If you want to add pixels around objects, see
**ExpandOrShrink** or **Morph**.""" % globals(),
        )

        self.wants_image = Binary(
            "Merge using a grayscale image?",
            False,
            doc="""\
*(Used only with the "%(OPTION_MERGE)s" option)*

Select *Yes* to use the objects’ intensity features to determine
whether two objects should be merged. If you choose to use a grayscale
image, *%(OPTION_MERGE)s* will merge two objects only if they are
within the distance you have specified *and* certain criteria about the
objects within the grayscale image are met.""" % globals(),
        )

        self.image_name = ImageSubscriber(
            "Select the grayscale image to guide merging",
            "None",
            doc="""\
*(Used only if a grayscale image is to be used as a guide for
merging)*

Select the name of an image loaded or created by a previous module.""",
        )

        self.minimum_intensity_fraction = Float(
            "Minimum intensity fraction",
            0.9,
            minval=0,
            maxval=1,
            doc="""\
*(Used only if a grayscale image is to be used as a guide for
merging)*

Select the minimum acceptable intensity fraction. This will be used as
described for the method you choose in the next setting.""",
        )

        self.where_algorithm = Choice(
            "Method to find object intensity",
            [CA_CLOSEST_POINT, CA_CENTROIDS],
            doc="""\
*(Used only if a grayscale image is to be used as a guide for
merging)*

You can use one of two methods to determine whether two objects should
merged, assuming they meet the distance criteria (as specified
above):

-  *%(CA_CENTROIDS)s:* When the module considers merging two objects,
   this method identifies the centroid of each object, records the
   intensity value of the dimmer of the two centroids, multiplies this
   value by the *minimum intensity fraction* to generate a threshold,
   and draws a line between the centroids. The method will merge the two
   objects only if the intensity of every point along the line is above
   the threshold. For instance, if the intensity of one centroid is 0.75
   and the other is 0.50 and the *minimum intensity fraction* has been
   chosen to be 0.9, all points along the line would need to have an
   intensity of min(0.75, 0.50) \* 0.9 = 0.50 \* 0.9 = 0.45.
   This method works well for round cells whose maximum intensity is in
   the center of the cell: a single cell that was incorrectly segmented
   into two objects will typically not have a dim line between the
   centroids of the two halves and will be correctly merged.
-  *%(CA_CLOSEST_POINT)s:* This method is useful for unifying
   irregularly shaped cells that are connected. It starts by assigning
   background pixels in the vicinity of the objects to the nearest
   object. Objects are then merged if each object has background pixels
   that are:

   -  Within a distance threshold from each object;
   -  Above the minimum intensity fraction of the nearest object pixel;
   -  Adjacent to background pixels assigned to a neighboring object.

   An example of a feature that satisfies the above constraints is a
   line of pixels that connects two neighboring objects and is roughly
   the same intensity as the boundary pixels of both (such as an axon
   connecting two neurons' soma).""" % globals(),
        )
    def create_settings(self):
        """Create the settings for the module

        Create the settings for the module during initialization.
        """
        self.image_name = ImageSubscriber(
            "Select the input image", "None",doc="""
            The name of a binary image from a previous module.
            <b>IdentifyLinearObjects</b> will use this image to establish the
            foreground and background for the fitting operation. You can use
            <b>ApplyThreshold</b> to threshold a grayscale image and
            create the binary mask. You can also use a module such as
            <b>IdentifyPrimaryObjects</b> to label each linear object and then use
            <b>ConvertObjectsToImage</b> to make the result a mask.""")

        self.object_name = LabelName(
            "Name the linear objects to be identified", "LinearObjects",doc="""
            This is the name for the linear objects. You can refer
            to this name in subsequent modules such as
            <b>IdentifySecondaryObjects</b>""")

        self.object_width = Integer(
            "Linear object width", 10, minval = 1,doc = """
            This is the width (the short axis), measured in pixels,
            of the diamond used as a template when
            matching against the linear object. It should be less than the width
            of a linear object.""")

        self.object_length = Integer(
            "Linear object length", 100, minval= 1,doc = """
            This is the length (the long axis), measured in pixels,
            of the diamond used as a template when matching against the
            linear object. It should be less than the length of a linear object""")

        self.angle_count = Integer(
            "Number of angles", 32, minval = 1,doc = """
            This is the number of different angles at which the
            template will be tried. For instance, if there are 12 angles,
            the template will be rotated by 0&deg;, 15&deg;, 30&deg;, 45&deg; ... 165&deg;.
            The shape is bilaterally symmetric; that is, you will get the same shape
            after rotating it by 180&deg;.""")

        self.wants_automatic_distance = cps.Binary(
            "Automatically calculate distance parameters?", True,doc = """
            This setting determines whether or not
            <b>IdentifyLinearObjects</b> automatically calculates the parameters
            used to determine whether two found-linear object centers belong to the
            same linear object.
            <p>Select <i>"Yes"</i> to have <b>IdentifyLinearObjects</b>
            automatically calculate the distance from the linear object length
            and width. Select <i>"No"</i> to set the distances manually.</p>"""%globals())

        self.space_distance = Float(
            "Spatial distance", 5, minval = 1,doc = """
            <i>(Used only if not automatically calculating distance parameters)</i><br>
            Enter the distance for calculating the linear object centers, in units of pixels.
            The linear object centers must be at least many pixels apart for the centers to
            be considered two separate linear objects.""")

        self.angular_distance = Float(
            "Angular distance", 30, minval = 1,doc = """
            <i>(Used only if automatically calculating distance parameters)</i><br>
            <b>IdentifyLinearObjects</b> calculates the linear object centers at different
            angles. Two linear object centers are considered to represent different
            linear objects if their angular distance is larger than this number. The
            number is measured in degrees.""")

        self.overlap_within_angle = cps.Binary(
            "Combine if overlapping and within angular distance?", False,doc = """
            This setting determines whether or not
            <b>IdentifyLinearObjects</b> merges putative linear objects that are within the
            angular distance specified AND in which pixels from the two 
            linear objects overlap.
            <p>Select <i>"Yes"</i> to have <b>IdentifyLinearObjects</b>
            merge these linear objects. Select <i>"No"</i> use only the spatial
            distance and angular distance parameters above.</p>"""%globals())
    def add_single_measurement(self, can_delete=True):
        """Add a single measurement to the group of single measurements

        can_delete - True to include a "remove" button, False if you're not
                     allowed to remove it.
        """
        group = SettingsGroup()
        if can_delete:
            group.append("divider", Divider(line=True))

        group.append(
            "object_name",
            LabelName(
                "Select the object to be classified",
                "None",
                doc="""\
The name of the objects to be classified. You can choose from objects
created by any previous module. See **IdentifyPrimaryObjects**,
**IdentifySecondaryObjects**, **IdentifyTertiaryObjects**, or **Watershed**
""",
            ),
        )

        def object_fn():
            return group.object_name.value

        group.append(
            "measurement",
            Measurement(
                "Select the measurement to classify by",
                object_fn,
                doc="""\
*(Used only if using a single measurement)*

Select a measurement made by a previous module. The objects will be
classified according to their values for this measurement.
""",
            ),
        )

        group.append(
            "bin_choice",
            Choice(
                "Select bin spacing",
                [BC_EVEN, BC_CUSTOM],
                doc="""\
*(Used only if using a single measurement)*

Select how you want to define the spacing of the bins. You have the
following options:

-  *%(BC_EVEN)s:* Choose this if you want to specify bins of equal
   size, bounded by upper and lower limits. If you want two bins, choose
   this option and then provide a single threshold when asked.
-  *%(BC_CUSTOM)s:* Choose this option to create the indicated number
   of bins at evenly spaced intervals between the low and high
   threshold. You also have the option to create bins for objects that
   fall below or above the low and high threshold.
""" % globals(),
            ),
        )

        group.append(
            "bin_count",
            Integer(
                "Number of bins",
                3,
                minval=1,
                doc="""\
*(Used only if using a single measurement)*

This is the number of bins that will be created between
the low and high threshold""",
            ),
        )

        group.append(
            "low_threshold",
            Float(
                "Lower threshold",
                0,
                doc="""\
*(Used only if using a single measurement and "%(BC_EVEN)s" selected)*

This is the threshold that separates the lowest bin from the others. The
lower threshold, upper threshold, and number of bins define the
thresholds of bins between the lowest and highest.
""" % globals(),
            ),
        )

        group.append(
            "wants_low_bin",
            Binary(
                "Use a bin for objects below the threshold?",
                False,
                doc="""\
*(Used only if using a single measurement)*

Select "*Yes*" if you want to create a bin for objects whose values
fall below the low threshold. Select "*No*" if you do not want a bin
for these objects.
""" % globals(),
            ),
        )

        group.append(
            "high_threshold",
            Float(
                "Upper threshold",
                1,
                doc="""\
*(Used only if using a single measurement and "%(BC_EVEN)s" selected)*

This is the threshold that separates the last bin from the others. Note
that if you would like two bins, you should select "*%(BC_CUSTOM)s*".
""" % globals(),
            ),
        )

        group.append(
            "wants_high_bin",
            Binary(
                "Use a bin for objects above the threshold?",
                False,
                doc="""\
*(Used only if using a single measurement)*

Select "*Yes*" if you want to create a bin for objects whose values
are above the high threshold.

Select "*No*" if you do not want a bin for these objects.
""" % globals(),
            ),
        )

        group.append(
            "custom_thresholds",
            Text(
                "Enter the custom thresholds separating the values between bins",
                "0,1",
                doc="""\
*(Used only if using a single measurement and "%(BC_CUSTOM)s" selected)*

This setting establishes the threshold values for the bins. You should
enter one threshold between each bin, separating thresholds with commas
(for example, *0.3, 1.5, 2.1* for four bins). The module will create one
more bin than there are thresholds.
""" % globals(),
            ),
        )

        group.append(
            "wants_custom_names",
            Binary(
                "Give each bin a name?",
                False,
                doc="""\
*(Used only if using a single measurement)*

Select "*Yes*" to assign custom names to bins you have specified.

Select "*No*" for the module to automatically assign names based on
the measurements and the bin number.
""" % globals(),
            ),
        )

        group.append(
            "bin_names",
            Text(
                "Enter the bin names separated by commas",
                "None",
                doc="""\
*(Used only if "Give each bin a name?" is checked)*

Enter names for each of the bins, separated by commas.
An example including three bins might be *First,Second,Third*.""",
            ),
        )

        group.append(
            "wants_images",
            Binary(
                "Retain an image of the classified objects?",
                False,
                doc="""\
Select "*Yes*" to keep an image of the objects which is color-coded
according to their classification, for use later in the pipeline (for
example, to be saved by a **SaveImages** module).
""" % globals(),
            ),
        )

        group.append(
            "image_name",
            ImageName(
                "Name the output image",
                "ClassifiedNuclei",
                doc=
                """Enter the name to be given to the classified object image.""",
            ),
        )

        group.can_delete = can_delete

        def number_of_bins():
            """Return the # of bins in this classification"""
            if group.bin_choice == BC_EVEN:
                value = group.bin_count.value
            else:
                value = len(group.custom_thresholds.value.split(",")) - 1
            if group.wants_low_bin:
                value += 1
            if group.wants_high_bin:
                value += 1
            return value

        group.number_of_bins = number_of_bins

        def measurement_name():
            """Get the measurement name to use inside the bin name

            Account for conflicts with previous measurements
            """
            measurement_name = group.measurement.value
            other_same = 0
            for other in self.single_measurements:
                if id(other) == id(group):
                    break
                if other.measurement.value == measurement_name:
                    other_same += 1
            if other_same > 0:
                measurement_name += str(other_same)
            return measurement_name

        def bin_feature_names():
            """Return the feature names for each bin"""
            if group.wants_custom_names:
                return [
                    name.strip() for name in group.bin_names.value.split(",")
                ]
            return [
                "_".join((measurement_name(), "Bin_%d" % (i + 1)))
                for i in range(number_of_bins())
            ]

        group.bin_feature_names = bin_feature_names

        def validate_group():
            bin_name_count = len(bin_feature_names())
            bin_count = number_of_bins()
            if bin_count < 1:
                bad_setting = (group.bin_count if group.bin_choice == BC_EVEN
                               else group.custom_thresholds)
                raise ValidationError(
                    "You must have at least one bin in order to take measurements. "
                    "Either add more bins or ask for bins for objects above or below threshold",
                    bad_setting,
                )
            if bin_name_count != number_of_bins():
                raise ValidationError(
                    "The number of bin names (%d) does not match the number of bins (%d)."
                    % (bin_name_count, bin_count),
                    group.bin_names,
                )
            for bin_feature_name in bin_feature_names():
                Alphanumeric.validate_alphanumeric_text(
                    bin_feature_name, group.bin_names, True)
            if group.bin_choice == BC_CUSTOM:
                try:
                    [
                        float(x.strip())
                        for x in group.custom_thresholds.value.split(",")
                    ]
                except ValueError:
                    raise ValidationError(
                        "Custom thresholds must be a comma-separated list "
                        'of numbers (example: "1.0, 2.3, 4.5")',
                        group.custom_thresholds,
                    )
            elif group.bin_choice == BC_EVEN:
                if group.low_threshold.value >= group.high_threshold.value:
                    raise ValidationError(
                        "Lower Threshold must be less than Upper Threshold",
                        group.low_threshold,
                    )

        group.validate_group = validate_group

        if can_delete:
            group.remove_settings_button = RemoveSettingButton(
                "", "Remove this classification", self.single_measurements,
                group)
        self.single_measurements.append(group)
    def create_settings(self):
        """Create the settings for the module

        Create the settings for the module during initialization.
        """
        self.contrast_choice = Choice(
            "Make each classification decision on how many measurements?",
            [BY_SINGLE_MEASUREMENT, BY_TWO_MEASUREMENTS],
            doc="""\
This setting controls how many measurements are used to make a
classifications decision for each object:

-  *%(BY_SINGLE_MEASUREMENT)s:* Classifies each object based on a
   single measurement.
-  *%(BY_TWO_MEASUREMENTS)s:* Classifies each object based on a pair
   of measurements taken together (that is, an object must meet two
   criteria to belong to a class).
""" % globals(),
        )

        ############### Single measurement settings ##################
        #
        # A list holding groupings for each of the single measurements
        # to be done
        #
        self.single_measurements = []
        #
        # A count of # of measurements
        #
        self.single_measurement_count = HiddenCount(self.single_measurements)
        #
        # Add one single measurement to start off
        #
        self.add_single_measurement(False)
        #
        # A button to press to get another measurement
        #
        self.add_measurement_button = DoSomething(
            "", "Add another classification", self.add_single_measurement)
        #
        ############### Two-measurement settings #####################
        #
        # The object for the contrasting method
        #
        self.object_name = LabelName(
            "Select the object name",
            "None",
            doc="""\
Choose the object that you want to measure from the list. This should be
an object created by a previous module such as
**IdentifyPrimaryObjects**, **IdentifySecondaryObjects**, **IdentifyTertiaryObjects**, or **Watershed**
""",
        )

        #
        # The two measurements for the contrasting method
        #
        def object_fn():
            return self.object_name.value

        self.first_measurement = Measurement(
            "Select the first measurement",
            object_fn,
            doc="""\
*(Used only if using a pair of measurements)*

Choose a measurement made on the above object. This is the first of two
measurements that will be contrasted together. The measurement should be
one made on the object in a prior module.
""",
        )

        self.first_threshold_method = Choice(
            "Method to select the cutoff",
            [TM_MEAN, TM_MEDIAN, TM_CUSTOM],
            doc="""\
*(Used only if using a pair of measurements)*

Objects are classified as being above or below a cutoff value for a
measurement. You can set this cutoff threshold in one of three ways:

-  *%(TM_MEAN)s*: At the mean of the measurement’s value for all
   objects in the image cycle.
-  *%(TM_MEDIAN)s*: At the median of the measurement’s value for all
   objects in the image set.
-  *%(TM_CUSTOM)s*: You specify a custom threshold value.
""" % globals(),
        )

        self.first_threshold = Float(
            "Enter the cutoff value",
            0.5,
            doc="""\
*(Used only if using a pair of measurements)*

This is the cutoff value separating objects in the two classes.""",
        )

        self.second_measurement = Measurement(
            "Select the second measurement",
            object_fn,
            doc="""\
*(Used only if using a pair of measurements)*

Select a measurement made on the above object. This is
the second of two measurements that will be contrasted together.
The measurement should be one made on the object in a prior
module.""",
        )

        self.second_threshold_method = Choice(
            "Method to select the cutoff",
            [TM_MEAN, TM_MEDIAN, TM_CUSTOM],
            doc="""\
*(Used only if using a pair of measurements)*

Objects are classified as being above or below a cutoff value for a
measurement. You can set this cutoff threshold in one of three ways:

-  *%(TM_MEAN)s:* At the mean of the measurement’s value for all
   objects in the image cycle.
-  *%(TM_MEDIAN)s:* At the median of the measurement’s value for all
   objects in the image set.
-  *%(TM_CUSTOM)s:* You specify a custom threshold value.
""" % globals(),
        )

        self.second_threshold = Float(
            "Enter the cutoff value",
            0.5,
            doc="""\
*(Used only if using a pair of measurements)*

This is the cutoff value separating objects in the two classes.""",
        )

        self.wants_custom_names = Binary(
            "Use custom names for the bins?",
            False,
            doc="""\
*(Used only if using a pair of measurements)*

Select "*Yes*" if you want to specify the names of each bin
measurement.

Select "*No*" to create names based on the measurements. For instance,
for “Intensity_MeanIntensity_Green” and
“Intensity_TotalIntensity_Blue”, the module generates measurements
such as
“Classify_Intensity_MeanIntensity_Green_High_Intensity_TotalIntensity_Low”.
""" % globals(),
        )

        self.low_low_custom_name = Alphanumeric(
            "Enter the low-low bin name",
            "low_low",
            doc="""\
*(Used only if using a pair of measurements)*

Name of the measurement for objects that fall below the threshold for
both measurements.
""",
        )

        self.low_high_custom_name = Alphanumeric(
            "Enter the low-high bin name",
            "low_high",
            doc="""\
*(Used only if using a pair of measurements)*

Name of the measurement for objects whose
first measurement is below threshold and whose second measurement
is above threshold.
""",
        )

        self.high_low_custom_name = Alphanumeric(
            "Enter the high-low bin name",
            "high_low",
            doc="""\
*(Used only if using a pair of measurements)*

Name of the measurement for objects whose
first measurement is above threshold and whose second measurement
is below threshold.""",
        )

        self.high_high_custom_name = Alphanumeric(
            "Enter the high-high bin name",
            "high_high",
            doc="""\
*(Used only if using a pair of measurements)*

Name of the measurement for objects that
are above the threshold for both measurements.""",
        )

        self.wants_image = Binary(
            "Retain an image of the classified objects?",
            False,
            doc="""\
Select "*Yes*" to retain the image of the objects color-coded
according to their classification, for use later in the pipeline (for
example, to be saved by a **SaveImages** module).
""" % globals(),
        )

        self.image_name = ImageName(
            "Enter the image name",
            "None",
            doc="""\
*(Used only if the classified object image is to be retained for later use in the pipeline)*

Enter the name to be given to the classified object image.""",
        )