docs: def detect_sources(data, threshold, npixels, filter_kernel=None,
                     connectivity=8, mask=None):
      """
      Detect sources above a specified threshold value in an image and
      return a `~photutils.segmentation.SegmentationImage` object.
  
      Detected sources must have ``npixels`` connected pixels that are
      each greater than the ``threshold`` value.  If the filtering option
      is used, then the ``threshold`` is applied to the filtered image.
      The input ``mask`` can be used to mask pixels in the input data.
      Masked pixels will not be included in any source.
  
      This function does not deblend overlapping sources.  First use this
      function to detect sources followed by
      :func:`~photutils.segmentation.deblend_sources` to deblend sources.
  
      Parameters
      ----------
      data : array_like
          The 2D array of the image.
  
      threshold : float or array-like
          The data value or pixel-wise data values to be used for the
          detection threshold.  A 2D ``threshold`` must have the same
          shape as ``data``.  See `~photutils.detection.detect_threshold`
          for one way to create a ``threshold`` image.
  
      npixels : int
          The number of connected pixels, each greater than ``threshold``,
          that an object must have to be detected.  ``npixels`` must be a
          positive integer.
  
      filter_kernel : array-like (2D) or `~astropy.convolution.Kernel2D`, optional
          The 2D array of the kernel used to filter the image before
          thresholding.  Filtering the image will smooth the noise and
          maximize detectability of objects with a shape similar to the
          kernel.
  
      connectivity : {4, 8}, optional
          The type of pixel connectivity used in determining how pixels
          are grouped into a detected source.  The options are 4 or 8
          (default).  4-connected pixels touch along their edges.
          8-connected pixels touch along their edges or corners.  For
          reference, SExtractor uses 8-connected pixels.
  
      mask : array_like (bool)
          A boolean mask, with the same shape as the input ``data``, where
          `True` values indicate masked pixels.  Masked pixels will not be
          included in any source.
  
      Returns
      -------
      segment_image : `~photutils.segmentation.SegmentationImage` or `None`
          A 2D segmentation image, with the same shape as ``data``, where
          sources are marked by different positive integer values.  A
          value of zero is reserved for the background.  If no sources
          are found then `None` is returned.
  
      See Also
      --------
      :func:`photutils.detection.detect_threshold`,
      :class:`photutils.segmentation.SegmentationImage`,
      :func:`photutils.segmentation.source_properties`
      :func:`photutils.segmentation.deblend_sources`
  
      Examples
      --------
  
      .. plot::
          :include-source:
  
          # make a table of Gaussian sources
          from astropy.table import Table
          table = Table()
          table['amplitude'] = [50, 70, 150, 210]
          table['x_mean'] = [160, 25, 150, 90]
          table['y_mean'] = [70, 40, 25, 60]
          table['x_stddev'] = [15.2, 5.1, 3., 8.1]
          table['y_stddev'] = [2.6, 2.5, 3., 4.7]
          table['theta'] = np.array([145., 20., 0., 60.]) * np.pi / 180.
  
          # make an image of the sources with Gaussian noise
          from photutils.datasets import make_gaussian_sources_image
          from photutils.datasets import make_noise_image
          shape = (100, 200)
          sources = make_gaussian_sources_image(shape, table)
          noise = make_noise_image(shape, type='gaussian', mean=0.,
                                   stddev=5., random_state=12345)
          image = sources + noise
  
          # detect the sources
          from photutils import detect_threshold, detect_sources
          threshold = detect_threshold(image, snr=3)
          from astropy.convolution import Gaussian2DKernel
          sigma = 3.0 / (2.0 * np.sqrt(2.0 * np.log(2.0)))   # FWHM = 3
          kernel = Gaussian2DKernel(sigma, x_size=3, y_size=3)
          kernel.normalize()
          segm = detect_sources(image, threshold, npixels=5,
                                filter_kernel=kernel)
  
          # plot the image and the segmentation image
          import matplotlib.pyplot as plt
          fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 8))
          ax1.imshow(image, origin='lower', interpolation='nearest')
          ax2.imshow(segm.data, origin='lower', interpolation='nearest')
      """
  
      from scipy import ndimage
  
      if (npixels <= 0) or (int(npixels) != npixels):
          raise ValueError('npixels must be a positive integer, got '
                           '"{0}"'.format(npixels))
  
      image = (filter_data(data, filter_kernel, mode='constant', fill_value=0.0,
                           check_normalization=True) > threshold)
  
      if mask is not None:
          if mask.shape != image.shape:
              raise ValueError('mask must have the same shape as the input '
                               'image.')
          image &= ~mask
  
      if connectivity == 4:
          selem = ndimage.generate_binary_structure(2, 1)
      elif connectivity == 8:
          selem = ndimage.generate_binary_structure(2, 2)
      else:
          raise ValueError('Invalid connectivity={0}.  '
                           'Options are 4 or 8'.format(connectivity))
  
      segm_img, nobj = ndimage.label(image, structure=selem)
  
      # remove objects with less than npixels
      # NOTE:  for typical data, making the cutout images is ~10x faster
      # than using segm_img directly
      segm_slices = ndimage.find_objects(segm_img)
      for i, slices in enumerate(segm_slices):
          cutout = segm_img[slices]
          segment_mask = (cutout == (i+1))
          if np.count_nonzero(segment_mask) < npixels:
              cutout[segment_mask] = 0
  
      # now relabel to make consecutive label indices
      segm_img, nobj = ndimage.label(segm_img, structure=selem)
  
      if nobj == 0:
          warnings.warn('No sources were found.', NoDetectionsWarning)
          return None
      else:
          return SegmentationImage(segm_img)