def create_low_test_beam(model: Image, use_local=True) -> Image: """Create a test power beam for LOW using an image from OSKAR This is not fit for anything except the most basic testing. It does not include any form of elevation/pa dependence. :param model: Template image :return: Image """ beam = import_image_from_fits( rascil_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.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) 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[footprint.data <= 0.0] = 0.0 for pol in range(npol): reprojected_beam.data[chan, pol, :, :] = reprojected_beam2d.data[:, :] set_pb_header(reprojected_beam, use_local=use_local) return reprojected_beam
def test_reproject(self): # Reproject an image cellsize = 1.5 * self.cellsize newwcs = self.m31image.wcs.deepcopy() newwcs.wcs.cdelt[0] = -cellsize newwcs.wcs.cdelt[1] = +cellsize newshape = numpy.array(self.m31image.data.shape) newshape[2] /= 1.5 newshape[3] /= 1.5 newimage, footprint = reproject_image(self.m31image, newwcs, shape=newshape)
def convert_azelvp_to_radec(vp, im, pa): """ Convert AZELGEO image to image coords at specific parallactic angle :param pb: Primary beam or voltagee pattern :param im: Template image :param pa: Parallactic angle (radians) :return: """ vp = scale_and_rotate_image(vp, angle=pa) vp.wcs.wcs.crval[0] = im.wcs.wcs.crval[0] vp.wcs.wcs.crval[1] = im.wcs.wcs.crval[1] vp.wcs.wcs.ctype[0] = im.wcs.wcs.ctype[0] vp.wcs.wcs.ctype[1] = im.wcs.wcs.ctype[1] rvp, footprint = reproject_image(vp, im.wcs, shape=im.shape) rvp.data[footprint.data < 1e-6] = 0.0 return rvp
def invert_timeslice_single(vis: Visibility, im: Image, dopsf, normalize=True, remove=True, gcfcf=None, **kwargs) -> (Image, numpy.ndarray): """Process single time slice Extracted for re-use in parallel version The w-term can be viewed as a time-variable distortion. Approximating the array as instantaneously co-planar, we have that w can be expressed in terms of u,v: .. math:: w = a u + b v Transforming to a new coordinate system: .. math:: l' = l + a ( \\sqrt{1-l^2-m^2}-1)) .. math:: m' = m + b ( \\sqrt{1-l^2-m^2}-1)) Ignoring changes in the normalisation term, we have: .. math:: V(u,v,w) =\\int \\frac{ I(l',m')} { \\sqrt{1-l'^2-m'^2}} e^{-2 \\pi j (ul'+um')} dl' dm' :param vis: Visibility to be inverted :param im: image template (not changed) :param dopsf: Make the psf instead of the dirty image :param gcfcf: (Grid correction function, convolution function) :param normalize: Normalize by the sum of weights (True) :returns: image, sum of weights """ assert isinstance(vis, Visibility), vis assert image_is_canonical(im) uvw = vis.uvw vis, p, q = fit_uvwplane(vis, remove=remove) workimage, sumwt = invert_2d(vis, im, dopsf, normalize=normalize, gcfcf=gcfcf, **kwargs) # Work image is distorted. We describe the distortion by putting the olbiquity parameters in # the wcs. The output image should be described as having zero olbiquity parameters. if numpy.abs(p) > 1e-7 or numpy.abs(q) > 1e-7: # Note that this has to be zero relative in first element, one relative in second!!!! workimage.wcs.wcs.set_pv([(0, 1, -p), (0, 2, -q)]) finalimage, footprint = reproject_image(workimage, im.wcs, im.shape) finalimage.data[footprint.data <= 0.0] = 0.0 finalimage.wcs.wcs.set_pv([(0, 1, 0.0), (0, 2, 0.0)]) if remove: vis.data['uvw'][...] = uvw return finalimage, sumwt else: if remove: vis.data['uvw'][...] = uvw return workimage, sumwt
def predict_timeslice_single(vis: Visibility, model: Image, predict=predict_2d, remove=True, gcfcf=None, **kwargs) -> Visibility: """ Predict using a single time slices. This fits a single plane and corrects the image geometry. The w-term can be viewed as a time-variable distortion. Approximating the array as instantaneously co-planar, we have that w can be expressed in terms of u,v: .. math:: w = a u + b v Transforming to a new coordinate system: .. math:: l' = l + a ( \\sqrt{1-l^2-m^2}-1)) .. math:: m' = m + b ( \\sqrt{1-l^2-m^2}-1)) Ignoring changes in the normalisation term, we have: .. math:: V(u,v,w) =\\int \\frac{ I(l',m')} { \\sqrt{1-l'^2-m'^2}} e^{-2 \\pi j (ul'+um')} dl' dm' :param vis: Visibility to be predicted :param model: model image :param predict: :param remove: Remove fitted w (so that wprojection will do the right thing) :param gcfcf: (Grid correction function, convolution function) :return: resulting visibility (in place works) """ assert image_is_canonical(model) assert isinstance(vis, Visibility), vis vis.data['vis'][...] = 0.0 # Fit and remove best fitting plane for this slice uvw = vis.uvw avis, p, q = fit_uvwplane(vis, remove=remove) # We want to describe work image as distorted. We describe the distortion by putting # the olbiquity parameters in the wcs. The input model should be described as having # zero olbiquity parameters. # Note that this has to be zero relative in first element, one relative in second!!! if numpy.abs(p) > 1e-7 or numpy.abs(q) > 1e-7: newwcs = model.wcs.deepcopy() newwcs.wcs.set_pv([(0, 1, -p), (0, 2, -q)]) workimage, footprintimage = reproject_image(model, newwcs, shape=model.shape) workimage.data[footprintimage.data <= 0.0] = 0.0 workimage.wcs.wcs.set_pv([(0, 1, -p), (0, 2, -q)]) # Now we can do the predict vis = predict(avis, workimage, gcfcf=gcfcf, **kwargs) else: vis = predict(avis, model, gcfcf=gcfcf, **kwargs) if remove: avis.data['uvw'][...] = uvw return vis
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 (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) 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