From 4bce99236299c04215dac6b97c84111af3005359 Mon Sep 17 00:00:00 2001 From: Teal Date: Mon, 19 Aug 2024 10:11:04 -0700 Subject: [PATCH] Add tags example to quickstart (#51) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit squashed 6 commits: * docs(quickstart): 🚧 Add first draft of tags exmaple. * docs(quickstart): 🚧 Tweak example classes. * docs(quickstart): ✍️ First full draft of quickstart. * docs(quickstart): ✍️ Add table with tag args * docs(nox): ✨ Add link to end of docs session. * docs: ✍️ Add clarifying comments to examples. --- astrodata/utils.py | 4 +- docs/quickstart.rst | 202 ++++++++++++++++++++++++++++++++++++++++++++ noxfile.py | 10 ++- 3 files changed, 213 insertions(+), 3 deletions(-) diff --git a/astrodata/utils.py b/astrodata/utils.py index 49d59f8f..cd4d5194 100644 --- a/astrodata/utils.py +++ b/astrodata/utils.py @@ -142,7 +142,7 @@ class TagSet(namedtuple("TagSet", "add remove blocked_by blocks if_present")): ) >>> TagSet({'BIAS', 'CAL'}) # doctest: +SKIP TagSet( - add={'BIAS', 'CAL'}, + add={'BIAS', 'CAL'}, # These tags are added to the global set remove=set(), blocked_by=set(), blocks=set(), @@ -151,7 +151,7 @@ class TagSet(namedtuple("TagSet", "add remove blocked_by blocks if_present")): >>> TagSet(remove={'BIAS', 'CAL'}) # doctest: +SKIP TagSet( add=set(), - remove={'BIAS', 'CAL'}, + remove={'BIAS', 'CAL'}, # These tags are removed from the global set blocked_by=set(), blocks=set(), if_present=set() diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 6de49ce1..c8dee4d7 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -336,6 +336,208 @@ get all descriptors from a class using the ``.descriptors`` attribute. You'll see that our ``airmass`` descriptor is available for the ``GMOSScienceAstroData`` class, but not for the ``GMOSAstroData`` class. +Tags +==== + +Tags are a way to categorize data in |astrodata|. They are meant to be +used to identify the type of data in a file, and can be used to filter +data when opening files. + +Tags are stored in the ``.tags`` attribute of an |AstroData| object. You +can add tags to a class by using the ``@astro_data_tag`` decorator on methods +that output tags. For example, we want to tag our ``GMOSScienceAstroData`` +class as ``'GMOS'`` and ``'SCIENCE'``: + +.. code-block:: python + + # Let's remove the GMOSScienceAstroData class from the factory to avoid conflicts. + factory.remove_class(GMOSScienceAstroData) + + from astrodata import astro_data_tag, TagSet + + class GMOSAstroDataTagged(GMOSAstroData): + """A class for GMOS science data with tags. + + Note: This still has all the methods from GMOSScienceAstroData! It is a + subset of the tags used for the GMOS instrument |AstroData| class. + """ + + @astro_data_tag + def _tag_instrument(self): + # tags = ['GMOS', self.instrument().upper().replace('-', '_')] + return TagSet(["GMOS"]) + + @astro_data_tag + def _tag_dark(self): + if self.phu.get("OBSTYPE") == "DARK": + return TagSet(["DARK", "CAL"], blocks=["IMAGE", "SPECT"]) + + @astro_data_tag + def _tag_arc(self): + if self.phu.get("OBSTYPE") == "ARC": + return TagSet(["ARC", "CAL"]) + + @astro_data_tag + def _tag_bias(self): + if self._tag_is_bias(): + return TagSet(["BIAS", "CAL"], blocks=["IMAGE", "SPECT"]) + + @astro_data_tag + def _tag_flat(self): + if self.phu.get("OBSTYPE") == "FLAT": + if self.phu.get("GRATING") == "MIRROR": + f1, f2 = self.phu.get("FILTER1"), self.phu.get("FILTER2") + # This kind of filter prevents imaging to be classified as FLAT + if any(("Hartmann" in f) for f in (f1, f2)): + return None + return TagSet(["GCALFLAT", "FLAT", "CAL"]) + + @astro_data_tag + def _tag_twilight(self): + if self.phu.get("OBJECT", "").upper() == "TWILIGHT": + # Twilight flats are of OBSTYPE == OBJECT, meaning that the generic + # FLAT tag won't be triggered. Add it explicitly. + return TagSet( + [ + "TWILIGHT", + "CAL", + "SLITILLUM" if self._tag_is_spect() else "FLAT", + ], + ) + + @astro_data_tag + def _tag_image_or_spect(self): + if self.phu.get('GRATING') == 'MIRROR': + return TagSet(['IMAGE']) + else: + return TagSet(['SPECT']) + + def _tag_is_bias(self): + if self.phu.get("OBSTYPE") == "BIAS": + return True + else: + return False + + def _tag_is_bpm(self): + if self.phu.get("OBSTYPE") == "BPM" or "BPMASK" in self.phu: + return True + else: + return False + + def _tag_is_spect(self): + pairs = ( + ('MASKTYP', 0), + ('MASKNAME', 'None'), + ('GRATING', 'MIRROR') + ) + + matches = (self.phu.get(kw) == value for (kw, value) in pairs) + if any(matches): + return False + return True + + factory.add_class(GMOSAstroDataTagged) + + +These tags were taken from the |DRAGONS| GMOS package, and exemplify some +basic and more complex tag usage in |astrodata|. For example, the ``_tag_dark`` +method will tag the data as ``'DARK'`` and ``'CAL'`` if the ``OBSTYPE`` is +``'DARK'``. The ``blocks`` argument is used to specify that the tags will +"block" other tags from being applied to the data, in this case the ``'IMAGE'`` +and ``'SPECT'`` tags. + ++------------------+----------------------------------------------------+ +| Tag group | Description | ++==================+====================================================+ +| ``add`` | Add the tag to the global set. | ++------------------+----------------------------------------------------+ +| ``remove`` | Remove the tag from the global set. | ++------------------+----------------------------------------------------+ +| ``blocked_by`` | Tags that, if present, will prevent the TagSet | +| | from being added. | ++------------------+----------------------------------------------------+ +| ``blocks`` | *Other* ``TagSet`` s containing these tags will | +| | not be added to the global set. | ++------------------+----------------------------------------------------+ +| ``if_present`` | This ``TagSet`` will only be added if the | +| | specified tags are present. | ++------------------+----------------------------------------------------+ + +Let's make new |AstroData| objects for our files and see what tags they have: + +.. code-block:: python + + all_ad_data = [] + + for f in files: + location = f"quickstart_data/{f}" + ad = astrodata.from_file(location) + print(f"Opened {ad.filename} with class {ad.__class__}") + print(f"Tags: {ad.tags}") + + all_ad_data.append(ad) + +The result: + +.. code-block:: text + + Opened N20170614S0201.fits with class + Tags: {'IMAGE', 'GMOS'} + Opened N20170614S0202.fits with class + Tags: {'IMAGE', 'GMOS'} + Opened N20170614S0203.fits with class + Tags: {'IMAGE', 'GMOS'} + Opened N20170614S0204.fits with class + Tags: {'IMAGE', 'GMOS'} + Opened N20170614S0205.fits with class + Tags: {'IMAGE', 'GMOS'} + Opened N20170613S0180.fits with class + Tags: {'BIAS', 'CAL', 'GMOS'} + Opened N20170613S0181.fits with class + Tags: {'BIAS', 'CAL', 'GMOS'} + Opened N20170613S0182.fits with class + Tags: {'BIAS', 'CAL', 'GMOS'} + Opened N20170613S0183.fits with class + Tags: {'BIAS', 'CAL', 'GMOS'} + Opened N20170613S0184.fits with class + Tags: {'BIAS', 'CAL', 'GMOS'} + Opened N20170615S0534.fits with class + Tags: {'BIAS', 'CAL', 'GMOS'} + Opened N20170615S0535.fits with class + Tags: {'BIAS', 'CAL', 'GMOS'} + Opened N20170615S0536.fits with class + Tags: {'BIAS', 'CAL', 'GMOS'} + Opened N20170615S0537.fits with class + Tags: {'BIAS', 'CAL', 'GMOS'} + Opened N20170615S0538.fits with class + Tags: {'BIAS', 'CAL', 'GMOS'} + Opened N20170702S0178.fits with class + Tags: {'FLAT', 'GMOS', 'TWILIGHT', 'IMAGE', 'CAL'} + Opened N20170702S0179.fits with class + Tags: {'FLAT', 'GMOS', 'TWILIGHT', 'IMAGE', 'CAL'} + Opened N20170702S0180.fits with class + Tags: {'FLAT', 'GMOS', 'TWILIGHT', 'IMAGE', 'CAL'} + Opened N20170702S0181.fits with class + Tags: {'FLAT', 'GMOS', 'TWILIGHT', 'IMAGE', 'CAL'} + Opened N20170702S0182.fits with class + Tags: {'FLAT', 'GMOS', 'TWILIGHT', 'IMAGE', 'CAL'} + Opened bpm_20170306_gmos-n_Ham_22_full_12amp.fits with class + Tags: {'SPECT', 'GMOS'} + + +Now, our data is automatically tagged with the appropriate tags when we open +the files, and we can use these tags to filter data when manipulating files. + +.. code-block:: python + + # Filter out all the bias frames. + for ad in all_ad_data: + if 'BIAS' in ad.tags: + print(f"{ad.filename} is a bias frame.") + +There are many more features available in |astrodata|, but this should give +you a good starting point for working with your own data. + Advanced Usage ============== diff --git a/noxfile.py b/noxfile.py index 7021273f..daeb8daf 100644 --- a/noxfile.py +++ b/noxfile.py @@ -671,9 +671,17 @@ def docs(session: nox.Session) -> None: # Build the documentation. # TODO(teald): Add nitpicky flag to fix warnings. -- Issue #42 - target = Path("_build") + target = Path("_build").absolute() session.run("rm", "-rf", target, external=True) session.run("sphinx-build", "-W", "docs", target) + session.log(f"You can find the documentation at: {str(target)}") + + index_loc = target / "index.html" + + session.log( + f"Your index can be opened in a browser with:\n" + f"{index_loc.absolute().as_uri()}" + ) def use_devpi_server(func):