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
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)
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)
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
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)
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
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:])
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
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)
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
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)