docs: def centroid_sources(data, xpos, ypos, box_size=11, footprint=None,
                       error=None, mask=None, centroid_func=centroid_com):
      """
      Calculate the centroid of sources at the defined positions.
  
      A cutout image centered on each input position will be used to
      calculate the centroid position.  The cutout image is defined either
      using the ``box_size`` or ``footprint`` keyword.  The ``footprint``
      keyword can be used to create a non-rectangular cutout image.
  
      Parameters
      ----------
      data : array_like
          The 2D array of the image.
  
      xpos, ypos : float or array-like of float
          The initial ``x`` and ``y`` pixel position(s) of the center
          position.  A cutout image centered on this position be used to
          calculate the centroid.
  
      box_size : int or array-like of int, optional
          The size of the cutout image along each axis.  If ``box_size``
          is a number, then a square cutout of ``box_size`` will be
          created.  If ``box_size`` has two elements, they should be in
          ``(ny, nx)`` order.
  
      footprint : `~numpy.ndarray` of bools, optional
          A 2D boolean array where `True` values describe the local
          footprint region to cutout.  ``footprint`` can be used to create
          a non-rectangular cutout image, in which case the input ``xpos``
          and ``ypos`` represent the center of the minimal bounding box
          for the input ``footprint``.  ``box_size=(n, m)`` is equivalent
          to ``footprint=np.ones((n, m))``.  Either ``box_size`` or
          ``footprint`` must be defined.  If they are both defined, then
          ``footprint`` overrides ``box_size``.
  
      mask : array_like, bool, optional
          A 2D boolean array with the same shape as ``data``, where a
          `True` value indicates the corresponding element of ``data`` is
          masked.
  
      error : array_like, optional
          The 2D array of the 1-sigma errors of the input ``data``.
          ``error`` must have the same shape as ``data``.  ``error`` will
          be used only if supported by the input ``centroid_func``.
  
      centroid_func : callable, optional
          A callable object (e.g. function or class) that is used to
          calculate the centroid of a 2D array.  The ``centroid_func``
          must accept a 2D `~numpy.ndarray`, have a ``mask`` keyword and
          optionally an ``error`` keyword.  The callable object must
          return a tuple of two 1D `~numpy.ndarray`\\s, representing the x
          and y centroids.  The default is
          `~photutils.centroids.centroid_com`.
  
      Returns
      -------
      xcentroid, ycentroid : `~numpy.ndarray`
          The ``x`` and ``y`` pixel position(s) of the centroids.
      """
  
      xpos = np.atleast_1d(xpos)
      ypos = np.atleast_1d(ypos)
      if xpos.ndim != 1:
          raise ValueError('xpos must be a 1D array.')
      if ypos.ndim != 1:
          raise ValueError('ypos must be a 1D array.')
  
      if footprint is None:
          if box_size is None:
              raise ValueError('box_size or footprint must be defined.')
          else:
              box_size = np.atleast_1d(box_size)
              if len(box_size) == 1:
                  box_size = np.repeat(box_size, 2)
              if len(box_size) != 2:
                  raise ValueError('box_size must have 1 or 2 elements.')
  
          footprint = np.ones(box_size, dtype=bool)
      else:
          footprint = np.asanyarray(footprint, dtype=bool)
          if footprint.ndim != 2:
              raise ValueError('footprint must be a 2D array.')
  
      use_error = False
      spec = inspect.getfullargspec(centroid_func)
      if 'mask' not in spec.args:
          raise ValueError('The input "centroid_func" must have a "mask" '
                           'keyword.')
      if 'error' in spec.args:
          use_error = True
  
      xcentroids = []
      ycentroids = []
      for xp, yp in zip(xpos, ypos):
          slices_large, slices_small = overlap_slices(data.shape,
                                                      footprint.shape, (yp, xp))
          data_cutout = data[slices_large]
  
          mask_cutout = None
          if mask is not None:
              mask_cutout = mask[slices_large]
  
          footprint_mask = ~footprint
          # trim footprint mask if partial overlap on the data
          footprint_mask = footprint_mask[slices_small]
  
          if mask_cutout is None:
              mask_cutout = footprint_mask
          else:
              # combine the input mask and footprint mask
              mask_cutout = np.logical_or(mask_cutout, footprint_mask)
  
          if error is not None and use_error:
              error_cutout = error[slices_large]
              xcen, ycen = centroid_func(data_cutout, mask=mask_cutout,
                                         error=error_cutout)
          else:
              xcen, ycen = centroid_func(data_cutout, mask=mask_cutout)
  
          xcentroids.append(xcen + slices_large[1].start)
          ycentroids.append(ycen + slices_large[0].start)
  
      return np.array(xcentroids), np.array(ycentroids)