diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 00000000..cb65069b --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 35651c053d6e451c5c1902a9e723d738 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/api/astrodata.AstroData.doctree b/.doctrees/api/astrodata.AstroData.doctree new file mode 100644 index 00000000..cd097fd9 Binary files /dev/null and b/.doctrees/api/astrodata.AstroData.doctree differ diff --git a/.doctrees/api/astrodata.AstroDataError.doctree b/.doctrees/api/astrodata.AstroDataError.doctree new file mode 100644 index 00000000..98f0f54c Binary files /dev/null and b/.doctrees/api/astrodata.AstroDataError.doctree differ diff --git a/.doctrees/api/astrodata.AstroDataMixin.doctree b/.doctrees/api/astrodata.AstroDataMixin.doctree new file mode 100644 index 00000000..59adaf74 Binary files /dev/null and b/.doctrees/api/astrodata.AstroDataMixin.doctree differ diff --git a/.doctrees/api/astrodata.NDAstroData.doctree b/.doctrees/api/astrodata.NDAstroData.doctree new file mode 100644 index 00000000..fc9aec5a Binary files /dev/null and b/.doctrees/api/astrodata.NDAstroData.doctree differ diff --git a/.doctrees/api/astrodata.Section.doctree b/.doctrees/api/astrodata.Section.doctree new file mode 100644 index 00000000..bab2b726 Binary files /dev/null and b/.doctrees/api/astrodata.Section.doctree differ diff --git a/.doctrees/api/astrodata.TagSet.doctree b/.doctrees/api/astrodata.TagSet.doctree new file mode 100644 index 00000000..eedb9e82 Binary files /dev/null and b/.doctrees/api/astrodata.TagSet.doctree differ diff --git a/.doctrees/api/astrodata.add_header_to_table.doctree b/.doctrees/api/astrodata.add_header_to_table.doctree new file mode 100644 index 00000000..6d73e408 Binary files /dev/null and b/.doctrees/api/astrodata.add_header_to_table.doctree differ diff --git a/.doctrees/api/astrodata.astro_data_descriptor.doctree b/.doctrees/api/astrodata.astro_data_descriptor.doctree new file mode 100644 index 00000000..7cad4102 Binary files /dev/null and b/.doctrees/api/astrodata.astro_data_descriptor.doctree differ diff --git a/.doctrees/api/astrodata.astro_data_tag.doctree b/.doctrees/api/astrodata.astro_data_tag.doctree new file mode 100644 index 00000000..2ada2beb Binary files /dev/null and b/.doctrees/api/astrodata.astro_data_tag.doctree differ diff --git a/.doctrees/api/astrodata.create.doctree b/.doctrees/api/astrodata.create.doctree new file mode 100644 index 00000000..d10df415 Binary files /dev/null and b/.doctrees/api/astrodata.create.doctree differ diff --git a/.doctrees/api/astrodata.from_file.doctree b/.doctrees/api/astrodata.from_file.doctree new file mode 100644 index 00000000..2f095291 Binary files /dev/null and b/.doctrees/api/astrodata.from_file.doctree differ diff --git a/.doctrees/api/astrodata.open.doctree b/.doctrees/api/astrodata.open.doctree new file mode 100644 index 00000000..ef35e5dd Binary files /dev/null and b/.doctrees/api/astrodata.open.doctree differ diff --git a/.doctrees/api/astrodata.returns_list.doctree b/.doctrees/api/astrodata.returns_list.doctree new file mode 100644 index 00000000..da7d2811 Binary files /dev/null and b/.doctrees/api/astrodata.returns_list.doctree differ diff --git a/.doctrees/api/astrodata.version.doctree b/.doctrees/api/astrodata.version.doctree new file mode 100644 index 00000000..fe4fdf47 Binary files /dev/null and b/.doctrees/api/astrodata.version.doctree differ diff --git a/.doctrees/api_short.doctree b/.doctrees/api_short.doctree new file mode 100644 index 00000000..15796165 Binary files /dev/null and b/.doctrees/api_short.doctree differ diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle new file mode 100644 index 00000000..ed595813 Binary files /dev/null and b/.doctrees/environment.pickle differ diff --git a/.doctrees/examples/gemini_examples/index.doctree b/.doctrees/examples/gemini_examples/index.doctree new file mode 100644 index 00000000..e0c3d110 Binary files /dev/null and b/.doctrees/examples/gemini_examples/index.doctree differ diff --git a/.doctrees/examples/generic_examples/index.doctree b/.doctrees/examples/generic_examples/index.doctree new file mode 100644 index 00000000..efd69225 Binary files /dev/null and b/.doctrees/examples/generic_examples/index.doctree differ diff --git a/.doctrees/examples/index.doctree b/.doctrees/examples/index.doctree new file mode 100644 index 00000000..dfa2b4ce Binary files /dev/null and b/.doctrees/examples/index.doctree differ diff --git a/.doctrees/index.doctree b/.doctrees/index.doctree new file mode 100644 index 00000000..d1f9adbd Binary files /dev/null and b/.doctrees/index.doctree differ diff --git a/.doctrees/manuals/appendix_descriptors.doctree b/.doctrees/manuals/appendix_descriptors.doctree new file mode 100644 index 00000000..5b006217 Binary files /dev/null and b/.doctrees/manuals/appendix_descriptors.doctree differ diff --git a/.doctrees/manuals/cheatsheet.doctree b/.doctrees/manuals/cheatsheet.doctree new file mode 100644 index 00000000..7f1468bd Binary files /dev/null and b/.doctrees/manuals/cheatsheet.doctree differ diff --git a/.doctrees/manuals/full_api.doctree b/.doctrees/manuals/full_api.doctree new file mode 100644 index 00000000..c68385bf Binary files /dev/null and b/.doctrees/manuals/full_api.doctree differ diff --git a/.doctrees/manuals/index.doctree b/.doctrees/manuals/index.doctree new file mode 100644 index 00000000..84fd2a65 Binary files /dev/null and b/.doctrees/manuals/index.doctree differ diff --git a/.doctrees/manuals/progmanual/adclass.doctree b/.doctrees/manuals/progmanual/adclass.doctree new file mode 100644 index 00000000..80209d77 Binary files /dev/null and b/.doctrees/manuals/progmanual/adclass.doctree differ diff --git a/.doctrees/manuals/progmanual/containers.doctree b/.doctrees/manuals/progmanual/containers.doctree new file mode 100644 index 00000000..686512ba Binary files /dev/null and b/.doctrees/manuals/progmanual/containers.doctree differ diff --git a/.doctrees/manuals/progmanual/descriptors.doctree b/.doctrees/manuals/progmanual/descriptors.doctree new file mode 100644 index 00000000..0b5cadcc Binary files /dev/null and b/.doctrees/manuals/progmanual/descriptors.doctree differ diff --git a/.doctrees/manuals/progmanual/design.doctree b/.doctrees/manuals/progmanual/design.doctree new file mode 100644 index 00000000..957ccaa8 Binary files /dev/null and b/.doctrees/manuals/progmanual/design.doctree differ diff --git a/.doctrees/manuals/progmanual/index.doctree b/.doctrees/manuals/progmanual/index.doctree new file mode 100644 index 00000000..b602155e Binary files /dev/null and b/.doctrees/manuals/progmanual/index.doctree differ diff --git a/.doctrees/manuals/progmanual/intro.doctree b/.doctrees/manuals/progmanual/intro.doctree new file mode 100644 index 00000000..50ea7d85 Binary files /dev/null and b/.doctrees/manuals/progmanual/intro.doctree differ diff --git a/.doctrees/manuals/progmanual/tags.doctree b/.doctrees/manuals/progmanual/tags.doctree new file mode 100644 index 00000000..5c099aec Binary files /dev/null and b/.doctrees/manuals/progmanual/tags.doctree differ diff --git a/.doctrees/manuals/usermanual/data.doctree b/.doctrees/manuals/usermanual/data.doctree new file mode 100644 index 00000000..a9cf6db7 Binary files /dev/null and b/.doctrees/manuals/usermanual/data.doctree differ diff --git a/.doctrees/manuals/usermanual/headers.doctree b/.doctrees/manuals/usermanual/headers.doctree new file mode 100644 index 00000000..23410fb9 Binary files /dev/null and b/.doctrees/manuals/usermanual/headers.doctree differ diff --git a/.doctrees/manuals/usermanual/index.doctree b/.doctrees/manuals/usermanual/index.doctree new file mode 100644 index 00000000..ff98855e Binary files /dev/null and b/.doctrees/manuals/usermanual/index.doctree differ diff --git a/.doctrees/manuals/usermanual/intro.doctree b/.doctrees/manuals/usermanual/intro.doctree new file mode 100644 index 00000000..7abb2623 Binary files /dev/null and b/.doctrees/manuals/usermanual/intro.doctree differ diff --git a/.doctrees/manuals/usermanual/iomef.doctree b/.doctrees/manuals/usermanual/iomef.doctree new file mode 100644 index 00000000..701572d5 Binary files /dev/null and b/.doctrees/manuals/usermanual/iomef.doctree differ diff --git a/.doctrees/manuals/usermanual/structure.doctree b/.doctrees/manuals/usermanual/structure.doctree new file mode 100644 index 00000000..76ea030c Binary files /dev/null and b/.doctrees/manuals/usermanual/structure.doctree differ diff --git a/.doctrees/manuals/usermanual/tables.doctree b/.doctrees/manuals/usermanual/tables.doctree new file mode 100644 index 00000000..d7d67a96 Binary files /dev/null and b/.doctrees/manuals/usermanual/tables.doctree differ diff --git a/.doctrees/manuals/usermanual/tags.doctree b/.doctrees/manuals/usermanual/tags.doctree new file mode 100644 index 00000000..d21c0a7e Binary files /dev/null and b/.doctrees/manuals/usermanual/tags.doctree differ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/_modules/astrodata.html b/_modules/astrodata.html new file mode 100644 index 00000000..ba679b60 --- /dev/null +++ b/_modules/astrodata.html @@ -0,0 +1,213 @@ + + + + + + astrodata — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for astrodata

+"""This package adds an abstraction layer to astronomical data by parsing the
+information contained in the headers as attributes. To do so, one must subclass
+:class:`astrodata.AstroData` and add parse methods accordingly to the
+:class:`~astrodata.TagSet` received.
+
+"""
+
+from .core import AstroData
+from .adfactory import AstroDataFactory, AstroDataError
+from .fits import add_header_to_table
+from .nddata import NDAstroData, AstroDataMixin
+from .utils import (
+    Section,
+    TagSet,
+    astro_data_descriptor,
+    astro_data_tag,
+    returns_list,
+    deprecated,
+)
+
+import importlib.metadata
+
+
+__version__ = importlib.metadata.version("astrodata")
+
+
+
+[docs] +def version(): + """Return the version of astrodata.""" + return __version__
+ + + +__all__ = [ + "AstroData", + "AstroDataError", + "AstroDataMixin", + "NDAstroData", + "Section", + "TagSet", + "__version__", + "add_header_to_table", + "astro_data_descriptor", + "astro_data_tag", + "from_file", + "create", + "returns_list", + "version", + # Below this are deprecated + "open", +] + +# Make sure __all__does not have duplicates +if len(__all__) != len(set(__all__)): + duplicates = [x for i, x in enumerate(__all__) if x in __all__[:i]] + raise ValueError(f"Duplicate entries in __all__: {', '.join(duplicates)}") + +factory = AstroDataFactory() + +# Let's make sure that there's at least one class that matches the data +# (if we're dealing with a FITS file) +factory.add_class(AstroData) + + +
+[docs] +def from_file(*args, **kwargs): + """Return an |AstroData| object from a file. + + For implementation details, see + :meth:`~astrodata.AstroDataFactory.get_astro_data`. + """ + return factory.get_astro_data(*args, **kwargs)
+ + + +
+[docs] +def create(*args, **kwargs): + """Return an |AstroData| object from data. + + For implementation details, see + :meth:`~astrodata.AstroDataFactory.create_from_scratch` + """ + return factory.create_from_scratch(*args, **kwargs)
+ + + +# Without raising a warning or error. +
+[docs] +@deprecated( + "Use 'astrodata.from_file'. astrodata.open is deprecated, " + "and will be removed in a future version." +) +def open(*args, **kwargs): # pylint: disable=redefined-builtin + """Return an |AstroData| object from a file (deprecated, use + :func:`~astrodata.from_file`). + """ + return from_file(*args, **kwargs)
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/astrodata/adfactory.html b/_modules/astrodata/adfactory.html new file mode 100644 index 00000000..fd72e557 --- /dev/null +++ b/_modules/astrodata/adfactory.html @@ -0,0 +1,370 @@ + + + + + + astrodata.adfactory — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for astrodata.adfactory

+"""Factory for AstroData objects."""
+
+import copy
+import logging
+import os
+from contextlib import contextmanager
+from copy import deepcopy
+
+from astropy.io import fits
+
+from .utils import deprecated
+
+LOGGER = logging.getLogger(__name__)
+
+
+
+[docs] +class AstroDataError(Exception): + """Exception raised when there is a problem with the AstroData class."""
+ + + +class AstroDataFactory: + """Factory class for AstroData objects.""" + + _file_openers = (fits.open,) + + def __init__(self): + self._registry = set() + + @property + def registry(self): + """Return the registry of classes.""" + # Shallow copy -- just don't want the set to be modified, but the + # classes don't need to be copied. + return copy.copy(self._registry) + + @staticmethod + @deprecated( + "Renamed to _open_file, please use that method instead: " + "astrodata.factory.AstroDataFactory._open_file" + ) + @contextmanager + def _openFile(source): # pylint: disable=invalid-name + return AstroDataFactory._open_file(source) + + @staticmethod + @contextmanager + def _open_file(source): + """Internal static method that takes a ``source``, assuming that it is + a string pointing to a file to be opened. + + If this is the case, it will try to open the file and return an + instance of the appropriate native class to be able to manipulate it + (eg. ``HDUList``). + + If ``source`` is not a string, it will be returned verbatim, assuming + that it represents an already opened file. + """ + if isinstance(source, (str, os.PathLike)): + # Check that the file exists. + if not os.path.isfile(source): + raise FileNotFoundError(f"Path is not a file: {source}") + + # Check that the file has nonzero size. + stats = os.stat(source) + + if stats.st_size == 0: + LOGGER.warning("File %s is zero size", source) + + if not AstroDataFactory._file_openers: + raise AstroDataError( + "No file openers registered. Register some using" + " 'add_class' method." + ) + + # try vs all handlers + for func in AstroDataFactory._file_openers: + try: + fp = func(source) + yield fp + + # Catch keyboard interrupts and re-raise them. + except KeyboardInterrupt: + raise + + except Exception as err: # pylint: disable=broad-except + LOGGER.error( + "Failed to open %s with %s, got error: %s", + source, + func, + err, + ) + + # Handle nonexistent files. + if isinstance(err, FileNotFoundError): + raise err + + else: + if hasattr(fp, "close"): + fp.close() + + return + + raise AstroDataError( + f"No access, or not supported format for: {source}" + ) + + yield source + + @deprecated( + "Renamed to add_class, please use that method instead: " + "astrodata.factory.AstroDataFactory.add_class" + ) + def addClass(self, cls): # pylint: disable=invalid-name + """Add a new class to the AstroDataFactory registry. It will be used + when instantiating an AstroData class for a FITS file. + """ + self.add_class(cls) + + def add_class(self, cls): + """Add a new class to the AstroDataFactory registry. It will be used + when instantiating an AstroData class for a FITS file. + """ + if not hasattr(cls, "_matches_data"): + raise AttributeError( + f"Class '{cls.__name__}' has no '_matches_data' method" + ) + + self._registry.add(cls) + + def remove_class(self, cls: type | str): + """Remove a class from the AstroDataFactory registry.""" + if isinstance(cls, str): + cls = next((c for c in self._registry if c.__name__ == cls), None) + + self._registry.remove(cls) + + @deprecated( + "Renamed to get_astro_data, please use that method instead: " + "astrodata.factory.AstroDataFactory.get_astro_data" + ) + def getAstroData(self, source): # pylint: disable=invalid-name + """Deprecated, see |get_astro_data|.""" + self.get_astro_data(source) + + def get_astro_data(self, source): + """Takes either a string (with the path to a file) or an HDUList as + input, and tries to return an AstroData instance. + + It will raise exceptions if the file is not found, or if there is no + match for the HDUList, among the registered AstroData classes. + + Returns an instantiated object, or raises AstroDataError if it was + not possible to find a match + + Parameters + ---------- + source : `str` or `pathlib.Path` or `fits.HDUList` + The file path or HDUList to read. + """ + candidates = [] + with self._open_file(source) as opened: + for adclass in self._registry: + try: + if adclass.matches_data(opened): + candidates.append(adclass) + + except KeyboardInterrupt: + raise + + except Exception as err: + LOGGER.error( + "Failed to open %s with %s, got error: %s", + source, + adclass, + err, + ) + + # For every candidate in the list, remove the ones that are base + # classes for other candidates. That way we keep only the more + # specific ones. + final_candidates = [] + + for cnd in candidates: + if any(cnd in x.mro() for x in candidates if x != cnd): + continue + + final_candidates.append(cnd) + + if len(final_candidates) > 1: + raise AstroDataError( + f"More than one class is candidate for this dataset: " + f"{', '.join((str(s) for s in final_candidates))}" + ) + + if not final_candidates: + raise AstroDataError("No class matches this dataset") + + return final_candidates[0].read(source) + + @deprecated( + "Renamed to create_from_scratch, please use that method instead: " + "astrodata.factory.AstroDataFactory.create_from_scratch" + ) + def createFromScratch( + self, + phu, + extensions=None, + ): # pylint: disable=invalid-name + """Deprecated, see |create_from_scratch|.""" + self.create_from_scratch(phu=phu, extensions=extensions) + + def create_from_scratch(self, phu, extensions=None): + """Creates an AstroData object from a collection of objects. + + Parameters + ---------- + phu : `fits.PrimaryHDU` or `fits.Header` or `dict` or `list` + FITS primary HDU or header, or something that can be used to create + a fits.Header (a dict, a list of "cards"). + + extensions : list of HDUs + List of HDU objects. + + Returns + ------- + `astrodata.AstroData` + An AstroData instance. + + Raises + ------ + ValueError + If ``phu`` is not a valid object. + """ + lst = fits.HDUList() + if phu is not None: + if isinstance(phu, fits.PrimaryHDU): + lst.append(deepcopy(phu)) + + elif isinstance(phu, fits.Header): + lst.append(fits.PrimaryHDU(header=deepcopy(phu))) + + elif isinstance(phu, (dict, list, tuple)): + p = fits.PrimaryHDU() + p.header.update(phu) + lst.append(p) + + else: + raise ValueError( + "phu must be a PrimaryHDU or a valid header object" + ) + + if extensions is not None: + for ext in extensions: + lst.append(ext) + + return self.get_astro_data(lst) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/astrodata/core.html b/_modules/astrodata/core.html new file mode 100644 index 00000000..93eb513b --- /dev/null +++ b/_modules/astrodata/core.html @@ -0,0 +1,1748 @@ + + + + + + astrodata.core — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for astrodata.core

+"""This is the core module of the AstroData package. It provides the
+`AstroData` class, which is the main interface to manipulate astronomical
+data sets.
+"""
+
+import inspect
+import logging
+import os
+import re
+import textwrap
+import warnings
+from collections import OrderedDict
+from contextlib import suppress
+from copy import deepcopy
+from functools import partial
+
+import numpy as np
+
+from astropy.io import fits
+from astropy.nddata import NDData
+from astropy.table import Table
+from astropy.utils import format_doc
+
+from .fits import (
+    DEFAULT_EXTENSION,
+    FitsHeaderCollection,
+    _process_table,
+    read_fits,
+    write_fits,
+)
+from .nddata import ADVarianceUncertainty
+from .nddata import NDAstroData
+from .utils import (
+    assign_only_single_slice,
+    astro_data_descriptor,
+    deprecated,
+    normalize_indices,
+    returns_list,
+)
+
+NO_DEFAULT = object()
+
+
+_ARIT_DOC = """
+    Performs {name} by evaluating ``self {op} operand``.
+
+    Parameters
+    ----------
+    oper : number or object
+        The operand to perform the operation  ``self {op} operand``.
+
+    Returns
+    --------
+    `AstroData` instance
+"""
+
+
+
+[docs] +class AstroData: + """Base class for the AstroData software package. It provides an interface + to manipulate astronomical data sets. + + Parameters + ---------- + nddata : `astrodata.NDAstroData` or list of `astrodata.NDAstroData` + List of NDAstroData objects. + + tables : dict[name, `astropy.table.Table`] + Dict of table objects. + + phu : `astropy.io.fits.Header` + Primary header. + + indices : list of int + List of indices mapping the `astrodata.NDAstroData` objects that this + object will access to. This is used when slicing an object, then the + sliced AstroData will have the ``.nddata`` list from its parent and + access the sliced NDAstroData through this list of indices. + """ + + # Derived classes may provide their own __keyword_dict. Being a private + # variable, each class will preserve its own, and there's no risk of + # overriding the whole thing + __keyword_dict = { + "instrument": "INSTRUME", + "object": "OBJECT", + "telescope": "TELESCOP", + "ut_date": "DATE-OBS", + } + +
+[docs] + def __init__( + self, nddata=None, tables=None, phu=None, indices=None, is_single=False + ): + if nddata is None: + nddata = [] + + # Check that nddata is either a single or iterable of NDAstroData + # objects + is_nddata = isinstance(nddata, NDAstroData) + + try: + is_nddata_iterable = isinstance(nddata[0], NDAstroData) + + except IndexError: + # Fall back on checking if it's a list or tuple---could be empty. + is_nddata_iterable = isinstance(nddata, (list, tuple)) + + if not (is_nddata or is_nddata_iterable): + raise TypeError( + f"nddata must be an NDAstroData object or a list of " + f"NDAstroData objects, not {type(nddata)} ({nddata})." + ) + + # If nddata is a single NDAstroData object, make it a list. + if not is_nddata_iterable: + nddata = [nddata] + + # _all_nddatas contains all the extensions from the original file or + # object. And _indices is used to map extensions for sliced objects. + self._all_nddatas = nddata + self._indices = indices + + self.is_single = is_single + + if tables is not None and not isinstance(tables, dict): + raise ValueError("tables must be a dict") + + self._tables = tables or {} + + self._phu = phu or fits.Header() + self._fixed_settable = { + "data", + "uncertainty", + "mask", + "variance", + "wcs", + "path", + "filename", + } + self._logger = logging.getLogger(__name__) + self._orig_filename = None + self._path = None
+ + +
+[docs] + def __deepcopy__(self, memo): + """Returns a new instance of this class. + + Parameters + ---------- + memo : dict + See the documentation on `deepcopy` for an explanation on how + this works. + + """ + obj = self.__class__() + + for attr in ("_phu", "_path", "_orig_filename", "_tables"): + obj.__dict__[attr] = deepcopy(self.__dict__[attr]) + + obj.__dict__["_all_nddatas"] = [deepcopy(nd) for nd in self._nddata] + return obj
+ + + def _keyword_for(self, name): + """Returns the FITS keyword name associated to ``name``. + + Parameters + ---------- + name : str + The common "key" name for which we want to know the associated + FITS keyword. + + Returns + ------- + str + The desired keyword name. + + Raises + ------ + AttributeError + If there is no keyword for the specified ``name``. + + """ + for cls in self.__class__.mro(): + with suppress(AttributeError, KeyError): + # __keyword_dict is a mangled variable + return getattr(self, f"_{cls.__name__}__keyword_dict")[name] + + raise AttributeError(f"No match for '{name}'") + + def _process_tags(self): + """Return the tag set (as a set of str) for the current instance.""" + results = [] + # Calling inspect.getmembers on `self` would trigger all the + # properties (tags, phu, hdr, etc.), and that's undesirable. To + # prevent that, we'll inspect the *class*. + members = inspect.getmembers( + self.__class__, lambda x: hasattr(x, "tag_method") + ) + + for _, method in members: + ts = method(self) + if ts.add or ts.remove or ts.blocks: + results.append(ts) + + # Sort by the length of substractions... those that substract + # from others go first + results = sorted( + results, key=lambda x: len(x.remove) + len(x.blocks), reverse=True + ) + + # Sort by length of blocked_by, those that are never disabled go first + results = sorted(results, key=lambda x: len(x.blocked_by)) + + # Sort by length of if_present... those that need other tags to + # be present go last + results = sorted(results, key=lambda x: len(x.if_present)) + + tags = set() + removals = set() + blocked = set() + for plus, minus, blocked_by, blocks, is_present in results: + if is_present: + # If this TagSet requires other tags to be present, make + # sure that all of them are. Otherwise, skip... + if len(tags & is_present) != len(is_present): + continue + + allowed = (len(tags & blocked_by) + len(plus & blocked)) == 0 + if allowed: + # This set is not being blocked by others... + removals.update(minus) + tags.update(plus - removals) + blocked.update(blocks) + + return tags + +
+[docs] + @classmethod + def matches_data(cls, source) -> bool: + """Returns True if the class can handle the data in the source. + + Parameters + ---------- + source : list of `astropy.io.fits.HDUList` + The FITS file to be read. + + Returns + ------- + bool + True if the class can handle the data in the source. + + Note + ---- + Typically, this method is implemented by the static method + `Astrodata._matches_data` or by a class method with the same signature + for subclasses. + + If you are implementing a subclass, you should override _matches_data + instead, which is a static method that takes a single argument, the + source data, and returns a boolean. + + If that method is not overridden, this method will call it with the + source data as argument. + + For more information, see the documentation for the + :py:meth:`~AstroData._matches_data` and the |DeveloperGuide|. + """ + return cls._matches_data(source)
+ + + @staticmethod + def _matches_data(source): + # This one is trivial. Will be more specific for subclasses. + logging.debug("Using default _matches_data with %s", source) + return True + + @property + def path(self): + """Return the file path.""" + return self._path + + @path.setter + def path(self, value): + if self._path is None and value is not None: + self._orig_filename = os.path.basename(value) + self._path = value + + @property + def filename(self): + """Return the file name.""" + if self.path is not None: + return os.path.basename(self.path) + + return self.path + + @filename.setter + def filename(self, value): + if os.path.isabs(value): + raise ValueError("Cannot set the filename to an absolute path!") + + if self.path is None: + self.path = os.path.abspath(value) + + else: + dirname = os.path.dirname(self.path) + self.path = os.path.join(dirname, value) + + @property + def orig_filename(self): + """Return the original file name (before it was modified).""" + return self._orig_filename + + @orig_filename.setter + def orig_filename(self, value): + self._orig_filename = value + + @property + def phu(self): + """Return the primary header.""" + return self._phu + + @phu.setter + def phu(self, phu): + self._phu = phu + + @property + def hdr(self): + """Return all headers, as a `astrodata.fits.FitsHeaderCollection`.""" + if not self.nddata: + return None + headers = [nd.meta["header"] for nd in self._nddata] + return headers[0] if self.is_single else FitsHeaderCollection(headers) + + @property + @deprecated( + "Access to headers through this property is deprecated and " + "will be removed in the future. Use '.hdr' instead." + ) + def header(self): + """Deprecated header access. Use ``.hdr`` instead.""" + return [self.phu] + [ndd.meta["header"] for ndd in self._nddata] + + @property + def tags(self): + """A set of strings that represent the tags defining this instance.""" + return self._process_tags() + + @property + def descriptors(self): + """Returns a sequence of names for the methods that have been + decorated as descriptors. + + Returns + -------- + tuple of str + """ + members = inspect.getmembers( + self.__class__, lambda x: hasattr(x, "descriptor_method") + ) + return tuple(mname for (mname, method) in members) + + @property + def id(self): + """Returns the extension identifier (1-based extension number) + for sliced objects. + """ + if self.is_single: + return self._indices[0] + 1 + + raise ValueError( + "Cannot return id for an AstroData object " + "that is not a single slice" + ) + + @property + def indices(self): + """Returns the extensions indices for sliced objects.""" + return self._indices if self._indices else list(range(len(self))) + + @property + def is_sliced(self): + """If this data provider instance represents the whole dataset, return + False. If it represents a slice out of the whole, return True. + """ + return self._indices is not None + +
+[docs] + def is_settable(self, attr): + """Return True if the attribute is meant to be modified.""" + if self.is_sliced and attr in {"path", "filename"}: + return False + + return attr in self._fixed_settable or attr.isupper()
+ + + @property + def _nddata(self): + """Return the list of `astrodata.NDAstroData` objects. Contrary to + ``self.nddata`` this always returns a list. + """ + if self._indices is not None: + return [self._all_nddatas[i] for i in self._indices] + + return self._all_nddatas + + @property + def nddata(self): + """Return the list of `astrodata.NDAstroData` objects. + + If the `AstroData` object is sliced, this returns only the NDData + objects of the sliced extensions. And if this is a single extension + object, the NDData object is returned directly (i.e. not a list). + """ + return self._nddata[0] if self.is_single else self._nddata + +
+[docs] + def table(self): + """Return a dictionary of `astropy.table.Table` objects. + + Notes + ----- + This returns a _copy_ of the tables, so modifying them will not + affect the original ones. + """ + # FIXME: do we need this in addition to .tables ? + return self._tables.copy()
+ + + @property + def tables(self): + """Return the names of the `astropy.table.Table` objects associated to + the top-level object. + """ + return set(self._tables) + + @property + def ext_tables(self): + """Return the names of the `astropy.table.Table` objects associated to + an extension. + """ + if not self.is_single: + raise AttributeError("this is only available for extensions") + + return set( + key + for key, obj in self.nddata.meta["other"].items() + if isinstance(obj, Table) + ) + + @property + @returns_list + def shape(self): + """Return the shape of the data array for each extension as a list of + shapes. + """ + return [nd.shape for nd in self._nddata] + + @property + @returns_list + def data(self): + """A list of the arrays (or single array, if this is a single slice) + corresponding to the science data attached to each extension. + """ + return [nd.data for nd in self._nddata] + + @data.setter + @assign_only_single_slice + def data(self, value): + # Setting the ._data in the NDData is a bit kludgy, but we're all + # grown adults and know what we're doing, isn't it? + if hasattr(value, "shape"): + self.nddata._data = value + + else: + raise AttributeError( + "Trying to assign data to be something with no shape" + ) + + @property + @returns_list + def uncertainty(self): + """A list of the uncertainty objects (or a single object, if this is + a single slice) attached to the science data, for each extension. + + The objects are instances of AstroPy's `astropy.nddata.NDUncertainty`, + or `None` where no information is available. + + See also + -------- + variance : The actual array supporting the uncertainty object. + + """ + return [nd.uncertainty for nd in self._nddata] + + @uncertainty.setter + @assign_only_single_slice + def uncertainty(self, value): + self.nddata.uncertainty = value + + @property + @returns_list + def mask(self): + """A list of the mask arrays (or a single array, if this is a single + slice) attached to the science data, for each extension. + + For objects that miss a mask, `None` will be provided instead. + """ + return [nd.mask for nd in self._nddata] + + @mask.setter + @assign_only_single_slice + def mask(self, value): + self.nddata.mask = value + + @property + @returns_list + def variance(self): + """A list of the variance arrays (or a single array, if this is a + single slice) attached to the science data, for each extension. + + For objects that miss uncertainty information, `None` will be provided + instead. + + See also + --------- + uncertainty : The uncertainty objects used under the hood. + + """ + return [nd.variance for nd in self._nddata] + + @variance.setter + @assign_only_single_slice + def variance(self, value): + if value is None: + self.nddata.uncertainty = value + + else: + self.nddata.uncertainty = ADVarianceUncertainty(value) + + @property + def wcs(self): + """Returns the list of WCS objects for each extension.""" + if self.is_single: + return self.nddata.wcs + + raise ValueError( + "Cannot return WCS for an AstroData object " + "that is not a single slice" + ) + + @wcs.setter + @assign_only_single_slice + def wcs(self, value): + self.nddata.wcs = value + +
+[docs] + def __iter__(self): + if self.is_single: + yield self + else: + for n in range(len(self)): + yield self[n]
+ + +
+[docs] + def __getitem__(self, idx): + """Returns a sliced view of the instance. It supports the standard + Python indexing syntax. + + Parameters + ---------- + slice : int, `slice` + An integer or an instance of a Python standard `slice` object + + Raises + ------- + TypeError + If trying to slice an object when it doesn't make sense (e.g. + slicing a single slice) + + ValueError + If `slice` does not belong to one of the recognized types + + IndexError + If an index is out of range + """ + if self.is_single: + raise TypeError("Can't slice a single slice!") + + indices, _ = normalize_indices(idx, nitems=len(self)) + + if self._indices: + indices = [self._indices[i] for i in indices] + + is_single = not isinstance(idx, (tuple, slice)) + + obj = self.__class__( + self._all_nddatas, + tables=self._tables, + phu=self.phu, + indices=indices, + is_single=is_single, + ) + + obj._path = self.path + obj._orig_filename = self.orig_filename + + return obj
+ + +
+[docs] + def __delitem__(self, idx): + """Called to implement deletion of ``self[idx]``. Supports standard + Python syntax (including negative indices). + + Parameters + ---------- + idx : int + This index represents the order of the element that you want + to remove. + + Raises + ------- + IndexError + If `idx` is out of range. + """ + if self.is_sliced: + raise TypeError("Can't remove items from a sliced object") + del self._all_nddatas[idx]
+ + +
+[docs] + def __getattr__(self, attribute): + """Called when an attribute lookup has not found the attribute in the + usual places (not an instance attribute, and not in the class tree for + ``self``). + + Parameters + ---------- + attribute : str + The attribute's name. + + Raises + ------- + AttributeError + If the attribute could not be found/computed. + """ + # If we're working with single slices, let's look some things up + # in the ND object + if self.is_single and attribute.isupper(): + with suppress(KeyError): + return self.nddata.meta["other"][attribute] + + if attribute in self._tables: + return self._tables[attribute] + + raise AttributeError( + f"{self.__class__.__name__!r} object has no " + f"attribute {attribute!r}" + )
+ + +
+[docs] + def __setattr__(self, attribute, value): + """Called when an attribute assignment is attempted, instead of the + normal mechanism. + + Parameters + ---------- + attribute : str + The attribute's name. + + value : object + The value to be assigned to the attribute. + """ + + def _my_attribute(attr): + return attr in self.__dict__ or attr in self.__class__.__dict__ + + if ( + attribute.isupper() + and self.is_settable(attribute) + and not _my_attribute(attribute) + ): + # This method is meant to let the user set certain attributes of + # the NDData objects. First we check if the attribute belongs to + # this object's dictionary. Otherwise, see if we can pass it down. + # + if self.is_sliced and not self.is_single: + raise TypeError( + "This attribute can only be " + "assigned to a single-slice object" + ) + + if attribute == DEFAULT_EXTENSION: + raise AttributeError( + f"{attribute} extensions should be " + "appended with .append" + ) + + if attribute in {"DQ", "VAR"}: + raise AttributeError( + f"{attribute} should be set on the " "nddata object" + ) + + add_to = self.nddata if self.is_single else None + self._append(value, name=attribute, add_to=add_to) + + return + + super().__setattr__(attribute, value)
+ + +
+[docs] + def __delattr__(self, attribute): + """Implements attribute removal.""" + if not attribute.isupper(): + super().__delattr__(attribute) + return + + if self.is_sliced: + if not self.is_single: + raise TypeError("Can't delete attributes on non-single slices") + + other = self.nddata.meta["other"] + if attribute in other: + del other[attribute] + else: + raise AttributeError( + f"{self.__class__.__name__!r} sliced " + "object has no attribute {attribute!r}" + ) + else: + if attribute in self._tables: + del self._tables[attribute] + else: + raise AttributeError( + f"'{attribute}' is not a global table " "for this instance" + )
+ + +
+[docs] + def __contains__(self, attribute): + """Implements the ability to use the ``in`` operator with an + `AstroData` object. + + Parameters + ---------- + attribute : str + An attribute name. + + Returns + -------- + bool + """ + return attribute in self.exposed
+ + +
+[docs] + def __len__(self): + """Return the number of independent extensions stored by the object.""" + if self._indices is not None: + return len(self._indices) + + if self.is_single: + return 1 + + return len(self._all_nddatas)
+ + + @property + def exposed(self): + """A collection of strings with the names of objects that can be + accessed directly by name as attributes of this instance, and that are + not part of its standard interface (i.e. data objects that have been + added dynamically). + + Examples + --------- + >>> ad[0].exposed # doctest: +SKIP + set(['OBJMASK', 'OBJCAT']) + + """ + exposed = set(self._tables) + if self.is_single: + exposed |= set(self.nddata.meta["other"]) + + return exposed + + def _pixel_info(self): + for idx, nd in enumerate(self._nddata): + other_objects = [] + uncer = nd.uncertainty + fixed = ( + ("variance", None if uncer is None else uncer), + ("mask", nd.mask), + ) + + for name, other in fixed + tuple(sorted(nd.meta["other"].items())): + if other is None: + continue + + if isinstance(other, Table): + other_objects.append( + { + "attr": name, + "type": "Table", + "dim": str((len(other), len(other.columns))), + "data_type": "n/a", + } + ) + + else: + dim = "" + if hasattr(other, "dtype"): + dt = other.dtype.name + dim = str(other.shape) + + elif hasattr(other, "data"): + dt = other.data.dtype.name + dim = str(other.data.shape) + + elif hasattr(other, "array"): + dt = other.array.dtype.name + dim = str(other.array.shape) + + else: + dt = "unknown" + + obj_dict = { + "attr": name, + "type": type(other).__name__, + "dim": dim, + "data_type": dt, + } + + other_objects.append(obj_dict) + + main_dict = { + "content": "science", + "type": type(nd).__name__, + "dim": str(nd.data.shape), + "data_type": nd.data.dtype.name, + } + + out_dict = { + "idx": f"[{idx:2}]", + "main": main_dict, + "other": other_objects, + } + + yield out_dict + +
+[docs] + def info(self): + """Prints out information about the contents of this instance.""" + unknown_file = "Unknown" + print(f"Filename: {self.path if self.path else unknown_file}") + + # Tags with proper indent and wrapping. + text = "Tags: " + " ".join(sorted(self.tags)) + textwrapper = textwrap.TextWrapper(width=80, subsequent_indent=" ") + + for line in textwrapper.wrap(text): + print(line) + + # Data information + if len(self) > 0: + main_fmt = "{:6} {:24} {:17} {:14} {}" + other_fmt = " .{:20} {:17} {:14} {}" + print("\nPixels Extensions") + print( + main_fmt.format( + "Index", "Content", "Type", "Dimensions", "Format" + ) + ) + for pi in self._pixel_info(): + main_obj = pi["main"] + print( + main_fmt.format( + pi["idx"], + main_obj["content"][:24], + main_obj["type"][:17], + main_obj["dim"], + main_obj["data_type"], + ) + ) + + for other in pi["other"]: + print( + other_fmt.format( + other["attr"][:20], + other["type"][:17], + other["dim"], + other["data_type"], + ) + ) + + # NOTE: This covers tables, only. Study other cases before + # implementing a more general solution + if self._tables: + print("\nOther Extensions") + print(" Type Dimensions") + for name, table in sorted(self._tables.items()): + if isinstance(table, list): + # This is not a free floating table + continue + + print( + f".{name[:13]:13s} {'Table':11s} {len(table), len(table.columns)}" + )
+ + + def _oper(self, operator, operand): + ind = self.indices + ndd = self._all_nddatas + if isinstance(operand, AstroData): + if len(operand) != len(self): + raise ValueError("Operands are not the same size") + + for n in range(len(self)): + try: + data = ( + operand.nddata + if operand.is_single + else operand.nddata[n] + ) + + ndd[ind[n]] = operator(ndd[ind[n]], data) + + except TypeError: + # This may happen if operand is a sliced, single + # AstroData object + ndd[ind[n]] = operator(ndd[ind[n]], operand.nddata) + + op_table = operand.table() + ltab, rtab = set(self._tables), set(op_table) + for tab in rtab - ltab: + self._tables[tab] = op_table[tab] + + else: + for n in range(len(self)): + ndd[ind[n]] = operator(ndd[ind[n]], operand) + + def _standard_nddata_op(self, fn, operand): + return self._oper( + partial(fn, handle_mask=np.bitwise_or, handle_meta="first_found"), + operand, + ) + +
+[docs] + @format_doc(_ARIT_DOC, name="addition", op="+") + def __add__(self, oper): + copy = deepcopy(self) + copy += oper + return copy
+ + +
+[docs] + @format_doc(_ARIT_DOC, name="subtraction", op="-") + def __sub__(self, oper): + copy = deepcopy(self) + copy -= oper + return copy
+ + +
+[docs] + @format_doc(_ARIT_DOC, name="multiplication", op="*") + def __mul__(self, oper): + copy = deepcopy(self) + copy *= oper + return copy
+ + +
+[docs] + @format_doc(_ARIT_DOC, name="division", op="/") + def __truediv__(self, oper): + copy = deepcopy(self) + copy /= oper + return copy
+ + +
+[docs] + @format_doc(_ARIT_DOC, name="inplace addition", op="+=") + def __iadd__(self, oper): + self._standard_nddata_op(NDAstroData.add, oper) + return self
+ + +
+[docs] + @format_doc(_ARIT_DOC, name="inplace subtraction", op="-=") + def __isub__(self, oper): + self._standard_nddata_op(NDAstroData.subtract, oper) + return self
+ + +
+[docs] + @format_doc(_ARIT_DOC, name="inplace multiplication", op="*=") + def __imul__(self, oper): + self._standard_nddata_op(NDAstroData.multiply, oper) + return self
+ + +
+[docs] + @format_doc(_ARIT_DOC, name="inplace division", op="/=") + def __itruediv__(self, oper): + self._standard_nddata_op(NDAstroData.divide, oper) + return self
+ + + add = __iadd__ + subtract = __isub__ + multiply = __imul__ + divide = __itruediv__ + + __radd__ = __add__ + __rmul__ = __mul__ + +
+[docs] + def __rsub__(self, oper): + copy = (deepcopy(self) - oper) * -1 + return copy
+ + + def _rdiv(self, ndd, operand): + # Divide method works with the operand first + return NDAstroData.divide(operand, ndd) + +
+[docs] + def __rtruediv__(self, oper): + obj = deepcopy(self) + obj._oper(obj._rdiv, oper) + return obj
+ + + def _process_pixel_plane( + self, pixim, name=None, top_level=False, custom_header=None + ): + # Assume that we get an ImageHDU or something that can be + # turned into one + if isinstance(pixim, fits.ImageHDU): + nd = NDAstroData(pixim.data, meta={"header": pixim.header}) + elif isinstance(pixim, NDAstroData): + nd = pixim + else: + nd = NDAstroData(pixim) + + if custom_header is not None: + nd.meta["header"] = custom_header + + header = nd.meta.setdefault("header", fits.Header()) + currname = header.get("EXTNAME") + + if currname is None: + header["EXTNAME"] = name if name is not None else DEFAULT_EXTENSION + + if top_level: + nd.meta.setdefault("other", OrderedDict()) + + return nd + + def _append_array(self, data, name=None, header=None, add_to=None): + if name in {"DQ", "VAR"}: + raise ValueError( + f"'{name}' need to be associated to a " + f"'{DEFAULT_EXTENSION}' one" + ) + + if add_to is None: + # Top level extension + if name is not None: + hname = name + elif header is not None: + hname = header.get("EXTNAME", DEFAULT_EXTENSION) + else: + hname = DEFAULT_EXTENSION + + hdu = fits.ImageHDU(data, header=header) + hdu.header["EXTNAME"] = hname + ret = self._append_imagehdu( + hdu, name=hname, header=None, add_to=None + ) + else: + ret = add_to.meta["other"][name] = data + + return ret + + def _append_imagehdu(self, hdu, name, header, add_to): + if name in {"DQ", "VAR"} or add_to is not None: + return self._append_array(hdu.data, name=name, add_to=add_to) + + nd = self._process_pixel_plane( + hdu, name=name, top_level=True, custom_header=header + ) + return self._append_nddata(nd, name, add_to=None) + + def _append_raw_nddata(self, raw_nddata, name, header, add_to): + logging.debug("Appending data to nddata: %s", name) + + # We want to make sure that the instance we add is whatever we specify + # as NDDataObject, instead of the random one that the user may pass + top_level = add_to is None + + if not isinstance(raw_nddata, NDAstroData): + raw_nddata = NDAstroData(raw_nddata) + + processed_nddata = self._process_pixel_plane( + raw_nddata, top_level=top_level, custom_header=header + ) + return self._append_nddata(processed_nddata, name=name, add_to=add_to) + + def _append_nddata(self, new_nddata, name, add_to): + # NOTE: This method is only used by others that have constructed NDData + # according to our internal format. We don't accept new headers at this + # point, and that's why it's missing from the signature. 'name' is + # ignored. It's there just to comply with the _append_XXX signature. + if add_to is not None: + raise TypeError( + "You can only append NDData derived instances " + "at the top level" + ) + + hd = new_nddata.meta["header"] + hname = hd.get("EXTNAME", DEFAULT_EXTENSION) + + if hname == DEFAULT_EXTENSION: + self._all_nddatas.append(new_nddata) + + else: + raise ValueError( + f"Arbitrary image extensions can only be added " + f"in association to a '{DEFAULT_EXTENSION}'" + ) + + logging.debug("Appending data to nddata: %s", name) + + return new_nddata + + def _append_table(self, new_table, name, header, add_to): + tb = _process_table(new_table, name, header) + hname = tb.meta["header"].get("EXTNAME") + + def find_next_num(tables): + table_num = 1 + while f"TABLE{table_num}" in tables: + table_num += 1 + return f"TABLE{table_num}" + + if add_to is None: + # Find table names for all extensions + ext_tables = set() + for nd in self._nddata: + ext_tables |= set( + key + for key, obj in nd.meta["other"].items() + if isinstance(obj, Table) + ) + + if hname is None: + hname = find_next_num(set(self._tables) | ext_tables) + elif hname in ext_tables: + raise ValueError( + f"Cannot append table '{hname}' because it " + "would hide an extension table" + ) + + self._tables[hname] = tb + else: + if hname in self._tables: + raise ValueError( + f"Cannot append table '{hname}' because it " + "would hide a top-level table" + ) + + add_to.meta["other"][hname] = tb + + return tb + + def _append_astrodata(self, ad, name, header, add_to): + logging.debug("Appending astrodata object: %s", name) + + if not ad.is_single: + raise ValueError( + "Cannot append AstroData instances that are " + "not single slices" + ) + + if add_to is not None: + raise ValueError( + "Cannot append an AstroData slice to another slice" + ) + + new_nddata = deepcopy(ad.nddata) + if header is not None: + new_nddata.meta["header"] = deepcopy(header) + + return self._append_nddata(new_nddata, name=None, add_to=None) + + def _append(self, ext, name=None, header=None, add_to=None): + """ + Internal method to dispatch to the type specific methods. This is + called either by ``.append`` to append on top-level objects only or + by ``__setattr__``. In the second case ``name`` cannot be None, so + this is always the case when appending to extensions (add_to != None). + """ + dispatcher = ( + (NDData, self._append_raw_nddata), + ((Table, fits.TableHDU, fits.BinTableHDU), self._append_table), + (fits.ImageHDU, self._append_imagehdu), + (AstroData, self._append_astrodata), + ) + + for bases, method in dispatcher: + if isinstance(ext, bases): + return method(ext, name=name, header=header, add_to=add_to) + + # Assume that this is an array for a pixel plane + return self._append_array(ext, name=name, header=header, add_to=add_to) + +
+[docs] + def append(self, ext, name=None, header=None): + """ + Adds a new top-level extension. + + Parameters + ---------- + ext : array, `astropy.nddata.NDData`, `astropy.table.Table`, other + The contents for the new extension. The exact accepted types depend + on the class implementing this interface. Implementations specific + to certain data formats may accept specialized types (eg. a FITS + provider will accept an `astropy.io.fits.ImageHDU` and extract the + array out of it). + name : str, optional + A name that may be used to access the new object, as an attribute + of the provider. The name is typically ignored for top-level + (global) objects, and required for the others. If the name cannot + be derived from the metadata associated to ``ext``, you will + have to provider one. + It can consist in a combination of numbers and letters, with the + restriction that the letters have to be all capital, and the first + character cannot be a number ("[A-Z][A-Z0-9]*"). + + Returns + -------- + The same object, or a new one, if it was necessary to convert it to + a more suitable format for internal use. + + Raises + ------- + TypeError + If adding the object in an invalid situation (eg. ``name`` is + `None` when adding to a single slice). + ValueError + Raised if the extension is of a proper type, but its value is + illegal somehow. + + """ + if self.is_sliced: + raise TypeError( + "Can't append objects to slices, use " + "'ext.NAME = obj' instead" + ) + + # NOTE: Most probably, if we want to copy the input argument, we + # should do it here... + if isinstance(ext, fits.PrimaryHDU): + raise ValueError( + "Only one Primary HDU allowed. " + "Use .phu if you really need to set one" + ) + + if isinstance(ext, Table): + raise ValueError( + "Tables should be set directly as attribute, " + "i.e. 'ad.MYTABLE = table'" + ) + + if name is not None and not name.isupper(): + warnings.warn( + f"extension name '{name}' should be uppercase", UserWarning + ) + name = name.upper() + + return self._append(ext, name=name, header=header)
+ + +
+[docs] + @classmethod + def read(cls, source, extname_parser=None): + """Read from a file, file object, HDUList, etc.""" + return read_fits(cls, source, extname_parser=extname_parser)
+ + + load = read # for backward compatibility + +
+[docs] + def write(self, filename=None, overwrite=False): + """ + Write the object to disk. + + Parameters + ---------- + filename : str, optional + If the filename is not given, ``self.path`` is used. + overwrite : bool + If True, overwrites existing file. + + """ + if filename is None: + if self.path is None: + raise ValueError("A filename needs to be specified") + filename = self.path + + write_fits(self, filename, overwrite=overwrite)
+ + +
+[docs] + def operate(self, operator, *args, **kwargs): + """ + Applies a function to the main data array on each extension, replacing + the data with the result. The data will be passed as the first argument + to the function. + + It will be applied to the mask and variance of each extension, too, if + they exist. + + This is a convenience method, which is equivalent to:: + + for ext in ad: + ext.data = operator(ext.data, *args, **kwargs) + if ext.mask is not None: + ext.mask = operator(ext.mask, *args, **kwargs) + if ext.variance is not None: + ext.variance = operator(ext.variance, *args, **kwargs) + + with the additional advantage that it will work on single slices, too. + + Parameters + ---------- + operator : callable + A function that takes an array (and, maybe, other arguments) + and returns an array. + args, kwargs : optional + Additional arguments to be passed to the ``operator``. + + Examples + --------- + >>> import numpy as np + >>> ad.operate(np.squeeze) # doctest: +SKIP + + """ + # Ensure we can iterate, even on a single slice + for ext in [self] if self.is_single else self: + ext.data = operator(ext.data, *args, **kwargs) + if ext.mask is not None: + ext.mask = operator(ext.mask, *args, **kwargs) + if ext.variance is not None: + ext.variance = operator(ext.variance, *args, **kwargs)
+ + +
+[docs] + def reset(self, data, mask=NO_DEFAULT, variance=NO_DEFAULT, check=True): + """ + Sets the ``.data``, and optionally ``.mask`` and ``.variance`` + attributes of a single-extension AstroData slice. This function will + optionally check whether these attributes have the same shape. + + Parameters + ---------- + data : ndarray + The array to assign to the ``.data`` attribute ("SCI"). + mask : ndarray, optional + The array to assign to the ``.mask`` attribute ("DQ"). + variance: ndarray, optional + The array to assign to the ``.variance`` attribute ("VAR"). + check: bool + If set, then the function will check that the mask and variance + arrays have the same shape as the data array. + + Raises + ------- + TypeError + if an attempt is made to set the .mask or .variance attributes + with something other than an array + ValueError + if the .mask or .variance attributes don't have the same shape as + .data, OR if this is called on an AD instance that isn't a single + extension slice + + """ + if not self.is_single: + raise ValueError("Trying to reset a non-sliced AstroData object") + + # In case data is an NDData object + try: + self.data = data.data + except AttributeError: + self.data = data + # Set mask, with checking if required + try: + if mask.shape != self.data.shape and check: + raise ValueError("Mask shape incompatible with data shape") + + except AttributeError as err: + if mask is None: + self.mask = mask + + elif mask == NO_DEFAULT: + if hasattr(data, "mask"): + self.mask = data.mask + + else: + raise TypeError("Attempt to set mask inappropriately") from err + + else: + self.mask = mask + + # Set variance, with checking if required + try: + if variance.shape != self.data.shape and check: + raise ValueError("Variance shape incompatible with data shape") + + except AttributeError as err: + if variance is None: + self.uncertainty = None + + elif variance == NO_DEFAULT: + if hasattr(data, "uncertainty"): + self.uncertainty = data.uncertainty + + else: + raise TypeError( + "Attempt to set variance inappropriately" + ) from err + + else: + self.variance = variance + + if hasattr(data, "wcs"): + self.wcs = data.wcs
+ + +
+[docs] + def update_filename(self, prefix=None, suffix=None, strip=False): + """Update the "filename" attribute of the AstroData object. + + A prefix and/or suffix can be specified. If ``strip=True``, these will + replace the existing prefix/suffix; if ``strip=False``, they will + simply be prepended/appended. + + The current filename is broken down into its existing prefix, root, and + suffix using the ``ORIGNAME`` phu keyword, if it exists and is + contained within the current filename. Otherwise, the filename is split + at the last underscore and the part before is assigned as the root and + the underscore and part after the suffix. No prefix is assigned. + + Note that, if ``strip=True``, a prefix or suffix will only be stripped + if '' is specified. + + Parameters + ---------- + prefix: str, optional + New prefix (None => leave alone) + + suffix: str, optional + New suffix (None => leave alone) + + strip: bool, optional + Strip existing prefixes and suffixes if new ones are given? + + Raises + ------ + ValueError + If the filename cannot be determined + """ + if self.filename is None: + if "ORIGNAME" in self.phu: + self.filename = self.phu["ORIGNAME"] + else: + raise ValueError( + "A filename needs to be set before it can be updated" + ) + + # Set the ORIGNAME keyword if it's not there + if "ORIGNAME" not in self.phu: + self.phu.set( + "ORIGNAME", + self.orig_filename, + "Original filename prior to processing", + ) + + if strip: + root, filetype = os.path.splitext(self.phu["ORIGNAME"]) + filename, filetype = os.path.splitext(self.filename) + m = re.match(f"(.*){re.escape(root)}(.*)", filename) + + # Do not strip a prefix/suffix unless a new one is provided + if m: + if prefix is None: + prefix = m.groups()[0] + + existing_suffix = m.groups()[1] + + if "_" in existing_suffix: + last_underscore = existing_suffix.rfind("_") + root += existing_suffix[:last_underscore] + existing_suffix = existing_suffix[last_underscore:] + + else: + try: + root, existing_suffix = filename.rsplit("_", 1) + existing_suffix = "_" + existing_suffix + + except ValueError as err: + logging.info( + "Could not split filename (ValueError): %s", err + ) + root, existing_suffix = filename, "" + + if suffix is None: + suffix = existing_suffix + + else: + root, filetype = os.path.splitext(self.filename) + + # Cope with prefix or suffix as None + self.filename = (prefix or "") + root + (suffix or "") + filetype
+ + + def _crop_nd(self, nd, x1, y1, x2, y2): + """Crop the input nd array and its associated attributes. + + Args: + nd: The input nd array. + x1: The starting x-coordinate of the crop region. + y1: The starting y-coordinate of the crop region. + x2: The ending x-coordinate of the crop region. + y2: The ending y-coordinate of the crop region. + """ + y_start, y_end = y1, y2 + 1 + x_start, x_end = x1, x2 + 1 + + nd.data = nd.data[y_start:y_end, x_start:x_end] + + if nd.uncertainty is not None: + nd.uncertainty = nd.uncertainty[y_start:y_end, x_start:x_end] + + if nd.mask is not None: + nd.mask = nd.mask[y_start:y_end, x_start:x_end] + +
+[docs] + def crop(self, x1, y1, x2, y2): + """Crop the NDData objects given indices. + + Parameters + ---------- + x1, y1, x2, y2 : int + Minimum and maximum indices for the x and y axis. + + """ + for nd in self._nddata: + orig_shape = nd.data.shape + self._crop_nd(nd, x1, y1, x2, y2) + + for o in nd.meta["other"].values(): + try: + if o.shape == orig_shape: + self._crop_nd(o, x1, y1, x2, y2) + + except AttributeError as err: + # No 'shape' attribute in the object. It's probably + # not array-like + err_str = f"{err.__class__.__name__}: {err}" + logging.info(f"Could not crop object {o}: {err_str}") + pass
+ + +
+[docs] + @astro_data_descriptor + def instrument(self): + """Returns the name of the instrument making the observation.""" + return self.phu.get(self._keyword_for("instrument"))
+ + +
+[docs] + @astro_data_descriptor + def object(self): + """Returns the name of the object being observed.""" + return self.phu.get(self._keyword_for("object"))
+ + +
+[docs] + @astro_data_descriptor + def telescope(self): + """Returns the name of the telescope.""" + return self.phu.get(self._keyword_for("telescope"))
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/astrodata/fits.html b/_modules/astrodata/fits.html new file mode 100644 index 00000000..db560354 --- /dev/null +++ b/_modules/astrodata/fits.html @@ -0,0 +1,1352 @@ + + + + + + astrodata.fits — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for astrodata.fits

+"""Functions used when interacting with FITS files and HDUs.
+
+.. |NDData| replace:: :class:`~astropy.nddata.NDData`
+.. |NDDataRef| replace:: :class:`~astropy.nddata.NDDataRef`
+.. |BinTableHDU| replace:: :class:`~astropy.io.fits.BinTableHDU`
+.. |TableHDU| replace:: :class:`~astropy.io.fits.TableHDU`
+.. |NDAstroData| replace:: :class:`~astrodata.nddata.NDAstroData`
+.. |NDAstroDataRef| replace:: :class:`~astrodata.nddata.NDAstroDataRef`
+"""
+
+from collections import OrderedDict
+from copy import deepcopy
+from io import BytesIO
+from itertools import product as cart_product, zip_longest
+import gc
+import logging
+import os
+import traceback
+import warnings
+
+from astropy import units as u
+from astropy.io import fits
+from astropy.io.fits import (
+    BinTableHDU,
+    Column,
+    DELAYED,
+    HDUList,
+    ImageHDU,
+    PrimaryHDU,
+    TableHDU,
+)
+from astropy.nddata import NDData
+
+# NDDataRef is still not in the stable astropy, but this should be the one
+# we use in the future...
+# from astropy.nddata import NDData, NDDataRef as NDDataObject
+from astropy.table import Table
+
+import asdf
+import astropy
+import jsonschema
+import numpy as np
+
+
+from gwcs.wcs import WCS as gWCS
+
+from .nddata import ADVarianceUncertainty, NDAstroData
+from .utils import deprecated
+from .wcs import fitswcs_to_gwcs, gwcs_to_fits
+
+DEFAULT_EXTENSION = "SCI"
+NO_DEFAULT = object()
+LOGGER = logging.getLogger(__name__)
+
+
+class FitsHeaderCollection:
+    """Group access to a list of FITS Header-like objects.
+
+    It exposes a number of methods (``set``, ``get``, etc.) that operate over
+    all the headers at the same time. It can also be iterated.
+
+    Parameters
+    ----------
+    headers : list of `astropy.io.fits.Header`
+        List of Header objects.
+    """
+
+    def __init__(self, headers):
+        self._headers = list(headers)
+
+    def _insert(self, idx, header):
+        self._headers.insert(idx, header)
+
+    def __iter__(self):
+        yield from self._headers
+
+    def __setitem__(self, key, value):
+        if isinstance(value, tuple):
+            self.set(key, value=value[0], comment=value[1])
+        else:
+            self.set(key, value=value)
+
+    def set(self, key, value=None, comment=None):
+        """Set a keyword in all the headers."""
+        for header in self._headers:
+            header.set(key, value=value, comment=comment)
+
+    def __getitem__(self, key):
+        missing_at = []
+        ret = []
+        for n, header in enumerate(self._headers):
+            try:
+                ret.append(header[key])
+
+            except KeyError:
+                logging.debug(
+                    "Assigning None to header missing keyword %s", key
+                )
+
+                missing_at.append(n)
+                ret.append(None)
+
+        if missing_at:
+            error = KeyError(
+                f"The keyword couldn't be found at headers: "
+                f"{tuple(missing_at)}"
+            )
+
+            error.missing_at = missing_at
+            error.values = ret
+            raise error
+
+        return ret
+
+    def get(self, key, default=None):
+        """Get a keyword, defaulting to None."""
+        try:
+            return self[key]
+        except KeyError as err:
+            vals = err.values
+            for n in err.missing_at:
+                vals[n] = default
+            return vals
+
+    def __delitem__(self, key):
+        self.remove(key)
+
+    def remove(self, key):
+        """Remove a keyword from all the headers."""
+        deleted = 0
+        for header in self._headers:
+            try:
+                del header[key]
+                deleted = deleted + 1
+            except KeyError:
+                pass
+        if not deleted:
+            raise KeyError(f"'{key}' is not on any of the extensions")
+
+    def get_comment(self, key):
+        """Get the comment for a keyword, from all the headers, as a list."""
+        return [header.comments[key] for header in self._headers]
+
+    def set_comment(self, key, comment):
+        """Set the comment for a keyword in all the headers."""
+
+        def _inner_set_comment(header):
+            if key not in header:
+                raise KeyError(f"Keyword {key!r} not available")
+
+            header.set(key, comment=comment)
+
+        for n, header in enumerate(self._headers):
+            try:
+                _inner_set_comment(header)
+            except KeyError as err:
+                raise KeyError(f"{err.args[0]} at header {n}") from err
+
+    def __contains__(self, key):
+        return any(tuple(key in h for h in self._headers))
+
+
+def new_imagehdu(data, header, name=None):
+    """Create a new ImageHDU from data and header.
+
+    Parameters
+    ----------
+    data : `numpy.ndarray`
+        The data array.
+
+    header : `astropy.io.fits.Header`
+        The header.
+
+    name : str
+        The extension name.
+
+    Notes
+    -----
+    Assigning data in a delayed way, won't reset BZERO/BSCALE in the header,
+    for some reason. Need to investigated. Maybe astropy.io.fits bug. Figure
+    out WHY were we delaying in the first place.
+
+    Example:
+    >> i = ImageHDU(data=DELAYED, header=header.copy(), name=name)
+    >> i.data = data
+    """
+    # Assigning data in a delayed way, won't reset BZERO/BSCALE in the header,
+    # for some reason. Need to investigated. Maybe astropy.io.fits bug. Figure
+    # out WHY were we delaying in the first place.
+    #    i = ImageHDU(data=DELAYED, header=header.copy(), name=name)
+    #    i.data = data
+    return ImageHDU(data=data, header=header.copy(), name=name)
+
+
+def table_to_bintablehdu(table, extname=None):
+    """Convert an astropy Table object to a BinTableHDU before writing to disk.
+
+    Parameters
+    ----------
+    table: astropy.table.Table instance
+        the table to be converted to a BinTableHDU
+
+    extname: str
+        name to go in the EXTNAME field of the FITS header
+
+    Returns
+    -------
+    BinTableHDU
+    """
+    # remove header to avoid warning from table_to_hdu
+    table_header = table.meta.pop("header", None)
+
+    # table_to_hdu sets units only if the unit conforms to the FITS standard,
+    # otherwise it issues a warning, which we catch here.
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", UserWarning)
+        hdu = fits.table_to_hdu(table)
+
+    # And now we try to set the units that do not conform to the standard,
+    # using unit.to_string() without the format='fits' argument.
+    for col in table.itercols():
+        if col.unit and not hdu.columns[col.name].unit:
+            hdu.columns[col.name].unit = col.unit.to_string()
+
+    if table_header is not None:
+        # Update with cards from table.meta, but skip structural FITS
+        # keywords since those have been set by table_to_hdu
+        exclude = (
+            "SIMPLE",
+            "XTENSION",
+            "BITPIX",
+            "NAXIS",
+            "EXTEND",
+            "PCOUNT",
+            "GCOUNT",
+            "TFIELDS",
+            "TFORM",
+            "TSCAL",
+            "TZERO",
+            "TNULL",
+            "TTYPE",
+            "TUNIT",
+            "TDISP",
+            "TDIM",
+            "THEAP",
+            "TBCOL",
+        )
+        hdr = fits.Header(
+            [
+                card
+                for card in table_header.cards
+                if not card.keyword.startswith(exclude)
+            ]
+        )
+        update_header(hdu.header, hdr)
+        # reset table's header
+        table.meta["header"] = table_header
+    if extname:
+        hdu.header["EXTNAME"] = (extname, "added by AstroData")
+    return hdu
+
+
+def header_for_table(table):
+    """Return a FITS header for a table."""
+    table_header = table.meta.pop("header", None)
+    fits_header = fits.table_to_hdu(table).header
+
+    if table_header:
+        table.meta["header"] = table_header  # restore original meta
+        fits_header = update_header(table_header, fits_header)
+
+    return fits_header
+
+
+
+[docs] +def add_header_to_table(table): + """Add a FITS header to a table.""" + header = header_for_table(table) + table.meta["header"] = header + return header
+ + + +def _process_table(table, name=None, header=None): + """Convert a BinTableHDU or TableHDU to an astropy Table object. + + Arguments + --------- + table : |BinTableHDU| or |TableHDU| or |Table| + The table to convert. If it's already an |Table|, it will be returned + as is. + + name : str + The name to assign to the table. + + header : `astropy.io.fits.Header` + The header to assign to the table. + """ + if isinstance(table, (BinTableHDU, TableHDU)): + obj = Table(table.data, meta={"header": header or table.header}) + for i, col in enumerate(obj.columns, start=1): + try: + obj[col].unit = u.Unit(obj.meta["header"][f"TUNIT{i}"]) + except (KeyError, TypeError, ValueError): + pass + elif isinstance(table, Table): + obj = Table(table) + if header is not None: + obj.meta["header"] = deepcopy(header) + elif "header" not in obj.meta: + obj.meta["header"] = header_for_table(obj) + else: + raise ValueError(f"{table.__class__} is not a recognized table type") + + if name is not None: + obj.meta["header"]["EXTNAME"] = name + + return obj + + +def card_filter(cards, include=None, exclude=None): + """Filter a list of cards, lazily returning only those that match the + criteria. + + Parameters + ---------- + cards : iterable + The cards to filter. + + include : iterable of str + Only cards with these keywords will be returned. + + exclude : iterable of str + Cards with these keywords will be skipped. + + Yields + ------ + card : tuple + A card that matches the criteria. + """ + for card in cards: + if include is not None and card[0] not in include: + continue + + if exclude is not None and card[0] in exclude: + continue + + yield card + + +def update_header(headera, headerb): + """Update headera with the cards from headerb, but only if they are + different. + + Parameters + ---------- + headera : `astropy.io.fits.Header` + The header to update. + + headerb : `astropy.io.fits.Header` + The header to update from. + """ + cardsa = tuple(tuple(cr) for cr in headera.cards) + cardsb = tuple(tuple(cr) for cr in headerb.cards) + + if cardsa == cardsb: + return headera + + # Ok, headerb differs somehow. Let's try to bring the changes to headera + # Updated keywords that should be unique + difference = set(cardsb) - set(cardsa) + headera.update(card_filter(difference, exclude={"HISTORY", "COMMENT", ""})) + + # Check the HISTORY and COMMENT cards, just in case + for key in ("HISTORY", "COMMENT"): + fltcardsa = card_filter(cardsa, include={key}) + fltcardsb = card_filter(cardsb, include={key}) + # assume we start with two headers that are mostly the same and + # that will have added comments/history at the end (in headerb) + for ca, cb in zip_longest(fltcardsa, fltcardsb): + if ca is None: + headera.update((cb,)) + + return headera + + +def fits_ext_comp_key(ext): + """Returns a pair (int, str) that will be used to sort extensions.""" + if isinstance(ext, PrimaryHDU): + # This will guarantee that the primary HDU goes first + ret = (-1, "") + else: + # When two extensions share version number, we'll use their names + # to sort them out. Choose a suitable key so that: + # + # - SCI extensions come first + # - unnamed extensions come last + # + # We'll resort to add 'z' in front of the usual name to force + # SCI to be the "smallest" + name = ext.name + if name == "": + name = "zzzz" + elif name != DEFAULT_EXTENSION: + name = "z" + name + + ver = ext.header.get("EXTVER") + if ver in (-1, None): + # In practice, this number should be larger than any EXTVER found + # in real life HDUs, pushing unnumbered HDUs to the end. + ver = 2**32 - 1 + + # For the general case, just return version and name, to let them + # be sorted naturally + ret = (ver, name) + + return ret + + +class FitsLazyLoadable: + """Class to delay loading of data from a FITS file.""" + + def __init__(self, obj): + """Initializes the object. + + Parameters + ---------- + obj : `astropy.io.fits.ImageHDU` or `astropy.io.fits.BinTableHDU` + The HDU to delay loading from. + """ + self._obj = obj + self.lazy = True + + def _create_result(self, shape): + """Create an empty array to hold the data.""" + return np.empty(shape, dtype=self.dtype) + + def _scale(self, data): + """Scale the data, if necessary.""" + # pylint: disable=protected-access + bscale = self._obj._orig_bscale + bzero = self._obj._orig_bzero + + # If bscale is None, then the data is already scaled + if bscale is None: + return data + + if bscale == 1 and bzero == 0: + return data + + return (bscale * data + bzero).astype(self.dtype) + + def __getitem__(self, arr_slice): + return self._scale(self._obj.section[arr_slice]) + + @property + def header(self): + """The header of the HDU.""" + return self._obj.header + + @property + def data(self): + """The data of the HDU.""" + res = self._create_result(self.shape) + res[:] = self._scale(self._obj.data) + return res + + @property + def shape(self): + """The shape of the data.""" + return self._obj.shape + + @property + def dtype(self): + """Need to to some overriding of astropy.io.fits since it doesn't + know about BITPIX=8 + """ + # pylint: disable=protected-access + bitpix = self._obj._orig_bitpix + + if self._obj._orig_bscale == 1 and self._obj._orig_bzero == 0: + dtype = fits.BITPIX2DTYPE[bitpix] + + else: + # this method from astropy will return the dtype if the data + # needs to be converted to unsigned int or scaled to float + dtype = self._obj._dtype_for_bitpix() + + if dtype is None: + if bitpix < 0: + dtype = np.dtype(f"float{abs(bitpix)}") + + if ( + self._obj.header["EXTNAME"] == "DQ" + or self._obj._uint + and self._obj._orig_bscale == 1 + and bitpix == 8 + ): + dtype = np.uint16 + + return dtype + + +def _prepare_hdulist(hdulist, default_extension="SCI", extname_parser=None): + """Prepare an HDUList for reading. + + Parameters + ---------- + hdulist : `astropy.io.fits.HDUList` + The HDUList to prepare. + + default_extension : str + The name of the default extension. + + extname_parser : callable + A function to parse the EXTNAME of an HDU. + + Returns + ------- + hdulist : `astropy.io.fits.HDUList` + The prepared HDUList. + """ + new_list = [] + highest_ver = 0 + recognized = set() + + if len(hdulist) > 1 or (len(hdulist) == 1 and hdulist[0].data is None): + # MEF file + # First get HDUs for which EXTVER is defined + for hdu in hdulist: + if extname_parser: + extname_parser(hdu) + ver = hdu.header.get("EXTVER") + if ver not in (-1, None) and hdu.name: + highest_ver = max(highest_ver, ver) + elif not isinstance(hdu, PrimaryHDU): + continue + + new_list.append(hdu) + recognized.add(hdu) + + # Then HDUs that miss EXTVER + for hdu in hdulist: + if hdu in recognized: + continue + + if isinstance(hdu, ImageHDU): + highest_ver += 1 + if "EXTNAME" not in hdu.header: + hdu.header["EXTNAME"] = ( + default_extension, + "Added by AstroData", + ) + + if hdu.header.get("EXTVER") in (-1, None): + hdu.header["EXTVER"] = (highest_ver, "Added by AstroData") + + new_list.append(hdu) + recognized.add(hdu) + + else: + # Uh-oh, a single image FITS file + new_list.append(PrimaryHDU(header=hdulist[0].header)) + image = ImageHDU(header=hdulist[0].header, data=hdulist[0].data) + # Fudge due to apparent issues with assigning ImageHDU from data + # pylint: disable=protected-access + image._orig_bscale = hdulist[0]._orig_bscale + image._orig_bzero = hdulist[0]._orig_bzero + + for keyw in ("SIMPLE", "EXTEND"): + if keyw in image.header: + del image.header[keyw] + + image.header["EXTNAME"] = (default_extension, "Added by AstroData") + image.header["EXTVER"] = (1, "Added by AstroData") + new_list.append(image) + + return HDUList(sorted(new_list, key=fits_ext_comp_key)) + + +def read_fits(cls, source, extname_parser=None): + """Takes either a string (with the path to a file) or an HDUList as input, + and tries to return a populated AstroData (or descendant) instance. + + It will raise exceptions if the file is not found, or if there is no match + for the HDUList, among the registered AstroData classes. + + Parameters + ---------- + cls : class + The class to instantiate. + + source : str or `astropy.io.fits.HDUList` + The path to the file, or an HDUList. + + extname_parser : callable + A function to parse the EXTNAME of an HDU. + + Returns + ------- + ad : `astrodata.AstroData` or descendant + The populated AstroData object. This is of the type specified by cls. + """ + + ad = cls() + + if isinstance(source, (str, os.PathLike)): + hdulist = fits.open( + source, memmap=True, do_not_scale_image_data=True, mode="readonly" + ) + + ad.path = source + + else: + hdulist = source + + try: + ad.path = source[0].header.get("ORIGNAME") + + except AttributeError as err: + logging.info("Attribute error in read_fits: %s", err) + ad.path = None + + # This is a hack to get around the fact that we don't have a proper way to + # pass the original filename to the object. This is needed for the writer + # to be able to write the ORIGNAME keyword. + # pylint: disable=protected-access + _file = hdulist._file + + hdulist = _prepare_hdulist( + hdulist, + default_extension=DEFAULT_EXTENSION, + extname_parser=extname_parser, + ) + + if _file is not None: + hdulist._file = _file + + # Initialize the object containers to a bare minimum + # pylint: disable=no-member + if "ORIGNAME" not in hdulist[0].header and ad.orig_filename is not None: + hdulist[0].header.set( + "ORIGNAME", + ad.orig_filename, + "Original filename prior to processing", + ) + + ad.phu = hdulist[0].header + + # This is hashable --- we can use it to check if we've seen this object + # before. + # pylint: disable=unhashable-member + seen = {hdulist[0]} + + skip_names = {DEFAULT_EXTENSION, "REFCAT", "MDF"} + + def associated_extensions(ver): + for hdu in hdulist: + if hdu.header.get("EXTVER") == ver and hdu.name not in skip_names: + yield hdu + + # Only SCI HDUs + sci_units = [hdu for hdu in hdulist[1:] if hdu.name == DEFAULT_EXTENSION] + + seen_vers = [] + + for hdu in sci_units: + seen.add(hdu) + ver = hdu.header.get("EXTVER", -1) + + if ver > -1 and seen_vers.count(ver) == 1: + LOGGER.warning("Multiple SCI extension with EXTVER %s", ver) + + seen_vers.append(ver) + parts = { + "data": hdu, + "uncertainty": None, + "mask": None, + "wcs": None, + "other": [], + } + + # For each SCI HDU find if it has an associated variance, mask, wcs + for extra_unit in associated_extensions(ver): + seen.add(extra_unit) + name = extra_unit.name + if name == "DQ": + parts["mask"] = extra_unit + elif name == "VAR": + parts["uncertainty"] = extra_unit + elif name == "WCS": + parts["wcs"] = extra_unit + else: + parts["other"].append(extra_unit) + + header = parts["data"].header + lazy = hdulist._file is not None and hdulist._file.memmap + + for part_name in ("data", "mask", "uncertainty"): + if parts[part_name] is not None: + if lazy: + # Use FitsLazyLoadable to delay loading of the data + parts[part_name] = FitsLazyLoadable(parts[part_name]) + else: + # We open the file with do_not_scale_data=True, so + # the data array does not have the correct data values. + # AstroData handles scaling internally, and we can ensure + # it does that by making the data a FitsLazyLoadable; the + # side-effect of this is that the is_lazy() function will + # return True, but this has minimal knock-on effects. + # Hopefully astropy will handle this better in future. + if hdulist._file is not None: # probably compressed + parts[part_name] = FitsLazyLoadable(parts[part_name]) + + else: # for astrodata.create() files + parts[part_name] = parts[part_name].data + + # handle the variance if not lazy + if parts["uncertainty"] is not None and not isinstance( + parts["uncertainty"], FitsLazyLoadable + ): + parts["uncertainty"] = ADVarianceUncertainty(parts["uncertainty"]) + + # Create the NDData object + nd = NDAstroData( + data=parts["data"], + uncertainty=parts["uncertainty"], + mask=parts["mask"], + meta={"header": header}, + ) + + ad.append(nd, name=DEFAULT_EXTENSION) + + # This is used in the writer to keep track of the extensions that + # were read from the current object. + nd.meta["parent_ad"] = id(ad) + + for other in parts["other"]: + if not other.name: + warnings.warn(f"Skip HDU {other} because it has no EXTNAME") + else: + setattr(ad[-1], other.name, other) + + if parts["wcs"] is not None: + # Load the gWCS object from the ASDF extension + nd.wcs = asdftablehdu_to_wcs(parts["wcs"]) + if nd.wcs is None: + # Fallback to the data header + nd.wcs = fitswcs_to_gwcs(nd) + if nd.wcs is None: + # In case WCS info is in the PHU + nd.wcs = fitswcs_to_gwcs(hdulist[0].header) + + for other in hdulist: + if other in seen: + continue + + name = other.header.get("EXTNAME") + + try: + ad.append(other, name=name) + + except ValueError as e: + warnings.warn(f"Discarding {name} :\n {e}") + + return ad + + +def ad_to_hdulist(ad): + """Creates an HDUList from an AstroData object.""" + hdul = HDUList() + hdul.append(PrimaryHDU(header=ad.phu, data=DELAYED)) + + # Find the maximum EXTVER for extensions that belonged with this + # object if it was read from a FITS file + # pylint: disable=protected-access + ad_nddata = ad.nddata + + if isinstance(ad_nddata, NDAstroData): + ad_nddata = [ad_nddata] + + maxver = max( + ( + nd.meta["header"].get("EXTVER", 0) + for nd in ad_nddata + if nd.meta.get("parent_ad") == id(ad) + ), + default=0, + ) + + for ext in ad_nddata: + header = ext.meta["header"].copy() + + if not isinstance(header, fits.Header): + header = fits.Header(header) + + if ext.meta.get("parent_ad") == id(ad): + # If the extension belonged with this object, use its + # original EXTVER + ver = header["EXTVER"] + else: + # Otherwise renumber the extension + ver = header["EXTVER"] = maxver + 1 + maxver += 1 + + wcs = ext.wcs + + if isinstance(wcs, gWCS): + # We don't have access to the AD tags so see if it's an image + # Catch ValueError as any sort of failure + try: + wcs_dict = gwcs_to_fits(ext, ad.phu) + + except (ValueError, NotImplementedError) as e: + LOGGER.warning(e) + + else: + # Must delete keywords if image WCS has been downscaled + # from a higher number of dimensions + for i in range(1, 5): + for kw in ( + f"CDELT{i}", + f"CRVAL{i}", + f"CUNIT{i}", + f"CTYPE{i}", + f"NAXIS{i}", + ): + if kw in header: + del header[kw] + + for j in range(1, 5): + for kw in (f"CD{i}_{j}", f"PC{i}_{j}", f"CRPIX{j}"): + if kw in header: + del header[kw] + + # Delete this if it's left over from a previous save + if "FITS-WCS" in header: + del header["FITS-WCS"] + + try: + extensions = wcs_dict.pop("extensions") + + except KeyError: + pass + + else: + for k, v in extensions.items(): + ext.meta["other"][k] = v + + header.update(wcs_dict) + + # Use "in" here as the dict entry may be (value, comment) + if "APPROXIMATE" not in wcs_dict.get("FITS-WCS", ""): + wcs = None # There's no need to create a WCS extension + + hdul.append(new_imagehdu(ext.data, header, "SCI")) + + if ext.uncertainty is not None: + hdul.append(new_imagehdu(ext.uncertainty.array, header, "VAR")) + + if ext.mask is not None: + hdul.append(new_imagehdu(ext.mask, header, "DQ")) + + if isinstance(wcs, gWCS): + hdul.append(wcs_to_asdftablehdu(ext.wcs, extver=ver)) + + for name, other in ext.meta.get("other", {}).items(): + if isinstance(other, Table): + hdu = table_to_bintablehdu(other, extname=name) + + elif isinstance(other, np.ndarray): + hdu = new_imagehdu(other, header, name=name) + + elif isinstance(other, NDAstroData): + hdu = new_imagehdu(other.data, ext.meta["header"]) + + else: + raise ValueError( + "I don't know how to write back an object " + f"of type {type(other)}" + ) + + hdu.ver = ver + hdul.append(hdu) + + if ad._tables is not None: + for name, table in sorted(ad._tables.items()): + hdul.append(table_to_bintablehdu(table, extname=name)) + + # Additional FITS compatibility, add to PHU + # pylint: disable=no-member + hdul[0].header["NEXTEND"] = len(hdul) - 1 + + return hdul + + +def write_fits(ad, filename, overwrite=False): + """Writes the AstroData object to a FITS file.""" + hdul = ad_to_hdulist(ad) + hdul.writeto(filename, overwrite=overwrite) + + +@deprecated( + "Renamed to 'windowed_operation', this is just an alias for now, " + "and will be removed in a future version." +) +def windowedOp(*args, **kwargs): # pylint: disable=invalid-name + """Deprecated alias for windowed_operation.""" + return windowed_operation(*args, **kwargs) + + +def _generate_boxes(shape, kernel): + """Break the input into chunks.""" + if len(shape) != len(kernel): + raise AssertionError( + f"Incompatible shape ({shape}) and kernel ({kernel})" + ) + + ticks = [ + [(x, x + step) for x in range(0, axis, step)] + for axis, step in zip(shape, kernel) + ] + + return list(cart_product(*ticks)) + + +def _get_shape(sequence): + """Get the shape of the input.""" + if len({x.shape for x in sequence}) > 1: + shapes = tuple(x.shape for x in sequence) + raise ValueError( + f"Can't calculate final shape: sequence elements " + f"mismatch on shape, and none was provided." + f" (shapes: {shapes}, found " + f" {len(set(shapes))} unique shapes: {tuple(set(shapes))}" + ) + + return sequence[0].shape + + +def _apply_func(func, sequence, boxes, result, **kwargs): + """ + Apply a given function to a sequence of elements within specified boxes and store the result in the result object. + + Parameters + ---------- + func : function + The function to apply to the elements. + sequence : list + The sequence of elements to apply the function to. + boxes : list + The list of boxes specifying the sections of the elements to apply the function to. + result : object + The object to store the result in. + + Returns + ------- + None + + Note + ---- + This function applies the function to the elements in the sequence within + the specified boxes and stores the result in the result object. There is no + data returned by this function. + """ + for coords in boxes: + section = tuple(slice(start, end) for (start, end) in coords) + out = func([element.window[section] for element in sequence], **kwargs) + result.set_section(section, out) + + # propagate additional attributes + if out.meta.get("other"): + for k, v in out.meta["other"].items(): + if len(boxes) > 1: + result.meta["other"][k, coords] = v + else: + result.meta["other"][k] = v + + gc.collect() + + +def windowed_operation( + func, + sequence, + kernel, + shape=None, + dtype=None, + with_uncertainty=False, + with_mask=False, + **kwargs, +): + """Apply function on a NDData obbjects, splitting the data in chunks to + limit memory usage. + + Parameters + ---------- + func : callable + The function to apply. + + sequence : list of NDData + List of NDData objects. + + kernel : tuple of int + Shape of the blocks. + + shape : tuple of int + Shape of inputs. Defaults to ``sequence[0].shape``. + + dtype : str or dtype + Type of the output array. Defaults to ``sequence[0].dtype``. + + with_uncertainty : bool + Compute uncertainty? + + with_mask : bool + Compute mask? + + **kwargs + Additional args are passed to ``func``. + """ + if shape is None: + shape = _get_shape(sequence) + + if dtype is None: + dtype = sequence[0].window[:1, :1].data.dtype + + result = NDAstroData( + np.empty(shape, dtype=dtype), + variance=np.zeros(shape, dtype=dtype) if with_uncertainty else None, + mask=np.empty(shape, dtype=np.uint16) if with_mask else None, + meta=sequence[0].meta, + wcs=sequence[0].wcs, + ) + + # Delete other extensions because we don't know what to do with them + result.meta["other"] = OrderedDict() + + # The Astropy logger's "INFO" messages aren't warnings, so have to fudge + # pylint: disable=no-member + log_level = astropy.logger.conf.log_level + astropy.log.setLevel(astropy.logger.WARNING) + + boxes = _generate_boxes(shape, kernel) + + try: + _apply_func(func, sequence, boxes, result, **kwargs) + + finally: + astropy.log.setLevel(log_level) # and reset + + # Now if the input arrays where splitted in chunks, we need to gather + # the data arrays for the additional attributes. + other = result.meta["other"] + if other: + if len(boxes) > 1: + for (name, coords), obj in list(other.items()): + if not isinstance(obj, NDData): + raise ValueError("only NDData objects are handled here") + + if name not in other: + other[name] = NDAstroData( + np.empty(shape, dtype=obj.data.dtype) + ) + + section = tuple(slice(start, end) for (start, end) in coords) + other[name].set_section(section, obj) + + del other[name, coords] + + for name in other: + # To set the name of our object we need to save it as an ndarray, + # otherwise for a NDData one AstroData would use the name of the + # AstroData object. + other[name] = other[name].data + + return result + + +# --------------------------------------------------------------------------- +# gWCS <-> FITS WCS helper functions go here +# --------------------------------------------------------------------------- +# Could parametrize some naming conventions in the following two functions if +# done elsewhere for hard-coded names like 'SCI' in future, but they only have +# to be self-consistent with one another anyway. + + +def wcs_to_asdftablehdu(wcs, extver=None): + """Serialize a gWCS object as a FITS TableHDU (ASCII) extension. + + The ASCII table is actually a mini ASDF file. The constituent AstroPy + models must have associated ASDF "tags" that specify how to serialize them. + + In the event that serialization as pure ASCII fails (this should not + happen), a binary table representation will be used as a fallback. + + Returns None (issuing a warning) if the WCS object cannot be serialized, + so the rest of the file can still be written. + + Parameters + ---------- + wcs : gWCS + The gWCS object to serialize. + + extver : int + The EXTVER to assign to the extension. + + Returns + ------- + hdu : TableHDU or BinTableHDU + The FITS table extension containing the serialized WCS object. + """ + # Create a small ASDF file in memory containing the WCS object + # representation because there's no public API for generating only the + # relevant YAML subsection and an ASDF file handles the "tags" properly. + try: + af = asdf.AsdfFile({"wcs": wcs}) + except jsonschema.exceptions.ValidationError as err: + # (The original traceback also gets printed here) + raise TypeError( + f"Cannot serialize model(s) for 'WCS' extension " f"{extver or ''}" + ) from err + + # ASDF can only dump YAML to a binary file object, so do that and read + # the contents back from it for storage in a FITS extension: + with BytesIO() as fd: + with af: + # Generate the YAML, dumping any binary arrays as text: + af.write_to(fd, all_array_storage="inline") + fd.seek(0) + wcsbuf = fd.read() + + # Convert the bytes to readable lines of text for storage (falling back to + # saving as binary in the unexpected event that this is not possible): + try: + wcsbuf = wcsbuf.decode("ascii").splitlines() + + except UnicodeDecodeError as err: + # This should not happen, but if the ASDF contains binary data in + # spite of the 'inline' option above, we have to dump the bytes to + # a non-human-readable binary table rather than an ASCII one: + LOGGER.warning( + "Could not convert WCS %s ASDF to ASCII; saving table " + "as binary (error was %s)", + extver or "", + err, + ) + + hduclass = BinTableHDU + fmt = "B" + wcsbuf = np.frombuffer(wcsbuf, dtype=np.uint8) + + else: + hduclass = TableHDU + fmt = f"A{max(len(line) for line in wcsbuf)}" + + # Construct the FITS table extension: + col = Column( + name="gWCS", format=fmt, array=wcsbuf, ascii=hduclass is TableHDU + ) + + return hduclass.from_columns([col], name="WCS", ver=extver) + + +def asdftablehdu_to_wcs(hdu): + """Recreate a gWCS object from its serialization in a FITS table extension. + + Returns None (issuing a warning) if the extension cannot be parsed, so + the rest of the file can still be read. + """ + ver = hdu.header.get("EXTVER", -1) + + if isinstance(hdu, (TableHDU, BinTableHDU)): + try: + colarr = hdu.data["gWCS"] + + except KeyError as err: + LOGGER.warning( + "Ignoring 'WCS' extension %s with no 'gWCS' table " + "column (error was %s)", + ver, + err, + ) + + return None + + # If this table column contains text strings as expected, join the rows + # as separate lines of a string buffer and encode the resulting YAML as + # bytes that ASDF can parse. If AstroData has produced another format, + # it will be a binary dump due to the unexpected presence of non-ASCII + # data, in which case we just extract unmodified bytes from the table. + if colarr.dtype.kind in ("U", "S"): + sep = os.linesep + # Just in case io.fits ever produces 'S' on Py 3 (not the default): + # join lines as str & avoid a TypeError with unicode linesep; could + # also use astype('U') but it assumes an encoding implicitly. + if colarr.dtype.kind == "S" and not isinstance(sep, bytes): + colarr = np.char.decode( + np.char.rstrip(colarr), encoding="ascii" + ) + wcsbuf = sep.join(colarr).encode("ascii") + else: + wcsbuf = colarr.tobytes() + + # Convert the stored text to a Bytes file object that ASDF can open: + with BytesIO(wcsbuf) as fd: + # Try to extract a 'wcs' entry from the YAML: + try: + af = asdf.open(fd) + + except IOError: + LOGGER.warning( + "Ignoring 'WCS' extension %s: failed to parse " + "ASDF.\nError was as follows:\n%s", + ver, + traceback.format_exc(), + ) + + return None + + with af: + try: + wcs = af.tree["wcs"] + + except KeyError as err: + LOGGER.warning( + "Ignoring 'WCS' extension %s: missing " + "'wcs' dict entry. Error was %s", + ver, + err, + ) + + return None + + else: + LOGGER.warning("Ignoring non-FITS-table 'WCS' extension %s", ver) + + return None + + return wcs +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/astrodata/nddata.html b/_modules/astrodata/nddata.html new file mode 100644 index 00000000..eef7027e --- /dev/null +++ b/_modules/astrodata/nddata.html @@ -0,0 +1,791 @@ + + + + + + astrodata.nddata — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for astrodata.nddata

+"""This module implements a derivative class based on NDData with some Mixins,
+implementing windowing and on-the-fly data scaling.
+"""
+
+import warnings
+from copy import deepcopy
+from functools import reduce
+
+import numpy as np
+
+from astropy.io.fits import ImageHDU
+from astropy.modeling import Model, models
+from astropy.nddata import (
+    NDArithmeticMixin,
+    NDData,
+    NDSlicingMixin,
+    VarianceUncertainty,
+)
+from gwcs.wcs import WCS as gWCS
+from .wcs import remove_axis_from_frame
+
+INTEGER_TYPES = (int, np.integer)
+
+__all__ = ["NDAstroData"]
+
+
+class ADVarianceUncertainty(VarianceUncertainty):
+    """Subclass VarianceUncertainty to check for negative values."""
+
+    @VarianceUncertainty.array.setter
+    def array(self, value):
+        if value is not None and np.any(value < 0):
+            warnings.warn(
+                "Negative variance values found. Setting to zero.",
+                RuntimeWarning,
+            )
+            value = np.where(value >= 0.0, value, 0.0)
+        VarianceUncertainty.array.fset(self, value)
+
+
+
+[docs] +class AstroDataMixin: + """A Mixin for ``NDData``-like classes (such as ``Spectrum1D``) to enable + them to behave similarly to ``AstroData`` objects. + + These behaviors are: + 1. ``mask`` attributes are combined with bitwise, not logical, or, + since the individual bits are important. + 2. The WCS must be a ``gwcs.WCS`` object and slicing results in + the model being modified. + 3. There is a settable ``variance`` attribute. + 4. Additional attributes such as OBJMASK can be extracted from + the .meta['other'] dict + """ + +
+[docs] + def __getattr__(self, attribute): + """Allow access to attributes stored in self.meta['other'], as we do + with AstroData objects. + """ + if attribute.isupper(): + try: + return self.meta["other"][attribute] + + # Does this ever happen? If so under what circumstances? + except KeyError: + pass + + raise AttributeError( + f"{self.__class__.__name__!r} object has no " + f"attribute {attribute!r}" + )
+ + + def _arithmetic( + self, + operation, + operand, + propagate_uncertainties=True, + handle_mask=np.bitwise_or, + handle_meta=None, + uncertainty_correlation=0, + compare_wcs="first_found", + **kwds, + ): + """Override the NDData method so that "bitwise_or" becomes the default + operation to combine masks, rather than "logical_or" + """ + return super()._arithmetic( + operation, + operand, + propagate_uncertainties=propagate_uncertainties, + handle_mask=handle_mask, + handle_meta=handle_meta, + uncertainty_correlation=uncertainty_correlation, + compare_wcs=compare_wcs, + **kwds, + ) + + def _slice_wcs(self, slices): + """The ``__call__()`` method of gWCS doesn't appear to conform to the + APE 14 interface for WCS implementations, and doesn't react to slicing + properly. We override NDSlicing's method to do what we want. + """ + if not isinstance(self.wcs, gWCS): + return self.wcs + + # Sanitize the slices, catching some errors early + if not isinstance(slices, (tuple, list)): + slices = (slices,) + slices = list(slices) + ndim = len(self.shape) + if len(slices) > ndim: + raise ValueError( + f"Too many dimensions specified in slice {slices}" + ) + + if Ellipsis in slices: + if slices.count(Ellipsis) > 1: + raise IndexError( + "Only one ellipsis can be specified in a slice" + ) + + ell_index = slices.index(Ellipsis) + 1 + slice_fill = [slice(None)] * (ndim - len(slices) + 1) + slices[ell_index:ell_index] = slice_fill + + slices.extend([slice(None)] * (ndim - len(slices))) + + mods = [] + mapped_axes = [] + for i, (slice_, length) in enumerate(zip(slices[::-1], self.shape)): + model = [] + if isinstance(slice_, slice): + if slice_.step and slice_.step > 1: + raise IndexError("Cannot slice with a step") + if slice_.start: + start = ( + length + slice_.start + if slice_.start < 1 + else slice_.start + ) + if start > 0: + model.append(models.Shift(start)) + mapped_axes.append(max(mapped_axes) + 1 if mapped_axes else 0) + elif isinstance(slice_, INTEGER_TYPES): + model.append(models.Const1D(slice_)) + mapped_axes.append(-1) + else: + raise IndexError("Slice not an integer or range") + if model: + mods.append(reduce(Model.__or__, model)) + else: + # If the previous model was an Identity, we can hang this + # one onto that without needing to append a new Identity + if i > 0 and isinstance(mods[-1], models.Identity): + mods[-1] = models.Identity(mods[-1].n_inputs + 1) + else: + mods.append(models.Identity(1)) + + slicing_model = reduce(Model.__and__, mods) + if mapped_axes != list(np.arange(ndim)): + slicing_model = ( + models.Mapping(tuple(max(ax, 0) for ax in mapped_axes)) + | slicing_model + ) + slicing_model.inverse = models.Mapping( + tuple(ax for ax in mapped_axes if ax != -1), n_inputs=ndim + ) + + if ( + isinstance(slicing_model, models.Identity) + and slicing_model.n_inputs == ndim + ): + return self.wcs # Unchanged! + new_wcs = deepcopy(self.wcs) + input_frame = new_wcs.input_frame + for axis, mapped_axis in reversed(list(enumerate(mapped_axes))): + if mapped_axis == -1: + input_frame = remove_axis_from_frame(input_frame, axis) + new_wcs.pipeline[0].frame = input_frame + new_wcs.insert_transform( + new_wcs.input_frame, slicing_model, after=True + ) + return new_wcs + + @property + def variance(self): + """A convenience property to access the contents of ``uncertainty``.""" + return getattr(self.uncertainty, "array", None) + + @variance.setter + def variance(self, value): + self.uncertainty = ( + ADVarianceUncertainty(value) if value is not None else None + ) + + @property + def wcs(self): + """The WCS of the data. This is a gWCS object, not a FITS WCS object. + + This is returning wcs from an inhertited class, see NDData.wcs for more + details. + """ + return super().wcs + + @wcs.setter + def wcs(self, value): + if value is not None and not isinstance(value, gWCS): + raise TypeError("wcs value must be None or a gWCS object") + self._wcs = value + + @property + def shape(self): + """The shape of the data.""" + return self._data.shape + + @property + def size(self): + """The size of the data.""" + return self._data.size
+ + + +class FakeArray: + """A class that pretends to be an array, but is actually a lazy-loaded""" + + def __init__(self, very_faked): + self.data = very_faked + self.shape = (100, 100) # Won't matter. This is just to fool NDData + self.dtype = np.float32 # Same here + + def __getitem__(self, index): + return None + + def __array__(self): + return self.data + + +class NDWindowing: + """A class to allow "windowed" access to some properties of an + ``NDAstroData`` instance. In particular, ``data``, ``uncertainty``, + ``variance``, and ``mask`` return clipped data. + """ + + def __init__(self, target): + self._target = target + + def __getitem__(self, window_slice): + return NDWindowingAstroData(self._target, window=window_slice) + + +class NDWindowingAstroData( + AstroDataMixin, NDArithmeticMixin, NDSlicingMixin, NDData +): + """Allows "windowed" access to some properties of an ``NDAstroData`` + instance. In particular, ``data``, ``uncertainty``, ``variance``, and + ``mask`` return clipped data. + """ + + # pylint: disable=super-init-not-called + def __init__(self, target, window): + self._target = target + self._window = window + + def __getattr__(self, attribute): + """Allow access to attributes stored in self.meta['other'], as we do + with AstroData objects. + """ + if attribute.isupper(): + try: + return self._target._get_simple( + attribute, section=self._window + ) + except KeyError: + pass + raise AttributeError( + f"{self.__class__.__name__!r} object has no " + f"attribute {attribute!r}" + ) + + @property + def unit(self): + return self._target.unit + + @property + def wcs(self): + # pylint: disable=protected-access + return self._target._slice_wcs(self._window) + + @property + def data(self): + # pylint: disable=protected-access + return self._target._get_simple("_data", section=self._window) + + @property + def uncertainty(self): + # pylint: disable=protected-access + return self._target._get_uncertainty(section=self._window) + + @property + def variance(self): + if self.uncertainty is not None: + return self.uncertainty.array + + return None + + @property + def mask(self): + # pylint: disable=protected-access + return self._target._get_simple("_mask", section=self._window) + + +def is_lazy(item): + """Returns True if the item is a lazy-loaded object, False otherwise.""" + return isinstance(item, ImageHDU) or getattr(item, "lazy", False) + + +
+[docs] +class NDAstroData(AstroDataMixin, NDArithmeticMixin, NDSlicingMixin, NDData): + """Implements ``NDData`` with all Mixins, plus some ``AstroData`` + specifics. + + This class implements an ``NDData``-like container that supports reading + and writing as implemented in the ``astropy.io.registry`` and also slicing + (indexing) and simple arithmetics (add, subtract, divide and multiply). + + A very important difference between ``NDAstroData`` and ``NDData`` is that + the former attempts to load all its data lazily. There are also some + important differences in the interface (eg. ``.data`` lets you reset its + contents after initialization). + + Documentation is provided where our class differs. + + See also + -------- + NDData + NDArithmeticMixin + NDSlicingMixin + + Examples + -------- + + The mixins allow operation that are not possible with ``NDData`` or + ``NDDataBase``, i.e. simple arithmetics:: + + >>> from astropy.nddata import StdDevUncertainty + >>> import numpy as np + >>> data = np.ones((3,3), dtype=float) + >>> ndd1 = NDAstroData(data, uncertainty=StdDevUncertainty(data)) + >>> ndd2 = NDAstroData(data, uncertainty=StdDevUncertainty(data)) + >>> ndd3 = ndd1.add(ndd2) + >>> ndd3.data + array([[2., 2., 2.], + [2., 2., 2.], + [2., 2., 2.]]) + >>> ndd3.uncertainty.array + array([[1.41421356, 1.41421356, 1.41421356], + [1.41421356, 1.41421356, 1.41421356], + [1.41421356, 1.41421356, 1.41421356]]) + + see ``NDArithmeticMixin`` for a complete list of all supported arithmetic + operations. + + But also slicing (indexing) is possible:: + + >>> ndd4 = ndd3[1,:] + >>> ndd4.data + array([2., 2., 2.]) + >>> ndd4.uncertainty.array + array([1.41421356, 1.41421356, 1.41421356]) + + See ``NDSlicingMixin`` for a description how slicing works (which + attributes) are sliced. + """ + +
+[docs] + def __init__( + self, + data, + uncertainty=None, + mask=None, + wcs=None, + meta=None, + unit=None, + copy=False, + variance=None, + ): + """Initialize an ``NDAstroData`` instance. + + Parameters + ---------- + data : array-like + The actual data. This can be a numpy array, a memmap, or a + ``fits.ImageHDU`` object. + + uncertainty : ``NDUncertainty``-like object, optional + An object that represents the uncertainty of the data. If not + specified, the uncertainty will be set to None. + + mask : array-like, optional + An array that represents the mask of the data. If not specified, + the mask will be set to None. + + wcs : ``gwcs.WCS`` object, optional + The WCS of the data. If not specified, the WCS will be set to None. + + meta : dict-like, optional + A dictionary-like object that holds the meta data. If not + specified, the meta data will be set to None. + + unit : ``astropy.units.Unit`` object, optional + The unit of the data. If not specified, the unit will be set to + None. + + copy : bool, optional + If True, the data, uncertainty, mask, wcs, meta, and unit will be + copied. Otherwise, they will be referenced. Default is False. + + variance : array-like, optional + An array that represents the variance of the data. If not + specified, the variance will be set to None. + + Raises + ------ + ValueError + If ``uncertainty`` and ``variance`` are both specified. + + Notes + ----- + The ``uncertainty`` and ``variance`` parameters are mutually exclusive. + """ + if variance is not None: + if uncertainty is not None: + raise ValueError( + f"Cannot specify both uncertainty and variance" + f"({uncertainty = }, {variance = })." + ) + + uncertainty = ADVarianceUncertainty(variance) + + super().__init__( + FakeArray(data) if is_lazy(data) else data, + None if is_lazy(uncertainty) else uncertainty, + mask, + wcs, + meta, + unit, + copy, + ) + + if is_lazy(data): + self.data = data + if is_lazy(uncertainty): + self.uncertainty = uncertainty
+ + +
+[docs] + def __deepcopy__(self, memo): + new = self.__class__( + self._data if is_lazy(self._data) else deepcopy(self.data, memo), + self._uncertainty if is_lazy(self._uncertainty) else None, + self._mask if is_lazy(self._mask) else deepcopy(self.mask, memo), + deepcopy(self.wcs, memo), + None, + self.unit, + ) + new.meta = deepcopy(self.meta, memo) + # Needed to avoid recursion because of uncertainty's weakref to self + if not is_lazy(self._uncertainty): + new.variance = deepcopy(self.variance) + return new
+ + + @property + def window(self): + """Interface to access a section of the data, using lazy access + whenever possible. + + Returns + -------- + An instance of ``NDWindowing``, which provides ``__getitem__``, + to allow the use of square brackets when specifying the window. + Ultimately, an ``NDWindowingAstrodata`` instance is returned. + + Examples + --------- + + >>> ad[0].nddata.window[100:200, 100:200] # doctest: +SKIP + <NDWindowingAstrodata .....> + """ + return NDWindowing(self) + + def _get_uncertainty(self, section=None): + """Return the ADVarianceUncertainty object, or a slice of it.""" + if self._uncertainty is not None: + if is_lazy(self._uncertainty): + if section is None: + self.uncertainty = ADVarianceUncertainty( + self._uncertainty.data + ) + return self.uncertainty + + return ADVarianceUncertainty(self._uncertainty[section]) + + if section is not None: + return self._uncertainty[section] + + return self._uncertainty + + return None + + def _get_simple(self, target, section=None): + """Only use 'section' for image-like objects that have the same shape + as the NDAstroData object; otherwise, return the whole object""" + source = getattr(self, target) + if source is not None: + if is_lazy(source): + if section is None: + ret = np.empty(source.shape, dtype=source.dtype) + ret[:] = source.data + setattr(self, target, ret) + + else: + ret = source[section] + + return ret + + if hasattr(source, "shape"): + if section is None or source.shape != self.shape: + return np.array(source, copy=False) + + return np.array(source, copy=False)[section] + + return source + + return None + + @property + def data(self): + """An array representing the raw data stored in this instance. It + implements a setter. + """ + return self._get_simple("_data") + + @data.setter + def data(self, value): + if value is None: + raise ValueError(f"Cannot set data to {value}.") + + if is_lazy(value): + self.meta["header"] = value.header + + self._data = value + + @property + def uncertainty(self): + return self._get_uncertainty() + + @uncertainty.setter + def uncertainty(self, value): + if value is not None and not is_lazy(value): + # TODO: Accessing protected member from value + # pylint: disable=protected-access + if value._parent_nddata is not None: + value = value.__class__(value, copy=False) + + value.parent_nddata = self + + self._uncertainty = value + + @property + def mask(self): + """Get or set the mask of the data.""" + return self._get_simple("_mask") + + @mask.setter + def mask(self, value): + self._mask = value + + @property + def variance(self): + """A convenience property to access the contents of ``uncertainty``, + squared (as the uncertainty data is stored as standard deviation). + """ + arr = self._get_uncertainty() + + if arr is not None: + return arr.array + + return arr + + @variance.setter + def variance(self, value): + self.uncertainty = ( + ADVarianceUncertainty(value) if value is not None else None + ) + +
+[docs] + def set_section(self, section, input_data): + """Sets only a section of the data. This method is meant to prevent + fragmentation in the Python heap, by reusing the internal structures + instead of replacing them with new ones. + + Args + ----- + section : ``slice`` + The area that will be replaced + + input_data : ``NDData``-like instance + This object needs to implement at least ``data``, ``uncertainty``, + and ``mask``. Their entire contents will replace the data in the + area defined by ``section``. + + Examples + --------- + + >>> def setup(): + ... sec = NDData(np.zeros((100,100))) + ... ad[0].nddata.set_section( + ... (slice(None,100),slice(None,100)), + ... sec + ... ) + ... + >>> setup() # doctest: +SKIP + + """ + self.data[section] = input_data.data + + if self.uncertainty is not None: + self.uncertainty.array[section] = input_data.uncertainty.array + + if self.mask is not None: + self.mask[section] = input_data.mask
+ + +
+[docs] + def __repr__(self): + if is_lazy(self._data): + return self.__class__.__name__ + "(Memmapped)" + + return super().__repr__()
+ + + # This is a common idiom in numpy, so keep the name. + # pylint: disable=invalid-name + @property + def T(self): + """Transpose the data. This is not a copy of the data.""" + return self.transpose() + +
+[docs] + def transpose(self): + """Transpose the data. This is not a copy of the data.""" + unc = self.uncertainty + new_wcs = deepcopy(self.wcs) + inframe = new_wcs.input_frame + new_wcs.insert_transform( + inframe, + models.Mapping(tuple(reversed(range(inframe.naxes)))), + after=True, + ) + return self.__class__( + self.data.T, + uncertainty=None if unc is None else unc.__class__(unc.array.T), + mask=None if self.mask is None else self.mask.T, + wcs=new_wcs, + copy=False, + )
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/astrodata/utils.html b/_modules/astrodata/utils.html new file mode 100644 index 00000000..28c22125 --- /dev/null +++ b/_modules/astrodata/utils.html @@ -0,0 +1,633 @@ + + + + + + astrodata.utils — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for astrodata.utils

+"""Utility functions and classes for AstroData objects."""
+
+import inspect
+import logging
+import warnings
+from collections import namedtuple
+from functools import wraps
+from traceback import format_stack
+
+import numpy as np
+
+INTEGER_TYPES = (int, np.integer)
+
+__all__ = (
+    "assign_only_single_slice",
+    "astro_data_descriptor",
+    "AstroDataDeprecationWarning",
+    "astro_data_tag",
+    "deprecated",
+    "normalize_indices",
+    "returns_list",
+    "TagSet",
+    "Section",
+)
+
+
+class AstroDataDeprecationWarning(DeprecationWarning):
+    """Warning class for deprecated AstroData methods."""
+
+
+warnings.simplefilter("always", AstroDataDeprecationWarning)
+
+
+def deprecated(reason):
+    """Marks a function as deprecated.
+
+    Parameters
+    ----------
+    reason : str
+        The reason why the function is deprecated
+
+    Returns
+    -------
+    function
+        The decorated function
+
+    Usage
+    -----
+
+    >>> @deprecated("Use another function instead")
+    ... def my_function():
+    ...     pass
+    """
+
+    def decorator_wrapper(fn):
+        @wraps(fn)
+        def wrapper(*args, **kw):
+            current_source = "|".join(format_stack(inspect.currentframe()))
+            if current_source not in wrapper.seen:
+                wrapper.seen.add(current_source)
+                warnings.warn(reason, AstroDataDeprecationWarning)
+            return fn(*args, **kw)
+
+        wrapper.seen = set()
+        return wrapper
+
+    return decorator_wrapper
+
+
+def normalize_indices(slc, nitems):
+    """Normalize a slice or index to a list of indices."""
+    multiple = True
+    if isinstance(slc, slice):
+        start, stop, step = slc.indices(nitems)
+        indices = list(range(start, stop, step))
+    elif isinstance(slc, INTEGER_TYPES) or (
+        isinstance(slc, tuple)
+        and all(isinstance(i, INTEGER_TYPES) for i in slc)
+    ):
+        if isinstance(slc, INTEGER_TYPES):
+            slc = (int(slc),)  # slc's type m
+            multiple = False
+
+        else:
+            multiple = True
+
+        # Normalize negative indices...
+        indices = [(x if x >= 0 else nitems + x) for x in slc]
+
+    else:
+        raise ValueError(f"Invalid index: {slc}")
+
+    if any(i >= nitems for i in indices):
+        raise IndexError("Index out of range")
+
+    return indices, multiple
+
+
+
+[docs] +class TagSet(namedtuple("TagSet", "add remove blocked_by blocks if_present")): + """Named tuple that is used by tag methods to return which actions should + be performed on a tag set. + + All the attributes are optional, and any combination of them can be used, + allowing to create complex tag structures. Read the documentation on the + tag-generating algorithm if you want to better understand the interactions. + + The simplest TagSet, though, tends to just add tags to the global set. + + It can be initialized by position, like any other tuple (the order of the + arguments is the one in which the attributes are listed below). It can + also be initialized by name. + + Attributes + ---------- + add : set of str, optional + Tags to be added to the global set + + remove : set of str, optional + Tags to be removed from the global set + + blocked_by : set of str, optional + Tags that will prevent this TagSet from being applied + + blocks : set of str, optional + Other TagSets containing these won't be applied + + if_present : set of str, optional + This TagSet will be applied only *all* of these tags are present + + Examples + --------- + >>> TagSet() # doctest: +SKIP + TagSet( + add=set(), + remove=set(), + blocked_by=set(), + blocks=set(), + if_present=set() + ) + >>> TagSet({'BIAS', 'CAL'}) # doctest: +SKIP + TagSet( + add={'BIAS', 'CAL'}, + remove=set(), + blocked_by=set(), + blocks=set(), + if_present=set() + ) + >>> TagSet(remove={'BIAS', 'CAL'}) # doctest: +SKIP + TagSet( + add=set(), + remove={'BIAS', 'CAL'}, + blocked_by=set(), + blocks=set(), + if_present=set() + ) + """ + +
+[docs] + def __new__( + cls, + add=None, + remove=None, + blocked_by=None, + blocks=None, + if_present=None, + ): + return super().__new__( + cls, + add or set(), + remove or set(), + blocked_by or set(), + blocks or set(), + if_present or set(), + )
+
+ + + +
+[docs] +def astro_data_descriptor(fn): + """Decorator that will mark a class method as an AstroData descriptor. + Useful to produce list of descriptors, for example. + + If used in combination with other decorators, this one *must* be the + one on the top (ie. the last one applying). It doesn't modify the + method in any other way. + + Args + ----- + fn : method + The method to be decorated + + Returns + -------- + The tagged method (not a wrapper) + """ + fn.descriptor_method = True + return fn
+ + + +
+[docs] +def returns_list(fn): + """Decorator to ensure that descriptors that should return a list (of one + value per extension) only returns single values when operating on single + slices; and vice versa. + + This is a common case, and you can use the decorator to simplify the + logic of your descriptors. + + Args + ----- + fn : method + The method to be decorated + + Returns + -------- + A function + """ + + @wraps(fn) + def wrapper(self, *args, **kwargs): + ret = fn(self, *args, **kwargs) + if self.is_single: + if isinstance(ret, list): + if len(ret) > 1: + logging.warning( + "Descriptor %s returned a list " + "of %s elements when operating on " + "a single slice", + fn.__name__, + len(ret), + ) + + return ret[0] + + return ret + + if isinstance(ret, list): + if len(ret) == len(self): + return ret + + raise IndexError( + f"Incompatible numbers of extensions and " + f"elements in {fn.__name__}" + ) + + return [ret] * len(self) + + return wrapper
+ + + +def assign_only_single_slice(fn): + """Raise `ValueError` if assigning to a non-single slice.""" + + @wraps(fn) + def wrapper(self, *args, **kwargs): + if not self.is_single: + raise ValueError( + "Trying to assign to an AstroData object that " + "is not a single slice" + ) + return fn(self, *args, **kwargs) + + return wrapper + + +
+[docs] +def astro_data_tag(fn): + """Decorator that marks methods of an `AstroData` derived class as part of + the tag-producing system. + + It wraps the method around a function that will ensure a consistent return + value: the wrapped method can return any sequence of sequences of strings, + and they will be converted to a TagSet. If the wrapped method + returns None, it will be turned into an empty TagSet. + + Args + ----- + fn : method + The method to be decorated + + Returns + -------- + A wrapper function + """ + + @wraps(fn) + def wrapper(self): + try: + ret = fn(self) + if ret is not None: + if not isinstance(ret, TagSet): + raise TypeError( + f"Tag function {fn.__name__} didn't return a TagSet" + ) + + return TagSet(*tuple(set(s) for s in ret)) + + except KeyError: + pass + + # Return empty TagSet for the "doesn't apply" case + return TagSet() + + wrapper.tag_method = True + return wrapper
+ + + +
+[docs] +class Section(tuple): + """A class to handle n-dimensional sections""" + +
+[docs] + def __new__(cls, *args, **kwargs): + # Ensure that the order of keys is what we want + axis_names = [x for axis in "xyzuvw" for x in (f"{axis}1", f"{axis}2")] + + _dict = dict(zip(axis_names, args + ("",) * len(kwargs))) + + _dict.update(kwargs) + + if list(_dict.values()).count("") or (len(_dict) % 2): + raise ValueError("Cannot initialize 'Section' object") + + instance = tuple.__new__(cls, tuple(_dict.values())) + instance._axis_names = tuple(_dict.keys()) + + if not all(np.diff(instance)[::2] > 0): + raise ValueError( + "Not all 'Section' end coordinates exceed the " + "start coordinates" + ) + + return instance
+ + + @property + def axis_dict(self): + return dict(zip(self._axis_names, self)) + +
+[docs] + def __getnewargs__(self): + return tuple(self)
+ + +
+[docs] + def __getattr__(self, attr): + if attr in self._axis_names: + return self.axis_dict[attr] + + raise AttributeError(f"No such attribute '{attr}'")
+ + +
+[docs] + def __repr__(self): + return ( + "Section(" + + ", ".join([f"{k}={self.axis_dict[k]}" for k in self._axis_names]) + + ")" + )
+ + + @property + def ndim(self): + """The number of dimensions in the section.""" + return len(self) // 2 + +
+[docs] + @staticmethod + def from_shape(value): + """Produce a Section object defining a given shape.""" + return Section(*[y for x in reversed(value) for y in (0, x)])
+ + +
+[docs] + @staticmethod + def from_string(value): + """The inverse of __str__, produce a Section object from a string.""" + return Section( + *[ + y + for x in value.strip("[]").split(",") + for start, end in [x.split(":")] + for y in ( + None if start == "" else int(start) - 1, + None if end == "" else int(end), + ) + ] + )
+ + +
+[docs] + @deprecated( + "Renamed to 'as_iraf_section', this is just an alias for now " + "and will be removed in a future version." + ) + def asIRAFsection(self): # pylint: disable=invalid-name + """Deprecated, see as_iraf_section""" + return self.as_iraf_section()
+ + +
+[docs] + def as_iraf_section(self): + """Produce string of style '[x1:x2,y1:y2]' that is 1-indexed + and end-inclusive + """ + return ( + "[" + + ",".join( + [ + ":".join( + [ + str(self.axis_dict[axis] + 1), + str(self.axis_dict[axis.replace("1", "2")]), + ] + ) + for axis in self._axis_names[::2] + ] + ) + + "]" + )
+ + +
+[docs] + def asslice(self, add_dims=0): + """Return the Section object as a slice/list of slices. Higher + dimensionality can be achieved with the add_dims parameter. + """ + return (slice(None),) * add_dims + tuple( + slice(self.axis_dict[axis], self.axis_dict[axis.replace("1", "2")]) + for axis in reversed(self._axis_names[::2]) + )
+ + +
+[docs] + def contains(self, section): + """Return True if the supplied section is entirely within self""" + if self.ndim != section.ndim: + raise ValueError("Sections have different dimensionality") + + con1 = all(s2 >= s1 for s1, s2 in zip(self[::2], section[::2])) + + if not con1: + return False + + con2 = all(s2 <= s1 for s1, s2 in zip(self[1::2], section[1::2])) + + return con1 and con2
+ + +
+[docs] + def is_same_size(self, section): + """Return True if the Sections are the same size""" + return np.array_equal(np.diff(self)[::2], np.diff(section)[::2])
+ + +
+[docs] + def overlap(self, section): + """Determine whether the two sections overlap. If so, the Section + common to both is returned, otherwise None + """ + if self.ndim != section.ndim: + raise ValueError("Sections have different dimensionality") + + mins = [max(s1, s2) for s1, s2 in zip(self[::2], section[::2])] + maxs = [min(s1, s2) for s1, s2 in zip(self[1::2], section[1::2])] + + try: + return self.__class__( + *[v for pair in zip(mins, maxs) for v in pair] + ) + + except ValueError as err: + logging.warning( + "Sections do not overlap, recieved %s: %s", + err.__class__.__name__, + err, + ) + + return None
+ + +
+[docs] + def shift(self, *shifts): + """Shift a section in each direction by the specified amount""" + if len(shifts) != self.ndim: + raise ValueError( + f"Number of shifts {len(shifts)} incompatible " + f"with dimensionality {self.ndim}" + ) + return self.__class__( + *[ + x + s + for x, s in zip(self, [ss for s in shifts for ss in [s] * 2]) + ] + )
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 00000000..990037b6 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,116 @@ + + + + + + Overview: module code — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ +

All modules for which code is available

+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_sources/api/astrodata.AstroData.rst.txt b/_sources/api/astrodata.AstroData.rst.txt new file mode 100644 index 00000000..a6730177 --- /dev/null +++ b/_sources/api/astrodata.AstroData.rst.txt @@ -0,0 +1,103 @@ +AstroData +========= + +.. currentmodule:: astrodata + +.. autoclass:: AstroData + :show-inheritance: + + .. rubric:: Attributes Summary + + .. autosummary:: + + ~AstroData.data + ~AstroData.descriptors + ~AstroData.exposed + ~AstroData.ext_tables + ~AstroData.filename + ~AstroData.hdr + ~AstroData.header + ~AstroData.id + ~AstroData.indices + ~AstroData.is_sliced + ~AstroData.mask + ~AstroData.nddata + ~AstroData.orig_filename + ~AstroData.path + ~AstroData.phu + ~AstroData.shape + ~AstroData.tables + ~AstroData.tags + ~AstroData.uncertainty + ~AstroData.variance + ~AstroData.wcs + + .. rubric:: Methods Summary + + .. autosummary:: + + ~AstroData.add + ~AstroData.append + ~AstroData.crop + ~AstroData.divide + ~AstroData.info + ~AstroData.instrument + ~AstroData.is_settable + ~AstroData.load + ~AstroData.matches_data + ~AstroData.multiply + ~AstroData.object + ~AstroData.operate + ~AstroData.read + ~AstroData.reset + ~AstroData.subtract + ~AstroData.table + ~AstroData.telescope + ~AstroData.update_filename + ~AstroData.write + + .. rubric:: Attributes Documentation + + .. autoattribute:: data + .. autoattribute:: descriptors + .. autoattribute:: exposed + .. autoattribute:: ext_tables + .. autoattribute:: filename + .. autoattribute:: hdr + .. autoattribute:: header + .. autoattribute:: id + .. autoattribute:: indices + .. autoattribute:: is_sliced + .. autoattribute:: mask + .. autoattribute:: nddata + .. autoattribute:: orig_filename + .. autoattribute:: path + .. autoattribute:: phu + .. autoattribute:: shape + .. autoattribute:: tables + .. autoattribute:: tags + .. autoattribute:: uncertainty + .. autoattribute:: variance + .. autoattribute:: wcs + + .. rubric:: Methods Documentation + + .. automethod:: add + .. automethod:: append + .. automethod:: crop + .. automethod:: divide + .. automethod:: info + .. automethod:: instrument + .. automethod:: is_settable + .. automethod:: load + .. automethod:: matches_data + .. automethod:: multiply + .. automethod:: object + .. automethod:: operate + .. automethod:: read + .. automethod:: reset + .. automethod:: subtract + .. automethod:: table + .. automethod:: telescope + .. automethod:: update_filename + .. automethod:: write diff --git a/_sources/api/astrodata.AstroDataError.rst.txt b/_sources/api/astrodata.AstroDataError.rst.txt new file mode 100644 index 00000000..b93c97b1 --- /dev/null +++ b/_sources/api/astrodata.AstroDataError.rst.txt @@ -0,0 +1,6 @@ +AstroDataError +============== + +.. currentmodule:: astrodata + +.. autoexception:: AstroDataError diff --git a/_sources/api/astrodata.AstroDataMixin.rst.txt b/_sources/api/astrodata.AstroDataMixin.rst.txt new file mode 100644 index 00000000..a9892094 --- /dev/null +++ b/_sources/api/astrodata.AstroDataMixin.rst.txt @@ -0,0 +1,23 @@ +AstroDataMixin +============== + +.. currentmodule:: astrodata + +.. autoclass:: AstroDataMixin + :show-inheritance: + + .. rubric:: Attributes Summary + + .. autosummary:: + + ~AstroDataMixin.shape + ~AstroDataMixin.size + ~AstroDataMixin.variance + ~AstroDataMixin.wcs + + .. rubric:: Attributes Documentation + + .. autoattribute:: shape + .. autoattribute:: size + .. autoattribute:: variance + .. autoattribute:: wcs diff --git a/_sources/api/astrodata.NDAstroData.rst.txt b/_sources/api/astrodata.NDAstroData.rst.txt new file mode 100644 index 00000000..65784550 --- /dev/null +++ b/_sources/api/astrodata.NDAstroData.rst.txt @@ -0,0 +1,39 @@ +NDAstroData +=========== + +.. currentmodule:: astrodata + +.. autoclass:: NDAstroData + :show-inheritance: + + .. rubric:: Attributes Summary + + .. autosummary:: + + ~NDAstroData.T + ~NDAstroData.data + ~NDAstroData.mask + ~NDAstroData.uncertainty + ~NDAstroData.variance + ~NDAstroData.window + + .. rubric:: Methods Summary + + .. autosummary:: + + ~NDAstroData.set_section + ~NDAstroData.transpose + + .. rubric:: Attributes Documentation + + .. autoattribute:: T + .. autoattribute:: data + .. autoattribute:: mask + .. autoattribute:: uncertainty + .. autoattribute:: variance + .. autoattribute:: window + + .. rubric:: Methods Documentation + + .. automethod:: set_section + .. automethod:: transpose diff --git a/_sources/api/astrodata.Section.rst.txt b/_sources/api/astrodata.Section.rst.txt new file mode 100644 index 00000000..1fa174a9 --- /dev/null +++ b/_sources/api/astrodata.Section.rst.txt @@ -0,0 +1,45 @@ +Section +======= + +.. currentmodule:: astrodata + +.. autoclass:: Section + :show-inheritance: + + .. rubric:: Attributes Summary + + .. autosummary:: + + ~Section.axis_dict + ~Section.ndim + + .. rubric:: Methods Summary + + .. autosummary:: + + ~Section.asIRAFsection + ~Section.as_iraf_section + ~Section.asslice + ~Section.contains + ~Section.from_shape + ~Section.from_string + ~Section.is_same_size + ~Section.overlap + ~Section.shift + + .. rubric:: Attributes Documentation + + .. autoattribute:: axis_dict + .. autoattribute:: ndim + + .. rubric:: Methods Documentation + + .. automethod:: asIRAFsection + .. automethod:: as_iraf_section + .. automethod:: asslice + .. automethod:: contains + .. automethod:: from_shape + .. automethod:: from_string + .. automethod:: is_same_size + .. automethod:: overlap + .. automethod:: shift diff --git a/_sources/api/astrodata.TagSet.rst.txt b/_sources/api/astrodata.TagSet.rst.txt new file mode 100644 index 00000000..2d6191cd --- /dev/null +++ b/_sources/api/astrodata.TagSet.rst.txt @@ -0,0 +1,7 @@ +TagSet +====== + +.. currentmodule:: astrodata + +.. autoclass:: TagSet + :show-inheritance: diff --git a/_sources/api/astrodata.add_header_to_table.rst.txt b/_sources/api/astrodata.add_header_to_table.rst.txt new file mode 100644 index 00000000..920b0cae --- /dev/null +++ b/_sources/api/astrodata.add_header_to_table.rst.txt @@ -0,0 +1,6 @@ +add_header_to_table +=================== + +.. currentmodule:: astrodata + +.. autofunction:: add_header_to_table diff --git a/_sources/api/astrodata.astro_data_descriptor.rst.txt b/_sources/api/astrodata.astro_data_descriptor.rst.txt new file mode 100644 index 00000000..80688c15 --- /dev/null +++ b/_sources/api/astrodata.astro_data_descriptor.rst.txt @@ -0,0 +1,6 @@ +astro_data_descriptor +===================== + +.. currentmodule:: astrodata + +.. autofunction:: astro_data_descriptor diff --git a/_sources/api/astrodata.astro_data_tag.rst.txt b/_sources/api/astrodata.astro_data_tag.rst.txt new file mode 100644 index 00000000..1f224c4d --- /dev/null +++ b/_sources/api/astrodata.astro_data_tag.rst.txt @@ -0,0 +1,6 @@ +astro_data_tag +============== + +.. currentmodule:: astrodata + +.. autofunction:: astro_data_tag diff --git a/_sources/api/astrodata.create.rst.txt b/_sources/api/astrodata.create.rst.txt new file mode 100644 index 00000000..ae5daa40 --- /dev/null +++ b/_sources/api/astrodata.create.rst.txt @@ -0,0 +1,6 @@ +create +====== + +.. currentmodule:: astrodata + +.. autofunction:: create diff --git a/_sources/api/astrodata.from_file.rst.txt b/_sources/api/astrodata.from_file.rst.txt new file mode 100644 index 00000000..fb9081fa --- /dev/null +++ b/_sources/api/astrodata.from_file.rst.txt @@ -0,0 +1,6 @@ +from_file +========= + +.. currentmodule:: astrodata + +.. autofunction:: from_file diff --git a/_sources/api/astrodata.open.rst.txt b/_sources/api/astrodata.open.rst.txt new file mode 100644 index 00000000..37110c63 --- /dev/null +++ b/_sources/api/astrodata.open.rst.txt @@ -0,0 +1,6 @@ +open +==== + +.. currentmodule:: astrodata + +.. autofunction:: open diff --git a/_sources/api/astrodata.returns_list.rst.txt b/_sources/api/astrodata.returns_list.rst.txt new file mode 100644 index 00000000..60a14c66 --- /dev/null +++ b/_sources/api/astrodata.returns_list.rst.txt @@ -0,0 +1,6 @@ +returns_list +============ + +.. currentmodule:: astrodata + +.. autofunction:: returns_list diff --git a/_sources/api/astrodata.version.rst.txt b/_sources/api/astrodata.version.rst.txt new file mode 100644 index 00000000..553303d6 --- /dev/null +++ b/_sources/api/astrodata.version.rst.txt @@ -0,0 +1,6 @@ +version +======= + +.. currentmodule:: astrodata + +.. autofunction:: version diff --git a/_sources/api_short.rst.txt b/_sources/api_short.rst.txt new file mode 100644 index 00000000..e19dc12a --- /dev/null +++ b/_sources/api_short.rst.txt @@ -0,0 +1,13 @@ +Common API for Users +==================== + +This package contains the common API for users of the AstroData system. It +provides a single interface to the data, regardless of the format in which it +is stored. + +If you would like a more extensive description of the astrodata package as a +user, see the |UserGuide|. If you are interested in developing with AstroData, +see the |DeveloperGuide|. + +.. automodapi:: astrodata + :no-inheritance-diagram: diff --git a/_sources/examples/gemini_examples/index.rst.txt b/_sources/examples/gemini_examples/index.rst.txt new file mode 100644 index 00000000..60856364 --- /dev/null +++ b/_sources/examples/gemini_examples/index.rst.txt @@ -0,0 +1,15 @@ +.. _gemini_examples: + +Gemini Examples +=============== + +These examples use the |Gemini|'s |DRAGONS| data reduction pipeline. They use +external code to perform operations outside of |astrodata|'s capabilities. + +Specifically, |DRAGONS| extends |AstroData| in its |gemini_instruments| +package. This package is a grounded example of how to extend |AstroData| for +specific instruments. All reduction functionality is supported by other parts +of the library. + +To use these examples, you need to install |DRAGONS| and its dependencies. +Please follow the |DRAGONS| installation instructions at |DRAGONS_install|. diff --git a/_sources/examples/generic_examples/index.rst.txt b/_sources/examples/generic_examples/index.rst.txt new file mode 100644 index 00000000..2a03aeac --- /dev/null +++ b/_sources/examples/generic_examples/index.rst.txt @@ -0,0 +1,30 @@ +.. _generic_examples: + +Generic Examples +================ + +This section contains examples using |astrodata| with no specific instrument +or pipeline in mind. The examples are intended to demonstrate the basic +functionality of |astrodata| and to provide a starting point for users +who are new to the package. + +Many of these patterns will apply broadly to anything derived from +|AstroData|. However, no "real" data is used in these examples, and +they are not intended to represent how any given reduction "should" +be done to achieve science quality data. + +.. _generic_examples_user: + +User Examples +------------- + +.. toctree:: + :maxdepth: 2 + :glob: + + usermanual_structure_examples/1_example_structure.rst + +.. _generic_examples_developer: + +Developer Examples +------------------ diff --git a/_sources/examples/index.rst.txt b/_sources/examples/index.rst.txt new file mode 100644 index 00000000..a281df6f --- /dev/null +++ b/_sources/examples/index.rst.txt @@ -0,0 +1,15 @@ +.. _examples_landing_page: + +Examples +======== + +We provide a number of examples to aid in understanding specific use cases, +common patterns, and best practices when working with |astrodata|. To see a +complete user guide, please see our |UserGuide|. For a deeper guide to +programming with |astrodata|, please visit our |DeveloperGuide|. + +.. toctree:: + :maxdepth: 2 + + generic_examples/index.rst + gemini_examples/index.rst diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 00000000..c9fc78ab --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,20 @@ +astrodata Documentation +----------------------- + +This is the documentation for astrodata. + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + manuals/index + examples/index + api_short + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/_sources/manuals/appendix_descriptors.rst.txt b/_sources/manuals/appendix_descriptors.rst.txt new file mode 100644 index 00000000..b808fd8a --- /dev/null +++ b/_sources/manuals/appendix_descriptors.rst.txt @@ -0,0 +1,246 @@ +.. descriptors.rst + +*********************************** +List of Gemini Standard Descriptors +*********************************** + +To run and re-use Gemini primitives and functions this list of Standard +Descriptors must be defined for input data. This also applies to data +that is to be served by the Gemini Observatory Archive (GOA). + +For any ``AstroData`` objects, to get the list of the descriptors that are +defined use the ``AstroData.descriptors`` attribute:: + + >> import astrodata + >> import gemini_instruments + >> ad = astrodata.open('../playdata/N20170609S0154.fits') + + >> ad.descriptors + ('airmass', 'amp_read_area', 'ao_seeing', ..., 'well_depth_setting') + +To get the values:: + + >> ad.airmass() + + >> for descriptor in ad.descriptors: + ... print(descriptor, getattr(ad, descriptor)()) + +Note that not all of the descriptors below are defined for all of the +instruments. For example, ``shuffle_pixels`` is defined only for GMOS data +since only GMOS offers a Nod & Shuffle mode. + + +.. tabularcolumns:: |l|p{3.0in}|l| + + ++--------------------------------+----------------------------------------------------------------+-----------------+ +| **Descriptor** | **Short Definition** | **Python type** | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| | | ad[0].desc() | +| | +-----------------+ +| | | ad.desc() | ++================================+================================================================+=================+ +| airmass | Airmass of the observation. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| amp_read_area | Combination of amplifier name and 1-indexed section relative | str | +| | to the detector. +-----------------+ +| | | list of str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| ao_seeing | Estimate of the natural seeing as calculated from the | float | +| | adaptive optics systems. | | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| array_name | Name assigned to the array generated by a given amplifier, | str | +| | one array per amplifier. +-----------------+ +| | | list of str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| array_section | Section covered by the array(s), in 0-indexed pixels, relative | Section | +| | to the detector frame (e.g. position of multiple amps read +-----------------+ +| | within a CCD). Uses ``namedtuple`` "Section" defined in | list of Section | +| | ``gemini_instruments.common``. | | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| azimuth | Pointing position in azimuth, in degrees. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| calibration_key | Key used in the database that the ``getProcessed*`` primitives | str | +| | use to store previous calibration association information. | | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| camera | Name of the camera. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| cass_rotator_pa | Position angle of the Cassegrain rotator, in degrees. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| central_wavelength | Central wavelength, in meters. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| coadds | Number of co-adds. | int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| data_label | Gemini data label. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| data_section | Section where the sky-exposed data falls, in 0-indexed pixels. | Section | +| | Uses ``namedtuple`` "Section" defined in +-----------------+ +| | ``gemini_instruments.common`` | list of Section | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| dec | Declination of the center of the field, in degrees. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| decker | Name of the decker. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| detector_name | Name assigned to the detector. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| detector_roi_setting | Human readable Region of Interest (ROI) setting | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| detector_rois_requested | Section defining the Regions of Interest, in 0-indexed pixels. | list of Section | +| | Uses ``namedtuple`` "Section" defined in | | +| | ``gemini_instruments.common``. | | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| detector_section | Section covered by the detector(s), in 0-indexed pixels, | list | +| | relative to the whole mosaic of detectors. +-----------------+ +| | Uses ``namedtuple`` "Section" defined in | list of Section | +| | ``gemini_instruments.common``. | | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| detector_x_bin | X-axis binning. | int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| detector_x_offset | Telescope offset along the detector X-axis, in pixels. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| detector_y_bin | Y-axis binning. | int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| detector_y_offset | Telescope offset along the detector Y-axis, in pixels. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| disperser | Name of the disperser. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| dispersion | Value for the dispersion, in meters per pixel. | float | +| | +-----------------+ +| | | list of float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| dispersion_axis | Dispersion axis. | int | +| | +-----------------+ +| | | list of int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| effective_wavelength | Wavelength representing the bandpass or the spectrum coverage. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| elevation | Pointing position in elevation, in degrees. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| exposure_time | Exposure time, in seconds. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| filter_name | Name of the filter combination. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| focal_plane_mask | Name of the mask in the focal plane. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| gain | Gain in electrons per ADU | float | +| | +-----------------+ +| | | list of float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| gain_setting | Human readable gain setting (eg. low, high) | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| gcal_lamp | Returns the name of the GCAL lamp being used, or "Off" if no | str | +| | lamp is in use. | | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| group_id | Gemini observation group ID that identifies compatible data. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| instrument | Name of the instrument | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| is_ao | Whether or not the adaptive optics system was used. | bool | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| is_coadds_summed | Whether co-adds are summed or averaged. | bool | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| local_time | Local time. | datetime | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| lyot_stop | Name of the lyot stop. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| mdf_row_id | Mask Definition File row ID of a cut MOS or XD spectrum. | int ?? | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| nod_count | Number of nods to A and B positions. | tuple of int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| nod_offsets | Nod offsets to A and B positions, in arcseconds | tuple of float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| nominal_atmospheric_extinction | Nomimal atmospheric extinction, from model. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| nominal_photometric_zeropoint | Nominal photometric zeropoint. | float | +| | +-----------------+ +| | | list of float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| non_linear_level | Lower boundary of the non-linear regime. | float | +| | +-----------------+ +| | | list of int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| object | Name of the target (as entered by the user). | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| observation_class | Gemini class name for the observation | str | +| | (eg. 'science', 'acq', 'dayCal'). | | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| observation_epoch | Observation epoch. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| observation_id | Gemini observation ID. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| observation_type | Gemini observation type (eg. 'OBJECT', 'FLAT', 'ARC'). | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| overscan_section | Section where the overscan data falls, in 0-indexed pixels. | Section | +| | Uses namedtuple "Section" defined in +-----------------+ +| | ``gemini_instruments.common``. | list of Section | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| pixel_scale | Pixel scale in arcsec per pixel. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| program_id | Gemini program ID. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| pupil_mask | Name of the pupil mask. | str ?? | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| qa_state | Gemini quality assessment state (eg. pass, usable, fail). | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| ra | Right ascension, in degrees. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| raw_bg | Gemini sky background band. | int ?? | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| raw_cc | Gemini cloud coverage band. | int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| raw_iq | Gemini image quality band. | int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| raw_wv | Gemini water vapor band. | int ?? | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| read_mode | Gemini name for combination for gain setting and read setting. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| read_noise | Read noise in electrons. | float | +| | +-----------------+ +| | | list of float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| read_speed_setting | human readable read mode setting (eg. slow, fast). | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| requested_bg | PI requested Gemini sky background band. | int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| requested_cc | PI requested Gemini cloud coverage band. | int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| requested_iq | PI requested Gemini image quality band. | int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| requested_wv | PI requested Gemini water vapor band. | int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| saturation_level | Saturation level. | int | +| | +-----------------+ +| | | list of int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| shuffle_pixels | Charge shuffle, in pixels. (nod and shuffle mode) | int | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| slit | Name of the slit. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| target_dec | Declination of the target, in degrees. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| target_ra | Right Ascension of the target, in degrees. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| telescope | Name of the telescope. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| telescope_x_offset | Offset along the telescope's x-axis. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| telescope_y_offset | Offset along the telescope's y-axis. | float | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| ut_date | UT date of the observation. | datetime.date | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| ut_datetime | UT date and time of the observation. | datetime | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| ut_time | UT time of the observation. | datetime.time | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| wavefront_sensor | Wavefront sensor used for the observation. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| wavelength_band | Band associated with the filter or the central wavelength. | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| wcs_dec | Declination of the center of field from the WCS keywords. | float | +| | In degrees. | | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| wcs_ra | Right Ascension of the center of field from the WCS keywords. | float | +| | In degrees. | | ++--------------------------------+----------------------------------------------------------------+-----------------+ +| well_depth_setting | Human readable well depth setting (eg. shallow, deep) | str | ++--------------------------------+----------------------------------------------------------------+-----------------+ diff --git a/_sources/manuals/cheatsheet.rst.txt b/_sources/manuals/cheatsheet.rst.txt new file mode 100644 index 00000000..624f6364 --- /dev/null +++ b/_sources/manuals/cheatsheet.rst.txt @@ -0,0 +1,463 @@ +.. cheatsheet + +.. _cheatsheet: + +*********** +Cheat Sheet +*********** + +.. admonition:: Document ID + + PIPE-USER-105_AstrodataCheatSheet + +A data package is available for download if you wish to run the examples +included in this cheat sheet. Download it at: + + ``_ + +To unpack:: + + $ cd + $ tar xvf ad_usermanual_datapkg-v1.tar + $ bunzip2 ad_usermanual/playdata/*.bz2 + +Then go to the ``ad_usermanual/playground`` directory to run the examples. + +Imports +======= + +Import :mod:`astrodata` and :mod:`gemini_instruments`:: + + >> import astrodata + >> import gemini_instruments + +Basic read and write operations +=============================== + +Open a file:: + + >> ad = astrodata.open('../playdata/N20170609S0154.fits') + +Get path and filename:: + + >> ad.path + '../playdata/N20170609S0154.fits' + >> ad.filename + 'N20170609S0154.fits' + +Write to a new file:: + + >> ad.write(filename='new154.fits') + >> ad.filename + N20170609S0154.fits + +Overwrite the file:: + + >> adnew = astrodata.open('new154.fits') + >> adnew.filename + new154.fits + >> adnew.write(overwrite=True) + +Object structure +================ + +Description +----------- +The |AstroData| object is assigned by "tags" that describe the +type of data it contains. The tags are drawn from rules defined in +|gemini_instruments| and are based on header information. + +When mapping a FITS file, each science pixel extension is loaded as a +|NDAstroData| object. The list is zero-indexed. So FITS +extension 1 becomes element 0 of the |AstroData| object. If a ``VAR`` +extension is present, it is loaded to the variance attribute of the +|NDAstroData|. If a ``DQ`` extension is present, it is loaded to the ``.mask`` +attribute of the |NDAstroData|. ``SCI``, ``VAR`` and ``DQ`` are associated +through the ``EXTVER`` keyword value. + +In the file below, each |AstroData| "extension" contains the pixel data, +then an error plane (``.variance``) and a bad pixel mask plane (``.mask``). +|Table| can be attached to an extension, like OBJCAT, or to the +|AstroData| object globally, like REFCAT. (In this case, OBJCAT is a +catalogue of the sources detected in the image, REFCAT is a reference catalog +for the area covered by the whole file.) If other 2D data needs to be +associated with an extension this can also be done, like here with OBJMASK, +a 2D mask matching the sources in the image. + +:: + + >> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits') + >> ad.info() + Filename: ../playdata/N20170609S0154_varAdded.fits + Tags: ACQUISITION GEMINI GMOS IMAGE NORTH OVERSCAN_SUBTRACTED OVERSCAN_TRIMMED + PREPARED SIDEREAL + Pixels Extensions + Index Content Type Dimensions Format + [ 0] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) int16 + .OBJCAT Table (6, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + [ 1] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) int16 + .OBJCAT Table (8, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + [ 2] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) int16 + .OBJCAT Table (7, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + [ 3] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) int16 + .OBJCAT Table (5, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + Other Extensions + Type Dimensions + .REFCAT Table (245, 16) + + + +Modifying the structure +----------------------- + +Let's first get our play data loaded. You are encouraged to do a +:meth:`~astrodata.AstroData.info` before and after each structure-modification +step, to see how things change. + +:: + + >> from copy import deepcopy + >> ad = astrodata.open('../playdata/N20170609S0154.fits') + >> adcopy = deepcopy(ad) + >> advar = astrodata.open('../playdata/N20170609S0154_varAdded.fits') + +Append an extension:: + + >> adcopy.append(advar[3]) + >> adcopy.append(advar[3].data) + + +Delete an extension:: + + >> del adcopy[5] + +Delete and add variance and mask planes:: + + >> var = adcopy[4].variance + >> adcopy[4].variance = None + >> adcopy[4].variance = var + +Attach a table to an extension:: + + >> adcopy[3].SMAUG = advar[0].OBJCAT.copy() + + +Attach a table to the |AstroData| object:: + + >> adcopy.DROGON = advar.REFCAT.copy() + +Delete a table:: + + >> del adcopy[3].SMAUG + >> del adcopy.DROGON + + + +Astrodata tags +============== + +:: + + >> ad = astrodata.open('../playdata/N20170521S0925_forStack.fits') + >> ad.tags + {'GMOS', 'OVERSCAN_SUBTRACTED', 'SIDEREAL', 'NORTH', 'OVERSCAN_TRIMMED', + 'PREPARED', 'IMAGE', 'GEMINI'} + + >> type(ad.tags) + + + >> {'IMAGE', 'PREPARED'}.issubset(ad.tags) + True + >> 'PREPARED' in ad.tags + True + + +Headers +======= + +The use of descriptors is favored over direct header access when retrieving +values already represented by descriptors, and when writing instrument agnostic +routines. + +Descriptors +----------- + +:: + + >> ad = astrodata.open('../playdata/N20170609S0154.fits') + >> ad.filter_name() + 'open1-6&g_G0301' + >> ad.filter_name(pretty=True) + 'g' + >> ad.gain() # uses a look-up table to get the correct values + [2.03, 1.97, 1.96, 2.01] + >> ad.hdr['GAIN'] + [1.0, 1.0, 1.0, 1.0] # the wrong values contained in the raw data. + >> ad[0].gain() + 2.03 + >> ad.gain()[0] + 2.03 + + >> ad.descriptors + ('airmass', 'amp_read_area', 'ao_seeing', ... + ...) + + +Direct access to header keywords +-------------------------------- + +:: + + >> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits') + +Primary Header Unit +******************* + +To see a print out of the full PHU: + + >> ad.phu + +Get value from PHU:: + + >> ad.phu['EXPTIME'] + 1.0 + + >> default = 5. + >> ad.phu.get('BOGUSKEY', default) + 5.0 + +Set PHU keyword, with and without comment:: + + >> ad.phu['NEWKEY'] = 50. + >> ad.phu['ANOTHER'] = (30., 'Some comment') + +Delete PHU keyword:: + + >> del ad.phu['NEWKEY'] + + + +Pixel extension header +********************** +To see a print out of the full header for an extension or all the extensions: + + >> ad[0].hdr + >> list(ad.hdr) + +Get value from an extension header:: + + >> ad[0].hdr['OVERSCAN'] + 469.7444308769482 + >> ad[0].hdr.get('OVERSCAN', default) + +Get keyword value for all extensions:: + + >> ad.hdr['OVERSCAN'] + [469.7444308769482, 469.656175780001, 464.9815279808291, 467.5701178951787] + >> ad.hdr.get('BOGUSKEY', 5.) + [5.0, 5.0, 5.0, 5.0] + +Set extension header keyword, with and without comment:: + + >> ad[0].hdr['NEWKEY'] = 50. + >> ad[0].hdr['ANOTHER'] = (30., 'Some comment') + +Delete an extension keyword:: + + >> del ad[0].hdr['NEWKEY'] + +Table header +************ +See the :ref:`cheatsheet_tables` section. + + +Pixel data +========== + +Arithmetics +----------- +Arithmetics with variance and mask propagation is offered for +``+``, ``-``, ``*``, ``/``, and ``**``. + +:: + + >> ad_hcont = astrodata.open('../playdata/N20170521S0925_forStack.fits') + >> ad_halpha = astrodata.open('../playdata/N20170521S0926_forStack.fits') + + >> adsub = ad_halpha - ad_hcont + + >> ad_halpha[0].data.mean() + 646.11896 + >> ad_hcont[0].data.mean() + 581.81342 + >> adsub[0].data.mean() + 64.305862 + + >> ad_halpha[0].variance.mean() + 669.80664 + >> ad_hcont[0].variance.mean() + 598.46667 + >> adsub[0].variance.mean() + 1268.274 + + + # In place multiplication + >> ad_mult = deepcopy(ad) + >> ad_mult.multiply(ad) + >> ad_mult.multiply(5.) + + + # Using descriptors to operate in-place on extensions. + >> from copy import deepcopy + >> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits') + >> ad_gain = deepcopy(ad) + >> for (ext, gain) in zip(ad_gain, ad_gain.gain()): + ... ext.multiply(gain) + >> ad_gain[0].data.mean() + 366.39545 + >> ad[0].data.mean() + 180.4904 + >> ad[0].gain() + 2.03 + + +Other pixel data operations +--------------------------- + +:: + + >> import numpy as np + >> ad_halpha[0].mask[300:350,300:350] = 1 + >> np.mean(ad_halpha[0].data[ad_halpha[0].mask==0]) + 657.1994 + >> np.mean(ad_halpha[0].data) + 646.11896 + + +.. _cheatsheet_tables: + +Tables +====== + +Tables are stored as :class:`astropy.table.Table` class. FITS tables are +represented in :mod:`astrodata` as |Table| and FITS headers are stored in the +|NDAstroData| ``meta`` attribute. Most table +access should be done through the |Table| interface. The best reference is the +|astropy| documentation itself. Below are just a few examples. + +:: + + >> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits') + +Get column names:: + + >> ad.REFCAT.colnames + +Get column content:: + + >> ad.REFCAT['zmag'] + >> ad.REFCAT['zmag', 'zmag_err'] + +Get content of row:: + + >> ad.REFCAT[4] # 5th row + >> ad.REFCAT[4:6] # 5th and 6th rows + + +Get content from specific row and column:: + + >> ad.REFCAT['zmag'][4] + +Add a column:: + + >> new_column = [0] * len(ad.REFCAT) + >> ad.REFCAT['new_column'] = new_column + +Add a row:: + + >> new_row = [0] * len(ad.REFCAT.colnames) + >> new_row[1] = '' # Cat_Id column is of "str" type. + >> ad.REFCAT.add_row(new_row) + +Selecting value from criterion:: + + >> ad.REFCAT['zmag'][ad.REFCAT['Cat_Id'] == '1237662500002005475'] + >> ad.REFCAT['zmag'][ad.REFCAT['zmag'] < 18.] + +Rejecting :class:`numpy.nan` before doing something with the values:: + + >> t = ad.REFCAT # to save typing. + >> t['zmag'][np.where(np.isnan(t['zmag']), 99, t['zmag']) < 18.] + + >> t['zmag'].mean() + nan + >> t['zmag'][np.where(~np.isnan(t['zmag']))].mean() + 20.377306 + +If for some reason you need to access the FITS table headers, here is how to do it. + +To see the FITS headers:: + + >> ad.REFCAT.meta + >> ad[0].OBJCAT.meta + +To retrieve a specific FITS table header:: + + >> ad.REFCAT.meta['header']['TTYPE3'] + 'RAJ2000' + >> ad[0].OBJCAT.meta['header']['TTYPE3'] + 'Y_IMAGE' + +To retrieve all the keyword names matching a selection:: + + >> keynames = [key for key in ad.REFCAT.meta['header'] if key.startswith('TTYPE')] + + +Create new AstroData object +=========================== + +Basic header and data array set to zeros:: + + >> from astropy.io import fits + + >> phu = fits.PrimaryHDU() + >> pixel_data = np.zeros((100,100)) + + >> hdu = fits.ImageHDU() + >> hdu.data = pixel_data + >> ad = astrodata.create(phu) + >> ad.append(hdu, name='SCI') + +or another way:: + + >> hdu = fits.ImageHDU(data=pixel_data, name='SCI') + >> ad = astrodata.create(phu, [hdu]) + +A |Table| as an |AstroData| object:: + + >> from astropy.table import Table + + >> my_astropy_table = Table(list(np.random.rand(2,100)), names=['col1', 'col2']) + >> phu = fits.PrimaryHDU() + + >> ad = astrodata.create(phu) + >> ad.SMAUG = my_astropy_table + + >> phu = fits.PrimaryHDU() + >> ad = astrodata.create(phu) + >> ad.SMAUG = my_fits_table + +WARNING: This last line will not run like the others as we have not defined +``my_fits_table``. This is nonetheless how it is done if you had a FITS table. diff --git a/_sources/manuals/full_api.rst.txt b/_sources/manuals/full_api.rst.txt new file mode 100644 index 00000000..9a14a20d --- /dev/null +++ b/_sources/manuals/full_api.rst.txt @@ -0,0 +1,22 @@ +.. _api: + +************* +Reference API +************* + +This API reference is auto-generated from the docstrings in the code. It is +meant to be a reference for the code, not a tutorial or guide. For more +information on how to use the code, see the :ref:`User Manual`. For +programming tips using AstroData, see the :ref:`Programmer's Manual`. + +.. automodule:: astrodata + :members: + :undoc-members: + :show-inheritance: + :inherited-members: + :special-members: + :exclude-members: __dict__, __weakref__ + :noindex: + +.. need to make this a reference for all the modules/functions/private + methods/attrs diff --git a/_sources/manuals/index.rst.txt b/_sources/manuals/index.rst.txt new file mode 100644 index 00000000..6e9add5d --- /dev/null +++ b/_sources/manuals/index.rst.txt @@ -0,0 +1,57 @@ +.. Astrodata Master Manual + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + Manually edited by SC December 2020 + +################ +Astrodata Manual +################ + +.. admonition:: Document ID + + PIPE-USER-120_AstrodataMasterManual + +.. toctree:: + :hidden: + + cheatsheet + usermanual/index + progmanual/index + ../examples/index + full_api + +This documentation provides different levels of information: + +- :doc:`cheatsheet` - A refresher on common astrodata operations +- :doc:`usermanual/index` - How to code with astrodata +- :doc:`progmanual/index` - How to code for astrodata +- :doc:`full_api` - The full API documentation for developers. +- :doc:`../examples/index` - Examples of how to use astrodata + +If you need support related to |astrodata|, see :ref:`ad_support`. + +.. raw:: latex + + % Set up the appendix mode and modify the LaTeX toc behavior + \appendix + \noappendicestocpagenum + \addappheadtotoc + + +.. rubric:: Appendix + +.. toctree:: + :maxdepth: 1 + + appendix_descriptors +.. api + +.. ****************** +.. Indices and tables +.. ****************** + +.. * :ref:`genindex` +.. * :ref:`modindex` +.. * :ref:`search` + +.. todolist:: diff --git a/_sources/manuals/progmanual/adclass.rst.txt b/_sources/manuals/progmanual/adclass.rst.txt new file mode 100644 index 00000000..6a620b85 --- /dev/null +++ b/_sources/manuals/progmanual/adclass.rst.txt @@ -0,0 +1,563 @@ +.. astrodata.rst + +.. _astrodata: + +************************* +AstroData and Derivatives +************************* + +The |AstroData| class is the main interface to the package. When opening files +or creating new objects, a derivative of this class is returned, as the base +|AstroData| class is not intended to be used directly. It provides the logic to +calculate the :ref:`tag set ` for an image, which is common to all +data products. Aside from that, it lacks any kind of specialized knowledge +about the different instruments that produce the FITS files. More importantly, +it defines two methods (:meth:`~astrodata.AstroData.info` and +:meth:`~astrodata.AstroData.load`) that read in and offer access to +data in FITS files. When extending to other file types, these methods should +be re-implemented. |AstroData| also defines several useful properties and +methods for FITS files specifically, such as :meth:`astrodata.AstroData.phu`, +:meth:`astrodata.AstroData.hdr`, and :meth:`astrodata.AstroData.write`, +that should also be overridden when extending to other file types. + +|AstroData| defines a common interface. Much of it consists of implementing +semantic behavior (access to components through indices, like a list; +arithmetic using standard operators; etc), mostly by implementing standard +Python methods: + +* Defines a common :meth:`~astrodata.AstroData.__init__` function. + +* Implements ``__deepcopy__``. + +* Implements ``__iter__`` to allow sequential iteration over the main set of + components (e.g., FITS science HDUs). + +* Implements ``__getitem__`` to allow data slicing (e.g., ``ad[2:4]`` returns + a new |AstroData| instance that contains only the third and fourth main + components). + +* Implements ``__delitem__`` to allow for data removal based on index. It does + not define ``__setitem__``, though. The basic AstroData series of classes + only allows to append new data blocks, not to replace them in one sweeping + move. + +* Implements ``__add__``, ``__sub__``, ``__mul__``, ``__truediv__``, and + their in-place equivalents, based on them. + +There are a few other methods. For a detailed discussion, please refer to the +:ref:`api`. + +.. _tags_prop_entry: + +The ``tags`` Property +===================== + +Additionally, and crucial to the package, AstroData offers a ``tags`` property, +that returns a resolved set of textual tags that describe the object +represented by an instance (as a set of strings). This is useful for quickly +determining if a certain dataset belongs to an arbitrary category. + +The implementation for the tags property is just a call to +``AstroData._process_tags()``. This function implements the actual logic behind +calculating the tag set (described :ref:`below `). A derivative class +could override this to provide a different logic, but this is not recommended +unless there is a very good reason to do so. + +For an example of how tags are resolved, seet :ref:`ad_tags`. + +Writing an ``AstroData`` Derivative +=================================== + +We will step through the process of creating a new |AstroData| derivative. + +.. creating_astrodata_derivative: + +Create a new class +------------------ + +The first step to creating a new |AstroData| derivative is to create a new +class that inherits from |AstroData|. If the new class is intended to handle +non-FITS files, it should override the ``info`` and ``load`` methods. In this +case, we will create a class to handle the following ASCII file: + +.. code-block:: text + + Wavelength (nm) Flux (erg/cm2/s/nm) + 1.0 1.0 + 2.0 1.5 + 3.0 2.0 + 4.0 2.5 + 5.0 3.0 + 6.0 2.5 + 7.0 1.0 + +Let's create our class to just override the info and load methods, and return a +formatted string containing the information in the header of the file when the +``AstroData.info`` method is called: + +.. code-block:: python + + from astrodata import AstroData, NDAstroData + + class AstroDataMyFile(AstroData): + _wavelength: None | NDAstroData + _flux: None | NDAstroData + _header: list[str] + + + def __init__(self, source): + super().__init__(source) + self._wavelength = None + self._flux = None + self._header = [] + + @staticmethod + def _matches_data(source): + return source.lower().endswith('.txt') + + def info(self) -> str: + def batch(iterable, n=1): + l = len(iterable) + for ndx in range(0, l, n): + yield iterable[ndx:min(ndx + n, l)] + + # Just printing out information retrieved from the text file + # header. + return ' || '.join( + f'{w:>10} {f:>10}' + for w, f in batch(self._header, 2) + ) + + def load(self, path: str): + with open(path, 'r') as f: + # First line is the header info + self._header = f.readline().split() + + # This should keep units with the data + self._header = [ + (col, unit) + for col, unit in zip(self._header[0::2], self._header[1::2]) + ] + + for line in f: + w, f = line.split() + self._wavelength.append(float(w)) + self._flux.append(float(f)) + +We now have a class that can be used to load and store data from our ASCII +file. The ``info`` method returns a formatted string containing the header +information, and the ``load`` method reads in the data from the file. The +``_matches_data`` method is used to determine if the file is of the correct +type. In this case, we are just checking that the file extension is ``.txt``. + +However, suppose we only want to use this class for files that contain +wavelength and flux information and nothing else. In that case, we can check +the header information in the ``_matches_data`` method: + +.. code-block:: python + + @staticmethod + def _matches_data(source): + if isinstance(source, str): + with open(source, 'r') as f: + header = f.readline().split() + + else: + header = source.readline().split() + + # Check that the header contains no extra information. + if any(col not in ('Wavelength', 'Flux') for col, _ in header): + return False + + # Check that the header contains both wavelength and flux information. + return all( + any(col == name for col, _ in header) + for name in ('Wavelength', 'Flux') + ) + +.. note:: + To conserve space in this document, we will only include modified code + snippets (with any necessary context) for the rest of the examples. At the + end of the document there will be an executable with the "final" code. Feel + free to use this code as a template. + +If there were other metadata contained in the file header, such as intrument +and mode information, we could use that to determine if the file is of the +correct type. + +.. _code_organization: + +Code Organization (Optional) +---------------------------- + +The code for our new class can be placed in a single file, but it is often +useful to organize our code into multiple files depending on their scope and +purpose. + +In DRAGONS, astrodata classes for individual instruments are organized into +packages. We'll use DRAGONS' GMOS instrument as an example (see +`the DRAGONS repository `_ +for the full code). It has the following structure: + +.. code-block:: text + + gemini_instruments + __init__.py + gmos + tests/ + __init__.py + adclass.py + lookup.py + +Where ``adclass.py`` contains the ``AstroDataGmos`` class, and ``lookup.py`` +contains a dictionary of filter names and their central wavelengths. The +``__init__.py`` files are used to import the classes and functions that are +needed by the package. For example, the ``gmos/__init__.py`` file contains the +following: + +.. code-block:: python + + __all__ = ['AstroDataGmos'] + + from astrodata import factory + from ..gemini import addInstrumentFilterWavelengths + from .adclass import AstroDataGmos + from .lookup import filter_wavelengths + + factory.addClass(AstroDataGmos) + # Use the generic GMOS name for both GMOS-N and GMOS-S + addInstrumentFilterWavelengths('GMOS', filter_wavelengths) + +``lookup.py`` contains information that is specific to the instrument but is +not explicitly required by the ``AstroDataGmos`` class. In this case, it is a +dictionary of filter names and their central wavelengths. The +``addInstrumentFilterWavelengths`` function is used to add this information to +the ``AstroDataGemini`` class, which is the parent class of ``AstroDataGmos``. +This function is defined in the ``gemini/__init__.py`` file, which is imported +by ``gmos/__init__.py``. The motivation here is to keep these lookup data +separated from the class so changes to these data are only reflected in one and +will not modify the class itself. + +The ``tests/`` directory contains unit tests for the ``AstroDataGmos`` class. +Determining the nature and scale of tests is left to the developer. + +.. + The first step when creating new |AstroData| derivative hierarchy would be to + create a new class that knows how to deal with some kind of specific data in a + broad sense. + + |AstroData| implements both ``.info()`` and ``.load()`` in ways that are + specific to FITS files. It also introduces a number of FITS-specific methods + and properties, e.g.: + + * The properties ``phu`` and ``hdr``, which return the primary header and + a list of headers for the science HDUs, respectively. + + * A ``write`` method, which will write the data back to a FITS file. + + * A ``_matches_data`` **static** method, which is very important, involved in + guiding for the automatic class choice algorithm during data loading. We'll + talk more about this when dealing with :ref:`registering our classes + `. + + It also defines the first few descriptors, which are common to all Gemini data: + ``instrument``, ``object``, and ``telescope``, which are good examples of simple + descriptors that just map a PHU keyword without applying any conversion. + + A typical AstroData programmer will extend this class (|AstroData|). Any of + the classes under the ``gemini_instruments`` package can be used as examples, + but we'll describe the important bits here. + + + Create a package for it + ----------------------- + + This is not strictly necessary, but simplifies many things, as we'll see when + talking about *registration*. The package layout is up to the designer, so you + can decide how to do it. For DRAGONS we've settled on the following + recommendation for our internal process (just to keep things familiar):: + + gemini_instruments + __init__.py + instrument_name + __init__.py + adclass.py + lookup.py + + Where ``instrument_name`` would be the package name (for Gemini we group all + our derivative packages under ``gemini_instruments``, and we would import + ``gemini_instruments.gmos``, for example). ``__init__.py`` and ``adclass.py`` + would be the only required modules under our recommended layout, with + ``lookup.py`` being there just to hold hard-coded values in a module separate + from the main logic. + + ``adclass.py`` would contain the declaration of the derivative class, and + ``__init__.py`` will contain any code needed to register our class with the + |AstroData| system upon import. + + + Create your derivative class + ---------------------------- + + This is an excerpt of a typical derivative module:: + + from astrodata import astro_data_tag, astro_data_descriptor, TagSet + from astrodata import AstroData + + from . import lookup + + class AstroDataInstrument(AstroData): + __keyword_dict = dict( + array_name = 'AMPNAME', + array_section = 'CCDSECT' + ) + + @staticmethod + def _matches_data(source): + return source[0].header.get('INSTRUME', '').upper() == 'MYINSTRUMENT' + + @astro_data_tag + def _tag_instrument(self): + return TagSet(['MYINSTRUMENT']) + + @astro_data_tag + def _tag_image(self): + if self.phu.get('GRATING') == 'MIRROR': + return TagSet(['IMAGE']) + + @astro_data_tag + def _tag_dark(self): + if self.phu.get('OBSTYPE') == 'DARK': + return TagSet(['DARK'], blocks=['IMAGE', 'SPECT']) + + @astro_data_descriptor + def array_name(self): + return self.phu.get(self._keyword_for('array_name')) + + @astro_data_descriptor + def amp_read_area(self): + ampname = self.array_name() + detector_section = self.detector_section() + return "'{}':{}".format(ampname, detector_section) + + .. note:: + An actual Gemini Facility Instrument class will derive from + ``gemini_instruments.AstroDataGemini``, but this is irrelevant + for the example. + + The class typically relies on functionality declared elsewhere, in some + ancestor, e.g., the tag set computation and the ``_keyword_for`` method are + defined at |AstroData|. + +Some highlights: + +* ``__keyword_dict``\ [#keywdict]_ defines one-to-one mappings, assigning a more + readable moniker for an HDU header keyword. The idea here is to prevent + hard-coding the names of the keywords, in the actual code. While these are + typically quite stable and not prone to change, it's better to be safe than + sorry, and this can come in useful during instrument development, which is + the more likely source of instability. The actual value can be extracted by + calling ``self._keyword_for('moniker')``. + +* ``_matches_data`` is a static method. It does not have any knowledge about + the class itself, and it does not work on an *instance* of the class: it's + a member of the class just to make it easier for the AstroData registry to + find it. This method is passed some object containing cues of the internal + structure and contents of the data. This could be, for example, an instance + of ``HDUList``. Using these data, ``_matches_data`` must return a boolean, + with ``True`` meaning "I know how to handle this data". + + Note that ``True`` **does not mean "I have full knowledge of the data"**. It + is acceptable for more than one class to claim compatibility. For a GMOS FITS + file, the classes that will return ``True`` are: |AstroData| (because it is + a FITS file that comply with certain minimum requirements), + `~gemini_instruments.gemini.AstroDataGemini` (the data contains Gemini + Facility common metadata), and `~gemini_instruments.gmos.AstroDataGmos` (the + actual handler!). + + But this does not mean that multiple classes can be valid "final" candidates. + If AstroData's automatic class discovery finds more than one class claiming + matching with the data, it will start discarding them on the basis of + inheritance: any class that appears in the inheritance tree of another one is + dropped, because the more specialized one is preferred. If at some point the + algorithm cannot find more classes to drop, and there is more than one left + in the list, an exception will occur, as AstroData will have no way to choose + one over the other. + +* A number of "tag methods" have been declared. Their naming is a convention, + at the end of the day (the "``_tag_``" prefix, and the related "``_status_``" + one, are *just hints* for the programmer): each team should establish + a convention that works for them. What is important here is to **decorate** + them using `~astrodata.astro_data_tag`, which earmarks the method so that it + can be discovered later, and ensures that it returns an appropriate value. + + A tag method will return either a `~astrodata.TagSet` instance (which can be + empty), or ``None``, which is the same as returning an empty + `~astrodata.TagSet`\ [#tagset1]_. + + **All** these methods will be executed when looking up for tags, and it's up + to the tag set construction algorithm (see :ref:`ad_tags`) to figure out the final + result. In theory, one **could** provide *just one* big method, but this is + feasible only when the logic behind deciding the tag set is simple. The + moment that there are a few competing alternatives, with some conditions + precluding other branches, one may end up with a rather complicated dozens of + lines of logic. Let the algorithm do the heavy work for you: split the tags + as needed to keep things simple, with an easy to understand logic. + + Also, keeping the individual (or related) tags in separate methods lets you + exploit the inheritance, keeping common ones at a higher level, and + redefining them as needed later on, at derived classes. + + Please, refer to `~gemini_instruments.gemini.AstroDataGemini`, + `~gemini_instruments.gmos.AstroDataGmos`, and + `~gemini_instruments.gnirs.AstroDataGnirs` for examples using most of the + features. + +* The `astrodata.AstroData.read` method calls the `astrodata.fits.read_fits` + function, which uses metadata in the FITS headers to determine how the data + should be stored in the |AstroData| object. In particular, the ``EXTNAME`` + and ``EXTVER`` keywords are used to assign individual FITS HDUs, using the + same names (``SCI``, ``DQ``, and ``VAR``) as Gemini-IRAF for the ``data``, + ``mask``, and ``variance`` planes. A ``SCI`` HDU *must* exist if there is + another HDU with the same ``EXTVER``, or else an error will occur. + + If the raw data do not conform to this format, the `astrodata.AstroData.read` + method can be overridden by your class, by having it call the + `astrodata.fits.read_fits` function with an additional parameter, + ``extname_parser``, that provides a function to modify the header. This + function will be called on each HDU before further processing. As an example, + the SOAR Adaptive Module Imager (SAMI) instrument writes raw data as + a 4-extension MEF file, with the extensions having ``EXTNAME`` values + ``im1``, ``im2``, etc. These need to be modified to ``SCI``, and an + appropriate ``EXTVER`` keyword added` [#extver]_\. This can be done by + writing a suitable ``read`` method for the ``AstroDataSami`` class:: + + @classmethod + def read(cls, source, extname_parser=None): + def sami_parser(hdu): + m = re.match('im(\d)', hdu.header.get('EXTNAME', '')) + if m: + hdu.header['EXTNAME'] = ('SCI', 'Added by AstroData') + hdu.header['EXTVER'] = (int(m.group(1)), 'Added by AstroData') + + return super().read(source, extname_parser=extname_parser) + + +* *Descriptors* will make the bulk of the class: again, the name is arbitrary, + and it should be descriptive. What *may* be important here is to use + `~astrodata.astro_data_descriptor` to decorate them. This is *not required*, + because unlike tag methods, descriptors are meant to be called explicitly by + the programmer, but they can still be marked (using this decorator) to be + listed when calling the ``descriptors`` property. The decorator does not + alter the descriptor input or output in any way, so it is always safe to use + it, and you probably should, unless there's a good reason against it (e.g., + if a descriptor is deprecated and you don't want it to show up in lookups). + + More detailed information can be found in :ref:`ad_descriptors`. + + +.. _class_registration: + +Register your class +------------------- + +Finally, you need to include your class in the **AstroData Registry**. This is +an internal structure with a list of all the |AstroData|\-derived classes that +we want to make available for our programs. Including the classes in this +registry is an important step, because a file should be opened using +`astrodata.from_file` or `astrodata.create_from_scratch`, which uses the +registry to identify the appropriate class (via the ``_matches_data`` methods), +instead of having the user specify it explicitly. + +A typical ``__init__.py`` file on an instrument package (example above) will +look like this:: + + __all__ = ['AstroDataMyInstrument'] + + from astrodata import factory + from .adclass import AstroDataMyInstrument + + factory.add_class(AstroDataMyInstrument) + +The call to ``factory.add_class`` is the one registering the class. This step +**needs** to be done **before** the class can be used effectively in the +AstroData system. Placing the registration step in the ``__init__.py`` file is +convenient, because importing the package will be enough! + +Thus, a script making use of DRAGONS' AstroData to manipulate GMOS data +could start like this:: + + import astrodata + from gemini_instruments import gmos + + ... + + ad = astrodata.open(some_file) + +The first import line is not needed, technically, because the ``gmos`` package +will import it too, anyway, but we'll probably need the ``astrodata`` package +in the namespace anyway, and it's always better to be explicit. Our +typical DRAGONS scripts and modules start like this, instead:: + + import astrodata + import gemini_instruments + +``gemini_instruments`` imports all the packages under it, making knowledge +about all Gemini instruments available for the script, which is perfect for a +multi-instrument pipeline, for example. Loading all the instrument classes is +not typically a burden on memory, though, so it's easier for everyone to take +the more general approach. It also makes things easier on the end user, because +they won't need to know internal details of our packages (like their naming +scheme). We suggest this "*cascade import*" scheme for all new source trees, +letting the user decide which level of detail they need. + +As an additional step, the ``__init__.py`` file in a package may do extra +initialization. For example, for the Gemini modules, one piece of functionality +that is shared across instruments is a descriptor that translates a filter's +name (say "u" or "FeII") to its central wavelength (e.g., +0.35µm, 1.644µm). As it is a rather common function for us, it is implemented +by `~gemini_instruments.gemini.AstroDataGemini`. This class **does not know** +about its daughter classes, though, meaning that it **cannot know** about the +filters offered by their instruments. Instead, we offer a function that can +be used to update the filter → wavelength mapping in +`gemini_instruments.gemini.lookup` so that it is accessible by the +`~gemini_instruments.gemini.AstroDataGemini`\-level descriptor. So our +``gmos/__init__.py`` looks like this:: + + __all__ = ['AstroDataGmos'] + + from astrodata import factory + from ..gemini import addInstrumentFilterWavelengths + from .adclass import AstroDataGmos + from .lookup import filter_wavelengths + + factory.addClass(AstroDataGmos) + # Use the generic GMOS name for both GMOS-N and GMOS-S + addInstrumentFilterWavelengths('GMOS', filter_wavelengths) + +where `~gemini_instruments.gemini.addInstrumentFilterWavelengths` is provided +by the ``gemini`` package to perform the update in a controlled way. + +We encourage package maintainers and creators to follow such explicit +initialization methods, driven by the modules that add functionality +themselves, as opposed to active discovery methods on the core code. This +favors decoupling between modules, which is generally a good idea. + +.. rubric:: Footnotes + +.. [#keywdict] The keyword dictionary is a "private" property of the + class (due to the double-underscore prefix). Each class can define its own + set, which will not be replaced by derivative classes. ``_keyword_for`` is + aware of this and will look up each class up the inheritance chain, in turn, + when looking up for keywords. + +.. [#tagset1] The example functions will return only + a `~astrodata.TagSet`, if appropriate. This is OK, remember that *every + function* in Python returns a value, which will be ``None``, implicitly, if + you don't specify otherwise. + +.. [#extver] An ``EXTVER`` keyword is not explicitly required; the + `astrodata.fits.read_fits` method will assign the lowest available integer + to a ``SCI`` header with no ``EXTVER`` keyword (or if its value is -1). But + we wish to be able to identify the original ``im1`` header by assigning it + an ``EXTVER`` of 1, etc. diff --git a/_sources/manuals/progmanual/containers.rst.txt b/_sources/manuals/progmanual/containers.rst.txt new file mode 100644 index 00000000..3b371efd --- /dev/null +++ b/_sources/manuals/progmanual/containers.rst.txt @@ -0,0 +1,161 @@ +.. containers.rst + +.. _containers: + +*************** +Data Containers +*************** + +The AstroData package is built around the concept of data containers. These are +objects that contain the data for a single observation, and determine the +structure of these data in memory. We have extended the Astropy |NDData| class +to provide the core functionality of these containers, and added a number of +mixins to provide additional functionality. + +Specifically, we extend |NDData| with the following: +* `astrodata.NDAstroData` - the main data container class +* `astrodata.NDAstroDataMixin` - a mixin class that adds additional functionality +to |NDData|, such as the ability to access image planes and tables stored in +the ``meta`` dict as attributes of the object +* `astrodata.NDArithmeticMixin` - a mixin class that adds arithmetic functionality +* `astrodata.NDSlicingMixin` - a mixin class that adds slicing functionality + +.. + A third, and very important part of the AstroData core package is the data + container. We have chosen to extend Astropy's |NDData| with our own + requirements, particularly lazy-loading of data using by opening the FITS files + in read-only, memory-mapping mode, and exploiting the windowing capability of + `astropy.io.fits` (using ``section``) to reduce our memory requirements, which + becomes important when reducing data (e.g., stacking). + +.. + We'll describe here how we depart from |NDData|, and how do we integrate the + data containers with the rest of the package. Please refer to |NDData| for the + full interface. + +.. _ad_nddata: + +|NDAstroData| class +------------------- + +Our main data container is |NDAstroData|. Fundamentally, it is +a derivative of :class:`astropy.nddata.NDData`, plus a number of mixins to add +functionality:: + + class NDAstroData(AstroDataMixin, NDArithmeticMixin, NDSlicingMixin, NDData): + ... + +With these mixins, |NDAstroData| is extended to allow for ease and efficiency +of use, as if a common array, with extra features such as uncertainty +propogation and efficient slicing with typically array syntax. + +Upon initialization (see :meth:`astrodata.AstroData.__init__`), the +``AstroData`` class will attempt to open the file in memory-mapping mode, which +is the default mode for opening FITS files in Astropy. This means that the data +is not loaded into memory until it is accessed, and is discarded from memory +when it is no longer needed. This is particularly important for large data +sets common in astronomy. + +Much of |NDAstrodata| acts to mimic the behavior of |NDData| and +:mod:`astropy.io.fits` objects, but is designed to be extensible to other +formats and means of storing, accessing, and manipulating data. + +.. + Our first customization is ``NDAstroData.__init__``. It relies mostly on the + upstream initialization, but customizes it because our class is initialized + with lazy-loaded data wrapped around a custom class + (`astrodata.fits.FitsLazyLoadable`) that mimics a `astropy.io.fits` HDU + instance just enough to play along with |NDData|'s initialization code. + + NOTE: This needs to be better described, the way it works is not like the + way it was originally described, and the caveats need to be made apparent. + +.. _ad_slices: + +Slicing +------- + +One can already slice |NDAstroData| objects as with |NDData|, as normal Python arrays + +.. testsetup:: + + import astrodata + +.. doctest:: + >>> ad = astrodata.from_file(some_fits_file) + >>> ad.shape + [(2048, 2048)] + + # Access pixels 100-200 in both dimensions on the first image plane. + >>> ad.data[0][100:200, 100:200].shape + (100, 100) + +It's also useful to access specific "windows" in the data, which is implemented +in |NDAstroData| such that only the data necessary to access a window is loaded +into memory. + +The :meth:`astrodata.AstroData.window` property returns an instance of +:class:`~astrodata.nddata.NDWindowing`, which only references the |AstroData| +object being windowed (i.e., it contains no direct references to the data). +|NDWindowingAstroData|, which has references +pointing to the memory mapped data requested by the window. + +.. + We've added another new property, ``window``, that can be used to + explicitly exploit the `astropy.io.fits`'s ``section`` property, to (again) + avoid loading unneeded data to memory. This property returns an instance of + ``NDWindowing`` which, when sliced, in turn produces an instance of + ``NDWindowingAstroData``, itself a proxy of ``NDAstroData``. This scheme may + seem complex, but it was deemed the easiest and cleanest way to achieve the + result that we were looking for. + +The base ``NDAstroData`` class provides the memory-mapping functionality built +upon by |NDWindowingAstroData|, with other important behaviors added by the +other mixins. + +.. + The base ``NDAstroData`` class provides the memory-mapping functionality, + with other important behaviors added by the ``AstroDataMixin``, which can + be used with other |NDData|-like classes (such as ``Spectrum1D``) to add + additional convenience. + +One addition is the ``variance`` property, which allows direct access and +setting of the data's uncertainty, without the user needing to explicitly wrap +it as an ``NDUncertainty`` object. Internally, the variance is stored as an +``ADVarianceUncertainty`` object, which is subclassed from Astropy's standard +``VarianceUncertainty`` class with the addition of a check for negative values +whenever the array is accessed. + +``NDAstroDataMixin`` also changes the default method of combining the ``mask`` +attributes during arithmetic operations from ``logical_or`` to ``bitwise_or``, +since the individual bits in the mask have separate meanings. + +.. todo:: + This section may shine light on the WCS issues I've encountered + writing slicing tests, so I'm leaving it intact for now. + +The way slicing affects the ``wcs`` is also changed since DRAGONS regularly +uses the callable nature of ``gWCS`` objects and this is broken by the standard +slicing method. + + +.. Is this tested? I don't remember seeing any tests that check if these + attributes are automatically sliced in teh same way/properly. + +.. todo:: + Check source for where this feature is implemented and write a test + for it. + +Finally, the additional image planes and tables stored in the ``meta`` dict +are exposed as attributes of the ``NDAstroData`` object, and any image planes +that have the same shape as the parent ``NDAstroData`` object will be handled +by ``NDWindowingAstroData``. Sections will be ignored when accessing image +planes with a different shape, as well as tables. + +.. note:: + + We expect to make changes to ``NDAstroData`` in future releases. In particular, + we plan to make use of the ``unit`` attribute provided by the + |NDData| class and increase the use of memory-mapping by default. These + changes mostly represent increased functionality and we anticipate a high + (and possibly full) degree of backward compatibility. diff --git a/_sources/manuals/progmanual/descriptors.rst.txt b/_sources/manuals/progmanual/descriptors.rst.txt new file mode 100644 index 00000000..1fd8776f --- /dev/null +++ b/_sources/manuals/progmanual/descriptors.rst.txt @@ -0,0 +1,85 @@ +.. descriptors.rst + +.. _ad_descriptors: + +*********** +Descriptors +*********** + +Descriptors are regular methods that translate metadata from the raw +data (e.g., cards from FITS headers) to values useful for the user, +potentially doing some processing in between. They exist to: + +* Abstract the actual organization of the metadata, with arbitrarily complex + processing to generate a useful value. These abstractions can be modified + to be instrument-specific. + +* Provide a common interface to a set of instruments. This simplifies user + training (no need to learn a different API for each instrument), and + facilitates the reuse of code for pipelines and data processing. + +* They can be used to directly translate character-limited FITS header keywords + into more descriptive names of arbitrary length. + +Descriptors **should** be decorated using `~astrodata.astro_data_descriptor`. +The only function of this decorator is to ensure that the descriptor is marked +as such: it does not alter its input or output in any way. This lets the user +explore the API of an |AstroData| object via the +`~astrodata.AstroData.descriptors` property. Here's an example of how we could +use a descriptor to build a simple class on top of the |AstroData| base class: + +.. code::python + from astrodata import AstroData, astro_data_descriptor + + class DescAstroData(AstroData): + @astro_data_descriptor + def airmass(self): + '''Retrieves the airmass stored in a PHU entry''' + return self.phu['AIRMASS'] + + @astro_data_descriptor + def total_exposure_time(self): + '''Retrieves the total exposure time from the headers.''' + return sum([ext.hdr['EXPTIME'] for ext in self]) + + ad = DescAstroData() + print(ad.descriptors) + # ('airmass', 'total_exposure_time') + +.. note:: + The above example is oversimplified, and would only work with a fits file + containing these keywords. In practice, an |AstroData| extension like this + would be specific to an instrument/file format that would be resolved using + tags. + +Descriptors **can** be decorated with :func:`~astrodata.core.returns_list` to +eliminate the need to code some logic. Some descriptors return single values, +while some return lists, one per extension. Typically, the former +descriptors refer calues associated with an entire observation (and, for MEF +files, are usually extracted from metadata in the PHU, such as ``airmass``), +while the latter descriptors where different extensions might return +different values (and typically come from metadata in the individual HDUs, such +as ``gain``). A list is returned even if there is only one extension in the +|AstroData| object, as this allows code to be written generically to iterate +over the |AstroData| object and the descriptor return, without needing to know +how many extensions there are. + +The `~astrodata.core.returns_list` decorator ensures that the descriptor +returns an appropriate object (value or list), using the following rules +to avoid unexpected behavior/confusing errors: + +* If the |AstroData| object is not a single slice: + + * If the undecorated descriptor returns a list, an exception is raised + if the list is not the same length as the number of extensions. + * If the undecorated descriptor returns a single value, the decorator + will turn it into a list of the correct length by copying this value. + +* If the |AstroData| object is a single slice and the undecorated + descriptor returns a list, only the first element is returned. + +An example of the use of this decorator is the NIRI +`~gemini_instruments.niri.AstroDataNiri.gain` descriptor, which reads the +value from a lookup table and simply returns it. A single value is only +appropriate if the |AstroData| object is singly-sliced and the decorator ensures +that a list is returned otherwise. diff --git a/_sources/manuals/progmanual/design.rst.txt b/_sources/manuals/progmanual/design.rst.txt new file mode 100644 index 00000000..3b71d915 --- /dev/null +++ b/_sources/manuals/progmanual/design.rst.txt @@ -0,0 +1,148 @@ +.. design.rst + +.. _design: + +************** +General Design +************** + +Astronomical instruments come in a variety of forms, each with unique features +and requirements that are not readily transferable between different +instruments, observatories, and systems. However, there are also many +similarities between them, and the data reduction process is often similar +regardless of the instrument. The AstroData package is designed to provide a +common interface to astronomical data, regardless of the instrument that +produced it. + +While at first glance this may seem counterintuitive---after all, how can a +spectrograph and a camera share the same interface?---breaking down their +common features and only developing their unique aspects once has proven to +significantly reduce the amount of code required to support each instrument. As +an example of such a system, see the Gemini Data Reduction System (|DRAGONS|), +which uses AstroData to support data from all of Gemini's instruments. + +As a developer, AstroData consists of several concepts used to automatically +resolve data based on the instrument and observation type: + +1. |AstroData| - This is the primary class from which all other data classes + are derived. It is a container for the data and metadata associated with a + single astronomical observation. It is also an iterable object, with each + iteration returning a "slice" of the data, which is itself an AstroData + object. This is discussed further in :ref:`ad_slices`. + +2. |Tags| - These are high-level metadata that describe the observation and + link it to the recipes required to process it. They are discussed further in + :ref:`ad_tags`. + +3. |Descriptors| - These are a way to access the metadata in a uniform manner, + and can be used to access quantities reuiring introspection of the data + itself. + +Thee three concepts are discussed in detail in the following sections. +Together, they provide a way to access astronomical data in a uniform manner, +regardless of the instrument that produced it, while still being "aware" of any +percularities of a given instrument completely controlled by the developer. + +.. note:: + Understanding the differences between AstroData objects, AstroData tags, and + AstroData descriptors is critical to understanding and implementing to full + range of features AstroData provides. One way to think of them is: + + 1. AstroData manages data and associated metadata, which may include Tags + and Descriptors. + 2. Tags are high-level metadata descripting the observation, observatory, + and instrument, that AstroData uses to automatically resolve how to read in + and process a data file. + 3. Descriptors are a way to access data not found in the metadata, or + requiring some manipulation of the data itself to determine a given value. + Like a python property, but without the attribute syntax (to reflect that it + may be costly to call). + +.. + As astronomical instruments have become more complex, there has been an + increasing need for reduction packages and pipelines to deal with the specific + needs of each instrument. Despite this complexity, many of the reduction steps + can be very similar and the overall effort could be reduced significantly by + sharing code. In practice, however, there are often issues regarding the manner + in which the data are stored internally. The purpose of AstroData is to provide + a uniform interface to the data and metadata, in a manner independent both of + the specific instrument and the way the data are stored on disk, thereby + facilitating this code-sharing. It is *not* a new astronomical data format; it + is a way to unify how those data are accessed. + + One of the main features of AstroData is the use of *descriptors*, which + provide a level of abstraction between the metadata and the code accessing it. + Somebody using the AstroData interface who wishes to know the exposure time of + a particular astronomical observation represented by the AstroData object + ``ad`` can simply write ``ad.exposure_time()`` without needing to concern + themselves about how that value is stored internally, for example, the name of + the FITS header keyword. These are discussed further in :ref:`ad_descriptors`. + + AstroData also provides a clearer representation of the relationships between + different parts of the data produced from a single astronomical observation. + Modern astronomical instruments often contain multiple detectors that are read + out separately and the multi-extension FITS (MEF) format used by many + institutions, including Gemini Observatory, handles the raw data well. In this + format, each detector's data and metadata is assigned to its own extension, + while there is also a separate extension (the Primary Header Unit, or PHU) + containing additional metadata that applies to the entire observation. However, + as the data are processed, more data and/or metadata may be added whose + relationship is obscured by the limitations of the MEF format. One example is + the creation and propagation of information describing the quality and + uncertainty of the scientific data: while this was a feature of + Gemini IRAF\[#iraf]_, the coding required to implement it was cumbersome. + AstroData uses the `astropy.nddata.NDData` class, as discussed in + :ref:`containers`. This makes the relationship between these data much clearer, + and AstroData creates a syntax that makes readily apparent the roles of other + data and metadata that may be created during the reduction process. + +AstroData was originally designed for the Gemini Observatories, which primarily +use the FITS and FITS MEF formats for their data. While AstroData comes +out-of-the-box with FITS-specific readers and syntax, extending it to include +other file formats is straightforward. See :ref:`astrodata` for more details. + +.. note:: + While there is currently only FITS support, we plan to include native asdf + support as well in the future. + +When using a FITS or FITS-like file, an AstroData object consists of one or +more self-contained "extensions" (data and metadata) plus additional data and +metadata that is relevant to all the extensions. In many data reduction +processes, the same operation will be performed on each extension (e.g., +subtracting an overscan region from a CCD frame) and an axiom of AstroData is +that iterating over the extensions produces AstroData "slices" which retain +knowledge of the top-level data and metadata. Since a slice has one (or more) +extensions plus this top-level (meta)data, it too is an AstroData object +and, specifically, an instance of the same subclass as its parent. + +.. + TODO: Need to remove the Recipe system reference as docs here and port + anything important here. It can be used for examples, but shouldn't be the + primary source for astrodata-relevant docs. + +.. + A final feature of AstroData is the implementation of very high-level metadata. + These data, called ``tags``, facilitate a key part of the Gemini data reduction + system, DRAGONS, by linking the astronomical data to the recipes required to + process them. They are explained in detail in :ref:`ad_tags` and the Recipe + System Programmers Manual\ [#rsprogman]_. + +.. + .. note:: + + AstroData and DRAGONS have been developed for the reduction of data from + Gemini Observatory, which produces data in the FITS format that is still the + most widely-used format for astronomical data. In light of this, and the + limited resources in the Science User Support Department, we have only + *developed* support for FITS, even though the AstroData format is designed + to be independent of the file format. In some cases, this has led to + uncertainty and internal disagreement over where precisely to engage in + abstraction and, should AstroData support a different file format, we + may find alternative solutions that result in small, but possibly + significant, changes to the API. + + +.. + .. [#iraf] ``_ + + .. [#rsprogman] |RSProgManual| diff --git a/_sources/manuals/progmanual/index.rst.txt b/_sources/manuals/progmanual/index.rst.txt new file mode 100644 index 00000000..751abd29 --- /dev/null +++ b/_sources/manuals/progmanual/index.rst.txt @@ -0,0 +1,22 @@ +.. Astrodata Programmer's Manual documentation master file, created by + sphinx-quickstart on Fri Jun 1 11:08:23 2018. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +=================== +Programmer's Manual +=================== + +.. admonition:: Document ID + + PIPE-USER-104_AstrodataProgManual + +.. toctree:: + :maxdepth: 2 + + intro + design + adclass + containers + tags + descriptors diff --git a/_sources/manuals/progmanual/intro.rst.txt b/_sources/manuals/progmanual/intro.rst.txt new file mode 100644 index 00000000..e48ded77 --- /dev/null +++ b/_sources/manuals/progmanual/intro.rst.txt @@ -0,0 +1,205 @@ +.. intro.rst + +.. _intro_progmanual: + +************ +Introduction +************ + +AstroData is a Python package that provides a common interface to astronomical +data. Originally part of the |DRAGONS| package, the data reduction package +developed at the Gemini Observatory, it has been split into its own package to +allow its use in other projects and to be developed by a wider, public core to +suit the needs of data reduction across the field of astronomy. + +AstroData's common interface, the |AstroData| class, is used to abstract the +details of any given data into a set of |Tags|, which can be used to resolve +the properties and reduction requirements of a given data file (most commonly, +a multi-extension FITS file). The |AstroData| class is also used to provide +methods to access the data in a consistent manner, regardless of the +underlying data format. + +The |AstroData| class is not intended to be used directly by the user. Instead, +the user should use the :func:`~astrodata.from_file` function, which will return an +|AstroData| object. The :func:`~astrodata.from_file` function will determine the type of +data file being opened and return the appropriate subclass of |AstroData|. + +For the programmer using |AstroData| to develop a data reduction pipeline, the +|AstroData| class should be subclassed to provide the functionality required +and to register the new class with the :func:`~astrodata.from_file` function. + +Several examples may be found throughout the documentation (see |Examples|). A +simple example is shown below as a complete, executable introduction. + +.. doctest:: + >>> # Defining an AstroData subclass + >>> from astrodata import ( + ... AstroData, + ... astro_data_tag, + ... astro_data_descriptor, + ... TagSet + ... ) + >>> import astrodata + + >>> class MyAstroData(AstroData): + ... @staticmethod + ... def _matches_data(source): + ... # This method is used by astrodata.from_file to determine if this + ... # class should be used to open the data. It should return True + ... # if the data is of the correct type, False otherwise. + ... + ... # E.g., if file limited by FITS standard or not for instrument + ... # keyword. + ... instrument_tags = {'INSTRUME', 'INSTRUMENT'} + ... + ... for tag in instrument_tags: + ... if tag in source[0].header: + ... return source[0].header.get(tag).upper() == 'MY_INSTRUMENT' + ... + ... # Could return None by default since it's Falsey, but this is more + ... # explicit and follows typing expectations. + ... return False + ... + ... @astro_data_tag + ... def my_tag(self): + ... # This method is used to define a new tag. It should return + ... # a string that will be used as the tag name. The method name + ... # is used as the tag name by default, but this can be overridden + ... # by passing a name to the decorator, e.g.: + ... # @astro_data_tag(name='my_tag_name') + ... # The method should return None if the tag is not applicable + ... # to the data. + ... + ... # This checks that the Primary HDU of the data has a specific + ... # keyword, 'MY_TAG'. + ... if self.phu.get('MY_TAG') is not None: + ... return TagSet(['MY_TAG']) + ... + ... # Not strictly necessary, but here for completeness. + ... return TagSet() + ... + ... @astro_data_descriptor + ... def my_descriptor(self): + ... # This method is used to define a new descriptor. It should + ... # return a string that will be used as the descriptor name. + ... # The method name is used as the descriptor name by default, + ... # but this can be overridden by passing a name to the decorator, + ... # e.g.: + ... # @astro_data_descriptor(name='my_descriptor_name') + ... # The method should return None if the descriptor is not + ... # applicable to the data. + ... + ... # Returns None if 'MY_DESC' is not in the Primary HDU + ... return self.phu.get('MY_DESC') + + >>> # Registering the new class with astrodata.factory + >>> astrodata.factory.add_class(MyAstroData) + + >>> # Now, if we give it a file that has the MY_TAG keyword in the Primary HDU, + >>> # we can open it with astrodata.from_file and it will return an instance of + >>> # MyAstroData. + >>> # Defining an example FITS file + >>> from astropy.io import fits + >>> import gwcs + >>> import tempfile + + >>> # Create a new FITS HDU + >>> phdu = fits.PrimaryHDU(data=[[1, 2], [3, 4]]) + + >>> # Add the necessary tags to the FITS header + >>> phdu.header['INSTRUME'] = 'MY_INSTRUMENT' + >>> phdu.header['MY_TAG'] = 'example_tag' + >>> phdu.header['MY_DESC'] = 'example_descriptor' + + >>> # Add a single dummy extension + >>> image = fits.ImageHDU(data=[[1, 2], [3, 4]]) + >>> hdu = fits.HDUList([phdu, image]) + + >>> # Save the FITS file + >>> with tempfile.NamedTemporaryFile(suffix='.fits') as f: + ... hdu.writeto(f, overwrite=True) + ... + ... # Open the file with astrodata.from_file + ... ad = astrodata.from_file(f.name) + ... + ... # Check that the tag and descriptor are present + ... assert 'MY_TAG' in ad.tags, f"Tag 'my_tag' not found in {ad.tags}" + ... + ... # Check that the tag and descriptor values are correct + ... assert ad.my_descriptor() == 'example_descriptor', ( + ... f"Descriptor 'my_descriptor' has incorrect value: " + ... f"{ad.my_descriptor()}" + ... ) + ... + ... # Finally, make sure that the object is an instance of MyAstroData. + ... # We can generally infer this from the above, but it's good to be + ... # thorough in our tests (in case any strange API change nullifies + ... # the above checks). + ... assert isinstance(ad, MyAstroData), ( + ... f"Incorrect class {type(ad)}, expected MyAstroData" + ... ) + + >>> # Now that our data is loaded in, we can use the AstroData API to access + >>> # the data. + >>> # For example, we can get the data as a numpy array + >>> data = ad[0].data + + >>> # Or we can get the WCS + >>> wcs = ad[0].wcs + + >>> # Or we can get the value of a keyword + >>> my_keyword = ad[0].hdr.get('MY_KEYWORD') + + >>> # Or we can get the resolved tags + >>> my_tags = ad.tags + + >>> # Or we can get the value of a descriptor + >>> my_descriptor = ad.my_descriptor() + +.. + TODO: Need to move this to a "history" section or something. It's not the + first thing that should be read by a programmer. + + ************************* + Precedents and Motivation + ************************* + + + The Gemini Observatory has produced a number of tools for data processing. + Historically this has translated into a number of IRAF\ [#IRAF]_ packages but + the lack of long-term support for IRAF, coupled with the well-known + difficulty in creating robust reduction pipelines within the IRAF + environment, led to a decision + to adopt Python as a programming tool and a new + package was born: Gemini Python. Gemini Python provided tools to load and + manipulate Gemini-produced multi-extension FITS\ [#FITS]_ (MEF) files, + along with a pipeline that + allowed the construction of reduction recipes. At the center of this package + was the AstroData subpackage, which supported the abstraction of the FITS + files. + + Gemini Python reached version 1.0.1, released during November 2014. In 2015 + the Science User Support Department (SUSD) was created at Gemini, which took on the + responsibility of maintaining the software reduction tools, and started + planning future steps. With improved oversight and time and thought, it became + evident that the design of Gemini Python and, specially, of AstroData, made + further development a daunting task. + + In 2016 a decision was reached to overhaul Gemini Python. While the + principles behind AstroData were sound, the coding involved unnecessary + layers of abstraction and eschewed features of the Python language in favor + of its own implementation. Thus, + |DRAGONS| was born, with a new, simplified (and backward *incompatible*) + AstroData v2.0 (which we will refer to simply as AstroData) + + This manual documents both the high level design and some implementation + details of AstroData, together with an explanation of how to extend the + package to work for new environments. + + + + .. rubric:: Footnotes + + .. [#IRAF] http://iraf.net + .. [#FITS] The `Flexible Image Transport System `_ + .. [#DRAGONS] The `Data Reduction for Astronomy from Gemini Observatory North and South `_ package diff --git a/_sources/manuals/progmanual/tags.rst.txt b/_sources/manuals/progmanual/tags.rst.txt new file mode 100644 index 00000000..91666509 --- /dev/null +++ b/_sources/manuals/progmanual/tags.rst.txt @@ -0,0 +1,164 @@ +.. tags.rst + +.. _ad_tags: + +**** +Tags +**** + +We described :ref:`in the previous section ` how to generate +tags for an AstroData derivative. In this section we'll describe the algorithm +that generates the complete tag set out of the individual ``TagSet`` instances. +The algorithm collects all the tags in a list and then decides whether to apply +them or not following certain rules, but let's talk about ``TagSet`` first. + +``TagSet`` is actually a standard named tuple customized to generate default +values (``None``) for its missing members. Its signature is:: + + TagSet(add=None, remove=None, blocked_by=None, blocks=None, + if_present=None) + +The most common ``TagSet`` is an **additive** one: ``TagSet(['FOO', 'BAR'])``. +If all you need is to add tags, then you're done here. But the real power of +our tag generating system is that you can specify some conditions to apply a +certain ``TagSet``, or put restrictions on others. The different arguments to +``TagSet`` all expect a list (or some others work in the following way): + +* ``add``: if this ``TagSet`` is selected, then add all these members to the tag + set. +* ``remove``: if this ``TagSet`` is selected, then prevent all these members + from joining the tag set. +* ``blocked_by``: if any of the tags listed in here exist in the tag set, then + discard this ``TagSet`` altogether. +* ``blocks``: discard from the list of unprocessed ones any ``TagSet`` that + would add any of the tags listed here. +* ``if_present``: process this tag only if all the tags listed in here exist in + the tag set at this point. + +Note that ``blocked_by`` and ``blocks`` look like two sides of the same coin. +This is intentional: which one to use is up to the programmer, depending on +what will reduce the amount of typing and/or make the logic easier (sometimes one +wants to block a bunch of other tags from a single one; sometimes one wants a +tag to be blocked by a bunch of others). Furthermore, while ``blocks`` and +``blocked_by`` prevent the entire ``TagSet`` from being added if it contains a +tag affected by these, ``remove`` only affects the specific tag. + +Now, the algorithm works like this: + +#. Collect all the ``TagSet`` generated by methods in the instance that are + decorated using ``astro_data_tag``. +#. Then we sort them out: + + #. Those that subtract tags from the tag set go first (the ones with + non-empty ``remove`` or ``blocks``), allowing them to act early on + #. Those with non-empty ``blocked_by`` are moved to the end of the list, to + ensure that other tags can be generated before them. + #. Those with non-empty ``if_present`` are moved behind those with + ``blocked_by``. + +#. Now that we've sorted the tags, process them sequentially and for each one: + + #. If they require other tags to be present, make sure that this is the case. + If the requirements are not met, drop the tagset. If not... + #. Figure out if any other tag is blocking the tagset. This will be the + case if *any* of the tags to be added is in the "blocked" list, or if + any of the tags added by previous tag sets are in the ``blocked_by`` + list of the one being processed. Then... + #. If all the previous hurdles have been passed, apply the changes declared + by this tag (add, remove, and/or block others). + +Note that Python's sort algorithm is stable. This means, that if two elements +are indistinguishable from the point of view of the sorting algorithm, they are +guaranteed to stay in the same relative position. To better understand how this +affects our tags, and the algorithm itself, let's follow up with an example taken +from real code (the Gemini-generic and GMOS modules) + +.. todo:: need to add a more generic example here + +:: + + # Simple tagset, with only a constant, additive content + @astro_data_tag + def _tag_instrument(self): + return TagSet(['GMOS']) + + # Simple tagset, also with additive content. This one will + # check if the frame fits the requirements to be classified + # as "GMOS imaging". It returns a value conditionally: + # if this is not imaging, then it will return None, which + # means the algorithm will ignore the value + @astro_data_tag + def _tag_image(self): + if self.phu.get('GRATING') == 'MIRROR': + return TagSet(['IMAGE']) + + # This is a slightly more complex TagSet (but fairly simple, anyway), + # inherited by all Gemini instruments. + @astro_data_tag + def _type_gcal_lamp(self): + if self.phu.get('GCALLAMP') == 'IRhigh': + shut = self.phu.get('GCALSHUT') + if shut == 'OPEN': + return TagSet(['GCAL_IR_ON', 'LAMPON'], + blocked_by=['PROCESSED']) + elif shut == 'CLOSED': + return TagSet(['GCAL_IR_OFF', 'LAMPOFF'], + blocked_by=['PROCESSED']) + + # This tagset is only active when we detect that the frame is + # a bias. In that case we want to prevent the frame from being + # classified as "imaging" or "spectroscopy", which depend on the + # configuration of the instrument + @astro_data_tag + def _tag_bias(self): + if self.phu.get('OBSTYPE') == 'BIAS': + return TagSet(['BIAS', 'CAL'], blocks=['IMAGE', 'SPECT']) + +These four simple tag methods will serve to illustrate the algorithm. Let's pretend +that the requirements for all four of them are somehow met, meaning that we get four +``TagSet`` instances in our list, in some random order. After step 1 in the algorithm, +then, we may have collected the following list:: + + [ TagSet(['GMOS']), + TagSet(['GCAL_IR_OFF', 'LAMPOFF'], blocked_by=['PROCESSED']), + TagSet(['BIAS', 'CAL'], blocks=['IMAGE', 'SPECT']), + TagSet(['IMAGE']) ] + +The algorithm then proceeds to sort them. First, it will promote the ``TagSet`` +with non-empty ``blocks`` or ``remove``:: + + [ TagSet(['BIAS', 'CAL'], blocks=['IMAGE', 'SPECT']), + TagSet(['GMOS']), + TagSet(['GCAL_IR_OFF', 'LAMPOFF'], blocked_by=['PROCESSED']), + TagSet(['IMAGE']) ] + +Note that the other three ``TagSet`` stay in exactly the same order. Now the +algorithm will sort the list again, moving the ones with non-empty +``blocked_by`` to the end:: + + [ TagSet(['BIAS', 'CAL'], blocks=['IMAGE', 'SPECT']), + TagSet(['GMOS']), TagSet(['IMAGE']), + TagSet(['GCAL_IR_OFF', 'LAMPOFF'], blocked_by=['PROCESSED']) ] + +Note that at each step, all the instances (except the ones "being moved") have +kept the same position relative to each other -here's where the "stability" of +the sorting comes into play,- ensuring that each step does not affect the previous +one. Finally, there are no ``if_present`` in our example, so no more instances are +moved around. + +Now the algorithm prepares three empty sets (``tags``, ``removals``, and ``blocked``), +and starts iterating over the ``TagSet`` list. + + 1. For the first ``TagSet`` there are no blocks or removals, so we just add its + contents to the current sets: ``tags = {'BIAS', 'CAL'}``, + ``blocked = {'IMAGE', 'SPECT'}``. + 2. Then comes ``TagSet(['GMOS'])``. Again, there are no removals in place, and + ``GMOS`` is not in the list of blocked tags. Thus, we just add it to the current + tag set: ``tags = {'BIAS', 'CAL', 'GMOS'}``. + 3. When processing ``TagSet(['IMAGE'])``, the algorithm observes that this ``IMAGE`` + is in the ``blocked`` set, and stops processing this tag set. + 4. Finally, neither ``GCAL_IR_OFF`` nor ``LAMPOFF`` are in ``blocked``, and + ``PROCESSED`` is not in ``tags``, meaning that we can add this tag set to + the final one. + +Our result will look something like: ``{'BIAS', 'CAL', 'GMOS', 'GCAL_IR_OFF', 'LAMPOFF'}`` diff --git a/_sources/manuals/usermanual/data.rst.txt b/_sources/manuals/usermanual/data.rst.txt new file mode 100644 index 00000000..17748cd3 --- /dev/null +++ b/_sources/manuals/usermanual/data.rst.txt @@ -0,0 +1,956 @@ +.. data.rst + +.. _pixel-data: + +********** +Pixel Data +********** + +The most important part of an |AstroData| object is the pixel data. +Manipulating and interacting with pixel data is a common task in astronomy, and +|astrodata| provides a number of tools, as well as many familiar operations, to +make working with such data efficient and straightforward. + + +Operate on Pixel Data +===================== + +The pixel data are stored in the |AstroData| object as a list of +|NDAstroData| objects. The |NDAstroData| is a subclass of Astropy's +|NDData| class which combines in one "package" the pixel values, the +variance, and the data quality plane or mask (as well as associated meta-data). +The data can be retrieved as a standard NumPy |NDArray|. + +Accessing pixel data can be done with the ``.data`` attribute. The +``.data`` attribute is a NumPy |NDArray|. The ``.data`` attribute is +a property of the |NDAstroData| object, and it is the pixel data itself. + +.. doctest:: + + >>> ad = astrodata.from_file(some_fits_file_with_extensions) + >>> the_data = ad[1].data + >>> type(the_data) + + + >>> # Loop through the extensions. + >>> for ext in ad: + ... the_data = ext.data + ... print(the_data.sum()) + 4194304.0 + 4194304.0 + 4194304.0 + 4194304.0 + 4194304.0 + + +.. note:: + Remember that extensions can be accessed by index, with index ``0`` being + the first extension, **not** the primary header unit (for FITS files). + +In this example, we first access the pixels for the second extension. The +``.data`` attribute contains a NumPy |NDArray|. In the for-loop, for each +extension, we get the data and use the NumPy ``.sum()`` method to sum the pixel +values. Anything that can be done with a |NDArray| can be done on +|AstroData| pixel data. + + +Arithmetic on AstroData Objects +=============================== + +|AstroData| objects support basic in-place arithmetics with these methods: + +.. |add| replace:: ``.add()`` +.. |subtract| replace:: ``.subtract()`` +.. |multiply| replace:: ``.multiply()`` +.. |divide| replace:: ``.divide()`` + ++----------------+-------------+ +| addition | |add| | ++----------------+-------------+ +| subtraction | |subtract| | ++----------------+-------------+ +| multiplication | |multiply| | ++----------------+-------------+ +| division | |divide| | ++----------------+-------------+ + +In-place operations are also supported with the standard in-place assignment +operators ``+=``, ``-=``, ``*=``, and ``/=``. Normal, not in-place, +arithmetics is also possible using the standard operators, ``+``, ``-``, ``*``, +and ``/``. + +When performing these operations, any variance or masks present will be +propagated forward to the resulting |AstroData| object (or during in-place +operations). + + +Simple operations +----------------- +Here are a few examples of arithmetics on |AstroData| objects. + +.. doctest:: + + >>> ad = astrodata.from_file(some_fits_file_with_extensions) + + >>> # Addition + >>> ad.add(50.) + <...DocTestAstroData object at ...> + >>> ad = ad + 50. + >>> ad += 50. + >>> print(ad[0].data[50,50]) + 151.0 + + >>> # Subtraction + >>> ad.subtract(50.) + <...DocTestAstroData object at ...> + >>> ad = ad - 50. + >>> ad -= 50. + >>> print(ad[0].data[50,50]) + 1.0 + + >>> # Multiplication (Using a descriptor) + >>> ad.multiply(ad.exposure_time()) + <...DocTestAstroData object at ...> + >>> ad = ad * ad.exposure_time() + >>> ad *= ad.exposure_time() + >>> print(ad[0].data[50,50]) + 1.0 + + >>> # Division (Using a descriptor) + >>> ad.divide(ad.exposure_time()) + <...DocTestAstroData object at ...> + >>> ad = ad / ad.exposure_time() + >>> ad /= ad.exposure_time() + >>> print(ad[0].data[50,50]) + 1.0 + +When the syntax ``adout = adin + 1`` is used, the output variable is a *copy* +of the original. In the examples above we reassign the result back onto the +original. The two other forms, ``ad.add()`` and ``ad +=`` are in-place +operations. + +When a descriptor returns a list because the value changes for each +extension, a for-loop is needed + +.. doctest:: + + >>> for i, (ext, gain) in enumerate(zip(ad, ad.gain())): + ... ext.multiply(gain) + ... print(f"Extension {i} has been multiplied by {gain}") + <...> + Extension 0 has been multiplied by 1.5 + <...> + Extension 1 has been multiplied by 1.5 + <...> + Extension 2 has been multiplied by 1.5 + <...> + Extension 3 has been multiplied by 1.5 + <...> + Extension 4 has been multiplied by 1.5 + +If you want to do the above but on a new object, leaving the original unchanged, +use ``deepcopy`` first. + +.. doctest:: + + >>> from copy import deepcopy + >>> adcopy = deepcopy(ad) + >>> for i, (ext, gain) in enumerate(zip(adcopy, adcopy.gain())): + ... ext.multiply(gain) + ... assert ext.data is not ad[i].data + <...> + <...> + <...> + <...> + <...> + +.. warning:: + The ``deepcopy`` function is a powerful tool but it can be slow, + memory-consuming, and it can lead to unexpected results if the object being + copied contains references to other objects. It is not recommended to use + it unless you are sure you need it. *In many situations, you can avoid + using it.* + +Operator Precedence +------------------- + +The |AstroData| arithmetics methods can be stringed together but beware that +there is no operator precedence when that is done. For arithmetics that +involve more than one operation, it is probably safer to use the normal +Python operator syntax. Here is a little example to illustrate the difference. + +.. doctest:: + + >>> ad_copy = deepcopy(ad) + >>> ad_copy.add(5).multiply(10).subtract(5) + <...> + >>> # means: ad = ((ad + 5) * 10) - 5 + >>> # NOT: ad = ad + (5 * 10) - 5 + >>> print(ad_copy[0].data[50, 50]) + 60.0 + +This is because the methods modify the object in-place, one operation after +the other from left to right. This also means that the original is modified. + +This example applies the expected operator precedence + +.. doctest:: + + >>> ad_copy = deepcopy(ad) + >>> ad_copy = ad_copy + ad_copy * 3 - 40. + >>> # means: ad_copy = ad_copy + (ad_copy * 3) - 40. + >>> print(ad_copy[0].data[50, 50]) + -34.0 + + +If you need a copy, leaving the original untouched, which is sometimes useful +you can use ``deepcopy`` or just use the normal operator and assign to a new +variable. + +.. doctest:: + + >>> adnew = ad + ad * 3 - 40. + >>> print(adnew[0].data[50, 50], ad[0].data[50, 50]) + -34.0 1.5 + >>> adnew[0] is not ad[0] + True + +Variance +======== + +When doing arithmetic on an |AstroData| object, if a variance is present +it will be propagated appropriately to the output no matter which syntax +you use (the methods or the Python operators). + +Adding a Variance Plane +----------------------- +In this example, we will add the poisson noise to an |AstroData| dataset. +The data is still in ADU, therefore the poisson noise as variance is +``signal / gain``. We want to set the variance for each of the pixel +extensions. + +.. doctest:: + + >>> ad = astrodata.from_file(some_fits_file_with_extensions) + >>> for (extension, gain) in zip(ad, ad.gain()): + ... extension.variance = extension.data / gain + +Check :meth:`~astrodata.AstroData.info`, you will see a variance plane for each +of the four extensions. + +Automatic Variance Propagation +------------------------------ + +If present, any variance plane will be propagated to the resulting |AstroData| +object when doing arithmetics. + +.. note:: + + The variance propagation assumes the data are not correlated. If the data + are correlated, the variance propagation will be incorrect. In that case, + the variance should be calculated from the data themselves. + +Let's look into an example. + +.. todo:: + Update this example + +.. doctest:: + + >>> # output = x * x + >>> # var_output = var * x^2 + var * x^2 + >>> ad = astrodata.from_file(some_fits_file_with_extensions) + >>> ad *= 1.5 + >>> ad[1].data[50,50] + 1.5 + >>> ad[1].variance[50,50] + 0.471 + >>> adout = ad * ad + >>> adout[1].data[50,50] + 2.25 + >>> adout[1].variance[50,50] + 0.7065 + +.. todo:: + make an example for the below warning + +.. warning:: + Variance must be implemented, either by setting it (above) or by including + it in the data ingestion. If variance is not present, the variance + propagation will not be done. + + For examples of how to set the variance, see :needs_replacement:`EXAMPLE`. + +Data Quality Plane +================== + +The |NDData| ``mask`` stores the data quality plane. The simplest form is a +True/False array of the same size at the pixel array. In Astrodata we favor a +bit array that allows for additional information about why the pixel is being +masked. For example, Gemini bit masks use the following for bad pixels: + ++---------------+-------+---------+ +| Meaning | Value | Binary | ++===============+=======+=========+ +| Good pixel | 0 | 0000000 | ++---------------+-------+---------+ +| Bad pixel | 1 | 0000001 | ++---------------+-------+---------+ +| Non Linear | 2 | 0000010 | ++---------------+-------+---------+ +| Saturated | 4 | 0000100 | ++---------------+-------+---------+ +| Cosmic Ray | 8 | 0001000 | ++---------------+-------+---------+ +| No Data | 16 | 0010000 | ++---------------+-------+---------+ +| Overlap | 32 | 0100000 | ++---------------+-------+---------+ +| Unilluminated | 64 | 1000000 | ++---------------+-------+---------+ + +.. _DQ_def_link: https://github.com/GeminiDRSoftware/DRAGONS/blob/f7cbfe8a7ecf575eeabc32ca6fc9da9a3ec0f3e8/geminidr/gemini/lookups/DQ_definitions.py + +.. note:: + These definitions are located in + `geminidr.gemini.lookups.DQ_definitions `_. The are + defined as ``np.uint16`` type integers. + +So a pixel marked 10 (binary 0001010) in the mask, would be a "non-linear" +"cosmic ray". The |AstroData| masks are propagated with bitwise-OR operation. +For example, let's say that we are stacking frames. A pixel is set as bad +(value 1 (0000001)) in one frame, saturated in another (value 4 (0000100)), and +fine in all the other the frames (value 0 (0000000)). The mask of the resulting +stack will be assigned a value of 5 (0000101) for that pixel. + +These bitmasks will work like any other NumPy True/False mask. There is a +usage example below using the mask. + +The mask can be accessed as follows: + +.. todo:: + Need to figure out a non-DRAGONS example here that makes sense. + +.. doctest:: + + # >>> ad = astrodata.open(some_fits_file_with_mask) + # >>> ad.info() # DOCTEST: +NORMALIZE_WHITESPACE + # Filename: /.../some_file.fits + # Tags: _DOCTEST_DATA + # + # Pixels Extensions + # Index Content Type Dimensions Format + # [ 0] science NDAstroData (2048, 2048) float64 + + # >>> ad[2].mask + +Display +======= + +Since the data is stored in the |AstroData| object as a NumPy |NDArray| any +tool that works on |NDArray| can be used. To display in |DS9| there is the +``imexam`` package. We will show how to use ``imexam`` to display and read +the cursor position. Read the documentation on that tool to learn more about +what else it has to offer (. + +.. warning:: + The ``numdisplay`` package is still available for now but it is no longer + supported by STScI. + +Useful tools from the NumPy, SciPy, and Astropy Packages +======================================================== + +Scientific libraries in python provide a rich menagerie of tools for data +analysis and visualization. They have their own extensive documentation and it +is highly recommend for the users to learn about what they have to offer. It +might save you from re-inventing the wheel for many common tasks (or uncommon +ones!). + +The pixels, variance, and mask are stored as NumPy |NDArray|'s. Let us go +through some basic examples, just to get a feel for how the data in an +|AstroData| object can be manipulated. + +ndarray +------- + +The data are contained in NumPy |NDArray| objects. Any tools that works +on an |NDArray| can be used with Astrodata. + +.. doctest:: + + >>> ad = astrodata.open(some_fits_file_with_extensions) + + >>> data = ad[0].data + + >>> # Shape of the array. (equivalent to NAXIS2, NAXIS1) + >>> data.shape + (2048, 2048) + + >>> # Value of a pixel at "IRAF" or DS9 coordinates (100, 50) + >>> data[49,99] + 1.0 + + >>> # Data type + >>> data.dtype + dtype('float64') + +The two most important things to remember for users coming from the IRAF world +or the Fortran world are that the array has the y-axis in the first index, the +x-axis in the second, and that the array indices are zero-indexed, not +one-indexed. The examples above illustrate those two critical differences. + +It is sometimes useful to know the data type of the values stored in the array. +Here, the file is a raw dataset, fresh off the telescope. No operations has +been done on the pixels yet. The data type of Gemini raw datasets is always +"Unsigned integer (0 to 65535)", ``uint16``. + +.. todo:: + What's the proper way of doing this in numpy without an operation? + +.. warning:: + Beware that doing arithmetic on ``uint16`` can lead to unexpected + results. This is a NumPy behavior. If the result of an operation + is higher than the range allowed by ``uint16``, the output value will + be "wrong". The data type will not be modified to accommodate the large + value. A workaround, and a safety net, is to multiply the array by + ``1.0`` to force the conversion to a ``float64``. + + .. doctest:: + + >>> a = np.array([65535], dtype='uint16') + >>> a + a + array([65534], dtype=uint16) + >>> 1.0*a + a + array([131070.]) + + + +Simple Numpy Statistics +----------------------- + +A lot of functions and methods are available in NumPy to probe the array, +too many to cover here, but here are a couple examples. + +.. doctest:: + + >>> import numpy as np + + >>> ad = astrodata.open(some_fits_file) + >>> data = ad[0].data + + # Add some data to it to make it more interesting + >>> data += 10 * (random_number.random(data.shape) - 1.0) + + # Calculate the mean, average, and median, using methods/functions. + >>> data.mean() + -5.00117... + >>> np.average(data) + -5.00117... + >>> np.median(data) + -5.00271... + +As shown, both array methods like ``.mean()`` as well as numpy ``ufunc`` +functions like ``np.average()`` can be used. + +See the NumPy documentation for more information and more functions that are +available for use in that library. + + +Clipped Statistics +------------------ + +It is common in astronomy to apply clipping to the statistics (e.g., a clipped +average). The NumPy ``ma`` module can be used to create masks of the values +to reject. In the examples below, we calculated the clipped average of the +first pixel extension with a rejection threshold set to +/- 3 times the +standard deviation. + +Before Astropy, it was possible to do something like that with only NumPy +tools, like in this example + +.. doctest:: + + >>> stddev = data.std() + >>> mean = data.mean() + + >>> clipped_mean = np.ma.masked_outside( + ... data, + ... mean-3*stddev, + ... mean+3*stddev + ... ).mean() + + >>> print( + ... f"standard deviation = {stddev:10.3e}", + ... f"mean = {mean:10.3e}", + ... f"clipped mean = {clipped_mean:10.3e}", + ... sep='\n', + ... ) # DOCTEST: +NORMALIZE_WHITESPACE + standard deviation = 2.887e+00 + mean = -5.001e+00 + clipped mean = -5.001e+00 + + + + +There is no iteration in that example. It is a one-time clipping of the data +specifically for this calculation. + +For something more robust, there is an Astropy function that can help, in +particular by adding an iterative process to the calculation. Here is +how it is done + +.. doctest:: + + >>> from astropy.stats import sigma_clip + + >>> clipped_mean = np.ma.mean(sigma_clip(data, sigma=3)) + >>> print(f"clipped mean = {clipped_mean:10.3e}") + clipped mean = -5.001e+00 + +Filters with SciPy +------------------ + +Another common operation is the filtering of an image, (e.g., convolusion with +a gaussian filter). The SciPy module ``ndimage.filters`` offers several +functions for image processing. See the SciPy documentation for more +information. + +The example below applies a gaussian filter to the pixel array. + +.. todo:: + Need to revisit this example + +.. doctest:: + + # >>> from scipy.ndimage import filters + # >>> import imexam + + # >>> ad = astrodata.open('../playdata/N20170521S0925_forStack.fits') + # >>> data = ad[0].data + + # >>> # We need to prepare an array of the same size and shape as + # >>> # the data array. The result will be put in there. + # >>> convolved_data = np.zeros(data.size).reshape(data.shape) + + # >>> # We now apply the convolution filter. + # >>> sigma = 10. + # >>> filters.gaussian_filter(data, sigma, output=convolved_data) + + # >>> # Let's visually compare the convolved image with the original + # >>> ds9 = imexam.connect(list(imexam.list_active_ds9())[0]) + # >>> ds9.view(data) + # >>> ds9.scale('zscale') + # >>> ds9.frame(2) + # >>> ds9.view(convolved_data) + # >>> ds9.scale('zscale') + # >>> ds9.blink() + # >>> # When you are convinced it's been convolved, stop the blinking. + # >>> ds9.blink(blink=False) + +.. todo:: + what is meant by "this particular kernel"? leaving this unedited on + the first pass for clarity later. + +Note that there is an Astropy way to do this convolution, with tools in +``astropy.convolution`` package. Beware that for this particular kernel +we have found that the Astropy ``convolve`` function is extremely slow +compared to the SciPy solution. + +This is because the SciPy function is optimized for a Gaussian convolution +while the generic ``convolve`` function in Astropy can take in any kernel. +Being able to take in any kernel is a very powerful feature, but the cost +is time. The lesson here is do your research, and find the best tool for +your needs. + + +Many other tools +---------------- + +There are many, many other tools available out there. Here are the links to +the three big projects we have featured in this section. + +* NumPy: `www.numpy.org `_ +* SciPy: `www.scipy.org `_ +* Astropy: `www.astropy.org `_ + +.. todo:: + This should be its own page, probably + +Using the Astrodata Data Quality Plane +====================================== + +Let us look at an example where the use of the Astrodata mask is +necessary to get correct statistics. A GMOS imaging frame has large sections +of unilluminated pixels; the edges are not illuminated and there are two +bands between the three CCDs that represent the physical gap between the +CCDs. Let us have a look at the pixels to have a better sense of the +data + +.. todo:: + Need to revisit this example + +.. doctest:: + + # >>> ad = astrodata.open('../playdata/N20170521S0925_forStack.fits') + # >>> import imexam + # >>> ds9 = imexam.connect(list(imexam.list_active_ds9())[0]) + + # >>> ds9.view(ad[0].data) + # >>> ds9.scale('zscale') + +.. todo:: + Was this supposed to have an associated image in the documentation? + does it exist in the docs? (Nope, need to generate it probably) + +See how the right and left portions of the frame are not exposed to the sky, +and the 45 degree angle cuts of the four corners. The chip gaps too. If we +wanted to do statistics on the whole frames, we certainly would not want to +include those unilluminated areas. We would want to mask them out. + +Let us have a look at the mask associated with that image + +.. todo:: + Need to revisit this example + +.. doctest:: + + # >>> ds9.view(ad[0].mask) + # >>> ds9.scale('zscale') + +The bad sections are all white (pixel value > 0). There are even some +illuminated pixels that have been marked as bad for a reason or another. + +Let us use that mask to reject the pixels with no or bad information and +do calculations only on the good pixels. For the sake of simplicity we will +just do an average. This is just illustrative. We show various ways to +accomplish the task; choose the one that best suits your need or that you +find most readable. + +.. doctest:: + + >>> # For clarity... + >>> ad = astrodata.from_file(some_fits_file_with_mask) + >>> data = ad[0].data + >>> mask = ad[0].mask + + >>> breakpoint() + >>> # Reject all flagged pixels and calculate the mean + >>> np.mean(data[mask == 0]) + + >>> np.ma.masked_array(data, mask).mean() + + >>> # Reject only the pixels flagged "no_data" (bit 16) + >>> np.mean(data[(mask & 16) == 0]) + >>> np.ma.masked_array(data, mask & 16).mean() + >>> np.ma.masked_where(mask & 16, data).mean() + +The "long" form with ``np.ma.masked_*`` is useful if you are planning to do +more than one operation on the masked array. For example + +.. doctest:: + + >>> clean_data = np.ma.masked_array(data, mask) + >>> clean_data.mean() + >>> np.ma.median(clean_data) + >>> clean_data.max() + + +Manipulate Data Sections +======================== + +So far we have shown examples using the entire data array. It is possible to +work on sections of that array. If you are already familiar with Python, the +following discussion about slixing is the same as you've seen throughout your +Python coding experience. For readers new to Python, and especially those +coming from IRAF, there are a few things that are worth explaining. + +When indexing a NumPy |NDArray|, the left most number refers to the highest +dimension's axis. For example, in a 2D array, the IRAF section are in (x-axis, +y-axis) format, while in Python they are in (y-axis, x-axis) format. Also +important to remember is that the |NDArray| is 0-indexed, rather than 1-indexed +like in Fortran or IRAF. + +Putting it all together, a pixel position (x,y) = (50,75) in IRAF or from the +cursor on a DS9 frame, is accessed in Python as ``data[74,49]``. Similarly, +the IRAF section [10:20, 30:40] translate in Python to [9:20, 29:40]. Also +remember that when slicing in Python, the upper limit of the slice is not +included in the slice. This is why here we request 20 and 40 rather 19 and 39. + +Basic Statistics on Section +--------------------------- + +In this example, we do simple statistics on a section of the image. + +.. doctest:: + + >>> import numpy as np + + >>> ad = astrodata.open('../playdata/N20170521S0925_forStack.fits') + >>> data = ad[0].data + + # Get statistics for a 25x25 pixel-wide box centered on pixel + # (50,75) (DS9 frame coordinate) + >>> xc = 49 + >>> yc = 74 + >>> buffer = 25 + >>> (xlow, xhigh) = (xc - buffer//2, xc + buffer//2 + 1) + >>> (ylow, yhigh) = (yc - buffer//2, yc + buffer//2 + 1) + + # The section is [62:87, 37:62] + >>> stamp = data[ylow:yhigh, xlow:xhigh] + >>> mean = stamp.mean() + >>> median = np.median(stamp) + >>> stddev = stamp.std() + >>> minimum = stamp.min() + >>> maximum = stamp.max() + + >>> print(' Mean Median Stddev Min Max\n \ + ... %.2f %.2f %.2f %.2f %.2f' % \ + ... (mean, median, stddev, minimum, maximum)) + +.. todo:: + implement a median method if it's that important + Have you noticed that the median is calculated with a function rather + than a method? This is simply because the |NDArray| object does not + have a method to calculate the median. + +.. todo:: + turn below example into a full example file + +Example - Overscan Subtraction with Trimming +-------------------------------------------- + +Several concepts from previous sections and chapters are used in this +example. The Descriptors are used to retrieve the overscan section and +the data section information from the headers. Statistics are done on the +NumPy |NDArray| representing the pixel data. Astrodata arithmetics is +used to subtract the overscan level. Finally, the overscan section is +trimmed off and the modified |AstroData| object is written to a new file +on disk. + +To make the example more complete, and to show that when the pixel data +array is trimmed, the variance (and mask) arrays are also trimmed, let us +add a variance plane to our raw data frame. + +.. doctest:: + + >>> ad = astrodata.open('../playdata/N20170609S0154.fits') + + >>> for (extension, gain) in zip(ad, ad.gain()): + ... extension.variance = extension.data / gain + ... + + >>> # Here is how the data structure looks like before the trimming. + >>> ad.info() + Filename: ../playdata/N20170609S0154.fits + Tags: ACQUISITION GEMINI GMOS IMAGE NORTH RAW SIDEREAL UNPREPARED + + Pixels Extensions + Index Content Type Dimensions Format + [ 0] science NDAstroData (2112, 288) uint16 + .variance ndarray (2112, 288) float64 + [ 1] science NDAstroData (2112, 288) uint16 + .variance ndarray (2112, 288) float64 + [ 2] science NDAstroData (2112, 288) uint16 + .variance ndarray (2112, 288) float64 + [ 3] science NDAstroData (2112, 288) uint16 + .variance ndarray (2112, 288) float64 + + # Let's operate on the first extension. + # + # The section descriptors return the section in a Python format + # ready to use, 0-indexed. + >>> oversec = ad[0].overscan_section() + >>> datasec = ad[0].data_section() + + # Measure the overscan level + >>> mean_overscan = ad[0].data[oversec.y1: oversec.y2, oversec.x1: oversec.x2].mean() + + # Subtract the overscan level. The variance will be propagated. + >>> ad[0].subtract(mean_overscan) + + # Trim the data to remove the overscan section and keep only + # the data section. Note that the WCS will be automatically + # adjusted when the trimming is done. + # + # Here we work on the NDAstroData object to have the variance + # trimmed automatically to the same size as the science array. + # To reassign the cropped NDAstroData, we use the reset() method. + >>> ad[0].reset(ad[0].nddata[datasec.y1:datasec.y2, datasec.x1:datasec.x2] + + # Now look at the dimensions of the first extension, science + # and variance. That extension is smaller than the others. + >>> ad.info() + Filename: ../playdata/N20170609S0154.fits + Tags: ACQUISITION GEMINI GMOS IMAGE NORTH RAW SIDEREAL UNPREPARED + + Pixels Extensions + Index Content Type Dimensions Format + [ 0] science NDAstroData (2112, 256) float64 + .variance ndarray (2112, 256) float64 + [ 1] science NDAstroData (2112, 288) uint16 + .variance ndarray (2112, 288) float64 + [ 2] science NDAstroData (2112, 288) uint16 + .variance ndarray (2112, 288) float64 + [ 3] science NDAstroData (2112, 288) uint16 + .variance ndarray (2112, 288) float64 + + # We can write this to a new file + >>> ad.write('partly_overscan_corrected.fits') + +A new feature presented in this example is the ability to work on the +|NDAstroData| object directly. This is particularly useful when cropping +the science pixel array as one will want the variance and the mask arrays +cropped exactly the same way. Taking a section of the |NDAstroData| +object (ad[0].nddata[y1:y2, x1:x2]), instead of just the ``.data`` array, +does all that for us. + +To reassign the cropped |NDAstroData| to the extension one uses the +``.reset()`` method as shown in the example. + +Of course to do the overscan correction correctly and completely, one would +loop over all four extensions. But that's the only difference. + +Data Cubes +========== + +Reduced Integral Field Unit (IFU) data is commonly represented as a cube, +a three-dimensional array. The ``data`` component of an |AstroData| +object extension can be such a cube, and it can be manipulated and explored +with NumPy, AstroPy, SciPy, imexam, like we did already in this section +with 2D arrays. We can use matplotlib to plot the 1D spectra represented +in the third dimension. + +In Gemini IFU cubes, the first axis is the X-axis, the second, the Y-axis, +and the wavelength is in the third axis. Remember that in a |NDArray| +that order is reversed (wlen, y, x). + +In the example below we "collapse" the cube along the wavelenth axis to +create a "white light" image and display it. Then we plot a 1D spectrum +from a given (x,y) position. + +:: + + >>> import imexam + >>> import matplotlib.pyplot as plt + + >>> ds9 = imexam.connect(list(imexam.list_active_ds9())[0]) + + >>> adcube = astrodata.open('../playdata/gmosifu_cube.fits') + >>> adcube.info() + + >>> # Sum along the wavelength axis to create a "white light" image + >>> summed_image = adcube[0].data.sum(axis=0) + >>> ds9.view(summed_image) + >>> ds9.scale('minmax') + + >>> # Plot a 1-D spectrum from the spatial position (14,25). + >>> plt.plot(adcube[0].data[:,24,13]) + >>> plt.show() # might be needed, depends on matplotlibrc interactive setting + + +Now that is nice but it would be nicer if we could plot the x-axis in units +of Angstroms instead of pixels. We use the AstroData's WCS handler, which is +based on ``gwcs.wcs.WCS`` to get the necessary information. A particularity +of ``gwcs.wcs.WCS`` is that it refers to the axes in the "natural" way, +(x, y, wlen) contrary to Python's (wlen, y, x). It truly requires you to pay +attention. + +:: + + >>> import matplotlib.pyplot as plt + + >>> adcube = astrodata.open('../playdata/gmosifu_cube.fits') + + # We get the wavelength axis in Angstroms at the position we want to + # extract, x=13, y=24. + # The wcs call returns a 3-element list, the third element ([2]) contains + # the wavelength values for each pixel along the wavelength axis. + + >>> length_wlen_axis = adcube[0].shape[0] # (wlen, y, x) + >>> wavelengths = adcube[0].wcs(13, 24, range(length_wlen_axis))[2] # (x, y, wlen) + + # We get the intensity along that axis + >>> intensity = adcube[0].data[:, 24, 13] # (wlen, y, x) + + # We plot + plt.clf() + plt.plot(wavelengths, intensity) + plt.show() + + +Plot Data +========= +The main plotting package in Python is ``matplotlib``. We have used it in the +previous section on data cubes to plot a spectrum. There is also the project +called ``imexam`` which provides astronomy-specific tools for the +exploration and measurement of data. We have also used that package above to +display images to DS9. + +In this section we absolutely do not aim at covering all the features of +either package but rather to give a few examples that can get the readers +started in their exploration of the data and of the visualization packages. + +Refer to the projects web pages for full documentation. + +* Matplotlib: `https://matplotlib.org `_ +* imexam: `https://github.com/spacetelescope/imexam `_ + +Matplotlib +---------- +With Matplotlib you have full control on your plot. You do have to do a bit +for work to get it perfect though. However it can produce publication +quality plots. Here we just scratch the surface of Matplotlib. + +:: + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from astropy import wcs + + >>> ad_image = astrodata.open('../playdata/N20170521S0925_forStack.fits') + >>> ad_spectrum = astrodata.open('../playdata/estgsS20080220S0078.fits') + + >>> # Line plot from image. Row #1044 (y-coordinate) + >>> line_index = 1043 + >>> line = ad_image[0].data[line_index, :] + >>> plt.clf() + >>> plt.plot(line) + >>> plt.show() + + >>> # Column plot from image, averaging across 11 pixels around colum #327 + >>> col_index = 326 + >>> width = 5 + >>> xlow = col_index - width + >>> xhigh = col_index + width + 1 + >>> thick_column = ad_image[0].data[:, xlow:xhigh] + >>> plt.clf() + >>> plt.plot(thick_column.mean(axis=1)) # mean along the width. + >>> plt.show() + >>> plt.ylim(0, 50) # Set the y-axis range + >>> plt.plot(thick_column.mean(axis=1)) + >>> plt.show() + + >>> # Contour plot for a section of an image. + >>> center = (1646, 2355) + >>> width = 15 + >>> xrange = (center[1]-width//2, center[1] + width//2 + 1) + >>> yrange = (center[0]-width//2, center[0] + width//2 + 1) + >>> blob = ad_image[0].data[yrange[0]:yrange[1], xrange[0]:xrange[1]] + >>> plt.clf() + >>> plt.imshow(blob, cmap='gray', origin='lower') + >>> plt.contour(blob) + >>> plt.show() + + >>> # Spectrum in pixels + >>> plt.clf() + >>> plt.plot(ad_spectrum[0].data) + >>> plt.show() + + >>> # Spectrum in Angstroms + >>> spec_wcs = wcs.WCS(ad_spectrum[0].hdr) + >>> pixcoords = np.array(range(ad_spectrum[0].data.shape[0])) + >>> wlen = spec_wcs.wcs_pix2world(pixcoords, 0)[0] + >>> plt.clf() + >>> plt.plot(wlen, ad_spectrum[0].data) + >>> plt.show() diff --git a/_sources/manuals/usermanual/headers.rst.txt b/_sources/manuals/usermanual/headers.rst.txt new file mode 100644 index 00000000..ca55d33d --- /dev/null +++ b/_sources/manuals/usermanual/headers.rst.txt @@ -0,0 +1,369 @@ +.. headers.rst + +.. _headers: + +******************** +Metadata and Headers +******************** + +Metadata is a critical component of astronomical observations. These data are +used to clarify and define various aspects of the observation, such as the +instrument configuration, the observation conditions, and the data reduction +history. The metadata is often stored in the FITS headers of the data files, +and in |AstroData| metadata is manipulated and access in two ways: through +descriptors (via |astro_data_descriptor|) and directly in filetype-specific +header access. + +.. warning:: + While we say that header access is filetype-specific, it's important to + note that this is not the same as saying that the headers are different + for each file type. The way headers are managed is FITS-centric, and + therefore implementing header access for a new type requires either updating + the methods that access headers or converting the headers to use + :mod:`astropy.io.fits` objects after loading. + + For more information about developing with descriptors, see + :doc:`../progmanual/descriptors`. + +.. + **Try it yourself** + + Download the data package (:ref:`datapkg`) if you wish to follow along and run the + examples. Then :: + + $ cd /ad_usermanual/playground + $ python + + You need to import Astrodata and the Gemini instrument configuration package. + + :: + + >>> import astrodata + >>> import gemini_instruments + +Astrodata Descriptors +===================== + +Descriptors provide a mapping between metadata or data and a value or set of +values. They are a way to access metadata in a consistent way, regardless of +other differences between metadata (such as differences in the instrument, +image type, etc.). Descriptors are implemented as methods, and can be +found using the :meth:`astrodata.AstroData.descriptors` property. + +As a user, your interactions with descriptors will depend on the specific +implementation of |AstroData| you are using. For example, if you're using +|gemini_instruments| (from |DRAGONS|), you will have access to the descriptors +defined for Gemini instruments. If you're using |astrodata| directly, you will +have access to the descriptors defined for the generic |AstroData| class. + +Descriptors are a way to access metadata in a consistent way, and may perform +operations to arrive at a given value. Descriptors should not, in best +practice, modify the state of any object; instead, they will return a new value +every time they are used. Therefore, they can be more computationally expensive +than direct header access, but they are far more flexible. + +For example, if the user is interested to know the effective filter used for a +Gemini observation, normally one needs to know which specific keyword or set of +keywords to look at for that instrument. However, once the concept of "filter" +is coded as a Descriptor (which happens in |gemini_instruments|), the user only +needs to call the ``filter_name()`` descriptor to retrieve the information. + +.. todo:: I don't know what this is trying to explain. This may be more + confusing in the long run, since it's conflating method-like descriptors + and attribute-like tags. + + The Descriptors are closely associated with the Astrodata Tags. In fact, + they are implemented in the same |AstroData| class as the tags. Once + the specific |AstroData| class is selected (upon opening the file), all + the tags and descriptors for that class are defined. For example, all the + descriptor functions of GMOS data, ie. the functions that map a descriptor + concept to the actual header content, are defined in the ``AstroDataGmos`` + class. + +This is all completely transparent to the user. One simply opens the data +file and all the descriptors are ready to be used. + +.. testsetup:: + + import astrodata + import gemini_instruments + + from astrodata import astro_data_descriptor + +.. doctest:: + >>> class MyAstroData(astrodata.AstroData): + ... @astro_data_descriptor + ... def my_descriptor(self): + ... return 42 + + >>> ad = MyAstroData() + >>> ad.my_descriptor() + 42 + + # Descriptors can be listed as a tuple through the AstroData.descriptors + # property + >>> ad.descriptors + ('my_descriptor',) + +.. note:: + + Descriptors must be defined for a given |AstroData|-derived class. + Descriptors are inherited like normal methods, so if a class inherits from + another class that has descriptors, the new class will have those + descriptors as well unless they are explicitly overridden. + +.. todo:: Need to test this... + Most Descriptor names are readily understood, but one can get a short + description of what the Descriptor refers to by calling the Python help + function. For example:: + + >>> help(ad.airmass) + >>> help(ad.filter_name) + +Accessing Metadata +================== + +Accessing Metadata with Descriptors +----------------------------------- + +Whenever possible, descriptors should be used to get information from headers. +This allows for straightforward re-usability of the code as it will propogate +to any datasets with an |AstroData| class. + +Here are a few examples using Descriptors + +.. todo:: REPLACE BELOW EXAMPLE + +.. doctest:: + >>> ad = astrodata.open('../playdata/N20170609S0154.fits') + + >>> #--- print a value + >>> print('The airmass is : ', ad.airmass()) + The airmass is : 1.089 + + >>> #--- use a value to control the flow + >>> if ad.exposure_time() < 240.: + ... print('This is a short exposure.') + ... else: + ... print('This is a long exposure.') + This is a short exposure. + + >>> #--- multiply all extensions by their respective gain + >>> for ext, gain in zip(ad, ad.gain()): + ... ext *= gain + + >>> #--- do arithmetics + >>> fwhm_pixel = 3.5 + >>> fwhm_arcsec = fwhm_pixel * ad.pixel_scale() + +The return value of a descriptor is determined by the developer who created the +descriptor. It's best practice to return a value of the same---or similar, +e.g., an iterable---type for each type of descriptor. However, this is not +always desirable between different instrument sets. For example, Gemini data +and JWST data may have different ways of describing specific values that are +most useful to observers on their respective telescopes. To avoid confusion, +check the return value of the descriptor explicitly when you are experimenting with +new data: + +.. testsetup:: + class TestAstroData(astrodata.AstroData): + @astro_data_descriptor + def unknown_descriptor(self): + return "you know what I am now!" + + class OtherTestAstroData(astrodata.AstroData): + @astro_data_descriptor + def unknown_descriptor(self): + string = ( + "My developer decided it's more useful to return the " + "words discretely" + ) + + return string.split() + +.. doctest:: + + >>> ad = TestAstroData() + >>> ad.unknown_descriptor() + 'you know what I am now!' + + >>> type(ad.unknown_descriptor()) + + + >>> ad = OtherTestAstroData() + >>> ad.unknown_descriptor() + ['My', 'developer', 'decided', "it's", 'more', 'useful', 'to', 'return', 'the', 'words', 'discretely'] + + >>> type(ad.unknown_descriptor()) + + + +Descriptors across multiple extensions +-------------------------------------- + +.. todo:: Rewrite this example and accompanying section + +The dataset used in this section has 4 extensions. When the descriptor +value can be different for each extension, the descriptor will return a +Python list. + +:: + + >>> ad.airmass() + 1.089 + >>> ad.gain() + [2.03, 1.97, 1.96, 2.01] + >>> ad.filter_name() + 'open1-6&g_G0301' + +Some descriptors accept arguments. For example:: + + >>> ad.filter_name(pretty=True) + 'g' + +Accessing Metadata Directly +--------------------------- + +Not all header content is mapped to descriptors, nor should it be. Direct +access is available for header content falling outside the scope of the +descriptors. + +One important thing to keep in mind is that the PHU (Primary Header Unit) and +the extension headers are accessed slightly differently. The attribute +``phu`` needs to be used for the PHU, and ``hdr`` for the extension headers. + +.. warning:: + The ``phu`` and ``hdr`` attributes are not available for all |AstroData| + classes. They are only available for classes that have been implemented to + use them. The default |AstroData| class without modification does have + minimal support for these attributes, but for other file types they will + need to be implemented by a developer/the instrument team. + +Here are some examples of direct header access + +.. todo:: replace example + +.. doctest:: + >>> ad = astrodata.open('../playdata/N20170609S0154.fits') + + >>> #--- Get keyword value from the PHU + >>> ad.phu['AOFOLD'] + 'park-pos.' + + >>> #--- Get keyword value from a specific extension + >>> ad[0].hdr['CRPIX1'] + 511.862999160781 + + >>> #--- Get keyword value from all the extensions in one call. + >>> ad.hdr['CRPIX1'] + [511.862999160781, 287.862999160781, -0.137000839218696, -224.137000839219] + + +Whole Headers +------------- + +Entire headers can be retrieved as ``fits`` ``Header`` objects + +.. todo:: replace example + +.. doctest:: + + >>> ad = astrodata.open('../playdata/N20170609S0154.fits') + >>> type(ad.phu) + + >>> type(ad[0].hdr) + + +In interactive mode, it is possible to print the headers on the screen as +follows + +.. doctest:: + + >>> ad.phu + SIMPLE = T / file does conform to FITS standard + BITPIX = 16 / number of bits per data pixel + NAXIS = 0 / number of data axes + .... + + >>> ad[0].hdr + XTENSION= 'IMAGE ' / IMAGE extension + BITPIX = 16 / number of bits per data pixel + NAXIS = 2 / number of data axes + .... + + + +Updating, Adding and Deleting Metadata +====================================== + +Header cards can be updated, added to, or deleted from the headers. The PHU +and the extensions headers are again accessed in a mostly identical way +with ``phu`` and ``hdr``, respectively. + +.. doctest:: + + >>> ad = astrodata.open('../playdata/N20170609S0154.fits') + +Add and update a keyword, without and with comment + +.. doctest:: + + >>> ad.phu['NEWKEY'] = 50. + >>> ad.phu['NEWKEY'] = (30., 'Updated PHU keyword') + + >>> ad[0].hdr['NEWKEY'] = 50. + >>> ad[0].hdr['NEWKEY'] = (30., 'Updated extension keyword') + +Delete a keyword + +.. doctest:: + + >>> del ad.phu['NEWKEY'] + >>> del ad[0].hdr['NEWKEY'] + + +.. todo:: This should probably be its own page + +.. _world_coordinates: + +World Coordinate System attribute +================================= + +The ``wcs`` of an extension's ``nddata`` attribute (eg. ``ad[0].nddata.wcs``; +see :ref:`pixel-data`) is stored as an instance of ``astropy.wcs.WCS`` (a +standard FITS WCS object) or ``gwcs.WCS`` (a `"Generalized WCS" or gWCS +`_ object). This defines a transformation between +array indices and some other co-ordinate system such as "World" co-ordinates +(see `APE 14 +`_). GWCS allows +multiple, almost arbitrary co-ordinate mappings from different calibration +steps (eg. CCD mosaicking, distortion correction & wavelength calibration) to +be combined in a single, reversible transformation chain --- but this +information cannot always be represented as a FITS standard WCS. If a gWCS +object is too complex to be defined by the basic FITS keywords, it gets stored +as a table extension named 'WCS' when the |AstroData| instance is saved to a +file (with the same EXTVER as the corresponding 'SCI' array) and the FITS +header keywords are updated to provide an approximation to the true WCS and an +additional keyword ``FITS-WCS`` is added with the value 'APPROXIMATE'. The +representation in the table is produced using `ASDF +`_, with one line of text per row. Likewise, when +the file is re-opened, the gWCS object gets recreated in ``wcs`` from the +table. If the transformation defined by the gWCS object can be accurately +described by standard FITS keywords, then no WCS extension is created as the +gWCS object can be created from these keywords when the file is re-opened. + +In future, it is intended to improve the quality of the FITS approximation +using the Simple Imaging Polynomial convention +(`SIP `_) or +a discrete sampling of the World co-ordinate +values will be stored as part of the FITS WCS, following `Greisen et al. (2006) +`_, S6 (in addition to the +definitive 'WCS' table), allowing standard FITS readers to report accurate +World co-ordinates for each pixel. + +.. _defining_descriptors: + +Adding Descriptors [Advanced Topic] +=================================== + +To learn how to add descriptors to |AstroData|, see the |progmanual|. diff --git a/_sources/manuals/usermanual/index.rst.txt b/_sources/manuals/usermanual/index.rst.txt new file mode 100644 index 00000000..9c5dff7a --- /dev/null +++ b/_sources/manuals/usermanual/index.rst.txt @@ -0,0 +1,23 @@ +.. Astrodata User Manual master file, created from team template + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + Manually edited by KL Wed Jan 18 2017 + +=========== +User Manual +=========== + +.. admonition:: Document ID + + PIPE-USER-106_AstrodataUserManual + +.. toctree:: + :maxdepth: 2 + + intro + structure + iomef + tags + headers + data + tables diff --git a/_sources/manuals/usermanual/intro.rst.txt b/_sources/manuals/usermanual/intro.rst.txt new file mode 100644 index 00000000..f5f255ab --- /dev/null +++ b/_sources/manuals/usermanual/intro.rst.txt @@ -0,0 +1,126 @@ +.. intro.rst + +.. _intro_usermanual: + +************ +Introduction +************ + +Welcome to the AstroData User's Manual, a user guide for the |astrodata| +package. |astrodata| was formerly a part of the |DRAGONS| data reduction suite +developed at the Gemini Observatory. It has undergone several iteractions of +major development and improvements, and is now designed as a standalone +solution for handling astronomical data. + +|astrodata| consolidates the handling of astronomical data into a single +package, using a uniform interface to access and manipulate data from +different instruments and observing modes. It is designed to be used in +conjunction with |astropy|, |numpy| and other scientific Python packages. + +.. + The current chapter covers basic concepts like what is the |astrodata| + package and how to install it (together with the other DRAGONS' packages). + :ref:`Chapter 2 ` explains with more details what is |AstroData| + and how the data is represented using it. :ref:`Chapter 3 ` + describes input and output operations and how multi-extension (MEF) FITS + files are represented. :ref:`Chapter 4 ` provides information + regarding the |TagSet| class, its usage and a few advanced topics. In + :ref:`Chapter 5 ` you will find information about the FITS headers + and how to access/modify the metadata. The last two chapters, :ref:`Chapter + 6 ` and :ref:`Chapter 7 ` cover more details about how + to read, manipulate and write pixel data and tables, respectively. + +This introduction will guide you through the installation process and provide +a brief overview of the package. If you are looking for a quick reference, +please head to the :doc:`../cheatsheet`. + +What is |astrodata|? +==================== + +|astrodata| is a package that wraps together tools to represent internally +astronomical datasets stored on disks and to properly parse their metadata +using the |AstroData| and the |TagSet| classes. |astrodata| provides uniform +interfaces for working on datasets from different instruments. Once a dataset +has been opened with |from_file|, the object assesses metadata to determine the +appropriate class and methods to use for reading and processing the data. +Information like instrument, observation mode, and how to access headers, is +readily available through the |AstroData| uniform interface returned by +|from_file|. All the details are coded inside the class associated with the +instrument, that class then provides the interface. The appropriate class is +selected automatically when the file is opened and inspected by |astrodata|. + +Currently |astrodata| implements a basic representation for Multi-Extension +FITS (MEF) files. Extending to other file formats is possible, but requires +programming (see the |DeveloperGuide| for more information). + + +.. _install: + +Installing Astrodata +==================== + +Using pip +--------- + +The |astrodata| package has a number of dependencies. These can be found in the +``requirements.txt`` file in the source code repository. + +To install the standalone |astrodata| package, you can use pip:: + + $ pip install astrodata + +Or you can install it from the source code:: + + $ git clone https://github.com/teald/astrodata + $ cd astrodata # Or the directory where you cloned the repository + $ pip install -e . + +If you're interested in using |astrodata| out-of-the-box with a specific +type of data, you may want to install the astrodata package together with +their extensions. |astrodata| alone defines a base class, |AstroData|, which +is meant to be extended with instrument-specific classes. For example, to +use |astrodata| with Gemini data, you will need to install |DRAGONS|, which +includes the |astrodata| package and its extensions for |gemini_instruments|. + +Source code +----------- +The source code is available on Github: + + ``_ + +.. _datapkg: + +A quick example +=============== + +Here is a quick example of how to use |astrodata| to open a file and access +its metadata using an |AstroData| object + +.. doctest:: + >>> import astrodata + # Create a fake file to use. + >>> from astrodata.testing import create_test_file + + # We can create a fake file to use for this example: + >>> path = create_test_file(include_header_keys=['INSTRUME', 'EXPTIME', 'DATE-OBS']) + >>> ad = astrodata.from_file(path) + >>> ad.phu['INSTRUME'] + 'TEST_VALUE' + +All file opening, closing and metadata management (which selects the +appropriate class for the header data) are handled by |astrodata|. There is no +need to "close" the ad object, as all file handles are closed when no longer +required or the program finishes. + +.. _ad_support: + +Astrodata Support +================= + +Astrodata is developed and supported by staff at the Gemini Observatory. +Questions about the reduction of Gemini data should be directed to the +Gemini Helpdesk system at +``_. + +Issues related to |astrodata| itself can be reported at our +github |IssueTracker|. diff --git a/_sources/manuals/usermanual/iomef.rst.txt b/_sources/manuals/usermanual/iomef.rst.txt new file mode 100644 index 00000000..d5555644 --- /dev/null +++ b/_sources/manuals/usermanual/iomef.rst.txt @@ -0,0 +1,625 @@ +.. todo:: Need to update the examples + +.. iomef.rst + +.. _iomef: + +************************************************************ +Input and Output Operations and Extension Manipulation - MEF +************************************************************ + +|AstroData| is not intended to exclusively support Multi-Extension FITS (MEF) +files. However, given FITS' unflagging popularity as an astronomical data format, +the base |AstroData| object supports FITS and MEF files without any additional +effort by a user or programmer. + +.. note:: + For more information about FITS support and extending |AstroData| to + support other file formats, see :ref:`astrodata`. + + +.. + In this chapter, we present examples that will help the reader understand how + to access the information stored in a MEF with the |AstroData| object and + understand that mapping. + +.. + **Try it yourself** + + Download the data package (:ref:`datapkg`) if you wish to follow along and run the + examples. Then :: + + $ cd /ad_usermanual/playground + $ python + + +Open and access existing dataset +================================ + +Read in the dataset +------------------- + +The file on disk is loaded into the |AstroData| class associated with the +instrument the data is from. This association is done automatically based on +header content. + +.. todo:: replace EXAMPLE FILE with the actual example + +.. doctest:: + >>> import astrodata + >>> ad = astrodata.open(EXAMPLE_FILE) + >>> type(ad) + + +``ad`` has loaded in the file's header and parsed the keys present. Header access is done +through the ``.hdr`` attribute. + +.. doctest:: + >>> ad.hdr['CCDSEC'] + ['[1:512,1:4224]', '[513:1024,1:4224]', '[1025:1536,1:4224]', '[1537:2048,1:4224]'] + + With descriptors: + >>> ad.array_section(pretty=True) + ['[1:512,1:4224]', '[513:1024,1:4224]', '[1025:1536,1:4224]', '[1537:2048,1:4224]'] + +The original path and filename are also stored. If you were to write +the |AstroData| object to disk without specifying anything, path and +file name would be set to ``None``. + +.. todo:: Update when updating the example + +.. doctest:: + >>> ad.path + '../playdata/N20170609S0154.fits' + >>> ad.filename + 'N20170609S0154.fits' + + +Accessing the content of a MEF file +----------------------------------- + +|AstroData| uses |NDData| as the core of its structure. Each FITS extension +becomes a |NDAstroData| object, subclassed from |NDData|, and is added to +a list representing all extensions in the file. + +.. note:: + For details on the |AstroData| object, please refer to + :ref:`structure`. + +Pixel data +^^^^^^^^^^ + +To access pixel data, the list index and the ``.data`` attribute are used. That +returns a :class:`numpy.ndarray`. The list of |NDAstroData| is zero-indexed. +*Extension number 1 in a MEF is index 0 in an |AstroData| object*. + +.. doctest:: + >>> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits') + >>> data = ad[0].data + >>> type(data) + + >>> data.shape + (2112, 256) + +.. note:: + This implementation ignores the fact that the first extension in a MEF + file is the Primary Header Unit (PHU). The PHU is accessibly through the + ``.phu`` attribute of the |AstroData| object, and indexing with ``[i]`` + notation will only access the extensions. + +.. note:: + Remember that in a :class:`~numpy.ndarray` the 'y-axis' of the image is + accessed through the first number. + +.. todo:: need to review how this implemented and update this. It's pretty + confusing the way it's worded right now (not something trivial to word + precisely and comprehensibly, either). + +The variance and data quality planes, the ``VAR`` and ``DQ`` planes in Gemini +MEF files, are represented by the ``.variance`` and ``.mask`` attributes, +respectively. They are not their own "extension", they don't have their own +index in the list, unlike in a MEF. They are attached to the pixel data, +packaged together by the |NDAstroData| object. They are represented as +:class:`numpy.ndarray` just like the pixel data + +.. doctest:: + >>> var = ad[0].variance + >>> dq = ad[0].mask + +Tables +^^^^^^ + +Tables in the MEF file will also be loaded into the |AstroData| object. If a table +is associated with a specific science extension through the EXTVER header keyword, that +table will be packaged within the same AstroData extension as the pixel data +and accessible like an attribute. The |AstroData| "extension" is the +|NDAstroData| object plus any table or other pixel array associated with the +image data. If the table is not associated with a specific extension and +applies globally, it will be added to the AstroData object as a global +addition. No indexing will be required to access it. In the example below, one +``OBJCAT`` is associated with each extension, while the ``REFCAT`` has a global +scope + +.. doctest:: + + >>> ad.info() + Filename: ../playdata/N20170609S0154_varAdded.fits + Tags: ACQUISITION GEMINI GMOS IMAGE NORTH OVERSCAN_SUBTRACTED OVERSCAN_TRIMMED + PREPARED SIDEREAL + + Pixels Extensions + Index Content Type Dimensions Format + [ 0] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) uint16 + .OBJCAT Table (6, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + [ 1] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) uint16 + .OBJCAT Table (8, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + [ 2] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) uint16 + .OBJCAT Table (7, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + [ 3] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) uint16 + .OBJCAT Table (5, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + + Other Extensions + Type Dimensions + .REFCAT Table (245, 16) + + +The tables are stored internally as :class:`astropy.table.Table` objects. + +.. doctest:: + + >>> ad[0].OBJCAT + + NUMBER X_IMAGE Y_IMAGE ... REF_MAG_ERR PROFILE_FWHM PROFILE_EE50 + int32 float32 float32 ... float32 float32 float32 + ------ ------- ------- ... ----------- ------------ ------------ + 1 283.461 55.4393 ... 0.16895 -999.0 -999.0 + ... + >>> type(ad[0].OBJCAT) + + + >>> refcat = ad.REFCAT + >>> type(refcat) + + +.. note:: + Tables are accessed through attribute notation. However, if a conflicting + attribute exists for a given |AstroData| or |NDData| object, a + :py:exc:`AttributeError` will be raised to avoid confusion. + +Headers +^^^^^^^ + +Headers are stored in the |NDAstroData| ``.meta`` attribute as +:class:`astropy.io.fits.Header` objects, which implements a ``dict``-like +object. Headers associated with extensions are stored with the corresponding +|NDAstroData| object. The MEF Primary Header Unit (PHU) is stored as an +attribute in the |AstroData| object. When slicing an |AstroData| object or +accessing an index, the PHU will be included in the new sliced object. The +slice of an |AstroData| object is an |AstroData| object. Headers can be +accessed directly, or for some predefined concepts, the use of Descriptors is +preferred. More detailed information on Headers is covered in the section +:ref:`headers`. + +Using Descriptors + +.. doctest:: + + >>> ad = astrodata.open('../playdata/N20170609S0154.fits') + >>> ad.filter_name() + 'open1-6&g_G0301' + >>> ad.filter_name(pretty=True) + 'g' + +Using direct header access + +.. doctest:: + + >>> ad.phu['FILTER1'] + 'open1-6' + >>> ad.phu['FILTER2'] + 'g_G0301' + +Accessing the extension headers + +.. doctest:: + + >>> ad.hdr['CCDSEC'] + ['[1:512,1:4224]', '[513:1024,1:4224]', '[1025:1536,1:4224]', '[1537:2048,1:4224]'] + >>> ad[0].hdr['CCDSEC'] + '[1:512,1:4224]' + + With descriptors: + >>> ad.array_section(pretty=True) + ['[1:512,1:4224]', '[513:1024,1:4224]', '[1025:1536,1:4224]', '[1537:2048,1:4224]'] + + +Modify Existing MEF Files +========================= + +Appending an extension +---------------------- + +Extensions can be appended to an |AstroData| objects using the +:meth:`~astrodata.AstroData.append` method. + +Here is an example appending a whole AstroData extension, with pixel data, +variance, mask and tables. While these are treated as separate extensions in +the MEF file, they are all packaged together in the |AstroData| object. + +.. doctest:: + + >>> ad = astrodata.open('../playdata/N20170609S0154.fits') + >>> advar = astrodata.open('../playdata/N20170609S0154_varAdded.fits') + + >>> ad.info() + Filename: ../playdata/N20170609S0154.fits + Tags: ACQUISITION GEMINI GMOS IMAGE NORTH RAW SIDEREAL UNPREPARED + Pixels Extensions + Index Content Type Dimensions Format + [ 0] science NDAstroData (2112, 288) uint16 + [ 1] science NDAstroData (2112, 288) uint16 + [ 2] science NDAstroData (2112, 288) uint16 + [ 3] science NDAstroData (2112, 288) uint16 + + >>> ad.append(advar[3]) + >>> ad.info() + Filename: ../playdata/N20170609S0154.fits + Tags: ACQUISITION GEMINI GMOS IMAGE NORTH RAW SIDEREAL UNPREPARED + Pixels Extensions + Index Content Type Dimensions Format + [ 0] science NDAstroData (2112, 288) uint16 + [ 1] science NDAstroData (2112, 288) uint16 + [ 2] science NDAstroData (2112, 288) uint16 + [ 3] science NDAstroData (2112, 288) uint16 + [ 4] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) int16 + .OBJCAT Table (5, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + + >>> ad[4].hdr['EXTVER'] + 4 + >>> advar[3].hdr['EXTVER'] + 4 + +As you can see above, the fourth extension of ``advar``, along with everything +it contains was appended at the end of the first |AstroData| object. However, +note that, because the ``EXTVER`` of the extension in ``advar`` was 4, there are +now two extensions in ``ad`` with this ``EXTVER``. This is not a problem because +``EXTVER`` is not used by |AstroData| (it uses the index instead) and it is handled +only when the file is written to disk. + +In this next example, we are appending only the pixel data, leaving behind the other +associated data. One can attach the headers too, like we do here. + +.. doctest:: + + >>> ad = astrodata.open('../playdata/N20170609S0154.fits') + >>> advar = astrodata.open('../playdata/N20170609S0154_varAdded.fits') + + >>> ad.append(advar[3].data, header=advar[3].hdr) + >>> ad.info() + Filename: ../playdata/N20170609S0154.fits + Tags: ACQUISITION GEMINI GMOS IMAGE NORTH RAW SIDEREAL UNPREPARED + Pixels Extensions + Index Content Type Dimensions Format + [ 0] science NDAstroData (2112, 288) uint16 + [ 1] science NDAstroData (2112, 288) uint16 + [ 2] science NDAstroData (2112, 288) uint16 + [ 3] science NDAstroData (2112, 288) uint16 + [ 4] science NDAstroData (2112, 256) float32 + +Notice how a new extension was created but ``variance``, ``mask``, the OBJCAT +table and OBJMASK image were not copied over. Only the science pixel data was +copied over. + +Please note, there is no implementation for the "insertion" of an extension. + +Removing an extension or part of one +------------------------------------ +Removing an extension or a part of an extension is straightforward. The +Python command :func:`del` is used on the item to remove. Below are a few +examples, but first let us load a file + +.. doctest:: + + >>> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits') + >>> ad.info() + +As you go through these examples, check the new structure with :func:`ad.info()` +after every removal to see how the structure has changed. + +Deleting a whole |AstroData| extension, the fourth one + +.. doctest:: + + >>> del ad[3] + +Deleting only the variance array from the second extension + +.. doctest:: + >>> ad[1].variance = None + +Deleting a table associated with the first extension + +.. doctest:: + >>> del ad[0].OBJCAT + +Deleting a global table, not attached to a specific extension + +.. doctest:: + >>> del ad.REFCAT + + +Writing back to a file +====================== + +The |AstroData| class implements methods for writing its data back to a +MEF file on disk. + +Writing to a new file +--------------------- + +There are various ways to define the destination for the new FITS file. +The most common and natural way is + +.. doctest:: + + >>> ad.write('new154.fits') + # If the file already exists, an error will be raised unless overwrite=True + # is specified. + >>> ad.write('new154.fits', overwrite=True) + +This will write a FITS file named 'new154.fits' in the current directory. With +``overwrite=True``, it will overwrite the file if it already exists. A path +can be prepended to the filename if the current directory is not the +destination. + +Note that ``ad.filename`` and ``ad.path`` have not changed, we have just +written to the new file, the |AstroData| object is in no way associated with +that new file. + +.. doctest:: + + >>> ad.path + '../playdata/N20170609S0154.fits' + >>> ad.filename + 'N20170609S0154.fits' + +If you want to create that association, the ``ad.filename`` and ``ad.path`` +needs to be modified first. For example + +.. doctest:: + + >>> ad.filename = 'new154.fits' + >>> ad.write(overwrite=True) + + >>> ad.path + '../playdata/new154.fits' + >>> ad.filename + 'new154.fits' + +Changing ``ad.filename`` also changes the filename in the ``ad.path``. The +sequence above will write 'new154.fits' not in the current directory but +rather to the directory that is specified in ``ad.path``. + +.. todo:: Need to update the code to change the filename, this seems a little + sus to me. + + Maybe introduce an "original filename" attribute that is not changed when + the filename is changed. That way, the user can always go back to the + original filename. + + Also, could have a printed note that the filename is changed. E.g., an + asterisk next to the filename value and a footnote about the meaning there. + + Will need to be in the next version, though, since this is breaking. + +.. warning:: + + :func:`ad.write` has an argument named ``filename``. Setting ``filename`` + in the call to :func:`ad.write`, as in ``ad.write(filename='new154.fits')`` + will NOT modify ``ad.filename`` or ``ad.path``. The two "filenames", one a + method argument the other a class attribute have no association to each + other. + + +Updating an existing file on disk +---------------------------------- + +Updating an existing file on disk requires explicitly allowing overwrite. + +If you have not written 'new154.fits' to disk yet (from previous section) + +.. doctest:: + + >>> ad = astrodata.open('../playdata/N20170609S0154.fits') + >>> ad.write('new154.fits', overwrite=True) + +Now let's open 'new154.fits', and write to it + +.. doctest:: + + >>> adnew = astrodata.open('new154.fits') + >>> adnew.write(overwrite=True) + + +A note on FITS header keywords +------------------------------ + +.. _fitskeys: + +When writing an |AstroData| object as a FITS file, it is necessary to add or +update header keywords to represent some of the internally-stored information. +Any extensions that did not originally belong to a given |AstroData| instance +will be assigned new ``EXTVER`` keywords to avoid conflicts with existing +extensions, and the internal ``WCS`` is converted to the appropriate FITS keywords. +Note that in some cases it may not be possible for standard FITS keywords to +accurately represent the true ``WCS``. In such cases, the FITS keywords are written +as an approximation to the true ``WCS``, together with an additional keyword + +.. code::python + + FITS-WCS= 'APPROXIMATE' / FITS WCS is approximate + +to indicate this. The accurate ``WCS`` is written as an additional FITS extension with +``EXTNAME='WCS'`` that AstroData will recognize when the file is read back in. The +``WCS`` extension will not be written to disk if there is an accurate FITS +representation of the ``WCS`` (e.g., for a simple image). + + +Create New MEF Files +==================== + +A new MEF file can be created from an existing, maybe modified, file or +created from scratch (e.g., using computer-generated data/images). + +Create New Copy of MEF Files +---------------------------- + +Basic example +^^^^^^^^^^^^^ + +As seen above, a MEF file can be opened with |astrodata|, the |AstroData| +object can be modified (or not), and then written back to disk under a +new name. + +.. doctest:: + + >>> ad = astrodata.open('../playdata/N20170609S0154.fits') + ... optional modifications here ... + >>> ad.write('newcopy.fits') + + +Needing true copies in memory +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Sometimes it is a true copy in memory that is needed. This is not specific +to MEF. In Python, doing something like ``adnew = ad`` does not create a +new copy of the AstrodData object; it just gives it a new name. If you +modify ``adnew`` you will be modifying ``ad`` too. They point to the same +block of memory. + +To create a true independent copy, the ``deepcopy`` utility needs to be used. :: + +.. doctest:: + + >>> from copy import deepcopy + >>> ad = astrodata.open('../playdata/N20170609S0154.fits') + >>> adcopy = deepcopy(ad) + +.. warning:: + ``deepcopy`` can cause memory problems, depending on the size of the data + being copied as well as the size of objects it references. If you notice + your memory becoming large/full, consider breaking down the copy into + smaller pieces and f. + + +Create New MEF Files from Scratch +--------------------------------- +Before one creates a new MEF file on disk, one has to create the AstroData +object that will be eventually written to disk. The |AstroData| object +created also needs to know that it will have to be written using the MEF +format. This is fortunately handled fairly transparently by |astrodata|. + +The key to associating the FITS data to the |AstroData| object is simply to +create the |AstroData| object from :mod:`astropy.io.fits` header objects. Those +will be recognized by |astrodata| as FITS and the constructor for FITS will be +used. The user does not need to do anything else special. Here is how it is +done. + +Create a MEF with basic header and data array set to zeros +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. doctest:: + + >>> import numpy as np + >>> from astropy.io import fits + + >>> phu = fits.PrimaryHDU() + + >>> pixel_data = np.zeros((100,100)) + + >>> hdu = fits.ImageHDU() + >>> hdu.data = pixel_data + + >>> ad = astrodata.create(phu) + >>> ad.append(hdu, name='SCI') + + # Or another way to do the last two blocks: + >>> hdu = fits.ImageHDU(data=pixel_data, name='SCI') + >>> ad = astrodata.create(phu, [hdu]) + + # Finally write to a file. + >>> ad.write('new_MEF.fits') + +Associate a pixel array with a science pixel array +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Only main science (labed as ``SCI``) pixel arrays are added an +|AstroData| object. It not uncommon to have pixel information associated with +those main science pixels, such as pixel masks, variance arrays, or other +information. + +These pixel arrays are added to specific slice of the astrodata object they are +associated with. + +Building on the |AstroData| object we created in the previously, we can add a +new pixel array directly to the slice(s) of the |AstroData| object it should be +associated with by assigning it as an attribute of the object. + +.. doctest:: + + >>> extra_data = np.ones((100, 100)) + >>> ad[0].EXTRADATA = extra_data + +When the file is written to disk as a MEF, an extension will be created with +``EXTNAME = EXTRADATA`` and an ``EXTVER`` that matches the slice's ``EXTVER``, +in this case is would be ``1``. + +.. todo:: Need to revisit below after working on tables section + +Represent a table as a FITS binary table in an ``AstroData`` object +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +One first needs to create a table, either an :class:`astropy.table.Table` +or a :class:`~astropy.io.fits.BinTableHDU`. See the |astropy| documentation +on tables and this manual's :ref:`section ` dedicated to tables for +more information. + +In the first example, we assume that ``my_astropy_table`` is +a :class:`~astropy.table.Table` ready to be attached to an |AstroData| +object. (Warning: we have not created ``my_astropy_table`` therefore the +example below will not run, though this is how it would be done.) + +.. doctest:: + + >>> phu = fits.PrimaryHDU() + >>> ad = astrodata.create(phu) + + >>> astrodata.add_header_to_table(my_astropy_table) + >>> ad.append(my_astropy_table, name='SMAUG') + + +In the second example, we start with a FITS :class:`~astropy.io.fits.BinTableHDU` +and attach it to a new |AstroData| object. (Again, we have not created +``my_fits_table`` so the example will not run.) :: + + >>> phu = fits.PrimaryHDU() + >>> ad = astrodata.create(phu) + >>> ad.append(my_fits_table, name='DROGON') + +As before, once the |AstroData| object is constructed, the ``ad.write()`` +method can be used to write it to disk as a MEF file. diff --git a/_sources/manuals/usermanual/structure.rst.txt b/_sources/manuals/usermanual/structure.rst.txt new file mode 100644 index 00000000..5c6ddfaf --- /dev/null +++ b/_sources/manuals/usermanual/structure.rst.txt @@ -0,0 +1,248 @@ +.. structure.rst + +.. _structure: + +******************** +The AstroData Object +******************** + +The |AstroData| object represents the data and metadata of a single file on +disk. As of this version, |AstroData| has a default implementation supporting +the FITS file format. If you wish to extend |AstroData| to support other file +formats, see :ref:`astrodata`. + +The internal structure of the |AstroData| object makes uses of astropy's +:class:`~astropy.nddata.NDData`, :mod:`~astropy.table`, and +:class:`~astropy.io.fits.Header`, the latter simply because it is a convenient +ordered dictionary. + +Example location +---------------- + +The examples in this section can be found here: :ref:`user_structure_examples`. + +Walkthrough +----------- + +Global vs Extension-specific +============================ + +At the top level, the |AstroData| structure is divided in two types of +information. In the first category, there is the information that applies to +the data globally, for example the information that would be stored in a FITS +Primary Header Unit, a table from a catalog that matches the RA and DEC of the +field, etc. In the second category, there is the information specific to +individual science pixel extensions, for example the gain of the amplifier, the +data themselves, the error on those data, etc. + +.. todo:: Turn the below code blocks into an example + +The composition and amount of information depends on the contents of the file +itself. This information varies dramatically between observatories, so ensure +that you have characterized your data well. Accessing the contents of an +|AstroData| object is done through the :meth:`~astrodata.AstroData.info` +method. + +.. testsetup:: + + import os + + + example_fits_file = os.path.dirname(__file__) + example_fits_file = os.path.join( + example_fits_file, + "../../examples/data/example_mef_file.fits" + ) + +.. code::python + + >>> import astrodata + + # You can find the example file in the examples/data directory. + >>> ad = astrodata.from_file(example_fits_file) + >>> ad.info() + + Filename: example_mef_file.fits + Tags: MY_TAG1 MY_TAG2 MY_TAG3 + + Pixels Extensions + Index Content Type Dimensions Format + [ 0] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + [ 1] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + [ 2] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + [ 3] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + + Other Extensions + Type Dimensions + .REFERENCE Table (245, 16) + +.. + Let us look at an example. The :meth:`~astrodata.AstroData.info` method shows + the content of the |AstroData| object and its organization, from the user's + perspective.:: + + >>> import astrodata + >>> import gemini_instruments + + >>> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits') + >>> ad.info() + Filename: N20170609S0154_varAdded.fits + Tags: ACQUISITION GEMINI GMOS IMAGE NORTH OVERSCAN_SUBTRACTED OVERSCAN_TRIMMED + PREPARED SIDEREAL + + Pixels Extensions + Index Content Type Dimensions Format + [ 0] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) uint16 + .OBJCAT Table (6, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + [ 1] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) uint16 + .OBJCAT Table (8, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + [ 2] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) uint16 + .OBJCAT Table (7, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + [ 3] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) uint16 + .OBJCAT Table (5, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + + Other Extensions + Type Dimensions + .REFCAT Table (245, 16) + + +The "Pixel Extensions" contain the pixel data (in this case, something specific +to our data type). Each extension is represented individually in a list +(0-indexed like all Python lists). The science pixel data, its associated +metadata (extension header), and any other pixel or table extensions directly +associated with that science pixel data are stored in a |NDAstroData| object +which subclasses astropy's |NDData|. An |AstroData| extension is accessed like +any list: ``ad[0]`` will return the first image. To access the science pixels, +one uses ``ad[0].data``; for the object mask of the first extension, +``ad[0].OBJMASK``; etc. + +.. todo:: incorporate this into the example + In the example above, the "Other Extensions" at the bottom of the + :meth:`~astrodata.AstroData.info` display contains a ``REFCAT`` table which in + this case is a list of stars from a catalog that overlaps the field of view + covered by the pixel data. The "Other Extensions" are global extensions. They + are not attached to any pixel extension in particular. To access a global + extension one simply uses the name of that extension: ``ad.REFCAT``. + + +Organization of Global Information +================================== + +All the global information can be accessed as attributes of the |AstroData| +object. The global headers, or Primary Header Unit (PHU), is stored in the +``phu`` attribute as an :class:`astropy.io.fits.Header`. + +.. todo:: Put in a link to a good gemini example below where it says + GEMINI_EXAMPLE + +Any global tables are stored in the private attribute ``_tables``. For example, +if we had a ``REFCAT`` global table as part of our data (see example +:needs_replacement:`GEMINI_EXAMPLE` a Python dictionary with the name (eg. +"REFCAT") as the key. All tables are stored as :class:`astropy.table.Table`. +Access to those table is done using the key directly as if it were a normal +attribute, eg. ``ad.REFCAT``. Header information for the table, if read in +from a FITS table, is stored in the ``meta`` attribute of the +:class:`astropy.table.Table`, eg. ``ad.REFCAT.meta['header']``. It is for +information only, it is not used. + + +Organization of the Extension-specific Information +================================================== + +The pixel data are stored in the |AstroData| attribute ``nddata`` as a list +of |NDAstroData| object. The |NDAstroData| object is a subclass of astropy +|NDData| and it is fully compatible with any function expecting an |NDData| as +input. The pixel extensions are accessible through slicing, eg. ``ad[0]`` or +even ``ad[0:2]``. A slice of an AstroData object is an AstroData object, and +all the global attributes are kept. For example:: + + >>> ad[0].info() + Filename: N20170609S0154_varAdded.fits + Tags: ACQUISITION GEMINI GMOS IMAGE NORTH OVERSCAN_SUBTRACTED OVERSCAN_TRIMMED + PREPARED SIDEREAL + + Pixels Extensions + Index Content Type Dimensions Format + [ 0] science NDAstroData (2112, 256) float32 + .variance ndarray (2112, 256) float32 + .mask ndarray (2112, 256) uint16 + .OBJCAT Table (6, 43) n/a + .OBJMASK ndarray (2112, 256) uint8 + + Other Extensions + Type Dimensions + .REFCAT Table (245, 16) + +Note how ``REFCAT`` is still present. + +The science data is accessed as ``ad[0].data``, the variance as ``ad[0].variance``, +and the data quality plane as ``ad[0].mask``. Those familiar with astropy +|NDData| will recognize the structure "data, error, mask", and will notice +some differences. First |AstroData| uses the variance for the error plane, not +the standard deviation. Another difference will be evident only when one looks +at the content of the mask. |NDData| masks contain booleans, |AstroData| masks +are ``uint16`` bit mask that contains information about the type of bad pixels +rather than just flagging them a bad or not. Since ``0`` is equivalent to +``False`` (good pixel), the |AstroData| mask is fully compatible with the +|NDData| mask. + +Header information for the extension is stored in the |NDAstroData| ``meta`` +attribute. All table and pixel extensions directly associated with the +science extension are also stored in the ``meta`` attribute. + +Technically, an extension header is located in ``ad.nddata[0].meta['header']``. +However, for obviously needed convenience, the normal way to access that header +is ``ad[0].hdr``. + +Tables and pixel arrays associated with a science extension are +stored in ``ad.nddata[0].meta['other']`` as a dictionary keyed on the array +name, eg. ``OBJCAT``, ``OBJMASK``. As it is for global tables, astropy tables +are used for extension tables. The extension tables and extra pixel arrays are +accessed, like the global tables, by using the table name rather than the long +format, for example ``ad[0].OBJCAT`` and ``ad[0].OBJMASK``. + +When reading a FITS Table, the header information is stored in the +``meta['header']`` of the table, eg. ``ad[0].OBJCAT.meta['header']``. That +information is not used, it is simply a place to store what was read from disk. + +The header of a pixel extension directly associated with the science extension +should match that of the science extension. Therefore such headers are not +stored in |AstroData|. For example, the header of ``ad[0].OBJMASK`` is the +same as that of the science, ``ad[0].hdr``. + +The world coordinate system (WCS) is stored internally in the ``wcs`` attribute +of the |NDAstroData| object. It is constructed from the header keywords when +the FITS file is read from disk, or directly from the ``WCS`` extension if +present (see :ref:`the next chapter `). If the WCS is modified (for +example, by refining the pointing or attaching a more accurate wavelength +calibration), the FITS header keywords are not updated and therefore they should +never be used to determine the world coordinates of any pixel. These keywords are +only updated when the object is written to disk as a FITS file. The WCS is +retrieved as follows: ``ad[0].wcs``. + + +.. todo:: Need to rephrase or replace the following subsection + A Note on Memory Usage + ====================== + + When an file is opened, the headers are loaded into memory, but the pixels + are not. The pixel data are loaded into memory only when they are first + needed. This is not real "memory mapping", more of a delayed loading. This + is useful when someone is only interested in the metadata, especially when + the files are very large. diff --git a/_sources/manuals/usermanual/tables.rst.txt b/_sources/manuals/usermanual/tables.rst.txt new file mode 100644 index 00000000..20316d38 --- /dev/null +++ b/_sources/manuals/usermanual/tables.rst.txt @@ -0,0 +1,228 @@ +.. tables.rst + +.. _tables: + +********** +Table Data +********** +**Try it yourself** + +Download the data package (:ref:`datapkg`) if you wish to follow along and run the +examples. Then :: + + $ cd /ad_usermanual/playground + $ python + +Then import core astrodata and the Gemini astrodata configurations. :: + + >>> import astrodata + >>> import gemini_instruments + +Tables and Astrodata +==================== +Tables are stored as ``astropy.table`` ``Table`` class. FITS tables too +are represented in Astrodata as ``Table`` and FITS headers are stored in +the NDAstroData `.meta` attribute. Most table access should be done +through the ``Table`` interface. The best reference on ``Table`` is the +Astropy documentation itself. In this chapter we covers some common +examples to get the reader started. + +The ``astropy.table`` documentation can be found at: ``_ + + +Operate on a Table +================== + +Let us open a file with tables. Some tables are associated with specific +extensions, and there is one table that is global to the `AstroData` object. + +:: + + >>> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits') + >>> ad.info() + +To access the global table named ``REFCAT``:: + + >>> ad.REFCAT + +To access the ``OBJCAT`` table in the first extension :: + + >>> ad[0].OBJCAT + + +Column and Row Operations +------------------------- +Columns are named. Those names are used to access the data as columns. +Rows are not names and are simply represented as a sequential list. + +Read columns and rows ++++++++++++++++++++++ +To get the names of the columns present in the table:: + + >>> ad.REFCAT.colnames + ['Id', 'Cat_Id', 'RAJ2000', 'DEJ2000', 'umag', 'umag_err', 'gmag', + 'gmag_err', 'rmag', 'rmag_err', 'imag', 'imag_err', 'zmag', 'zmag_err', + 'filtermag', 'filtermag_err'] + +Then it is easy to request the values for specific columns:: + + >>> ad.REFCAT['zmag'] + >>> ad.REFCAT['zmag', 'zmag_err'] + +To get the content of a specific row, row 10 in this case:: + + >>> ad.REFCAT[9] + +To get the content of a specific row(s) from a specific column(s):: + + >>> ad.REFCAT['zmag'][4] + >>> ad.REFCAT['zmag'][4:10] + >>> ad.REFCAT['zmag', 'zmag_err'][4:10] + +Change values ++++++++++++++ +Assigning new values works in a similar way. When working on multiple elements +it is important to feed a list that matches in size with the number of elements +to replace. + +:: + + >>> ad.REFCAT['imag'][4] = 20.999 + >>> ad.REFCAT['imag'][4:10] = [5, 6, 7, 8, 9, 10] + + >>> overwrite_col = [0] * len(ad.REFCAT) # a list of zeros, size = nb of rows + >>> ad.REFCAT['imag_err'] = overwrite_col + +Add a row ++++++++++ +To append a row, there is the ``add_row()`` method. The length of the row +should match the number of columns:: + + >>> new_row = [0] * len(ad.REFCAT.colnames) + >>> new_row[1] = '' # Cat_Id column is of "str" type. + >>> ad.REFCAT.add_row(new_row) + +Add a column +++++++++++++ +Adding a new column can be more involved. If you need full control, please +see the AstroPy Table documentation. For a quick addition, which might be +sufficient for your use case, we simply use the "dictionary" technique. Please +note that when adding a column, it is important to ensure that all the +elements are of the same type. Also, if you are planning to use that table +in IRAF/PyRAF, we recommend not using 64-bit types. + +:: + + >>> import numpy as np + + >>> new_column = [0] * len(ad.REFCAT) + >>> # Ensure that the type is int32, otherwise it will default to int64 + >>> # which generally not necessary. Also, IRAF 32-bit does not like it. + >>> new_column = np.array(new_column).astype(np.int32) + >>> ad.REFCAT['my_column'] = new_column + +If you are going to write that table back to disk as a FITS Bintable, then +some additional headers need to be set. Astrodata will take care of that +under the hood when the `write` method is invoked. + +:: + + >>> ad.write('myfile_with_modified_table.fits') + + +Selection and Rejection Operations +---------------------------------- +Normally, one does not know exactly where the information needed is located +in a table. Rather some sort of selection needs to be done. This can also +be combined with various calculations. We show two such examples here. + +Select a table element from criterion ++++++++++++++++++++++++++++++++++++++ + +:: + + >>> # Get the magnitude of a star selected by ID number + >>> ad.REFCAT['zmag'][ad.REFCAT['Cat_Id'] == '1237662500002005475'] + + >>> # Get the ID and magnitude of all the stars brighter than zmag 18. + >>> ad.REFCAT['Cat_Id', 'zmag'][ad.REFCAT['zmag'] < 18.] + + +Rejection and selection before statistics ++++++++++++++++++++++++++++++++++++++++++ + +:: + + >>> t = ad.REFCAT # to save typing + + >>> # The table has "NaN" values. ("Not a number") We need to ignore them. + >>> t['zmag'].mean() + nan + >>> # applying rejection of NaN values: + >>> t['zmag'][np.where(~np.isnan(t['zmag']))].mean() + 20.377306 + + + +Accessing FITS table headers directly +------------------------------------- +If for some reason you need to access the FITS table headers directly, here +is how to do it. It is very unlikely that you will need this. + +To see the FITS headers:: + + >>> ad.REFCAT.meta['header'] + >>> ad[0].OBJCAT.meta['header'] + +To retrieve a specific FITS table header:: + + >>> ad.REFCAT.meta['header']['TTYPE3'] + 'RAJ2000' + >>> ad[0].OBJCAT.meta['header']['TTYPE3'] + 'Y_IMAGE' + +To retrieve all the keyword names matching a selection:: + + >>> keynames = [key for key in ad.REFCAT.meta['header'] if key.startswith('TTYPE')] + + + +Create a Table +============== + +To create a table that can be added to an ``AstroData`` object and eventually +written to disk as a FITS file, the first step is to create an Astropy +``Table``. + +Let us first add our data to NumPy arrays, one array per column:: + + >>> import numpy as np + + >>> snr_id = np.array(['S001', 'S002', 'S003']) + >>> feii = np.array([780., 78., 179.]) + >>> pabeta = np.array([740., 307., 220.]) + >>> ratio = pabeta / feii + +Then build the table from that data:: + + >>> from astropy.table import Table + + >>> my_astropy_table = Table([snr_id, feii, pabeta, ratio], + ... names=('SNR_ID', 'FeII', 'PaBeta', 'ratio')) + + +Now we append this Astropy ``Table`` to a new ``AstroData`` object. + +:: + + >>> # Since we are going to write a FITS, we build the AstroData object + >>> # from FITS objects. + >>> from astropy.io import fits + + >>> phu = fits.PrimaryHDU() + >>> ad = astrodata.create(phu) + >>> ad.MYTABLE = my_astropy_table + >>> ad.info() + >>> ad.MYTABLE + + >>> ad.write('new_table.fits') diff --git a/_sources/manuals/usermanual/tags.rst.txt b/_sources/manuals/usermanual/tags.rst.txt new file mode 100644 index 00000000..dc502258 --- /dev/null +++ b/_sources/manuals/usermanual/tags.rst.txt @@ -0,0 +1,274 @@ +.. tags.rst + +.. _tags: + +*************** +Astrodata |Tag| +*************** + +What is an Astrodata |Tag|? +=========================== + +|Tag| is a way to describe the data in an |AstroData| object. Tags are used to +idenfity the type of |AstroData| object to be created when |open| is called. + +A |Tag| is added to an |AstroData| object by defining a function wrapped with +the :func:`~astrodata.astro_data_tag` decorator. The function must return a +:class:`~astrodata.TagSet` object, which describes the behavior of a tag. + +For example, the following function defines a tag called "RAW" + +.. testsetup:: + + from astrodata import TagSet, astro_data_tag + +.. doctest:: + + class RawAstroData(AstroData): + @astro_data_tag + def _tag_raw(self): + """Identify if this is raw data""" + if self.phu.get('PROCTYPE') == 'RAW': + return TagSet(['RAW']) + +Now, if we call |open| on a file that has a PROCTYPE keyword set to "RAW", the +|AstroData| object will have the "`RAW`" tag + + +.. doctest:: + >>> ad = astrodata.open('somefile.fits') + >>> ad.tags + {'RAW'} + +From here, these tag sets can be used to understand what the data is describing +and how best to process it. It can also contain information about the state of +processing (e.g., ``RAW`` vs ``PROCESSED``), or any important flags. + +.. _ad_tags: :ref:`../progmanual/tags.rst` + +These tags are meant to work well with FITS data, using the headers to +determine what the data is. However, they can be used with any data type that +can be described by a set of tags, as long as they are properly defined by the +developer (see ad_tags_ for more information about developing with |Tag|). + +.. + The Astrodata Tags identify the data represented in the |AstroData| object. + When a file on disk is opened with |astrodata|, the headers are inspected to + identify which specific |AstroData| class needs to be loaded, + :class:`~gemini_instruments.gmos.AstroDataGmos`, + :class:`~gemini_instruments.niri.AstroDataNiri`, etc. Based on the class the data is + associated with, a list of "tags" will be defined. The tags will tell whether the + file is a flatfield or a dark, if it is a raw dataset, or if it has been processed by the + recipe system, if it is imaging or spectroscopy. The tags will tell the + users and the system what that data is and also give some information about + the processing status. + +For some examples of tags in production code, see the |gemini_instruments| +package, which defined a number of |AstroData| derivatives used as part of the +|DRAGONS| data reduction library for reading as well as processing data. + +Using the Astrodata Tags +======================== + +**Try it yourself** + +Download the data package (:ref:`datapkg`) if you wish to follow along and run the +examples. Then :: + + $ cd /ad_usermanual/playground + $ python + +Before doing anything, you need to import |astrodata| and the Gemini instrument +configuration package (|gemini_instruments|). + +:: + + >>> import astrodata + >>> import gemini_instruments + +Let us open a Gemini dataset and see what tags we get:: + + >>> ad = astrodata.open('../playdata/N20170609S0154.fits') + >>> ad.tags + {'RAW', 'GMOS', 'GEMINI', 'NORTH', 'SIDEREAL', 'UNPREPARED', 'IMAGE', 'ACQUISITION'} + +The file we loaded is raw, GMOS North data. It is a 2D image and it is an +acquisition image, not a science observation. The "UNPREPARED" tag indicates +that the file has never been touched by the Recipe System which runs a +"prepare" primitive as the first step of each recipe. + +Let's try another :: + + >>> ad = astrodata.open('../playdata/N20170521S0925_forStack.fits') + >>> ad.tags + {'GMOS', 'GEMINI', 'NORTH', 'SIDEREAL', 'OVERSCAN_TRIMMED', 'IMAGE', + 'OVERSCAN_SUBTRACTED', 'PREPARED'} + +This file is a science GMOS North image. It has been processed by the +Recipe System. The overscan level has been subtracted and the overscan section +has been trimmed away. The tags do NOT include all the processing steps. Rather, +at least from the time being, it focuses on steps that matter when associating +calibrations. + +The tags can be used when coding. For example + +.. doctest:: + + >>> if 'GMOS' in ad.tags: + ... print('I am GMOS') + ... else: + ... print('I am these instead:', ad.tags) + +And + +.. doctest:: + + >>> if {'IMAGE', 'GMOS'}.issubset(ad.tags): + ... print('I am a GMOS Image.') + +.. todo:: + + Below needs to be ported back to DRAGONS documentation since it is a + part of gempy (I think, definitely a part of DRAGONS no matter what) + + Using typewalk + + In DRAGONS, there is a convenience tool that will list the Astrodata tags + for all the FITS file in a directory. + + To try it, from the shell, not Python, go to the "playdata" directory and + run typewalk + + .. code-block:: console + + % cd /ad_usermanual/playdata + % typewalk + + directory: /data/workspace/ad_usermanual/playdata + N20170521S0925_forStack.fits ...... (GEMINI) (GMOS) (IMAGE) (NORTH) (OVERSCAN_SUBTRACTED) (OVERSCAN_TRIMMED) (PREPARED) (SIDEREAL) + N20170521S0926_forStack.fits ...... (GEMINI) (GMOS) (IMAGE) (NORTH) (OVERSCAN_SUBTRACTED) (OVERSCAN_TRIMMED) (PREPARED) (PROCESSED) (PROCESSED_SCIENCE) (SIDEREAL) + N20170609S0154.fits ............... (ACQUISITION) (GEMINI) (GMOS) (IMAGE) (NORTH) (RAW) (SIDEREAL) (UNPREPARED) + N20170609S0154_varAdded.fits ...... (ACQUISITION) (GEMINI) (GMOS) (IMAGE) (NORTH) (OVERSCAN_SUBTRACTED) (OVERSCAN_TRIMMED) (PREPARED) (SIDEREAL) + estgsS20080220S0078.fits .......... (GEMINI) (GMOS) (LONGSLIT) (LS) (PREPARED) (PROCESSED) (PROCESSED_SCIENCE) (SIDEREAL) (SOUTH) (SPECT) + gmosifu_cube.fits ................. (GEMINI) (GMOS) (IFU) (NORTH) (ONESLIT_RED) (PREPARED) (PROCESSED) (PROCESSED_SCIENCE) (SIDEREAL) (SPECT) + new154.fits ....................... (ACQUISITION) (GEMINI) (GMOS) (IMAGE) (NORTH) (RAW) (SIDEREAL) (UNPREPARED) + Done DataSpider.typewalk(..) + + ``typewalk`` can be used to select specific data based on tags, and even create + lists + + .. code-block::console + + % typewalk --tags RAW + directory: /data/workspace/ad_usermanual/playdata + N20170609S0154.fits ............... (ACQUISITION) (GEMINI) (GMOS) (IMAGE) (NORTH) (RAW) (SIDEREAL) (UNPREPARED) + new154.fits ....................... (ACQUISITION) (GEMINI) (GMOS) (IMAGE) (NORTH) (RAW) (SIDEREAL) (UNPREPARED) + Done DataSpider.typewalk(..) + + .. code-block::console + + % typewalk --tags RAW -o rawfiles.lis + % cat rawfiles.lis + # Auto-generated by typewalk, vv2.0 (beta) + # Written: Tue Mar 6 13:06:06 2018 + # Qualifying types: RAW + # Qualifying logic: AND + # ----------------------- + //data/tutorials/ad_usermanual/playdata/N20170609S0154.fits + //data/tutorials/ad_usermanual/playdata/new154.fits + + + +Creating New Astrodata Tags [Advanced Topic] +============================================ + +The |ProgManual| describes how to create new |AstroData| classes for new +instruments (specifically, see ad_tags_). This section describes the very basic +steps for a new user to create self-defined tags. + +.. todo:: add example file. + +The content of this section is based on the example file +:needs_replacement:`EXAMPLE FILE`. That file can be used as a full reference. + +.. testsetup:: + + >>> from astrodata import AstroData, TagSet, astro_data_tag + +.. doctest:: + + >>> class MyAstroData(AstroData): + ... @astro_data_tag + ... def _tag_mytag(self): + ... return TagSet(['MYTAG']) + ... + +The |astro_data_tag| decorator is used to identify the function as a tag +function. While not strictly necessary, it is recommended to use the +``_tag`` prefix in the function name to make it clear that it is a tag +function. When a file is opened using |open|, the |AstroData| class will +automatically call all the tag functions to determine the tags for the +|AstroData| object, and then determine if the file being opened is +appropriately tagged for the |AstroData| class. If it is not, the class is +not used to load in the object and its data; otherwise, it attempts to resolve +all known |AstroData| types to construct the appropriate instance. + +|AstroData| only knows of *registered* |AstroData| class types. To register our +class, we use |factory|: + +.. doctest:: + >>> import astrodata.factory as factory + >>> factory.addClass(MyAstroData) + >>> print(factory.getClasses()) + [] + +We now see our class is registered, and can use |open| to open a file that has +the identifying tag: + +.. doctest:: + + # Fake FITS file with a MYTAG keyword + >>> ad = astrodata.open('mytag.fits') + >>> ad.tags + {'MYTAG'} + + # Create one from scratch with the MYTAG keyword + >>> from astrodata import create_from_scratch + >>> from astropy.io import fits + >>> phu = fits.PrimaryHDU(header={'MYTAG': True}).header + >>> ad = create_from_scratch(phu) + >>> print(ad.tags) + {'MYTAG'} + >>> type(ad) + + + +The tag function looks at the provided headers and if the keyword "OBSTYPE" is +set to "ARC", the tags "ARC" and "CAL" (for calibration) will be assigned to +the |AstroData| object. + +.. warning:: + |Tag| functionality is primarily designed with FITS files in mind. If you + are extending |AstroData| to work with other data types, you will need to + define your own tag functions that specifically handle resolving tags for + that file type. + + This does **not** mean that you cannot use |AstroData| with other data + types, or that it is especially difficult. It just means that you will need + to define your own tag functions in such a way that they do not use, e.g., + ``self.phu`` if no such concept/equivalent exists in your desired file + type. + +A whole suite of such tag functions is needed to fully characterize all +types of data an instrument can produce. |gemini_instruments| is an +example of a package defining a number of |AstroData| types that use the +tag system to automaticlaly and precisely identify the specific instrument +used to produce the data, and to process it accordingly. + +Tags should be exact and precise. For quantities and values that are +not so well defined (for example, the type of observation), descriptors +are used. For more information about descriptors, see the section on +:ref:`headers`. + +For more information on creating and working with Tags, as well as developing +with/for |astrodata|, see the |ProgManual|. diff --git a/_static/_sphinx_javascript_frameworks_compat.js b/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 00000000..81415803 --- /dev/null +++ b/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 00000000..0d16a3f5 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: none; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css new file mode 100644 index 00000000..c718cee4 --- /dev/null +++ b/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff b/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff2 b/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff b/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff2 b/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/_static/css/fonts/fontawesome-webfont.eot b/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/_static/css/fonts/fontawesome-webfont.svg b/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/css/fonts/fontawesome-webfont.ttf b/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/_static/css/fonts/fontawesome-webfont.woff b/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/_static/css/fonts/fontawesome-webfont.woff2 b/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/_static/css/fonts/lato-bold-italic.woff b/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff differ diff --git a/_static/css/fonts/lato-bold-italic.woff2 b/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/_static/css/fonts/lato-bold.woff b/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/_static/css/fonts/lato-bold.woff differ diff --git a/_static/css/fonts/lato-bold.woff2 b/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff2 differ diff --git a/_static/css/fonts/lato-normal-italic.woff b/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff differ diff --git a/_static/css/fonts/lato-normal-italic.woff2 b/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/_static/css/fonts/lato-normal.woff b/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/_static/css/fonts/lato-normal.woff differ diff --git a/_static/css/fonts/lato-normal.woff2 b/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff2 differ diff --git a/_static/css/theme.css b/_static/css/theme.css new file mode 100644 index 00000000..19a446a0 --- /dev/null +++ b/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/dark_mode_css/custom.css b/_static/dark_mode_css/custom.css new file mode 100644 index 00000000..989c2ada --- /dev/null +++ b/_static/dark_mode_css/custom.css @@ -0,0 +1,77 @@ +.wy-side-nav-search input[type='text'] { + border-radius: 3px; +} + +input[type='color'], +input[type='date'], +input[type='datetime-local'], +input[type='datetime'], +input[type='email'], +input[type='month'], +input[type='number'], +input[type='password'], +input[type='search'], +input[type='tel'], +input[type='text'], +input[type='time'], +input[type='url'], +input[type='week'] { + box-shadow: none; +} + +.theme-switcher { + border-radius: 50%; + position: fixed; + right: 1.6em; + bottom: 1.4em; + z-index: 3; + border: none; + height: 2.2em; + width: 2.2em; + background-color: #fcfcfc; + font-size: 20px; + -webkit-box-shadow: 0px 3px 14px 4px rgba(0, 0, 0, 0.62); + box-shadow: 0px 3px 14px 4px rgba(0, 0, 0, 0.62); + color: #404040; + transition: all 0.3s ease-in-out; +} + +.wy-nav-content a, +.wy-nav-content a:visited { + color: #3091d1; +} + +body, +.wy-nav-content-wrap, +.wy-nav-content, +.section, +.highlight, +.rst-content div[class^='highlight'], +.wy-nav-content a, +.btn-neutral, +.btn, +footer, +.wy-nav-side, +.wy-menu-vertical li, +.wy-menu-vertical a, +.wy-side-nav-search .wy-dropdown, +.wy-side-nav-search a, +.wy-side-nav-search input, +html.writer-html4 .rst-content dl:not(.docutils) > dt, +html.writer-html5 + .rst-content + dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) + > dt, +.rst-content code, +.rst-content tt, +html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list) > dt, +html.writer-html5 + .rst-content + dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) + dl:not(.field-list) + > dt, +code, +.rst-content code.xref, +.rst-content tt.xref { + transition: all 0.2s ease-in-out; +} diff --git a/_static/dark_mode_css/dark.css b/_static/dark_mode_css/dark.css new file mode 100644 index 00000000..e863889c --- /dev/null +++ b/_static/dark_mode_css/dark.css @@ -0,0 +1,520 @@ +:root { + --dark-text-color: #c1c1c1; + --dark-link-color: #249ee8; +} + +html[data-theme="dark"] body { + color: #bfbfbf; +} + +html[data-theme="dark"] .wy-nav-content-wrap { + background-color: #101010; +} + +html[data-theme="dark"] .wy-nav-content { + background-color: #141414; +} + +html[data-theme="dark"] .section { + color: var(--dark-text-color); +} + +html[data-theme="dark"] .highlight { + background-color: #17181c; +} + +html[data-theme="dark"] .highlight .nn { + color: var(--dark-text-color); +} + +html[data-theme="dark"] .highlight .nb { + color: #8bb8df; +} + +html[data-theme="dark"] .highlight .nv { + color: #40ffff; +} + +html[data-theme="dark"] .highlight .kn, +html[data-theme="dark"] .highlight .kc, +html[data-theme="dark"] .highlight .k { + color: #41c2ea; +} + +html[data-theme="dark"] .highlight .s1, +html[data-theme="dark"] .highlight .s2 { + color: #b3e87f; +} + +html[data-theme="dark"] .highlight .nt { + color: #ccb350; +} + +html[data-theme="dark"] .highlight .c1 { + color: #686868; +} + +html[data-theme="dark"] .highlight .hll { + background-color: #002c4d; +} + +html[data-theme="dark"] .rst-content div[class^="highlight"] { + border-color: #1a1a1a; +} + +html[data-theme="dark"] .wy-nav-content a, +html[data-theme="dark"] .wy-nav-content a:visited { + color: var(--dark-link-color); +} + +html[data-theme="dark"] .btn-neutral { + background-color: #17181c !important; +} + +html[data-theme="dark"] .btn-neutral:hover { + background-color: #101114 !important; +} + +html[data-theme="dark"] .btn-neutral:visited { + color: #c1c1c1 !important; +} + +html[data-theme="dark"] .btn { + box-shadow: none; +} + +html[data-theme="dark"] footer { + color: #bdbdbd; +} + +html[data-theme="dark"] .wy-nav-side { + background-color: #0d0d0d; +} + +html[data-theme="dark"] .wy-menu-vertical li.current { + background-color: #141414; +} + +html[data-theme="dark"] .wy-menu-vertical li.current > a, +html[data-theme="dark"] .wy-menu-vertical li.on a { + background-color: #141415; + color: var(--dark-text-color); +} + +html[data-theme="dark"] .wy-menu-vertical li.toctree-l1.current > a, +html[data-theme="dark"] .wy-menu-vertical li.current a { + border-color: #0b0c0d; +} + +html[data-theme="dark"] .wy-menu-vertical li.current a { + color: #bbb; +} + +html[data-theme="dark"] .wy-menu-vertical li.current a:hover { + background-color: #222; +} + +html[data-theme="dark"] .wy-menu-vertical a:hover, +html[data-theme="dark"] .wy-menu-vertical li.current > a:hover, +html[data-theme="dark"] .wy-menu-vertical li.on a:hover { + background-color: #1e1e1e; +} + +html[data-theme="dark"] .wy-menu-vertical li.toctree-l2.current > a, +html[data-theme="dark"] + .wy-menu-vertical + li.toctree-l2.current + li.toctree-l3 + > a { + background-color: #18181a; +} + +html[data-theme="dark"] .wy-side-nav-search { + background-color: #0b152d; +} + +html[data-theme="dark"] .wy-side-nav-search .wy-dropdown > a, +html[data-theme="dark"] .wy-side-nav-search > a { + color: #ddd; +} + +html[data-theme="dark"] .wy-side-nav-search input[type="text"] { + border-color: #111; + background-color: #141414; + color: var(--dark-text-color); +} + +html[data-theme="dark"] .theme-switcher { + background-color: #0b0c0d; + color: var(--dark-text-color); +} + +html[data-theme="dark"].writer-html4 .rst-content dl:not(.docutils) > dt, +html[data-theme="dark"].writer-html5 + .rst-content + dl[class]:not(.option-list):not(.field-list):not(.footnote):not( + .glossary + ):not(.simple) + > dt { + background-color: #0b0b0b; + color: #007dce; + border-color: #282828; +} + +html[data-theme="dark"] .rst-content code, +html[data-theme="dark"] .rst-content tt { + color: var(--dark-text-color); +} + +html[data-theme="dark"].writer-html4 + .rst-content + dl:not(.docutils) + dl:not(.field-list) + > dt, +html[data-theme="dark"].writer-html5 + .rst-content + dl[class]:not(.option-list):not(.field-list):not(.footnote):not( + .glossary + ):not(.simple) + dl:not(.field-list) + > dt { + background-color: #0f0f0f; + color: #959595; + border-color: #2b2b2b; +} + +html[data-theme="dark"] .rst-content code, +html[data-theme="dark"] .rst-content tt, +html[data-theme="dark"] code { + background-color: #2d2d2d; + border-color: #1c1c1c; +} + +html[data-theme="dark"] .rst-content code.xref, +html[data-theme="dark"] .rst-content tt.xref, +html[data-theme="dark"] a .rst-content code, +html[data-theme="dark"] a .rst-content tt { + color: #cecece; +} + +html[data-theme="dark"] .rst-content .hint, +html[data-theme="dark"] .rst-content .important, +html[data-theme="dark"] .rst-content .tip, +html[data-theme="dark"] .rst-content .wy-alert-success.admonition, +html[data-theme="dark"] .rst-content .wy-alert-success.admonition-todo, +html[data-theme="dark"] .rst-content .wy-alert-success.attention, +html[data-theme="dark"] .rst-content .wy-alert-success.caution, +html[data-theme="dark"] .rst-content .wy-alert-success.danger, +html[data-theme="dark"] .rst-content .wy-alert-success.error, +html[data-theme="dark"] .rst-content .wy-alert-success.note, +html[data-theme="dark"] .rst-content .wy-alert-success.seealso, +html[data-theme="dark"] .rst-content .wy-alert-success.warning, +html[data-theme="dark"] .wy-alert.wy-alert-success { + background-color: #00392e; +} + +html[data-theme="dark"] .rst-content .hint .admonition-title, +html[data-theme="dark"] .rst-content .hint .wy-alert-title, +html[data-theme="dark"] .rst-content .important .admonition-title, +html[data-theme="dark"] .rst-content .important .wy-alert-title, +html[data-theme="dark"] .rst-content .tip .admonition-title, +html[data-theme="dark"] .rst-content .tip .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-success.admonition-todo + .admonition-title, +html[data-theme="dark"] + .rst-content + .wy-alert-success.admonition-todo + .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-success.admonition + .admonition-title, +html[data-theme="dark"] + .rst-content + .wy-alert-success.admonition + .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-success.attention + .admonition-title, +html[data-theme="dark"] + .rst-content + .wy-alert-success.attention + .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-success.caution + .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-success.caution .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-success.danger .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-success.danger .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-success.error .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-success.error .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-success.note .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-success.note .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-success.seealso + .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-success.seealso .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-success.warning + .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-success.warning .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert.wy-alert-success + .admonition-title, +html[data-theme="dark"] + .wy-alert.wy-alert-success + .rst-content + .admonition-title, +html[data-theme="dark"] .wy-alert.wy-alert-success .wy-alert-title { + background-color: #006a56; +} + +html[data-theme="dark"] .rst-content .admonition, +html[data-theme="dark"] .rst-content .note, +html[data-theme="dark"] .rst-content .seealso, +html[data-theme="dark"] .rst-content .wy-alert-info.admonition, +html[data-theme="dark"] .rst-content .wy-alert-info.admonition-todo, +html[data-theme="dark"] .rst-content .wy-alert-info.attention, +html[data-theme="dark"] .rst-content .wy-alert-info.caution, +html[data-theme="dark"] .rst-content .wy-alert-info.danger, +html[data-theme="dark"] .rst-content .wy-alert-info.error, +html[data-theme="dark"] .rst-content .wy-alert-info.hint, +html[data-theme="dark"] .rst-content .wy-alert-info.important, +html[data-theme="dark"] .rst-content .wy-alert-info.tip, +html[data-theme="dark"] .rst-content .wy-alert-info.warning, +html[data-theme="dark"] .wy-alert.wy-alert-info { + background-color: #002c4d; +} + +html[data-theme="dark"] .rst-content .admonition .admonition-title, +html[data-theme="dark"] .rst-content .note .admonition-title, +html[data-theme="dark"] .rst-content .note .wy-alert-title, +html[data-theme="dark"] .rst-content .seealso .admonition-title, +html[data-theme="dark"] .rst-content .seealso .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-info.admonition-todo + .admonition-title, +html[data-theme="dark"] + .rst-content + .wy-alert-info.admonition-todo + .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-info.admonition + .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-info.admonition .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-info.attention .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-info.attention .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-info.caution .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-info.caution .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-info.danger .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-info.danger .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-info.error .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-info.error .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-info.hint .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-info.hint .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-info.important .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-info.important .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-info.tip .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-info.tip .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-info.warning .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-info.warning .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert.wy-alert-info .admonition-title, +html[data-theme="dark"] .wy-alert.wy-alert-info .rst-content .admonition-title, +html[data-theme="dark"] .wy-alert.wy-alert-info .wy-alert-title { + background-color: #004a7b; +} + +html[data-theme="dark"] .rst-content .admonition-todo, +html[data-theme="dark"] .rst-content .attention, +html[data-theme="dark"] .rst-content .caution, +html[data-theme="dark"] .rst-content .warning, +html[data-theme="dark"] .rst-content .wy-alert-warning.admonition, +html[data-theme="dark"] .rst-content .wy-alert-warning.danger, +html[data-theme="dark"] .rst-content .wy-alert-warning.error, +html[data-theme="dark"] .rst-content .wy-alert-warning.hint, +html[data-theme="dark"] .rst-content .wy-alert-warning.important, +html[data-theme="dark"] .rst-content .wy-alert-warning.note, +html[data-theme="dark"] .rst-content .wy-alert-warning.seealso, +html[data-theme="dark"] .rst-content .wy-alert-warning.tip, +html[data-theme="dark"] .wy-alert.wy-alert-warning { + background-color: #533500; +} + +html[data-theme="dark"] .rst-content .admonition-todo .admonition-title, +html[data-theme="dark"] .rst-content .admonition-todo .wy-alert-title, +html[data-theme="dark"] .rst-content .attention .admonition-title, +html[data-theme="dark"] .rst-content .attention .wy-alert-title, +html[data-theme="dark"] .rst-content .caution .admonition-title, +html[data-theme="dark"] .rst-content .caution .wy-alert-title, +html[data-theme="dark"] .rst-content .warning .admonition-title, +html[data-theme="dark"] .rst-content .warning .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-warning.admonition + .admonition-title, +html[data-theme="dark"] + .rst-content + .wy-alert-warning.admonition + .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-warning.danger .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-warning.danger .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-warning.error .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-warning.error .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-warning.hint .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-warning.hint .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-warning.important + .admonition-title, +html[data-theme="dark"] + .rst-content + .wy-alert-warning.important + .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-warning.note .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-warning.note .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-warning.seealso + .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-warning.seealso .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-warning.tip .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-warning.tip .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert.wy-alert-warning + .admonition-title, +html[data-theme="dark"] + .wy-alert.wy-alert-warning + .rst-content + .admonition-title, +html[data-theme="dark"] .wy-alert.wy-alert-warning .wy-alert-title { + background-color: #803b00; +} + +html[data-theme="dark"] .rst-content .danger, +html[data-theme="dark"] .rst-content .error, +html[data-theme="dark"] .rst-content .wy-alert-danger.admonition, +html[data-theme="dark"] .rst-content .wy-alert-danger.admonition-todo, +html[data-theme="dark"] .rst-content .wy-alert-danger.attention, +html[data-theme="dark"] .rst-content .wy-alert-danger.caution, +html[data-theme="dark"] .rst-content .wy-alert-danger.hint, +html[data-theme="dark"] .rst-content .wy-alert-danger.important, +html[data-theme="dark"] .rst-content .wy-alert-danger.note, +html[data-theme="dark"] .rst-content .wy-alert-danger.seealso, +html[data-theme="dark"] .rst-content .wy-alert-danger.tip, +html[data-theme="dark"] .rst-content .wy-alert-danger.warning, +html[data-theme="dark"] .wy-alert.wy-alert-danger { + background-color: #82231a; +} + +html[data-theme="dark"] .rst-content .danger .admonition-title, +html[data-theme="dark"] .rst-content .danger .wy-alert-title, +html[data-theme="dark"] .rst-content .error .admonition-title, +html[data-theme="dark"] .rst-content .error .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-danger.admonition-todo + .admonition-title, +html[data-theme="dark"] + .rst-content + .wy-alert-danger.admonition-todo + .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-danger.admonition + .admonition-title, +html[data-theme="dark"] + .rst-content + .wy-alert-danger.admonition + .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-danger.attention + .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.attention .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.caution .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.caution .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.hint .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.hint .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert-danger.important + .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.important .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.note .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.note .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.seealso .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.seealso .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.tip .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.tip .wy-alert-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.warning .admonition-title, +html[data-theme="dark"] .rst-content .wy-alert-danger.warning .wy-alert-title, +html[data-theme="dark"] + .rst-content + .wy-alert.wy-alert-danger + .admonition-title, +html[data-theme="dark"] + .wy-alert.wy-alert-danger + .rst-content + .admonition-title, +html[data-theme="dark"] .wy-alert.wy-alert-danger .wy-alert-title { + background-color: #b9372b; +} + +html[data-theme="dark"] .wy-nav-top { + background-color: #0b152d; +} + +html[data-theme="dark"] .rst-content table.docutils thead, +html[data-theme="dark"] .rst-content table.field-list thead, +html[data-theme="dark"] .wy-table thead { + color: var(--dark-text-color); +} + +html[data-theme="dark"] + .rst-content + table.docutils:not(.field-list) + tr:nth-child(2n-1) + td, +html[data-theme="dark"] .wy-table-backed, +html[data-theme="dark"] html[data-theme="dark"] .wy-table-odd td, +html[data-theme="dark"] .wy-table-striped tr:nth-child(2n-1) td { + background-color: #181818; +} + +html[data-theme="dark"] .rst-content table.docutils td, +html[data-theme="dark"] .wy-table-bordered-all td, +html[data-theme="dark"].writer-html5 .rst-content table.docutils th, +html[data-theme="dark"] .rst-content table.docutils, +html[data-theme="dark"] .wy-table-bordered-all { + border-color: #262626; +} + +html[data-theme="dark"] .rst-content table.docutils caption, +html[data-theme="dark"] .rst-content table.field-list caption, +html[data-theme="dark"] .wy-table caption { + color: var(--dark-text-color); +} + +html[data-theme="dark"] .wy-menu-vertical li.toctree-l3.current > a, +html[data-theme="dark"] + .wy-menu-vertical + li.toctree-l3.current + li.toctree-l4 + > a { + background-color: #18181a; +} + +html[data-theme="dark"] .guilabel { + background-color: #343434; + border-color: #4d4d4d; +} diff --git a/_static/dark_mode_css/general.css b/_static/dark_mode_css/general.css new file mode 100644 index 00000000..aa614f81 --- /dev/null +++ b/_static/dark_mode_css/general.css @@ -0,0 +1,68 @@ +input[type='color'], +input[type='date'], +input[type='datetime-local'], +input[type='datetime'], +input[type='email'], +input[type='month'], +input[type='number'], +input[type='password'], +input[type='search'], +input[type='tel'], +input[type='text'], +input[type='time'], +input[type='url'], +input[type='week'] { + box-shadow: none; +} + +.theme-switcher { + border-radius: 50%; + position: fixed; + right: 1.6em; + bottom: 1.4em; + z-index: 3; + border: none; + height: 2.2em; + width: 2.2em; + background-color: #fcfcfc; + font-size: 20px; + -webkit-box-shadow: 0px 3px 14px 4px rgba(0, 0, 0, 0.62); + box-shadow: 0px 3px 14px 4px rgba(0, 0, 0, 0.62); + color: #404040; + transition: all 0.3s ease-in-out; +} + +body, +.wy-nav-content-wrap, +.wy-nav-content, +.section, +.highlight, +.rst-content div[class^='highlight'], +.wy-nav-content a, +.btn-neutral, +.btn, +footer, +.wy-nav-side, +.wy-menu-vertical li, +.wy-menu-vertical a, +.wy-side-nav-search .wy-dropdown, +.wy-side-nav-search a, +.wy-side-nav-search input, +html.writer-html4 .rst-content dl:not(.docutils) > dt, +html.writer-html5 + .rst-content + dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) + > dt, +.rst-content code, +.rst-content tt, +html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list) > dt, +html.writer-html5 + .rst-content + dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) + dl:not(.field-list) + > dt, +code, +.rst-content code.xref, +.rst-content tt.xref { + transition: all 0.2s ease-in-out; +} diff --git a/_static/dark_mode_js/default_dark.js b/_static/dark_mode_js/default_dark.js new file mode 100644 index 00000000..ea63e072 --- /dev/null +++ b/_static/dark_mode_js/default_dark.js @@ -0,0 +1,13 @@ +const loadTheme = () => { + let theme = localStorage.getItem('theme'); + + if (theme !== null) { + if (theme === 'dark') + document.documentElement.setAttribute('data-theme', 'dark'); + } else { + localStorage.setItem('theme', 'dark'); + document.documentElement.setAttribute('data-theme', 'dark'); + } +}; + +loadTheme(); diff --git a/_static/dark_mode_js/default_light.js b/_static/dark_mode_js/default_light.js new file mode 100644 index 00000000..2b19f92e --- /dev/null +++ b/_static/dark_mode_js/default_light.js @@ -0,0 +1,13 @@ +const loadTheme = () => { + let theme = localStorage.getItem('theme'); + + if (theme !== null) { + if (theme === 'dark') + document.documentElement.setAttribute('data-theme', 'dark'); + } else { + localStorage.setItem('theme', 'light'); + document.documentElement.setAttribute('data-theme', 'light'); + } +}; + +loadTheme(); diff --git a/_static/dark_mode_js/theme_switcher.js b/_static/dark_mode_js/theme_switcher.js new file mode 100644 index 00000000..8e260552 --- /dev/null +++ b/_static/dark_mode_js/theme_switcher.js @@ -0,0 +1,39 @@ +const createThemeSwitcher = () => { + let btn = document.createElement('BUTTON'); + btn.className = 'theme-switcher'; + btn.id = 'themeSwitcher'; + btn.innerHTML = + ''; + document.body.appendChild(btn); + + if (localStorage.getItem('theme') === 'dark') $('#themeMoon').hide(0); + else $('#themeSun').hide(0); +}; + +$(document).ready(() => { + createThemeSwitcher(); + $('#themeSwitcher').click(switchTheme); + + $('footer').html( + $('footer').html() + + 'Dark theme provided by MrDogeBro.' + ); +}); + +const switchTheme = () => { + if (localStorage.getItem('theme') === 'dark') { + localStorage.setItem('theme', 'light'); + document.documentElement.setAttribute('data-theme', 'light'); + + $('#themeSun').fadeOut(200, () => { + $('#themeMoon').fadeIn(200); + }); + } else { + localStorage.setItem('theme', 'dark'); + document.documentElement.setAttribute('data-theme', 'dark'); + + $('#themeMoon').fadeOut(200, () => { + $('#themeSun').fadeIn(200); + }); + } +}; diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 00000000..d06a71d7 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 00000000..976fa38d --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '0.0.0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/graphviz.css b/_static/graphviz.css new file mode 100644 index 00000000..8d81c02e --- /dev/null +++ b/_static/graphviz.css @@ -0,0 +1,19 @@ +/* + * graphviz.css + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- graphviz extension. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +img.graphviz { + border: 0; + max-width: 100%; +} + +object.graphviz { + max-width: 100%; +} diff --git a/_static/jquery.js b/_static/jquery.js new file mode 100644 index 00000000..c4c6022f --- /dev/null +++ b/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"
","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/_static/js/html5shiv.min.js b/_static/js/html5shiv.min.js new file mode 100644 index 00000000..cd1c674f --- /dev/null +++ b/_static/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/_static/js/theme.js b/_static/js/theme.js new file mode 100644 index 00000000..1fddb6ee --- /dev/null +++ b/_static/js/theme.js @@ -0,0 +1 @@ +!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 00000000..d96755fd Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 00000000..7107cec9 Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 00000000..84ab3030 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0000FF } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 00000000..7918c3fa --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,574 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 00000000..8a96c69a --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/api/astrodata.AstroData.html b/api/astrodata.AstroData.html new file mode 100644 index 00000000..2f9598b5 --- /dev/null +++ b/api/astrodata.AstroData.html @@ -0,0 +1,757 @@ + + + + + + + AstroData — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

AstroData

+
+
+class astrodata.AstroData(nddata=None, tables=None, phu=None, indices=None, is_single=False)[source]
+

Bases: object

+

Base class for the AstroData software package. It provides an interface +to manipulate astronomical data sets.

+
+
Parameters:
+
    +
  • nddata (astrodata.NDAstroData or list of astrodata.NDAstroData) – List of NDAstroData objects.

  • +
  • tables (dict[name, astropy.table.Table]) – Dict of table objects.

  • +
  • phu (astropy.io.fits.Header) – Primary header.

  • +
  • indices (list of int) – List of indices mapping the astrodata.NDAstroData objects that this +object will access to. This is used when slicing an object, then the +sliced AstroData will have the .nddata list from its parent and +access the sliced NDAstroData through this list of indices.

  • +
+
+
+

Attributes Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

data

A list of the arrays (or single array, if this is a single slice) corresponding to the science data attached to each extension.

descriptors

Returns a sequence of names for the methods that have been decorated as descriptors.

exposed

A collection of strings with the names of objects that can be accessed directly by name as attributes of this instance, and that are not part of its standard interface (i.e. data objects that have been added dynamically).

ext_tables

Return the names of the astropy.table.Table objects associated to an extension.

filename

Return the file name.

hdr

Return all headers, as a astrodata.fits.FitsHeaderCollection.

header

Deprecated header access.

id

Returns the extension identifier (1-based extension number) for sliced objects.

indices

Returns the extensions indices for sliced objects.

is_sliced

If this data provider instance represents the whole dataset, return False.

mask

A list of the mask arrays (or a single array, if this is a single slice) attached to the science data, for each extension.

nddata

Return the list of astrodata.NDAstroData objects.

orig_filename

Return the original file name (before it was modified).

path

Return the file path.

phu

Return the primary header.

shape

Return the shape of the data array for each extension as a list of shapes.

tables

Return the names of the astropy.table.Table objects associated to the top-level object.

tags

A set of strings that represent the tags defining this instance.

uncertainty

A list of the uncertainty objects (or a single object, if this is a single slice) attached to the science data, for each extension.

variance

A list of the variance arrays (or a single array, if this is a single slice) attached to the science data, for each extension.

wcs

Returns the list of WCS objects for each extension.

+

Methods Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

add(oper)

Performs inplace addition by evaluating self += operand.

append(ext[, name, header])

Adds a new top-level extension.

crop(x1, y1, x2, y2)

Crop the NDData objects given indices.

divide(oper)

Performs inplace division by evaluating self /= operand.

info()

Prints out information about the contents of this instance.

instrument()

Returns the name of the instrument making the observation.

is_settable(attr)

Return True if the attribute is meant to be modified.

load(source[, extname_parser])

Read from a file, file object, HDUList, etc.

matches_data(source)

Returns True if the class can handle the data in the source.

multiply(oper)

Performs inplace multiplication by evaluating self *= operand.

object()

Returns the name of the object being observed.

operate(operator, *args, **kwargs)

Applies a function to the main data array on each extension, replacing the data with the result.

read(source[, extname_parser])

Read from a file, file object, HDUList, etc.

reset(data[, mask, variance, check])

Sets the .data, and optionally .mask and .variance attributes of a single-extension AstroData slice.

subtract(oper)

Performs inplace subtraction by evaluating self -= operand.

table()

Return a dictionary of astropy.table.Table objects.

telescope()

Returns the name of the telescope.

update_filename([prefix, suffix, strip])

Update the "filename" attribute of the AstroData object.

write([filename, overwrite])

Write the object to disk.

+

Attributes Documentation

+
+
+data
+

A list of the arrays (or single array, if this is a single slice) +corresponding to the science data attached to each extension.

+
+ +
+
+descriptors
+

Returns a sequence of names for the methods that have been +decorated as descriptors.

+
+
Return type:
+

tuple of str

+
+
+
+ +
+
+exposed
+

A collection of strings with the names of objects that can be +accessed directly by name as attributes of this instance, and that are +not part of its standard interface (i.e. data objects that have been +added dynamically).

+

Examples

+
>>> ad[0].exposed  
+set(['OBJMASK', 'OBJCAT'])
+
+
+
+ +
+
+ext_tables
+

Return the names of the astropy.table.Table objects associated to +an extension.

+
+ +
+
+filename
+

Return the file name.

+
+ +
+
+hdr
+

Return all headers, as a astrodata.fits.FitsHeaderCollection.

+
+ +
+
+header
+

Deprecated header access. Use .hdr instead.

+
+ +
+
+id
+

Returns the extension identifier (1-based extension number) +for sliced objects.

+
+ +
+
+indices
+

Returns the extensions indices for sliced objects.

+
+ +
+
+is_sliced
+

If this data provider instance represents the whole dataset, return +False. If it represents a slice out of the whole, return True.

+
+ +
+
+mask
+

A list of the mask arrays (or a single array, if this is a single +slice) attached to the science data, for each extension.

+

For objects that miss a mask, None will be provided instead.

+
+ +
+
+nddata
+

Return the list of astrodata.NDAstroData objects.

+

If the AstroData object is sliced, this returns only the NDData +objects of the sliced extensions. And if this is a single extension +object, the NDData object is returned directly (i.e. not a list).

+
+ +
+
+orig_filename
+

Return the original file name (before it was modified).

+
+ +
+
+path
+

Return the file path.

+
+ +
+
+phu
+

Return the primary header.

+
+ +
+
+shape
+

Return the shape of the data array for each extension as a list of +shapes.

+
+ +
+
+tables
+

Return the names of the astropy.table.Table objects associated to +the top-level object.

+
+ +
+
+tags
+

A set of strings that represent the tags defining this instance.

+
+ +
+
+uncertainty
+

A list of the uncertainty objects (or a single object, if this is +a single slice) attached to the science data, for each extension.

+

The objects are instances of AstroPy’s astropy.nddata.NDUncertainty, +or None where no information is available.

+
+

See also

+
+
variance

The actual array supporting the uncertainty object.

+
+
+
+
+ +
+
+variance
+

A list of the variance arrays (or a single array, if this is a +single slice) attached to the science data, for each extension.

+

For objects that miss uncertainty information, None will be provided +instead.

+
+

See also

+
+
uncertainty

The uncertainty objects used under the hood.

+
+
+
+
+ +
+
+wcs
+

Returns the list of WCS objects for each extension.

+
+ +

Methods Documentation

+
+
+add(oper)
+

Performs inplace addition by evaluating self += operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self += operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+append(ext, name=None, header=None)[source]
+

Adds a new top-level extension.

+
+
Parameters:
+
    +
  • ext (array, astropy.nddata.NDData, astropy.table.Table, other) – The contents for the new extension. The exact accepted types depend +on the class implementing this interface. Implementations specific +to certain data formats may accept specialized types (eg. a FITS +provider will accept an astropy.io.fits.ImageHDU and extract the +array out of it).

  • +
  • name (str, optional) – A name that may be used to access the new object, as an attribute +of the provider. The name is typically ignored for top-level +(global) objects, and required for the others. If the name cannot +be derived from the metadata associated to ext, you will +have to provider one. +It can consist in a combination of numbers and letters, with the +restriction that the letters have to be all capital, and the first +character cannot be a number (“[A-Z][A-Z0-9]*”).

  • +
+
+
Returns:
+

    +
  • The same object, or a new one, if it was necessary to convert it to

  • +
  • a more suitable format for internal use.

  • +
+

+
+
Raises:
+
    +
  • TypeError – If adding the object in an invalid situation (eg. name is + None when adding to a single slice).

  • +
  • ValueError – Raised if the extension is of a proper type, but its value is + illegal somehow.

  • +
+
+
+
+ +
+
+crop(x1, y1, x2, y2)[source]
+

Crop the NDData objects given indices.

+
+
Parameters:
+
    +
  • x1 (int) – Minimum and maximum indices for the x and y axis.

  • +
  • y1 (int) – Minimum and maximum indices for the x and y axis.

  • +
  • x2 (int) – Minimum and maximum indices for the x and y axis.

  • +
  • y2 (int) – Minimum and maximum indices for the x and y axis.

  • +
+
+
+
+ +
+
+divide(oper)
+

Performs inplace division by evaluating self /= operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self /= operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+info()[source]
+

Prints out information about the contents of this instance.

+
+ +
+
+instrument()[source]
+

Returns the name of the instrument making the observation.

+
+ +
+
+is_settable(attr)[source]
+

Return True if the attribute is meant to be modified.

+
+ +
+
+classmethod load(source, extname_parser=None)
+

Read from a file, file object, HDUList, etc.

+
+ +
+
+classmethod matches_data(source) bool[source]
+

Returns True if the class can handle the data in the source.

+
+
Parameters:
+

source (list of astropy.io.fits.HDUList) – The FITS file to be read.

+
+
Returns:
+

True if the class can handle the data in the source.

+
+
Return type:
+

bool

+
+
+
+

Note

+

Typically, this method is implemented by the static method +Astrodata._matches_data or by a class method with the same signature +for subclasses.

+

If you are implementing a subclass, you should override _matches_data +instead, which is a static method that takes a single argument, the +source data, and returns a boolean.

+

If that method is not overridden, this method will call it with the +source data as argument.

+

For more information, see the documentation for the +_matches_data() and the Developer Guide.

+
+
+ +
+
+multiply(oper)
+

Performs inplace multiplication by evaluating self *= operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self *= operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+object()[source]
+

Returns the name of the object being observed.

+
+ +
+
+operate(operator, *args, **kwargs)[source]
+

Applies a function to the main data array on each extension, replacing +the data with the result. The data will be passed as the first argument +to the function.

+

It will be applied to the mask and variance of each extension, too, if +they exist.

+

This is a convenience method, which is equivalent to:

+
for ext in ad:
+    ext.data = operator(ext.data, *args, **kwargs)
+    if ext.mask is not None:
+        ext.mask = operator(ext.mask, *args, **kwargs)
+    if ext.variance is not None:
+        ext.variance = operator(ext.variance, *args, **kwargs)
+
+
+

with the additional advantage that it will work on single slices, too.

+
+
Parameters:
+
    +
  • operator (callable) – A function that takes an array (and, maybe, other arguments) +and returns an array.

  • +
  • args (optional) – Additional arguments to be passed to the operator.

  • +
  • kwargs (optional) – Additional arguments to be passed to the operator.

  • +
+
+
+

Examples

+
>>> import numpy as np
+>>> ad.operate(np.squeeze)  
+
+
+
+ +
+
+classmethod read(source, extname_parser=None)[source]
+

Read from a file, file object, HDUList, etc.

+
+ +
+
+reset(data, mask=<object object>, variance=<object object>, check=True)[source]
+

Sets the .data, and optionally .mask and .variance +attributes of a single-extension AstroData slice. This function will +optionally check whether these attributes have the same shape.

+
+
Parameters:
+
    +
  • data (ndarray) – The array to assign to the .data attribute (“SCI”).

  • +
  • mask (ndarray, optional) – The array to assign to the .mask attribute (“DQ”).

  • +
  • variance (ndarray, optional) – The array to assign to the .variance attribute (“VAR”).

  • +
  • check (bool) – If set, then the function will check that the mask and variance +arrays have the same shape as the data array.

  • +
+
+
Raises:
+
    +
  • TypeError – if an attempt is made to set the .mask or .variance attributes + with something other than an array

  • +
  • ValueError – if the .mask or .variance attributes don’t have the same shape as + .data, OR if this is called on an AD instance that isn’t a single + extension slice

  • +
+
+
+
+ +
+
+subtract(oper)
+

Performs inplace subtraction by evaluating self -= operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self -= operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+table()[source]
+

Return a dictionary of astropy.table.Table objects.

+

Notes

+

This returns a _copy_ of the tables, so modifying them will not +affect the original ones.

+
+ +
+
+telescope()[source]
+

Returns the name of the telescope.

+
+ +
+
+update_filename(prefix=None, suffix=None, strip=False)[source]
+

Update the “filename” attribute of the AstroData object.

+

A prefix and/or suffix can be specified. If strip=True, these will +replace the existing prefix/suffix; if strip=False, they will +simply be prepended/appended.

+

The current filename is broken down into its existing prefix, root, and +suffix using the ORIGNAME phu keyword, if it exists and is +contained within the current filename. Otherwise, the filename is split +at the last underscore and the part before is assigned as the root and +the underscore and part after the suffix. No prefix is assigned.

+

Note that, if strip=True, a prefix or suffix will only be stripped +if ‘’ is specified.

+
+
Parameters:
+
    +
  • prefix (str, optional) – New prefix (None => leave alone)

  • +
  • suffix (str, optional) – New suffix (None => leave alone)

  • +
  • strip (bool, optional) – Strip existing prefixes and suffixes if new ones are given?

  • +
+
+
Raises:
+

ValueError – If the filename cannot be determined

+
+
+
+ +
+
+write(filename=None, overwrite=False)[source]
+

Write the object to disk.

+
+
Parameters:
+
    +
  • filename (str, optional) – If the filename is not given, self.path is used.

  • +
  • overwrite (bool) – If True, overwrites existing file.

  • +
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.AstroDataError.html b/api/astrodata.AstroDataError.html new file mode 100644 index 00000000..375f258c --- /dev/null +++ b/api/astrodata.AstroDataError.html @@ -0,0 +1,141 @@ + + + + + + + AstroDataError — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

AstroDataError

+
+
+exception astrodata.AstroDataError[source]
+

Exception raised when there is a problem with the AstroData class.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.AstroDataMixin.html b/api/astrodata.AstroDataMixin.html new file mode 100644 index 00000000..16bb4638 --- /dev/null +++ b/api/astrodata.AstroDataMixin.html @@ -0,0 +1,199 @@ + + + + + + + AstroDataMixin — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

AstroDataMixin

+
+
+class astrodata.AstroDataMixin[source]
+

Bases: object

+

A Mixin for NDData-like classes (such as Spectrum1D) to enable +them to behave similarly to AstroData objects.

+
+
These behaviors are:
    +
  1. mask attributes are combined with bitwise, not logical, or, +since the individual bits are important.

  2. +
  3. The WCS must be a gwcs.WCS object and slicing results in +the model being modified.

  4. +
  5. There is a settable variance attribute.

  6. +
  7. Additional attributes such as OBJMASK can be extracted from +the .meta[‘other’] dict

  8. +
+
+
+

Attributes Summary

+ + + + + + + + + + + + + + + +

shape

The shape of the data.

size

The size of the data.

variance

A convenience property to access the contents of uncertainty.

wcs

The WCS of the data.

+

Attributes Documentation

+
+
+shape
+

The shape of the data.

+
+ +
+
+size
+

The size of the data.

+
+ +
+
+variance
+

A convenience property to access the contents of uncertainty.

+
+ +
+
+wcs
+

The WCS of the data. This is a gWCS object, not a FITS WCS object.

+

This is returning wcs from an inhertited class, see NDData.wcs for more +details.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.NDAstroData.html b/api/astrodata.NDAstroData.html new file mode 100644 index 00000000..7e58c474 --- /dev/null +++ b/api/astrodata.NDAstroData.html @@ -0,0 +1,337 @@ + + + + + + + NDAstroData — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

NDAstroData

+
+
+class astrodata.NDAstroData(data, uncertainty=None, mask=None, wcs=None, meta=None, unit=None, copy=False, variance=None)[source]
+

Bases: AstroDataMixin, NDArithmeticMixin, NDSlicingMixin, NDData

+

Implements NDData with all Mixins, plus some AstroData +specifics.

+

This class implements an NDData-like container that supports reading +and writing as implemented in the astropy.io.registry and also slicing +(indexing) and simple arithmetics (add, subtract, divide and multiply).

+

A very important difference between NDAstroData and NDData is that +the former attempts to load all its data lazily. There are also some +important differences in the interface (eg. .data lets you reset its +contents after initialization).

+

Documentation is provided where our class differs.

+
+

See also

+

NDData, NDArithmeticMixin, NDSlicingMixin

+
+

Examples

+

The mixins allow operation that are not possible with NDData or +NDDataBase, i.e. simple arithmetics:

+
>>> from astropy.nddata import StdDevUncertainty
+>>> import numpy as np
+>>> data = np.ones((3,3), dtype=float)
+>>> ndd1 = NDAstroData(data, uncertainty=StdDevUncertainty(data))
+>>> ndd2 = NDAstroData(data, uncertainty=StdDevUncertainty(data))
+>>> ndd3 = ndd1.add(ndd2)
+>>> ndd3.data
+array([[2., 2., 2.],
+    [2., 2., 2.],
+    [2., 2., 2.]])
+>>> ndd3.uncertainty.array
+array([[1.41421356, 1.41421356, 1.41421356],
+    [1.41421356, 1.41421356, 1.41421356],
+    [1.41421356, 1.41421356, 1.41421356]])
+
+
+

see NDArithmeticMixin for a complete list of all supported arithmetic +operations.

+

But also slicing (indexing) is possible:

+
>>> ndd4 = ndd3[1,:]
+>>> ndd4.data
+array([2., 2., 2.])
+>>> ndd4.uncertainty.array
+array([1.41421356, 1.41421356, 1.41421356])
+
+
+

See NDSlicingMixin for a description how slicing works (which +attributes) are sliced.

+

Initialize an NDAstroData instance.

+
+
Parameters:
+
    +
  • data (array-like) – The actual data. This can be a numpy array, a memmap, or a +fits.ImageHDU object.

  • +
  • uncertainty (NDUncertainty-like object, optional) – An object that represents the uncertainty of the data. If not +specified, the uncertainty will be set to None.

  • +
  • mask (array-like, optional) – An array that represents the mask of the data. If not specified, +the mask will be set to None.

  • +
  • wcs (gwcs.WCS object, optional) – The WCS of the data. If not specified, the WCS will be set to None.

  • +
  • meta (dict-like, optional) – A dictionary-like object that holds the meta data. If not +specified, the meta data will be set to None.

  • +
  • unit (astropy.units.Unit object, optional) – The unit of the data. If not specified, the unit will be set to +None.

  • +
  • copy (bool, optional) – If True, the data, uncertainty, mask, wcs, meta, and unit will be +copied. Otherwise, they will be referenced. Default is False.

  • +
  • variance (array-like, optional) – An array that represents the variance of the data. If not +specified, the variance will be set to None.

  • +
+
+
Raises:
+

ValueError – If uncertainty and variance are both specified.

+
+
+

Notes

+

The uncertainty and variance parameters are mutually exclusive.

+

Attributes Summary

+ + + + + + + + + + + + + + + + + + + + + +

T

Transpose the data.

data

An array representing the raw data stored in this instance.

mask

Get or set the mask of the data.

uncertainty

Uncertainty in the dataset, if any.

variance

A convenience property to access the contents of uncertainty, squared (as the uncertainty data is stored as standard deviation).

window

Interface to access a section of the data, using lazy access whenever possible.

+

Methods Summary

+ + + + + + + + + +

set_section(section, input_data)

Sets only a section of the data.

transpose()

Transpose the data.

+

Attributes Documentation

+
+
+T
+

Transpose the data. This is not a copy of the data.

+
+ +
+
+data
+

An array representing the raw data stored in this instance. It +implements a setter.

+
+ +
+
+mask
+

Get or set the mask of the data.

+
+ +
+
+uncertainty
+
+ +
+
+variance
+

A convenience property to access the contents of uncertainty, +squared (as the uncertainty data is stored as standard deviation).

+
+ +
+
+window
+

Interface to access a section of the data, using lazy access +whenever possible.

+
+
Returns:
+

    +
  • An instance of NDWindowing, which provides __getitem__,

  • +
  • to allow the use of square brackets when specifying the window.

  • +
  • Ultimately, an NDWindowingAstrodata instance is returned.

  • +
+

+
+
+

Examples

+
>>> ad[0].nddata.window[100:200, 100:200]  
+<NDWindowingAstrodata .....>
+
+
+
+ +

Methods Documentation

+
+
+set_section(section, input_data)[source]
+

Sets only a section of the data. This method is meant to prevent +fragmentation in the Python heap, by reusing the internal structures +instead of replacing them with new ones.

+
+
Parameters:
+
    +
  • section (slice) – The area that will be replaced

  • +
  • input_data (NDData-like instance) – This object needs to implement at least data, uncertainty, +and mask. Their entire contents will replace the data in the +area defined by section.

  • +
+
+
+

Examples

+
>>> def setup():
+...     sec = NDData(np.zeros((100,100)))
+...     ad[0].nddata.set_section(
+...         (slice(None,100),slice(None,100)),
+...         sec
+...     )
+...
+>>> setup()  
+
+
+
+ +
+
+transpose()[source]
+

Transpose the data. This is not a copy of the data.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.Section.html b/api/astrodata.Section.html new file mode 100644 index 00000000..fe6ded8c --- /dev/null +++ b/api/astrodata.Section.html @@ -0,0 +1,255 @@ + + + + + + + Section — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Section

+
+
+class astrodata.Section(*args, **kwargs)[source]
+

Bases: tuple

+

A class to handle n-dimensional sections

+

Attributes Summary

+ + + + + + + + + +

axis_dict

ndim

The number of dimensions in the section.

+

Methods Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

asIRAFsection()

Deprecated, see as_iraf_section

as_iraf_section()

Produce string of style '[x1:x2,y1:y2]' that is 1-indexed and end-inclusive

asslice([add_dims])

Return the Section object as a slice/list of slices.

contains(section)

Return True if the supplied section is entirely within self

from_shape(value)

Produce a Section object defining a given shape.

from_string(value)

The inverse of __str__, produce a Section object from a string.

is_same_size(section)

Return True if the Sections are the same size

overlap(section)

Determine whether the two sections overlap.

shift(*shifts)

Shift a section in each direction by the specified amount

+

Attributes Documentation

+
+
+axis_dict
+
+ +
+
+ndim
+

The number of dimensions in the section.

+
+ +

Methods Documentation

+
+
+asIRAFsection()[source]
+

Deprecated, see as_iraf_section

+
+ +
+
+as_iraf_section()[source]
+

Produce string of style ‘[x1:x2,y1:y2]’ that is 1-indexed +and end-inclusive

+
+ +
+
+asslice(add_dims=0)[source]
+

Return the Section object as a slice/list of slices. Higher +dimensionality can be achieved with the add_dims parameter.

+
+ +
+
+contains(section)[source]
+

Return True if the supplied section is entirely within self

+
+ +
+
+static from_shape(value)[source]
+

Produce a Section object defining a given shape.

+
+ +
+
+static from_string(value)[source]
+

The inverse of __str__, produce a Section object from a string.

+
+ +
+
+is_same_size(section)[source]
+

Return True if the Sections are the same size

+
+ +
+
+overlap(section)[source]
+

Determine whether the two sections overlap. If so, the Section +common to both is returned, otherwise None

+
+ +
+
+shift(*shifts)[source]
+

Shift a section in each direction by the specified amount

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.TagSet.html b/api/astrodata.TagSet.html new file mode 100644 index 00000000..d6b42a56 --- /dev/null +++ b/api/astrodata.TagSet.html @@ -0,0 +1,231 @@ + + + + + + + TagSet — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

TagSet

+
+
+class astrodata.TagSet(add=None, remove=None, blocked_by=None, blocks=None, if_present=None)[source]
+

Bases: TagSet

+

Named tuple that is used by tag methods to return which actions should +be performed on a tag set.

+

All the attributes are optional, and any combination of them can be used, +allowing to create complex tag structures. Read the documentation on the +tag-generating algorithm if you want to better understand the interactions.

+

The simplest TagSet, though, tends to just add tags to the global set.

+

It can be initialized by position, like any other tuple (the order of the +arguments is the one in which the attributes are listed below). It can +also be initialized by name.

+
+
+add
+

Tags to be added to the global set

+
+
Type:
+

set of str, optional

+
+
+
+ +
+
+remove
+

Tags to be removed from the global set

+
+
Type:
+

set of str, optional

+
+
+
+ +
+
+blocked_by
+

Tags that will prevent this TagSet from being applied

+
+
Type:
+

set of str, optional

+
+
+
+ +
+
+blocks
+

Other TagSets containing these won’t be applied

+
+
Type:
+

set of str, optional

+
+
+
+ +
+
+if_present
+

This TagSet will be applied only all of these tags are present

+
+
Type:
+

set of str, optional

+
+
+
+ +

Examples

+
>>> TagSet()  
+TagSet(
+    add=set(),
+    remove=set(),
+    blocked_by=set(),
+    blocks=set(),
+    if_present=set()
+)
+>>> TagSet({'BIAS', 'CAL'})  
+TagSet(
+    add={'BIAS', 'CAL'},
+    remove=set(),
+    blocked_by=set(),
+    blocks=set(),
+    if_present=set()
+)
+>>> TagSet(remove={'BIAS', 'CAL'}) 
+TagSet(
+    add=set(),
+    remove={'BIAS', 'CAL'},
+    blocked_by=set(),
+    blocks=set(),
+    if_present=set()
+)
+
+
+

Create new instance of TagSet(add, remove, blocked_by, blocks, if_present)

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.add_header_to_table.html b/api/astrodata.add_header_to_table.html new file mode 100644 index 00000000..18e37653 --- /dev/null +++ b/api/astrodata.add_header_to_table.html @@ -0,0 +1,143 @@ + + + + + + + add_header_to_table — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

add_header_to_table

+
+
+astrodata.add_header_to_table(table)[source]
+

Add a FITS header to a table.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.astro_data_descriptor.html b/api/astrodata.astro_data_descriptor.html new file mode 100644 index 00000000..dd0b29be --- /dev/null +++ b/api/astrodata.astro_data_descriptor.html @@ -0,0 +1,155 @@ + + + + + + + astro_data_descriptor — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

astro_data_descriptor

+
+
+astrodata.astro_data_descriptor(fn)[source]
+

Decorator that will mark a class method as an AstroData descriptor. +Useful to produce list of descriptors, for example.

+

If used in combination with other decorators, this one must be the +one on the top (ie. the last one applying). It doesn’t modify the +method in any other way.

+
+
Parameters:
+

fn (method) – The method to be decorated

+
+
Return type:
+

The tagged method (not a wrapper)

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.astro_data_tag.html b/api/astrodata.astro_data_tag.html new file mode 100644 index 00000000..dca3b676 --- /dev/null +++ b/api/astrodata.astro_data_tag.html @@ -0,0 +1,156 @@ + + + + + + + astro_data_tag — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

astro_data_tag

+
+
+astrodata.astro_data_tag(fn)[source]
+

Decorator that marks methods of an AstroData derived class as part of +the tag-producing system.

+

It wraps the method around a function that will ensure a consistent return +value: the wrapped method can return any sequence of sequences of strings, +and they will be converted to a TagSet. If the wrapped method +returns None, it will be turned into an empty TagSet.

+
+
Parameters:
+

fn (method) – The method to be decorated

+
+
Return type:
+

A wrapper function

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.create.html b/api/astrodata.create.html new file mode 100644 index 00000000..b794407b --- /dev/null +++ b/api/astrodata.create.html @@ -0,0 +1,145 @@ + + + + + + + create — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

create

+
+
+astrodata.create(*args, **kwargs)[source]
+

Return an AstroData object from data.

+

For implementation details, see +create_from_scratch()

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.from_file.html b/api/astrodata.from_file.html new file mode 100644 index 00000000..544a453d --- /dev/null +++ b/api/astrodata.from_file.html @@ -0,0 +1,145 @@ + + + + + + + from_file — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

from_file

+
+
+astrodata.from_file(*args, **kwargs)[source]
+

Return an AstroData object from a file.

+

For implementation details, see +get_astro_data().

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.open.html b/api/astrodata.open.html new file mode 100644 index 00000000..c2dc2816 --- /dev/null +++ b/api/astrodata.open.html @@ -0,0 +1,144 @@ + + + + + + + open — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

open

+
+
+astrodata.open(*args, **kwargs)[source]
+

Return an AstroData object from a file (deprecated, use +from_file()).

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.returns_list.html b/api/astrodata.returns_list.html new file mode 100644 index 00000000..4a90b2a2 --- /dev/null +++ b/api/astrodata.returns_list.html @@ -0,0 +1,155 @@ + + + + + + + returns_list — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

returns_list

+
+
+astrodata.returns_list(fn)[source]
+

Decorator to ensure that descriptors that should return a list (of one +value per extension) only returns single values when operating on single +slices; and vice versa.

+

This is a common case, and you can use the decorator to simplify the +logic of your descriptors.

+
+
Parameters:
+

fn (method) – The method to be decorated

+
+
Return type:
+

A function

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/astrodata.version.html b/api/astrodata.version.html new file mode 100644 index 00000000..3ccf3d0a --- /dev/null +++ b/api/astrodata.version.html @@ -0,0 +1,143 @@ + + + + + + + version — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

version

+
+
+astrodata.version()[source]
+

Return the version of astrodata.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api_short.html b/api_short.html new file mode 100644 index 00000000..42baa502 --- /dev/null +++ b/api_short.html @@ -0,0 +1,213 @@ + + + + + + + Common API for Users — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Common API for Users

+

This package contains the common API for users of the AstroData system. It +provides a single interface to the data, regardless of the format in which it +is stored.

+

If you would like a more extensive description of the astrodata package as a +user, see the User Guide. If you are interested in developing with AstroData, +see the Developer Guide.

+
+

astrodata Package

+

This package adds an abstraction layer to astronomical data by parsing the +information contained in the headers as attributes. To do so, one must subclass +astrodata.AstroData and add parse methods accordingly to the +TagSet received.

+
+

Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

add_header_to_table(table)

Add a FITS header to a table.

astro_data_descriptor(fn)

Decorator that will mark a class method as an AstroData descriptor.

astro_data_tag(fn)

Decorator that marks methods of an AstroData derived class as part of the tag-producing system.

from_file(*args, **kwargs)

Return an AstroData object from a file.

create(*args, **kwargs)

Return an AstroData object from data.

returns_list(fn)

Decorator to ensure that descriptors that should return a list (of one value per extension) only returns single values when operating on single slices; and vice versa.

version()

Return the version of astrodata.

open(*args, **kwargs)

Return an AstroData object from a file (deprecated, use from_file()).

+
+
+

Classes

+ + + + + + + + + + + + + + + + + + + + + +

AstroData([nddata, tables, phu, indices, ...])

Base class for the AstroData software package.

AstroDataError

Exception raised when there is a problem with the AstroData class.

AstroDataMixin()

A Mixin for NDData-like classes (such as Spectrum1D) to enable them to behave similarly to AstroData objects.

NDAstroData(data[, uncertainty, mask, wcs, ...])

Implements NDData with all Mixins, plus some AstroData specifics.

Section(*args, **kwargs)

A class to handle n-dimensional sections

TagSet([add, remove, blocked_by, blocks, ...])

Named tuple that is used by tag methods to return which actions should be performed on a tag set.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/gemini_examples/index.html b/examples/gemini_examples/index.html new file mode 100644 index 00000000..08da0879 --- /dev/null +++ b/examples/gemini_examples/index.html @@ -0,0 +1,145 @@ + + + + + + + Gemini Examples — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Gemini Examples

+

These examples use the Gemini Observatory’s DRAGONS data reduction pipeline. They use +external code to perform operations outside of astrodata’s capabilities.

+

Specifically, DRAGONS extends AstroData in its DRAGONS:gemini_instruments +package. This package is a grounded example of how to extend AstroData for +specific instruments. All reduction functionality is supported by other parts +of the library.

+

To use these examples, you need to install DRAGONS and its dependencies. +Please follow the DRAGONS installation instructions at DRAGONS Installation.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/generic_examples/index.html b/examples/generic_examples/index.html new file mode 100644 index 00000000..6ace241e --- /dev/null +++ b/examples/generic_examples/index.html @@ -0,0 +1,163 @@ + + + + + + + Generic Examples — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Generic Examples

+

This section contains examples using astrodata with no specific instrument +or pipeline in mind. The examples are intended to demonstrate the basic +functionality of astrodata and to provide a starting point for users +who are new to the package.

+

Many of these patterns will apply broadly to anything derived from +AstroData. However, no “real” data is used in these examples, and +they are not intended to represent how any given reduction “should” +be done to achieve science quality data.

+
+

User Examples

+
+
+
+
+

Developer Examples

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 00000000..4653ff77 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,150 @@ + + + + + + + Examples — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Examples

+

We provide a number of examples to aid in understanding specific use cases, +common patterns, and best practices when working with astrodata. To see a +complete user guide, please see our User Guide. For a deeper guide to +programming with astrodata, please visit our Developer Guide.

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html new file mode 100644 index 00000000..153e9929 --- /dev/null +++ b/genindex.html @@ -0,0 +1,474 @@ + + + + + + Index — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ A + | B + | C + | D + | E + | F + | H + | I + | L + | M + | N + | O + | P + | R + | S + | T + | U + | V + | W + +
+

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

L

+ + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ + + +
+
+
+ +
+ +
+

© Copyright 2023-present, NOIRLab/Gemini Observatories.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..391158e1 --- /dev/null +++ b/index.html @@ -0,0 +1,134 @@ + + + + + + + astrodata Documentation — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

astrodata Documentation

+

This is the documentation for astrodata.

+ +
+

Indices and tables

+ +
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023-present, NOIRLab/Gemini Observatories.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/appendix_descriptors.html b/manuals/appendix_descriptors.html new file mode 100644 index 00000000..e2ebc39a --- /dev/null +++ b/manuals/appendix_descriptors.html @@ -0,0 +1,549 @@ + + + + + + + List of Gemini Standard Descriptors — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

List of Gemini Standard Descriptors

+

To run and re-use Gemini primitives and functions this list of Standard +Descriptors must be defined for input data. This also applies to data +that is to be served by the Gemini Observatory Archive (GOA).

+

For any AstroData objects, to get the list of the descriptors that are +defined use the AstroData.descriptors attribute:

+
>> import astrodata
+>> import gemini_instruments
+>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+
+>> ad.descriptors
+('airmass', 'amp_read_area', 'ao_seeing', ..., 'well_depth_setting')
+
+
+

To get the values:

+
>> ad.airmass()
+
+>> for descriptor in ad.descriptors:
+...     print(descriptor, getattr(ad, descriptor)())
+
+
+

Note that not all of the descriptors below are defined for all of the +instruments. For example, shuffle_pixels is defined only for GMOS data +since only GMOS offers a Nod & Shuffle mode.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Descriptor

Short Definition

Python type

ad[0].desc()

ad.desc()

airmass

Airmass of the observation.

float

amp_read_area

Combination of amplifier name and 1-indexed section relative +to the detector.

str

list of str

ao_seeing

Estimate of the natural seeing as calculated from the +adaptive optics systems.

float

array_name

Name assigned to the array generated by a given amplifier, +one array per amplifier.

str

list of str

array_section

Section covered by the array(s), in 0-indexed pixels, relative +to the detector frame (e.g. position of multiple amps read +within a CCD). Uses namedtuple “Section” defined in +gemini_instruments.common.

Section

list of Section

azimuth

Pointing position in azimuth, in degrees.

float

calibration_key

Key used in the database that the getProcessed* primitives +use to store previous calibration association information.

str

camera

Name of the camera.

str

cass_rotator_pa

Position angle of the Cassegrain rotator, in degrees.

float

central_wavelength

Central wavelength, in meters.

float

coadds

Number of co-adds.

int

data_label

Gemini data label.

str

data_section

Section where the sky-exposed data falls, in 0-indexed pixels. +Uses namedtuple “Section” defined in +gemini_instruments.common

Section

list of Section

dec

Declination of the center of the field, in degrees.

float

decker

Name of the decker.

str

detector_name

Name assigned to the detector.

str

detector_roi_setting

Human readable Region of Interest (ROI) setting

str

detector_rois_requested

Section defining the Regions of Interest, in 0-indexed pixels. +Uses namedtuple “Section” defined in +gemini_instruments.common.

list of Section

detector_section

Section covered by the detector(s), in 0-indexed pixels, +relative to the whole mosaic of detectors. +Uses namedtuple “Section” defined in +gemini_instruments.common.

list

list of Section

detector_x_bin

X-axis binning.

int

detector_x_offset

Telescope offset along the detector X-axis, in pixels.

float

detector_y_bin

Y-axis binning.

int

detector_y_offset

Telescope offset along the detector Y-axis, in pixels.

float

disperser

Name of the disperser.

str

dispersion

Value for the dispersion, in meters per pixel.

float

list of float

dispersion_axis

Dispersion axis.

int

list of int

effective_wavelength

Wavelength representing the bandpass or the spectrum coverage.

float

elevation

Pointing position in elevation, in degrees.

float

exposure_time

Exposure time, in seconds.

float

filter_name

Name of the filter combination.

str

focal_plane_mask

Name of the mask in the focal plane.

str

gain

Gain in electrons per ADU

float

list of float

gain_setting

Human readable gain setting (eg. low, high)

str

gcal_lamp

Returns the name of the GCAL lamp being used, or “Off” if no +lamp is in use.

str

group_id

Gemini observation group ID that identifies compatible data.

str

instrument

Name of the instrument

str

is_ao

Whether or not the adaptive optics system was used.

bool

is_coadds_summed

Whether co-adds are summed or averaged.

bool

local_time

Local time.

datetime

lyot_stop

Name of the lyot stop.

str

mdf_row_id

Mask Definition File row ID of a cut MOS or XD spectrum.

int ??

nod_count

Number of nods to A and B positions.

tuple of int

nod_offsets

Nod offsets to A and B positions, in arcseconds

tuple of float

nominal_atmospheric_extinction

Nomimal atmospheric extinction, from model.

float

nominal_photometric_zeropoint

Nominal photometric zeropoint.

float

list of float

non_linear_level

Lower boundary of the non-linear regime.

float

list of int

object

Name of the target (as entered by the user).

str

observation_class

Gemini class name for the observation +(eg. ‘science’, ‘acq’, ‘dayCal’).

str

observation_epoch

Observation epoch.

float

observation_id

Gemini observation ID.

str

observation_type

Gemini observation type (eg. ‘OBJECT’, ‘FLAT’, ‘ARC’).

str

overscan_section

Section where the overscan data falls, in 0-indexed pixels. +Uses namedtuple “Section” defined in +gemini_instruments.common.

Section

list of Section

pixel_scale

Pixel scale in arcsec per pixel.

float

program_id

Gemini program ID.

str

pupil_mask

Name of the pupil mask.

str ??

qa_state

Gemini quality assessment state (eg. pass, usable, fail).

str

ra

Right ascension, in degrees.

float

raw_bg

Gemini sky background band.

int ??

raw_cc

Gemini cloud coverage band.

int

raw_iq

Gemini image quality band.

int

raw_wv

Gemini water vapor band.

int ??

read_mode

Gemini name for combination for gain setting and read setting.

str

read_noise

Read noise in electrons.

float

list of float

read_speed_setting

human readable read mode setting (eg. slow, fast).

str

requested_bg

PI requested Gemini sky background band.

int

requested_cc

PI requested Gemini cloud coverage band.

int

requested_iq

PI requested Gemini image quality band.

int

requested_wv

PI requested Gemini water vapor band.

int

saturation_level

Saturation level.

int

list of int

shuffle_pixels

Charge shuffle, in pixels. (nod and shuffle mode)

int

slit

Name of the slit.

str

target_dec

Declination of the target, in degrees.

float

target_ra

Right Ascension of the target, in degrees.

float

telescope

Name of the telescope.

str

telescope_x_offset

Offset along the telescope’s x-axis.

float

telescope_y_offset

Offset along the telescope’s y-axis.

float

ut_date

UT date of the observation.

datetime.date

ut_datetime

UT date and time of the observation.

datetime

ut_time

UT time of the observation.

datetime.time

wavefront_sensor

Wavefront sensor used for the observation.

str

wavelength_band

Band associated with the filter or the central wavelength.

str

wcs_dec

Declination of the center of field from the WCS keywords. +In degrees.

float

wcs_ra

Right Ascension of the center of field from the WCS keywords. +In degrees.

float

well_depth_setting

Human readable well depth setting (eg. shallow, deep)

str

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/cheatsheet.html b/manuals/cheatsheet.html new file mode 100644 index 00000000..bd4675ab --- /dev/null +++ b/manuals/cheatsheet.html @@ -0,0 +1,574 @@ + + + + + + + Cheat Sheet — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Cheat Sheet

+
+

Document ID

+

PIPE-USER-105_AstrodataCheatSheet

+
+

A data package is available for download if you wish to run the examples +included in this cheat sheet. Download it at:

+
+
+

To unpack:

+
$ cd <somewhere_convenient>
+$ tar xvf ad_usermanual_datapkg-v1.tar
+$ bunzip2 ad_usermanual/playdata/*.bz2
+
+
+

Then go to the ad_usermanual/playground directory to run the examples.

+
+

Imports

+

Import astrodata and gemini_instruments:

+
>> import astrodata
+>> import gemini_instruments
+
+
+
+
+

Basic read and write operations

+

Open a file:

+
>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+
+
+

Get path and filename:

+
>> ad.path
+'../playdata/N20170609S0154.fits'
+>> ad.filename
+'N20170609S0154.fits'
+
+
+

Write to a new file:

+
>> ad.write(filename='new154.fits')
+>> ad.filename
+N20170609S0154.fits
+
+
+

Overwrite the file:

+
>> adnew = astrodata.open('new154.fits')
+>> adnew.filename
+new154.fits
+>> adnew.write(overwrite=True)
+
+
+
+
+

Object structure

+
+

Description

+

The AstroData object is assigned by “tags” that describe the +type of data it contains. The tags are drawn from rules defined in +DRAGONS:gemini_instruments and are based on header information.

+

When mapping a FITS file, each science pixel extension is loaded as a +NDAstroData object. The list is zero-indexed. So FITS +extension 1 becomes element 0 of the AstroData object. If a VAR +extension is present, it is loaded to the variance attribute of the +NDAstroData. If a DQ extension is present, it is loaded to the .mask +attribute of the NDAstroData. SCI, VAR and DQ are associated +through the EXTVER keyword value.

+

In the file below, each AstroData “extension” contains the pixel data, +then an error plane (.variance) and a bad pixel mask plane (.mask). +Table can be attached to an extension, like OBJCAT, or to the +AstroData object globally, like REFCAT. (In this case, OBJCAT is a +catalogue of the sources detected in the image, REFCAT is a reference catalog +for the area covered by the whole file.) If other 2D data needs to be +associated with an extension this can also be done, like here with OBJMASK, +a 2D mask matching the sources in the image.

+
>> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits')
+>> ad.info()
+Filename: ../playdata/N20170609S0154_varAdded.fits
+Tags: ACQUISITION GEMINI GMOS IMAGE NORTH OVERSCAN_SUBTRACTED OVERSCAN_TRIMMED
+    PREPARED SIDEREAL
+Pixels Extensions
+Index  Content                  Type              Dimensions     Format
+[ 0]   science                  NDAstroData       (2112, 256)    float32
+          .variance             ndarray           (2112, 256)    float32
+          .mask                 ndarray           (2112, 256)    int16
+          .OBJCAT               Table             (6, 43)        n/a
+          .OBJMASK              ndarray           (2112, 256)    uint8
+[ 1]   science                  NDAstroData       (2112, 256)    float32
+          .variance             ndarray           (2112, 256)    float32
+          .mask                 ndarray           (2112, 256)    int16
+          .OBJCAT               Table             (8, 43)        n/a
+          .OBJMASK              ndarray           (2112, 256)    uint8
+[ 2]   science                  NDAstroData       (2112, 256)    float32
+          .variance             ndarray           (2112, 256)    float32
+          .mask                 ndarray           (2112, 256)    int16
+          .OBJCAT               Table             (7, 43)        n/a
+          .OBJMASK              ndarray           (2112, 256)    uint8
+[ 3]   science                  NDAstroData       (2112, 256)    float32
+          .variance             ndarray           (2112, 256)    float32
+          .mask                 ndarray           (2112, 256)    int16
+          .OBJCAT               Table             (5, 43)        n/a
+          .OBJMASK              ndarray           (2112, 256)    uint8
+Other Extensions
+               Type        Dimensions
+.REFCAT        Table       (245, 16)
+
+
+
+
+

Modifying the structure

+

Let’s first get our play data loaded. You are encouraged to do a +info() before and after each structure-modification +step, to see how things change.

+
>> from copy import deepcopy
+>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+>> adcopy = deepcopy(ad)
+>> advar = astrodata.open('../playdata/N20170609S0154_varAdded.fits')
+
+
+

Append an extension:

+
>> adcopy.append(advar[3])
+>> adcopy.append(advar[3].data)
+
+
+

Delete an extension:

+
>> del adcopy[5]
+
+
+

Delete and add variance and mask planes:

+
>> var = adcopy[4].variance
+>> adcopy[4].variance = None
+>> adcopy[4].variance = var
+
+
+

Attach a table to an extension:

+
>> adcopy[3].SMAUG = advar[0].OBJCAT.copy()
+
+
+

Attach a table to the AstroData object:

+
>> adcopy.DROGON = advar.REFCAT.copy()
+
+
+

Delete a table:

+
>> del adcopy[3].SMAUG
+>> del adcopy.DROGON
+
+
+
+
+
+

Astrodata tags

+
>> ad = astrodata.open('../playdata/N20170521S0925_forStack.fits')
+>> ad.tags
+{'GMOS', 'OVERSCAN_SUBTRACTED', 'SIDEREAL', 'NORTH', 'OVERSCAN_TRIMMED',
+'PREPARED', 'IMAGE', 'GEMINI'}
+
+>> type(ad.tags)
+<class 'set'>
+
+>> {'IMAGE', 'PREPARED'}.issubset(ad.tags)
+True
+>> 'PREPARED' in ad.tags
+True
+
+
+
+
+

Headers

+

The use of descriptors is favored over direct header access when retrieving +values already represented by descriptors, and when writing instrument agnostic +routines.

+
+

Descriptors

+
>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+>> ad.filter_name()
+'open1-6&g_G0301'
+>> ad.filter_name(pretty=True)
+'g'
+>> ad.gain()   # uses a look-up table to get the correct values
+[2.03, 1.97, 1.96, 2.01]
+>> ad.hdr['GAIN']
+[1.0, 1.0, 1.0, 1.0]    # the wrong values contained in the raw data.
+>> ad[0].gain()
+2.03
+>> ad.gain()[0]
+2.03
+
+>> ad.descriptors
+('airmass', 'amp_read_area', 'ao_seeing', ...
+ ...)
+
+
+
+
+

Direct access to header keywords

+
>> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits')
+
+
+
+

Primary Header Unit

+

To see a print out of the full PHU:

+
+

>> ad.phu

+
+

Get value from PHU:

+
>> ad.phu['EXPTIME']
+1.0
+
+>> default = 5.
+>> ad.phu.get('BOGUSKEY', default)
+5.0
+
+
+

Set PHU keyword, with and without comment:

+
>> ad.phu['NEWKEY'] = 50.
+>> ad.phu['ANOTHER'] = (30., 'Some comment')
+
+
+

Delete PHU keyword:

+
>> del ad.phu['NEWKEY']
+
+
+
+
+

Pixel extension header

+

To see a print out of the full header for an extension or all the extensions:

+
+

>> ad[0].hdr +>> list(ad.hdr)

+
+

Get value from an extension header:

+
>> ad[0].hdr['OVERSCAN']
+469.7444308769482
+>> ad[0].hdr.get('OVERSCAN', default)
+
+
+

Get keyword value for all extensions:

+
>> ad.hdr['OVERSCAN']
+[469.7444308769482, 469.656175780001, 464.9815279808291, 467.5701178951787]
+>> ad.hdr.get('BOGUSKEY', 5.)
+[5.0, 5.0, 5.0, 5.0]
+
+
+

Set extension header keyword, with and without comment:

+
>> ad[0].hdr['NEWKEY'] = 50.
+>> ad[0].hdr['ANOTHER'] = (30., 'Some comment')
+
+
+

Delete an extension keyword:

+
>> del ad[0].hdr['NEWKEY']
+
+
+
+
+

Table header

+

See the Tables section.

+
+
+
+
+

Pixel data

+
+

Arithmetics

+

Arithmetics with variance and mask propagation is offered for ++, -, *, /, and **.

+
>> ad_hcont = astrodata.open('../playdata/N20170521S0925_forStack.fits')
+>> ad_halpha = astrodata.open('../playdata/N20170521S0926_forStack.fits')
+
+>> adsub = ad_halpha - ad_hcont
+
+>> ad_halpha[0].data.mean()
+646.11896
+>> ad_hcont[0].data.mean()
+581.81342
+>> adsub[0].data.mean()
+64.305862
+
+>> ad_halpha[0].variance.mean()
+669.80664
+>> ad_hcont[0].variance.mean()
+598.46667
+>> adsub[0].variance.mean()
+1268.274
+
+
+# In place multiplication
+>> ad_mult = deepcopy(ad)
+>> ad_mult.multiply(ad)
+>> ad_mult.multiply(5.)
+
+
+# Using descriptors to operate in-place on extensions.
+>> from copy import deepcopy
+>> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits')
+>> ad_gain = deepcopy(ad)
+>> for (ext, gain) in zip(ad_gain, ad_gain.gain()):
+...     ext.multiply(gain)
+>> ad_gain[0].data.mean()
+366.39545
+>> ad[0].data.mean()
+180.4904
+>> ad[0].gain()
+2.03
+
+
+
+
+

Other pixel data operations

+
>> import numpy as np
+>> ad_halpha[0].mask[300:350,300:350] = 1
+>> np.mean(ad_halpha[0].data[ad_halpha[0].mask==0])
+657.1994
+>> np.mean(ad_halpha[0].data)
+646.11896
+
+
+
+
+
+

Tables

+

Tables are stored as astropy.table.Table class. FITS tables are +represented in astrodata as Table and FITS headers are stored in the +NDAstroData meta attribute. Most table +access should be done through the Table interface. The best reference is the +Astropy documentation itself. Below are just a few examples.

+
>> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits')
+
+
+

Get column names:

+
>> ad.REFCAT.colnames
+
+
+

Get column content:

+
>> ad.REFCAT['zmag']
+>> ad.REFCAT['zmag', 'zmag_err']
+
+
+

Get content of row:

+
>> ad.REFCAT[4]     # 5th row
+>> ad.REFCAT[4:6]   # 5th and 6th rows
+
+
+

Get content from specific row and column:

+
>> ad.REFCAT['zmag'][4]
+
+
+

Add a column:

+
>> new_column = [0] * len(ad.REFCAT)
+>> ad.REFCAT['new_column'] = new_column
+
+
+

Add a row:

+
>> new_row = [0] * len(ad.REFCAT.colnames)
+>> new_row[1] = ''   # Cat_Id column is of "str" type.
+>> ad.REFCAT.add_row(new_row)
+
+
+

Selecting value from criterion:

+
>> ad.REFCAT['zmag'][ad.REFCAT['Cat_Id'] == '1237662500002005475']
+>> ad.REFCAT['zmag'][ad.REFCAT['zmag'] < 18.]
+
+
+

Rejecting numpy.nan before doing something with the values:

+
>> t = ad.REFCAT   # to save typing.
+>> t['zmag'][np.where(np.isnan(t['zmag']), 99, t['zmag']) < 18.]
+
+>> t['zmag'].mean()
+nan
+>> t['zmag'][np.where(~np.isnan(t['zmag']))].mean()
+20.377306
+
+
+

If for some reason you need to access the FITS table headers, here is how to do it.

+

To see the FITS headers:

+
>> ad.REFCAT.meta
+>> ad[0].OBJCAT.meta
+
+
+

To retrieve a specific FITS table header:

+
>> ad.REFCAT.meta['header']['TTYPE3']
+'RAJ2000'
+>> ad[0].OBJCAT.meta['header']['TTYPE3']
+'Y_IMAGE'
+
+
+

To retrieve all the keyword names matching a selection:

+
>> keynames = [key for key in ad.REFCAT.meta['header'] if key.startswith('TTYPE')]
+
+
+
+
+

Create new AstroData object

+

Basic header and data array set to zeros:

+
>> from astropy.io import fits
+
+>> phu = fits.PrimaryHDU()
+>> pixel_data = np.zeros((100,100))
+
+>> hdu = fits.ImageHDU()
+>> hdu.data = pixel_data
+>> ad = astrodata.create(phu)
+>> ad.append(hdu, name='SCI')
+
+
+

or another way:

+
>> hdu = fits.ImageHDU(data=pixel_data, name='SCI')
+>> ad = astrodata.create(phu, [hdu])
+
+
+

A Table as an AstroData object:

+
>> from astropy.table import Table
+
+>> my_astropy_table = Table(list(np.random.rand(2,100)), names=['col1', 'col2'])
+>> phu = fits.PrimaryHDU()
+
+>> ad = astrodata.create(phu)
+>> ad.SMAUG = my_astropy_table
+
+>> phu = fits.PrimaryHDU()
+>> ad = astrodata.create(phu)
+>> ad.SMAUG = my_fits_table
+
+
+

WARNING: This last line will not run like the others as we have not defined +my_fits_table. This is nonetheless how it is done if you had a FITS table.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/full_api.html b/manuals/full_api.html new file mode 100644 index 00000000..c72e4cf9 --- /dev/null +++ b/manuals/full_api.html @@ -0,0 +1,2329 @@ + + + + + + + Reference API — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Reference API

+

This API reference is auto-generated from the docstrings in the code. It is +meant to be a reference for the code, not a tutorial or guide. For more +information on how to use the code, see the User Manual. For +programming tips using AstroData, see the Programmer’s Manual.

+

This package adds an abstraction layer to astronomical data by parsing the +information contained in the headers as attributes. To do so, one must subclass +astrodata.AstroData and add parse methods accordingly to the +TagSet received.

+
+
+class astrodata.AstroData(nddata=None, tables=None, phu=None, indices=None, is_single=False)[source]
+

Bases: object

+

Base class for the AstroData software package. It provides an interface +to manipulate astronomical data sets.

+
+
Parameters:
+
    +
  • nddata (astrodata.NDAstroData or list of astrodata.NDAstroData) – List of NDAstroData objects.

  • +
  • tables (dict[name, astropy.table.Table]) – Dict of table objects.

  • +
  • phu (astropy.io.fits.Header) – Primary header.

  • +
  • indices (list of int) – List of indices mapping the astrodata.NDAstroData objects that this +object will access to. This is used when slicing an object, then the +sliced AstroData will have the .nddata list from its parent and +access the sliced NDAstroData through this list of indices.

  • +
+
+
+
+
+__add__(oper)[source]
+

Performs addition by evaluating self + operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self + operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+__annotations__ = {}
+
+ +
+
+__contains__(attribute)[source]
+

Implements the ability to use the in operator with an +AstroData object.

+
+
Parameters:
+

attribute (str) – An attribute name.

+
+
Return type:
+

bool

+
+
+
+ +
+
+__deepcopy__(memo)[source]
+

Returns a new instance of this class.

+
+
Parameters:
+

memo (dict) – See the documentation on deepcopy for an explanation on how +this works.

+
+
+
+ +
+
+__delattr__(attribute)[source]
+

Implements attribute removal.

+
+ +
+
+__delitem__(idx)[source]
+

Called to implement deletion of self[idx]. Supports standard +Python syntax (including negative indices).

+
+
Parameters:
+

idx (int) – This index represents the order of the element that you want +to remove.

+
+
Raises:
+

IndexError – If idx is out of range.

+
+
+
+ +
+
+__getattr__(attribute)[source]
+

Called when an attribute lookup has not found the attribute in the +usual places (not an instance attribute, and not in the class tree for +self).

+
+
Parameters:
+

attribute (str) – The attribute’s name.

+
+
Raises:
+

AttributeError – If the attribute could not be found/computed.

+
+
+
+ +
+
+__getitem__(idx)[source]
+

Returns a sliced view of the instance. It supports the standard +Python indexing syntax.

+
+
Parameters:
+

slice (int, slice) – An integer or an instance of a Python standard slice object

+
+
Raises:
+
    +
  • TypeError – If trying to slice an object when it doesn’t make sense (e.g. + slicing a single slice)

  • +
  • ValueError – If slice does not belong to one of the recognized types

  • +
  • IndexError – If an index is out of range

  • +
+
+
+
+ +
+
+__iadd__(oper)[source]
+

Performs inplace addition by evaluating self += operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self += operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+__imul__(oper)[source]
+

Performs inplace multiplication by evaluating self *= operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self *= operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+__init__(nddata=None, tables=None, phu=None, indices=None, is_single=False)[source]
+
+ +
+
+__isub__(oper)[source]
+

Performs inplace subtraction by evaluating self -= operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self -= operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+__iter__()[source]
+
+ +
+
+__itruediv__(oper)[source]
+

Performs inplace division by evaluating self /= operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self /= operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+__len__()[source]
+

Return the number of independent extensions stored by the object.

+
+ +
+
+__module__ = 'astrodata.core'
+
+ +
+
+__mul__(oper)[source]
+

Performs multiplication by evaluating self * operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self * operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+__radd__(oper)
+

Performs addition by evaluating self + operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self + operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+__rmul__(oper)
+

Performs multiplication by evaluating self * operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self * operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+__rsub__(oper)[source]
+
+ +
+
+__rtruediv__(oper)[source]
+
+ +
+
+__setattr__(attribute, value)[source]
+

Called when an attribute assignment is attempted, instead of the +normal mechanism.

+
+
Parameters:
+
    +
  • attribute (str) – The attribute’s name.

  • +
  • value (object) – The value to be assigned to the attribute.

  • +
+
+
+
+ +
+
+__sub__(oper)[source]
+

Performs subtraction by evaluating self - operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self - operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+__truediv__(oper)[source]
+

Performs division by evaluating self / operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self / operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+add(oper)
+

Performs inplace addition by evaluating self += operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self += operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+append(ext, name=None, header=None)[source]
+

Adds a new top-level extension.

+
+
Parameters:
+
    +
  • ext (array, astropy.nddata.NDData, astropy.table.Table, other) – The contents for the new extension. The exact accepted types depend +on the class implementing this interface. Implementations specific +to certain data formats may accept specialized types (eg. a FITS +provider will accept an astropy.io.fits.ImageHDU and extract the +array out of it).

  • +
  • name (str, optional) – A name that may be used to access the new object, as an attribute +of the provider. The name is typically ignored for top-level +(global) objects, and required for the others. If the name cannot +be derived from the metadata associated to ext, you will +have to provider one. +It can consist in a combination of numbers and letters, with the +restriction that the letters have to be all capital, and the first +character cannot be a number (“[A-Z][A-Z0-9]*”).

  • +
+
+
Returns:
+

    +
  • The same object, or a new one, if it was necessary to convert it to

  • +
  • a more suitable format for internal use.

  • +
+

+
+
Raises:
+
    +
  • TypeError – If adding the object in an invalid situation (eg. name is + None when adding to a single slice).

  • +
  • ValueError – Raised if the extension is of a proper type, but its value is + illegal somehow.

  • +
+
+
+
+ +
+
+crop(x1, y1, x2, y2)[source]
+

Crop the NDData objects given indices.

+
+
Parameters:
+
    +
  • x1 (int) – Minimum and maximum indices for the x and y axis.

  • +
  • y1 (int) – Minimum and maximum indices for the x and y axis.

  • +
  • x2 (int) – Minimum and maximum indices for the x and y axis.

  • +
  • y2 (int) – Minimum and maximum indices for the x and y axis.

  • +
+
+
+
+ +
+
+property data
+

A list of the arrays (or single array, if this is a single slice) +corresponding to the science data attached to each extension.

+
+ +
+
+property descriptors
+

Returns a sequence of names for the methods that have been +decorated as descriptors.

+
+
Return type:
+

tuple of str

+
+
+
+ +
+
+divide(oper)
+

Performs inplace division by evaluating self /= operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self /= operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+property exposed
+

A collection of strings with the names of objects that can be +accessed directly by name as attributes of this instance, and that are +not part of its standard interface (i.e. data objects that have been +added dynamically).

+

Examples

+
>>> ad[0].exposed  
+set(['OBJMASK', 'OBJCAT'])
+
+
+
+ +
+
+property ext_tables
+

Return the names of the astropy.table.Table objects associated to +an extension.

+
+ +
+
+property filename
+

Return the file name.

+
+ +
+
+property hdr
+

Return all headers, as a astrodata.fits.FitsHeaderCollection.

+
+ +
+
+property header
+

Deprecated header access. Use .hdr instead.

+
+ +
+
+property id
+

Returns the extension identifier (1-based extension number) +for sliced objects.

+
+ +
+
+property indices
+

Returns the extensions indices for sliced objects.

+
+ +
+
+info()[source]
+

Prints out information about the contents of this instance.

+
+ +
+
+instrument()[source]
+

Returns the name of the instrument making the observation.

+
+ +
+
+is_settable(attr)[source]
+

Return True if the attribute is meant to be modified.

+
+ +
+
+property is_sliced
+

If this data provider instance represents the whole dataset, return +False. If it represents a slice out of the whole, return True.

+
+ +
+
+classmethod load(source, extname_parser=None)
+

Read from a file, file object, HDUList, etc.

+
+ +
+
+property mask
+

A list of the mask arrays (or a single array, if this is a single +slice) attached to the science data, for each extension.

+

For objects that miss a mask, None will be provided instead.

+
+ +
+
+classmethod matches_data(source) bool[source]
+

Returns True if the class can handle the data in the source.

+
+
Parameters:
+

source (list of astropy.io.fits.HDUList) – The FITS file to be read.

+
+
Returns:
+

True if the class can handle the data in the source.

+
+
Return type:
+

bool

+
+
+
+

Note

+

Typically, this method is implemented by the static method +Astrodata._matches_data or by a class method with the same signature +for subclasses.

+

If you are implementing a subclass, you should override _matches_data +instead, which is a static method that takes a single argument, the +source data, and returns a boolean.

+

If that method is not overridden, this method will call it with the +source data as argument.

+

For more information, see the documentation for the +_matches_data() and the Developer Guide.

+
+
+ +
+
+multiply(oper)
+

Performs inplace multiplication by evaluating self *= operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self *= operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+property nddata
+

Return the list of astrodata.NDAstroData objects.

+

If the AstroData object is sliced, this returns only the NDData +objects of the sliced extensions. And if this is a single extension +object, the NDData object is returned directly (i.e. not a list).

+
+ +
+
+object()[source]
+

Returns the name of the object being observed.

+
+ +
+
+operate(operator, *args, **kwargs)[source]
+

Applies a function to the main data array on each extension, replacing +the data with the result. The data will be passed as the first argument +to the function.

+

It will be applied to the mask and variance of each extension, too, if +they exist.

+

This is a convenience method, which is equivalent to:

+
for ext in ad:
+    ext.data = operator(ext.data, *args, **kwargs)
+    if ext.mask is not None:
+        ext.mask = operator(ext.mask, *args, **kwargs)
+    if ext.variance is not None:
+        ext.variance = operator(ext.variance, *args, **kwargs)
+
+
+

with the additional advantage that it will work on single slices, too.

+
+
Parameters:
+
    +
  • operator (callable) – A function that takes an array (and, maybe, other arguments) +and returns an array.

  • +
  • args (optional) – Additional arguments to be passed to the operator.

  • +
  • kwargs (optional) – Additional arguments to be passed to the operator.

  • +
+
+
+

Examples

+
>>> import numpy as np
+>>> ad.operate(np.squeeze)  
+
+
+
+ +
+
+property orig_filename
+

Return the original file name (before it was modified).

+
+ +
+
+property path
+

Return the file path.

+
+ +
+
+property phu
+

Return the primary header.

+
+ +
+
+classmethod read(source, extname_parser=None)[source]
+

Read from a file, file object, HDUList, etc.

+
+ +
+
+reset(data, mask=<object object>, variance=<object object>, check=True)[source]
+

Sets the .data, and optionally .mask and .variance +attributes of a single-extension AstroData slice. This function will +optionally check whether these attributes have the same shape.

+
+
Parameters:
+
    +
  • data (ndarray) – The array to assign to the .data attribute (“SCI”).

  • +
  • mask (ndarray, optional) – The array to assign to the .mask attribute (“DQ”).

  • +
  • variance (ndarray, optional) – The array to assign to the .variance attribute (“VAR”).

  • +
  • check (bool) – If set, then the function will check that the mask and variance +arrays have the same shape as the data array.

  • +
+
+
Raises:
+
    +
  • TypeError – if an attempt is made to set the .mask or .variance attributes + with something other than an array

  • +
  • ValueError – if the .mask or .variance attributes don’t have the same shape as + .data, OR if this is called on an AD instance that isn’t a single + extension slice

  • +
+
+
+
+ +
+
+property shape
+

Return the shape of the data array for each extension as a list of +shapes.

+
+ +
+
+subtract(oper)
+

Performs inplace subtraction by evaluating self -= operand.

+
+
Parameters:
+

oper (number or object) – The operand to perform the operation self -= operand.

+
+
Return type:
+

AstroData instance

+
+
+
+ +
+
+table()[source]
+

Return a dictionary of astropy.table.Table objects.

+

Notes

+

This returns a _copy_ of the tables, so modifying them will not +affect the original ones.

+
+ +
+
+property tables
+

Return the names of the astropy.table.Table objects associated to +the top-level object.

+
+ +
+
+property tags
+

A set of strings that represent the tags defining this instance.

+
+ +
+
+telescope()[source]
+

Returns the name of the telescope.

+
+ +
+
+property uncertainty
+

A list of the uncertainty objects (or a single object, if this is +a single slice) attached to the science data, for each extension.

+

The objects are instances of AstroPy’s astropy.nddata.NDUncertainty, +or None where no information is available.

+
+

See also

+
+
variance

The actual array supporting the uncertainty object.

+
+
+
+
+ +
+
+update_filename(prefix=None, suffix=None, strip=False)[source]
+

Update the “filename” attribute of the AstroData object.

+

A prefix and/or suffix can be specified. If strip=True, these will +replace the existing prefix/suffix; if strip=False, they will +simply be prepended/appended.

+

The current filename is broken down into its existing prefix, root, and +suffix using the ORIGNAME phu keyword, if it exists and is +contained within the current filename. Otherwise, the filename is split +at the last underscore and the part before is assigned as the root and +the underscore and part after the suffix. No prefix is assigned.

+

Note that, if strip=True, a prefix or suffix will only be stripped +if ‘’ is specified.

+
+
Parameters:
+
    +
  • prefix (str, optional) – New prefix (None => leave alone)

  • +
  • suffix (str, optional) – New suffix (None => leave alone)

  • +
  • strip (bool, optional) – Strip existing prefixes and suffixes if new ones are given?

  • +
+
+
Raises:
+

ValueError – If the filename cannot be determined

+
+
+
+ +
+
+property variance
+

A list of the variance arrays (or a single array, if this is a +single slice) attached to the science data, for each extension.

+

For objects that miss uncertainty information, None will be provided +instead.

+
+

See also

+
+
uncertainty

The uncertainty objects used under the hood.

+
+
+
+
+ +
+
+property wcs
+

Returns the list of WCS objects for each extension.

+
+ +
+
+write(filename=None, overwrite=False)[source]
+

Write the object to disk.

+
+
Parameters:
+
    +
  • filename (str, optional) – If the filename is not given, self.path is used.

  • +
  • overwrite (bool) – If True, overwrites existing file.

  • +
+
+
+
+ +
+ +
+
+exception astrodata.AstroDataError[source]
+

Bases: Exception

+

Exception raised when there is a problem with the AstroData class.

+
+
+__annotations__ = {}
+
+ +
+
+__cause__
+

exception cause

+
+ +
+
+__context__
+

exception context

+
+ +
+
+__delattr__(name, /)
+

Implement delattr(self, name).

+
+ +
+
+__getattribute__(name, /)
+

Return getattr(self, name).

+
+ +
+
+__init__(*args, **kwargs)
+
+ +
+
+__module__ = 'astrodata.adfactory'
+
+ +
+
+__new__(**kwargs)
+
+ +
+
+__reduce__()
+

Helper for pickle.

+
+ +
+
+__repr__()
+

Return repr(self).

+
+ +
+
+__setattr__(name, value, /)
+

Implement setattr(self, name, value).

+
+ +
+
+__setstate__()
+
+ +
+
+__str__()
+

Return str(self).

+
+ +
+
+__suppress_context__
+
+ +
+
+__traceback__
+
+ +
+
+add_note()
+

Exception.add_note(note) – +add a note to the exception

+
+ +
+
+args
+
+ +
+
+with_traceback()
+

Exception.with_traceback(tb) – +set self.__traceback__ to tb and return self.

+
+ +
+ +
+
+class astrodata.AstroDataMixin[source]
+

Bases: object

+

A Mixin for NDData-like classes (such as Spectrum1D) to enable +them to behave similarly to AstroData objects.

+
+
These behaviors are:
    +
  1. mask attributes are combined with bitwise, not logical, or, +since the individual bits are important.

  2. +
  3. The WCS must be a gwcs.WCS object and slicing results in +the model being modified.

  4. +
  5. There is a settable variance attribute.

  6. +
  7. Additional attributes such as OBJMASK can be extracted from +the .meta[‘other’] dict

  8. +
+
+
+
+
+__annotations__ = {}
+
+ +
+
+__getattr__(attribute)[source]
+

Allow access to attributes stored in self.meta[‘other’], as we do +with AstroData objects.

+
+ +
+
+__module__ = 'astrodata.nddata'
+
+ +
+
+property shape
+

The shape of the data.

+
+ +
+
+property size
+

The size of the data.

+
+ +
+
+property variance
+

A convenience property to access the contents of uncertainty.

+
+ +
+
+property wcs
+

The WCS of the data. This is a gWCS object, not a FITS WCS object.

+

This is returning wcs from an inhertited class, see NDData.wcs for more +details.

+
+ +
+ +
+
+class astrodata.NDAstroData(data, uncertainty=None, mask=None, wcs=None, meta=None, unit=None, copy=False, variance=None)[source]
+

Bases: AstroDataMixin, NDArithmeticMixin, NDSlicingMixin, NDData

+

Implements NDData with all Mixins, plus some AstroData +specifics.

+

This class implements an NDData-like container that supports reading +and writing as implemented in the astropy.io.registry and also slicing +(indexing) and simple arithmetics (add, subtract, divide and multiply).

+

A very important difference between NDAstroData and NDData is that +the former attempts to load all its data lazily. There are also some +important differences in the interface (eg. .data lets you reset its +contents after initialization).

+

Documentation is provided where our class differs.

+
+

See also

+

NDData, NDArithmeticMixin, NDSlicingMixin

+
+

Examples

+

The mixins allow operation that are not possible with NDData or +NDDataBase, i.e. simple arithmetics:

+
>>> from astropy.nddata import StdDevUncertainty
+>>> import numpy as np
+>>> data = np.ones((3,3), dtype=float)
+>>> ndd1 = NDAstroData(data, uncertainty=StdDevUncertainty(data))
+>>> ndd2 = NDAstroData(data, uncertainty=StdDevUncertainty(data))
+>>> ndd3 = ndd1.add(ndd2)
+>>> ndd3.data
+array([[2., 2., 2.],
+    [2., 2., 2.],
+    [2., 2., 2.]])
+>>> ndd3.uncertainty.array
+array([[1.41421356, 1.41421356, 1.41421356],
+    [1.41421356, 1.41421356, 1.41421356],
+    [1.41421356, 1.41421356, 1.41421356]])
+
+
+

see NDArithmeticMixin for a complete list of all supported arithmetic +operations.

+

But also slicing (indexing) is possible:

+
>>> ndd4 = ndd3[1,:]
+>>> ndd4.data
+array([2., 2., 2.])
+>>> ndd4.uncertainty.array
+array([1.41421356, 1.41421356, 1.41421356])
+
+
+

See NDSlicingMixin for a description how slicing works (which +attributes) are sliced.

+

Initialize an NDAstroData instance.

+
+
Parameters:
+
    +
  • data (array-like) – The actual data. This can be a numpy array, a memmap, or a +fits.ImageHDU object.

  • +
  • uncertainty (NDUncertainty-like object, optional) – An object that represents the uncertainty of the data. If not +specified, the uncertainty will be set to None.

  • +
  • mask (array-like, optional) – An array that represents the mask of the data. If not specified, +the mask will be set to None.

  • +
  • wcs (gwcs.WCS object, optional) – The WCS of the data. If not specified, the WCS will be set to None.

  • +
  • meta (dict-like, optional) – A dictionary-like object that holds the meta data. If not +specified, the meta data will be set to None.

  • +
  • unit (astropy.units.Unit object, optional) – The unit of the data. If not specified, the unit will be set to +None.

  • +
  • copy (bool, optional) – If True, the data, uncertainty, mask, wcs, meta, and unit will be +copied. Otherwise, they will be referenced. Default is False.

  • +
  • variance (array-like, optional) – An array that represents the variance of the data. If not +specified, the variance will be set to None.

  • +
+
+
Raises:
+

ValueError – If uncertainty and variance are both specified.

+
+
+

Notes

+

The uncertainty and variance parameters are mutually exclusive.

+
+
+property T
+

Transpose the data. This is not a copy of the data.

+
+ +
+
+__abstractmethods__ = frozenset({})
+
+ +
+
+__annotations__ = {}
+
+ +
+
+__deepcopy__(memo)[source]
+
+ +
+
+__getattr__(attribute)
+

Allow access to attributes stored in self.meta[‘other’], as we do +with AstroData objects.

+
+ +
+
+__getitem__(item)
+
+ +
+
+__init__(data, uncertainty=None, mask=None, wcs=None, meta=None, unit=None, copy=False, variance=None)[source]
+

Initialize an NDAstroData instance.

+
+
Parameters:
+
    +
  • data (array-like) – The actual data. This can be a numpy array, a memmap, or a +fits.ImageHDU object.

  • +
  • uncertainty (NDUncertainty-like object, optional) – An object that represents the uncertainty of the data. If not +specified, the uncertainty will be set to None.

  • +
  • mask (array-like, optional) – An array that represents the mask of the data. If not specified, +the mask will be set to None.

  • +
  • wcs (gwcs.WCS object, optional) – The WCS of the data. If not specified, the WCS will be set to None.

  • +
  • meta (dict-like, optional) – A dictionary-like object that holds the meta data. If not +specified, the meta data will be set to None.

  • +
  • unit (astropy.units.Unit object, optional) – The unit of the data. If not specified, the unit will be set to +None.

  • +
  • copy (bool, optional) – If True, the data, uncertainty, mask, wcs, meta, and unit will be +copied. Otherwise, they will be referenced. Default is False.

  • +
  • variance (array-like, optional) – An array that represents the variance of the data. If not +specified, the variance will be set to None.

  • +
+
+
Raises:
+

ValueError – If uncertainty and variance are both specified.

+
+
+

Notes

+

The uncertainty and variance parameters are mutually exclusive.

+
+ +
+
+__module__ = 'astrodata.nddata'
+
+ +
+
+__repr__()[source]
+

Return repr(self).

+
+ +
+
+__str__()
+

Return str(self).

+
+ +
+
+classmethod add(operand, operand2=None, **kwargs)
+

Performs addition by evaluating self + operand.

+
+
Parameters:
+
    +
  • operand (NDData-like instance) – If operand2 is None or not given it will perform the operation +self + operand. +If operand2 is given it will perform operand + operand2. +If the method was called on a class rather than on the instance +operand2 must be given.

  • +
  • operand2 (NDData-like instance) – If operand2 is None or not given it will perform the operation +self + operand. +If operand2 is given it will perform operand + operand2. +If the method was called on a class rather than on the instance +operand2 must be given.

  • +
  • propagate_uncertainties (bool or None, optional) –

    If None the result will have no uncertainty. If False the +result will have a copied version of the first operand that has an +uncertainty. If True the result will have a correctly propagated +uncertainty from the uncertainties of the operands but this assumes +that the uncertainties are NDUncertainty-like. Default is True.

    +
    +

    Changed in version 1.2: This parameter must be given as keyword-parameter. Using it as +positional parameter is deprecated. +None was added as valid parameter value.

    +
    +

  • +
  • handle_mask (callable, 'first_found' or None, optional) –

    If None the result will have no mask. If 'first_found' the +result will have a copied version of the first operand that has a +mask). If it is a callable then the specified callable must +create the results mask and if necessary provide a copy. +Default is numpy.logical_or.

    +
    +

    New in version 1.2.

    +
    +

  • +
  • handle_meta (callable, 'first_found' or None, optional) –

    If None the result will have no meta. If 'first_found' the +result will have a copied version of the first operand that has a +(not empty) meta. If it is a callable then the specified callable must +create the results meta and if necessary provide a copy. +Default is None.

    +
    +

    New in version 1.2.

    +
    +

  • +
  • compare_wcs (callable, 'first_found' or None, optional) –

    If None the result will have no wcs and no comparison between +the wcs of the operands is made. If 'first_found' the +result will have a copied version of the first operand that has a +wcs. If it is a callable then the specified callable must +compare the wcs. The resulting wcs will be like if False +was given otherwise it raises a ValueError if the comparison was +not successful. Default is 'first_found'.

    +
    +

    New in version 1.2.

    +
    +

  • +
  • uncertainty_correlation (number or ~numpy.ndarray, optional) –

    The correlation between the two operands is used for correct error +propagation for correlated data as given in: +https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulas +Default is 0.

    +
    +

    New in version 1.2.

    +
    +

  • +
+
+
+
+
kwargs :

Any other parameter that should be passed to the callables used.

+
+
+
+
Returns:
+

result – The resulting dataset

+
+
Return type:
+

~astropy.nddata.NDData-like

+
+
+

Notes

+

If a callable is used for mask, wcs or meta the +callable must accept the corresponding attributes as first two +parameters. If the callable also needs additional parameters these can be +defined as kwargs and must start with "wcs_" (for wcs callable) or +"meta_" (for meta callable). This startstring is removed before the +callable is called.

+

"first_found" can also be abbreviated with "ff".

+
+ +
+
+property data
+

An array representing the raw data stored in this instance. It +implements a setter.

+
+ +
+
+classmethod divide(operand, operand2=None, **kwargs)
+

Performs division by evaluating self / operand.

+
+
Parameters:
+
    +
  • operand (NDData-like instance) – If operand2 is None or not given it will perform the operation +self / operand. +If operand2 is given it will perform operand / operand2. +If the method was called on a class rather than on the instance +operand2 must be given.

  • +
  • operand2 (NDData-like instance) – If operand2 is None or not given it will perform the operation +self / operand. +If operand2 is given it will perform operand / operand2. +If the method was called on a class rather than on the instance +operand2 must be given.

  • +
  • propagate_uncertainties (bool or None, optional) –

    If None the result will have no uncertainty. If False the +result will have a copied version of the first operand that has an +uncertainty. If True the result will have a correctly propagated +uncertainty from the uncertainties of the operands but this assumes +that the uncertainties are NDUncertainty-like. Default is True.

    +
    +

    Changed in version 1.2: This parameter must be given as keyword-parameter. Using it as +positional parameter is deprecated. +None was added as valid parameter value.

    +
    +

  • +
  • handle_mask (callable, 'first_found' or None, optional) –

    If None the result will have no mask. If 'first_found' the +result will have a copied version of the first operand that has a +mask). If it is a callable then the specified callable must +create the results mask and if necessary provide a copy. +Default is numpy.logical_or.

    +
    +

    New in version 1.2.

    +
    +

  • +
  • handle_meta (callable, 'first_found' or None, optional) –

    If None the result will have no meta. If 'first_found' the +result will have a copied version of the first operand that has a +(not empty) meta. If it is a callable then the specified callable must +create the results meta and if necessary provide a copy. +Default is None.

    +
    +

    New in version 1.2.

    +
    +

  • +
  • compare_wcs (callable, 'first_found' or None, optional) –

    If None the result will have no wcs and no comparison between +the wcs of the operands is made. If 'first_found' the +result will have a copied version of the first operand that has a +wcs. If it is a callable then the specified callable must +compare the wcs. The resulting wcs will be like if False +was given otherwise it raises a ValueError if the comparison was +not successful. Default is 'first_found'.

    +
    +

    New in version 1.2.

    +
    +

  • +
  • uncertainty_correlation (number or ~numpy.ndarray, optional) –

    The correlation between the two operands is used for correct error +propagation for correlated data as given in: +https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulas +Default is 0.

    +
    +

    New in version 1.2.

    +
    +

  • +
+
+
+
+
kwargs :

Any other parameter that should be passed to the callables used.

+
+
+
+
Returns:
+

result – The resulting dataset

+
+
Return type:
+

~astropy.nddata.NDData-like

+
+
+

Notes

+

If a callable is used for mask, wcs or meta the +callable must accept the corresponding attributes as first two +parameters. If the callable also needs additional parameters these can be +defined as kwargs and must start with "wcs_" (for wcs callable) or +"meta_" (for meta callable). This startstring is removed before the +callable is called.

+

"first_found" can also be abbreviated with "ff".

+
+ +
+
+property mask
+

Get or set the mask of the data.

+
+ +
+
+classmethod max(**kwargs)
+
+ +
+
+classmethod mean(**kwargs)
+
+ +
+
+meta = None
+
+ +
+
+classmethod min(**kwargs)
+
+ +
+
+classmethod multiply(operand, operand2=None, **kwargs)
+

Performs multiplication by evaluating self * operand.

+
+
Parameters:
+
    +
  • operand (NDData-like instance) – If operand2 is None or not given it will perform the operation +self * operand. +If operand2 is given it will perform operand * operand2. +If the method was called on a class rather than on the instance +operand2 must be given.

  • +
  • operand2 (NDData-like instance) – If operand2 is None or not given it will perform the operation +self * operand. +If operand2 is given it will perform operand * operand2. +If the method was called on a class rather than on the instance +operand2 must be given.

  • +
  • propagate_uncertainties (bool or None, optional) –

    If None the result will have no uncertainty. If False the +result will have a copied version of the first operand that has an +uncertainty. If True the result will have a correctly propagated +uncertainty from the uncertainties of the operands but this assumes +that the uncertainties are NDUncertainty-like. Default is True.

    +
    +

    Changed in version 1.2: This parameter must be given as keyword-parameter. Using it as +positional parameter is deprecated. +None was added as valid parameter value.

    +
    +

  • +
  • handle_mask (callable, 'first_found' or None, optional) –

    If None the result will have no mask. If 'first_found' the +result will have a copied version of the first operand that has a +mask). If it is a callable then the specified callable must +create the results mask and if necessary provide a copy. +Default is numpy.logical_or.

    +
    +

    New in version 1.2.

    +
    +

  • +
  • handle_meta (callable, 'first_found' or None, optional) –

    If None the result will have no meta. If 'first_found' the +result will have a copied version of the first operand that has a +(not empty) meta. If it is a callable then the specified callable must +create the results meta and if necessary provide a copy. +Default is None.

    +
    +

    New in version 1.2.

    +
    +

  • +
  • compare_wcs (callable, 'first_found' or None, optional) –

    If None the result will have no wcs and no comparison between +the wcs of the operands is made. If 'first_found' the +result will have a copied version of the first operand that has a +wcs. If it is a callable then the specified callable must +compare the wcs. The resulting wcs will be like if False +was given otherwise it raises a ValueError if the comparison was +not successful. Default is 'first_found'.

    +
    +

    New in version 1.2.

    +
    +

  • +
  • uncertainty_correlation (number or ~numpy.ndarray, optional) –

    The correlation between the two operands is used for correct error +propagation for correlated data as given in: +https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulas +Default is 0.

    +
    +

    New in version 1.2.

    +
    +

  • +
+
+
+
+
kwargs :

Any other parameter that should be passed to the callables used.

+
+
+
+
Returns:
+

result – The resulting dataset

+
+
Return type:
+

~astropy.nddata.NDData-like

+
+
+

Notes

+

If a callable is used for mask, wcs or meta the +callable must accept the corresponding attributes as first two +parameters. If the callable also needs additional parameters these can be +defined as kwargs and must start with "wcs_" (for wcs callable) or +"meta_" (for meta callable). This startstring is removed before the +callable is called.

+

"first_found" can also be abbreviated with "ff".

+
+ +
+
+property psf
+

Image representation of the PSF for the dataset.

+

Should be ndarray-like.

+
+ +
+
+set_section(section, input_data)[source]
+

Sets only a section of the data. This method is meant to prevent +fragmentation in the Python heap, by reusing the internal structures +instead of replacing them with new ones.

+
+
Parameters:
+
    +
  • section (slice) – The area that will be replaced

  • +
  • input_data (NDData-like instance) – This object needs to implement at least data, uncertainty, +and mask. Their entire contents will replace the data in the +area defined by section.

  • +
+
+
+

Examples

+
>>> def setup():
+...     sec = NDData(np.zeros((100,100)))
+...     ad[0].nddata.set_section(
+...         (slice(None,100),slice(None,100)),
+...         sec
+...     )
+...
+>>> setup()  
+
+
+
+ +
+
+property shape
+

The shape of the data.

+
+ +
+
+property size
+

The size of the data.

+
+ +
+
+classmethod subtract(operand, operand2=None, **kwargs)
+

Performs subtraction by evaluating self - operand.

+
+
Parameters:
+
    +
  • operand (NDData-like instance) – If operand2 is None or not given it will perform the operation +self - operand. +If operand2 is given it will perform operand - operand2. +If the method was called on a class rather than on the instance +operand2 must be given.

  • +
  • operand2 (NDData-like instance) – If operand2 is None or not given it will perform the operation +self - operand. +If operand2 is given it will perform operand - operand2. +If the method was called on a class rather than on the instance +operand2 must be given.

  • +
  • propagate_uncertainties (bool or None, optional) –

    If None the result will have no uncertainty. If False the +result will have a copied version of the first operand that has an +uncertainty. If True the result will have a correctly propagated +uncertainty from the uncertainties of the operands but this assumes +that the uncertainties are NDUncertainty-like. Default is True.

    +
    +

    Changed in version 1.2: This parameter must be given as keyword-parameter. Using it as +positional parameter is deprecated. +None was added as valid parameter value.

    +
    +

  • +
  • handle_mask (callable, 'first_found' or None, optional) –

    If None the result will have no mask. If 'first_found' the +result will have a copied version of the first operand that has a +mask). If it is a callable then the specified callable must +create the results mask and if necessary provide a copy. +Default is numpy.logical_or.

    +
    +

    New in version 1.2.

    +
    +

  • +
  • handle_meta (callable, 'first_found' or None, optional) –

    If None the result will have no meta. If 'first_found' the +result will have a copied version of the first operand that has a +(not empty) meta. If it is a callable then the specified callable must +create the results meta and if necessary provide a copy. +Default is None.

    +
    +

    New in version 1.2.

    +
    +

  • +
  • compare_wcs (callable, 'first_found' or None, optional) –

    If None the result will have no wcs and no comparison between +the wcs of the operands is made. If 'first_found' the +result will have a copied version of the first operand that has a +wcs. If it is a callable then the specified callable must +compare the wcs. The resulting wcs will be like if False +was given otherwise it raises a ValueError if the comparison was +not successful. Default is 'first_found'.

    +
    +

    New in version 1.2.

    +
    +

  • +
  • uncertainty_correlation (number or ~numpy.ndarray, optional) –

    The correlation between the two operands is used for correct error +propagation for correlated data as given in: +https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulas +Default is 0.

    +
    +

    New in version 1.2.

    +
    +

  • +
+
+
+
+
kwargs :

Any other parameter that should be passed to the callables used.

+
+
+
+
Returns:
+

result – The resulting dataset

+
+
Return type:
+

~astropy.nddata.NDData-like

+
+
+

Notes

+

If a callable is used for mask, wcs or meta the +callable must accept the corresponding attributes as first two +parameters. If the callable also needs additional parameters these can be +defined as kwargs and must start with "wcs_" (for wcs callable) or +"meta_" (for meta callable). This startstring is removed before the +callable is called.

+

"first_found" can also be abbreviated with "ff".

+
+ +
+
+classmethod sum(**kwargs)
+
+ +
+
+transpose()[source]
+

Transpose the data. This is not a copy of the data.

+
+ +
+
+property uncertainty
+

Uncertainty in the dataset, if any.

+

Should have an attribute uncertainty_type that defines what kind of +uncertainty is stored, such as 'std' for standard deviation or +'var' for variance. A metaclass defining such an interface is +~astropy.nddata.NDUncertainty but isn’t mandatory.

+
+
Type:
+

any type

+
+
+
+ +
+
+property unit
+

Unit for the dataset, if any.

+
+
Type:
+

~astropy.units.Unit

+
+
+
+ +
+
+property variance
+

A convenience property to access the contents of uncertainty, +squared (as the uncertainty data is stored as standard deviation).

+
+ +
+
+property wcs
+

The WCS of the data. This is a gWCS object, not a FITS WCS object.

+

This is returning wcs from an inhertited class, see NDData.wcs for more +details.

+
+ +
+
+property window
+

Interface to access a section of the data, using lazy access +whenever possible.

+
+
Returns:
+

    +
  • An instance of NDWindowing, which provides __getitem__,

  • +
  • to allow the use of square brackets when specifying the window.

  • +
  • Ultimately, an NDWindowingAstrodata instance is returned.

  • +
+

+
+
+

Examples

+
>>> ad[0].nddata.window[100:200, 100:200]  
+<NDWindowingAstrodata .....>
+
+
+
+ +
+ +
+
+class astrodata.Section(*args, **kwargs)[source]
+

Bases: tuple

+

A class to handle n-dimensional sections

+
+
+__add__(value, /)
+

Return self+value.

+
+ +
+
+__annotations__ = {}
+
+ +
+
+__class_getitem__()
+

See PEP 585

+
+ +
+
+__contains__(key, /)
+

Return bool(key in self).

+
+ +
+
+__eq__(value, /)
+

Return self==value.

+
+ +
+
+__ge__(value, /)
+

Return self>=value.

+
+ +
+
+__getattr__(attr)[source]
+
+ +
+
+__getattribute__(name, /)
+

Return getattr(self, name).

+
+ +
+
+__getitem__(key, /)
+

Return self[key].

+
+ +
+
+__getnewargs__()[source]
+
+ +
+
+__gt__(value, /)
+

Return self>value.

+
+ +
+
+__hash__()
+

Return hash(self).

+
+ +
+
+__iter__()
+

Implement iter(self).

+
+ +
+
+__le__(value, /)
+

Return self<=value.

+
+ +
+
+__len__()
+

Return len(self).

+
+ +
+
+__lt__(value, /)
+

Return self<value.

+
+ +
+
+__module__ = 'astrodata.utils'
+
+ +
+
+__mul__(value, /)
+

Return self*value.

+
+ +
+
+__ne__(value, /)
+

Return self!=value.

+
+ +
+
+static __new__(cls, *args, **kwargs)[source]
+
+ +
+
+__repr__()[source]
+

Return repr(self).

+
+ +
+
+__rmul__(value, /)
+

Return value*self.

+
+ +
+
+asIRAFsection()[source]
+

Deprecated, see as_iraf_section

+
+ +
+
+as_iraf_section()[source]
+

Produce string of style ‘[x1:x2,y1:y2]’ that is 1-indexed +and end-inclusive

+
+ +
+
+asslice(add_dims=0)[source]
+

Return the Section object as a slice/list of slices. Higher +dimensionality can be achieved with the add_dims parameter.

+
+ +
+
+property axis_dict
+
+ +
+
+contains(section)[source]
+

Return True if the supplied section is entirely within self

+
+ +
+
+count(value, /)
+

Return number of occurrences of value.

+
+ +
+
+static from_shape(value)[source]
+

Produce a Section object defining a given shape.

+
+ +
+
+static from_string(value)[source]
+

The inverse of __str__, produce a Section object from a string.

+
+ +
+
+index(value, start=0, stop=9223372036854775807, /)
+

Return first index of value.

+

Raises ValueError if the value is not present.

+
+ +
+
+is_same_size(section)[source]
+

Return True if the Sections are the same size

+
+ +
+
+property ndim
+

The number of dimensions in the section.

+
+ +
+
+overlap(section)[source]
+

Determine whether the two sections overlap. If so, the Section +common to both is returned, otherwise None

+
+ +
+
+shift(*shifts)[source]
+

Shift a section in each direction by the specified amount

+
+ +
+ +
+
+class astrodata.TagSet(add=None, remove=None, blocked_by=None, blocks=None, if_present=None)[source]
+

Bases: TagSet

+

Named tuple that is used by tag methods to return which actions should +be performed on a tag set.

+

All the attributes are optional, and any combination of them can be used, +allowing to create complex tag structures. Read the documentation on the +tag-generating algorithm if you want to better understand the interactions.

+

The simplest TagSet, though, tends to just add tags to the global set.

+

It can be initialized by position, like any other tuple (the order of the +arguments is the one in which the attributes are listed below). It can +also be initialized by name.

+
+
+add
+

Tags to be added to the global set

+
+
Type:
+

set of str, optional

+
+
+
+ +
+
+remove
+

Tags to be removed from the global set

+
+
Type:
+

set of str, optional

+
+
+
+ +
+
+blocked_by
+

Tags that will prevent this TagSet from being applied

+
+
Type:
+

set of str, optional

+
+
+
+ +
+
+blocks
+

Other TagSets containing these won’t be applied

+
+
Type:
+

set of str, optional

+
+
+
+ +
+
+if_present
+

This TagSet will be applied only all of these tags are present

+
+
Type:
+

set of str, optional

+
+
+
+ +

Examples

+
>>> TagSet()  
+TagSet(
+    add=set(),
+    remove=set(),
+    blocked_by=set(),
+    blocks=set(),
+    if_present=set()
+)
+>>> TagSet({'BIAS', 'CAL'})  
+TagSet(
+    add={'BIAS', 'CAL'},
+    remove=set(),
+    blocked_by=set(),
+    blocks=set(),
+    if_present=set()
+)
+>>> TagSet(remove={'BIAS', 'CAL'}) 
+TagSet(
+    add=set(),
+    remove={'BIAS', 'CAL'},
+    blocked_by=set(),
+    blocks=set(),
+    if_present=set()
+)
+
+
+

Create new instance of TagSet(add, remove, blocked_by, blocks, if_present)

+
+
+__add__(value, /)
+

Return self+value.

+
+ +
+
+__annotations__ = {}
+
+ +
+
+__class_getitem__()
+

See PEP 585

+
+ +
+
+__contains__(key, /)
+

Return bool(key in self).

+
+ +
+
+__eq__(value, /)
+

Return self==value.

+
+ +
+
+__ge__(value, /)
+

Return self>=value.

+
+ +
+
+__getattribute__(name, /)
+

Return getattr(self, name).

+
+ +
+
+__getitem__(key, /)
+

Return self[key].

+
+ +
+
+__getnewargs__()
+

Return self as a plain tuple. Used by copy and pickle.

+
+ +
+
+__gt__(value, /)
+

Return self>value.

+
+ +
+
+__hash__()
+

Return hash(self).

+
+ +
+
+__iter__()
+

Implement iter(self).

+
+ +
+
+__le__(value, /)
+

Return self<=value.

+
+ +
+
+__len__()
+

Return len(self).

+
+ +
+
+__lt__(value, /)
+

Return self<value.

+
+ +
+
+__match_args__ = ('add', 'remove', 'blocked_by', 'blocks', 'if_present')
+
+ +
+
+__module__ = 'astrodata.utils'
+
+ +
+
+__mul__(value, /)
+

Return self*value.

+
+ +
+
+__ne__(value, /)
+

Return self!=value.

+
+ +
+
+static __new__(cls, add=None, remove=None, blocked_by=None, blocks=None, if_present=None)[source]
+

Create new instance of TagSet(add, remove, blocked_by, blocks, if_present)

+
+ +
+
+__repr__()
+

Return a nicely formatted representation string

+
+ +
+
+__rmul__(value, /)
+

Return value*self.

+
+ +
+
+__slots__ = ()
+
+ +
+
+add
+

Alias for field number 0

+
+ +
+
+blocked_by
+

Alias for field number 2

+
+ +
+
+blocks
+

Alias for field number 3

+
+ +
+
+count(value, /)
+

Return number of occurrences of value.

+
+ +
+
+if_present
+

Alias for field number 4

+
+ +
+
+index(value, start=0, stop=9223372036854775807, /)
+

Return first index of value.

+

Raises ValueError if the value is not present.

+
+ +
+
+remove
+

Alias for field number 1

+
+ +
+ +
+
+astrodata.add_header_to_table(table)[source]
+

Add a FITS header to a table.

+
+ +
+
+astrodata.astro_data_descriptor(fn)[source]
+

Decorator that will mark a class method as an AstroData descriptor. +Useful to produce list of descriptors, for example.

+

If used in combination with other decorators, this one must be the +one on the top (ie. the last one applying). It doesn’t modify the +method in any other way.

+
+
Parameters:
+

fn (method) – The method to be decorated

+
+
Return type:
+

The tagged method (not a wrapper)

+
+
+
+ +
+
+astrodata.astro_data_tag(fn)[source]
+

Decorator that marks methods of an AstroData derived class as part of +the tag-producing system.

+

It wraps the method around a function that will ensure a consistent return +value: the wrapped method can return any sequence of sequences of strings, +and they will be converted to a TagSet. If the wrapped method +returns None, it will be turned into an empty TagSet.

+
+
Parameters:
+

fn (method) – The method to be decorated

+
+
Return type:
+

A wrapper function

+
+
+
+ +
+
+astrodata.create(*args, **kwargs)[source]
+

Return an AstroData object from data.

+

For implementation details, see +create_from_scratch()

+
+ +
+
+astrodata.from_file(*args, **kwargs)[source]
+

Return an AstroData object from a file.

+

For implementation details, see +get_astro_data().

+
+ +
+
+astrodata.open(*args, **kwargs)[source]
+

Return an AstroData object from a file (deprecated, use +from_file()).

+
+ +
+
+astrodata.returns_list(fn)[source]
+

Decorator to ensure that descriptors that should return a list (of one +value per extension) only returns single values when operating on single +slices; and vice versa.

+

This is a common case, and you can use the decorator to simplify the +logic of your descriptors.

+
+
Parameters:
+

fn (method) – The method to be decorated

+
+
Return type:
+

A function

+
+
+
+ +
+
+astrodata.version()[source]
+

Return the version of astrodata.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/index.html b/manuals/index.html new file mode 100644 index 00000000..f8ad0794 --- /dev/null +++ b/manuals/index.html @@ -0,0 +1,148 @@ + + + + + + + Astrodata Manual — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Astrodata Manual

+
+

Document ID

+

PIPE-USER-120_AstrodataMasterManual

+
+
+
+

This documentation provides different levels of information:

+ +

If you need support related to astrodata, see Astrodata Support.

+

Appendix

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/progmanual/adclass.html b/manuals/progmanual/adclass.html new file mode 100644 index 00000000..f2b17088 --- /dev/null +++ b/manuals/progmanual/adclass.html @@ -0,0 +1,549 @@ + + + + + + + AstroData and Derivatives — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

AstroData and Derivatives

+

The AstroData class is the main interface to the package. When opening files +or creating new objects, a derivative of this class is returned, as the base +AstroData class is not intended to be used directly. It provides the logic to +calculate the tag set for an image, which is common to all +data products. Aside from that, it lacks any kind of specialized knowledge +about the different instruments that produce the FITS files. More importantly, +it defines two methods (info() and +load()) that read in and offer access to +data in FITS files. When extending to other file types, these methods should +be re-implemented. AstroData also defines several useful properties and +methods for FITS files specifically, such as astrodata.AstroData.phu(), +astrodata.AstroData.hdr(), and astrodata.AstroData.write(), +that should also be overridden when extending to other file types.

+

AstroData defines a common interface. Much of it consists of implementing +semantic behavior (access to components through indices, like a list; +arithmetic using standard operators; etc), mostly by implementing standard +Python methods:

+
    +
  • Defines a common __init__() function.

  • +
  • Implements __deepcopy__.

  • +
  • Implements __iter__ to allow sequential iteration over the main set of +components (e.g., FITS science HDUs).

  • +
  • Implements __getitem__ to allow data slicing (e.g., ad[2:4] returns +a new AstroData instance that contains only the third and fourth main +components).

  • +
  • Implements __delitem__ to allow for data removal based on index. It does +not define __setitem__, though. The basic AstroData series of classes +only allows to append new data blocks, not to replace them in one sweeping +move.

  • +
  • Implements __add__, __sub__, __mul__, __truediv__, and +their in-place equivalents, based on them.

  • +
+

There are a few other methods. For a detailed discussion, please refer to the +Reference API.

+
+

The tags Property

+

Additionally, and crucial to the package, AstroData offers a tags property, +that returns a resolved set of textual tags that describe the object +represented by an instance (as a set of strings). This is useful for quickly +determining if a certain dataset belongs to an arbitrary category.

+

The implementation for the tags property is just a call to +AstroData._process_tags(). This function implements the actual logic behind +calculating the tag set (described below). A derivative class +could override this to provide a different logic, but this is not recommended +unless there is a very good reason to do so.

+

For an example of how tags are resolved, seet Tags.

+
+
+

Writing an AstroData Derivative

+

We will step through the process of creating a new AstroData derivative.

+
+

Create a new class

+

The first step to creating a new AstroData derivative is to create a new +class that inherits from AstroData. If the new class is intended to handle +non-FITS files, it should override the info and load methods. In this +case, we will create a class to handle the following ASCII file:

+
Wavelength (nm)  Flux (erg/cm2/s/nm)
+1.0              1.0
+2.0              1.5
+3.0              2.0
+4.0              2.5
+5.0              3.0
+6.0              2.5
+7.0              1.0
+
+
+

Let’s create our class to just override the info and load methods, and return a +formatted string containing the information in the header of the file when the +AstroData.info method is called:

+
from astrodata import AstroData, NDAstroData
+
+class AstroDataMyFile(AstroData):
+    _wavelength: None | NDAstroData
+    _flux: None | NDAstroData
+    _header: list[str]
+
+
+    def __init__(self, source):
+        super().__init__(source)
+        self._wavelength = None
+        self._flux = None
+        self._header = []
+
+    @staticmethod
+    def _matches_data(source):
+        return source.lower().endswith('.txt')
+
+    def info(self) -> str:
+        def batch(iterable, n=1):
+            l = len(iterable)
+            for ndx in range(0, l, n):
+                yield iterable[ndx:min(ndx + n, l)]
+
+        # Just printing out information retrieved from the text file
+        # header.
+        return ' || '.join(
+          f'{w:>10} {f:>10}'
+          for w, f in batch(self._header, 2)
+        )
+
+    def load(self, path: str):
+        with open(path, 'r') as f:
+            # First line is the header info
+            self._header = f.readline().split()
+
+            # This should keep units with the data
+            self._header = [
+              (col, unit)
+              for col, unit in zip(self._header[0::2], self._header[1::2])
+            ]
+
+            for line in f:
+                  w, f = line.split()
+                  self._wavelength.append(float(w))
+                  self._flux.append(float(f))
+
+
+

We now have a class that can be used to load and store data from our ASCII +file. The info method returns a formatted string containing the header +information, and the load method reads in the data from the file. The +_matches_data method is used to determine if the file is of the correct +type. In this case, we are just checking that the file extension is .txt.

+

However, suppose we only want to use this class for files that contain +wavelength and flux information and nothing else. In that case, we can check +the header information in the _matches_data method:

+
@staticmethod
+def _matches_data(source):
+    if isinstance(source, str):
+        with open(source, 'r') as f:
+            header = f.readline().split()
+
+    else:
+        header = source.readline().split()
+
+    # Check that the header contains no extra information.
+    if any(col not in ('Wavelength', 'Flux') for col, _ in header):
+        return False
+
+    # Check that the header contains both wavelength and flux information.
+    return all(
+      any(col == name for col, _ in header)
+      for name in ('Wavelength', 'Flux')
+    )
+
+
+
+

Note

+

To conserve space in this document, we will only include modified code +snippets (with any necessary context) for the rest of the examples. At the +end of the document there will be an executable with the “final” code. Feel +free to use this code as a template.

+
+

If there were other metadata contained in the file header, such as intrument +and mode information, we could use that to determine if the file is of the +correct type.

+
+
+

Code Organization (Optional)

+

The code for our new class can be placed in a single file, but it is often +useful to organize our code into multiple files depending on their scope and +purpose.

+

In DRAGONS, astrodata classes for individual instruments are organized into +packages. We’ll use DRAGONS’ GMOS instrument as an example (see +the DRAGONS repository +for the full code). It has the following structure:

+
gemini_instruments
+    __init__.py
+    gmos
+        tests/
+        __init__.py
+        adclass.py
+        lookup.py
+
+
+

Where adclass.py contains the AstroDataGmos class, and lookup.py +contains a dictionary of filter names and their central wavelengths. The +__init__.py files are used to import the classes and functions that are +needed by the package. For example, the gmos/__init__.py file contains the +following:

+
__all__ = ['AstroDataGmos']
+
+from astrodata import factory
+from ..gemini import addInstrumentFilterWavelengths
+from .adclass import AstroDataGmos
+from .lookup import filter_wavelengths
+
+factory.addClass(AstroDataGmos)
+# Use the generic GMOS name for both GMOS-N and GMOS-S
+addInstrumentFilterWavelengths('GMOS', filter_wavelengths)
+
+
+

lookup.py contains information that is specific to the instrument but is +not explicitly required by the AstroDataGmos class. In this case, it is a +dictionary of filter names and their central wavelengths. The +addInstrumentFilterWavelengths function is used to add this information to +the AstroDataGemini class, which is the parent class of AstroDataGmos. +This function is defined in the gemini/__init__.py file, which is imported +by gmos/__init__.py. The motivation here is to keep these lookup data +separated from the class so changes to these data are only reflected in one and +will not modify the class itself.

+

The tests/ directory contains unit tests for the AstroDataGmos class. +Determining the nature and scale of tests is left to the developer.

+

Some highlights:

+
    +
  • __keyword_dict[1] defines one-to-one mappings, assigning a more +readable moniker for an HDU header keyword. The idea here is to prevent +hard-coding the names of the keywords, in the actual code. While these are +typically quite stable and not prone to change, it’s better to be safe than +sorry, and this can come in useful during instrument development, which is +the more likely source of instability. The actual value can be extracted by +calling self._keyword_for('moniker').

  • +
  • _matches_data is a static method. It does not have any knowledge about +the class itself, and it does not work on an instance of the class: it’s +a member of the class just to make it easier for the AstroData registry to +find it. This method is passed some object containing cues of the internal +structure and contents of the data. This could be, for example, an instance +of HDUList. Using these data, _matches_data must return a boolean, +with True meaning “I know how to handle this data”.

    +

    Note that True does not mean “I have full knowledge of the data”. It +is acceptable for more than one class to claim compatibility. For a GMOS FITS +file, the classes that will return True are: AstroData (because it is +a FITS file that comply with certain minimum requirements), +~gemini_instruments.gemini.AstroDataGemini (the data contains Gemini +Facility common metadata), and ~gemini_instruments.gmos.AstroDataGmos (the +actual handler!).

    +

    But this does not mean that multiple classes can be valid “final” candidates. +If AstroData’s automatic class discovery finds more than one class claiming +matching with the data, it will start discarding them on the basis of +inheritance: any class that appears in the inheritance tree of another one is +dropped, because the more specialized one is preferred. If at some point the +algorithm cannot find more classes to drop, and there is more than one left +in the list, an exception will occur, as AstroData will have no way to choose +one over the other.

    +
  • +
  • A number of “tag methods” have been declared. Their naming is a convention, +at the end of the day (the “_tag_” prefix, and the related “_status_” +one, are just hints for the programmer): each team should establish +a convention that works for them. What is important here is to decorate +them using ~astrodata.astro_data_tag, which earmarks the method so that it +can be discovered later, and ensures that it returns an appropriate value.

    +

    A tag method will return either a ~astrodata.TagSet instance (which can be +empty), or None, which is the same as returning an empty +~astrodata.TagSet[2].

    +

    All these methods will be executed when looking up for tags, and it’s up +to the tag set construction algorithm (see Tags) to figure out the final +result. In theory, one could provide just one big method, but this is +feasible only when the logic behind deciding the tag set is simple. The +moment that there are a few competing alternatives, with some conditions +precluding other branches, one may end up with a rather complicated dozens of +lines of logic. Let the algorithm do the heavy work for you: split the tags +as needed to keep things simple, with an easy to understand logic.

    +

    Also, keeping the individual (or related) tags in separate methods lets you +exploit the inheritance, keeping common ones at a higher level, and +redefining them as needed later on, at derived classes.

    +

    Please, refer to ~gemini_instruments.gemini.AstroDataGemini, +~gemini_instruments.gmos.AstroDataGmos, and +~gemini_instruments.gnirs.AstroDataGnirs for examples using most of the +features.

    +
  • +
  • The astrodata.AstroData.read method calls the astrodata.fits.read_fits +function, which uses metadata in the FITS headers to determine how the data +should be stored in the AstroData object. In particular, the EXTNAME +and EXTVER keywords are used to assign individual FITS HDUs, using the +same names (SCI, DQ, and VAR) as Gemini-IRAF for the data, +mask, and variance planes. A SCI HDU must exist if there is +another HDU with the same EXTVER, or else an error will occur.

    +

    If the raw data do not conform to this format, the astrodata.AstroData.read +method can be overridden by your class, by having it call the +astrodata.fits.read_fits function with an additional parameter, +extname_parser, that provides a function to modify the header. This +function will be called on each HDU before further processing. As an example, +the SOAR Adaptive Module Imager (SAMI) instrument writes raw data as +a 4-extension MEF file, with the extensions having EXTNAME values +im1, im2, etc. These need to be modified to SCI, and an +appropriate EXTVER keyword added` [3]. This can be done by +writing a suitable read method for the AstroDataSami class:

    +
    @classmethod
    +def read(cls, source, extname_parser=None):
    +    def sami_parser(hdu):
    +        m = re.match('im(\d)', hdu.header.get('EXTNAME', ''))
    +        if m:
    +            hdu.header['EXTNAME'] = ('SCI', 'Added by AstroData')
    +            hdu.header['EXTVER'] = (int(m.group(1)), 'Added by AstroData')
    +
    +    return super().read(source, extname_parser=extname_parser)
    +
    +
    +
  • +
  • Descriptors will make the bulk of the class: again, the name is arbitrary, +and it should be descriptive. What may be important here is to use +~astrodata.astro_data_descriptor to decorate them. This is not required, +because unlike tag methods, descriptors are meant to be called explicitly by +the programmer, but they can still be marked (using this decorator) to be +listed when calling the descriptors property. The decorator does not +alter the descriptor input or output in any way, so it is always safe to use +it, and you probably should, unless there’s a good reason against it (e.g., +if a descriptor is deprecated and you don’t want it to show up in lookups).

    +

    More detailed information can be found in Descriptors.

    +
  • +
+
+
+

Register your class

+

Finally, you need to include your class in the AstroData Registry. This is +an internal structure with a list of all the AstroData-derived classes that +we want to make available for our programs. Including the classes in this +registry is an important step, because a file should be opened using +astrodata.from_file or astrodata.create_from_scratch, which uses the +registry to identify the appropriate class (via the _matches_data methods), +instead of having the user specify it explicitly.

+

A typical __init__.py file on an instrument package (example above) will +look like this:

+
__all__ = ['AstroDataMyInstrument']
+
+from astrodata import factory
+from .adclass import AstroDataMyInstrument
+
+factory.add_class(AstroDataMyInstrument)
+
+
+

The call to factory.add_class is the one registering the class. This step +needs to be done before the class can be used effectively in the +AstroData system. Placing the registration step in the __init__.py file is +convenient, because importing the package will be enough!

+

Thus, a script making use of DRAGONS’ AstroData to manipulate GMOS data +could start like this:

+
import astrodata
+from gemini_instruments import gmos
+
+...
+
+ad = astrodata.open(some_file)
+
+
+

The first import line is not needed, technically, because the gmos package +will import it too, anyway, but we’ll probably need the astrodata package +in the namespace anyway, and it’s always better to be explicit. Our +typical DRAGONS scripts and modules start like this, instead:

+
import astrodata
+import gemini_instruments
+
+
+

gemini_instruments imports all the packages under it, making knowledge +about all Gemini instruments available for the script, which is perfect for a +multi-instrument pipeline, for example. Loading all the instrument classes is +not typically a burden on memory, though, so it’s easier for everyone to take +the more general approach. It also makes things easier on the end user, because +they won’t need to know internal details of our packages (like their naming +scheme). We suggest this “cascade import” scheme for all new source trees, +letting the user decide which level of detail they need.

+

As an additional step, the __init__.py file in a package may do extra +initialization. For example, for the Gemini modules, one piece of functionality +that is shared across instruments is a descriptor that translates a filter’s +name (say “u” or “FeII”) to its central wavelength (e.g., +0.35µm, 1.644µm). As it is a rather common function for us, it is implemented +by ~gemini_instruments.gemini.AstroDataGemini. This class does not know +about its daughter classes, though, meaning that it cannot know about the +filters offered by their instruments. Instead, we offer a function that can +be used to update the filter → wavelength mapping in +gemini_instruments.gemini.lookup so that it is accessible by the +~gemini_instruments.gemini.AstroDataGemini-level descriptor. So our +gmos/__init__.py looks like this:

+
__all__ = ['AstroDataGmos']
+
+from astrodata import factory
+from ..gemini import addInstrumentFilterWavelengths
+from .adclass import AstroDataGmos
+from .lookup import filter_wavelengths
+
+factory.addClass(AstroDataGmos)
+# Use the generic GMOS name for both GMOS-N and GMOS-S
+addInstrumentFilterWavelengths('GMOS', filter_wavelengths)
+
+
+

where ~gemini_instruments.gemini.addInstrumentFilterWavelengths is provided +by the gemini package to perform the update in a controlled way.

+

We encourage package maintainers and creators to follow such explicit +initialization methods, driven by the modules that add functionality +themselves, as opposed to active discovery methods on the core code. This +favors decoupling between modules, which is generally a good idea.

+

Footnotes

+ +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/progmanual/containers.html b/manuals/progmanual/containers.html new file mode 100644 index 00000000..c7adca82 --- /dev/null +++ b/manuals/progmanual/containers.html @@ -0,0 +1,220 @@ + + + + + + + Data Containers — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Data Containers

+

The AstroData package is built around the concept of data containers. These are +objects that contain the data for a single observation, and determine the +structure of these data in memory. We have extended the Astropy NDData class +to provide the core functionality of these containers, and added a number of +mixins to provide additional functionality.

+

Specifically, we extend NDData with the following: +* astrodata.NDAstroData - the main data container class +* astrodata.NDAstroDataMixin - a mixin class that adds additional functionality +to NDData, such as the ability to access image planes and tables stored in +the meta dict as attributes of the object +* astrodata.NDArithmeticMixin - a mixin class that adds arithmetic functionality +* astrodata.NDSlicingMixin - a mixin class that adds slicing functionality

+
+

NDAstroData class

+

Our main data container is NDAstroData. Fundamentally, it is +a derivative of astropy.nddata.NDData, plus a number of mixins to add +functionality:

+
class NDAstroData(AstroDataMixin, NDArithmeticMixin, NDSlicingMixin, NDData):
+    ...
+
+
+

With these mixins, NDAstroData is extended to allow for ease and efficiency +of use, as if a common array, with extra features such as uncertainty +propogation and efficient slicing with typically array syntax.

+

Upon initialization (see astrodata.AstroData.__init__()), the +AstroData class will attempt to open the file in memory-mapping mode, which +is the default mode for opening FITS files in Astropy. This means that the data +is not loaded into memory until it is accessed, and is discarded from memory +when it is no longer needed. This is particularly important for large data +sets common in astronomy.

+

Much of NDAstroData acts to mimic the behavior of NDData and +astropy.io.fits objects, but is designed to be extensible to other +formats and means of storing, accessing, and manipulating data.

+
+
+

Slicing

+

One can already slice NDAstroData objects as with NDData, as normal Python arrays

+
# Access pixels 100-200 in both dimensions on the first image plane.
+>>> ad.data[0][100:200, 100:200].shape
+(100, 100)
+
+
+

It’s also useful to access specific “windows” in the data, which is implemented +in NDAstroData such that only the data necessary to access a window is loaded +into memory.

+

The astrodata.AstroData.window() property returns an instance of +NDWindowing, which only references the AstroData +object being windowed (i.e., it contains no direct references to the data). +NDWindowingAstroData, which has references +pointing to the memory mapped data requested by the window.

+

The base NDAstroData class provides the memory-mapping functionality built +upon by NDWindowingAstroData, with other important behaviors added by the +other mixins.

+

One addition is the variance property, which allows direct access and +setting of the data’s uncertainty, without the user needing to explicitly wrap +it as an NDUncertainty object. Internally, the variance is stored as an +ADVarianceUncertainty object, which is subclassed from Astropy’s standard +VarianceUncertainty class with the addition of a check for negative values +whenever the array is accessed.

+

NDAstroDataMixin also changes the default method of combining the mask +attributes during arithmetic operations from logical_or to bitwise_or, +since the individual bits in the mask have separate meanings.

+

The way slicing affects the wcs is also changed since DRAGONS regularly +uses the callable nature of gWCS objects and this is broken by the standard +slicing method.

+

Finally, the additional image planes and tables stored in the meta dict +are exposed as attributes of the NDAstroData object, and any image planes +that have the same shape as the parent NDAstroData object will be handled +by NDWindowingAstroData. Sections will be ignored when accessing image +planes with a different shape, as well as tables.

+
+

Note

+

We expect to make changes to NDAstroData in future releases. In particular, +we plan to make use of the unit attribute provided by the +NDData class and increase the use of memory-mapping by default. These +changes mostly represent increased functionality and we anticipate a high +(and possibly full) degree of backward compatibility.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/progmanual/descriptors.html b/manuals/progmanual/descriptors.html new file mode 100644 index 00000000..700c9180 --- /dev/null +++ b/manuals/progmanual/descriptors.html @@ -0,0 +1,194 @@ + + + + + + + Descriptors — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Descriptors

+

Descriptors are regular methods that translate metadata from the raw +data (e.g., cards from FITS headers) to values useful for the user, +potentially doing some processing in between. They exist to:

+
    +
  • Abstract the actual organization of the metadata, with arbitrarily complex +processing to generate a useful value. These abstractions can be modified +to be instrument-specific.

  • +
  • Provide a common interface to a set of instruments. This simplifies user +training (no need to learn a different API for each instrument), and +facilitates the reuse of code for pipelines and data processing.

  • +
  • They can be used to directly translate character-limited FITS header keywords +into more descriptive names of arbitrary length.

  • +
+

Descriptors should be decorated using ~astrodata.astro_data_descriptor. +The only function of this decorator is to ensure that the descriptor is marked +as such: it does not alter its input or output in any way. This lets the user +explore the API of an AstroData object via the +~astrodata.AstroData.descriptors property. Here’s an example of how we could +use a descriptor to build a simple class on top of the AstroData base class:

+
+

Note

+

The above example is oversimplified, and would only work with a fits file +containing these keywords. In practice, an AstroData extension like this +would be specific to an instrument/file format that would be resolved using +tags.

+
+

Descriptors can be decorated with returns_list() to +eliminate the need to code some logic. Some descriptors return single values, +while some return lists, one per extension. Typically, the former +descriptors refer calues associated with an entire observation (and, for MEF +files, are usually extracted from metadata in the PHU, such as airmass), +while the latter descriptors where different extensions might return +different values (and typically come from metadata in the individual HDUs, such +as gain). A list is returned even if there is only one extension in the +AstroData object, as this allows code to be written generically to iterate +over the AstroData object and the descriptor return, without needing to know +how many extensions there are.

+

The ~astrodata.core.returns_list decorator ensures that the descriptor +returns an appropriate object (value or list), using the following rules +to avoid unexpected behavior/confusing errors:

+
    +
  • If the AstroData object is not a single slice:

    +
      +
    • If the undecorated descriptor returns a list, an exception is raised +if the list is not the same length as the number of extensions.

    • +
    • If the undecorated descriptor returns a single value, the decorator +will turn it into a list of the correct length by copying this value.

    • +
    +
  • +
  • If the AstroData object is a single slice and the undecorated +descriptor returns a list, only the first element is returned.

  • +
+

An example of the use of this decorator is the NIRI +~gemini_instruments.niri.AstroDataNiri.gain descriptor, which reads the +value from a lookup table and simply returns it. A single value is only +appropriate if the AstroData object is singly-sliced and the decorator ensures +that a list is returned otherwise.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/progmanual/design.html b/manuals/progmanual/design.html new file mode 100644 index 00000000..4481c381 --- /dev/null +++ b/manuals/progmanual/design.html @@ -0,0 +1,202 @@ + + + + + + + General Design — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

General Design

+

Astronomical instruments come in a variety of forms, each with unique features +and requirements that are not readily transferable between different +instruments, observatories, and systems. However, there are also many +similarities between them, and the data reduction process is often similar +regardless of the instrument. The AstroData package is designed to provide a +common interface to astronomical data, regardless of the instrument that +produced it.

+

While at first glance this may seem counterintuitive—after all, how can a +spectrograph and a camera share the same interface?—breaking down their +common features and only developing their unique aspects once has proven to +significantly reduce the amount of code required to support each instrument. As +an example of such a system, see the Gemini Data Reduction System (DRAGONS), +which uses AstroData to support data from all of Gemini’s instruments.

+

As a developer, AstroData consists of several concepts used to automatically +resolve data based on the instrument and observation type:

+
    +
  1. AstroData - This is the primary class from which all other data classes +are derived. It is a container for the data and metadata associated with a +single astronomical observation. It is also an iterable object, with each +iteration returning a “slice” of the data, which is itself an AstroData +object. This is discussed further in Slicing.

  2. +
  3. Astrodata Tag - These are high-level metadata that describe the observation and +link it to the recipes required to process it. They are discussed further in +Tags.

  4. +
  5. Descriptors - These are a way to access the metadata in a uniform manner, +and can be used to access quantities reuiring introspection of the data +itself.

  6. +
+

Thee three concepts are discussed in detail in the following sections. +Together, they provide a way to access astronomical data in a uniform manner, +regardless of the instrument that produced it, while still being “aware” of any +percularities of a given instrument completely controlled by the developer.

+
+

Note

+

Understanding the differences between AstroData objects, AstroData tags, and +AstroData descriptors is critical to understanding and implementing to full +range of features AstroData provides. One way to think of them is:

+

1. AstroData manages data and associated metadata, which may include Tags +and Descriptors. +2. Tags are high-level metadata descripting the observation, observatory, +and instrument, that AstroData uses to automatically resolve how to read in +and process a data file. +3. Descriptors are a way to access data not found in the metadata, or +requiring some manipulation of the data itself to determine a given value. +Like a python property, but without the attribute syntax (to reflect that it +may be costly to call).

+
+

AstroData was originally designed for the Gemini Observatories, which primarily +use the FITS and FITS MEF formats for their data. While AstroData comes +out-of-the-box with FITS-specific readers and syntax, extending it to include +other file formats is straightforward. See AstroData and Derivatives for more details.

+
+

Note

+

While there is currently only FITS support, we plan to include native asdf +support as well in the future.

+
+

When using a FITS or FITS-like file, an AstroData object consists of one or +more self-contained “extensions” (data and metadata) plus additional data and +metadata that is relevant to all the extensions. In many data reduction +processes, the same operation will be performed on each extension (e.g., +subtracting an overscan region from a CCD frame) and an axiom of AstroData is +that iterating over the extensions produces AstroData “slices” which retain +knowledge of the top-level data and metadata. Since a slice has one (or more) +extensions plus this top-level (meta)data, it too is an AstroData object +and, specifically, an instance of the same subclass as its parent.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/progmanual/index.html b/manuals/progmanual/index.html new file mode 100644 index 00000000..45500020 --- /dev/null +++ b/manuals/progmanual/index.html @@ -0,0 +1,158 @@ + + + + + + + Programmer’s Manual — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Programmer’s Manual

+
+

Document ID

+

PIPE-USER-104_AstrodataProgManual

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/progmanual/intro.html b/manuals/progmanual/intro.html new file mode 100644 index 00000000..a7f0bd03 --- /dev/null +++ b/manuals/progmanual/intro.html @@ -0,0 +1,273 @@ + + + + + + + Introduction — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Introduction

+

AstroData is a Python package that provides a common interface to astronomical +data. Originally part of the DRAGONS package, the data reduction package +developed at the Gemini Observatory, it has been split into its own package to +allow its use in other projects and to be developed by a wider, public core to +suit the needs of data reduction across the field of astronomy.

+

AstroData’s common interface, the AstroData class, is used to abstract the +details of any given data into a set of Astrodata Tag, which can be used to resolve +the properties and reduction requirements of a given data file (most commonly, +a multi-extension FITS file). The AstroData class is also used to provide +methods to access the data in a consistent manner, regardless of the +underlying data format.

+

The AstroData class is not intended to be used directly by the user. Instead, +the user should use the from_file() function, which will return an +AstroData object. The from_file() function will determine the type of +data file being opened and return the appropriate subclass of AstroData.

+

For the programmer using AstroData to develop a data reduction pipeline, the +AstroData class should be subclassed to provide the functionality required +and to register the new class with the from_file() function.

+

Several examples may be found throughout the documentation (see Examples). A +simple example is shown below as a complete, executable introduction.

+
>>> class MyAstroData(AstroData):
+...     @staticmethod
+...     def _matches_data(source):
+...         # This method is used by astrodata.from_file to determine if this
+...         # class should be used to open the data. It should return True
+...         # if the data is of the correct type, False otherwise.
+...
+...         # E.g., if file limited by FITS standard or not for instrument
+...         # keyword.
+...         instrument_tags = {'INSTRUME', 'INSTRUMENT'}
+...
+...         for tag in instrument_tags:
+...             if tag in source[0].header:
+...                 return source[0].header.get(tag).upper() == 'MY_INSTRUMENT'
+...
+...         # Could return None by default since it's Falsey, but this is more
+...         # explicit and follows typing expectations.
+...         return False
+...
+...     @astro_data_tag
+...     def my_tag(self):
+...         # This method is used to define a new tag. It should return
+...         # a string that will be used as the tag name. The method name
+...         # is used as the tag name by default, but this can be overridden
+...         # by passing a name to the decorator, e.g.:
+...         # @astro_data_tag(name='my_tag_name')
+...         # The method should return None if the tag is not applicable
+...         # to the data.
+...
+...         # This checks that the Primary HDU of the data has a specific
+...         # keyword, 'MY_TAG'.
+...         if self.phu.get('MY_TAG') is not None:
+...             return TagSet(['MY_TAG'])
+...
+...         # Not strictly necessary, but here for completeness.
+...         return TagSet()
+...
+...     @astro_data_descriptor
+...     def my_descriptor(self):
+...         # This method is used to define a new descriptor. It should
+...         # return a string that will be used as the descriptor name.
+...         # The method name is used as the descriptor name by default,
+...         # but this can be overridden by passing a name to the decorator,
+...         # e.g.:
+...         # @astro_data_descriptor(name='my_descriptor_name')
+...         # The method should return None if the descriptor is not
+...         # applicable to the data.
+...
+...         # Returns None if 'MY_DESC' is not in the Primary HDU
+...         return self.phu.get('MY_DESC')
+
+>>> # Registering the new class with astrodata.factory
+>>> astrodata.factory.add_class(MyAstroData)
+
+>>> # Now, if we give it a file that has the MY_TAG keyword in the Primary HDU,
+>>> # we can open it with astrodata.from_file and it will return an instance of
+>>> # MyAstroData.
+>>> # Defining an example FITS file
+>>> from astropy.io import fits
+>>> import gwcs
+>>> import tempfile
+
+>>> # Create a new FITS HDU
+>>> phdu = fits.PrimaryHDU(data=[[1, 2], [3, 4]])
+
+>>> # Add the necessary tags to the FITS header
+>>> phdu.header['INSTRUME'] = 'MY_INSTRUMENT'
+>>> phdu.header['MY_TAG'] = 'example_tag'
+>>> phdu.header['MY_DESC'] = 'example_descriptor'
+
+>>> # Add a single dummy extension
+>>> image = fits.ImageHDU(data=[[1, 2], [3, 4]])
+>>> hdu = fits.HDUList([phdu, image])
+
+>>> # Save the FITS file
+>>> with tempfile.NamedTemporaryFile(suffix='.fits') as f:
+...     hdu.writeto(f, overwrite=True)
+...
+...     # Open the file with astrodata.from_file
+...     ad = astrodata.from_file(f.name)
+...
+...     # Check that the tag and descriptor are present
+...     assert 'MY_TAG' in ad.tags, f"Tag 'my_tag' not found in {ad.tags}"
+...
+...     # Check that the tag and descriptor values are correct
+...     assert ad.my_descriptor() == 'example_descriptor', (
+...         f"Descriptor 'my_descriptor' has incorrect value: "
+...         f"{ad.my_descriptor()}"
+...     )
+...
+...     # Finally, make sure that the object is an instance of MyAstroData.
+...     # We can generally infer this from the above, but it's good to be
+...     # thorough in our tests (in case any strange API change nullifies
+...     # the above checks).
+...     assert isinstance(ad, MyAstroData), (
+...         f"Incorrect class {type(ad)}, expected MyAstroData"
+...     )
+
+>>> # Now that our data is loaded in, we can use the AstroData API to access
+>>> # the data.
+>>> # For example, we can get the data as a numpy array
+>>> data = ad[0].data
+
+>>> # Or we can get the WCS
+>>> wcs = ad[0].wcs
+
+>>> # Or we can get the value of a keyword
+>>> my_keyword = ad[0].hdr.get('MY_KEYWORD')
+
+>>> # Or we can get the resolved tags
+>>> my_tags = ad.tags
+
+>>> # Or we can get the value of a descriptor
+>>> my_descriptor = ad.my_descriptor()
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/progmanual/tags.html b/manuals/progmanual/tags.html new file mode 100644 index 00000000..585840e3 --- /dev/null +++ b/manuals/progmanual/tags.html @@ -0,0 +1,291 @@ + + + + + + + Tags — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+ +
+

Tags

+

We described in the previous section how to generate +tags for an AstroData derivative. In this section we’ll describe the algorithm +that generates the complete tag set out of the individual TagSet instances. +The algorithm collects all the tags in a list and then decides whether to apply +them or not following certain rules, but let’s talk about TagSet first.

+

TagSet is actually a standard named tuple customized to generate default +values (None) for its missing members. Its signature is:

+
TagSet(add=None, remove=None, blocked_by=None, blocks=None,
+       if_present=None)
+
+
+

The most common TagSet is an additive one: TagSet(['FOO', 'BAR']). +If all you need is to add tags, then you’re done here. But the real power of +our tag generating system is that you can specify some conditions to apply a +certain TagSet, or put restrictions on others. The different arguments to +TagSet all expect a list (or some others work in the following way):

+
    +
  • add: if this TagSet is selected, then add all these members to the tag +set.

  • +
  • remove: if this TagSet is selected, then prevent all these members +from joining the tag set.

  • +
  • blocked_by: if any of the tags listed in here exist in the tag set, then +discard this TagSet altogether.

  • +
  • blocks: discard from the list of unprocessed ones any TagSet that +would add any of the tags listed here.

  • +
  • if_present: process this tag only if all the tags listed in here exist in +the tag set at this point.

  • +
+

Note that blocked_by and blocks look like two sides of the same coin. +This is intentional: which one to use is up to the programmer, depending on +what will reduce the amount of typing and/or make the logic easier (sometimes one +wants to block a bunch of other tags from a single one; sometimes one wants a +tag to be blocked by a bunch of others). Furthermore, while blocks and +blocked_by prevent the entire TagSet from being added if it contains a +tag affected by these, remove only affects the specific tag.

+

Now, the algorithm works like this:

+
    +
  1. Collect all the TagSet generated by methods in the instance that are +decorated using astro_data_tag.

  2. +
  3. Then we sort them out:

    +
      +
    1. Those that subtract tags from the tag set go first (the ones with +non-empty remove or blocks), allowing them to act early on

    2. +
    3. Those with non-empty blocked_by are moved to the end of the list, to +ensure that other tags can be generated before them.

    4. +
    5. Those with non-empty if_present are moved behind those with +blocked_by.

    6. +
    +
  4. +
  5. Now that we’ve sorted the tags, process them sequentially and for each one:

    +
      +
    1. If they require other tags to be present, make sure that this is the case. +If the requirements are not met, drop the tagset. If not…

    2. +
    3. Figure out if any other tag is blocking the tagset. This will be the +case if any of the tags to be added is in the “blocked” list, or if +any of the tags added by previous tag sets are in the blocked_by +list of the one being processed. Then…

    4. +
    5. If all the previous hurdles have been passed, apply the changes declared +by this tag (add, remove, and/or block others).

    6. +
    +
  6. +
+

Note that Python’s sort algorithm is stable. This means, that if two elements +are indistinguishable from the point of view of the sorting algorithm, they are +guaranteed to stay in the same relative position. To better understand how this +affects our tags, and the algorithm itself, let’s follow up with an example taken +from real code (the Gemini-generic and GMOS modules)

+
# Simple tagset, with only a constant, additive content
+@astro_data_tag
+def _tag_instrument(self):
+    return TagSet(['GMOS'])
+
+# Simple tagset, also with additive content. This one will
+# check if the frame fits the requirements to be classified
+# as "GMOS imaging". It returns a value conditionally:
+# if this is not imaging, then it will return None, which
+# means the algorithm will ignore the value
+@astro_data_tag
+def _tag_image(self):
+    if self.phu.get('GRATING') == 'MIRROR':
+        return TagSet(['IMAGE'])
+
+# This is a slightly more complex TagSet (but fairly simple, anyway),
+# inherited by all Gemini instruments.
+@astro_data_tag
+def _type_gcal_lamp(self):
+    if self.phu.get('GCALLAMP') == 'IRhigh':
+        shut = self.phu.get('GCALSHUT')
+        if shut == 'OPEN':
+            return TagSet(['GCAL_IR_ON', 'LAMPON'],
+                          blocked_by=['PROCESSED'])
+        elif shut == 'CLOSED':
+            return TagSet(['GCAL_IR_OFF', 'LAMPOFF'],
+                          blocked_by=['PROCESSED'])
+
+# This tagset is only active when we detect that the frame is
+# a bias. In that case we want to prevent the frame from being
+# classified as "imaging" or "spectroscopy", which depend on the
+# configuration of the instrument
+@astro_data_tag
+def _tag_bias(self):
+    if self.phu.get('OBSTYPE') == 'BIAS':
+        return TagSet(['BIAS', 'CAL'], blocks=['IMAGE', 'SPECT'])
+
+
+

These four simple tag methods will serve to illustrate the algorithm. Let’s pretend +that the requirements for all four of them are somehow met, meaning that we get four +TagSet instances in our list, in some random order. After step 1 in the algorithm, +then, we may have collected the following list:

+
[ TagSet(['GMOS']),
+  TagSet(['GCAL_IR_OFF', 'LAMPOFF'], blocked_by=['PROCESSED']),
+  TagSet(['BIAS', 'CAL'], blocks=['IMAGE', 'SPECT']),
+  TagSet(['IMAGE']) ]
+
+
+

The algorithm then proceeds to sort them. First, it will promote the TagSet +with non-empty blocks or remove:

+
[ TagSet(['BIAS', 'CAL'], blocks=['IMAGE', 'SPECT']),
+  TagSet(['GMOS']),
+  TagSet(['GCAL_IR_OFF', 'LAMPOFF'], blocked_by=['PROCESSED']),
+  TagSet(['IMAGE']) ]
+
+
+

Note that the other three TagSet stay in exactly the same order. Now the +algorithm will sort the list again, moving the ones with non-empty +blocked_by to the end:

+
[ TagSet(['BIAS', 'CAL'], blocks=['IMAGE', 'SPECT']),
+  TagSet(['GMOS']), TagSet(['IMAGE']),
+  TagSet(['GCAL_IR_OFF', 'LAMPOFF'], blocked_by=['PROCESSED']) ]
+
+
+

Note that at each step, all the instances (except the ones “being moved”) have +kept the same position relative to each other -here’s where the “stability” of +the sorting comes into play,- ensuring that each step does not affect the previous +one. Finally, there are no if_present in our example, so no more instances are +moved around.

+

Now the algorithm prepares three empty sets (tags, removals, and blocked), +and starts iterating over the TagSet list.

+
+
    +
  1. For the first TagSet there are no blocks or removals, so we just add its +contents to the current sets: tags = {'BIAS', 'CAL'}, +blocked = {'IMAGE', 'SPECT'}.

  2. +
  3. Then comes TagSet(['GMOS']). Again, there are no removals in place, and +GMOS is not in the list of blocked tags. Thus, we just add it to the current +tag set: tags = {'BIAS', 'CAL', 'GMOS'}.

  4. +
  5. When processing TagSet(['IMAGE']), the algorithm observes that this IMAGE +is in the blocked set, and stops processing this tag set.

  6. +
  7. Finally, neither GCAL_IR_OFF nor LAMPOFF are in blocked, and +PROCESSED is not in tags, meaning that we can add this tag set to +the final one.

  8. +
+
+

Our result will look something like: {'BIAS', 'CAL', 'GMOS', 'GCAL_IR_OFF', 'LAMPOFF'}

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/usermanual/data.html b/manuals/usermanual/data.html new file mode 100644 index 00000000..2f3727bb --- /dev/null +++ b/manuals/usermanual/data.html @@ -0,0 +1,985 @@ + + + + + + + Pixel Data — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Pixel Data

+

The most important part of an AstroData object is the pixel data. +Manipulating and interacting with pixel data is a common task in astronomy, and +astrodata provides a number of tools, as well as many familiar operations, to +make working with such data efficient and straightforward.

+
+

Operate on Pixel Data

+

The pixel data are stored in the AstroData object as a list of +NDAstroData objects. The NDAstroData is a subclass of Astropy’s +NDData class which combines in one “package” the pixel values, the +variance, and the data quality plane or mask (as well as associated meta-data). +The data can be retrieved as a standard NumPy ndarray.

+

Accessing pixel data can be done with the .data attribute. The +.data attribute is a NumPy ndarray. The .data attribute is +a property of the NDAstroData object, and it is the pixel data itself.

+
>>> ad = astrodata.from_file(some_fits_file_with_extensions)
+>>> the_data = ad[1].data
+>>> type(the_data)
+<class 'numpy.ndarray'>
+
+>>> # Loop through the extensions.
+>>> for ext in ad:
+...     the_data = ext.data
+...     print(the_data.sum())
+4194304.0
+4194304.0
+4194304.0
+4194304.0
+4194304.0
+
+
+
+

Note

+

Remember that extensions can be accessed by index, with index 0 being +the first extension, not the primary header unit (for FITS files).

+
+

In this example, we first access the pixels for the second extension. The +.data attribute contains a NumPy ndarray. In the for-loop, for each +extension, we get the data and use the NumPy .sum() method to sum the pixel +values. Anything that can be done with a ndarray can be done on +AstroData pixel data.

+
+
+

Arithmetic on AstroData Objects

+

AstroData objects support basic in-place arithmetics with these methods:

+ + + + + + + + + + + + + + + +

addition

.add()

subtraction

.subtract()

multiplication

.multiply()

division

.divide()

+

In-place operations are also supported with the standard in-place assignment +operators +=, -=, *=, and /=. Normal, not in-place, +arithmetics is also possible using the standard operators, +, -, *, +and /.

+

When performing these operations, any variance or masks present will be +propagated forward to the resulting AstroData object (or during in-place +operations).

+
+

Simple operations

+

Here are a few examples of arithmetics on AstroData objects.

+
>>> ad = astrodata.from_file(some_fits_file_with_extensions)
+
+>>> # Addition
+>>> ad.add(50.)
+<...DocTestAstroData object at ...>
+>>> ad = ad + 50.
+>>> ad += 50.
+>>> print(ad[0].data[50,50])
+151.0
+
+>>> # Subtraction
+>>> ad.subtract(50.)
+<...DocTestAstroData object at ...>
+>>> ad = ad - 50.
+>>> ad -= 50.
+>>> print(ad[0].data[50,50])
+1.0
+
+>>> # Multiplication (Using a descriptor)
+>>> ad.multiply(ad.exposure_time())
+<...DocTestAstroData object at ...>
+>>> ad = ad * ad.exposure_time()
+>>> ad *= ad.exposure_time()
+>>> print(ad[0].data[50,50])
+1.0
+
+>>> # Division (Using a descriptor)
+>>> ad.divide(ad.exposure_time())
+<...DocTestAstroData object at ...>
+>>> ad = ad / ad.exposure_time()
+>>> ad /= ad.exposure_time()
+>>> print(ad[0].data[50,50])
+1.0
+
+
+

When the syntax adout = adin + 1 is used, the output variable is a copy +of the original. In the examples above we reassign the result back onto the +original. The two other forms, ad.add() and ad += are in-place +operations.

+

When a descriptor returns a list because the value changes for each +extension, a for-loop is needed

+
>>> for i, (ext, gain) in enumerate(zip(ad, ad.gain())):
+...     ext.multiply(gain)
+...     print(f"Extension {i} has been multiplied by {gain}")
+<...>
+Extension 0 has been multiplied by 1.5
+<...>
+Extension 1 has been multiplied by 1.5
+<...>
+Extension 2 has been multiplied by 1.5
+<...>
+Extension 3 has been multiplied by 1.5
+<...>
+Extension 4 has been multiplied by 1.5
+
+
+

If you want to do the above but on a new object, leaving the original unchanged, +use deepcopy first.

+
>>> from copy import deepcopy
+>>> adcopy = deepcopy(ad)
+>>> for i, (ext, gain) in enumerate(zip(adcopy, adcopy.gain())):
+...     ext.multiply(gain)
+...     assert ext.data is not ad[i].data
+<...>
+<...>
+<...>
+<...>
+<...>
+
+
+
+

Warning

+

The deepcopy function is a powerful tool but it can be slow, +memory-consuming, and it can lead to unexpected results if the object being +copied contains references to other objects. It is not recommended to use +it unless you are sure you need it. In many situations, you can avoid +using it.

+
+
+
+

Operator Precedence

+

The AstroData arithmetics methods can be stringed together but beware that +there is no operator precedence when that is done. For arithmetics that +involve more than one operation, it is probably safer to use the normal +Python operator syntax. Here is a little example to illustrate the difference.

+
>>> ad_copy = deepcopy(ad)
+>>> ad_copy.add(5).multiply(10).subtract(5)
+<...>
+>>> # means:  ad = ((ad + 5) * 10) - 5
+>>> # NOT: ad = ad + (5 * 10) - 5
+>>> print(ad_copy[0].data[50, 50])
+60.0
+
+
+

This is because the methods modify the object in-place, one operation after +the other from left to right. This also means that the original is modified.

+

This example applies the expected operator precedence

+
>>> ad_copy = deepcopy(ad)
+>>> ad_copy = ad_copy + ad_copy * 3 - 40.
+>>> # means: ad_copy = ad_copy + (ad_copy * 3) - 40.
+>>> print(ad_copy[0].data[50, 50])
+-34.0
+
+
+

If you need a copy, leaving the original untouched, which is sometimes useful +you can use deepcopy or just use the normal operator and assign to a new +variable.

+
>>> adnew = ad + ad * 3 - 40.
+>>> print(adnew[0].data[50, 50], ad[0].data[50, 50])
+-34.0 1.5
+>>> adnew[0] is not ad[0]
+True
+
+
+
+
+
+

Variance

+

When doing arithmetic on an AstroData object, if a variance is present +it will be propagated appropriately to the output no matter which syntax +you use (the methods or the Python operators).

+
+

Adding a Variance Plane

+

In this example, we will add the poisson noise to an AstroData dataset. +The data is still in ADU, therefore the poisson noise as variance is +signal / gain. We want to set the variance for each of the pixel +extensions.

+
>>> ad = astrodata.from_file(some_fits_file_with_extensions)
+>>> for (extension, gain) in zip(ad, ad.gain()):
+...    extension.variance = extension.data / gain
+
+
+

Check info(), you will see a variance plane for each +of the four extensions.

+
+
+

Automatic Variance Propagation

+

If present, any variance plane will be propagated to the resulting AstroData +object when doing arithmetics.

+
+

Note

+

The variance propagation assumes the data are not correlated. If the data +are correlated, the variance propagation will be incorrect. In that case, +the variance should be calculated from the data themselves.

+
+

Let’s look into an example.

+
>>> #     output = x * x
+>>> # var_output = var * x^2 + var * x^2
+>>> ad = astrodata.from_file(some_fits_file_with_extensions)
+>>> ad *= 1.5
+>>> ad[1].data[50,50]
+1.5
+>>> ad[1].variance[50,50]
+0.471
+>>> adout = ad * ad
+>>> adout[1].data[50,50]
+2.25
+>>> adout[1].variance[50,50]
+0.7065
+
+
+
+

Warning

+

Variance must be implemented, either by setting it (above) or by including +it in the data ingestion. If variance is not present, the variance +propagation will not be done.

+

For examples of how to set the variance, see EXAMPLE.

+
+
+
+
+

Data Quality Plane

+

The NDData mask stores the data quality plane. The simplest form is a +True/False array of the same size at the pixel array. In Astrodata we favor a +bit array that allows for additional information about why the pixel is being +masked. For example, Gemini bit masks use the following for bad pixels:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Meaning

Value

Binary

Good pixel

0

0000000

Bad pixel

1

0000001

Non Linear

2

0000010

Saturated

4

0000100

Cosmic Ray

8

0001000

No Data

16

0010000

Overlap

32

0100000

Unilluminated

64

1000000

+
+

Note

+

These definitions are located in +geminidr.gemini.lookups.DQ_definitions. The are +defined as np.uint16 type integers.

+
+

So a pixel marked 10 (binary 0001010) in the mask, would be a “non-linear” +“cosmic ray”. The AstroData masks are propagated with bitwise-OR operation. +For example, let’s say that we are stacking frames. A pixel is set as bad +(value 1 (0000001)) in one frame, saturated in another (value 4 (0000100)), and +fine in all the other the frames (value 0 (0000000)). The mask of the resulting +stack will be assigned a value of 5 (0000101) for that pixel.

+

These bitmasks will work like any other NumPy True/False mask. There is a +usage example below using the mask.

+

The mask can be accessed as follows:

+
# >>> ad = astrodata.open(some_fits_file_with_mask)
+# >>> ad.info() # DOCTEST: +NORMALIZE_WHITESPACE
+# Filename: /.../some_file.fits
+# Tags: _DOCTEST_DATA
+# <BLANKLINE>
+# Pixels Extensions
+# Index  Content  Type         Dimensions   Format
+# [ 0]   science  NDAstroData  (2048, 2048) float64
+
+# >>> ad[2].mask
+
+
+
+
+

Display

+

Since the data is stored in the AstroData object as a NumPy ndarray any +tool that works on ndarray can be used. To display in DS9 there is the +imexam package. We will show how to use imexam to display and read +the cursor position. Read the documentation on that tool to learn more about +what else it has to offer (.

+
+

Warning

+

The numdisplay package is still available for now but it is no longer +supported by STScI.

+
+
+
+

Useful tools from the NumPy, SciPy, and Astropy Packages

+

Scientific libraries in python provide a rich menagerie of tools for data +analysis and visualization. They have their own extensive documentation and it +is highly recommend for the users to learn about what they have to offer. It +might save you from re-inventing the wheel for many common tasks (or uncommon +ones!).

+

The pixels, variance, and mask are stored as NumPy ndarray’s. Let us go +through some basic examples, just to get a feel for how the data in an +AstroData object can be manipulated.

+
+

ndarray

+

The data are contained in NumPy ndarray objects. Any tools that works +on an ndarray can be used with Astrodata.

+
>>> ad = astrodata.open(some_fits_file_with_extensions)
+
+>>> data = ad[0].data
+
+>>> # Shape of the array.  (equivalent to NAXIS2, NAXIS1)
+>>> data.shape
+(2048, 2048)
+
+>>> # Value of a pixel at "IRAF" or DS9 coordinates (100, 50)
+>>> data[49,99]
+1.0
+
+>>> # Data type
+>>> data.dtype
+dtype('float64')
+
+
+

The two most important things to remember for users coming from the IRAF world +or the Fortran world are that the array has the y-axis in the first index, the +x-axis in the second, and that the array indices are zero-indexed, not +one-indexed. The examples above illustrate those two critical differences.

+

It is sometimes useful to know the data type of the values stored in the array. +Here, the file is a raw dataset, fresh off the telescope. No operations has +been done on the pixels yet. The data type of Gemini raw datasets is always +“Unsigned integer (0 to 65535)”, uint16.

+
+

Warning

+

Beware that doing arithmetic on uint16 can lead to unexpected +results. This is a NumPy behavior. If the result of an operation +is higher than the range allowed by uint16, the output value will +be “wrong”. The data type will not be modified to accommodate the large +value. A workaround, and a safety net, is to multiply the array by +1.0 to force the conversion to a float64.

+
>>> a = np.array([65535], dtype='uint16')
+>>> a + a
+array([65534], dtype=uint16)
+>>> 1.0*a + a
+array([131070.])
+
+
+
+
+
+

Simple Numpy Statistics

+

A lot of functions and methods are available in NumPy to probe the array, +too many to cover here, but here are a couple examples.

+
>>> import numpy as np
+
+>>> ad = astrodata.open(some_fits_file)
+>>> data = ad[0].data
+
+# Add some data to it to make it more interesting
+>>> data += 10 * (random_number.random(data.shape) - 1.0)
+
+# Calculate the mean, average, and median, using methods/functions.
+>>> data.mean()
+    -5.00117...
+>>> np.average(data)
+    -5.00117...
+>>> np.median(data)
+    -5.00271...
+
+
+

As shown, both array methods like .mean() as well as numpy ufunc +functions like np.average() can be used.

+

See the NumPy documentation for more information and more functions that are +available for use in that library.

+
+
+

Clipped Statistics

+

It is common in astronomy to apply clipping to the statistics (e.g., a clipped +average). The NumPy ma module can be used to create masks of the values +to reject. In the examples below, we calculated the clipped average of the +first pixel extension with a rejection threshold set to +/- 3 times the +standard deviation.

+

Before Astropy, it was possible to do something like that with only NumPy +tools, like in this example

+
>>> stddev = data.std()
+>>> mean = data.mean()
+
+>>> clipped_mean = np.ma.masked_outside(
+...     data,
+...     mean-3*stddev,
+...     mean+3*stddev
+... ).mean()
+
+>>> print(
+...     f"standard deviation = {stddev:10.3e}",
+...     f"mean               = {mean:10.3e}",
+...     f"clipped mean       = {clipped_mean:10.3e}",
+...     sep='\n',
+... ) # DOCTEST: +NORMALIZE_WHITESPACE
+standard deviation =  2.887e+00
+mean               = -5.001e+00
+clipped mean       = -5.001e+00
+
+
+

There is no iteration in that example. It is a one-time clipping of the data +specifically for this calculation.

+

For something more robust, there is an Astropy function that can help, in +particular by adding an iterative process to the calculation. Here is +how it is done

+
>>> from astropy.stats import sigma_clip
+
+>>> clipped_mean = np.ma.mean(sigma_clip(data, sigma=3))
+>>> print(f"clipped mean = {clipped_mean:10.3e}")
+clipped mean = -5.001e+00
+
+
+
+
+

Filters with SciPy

+

Another common operation is the filtering of an image, (e.g., convolusion with +a gaussian filter). The SciPy module ndimage.filters offers several +functions for image processing. See the SciPy documentation for more +information.

+

The example below applies a gaussian filter to the pixel array.

+
# >>> from scipy.ndimage import filters
+# >>> import imexam
+
+# >>> ad = astrodata.open('../playdata/N20170521S0925_forStack.fits')
+# >>> data = ad[0].data
+
+# >>> # We need to prepare an array of the same size and shape as
+# >>> # the data array.  The result will be put in there.
+# >>> convolved_data = np.zeros(data.size).reshape(data.shape)
+
+# >>> # We now apply the convolution filter.
+# >>> sigma = 10.
+# >>> filters.gaussian_filter(data, sigma, output=convolved_data)
+
+# >>> # Let's visually compare the convolved image with the original
+# >>> ds9 = imexam.connect(list(imexam.list_active_ds9())[0])
+# >>> ds9.view(data)
+# >>> ds9.scale('zscale')
+# >>> ds9.frame(2)
+# >>> ds9.view(convolved_data)
+# >>> ds9.scale('zscale')
+# >>> ds9.blink()
+# >>> # When you are convinced it's been convolved, stop the blinking.
+# >>> ds9.blink(blink=False)
+
+
+

Note that there is an Astropy way to do this convolution, with tools in +astropy.convolution package. Beware that for this particular kernel +we have found that the Astropy convolve function is extremely slow +compared to the SciPy solution.

+

This is because the SciPy function is optimized for a Gaussian convolution +while the generic convolve function in Astropy can take in any kernel. +Being able to take in any kernel is a very powerful feature, but the cost +is time. The lesson here is do your research, and find the best tool for +your needs.

+
+
+

Many other tools

+

There are many, many other tools available out there. Here are the links to +the three big projects we have featured in this section.

+ +
+
+
+

Using the Astrodata Data Quality Plane

+

Let us look at an example where the use of the Astrodata mask is +necessary to get correct statistics. A GMOS imaging frame has large sections +of unilluminated pixels; the edges are not illuminated and there are two +bands between the three CCDs that represent the physical gap between the +CCDs. Let us have a look at the pixels to have a better sense of the +data

+
# >>> ad = astrodata.open('../playdata/N20170521S0925_forStack.fits')
+# >>> import imexam
+# >>> ds9 = imexam.connect(list(imexam.list_active_ds9())[0])
+
+# >>> ds9.view(ad[0].data)
+# >>> ds9.scale('zscale')
+
+
+

See how the right and left portions of the frame are not exposed to the sky, +and the 45 degree angle cuts of the four corners. The chip gaps too. If we +wanted to do statistics on the whole frames, we certainly would not want to +include those unilluminated areas. We would want to mask them out.

+

Let us have a look at the mask associated with that image

+
# >>> ds9.view(ad[0].mask)
+# >>> ds9.scale('zscale')
+
+
+

The bad sections are all white (pixel value > 0). There are even some +illuminated pixels that have been marked as bad for a reason or another.

+

Let us use that mask to reject the pixels with no or bad information and +do calculations only on the good pixels. For the sake of simplicity we will +just do an average. This is just illustrative. We show various ways to +accomplish the task; choose the one that best suits your need or that you +find most readable.

+
>>> # For clarity...
+>>> ad = astrodata.from_file(some_fits_file_with_mask)
+>>> data = ad[0].data
+>>> mask = ad[0].mask
+
+>>> breakpoint()
+>>> # Reject all flagged pixels and calculate the mean
+>>> np.mean(data[mask == 0])
+
+>>> np.ma.masked_array(data, mask).mean()
+
+>>> # Reject only the pixels flagged "no_data" (bit 16)
+>>> np.mean(data[(mask & 16) == 0])
+>>> np.ma.masked_array(data, mask & 16).mean()
+>>> np.ma.masked_where(mask & 16, data).mean()
+
+
+

The “long” form with np.ma.masked_* is useful if you are planning to do +more than one operation on the masked array. For example

+
>>> clean_data = np.ma.masked_array(data, mask)
+>>> clean_data.mean()
+>>> np.ma.median(clean_data)
+>>> clean_data.max()
+
+
+
+
+

Manipulate Data Sections

+

So far we have shown examples using the entire data array. It is possible to +work on sections of that array. If you are already familiar with Python, the +following discussion about slixing is the same as you’ve seen throughout your +Python coding experience. For readers new to Python, and especially those +coming from IRAF, there are a few things that are worth explaining.

+

When indexing a NumPy ndarray, the left most number refers to the highest +dimension’s axis. For example, in a 2D array, the IRAF section are in (x-axis, +y-axis) format, while in Python they are in (y-axis, x-axis) format. Also +important to remember is that the ndarray is 0-indexed, rather than 1-indexed +like in Fortran or IRAF.

+

Putting it all together, a pixel position (x,y) = (50,75) in IRAF or from the +cursor on a DS9 frame, is accessed in Python as data[74,49]. Similarly, +the IRAF section [10:20, 30:40] translate in Python to [9:20, 29:40]. Also +remember that when slicing in Python, the upper limit of the slice is not +included in the slice. This is why here we request 20 and 40 rather 19 and 39.

+
+

Basic Statistics on Section

+

In this example, we do simple statistics on a section of the image.

+
>>> import numpy as np
+
+>>> ad = astrodata.open('../playdata/N20170521S0925_forStack.fits')
+>>> data = ad[0].data
+
+# Get statistics for a 25x25 pixel-wide box centered on pixel
+# (50,75)  (DS9 frame coordinate)
+>>> xc = 49
+>>> yc = 74
+>>> buffer = 25
+>>> (xlow, xhigh) = (xc - buffer//2, xc + buffer//2 + 1)
+>>> (ylow, yhigh) = (yc - buffer//2, yc + buffer//2 + 1)
+
+# The section is [62:87, 37:62]
+>>> stamp = data[ylow:yhigh, xlow:xhigh]
+>>> mean = stamp.mean()
+>>> median = np.median(stamp)
+>>> stddev = stamp.std()
+>>> minimum = stamp.min()
+>>> maximum = stamp.max()
+
+>>> print(' Mean   Median  Stddev  Min   Max\n \
+... %.2f  %.2f   %.2f    %.2f  %.2f' % \
+... (mean, median, stddev, minimum, maximum))
+
+
+
+
+

Example - Overscan Subtraction with Trimming

+

Several concepts from previous sections and chapters are used in this +example. The Descriptors are used to retrieve the overscan section and +the data section information from the headers. Statistics are done on the +NumPy ndarray representing the pixel data. Astrodata arithmetics is +used to subtract the overscan level. Finally, the overscan section is +trimmed off and the modified AstroData object is written to a new file +on disk.

+

To make the example more complete, and to show that when the pixel data +array is trimmed, the variance (and mask) arrays are also trimmed, let us +add a variance plane to our raw data frame.

+
>>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+
+>>> for (extension, gain) in zip(ad, ad.gain()):
+...    extension.variance = extension.data / gain
+...
+
+>>> # Here is how the data structure looks like before the trimming.
+>>> ad.info()
+Filename: ../playdata/N20170609S0154.fits
+Tags: ACQUISITION GEMINI GMOS IMAGE NORTH RAW SIDEREAL UNPREPARED
+
+Pixels Extensions
+Index  Content                  Type              Dimensions     Format
+[ 0]   science                  NDAstroData       (2112, 288)    uint16
+          .variance             ndarray           (2112, 288)    float64
+[ 1]   science                  NDAstroData       (2112, 288)    uint16
+          .variance             ndarray           (2112, 288)    float64
+[ 2]   science                  NDAstroData       (2112, 288)    uint16
+          .variance             ndarray           (2112, 288)    float64
+[ 3]   science                  NDAstroData       (2112, 288)    uint16
+          .variance             ndarray           (2112, 288)    float64
+
+# Let's operate on the first extension.
+#
+# The section descriptors return the section in a Python format
+# ready to use, 0-indexed.
+>>> oversec = ad[0].overscan_section()
+>>> datasec = ad[0].data_section()
+
+# Measure the overscan level
+>>> mean_overscan = ad[0].data[oversec.y1: oversec.y2, oversec.x1: oversec.x2].mean()
+
+# Subtract the overscan level.  The variance will be propagated.
+>>> ad[0].subtract(mean_overscan)
+
+# Trim the data to remove the overscan section and keep only
+# the data section.  Note that the WCS will be automatically
+# adjusted when the trimming is done.
+#
+# Here we work on the NDAstroData object to have the variance
+# trimmed automatically to the same size as the science array.
+# To reassign the cropped NDAstroData, we use the reset() method.
+>>> ad[0].reset(ad[0].nddata[datasec.y1:datasec.y2, datasec.x1:datasec.x2]
+
+# Now look at the dimensions of the first extension, science
+# and variance.  That extension is smaller than the others.
+>>> ad.info()
+Filename: ../playdata/N20170609S0154.fits
+Tags: ACQUISITION GEMINI GMOS IMAGE NORTH RAW SIDEREAL UNPREPARED
+
+Pixels Extensions
+Index  Content                  Type              Dimensions     Format
+[ 0]   science                  NDAstroData       (2112, 256)    float64
+          .variance             ndarray           (2112, 256)    float64
+[ 1]   science                  NDAstroData       (2112, 288)    uint16
+          .variance             ndarray           (2112, 288)    float64
+[ 2]   science                  NDAstroData       (2112, 288)    uint16
+          .variance             ndarray           (2112, 288)    float64
+[ 3]   science                  NDAstroData       (2112, 288)    uint16
+          .variance             ndarray           (2112, 288)    float64
+
+# We can write this to a new file
+>>> ad.write('partly_overscan_corrected.fits')
+
+
+

A new feature presented in this example is the ability to work on the +NDAstroData object directly. This is particularly useful when cropping +the science pixel array as one will want the variance and the mask arrays +cropped exactly the same way. Taking a section of the NDAstroData +object (ad[0].nddata[y1:y2, x1:x2]), instead of just the .data array, +does all that for us.

+

To reassign the cropped NDAstroData to the extension one uses the +.reset() method as shown in the example.

+

Of course to do the overscan correction correctly and completely, one would +loop over all four extensions. But that’s the only difference.

+
+
+
+

Data Cubes

+

Reduced Integral Field Unit (IFU) data is commonly represented as a cube, +a three-dimensional array. The data component of an AstroData +object extension can be such a cube, and it can be manipulated and explored +with NumPy, AstroPy, SciPy, imexam, like we did already in this section +with 2D arrays. We can use matplotlib to plot the 1D spectra represented +in the third dimension.

+

In Gemini IFU cubes, the first axis is the X-axis, the second, the Y-axis, +and the wavelength is in the third axis. Remember that in a ndarray +that order is reversed (wlen, y, x).

+

In the example below we “collapse” the cube along the wavelenth axis to +create a “white light” image and display it. Then we plot a 1D spectrum +from a given (x,y) position.

+
>>> import imexam
+>>> import matplotlib.pyplot as plt
+
+>>> ds9 = imexam.connect(list(imexam.list_active_ds9())[0])
+
+>>> adcube = astrodata.open('../playdata/gmosifu_cube.fits')
+>>> adcube.info()
+
+>>> # Sum along the wavelength axis to create a "white light" image
+>>> summed_image = adcube[0].data.sum(axis=0)
+>>> ds9.view(summed_image)
+>>> ds9.scale('minmax')
+
+>>> # Plot a 1-D spectrum from the spatial position (14,25).
+>>> plt.plot(adcube[0].data[:,24,13])
+>>> plt.show()   # might be needed, depends on matplotlibrc interactive setting
+
+
+

Now that is nice but it would be nicer if we could plot the x-axis in units +of Angstroms instead of pixels. We use the AstroData’s WCS handler, which is +based on gwcs.wcs.WCS to get the necessary information. A particularity +of gwcs.wcs.WCS is that it refers to the axes in the “natural” way, +(x, y, wlen) contrary to Python’s (wlen, y, x). It truly requires you to pay +attention.

+
>>> import matplotlib.pyplot as plt
+
+>>> adcube = astrodata.open('../playdata/gmosifu_cube.fits')
+
+# We get the wavelength axis in Angstroms at the position we want to
+# extract, x=13, y=24.
+# The wcs call returns a 3-element list, the third element ([2]) contains
+# the wavelength values for each pixel along the wavelength axis.
+
+>>> length_wlen_axis = adcube[0].shape[0]   # (wlen, y, x)
+>>> wavelengths = adcube[0].wcs(13, 24, range(length_wlen_axis))[2] # (x, y, wlen)
+
+# We get the intensity along that axis
+>>> intensity = adcube[0].data[:, 24, 13]   # (wlen, y, x)
+
+# We plot
+plt.clf()
+plt.plot(wavelengths, intensity)
+plt.show()
+
+
+
+
+

Plot Data

+

The main plotting package in Python is matplotlib. We have used it in the +previous section on data cubes to plot a spectrum. There is also the project +called imexam which provides astronomy-specific tools for the +exploration and measurement of data. We have also used that package above to +display images to DS9.

+

In this section we absolutely do not aim at covering all the features of +either package but rather to give a few examples that can get the readers +started in their exploration of the data and of the visualization packages.

+

Refer to the projects web pages for full documentation.

+ +
+

Matplotlib

+

With Matplotlib you have full control on your plot. You do have to do a bit +for work to get it perfect though. However it can produce publication +quality plots. Here we just scratch the surface of Matplotlib.

+
>>> import numpy as np
+>>> import matplotlib.pyplot as plt
+>>> from astropy import wcs
+
+>>> ad_image = astrodata.open('../playdata/N20170521S0925_forStack.fits')
+>>> ad_spectrum = astrodata.open('../playdata/estgsS20080220S0078.fits')
+
+>>> # Line plot from image.  Row #1044 (y-coordinate)
+>>> line_index = 1043
+>>> line = ad_image[0].data[line_index, :]
+>>> plt.clf()
+>>> plt.plot(line)
+>>> plt.show()
+
+>>> # Column plot from image, averaging across 11 pixels around colum #327
+>>> col_index = 326
+>>> width = 5
+>>> xlow = col_index - width
+>>> xhigh = col_index + width + 1
+>>> thick_column = ad_image[0].data[:, xlow:xhigh]
+>>> plt.clf()
+>>> plt.plot(thick_column.mean(axis=1))  # mean along the width.
+>>> plt.show()
+>>> plt.ylim(0, 50)     # Set the y-axis range
+>>> plt.plot(thick_column.mean(axis=1))
+>>> plt.show()
+
+>>> # Contour plot for a section of an image.
+>>> center = (1646, 2355)
+>>> width = 15
+>>> xrange = (center[1]-width//2, center[1] + width//2 + 1)
+>>> yrange = (center[0]-width//2, center[0] + width//2 + 1)
+>>> blob = ad_image[0].data[yrange[0]:yrange[1], xrange[0]:xrange[1]]
+>>> plt.clf()
+>>> plt.imshow(blob, cmap='gray', origin='lower')
+>>> plt.contour(blob)
+>>> plt.show()
+
+>>> # Spectrum in pixels
+>>> plt.clf()
+>>> plt.plot(ad_spectrum[0].data)
+>>> plt.show()
+
+>>> # Spectrum in Angstroms
+>>> spec_wcs = wcs.WCS(ad_spectrum[0].hdr)
+>>> pixcoords = np.array(range(ad_spectrum[0].data.shape[0]))
+>>> wlen = spec_wcs.wcs_pix2world(pixcoords, 0)[0]
+>>> plt.clf()
+>>> plt.plot(wlen, ad_spectrum[0].data)
+>>> plt.show()
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/usermanual/headers.html b/manuals/usermanual/headers.html new file mode 100644 index 00000000..4d74c782 --- /dev/null +++ b/manuals/usermanual/headers.html @@ -0,0 +1,389 @@ + + + + + + + Metadata and Headers — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Metadata and Headers

+

Metadata is a critical component of astronomical observations. These data are +used to clarify and define various aspects of the observation, such as the +instrument configuration, the observation conditions, and the data reduction +history. The metadata is often stored in the FITS headers of the data files, +and in AstroData metadata is manipulated and access in two ways: through +descriptors (via astro_data_descriptor()) and directly in filetype-specific +header access.

+
+

Warning

+

While we say that header access is filetype-specific, it’s important to +note that this is not the same as saying that the headers are different +for each file type. The way headers are managed is FITS-centric, and +therefore implementing header access for a new type requires either updating +the methods that access headers or converting the headers to use +astropy.io.fits objects after loading.

+

For more information about developing with descriptors, see +Descriptors.

+
+
+

Astrodata Descriptors

+

Descriptors provide a mapping between metadata or data and a value or set of +values. They are a way to access metadata in a consistent way, regardless of +other differences between metadata (such as differences in the instrument, +image type, etc.). Descriptors are implemented as methods, and can be +found using the astrodata.AstroData.descriptors() property.

+

As a user, your interactions with descriptors will depend on the specific +implementation of AstroData you are using. For example, if you’re using +DRAGONS:gemini_instruments (from DRAGONS), you will have access to the descriptors +defined for Gemini instruments. If you’re using astrodata directly, you will +have access to the descriptors defined for the generic AstroData class.

+

Descriptors are a way to access metadata in a consistent way, and may perform +operations to arrive at a given value. Descriptors should not, in best +practice, modify the state of any object; instead, they will return a new value +every time they are used. Therefore, they can be more computationally expensive +than direct header access, but they are far more flexible.

+

For example, if the user is interested to know the effective filter used for a +Gemini observation, normally one needs to know which specific keyword or set of +keywords to look at for that instrument. However, once the concept of “filter” +is coded as a Descriptor (which happens in DRAGONS:gemini_instruments), the user only +needs to call the filter_name() descriptor to retrieve the information.

+

This is all completely transparent to the user. One simply opens the data +file and all the descriptors are ready to be used.

+
>>> ad = MyAstroData()
+>>> ad.my_descriptor()
+42
+
+# Descriptors can be listed as a tuple through the AstroData.descriptors
+# property
+>>> ad.descriptors
+('my_descriptor',)
+
+
+
+

Note

+

Descriptors must be defined for a given AstroData-derived class. +Descriptors are inherited like normal methods, so if a class inherits from +another class that has descriptors, the new class will have those +descriptors as well unless they are explicitly overridden.

+
+
+
+

Accessing Metadata

+
+

Accessing Metadata with Descriptors

+

Whenever possible, descriptors should be used to get information from headers. +This allows for straightforward re-usability of the code as it will propogate +to any datasets with an AstroData class.

+

Here are a few examples using Descriptors

+
>>> #--- print a value
+>>> print('The airmass is : ', ad.airmass())
+The airmass is :  1.089
+
+>>> #--- use a value to control the flow
+>>> if ad.exposure_time() < 240.:
+...     print('This is a short exposure.')
+... else:
+...     print('This is a long exposure.')
+This is a short exposure.
+
+>>> #--- multiply all extensions by their respective gain
+>>> for ext, gain in zip(ad, ad.gain()):
+...     ext *= gain
+
+>>> #--- do arithmetics
+>>> fwhm_pixel = 3.5
+>>> fwhm_arcsec = fwhm_pixel * ad.pixel_scale()
+
+
+

The return value of a descriptor is determined by the developer who created the +descriptor. It’s best practice to return a value of the same—or similar, +e.g., an iterable—type for each type of descriptor. However, this is not +always desirable between different instrument sets. For example, Gemini data +and JWST data may have different ways of describing specific values that are +most useful to observers on their respective telescopes. To avoid confusion, +check the return value of the descriptor explicitly when you are experimenting with +new data:

+
>>> ad = TestAstroData()
+>>> ad.unknown_descriptor()
+'you know what I am now!'
+
+>>> type(ad.unknown_descriptor())
+<class 'str'>
+
+>>> ad = OtherTestAstroData()
+>>> ad.unknown_descriptor()
+['My', 'developer', 'decided', "it's", 'more', 'useful', 'to', 'return', 'the', 'words', 'discretely']
+
+>>> type(ad.unknown_descriptor())
+<class 'list'>
+
+
+
+
+

Descriptors across multiple extensions

+

The dataset used in this section has 4 extensions. When the descriptor +value can be different for each extension, the descriptor will return a +Python list.

+
>>> ad.airmass()
+1.089
+>>> ad.gain()
+[2.03, 1.97, 1.96, 2.01]
+>>> ad.filter_name()
+'open1-6&g_G0301'
+
+
+

Some descriptors accept arguments. For example:

+
>>> ad.filter_name(pretty=True)
+'g'
+
+
+
+
+

Accessing Metadata Directly

+

Not all header content is mapped to descriptors, nor should it be. Direct +access is available for header content falling outside the scope of the +descriptors.

+

One important thing to keep in mind is that the PHU (Primary Header Unit) and +the extension headers are accessed slightly differently. The attribute +phu needs to be used for the PHU, and hdr for the extension headers.

+
+

Warning

+

The phu and hdr attributes are not available for all AstroData +classes. They are only available for classes that have been implemented to +use them. The default AstroData class without modification does have +minimal support for these attributes, but for other file types they will +need to be implemented by a developer/the instrument team.

+
+

Here are some examples of direct header access

+
>>> #--- Get keyword value from the PHU
+>>> ad.phu['AOFOLD']
+'park-pos.'
+
+>>> #--- Get keyword value from a specific extension
+>>> ad[0].hdr['CRPIX1']
+511.862999160781
+
+>>> #--- Get keyword value from all the extensions in one call.
+>>> ad.hdr['CRPIX1']
+[511.862999160781, 287.862999160781, -0.137000839218696, -224.137000839219]
+
+
+
+
+

Whole Headers

+

Entire headers can be retrieved as fits Header objects

+
>>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+>>> type(ad.phu)
+<class 'astropy.io.fits.header.Header'>
+>>> type(ad[0].hdr)
+<class 'astropy.io.fits.header.Header'>
+
+
+

In interactive mode, it is possible to print the headers on the screen as +follows

+
>>> ad.phu
+SIMPLE  =                    T / file does conform to FITS standard
+BITPIX  =                   16 / number of bits per data pixel
+NAXIS   =                    0 / number of data axes
+....
+
+>>> ad[0].hdr
+XTENSION= 'IMAGE   '           / IMAGE extension
+BITPIX  =                   16 / number of bits per data pixel
+NAXIS   =                    2 / number of data axes
+....
+
+
+
+
+
+

Updating, Adding and Deleting Metadata

+

Header cards can be updated, added to, or deleted from the headers. The PHU +and the extensions headers are again accessed in a mostly identical way +with phu and hdr, respectively.

+
>>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+
+
+

Add and update a keyword, without and with comment

+
>>> ad.phu['NEWKEY'] = 50.
+>>> ad.phu['NEWKEY'] = (30., 'Updated PHU keyword')
+
+>>> ad[0].hdr['NEWKEY'] = 50.
+>>> ad[0].hdr['NEWKEY'] = (30., 'Updated extension keyword')
+
+
+

Delete a keyword

+
>>> del ad.phu['NEWKEY']
+>>> del ad[0].hdr['NEWKEY']
+
+
+
+
+

World Coordinate System attribute

+

The wcs of an extension’s nddata attribute (eg. ad[0].nddata.wcs; +see Pixel Data) is stored as an instance of astropy.wcs.WCS (a +standard FITS WCS object) or gwcs.WCS (a “Generalized WCS” or gWCS object). This defines a transformation between +array indices and some other co-ordinate system such as “World” co-ordinates +(see APE 14). GWCS allows +multiple, almost arbitrary co-ordinate mappings from different calibration +steps (eg. CCD mosaicking, distortion correction & wavelength calibration) to +be combined in a single, reversible transformation chain — but this +information cannot always be represented as a FITS standard WCS. If a gWCS +object is too complex to be defined by the basic FITS keywords, it gets stored +as a table extension named ‘WCS’ when the AstroData instance is saved to a +file (with the same EXTVER as the corresponding ‘SCI’ array) and the FITS +header keywords are updated to provide an approximation to the true WCS and an +additional keyword FITS-WCS is added with the value ‘APPROXIMATE’. The +representation in the table is produced using ASDF, with one line of text per row. Likewise, when +the file is re-opened, the gWCS object gets recreated in wcs from the +table. If the transformation defined by the gWCS object can be accurately +described by standard FITS keywords, then no WCS extension is created as the +gWCS object can be created from these keywords when the file is re-opened.

+

In future, it is intended to improve the quality of the FITS approximation +using the Simple Imaging Polynomial convention +(SIP) or +a discrete sampling of the World co-ordinate +values will be stored as part of the FITS WCS, following Greisen et al. (2006), S6 (in addition to the +definitive ‘WCS’ table), allowing standard FITS readers to report accurate +World co-ordinates for each pixel.

+
+
+

Adding Descriptors [Advanced Topic]

+

To learn how to add descriptors to AstroData, see the Developer Guide.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/usermanual/index.html b/manuals/usermanual/index.html new file mode 100644 index 00000000..892416c2 --- /dev/null +++ b/manuals/usermanual/index.html @@ -0,0 +1,197 @@ + + + + + + + User Manual — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+ + + + \ No newline at end of file diff --git a/manuals/usermanual/intro.html b/manuals/usermanual/intro.html new file mode 100644 index 00000000..a3a787e0 --- /dev/null +++ b/manuals/usermanual/intro.html @@ -0,0 +1,229 @@ + + + + + + + Introduction — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Introduction

+

Welcome to the AstroData User’s Manual, a user guide for the astrodata +package. astrodata was formerly a part of the DRAGONS data reduction suite +developed at the Gemini Observatory. It has undergone several iteractions of +major development and improvements, and is now designed as a standalone +solution for handling astronomical data.

+

astrodata consolidates the handling of astronomical data into a single +package, using a uniform interface to access and manipulate data from +different instruments and observing modes. It is designed to be used in +conjunction with Astropy, Numpy and other scientific Python packages.

+

This introduction will guide you through the installation process and provide +a brief overview of the package. If you are looking for a quick reference, +please head to the Cheat Sheet.

+
+

What is astrodata?

+

astrodata is a package that wraps together tools to represent internally +astronomical datasets stored on disks and to properly parse their metadata +using the AstroData and the TagSet classes. astrodata provides uniform +interfaces for working on datasets from different instruments. Once a dataset +has been opened with from_file(), the object assesses metadata to determine the +appropriate class and methods to use for reading and processing the data. +Information like instrument, observation mode, and how to access headers, is +readily available through the AstroData uniform interface returned by +from_file(). All the details are coded inside the class associated with the +instrument, that class then provides the interface. The appropriate class is +selected automatically when the file is opened and inspected by astrodata.

+

Currently astrodata implements a basic representation for Multi-Extension +FITS (MEF) files. Extending to other file formats is possible, but requires +programming (see the Developer Guide for more information).

+
+
+

Installing Astrodata

+
+

Using pip

+

The astrodata package has a number of dependencies. These can be found in the +requirements.txt file in the source code repository.

+

To install the standalone astrodata package, you can use pip:

+
$ pip install astrodata
+
+
+

Or you can install it from the source code:

+
$ git clone https://github.com/teald/astrodata
+$ cd astrodata # Or the directory where you cloned the repository
+$ pip install -e .
+
+
+

If you’re interested in using astrodata out-of-the-box with a specific +type of data, you may want to install the astrodata package together with +their extensions. astrodata alone defines a base class, AstroData, which +is meant to be extended with instrument-specific classes. For example, to +use astrodata with Gemini data, you will need to install DRAGONS, which +includes the astrodata package and its extensions for DRAGONS:gemini_instruments.

+
+
+

Source code

+

The source code is available on Github:

+
+
+
+
+
+

A quick example

+

Here is a quick example of how to use astrodata to open a file and access +its metadata using an AstroData object

+
# We can create a fake file to use for this example:
+>>> path = create_test_file(include_header_keys=['INSTRUME', 'EXPTIME', 'DATE-OBS'])
+>>> ad = astrodata.from_file(path)
+>>> ad.phu['INSTRUME']
+'TEST_VALUE'
+
+
+

All file opening, closing and metadata management (which selects the +appropriate class for the header data) are handled by astrodata. There is no +need to “close” the ad object, as all file handles are closed when no longer +required or the program finishes.

+
+
+

Astrodata Support

+

Astrodata is developed and supported by staff at the Gemini Observatory. +Questions about the reduction of Gemini data should be directed to the +Gemini Helpdesk system at +https://noirlab.atlassian.net/servicedesk/customer/portal/12.

+

Issues related to astrodata itself can be reported at our +github Issue Tracker.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/usermanual/iomef.html b/manuals/usermanual/iomef.html new file mode 100644 index 00000000..d99db708 --- /dev/null +++ b/manuals/usermanual/iomef.html @@ -0,0 +1,634 @@ + + + + + + + Input and Output Operations and Extension Manipulation - MEF — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Input and Output Operations and Extension Manipulation - MEF

+

AstroData is not intended to exclusively support Multi-Extension FITS (MEF) +files. However, given FITS’ unflagging popularity as an astronomical data format, +the base AstroData object supports FITS and MEF files without any additional +effort by a user or programmer.

+
+

Note

+

For more information about FITS support and extending AstroData to +support other file formats, see AstroData and Derivatives.

+
+
+

Open and access existing dataset

+
+

Read in the dataset

+

The file on disk is loaded into the AstroData class associated with the +instrument the data is from. This association is done automatically based on +header content.

+

+
+
+

ad has loaded in the file’s header and parsed the keys present. Header access is done +through the .hdr attribute.

+
With descriptors:
+>>> ad.array_section(pretty=True)
+['[1:512,1:4224]', '[513:1024,1:4224]', '[1025:1536,1:4224]', '[1537:2048,1:4224]']
+
+
+

The original path and filename are also stored. If you were to write +the AstroData object to disk without specifying anything, path and +file name would be set to None.

+

+
+
+
+
+

Accessing the content of a MEF file

+

AstroData uses NDData as the core of its structure. Each FITS extension +becomes a NDAstroData object, subclassed from NDData, and is added to +a list representing all extensions in the file.

+
+

Note

+

For details on the AstroData object, please refer to +The AstroData Object.

+
+
+

Pixel data

+

To access pixel data, the list index and the .data attribute are used. That +returns a numpy.ndarray. The list of NDAstroData is zero-indexed. +Extension number 1 in a MEF is index 0 in an |AstroData| object.

+

+
+
+
+

Note

+

This implementation ignores the fact that the first extension in a MEF +file is the Primary Header Unit (PHU). The PHU is accessibly through the +.phu attribute of the AstroData object, and indexing with [i] +notation will only access the extensions.

+
+
+

Note

+

Remember that in a ndarray the ‘y-axis’ of the image is +accessed through the first number.

+
+

The variance and data quality planes, the VAR and DQ planes in Gemini +MEF files, are represented by the .variance and .mask attributes, +respectively. They are not their own “extension”, they don’t have their own +index in the list, unlike in a MEF. They are attached to the pixel data, +packaged together by the NDAstroData object. They are represented as +numpy.ndarray just like the pixel data

+

+
+
+
+
+

Tables

+

Tables in the MEF file will also be loaded into the AstroData object. If a table +is associated with a specific science extension through the EXTVER header keyword, that +table will be packaged within the same AstroData extension as the pixel data +and accessible like an attribute. The AstroData “extension” is the +NDAstroData object plus any table or other pixel array associated with the +image data. If the table is not associated with a specific extension and +applies globally, it will be added to the AstroData object as a global +addition. No indexing will be required to access it. In the example below, one +OBJCAT is associated with each extension, while the REFCAT has a global +scope

+
>>> ad.info()
+Filename: ../playdata/N20170609S0154_varAdded.fits
+Tags: ACQUISITION GEMINI GMOS IMAGE NORTH OVERSCAN_SUBTRACTED OVERSCAN_TRIMMED
+    PREPARED SIDEREAL
+
+Pixels Extensions
+Index  Content                  Type              Dimensions     Format
+[ 0]   science                  NDAstroData       (2112, 256)    float32
+          .variance             ndarray           (2112, 256)    float32
+          .mask                 ndarray           (2112, 256)    uint16
+          .OBJCAT               Table             (6, 43)        n/a
+          .OBJMASK              ndarray           (2112, 256)    uint8
+[ 1]   science                  NDAstroData       (2112, 256)    float32
+          .variance             ndarray           (2112, 256)    float32
+          .mask                 ndarray           (2112, 256)    uint16
+          .OBJCAT               Table             (8, 43)        n/a
+          .OBJMASK              ndarray           (2112, 256)    uint8
+[ 2]   science                  NDAstroData       (2112, 256)    float32
+          .variance             ndarray           (2112, 256)    float32
+          .mask                 ndarray           (2112, 256)    uint16
+          .OBJCAT               Table             (7, 43)        n/a
+          .OBJMASK              ndarray           (2112, 256)    uint8
+[ 3]   science                  NDAstroData       (2112, 256)    float32
+          .variance             ndarray           (2112, 256)    float32
+          .mask                 ndarray           (2112, 256)    uint16
+          .OBJCAT               Table             (5, 43)        n/a
+          .OBJMASK              ndarray           (2112, 256)    uint8
+
+Other Extensions
+               Type        Dimensions
+.REFCAT        Table       (245, 16)
+
+
+

The tables are stored internally as astropy.table.Table objects.

+
>>> ad[0].OBJCAT
+<Table length=6>
+NUMBER X_IMAGE Y_IMAGE ... REF_MAG_ERR PROFILE_FWHM PROFILE_EE50
+int32  float32 float32 ...   float32     float32      float32
+------ ------- ------- ... ----------- ------------ ------------
+     1 283.461 55.4393 ...     0.16895       -999.0       -999.0
+...
+>>> type(ad[0].OBJCAT)
+<class 'astropy.table.table.Table'>
+
+>>> refcat = ad.REFCAT
+>>> type(refcat)
+<class 'astropy.table.table.Table'>
+
+
+
+

Note

+

Tables are accessed through attribute notation. However, if a conflicting +attribute exists for a given AstroData or NDData object, a +AttributeError will be raised to avoid confusion.

+
+
+
+

Headers

+

Headers are stored in the NDAstroData .meta attribute as +astropy.io.fits.Header objects, which implements a dict-like +object. Headers associated with extensions are stored with the corresponding +NDAstroData object. The MEF Primary Header Unit (PHU) is stored as an +attribute in the AstroData object. When slicing an AstroData object or +accessing an index, the PHU will be included in the new sliced object. The +slice of an AstroData object is an AstroData object. Headers can be +accessed directly, or for some predefined concepts, the use of Descriptors is +preferred. More detailed information on Headers is covered in the section +Metadata and Headers.

+

Using Descriptors

+
>>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+>>> ad.filter_name()
+'open1-6&g_G0301'
+>>> ad.filter_name(pretty=True)
+'g'
+
+
+

Using direct header access

+
>>> ad.phu['FILTER1']
+'open1-6'
+>>> ad.phu['FILTER2']
+'g_G0301'
+
+
+

Accessing the extension headers

+
>>> ad.hdr['CCDSEC']
+['[1:512,1:4224]', '[513:1024,1:4224]', '[1025:1536,1:4224]', '[1537:2048,1:4224]']
+>>> ad[0].hdr['CCDSEC']
+'[1:512,1:4224]'
+
+With descriptors:
+>>> ad.array_section(pretty=True)
+['[1:512,1:4224]', '[513:1024,1:4224]', '[1025:1536,1:4224]', '[1537:2048,1:4224]']
+
+
+
+
+
+
+

Modify Existing MEF Files

+
+

Appending an extension

+

Extensions can be appended to an AstroData objects using the +append() method.

+

Here is an example appending a whole AstroData extension, with pixel data, +variance, mask and tables. While these are treated as separate extensions in +the MEF file, they are all packaged together in the AstroData object.

+
>>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+>>> advar = astrodata.open('../playdata/N20170609S0154_varAdded.fits')
+
+>>> ad.info()
+Filename: ../playdata/N20170609S0154.fits
+Tags: ACQUISITION GEMINI GMOS IMAGE NORTH RAW SIDEREAL UNPREPARED
+Pixels Extensions
+Index  Content                  Type              Dimensions     Format
+[ 0]   science                  NDAstroData       (2112, 288)    uint16
+[ 1]   science                  NDAstroData       (2112, 288)    uint16
+[ 2]   science                  NDAstroData       (2112, 288)    uint16
+[ 3]   science                  NDAstroData       (2112, 288)    uint16
+
+>>> ad.append(advar[3])
+>>> ad.info()
+Filename: ../playdata/N20170609S0154.fits
+Tags: ACQUISITION GEMINI GMOS IMAGE NORTH RAW SIDEREAL UNPREPARED
+Pixels Extensions
+Index  Content                  Type              Dimensions     Format
+[ 0]   science                  NDAstroData       (2112, 288)    uint16
+[ 1]   science                  NDAstroData       (2112, 288)    uint16
+[ 2]   science                  NDAstroData       (2112, 288)    uint16
+[ 3]   science                  NDAstroData       (2112, 288)    uint16
+[ 4]   science                  NDAstroData       (2112, 256)    float32
+          .variance             ndarray           (2112, 256)    float32
+          .mask                 ndarray           (2112, 256)    int16
+          .OBJCAT               Table             (5, 43)        n/a
+          .OBJMASK              ndarray           (2112, 256)    uint8
+
+>>> ad[4].hdr['EXTVER']
+4
+>>> advar[3].hdr['EXTVER']
+4
+
+
+

As you can see above, the fourth extension of advar, along with everything +it contains was appended at the end of the first AstroData object. However, +note that, because the EXTVER of the extension in advar was 4, there are +now two extensions in ad with this EXTVER. This is not a problem because +EXTVER is not used by AstroData (it uses the index instead) and it is handled +only when the file is written to disk.

+

In this next example, we are appending only the pixel data, leaving behind the other +associated data. One can attach the headers too, like we do here.

+
>>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+>>> advar = astrodata.open('../playdata/N20170609S0154_varAdded.fits')
+
+>>> ad.append(advar[3].data, header=advar[3].hdr)
+>>> ad.info()
+Filename: ../playdata/N20170609S0154.fits
+Tags: ACQUISITION GEMINI GMOS IMAGE NORTH RAW SIDEREAL UNPREPARED
+Pixels Extensions
+Index  Content                  Type              Dimensions     Format
+[ 0]   science                  NDAstroData       (2112, 288)    uint16
+[ 1]   science                  NDAstroData       (2112, 288)    uint16
+[ 2]   science                  NDAstroData       (2112, 288)    uint16
+[ 3]   science                  NDAstroData       (2112, 288)    uint16
+[ 4]   science                  NDAstroData       (2112, 256)    float32
+
+
+

Notice how a new extension was created but variance, mask, the OBJCAT +table and OBJMASK image were not copied over. Only the science pixel data was +copied over.

+

Please note, there is no implementation for the “insertion” of an extension.

+
+
+

Removing an extension or part of one

+

Removing an extension or a part of an extension is straightforward. The +Python command del() is used on the item to remove. Below are a few +examples, but first let us load a file

+
>>> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits')
+>>> ad.info()
+
+
+

As you go through these examples, check the new structure with ad.info() +after every removal to see how the structure has changed.

+

Deleting a whole AstroData extension, the fourth one

+
>>> del ad[3]
+
+
+

Deleting only the variance array from the second extension

+

+
+
+

Deleting a table associated with the first extension

+

+
+
+

Deleting a global table, not attached to a specific extension

+

+
+
+
+
+
+

Writing back to a file

+

The AstroData class implements methods for writing its data back to a +MEF file on disk.

+
+

Writing to a new file

+

There are various ways to define the destination for the new FITS file. +The most common and natural way is

+
>>> ad.write('new154.fits')
+# If the file already exists, an error will be raised unless overwrite=True
+# is specified.
+>>> ad.write('new154.fits', overwrite=True)
+
+
+

This will write a FITS file named ‘new154.fits’ in the current directory. With +overwrite=True, it will overwrite the file if it already exists. A path +can be prepended to the filename if the current directory is not the +destination.

+

Note that ad.filename and ad.path have not changed, we have just +written to the new file, the AstroData object is in no way associated with +that new file.

+
>>> ad.path
+'../playdata/N20170609S0154.fits'
+>>> ad.filename
+'N20170609S0154.fits'
+
+
+

If you want to create that association, the ad.filename and ad.path +needs to be modified first. For example

+
>>> ad.filename = 'new154.fits'
+>>> ad.write(overwrite=True)
+
+>>> ad.path
+'../playdata/new154.fits'
+>>> ad.filename
+'new154.fits'
+
+
+

Changing ad.filename also changes the filename in the ad.path. The +sequence above will write ‘new154.fits’ not in the current directory but +rather to the directory that is specified in ad.path.

+
+

Warning

+

ad.write() has an argument named filename. Setting filename +in the call to ad.write(), as in ad.write(filename='new154.fits') +will NOT modify ad.filename or ad.path. The two “filenames”, one a +method argument the other a class attribute have no association to each +other.

+
+
+
+

Updating an existing file on disk

+

Updating an existing file on disk requires explicitly allowing overwrite.

+

If you have not written ‘new154.fits’ to disk yet (from previous section)

+
>>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+>>> ad.write('new154.fits', overwrite=True)
+
+
+

Now let’s open ‘new154.fits’, and write to it

+
>>> adnew = astrodata.open('new154.fits')
+>>> adnew.write(overwrite=True)
+
+
+
+
+

A note on FITS header keywords

+

When writing an AstroData object as a FITS file, it is necessary to add or +update header keywords to represent some of the internally-stored information. +Any extensions that did not originally belong to a given AstroData instance +will be assigned new EXTVER keywords to avoid conflicts with existing +extensions, and the internal WCS is converted to the appropriate FITS keywords. +Note that in some cases it may not be possible for standard FITS keywords to +accurately represent the true WCS. In such cases, the FITS keywords are written +as an approximation to the true WCS, together with an additional keyword

+

to indicate this. The accurate WCS is written as an additional FITS extension with +EXTNAME='WCS' that AstroData will recognize when the file is read back in. The +WCS extension will not be written to disk if there is an accurate FITS +representation of the WCS (e.g., for a simple image).

+
+
+
+

Create New MEF Files

+

A new MEF file can be created from an existing, maybe modified, file or +created from scratch (e.g., using computer-generated data/images).

+
+

Create New Copy of MEF Files

+
+

Basic example

+

As seen above, a MEF file can be opened with astrodata, the AstroData +object can be modified (or not), and then written back to disk under a +new name.

+
>>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+... optional modifications here ...
+>>> ad.write('newcopy.fits')
+
+
+
+
+

Needing true copies in memory

+

Sometimes it is a true copy in memory that is needed. This is not specific +to MEF. In Python, doing something like adnew = ad does not create a +new copy of the AstrodData object; it just gives it a new name. If you +modify adnew you will be modifying ad too. They point to the same +block of memory.

+

To create a true independent copy, the deepcopy utility needs to be used.

+
.. doctest::
+
+
+
>>> from copy import deepcopy
+>>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+>>> adcopy = deepcopy(ad)
+
+
+
+

Warning

+

deepcopy can cause memory problems, depending on the size of the data +being copied as well as the size of objects it references. If you notice +your memory becoming large/full, consider breaking down the copy into +smaller pieces and f.

+
+
+
+
+

Create New MEF Files from Scratch

+

Before one creates a new MEF file on disk, one has to create the AstroData +object that will be eventually written to disk. The AstroData object +created also needs to know that it will have to be written using the MEF +format. This is fortunately handled fairly transparently by astrodata.

+

The key to associating the FITS data to the AstroData object is simply to +create the AstroData object from astropy.io.fits header objects. Those +will be recognized by astrodata as FITS and the constructor for FITS will be +used. The user does not need to do anything else special. Here is how it is +done.

+
+

Create a MEF with basic header and data array set to zeros

+
>>> import numpy as np
+>>> from astropy.io import fits
+
+>>> phu = fits.PrimaryHDU()
+
+>>> pixel_data = np.zeros((100,100))
+
+>>> hdu = fits.ImageHDU()
+>>> hdu.data = pixel_data
+
+>>> ad = astrodata.create(phu)
+>>> ad.append(hdu, name='SCI')
+
+# Or another way to do the last two blocks:
+>>> hdu = fits.ImageHDU(data=pixel_data, name='SCI')
+>>> ad = astrodata.create(phu, [hdu])
+
+# Finally write to a file.
+>>> ad.write('new_MEF.fits')
+
+
+
+
+

Associate a pixel array with a science pixel array

+

Only main science (labed as SCI) pixel arrays are added an +AstroData object. It not uncommon to have pixel information associated with +those main science pixels, such as pixel masks, variance arrays, or other +information.

+

These pixel arrays are added to specific slice of the astrodata object they are +associated with.

+

Building on the AstroData object we created in the previously, we can add a +new pixel array directly to the slice(s) of the AstroData object it should be +associated with by assigning it as an attribute of the object.

+
>>> extra_data = np.ones((100, 100))
+>>> ad[0].EXTRADATA = extra_data
+
+
+

When the file is written to disk as a MEF, an extension will be created with +EXTNAME = EXTRADATA and an EXTVER that matches the slice’s EXTVER, +in this case is would be 1.

+
+
+

Represent a table as a FITS binary table in an AstroData object

+

One first needs to create a table, either an astropy.table.Table +or a BinTableHDU. See the Astropy documentation +on tables and this manual’s section dedicated to tables for +more information.

+

In the first example, we assume that my_astropy_table is +a Table ready to be attached to an AstroData +object. (Warning: we have not created my_astropy_table therefore the +example below will not run, though this is how it would be done.)

+
>>> phu = fits.PrimaryHDU()
+>>> ad = astrodata.create(phu)
+
+>>> astrodata.add_header_to_table(my_astropy_table)
+>>> ad.append(my_astropy_table, name='SMAUG')
+
+
+

In the second example, we start with a FITS BinTableHDU +and attach it to a new AstroData object. (Again, we have not created +my_fits_table so the example will not run.)

+
>>> phu = fits.PrimaryHDU()
+>>> ad = astrodata.create(phu)
+>>> ad.append(my_fits_table, name='DROGON')
+
+
+

As before, once the AstroData object is constructed, the ad.write() +method can be used to write it to disk as a MEF file.

+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/usermanual/structure.html b/manuals/usermanual/structure.html new file mode 100644 index 00000000..e55ab598 --- /dev/null +++ b/manuals/usermanual/structure.html @@ -0,0 +1,262 @@ + + + + + + + The AstroData Object — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

The AstroData Object

+

The AstroData object represents the data and metadata of a single file on +disk. As of this version, AstroData has a default implementation supporting +the FITS file format. If you wish to extend AstroData to support other file +formats, see AstroData and Derivatives.

+

The internal structure of the AstroData object makes uses of astropy’s +NDData, table, and +Header, the latter simply because it is a convenient +ordered dictionary.

+
+

Example location

+

The examples in this section can be found here: user_structure_examples.

+
+
+

Walkthrough

+
+

Global vs Extension-specific

+

At the top level, the AstroData structure is divided in two types of +information. In the first category, there is the information that applies to +the data globally, for example the information that would be stored in a FITS +Primary Header Unit, a table from a catalog that matches the RA and DEC of the +field, etc. In the second category, there is the information specific to +individual science pixel extensions, for example the gain of the amplifier, the +data themselves, the error on those data, etc.

+

The composition and amount of information depends on the contents of the file +itself. This information varies dramatically between observatories, so ensure +that you have characterized your data well. Accessing the contents of an +AstroData object is done through the info() +method.

+

The “Pixel Extensions” contain the pixel data (in this case, something specific +to our data type). Each extension is represented individually in a list +(0-indexed like all Python lists). The science pixel data, its associated +metadata (extension header), and any other pixel or table extensions directly +associated with that science pixel data are stored in a NDAstroData object +which subclasses astropy’s NDData. An AstroData extension is accessed like +any list: ad[0] will return the first image. To access the science pixels, +one uses ad[0].data; for the object mask of the first extension, +ad[0].OBJMASK; etc.

+
+
+

Organization of Global Information

+

All the global information can be accessed as attributes of the AstroData +object. The global headers, or Primary Header Unit (PHU), is stored in the +phu attribute as an astropy.io.fits.Header.

+

Any global tables are stored in the private attribute _tables. For example, +if we had a REFCAT global table as part of our data (see example +GEMINI_EXAMPLE a Python dictionary with the name (eg. +“REFCAT”) as the key. All tables are stored as astropy.table.Table. +Access to those table is done using the key directly as if it were a normal +attribute, eg. ad.REFCAT. Header information for the table, if read in +from a FITS table, is stored in the meta attribute of the +astropy.table.Table, eg. ad.REFCAT.meta['header']. It is for +information only, it is not used.

+
+
+

Organization of the Extension-specific Information

+

The pixel data are stored in the AstroData attribute nddata as a list +of NDAstroData object. The NDAstroData object is a subclass of astropy +NDData and it is fully compatible with any function expecting an NDData as +input. The pixel extensions are accessible through slicing, eg. ad[0] or +even ad[0:2]. A slice of an AstroData object is an AstroData object, and +all the global attributes are kept. For example:

+
>>> ad[0].info()
+Filename: N20170609S0154_varAdded.fits
+Tags: ACQUISITION GEMINI GMOS IMAGE NORTH OVERSCAN_SUBTRACTED OVERSCAN_TRIMMED
+    PREPARED SIDEREAL
+
+Pixels Extensions
+Index  Content                  Type              Dimensions     Format
+[ 0]   science                  NDAstroData       (2112, 256)    float32
+          .variance             ndarray           (2112, 256)    float32
+          .mask                 ndarray           (2112, 256)    uint16
+          .OBJCAT               Table             (6, 43)        n/a
+          .OBJMASK              ndarray           (2112, 256)    uint8
+
+Other Extensions
+               Type        Dimensions
+.REFCAT        Table       (245, 16)
+
+
+

Note how REFCAT is still present.

+

The science data is accessed as ad[0].data, the variance as ad[0].variance, +and the data quality plane as ad[0].mask. Those familiar with astropy +NDData will recognize the structure “data, error, mask”, and will notice +some differences. First AstroData uses the variance for the error plane, not +the standard deviation. Another difference will be evident only when one looks +at the content of the mask. NDData masks contain booleans, AstroData masks +are uint16 bit mask that contains information about the type of bad pixels +rather than just flagging them a bad or not. Since 0 is equivalent to +False (good pixel), the AstroData mask is fully compatible with the +NDData mask.

+

Header information for the extension is stored in the NDAstroData meta +attribute. All table and pixel extensions directly associated with the +science extension are also stored in the meta attribute.

+

Technically, an extension header is located in ad.nddata[0].meta['header']. +However, for obviously needed convenience, the normal way to access that header +is ad[0].hdr.

+

Tables and pixel arrays associated with a science extension are +stored in ad.nddata[0].meta['other'] as a dictionary keyed on the array +name, eg. OBJCAT, OBJMASK. As it is for global tables, astropy tables +are used for extension tables. The extension tables and extra pixel arrays are +accessed, like the global tables, by using the table name rather than the long +format, for example ad[0].OBJCAT and ad[0].OBJMASK.

+

When reading a FITS Table, the header information is stored in the +meta['header'] of the table, eg. ad[0].OBJCAT.meta['header']. That +information is not used, it is simply a place to store what was read from disk.

+

The header of a pixel extension directly associated with the science extension +should match that of the science extension. Therefore such headers are not +stored in AstroData. For example, the header of ad[0].OBJMASK is the +same as that of the science, ad[0].hdr.

+

The world coordinate system (WCS) is stored internally in the wcs attribute +of the NDAstroData object. It is constructed from the header keywords when +the FITS file is read from disk, or directly from the WCS extension if +present (see the next chapter). If the WCS is modified (for +example, by refining the pointing or attaching a more accurate wavelength +calibration), the FITS header keywords are not updated and therefore they should +never be used to determine the world coordinates of any pixel. These keywords are +only updated when the object is written to disk as a FITS file. The WCS is +retrieved as follows: ad[0].wcs.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/usermanual/tables.html b/manuals/usermanual/tables.html new file mode 100644 index 00000000..59e070b0 --- /dev/null +++ b/manuals/usermanual/tables.html @@ -0,0 +1,345 @@ + + + + + + + Table Data — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Table Data

+

Try it yourself

+

Download the data package (A quick example) if you wish to follow along and run the +examples. Then

+
$ cd <path>/ad_usermanual/playground
+$ python
+
+
+

Then import core astrodata and the Gemini astrodata configurations.

+
>>> import astrodata
+>>> import gemini_instruments
+
+
+
+

Tables and Astrodata

+

Tables are stored as astropy.table Table class. FITS tables too +are represented in Astrodata as Table and FITS headers are stored in +the NDAstroData .meta attribute. Most table access should be done +through the Table interface. The best reference on Table is the +Astropy documentation itself. In this chapter we covers some common +examples to get the reader started.

+

The astropy.table documentation can be found at: http://docs.astropy.org/en/stable/table/index.html

+
+
+

Operate on a Table

+

Let us open a file with tables. Some tables are associated with specific +extensions, and there is one table that is global to the AstroData object.

+
>>> ad = astrodata.open('../playdata/N20170609S0154_varAdded.fits')
+>>> ad.info()
+
+
+

To access the global table named REFCAT:

+
>>> ad.REFCAT
+
+
+

To access the OBJCAT table in the first extension

+
>>> ad[0].OBJCAT
+
+
+
+

Column and Row Operations

+

Columns are named. Those names are used to access the data as columns. +Rows are not names and are simply represented as a sequential list.

+
+

Read columns and rows

+

To get the names of the columns present in the table:

+
>>> ad.REFCAT.colnames
+['Id', 'Cat_Id', 'RAJ2000', 'DEJ2000', 'umag', 'umag_err', 'gmag',
+'gmag_err', 'rmag', 'rmag_err', 'imag', 'imag_err', 'zmag', 'zmag_err',
+'filtermag', 'filtermag_err']
+
+
+

Then it is easy to request the values for specific columns:

+
>>> ad.REFCAT['zmag']
+>>> ad.REFCAT['zmag', 'zmag_err']
+
+
+

To get the content of a specific row, row 10 in this case:

+
>>> ad.REFCAT[9]
+
+
+

To get the content of a specific row(s) from a specific column(s):

+
>>> ad.REFCAT['zmag'][4]
+>>> ad.REFCAT['zmag'][4:10]
+>>> ad.REFCAT['zmag', 'zmag_err'][4:10]
+
+
+
+
+

Change values

+

Assigning new values works in a similar way. When working on multiple elements +it is important to feed a list that matches in size with the number of elements +to replace.

+
>>> ad.REFCAT['imag'][4] = 20.999
+>>> ad.REFCAT['imag'][4:10] = [5, 6, 7, 8, 9, 10]
+
+>>> overwrite_col = [0] * len(ad.REFCAT)  # a list of zeros, size = nb of rows
+>>> ad.REFCAT['imag_err'] = overwrite_col
+
+
+
+
+

Add a row

+

To append a row, there is the add_row() method. The length of the row +should match the number of columns:

+
>>> new_row = [0] * len(ad.REFCAT.colnames)
+>>> new_row[1] = ''   # Cat_Id column is of "str" type.
+>>> ad.REFCAT.add_row(new_row)
+
+
+
+
+

Add a column

+

Adding a new column can be more involved. If you need full control, please +see the AstroPy Table documentation. For a quick addition, which might be +sufficient for your use case, we simply use the “dictionary” technique. Please +note that when adding a column, it is important to ensure that all the +elements are of the same type. Also, if you are planning to use that table +in IRAF/PyRAF, we recommend not using 64-bit types.

+
>>> import numpy as np
+
+>>> new_column = [0] * len(ad.REFCAT)
+>>> # Ensure that the type is int32, otherwise it will default to int64
+>>> # which generally not necessary.  Also, IRAF 32-bit does not like it.
+>>> new_column = np.array(new_column).astype(np.int32)
+>>> ad.REFCAT['my_column'] = new_column
+
+
+

If you are going to write that table back to disk as a FITS Bintable, then +some additional headers need to be set. Astrodata will take care of that +under the hood when the write method is invoked.

+
>>> ad.write('myfile_with_modified_table.fits')
+
+
+
+
+
+

Selection and Rejection Operations

+

Normally, one does not know exactly where the information needed is located +in a table. Rather some sort of selection needs to be done. This can also +be combined with various calculations. We show two such examples here.

+
+

Select a table element from criterion

+
>>> # Get the magnitude of a star selected by ID number
+>>> ad.REFCAT['zmag'][ad.REFCAT['Cat_Id'] == '1237662500002005475']
+
+>>> # Get the ID and magnitude of all the stars brighter than zmag 18.
+>>> ad.REFCAT['Cat_Id', 'zmag'][ad.REFCAT['zmag'] < 18.]
+
+
+
+
+

Rejection and selection before statistics

+
>>> t = ad.REFCAT   # to save typing
+
+>>> # The table has "NaN" values.  ("Not a number")  We need to ignore them.
+>>> t['zmag'].mean()
+nan
+>>> # applying rejection of NaN values:
+>>> t['zmag'][np.where(~np.isnan(t['zmag']))].mean()
+20.377306
+
+
+
+
+
+

Accessing FITS table headers directly

+

If for some reason you need to access the FITS table headers directly, here +is how to do it. It is very unlikely that you will need this.

+

To see the FITS headers:

+
>>> ad.REFCAT.meta['header']
+>>> ad[0].OBJCAT.meta['header']
+
+
+

To retrieve a specific FITS table header:

+
>>> ad.REFCAT.meta['header']['TTYPE3']
+'RAJ2000'
+>>> ad[0].OBJCAT.meta['header']['TTYPE3']
+'Y_IMAGE'
+
+
+

To retrieve all the keyword names matching a selection:

+
>>> keynames = [key for key in ad.REFCAT.meta['header'] if key.startswith('TTYPE')]
+
+
+
+
+
+

Create a Table

+

To create a table that can be added to an AstroData object and eventually +written to disk as a FITS file, the first step is to create an Astropy +Table.

+

Let us first add our data to NumPy arrays, one array per column:

+
>>> import numpy as np
+
+>>> snr_id = np.array(['S001', 'S002', 'S003'])
+>>> feii = np.array([780., 78., 179.])
+>>> pabeta = np.array([740., 307., 220.])
+>>> ratio = pabeta / feii
+
+
+

Then build the table from that data:

+
>>> from astropy.table import Table
+
+>>> my_astropy_table = Table([snr_id, feii, pabeta, ratio],
+...                          names=('SNR_ID', 'FeII', 'PaBeta', 'ratio'))
+
+
+

Now we append this Astropy Table to a new AstroData object.

+
>>> # Since we are going to write a FITS, we build the AstroData object
+>>> # from FITS objects.
+>>> from astropy.io import fits
+
+>>> phu = fits.PrimaryHDU()
+>>> ad = astrodata.create(phu)
+>>> ad.MYTABLE = my_astropy_table
+>>> ad.info()
+>>> ad.MYTABLE
+
+>>> ad.write('new_table.fits')
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/manuals/usermanual/tags.html b/manuals/usermanual/tags.html new file mode 100644 index 00000000..59c1fbfa --- /dev/null +++ b/manuals/usermanual/tags.html @@ -0,0 +1,298 @@ + + + + + + + Astrodata Tag — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Astrodata Tag

+
+

What is an Astrodata Tag?

+

Tag is a way to describe the data in an AstroData object. Tags are used to +idenfity the type of AstroData object to be created when from_file() is called.

+

A Tag is added to an AstroData object by defining a function wrapped with +the astro_data_tag() decorator. The function must return a +TagSet object, which describes the behavior of a tag.

+

For example, the following function defines a tag called “RAW”

+
class RawAstroData(AstroData):
+    @astro_data_tag
+    def _tag_raw(self):
+        """Identify if this is raw data"""
+        if self.phu.get('PROCTYPE') == 'RAW':
+            return TagSet(['RAW'])
+
+
+

Now, if we call from_file() on a file that has a PROCTYPE keyword set to “RAW”, the +AstroData object will have the “RAW” tag

+

+
+
+

From here, these tag sets can be used to understand what the data is describing +and how best to process it. It can also contain information about the state of +processing (e.g., RAW vs PROCESSED), or any important flags.

+

These tags are meant to work well with FITS data, using the headers to +determine what the data is. However, they can be used with any data type that +can be described by a set of tags, as long as they are properly defined by the +developer (see ad_tags for more information about developing with Tag).

+

For some examples of tags in production code, see the DRAGONS:gemini_instruments +package, which defined a number of AstroData derivatives used as part of the +DRAGONS data reduction library for reading as well as processing data.

+
+
+

Using the Astrodata Tags

+

Try it yourself

+

Download the data package (A quick example) if you wish to follow along and run the +examples. Then

+
$ cd <path>/ad_usermanual/playground
+$ python
+
+
+

Before doing anything, you need to import astrodata and the Gemini instrument +configuration package (DRAGONS:gemini_instruments).

+
>>> import astrodata
+>>> import gemini_instruments
+
+
+

Let us open a Gemini dataset and see what tags we get:

+
>>> ad = astrodata.open('../playdata/N20170609S0154.fits')
+>>> ad.tags
+{'RAW', 'GMOS', 'GEMINI', 'NORTH', 'SIDEREAL', 'UNPREPARED', 'IMAGE', 'ACQUISITION'}
+
+
+

The file we loaded is raw, GMOS North data. It is a 2D image and it is an +acquisition image, not a science observation. The “UNPREPARED” tag indicates +that the file has never been touched by the Recipe System which runs a +“prepare” primitive as the first step of each recipe.

+

Let’s try another

+
>>> ad = astrodata.open('../playdata/N20170521S0925_forStack.fits')
+>>> ad.tags
+{'GMOS', 'GEMINI', 'NORTH', 'SIDEREAL', 'OVERSCAN_TRIMMED', 'IMAGE',
+'OVERSCAN_SUBTRACTED', 'PREPARED'}
+
+
+

This file is a science GMOS North image. It has been processed by the +Recipe System. The overscan level has been subtracted and the overscan section +has been trimmed away. The tags do NOT include all the processing steps. Rather, +at least from the time being, it focuses on steps that matter when associating +calibrations.

+

The tags can be used when coding. For example

+
>>> if 'GMOS' in ad.tags:
+...    print('I am GMOS')
+... else:
+...    print('I am these instead:', ad.tags)
+
+
+

And

+
>>> if {'IMAGE', 'GMOS'}.issubset(ad.tags):
+...   print('I am a GMOS Image.')
+
+
+
+
+

Creating New Astrodata Tags [Advanced Topic]

+

The Developer Guide describes how to create new AstroData classes for new +instruments (specifically, see ad_tags). This section describes the very basic +steps for a new user to create self-defined tags.

+

The content of this section is based on the example file +EXAMPLE FILE. That file can be used as a full reference.

+
>>> class MyAstroData(AstroData):
+...     @astro_data_tag
+...     def _tag_mytag(self):
+...         return TagSet(['MYTAG'])
+...
+
+
+

The astro_data_tag() decorator is used to identify the function as a tag +function. While not strictly necessary, it is recommended to use the +_tag prefix in the function name to make it clear that it is a tag +function. When a file is opened using from_file(), the AstroData class will +automatically call all the tag functions to determine the tags for the +AstroData object, and then determine if the file being opened is +appropriately tagged for the AstroData class. If it is not, the class is +not used to load in the object and its data; otherwise, it attempts to resolve +all known AstroData types to construct the appropriate instance.

+

AstroData only knows of registered AstroData class types. To register our +class, we use AstroDataFactory:

+

+
+
+

We now see our class is registered, and can use from_file() to open a file that has +the identifying tag:

+
# Fake FITS file with a MYTAG keyword
+>>> ad = astrodata.open('mytag.fits')
+>>> ad.tags
+{'MYTAG'}
+
+# Create one from scratch with the MYTAG keyword
+>>> from astrodata import create_from_scratch
+>>> from astropy.io import fits
+>>> phu = fits.PrimaryHDU(header={'MYTAG': True}).header
+>>> ad = create_from_scratch(phu)
+>>> print(ad.tags)
+{'MYTAG'}
+>>> type(ad)
+<class 'astrodata.ad_tag_example_user.MyAstroData'>
+
+
+

The tag function looks at the provided headers and if the keyword “OBSTYPE” is +set to “ARC”, the tags “ARC” and “CAL” (for calibration) will be assigned to +the AstroData object.

+
+

Warning

+

Tag functionality is primarily designed with FITS files in mind. If you +are extending AstroData to work with other data types, you will need to +define your own tag functions that specifically handle resolving tags for +that file type.

+

This does not mean that you cannot use AstroData with other data +types, or that it is especially difficult. It just means that you will need +to define your own tag functions in such a way that they do not use, e.g., +self.phu if no such concept/equivalent exists in your desired file +type.

+
+

A whole suite of such tag functions is needed to fully characterize all +types of data an instrument can produce. DRAGONS:gemini_instruments is an +example of a package defining a number of AstroData types that use the +tag system to automaticlaly and precisely identify the specific instrument +used to produce the data, and to process it accordingly.

+

Tags should be exact and precise. For quantities and values that are +not so well defined (for example, the type of observation), descriptors +are used. For more information about descriptors, see the section on +Metadata and Headers.

+

For more information on creating and working with Tags, as well as developing +with/for astrodata, see the Developer Guide.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 00000000..7b7e5ecf Binary files /dev/null and b/objects.inv differ diff --git a/py-modindex.html b/py-modindex.html new file mode 100644 index 00000000..87e5aff9 --- /dev/null +++ b/py-modindex.html @@ -0,0 +1,133 @@ + + + + + + Python Module Index — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Python Module Index

+ +
+ a +
+ + + + + + + +
 
+ a
+ astrodata +
+ + +
+
+
+ +
+ +
+

© Copyright 2023-present, NOIRLab/Gemini Observatories.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/search.html b/search.html new file mode 100644 index 00000000..15197095 --- /dev/null +++ b/search.html @@ -0,0 +1,130 @@ + + + + + + Search — astrodata 0.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2023-present, NOIRLab/Gemini Observatories.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 00000000..ae458339 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["api/astrodata.AstroData", "api/astrodata.AstroDataError", "api/astrodata.AstroDataMixin", "api/astrodata.NDAstroData", "api/astrodata.Section", "api/astrodata.TagSet", "api/astrodata.add_header_to_table", "api/astrodata.astro_data_descriptor", "api/astrodata.astro_data_tag", "api/astrodata.create", "api/astrodata.from_file", "api/astrodata.open", "api/astrodata.returns_list", "api/astrodata.version", "api_short", "examples/gemini_examples/index", "examples/generic_examples/index", "examples/index", "index", "manuals/appendix_descriptors", "manuals/cheatsheet", "manuals/full_api", "manuals/index", "manuals/progmanual/adclass", "manuals/progmanual/containers", "manuals/progmanual/descriptors", "manuals/progmanual/design", "manuals/progmanual/index", "manuals/progmanual/intro", "manuals/progmanual/tags", "manuals/usermanual/data", "manuals/usermanual/headers", "manuals/usermanual/index", "manuals/usermanual/intro", "manuals/usermanual/iomef", "manuals/usermanual/structure", "manuals/usermanual/tables", "manuals/usermanual/tags"], "filenames": ["api/astrodata.AstroData.rst", "api/astrodata.AstroDataError.rst", "api/astrodata.AstroDataMixin.rst", "api/astrodata.NDAstroData.rst", "api/astrodata.Section.rst", "api/astrodata.TagSet.rst", "api/astrodata.add_header_to_table.rst", "api/astrodata.astro_data_descriptor.rst", "api/astrodata.astro_data_tag.rst", "api/astrodata.create.rst", "api/astrodata.from_file.rst", "api/astrodata.open.rst", "api/astrodata.returns_list.rst", "api/astrodata.version.rst", "api_short.rst", "examples/gemini_examples/index.rst", "examples/generic_examples/index.rst", "examples/index.rst", "index.rst", "manuals/appendix_descriptors.rst", "manuals/cheatsheet.rst", "manuals/full_api.rst", "manuals/index.rst", "manuals/progmanual/adclass.rst", "manuals/progmanual/containers.rst", "manuals/progmanual/descriptors.rst", "manuals/progmanual/design.rst", "manuals/progmanual/index.rst", "manuals/progmanual/intro.rst", "manuals/progmanual/tags.rst", "manuals/usermanual/data.rst", "manuals/usermanual/headers.rst", "manuals/usermanual/index.rst", "manuals/usermanual/intro.rst", "manuals/usermanual/iomef.rst", "manuals/usermanual/structure.rst", "manuals/usermanual/tables.rst", "manuals/usermanual/tags.rst"], "titles": ["AstroData", "AstroDataError", "AstroDataMixin", "NDAstroData", "Section", "TagSet", "add_header_to_table", "astro_data_descriptor", "astro_data_tag", "create", "from_file", "open", "returns_list", "version", "Common API for Users", "Gemini Examples", "Generic Examples", "Examples", "astrodata Documentation", "List of Gemini Standard Descriptors", "Cheat Sheet", "Reference API", "Astrodata Manual", "AstroData and Derivatives", "Data Containers", "Descriptors", "General Design", "Programmer\u2019s Manual", "Introduction", "Tags", "Pixel Data", "Metadata and Headers", "User Manual", "Introduction", "Input and Output Operations and Extension Manipulation - MEF", "The AstroData Object", "Table Data", "Astrodata Tag"], "terms": {"astrodataerror": [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "astrodatamixin": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "ndastrodata": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "section": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 37], "tagset": [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "astro_data_descriptor": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "astro_data_tag": [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "creat": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35], "from_fil": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "return_list": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "version": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "user": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37], "guid": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "develop": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "exampl": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32, 36, 37], "numpi": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 37], "astropi": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 37], "mapper": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "nddata": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "ndarrai": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 37], "ndwindow": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "ndwindowingastrodata": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "primitivemapp": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "recipemapp": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "reduc": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "tabl": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35, 37], "dragon": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "gemini_instru": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "geminidr": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "gemini": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "observatori": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "recipe_system": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "astrodatafactori": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "recip": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "system": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 36, 37], "programm": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "manual": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 28, 29, 30, 31, 33, 34, 35, 36, 37], "instal": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 34, 35, 36, 37], "tag": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 36], "descriptor": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 21, 22, 23, 24, 26, 27, 28, 29, 30, 32, 33, 34, 35, 36, 37], "issu": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "tracker": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "index": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "ds9": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37], "class": [0, 1, 2, 3, 4, 5, 7, 8, 19, 20, 21, 25, 26, 27, 28, 30, 31, 33, 34, 36, 37], "none": [0, 3, 4, 5, 8, 20, 21, 23, 28, 29, 34], "phu": [0, 20, 21, 23, 25, 28, 29, 31, 33, 34, 35, 36, 37], "indic": [0, 21, 23, 30, 31, 34, 37], "is_singl": [0, 21], "fals": [0, 3, 21, 23, 28, 30, 35], "sourc": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 20, 21, 23, 24, 28], "base": [0, 2, 3, 4, 5, 20, 21, 23, 24, 25, 26, 30, 33, 34, 37], "object": [0, 2, 3, 4, 9, 10, 11, 19, 21, 23, 24, 25, 26, 28, 31, 32, 33, 36, 37], "softwar": [0, 20, 21], "packag": [0, 15, 16, 20, 21, 23, 24, 26, 28, 32, 33, 34, 36, 37], "It": [0, 3, 5, 7, 8, 14, 21, 23, 24, 26, 28, 29, 30, 31, 33, 34, 35, 36, 37], "provid": [0, 3, 14, 16, 17, 21, 22, 23, 24, 25, 26, 28, 30, 31, 33, 37], "an": [0, 2, 3, 7, 8, 9, 10, 11, 14, 20, 21, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35, 36], "interfac": [0, 3, 14, 20, 21, 23, 25, 26, 28, 33, 36], "manipul": [0, 21, 23, 24, 26, 31, 32, 33], "astronom": [0, 14, 21, 26, 28, 31, 33, 34], "data": [0, 2, 3, 9, 14, 15, 16, 19, 21, 23, 25, 26, 27, 28, 31, 32, 33, 35, 37], "set": [0, 3, 5, 19, 20, 21, 23, 24, 25, 28, 29, 30, 31, 36, 37], "paramet": [0, 3, 4, 7, 8, 12, 21, 23], "list": [0, 3, 4, 5, 7, 12, 20, 21, 22, 23, 25, 29, 30, 31, 34, 35, 36, 37], "dict": [0, 2, 3, 21, 24, 34], "name": [0, 5, 19, 20, 21, 23, 25, 28, 29, 31, 34, 35, 36, 37], "io": [0, 3, 20, 21, 24, 28, 31, 34, 35, 36, 37], "fit": [0, 2, 3, 6, 19, 20, 21, 23, 24, 25, 26, 28, 29, 30, 31, 33, 35, 37], "header": [0, 6, 14, 21, 23, 25, 28, 30, 32, 33, 35, 37], "primari": [0, 21, 26, 28, 30, 31, 34, 35], "int": [0, 19, 21, 23], "map": [0, 20, 21, 23, 24, 31, 35], "thi": [0, 2, 3, 5, 7, 12, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 28, 29, 30, 31, 33, 34, 35, 36, 37], "access": [0, 2, 3, 21, 23, 24, 26, 28, 30, 32, 33, 35], "i": [0, 1, 2, 3, 4, 5, 12, 14, 15, 16, 18, 19, 20, 21, 23, 24, 25, 26, 28, 29, 30, 31, 32, 34, 35, 36], "us": [0, 3, 5, 7, 11, 12, 15, 16, 17, 19, 20, 21, 22, 23, 24, 25, 26, 28, 29, 31, 32, 34, 35, 36], "when": [0, 1, 3, 12, 17, 20, 21, 23, 24, 26, 29, 30, 31, 33, 34, 35, 36, 37], "slice": [0, 2, 3, 4, 12, 21, 23, 25, 26, 27, 30, 34, 35], "have": [0, 20, 21, 23, 24, 29, 30, 31, 34, 35, 37], "from": [0, 2, 3, 4, 5, 9, 10, 11, 16, 19, 20, 21, 23, 24, 25, 26, 28, 29, 31, 32, 33, 35, 37], "its": [0, 3, 15, 21, 23, 25, 26, 28, 29, 30, 31, 33, 34, 35, 37], "parent": [0, 21, 23, 24, 26], "through": [0, 20, 21, 23, 30, 31, 33, 34, 35, 36], "attribut": [0, 2, 3, 4, 5, 14, 19, 20, 21, 24, 26, 30, 32, 34, 35, 36], "summari": [0, 2, 3, 4], "method": [0, 3, 4, 5, 7, 8, 12, 14, 21, 23, 24, 25, 28, 29, 30, 31, 33, 34, 35, 36], "document": [0, 2, 3, 4, 5, 21, 23, 28, 30, 34, 36, 37], "A": [0, 2, 3, 4, 8, 12, 19, 20, 21, 22, 23, 25, 28, 30, 32, 35, 36, 37], "arrai": [0, 3, 19, 20, 21, 24, 28, 30, 31, 35, 36], "singl": [0, 12, 14, 21, 23, 24, 25, 26, 28, 29, 31, 33, 35], "correspond": [0, 21, 31, 34], "scienc": [0, 16, 19, 20, 21, 23, 30, 35, 37], "attach": [0, 20, 21, 34, 35], "each": [0, 4, 20, 21, 23, 25, 26, 29, 30, 31, 34, 35, 37], "extens": [0, 12, 14, 21, 23, 24, 25, 26, 28, 30, 32, 33, 36], "return": [0, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 19, 21, 23, 24, 25, 26, 28, 29, 30, 31, 33, 34, 35, 37], "sequenc": [0, 8, 21, 34], "been": [0, 21, 23, 28, 29, 30, 31, 33, 37], "decor": [0, 7, 8, 12, 21, 23, 25, 28, 29, 37], "type": [0, 5, 7, 8, 12, 19, 20, 21, 23, 26, 28, 29, 30, 31, 33, 34, 35, 36, 37], "tupl": [0, 4, 5, 19, 21, 29, 31], "str": [0, 5, 19, 20, 21, 23, 31, 36], "expos": [0, 19, 21, 24, 30], "collect": [0, 21, 29], "string": [0, 4, 8, 21, 23, 28, 30], "can": [0, 2, 3, 4, 5, 8, 12, 20, 21, 23, 24, 25, 26, 28, 29, 30, 31, 33, 34, 35, 36, 37], "directli": [0, 21, 23, 25, 28, 30, 34, 35], "instanc": [0, 3, 5, 21, 23, 24, 26, 28, 29, 31, 34, 37], "ar": [0, 2, 3, 4, 5, 14, 16, 19, 20, 21, 23, 24, 25, 26, 28, 29, 30, 31, 33, 34, 35, 36, 37], "part": [0, 8, 15, 21, 28, 30, 31, 33, 35, 37], "standard": [0, 3, 21, 22, 23, 24, 28, 29, 30, 31, 34, 35], "e": [0, 3, 19, 21, 23, 24, 25, 26, 28, 30, 31, 33, 34, 37], "ad": [0, 3, 5, 19, 20, 21, 23, 24, 28, 29, 32, 33, 34, 35, 36, 37], "dynam": [0, 21], "0": [0, 3, 4, 19, 20, 21, 23, 24, 28, 30, 31, 34, 35, 36], "objmask": [0, 2, 20, 21, 34, 35], "objcat": [0, 20, 21, 34, 35, 36], "ext_tabl": [0, 21], "associ": [0, 19, 20, 21, 25, 26, 30, 31, 33, 35, 36, 37], "filenam": [0, 20, 21, 30, 34, 35], "file": [0, 10, 11, 19, 20, 21, 23, 24, 25, 26, 28, 30, 31, 32, 33, 35, 36, 37], "hdr": [0, 20, 21, 23, 28, 30, 31, 34, 35], "all": [0, 3, 5, 15, 19, 20, 21, 23, 26, 29, 30, 31, 33, 34, 35, 36, 37], "fitsheadercollect": [0, 21], "deprec": [0, 4, 11, 21, 23], "instead": [0, 3, 21, 23, 28, 30, 31, 34, 37], "id": [0, 19, 21, 36], "identifi": [0, 19, 21, 23, 37], "1": [0, 3, 4, 19, 20, 21, 23, 26, 28, 29, 30, 31, 34, 36], "number": [0, 4, 17, 19, 21, 23, 24, 25, 30, 31, 33, 34, 36, 37], "is_slic": [0, 21], "If": [0, 3, 4, 7, 8, 14, 20, 21, 22, 23, 25, 29, 30, 31, 33, 34, 35, 36, 37], "repres": [0, 3, 16, 19, 20, 21, 23, 24, 30, 31, 33, 35, 36], "whole": [0, 19, 20, 21, 30, 34, 37], "dataset": [0, 21, 23, 30, 31, 32, 33, 37], "out": [0, 20, 21, 23, 26, 29, 30, 33], "true": [0, 3, 4, 20, 21, 23, 28, 30, 31, 37], "mask": [0, 2, 3, 19, 20, 21, 23, 24, 30, 34, 35], "For": [0, 9, 10, 17, 19, 21, 23, 28, 29, 30, 31, 33, 34, 35, 36, 37], "miss": [0, 21, 29], "onli": [0, 3, 5, 12, 19, 21, 23, 24, 25, 26, 29, 30, 31, 34, 35, 37], "And": [0, 21, 37], "orig_filenam": [0, 21], "origin": [0, 21, 23, 26, 28, 30, 34], "befor": [0, 20, 21, 23, 29, 30, 34, 37], "wa": [0, 19, 21, 26, 30, 33, 34, 35], "modifi": [0, 2, 7, 21, 23, 25, 30, 31, 32, 35], "path": [0, 20, 21, 23, 33, 34, 36, 37], "shape": [0, 2, 4, 21, 24, 30], "top": [0, 7, 21, 25, 26, 35], "level": [0, 19, 21, 22, 23, 26, 30, 35, 37], "defin": [0, 3, 4, 19, 20, 21, 23, 28, 30, 31, 33, 34, 37], "uncertainti": [0, 2, 3, 21, 24], "The": [0, 2, 3, 4, 5, 7, 8, 12, 16, 20, 21, 22, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 36, 37], "": [0, 15, 19, 20, 21, 22, 23, 24, 25, 26, 28, 29, 30, 31, 33, 34, 35, 36, 37], "nduncertainti": [0, 3, 21, 24], "where": [0, 3, 19, 20, 21, 23, 24, 25, 29, 30, 33, 35, 36], "inform": [0, 14, 19, 20, 21, 22, 23, 30, 31, 33, 34, 36, 37], "avail": [0, 20, 21, 23, 30, 31, 33], "varianc": [0, 2, 3, 20, 21, 23, 24, 32, 34, 35], "actual": [0, 3, 21, 23, 25, 29, 31, 34], "support": [0, 3, 15, 21, 22, 26, 30, 31, 32, 34, 35], "under": [0, 21, 23, 34, 36], "hood": [0, 21, 36], "wc": [0, 2, 3, 19, 21, 24, 28, 30, 31, 34, 35], "add": [0, 3, 5, 6, 14, 19, 20, 21, 23, 24, 28, 29, 30, 31, 34, 37], "oper": [0, 3, 12, 15, 21, 22, 23, 24, 26, 31, 32], "perform": [0, 5, 15, 21, 23, 26, 30, 31], "inplac": [0, 21], "addit": [0, 2, 21, 23, 24, 26, 29, 30, 31, 34, 36], "evalu": [0, 21], "self": [0, 4, 21, 23, 26, 28, 29, 37], "operand": [0, 21], "append": [0, 20, 21, 23, 36], "ext": [0, 20, 21, 30, 31], "new": [0, 3, 5, 16, 21, 28, 30, 31, 32, 36], "other": [0, 2, 5, 7, 15, 21, 23, 24, 26, 28, 29, 31, 33, 34, 35, 37], "content": [0, 2, 3, 20, 21, 23, 29, 30, 31, 35, 36, 37], "exact": [0, 21, 37], "accept": [0, 21, 23, 31], "depend": [0, 15, 21, 23, 29, 30, 31, 33, 34, 35], "implement": [0, 3, 9, 10, 21, 23, 24, 26, 30, 31, 33, 34, 35], "specif": [0, 3, 15, 16, 17, 20, 21, 23, 24, 25, 26, 28, 29, 30, 31, 33, 34, 36, 37], "certain": [0, 21, 23, 29], "format": [0, 14, 20, 21, 23, 24, 25, 26, 28, 30, 33, 34, 35], "mai": [0, 21, 23, 24, 26, 28, 29, 31, 33, 34], "special": [0, 21, 23, 34], "eg": [0, 3, 19, 21, 31, 35], "imagehdu": [0, 3, 20, 21, 28, 34], "extract": [0, 2, 21, 23, 25, 30], "option": [0, 3, 5, 21, 34], "typic": [0, 21, 23, 24, 25], "ignor": [0, 21, 24, 29, 34, 36], "global": [0, 5, 20, 21, 34, 36], "requir": [0, 21, 23, 26, 28, 29, 30, 31, 33, 34], "cannot": [0, 21, 23, 31, 37], "deriv": [0, 8, 16, 21, 24, 26, 27, 29, 31, 34, 35, 37], "metadata": [0, 21, 23, 25, 26, 32, 33, 34, 35, 37], "you": [0, 3, 5, 12, 14, 15, 20, 21, 22, 23, 29, 30, 31, 33, 34, 35, 36, 37], "one": [0, 5, 7, 12, 14, 19, 21, 23, 25, 26, 29, 30, 31, 35, 36, 37], "consist": [0, 8, 21, 23, 26, 28, 31], "combin": [0, 2, 5, 7, 19, 21, 24, 30, 31, 36], "letter": [0, 21], "restrict": [0, 21, 29], "capit": [0, 21], "first": [0, 20, 21, 23, 24, 25, 26, 29, 30, 34, 35, 36, 37], "charact": [0, 21, 25], "z": [0, 21], "z0": [0, 21], "9": [0, 21, 30, 36], "same": [0, 4, 21, 23, 24, 25, 26, 29, 30, 31, 34, 35, 36], "necessari": [0, 21, 23, 24, 28, 30, 34, 36, 37], "convert": [0, 8, 21, 31, 34], "more": [0, 2, 14, 21, 23, 25, 26, 28, 29, 30, 31, 33, 34, 35, 36, 37], "suitabl": [0, 21, 23], "intern": [0, 3, 21, 23, 24, 33, 34, 35], "rais": [0, 1, 3, 21, 25, 34], "typeerror": [0, 21], "invalid": [0, 21], "situat": [0, 21, 30], "valueerror": [0, 3, 21], "proper": [0, 21, 30], "valu": [0, 4, 8, 12, 19, 20, 21, 23, 24, 25, 26, 28, 29, 30, 31, 34, 37], "illeg": [0, 21], "somehow": [0, 21, 29], "crop": [0, 21, 30], "x1": [0, 4, 21, 30], "y1": [0, 4, 21, 30], "x2": [0, 4, 21, 30], "y2": [0, 4, 21, 30], "given": [0, 4, 16, 19, 21, 26, 28, 30, 31, 34], "minimum": [0, 21, 23, 30], "maximum": [0, 21, 30], "x": [0, 19, 21, 30], "y": [0, 19, 21, 30, 34], "axi": [0, 19, 21, 30, 34], "divid": [0, 3, 21, 30, 35], "divis": [0, 21, 30], "info": [0, 20, 21, 23, 30, 34, 35, 36], "print": [0, 19, 20, 21, 23, 30, 31, 34, 37], "about": [0, 21, 23, 29, 30, 31, 33, 34, 35, 37], "instrument": [0, 15, 16, 19, 20, 21, 23, 25, 26, 28, 29, 31, 33, 34, 37], "make": [0, 21, 23, 24, 28, 29, 30, 35, 37], "observ": [0, 19, 21, 24, 25, 26, 29, 31, 33, 37], "is_sett": [0, 21], "attr": [0, 21], "meant": [0, 3, 21, 23, 30, 33, 37], "classmethod": [0, 21, 23], "load": [0, 3, 20, 21, 23, 24, 28, 31, 34, 35, 37], "extname_pars": [0, 21, 23], "read": [0, 3, 5, 19, 21, 23, 25, 26, 30, 33, 35, 37], "hdulist": [0, 21, 23, 28], "etc": [0, 21, 23, 31, 35], "matches_data": [0, 21], "bool": [0, 3, 19, 21], "handl": [0, 4, 21, 23, 24, 33, 34, 37], "static": [0, 4, 21, 23], "_matches_data": [0, 21, 23, 28], "signatur": [0, 21, 29], "subclass": [0, 14, 21, 24, 26, 28, 30, 34, 35], "should": [0, 5, 12, 16, 20, 21, 23, 25, 28, 30, 31, 33, 34, 35, 36, 37], "overrid": [0, 21, 23], "which": [0, 3, 5, 14, 21, 23, 24, 25, 26, 28, 29, 30, 31, 33, 34, 35, 36, 37], "take": [0, 21, 23, 30, 36], "argument": [0, 5, 21, 29, 31, 34], "boolean": [0, 21, 23, 35], "overridden": [0, 21, 23, 28, 31], "call": [0, 21, 23, 26, 30, 31, 34, 37], "see": [0, 2, 3, 4, 9, 10, 14, 17, 19, 20, 21, 22, 23, 24, 26, 28, 30, 31, 33, 34, 35, 36, 37], "multipli": [0, 3, 20, 21, 30, 31], "multipl": [0, 19, 20, 21, 23, 30, 36], "being": [0, 2, 5, 19, 21, 24, 26, 28, 29, 30, 34, 37], "arg": [0, 4, 9, 10, 11, 21], "kwarg": [0, 4, 9, 10, 11, 21], "appli": [0, 5, 7, 16, 19, 21, 29, 30, 34, 35, 36], "function": [0, 8, 12, 15, 16, 19, 21, 23, 24, 25, 28, 30, 31, 35, 37], "main": [0, 21, 23, 24, 30, 34], "replac": [0, 3, 21, 23, 31, 34, 35, 36], "result": [0, 2, 21, 23, 29, 30], "pass": [0, 19, 21, 23, 28, 29, 30], "too": [0, 21, 23, 26, 30, 31, 34, 36], "thei": [0, 3, 8, 15, 16, 21, 23, 25, 26, 29, 30, 31, 34, 35, 37], "exist": [0, 21, 23, 25, 29, 30, 32, 37], "conveni": [0, 2, 3, 21, 23, 35, 37], "equival": [0, 21, 23, 30, 35, 37], "advantag": [0, 21], "work": [0, 3, 17, 21, 23, 25, 29, 30, 33, 34, 36, 37], "callabl": [0, 21, 24], "mayb": [0, 21, 34], "import": [0, 2, 3, 19, 21, 23, 24, 28, 30, 31, 34, 36, 37], "np": [0, 3, 20, 21, 30, 34, 36], "squeez": [0, 21], "reset": [0, 3, 21, 30], "check": [0, 21, 23, 24, 28, 29, 30, 31, 34], "whether": [0, 4, 19, 21, 29], "assign": [0, 19, 20, 21, 23, 30, 34, 36, 37], "sci": [0, 20, 21, 23, 31, 34], "dq": [0, 20, 21, 23, 34], "var": [0, 20, 21, 23, 30, 34], "attempt": [0, 3, 21, 24, 37], "made": [0, 21], "someth": [0, 20, 21, 29, 30, 34, 35], "than": [0, 21, 23, 30, 31, 35, 36], "don": [0, 21, 23, 31, 34], "t": [0, 3, 5, 7, 20, 21, 23, 31, 34, 36], "OR": [0, 21, 30], "isn": [0, 21], "subtract": [0, 3, 21, 26, 29, 37], "dictionari": [0, 3, 21, 23, 35, 36], "note": [0, 3, 19, 21, 23, 29, 30, 31, 35, 36], "_copy_": [0, 21], "so": [0, 4, 14, 20, 21, 23, 24, 29, 30, 31, 34, 35, 37], "them": [0, 2, 3, 5, 21, 23, 26, 29, 30, 31, 35, 36], "affect": [0, 21, 24, 29], "ones": [0, 3, 21, 23, 29, 30, 34], "telescop": [0, 19, 21, 30, 31], "update_filenam": [0, 21], "prefix": [0, 21, 23, 37], "suffix": [0, 21, 28], "strip": [0, 21], "updat": [0, 21, 23, 30, 32, 35], "specifi": [0, 3, 4, 21, 23, 29, 34], "simpli": [0, 21, 25, 30, 31, 34, 35, 36], "prepend": [0, 21, 34], "current": [0, 21, 26, 29, 33, 34], "broken": [0, 21, 24], "down": [0, 21, 26, 34], "root": [0, 21], "orignam": [0, 21], "keyword": [0, 19, 21, 23, 25, 28, 31, 35, 36, 37], "contain": [0, 3, 4, 5, 14, 16, 20, 21, 23, 25, 26, 27, 29, 30, 34, 35, 37], "within": [0, 4, 19, 21, 34], "otherwis": [0, 3, 4, 21, 23, 25, 28, 36, 37], "split": [0, 21, 23, 28], "last": [0, 7, 20, 21, 34], "underscor": [0, 21, 23], "after": [0, 3, 20, 21, 26, 29, 30, 31, 34], "No": [0, 21, 30, 34], "leav": [0, 21, 24, 30, 34], "alon": [0, 21, 33], "determin": [0, 4, 21, 23, 24, 26, 28, 31, 33, 35, 37], "write": [0, 3, 21, 24, 27, 30, 32, 36], "overwrit": [0, 20, 21, 28, 34], "disk": [0, 21, 30, 33, 35, 36], "astrodata": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 19, 21, 24, 25, 26, 27, 28, 29, 32], "except": [1, 21, 23, 25, 29], "problem": [1, 21, 34], "mixin": [2, 3, 21, 24], "like": [2, 3, 5, 14, 20, 21, 23, 25, 26, 29, 30, 31, 33, 34, 35, 36], "spectrum1d": [2, 21], "enabl": [2, 21], "behav": [2, 21], "similarli": [2, 21, 30], "These": [2, 15, 21, 23, 24, 25, 26, 29, 30, 31, 33, 34, 35, 37], "behavior": [2, 21, 23, 24, 25, 30, 37], "bitwis": [2, 21, 30], "logic": [2, 12, 21, 23, 25, 29], "sinc": [2, 19, 21, 24, 26, 28, 30, 31, 34, 35, 36, 37], "individu": [2, 21, 23, 24, 25, 29, 35], "bit": [2, 21, 24, 30, 31, 35, 36], "must": [2, 7, 14, 19, 21, 23, 30, 31, 37], "gwc": [2, 3, 21, 24, 28, 30, 31], "model": [2, 19, 21], "There": [2, 3, 21, 23, 30, 33, 34], "settabl": [2, 21], "meta": [2, 3, 20, 21, 24, 26, 30, 34, 35, 36], "size": [2, 4, 21, 30, 34, 36], "properti": [2, 3, 21, 24, 25, 26, 27, 28, 30, 31], "inhertit": [2, 21], "detail": [2, 9, 10, 21, 23, 26, 28, 33, 34], "unit": [3, 21, 23, 24, 30, 31, 34, 35], "copi": [3, 20, 21, 25, 30], "ndarithmeticmixin": [3, 21, 24], "ndslicingmixin": [3, 21, 24], "plu": [3, 21, 24, 26, 34], "some": [3, 20, 21, 23, 25, 26, 29, 30, 31, 34, 35, 36, 37], "registri": [3, 21, 23], "also": [3, 5, 19, 20, 21, 23, 24, 26, 28, 29, 30, 34, 35, 36, 37], "simpl": [3, 21, 23, 25, 28, 29, 31, 34], "arithmet": [3, 21, 23, 24, 31, 32], "veri": [3, 21, 23, 30, 35, 36, 37], "differ": [3, 21, 22, 23, 24, 25, 26, 29, 30, 31, 33, 35], "between": [3, 21, 23, 25, 26, 30, 31, 35], "former": [3, 21, 25], "lazili": [3, 21], "let": [3, 20, 21, 23, 25, 29, 30, 34, 36, 37], "initi": [3, 5, 21, 23, 24], "our": [3, 17, 20, 21, 23, 24, 28, 29, 30, 33, 35, 36, 37], "allow": [3, 5, 21, 23, 24, 25, 28, 29, 30, 31, 34], "possibl": [3, 21, 30, 31, 33, 34], "nddatabas": [3, 21], "stddevuncertainti": [3, 21], "3": [3, 20, 21, 23, 26, 28, 30, 31, 34], "dtype": [3, 21, 30], "float": [3, 19, 21, 23], "ndd1": [3, 21], "ndd2": [3, 21], "ndd3": [3, 21], "2": [3, 20, 21, 23, 26, 28, 30, 31, 34, 35], "41421356": [3, 21], "complet": [3, 17, 21, 26, 28, 29, 30, 31], "But": [3, 21, 23, 29, 30], "ndd4": [3, 21], "descript": [3, 14, 21, 23, 25, 26, 31], "how": [3, 15, 16, 20, 21, 22, 23, 25, 26, 29, 30, 31, 33, 34, 35, 36, 37], "memmap": [3, 21], "hold": [3, 21], "referenc": [3, 21], "default": [3, 20, 21, 24, 28, 29, 31, 35, 36], "both": [3, 4, 21, 23, 24, 30], "mutual": [3, 21], "exclus": [3, 21, 34], "transpos": [3, 21], "raw": [3, 20, 21, 23, 25, 30, 34, 37], "store": [3, 14, 19, 20, 21, 23, 24, 30, 31, 33, 34, 35, 36], "setter": [3, 21], "get": [3, 19, 20, 21, 23, 28, 29, 30, 31, 36, 37], "squar": [3, 21], "deviat": [3, 21, 30, 35], "window": [3, 21, 24], "lazi": [3, 21], "whenev": [3, 21, 24, 31], "__getitem__": [3, 21, 23], "bracket": [3, 21], "ultim": [3, 21], "100": [3, 20, 21, 24, 30, 34], "200": [3, 21, 24], "set_sect": [3, 21], "input_data": [3, 21], "prevent": [3, 5, 21, 23, 29], "fragment": [3, 21], "python": [3, 19, 21, 23, 24, 26, 28, 29, 30, 31, 33, 34, 35, 36, 37], "heap": [3, 21], "reus": [3, 21, 25], "structur": [3, 5, 21, 23, 24, 30, 34, 35], "area": [3, 20, 21, 30], "need": [3, 15, 20, 21, 22, 23, 24, 25, 28, 29, 30, 31, 33, 35, 36, 37], "least": [3, 21, 37], "Their": [3, 21, 23], "entir": [3, 4, 21, 25, 29, 30, 31], "def": [3, 21, 23, 28, 29, 37], "setup": [3, 21], "sec": [3, 21], "zero": [3, 20, 21, 30, 36], "n": [4, 20, 21, 23, 30, 34, 35], "dimension": [4, 21, 30], "axis_dict": [4, 21], "ndim": [4, 21], "dimens": [4, 20, 21, 24, 30, 34, 35], "asirafsect": [4, 21], "as_iraf_sect": [4, 21], "produc": [4, 7, 8, 21, 23, 26, 30, 31, 37], "style": [4, 21], "end": [4, 21, 23, 29, 34], "inclus": [4, 21], "asslic": [4, 21], "add_dim": [4, 21], "higher": [4, 21, 23, 30], "achiev": [4, 16, 21], "suppli": [4, 21], "from_shap": [4, 21], "from_str": [4, 21], "invers": [4, 21], "__str__": [4, 21], "is_same_s": [4, 21], "overlap": [4, 21, 30, 35], "two": [4, 21, 23, 29, 30, 31, 34, 35, 36], "common": [4, 12, 17, 18, 19, 21, 22, 23, 24, 25, 26, 28, 29, 30, 34, 36], "shift": [4, 21], "direct": [4, 21, 24, 31, 33, 34], "amount": [4, 21, 26, 29, 35], "remov": [5, 21, 23, 29, 30], "blocked_bi": [5, 21, 29], "block": [5, 21, 23, 29, 34, 35], "if_pres": [5, 21, 29], "action": [5, 21], "ani": [5, 7, 8, 16, 19, 21, 23, 24, 25, 26, 28, 29, 30, 31, 34, 35, 37], "complex": [5, 21, 25, 29, 31], "gener": [5, 17, 19, 21, 23, 25, 27, 28, 29, 30, 31, 34, 36], "algorithm": [5, 21, 23, 29], "want": [5, 21, 23, 29, 30, 33, 34], "better": [5, 21, 23, 29, 30], "understand": [5, 17, 21, 23, 26, 29, 37], "interact": [5, 21, 30, 31], "simplest": [5, 21, 30], "though": [5, 21, 23, 30, 34], "tend": [5, 21], "just": [5, 20, 21, 23, 29, 30, 34, 35, 37], "posit": [5, 19, 21, 29, 30], "order": [5, 21, 29, 30, 35], "below": [5, 19, 20, 21, 23, 28, 30, 31, 34, 35, 37], "won": [5, 21, 23], "present": [5, 20, 21, 28, 29, 30, 34, 35, 36], "bia": [5, 21, 29], "cal": [5, 21, 29, 37], "fn": [7, 8, 12, 21], "mark": [7, 8, 21, 23, 25, 30], "ie": [7, 21, 31], "doesn": [7, 21], "wai": [7, 20, 21, 23, 24, 25, 26, 29, 30, 31, 34, 35, 36, 37], "wrapper": [7, 8, 21], "wrap": [8, 21, 24, 33, 37], "around": [8, 21, 24, 29, 30], "ensur": [8, 12, 21, 23, 25, 29, 35, 36], "turn": [8, 21, 23, 25, 30, 35], "empti": [8, 21, 23, 29], "create_from_scratch": [9, 21, 23, 37], "get_astro_data": [10, 21], "per": [12, 19, 21, 25, 31, 36], "vice": [12, 21], "versa": [12, 21], "case": [12, 17, 20, 21, 23, 28, 29, 30, 34, 35, 36], "simplifi": [12, 21, 25], "your": [12, 21, 30, 31, 34, 35, 36, 37], "regardless": [14, 26, 28, 31], "would": [14, 25, 29, 30, 34, 35], "interest": [14, 19, 30, 31, 33, 35], "abstract": [14, 21, 25, 28], "layer": [14, 21], "pars": [14, 21, 33, 34], "To": [14, 15, 17, 19, 20, 21, 23, 29, 30, 31, 33, 34, 35, 36, 37], "do": [14, 20, 21, 23, 25, 30, 31, 34, 36, 37], "accordingli": [14, 21, 37], "receiv": [14, 21], "reduct": [15, 16, 26, 28, 31, 33, 37], "pipelin": [15, 16, 23, 25, 28], "extern": 15, "code": [15, 21, 22, 25, 26, 29, 30, 31, 34, 35, 37], "outsid": [15, 31], "capabl": 15, "extend": [15, 23, 24, 26, 33, 34, 35, 37], "ground": 15, "librari": [15, 30, 37], "pleas": [15, 17, 23, 33, 34, 36], "follow": [15, 23, 24, 25, 26, 28, 29, 30, 31, 35, 36, 37], "instruct": 15, "mind": [16, 31, 37], "intend": [16, 23, 28, 31, 34], "demonstr": 16, "basic": [16, 23, 31, 33, 37], "start": [16, 21, 23, 29, 30, 34, 36], "point": [16, 19, 23, 24, 29, 34, 35], "who": [16, 31], "mani": [16, 25, 26], "pattern": [16, 17], "broadli": 16, "anyth": [16, 30, 34, 37], "howev": [16, 23, 26, 30, 31, 34, 35, 37], "real": [16, 29, 35], "done": [16, 20, 23, 29, 30, 34, 35, 36, 37], "qualiti": [16, 19, 31, 32, 34, 35], "we": [17, 20, 21, 23, 24, 25, 26, 28, 29, 30, 31, 33, 34, 35, 36, 37], "aid": 17, "best": [17, 20, 30, 31, 36, 37], "practic": [17, 25, 31], "deeper": 17, "program": [17, 19, 21, 23, 33], "visit": 17, "api": [18, 22, 23, 25, 28], "modul": [18, 23, 29, 30], "search": 18, "page": [18, 30, 31], "run": [19, 20, 31, 34, 36, 37], "re": [19, 23, 29, 30, 31, 33], "primit": [19, 37], "input": [19, 23, 25, 32, 35], "serv": [19, 29], "archiv": 19, "goa": 19, "open": [19, 20, 21, 23, 24, 28, 29, 30, 31, 32, 33, 35, 36, 37], "playdata": [19, 20, 30, 31, 34, 36, 37], "n20170609s0154": [19, 20, 30, 31, 34, 37], "airmass": [19, 20, 25, 31], "amp_read_area": [19, 20], "ao_se": [19, 20], "well_depth_set": 19, "getattr": [19, 21], "shuffle_pixel": 19, "gmo": [19, 20, 23, 29, 30, 31, 34, 35, 37], "offer": [19, 20, 23, 30], "nod": 19, "shuffl": 19, "mode": [19, 23, 24, 31, 33], "short": [19, 31], "definit": [19, 30, 31, 37], "desc": 19, "amplifi": [19, 35], "rel": [19, 29], "detector": 19, "estim": 19, "natur": [19, 23, 24, 30, 34], "calcul": [19, 23, 30, 36], "adapt": [19, 23], "optic": 19, "array_nam": 19, "array_sect": [19, 34], "cover": [19, 20, 30, 34, 35, 36], "pixel": [19, 24, 31, 32, 35], "frame": [19, 26, 29, 30], "g": [19, 20, 21, 23, 25, 26, 28, 30, 31, 34, 37], "amp": 19, "ccd": [19, 26, 30, 31], "namedtupl": 19, "azimuth": 19, "degre": [19, 24, 30], "calibration_kei": 19, "kei": [19, 20, 21, 34, 35, 36], "databas": 19, "getprocess": 19, "previou": [19, 29, 30, 34], "calibr": [19, 31, 35, 37], "camera": [19, 26], "cass_rotator_pa": 19, "angl": [19, 30], "cassegrain": 19, "rotat": 19, "central_wavelength": 19, "central": [19, 23], "wavelength": [19, 23, 30, 31, 35], "meter": 19, "coadd": 19, "co": [19, 31], "data_label": 19, "label": 19, "data_sect": [19, 30], "sky": [19, 30], "fall": [19, 31], "dec": [19, 35], "declin": 19, "center": [19, 30], "field": [19, 21, 28, 30, 35], "decker": 19, "detector_nam": 19, "detector_roi_set": 19, "human": 19, "readabl": [19, 23, 30], "region": [19, 26], "roi": 19, "detector_rois_request": 19, "detector_sect": 19, "mosaic": 19, "detector_x_bin": 19, "bin": 19, "detector_x_offset": 19, "offset": 19, "along": [19, 30, 34, 36, 37], "detector_y_bin": 19, "detector_y_offset": 19, "dispers": 19, "dispersion_axi": 19, "effective_wavelength": 19, "bandpass": 19, "spectrum": [19, 30], "coverag": 19, "elev": 19, "exposure_tim": [19, 30, 31], "exposur": [19, 31], "time": [19, 30, 31, 37], "second": [19, 30, 34, 35], "filter_nam": [19, 20, 31, 34], "filter": [19, 23, 31], "focal_plane_mask": 19, "focal": 19, "plane": [19, 20, 23, 24, 32, 34, 35], "gain": [19, 20, 25, 30, 31, 35], "electron": 19, "adu": [19, 30], "gain_set": 19, "low": 19, "high": [19, 24, 26], "gcal_lamp": 19, "gcal": 19, "lamp": 19, "off": [19, 30], "group_id": 19, "group": [19, 23], "compat": [19, 23, 24, 35], "is_ao": 19, "is_coadds_sum": 19, "sum": [19, 21, 30], "averag": [19, 30], "local_tim": 19, "local": 19, "datetim": 19, "lyot_stop": 19, "lyot": 19, "stop": [19, 21, 29, 30], "mdf_row_id": 19, "row": [19, 20, 30, 31], "cut": [19, 30], "mo": 19, "xd": 19, "nod_count": 19, "b": 19, "nod_offset": 19, "arcsecond": 19, "nominal_atmospheric_extinct": 19, "nomim": 19, "atmospher": 19, "extinct": 19, "nominal_photometric_zeropoint": 19, "nomin": 19, "photometr": 19, "zeropoint": 19, "non_linear_level": 19, "lower": [19, 23, 30], "boundari": 19, "non": [19, 23, 29, 30], "linear": [19, 30], "regim": 19, "target": 19, "enter": 19, "observation_class": 19, "acq": 19, "daycal": 19, "observation_epoch": 19, "epoch": 19, "observation_id": 19, "observation_typ": 19, "flat": 19, "arc": [19, 37], "overscan_sect": [19, 30], "overscan": [19, 20, 26, 37], "pixel_scal": [19, 31], "scale": [19, 23, 30], "arcsec": 19, "program_id": 19, "pupil_mask": 19, "pupil": 19, "qa_stat": 19, "assess": [19, 33], "state": [19, 31, 37], "usabl": [19, 31], "fail": 19, "ra": [19, 35], "right": [19, 30, 34], "ascens": 19, "raw_bg": 19, "background": 19, "band": [19, 30], "raw_cc": 19, "cloud": 19, "raw_iq": 19, "imag": [19, 20, 21, 23, 24, 28, 29, 30, 31, 34, 35, 36, 37], "raw_wv": 19, "water": 19, "vapor": 19, "read_mod": 19, "read_nois": 19, "nois": [19, 30], "read_speed_set": 19, "slow": [19, 30], "fast": 19, "requested_bg": 19, "pi": 19, "request": [19, 24, 30, 36], "requested_cc": 19, "requested_iq": 19, "requested_wv": 19, "saturation_level": 19, "satur": [19, 30], "charg": 19, "slit": 19, "target_dec": 19, "target_ra": 19, "telescope_x_offset": 19, "telescope_y_offset": 19, "ut_dat": 19, "ut": 19, "date": [19, 33], "ut_datetim": 19, "ut_tim": 19, "wavefront_sensor": 19, "wavefront": 19, "sensor": 19, "wavelength_band": 19, "wcs_dec": 19, "In": [19, 20, 23, 24, 25, 26, 29, 30, 31, 34, 35, 36, 37], "wcs_ra": 19, "well": [19, 24, 26, 30, 31, 34, 35, 37], "depth": 19, "shallow": 19, "deep": 19, "pipe": [20, 22, 27, 32], "105_astrodatacheatsheet": 20, "download": [20, 36, 37], "wish": [20, 23, 35, 36, 37], "includ": [20, 21, 23, 26, 30, 33, 34, 37], "http": [20, 21, 30, 33, 36], "www": [20, 30], "edu": 20, "sciop": 20, "datapkg": 20, "ad_usermanual_datapkg": 20, "v1": 20, "tar": 20, "unpack": 20, "cd": [20, 33, 36, 37], "somewhere_conveni": 20, "xvf": 20, "bunzip2": 20, "ad_usermanu": [20, 36, 37], "bz2": 20, "Then": [20, 29, 30, 36, 37], "go": [20, 29, 30, 34, 36, 37], "playground": [20, 36, 37], "directori": [20, 23, 33, 34, 37], "new154": [20, 34, 37], "adnew": [20, 30, 34], "describ": [20, 23, 26, 29, 31, 37], "drawn": 20, "rule": [20, 25, 29], "becom": [20, 34], "element": [20, 21, 25, 29, 30], "extver": [20, 23, 31, 34], "error": [20, 21, 23, 25, 34, 35], "bad": [20, 30, 35], "refcat": [20, 34, 35, 36], "catalogu": 20, "detect": [20, 29], "refer": [20, 22, 23, 24, 25, 30, 31, 33, 34, 36, 37], "catalog": [20, 35], "2d": [20, 30, 37], "here": [20, 23, 25, 28, 29, 30, 31, 33, 34, 35, 36, 37], "match": [20, 23, 34, 35, 36], "n20170609s0154_varad": [20, 34, 35, 36, 37], "acquisit": [20, 30, 34, 35, 37], "north": [20, 30, 34, 35, 37], "overscan_subtract": [20, 34, 35, 37], "overscan_trim": [20, 34, 35, 37], "prepar": [20, 29, 30, 34, 35, 37], "sider": [20, 30, 34, 35, 37], "2112": [20, 30, 34, 35], "256": [20, 30, 34, 35], "float32": [20, 34, 35], "int16": [20, 34], "6": [20, 23, 31, 34, 35, 36], "43": [20, 34, 35], "uint8": [20, 34, 35], "8": [20, 30, 34, 36], "7": [20, 23, 34, 36], "5": [20, 23, 30, 31, 34, 36], "245": [20, 34, 35], "16": [20, 30, 31, 34, 35], "plai": [20, 29], "encourag": [20, 23], "modif": [20, 31, 34], "step": [20, 23, 29, 31, 36, 37], "thing": [20, 23, 30, 31], "chang": [20, 21, 23, 24, 28, 29, 30, 34], "deepcopi": [20, 21, 30, 34], "adcopi": [20, 30, 34], "advar": [20, 34], "delet": [20, 21, 32, 34], "del": [20, 31, 34], "4": [20, 21, 23, 28, 30, 31, 34, 36], "smaug": [20, 34], "drogon": [20, 34], "n20170521s0925_forstack": [20, 30, 37], "issubset": [20, 37], "favor": [20, 23, 30], "over": [20, 23, 25, 26, 29, 30, 34], "retriev": [20, 23, 30, 31, 35, 36], "alreadi": [20, 24, 30, 34], "agnost": 20, "routin": 20, "open1": [20, 31, 34], "g_g0301": [20, 31, 34], "pretti": [20, 31, 34], "look": [20, 23, 29, 30, 31, 33, 35, 37], "up": [20, 23, 29], "correct": [20, 21, 23, 25, 28, 30, 31], "03": [20, 31], "97": [20, 31], "96": [20, 31], "01": [20, 31], "wrong": [20, 30], "full": [20, 22, 23, 24, 26, 30, 34, 36, 37], "exptim": [20, 33], "boguskei": 20, "without": [20, 24, 25, 26, 30, 31, 34], "comment": [20, 31], "newkei": [20, 31], "50": [20, 30, 31], "anoth": [20, 23, 30, 31, 34, 35, 37], "30": [20, 30, 31], "469": 20, "7444308769482": 20, "656175780001": 20, "464": 20, "9815279808291": 20, "467": 20, "5701178951787": 20, "propag": [20, 21], "ad_hcont": 20, "ad_halpha": 20, "n20170521s0926_forstack": [20, 37], "adsub": 20, "mean": [20, 21, 23, 24, 29, 30, 34, 36, 37], "646": 20, "11896": 20, "581": 20, "81342": 20, "64": [20, 30, 36], "305862": 20, "669": 20, "80664": 20, "598": 20, "46667": 20, "1268": 20, "274": 20, "place": [20, 21, 23, 29, 30, 35], "ad_mult": 20, "ad_gain": 20, "zip": [20, 23, 30, 31], "366": 20, "39545": 20, "180": 20, "4904": 20, "300": 20, "350": 20, "657": 20, "1994": 20, "most": [20, 23, 28, 29, 30, 31, 34, 36], "itself": [20, 23, 26, 29, 30, 33, 35, 36], "few": [20, 23, 30, 31, 34], "column": [20, 30], "colnam": [20, 36], "zmag": [20, 36], "zmag_err": [20, 36], "5th": 20, "6th": 20, "new_column": [20, 36], "len": [20, 21, 23, 36], "new_row": [20, 36], "cat_id": [20, 36], "add_row": [20, 36], "select": [20, 29, 31, 33, 37], "criterion": 20, "1237662500002005475": [20, 36], "18": [20, 36], "reject": [20, 30], "nan": [20, 36], "save": [20, 28, 30, 31, 36], "isnan": [20, 36], "99": [20, 30], "20": [20, 30, 36], "377306": [20, 36], "reason": [20, 23, 30, 36], "ttype3": [20, 36], "raj2000": [20, 36], "y_imag": [20, 34, 36], "keynam": [20, 36], "startswith": [20, 36], "ttype": [20, 36], "primaryhdu": [20, 28, 34, 36, 37], "pixel_data": [20, 34], "hdu": [20, 23, 25, 28, 34], "my_astropy_t": [20, 34, 36], "random": [20, 29, 30], "rand": 20, "col1": 20, "col2": 20, "my_fits_t": [20, 34], "warn": [20, 30, 34], "line": [20, 23, 30, 31], "nonetheless": 20, "had": [20, 35], "auto": 21, "docstr": 21, "tutori": 21, "tip": 21, "__add__": [21, 23], "__annotations__": 21, "__contains__": 21, "abil": [21, 24, 30], "__deepcopy__": [21, 23], "memo": 21, "explan": 21, "__delattr__": 21, "__delitem__": [21, 23], "idx": 21, "syntax": [21, 24, 26, 30], "neg": [21, 24], "indexerror": 21, "rang": [21, 23, 26, 30], "__getattr__": 21, "lookup": [21, 23, 25, 30], "ha": [21, 23, 24, 26, 28, 30, 31, 33, 34, 35, 36, 37], "found": [21, 23, 26, 28, 30, 31, 33, 35, 36], "usual": [21, 25], "tree": [21, 23], "attributeerror": [21, 34], "could": [21, 23, 25, 28, 30, 34], "comput": [21, 34], "view": [21, 29, 30, 35], "integ": [21, 23, 30], "try": [21, 31, 36, 37], "sens": [21, 30], "doe": [21, 23, 25, 29, 30, 31, 34, 36, 37], "belong": [21, 23, 34], "recogn": [21, 34, 35], "__iadd__": 21, "__imul__": 21, "__init__": [21, 23, 24], "__isub__": 21, "__iter__": [21, 23], "__itruediv__": 21, "__len__": 21, "independ": [21, 34], "__module__": 21, "core": [21, 23, 24, 25, 28, 34, 36], "__mul__": [21, 23], "__radd__": 21, "__rmul__": 21, "__rsub__": 21, "__rtruediv__": 21, "__setattr__": 21, "normal": [21, 24, 30, 31, 35, 36], "mechan": 21, "__sub__": [21, 23], "__truediv__": [21, 23], "__cause__": 21, "caus": [21, 34], "__context__": 21, "context": [21, 23], "delattr": 21, "__getattribute__": 21, "adfactori": 21, "__new__": 21, "__reduce__": 21, "helper": 21, "pickl": 21, "__repr__": 21, "repr": 21, "setattr": 21, "__setstate__": 21, "__suppress_context__": 21, "__traceback__": 21, "add_not": 21, "with_traceback": 21, "tb": 21, "__abstractmethods__": 21, "frozenset": 21, "item": [21, 34], "operand2": 21, "rather": [21, 23, 30, 34, 35, 36, 37], "propagate_uncertainti": 21, "correctli": [21, 30], "assum": [21, 30, 34], "valid": [21, 23], "handle_mask": 21, "first_found": 21, "logical_or": [21, 24], "handle_meta": 21, "compare_wc": 21, "comparison": 21, "compar": [21, 30], "success": 21, "uncertainty_correl": 21, "correl": [21, 30], "en": [21, 36], "wikipedia": 21, "org": [21, 30, 36], "wiki": 21, "propagation_of_uncertainti": 21, "example_formula": 21, "wcs_": 21, "meta_": 21, "startstr": 21, "abbrevi": 21, "ff": 21, "max": [21, 30], "min": [21, 23, 30], "psf": 21, "represent": [21, 31, 33, 34], "uncertainty_typ": 21, "what": [21, 23, 29, 30, 31, 32, 35], "kind": [21, 23], "std": [21, 30], "metaclass": 21, "mandatori": 21, "__class_getitem__": 21, "pep": 21, "585": 21, "__eq__": 21, "__ge__": 21, "__getnewargs__": 21, "__gt__": 21, "__hash__": 21, "hash": 21, "iter": [21, 23, 25, 26, 29, 30, 31], "__le__": 21, "__lt__": 21, "util": [21, 34], "__ne__": 21, "cl": [21, 23], "count": 21, "occurr": 21, "9223372036854775807": 21, "plain": 21, "__match_args__": 21, "nice": [21, 30], "__slots__": 21, "alia": 21, "add_header_to_t": [21, 34], "returns_list": [21, 25], "120_astrodatamastermanu": 22, "cheat": [22, 33], "sheet": [22, 33], "refresh": 22, "relat": [22, 23, 33], "appendix": 22, "product": [23, 37], "asid": 23, "lack": 23, "knowledg": [23, 26], "importantli": 23, "sever": [23, 26, 28, 30, 33], "much": [23, 24], "semant": 23, "compon": [23, 30, 31], "mostli": [23, 24, 31], "sequenti": [23, 29, 36], "third": [23, 30], "fourth": [23, 34], "__setitem__": 23, "seri": 23, "sweep": 23, "move": [23, 29], "discuss": [23, 26, 30], "addition": 23, "crucial": 23, "resolv": [23, 25, 26, 28, 37], "textual": 23, "quickli": 23, "arbitrari": [23, 25, 31], "categori": [23, 35], "_process_tag": 23, "behind": [23, 29, 34], "recommend": [23, 30, 36, 37], "unless": [23, 30, 31, 34], "good": [23, 28, 30, 35], "seet": 23, "process": [23, 25, 26, 29, 30, 33, 37], "inherit": [23, 29, 31], "ascii": 23, "nm": 23, "flux": 23, "erg": 23, "cm2": 23, "astrodatamyfil": 23, "_wavelength": 23, "_flux": 23, "_header": 23, "super": 23, "staticmethod": [23, 28], "endswith": 23, "txt": [23, 33], "batch": 23, "l": [23, 37], "ndx": 23, "yield": 23, "text": [23, 31], "join": [23, 29], "f": [23, 28, 30, 34], "w": 23, "10": [23, 30, 36], "r": 23, "readlin": 23, "keep": [23, 30, 31], "col": 23, "now": [23, 24, 28, 29, 30, 31, 33, 34, 36, 37], "suppos": [23, 30], "noth": 23, "els": [23, 30, 31, 34, 37], "isinst": [23, 28], "extra": [23, 24, 35], "_": 23, "conserv": 23, "space": 23, "snippet": 23, "rest": 23, "At": [23, 35], "execut": [23, 28], "final": [23, 24, 28, 29, 30, 34], "feel": [23, 30], "free": 23, "templat": 23, "were": [23, 34, 35], "intrument": 23, "often": [23, 26, 31], "scope": [23, 31, 34], "purpos": 23, "ll": [23, 29], "repositori": [23, 33], "py": 23, "test": [23, 24, 28, 31], "adclass": 23, "astrodatagmo": [23, 31], "__all__": 23, "factori": [23, 28], "addinstrumentfilterwavelength": 23, "filter_wavelength": 23, "addclass": 23, "explicitli": [23, 24, 31, 34], "astrodatagemini": 23, "motiv": 23, "separ": [23, 24, 34], "reflect": [23, 26], "left": [23, 30], "highlight": 23, "__keyword_dict": 23, "monik": 23, "idea": 23, "hard": 23, "while": [23, 25, 26, 29, 30, 31, 34, 37], "quit": 23, "stabl": [23, 29, 36], "prone": 23, "safe": 23, "sorri": 23, "come": [23, 25, 26, 29, 30], "dure": [23, 24, 30], "instabl": 23, "_keyword_for": 23, "member": [23, 29], "easier": [23, 29], "find": [23, 30], "cue": 23, "know": [23, 25, 30, 31, 34, 36, 37], "claim": 23, "becaus": [23, 30, 34, 35], "compli": 23, "facil": 23, "handler": [23, 30], "candid": 23, "automat": [23, 26, 33, 34, 37], "discoveri": 23, "discard": [23, 24, 29], "basi": 23, "appear": 23, "drop": [23, 29], "prefer": [23, 34], "occur": 23, "choos": [23, 30], "declar": [23, 29], "convent": [23, 31], "dai": 23, "_tag_": 23, "_status_": 23, "hint": 23, "team": [23, 31], "establish": 23, "earmark": 23, "discov": 23, "later": [23, 30], "appropri": [23, 25, 28, 30, 33, 34, 37], "either": [23, 30, 31, 34], "construct": [23, 34, 35, 37], "figur": [23, 29, 30], "theori": 23, "big": [23, 30], "feasibl": 23, "decid": [23, 29, 31], "moment": 23, "compet": 23, "altern": 23, "condit": [23, 29, 31], "preclud": 23, "branch": 23, "complic": 23, "dozen": 23, "heavi": 23, "easi": [23, 36], "exploit": 23, "redefin": 23, "gnir": 23, "astrodatagnir": 23, "featur": [23, 24, 26, 30], "read_fit": 23, "particular": [23, 24, 30, 35], "extnam": [23, 34], "iraf": [23, 30, 36], "conform": [23, 31], "further": [23, 26], "As": [23, 26, 30, 31, 34, 35], "soar": 23, "sami": 23, "mef": [23, 25, 26, 32, 33], "im1": 23, "im2": 23, "astrodatasami": 23, "sami_pars": 23, "m": [23, 24], "im": 23, "d": [23, 30], "bulk": 23, "again": [23, 29, 31, 34], "unlik": [23, 34, 36], "still": [23, 26, 30, 35], "alter": [23, 25], "output": [23, 25, 30, 32], "alwai": [23, 30, 31, 34], "probabl": [23, 30, 31], "against": 23, "show": [23, 30, 36], "via": [23, 25, 31], "abov": [23, 25, 28, 30, 34, 35], "astrodatamyinstru": 23, "add_class": [23, 28], "effect": [23, 31], "registr": 23, "enough": 23, "thu": [23, 29], "script": 23, "some_fil": [23, 30], "technic": [23, 35], "anywai": [23, 29], "namespac": 23, "explicit": [23, 28], "perfect": [23, 30], "multi": [23, 28, 33, 34], "burden": 23, "memori": [23, 24, 30, 35], "everyon": 23, "approach": 23, "scheme": 23, "suggest": 23, "cascad": 23, "piec": [23, 34], "share": [23, 26], "across": [23, 28, 30], "translat": [23, 25, 30], "sai": [23, 30, 31, 35], "u": [23, 30, 34, 36, 37], "feii": [23, 36], "35\u00b5m": 23, "644\u00b5m": 23, "daughter": 23, "control": [23, 26, 30, 31, 36], "maintain": 23, "creator": 23, "driven": 23, "themselv": [23, 30, 35], "oppos": 23, "activ": [23, 29], "decoupl": 23, "footnot": [23, 34], "privat": [23, 35], "due": 23, "doubl": 23, "own": [23, 28, 30, 31, 34, 37], "awar": [23, 26], "chain": [23, 31], "ok": 23, "rememb": [23, 30, 34], "everi": [23, 31, 34], "implicitli": 23, "lowest": 23, "abl": [23, 30], "built": 24, "concept": [24, 26, 30, 31, 34, 37], "ndastrodatamixin": 24, "fundament": 24, "With": [24, 30, 34], "eas": 24, "effici": [24, 30], "propog": [24, 31], "upon": [24, 31], "until": 24, "longer": [24, 30, 33], "particularli": [24, 30], "larg": [24, 30, 34, 35], "astronomi": [24, 28, 30], "act": [24, 29], "mimic": 24, "design": [24, 27, 33, 37], "One": [24, 26, 31, 34], "advarianceuncertainti": 24, "varianceuncertainti": 24, "bitwise_or": 24, "shine": 24, "light": [24, 30], "ve": [24, 29, 30], "encount": 24, "intact": 24, "regularli": 24, "expect": [24, 28, 29, 30, 35], "futur": [24, 26, 31], "releas": 24, "plan": [24, 26, 30, 36], "increas": 24, "anticip": 24, "possibli": 24, "backward": 24, "regular": 25, "card": [25, 31], "potenti": 25, "organ": 25, "arbitrarili": 25, "train": 25, "learn": [25, 30, 31], "facilit": 25, "limit": [25, 28, 30], "length": [25, 34, 36], "explor": [25, 30], "build": [25, 34, 36], "oversimplifi": 25, "elimin": 25, "calu": 25, "latter": [25, 35], "might": [25, 30, 36], "even": [25, 30, 35, 37], "written": [25, 30, 34, 35, 36], "avoid": [25, 30, 31, 34], "unexpect": [25, 30], "confus": [25, 31, 34], "undecor": 25, "niri": 25, "astrodataniri": 25, "singli": 25, "varieti": 26, "form": [26, 30], "uniqu": 26, "readili": [26, 31, 33], "transfer": 26, "similar": [26, 31, 36], "glanc": 26, "seem": [26, 34], "counterintuit": 26, "spectrograph": 26, "break": [26, 34], "aspect": [26, 31], "onc": [26, 31, 33, 34], "proven": 26, "significantli": 26, "link": [26, 30, 35], "uniform": [26, 33], "manner": [26, 28], "quantiti": [26, 37], "reuir": 26, "introspect": 26, "thee": 26, "three": [26, 29, 30], "togeth": [26, 30, 33, 34], "percular": 26, "critic": [26, 30, 31], "think": [26, 37], "manag": [26, 31, 33], "costli": 26, "primarili": [26, 37], "box": [26, 30, 33], "reader": [26, 30, 31, 36], "straightforward": [26, 30, 31, 34], "nativ": 26, "asdf": [26, 31], "relev": 26, "axiom": 26, "retain": 26, "104_astrodataprogmanu": 27, "introduct": [27, 32], "project": [28, 30], "wider": 28, "public": [28, 30], "suit": [28, 30, 33, 37], "commonli": [28, 30], "underli": 28, "regist": [28, 37], "throughout": [28, 30], "shown": [28, 30], "myastrodata": [28, 31, 37], "instrument_tag": 28, "instrum": [28, 33], "upper": [28, 30], "my_instru": 28, "falsei": 28, "my_tag": 28, "my_tag_nam": 28, "applic": 28, "Not": [28, 31, 36], "strictli": [28, 37], "my_descriptor": [28, 31], "my_descriptor_nam": 28, "my_desc": 28, "give": [28, 30, 34], "tempfil": 28, "phdu": 28, "example_tag": 28, "example_descriptor": 28, "dummi": 28, "namedtemporaryfil": 28, "writeto": 28, "assert": [28, 30], "incorrect": [28, 30], "sure": [28, 29, 30], "infer": 28, "thorough": 28, "strang": 28, "nullifi": 28, "Or": [28, 33, 34], "my_keyword": 28, "talk": 29, "custom": [29, 33], "Its": 29, "foo": 29, "bar": 29, "power": [29, 30], "put": [29, 30, 35], "altogeth": 29, "unprocess": 29, "side": 29, "coin": 29, "intent": 29, "sometim": [29, 30, 34], "bunch": 29, "furthermor": 29, "sort": [29, 36], "those": [29, 30, 31, 34, 35, 36], "earli": 29, "met": 29, "hurdl": 29, "indistinguish": 29, "guarante": 29, "stai": 29, "taken": 29, "constant": 29, "_tag_instru": 29, "classifi": 29, "condition": 29, "_tag_imag": 29, "grate": 29, "mirror": 29, "slightli": [29, 31], "fairli": [29, 34], "_type_gcal_lamp": 29, "gcallamp": 29, "irhigh": 29, "shut": 29, "gcalshut": 29, "gcal_ir_on": 29, "lampon": 29, "elif": 29, "close": [29, 31, 33], "gcal_ir_off": 29, "lampoff": 29, "spectroscopi": 29, "configur": [29, 31, 36, 37], "_tag_bia": 29, "obstyp": [29, 37], "spect": [29, 37], "four": [29, 30], "illustr": [29, 30], "pretend": 29, "proce": 29, "promot": 29, "exactli": [29, 30, 36], "kept": [29, 35], "stabil": 29, "neither": 29, "nor": [29, 31], "task": 30, "familiar": [30, 35], "some_fits_file_with_extens": 30, "the_data": 30, "loop": 30, "4194304": 30, "forward": 30, "doctestastrodata": 30, "151": 30, "adout": 30, "adin": 30, "variabl": 30, "reassign": 30, "back": [30, 32, 36, 37], "onto": 30, "enumer": 30, "unchang": 30, "consum": 30, "lead": 30, "bewar": 30, "involv": [30, 36], "safer": 30, "littl": [30, 34], "ad_copi": 30, "NOT": [30, 34, 37], "60": 30, "40": 30, "34": 30, "untouch": 30, "matter": [30, 37], "poisson": 30, "therefor": [30, 31, 34, 35], "signal": 30, "var_output": 30, "471": 30, "25": 30, "7065": 30, "ingest": 30, "why": 30, "binari": 30, "0000000": 30, "0000001": 30, "0000010": 30, "0000100": 30, "cosmic": 30, "rai": 30, "0001000": 30, "0010000": 30, "32": [30, 36], "0100000": 30, "unillumin": 30, "1000000": 30, "locat": [30, 32, 36], "dq_definit": 30, "uint16": [30, 34, 35], "0001010": 30, "stack": 30, "fine": 30, "0000101": 30, "bitmask": 30, "usag": [30, 35], "some_fits_file_with_mask": 30, "doctest": [30, 34], "normalize_whitespac": 30, "_doctest_data": 30, "blanklin": 30, "2048": [30, 34], "float64": 30, "imexam": 30, "cursor": 30, "numdisplai": 30, "stsci": 30, "scientif": [30, 33], "rich": 30, "menageri": 30, "analysi": 30, "visual": 30, "highli": 30, "invent": 30, "wheel": 30, "uncommon": [30, 34], "naxis2": 30, "naxis1": 30, "coordin": [30, 32, 35], "49": 30, "world": [30, 32, 35], "fortran": 30, "fresh": 30, "yet": [30, 34], "unsign": 30, "65535": 30, "accommod": 30, "workaround": 30, "safeti": 30, "net": [30, 33], "forc": 30, "convers": 30, "65534": 30, "131070": 30, "lot": 30, "probe": 30, "coupl": 30, "some_fits_fil": 30, "random_numb": 30, "median": 30, "00117": 30, "00271": 30, "ufunc": 30, "ma": 30, "threshold": 30, "stddev": 30, "clipped_mean": 30, "masked_outsid": 30, "3e": 30, "sep": 30, "887e": 30, "00": 30, "001e": 30, "robust": 30, "help": [30, 31], "stat": 30, "sigma_clip": 30, "sigma": 30, "convolus": 30, "gaussian": 30, "ndimag": 30, "revisit": [30, 34], "convolved_data": 30, "reshap": 30, "convolut": 30, "gaussian_filt": 30, "convolv": 30, "connect": 30, "list_active_ds9": 30, "zscale": 30, "blink": 30, "convinc": 30, "kernel": 30, "unedit": 30, "clariti": 30, "extrem": 30, "solut": [30, 33], "optim": 30, "Being": 30, "cost": 30, "lesson": 30, "research": 30, "edg": 30, "illumin": 30, "physic": 30, "gap": 30, "doc": [30, 36], "nope": 30, "portion": 30, "45": 30, "corner": 30, "chip": 30, "certainli": 30, "white": 30, "sake": 30, "simplic": 30, "variou": [30, 31, 34, 36], "accomplish": 30, "breakpoint": 30, "flag": [30, 35, 37], "masked_arrai": 30, "no_data": 30, "masked_wher": 30, "long": [30, 31, 35, 37], "masked_": 30, "clean_data": 30, "far": [30, 31], "slix": 30, "seen": [30, 34], "experi": [30, 31], "especi": [30, 35, 37], "worth": 30, "explain": [30, 31], "highest": 30, "75": 30, "74": 30, "29": 30, "19": 30, "39": 30, "25x25": 30, "wide": 30, "xc": 30, "yc": 30, "buffer": 30, "xlow": 30, "xhigh": 30, "ylow": 30, "yhigh": 30, "62": 30, "87": 30, "37": 30, "stamp": 30, "2f": 30, "notic": [30, 34, 35], "chapter": [30, 35, 36], "unprepar": [30, 34, 37], "288": [30, 34], "readi": [30, 31, 34], "oversec": 30, "datasec": 30, "measur": 30, "mean_overscan": 30, "adjust": 30, "That": [30, 34, 35, 37], "smaller": [30, 34], "partly_overscan_correct": 30, "Of": 30, "cours": 30, "integr": 30, "ifu": [30, 37], "did": [30, 34], "1d": 30, "spectra": 30, "revers": [30, 31], "wlen": 30, "collaps": 30, "wavelenth": 30, "pyplot": 30, "plt": 30, "adcub": 30, "gmosifu_cub": [30, 37], "summed_imag": 30, "minmax": 30, "spatial": 30, "14": [30, 31], "24": 30, "13": 30, "matplotlibrc": 30, "nicer": 30, "angstrom": 30, "ax": [30, 31], "contrari": 30, "truli": 30, "pai": 30, "attent": 30, "length_wlen_axi": 30, "intens": 30, "clf": 30, "absolut": 30, "aim": 30, "web": 30, "github": [30, 33], "com": [30, 33], "spacetelescop": 30, "scratch": [30, 37], "surfac": 30, "ad_imag": 30, "ad_spectrum": 30, "estgss20080220s0078": [30, 37], "1044": 30, "line_index": 30, "1043": 30, "11": 30, "colum": 30, "327": 30, "col_index": 30, "326": 30, "width": 30, "thick_column": 30, "ylim": 30, "contour": 30, "1646": 30, "2355": 30, "15": 30, "xrang": 30, "yrang": 30, "blob": 30, "imshow": 30, "cmap": 30, "grai": 30, "spec_wc": 30, "pixcoord": 30, "wcs_pix2world": 30, "clarifi": 31, "histori": 31, "filetyp": 31, "centric": 31, "arriv": 31, "computation": 31, "expens": 31, "flexibl": 31, "happen": 31, "conflat": 31, "fact": [31, 34], "transpar": [31, 34], "42": 31, "understood": 31, "089": 31, "flow": 31, "240": 31, "respect": [31, 34], "fwhm_pixel": 31, "fwhm_arcsec": 31, "desir": [31, 37], "jwst": 31, "testastrodata": 31, "unknown_descriptor": 31, "am": [31, 37], "othertestastrodata": 31, "my": 31, "word": [31, 34], "discret": 31, "rewrit": 31, "accompani": 31, "minim": 31, "aofold": 31, "park": 31, "po": 31, "crpix1": 31, "511": 31, "862999160781": 31, "287": 31, "137000839218696": 31, "224": 31, "137000839219": 31, "screen": 31, "bitpix": 31, "naxi": 31, "xtension": 31, "ident": 31, "transform": 31, "ordin": 31, "ap": 31, "almost": 31, "mosaick": 31, "distort": 31, "approxim": [31, 34], "likewis": 31, "recreat": 31, "accur": [31, 34, 35], "improv": [31, 33], "polynomi": 31, "sip": 31, "sampl": 31, "greisen": 31, "et": 31, "al": 31, "2006": 31, "s6": 31, "report": [31, 33], "106_astrodatausermanu": 32, "quick": [32, 36, 37], "walkthrough": 32, "advanc": 32, "topic": 32, "displai": [32, 35], "tool": [32, 33, 37], "scipi": 32, "cube": 32, "plot": 32, "welcom": 33, "formerli": 33, "undergon": 33, "iteract": 33, "major": 33, "standalon": 33, "consolid": 33, "conjunct": 33, "brief": 33, "overview": 33, "head": 33, "properli": [33, 37], "insid": 33, "inspect": 33, "git": 33, "clone": 33, "teald": 33, "geminidrsoftwar": 33, "fake": [33, 37], "create_test_fil": 33, "include_header_kei": 33, "ob": 33, "test_valu": 33, "finish": 33, "staff": 33, "question": 33, "helpdesk": 33, "noirlab": 33, "atlassian": 33, "servicedesk": 33, "portal": 33, "12": 33, "unflag": 34, "popular": 34, "effort": 34, "512": 34, "4224": 34, "513": 34, "1024": 34, "1025": 34, "1536": 34, "1537": 34, "accessibli": 34, "notat": 34, "review": 34, "trivial": 34, "precis": [34, 37], "comprehensibli": 34, "x_imag": 34, "ref_mag_err": 34, "profile_fwhm": 34, "profile_ee50": 34, "int32": [34, 36], "283": 34, "461": 34, "55": 34, "4393": 34, "16895": 34, "999": [34, 36], "conflict": 34, "predefin": 34, "filter1": 34, "filter2": 34, "ccdsec": 34, "treat": 34, "everyth": 34, "next": [34, 35], "insert": 34, "command": 34, "destin": 34, "su": 34, "me": 34, "introduc": 34, "asterisk": 34, "Will": 34, "newcopi": 34, "astroddata": 34, "consid": 34, "eventu": [34, 36], "fortun": 34, "constructor": 34, "new_mef": 34, "labe": 34, "previous": 34, "extra_data": 34, "extradata": 34, "bintablehdu": 34, "dedic": 34, "user_structure_exampl": 35, "composit": 35, "vari": 35, "dramat": 35, "character": [35, 37], "incorpor": 35, "bottom": 35, "star": [35, 36], "gemini_exampl": 35, "_tabl": 35, "fulli": [35, 37], "evid": 35, "obvious": 35, "refin": 35, "never": [35, 37], "rephras": 35, "subsect": 35, "delai": 35, "someon": 35, "yourself": [36, 37], "html": 36, "dej2000": 36, "umag": 36, "umag_err": 36, "gmag": 36, "gmag_err": 36, "rmag": 36, "rmag_err": 36, "imag_err": 36, "filtermag": 36, "filtermag_err": 36, "feed": 36, "overwrite_col": 36, "nb": 36, "suffici": 36, "techniqu": 36, "pyraf": 36, "int64": 36, "astyp": 36, "my_column": 36, "bintabl": 36, "care": 36, "invok": 36, "myfile_with_modified_t": 36, "magnitud": 36, "brighter": 36, "snr_id": 36, "s001": 36, "s002": 36, "s003": 36, "780": 36, "78": 36, "179": 36, "pabeta": 36, "740": 36, "307": 36, "220": 36, "ratio": 36, "mytabl": 36, "new_tabl": 36, "idenf": 37, "rawastrodata": 37, "_tag_raw": 37, "proctyp": 37, "v": 37, "ad_tag": 37, "touch": 37, "trim": 37, "awai": 37, "focus": 37, "port": 37, "gempi": 37, "typewalk": 37, "shell": 37, "workspac": 37, "processed_sci": 37, "longslit": 37, "south": 37, "oneslit_r": 37, "dataspid": 37, "_tag_mytag": 37, "mytag": 37, "_tag": 37, "clear": 37, "known": 37, "ad_tag_example_us": 37, "difficult": 37, "automaticlali": 37}, "objects": {"": [[14, 0, 0, "-", "astrodata"]], "astrodata": [[0, 1, 1, "", "AstroData"], [1, 4, 1, "", "AstroDataError"], [2, 1, 1, "", "AstroDataMixin"], [3, 1, 1, "", "NDAstroData"], [4, 1, 1, "", "Section"], [5, 1, 1, "", "TagSet"], [6, 5, 1, "", "add_header_to_table"], [7, 5, 1, "", "astro_data_descriptor"], [8, 5, 1, "", "astro_data_tag"], [9, 5, 1, "", "create"], [10, 5, 1, "", "from_file"], [11, 5, 1, "", "open"], [12, 5, 1, "", "returns_list"], [13, 5, 1, "", "version"]], "astrodata.AstroData": [[0, 2, 1, "", "add"], [0, 2, 1, "", "append"], [0, 2, 1, "", "crop"], [0, 3, 1, "", "data"], [0, 3, 1, "", "descriptors"], [0, 2, 1, "", "divide"], [0, 3, 1, "", "exposed"], [0, 3, 1, "", "ext_tables"], [0, 3, 1, "", "filename"], [0, 3, 1, "", "hdr"], [0, 3, 1, "", "header"], [0, 3, 1, "", "id"], [0, 3, 1, "", "indices"], [0, 2, 1, "", "info"], [0, 2, 1, "", "instrument"], [0, 2, 1, "", "is_settable"], [0, 3, 1, "", "is_sliced"], [0, 2, 1, "", "load"], [0, 3, 1, "", "mask"], [0, 2, 1, "", "matches_data"], [0, 2, 1, "", "multiply"], [0, 3, 1, "", "nddata"], [0, 2, 1, "", "object"], [0, 2, 1, "", "operate"], [0, 3, 1, "", "orig_filename"], [0, 3, 1, "", "path"], [0, 3, 1, "", "phu"], [0, 2, 1, "", "read"], [0, 2, 1, "", "reset"], [0, 3, 1, "", "shape"], [0, 2, 1, "", "subtract"], [0, 2, 1, "", "table"], [0, 3, 1, "", "tables"], [0, 3, 1, "", "tags"], [0, 2, 1, "", "telescope"], [0, 3, 1, "", "uncertainty"], [0, 2, 1, "", "update_filename"], [0, 3, 1, "", "variance"], [0, 3, 1, "", "wcs"], [0, 2, 1, "", "write"]], "astrodata.AstroDataMixin": [[2, 3, 1, "", "shape"], [2, 3, 1, "", "size"], [2, 3, 1, "", "variance"], [2, 3, 1, "", "wcs"]], "astrodata.NDAstroData": [[3, 3, 1, "", "T"], [3, 3, 1, "", "data"], [3, 3, 1, "", "mask"], [3, 2, 1, "", "set_section"], [3, 2, 1, "", "transpose"], [3, 3, 1, "", "uncertainty"], [3, 3, 1, "", "variance"], [3, 3, 1, "", "window"]], "astrodata.Section": [[4, 2, 1, "", "asIRAFsection"], [4, 2, 1, "", "as_iraf_section"], [4, 2, 1, "", "asslice"], [4, 3, 1, "", "axis_dict"], [4, 2, 1, "", "contains"], [4, 2, 1, "", "from_shape"], [4, 2, 1, "", "from_string"], [4, 2, 1, "", "is_same_size"], [4, 3, 1, "", "ndim"], [4, 2, 1, "", "overlap"], [4, 2, 1, "", "shift"]], "astrodata.TagSet": [[5, 3, 1, "", "add"], [5, 3, 1, "", "blocked_by"], [5, 3, 1, "", "blocks"], [5, 3, 1, "", "if_present"], [5, 3, 1, "", "remove"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:attribute", "4": "py:exception", "5": "py:function"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "attribute", "Python attribute"], "4": ["py", "exception", "Python exception"], "5": ["py", "function", "Python function"]}, "titleterms": {"astrodata": [0, 14, 18, 20, 22, 23, 30, 31, 33, 34, 35, 36, 37], "astrodataerror": 1, "astrodatamixin": 2, "ndastrodata": [3, 24], "section": [4, 30], "tagset": 5, "add_header_to_t": 6, "astro_data_descriptor": 7, "astro_data_tag": 8, "creat": [9, 20, 23, 34, 36, 37], "from_fil": 10, "open": [11, 34], "returns_list": 12, "version": 13, "common": 14, "api": [14, 21], "user": [14, 16, 32], "packag": [14, 30], "function": 14, "class": [14, 23, 24], "gemini": [15, 19], "exampl": [15, 16, 17, 30, 33, 34, 35], "gener": [16, 26], "develop": 16, "document": [18, 20, 22, 27, 32], "content": [18, 34], "indic": 18, "tabl": [18, 20, 34, 36], "list": 19, "standard": 19, "descriptor": [19, 20, 25, 31], "cheat": 20, "sheet": 20, "id": [20, 22, 27, 32], "import": 20, "basic": [20, 30, 34], "read": [20, 34, 36], "write": [20, 23, 34], "oper": [20, 30, 34, 36], "object": [20, 30, 34, 35], "structur": 20, "descript": 20, "modifi": [20, 34], "tag": [20, 23, 29, 37], "header": [20, 31, 34, 36], "direct": 20, "access": [20, 31, 34, 36], "keyword": [20, 34], "primari": 20, "unit": 20, "pixel": [20, 30, 34], "extens": [20, 31, 34, 35], "data": [20, 24, 30, 34, 36], "arithmet": [20, 30], "other": [20, 30], "new": [20, 23, 34, 37], "refer": 21, "manual": [22, 27, 32], "deriv": 23, "The": [23, 35], "properti": 23, "an": [23, 34, 37], "code": [23, 33], "organ": [23, 35], "option": 23, "regist": 23, "your": 23, "contain": 24, "slice": 24, "todo": [24, 29, 30, 31, 34, 35, 37], "design": 26, "programm": 27, "": 27, "introduct": [28, 33], "simpl": 30, "preced": 30, "varianc": 30, "ad": [30, 31], "plane": 30, "automat": 30, "propag": 30, "qualiti": 30, "displai": 30, "us": [30, 33, 37], "tool": 30, "from": [30, 34, 36], "numpi": 30, "scipi": 30, "astropi": 30, "ndarrai": 30, "statist": [30, 36], "clip": 30, "filter": 30, "mani": 30, "manipul": [30, 34], "overscan": 30, "subtract": 30, "trim": 30, "cube": 30, "plot": 30, "matplotlib": 30, "metadata": 31, "across": 31, "multipl": 31, "directli": [31, 36], "whole": 31, "updat": [31, 34], "delet": 31, "world": 31, "coordin": 31, "system": 31, "attribut": 31, "advanc": [31, 37], "topic": [31, 37], "what": [33, 37], "i": [33, 37], "instal": 33, "pip": 33, "sourc": 33, "A": [33, 34], "quick": 33, "support": 33, "input": 34, "output": 34, "mef": 34, "exist": 34, "dataset": 34, "file": 34, "append": 34, "remov": 34, "part": 34, "one": 34, "back": 34, "disk": 34, "note": 34, "fit": [34, 36], "copi": 34, "need": 34, "true": 34, "memori": 34, "scratch": 34, "arrai": 34, "set": 34, "zero": 34, "associ": 34, "scienc": 34, "repres": 34, "binari": 34, "locat": 35, "walkthrough": 35, "global": 35, "v": 35, "specif": 35, "inform": 35, "column": 36, "row": 36, "chang": 36, "valu": 36, "add": 36, "select": 36, "reject": 36, "element": 36, "criterion": 36, "befor": 36}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.todo": 2, "sphinx.ext.viewcode": 1, "sphinx": 60}, "alltitles": {"AstroData": [[0, "astrodata"]], "AstroDataError": [[1, "astrodataerror"]], "AstroDataMixin": [[2, "astrodatamixin"]], "NDAstroData": [[3, "ndastrodata"]], "Section": [[4, "section"]], "TagSet": [[5, "tagset"]], "add_header_to_table": [[6, "add-header-to-table"]], "astro_data_descriptor": [[7, "astro-data-descriptor"]], "astro_data_tag": [[8, "astro-data-tag"]], "create": [[9, "create"]], "from_file": [[10, "from-file"]], "open": [[11, "open"]], "returns_list": [[12, "returns-list"]], "version": [[13, "version"]], "Common API for Users": [[14, "common-api-for-users"]], "astrodata Package": [[14, "module-astrodata"]], "Functions": [[14, "functions"]], "Classes": [[14, "classes"]], "Gemini Examples": [[15, "gemini-examples"]], "Generic Examples": [[16, "generic-examples"]], "User Examples": [[16, "user-examples"]], "Developer Examples": [[16, "developer-examples"]], "Examples": [[17, "examples"]], "astrodata Documentation": [[18, "astrodata-documentation"]], "Contents:": [[18, null]], "Indices and tables": [[18, "indices-and-tables"]], "List of Gemini Standard Descriptors": [[19, "list-of-gemini-standard-descriptors"]], "Cheat Sheet": [[20, "cheat-sheet"]], "Document ID": [[20, null], [22, null], [27, null], [32, null]], "Imports": [[20, "imports"]], "Basic read and write operations": [[20, "basic-read-and-write-operations"]], "Object structure": [[20, "object-structure"]], "Description": [[20, "description"]], "Modifying the structure": [[20, "modifying-the-structure"]], "Astrodata tags": [[20, "astrodata-tags"]], "Headers": [[20, "headers"], [34, "headers"]], "Descriptors": [[20, "descriptors"], [25, "descriptors"]], "Direct access to header keywords": [[20, "direct-access-to-header-keywords"]], "Primary Header Unit": [[20, "primary-header-unit"]], "Pixel extension header": [[20, "pixel-extension-header"]], "Table header": [[20, "table-header"]], "Pixel data": [[20, "pixel-data"], [34, "pixel-data"]], "Arithmetics": [[20, "arithmetics"]], "Other pixel data operations": [[20, "other-pixel-data-operations"]], "Tables": [[20, "tables"], [34, "tables"]], "Create new AstroData object": [[20, "create-new-astrodata-object"]], "Reference API": [[21, "reference-api"]], "Astrodata Manual": [[22, "astrodata-manual"]], "AstroData and Derivatives": [[23, "astrodata-and-derivatives"]], "The tags Property": [[23, "the-tags-property"]], "Writing an AstroData Derivative": [[23, "writing-an-astrodata-derivative"]], "Create a new class": [[23, "create-a-new-class"]], "Code Organization (Optional)": [[23, "code-organization-optional"]], "Register your class": [[23, "register-your-class"]], "Data Containers": [[24, "data-containers"]], "NDAstroData class": [[24, "ndastrodata-class"]], "Slicing": [[24, "slicing"]], "Todo": [[24, "id1"], [24, "id2"], [29, "id1"], [30, "id2"], [30, "id3"], [30, "id4"], [30, "id5"], [30, "id6"], [30, "id7"], [30, "id8"], [30, "id9"], [30, "id10"], [30, "id11"], [30, "id12"], [30, "id13"], [31, "id1"], [31, "id2"], [31, "id3"], [31, "id4"], [31, "id5"], [31, "id6"], [31, "id7"], [34, "id1"], [34, "id2"], [34, "id3"], [34, "id4"], [34, "id5"], [34, "id6"], [35, "id1"], [35, "id2"], [35, "id3"], [35, "id4"], [37, "id1"], [37, "id2"]], "General Design": [[26, "general-design"]], "Programmer\u2019s Manual": [[27, "programmer-s-manual"]], "Introduction": [[28, "introduction"], [33, "introduction"]], "Tags": [[29, "tags"]], "Pixel Data": [[30, "pixel-data"]], "Operate on Pixel Data": [[30, "operate-on-pixel-data"]], "Arithmetic on AstroData Objects": [[30, "arithmetic-on-astrodata-objects"]], "Simple operations": [[30, "simple-operations"]], "Operator Precedence": [[30, "operator-precedence"]], "Variance": [[30, "variance"]], "Adding a Variance Plane": [[30, "adding-a-variance-plane"]], "Automatic Variance Propagation": [[30, "automatic-variance-propagation"]], "Data Quality Plane": [[30, "data-quality-plane"]], "Display": [[30, "display"]], "Useful tools from the NumPy, SciPy, and Astropy Packages": [[30, "useful-tools-from-the-numpy-scipy-and-astropy-packages"]], "ndarray": [[30, "ndarray"]], "Simple Numpy Statistics": [[30, "simple-numpy-statistics"]], "Clipped Statistics": [[30, "clipped-statistics"]], "Filters with SciPy": [[30, "filters-with-scipy"]], "Many other tools": [[30, "many-other-tools"]], "Using the Astrodata Data Quality Plane": [[30, "using-the-astrodata-data-quality-plane"]], "Manipulate Data Sections": [[30, "manipulate-data-sections"]], "Basic Statistics on Section": [[30, "basic-statistics-on-section"]], "Example - Overscan Subtraction with Trimming": [[30, "example-overscan-subtraction-with-trimming"]], "Data Cubes": [[30, "data-cubes"]], "Plot Data": [[30, "plot-data"]], "Matplotlib": [[30, "matplotlib"]], "Metadata and Headers": [[31, "metadata-and-headers"]], "Astrodata Descriptors": [[31, "astrodata-descriptors"]], "Accessing Metadata": [[31, "accessing-metadata"]], "Accessing Metadata with Descriptors": [[31, "accessing-metadata-with-descriptors"]], "Descriptors across multiple extensions": [[31, "descriptors-across-multiple-extensions"]], "Accessing Metadata Directly": [[31, "accessing-metadata-directly"]], "Whole Headers": [[31, "whole-headers"]], "Updating, Adding and Deleting Metadata": [[31, "updating-adding-and-deleting-metadata"]], "World Coordinate System attribute": [[31, "world-coordinate-system-attribute"]], "Adding Descriptors [Advanced Topic]": [[31, "adding-descriptors-advanced-topic"]], "User Manual": [[32, "user-manual"]], "What is astrodata?": [[33, "what-is-astrodata"]], "Installing Astrodata": [[33, "installing-astrodata"]], "Using pip": [[33, "using-pip"]], "Source code": [[33, "source-code"]], "A quick example": [[33, "a-quick-example"]], "Astrodata Support": [[33, "astrodata-support"]], "Input and Output Operations and Extension Manipulation - MEF": [[34, "input-and-output-operations-and-extension-manipulation-mef"]], "Open and access existing dataset": [[34, "open-and-access-existing-dataset"]], "Read in the dataset": [[34, "read-in-the-dataset"]], "Accessing the content of a MEF file": [[34, "accessing-the-content-of-a-mef-file"]], "Modify Existing MEF Files": [[34, "modify-existing-mef-files"]], "Appending an extension": [[34, "appending-an-extension"]], "Removing an extension or part of one": [[34, "removing-an-extension-or-part-of-one"]], "Writing back to a file": [[34, "writing-back-to-a-file"]], "Writing to a new file": [[34, "writing-to-a-new-file"]], "Updating an existing file on disk": [[34, "updating-an-existing-file-on-disk"]], "A note on FITS header keywords": [[34, "a-note-on-fits-header-keywords"]], "Create New MEF Files": [[34, "create-new-mef-files"]], "Create New Copy of MEF Files": [[34, "create-new-copy-of-mef-files"]], "Basic example": [[34, "basic-example"]], "Needing true copies in memory": [[34, "needing-true-copies-in-memory"]], "Create New MEF Files from Scratch": [[34, "create-new-mef-files-from-scratch"]], "Create a MEF with basic header and data array set to zeros": [[34, "create-a-mef-with-basic-header-and-data-array-set-to-zeros"]], "Associate a pixel array with a science pixel array": [[34, "associate-a-pixel-array-with-a-science-pixel-array"]], "Represent a table as a FITS binary table in an AstroData object": [[34, "represent-a-table-as-a-fits-binary-table-in-an-astrodata-object"]], "The AstroData Object": [[35, "the-astrodata-object"]], "Example location": [[35, "example-location"]], "Walkthrough": [[35, "walkthrough"]], "Global vs Extension-specific": [[35, "global-vs-extension-specific"]], "Organization of Global Information": [[35, "organization-of-global-information"]], "Organization of the Extension-specific Information": [[35, "organization-of-the-extension-specific-information"]], "Table Data": [[36, "table-data"]], "Tables and Astrodata": [[36, "tables-and-astrodata"]], "Operate on a Table": [[36, "operate-on-a-table"]], "Column and Row Operations": [[36, "column-and-row-operations"]], "Read columns and rows": [[36, "read-columns-and-rows"]], "Change values": [[36, "change-values"]], "Add a row": [[36, "add-a-row"]], "Add a column": [[36, "add-a-column"]], "Selection and Rejection Operations": [[36, "selection-and-rejection-operations"]], "Select a table element from criterion": [[36, "select-a-table-element-from-criterion"]], "Rejection and selection before statistics": [[36, "rejection-and-selection-before-statistics"]], "Accessing FITS table headers directly": [[36, "accessing-fits-table-headers-directly"]], "Create a Table": [[36, "create-a-table"]], "Astrodata Tag": [[37, "astrodata-tag"]], "What is an Astrodata Tag?": [[37, "what-is-an-astrodata-tag"]], "Using the Astrodata Tags": [[37, "using-the-astrodata-tags"]], "Creating New Astrodata Tags [Advanced Topic]": [[37, "creating-new-astrodata-tags-advanced-topic"]]}, "indexentries": {"astrodata (class in astrodata)": [[0, "astrodata.AstroData"]], "add() (astrodata.astrodata method)": [[0, "astrodata.AstroData.add"]], "append() (astrodata.astrodata method)": [[0, "astrodata.AstroData.append"]], "crop() (astrodata.astrodata method)": [[0, "astrodata.AstroData.crop"]], "data (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.data"]], "descriptors (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.descriptors"]], "divide() (astrodata.astrodata method)": [[0, "astrodata.AstroData.divide"]], "exposed (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.exposed"]], "ext_tables (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.ext_tables"]], "filename (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.filename"]], "hdr (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.hdr"]], "header (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.header"]], "id (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.id"]], "indices (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.indices"]], "info() (astrodata.astrodata method)": [[0, "astrodata.AstroData.info"]], "instrument() (astrodata.astrodata method)": [[0, "astrodata.AstroData.instrument"]], "is_settable() (astrodata.astrodata method)": [[0, "astrodata.AstroData.is_settable"]], "is_sliced (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.is_sliced"]], "load() (astrodata.astrodata class method)": [[0, "astrodata.AstroData.load"]], "mask (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.mask"]], "matches_data() (astrodata.astrodata class method)": [[0, "astrodata.AstroData.matches_data"]], "multiply() (astrodata.astrodata method)": [[0, "astrodata.AstroData.multiply"]], "nddata (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.nddata"]], "object() (astrodata.astrodata method)": [[0, "astrodata.AstroData.object"]], "operate() (astrodata.astrodata method)": [[0, "astrodata.AstroData.operate"]], "orig_filename (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.orig_filename"]], "path (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.path"]], "phu (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.phu"]], "read() (astrodata.astrodata class method)": [[0, "astrodata.AstroData.read"]], "reset() (astrodata.astrodata method)": [[0, "astrodata.AstroData.reset"]], "shape (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.shape"]], "subtract() (astrodata.astrodata method)": [[0, "astrodata.AstroData.subtract"]], "table() (astrodata.astrodata method)": [[0, "astrodata.AstroData.table"]], "tables (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.tables"]], "tags (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.tags"]], "telescope() (astrodata.astrodata method)": [[0, "astrodata.AstroData.telescope"]], "uncertainty (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.uncertainty"]], "update_filename() (astrodata.astrodata method)": [[0, "astrodata.AstroData.update_filename"]], "variance (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.variance"]], "wcs (astrodata.astrodata attribute)": [[0, "astrodata.AstroData.wcs"]], "write() (astrodata.astrodata method)": [[0, "astrodata.AstroData.write"]], "astrodataerror": [[1, "astrodata.AstroDataError"]], "astrodatamixin (class in astrodata)": [[2, "astrodata.AstroDataMixin"]], "shape (astrodata.astrodatamixin attribute)": [[2, "astrodata.AstroDataMixin.shape"]], "size (astrodata.astrodatamixin attribute)": [[2, "astrodata.AstroDataMixin.size"]], "variance (astrodata.astrodatamixin attribute)": [[2, "astrodata.AstroDataMixin.variance"]], "wcs (astrodata.astrodatamixin attribute)": [[2, "astrodata.AstroDataMixin.wcs"]], "ndastrodata (class in astrodata)": [[3, "astrodata.NDAstroData"]], "t (astrodata.ndastrodata attribute)": [[3, "astrodata.NDAstroData.T"]], "data (astrodata.ndastrodata attribute)": [[3, "astrodata.NDAstroData.data"]], "mask (astrodata.ndastrodata attribute)": [[3, "astrodata.NDAstroData.mask"]], "set_section() (astrodata.ndastrodata method)": [[3, "astrodata.NDAstroData.set_section"]], "transpose() (astrodata.ndastrodata method)": [[3, "astrodata.NDAstroData.transpose"]], "uncertainty (astrodata.ndastrodata attribute)": [[3, "astrodata.NDAstroData.uncertainty"]], "variance (astrodata.ndastrodata attribute)": [[3, "astrodata.NDAstroData.variance"]], "window (astrodata.ndastrodata attribute)": [[3, "astrodata.NDAstroData.window"]], "section (class in astrodata)": [[4, "astrodata.Section"]], "asirafsection() (astrodata.section method)": [[4, "astrodata.Section.asIRAFsection"]], "as_iraf_section() (astrodata.section method)": [[4, "astrodata.Section.as_iraf_section"]], "asslice() (astrodata.section method)": [[4, "astrodata.Section.asslice"]], "axis_dict (astrodata.section attribute)": [[4, "astrodata.Section.axis_dict"]], "contains() (astrodata.section method)": [[4, "astrodata.Section.contains"]], "from_shape() (astrodata.section static method)": [[4, "astrodata.Section.from_shape"]], "from_string() (astrodata.section static method)": [[4, "astrodata.Section.from_string"]], "is_same_size() (astrodata.section method)": [[4, "astrodata.Section.is_same_size"]], "ndim (astrodata.section attribute)": [[4, "astrodata.Section.ndim"]], "overlap() (astrodata.section method)": [[4, "astrodata.Section.overlap"]], "shift() (astrodata.section method)": [[4, "astrodata.Section.shift"]], "tagset (class in astrodata)": [[5, "astrodata.TagSet"]], "add (astrodata.tagset attribute)": [[5, "astrodata.TagSet.add"]], "blocked_by (astrodata.tagset attribute)": [[5, "astrodata.TagSet.blocked_by"]], "blocks (astrodata.tagset attribute)": [[5, "astrodata.TagSet.blocks"]], "if_present (astrodata.tagset attribute)": [[5, "astrodata.TagSet.if_present"]], "remove (astrodata.tagset attribute)": [[5, "astrodata.TagSet.remove"]], "add_header_to_table() (in module astrodata)": [[6, "astrodata.add_header_to_table"]], "astro_data_descriptor() (in module astrodata)": [[7, "astrodata.astro_data_descriptor"]], "astro_data_tag() (in module astrodata)": [[8, "astrodata.astro_data_tag"]], "create() (in module astrodata)": [[9, "astrodata.create"]], "from_file() (in module astrodata)": [[10, "astrodata.from_file"]], "open() (in module astrodata)": [[11, "astrodata.open"]], "returns_list() (in module astrodata)": [[12, "astrodata.returns_list"]], "version() (in module astrodata)": [[13, "astrodata.version"]], "astrodata": [[14, "module-astrodata"]], "module": [[14, "module-astrodata"]]}}) \ No newline at end of file