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 ti 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) 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 setUp(self): from data_models.parameters import arl_path self.dir = arl_path('test_results') 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) export_image_to_fits(beam, "%s/test_deconvolve_mmclean_beam.fits" % self.dir) self.test_model.data *= beam.data 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) export_image_to_fits( self.dirty, "%s/test_deconvolve_mmclean-dirty.fits" % self.dir) 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 convert_polimage_to_stokes(im: Image): """Convert a polarisation image to stokes (complex) """ assert isinstance(im, Image) assert im.data.dtype == 'complex' if im.polarisation_frame == PolarisationFrame('linear'): cimarr = convert_linear_to_stokes(im.data) return create_image_from_array(cimarr, im.wcs, PolarisationFrame('stokesIQUV')) elif im.polarisation_frame == PolarisationFrame('circular'): cimarr = convert_circular_to_stokes(im.data) return create_image_from_array(cimarr, im.wcs, PolarisationFrame('stokesIQUV')) else: raise ValueError("Cannot convert %s to stokes" % (im.polarisation_frame.type))
def convert_stokes_to_polimage(im: Image, polarisation_frame: PolarisationFrame): """Convert a stokes image to polarisation_frame """ assert isinstance(im, Image) assert isinstance(polarisation_frame, PolarisationFrame) if polarisation_frame == PolarisationFrame('linear'): cimarr = convert_stokes_to_linear(im.data) return create_image_from_array(cimarr, im.wcs, polarisation_frame) elif polarisation_frame == PolarisationFrame('circular'): cimarr = convert_stokes_to_circular(im.data) return create_image_from_array(cimarr, im.wcs, polarisation_frame) else: raise ValueError("Cannot convert stokes to %s" % (polarisation_frame.type))
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 calculate_image_frequency_moments(im: Image, reference_frequency=None, nmoments=3) -> Image: """Calculate frequency weighted moments Weights are ((freq-reference_frequency)/reference_frequency)**moment Note that the spectral axis is replaced by a MOMENT axis. For example, to find the moments and then reconstruct from just the moments:: moment_cube = calculate_image_frequency_moments(model_multichannel, nmoments=5) reconstructed_cube = calculate_image_from_frequency_moments(model_multichannel, moment_cube) :param im: Image cube :param reference_frequency: Reference frequency (default None uses average) :param nmoments: Number of moments to calculate :return: Moments image """ assert isinstance(im, Image) nchan, npol, ny, nx = im.shape channels = numpy.arange(nchan) with warnings.catch_warnings(): warnings.simplefilter('ignore', FITSFixedWarning) freq = im.wcs.sub(['spectral']).wcs_pix2world(channels, 0)[0] assert nmoments <= nchan, "Number of moments %d cannot exceed the number of channels %d" % ( nmoments, nchan) if reference_frequency is None: reference_frequency = numpy.average(freq) log.debug( "calculate_image_frequency_moments: Reference frequency = %.3f (MHz)" % (reference_frequency / 1e6)) moment_data = numpy.zeros([nmoments, npol, ny, nx]) for moment in range(nmoments): for chan in range(nchan): weight = numpy.power( (freq[chan] - reference_frequency) / reference_frequency, moment) moment_data[moment, ...] += im.data[chan, ...] * weight moment_wcs = copy.deepcopy(im.wcs) moment_wcs.wcs.ctype[3] = 'MOMENT' moment_wcs.wcs.crval[3] = 0.0 moment_wcs.wcs.crpix[3] = 1.0 moment_wcs.wcs.cdelt[3] = 1.0 moment_wcs.wcs.cunit[3] = '' return create_image_from_array(moment_data, moment_wcs, im.polarisation_frame)
def test_create_image_from_array(self): m31model_by_array = create_image_from_array( self.m31image.data, self.m31image.wcs, self.m31image.polarisation_frame) add_image(self.m31image, m31model_by_array) add_image(self.m31image, m31model_by_array, docheckwcs=True) assert m31model_by_array.shape == self.m31image.shape log.debug( export_image_to_fits(self.m31image, fitsfile='%s/test_model.fits' % (self.dir))) log.debug(qa_image(m31model_by_array, context='test_create_from_image'))
def reproject_image(im: Image, newwcs: WCS, shape=None) -> (Image, Image): """ Re-project an image to a new coordinate system Currently uses the reproject python package. This seems to have some features do be careful using this method. For timeslice imaging I had to use griddata. :param im: Image to be reprojected :param newwcs: New WCS :param shape: :return: Reprojected Image, Footprint Image """ assert isinstance(im, Image), im rep, foot = reproject_interp((im.data, im.wcs), newwcs, shape, order='bicubic', independent_celestial_slices=True) return create_image_from_array( rep, newwcs, im.polarisation_frame), create_image_from_array( foot, newwcs, im.polarisation_frame)
def convert_hdf_to_image(f): """ Convert HDF root to an Image :param f: :return: """ assert f.attrs['ARL_data_model'] == "Image", "Not an Image" data = numpy.array(f['data']) polarisation_frame = PolarisationFrame(f.attrs['polarisation_frame']) wcs = WCS(f.attrs['wcs']) im = create_image_from_array(data, wcs=wcs, polarisation_frame=polarisation_frame) return im
def add_image(im1: Image, im2: Image, docheckwcs=False) -> Image: """ Add two images :param docheckwcs: :param im1: :param im2: :return: Image """ assert isinstance(im1, Image), im1 assert isinstance(im2, Image), im2 if docheckwcs: checkwcs(im1.wcs, im2.wcs) assert im1.polarisation_frame == im2.polarisation_frame return create_image_from_array(im1.data + im2.data, im1.wcs, im1.polarisation_frame)
def setUp(self): from data_models.parameters import arl_path self.dir = arl_path('test_results') self.lowcore = create_named_configuration('LOWBD2-CORE') self.times = (numpy.pi / (12.0)) * numpy.linspace(-3.0, 3.0, 7) self.frequency = numpy.array([1e8]) self.channel_bandwidth = numpy.array([1e6]) self.phasecentre = SkyCoord(ra=+180.0 * u.deg, dec=-60.0 * u.deg, frame='icrs', equinox='J2000') self.vis = create_visibility( self.lowcore, self.times, self.frequency, channel_bandwidth=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_test_image(cellsize=0.001, phasecentre=self.vis.phasecentre, frequency=self.frequency) 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) window = numpy.zeros(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_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 raster(im, facets=2):: r.data[...] = numpy.sqrt(r.data[...]) :param im: Image :param subimages: Number of subimages """ 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 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, 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 """ 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) 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_low_test_beam(model) model.data[...] *= beam.data[...] log.info(qa_image(model, context='create_low_test_image_from_gleam')) return model
def create_image_from_visibility(vis, **kwargs) -> Image: """Make an empty image from params and Visibility :param vis: :param phasecentre: Phasecentre (Skycoord) :param channel_bandwidth: Channel width (Hz) :param cellsize: Cellsize (radians) :param npixel: Number of pixels on each axis (512) :param frame: Coordinate frame for WCS (ICRS) :param equinox: Equinox for WCS (2000.0) :param nchan: Number of image channels (Default is 1 -> MFS) :return: image """ assert isinstance(vis, Visibility) or isinstance(vis, BlockVisibility), \ "vis is not a Visibility or a BlockVisibility: %r" % (vis) log.info( "create_image_from_visibility: Parsing parameters to get definition of WCS" ) imagecentre = get_parameter(kwargs, "imagecentre", vis.phasecentre) phasecentre = get_parameter(kwargs, "phasecentre", vis.phasecentre) # Spectral processing options ufrequency = numpy.unique(vis.frequency) vnchan = len(ufrequency) frequency = get_parameter(kwargs, "frequency", vis.frequency) inchan = get_parameter(kwargs, "nchan", vnchan) reffrequency = frequency[0] * units.Hz channel_bandwidth = get_parameter( kwargs, "channel_bandwidth", 0.99999999999 * vis.channel_bandwidth[0]) * units.Hz if (inchan == vnchan) and vnchan > 1: log.info( "create_image_from_visibility: Defining %d channel Image at %s, starting frequency %s, and bandwidth %s" % (inchan, imagecentre, reffrequency, channel_bandwidth)) elif (inchan == 1) and vnchan > 1: assert numpy.abs(channel_bandwidth.value ) > 0.0, "Channel width must be non-zero for mfs mode" log.info( "create_image_from_visibility: Defining single channel MFS Image at %s, starting frequency %s, " "and bandwidth %s" % (imagecentre, reffrequency, channel_bandwidth)) elif inchan > 1 and vnchan > 1: assert numpy.abs(channel_bandwidth.value ) > 0.0, "Channel width must be non-zero for mfs mode" log.info( "create_image_from_visibility: Defining multi-channel MFS Image at %s, starting frequency %s, " "and bandwidth %s" % (imagecentre, reffrequency, channel_bandwidth)) elif (inchan == 1) and (vnchan == 1): assert numpy.abs(channel_bandwidth.value ) > 0.0, "Channel width must be non-zero for mfs mode" log.info( "create_image_from_visibility: Defining single channel Image at %s, starting frequency %s, " "and bandwidth %s" % (imagecentre, reffrequency, channel_bandwidth)) else: raise ValueError( "create_image_from_visibility: unknown spectral mode ") # Image sampling options npixel = get_parameter(kwargs, "npixel", 512) uvmax = numpy.max((numpy.abs(vis.data['uvw'][:, 0:1]))) if isinstance(vis, BlockVisibility): uvmax *= numpy.max(frequency) / constants.c.to('m s^-1').value log.info("create_image_from_visibility: uvmax = %f wavelengths" % uvmax) criticalcellsize = 1.0 / (uvmax * 2.0) log.info( "create_image_from_visibility: Critical cellsize = %f radians, %f degrees" % (criticalcellsize, criticalcellsize * 180.0 / numpy.pi)) cellsize = get_parameter(kwargs, "cellsize", 0.5 * criticalcellsize) log.info( "create_image_from_visibility: Cellsize = %f radians, %f degrees" % (cellsize, cellsize * 180.0 / numpy.pi)) override_cellsize = get_parameter(kwargs, "override_cellsize", True) if override_cellsize and cellsize > criticalcellsize: log.info( "create_image_from_visibility: Resetting cellsize %f radians to criticalcellsize %f radians" % (cellsize, criticalcellsize)) cellsize = criticalcellsize pol_frame = get_parameter(kwargs, "polarisation_frame", PolarisationFrame("stokesI")) inpol = pol_frame.npol # Now we can define the WCS, which is a convenient place to hold the info above # Beware of python indexing order! wcs and the array have opposite ordering shape = [inchan, inpol, npixel, npixel] w = wcs.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.to(units.Hz).value ] # The numpy definition of the phase centre of an FFT is n // 2 (0 - rel) so that's what we use for # the reference pixel. We have to use 0 rel everywhere. 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, reffrequency.to(units.Hz).value ] w.naxis = 4 direction_centre = pixel_to_skycoord(npixel // 2 + 1, npixel // 2 + 1, wcs=w, origin=1) assert direction_centre.separation(imagecentre).value < 1e-7, \ "Image phase centre [npixel//2, npixel//2] should be %s, actually is %s" % \ (str(imagecentre), str(direction_centre)) w.wcs.radesys = get_parameter(kwargs, 'frame', 'ICRS') w.wcs.equinox = get_parameter(kwargs, 'equinox', 2000.0) return create_image_from_array(numpy.zeros(shape), wcs=w, polarisation_frame=pol_frame)
def invert_2d(vis: Visibility, im: Image, dopsf: bool = False, normalize: bool = True, **kwargs) \ -> (Image, numpy.ndarray): """ Invert using 2D convolution function, including w projection optionally Use the image im as a template. Do PSF in a separate call. This is at the bottom of the layering i.e. all transforms are eventually expressed in terms of this function. . Any shifting needed is performed here. :param vis: Visibility to be inverted :param im: image template (not changed) :param dopsf: Make the psf instead of the dirty image :param normalize: Normalize by the sum of weights (True) :return: resulting image """ if not isinstance(vis, Visibility): svis = coalesce_visibility(vis, **kwargs) else: svis = copy_visibility(vis) if dopsf: svis.data['vis'] = numpy.ones_like(svis.data['vis']) svis = shift_vis_to_image(svis, im, tangent=True, inverse=False) nchan, npol, ny, nx = im.data.shape padding = {} if get_parameter(kwargs, "padding", False): padding = {'padding': get_parameter(kwargs, "padding", False)} spectral_mode, vfrequencymap = get_frequency_map(svis, im) polarisation_mode, vpolarisationmap = get_polarisation_map(svis, im) uvw_mode, shape, padding, vuvwmap = get_uvw_map(svis, im, **padding) kernel_name, gcf, vkernellist = get_kernel_list(svis, im, **kwargs) # Optionally pad to control aliasing imgridpad = numpy.zeros( [nchan, npol, int(round(padding * ny)), int(round(padding * nx))], dtype='complex') imgridpad, sumwt = convolutional_grid(vkernellist, imgridpad, svis.data['vis'], svis.data['imaging_weight'], vuvwmap, vfrequencymap) # Fourier transform the padded grid to image, multiply by the gridding correction # function, and extract the unpadded inner part. # Normalise weights for consistency with transform sumwt /= float(padding * int(round(padding * nx)) * ny) imaginary = get_parameter(kwargs, "imaginary", False) if imaginary: log.debug("invert_2d: retaining imaginary part of dirty image") result = extract_mid(ifft(imgridpad) * gcf, npixel=nx) resultreal = create_image_from_array(result.real, im.wcs, im.polarisation_frame) resultimag = create_image_from_array(result.imag, im.wcs, im.polarisation_frame) if normalize: resultreal = normalize_sumwt(resultreal, sumwt) resultimag = normalize_sumwt(resultimag, sumwt) return resultreal, sumwt, resultimag else: result = extract_mid(numpy.real(ifft(imgridpad)) * gcf, npixel=nx) resultimage = create_image_from_array(result, im.wcs, im.polarisation_frame) if normalize: resultimage = normalize_sumwt(resultimage, sumwt) return resultimage, sumwt
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 raster(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 """ nchan, npol, ny, nx = im.shape log.debug("image_raster_iter: predicting using %d x %d image partitions" % (facets, facets)) assert facets <= ny, "Cannot have more raster elements than pixels" assert facets <= nx, "Cannot have more raster elements than pixels" if facets == 1 and overlap == 0: 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 log.debug('image_raster_iter: spacing of raster (%d, %d)' % (dx, dy)) 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: log.debug( 'image_raster_iter: partition (%d, %d) of (%d, %d)' % (fy, fx, facets, facets)) # 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 deconvolve_cube(dirty: Image, psf: Image, prefix='', **kwargs) -> (Image, Image): """ Clean using a variety of algorithms Functions that clean a dirty image using a point spread function. The algorithms available are: hogbom: Hogbom CLEAN See: Hogbom CLEAN A&A Suppl, 15, 417, (1974) 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 * nmoments :param dirty: Image dirty image :param psf: Image Point Spread Function :param window: Window image (Bool) - clean where True :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 nmoments: Number of frequency moments (default 3) :param findpeak: Method of finding peak in mfsclean: 'Algorithm1'|'ASKAPSoft'|'CASA'|'ARL', Default is ARL. :return: componentimage, residual """ assert isinstance(dirty, Image), dirty assert isinstance(psf, Image), psf window_shape = get_parameter(kwargs, 'window_shape', None) if window_shape == 'quarter': 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) else: window = None 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", 'ARL') log.info( "deconvolve_cube %s: Multi-scale multi-frequency clean of each polarisation separately" % prefix) nmoments = get_parameter(kwargs, "nmoments", 3) assert nmoments > 0, "Number of frequency moments must be greater than zero" nchan = dirty.shape[0] assert nchan > 2 * nmoments, "Require nchan %d > 2 * nmoments %d" % ( nchan, 2 * nmoments) dirty_taylor = calculate_image_frequency_moments(dirty, nmoments=nmoments) psf_taylor = calculate_image_frequency_moments(psf, nmoments=2 * nmoments) 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]): if psf_taylor.data[0, pol, :, :].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[:, pol, :, :], None, gain, thresh, niter, scales, fracthresh, findpeak, prefix) else: qx = dirty.shape[3] // 4 qy = dirty.shape[2] // 4 window_taylor = numpy.zeros_like(dirty_taylor.data) window_taylor[..., (qy + 1):3 * qy, (qx + 1):3 * qx] = 1.0 log.info( 'deconvolve_cube %s: Cleaning inner quarter of each moment plane' % prefix) comp_array[:, pol, :, :], residual_array[:, pol, :, :] = \ msmfsclean(dirty_taylor.data[:, pol, :, :], psf_taylor.data[:, pol, :, :], window_taylor[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.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 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) else: raise ValueError('deconvolve_cube %s: Unknown algorithm %s' % (prefix, algorithm)) return comp_image, residual_image
def deconvolve_cube_complex(dirty: Image, psf: Image, **kwargs) -> (Image, Image): """ Clean using the complex Hogbom algorithm for polarised data (2016MNRAS.462.3483P) The algorithm available is: hogbom-complex: See: Pratley L. & Johnston-Hollitt M., (2016), MNRAS, 462, 3483. This code is based upon the deconvolve_cube code for standard Hogbom clean available in ARL. Args: dirty (numpy array): The dirty image, i.e., the image to be deconvolved. psf (numpy array): The point spread-function. window (float): Regions where clean components are allowed. If True, entire dirty Image is allowed. algorithm (str): Cleaning algorithm: 'hogbom-complex' only. gain (float): The "loop gain", i.e., the fraction of the brightest pixel that is removed in each iteration. threshold (float): Cleaning stops when the maximum of the absolute deviation of the residual is less than this value. niter (int): Maximum number of components to make if the threshold `thresh` is not hit. fractional_threshold (float): The predefined fractional threshold at which to stop cleaning. Returns: comp_image: clean component image. residual_image: residual image. """ assert isinstance(dirty, Image), "Type is %s" % (type(dirty)) assert isinstance(psf, Image), "Type is %s" % (type(psf)) window_shape = get_parameter(kwargs, 'window_shape', None) if window_shape == 'quarter': qx = dirty.shape[3] // 4 qy = dirty.shape[2] // 4 window = np.zeros_like(dirty.data) window[..., (qy + 1):3 * qy, (qx + 1):3 * qx] = 1.0 log.info( 'deconvolve_cube_complex: Cleaning inner quarter of each sky plane' ) else: window = None psf_support = get_parameter(kwargs, 'psf_support', None) if isinstance(psf_support, int): 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_complex: PSF support = +/- %d pixels' % (psf_support)) algorithm = get_parameter(kwargs, 'algorithm', 'msclean') if algorithm == 'hogbom-complex': log.info( "deconvolve_cube_complex: Hogbom-complex clean of each polarisation and channel separately" ) 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 fracthresh = get_parameter(kwargs, 'fractional_threshold', 0.1) assert 0.0 <= fracthresh < 1.0 comp_array = np.zeros(dirty.data.shape) residual_array = np.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_complex: Unknown algorithm %s' % algorithm) return comp_image, residual_image
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 LOW 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 """ 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 = arl_path('data/models/S3_1400MHz_1mJy_%sdeg.csv' % fovstr) else: csvfilename = arl_path('data/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 = arl_path('data/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.1E9: 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 flux.any() > 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) total_flux = numpy.sum(fluxes) fluxes = numpy.array(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