Esempio n. 1
0
 def create_settings(self):
     self.image_name = cps.ImageNameSubscriber(
         "Select the input image","None", 
         doc = '''What did you call the image in which you want to enhance the edges?''')
     self.output_image_name = cps.ImageNameProvider(
         "Name the output image","EdgedImage",
         doc = '''What do you want to call the image with edges enhanced?''')
     self.wants_automatic_threshold = cps.Binary(
         "Automatically calculate the threshold?", True,
         doc = '''<i>(Used only with the Canny option and automatic thresholding)</i> <br> 
         If you select automatic thresholding, it is done using a three-category
         Otsu algorithm performed on the Sobel transform of the image.''')
     self.manual_threshold = cps.Float(
         "Absolute threshold",.2,0,1, doc = '''<i>(Used only with the Canny option and manual thresholding)</i><br>
         The upper cutoff for Canny edges. All Sobel-transformed 
         pixels with this value or higher will be marked as an edge.
         You can enter a threshold between 0 and 1.''')
     self.threshold_adjustment_factor = cps.Float(
         "Threshold adjustment factor",1, doc = '''<i>(Used only with the Canny option and automatic thresholding)</i><br>
         This threshold adjustment factor is a multiplier that is applied to
         both the lower and upper Canny thresholds if they are calculated
         automatically. An adjustment factor of 1 indicates no adjustment.
         The adjustment factor has no effect on any threshhold entered manually entered.''')
     self.method = cps.Choice(
         "Select an edge-finding method",
         [M_SOBEL, M_PREWITT, M_ROBERTS,
          M_LOG, M_CANNY], doc = '''There are several methods that can be used to enhance edges:
          <ul><li><i>Sobel Method:</i> finds edges using the Sobel approximation to the derivative. 
          The Sobel method derives a horizontal and vertical gradient measure and returns the 
          square-root of the sum of the two squared signals.</li>
          <li><i>Prewitt Method:</i> Finds edges using the Prewitt approximation to the derivative.
          It returns edges at those points where the gradient of the image is maximum.</li>
          <li><i>Roberts Method:</i> Finds edges using the Roberts approximation to the derivative. 
          The Roberts method looks for gradients in the diagonal and anti-diagonal directions 
          and returns the square-root of the sum of the two squared signals. This method is fast,
          but it creates diagonal artifacts that may need to be removed by smoothing.</li> 
          <li><i>LoG Method:</i> Applies a Laplacian of Gaussian filter to the image 
          and finds zero crossings. </li>
          <li><i>Canny Method:</i> Finds edges by looking for local maxima 
          of the gradient of the image. The gradient is calculated using the derivative
          of a Gaussian filter. The method uses two thresholds to detect strong and weak 
          edges, and includes the weak edges in the output only if they are connected to 
          strong edges. This method is therefore less likely than the others to be fooled 
          by noise, and more likely to detect true weak edges.</li></ul>''')
     self.direction = cps.Choice("Select edge direction to enhance",
                                 [ E_ALL, E_HORIZONTAL, E_VERTICAL], doc = '''<i>(Used only with Prewitt and Sobel methods)</i> <br> 
                                 The direction of the edges
                                 are you are identifying in the image (predominantly horizontal, predominantly vertical,
                                 or both).''')
     self.wants_automatic_sigma = cps.Binary("Calculate Gaussian's sigma automatically?", True)
     self.sigma = cps.Float("Gaussian's sigma value", 10)
     self.wants_automatic_low_threshold = cps.Binary(
         "Calculate value for low threshold automatically?", True, 
         doc="""<i>(Used only with the Canny option and automatic thresholding)</i> <br>Automatically calculate the low / soft threshold cutoff for
         the Canny method""")
     self.low_threshold = cps.Float(
         "Low threshold value",.1,0,1,
         doc="""<i>(Used only with the Canny option and manual thresholding)</i><br> The soft threshold cutoff for the Canny method.
         The Canny method will mark all Sobel-transformed pixels with values
         below this threshold as not being edges.""")
Esempio n. 2
0
 def __init__(self, index, operation):
     self.__index = index
     self.__operation = operation
     self.__operand_choice = cps.Choice(
         self.operand_choice_text(), MC_ALL,doc="""
         Indicate whether the operand is an image or object measurement.""")
     
     self.__operand_objects = cps.ObjectNameSubscriber(
         self.operand_objects_text(),cps.NONE,doc="""
         Choose the objects you want to measure for this operation.""")
     
     self.__operand_measurement = cps.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 = cps.Float(
         "Multiply the above operand by",1,doc="""
         Enter the number by which you would like to multiply the above operand.""")
     
     self.__exponent = cps.Float(
         "Raise the power of above operand by",1,doc="""
         Enter the power by which you would like to raise the above operand.""")
Esempio n. 3
0
    def add_measurement(self, flag_settings, can_delete=True):
        measurement_settings = flag_settings.measurement_settings

        group = cps.SettingsGroup()
        group.append("divider1", cps.Divider(line=False))
        group.append(
            "source_choice",
            cps.Choice("Flag is based on",
                       S_ALL,
                       doc='''
                <ul>
                <li><i> Whole-image measurement:</i> A per-image measurement, such as intensity or granularity.</li>
                <li><i> Average measurement for all objects in each image:</i> The average of all object measurements in the image.</li>
                <li><i> Measurements for all objects in each image:</i> All the 
                object measurements in an image, without averaging. In other words, if <i>any</i> of the objects meet the criteria, the image will be flagged.</li>
                </ul>'''))
        group.append(
            "object_name",
            cps.ObjectNameSubscriber(
                "Select the object whose measurements will be used to flag",
                "None",
                doc=
                '''<i>(Used only when flag is based on an object measurement)</i><br>What did you call the objects whose measurements you want to use for flagging?'''
            ))

        def object_fn():
            if group.source_choice == S_IMAGE:
                return cpmeas.IMAGE
            return group.object_name.value

        group.append("measurement",
                     cps.Measurement("Which measurement?", object_fn))
        group.append(
            "wants_minimum",
            cps.Binary(
                "Flag images based on low values?",
                True,
                doc=
                '''Images with measurements below this cutoff will be flagged.'''
            ))
        group.append("minimum_value", cps.Float("Minimum value", 0))
        group.append(
            "wants_maximum",
            cps.Binary(
                "Flag images based on high values?",
                True,
                doc=
                '''Images with measurements above this cutoff will be flagged.'''
            ))
        group.append("maximum_value", cps.Float("Maximum value", 1))

        if can_delete:
            group.append(
                "remover",
                cps.RemoveSettingButton("", "Remove this measurement",
                                        measurement_settings, group))

        group.append("divider2", cps.Divider(line=True))
        measurement_settings.append(group)
Esempio n. 4
0
    def create_settings(self):
        '''Create the initial settings for the module'''
        self.manual_threshold = cps.Float("Enter threshold:", .15)
        self.image_groups = []
        self.add_image(can_delete=False)
        self.spacer_1 = cps.Divider()
        self.add_image(can_delete=False)
        self.image_count = cps.HiddenCount(self.image_groups)

        self.add_image_button = cps.DoSomething("", 'Add another image',
                                                self.add_image)
        self.spacer_2 = cps.Divider()
        self.images_or_objects = cps.Choice(
            'Select where to measure correlation',
            [M_IMAGES, M_OBJECTS, M_IMAGES_AND_OBJECTS],
            doc='''
                                            Do you want to measure the RWC over the whole image, 
                                            within objects, or both?
                                            Both methods measure RWC on a pixel by pixel basis.
                                            Selecting <i>Objects</i> will measure RWC only in those pixels previously
                                            identified as an object (you will be asked to specify which object).  Selecting 
                                            <i>Images</i> will measure RWC across all pixels in the images.
                                            <i>Images and objects</i> will calculate both measurements.'''
        )

        self.object_groups = []
        self.add_object(can_delete=False)
        self.object_count = cps.HiddenCount(self.object_groups)

        self.spacer_2 = cps.Divider(line=True)

        self.add_object_button = cps.DoSomething("", 'Add another object',
                                                 self.add_object)
    def create_settings(self):
        #
        # The ImageNameSubscriber "subscribes" to all ImageNameProviders in
        # prior modules. Modules before yours will put images into CellProfiler.
        # The ImageSubscriber gives your user a list of these images
        # which can then be used as inputs in your module.
        #
        self.manual_threshold = cps.Float("Enter threshold:", .118)
        self.input_image_name = cps.ImageNameSubscriber(
            # The text to the left of the edit box
            "Input image name:",
            # HTML help that gets displayed when the user presses the
            # help button to the right of the edit box
            doc="""This is the image that the module operates on. You can
            choose any image that is made available by a prior module.
            """)

        #
        # The ObjectNameSubscriber is similar - it will ask the user
        # which object to pick from the list of objects provided by
        # upstream modules.
        #
        self.input_object_name = cps.ObjectNameSubscriber(
            "Input object name",
            doc="""These are the objects that the module operates on.""")
Esempio n. 6
0
    def create_settings(self):
        threshold_methods = [
            method for method in TM_METHODS if method != TM_BINARY_IMAGE
        ]
        self.image_name = cpsetting.ImageNameSubscriber(
            "Select the input image",
            doc='''
                                Specify the image to be thresholded.''')

        self.thresholded_image_name = cpsetting.ImageNameProvider(
            "Name the output image",
            "ThreshBlue",
            doc='''
                                Give a name to the thresholded image?''')

        self.binary = cpsetting.Choice("Select the output image type",
                                       [GRAYSCALE, BINARY],
                                       doc='''
                                Two types of output images can be produced:<br>
                                <ul>
                                <li><i>Grayscale:</i> The pixels that are retained after some pixels are set to zero or shifted (based on your selections for thresholding options) will have their original 
                                intensity values.</li>
                                <li><i>Binary:</i> The pixels that are retained after some pixels are set to zero (based on your selections for thresholding options) will be white and all other pixels will be black (zeroes).</li>
                                </ul>''')
        # if not binary:
        self.low_or_high = cpsetting.Choice(
            "Set pixels below or above the threshold to zero?",
            [TH_BELOW_THRESHOLD, TH_ABOVE_THRESHOLD],
            doc=
            """<i>(Used only when thresholding a grayscale image)</i> For grayscale output, the dim pixels below 
                                the threshold can be set to zero or the bright pixels above 
                                the threshold can be set to zero.
                                Choose <i>Below threshold</i> to threshold dim pixels and
                                <i>Above threshold</i> to threshold bright pixels."""
        )

        # if not binary and below threshold

        self.shift = cpsetting.Binary(
            "Subtract the threshold value from the remaining pixel intensities?",
            False,
            doc='''
                                <i>(Used only if the image is grayscale and pixels below a given intensity are to be set to zero)</i><br>
                                Use this setting if the dim pixels are to be shifted in value by the amount of the threshold.'''
        )

        # if not binary and above threshold

        self.dilation = cpsetting.Float(
            "Number of pixels by which to expand the thresholding around those excluded bright pixels",
            0.0,
            doc='''
                                <i>(Used only if the output image is grayscale and pixels above a given intensity are to be set to zero)</i><br>
                                This setting is useful when attempting to exclude bright artifactual objects: 
                                first, set the threshold to exclude these bright objects; it may also be desirable to expand the
                                thresholded region around those bright objects by a certain distance so as to avoid a "halo" effect.'''
        )

        self.create_threshold_settings(threshold_methods)
        self.threshold_smoothing_choice.value = TSM_NONE
 def create_settings(self): # "self" refers to the module's class attributes
     self.text_setting = cps.Text("Text setting", "suggested value")
     self.choice_setting = cps.Choice(
         "Choice setting", ["Choice 1", "Choice 2", "Choice 3"])
     self.binary_setting = cps.Binary("Binary setting", False)
     self.integer_setting = cps.Integer("Integer setting", 15)
     self.float_setting = cps.Float("Float setting", 1.5)
Esempio n. 8
0
 def test_01_02_set_value(self):
     s = cps.Float("foo", value = 5)
     for test_case in ("6.00", "-1.75"):
         s.value_text = test_case
         self.assertEqual(s, float(test_case))
         self.assertEqual(s.value_text, test_case)
         s.test_valid(None)
Esempio n. 9
0
    def create_settings(self):
        self.image_name = cps.ImageNameSubscriber("Select the input image",
                                                  "None")
        self.output_name = cps.ImageNameProvider("Name the output image",
                                                 "FlippedOrigBlue")
        self.flip_choice = cps.Choice(
            "Select method to flip image",
            FLIP_ALL,
            doc=
            """How do you want to flip the image? Left to right, Top to bottom, or both?"""
        )
        self.rotate_choice = cps.Choice("Select method to rotate image",
                                        ROTATE_ALL,
                                        doc='''
             <ul> <li> <i>Angle:</i> Provide the numerical angle by which the 
             image should be rotated.</li>
             <li><i>Coordinates:</i> Provide the X,Y pixel locations of 
             two points in the image that should be aligned horizontally or 
             vertically.</li> 
             <li> <i>Mouse:</i> CellProfiler will pause so you can select the 
             rotation interactively. When prompted during the analysis run, grab the image by 
             clicking the left mouse button, rotate the image by 
             dragging with the mouse, then release the mouse button. Press the <i>Done</i> button on the image 
             after rotating the image appropriately.</li>
             </ul>''')

        self.wants_crop = cps.Binary(
            "Crop away the rotated edges?",
            True,
            doc=
            '''<i>(Used only when rotating images)</i> <br> When an image is rotated, there will be black space at the 
             corners/edges unless you choose to crop away the incomplete rows 
             and columns of the image. This cropping will produce an image that 
             is not exactly the same size as the original, which may affect 
             downstream modules.''')

        self.how_often = cps.Choice(
            "Calculate rotation",
            IO_ALL,
            doc='''<i>(Used only when rotating images with the mouse)</i> <br> 
            Do you want to determine the amount of rotation for each image
            individually as you cycle through, or do you want to define it
            only once (on the first image) and then apply it to all images?''')
        self.first_pixel = cps.Coordinates(
            "Enter coordinates of the top or left pixel", (0, 0))
        self.second_pixel = cps.Coordinates(
            "Enter the coordinates of the bottom or right pixel", (0, 100))
        self.horiz_or_vert = cps.Choice(
            "Select how the specified points should be aligned",
            C_ALL,
            doc=
            """<i>(Used only when rotating images by entering coordinates)</i><br> Should the points you specified be horizontally or vertically aligned after the rotation is complete?"""
        )
        self.angle = cps.Float(
            "Enter angle of rotation",
            0,
            doc=
            """<i>(Used only when rotating images by entering an angle)</i> <br> By what angle would you like to rotate the image 
            (in degrees; positive = counterclockwise and 
            negative = clockwise)?""")
    def create_settings(self):
        '''Create the initial settings for the module'''
        self.image_groups = []
        self.add_image(can_delete=False)
        self.spacer_1 = cps.Divider()
        self.add_image(can_delete=False)
        self.image_count = cps.HiddenCount(self.image_groups)

        self.add_image_button = cps.DoSomething("", 'Add another image', self.add_image)
        self.spacer_2 = cps.Divider()
        self.thr = cps.Float(
                "Set threshold as percentage of maximum intensity for the images",
                15, minval=0, maxval=99, doc='''\
            Select the threshold as a percentage of the maximum intensity of the above image [0-99].''')

        self.images_or_objects = cps.Choice(
                'Select where to measure correlation',
                [M_IMAGES, M_OBJECTS, M_IMAGES_AND_OBJECTS], doc='''
            You can measure the correlation in several ways:
            <ul>
            <li><i>%(M_OBJECTS)s:</i> Measure correlation only in those pixels previously
            identified as an object. You will be asked to specify which object to measure from.</li>
            <li><i>%(M_IMAGES)s:</i> Measure the correlation across all pixels in the images.</li>
            <li><i>%(M_IMAGES_AND_OBJECTS)s:</i> Calculate both measurements above.</li>
            </ul>
            All methods measure correlation on a pixel by pixel basis.''' % globals())

        self.object_groups = []
        self.add_object(can_delete=False)
        self.object_count = cps.HiddenCount(self.object_groups)

        self.spacer_2 = cps.Divider(line=True)

        self.add_object_button = cps.DoSomething("", 'Add another object', self.add_object)
Esempio n. 11
0
 def add_function(self, can_remove = True):
     group = MorphSettingsGroup()
     if can_remove:
         group.append("divider", cps.Divider(line=False))
     group.append("function", cps.Choice("Select the operation to perform",
                                        F_ALL, F_OPEN,doc="""
                                        What operation do you want to perform?
                 Choose one of the operations described in this module's help."""))
     group.append("repeats_choice", cps.Choice("Number of times to repeat operation",
                                               R_ALL,doc="""
                 This setting controls the number of times that the same operation is applied
                 successively to the image.
                 <ul>
                 <li><i>Once:</i> Perform the operation once on the image.</li>
                 <li><i>Forever:</i> Perform the operation on the image until successive
                 iterations yield the same image.</li>
                 <li><i>Custom:</i> Perform the operation a custom number of times.</li>
                 </ul>"""))
     group.append("custom_repeats", cps.Integer(self.CUSTOM_REPEATS_TEXT,2,1,
                  doc=self.CUSTOM_REPEATS_DOC))
     group.append("scale", cps.Float(
         "Scale",3, minval=3,
         doc="""Morphological open, close, erode and dialate are performed
         with structuring elements which determine the diameter of the
         circle enclosing the pixels to consider when applying the operation.
         This setting controls the diameter of the structuring element."""))
                                     
     if can_remove:
         group.append("remove", cps.RemoveSettingButton("", "Remove this operation", self.functions, group))
     self.functions.append(group)
Esempio n. 12
0
 def add_image(self, removable=True):
     # The text for these settings will be replaced in renumber_settings()
     group = cps.SettingsGroup()
     group.removable = removable
     group.append("image_or_measurement", cps.Choice(
         "Image or measurement?", [IM_IMAGE, IM_MEASUREMENT],
         doc="""You can perform math operations using two images or you
         can use a measurement for one of the operands. For instance,
         to divide the intensity of one image by another, choose Image
         for both and pick the respective images. To divide the intensity
         of an image by its median intensity, use <b>MeasureImageIntensity</b>
         prior to this module to calculate the median intensity, then
         select "Measurement" and use the median intensity measurement as
         the denominator"""))
     group.append("image_name", cps.ImageNameSubscriber("", "",doc="""Which image do you want to use for this operation?"""))
     group.append("measurement", cps.Measurement(
         "Measurement", lambda : cpmeas.IMAGE,"",
         doc="""This is a measurement made on the image. The value of the
         measurement is used for the operand for all of the pixels of the
         other operand's image."""))
     group.append("factor", cps.Float("", 1,doc="""By what number would you like to multiply the above image? This multiplication
             is applied before other operations."""))
     if removable:
         group.append("remover", cps.RemoveSettingButton("", "Remove this image", self.images, group))
     group.append("divider", cps.Divider())
     self.images.append(group)
 def create_settings(self):
     #
     # Put your ImageNameProvider and ImageNameSubscriber here
     #
     # use self.input_image_name as the ImageNameSubscriber
     # use self.output_image_name as the ImageNameProvider
     #
     # Those are the names that are expected by the unit tests.
     #
     ##self.input_image_name = cps.ImageNameSubscriber("Input image")
     ##self.output_image_name = cps.ImageNameProvider("Output image",
     ##                                               "Sharpened")
     self.scale = cps.Float(
         "Scale",
         .5,
         0,
         doc=
         """This is the sigma of the Gaussian used as the point spread function"""
     )
     #
     # We use a number of iterations to perform. An alternative or adjunct
     # would be to have the loop exit based on the estimated error reaching
     # a certain value
     #
     self.iterations = cps.Integer(
         "Iterations",
         10,
         1,
         doc="""The number of times to iterate toward maximum likelihood
         estimate.""")
Esempio n. 14
0
    def create_settings(self):
        self.image_name = cps.ImageNameSubscriber("Select the input image",
                                                  "None", doc = '''What did you call the image to be resized?''')

        self.resized_image_name = cps.ImageNameProvider("Name the output image",
                                                        "ResizedBlue", doc = '''What do you want to call the resized image?''')

        self.size_method = cps.Choice("Select resizing method",
                                      R_ALL, doc = """How do you want to resize the image? 
                                      <ul><li><i>Resize by a fraction or multiple of the original size:</i> 
                                      Enter a single value which specifies the scaling. </li>
                                      <li><i>Resize by specifying desired final dimensions:</i></li> 
                                      Enter the new height and width of the resized image.</ul>""")

        self.resizing_factor = cps.Float("Resizing factor",
                                         .25, minval=0, doc = '''
                                         <i>(Used only if resizing by a fraction or multiple of the original size)</i><br>
                                         Numbers less than one (that is, fractions) will shrink the image; 
                                         numbers greater than one (that is, multiples) will enlarge the image.''')

        self.use_manual_or_image = cps.Choice("How do you want to specify the dimensions?",C_ALL, doc = """
                                        <i>(Used only if resizing by specifying the dimensions)</i><br>
                                        You have two options on how to resize your image:
                                        <ul>
                                        <li><i>Manual:</i> Specify the height and width of the output image.</li>
                                        <li><i>Image:</i> Specify an image and the input image will be resized
                                        to the same dimensions.</li>
                                        </ul>""")
        
        self.specific_width = cps.Integer("Width of the final image, in pixels", 100, minval=1, doc = '''
                                         <i>(Used only if resizing by specifying desired final dimensions)</i><br>
                                         Enter the desired width of the final image.''')

        self.specific_height = cps.Integer("Height of the final image, in pixels", 100, minval=1, doc = '''
                                         <i>(Used only if resizing by specifying desired final dimensions)</i><br>
                                         Enter the desired height of the final image.''')
        
        self.specific_image = cps.ImageNameSubscriber("Select the image with the desired dimensions", "None", doc = """"
                                        <i>(Used only if resizing by specifying desired final dimensions using an image)</i><br>
                                        The input image will be resized to the dimensions of the specified image.""")

        self.interpolation = cps.Choice("Interpolation method",
                                        I_ALL, doc = '''<ul><li><i>Nearest Neighbor:</i> Each output pixel is given the intensity of the nearest
                                        corresponding pixel in the input image.</li>
                                        <li><i>Bilinear:</i> Each output pixel is given the intensity of the weighted average
                                        of the 2x2 neighborhood at the corresponding position in the input image.</li>
                                        <li><i>Bicubic:</i> Each output pixel is given the intensity of the weighted average
                                        of the 4x4 neighborhood at the corresponding position in the input image.</li>
                                        </ul>''')
        
        self.separator = cps.Divider(line=False)
        
        self.additional_images = []
        
        self.additional_image_count = cps.HiddenCount(
            self.additional_images, "Additional image count")
        
        self.add_button = cps.DoSomething("", "Add another image",
                                          self.add_image)
Esempio n. 15
0
    def create_settings(self):
        self.blank_image = cps.Binary(
                "Display outlines on a blank image?",
                False, doc="""
            Select <i>%(YES)s</i> to produce an
            image of the outlines on a black background.
            <p>Select <i>%(NO)s</i>, the module will overlay the
            outlines on an image of your choosing.</p>""" % globals())

        self.image_name = cps.ImageNameSubscriber(
                "Select image on which to display outlines", cps.NONE, doc="""
            <i>(Used only when a blank image has not been selected)</i> <br>
            Choose the image to serve as the background for the outlines.
            You can choose from images that were loaded or created by modules
            previous to this one.""")

        self.line_width = cps.Float(
                "Width of outlines", "1", doc="""
            Enter the width, in pixels, of the
            outlines to be displayed on the image.""")

        self.output_image_name = cps.ImageNameProvider(
                "Name the output image", "OrigOverlay", doc="""
            Enter the name of the output image with the outlines overlaid.
            This image can be selected in later modules (for instance, <b>SaveImages</b>).""")

        self.wants_color = cps.Choice(
                "Outline display mode",
                [WANTS_COLOR, WANTS_GRAYSCALE], doc="""
            Specify how to display the outline contours around
            your objects. Color outlines produce a clearer display for
            images where the cell borders have a high intensity, but take
            up more space in memory. Grayscale outlines are displayed with
            either the highest possible intensity or the same intensity
            as the brightest pixel in the image.""")

        self.spacer = cps.Divider(line=False)

        self.max_type = cps.Choice(
                "Select method to determine brightness of outlines",
                [MAX_IMAGE, MAX_POSSIBLE], doc="""
            <i>(Used only when outline display mode is grayscale)</i> <br>
            The following options are possible for setting the intensity
            (brightness) of the outlines:
            <ul>
            <li><i>%(MAX_IMAGE)s:</i> Set the brighness to the
            the same as the brightest point in the image.</li>
            <li><i>%(MAX_POSSIBLE)s:</i> Set to the maximum
            possible value for this image format.</li>
            </ul>
            If your image is quite dim, then putting bright white lines
            onto it may not be useful. It may be preferable to make the
            outlines equal to the maximal brightness already occurring
            in the image.""" % globals())

        self.outlines = []
        self.add_outline(can_remove=False)
        self.add_outline_button = cps.DoSomething("", "Add another outline", self.add_outline)
Esempio n. 16
0
    def add_measurement(self, can_delete=True):
        '''Add another measurement to the filter list'''
        group = cps.SettingsGroup()
        group.append(
            "measurement",
            cps.Measurement('Select the measurement to filter by',
                            self.object_name.get_value,
                            "AreaShape_Area",
                            doc="""
            <i>(Used only if filtering using measurements)</i><br>
            See the <b>Measurements</b> modules help pages 
            for more information on the features measured."""))

        group.append(
            "wants_minimum",
            cps.Binary('Filter using a minimum measurement value?',
                       True,
                       doc="""
            <i>(Used only if Limits is selected for filtering method)</i><br>
            Check this box to filter the objects based on a minimum acceptable object
            measurement value. Objects which are greater than or equal to this value
            will be retained."""))

        group.append("min_limit", cps.Float('Minimum value', 0))

        group.append(
            "wants_maximum",
            cps.Binary('Filter using a maximum measurement value?',
                       True,
                       doc="""
            <i>(Used only if Limits is selected for filtering method)</i><br>
            Check this box to filter the objects based on a maximum acceptable object
            measurement value. Objects which are less than or equal to this value
            will be retained."""))

        group.append("max_limit", cps.Float('Maximum value', 1))
        group.append("divider", cps.Divider())
        self.measurements.append(group)
        if can_delete:
            group.append(
                "remover",
                cps.RemoveSettingButton("Remove above measurement", "Remove",
                                        self.measurements, group))
Esempio n. 17
0
 def create_settings(self):
     self.input_image_name = cps.ImageNameSubscriber("Input image", "None")
     self.output_image_name = cps.ImageNameProvider("Output image",
                                                    "Projection")
     self.scale = cps.Float(
         "Scale",
         .125,
         .0001,
         1,
         doc="""Scale the image dimensions by this fraction""")
Esempio n. 18
0
 def test_01_06_display_float_setting(self):
     v = cps.Float("text",1.5)
     app,text_control,edit_control = self.set_setting(v)
     self.assertAlmostEqual(float(edit_control.Value),1.5)
     self.assertTrue(isinstance(edit_control, wx.TextCtrl))
     edit_control.SetValue("2.5")
     app.ProcessPendingEvents()
     edit_control = self.get_edit_control(app,v)
     self.assertAlmostEqual(float(edit_control.Value),2.5)
     self.assertAlmostEqual(v.value,2.5)
     app.frame.Destroy()
     app.ProcessPendingEvents()
     app.ProcessIdle()
Esempio n. 19
0
 def add_group(self, can_delete = True):
     '''Add a group to the list of groups
     
     can_delete - if true, add a button that removes the entry
     '''
     #
     # Make a new settings group to hold the settings
     #
     group = cps.SettingsGroup()
     #
     # if you can delete, that means there's a setting above this one,
     # so it's nice to add a divider in that case.
     #
     if can_delete:
         group.append("divider", cps.Divider())
     #
     # Each instance has an addend and a multiplicand. We run through
     # them all, first adding, then multiplying to get our final answer
     #
     # group.append takes two arguments. The first is the name for the
     # attribute. In this case, we used "addend" so if you want to get
     # the addend, you say, "group.addend.value"
     #
     group.append("addend", cps.Float("Addend", 0))
     group.append("multiplicand", cps.Float("Multiplicand", 1))
     #
     # Only add the RemoveSettingButton if we can delete
     #
     if can_delete:
         group.append("remover", cps.RemoveSettingButton(
             "Remove this entry", "Remove",
             self.groups, group))
     #
     # Add the new group to the list.
     #
     self.groups.append(group)
Esempio n. 20
0
    def add_channel(self, can_remove=True):
        '''Add another channel to the channels list'''
        group = cps.SettingsGroup()
        group.can_remove = can_remove
        group.append(
            "channel_choice",
            cps.Choice("Channel number",
                       self.channel_names,
                       self.channel_names[len(self.channels) %
                                          len(self.channel_names)],
                       doc="""
            This setting chooses a channel to be processed.
            <i>Red: 1</i> is the first channel in a .TIF or the red channel
            in a traditional image file. <i>Green: 2</i> and <i>Blue: 3</i>
            are the second and third channels of a TIF or the green and blue
            channels in other formats. <i>Alpha: 4</i> is the transparency
            channel for image formats that support transparency and is
            channel # 4 for a .TIF file.
            
            <b>ColorToGray</b> will fail to process an image if you select
            a channel that is not supported by that image, for example, "5"
            for a .PNG file"""))

        group.append(
            "contribution",
            cps.Float("Relative weight of the channel",
                      1,
                      0,
                      doc='''
            <i>(Used only when combining channels)</i><br>
            Relative weights: If all relative weights are equal, all three 
            colors contribute equally in the final image. To weight colors relative 
            to each other, increase or decrease the relative weights.'''))

        group.append(
            "image_name",
            cps.ImageNameProvider("Image name",
                                  value="Channel%d" % (len(self.channels) + 1),
                                  doc="""
            This is the name of the grayscale image that holds
            the image data from the chosen channel."""))

        if group.can_remove:
            group.append(
                "remover",
                cps.RemoveSettingButton("", "Remove this channel",
                                        self.channels, group))
        self.channels.append(group)
Esempio n. 21
0
 def create_settings(self): # "self" refers to the module's class attributes
     self.text_setting = cps.Text(
         "Text setting", "suggested value",
     ##    doc = "This is the help for the text setting"
     )
     self.choice_setting = cps.Choice(
         "Choice setting", ["Choice 1", "Choice 2", "Choice 3"],
     ##    doc = "This is the help for the choice setting"
     )
     self.binary_setting = cps.Binary(
         "Binary setting", False,
     ##    doc = "This is the help for the binary setting"
     )
     self.integer_setting = cps.Integer(
         "Integer setting", 15,
     ##    doc = "This is the help for the integer setting"
     )
     self.float_setting = cps.Float(
         "Float setting", 1.5,
         doc = "This is the help for the float setting")
Esempio n. 22
0
    def add_stack_channel_cb(self, can_remove=True):
        group = cps.SettingsGroup()
        default_color = DEFAULT_COLORS[len(self.stack_channels) %
                                       len(DEFAULT_COLORS)]
        group.append(
            "image_name",
            cps.ImageNameSubscriber("Image name",
                                    cps.NONE,
                                    doc="""
                <i>Used only if %(SCHEME_STACK)s or %(SCHEME_COMPOSITE)s is
                chosen</i><br>
                Select the input image to add to the stacked image""" %
                                    globals()))
        group.append(
            "color",
            cps.Color("Color",
                      default_color,
                      doc="""
            <i>Used only if %(SCHEME_COMPOSITE)s is chosen</i>
            <br>The color to be assigned to the above image.
            """ % globals()))
        group.append(
            "weight",
            cps.Float("Weight",
                      1.0,
                      minval=.5 / 255,
                      doc="""
            <i>Used only if %(SCHEME_COMPOSITE)s is chosen</i>
            <br>The weighting of the above image relative to the others. The
            image's pixel values are multiplied by this weight before assigning
            the color.
            """ % globals()))

        if can_remove:
            group.append(
                "remover",
                cps.RemoveSettingButton("", "Remove this image",
                                        self.stack_channels, group))
        self.stack_channels.append(group)
Esempio n. 23
0
    def create_settings(self):
        """Create the settings for the module
        
        Create the settings for the module during initialization.
        """
        self.image_name = cps.ImageNameSubscriber("Select the input image",
                                                  cps.NONE,
                                                  doc="""
            The name of a binary image from a previous module.
            <b>IdentifyDeadWorms</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 worm and then use
            <b>ConvertObjectsToImage</b> to make the result a mask.""")

        self.object_name = cps.ObjectNameProvider(
            "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
            <b>IdentifySecondaryObjects</b>""")

        self.worm_width = cps.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 = cps.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 = cps.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>IdentifyDeadWorms</b> automatically calculates the parameters
            used to determine whether two found-worm centers belong to the
            same worm. 
            <p>Select <i>%(YES)s</i> to have <b>IdentifyDeadWorms</b>
            automatically calculate the distance from the worm length
            and width. Select <i>%(NO)s</i> to set the distances manually.</p>"""
            % globals())

        self.space_distance = cps.Float("Spatial distance",
                                        5,
                                        minval=1,
                                        doc="""
            <i>(Used only if not automatically calculating distance parameters)</i><br>
            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 = cps.Float("Angular distance",
                                          30,
                                          minval=1,
                                          doc="""
            <i>(Used only if automatically calculating distance parameters)</i><br>
            <b>IdentifyDeadWorms</b> 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.""")
Esempio n. 24
0
    def create_settings(self):
        #XXX needs to use cps.SettingsGroup
        class Operand(object):
            '''Represents the collection of settings needed by each operand'''
            def __init__(self, index, operation):
                self.__index = index
                self.__operation = operation
                self.__operand_choice = cps.Choice(self.operand_choice_text(),
                                                   MC_ALL,
                                                   doc="""
                    Indicate whether the operand is an image or object measurement."""
                                                   )

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

                self.__operand_measurement = cps.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 = cps.Float(
                    "Multiply the above operand by",
                    1,
                    doc="""
                    Enter the number by which you would like to multiply the above operand."""
                )

                self.__exponent = cps.Float(
                    "Raise the power of above operand by",
                    1,
                    doc="""
                    Enter the power by which you would like to raise the above operand."""
                )

            @property
            def operand_choice(self):
                '''Either MC_IMAGE for image measurements or MC_OBJECT for object'''
                return self.__operand_choice

            @property
            def operand_objects(self):
                '''Get measurements from these objects'''
                return self.__operand_objects

            @property
            def operand_measurement(self):
                '''The measurement providing the value of the operand'''
                return self.__operand_measurement

            @property
            def multiplicand(self):
                '''Premultiply the measurement by this value'''
                return self.__multiplicand

            @property
            def exponent(self):
                '''Raise the measurement to this power'''
                return self.__exponent

            @property
            def object(self):
                '''The name of the object for measurement or "Image"'''
                if self.operand_choice == MC_IMAGE:
                    return cpmeas.IMAGE
                else:
                    return self.operand_objects.value

            def object_fn(self):
                if self.__operand_choice == MC_IMAGE:
                    return cpmeas.IMAGE
                elif self.__operand_choice == MC_OBJECT:
                    return self.__operand_objects.value
                else:
                    raise NotImplementedError(
                        "Measurement type %s is not supported" %
                        self.__operand_choice.value)

            def operand_name(self):
                '''A fancy name based on what operation is being performed'''
                if self.__index == 0:
                    return ("first operand" if self.__operation
                            in (O_ADD, O_MULTIPLY) else "minuend"
                            if self.__operation == O_SUBTRACT else "numerator")
                elif self.__index == 1:
                    return ("second operand" if self.__operation
                            in (O_ADD, O_MULTIPLY) else "subtrahend" if
                            self.__operation == O_SUBTRACT else "denominator")

            def operand_choice_text(self):
                return self.operand_text("Select the %s measurement type")

            def operand_objects_text(self):
                return self.operand_text("Select the %s objects")

            def operand_text(self, format):
                return format % self.operand_name()

            def operand_measurement_text(self):
                return self.operand_text("Select the %s measurement")

            def settings(self):
                '''The operand settings to be saved in the output file'''
                return [
                    self.operand_choice, self.operand_objects,
                    self.operand_measurement, self.multiplicand, self.exponent
                ]

            def visible_settings(self):
                '''The operand settings to be displayed'''
                self.operand_choice.text = self.operand_choice_text()
                self.operand_objects.text = self.operand_objects_text()
                self.operand_measurement.text = self.operand_measurement_text()
                result = [self.operand_choice]
                result += ([self.operand_objects] if self.operand_choice == MC_OBJECT \
                          else [])
                result += [
                    self.operand_measurement, self.multiplicand, self.exponent
                ]
                return (result)

        self.output_feature_name = cps.AlphanumericText(
            "Name the output measurement",
            "Measurement",
            doc="""
            Enter a name for the measurement calculated by this module.""")

        self.operation = cps.Choice("Operation",
                                    O_ALL,
                                    doc="""
            Choose the arithmetic operation would you like to perform. <i>None</i> is useful if 
            you simply want to select some of the later options in the module, such as multiplying
            or exponentiating your image by a constant.""")

        self.operands = (Operand(0,
                                 self.operation), Operand(1, self.operation))

        self.spacer_1 = cps.Divider(line=True)

        self.spacer_2 = cps.Divider(line=True)

        self.spacer_3 = cps.Divider(line=True)

        self.wants_log = cps.Binary("Take log10 of result?",
                                    False,
                                    doc="""
            Select <i>%(YES)s</i> if you want the log (base 10) of the result."""
                                    % globals())

        self.final_multiplicand = cps.Float("Multiply the result by",
                                            1,
                                            doc="""
            <i>(Used only for operations other than None)</i><br>
            Enter the number by which you would like to multiply the result."""
                                            )

        self.final_exponent = cps.Float("Raise the power of result by",
                                        1,
                                        doc="""
            <i>(Used only for operations other than None)</i><br>
            Enter the power by which you would like to raise the result.""")

        self.final_addend = cps.Float("Add to the result",
                                      0,
                                      doc="""
            Enter the number you like to add to the result.""")

        self.constrain_lower_bound = cps.Binary(
            "Constrain the result to a lower bound?",
            False,
            doc="""
            Select <i>%(YES)s</i> if you want the result to be 
            constrained to a lower bound.""" % globals())

        self.lower_bound = cps.Float("Enter the lower bound",
                                     0,
                                     doc="""
            Enter the lower bound of the result here.""")

        self.constrain_upper_bound = cps.Binary(
            "Constrain the result to an upper bound?",
            False,
            doc="""
            Select <i>%(YES)s</i> if you want the result to be 
            constrained to an upper bound.""" % globals())

        self.upper_bound = cps.Float("Enter the upper bound",
                                     1,
                                     doc="""
            Enter the upper bound of the result here.""")
Esempio n. 25
0
    def add_function(self, can_remove=True):
        group = MorphSettingsGroup()
        group.can_remove = can_remove
        if can_remove:
            group.append("divider", cps.Divider(line=False))
        group.append(
            "function",
            cps.Choice("Select the operation to perform",
                       F_ALL,
                       F_OPEN,
                       doc="""
            Choose one of the operations described in this module's help."""))

        group.append(
            "repeats_choice",
            cps.Choice("Number of times to repeat operation",
                       R_ALL,
                       doc="""
            This setting controls the number of times that the same operation is applied
            successively to the image.
            <ul>
            <li><i>%(R_ONCE)s:</i> Perform the operation once on the image.</li>
            <li><i>%(R_FOREVER)s:</i> Perform the operation on the image until successive
            iterations yield the same image.</li>
            <li><i>%(R_CUSTOM)s:</i> Perform the operation a custom number of times.</li>
            </ul>""" % globals()))

        group.append(
            "custom_repeats",
            cps.Integer(self.CUSTOM_REPEATS_TEXT,
                        2,
                        1,
                        doc=self.CUSTOM_REPEATS_DOC))

        group.append(
            "structuring_element",
            cps.Choice("Structuring element",
                       SE_ALL,
                       SE_DISK,
                       doc="""
            <i>(Used only for %(SE_F_TEXT)s)</i><br>
            The structuring element controls which neighboring pixels participate
            in the operation. For instance, for the %(F_ERODE)s operation, all
            pixels in the neighborhood of the pixel must be in the foreground for
            the pixel to be in the foreground in the output image. If a circular
            structuring element is used, then a pixel will be in the foreground
            only if all neighborhood pixels within a circle surrounding the
            pixel are in the foreground in the input image.

            <p>The structuring elements are:<br>
            <ul>
            <li><i>%(SE_DISK)s</i>: A disk centered on the pixel. The diameter
            setting determines the circle's diameter and all pixels that are
            at or closer than that diameter will be in the neighborhood.</li>
            <li><i>%(SE_ARBITRARY)s</i>: A structuring element which lets
            the user choose the exact neighborhood pixels to use.</li>
            <li><i>%(SE_DIAMOND)s</i>: A diamond centered on the pixel. The
            diameter setting determines the distance between the top and bottom
            and left and right corners of the diamond.</li>
            <li><i>%(SE_LINE)s</i>: A line centered on the pixel. The line
            has two settings. The angle setting gives the rotation of the line
            in the counter-clockwise direction in degrees, with a horizontal
            line having an angle of zero. The length of the line is determined
            by the diameter setting - only pixels at or closer than 1/2 of the
            diameter are included in the neighborhood. The line is drawn using
            the <a href="http://dx.doi.org/10.1147%%2Fsj.41.0025">
            Bresenham algorithm</a>.</li>
            <li><i>%(SE_OCTAGON)s</i>: An octagon centered on the pixel. The
            octagon is inscribed inside a square. The diameter setting controls
            the length of the square's side. The diameter is rounded to the nearest
            integer in the series, n * 6 + 1 so a perfect octagon can be drawn.</li>
            <li><i>%(SE_PAIR)s</i>: The neighborhood of the pixel is
            composed of the pixel itself and the pixel at the x and y offsets
            given by the settings.</li>
            <li><i>%(SE_PERIODIC_LINE)s</i>: The points along a line described
            by an offset, centered on the pixel. The periodic line has three
            settings. The neighborhood pixels are all within a circle whose
            diameter is the diameter setting. Within the circle, pixels are
            chosen at N times the x and y offset from the center for positive
            and negative values of N.</li>
            <li><i>%(SE_RECTANGLE)s</i>: A rectangle centered on the pixel.
            The rectangle's height and width are given by two settings.</li>
            <li><i>%(SE_SQUARE)s</i>: a square centered on the pixel. The
            diameter setting determines the length of the square's side.</li>
            </ul></p>""" % globals()))

        group.append(
            "scale",
            cps.Float("Diameter",
                      3,
                      minval=3,
                      doc="""
            Morphological open, close, erode and dialate are performed
            with structuring elements which determine the diameter of the
            circle enclosing the pixels to consider when applying the operation.
            This setting controls the diameter of the structuring element."""))

        group.append(
            "x_offset",
            cps.Float("X offset",
                      1,
                      doc="""
            <i>(Used only for the %(SE_PAIR)s and %(SE_PERIODIC_LINE)s
            settings)</i>. The X offset to the first neighborhood pixel in
            the structuring element.
            """ % globals()))

        group.append(
            "y_offset",
            cps.Float("Y offset",
                      1,
                      doc="""
            <i>(Used only for the %(SE_PAIR)s and %(SE_PERIODIC_LINE)s
            structuring elements)</i>. The Y offset to the first neighborhood
            pixel in the structuring element.
            """ % globals()))

        group.append(
            "angle",
            cps.Float("Angle",
                      0,
                      minval=-180,
                      maxval=180,
                      doc="""
            <i>(Used only for the %(SE_LINE)s structuring element).</i>
            The angle, in degrees counter-clockwise from the horizontal,
            of the line.
            """ % globals()))

        group.append(
            "width",
            cps.Float("Width",
                      3,
                      minval=1,
                      doc="""
            <i>(Used only for the %(SE_RECTANGLE)s structuring element).</i>
            The width of the rectangle in pixels.
            """ % globals()))

        group.append(
            "height",
            cps.Float("Height",
                      3,
                      minval=1,
                      doc="""
            <i>(Used only for the %(SE_RECTANGLE)s structuring element).</i>
            The height of the rectangle in pixels.
            """ % globals()))

        group.append(
            "strel",
            cps.BinaryMatrix("Custom",
                             doc="""
            <i>(Used only for the %(SE_ARBITRARY)s structuring element).</i>
            This control lets you specify a custom structuring element.
            """ % globals()))

        group.append(
            "rescale_values",
            cps.Binary("Rescale values from 0 to 1?",
                       True,
                       doc="""
            <i>(Used only for the %(F_DISTANCE)s operation).</i>
            <p>Select <i>%(YES)s</i> to rescale the transformed values to lie between 0 and 1.
            This is the option to use if the distance transformed image is to be used
            for thresholding by an <b>Identify</b> module or the like, which assumes
            a 0-1 scaling.</p>
            <p>Select <i>%(NO)s</i> to leave the values in absolute pixel units.
            This useful in cases where the actual pixel distances are to be used
            downstream as input for a measurement module.</p>
            """ % globals()))

        if can_remove:
            group.append(
                "remove",
                cps.RemoveSettingButton("", "Remove this operation",
                                        self.functions, group))
        self.functions.append(group)
    def create_settings(self):
        self.image_name = cps.ImageNameSubscriber('Select the input image',
                                                  'None',
                                                  doc="""
            What did you call the image with features to be enhanced or suppressed?"""
                                                  )

        self.filtered_image_name = cps.ImageNameProvider(
            'Name the output image',
            'FilteredBlue',
            doc="""
            What do you want to call the feature-enhanced or suppressed image?"""
        )

        self.method = cps.Choice('Select the operation', [ENHANCE, SUPPRESS],
                                 doc="""
            Do you want to enhance or suppress the features you designated?
            <ul><li><i>Enhance</i> produces an image whose intensity is largely 
            composed of the features of interest.</li>
            <li>Choose <i>Suppress</i> to produce an image with the features largely
            removed.</li></ul>""")

        self.enhance_method = cps.Choice(
            'Feature type', [
                E_SPECKLES, E_NEURITES, E_DARK_HOLES, E_CIRCLES, E_TEXTURE,
                E_DIC
            ],
            doc="""<i>(Used only if Enhance is selected)</i><br>
            This module can enhance three kinds of image intensity features:
            <ul><li><i>Speckles</i>: A speckle is an area of enhanced intensity
            relative to its immediate neighborhood. The module enhances
            speckles using a white tophat filter, which is the image minus the
            morphological grayscale opening of the image. The opening operation
            first suppresses the speckles by applying a grayscale erosion to reduce everything
            within a given radius to the lowest value within that radius, then uses
            a grayscale dilation to restore objects larger than the radius to an
            approximation of their former shape. The white tophat filter enhances 
            speckles by subtracting the effects of opening from the original image.
            </li>
            <li><i>Neurites</i>: Neurites are taken to be long, thin features
            of enhanced intensity. Choose this option to enhance the intensity
            of the neurites using the %(N_GRADIENT)s or %(N_TUBENESS)s methods
            described below.</li>
            <li><i>Dark holes</i>: The module uses morphological reconstruction 
            (the rolling-ball algorithm) to identify dark holes within brighter
            areas, or brighter ring shapes. The image is inverted so that the dark holes turn into
            bright peaks. The image is successively eroded and the eroded image
            is reconstructed at each step, resulting in an image which is
            missing the peaks. Finally, the reconstructed image is subtracted
            from the previous reconstructed image. This leaves circular bright
            spots with a radius equal to the number of iterations performed.
            </li>
            <li><i>Circles</i>: The module calculates the circular Hough transform of
            the image at the diameter given by the feature size. The Hough transform
            will have the highest intensity at points that are centered within a ring
            of high intensity pixels where the ring diameter is the feature size. You
            may want to use the <b>EnhanceEdges</b> module to find the edges of your
            circular object and then process the output by enhancing circles. You can
            use <b>IdentifyPrimaryObjects</b> to find the circle centers and then use
            these centers as seeds in <b>IdentifySecondaryObjects</b> to find whole,
            circular objects using a watershed.</li>
            <li><i>Texture</i>: <b>EnanceOrSuppressFeatures</b> produces an image
            whose intensity is the variance among nearby pixels. This method weights
            pixel contributions by distance using a Gaussian to calculate the weighting.
            You can use this method to separate foreground from background if the foreground
            is textured and the background is not.
            </li>
            <li><i>DIC</i>: This method recovers the optical density of a DIC image by
            integrating in a direction perpendicular to the shear direction of the image.
            </li>
            </ul>
            In addition, this module enables you to suppress certain features (such as speckles)
            by specifying the feature size.""" % globals())

        self.object_size = cps.Integer('Feature size',
                                       10,
                                       2,
                                       doc="""
            <i>(Used only if circles, speckles or neurites are selected, or if suppressing features)</i><br>
            What is the feature size? 
            The diameter of the largest speckle, the width of the circle
            or the width of the neurites to be enhanced or suppressed, which
            will be used to calculate an adequate filter size. %(HELP_ON_PIXEL_INTENSITIES)s"""
                                       % globals())

        self.hole_size = cps.IntegerRange('Range of hole sizes',
                                          value=(1, 10),
                                          minval=1,
                                          doc="""
            <i>(Used only if dark hole detection is selected)</i><br>
            The range of hole sizes to be enhanced. The algorithm will
            identify only holes whose diameters fall between these two 
            values""")

        self.smoothing = cps.Float(
            'Smoothing scale',
            value=2.0,
            minval=0.1,
            doc="""<i>(Used only for the texture, DIC or neurite methods)</i><br>
            For texture, this is the scale of the texture features, roughly
            in pixels. The algorithm uses the smoothing value entered as
            the sigma of the Gaussian used to weight nearby pixels by distance
            in the variance calculation.<br>
            The DIC method smooths the image in the direction parallel to the
            shear axis of the image. The line integration method will leave
            streaks in the image without smoothing as it encounters noisy
            pixels during the course of the integration. The smoothing takes
            contributions from nearby pixels which decreases the noise but
            smooths the resulting image. For DIC, increase the smoothing to
            eliminate streakiness and decrease the smoothing to sharpen
            the image.<br>
            The %(N_TUBENESS)s option of the neurite method uses this scale
            as the sigma of the Gaussian used to smooth the image prior to
            gradient detection.""" % globals())

        self.angle = cps.Float('Shear angle',
                               value=0,
                               doc="""<i>(Used only for the DIC method)</i><br>
            The shear angle is the direction of constant value for the
            shadows and highlights in a DIC image. The gradients in a DIC
            image run in the direction perpendicular to the shear angle.
            For example, if the shadows run diagonally from lower left
            to upper right and the highlights appear above the shadows,
            the shear angle is 45 degrees. If the shadows appear on top,
            the shear angle is 180 + 45 = 225 degrees.
            """)

        self.decay = cps.Float('Decay',
                               value=.95,
                               minval=0.1,
                               maxval=1,
                               doc="""<i>(Used only for the DIC method)</i><br>
            The decay setting applies an exponential decay during the process
            of integration by multiplying the accumulated sum by the decay
            at each step. This lets the integration recover from accumulated
            error during the course of the integration, but it also results
            in diminished intensities in the middle of large objects.
            Set the decay to a large value, on the order of 1 - 1/diameter
            of your objects if the intensities decrease toward the middle.
            Set the decay to a small value if there appears to be a bias
            in the integration direction.""")

        self.neurite_choice = cps.Choice(
            "Enhancement method", [N_TUBENESS, N_GRADIENT],
            doc="""<i>(Used only for the neurites method)</i><br>
            Two methods can be used to enhance neurites:<br>
            <ul><li><i>%(N_TUBENESS)s</i>: This method is an adaptation of
            the method used by the <a href="http://www.longair.net/edinburgh/imagej/tubeness/">
            ImageJ Tubeness plugin</a>. The image
            is smoothed with a Gaussian. The Hessian is then computed at every
            point to measure the intensity gradient and the eigenvalues of the
            Hessian are computed to determine the magnitude of the intensity.
            The absolute maximum of the two eigenvalues gives a measure of
            the ratio of the intensity of the gradient in the direction of
            its most rapid descent versus in the orthogonal direction. The
            output image is the absolute magnitude of the highest eigenvalue
            if that eigenvalue is negative (white neurite on dark background),
            otherwise, zero.</li>
            <li><i>%(N_GRADIENT)s</i>: The module takes the difference of the
            white and black tophat filters (a white tophat filtering is the image minus 
            the morphological grayscale opening of the image; a black tophat filtering is the 
            morphological grayscale closing of the image minus the image). 
            The effect is to enhance lines whose width is the "feature size".
            </li></ul>""" % globals())
Esempio n. 27
0
    def create_settings(self):
        self.primary_objects = cps.ObjectNameSubscriber(
            "Select the input objects",
            "Nuclei",
            doc="""
            What did you call the objects you want to use as "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.objects_name = cps.ObjectNameProvider(
            "Name the objects to be identified",
            "Cells",
            doc="""
            Enter the name that you want to call the objects identified by this module."""
        )

        self.method = cps.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="""
            <p>There are several methods available to find the dividing lines 
            between secondary objects which touch each other:
            <ul>
            <li><i>%(M_PROPAGATION)s:</i> 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 <i>Watershed</i> 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 (<i>Jones et al, 2005</i>).</li>
           
            <li><i>%(M_WATERSHED_G)s:</i> This method uses the watershed algorithm
            (<i>Vincent and Soille, 1991</i>) 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.
            </li>
            <li><i>%(M_WATERSHED_I)s:</i> 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 form the boundaries between
            cells. This method works best when there is a saddle of relatively
            low intensity at the cell-cell boundary.
            </li>
            <li><i>Distance:</i> 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:
            <ul>
            <li><i>%(M_DISTANCE_N)s</i>: In this method, the image of the secondary 
            staining is not used at all; the expanded objects are the 
            final secondary objects.</li> 
            <li><i>%(M_DISTANCE_B)s</i>: 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.</li></ul></li>
            </ul>
            <b>References</b>
            <ul>
            <li>Jones TR, Carpenter AE, Golland P (2005) "Voronoi-Based Segmentation of Cells on Image Manifolds",
            <i>ICCV Workshop on Computer Vision for Biomedical Image Applications</i>, 535-543. 
            (<a href="http://www.cellprofiler.org/linked_files/Papers/JonesCVBIA2005.pdf">link</a>)</li>
            <li>(Vincent L, Soille P (1991) "Watersheds in Digital Spaces: An Efficient Algorithm Based on Immersion
            Simulations", <i>IEEE Transactions of Pattern Analysis and Machine
            Intelligence</i>, 13(6): 583-598 
            (<a href="http://dx.doi.org/10.1109/34.87344">link</a>)</li>
            </ul>""" % globals())

        self.image_name = cps.ImageNameSubscriber("Select the input image",
                                                  cps.NONE,
                                                  doc="""
            The selected image will be used to find the edges of the secondary objects.
            For <i>%(M_DISTANCE_N)s</i> this will not affect object identification, 
            only the final display.""" % globals())

        self.create_threshold_settings()
        # default smoothing choice is different for idprimary and idsecondary
        self.threshold_smoothing_choice.value = cpmi.TSM_NONE

        self.distance_to_dilate = cps.Integer(
            "Number of pixels by which to expand the primary objects",
            10,
            minval=1)

        self.regularization_factor = cps.Float("Regularization factor",
                                               0.05,
                                               minval=0,
                                               doc="""
            <i>(Used only if %(M_PROPAGATION)s method is selected)</i> <br>
            The regularization factor &lambda; 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: 
            <ul>
            <li>A &lambda; 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. </li>
            <li>Larger values of &lambda; put more and more weight on the distance between the two objects.
            This relationship is such that small changes in &lambda; will have fairly different 
            results (e.,g 0.01 vs 0.001). However, the intensity image is almost completely 
            ignored at &lambda; much greater than 1.</li>
            <li>At infinity, the result will look like %(M_DISTANCE_B)s, masked to the
            secondary staining image.</li>
            </ul>""" % globals())

        self.use_outlines = cps.Binary(
            "Retain outlines of the identified secondary objects?",
            False,
            doc="""
            %(RETAINING_OUTLINES_HELP)s""" % globals())

        self.outlines_name = cps.OutlineNameProvider('Name the outline image',
                                                     "SecondaryOutlines",
                                                     doc="""
            %(NAMING_OUTLINES_HELP)s""" % globals())

        self.wants_discard_edge = cps.Binary(
            "Discard secondary objects touching the border of the image?",
            False,
            doc="""
            Select <i>%(YES)s</i> to discard secondary objects which touch
            the image border. Select <i>%(NO)s</i> to retain objects regardless
            of whether they touch the image edge or not.
            <p>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.</p>""" % globals())

        self.fill_holes = cps.Binary("Fill holes in identified objects?",
                                     True,
                                     doc="""
            Select <i>%(YES)s</i> to fill any holes inside objects.""" %
                                     globals())

        self.wants_discard_primary = cps.Binary(
            "Discard the associated primary objects?",
            False,
            doc="""
            <i>(Used only if discarding secondary objects touching the image border)</i> <br>
            It might be appropriate to discard the primary object
            for any secondary object that touches the edge of the image.
            <p>Select <i>%(YES)s</i> to create a new set of objects that are identical 
            to the original primary objects set, minus the objects for which the associated 
            secondary object touches the image edge.</p>""" % globals())

        self.new_primary_objects_name = cps.ObjectNameProvider(
            "Name the new primary objects",
            "FilteredNuclei",
            doc="""
            <i>(Used only if associated primary objects are discarded)</i> <br>
            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.wants_primary_outlines = cps.Binary(
            "Retain outlines of the new primary objects?",
            False,
            doc="""
            <i>(Used only if associated primary objects are discarded)</i><br>
            %(RETAINING_OUTLINES_HELP)s""" % globals())

        self.new_primary_outlines_name = cps.OutlineNameProvider(
            "Name the new primary object outlines",
            "FilteredNucleiOutlines",
            doc="""
            <i>(Used only if associated primary objects are discarded and saving outlines of new primary objects)</i><br>
            Enter a name for the outlines of the identified 
            objects. The outlined image can be selected in downstream modules by selecting 
            them from any drop-down image list.""")
Esempio n. 28
0
    def add_measurement(self, flag_settings, can_delete=True):
        measurement_settings = flag_settings.measurement_settings

        group = cps.SettingsGroup()
        group.append("divider1", cps.Divider(line=False))
        group.append(
            "source_choice",
            cps.Choice("Flag is based on",
                       S_ALL,
                       doc='''
                        <ul>
                        <li><i>%(S_IMAGE)s:</i> A per-image measurement, such as intensity or 
                        granularity.</li>
                        <li><i>%(S_AVERAGE_OBJECT)s:</i> The average of all 
                        object measurements in the image.</li>
                        <li><i>%(S_ALL_OBJECTS)s:</i> All the 
                        object measurements in an image, without averaging. In other words, if <i>any</i> 
                        of the objects meet the criteria, the image will be flagged.</li>
                        <li><i>%(S_RULES)s:</i>Use a text file of rules produced by CellProfiler Analyst. If you 
                        choose <i>Rules</i>, you will have to ensure that this pipeline makes every measurement 
                        in the rules file prior to this module.</li>
                        </ul>''' % globals()))

        group.append(
            "object_name",
            cps.ObjectNameSubscriber(
                "Select the object to be used for flagging",
                cps.NONE,
                doc='''
                        <i>(Used only when flag is based on an object measurement)</i><br>
                        Select the objects whose measurements you want to use for flagging.'''
            ))

        def object_fn():
            if group.source_choice == S_IMAGE:
                return cpmeas.IMAGE
            return group.object_name.value

        group.append(
            "rules_directory",
            cps.DirectoryPath("Rules file location",
                              doc="""
                        <i>(Used only when flagging using %(S_RULES)s)</i><br>
                        Select the location of the rules file that will be used for filtering.
                        %(IO_FOLDER_CHOICE_HELP_TEXT)s""" % globals()))

        def get_directory_fn():
            '''Get the directory for the rules file name'''
            return group.rules_directory.get_absolute_path()

        def set_directory_fn(path):
            dir_choice, custom_path = group.rules_directory.get_parts_from_path(
                path)
            group.rules_directory.join_parts(dir_choice, custom_path)

        group.append(
            "rules_file_name",
            cps.FilenameText("Rules file name",
                             "rules.txt",
                             get_directory_fn=get_directory_fn,
                             set_directory_fn=set_directory_fn,
                             doc="""
                        <i>(Used only when flagging using %(S_RULES)s)</i><br>
                        The name of the rules file. This file should be a plain text
                        file containing the complete set of rules.
                        <p>Each line of
                        this file should be a rule naming a measurement to be made
                        on an image, for instance:
                        <pre>IF (Image_ImageQuality_PowerLogLogSlope_DNA &lt; -2.5, [0.79, -0.79], [-0.94, 0.94])</pre><br><br>
                        The above rule will score +0.79 for the positive category and -0.94
                        for the negative category for images whose power log slope is less than -2.5
                        pixels and will score the opposite for images whose slope is larger.
                        The filter adds positive and negative and flags the images whose
                        positive score is higher than the negative score.</p>"""
                             % globals()))

        def get_rules_class_choices(group=group):
            '''Get the available choices from the rules file'''
            try:
                rules = self.get_rules(group)
                nclasses = len(rules.rules[0].weights[0])
                return [str(i) for i in range(1, nclasses + 1)]
            except:
                return [str(i) for i in range(1, 3)]

        group.append(
            "rules_class",
            cps.MultiChoice("Class number",
                            choices=["1", "2"],
                            doc="""
                        <i>(Used only when flagging using %(S_RULES)s)</i><br>
                        Select which classes to flag when filtering. The
                        CellProfiler Analyst classifier user interface lists the names of 
                        the classes in order. By default, these are the positive (class 1)
                        and negative (class 2) classes. <b>FlagImage</b> uses the
                        first class from CellProfiler Analyst if you choose "1", etc. 
                        <p>Please note the following:
                        <ul>
                        <li>The flag is set if the image falls into the selected class.</li>
                        <li>You can make multiple class selections. If you do so, the module
                        will set the flag if the image falls into any of the selected classes.</li>
                        </ul></p>""" % globals()))

        group.rules_class.get_choices = get_rules_class_choices

        group.append("measurement",
                     cps.Measurement("Which measurement?", object_fn))

        group.append(
            "wants_minimum",
            cps.Binary("Flag images based on low values?",
                       True,
                       doc='''
                         Select <i>%(YES)s</i> to flag images with measurements below the specified cutoff.'''
                       % globals()))

        group.append("minimum_value", cps.Float("Minimum value", 0))

        group.append(
            "wants_maximum",
            cps.Binary("Flag images based on high values?",
                       True,
                       doc='''
                         Select <i>%(YES)s</i> to flag images with measurements above the specified cutoff.'''
                       % globals()))

        group.append("maximum_value", cps.Float("Maximum value", 1))

        if can_delete:
            group.append(
                "remover",
                cps.RemoveSettingButton("", "Remove this measurement",
                                        measurement_settings, group))

        group.append("divider2", cps.Divider(line=True))
        measurement_settings.append(group)
Esempio n. 29
0
    def create_settings(self):
        """Create the settings for the module

        Create the settings for the module during initialization.
        """
        self.contrast_choice = cps.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:
            <ul>
            <li><i>%(BY_SINGLE_MEASUREMENT)s:</i> Classifies each object based on a single measurement.</li>
            <li><i>%(BY_TWO_MEASUREMENTS)s:</i> Classifies each object based on a pair of measurements taken
            together (that is, an object must meet two criteria to belong to a class).</li>
            </ul>""" % 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 = cps.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 = cps.DoSomething(
            "", "Add another classification", self.add_single_measurement)
        #
        ############### Two-measurement settings #####################
        #
        # The object for the contrasting method
        #
        self.object_name = cps.ObjectNameSubscriber("Select the object name",
                                                    cps.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
            <b>IdentifyPrimaryObjects</b>, <b>IdentifySecondaryObjects</b>, or
            <b>IdentifyTertiaryObjects</b>.""")

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

        self.first_measurement = cps.Measurement(
            "Select the first measurement",
            object_fn,
            doc="""
            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 = cps.Choice(
            "Method to select the cutoff", [TM_MEAN, TM_MEDIAN, TM_CUSTOM],
            doc="""
            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:<br>
            <ul>
            <li><i>%(TM_MEAN)s</i>: At the mean
            of the measurement's value for all objects in the image cycle.</li>
            <li><i>%(TM_MEDIAN)s</i>: At the median of the
            measurement's value for all objects in the image set.</li>
            <li><i>%(TM_CUSTOM)s</i>: You specify a custom threshold value.</li>
            </ul>""" % globals())

        self.first_threshold = cps.Float("Enter the cutoff value",
                                         0.5,
                                         doc="""
            This is the cutoff value separating objects in the two
            classes.""")

        self.second_measurement = cps.Measurement(
            "Select the second measurement",
            object_fn,
            doc="""
            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 = cps.Choice(
            "Method to select the cutoff", [TM_MEAN, TM_MEDIAN, TM_CUSTOM],
            doc="""
            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:<br>
            <ul>
            <li><i>%(TM_MEAN)s:</i> At the mean
            of the measurement's value for all objects in the image cycle.</li>
            <li><i>%(TM_MEDIAN)s:</i> At the median of the
            measurement's value for all objects in the image set.</li>
            <li><i>%(TM_CUSTOM)s:</i> You specify a custom threshold value.</li>
            </ul>""" % globals())

        self.second_threshold = cps.Float("Enter the cutoff value",
                                          0.5,
                                          doc="""
            This is the cutoff value separating objects in the two
            classes.""")

        self.wants_custom_names = cps.Binary("Use custom names for the bins?",
                                             False,
                                             doc="""
            Select <i>%(YES)s</i> if you want to specify the names of each bin
            measurement. <br>
            Select <i>%(NO)s</i> 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 = cps.AlphanumericText(
            "Enter the low-low bin name",
            "low_low",
            doc="""
            <i>(Used only if using a pair of measurements)</i><br>
            Name of the measurement for objects that
            fall below the threshold for both measurements.""")

        self.low_high_custom_name = cps.AlphanumericText(
            "Enter the low-high bin name",
            "low_high",
            doc="""
            <i>(Used only if using a pair of measurements)</i><br>
            Name of the measurement for objects whose
            first measurement is below threshold and whose second measurement
            is above threshold.""")

        self.high_low_custom_name = cps.AlphanumericText(
            "Enter the high-low bin name",
            "high_low",
            doc="""
            <i>(Used only if using a pair of measurements)</i><br>
            Name of the measurement for objects whose
            first measurement is above threshold and whose second measurement
            is below threshold.""")

        self.high_high_custom_name = cps.AlphanumericText(
            "Enter the high-high bin name",
            "high_high",
            doc="""
            <i>(Used only if using a pair of measurements)</i><br>
            Name of the measurement for objects that
            are above the threshold for both measurements.""")

        self.wants_image = cps.Binary(
            "Retain an image of the classified objects?",
            False,
            doc="""
            Select <i>%(YES)s</i> 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 <b>SaveImages</b> module).""" % globals())

        self.image_name = cps.ImageNameProvider("Enter the image name",
                                                cps.NONE,
                                                doc="""
            <i>(Used only if the classified object image is to be retained for later use in the pipeline)</i> <br>
            Enter the name to be given to the classified object image.""")
Esempio n. 30
0
    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 = cps.SettingsGroup()
        if can_delete:
            group.append("divider", cps.Divider(line=True))

        group.append(
            "object_name",
            cps.ObjectNameSubscriber(
                "Select the object to be classified",
                cps.NONE,
                doc="""The name of the objects to be classified. You can
            choose from objects created by any previous module. See
            <b>IdentifyPrimaryObjects</b>, <b>IdentifySecondaryObjects</b>, or
            <b>IdentifyTertiaryObjects</b>."""))

        def object_fn():
            return group.object_name.value

        group.append(
            "measurement",
            cps.Measurement("Select the measurement to classify by",
                            object_fn,
                            doc="""
            Select a measurement made by a previous module. The objects
            will be classified according to their values for this
            measurement."""))

        group.append(
            "bin_choice",
            cps.Choice("Select bin spacing", [BC_EVEN, BC_CUSTOM],
                       doc="""
            Select how you want to define the spacing of the bins.
            You have the following options:
            <ul>
            <li><i>%(BC_EVEN)s:</i> 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.</li>
            <li><i>%(BC_CUSTOM)s:</i> 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.</li>
            </ul>
            """ % globals()))

        group.append(
            "bin_count",
            cps.Integer("Number of bins",
                        3,
                        minval=1,
                        doc="""
            This is the number of bins that will be created between
            the low and high threshold"""))

        group.append(
            "low_threshold",
            cps.Float("Lower threshold",
                      0,
                      doc="""
            <i>(Used only if "%(BC_EVEN)s" selected)</i><br>
            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",
            cps.Binary("Use a bin for objects below the threshold?",
                       False,
                       doc="""
            Select <i>%(YES)s</i> if you want to create a bin for objects
            whose values fall below the low threshold. Select <i>%(NO)s</i>
            if you do not want a bin for these objects.
            """ % globals()))

        def min_upper_threshold():
            return group.low_threshold.value + np.finfo(float).eps

        group.append(
            "high_threshold",
            cps.Float("Upper threshold",
                      1,
                      minval=cps.NumberConnector(min_upper_threshold),
                      doc="""
            <i>(Used only if "%(BC_EVEN)s" selected)</i><br>
            This is the threshold that separates the last bin from
            the others.
            <i>Note:</i> If you would like two bins, you should select <i>%(BC_CUSTOM)s</i>.
            """ % globals()))

        group.append(
            "wants_high_bin",
            cps.Binary("Use a bin for objects above the threshold?",
                       False,
                       doc="""
            Select <i>%(YES)s</i> if you want to create a bin for objects
            whose values are above the high threshold. <br>
            Select <i>%(NO)s</i> if you do not want a bin for these objects."""
                       % globals()))

        group.append(
            "custom_thresholds",
            cps.Text(
                "Enter the custom thresholds separating the values between bins",
                "0,1",
                doc="""
            <i>(Used only if "%(BC_CUSTOM)s" selected)</i><br>
            This setting establishes the threshold values for the
            bins. You should enter one threshold between each bin, separating
            thresholds with commas (for example, <i>0.3, 1.5, 2.1</i> for four bins).
            The module will create one more bin than there are thresholds.
            """ % globals()))

        group.append(
            "wants_custom_names",
            cps.Binary("Give each bin a name?",
                       False,
                       doc="""
            Select <i>%(YES)s</i> to assign custom names to bins you have
            specified.
            <p>Select <i>%(NO)s</i> for the module to automatically
            assign names based on the measurements and the bin number.""" %
                       globals()))

        group.append(
            "bin_names",
            cps.Text("Enter the bin names separated by commas",
                     cps.NONE,
                     doc="""
            <i>(Used only if Give each bin a name? is checked)</i><br>
            Enter names for each of the bins, separated by commas.
            An example including three bins might be <i>First,Second,Third</i>."""
                     ))

        group.append(
            "wants_images",
            cps.Binary("Retain an image of the classified objects?",
                       False,
                       doc="""
            Select <i>%(YES)s</i> 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 <b>SaveImages</b> module).""" % globals()))

        group.append(
            "image_name",
            cps.ImageNameProvider("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 cps.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 cps.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():
                cps.AlphanumericText.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 cps.ValidationError(
                        'Custom thresholds must be a comma-separated list '
                        'of numbers (example: "1.0, 2.3, 4.5")',
                        group.custom_thresholds)

        group.validate_group = validate_group

        if can_delete:
            group.remove_settings_button = cps.RemoveSettingButton(
                "", "Remove this classification", self.single_measurements,
                group)
        self.single_measurements.append(group)