docs: def _properties_table(obj, columns=None, exclude_columns=None):
      """
      Construct a `~astropy.table.QTable` of source properties from a
      `SourceProperties` or `SourceCatalog` object.
  
      Parameters
      ----------
      obj : `SourceProperties` or `SourceCatalog` instance
          The object containing the source properties.
  
      columns : str or list of str, optional
          Names of columns, in order, to include in the output
          `~astropy.table.QTable`.  The allowed column names are any
          of the attributes of `SourceProperties`.
  
      exclude_columns : str or list of str, optional
          Names of columns to exclude from the default properties list
          in the output `~astropy.table.QTable`.
  
      Returns
      -------
      table : `~astropy.table.QTable`
          A table of source properties with one row per source.
      """
  
      # default properties
      columns_all = ['id', 'xcentroid', 'ycentroid', 'sky_centroid',
                     'sky_centroid_icrs', 'source_sum', 'source_sum_err',
                     'background_sum', 'background_mean',
                     'background_at_centroid', 'xmin', 'xmax', 'ymin',
                     'ymax', 'min_value', 'max_value', 'minval_xpos',
                     'minval_ypos', 'maxval_xpos', 'maxval_ypos', 'area',
                     'equivalent_radius', 'perimeter',
                     'semimajor_axis_sigma', 'semiminor_axis_sigma',
                     'eccentricity', 'orientation', 'ellipticity',
                     'elongation', 'covar_sigx2', 'covar_sigxy',
                     'covar_sigy2', 'cxx', 'cxy', 'cyy']
  
      table_columns = None
      if exclude_columns is not None:
          table_columns = [s for s in columns_all if s not in exclude_columns]
      if columns is not None:
          table_columns = np.atleast_1d(columns)
      if table_columns is None:
          table_columns = columns_all
  
      tbl = QTable()
      for column in table_columns:
          values = getattr(obj, column)
  
          if isinstance(obj, SourceProperties):
              # turn scalar values into length-1 arrays because QTable
              # column assignment requires an object with a length
              values = np.atleast_1d(values)
  
              # Unfortunately np.atleast_1d creates an array of SkyCoord
              # instead of a SkyCoord array (Quantity does work correctly
              # with np.atleast_1d).  Here we make a SkyCoord array for
              # the output table column.
              if isinstance(values[0], SkyCoord):
                  values = SkyCoord(values)  # length-1 SkyCoord array
  
          tbl[column] = values
  
      return tbl