diff --git a/gunagala/imager.py b/gunagala/imager.py index 5eed8f1..50b9fcd 100644 --- a/gunagala/imager.py +++ b/gunagala/imager.py @@ -250,7 +250,7 @@ def __init__(self, optic, camera, filters, psf, sky, num_imagers=1, num_per_comp # Construct a simple template WCS to store the focal plane configuration parameters self.wcs = WCS(naxis=2) - self.wcs._naxis2, self.wcs._naxis1 = self.camera.resolution.value.astype(int) + self.wcs.pixel_shape = self.camera.resolution.value.astype(int) self.wcs.wcs.crpix = [(self.camera.resolution[1].value - 1) / 2, (self.camera.resolution[0].value - 1) / 2] self.wcs.wcs.cdelt = [self.pixel_scale.to(u.degree / u.pixel).value, @@ -422,9 +422,8 @@ def extended_source_signal_noise(self, surface_brightness, filter_name, total_ex # Sky counts already included in _is_saturated, need to avoid counting them twice saturated = self._is_saturated( 0 * u.electron / (u.pixel * u.second), sub_exp_time, filter_name) - # np.where strips units, need to manually put them back. - signal = np.where(saturated, 0, signal) * u.electron / u.pixel - noise = np.where(saturated, 0, noise) * u.electron / u.pixel + signal = np.where(saturated, 0 * u.electron / u.pixel, signal) + noise = np.where(saturated, 0 * u.electron / u.pixel, noise) # Totals per (binned) pixel for all imagers. signal = signal * self.num_imagers * binning @@ -483,8 +482,7 @@ def extended_source_snr(self, surface_brightness, filter_name, total_exp_time, s signal, noise = self.extended_source_signal_noise(surface_brightness, filter_name, total_exp_time, sub_exp_time, calc_type, saturation_check, binning) - # np.where() strips units, need to manually put them back - snr = np.where(noise != 0.0, signal / noise, 0.0) * u.dimensionless_unscaled + snr = np.where(noise != 0.0, signal / noise, 0.0 * u.dimensionless_unscaled) return snr @@ -1003,9 +1001,8 @@ def point_source_signal_noise(self, brightness, filter_name, total_exp_time, sub # in a single sub exposure, and check against saturation_level. if saturation_check: saturated = self._is_saturated(rate * self.psf.peak, sub_exp_time, filter_name) - # np.where strips units, need to manually put them back. - signal = np.where(saturated, 0.0, signal) * u.electron - noise = np.where(saturated, 0.0, noise) * u.electron + signal = np.where(saturated, 0.0 * u.electron, signal) + noise = np.where(saturated, 0.0 * u.electron , noise) return signal, noise @@ -1046,8 +1043,7 @@ def point_source_snr(self, brightness, filter_name, total_exp_time, sub_exp_time signal, noise = self.point_source_signal_noise(brightness, filter_name, total_exp_time, sub_exp_time, saturation_check) - # np.where() strips units, need to manually put them back. - snr = np.where(noise != 0.0, signal / noise, 0.0) * u.dimensionless_unscaled + snr = np.where(noise != 0.0, signal / noise, 0.0 * u.dimensionless_unscaled) return snr @@ -1096,8 +1092,7 @@ def point_source_etc(self, brightness, filter_name, snr_target, sub_exp_time, sa # in a single sub exposure, and check against saturation_level. if saturation_check: saturated = self._is_saturated(rate * self.psf.peak, sub_exp_time, filter_name) - # np.where() strips units, need to manually put them back - total_exp_time = np.where(saturated, 0.0, total_exp_time) * u.second + total_exp_time = np.where(saturated, 0.0 * u.second, total_exp_time) return total_exp_time @@ -1507,6 +1502,7 @@ def snr_vs_ABmag(self, exp_times, filter_name, magnitude_interval=0.02 * u.ABmag filter_name=filter_name, total_exp_time=exp_time, sub_exp_time=exp_time) + # print(total_signals, signals, "LALA") total_signals += signals total_noises_squared += noises**2 @@ -1557,7 +1553,8 @@ def get_pixel_coords(self): of the centre of all the pixels in the image. """ # Arrays of pixel coordinates - XY = np.meshgrid(np.arange(self.wcs._naxis1), np.arange(self.wcs._naxis2)) + naxis1, naxis2 = self.wcs.pixel_shape + XY = np.meshgrid(np.arange(naxis1), np.arange(naxis2)) # Convert to arrays of RA, dec (ICRS, decimal degrees) try: @@ -1607,8 +1604,8 @@ def make_noiseless_image(self, if isinstance(self.psf, FittablePSF): raise NotImplementedError("Analytical PSFs currently don't work.\n They will take all system memory. See: https://github.com/AstroHuntsman/gunagala/pull/16#issuecomment-426844974 ") - electrons = np.zeros((self.wcs._naxis2, - self.wcs._naxis1)) * u.electron / (u.second * u.pixel) + naxis1, naxis2 = self.wcs.pixel_shape + electrons = np.zeros((naxis2, naxis1)) * u.electron / (u.second * u.pixel) self.set_WCS_centre(centre, **centre_kwargs) # Calculate observed sky background diff --git a/gunagala/tests/test_imager.py b/gunagala/tests/test_imager.py index ccfe25e..f5ba6d9 100644 --- a/gunagala/tests/test_imager.py +++ b/gunagala/tests/test_imager.py @@ -1,5 +1,6 @@ # Tests for the signal-to-noise module import pytest +from pytest import approx import numpy as np import astropy.units as u import astropy.constants as c @@ -336,11 +337,11 @@ def test_extended_rates(imager, filter_name): saturation_check=True) # Calculating surface brightness given exposure time and SNR should match original surface brightness - assert imager.rate_to_SB(rate, filter_name) == imager.extended_source_limit(total_exp_time=t_exp, + assert approx(imager.rate_to_SB(rate, filter_name).value) == imager.extended_source_limit(total_exp_time=t_exp, filter_name=filter_name, snr_target=snr, sub_exp_time=t_sub, - calc_type='per arcsecond squared') + calc_type='per arcsecond squared').value # Can't use pixel binning with per arcsecond squared signal, noise values with pytest.raises(ValueError): @@ -754,8 +755,8 @@ def test_get_pixel_coords_with_WCS_call_first(imager_function_scope): # now set WCS centre first, then try and get_pixel_coords imager_function_scope.set_WCS_centre(test_coord_string, unit='deg') centre_field_pixels = imager_function_scope.get_pixel_coords() - assert imager_function_scope.wcs._naxis1 == centre_field_pixels.shape[1] - assert imager_function_scope.wcs._naxis2 == centre_field_pixels.shape[0] + assert imager_function_scope.wcs.pixel_shape[0] == centre_field_pixels.shape[1] + assert imager_function_scope.wcs.pixel_shape[1] == centre_field_pixels.shape[0] def test_create_imagers(): diff --git a/gunagala/tests/test_psf.py b/gunagala/tests/test_psf.py index e932c54..e8085a9 100644 --- a/gunagala/tests/test_psf.py +++ b/gunagala/tests/test_psf.py @@ -7,12 +7,20 @@ @pytest.fixture(scope='module') def psf_moffat(): + return make_moffat() + + +def make_moffat(): psf = MoffatPSF(FWHM=1 / 30 * u.arcminute, shape=4.7) return psf @pytest.fixture(scope='module') def psf_pixellated(): + return make_pixellated() + + +def make_pixellated(): psf_data = np.array([[0.0, 0.0, 0.1, 0.0, 0.0], [0.0, 0.3, 0.7, 0.4, 0.0], [0.1, 0.8, 1.0, 0.6, 0.1], @@ -29,12 +37,12 @@ def psf_pixellated(): def test_base(): with pytest.raises(TypeError): # Try to instantiate abstract base class, should fail - psf_base = PSF(FWHM=1 / 30 * u.arcminute) + _ = PSF(FWHM=1 / 30 * u.arcminute) @pytest.mark.parametrize("psf, type", [ - (psf_moffat(), MoffatPSF), - (psf_pixellated(), PixellatedPSF)], + (make_moffat(), MoffatPSF), + (make_pixellated(), PixellatedPSF)], ids=["moffat", "pixellated"] ) def test_instance(psf, type): @@ -42,59 +50,46 @@ def test_instance(psf, type): assert isinstance(psf, PSF) -def test_pix(pix_psf): - assert isinstance(pix_psf, PixellatedPSF) - assert isinstance(pix_psf, PSF) +def test_pix(psf_pixellated): + assert isinstance(psf_pixellated, PixellatedPSF) + assert isinstance(psf_pixellated, PSF) -def test_FWHM(psf): - assert psf.FWHM == 2 * u.arcsecond - psf.FWHM = 4 * u.arcsecond - assert psf.FWHM == 1 / 15 * u.arcminute +def test_FWHM(psf_moffat): + assert psf_moffat.FWHM == 2 * u.arcsecond + psf_moffat.FWHM = 4 * u.arcsecond + assert psf_moffat.FWHM == 1 / 15 * u.arcminute with pytest.raises(ValueError): - psf.FWHM = -1 * u.degree - psf.FWHM = 2 * u.arcsecond - - -def test_pixel_scale(psf): - psf.pixel_scale = 2.85 * u.arcsecond / u.pixel - assert psf.pixel_scale == 2.85 * u.arcsecond / u.pixel - - -def test_pixel_scale_pix(pix_psf): - pix_psf.pixel_scale = (1 / 3) * u.arcsecond / u.pixel - assert pix_psf.pixel_scale == (1 / 3) * u.arcsecond / u.pixel - pix_psf.pixel_scale = (2 / 3) * u.arcsecond / u.pixel - - -def test_n_pix(psf): - assert psf.n_pix == 4.25754067000986 * u.pixel + psf_moffat.FWHM = -1 * u.degree + psf_moffat.FWHM = 2 * u.arcsecond -def test_n_pix_pix(pix_psf): - assert pix_psf.n_pix.to(u.pixel).value == pytest.approx(21.01351017) +def test_pixel_scale_pix(psf_pixellated): + psf_pixellated.pixel_scale = (1 / 3) * u.arcsecond / u.pixel + assert psf_pixellated.pixel_scale == (1 / 3) * u.arcsecond / u.pixel + psf_pixellated.pixel_scale = (2 / 3) * u.arcsecond / u.pixel -def test_peak(psf): - assert psf.peak == 0.7134084656751443 / u.pixel +def test_n_pix_pix(psf_pixellated): + assert psf_pixellated.n_pix.to(u.pixel).value == pytest.approx(21.0699454) -def test_peak_pix(pix_psf): - assert pix_psf.peak.to(1 / u.pixel).value == pytest.approx(0.08073066) +def test_peak_pix(psf_pixellated): + assert psf_pixellated.peak.to(1 / u.pixel).value == pytest.approx(0.08073066) -def test_shape(psf): - assert psf.shape == 4.7 - psf.shape = 2.5 - assert psf.shape == 2.5 +def test_shape(psf_moffat): + assert psf_moffat.shape == 4.7 + psf_moffat.shape = 2.5 + assert psf_moffat.shape == 2.5 with pytest.raises(ValueError): psf_moffat.FWHM = -1 * u.degree psf_moffat.FWHM = 2 * u.arcsecond @pytest.mark.parametrize("psf, t_pixel_scale, pixel_scale", [ - (psf_moffat(), 2.85, 2.85), - (psf_pixellated(), (1 / 3), (2 / 3))], + (make_moffat(), 2.85, 2.85), + (make_pixellated(), (1 / 3), (2 / 3))], ids=["moffat", "pixellated"] ) def test_pixel_scale(psf, t_pixel_scale, pixel_scale): @@ -104,8 +99,8 @@ def test_pixel_scale(psf, t_pixel_scale, pixel_scale): @pytest.mark.parametrize("psf, expected_n_pix, pixel_scale", [ - (psf_moffat(), 4.25754067000986, 2.85), - (psf_pixellated(), 21.06994544, (2 / 3))], + (make_moffat(), 4.25754067000986, 2.85), + (make_pixellated(), 21.06994544, (2 / 3))], ids=["moffat", "pixellated"] ) def test_n_pix(psf, expected_n_pix, pixel_scale): @@ -114,8 +109,8 @@ def test_n_pix(psf, expected_n_pix, pixel_scale): @pytest.mark.parametrize("psf, expected_peak, pixel_scale", [ - (psf_moffat(), 0.7134084656751443, 2.85), - (psf_pixellated(), 0.08073066, (2 / 3))], + (make_moffat(), 0.7134084656751443, 2.85), + (make_pixellated(), 0.08073066, (2 / 3))], ids=["moffat", "pixellated"] ) def test_peak(psf, expected_peak, pixel_scale): @@ -123,20 +118,11 @@ def test_peak(psf, expected_peak, pixel_scale): assert psf.peak.to(1 / (u.pixel)).value == pytest.approx(expected_peak) -def test_shape(psf_moffat): - assert psf_moffat.shape == 4.7 - psf_moffat.shape = 2.5 - assert psf_moffat.shape == 2.5 - with pytest.raises(ValueError): - psf_moffat.shape = 0.5 - psf_moffat.shape = 4.7 - - @pytest.mark.parametrize("psf, image_size", [ - (psf_moffat(), (21, 21)), - (psf_pixellated(), (21, 21)), - (psf_moffat(), (7, 9)), - (psf_pixellated(), (7, 9))], + (make_moffat(), (21, 21)), + (make_pixellated(), (21, 21)), + (make_moffat(), (7, 9)), + (make_pixellated(), (7, 9))], ids=["moffat_square", "pixellated_square", "moffat_rectangle", @@ -155,10 +141,10 @@ def test_pixellated_dimension(psf, image_size): @pytest.mark.parametrize("psf, offset", [ - (psf_moffat(), (0.0, 0.0)), - (psf_pixellated(), (0.0, 0.0)), - (psf_moffat(), (0.3, -0.7)), - (psf_pixellated(), (0.3, -0.7))], + (make_moffat(), (0.0, 0.0)), + (make_pixellated(), (0.0, 0.0)), + (make_moffat(), (0.3, -0.7)), + (make_pixellated(), (0.3, -0.7))], ids=["moffat_centre_offsets", "pixellated_centre_offsets", "moffat_noncentre_offsets", @@ -172,8 +158,8 @@ def test_offsets(psf, offset): @pytest.mark.parametrize("psf, test_size", [ - (psf_moffat(), (1.3, -1.3)), - (psf_pixellated(), (-1.3, 1.3))], + (make_moffat(), (1.3, -1.3)), + (make_pixellated(), (-1.3, 1.3))], ids=["moffat", "pixellated"] ) def test_pixellated_invalid_size(psf, test_size): diff --git a/gunagala/utils/__init__.py b/gunagala/utils/__init__.py index 5319f30..e4d98a8 100644 --- a/gunagala/utils/__init__.py +++ b/gunagala/utils/__init__.py @@ -123,9 +123,9 @@ def array_sequence_equal(array_sequence, reference=None): for array in array_sequence: try: assert (array == reference).all() - except (AttributeError, AssertionError): + except (AttributeError, ValueError, AssertionError): # Attribute error if array & reference different lengths, Assertion Error if same - # length but one or more elements differ in value. + # length but one or more elements differ in value. ValueError if different lengths. return False return True