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.""")
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.""")
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)
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.""")
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)
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)
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)
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)
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.""")
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)
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)
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))
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""")
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()
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)
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)
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")
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)
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°, 15°, 30°, 45° ... 165°. The shape is bilaterally symmetric; that is, you will get the same shape after rotating it by 180°.""") self.wants_automatic_distance = 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.""")
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.""")
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())
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 λ 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 λ 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 λ put more and more weight on the distance between the two objects. This relationship is such that small changes in λ will have fairly different results (e.,g 0.01 vs 0.001). However, the intensity image is almost completely ignored at λ much greater than 1.</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.""")
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 < -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)
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.""")
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)