docs: def moment(self, axis, channel=0, moment=1, *, resultant=None):
          """Take the nth moment the dataset along one axis, adding lower rank channels.
  
          New channels have names ``<channel name>_<axis name>_moment_<moment num>``.
  
          Moment 0 is the integral of the slice.
          Moment 1 is the weighted average or "Center of Mass", normalized by the integral
          Moment 2 is the variance, the central moment about the center of mass,
          normalized by the integral
          Moments 3+ are central moments about the center of mass, normalized by the integral
          and by the standard deviation to the power of the moment.
  
          Moments, especially higher order moments, are susceptible to noise and baseline.
          It is recommended when used with real data to use :meth:`WrightTools.data.Channel.clip`
          in conjunction with moments to reduce effects of noise.
  
          Parameters
          ----------
          axis : int or str
              The axis to take the moment along.
              If given as an integer, the axis with that index is used.
              If given as a string, the axis with that name is used.
              The axis must exist, and be a 1D array-aligned axis.
              (i.e. have a shape with a single value which is not ``1``)
              The collapsed axis must be monotonic to produce correct results.
              The axis to collapse along is inferred from the shape of the axis.
          channel : int or str
              The channel to take the moment.
              If given as an integer, the channel with that index is used.
              If given as a string, the channel with that name is used.
              The channel must have values along the axis
              (i.e. its shape must not be ``1`` in the dimension for which the axis is not ``1``)
              Default is 0, the first channel.
          moment : int or tuple of int
              The moments to take.
              One channel will be created for each number given.
              Default is 1, the center of mass.
          resultant : tuple of int
              The resultant shape after the moment operation.
              By default, it is intuited by the axis along which the moment is being taken.
              This default only works if that axis is 1D, so resultant is required if a 
              multidimensional axis is passed as the first argument.
              The requirement of monotonicity applies on a per pixel basis.
  
          See Also
          --------
          collapse
              Reduce dimensionality by some mathematical operation
          clip
              Set values above/below a threshold to a particular value
          WrightTools.kit.joint_shape
              Useful for setting `resultant` kwarg based off of axes not collapsed.
          """
          # get axis index --------------------------------------------------------------------------
          axis_index = None
          if resultant is not None:
              for i, (s, r) in enumerate(zip(self.shape, resultant)):
                  if s != r and r == 1 and axis_index is None:
                      axis_index = i
                  elif s == r:
                      continue
                  else:
                      raise wt_exceptions.ValueError(
                          f"Invalid resultant shape '{resultant}' for shape {self.shape}. "
                          + "Consider using `wt.kit.joint_shape` to join non-collapsed axes."
                      )
  
          index = wt_kit.get_index(self.axis_names, axis)
          if axis_index is None:
              axes = [i for i in range(self.ndim) if self.axes[index].shape[i] > 1]
              if len(axes) > 1:
                  raise wt_exceptions.MultidimensionalAxisError(axis, "moment")
              elif len(axes) == 0:
                  raise wt_exceptions.ValueError(
                      "Axis {} is a single point, cannot compute moment".format(axis)
                  )
              axis_index = axes[0]
  
          warnings.warn("moment", category=wt_exceptions.EntireDatasetInMemoryWarning)
  
          channel_index = wt_kit.get_index(self.channel_names, channel)
          channel = self.channel_names[channel_index]
  
          if self[channel].shape[axis_index] == 1:
              raise wt_exceptions.ValueError(
                  "Channel '{}' has a single point along Axis '{}', cannot compute moment".format(
                      channel, axis
                  )
              )
  
          new_shape = list(self[channel].shape)
          new_shape[axis_index] = 1
  
          channel = self[channel]
          axis_inp = axis
          axis = self.axes[index]
          x = axis[:]
          if np.any(np.isnan(x)):
              raise wt_exceptions.ValueError("Axis '{}' includes NaN".format(axis_inp))
          y = np.nan_to_num(channel[:])
  
          try:
              moments = tuple(moment)
          except TypeError:
              moments = (moment,)
  
          multiplier = 1
          if 0 in moments:
              # May be possible to optimize, probably doesn't need the sum
              # only matters for integral, all others normalize by integral
              multiplier = np.sign(
                  np.sum(np.diff(x, axis=axis_index), axis=axis_index, keepdims=True)
              )
  
          for moment in moments:
              about = 0
              norm = 1
              if moment > 0:
                  norm = np.trapz(y, x, axis=axis_index)
                  norm = np.array(norm)
                  norm.shape = new_shape
              if moment > 1:
                  about = np.trapz(x * y, x, axis=axis_index)
                  about = np.array(about)
                  about.shape = new_shape
                  about /= norm
              if moment > 2:
                  sigma = np.trapz((x - about) ** 2 * y, x, axis=axis_index)
                  sigma = np.array(sigma)
                  sigma.shape = new_shape
                  sigma /= norm
                  sigma **= 0.5
                  norm *= sigma ** moment
  
              values = np.trapz((x - about) ** moment * y, x, axis=axis_index)
              values = np.array(values)
              values.shape = new_shape
              values /= norm
              if moment == 0:
                  values *= multiplier
              self.create_channel(
                  "{}_{}_{}_{}".format(channel.natural_name, axis_inp, "moment", moment),
                  values=values,
              )