APLpy-1.1.1/0000755000077000000240000000000012774747316012437 5ustar tomstaff00000000000000APLpy-1.1.1/ah_bootstrap.py0000644000077000000240000010650212774746217015501 0ustar tomstaff00000000000000""" This bootstrap module contains code for ensuring that the astropy_helpers package will be importable by the time the setup.py script runs. It also includes some workarounds to ensure that a recent-enough version of setuptools is being used for the installation. This module should be the first thing imported in the setup.py of distributions that make use of the utilities in astropy_helpers. If the distribution ships with its own copy of astropy_helpers, this module will first attempt to import from the shipped copy. However, it will also check PyPI to see if there are any bug-fix releases on top of the current version that may be useful to get past platform-specific bugs that have been fixed. When running setup.py, use the ``--offline`` command-line option to disable the auto-upgrade checks. When this module is imported or otherwise executed it automatically calls a main function that attempts to read the project's setup.cfg file, which it checks for a configuration section called ``[ah_bootstrap]`` the presences of that section, and options therein, determine the next step taken: If it contains an option called ``auto_use`` with a value of ``True``, it will automatically call the main function of this module called `use_astropy_helpers` (see that function's docstring for full details). Otherwise no further action is taken (however, ``ah_bootstrap.use_astropy_helpers`` may be called manually from within the setup.py script). Additional options in the ``[ah_boostrap]`` section of setup.cfg have the same names as the arguments to `use_astropy_helpers`, and can be used to configure the bootstrap script when ``auto_use = True``. See https://github.com/astropy/astropy-helpers for more details, and for the latest version of this module. """ import contextlib import errno import imp import io import locale import os import re import subprocess as sp import sys try: from ConfigParser import ConfigParser, RawConfigParser except ImportError: from configparser import ConfigParser, RawConfigParser if sys.version_info[0] < 3: _str_types = (str, unicode) _text_type = unicode PY3 = False else: _str_types = (str, bytes) _text_type = str PY3 = True # What follows are several import statements meant to deal with install-time # issues with either missing or misbehaving pacakges (including making sure # setuptools itself is installed): # Some pre-setuptools checks to ensure that either distribute or setuptools >= # 0.7 is used (over pre-distribute setuptools) if it is available on the path; # otherwise the latest setuptools will be downloaded and bootstrapped with # ``ez_setup.py``. This used to be included in a separate file called # setuptools_bootstrap.py; but it was combined into ah_bootstrap.py try: import pkg_resources _setuptools_req = pkg_resources.Requirement.parse('setuptools>=0.7') # This may raise a DistributionNotFound in which case no version of # setuptools or distribute is properly installed _setuptools = pkg_resources.get_distribution('setuptools') if _setuptools not in _setuptools_req: # Older version of setuptools; check if we have distribute; again if # this results in DistributionNotFound we want to give up _distribute = pkg_resources.get_distribution('distribute') if _setuptools != _distribute: # It's possible on some pathological systems to have an old version # of setuptools and distribute on sys.path simultaneously; make # sure distribute is the one that's used sys.path.insert(1, _distribute.location) _distribute.activate() imp.reload(pkg_resources) except: # There are several types of exceptions that can occur here; if all else # fails bootstrap and use the bootstrapped version from ez_setup import use_setuptools use_setuptools() # Note: The following import is required as a workaround to # https://github.com/astropy/astropy-helpers/issues/89; if we don't import this # module now, it will get cleaned up after `run_setup` is called, but that will # later cause the TemporaryDirectory class defined in it to stop working when # used later on by setuptools try: import setuptools.py31compat except ImportError: pass # matplotlib can cause problems if it is imported from within a call of # run_setup(), because in some circumstances it will try to write to the user's # home directory, resulting in a SandboxViolation. See # https://github.com/matplotlib/matplotlib/pull/4165 # Making sure matplotlib, if it is available, is imported early in the setup # process can mitigate this (note importing matplotlib.pyplot has the same # issue) try: import matplotlib matplotlib.use('Agg') import matplotlib.pyplot except: # Ignore if this fails for *any* reason* pass # End compatibility imports... # In case it didn't successfully import before the ez_setup checks import pkg_resources from setuptools import Distribution from setuptools.package_index import PackageIndex from setuptools.sandbox import run_setup from distutils import log from distutils.debug import DEBUG # TODO: Maybe enable checking for a specific version of astropy_helpers? DIST_NAME = 'astropy-helpers' PACKAGE_NAME = 'astropy_helpers' # Defaults for other options DOWNLOAD_IF_NEEDED = True INDEX_URL = 'https://pypi.python.org/simple' USE_GIT = True OFFLINE = False AUTO_UPGRADE = True # A list of all the configuration options and their required types CFG_OPTIONS = [ ('auto_use', bool), ('path', str), ('download_if_needed', bool), ('index_url', str), ('use_git', bool), ('offline', bool), ('auto_upgrade', bool) ] class _Bootstrapper(object): """ Bootstrapper implementation. See ``use_astropy_helpers`` for parameter documentation. """ def __init__(self, path=None, index_url=None, use_git=None, offline=None, download_if_needed=None, auto_upgrade=None): if path is None: path = PACKAGE_NAME if not (isinstance(path, _str_types) or path is False): raise TypeError('path must be a string or False') if PY3 and not isinstance(path, _text_type): fs_encoding = sys.getfilesystemencoding() path = path.decode(fs_encoding) # path to unicode self.path = path # Set other option attributes, using defaults where necessary self.index_url = index_url if index_url is not None else INDEX_URL self.offline = offline if offline is not None else OFFLINE # If offline=True, override download and auto-upgrade if self.offline: download_if_needed = False auto_upgrade = False self.download = (download_if_needed if download_if_needed is not None else DOWNLOAD_IF_NEEDED) self.auto_upgrade = (auto_upgrade if auto_upgrade is not None else AUTO_UPGRADE) # If this is a release then the .git directory will not exist so we # should not use git. git_dir_exists = os.path.exists(os.path.join(os.path.dirname(__file__), '.git')) if use_git is None and not git_dir_exists: use_git = False self.use_git = use_git if use_git is not None else USE_GIT # Declared as False by default--later we check if astropy-helpers can be # upgraded from PyPI, but only if not using a source distribution (as in # the case of import from a git submodule) self.is_submodule = False @classmethod def main(cls, argv=None): if argv is None: argv = sys.argv config = cls.parse_config() config.update(cls.parse_command_line(argv)) auto_use = config.pop('auto_use', False) bootstrapper = cls(**config) if auto_use: # Run the bootstrapper, otherwise the setup.py is using the old # use_astropy_helpers() interface, in which case it will run the # bootstrapper manually after reconfiguring it. bootstrapper.run() return bootstrapper @classmethod def parse_config(cls): if not os.path.exists('setup.cfg'): return {} cfg = ConfigParser() try: cfg.read('setup.cfg') except Exception as e: if DEBUG: raise log.error( "Error reading setup.cfg: {0!r}\n{1} will not be " "automatically bootstrapped and package installation may fail." "\n{2}".format(e, PACKAGE_NAME, _err_help_msg)) return {} if not cfg.has_section('ah_bootstrap'): return {} config = {} for option, type_ in CFG_OPTIONS: if not cfg.has_option('ah_bootstrap', option): continue if type_ is bool: value = cfg.getboolean('ah_bootstrap', option) else: value = cfg.get('ah_bootstrap', option) config[option] = value return config @classmethod def parse_command_line(cls, argv=None): if argv is None: argv = sys.argv config = {} # For now we just pop recognized ah_bootstrap options out of the # arg list. This is imperfect; in the unlikely case that a setup.py # custom command or even custom Distribution class defines an argument # of the same name then we will break that. However there's a catch22 # here that we can't just do full argument parsing right here, because # we don't yet know *how* to parse all possible command-line arguments. if '--no-git' in argv: config['use_git'] = False argv.remove('--no-git') if '--offline' in argv: config['offline'] = True argv.remove('--offline') return config def run(self): strategies = ['local_directory', 'local_file', 'index'] dist = None # First, remove any previously imported versions of astropy_helpers; # this is necessary for nested installs where one package's installer # is installing another package via setuptools.sandbox.run_setup, as in # the case of setup_requires for key in list(sys.modules): try: if key == PACKAGE_NAME or key.startswith(PACKAGE_NAME + '.'): del sys.modules[key] except AttributeError: # Sometimes mysterious non-string things can turn up in # sys.modules continue # Check to see if the path is a submodule self.is_submodule = self._check_submodule() for strategy in strategies: method = getattr(self, 'get_{0}_dist'.format(strategy)) dist = method() if dist is not None: break else: raise _AHBootstrapSystemExit( "No source found for the {0!r} package; {0} must be " "available and importable as a prerequisite to building " "or installing this package.".format(PACKAGE_NAME)) # This is a bit hacky, but if astropy_helpers was loaded from a # directory/submodule its Distribution object gets a "precedence" of # "DEVELOP_DIST". However, in other cases it gets a precedence of # "EGG_DIST". However, when activing the distribution it will only be # placed early on sys.path if it is treated as an EGG_DIST, so always # do that dist = dist.clone(precedence=pkg_resources.EGG_DIST) # Otherwise we found a version of astropy-helpers, so we're done # Just active the found distribution on sys.path--if we did a # download this usually happens automatically but it doesn't hurt to # do it again # Note: Adding the dist to the global working set also activates it # (makes it importable on sys.path) by default. try: pkg_resources.working_set.add(dist, replace=True) except TypeError: # Some (much) older versions of setuptools do not have the # replace=True option here. These versions are old enough that all # bets may be off anyways, but it's easy enough to work around just # in case... if dist.key in pkg_resources.working_set.by_key: del pkg_resources.working_set.by_key[dist.key] pkg_resources.working_set.add(dist) @property def config(self): """ A `dict` containing the options this `_Bootstrapper` was configured with. """ return dict((optname, getattr(self, optname)) for optname, _ in CFG_OPTIONS if hasattr(self, optname)) def get_local_directory_dist(self): """ Handle importing a vendored package from a subdirectory of the source distribution. """ if not os.path.isdir(self.path): return log.info('Attempting to import astropy_helpers from {0} {1!r}'.format( 'submodule' if self.is_submodule else 'directory', self.path)) dist = self._directory_import() if dist is None: log.warn( 'The requested path {0!r} for importing {1} does not ' 'exist, or does not contain a copy of the {1} ' 'package.'.format(self.path, PACKAGE_NAME)) elif self.auto_upgrade and not self.is_submodule: # A version of astropy-helpers was found on the available path, but # check to see if a bugfix release is available on PyPI upgrade = self._do_upgrade(dist) if upgrade is not None: dist = upgrade return dist def get_local_file_dist(self): """ Handle importing from a source archive; this also uses setup_requires but points easy_install directly to the source archive. """ if not os.path.isfile(self.path): return log.info('Attempting to unpack and import astropy_helpers from ' '{0!r}'.format(self.path)) try: dist = self._do_download(find_links=[self.path]) except Exception as e: if DEBUG: raise log.warn( 'Failed to import {0} from the specified archive {1!r}: ' '{2}'.format(PACKAGE_NAME, self.path, str(e))) dist = None if dist is not None and self.auto_upgrade: # A version of astropy-helpers was found on the available path, but # check to see if a bugfix release is available on PyPI upgrade = self._do_upgrade(dist) if upgrade is not None: dist = upgrade return dist def get_index_dist(self): if not self.download: log.warn('Downloading {0!r} disabled.'.format(DIST_NAME)) return None log.warn( "Downloading {0!r}; run setup.py with the --offline option to " "force offline installation.".format(DIST_NAME)) try: dist = self._do_download() except Exception as e: if DEBUG: raise log.warn( 'Failed to download and/or install {0!r} from {1!r}:\n' '{2}'.format(DIST_NAME, self.index_url, str(e))) dist = None # No need to run auto-upgrade here since we've already presumably # gotten the most up-to-date version from the package index return dist def _directory_import(self): """ Import astropy_helpers from the given path, which will be added to sys.path. Must return True if the import succeeded, and False otherwise. """ # Return True on success, False on failure but download is allowed, and # otherwise raise SystemExit path = os.path.abspath(self.path) # Use an empty WorkingSet rather than the man # pkg_resources.working_set, since on older versions of setuptools this # will invoke a VersionConflict when trying to install an upgrade ws = pkg_resources.WorkingSet([]) ws.add_entry(path) dist = ws.by_key.get(DIST_NAME) if dist is None: # We didn't find an egg-info/dist-info in the given path, but if a # setup.py exists we can generate it setup_py = os.path.join(path, 'setup.py') if os.path.isfile(setup_py): with _silence(): run_setup(os.path.join(path, 'setup.py'), ['egg_info']) for dist in pkg_resources.find_distributions(path, True): # There should be only one... return dist return dist def _do_download(self, version='', find_links=None): if find_links: allow_hosts = '' index_url = None else: allow_hosts = None index_url = self.index_url # Annoyingly, setuptools will not handle other arguments to # Distribution (such as options) before handling setup_requires, so it # is not straightforward to programmatically augment the arguments which # are passed to easy_install class _Distribution(Distribution): def get_option_dict(self, command_name): opts = Distribution.get_option_dict(self, command_name) if command_name == 'easy_install': if find_links is not None: opts['find_links'] = ('setup script', find_links) if index_url is not None: opts['index_url'] = ('setup script', index_url) if allow_hosts is not None: opts['allow_hosts'] = ('setup script', allow_hosts) return opts if version: req = '{0}=={1}'.format(DIST_NAME, version) else: req = DIST_NAME attrs = {'setup_requires': [req]} try: if DEBUG: _Distribution(attrs=attrs) else: with _silence(): _Distribution(attrs=attrs) # If the setup_requires succeeded it will have added the new dist to # the main working_set return pkg_resources.working_set.by_key.get(DIST_NAME) except Exception as e: if DEBUG: raise msg = 'Error retrieving {0} from {1}:\n{2}' if find_links: source = find_links[0] elif index_url != INDEX_URL: source = index_url else: source = 'PyPI' raise Exception(msg.format(DIST_NAME, source, repr(e))) def _do_upgrade(self, dist): # Build up a requirement for a higher bugfix release but a lower minor # release (so API compatibility is guaranteed) next_version = _next_version(dist.parsed_version) req = pkg_resources.Requirement.parse( '{0}>{1},<{2}'.format(DIST_NAME, dist.version, next_version)) package_index = PackageIndex(index_url=self.index_url) upgrade = package_index.obtain(req) if upgrade is not None: return self._do_download(version=upgrade.version) def _check_submodule(self): """ Check if the given path is a git submodule. See the docstrings for ``_check_submodule_using_git`` and ``_check_submodule_no_git`` for further details. """ if (self.path is None or (os.path.exists(self.path) and not os.path.isdir(self.path))): return False if self.use_git: return self._check_submodule_using_git() else: return self._check_submodule_no_git() def _check_submodule_using_git(self): """ Check if the given path is a git submodule. If so, attempt to initialize and/or update the submodule if needed. This function makes calls to the ``git`` command in subprocesses. The ``_check_submodule_no_git`` option uses pure Python to check if the given path looks like a git submodule, but it cannot perform updates. """ cmd = ['git', 'submodule', 'status', '--', self.path] try: log.info('Running `{0}`; use the --no-git option to disable git ' 'commands'.format(' '.join(cmd))) returncode, stdout, stderr = run_cmd(cmd) except _CommandNotFound: # The git command simply wasn't found; this is most likely the # case on user systems that don't have git and are simply # trying to install the package from PyPI or a source # distribution. Silently ignore this case and simply don't try # to use submodules return False stderr = stderr.strip() if returncode != 0 and stderr: # Unfortunately the return code alone cannot be relied on, as # earlier versions of git returned 0 even if the requested submodule # does not exist # This is a warning that occurs in perl (from running git submodule) # which only occurs with a malformatted locale setting which can # happen sometimes on OSX. See again # https://github.com/astropy/astropy/issues/2749 perl_warning = ('perl: warning: Falling back to the standard locale ' '("C").') if not stderr.strip().endswith(perl_warning): # Some other unknown error condition occurred log.warn('git submodule command failed ' 'unexpectedly:\n{0}'.format(stderr)) return False # Output of `git submodule status` is as follows: # # 1: Status indicator: '-' for submodule is uninitialized, '+' if # submodule is initialized but is not at the commit currently indicated # in .gitmodules (and thus needs to be updated), or 'U' if the # submodule is in an unstable state (i.e. has merge conflicts) # # 2. SHA-1 hash of the current commit of the submodule (we don't really # need this information but it's useful for checking that the output is # correct) # # 3. The output of `git describe` for the submodule's current commit # hash (this includes for example what branches the commit is on) but # only if the submodule is initialized. We ignore this information for # now _git_submodule_status_re = re.compile( '^(?P[+-U ])(?P[0-9a-f]{40}) ' '(?P\S+)( .*)?$') # The stdout should only contain one line--the status of the # requested submodule m = _git_submodule_status_re.match(stdout) if m: # Yes, the path *is* a git submodule self._update_submodule(m.group('submodule'), m.group('status')) return True else: log.warn( 'Unexpected output from `git submodule status`:\n{0}\n' 'Will attempt import from {1!r} regardless.'.format( stdout, self.path)) return False def _check_submodule_no_git(self): """ Like ``_check_submodule_using_git``, but simply parses the .gitmodules file to determine if the supplied path is a git submodule, and does not exec any subprocesses. This can only determine if a path is a submodule--it does not perform updates, etc. This function may need to be updated if the format of the .gitmodules file is changed between git versions. """ gitmodules_path = os.path.abspath('.gitmodules') if not os.path.isfile(gitmodules_path): return False # This is a minimal reader for gitconfig-style files. It handles a few of # the quirks that make gitconfig files incompatible with ConfigParser-style # files, but does not support the full gitconfig syntax (just enough # needed to read a .gitmodules file). gitmodules_fileobj = io.StringIO() # Must use io.open for cross-Python-compatible behavior wrt unicode with io.open(gitmodules_path) as f: for line in f: # gitconfig files are more flexible with leading whitespace; just # go ahead and remove it line = line.lstrip() # comments can start with either # or ; if line and line[0] in (':', ';'): continue gitmodules_fileobj.write(line) gitmodules_fileobj.seek(0) cfg = RawConfigParser() try: cfg.readfp(gitmodules_fileobj) except Exception as exc: log.warn('Malformatted .gitmodules file: {0}\n' '{1} cannot be assumed to be a git submodule.'.format( exc, self.path)) return False for section in cfg.sections(): if not cfg.has_option(section, 'path'): continue submodule_path = cfg.get(section, 'path').rstrip(os.sep) if submodule_path == self.path.rstrip(os.sep): return True return False def _update_submodule(self, submodule, status): if status == ' ': # The submodule is up to date; no action necessary return elif status == '-': if self.offline: raise _AHBootstrapSystemExit( "Cannot initialize the {0} submodule in --offline mode; " "this requires being able to clone the submodule from an " "online repository.".format(submodule)) cmd = ['update', '--init'] action = 'Initializing' elif status == '+': cmd = ['update'] action = 'Updating' if self.offline: cmd.append('--no-fetch') elif status == 'U': raise _AHBoostrapSystemExit( 'Error: Submodule {0} contains unresolved merge conflicts. ' 'Please complete or abandon any changes in the submodule so that ' 'it is in a usable state, then try again.'.format(submodule)) else: log.warn('Unknown status {0!r} for git submodule {1!r}. Will ' 'attempt to use the submodule as-is, but try to ensure ' 'that the submodule is in a clean state and contains no ' 'conflicts or errors.\n{2}'.format(status, submodule, _err_help_msg)) return err_msg = None cmd = ['git', 'submodule'] + cmd + ['--', submodule] log.warn('{0} {1} submodule with: `{2}`'.format( action, submodule, ' '.join(cmd))) try: log.info('Running `{0}`; use the --no-git option to disable git ' 'commands'.format(' '.join(cmd))) returncode, stdout, stderr = run_cmd(cmd) except OSError as e: err_msg = str(e) else: if returncode != 0: err_msg = stderr if err_msg is not None: log.warn('An unexpected error occurred updating the git submodule ' '{0!r}:\n{1}\n{2}'.format(submodule, err_msg, _err_help_msg)) class _CommandNotFound(OSError): """ An exception raised when a command run with run_cmd is not found on the system. """ def run_cmd(cmd): """ Run a command in a subprocess, given as a list of command-line arguments. Returns a ``(returncode, stdout, stderr)`` tuple. """ try: p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE) # XXX: May block if either stdout or stderr fill their buffers; # however for the commands this is currently used for that is # unlikely (they should have very brief output) stdout, stderr = p.communicate() except OSError as e: if DEBUG: raise if e.errno == errno.ENOENT: msg = 'Command not found: `{0}`'.format(' '.join(cmd)) raise _CommandNotFound(msg, cmd) else: raise _AHBoostrapSystemExit( 'An unexpected error occurred when running the ' '`{0}` command:\n{1}'.format(' '.join(cmd), str(e))) # Can fail of the default locale is not configured properly. See # https://github.com/astropy/astropy/issues/2749. For the purposes under # consideration 'latin1' is an acceptable fallback. try: stdio_encoding = locale.getdefaultlocale()[1] or 'latin1' except ValueError: # Due to an OSX oddity locale.getdefaultlocale() can also crash # depending on the user's locale/language settings. See: # http://bugs.python.org/issue18378 stdio_encoding = 'latin1' # Unlikely to fail at this point but even then let's be flexible if not isinstance(stdout, _text_type): stdout = stdout.decode(stdio_encoding, 'replace') if not isinstance(stderr, _text_type): stderr = stderr.decode(stdio_encoding, 'replace') return (p.returncode, stdout, stderr) def _next_version(version): """ Given a parsed version from pkg_resources.parse_version, returns a new version string with the next minor version. Examples ======== >>> _next_version(pkg_resources.parse_version('1.2.3')) '1.3.0' """ if hasattr(version, 'base_version'): # New version parsing from setuptools >= 8.0 if version.base_version: parts = version.base_version.split('.') else: parts = [] else: parts = [] for part in version: if part.startswith('*'): break parts.append(part) parts = [int(p) for p in parts] if len(parts) < 3: parts += [0] * (3 - len(parts)) major, minor, micro = parts[:3] return '{0}.{1}.{2}'.format(major, minor + 1, 0) class _DummyFile(object): """A noop writeable object.""" errors = '' # Required for Python 3.x encoding = 'utf-8' def write(self, s): pass def flush(self): pass @contextlib.contextmanager def _silence(): """A context manager that silences sys.stdout and sys.stderr.""" old_stdout = sys.stdout old_stderr = sys.stderr sys.stdout = _DummyFile() sys.stderr = _DummyFile() exception_occurred = False try: yield except: exception_occurred = True # Go ahead and clean up so that exception handling can work normally sys.stdout = old_stdout sys.stderr = old_stderr raise if not exception_occurred: sys.stdout = old_stdout sys.stderr = old_stderr _err_help_msg = """ If the problem persists consider installing astropy_helpers manually using pip (`pip install astropy_helpers`) or by manually downloading the source archive, extracting it, and installing by running `python setup.py install` from the root of the extracted source code. """ class _AHBootstrapSystemExit(SystemExit): def __init__(self, *args): if not args: msg = 'An unknown problem occurred bootstrapping astropy_helpers.' else: msg = args[0] msg += '\n' + _err_help_msg super(_AHBootstrapSystemExit, self).__init__(msg, *args[1:]) if sys.version_info[:2] < (2, 7): # In Python 2.6 the distutils log does not log warnings, errors, etc. to # stderr so we have to wrap it to ensure consistency at least in this # module import distutils class log(object): def __getattr__(self, attr): return getattr(distutils.log, attr) def warn(self, msg, *args): self._log_to_stderr(distutils.log.WARN, msg, *args) def error(self, msg): self._log_to_stderr(distutils.log.ERROR, msg, *args) def fatal(self, msg): self._log_to_stderr(distutils.log.FATAL, msg, *args) def log(self, level, msg, *args): if level in (distutils.log.WARN, distutils.log.ERROR, distutils.log.FATAL): self._log_to_stderr(level, msg, *args) else: distutils.log.log(level, msg, *args) def _log_to_stderr(self, level, msg, *args): # This is the only truly 'public' way to get the current threshold # of the log current_threshold = distutils.log.set_threshold(distutils.log.WARN) distutils.log.set_threshold(current_threshold) if level >= current_threshold: if args: msg = msg % args sys.stderr.write('%s\n' % msg) sys.stderr.flush() log = log() BOOTSTRAPPER = _Bootstrapper.main() def use_astropy_helpers(**kwargs): """ Ensure that the `astropy_helpers` module is available and is importable. This supports automatic submodule initialization if astropy_helpers is included in a project as a git submodule, or will download it from PyPI if necessary. Parameters ---------- path : str or None, optional A filesystem path relative to the root of the project's source code that should be added to `sys.path` so that `astropy_helpers` can be imported from that path. If the path is a git submodule it will automatically be initialized and/or updated. The path may also be to a ``.tar.gz`` archive of the astropy_helpers source distribution. In this case the archive is automatically unpacked and made temporarily available on `sys.path` as a ``.egg`` archive. If `None` skip straight to downloading. download_if_needed : bool, optional If the provided filesystem path is not found an attempt will be made to download astropy_helpers from PyPI. It will then be made temporarily available on `sys.path` as a ``.egg`` archive (using the ``setup_requires`` feature of setuptools. If the ``--offline`` option is given at the command line the value of this argument is overridden to `False`. index_url : str, optional If provided, use a different URL for the Python package index than the main PyPI server. use_git : bool, optional If `False` no git commands will be used--this effectively disables support for git submodules. If the ``--no-git`` option is given at the command line the value of this argument is overridden to `False`. auto_upgrade : bool, optional By default, when installing a package from a non-development source distribution ah_boostrap will try to automatically check for patch releases to astropy-helpers on PyPI and use the patched version over any bundled versions. Setting this to `False` will disable that functionality. If the ``--offline`` option is given at the command line the value of this argument is overridden to `False`. offline : bool, optional If `False` disable all actions that require an internet connection, including downloading packages from the package index and fetching updates to any git submodule. Defaults to `True`. """ global BOOTSTRAPPER config = BOOTSTRAPPER.config config.update(**kwargs) # Create a new bootstrapper with the updated configuration and run it BOOTSTRAPPER = _Bootstrapper(**config) BOOTSTRAPPER.run() APLpy-1.1.1/aplpy/0000755000077000000240000000000012774747316013564 5ustar tomstaff00000000000000APLpy-1.1.1/aplpy/__init__.py0000644000077000000240000000137112536551501015660 0ustar tomstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ APLpy : Astronomical Plotting Library in Python """ # Affiliated packages may add whatever they like to this file, but # should keep this content at the top. # ---------------------------------------------------------------------------- from ._astropy_init import * # ---------------------------------------------------------------------------- if not _ASTROPY_SETUP_: from .core import FITSFigure from .rgb import make_rgb_image, make_rgb_cube from .frame import Frame from .overlays import Scalebar, Beam from .colorbar import Colorbar from .grid import Grid from .ticks import Ticks from .labels import TickLabels from .axis_labels import AxisLabels APLpy-1.1.1/aplpy/_astropy_init.py0000644000077000000240000001221412536551501017002 0ustar tomstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst __all__ = ['__version__', '__githash__', 'test'] # this indicates whether or not we are in the package's setup.py try: _ASTROPY_SETUP_ except NameError: from sys import version_info if version_info[0] >= 3: import builtins else: import __builtin__ as builtins builtins._ASTROPY_SETUP_ = False try: from .version import version as __version__ except ImportError: __version__ = '' try: from .version import githash as __githash__ except ImportError: __githash__ = '' # set up the test command def _get_test_runner(): import os from astropy.tests.helper import TestRunner return TestRunner(os.path.dirname(__file__)) def test(package=None, test_path=None, args=None, plugins=None, verbose=False, pastebin=None, remote_data=False, pep8=False, pdb=False, coverage=False, open_files=False, **kwargs): """ Run the tests using `py.test `__. A proper set of arguments is constructed and passed to `pytest.main`_. .. _py.test: http://pytest.org/latest/ .. _pytest.main: http://pytest.org/latest/builtin.html#pytest.main Parameters ---------- package : str, optional The name of a specific package to test, e.g. 'io.fits' or 'utils'. If nothing is specified all default tests are run. test_path : str, optional Specify location to test by path. May be a single file or directory. Must be specified absolutely or relative to the calling directory. args : str, optional Additional arguments to be passed to pytest.main_ in the ``args`` keyword argument. plugins : list, optional Plugins to be passed to pytest.main_ in the ``plugins`` keyword argument. verbose : bool, optional Convenience option to turn on verbose output from py.test_. Passing True is the same as specifying ``'-v'`` in ``args``. pastebin : {'failed','all',None}, optional Convenience option for turning on py.test_ pastebin output. Set to ``'failed'`` to upload info for failed tests, or ``'all'`` to upload info for all tests. remote_data : bool, optional Controls whether to run tests marked with @remote_data. These tests use online data and are not run by default. Set to True to run these tests. pep8 : bool, optional Turn on PEP8 checking via the `pytest-pep8 plugin `_ and disable normal tests. Same as specifying ``'--pep8 -k pep8'`` in ``args``. pdb : bool, optional Turn on PDB post-mortem analysis for failing tests. Same as specifying ``'--pdb'`` in ``args``. coverage : bool, optional Generate a test coverage report. The result will be placed in the directory htmlcov. open_files : bool, optional Fail when any tests leave files open. Off by default, because this adds extra run time to the test suite. Works only on platforms with a working ``lsof`` command. parallel : int, optional When provided, run the tests in parallel on the specified number of CPUs. If parallel is negative, it will use the all the cores on the machine. Requires the `pytest-xdist `_ plugin installed. Only available when using Astropy 0.3 or later. kwargs Any additional keywords passed into this function will be passed on to the astropy test runner. This allows use of test-related functionality implemented in later versions of astropy without explicitly updating the package template. """ test_runner = _get_test_runner() return test_runner.run_tests( package=package, test_path=test_path, args=args, plugins=plugins, verbose=verbose, pastebin=pastebin, remote_data=remote_data, pep8=pep8, pdb=pdb, coverage=coverage, open_files=open_files, **kwargs) if not _ASTROPY_SETUP_: import os from warnings import warn from astropy import config # add these here so we only need to cleanup the namespace at the end config_dir = None if not os.environ.get('ASTROPY_SKIP_CONFIG_UPDATE', False): config_dir = os.path.dirname(__file__) config_template = os.path.join(config_dir, __package__ + ".cfg") if os.path.isfile(config_template): try: config.configuration.update_default_config( __package__, config_dir, version=__version__) except TypeError as orig_error: try: config.configuration.update_default_config( __package__, config_dir) except config.configuration.ConfigurationDefaultMissingError as e: wmsg = (e.args[0] + " Cannot install default profile. If you are " "importing from source, this is expected.") warn(config.configuration.ConfigurationDefaultMissingWarning(wmsg)) del e except: raise orig_error APLpy-1.1.1/aplpy/angle_util.py0000644000077000000240000002503212536551501016244 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import math import struct import numpy as np from . import math_util def almost_equal(a, b): c = struct.pack(" 90: if d >= 270: self.negative = True d, m, s = 359 - d, 59 - m, 60. - s if s == 60.: s = s - 60. m = m + 1 if m == 60: m = m - 60. d = d + 1 else: raise Exception("latitude should be between -90 and 90 \ degrees") # Set new angle self.angle = (d, m, s) def todegrees(self): d, m, s = self.angle degrees = d + m / 60. + s / 3600. if self.negative: degrees = - degrees return degrees def tohours(self): d, m, s = self.angle rd = np.mod(d, 15) h = (d - rd) / 15 rm = np.mod(m, 15) m = (m - rm) / 15 + rd * 4 s = s / 15. + rm * 4. a = Angle(sexagesimal=(h, m, s), latitude=self.latitude) a.negative = self.negative return a def toround(self, rval=3): # Decompose angle d, m, s = self.angle # Round numbers: # 1: degrees only # 2: degrees and minutes # 3: degrees, minutes, and seconds # 4.n: degrees, minutes, and decimal seconds with n decimal places if rval < 2: n = int(round((rval - 1.) * 100)) d = round(d + m / 60. + s / 3600., n) if n == 0: d = int(d) return d elif rval < 3: m = int(round(m + s / 60.)) if m == 60: m = 0 d = d + 1 return (d, m) elif rval < 4: s = int(round(s)) if s == 60: s = 0 m = m + 1 if m == 60: m = 0 d = d + 1 return (d, m, s) else: n = int(round((rval - 4.) * 100)) s = round(s, n) if s == 60.: s = 0. m = m + 1 if m == 60: m = 0 d = d + 1 return (d, m, s) def tostringlist(self, format='ddd:mm:ss', sep=("d", "m", "s")): format = format.replace('h', 'd') r = 1 if '.d' in format: r = 1 pos = format.find('.') nd = len(format[pos + 1:]) r = r + nd / 100. if 'mm' in format: r = 2 if 'ss' in format: r = 3 if '.s' in format: r = 4 pos = format.find('.') ns = len(format[pos + 1:]) r = r + ns / 100. tup = self.toround(rval=r) if type(tup) == tuple: tup = list(tup) else: tup = [tup] string = [] if 'dd' in format: if '.d' in format: string.append(("%0" + str(nd + 3) + "." + str(nd) + "f") % \ tup[0] + sep[0]) else: string.append("%i" % tup[0] + sep[0]) if 'mm' in format: string.append("%02i" % tup[1] + sep[1]) if 'ss' in format and not '.s' in format: string.append("%02i" % tup[2] + sep[2]) if 'ss.s' in format: string.append(("%0" + str(ns + 3) + "." + str(ns) + "f") % tup[2] + sep[2]) # If style is colons, need to remove trailing colon if len(string) >= 1 and sep[0] == ':' and not 'mm' in format: string[0] = string[0][:-1] if len(string) >= 2 and sep[1] == ':' and not 'ss' in format: string[1] = string[1][:-1] if self.latitude: if self.negative: string[0] = "-" + string[0] else: string[0] = "+" + string[0] return string def __str__(self): return self.angle.__str__() def __repr__(self): return self.angle.__repr__() def __add__(self, other): s = self.angle[2] + other.angle[2] m = self.angle[1] + other.angle[1] d = self.angle[0] + other.angle[0] s = Angle(sexagesimal=(d, m, s), latitude=self.latitude) s._simplify() return s def __mul__(self, other): d, m, s = self.angle s = s * other m = m * other d = d * other if self.latitude and self.negative: d, m, s = -d, -m, -s s = Angle(sexagesimal=(d, m, s), latitude=self.latitude) s._simplify() return s def __eq__(self, other): return self.angle[0] == other.angle[0] \ and self.angle[1] == other.angle[1] \ and almost_equal(self.angle[2], other.angle[2]) def __div__(self, other): ''' Divide an angle by another This method calculates the division using the angles in degrees, and then corrects for any rounding errors if the division should be exact. ''' # Find division of angles in degrees div = self.todegrees() / other.todegrees() # Find the nearest integer divint = int(round(div)) # Check whether the denominator multiplied by this number is exactly # the numerator if other * divint == self: return divint else: return div __truediv__ = __div__ def smart_round_angle_sexagesimal(x, latitude=False, hours=False): d, m, s = 0, 0, 0. divisors_360 = math_util.divisors(360) divisors_10 = math_util.divisors(10) divisors_60 = math_util.divisors(60) if hours: x /= 15. if x >= 1: d = math_util.closest(divisors_360, x) else: x = x * 60. if x >= 1: m = math_util.closest(divisors_60, x) else: x = x * 60. if x >= 1: s = math_util.closest(divisors_60, x) else: t = 1. while True: t = t * 10. x = x * 10. if x >= 1: s = math_util.closest(divisors_10, x) / t break a = Angle(sexagesimal=(d, m, s), latitude=latitude) if hours: a *= 15 return a def smart_round_angle_decimal(x, latitude=False): divisors_360 = math_util.divisors(360) divisors_10 = math_util.divisors(10) if x >= 1: d = math_util.closest(divisors_360, x) else: t = 1. while True: t = t * 10. x = x * 10. if x >= 1: d = math_util.closest(divisors_10, x) / t break a = Angle(degrees=d, latitude=latitude) return a def _get_label_precision(format, latitude=False): # Find base spacing if "mm" in format: if "ss" in format: if "ss.s" in format: n_decimal = len(format.split('.')[1]) label_spacing = Angle(sexagesimal=(0, 0, 10 ** (-n_decimal)), latitude=latitude) else: label_spacing = Angle(sexagesimal=(0, 0, 1), latitude=latitude) else: label_spacing = Angle(sexagesimal=(0, 1, 0), latitude=latitude) elif "." in format: ns = len(format.split('.')[1]) label_spacing = Angle(degrees=10 ** (-ns), latitude=latitude) else: label_spacing = Angle(sexagesimal=(1, 0, 0), latitude=latitude) # Check if hours are used instead of degrees if "hh" in format: label_spacing *= 15 return label_spacing class InconsistentSpacing(Exception): pass def _check_format_spacing_consistency(format, spacing): ''' Check whether the format can correctly show labels with the specified spacing. For example, if the tick spacing is set to 1 arcsecond, but the format is set to dd:mm, then the labels cannot be correctly shown. Similarly, if the spacing is set to 1/1000 of a degree, or 3.6", then a format of dd:mm:ss will cause rounding errors, because the spacing includes fractional arcseconds. This function will raise a warning if the format and spacing are inconsistent. ''' label_spacing = _get_label_precision(format) if type(spacing / label_spacing) != int: raise InconsistentSpacing('Label format and tick spacing are inconsistent. Make sure that the tick spacing is a multiple of the smallest angle that can be represented by the specified format (currently %s). For example, if the format is dd:mm:ss.s, then the tick spacing has to be a multiple of 0.1". Similarly, if the format is hh:mm:ss, then the tick spacing has to be a multiple of 15". If you got this error as a result of interactively zooming in to a small region, this means that the default display format for the labels is not accurate enough, so you will need to increase the format precision.' % format) APLpy-1.1.1/aplpy/axis_labels.py0000644000077000000240000001557412771205772016430 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division from matplotlib.font_manager import FontProperties from . import wcs_util from .decorators import auto_refresh, fixdocstring class AxisLabels(object): def __init__(self, parent): # Store references to axes self._ax1 = parent._ax1 self._ax2 = parent._ax2 self._wcs = parent._wcs self._figure = parent._figure # Save plotting parameters (required for @auto_refresh) self._parameters = parent._parameters # Set font self._label_fontproperties = FontProperties() self._ax2.yaxis.set_label_position('right') self._ax2.xaxis.set_label_position('top') system, equinox, units = wcs_util.system(self._wcs) if system['name'] == 'equatorial': if equinox == 'b1950': xtext = 'RA (B1950)' ytext = 'Dec (B1950)' else: xtext = 'RA (J2000)' ytext = 'Dec (J2000)' elif system['name'] == 'galactic': xtext = 'Galactic Longitude' ytext = 'Galactic Latitude' elif system['name'] == 'ecliptic': xtext = 'Ecliptic Longitude' ytext = 'Ecliptic Latitude' elif system['name'] == 'unknown': xunit = " (%s)" % self._wcs.cunit_x if self._wcs.cunit_x not in ["", None] else "" yunit = " (%s)" % self._wcs.cunit_y if self._wcs.cunit_y not in ["", None] else "" if len(self._wcs.cname_x) > 0: xtext = self._wcs.cname_x + xunit else: if len(self._wcs.ctype_x) == 8 and self._wcs.ctype_x[4] == '-': xtext = self._wcs.ctype_x[:4].replace('-', '') + xunit else: xtext = self._wcs.ctype_x + xunit if len(self._wcs.cname_y) > 0: ytext = self._wcs.cname_y + yunit else: if len(self._wcs.ctype_y) == 8 and self._wcs.ctype_y[4] == '-': ytext = self._wcs.ctype_y[:4].replace('-', '') + yunit else: ytext = self._wcs.ctype_y + yunit if system['inverted']: xtext, ytext = ytext, xtext self.set_xtext(xtext) self.set_ytext(ytext) self.set_xposition('bottom') self.set_yposition('left') @auto_refresh def set_xtext(self, label): """ Set the x-axis label text. """ self._xlabel1 = self._ax1.set_xlabel(label) self._xlabel2 = self._ax2.set_xlabel(label) @auto_refresh def set_ytext(self, label): """ Set the y-axis label text. """ self._ylabel1 = self._ax1.set_ylabel(label) self._ylabel2 = self._ax2.set_ylabel(label) @auto_refresh def set_xpad(self, pad): """ Set the x-axis label displacement, in points. """ self._xlabel1 = self._ax1.set_xlabel(self._xlabel1.get_text(), labelpad=pad) self._xlabel2 = self._ax2.set_xlabel(self._xlabel2.get_text(), labelpad=pad) @auto_refresh def set_ypad(self, pad): """ Set the y-axis label displacement, in points. """ self._ylabel1 = self._ax1.set_ylabel(self._ylabel1.get_text(), labelpad=pad) self._ylabel2 = self._ax2.set_ylabel(self._ylabel2.get_text(), labelpad=pad) @auto_refresh @fixdocstring def set_font(self, family=None, style=None, variant=None, stretch=None, weight=None, size=None, fontproperties=None): """ Set the font of the axis labels. Parameters ---------- common: family, style, variant, stretch, weight, size, fontproperties Notes ----- Default values are set by matplotlib or previously set values if set_font has already been called. Global default values can be set by editing the matplotlibrc file. """ if family: self._label_fontproperties.set_family(family) if style: self._label_fontproperties.set_style(style) if variant: self._label_fontproperties.set_variant(variant) if stretch: self._label_fontproperties.set_stretch(stretch) if weight: self._label_fontproperties.set_weight(weight) if size: self._label_fontproperties.set_size(size) if fontproperties: self._label_fontproperties = fontproperties self._xlabel1.set_fontproperties(self._label_fontproperties) self._xlabel2.set_fontproperties(self._label_fontproperties) self._ylabel1.set_fontproperties(self._label_fontproperties) self._ylabel2.set_fontproperties(self._label_fontproperties) @auto_refresh def show(self): """ Show the x- and y-axis labels. """ self.show_x() self.show_y() @auto_refresh def hide(self): """ Hide the x- and y-axis labels. """ self.hide_x() self.hide_y() @auto_refresh def show_x(self): """ Show the x-axis label. """ if self._xposition == 'bottom': self._xlabel1.set_visible(True) else: self._xlabel2.set_visible(True) @auto_refresh def hide_x(self): """ Hide the x-axis label. """ if self._xposition == 'bottom': self._xlabel1.set_visible(False) else: self._xlabel2.set_visible(False) @auto_refresh def show_y(self): """ Show the y-axis label. """ if self._yposition == 'left': self._ylabel1.set_visible(True) else: self._ylabel2.set_visible(True) @auto_refresh def hide_y(self): """ Hide the y-axis label. """ if self._yposition == 'left': self._ylabel1.set_visible(False) else: self._ylabel2.set_visible(False) @auto_refresh def set_xposition(self, position): "Set the position of the x-axis label ('top' or 'bottom')" if position == 'bottom': self._xlabel1.set_visible(True) self._xlabel2.set_visible(False) elif position == 'top': self._xlabel1.set_visible(False) self._xlabel2.set_visible(True) else: raise ValueError("position should be one of 'top' or 'bottom'") self._xposition = position @auto_refresh def set_yposition(self, position): "Set the position of the y-axis label ('left' or 'right')" if position == 'left': self._ylabel1.set_visible(True) self._ylabel2.set_visible(False) elif position == 'right': self._ylabel1.set_visible(False) self._ylabel2.set_visible(True) else: raise ValueError("position should be one of 'left' or 'right'") self._yposition = position APLpy-1.1.1/aplpy/colorbar.py0000644000077000000240000003612612536551501015732 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import warnings import matplotlib.axes as maxes from mpl_toolkits.axes_grid import make_axes_locatable from matplotlib.font_manager import FontProperties from matplotlib.ticker import LogFormatterMathtext from .decorators import auto_refresh, fixdocstring # As of matplotlib 0.99.1.1, any time a colorbar property is updated, the axes # need to be removed and re-created. This has been fixed in svn r8213 but we # should wait until we up the required version of matplotlib before changing the # code here class Colorbar(object): def __init__(self, parent): self._figure = parent._figure self._colorbar_axes = None self._parent = parent # Save plotting parameters (required for @auto_refresh) self._parameters = parent._parameters self._base_settings = {} self._ticklabel_fontproperties = FontProperties() self._axislabel_fontproperties = FontProperties() @auto_refresh def show(self, location='right', width=0.2, pad=0.05, ticks=None, labels=True, log_format=False, box=None, box_orientation='vertical', axis_label_text=None, axis_label_rotation=None, axis_label_pad=5): ''' Show a colorbar on the side of the image. Parameters ---------- location : str, optional Where to place the colorbar. Should be one of 'left', 'right', 'top', 'bottom'. width : float, optional The width of the colorbar relative to the canvas size. pad : float, optional The spacing between the colorbar and the image relative to the canvas size. ticks : list, optional The position of the ticks on the colorbar. labels : bool, optional Whether to show numerical labels. log_format : bool, optional Whether to format ticks in exponential notation box : list, optional A custom box within which to place the colorbar. This should be in the form [xmin, ymin, dx, dy] and be in relative figure units. This overrides the location argument. box_orientation str, optional The orientation of the colorbar within the box. Can be 'horizontal' or 'vertical' axis_label_text str, optional Optional text label of the colorbar. ''' self._base_settings['location'] = location self._base_settings['width'] = width self._base_settings['pad'] = pad self._base_settings['ticks'] = ticks self._base_settings['labels'] = labels self._base_settings['log_format'] = log_format self._base_settings['box'] = box self._base_settings['box_orientation'] = box_orientation self._base_settings['axis_label_text'] = axis_label_text self._base_settings['axis_label_rotation'] = axis_label_rotation self._base_settings['axis_label_pad'] = axis_label_pad if self._parent.image: if self._colorbar_axes: self._parent._figure.delaxes(self._colorbar_axes) if box is None: divider = make_axes_locatable(self._parent._ax1) if location == 'right': self._colorbar_axes = divider.new_horizontal(size=width, pad=pad, axes_class=maxes.Axes) orientation = 'vertical' elif location == 'top': self._colorbar_axes = divider.new_vertical(size=width, pad=pad, axes_class=maxes.Axes) orientation = 'horizontal' elif location == 'left': warnings.warn("Left colorbar not fully implemented") self._colorbar_axes = divider.new_horizontal(size=width, pad=pad, pack_start=True, axes_class=maxes.Axes) locator = divider.new_locator(nx=0, ny=0) self._colorbar_axes.set_axes_locator(locator) orientation = 'vertical' elif location == 'bottom': warnings.warn("Bottom colorbar not fully implemented") self._colorbar_axes = divider.new_vertical(size=width, pad=pad, pack_start=True, axes_class=maxes.Axes) locator = divider.new_locator(nx=0, ny=0) self._colorbar_axes.set_axes_locator(locator) orientation = 'horizontal' else: raise Exception("location should be one of: right/top") self._parent._figure.add_axes(self._colorbar_axes) else: self._colorbar_axes = self._parent._figure.add_axes(box) orientation = box_orientation if log_format: format=LogFormatterMathtext() else: format=None self._colorbar = self._parent._figure.colorbar(self._parent.image, cax=self._colorbar_axes, orientation=orientation, format=format, ticks=ticks) if axis_label_text: if axis_label_rotation: self._colorbar.set_label(axis_label_text, rotation=axis_label_rotation) else: self._colorbar.set_label(axis_label_text) if location == 'right': for tick in self._colorbar_axes.yaxis.get_major_ticks(): tick.tick1On = True tick.tick2On = True tick.label1On = False tick.label2On = labels self._colorbar_axes.yaxis.set_label_position('right') self._colorbar_axes.yaxis.labelpad = axis_label_pad elif location == 'top': for tick in self._colorbar_axes.xaxis.get_major_ticks(): tick.tick1On = True tick.tick2On = True tick.label1On = False tick.label2On = labels self._colorbar_axes.xaxis.set_label_position('top') self._colorbar_axes.xaxis.labelpad = axis_label_pad elif location == 'left': for tick in self._colorbar_axes.yaxis.get_major_ticks(): tick.tick1On = True tick.tick2On = True tick.label1On = labels tick.label2On = False self._colorbar_axes.yaxis.set_label_position('left') self._colorbar_axes.yaxis.labelpad = axis_label_pad elif location == 'bottom': for tick in self._colorbar_axes.xaxis.get_major_ticks(): tick.tick1On = True tick.tick2On = True tick.label1On = labels tick.label2On = False self._colorbar_axes.xaxis.set_label_position('bottom') self._colorbar_axes.xaxis.labelpad = axis_label_pad else: warnings.warn("No image is shown, therefore, no colorbar will be plotted") @auto_refresh def update(self): if self._colorbar_axes: self.show(**self._base_settings) @auto_refresh def hide(self): self._parent._figure.delaxes(self._colorbar_axes) self._colorbar_axes = None @auto_refresh def _remove(self): self._parent._figure.delaxes(self._colorbar_axes) # LOCATION AND SIZE @auto_refresh def set_location(self, location): ''' Set the location of the colorbar. Should be one of 'left', 'right', 'top', 'bottom'. ''' self._base_settings['location'] = location self.show(**self._base_settings) self.set_font(fontproperties=self._ticklabel_fontproperties) self.set_axis_label_font(fontproperties=self._axislabel_fontproperties) @auto_refresh def set_width(self, width): ''' Set the width of the colorbar relative to the canvas size. ''' self._base_settings['width'] = width self.show(**self._base_settings) self.set_font(fontproperties=self._ticklabel_fontproperties) self.set_axis_label_font(fontproperties=self._axislabel_fontproperties) @auto_refresh def set_pad(self, pad): ''' Set the spacing between the colorbar and the image relative to the canvas size. ''' self._base_settings['pad'] = pad self.show(**self._base_settings) self.set_font(fontproperties=self._ticklabel_fontproperties) self.set_axis_label_font(fontproperties=self._axislabel_fontproperties) @auto_refresh def set_ticks(self, ticks): ''' Set the position of the ticks on the colorbar. ''' self._base_settings['ticks'] = ticks self.show(**self._base_settings) self.set_font(fontproperties=self._ticklabel_fontproperties) self.set_axis_label_font(fontproperties=self._axislabel_fontproperties) @auto_refresh def set_labels(self, labels): ''' Set whether to show numerical labels. ''' self._base_settings['labels'] = labels self.show(**self._base_settings) self.set_font(fontproperties=self._ticklabel_fontproperties) self.set_axis_label_font(fontproperties=self._axislabel_fontproperties) @auto_refresh def set_box(self, box, box_orientation='vertical'): ''' Set the box within which to place the colorbar. This should be in the form [xmin, ymin, dx, dy] and be in relative figure units. The orientation of the colorbar within the box can be controlled with the box_orientation argument. ''' self._base_settings['box'] = box self._base_settings['box_orientation'] = box_orientation self.show(**self._base_settings) self.set_font(fontproperties=self._ticklabel_fontproperties) self.set_axis_label_font(fontproperties=self._axislabel_fontproperties) @auto_refresh def set_axis_label_text(self, axis_label_text): ''' Set the colorbar label text. ''' self._base_settings['axis_label_text'] = axis_label_text self.show(**self._base_settings) self.set_font(fontproperties=self._ticklabel_fontproperties) self.set_axis_label_font(fontproperties=self._axislabel_fontproperties) @auto_refresh def set_axis_label_rotation(self, axis_label_rotation): ''' Set the colorbar label rotation. ''' self._base_settings['axis_label_rotation'] = axis_label_rotation self.show(**self._base_settings) self.set_font(fontproperties=self._ticklabel_fontproperties) self.set_axis_label_font(fontproperties=self._axislabel_fontproperties) @auto_refresh def set_axis_label_pad(self, axis_label_pad): ''' Set the colorbar label displacement, in points. ''' self._base_settings['axis_label_pad'] = axis_label_pad self.show(**self._base_settings) self.set_font(fontproperties=self._ticklabel_fontproperties) self.set_axis_label_font(fontproperties=self._axislabel_fontproperties) # FONT PROPERTIES @auto_refresh def set_label_properties(self, *args, **kwargs): warnings.warn("set_label_properties is deprecated - use set_font instead", DeprecationWarning) self.set_font(*args, **kwargs) @auto_refresh @fixdocstring def set_font(self, family=None, style=None, variant=None, stretch=None, weight=None, size=None, fontproperties=None): ''' Set the font of the tick labels. Parameters ---------- common: family, style, variant, stretch, weight, size, fontproperties Notes ----- Default values are set by matplotlib or previously set values if set_font has already been called. Global default values can be set by editing the matplotlibrc file. ''' if family: self._ticklabel_fontproperties.set_family(family) if style: self._ticklabel_fontproperties.set_style(style) if variant: self._ticklabel_fontproperties.set_variant(variant) if stretch: self._ticklabel_fontproperties.set_stretch(stretch) if weight: self._ticklabel_fontproperties.set_weight(weight) if size: self._ticklabel_fontproperties.set_size(size) if fontproperties: self._ticklabel_fontproperties = fontproperties # Update the tick label font properties for label in self._colorbar_axes.get_xticklabels(): label.set_fontproperties(self._ticklabel_fontproperties) for label in self._colorbar_axes.get_yticklabels(): label.set_fontproperties(self._ticklabel_fontproperties) # Also update the offset text font properties label = self._colorbar_axes.xaxis.get_offset_text() label.set_fontproperties(self._ticklabel_fontproperties) label = self._colorbar_axes.yaxis.get_offset_text() label.set_fontproperties(self._ticklabel_fontproperties) @auto_refresh @fixdocstring def set_axis_label_font(self, family=None, style=None, variant=None, stretch=None, weight=None, size=None, fontproperties=None): ''' Set the font of the tick labels. Parameters ---------- common: family, style, variant, stretch, weight, size, fontproperties Notes ----- Default values are set by matplotlib or previously set values if set_font has already been called. Global default values can be set by editing the matplotlibrc file. ''' if family: self._axislabel_fontproperties.set_family(family) if style: self._axislabel_fontproperties.set_style(style) if variant: self._axislabel_fontproperties.set_variant(variant) if stretch: self._axislabel_fontproperties.set_stretch(stretch) if weight: self._axislabel_fontproperties.set_weight(weight) if size: self._axislabel_fontproperties.set_size(size) if fontproperties: self._axislabel_fontproperties = fontproperties # Update the label font properties label = self._colorbar_axes.xaxis.get_label() label.set_fontproperties(self._axislabel_fontproperties) label = self._colorbar_axes.yaxis.get_label() label.set_fontproperties(self._axislabel_fontproperties) # FRAME PROPERTIES @auto_refresh def set_frame_linewidth(self, linewidth): ''' Set the linewidth of the colorbar frame, in points. ''' warnings.warn("This method is not functional at this time") for key in self._colorbar_axes.spines: self._colorbar_axes.spines[key].set_linewidth(linewidth) @auto_refresh def set_frame_color(self, color): ''' Set the color of the colorbar frame, in points. ''' warnings.warn("This method is not functional at this time") for key in self._colorbar_axes.spines: self._colorbar_axes.spines[key].set_edgecolor(color) APLpy-1.1.1/aplpy/conftest.py0000644000077000000240000000265312774746217015770 0ustar tomstaff00000000000000# this contains imports plugins that configure py.test for astropy tests. # by importing them here in conftest.py they are discoverable by py.test # no matter how it is invoked within the source tree. from astropy.tests.pytest_plugins import * from astropy.tests.pytest_plugins import pytest_addoption as astropy_pytest_addoption # This is to figure out APLpy version, rather than using Astropy's from . import version import matplotlib matplotlib.use('Agg') try: packagename = os.path.basename(os.path.dirname(__file__)) TESTED_VERSIONS[packagename] = version.version except NameError: pass # Uncomment the following line to treat all DeprecationWarnings as # exceptions enable_deprecations_as_exceptions() from astropy.tests.helper import pytest def pytest_addoption(parser): parser.addoption('--generate-images-path', help="directory to generate reference images in", action='store') return astropy_pytest_addoption(parser) @pytest.fixture def generate(request): return request.config.getoption("--generate-images-path") # Add astropy to test header information and remove unused packages. try: PYTEST_HEADER_MODULES['Astropy'] = 'astropy' PYTEST_HEADER_MODULES['pyregion'] = 'pyregion' PYTEST_HEADER_MODULES['PyAVM'] = 'PyAVM' PYTEST_HEADER_MODULES['montage-wrapper'] = 'montage-wrapper' del PYTEST_HEADER_MODULES['h5py'] except NameError: pass APLpy-1.1.1/aplpy/contour_util.py0000644000077000000240000000255312771205772016661 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import numpy as np from matplotlib.path import Path from . import wcs_util def transform(contours, wcs_in, wcs_out, filled=False, overlap=False): system_in, equinox_in, units_in = wcs_util.system(wcs_in) system_out, equinox_out, units_out = wcs_util.system(wcs_out) for contour in contours.collections: polygons_out = [] for polygon in contour.get_paths(): xp_in = polygon.vertices[:, 0] yp_in = polygon.vertices[:, 1] xw, yw = wcs_util.pix2world(wcs_in, xp_in, yp_in) xw, yw = wcs_util.convert_coords(xw, yw, input=(system_in, equinox_in), output=(system_out, equinox_out)) xp_out, yp_out = wcs_util.world2pix(wcs_out, xw, yw) if overlap: if np.all(xp_out < 0) or np.all(yp_out < 0) or \ np.all(xp_out > wcs_out.nx) or np.all(yp_out > wcs_out.ny): continue if filled: polygons_out.append(Path(np.array(list(zip(xp_out, yp_out))), codes=polygon.codes)) else: polygons_out.append(list(zip(xp_out, yp_out))) if filled: contour.set_paths(polygons_out) else: contour.set_verts(polygons_out) contour.apl_converted = True APLpy-1.1.1/aplpy/convolve_util.py0000644000077000000240000000265312536551501017015 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import numpy as np try: from astropy.convolution import convolve as astropy_convolve, Gaussian2DKernel, Box2DKernel make_kernel = None except ImportError: from astropy.nddata import convolve as astropy_convolve, make_kernel def convolve(image, smooth=3, kernel='gauss'): if smooth is None and kernel in ['box', 'gauss']: return image if smooth is not None and not np.isscalar(smooth): raise ValueError("smooth= should be an integer - for more complex " "kernels, pass an array containing the kernel " "to the kernel= option") # The Astropy convolution doesn't treat +/-Inf values correctly yet, so we # convert to NaN here. image_fixed = np.array(image, dtype=float, copy=True) image_fixed[np.isinf(image)] = np.nan if kernel == 'gauss': if make_kernel is None: kernel = Gaussian2DKernel(smooth, x_size=smooth * 5, y_size=smooth * 5) else: kernel = make_kernel((smooth * 5, smooth * 5), smooth, 'gaussian') elif kernel == 'box': if make_kernel is None: kernel = Box2DKernel(smooth, x_size=smooth * 5, y_size=smooth * 5) else: kernel = make_kernel((smooth * 5, smooth * 5), smooth, 'boxcar') else: kernel = kernel return astropy_convolve(image, kernel, boundary='extend') APLpy-1.1.1/aplpy/core.py0000644000077000000240000021644312774746217015077 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division from distutils import version import os import operator from functools import reduce import matplotlib if version.LooseVersion(matplotlib.__version__) < version.LooseVersion('1.0.0'): raise Exception("matplotlib 1.0.0 or later is required for APLpy") import matplotlib.pyplot as mpl import mpl_toolkits.axes_grid.parasite_axes as mpltk from astropy.extern import six from astropy.wcs import WCS from astropy.io import fits HDU_TYPES = (fits.PrimaryHDU, fits.ImageHDU, fits.CompImageHDU) import numpy as np from matplotlib.patches import Circle, Rectangle, Ellipse, Polygon, FancyArrow from matplotlib.collections import PatchCollection, LineCollection from astropy import log import astropy.utils.exceptions as aue from . import contour_util from . import convolve_util from . import image_util from . import header as header_util from . import wcs_util from . import slicer from .layers import Layers from .grid import Grid from .ticks import Ticks from .labels import TickLabels from .axis_labels import AxisLabels from .overlays import Beam, Scalebar from .regions import Regions from .colorbar import Colorbar from .normalize import APLpyNormalize from .frame import Frame from .decorators import auto_refresh, fixdocstring from .deprecated import Deprecated class Parameters(): ''' A class to contain the current plotting parameters ''' pass class FITSFigure(Layers, Regions, Deprecated): "A class for plotting FITS files." _parameters = Parameters() @auto_refresh def __init__(self, data, hdu=0, figure=None, subplot=(1, 1, 1), downsample=False, north=False, convention=None, dimensions=[0, 1], slices=[], auto_refresh=None, **kwargs): ''' Create a FITSFigure instance. Parameters ---------- data : see below The FITS file to open. The following data types can be passed: string astropy.io.fits.PrimaryHDU astropy.io.fits.ImageHDU astropy.wcs.WCS np.ndarray RGB image with AVM meta-data hdu : int, optional By default, the image in the primary HDU is read in. If a different HDU is required, use this argument. figure : ~matplotlib.figure.Figure, optional If specified, a subplot will be added to this existing matplotlib figure() instance, rather than a new figure being created from scratch. subplot : tuple or list, optional If specified, a subplot will be added at this position. If a tuple of three values, the tuple should contain the standard matplotlib subplot parameters, i.e. (ny, nx, subplot). If a list of four values, the list should contain [xmin, ymin, dx, dy] where xmin and ymin are the position of the bottom left corner of the subplot, and dx and dy are the width and height of the subplot respectively. These should all be given in units of the figure width and height. For example, [0.1, 0.1, 0.8, 0.8] will almost fill the entire figure, leaving a 10 percent margin on all sides. downsample : int, optional If this option is specified, the image will be downsampled by a factor *downsample* when reading in the data. north : str, optional Whether to rotate the image so that the North Celestial Pole is up. Note that this option requires Montage to be installed. convention : str, optional This is used in cases where a FITS header can be interpreted in multiple ways. For example, for files with a -CAR projection and CRVAL2=0, this can be set to 'wells' or 'calabretta' to choose the appropriate convention. dimensions : tuple or list, optional The index of the axes to use if the data has more than three dimensions. slices : tuple or list, optional If a FITS file with more than two dimensions is specified, then these are the slices to extract. If all extra dimensions only have size 1, then this is not required. auto_refresh : bool, optional Whether to refresh the figure automatically every time a plotting method is called. This can also be set using the set_auto_refresh method. This defaults to `True` if and only if APLpy is being used from IPython and the Matplotlib backend is interactive. kwargs Any additional arguments are passed on to matplotlib's Figure() class. For example, to set the figure size, use the figsize=(xsize, ysize) argument (where xsize and ysize are in inches). For more information on these additional arguments, see the *Optional keyword arguments* section in the documentation for `Figure `_ ''' # Set whether to automatically refresh the display self.set_auto_refresh(auto_refresh) if not 'figsize' in kwargs: kwargs['figsize'] = (10, 9) if isinstance(data, six.string_types) and data.split('.')[-1].lower() in ['png', 'jpg', 'tif']: try: from PIL import Image except ImportError: try: import Image except ImportError: raise ImportError("The Python Imaging Library (PIL) is required to read in RGB images") try: import pyavm except ImportError: raise ImportError("PyAVM is required to read in AVM meta-data from RGB images") if version.LooseVersion(pyavm.__version__) < version.LooseVersion('0.9.1'): raise ImportError("PyAVM installation is not recent enough " "(version 0.9.1 or later is required).") from pyavm import AVM # Remember image filename self._rgb_image = data # Find image size nx, ny = Image.open(data).size # Now convert AVM information to WCS data = AVM.from_image(data).to_wcs() # Need to scale CDELT values sometimes the AVM meta-data is only really valid for the full-resolution image data.wcs.cdelt = [data.wcs.cdelt[0] * nx / float(nx), data.wcs.cdelt[1] * ny / float(ny)] data.wcs.crpix = [data.wcs.crpix[0] / nx * float(nx), data.wcs.crpix[1] / ny * float(ny)] # Update the NAXIS values with the true dimensions of the RGB image data.nx = nx data.ny = ny if isinstance(data, WCS): wcs = data if not hasattr(wcs, 'naxis1'): raise aue.AstropyDeprecationWarning('WCS no longer stores information about NAXISn ' 'so it is not possibly to instantiate a FITSFigure ' 'from WCS alone') if wcs.naxis != 2: raise ValueError("FITSFigure initialization via WCS objects can only be done with 2-dimensional WCS objects") header = wcs.to_header() header['NAXIS1'] = wcs.naxis1 header['NAXIS2'] = wcs.naxis2 nx = header['NAXIS%i' % (dimensions[0] + 1)] ny = header['NAXIS%i' % (dimensions[1] + 1)] self._data = np.zeros((ny, nx), dtype=float) self._header = header self._wcs = wcs_util.WCS(header, dimensions=dimensions, slices=slices, relax=True) self._wcs.nx = nx self._wcs.ny = ny if downsample: log.warning("downsample argument is ignored if data passed is a WCS object") downsample = False if north: log.warning("north argument is ignored if data passed is a WCS object") north = False else: self._data, self._header, self._wcs = self._get_hdu(data, hdu, north, \ convention=convention, dimensions=dimensions, slices=slices) self._wcs.nx = self._header['NAXIS%i' % (dimensions[0] + 1)] self._wcs.ny = self._header['NAXIS%i' % (dimensions[1] + 1)] # Downsample if requested if downsample: nx_new = self._wcs.nx - np.mod(self._wcs.nx, downsample) ny_new = self._wcs.ny - np.mod(self._wcs.ny, downsample) self._data = self._data[0:ny_new, 0:nx_new] self._data = image_util.resample(self._data, downsample) self._wcs.nx, self._wcs.ny = nx_new, ny_new # Open the figure if figure: self._figure = figure else: self._figure = mpl.figure(**kwargs) # Create first axis instance if type(subplot) == list and len(subplot) == 4: self._ax1 = mpltk.HostAxes(self._figure, subplot, adjustable='datalim') elif type(subplot) == tuple and len(subplot) == 3: self._ax1 = mpltk.SubplotHost(self._figure, *subplot) else: raise ValueError("subplot= should be either a tuple of three values, or a list of four values") self._ax1.toggle_axisline(False) self._figure.add_axes(self._ax1) # Create second axis instance self._ax2 = self._ax1.twin() self._ax2.set_frame_on(False) self._ax2.toggle_axisline(False) # Turn off autoscaling self._ax1.set_autoscale_on(False) self._ax2.set_autoscale_on(False) # Force zorder of parasite axes self._ax2.xaxis.set_zorder(2.5) self._ax2.yaxis.set_zorder(2.5) # Store WCS in axes self._ax1._wcs = self._wcs self._ax2._wcs = self._wcs # Set view to whole FITS file self._initialize_view() # Initialize ticks self.ticks = Ticks(self) # Initialize labels self.axis_labels = AxisLabels(self) self.tick_labels = TickLabels(self) self.frame = Frame(self) self._ax1.format_coord = self.tick_labels._cursor_position # Initialize layers list self._initialize_layers() # Find generating function for vmin/vmax self._auto_v = image_util.percentile_function(self._data) # Set image holder to be empty self.image = None # Set default theme self.set_theme(theme='pretty') def _get_hdu(self, data, hdu, north, convention=None, dimensions=[0, 1], slices=[]): if isinstance(data, six.string_types): filename = data # Check file exists if not os.path.exists(filename): raise IOError("File not found: " + filename) # Read in FITS file try: hdulist = fits.open(filename) except: raise IOError("An error occurred while reading the FITS file") # Check whether the HDU specified contains any data, otherwise # cycle through all HDUs to find one that contains valid image data if hdulist[hdu].data is None: found = False for alt_hdu in range(len(hdulist)): if isinstance(hdulist[alt_hdu], HDU_TYPES): if hdulist[alt_hdu].data is not None: log.warning("hdu=%i does not contain any data, using hdu=%i instead" % (hdu, alt_hdu)) hdu = hdulist[alt_hdu] found = True break if not found: raise Exception("FITS file does not contain any image data") else: hdu = hdulist[hdu] elif type(data) == np.ndarray: hdu = fits.ImageHDU(data) elif isinstance(data, HDU_TYPES): hdu = data elif isinstance(data, fits.HDUList): hdu = data[hdu] else: raise Exception("data argument should either be a filename, an HDU object from astropy.io.fits, a WCS object from astropy.wcs, or a Numpy array.") # Check that we have at least 2-dimensional data if hdu.header['NAXIS'] < 2: raise ValueError("Data should have at least two dimensions") # Check dimensions= argument if type(dimensions) not in [list, tuple]: raise ValueError('dimensions= should be a list or a tuple') if len(set(dimensions)) != 2 or len(dimensions) != 2: raise ValueError("dimensions= should be a tuple of two different values") if dimensions[0] < 0 or dimensions[0] > hdu.header['NAXIS'] - 1: raise ValueError('values of dimensions= should be between %i and %i' % (0, hdu.header['NAXIS'] - 1)) if dimensions[1] < 0 or dimensions[1] > hdu.header['NAXIS'] - 1: raise ValueError('values of dimensions= should be between %i and %i' % (0, hdu.header['NAXIS'] - 1)) # Reproject to face north if requested if north: try: import montage_wrapper as montage except ImportError: raise Exception("Both the Montage command-line tools and the" " montage-wrapper Python module are required" " to use the north= argument") hdu = montage.reproject_hdu(hdu, north_aligned=True) # Now copy the data and header to new objects, since in PyFITS the two # attributes are linked, which can lead to confusing behavior. We just # need to copy the header to avoid memory issues - as long as one item # is copied, the two variables are decoupled. data = hdu.data header = hdu.header.copy() del hdu # If slices wasn't specified, check if we can guess shape = data.shape if len(shape) > 2: n_total = reduce(operator.mul, shape) n_image = shape[len(shape) - 1 - dimensions[0]] \ * shape[len(shape) - 1 - dimensions[1]] if n_total == n_image: slices = [0 for i in range(1, len(shape) - 1)] log.info("Setting slices=%s" % str(slices)) # Extract slices data = slicer.slice_hypercube(data, header, dimensions=dimensions, slices=slices) # Check header header = header_util.check(header, convention=convention, dimensions=dimensions) # Parse WCS info wcs = wcs_util.WCS(header, dimensions=dimensions, slices=slices, relax=True) return data, header, wcs @auto_refresh def set_title(self, title, **kwargs): ''' Set the figure title ''' self._ax1.set_title(title, **kwargs) @auto_refresh def set_xaxis_coord_type(self, coord_type): ''' Set the type of x coordinate. Options are: * ``scalar``: treat the values are normal decimal scalar values * ``longitude``: treat the values as a longitude in the 0 to 360 range * ``latitude``: treat the values as a latitude in the -90 to 90 range ''' self._wcs.set_xaxis_coord_type(coord_type) @auto_refresh def set_yaxis_coord_type(self, coord_type): ''' Set the type of y coordinate. Options are: * ``scalar``: treat the values are normal decimal scalar values * ``longitude``: treat the values as a longitude in the 0 to 360 range * ``latitude``: treat the values as a latitude in the -90 to 90 range ''' self._wcs.set_yaxis_coord_type(coord_type) @auto_refresh def set_system_latex(self, usetex): ''' Set whether to use a real LaTeX installation or the built-in matplotlib LaTeX. Parameters ---------- usetex : str Whether to use a real LaTex installation (True) or the built-in matplotlib LaTeX (False). Note that if the former is chosen, an installation of LaTex is required. ''' mpl.rc('text', usetex=usetex) @auto_refresh def recenter(self, x, y, radius=None, width=None, height=None): ''' Center the image on a given position and with a given radius. Either the radius or width/height arguments should be specified. The units of the radius or width/height should be the same as the world coordinates in the WCS. For images of the sky, this is often (but not always) degrees. Parameters ---------- x, y : float Coordinates to center on radius : float, optional Radius of the region to view in degrees. This produces a square plot. width : float, optional Width of the region to view. This should be given in conjunction with the height argument. height : float, optional Height of the region to view. This should be given in conjunction with the width argument. ''' xpix, ypix = wcs_util.world2pix(self._wcs, x, y) if self._wcs.is_celestial: sx = sy = wcs_util.celestial_pixel_scale(self._wcs) else: sx, sy = wcs_util.non_celestial_pixel_scales(self._wcs) if radius: dx_pix = radius / sx dy_pix = radius / sy elif width and height: dx_pix = width / sx * 0.5 dy_pix = height / sy * 0.5 else: raise Exception("Need to specify either radius= or width= and height= arguments") if xpix + dx_pix < self._extent[0] or \ xpix - dx_pix > self._extent[1] or \ ypix + dy_pix < self._extent[2] or \ ypix - dy_pix > self._extent[3]: raise Exception("Zoom region falls outside the image") self._ax1.set_xlim(xpix - dx_pix, xpix + dx_pix) self._ax1.set_ylim(ypix - dy_pix, ypix + dy_pix) @auto_refresh def show_grayscale(self, vmin=None, vmid=None, vmax=None, pmin=0.25, pmax=99.75, stretch='linear', exponent=2, invert='default', smooth=None, kernel='gauss', aspect='equal', interpolation='nearest'): ''' Show a grayscale image of the FITS file. Parameters ---------- vmin : None or float, optional Minimum pixel value to use for the grayscale. If set to None, the minimum pixel value is determined using pmin (default). vmax : None or float, optional Maximum pixel value to use for the grayscale. If set to None, the maximum pixel value is determined using pmax (default). pmin : float, optional Percentile value used to determine the minimum pixel value to use for the grayscale if vmin is set to None. The default value is 0.25%. pmax : float, optional Percentile value used to determine the maximum pixel value to use for the grayscale if vmax is set to None. The default value is 99.75%. stretch : { 'linear', 'log', 'sqrt', 'arcsinh', 'power' }, optional The stretch function to use vmid : None or float, optional Baseline value used for the log and arcsinh stretches. If set to None, this is set to zero for log stretches and to vmin - (vmax - vmin) / 30. for arcsinh stretches exponent : float, optional If stretch is set to 'power', this is the exponent to use invert : str, optional Whether to invert the grayscale or not. The default is False, unless set_theme is used, in which case the default depends on the theme. smooth : int or tuple, optional Default smoothing scale is 3 pixels across. User can define whether they want an NxN kernel (integer), or NxM kernel (tuple). This argument corresponds to the 'gauss' and 'box' smoothing kernels. kernel : { 'gauss', 'box', numpy.array }, optional Default kernel used for smoothing is 'gauss'. The user can specify if they would prefer 'gauss', 'box', or a custom kernel. All kernels are normalized to ensure flux retention. aspect : { 'auto', 'equal' }, optional Whether to change the aspect ratio of the image to match that of the axes ('auto') or to change the aspect ratio of the axes to match that of the data ('equal'; default) interpolation : str, optional The type of interpolation to use for the image. The default is 'nearest'. Other options include 'none' (no interpolation, meaning that if exported to a postscript file, the grayscale will be output at native resolution irrespective of the dpi setting), 'bilinear', 'bicubic', and many more (see the matplotlib documentation for imshow). ''' if invert == 'default': invert = self._get_invert_default() if invert: cmap = 'gist_yarg' else: cmap = 'gray' self.show_colorscale(vmin=vmin, vmid=vmid, vmax=vmax, pmin=pmin, pmax=pmax, stretch=stretch, exponent=exponent, cmap=cmap, smooth=smooth, kernel=kernel, aspect=aspect, interpolation=interpolation) @auto_refresh def hide_grayscale(self, *args, **kwargs): self.hide_colorscale(*args, **kwargs) @auto_refresh def show_colorscale(self, vmin=None, vmid=None, vmax=None, \ pmin=0.25, pmax=99.75, stretch='linear', exponent=2, cmap='default', smooth=None, kernel='gauss', aspect='equal', interpolation='nearest'): ''' Show a colorscale image of the FITS file. Parameters ---------- vmin : None or float, optional Minimum pixel value to use for the colorscale. If set to None, the minimum pixel value is determined using pmin (default). vmax : None or float, optional Maximum pixel value to use for the colorscale. If set to None, the maximum pixel value is determined using pmax (default). pmin : float, optional Percentile value used to determine the minimum pixel value to use for the colorscale if vmin is set to None. The default value is 0.25%. pmax : float, optional Percentile value used to determine the maximum pixel value to use for the colorscale if vmax is set to None. The default value is 99.75%. stretch : { 'linear', 'log', 'sqrt', 'arcsinh', 'power' }, optional The stretch function to use vmid : None or float, optional Baseline value used for the log and arcsinh stretches. If set to None, this is set to zero for log stretches and to vmin - (vmax - vmin) / 30. for arcsinh stretches exponent : float, optional If stretch is set to 'power', this is the exponent to use cmap : str, optional The name of the colormap to use smooth : int or tuple, optional Default smoothing scale is 3 pixels across. User can define whether they want an NxN kernel (integer), or NxM kernel (tuple). This argument corresponds to the 'gauss' and 'box' smoothing kernels. kernel : { 'gauss', 'box', numpy.array }, optional Default kernel used for smoothing is 'gauss'. The user can specify if they would prefer 'gauss', 'box', or a custom kernel. All kernels are normalized to ensure flux retention. aspect : { 'auto', 'equal' }, optional Whether to change the aspect ratio of the image to match that of the axes ('auto') or to change the aspect ratio of the axes to match that of the data ('equal'; default) interpolation : str, optional The type of interpolation to use for the image. The default is 'nearest'. Other options include 'none' (no interpolation, meaning that if exported to a postscript file, the colorscale will be output at native resolution irrespective of the dpi setting), 'bilinear', 'bicubic', and many more (see the matplotlib documentation for imshow). ''' if cmap == 'default': cmap = self._get_colormap_default() min_auto = np.equal(vmin, None) max_auto = np.equal(vmax, None) # The set of available functions cmap = mpl.cm.get_cmap(cmap) if min_auto: vmin = self._auto_v(pmin) if max_auto: vmax = self._auto_v(pmax) # Prepare normalizer object normalizer = APLpyNormalize(stretch=stretch, exponent=exponent, vmid=vmid, vmin=vmin, vmax=vmax) # Adjust vmin/vmax if auto if min_auto: if stretch == 'linear': vmin = -0.1 * (vmax - vmin) + vmin log.info("Auto-setting vmin to %10.3e" % vmin) if max_auto: if stretch == 'linear': vmax = 0.1 * (vmax - vmin) + vmax log.info("Auto-setting vmax to %10.3e" % vmax) # Update normalizer object normalizer.vmin = vmin normalizer.vmax = vmax if self.image: self.image.set_visible(True) self.image.set_norm(normalizer) self.image.set_cmap(cmap=cmap) self.image.origin = 'lower' self.image.set_interpolation(interpolation) self.image.set_data(convolve_util.convolve(self._data, smooth=smooth, kernel=kernel)) else: self.image = self._ax1.imshow( convolve_util.convolve(self._data, smooth=smooth, kernel=kernel), cmap=cmap, interpolation=interpolation, origin='lower', extent=self._extent, norm=normalizer, aspect=aspect) xmin, xmax = self._ax1.get_xbound() if xmin == 0.0: self._ax1.set_xlim(0.5, xmax) ymin, ymax = self._ax1.get_ybound() if ymin == 0.0: self._ax1.set_ylim(0.5, ymax) if hasattr(self, 'colorbar'): self.colorbar.update() @auto_refresh def hide_colorscale(self): self.image.set_visible(False) @auto_refresh def set_nan_color(self, color): ''' Set the color for NaN pixels. Parameters ---------- color : str This can be any valid matplotlib color ''' from copy import deepcopy cm = deepcopy(self.image.get_cmap()) cm.set_bad(color) self.image.set_cmap(cm) @auto_refresh def show_rgb(self, filename=None, interpolation='nearest', vertical_flip=False, horizontal_flip=False, flip=False): ''' Show a 3-color image instead of the FITS file data. Parameters ---------- filename, optional The 3-color image should have exactly the same dimensions as the FITS file, and will be shown with exactly the same projection. If FITSFigure was initialized with an AVM-tagged RGB image, the filename is not needed here. vertical_flip : str, optional Whether to vertically flip the RGB image horizontal_flip : str, optional Whether to horizontally flip the RGB image ''' try: from PIL import Image except ImportError: try: import Image except ImportError: raise ImportError("The Python Imaging Library (PIL) is required to read in RGB images") if flip: log.warning("Note that show_rgb should now correctly flip RGB images, so the flip= argument is now deprecated. If you still need to flip an image vertically or horizontally, you can use the vertical_flip= and horizontal_flip arguments instead.") if filename is None: if hasattr(self, '_rgb_image'): image = Image.open(self._rgb_image) else: raise Exception("Need to specify the filename of an RGB image") else: image = Image.open(filename) if image_util._matplotlib_pil_bug_present(): vertical_flip = True if vertical_flip: image = image.transpose(Image.FLIP_TOP_BOTTOM) if horizontal_flip: image = image.transpose(Image.FLIP_LEFT_RIGHT) # Elsewhere in APLpy we assume that we are using origin='lower' so here # we flip the image by default (since RGB images usually would require # origin='upper') then we use origin='lower' image = image.transpose(Image.FLIP_TOP_BOTTOM) self.image = self._ax1.imshow(image, extent=self._extent, interpolation=interpolation, origin='lower') @auto_refresh def show_contour(self, data=None, hdu=0, layer=None, levels=5, filled=False, cmap=None, colors=None, returnlevels=False, convention=None, dimensions=[0, 1], slices=[], smooth=None, kernel='gauss', overlap=False, **kwargs): ''' Overlay contours on the current plot. Parameters ---------- data : see below The FITS file to plot contours for. The following data types can be passed: string astropy.io.fits.PrimaryHDU astropy.io.fits.ImageHDU astropy.wcs.WCS np.ndarray hdu : int, optional By default, the image in the primary HDU is read in. If a different HDU is required, use this argument. layer : str, optional The name of the contour layer. This is useful for giving custom names to layers (instead of contour_set_n) and for replacing existing layers. levels : int or list, optional This can either be the number of contour levels to compute (if an integer is provided) or the actual list of contours to show (if a list of floats is provided) filled : str, optional Whether to show filled or line contours cmap : str, optional The colormap to use for the contours colors : str or tuple, optional If a single string is provided, all contour levels will be shown in this color. If a tuple of strings is provided, each contour will be colored according to the corresponding tuple element. returnlevels : str, optional Whether to return the list of contours to the caller. convention : str, optional This is used in cases where a FITS header can be interpreted in multiple ways. For example, for files with a -CAR projection and CRVAL2=0, this can be set to 'wells' or 'calabretta' to choose the appropriate convention. dimensions : tuple or list, optional The index of the axes to use if the data has more than three dimensions. slices : tuple or list, optional If a FITS file with more than two dimensions is specified, then these are the slices to extract. If all extra dimensions only have size 1, then this is not required. smooth : int or tuple, optional Default smoothing scale is 3 pixels across. User can define whether they want an NxN kernel (integer), or NxM kernel (tuple). This argument corresponds to the 'gauss' and 'box' smoothing kernels. kernel : { 'gauss' , 'box' , numpy.array }, optional Default kernel used for smoothing is 'gauss'. The user can specify if they would prefer 'gauss', 'box', or a custom kernel. All kernels are normalized to ensure flux retention. overlap str, optional Whether to include only contours that overlap with the image area. This significantly speeds up the drawing of contours and reduces file size when using a file for the contours covering a much larger area than the image. kwargs Additional keyword arguments (such as alpha, linewidths, or linestyles) will be passed on directly to Matplotlib's :meth:`~matplotlib.axes.Axes.contour` or :meth:`~matplotlib.axes.Axes.contourf` methods. For more information on these additional arguments, see the *Optional keyword arguments* sections in the documentation for those methods. ''' if layer: self.remove_layer(layer, raise_exception=False) if cmap: cmap = mpl.cm.get_cmap(cmap) elif not colors: cmap = mpl.cm.get_cmap('jet') if data is not None: data_contour, header_contour, wcs_contour = self._get_hdu(data, \ hdu, False, convention=convention, dimensions=dimensions, \ slices=slices) else: data_contour = self._data header_contour = self._header wcs_contour = self._wcs wcs_contour.nx = header_contour['NAXIS%i' % (dimensions[0] + 1)] wcs_contour.ny = header_contour['NAXIS%i' % (dimensions[1] + 1)] image_contour = convolve_util.convolve(data_contour, smooth=smooth, kernel=kernel) extent_contour = (0.5, wcs_contour.nx + 0.5, 0.5, wcs_contour.ny + 0.5) if type(levels) == int: auto_levels = image_util.percentile_function(image_contour) vmin = auto_levels(0.25) vmax = auto_levels(99.75) levels = np.linspace(vmin, vmax, levels) if filled: c = self._ax1.contourf(image_contour, levels, extent=extent_contour, cmap=cmap, colors=colors, **kwargs) else: c = self._ax1.contour(image_contour, levels, extent=extent_contour, cmap=cmap, colors=colors, **kwargs) if layer: contour_set_name = layer else: self._contour_counter += 1 contour_set_name = 'contour_set_' + str(self._contour_counter) contour_util.transform(c, wcs_contour, self._wcs, filled=filled, overlap=overlap) self._layers[contour_set_name] = c if returnlevels: return levels @auto_refresh def show_vectors(self, pdata, adata, phdu=0, ahdu=0, step=1, scale=1, rotate=0, cutoff=0, units='degrees', layer=None, convention=None, dimensions=[0, 1], slices=[], **kwargs): ''' Overlay vectors on the current plot. Parameters ---------- pdata : see below The FITS file specifying the magnitude of vectors. The following data types can be passed: string astropy.io.fits.PrimaryHDU astropy.io.fits.ImageHDU astropy.wcs.WCS np.ndarray adata : see below The FITS file specifying the angle of vectors. The following data types can be passed: string astropy.io.fits.PrimaryHDU astropy.io.fits.ImageHDU astropy.wcs.WCS np.ndarray phdu : int, optional By default, the image in the primary HDU is read in. If a different HDU is required for pdata, use this argument. ahdu : int, optional By default, the image in the primary HDU is read in. If a different HDU is required for adata, use this argument. step : int, optional Derive a vector only from every 'step' pixels. You will normally want this to be >1 to get sensible vector spacing. scale : int, optional The length, in pixels, of a vector with magnitude 1 in the image specified by pdata. If pdata specifies fractional polarization, make this comparable to step. rotate : float, optional An angle to rotate by, in units the same as those of the angle map. cutoff : float, optional The value of magnitude below which no vectors should be plotted. The default value, zero, excludes negative-length and NaN-masked data. units : str, optional Units to assume for the angle map. Valid values are 'degrees' (the default) or 'radians' (or anything else), which will not apply a scaling factor of pi/180 to the angle data. layer : str, optional The name of the vector layer. This is useful for giving custom names to layers (instead of vector_set_n) and for replacing existing layers. convention : str, optional This is used in cases where a FITS header can be interpreted in multiple ways. For example, for files with a -CAR projection and CRVAL2=0, this can be set to 'wells' or 'calabretta' to choose the appropriate convention. dimensions : tuple or list, optional The index of the axes to use if the data has more than three dimensions. slices : tuple or list, optional If a FITS file with more than two dimensions is specified, then these are the slices to extract. If all extra dimensions only have size 1, then this is not required. kwargs Additional keyword arguments (such as alpha, linewidths, or color) which are passed to Matplotlib's :class:`~matplotlib.collections.LineCollection` class, and can be used to control the appearance of the lines. For more information on these additional arguments, see the *Optional keyword arguments* sections in the documentation for those methods. ''' # over-ride default color (none) that will otherwise be set by #show_lines() if not 'color' in kwargs: kwargs.setdefault('color', 'black') if layer: self.remove_layer(layer, raise_exception=False) data_p, header_p, wcs_p = self._get_hdu(pdata, phdu, False, \ convention=convention, dimensions=dimensions, slices=slices) data_a, header_a, wcs_a = self._get_hdu(adata, ahdu, False, \ convention=convention, dimensions=dimensions, slices=slices) wcs_p.nx = header_p['NAXIS%i' % (dimensions[0] + 1)] wcs_p.ny = header_p['NAXIS%i' % (dimensions[1] + 1)] wcs_a.nx = header_a['NAXIS%i' % (dimensions[0] + 1)] wcs_a.ny = header_a['NAXIS%i' % (dimensions[1] + 1)] if (wcs_p.nx!=wcs_a.nx or wcs_p.ny!=wcs_a.ny): raise Exception("Angle and magnitude images must be same size") angle = data_a + rotate if units == 'degrees': angle = np.radians(angle) linelist=[] for y in range(0,wcs_p.ny,step): for x in range(0,wcs_p.nx,step): if data_p[y,x]>cutoff and np.isfinite(angle[y,x]): r=data_p[y,x]*0.5*scale a=angle[y,x] x1=1 + x + r*np.sin(a) y1=1 + y - r*np.cos(a) x2=1 + x - r*np.sin(a) y2=1 + y + r*np.cos(a) x_world,y_world=wcs_util.pix2world(wcs_p, [x1,x2],[y1,y2] ) line=np.array([x_world,y_world]) linelist.append(line) if layer: vector_set_name = layer else: self._vector_counter += 1 vector_set_name = 'vector_set_' + str(self._vector_counter) # Use show_lines to finish the process off self.show_lines(linelist,layer=vector_set_name,**kwargs) # This method plots markers. The input should be an Nx2 array with WCS coordinates # in degree format. @auto_refresh def show_markers(self, xw, yw, layer=False, **kwargs): ''' Overlay markers on the current plot. Parameters ---------- xw : list or `~numpy.ndarray` The x positions of the markers (in world coordinates) yw : list or `~numpy.ndarray` The y positions of the markers (in world coordinates) layer : str, optional The name of the scatter layer. This is useful for giving custom names to layers (instead of marker_set_n) and for replacing existing layers. kwargs Additional keyword arguments (such as marker, facecolor, edgecolor, alpha, or linewidth) will be passed on directly to Matplotlib's :meth:`~matplotlib.axes.Axes.scatter` method (in particular, have a look at the *Optional keyword arguments* in the documentation for that method). ''' if not 'c' in kwargs: kwargs.setdefault('edgecolor', 'red') kwargs.setdefault('facecolor', 'none') kwargs.setdefault('s', 30) if layer: self.remove_layer(layer, raise_exception=False) xp, yp = wcs_util.world2pix(self._wcs, xw, yw) s = self._ax1.scatter(xp, yp, **kwargs) if layer: marker_set_name = layer else: self._scatter_counter += 1 marker_set_name = 'marker_set_' + str(self._scatter_counter) self._layers[marker_set_name] = s # Show circles. Different from markers as this method allows more definitions # for the circles. @auto_refresh def show_circles(self, xw, yw, radius, layer=False, zorder=None, **kwargs): ''' Overlay circles on the current plot. Parameters ---------- xw : list or `~numpy.ndarray` The x positions of the centers of the circles (in world coordinates) yw : list or `~numpy.ndarray` The y positions of the centers of the circles (in world coordinates) radius : int or float or list or `~numpy.ndarray` The radii of the circles (in world coordinates) layer : str, optional The name of the circle layer. This is useful for giving custom names to layers (instead of circle_set_n) and for replacing existing layers. kwargs Additional keyword arguments (such as facecolor, edgecolor, alpha, or linewidth) are passed to Matplotlib :class:`~matplotlib.collections.PatchCollection` class, and can be used to control the appearance of the circles. ''' if np.isscalar(xw): xw = np.array([xw]) else: xw = np.array(xw) if np.isscalar(yw): yw = np.array([yw]) else: yw = np.array(yw) if np.isscalar(radius): radius = np.repeat(radius, len(xw)) else: radius = np.array(radius) if not 'facecolor' in kwargs: kwargs.setdefault('facecolor', 'none') if layer: self.remove_layer(layer, raise_exception=False) xp, yp = wcs_util.world2pix(self._wcs, xw, yw) rp = radius / wcs_util.celestial_pixel_scale(self._wcs) patches = [] for i in range(len(xp)): patches.append(Circle((xp[i], yp[i]), radius=rp[i])) # Due to bugs in matplotlib, we need to pass the patch properties # directly to the PatchCollection rather than use match_original. p = PatchCollection(patches, **kwargs) if zorder is not None: p.zorder = zorder c = self._ax1.add_collection(p) if layer: circle_set_name = layer else: self._circle_counter += 1 circle_set_name = 'circle_set_' + str(self._circle_counter) self._layers[circle_set_name] = c @auto_refresh def show_ellipses(self, xw, yw, width, height, angle=0, layer=False, zorder=None, **kwargs): ''' Overlay ellipses on the current plot. Parameters ---------- xw : list or `~numpy.ndarray` The x positions of the centers of the ellipses (in world coordinates) yw : list or `~numpy.ndarray` The y positions of the centers of the ellipses (in world coordinates) width : int or float or list or `~numpy.ndarray` The width of the ellipse (in world coordinates) height : int or float or list or `~numpy.ndarray` The height of the ellipse (in world coordinates) angle : int or float or list or `~numpy.ndarray`, optional rotation in degrees (anti-clockwise). Default angle is 0.0. layer : str, optional The name of the ellipse layer. This is useful for giving custom names to layers (instead of ellipse_set_n) and for replacing existing layers. kwargs Additional keyword arguments (such as facecolor, edgecolor, alpha, or linewidth) are passed to Matplotlib :class:`~matplotlib.collections.PatchCollection` class, and can be used to control the appearance of the ellipses. ''' if np.isscalar(xw): xw = np.array([xw]) else: xw = np.array(xw) if np.isscalar(yw): yw = np.array([yw]) else: yw = np.array(yw) if np.isscalar(width): width = np.repeat(width, len(xw)) else: width = np.array(width) if np.isscalar(angle): angle = np.repeat(angle, len(xw)) else: angle = np.array(angle) if np.isscalar(height): height = np.repeat(height, len(xw)) else: height = np.array(height) if not 'facecolor' in kwargs: kwargs.setdefault('facecolor', 'none') if layer: self.remove_layer(layer, raise_exception=False) xp, yp = wcs_util.world2pix(self._wcs, xw, yw) wp = width / wcs_util.celestial_pixel_scale(self._wcs) hp = height / wcs_util.celestial_pixel_scale(self._wcs) ap = angle patches = [] for i in range(len(xp)): patches.append(Ellipse((xp[i], yp[i]), width=wp[i], height=hp[i], angle=ap[i])) # Due to bugs in matplotlib, we need to pass the patch properties # directly to the PatchCollection rather than use match_original. p = PatchCollection(patches, **kwargs) if zorder is not None: p.zorder = zorder c = self._ax1.add_collection(p) if layer: ellipse_set_name = layer else: self._ellipse_counter += 1 ellipse_set_name = 'ellipse_set_' + str(self._ellipse_counter) self._layers[ellipse_set_name] = c @auto_refresh def show_rectangles(self, xw, yw, width, height, layer=False, zorder=None, **kwargs): ''' Overlay rectangles on the current plot. Parameters ---------- xw : list or `~numpy.ndarray` The x positions of the centers of the rectangles (in world coordinates) yw : list or `~numpy.ndarray` The y positions of the centers of the rectangles (in world coordinates) width : int or float or list or `~numpy.ndarray` The width of the rectangle (in world coordinates) height : int or float or list or `~numpy.ndarray` The height of the rectangle (in world coordinates) layer : str, optional The name of the rectangle layer. This is useful for giving custom names to layers (instead of rectangle_set_n) and for replacing existing layers. kwargs Additional keyword arguments (such as facecolor, edgecolor, alpha, or linewidth) are passed to Matplotlib :class:`~matplotlib.collections.PatchCollection` class, and can be used to control the appearance of the rectangles. ''' if np.isscalar(xw): xw = np.array([xw]) else: xw = np.array(xw) if np.isscalar(yw): yw = np.array([yw]) else: yw = np.array(yw) if np.isscalar(width): width = np.repeat(width, len(xw)) else: width = np.array(width) if np.isscalar(height): height = np.repeat(height, len(xw)) else: height = np.array(height) if not 'facecolor' in kwargs: kwargs.setdefault('facecolor', 'none') if layer: self.remove_layer(layer, raise_exception=False) xp, yp = wcs_util.world2pix(self._wcs, xw, yw) wp = width / wcs_util.celestial_pixel_scale(self._wcs) hp = height / wcs_util.celestial_pixel_scale(self._wcs) patches = [] xp = xp - wp / 2. yp = yp - hp / 2. for i in range(len(xp)): patches.append(Rectangle((xp[i], yp[i]), width=wp[i], height=hp[i])) # Due to bugs in matplotlib, we need to pass the patch properties # directly to the PatchCollection rather than use match_original. p = PatchCollection(patches, **kwargs) if zorder is not None: p.zorder = zorder c = self._ax1.add_collection(p) if layer: rectangle_set_name = layer else: self._rectangle_counter += 1 rectangle_set_name = 'rectangle_set_' + str(self._rectangle_counter) self._layers[rectangle_set_name] = c @auto_refresh def show_lines(self, line_list, layer=False, zorder=None, **kwargs): ''' Overlay lines on the current plot. Parameters ---------- line_list : list A list of one or more 2xN numpy arrays which contain the [x, y] positions of the vertices in world coordinates. layer : str, optional The name of the line(s) layer. This is useful for giving custom names to layers (instead of line_set_n) and for replacing existing layers. kwargs Additional keyword arguments (such as color, offsets, linestyle, or linewidth) are passed to Matplotlib :class:`~matplotlib.collections.LineCollection` class, and can be used to control the appearance of the lines. ''' if not 'color' in kwargs: kwargs.setdefault('color', 'none') if layer: self.remove_layer(layer, raise_exception=False) lines = [] for line in line_list: xp, yp = wcs_util.world2pix(self._wcs, line[0, :], line[1, :]) lines.append(np.column_stack((xp, yp))) l = LineCollection(lines, **kwargs) if zorder is not None: l.zorder = zorder c = self._ax1.add_collection(l) if layer: line_set_name = layer else: self._linelist_counter += 1 line_set_name = 'line_set_' + str(self._linelist_counter) self._layers[line_set_name] = c @auto_refresh def show_arrows(self, x, y, dx, dy, width='auto', head_width='auto', head_length='auto', length_includes_head=True, layer=False, zorder=None, **kwargs): ''' Overlay arrows on the current plot. Parameters ---------- x, y, dx, dy : float or list or `~numpy.ndarray` Origin and displacement of the arrows in world coordinates. These can either be scalars to plot a single arrow, or lists or arrays to plot multiple arrows. width : float, optional The width of the arrow body, in pixels (default: 2% of the arrow length) head_width : float, optional The width of the arrow head, in pixels (default: 5% of the arrow length) head_length : float, optional The length of the arrow head, in pixels (default: 5% of the arrow length) length_includes_head : bool, optional Whether the head includes the length layer : str, optional The name of the arrow(s) layer. This is useful for giving custom names to layers (instead of line_set_n) and for replacing existing layers. kwargs Additional keyword arguments (such as facecolor, edgecolor, alpha, or linewidth) are passed to Matplotlib :class:`~matplotlib.collections.PatchCollection` class, and can be used to control the appearance of the arrows. ''' if layer: self.remove_layer(layer, raise_exception=False) arrows = [] if np.isscalar(x): x, y, dx, dy = [x], [y], [dx], [dy] for i in range(len(x)): xp1, yp1 = wcs_util.world2pix(self._wcs, x[i], y[i]) xp2, yp2 = wcs_util.world2pix(self._wcs, x[i] + dx[i], y[i] + dy[i]) if width == 'auto': width = 0.02 * np.sqrt((xp2 - xp1) ** 2 + (yp2 - yp1) ** 2) if head_width == 'auto': head_width = 0.1 * np.sqrt((xp2 - xp1) ** 2 + (yp2 - yp1) ** 2) if head_length == 'auto': head_length = 0.1 * np.sqrt((xp2 - xp1) ** 2 + (yp2 - yp1) ** 2) arrows.append(FancyArrow(xp1, yp1, xp2 - xp1, yp2 - yp1, width=width, head_width=head_width, head_length=head_length, length_includes_head=length_includes_head) ) # Due to bugs in matplotlib, we need to pass the patch properties # directly to the PatchCollection rather than use match_original. p = PatchCollection(arrows, **kwargs) if zorder is not None: p.zorder = zorder c = self._ax1.add_collection(p) if layer: line_set_name = layer else: self._linelist_counter += 1 line_set_name = 'arrow_set_' + str(self._linelist_counter) self._layers[line_set_name] = c @auto_refresh def show_polygons(self, polygon_list, layer=False, zorder=None, **kwargs): ''' Overlay polygons on the current plot. Parameters ---------- polygon_list : list or tuple A list of one or more 2xN or Nx2 Numpy arrays which contain the [x, y] positions of the vertices in world coordinates. Note that N should be greater than 2. layer : str, optional The name of the circle layer. This is useful for giving custom names to layers (instead of circle_set_n) and for replacing existing layers. kwargs Additional keyword arguments (such as facecolor, edgecolor, alpha, or linewidth) are passed to Matplotlib :class:`~matplotlib.collections.PatchCollection` class, and can be used to control the appearance of the polygons. ''' if not 'facecolor' in kwargs: kwargs.setdefault('facecolor', 'none') if layer: self.remove_layer(layer, raise_exception=False) if type(polygon_list) not in [list, tuple]: raise Exception("polygon_list should be a list or tuple of Numpy arrays") pix_polygon_list = [] for polygon in polygon_list: if type(polygon) is not np.ndarray: raise Exception("Polygon should be given as a Numpy array") if polygon.shape[0] == 2 and polygon.shape[1] > 2: xw = polygon[0, :] yw = polygon[1, :] elif polygon.shape[0] > 2 and polygon.shape[1] == 2: xw = polygon[:, 0] yw = polygon[:, 1] else: raise Exception("Polygon should have dimensions 2xN or Nx2 with N>2") xp, yp = wcs_util.world2pix(self._wcs, xw, yw) pix_polygon_list.append(np.column_stack((xp, yp))) patches = [] for i in range(len(pix_polygon_list)): patches.append(Polygon(pix_polygon_list[i], **kwargs)) # Due to bugs in matplotlib, we need to pass the patch properties # directly to the PatchCollection rather than use match_original. p = PatchCollection(patches, **kwargs) if zorder is not None: p.zorder = zorder c = self._ax1.add_collection(p) if layer: poly_set_name = layer else: self._poly_counter += 1 poly_set_name = 'poly_set_' + str(self._poly_counter) self._layers[poly_set_name] = c @auto_refresh @fixdocstring def add_label(self, x, y, text, relative=False, color='black', family=None, style=None, variant=None, stretch=None, weight=None, size=None, fontproperties=None, horizontalalignment='center', verticalalignment='center', layer=None, **kwargs): ''' Add a text label. Parameters ---------- x, y : float Coordinates of the text label text : str The label relative : str, optional Whether the coordinates are to be interpreted as world coordinates (e.g. RA/Dec or longitude/latitude), or coordinates relative to the axes (where 0.0 is left or bottom and 1.0 is right or top). common: color, family, style, variant, stretch, weight, size, fontproperties, horizontalalignment, verticalalignment ''' if layer: self.remove_layer(layer, raise_exception=False) # Can't pass fontproperties=None to text. Only pass it if it is not None. if fontproperties: kwargs['fontproperties'] = fontproperties if not np.isscalar(x): raise Exception("x should be a single value") if not np.isscalar(y): raise Exception("y should be a single value") if not np.isscalar(text): raise Exception("text should be a single value") if relative: l = self._ax1.text(x, y, text, color=color, family=family, style=style, variant=variant, stretch=stretch, weight=weight, size=size, horizontalalignment=horizontalalignment, verticalalignment=verticalalignment, transform=self._ax1.transAxes, **kwargs) else: xp, yp = wcs_util.world2pix(self._wcs, x, y) l = self._ax1.text(xp, yp, text, color=color, family=family, style=style, variant=variant, stretch=stretch, weight=weight, size=size, horizontalalignment=horizontalalignment, verticalalignment=verticalalignment, **kwargs) if layer: label_name = layer else: self._label_counter += 1 label_name = 'label_' + str(self._label_counter) self._layers[label_name] = l def set_auto_refresh(self, refresh): ''' Set whether the display should refresh after each method call. Parameters ---------- refresh : bool Whether to refresh the display every time a FITSFigure method is called. This defaults to `True` if and only if APLpy is being used from IPython and the Matplotlib backend is interactive. ''' if refresh is None: if matplotlib.is_interactive(): try: get_ipython() except NameError: refresh = False else: refresh = True else: refresh = False elif not isinstance(refresh, bool): raise TypeError("refresh argument should be boolean or `None`") self._parameters.auto_refresh = refresh def refresh(self, force=True): ''' Refresh the display. Parameters ---------- force : str, optional If set to False, refresh() will only have an effect if auto refresh is on. If set to True, the display will be refreshed whatever the auto refresh setting is set to. The default is True. ''' if self._parameters.auto_refresh or force: self._figure.canvas.draw() def save(self, filename, dpi=None, transparent=False, adjust_bbox=True, max_dpi=300, format=None): ''' Save the current figure to a file. Parameters ---------- filename : str or fileobj The name of the file to save the plot to. This can be for example a PS, EPS, PDF, PNG, JPEG, or SVG file. Note that it is also possible to pass file-like object. dpi : float, optional The output resolution, in dots per inch. If the output file is a vector graphics format (such as PS, EPS, PDF or SVG) only the image itself will be rasterized. If the output is a PS or EPS file and no dpi is specified, the dpi is automatically calculated to match the resolution of the image. If this value is larger than max_dpi, then dpi is set to max_dpi. transparent : str, optional Whether to preserve transparency adjust_bbox : str, optional Auto-adjust the bounding box for the output max_dpi : float, optional The maximum resolution to output images at. If no maximum is wanted, enter None or 0. format : str, optional By default, APLpy tries to guess the file format based on the file extension, but the format can also be specified explicitly. Should be one of 'eps', 'ps', 'pdf', 'svg', 'png'. ''' if isinstance(filename, six.string_types) and format is None: format = os.path.splitext(filename)[1].lower()[1:] if dpi is None and format in ['eps', 'ps', 'pdf']: width = self._ax1.get_position().width * self._figure.get_figwidth() interval = self._ax1.xaxis.get_view_interval() nx = interval[1] - interval[0] if max_dpi: dpi = np.minimum(nx / width, max_dpi) else: dpi = nx / width log.info("Auto-setting resolution to %g dpi" % dpi) artists = [] if adjust_bbox: for artist in self._layers.values(): if isinstance(artist, matplotlib.text.Text): artists.append(artist) self._figure.savefig(filename, dpi=dpi, transparent=transparent, bbox_inches='tight', bbox_extra_artists=artists, format=format) else: self._figure.savefig(filename, dpi=dpi, transparent=transparent, format=format) def _initialize_view(self): self._ax1.xaxis.set_view_interval(+0.5, self._wcs.nx + 0.5, ignore=True) self._ax1.yaxis.set_view_interval(+0.5, self._wcs.ny + 0.5, ignore=True) self._ax2.xaxis.set_view_interval(+0.5, self._wcs.nx + 0.5, ignore=True) self._ax2.yaxis.set_view_interval(+0.5, self._wcs.ny + 0.5, ignore=True) # set the image extent to FITS pixel coordinates self._extent = (0.5, self._wcs.nx + 0.5, 0.5, self._wcs.ny + 0.5) def _get_invert_default(self): return self._figure.apl_grayscale_invert_default def _get_colormap_default(self): return self._figure.apl_colorscale_cmap_default @auto_refresh def set_theme(self, theme): ''' Set the axes, ticks, grid, and image colors to a certain style (experimental). Parameters ---------- theme : str The theme to use. At the moment, this can be 'pretty' (for viewing on-screen) and 'publication' (which makes the ticks and grid black, and displays the image in inverted grayscale) ''' if theme == 'pretty': self.frame.set_color('black') self.frame.set_linewidth(1.0) self.ticks.set_color('white') self.ticks.set_length(7) self._figure.apl_grayscale_invert_default = False self._figure.apl_colorscale_cmap_default = 'jet' if self.image: self.image.set_cmap(cmap=mpl.cm.get_cmap('jet')) elif theme == 'publication': self.frame.set_color('black') self.frame.set_linewidth(1.0) self.ticks.set_color('black') self.ticks.set_length(7) self._figure.apl_grayscale_invert_default = True self._figure.apl_colorscale_cmap_default = 'gist_heat' if self.image: self.image.set_cmap(cmap=mpl.cm.get_cmap('gist_yarg')) def world2pixel(self, xw, yw): ''' Convert world to pixel coordinates. Parameters ---------- xw : float or list or `~numpy.ndarray` x world coordinate yw : float or list or `~numpy.ndarray` y world coordinate Returns ------- xp : float or list or `~numpy.ndarray` x pixel coordinate yp : float or list or `~numpy.ndarray` y pixel coordinate ''' return wcs_util.world2pix(self._wcs, xw, yw) def pixel2world(self, xp, yp): ''' Convert pixel to world coordinates. Parameters ---------- xp : float or list or `~numpy.ndarray` x pixel coordinate yp : float or list or `~numpy.ndarray` y pixel coordinate Returns ------- xw : float or list or `~numpy.ndarray` x world coordinate yw : float or list or `~numpy.ndarray` y world coordinate ''' return wcs_util.pix2world(self._wcs, xp, yp) @auto_refresh def add_grid(self, *args, **kwargs): ''' Add a coordinate to the current figure. Once this method has been run, a grid attribute becomes available, and can be used to control the aspect of the grid:: >>> f = aplpy.FITSFigure(...) >>> ... >>> f.add_grid() >>> f.grid.set_color('white') >>> f.grid.set_alpha(0.5) >>> ... ''' if hasattr(self, 'grid'): raise Exception("Grid already exists") try: self.grid = Grid(self) self.grid.show(*args, **kwargs) except: del self.grid raise @auto_refresh def remove_grid(self): ''' Removes the grid from the current figure. ''' self.grid._remove() del self.grid @auto_refresh def add_beam(self, *args, **kwargs): ''' Add a beam to the current figure. Once this method has been run, a beam attribute becomes available, and can be used to control the aspect of the beam:: >>> f = aplpy.FITSFigure(...) >>> ... >>> f.add_beam() >>> f.beam.set_color('white') >>> f.beam.set_hatch('+') >>> ... If more than one beam is added, the beam object becomes a list. In this case, to control the aspect of one of the beams, you will need to specify the beam index:: >>> ... >>> f.beam[2].set_hatch('/') >>> ... ''' # Initalize the beam and set parameters b = Beam(self) b.show(*args, **kwargs) if hasattr(self, 'beam'): if type(self.beam) is list: self.beam.append(b) else: self.beam = [self.beam, b] else: self.beam = b @auto_refresh def remove_beam(self, beam_index=None): ''' Removes the beam from the current figure. If more than one beam is present, the index of the beam should be specified using beam_index= ''' if type(self.beam) is list: if beam_index is None: raise Exception("More than one beam present - use beam_index= to specify which one to remove") else: b = self.beam.pop(beam_index) b._remove() del b # If only one beam is present, remove containing list if len(self.beam) == 1: self.beam = self.beam[0] else: self.beam._remove() del self.beam @auto_refresh def add_scalebar(self, length, *args, **kwargs): ''' Add a scalebar to the current figure. Once this method has been run, a scalebar attribute becomes available, and can be used to control the aspect of the scalebar:: >>> f = aplpy.FITSFigure(...) >>> ... >>> f.add_scalebar(0.01) # length has to be specified >>> f.scalebar.set_label('100 AU') >>> ... Parameters ---------- length : float, or quantity The length of the scalebar in degrees, an angular quantity, or angular unit label : str, optional Label to place below the scalebar corner : int, optional Where to place the scalebar. Acceptable values are:, 'left', 'right', 'top', 'bottom', 'top left', 'top right', 'bottom left' (default), 'bottom right' frame : str, optional Whether to display a frame behind the scalebar (default is False) kwargs Additional arguments are passed to the matplotlib Rectangle and Text classes. See the matplotlib documentation for more details. In cases where the same argument exists for the two objects, the argument is passed to both the Text and Rectangle instance. ''' if hasattr(self, 'scalebar'): raise Exception("Scalebar already exists") try: self.scalebar = Scalebar(self) self.scalebar.show(length, *args, **kwargs) except: del self.scalebar raise @auto_refresh def remove_scalebar(self): ''' Removes the scalebar from the current figure. ''' self.scalebar._remove() del self.scalebar @auto_refresh def add_colorbar(self, *args, **kwargs): ''' Add a colorbar to the current figure. Once this method has been run, a colorbar attribute becomes available, and can be used to control the aspect of the colorbar:: >>> f = aplpy.FITSFigure(...) >>> ... >>> f.add_colorbar() >>> f.colorbar.set_width(0.3) >>> f.colorbar.set_location('top') >>> ... ''' if hasattr(self, 'colorbar'): raise Exception("Colorbar already exists") if self.image is None: raise Exception("No image is shown, so a colorbar cannot be displayed") try: self.colorbar = Colorbar(self) self.colorbar.show(*args, **kwargs) except: del self.colorbar raise @auto_refresh def remove_colorbar(self): ''' Removes the colorbar from the current figure. ''' self.colorbar._remove() del self.colorbar def close(self): ''' Close the figure and free up the memory. ''' mpl.close(self._figure) savefig = save APLpy-1.1.1/aplpy/decorator.py0000644000077000000240000002467212536551501016114 0ustar tomstaff00000000000000from __future__ import print_function ########################## LICENCE ############################### # Copyright (c) 2005-2012, Michele Simionato # All rights reserved. # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # Redistributions in bytecode form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in # the documentation and/or other materials provided with the # distribution. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. """ Decorator module, see http://pypi.python.org/pypi/decorator for the documentation. """ __version__ = '3.4.0' __all__ = ["decorator", "FunctionMaker", "contextmanager"] import sys, re, inspect if sys.version >= '3': from inspect import getfullargspec def get_init(cls): return cls.__init__ else: class getfullargspec(object): "A quick and dirty replacement for getfullargspec for Python 2.X" def __init__(self, f): self.args, self.varargs, self.varkw, self.defaults = \ inspect.getargspec(f) self.kwonlyargs = [] self.kwonlydefaults = None def __iter__(self): yield self.args yield self.varargs yield self.varkw yield self.defaults def get_init(cls): return cls.__init__.__func__ DEF = re.compile('\s*def\s*([_\w][_\w\d]*)\s*\(') # basic functionality class FunctionMaker(object): """ An object with the ability to create functions with a given signature. It has attributes name, doc, module, signature, defaults, dict and methods update and make. """ def __init__(self, func=None, name=None, signature=None, defaults=None, doc=None, module=None, funcdict=None): self.shortsignature = signature if func: # func can be a class or a callable, but not an instance method self.name = func.__name__ if self.name == '': # small hack for lambda functions self.name = '_lambda_' self.doc = func.__doc__ self.module = func.__module__ if inspect.isfunction(func): argspec = getfullargspec(func) self.annotations = getattr(func, '__annotations__', {}) for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', 'kwonlydefaults'): setattr(self, a, getattr(argspec, a)) for i, arg in enumerate(self.args): setattr(self, 'arg%d' % i, arg) if sys.version < '3': # easy way self.shortsignature = self.signature = \ inspect.formatargspec( formatvalue=lambda val: "", *argspec)[1:-1] else: # Python 3 way allargs = list(self.args) allshortargs = list(self.args) if self.varargs: allargs.append('*' + self.varargs) allshortargs.append('*' + self.varargs) elif self.kwonlyargs: allargs.append('*') # single star syntax for a in self.kwonlyargs: allargs.append('%s=None' % a) allshortargs.append('%s=%s' % (a, a)) if self.varkw: allargs.append('**' + self.varkw) allshortargs.append('**' + self.varkw) self.signature = ', '.join(allargs) self.shortsignature = ', '.join(allshortargs) self.dict = func.__dict__.copy() # func=None happens when decorating a caller if name: self.name = name if signature is not None: self.signature = signature if defaults: self.defaults = defaults if doc: self.doc = doc if module: self.module = module if funcdict: self.dict = funcdict # check existence required attributes assert hasattr(self, 'name') if not hasattr(self, 'signature'): raise TypeError('You are decorating a non function: %s' % func) def update(self, func, **kw): "Update the signature of func with the data in self" func.__name__ = self.name func.__doc__ = getattr(self, 'doc', None) func.__dict__ = getattr(self, 'dict', {}) func.__defaults__ = getattr(self, 'defaults', ()) func.__kwdefaults__ = getattr(self, 'kwonlydefaults', None) func.__annotations__ = getattr(self, 'annotations', None) callermodule = sys._getframe(3).f_globals.get('__name__', '?') func.__module__ = getattr(self, 'module', callermodule) func.__dict__.update(kw) def make(self, src_templ, evaldict=None, addsource=False, **attrs): "Make a new function from a given template and update the signature" src = src_templ % vars(self) # expand name and signature evaldict = evaldict or {} mo = DEF.match(src) if mo is None: raise SyntaxError('not a valid function template\n%s' % src) name = mo.group(1) # extract the function name names = set([name] + [arg.strip(' *') for arg in self.shortsignature.split(',')]) for n in names: if n in ('_func_', '_call_'): raise NameError('%s is overridden in\n%s' % (n, src)) if not src.endswith('\n'): # add a newline just for safety src += '\n' # this is needed in old versions of Python try: code = compile(src, '', 'single') # print >> sys.stderr, 'Compiling %s' % src exec(code, evaldict) except: print('Error in generated code:', file=sys.stderr) print(src, file=sys.stderr) raise func = evaldict[name] if addsource: attrs['__source__'] = src self.update(func, **attrs) return func @classmethod def create(cls, obj, body, evaldict, defaults=None, doc=None, module=None, addsource=True, **attrs): """ Create a function from the strings name, signature and body. evaldict is the evaluation dictionary. If addsource is true an attribute __source__ is added to the result. The attributes attrs are added, if any. """ if isinstance(obj, str): # "name(signature)" name, rest = obj.strip().split('(', 1) signature = rest[:-1] #strip a right parens func = None else: # a function name = None signature = None func = obj self = cls(func, name, signature, defaults, doc, module) ibody = '\n'.join(' ' + line for line in body.splitlines()) return self.make('def %(name)s(%(signature)s):\n' + ibody, evaldict, addsource, **attrs) def decorator(caller, func=None): """ decorator(caller) converts a caller function into a decorator; decorator(caller, func) decorates a function using a caller. """ if func is not None: # returns a decorated function evaldict = func.__globals__.copy() evaldict['_call_'] = caller evaldict['_func_'] = func return FunctionMaker.create( func, "return _call_(_func_, %(shortsignature)s)", evaldict, undecorated=func, __wrapped__=func) else: # returns a decorator if inspect.isclass(caller): name = caller.__name__.lower() callerfunc = get_init(caller) doc = 'decorator(%s) converts functions/generators into ' \ 'factories of %s objects' % (caller.__name__, caller.__name__) fun = getfullargspec(callerfunc).args[1] # second arg elif inspect.isfunction(caller): name = '_lambda_' if caller.__name__ == '' \ else caller.__name__ callerfunc = caller doc = caller.__doc__ fun = getfullargspec(callerfunc).args[0] # first arg else: # assume caller is an object with a __call__ method name = caller.__class__.__name__.lower() callerfunc = caller.__call__.__func__ doc = caller.__call__.__doc__ fun = getfullargspec(callerfunc).args[1] # second arg evaldict = callerfunc.__globals__.copy() evaldict['_call_'] = caller evaldict['decorator'] = decorator return FunctionMaker.create( '%s(%s)' % (name, fun), 'return decorator(_call_, %s)' % fun, evaldict, undecorated=caller, __wrapped__=caller, doc=doc, module=caller.__module__) ######################### contextmanager ######################## def __call__(self, func): 'Context manager decorator' return FunctionMaker.create( func, "with _self_: return _func_(%(shortsignature)s)", dict(_self_=self, _func_=func), __wrapped__=func) try: # Python >= 3.2 from contextlib import _GeneratorContextManager ContextManager = type( 'ContextManager', (_GeneratorContextManager,), dict(__call__=__call__)) except ImportError: # Python >= 2.5 from contextlib import GeneratorContextManager def __init__(self, f, *a, **k): return GeneratorContextManager.__init__(self, f(*a, **k)) ContextManager = type( 'ContextManager', (GeneratorContextManager,), dict(__call__=__call__, __init__=__init__)) contextmanager = decorator(ContextManager) APLpy-1.1.1/aplpy/decorators.py0000644000077000000240000000540412536551501016267 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import threading from .decorator import decorator mydata = threading.local() def auto_refresh(f): return decorator(_auto_refresh, f) def _auto_refresh(f, *args, **kwargs): if 'refresh' in kwargs: refresh = kwargs.pop('refresh') else: refresh = True # The following is necessary rather than using mydata.nesting = 0 at the # start of the file, because doing the latter caused issues with the Django # development server. mydata.nesting = getattr(mydata, 'nesting', 0) + 1 try: return f(*args, **kwargs) finally: mydata.nesting -= 1 if hasattr(args[0], '_figure'): if refresh and mydata.nesting == 0 and args[0]._parameters.auto_refresh: args[0]._figure.canvas.draw() doc = {} doc['size'] = '''size : str or int or float, optional The size of the font. This can either be a numeric value (e.g. 12), giving the size in points, or one of 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', or 'xx-large'. ''' doc['weight'] = '''weight : str or int or float, optional The weight (or boldness) of the font. This can either be a numeric value in the range 0-1000 or one of 'ultralight', 'light', 'normal', 'regular', 'book', 'medium', 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy', 'extra bold', 'black'. ''' doc['stretch'] = '''stretch : str or int or float, optional The stretching (spacing between letters) for the font. This can either be a numeric value in the range 0-1000 or one of 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'normal', 'semi-expanded', 'expanded', 'extra-expanded' or 'ultra-expanded'. ''' doc['family'] = '''family : str, optional The family of the font to use. This can either be a generic font family name, either 'serif', 'sans-serif', 'cursive', 'fantasy', or 'monospace', or a list of font names in decreasing order of priority. ''' doc['style'] = '''style : str, optional The font style. This can be 'normal', 'italic' or 'oblique'. ''' doc['variant'] = '''variant : str, optional The font variant. This can be 'normal' or 'small-caps' ''' def fixdocstring(func): lines = func.__doc__.split('\n') for i, line in enumerate(lines): if 'common:' in line: break header = lines[:i] footer = lines[i + 1:] indent = lines[i].index('common:') common = [] for item in lines[i].split(':')[1].split(','): if item.strip() in doc: common.append(" " * indent + doc[item.strip()].replace('\n', '\n' + " " * indent)) docstring = "\n".join(header + common + footer) func.__doc__ = docstring return func APLpy-1.1.1/aplpy/deprecated.py0000644000077000000240000002617412536551501016231 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import warnings from .decorators import auto_refresh class Deprecated(object): @auto_refresh def set_tick_xspacing(self, *args, **kwargs): warnings.warn("set_tick_xspacing is deprecated - use ticks.set_xspacing instead", DeprecationWarning) self.ticks.set_xspacing(*args, **kwargs) @auto_refresh def set_tick_yspacing(self, *args, **kwargs): warnings.warn("set_tick_yspacing is deprecated - use ticks.set_yspacing instead", DeprecationWarning) self.ticks.set_yspacing(*args, **kwargs) @auto_refresh def set_tick_size(self, *args, **kwargs): warnings.warn("set_tick_size is deprecated - use ticks.set_length instead", DeprecationWarning) self.ticks.set_length(*args, **kwargs) @auto_refresh def set_tick_color(self, *args, **kwargs): warnings.warn("set_tick_color is deprecated - use ticks.set_color instead", DeprecationWarning) self.ticks.set_color(*args, **kwargs) @auto_refresh def show_grid(self, *args, **kwargs): warnings.warn("show_grid is deprecated - use add_grid instead", DeprecationWarning) self.add_grid(*args, **kwargs) @auto_refresh def hide_grid(self, *args, **kwargs): warnings.warn("hide_grid is deprecated - use remove_grid instead", DeprecationWarning) self.remove_grid(*args, **kwargs) @auto_refresh def show_beam(self, *args, **kwargs): warnings.warn("show_beam is deprecated - use add_beam instead", DeprecationWarning) self.add_beam(*args, **kwargs) @auto_refresh def hide_beam(self, *args, **kwargs): warnings.warn("hide_beam is deprecated - use remove_beam instead", DeprecationWarning) self.add_beam(*args, **kwargs) @auto_refresh def show_scalebar(self, *args, **kwargs): warnings.warn("show_scalebar is deprecated - use add_scalebar instead", DeprecationWarning) self.add_scalebar(*args, **kwargs) @auto_refresh def hide_scalebar(self, *args, **kwargs): warnings.warn("hide_scalebar is deprecated - use remove_scalebar instead", DeprecationWarning) self.add_scalebar(*args, **kwargs) @auto_refresh def show_colorbar(self, *args, **kwargs): warnings.warn("show_colorbar is deprecated - use add_colorbar instead", DeprecationWarning) self.add_colorbar(*args, **kwargs) @auto_refresh def hide_colorbar(self, *args, **kwargs): warnings.warn("hide_colorbar is deprecated - use remove_colorbar instead", DeprecationWarning) self.add_colorbar(*args, **kwargs) @auto_refresh def set_grid_alpha(self, *args, **kwargs): warnings.warn("set_grid_alpha is deprecated - use grid.set_alpha instead", DeprecationWarning) self.grid.set_alpha(*args, **kwargs) @auto_refresh def set_grid_color(self, *args, **kwargs): warnings.warn("set_grid_color is deprecated - use grid.set_color instead", DeprecationWarning) self.grid.set_color(*args, **kwargs) @auto_refresh def set_grid_xspacing(self, *args, **kwargs): warnings.warn("set_grid_xspacing is deprecated - use grid.set_xspacing instead", DeprecationWarning) self.grid.set_xspacing(*args, **kwargs) @auto_refresh def set_grid_yspacing(self, *args, **kwargs): warnings.warn("set_grid_yspacing is deprecated - use grid.set_yspacing instead", DeprecationWarning) self.grid.set_yspacing(*args, **kwargs) @auto_refresh def set_scalebar_properties(self, *args, **kwargs): warnings.warn("set_scalebar_properties is deprecated - use scalebar.set instead", DeprecationWarning) self.scalebar._set_scalebar_properties(*args, **kwargs) @auto_refresh def set_label_properties(self, *args, **kwargs): warnings.warn("set_label_properties is deprecated - use scalebar.set instead", DeprecationWarning) self.scalebar._set_label_properties(*args, **kwargs) @auto_refresh def set_beam_properties(self, *args, **kwargs): warnings.warn("set_beam_properties is deprecated - use beam.set instead", DeprecationWarning) self.beam.set(*args, **kwargs) @auto_refresh def set_labels_latex(self, usetex): warnings.warn("set_labels_latex has been deprecated - use set_system_latex instead", DeprecationWarning) self.set_system_latex(usetex) # TICK LABELS @auto_refresh def set_tick_labels_format(self, xformat=None, yformat=None): warnings.warn("set_tick_labels_format has been deprecated - use tick_labels.set_xformat() and tick_labels.set_yformat instead", DeprecationWarning) if xformat: self.tick_labels.set_xformat(xformat) if yformat: self.tick_labels.set_yformat(yformat) @auto_refresh def set_tick_labels_xformat(self, format): warnings.warn("set_tick_labels_xformat has been deprecated - use tick_labels.set_xformat() instead", DeprecationWarning) self.tick_labels.set_xformat(format) @auto_refresh def set_tick_labels_yformat(self, format): warnings.warn("set_tick_labels_yformat has been deprecated - use tick_labels.set_yformat() instead", DeprecationWarning) self.tick_labels.set_yformat(format) @auto_refresh def set_tick_labels_style(self, style): warnings.warn("set_tick_labels_style has been deprecated - use tick_labels.set_style instead", DeprecationWarning) self.tick_labels.set_style(style) @auto_refresh def set_tick_labels_size(self, size): warnings.warn("set_tick_labels_size has been deprecated - use tick_labels.set_font instead", DeprecationWarning) self.tick_labels.set_font(size=size) @auto_refresh def set_tick_labels_weight(self, weight): warnings.warn("set_tick_labels_weight has been deprecated - use tick_labels.set_font instead", DeprecationWarning) self.tick_labels.set_font(weight=weight) @auto_refresh def set_tick_labels_family(self, family): warnings.warn("set_tick_labels_family has been deprecated - use tick_labels.set_font instead", DeprecationWarning) self.tick_labels.set_font(family=family) @auto_refresh def set_tick_labels_font(self, *args, **kwargs): warnings.warn("set_tick_labels_font has been deprecated - use tick_labels.set_font instead", DeprecationWarning) self.tick_labels.set_font(*args, **kwargs) @auto_refresh def show_tick_labels(self): warnings.warn("show_tick_labels has been deprecated - use tick_labels.show instead", DeprecationWarning) self.tick_labels.show() @auto_refresh def hide_tick_labels(self): warnings.warn("hide_tick_labels has been deprecated - use tick_labels.hide instead", DeprecationWarning) self.tick_labels.hide() @auto_refresh def show_xtick_labels(self): warnings.warn("show_xtick_labels has been deprecated - use tick_labels.show_x instead", DeprecationWarning) self.tick_labels.show_x() @auto_refresh def hide_xtick_labels(self): warnings.warn("hide_xtick_labels has been deprecated - use tick_labels.hide_x instead", DeprecationWarning) self.tick_labels.hide_x() @auto_refresh def show_ytick_labels(self): warnings.warn("show_ytick_labels has been deprecated - use tick_labels.show_y instead", DeprecationWarning) self.tick_labels.show_y() @auto_refresh def hide_ytick_labels(self): warnings.warn("hide_ytick_labels has been deprecated - use tick_labels.hide_y instead", DeprecationWarning) self.tick_labels.hide_y() # AXIS LABELS @auto_refresh def set_axis_labels(self, xlabel='default', ylabel='default', xpad='default', ypad='default'): warnings.warn("set_axis_labels has been deprecated - use axis_labels.set_xtext, axis_labels.set_ytext, axis_labels.set_xpad, and axis_labels.set_ypad instead", DeprecationWarning) if xlabel != 'default': self.axis_labels.set_xtext(xlabel) if xpad != 'default': self.axis_labels.set_xpad(xpad) if ylabel != 'default': self.axis_labels.set_ytext(ylabel) if ypad != 'default': self.axis_labels.set_ypad(ypad) @auto_refresh def set_axis_labels_xdisp(self, xpad): warnings.warn("set_axis_labels_xdisk has been deprecated - use axis_labels.set_xpad instead", DeprecationWarning) self.axis_labels.set_xpad(xpad) @auto_refresh def set_axis_labels_ydisp(self, ypad): warnings.warn("set_axis_labels_xdisp has been deprecated - use axis_labels.set_ypad instead", DeprecationWarning) self.axis_labels.set_ypad(ypad) @auto_refresh def set_axis_labels_size(self, size): warnings.warn("set_axis_labels_size has been deprecated - use axis_labels.set_font instead", DeprecationWarning) self.axis_labels.set_font(size=size) @auto_refresh def set_axis_labels_weight(self, weight): warnings.warn("set_axis_labels_weight has been deprecated - use axis_labels.set_font instead", DeprecationWarning) self.axis_labels.set_font(weight=weight) @auto_refresh def set_axis_labels_family(self, family): warnings.warn("set_axis_labels_family has been deprecated - use axis_labels.set_font instead", DeprecationWarning) self.axis_labels.set_font(family=family) @auto_refresh def set_axis_labels_font(self, *args, **kwargs): warnings.warn("set_axis_labels_font has been deprecated - use axis_labels.set_font instead", DeprecationWarning) self.axis_labels.set_font(*args, **kwargs) @auto_refresh def show_axis_labels(self): warnings.warn("show_axis_labels has been deprecated - use axis_labels.show instead", DeprecationWarning) self.axis_labels.show() @auto_refresh def hide_axis_labels(self): warnings.warn("hide_axis_labels has been deprecated - use axis_labels.hide instead", DeprecationWarning) self.axis_labels.hide() @auto_refresh def show_xaxis_label(self): warnings.warn("show_xaxis_label has been deprecated - use axis_labels.show_x instead", DeprecationWarning) self.axis_labels.show_x() @auto_refresh def hide_xaxis_label(self): warnings.warn("hide_xaxis_label has been deprecated - use axis_labels.hide_x instead", DeprecationWarning) self.axis_labels.hide_x() @auto_refresh def show_yaxis_label(self): warnings.warn("show_yaxis_label has been deprecated - use axis_labels.show_y instead", DeprecationWarning) self.axis_labels.show_y() @auto_refresh def hide_yaxis_label(self): warnings.warn("hide_yaxis_label has been deprecated - use axis_labels.hide_y instead", DeprecationWarning) self.axis_labels.hide_y() # FRAME @auto_refresh def set_frame_color(self, *args, **kwargs): warnings.warn("set_frame_color has been deprecated - use frame.set_color instead", DeprecationWarning) self.frame.set_color(*args, **kwargs) @auto_refresh def set_frame_linewidth(self, *args, **kwargs): warnings.warn("set_frame_linewidth has been deprecated - use frame.set_linewidth instead", DeprecationWarning) self.frame.set_linewidth(*args, **kwargs) APLpy-1.1.1/aplpy/frame.py0000644000077000000240000000225612536551501015216 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division from .decorators import auto_refresh class Frame(object): @auto_refresh def __init__(self, parent): self._ax1 = parent._ax1 self._ax2 = parent._ax2 self._figure = parent._figure # Save plotting parameters (required for @auto_refresh) self._parameters = parent._parameters @auto_refresh def set_linewidth(self, linewidth): ''' Set line width of the frame. Parameters ---------- linewidth: The linewidth to use for the frame. ''' for key in self._ax1.spines: self._ax1.spines[key].set_linewidth(linewidth) for key in self._ax2.spines: self._ax2.spines[key].set_linewidth(linewidth) @auto_refresh def set_color(self, color): ''' Set color of the frame. Parameters ---------- color: The color to use for the frame. ''' for key in self._ax1.spines: self._ax1.spines[key].set_edgecolor(color) for key in self._ax2.spines: self._ax2.spines[key].set_edgecolor(color) APLpy-1.1.1/aplpy/grid.py0000644000077000000240000003750212536551501015053 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import warnings import numpy as np from matplotlib.collections import LineCollection from . import math_util from . import wcs_util from . import angle_util as au from .ticks import tick_positions, default_spacing from .decorators import auto_refresh class Grid(object): @auto_refresh def __init__(self, parent): # Save axes and wcs information self.ax = parent._ax1 self._wcs = parent._wcs self._figure = parent._figure # Save plotting parameters (required for @auto_refresh) self._parameters = parent._parameters # Initialize grid container self._grid = None self._active = False # Set defaults self.x_auto_spacing = True self.y_auto_spacing = True self.default_color = 'white' self.default_alpha = 0.5 # Set grid event handler self.ax.callbacks.connect('xlim_changed', self._update_norefresh) self.ax.callbacks.connect('ylim_changed', self._update_norefresh) @auto_refresh def _remove(self): self._grid.remove() @auto_refresh def set_xspacing(self, xspacing): ''' Set the grid line spacing in the longitudinal direction Parameters ---------- xspacing : { float, str } The spacing in the longitudinal direction. To set the spacing to be the same as the ticks, set this to 'tick' ''' if xspacing == 'tick': self.x_auto_spacing = True elif np.isreal(xspacing): self.x_auto_spacing = False if self._wcs.xaxis_coord_type in ['longitude', 'latitude']: self.x_grid_spacing = au.Angle( degrees=xspacing, latitude=self._wcs.xaxis_coord_type == 'latitude') else: self.x_grid_spacing = xspacing else: raise ValueError("Grid spacing should be a scalar or 'tick'") self._update() @auto_refresh def set_yspacing(self, yspacing): ''' Set the grid line spacing in the latitudinal direction Parameters ---------- yspacing : { float, str } The spacing in the latitudinal direction. To set the spacing to be the same as the ticks, set this to 'tick' ''' if yspacing == 'tick': self.y_auto_spacing = True elif np.isreal(yspacing): self.y_auto_spacing = False if self._wcs.yaxis_coord_type in ['longitude', 'latitude']: self.y_grid_spacing = au.Angle( degrees=yspacing, latitude=self._wcs.yaxis_coord_type == 'latitude') else: self.y_grid_spacing = yspacing else: raise ValueError("Grid spacing should be a scalar or 'tick'") self._update() @auto_refresh def set_color(self, color): ''' Set the color of the grid lines Parameters ---------- color : str The color of the grid lines ''' if self._grid: self._grid.set_edgecolor(color) else: self.default_color = color @auto_refresh def set_alpha(self, alpha): ''' Set the alpha (transparency) of the grid lines Parameters ---------- alpha : float The alpha value of the grid. This should be a floating point value between 0 and 1, where 0 is completely transparent, and 1 is completely opaque. ''' if self._grid: self._grid.set_alpha(alpha) else: self.default_alpha = alpha @auto_refresh def set_linewidth(self, linewidth): self._grid.set_linewidth(linewidth) @auto_refresh def set_linestyle(self, linestyle): self._grid.set_linestyle(linestyle) @auto_refresh def show(self): if self._grid: self._grid.set_visible(True) else: self._active = True self._update() self.set_color(self.default_color) self.set_alpha(self.default_alpha) @auto_refresh def hide(self): self._grid.set_visible(False) @auto_refresh def _update(self, *args): self._update_norefresh(*args) def _update_norefresh(self, *args): if not self._active: return self.ax if len(args) == 1: if id(self.ax) != id(args[0]): raise Exception("ax ids should match") lines = [] # Set x grid spacing if self.x_auto_spacing: if self.ax.xaxis.apl_auto_tick_spacing: xspacing = default_spacing(self.ax, 'x', self.ax.xaxis.apl_label_form) else: xspacing = self.ax.xaxis.apl_tick_spacing else: xspacing = self.x_grid_spacing if xspacing is None: warnings.warn("Could not determine x tick spacing - grid cannot be drawn") return if self._wcs.xaxis_coord_type in ['longitude', 'latitude']: xspacing = xspacing.todegrees() # Set y grid spacing if self.y_auto_spacing: if self.ax.yaxis.apl_auto_tick_spacing: yspacing = default_spacing(self.ax, 'y', self.ax.yaxis.apl_label_form) else: yspacing = self.ax.yaxis.apl_tick_spacing else: yspacing = self.y_grid_spacing if yspacing is None: warnings.warn("Could not determine y tick spacing - grid cannot be drawn") return if self._wcs.yaxis_coord_type in ['longitude', 'latitude']: yspacing = yspacing.todegrees() # Find x lines that intersect with axes grid_x_i, grid_y_i = find_intersections(self.ax, 'x', xspacing) # Ensure that longitudes are between 0 and 360, and latitudes between # -90 and 90 if self._wcs.xaxis_coord_type == 'longitude': grid_x_i = np.mod(grid_x_i, 360.) elif self._wcs.xaxis_coord_type == 'latitude': grid_x_i = np.mod(grid_x_i + 90., 180.) - 90. if self._wcs.yaxis_coord_type == 'longitude': grid_y_i = np.mod(grid_y_i, 360.) elif self._wcs.yaxis_coord_type == 'latitude': grid_y_i = np.mod(grid_y_i + 90., 180.) - 90. # If we are dealing with longitude/latitude then can search all # neighboring grid lines to see if there are any closed longitude # lines if self._wcs.xaxis_coord_type == 'latitude' and self._wcs.yaxis_coord_type == 'longitude' and len(grid_x_i) > 0: gx = grid_x_i.min() while True: gx -= xspacing xpix, ypix = wcs_util.world2pix(self._wcs, gx, 0.) if in_plot(self.ax, xpix, ypix) and gx >= -90.: grid_x_i = np.hstack([grid_x_i, gx, gx]) grid_y_i = np.hstack([grid_y_i, 0., 360.]) else: break gx = grid_x_i.max() while True: gx += xspacing xpix, ypix = wcs_util.world2pix(self._wcs, gx, 0.) if in_plot(self.ax, xpix, ypix) and gx <= +90.: grid_x_i = np.hstack([grid_x_i, gx, gx]) grid_y_i = np.hstack([grid_y_i, 0., 360.]) else: break # Plot those lines for gx in np.unique(grid_x_i): for line in plot_grid_x(self.ax, grid_x_i, grid_y_i, gx): lines.append(line) # Find y lines that intersect with axes grid_x_i, grid_y_i = find_intersections(self.ax, 'y', yspacing) if self._wcs.xaxis_coord_type == 'longitude': grid_x_i = np.mod(grid_x_i, 360.) elif self._wcs.xaxis_coord_type == 'latitude': grid_x_i = np.mod(grid_x_i + 90., 180.) - 90. if self._wcs.yaxis_coord_type == 'longitude': grid_y_i = np.mod(grid_y_i, 360.) elif self._wcs.yaxis_coord_type == 'latitude': grid_y_i = np.mod(grid_y_i + 90., 180.) - 90. # If we are dealing with longitude/latitude then can search all # neighboring grid lines to see if there are any closed longitude # lines if (self._wcs.xaxis_coord_type == 'longitude' and self._wcs.yaxis_coord_type == 'latitude' and len(grid_y_i) > 0): gy = grid_y_i.min() while True: gy -= yspacing xpix, ypix = wcs_util.world2pix(self._wcs, 0., gy) if in_plot(self.ax, xpix, ypix) and gy >= -90.: grid_x_i = np.hstack([grid_x_i, 0., 360.]) grid_y_i = np.hstack([grid_y_i, gy, gy]) else: break gy = grid_y_i.max() while True: gy += yspacing xpix, ypix = wcs_util.world2pix(self._wcs, 0., gy) if in_plot(self.ax, xpix, ypix) and gy <= +90.: grid_x_i = np.hstack([grid_x_i, 0., 360.]) grid_y_i = np.hstack([grid_y_i, gy, gy]) else: break # Plot those lines for gy in np.unique(grid_y_i): for line in plot_grid_y(self.ax, grid_x_i, grid_y_i, gy): lines.append(line) if self._grid: self._grid.set_verts(lines) else: self._grid = LineCollection(lines, transOffset=self.ax.transData) self.ax.add_collection(self._grid, False) return self.ax def plot_grid_y(ax, grid_x, grid_y, gy, alpha=0.5): '''Plot a single grid line in the y direction''' wcs = ax._wcs lines_out = [] # Find intersections that correspond to latitude lat0 index = np.where(grid_y == gy) # Produce sorted array of the longitudes of all intersections grid_x_sorted = np.sort(grid_x[index]) # If coordinate type is a latitude or longitude, also need to check if # end-points fall inside the plot if wcs.xaxis_coord_type == 'latitude': if not np.any(grid_x_sorted == -90): xpix, ypix = wcs_util.world2pix(wcs, max(grid_x_sorted[0] - 1., -90.), gy) if in_plot(ax, xpix, ypix): grid_x_sorted = np.hstack([-90., grid_x_sorted]) if not np.any(grid_x_sorted == +90): xpix, ypix = wcs_util.world2pix(wcs, min(grid_x_sorted[-1] + 1., +90.), gy) if in_plot(ax, xpix, ypix): grid_x_sorted = np.hstack([grid_x_sorted, +90.]) elif wcs.xaxis_coord_type == 'longitude': if not np.any(grid_x_sorted == 0.): xpix, ypix = wcs_util.world2pix(wcs, max(grid_x_sorted[0] - 1., 0.), gy) if in_plot(ax, xpix, ypix): grid_x_sorted = np.hstack([0., grid_x_sorted]) if not np.any(grid_x_sorted == 360.): xpix, ypix = wcs_util.world2pix(wcs, min(grid_x_sorted[-1] + 1., 360.), gy) if in_plot(ax, xpix, ypix): grid_x_sorted = np.hstack([grid_x_sorted, 360.]) # Check if the first mid-point with coordinates is inside the viewport xpix, ypix = wcs_util.world2pix(wcs, (grid_x_sorted[0] + grid_x_sorted[1]) / 2., gy) if not in_plot(ax, xpix, ypix): grid_x_sorted = np.roll(grid_x_sorted, 1) # Check that number of grid points is even if len(grid_x_sorted) % 2 == 1: warnings.warn("Unexpected number of grid points - x grid lines cannot be drawn") return [] # Cycle through intersections for i in range(0, len(grid_x_sorted), 2): grid_x_min = grid_x_sorted[i] grid_x_max = grid_x_sorted[i + 1] x_world = math_util.complete_range(grid_x_min, grid_x_max, 100) y_world = np.repeat(gy, len(x_world)) x_pix, y_pix = wcs_util.world2pix(wcs, x_world, y_world) lines_out.append(list(zip(x_pix, y_pix))) return lines_out def plot_grid_x(ax, grid_x, grid_y, gx, alpha=0.5): '''Plot a single longitude line''' wcs = ax._wcs lines_out = [] # Find intersections that correspond to longitude gx index = np.where(grid_x == gx) # Produce sorted array of the latitudes of all intersections grid_y_sorted = np.sort(grid_y[index]) # If coordinate type is a latitude or longitude, also need to check if # end-points fall inside the plot if wcs.yaxis_coord_type == 'latitude': if not np.any(grid_y_sorted == -90): xpix, ypix = wcs_util.world2pix(wcs, gx, max(grid_y_sorted[0] - 1., -90.)) if in_plot(ax, xpix, ypix): grid_y_sorted = np.hstack([-90., grid_y_sorted]) if not np.any(grid_y_sorted == +90): xpix, ypix = wcs_util.world2pix(wcs, gx, min(grid_y_sorted[-1] + 1., +90.)) if in_plot(ax, xpix, ypix): grid_y_sorted = np.hstack([grid_y_sorted, +90.]) elif wcs.yaxis_coord_type == 'longitude': if not np.any(grid_y_sorted == 0.): xpix, ypix = wcs_util.world2pix(wcs, gx, max(grid_y_sorted[0] - 1., 0.)) if in_plot(ax, xpix, ypix): grid_y_sorted = np.hstack([0., grid_y_sorted]) if not np.any(grid_y_sorted == 360.): xpix, ypix = wcs_util.world2pix(wcs, gx, min(grid_y_sorted[-1] + 1., 360.)) if in_plot(ax, xpix, ypix): grid_y_sorted = np.hstack([grid_y_sorted, 360.]) # Check if the first mid-point with coordinates is inside the viewport xpix, ypix = wcs_util.world2pix(wcs, gx, (grid_y_sorted[0] + grid_y_sorted[1]) / 2.) if not in_plot(ax, xpix, ypix): grid_y_sorted = np.roll(grid_y_sorted, 1) # Check that number of grid points is even if len(grid_y_sorted) % 2 == 1: warnings.warn("Unexpected number of grid points - y grid lines cannot be drawn") return [] # Cycle through intersections for i in range(0, len(grid_y_sorted), 2): grid_y_min = grid_y_sorted[i] grid_y_max = grid_y_sorted[i + 1] y_world = math_util.complete_range(grid_y_min, grid_y_max, 100) x_world = np.repeat(gx, len(y_world)) x_pix, y_pix = wcs_util.world2pix(wcs, x_world, y_world) lines_out.append(list(zip(x_pix, y_pix))) return lines_out def in_plot(ax, x_pix, y_pix): '''Check whether a given point is in a plot''' xmin, xmax = ax.xaxis.get_view_interval() ymin, ymax = ax.yaxis.get_view_interval() return (x_pix > xmin + 0.5 and x_pix < xmax + 0.5 and y_pix > ymin + 0.5 and y_pix < ymax + 0.5) def find_intersections(ax, coord, spacing): ''' Find intersections of a given coordinate with all axes Parameters ---------- ax : The matplotlib axis instance for the figure. coord : { 'x', 'y' } The coordinate for which we are looking for ticks. spacing : float The spacing along the axis. ''' wcs = ax._wcs xmin, xmax = ax.xaxis.get_view_interval() ymin, ymax = ax.yaxis.get_view_interval() options = dict(mode='xy', xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax) # Initialize arrays x, y = [], [] # Bottom X axis (labels_x, labels_y, world_x, world_y) = tick_positions( wcs, spacing, 'x', coord, farside=False, **options) x.extend(world_x) y.extend(world_y) # Top X axis (labels_x, labels_y, world_x, world_y) = tick_positions( wcs, spacing, 'x', coord, farside=True, **options) x.extend(world_x) y.extend(world_y) # Left Y axis (labels_x, labels_y, world_x, world_y) = tick_positions( wcs, spacing, 'y', coord, farside=False, **options) x.extend(world_x) y.extend(world_y) # Right Y axis (labels_x, labels_y, world_x, world_y) = tick_positions( wcs, spacing, 'y', coord, farside=True, **options) x.extend(world_x) y.extend(world_y) return np.array(x), np.array(y) APLpy-1.1.1/aplpy/header.py0000644000077000000240000000471512774746217015374 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division from astropy import log def check(header, convention=None, dimensions=[0, 1]): ix = dimensions[0] + 1 iy = dimensions[1] + 1 # If header does not contain CTYPE keywords, assume that the WCS is # missing or incomplete, and replace it with a 1-to-1 pixel mapping if 'CTYPE%i' % ix not in header or 'CTYPE%i' % iy not in header: log.warning("No WCS information found in header - using pixel coordinates") header['CTYPE%i' % ix] = 'PIXEL' header['CTYPE%i' % iy] = 'PIXEL' header['CRVAL%i' % ix] = 0. header['CRVAL%i' % iy] = 0. header['CRPIX%i' % ix] = 0. header['CRPIX%i' % iy] = 0. header['CDELT%i' % ix] = 1. header['CDELT%i' % iy] = 1. if header['CTYPE%i' % ix][4:] == '-CAR' and header['CTYPE%i' % iy][4:] == '-CAR': if header['CTYPE%i' % ix][:4] == 'DEC-' or header['CTYPE%i' % ix][1:4] == 'LAT': ilon = iy ilat = ix elif header['CTYPE%i' % iy][:4] == 'DEC-' or header['CTYPE%i' % iy][1:4] == 'LAT': ilon = ix ilat = iy else: ilon = None ilat = None if ilat is not None and header['CRVAL%i' % ilat] != 0: if convention == 'calabretta': pass # we don't need to do anything elif convention == 'wells': if 'CDELT%i' % ilat not in header: raise Exception("Need CDELT%i to be present for wells convention" % ilat) crpix = header['CRPIX%i' % ilat] crval = header['CRVAL%i' % ilat] cdelt = header['CDELT%i' % ilat] crpix = crpix - crval / cdelt header['CRPIX%i' % ilat] = crpix header['CRVAL%i' % ilat] = 0. else: raise Exception('''WARNING: projection is Plate Caree (-CAR) and CRVALy is not zero. This can be interpreted either according to Wells (1981) or Calabretta (2002). The former defines the projection as rectilinear regardless of the value of CRVALy, whereas the latter defines the projection as rectilinear only when CRVALy is zero. You will need to specify the convention to assume by setting either convention='wells' or convention='calabretta' when initializing the FITSFigure instance. ''') return header APLpy-1.1.1/aplpy/image_util.py0000644000077000000240000000712012536551501016236 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import numpy as np from astropy import log from . import math_util as m class interp1d(object): def __init__(self, x, y): self.x = x self.y = y self.dy = np.zeros(y.shape, dtype=y.dtype) self.dy[:-1] = (self.y[1:] - self.y[:-1]) / (self.x[1:] - self.x[:-1]) self.dy[-1] = self.dy[-2] def __call__(self, x_new): ipos = np.searchsorted(self.x, x_new) if m.isnumeric(x_new): if ipos == 0: ipos = 1 if ipos == len(self.x): ipos = len(self.x) - 1 else: ipos[ipos == 0] = 1 ipos[ipos == len(self.x)] = len(self.x) - 1 ipos = ipos - 1 return (x_new - self.x[ipos]) * self.dy[ipos] + self.y[ipos] def resample(array, factor): nx, ny = np.shape(array) nx_new = nx // factor ny_new = ny // factor array2 = np.zeros((nx_new, ny)) for i in range(nx_new): array2[i, :] = np.mean(array[i * factor:(i + 1) * factor, :], axis=0) array3 = np.zeros((nx_new, ny_new)) for j in range(ny_new): array3[:, j] = np.mean(array2[:, j * factor:(j + 1) * factor], axis=1) return array3 def percentile_function(array): if np.all(np.isnan(array) | np.isinf(array)): log.warning("Image contains only NaN or Inf values") return lambda x: 0 array = array.ravel() array = array[np.where(np.isnan(array) == False)] array = array[np.where(np.isinf(array) == False)] n_total = np.shape(array)[0] if n_total == 0: def return_zero(x): return 0 return return_zero elif n_total == 1: def return_single(x): return array[0] return return_single array = np.sort(array) x = np.linspace(0., 100., num=n_total) spl = interp1d(x=x, y=array) if n_total > 10000: x = np.linspace(0., 100., num=10000) spl = interp1d(x=x, y=spl(x)) array = None return spl def stretch(array, function, exponent=2, midpoint=None): if function == 'linear': return array elif function == 'log': if not m.isnumeric(midpoint): midpoint = 0.05 return np.log10(array / midpoint + 1.) / np.log10(1. / midpoint + 1.) elif function == 'sqrt': return np.sqrt(array) elif function == 'arcsinh': if not m.isnumeric(midpoint): midpoint = -0.033 return np.arcsinh(array / midpoint) / np.arcsinh(1. / midpoint) elif function == 'power': return np.power(array, exponent) else: raise Exception("Unknown function : " + function) def _matplotlib_pil_bug_present(): """ Determine whether PIL images should be pre-flipped due to a bug in Matplotlib. Prior to Matplotlib 1.2.0, RGB images provided as PIL objects were oriented wrongly. This function tests whether the bug is present. """ from matplotlib.image import pil_to_array try: from PIL import Image except: import Image from astropy import log array1 = np.array([[1,2],[3,4]], dtype=np.uint8) image = Image.fromarray(array1) array2 = pil_to_array(image) if np.all(array1 == array2): log.debug("PIL Image flipping bug not present in Matplotlib") return False elif np.all(array1 == array2[::-1,:]): log.debug("PIL Image flipping bug detected in Matplotlib") return True else: log.warning("Could not properly determine Matplotlib behavior for RGB images - image may be flipped incorrectly") return False APLpy-1.1.1/aplpy/labels.py0000644000077000000240000003757712654137152015407 0ustar tomstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, division, unicode_literals import warnings import numpy as np import matplotlib.pyplot as mpl from matplotlib.font_manager import FontProperties from . import wcs_util from . import angle_util as au from .decorators import auto_refresh, fixdocstring class TickLabels(object): def __init__(self, parent): # Store references to axes self._ax1 = parent._ax1 self._ax2 = parent._ax2 self._wcs = parent._wcs self._figure = parent._figure # Save plotting parameters (required for @auto_refresh) self._parameters = parent._parameters # Set font self._label_fontproperties = FontProperties() self.set_style('plain') system, equinox, units = wcs_util.system(self._wcs) # Set default label format if self._wcs.xaxis_coord_type in ['longitude', 'latitude']: if system['name'] == 'equatorial': if self._wcs.xaxis_coord_type == 'longitude': self.set_xformat("hh:mm:ss.ss") else: self.set_xformat("dd:mm:ss.s") else: self.set_xformat("ddd.dddd") else: self.set_xformat('%g') if self._wcs.yaxis_coord_type in ['longitude', 'latitude']: if system['name'] == 'equatorial': if self._wcs.yaxis_coord_type == 'longitude': self.set_yformat("hh:mm:ss.ss") else: self.set_yformat("dd:mm:ss.s") else: self.set_yformat("ddd.dddd") else: self.set_yformat('%g') # Set major tick formatters fx1 = WCSFormatter(wcs=self._wcs, coord='x') fy1 = WCSFormatter(wcs=self._wcs, coord='y') self._ax1.xaxis.set_major_formatter(fx1) self._ax1.yaxis.set_major_formatter(fy1) fx2 = mpl.NullFormatter() fy2 = mpl.NullFormatter() self._ax2.xaxis.set_major_formatter(fx2) self._ax2.yaxis.set_major_formatter(fy2) # Cursor display self._ax1._cursor_world = True self._figure.canvas.mpl_connect('key_press_event', self._set_cursor_prefs) @auto_refresh def set_xformat(self, format): ''' Set the format of the x-axis tick labels. If the x-axis type is ``longitude`` or ``latitude``, then the options are: * ``ddd.ddddd`` - decimal degrees, where the number of decimal places can be varied * ``hh`` or ``dd`` - hours (or degrees) * ``hh:mm`` or ``dd:mm`` - hours and minutes (or degrees and arcminutes) * ``hh:mm:ss`` or ``dd:mm:ss`` - hours, minutes, and seconds (or degrees, arcminutes, and arcseconds) * ``hh:mm:ss.ss`` or ``dd:mm:ss.ss`` - hours, minutes, and seconds (or degrees, arcminutes, and arcseconds), where the number of decimal places can be varied. If the x-axis type is ``scalar``, then the format should be a valid python string format beginning with a ``%``. If one of these arguments is not specified, the format for that axis is left unchanged. ''' if self._wcs.xaxis_coord_type in ['longitude', 'latitude']: if format.startswith('%'): raise Exception("Cannot specify Python format for longitude or latitude") try: if not self._ax1.xaxis.apl_auto_tick_spacing: au._check_format_spacing_consistency(format, self._ax1.xaxis.apl_tick_spacing) except au.InconsistentSpacing: warnings.warn("WARNING: Requested label format is not accurate enough to display ticks. The label format will not be changed.") return else: if not format.startswith('%'): raise Exception("For scalar tick labels, format should be a Python format beginning with %") self._ax1.xaxis.apl_label_form = format self._ax2.xaxis.apl_label_form = format @auto_refresh def set_yformat(self, format): ''' Set the format of the y-axis tick labels. If the y-axis type is ``longitude`` or ``latitude``, then the options are: * ``ddd.ddddd`` - decimal degrees, where the number of decimal places can be varied * ``hh`` or ``dd`` - hours (or degrees) * ``hh:mm`` or ``dd:mm`` - hours and minutes (or degrees and arcminutes) * ``hh:mm:ss`` or ``dd:mm:ss`` - hours, minutes, and seconds (or degrees, arcminutes, and arcseconds) * ``hh:mm:ss.ss`` or ``dd:mm:ss.ss`` - hours, minutes, and seconds (or degrees, arcminutes, and arcseconds), where the number of decimal places can be varied. If the y-axis type is ``scalar``, then the format should be a valid python string format beginning with a ``%``. If one of these arguments is not specified, the format for that axis is left unchanged. ''' if self._wcs.yaxis_coord_type in ['longitude', 'latitude']: if format.startswith('%'): raise Exception("Cannot specify Python format for longitude or latitude") try: if not self._ax1.yaxis.apl_auto_tick_spacing: au._check_format_spacing_consistency(format, self._ax1.yaxis.apl_tick_spacing) except au.InconsistentSpacing: warnings.warn("WARNING: Requested label format is not accurate enough to display ticks. The label format will not be changed.") return else: if not format.startswith('%'): raise Exception("For scalar tick labels, format should be a Python format beginning with %") self._ax1.yaxis.apl_label_form = format self._ax2.yaxis.apl_label_form = format @auto_refresh def set_style(self, style): """ Set the format of the x-axis tick labels. This can be 'colons' or 'plain': * 'colons' uses colons as separators, for example 31:41:59.26 +27:18:28.1 * 'plain' uses letters and symbols as separators, for example 31h41m59.26s +27º18'28.1" """ if style == 'latex': warnings.warn("latex has now been merged with plain - whether or not to use LaTeX is controlled through set_system_latex") style = 'plain' if not style in ['colons', 'plain']: raise Exception("Label style should be one of colons/plain") self._ax1.xaxis.apl_labels_style = style self._ax1.yaxis.apl_labels_style = style self._ax2.xaxis.apl_labels_style = style self._ax2.yaxis.apl_labels_style = style @auto_refresh @fixdocstring def set_font(self, family=None, style=None, variant=None, stretch=None, weight=None, size=None, fontproperties=None): """ Set the font of the tick labels. Parameters ---------- common: family, style, variant, stretch, weight, size, fontproperties Notes ----- Default values are set by matplotlib or previously set values if set_font has already been called. Global default values can be set by editing the matplotlibrc file. """ if family: self._label_fontproperties.set_family(family) if style: self._label_fontproperties.set_style(style) if variant: self._label_fontproperties.set_variant(variant) if stretch: self._label_fontproperties.set_stretch(stretch) if weight: self._label_fontproperties.set_weight(weight) if size: self._label_fontproperties.set_size(size) if fontproperties: self._label_fontproperties = fontproperties for tick in self._ax1.get_xticklabels(): tick.set_fontproperties(self._label_fontproperties) for tick in self._ax1.get_yticklabels(): tick.set_fontproperties(self._label_fontproperties) for tick in self._ax2.get_xticklabels(): tick.set_fontproperties(self._label_fontproperties) for tick in self._ax2.get_yticklabels(): tick.set_fontproperties(self._label_fontproperties) @auto_refresh def show(self): """ Show the x- and y-axis tick labels. """ self.show_x() self.show_y() @auto_refresh def hide(self): """ Hide the x- and y-axis tick labels. """ self.hide_x() self.hide_y() @auto_refresh def show_x(self): """ Show the x-axis tick labels. """ for tick in self._ax1.get_xticklabels(): tick.set_visible(True) for tick in self._ax2.get_xticklabels(): tick.set_visible(True) @auto_refresh def hide_x(self): """ Hide the x-axis tick labels. """ for tick in self._ax1.get_xticklabels(): tick.set_visible(False) for tick in self._ax2.get_xticklabels(): tick.set_visible(False) @auto_refresh def show_y(self): """ Show the y-axis tick labels. """ for tick in self._ax1.get_yticklabels(): tick.set_visible(True) for tick in self._ax2.get_yticklabels(): tick.set_visible(True) @auto_refresh def hide_y(self): """ Hide the y-axis tick labels. """ for tick in self._ax1.get_yticklabels(): tick.set_visible(False) for tick in self._ax2.get_yticklabels(): tick.set_visible(False) @auto_refresh def set_xposition(self, position): """ Set the position of the x-axis tick labels ('top' or 'bottom') """ if position == 'bottom': fx1 = WCSFormatter(wcs=self._wcs, coord='x') self._ax1.xaxis.set_major_formatter(fx1) fx2 = mpl.NullFormatter() self._ax2.xaxis.set_major_formatter(fx2) elif position == 'top': fx1 = mpl.NullFormatter() self._ax1.xaxis.set_major_formatter(fx1) fx2 = WCSFormatter(wcs=self._wcs, coord='x') self._ax2.xaxis.set_major_formatter(fx2) else: raise ValueError("position should be one of 'top' or 'bottom'") @auto_refresh def set_yposition(self, position): """ Set the position of the y-axis tick labels ('left' or 'right') """ if position == 'left': fy1 = WCSFormatter(wcs=self._wcs, coord='y') self._ax1.yaxis.set_major_formatter(fy1) fy2 = mpl.NullFormatter() self._ax2.yaxis.set_major_formatter(fy2) elif position == 'right': fy1 = mpl.NullFormatter() self._ax1.yaxis.set_major_formatter(fy1) fy2 = WCSFormatter(wcs=self._wcs, coord='y') self._ax2.yaxis.set_major_formatter(fy2) else: raise ValueError("position should be one of 'left' or 'right'") def _set_cursor_prefs(self, event, **kwargs): if event.key == 'c': self._ax1._cursor_world = not self._ax1._cursor_world def _cursor_position(self, x, y): xaxis = self._ax1.xaxis yaxis = self._ax1.yaxis if self._ax1._cursor_world: xw, yw = wcs_util.pix2world(self._wcs, x, y) if self._wcs.xaxis_coord_type in ['longitude', 'latitude']: xw = au.Angle(degrees=xw, latitude=self._wcs.xaxis_coord_type == 'latitude') hours = 'h' in xaxis.apl_label_form if hours: xw = xw.tohours() if xaxis.apl_labels_style in ['plain', 'latex']: sep = ('d', 'm', 's') if hours: sep = ('h', 'm', 's') elif xaxis.apl_labels_style == 'colons': sep = (':', ':', '') xlabel = xw.tostringlist(format=xaxis.apl_label_form, sep=sep) xlabel = "".join(xlabel) else: xlabel = xaxis.apl_label_form % xw if self._wcs.yaxis_coord_type in ['longitude', 'latitude']: yw = au.Angle(degrees=yw, latitude=self._wcs.yaxis_coord_type == 'latitude') hours = 'h' in yaxis.apl_label_form if hours: yw = yw.tohours() if yaxis.apl_labels_style in ['plain', 'latex']: sep = ('d', 'm', 's') if hours: sep = ('h', 'm', 's') elif yaxis.apl_labels_style == 'colons': sep = (':', ':', '') ylabel = yw.tostringlist(format=yaxis.apl_label_form, sep=sep) ylabel = "".join(ylabel) else: ylabel = yaxis.apl_label_form % yw return "%s %s (world)" % (xlabel, ylabel) else: return "%g %g (pixel)" % (x, y) class WCSFormatter(mpl.Formatter): def __init__(self, wcs=False, coord='x'): self._wcs = wcs self.coord = coord def __call__(self, x, pos=None): """ Return the format for tick val x at position pos; pos=None indicated unspecified """ self.coord_type = self._wcs.xaxis_coord_type if self.coord == 'x' else self._wcs.yaxis_coord_type if self.coord_type in ['longitude', 'latitude']: au._check_format_spacing_consistency(self.axis.apl_label_form, self.axis.apl_tick_spacing) hours = 'h' in self.axis.apl_label_form if self.axis.apl_labels_style == 'plain': if mpl.rcParams['text.usetex']: label_style = 'plain_tex' else: label_style = 'plain_notex' else: label_style = self.axis.apl_labels_style if label_style == 'plain_notex': sep = ('\u00b0', "'", '"') if hours: sep = ('h', 'm', 's') elif label_style == 'colons': sep = (':', ':', '') elif label_style == 'plain_tex': if hours: sep = ('^{h}', '^{m}', '^{s}') else: sep = ('^{\circ}', '^{\prime}', '^{\prime\prime}') ipos = np.argmin(np.abs(self.axis.apl_tick_positions_pix - x)) label = self.axis.apl_tick_spacing * self.axis.apl_tick_positions_world[ipos] if hours: label = label.tohours() label = label.tostringlist(format=self.axis.apl_label_form, sep=sep) # Check if neighboring label is similar and if so whether some # elements of the current label are redundant and can be dropped. # This should only be done for sexagesimal coordinates if len(label) > 1: if self.coord == x or self.axis.apl_tick_positions_world[ipos] > 0: comp_ipos = ipos - 1 else: comp_ipos = ipos + 1 if comp_ipos >= 0 and comp_ipos <= len(self.axis.apl_tick_positions_pix) - 1: comp_label = self.axis.apl_tick_spacing * self.axis.apl_tick_positions_world[comp_ipos] if hours: comp_label = comp_label.tohours() comp_label = comp_label.tostringlist(format=self.axis.apl_label_form, sep=sep) for iter in range(len(label)): if comp_label[0] == label[0]: label.pop(0) comp_label.pop(0) else: break else: ipos = np.argmin(np.abs(self.axis.apl_tick_positions_pix - x)) label = self.axis.apl_tick_spacing * self.axis.apl_tick_positions_world[ipos] label = self.axis.apl_label_form % label if mpl.rcParams['text.usetex']: return "$" + "".join(label) + "$" else: return "".join(label) APLpy-1.1.1/aplpy/layers.py0000644000077000000240000001270212536551502015421 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division from matplotlib.contour import ContourSet from matplotlib.collections import RegularPolyCollection, \ PatchCollection, CircleCollection, LineCollection from .regions import ArtistCollection from .decorators import auto_refresh class Layers(object): def __init__(self): pass def _layer_type(self, layer): if isinstance(self._layers[layer], ContourSet): return 'contour' elif isinstance(self._layers[layer], RegularPolyCollection): return 'collection' elif isinstance(self._layers[layer], PatchCollection): return 'collection' elif isinstance(self._layers[layer], CircleCollection): return 'collection' elif isinstance(self._layers[layer], LineCollection): return 'collection' elif isinstance(self._layers[layer], ArtistCollection): return 'collection' elif hasattr(self._layers[layer], 'remove') and hasattr(self._layers[layer], 'get_visible') and hasattr(self._layers[layer], 'set_visible'): return 'collection' else: raise Exception("Unknown layer type: " + \ str(type(self._layers[layer]))) def _initialize_layers(self): self._layers = {} self._contour_counter = 0 self._vector_counter = 0 self._scatter_counter = 0 self._circle_counter = 0 self._ellipse_counter = 0 self._rectangle_counter = 0 self._linelist_counter = 0 self._region_counter = 0 self._label_counter = 0 self._poly_counter = 0 def list_layers(self): ''' Print a list of layers to standard output. ''' layers_list = [] for layer in self._layers: layer_type = self._layer_type(layer) if layer_type == 'contour': visible = self._layers[layer].collections[0].get_visible() elif layer_type == 'collection': visible = self._layers[layer].get_visible() layers_list.append({'name': layer, 'visible': visible}) n_layers = len(layers_list) if n_layers == 0: print("\n There are no layers in this figure") else: if n_layers == 1: print("\n There is one layer in this figure:\n") else: print("\n There are " + str(n_layers) + \ " layers in this figure:\n") for layer in layers_list: if layer['visible']: print(" -> " + layer['name']) else: print(" -> " + layer['name'] + " (hidden)") @auto_refresh def remove_layer(self, layer, raise_exception=True): ''' Remove a layer. Parameters ---------- layer : str The name of the layer to remove ''' if layer in self._layers: layer_type = self._layer_type(layer) if layer_type == 'contour': for contour in self._layers[layer].collections: contour.remove() self._layers.pop(layer) elif layer_type == 'collection': self._layers[layer].remove() self._layers.pop(layer) if (layer + '_txt') in self._layers: self._layers[layer + '_txt'].remove() self._layers.pop(layer + '_txt') else: if raise_exception: raise Exception("Layer " + layer + " does not exist") @auto_refresh def hide_layer(self, layer, raise_exception=True): ''' Hide a layer. This differs from remove_layer in that if a layer is hidden it can be shown again using show_layer. Parameters ---------- layer : str The name of the layer to hide ''' if layer in self._layers: layer_type = self._layer_type(layer) if layer_type == 'contour': for contour in self._layers[layer].collections: contour.set_visible(False) elif layer_type == 'collection': self._layers[layer].set_visible(False) else: if raise_exception: raise Exception("Layer " + layer + " does not exist") @auto_refresh def show_layer(self, layer, raise_exception=True): ''' Show a layer. This shows a layer previously hidden with hide_layer Parameters ---------- layer : str The name of the layer to show ''' if layer in self._layers: layer_type = self._layer_type(layer) if layer_type == 'contour': for contour in self._layers[layer].collections: contour.set_visible(True) elif layer_type == 'collection': self._layers[layer].set_visible(True) else: if raise_exception: raise Exception("Layer " + layer + " does not exist") def get_layer(self, layer, raise_exception=True): ''' Return a layer object. Parameters ---------- layer : str The name of the layer to return ''' if layer in self._layers: return self._layers[layer] else: if raise_exception: raise Exception("Layer " + layer + " does not exist") APLpy-1.1.1/aplpy/math_util.py0000644000077000000240000000247712536551502016120 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import numpy as np def isnumeric(value): return type(value) in [float, int, np.int8, np.int16, np.int32, \ np.float32, np.float64] def smart_range(array): array.sort() minval = 360. i1 = 0 i2 = 0 for i in range(0, np.size(array) - 1): if 360. - abs(array[i + 1] - array[i]) < minval: minval = 360. - abs(array[i + 1] - array[i]) i1 = i + 1 i2 = i if(max(array) - min(array) < minval): i1 = 0 i2 = np.size(array) - 1 x_min = array[i1] x_max = array[i2] if(x_min > x_max): x_min = x_min - 360. return x_min, x_max def complete_range(xmin, xmax, spacing): if(xmax - xmin < 1): spacing = 10 xstep = (xmax - xmin) / float(spacing) r = np.arange(xmin, xmax, xstep) if(np.any(r >= xmax)): return r else: return np.hstack([r, xmax]) def closest(array, a): ipos = np.argmin(np.abs(a - array)) return array[ipos] def divisors(n, dup=False): divisors = [] i = 0 if n == 1: return {1: 1} while 1: i += 1 if dup: break if i == n + 1: break if n % i == 0: divisors[i:i + 1] = [i] return np.array(divisors) APLpy-1.1.1/aplpy/normalize.py0000644000077000000240000001310312536551502016116 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division # The APLpyNormalize class is largely based on code provided by Sarah Graves. import numpy as np import numpy.ma as ma import matplotlib.cbook as cbook from matplotlib.colors import Normalize class APLpyNormalize(Normalize): ''' A Normalize class for imshow that allows different stretching functions for astronomical images. ''' def __init__(self, stretch='linear', exponent=5, vmid=None, vmin=None, vmax=None, clip=False): ''' Initalize an APLpyNormalize instance. Parameters ---------- vmin : None or float, optional Minimum pixel value to use for the scaling. vmax : None or float, optional Maximum pixel value to use for the scaling. stretch : { 'linear', 'log', 'sqrt', 'arcsinh', 'power' }, optional The stretch function to use (default is 'linear'). vmid : None or float, optional Mid-pixel value used for the log and arcsinh stretches. If set to None, a default value is picked. exponent : float, optional if self.stretch is set to 'power', this is the exponent to use. clip : str, optional If clip is True and the given value falls outside the range, the returned value will be 0 or 1, whichever is closer. ''' if vmax < vmin: raise Exception("vmax should be larger than vmin") # Call original initalization routine Normalize.__init__(self, vmin=vmin, vmax=vmax, clip=clip) # Save parameters self.stretch = stretch self.exponent = exponent if stretch == 'power' and np.equal(self.exponent, None): raise Exception("For stretch=='power', an exponent should be specified") if np.equal(vmid, None): if stretch == 'log': if vmin > 0: self.midpoint = vmax / vmin else: raise Exception("When using a log stretch, if vmin < 0, then vmid has to be specified") elif stretch == 'arcsinh': self.midpoint = -1. / 30. else: self.midpoint = None else: if stretch == 'log': if vmin < vmid: raise Exception("When using a log stretch, vmin should be larger than vmid") self.midpoint = (vmax - vmid) / (vmin - vmid) elif stretch == 'arcsinh': self.midpoint = (vmid - vmin) / (vmax - vmin) else: self.midpoint = None def __call__(self, value, clip=None): #read in parameters method = self.stretch exponent = self.exponent midpoint = self.midpoint # ORIGINAL MATPLOTLIB CODE if clip is None: clip = self.clip if cbook.iterable(value): vtype = 'array' val = ma.asarray(value).astype(np.float) else: vtype = 'scalar' val = ma.array([value]).astype(np.float) self.autoscale_None(val) vmin, vmax = self.vmin, self.vmax if vmin > vmax: raise ValueError("minvalue must be less than or equal to maxvalue") elif vmin == vmax: return 0.0 * val else: if clip: mask = ma.getmask(val) val = ma.array(np.clip(val.filled(vmax), vmin, vmax), mask=mask) result = (val - vmin) * (1.0 / (vmax - vmin)) # CUSTOM APLPY CODE # Keep track of negative values negative = result < 0. if self.stretch == 'linear': pass elif self.stretch == 'log': result = ma.log10(result * (self.midpoint - 1.) + 1.) \ / ma.log10(self.midpoint) elif self.stretch == 'sqrt': result = ma.sqrt(result) elif self.stretch == 'arcsinh': result = ma.arcsinh(result / self.midpoint) \ / ma.arcsinh(1. / self.midpoint) elif self.stretch == 'power': result = ma.power(result, exponent) else: raise Exception("Unknown stretch in APLpyNormalize: %s" % self.stretch) # Now set previously negative values to 0, as these are # different from true NaN values in the FITS image result[negative] = -np.inf if vtype == 'scalar': result = result[0] return result def inverse(self, value): # ORIGINAL MATPLOTLIB CODE if not self.scaled(): raise ValueError("Not invertible until scaled") vmin, vmax = self.vmin, self.vmax # CUSTOM APLPY CODE if cbook.iterable(value): val = ma.asarray(value) else: val = value if self.stretch == 'linear': pass elif self.stretch == 'log': val = (ma.power(10., val * ma.log10(self.midpoint)) - 1.) / (self.midpoint - 1.) elif self.stretch == 'sqrt': val = val * val elif self.stretch == 'arcsinh': val = self.midpoint * \ ma.sinh(val * ma.arcsinh(1. / self.midpoint)) elif self.stretch == 'power': val = ma.power(val, (1. / self.exponent)) else: raise Exception("Unknown stretch in APLpyNormalize: %s" % self.stretch) return vmin + val * (vmax - vmin) APLpy-1.1.1/aplpy/overlays.py0000644000077000000240000005102212774746217016001 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import warnings from mpl_toolkits.axes_grid.anchored_artists \ import AnchoredEllipse, AnchoredSizeBar import numpy as np from matplotlib.patches import FancyArrowPatch from matplotlib.font_manager import FontProperties from astropy import units as u from astropy.extern import six from . import wcs_util from .decorators import auto_refresh corners = {} corners['top right'] = 1 corners['top left'] = 2 corners['bottom left'] = 3 corners['bottom right'] = 4 corners['right'] = 5 corners['left'] = 6 corners['bottom'] = 8 corners['top'] = 9 class Compass(object): def _initialize_compass(self): # Initialize compass holder self._compass = None self._compass_show = False # Set grid event handler self._ax1.callbacks.connect('xlim_changed', self.update_compass) self._ax1.callbacks.connect('ylim_changed', self.update_compass) @auto_refresh def show_compass(self, color='red', length=0.1, corner=4, frame=True): ''' Display a scalebar. Parameters ---------- length : float, optional The length of the scalebar label : str, optional Label to place above the scalebar corner : int, optional Where to place the scalebar. Acceptable values are:, 'left', 'right', 'top', 'bottom', 'top left', 'top right', 'bottom left' (default), 'bottom right' frame : str, optional Whether to display a frame behind the scalebar (default is False) kwargs Additional keyword arguments can be used to control the appearance of the scalebar, which is made up of an instance of the matplotlib Rectangle class and a an instance of the Text class. For more information on available arguments, see `Rectangle `_ and `Text `_`. In cases where the same argument exists for the two objects, the argument is passed to both the Text and Rectangle instance ''' w = 2 * length pos = {1: (1 - w, 1 - w), 2: (w, 1 - w), 3: (w, w), 4: (1 - w, w), 5: (1 - w, 0.5), 6: (w, 0.5), 7: (1 - w, 0.5), 8: (0.5, w), 9: (0.5, 1 - w)} self._compass_position = pos[corner] self._compass_length = length self._compass_color = color self._compass = None self._compass_show = True self.update_compass() @auto_refresh def update_compass(self, *args, **kwargs): if not self._compass_show: return rx, ry = self._compass_position length = self._compass_length color = self._compass_color xmin, xmax = self._ax1.get_xlim() ymin, ymax = self._ax1.get_ylim() x0 = rx * (xmax - xmin) + xmin y0 = ry * (ymax - ymin) + ymin xw, yw = self.pixel2world(x0, y0) len_pix = length * (ymax - ymin) degrees_per_pixel = wcs_util.celestial_pixel_scale(self._wcs) len_deg = len_pix * degrees_per_pixel # Should really only do tiny displacement then magnify the vectors - important if there is curvature x1, y1 = self.world2pixel(xw + len_deg / np.cos(np.radians(yw)), yw) x2, y2 = self.world2pixel(xw, yw + len_deg) if self._compass: self._compass[0].remove() self._compass[1].remove() arrow1 = FancyArrowPatch(posA=(x0, y0), posB=(x1, y1), arrowstyle='-|>', mutation_scale=20., fc=color, ec=color, shrinkA=0., shrinkB=0.) arrow2 = FancyArrowPatch(posA=(x0, y0), posB=(x2, y2), arrowstyle='-|>', mutation_scale=20., fc=color, ec=color, shrinkA=0., shrinkB=0.) self._compass = (arrow1, arrow2) self._ax1.add_patch(arrow1) self._ax1.add_patch(arrow2) @auto_refresh def hide_compass(self): pass class Scalebar(object): def __init__(self, parent): # Retrieve info from parent figure self._ax = parent._ax1 self._wcs = parent._wcs self._figure = parent._figure # Save plotting parameters (required for @auto_refresh) self._parameters = parent._parameters # Initialize settings self._base_settings = {} self._scalebar_settings = {} self._label_settings = {} self._label_settings['fontproperties'] = FontProperties() # LAYOUT @auto_refresh def show(self, length, label=None, corner='bottom right', frame=False, borderpad=0.4, pad=0.5, **kwargs): ''' Overlay a scale bar on the image. Parameters ---------- length : float, or quantity The length of the scalebar in degrees, an angular quantity, or angular unit label : str, optional Label to place below the scalebar corner : int, optional Where to place the scalebar. Acceptable values are:, 'left', 'right', 'top', 'bottom', 'top left', 'top right', 'bottom left' (default), 'bottom right' frame : str, optional Whether to display a frame behind the scalebar (default is False) kwargs Additional arguments are passed to the matplotlib Rectangle and Text classes. See the matplotlib documentation for more details. In cases where the same argument exists for the two objects, the argument is passed to both the Text and Rectangle instance. ''' self._length = length self._base_settings['corner'] = corner self._base_settings['frame'] = frame self._base_settings['borderpad'] = borderpad self._base_settings['pad'] = pad if isinstance(length, u.Quantity): length = length.to(u.degree).value elif isinstance(length, u.Unit): length = length.to(u.degree) degrees_per_pixel = wcs_util.celestial_pixel_scale(self._wcs) length = length / degrees_per_pixel try: self._scalebar.remove() except: pass if isinstance(corner, six.string_types): corner = corners[corner] self._scalebar = AnchoredSizeBar(self._ax.transData, length, label, corner, \ pad=pad, borderpad=borderpad, sep=5, frameon=frame) self._ax.add_artist(self._scalebar) self.set(**kwargs) @auto_refresh def _remove(self): self._scalebar.remove() @auto_refresh def hide(self): ''' Hide the scalebar. ''' try: self._scalebar.remove() except: pass @auto_refresh def set_length(self, length): ''' Set the length of the scale bar. ''' self.show(length, **self._base_settings) self._set_scalebar_properties(**self._scalebar_settings) self._set_label_properties(**self._scalebar_settings) @auto_refresh def set_label(self, label): ''' Set the label of the scale bar. ''' self._set_label_properties(text=label) @auto_refresh def set_corner(self, corner): ''' Set where to place the scalebar. Acceptable values are 'left', 'right', 'top', 'bottom', 'top left', 'top right', 'bottom left' (default), and 'bottom right'. ''' self._base_settings['corner'] = corner self.show(self._length, **self._base_settings) self._set_scalebar_properties(**self._scalebar_settings) self._set_label_properties(**self._scalebar_settings) @auto_refresh def set_frame(self, frame): ''' Set whether to display a frame around the scalebar. ''' self._base_settings['frame'] = frame self.show(self._length, **self._base_settings) self._set_scalebar_properties(**self._scalebar_settings) self._set_label_properties(**self._scalebar_settings) # APPEARANCE @auto_refresh def set_linewidth(self, linewidth): ''' Set the linewidth of the scalebar, in points. ''' self._set_scalebar_properties(linewidth=linewidth) @auto_refresh def set_linestyle(self, linestyle): ''' Set the linestyle of the scalebar. Should be one of 'solid', 'dashed', 'dashdot', or 'dotted'. ''' self._set_scalebar_properties(linestyle=linestyle) @auto_refresh def set_alpha(self, alpha): ''' Set the alpha value (transparency). This should be a floating point value between 0 and 1. ''' self._set_scalebar_properties(alpha=alpha) self._set_label_properties(alpha=alpha) @auto_refresh def set_color(self, color): ''' Set the label and scalebar color. ''' self._set_scalebar_properties(color=color) self._set_label_properties(color=color) @auto_refresh def set_font(self, family=None, style=None, variant=None, stretch=None, weight=None, size=None, fontproperties=None): ''' Set the font of the tick labels Parameters ---------- common: family, style, variant, stretch, weight, size, fontproperties Notes ----- Default values are set by matplotlib or previously set values if set_font has already been called. Global default values can be set by editing the matplotlibrc file. ''' if family: self._label_settings['fontproperties'].set_family(family) if style: self._label_settings['fontproperties'].set_style(style) if variant: self._label_settings['fontproperties'].set_variant(variant) if stretch: self._label_settings['fontproperties'].set_stretch(stretch) if weight: self._label_settings['fontproperties'].set_weight(weight) if size: self._label_settings['fontproperties'].set_size(size) if fontproperties: self._label_settings['fontproperties'] = fontproperties self._set_label_properties(fontproperties=self._label_settings['fontproperties']) @auto_refresh def _set_label_properties(self, **kwargs): ''' Modify the scalebar label properties. All arguments are passed to the matplotlib Text class. See the matplotlib documentation for more details. ''' for kwarg,val in kwargs.items(): try: # Only set attributes that exist kvpair = {kwarg:val} self._scalebar.txt_label.get_children()[0].set(**kvpair) self._label_settings[kwarg] = val except AttributeError: warnings.warn("Text labels do not have attribute {0}. Skipping.".format(kwarg)) @auto_refresh def _set_scalebar_properties(self, **kwargs): ''' Modify the scalebar properties. All arguments are passed to the matplotlib Rectangle class. See the matplotlib documentation for more details. ''' for kwarg,val in kwargs.items(): try: kvpair = {kwarg:val} self._scalebar.size_bar.get_children()[0].set(**kvpair) self._scalebar_settings[kwarg] = val except AttributeError: warnings.warn("Scalebar does not have attribute {0}. Skipping.".format(kwarg)) @auto_refresh def set(self, **kwargs): ''' Modify the scalebar and scalebar properties. All arguments are passed to the matplotlib Rectangle and Text classes. See the matplotlib documentation for more details. In cases where the same argument exists for the two objects, the argument is passed to both the Text and Rectangle instance. ''' for kwarg in kwargs: kwargs_single = {kwarg: kwargs[kwarg]} try: self._set_label_properties(**kwargs_single) except (AttributeError, TypeError): pass try: self._set_scalebar_properties(**kwargs_single) except (AttributeError, TypeError): pass # DEPRECATED @auto_refresh def set_font_family(self, family): warnings.warn("scalebar.set_font_family is deprecated - use scalebar.set_font instead", DeprecationWarning) self.set_font(family=family) @auto_refresh def set_font_weight(self, weight): warnings.warn("scalebar.set_font_weight is deprecated - use scalebar.set_font instead", DeprecationWarning) self.set_font(weight=weight) @auto_refresh def set_font_size(self, size): warnings.warn("scalebar.set_font_size is deprecated - use scalebar.set_font instead", DeprecationWarning) self.set_font(size=size) @auto_refresh def set_font_style(self, style): warnings.warn("scalebar.set_font_style is deprecated - use scalebar.set_font instead", DeprecationWarning) self.set_font(style=style) # For backward-compatibility ScaleBar = Scalebar class Beam(object): def __init__(self, parent): # Retrieve info from parent figure self._figure = parent._figure self._header = parent._header self._ax = parent._ax1 self._wcs = parent._wcs # Save plotting parameters (required for @auto_refresh) self._parameters = parent._parameters # Initialize settings self._base_settings = {} self._beam_settings = {} # LAYOUT @auto_refresh def show(self, major='BMAJ', minor='BMIN', \ angle='BPA', corner='bottom left', frame=False, borderpad=0.4, pad=0.5, **kwargs): ''' Display the beam shape and size for the primary image. By default, this method will search for the BMAJ, BMIN, and BPA keywords in the FITS header to set the major and minor axes and the position angle on the sky. Parameters ---------- major : float, quantity or unit, optional Major axis of the beam in degrees or an angular quantity (overrides BMAJ if present) minor : float, quantity or unit, optional Minor axis of the beam in degrees or an angular quantity (overrides BMIN if present) angle : float, quantity or unit, optional Position angle of the beam on the sky in degrees or an angular quantity (overrides BPA if present) in the anticlockwise direction. corner : int, optional The beam location. Acceptable values are 'left', 'right', 'top', 'bottom', 'top left', 'top right', 'bottom left' (default), and 'bottom right'. frame : str, optional Whether to display a frame behind the beam (default is False) kwargs Additional arguments are passed to the matplotlib Ellipse class. See the matplotlib documentation for more details. ''' if isinstance(major, six.string_types): major = self._header[major] if isinstance(minor, six.string_types): minor = self._header[minor] if isinstance(angle, six.string_types): angle = self._header[angle] if isinstance(major, u.Quantity): major = major.to(u.degree).value elif isinstance(major, u.Unit): major = major.to(u.degree) if isinstance(minor, u.Quantity): minor = minor.to(u.degree).value elif isinstance(minor, u.Unit): minor = minor.to(u.degree) if isinstance(angle, u.Quantity): angle = angle.to(u.degree).value elif isinstance(angle, u.Unit): angle = angle.to(u.degree) degrees_per_pixel = wcs_util.celestial_pixel_scale(self._wcs) self._base_settings['minor'] = minor self._base_settings['major'] = major self._base_settings['angle'] = angle self._base_settings['corner'] = corner self._base_settings['frame'] = frame self._base_settings['borderpad'] = borderpad self._base_settings['pad'] = pad minor /= degrees_per_pixel major /= degrees_per_pixel try: self._beam.remove() except: pass if isinstance(corner, six.string_types): corner = corners[corner] self._beam = AnchoredEllipse(self._ax.transData, \ width=minor, height=major, angle=angle, \ loc=corner, pad=pad, borderpad=borderpad, frameon=frame) self._ax.add_artist(self._beam) self.set(**kwargs) @auto_refresh def _remove(self): self._beam.remove() @auto_refresh def hide(self): ''' Hide the beam ''' try: self._beam.remove() except: pass @auto_refresh def set_major(self, major): ''' Set the major axis of the beam, in degrees. ''' self._base_settings['major'] = major self.show(**self._base_settings) self.set(**self._beam_settings) @auto_refresh def set_minor(self, minor): ''' Set the minor axis of the beam, in degrees. ''' self._base_settings['minor'] = minor self.show(**self._base_settings) self.set(**self._beam_settings) @auto_refresh def set_angle(self, angle): ''' Set the position angle of the beam on the sky, in degrees. ''' self._base_settings['angle'] = angle self.show(**self._base_settings) self.set(**self._beam_settings) @auto_refresh def set_corner(self, corner): ''' Set the beam location. Acceptable values are 'left', 'right', 'top', 'bottom', 'top left', 'top right', 'bottom left' (default), and 'bottom right'. ''' self._base_settings['corner'] = corner self.show(**self._base_settings) self.set(**self._beam_settings) @auto_refresh def set_frame(self, frame): ''' Set whether to display a frame around the beam. ''' self._base_settings['frame'] = frame self.show(**self._base_settings) self.set(**self._beam_settings) @auto_refresh def set_borderpad(self, borderpad): ''' Set the amount of padding within the beam object, relative to the canvas size. ''' self._base_settings['borderpad'] = borderpad self.show(**self._base_settings) self.set(**self._beam_settings) @auto_refresh def set_pad(self, pad): ''' Set the amount of padding between the beam object and the image corner/edge, relative to the canvas size. ''' self._base_settings['pad'] = pad self.show(**self._base_settings) self.set(**self._beam_settings) # APPEARANCE @auto_refresh def set_alpha(self, alpha): ''' Set the alpha value (transparency). This should be a floating point value between 0 and 1. ''' self.set(alpha=alpha) @auto_refresh def set_color(self, color): ''' Set the beam color. ''' self.set(color=color) @auto_refresh def set_edgecolor(self, edgecolor): ''' Set the color for the edge of the beam. ''' self.set(edgecolor=edgecolor) @auto_refresh def set_facecolor(self, facecolor): ''' Set the color for the interior of the beam. ''' self.set(facecolor=facecolor) @auto_refresh def set_linestyle(self, linestyle): ''' Set the line style for the edge of the beam. This should be one of 'solid', 'dashed', 'dashdot', or 'dotted'. ''' self.set(linestyle=linestyle) @auto_refresh def set_linewidth(self, linewidth): ''' Set the line width for the edge of the beam, in points. ''' self.set(linewidth=linewidth) @auto_refresh def set_hatch(self, hatch): ''' Set the hatch pattern. This should be one of '/', '\', '|', '-', '+', 'x', 'o', 'O', '.', or '*'. ''' self.set(hatch=hatch) @auto_refresh def set(self, **kwargs): ''' Modify the beam properties. All arguments are passed to the matplotlib Ellipse class. See the matplotlib documentation for more details. ''' for kwarg in kwargs: self._beam_settings[kwarg] = kwargs[kwarg] self._beam.ellipse.set(**kwargs) APLpy-1.1.1/aplpy/regions.py0000644000077000000240000001264512774746217015613 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division from astropy.extern import six from astropy import log from astropy import wcs from .decorators import auto_refresh class Regions: """ Regions sub-class of APLpy. Used for overplotting various shapes and annotations on APLpy fitsfigures. Example: # DS9 region file called "test.reg" # (the coordinates are around l=28 in the Galactic Plane) # Filename: test.fits fk5 box(18:42:48.262,-04:01:17.91,505.668",459.714",0) # color=red dash=1 point(18:42:51.797,-03:59:44.82) # point=x color=red dash=1 point(18:42:50.491,-04:03:09.39) # point=box color=red dash=1 # vector(18:42:37.433,-04:02:10.77,107.966",115.201) vector=1 color=red dash=1 ellipse(18:42:37.279,-04:02:11.92,26.4336",40.225",0) # color=red dash=1 polygon(18:42:59.016,-03:58:22.06,18:42:58.219,-03:58:11.30,18:42:57.403,-03:58:35.86,18:42:58.094,-03:58:57.69,18:42:59.861,-03:58:41.60,18:42:59.707,-03:58:23.21) # color=red dash=1 point(18:42:52.284,-04:00:02.80) # point=diamond color=red dash=1 point(18:42:46.561,-03:58:01.57) # point=circle color=red dash=1 point(18:42:42.615,-03:58:25.84) # point=cross color=red dash=1 point(18:42:42.946,-04:01:44.74) # point=arrow color=red dash=1 point(18:42:41.961,-03:57:26.16) # point=boxcircle color=red dash=1 # text(18:42:41.961,-03:57:26.16) text={This is text} color=red Code: import aplpy import regions ff = aplpy.FITSFigure("test.fits") ff.show_grayscale() ff.show_regions('test.reg') """ @auto_refresh def show_regions(self, region_file, layer=False, **kwargs): """ Overplot regions as specified in the region file. Parameters ---------- region_file: string or pyregion.ShapeList Path to a ds9 regions file or a ShapeList already read in by pyregion. layer: str, optional The name of the layer kwargs Additional keyword arguments, e.g. zorder, will be passed to the ds9 call and onto the patchcollections. """ PC, TC = ds9(region_file, wcs.WCS(self._header).sub([wcs.WCSSUB_CELESTIAL]), **kwargs) # ffpc = self._ax1.add_collection(PC) PC.add_to_axes(self._ax1) TC.add_to_axes(self._ax1) if layer: region_set_name = layer else: self._region_counter += 1 region_set_name = 'region_set_' + str(self._region_counter) self._layers[region_set_name] = PC self._layers[region_set_name + "_txt"] = TC def ds9(region_file, header, zorder=3, **kwargs): """ Wrapper to return a PatchCollection given a ds9 region file and a fits header. zorder - defaults to 3 so that regions are on top of contours """ try: import pyregion except: raise ImportError("The pyregion package is required to load region files") # read region file if isinstance(region_file, six.string_types): rr = pyregion.open(region_file) elif isinstance(region_file, pyregion.ShapeList): rr = region_file else: raise Exception("Invalid type for region_file: %s - should be string or pyregion.ShapeList" % type(region_file)) # convert coordinates to image coordinates rrim = rr.as_imagecoord(header) # pyregion and aplpy both correct for the FITS standard origin=1,1 # need to avoid double-correcting. Also, only some items in `coord_list` # are pixel coordinates, so which ones should be corrected depends on the # shape. for r in rrim: if r.name == 'polygon': correct = range(len(r.coord_list)) elif r.name == 'line': correct = range(4) elif r.name in ['rotbox', 'box', 'ellipse', 'annulus', 'circle', 'panda', 'pie', 'epanda', 'text', 'point', 'vector']: correct = range(2) else: log.warning("Unknown region type '{0}' - please report to the developers") correct = range(2) for i in correct: r.coord_list[i] += 1 if 'text_offset' in kwargs: text_offset = kwargs['text_offset'] del kwargs['text_offset'] else: text_offset = 5.0 # grab the shapes to overplot pp, aa = rrim.get_mpl_patches_texts(text_offset=text_offset) PC = ArtistCollection(pp, **kwargs) # preserves line style (dashed) TC = ArtistCollection(aa, **kwargs) PC.set_zorder(zorder) TC.set_zorder(zorder) return PC, TC class ArtistCollection(): """ Matplotlib collections can't handle Text. This is a barebones collection for text objects that supports removing and making (in)visible """ def __init__(self, artistlist): """ Pass in a list of matplotlib.text.Text objects (or possibly any matplotlib Artist will work) """ self.artistlist = artistlist def remove(self): for T in self.artistlist: T.remove() def add_to_axes(self, ax): for T in self.artistlist: ax.add_artist(T) def get_visible(self): visible = True for T in self.artistlist: if not T.get_visible(): visible = False return visible def set_visible(self, visible=True): for T in self.artistlist: T.set_visible(visible) def set_zorder(self, zorder): for T in self.artistlist: T.set_zorder(zorder) APLpy-1.1.1/aplpy/rgb.py0000644000077000000240000003026712536551502014702 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division from distutils import version import os import warnings import tempfile import shutil import numpy as np from astropy.extern import six from astropy import log from astropy.io import fits from . import image_util from . import math_util def _data_stretch(image, vmin=None, vmax=None, pmin=0.25, pmax=99.75, stretch='linear', vmid=None, exponent=2): min_auto = not math_util.isnumeric(vmin) max_auto = not math_util.isnumeric(vmax) if min_auto or max_auto: auto_v = image_util.percentile_function(image) vmin_auto, vmax_auto = auto_v(pmin), auto_v(pmax) if min_auto: log.info("vmin = %10.3e (auto)" % vmin_auto) vmin = vmin_auto else: log.info("vmin = %10.3e" % vmin) if max_auto: log.info("vmax = %10.3e (auto)" % vmax_auto) vmax = vmax_auto else: log.info("vmax = %10.3e" % vmax) image = (image - vmin) / (vmax - vmin) data = image_util.stretch(image, stretch, exponent=exponent, midpoint=vmid) data = np.nan_to_num(data) data = np.clip(data * 255., 0., 255.) return data.astype(np.uint8) def make_rgb_image(data, output, indices=(0, 1, 2), \ vmin_r=None, vmax_r=None, pmin_r=0.25, pmax_r=99.75, \ stretch_r='linear', vmid_r=None, exponent_r=2, \ vmin_g=None, vmax_g=None, pmin_g=0.25, pmax_g=99.75, \ stretch_g='linear', vmid_g=None, exponent_g=2, \ vmin_b=None, vmax_b=None, pmin_b=0.25, pmax_b=99.75, \ stretch_b='linear', vmid_b=None, exponent_b=2, \ make_nans_transparent=False, \ embed_avm_tags=True): ''' Make an RGB image from a FITS RGB cube or from three FITS files. Parameters ---------- data : str or tuple or list If a string, this is the filename of an RGB FITS cube. If a tuple or list, this should give the filename of three files to use for the red, green, and blue channel. output : str The output filename. The image type (e.g. PNG, JPEG, TIFF, ...) will be determined from the extension. Any image type supported by the Python Imaging Library can be used. indices : tuple, optional If data is the filename of a FITS cube, these indices are the positions in the third dimension to use for red, green, and blue respectively. The default is to use the first three indices. vmin_r, vmin_g, vmin_b : float, optional Minimum pixel value to use for the red, green, and blue channels. If set to None for a given channel, the minimum pixel value for that channel is determined using the corresponding pmin_x argument (default). vmax_r, vmax_g, vmax_b : float, optional Maximum pixel value to use for the red, green, and blue channels. If set to None for a given channel, the maximum pixel value for that channel is determined using the corresponding pmax_x argument (default). pmin_r, pmin_r, pmin_g : float, optional Percentile values used to determine for a given channel the minimum pixel value to use for that channel if the corresponding vmin_x is set to None. The default is 0.25% for all channels. pmax_r, pmax_g, pmax_b : float, optional Percentile values used to determine for a given channel the maximum pixel value to use for that channel if the corresponding vmax_x is set to None. The default is 99.75% for all channels. stretch_r, stretch_g, stretch_b : { 'linear', 'log', 'sqrt', 'arcsinh', 'power' } The stretch function to use for the different channels. vmid_r, vmid_g, vmid_b : float, optional Baseline values used for the log and arcsinh stretches. If set to None, this is set to zero for log stretches and to vmin - (vmax - vmin) / 30. for arcsinh stretches exponent_r, exponent_g, exponent_b : float, optional If stretch_x is set to 'power', this is the exponent to use. make_nans_transparent : bool, optional If set AND output is png, will add an alpha layer that sets pixels containing a NaN to transparent. embed_avm_tags : bool, optional Whether to embed AVM tags inside the image - this can only be done for JPEG and PNG files, and only if PyAVM is installed. ''' try: from PIL import Image except ImportError: try: import Image except ImportError: raise ImportError("The Python Imaging Library (PIL) is required to make an RGB image") if isinstance(data, six.string_types): image = fits.getdata(data) image_r = image[indices[0], :, :] image_g = image[indices[1], :, :] image_b = image[indices[2], :, :] # Read in header header = fits.getheader(data) # Remove information about third dimension header['NAXIS'] = 2 for key in ['NAXIS', 'CTYPE', 'CRPIX', 'CRVAL', 'CUNIT', 'CDELT', 'CROTA']: for coord in range(3, 6): name = key + str(coord) if name in header: header.__delitem__(name) elif (type(data) == list or type(data) == tuple) and len(data) == 3: filename_r, filename_g, filename_b = data image_r = fits.getdata(filename_r) image_g = fits.getdata(filename_g) image_b = fits.getdata(filename_b) # Read in header header = fits.getheader(filename_r) else: raise Exception("data should either be the filename of a FITS cube or a list/tuple of three images") # are we making a transparent layer? do_alpha = make_nans_transparent and output.lower().endswith('.png') if do_alpha: log.info("Making alpha layer") # initialize alpha layer image_alpha = np.empty_like(image_r, dtype=np.uint8) image_alpha[:] = 255 # look for nans in images for im in [image_r, image_g, image_b]: image_alpha[np.isnan(im)] = 0 log.info("Red:") image_r = Image.fromarray(_data_stretch(image_r, \ vmin=vmin_r, vmax=vmax_r, \ pmin=pmin_r, pmax=pmax_r, \ stretch=stretch_r, \ vmid=vmid_r, \ exponent=exponent_r)) log.info("Green:") image_g = Image.fromarray(_data_stretch(image_g, \ vmin=vmin_g, vmax=vmax_g, \ pmin=pmin_g, pmax=pmax_g, \ stretch=stretch_g, \ vmid=vmid_g, \ exponent=exponent_g)) log.info("Blue:") image_b = Image.fromarray(_data_stretch(image_b, \ vmin=vmin_b, vmax=vmax_b, \ pmin=pmin_b, pmax=pmax_b, \ stretch=stretch_b, \ vmid=vmid_b, \ exponent=exponent_b)) img = Image.merge("RGB", (image_r, image_g, image_b)) if do_alpha: # convert to RGBA and add alpha layer image_alpha = Image.fromarray(image_alpha) img.convert("RGBA") img.putalpha(image_alpha) img = img.transpose(Image.FLIP_TOP_BOTTOM) img.save(output) if embed_avm_tags: try: import pyavm except ImportError: warnings.warn("PyAVM 0.9.1 or later is not installed, so AVM tags will not be embedded in RGB image") return if version.LooseVersion(pyavm.__version__) < version.LooseVersion('0.9.1'): warnings.warn("PyAVM 0.9.1 or later is not installed, so AVM tags will not be embedded in RGB image") return from pyavm import AVM if output.lower().endswith(('.jpg', '.jpeg', '.png')): avm = AVM.from_header(header) avm.embed(output, output) else: warnings.warn("AVM tags will not be embedded in RGB image, as only JPEG and PNG files are supported") def make_rgb_cube(files, output, north=False, system=None, equinox=None): ''' Make an RGB data cube from a list of three FITS images. This method can read in three FITS files with different projections/sizes/resolutions and uses Montage to reproject them all to the same projection. Two files are produced by this function. The first is a three-dimensional FITS cube with a filename give by `output`, where the third dimension contains the different channels. The second is a two-dimensional FITS image with a filename given by `output` with a `_2d` suffix. This file contains the mean of the different channels, and is required as input to FITSFigure if show_rgb is subsequently used to show a color image generated from the FITS cube (to provide the correct WCS information to FITSFigure). Parameters ---------- files : tuple or list A list of the filenames of three FITS filename to reproject. The order is red, green, blue. output : str The filename of the output RGB FITS cube. north : bool, optional By default, the FITS header generated by Montage represents the best fit to the images, often resulting in a slight rotation. If you want north to be straight up in your final mosaic, you should use this option. system : str, optional Specifies the system for the header (default is EQUJ). Possible values are: EQUJ EQUB ECLJ ECLB GAL SGAL equinox : str, optional If a coordinate system is specified, the equinox can also be given in the form YYYY. Default is J2000. ''' # Check whether the Python montage module is installed. The Python module # checks itself whether the Montage command-line tools are available, and # if they are not then importing the Python module will fail. try: import montage_wrapper as montage except ImportError: raise Exception("Both the Montage command-line tools and the" " montage-wrapper Python module are required" " for this function") # Check that input files exist for f in files: if not os.path.exists(f): raise Exception("File does not exist : " + f) # Create work directory work_dir = tempfile.mkdtemp() raw_dir = '%s/raw' % work_dir final_dir = '%s/final' % work_dir images_raw_tbl = '%s/images_raw.tbl' % work_dir header_hdr = '%s/header.hdr' % work_dir # Create raw and final directory in work directory os.mkdir(raw_dir) os.mkdir(final_dir) # Create symbolic links to input files for i, f in enumerate(files): os.symlink(os.path.abspath(f), '%s/image_%i.fits' % (raw_dir, i)) # List files and create optimal header montage.mImgtbl(raw_dir, images_raw_tbl, corners=True) montage.mMakeHdr(images_raw_tbl, header_hdr, north_aligned=north, system=system, equinox=equinox) # Read header in with astropy.io.fits header = fits.Header.fromtextfile(header_hdr) # Find image dimensions nx = int(header['NAXIS1']) ny = int(header['NAXIS2']) # Generate empty datacube image_cube = np.zeros((len(files), ny, nx), dtype=np.float32) # Loop through files for i in range(len(files)): # Reproject channel to optimal header montage.reproject('%s/image_%i.fits' % (raw_dir, i), '%s/image_%i.fits' % (final_dir, i), header=header_hdr, exact_size=True, bitpix=-32) # Read in and add to datacube image_cube[i, :, :] = fits.getdata('%s/image_%i.fits' % (final_dir, i)) # Write out final cube fits.writeto(output, image_cube, header, clobber=True) # Write out collapsed version of cube fits.writeto(output.replace('.fits', '_2d.fits'), \ np.mean(image_cube, axis=0), header, clobber=True) # Remove work directory shutil.rmtree(work_dir) APLpy-1.1.1/aplpy/scalar_util.py0000644000077000000240000000425612536551502016431 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import numpy as np from . import math_util def smart_round_angle_decimal(x, latitude=False): x = np.log10(x) e = np.floor(x) x -= e x = 10. ** x divisors_10 = math_util.divisors(10) x = math_util.closest(divisors_10, x) x = x * 10. ** e return x def _get_label_precision(format): if format[0] == "%": if "i" in format: return 1 elif "f" in format: return 10. ** (-len((format % 1).split('.')[1])) elif "e" in format: return None # need to figure this out else: return None # need to figure this out elif "." in format: return 10 ** (-len(format.split('.')[1])) else: return 1 class InconsistentSpacing(Exception): pass def _check_format_spacing_consistency(format, spacing): ''' Check whether the format can correctly show labels with the specified spacing. For example, if the tick spacing is set to 1 arcsecond, but the format is set to dd:mm, then the labels cannot be correctly shown. Similarly, if the spacing is set to 1/1000 of a degree, or 3.6", then a format of dd:mm:ss will cause rounding errors, because the spacing includes fractional arcseconds. This function will raise a warning if the format and spacing are inconsistent. ''' label_spacing = _get_label_precision(format) if label_spacing is None: return # can't determine minimum spacing, so can't check if spacing % label_spacing != 0.: raise InconsistentSpacing('Label format and tick spacing are inconsistent. Make sure that the tick spacing is a multiple of the smallest angle that can be represented by the specified format (currently %s). For example, if the format is dd:mm:ss.s, then the tick spacing has to be a multiple of 0.1". Similarly, if the format is hh:mm:ss, then the tick spacing has to be a multiple of 15". If you got this error as a result of interactively zooming in to a small region, this means that the default display format for the labels is not accurate enough, so you will need to increase the format precision.' % format) APLpy-1.1.1/aplpy/setup_package.py0000644000077000000240000000010512536551502016727 0ustar tomstaff00000000000000def get_package_data(): return {'aplpy.tests': ['data/*/*.hdr']} APLpy-1.1.1/aplpy/slicer.py0000644000077000000240000000336312536551502015406 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division def slice_hypercube(data, header, dimensions=[0, 1], slices=[]): ''' Extract a slice from an n-dimensional HDU data/header pair, and return the new data (without changing the header). ''' if type(slices) == int: slices = (slices, ) else: slices = slices[:] shape = data.shape if len(shape) < 2: raise Exception("FITS file does not have enough dimensions") elif len(shape) == 2: if dimensions[1] < dimensions[0]: data = data.transpose() return data else: if slices: if dimensions[0] < dimensions[1]: slices.insert(dimensions[0], slice(None, None, None)) slices.insert(dimensions[1], slice(None, None, None)) else: slices.insert(dimensions[1], slice(None, None, None)) slices.insert(dimensions[0], slice(None, None, None)) if type(slices) == list: slices = tuple(slices) data = data[slices[::-1]] if dimensions[1] < dimensions[0]: data = data.transpose() else: message = ''' Attempted to read in %i-dimensional FITS cube, but dimensions and slices were not specified. Please specify these using the dimensions= and slices= argument. The cube dimensions are:\n\n''' % len(shape) for i in range(1, len(shape) + 1): message += " " * 10 message += " %i %s %i\n" % (i - 1, header["CTYPE%i" % i], header["NAXIS%i" % i]) raise Exception(message) return data APLpy-1.1.1/aplpy/tests/0000755000077000000240000000000012774747316014726 5ustar tomstaff00000000000000APLpy-1.1.1/aplpy/tests/__init__.py0000644000077000000240000000046312774746217017041 0ustar tomstaff00000000000000from distutils.version import LooseVersion import matplotlib MPL_VERSION = LooseVersion(matplotlib.__version__) ROOT = "http://aplpy.github.io/aplpy-data/1.x/2016-09-21T08:12:02.530/" if MPL_VERSION >= LooseVersion('1.5.0'): baseline_dir = ROOT + '/1.5.x/' else: baseline_dir = ROOT + '/1.4.x/' APLpy-1.1.1/aplpy/tests/coveragerc0000644000077000000240000000140012536551502016746 0ustar tomstaff00000000000000[run] source = {packagename} omit = {packagename}/_astropy_init* {packagename}/conftest* {packagename}/cython_version* {packagename}/setup_package* {packagename}/*/setup_package* {packagename}/*/*/setup_package* {packagename}/tests/* {packagename}/*/tests/* {packagename}/*/*/tests/* {packagename}/version* [report] exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain about packages we have installed except ImportError # Don't complain if tests don't hit assertions raise AssertionError raise NotImplementedError # Don't complain about script hooks def main\(.*\): # Ignore branches that don't pertain to this version of Python pragma: py{ignore_python_version}APLpy-1.1.1/aplpy/tests/data/0000755000077000000240000000000012774747316015637 5ustar tomstaff00000000000000APLpy-1.1.1/aplpy/tests/data/2d_fits/0000755000077000000240000000000012774747316017171 5ustar tomstaff00000000000000APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_AIR.hdr0000644000077000000240000000166712536551502021205 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---AIR' CRPIX1 = 8.339330824421999 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--AIR' CRPIX2 = -234.7545010835 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -9.000000000000E+01 / Native latitude of celestial pole PV2_1 = 4.500000000000E+01 / Projection parameter 1 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_AIT.hdr0000644000077000000240000000157712536551502021207 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---AIT' CRPIX1 = 7.115850027049 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--AIT' CRPIX2 = -246.2317116277 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_ARC.hdr0000644000077000000240000000157712536551502021177 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---ARC' CRPIX1 = 5.082274450444 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--ARC' CRPIX2 = -246.941901905 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -9.000000000000E+01 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_AZP.hdr0000644000077000000240000000175712536551502021224 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---AZP' CRPIX1 = -11.34948542534 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--AZP' CRPIX2 = -254.1100848779 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -9.000000000000E+01 / Native latitude of celestial pole PV2_1 = 2.000000000000E+00 / Projection parameter 1 PV2_2 = 3.000000000000E+01 / Projection parameter 2 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_BON.hdr0000644000077000000240000000166712536551502021210 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---BON' CRPIX1 = -33.0741266819 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--BON' CRPIX2 = -243.1263982441 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole PV2_1 = 4.500000000000E+01 / Projection parameter 1 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_CAR.hdr0000644000077000000240000000157712536551502021177 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---CAR' CRPIX1 = 7.527038199745 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--CAR' CRPIX2 = -248.2173814412 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_CEA.hdr0000644000077000000240000000166712536551502021162 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---CEA' CRPIX1 = 7.688571124876 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--CEA' CRPIX2 = -248.2173814412 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole PV2_1 = 1.000000000000E+00 / Projection parameter 1 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_COD.hdr0000644000077000000240000000175712536551502021177 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---COD' CRPIX1 = 15.61302682707 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--COD' CRPIX2 = -215.3431714695 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -4.500000000000E+01 / Native latitude of celestial pole PV2_1 = 4.500000000000E+01 / Projection parameter 1 PV2_2 = 2.500000000000E+01 / Projection parameter 2 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_COE.hdr0000644000077000000240000000175712536551502021200 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---COE' CRPIX1 = -14.35249668783 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--COE' CRPIX2 = -223.0375366798 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 4.500000000000E+01 / Native latitude of celestial pole PV2_1 = -4.500000000000E+01 / Projection parameter 1 PV2_2 = 2.500000000000E+01 / Projection parameter 2 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_COO.hdr0000644000077000000240000000175712536551502021212 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---COO' CRPIX1 = 12.92640949564 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--COO' CRPIX2 = -213.6486051767 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -4.500000000000E+01 / Native latitude of celestial pole PV2_1 = 4.500000000000E+01 / Projection parameter 1 PV2_2 = 2.500000000000E+01 / Projection parameter 2 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_COP.hdr0000644000077000000240000000175712536551502021213 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---COP' CRPIX1 = 15.05768272737 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--COP' CRPIX2 = -215.1923139086 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -4.500000000000E+01 / Native latitude of celestial pole PV2_1 = 4.500000000000E+01 / Projection parameter 1 PV2_2 = 2.500000000000E+01 / Projection parameter 2 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_CSC.hdr0000644000077000000240000000157712536551502021202 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---CSC' CRPIX1 = -7.043520126533 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--CSC' CRPIX2 = -268.6531829635 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_CYP.hdr0000644000077000000240000000175712536551502021225 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---CYP' CRPIX1 = 20.56099939277 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--CYP' CRPIX2 = -147.1055514007 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole PV2_1 = 1.000000000000E+00 / Projection parameter 1 PV2_2 = 7.071067811870E-01 / Projection parameter 2 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_HPX.hdr0000644000077000000240000000240312774746217021233 0ustar tomstaff00000000000000SIMPLE = T / file does conform to FITS standard BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 / number of data axes NAXIS1 = 192 / length of data axis 1 NAXIS2 = 192 / length of data axis 2 EXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'Astronomy COMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H BUNIT = 'Jy/beam ' / Pixel value is surface brightness CTYPE1 = 'RA---HPX' CRPIX1 = -8.21754831338666 CDELT1 = -0.0666666666666667 CRVAL1 = 0. CTYPE2 = 'DEC--HPX' CRPIX2 = -248.217381441188 CDELT2 = 0.0666666666666667 CRVAL2 = -90. LONPOLE = 180. / Native longitude of celestial pole LATPOLE = 0. / Native latitude of celestial pole RADESYS = 'FK5 ' / Equatorial coordinate system EQUINOX = 2000.0 / Equinox of equatorial coordinates BMAJ = 0.24000 / Beam major axis in degrees BMIN = 0.24000 / Beam minor axis in degrees BPA = 0.0 / Beam position angle in degrees APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_MER.hdr0000644000077000000240000000157712536551502021215 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---MER' CRPIX1 = 7.364978412864 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--MER' CRPIX2 = -248.2173814412 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_MOL.hdr0000644000077000000240000000157712536551502021221 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---MOL' CRPIX1 = -2.310670994515 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--MOL' CRPIX2 = -212.7655947497 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_NCP.hdr0000644000077000000240000000175712536551502021212 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---SIN' CRPIX1 = 7.688572009351 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--SIN' CRPIX2 = -237.1895431541 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -9.000000000000E+01 / Native latitude of celestial pole PV2_1 = 0.000000000000E+00 / Projection parameter 1 PV2_2 = -1.216796447506E-08 / Projection parameter 2 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_PAR.hdr0000644000077000000240000000157712536551502021214 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---PAR' CRPIX1 = 3.322937769653 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--PAR' CRPIX2 = -246.5551494284 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_PCO.hdr0000644000077000000240000000157712536551502021213 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---PCO' CRPIX1 = 0.3620782775517 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--PCO' CRPIX2 = -246.2486098896 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_QSC.hdr0000644000077000000240000000157712536551502021220 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---QSC' CRPIX1 = -8.258194421088 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--QSC' CRPIX2 = -258.3408175994 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_SFL.hdr0000644000077000000240000000157712536551502021216 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---SFL' CRPIX1 = 7.527038199745 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--SFL' CRPIX2 = -246.3483086237 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_SIN.hdr0000644000077000000240000000175712536551502021223 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---SIN' CRPIX1 = 7.688571124876 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--SIN' CRPIX2 = -237.1895431541 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -9.000000000000E+01 / Native latitude of celestial pole PV2_1 = 0.000000000000E+00 / Projection parameter 1 PV2_2 = 0.000000000000E+00 / Projection parameter 2 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_STG.hdr0000644000077000000240000000157712536551502021227 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---STG' CRPIX1 = 3.744942537739 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--STG' CRPIX2 = -251.945990929 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -9.000000000000E+01 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_SZP.hdr0000644000077000000240000000204712536551502021237 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---SZP' CRPIX1 = -22.62051956373 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--SZP' CRPIX2 = -247.8656972779 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -9.000000000000E+01 / Native latitude of celestial pole PV2_1 = 2.000000000000E+00 / Projection parameter 1 PV2_2 = 1.800000000000E+02 / Projection parameter 2 PV2_3 = 6.000000000000E+01 / Projection parameter 3 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_TAN.hdr0000644000077000000240000000157712536551502021214 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---TAN' CRPIX1 = -0.5630437201085 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--TAN' CRPIX2 = -268.0658087122 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -9.000000000000E+01 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_TSC.hdr0000644000077000000240000000157712536551502021223 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---TSC' CRPIX1 = 20.37416464676 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--TSC' CRPIX2 = -189.7220156818 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = 0.000000000000E+00 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_ZEA.hdr0000644000077000000240000000157712536551502021211 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---ZEA' CRPIX1 = 5.738055949994 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--ZEA' CRPIX2 = -244.4880690361 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -9.000000000000E+01 / Native latitude of celestial pole EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/1904-66_ZPN.hdr0000644000077000000240000000375112536551502021235 0ustar tomstaff00000000000000SIMPLE = T BITPIX = -32 / IEEE (big-endian) 32-bit floating point data NAXIS = 2 NAXIS1 = 192 NAXIS2 = 192 BUNIT = 'JY/BEAM ' CTYPE1 = 'RA---ZPN' CRPIX1 = 22.09211120575 CDELT1 = -6.666666666667E-02 CRVAL1 = 0.000000000000E+00 CTYPE2 = 'DEC--ZPN' CRPIX2 = -183.2937255632 CDELT2 = 6.666666666667E-02 CRVAL2 = -9.000000000000E+01 LONPOLE = 1.800000000000E+02 / Native longitude of celestial pole LATPOLE = -9.000000000000E+01 / Native latitude of celestial pole PV2_0 = 5.000000000000E-02 / Projection parameter 0 PV2_1 = 9.750000000000E-01 / Projection parameter 1 PV2_2 = -8.070000000000E-01 / Projection parameter 2 PV2_3 = 3.370000000000E-01 / Projection parameter 3 PV2_4 = -6.500000000000E-02 / Projection parameter 4 PV2_5 = 1.000000000000E-02 / Projection parameter 5 PV2_6 = 3.000000000000E-03 / Projection parameter 6 PV2_7 = -1.000000000000E-03 / Projection parameter 7 PV2_8 = 0.000000000000E+00 / Projection parameter 8 PV2_9 = 0.000000000000E+00 / Projection parameter 9 PV2_10 = 0.000000000000E+00 / Projection parameter 10 PV2_11 = 0.000000000000E+00 / Projection parameter 11 PV2_12 = 0.000000000000E+00 / Projection parameter 12 PV2_13 = 0.000000000000E+00 / Projection parameter 13 PV2_14 = 0.000000000000E+00 / Projection parameter 14 PV2_15 = 0.000000000000E+00 / Projection parameter 15 PV2_16 = 0.000000000000E+00 / Projection parameter 16 PV2_17 = 0.000000000000E+00 / Projection parameter 17 PV2_18 = 0.000000000000E+00 / Projection parameter 18 PV2_19 = 0.000000000000E+00 / Projection parameter 19 EQUINOX = 2.000000000000E+03 / Equinox of equatorial coordinates BMAJ = 2.399999936422E-01 / Beam major axis in degrees BMIN = 2.399999936422E-01 / Beam minor axis in degrees BPA = 0.000000000000E+00 / Beam position angle in degrees RESTFRQ = 1.420405750000E+09 / Line rest frequency, Hz APLpy-1.1.1/aplpy/tests/data/2d_fits/2MASS_k.hdr0000644000077000000240000000125612536551502021015 0ustar tomstaff00000000000000SIMPLE = T BITPIX = 16 NAXIS = 2 NAXIS1 = 721 NAXIS2 = 720 EXTEND = T / FITS dataset may contain extensions DATASET = '2MASS ' BAND = 'K ' CDATE = 'Wed Feb 25 11:57:21 2009' CTYPE1 = 'RA---TAN' CTYPE2 = 'DEC--TAN' CRVAL1 = 266.400000 CRVAL2 = -28.933330 CRPIX1 = 361. CRPIX2 = 360.5 CDELT1 = -0.001388889 CDELT2 = 0.001388889 CROTA2 = 0.000000 EQUINOX = 2000.0 MAGZP = 19.9757 BSCALE = 0.045777764213996 BZERO = 1500. APLpy-1.1.1/aplpy/tests/data/3d_fits/0000755000077000000240000000000012774747316017172 5ustar tomstaff00000000000000APLpy-1.1.1/aplpy/tests/data/3d_fits/cube.hdr0000644000077000000240000000132112536551502020566 0ustar tomstaff00000000000000SIMPLE = T / Written by IDL: Fri Oct 27 10:49:59 2006 BITPIX = -32 / bits per data value NAXIS = 3 / number of axes NAXIS1 = 11 NAXIS2 = 12 NAXIS3 = 32 EXTEND = T /file may contain extensions CRVAL1 = 57.6599999999 / CRPIX1 = -799.000000000 / CDELT1 = -0.00638888900000 CTYPE1 = 'RA---SFL' / CRVAL2 = 0.00000000000 / CRPIX2 = -4741.91300000 / CDELT2 = 0.00638888900000 CTYPE2 = 'DEC--SFL' / CRVAL3 = -9959.44378305 / CRPIX3 = 1.00000 / CDELT3 = 66.4236100000 / CTYPE3 = 'VELO-LSR' / APLpy-1.1.1/aplpy/tests/helpers.py0000644000077000000240000000247512536551502016734 0ustar tomstaff00000000000000import string import random import os import numpy as np from astropy.io import fits from astropy.wcs import WCS def random_id(): return ''.join(random.sample(string.ascii_letters + string.digits, 16)) def generate_header(header_file): # Read in header header = fits.Header.fromtextfile(header_file) return header def generate_data(header_file): # Read in header header = generate_header(header_file) # Find shape of array shape = [] for i in range(header['NAXIS']): shape.append(header['NAXIS%i' % (i + 1)]) # Generate data array data = np.zeros(shape[::-1]) return data def generate_hdu(header_file): # Read in header header = generate_header(header_file) # Generate data array data = generate_data(header_file) # Generate primary HDU hdu = fits.PrimaryHDU(data=data, header=header) return hdu def generate_wcs(header_file): # Read in header header = generate_header(header_file) # Compute WCS object wcs = WCS(header) return wcs def generate_file(header_file, directory): # Generate HDU object hdu = generate_hdu(header_file) # Write out to a temporary file in the specified directory filename = os.path.join(directory, random_id() + '.fits') hdu.writeto(filename) return filename APLpy-1.1.1/aplpy/tests/setup_package.py0000644000077000000240000000021112536551502020067 0ustar tomstaff00000000000000def get_package_data(): return { _ASTROPY_PACKAGE_NAME_ + '.tests': ['coveragerc', 'data/*/*.hdr', 'baseline_images/*.png']} APLpy-1.1.1/aplpy/tests/test_axis_labels.py0000644000077000000240000000320612536551502020610 0ustar tomstaff00000000000000import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from .. import FITSFigure def test_axis_labels_show_hide(): data = np.zeros((16, 16)) f = FITSFigure(data) f.axis_labels.hide() f.axis_labels.show() f.axis_labels.hide_x() f.axis_labels.show_x() f.axis_labels.hide_y() f.axis_labels.show_y() f.close() def test_axis_labels_text(): data = np.zeros((16, 16)) f = FITSFigure(data) f.axis_labels.set_xtext('x') f.axis_labels.set_ytext('y') f.close() def test_axis_labels_pad(): data = np.zeros((16, 16)) f = FITSFigure(data) f.axis_labels.set_xpad(-1.) f.axis_labels.set_ypad(0.5) f.close() def test_axis_labels_position(): data = np.zeros((16, 16)) f = FITSFigure(data) f.axis_labels.set_xposition('top') f.axis_labels.set_xposition('bottom') f.axis_labels.set_yposition('right') f.axis_labels.set_yposition('left') f.close() def test_axis_labels_position_invalid(): data = np.zeros((16, 16)) f = FITSFigure(data) with pytest.raises(ValueError): f.axis_labels.set_xposition('right') with pytest.raises(ValueError): f.axis_labels.set_xposition('left') with pytest.raises(ValueError): f.axis_labels.set_yposition('top') with pytest.raises(ValueError): f.axis_labels.set_yposition('bottom') f.close() def test_axis_labels_font(): data = np.zeros((16, 16)) f = FITSFigure(data) f.axis_labels.set_font(size='small', weight='bold', stretch='normal', family='serif', style='normal', variant='normal') f.close() APLpy-1.1.1/aplpy/tests/test_beam.py0000644000077000000240000001066212536551502017232 0ustar tomstaff00000000000000import os import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from astropy import units as u from astropy.io import fits from .. import FITSFigure header_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data/2d_fits') HEADER = fits.Header.fromtextfile(os.path.join(header_dir, '1904-66_TAN.hdr')) HDU = fits.PrimaryHDU(np.zeros((16, 16)), HEADER) def test_beam_addremove(): f = FITSFigure(HDU) f.show_grayscale() f.add_beam(major=0.1, minor=0.04, angle=10.) f.remove_beam() f.add_beam(major=0.1, minor=0.04, angle=10.) f.remove_beam() f.close() def test_beam_showhide(): f = FITSFigure(HDU) f.show_grayscale() f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.hide() f.beam.show(major=0.1, minor=0.04, angle=10.) f.close() def test_beam_major(): f = FITSFigure(HDU) f.show_grayscale() f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.set_major(0.5) f.beam.set_major(1.0) f.close() @pytest.mark.parametrize('quantity', [u.arcsec, 5*u.arcsec, 1*u.degree, 1*u.radian]) def test_beam_major_quantity(quantity): f = FITSFigure(HDU) f.show_grayscale() f.add_beam(major=quantity, minor=0.04, angle=10.) f.beam.set_major(quantity) f.close() def test_beam_minor(): f = FITSFigure(HDU) f.show_grayscale() f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.set_minor(0.05) f.beam.set_minor(0.08) f.close() @pytest.mark.parametrize('quantity', [u.arcsec, 5*u.arcsec, 1*u.degree, 1*u.radian]) def test_beam_minor_quantity(quantity): f = FITSFigure(HDU) f.show_grayscale() f.add_beam(major=0.1, minor=quantity, angle=10.) assert type(f.beam) != list f.beam.set_minor(quantity) f.close() def test_beam_angle(): f = FITSFigure(HDU) f.show_grayscale() f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.set_angle(0.) f.beam.set_angle(55.) f.close() @pytest.mark.parametrize('quantity', [u.arcsec, 5*u.arcsec, 1*u.degree, 1*u.radian]) def test_beam_angle_quantity(quantity): f = FITSFigure(HDU) f.show_grayscale() f.add_beam(major=0.1, minor=0.04, angle=quantity) f.beam.set_angle(quantity) f.close() def test_beam_corner(): f = FITSFigure(HDU) f.add_beam(major=0.1, minor=0.04, angle=10.) for corner in ['top', 'bottom', 'left', 'right', 'top left', 'top right', 'bottom left', 'bottom right']: f.beam.set_corner(corner) f.close() def test_beam_frame(): f = FITSFigure(HDU) f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.set_frame(True) f.beam.set_frame(False) f.close() def test_beam_borderpad(): f = FITSFigure(HDU) f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.set_borderpad(0.1) f.beam.set_borderpad(0.3) f.close() def test_beam_pad(): f = FITSFigure(HDU) f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.set_pad(0.1) f.beam.set_pad(0.3) f.close() def test_beam_alpha(): f = FITSFigure(HDU) f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.set_alpha(0.1) f.beam.set_alpha(0.2) f.beam.set_alpha(0.5) f.close() def test_beam_color(): f = FITSFigure(HDU) f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.set_color('black') f.beam.set_color('#003344') f.beam.set_color((1.0, 0.4, 0.3)) f.close() def test_beam_facecolor(): f = FITSFigure(HDU) f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.set_facecolor('black') f.beam.set_facecolor('#003344') f.beam.set_facecolor((1.0, 0.4, 0.3)) f.close() def test_beam_edgecolor(): f = FITSFigure(HDU) f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.set_edgecolor('black') f.beam.set_edgecolor('#003344') f.beam.set_edgecolor((1.0, 0.4, 0.3)) f.close() def test_beam_linestyle(): f = FITSFigure(HDU) f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.set_linestyle('solid') f.beam.set_linestyle('dotted') f.beam.set_linestyle('dashed') f.close() def test_beam_linewidth(): f = FITSFigure(HDU) f.add_beam(major=0.1, minor=0.04, angle=10.) f.beam.set_linewidth(0) f.beam.set_linewidth(1) f.beam.set_linewidth(5) f.close() def test_beam_hatch(): f = FITSFigure(HDU) f.add_beam(major=0.1, minor=0.04, angle=10.) for hatch in ['/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*']: f.beam.set_hatch(hatch) f.close() APLpy-1.1.1/aplpy/tests/test_colorbar.py0000644000077000000240000000421112774746217020137 0ustar tomstaff00000000000000import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from .. import FITSFigure def test_colorbar_invalid(): data = np.zeros((16, 16)) f = FITSFigure(data) with pytest.raises(Exception): f.add_colorbar() # no grayscale/colorscale was shown def test_colorbar_addremove(): data = np.zeros((16, 16)) f = FITSFigure(data) f.show_grayscale() f.add_colorbar() f.remove_colorbar() f.add_colorbar() f.close() def test_colorbar_showhide(): data = np.zeros((16, 16)) f = FITSFigure(data) f.show_grayscale() f.add_colorbar() f.colorbar.hide() f.colorbar.show() f.close() def test_colorbar_location(): data = np.zeros((16, 16)) f = FITSFigure(data) f.show_grayscale() f.add_colorbar() f.colorbar.set_location('top') f.colorbar.set_location('bottom') f.colorbar.set_location('left') f.colorbar.set_location('right') f.close() def test_colorbar_width(): data = np.zeros((16, 16)) f = FITSFigure(data) f.show_grayscale() f.add_colorbar() f.colorbar.set_width(0.1) f.colorbar.set_width(0.2) f.colorbar.set_width(0.5) f.close() def test_colorbar_pad(): data = np.zeros((16, 16)) f = FITSFigure(data) f.show_grayscale() f.add_colorbar() f.colorbar.set_pad(0.1) f.colorbar.set_pad(0.2) f.colorbar.set_pad(0.5) f.close() def test_colorbar_font(): data = np.zeros((16, 16)) f = FITSFigure(data) f.show_grayscale() f.add_colorbar() f.colorbar.set_font(size='small', weight='bold', stretch='normal', family='serif', style='normal', variant='normal') f.close() def test_colorbar_axis_label(): data = np.zeros((16, 16)) f = FITSFigure(data) f.show_grayscale() f.add_colorbar() f.colorbar.set_axis_label_text('Surface Brightness (MJy/sr)') f.colorbar.set_axis_label_rotation(45.) f.colorbar.set_axis_label_font(size='small', weight='bold', stretch='normal', family='serif', style='normal', variant='normal') f.colorbar.set_axis_label_pad(5.) f.close() APLpy-1.1.1/aplpy/tests/test_contour.py0000644000077000000240000000067612536551502020023 0ustar tomstaff00000000000000import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from .. import FITSFigure # Test simple contour generation with Numpy example @pytest.mark.parametrize(('filled'), [True, False]) def test_numpy_contour(filled): data = np.arange(256).reshape((16, 16)) f = FITSFigure(data) f.show_grayscale() f.show_contour(data, levels=np.linspace(1., 254., 10), filled=filled) f.close() APLpy-1.1.1/aplpy/tests/test_convolve.py0000644000077000000240000000226112536551502020155 0ustar tomstaff00000000000000import os import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from astropy.io import fits from astropy.wcs import WCS as AstropyWCS from .helpers import generate_file, generate_hdu, generate_wcs from .. import FITSFigure def test_convolve_default(): data = np.random.random((16, 16)) hdu = fits.PrimaryHDU(data) f = FITSFigure(hdu) f.show_grayscale(smooth=3) f.close() def test_convolve_gauss(): data = np.random.random((16, 16)) hdu = fits.PrimaryHDU(data) f = FITSFigure(hdu) f.show_grayscale(kernel='gauss', smooth=3) f.close() def test_convolve_box(): data = np.random.random((16, 16)) hdu = fits.PrimaryHDU(data) f = FITSFigure(hdu) f.show_grayscale(kernel='box', smooth=3) f.close() def test_convolve_custom(): data = np.random.random((16, 16)) hdu = fits.PrimaryHDU(data) f = FITSFigure(hdu) f.show_grayscale(kernel=np.ones((3,3))) f.close() def test_convolve_default(): # Regression test for aplpy/aplpy#165 data = np.ones((16, 16), dtype=int) hdu = fits.PrimaryHDU(data) f = FITSFigure(hdu) f.show_grayscale(smooth=3) f.close() APLpy-1.1.1/aplpy/tests/test_downsample.py0000644000077000000240000000051312536551502020471 0ustar tomstaff00000000000000import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from .. import FITSFigure # Test simple contour generation with Numpy example def test_numpy_downsample(): data = np.arange(256).reshape((16, 16)) f = FITSFigure(data, downsample=2) f.show_grayscale() f.close() APLpy-1.1.1/aplpy/tests/test_frame.py0000644000077000000240000000100012536551502017402 0ustar tomstaff00000000000000import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from .. import FITSFigure def test_frame_linewidth(): data = np.zeros((16, 16)) f = FITSFigure(data) f.frame.set_linewidth(0) f.frame.set_linewidth(1) f.frame.set_linewidth(10) f.close() def test_frame_color(): data = np.zeros((16, 16)) f = FITSFigure(data) f.frame.set_color('black') f.frame.set_color('#003344') f.frame.set_color((1.0, 0.4, 0.3)) f.close() APLpy-1.1.1/aplpy/tests/test_grid.py0000644000077000000240000000313012536551502017243 0ustar tomstaff00000000000000import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from .. import FITSFigure def test_grid_addremove(): data = np.zeros((16, 16)) f = FITSFigure(data) f.add_grid() f.remove_grid() f.add_grid() f.close() def test_grid_showhide(): data = np.zeros((16, 16)) f = FITSFigure(data) f.add_grid() f.grid.hide() f.grid.show() f.close() def test_grid_spacing(): data = np.zeros((16, 16)) f = FITSFigure(data) f.add_grid() f.grid.set_xspacing(1.) f.grid.set_xspacing('tick') with pytest.raises(ValueError): f.grid.set_xspacing('auto') f.grid.set_yspacing(2.) f.grid.set_yspacing('tick') with pytest.raises(ValueError): f.grid.set_yspacing('auto') f.close() def test_grid_color(): data = np.zeros((16, 16)) f = FITSFigure(data) f.add_grid() f.grid.set_color('black') f.grid.set_color('#003344') f.grid.set_color((1.0, 0.4, 0.3)) f.close() def test_grid_alpha(): data = np.zeros((16, 16)) f = FITSFigure(data) f.add_grid() f.grid.set_alpha(0.0) f.grid.set_alpha(0.3) f.grid.set_alpha(1.0) f.close() def test_grid_linestyle(): data = np.zeros((16, 16)) f = FITSFigure(data) f.add_grid() f.grid.set_linestyle('solid') f.grid.set_linestyle('dashed') f.grid.set_linestyle('dotted') f.close() def test_grid_linewidth(): data = np.zeros((16, 16)) f = FITSFigure(data) f.add_grid() f.grid.set_linewidth(0) f.grid.set_linewidth(2) f.grid.set_linewidth(5) f.close() APLpy-1.1.1/aplpy/tests/test_images.py0000644000077000000240000001127712774746217017613 0ustar tomstaff00000000000000import os import tempfile import numpy as np from astropy.tests.helper import pytest, remote_data from .. import FITSFigure from .helpers import generate_file from . import baseline_dir class BaseImageTests(object): @classmethod def setup_class(cls): cls._moduledir = os.path.dirname(__file__) cls._data_dir = os.path.abspath(os.path.join(cls._moduledir, 'data')) cls._baseline_images_dir = os.path.abspath(os.path.join(cls._moduledir, 'baseline_images')) header_1 = os.path.join(cls._data_dir, '2d_fits/1904-66_AIR.hdr') cls.filename_1 = generate_file(header_1, str(tempfile.mkdtemp())) header_2 = os.path.join(cls._data_dir, '2d_fits/2MASS_k.hdr') cls.filename_2 = generate_file(header_2, str(tempfile.mkdtemp())) header_3 = os.path.join(cls._data_dir, '3d_fits/cube.hdr') cls.filename_3 = generate_file(header_3, str(tempfile.mkdtemp())) class TestBasic(BaseImageTests): # Test for showing grayscale @remote_data @pytest.mark.mpl_image_compare(baseline_dir=baseline_dir, tolerance=1.5) def test_basic_image(self, generate): f = FITSFigure(self.filename_2) f.show_grayscale() return f @remote_data @pytest.mark.mpl_image_compare(baseline_dir=baseline_dir, tolerance=1.5) def test_ticks_labels_options(self, generate): f = FITSFigure(self.filename_2) f.ticks.set_color('black') f.axis_labels.set_xposition('top') f.axis_labels.set_yposition('right') f.axis_labels.set_font(size='medium', weight='medium', stretch='normal', style='normal') f.tick_labels.set_xformat('dd:mm:ss.ss') f.tick_labels.set_yformat('hh:mm:ss.ss') f.tick_labels.set_style('colons') f.ticks.set_xspacing(0.2) f.ticks.set_yspacing(0.2) f.ticks.set_minor_frequency(10) return f # Test for showing colorscale @remote_data @pytest.mark.mpl_image_compare(baseline_dir=baseline_dir, tolerance=1.5) def test_show_colorbar_scalebar_beam(self, generate): f = FITSFigure(self.filename_1) f.ticks.set_color('black') f.show_colorscale(vmin=-0.1, vmax=0.1) f.add_colorbar() f.add_scalebar(7.5) f.add_beam(major=0.5, minor=0.2, angle=10.) f.tick_labels.hide() return f # Test for overlaying shapes @remote_data @pytest.mark.mpl_image_compare(baseline_dir=baseline_dir, tolerance=1.5) def test_overlay_shapes(self, generate): f = FITSFigure(self.filename_1) f.ticks.set_color('black') f.show_markers([360., 350., 340.], [-61., -62., -63]) f.show_ellipses(330., -66., 0.15, 2., 10.) f.show_rectangles([355., 350.], [-71, -72], [0.5, 1], [2, 1]) f.show_arrows([340., 360], [-72, -68], [2, -2], [2, 2]) f.add_label(350., -66., 'text') f.add_label(0.4, 0.25, 'text', relative=True) f.frame.set_linewidth(1) # points f.frame.set_color('black') f.axis_labels.hide() return f # Test for grid @remote_data @pytest.mark.mpl_image_compare(baseline_dir=baseline_dir, tolerance=1.5) def test_grid(self, generate): f = FITSFigure(self.filename_1) f.ticks.set_color('black') f.add_grid() f.grid.set_color('red') f.grid.set_alpha(0.8) f.grid.set_linestyle('solid') f.grid.set_xspacing('tick') f.grid.set_yspacing(3) return f # Test recenter @remote_data @pytest.mark.mpl_image_compare(baseline_dir=baseline_dir, tolerance=1.5) def test_recenter(self, generate): f = FITSFigure(self.filename_2) f.ticks.set_color('black') f.recenter(266.5, -29.0, width=0.1, height=0.1) f.axis_labels.set_xpad(20) f.axis_labels.set_ypad(20) return f # Test overlaying contours @remote_data @pytest.mark.mpl_image_compare(baseline_dir=baseline_dir, tolerance=1.5) def test_contours(self, generate): data = np.arange(256).reshape((16, 16)) f = FITSFigure(data) f.ticks.set_color('black') f.show_contour(data, levels=np.linspace(1., 254., 10), filled=False) return f # Test cube slice @remote_data @pytest.mark.mpl_image_compare(baseline_dir=baseline_dir, tolerance=1.5) def test_cube_slice(self, generate): f = FITSFigure(self.filename_3, dimensions=[2, 0], slices=[10]) f.ticks.set_color('black') f.add_grid() f.grid.set_color('black') f.grid.set_linestyle('solid') f.grid.set_xspacing(250) f.grid.set_yspacing(0.01) f.tick_labels.set_xformat('%g') f.tick_labels.set_yformat('dd:mm:ss.ss') return f APLpy-1.1.1/aplpy/tests/test_init_cube.py0000644000077000000240000000731512536551502020270 0ustar tomstaff00000000000000import os import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from astropy.io import fits import astropy.utils.exceptions as aue from .helpers import generate_file, generate_hdu, generate_wcs from .. import FITSFigure # The tests in this file check that the initialization and basic plotting do # not crash for FITS files with 3+ dimensions. No reference images are # required here. header_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data/3d_fits') HEADERS = [os.path.join(header_dir, 'cube.hdr')] REFERENCE = os.path.join(header_dir, 'cube.hdr') VALID_DIMENSIONS = [(0, 1), (1, 0), (0, 2), (2, 0), (1, 2), (2, 1)] INVALID_DIMENSIONS = [None, (1,), (0, 3), (-4, 2), (1, 1), (2, 2), (3, 3), (1, 2, 3), (3, 5, 3, 2)] # Test initialization through a filename def test_file_init(tmpdir): filename = generate_file(REFERENCE, str(tmpdir)) f = FITSFigure(filename, slices=[5]) f.show_grayscale() f.close() # Test initialization through an HDU object def test_hdu_init(): hdu = generate_hdu(REFERENCE) f = FITSFigure(hdu, slices=[5]) f.show_grayscale() f.close() # Test initialization through a WCS object (should not work) def test_wcs_init(): wcs = generate_wcs(REFERENCE) exc = ValueError if hasattr(wcs,'naxis1') else aue.AstropyDeprecationWarning with pytest.raises(exc): FITSFigure(wcs, slices=[5]) # Test initialization through an HDU object (no WCS) def test_hdu_nowcs_init(): data = np.zeros((16, 16, 16)) hdu = fits.PrimaryHDU(data) f = FITSFigure(hdu, slices=[5]) f.show_grayscale() f.close() # Test initalization through a Numpy array (no WCS) def test_numpy_nowcs_init(): data = np.zeros((16, 16, 16)) f = FITSFigure(data, slices=[5]) f.show_grayscale() f.close() # Test that initialization without specifying slices raises an exception for a # true 3D cube def test_hdu_noslices(): hdu = generate_hdu(REFERENCE) with pytest.raises(Exception): FITSFigure(hdu) # Test that initialization without specifying slices does *not* raise an # exception if the remaining dimensions have size 1. def test_hdu_noslices_2d(): data = np.zeros((1, 16, 16)) f = FITSFigure(data) f.show_grayscale() f.close() # Now check initialization with valid and invalid dimensions. We just need to # tes with HDU objects since we already tested that reading from files is ok. # Test initialization with valid dimensions @pytest.mark.parametrize(('dimensions'), VALID_DIMENSIONS) def test_init_dimensions_valid(dimensions): hdu = generate_hdu(REFERENCE) f = FITSFigure(hdu, dimensions=dimensions, slices=[5]) f.show_grayscale() f.close() # Test initialization with invalid dimensions @pytest.mark.parametrize(('dimensions'), INVALID_DIMENSIONS) def test_init_dimensions_invalid(dimensions): hdu = generate_hdu(REFERENCE) with pytest.raises(ValueError): FITSFigure(hdu, dimensions=dimensions, slices=[5]) # Now check initialization of different WCS projections, and we check only # valid dimensions valid_parameters = [] for h in HEADERS: for d in VALID_DIMENSIONS: valid_parameters.append((h, d)) @pytest.mark.parametrize(('header', 'dimensions'), valid_parameters) def test_init_extensive_wcs(tmpdir, header, dimensions): filename = generate_file(header, str(tmpdir)) f = FITSFigure(filename, dimensions=dimensions, slices=[5]) f.show_grayscale() f.close() # Test that recenter works for cube slices def test_hdu_nowcs_init(): data = np.zeros((16, 16, 16)) hdu = fits.PrimaryHDU(data) f = FITSFigure(hdu, slices=[5]) f.show_grayscale() f.recenter(5., 5., width=3., height=3.) f.close() APLpy-1.1.1/aplpy/tests/test_init_image.py0000644000077000000240000001342712536551502020435 0ustar tomstaff00000000000000import os import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from astropy.io import fits from astropy.wcs import WCS as AstropyWCS import astropy.utils.exceptions as aue from .helpers import generate_file, generate_hdu, generate_wcs from .. import FITSFigure # The tests in this file check that the initialization and basic plotting do # not crash for FITS files with 2 dimensions. No reference images are # required here. header_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data/2d_fits') HEADERS = [os.path.join(header_dir, '1904-66_AIR.hdr'), os.path.join(header_dir, '1904-66_AIT.hdr'), os.path.join(header_dir, '1904-66_ARC.hdr'), os.path.join(header_dir, '1904-66_AZP.hdr'), os.path.join(header_dir, '1904-66_BON.hdr'), os.path.join(header_dir, '1904-66_CAR.hdr'), os.path.join(header_dir, '1904-66_CEA.hdr'), os.path.join(header_dir, '1904-66_COD.hdr'), os.path.join(header_dir, '1904-66_COE.hdr'), os.path.join(header_dir, '1904-66_COO.hdr'), os.path.join(header_dir, '1904-66_COP.hdr'), os.path.join(header_dir, '1904-66_CSC.hdr'), os.path.join(header_dir, '1904-66_CYP.hdr'), os.path.join(header_dir, '1904-66_HPX.hdr'), os.path.join(header_dir, '1904-66_MER.hdr'), os.path.join(header_dir, '1904-66_MOL.hdr'), os.path.join(header_dir, '1904-66_NCP.hdr'), os.path.join(header_dir, '1904-66_PAR.hdr'), os.path.join(header_dir, '1904-66_PCO.hdr'), os.path.join(header_dir, '1904-66_QSC.hdr'), os.path.join(header_dir, '1904-66_SFL.hdr'), os.path.join(header_dir, '1904-66_SIN.hdr'), os.path.join(header_dir, '1904-66_STG.hdr'), os.path.join(header_dir, '1904-66_SZP.hdr'), os.path.join(header_dir, '1904-66_TAN.hdr'), os.path.join(header_dir, '1904-66_TSC.hdr'), os.path.join(header_dir, '1904-66_ZEA.hdr'), os.path.join(header_dir, '1904-66_ZPN.hdr')] REFERENCE = os.path.join(header_dir, '1904-66_TAN.hdr') CAR_REFERENCE = os.path.join(header_dir, '1904-66_CAR.hdr') VALID_DIMENSIONS = [(0, 1), (1, 0)] INVALID_DIMENSIONS = [None, (1,), (0, 2), (-4, 2), (1, 1), (2, 2), (1, 2, 3)] # Test initialization through a filename def test_file_init(tmpdir): filename = generate_file(REFERENCE, str(tmpdir)) f = FITSFigure(filename) f.show_grayscale() f.close() # Test initialization through an HDU object def test_hdu_init(): hdu = generate_hdu(REFERENCE) f = FITSFigure(hdu) f.show_grayscale() f.close() # Test initialization through a WCS object def test_wcs_init(): wcs = generate_wcs(REFERENCE) if hasattr(wcs,'naxis1'): f = FITSFigure(wcs) f.show_grayscale() f.close() else: with pytest.raises(aue.AstropyDeprecationWarning): f = FITSFigure(wcs) # Test initialization through a WCS object with wcs.to_header() as a go-between # specifically for testing the cd -> pc -> cd hack, and has particular importance # for AVM-generated headers def test_wcs_toheader_init(): wcs = generate_wcs(REFERENCE) header_ = fits.Header.fromtextfile(REFERENCE) header = wcs.to_header() wcs2 = AstropyWCS(header) wcs2.naxis1 = wcs.naxis1 = header_['NAXIS1'] wcs2.naxis2 = wcs.naxis2 = header_['NAXIS2'] f = FITSFigure(wcs2) f.show_grayscale() f.add_grid() f.close() # Test initialization through an HDU object (no WCS) def test_hdu_nowcs_init(): data = np.zeros((16, 16)) hdu = fits.PrimaryHDU(data) f = FITSFigure(hdu) f.show_grayscale() f.close() # Test initalization through a Numpy array (no WCS) def test_numpy_nowcs_init(): data = np.zeros((16, 16)) f = FITSFigure(data) f.show_grayscale() f.close() # Now check initialization with valid and invalid dimensions. We just need to # tes with HDU objects since we already tested that reading from files is ok. # Test initialization with valid dimensions @pytest.mark.parametrize(('dimensions'), VALID_DIMENSIONS) def test_init_dimensions_valid(dimensions): hdu = generate_hdu(REFERENCE) f = FITSFigure(hdu, dimensions=dimensions) f.show_grayscale() f.close() # Test initialization with invalid dimensions @pytest.mark.parametrize(('dimensions'), INVALID_DIMENSIONS) def test_init_dimensions_invalid(dimensions): hdu = generate_hdu(REFERENCE) with pytest.raises(ValueError): FITSFigure(hdu, dimensions=dimensions) # Now check initialization of different WCS projections, and we check only # valid dimensions valid_parameters = [] for h in HEADERS: for d in VALID_DIMENSIONS: valid_parameters.append((h, d)) @pytest.mark.parametrize(('header', 'dimensions'), valid_parameters) def test_init_extensive_wcs(header, dimensions): hdu = generate_hdu(header) if 'CAR' in header: f = FITSFigure(hdu, dimensions=dimensions, convention='calabretta') else: f = FITSFigure(hdu, dimensions=dimensions) f.show_grayscale() f.add_grid() f.close() # Check that for CAR projections, an exception is raised if no convention is specified @pytest.mark.parametrize(('dimensions'), VALID_DIMENSIONS) def test_init_car_invalid(dimensions): hdu = generate_hdu(CAR_REFERENCE) with pytest.raises(Exception): FITSFigure(hdu, dimensions=dimensions) # Check that images containing only NaN or Inf values don't crash FITSFigure def test_init_only_naninf(): data = np.ones((10,10)) * np.nan data[::2,::2] = np.inf f = FITSFigure(data) f.show_grayscale() f.show_colorscale() def test_init_single_pixel(): data = np.zeros((4,4)) data[...] = np.nan data[2,2] = 1 f = FITSFigure(data) f.show_grayscale() APLpy-1.1.1/aplpy/tests/test_misc.py0000644000077000000240000000105112536551502017251 0ustar tomstaff00000000000000import numpy as np from astropy.io import fits from ..core import FITSFigure def test_nan_color_copy(): """ Regression test to ensure that NaN values set in one image don't affect global Matplotlib colormap. """ data = np.zeros((16, 16)) f1 = FITSFigure(data) f1.show_grayscale() f1.set_nan_color('blue') f2 = FITSFigure(data) f2.show_grayscale() f2.set_nan_color('red') assert f1.image.get_cmap()._rgba_bad == (0.0, 0.0, 1.0, 1.0) assert f2.image.get_cmap()._rgba_bad == (1.0, 0.0, 0.0, 1.0) APLpy-1.1.1/aplpy/tests/test_pixworld.py0000644000077000000240000000430412536551502020172 0ustar tomstaff00000000000000import os import matplotlib matplotlib.use('Agg') import numpy as np from astropy.table import Table from astropy.tests.helper import pytest from .helpers import generate_wcs from .. import wcs_util HEADER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data/2d_fits', '1904-66_TAN.hdr') tab = Table({'RA':[347.,349.], 'DEC':[-68.,-68]}) GOOD_INPUT = [ [1.,2.], [[1.,2.],[3,4]], [np.arange(2), np.arange(2)], [tab['RA'], tab['DEC']] ] BAD_INPUT = [ [1,['s','w']], [np.arange(2), np.sum], [tab['RA'], 'ewr'] ] @pytest.mark.parametrize(('inputval'), GOOD_INPUT) def test_pixworld_input_and_consistency(inputval): wcs = generate_wcs(HEADER) ra, dec = wcs_util.pix2world(wcs, inputval[0], inputval[1]) x, y = wcs_util.world2pix(wcs, ra, dec) # For some inputs (e.g. list) a-b is not defined # so change them all to np.ndarrays here to make sure assert np.all(np.abs(np.array(x) - np.array(inputval[0])) < 1e-5) assert np.all(np.abs(np.array(y) - np.array(inputval[1])) < 1e-5) def test_returntypes(): wcs = generate_wcs(HEADER) ra, dec = wcs_util.pix2world(wcs, 1.,2.) assert np.isscalar(ra) and np.isscalar(dec) ra, dec = wcs_util.pix2world(wcs, [1.],[2.]) assert (type(ra) == list) and (type(dec) == list) # Astropy.table.Column objects get donwconverted np.ndarray ra, dec = wcs_util.pix2world(wcs, np.arange(2), tab['DEC']) assert isinstance(ra, np.ndarray) and isinstance(dec, np.ndarray) @pytest.mark.parametrize(('inputval'), BAD_INPUT) def test_pix2world_fail(inputval): wcs = generate_wcs(HEADER) with pytest.raises(Exception) as exc: wcs_util.pix2world(wcs, inputval[0], inputval[1]) assert exc.value.args[0] == "pix2world should be provided either with two scalars, two lists, or two numpy arrays" @pytest.mark.parametrize(('inputval'), BAD_INPUT) def test_world2pix_fail(inputval): wcs = generate_wcs(HEADER) with pytest.raises(Exception) as exc: wcs_util.world2pix(wcs, inputval[0], inputval[1]) assert exc.value.args[0] == "world2pix should be provided either with two scalars, two lists, or two numpy arrays" APLpy-1.1.1/aplpy/tests/test_pixworldmarkers.py0000644000077000000240000000373312536551502021564 0ustar tomstaff00000000000000import os import matplotlib matplotlib.use('Agg') import numpy as np from astropy.table import Table from astropy.tests.helper import pytest from astropy.io import fits from .helpers import generate_wcs from .. import FITSFigure HEADER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data/2d_fits', '1904-66_TAN.hdr') tab = Table({'RA':[347.,349.], 'DEC':[-68.,-68]}) GOOD_INPUT = [ [1,2], [[1,2],[3,4]], [np.arange(2), np.arange(2)], [tab['RA'], tab['DEC']] ] BAD_INPUT = [ [1,['s', 'e']], [np.arange(2), np.sum], [tab['RA'], 'ewr'] ] @pytest.mark.parametrize(('inputval'), GOOD_INPUT) def test_pixel_coords(inputval): data = np.zeros((16, 16)) f = FITSFigure(data) f.show_markers(inputval[0], inputval[1]) f.close() @pytest.mark.parametrize(('inputval'), GOOD_INPUT) def test_wcs_coords(inputval): wcs = generate_wcs(HEADER) header = fits.Header.fromtextfile(HEADER) wcs.naxis1 = header['NAXIS1'] wcs.naxis2 = header['NAXIS2'] f = FITSFigure(wcs) f.show_markers(inputval[0], inputval[1]) f.close() @pytest.mark.parametrize(('inputval'), BAD_INPUT) def test_pixel_coords_bad(inputval): data = np.zeros((16, 16)) f = FITSFigure(data) with pytest.raises(Exception) as exc: f.show_markers(inputval[0], inputval[1]) assert exc.value.args[0] == "world2pix should be provided either with two scalars, two lists, or two numpy arrays" f.close() @pytest.mark.parametrize(('inputval'), BAD_INPUT) def test_wcs_coords_bad(inputval): wcs = generate_wcs(HEADER) header = fits.Header.fromtextfile(HEADER) wcs.naxis1 = header['NAXIS1'] wcs.naxis2 = header['NAXIS2'] f = FITSFigure(wcs) with pytest.raises(Exception) as exc: f.show_markers(inputval[0], inputval[1]) f.close() assert exc.value.args[0] == "world2pix should be provided either with two scalars, two lists, or two numpy arrays" APLpy-1.1.1/aplpy/tests/test_rgb.py0000644000077000000240000000300512774746217017106 0ustar tomstaff00000000000000import os import warnings import numpy as np from astropy.io import fits from astropy.tests.helper import pytest, remote_data from .. import FITSFigure from ..rgb import make_rgb_image from .test_images import BaseImageTests from . import baseline_dir HEADER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data/2d_fits', '1904-66_TAN.hdr') class TestRGB(BaseImageTests): @remote_data @pytest.mark.mpl_image_compare(baseline_dir=baseline_dir, tolerance=1.5) def test_rgb(self, generate, tmpdir): # Regression test to check that RGB recenter works properly r_file = tmpdir.join('r.fits').strpath g_file = tmpdir.join('g.fits').strpath b_file = tmpdir.join('b.fits').strpath rgb_file = tmpdir.join('rgb.png').strpath np.random.seed(12345) header = fits.Header.fromtextfile(HEADER) r = fits.PrimaryHDU(np.random.random((12, 12)), header) r.writeto(r_file) g = fits.PrimaryHDU(np.random.random((12, 12)), header) g.writeto(g_file) b = fits.PrimaryHDU(np.random.random((12, 12)), header) b.writeto(b_file) make_rgb_image([r_file, g_file, b_file], rgb_file, embed_avm_tags=False) f = FITSFigure(r_file, figsize=(3, 3)) with warnings.catch_warnings(): warnings.simplefilter("ignore") f.show_rgb(rgb_file) f.tick_labels.set_xformat('dd.d') f.tick_labels.set_yformat('dd.d') f.recenter(359.3, -72.1, radius=0.05) return f APLpy-1.1.1/aplpy/tests/test_save.py0000644000077000000240000000560712536551502017267 0ustar tomstaff00000000000000import os import sys from astropy.extern import six if six.PY3: from io import BytesIO as StringIO else: from StringIO import StringIO import numpy as np from astropy.tests.helper import pytest from .. import FITSFigure FORMATS = [None, 'png', 'pdf', 'eps', 'ps', 'svg'] def is_format(filename, format): if isinstance(filename, six.string_types): f = open(filename, 'rb') else: f = filename if format == 'png': return f.read(8) == b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a' elif format == 'pdf': return f.read(4) == b'\x25\x50\x44\x46' elif format == 'eps': return f.read(23) == b'%!PS-Adobe-3.0 EPSF-3.0' elif format == 'ps': return f.read(14) == b'%!PS-Adobe-3.0' elif format == 'svg': from xml.dom import minidom return minidom.parse(f).childNodes[2].attributes['xmlns'].value == 'http://www.w3.org/2000/svg' else: raise Exception("Unknown format: %s" % format) @pytest.mark.parametrize(('format'), FORMATS) def test_write_png(tmpdir, format): filename = os.path.join(str(tmpdir), 'test_output.png') data = np.zeros((16, 16)) f = FITSFigure(data) f.show_grayscale() try: f.save(filename, format=format) except TypeError: pytest.xfail() finally: f.close() if format is None: assert is_format(filename, 'png') else: assert is_format(filename, format) @pytest.mark.parametrize(('format'), FORMATS) def test_write_pdf(tmpdir, format): filename = os.path.join(str(tmpdir), 'test_output.pdf') data = np.zeros((16, 16)) f = FITSFigure(data) f.show_grayscale() try: f.save(filename, format=format) except TypeError: pytest.xfail() finally: f.close() if format is None: assert is_format(filename, 'pdf') else: assert is_format(filename, format) @pytest.mark.parametrize(('format'), FORMATS) def test_write_eps(tmpdir, format): filename = os.path.join(str(tmpdir), 'test_output.eps') data = np.zeros((16, 16)) f = FITSFigure(data) f.show_grayscale() try: f.save(filename, format=format) except TypeError: pytest.xfail() finally: f.close() if format is None: assert is_format(filename, 'eps') else: assert is_format(filename, format) @pytest.mark.parametrize(('format'), FORMATS) def test_write_stringio(tmpdir, format): s = StringIO() data = np.zeros((16, 16)) f = FITSFigure(data) f.show_grayscale() try: f.save(s, format=format) except TypeError: pytest.xfail() finally: f.close() try: s.seek(0) except ValueError: if format == 'svg' and sys.version_info[:2] >= (3, 3): pytest.xfail() else: raise if format is None: assert is_format(s, 'png') else: assert is_format(s, format) APLpy-1.1.1/aplpy/tests/test_scalebar.py0000644000077000000240000000520612774746217020115 0ustar tomstaff00000000000000import os import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from astropy import units as u from astropy.io import fits from .. import FITSFigure header_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data/2d_fits') HEADER = fits.Header.fromtextfile(os.path.join(header_dir, '1904-66_TAN.hdr')) HDU = fits.PrimaryHDU(np.zeros((16, 16)), HEADER) def test_scalebar_add_invalid(): f = FITSFigure(HDU) with pytest.raises(TypeError): f.add_scalebar() def test_scalebar_addremove(): f = FITSFigure(HDU) f.add_scalebar(0.1) f.remove_scalebar() f.add_scalebar(0.1) f.close() def test_scalebar_showhide(): f = FITSFigure(HDU) f.add_scalebar(0.1) f.scalebar.hide() f.scalebar.show(0.1) f.close() def test_scalebar_length(): f = FITSFigure(HDU) f.add_scalebar(0.1) f.scalebar.set_length(0.01) f.scalebar.set_length(0.1) f.close() @pytest.mark.parametrize('quantity', [1*u.arcsec, u.arcsec, 2*u.degree, 5*u.radian]) def test_scalebar_length_quantity(quantity): f = FITSFigure(HDU) f.add_scalebar(quantity) f.scalebar.set_length(quantity) f.close() def test_scalebar_label(): f = FITSFigure(HDU) f.add_scalebar(0.1) f.scalebar.set_label('1 pc') f.scalebar.set_label('5 AU') f.scalebar.set_label('2"') f.close() def test_scalebar_corner(): f = FITSFigure(HDU) f.add_scalebar(0.1) for corner in ['top', 'bottom', 'left', 'right', 'top left', 'top right', 'bottom left', 'bottom right']: f.scalebar.set_corner(corner) f.close() def test_scalebar_frame(): f = FITSFigure(HDU) f.add_scalebar(0.1) f.scalebar.set_frame(True) f.scalebar.set_frame(False) f.close() def test_scalebar_color(): f = FITSFigure(HDU) f.add_scalebar(0.1) f.scalebar.set_color('black') f.scalebar.set_color('#003344') f.scalebar.set_color((1.0, 0.4, 0.3)) f.close() def test_scalebar_alpha(): f = FITSFigure(HDU) f.add_scalebar(0.1) f.scalebar.set_alpha(0.1) f.scalebar.set_alpha(0.2) f.scalebar.set_alpha(0.5) f.close() def test_scalebar_font(): f = FITSFigure(HDU) f.add_scalebar(0.1) f.scalebar.set_font(size='small', weight='bold', stretch='normal', family='serif', style='normal', variant='normal') f.close() def test_regression_exception_type(): # In Matplotlib 1.5, the exception type changed for when a property doesn't # exist, so we need to catch both AttributeError and TypeError. f = FITSFigure(HDU) f.add_scalebar(0.1, family='serif') f.close() APLpy-1.1.1/aplpy/tests/test_subplot.py0000644000077000000240000000117312536551502020013 0ustar tomstaff00000000000000import pytest import numpy as np from .. import FITSFigure def test_subplot_grid(): f = FITSFigure(np.zeros((10, 10)), subplot=(2, 2, 1)) f.show_grayscale() f.close() def test_subplot_box(): f = FITSFigure(np.zeros((10, 10)), subplot=[0.1, 0.1, 0.8, 0.8]) f.show_grayscale() f.close() @pytest.mark.parametrize('subplot', [(1, 2, 3, 4), [1, 2, 3], '111', 1.2]) def test_subplot_invalid(subplot): with pytest.raises(ValueError) as exc: FITSFigure(np.zeros((10, 10)), subplot=subplot) assert exc.value.args[0] == "subplot= should be either a tuple of three values, or a list of four values" APLpy-1.1.1/aplpy/tests/test_tick_labels.py0000644000077000000240000000276212536551502020604 0ustar tomstaff00000000000000import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from .. import FITSFigure def test_tick_labels_show_hide(): data = np.zeros((16, 16)) f = FITSFigure(data) f.tick_labels.hide() f.tick_labels.show() f.tick_labels.hide_x() f.tick_labels.show_x() f.tick_labels.hide_y() f.tick_labels.show_y() f.close() def test_tick_labels_format_scalar(): data = np.zeros((16, 16)) f = FITSFigure(data) f.tick_labels.set_xformat('%i') f.tick_labels.set_yformat('%i') f.close() def test_tick_labels_position(): data = np.zeros((16, 16)) f = FITSFigure(data) f.tick_labels.set_xposition('top') f.tick_labels.set_xposition('bottom') f.tick_labels.set_yposition('right') f.tick_labels.set_yposition('left') f.close() def test_tick_labels_position_invalid(): data = np.zeros((16, 16)) f = FITSFigure(data) with pytest.raises(ValueError): f.tick_labels.set_xposition('right') with pytest.raises(ValueError): f.tick_labels.set_xposition('left') with pytest.raises(ValueError): f.tick_labels.set_yposition('top') with pytest.raises(ValueError): f.tick_labels.set_yposition('bottom') f.close() def test_tick_labels_font(): data = np.zeros((16, 16)) f = FITSFigure(data) f.tick_labels.set_font(size='small', weight='bold', stretch='normal', family='serif', style='normal', variant='normal') f.close() APLpy-1.1.1/aplpy/tests/test_ticks.py0000644000077000000240000000246612536551502017446 0ustar tomstaff00000000000000import matplotlib matplotlib.use('Agg') import numpy as np from astropy.tests.helper import pytest from .. import FITSFigure def test_ticks_show_hide(): data = np.zeros((16, 16)) f = FITSFigure(data) f.ticks.hide() f.ticks.show() f.ticks.hide_x() f.ticks.show_x() f.ticks.hide_y() f.ticks.show_y() f.close() def test_ticks_spacing(): data = np.zeros((16, 16)) f = FITSFigure(data) f.ticks.set_xspacing(0.5) f.ticks.set_xspacing(1.) f.ticks.set_yspacing(0.5) f.ticks.set_yspacing(1.) f.close() def test_ticks_length(): data = np.zeros((16, 16)) f = FITSFigure(data) f.ticks.set_length(0) f.ticks.set_length(1) f.ticks.set_length(10) f.close() def test_ticks_color(): data = np.zeros((16, 16)) f = FITSFigure(data) f.ticks.set_color('black') f.ticks.set_color('#003344') f.ticks.set_color((1.0, 0.4, 0.3)) f.close() def test_ticks_linewidth(): data = np.zeros((16, 16)) f = FITSFigure(data) f.ticks.set_linewidth(1) f.ticks.set_linewidth(3) f.ticks.set_linewidth(10) f.close() def test_ticks_minor_frequency(): data = np.zeros((16, 16)) f = FITSFigure(data) f.ticks.set_minor_frequency(1) f.ticks.set_minor_frequency(5) f.ticks.set_minor_frequency(10) f.close() APLpy-1.1.1/aplpy/tests/test_vectors.py0000644000077000000240000000176612774746217020035 0ustar tomstaff00000000000000import numpy as np from astropy.tests.helper import pytest, remote_data from ..core import FITSFigure from .test_images import BaseImageTests from . import baseline_dir x = np.linspace(-1., 1., 10) y = np.linspace(-1., 1., 10) X, Y = np.meshgrid(x, y) np.random.seed(12345) IMAGE = np.random.random((10, 10)) PDATA = np.arange(100).reshape((10, 10)) / 50. + 0.5 ADATA = np.degrees(np.arctan2(Y, X)) class TestVectors(BaseImageTests): @remote_data @pytest.mark.mpl_image_compare(baseline_dir=baseline_dir, tolerance=1.5) def test_default(self, generate): f = FITSFigure(IMAGE, figsize=(4, 4)) f.show_grayscale() f.show_vectors(PDATA, ADATA, color='orange') return f @remote_data @pytest.mark.mpl_image_compare(baseline_dir=baseline_dir, tolerance=1.5) def test_step_scale(self, generate): f = FITSFigure(IMAGE, figsize=(4, 4)) f.show_grayscale() f.show_vectors(PDATA, ADATA, step=2, scale=0.8, color='orange') return f APLpy-1.1.1/aplpy/tests/test_wcs_util.py0000644000077000000240000000324612536551502020157 0ustar tomstaff00000000000000import os import numpy as np from astropy.io import fits from astropy.tests.helper import pytest from ..wcs_util import WCS, celestial_pixel_scale, non_celestial_pixel_scales from .helpers import generate_wcs HEADER_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') HEADER_2D = fits.Header.fromtextfile(os.path.join(HEADER_DIR, '2d_fits', '1904-66_TAN.hdr')) HEADER_3D = fits.Header.fromtextfile(os.path.join(HEADER_DIR, '3d_fits', 'cube.hdr')) def test_pixel_scale_matrix_1(): wcs = WCS(HEADER_2D) np.testing.assert_allclose(wcs.pixel_scale_matrix, [[-0.06666667, 0.], [0., 0.06666667]]) def test_celestial_scale_1(): wcs = WCS(HEADER_2D) for crota2 in range(0, 360, 20): wcs.wcs.crota = [0.,crota2] np.testing.assert_allclose(celestial_pixel_scale(wcs), 0.06666667) def test_non_celestial_scales_1(): wcs = WCS(HEADER_2D) with pytest.raises(ValueError) as exc: non_celestial_pixel_scales(wcs) assert exc.value.args[0] == "WCS is celestial, use celestial_pixel_scale instead" def test_pixel_scale_matrix_2(): wcs = WCS(HEADER_3D, dimensions=[0,2], slices=[0]) np.testing.assert_allclose(wcs.pixel_scale_matrix, [[-6.38888900e-03, 0.], [0., 6.64236100e+01]]) def test_celestial_scale_2(): wcs = WCS(HEADER_3D, dimensions=[0,2], slices=[0]) with pytest.raises(ValueError) as exc: celestial_pixel_scale(wcs) assert exc.value.args[0] == "WCS is not celestial, cannot determine celestial pixel scale" def test_non_celestial_scales_2(): wcs = WCS(HEADER_3D, dimensions=[0,2], slices=[0]) np.testing.assert_allclose(non_celestial_pixel_scales(wcs), [6.38888900e-03, 6.64236100e+01]) APLpy-1.1.1/aplpy/ticks.py0000644000077000000240000005176612774746217015271 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import warnings import numpy as np from matplotlib.pyplot import Locator from . import wcs_util from . import angle_util as au from . import scalar_util as su from . import math_util from .decorators import auto_refresh class Ticks(object): @auto_refresh def __init__(self, parent): # Store references to axes self._ax1 = parent._ax1 self._ax2 = parent._ax2 self._wcs = parent._wcs self._figure = parent._figure self._parent = parent # Save plotting parameters (required for @auto_refresh) self._parameters = parent._parameters # Set tick positions self._ax1.yaxis.tick_left() self._ax1.xaxis.tick_bottom() self._ax2.yaxis.tick_right() self._ax2.xaxis.tick_top() # Set tick spacing to default self.set_xspacing('auto') self.set_yspacing('auto') # Set major tick locators lx = WCSLocator(wcs=self._wcs, coord='x') self._ax1.xaxis.set_major_locator(lx) ly = WCSLocator(wcs=self._wcs, coord='y') self._ax1.yaxis.set_major_locator(ly) lxt = WCSLocator(wcs=self._wcs, coord='x', farside=True) self._ax2.xaxis.set_major_locator(lxt) lyt = WCSLocator(wcs=self._wcs, coord='y', farside=True) self._ax2.yaxis.set_major_locator(lyt) # Set minor tick locators lx = WCSLocator(wcs=self._wcs, coord='x', minor=True) self._ax1.xaxis.set_minor_locator(lx) ly = WCSLocator(wcs=self._wcs, coord='y', minor=True) self._ax1.yaxis.set_minor_locator(ly) lxt = WCSLocator(wcs=self._wcs, coord='x', farside=True, minor=True) self._ax2.xaxis.set_minor_locator(lxt) lyt = WCSLocator(wcs=self._wcs, coord='y', farside=True, minor=True) self._ax2.yaxis.set_minor_locator(lyt) @auto_refresh def set_xspacing(self, spacing): ''' Set the x-axis tick spacing, in degrees. To set the tick spacing to be automatically determined, set this to 'auto'. ''' if spacing == 'auto': self._ax1.xaxis.apl_auto_tick_spacing = True self._ax2.xaxis.apl_auto_tick_spacing = True else: self._ax1.xaxis.apl_auto_tick_spacing = False self._ax2.xaxis.apl_auto_tick_spacing = False if self._wcs.xaxis_coord_type in ['longitude', 'latitude']: try: au._check_format_spacing_consistency(self._ax1.xaxis.apl_label_form, au.Angle(degrees=spacing, latitude=self._wcs.xaxis_coord_type == 'latitude')) except au.InconsistentSpacing: warnings.warn("WARNING: Requested tick spacing format cannot be shown by current label format. The tick spacing will not be changed.") return self._ax1.xaxis.apl_tick_spacing = au.Angle(degrees=spacing, latitude=self._wcs.xaxis_coord_type == 'latitude') self._ax2.xaxis.apl_tick_spacing = au.Angle(degrees=spacing, latitude=self._wcs.xaxis_coord_type == 'latitude') else: try: su._check_format_spacing_consistency(self._ax1.xaxis.apl_label_form, spacing) except au.InconsistentSpacing: warnings.warn("WARNING: Requested tick spacing format cannot be shown by current label format. The tick spacing will not be changed.") return self._ax1.xaxis.apl_tick_spacing = spacing self._ax2.xaxis.apl_tick_spacing = spacing if hasattr(self._parent, 'grid'): self._parent.grid._update() @auto_refresh def set_yspacing(self, spacing): ''' Set the y-axis tick spacing, in degrees. To set the tick spacing to be automatically determined, set this to 'auto'. ''' if spacing == 'auto': self._ax1.yaxis.apl_auto_tick_spacing = True self._ax2.yaxis.apl_auto_tick_spacing = True else: self._ax1.yaxis.apl_auto_tick_spacing = False self._ax2.yaxis.apl_auto_tick_spacing = False if self._wcs.yaxis_coord_type in ['longitude', 'latitude']: try: au._check_format_spacing_consistency(self._ax1.yaxis.apl_label_form, au.Angle(degrees=spacing, latitude=self._wcs.yaxis_coord_type == 'latitude')) except au.InconsistentSpacing: warnings.warn("WARNING: Requested tick spacing format cannot be shown by current label format. The tick spacing will not be changed.") return self._ax1.yaxis.apl_tick_spacing = au.Angle(degrees=spacing, latitude=self._wcs.yaxis_coord_type == 'latitude') self._ax2.yaxis.apl_tick_spacing = au.Angle(degrees=spacing, latitude=self._wcs.yaxis_coord_type == 'latitude') else: try: su._check_format_spacing_consistency(self._ax1.yaxis.apl_label_form, spacing) except au.InconsistentSpacing: warnings.warn("WARNING: Requested tick spacing format cannot be shown by current label format. The tick spacing will not be changed.") return self._ax1.yaxis.apl_tick_spacing = spacing self._ax2.yaxis.apl_tick_spacing = spacing if hasattr(self._parent, 'grid'): self._parent.grid._update() @auto_refresh def set_color(self, color): ''' Set the color of the ticks ''' # Major ticks for line in self._ax1.xaxis.get_ticklines(): line.set_color(color) for line in self._ax1.yaxis.get_ticklines(): line.set_color(color) for line in self._ax2.xaxis.get_ticklines(): line.set_color(color) for line in self._ax2.yaxis.get_ticklines(): line.set_color(color) # Minor ticks for line in self._ax1.xaxis.get_minorticklines(): line.set_color(color) for line in self._ax1.yaxis.get_minorticklines(): line.set_color(color) for line in self._ax2.xaxis.get_minorticklines(): line.set_color(color) for line in self._ax2.yaxis.get_minorticklines(): line.set_color(color) @auto_refresh def set_length(self, length, minor_factor=0.5): ''' Set the length of the ticks (in points) ''' # Major ticks for line in self._ax1.xaxis.get_ticklines(): line.set_markersize(length) for line in self._ax1.yaxis.get_ticklines(): line.set_markersize(length) for line in self._ax2.xaxis.get_ticklines(): line.set_markersize(length) for line in self._ax2.yaxis.get_ticklines(): line.set_markersize(length) # Minor ticks for line in self._ax1.xaxis.get_minorticklines(): line.set_markersize(length * minor_factor) for line in self._ax1.yaxis.get_minorticklines(): line.set_markersize(length * minor_factor) for line in self._ax2.xaxis.get_minorticklines(): line.set_markersize(length * minor_factor) for line in self._ax2.yaxis.get_minorticklines(): line.set_markersize(length * minor_factor) @auto_refresh def set_linewidth(self, linewidth): ''' Set the linewidth of the ticks (in points) ''' # Major ticks for line in self._ax1.xaxis.get_ticklines(): line.set_mew(linewidth) for line in self._ax1.yaxis.get_ticklines(): line.set_mew(linewidth) for line in self._ax2.xaxis.get_ticklines(): line.set_mew(linewidth) for line in self._ax2.yaxis.get_ticklines(): line.set_mew(linewidth) # Minor ticks for line in self._ax1.xaxis.get_minorticklines(): line.set_mew(linewidth) for line in self._ax1.yaxis.get_minorticklines(): line.set_mew(linewidth) for line in self._ax2.xaxis.get_minorticklines(): line.set_mew(linewidth) for line in self._ax2.yaxis.get_minorticklines(): line.set_mew(linewidth) @auto_refresh def set_minor_frequency(self, frequency): ''' Set the number of subticks per major tick. Set to one to hide minor ticks. ''' self._ax1.xaxis.get_minor_locator().subticks = frequency self._ax1.yaxis.get_minor_locator().subticks = frequency self._ax2.xaxis.get_minor_locator().subticks = frequency self._ax2.yaxis.get_minor_locator().subticks = frequency @auto_refresh def show(self): """ Show the x- and y-axis ticks """ self.show_x() self.show_y() @auto_refresh def hide(self): """ Hide the x- and y-axis ticks """ self.hide_x() self.hide_y() @auto_refresh def show_x(self): """ Show the x-axis ticks """ for line in self._ax1.xaxis.get_ticklines(): line.set_visible(True) for line in self._ax2.xaxis.get_ticklines(): line.set_visible(True) for line in self._ax1.xaxis.get_minorticklines(): line.set_visible(True) for line in self._ax2.xaxis.get_minorticklines(): line.set_visible(True) @auto_refresh def hide_x(self): """ Hide the x-axis ticks """ for line in self._ax1.xaxis.get_ticklines(): line.set_visible(False) for line in self._ax2.xaxis.get_ticklines(): line.set_visible(False) for line in self._ax1.xaxis.get_minorticklines(): line.set_visible(False) for line in self._ax2.xaxis.get_minorticklines(): line.set_visible(False) @auto_refresh def show_y(self): """ Show the y-axis ticks """ for line in self._ax1.yaxis.get_ticklines(): line.set_visible(True) for line in self._ax2.yaxis.get_ticklines(): line.set_visible(True) for line in self._ax1.yaxis.get_minorticklines(): line.set_visible(True) for line in self._ax2.yaxis.get_minorticklines(): line.set_visible(True) @auto_refresh def hide_y(self): """ Hide the y-axis ticks """ for line in self._ax1.yaxis.get_ticklines(): line.set_visible(False) for line in self._ax2.yaxis.get_ticklines(): line.set_visible(False) for line in self._ax1.yaxis.get_minorticklines(): line.set_visible(False) for line in self._ax2.yaxis.get_minorticklines(): line.set_visible(False) class WCSLocator(Locator): def __init__(self, presets=None, wcs=False, coord='x', farside=False, minor=False, subticks=5): if presets is None: self.presets = {} else: self.presets = presets self._wcs = wcs self.coord = coord self.farside = farside self.minor = minor self.subticks = subticks def __call__(self): self.coord_type = self._wcs.xaxis_coord_type if self.coord == 'x' else self._wcs.yaxis_coord_type ymin, ymax = self.axis.axes.yaxis.get_view_interval() xmin, xmax = self.axis.axes.xaxis.get_view_interval() if self.axis.apl_auto_tick_spacing: self.axis.apl_tick_spacing = default_spacing(self.axis.axes, self.coord, self.axis.apl_label_form) if self.axis.apl_tick_spacing is None: self.axis.apl_tick_positions_pix = [] self.axis.apl_tick_positions_world = [] return [] if self.coord_type in ['longitude', 'latitude']: tick_spacing = self.axis.apl_tick_spacing.todegrees() else: tick_spacing = self.axis.apl_tick_spacing if self.minor: tick_spacing /= float(self.subticks) px, py, wx = tick_positions(self._wcs, tick_spacing, self.coord, self.coord, farside=self.farside, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, mode='xscaled') px, py, wx = np.array(px, float), np.array(py, float), np.array(wx, int) if self.minor: keep = np.mod(wx, self.subticks) > 0 px, py, wx = px[keep], py[keep], wx[keep] / float(self.subticks) self.axis.apl_tick_positions_world = np.array(wx, int) if self.coord == 'x': self.axis.apl_tick_positions_pix = px else: self.axis.apl_tick_positions_pix = py return self.axis.apl_tick_positions_pix def default_spacing(ax, coord, format): wcs = ax._wcs xmin, xmax = ax.xaxis.get_view_interval() ymin, ymax = ax.yaxis.get_view_interval() px, py, wx, wy = axis_positions(wcs, coord, False, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax) # Keep only pixels that fall inside the sky. This will only really work # for PyWCS 0.11 or more recent keep = ~np.isnan(wx) & ~np.isnan(wy) if np.sum(keep) == 0: return None else: px = px[keep] py = py[keep] wx = wx[keep] wy = wy[keep] coord_type = wcs.xaxis_coord_type if coord == 'x' else wcs.yaxis_coord_type if coord == 'x': # The following is required because PyWCS 0.10 and earlier did not return # NaNs for positions outside the sky, but instead returned an array with # all the same world coordinates regardless of input pixel coordinates. if len(wx) > 1 and len(np.unique(wx)) == 1: return None if coord_type in ['longitude', 'latitude']: if coord_type == 'longitude': wxmin, wxmax = math_util.smart_range(wx) else: wxmin, wxmax = min(wx), max(wx) if 'd.' in format: spacing = au.smart_round_angle_decimal((wxmax - wxmin) / 5., latitude=coord_type == 'latitude') else: spacing = au.smart_round_angle_sexagesimal((wxmax - wxmin) / 5., latitude=coord_type == 'latitude', hours='hh' in format) else: wxmin, wxmax = np.min(wx), np.max(wx) spacing = su.smart_round_angle_decimal((wxmax - wxmin) / 5.) else: # The following is required because PyWCS 0.10 and earlier did not return # NaNs for positions outside the sky, but instead returned an array with # all the same world coordinates regardless of input pixel coordinates. if len(wy) > 1 and len(np.unique(wy)) == 1: return None if coord_type in ['longitude', 'latitude']: if coord_type == 'longitude': wymin, wymax = math_util.smart_range(wy) else: wymin, wymax = min(wy), max(wy) if 'd.' in format: spacing = au.smart_round_angle_decimal((wymax - wymin) / 5., latitude=coord_type == 'latitude') else: spacing = au.smart_round_angle_sexagesimal((wymax - wymin) / 5., latitude=coord_type == 'latitude', hours='hh' in format) else: wymin, wymax = np.min(wy), np.max(wy) spacing = su.smart_round_angle_decimal((wymax - wymin) / 5.) # Find minimum spacing allowed by labels if coord_type in ['longitude', 'latitude']: min_spacing = au._get_label_precision(format, latitude=coord_type == 'latitude') if min_spacing.todegrees() > spacing.todegrees(): return min_spacing else: return spacing else: min_spacing = su._get_label_precision(format) if min_spacing is not None and min_spacing > spacing: return min_spacing else: return spacing def tick_positions(wcs, spacing, axis, coord, farside=False, xmin=False, xmax=False, ymin=False, ymax=False, mode='xscaled'): ''' Find positions of ticks along a given axis. Parameters ---------- wcs : ~aplpy.wcs_util.WCS The WCS instance for the image. spacing : float The spacing along the axis. axis : { 'x', 'y' } The axis along which we are looking for ticks. coord : { 'x', 'y' } The coordinate for which we are looking for ticks. farside : bool, optional Whether we are looking on the left or bottom axes (False) or the right or top axes (True). xmin, xmax, ymin, ymax : float, optional The range of pixel values covered by the image. mode : { 'xy', 'xscaled' }, optional If set to 'xy' the function returns the world coordinates of the ticks. If 'xscaled', then only the coordinate requested is returned, in units of the tick spacing. ''' (px, py, wx, wy) = axis_positions(wcs, axis, farside, xmin, xmax, ymin, ymax) if coord == 'x': warr, walt = wx, wy else: warr, walt = wy, wx # Check for 360 degree transition, and if encountered, # change the values so that there is continuity if (coord == 'x' and wcs.xaxis_coord_type == 'longitude') or \ (coord == 'y' and wcs.yaxis_coord_type == 'longitude'): for i in range(0, len(warr) - 1): if(abs(warr[i] - warr[i + 1]) > 180.): if(warr[i] > warr[i + 1]): warr[i + 1:] = warr[i + 1:] + 360. else: warr[i + 1:] = warr[i + 1:] - 360. # Convert warr to units of the spacing, then ticks are at integer values warr = warr / spacing # Create empty arrays for tick positions iall = [] wall = [] # Loop over ticks which lie in the range covered by the axis for w in np.arange(np.floor(min(warr)), np.ceil(max(warr)), 1.): # Find all the positions at which to interpolate inter = np.where(((warr[:-1] <= w) & (warr[1:] > w)) | ((warr[:-1] > w) & (warr[1:] <= w)))[0] # If there are any intersections, keep the indices, and the position # of the interpolation if len(inter) > 0: iall.append(inter.astype(int)) wall.append(np.repeat(w, len(inter)).astype(float)) if len(iall) > 0: iall = np.hstack(iall) wall = np.hstack(wall) else: if mode == 'xscaled': return [], [], [] else: return [], [], [], [] # Now we can interpolate as needed dwarr = warr[1:] - warr[:-1] px_out = px[:-1][iall] + (px[1:][iall] - px[:-1][iall]) * (wall - warr[:-1][iall]) / dwarr[iall] py_out = py[:-1][iall] + (py[1:][iall] - py[:-1][iall]) * (wall - warr[:-1][iall]) / dwarr[iall] if mode == 'xscaled': warr_out = wall return px_out, py_out, warr_out elif mode == 'xy': warr_out = wall * spacing walt_out = walt[:-1][iall] + (walt[1:][iall] - walt[:-1][iall]) * (wall - warr[:-1][iall]) / dwarr[iall] if coord == 'x': return px_out, py_out, warr_out, walt_out else: return px_out, py_out, walt_out, warr_out def axis_positions(wcs, axis, farside, xmin=False, xmax=False, ymin=False, ymax=False): ''' Find the world coordinates of all pixels along an axis. Parameters ---------- wcs : ~aplpy.wcs_util.WCS The WCS instance for the image. axis : { 'x', 'y' } The axis along which we are computing world coordinates. farside : bool Whether we are looking on the left or bottom axes (False) or the right or top axes (True). xmin, xmax, ymin, ymax : float, optional The range of pixel values covered by the image ''' if not xmin: xmin = 0.5 if not xmax: xmax = 0.5 + wcs.nx if not ymin: ymin = 0.5 if not ymax: ymax = 0.5 + wcs.ny # Check options assert axis == 'x' or axis == 'y', "The axis= argument should be set to x or y" # Generate an array of pixel values for the x-axis if axis == 'x': x_pix = np.linspace(xmin, xmax, 512) y_pix = np.ones(np.shape(x_pix)) if(farside): y_pix = y_pix * ymax else: y_pix = y_pix * ymin else: y_pix = np.linspace(ymin, ymax, 512) x_pix = np.ones(np.shape(y_pix)) if(farside): x_pix = x_pix * xmax else: x_pix = x_pix * xmin # Convert these to world coordinates x_world, y_world = wcs_util.pix2world(wcs, x_pix, y_pix) return x_pix, y_pix, x_world, y_world def coord_range(wcs): ''' Find the range of coordinates that intersect the axes. Parameters ---------- wcs : ~aplpy.wcs_util.WCS The WCS instance for the image. ''' x_pix, y_pix, x_world_1, y_world_1 = axis_positions(wcs, 'x', farside=False) x_pix, y_pix, x_world_2, y_world_2 = axis_positions(wcs, 'x', farside=True) x_pix, y_pix, x_world_3, y_world_3 = axis_positions(wcs, 'y', farside=False) x_pix, y_pix, x_world_4, y_world_4 = axis_positions(wcs, 'y', farside=True) x_world = np.hstack([x_world_1, x_world_2, x_world_3, x_world_4]) y_world = np.hstack([y_world_1, y_world_2, y_world_3, y_world_4]) x_min = min(x_world) x_max = max(x_world) y_min = min(y_world) y_max = max(y_world) return x_min, x_max, y_min, y_max APLpy-1.1.1/aplpy/version.py0000644000077000000240000001545212774747316015632 0ustar tomstaff00000000000000# Autogenerated by Astropy-affiliated package aplpy's setup.py on 2016-10-04 16:46:22.235148 from __future__ import unicode_literals import datetime import locale import os import subprocess import warnings def _decode_stdio(stream): try: stdio_encoding = locale.getdefaultlocale()[1] or 'utf-8' except ValueError: stdio_encoding = 'utf-8' try: text = stream.decode(stdio_encoding) except UnicodeDecodeError: # Final fallback text = stream.decode('latin1') return text def update_git_devstr(version, path=None): """ Updates the git revision string if and only if the path is being imported directly from a git working copy. This ensures that the revision number in the version string is accurate. """ try: # Quick way to determine if we're in git or not - returns '' if not devstr = get_git_devstr(sha=True, show_warning=False, path=path) except OSError: return version if not devstr: # Probably not in git so just pass silently return version if 'dev' in version: # update to the current git revision version_base = version.split('.dev', 1)[0] devstr = get_git_devstr(sha=False, show_warning=False, path=path) return version_base + '.dev' + devstr else: # otherwise it's already the true/release version return version def get_git_devstr(sha=False, show_warning=True, path=None): """ Determines the number of revisions in this repository. Parameters ---------- sha : bool If True, the full SHA1 hash will be returned. Otherwise, the total count of commits in the repository will be used as a "revision number". show_warning : bool If True, issue a warning if git returns an error code, otherwise errors pass silently. path : str or None If a string, specifies the directory to look in to find the git repository. If `None`, the current working directory is used, and must be the root of the git repository. If given a filename it uses the directory containing that file. Returns ------- devversion : str Either a string with the revision number (if `sha` is False), the SHA1 hash of the current commit (if `sha` is True), or an empty string if git version info could not be identified. """ if path is None: path = os.getcwd() if not _get_repo_path(path, levels=0): return '' if not os.path.isdir(path): path = os.path.abspath(os.path.dirname(path)) if sha: # Faster for getting just the hash of HEAD cmd = ['rev-parse', 'HEAD'] else: cmd = ['rev-list', '--count', 'HEAD'] def run_git(cmd): try: p = subprocess.Popen(['git'] + cmd, cwd=path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) stdout, stderr = p.communicate() except OSError as e: if show_warning: warnings.warn('Error running git: ' + str(e)) return (None, b'', b'') if p.returncode == 128: if show_warning: warnings.warn('No git repository present at {0!r}! Using ' 'default dev version.'.format(path)) return (p.returncode, b'', b'') if p.returncode == 129: if show_warning: warnings.warn('Your git looks old (does it support {0}?); ' 'consider upgrading to v1.7.2 or ' 'later.'.format(cmd[0])) return (p.returncode, stdout, stderr) elif p.returncode != 0: if show_warning: warnings.warn('Git failed while determining revision ' 'count: {0}'.format(_decode_stdio(stderr))) return (p.returncode, stdout, stderr) return p.returncode, stdout, stderr returncode, stdout, stderr = run_git(cmd) if not sha and returncode == 129: # git returns 129 if a command option failed to parse; in # particular this could happen in git versions older than 1.7.2 # where the --count option is not supported # Also use --abbrev-commit and --abbrev=0 to display the minimum # number of characters needed per-commit (rather than the full hash) cmd = ['rev-list', '--abbrev-commit', '--abbrev=0', 'HEAD'] returncode, stdout, stderr = run_git(cmd) # Fall back on the old method of getting all revisions and counting # the lines if returncode == 0: return str(stdout.count(b'\n')) else: return '' elif sha: return _decode_stdio(stdout)[:40] else: return _decode_stdio(stdout).strip() def _get_repo_path(pathname, levels=None): """ Given a file or directory name, determine the root of the git repository this path is under. If given, this won't look any higher than ``levels`` (that is, if ``levels=0`` then the given path must be the root of the git repository and is returned if so. Returns `None` if the given path could not be determined to belong to a git repo. """ if os.path.isfile(pathname): current_dir = os.path.abspath(os.path.dirname(pathname)) elif os.path.isdir(pathname): current_dir = os.path.abspath(pathname) else: return None current_level = 0 while levels is None or current_level <= levels: if os.path.exists(os.path.join(current_dir, '.git')): return current_dir current_level += 1 if current_dir == os.path.dirname(current_dir): break current_dir = os.path.dirname(current_dir) return None _packagename = "aplpy" _last_generated_version = "1.1.1" _last_githash = "1c8402d91a08e309db9891f7f7a81632074df336" # Determine where the source code for this module # lives. If __file__ is not a filesystem path then # it is assumed not to live in a git repo at all. if _get_repo_path(__file__, levels=len(_packagename.split('.'))): version = update_git_devstr(_last_generated_version, path=__file__) githash = get_git_devstr(sha=True, show_warning=False, path=__file__) or _last_githash else: # The file does not appear to live in a git repo so don't bother # invoking git version = _last_generated_version githash = _last_githash major = 1 minor = 1 bugfix = 1 release = True timestamp = datetime.datetime(2016, 10, 4, 16, 46, 22, 235148) debug = False try: from ._compiler import compiler except ImportError: compiler = "unknown" try: from .cython_version import cython_version except ImportError: cython_version = "unknown" APLpy-1.1.1/aplpy/wcs_util.py0000644000077000000240000004162112771205772015763 0ustar tomstaff00000000000000from __future__ import absolute_import, print_function, division import numpy as np import warnings from astropy import log from astropy.wcs import WCS as AstropyWCS import astropy.wcs def decode_ascii(string): try: return string.decode('ascii') except AttributeError: return string class WCS(AstropyWCS): def __init__(self, *args, **kwargs): if 'slices' in kwargs: self._slices = kwargs.pop('slices') else: self._slices = [] if 'dimensions' in kwargs: self._dimensions = kwargs.pop('dimensions') else: self._dimensions = [0, 1] AstropyWCS.__init__(self, *args, **kwargs) # Fix common non-standard units self.wcs.unitfix() # Now find the values of the coordinates in the slices - only needed if # data has more than two dimensions if len(self._slices) > 0: self.nx = args[0]['NAXIS%i' % (self._dimensions[0] + 1)] self.ny = args[0]['NAXIS%i' % (self._dimensions[1] + 1)] xpix = np.arange(self.nx) + 1. ypix = np.arange(self.ny) + 1. xpix, ypix = np.meshgrid(xpix, ypix) xpix, ypix = xpix.reshape(self.nx * self.ny), ypix.reshape(self.nx * self.ny) s = 0 coords = [] for dim in range(self.naxis): if dim == self._dimensions[0]: coords.append(xpix) elif dim == self._dimensions[1]: coords.append(ypix) else: coords.append(np.repeat(self._slices[s], xpix.shape)) s += 1 coords = np.vstack(coords).transpose() result = AstropyWCS.wcs_pix2world(self, coords, 1) self._mean_world = np.mean(result, axis=0) # result = result.transpose() # result = result.reshape((result.shape[0],) + (self.ny, self.nx)) # Now guess what 'type' of values are on each axis if self.ctype_x[:4] == 'RA--' or \ self.ctype_x[1:4] == 'LON': self.set_xaxis_coord_type('longitude') self.set_xaxis_coord_type('longitude') elif self.ctype_x[:4] == 'DEC-' or \ self.ctype_x[1:4] == 'LAT': self.set_xaxis_coord_type('latitude') self.set_xaxis_coord_type('latitude') else: self.set_xaxis_coord_type('scalar') self.set_xaxis_coord_type('scalar') if self.ctype_y[:4] == 'RA--' or \ self.ctype_y[1:4] == 'LON': self.set_yaxis_coord_type('longitude') self.set_yaxis_coord_type('longitude') elif self.ctype_y[:4] == 'DEC-' or \ self.ctype_y[1:4] == 'LAT': self.set_yaxis_coord_type('latitude') self.set_yaxis_coord_type('latitude') else: self.set_yaxis_coord_type('scalar') self.set_yaxis_coord_type('scalar') @property def is_celestial(self): return self.wcs.lng in self._dimensions and self.wcs.lat in self._dimensions @property def pixel_scale_matrix(self): # Only for the subset of dimensions used here d0 = self._dimensions[0] d1 = self._dimensions[1] cdelt = np.matrix([[self.wcs.get_cdelt()[d0],0], [0, self.wcs.get_cdelt()[d1]]]) pc_full = self.wcs.get_pc() pc = np.matrix([[pc_full[d0,d0], pc_full[d0,d1]], [pc_full[d1,d0], pc_full[d1,d1]]]) m = np.array(cdelt * pc) return m def set_xaxis_coord_type(self, coord_type): if coord_type in ['longitude', 'latitude', 'scalar']: self.xaxis_coord_type = coord_type else: raise Exception("coord_type should be one of longitude/latitude/scalar") def set_yaxis_coord_type(self, coord_type): if coord_type in ['longitude', 'latitude', 'scalar']: self.yaxis_coord_type = coord_type else: raise Exception("coord_type should be one of longitude/latitude/scalar") def __getattr__(self, attribute): if attribute[-2:] == '_x': axis = self._dimensions[0] elif attribute[-2:] == '_y': axis = self._dimensions[1] else: raise AttributeError("Attribute %s does not exist" % attribute) if attribute[:5] == 'ctype': return decode_ascii(self.wcs.ctype[axis]) elif attribute[:5] == 'cname': return decode_ascii(self.wcs.cname[axis]) elif attribute[:5] == 'cunit': return str(self.wcs.cunit[axis]) elif attribute[:5] == 'crval': return decode_ascii(self.wcs.crval[axis]) elif attribute[:5] == 'crpix': return decode_ascii(self.wcs.crpix[axis]) else: raise AttributeError("Attribute %s does not exist" % attribute) def wcs_world2pix(self, x, y, origin): if self.naxis == 2: if self._dimensions[1] < self._dimensions[0]: xp, yp = AstropyWCS.wcs_world2pix(self, y, x, origin) return yp, xp else: return AstropyWCS.wcs_world2pix(self, x, y, origin) else: coords = [] s = 0 for dim in range(self.naxis): if dim == self._dimensions[0]: coords.append(x) elif dim == self._dimensions[1]: coords.append(y) else: # The following is an approximation, and will break down if # the world coordinate changes significantly over the slice coords.append(np.repeat(self._mean_world[dim], x.shape)) s += 1 coords = np.vstack(coords).transpose() # Due to a bug in pywcs, we need to loop over each coordinate # result = AstropyWCS.wcs_world2pix(self, coords, origin) result = np.zeros(coords.shape) for i in range(result.shape[0]): result[i:i + 1, :] = AstropyWCS.wcs_world2pix(self, coords[i:i + 1, :], origin) return result[:, self._dimensions[0]], result[:, self._dimensions[1]] def wcs_pix2world(self, x, y, origin): if self.naxis == 2: if self._dimensions[1] < self._dimensions[0]: xw, yw = AstropyWCS.wcs_pix2world(self, y, x, origin) return yw, xw else: return AstropyWCS.wcs_pix2world(self, x, y, origin) else: coords = [] s = 0 for dim in range(self.naxis): if dim == self._dimensions[0]: coords.append(x) elif dim == self._dimensions[1]: coords.append(y) else: coords.append(np.repeat(self._slices[s] + 0.5, x.shape)) s += 1 coords = np.vstack(coords).transpose() result = AstropyWCS.wcs_pix2world(self, coords, origin) return result[:, self._dimensions[0]], result[:, self._dimensions[1]] def convert_coords(x, y, input, output): system_in, equinox_in = input system_out, equinox_out = output if input == output: return x, y # Need to take into account inverted coords if system_in['name'] == 'galactic' and system_out['name'] == 'equatorial': if equinox_out == 'j2000': x, y = gal2fk5(x, y) elif equinox_out == 'b1950': x, y = gal2fk5(x, y) x, y = j2000tob1950(x, y) else: raise Exception("Cannot convert from galactic to equatorial coordinates for equinox=%s" % equinox_out) elif system_in['name'] == 'equatorial' and system_out['name'] == 'galactic': if equinox_in == 'j2000': x, y = fk52gal(x, y) elif equinox_in == 'b1950': x, y = b1950toj2000(x, y) x, y = fk52gal(x, y) else: raise Exception("Cannot convert from equatorial to equatorial coordinates for equinox=%s" % equinox_in) elif system_in['name'] == 'equatorial' and system_out['name'] == 'equatorial': if equinox_in == 'b1950' and equinox_out == 'j2000': x, y = b1950toj2000(x, y) elif equinox_in == 'j2000' and equinox_out == 'b1950': x, y = j2000tob1950(x, y) elif equinox_in == equinox_out: pass else: raise Exception("Cannot convert between equatorial coordinates for equinoxes %s and %s" % (equinox_in, equinox_out)) else: raise Exception("Cannot (yet) convert between %s, %s and %s, %s" % (system_in['name'], equinox_in, system_out['name'], equinox_out)) # Take into account inverted coordinates if system_in['inverted'] is system_out['inverted']: return x, y else: return y, x def precession_matrix(equinox1, equinox2, fk4=False): "Adapted from the IDL astronomy library" deg_to_rad = np.pi / 180. sec_to_rad = deg_to_rad / 3600. t = 0.001 * (equinox2 - equinox1) if not fk4: st = 0.001 * (equinox1 - 2000.) # Compute 3 rotation angles a = sec_to_rad * t * (23062.181 + st * (139.656 + 0.0139 * st) + t * (30.188 - 0.344 * st + 17.998 * t)) b = sec_to_rad * t * t * (79.280 + 0.410 * st + 0.205 * t) + a c = sec_to_rad * t * (20043.109 - st * (85.33 + 0.217 * st) + t * (- 42.665 - 0.217 * st - 41.833 * t)) else: st = 0.001 * (equinox1 - 1900.) # Compute 3 rotation angles a = sec_to_rad * t * (23042.53 + st * (139.75 + 0.06 * st) + t * (30.23 - 0.27 * st + 18.0 * t)) b = sec_to_rad * t * t * (79.27 + 0.66 * st + 0.32 * t) + a c = sec_to_rad * t * (20046.85 - st * (85.33 + 0.37 * st) + t * (- 42.67 - 0.37 * st - 41.8 * t)) sina = np.sin(a) sinb = np.sin(b) sinc = np.sin(c) cosa = np.cos(a) cosb = np.cos(b) cosc = np.cos(c) r = np.matrix([[cosa * cosb * cosc - sina * sinb, sina * cosb + cosa * sinb * cosc, cosa * sinc], [- cosa * sinb - sina * cosb * cosc, cosa * cosb - sina * sinb * cosc, - sina * sinc], [- cosb * sinc, - sinb * sinc, cosc]]) return r P1 = precession_matrix(1950., 2000.) P2 = precession_matrix(2000., 1950.) def b1950toj2000(ra, dec): ''' Convert B1950 to J2000 coordinates. This routine is based on the technique described at http://www.stargazing.net/kepler/b1950.html ''' # Convert to radians ra = np.radians(ra) dec = np.radians(dec) # Convert RA, Dec to rectangular coordinates x = np.cos(ra) * np.cos(dec) y = np.sin(ra) * np.cos(dec) z = np.sin(dec) # Apply the precession matrix x2 = P1[0, 0] * x + P1[1, 0] * y + P1[2, 0] * z y2 = P1[0, 1] * x + P1[1, 1] * y + P1[2, 1] * z z2 = P1[0, 2] * x + P1[1, 2] * y + P1[2, 2] * z # Convert the new rectangular coordinates back to RA, Dec ra = np.arctan2(y2, x2) dec = np.arcsin(z2) # Convert to degrees ra = np.degrees(ra) dec = np.degrees(dec) # Make sure ra is between 0. and 360. ra = np.mod(ra, 360.) dec = np.mod(dec + 90., 180.) - 90. return ra, dec def j2000tob1950(ra, dec): ''' Convert J2000 to B1950 coordinates. This routine was derived by taking the inverse of the b1950toj2000 routine ''' # Convert to radians ra = np.radians(ra) dec = np.radians(dec) # Convert RA, Dec to rectangular coordinates x = np.cos(ra) * np.cos(dec) y = np.sin(ra) * np.cos(dec) z = np.sin(dec) # Apply the precession matrix x2 = P2[0, 0] * x + P2[1, 0] * y + P2[2, 0] * z y2 = P2[0, 1] * x + P2[1, 1] * y + P2[2, 1] * z z2 = P2[0, 2] * x + P2[1, 2] * y + P2[2, 2] * z # Convert the new rectangular coordinates back to RA, Dec ra = np.arctan2(y2, x2) dec = np.arcsin(z2) # Convert to degrees ra = np.degrees(ra) dec = np.degrees(dec) # Make sure ra is between 0. and 360. ra = np.mod(ra, 360.) dec = np.mod(dec + 90., 180.) - 90. return ra, dec # Galactic conversion constants RA_NGP = np.radians(192.859508333333) DEC_NGP = np.radians(27.1283361111111) L_CP = np.radians(122.932) L_0 = L_CP - np.pi / 2. RA_0 = RA_NGP + np.pi / 2. DEC_0 = np.pi / 2. - DEC_NGP def gal2fk5(l, b): l = np.radians(l) b = np.radians(b) sind = np.sin(b) * np.sin(DEC_NGP) + np.cos(b) * np.cos(DEC_NGP) * np.sin(l - L_0) dec = np.arcsin(sind) cosa = np.cos(l - L_0) * np.cos(b) / np.cos(dec) sina = (np.cos(b) * np.sin(DEC_NGP) * np.sin(l - L_0) - np.sin(b) * np.cos(DEC_NGP)) / np.cos(dec) dec = np.degrees(dec) ra = np.arccos(cosa) ra[np.where(sina < 0.)] = -ra[np.where(sina < 0.)] ra = np.degrees(ra + RA_0) ra = np.mod(ra, 360.) dec = np.mod(dec + 90., 180.) - 90. return ra, dec def fk52gal(ra, dec): ra, dec = np.radians(ra), np.radians(dec) sinb = np.sin(dec) * np.cos(DEC_0) - np.cos(dec) * np.sin(ra - RA_0) * np.sin(DEC_0) b = np.arcsin(sinb) cosl = np.cos(dec) * np.cos(ra - RA_0) / np.cos(b) sinl = (np.sin(dec) * np.sin(DEC_0) + np.cos(dec) * np.sin(ra - RA_0) * np.cos(DEC_0)) / np.cos(b) b = np.degrees(b) l = np.arccos(cosl) l[np.where(sinl < 0.)] = - l[np.where(sinl < 0.)] l = np.degrees(l + L_0) l = np.mod(l, 360.) b = np.mod(b + 90., 180.) - 90. return l, b def system(wcs): xcoord = wcs.ctype_x[0:4] ycoord = wcs.ctype_y[0:4] equinox = wcs.wcs.equinox system = {} if xcoord == 'RA--' and ycoord == 'DEC-': system['name'] = 'equatorial' system['inverted'] = False elif ycoord == 'RA--' and xcoord == 'DEC-': system['name'] = 'equatorial' system['inverted'] = True elif xcoord == 'GLON' and ycoord == 'GLAT': system['name'] = 'galactic' system['inverted'] = False elif ycoord == 'GLON' and xcoord == 'GLAT': system['name'] = 'galactic' system['inverted'] = True elif xcoord == 'ELON' and ycoord == 'ELAT': system['name'] = 'ecliptic' system['inverted'] = False elif ycoord == 'ELON' and xcoord == 'ELAT': system['name'] = 'ecliptic' system['inverted'] = True else: system['name'] = 'unknown' system['inverted'] = False if system['name'] == 'equatorial': if equinox == '' or np.isnan(equinox) or equinox == 0.: log.warning("Cannot determine equinox. Assuming J2000.") equinox = 'j2000' elif equinox == 1950.: equinox = 'b1950' elif equinox == 2000.: equinox = 'j2000' else: raise Exception("Cannot use equinox %s" % equinox) else: equinox = 'none' units = 'degrees' return system, equinox, units def world2pix(wcs, x_world, y_world): if np.isscalar(x_world) and np.isscalar(y_world): x_pix, y_pix = wcs.wcs_world2pix(np.array([x_world]), np.array([y_world]), 1) return x_pix[0], y_pix[0] elif (type(x_world) == list) and (type(y_world) == list): x_pix, y_pix = wcs.wcs_world2pix(np.array(x_world), np.array(y_world), 1) return x_pix.tolist(), y_pix.tolist() elif isinstance(x_world, np.ndarray) and isinstance(y_world, np.ndarray): return wcs.wcs_world2pix(x_world, y_world, 1) else: raise Exception("world2pix should be provided either with two scalars, two lists, or two numpy arrays") def pix2world(wcs, x_pix, y_pix): if np.isscalar(x_pix) and np.isscalar(y_pix): x_world, y_world = wcs.wcs_pix2world(np.array([x_pix]), np.array([y_pix]), 1) return x_world[0], y_world[0] elif (type(x_pix) == list) and (type(y_pix) == list): x_world, y_world = wcs.wcs_pix2world(np.array(x_pix), np.array(y_pix), 1) return x_world.tolist(), y_world.tolist() elif isinstance(x_pix, np.ndarray) and isinstance(y_pix, np.ndarray): return wcs.wcs_pix2world(x_pix, y_pix, 1) else: raise Exception("pix2world should be provided either with two scalars, two lists, or two numpy arrays") def celestial_pixel_scale(wcs): """ If the pixels are square, return the pixel scale in the spatial dimensions """ if not wcs.is_celestial: raise ValueError("WCS is not celestial, cannot determine celestial pixel scale") # Extract celestial part of WCS and sanitize from astropy.wcs import WCSSUB_CELESTIAL wcs = wcs.sub([WCSSUB_CELESTIAL]) pccd = wcs.pixel_scale_matrix scale = np.sqrt((pccd ** 2).sum(axis=0)) if not np.allclose(scale[0], scale[1]): warnings.warn("Pixels are not square, using an average pixel scale") return np.mean(scale) else: return scale[0] def non_celestial_pixel_scales(wcs): if wcs.is_celestial: raise ValueError("WCS is celestial, use celestial_pixel_scale instead") pccd = wcs.pixel_scale_matrix if np.allclose(pccd[1,0], 0) and np.allclose(pccd[0,1], 0): return np.abs(np.diagonal(pccd)) else: raise ValueError("WCS is rotated, cannot determine consistent pixel scales") APLpy-1.1.1/astropy_helpers/0000755000077000000240000000000012774747316015662 5ustar tomstaff00000000000000APLpy-1.1.1/astropy_helpers/.travis.yml0000644000077000000240000000417512770325077017772 0ustar tomstaff00000000000000# We set the language to c because python isn't supported on the MacOS X nodes # on Travis. However, the language ends up being irrelevant anyway, since we # install Python ourselves using conda. language: c os: - osx - linux # Setting sudo to false opts in to Travis-CI container-based builds. sudo: false # The apt packages below are needed for sphinx builds, which can no longer # be installed with sudo apt-get. addons: apt: packages: - graphviz env: matrix: - PYTHON_VERSION=2.7 - PYTHON_VERSION=3.3 - PYTHON_VERSION=3.4 - PYTHON_VERSION=3.5 global: - CONDA_DEPENDENCIES="setuptools pytest sphinx cython numpy" - PIP_DEPENDENCIES="coveralls pytest-cov" matrix: include: - os: linux env: PYTHON_VERSION=2.7 SETUPTOOLS_VERSION=20 - os: linux env: PYTHON_VERSION=2.7 SETUPTOOLS_VERSION=dev DEBUG=True CONDA_DEPENDENCIES='pytest sphinx cython numpy' allow_failures: - os: linux env: PYTHON_VERSION=2.7 SETUPTOOLS_VERSION=dev DEBUG=True CONDA_DEPENDENCIES='pytest sphinx cython numpy' before_install: - if [[ $TRAVIS_OS_NAME == osx ]]; then brew update; brew install graphviz; fi install: - git clone git://github.com/astropy/ci-helpers.git - source ci-helpers/travis/setup_conda_$TRAVIS_OS_NAME.sh # We cannot install the developer version of setuptools using pip because # pip tries to remove the previous version of setuptools before the # installation is complete, which causes issues. Instead, we just install # setuptools manually. - if [[ $SETUPTOOLS_VERSION == dev ]]; then git clone http://github.com/pypa/setuptools.git; cd setuptools; python setup.py install; cd ..; fi before_script: # Some of the tests use git commands that require a user to be configured - git config --global user.name "A U Thor" - git config --global user.email "author@example.com" script: # Use full path for coveragerc; see issue #193 - py.test --cov astropy_helpers --cov-config $(pwd)/astropy_helpers/tests/coveragerc astropy_helpers after_success: - coveralls APLpy-1.1.1/astropy_helpers/ah_bootstrap.py0000644000077000000240000010650212703324024020700 0ustar tomstaff00000000000000""" This bootstrap module contains code for ensuring that the astropy_helpers package will be importable by the time the setup.py script runs. It also includes some workarounds to ensure that a recent-enough version of setuptools is being used for the installation. This module should be the first thing imported in the setup.py of distributions that make use of the utilities in astropy_helpers. If the distribution ships with its own copy of astropy_helpers, this module will first attempt to import from the shipped copy. However, it will also check PyPI to see if there are any bug-fix releases on top of the current version that may be useful to get past platform-specific bugs that have been fixed. When running setup.py, use the ``--offline`` command-line option to disable the auto-upgrade checks. When this module is imported or otherwise executed it automatically calls a main function that attempts to read the project's setup.cfg file, which it checks for a configuration section called ``[ah_bootstrap]`` the presences of that section, and options therein, determine the next step taken: If it contains an option called ``auto_use`` with a value of ``True``, it will automatically call the main function of this module called `use_astropy_helpers` (see that function's docstring for full details). Otherwise no further action is taken (however, ``ah_bootstrap.use_astropy_helpers`` may be called manually from within the setup.py script). Additional options in the ``[ah_boostrap]`` section of setup.cfg have the same names as the arguments to `use_astropy_helpers`, and can be used to configure the bootstrap script when ``auto_use = True``. See https://github.com/astropy/astropy-helpers for more details, and for the latest version of this module. """ import contextlib import errno import imp import io import locale import os import re import subprocess as sp import sys try: from ConfigParser import ConfigParser, RawConfigParser except ImportError: from configparser import ConfigParser, RawConfigParser if sys.version_info[0] < 3: _str_types = (str, unicode) _text_type = unicode PY3 = False else: _str_types = (str, bytes) _text_type = str PY3 = True # What follows are several import statements meant to deal with install-time # issues with either missing or misbehaving pacakges (including making sure # setuptools itself is installed): # Some pre-setuptools checks to ensure that either distribute or setuptools >= # 0.7 is used (over pre-distribute setuptools) if it is available on the path; # otherwise the latest setuptools will be downloaded and bootstrapped with # ``ez_setup.py``. This used to be included in a separate file called # setuptools_bootstrap.py; but it was combined into ah_bootstrap.py try: import pkg_resources _setuptools_req = pkg_resources.Requirement.parse('setuptools>=0.7') # This may raise a DistributionNotFound in which case no version of # setuptools or distribute is properly installed _setuptools = pkg_resources.get_distribution('setuptools') if _setuptools not in _setuptools_req: # Older version of setuptools; check if we have distribute; again if # this results in DistributionNotFound we want to give up _distribute = pkg_resources.get_distribution('distribute') if _setuptools != _distribute: # It's possible on some pathological systems to have an old version # of setuptools and distribute on sys.path simultaneously; make # sure distribute is the one that's used sys.path.insert(1, _distribute.location) _distribute.activate() imp.reload(pkg_resources) except: # There are several types of exceptions that can occur here; if all else # fails bootstrap and use the bootstrapped version from ez_setup import use_setuptools use_setuptools() # Note: The following import is required as a workaround to # https://github.com/astropy/astropy-helpers/issues/89; if we don't import this # module now, it will get cleaned up after `run_setup` is called, but that will # later cause the TemporaryDirectory class defined in it to stop working when # used later on by setuptools try: import setuptools.py31compat except ImportError: pass # matplotlib can cause problems if it is imported from within a call of # run_setup(), because in some circumstances it will try to write to the user's # home directory, resulting in a SandboxViolation. See # https://github.com/matplotlib/matplotlib/pull/4165 # Making sure matplotlib, if it is available, is imported early in the setup # process can mitigate this (note importing matplotlib.pyplot has the same # issue) try: import matplotlib matplotlib.use('Agg') import matplotlib.pyplot except: # Ignore if this fails for *any* reason* pass # End compatibility imports... # In case it didn't successfully import before the ez_setup checks import pkg_resources from setuptools import Distribution from setuptools.package_index import PackageIndex from setuptools.sandbox import run_setup from distutils import log from distutils.debug import DEBUG # TODO: Maybe enable checking for a specific version of astropy_helpers? DIST_NAME = 'astropy-helpers' PACKAGE_NAME = 'astropy_helpers' # Defaults for other options DOWNLOAD_IF_NEEDED = True INDEX_URL = 'https://pypi.python.org/simple' USE_GIT = True OFFLINE = False AUTO_UPGRADE = True # A list of all the configuration options and their required types CFG_OPTIONS = [ ('auto_use', bool), ('path', str), ('download_if_needed', bool), ('index_url', str), ('use_git', bool), ('offline', bool), ('auto_upgrade', bool) ] class _Bootstrapper(object): """ Bootstrapper implementation. See ``use_astropy_helpers`` for parameter documentation. """ def __init__(self, path=None, index_url=None, use_git=None, offline=None, download_if_needed=None, auto_upgrade=None): if path is None: path = PACKAGE_NAME if not (isinstance(path, _str_types) or path is False): raise TypeError('path must be a string or False') if PY3 and not isinstance(path, _text_type): fs_encoding = sys.getfilesystemencoding() path = path.decode(fs_encoding) # path to unicode self.path = path # Set other option attributes, using defaults where necessary self.index_url = index_url if index_url is not None else INDEX_URL self.offline = offline if offline is not None else OFFLINE # If offline=True, override download and auto-upgrade if self.offline: download_if_needed = False auto_upgrade = False self.download = (download_if_needed if download_if_needed is not None else DOWNLOAD_IF_NEEDED) self.auto_upgrade = (auto_upgrade if auto_upgrade is not None else AUTO_UPGRADE) # If this is a release then the .git directory will not exist so we # should not use git. git_dir_exists = os.path.exists(os.path.join(os.path.dirname(__file__), '.git')) if use_git is None and not git_dir_exists: use_git = False self.use_git = use_git if use_git is not None else USE_GIT # Declared as False by default--later we check if astropy-helpers can be # upgraded from PyPI, but only if not using a source distribution (as in # the case of import from a git submodule) self.is_submodule = False @classmethod def main(cls, argv=None): if argv is None: argv = sys.argv config = cls.parse_config() config.update(cls.parse_command_line(argv)) auto_use = config.pop('auto_use', False) bootstrapper = cls(**config) if auto_use: # Run the bootstrapper, otherwise the setup.py is using the old # use_astropy_helpers() interface, in which case it will run the # bootstrapper manually after reconfiguring it. bootstrapper.run() return bootstrapper @classmethod def parse_config(cls): if not os.path.exists('setup.cfg'): return {} cfg = ConfigParser() try: cfg.read('setup.cfg') except Exception as e: if DEBUG: raise log.error( "Error reading setup.cfg: {0!r}\n{1} will not be " "automatically bootstrapped and package installation may fail." "\n{2}".format(e, PACKAGE_NAME, _err_help_msg)) return {} if not cfg.has_section('ah_bootstrap'): return {} config = {} for option, type_ in CFG_OPTIONS: if not cfg.has_option('ah_bootstrap', option): continue if type_ is bool: value = cfg.getboolean('ah_bootstrap', option) else: value = cfg.get('ah_bootstrap', option) config[option] = value return config @classmethod def parse_command_line(cls, argv=None): if argv is None: argv = sys.argv config = {} # For now we just pop recognized ah_bootstrap options out of the # arg list. This is imperfect; in the unlikely case that a setup.py # custom command or even custom Distribution class defines an argument # of the same name then we will break that. However there's a catch22 # here that we can't just do full argument parsing right here, because # we don't yet know *how* to parse all possible command-line arguments. if '--no-git' in argv: config['use_git'] = False argv.remove('--no-git') if '--offline' in argv: config['offline'] = True argv.remove('--offline') return config def run(self): strategies = ['local_directory', 'local_file', 'index'] dist = None # First, remove any previously imported versions of astropy_helpers; # this is necessary for nested installs where one package's installer # is installing another package via setuptools.sandbox.run_setup, as in # the case of setup_requires for key in list(sys.modules): try: if key == PACKAGE_NAME or key.startswith(PACKAGE_NAME + '.'): del sys.modules[key] except AttributeError: # Sometimes mysterious non-string things can turn up in # sys.modules continue # Check to see if the path is a submodule self.is_submodule = self._check_submodule() for strategy in strategies: method = getattr(self, 'get_{0}_dist'.format(strategy)) dist = method() if dist is not None: break else: raise _AHBootstrapSystemExit( "No source found for the {0!r} package; {0} must be " "available and importable as a prerequisite to building " "or installing this package.".format(PACKAGE_NAME)) # This is a bit hacky, but if astropy_helpers was loaded from a # directory/submodule its Distribution object gets a "precedence" of # "DEVELOP_DIST". However, in other cases it gets a precedence of # "EGG_DIST". However, when activing the distribution it will only be # placed early on sys.path if it is treated as an EGG_DIST, so always # do that dist = dist.clone(precedence=pkg_resources.EGG_DIST) # Otherwise we found a version of astropy-helpers, so we're done # Just active the found distribution on sys.path--if we did a # download this usually happens automatically but it doesn't hurt to # do it again # Note: Adding the dist to the global working set also activates it # (makes it importable on sys.path) by default. try: pkg_resources.working_set.add(dist, replace=True) except TypeError: # Some (much) older versions of setuptools do not have the # replace=True option here. These versions are old enough that all # bets may be off anyways, but it's easy enough to work around just # in case... if dist.key in pkg_resources.working_set.by_key: del pkg_resources.working_set.by_key[dist.key] pkg_resources.working_set.add(dist) @property def config(self): """ A `dict` containing the options this `_Bootstrapper` was configured with. """ return dict((optname, getattr(self, optname)) for optname, _ in CFG_OPTIONS if hasattr(self, optname)) def get_local_directory_dist(self): """ Handle importing a vendored package from a subdirectory of the source distribution. """ if not os.path.isdir(self.path): return log.info('Attempting to import astropy_helpers from {0} {1!r}'.format( 'submodule' if self.is_submodule else 'directory', self.path)) dist = self._directory_import() if dist is None: log.warn( 'The requested path {0!r} for importing {1} does not ' 'exist, or does not contain a copy of the {1} ' 'package.'.format(self.path, PACKAGE_NAME)) elif self.auto_upgrade and not self.is_submodule: # A version of astropy-helpers was found on the available path, but # check to see if a bugfix release is available on PyPI upgrade = self._do_upgrade(dist) if upgrade is not None: dist = upgrade return dist def get_local_file_dist(self): """ Handle importing from a source archive; this also uses setup_requires but points easy_install directly to the source archive. """ if not os.path.isfile(self.path): return log.info('Attempting to unpack and import astropy_helpers from ' '{0!r}'.format(self.path)) try: dist = self._do_download(find_links=[self.path]) except Exception as e: if DEBUG: raise log.warn( 'Failed to import {0} from the specified archive {1!r}: ' '{2}'.format(PACKAGE_NAME, self.path, str(e))) dist = None if dist is not None and self.auto_upgrade: # A version of astropy-helpers was found on the available path, but # check to see if a bugfix release is available on PyPI upgrade = self._do_upgrade(dist) if upgrade is not None: dist = upgrade return dist def get_index_dist(self): if not self.download: log.warn('Downloading {0!r} disabled.'.format(DIST_NAME)) return None log.warn( "Downloading {0!r}; run setup.py with the --offline option to " "force offline installation.".format(DIST_NAME)) try: dist = self._do_download() except Exception as e: if DEBUG: raise log.warn( 'Failed to download and/or install {0!r} from {1!r}:\n' '{2}'.format(DIST_NAME, self.index_url, str(e))) dist = None # No need to run auto-upgrade here since we've already presumably # gotten the most up-to-date version from the package index return dist def _directory_import(self): """ Import astropy_helpers from the given path, which will be added to sys.path. Must return True if the import succeeded, and False otherwise. """ # Return True on success, False on failure but download is allowed, and # otherwise raise SystemExit path = os.path.abspath(self.path) # Use an empty WorkingSet rather than the man # pkg_resources.working_set, since on older versions of setuptools this # will invoke a VersionConflict when trying to install an upgrade ws = pkg_resources.WorkingSet([]) ws.add_entry(path) dist = ws.by_key.get(DIST_NAME) if dist is None: # We didn't find an egg-info/dist-info in the given path, but if a # setup.py exists we can generate it setup_py = os.path.join(path, 'setup.py') if os.path.isfile(setup_py): with _silence(): run_setup(os.path.join(path, 'setup.py'), ['egg_info']) for dist in pkg_resources.find_distributions(path, True): # There should be only one... return dist return dist def _do_download(self, version='', find_links=None): if find_links: allow_hosts = '' index_url = None else: allow_hosts = None index_url = self.index_url # Annoyingly, setuptools will not handle other arguments to # Distribution (such as options) before handling setup_requires, so it # is not straightforward to programmatically augment the arguments which # are passed to easy_install class _Distribution(Distribution): def get_option_dict(self, command_name): opts = Distribution.get_option_dict(self, command_name) if command_name == 'easy_install': if find_links is not None: opts['find_links'] = ('setup script', find_links) if index_url is not None: opts['index_url'] = ('setup script', index_url) if allow_hosts is not None: opts['allow_hosts'] = ('setup script', allow_hosts) return opts if version: req = '{0}=={1}'.format(DIST_NAME, version) else: req = DIST_NAME attrs = {'setup_requires': [req]} try: if DEBUG: _Distribution(attrs=attrs) else: with _silence(): _Distribution(attrs=attrs) # If the setup_requires succeeded it will have added the new dist to # the main working_set return pkg_resources.working_set.by_key.get(DIST_NAME) except Exception as e: if DEBUG: raise msg = 'Error retrieving {0} from {1}:\n{2}' if find_links: source = find_links[0] elif index_url != INDEX_URL: source = index_url else: source = 'PyPI' raise Exception(msg.format(DIST_NAME, source, repr(e))) def _do_upgrade(self, dist): # Build up a requirement for a higher bugfix release but a lower minor # release (so API compatibility is guaranteed) next_version = _next_version(dist.parsed_version) req = pkg_resources.Requirement.parse( '{0}>{1},<{2}'.format(DIST_NAME, dist.version, next_version)) package_index = PackageIndex(index_url=self.index_url) upgrade = package_index.obtain(req) if upgrade is not None: return self._do_download(version=upgrade.version) def _check_submodule(self): """ Check if the given path is a git submodule. See the docstrings for ``_check_submodule_using_git`` and ``_check_submodule_no_git`` for further details. """ if (self.path is None or (os.path.exists(self.path) and not os.path.isdir(self.path))): return False if self.use_git: return self._check_submodule_using_git() else: return self._check_submodule_no_git() def _check_submodule_using_git(self): """ Check if the given path is a git submodule. If so, attempt to initialize and/or update the submodule if needed. This function makes calls to the ``git`` command in subprocesses. The ``_check_submodule_no_git`` option uses pure Python to check if the given path looks like a git submodule, but it cannot perform updates. """ cmd = ['git', 'submodule', 'status', '--', self.path] try: log.info('Running `{0}`; use the --no-git option to disable git ' 'commands'.format(' '.join(cmd))) returncode, stdout, stderr = run_cmd(cmd) except _CommandNotFound: # The git command simply wasn't found; this is most likely the # case on user systems that don't have git and are simply # trying to install the package from PyPI or a source # distribution. Silently ignore this case and simply don't try # to use submodules return False stderr = stderr.strip() if returncode != 0 and stderr: # Unfortunately the return code alone cannot be relied on, as # earlier versions of git returned 0 even if the requested submodule # does not exist # This is a warning that occurs in perl (from running git submodule) # which only occurs with a malformatted locale setting which can # happen sometimes on OSX. See again # https://github.com/astropy/astropy/issues/2749 perl_warning = ('perl: warning: Falling back to the standard locale ' '("C").') if not stderr.strip().endswith(perl_warning): # Some other unknown error condition occurred log.warn('git submodule command failed ' 'unexpectedly:\n{0}'.format(stderr)) return False # Output of `git submodule status` is as follows: # # 1: Status indicator: '-' for submodule is uninitialized, '+' if # submodule is initialized but is not at the commit currently indicated # in .gitmodules (and thus needs to be updated), or 'U' if the # submodule is in an unstable state (i.e. has merge conflicts) # # 2. SHA-1 hash of the current commit of the submodule (we don't really # need this information but it's useful for checking that the output is # correct) # # 3. The output of `git describe` for the submodule's current commit # hash (this includes for example what branches the commit is on) but # only if the submodule is initialized. We ignore this information for # now _git_submodule_status_re = re.compile( '^(?P[+-U ])(?P[0-9a-f]{40}) ' '(?P\S+)( .*)?$') # The stdout should only contain one line--the status of the # requested submodule m = _git_submodule_status_re.match(stdout) if m: # Yes, the path *is* a git submodule self._update_submodule(m.group('submodule'), m.group('status')) return True else: log.warn( 'Unexpected output from `git submodule status`:\n{0}\n' 'Will attempt import from {1!r} regardless.'.format( stdout, self.path)) return False def _check_submodule_no_git(self): """ Like ``_check_submodule_using_git``, but simply parses the .gitmodules file to determine if the supplied path is a git submodule, and does not exec any subprocesses. This can only determine if a path is a submodule--it does not perform updates, etc. This function may need to be updated if the format of the .gitmodules file is changed between git versions. """ gitmodules_path = os.path.abspath('.gitmodules') if not os.path.isfile(gitmodules_path): return False # This is a minimal reader for gitconfig-style files. It handles a few of # the quirks that make gitconfig files incompatible with ConfigParser-style # files, but does not support the full gitconfig syntax (just enough # needed to read a .gitmodules file). gitmodules_fileobj = io.StringIO() # Must use io.open for cross-Python-compatible behavior wrt unicode with io.open(gitmodules_path) as f: for line in f: # gitconfig files are more flexible with leading whitespace; just # go ahead and remove it line = line.lstrip() # comments can start with either # or ; if line and line[0] in (':', ';'): continue gitmodules_fileobj.write(line) gitmodules_fileobj.seek(0) cfg = RawConfigParser() try: cfg.readfp(gitmodules_fileobj) except Exception as exc: log.warn('Malformatted .gitmodules file: {0}\n' '{1} cannot be assumed to be a git submodule.'.format( exc, self.path)) return False for section in cfg.sections(): if not cfg.has_option(section, 'path'): continue submodule_path = cfg.get(section, 'path').rstrip(os.sep) if submodule_path == self.path.rstrip(os.sep): return True return False def _update_submodule(self, submodule, status): if status == ' ': # The submodule is up to date; no action necessary return elif status == '-': if self.offline: raise _AHBootstrapSystemExit( "Cannot initialize the {0} submodule in --offline mode; " "this requires being able to clone the submodule from an " "online repository.".format(submodule)) cmd = ['update', '--init'] action = 'Initializing' elif status == '+': cmd = ['update'] action = 'Updating' if self.offline: cmd.append('--no-fetch') elif status == 'U': raise _AHBoostrapSystemExit( 'Error: Submodule {0} contains unresolved merge conflicts. ' 'Please complete or abandon any changes in the submodule so that ' 'it is in a usable state, then try again.'.format(submodule)) else: log.warn('Unknown status {0!r} for git submodule {1!r}. Will ' 'attempt to use the submodule as-is, but try to ensure ' 'that the submodule is in a clean state and contains no ' 'conflicts or errors.\n{2}'.format(status, submodule, _err_help_msg)) return err_msg = None cmd = ['git', 'submodule'] + cmd + ['--', submodule] log.warn('{0} {1} submodule with: `{2}`'.format( action, submodule, ' '.join(cmd))) try: log.info('Running `{0}`; use the --no-git option to disable git ' 'commands'.format(' '.join(cmd))) returncode, stdout, stderr = run_cmd(cmd) except OSError as e: err_msg = str(e) else: if returncode != 0: err_msg = stderr if err_msg is not None: log.warn('An unexpected error occurred updating the git submodule ' '{0!r}:\n{1}\n{2}'.format(submodule, err_msg, _err_help_msg)) class _CommandNotFound(OSError): """ An exception raised when a command run with run_cmd is not found on the system. """ def run_cmd(cmd): """ Run a command in a subprocess, given as a list of command-line arguments. Returns a ``(returncode, stdout, stderr)`` tuple. """ try: p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE) # XXX: May block if either stdout or stderr fill their buffers; # however for the commands this is currently used for that is # unlikely (they should have very brief output) stdout, stderr = p.communicate() except OSError as e: if DEBUG: raise if e.errno == errno.ENOENT: msg = 'Command not found: `{0}`'.format(' '.join(cmd)) raise _CommandNotFound(msg, cmd) else: raise _AHBoostrapSystemExit( 'An unexpected error occurred when running the ' '`{0}` command:\n{1}'.format(' '.join(cmd), str(e))) # Can fail of the default locale is not configured properly. See # https://github.com/astropy/astropy/issues/2749. For the purposes under # consideration 'latin1' is an acceptable fallback. try: stdio_encoding = locale.getdefaultlocale()[1] or 'latin1' except ValueError: # Due to an OSX oddity locale.getdefaultlocale() can also crash # depending on the user's locale/language settings. See: # http://bugs.python.org/issue18378 stdio_encoding = 'latin1' # Unlikely to fail at this point but even then let's be flexible if not isinstance(stdout, _text_type): stdout = stdout.decode(stdio_encoding, 'replace') if not isinstance(stderr, _text_type): stderr = stderr.decode(stdio_encoding, 'replace') return (p.returncode, stdout, stderr) def _next_version(version): """ Given a parsed version from pkg_resources.parse_version, returns a new version string with the next minor version. Examples ======== >>> _next_version(pkg_resources.parse_version('1.2.3')) '1.3.0' """ if hasattr(version, 'base_version'): # New version parsing from setuptools >= 8.0 if version.base_version: parts = version.base_version.split('.') else: parts = [] else: parts = [] for part in version: if part.startswith('*'): break parts.append(part) parts = [int(p) for p in parts] if len(parts) < 3: parts += [0] * (3 - len(parts)) major, minor, micro = parts[:3] return '{0}.{1}.{2}'.format(major, minor + 1, 0) class _DummyFile(object): """A noop writeable object.""" errors = '' # Required for Python 3.x encoding = 'utf-8' def write(self, s): pass def flush(self): pass @contextlib.contextmanager def _silence(): """A context manager that silences sys.stdout and sys.stderr.""" old_stdout = sys.stdout old_stderr = sys.stderr sys.stdout = _DummyFile() sys.stderr = _DummyFile() exception_occurred = False try: yield except: exception_occurred = True # Go ahead and clean up so that exception handling can work normally sys.stdout = old_stdout sys.stderr = old_stderr raise if not exception_occurred: sys.stdout = old_stdout sys.stderr = old_stderr _err_help_msg = """ If the problem persists consider installing astropy_helpers manually using pip (`pip install astropy_helpers`) or by manually downloading the source archive, extracting it, and installing by running `python setup.py install` from the root of the extracted source code. """ class _AHBootstrapSystemExit(SystemExit): def __init__(self, *args): if not args: msg = 'An unknown problem occurred bootstrapping astropy_helpers.' else: msg = args[0] msg += '\n' + _err_help_msg super(_AHBootstrapSystemExit, self).__init__(msg, *args[1:]) if sys.version_info[:2] < (2, 7): # In Python 2.6 the distutils log does not log warnings, errors, etc. to # stderr so we have to wrap it to ensure consistency at least in this # module import distutils class log(object): def __getattr__(self, attr): return getattr(distutils.log, attr) def warn(self, msg, *args): self._log_to_stderr(distutils.log.WARN, msg, *args) def error(self, msg): self._log_to_stderr(distutils.log.ERROR, msg, *args) def fatal(self, msg): self._log_to_stderr(distutils.log.FATAL, msg, *args) def log(self, level, msg, *args): if level in (distutils.log.WARN, distutils.log.ERROR, distutils.log.FATAL): self._log_to_stderr(level, msg, *args) else: distutils.log.log(level, msg, *args) def _log_to_stderr(self, level, msg, *args): # This is the only truly 'public' way to get the current threshold # of the log current_threshold = distutils.log.set_threshold(distutils.log.WARN) distutils.log.set_threshold(current_threshold) if level >= current_threshold: if args: msg = msg % args sys.stderr.write('%s\n' % msg) sys.stderr.flush() log = log() BOOTSTRAPPER = _Bootstrapper.main() def use_astropy_helpers(**kwargs): """ Ensure that the `astropy_helpers` module is available and is importable. This supports automatic submodule initialization if astropy_helpers is included in a project as a git submodule, or will download it from PyPI if necessary. Parameters ---------- path : str or None, optional A filesystem path relative to the root of the project's source code that should be added to `sys.path` so that `astropy_helpers` can be imported from that path. If the path is a git submodule it will automatically be initialized and/or updated. The path may also be to a ``.tar.gz`` archive of the astropy_helpers source distribution. In this case the archive is automatically unpacked and made temporarily available on `sys.path` as a ``.egg`` archive. If `None` skip straight to downloading. download_if_needed : bool, optional If the provided filesystem path is not found an attempt will be made to download astropy_helpers from PyPI. It will then be made temporarily available on `sys.path` as a ``.egg`` archive (using the ``setup_requires`` feature of setuptools. If the ``--offline`` option is given at the command line the value of this argument is overridden to `False`. index_url : str, optional If provided, use a different URL for the Python package index than the main PyPI server. use_git : bool, optional If `False` no git commands will be used--this effectively disables support for git submodules. If the ``--no-git`` option is given at the command line the value of this argument is overridden to `False`. auto_upgrade : bool, optional By default, when installing a package from a non-development source distribution ah_boostrap will try to automatically check for patch releases to astropy-helpers on PyPI and use the patched version over any bundled versions. Setting this to `False` will disable that functionality. If the ``--offline`` option is given at the command line the value of this argument is overridden to `False`. offline : bool, optional If `False` disable all actions that require an internet connection, including downloading packages from the package index and fetching updates to any git submodule. Defaults to `True`. """ global BOOTSTRAPPER config = BOOTSTRAPPER.config config.update(**kwargs) # Create a new bootstrapper with the updated configuration and run it BOOTSTRAPPER = _Bootstrapper(**config) BOOTSTRAPPER.run() APLpy-1.1.1/astropy_helpers/appveyor.yml0000644000077000000240000000277312770325077020253 0ustar tomstaff00000000000000# AppVeyor.com is a Continuous Integration service to build and run tests under # Windows environment: global: PYTHON: "C:\\conda" MINICONDA_VERSION: "latest" CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci-helpers\\appveyor\\windows_sdk.cmd" PYTHON_ARCH: "64" # needs to be set for CMD_IN_ENV to succeed. If a mix # of 32 bit and 64 bit builds are needed, move this # to the matrix section. # babel 2.0 is known to break on Windows: # https://github.com/python-babel/babel/issues/174 CONDA_DEPENDENCIES: "numpy Cython sphinx pytest babel!=2.0" matrix: - PYTHON_VERSION: "2.7" - PYTHON_VERSION: "3.3" - PYTHON_VERSION: "3.4" - PYTHON_VERSION: "3.5" platform: -x64 install: # Set up ci-helpers - "git clone git://github.com/astropy/ci-helpers.git" - "powershell ci-helpers/appveyor/install-miniconda.ps1" - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - "activate test" # Some of the tests use git commands that require a user to be configured - git config --global user.name "A U Thor" - git config --global user.email "author@example.com" # For now we skip the installation of the graphviz package because the # graphviz.org website seems to be unresponsive intermittently, which # causes timeouts in downloads. # - cinst graphviz.portable # Not a .NET project, we build SunPy in the install step instead build: false test_script: - "%CMD_IN_ENV% py.test" APLpy-1.1.1/astropy_helpers/astropy_helpers/0000755000077000000240000000000012774747316021105 5ustar tomstaff00000000000000APLpy-1.1.1/astropy_helpers/astropy_helpers/__init__.py0000644000077000000240000000345412703324024023177 0ustar tomstaff00000000000000try: from .version import version as __version__ from .version import githash as __githash__ except ImportError: __version__ = '' __githash__ = '' # If we've made it as far as importing astropy_helpers, we don't need # ah_bootstrap in sys.modules anymore. Getting rid of it is actually necessary # if the package we're installing has a setup_requires of another package that # uses astropy_helpers (and possibly a different version at that) # See https://github.com/astropy/astropy/issues/3541 import sys if 'ah_bootstrap' in sys.modules: del sys.modules['ah_bootstrap'] # Note, this is repeated from ah_bootstrap.py, but is here too in case this # astropy-helpers was upgraded to from an older version that did not have this # check in its ah_bootstrap. # matplotlib can cause problems if it is imported from within a call of # run_setup(), because in some circumstances it will try to write to the user's # home directory, resulting in a SandboxViolation. See # https://github.com/matplotlib/matplotlib/pull/4165 # Making sure matplotlib, if it is available, is imported early in the setup # process can mitigate this (note importing matplotlib.pyplot has the same # issue) try: import matplotlib matplotlib.use('Agg') import matplotlib.pyplot except: # Ignore if this fails for *any* reason* pass import os # Ensure that all module-level code in astropy or other packages know that # we're in setup mode: if ('__main__' in sys.modules and hasattr(sys.modules['__main__'], '__file__')): filename = os.path.basename(sys.modules['__main__'].__file__) if filename.rstrip('co') == 'setup.py': if sys.version_info[0] >= 3: import builtins else: import __builtin__ as builtins builtins._ASTROPY_SETUP_ = True del filename APLpy-1.1.1/astropy_helpers/astropy_helpers/__init__.pyc0000644000077000000240000000166112703324024023340 0ustar tomstaff00000000000000  Wc@sEy$ddlmZddlmZWnek rCdZdZnXddlZdejkrlejd=ny)ddlZejdddl ZWnnXddl Z dejkrAe ejdd rAe j j ejdjZejd d kr;ejd d kr#ddlZn ddlZee_n[ndS(i(tversion(tgithashtiNt ah_bootstraptAggt__main__t__file__tcossetup.pyii(Rt __version__Rt __githash__t ImportErrortsystmodulest matplotlibtusetmatplotlib.pyplottosthasattrtpathtbasenameRtfilenametrstript version_infotbuiltinst __builtin__tTruet_ASTROPY_SETUP_(((s[/Users/tom/Dropbox/Code/development/APLpy/APLpy/astropy_helpers/astropy_helpers/__init__.pyts0         APLpy-1.1.1/astropy_helpers/astropy_helpers/__pycache__/0000755000077000000240000000000012774747316023315 5ustar tomstaff00000000000000APLpy-1.1.1/astropy_helpers/astropy_helpers/__pycache__/__init__.cpython-34.pyc0000644000077000000240000000100512536567761027474 0ustar tomstaff00000000000000 D TB@sy$ddlmZddlmZWnek rDdZdZYnXddlZdejkrmejd=ny)ddlZejdddl ZWnYnXdS))version)githashN ah_bootstrapAgg) r __version__rZ __githash__ ImportErrorsysmodules matplotlibusematplotlib.pyplotrr`/Volumes/Raptor/Dropbox/Code/development/APLpy/APLpy/astropy_helpers/astropy_helpers/__init__.pys      APLpy-1.1.1/astropy_helpers/astropy_helpers/__pycache__/__init__.cpython-35.pyc0000644000077000000240000000146312770325077027475 0ustar tomstaff00000000000000  W,@s>y$ddlmZddlmZWnek rDdZdZYnXddlZdejkrjejd=y)ddlZejdddl ZWnYnXddl Z dejkr:e ejdd r:e j j ejdjZejd d kr7ejdd kr"ddlZn ddlZd e_[dS))version)githashN ah_bootstrapAgg__main____file__cozsetup.pyT)r __version__rZ __githash__ ImportErrorsysmodules matplotlibusematplotlib.pyplotoshasattrpathbasenamer filenamerstrip version_infobuiltins __builtin___ASTROPY_SETUP_rrO/Users/tom/Dropbox/Code/APLpy/APLpy/astropy_helpers/astropy_helpers/__init__.pys0         APLpy-1.1.1/astropy_helpers/astropy_helpers/__pycache__/distutils_helpers.cpython-34.pyc0000644000077000000240000001547312536567761031521 0ustar tomstaff00000000000000 D T@sdZddlZddlZddlmZddlmZddlmZddl m Z dd Z d d Z d d Z ddZddZddZdddZddZddZdS)a^ This module contains various utilities for introspecting the distutils module and the setup process. Some of these utilities require the `astropy_helpers.setup_helpers.register_commands` function to be called first, as it will affect introspection of setuptools command-line arguments. Other utilities in this module do not have that restriction. N) ccompiler) Distribution)DistutilsError)silencecCsddlm}|ddkr/tdntitjjtjdd6tjddd6}|j j |dt ;y|j |j Wntttfk rYnXWdQX|S) z Returns a distutils Distribution object used to instrument the setup environment before calling the actual setup() function. r) _module_stateregistered_commandsNzastropy_helpers.setup_helpers.register_commands() must be called before using astropy_helpers.setup_helpers.get_dummy_distribution()r script_name script_args)Z setup_helpersr RuntimeErrorrospathbasenamesysargvcmdclassupdaterparse_config_filesparse_command_linerAttributeError SystemExit)rdistri/Volumes/Raptor/Dropbox/Code/development/APLpy/APLpy/astropy_helpers/astropy_helpers/distutils_helpers.pyget_dummy_distributions     rcCsXt}xH|D]<}|jj|}|dk r||kr||dSqWdSdS)ap Returns the value of the given distutils option. Parameters ---------- option : str The name of the option commands : list of str The list of commands on which this option is available Returns ------- val : str or None the value of the given distutils option. If the option is not set, returns None. Nr)rcommand_optionsget)optioncommandsrcmdZcmd_optsrrrget_distutils_option8s   r cCst|dddgS)a! Returns the value of the given distutils build option. Parameters ---------- option : str The name of the option Returns ------- val : str or None The value of the given distutils build option. If the option is not set, returns None. build build_ext build_clib)r )rrrrget_distutils_build_optionTsr$cCst|dgS)a# Returns the value of the given distutils install option. Parameters ---------- option : str The name of the option Returns ------- val : str or None The value of the given distutils build option. If the option is not set, returns None. install)r )rrrrget_distutils_install_optionesr&cCst|ddddgS)a7 Returns the value of the given distutils build or install option. Parameters ---------- option : str The name of the option Returns ------- val : str or None The value of the given distutils build or install option. If the option is not set, returns None. r!r"r#r%)r )rrrr%get_distutils_build_or_install_optionvsr'cCs&td}|dkr"tjS|S)a Determines the compiler that will be used to build extension modules. Returns ------- compiler : str The compiler option specified for the build, build_ext, or build_clib command; or the default compiler for the platform if none was specified. compilerN)r$rget_default_compiler)r(rrrget_compiler_options   r*Fc Cslt}|j|}t|dr:||jkr:dS|jdd}t||rytdj|||nxvt|jD]e\}}|d|krt j dj|||j|=||j kr|j j |nPqqW|jj |d|f|r$|j j |nt||dt|dsXt|g|_n|jj|dS)a Add a custom option to a setup command. Issues a warning if the option already exists on that command. Parameters ---------- command : str The name of the command as given on the command line name : str The name of the build option doc : str A short description of the option, for the `--help` message is_bool : bool, optional When `True`, the option is a boolean option and doesn't require an associated value. _astropy_helpers_optionsN-_zc{0!r} already has a {1!r} class attribute, barring {2!r} from being usable as a custom option name.rz&Overriding existing {0!r} option {1!r})rget_command_classhasattrr+replacer format enumerate user_optionslogwarnboolean_optionsremoveappendsetattrsetadd) commandnamedocis_boolrcmdclsattridxrrrradd_command_options2    rCcCstddtjD}tddtjD}|jd|jdtdddd d d g}|j|j|S) aG Returns a set of all the distutils display options in their long and short forms. These are the setup.py arguments such as --name or --version which print the project's metadata and then exit. Returns ------- opts : set The long and short form display option arguments, including the - or -- css'|]}|drd|dVqdS)rr,Nr).0orrr sz0get_distutils_display_options..css|]}d|dVqdS)z--rNr)rDrErrrrFsz-hz--helpcleanregisterZsetoptZsaveoptsegg_infoalias)r:rdisplay_optionsr;union)Zshort_display_optsZlong_display_optsZdisplay_commandsrrrget_distutils_display_optionss    rMcCs/t}tttjddj|S)zm Returns True if sys.argv contains any of the distutils display options such as --version or --name. rN)rMboolr:rr intersection)rKrrris_distutils_display_options rP)__doc__r r distutilsrdistutils.distrdistutils.errorsrutilsrrr r$r&r'r*rCrMrPrrrr s   !     A APLpy-1.1.1/astropy_helpers/astropy_helpers/__pycache__/distutils_helpers.cpython-35.pyc0000644000077000000240000001544612654136413031505 0ustar tomstaff00000000000000 D T@sdZddlZddlZddlmZddlmZddlmZddl m Z dd Z d d Z d d Z ddZddZddZdddZddZddZdS)a^ This module contains various utilities for introspecting the distutils module and the setup process. Some of these utilities require the `astropy_helpers.setup_helpers.register_commands` function to be called first, as it will affect introspection of setuptools command-line arguments. Other utilities in this module do not have that restriction. N) ccompiler) Distribution)DistutilsError)silencecCsddlm}|ddkr,tdtdtjjtjddtjddi}|j j |dt ;y|j |j Wntttfk rYnXWdQRX|S) z Returns a distutils Distribution object used to instrument the setup environment before calling the actual setup() function. r) _module_stateregistered_commandsNzastropy_helpers.setup_helpers.register_commands() must be called before using astropy_helpers.setup_helpers.get_dummy_distribution() script_namer script_args)Z setup_helpersr RuntimeErrorrospathbasenamesysargvcmdclassupdaterparse_config_filesparse_command_linerAttributeError SystemExit)rdistrd/Users/tom/Dropbox/Code/development/APLpy/APLpy/astropy_helpers/astropy_helpers/distutils_helpers.pyget_dummy_distributions    rcCsXt}xH|D]<}|jj|}|dk r||kr||dSqWdSdS)ap Returns the value of the given distutils option. Parameters ---------- option : str The name of the option commands : list of str The list of commands on which this option is available Returns ------- val : str or None the value of the given distutils option. If the option is not set, returns None. Nr)rcommand_optionsget)optioncommandsrcmdZcmd_optsrrrget_distutils_option8s   r cCst|dddgS)a! Returns the value of the given distutils build option. Parameters ---------- option : str The name of the option Returns ------- val : str or None The value of the given distutils build option. If the option is not set, returns None. build build_ext build_clib)r )rrrrget_distutils_build_optionTsr$cCst|dgS)a# Returns the value of the given distutils install option. Parameters ---------- option : str The name of the option Returns ------- val : str or None The value of the given distutils build option. If the option is not set, returns None. install)r )rrrrget_distutils_install_optionesr&cCst|ddddgS)a7 Returns the value of the given distutils build or install option. Parameters ---------- option : str The name of the option Returns ------- val : str or None The value of the given distutils build or install option. If the option is not set, returns None. r!r"r#r%)r )rrrr%get_distutils_build_or_install_optionvsr'cCs&td}|dkr"tjS|S)a Determines the compiler that will be used to build extension modules. Returns ------- compiler : str The compiler option specified for the build, build_ext, or build_clib command; or the default compiler for the platform if none was specified. compilerN)r$rget_default_compiler)r(rrrget_compiler_options   r*Fc Cs`t}|j|}t|dr:||jkr:dS|jdd}t||rvtdj|||xpt|jD]_\}}|d|krt j dj|||j|=||j kr|j j |PqW|jj |d|f|r|j j |t||dt|dsLt|g|_n|jj|dS)a Add a custom option to a setup command. Issues a warning if the option already exists on that command. Parameters ---------- command : str The name of the command as given on the command line name : str The name of the build option doc : str A short description of the option, for the `--help` message is_bool : bool, optional When `True`, the option is a boolean option and doesn't require an associated value. _astropy_helpers_optionsN-_zc{0!r} already has a {1!r} class attribute, barring {2!r} from being usable as a custom option name.rz&Overriding existing {0!r} option {1!r})rget_command_classhasattrr+replacer format enumerate user_optionslogwarnboolean_optionsremoveappendsetattrsetadd) commandnamedocis_boolrcmdclsattridxrrrradd_command_options2    rCcCstddtjD}tddtjD}|jd|jdtdddd d d g}|j|j|S) aG Returns a set of all the distutils display options in their long and short forms. These are the setup.py arguments such as --name or --version which print the project's metadata and then exit. Returns ------- opts : set The long and short form display option arguments, including the - or -- css'|]}|drd|dVqdS)rr,Nr).0orrr sz0get_distutils_display_options..css|]}d|dVqdS)z--rNr)rDrErrrrFsz-hz--helpcleanregisterZsetoptZsaveoptsegg_infoalias)r:rdisplay_optionsr;union)Zshort_display_optsZlong_display_optsZdisplay_commandsrrrget_distutils_display_optionss    rMcCs/t}tttjddj|S)zm Returns True if sys.argv contains any of the distutils display options such as --version or --name. rN)rMboolr:rr intersection)rKrrris_distutils_display_options rP)__doc__r r distutilsrdistutils.distrdistutils.errorsrutilsrrr r$r&r'r*rCrMrPrrrr s   !     A APLpy-1.1.1/astropy_helpers/astropy_helpers/__pycache__/git_helpers.cpython-34.pyc0000644000077000000240000001105712476564314030244 0ustar tomstaff00000000000000 jT@sydZddlZddlZddlZddlZddZdddZdddd d Zdd d ZdS) zP Utilities for retrieving revision information from a project's git repository. NcCspytjdpd}Wntk r4d}YnXy|j|}Wn!tk rk|jd}YnX|S)Nzutf-8latin1)localegetdefaultlocale ValueErrordecodeUnicodeDecodeError)streamstdio_encodingtextr ^/Users/tom/Dropbox/Code/development/APLpy/APLpy/astropy_helpers/astropy_helpers/git_helpers.py _decode_stdios   rcCsytddddd|}Wntk r7|SYnX|sB|Sd|kr|jddd }tddddd|}|d|S|Sd S) z Updates the git revision string if and only if the path is being imported directly from a git working copy. This ensures that the revision number in the version string is accurate. shaT show_warningFpathdevz.devrrN)get_git_devstrOSErrorsplit)versionrZdevstrZ version_baser r r update_git_devstr"s    rFTcs@dkr1tjtdds1dSntjjsdtjjtjjn|ryddg}ndddg}fd d }||\}}}| r|d krdd d dg}||\}}}|dkr t|jdSdSn*|r,t |ddSt |j SdS)a Determines the number of revisions in this repository. Parameters ---------- sha : bool If True, the full SHA1 hash will be returned. Otherwise, the total count of commits in the repository will be used as a "revision number". show_warning : bool If True, issue a warning if git returns an error code, otherwise errors pass silently. path : str or None If a string, specifies the directory to look in to find the git repository. If `None`, the current working directory is used, and must be the root of the git repository. If given a filename it uses the directory containing that file. Returns ------- devversion : str Either a string with the revision number (if `sha` is False), the SHA1 hash of the current commit (if `sha` is True), or an empty string if git version info could not be identified. Nlevelsrz rev-parseHEADzrev-listz--countcsmyMtjdg|ddtjdtjdtj}|j\}}WnItk r}z)rtjdt|ndSWYdd}~XnX|jdkrrtjd j n|jddfS|jd krr tjd j |d n|j||fS|jd kr]rMtjd j t |n|j||fS|j||fS)NgitcwdstdoutstderrstdinzError running git: z>No git repository present at {0!r}! Using default dev version.zQYour git looks old (does it support {0}?); consider upgrading to v1.7.2 or later.rz0Git failed while determining revision count: {0})Nr r ) subprocessPopenPIPE communicaterwarningswarnstr returncodeformatr)cmdprre)rrr r run_gitis4   zget_git_devstr..run_gitr"z--abbrev-commitz --abbrev=0s () osgetcwd_get_repo_pathrisdirabspathdirnamer)countrstrip)rrrr,r/r*rrr )rrr r=s(  ! rcCstjj|r3tjjtjj|}n+tjj|rZtjj|}ndSd}xy|dks||krtjjtjj|dr|S|d7}|tjj|krPntjj|}qgWdS)ao Given a file or directory name, determine the root of the git repository this path is under. If given, this won't look any higher than ``levels`` (that is, if ``levels=0`` then the given path must be the root of the git repository and is returned if so. Returns `None` if the given path could not be determined to belong to a git repo. Nrz.gitr)r1risfiler5r6r4existsjoin)pathnamerZ current_dirZ current_levelr r r r3s !! r3) __doc__rr1r#r'rrrr3r r r r s     aAPLpy-1.1.1/astropy_helpers/astropy_helpers/__pycache__/git_helpers.cpython-35.pyc0000644000077000000240000001101612770325077030236 0ustar tomstaff00000000000000 ?W@sydZddlZddlZddlZddlZddZdddZdddd d Zdd d ZdS) zP Utilities for retrieving revision information from a project's git repository. NcCspytjdpd}Wntk r4d}YnXy|j|}Wn!tk rk|jd}YnX|S)Nzutf-8latin1)localegetdefaultlocale ValueErrordecodeUnicodeDecodeError)streamstdio_encodingtextr R/Users/tom/Dropbox/Code/APLpy/APLpy/astropy_helpers/astropy_helpers/git_helpers.py _decode_stdios   rcCsytddddd|}Wntk r7|SYnX|sB|Sd|kr|jddd }tddddd|}|d|S|Sd S) z Updates the git revision string if and only if the path is being imported directly from a git working copy. This ensures that the revision number in the version string is accurate. shaT show_warningFpathdevz.devrrN)get_git_devstrOSErrorsplit)versionrZdevstrZ version_baser r r update_git_devstr"s    rFTcs:dkr.tjtdds.dStjjs^tjjtjj|rsddg}ndddg}fd d }||\}}}| r |d kr dd d dg}||\}}}|dkrt|jdSdSn*|r&t |ddSt |j SdS)a Determines the number of revisions in this repository. Parameters ---------- sha : bool If True, the full SHA1 hash will be returned. Otherwise, the total count of commits in the repository will be used as a "revision number". show_warning : bool If True, issue a warning if git returns an error code, otherwise errors pass silently. path : str or None If a string, specifies the directory to look in to find the git repository. If `None`, the current working directory is used, and must be the root of the git repository. If given a filename it uses the directory containing that file. Returns ------- devversion : str Either a string with the revision number (if `sha` is False), the SHA1 hash of the current commit (if `sha` is True), or an empty string if git version info could not be identified. Nlevelsrz rev-parseHEADzrev-listz--countcsayMtjdg|ddtjdtjdtj}|j\}}WnFtk r}z&rtjdt|dSWYdd}~XnX|jdkrrtjd j |jddfS|jd krrtjd j |d |j||fS|jd krQrAtjd j t ||j||fS|j||fS)NgitcwdstdoutstderrstdinzError running git: z>No git repository present at {0!r}! Using default dev version.zQYour git looks old (does it support {0}?); consider upgrading to v1.7.2 or later.rz0Git failed while determining revision count: {0})Nr r ) subprocessPopenPIPE communicaterwarningswarnstr returncodeformatr)cmdprre)rrr r run_gitis4       zget_git_devstr..run_gitr"z--abbrev-commitz --abbrev=0s () osgetcwd_get_repo_pathrisdirabspathdirnamer)countrstrip)rrrr,r/r*rrr )rrr r=s(   rcCstjj|r3tjjtjj|}n+tjj|rZtjj|}ndSd}xv|dks||krtjjtjj|dr|S|d7}|tjj|krPtjj|}qgWdS)ao Given a file or directory name, determine the root of the git repository this path is under. If given, this won't look any higher than ``levels`` (that is, if ``levels=0`` then the given path must be the root of the git repository and is returned if so. Returns `None` if the given path could not be determined to belong to a git repo. Nrz.gitr)r1risfiler5r6r4existsjoin)pathnamer current_dirZ current_levelr r r r3s !! r3) __doc__rr1r#r'rrrr3r r r r s     aAPLpy-1.1.1/astropy_helpers/astropy_helpers/__pycache__/setup_helpers.cpython-34.pyc0000644000077000000240000005635512536567761030641 0ustar tomstaff00000000000000 D Ts@sdZddlmZmZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z ddl Z ddl mZmZmZddlmZddlmZmZddlmZddlmZdd lmZdd lmZd d lTd d l m!Z!d dl"m#Z#d dl$m%Z%m&Z&m'Z'm(Z(m)Z)d dl*m+Z+d dl,m-Z-d dl.m/Z/d dl0m1Z1d dl2m3Z3d dl$m4Z4m5Z5d dl*m6Z6idd6dd6dd6dd6dd6Z7yddl8Z8de7ddnWYddZ<[<Xn#e9k rYne?k rYnXe j@dd kZAge_Bd!d"ZCd#d$ZDd%d&ZEd'd(d)ZFd'd*d+ZGd,d-ZHd.d/ZId0d1ZJd2d3ZKd'fd4d5ZLd6d7ZMd8d9ZNeOdd:d;ZPGd<d=d=ejQZRd>d?d@ZSdAdBZTdCdDZUe(ed'fddEdFZdGdHZVGdIdJdJeZWdS)Kzx This module contains a number of utilities for use during setup/build/packaging that are useful to astropy as a whole. )absolute_importprint_functionN)log ccompiler sysconfig) Distribution)DistutilsOptionErrorDistutilsModuleError) Extension)Command)sdist) find_packages)*)get_pkg_version_module) AstropyTest)silencewalk_skip_hidden import_file extends_doc resolve_name)generate_build_ext_command)AstropyBuildPy)AstropyInstall)AstropyInstallLib)AstropyRegister)get_numpy_include_pathwrite_if_different)should_build_with_cythonFadjusted_compilerregistered_commandsZ have_cython have_sphinx package_cacheTzunknown localeaPossible misconfiguration of one of the environment variables LC_ALL, LC_CTYPES, LANG, or LANGUAGE. For an example of how to configure your system's language environment on OSX see http://blog.remibergsma.com/2012/07/10/setting-locales-correctly-on-mac-osx-terminal-application/cCsdg}tdrdSdtd where is the command you ran. versionpkgfixedunixa The C compiler used to compile Python {compiler:s}, and which is normally used to compile C extensions, is not available. You can explicitly specify which compiler to use by setting the CC environment variable, for example: CC=gcc python setup.py or if you are using MacOS X, you can try: CC=clang python setup.py )r$zclang) _module_stateosenvironget_compiler_versionOSErrortextwrapdedentformatrwarnsysexitrematchget_distutils_build_optionrget_default_compilerrget_config_var)packageZcompiler_mappingZ c_compilerr(msgbrokenr* compiler_typer@e/Volumes/Raptor/Dropbox/Code/development/APLpy/APLpy/astropy_helpers/astropy_helpers/setup_helpers.pyadjust_compilerYsJ                rBc Csotjtj|dgdtj}|jdj}y|jd}Wntk rjdSYnX|S)Nz --versionstdoutrunknown) subprocessPopenshlexsplitPIPE communicatestrip IndexError)r'processoutputr(r@r@rAr/s"  r/c syt|ddgd}Wnttfk r=d}YnXttfddddgDrttd}n t|}|dk r||krjd}d |_n|S) z Determines if the build is in debug mode. Returns ------- debug : bool True if the current build was started with the debug option, False otherwise. fromlistdebugrNc3s|]}|jkVqdS)N)commands).0cmd)distr@rA sz#get_debug_option..build build_extT) r ImportErrorAttributeErrorget_dummy_distributionanyboolr9get_command_classZ force_rebuild) packagenameZ current_debugrPZ build_ext_cmdr@)rTrAget_debug_options    %  r_.c Cstddk rtdStdr5ddlm}nt}it|d6td6t||d6td6td 6t d 6t d 6|d 6td<}x#|j D]\}}||_ qWx+dgD] }t d|t d |qWt|d||S)Nr r!r)AstropyBuildSphinxtestr rWZbuild_pyinstallZ install_libregisterZ build_sphinxuse-system-libraries&Use system libraries whenever possibleTrVsrcdir)rerfT)r,Zcommands.build_sphinxraFakeBuildSphinxgenerate_test_commandDistutilsSdistrrrrritems__name__add_command_optionadd_command_hooks) r<r(releasergrar nameclsoptionr@r@rAregister_commandss.     rscCsItjd}dd}tt|}t}tjt}xt||D]}xt |j D]\}} |j |} | sqnn| j d} | j d} |j | } | || krg|| | _hook`` and ``post__hook`` where ```` is the name of a ``setup.py`` command (e.g. build_ext). If either hook is present this adds a wrapped version of that command to the passed in ``commands`` `dict`. ``commands`` may be pre-populated with other custom distutils command classes that should be wrapped if there are hooks for them (e.g. `AstropyBuildPy`). z^(pre|post)_(.+)_hook$cSs!t|dr|jS|jSdS)N command_name)hasattrrtrl)Zcmdclsr@r@rAget_command_name?sz+add_command_hooks..get_command_namerN)r7compilefilter_packagesr rZ collections defaultdictdictiter_setup_packagesvarsrkr8groupr]appendrlgenerate_hooked_command)rQrgZhook_rervpackagesrThookssetuppkgrpobjr8Z hook_typecmd_namecmd_clsZ cmd_hooksr@r@rArn.s&   &rncCs\|jdd}t||tfi|d6td6|jdgd6|jdgd6S) z Returns a generated subclass of ``cmd_cls`` that runs the pre- and post-command hooks for that command before and after the ``cmd_cls.run`` method. cSs(|jd|||jddS)N pre_hooks post_hooks)run_command_hooks)selfZorig_runr@r@rArunfs  z$generate_hooked_command..runrrprerpostr)rtypeobjectrget)rrrrr@r@rAr_s rcCsat||d}|sdSx>|D]6\}}t|tryt|}Wqtk r}ztdj|tWYdd}~XqXn|}t|st d|nt j dj|j d||j y||Wq#tk rX}zGt jdj|j|j t jtjtjdWYdd}~Xq#Xq#WdS)zRun hooks registered for that command and phase. *cmd_obj* is a finalized command object; *hook_kind* is either 'pre_hook' or 'post_hook'. Nzcannot find hook {0}: {1}zhook {0!r} is not callablez$running {0} from {1} for {2} commandsz-{0} command hook {1} raised an exception: %s r)getattr isinstancestrrrXr r3errcallablerrinforstriprv Exceptionerrorrl traceback format_excr5r6)cmd_objZ hook_kindrmodnamehookZhook_objexcr@r@rArqs,+   rcCs$t|jdtfi|d6S)z Creates a custom 'test' command for the given package which sets the command's ``package_name`` class attribute to the name of the package being tested. ZTest package_name)rtitler)rr@r@rArisricCs]t|}|j|d|j|dtt||d}|j|ddS)z This function is deprecated and maintained for backward compatibility with affiliated packages. Affiliated packages should update their setup.py to use `get_package_info` instead. ext_modules package_datar package_dirN)get_package_infoextendupdatelistset)rg extensionsr packagenamesZ package_dirsrr@r@rAupdate_package_filess  rcCs-g}g}i}i}g}tt|d|}xt||D]}t|dr|j}x|D]} td| qnWnt|dr|j} x| D]} t| qWnt|dr|j} nd} | sF|j t j j |j qFqFWxat||D]P}t|drI|j|jnt|dr|j|jqqW|jt|||d gx?ttt|D]%\} }|jd kr|| =qqWtd krx!|D]}|jj d qWni|d 6|d6|d6|d6|d6S)a Collates all of the information for building all subpackages subpackages and returns a dictionary of keyword arguments that can be passed directly to `distutils.setup`. The purpose of this function is to allow subpackages to update the arguments to the package's ``setup()`` function in its setup.py script, rather than having to specify all extensions/package data directly in the ``setup.py``. See Astropy's own ``setup.py`` for example usage and the Astropy development docs for more details. This function obtains that information by iterating through all packages in ``srcdir`` and locating a ``setup_package.py`` module. This module can contain the following functions: ``get_extensions()``, ``get_package_data()``, ``get_build_options()``, ``get_external_libraries()``, and ``requires_2to3()``. Each of those functions take no arguments. - ``get_extensions`` returns a list of `distutils.extension.Extension` objects. - ``get_package_data()`` returns a dict formatted as required by the ``package_data`` argument to ``setup()``. - ``get_build_options()`` returns a list of tuples describing the extra build options to add. - ``get_external_libraries()`` returns a list of libraries that can optionally be built using external dependencies. - ``get_entry_points()`` returns a dict formatted as required by the ``entry_points`` argument to ``setup()``. - ``requires_2to3()`` should return `True` when the source code requires `2to3` processing to run on Python 3.x. If ``requires_2to3()`` is missing, it is assumed to return `True`. excludeget_build_optionsrVget_external_libraries requires_2to3Tget_extensionsget_package_datanumpyZ skip_cythonmsvcz /MANIFESTrrrr skip_2to3)ryr r}rurrmradd_external_libraryrrr-pathdirname__file__rrrrget_cython_extensionsreversedr enumeraterpZget_compiler_optionextra_link_args)rgrrrrrrroptionsrr librarieslibraryriextr@r@rArsP+     % rccsx|D]{}|jd}tjj||}tjjtjj|d}tjj|rt|d|d}|VqqWdS)a6 A generator that finds and imports all of the ``setup_package.py`` modules in the source packages. Returns ------- modgen : generator A generator that yields (modname, mod), where `mod` is the module and `modname` is the module name for the ``setup_package.py`` modules. r`zsetup_package.pyrpz.setup_packageN)rHr-rjoinrelpathisfiler)rgrr^ package_parts package_pathZ setup_packagemoduler@r@rAr}s    r}ccsxt|D]~\}}}xk|D]c}|jdr#tjjtjj||}dj||ddg}||fVq#q#WPq WdS)a A generator that yields Cython source files (ending in '.pyx') in the source packages. Returns ------- pyxgen : generator A generator that yields (extmod, fullfn) where `extmod` is the full name of the module that the .pyx file would live in based on the source directory structure, and `fullfn` is the path to the .pyx file. z.pyxr`N)rendswithr-rrr)rrdirpathdirnames filenamesfnZfullfnextmodr@r@rAiter_pyx_files5s  !rcCsg}g}xc|D][}xR|jD]G}|jdr#tjjtjj|d}|j|q#q#WqWx|D]} | jd} tjj|| } xmt | | D]\\} } tjjtjj| d}||kr|jt | | gd|qqWqyW|S)a Looks for Cython files and generates Extensions if needed. Parameters ---------- srcdir : str Path to the root of the source directory to search. prevextensions : list of `~distutils.core.Extension` objects The extensions that are already defined. Any .pyx files already here will be ignored. extincludedirs : list of str or None Directories to include as the `include_dirs` argument to the generated `~distutils.core.Extension` objects. Returns ------- exts : list of `~distutils.core.Extension` objects The new extensions that are needed to compile all .pyx files (does not include any already in `prevextensions`). .pyx.crr` include_dirs)rr) sourcesrr-rrealpathsplitextrrHrrr )rgrZprevextensionsZextincludedirsZprevsourcepathsrrrZ sourcepathrrrrZpyxfnr@r@rArMs  " " rcs4eZdZdZfddZddZS)DistutilsExtensionArgsz A special dictionary whose default values are the empty list. This is useful for building up a set of arguments for `distutils.Extension` without worrying whether the entry is already present. cs,dd}tt|j|||dS)NcSsgS)Nr@r@r@r@rAdefault_factorysz8DistutilsExtensionArgs.__init__..default_factory)superr__init__)rargskwargsr) __class__r@rArs zDistutilsExtensionArgs.__init__cCs2x+|jD]\}}||j|q WdS)N)rkr)rotherkeyvalr@r@rArszDistutilsExtensionArgs.update)rl __module__ __qualname____doc__rrr@r@)rrArs rz pkg-configc Csidd6dd6dd6dd6d d 6}d j|d j|f}t}y8tj|d ddtj}|jdj}Wntjk r}zkdj|dj|j dj|j dj|j g} t j dj| |dj|WYdd}~XnX|j dkrpdjdj|dg} t j dj| |dj|nx|jD]} | ddjd} | ddjtj} | |kr| dkrt| jdd} n||| j| q}|dj| q}W|S)a Uses pkg-config to update a set of distutils Extension arguments to include the flags necessary to link against the given packages. If the pkg-config lookup fails, default_libraries is applied to libraries. Parameters ---------- packages : list of str A list of pkg-config packages to look up. default_libraries : list of str A list of library names to use if the pkg-config lookup fails. Returns ------- config : dict A dictionary containing keyword arguments to `distutils.Extension`. These entries include: - ``include_dirs``: A list of include directories - ``library_dirs``: A list of library directories - ``libraries``: A list of libraries - ``define_macros``: A list of macro defines - ``undef_macros``: A list of macros to undefine - ``extra_compile_args``: A list of extra arguments to pass to the compiler rz-I library_dirsz-Lrz-l define_macrosz-D undef_macrosz-Uz{0} --libs --cflags {1} shellTrCrz4{0} failed. This may cause the build to fail below.z command: {0}z returncode: {0}z output: {0} Nz.pkg-config could not lookup up package(s) {0}.z, z'This may cause the build to fail below.rwascii=rextra_compile_args)r3rrrErFrIrJrKCalledProcessErrorrS returncoderNrr4rrHdecoder5getfilesystemencodingtupler) rZdefault_libraries executableZflag_mapcommandresultpiperNelinestokenargvaluer@r@rA pkg_configs:  $   rcCsGx@dddgD]/}t|td|dj|ddqWdS) z Add a build option for selecting the internal or system copy of a library. Parameters ---------- library : str The name of the library. If the library is `foo`, the build option will be called `--use-system-foo`. rVrWrcz use-system-zUse the system {0} libraryZis_boolTN)rmrr3)rrr@r@rArs rcCstdj|ptdS)a Returns `True` if the build configuration indicates that the given library should use the system copy of the library rather than the internal one. For the given library `foo`, this will be `True` if `--use-system-foo` or `--use-system-libraries` was provided at the commandline or in `setup.cfg`. Parameters ---------- library : str The name of the library Returns ------- use_system : bool `True` if the build should use the system copy of the library. zuse_system_{0}Zuse_system_libraries)Z%get_distutils_build_or_install_optionr3)rr@r@rAuse_system_librarysrcCsB| rtddk rtdStd|d|}|td<|S)z This version of ``find_packages`` caches previous results to speed up subsequent calls. Use ``invalide_cache=True`` to ignore cached results from previous ``find_packages`` calls, and repeat the package search. r"Nwherer)r,_find_packages)rrZinvalidate_cacherr@r@rAr s  r cs,trdndfdd|DS)zw Removes some packages from the package list that shouldn't be installed on the current version of Python. Z_py2Z_py3cs%g|]}|js|qSr@)r)rRx)rr@rA (s z#filter_packages..)PY3)rr@)rrArys ryc @s}eZdZdZdd d!d"d#d$d%d&d'd(d)g Zejd*ejd+ejd,ejd-ddZd S).rhzz A dummy build_sphinx command that is called if Sphinx is not installed and displays a relevant error message fresh-envE all-filesa source-dir=r build-dir=N config-dir=cbuilder=bproject=version=release=today= link-indexrwarnings-returncodew clean-docslno-intersphinxnopen-docs-in-browseroc Cs9ytdWn"tjdtjdYnXdS)Nz)Sphinx must be installed for build_sphinxz1error : Sphinx must be installed for build_sphinxr) RuntimeErrorrrr5r6)rr@r@rAinitialize_optionsFs  z"FakeBuildSphinx.initialize_options)rrr)rr r)r rr)r Nr)r r r)rrr)rNr)rNr)rNr)rNr)rrr)rrr)rrr)rrr)rrr)rlrrr user_optionsrrr@r@r@rArh+s"      rh)Xr __future__rrrzr-r7rGshutilrEr5r1r distutilsrrrdistutils.distrdistutils.errorsrr distutils.corer r Zdistutils.command.sdistr rj setuptoolsr rZdistutils_helpersZversion_helpersrZ test_helpersrutilsrrrrrZcommands.build_extrZcommands.build_pyrZcommands.installrZcommands.install_librZcommands.registerrrrrr,ZCythonrXZsphinx ValueErrorrrr4 SyntaxError version_inforrrBr/r_rsrnrrrirrr}rrrr{rrrrryrhr@r@r@rAs          (        p  71  % m  1O    APLpy-1.1.1/astropy_helpers/astropy_helpers/__pycache__/setup_helpers.cpython-35.pyc0000644000077000000240000005200112770325077030612 0ustar tomstaff00000000000000 ?We@sdZddlmZmZddlZddlZddlZddlZddlZddl Z ddl Z ddl m Z ddl mZddlmZmZddlmZddlmZdd lmZdd lmZd d lTd d lmZd dlmZm Z m!Z!m"Z"m#Z#d dl$m%Z%d dl&m'Z'd dl(m)Z)d dl*m+Z+d dl,m-Z-d dl.m/Z/d dlm0Z0m1Z1d dl$m2Z2m3Z3ddddddiZ4yddl5Z5de4dYne;k rOYnXej<ddkZ=ge_>dd Z?d!d"Z@d#d$d%ZAd#d&d'ZBd(d)ZCd*d+ZDd,d-ZEd.d/ZFd#fd0d1ZGd2d3ZHd4d5ZIeJdd6d7ZKGd8d9d9ejLZMd:d;d<ZNd=d>ZOd?d@ZPe!ed#fddAdBZdCdDZQGdEdFdFeZRdS)Gzx This module contains a number of utilities for use during setup/build/packaging that are useful to astropy as a whole. )absolute_importprint_functionN)log) Distribution)DistutilsOptionErrorDistutilsModuleError) Extension)Command)sdist) find_packages)*)get_pkg_version_module)walk_skip_hidden import_file extends_doc resolve_nameAstropyDeprecationWarning)generate_build_ext_command)AstropyBuildPy)AstropyInstall)AstropyInstallLib)AstropyRegister) AstropyTest)get_numpy_include_pathwrite_if_different)should_build_with_cythonget_compiler_versionregistered_commands have_sphinxF package_cacheTzunknown localeaPossible misconfiguration of one of the environment variables LC_ALL, LC_CTYPES, LANG, or LANGUAGE. For an example of how to configure your system's language environment on OSX see http://blog.remibergsma.com/2012/07/10/setting-locales-correctly-on-mac-osx-terminal-application/cCstjdtdS)a This function detects broken compilers and switches to another. If the environment variable CC is explicitly set, or a compiler is specified on the commandline, no override is performed -- the purpose here is to only override a default compiler. The specific compilers with problems are: * The default compiler in XCode-4.2, llvm-gcc-4.2, segfaults when compiling wcslib. The set of broken compilers can be updated by changing the compiler_mapping variable. It is a list of 2-tuples where the first in the pair is a regular expression matching the version of the broken compiler, and the second is the compiler to change to. zDirect use of the adjust_compiler function in setup.py is deprecated and can be removed from your setup.py. This functionality is now incorporated directly into the build_ext command.N)warningswarnr)packager%T/Users/tom/Dropbox/Code/APLpy/APLpy/astropy_helpers/astropy_helpers/setup_helpers.pyadjust_compilerMsr'c syt|ddgd}Wnttfk r=d}YnXttfddddgDrttd}n t|}|dk r||krjd}d |_|S) z Determines if the build is in debug mode. Returns ------- debug : bool True if the current build was started with the debug option, False otherwise. fromlistdebugrNc3s|]}|jkVqdS)N)commands).0cmd)distr%r& {sz#get_debug_option..build build_extT) r ImportErrorAttributeErrorget_dummy_distributionanyboolZget_distutils_build_optionget_command_classZ force_rebuild) packagenameZ current_debugr)Z build_ext_cmdr%)r-r&get_debug_optiongs    %  r8.c Cstddk rtdStdr;ddlm}m}n t}}dt|dtdt||dtd t d t d t d |d |i td<}x#|j D]\}}||_ qWx+dgD] } td| td | qWt|d||S)Nrrr )AstropyBuildSphinxAstropyBuildDocstestr r0Zbuild_pyinstallZ install_libregisterZ build_sphinxZ build_docsuse-system-libraries&Use system libraries whenever possibleTr/srcdir)r?r@T) _module_stateZcommands.build_sphinxr:r;FakeBuildSphinxgenerate_test_commandDistutilsSdistrrrrritems__name__add_command_optionadd_command_hooks) r$versionreleaserAr:r;rnameclsoptionr%r%r&register_commandss.      rOcCs4tjd}dd}tt|}t}tjt}xt||D]}xt |j D]~\}} |j |} | sqn| j d} | j d} | || krg|| | <|| | j |j| fqnWqUWx9|j D]+\} } t| |j| | || _hook`` and ``post__hook`` where ```` is the name of a ``setup.py`` command (e.g. build_ext). If either hook is present this adds a wrapped version of that command to the passed in ``commands`` `dict`. ``commands`` may be pre-populated with other custom distutils command classes that should be wrapped if there are hooks for them (e.g. `AstropyBuildPy`). z^(pre|post)_(.+)_hook$cSs!t|dr|jS|jSdS)N command_name)hasattrrPrG)Zcmdclsr%r%r&get_command_namesz+add_command_hooks..get_command_namer N)recompilefilter_packagesr r3 collections defaultdictdictiter_setup_packagesvarsrFmatchgroupappendrGgenerate_hooked_commandr6)r*rAZhook_rerRpackagesr-hookssetuppkgrLobjr\Z hook_typecmd_nameZ cmd_hooksr%r%r&rIs$   &rIc CsX|jdd}t||tfd|dtd|jdgd|jdgiS) z Returns a generated subclass of ``cmd_cls`` that runs the pre- and post-command hooks for that command before and after the ``cmd_cls.run`` method. cSs(|jd|||jddS)N pre_hooks post_hooks)run_command_hooks)selfZorig_runr%r%r&runs  z$generate_hooked_command..runrirgreprerfpost)ritypeobjectrgget)rdZcmd_clsrarir%r%r&r_s  r_cCsKt||d}|sdSx(|D] \}}t|tryt|}Wqtk r}ztdj||WYdd}~XqXn|}t|std|t j dj|j d||j y||Wq#t k rBt jdj|j|j t jtjtjdYq#Xq#WdS)zRun hooks registered for that command and phase. *cmd_obj* is a finalized command object; *hook_kind* is either 'pre_hook' or 'post_hook'. Nzcannot find hook {0}: {1}zhook {0!r} is not callablez$running {0} from {1} for {2} commandsz-{0} command hook {1} raised an exception: %s r )getattr isinstancestrrr1rformatcallablerrinforstriprR ExceptionerrorrG traceback format_excsysexit)cmd_objZ hook_kindramodnamehookZhook_objexcr%r%r&rgs,+    rgcCs#t|jdtfd|iS)z Creates a custom 'test' command for the given package which sets the command's ``package_name`` class attribute to the name of the package being tested. ZTest package_name)rltitler)rr%r%r&rD%srDcCs]t|}|j|d|j|dtt||d}|j|ddS)z This function is deprecated and maintained for backward compatibility with affiliated packages. Affiliated packages should update their setup.py to use `get_package_info` instead. ext_modules package_datar` package_dirN)get_package_infoextendupdatelistset)rA extensionsr packagenamesZ package_dirsrur%r%r&update_package_files0s  rc Csg}g}i}i}g}tt|d|}xt||D]}t|dr|j}x|D]} td| qnWt|dr|j} x| D]} t| qWt|dr|j} nd} | sF|j t j j |j qFWx[t||D]J}t|dr=|j|jt|dr|j|jqW|jt|||d gx<ttt|D]"\} }|jd kr|| =qWtd krx|D]}|jj d qWd |d|d|d|d|iS)a Collates all of the information for building all subpackages subpackages and returns a dictionary of keyword arguments that can be passed directly to `distutils.setup`. The purpose of this function is to allow subpackages to update the arguments to the package's ``setup()`` function in its setup.py script, rather than having to specify all extensions/package data directly in the ``setup.py``. See Astropy's own ``setup.py`` for example usage and the Astropy development docs for more details. This function obtains that information by iterating through all packages in ``srcdir`` and locating a ``setup_package.py`` module. This module can contain the following functions: ``get_extensions()``, ``get_package_data()``, ``get_build_options()``, ``get_external_libraries()``, and ``requires_2to3()``. Each of those functions take no arguments. - ``get_extensions`` returns a list of `distutils.extension.Extension` objects. - ``get_package_data()`` returns a dict formatted as required by the ``package_data`` argument to ``setup()``. - ``get_build_options()`` returns a list of tuples describing the extra build options to add. - ``get_external_libraries()`` returns a list of libraries that can optionally be built using external dependencies. - ``get_entry_points()`` returns a dict formatted as required by the ``entry_points`` argument to ``setup()``. - ``requires_2to3()`` should return `True` when the source code requires `2to3` processing to run on Python 3.x. If ``requires_2to3()`` is missing, it is assumed to return `True`. excludeget_build_optionsr/get_external_libraries requires_2to3Tget_extensionsget_package_datanumpyZ skip_cythonmsvcz /MANIFESTrr`rr skip_2to3)rVr rZrQrrHradd_external_libraryrr^ospathdirname__file__rrrrget_cython_extensionsreversedr enumeraterLZget_compiler_optionextra_link_args)rArrr`rrrrboptionsrN librarieslibraryriextr%r%r&r?sN+     %  rccsx|D]x}|jd}tjj||}tjjtjj|d}tjj|rt|d|d}|VqWdS)a6 A generator that finds and imports all of the ``setup_package.py`` modules in the source packages. Returns ------- modgen : generator A generator that yields (modname, mod), where `mod` is the module and `modname` is the module name for the ``setup_package.py`` modules. r9zsetup_package.pyrLz.setup_packageN)splitrrjoinrelpathisfiler)rAr`r7 package_parts package_pathZ setup_packagemoduler%r%r&rZs    rZccsxt|D]{\}}}xh|D]`}|jdr#tjjtjj||}dj||ddg}||fVq#WPq WdS)a A generator that yields Cython source files (ending in '.pyx') in the source packages. Returns ------- pyxgen : generator A generator that yields (extmod, fullfn) where `extmod` is the full name of the module that the .pyx file would live in based on the source directory structure, and `fullfn` is the path to the .pyx file. z.pyxr9N)rendswithrrrr)rrdirpathdirnames filenamesfnZfullfnextmodr%r%r&iter_pyx_filess  !rcCsg}g}x`|D]X}xO|jD]D}|jdr#tjjtjj|d}|j|q#WqWx|D]} | jd} tjj|| } xjt | | D]Y\} } tjjtjj| d}||kr|jt | | gd|qWqvW|S)a Looks for Cython files and generates Extensions if needed. Parameters ---------- srcdir : str Path to the root of the source directory to search. prevextensions : list of `~distutils.core.Extension` objects The extensions that are already defined. Any .pyx files already here will be ignored. extincludedirs : list of str or None Directories to include as the `include_dirs` argument to the generated `~distutils.core.Extension` objects. Returns ------- exts : list of `~distutils.core.Extension` objects The new extensions that are needed to compile all .pyx files (does not include any already in `prevextensions`). .pyx.c.cpprr9 include_dirs)rrr) sourcesrrrrealpathsplitextr^rrrr)rAr`ZprevextensionsZextincludedirsZprevsourcepathsrrroZ sourcepathrrrrZpyxfnr%r%r&rs  " " rcs4eZdZdZfddZddZS)DistutilsExtensionArgsz A special dictionary whose default values are the empty list. This is useful for building up a set of arguments for `distutils.Extension` without worrying whether the entry is already present. cs,dd}tt|j|||dS)NcSsgS)Nr%r%r%r%r&default_factorysz8DistutilsExtensionArgs.__init__..default_factory)superr__init__)rhargskwargsr) __class__r%r&rs zDistutilsExtensionArgs.__init__cCs2x+|jD]\}}||j|q WdS)N)rFr)rhotherkeyvalr%r%r&rszDistutilsExtensionArgs.update)rG __module__ __qualname____doc__rrr%r%)rr&rs rz pkg-configc Csddddddddd d i}d j|d j|f}t}y8tj|d ddtj}|jdj}Wntjk r}zkdj|dj|j dj|j dj|j g} t j dj| |dj|WYdd}~XnX|j dkrkdjdj|dg} t j dj| |dj|nx|jD]} | ddjd} | ddjtj} | |kr| dkrt| jdd} ||| j| qx|dj| qxW|S)a Uses pkg-config to update a set of distutils Extension arguments to include the flags necessary to link against the given packages. If the pkg-config lookup fails, default_libraries is applied to libraries. Parameters ---------- packages : list of str A list of pkg-config packages to look up. default_libraries : list of str A list of library names to use if the pkg-config lookup fails. Returns ------- config : dict A dictionary containing keyword arguments to `distutils.Extension`. These entries include: - ``include_dirs``: A list of include directories - ``library_dirs``: A list of library directories - ``libraries``: A list of libraries - ``define_macros``: A list of macro defines - ``undef_macros``: A list of macros to undefine - ``extra_compile_args``: A list of extra arguments to pass to the compiler z-Irz-L library_dirsz-lrz-D define_macrosz-U undef_macrosz{0} --libs --cflags {1} shellTstdoutrz3{0} failed. This may cause the build to fail below.z command: {0}z returncode: {0}z output: {0} Nz.pkg-config could not lookup up package(s) {0}.z, z'This may cause the build to fail below.rSascii=r extra_compile_args)rsrr subprocessPopenPIPE communicatestripCalledProcessErrorr, returncodeoutputrr#rrdecoder{getfilesystemencodingtupler^) r`Zdefault_libraries executableZflag_mapcommandresultpiperelinestokenargvaluer%r%r& pkg_config"s< $   rcCsGx@dddgD]/}t|td|dj|ddqWdS) z Add a build option for selecting the internal or system copy of a library. Parameters ---------- library : str The name of the library. If the library is `foo`, the build option will be called `--use-system-foo`. r/r0r=z use-system-zUse the system {0} libraryZis_boolTN)rHrrrs)rrr%r%r&rrs rcCstdj|ptdS)a Returns `True` if the build configuration indicates that the given library should use the system copy of the library rather than the internal one. For the given library `foo`, this will be `True` if `--use-system-foo` or `--use-system-libraries` was provided at the commandline or in `setup.cfg`. Parameters ---------- library : str The name of the library Returns ------- use_system : bool `True` if the build should use the system copy of the library. zuse_system_{0}Zuse_system_libraries)Z%get_distutils_build_or_install_optionrs)rr%r%r&use_system_librarysrcCsB| rtddk rtdStd|d|}|td<|S)z This version of ``find_packages`` caches previous results to speed up subsequent calls. Use ``invalide_cache=True`` to ignore cached results from previous ``find_packages`` calls, and repeat the package search. r Nwherer)rB_find_packages)rrZinvalidate_cacher`r%r%r&r s  r cs,trdndfdd|DS)zw Removes some packages from the package list that shouldn't be installed on the current version of Python. Z_py2Z_py3cs%g|]}|js|qSr%)r)r+x)rr%r& s z#filter_packages..)PY3)rr%)rr&rVs rVc @s}eZdZdZdd d!d"d#d$d%d&d'd(d)g Zejd*ejd+ejd,ejd-ddZd S).rCzz A dummy build_sphinx command that is called if Sphinx is not installed and displays a relevant error message fresh-envE all-filesa source-dir=ro build-dir=N config-dir=cbuilder=bproject=version=release=today= link-indexrwarnings-returncodew clean-docslno-intersphinxnopen-docs-in-browseroc Cs9ytdWn"tjdtjdYnXdS)Nz)Sphinx must be installed for build_sphinxz0error: Sphinx must be installed for build_sphinxr ) RuntimeErrorrrxr{r|)rhr%r%r&initialize_optionss  z"FakeBuildSphinx.initialize_options)rrr)rrr)rror)rNr)rrr)rrr)rNr)rNr)rNr)rNr)rrr)rrr)rrr)rrr)rrr)rGrrr user_optionsr^rr%r%r%r&rCs"      rC)Sr __future__rrrWrrTrr{ryr" distutilsrdistutils.distrdistutils.errorsrrdistutils.corerr Zdistutils.command.sdistr rE setuptoolsr rZdistutils_helpersZversion_helpersrutilsrrrrrZcommands.build_extrZcommands.build_pyrZcommands.installrZcommands.install_librZcommands.registerrZ commands.testrrrrrrBZsphinx ValueErrorrrr#r1 SyntaxError version_inforrr'r8rOrIr_rgrDrrrZrrrrXrrrrrVrCr%r%r%r&s~        (       8/  % m  1P    APLpy-1.1.1/astropy_helpers/astropy_helpers/__pycache__/test_helpers.cpython-34.pyc0000644000077000000240000001612112536567761030443 0ustar tomstaff00000000000000 D T%@sddlmZmZmZmZddlZddlZddlZddlZddl Z ddl m Z ddl m Z ejddkZGddde eZdS) )absolute_importdivisionprint_functionunicode_literalsN)Command)_fix_user_optionsc@seZdZdZd<d=d>d?d@dAdBdCdDdEdFdGdHdIdJgZeeZd-Zd.d/Zd0d1Zd2d3Z d4d5Z d6d7Z d8d9Z d:d;Z d'S)K AstropyTestzRun the tests for this packagepackage=PwThe name of a specific package to test, e.g. 'io.fits' or 'utils'. If nothing is specified, all default tests are run. test-path=tHSpecify a test location by path. If a relative path to a .py file, it is relative to the built package, so e.g., a leading "astropy/" is necessary. If a relative path to a .rst file, it is relative to the directory *below* the --docs-path directory, so a leading "docs/" is usually necessary. May also be an absolute path.verbose-resultsV#Turn on verbose output from pytest.plugins=p&Plugins to enable when running pytest. pastebin=b8Enable pytest pastebin output. Either 'all' or 'failed'.args=a,Additional arguments to be passed to pytest. remote-dataR$Run tests that download remote data.pep88PEnable PEP8 checking and disable regular tests. Requires the pytest-pep8 plugin.pdbd0Start the interactive Python debugger on errors.coveragec8Create a coverage report. Requires the coverage package. open-filesoAFail if any tests leave files open. Requires the psutil package. parallel=jRun the tests in parallel on the specified number of CPUs. If negative, all the cores on the machine will be used. Requires the pytest-xdist plugin. docs-path=NThe path to the documentation .rst files. If not provided, and the current directory contains a directory called "docs", that will be used. skip-docs(Don't test the documentation .rst files.repeat=PHow many times to repeat each test (can be used to check for sporadic failures).cCsd|_d|_d|_d|_d|_d|_d|_d|_d|_d|_ d|_ d|_ d|_ d|_ d|_dS)NFr)packageZ test_pathZverbose_resultspluginsZpastebinargsZ remote_datar r#r&Z open_filesparallel docs_pathZ skip_docsrepeat)selfr=d/Volumes/Raptor/Dropbox/Code/development/APLpy/APLpy/astropy_helpers/astropy_helpers/test_helpers.pyinitialize_optionsCs              zAstropyTest.initialize_optionscCsdS)Nr=)r<r=r=r>finalize_optionsTszAstropyTest.finalize_optionscCsud}d}|jr>|j\}}||7}||7}ntrMd}nd}d}|j||d|d|S)z9 Build a Python script to run the tests. r5z/import builtins; builtins._ASTROPY_TEST_ = Truez5import __builtin__; __builtin__._ASTROPY_TEST_ = Truea{cmd_pre}{0}; import {1.package_name}, sys; result = ({1.package_name}.test(package={1.package!r}, test_path={1.test_path!r}, args={1.args!r}, plugins={1.plugins!r}, verbose={1.verbose_results!r}, pastebin={1.pastebin!r}, remote_data={1.remote_data!r}, pep8={1.pep8!r}, pdb={1.pdb!r}, open_files={1.open_files!r}, parallel={1.parallel!r}, docs_path={1.docs_path!r}, skip_docs={1.skip_docs!r}, repeat={1.repeat!r})); {cmd_post}sys.exit(result)cmd_precmd_post)r&_generate_coverage_commandsPY3format)r<rArBprepostZset_flagcmdr=r=r>generate_testing_commandYs    z$AstropyTest.generate_testing_commandc Cs5yddl}Wntk r0tdYnXdS)zn This method checks that any required modules are installed before running the tests. rNzOThe 'test' command requires the astropy package to be installed and importable.)astropy ImportError)r<rJr=r=r>_validate_required_depss  z#AstropyTest._validate_required_depsc Cs|j|j|jdkrPtjjdrPtjjd|_qPnz=|j}tj t j dd|gd|j dd}Wdt j|jXt|dS)z Run the tests! Ndocsz-Bz-ccwd close_fdsF)_build_temp_installrLr:ospathexistsabspathrI subprocesscallsys executable testing_pathshutilrmtreetmp_dir SystemExit)r<rHretcoder=r=r>runs   zAstropyTest.runcCs|jddd|jd|jd}tjj|j}tjd|j d|_ tjj |j tjj ||_ tj||j tjd|j dS)z Build the package and copy the build to a temporary directory for the purposes of testing this avoids creating pyc and __pycache__ directories inside the build directory buildinplaceTprefixz-test-z setup.cfgN)reinitialize_command run_commandget_finalized_commandrQrRrTZ build_libtempfilemkdtemp package_namer\joinbasenamerYrZcopytreecopy)r<Z build_cmdnew_pathr=r=r>rPs 'zAstropyTest._build_temp_installc CsY|jdkrtdnyddl}Wntk rNtdYnXtjj|j|jdd}t |d}|j }WdQXt rd}nd }|j d |j d |j}tjj|j d}t |d }|j|jd WdQXdjtjjd|}djtjjd|j} || fS)zf This method creates the post and pre commands if coverage is to be generated rz*--coverage can not be used with --parallelNz;--coverage requires that the coverage package is installed.tests coveragercr23z{ignore_python_version}z {packagename}wbzutf-8zZimport coverage; cov = coverage.coverage(data_file="{0}", config_file="{1}"); cov.start();z .coveragezgcov.stop(); from astropy.tests.helper import _save_coverage; _save_coverage(cov, result, "{0}", "{1}");.)r9 ValueErrorr&rKrQrRrirYrhopenreadrDreplacer\writeencoderErT) r<r&rofdZcoveragerc_contentZignore_python_versionZtmp_coveragerctmprArBr=r=r>rCs4     z'AstropyTest._generate_coverage_commands)r r r )rrr)rrr)rrr)rrr)rrr)rrr)zpep8r!r")zpdbr$r%)zcoverager'r()r)r*r+)r,r-r.)r/Nr0)r1Nr2)r3Nr4)__name__ __module__ __qualname__ description user_optionsrrhr?r@rIrLr_rPrCr=r=r=r>r sL      & . r ) __future__rrrrrQrZrUrWrf setuptoolsrcompatr version_inforDobjectr r=r=r=r>s"     APLpy-1.1.1/astropy_helpers/astropy_helpers/__pycache__/test_helpers.cpython-35.pyc0000644000077000000240000001610212654136413030426 0ustar tomstaff00000000000000 D T%@sddlmZmZmZmZddlZddlZddlZddlZddl Z ddl m Z ddl m Z ejddkZGddde eZdS) )absolute_importdivisionprint_functionunicode_literalsN)Command)_fix_user_optionsc@seZdZdZd<d=d>d?d@dAdBdCdDdEdFdGdHdIdJgZeeZd-Zd.d/Zd0d1Zd2d3Z d4d5Z d6d7Z d8d9Z d:d;Z d'S)K AstropyTestzRun the tests for this packagepackage=PwThe name of a specific package to test, e.g. 'io.fits' or 'utils'. If nothing is specified, all default tests are run. test-path=tHSpecify a test location by path. If a relative path to a .py file, it is relative to the built package, so e.g., a leading "astropy/" is necessary. If a relative path to a .rst file, it is relative to the directory *below* the --docs-path directory, so a leading "docs/" is usually necessary. May also be an absolute path.verbose-resultsV#Turn on verbose output from pytest.plugins=p&Plugins to enable when running pytest. pastebin=b8Enable pytest pastebin output. Either 'all' or 'failed'.args=a,Additional arguments to be passed to pytest. remote-dataR$Run tests that download remote data.pep88PEnable PEP8 checking and disable regular tests. Requires the pytest-pep8 plugin.pdbd0Start the interactive Python debugger on errors.coveragec8Create a coverage report. Requires the coverage package. open-filesoAFail if any tests leave files open. Requires the psutil package. parallel=jRun the tests in parallel on the specified number of CPUs. If negative, all the cores on the machine will be used. Requires the pytest-xdist plugin. docs-path=NThe path to the documentation .rst files. If not provided, and the current directory contains a directory called "docs", that will be used. skip-docs(Don't test the documentation .rst files.repeat=PHow many times to repeat each test (can be used to check for sporadic failures).cCsd|_d|_d|_d|_d|_d|_d|_d|_d|_d|_ d|_ d|_ d|_ d|_ d|_dS)NFr)packageZ test_pathZverbose_resultspluginsZpastebinargsZ remote_datar r#r&Z open_filesparallel docs_pathZ skip_docsrepeat)selfr=_/Users/tom/Dropbox/Code/development/APLpy/APLpy/astropy_helpers/astropy_helpers/test_helpers.pyinitialize_optionsCs              zAstropyTest.initialize_optionscCsdS)Nr=)r<r=r=r>finalize_optionsTszAstropyTest.finalize_optionscCsrd}d}|jr;|j\}}||7}||7}trJd}nd}d}|j||d|d|S)z9 Build a Python script to run the tests. r5z/import builtins; builtins._ASTROPY_TEST_ = Truez5import __builtin__; __builtin__._ASTROPY_TEST_ = Truea{cmd_pre}{0}; import {1.package_name}, sys; result = ({1.package_name}.test(package={1.package!r}, test_path={1.test_path!r}, args={1.args!r}, plugins={1.plugins!r}, verbose={1.verbose_results!r}, pastebin={1.pastebin!r}, remote_data={1.remote_data!r}, pep8={1.pep8!r}, pdb={1.pdb!r}, open_files={1.open_files!r}, parallel={1.parallel!r}, docs_path={1.docs_path!r}, skip_docs={1.skip_docs!r}, repeat={1.repeat!r})); {cmd_post}sys.exit(result)cmd_precmd_post)r&_generate_coverage_commandsPY3format)r<rArBprepostZset_flagcmdr=r=r>generate_testing_commandYs    z$AstropyTest.generate_testing_commandc Cs5yddl}Wntk r0tdYnXdS)zn This method checks that any required modules are installed before running the tests. rNzOThe 'test' command requires the astropy package to be installed and importable.)astropy ImportError)r<rJr=r=r>_validate_required_depss  z#AstropyTest._validate_required_depsc Cs|j|j|jdkrJtjjdrJtjjd|_z=|j}tj t j dd|gd|j dd}Wdt j|jXt|dS)z Run the tests! Ndocsz-Bz-ccwd close_fdsF)_build_temp_installrLr:ospathexistsabspathrI subprocesscallsys executable testing_pathshutilrmtreetmp_dir SystemExit)r<rHretcoder=r=r>runs   zAstropyTest.runcCs|jddd|jd|jd}tjj|j}tjd|j d|_ tjj |j tjj ||_ tj||j tjd|j dS)z Build the package and copy the build to a temporary directory for the purposes of testing this avoids creating pyc and __pycache__ directories inside the build directory buildinplaceTprefixz-test-z setup.cfgN)reinitialize_command run_commandget_finalized_commandrQrRrTZ build_libtempfilemkdtemp package_namer\joinbasenamerYrZcopytreecopy)r<Z build_cmdnew_pathr=r=r>rPs 'zAstropyTest._build_temp_installc CsX|jdkrtdyddl}Wntk rKtdYnXtjj|j|jdd}t |d}|j }WdQRXt rd}nd }|j d |j d |j}tjj|j d}t |d }|j|jd WdQRXdjtjjd|}djtjjd|j} || fS)zf This method creates the post and pre commands if coverage is to be generated rz*--coverage can not be used with --parallelNz;--coverage requires that the coverage package is installed.tests coveragercr23z{ignore_python_version}z {packagename}wbzutf-8zZimport coverage; cov = coverage.coverage(data_file="{0}", config_file="{1}"); cov.start();z .coveragezgcov.stop(); from astropy.tests.helper import _save_coverage; _save_coverage(cov, result, "{0}", "{1}");.)r9 ValueErrorr&rKrQrRrirYrhopenreadrDreplacer\writeencoderErT) r<r&rofdZcoveragerc_contentZignore_python_versionZtmp_coveragerctmprArBr=r=r>rCs4     z'AstropyTest._generate_coverage_commands)r r r )rrr)rrr)rrr)rrr)rrr)rrr)zpep8r!r")zpdbr$r%)zcoverager'r()r)r*r+)r,r-r.)r/Nr0)r1Nr2)r3Nr4)__name__ __module__ __qualname__ description user_optionsrrhr?r@rIrLr_rPrCr=r=r=r>r sL      & . r ) __future__rrrrrQrZrUrWrf setuptoolsrcompatr version_inforDobjectr r=r=r=r>s"     APLpy-1.1.1/astropy_helpers/astropy_helpers/__pycache__/utils.cpython-34.pyc0000644000077000000240000003757012536567761027115 0ustar tomstaff00000000000000 D TH@srddlmZmZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z y,ddl m ZeedsdZnWnek rdZYnXejddd1krddl mZn dd ZGd d d eZGd d d eZGdddeeZddZddZGdddeZejddZejdkrddlZddZ n ddZ ddZ!dddd Z"d!d"Z#dd#d$Z$d%d&Z%ejddkr&d'd(Z&n d)d(Z&d*d+Z'd,d,d,ddd-d.Z(dddd/d0Z)dS)2)absolute_importunicode_literalsN) machinery SourceLoader)invalidate_cachescCsdS)Nr r r ]/Volumes/Raptor/Dropbox/Code/development/APLpy/APLpy/astropy_helpers/astropy_helpers/utils.pysr c@seZdZdZdS)AstropyWarningz The base warning class from which all Astropy warnings should inherit. Any warning inheriting from this class is handled by the Astropy logger. N)__name__ __module__ __qualname____doc__r r r r r #s r c@seZdZdZdS)AstropyDeprecationWarningz; A warning class to indicate a deprecated feature. N)r rrrr r r r r+s rc@seZdZdZdS) AstropyPendingDeprecationWarningzF A warning class to indicate a soon-to-be deprecated feature. N)r rrrr r r r r1s rcCs<dj|jtjdd}tjj|jd|S)z Given a build command, return the name of the appropriate platform-specific build subdirectory directory (e.g. build/lib.linux-x86_64-2.7) z.{0}-{1}rrlib)formatZ plat_namesysversionospathjoin build_base)cmdZplat_specifierr r r _get_platlib_dir8s"rc Cstjddkr_ddl}t|dr7|`nddl}ddl}|j|n:ddl}t|dr|`nddl}t|y|j }Wnt k r|j }YnX|S)z- Gets the path to the numpy headers. rrN__NUMPY_SETUP__) r version_infobuiltinshasattrrimpnumpyreload __builtin__ get_includeAttributeErrorZget_numpy_include)rr!r"r$Z numpy_includer r r get_numpy_include_pathBs"          r'c@s4eZdZdZdZddZddZdS) _DummyFilezA noop writeable object.cCsdS)Nr )selfsr r r writeesz_DummyFile.writecCsdS)Nr )r*r r r flushhsz_DummyFile.flushN)r rrrerrorsr,r-r r r r r(`s  r(c cs~tj}tj}tt_tt_d}y dVWn#d}|t_|t_YnX|sz|t_|t_ndS)z:A context manager that silences sys.stdout and sys.stderr.FNT)rstdoutstderrr() old_stdout old_stderrexception_occurredr r r silencels        r4win32c Cst|tr'|jtj}ny;tjjj|}|dksQt t |d@}Wnt t fk rd}YnX|S)z Returns True if the given filepath has the hidden attribute on MS-Windows. Based on a post here: http://stackoverflow.com/questions/284115/cross-platform-hidden-file-detection rF) isinstancebytesdecodergetfilesystemencodingctypesZwindllZkernel32ZGetFileAttributesWAssertionErrorboolr&)filepathattrsresultr r r _has_hidden_attributes rBcCsdS)NFr )r?r r r rBscCs^tjjtjj|}t|tr?|jd}n|jd}|p]t|S)z Determines if a given file or directory is hidden. Parameters ---------- filepath : str The path to a file or directory Returns ------- hidden : bool Returns `True` if the file is hidden ..)rrbasenameabspathr8r9 startswithrB)r?nameZ is_dottedr r r is_path_hiddens rIFccsxztj|ddd|d|D]W\}}}dd|D|dd