Example #1
0
def test_fortran_order(mock_data):
    np.random.seed(42)
    _, data, _ = mock_data
    sigma = np.random.uniform(1e-2, 5e-2, data.size).reshape(data.shape)
    x1, dx1, y1, dy1 = bm.quadratic(data, sigma)

    data_f = np.array(data, copy=True, order="F")
    x2, dx2, y2, dy2 = bm.quadratic(data_f, sigma)
    assert np.allclose(x1, x2)
    assert np.allclose(dx1, dx2)
    assert np.allclose(y1, y2)
    assert np.allclose(dy1, dy2)

    sigma_f = np.array(sigma, copy=True, order="F")
    x2, dx2, y2, dy2 = bm.quadratic(data_f, sigma_f)
    assert np.allclose(x1, x2)
    assert np.allclose(dx1, dx2)
    assert np.allclose(y1, y2)
    assert np.allclose(dy1, dy2)

    old_axis = 0
    for axis in [1, 2]:
        data_f = np.moveaxis(data_f, old_axis, axis)
        sigma_f = np.moveaxis(sigma_f, old_axis, axis)
        x2, dx2, y2, dy2 = bm.quadratic(data_f, sigma_f, axis=axis)
        assert np.allclose(x1, x2)
        assert np.allclose(dx1, dx2)
        assert np.allclose(y1, y2)
        assert np.allclose(dy1, dy2)
        old_axis = axis
Example #2
0
def test_constant_uncertainties(mock_data):
    velax, data, vproj = mock_data
    sig1 = 1.0
    x1, dx1, y1, dy1 = bm.quadratic(data, sig1)
    sig2 = sig1 + np.zeros_like(data)
    x2, dx2, y2, dy2 = bm.quadratic(data, sig2)
    assert np.allclose(x1, x2)
    assert np.allclose(dx1, dx2)
    assert np.allclose(y1, y2)
    assert np.allclose(dy1, dy2)
Example #3
0
def test_units(mock_data):
    np.random.seed(42)
    velax, data, _ = mock_data
    sigma = np.random.uniform(1e-2, 5e-2, data.size).reshape(data.shape)
    x1, dx1, _, _ = bm.quadratic(data, sigma)

    x0 = velax[0]
    dx = velax[1] - velax[0]
    x2, dx2, _, _ = bm.quadratic(data, sigma, x0=x0, dx=dx)

    assert np.allclose(x0 + x1 * dx, x2)
    assert np.allclose(dx1 * dx, dx2)
Example #4
0
def test_uncertainty_axis(mock_data):
    np.random.seed(42)
    _, data, _ = mock_data
    sigma = np.random.uniform(1e-2, 5e-2, data.size).reshape(data.shape)
    x1, dx1, y1, dy1 = bm.quadratic(data, sigma)

    old_axis = 0
    for axis in [1, 2]:
        data = np.moveaxis(data, old_axis, axis)
        sigma = np.moveaxis(sigma, old_axis, axis)
        x2, dx2, y2, dy2 = bm.quadratic(data, sigma, axis=axis)
        assert np.allclose(x1, x2)
        assert np.allclose(dx1, dx2)
        assert np.allclose(y1, y2)
        assert np.allclose(dy1, dy2)
        old_axis = axis
Example #5
0
def test_isclose(mock_data):
    velax, data, vproj = mock_data
    x0 = velax[0]
    dx = velax[1] - velax[0]
    x = bm.quadratic(data, x0=x0, dx=dx)[0]

    a1, b1 = vproj.shape[0] // 3, 2 * vproj.shape[0] // 3
    a2, b2 = vproj.shape[1] // 3, 2 * vproj.shape[1] // 3

    assert np.all(np.abs(x[a1:b1, a2:b2] - vproj[a1:b1, a2:b2]) < dx)
Example #6
0
def test_shapes(mock_data):
    velax, data, vproj = mock_data

    # No uncertainties
    x, dx, y, dy = bm.quadratic(data)
    assert x.shape == vproj.shape
    assert dx is None
    assert y.shape == vproj.shape
    assert dy is None

    # With scalar uncertainty
    sigma = 1.0
    x, dx, y, dy = bm.quadratic(data, sigma)
    assert x.shape == vproj.shape
    assert dx.shape == vproj.shape
    assert y.shape == vproj.shape
    assert dy.shape == vproj.shape

    # With full uncertainties
    sigma = np.ones_like(data)
    x, dx, y, dy = bm.quadratic(data, sigma)
    assert x.shape == vproj.shape
    assert dx.shape == vproj.shape
    assert y.shape == vproj.shape
    assert dy.shape == vproj.shape

    # Make sure that everything works with different axes
    old_axis = 0
    for axis in [1, 2]:
        data = np.moveaxis(data, old_axis, axis)
        sigma = np.moveaxis(sigma, old_axis, axis)
        x, dx, y, dy = bm.quadratic(data, sigma, axis=axis)
        assert x.shape == vproj.shape
        assert dx.shape == vproj.shape
        assert y.shape == vproj.shape
        assert dy.shape == vproj.shape
        old_axis = axis
Example #7
0
def test_shape_error(mock_data):
    velax, data, vproj = mock_data
    sigma = np.random.rand(*data.shape)

    with pytest.raises(ValueError):
        bm.quadratic(data, sigma[1:])
    with pytest.raises(ValueError):
        bm.quadratic(data, sigma[:, 1:])
    with pytest.raises(ValueError):
        bm.quadratic(data, sigma[:, :, 1:])
Example #8
0
    def _line_centroids(self, method='max', spectra=None, velax=None):
        """
        Return the velocities of the peak pixels.

        Args:
            method (str): Method used to determine the line centroid. Must be
                in ['max', 'quadratic', 'gaussian']. The former returns the
                pixel of maximum value, 'quadratic' fits a quadratic to the
                pixel of maximum value and its two neighbouring pixels (see
                Teague & Foreman-Mackey 2018 for details) and 'gaussian' fits a
                Gaussian profile to the line.
            spectra (Optional[ndarray]): The array of spectra to calculate the
                line centroids on. If nothing is given, use self.spectra. Must
                be on the same velocity axis.
            velax (Optional[ndarray]): Must provide the velocity axis if
                different from the attached array.

        Returns:
            vmax (ndarray): Line centroids.
        """
        method = method.lower()
        spectra = self.spectra if spectra is None else spectra
        velax = self.velax if velax is None else velax
        if method == 'max':
            vmax = np.take(velax, np.argmax(spectra, axis=1))
        elif method == 'quadratic':
            try:
                from bettermoments.methods import quadratic
            except ImportError:
                raise ImportError("Please install 'bettermoments'.")
            vmax = [
                quadratic(spectrum, x0=velax[0], dx=np.diff(velax)[0])[0]
                for spectrum in spectra
            ]
            vmax = np.array(vmax)
        elif method == 'gaussian':
            vmax = [
                annulus._get_gaussian_center(velax, spectrum)
                for spectrum in spectra
            ]
            vmax = np.array(vmax)
        else:
            raise ValueError("method is not 'max', 'gaussian' or 'quadratic'.")
        return vmax
Example #9
0
def collapse_quadratic(velax, data, linewidth=None, rms=None, N=5, axis=0):
    """
    Collapse the cube using the quadratic method presented in `Teague &
    Foreman-Mackey (2018)`_. Will return the line center, ``v0``, and the
    uncertainty on this, ``dv0``, as well as the line peak, ``Fnu``, and the
    uncertainty on that, ``dFnu``. This provides the sub-channel precision of
    :func:`bettermoments.collapse_cube.collapse_first` with the robustness to
    noise from :func:`bettermoments.collapse_cube.collapse_ninth`.

    .. _Teague & Foreman-Mackey (2018): https://iopscience.iop.org/article/10.3847/2515-5172/aae265

    Args:
        velax (ndarray): Velocity axis of the cube.
        data (ndarray): Flux density or brightness temperature array. Assumes
            that the zeroth axis is the velocity axis.
        linewidth (Optional[float]): Doppler width of the line. If specified
            will be used to smooth the data prior to the quadratic fit.
        rms (Optional[float]): Noise per pixel. If ``None`` is specified,
            this will be calculated from the first and last ``N`` channels.
        N (Optional[int]): Number of channels to use in the estimation of the
            noise.
        axis (Optional[int]): Spectral axis to collapse the cube along, by
            default ``axis=0``.

    Returns:
        ``v0`` (`ndarray`), ``dv0`` (`ndarray`), ``Fnu`` (`ndarray`), ``dFnu`` (`ndarray`):
            ``v0``, the line center in the same units as ``velax`` with ``dv0``
            as the uncertainty on ``v0`` in the same units as ``velax``.
            ``Fnu`` is the line peak in the same units as the
            ``data`` with associated uncertainties, ``dFnu``.
    """
    from bettermoments.methods import quadratic
    rms, chan = _verify_data(data, velax, rms=rms, N=N, axis=axis)
    if linewidth > 0.0:
        linewidth = abs(linewidth / chan / np.sqrt(2.))
    else:
        linewidth = None
    return quadratic(data,
                     x0=velax[0],
                     dx=chan,
                     linewidth=linewidth,
                     uncertainty=np.ones(data.shape) * rms,
                     axis=axis)
Example #10
0
def collapse_width(velax,
                   data,
                   linewidth=0.0,
                   rms=None,
                   N=5,
                   threshold=None,
                   mask_path=None,
                   axis=0):
    r"""
    Returns an effective width, a rescaled ratio of the integrated intensity
    and the line peak. For a Gaussian line profile this would be the Doppler
    width as the total intensity is given by,

    .. math::
        M_0 = \sum_{i}^N I_i \, \Delta v_{{\rm chan},\,i}

    where :math:`\Delta v_i` and :math:`I_i` are the chanenl width and flux
    density at the :math:`i^{\rm th}` channel. If the line profile is Gaussian,
    then equally

    .. math::
        M_0 = \sqrt{\pi} \times F_{\nu} \times \Delta V

    where :math:`F_{\nu}` is the peak value of the line and :math:`\Delta V` is
    the Doppler width of the line. As :math:`M_0` and :math:`F_{\nu}` are
    readily calculated using :func:`bettermoments.collapse_cube.collapse_zeroth`
    and :func:`bettermoments.collapse_cube.collapse_quadratic`, respectively,
    :math:`\Delta V` can calculated through :math:`\Delta V = M_{0} \, / \,
    (\sqrt{\pi} \, F_{\nu})`. This should be more robust against noise than
    second moment maps.

    Args:
        velax (ndarray): Velocity axis of the cube.
        data (ndarray): Flux densities or brightness temperature array. Assumes
            that the first axis is the velocity axis.
        linewidth (Optional[float]): Doppler width of the line. If specified
            will be used to smooth the data prior to the quadratic fit.
        rms (Optional[float]): Noise per pixel. If ``None`` is specified, this
            will be calculated from the first and last ``N`` channels.
        N (Optional[int]): Number of channels to use in the estimation of the
            noise.
        threshold (Optional[float]): Clip any pixels below this RMS value.
        mask_path (Optional[str]): Path to a file containing a boolean mask,
            either stored as `.FITS` or `.npy` file.
        axis (Optional[int]): Spectral axis to collapse the cube along.

    Returns:
        ``dV`` (`ndarray`), ``ddV`` (`ndarray`):
            The effective velocity dispersion, ``dV`` and ``ddV``, the
            associated uncertainty.
    """
    from bettermoments.methods import integrated_intensity, quadratic
    rms, chan = _verify_data(data, velax, rms=rms, N=N, axis=axis)
    I0, dI0 = integrated_intensity(data=data,
                                   dx=abs(chan),
                                   threshold=threshold,
                                   rms=rms,
                                   mask_path=mask_path,
                                   axis=axis)
    if linewidth > 0.0:
        linewidth = abs(linewidth / chan / np.sqrt(2.))
    else:
        linewidth = None
    _, _, Fnu, dFnu = quadratic(data,
                                x0=velax[0],
                                dx=chan,
                                uncertainty=np.ones(data.shape) * rms,
                                linewidth=linewidth,
                                axis=axis)
    dV = I0 / Fnu / np.sqrt(np.pi)
    ddV = dV * np.hypot(dFnu / Fnu, dI0 / I0)
    return dV, ddV
Example #11
0
def test_compare_ninth(mock_data):
    _, data, _ = mock_data
    x9 = np.argmax(data, axis=0)
    x = bm.quadratic(data)[0]
    assert np.all(np.abs(x - x9) <= 0.5)