def _get_geometry(self, station_only):
        """
        Load instrument geometry.

        Parameters
        ----------
        station_only : bool
            If :py:obj:`True`, model stations as single-element antennas.

        Returns
        -------
        :py:class:`~pypeline.phased_array.instrument.InstrumentGeometry`
            ITRS instrument geometry.
        """
        rel_path = pathlib.Path('data', 'phased_array', 'instrument',
                                'MWA.csv')
        abs_path = pkg.resource_filename('pypeline', str(rel_path))

        itrs_geom = (pd.read_csv(abs_path).set_index('STATION_ID'))

        station_id = itrs_geom.index.get_level_values('STATION_ID')
        if station_only:
            itrs_geom.index = (pd.MultiIndex.from_product(
                [station_id, [0]], names=['STATION_ID', 'ANTENNA_ID']))
        else:
            # Generate flat 4x4 antenna grid pointing towards the Noth pole.
            x_lim = y_lim = 1.65
            lY, lX = np.meshgrid(np.linspace(-y_lim, y_lim, 4),
                                 np.linspace(-x_lim, x_lim, 4),
                                 indexing='ij')
            l = np.stack((lX, lY, np.zeros((4, 4))), axis=0)

            # For each station: rotate 4x4 array to lie on the sphere's surface.
            xyz_station = itrs_geom.loc[:, ['X', 'Y', 'Z']].values
            df_stations = []
            for st_id, st_cog in zip(station_id, xyz_station):
                _, st_colat, st_lon = sph.cart2pol(*st_cog)
                st_cog_unit = np.array(sph.pol2cart(1, st_colat, st_lon))

                R_1 = pylinalg.rot([0, 0, 1], st_lon)
                R_2 = pylinalg.rot(axis=np.cross([0, 0, 1], st_cog_unit),
                                   angle=st_colat)
                R = R_2 @ R_1

                st_layout = np.reshape(
                    st_cog.reshape(3, 1, 1) + np.tensordot(R, l, axes=1),
                    (3, -1))
                idx = (pd.MultiIndex.from_product(
                    [[st_id], range(16)], names=['STATION_ID', 'ANTENNA_ID']))
                df_stations += [
                    pd.DataFrame(data=st_layout.T,
                                 index=idx,
                                 columns=['X', 'Y', 'Z'])
                ]
            itrs_geom = pd.concat(df_stations)

        XYZ = _as_InstrumentGeometry(itrs_geom)
        return XYZ
Beispiel #2
0
def uniform_grid(direction, FoV, size):
    """
    Uniform pixel grid.

    Parameters
    ----------
    direction : :py:class:`~numpy.ndarray`
        (3,) vector around which the grid is centered.
    FoV : float
        Span of the grid centered at `direction` [rad].

        Due to the definition of this grid, `FoV` should be at most 90 [deg].
    size : array-like(int)
        (N_height, N_width)

    Returns
    -------
    :py:class:`~numpy.ndarray`
        (3, N_height, N_width) pixel grid.
    """
    direction = np.array(direction, dtype=float)
    direction /= linalg.norm(direction)

    if not (0 < FoV <= np.pi / 2):
        raise ValueError('Parameter[FoV] must lie in (0, 90] degrees.')

    size = np.array(size, copy=False)
    if np.any(size <= 0):
        raise ValueError('Parameter[size] must contain positive entries.')

    N_height, N_width = size
    lim = np.sin(FoV / 2)
    Y, X = np.meshgrid(np.linspace(-lim, lim, N_height),
                       np.linspace(-lim, lim, N_width),
                       indexing='ij')
    Z = 1 - X**2 - Y**2
    X[Z < 0], Y[Z < 0], Z[Z < 0] = 0, 0, 0
    Z = np.sqrt(Z)
    XYZ = np.stack([X, Y, Z], axis=0)

    # Center grid at 'direction'
    _, dir_colat, dir_lon = sph.cart2pol(*direction)
    R1 = pylinalg.rot(axis=np.r_[0, 0, 1], angle=dir_lon)
    R2_axis = np.cross([0, 0, 1], direction)
    if np.allclose(R2_axis, 0):
        # R2_axis is in span(E_z), so we must manually set R2.
        R2 = np.eye(3)
        if direction[2] < 0:
            R2[2, 2] = -1
    else:
        R2 = pylinalg.rot(axis=R2_axis, angle=dir_colat)
    R = R2 @ R1

    XYZ = np.tensordot(R, XYZ, axes=1)
    return XYZ
Beispiel #3
0
def spherical_grid(direction, FoV, size):
    """
    Spherical pixel grid.

    Parameters
    ----------
    direction : :py:class:`~numpy.ndarray`
        (3,) vector around which the grid is centered.
    FoV : float
        Span of the grid centered at `direction` [rad].
    size : array-like(int)
        (N_height, N_width)

        The grid will consist of `N_height` concentric circles around `direction`, each containing `N_width` pixels.

    Returns
    -------
    :py:class:`~numpy.ndarray`
        (3, N_height, N_width) pixel grid.
    """
    direction = np.array(direction, dtype=float)
    direction /= linalg.norm(direction)

    if not (0 < FoV <= 2 * np.pi):
        raise ValueError('Parameter[FoV] must lie in (0, 360] degrees.')

    size = np.array(size, copy=False)
    if np.any(size <= 0):
        raise ValueError('Parameter[size] must contain positive entries.')

    N_height, N_width = size
    colat, lon = np.meshgrid(np.linspace(0, FoV / 2, N_height),
                             np.linspace(0, 2 * np.pi, N_width),
                             indexing='ij')
    XYZ = np.stack(sph.pol2cart(1, colat, lon), axis=0)

    # Center grid at 'direction'
    _, dir_colat, _ = sph.cart2pol(*direction)
    R_axis = np.cross([0, 0, 1], direction)
    if np.allclose(R_axis, 0):
        # R_axis is in span(E_z), so we must manually set R
        R = np.eye(3)
        if direction[2] < 0:
            R[2, 2] = -1
    else:
        R = pylinalg.rot(axis=R_axis, angle=dir_colat)

    XYZ = np.tensordot(R, XYZ, axes=1)
    return XYZ
Beispiel #4
0
def ea_grid(direction, FoV, size):
    """
    Equal-Angle pixel grid.

    Parameters
    ----------
    direction : :py:class:`~numpy.ndarray`
        (3,) vector around which the grid is centered.
    FoV : float
        Span of the grid centered at `direction` [rad].
    size : array-like(int)
        (N_height, N_width)

    Returns
    -------
    colat : :py:class:`~numpy.ndarray`
        (N_height, 1) polar angles [rad].

    lon : :py:class:`~numpy.ndarray`
        (1, N_width) azimuthal angles [rad].

    Notes
    -----
    Due to the way the grid is constructed, `direction` cannot point to the North/South pole.
    """
    direction = np.array(direction, dtype=float)
    direction /= linalg.norm(direction)

    if np.allclose(np.cross([0, 0, 1], direction), 0):
        raise ValueError('Generating Equal-Angle grids centered at poles are '
                         'not supported.')

    if not (0 < FoV < 2 * np.pi):
        raise ValueError('Parameter[FoV] must lie in (0, 360) degrees.')

    size = np.array(size, copy=False)
    if np.any(size <= 0):
        raise ValueError('Parameter[size] must contain positive entries.')

    _, dir_colat, dir_lon = sph.cart2pol(*direction)
    lim_lon = dir_lon + (FoV / 2) * np.r_[-1, 1]
    lim_colat = dir_colat + (FoV / 2) * np.r_[-1, 1]
    lim_colat = (max(np.radians(0.5),
                     lim_colat[0]), min(lim_colat[1], np.radians(179.5)))

    N_height, N_width = size
    colat = np.linspace(*lim_colat, num=N_height).reshape(-1, 1)
    lon = np.linspace(*lim_lon, num=N_width).reshape(1, -1)
    return colat, lon
Beispiel #5
0
    def _PrimaryHDU(self):
        """
        Generate primary Header Descriptor Unit (HDU) for FITS export.

        Returns
        -------
        hdu : :py:class:`~astropy.io.fits.PrimaryHDU`
        """
        metadata = dict(IMG_TYPE=(self.__class__.__name__,
                                  'SphericalImage subclass'), )

        # grid: stored as angles to reduce file size.
        _, colat, lon = sph.cart2pol(*self.grid)
        coordinates = np.stack([np.degrees(colat), np.degrees(lon)], axis=0)

        hdu = fits.PrimaryHDU(data=coordinates)
        for k, v in metadata.items():
            hdu.header[k] = v
        return hdu
Beispiel #6
0
def ea_harmonic_grid(direction, FoV, N):
    """
    Region-limited Equal-Angle pixel grid of order `N`.

    Parameters
    ----------
    direction : :py:class:`~numpy.ndarray`
        (3,) vector around which the grid is centered.
    FoV : float
        Span of the grid centered at `direction` [rad].
    N : int
        Order of the grid, i.e. there will be :math:`4 (N + 1)^{2}` points on the whole sphere.

    Returns
    -------
    q : :py:class:`~numpy.ndarray`
        (N_height,) polar indices.

    l : :py:class:`~numpy.ndarray`
        (N_width,) azimuthal indices.

    colat : :py:class:`~numpy.ndarray`
        (N_height, 1) polar angles [rad].

    lon : :py:class:`~numpy.ndarray`
        (1, N_width) azimuthal angles [rad].

    Notes
    -----
    Due to the way Equal-Angle grids are specified, `direction` cannot point to the North/South pole.

    See Also
    --------
    :py:class:`~pypeline.util.math.sphere.EqualAngleInterpolator`
    """
    direction = np.array(direction, dtype=float)
    direction /= linalg.norm(direction)

    if np.allclose(np.cross([0, 0, 1], direction), 0):
        raise ValueError('Generating Equal-Angle grids centered at poles are '
                         'not supported.')

    if not (0 < FoV < 2 * np.pi):
        raise ValueError('Parameter[FoV] must lie in (0, 360) degrees.')

    if N <= 0:
        raise ValueError('Parameter[N] must be non-negative.')

    _, dir_colat, dir_lon = sph.cart2pol(*direction)
    lim_lon = dir_lon + (FoV / 2) * np.r_[-1, 1]
    lim_lon = (coord.Angle(lim_lon * u.rad).wrap_at(360 * u.deg).to_value(
        u.rad))
    lim_colat = dir_colat + (FoV / 2) * np.r_[-1, 1]
    lim_colat = (max(np.radians(0.5),
                     lim_colat[0]), min(lim_colat[1], np.radians(179.5)))

    colat_full, lon_full = sph.ea_sample(N)
    q_full = np.arange(colat_full.size).reshape(-1, 1)
    l_full = np.arange(lon_full.size).reshape(1, -1)

    q_mask = ((lim_colat[0] <= colat_full) & (colat_full <= lim_colat[1]))
    if lim_lon[0] < lim_lon[1]:
        l_mask = ((lim_lon[0] <= lon_full) & (lon_full <= lim_lon[1]))
    else:
        l_mask = ((lim_lon[0] <= lon_full) | (lon_full <= lim_lon[1]))

    q = q_full[q_mask]
    l = l_full[l_mask]
    colat = colat_full[q_mask].reshape(-1, 1)
    lon = lon_full[l_mask].reshape(1, -1)
    return q, l, colat, lon