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]))
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)
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
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")
def enu(self): """ """ from nenupy.astro import l93_to_etrs, etrs_to_enu return etrs_to_enu(l93_to_etrs(self.position))