Esempio n. 1
0
def test_wavelength():
    wvl = wavelength(30 * u.MHz)
    assert isinstance(wvl, u.Quantity)
    assert wvl.to(u.m).value == pytest.approx(9.993, 1e-3)

    wvl_array = wavelength([10, 20, 30] * u.MHz)
    assert wvl_array.shape == (3, )
Esempio n. 2
0
    def array_factor(self, phase_center, coords, antpos):
        """
        """
        if not (isinstance(phase_center, AltAz)
                or hasattr(phase_center, 'altaz')):
            raise TypeError('phase_center should be an AltAz instance')
        if not (isinstance(coords, AltAz) or hasattr(coords, 'altaz')):
            raise TypeError('coords should be an AltAz instance')
        if not isinstance(antpos, np.ndarray):
            raise TypeError('antpos should be an np.ndarray instance')
        if antpos.shape[1] != 3:
            raise IndexError('antpos should have 2nd dimension = 3 (x, y, z)')

        def get_phi(az, el, antpos):
            """ az, el in radians
            """
            xyz_proj = np.array(
                [np.cos(az) * np.cos(el),
                 np.sin(az) * np.cos(el),
                 np.sin(el)])
            antennas = np.array(antpos)
            phi = np.dot(antennas, xyz_proj)
            return phi

        phi0 = get_phi(az=[phase_center.az.rad],
                       el=[phase_center.alt.rad],
                       antpos=antpos)
        phi_grid = get_phi(az=coords.az.rad, el=coords.alt.rad, antpos=antpos)
        delay = phi_grid - phi0
        coeff = 2j * np.pi / wavelength(self.freq).value
        af = np.sum(np.exp(coeff * delay), axis=0)
        return np.real(af * af.conjugate())
Esempio n. 3
0
 def freq_range(self, f):
     if f == [None, None]:
         self._conditions['freq'] = ''
         self._freq_range = f
         return
     if not isinstance(f, list):
         raise TypeError('freq_range must be a list')
     if not len(f) == 2:
         raise ValueError('freq_range must be of length 2')
     if not all([isinstance(fi, u.Quantity) for fi in f]):
         f = [fi * u.MHz for fi in f]
     lmax = wavelength(f[0]).to(u.m).value
     lmin = wavelength(f[1]).to(u.m).value
     self._conditions['freq'] = f'(em_min >= {lmin} AND '\
         f'em_max <= {lmax})'
     log.info(f'freq_range set to {f}.')
     self._freq_range = f
     return
Esempio n. 4
0
def test_wavelength():
    # Float input
    wl = wavelength(30)
    assert isinstance(wl, u.Quantity)
    assert wl.unit == 'm'
    assert wl.to(u.m).value == pytest.approx(10., 1e-2)
    # Nnumpy ndarray input
    freqs = np.array([10, 20, 30, 40])
    wavel = np.array([30, 15, 10, 7.5])
    wl = wavelength(freqs)
    assert isinstance(wl, u.Quantity)
    assert wl.unit == 'm'
    assert wl.to(u.m).value == pytest.approx(wavel, 1e-2)
    # Astropy Quantity input
    freqs = freqs * 1e6 * u.Hz
    wl = wavelength(freqs)
    assert isinstance(wl, u.Quantity)
    assert wl.unit == 'm'
    assert wl.to(u.m).value == pytest.approx(wavel, 1e-2)
Esempio n. 5
0
    def uvw_wave(self):
        """ UVW in lambdas.

            :getter: (times, freqs, baselines, UVW)
            
            :type: :class:`~numpy.ndarray`
        """
        if not hasattr(self, '_uvw'):
            raise Exception('Run .compute() first.')
        if self.freqs is None:
            raise ValueError('No frequency input, fill self.freqs.')
        lamb = wavelength(self.freqs).value
        na = np.newaxis
        return self._uvw[:, na, :, :] / lamb[na, :, na, na]
Esempio n. 6
0
    def get_beamform(self,
            pointing: Pointing,
            frequency_selection: str = None,
            time_selection: str = None,
            mini_arrays: np.ndarray = np.array([0, 1]),
            polarization: str = "NW",
            calibration: str = "default"
        ):
        """
            :Example:

                from nenupy.io.bst import BST, XST
                bst = BST("20191129_141900_BST.fits")
                xst = XST("20191129_141900_XST.fits")
                bf_cal = xst.get_beamform(
                    pointing = Pointing.from_bst(bst, beam=0, analog=False),
                    mini_arrays=bst.mini_arrays,
                    calibration="default"
                )

        """
        frequency_mask = self._get_freq_mask(frequency_selection)
        time_mask = self._get_time_mask(time_selection)

        # Select the mini-arrays cross correlations
        nenufar = NenuFAR()#[self.mini_arrays]
        bf_nenufar = NenuFAR()[mini_arrays]
        ma_real_indices = np.array([nenufar_miniarrays[name]["id"] for name in bf_nenufar.antenna_names])
        if np.any( ~np.isin(ma_real_indices, self.mini_arrays) ):
            raise IndexError(
                f"Selected Mini-Arrays {mini_arrays} are outside possible values: {self.mini_arrays}."
            )
        ma_indices = np.arange(self.mini_arrays.size, dtype="int")[np.isin(self.mini_arrays, ma_real_indices)]
        ma1, ma2 = np.tril_indices(self.mini_arrays.size, 0)
        mask = np.isin(ma1, ma_indices) & np.isin(ma2, ma_indices)

        # Calibration table
        if calibration.lower() == "none":
            # No calibration
            cal = np.ones(
                (self.frequencies[frequency_mask].size, ma_indices.size)
            )
        else:
            pol_idx = {"NW": [0], "NE": [1]}
            cal = read_cal_table(
                calibration_file=calibration
            )
            cal = cal[np.ix_(
                freq2sb(self.frequencies[frequency_mask]),
                ma_real_indices,
                pol_idx[polarization]
            )].squeeze(axis=2)

        # Load and filter the data
        vis = self.get(
            frequency_selection=frequency_selection,
            time_selection=time_selection,
            polarization= "XX" if polarization.upper() == "NW" else "YY",
        )[:, :, mask]

        # Insert the data in a matrix
        tri_x, tri_y = np.tril_indices(ma_indices.size, 0)
        vis_matrix = np.zeros(
            (
                self.time[time_mask].size,
                self.frequencies[frequency_mask].size,
                ma_indices.size,
                ma_indices.size
            ),
            dtype=np.complex
        )
        vis_matrix[:, :, tri_x, tri_y] = vis
        vis_matrix[:, :, tri_y, tri_x] = vis_matrix[:, :, tri_x, tri_y].conj()

        # Calibrate the Xcorr with the caltable
        for fi in range(vis_matrix.shape[1]):
            cal_i = np.expand_dims(cal[fi], axis=1)
            cal_i_h = np.expand_dims(cal[fi].T.conj(), axis=0)
            mul = np.dot(cal_i, cal_i_h)
            vis_matrix[:, fi, :, :] *= mul[np.newaxis, :, :]
        
        # Phase the visibilities towards the phase center
        phase = np.ones(
            (
                self.time[time_mask].size,
                self.frequencies[frequency_mask].size,
                ma_indices.size,
                ma_indices.size
            ),
            dtype=np.complex
        )
        altaz_pointing = pointing.horizontal_coordinates
        if altaz_pointing.size == 1:
            # Transit
            pass
        else:
            # Multiple pointings, get the correct value for all times
            altaz_pointing = pointing[self.time[time_mask]].horizontal_coordinates
        az = altaz_pointing.az.rad
        el = altaz_pointing.alt.rad
        ground_projection = np.array([
            np.cos(el) * np.cos(az),
            np.cos(el) * np.sin(az),
            np.sin(el)
        ])
        rot = np.radians(-90)
        rotation = np.array(
            [
                [ np.cos(rot), np.sin(rot), 0],
                [-np.sin(rot), np.cos(rot), 0],
                [ 0,           0,           1]
            ]
        )
        ma1_pos = np.dot(
            nenufar.antenna_positions[ma1[mask]],
            rotation
        )
        ma2_pos = np.dot(
            nenufar.antenna_positions[ma2[mask]],
            rotation
        )
        dphi = np.dot(
            ma1_pos - ma2_pos,
            ground_projection
        ).T
        wvl = wavelength(self.frequencies[frequency_mask]).to(u.m).value
        phase[:, :, tri_x, tri_y] = np.exp(
            -2.j*np.pi/wvl[None, :, None] * dphi[:, None, :]
        )
        phase[:, :, tri_y, tri_x] = phase[:, :, tri_x, tri_y].conj().copy()
        data = np.sum((vis_matrix * phase).real, axis=(2, 3))

        return BST_Slice(
            time=self.time[time_mask],
            frequency=self.frequencies[frequency_mask],
            value=data.squeeze()
        )
Esempio n. 7
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
Esempio n. 8
0
    def make_image(self,
            resolution: u.Quantity = 1*u.deg,
            fov_radius: u.Quantity = 25*u.deg,
            phase_center: SkyCoord = None,
            stokes: str = "I"
        ):
        """
            :Example:

                xst = XST("XST.fits")
                data = xst.get_stokes("I")
                sky = data.make_image(
                    resolution=0.5*u.deg,
                    fov_radius=27*u.deg,
                    phase_center=SkyCoord(277.382, 48.746, unit="deg")
                )
                sky[0, 0, 0].plot(
                    center=SkyCoord(277.382, 48.746, unit="deg"),
                    radius=24.5*u.deg
                )

        """
        exposure = self.time[-1] - self.time[0]

        # Compute XST UVW coordinates (zenith phased)
        uvw = compute_uvw(
            interferometer=NenuFAR()[self.mini_arrays],
            phase_center=None, # will be zenith
            time=self.time,
        )

        # Prepare visibilities rephasing
        rephase_matrix, uvw = self.rephase_visibilities(
            phase_center=phase_center,
            uvw=uvw
        )

        # Mask auto-correlations
        ma1, ma2 = np.tril_indices(self.mini_arrays.size, 0)
        cross_mask = ma1 != ma2
        uvw = uvw[:, cross_mask, :]
        # Transform to lambda units
        wvl = wavelength(self.frequency).to(u.m).value
        uvw = uvw[:, None, :, :]/wvl[None, :, None, None] # (t, f, bsl, 3)
        # Mean in time
        uvw = np.mean(uvw, axis=0)

        # Prepare the sky
        sky = HpxSky(
            resolution=resolution,
            time=self.time[0] + exposure/2,
            frequency=np.mean(self.frequency),
            polarization=np.array([stokes]),
            value=np.nan
        )

        # Compute LMN coordinates
        image_mask = sky.visible_mask[0, 0, 0]
        image_mask *= sky.coordinates.separation(phase_center) <= fov_radius
        l, m, n = sky.compute_lmn(
            phase_center=phase_center,
            coordinate_mask=image_mask
        )
        lmn = np.array([l, m, (n - 1)], dtype=np.float32).T
        n_pix = l.size
        lmn = da.from_array(
            lmn,
            chunks=(np.floor(n_pix/os.cpu_count()), 3)
        )

        # Transform to Dask array
        n_bsl = uvw.shape[1]
        n_freq = self.frequency.size
        n_pix = l.size
        uvw = da.from_array(
            uvw.astype(np.float32),
            chunks=(n_freq, np.floor(n_bsl/os.cpu_count()), 3)
        )

        # Compute the phase
        uvwlmn = np.sum(uvw[:, :, None, :] * lmn[None, None, :, :], axis=-1)
        phase = np.exp( -2j * np.pi * uvwlmn ) # (f, bsl, npix)

        # Rephase and average visibilites
        vis = np.mean( # Mean in time
            self.value * rephase_matrix,
            axis=0
        )[..., cross_mask] # (nfreqs, nvis)

        # Make dirty image
        dirty = np.nanmean( # mean in baselines
            np.real(
                np.mean( # mean in freq
                    vis[:, :, None] * phase,
                    axis=0
                )
            ),
            axis=0
        )

        # Insert dirty image in Sky object
        log.info(
            f"Computing image (time: {self.time.size}, frequency: {self.frequency.size}, baselines: {vis.shape[1]}, pixels: {phase.shape[-1]})... "
        )
        with ProgressBar() if log.getEffectiveLevel() <= logging.INFO else DummyCtMgr():
            sky.value[0, 0, 0, image_mask] = dirty.compute()

        return sky
Esempio n. 9
0
    def rephase_visibilities(self, phase_center, uvw):
        """ """

        # Compute the zenith original phase center
        zenith = SkyCoord(
            np.zeros(self.time.size),
            np.ones(self.time.size)*90,
            unit="deg",
            frame=AltAz(
                obstime=self.time,
                location=nenufar_position
            )
        )
        zenith_phase_center = altaz_to_radec(zenith)

        # Define the rotation matrix
        def rotation_matrix(skycoord):
            """
            """
            ra_rad = skycoord.ra.rad
            dec_rad = skycoord.dec.rad

            if np.isscalar(ra_rad):
                ra_rad = np.array([ra_rad])
                dec_rad = np.array([dec_rad])

            cos_ra = np.cos(ra_rad)
            sin_ra = np.sin(ra_rad)
            cos_dec = np.cos(dec_rad)
            sin_dec = np.sin(dec_rad)

            return np.array([
                [cos_ra, -sin_ra, np.zeros(ra_rad.size)],
                [-sin_ra*sin_dec, -cos_ra*sin_dec, cos_dec],
                [sin_ra*cos_dec, cos_ra*cos_dec, sin_dec],
            ])

        # Transformation matrices
        to_origin = rotation_matrix(zenith_phase_center) # (3, 3, ntimes)
        to_new_center = rotation_matrix(phase_center) # (3, 3, 1)
        total_transformation = np.matmul(
            np.transpose(
                to_new_center,
                (2, 0, 1)
            ),
            to_origin
        ) # (3, 3, ntimes)
        rotUVW = np.matmul(
            np.expand_dims(
                (to_origin[2, :] - to_new_center[2, :]).T,
                axis=1
            ),
            np.transpose(
                to_origin,
                (2, 1, 0)
            )
        ) # (ntimes, 1, 3)
        phase = np.matmul(
            rotUVW,
            np.transpose(uvw, (0, 2, 1))
        ) # (ntimes, 1, nvis)
        rotate_visibilities = np.exp(
            2.j*np.pi*phase/wavelength(self.frequency).to(u.m).value[None, :, None]
        ) # (ntimes, nfreqs, nvis)

        new_uvw = np.matmul(
            uvw, # (ntimes, nvis, 3)
            np.transpose(total_transformation, (2, 0, 1))
        )

        return rotate_visibilities, new_uvw
Esempio n. 10
0
    def array_factor(self, az, el, antpos, freq):
        r""" Computes the array factor :math:`\mathcal{A}` (i.e.
            the far-field radiation pattern obtained for an array
            of :math:`n_{\rm ant}` radiators).

            .. math::
                \mathcal{A} = \left| \sum_{n_{\scriptscriptstyle \rm ant}} e^{2 \pi i \frac{\nu}{c} (\varphi_0 - \varphi)} \right|^2

            .. math::
                \varphi = \underset{\scriptstyle n_{\scriptscriptstyle \rm ant}\, \times\, 3}{\mathbf{P}_{\rm ant}} \cdot \pmatrix{
                    \cos(\phi)\cos(\theta)\\
                    \sin(\phi)\cos(\theta)\\
                    \sin(\theta)
                }

            .. math::
                \varphi_0 = \underset{\scriptstyle n_{\scriptscriptstyle \rm ant}\, \times\, 3}{\mathbf{P}_{\rm ant}} \cdot \pmatrix{
                    \cos(\phi_0)\cos(\theta_0)\\
                    \sin(\phi_0)\cos(\theta_0)\\
                    \sin(\theta_0)
                }

            :math:`\mathbf{P}_{\rm ant}` is the antenna position
            matrix, :math:`\phi` and :math:`\theta` are the sky
            local coordinates (azimuth and elevation respectively)
            gridded on a HEALPix representation, whereas 
            :math:`\phi_0` and :math:`\theta_0` are the pointing
            direction in local coordinates.

            :param az:
                Pointing azimuth (in degrees if `float`)
            :type az: `float` or :class:`~astropy.units.Quantity`
            :param el:
                Pointing elevation (in degrees if `float`)
            :type el: `float` or :class:`~astropy.units.Quantity`
            :param antpos:
                Antenna positions shaped as (n_ant, 3)
            :type antpos: :class:`~numpy.ndarray`
            :param freq:
                Frequency (in MHz if `float`)
            :type freq: `float` or :class:`~astropy.units.Quantity`

            :returns: Array factor
            :rtype: :class:`~numpy.ndarray`

        """
        def get_phi(az, el, antpos):
            """ az, el in radians
            """
            xyz_proj = np.array(
                [np.cos(az) * np.cos(el),
                 np.sin(az) * np.cos(el),
                 np.sin(el)])
            antennas = np.array(antpos)
            phi = np.dot(antennas, xyz_proj)
            return phi

        if not isinstance(az, u.Quantity):
            az *= u.deg
        if not isinstance(el, u.Quantity):
            el *= u.deg

        self.phase_center = to_radec(ho_coord(az=az, alt=el, time=self.time))

        phi0 = get_phi(az=[az.to(u.rad).value],
                       el=[el.to(u.rad).value],
                       antpos=antpos)
        phi_grid = get_phi(az=self.ho_coords.az.rad,
                           el=self.ho_coords.alt.rad,
                           antpos=antpos)
        nt = ne.set_num_threads(ne._init_num_threads())
        delay = ne.evaluate('phi_grid-phi0')
        coeff = 2j * np.pi / wavelength(freq).value

        if self.ncpus == 1:
            # Normal
            af = ne.evaluate('sum(exp(coeff*delay),axis=0)')
        # elif self.ncpus == 'numba':
        #     af = perfcompute(coeff * delay)
        else:
            # Multiproc
            af = np.sum(mp_expo(self.ncpus, coeff, delay), axis=0)

        #return np.abs(af * af.conjugate())
        return np.real(af * af.conjugate())
Esempio n. 11
0
    def image(self, resolution=1, fov=50):
        r""" Converts NenuFAR-TV-like data sets containing
            visibilities (:math:`V(u,v,\nu , t)`) into images
            :math:`I(l, m, \nu)` phase-centered at the local
            zenith while time averaging the visibilities.
            The Field of View ``fov`` argument defines the
            diameter angular size (zenith-centered) above which
            the image is not computed.
            
            .. math::
                I(l, m, \nu) = \int
                    \langle V(u, v, \nu, t) \rangle_t e^{
                        2 \pi i \frac{\nu}{c} \left(
                            \langle u(t) \rangle_t l + \langle v(t) \rangle_t m
                        \right)
                    }
                    \, du \, dv

            :param resolution:
                Resoltion (in degrees if a `float` is given) of
                the HEALPix grid (passed to initialize the 
                :class:`~nenupy.astro.hpxsky.HpxSky` object).
            :type resolution: `float` or :class:`~astropy.units.Quantity`
            :param fov:
                Field of view diameter of the image (in degrees
                if a `float` is given).
            :type fov: `float` or :class:`~astropy.units.Quantity`

            :returns: HEALPix sky object embedding the computed
                image.
            :rtype: :class:`~nenupy.astro.hpxsky.HpxSky`

            :Example:
                >>> from nenupy.crosslet import TV_Data
                >>> import astropy.units as u
                >>> tv = TV_Data('20191204_132113_nenufarTV.dat')
                >>> im = tv.image(
                        resolution=0.2*u.deg,
                        fov=60*u.deg
                    )

            .. seealso::
                :class:`~nenupy.astro.hpxsky.HpxSky`,
                :meth:`~nenupy.astro.hpxsky.HpxSky.lmn`,
                :meth:`~nenupy.crosslet.uvw.UVW.from_tvdata`

            .. warning::
                This method is intended to be used for NenuFAR-TV
                data and relatively small XST datasets. It is not
                suited to long observations for which a MS
                conversion is required before using imaging
                dedicated softwares.
        """
        if not isinstance(fov, un.Quantity):
            fov *= un.deg
        f_idx = 0  # Frequency index
        # Sky preparation
        sky = HpxSky(resolution=resolution)
        exposure = self.times[-1] - self.times[0]
        sky.time = self.times[0] + exposure / 2.
        sky._is_visible = sky._ho_coords.alt >= 90 * un.deg - fov / 2.
        phase_center = eq_zenith(sky.time)
        l, m, n = sky.lmn(phase_center=phase_center)
        # UVW coordinates
        uvw = UVW.from_tvdata(self)
        u = np.mean(  # Mean in time
            uvw.uvw[:, :, 0], axis=0)[self.mask_auto] / wavelength(
                self.freqs[f_idx]).value
        v = np.mean(uvw.uvw[:, :, 1], axis=0)[self.mask_auto] / wavelength(
            self.freqs[f_idx]).value
        w = np.mean(  # Mean in time
            uvw.uvw[:, :, 2], axis=0)[self.mask_auto] / wavelength(
                self.freqs[f_idx]).value
        # Mulitply (u, v) by (l, m) and compute FT exp
        ul = ft_mul(x=np.tile(u, (l.size, 1)).T, y=np.tile(l, (u.size, 1)))
        vm = ft_mul(x=np.tile(v, (m.size, 1)).T, y=np.tile(m, (v.size, 1)))
        phase = ft_phase(ul, vm)
        # Phase visibilities
        vis = np.mean(  # Mean in time
            self.stokes_i, axis=0)[f_idx, :][self.mask_auto]
        im = np.zeros(l.size)
        for i in tqdm(prange(l.size)):
            im[i] = np.real(ft_sum(vis, phase[:, i]))
        sky.skymap[sky._is_visible] = im
        return sky
Esempio n. 12
0
    def beamform(self, az, el, pol='NW', ma=None, calibration='default'):
        r""" Converts cross correlation statistics data XST, 
            :math:`\mathbf{X}(t, \nu)`, in beamformed data BST,
            :math:`B(t, \nu)`, where :math:`t` and :math:`\nu` are
            the time and the frequency respectively.
            :math:`\mathbf{X}(t, \nu)` is a subset of XST data at
            the required polarization ``pol``.

            This is done for a given phasing direction in local
            sky coordinates :math:`\varphi` (azimuth, ``az``) and
            :math:`\theta` (elevation, ``el``), with a selection
            of Mini-Arrays ``ma`` (numbered :math:`a`).

            .. math::
                B (t, \nu) = \operatorname{Re} \left\{
                \sum
                    \left[
                        \underset{\scriptscriptstyle a \times 1}{\mathbf{C}}
                        \cdot
                        \underset{\scriptscriptstyle 1 \times a}{\mathbf{C}^{H}}
                    \right](\nu)
                    \cdot
                    \underset{\scriptscriptstyle a \times a}{\mathbf{X}} (t, \nu)
                    \cdot
                    \left[
                        \underset{\scriptscriptstyle a \times 1}{\mathbf{P}}
                        \cdot
                        \underset{\scriptscriptstyle 1 \times a}{\mathbf{P}^{H}}
                    \right](\nu)
                \right\}

            .. math::
                \rm{with} \quad
                \cases{
                    \mathbf{C}(\nu ) = e^{2 \pi i \nu \mathbf{ t }} \quad \rm{the~calibration~ file}\\
                    \mathbf{P} (\nu) = e^{-2 \pi i \frac{\nu}{c} (\mathbf{b} \cdot \mathbf{u})} \quad \rm{phasing}\\
                    \underset{\scriptscriptstyle a \times 3}{\mathbf{b}} = \mathbf{a}_{1} - \mathbf{a}_{2} \quad \rm{baseline~positions}\\
                    \underset{\scriptscriptstyle 3 \times 1}{\mathbf{u}} = \left[
                        \cos(\theta)\cos(\varphi),
                        \cos(\theta)\sin(\varphi),
                        \sin(\theta) \right]
                }

            :param az:
                Azimuth coordinate used for beamforming (default
                unit is degrees in `float` input).
            :type az: `float` or :class:`~astropy.units.Quantity`
            :param el:
                Elevation coordinate used for beamforming (default
                unit is degrees in `float` input).
            :type el: `float` or :class:`~astropy.units.Quantity`
            :param pol:
                Polarization (either ``'NW'`` or ``'NE'``.
            :type pol: `str`
            :param ma:
                Subset of Mini-Arrays (minimum 2) used for
                beamforming.
            :type ma: `list` or :class:`~numpy.ndarray`
            :param calibration:
                Antenna delay calibration file (i.e, :math:`\mathbf{C}`).
                If ``'none'``, no calibration is applied.
                If ``'default'``, the standard calibration file is
                used, otherwise the calibration file name should
                be given (see also :func:`~nenupy.instru.instru.read_cal_table`).
            :type calibration: `str`

            :returns: Beamformed data.
            :rtype: :class:`~nenupy.beamlet.sdata.SData`

            :Example:
                >>> from nenupy.crosslet import XST_Data
                >>> xst = XST_Data('20191129_141900_XST.fits')
                >>> bf = xst.beamform(
                        az=180,
                        el=90,
                        pol='NW',
                        ma=[17, 44],
                        calibration='default'
                    )
        """
        log.info('Beamforming towards az={}, el={}, pol={}'.format(
            az, el, pol))
        # Mini-Array selection
        if ma is None:
            ma = self.mas.copy()
        mas = self._mas_idx[np.isin(self.mas, ma)]
        # Calibration table
        if calibration.lower() == 'none':
            # No calibration
            cal = np.ones((self.sb_idx.size, mas.size))
        else:
            pol_idx = {'NW': [0], 'NE': [1]}
            cal = read_cal_table(calfile=calibration)
            cal = cal[np.ix_(self.sb_idx, mas, pol_idx[pol])].squeeze()
        # Matrix of BSTs
        c = np.zeros((self.times.size, self.freqs.size, mas.size, mas.size),
                     dtype=np.complex)
        # Matrix of phasings
        p = np.ones((self.freqs.size, mas.size, mas.size), dtype=np.complex)
        # Pointing direction
        if isinstance(az, un.Quantity):
            az = az.to(un.deg).value
        if isinstance(el, un.Quantity):
            el = el.to(un.deg).value
        az = np.radians(az)
        el = np.radians(el)
        u = np.array(
            [np.cos(el) * np.cos(az),
             np.cos(el) * np.sin(az),
             np.sin(el)])
        # Polarization selection
        mask = np.isin(self._ant1, mas) & np.isin(self._ant2, mas)
        log.info('Loading data...')
        if pol.upper() == 'NW':
            cpol = self.xx[:, :, mask]
        else:
            cpol = self.yy[:, :, mask]
        log.info('Data of shape {} loaded for beamforming'.format(cpol.shape))
        # Put the Xcorr in a matrix
        trix, triy = np.tril_indices(mas.size, 0)
        c[:, :, trix, triy] = cpol
        c[:, :, triy, trix] = c[:, :, trix, triy].conj()
        # Calibrate the Xcorr with the caltable
        for fi in prange(c.shape[1]):
            cal_i = np.expand_dims(cal[fi], axis=1)
            cal_i_h = np.expand_dims(cal[fi].T.conj(), axis=0)
            mul = np.dot(cal_i, cal_i_h)
            c[:, fi, :, :] *= mul[np.newaxis, :, :]
        # Phase the Xcorr
        dphi = np.dot(ma_pos[self._ant1[mask]] - ma_pos[self._ant2[mask]], u)
        wavel = wavelength(self.freqs).value
        p[:, trix, triy] = np.exp(-2.j * np.pi / wavel[:, None] * dphi)
        p[:, triy, trix] = p[:, trix, triy].conj()
        data = np.sum((c * p).real, axis=(2, 3))
        log.info('Beamforming complete.')
        return SData(data=np.expand_dims(data, axis=2),
                     time=self.times,
                     freq=self.freqs,
                     polar=np.array([pol.upper()]))