From 6ed2bdcc9f012b7c8c5c6d08293a9bb9f0938708 Mon Sep 17 00:00:00 2001 From: Juan Cabanela Date: Tue, 18 Jul 2023 15:17:58 -0500 Subject: [PATCH] pytests re-written (and Camera fixed to require units, which we also test for now). --- stellarphot/core.py | 39 ++++++++++++++++++++++------------ stellarphot/tests/test_core.py | 38 ++++++++++++++++++++++----------- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/stellarphot/core.py b/stellarphot/core.py index 94185037..4d9d9e50 100644 --- a/stellarphot/core.py +++ b/stellarphot/core.py @@ -1,9 +1,6 @@ from astropy import units as u from astropy.table import QTable, Table, Column - -from astropy import units as u from astropy.coordinates import EarthLocation, SkyCoord -from astropy.timeseries.core import BaseTimeSeries, autocheck_required_columns from astropy.time import Time import numpy as np @@ -23,7 +20,7 @@ class Camera: times the image data matches the unit of the `read_noise`. read_noise : `astropy.quantity.Quantity` - The read noise of the camera in units of electrons. + The read noise of the camera with units dark_current : `astropy.quantity.Quantity` The dark current of the camera in units such that, when multiplied by @@ -67,12 +64,27 @@ def __init__(self, gain=1.0 * u.electron / u.adu, read_noise=1.0 * u.electron, dark_current=0.01 * u.electron / u.second): super().__init__() - self.gain = gain - self.read_noise = read_noise - self.dark_current = dark_current + + # Check that input has units and if not crap out. + if isinstance(gain, u.Quantity): + self.gain = gain + else: + raise TypeError("gain must have units.") + + if isinstance(read_noise, u.Quantity): + self.read_noise = read_noise + else: + raise TypeError("read_noise must have units.") + + if isinstance(dark_current, u.Quantity): + self.dark_current = dark_current + else: + raise TypeError("dark_current must have units.") def copy(self): - return Camera(gain=self.gain, read_noise=self.read_noise, dark_current=self.dark_current) + return Camera(gain=self.gain, + read_noise=self.read_noise, + dark_current=self.dark_current) def __copy__(self): return self.copy() @@ -107,14 +119,15 @@ class BaseEnhancedTable(QTable): """ def __init__(self, table_description, data): - # Confirm a proper table description is passed (that is dict-like with keys and values) + # Confirm a proper table description is passed (that is dict-like with keys and + # values) try: self._table_description = {k: v for k, v in table_description.items()} except AttributeError: raise TypeError(f"You must provide a dict as table_description (it is type {type(self._table_description)}).") # Build the data table - if not isinstance(data, QTable): + if not isinstance(data, Table): raise TypeError(f"You must provide an astropy QTable as data (it is type {type(data)}).") else: # Check the format of the data table matches the table_description by checking @@ -263,7 +276,7 @@ def __init__(self, observatory, camera, data, passband_map=None, retain_user_co raise ValueError(f"data['date-obs'] astropy.time.Time must have scale='utc', not \'{data['date-obs'][0].scale}\'.") except AttributeError: # Happens if first item dosn't have a "scale" - raise ValueError(f"data['date-obs'] is not column of astropy.time.Time objects.") + raise ValueError("data['date-obs'] is not column of astropy.time.Time objects.") # Check for consistency of counts-related columns counts_columns = ['aperture_sum', 'annulus_sum', 'sky_per_pix_avg', 'sky_per_pix_med', @@ -296,7 +309,7 @@ def __init__(self, observatory, camera, data, passband_map=None, retain_user_co case 'snr': # Since noise in counts, the proper way to compute SNR is sqrt(gain)*counts/noise - self['snr'] = np.sqrt(self.camera.gain) * self['aperture_net_cnts'].value / self['noise'].value + self['snr'] = np.sqrt(self.camera.gain.value) * self['aperture_net_cnts'].value / self['noise'].value case 'bjd': self['bjd'] = self.add_bjd_col() @@ -317,7 +330,7 @@ def __init__(self, observatory, camera, data, passband_map=None, retain_user_co self['night'] = Column(data=np.array((Time(self['date-obs']) + shift).to_value('mjd'), dtype=int), name='night') case 'mag_inst': - self['mag_inst'] = -2.5 * np.log10(self.camera.gain * self['aperture_net_cnts'].value / + self['mag_inst'] = -2.5 * np.log10(self.camera.gain.value * self['aperture_net_cnts'].value / self['exposure'].value) case 'mag_error': diff --git a/stellarphot/tests/test_core.py b/stellarphot/tests/test_core.py index 26d6c139..dd00685c 100644 --- a/stellarphot/tests/test_core.py +++ b/stellarphot/tests/test_core.py @@ -9,15 +9,23 @@ def test_camera_attributes(): - gain = 2.0 - read_noise = 10 - dark_current = 0.01 + gain = 2.0 * u.electron / u.adu + read_noise = 10 * u.electron + dark_current = 0.01 * u.electron / u.second c = Camera(gain=gain, read_noise=read_noise, dark_current=dark_current) assert c.gain == gain assert c.dark_current == dark_current assert c.read_noise == read_noise +def test_camera_unitscheck(): + gain = 2.0 + read_noise = 10 + dark_current = 0.01 + with pytest.raises(TypeError): + c = Camera(gain=gain, read_noise=read_noise, dark_current=dark_current) + + # Create several test descriptions for use in base_enhanced_table tests. test_descript = {'id': None, 'ra': u.deg, @@ -40,7 +48,9 @@ def test_camera_attributes(): testdata = Table(data, names=colnames, dtype=coltypes, units=colunits) # Define some configuration information assuming Feder telescope -feder_cg_16m = Camera(gain=1.5, read_noise=10.0, dark_current=0.01) +feder_cg_16m = Camera(gain = 1.5 * u.electron / u.adu, + read_noise = 10.0 * u.electron, + dark_current=0.01 * u.electron / u.second) feder_passbands = {'up':'SU', 'gp':'SG', 'rp':'SR', 'zp':'SZ', 'ip':'SI'} feder_obs = EarthLocation(lat = 46.86678,lon=-96.45328, height=311) @@ -123,8 +133,8 @@ def test_base_enhanced_table_blank(): def test_base_enhanced_table_from_existing_table(): # Should create a populated dataset properly and display the astropy data test_base2 = BaseEnhancedTable(test_descript, testdata) - assert len(test_base2.data['ra']) == 1 - assert len(test_base2.data['dec']) == 1 + assert len(test_base2['ra']) == 1 + assert len(test_base2['dec']) == 1 def test_base_enhanced_table_no_inputs(): @@ -137,7 +147,7 @@ def test_base_enhanced_table_missing_column(): # Should raise exception because the RA data is missing from input data testdata_nora = testdata.copy() testdata_nora.remove_column('ra') - with pytest.raises(KeyError): + with pytest.raises(ValueError): test_base = BaseEnhancedTable(test_descript, testdata_nora) @@ -155,9 +165,9 @@ def test_photometry_data(): phot_data = PhotometryData(observatory=feder_obs, camera=feder_cg_16m, passband_map=feder_passbands, data=testphot_clean) # Check some aspects of that data are sound - assert phot_data.camera.gain == 1.5 - assert phot_data.camera.read_noise == 10.0 - assert phot_data.camera.dark_current == 0.01 + assert phot_data.camera.gain == 1.5 * u.electron / u.adu + assert phot_data.camera.read_noise == 10.0 * u.electron + assert phot_data.camera.dark_current == 0.01 * u.electron / u.second assert phot_data.observatory.lat.value == 46.86678 assert phot_data.observatory.lat.unit == u.deg assert phot_data.observatory.lon.value == -96.45328 @@ -175,7 +185,7 @@ def test_photometry_data(): # Dec 22 30 20.77733059 # which had a result of 2459910.775405664 (Uses IDL routine, astropy is SOFA checked). # Demand a difference of less than 1/20 of a second. - assert (phot_data.data['bjd'][0].value - 2459910.775405664)*86400 < 0.05 + assert (phot_data['bjd'][0].value - 2459910.775405664)*86400 < 0.05 def test_photometry_badtime(): with pytest.raises(ValueError): @@ -194,5 +204,9 @@ def test_photometry_inconsistent_computed_col_exists(): phot_data = PhotometryData(observatory=feder_obs, camera=feder_cg_16m, passband_map=feder_passbands, data=testphot_goodUnits) phot_data = PhotometryData(observatory=feder_obs, camera=feder_cg_16m, passband_map=feder_passbands, data=testphot_goodUnits, retain_user_computed=True) - assert np.abs(phot_data['snr'][0] - 46.795229859903905) < 1e-6 + # This keeps a bad user column for 'snr' which has bogus units, so check the units + # cause a crash in the math. + with pytest.raises(u.core.UnitConversionError): + assert np.abs(phot_data['snr'][0] - 46.795229859903905) < 1e-6 + assert np.abs(phot_data['snr'][0].value - 46.795229859903905) < 1e-6