docstr: 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.
docstr_tok: ["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","."]
code: def add_single_measurement(self, can_delete=True):
      """Add a single measurement to the group of single measurements
  
      can_delete - True to include a "remove" button, False if you're not
                   allowed to remove it.
      """
      group = SettingsGroup()
      if can_delete:
          group.append("divider", Divider(line=True))
  
      group.append(
          "object_name",
          LabelSubscriber(
              "Select the object to be classified",
              "None",
              doc="""\
  The name of the objects to be classified. You can choose from objects
  created by any previous module. See **IdentifyPrimaryObjects**,
  **IdentifySecondaryObjects**, **IdentifyTertiaryObjects**, or **Watershed**
  """,
          ),
      )
  
      def object_fn():
          return group.object_name.value
  
      group.append(
          "measurement",
          Measurement(
              "Select the measurement to classify by",
              object_fn,
              doc="""\
  *(Used only if using a single measurement)*
  
  Select a measurement made by a previous module. The objects will be
  classified according to their values for this measurement.
  """,
          ),
      )
  
      group.append(
          "bin_choice",
          Choice(
              "Select bin spacing",
              [BC_EVEN, BC_CUSTOM],
              doc="""\
  *(Used only if using a single measurement)*
  
  Select how you want to define the spacing of the bins. You have the
  following options:
  
  -  *%(BC_EVEN)s:* Choose this if you want to specify bins of equal
     size, bounded by upper and lower limits. If you want two bins, choose
     this option and then provide a single threshold when asked.
  -  *%(BC_CUSTOM)s:* Choose this option to create the indicated number
     of bins at evenly spaced intervals between the low and high
     threshold. You also have the option to create bins for objects that
     fall below or above the low and high threshold.
  """
              % globals(),
          ),
      )
  
      group.append(
          "bin_count",
          Integer(
              "Number of bins",
              3,
              minval=1,
              doc="""\
  *(Used only if using a single measurement)*
  
  This is the number of bins that will be created between
  the low and high threshold""",
          ),
      )
  
      group.append(
          "low_threshold",
          Float(
              "Lower threshold",
              0,
              doc="""\
  *(Used only if using a single measurement and "%(BC_EVEN)s" selected)*
  
  This is the threshold that separates the lowest bin from the others. The
  lower threshold, upper threshold, and number of bins define the
  thresholds of bins between the lowest and highest.
  """
              % globals(),
          ),
      )
  
      group.append(
          "wants_low_bin",
          Binary(
              "Use a bin for objects below the threshold?",
              False,
              doc="""\
  *(Used only if using a single measurement)*
  
  Select "*Yes*" if you want to create a bin for objects whose values
  fall below the low threshold. Select "*No*" if you do not want a bin
  for these objects.
  """,
          ),
      )
  
      group.append(
          "high_threshold",
          Float(
              "Upper threshold",
              1,
              doc="""\
  *(Used only if using a single measurement and "%(BC_EVEN)s" selected)*
  
  This is the threshold that separates the last bin from the others. Note
  that if you would like two bins, you should select "*%(BC_CUSTOM)s*".
  """
              % globals(),
          ),
      )
  
      group.append(
          "wants_high_bin",
          Binary(
              "Use a bin for objects above the threshold?",
              False,
              doc="""\
  *(Used only if using a single measurement)*
  
  Select "*Yes*" if you want to create a bin for objects whose values
  are above the high threshold.
  
  Select "*No*" if you do not want a bin for these objects.
  """,
          ),
      )
  
      group.append(
          "custom_thresholds",
          Text(
              "Enter the custom thresholds separating the values between bins",
              "0,1",
              doc="""\
  *(Used only if using a single measurement and "%(BC_CUSTOM)s" selected)*
  
  This setting establishes the threshold values for the bins. You should
  enter one threshold between each bin, separating thresholds with commas
  (for example, *0.3, 1.5, 2.1* for four bins). The module will create one
  more bin than there are thresholds.
  """
              % globals(),
          ),
      )
  
      group.append(
          "wants_custom_names",
          Binary(
              "Give each bin a name?",
              False,
              doc="""\
  *(Used only if using a single measurement)*
  
  Select "*Yes*" to assign custom names to bins you have specified.
  
  Select "*No*" for the module to automatically assign names based on
  the measurements and the bin number.
  """,
          ),
      )
  
      group.append(
          "bin_names",
          Text(
              "Enter the bin names separated by commas",
              "None",
              doc="""\
  *(Used only if "Give each bin a name?" is checked)*
  
  Enter names for each of the bins, separated by commas.
  An example including three bins might be *First,Second,Third*.""",
          ),
      )
  
      group.append(
          "wants_images",
          Binary(
              "Retain an image of the classified objects?",
              False,
              doc="""\
  Select "*Yes*" to keep an image of the objects which is color-coded
  according to their classification, for use later in the pipeline (for
  example, to be saved by a **SaveImages** module).
  """,
          ),
      )
  
      group.append(
          "image_name",
          ImageName(
              "Name the output image",
              "ClassifiedNuclei",
              doc="""Enter the name to be given to the classified object image.""",
          ),
      )
  
      group.can_delete = can_delete
  
      def number_of_bins():
          """Return the # of bins in this classification"""
          if group.bin_choice == BC_EVEN:
              value = group.bin_count.value
          else:
              value = len(group.custom_thresholds.value.split(",")) - 1
          if group.wants_low_bin:
              value += 1
          if group.wants_high_bin:
              value += 1
          return value
  
      group.number_of_bins = number_of_bins
  
      def measurement_name():
          """Get the measurement name to use inside the bin name
  
          Account for conflicts with previous measurements
          """
          measurement_name = group.measurement.value
          other_same = 0
          for other in self.single_measurements:
              if id(other) == id(group):
                  break
              if other.measurement.value == measurement_name:
                  other_same += 1
          if other_same > 0:
              measurement_name += str(other_same)
          return measurement_name
  
      def bin_feature_names():
          """Return the feature names for each bin"""
          if group.wants_custom_names:
              return [
                  name.strip()
                  for name in group.bin_names.value.split(",")
              ]
          return [
              "_".join((measurement_name(), "Bin_%d" % (i + 1)))
              for i in range(number_of_bins())
          ]
  
      group.bin_feature_names = bin_feature_names
  
      def validate_group():
          bin_name_count = len(bin_feature_names())
          bin_count = number_of_bins()
          if bin_count < 1:
              bad_setting = (
                  group.bin_count
                  if group.bin_choice == BC_EVEN
                  else group.custom_thresholds
              )
              raise ValidationError(
                  "You must have at least one bin in order to take measurements. "
                  "Either add more bins or ask for bins for objects above or below threshold",
                  bad_setting,
              )
          if bin_name_count != number_of_bins():
              raise ValidationError(
                  "The number of bin names (%d) does not match the number of bins (%d)."
                  % (bin_name_count, bin_count),
                  group.bin_names,
              )
          for bin_feature_name in bin_feature_names():
              Alphanumeric.validate_alphanumeric_text(
                  bin_feature_name, group.bin_names, True
              )
          if group.bin_choice == BC_CUSTOM:
              try:
                  [
                      float(x.strip())
                      for x in group.custom_thresholds.value.split(",")
                  ]
              except ValueError:
                  raise ValidationError(
                      "Custom thresholds must be a comma-separated list "
                      'of numbers (example: "1.0, 2.3, 4.5")',
                      group.custom_thresholds,
                  )
          elif group.bin_choice == BC_EVEN:
              if (
                  group.low_threshold.value
                  >= group.high_threshold.value
              ):
                  raise ValidationError(
                      "Lower Threshold must be less than Upper Threshold",
                      group.low_threshold,
                  )
  
      group.validate_group = validate_group
  
      if can_delete:
          group.remove_settings_button = RemoveSettingButton(
              "",
              "Remove this classification",
              self.single_measurements,
              group,
          )
      self.single_measurements.append(group)
  
      self.model_directory = Directory(
          "Select the location of the classifier model file",
          doc=f"""\
              *(Used only when using {BY_MODEL} mode)*
  
              Select the location of the classifier file that will be used for
              classification.
  
              {_help.IO_FOLDER_CHOICE_HELP_TEXT}
              """,
      )
  
      self.create_class_sets = Binary(
          "Save classes as new object sets?",
          False,
          doc="Choose whether to create new object sets from classes specified by the classifier model",
      )
  
      def get_directory_fn():
          """Get the directory for the rules file name"""
          return self.model_directory.get_absolute_path()
  
      def set_directory_fn(path):
          (
              dir_choice,
              custom_path,
          ) = self.model_directory.get_parts_from_path(path)
  
          self.model_directory.join_parts(dir_choice, custom_path)
          if not get_headless():
              import wx
  
              wx.CallAfter(update_choices)
  
      def update_choices():
          # Very hacky, but we can force the UI to update the list of available classes
          # by running the Choice object validation function. Otherwise new class names
          # won't be available until the user changes another setting.
          for groupid in self.desired_classes:
              try:
                  groupid.class_name.test_valid(None)
              except:
                  # There will almost always be errors, but we just want the box updated.
                  pass
  
      self.model_file_name = Filename(
          "Rules or classifier file name",
          "mymodel.model",
          get_directory_fn=get_directory_fn,
          set_directory_fn=set_directory_fn,
          doc=f"""\
  *(Used only when using {BY_MODEL} mode)*
  
  The name of the classifier model file.
  
  A classifier file is a trained classifier exported from CellProfiler Analyst.
  You will need to ensure that the measurements specified by the file are
  produced by upstream modules in the pipeline. This setting is not compatible
  with data processed as 3D.
  
  This should either be a .model file exported from CPA, or a .txt file featuring 
  rules from CPA's FastGentleBoosting classifier type.
  
  If you're working with sklearn outside of CPA and want to build your own model, 
  the saved object should be a tuple containing the following:
  
  [0] - The sklearn classifier object. Any scaler to be applied should be attached as self.scaler.
  
  [1] - A list of class names in the order produced by the classifier.
  
  [2] - A string containing the name of the model class.
  
  [3] - A list of CellProfiler feature names used to generate the model. Must be in the 
  same order as the training set.
  
  Use joblib.dump to export this tuple to a file.
  """,
      )
  
      self.want_class_object_sets = Binary(
          "Create object sets from classes?",
          value=False,
          doc="""\
                  Choose whether to generate object sets from classes identified by the classifier model
                  """,
      )
  
      ############### Classifier class settings ##################
      #
      # A list holding groupings for each of the classifier classes
      # to be extracted
      #
      self.desired_classes = []
      #
      # A count of # of measurements
      #
      self.desired_classes_count = HiddenCount(self.desired_classes)
      #
      # Add one single measurement to start off
      #
      self.add_single_class(False)
      #
      # A button to press to get another measurement
      #
      self.add_class_button = DoSomething(
          "", "Add another class", self.add_single_class
      )
  
imports: import functools
  import os
  import numpy
  from cellprofiler.modules import _help
  from cellprofiler.utilities.rules import Rules
  from cellprofiler_core.constants.measurement import COLTYPE_FLOAT, M_LOCATION_CENTER_X, M_LOCATION_CENTER_Y, COLTYPE_VARCHAR, C_COUNT, C_CHILDREN, C_LOCATION, C_NUMBER, C_PARENT, FTR_OBJECT_NUMBER, FTR_CENTER_X, FTR_CENTER_Y, FTR_CENTER_Z
  from cellprofiler_core.constants.measurement import COLTYPE_INTEGER
  from cellprofiler_core.constants.measurement import IMAGE
  from cellprofiler_core.image import Image
  from cellprofiler_core.module import Module
  from cellprofiler_core.object import Objects
  from cellprofiler_core.preferences import get_default_colormap, get_headless
  from cellprofiler_core.setting import Binary
  from cellprofiler_core.setting import Divider
  from cellprofiler_core.setting import HiddenCount
  from cellprofiler_core.setting import Measurement
  from cellprofiler_core.setting import SettingsGroup
  from cellprofiler_core.setting import ValidationError
  from cellprofiler_core.setting.choice import Choice
  from cellprofiler_core.setting.do_something import DoSomething
  from cellprofiler_core.setting.do_something import RemoveSettingButton
  from cellprofiler_core.setting.subscriber import LabelSubscriber
  from cellprofiler_core.setting.text import Alphanumeric, Directory, Filename, LabelName
  from cellprofiler_core.setting.text import Float
  from cellprofiler_core.setting.text import ImageName
  from cellprofiler_core.setting.text import Integer
  from cellprofiler_core.setting.text import Text
  from cellprofiler_core.constants.measurement import FF_CHILDREN_COUNT
  from cellprofiler_core.constants.measurement import FF_COUNT
  from cellprofiler_core.constants.measurement import FF_PARENT
  from cellprofiler_core.module.image_segmentation import ImageSegmentation
label_desc: Use image node_id 3 for calling the ClassifyObjects obj's underlying member method code with example usage: obj.add_single_measurement(can_delete) and returns: group, value, measurement_name, unknown, self, unknown