def convert_griddata_to_image(gd): """ Convert griddata to an image :param gd: :return: """ return create_image_from_array(gd.data, gd.grid_wcs, gd.polarisation_frame)
def convert_convolutionfunction_to_image(cf): """ Convert ConvolutionFunction to an image :param cf: :return: """ return create_image_from_array(cf.data, cf.grid_wcs, cf.polarisation_frame)
def create_box_convolutionfunction(im, oversampling=1, support=1): """ Fill a box car function into a ConvolutionFunction Also returns the griddata correction function as an image :param im: Image template :param oversampling: Oversampling of the convolution function in uv space :return: griddata correction Image, griddata kernel as ConvolutionFunction """ assert isinstance(im, Image) cf = create_convolutionfunction_from_image(im, oversampling=1, support=4) nchan, npol, _, _ = im.shape cf.data[...] = 0.0 + 0.0j cf.data[..., 2, 2] = 1.0 + 0.0j # Now calculate the griddata correction function as an image with the same coordinates as the image # which is necessary so that the correction function can be applied directly to the image nchan, npol, ny, nx = im.data.shape nu = numpy.abs(coordinates(nx)) gcf1d = numpy.sinc(nu) gcf = numpy.outer(gcf1d, gcf1d) gcf = 1.0 / gcf gcf_data = numpy.zeros_like(im.data) gcf_data[...] = gcf[numpy.newaxis, numpy.newaxis, ...] gcf_image = create_image_from_array(gcf_data, cf.projection_wcs, im.polarisation_frame) return gcf_image, cf
def image_gather_channels(image_list: List[Image], im: Image = None, subimages=0) -> Image: """Gather a list of subimages back into an image using the channel_iterator If the template image is not given then it will be formed assuming that the list has been generated by image_scatter_channels with subimages = number of channels :param image_list: List of subimages :param im: Output image :param subimages: Number of image partitions on each axis (2) :return: list of subimages """ if im is None: nchan = len(image_list) _, npol, ny, nx = image_list[0].shape im_shape = nchan, npol, ny, ny im = create_image_from_array( numpy.zeros(im_shape, dtype=image_list[0].data.dtype), image_list[0].wcs, image_list[0].polarisation_frame) assert image_is_canonical(im) if subimages == 0: subimages = len(image_list) for i, slab in enumerate(image_channel_iter(im, subimages=subimages)): slab.data[...] = image_list[i].data[...] return im
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 create_pswf_convolutionfunction(im, oversampling=8, support=6): """ Fill an Anti-Aliasing filter into a ConvolutionFunction Fill the Prolate Spheroidal Wave Function into a GriData with the specified oversampling. Only the inner non-zero part is retained Also returns the griddata correction function as an image :param im: Image template :param oversampling: Oversampling of the convolution function in uv space :return: griddata correction Image, griddata kernel as ConvolutionFunction """ assert isinstance(im, Image), im # Calculate the convolution kernel. We oversample in u,v space by the factor oversampling cf = create_convolutionfunction_from_image(im, oversampling=oversampling, support=support) kernel = numpy.zeros([oversampling, support]) for grid in range(support): for subsample in range(oversampling): nu = ((grid - support // 2) - (subsample - oversampling // 2) / oversampling) kernel[subsample, grid] = grdsf([nu / (support // 2)])[1] kernel /= numpy.sum(numpy.real(kernel[oversampling // 2, :])) nchan, npol, _, _ = im.shape cf.data = numpy.zeros( [nchan, npol, 1, oversampling, oversampling, support, support]).astype('complex') for y in range(oversampling): for x in range(oversampling): cf.data[:, :, 0, y, x, :, :] = numpy.outer(kernel[y, :], kernel[x, :])[numpy.newaxis, numpy.newaxis, ...] norm = numpy.sum(numpy.real(cf.data[0, 0, 0, 0, 0, :, :])) cf.data /= norm # Now calculate the griddata correction function as an image with the same coordinates as the image # which is necessary so that the correction function can be applied directly to the image nchan, npol, ny, nx = im.data.shape nu = numpy.abs(2.0 * coordinates(nx)) gcf1d = grdsf(nu)[0] gcf = numpy.outer(gcf1d, gcf1d) gcf[gcf > 0.0] = gcf.max() / gcf[gcf > 0.0] gcf_data = numpy.zeros_like(im.data) gcf_data[...] = gcf[numpy.newaxis, numpy.newaxis, ...] gcf_image = create_image_from_array(gcf_data, cf.projection_wcs, im.polarisation_frame) return gcf_image, cf
def image_voronoi_iter(im: Image, components: Skycomponent) -> collections.Iterable: """Iterate through Voronoi decomposition, returning a generator yielding fullsize images :param im: Image :param components: Components to define Voronoi decomposition """ if len(components) == 1: mask = numpy.ones(im.data.shape) yield create_image_from_array(mask, wcs=im.wcs, polarisation_frame=im.polarisation_frame) else: vor, vertex_array = voronoi_decomposition(im, components) nregions = numpy.max(vertex_array) + 1 for region in range(nregions): mask = numpy.zeros(im.data.shape) mask[(vertex_array == region)[numpy.newaxis, numpy.newaxis, ...]] = 1.0 yield create_image_from_array( mask, wcs=im.wcs, polarisation_frame=im.polarisation_frame)
def fft_griddata_to_image(griddata, gcf=None, imaginary=False): """ FFT griddata after applying gcf :param griddata: :param gcf: Grid correction image :return: """ projected = numpy.sum(griddata.data, axis=2) ny, nx = projected.data.shape[-2], projected.data.shape[-1] if gcf is None: im_data = ifft(projected) * float(nx) * float(ny) else: im_data = ifft(projected) * gcf.data * float(nx) * float(ny) im_real = create_image_from_array(im_data.real, griddata.projection_wcs, griddata.polarisation_frame) if imaginary: im_imag = create_image_from_array(im_data.imag, griddata.projection_wcs, griddata.polarisation_frame) return im_real, im_imag else: return im_real
def image_channel_iter(im: Image, subimages=1) -> collections.Iterable: """Create a image_channel_iter generator, returning images The WCS is adjusted appropriately for each raster element. Hence this is a coordinate-aware way to iterate through an image. Provided we don't break reference semantics, memory should be conserved To update the image in place:: for r in image_channel_iter(im, subimages=nchan): r.data[...] = numpy.sqrt(r.data[...]) :param im: Image :param subimages: Number of subimages :returns: Generator of images See also :py:func:`rascil.processing_components.image.image_gather_channels` :py:func:`rascil.processing_components.image.image_scatter_channels` """ assert image_is_canonical(im) nchan, npol, ny, nx = im.shape assert subimages <= nchan, "More subimages %d than channels %d" % ( subimages, nchan) step = nchan // subimages channels = numpy.array(range(0, nchan, step), dtype='int') assert len( channels ) == subimages, "subimages %d does not match length of channels %d" % ( subimages, len(channels)) for i, channel in enumerate(channels): if i + 1 < len(channels): channel_max = channels[i + 1] else: channel_max = nchan # Adjust WCS wcs = im.wcs.deepcopy() wcs.wcs.crpix[3] -= channel # Yield image from slice (reference!) yield create_image_from_array(im.data[channel:channel_max, ...], wcs, im.polarisation_frame)
def fft_griddata_to_image(griddata, gcf=None): """ FFT griddata after applying gcf If imaginary is true the data array is complex :param griddata: :param gcf: Grid correction image :return: """ projected = numpy.sum(griddata.data, axis=2) ny, nx = projected.data.shape[-2], projected.data.shape[-1] if gcf is None: im_data = ifft(projected) * float(nx) * float(ny) else: im_data = ifft(projected) * gcf.data * float(nx) * float(ny) return create_image_from_array(im_data, griddata.projection_wcs, griddata.polarisation_frame)
def setUp(self): from rascil.data_models.parameters import rascil_path self.dir = rascil_path('test_results') self.persist = os.getenv("RASCIL_PERSIST", False) self.niter = 1000 self.lowcore = create_named_configuration('LOWBD2-CORE') self.nchan = 5 self.times = (numpy.pi / 12.0) * numpy.linspace(-3.0, 3.0, 7) self.frequency = numpy.linspace(0.9e8, 1.1e8, self.nchan) self.channel_bandwidth = numpy.array(self.nchan * [self.frequency[1] - self.frequency[0]]) self.phasecentre = SkyCoord(ra=+0.0 * u.deg, dec=-45.0 * u.deg, frame='icrs', equinox='J2000') self.vis = create_visibility(self.lowcore, self.times, self.frequency, self.channel_bandwidth, phasecentre=self.phasecentre, weight=1.0, polarisation_frame=PolarisationFrame('stokesI'), zerow=True) self.vis.data['vis'] *= 0.0 # Create model self.test_model = create_low_test_image_from_gleam(npixel=512, cellsize=0.001, phasecentre=self.vis.phasecentre, frequency=self.frequency, channel_bandwidth=self.channel_bandwidth, flux_limit=1.0) beam = create_low_test_beam(self.test_model) if self.persist: export_image_to_fits(beam, "%s/test_deconvolve_mmclean_beam.fits" % self.dir) self.test_model.data *= beam.data if self.persist: export_image_to_fits(self.test_model, "%s/test_deconvolve_mmclean_model.fits" % self.dir) self.vis = predict_2d(self.vis, self.test_model) assert numpy.max(numpy.abs(self.vis.vis)) > 0.0 self.model = create_image_from_visibility(self.vis, npixel=512, cellsize=0.001, polarisation_frame=PolarisationFrame('stokesI')) self.dirty, sumwt = invert_2d(self.vis, self.model) self.psf, sumwt = invert_2d(self.vis, self.model, dopsf=True) if self.persist: export_image_to_fits(self.dirty, "%s/test_deconvolve_mmclean-dirty.fits" % self.dir) if self.persist: export_image_to_fits(self.psf, "%s/test_deconvolve_mmclean-psf.fits" % self.dir) window = numpy.ones(shape=self.model.shape, dtype=numpy.bool) window[..., 129:384, 129:384] = True self.innerquarter = create_image_from_array(window, self.model.wcs, polarisation_frame=PolarisationFrame('stokesI'))
def image_raster_iter(im: Image, facets=1, overlap=0, taper='flat', make_flat=False) -> collections.Iterable: """Create an image_raster_iter generator, returning images, optionally with overlaps The WCS is adjusted appropriately for each raster element. Hence this is a coordinate-aware way to iterate through an image. Provided we don't break reference semantics, memory should be conserved. However make_flat creates a new set of images and thus reference semantics dont hold. To update the image in place:: for r in image_raster_iter(im, facets=2): r.data[...] = numpy.sqrt(r.data[...]) If the overlap is greater than zero, we choose to keep all images the same size so the other ring of facets are ignored. So if facets=4 and overlap > 0 then the iterator returns (facets-2)**2 = 4 images. A taper is applied in the overlap regions. None implies a constant value, linear is a ramp, and quadratic is parabolic at the ends. :param im: Image :param facets: Number of image partitions on each axis (2) :param overlap: overlap in pixels :param taper: method of tapering at the edges: 'flat' or 'linear' or 'quadratic' or 'tukey' :param make_flat: Make the flat images :returns: Generator of images See also :py:func:`rascil.processing_components.image.image_gather_facets` :py:func:`rascil.processing_components.image.image_scatter_facets` """ assert image_is_canonical(im) nchan, npol, ny, nx = im.shape assert facets <= ny, "Cannot have more raster elements than pixels" assert facets <= nx, "Cannot have more raster elements than pixels" assert facets >= 1, "Facets cannot be zero or less" assert overlap >= 0, "Overlap must be zero or greater" if facets == 1: yield im else: assert overlap < (nx // facets), "Overlap in facets is too large" assert overlap < (ny // facets), "Overlap in facets is too large" # Step between facets sx = nx // facets + overlap sy = ny // facets + overlap # Size of facet dx = sx + overlap dy = sy + overlap # Step between facets sx = nx // facets + overlap sy = ny // facets + overlap # Size of facet dx = nx // facets + 2 * overlap dy = nx // facets + 2 * overlap def taper_linear(): t = numpy.ones(dx) ramp = numpy.arange(0, overlap).astype(float) / float(overlap) t[:overlap] = ramp t[(dx - overlap):dx] = 1.0 - ramp result = numpy.outer(t, t) return result def taper_quadratic(): t = numpy.ones(dx) ramp = numpy.arange(0, overlap).astype(float) / float(overlap) quadratic_ramp = numpy.ones(overlap) quadratic_ramp[0:overlap // 2] = 2.0 * ramp[0:overlap // 2]**2 quadratic_ramp[overlap // 2:] = 1 - 2.0 * ramp[overlap // 2:0:-1]**2 t[:overlap] = quadratic_ramp t[(dx - overlap):dx] = 1.0 - quadratic_ramp result = numpy.outer(t, t) return result def taper_tukey(): xs = numpy.arange(dx) / float(dx) r = 2 * overlap / dx t = [tukey_filter(x, r) for x in xs] result = numpy.outer(t, t) return result i = 0 for fy in range(facets): y = ny // 2 + sy * (fy - facets // 2) - overlap // 2 for fx in range(facets): x = nx // 2 + sx * (fx - facets // 2) - overlap // 2 if (x >= 0) and (x + dx) <= nx and (y >= 0) and (y + dy) <= ny: # Adjust WCS wcs = im.wcs.deepcopy() wcs.wcs.crpix[0] -= x wcs.wcs.crpix[1] -= y # yield image from slice (reference!) subim = create_image_from_array( im.data[..., y:y + dy, x:x + dx], wcs, im.polarisation_frame) if overlap > 0 and make_flat: flat = create_empty_image_like(subim) if taper == 'linear': flat.data[..., :, :] = taper_linear() elif taper == 'quadratic': flat.data[..., :, :] = taper_quadratic() elif taper == 'tukey': flat.data[..., :, :] = taper_tukey() else: flat.data[...] = 1.0 yield flat else: yield subim i += 1
def convert_image_to_kernel(im: Image, oversampling, kernelwidth): """ Convert an image to a griddata kernel :param im: Image to be converted :param oversampling: Oversampling of Image spatially :param kernelwidth: Kernel width to be extracted :return: numpy.ndarray[nchan, npol, oversampling, oversampling, kernelwidth, kernelwidth] """ naxis = len(im.shape) assert numpy.max(numpy.abs(im.data)) > 0.0, "Image is empty" nchan, npol, ny, nx = im.shape assert nx % oversampling == 0, "Oversampling must be even" assert ny % oversampling == 0, "Oversampling must be even" assert kernelwidth < nx and kernelwidth < ny, "Specified kernel width %d too large" assert im.wcs.wcs.ctype[ 0] == 'UU', 'Axis type %s inappropriate for construction of kernel' % im.wcs.wcs.ctype[ 0] assert im.wcs.wcs.ctype[ 1] == 'VV', 'Axis type %s inappropriate for construction of kernel' % im.wcs.wcs.ctype[ 1] newwcs = WCS(naxis=naxis + 2) for axis in range(2): newwcs.wcs.ctype[axis] = im.wcs.wcs.ctype[axis] newwcs.wcs.crpix[axis] = kernelwidth // 2 newwcs.wcs.crval[axis] = 0.0 newwcs.wcs.cdelt[axis] = im.wcs.wcs.cdelt[axis] * oversampling newwcs.wcs.ctype[axis + 2] = im.wcs.wcs.ctype[axis] newwcs.wcs.crpix[axis + 2] = oversampling // 2 newwcs.wcs.crval[axis + 2] = 0.0 newwcs.wcs.cdelt[axis + 2] = im.wcs.wcs.cdelt[axis] # Now do Stokes and Frequency newwcs.wcs.ctype[axis + 4] = im.wcs.wcs.ctype[axis + 2] newwcs.wcs.crpix[axis + 4] = im.wcs.wcs.crpix[axis + 2] newwcs.wcs.crval[axis + 4] = im.wcs.wcs.crval[axis + 2] newwcs.wcs.cdelt[axis + 4] = im.wcs.wcs.cdelt[axis + 2] newdata_shape = [ nchan, npol, oversampling, oversampling, kernelwidth, kernelwidth ] newdata = numpy.zeros(newdata_shape, dtype=im.data.dtype) assert oversampling * kernelwidth < ny assert oversampling * kernelwidth < nx ystart = ny // 2 - oversampling * kernelwidth // 2 xstart = nx // 2 - oversampling * kernelwidth // 2 yend = ny // 2 + oversampling * kernelwidth // 2 xend = nx // 2 + oversampling * kernelwidth // 2 for chan in range(nchan): for pol in range(npol): for y in range(oversampling): slicey = slice(yend + y, ystart + y, -oversampling) for x in range(oversampling): slicex = slice(xend + x, xstart + x, -oversampling) newdata[chan, pol, y, x, ...] = im.data[chan, pol, slicey, slicex] return create_image_from_array(newdata, newwcs, polarisation_frame=im.polarisation_frame)
filename = 'low_screen_%.1fr0_%.3frate.fits' % (r0, rate) my_screens = ArScreens.ArScreens(n, m, pscale, rate, lowparamcube, alpha_mag) my_screens.run(ntimes, verbose=True) from astropy.wcs import WCS nfreqwin = 1 npol = 1 frequency = [1e8] channel_bandwidth = [0.1e8] w = WCS(naxis=4) cellsize = pscale w.wcs.cdelt = [cellsize, cellsize, 1.0 / rate, channel_bandwidth[0]] w.wcs.crpix = [npixel // 2 + 1, npixel // 2 + 1, ntimes // 2 + 1, 1.0] w.wcs.ctype = ['XX', 'YY', 'TIME', 'FREQ'] w.wcs.crval = [0.0, 0.0, 0.0, frequency[0]] w.naxis = 4 w.wcs.radesys = 'ICRS' w.wcs.equinox = 2000.0 data = numpy.zeros([nfreqwin, ntimes, npixel, npixel]) for i, screen in enumerate(my_screens.screens[0]): data[:, i, ...] = screen[numpy.newaxis, ...] im = create_image_from_array( data, wcs=w, polarisation_frame=PolarisationFrame("stokesI")) print(im) export_image_to_fits(im, filename)
def create_test_image_from_s3(npixel=16384, polarisation_frame=PolarisationFrame("stokesI"), cellsize=0.000015, frequency=numpy.array([1e8]), channel_bandwidth=numpy.array([1e6]), phasecentre=None, fov=20, flux_limit=1e-3) -> Image: """Create MID test image from S3 The input catalog was generated at http://s-cubed.physics.ox.ac.uk/s3_sex using the following query:: Database: s3_sex SQL: select * from Galaxies where (pow(10,itot_151)*1000 > 1.0) and (right_ascension between -5 and 5) and (declination between -5 and 5);; Number of rows returned: 29966 For frequencies < 610MHz, there are three tables to use:: data/models/S3_151MHz_10deg.csv, use fov=10 data/models/S3_151MHz_20deg.csv, use fov=20 data/models/S3_151MHz_40deg.csv, use fov=40 For frequencies > 610MHz, there are three tables: data/models/S3_1400MHz_1mJy_10deg.csv, use flux_limit>= 1e-3 data/models/S3_1400MHz_100uJy_10deg.csv, use flux_limit < 1e-3 data/models/S3_1400MHz_1mJy_18deg.csv, use flux_limit>= 1e-3 data/models/S3_1400MHz_100uJy_18deg.csv, use flux_limit < 1e-3 The component spectral index is calculated from the 610MHz and 151MHz or 1400MHz and 610MHz, and then calculated for the specified frequencies. If polarisation_frame is not stokesI then the image will a polarised axis but the values will be zero. :param npixel: Number of pixels :param polarisation_frame: Polarisation frame (default PolarisationFrame("stokesI")) :param cellsize: cellsize in radians :param frequency: :param channel_bandwidth: Channel width (Hz) :param phasecentre: phasecentre (SkyCoord) :param fov: fov 10 | 20 | 40 :param flux_limit: Minimum flux (Jy) :return: Image """ check_data_directory() ras = [] decs = [] fluxes = [] if phasecentre is None: phasecentre = SkyCoord(ra=+180.0 * u.deg, dec=-60.0 * u.deg, frame='icrs', equinox='J2000') if polarisation_frame is None: polarisation_frame = PolarisationFrame("stokesI") npol = polarisation_frame.npol nchan = len(frequency) shape = [nchan, npol, npixel, npixel] w = WCS(naxis=4) # The negation in the longitude is needed by definition of RA, DEC w.wcs.cdelt = [ -cellsize * 180.0 / numpy.pi, cellsize * 180.0 / numpy.pi, 1.0, channel_bandwidth[0] ] w.wcs.crpix = [npixel // 2 + 1, npixel // 2 + 1, 1.0, 1.0] w.wcs.ctype = ["RA---SIN", "DEC--SIN", 'STOKES', 'FREQ'] w.wcs.crval = [phasecentre.ra.deg, phasecentre.dec.deg, 1.0, frequency[0]] w.naxis = 4 w.wcs.radesys = 'ICRS' w.wcs.equinox = 2000.0 model = create_image_from_array(numpy.zeros(shape), w, polarisation_frame=polarisation_frame) if numpy.max(frequency) > 6.1E8: if fov > 10: fovstr = '18' else: fovstr = '10' if flux_limit >= 1e-3: csvfilename = rascil_data_path('models/S3_1400MHz_1mJy_%sdeg.csv' % fovstr) else: csvfilename = rascil_data_path( 'models/S3_1400MHz_100uJy_%sdeg.csv' % fovstr) log.info('create_test_image_from_s3: Reading S3 sources from %s ' % csvfilename) else: assert fov in [ 10, 20, 40 ], "Field of view invalid: use one of %s" % ([10, 20, 40]) csvfilename = rascil_data_path('models/S3_151MHz_%ddeg.csv' % (fov)) log.info('create_test_image_from_s3: Reading S3 sources from %s ' % csvfilename) with open(csvfilename) as csvfile: readCSV = csv.reader(csvfile, delimiter=',') r = 0 for row in readCSV: # Skip first row if r > 0: ra = float(row[4]) + phasecentre.ra.deg dec = float(row[5]) + phasecentre.dec.deg if numpy.max(frequency) > 6.1E8: alpha = (float(row[11]) - float(row[10])) / numpy.log10( 1400.0 / 610.0) flux = numpy.power(10, float(row[10])) * numpy.power( frequency / 1.4e9, alpha) else: alpha = (float(row[10]) - float(row[9])) / numpy.log10( 610.0 / 151.0) flux = numpy.power(10, float(row[9])) * numpy.power( frequency / 1.51e8, alpha) if numpy.max(flux) > flux_limit: ras.append(ra) decs.append(dec) fluxes.append(flux) r += 1 csvfile.close() assert len(fluxes) > 0, "No sources found above flux limit %s" % flux_limit log.info('create_test_image_from_s3: %d sources read' % (len(fluxes))) p = w.sub(2).wcs_world2pix(numpy.array(ras), numpy.array(decs), 1) fluxes = numpy.array(fluxes) total_flux = numpy.sum(fluxes) ip = numpy.round(p).astype('int') ok = numpy.where((0 <= ip[0, :]) & (npixel > ip[0, :]) & (0 <= ip[1, :]) & (npixel > ip[1, :]))[0] ps = ip[:, ok] fluxes = fluxes[ok] actual_flux = numpy.sum(fluxes) log.info('create_test_image_from_s3: %d sources inside the image' % (ps.shape[1])) log.info( 'create_test_image_from_s3: average channel flux in S3 model = %.3f, actual average channel flux in ' 'image = %.3f' % (total_flux / float(nchan), actual_flux / float(nchan))) for chan in range(nchan): for iflux, flux in enumerate(fluxes): model.data[chan, 0, ps[1, iflux], ps[0, iflux]] = flux[chan] return model
def deconvolve_cube(dirty: Image, psf: Image, prefix='', **kwargs) -> (Image, Image): """ Clean using a variety of algorithms The algorithms available are: hogbom: Hogbom CLEAN See: Hogbom CLEAN A&A Suppl, 15, 417, (1974) hogbom-complex: Complex Hogbom CLEAN of stokesIQUV image msclean: MultiScale CLEAN See: Cornwell, T.J., Multiscale CLEAN (IEEE Journal of Selected Topics in Sig Proc, 2008 vol. 2 pp. 793-801) mfsmsclean, msmfsclean, mmclean: MultiScale Multi-Frequency See: U. Rau and T. J. Cornwell, “A multi-scale multi-frequency deconvolution algorithm for synthesis imaging in radio interferometry,” A&A 532, A71 (2011). For example:: comp, residual = deconvolve_cube(dirty, psf, niter=1000, gain=0.7, algorithm='msclean', scales=[0, 3, 10, 30], threshold=0.01) For the MFS clean, the psf must have number of channels >= 2 * nmoment :param dirty: Image dirty image :param psf: Image Point Spread Function :param window_shape: Window image (Bool) - clean where True :param mask: Window in the form of an image, overrides window_shape :param algorithm: Cleaning algorithm: 'msclean'|'hogbom'|'mfsmsclean' :param gain: loop gain (float) 0.7 :param threshold: Clean threshold (0.0) :param fractional_threshold: Fractional threshold (0.01) :param scales: Scales (in pixels) for multiscale ([0, 3, 10, 30]) :param nmoment: Number of frequency moments (default 3) :param findpeak: Method of finding peak in mfsclean: 'Algorithm1'|'ASKAPSoft'|'CASA'|'RASCIL', Default is RASCIL. :return: component image, residual image See also :py:func:`rascil.processing_components.arrays.cleaners.hogbom` :py:func:`rascil.processing_components.arrays.cleaners.hogbom_complex` :py:func:`rascil.processing_components.arrays.cleaners.msclean` :py:func:`rascil.processing_components.arrays.cleaners.msmfsclean` """ assert isinstance(dirty, Image), dirty assert image_is_canonical(dirty) assert isinstance(psf, Image), psf assert image_is_canonical(psf) window_shape = get_parameter(kwargs, 'window_shape', None) if window_shape == 'quarter': log.info("deconvolve_cube %s: window is inner quarter" % prefix) qx = dirty.shape[3] // 4 qy = dirty.shape[2] // 4 window = numpy.zeros_like(dirty.data) window[..., (qy + 1):3 * qy, (qx + 1):3 * qx] = 1.0 log.info( 'deconvolve_cube %s: Cleaning inner quarter of each sky plane' % prefix) elif window_shape == 'no_edge': edge = get_parameter(kwargs, 'window_edge', 16) nx = dirty.shape[3] ny = dirty.shape[2] window = numpy.zeros_like(dirty.data) window[..., (edge + 1):(ny - edge), (edge + 1):(nx - edge)] = 1.0 log.info( 'deconvolve_cube %s: Window omits %d-pixel edge of each sky plane' % (prefix, edge)) elif window_shape is None: log.info("deconvolve_cube %s: Cleaning entire image" % prefix) window = None else: raise ValueError("Window shape %s is not recognized" % window_shape) mask = get_parameter(kwargs, 'mask', None) if isinstance(mask, Image): if window is not None: log.warning( 'deconvolve_cube %s: Overriding window_shape with mask image' % (prefix)) window = mask.data psf_support = get_parameter(kwargs, 'psf_support', max(dirty.shape[2] // 2, dirty.shape[3] // 2)) if (psf_support <= psf.shape[2] // 2) and ( (psf_support <= psf.shape[3] // 2)): centre = [psf.shape[2] // 2, psf.shape[3] // 2] psf.data = psf.data[..., (centre[0] - psf_support):(centre[0] + psf_support), (centre[1] - psf_support):(centre[1] + psf_support)] log.info('deconvolve_cube %s: PSF support = +/- %d pixels' % (prefix, psf_support)) log.info('deconvolve_cube %s: PSF shape %s' % (prefix, str(psf.data.shape))) algorithm = get_parameter(kwargs, 'algorithm', 'msclean') if algorithm == 'msclean': log.info( "deconvolve_cube %s: Multi-scale clean of each polarisation and channel separately" % prefix) gain = get_parameter(kwargs, 'gain', 0.7) assert 0.0 < gain < 2.0, "Loop gain must be between 0 and 2" thresh = get_parameter(kwargs, 'threshold', 0.0) assert thresh >= 0.0 niter = get_parameter(kwargs, 'niter', 100) assert niter > 0 scales = get_parameter(kwargs, 'scales', [0, 3, 10, 30]) fracthresh = get_parameter(kwargs, 'fractional_threshold', 0.01) assert 0.0 < fracthresh < 1.0 comp_array = numpy.zeros_like(dirty.data) residual_array = numpy.zeros_like(dirty.data) for channel in range(dirty.data.shape[0]): for pol in range(dirty.data.shape[1]): if psf.data[channel, pol, :, :].max(): log.info( "deconvolve_cube %s: Processing pol %d, channel %d" % (prefix, pol, channel)) if window is None: comp_array[channel, pol, :, :], residual_array[channel, pol, :, :] = \ msclean(dirty.data[channel, pol, :, :], psf.data[channel, pol, :, :], None, gain, thresh, niter, scales, fracthresh, prefix) else: comp_array[channel, pol, :, :], residual_array[channel, pol, :, :] = \ msclean(dirty.data[channel, pol, :, :], psf.data[channel, pol, :, :], window[channel, pol, :, :], gain, thresh, niter, scales, fracthresh, prefix) else: log.info( "deconvolve_cube %s: Skipping pol %d, channel %d" % (prefix, pol, channel)) comp_image = create_image_from_array(comp_array, dirty.wcs, dirty.polarisation_frame) residual_image = create_image_from_array(residual_array, dirty.wcs, dirty.polarisation_frame) elif algorithm == 'msmfsclean' or algorithm == 'mfsmsclean' or algorithm == 'mmclean': findpeak = get_parameter(kwargs, "findpeak", 'RASCIL') log.info( "deconvolve_cube %s: Multi-scale multi-frequency clean of each polarisation separately" % prefix) nmoment = get_parameter(kwargs, "nmoment", 3) assert nmoment >= 1, "Number of frequency moments must be greater than or equal to one" nchan = dirty.shape[0] assert nchan > 2 * (nmoment - 1), "Require nchan %d > 2 * (nmoment %d - 1)" % ( nchan, 2 * (nmoment - 1)) dirty_taylor = calculate_image_frequency_moments(dirty, nmoment=nmoment) if nmoment > 1: psf_taylor = calculate_image_frequency_moments(psf, nmoment=2 * nmoment) else: psf_taylor = calculate_image_frequency_moments(psf, nmoment=1) psf_peak = numpy.max(psf_taylor.data) dirty_taylor.data /= psf_peak psf_taylor.data /= psf_peak log.info("deconvolve_cube %s: Shape of Dirty moments image %s" % (prefix, str(dirty_taylor.shape))) log.info("deconvolve_cube %s: Shape of PSF moments image %s" % (prefix, str(psf_taylor.shape))) gain = get_parameter(kwargs, 'gain', 0.7) assert 0.0 < gain < 2.0, "Loop gain must be between 0 and 2" thresh = get_parameter(kwargs, 'threshold', 0.0) assert thresh >= 0.0 niter = get_parameter(kwargs, 'niter', 100) assert niter > 0 scales = get_parameter(kwargs, 'scales', [0, 3, 10, 30]) fracthresh = get_parameter(kwargs, 'fractional_threshold', 0.1) assert 0.0 < fracthresh < 1.0 comp_array = numpy.zeros(dirty_taylor.data.shape) residual_array = numpy.zeros(dirty_taylor.data.shape) for pol in range(dirty_taylor.data.shape[1]): # Always use the Stokes I PSF if psf_taylor.data[0, 0, :, :].max(): log.info("deconvolve_cube %s: Processing pol %d" % (prefix, pol)) if window is None: comp_array[:, pol, :, :], residual_array[:, pol, :, :] = \ msmfsclean(dirty_taylor.data[:, pol, :, :], psf_taylor.data[:, 0, :, :], None, gain, thresh, niter, scales, fracthresh, findpeak, prefix) else: log.info( 'deconvolve_cube %s: Clean window has %d valid pixels' % (prefix, int(numpy.sum(window[0, pol])))) comp_array[:, pol, :, :], residual_array[:, pol, :, :] = \ msmfsclean(dirty_taylor.data[:, pol, :, :], psf_taylor.data[:, 0, :, :], window[0, pol, :, :], gain, thresh, niter, scales, fracthresh, findpeak, prefix) else: log.info("deconvolve_cube %s: Skipping pol %d" % (prefix, pol)) comp_image = create_image_from_array(comp_array, dirty_taylor.wcs, dirty.polarisation_frame) residual_image = create_image_from_array(residual_array, dirty_taylor.wcs, dirty.polarisation_frame) return_moments = get_parameter(kwargs, "return_moments", False) if not return_moments: log.info("deconvolve_cube %s: calculating spectral cubes" % prefix) comp_image = calculate_image_from_frequency_moments( dirty, comp_image) residual_image = calculate_image_from_frequency_moments( dirty, residual_image) else: log.info("deconvolve_cube %s: constructed moment cubes" % prefix) elif algorithm == 'hogbom': log.info( "deconvolve_cube %s: Hogbom clean of each polarisation and channel separately" % prefix) gain = get_parameter(kwargs, 'gain', 0.1) assert 0.0 < gain < 2.0, "Loop gain must be between 0 and 2" thresh = get_parameter(kwargs, 'threshold', 0.0) assert thresh >= 0.0 niter = get_parameter(kwargs, 'niter', 100) assert niter > 0 fracthresh = get_parameter(kwargs, 'fractional_threshold', 0.1) assert 0.0 < fracthresh < 1.0 comp_array = numpy.zeros(dirty.data.shape) residual_array = numpy.zeros(dirty.data.shape) for channel in range(dirty.data.shape[0]): for pol in range(dirty.data.shape[1]): if psf.data[channel, pol, :, :].max(): log.info( "deconvolve_cube %s: Processing pol %d, channel %d" % (prefix, pol, channel)) if window is None: comp_array[channel, pol, :, :], residual_array[channel, pol, :, :] = \ hogbom(dirty.data[channel, pol, :, :], psf.data[channel, pol, :, :], None, gain, thresh, niter, fracthresh, prefix) else: comp_array[channel, pol, :, :], residual_array[channel, pol, :, :] = \ hogbom(dirty.data[channel, pol, :, :], psf.data[channel, pol, :, :], window[channel, pol, :, :], gain, thresh, niter, fracthresh, prefix) else: log.info( "deconvolve_cube %s: Skipping pol %d, channel %d" % (prefix, pol, channel)) comp_image = create_image_from_array(comp_array, dirty.wcs, dirty.polarisation_frame) residual_image = create_image_from_array(residual_array, dirty.wcs, dirty.polarisation_frame) elif algorithm == 'hogbom-complex': log.info( "deconvolve_cube_complex: Hogbom-complex clean of each polarisation and channel separately" ) gain = get_parameter(kwargs, 'gain', 0.1) assert 0.0 < gain < 2.0, "Loop gain must be between 0 and 2" thresh = get_parameter(kwargs, 'threshold', 0.0) assert thresh >= 0.0 niter = get_parameter(kwargs, 'niter', 100) assert niter > 0 fracthresh = get_parameter(kwargs, 'fractional_threshold', 0.1) assert 0.0 <= fracthresh < 1.0 comp_array = numpy.zeros(dirty.data.shape) residual_array = numpy.zeros(dirty.data.shape) for channel in range(dirty.data.shape[0]): for pol in range(dirty.data.shape[1]): if pol == 0 or pol == 3: if psf.data[channel, pol, :, :].max(): log.info( "deconvolve_cube_complex: Processing pol %d, channel %d" % (pol, channel)) if window is None: comp_array[channel, pol, :, :], residual_array[channel, pol, :, :] = \ hogbom(dirty.data[channel, pol, :, :], psf.data[channel, pol, :, :], None, gain, thresh, niter, fracthresh) else: comp_array[channel, pol, :, :], residual_array[channel, pol, :, :] = \ hogbom(dirty.data[channel, pol, :, :], psf.data[channel, pol, :, :], window[channel, pol, :, :], gain, thresh, niter, fracthresh) else: log.info( "deconvolve_cube_complex: Skipping pol %d, channel %d" % (pol, channel)) if pol == 1: if psf.data[channel, 1:2, :, :].max(): log.info( "deconvolve_cube_complex: Processing pol 1 and 2, channel %d" % (channel)) if window is None: comp_array[channel, 1, :, :], comp_array[ channel, 2, :, :], residual_array[ channel, 1, :, :], residual_array[ channel, 2, :, :] = hogbom_complex( dirty.data[channel, 1, :, :], dirty.data[channel, 2, :, :], psf.data[channel, 1, :, :], psf.data[channel, 2, :, :], None, gain, thresh, niter, fracthresh) else: comp_array[channel, 1, :, :], comp_array[ channel, 2, :, :], residual_array[ channel, 1, :, :], residual_array[ channel, 2, :, :] = hogbom_complex( dirty.data[channel, 1, :, :], dirty.data[channel, 2, :, :], psf.data[channel, 1, :, :], psf.data[channel, 2, :, :], window[channel, pol, :, :], gain, thresh, niter, fracthresh) else: log.info( "deconvolve_cube_complex: Skipping pol 1 and 2, channel %d" % (channel)) if pol == 2: continue comp_image = create_image_from_array( comp_array, dirty.wcs, polarisation_frame=PolarisationFrame('stokesIQUV')) residual_image = create_image_from_array( residual_array, dirty.wcs, polarisation_frame=PolarisationFrame('stokesIQUV')) else: raise ValueError('deconvolve_cube %s: Unknown algorithm %s' % (prefix, algorithm)) return comp_image, residual_image
def create_low_test_skymodel_from_gleam(npixel=512, polarisation_frame=PolarisationFrame( "stokesI"), cellsize=0.000015, frequency=numpy.array([1e8]), channel_bandwidth=numpy.array([1e6]), phasecentre=None, kind='cubic', applybeam=True, flux_limit=0.1, flux_max=numpy.inf, flux_threshold=1.0, insert_method='Nearest', telescope='LOW') -> SkyModel: """Create LOW test skymodel from the GLEAM survey Stokes I is estimated from a cubic spline fit to the measured fluxes. The polarised flux is always zero. See http://www.mwatelescope.org/science/gleam-survey The catalog is available from Vizier. VIII/100 GaLactic and Extragalactic All-sky MWA survey (Hurley-Walker+, 2016) GaLactic and Extragalactic All-sky Murchison Wide Field Array (GLEAM) survey. I: A low-frequency extragalactic catalogue. Hurley-Walker N., et al., Mon. Not. R. Astron. Soc., 464, 1146-1167 (2017), 2017MNRAS.464.1146H :param telescope: :param npixel: Number of pixels :param polarisation_frame: Polarisation frame (default PolarisationFrame("stokesI")) :param cellsize: cellsize in radians :param frequency: :param channel_bandwidth: Channel width (Hz) :param phasecentre: phasecentre (SkyCoord) :param kind: Kind of interpolation (see scipy.interpolate.interp1d) Default: cubic :param applybeam: Apply the primary beam? :param flux_limit: Weakest component :param flux_max: Maximum strength component to be included in components :param flux_threshold: Split between components (brighter) and image (weaker) :param insert_method: Nearest | PSWF | Lanczos :return: :return: SkyModel """ check_data_directory() if phasecentre is None: phasecentre = SkyCoord(ra=+15.0 * u.deg, dec=-35.0 * u.deg, frame='icrs', equinox='J2000') radius = npixel * cellsize sc = create_low_test_skycomponents_from_gleam( flux_limit=flux_limit, polarisation_frame=polarisation_frame, frequency=frequency, phasecentre=phasecentre, kind=kind, radius=radius) sc = filter_skycomponents_by_flux(sc, flux_max=flux_max) if polarisation_frame is None: polarisation_frame = PolarisationFrame("stokesI") npol = polarisation_frame.npol nchan = len(frequency) shape = [nchan, npol, npixel, npixel] w = WCS(naxis=4) # The negation in the longitude is needed by definition of RA, DEC w.wcs.cdelt = [ -cellsize * 180.0 / numpy.pi, cellsize * 180.0 / numpy.pi, 1.0, channel_bandwidth[0] ] w.wcs.crpix = [npixel // 2 + 1, npixel // 2 + 1, 1.0, 1.0] w.wcs.ctype = ["RA---SIN", "DEC--SIN", 'STOKES', 'FREQ'] w.wcs.crval = [phasecentre.ra.deg, phasecentre.dec.deg, 1.0, frequency[0]] w.naxis = 4 w.wcs.radesys = 'ICRS' w.wcs.equinox = 2000.0 model = create_image_from_array(numpy.zeros(shape), w, polarisation_frame=polarisation_frame) if applybeam: beam = create_pb(model, telescope=telescope, use_local=False) sc = apply_beam_to_skycomponent(sc, beam) weaksc = filter_skycomponents_by_flux(sc, flux_max=flux_threshold) brightsc = filter_skycomponents_by_flux(sc, flux_min=flux_threshold) model = insert_skycomponent(model, weaksc, insert_method=insert_method) log.info( 'create_low_test_skymodel_from_gleam: %d bright sources above flux threshold %.3f, %d weak sources below ' % (len(brightsc), flux_threshold, len(weaksc))) return SkyModel(components=brightsc, image=model, mask=None, gaintable=None)
def create_low_test_image_from_gleam(npixel=512, polarisation_frame=PolarisationFrame( "stokesI"), cellsize=0.000015, frequency=numpy.array([1e8]), channel_bandwidth=numpy.array([1e6]), phasecentre=None, kind='cubic', applybeam=False, flux_limit=0.1, flux_max=numpy.inf, flux_min=-numpy.inf, radius=None, insert_method='Nearest') -> Image: """Create LOW test image from the GLEAM survey Stokes I is estimated from a cubic spline fit to the measured fluxes. The polarised flux is always zero. See http://www.mwatelescope.org/science/gleam-survey The catalog is available from Vizier. VIII/100 GaLactic and Extragalactic All-sky MWA survey (Hurley-Walker+, 2016) GaLactic and Extragalactic All-sky Murchison Wide Field Array (GLEAM) survey. I: A low-frequency extragalactic catalogue. Hurley-Walker N., et al., Mon. Not. R. Astron. Soc., 464, 1146-1167 (2017), 2017MNRAS.464.1146H :param npixel: Number of pixels :param polarisation_frame: Polarisation frame (default PolarisationFrame("stokesI")) :param cellsize: cellsize in radians :param frequency: :param channel_bandwidth: Channel width (Hz) :param phasecentre: phasecentre (SkyCoord) :param kind: Kind of interpolation (see scipy.interpolate.interp1d) Default: linear :return: Image """ check_data_directory() if phasecentre is None: phasecentre = SkyCoord(ra=+15.0 * u.deg, dec=-35.0 * u.deg, frame='icrs', equinox='J2000') if radius is None: radius = npixel * cellsize / numpy.sqrt(2.0) sc = create_low_test_skycomponents_from_gleam( flux_limit=flux_limit, polarisation_frame=polarisation_frame, frequency=frequency, phasecentre=phasecentre, kind=kind, radius=radius) sc = filter_skycomponents_by_flux(sc, flux_min=flux_min, flux_max=flux_max) if polarisation_frame is None: polarisation_frame = PolarisationFrame("stokesI") npol = polarisation_frame.npol nchan = len(frequency) shape = [nchan, npol, npixel, npixel] w = WCS(naxis=4) # The negation in the longitude is needed by definition of RA, DEC w.wcs.cdelt = [ -cellsize * 180.0 / numpy.pi, cellsize * 180.0 / numpy.pi, 1.0, channel_bandwidth[0] ] w.wcs.crpix = [npixel // 2 + 1, npixel // 2 + 1, 1.0, 1.0] w.wcs.ctype = ["RA---SIN", "DEC--SIN", 'STOKES', 'FREQ'] w.wcs.crval = [phasecentre.ra.deg, phasecentre.dec.deg, 1.0, frequency[0]] w.naxis = 4 w.wcs.radesys = 'ICRS' w.wcs.equinox = 2000.0 model = create_image_from_array(numpy.zeros(shape), w, polarisation_frame=polarisation_frame) model = insert_skycomponent(model, sc, insert_method=insert_method) if applybeam: beam = create_pb(model, telescope='LOW', use_local=False) model.data[...] *= beam.data[...] return model