def test_bounds_checking(): n_image = 8 support = 2 # n_image = 8 then the co-ords in the u/x-direction are: # [-4, -3, -2, -1, 0, 1, 2, 3 ] # Support = 2 takes this over the edge (since -3 -2 = -5): bad_uv = np.array([(-3., 0)]) vis = np.ones(len(bad_uv), dtype=np.float_) kernel_func = conv_funcs.Pillbox(1.5) with pytest.raises(ValueError): grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=bad_uv, vis=vis, vis_weights=np.ones_like(vis), ) grid, _ = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=bad_uv, vis=vis, vis_weights=np.ones_like(vis), raise_bounds=False ) assert grid.sum() == 0. # Now check we're filtering indices in the correct order # The mixed good_uv = np.array([(0., 0.)]) mixed_uv = np.array([(-3., 0), (0., 0.)]) good_grid, _ = convolve_to_grid( kernel_func, support=support, image_size=n_image, uv=good_uv, vis=np.ones(len(good_uv), dtype=np.float_), vis_weights=np.ones(len(good_uv), dtype=np.float_), raise_bounds=False ) mixed_grid, _ = convolve_to_grid( kernel_func, support=support, image_size=n_image, uv=mixed_uv, vis=np.ones(len(mixed_uv), dtype=np.float_), vis_weights=np.ones(len(mixed_uv), dtype=np.float_), raise_bounds=False ) assert (good_grid == mixed_grid).all()
def test_bounds_checking(): n_image = 8 support = 2 # n_image = 8 then the co-ords in the u/x-direction are: # [-4, -3, -2, -1, 0, 1, 2, 3 ] # Support = 2 takes this over the edge (since -3 -2 = -5): bad_uv = np.array([(-3., 0)]) vis = np.ones(len(bad_uv), dtype=np.float_) kernel_func = conv_funcs.Pillbox(1.5) with pytest.raises(ValueError): grid = convolve_to_grid( kernel_func, support=support, image_size=n_image, uv=bad_uv, vis=vis, vis_weights=np.ones_like(vis), ) grid, _ = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=bad_uv, vis=vis, vis_weights=np.ones_like(vis), raise_bounds=False) assert grid.sum() == 0. # Now check we're filtering indices in the correct order # The mixed good_uv = np.array([(0., 0.)]) mixed_uv = np.array([(-3., 0), (0., 0.)]) good_grid, _ = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=good_uv, vis=np.ones(len(good_uv), dtype=np.float_), vis_weights=np.ones(len(good_uv), dtype=np.float_), raise_bounds=False) mixed_grid, _ = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=mixed_uv, vis=np.ones(len(mixed_uv), dtype=np.float_), vis_weights=np.ones(len(mixed_uv), dtype=np.float_), raise_bounds=False) assert (good_grid == mixed_grid).all()
def test_triangle(): # Now let's try a triangle function, larger support this time: n_image = 8 support = 2 uv = np.array([(1.0, 0.0)]) subpix_offset = np.array([(0.1, -0.15)]) vis = np.ones(len(uv), dtype=np.float_) # offset = np.array([(0.0, 0.0)]) uv += subpix_offset kernel_func = conv_funcs.Triangle(2.0) grid, _ = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, oversampling=None) kernel = Kernel(kernel_func=kernel_func, support=support, offset=subpix_offset[0], oversampling=1) assert grid.sum() == vis.sum() # uv location of sample is 1, therefore pixel index = n_image/2 +1 xrange = slice(n_image / 2 + 1 - support, n_image / 2 + 1 + support + 1) yrange = slice(n_image / 2 - support, n_image / 2 + support + 1) assert (grid[yrange, xrange] == (kernel.array / kernel.array.sum())).all()
def test_small_pillbox(): n_image = 8 support = 1 uv = np.array([(-1.5, 0.5)]) vis = np.ones(len(uv), dtype=np.float_) kernel_func = conv_funcs.Pillbox(0.55) grid, _ = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, oversampling=None) assert grid.sum() == vis.sum() # This time we're on a mid-point, with a smaller pillbox # so we should get a 2x2 output v = 1. / 4. expected_result = np.array( # [-4, -3, -2, -1, 0, 1, 2, 3 ] [[0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., v, v, 0., 0., 0., 0., ], [0., 0., v, v, 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ]] ) assert (expected_result == grid).all()
def test_multi_pixel_pillbox(): n_image = 8 support = 1 uv = np.array([(-2., 0)]) vis = np.ones(len(uv), dtype=np.float_) kernel_func = conv_funcs.Pillbox(1.1) vis_grid, sampling_grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, vis_weights=np.ones_like(vis), ) assert vis_grid.sum() == vis.sum() # Since uv is precisely on a sampling point, we'll get a # 3x3 pillbox v = 1. / 9. expected_result = np.array( [[0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., v, v, v, 0., 0., 0., 0., ], [0., v, v, v, 0., 0., 0., 0., ], [0., v, v, v, 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ]] ) assert (expected_result == vis_grid).all() # In this case we expect sampling == vis, since we only have one vis == 1.0 assert (expected_result == sampling_grid).all()
def test_oversampled_gridding(): """ Integration test of the convolve_to_grid function with oversampling Mostly tests same functionality as ``test_kernel_caching``. """ # Let's grid a triangle function n_image = 8 support = 2 uv = np.array([(1.0, 0.0), (1.3, 0.0), (.01, -1.32), ]) vis = np.ones(len(uv), dtype=np.float_) vis_weights=np.ones_like(vis) kernel_func = conv_funcs.Triangle(2.0) vis_grid, sample_grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, vis_weights=vis_weights, exact=False, oversampling=9 ) # simplification true since weights are all 1: assert vis_grid.sum() == vis.sum()
def test_single_pixel_overlap_pillbox(): # NB Grid uv-coordinates are np.arange(n_image) - n_image/2, so e.g. if # n_image = 8 then the co-ords in the u/x-direction are: # [-4, -3, -2, -1, 0, 1, 2, 3 ] n_image = 8 support = 1 uv = np.array([(-2., 0), (-2., 0)]) # Real vis will be complex_, but we can substitute float_ for testing: vis_amplitude = 42.123 vis = vis_amplitude * np.ones(len(uv), dtype=np.float_) vis_weights = np.ones_like(vis) kernel_func = conv_funcs.Pillbox(0.5) vis_grid, sampling_grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, vis_weights=vis_weights, oversampling=None) expected_sample_grid = np.array( [[0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 1., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ]] ) assert (expected_sample_grid * vis_amplitude * vis_weights.sum() == vis_grid).all() assert (expected_sample_grid * vis_weights.sum() == sampling_grid).all()
def test_triangle(): # Now let's try a triangle function, larger support this time: n_image = 8 support = 2 uv = np.array([(1.0, 0.0)]) subpix_offset = np.array([(0.1, -0.15)]) vis = np.ones(len(uv), dtype=np.float_) vis_weights = np.ones_like(vis) # offset = np.array([(0.0, 0.0)]) uv += subpix_offset kernel_func = conv_funcs.Triangle(2.0) vis_grid, sample_grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, vis_weights=vis_weights, ) kernel = Kernel(kernel_func=kernel_func, support=support, offset=subpix_offset[0], oversampling=1) assert vis_grid.sum() == vis.sum() assert sample_grid.sum() == vis_weights.sum() # uv location of sample is 1, therefore pixel index = n_image/2 +1 xrange = slice(n_image // 2 + 1 - support, n_image // 2 + 1 + support + 1) yrange = slice(n_image // 2 - support, n_image // 2 + support + 1) assert (vis_grid[yrange, xrange] == (kernel.array / kernel.array.sum())).all()
def test_small_pillbox(): n_image = 8 support = 1 uv = np.array([(-1.5, 0.5)]) vis = np.ones(len(uv), dtype=np.float_) kernel_func = conv_funcs.Pillbox(0.55) grid, _ = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, vis_weights=np.ones_like(vis), ) assert grid.sum() == vis.sum() # This time we're on a mid-point, with a smaller pillbox # so we should get a 2x2 output v = 1. / 4. expected_result = np.array( # [-4, -3, -2, -1, 0, 1, 2, 3 ] [[0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., v, v, 0., 0., 0., 0., ], [0., 0., v, v, 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ]] ) assert (expected_result == grid).all()
def test_single_pixel_overlap_pillbox(): # NB Grid uv-coordinates are np.arange(n_image) - n_image/2, so e.g. if # n_image = 8 then the co-ords in the u/x-direction are: # [-4, -3, -2, -1, 0, 1, 2, 3 ] n_image = 8 support = 1 uv = np.array([(-2., 0), (-2., 0)]) # Real vis will be complex_, but we can substitute float_ for testing: vis_amplitude = 42.123 vis = vis_amplitude * np.ones(len(uv), dtype=np.float_) kernel_func = conv_funcs.Pillbox(0.5) vis_grid, sampling_grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, oversampling=None) assert vis_grid.sum() == vis.sum() expected_result = np.array( [[0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 1., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ]] ) assert (expected_result * vis.sum() == vis_grid).all() assert (expected_result * len(uv) == sampling_grid).all()
def test_multi_pixel_pillbox(): n_image = 8 support = 1 uv = np.array([(-2., 0)]) vis = np.ones(len(uv), dtype=np.float_) kernel_func = conv_funcs.Pillbox(1.1) vis_grid, sampling_grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, oversampling=None) assert vis_grid.sum() == vis.sum() # Since uv is precisely on a sampling point, we'll get a # 3x3 pillbox v = 1. / 9. expected_result = np.array( [[0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., v, v, v, 0., 0., 0., 0., ], [0., v, v, v, 0., 0., 0., 0., ], [0., v, v, v, 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ]] ) assert (expected_result == vis_grid).all() # In this case we expect sampling == vis, since we only have one vis == 1.0 assert (expected_result == sampling_grid).all()
def test_zero_weighting(): """ Sanity check that the weights are having some effect """ n_image = 8 support = 1 uv = np.array([(-2., 0), (-2., 0)]) # Real vis will be complex_, but we can substitute float_ for testing: vis_amplitude = 42.123 vis = vis_amplitude * np.ones(len(uv), dtype=np.float_) vis_weights = np.zeros_like(vis) kernel_func = conv_funcs.Pillbox(0.5) zeros_array = np.zeros((8, 8), dtype=vis.dtype) # Exact gridding vis_grid, sampling_grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, vis_weights=vis_weights, exact=True, oversampling=None) assert vis.sum() != 0 assert (vis_grid == zeros_array).all() # Kernel-cache vis_grid, sampling_grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, vis_weights=vis_weights, exact=False, oversampling=5) assert (vis_grid == zeros_array).all()
def test_bounds_checking(): n_image = 8 support = 2 # n_image = 8 then the co-ords in the u/x-direction are: # [-4, -3, -2, -1, 0, 1, 2, 3 ] # Support = 2 takes this over the edge (since -3 -2 = -5): uv = np.array([(-3., 0)]) vis = np.ones(len(uv), dtype=np.float_) kernel_func = conv_funcs.Pillbox(1.5) with pytest.raises(ValueError): grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, oversampling=None) grid, _ = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, raise_bounds=False ) assert grid.sum() == 0.
def test_natural_weighting(): """ Confirm natural weighting works as expected in most basic non-zero case """ n_image = 8 support = 1 uv = np.array([(-2., 0), (-2., 0)]) # Real vis will be complex_, but we can substitute float_ for testing: vis = np.asarray([3. / 2., 3.]) vis_weights = np.asarray([2. / 3., 1. / 3]) vis_weights = np.asarray([2., 3.]) # vis_weights = np.ones_like(vis) kernel_func = conv_funcs.Pillbox(0.5) # Exact gridding vis_grid, sampling_grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, vis_weights=vis_weights, exact=True, oversampling=None) natural_weighted_sum = (vis * vis_weights).sum() / vis_weights.sum() # print("Natural estimate", natural_weighted_sum) # print("SAMPLES\n", sampling_grid) # print("VIS\n", vis_grid) expected_sample_locations = np.array( [[0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 1., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ], [0., 0., 0., 0., 0., 0., 0., 0., ]] ) assert ( expected_sample_locations == sampling_grid/sampling_grid.sum()).all() assert ((expected_sample_locations * natural_weighted_sum) == vis_grid / sampling_grid.sum()).all()
def test_nearby_complex_vis(): # Quick sanity check for multiple visibilities, kernel footprints # overlapping: n_image = 8 support = 2 uv = np.array([(-2., 1), (0., -1), ]) # vis = np.ones(len(uv), dtype=np.float_) vis = np.ones(len(uv), dtype=np.complex_) vis_weights = np.ones_like(vis) kernel_func = conv_funcs.Pillbox(1.1) vis_grid, sampling_grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, vis_weights=vis_weights, ) # simplification true since weights are all 1: assert vis_grid.sum() == vis.sum() # Since uv is precisely on a sampling point, we'll get a # 3x3 pillbox v = 1. / 9. + 0j expected_sample_grid = np.array( # [-4, -3, -2, -1, 0, 1, 2, 3 ] [[0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., v, v, v, 0., 0.], [0., 0., 0., v, v, v, 0., 0.], [0., v, v, 2. * v, v, v, 0., 0.], [0., v, v, v, 0., 0., 0., 0.], [0., v, v, v, 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.]] ) # simplification true since weights are all 1: assert (expected_sample_grid == vis_grid).all() assert (expected_sample_grid == sampling_grid).all()
def test_nearby_complex_vis(): # Quick sanity check for multiple visibilities, kernel footprints # overlapping: n_image = 8 support = 2 uv = np.array([ (-2., 1), (0., -1), ]) # vis = np.ones(len(uv), dtype=np.float_) vis = np.ones(len(uv), dtype=np.complex_) vis_weights = np.ones_like(vis) kernel_func = conv_funcs.Pillbox(1.1) vis_grid, sampling_grid = convolve_to_grid( kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, vis_weights=vis_weights, ) # simplification true since weights are all 1: assert vis_grid.sum() == vis.sum() # Since uv is precisely on a sampling point, we'll get a # 3x3 pillbox v = 1. / 9. + 0j expected_sample_grid = np.array( # [-4, -3, -2, -1, 0, 1, 2, 3 ] [[0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., v, v, v, 0., 0.], [0., 0., 0., v, v, v, 0., 0.], [0., v, v, 2. * v, v, v, 0., 0.], [0., v, v, v, 0., 0., 0., 0.], [0., v, v, v, 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.]]) # simplification true since weights are all 1: assert (expected_sample_grid == vis_grid).all() assert (expected_sample_grid == sampling_grid).all()
def test_multiple_complex_vis(): # Quick sanity check for multiple visibilities, complex_ this time: n_image = 8 support = 2 uv = np.array([(-2., 1), (1., -1), ]) # vis = np.ones(len(uv), dtype=np.float_) vis = np.ones(len(uv), dtype=np.complex_) kernel_func = conv_funcs.Pillbox(1.1) vis_grid, sampling_grid = convolve_to_grid(kernel_func, support=support, image_size=n_image, uv=uv, vis=vis, oversampling=None) assert vis_grid.sum() == vis.sum() # Since uv is precisely on a sampling point, we'll get a # 3x3 pillbox v = 1. / 9. + 0j expected_result = np.array( # [-4, -3, -2, -1, 0, 1, 2, 3 ] [[0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., v, v, v, 0.], [0., 0., 0., 0., v, v, v, 0.], [0., v, v, v, v, v, v, 0.], [0., v, v, v, 0., 0., 0., 0.], [0., v, v, v, 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.]] ) assert (expected_result == vis_grid).all() assert (expected_result == sampling_grid).all()
def image_visibilities(vis, vis_weights, uvw_lambda, image_size, cell_size, kernel_func, kernel_support, kernel_exact=True, kernel_oversampling=0, progress_bar=None): """ Args: vis (numpy.ndarray): Complex visibilities. 1d array, shape: `(n_vis,)`. uvw_lambda (numpy.ndarray): UVW-coordinates of visibilities. Units are multiples of wavelength. 2d array of ``np.float_``, shape: ``(n_vis, 3)``. Assumed ordering is u,v,w i.e. ``u,v,w = uvw[idx]`` image_size (astropy.units.Quantity): Width of the image in pixels. e.g. ``1024 * u.pixel``. NB we assume the pixel ``[image_size//2,image_size//2]`` corresponds to the origin in UV-space. cell_size (astropy.units.Quantity): Angular-width of a synthesized pixel in the image to be created, e.g. ``3.5 * u.arcsecond``. kernel_func (callable): Callable object, (e.g. :class:`.conv_funcs.Pillbox`,) that returns a convolution co-efficient for a given distance in pixel-widths. kernel_support (int): Defines the 'radius' of the bounding box within which convolution takes place. `Box width in pixels = 2*support+1`. (The central pixel is the one nearest to the UV co-ordinates.) (This is sometimes known as the 'half-support') kernel_exact (bool): Calculate exact kernel-values for every UV-sample. kernel_oversampling (int): Controls kernel-generation if ``exact==False``. Larger values give a finer-sampled set of pre-cached kernels. raise_bounds (bool): Raise an exception if any of the UV kernel_oversampling (int): (Or None). Controls kernel-generation, see :func:`fastimgproto.gridder.gridder.convolve_to_grid` for details. progress_bar (tqdm.tqdm): [Optional] progressbar to update. Returns: tuple: (image, beam) Tuple of ndarrays representing the image map and beam model. These are 2d arrays of same dtype as ``vis``, (typically ``np._complex``), shape ``(image_size, image_size)``. Note numpy style index-order, i.e. access like ``image[y,x]``. """ image_size = image_size.to(u.pix) image_size_int = int(image_size.value) if image_size_int != image_size.value: raise ValueError("Please supply an integer-valued image size") # Size of a UV-grid pixel, in multiples of wavelength (lambda): grid_pixel_width_lambda = 1.0 / (cell_size.to(u.rad) * image_size) uvw_in_pixels = (uvw_lambda / grid_pixel_width_lambda).value uv_in_pixels = uvw_in_pixels[:, :2] vis_grid, sample_grid = convolve_to_grid(kernel_func, support=kernel_support, image_size=image_size_int, uv=uv_in_pixels, vis=vis, vis_weights=vis_weights, exact=kernel_exact, oversampling=kernel_oversampling, progress_bar=progress_bar) image = fft_to_image_plane(vis_grid) beam = fft_to_image_plane(sample_grid) total_sample_weight = sample_grid.sum() # To calculate the normalization factor: # We correct for the FFT scale factor of 1/image_size**2 # (cf https://docs.scipy.org/doc/numpy/reference/routines.fft.html#implementation-details) # And then divide by the total sample weight to renormalize the visibilities. if total_sample_weight != 0: renormalization_factor = (image_size_int * image_size_int) / total_sample_weight beam *= renormalization_factor image *= renormalization_factor return (image, beam)
def image_visibilities( vis, vis_weights, uvw_lambda, image_size, cell_size, kernel_func, kernel_support, kernel_exact=True, kernel_oversampling=0, progress_bar=None): """ Args: vis (numpy.ndarray): Complex visibilities. 1d array, shape: `(n_vis,)`. uvw_lambda (numpy.ndarray): UVW-coordinates of visibilities. Units are multiples of wavelength. 2d array of ``np.float_``, shape: ``(n_vis, 3)``. Assumed ordering is u,v,w i.e. ``u,v,w = uvw[idx]`` image_size (astropy.units.Quantity): Width of the image in pixels. e.g. ``1024 * u.pixel``. NB we assume the pixel ``[image_size//2,image_size//2]`` corresponds to the origin in UV-space. cell_size (astropy.units.Quantity): Angular-width of a synthesized pixel in the image to be created, e.g. ``3.5 * u.arcsecond``. kernel_func (callable): Callable object, (e.g. :class:`.conv_funcs.Pillbox`,) that returns a convolution co-efficient for a given distance in pixel-widths. kernel_support (int): Defines the 'radius' of the bounding box within which convolution takes place. `Box width in pixels = 2*support+1`. (The central pixel is the one nearest to the UV co-ordinates.) (This is sometimes known as the 'half-support') kernel_exact (bool): Calculate exact kernel-values for every UV-sample. kernel_oversampling (int): Controls kernel-generation if ``exact==False``. Larger values give a finer-sampled set of pre-cached kernels. raise_bounds (bool): Raise an exception if any of the UV kernel_oversampling (int): (Or None). Controls kernel-generation, see :func:`fastimgproto.gridder.gridder.convolve_to_grid` for details. progress_bar (tqdm.tqdm): [Optional] progressbar to update. Returns: tuple: (image, beam) Tuple of ndarrays representing the image map and beam model. These are 2d arrays of same dtype as ``vis``, (typically ``np._complex``), shape ``(image_size, image_size)``. Note numpy style index-order, i.e. access like ``image[y,x]``. """ image_size = image_size.to(u.pix) image_size_int = int(image_size.value) if image_size_int != image_size.value: raise ValueError("Please supply an integer-valued image size") # Size of a UV-grid pixel, in multiples of wavelength (lambda): grid_pixel_width_lambda = 1.0 / (cell_size.to(u.rad) * image_size) uvw_in_pixels = (uvw_lambda / grid_pixel_width_lambda).value uv_in_pixels = uvw_in_pixels[:, :2] vis_grid, sample_grid = convolve_to_grid(kernel_func, support=kernel_support, image_size=image_size_int, uv=uv_in_pixels, vis=vis, vis_weights=vis_weights, exact=kernel_exact, oversampling=kernel_oversampling, progress_bar=progress_bar ) image = fft_to_image_plane(vis_grid) beam = fft_to_image_plane(sample_grid) total_sample_weight = sample_grid.sum() # To calculate the normalization factor: # We correct for the FFT scale factor of 1/image_size**2 # (cf https://docs.scipy.org/doc/numpy/reference/routines.fft.html#implementation-details) # And then divide by the total sample weight to renormalize the visibilities. if total_sample_weight != 0: renormalization_factor = ( image_size_int * image_size_int) / total_sample_weight beam *= renormalization_factor image *= renormalization_factor return (image, beam)