def test_crop(): """Image class: testing crop method""" # Create an image whose pixels are all masked. image1 = generate_image(shape=(9, 7), data=2.0, var=0.5, mask=True) # Create a masked array of unmasked values to be assigned to the # part of the image, with just a diamond shaped area of pixels # unmasked. diamond = np.ma.array(data=[[6.0, 2.0, 9.0], [1.0, 4.0, 8.0], [0.0, 5.0, 3.0]], mask=[[True, False, True], [False, False, False], [True, False, True]]) # Assign the above array to part of the image to clear the mask of # an irregular rectangular area of pixels there. image1.data[2:5, 1:4] = diamond # The following should crop all but the rectangular area that was # assigned above. image1.crop() # Check that the masked data array is as expected. assert_masked_allclose(image1.data, diamond) # The cropped variance array should look like the following array. expected_var = np.ma.array(data=[[0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [0.5, 0.5, 0.5]], mask=diamond.mask) # Check that the masked variance array is as expected. assert_masked_allclose(image1.var, expected_var) # Check the WCS characteristics of the cropped image. assert_image_equal(image1, shape=(3, 3), start=(2, 1), end=(4, 3)) assert image1.get_rot() == 0
def test_convolve(): """Image class: testing discrete convolution method.""" shape = (12, 25) wcs = WCS(cdelt=(1.0, 1.0), crval=(0.0, 0.0), shape=shape) data = np.zeros(shape) data[7, 5] = 1.0 mask = np.zeros(shape, dtype=bool) mask[5, 3] = True ima = Image(wcs=wcs, data=data, mask=mask, copy=False) # Create a symmetric convolution kernel with an even number of elements # along one dimension and and odd number along the other dimension. # Make the kernel symmetric around (shape-1)//2. This requires that # the final column be all zeros. kern = np.array([[0.1, 0.25, 0.1, 0.0], [0.25, 0.50, 0.25, 0.0], [0.1, 0.25, 0.1, 0.0]]) # The image should consist of a copy of the convolution kernel, centered # such that pixels (kern.shape-1)//2 is at pixel 7,5 of data. expected_data = np.ma.array(data=np.zeros(shape), mask=mask) expected_data.data[6:9, 4:8] = kern res = ima.convolve(kern) assert_masked_allclose(res.data, expected_data) res = ima.convolve(Image(data=kern)) assert_masked_allclose(res.data, expected_data) res = ima.fftconvolve(kern) assert_masked_allclose(res.data, expected_data, atol=1e-15)
def test_convolve(): shape = (3, 12, 25) data = np.zeros(shape) data[:, 7, 5] = 1.0 mask = np.zeros(shape, dtype=bool) mask[:, 5, 3] = True c = generate_cube(data=data, mask=mask, shape=shape, wave=WaveCoord(crval=1, cunit=u.angstrom)) # Create a symmetric convolution kernel with an even number of elements # along one dimension and and odd number along the other dimension. # Make the kernel symmetric around (shape-1)//2. This requires that # the final column be all zeros. kern = np.array([[[0.1, 0.25, 0.1, 0.0], [0.25, 0.50, 0.25, 0.0], [0.1, 0.25, 0.1, 0.0]]]) # The image should consist of a copy of the convolution kernel, centered # such that pixels (kern.shape-1)//2 is at pixel 7,5 of data. expected_data = ma.array(data=np.zeros(shape), mask=mask) expected_data.data[:, 6:9, 4:8] = kern res = c.convolve(kern) assert_masked_allclose(res.data, expected_data, atol=1e-15) res = c.convolve(Image(data=kern)) assert_masked_allclose(res.data, expected_data, atol=1e-15) res = c.fftconvolve(kern) assert_masked_allclose(res.data, expected_data, atol=1e-15)
def test_rebin(): """Image class: testing rebin methods.""" wcs = WCS(crval=(0, 0)) data = np.arange(30).reshape(6, 5) image1 = Image(data=data, wcs=wcs, var=np.ones(data.shape) * 0.5) image1.mask_region((2, 2), (1.5, 1.5), inside=False, unit_center=None, unit_radius=None) # The test data array looks as follows: # # ---- ---- ---- ---- ---- # ---- 6.0 7.0 8.0 ---- # ---- 11.0 12.0 13.0 ---- # ---- 16.0 17.0 18.0 ---- # ---- ---- ---- ---- ---- # ---- ---- ---- ---- ---- # # Where ---- signifies a masked value. # # After reducing both dimensions by a factor of 2, we should # get a data array of the following 6 means of 4 pixels each: # # ---- ---- => 6/1 ---- ---- => (7+8)/2 # ---- 6.0 7.0 8.0 # # ---- 11.0 => (11+16)/2 12.0 13.0 => (12+13+17+18)/4 # ---- 16.0 17.0 18.0 # # ---- ---- => ---- ---- ---- => ---- # ---- ---- ---- ---- expected = np.ma.array(data=[[6.0, 7.5], [13.5, 15], [0.0, 0.0]], mask=[[False, False], [False, False], [True, True]]) image2 = image1.rebin(2) assert_masked_allclose(image2.data, expected) image2 = image1.rebin(factor=(2, 2)) assert_masked_allclose(image2.data, expected) # The variances of the original pixels were all 0.5, so taking the # mean of N of these should give the mean a variance of 0.5/N. # Given the number of pixels averaged in each of the above means, # we thus expect the variance array to look as follows. expected = np.ma.array(data=[[0.5, 0.25], [0.25, 0.125], [0.0, 0.0]], mask=[[False, False], [False, False], [True, True]]) assert_masked_allclose(image2.var, expected) # Check the WCS information. start = image2.get_start() assert start[0] == 0.5 assert start[1] == 0.5
def test_add_image(tmpdir, source2, a478hst, a370II): """Source class: testing add_image method""" minicube = Cube(get_data_file('sdetect', 'minicube.fits'), dtype=float) source2.add_white_image(minicube) ima = minicube.mean(axis=0) # The position source2.dec, source2.ra corresponds # to pixel index 18.817,32.432 in the cube. The default 5 # arcsecond requested size of the white-light image corresponds # to 25 pixels. There will thus be 12 pixels on either side of # a central pixel. The nearest pixel to the center is 19,32, so # we expect add_white_image() to have selected the following # range of pixels cube[19-12:19+12+1, 32-12:32+12+1], which # is cube[7:32, 20:45]. However the cube only has 40 pixels # along the X-axis, so the white-light image should correspond # to: # # white[:,:] = cube[7:32, 20:40] # white.shape=(25,20) # # So: cube[15,25] = white[15-7, 25-20] = white[8, 5] assert ima[15, 25] == source2.images['MUSE_WHITE'][8, 5] # Add a square patch of an HST image equal in width and height # to the height of the white-light image, which has a height # of 25 white-light pixels. source2.add_image(a478hst, 'HST1') # Add the same HST image, but this time set the width and height # equal to the height of the above HST patch (ie. 50 pixels). This # should have the same result as giving it the same size as the # white-light image. size = source2.images['HST1'].shape[0] source2.add_image(a478hst, 'HST2', size=size, minsize=size, unit_size=None) assert source2.images['HST1'][10, 10] == source2.images['HST2'][10, 10] # Add the HST image again, but this time rotate it to the same # orientation as the white-light image, then check that they end # up with the same rotation angles. source2.add_image(a478hst, 'HST3', rotate=True) assert_almost_equal(source2.images['HST3'].get_rot(), source2.images['MUSE_WHITE'].get_rot(), 3) # Trying to add image not overlapping with Source assert source2.add_image(a370II, 'ERROR') is None white = source2.images['MUSE_WHITE'] mean, std = white.background() mask = white.data > (mean + 2 * std) mask = Image.new_from_obj(white, data=mask.astype(int)) mask.data = mask.data.astype(int) source2.add_image(mask, 'MYMASK') filename = str(tmpdir.join('source.fits')) source2.write(filename) src = Source.from_file(filename) assert src.images['MYMASK'].data.dtype == '>i8' assert_masked_allclose(mask.data, src.images['MYMASK'].data) with fits.open(filename) as hdul: 'IMA_MYMASK_DQ' in hdul assert (np.count_nonzero(hdul['IMA_MYMASK_DQ'].data) == np.count_nonzero(white.mask))
def test_bandpass_image(): """Cube class: testing bandpass_image""" shape = (7, 2, 2) # Create a rectangular shaped bandpass response whose ends are half # way into pixels. wavelengths = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) sensitivities = np.array([1.0, 1.0, 1.0, 1.0, 1.0]) # Specify a ramp for the values of the pixels in the cube versus # wavelength. spectral_values = np.arange(shape[0], dtype=float) # Specify a ramp for the variances of the pixels in the cube # versus wavelength. spectral_vars = np.arange(shape[0], dtype=float) * 0.5 # Calculate the expected weights versus wavelength for each # spectral pixels. The weight of each pixel is supposed to be # integral of the sensitivity function over the width of the pixel. # # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | Pixel indexes # _______________________ # _______| |_________ Sensitivities # # 0.0 0.5 1.0 1.0 1.0 0.5 0.0 Weights # 0.0 1.0 2.0 3.0 4.0 5.0 6.0 Pixel values vs wavelength # 0.0 0.5 2.0 3.0 4.0 2.5 0.0 Pixel values * weights weights = np.array([0.0, 0.5, 1.0, 1.0, 1.0, 0.5, 0.0]) # Compute the expected weighted mean of the spectral pixel values, # assuming that no pixels are unmasked. unmasked_mean = (weights * spectral_values).sum() / weights.sum() # Compute the expected weighted mean if pixel 1 is masked. masked_pixel = 1 masked_mean = (((weights * spectral_values).sum() - weights[masked_pixel] * spectral_values[masked_pixel]) / (weights.sum() - weights[masked_pixel])) # Compute the expected variances of the unmasked and masked means. unmasked_var = (weights**2 * spectral_vars).sum() / weights.sum()**2 masked_var = (((weights**2 * spectral_vars).sum() - weights[masked_pixel]**2 * spectral_vars[masked_pixel]) / (weights.sum() - weights[masked_pixel])**2) # Create the data array of the cube, giving all map pixels the # same data and variance spectrums. data = spectral_values[:, np.newaxis, np.newaxis] * np.ones(shape) var = spectral_vars[:, np.newaxis, np.newaxis] * np.ones(shape) # Create a mask with all pixels unmasked. mask = np.zeros(shape) # Mask spectral pixel 'masked_pixel' of map index 1,1. mask[masked_pixel, 1, 1] = True # Also mask all pixels of map pixel 0,0. mask[:, 0, 0] = True # Create a test cube with the above data and mask arrays. c = generate_cube(shape=shape, data=data, mask=mask, var=var, wave=WaveCoord(crval=0.0, cdelt=1.0, crpix=1.0, cunit=u.angstrom)) # Extract an image that has the above bandpass response. im = c.bandpass_image(wavelengths, sensitivities) # Only the map pixel in which all spectral pixels are masked should # be masked in the output, so just map pixel [0,0] should be masked. expected_mask = np.array([[True, False], [False, False]], dtype=bool) # What do we expect? expected_data = ma.array( data=[[unmasked_mean, unmasked_mean], [unmasked_mean, masked_mean]], mask=expected_mask) expected_var = ma.array( data=[[unmasked_var, unmasked_var], [unmasked_var, masked_var]], mask=expected_mask) # Are the results consistent with the predicted values? assert_masked_allclose(im.data, expected_data) assert_masked_allclose(im.var, expected_var)
def test_rebin(): """Cube class: testing rebin methods""" # Create spectral and spatial world coordinates that make each # pixel equal its index. wcs = WCS(crval=(0, 0), crpix=(1, 1), cdelt=(1.0, 1.0)) wave = WaveCoord(crval=0.0, crpix=1.0, cdelt=1.0) # Create a cube with even valued dimensions, filled with ones. data = ma.ones((4, 6, 8)) # Start with all pixels 1.0 data.reshape(4 * 6 * 8)[::2] = 0.0 # Set every second pixel to 0.0 data.mask = data < -1 # Unmask all pixels. cube1 = generate_cube(data=data.data, mask=data.mask, wcs=wcs, wave=wave) # Rebin each of the axes such as to leave only 2 pixels along each # dimension. factor = (2, 3, 4) cube2 = cube1.rebin(factor=factor) # Compute the expected output cube, given that the input cube is a # repeating pattern of 1,0, and we divided the x-axis by a # multiple of 2, the output pixels should all be 0.5. expected = ma.ones((2, 2, 2)) * 0.5 expected.mask = expected < 0 # All pixels unmasked. assert_masked_allclose(cube2.data, expected) # Do the same experiment but with the zero valued pixels all masked. data = ma.ones((4, 6, 8)) # Start with all pixels 1.0 data.reshape(4 * 6 * 8)[::2] = 0.0 # Set every second pixel to 0.0 data.mask = data < 0.1 # Mask the pixels that are 0.0 cube1 = generate_cube(data=data.data, mask=data.mask, wcs=wcs, wave=wave) # Rebin each of the axes such as to leave only 2 pixels along each # dimension. factor = np.array([2, 3, 4]) cube2 = cube1.rebin(factor=factor) # Compute the expected output cube. The zero valued pixels are all # masked, leaving just pixels with values of 1, so the mean that is # recorded in each output pixel should be 1.0. expected = ma.ones((2, 2, 2)) * 1.0 expected.mask = expected < 0 # All output pixels should be unmasked. assert_masked_allclose(cube2.data, expected) # Check that the world coordinates are correct. We averaged # factor[] pixels whose coordinates were equal to their pixel # indexes, so the coordinates of the first pixel of the rebinned # cube should be the mean of the first factor indexes along each # dimension. The sum from k=0 to factor-1 is # ((factor-1)*factor)/2, and dividing this by the number of pixels # gives (factor-1)/2. assert_allclose(np.asarray(cube2.get_start()), (factor - 1) / 2.0) # Create a cube that has a larger number of pixels along the # y and x axes of the images, so that we can divide those axes # by a number whose remainder is large enough to test selection # of the truncated part of the cube. shape = np.array([4, 17, 15]) data = ma.ones(shape) # Start with all pixels 1.0 data.reshape(shape.prod())[::2] = 0.0 # Set every second pixel to 0.0 data.mask = data < -1 # Unmask all pixels. cube1 = generate_cube(data=data.data, mask=data.mask, wcs=wcs, wave=wave) # Choose the rebinning factors such that there is a significant # remainder after dividing the final two dimensions of the cube by # the specified factor. We don't do this for the first axis because # we want the interleaved pattern of 0s and 1s to remain. factor = np.array([2, 7, 9]) cube2 = cube1.rebin(factor=factor, margin='origin') # Compute the expected output cube. Given that the input cube is a # repeating pattern of 1,0, and we divided the x-axis by a # multiple of 2, the output pixels should all be 0.5. expected_shape = cube1.shape // factor expected = ma.ones(expected_shape) * 0.5 expected.mask = expected < 0 # All pixels unmasked. assert_masked_allclose(cube2.data, expected) # We chose a margin value of 'origin', so that the outer corner of # pixel 0,0,0 of the input cube would also be the outer corner of # pixel 0,0,0 of the rebinned cube. The output world coordinates # can be calculated as described for the previous test. assert_allclose(np.asarray(cube2.get_start()), (factor - 1) / 2.0) # Do the same test, but with margin='center'. cube2 = cube1.rebin(factor=factor, margin='center') # Compute the expected output cube. The values should be the # same as the previous test. expected_shape = cube1.shape // factor expected = ma.ones(expected_shape) * 0.5 expected.mask = expected < 0 # All pixels unmasked. assert_masked_allclose(cube2.data, expected) # We chose a margin value of 'center'. We need to know which # pixels should have contributed to pixel 0,0,0. First calculate # how many pixels remain after dividing the shape by the reduction # factors. This is the number of extra pixels that should have been # discarded. Divide this by 2 to determine the number of pixels that # are removed from the start of each axis. cut = np.mod(shape, factor).astype(int) // 2 # The world coordinates of the input cube were equal to its pixel # indexes, and the world coordinates of a pixel of the output cube # is thus the mean of the indexes of the pixels that were combined # to make that pixel. In this case, we combine pixels # data[cut:cut+factor] along each axis. This can be calculated as # the sum of pixel indexes from 0 to cut+factor, minus the sum of # pixel indexes from 0 to cut, with the result divided by the number # of pixels that were averaged. Again we make use of the series sum, # sum[n=0..N] = (n*(n-1))/2. tmp = cut + factor assert_allclose(np.asarray(cube2.get_start()), ((tmp * (tmp - 1)) / 2.0 - (cut * (cut - 1)) / 2.0) / factor)