Exemple #1
0
def test_l93_to_etrs():
    l93 = np.array([[6.39113316e+05, 6.69766347e+06, 1.81735000e+02],
                    [6.39094578e+05, 6.69764471e+06, 1.81750000e+02]])
    etrs = l93_to_etrs(positions=l93)
    assert etrs.shape == (2, 3)
    assert etrs[1, 2] == pytest.approx(4670332.180, 1e-3)

    with pytest.raises(ValueError):
        l93_to_etrs(np.array([6.39113316e+05, 6.69766347e+06, 1.81735000e+02]))
Exemple #2
0
def test_geo_to_etrs():
    nenufar_etrs = geo_to_etrs(location=nenufar_position)
    assert nenufar_etrs.shape == (1, 3)
    assert nenufar_etrs[0, 1] == pytest.approx(165533.668, 1e-3)

    positions = EarthLocation(lat=[30, 40] * u.deg,
                              lon=[0, 10] * u.deg,
                              height=[100, 200] * u.m)
    arrays_etrs = geo_to_etrs(location=positions)
    assert arrays_etrs.shape == (2, 3)
    assert arrays_etrs[1, 2] == pytest.approx(4078114.130, 1e-3)

    # l93_to_etrs and geo_to_etrs should give the same results (MA 00)
    etrs_from_l93 = l93_to_etrs(positions=np.array(
        [6.39113316e+05, 6.69766347e+06, 1.81735000e+02]).reshape(1, 3))
    etrs_from_geo = geo_to_etrs(
        EarthLocation(lat=47.37650985 * u.deg,
                      lon=2.19307873 * u.deg,
                      height=181.7350 * u.m))
    assert etrs_from_l93[0, 0] == pytest.approx(etrs_from_geo[0, 0], 1e-3)
    assert etrs_from_l93[0, 1] == pytest.approx(etrs_from_geo[0, 1], 1e-3)
    assert etrs_from_l93[0, 2] == pytest.approx(etrs_from_geo[0, 2], 1e-3)
Exemple #3
0
    def make_nearfield(self,
            radius: u.Quantity = 400*u.m,
            npix: int = 64,
            sources: list = []
        ):
        r""" Computes the Near-field image from the cross-correlation
            statistics data :math:`\mathcal{V}`.

            The distances between each Mini-Array :math:`{\rm MA}_i`
            and the ground positions :math:`Delta` is:

            .. math::
                d_{\rm{MA}_i} (x, y) = \sqrt{
                    ({\rm MA}_{i, x} - \Delta_x)^2 + ({\rm MA}_{i, y} - \Delta_y)^2 + \left( {\rm MA}_{i, z} - \sum_j \frac{{\rm MA}_{j, z}}{n_{\rm MA}} - 1 \right)^2
                } 

            Then, the near-field image :math:`n_f` can be retrieved
            as follows (:math:`k` and :math:`l` being two distinct
            Mini-Arrays):

            .. math::
                n_f (x, y) = \sum_{k, l} \left| \sum_{\nu} \langle \mathcal{V}_{\nu, k, l}(t) \rangle_t e^{2 \pi i \left( d_{{\rm MA}_k} - d_{{\rm MA}_l} \right) (x, y) \frac{\nu}{c}} \right|

            .. note::
                To simulate astrophysical source of brightness :math:`\mathcal{B}`
                footprint on the near-field, its visibility per baseline
                of Mini-Arrays :math:`k` and :math:`l` are computed as:

                .. math::
                    \mathcal{V}_{{\rm simu}, k, l} = \mathcal{B} e^{2 \pi i \left( \mathbf{r}_k - \mathbf{r}_l \right) \cdot \mathbf{u} \frac{\nu}{c}}

                with :math:`\mathbf{r}` the ENU position of the Mini-Arrays,
                :math:`\mathbf{u} = \left( \cos(\theta) \sin(\phi), \cos(\theta) \cos(\phi), sin(\theta) \right)`
                the ground projection vector (in East-North-Up coordinates),
                (:math:`\phi` and :math:`\theta` are the source horizontal
                coordinates azimuth and elevation respectively).
    

            :param radius:
                Radius of the ground image. Default is ``400m``.
            :type radius:
                :class:`~astropy.units.Quantity`
            :param npix:
                Number of pixels of the image size. Default is ``64``.
            :type npix:
                `int`
            :param sources:
                List of source names for which their near-field footprint
                may be computed. Only sources above 10 deg elevation
                will be considered.
            :type sources:
                `list`

            :returns:
                Tuple of near-field image and a dictionnary 
                containing all source footprints. 
            :rtype:
                `tuple`(:class:`~numpy.ndarray`, `dict`)

            :Example:

                from nenupy.io.xst import XST
                xst = XST("xst_file.fits")
                nearfield, src_dict = xst.make_nearfield(sources=["Cas A", "Sun"])

            .. versionadded:: 1.1.0

        """

        def compute_nearfield_imprint(visibilities, phase):
            # Phase and average in frequency
            nearfield = np.mean(
                visibilities[..., None, None] * phase,
                axis=0
            )
            # Average in baselines
            nearfield = np.nanmean(np.abs(nearfield), axis=0)
            with ProgressBar() if log.getEffectiveLevel() <= logging.INFO else DummyCtMgr():
                return nearfield.compute()

        # Mini-Array positions in ENU coordinates
        nenufar = NenuFAR()[self.mini_arrays]
        ma_etrs = l93_to_etrs(nenufar.antenna_positions)
        ma_enu = etrs_to_enu(ma_etrs)

        # Treat baselines
        ma1, ma2 = np.tril_indices(self.mini_arrays.size, 0)
        cross_mask = ma1 != ma2

        # Mean time of observation
        obs_time = self.time[0] + (self.time[-1] - self.time[0])/2.

        # Delays at the ground
        radius_m = radius.to(u.m).value
        ground_granularity = np.linspace(-radius_m, radius_m, npix)
        posx, posy = np.meshgrid(ground_granularity, ground_granularity)
        posz = np.ones_like(posx) * (np.average(ma_enu[:, 2]) + 1)
        ground_grid = np.stack((posx, posy, posz), axis=2)
        ground_distances = np.sqrt(
            np.sum(
                (ma_enu[:, None, None, :] - ground_grid[None])**2,
                axis=-1
            )
        )
        grid_delays = ground_distances[ma1] - ground_distances[ma2] # (nvis, npix, npix)
        n_bsl = ma1[cross_mask].size
        grid_delays = da.from_array(
            grid_delays[cross_mask],
            chunks=(np.floor(n_bsl/os.cpu_count()), npix, npix)
        )
    
        # Mean in time the visibilities
        vis = np.mean(
            self.value,
            axis=0
        )[..., cross_mask] # (nfreqs, nvis)
        vis = da.from_array(
            vis,
            chunks=(1, np.floor(n_bsl/os.cpu_count()))#(self.frequency.size, np.floor(n_bsl/os.cpu_count()))
        )

        # Make the nearfield image
        log.info(
            f"Computing nearfield (time: {self.time.size}, frequency: {self.frequency.size}, baselines: {vis.shape[1]}, pixels: {posx.size})... "
        )
        wvl = wavelength(self.frequency).to(u.m).value
        phase = np.exp(2.j * np.pi * (grid_delays[None, ...]/wvl[:, None, None, None]))
        log.debug("Computing the phase term...")
        with ProgressBar() if log.getEffectiveLevel() <= logging.INFO else DummyCtMgr():
            phase = phase.compute()
        log.debug("Computing the nearf-field...")
        nearfield = compute_nearfield_imprint(vis, phase)

        # Compute nearfield imprints for other sources
        simu_sources = {}
        for src_name in sources:

            # Check that the source is visible
            if src_name.lower() in ["sun", "moon", "venus", "mars", "jupiter", "saturn", "uranus", "neptune"]:
                src = SolarSystemTarget.from_name(name=src_name, time=obs_time)
            else:
                src = FixedTarget.from_name(name=src_name, time=obs_time)
            altaz = src.horizontal_coordinates#[0]
            if altaz.alt.deg <= 10:
                log.debug(f"{src_name}'s elevation {altaz[0].alt.deg}<=10deg, not considered for nearfield imprint.")
                continue

            # Projection from AltAz to ENU vector
            az_rad = altaz.az.rad
            el_rad = altaz.alt.rad
            cos_az = np.cos(az_rad)
            sin_az = np.sin(az_rad)
            cos_el = np.cos(el_rad)
            sin_el = np.sin(el_rad)
            to_enu = np.array(
                [cos_el*sin_az, cos_el*cos_az, sin_el]
            )
            # src_delays = np.matmul(
            #     ma_enu[ma1] - ma_enu[ma2],
            #     to_enu
            # )
            # src_delays = da.from_array(
            #     src_delays[cross_mask, :],
            #     chunks=((np.floor(n_bsl/os.cpu_count()), npix, npix), 1)
            # )
            
            ma1_enu = da.from_array(
                ma_enu[ma1[cross_mask]],
                chunks=np.floor(n_bsl/os.cpu_count())
            )
            ma2_enu = da.from_array(
                ma_enu[ma2[cross_mask]],
                chunks=np.floor(n_bsl/os.cpu_count())
            )
            src_delays = np.matmul(
                ma1_enu - ma2_enu,
                to_enu
            )

            # Simulate visibilities
            src_vis = np.exp(2.j * np.pi * (src_delays/wvl))
            src_vis = np.swapaxes(src_vis, 1, 0)
            log.debug(f"Computing the nearf-field imprint of {src_name}...")
            simu_sources[src_name] = compute_nearfield_imprint(src_vis, phase)

        return nearfield, simu_sources
Exemple #4
0
    def save_png(self, figname: str = "", **kwargs):
        """ """
        radius = self.radius.to(u.m).value
        colormap = kwargs.get("cmap", "YlGnBu_r")

        # Mini-Array positions in ENU coordinates
        nenufar = NenuFAR()[self.mini_arrays]
        ma_etrs = l93_to_etrs(nenufar.antenna_positions)
        ma_enu = etrs_to_enu(ma_etrs)

        # Plot the nearfield
        fig, ax = plt.subplots(figsize=kwargs.get("figsize", (10, 10)))
        nf_image_db = 10*np.log10(self.nearfield)
        ax.imshow(
            np.flipud(nf_image_db), # This needs to be understood...
            cmap=colormap,
            extent=[-radius, radius, -radius, radius],
            zorder=0,
            vmin=kwargs.get("vmin", np.min(nf_image_db)),
            vmax=kwargs.get("vmax", np.max(nf_image_db))
        )

        # Colorbar
        cax = inset_axes(ax,
           width="5%",
           height="100%",
           loc="lower left",
           bbox_to_anchor=(1.05, 0., 1, 1),
           bbox_transform=ax.transAxes,
           borderpad=0,
           )
        cb = ColorbarBase(
            cax,
            cmap=get_cmap(name=colormap),
            orientation="vertical",
            norm=Normalize(
                vmin=kwargs.get("vmin", np.min(nf_image_db)),
                vmax=kwargs.get("vmax", np.max(nf_image_db))
            ),
            ticks=LinearLocator(),
            format='%.2f'
        )
        cb.solids.set_edgecolor("face")
        cb.set_label(f"dB (Stokes {self.stokes})")
    
        # Show the contour of the simulated source imprints
        ground_granularity = np.linspace(-radius, radius, self.npix)
        posx, posy = np.meshgrid(ground_granularity, ground_granularity)
        dist = np.sqrt(posx**2 + posy**2)
        border_min = 0.1*self.npix
        border_max = self.npix - 0.1*self.npix
        for src in self.source_imprints.keys():
            # Normalize the imprint
            imprint = self.source_imprints[src]
            imprint /= imprint.max()
            # Plot the contours
            ax.contour(
                imprint,
                np.arange(0.8, 1, 0.04),
                cmap="copper",
                alpha=0.5,
                extent=[-radius, radius, -radius, radius],
                zorder=5
            )
            # Find the maximum of the emission
            max_y, max_x = np.unravel_index(
                imprint.argmax(),
                imprint.shape
            )
            # If maximum outside the plot, recenter it
            if (max_x <= border_min) or (max_y <= border_min) or (max_x >= border_max) or (max_y >= border_max):
                dist[dist<=np.median(dist)] = 0
                max_y, max_x = np.unravel_index(
                    ((1 - dist/dist.max())*imprint).argmax(),
                    imprint.shape
                )
            # Show the source name associated to the imprint
            ax.text(
                ground_granularity[max_x],
                ground_granularity[max_y],
                f" {src}",
                color="#b35900",
                fontweight="bold",
                va="center",
                ha="center",
                zorder=30
            )
        
        # NenuFAR mini-array positions
        ax.scatter(
            ma_enu[:, 0],
            ma_enu[:, 1],
            20,
            color='black',
            zorder=10
        )
        for i in range(ma_enu.shape[0]):
            ax.text(
                ma_enu[i, 0],
                ma_enu[i, 1],
                f" {self.mini_arrays[i]}",
                color="black",
                zorder=10
            )
        # ax.scatter(
        #     building_enu[:, 0],
        #     building_enu[:, 1],
        #     20,
        #     color="tab:red",#'tab:orange',
        #     zorder=10
        # )

        # Plot axis labels       
        ax.set_xlabel(r"$\Delta x$ (m)")
        ax.set_ylabel(r"$\Delta y$ (m)")
        ax.set_title(
            f"{np.mean(self.frequency.to(u.MHz).value):.3f} MHz -- {self.time.isot}"
        )

        # Save or show the figure
        if figname != "":
            plt.savefig(
                figname,
                dpi=300,
                bbox_inches="tight",
                transparent=True
            )
            log.info(f"Figure '{figname}' saved.")
        else:
            plt.show()
        plt.close("all")
Exemple #5
0
    def enu(self):
        """
        """
        from nenupy.astro import l93_to_etrs, etrs_to_enu

        return etrs_to_enu(l93_to_etrs(self.position))