def invert_serial(vis, im: Image, dopsf=False, normalize=True, context='2d', vis_slices=1, facets=1, overlap=0, taper=None, **kwargs): """ Invert using algorithm specified by context: * 2d: Two-dimensional transform * wstack: wstacking with either vis_slices or wstack (spacing between w planes) set * wprojection: w projection with wstep (spacing between w places) set, also kernel='wprojection' * timeslice: snapshot imaging with either vis_slices or timeslice set. timeslice='auto' does every time * facets: Faceted imaging with facets facets on each axis * facets_wprojection: facets AND wprojection * facets_wstack: facets AND wstacking * wprojection_wstack: wprojection and wstacking :param vis: :param im: :param dopsf: Make the psf instead of the dirty image (False) :param normalize: Normalize by the sum of weights (True) :param context: Imaging context e.g. '2d', 'timeslice', etc. :param kwargs: :return: Image, sum of weights """ c = imaging_context(context) vis_iter = c['vis_iterator'] invert = c['invert'] if not isinstance(vis, Visibility): svis = convert_blockvisibility_to_visibility(vis) else: svis = vis resultimage = create_empty_image_like(im) totalwt = None for rows in vis_iter(svis, vis_slices=vis_slices): if numpy.sum(rows): visslice = create_visibility_from_rows(svis, rows) sumwt = 0.0 workimage = create_empty_image_like(im) for dpatch in image_scatter_facets(workimage, facets=facets, overlap=overlap, taper=taper): result, sumwt = invert(visslice, dpatch, dopsf, normalize=False, facets=facets, vis_slices=vis_slices, **kwargs) # Ensure that we fill in the elements of dpatch instead of creating a new numpy arrray dpatch.data[...] = result.data[...] # Assume that sumwt is the same for all patches if totalwt is None: totalwt = sumwt else: totalwt += sumwt resultimage.data += workimage.data assert totalwt is not None, "No valid data found for imaging" if normalize: resultimage = normalize_sumwt(resultimage, totalwt) return resultimage, totalwt
def test_scatter_gather_facet(self): m31original = create_test_image( polarisation_frame=PolarisationFrame('stokesI')) assert numpy.max(numpy.abs(m31original.data)), "Original is empty" for nraster in [1, 4, 8]: m31model = create_test_image( polarisation_frame=PolarisationFrame('stokesI')) image_list = image_scatter_facets(m31model, facets=nraster) for patch in image_list: assert patch.data.shape[3] == (m31model.data.shape[3] // nraster), \ "Number of pixels in each patch: %d not as expected: %d" % (patch.data.shape[3], (m31model.data.shape[3] // nraster)) assert patch.data.shape[2] == (m31model.data.shape[2] // nraster), \ "Number of pixels in each patch: %d not as expected: %d" % (patch.data.shape[2], (m31model.data.shape[2] // nraster)) patch.data[...] = 1.0 m31reconstructed = create_empty_image_like(m31model) m31reconstructed = image_gather_facets(image_list, m31reconstructed, facets=nraster) flat = image_gather_facets(image_list, m31reconstructed, facets=nraster, return_flat=True) assert numpy.max(numpy.abs( flat.data)), "Flat is empty for %d" % nraster assert numpy.max(numpy.abs( m31reconstructed.data)), "Raster is empty for %d" % nraster
def test_scatter_gather_facet_overlap_taper(self): m31original = create_test_image(polarisation_frame=PolarisationFrame('stokesI')) assert numpy.max(numpy.abs(m31original.data)), "Original is empty" for taper in ['linear', None]: for nraster, overlap in [(1, 0), (4, 8), (8, 8), (8, 16)]: m31model = create_test_image(polarisation_frame=PolarisationFrame('stokesI')) image_list = image_scatter_facets(m31model, facets=nraster, overlap=overlap, taper=taper) for patch in image_list: assert patch.data.shape[3] == (2 * overlap + m31model.data.shape[3] // nraster), \ "Number of pixels in each patch: %d not as expected: %d" % (patch.data.shape[3], (2 * overlap + m31model.data.shape[3] // nraster)) assert patch.data.shape[2] == (2 * overlap + m31model.data.shape[2] // nraster), \ "Number of pixels in each patch: %d not as expected: %d" % (patch.data.shape[2], (2 * overlap + m31model.data.shape[2] // nraster)) m31reconstructed = create_empty_image_like(m31model) m31reconstructed = image_gather_facets(image_list, m31reconstructed, facets=nraster, overlap=overlap, taper=taper) flat = image_gather_facets(image_list, m31reconstructed, facets=nraster, overlap=overlap, taper=taper, return_flat=True) export_image_to_fits(m31reconstructed, "%s/test_image_gather_scatter_%dnraster_%doverlap_%s_reconstructed.fits" % (self.dir, nraster, overlap, taper)) export_image_to_fits(flat, "%s/test_image_gather_scatter_%dnraster_%doverlap_%s_flat.fits" % (self.dir, nraster, overlap, taper)) assert numpy.max(numpy.abs(flat.data)), "Flat is empty for %d" % nraster assert numpy.max(numpy.abs(m31reconstructed.data)), "Raster is empty for %d" % nraster
def create_low_test_beam(model: Image) -> Image: """Create a test power beam for LOW using an image from OSKAR :param model: Template image :return: Image """ beam = import_image_from_fits(arl_path('data/models/SKA1_LOW_beam.fits')) # Scale the image cellsize to account for the different in frequencies. Eventually we will want to # use a frequency cube log.info("create_low_test_beam: primary beam is defined at %.3f MHz" % (beam.wcs.wcs.crval[2] * 1e-6)) nchan, npol, ny, nx = model.shape # We need to interpolate each frequency channel separately. The beam is assumed to just scale with # frequency. reprojected_beam = create_empty_image_like(model) for chan in range(nchan): model2dwcs = model.wcs.sub(2).deepcopy() model2dshape = [model.shape[2], model.shape[3]] beam2dwcs = beam.wcs.sub(2).deepcopy() # The frequency axis is the second to last in the beam frequency = model.wcs.sub(['spectral']).wcs_pix2world([chan], 0)[0] fscale = beam.wcs.wcs.crval[2] / frequency beam2dwcs.wcs.cdelt = fscale * beam.wcs.sub(2).wcs.cdelt beam2dwcs.wcs.crpix = beam.wcs.sub(2).wcs.crpix beam2dwcs.wcs.crval = model.wcs.sub(2).wcs.crval beam2dwcs.wcs.ctype = model.wcs.sub(2).wcs.ctype model2dwcs.wcs.crpix = [ model.shape[2] // 2 + 1, model.shape[3] // 2 + 1 ] beam2d = create_image_from_array(beam.data[0, 0, :, :], beam2dwcs, model.polarisation_frame) reprojected_beam2d, footprint = reproject_image(beam2d, model2dwcs, shape=model2dshape) assert numpy.max( footprint.data) > 0.0, "No overlap between beam and model" reprojected_beam2d.data *= reprojected_beam2d.data reprojected_beam2d.data[footprint.data <= 0.0] = 0.0 for pol in range(npol): reprojected_beam.data[chan, pol, :, :] = reprojected_beam2d.data[:, :] return reprojected_beam
def create_low_test_vp(model: Image, use_local=True) -> Image: """Create a test power beam for LOW using an image from OSKAR :param model: Template image :return: Image """ # TODO: Get true voltage beam from OSKAR beam = import_image_from_fits(arl_path('data/models/SKA1_LOW_beam.fits')) beam.data = numpy.sqrt(beam.data).astype('complex') # Scale the image cellsize to account for the different in frequencies. Eventually we will want to # use a frequency cube log.debug("create_low_test_beam: LOW voltage pattern is defined at %.3f MHz" % (beam.wcs.wcs.crval[2] * 1e-6)) nchan, npol, ny, nx = model.shape # We need to interpolate each frequency channel separately. The beam is assumed to just scale with # frequency. reprojected_beam = create_empty_image_like(model) reprojected_beam.data = reprojected_beam.data.astype('complex') for chan in range(nchan): model2dwcs = model.wcs.sub(2).deepcopy() model2dshape = [model.shape[2], model.shape[3]] beam2dwcs = beam.wcs.sub(2).deepcopy() # The frequency axis is the second to last in the beam frequency = model.wcs.sub(['spectral']).wcs_pix2world([chan], 0)[0] fscale = beam.wcs.wcs.crval[2] / frequency beam2dwcs.wcs.cdelt = fscale * beam.wcs.sub(2).wcs.cdelt beam2dwcs.wcs.crpix = beam.wcs.sub(2).wcs.crpix beam2dwcs.wcs.crval = model.wcs.sub(2).wcs.crval beam2dwcs.wcs.ctype = model.wcs.sub(2).wcs.ctype model2dwcs.wcs.crpix = [model.shape[2] // 2 + 1, model.shape[3] // 2 + 1] beam2d_real = create_image_from_array(numpy.real(beam.data[0, 0, :, :]), beam2dwcs, model.polarisation_frame) beam2d_imag = create_image_from_array(numpy.imag(beam.data[0, 0, :, :]), beam2dwcs, model.polarisation_frame) reprojected_beam2d_real, footprint = reproject_image(beam2d_real, model2dwcs, shape=model2dshape) reprojected_beam2d_imag, footprint = reproject_image(beam2d_imag, model2dwcs, shape=model2dshape) assert numpy.max(footprint.data) > 0.0, "No overlap between beam and model" reprojected_beam2d_real.data[footprint.data <= 0.0] = 0.0 reprojected_beam2d_imag.data[footprint.data <= 0.0] = 0.0 for pol in range(npol): reprojected_beam.data[chan, pol, :, :] = reprojected_beam2d_real.data[:, :] \ + 1j * reprojected_beam2d_imag.data[:, :] set_pb_header(reprojected_beam, use_local=use_local) return reprojected_beam
def test_raster_overlap(self): m31original = create_test_image(polarisation_frame=PolarisationFrame('stokesI')) assert numpy.max(numpy.abs(m31original.data)), "Original is empty" flat = create_empty_image_like(m31original) for nraster, overlap in [(1, 0), (1, 16), (4, 8), (4, 16), (8, 8), (16, 4), (9, 5)]: m31model = create_test_image(polarisation_frame=PolarisationFrame('stokesI')) for patch, flat_patch in zip(image_raster_iter(m31model, facets=nraster, overlap=overlap), image_raster_iter(flat, facets=nraster, overlap=overlap)): patch.data *= 2.0 flat_patch.data[...] += 1.0 assert numpy.max(numpy.abs(m31model.data)), "Raster is empty for %d" % nraster
def mosaic_pb(model, telescope, pointingcentres, use_local=True): """ Create a mosaic primary beam by adding primary beams for a set of pointing centres Note that the addition is root sum of squares :param model: Template image :param telescope: :param pointingcentres: list of pointing centres :return: """ assert isinstance(pointingcentres, collections.Iterable), "Need a list of pointing centres" sumpb = create_empty_image_like(model) for pc in pointingcentres: pb = create_pb(model, telescope, pointingcentre=pc, use_local=use_local) sumpb.data += pb.data ** 2 sumpb.data = numpy.sqrt(sumpb.data) return sumpb
def create_vp_generic(model, pointingcentre=None, diameter=25.0, blockage=1.8, use_local=True): """ Make an image like model and fill it with an analytical model of the primary beam :param model: :return: """ beam = create_empty_image_like(model) beam.data = numpy.zeros(beam.data.shape, dtype='complex') nchan, npol, ny, nx = model.shape if pointingcentre is not None: cx, cy = pointingcentre.to_pixel(model.wcs, origin=0) else: cx, cy = beam.wcs.sub(2).wcs.crpix[0] - 1, beam.wcs.sub( 2).wcs.crpix[1] - 1 for chan in range(nchan): # The frequency axis is the second to last in the beam frequency = model.wcs.sub(['spectral']).wcs_pix2world([chan], 0)[0] wavelength = const.c.to('m s^-1').value / frequency d2r = numpy.pi / 180.0 scale = d2r * numpy.abs(beam.wcs.sub(2).wcs.cdelt[0]) xx, yy = numpy.meshgrid(scale * (range(nx) - cx), scale * (range(ny) - cy)) # Radius of each cell in radians rr = numpy.sqrt(xx**2 + yy**2) blockage_factor = (blockage / diameter)**2 for pol in range(npol): reflector = ft_disk(rr * numpy.pi * diameter / wavelength) blockage = ft_disk(rr * numpy.pi * blockage / wavelength) beam.data[chan, pol, ...] = reflector - blockage_factor * blockage set_pb_header(beam, use_local=use_local) return beam
def create_pb_generic(model, pointingcentre=None, diameter=25.0, blockage=1.8): """ Make an image like model and fill it with an analytical model of the primary beam :param model: :return: """ beam = create_empty_image_like(model) nchan, npol, ny, nx = model.shape if pointingcentre is not None: cx, cy = skycoord_to_pixel(pointingcentre, model.wcs, 0, 'wcs') else: with warnings.catch_warnings(): warnings.simplefilter('ignore', FITSFixedWarning) cx, cy = beam.wcs.sub(2).wcs.crpix[0] - 1, beam.wcs.sub( 2).wcs.crpix[1] - 1 for chan in range(nchan): # The frequency axis is the second to last in the beam with warnings.catch_warnings(): warnings.simplefilter('ignore', FITSFixedWarning) frequency = model.wcs.sub(['spectral']).wcs_pix2world([chan], 0)[0] wavelength = const.c.to('m s^-1').value / frequency d2r = numpy.pi / 180.0 scale = d2r * numpy.abs(beam.wcs.sub(2).wcs.cdelt[0]) xx, yy = numpy.meshgrid(scale * (range(nx) - cx), scale * (range(ny) - cy)) # Radius of each cell in radians rr = numpy.sqrt(xx**2 + yy**2) blockage_factor = (blockage / diameter)**2 for pol in range(npol): reflector = ft_disk(rr * numpy.pi * diameter / wavelength) blockage = ft_disk(rr * numpy.pi * blockage / wavelength) beam.data[chan, pol, ...] = reflector - blockage_factor * blockage beam.data *= beam.data return beam
def test_grid_gaintable_to_screen(self): screen = import_image_from_fits( arl_path('data/models/test_mpc_screen.fits')) beam = create_test_image(cellsize=0.0015, phasecentre=self.vis.phasecentre, frequency=self.frequency) beam = create_low_test_beam(beam, use_local=False) gleam_components = create_low_test_skycomponents_from_gleam( flux_limit=1.0, phasecentre=self.phasecentre, frequency=self.frequency, polarisation_frame=PolarisationFrame('stokesI'), radius=0.2) pb_gleam_components = apply_beam_to_skycomponent( gleam_components, beam) actual_components = filter_skycomponents_by_flux(pb_gleam_components, flux_min=1.0) gaintables = create_gaintable_from_screen(self.vis, actual_components, screen) assert len(gaintables) == len(actual_components), len(gaintables) assert gaintables[0].gain.shape == (3, 94, 3, 1, 1), gaintables[0].gain.shape newscreen = create_empty_image_like(screen) newscreen, weights = grid_gaintable_to_screen(self.vis, gaintables, newscreen) assert numpy.max(numpy.abs(screen.data)) > 0.0 if self.persist: export_image_to_fits( newscreen, arl_path('test_results/test_mpc_screen_gridded.fits')) if self.persist: export_image_to_fits( weights, arl_path('test_results/test_mpc_screen_gridded_weights.fits'))
def sum_invert_results(image_list, normalize=True): """ Sum a set of invert results with appropriate weighting :param image_list: List of [image, sum weights] pairs :return: image, sum of weights """ if len(image_list) == 1: return image_list[0] im = create_empty_image_like(image_list[0][0]) sumwt = image_list[0][1].copy() sumwt *= 0.0 for i, arg in enumerate(image_list): if arg is not None: im.data += arg[1][..., numpy.newaxis, numpy.newaxis] * arg[0].data sumwt += arg[1] if normalize: im = normalize_sumwt(im, sumwt) return im, sumwt
def grid_gaintable_to_screen(vis, gaintables, screen, height=3e5, gaintable_slices=None, scale=1.0, **kwargs): """ Grid a gaintable to a screen image The phases are just average per grid cell, no phase unwrapping is performed. :param vis: :param sc: Sky components for which pierce points are needed :param screen: :param height: Height (in m) of screen above telescope e.g. 3e5 :param scale: Multiply the screen by this factor :return: gridded screen image, weights image """ assert isinstance(vis, BlockVisibility) station_locations = vis.configuration.xyz nant = station_locations.shape[0] t2r = numpy.pi / 43200.0 newscreen = create_empty_image_like(screen) weights = create_empty_image_like(screen) nchan, ntimes, ny, nx = screen.shape # The time in the Visibility is hour angle in seconds! number_no_weight = 0 for gaintable in gaintables: for iha, rows in enumerate( gaintable_timeslice_iter(gaintable, gaintable_slices=gaintable_slices)): gt = create_gaintable_from_rows(gaintable, rows) ha = numpy.average(gt.time) pp = find_pierce_points(station_locations, (gt.phasecentre.ra.rad + t2r * ha) * u.rad, gt.phasecentre.dec, height=height, phasecentre=vis.phasecentre) scr = numpy.angle(gt.gain[0, :, 0, 0, 0]) wt = gt.weight[0, :, 0, 0, 0] for ant in range(nant): pp0 = pp[ant][0:2] worldloc = [pp0[0], pp0[1], ha, 1e8] pixloc = newscreen.wcs.wcs_world2pix([worldloc], 0)[0].astype('int') assert pixloc[0] >= 0 assert pixloc[0] < nx assert pixloc[1] >= 0 assert pixloc[1] < ny newscreen.data[pixloc[3], pixloc[2], pixloc[1], pixloc[0]] += wt[ant] * scr[ant] weights.data[pixloc[3], pixloc[2], pixloc[1], pixloc[0]] += wt[ant] if wt[ant] == 0.0: number_no_weight += 1 if number_no_weight > 0: print("grid_gaintable_to_screen: %d pierce points are have no weight" % (number_no_weight)) log.warning( "grid_gaintable_to_screen: %d pierce points are have no weight" % (number_no_weight)) newscreen.data[weights.data > 0.0] = newscreen.data[ weights.data > 0.0] / weights.data[weights.data > 0.0] return newscreen, weights
def test_mpccal_MPCCAL_manysources_subimages(self): self.actualSetup() model = create_empty_image_like(self.theta_list[0].image) if arlexecute.using_dask: progress = None else: progress = self.progress future_vis = arlexecute.scatter(self.all_skymodel_noniso_vis) future_model = arlexecute.scatter(model) future_theta_list = arlexecute.scatter(self.theta_list) result = mpccal_skymodel_list_arlexecute_workflow( future_vis, future_model, future_theta_list, mpccal_progress=progress, nmajor=5, context='2d', algorithm='hogbom', scales=[0, 3, 10], fractional_threshold=0.3, threshold=0.2, gain=0.1, niter=1000, psf_support=256, deconvolve_facets=8, deconvolve_overlap=8, deconvolve_taper='tukey') (self.theta_list, residual) = arlexecute.compute(result, sync=True) combined_model = calculate_skymodel_equivalent_image(self.theta_list) psf_obs = invert_list_arlexecute_workflow( [self.all_skymodel_noniso_vis], [model], context='2d', dopsf=True) result = restore_list_arlexecute_workflow([combined_model], psf_obs, [(residual, 0.0)]) result = arlexecute.compute(result, sync=True) if self.persist: export_image_to_fits( residual, arl_path('test_results/test_mpccal_no_edge_residual.fits')) if self.persist: export_image_to_fits( result[0], arl_path('test_results/test_mpccal_no_edge_restored.fits')) if self.persist: export_image_to_fits( combined_model, arl_path('test_results/test_mpccal_no_edge_deconvolved.fits')) recovered_mpccal_components = find_skycomponents(result[0], fwhm=2, threshold=0.32, npixels=12) def max_flux(elem): return numpy.max(elem.flux) recovered_mpccal_components = sorted(recovered_mpccal_components, key=max_flux, reverse=True) assert recovered_mpccal_components[ 0].name == 'Segment 8', recovered_mpccal_components[0].name assert numpy.abs(recovered_mpccal_components[0].flux[0, 0] - 7.773751416364857) < 1e-7, \ recovered_mpccal_components[0].flux[0, 0] newscreen = create_empty_image_like(self.screen) gaintables = [th.gaintable for th in self.theta_list] newscreen, weights = grid_gaintable_to_screen( self.all_skymodel_noniso_blockvis, gaintables, newscreen) if self.persist: export_image_to_fits( newscreen, arl_path('test_results/test_mpccal_no_edge_screen.fits')) if self.persist: export_image_to_fits( weights, arl_path( 'test_results/test_mpccal_no_edge_screenweights.fits')) arlexecute.close()
def create_awterm_convolutionfunction(im, make_pb=None, nw=1, wstep=1e15, oversampling=8, support=6, use_aaf=True, maxsupport=512): """ Fill AW projection kernel into a GridData. :param im: Image template :param make_pb: Function to make the primary beam model image :param nw: Number of w planes :param wstep: Step in w (wavelengths) :param oversampling: Oversampling of the convolution function in uv space :return: griddata correction Image, griddata kernel as GridData """ d2r = numpy.pi / 180.0 # We only need the griddata correction function for the PSWF so we make # it for the shape of the image nchan, npol, ony, onx = im.data.shape assert isinstance(im, Image) # Calculate the template convolution kernel. cf = create_convolutionfunction_from_image(im, oversampling=oversampling, support=support) cf_shape = list(cf.data.shape) cf_shape[2] = nw cf.data = numpy.zeros(cf_shape).astype('complex') cf.grid_wcs.wcs.crpix[4] = nw // 2 + 1.0 cf.grid_wcs.wcs.cdelt[4] = wstep cf.grid_wcs.wcs.ctype[4] = 'WW' if numpy.abs(wstep) > 0.0: w_list = cf.grid_wcs.sub([5]).wcs_pix2world(range(nw), 0)[0] else: w_list = [0.0] assert isinstance(oversampling, int) assert oversampling > 0 nx = max(maxsupport, 2 * oversampling * support) ny = max(maxsupport, 2 * oversampling * support) qnx = nx // oversampling qny = ny // oversampling cf.data[...] = 0.0 subim = copy_image(im) ccell = onx * numpy.abs(d2r * subim.wcs.wcs.cdelt[0]) / qnx subim.data = numpy.zeros([nchan, npol, qny, qnx]) subim.wcs.wcs.cdelt[0] = -ccell / d2r subim.wcs.wcs.cdelt[1] = +ccell / d2r subim.wcs.wcs.crpix[0] = qnx // 2 + 1.0 subim.wcs.wcs.crpix[1] = qny // 2 + 1.0 if use_aaf: this_pswf_gcf, _ = create_pswf_convolutionfunction(subim, oversampling=1, support=6) norm = 1.0 / this_pswf_gcf.data else: norm = 1.0 if make_pb is not None: pb = make_pb(subim) rpb, footprint = reproject_image(pb, subim.wcs, shape=subim.shape) rpb.data[footprint.data < 1e-6] = 0.0 norm *= rpb.data # We might need to work with a larger image padded_shape = [nchan, npol, ny, nx] thisplane = copy_image(subim) thisplane.data = numpy.zeros(thisplane.shape, dtype='complex') for z, w in enumerate(w_list): thisplane.data[...] = 0.0 + 0.0j thisplane = create_w_term_like(thisplane, w, dopol=True) thisplane.data *= norm paddedplane = pad_image(thisplane, padded_shape) paddedplane = fft_image(paddedplane) ycen, xcen = ny // 2, nx // 2 for y in range(oversampling): ybeg = y + ycen + (support * oversampling) // 2 - oversampling // 2 yend = y + ycen - (support * oversampling) // 2 - oversampling // 2 vv = range(ybeg, yend, -oversampling) for x in range(oversampling): xbeg = x + xcen + (support * oversampling) // 2 - oversampling // 2 xend = x + xcen - (support * oversampling) // 2 - oversampling // 2 uu = range(xbeg, xend, -oversampling) for chan in range(nchan): for pol in range(npol): cf.data[chan, pol, z, y, x, :, :] = paddedplane.data[ chan, pol, :, :][vv, :][:, uu] cf.data /= numpy.sum( numpy.real(cf.data[0, 0, nw // 2, oversampling // 2, oversampling // 2, :, :])) cf.data = numpy.conjugate(cf.data) if use_aaf: pswf_gcf, _ = create_pswf_convolutionfunction(im, oversampling=1, support=6) else: pswf_gcf = create_empty_image_like(im) pswf_gcf.data[...] = 1.0 return pswf_gcf, cf
def create_awterm_convolutionfunction(im, make_pb=None, nw=1, wstep=1e15, oversampling=8, support=6, use_aaf=True, maxsupport=512, **kwargs): """ Fill AW projection kernel into a GridData. :param im: Image template :param make_pb: Function to make the primary beam model image (hint: use a partial) :param nw: Number of w planes :param wstep: Step in w (wavelengths) :param oversampling: Oversampling of the convolution function in uv space :return: griddata correction Image, griddata kernel as GridData """ d2r = numpy.pi / 180.0 # We only need the griddata correction function for the PSWF so we make # it for the shape of the image nchan, npol, ony, onx = im.data.shape assert isinstance(im, Image) # Calculate the template convolution kernel. cf = create_convolutionfunction_from_image(im, oversampling=oversampling, support=support) cf_shape = list(cf.data.shape) cf_shape[2] = nw cf.data = numpy.zeros(cf_shape).astype('complex') cf.grid_wcs.wcs.crpix[4] = nw // 2 + 1.0 cf.grid_wcs.wcs.cdelt[4] = wstep cf.grid_wcs.wcs.ctype[4] = 'WW' if numpy.abs(wstep) > 0.0: w_list = cf.grid_wcs.sub([5]).wcs_pix2world(range(nw), 0)[0] else: w_list = [0.0] assert isinstance(oversampling, int) assert oversampling > 0 nx = max(maxsupport, 2 * oversampling * support) ny = max(maxsupport, 2 * oversampling * support) qnx = nx // oversampling qny = ny // oversampling cf.data[...] = 0.0 subim = copy_image(im) ccell = onx * numpy.abs(d2r * subim.wcs.wcs.cdelt[0]) / qnx subim.data = numpy.zeros([nchan, npol, qny, qnx]) subim.wcs.wcs.cdelt[0] = -ccell / d2r subim.wcs.wcs.cdelt[1] = +ccell / d2r subim.wcs.wcs.crpix[0] = qnx // 2 + 1.0 subim.wcs.wcs.crpix[1] = qny // 2 + 1.0 if use_aaf: this_pswf_gcf, _ = create_pswf_convolutionfunction(subim, oversampling=1, support=6) norm = 1.0 / this_pswf_gcf.data else: norm = 1.0 if make_pb is not None: pb = make_pb(subim) rpb, footprint = reproject_image(pb, subim.wcs, shape=subim.shape) rpb.data[footprint.data < 1e-6] = 0.0 norm *= rpb.data # We might need to work with a larger image padded_shape = [nchan, npol, ny, nx] thisplane = copy_image(subim) thisplane.data = numpy.zeros(thisplane.shape, dtype='complex') for z, w in enumerate(w_list): thisplane.data[...] = 0.0 + 0.0j thisplane = create_w_term_like(thisplane, w, dopol=True) thisplane.data *= norm paddedplane = pad_image(thisplane, padded_shape) paddedplane = fft_image(paddedplane) ycen, xcen = ny // 2, nx // 2 for y in range(oversampling): ybeg = y + ycen + (support * oversampling) // 2 - oversampling // 2 yend = y + ycen - (support * oversampling) // 2 - oversampling // 2 # vv = range(ybeg, yend, -oversampling) for x in range(oversampling): xbeg = x + xcen + (support * oversampling) // 2 - oversampling // 2 xend = x + xcen - (support * oversampling) // 2 - oversampling // 2 # uu = range(xbeg, xend, -oversampling) cf.data[..., z, y, x, :, :] = paddedplane.data[..., ybeg:yend:-oversampling, xbeg:xend:-oversampling] # for chan in range(nchan): # for pol in range(npol): # cf.data[chan, pol, z, y, x, :, :] = paddedplane.data[chan, pol, :, :][vv, :][:, uu] cf.data /= numpy.sum( numpy.real(cf.data[0, 0, nw // 2, oversampling // 2, oversampling // 2, :, :])) cf.data = numpy.conjugate(cf.data) #==================================== #Use ASKAPSoft routine to crop the support size crop_ASKAPSOft_like = True if crop_ASKAPSOft_like: #Hardcode the cellsize: 1 / FOV #uv_cellsize = 57.3;#N=1200 pixel and pixelsize is 3 arcseconds #uv_cellsize = 43.97;#N=1600 pixel and pixelsize is 3 arcseconds #uv_cellsize = 114.6;#N=1800 pixel with 1 arcsecond pixelsize #uv_cellsize = 57.3;#N=1800 pixel with 2 arcsecond pixelsize #uv_cellsize = 1145.91509915;#N=1800 pixxel with 0.1 arcsecond pixelsize #Get from **kwargs if kwargs is None: #Safe solution works for baselines up to > 100km and result in small kernels uv_cellsize = 1145.91509915 #N=1800 pixxel with 0.1 arcsecond pixelsize if 'UVcellsize' in kwargs.keys(): uv_cellsize = kwargs['UVcellsize'] #print(uv_cellsize); #Cutoff param in ASKAPSoft hardcoded as well ASKAPSoft_cutof = 0.1 wTheta_list = numpy.zeros(len(w_list)) for i in range(0, len(w_list)): if w_list[i] == 0: wTheta_list[i] = 0.9 #This is due to the future if statements cause if it is small, the kernel will be 3 which is a clear cutoff else: wTheta_list[i] = numpy.fabs( w_list[i]) / (uv_cellsize * uv_cellsize) kernel_size_list = [] #We rounded the kernels according to conventional rounding rules for i in range(0, len(wTheta_list)): #if wTheta_list[i] < 1: if wTheta_list[i] < 1: #Change to ASKAPSoft kernel_size_list.append(int(3.)) elif ASKAPSoft_cutof < 0.01: kernel_size_list.append(int(6 + 1.14 * wTheta_list[i])) else: kernel_size_list.append( int(numpy.sqrt(49 + wTheta_list[i] * wTheta_list[i]))) log.info('W-kernel w-terms:') log.info(w_list) log.info('Corresponding w-kernel sizes:') log.info(kernel_size_list) print(numpy.unique(kernel_size_list)) #print(kernel_size_list); crop_list = [] #another rounding according to conventional rounding rules for i in range(0, len(kernel_size_list)): if support - kernel_size_list[i] <= 0: crop_list.append(int(0)) else: crop_list.append(int((support - kernel_size_list[i]) / 2)) #Crop original suppor for i in range(0, nw): if crop_list[i] != 0: cf.data[0, 0, i, :, :, 0:crop_list[i], :] = 0 cf.data[0, 0, i, :, :, -crop_list[i]:, :] = 0 cf.data[0, 0, i, :, :, :, 0:crop_list[i]] = 0 cf.data[0, 0, i, :, :, :, -crop_list[i]:] = 0 else: pass #Plot #import matplotlib.pyplot as plt #cf.data[0,0,i,0,0,...][cf.data[0,0,i,0,0,...] != 0.] = 1+0.j; #plt.imshow(numpy.real(cf.data[0,0,i,0,0,...])) #plt.show(block=True) #plt.close(); #==================================== if use_aaf: pswf_gcf, _ = create_pswf_convolutionfunction(im, oversampling=1, support=6) else: pswf_gcf = create_empty_image_like(im) pswf_gcf.data[...] = 1.0 return pswf_gcf, cf
def create_vp_generic_numeric(model, pointingcentre=None, diameter=15.0, blockage=0.0, taper='gaussian', edge=0.03162278, zernikes=None, padding=4, use_local=True, rho=0.0, diff=0.0): """ Make an image like model and fill it with an analytical model of the primary beam The elements of the analytical model are: - dish, optionally blocked - Gaussian taper, default is -12dB at the edge - Offset to pointing centre (optional) - zernikes in a list of dictionaries. Each list element is of the form {"coeff":0.1, "noll":5}. See aotools for more details - Output image can be in RA, DEC coordinates or AZELGEO coordinates (the default). use_local=True means to use AZELGEO coordinates centered on 0deg 0deg. The dish is zero padded according to padding and FFT'ed to get the voltage pattern. :param model: :param pointingcentre: SkyCoord of desired pointing centre :param diameter: Diameter of dish in metres :param blockage: Blockage of dish in metres :param taper: "Gaussian" or None :param edge: Value of taper at the end of the dish (default corresponds to -12dB) :param zernikes: Zernikes to be applied as phase across the dish (see above) :param padding: Pad the image by this amount :param use_local: Use local frame (AZELGEO)? :return: """ beam = create_empty_image_like(model) nchan, npol, ny, nx = beam.shape padded_shape = [nchan, npol, padding * ny, padding * nx] padded_beam = pad_image(beam, padded_shape) padded_beam.data = numpy.zeros(padded_beam.data.shape, dtype='complex') _, _, pny, pnx = padded_beam.shape xfr = fft_image(padded_beam) cx, cy = xfr.wcs.sub(2).wcs.crpix[0] - 1, xfr.wcs.sub(2).wcs.crpix[1] - 1 for chan in range(nchan): # The frequency axis is the second to last in the beam frequency = xfr.wcs.sub(['spectral']).wcs_pix2world([chan], 0)[0] wavelength = const.c.to('m s^-1').value / frequency scalex = xfr.wcs.sub(2).wcs.cdelt[0] * wavelength scaley = xfr.wcs.sub(2).wcs.cdelt[1] * wavelength # xx, yy in metres xx, yy = numpy.meshgrid(scalex * (range(pnx) - cx), scaley * (range(pny) - cy)) # rr in metres rr = numpy.sqrt(xx**2 + yy**2) for pol in range(npol): xfr.data[chan, pol, ...] = tapered_disk(rr, diameter / 2.0, blockage=blockage / 2.0, edge=edge, taper=taper) if pointingcentre is not None: # Correct for pointing centre pcx, pcy = pointingcentre.to_pixel(padded_beam.wcs, origin=0) pxx, pyy = numpy.meshgrid((range(pnx) - cx), (range(pny) - cy)) phase = 2 * numpy.pi * ((pcx - cx) * pxx / float(pnx) + (pcy - cy) * pyy / float(pny)) for pol in range(npol): xfr.data[chan, pol, ...] *= numpy.exp(1j * phase) if isinstance(zernikes, collections.Iterable): try: import aotools except ModuleNotFoundError: raise ModuleNotFoundError("aotools is not installed") ndisk = numpy.ceil(numpy.abs(diameter / scalex)).astype('int')[0] ndisk = 2 * ((ndisk + 1) // 2) phase = numpy.zeros([ndisk, ndisk]) for zernike in zernikes: phase = zernike['coeff'] * aotools.functions.zernike( zernike['noll'], ndisk) # import matplotlib.pyplot as plt # plt.clf() # plt.imshow(phase) # plt.colorbar() # plt.show() # blc = pnx // 2 - ndisk // 2 trc = pnx // 2 + ndisk // 2 for pol in range(npol): xfr.data[chan, pol, blc:trc, blc:trc] = xfr.data[chan, pol, blc:trc, blc:trc] * numpy.exp(1j * phase) padded_beam = fft_image(xfr, padded_beam) # Undo padding beam = create_empty_image_like(model) beam.data = padded_beam.data[..., (pny // 2 - ny // 2):(pny // 2 + ny // 2), (pnx // 2 - nx // 2):(pnx // 2 + nx // 2)] for chan in range(nchan): beam.data[chan, ...] /= numpy.max(numpy.abs(beam.data[chan, ...])) set_pb_header(beam, use_local=use_local) return beam
def ingest_visibility(self, freq=None, chan_width=None, times=None, add_errors=False, block=True, bandpass=False): if freq is None: freq = [1e8] if chan_width is None: chan_width = [1e6] if times is None: times = (numpy.pi / 12.0) * numpy.linspace(-3.0, 3.0, 5) lowcore = create_named_configuration('LOWBD2', rmax=750.0) frequency = numpy.array(freq) channel_bandwidth = numpy.array(chan_width) phasecentre = SkyCoord(ra=+180.0 * u.deg, dec=-60.0 * u.deg, frame='icrs', equinox='J2000') if block: vt = create_blockvisibility( lowcore, times, frequency, channel_bandwidth=channel_bandwidth, weight=1.0, phasecentre=phasecentre, polarisation_frame=PolarisationFrame("stokesI")) else: vt = create_visibility( lowcore, times, frequency, channel_bandwidth=channel_bandwidth, weight=1.0, phasecentre=phasecentre, polarisation_frame=PolarisationFrame("stokesI")) cellsize = 0.001 model = create_image_from_visibility( vt, npixel=self.npixel, cellsize=cellsize, npol=1, frequency=frequency, phasecentre=phasecentre, polarisation_frame=PolarisationFrame("stokesI")) nchan = len(self.frequency) flux = numpy.array(nchan * [[100.0]]) facets = 4 rpix = model.wcs.wcs.crpix - 1.0 spacing_pixels = self.npixel // facets centers = [-1.5, -0.5, 0.5, 1.5] comps = list() for iy in centers: for ix in centers: p = int(round(rpix[0] + ix * spacing_pixels * numpy.sign(model.wcs.wcs.cdelt[0]))), \ int(round(rpix[1] + iy * spacing_pixels * numpy.sign(model.wcs.wcs.cdelt[1]))) sc = pixel_to_skycoord(p[0], p[1], model.wcs, origin=1) comp = create_skycomponent( direction=sc, flux=flux, frequency=frequency, polarisation_frame=PolarisationFrame("stokesI")) comps.append(comp) if block: predict_skycomponent_visibility(vt, comps) else: predict_skycomponent_visibility(vt, comps) insert_skycomponent(model, comps) self.comps = comps self.model = copy_image(model) self.empty_model = create_empty_image_like(model) export_image_to_fits( model, '%s/test_pipeline_functions_model.fits' % (self.dir)) if add_errors: # These will be the same for all calls numpy.random.seed(180555) gt = create_gaintable_from_blockvisibility(vt) gt = simulate_gaintable(gt, phase_error=1.0, amplitude_error=0.0) vt = apply_gaintable(vt, gt) if bandpass: bgt = create_gaintable_from_blockvisibility(vt, timeslice=1e5) bgt = simulate_gaintable(bgt, phase_error=0.01, amplitude_error=0.01, smooth_channels=4) vt = apply_gaintable(vt, bgt) return vt
def test_create_empty_image_like(self): emptyimage = create_empty_image_like(self.m31image) assert emptyimage.shape == self.m31image.shape assert numpy.max(numpy.abs(emptyimage.data)) == 0.0