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,
            )
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.