diff --git a/proseco/catalog.py b/proseco/catalog.py index ede42dab..583bb0e3 100644 --- a/proseco/catalog.py +++ b/proseco/catalog.py @@ -114,9 +114,6 @@ def _get_aca_catalog(**kwargs): aca.set_stars(filter_near_fov=False) kwargs.pop('stars', None) - # Share a common dark map for memory / processing - kwargs['dark'] = aca.dark - aca.log('Starting get_acq_catalog') aca.acqs = get_acq_catalog(stars=aca.stars, **kwargs) diff --git a/proseco/core.py b/proseco/core.py index 4ac048bb..9814b488 100644 --- a/proseco/core.py +++ b/proseco/core.py @@ -17,6 +17,7 @@ from Ska.quatutil import radec2yagzag, yagzag2radec import agasc from Quaternion import Quat +from mica.archive.aca_dark import get_dark_cal_image from . import characteristics as ACA @@ -24,6 +25,9 @@ FIDS_CACHE = {} APL = AcaPsfLibrary() +# Cache recently retrieved images which are called with the same args/kwargs +get_dark_cal_image = functools.lru_cache(maxsize=6)(get_dark_cal_image) + def yagzag_to_radec(yag, zag, att): """ @@ -263,6 +267,7 @@ class AliasAttribute: The name is the lower case version of everything before ``Table`` in the subclass name, so GuideTable => 'guide'. """ + def __get__(self, instance, owner): if instance is None: # When called without an instance, return self to allow access @@ -514,7 +519,7 @@ class ACACatalogTable(BaseCatalogTable): name = 'aca_cat' # Catalog attributes, gets set in MetaAttribute or AliasAttribute - allowed_kwargs = set() + allowed_kwargs = set(['dark']) required_attrs = ('dither_acq', 'dither_guide', 'date') @@ -532,7 +537,6 @@ class ACACatalogTable(BaseCatalogTable): detector = MetaAttribute() sim_offset = MetaAttribute() focus_offset = MetaAttribute() - dark = MetaAttribute(pickle=False) stars = MetaAttribute(pickle=False) include_ids_acq = IntListMetaAttribute(default=[]) include_halfws_acq = IntListMetaAttribute(default=[]) @@ -544,6 +548,34 @@ class ACACatalogTable(BaseCatalogTable): print_log = MetaAttribute(default=False) log_info = MetaAttribute(default={}, is_kwarg=False) + @property + def dark(self): + if hasattr(self, '_dark'): + return self._dark + + # Dark current map handling. If asking for `dark` attribute without having set + # it then auto-fetch from mica. Note that get_dark_cal_image caches returned values + # using LRU cache on all params, so this is efficient for the different + # catalog tables. + self.log(f'Getting dark cal image at date={self.date} t_ccd={self.t_ccd:.1f}') + self.dark = get_dark_cal_image(date=self.date, select='before', + t_ccd_ref=self.t_ccd, + aca_image=True) + return self._dark + + @dark.setter + def dark(self, value): + if not isinstance(value, ACAImage): + assert value.shape == (1024, 1024) + value = ACAImage(value, row0=-512, col0=-512, copy=False) + + self._dark = value + + # Set pixel regions from ACA.bad_pixels to have acqs.dark=700000 (5.0 mag + # star) per pixel. + for r0, r1, c0, c1 in ACA.bad_pixels: + self._dark.aca[r0:r1 + 1, c0:c1 + 1] = ACA.bad_pixel_dark_current + def set_attrs_from_kwargs(self, **kwargs): for name, val in kwargs.items(): if name in self.allowed_kwargs: @@ -609,29 +641,6 @@ def set_attrs_from_kwargs(self, **kwargs): if not isinstance(dither, ACABox): setattr(self, dither_attr, ACABox(dither)) - # Dark current map handling. Either get from mica archive or from - # kwarg input. - if self.dark is None: - from mica.archive.aca_dark import get_dark_cal_image - self.log(f'Getting dark cal image at date={self.date} t_ccd={self.t_ccd:.1f}') - self.dark = get_dark_cal_image(date=self.date, select='before', - t_ccd_ref=self.t_ccd, aca_image=True) - elif not isinstance(self.dark, ACAImage): - self.dark = ACAImage(self.dark, row0=-512, col0=-512, copy=False) - - # Set pixel regions from ACA.bad_pixels to have acqs.dark=700000 (5.0 mag - # star) per pixel. - self.set_bad_pixels_in_dark() - - def set_bad_pixels_in_dark(self): - """ - Set pixel regions from ACA.bad_pixels to have acqs.dark=700000 (5.0 mag - star) per pixel. This will effectively spoil any star or fid. - - """ - for r0, r1, c0, c1 in ACA.bad_pixels: - self.dark.aca[r0:r1 + 1, c0:c1 + 1] = ACA.bad_pixel_dark_current - def set_stars(self, acqs=None, filter_near_fov=True): """Set the object ``stars`` attribute to an appropriate StarsTable object. @@ -891,6 +900,7 @@ def has_column_spoiler(self, cand, stars, mask=None): else: return False + # AGASC columns not needed (at this moment) for acq star selection. # Change as needed. AGASC_COLS_DROP = [ @@ -967,6 +977,7 @@ def get_logger(logger): else: def null_logger(*args, **kwargs): pass + return null_logger def get_catalog_for_plot(self): diff --git a/proseco/tests/test_catalog.py b/proseco/tests/test_catalog.py index 3947f967..fbdbf170 100644 --- a/proseco/tests/test_catalog.py +++ b/proseco/tests/test_catalog.py @@ -516,6 +516,28 @@ def offset(id, drow, dcol, dmag, rmult=1): assert aca.acqs['id'].tolist() == [101, 102, 103, 104] +def test_dark_property(): + """ + Test that in the case of a common t_ccd, all the dark current maps are + actually the same object. + + :return: None + """ + aca = get_aca_catalog(**STD_INFO) + for attr in ('acqs', 'guides', 'fids'): + assert aca.dark is getattr(aca, attr).dark + + kwargs = STD_INFO.copy() + del kwargs['t_ccd'] + kwargs['t_ccd_acq'] = -12.5 + kwargs['t_ccd_guide'] = -11.5 + aca = get_aca_catalog(**kwargs) + assert aca.dark is aca.guides.dark + assert aca.dark is aca.fids.dark + assert aca.dark is not aca.acqs.dark + assert aca.dark.mean() > aca.acqs.dark.mean() + + def test_dense_star_field_regress(): """ Test getting stars at the most dense star field in the sky. Taken from: