├── Doc ├── requirements.txt ├── _static │ ├── lemon-sidebar.png │ └── HAT-P-16b-2014-10-31.jpg ├── user │ ├── images │ │ └── gui │ │ │ ├── finding-chart.png │ │ │ ├── main-window.png │ │ │ ├── star-details.png │ │ │ └── export-to-file.png │ └── install.rst ├── _themes │ ├── kr │ │ ├── theme.conf │ │ ├── relations.html │ │ ├── layout.html │ │ └── static │ │ │ └── small_flask.css │ ├── kr_small │ │ ├── theme.conf │ │ ├── layout.html │ │ └── static │ │ │ └── flasky.css_t │ ├── README.rst │ ├── LICENSE │ └── flask_theme_support.py ├── commands │ ├── index.rst │ └── rename.sh ├── _templates │ ├── sidebarlogo.html │ └── sidebarintro.html ├── index.rst ├── make.bat └── Makefile ├── juicer ├── __init__.py ├── gui │ ├── img │ │ ├── lemon.png │ │ └── compass.png │ ├── overview.glade │ ├── loading-dialog.glade │ ├── finding-chart-dialog.glade │ ├── about.glade │ └── snr-threshold-dialog.glade ├── main.py ├── simbad.py ├── glade.py └── config.py ├── util ├── test │ ├── __init__.py │ ├── test_queue.py │ └── test_log.py ├── __init__.py ├── memoize.py ├── context.py ├── gtkutil.py ├── queue.py ├── display.py ├── log.py └── coords.py ├── Misc ├── lemon-icon.png ├── lemon-icon_200px.png ├── ACKS └── CHANGES ├── pre-requirements.txt ├── sextractor ├── sextractor.conv ├── sextractor.nnw ├── sextractor.param └── sextractor.sex ├── version.py ├── .gitignore ├── __init__.py ├── .github ├── workflows │ └── black.yml └── stale.yml ├── test ├── __init__.py ├── integration │ ├── wasp10b-download.sh │ ├── WASP10b-coordinates.txt │ └── README.md ├── test_data │ ├── filters │ │ ├── Unknown │ │ ├── Cousins │ │ ├── Harris │ │ ├── Halpha │ │ ├── 2MASS │ │ ├── Gunn │ │ └── SDSS │ └── sextractor_noasciihead.cat ├── test_customparser.py └── dss_images.py ├── Pipfile ├── Makefile ├── requirements.txt ├── style.py ├── README.rst ├── run_tests.py ├── .travis.yml ├── ci └── travis-setup.sh ├── defaults.py ├── lemon ├── export.py ├── keywords.py ├── setup.py ├── json_parse.py ├── lemon-completion.sh ├── check_versions.py └── customparser.py /Doc/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinxcontrib-images>=0.5.0 2 | -------------------------------------------------------------------------------- /juicer/__init__.py: -------------------------------------------------------------------------------- 1 | # Dummy file to make this directory a package. 2 | -------------------------------------------------------------------------------- /util/test/__init__.py: -------------------------------------------------------------------------------- 1 | # Empty __init__.py to make this directory a package. 2 | -------------------------------------------------------------------------------- /Misc/lemon-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vterron/lemon/HEAD/Misc/lemon-icon.png -------------------------------------------------------------------------------- /juicer/gui/img/lemon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vterron/lemon/HEAD/juicer/gui/img/lemon.png -------------------------------------------------------------------------------- /Misc/lemon-icon_200px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vterron/lemon/HEAD/Misc/lemon-icon_200px.png -------------------------------------------------------------------------------- /juicer/gui/img/compass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vterron/lemon/HEAD/juicer/gui/img/compass.png -------------------------------------------------------------------------------- /Doc/_static/lemon-sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vterron/lemon/HEAD/Doc/_static/lemon-sidebar.png -------------------------------------------------------------------------------- /Doc/_static/HAT-P-16b-2014-10-31.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vterron/lemon/HEAD/Doc/_static/HAT-P-16b-2014-10-31.jpg -------------------------------------------------------------------------------- /Doc/user/images/gui/finding-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vterron/lemon/HEAD/Doc/user/images/gui/finding-chart.png -------------------------------------------------------------------------------- /Doc/user/images/gui/main-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vterron/lemon/HEAD/Doc/user/images/gui/main-window.png -------------------------------------------------------------------------------- /Doc/user/images/gui/star-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vterron/lemon/HEAD/Doc/user/images/gui/star-details.png -------------------------------------------------------------------------------- /pre-requirements.txt: -------------------------------------------------------------------------------- 1 | astropy>=0.2.4 # needed by APLpy / montage-wrapper 2 | d2to1>=0.2.10 # needed by PyRAF 3 | 4 | -------------------------------------------------------------------------------- /Doc/user/images/gui/export-to-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vterron/lemon/HEAD/Doc/user/images/gui/export-to-file.png -------------------------------------------------------------------------------- /sextractor/sextractor.conv: -------------------------------------------------------------------------------- 1 | CONV NORM 2 | # 3x3 ``all-ground'' convolution mask with FWHM = 2 pixels. 3 | 1 2 1 4 | 2 4 2 5 | 1 2 1 6 | -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | __version_info__ = (0, 3, 0) 4 | __version__ = ".".join(str(x) for x in __version_info__) 5 | -------------------------------------------------------------------------------- /Doc/_themes/kr/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | touch_icon = 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | *~ 3 | 4 | # PyRAF cache directory 5 | pyraf 6 | 7 | # User login file for IRAF 8 | login.cl 9 | 10 | # IRAF user parameter directory 11 | uparm 12 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | # encoding:UTF-8 3 | 4 | from version import __version_info__, __version__ 5 | 6 | __author__ = "Víctor Terrón" 7 | __email__ = "`echo vt2rron1iaa32s | tr 132 @.e`" 8 | __license__ = "GNU GPL v3" 9 | -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from .context import * 4 | from .coords import * 5 | from .display import * 6 | from .gtkutil import * 7 | from .io import * 8 | from .log import * 9 | from .memoize import * 10 | from .queue import * 11 | -------------------------------------------------------------------------------- /Doc/_themes/kr_small/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | nosidebar = true 5 | pygments_style = flask_theme_support.FlaskyStyle 6 | 7 | [options] 8 | index_logo = '' 9 | index_logo_height = 120px 10 | github_fork = '' 11 | -------------------------------------------------------------------------------- /.github/workflows/black.yml: -------------------------------------------------------------------------------- 1 | name: Enforce Black formatting 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-python@v2 11 | - uses: psf/black@stable 12 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | import sys 4 | 5 | # Several convenient features of unittest, such as assertRaises as a context 6 | # manager and test skipping, are not available until Python 2.7. In previous 7 | # versions, use unittest2, a backport of the new features. 8 | 9 | if sys.version_info < (2, 7): 10 | import unittest2 as unittest 11 | else: 12 | import unittest 13 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | APLpy = ">=0.9.13" 10 | astropy = ">=1.0.2" 11 | d2to1 = "==0.2.11" 12 | matplotlib = "==1.4.1" 13 | mock = "==1.0.1" 14 | montage-wrapper = ">=0.9.8" 15 | pyfits = "==3.2" 16 | pyraf = ">=2.1.7" 17 | requests = "==2.20.0" 18 | scipy = "==0.14.0" 19 | subprocess32 = "==3.2.6" 20 | uncertainties = "==2.4.6.1" 21 | unittest2 = "==0.5.1" 22 | numpy = ">=1.10.1" 23 | pyside = "*" 24 | 25 | [requires] 26 | python_version = "2.7" 27 | -------------------------------------------------------------------------------- /Doc/_themes/kr/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Inspired by https://pipenv.pypa.io/en/latest/advanced/#travis-ci. 2 | 3 | .PHONY: all init test 4 | 5 | all: init test 6 | 7 | init: 8 | # Use --site-packages + PIP_IGNORE_INSTALLED "to make sure that any 9 | # pip-installable dependencies actually are installed into the virtual 10 | # environment, with the system site-packages then being used only to 11 | # resolve imports that aren't covered by the Python level dependency 12 | # graph at all" (in our case, that's PyGTK). See 13 | # https://github.com/pypa/pipenv/issues/748#issue-261233053 14 | pipenv --two --site-packages && PIP_IGNORE_INSTALLED=1 pipenv install 15 | 16 | test: 17 | pipenv run run_tests.py 18 | -------------------------------------------------------------------------------- /Doc/commands/index.rst: -------------------------------------------------------------------------------- 1 | .. _commands-index: 2 | 3 | ############## 4 | LEMON commands 5 | ############## 6 | 7 | The LEMON pipeline consists of ten commands, implementing different atronomical 8 | data reduction and analysis steps, that are usually run sequentially, although 9 | depending on your needs only a specific subset of them may be used. You may, 10 | for example, be interested in just aligning your FITS images or update their 11 | headers with astrometric information. In this sense, and following the Unix 12 | tools philosophy (*write programs that do one thing and do it well*), LEMON 13 | can be viewed as a set of tasks that *may* be used as a pipeline. 14 | 15 | .. toctree:: 16 | :numbered: 17 | 18 | import.rst 19 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # The reason why we need two different requirements files, and also 2 | # why numpy>=1.7.1 is not included in any of them, is that some of 3 | # the packages depend on another package in order to even run their 4 | # setup.py, without mattering that the dependency is also going to be 5 | # installed. As explained by the pip developers, these are broken 6 | # setup.py files, not pip's fault. Serial installation is the only 7 | # answer here [https://github.com/pypa/pip/issues/25] 8 | 9 | absl-py>=0.9.0 10 | APLpy>=0.9.9 11 | scipy>=0.12.0 12 | matplotlib>=1.2.1 13 | mock>=1.0.1 14 | prettytable>=0.7.2 15 | pyfits>=3.2 16 | pyraf>=2.1.1 17 | uncertainties>=2.4.1 18 | unittest2>=0.5.1 19 | montage-wrapper>=0.9.7 20 | requests>=2.0.1 21 | subprocess32>=3.2.6 22 | -------------------------------------------------------------------------------- /Doc/_themes/kr_small/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | {% block header %} 3 | {{ super() }} 4 | {% if pagename == 'index' %} 5 |
6 | {% endif %} 7 | {% endblock %} 8 | {% block footer %} 9 | {% if pagename == 'index' %} 10 |
11 | {% endif %} 12 | {% endblock %} 13 | {# do not display relbars #} 14 | {% block relbar1 %}{% endblock %} 15 | {% block relbar2 %} 16 | {% if theme_github_fork %} 17 | Fork me on GitHub 19 | {% endif %} 20 | {% endblock %} 21 | {% block sidebar1 %}{% endblock %} 22 | {% block sidebar2 %}{% endblock %} 23 | -------------------------------------------------------------------------------- /Doc/_templates/sidebarlogo.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 |

11 | 13 |

14 | 15 |

16 | LEMON is an astronomical pipeline for automated time-series reduction and analysis. Input your FITS images and obtain the light curve of each object — it's almost as simple as that. 17 |

18 | -------------------------------------------------------------------------------- /Doc/_themes/README.rst: -------------------------------------------------------------------------------- 1 | krTheme Sphinx Style 2 | ==================== 3 | 4 | This repository contains sphinx styles Kenneth Reitz uses in most of 5 | his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related 6 | projects. To use this style in your Sphinx documentation, follow 7 | this guide: 8 | 9 | 1. put this folder as _themes into your docs folder. Alternatively 10 | you can also use git submodules to check out the contents there. 11 | 12 | 2. add this to your conf.py: :: 13 | 14 | sys.path.append(os.path.abspath('_themes')) 15 | html_theme_path = ['_themes'] 16 | html_theme = 'kr' 17 | 18 | The following themes exist: 19 | 20 | **kr** 21 | the standard flask documentation theme for large projects 22 | 23 | **kr_small** 24 | small one-page theme. Intended to be used by very small addon libraries. 25 | 26 | -------------------------------------------------------------------------------- /Doc/_themes/kr/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 9 | 10 | {% endblock %} 11 | {%- block relbar2 %}{% endblock %} 12 | {%- block footer %} 13 | 16 | 17 | 18 | Fork me on GitHub 19 | 20 | 21 | {%- endblock %} 22 | -------------------------------------------------------------------------------- /Doc/_templates/sidebarintro.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 |

11 | 13 |

14 | 15 |

16 | LEMON is an astronomical pipeline for automated time-series reduction and analysis. Input your FITS images and obtain the light curve of each object — it's almost as simple as that. 17 |

18 | 19 | 20 |

Useful Links

21 | 25 | -------------------------------------------------------------------------------- /juicer/gui/overview.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | vertical 8 | 9 | 10 | True 11 | True 12 | automatic 13 | automatic 14 | 15 | 16 | True 17 | True 18 | 19 | 20 | 21 | 22 | 0 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /juicer/main.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2012 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import app 22 | 23 | 24 | def main(**kwargs): 25 | juicer = app.LEMONJuicerGUI(**kwargs) 26 | juicer.run() 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /test/integration/wasp10b-download.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -Eeuo pipefail 4 | 5 | # Takes the path to a directory as argument and downloads the WASP10b test data 6 | # if any of the expected files is missing. In this manner, the .xz file is only 7 | # downloaded from the remote server if necessary. 8 | 9 | # Name of the file stored in $WASP10_URL. 10 | XZ_FILE="WASP10b-2011-08-23.tar.xz" 11 | 12 | pushd $1 13 | 14 | if sha1sum -c SHA1SUMS; then 15 | # Cache archives expire after 45 days for repositories on https://travis-ci.com. 16 | # [https://docs.travis-ci.com/user/caching/#caches-expiration] Write the current 17 | # date and time to a file so that (because of this modification inside the cache 18 | # directory) the expiration delay is reset. 19 | date > last-access.txt 20 | 21 | popd 22 | exit 0 23 | fi 24 | 25 | # Don't use rm *; explicitly list the files to remove. 26 | rm -vrf *.fits SHA1SUMS $XZ_FILE 27 | curl --remote-header-name --remote-name $WASP10_URL 28 | tar -xvf $XZ_FILE 29 | rm -fv $XZ_FILE 30 | chmod a-w -v *.fits 31 | 32 | popd 33 | -------------------------------------------------------------------------------- /style.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2012 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | # Used to change the format with which the logging module displays messages 22 | LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 23 | 24 | # Prefix that will be added to each line printed to the standard output 25 | prefix = ">> " 26 | 27 | # The error message to be printed if the execution is aborted.w 28 | error_exit_message = "%sExecution aborted." % prefix 29 | -------------------------------------------------------------------------------- /test/test_data/filters/Unknown: -------------------------------------------------------------------------------- 1 | # A series of two-element tuples, one per line, containing two strings. 2 | # The first element must be the name of the photometric filter whose 3 | # system is unknown, while the second is the letter that the Passband 4 | # class must identify when the former is parsed. 5 | 6 | 'wide', 'WIDE' 7 | 'B', 'B' 8 | 'Z', 'Z' 9 | 'G', 'G' 10 | 'I', 'I' 11 | 'H', 'H' 12 | 'K', 'K' 13 | 'J', 'J' 14 | 'M', 'M' 15 | 'L', 'L' 16 | 'N', 'N' 17 | 'KS', 'KS' 18 | 'R', 'R' 19 | 'U', 'U' 20 | 'W', 'W' 21 | 'V', 'V' 22 | 'Y', 'Y' 23 | 'narrow', 'NARROW' 24 | 25 | # Somewhat convoluted test cases 26 | 27 | "__--_wiDE___", 'WIDE' 28 | "wide---", 'WIDE' 29 | "_---b- -", 'B' 30 | "___b _-", 'B' 31 | "Z-", 'Z' 32 | "Z-__", 'Z' 33 | "__G", 'G' 34 | "g-", 'G' 35 | "--_-I", 'I' 36 | " i-", 'I' 37 | "h_---", 'H' 38 | "H -", 'H' 39 | "-K ", 'K' 40 | "-__--K--_ ", 'K' 41 | " j", 'J' 42 | " J", 'J' 43 | "_-__-M__", 'M' 44 | "_-m-", 'M' 45 | " l", 'L' 46 | " _L _-", 'L' 47 | " -__N-", 'N' 48 | " -N--__", 'N' 49 | "kS", 'KS' 50 | "KS-", 'KS' 51 | "_r_-_", 'R' 52 | "- ___R-_", 'R' 53 | " _U", 'U' 54 | " -_U ", 'U' 55 | "-__W-", 'W' 56 | "- _W--", 'W' 57 | "_-V -", 'V' 58 | " v_ _", 'V' 59 | "-_Y_", 'Y' 60 | "Y_", 'Y' 61 | "NARRow_-_", 'NARROW' 62 | "-_narRoW", 'NARROW' 63 | -------------------------------------------------------------------------------- /util/memoize.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Copyright (c) 2019 Victor Terron. All rights reserved. 4 | # 5 | # This file is part of LEMON. 6 | # 7 | # LEMON is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import functools 21 | 22 | 23 | def memoize(f): 24 | """Minimalistic memoization decorator (*args / **kwargs) 25 | Based on: http://code.activestate.com/recipes/577219/""" 26 | 27 | cache = {} 28 | 29 | @functools.wraps(f) 30 | def memf(*args, **kwargs): 31 | fkwargs = frozenset(kwargs.iteritems()) 32 | if (args, fkwargs) not in cache: 33 | cache[args, fkwargs] = f(*args, **kwargs) 34 | return cache[args, fkwargs] 35 | 36 | return memf 37 | -------------------------------------------------------------------------------- /Doc/commands/rename.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | INITIAL_NUMBER=$1; # will be zero if no argument is provided 4 | PREFIX="ferM_"; 5 | PREFIX_LENGTH=${#PREFIX}; 6 | EXTENSION=".fits"; 7 | NUMBER_OF_DIGITS=4; 8 | 9 | 10 | echo "Initial counting number: $INITIAL_NUMBER"; 11 | echo "Filename prefix: \"$PREFIX\"" 12 | echo "Filename prefix length: $PREFIX_LENGTH" 13 | echo "File extension: \"$EXTENSION\"" 14 | echo "Number of digits in the filename: $NUMBER_OF_DIGITS" 15 | 16 | LAST_NUMBER=`ls *$EXTENSION | sort| tail -n 1`; 17 | echo "Last file in destination directory is: $LAST_NUMBER"; 18 | 19 | for filename in *.fits; 20 | do 21 | originalName=$filename; 22 | echo "Filename: $filename"; 23 | filename=`basename $filename $EXTENSION`; 24 | #echo "Stripping file extension: $filename"; 25 | filename=${filename:$PREFIX_LENGTH}; 26 | #echo "Stripping filename prefix: $filename"; 27 | 28 | fileNumber=$INITIAL_NUMBER; 29 | let INITIAL_NUMBER++; 30 | echo "Assigned file number: $fileNumber" 31 | 32 | until [ ${#fileNumber} -ge $NUMBER_OF_DIGITS ]; 33 | do 34 | fileNumber="0$fileNumber"; 35 | done; 36 | 37 | echo "Adding the necessary number of zeros: $fileNumber" 38 | DEST_NAME="$PREFIX$fileNumber$EXTENSION" 39 | echo "New filename: $DEST_NAME" 40 | echo "Renaming $originalName to $DEST_NAME" 41 | mv $originalName $DEST_NAME 42 | 43 | done 44 | 45 | -------------------------------------------------------------------------------- /juicer/simbad.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2014 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import webbrowser 22 | 23 | 24 | def coordinate_query(ra, dec): 25 | """Look up an astronomical object in the SIMBAD database. 26 | 27 | Submit a coordinate-query to the SIMBAD database, using the given right 28 | ascension and declination (ICRS, J2000, 2000.0). Display it using the 29 | default browser, opening the page in the same window if possible. 30 | 31 | """ 32 | 33 | SIMBAD_URL = ( 34 | "http://simbad.u-strasbg.fr/simbad/sim-basic?" 35 | "Ident={0}+{1}&submit=SIMBAD+search" 36 | ) 37 | url = SIMBAD_URL.format(ra, dec) 38 | webbrowser.open(url) 39 | -------------------------------------------------------------------------------- /juicer/glade.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2012 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import functools 22 | import os.path 23 | 24 | GLADE_DIR = os.path.join(os.path.dirname(__file__), "./gui/") 25 | get = functools.partial(os.path.join, GLADE_DIR) 26 | 27 | GUI_MAIN = get("main.glade") 28 | GUI_ABOUT = get("about.glade") 29 | GUI_OVERVIEW = get("overview.glade") 30 | LOADING_DIALOG = get("loading-dialog.glade") 31 | STAR_DETAILS = get("star-details.glade") 32 | SNR_THRESHOLD_DIALOG = get("snr-threshold-dialog.glade") 33 | EXPORT_CURVE_DIALOG = get("curve-dump-dialog.glade") 34 | FINDING_CHART_DIALOG = get("finding-chart-dialog.glade") 35 | CHART_PREFERENCES_DIALOG = get("chart-preferences-dialog.glade") 36 | -------------------------------------------------------------------------------- /Doc/_themes/kr/static/small_flask.css: -------------------------------------------------------------------------------- 1 | /* 2 | * small_flask.css_t 3 | * ~~~~~~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | body { 10 | margin: 0; 11 | padding: 20px 30px; 12 | } 13 | 14 | div.documentwrapper { 15 | float: none; 16 | background: white; 17 | } 18 | 19 | div.sphinxsidebar { 20 | display: block; 21 | float: none; 22 | width: 102.5%; 23 | margin: 50px -30px -20px -30px; 24 | padding: 10px 20px; 25 | background: #333; 26 | color: white; 27 | } 28 | 29 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 30 | div.sphinxsidebar h3 a { 31 | color: white; 32 | } 33 | 34 | div.sphinxsidebar a { 35 | color: #aaa; 36 | } 37 | 38 | div.sphinxsidebar p.logo { 39 | display: none; 40 | } 41 | 42 | div.document { 43 | width: 100%; 44 | margin: 0; 45 | } 46 | 47 | div.related { 48 | display: block; 49 | margin: 0; 50 | padding: 10px 0 20px 0; 51 | } 52 | 53 | div.related ul, 54 | div.related ul li { 55 | margin: 0; 56 | padding: 0; 57 | } 58 | 59 | div.footer { 60 | display: none; 61 | } 62 | 63 | div.bodywrapper { 64 | margin: 0; 65 | } 66 | 67 | div.body { 68 | min-height: 0; 69 | padding: 0; 70 | } 71 | 72 | .rtd_doc_footer { 73 | display: none; 74 | } 75 | 76 | .document { 77 | width: auto; 78 | } 79 | 80 | .footer { 81 | width: auto; 82 | } 83 | 84 | .footer { 85 | width: auto; 86 | } 87 | 88 | .github { 89 | display: none; 90 | } -------------------------------------------------------------------------------- /test/integration/WASP10b-coordinates.txt: -------------------------------------------------------------------------------- 1 | 348.9695584 31.4569795 2 | 348.9706593 31.4786831 3 | 348.9708807 31.4946751 4 | 348.9716835 31.4465233 5 | 348.9755939 31.5118557 6 | 348.9762762 31.4427023 7 | 348.9768874 31.5459851 8 | 348.9802179 31.4777670 9 | 348.9877599 31.5169965 10 | 348.9929917 31.4628800 11 | 348.9948081 31.5149581 12 | 348.9955399 31.5168039 13 | 349.0005937 31.4859562 14 | 349.0026969 31.4235611 15 | 349.0037716 31.5311517 16 | 349.0071724 31.4950792 17 | 349.0090988 31.4468615 18 | 349.0178073 31.5295721 19 | 349.0216961 31.5262667 20 | 349.0256513 31.4267100 21 | 349.0267606 31.4887238 22 | 349.0279426 31.4565166 23 | 349.0290935 31.4751772 24 | 349.0347477 31.4282842 25 | 349.0355844 31.5335729 26 | 349.0368935 31.4940076 27 | 349.0378078 31.4599099 28 | 349.0418034 31.4913782 29 | 349.0424539 31.5084784 30 | 349.0487474 31.5452358 31 | 349.0511326 31.5124036 32 | 349.0521123 31.4836650 33 | 349.0524923 31.4603205 34 | 349.0578772 31.4870474 35 | 349.0622240 31.5246747 36 | 349.0664677 31.5208836 37 | 349.0675991 31.4276511 38 | 349.0680332 31.4743616 39 | 349.0686085 31.4869822 40 | 349.0700309 31.5271816 41 | 349.0800821 31.5451665 42 | 349.0810459 31.5465297 43 | 349.0861453 31.4386185 44 | 349.0906927 31.4335912 45 | 349.0910049 31.5242583 46 | 349.0920768 31.5274161 47 | 349.0940299 31.5444274 48 | 349.0945085 31.4288481 49 | 349.0960586 31.4818339 50 | 349.0971139 31.4691589 51 | 349.0978631 31.4487778 52 | 349.0988346 31.4819169 53 | 349.0996688 31.4372169 54 | 349.1003909 31.4279043 55 | 349.1067420 31.5295854 56 | 349.1107878 31.4343480 57 | 349.1151697 31.5462396 58 | 349.1196789 31.4575158 59 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | LEMON 2 | ===== 3 | 4 | LEMON_ is a differential-photometry pipeline, written in Python_, that determines the changes in the brightness of astronomical objects over time and compiles their measurements into `light curves`_. The aim of this program is to make it possible to completely reduce thousands of FITS images of time series in a matter of only a few hours, requiring minimal user interaction. 5 | 6 | For example, to get the light curve of a transit of HAT-P-16b_: 7 | 8 | :: 9 | 10 | $ lemon astrometry data/*.fits HAT-P-16/ 11 | $ lemon mosaic HAT-P-16/*.fits HAT-P-16-mosaic.fits 12 | $ lemon photometry HAT-P-16-mosaic.fits HAT-P-16/*.fits phot.LEMONdB 13 | $ lemon diffphot phot.LEMONdB curves.LEMONdB 14 | 15 | The above commands produce, among many others, the following plot: 16 | 17 | .. image:: Doc/_static/HAT-P-16b-2014-10-31.jpg 18 | 19 | Homepage 20 | -------- 21 | 22 | http://lemon.readthedocs.org/ 23 | 24 | Acknowledging us 25 | ---------------- 26 | 27 | If you use LEMON, please cite the package via its Zenodo_ record. 28 | 29 | .. image:: https://zenodo.org/badge/4550305.svg 30 | :target: https://zenodo.org/badge/latestdoi/4550305 31 | 32 | Project Status 33 | -------------- 34 | 35 | .. image:: https://travis-ci.org/vterron/lemon.png?branch=master 36 | :target: https://travis-ci.org/vterron/lemon 37 | 38 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg 39 | :target: https://github.com/psf/black 40 | 41 | .. _LEMON: https://lemon.readthedocs.org/ 42 | .. _Python: https://www.python.org/ 43 | .. _light curves: https://en.wikipedia.org/wiki/Light_curve 44 | .. _HAT-P-16b: http://exoplanet.eu/catalog/hat-p-16_b/ 45 | .. _Zenodo: https://zenodo.org/ 46 | -------------------------------------------------------------------------------- /run_tests.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2012 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | """ 22 | This is a convenience script for finding all the unit tests in the ./test/ 23 | directory and running them. Test modules are automatically detected, using 24 | TestLoader.discover(), which loads the test files that match the Unix-shell 25 | pattern 'test*.py' (such as ./test/test_passband.py). 26 | 27 | """ 28 | 29 | import sys 30 | from test import unittest 31 | 32 | # This import checks whether the FITS images used by some tests are where 33 | # expected and, if that is not the case, automatically downloads them from the 34 | # STScI Digitized Sky Survey. In this manner, any image retrieval will be done 35 | # before running the unit tests, never halfway through their execution. 36 | import test.dss_images 37 | 38 | if __name__ == "__main__": 39 | 40 | loader = unittest.TestLoader() 41 | tests = loader.discover(".", top_level_dir=".") 42 | runner = unittest.runner.TextTestRunner(verbosity=2) 43 | runner.failfast = True 44 | result = runner.run(tests) 45 | sys.exit(not result.wasSuccessful()) 46 | -------------------------------------------------------------------------------- /juicer/gui/loading-dialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 5 7 | True 8 | center-on-parent 9 | dialog 10 | True 11 | False 12 | cancel 13 | Loading database 14 | This may take a while... 15 | 16 | 17 | True 18 | vertical 19 | 2 20 | 21 | 22 | True 23 | True 24 | 25 | 26 | 3 27 | 28 | 29 | 30 | 31 | True 32 | end 33 | 34 | 35 | False 36 | end 37 | 0 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Doc/index.rst: -------------------------------------------------------------------------------- 1 | .. LEMON documentation master file, created by 2 | sphinx-quickstart on Mon May 6 12:08:02 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | LEMON: differential photometry 7 | ============================== 8 | 9 | LEMON is a scientific pipeline, written in Python_, that determines the changes in the brightness of astronomical objects over time and compiles their measurements into `light curves`_. The aim of this program is to make it possible to completely **reduce thousands of FITS images of time series** in a matter of only a few hours, requiring minimal user interaction. 10 | 11 | For example, to get the light curve of a transit of HAT-P-16b_: 12 | 13 | :: 14 | 15 | $ lemon astrometry data/*.fits HAT-P-16/ 16 | $ lemon mosaic HAT-P-16/*.fits HAT-P-16-mosaic.fits 17 | $ lemon photometry HAT-P-16-mosaic.fits HAT-P-16/*.fits phot.LEMONdB 18 | $ lemon diffphot phot.LEMONdB curves.LEMONdB 19 | 20 | The above commands produce, among many others, the following plot: 21 | 22 | .. only:: html 23 | 24 | .. thumbnail:: _static/HAT-P-16b-2014-10-31.svg 25 | :title: The light curve of a transit of exoplanet HAT-P-16b 26 | 27 | .. only:: latex 28 | 29 | .. image:: _static/HAT-P-16b-2014-10-31.jpg 30 | 31 | LEMON aims at taking most of the burden out of the astronomer, working out of the box with any set of images that conform to the `FITS standard`_. In most scenarios, the above four commands are enough to generate the high-precision light curves of all your astronomical objects. 32 | 33 | .. _Python: https://www.python.org/ 34 | .. _light curves: https://en.wikipedia.org/wiki/Light_curve 35 | .. _HAT-P-16b: http://exoplanet.eu/catalog/hat-p-16_b/ 36 | .. _FITS standard: http://fits.gsfc.nasa.gov/fits_standard.html 37 | 38 | User Guide 39 | ========== 40 | 41 | .. toctree:: 42 | :maxdepth: 2 43 | 44 | user/install 45 | user/quickstart 46 | -------------------------------------------------------------------------------- /test/test_data/filters/Cousins: -------------------------------------------------------------------------------- 1 | # A series of two-element tuples, one per line, containing two strings. 2 | # The first element must be the name of the Cousins photometric filter, 3 | # while the second is the letter that the Passband class must identify 4 | # when the former is parsed. 5 | 6 | "Cousins R", 'R' 7 | "Cousins I", 'I' 8 | "R (Cousins)", 'R' 9 | "I (Cousins)", 'I' 10 | "rCousins", 'R' 11 | "iCousins", 'I' 12 | 13 | # Suggested by @paramoreta at issue #81 14 | # https://github.com/vterron/lemon/issues/81 15 | 16 | "CousR", 'R' 17 | "Cous I", 'I' 18 | "CousinR", 'R' 19 | "Cousin I", 'I' 20 | 21 | # ... and, by extension: 22 | 23 | "Cous_R", 'R' 24 | "Cous-I", 'I' 25 | "r-Cousin", 'R' 26 | "Icousin", 'I' 27 | 28 | # Extremely convoluted test cases 29 | 30 | " coui_-_", 'I' 31 | "-- coUI_-", 'I' 32 | "- _cOui_", 'I' 33 | " _cOUI -", 'I' 34 | "-_ COUI_-", 'I' 35 | "_-_-couR", 'R' 36 | "-coU -_r-_", 'R' 37 | "-_---CouR_", 'R' 38 | "__CoU-_-r--_", 'R' 39 | " cousInS__i--_", 'I' 40 | "__- couSinsi__", 'I' 41 | " -__-cOUSInsi ", 'I' 42 | " ___CouSInS_i-", 'I' 43 | "-COUsInSi___-_", 'I' 44 | "____ COUsINsi-_ ", 'I' 45 | "__COUSiNsI-", 'I' 46 | "--cousinSr- -", 'R' 47 | "-- -coUSins-r-", 'R' 48 | " _cOusiNSR _", 'R' 49 | " _cOUsins-r ", 'R' 50 | " _COUsiNs- r__", 'R' 51 | "- COUsINS-_r -_-", 'R' 52 | "_icoU_-__", 'I' 53 | "_---icOu-__", 'I' 54 | " -i__COU-", 'I' 55 | "_I--_ (- cou_-)-_ ", 'I' 56 | "-Icou -", 'I' 57 | "_I_ cOU----", 'I' 58 | "I-Cou_- __", 'I' 59 | "--I _ -CoU--", 'I' 60 | "-I -CoU- ", 'I' 61 | "__ICOu_-", 'I' 62 | "-__-ICOU ", 'I' 63 | "__icousINs- -", 'I' 64 | " -icouSins--_-", 'I' 65 | "- icouSIns__", 'I' 66 | "i_ -_cOUsiNs_-", 'I' 67 | "-_i_--(_COUsiNS ) ", 'I' 68 | " __i_COUsInS__", 'I' 69 | "_- -I__(_ cOUSInS- -__)__-_-", 'I' 70 | "ICousins-", 'I' 71 | "__rcou--", 'R' 72 | "-r-- (_-cou__ -)--_-", 'R' 73 | "---rcOU-_ ", 'R' 74 | "_RcOU-__-_", 'R' 75 | "_RCOU -", 'R' 76 | " rCoUsInS___ ", 'R' 77 | " -_r_-( __COusInS_-__) -- _", 'R' 78 | "__rCOUsins-", 'R' 79 | " R-__ (--COusInS_)__-", 'R' 80 | -------------------------------------------------------------------------------- /Doc/_themes/LICENSE: -------------------------------------------------------------------------------- 1 | Modifications: 2 | 3 | Copyright (c) 2010 Kenneth Reitz. 4 | 5 | 6 | Original Project: 7 | 8 | Copyright (c) 2010 by Armin Ronacher. 9 | 10 | 11 | Some rights reserved. 12 | 13 | Redistribution and use in source and binary forms of the theme, with or 14 | without modification, are permitted provided that the following conditions 15 | are met: 16 | 17 | * Redistributions of source code must retain the above copyright 18 | notice, this list of conditions and the following disclaimer. 19 | 20 | * Redistributions in binary form must reproduce the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer in the documentation and/or other materials provided 23 | with the distribution. 24 | 25 | * The names of the contributors may not be used to endorse or 26 | promote products derived from this software without specific 27 | prior written permission. 28 | 29 | We kindly ask you to only use these themes in an unmodified manner just 30 | for Flask and Flask-related products, not for unrelated projects. If you 31 | like the visual style and want to use it for your own projects, please 32 | consider making some larger changes to the themes (such as changing 33 | font faces, sizes, colors or margins). 34 | 35 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 36 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 37 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 38 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 39 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 40 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 41 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 42 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 43 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 44 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 45 | POSSIBILITY OF SUCH DAMAGE. 46 | -------------------------------------------------------------------------------- /test/test_data/filters/Harris: -------------------------------------------------------------------------------- 1 | # A series of two-element tuples, one per line, containing two strings. 2 | # The first element must be the name of the Harris photometric filter, 3 | # while the second is the letter that the Passband class must identify 4 | # when the former is parsed. 5 | 6 | "Harris U", 'U' 7 | "Harris_B", 'B' 8 | "Harris-V", 'V' 9 | "R (Harris)", 'R' 10 | "I_(Harris)", 'I' 11 | "u-(harris)", 'U' 12 | "bHarris", 'B' 13 | "v-Harris", 'V' 14 | "r_harris", 'R' 15 | 16 | # These are *real* examples of Harris filters, kindly provided by 17 | # Javier Blasco, who found them among the FITS images of several 18 | # astronomical campaigns. 19 | 20 | "Harris R", 'R' 21 | "Harris V", 'V' 22 | 23 | # Extremely convoluted test cases 24 | 25 | "--HArRis_-_V_ __-", 'V' 26 | "__--IhaRriS--", 'I' 27 | "----I_haRRiS_", 'I' 28 | " v-__-_(hArris_)-_ _", 'V' 29 | "___ i -_(_haRris_)", 'I' 30 | "__u-_(_HARris_)-", 'U' 31 | "- __hARRiSb--", 'B' 32 | " _-hArriS-- u--", 'U' 33 | "_-r-_haRrIS_ -", 'R' 34 | "___V -_(____HArrIS__) -", 'V' 35 | "--_V( -haRris_ )", 'V' 36 | "__-HarRisb-", 'B' 37 | "-_-haRriSI_ ", 'I' 38 | "-HARRisR--", 'R' 39 | "_ _iHArris--_", 'I' 40 | "B__harRIs", 'B' 41 | " -HaRrisU", 'U' 42 | "BhArris-_", 'B' 43 | "--rHARRis ", 'R' 44 | "__ haRRISb__-_ ", 'B' 45 | " ihARRis---", 'I' 46 | "-_HArRISI_ ", 'I' 47 | " -HaRRis_ _-r_", 'R' 48 | "- _haRrisb__-", 'B' 49 | "_ -v-HarRis--__", 'V' 50 | "b---haRRIs- ", 'B' 51 | "___ vhaRRIS", 'V' 52 | "v_HaRrIs ", 'V' 53 | "--v- _HarrIs-__ _", 'V' 54 | "-harrISu--__", 'U' 55 | "V_ __Harris ", 'V' 56 | "- u-_-(-hARrIS-)_-", 'U' 57 | "b(_hArrIs _ -)", 'B' 58 | "-BHarRis--_ ", 'B' 59 | "__--uhARrIs__-", 'U' 60 | " -__bhARrIs_--", 'B' 61 | " _-HArRISv__---", 'V' 62 | " v-_-hARRIS-__ ", 'V' 63 | "B-Harris-", 'B' 64 | "_-R__hARrIs_-_-", 'R' 65 | "_-RhARRiS", 'R' 66 | "-_-_I--__(__HARRiS- _) - -", 'I' 67 | " -hARRISb -_-", 'B' 68 | "_ -_bhaRrIS-", 'B' 69 | "_-haRRis_- u", 'U' 70 | "_-HARrIsv-", 'V' 71 | " V- (_-_harRIs)- _", 'V' 72 | "_ -hARRis _u-_", 'U' 73 | "b--(---HArriS----_)- ", 'B' 74 | "I_-- HarRis_-", 'I' 75 | -------------------------------------------------------------------------------- /util/context.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Copyright (c) 2019 Victor Terron. All rights reserved. 4 | # 5 | # This file is part of LEMON. 6 | # 7 | # LEMON is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import contextlib 21 | import os 22 | import tempfile 23 | 24 | 25 | @contextlib.contextmanager 26 | def tmp_chdir(path): 27 | """A context manager to temporarily change the working directory. 28 | 29 | This is a rather simple context manager to change the current working 30 | directory within a with statement, restoring the original one upon exit. 31 | 32 | """ 33 | 34 | cwd = os.getcwd() 35 | try: 36 | os.chdir(path) 37 | yield 38 | finally: 39 | os.chdir(cwd) 40 | 41 | 42 | @contextlib.contextmanager 43 | def tempinput(data): 44 | """A context manager to work with StringIO-like temporary files. 45 | 46 | The open() built-in command only takes filenames, so we cannot feed to it a 47 | StringIO. This context manager writes 'data' to a temporary file, ensuring 48 | that it is cleaned up afterwards. In this manner, we can work in a similar 49 | manner to what we would have done with StringIO, as the data is written to 50 | disk only temporarily and transparently to us. 51 | 52 | with tempinput('some data\more data') as path: 53 | open(path) 54 | 55 | Taken from Martijn Pieters's answer on Stack Overflow: 56 | [URL] https://stackoverflow.com/a/11892712/184363 57 | 58 | """ 59 | 60 | fd, path = tempfile.mkstemp() 61 | os.write(fd, data) 62 | os.close(fd) 63 | yield path 64 | os.unlink(path) 65 | -------------------------------------------------------------------------------- /Misc/ACKS: -------------------------------------------------------------------------------- 1 | Acknowledgements 2 | ---------------- 3 | 4 | This list, hopefully complete and in chronological order, is a compendium of 5 | all the people who contributed and whom I would like to thank. I am deeply 6 | grateful for the ideas, enthusiastic questions, thorough answers and moral 7 | support received during these years, and look forward to the day on which 8 | somebody finally submits a patch. See you at GitHub! -- Víctor 9 | 10 | Matilde Fernández, aka The Mastermind (also, the Principal Investigator) 11 | Nuria Huélamo, a loyal companion at the Fortress of Solitude (1.23m CAHA) 12 | Nicolás Cardiel, for his help on error propagation (via email) 13 | Frank Valdes, on qphot and stars saturation (the IRAF.net forums) 14 | 15 | Michael Newberry, 16 | Jim Jones, 17 | Gary Walker and 18 | Bradley Walter [AAVSO-Photometry mailing list] 19 | 20 | Hernán J. González, also helped on error propagation (at Math Stack Exchange) 21 | César Husillos, for his help on image alignment through cross-correlation 22 | Ilse Plauchu, for her useful comments on Gaussian distribution fitting 23 | Emmanuel Bertin, for the ample and diligent help with SExtractor 24 | Javier Blasco, our most enthusiastic user, for such thoughtful suggestions 25 | Pablo Ramírez, the second person to ever use LEMON — that we know of 26 | Sofía León, for designing for us such a marvelous LEMON icon 27 | José Enrique Ruiz, for suggesting that we set extglob 28 | Jean-Baptiste Marquette, for reporting a bug under Mac OS X 29 | Scott Engle, for the many bugs diligently reported 30 | Juha Sahakangas, for the workaround for GTK+ bug 632538 31 | Felipe Gallego, for reporting a bug in get__version__() 32 | Muhammad Yusuf, for reporting bugs in the unit tests 33 | Pablo Galindo, our most relentless bug hunter 34 | Jorge Gómez, for reporting a bug with PyFITS > 3.2 35 | Ted Groner, for reporting a bug in the unit tests on 32-bit IRAF 36 | William Schoenell, for helping debug the aforementioned bug 37 | Emilio Falco, for the many bugs perseveringly reported 38 | Hervé Bouy, for the help and advice on Astrometry.net 39 | Michael Hlabathe, for reporting a bug in the unit tests. 40 | Derek O'Keeffe, for the bug fix for load_coordinates(). 41 | Ana Karla Díaz (@akdiaz), for the help debugging `juicer`. 42 | -------------------------------------------------------------------------------- /sextractor/sextractor.nnw: -------------------------------------------------------------------------------- 1 | NNW 2 | # Neural Network Weights for the SExtractor star/galaxy classifier (V1.3) 3 | # inputs: 9 for profile parameters + 1 for seeing. 4 | # outputs: ``Stellarity index'' (0.0 to 1.0) 5 | # Seeing FWHM range: from 0.025 to 5.5'' (images must have 1.5 < FWHM < 5 pixels) 6 | # Optimized for Moffat profiles with 2<= beta <= 4. 7 | 8 | 3 10 10 1 9 | 10 | -1.56604e+00 -2.48265e+00 -1.44564e+00 -1.24675e+00 -9.44913e-01 -5.22453e-01 4.61342e-02 8.31957e-01 2.15505e+00 2.64769e-01 11 | 3.03477e+00 2.69561e+00 3.16188e+00 3.34497e+00 3.51885e+00 3.65570e+00 3.74856e+00 3.84541e+00 4.22811e+00 3.27734e+00 12 | 13 | -3.22480e-01 -2.12804e+00 6.50750e-01 -1.11242e+00 -1.40683e+00 -1.55944e+00 -1.84558e+00 -1.18946e-01 5.52395e-01 -4.36564e-01 -5.30052e+00 14 | 4.62594e-01 -3.29127e+00 1.10950e+00 -6.01857e-01 1.29492e-01 1.42290e+00 2.90741e+00 2.44058e+00 -9.19118e-01 8.42851e-01 -4.69824e+00 15 | -2.57424e+00 8.96469e-01 8.34775e-01 2.18845e+00 2.46526e+00 8.60878e-02 -6.88080e-01 -1.33623e-02 9.30403e-02 1.64942e+00 -1.01231e+00 16 | 4.81041e+00 1.53747e+00 -1.12216e+00 -3.16008e+00 -1.67404e+00 -1.75767e+00 -1.29310e+00 5.59549e-01 8.08468e-01 -1.01592e-02 -7.54052e+00 17 | 1.01933e+01 -2.09484e+01 -1.07426e+00 9.87912e-01 6.05210e-01 -6.04535e-02 -5.87826e-01 -7.94117e-01 -4.89190e-01 -8.12710e-02 -2.07067e+01 18 | -5.31793e+00 7.94240e+00 -4.64165e+00 -4.37436e+00 -1.55417e+00 7.54368e-01 1.09608e+00 1.45967e+00 1.62946e+00 -1.01301e+00 1.13514e-01 19 | 2.20336e-01 1.70056e+00 -5.20105e-01 -4.28330e-01 1.57258e-03 -3.36502e-01 -8.18568e-02 -7.16163e+00 8.23195e+00 -1.71561e-02 -1.13749e+01 20 | 3.75075e+00 7.25399e+00 -1.75325e+00 -2.68814e+00 -3.71128e+00 -4.62933e+00 -2.13747e+00 -1.89186e-01 1.29122e+00 -7.49380e-01 6.71712e-01 21 | -8.41923e-01 4.64997e+00 5.65808e-01 -3.08277e-01 -1.01687e+00 1.73127e-01 -8.92130e-01 1.89044e+00 -2.75543e-01 -7.72828e-01 5.36745e-01 22 | -3.65598e+00 7.56997e+00 -3.76373e+00 -1.74542e+00 -1.37540e-01 -5.55400e-01 -1.59195e-01 1.27910e-01 1.91906e+00 1.42119e+00 -4.35502e+00 23 | 24 | -1.70059e+00 -3.65695e+00 1.22367e+00 -5.74367e-01 -3.29571e+00 2.46316e+00 5.22353e+00 2.42038e+00 1.22919e+00 -9.22250e-01 -2.32028e+00 25 | 26 | 27 | 0.00000e+00 28 | 1.00000e+00 29 | -------------------------------------------------------------------------------- /test/test_customparser.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2015 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | # LEMON modules 22 | from test import unittest 23 | import customparser 24 | 25 | 26 | class CustomParserTest(unittest.TestCase): 27 | def test_additional_options_callback(self): 28 | 29 | parser = customparser.get_parser(None) 30 | parser.add_option( 31 | "-o", 32 | action="callback", 33 | type="str", 34 | dest="parsed_options", 35 | default={}, 36 | callback=customparser.additional_options_callback, 37 | ) 38 | 39 | arguments = [ 40 | "-o", 41 | "-v", 42 | "-o", 43 | "--batch", 44 | "-o", 45 | "-r=2", 46 | "-o", 47 | "--sigma 3", 48 | "-o=-h", 49 | "-o=--invert", 50 | "-o=-d=cubic", 51 | "-o=--no-verify = True", 52 | ] 53 | 54 | (options, args) = parser.parse_args(args=arguments) 55 | options = options.parsed_options 56 | self.assertIsNone(options["-v"]) 57 | self.assertIsNone(options["--batch"]) 58 | self.assertEqual(options["-r"], "2") 59 | self.assertEqual(options["--sigma"], "3") 60 | self.assertIsNone(options["-h"]) 61 | self.assertIsNone(options["--invert"]) 62 | self.assertEqual(options["-d"], "cubic") 63 | self.assertEqual(options["--no-verify"], "True") 64 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 180 5 | 6 | # Number of days of inactivity before a stale Issue or Pull Request is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 30 9 | 10 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 11 | exemptLabels: 12 | - WIP 13 | - work in progress 14 | - important 15 | - locked 16 | - feature-request 17 | - pinned 18 | - security 19 | 20 | # Set to true to ignore issues in a project (defaults to false) 21 | exemptProjects: false 22 | 23 | # Set to true to ignore issues in a milestone (defaults to false) 24 | exemptMilestones: false 25 | 26 | # Label to use when marking as stale 27 | staleLabel: stale 28 | 29 | # Comment to post when marking as stale. Set to `false` to disable 30 | markComment: > 31 | Thanks for contributing to this issue. As it has been 180 days since the last activity, we'll be automatically closing the issue soon. This is often because the request was already solved in some way and it just wasn't updated or it's just no longer relevant. If that's not the case, please respond here within the next 30 days to keep it open. You can read more here: https://github.com/probot/stale#is-closing-stale-issues-really-a-good-idea. 32 | 33 | # Comment to post when removing the stale label. 34 | # unmarkComment: > 35 | # Your comment here. 36 | 37 | # Comment to post when closing a stale Issue or Pull Request. 38 | #closeComment: > 39 | # Your comment here. 40 | 41 | # Limit the number of actions per hour, from 1-30. Default is 30 42 | limitPerRun: 30 43 | 44 | # Limit to only `issues` or `pulls` 45 | only: issues 46 | 47 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 48 | # pulls: 49 | # daysUntilStale: 30 50 | # markComment: > 51 | # This pull request has been automatically marked as stale because it has not had 52 | # recent activity. It will be closed if no further activity occurs. Thank you 53 | # for your contributions. 54 | 55 | # issues: 56 | # exemptLabels: 57 | # - confirmed 58 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | # Don't test against 2.6. We use apt-get to install many dependencies, but 5 | # Python system packages only include Python 2.7 libraries on Ubuntu 12.04. 6 | - "2.7" 7 | 8 | env: 9 | global: 10 | - WASP10_DATA=$HOME/WASP10_data 11 | - secure: "EEA1YcB7rIBu5jQipl89lGxmRvk1IZ158i0J1PC1L7tFaq6hK7g1nf4rJWjrwYBS4gZT8z1fA8eiP3KJ8hRmh4S9cYq6FD/PpwknFo2QysoUrS2d2XxK92nfJSkjJ7Gx5F1fVqtqITda5uJwhEVGZdgkCFv5JGvJcdGWHZVyNAc=" 12 | 13 | virtualenv: 14 | system_site_packages: true 15 | 16 | git: 17 | depth: false 18 | 19 | cache: 20 | directories: 21 | - $WASP10_DATA 22 | 23 | before_install: 24 | - sudo apt-get update 25 | - sudo apt-get install -y alien realpath 26 | - sudo apt-get install -y git python-pip csh realpath 27 | - sudo apt-get build-dep -y python-matplotlib python-scipy 28 | - sudo easy_install -U distribute 29 | - sudo pip install "numpy>=1.7.1" 30 | - sudo pip install -r pre-requirements.txt 31 | # 'travis_wait': avoid timeout during SciPy installation 32 | - travis_wait 45 sudo pip install -r requirements.txt 33 | # Install IRAF and SExtractor 34 | - sudo ./ci/travis-setup.sh 35 | - python ./setup.py 36 | 37 | - export iraf=/iraf/iraf/ 38 | - export IRAFARCH=linux64 39 | - export PATH=$(pwd):$PATH 40 | 41 | install: true 42 | 43 | jobs: 44 | include: 45 | # Download integration test data to the cached directory. 46 | - stage: Setup 47 | name: "Prepare test data" 48 | script: ./test/integration/wasp10b-download.sh $WASP10_DATA 49 | 50 | - stage: Tests 51 | name: "Unit Tests" 52 | script: ./run_tests.py 53 | 54 | # Exercise the multiprocessing logic of `photometry` and `diffphot`. 55 | # The goal is to make sure that light cuves computed in parallel are 56 | # absolutely independent of each other. 57 | 58 | - script: NCORES=1 ./test/integration/wasp10b.py 59 | name: "Integration Test (cores = 1)" 60 | 61 | - script: NCORES=2 ./test/integration/wasp10b.py 62 | name: "Integration Test (cores = 2)" 63 | 64 | - script: NCORES=3 ./test/integration/wasp10b.py 65 | name: "Integration Test (cores = 3)" 66 | 67 | - script: NCORES=4 ./test/integration/wasp10b.py 68 | name: "Integration Test (cores = 4)" 69 | 70 | notifications: 71 | email: 72 | on_success: change 73 | on_failure: change 74 | -------------------------------------------------------------------------------- /test/test_data/filters/Halpha: -------------------------------------------------------------------------------- 1 | # A series of two-element tuples, one per line, containing two strings. 2 | # The first element must be the name of the H-alpha photometric filter, 3 | # while the second is the wavelength of the filter that the Passband 4 | # class must identify when the former is parsed. 5 | 6 | 'H6341', '6341' 7 | 'H6342/73', '6342' 8 | 'Ha0043', '0043' 9 | 'HA_6344/74', '6344' 10 | 'Halpha-6345', '6345' 11 | 'Halpha_6346/75', '6346' 12 | 13 | # These are *real* examples of Halpha filters, provided by Javier Blasco, 14 | # who found them among the FITS images of several astronomical campaigns. 15 | 16 | 'H6563', '6563' 17 | 'H6607', '6607' 18 | 'H6650', '6650' 19 | 'H6652', '6652' 20 | 'H6700', '6700' 21 | 'H6702', '6702' 22 | 'Ha6607', '6607' 23 | 'Ha6652', '6652' 24 | 'HALPHA6563', '6563' 25 | 26 | # Extremely convoluted test cases 27 | 28 | " __H_-__ A-_--0817/91", '0817' 29 | "H_-_ A0825/63", '0825' 30 | "_--H - _-A __0939", '0939' 31 | "-_ -ha -_ 1096", '1096' 32 | "hA_1288/75", '1288' 33 | "__-H A--_1518/13", '1518' 34 | "-_h a_1611", '1611' 35 | "H_--a__--_1807", '1807' 36 | " -hA 2291/42", '2291' 37 | "_--H- A 2683", '2683' 38 | " - H-_A_ -_2731/70", '2731' 39 | " --_h--_a__3137/61", '3137' 40 | " H_-_A_3786/51", '3786' 41 | "-- _H _--a_3970", '3970' 42 | "-- h _-a4409", '4409' 43 | "_H-a-_- _5354", '5354' 44 | "- h_ -A-5754/38", '5754' 45 | " -___H-a-6917/17", '6917' 46 | "-h_-a-7062/24", '7062' 47 | "_- _hA --7324", '7324' 48 | "_h-_a---_7512", '7512' 49 | "-__- h- _a_7758", '7758' 50 | "hA ___8816/37", '8816' 51 | " __h-a9032", '9032' 52 | "- __h-A-9079", '9079' 53 | "-_h- a9605/99", '9605' 54 | "_--ha 9727/84", '9727' 55 | "H-alpha0189", '0189' 56 | "- _h -alpHA0305/93", '0305' 57 | "_ -__H_aLpHa 1416", '1416' 58 | "-_-H- _ _ALpha_-_1664/09", '1664' 59 | " -hALpHA1673/77", '1673' 60 | "_- H_ alPHa _-2559", '2559' 61 | "_--H_ALphA -0335", '0335' 62 | "-___h_ -_AlPHA-3584", '3584' 63 | "--HaLPha-_-_-4043/96", '4043' 64 | "___ H __alphA--_4766", '4766' 65 | "_ _H-_AlPHA_5061", '5061' 66 | "--H AlphA__-5389", '5389' 67 | "H-_ALPhA--5571/04", '5571' 68 | "H__ _aLphA___-5742", '5742' 69 | "_-H-___aLpha__6506", '6506' 70 | "h- _ alpHA_6683", '6683' 71 | " -__H_AlPha0008", '0008' 72 | " -_h-_AlPHa_ 6867/02", '6867' 73 | "___ H-- ALpha _7433", '7433' 74 | " _-H_Alpha_ -7571", '7571' 75 | " HALpHa-8515", '8515' 76 | " -_H-_-aLphA8622", '8622' 77 | "--_-h___ alPHA_9253/17", '9253' 78 | -------------------------------------------------------------------------------- /ci/travis-setup.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Install LEMON dependencies in the Travis CI environment OS 4 | # Author: Victor Terron (c) 2013 5 | # License: GNU GPLv3 6 | 7 | set -e # exit if any statement returns a non-true return value 8 | set -u # any attempt to use an undefined variable is an error 9 | set -x # print commands and their arguments as they are executed 10 | 11 | REGEXP=".*64$" 12 | HARDWARE_NAME=`uname -m` 13 | 14 | echo -n "Machine architecture: " 15 | if [[ $HARDWARE_NAME =~ $REGEXP ]]; then 16 | ARCH_64_BITS=1; 17 | echo "64-bit" 18 | else 19 | ARCH_64_BITS=0; 20 | echo "32-bit" 21 | fi 22 | 23 | CWD=$(pwd) 24 | 25 | cd ~ 26 | 27 | ########### Install IRAF ################ 28 | 29 | IRAF_TAR="v2.16.1+2018.11.01.tar.gz" 30 | IRAF_SERVER="https://github.com/iraf-community/iraf/archive/" 31 | IRAF_URL=$IRAF_SERVER$IRAF_TAR 32 | 33 | IRAF_DIR="/iraf/iraf/" 34 | mkdir -p $IRAF_DIR 35 | IRAF_DIR=`realpath $IRAF_DIR` 36 | cd $IRAF_DIR 37 | 38 | wget $IRAF_URL 39 | tar xfz $IRAF_TAR 40 | mv iraf-2.16.1-2018.11.01/* . 41 | 42 | # https://iraf-community.github.io/install 43 | yes "" | ./install --system 44 | make linux64 45 | make sysgen 2>&1 | tee build.log 46 | ./test/run_tests 47 | rm $IRAF_TAR 48 | 49 | ########### Install SExtractor ########### 50 | 51 | cd ~ 52 | 53 | if [[ $ARCH_64_BITS == 1 ]]; then 54 | SEXTRACTOR_RPM="sextractor-2.19.5-1.x86_64.rpm" 55 | else 56 | SEXTRACTOR_RMP="sextractor-2.19.5-1.i386.rpm" 57 | fi 58 | 59 | SEXTRACTOR_SERVER="http://www.astromatic.net/download/sextractor/" 60 | SEXTRACTOR_URL=$SEXTRACTOR_SERVER$SEXTRACTOR_RPM 61 | wget $SEXTRACTOR_URL 62 | alien -i $SEXTRACTOR_RPM 63 | rm $SEXTRACTOR_RPM 64 | 65 | cd $CWD # back to the LEMON directory 66 | 67 | # The unit tests use several FITS images that are downloaded from the 68 | # STScI Digitized Sky Survey to test/test_data/fits/. Be considerate 69 | # and, instead of downloading them every time the tests are run, keep 70 | # a copy on our server. 71 | 72 | TEST_FITS_DIR="test/test_data/fits/" 73 | DSS_IMAGES_URL="https://github.com/vterron/lemon-test-data/raw/master/DSS/" 74 | 75 | DSS_FILENAMES=( 76 | "Barnard's_Star.fits" 77 | "IC_5070.fits" 78 | "IC_5146.fits" 79 | "Messier_92.fits" 80 | "NGC_2264.fits" 81 | "Orion.fits" 82 | "RMC_136.fits" 83 | "Serpens.fits" 84 | "Trapezium.fits" 85 | "Trumpler_37.fits" 86 | ) 87 | 88 | mkdir -p $TEST_FITS_DIR 89 | cd $TEST_FITS_DIR 90 | 91 | echo "Downloading test FITS images to $(pwd)" 92 | for filename in "${DSS_FILENAMES[@]}"; do 93 | wget $DSS_IMAGES_URL$filename -O $filename; 94 | done; 95 | 96 | cd $CWD 97 | 98 | exit 0 99 | -------------------------------------------------------------------------------- /juicer/gui/finding-chart-dialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 5 7 | normal 8 | False 9 | 10 | 11 | True 12 | vertical 13 | 2 14 | 15 | 16 | True 17 | vertical 18 | 19 | 20 | True 21 | 22 | 23 | 24 | 25 | 26 | 0 27 | 28 | 29 | 30 | 31 | True 32 | 33 | 34 | 35 | 36 | 37 | False 38 | 1 39 | 40 | 41 | 42 | 43 | 1 44 | 45 | 46 | 47 | 48 | True 49 | True 50 | True 51 | center 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | False 64 | end 65 | 0 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /sextractor/sextractor.param: -------------------------------------------------------------------------------- 1 | NUMBER 2 | 3 | X_IMAGE 4 | Y_IMAGE 5 | 6 | ALPHA_SKY 7 | DELTA_SKY 8 | 9 | XWIN_IMAGE # parameter needed by SCAMP, do _not_ comment! 10 | YWIN_IMAGE # parameter needed by SCAMP, do _not_ comment! 11 | 12 | ERRAWIN_IMAGE # parameter needed by SCAMP, do _not_ comment! 13 | ERRBWIN_IMAGE # parameter needed by SCAMP, do _not_ comment! 14 | ERRTHETAWIN_IMAGE # parameter needed by SCAMP, do _not_ comment! 15 | 16 | # SCAMP advises (but it is not mandatory) to include these fields 17 | # in the catalog. They are used to filter out blended and corrupted 18 | # detections, and also in order to identify small glitches or 19 | # extended objects. 20 | 21 | FLAGS 22 | #FLAGS_WEIGHT # "WARNING: FLAGS_WEIGHT catalog parameter unknown" ? 23 | #IMAFLAGS_ISO # This one needed to use weight-maps (from flag.fits) 24 | FLUX_RADIUS 25 | 26 | 27 | FLUX_ISO # SNR is defined as FLUX_ISO divided by FLUXERR_APER 28 | FLUXERR_ISO # # (that is, the signal divided by the noise ;-) 29 | #MAG_ISO 30 | #MAGERR_ISO 31 | 32 | #FLUX_ISOCOR 33 | #FLUXERR_ISOCOR 34 | #MAG_ISOCOR 35 | #MAGERR_ISOCOR 36 | 37 | #FLUX_APER(1) 38 | #FLUXERR_APER(1) 39 | #MAG_APER(1) 40 | #MAGERR_APER(1) 41 | 42 | FLUX_AUTO # parameter needed by SCAMP, do _not_ comment out! 43 | FLUXERR_AUTO # parameter needed by SCAMP, do _not_ comment out! 44 | MAG_AUTO # Automatic aperture magnitudes 45 | MAGERR_AUTO 46 | 47 | #FLUX_BEST 48 | #FLUXERR_BEST 49 | #MAG_BEST 50 | #MAGERR_BEST 51 | 52 | #KRON_RADIUS 53 | #BACKGROUND 54 | 55 | #THRESHOLD 56 | #MU_THRESHOLD 57 | FLUX_MAX 58 | #MU_MAX 59 | 60 | 61 | ISOAREAF_IMAGE # Isophotal area (filtered) above Detection threshold (Pix^2) 62 | ISOAREAF_WORLD # Isophotal area (filtered) above Detection threshold (Deg^2) 63 | 64 | 65 | #XMIN_IMAGE 66 | #YMIN_IMAGE 67 | #XMAX_IMAGE 68 | #YMAX_IMAGE 69 | 70 | 71 | #X_WORLD 72 | #Y_WORLD 73 | 74 | #ALPHA_J2000 75 | #DELTA_J2000 76 | #ALPHA_B1950 77 | #DELTA_B1950 78 | 79 | #X2_IMAGE 80 | #Y2_IMAGE 81 | #XY_IMAGE 82 | #X2_WORLD 83 | #Y2_WORLD 84 | #XY_WORLD 85 | 86 | #CXX_IMAGE 87 | #CYY_IMAGE 88 | #CXY_IMAGE 89 | #CXX_WORLD 90 | #CYY_WORLD 91 | #CXY_WORLD 92 | 93 | #A_IMAGE 94 | #B_IMAGE 95 | #A_WORLD 96 | #B_WORLD 97 | 98 | #THETA_IMAGE 99 | #THETA_WORLD 100 | #THETA_SKY 101 | #THETA_J2000 102 | #THETA_B1950 103 | 104 | # Do _not_ comment! 105 | ELONGATION 106 | #ELLIPTICITY 107 | 108 | #ERRX2_IMAGE 109 | #ERRY2_IMAGE 110 | #ERRXY_IMAGE 111 | #ERRX2_WORLD 112 | #ERRY2_WORLD 113 | #ERRXY_WORLD 114 | 115 | #ERRCXX_IMAGE 116 | #ERRCYY_IMAGE 117 | #ERRCXY_IMAGE 118 | #ERRCXX_WORLD 119 | #ERRCYY_WORLD 120 | #ERRCXY_WORLD 121 | 122 | #ERRA_IMAGE 123 | #ERRB_IMAGE 124 | #ERRA_WORLD 125 | #ERRB_WORLD 126 | 127 | #ERRTHETA_IMAGE 128 | #ERRTHETA_WORLD 129 | #ERRTHETA_SKY 130 | #ERRTHETA_J2000 131 | #ERRTHETA_B1950 132 | 133 | #FWHM_IMAGE 134 | #FWHM_WORLD 135 | 136 | #ISO0 137 | #ISO1 138 | #ISO2 139 | #ISO3 140 | #ISO4 141 | #ISO5 142 | #ISO6 143 | #ISO7 144 | 145 | #NIMAFLAGS_ISO(1) 146 | 147 | #CLASS_STAR 148 | #VIGNET(5,5) 149 | -------------------------------------------------------------------------------- /test/test_data/filters/2MASS: -------------------------------------------------------------------------------- 1 | # A series of two-element tuples, one per line, containing two strings. 2 | # The first element must be the name of the 2MASS photometric filter, 3 | # while the second is the letter that the Passband class must identify 4 | # when the former is parsed. 5 | 6 | "2MASS J", 'J' 7 | "2MASS h", 'H' 8 | "2MASS Ks", 'KS' 9 | "2mass_J", 'J' 10 | "H (2MASS)", 'H' 11 | "KS (2MASS)", 'KS' 12 | "j2MASS", 'J' 13 | "2MASSh", 'H' 14 | "2massKS", 'KS' 15 | 16 | # Extremely convoluted test cases 17 | 18 | " _ 2masS-_H_", 'H' 19 | " 2maSS __--H -_", 'H' 20 | "-2mASsH- -_-", 'H' 21 | "-2mASSh-", 'H' 22 | "-_2MaSS H--", 'H' 23 | " -__2MAsSH-_", 'H' 24 | "--2MASS_h-_", 'H' 25 | "---_2MASSH_____", 'H' 26 | "__2massj_-", 'J' 27 | "-_2masS_--j-_", 'J' 28 | "- --2MasS __J-", 'J' 29 | " -_2MASSj-_", 'J' 30 | "---2MASSJ__ -", 'J' 31 | " 2masSkS-", 'KS' 32 | "-_2maSsKS _", 'KS' 33 | "- _2mAsSkS_", 'KS' 34 | "___-2MasSks_", 'KS' 35 | " 2MAss_ Ks--", 'KS' 36 | "_- 2m--h- -_-", 'H' 37 | "-2m--_-h- -", 'H' 38 | "__--_2m-_--H-", 'H' 39 | " _-_2M--- h_", 'H' 40 | "__ 2M-h-", 'H' 41 | "__-_2Mh__-", 'H' 42 | "_2M_h _-_", 'H' 43 | "-2MH-", 'H' 44 | "-_ 2mj-__", 'J' 45 | " _2M_ _j -", 'J' 46 | "___2M _ j -__", 'J' 47 | "--_- 2Mj", 'J' 48 | "--2Mj _", 'J' 49 | " _2MJ____", 'J' 50 | " ---2MJ ", 'J' 51 | "___2MJ_", 'J' 52 | "- 2m_KS-- _", 'KS' 53 | " __2M-ks _---", 'KS' 54 | " -2Mks-", 'KS' 55 | "-2M--_ks__--", 'KS' 56 | "__2M_kS -___", 'KS' 57 | "- 2M-KS-", 'KS' 58 | "-__2M--KS --", 'KS' 59 | "____-h2mass---", 'H' 60 | "_- --h _ __(- 2masS-)-", 'H' 61 | "__h__ (_-2mAss_)_", 'H' 62 | "__--h_ _ (_---2mAsS__- )_ -", 'H' 63 | "-h2mAsS - _-", 'H' 64 | "-_h-_2MaSS__", 'H' 65 | "_---H_ (_- 2mass)-__-", 'H' 66 | "-- H -_ _(_-2MAsS---) ", 'H' 67 | "--H2MASS -_ ", 'H' 68 | "_--h-2m-_-_-", 'H' 69 | "-h__- (_2m-)_", 'H' 70 | "-h--__ 2m", 'H' 71 | "_-__-h2M_ _", 'H' 72 | "- --h2M--", 'H' 73 | "-h_ _2M_", 'H' 74 | "-h2M-", 'H' 75 | " --_-H2m_", 'H' 76 | "-H--2M ---", 'H' 77 | "__j (__ -2masS ) -", 'J' 78 | "___-j2mASS", 'J' 79 | " - _-j-_2Mass-- _", 'J' 80 | "- j2MasS", 'J' 81 | "_-_j____2MASs_--", 'J' 82 | "_-_J_--2masS_-_", 'J' 83 | " -J_2mAss--_-", 'J' 84 | "--_J__-( _2mASS- --)-_", 'J' 85 | "___J_(2Mass_- )-", 'J' 86 | "__ _J-(----2MASS-_ )-_--", 'J' 87 | " - -j-2m--", 'J' 88 | "_j -__ (_-2m__ )-", 'J' 89 | "--j-__ 2m-___", 'J' 90 | " j2M-", 'J' 91 | "____j_ -2M -- ", 'J' 92 | "-_j2M__", 'J' 93 | " -J2m-_", 'J' 94 | " J_- -_(----2m-) -", 'J' 95 | " J-_2m-", 'J' 96 | "-_ J- (-2m- _ )_- ", 'J' 97 | "__J_ -( --2M- _)__", 'J' 98 | "_-J2M_- ", 'J' 99 | "_-J2M_-_-", 'J' 100 | "_ ks(- - 2Mass--) _", 'KS' 101 | "-_ks__- (-2Mass__-)-", 'KS' 102 | " -- ks _ ( - 2MAss___)- _", 'KS' 103 | "- _-kS -( _-2MaSs_--)_ _ ", 'KS' 104 | "__ Ks -_2MAsS-", 'KS' 105 | " ___-KS-(__-_2mASs-)_", 'KS' 106 | " KS- 2MASs -", 'KS' 107 | "-_-ks2M--- ", 'KS' 108 | " _--kS- _ 2m-", 'KS' 109 | " -_ kS2M-__-", 'KS' 110 | "_-_kS-- _(- 2M-)-", 'KS' 111 | "- -kS2M-___", 'KS' 112 | "--kS2M __-", 'KS' 113 | "__Ks2M-", 'KS' 114 | "_ KS _2m _", 'KS' 115 | "_KS2m_-", 'KS' 116 | "-__KS_- (-___-2m_)---", 'KS' 117 | "-_ KS2M_-", 'KS' 118 | -------------------------------------------------------------------------------- /juicer/gui/about.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 5 7 | True 8 | center-on-parent 9 | img/lemon.png 10 | dialog 11 | True 12 | True 13 | True 14 | False 15 | Lemon Juicer 16 | x.x.x 17 | Copyright © YYYY Víctor Terrón & Matilde Fernández 18 | Institute of Astrophysics of Andalusia, IAA-CSIC 19 | Lemon Juicer is a scientific tool for automated variable stars detection and analysis and part of the LEMON (Long-term Photometric Monitoring) pipeline. 20 | https://github.com/vterron/lemon 21 | Lemon Juicer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 22 | 23 | Lemon Juicer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 24 | 25 | You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. 26 | Víctor Terrón <vterron@iaa.es> 27 | Matilde Fernández <matilde@iaa.es> 28 | Sofía León (lemon icon) 29 | Asher Abbasi (compass icon) 30 | Artwork license: CC BY-NC-ND 3.0 31 | img/lemon.png 32 | True 33 | 34 | 35 | True 36 | vertical 37 | 2 38 | 39 | 40 | 41 | 42 | 43 | True 44 | end 45 | 46 | 47 | False 48 | end 49 | 0 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /test/test_data/filters/Gunn: -------------------------------------------------------------------------------- 1 | # A series of two-element tuples, one per line, containing two strings. 2 | # The first element must be the name of the Gunn photometric filter, 3 | # while the second is the letter that the Passband class must identify 4 | # when the former is parsed. 5 | 6 | "U Gunn", 'U' 7 | "V Gunn", 'V' 8 | "G (Gunn)", 'G' 9 | "R (Gunn)", 'R' 10 | 11 | "uGunn", 'U' 12 | "vGunn", 'V' 13 | "GunnG", 'G' 14 | "GunnR", 'R' 15 | 16 | # These are *real* examples of Gunn filters, provided by Javier Blasco, 17 | # who found them among the FITS images of several astronomical campaigns. 18 | 19 | "RGun", 'R' 20 | "rgunn", 'R' 21 | "rGunn", 'R' 22 | "Rgunn", 'R' 23 | 24 | # Extremely convoluted test cases 25 | 26 | "--_-g_( _-_gUn- )", 'G' 27 | "_ g Gun- -", 'G' 28 | "_ __Ggun_ -", 'G' 29 | "_-_G_-_gun-__", 'G' 30 | "-_ G__-_(GUN-__- )-_-", 'G' 31 | "__ggUnN---", 'G' 32 | " _g--_GunN_---_", 'G' 33 | "_-gGuNN-", 'G' 34 | "-g---(_--GUnn _-)___-", 'G' 35 | "__ G___GUnn --_", 'G' 36 | " guNg__-", 'G' 37 | "--guN -- g ", 'G' 38 | "__-gUNg---", 'G' 39 | "_ _-_Gung__", 'G' 40 | "_-GuNg__-", 'G' 41 | "__GUNG- ", 'G' 42 | " gunng-_ ", 'G' 43 | "- _gunNg ", 'G' 44 | " guNnG _", 'G' 45 | "_GunN__ -_G -", 'G' 46 | " --guNnR - ", 'R' 47 | " guNN----R__--", 'R' 48 | "-_gUNNr ", 'R' 49 | "_Gunnr--", 'R' 50 | "_- GunNr__", 'R' 51 | " -GUnN r -_", 'R' 52 | "_ -gunN--_U_", 'U' 53 | "_- _gUnnU ---", 'U' 54 | "Gunn___ u_--", 'U' 55 | " ____GunNu_-", 'U' 56 | "-GUnNU _", 'U' 57 | "-gunNV -", 'V' 58 | " guNn-__v --", 'V' 59 | "-__ guNn- v___", 'V' 60 | "_-guNN_- V__", 'V' 61 | "--guNR____", 'R' 62 | "_-- gUNr", 'R' 63 | "_gUn u-- __", 'U' 64 | "-___GUnU___", 'U' 65 | "___-GUN- U__-", 'U' 66 | "_-__-gun-_v__", 'V' 67 | "_--gunV ", 'V' 68 | "_Gun_v__-", 'V' 69 | "_GUnV--", 'V' 70 | "_GUNv-", 'V' 71 | " GUNV-__", 'V' 72 | "_GUNV_-_--", 'V' 73 | "-_--GUNV", 'V' 74 | "- rGuNn --_", 'R' 75 | "-r-_-_GUnn -__", 'R' 76 | "- _r (_-_GUNN_ -)_ - ", 'R' 77 | "-_R- (_guNn-)", 'R' 78 | "-RguNN ", 'R' 79 | "- -_RGUnn _-_", 'R' 80 | "_- _R___GUnN_- ", 'R' 81 | " r-guN ", 'R' 82 | "_-_-rguN__-", 'R' 83 | "- _-r_( _guN_-)-_ _", 'R' 84 | " r_(-__-gUn )-", 'R' 85 | "-_-rgUn---", 'R' 86 | "-__rgUN-", 'R' 87 | "___rGuN_-_", 'R' 88 | "-_-_-rGuN-", 'R' 89 | "----r-(_GuN--_) ", 'R' 90 | "-___R__gun--", 'R' 91 | "--_-R---_gun-__", 'R' 92 | "-RgUn ", 'R' 93 | "_-_ RGuN ", 'R' 94 | "-RGuN ", 'R' 95 | "_R-(__--GUN )-_ -", 'R' 96 | "u-guNn___-", 'U' 97 | "u- _( ---_gUnn_-)_", 'U' 98 | "_ u (__gUnN-)_-", 'U' 99 | "_-_ ugUNn", 'U' 100 | "__--u_ GUNn--_", 'U' 101 | "-u__-( _GUNn _ ) ", 'U' 102 | " -_UGUnN_--", 'U' 103 | "-_U-(__-GUNN )___ _", 'U' 104 | "-_u ---(- gun__-__)_", 'U' 105 | "_-u gUN---", 'U' 106 | "---_u- (--- Gun---)____", 'U' 107 | "__uGUN _", 'U' 108 | "-_ -_u__-GUN - _-", 'U' 109 | "__-Ugun ", 'U' 110 | "--U_-_(-GUn )-", 'U' 111 | "- UGUN-", 'U' 112 | "----_U_(--GUN_--)--_", 'U' 113 | "_ -v_gUNN-", 'V' 114 | "_vGUnN__", 'V' 115 | "--v_GUNN_", 'V' 116 | "- -V -( gunN--_) __", 'V' 117 | "-__V__( _guNn) _", 'V' 118 | " --VgUNn ", 'V' 119 | " _V GunN _", 'V' 120 | "V __(_GUnN_ )-_-_ ", 'V' 121 | "_vGun_", 'V' 122 | "-___v_( __-GuN _-- )", 'V' 123 | "-- V ___gun- -", 'V' 124 | " V----(-GUn-- )-", 'V' 125 | "_VGUn ", 'V' 126 | -------------------------------------------------------------------------------- /util/gtkutil.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2012 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import contextlib 22 | import functools 23 | import gtk 24 | 25 | 26 | @contextlib.contextmanager 27 | def destroying(thing): 28 | try: 29 | yield thing 30 | finally: 31 | thing.destroy() 32 | 33 | 34 | def show_message_dialog( 35 | parent_window, title, msg, msg_type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE 36 | ): 37 | 38 | kwargs = dict(type=msg_type, buttons=buttons, message_format=msg) 39 | msg_dlg = gtk.MessageDialog(**kwargs) 40 | msg_dlg.set_title(title) 41 | msg_dlg.set_transient_for(parent_window) 42 | msg_dlg.set_position(gtk.WIN_POS_CENTER_ON_PARENT) 43 | res = msg_dlg.run() 44 | msg_dlg.destroy() 45 | return res 46 | 47 | 48 | show_error_dialog = functools.partial(show_message_dialog, msg_type=gtk.MESSAGE_ERROR) 49 | 50 | 51 | @contextlib.contextmanager 52 | def gtk_sync(): 53 | """A context manager to force an update to the GTK application window. 54 | 55 | By design, all GTK events (including window refreshing and updates) are 56 | handled in the main loop, which cannot handle window update events while 57 | the application or callback code is running [1]. This means that nothing 58 | will happen in the application windows unless we explicitly tell GTK to 59 | process any events that have been left pending. This is what this context 60 | manager does, running any pending iteration of the main loop immediately 61 | before and after the 'with' block. 62 | 63 | [1] PyGTK: FAQ Entry 3.7 64 | http://faq.pygtk.org/index.py?req=show&file=faq03.007.htp 65 | 66 | """ 67 | 68 | def sync(): 69 | # Ensure rendering is done immediately 70 | while gtk.events_pending(): 71 | gtk.main_iteration() 72 | 73 | sync() 74 | try: 75 | yield 76 | finally: 77 | sync() 78 | 79 | 80 | @contextlib.contextmanager 81 | def disable_while(widget): 82 | """A context manager to temporarily disable a GTK widget. 83 | 84 | Use the gtk.Widget.set_sensitive() method to set the 'sensitive' property 85 | of the widget to False (therefore disabling it, so that it appears 'grayed 86 | out' and the user cannot interact with it) when the block is entered and to 87 | True (enabling it again) after the block is exited. In this manner, we can 88 | disable a GTK widget while in the 'with block'. 89 | 90 | """ 91 | 92 | with gtk_sync(): 93 | widget.set_sensitive(False) 94 | 95 | yield 96 | 97 | with gtk_sync(): 98 | widget.set_sensitive(True) 99 | -------------------------------------------------------------------------------- /Doc/user/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Installation 4 | ============ 5 | 6 | Release v\ |version|. 7 | 8 | The installation of LEMON is a somewhat tedious —although not particularly difficult— process, involving several dependencies for which there not exist Debian packages. 9 | 10 | These are the steps to install LEMON on a fresh `Debian 7`_ machine: 11 | 12 | 1. ``apt-get install git python-pip csh`` 13 | #. ``apt-get build-dep python-matplotlib python-scipy`` 14 | #. ``apt-get install openmpi-dev`` 15 | #. ``easy_install -U distribute`` 16 | #. ``git clone --branch v0.3 git://github.com/vterron/lemon.git ~/lemon`` 17 | #. ``cd ~/lemon`` 18 | #. ``pip install "numpy>=1.7.1"`` 19 | #. ``pip install -r pre-requirements.txt`` # :download:`[View] <../../pre-requirements.txt>` 20 | #. ``pip install -r requirements.txt`` # :download:`[View] <../../requirements.txt>` 21 | 22 | #. Install IRAF_. 23 | #. Install SExtractor_ (version 2.19.5 or newer) [#]_ 24 | #. Install `Astrometry.net`_. 25 | #. Install the MPI-enabled Montage_ binaries [#]_ 26 | #. ``python ./setup.py`` 27 | #. ``echo 'PATH=$PATH:~/lemon' >> ~/.bashrc`` 28 | #. ``echo "source ~/lemon/lemon-completion.sh" >> ~/.bashrc`` 29 | #. ``./run_tests.py`` — optional, although recommended! 30 | 31 | Note that in October 2018 NOAO began the transition of IRAF to an end-of-support state. IRAF is now maintained by the `iraf-community project `_ on GitHub, which integrates any available patches into the source code. You can download the IRAF 2.16.1 snapshot `here `_. 32 | 33 | .. [#] The important thing to `keep in mind `_ is that SExtractor does not rely on the CLAPACK_ implementation of LAPACK_ — instead, it only uses the subset of the LAPACK functions available in ATLAS_. That is the reason why, in case the ``liblapack-dev`` package is installed, you may encounter an error such as :code:`configure: error: CBLAS/LAPack library files not found at usual locations! Exiting`. If that is your case, you may need to do something like this: 34 | 35 | .. code:: bash 36 | 37 | cd ./sextractor-2.19.5 38 | apt-get install fftw3-dev libatlas-base-dev 39 | update-alternatives --set liblapack.so /usr/lib/atlas-base/atlas/liblapack.so 40 | ./configure --with-atlas-incdir=/usr/include/atlas 41 | make 42 | make install 43 | 44 | .. [#] Edit these two lines in ``Montage/Makefile.LINUX`` before doing ``make`` 45 | 46 | .. code:: bash 47 | 48 | # uncomment the next two lines to build MPI modules 49 | # MPICC = mpicc 50 | # BINS = $(SBINS) $(MBINS) 51 | 52 | 53 | .. note:: 54 | 55 | LEMON is **not** yet available on PyPI_, but we intend to package it soon. This will enormously simplify the installation process, which should consist of a single ``pip install lemon`` command — provided that IRAF_, SExtractor_, `Astrometry.net`_ and Montage_ are already installed on your system. 56 | 57 | .. _Debian 7: https://www.debian.org/releases/wheezy/ 58 | .. _IRAF: http://iraf.noao.edu/ 59 | .. _SExtractor: http://www.astromatic.net/software/sextractor 60 | .. _Astrometry.net: http://astrometry.net/use.html 61 | .. _Montage: http://montage.ipac.caltech.edu/docs/download2.html 62 | .. _CLAPACK: http://www.netlib.org/clapack/ 63 | .. _LAPACK: http://www.netlib.org/lapack/ 64 | .. _ATLAS: http://math-atlas.sourceforge.net/ 65 | .. _PyPI: https://pypi.python.org/pypi 66 | -------------------------------------------------------------------------------- /test/integration/README.md: -------------------------------------------------------------------------------- 1 | # WASP-10b integration test 2 | 3 | LEMON has [an integration test](./wasp10b.py) that reduces a transit of exoplanet [WASP-10b](https://en.wikipedia.org/wiki/WASP-10b), comparing the resulting light curve to that in a [golden file](./WASP10b-golden-curve.txt). Although this test doesn't exercise the entire pipeline, it covers the two critical commands: `photometry` (to perform aperture photometry) and `diffphot` (to generate the light curves). These are the steps that the integration test follows: 4 | 5 | 1. Downloads a 2.3 GiB `.xz` file with [the test data](#test-data). 6 | * The URL of the file is read from the `WASP10_URL` environment variable. 7 | 1. Extracts and verifies the SHA-1 checksums of the test data. 8 | 1. Runs `lemon photometry` on the test FITS images. 9 | * Instead of relying on [SExtractor](http://www.astromatic.net/software/sextractor) for the detection of stars, we use a [hand-curated list](./WASP10b-coordinates.txt) of just 58 stars in the field of view. This considerably improves the execution time of the light curve generation (in the subsequent step), given the quadratic complexity of [Broeg's algorithm](http://adsabs.harvard.edu/abs/2005AN....326..134B). 10 | 1. Runs `lemon diffphot`, generating the light curve of all the objects. 11 | 1. Runs `lemon export`, writing the light curve of WASP-10 to a text file. 12 | 1. Compares the exported light curve to those in [the golden file](./WASP10b-golden-curve.txt). 13 | * For each data point, compares three values: Julian Date, differential magnitude and signal-to-noise ratio. 14 | * For each (floating-point) value, uses [`assertAlmostEqual()`](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual) with `places=6`. 15 | 16 | The light curve is generated for multiple values of `--cores`, including a single CPU. This exercises the multiprocessing logic, making sure that light curves generated in parallel are absolutely independent of each other: the resulting light curves are always the same, regardless of the number of CPUs among which the work was divided. 17 | 18 | This integration test is part of [our Travis CI configuration](../.travis.yml), and thus runs automatically for any code change. 19 | 20 | ## Test data 21 | 22 | The FITS files used in the integration test are 182 images of a transit of [exoplanet WASP-10b](https://en.wikipedia.org/wiki/WASP-10b). These images were taken by our team with [the 1.5 telescope](https://www.osn.iaa.csic.es/en/page/15-m-telescope) at the [Sierra Nevada Observatory](https://www.osn.iaa.csic.es/en/) (Granada, Spain) on August 4th 2011. The data is compressed into a 2.3 GiB `.xz` file stored on @vterron's [NextCloud](https://nextcloud.com/) server. For this reason, the URL of the file is stored in [an encrypted environment variable](https://docs.travis-ci.com/user/environment-variables/#defining-encrypted-variables-in-travisyml), and not publicly available. Although not infallible, the goal of this approach is merely to avoid unnecesarily exposing the address of a personal NextCloud server. 23 | 24 | ### Obtaining a copy 25 | 26 | If you'd like to download a copy of WASP-10b's transit FITS images, please [file an issue](https://github.com/vterron/lemon/issues/new) and we'll share them with you. 27 | 28 | ### Plotting the exoplanet transit 29 | 30 | This is what the WASP-10b's transit in [the golden file](./WASP10b-golden-curve.txt) looks like: 31 | 32 | ![Plot of WASP-10b's transit](WASP10b-golden-curve.svg) 33 | -------------------------------------------------------------------------------- /defaults.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2012 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | """ Definition of the default options used by the different modules """ 22 | 23 | import multiprocessing 24 | 25 | # LEMON modules 26 | import setup 27 | 28 | desc = {} # option descriptions (for optparse) 29 | 30 | ncores = multiprocessing.cpu_count() 31 | desc["ncores"] = ( 32 | "the maximum number of cores available to the module. This option " 33 | "defaults to the number of CPUs in the system, which are automatically " 34 | "detected [default: %default]" 35 | ) 36 | 37 | maximum = 50000 38 | desc["maximum"] = ( 39 | "the CCD saturation level, in ADUs. Those star which have one or more " 40 | "pixels above this value are considered to be saturated. Note that, for " 41 | "coadded images, the effective saturation level is obtained by multiplying " 42 | "this value by the number of coadds (see --coaddk option) [default: %default]" 43 | ) 44 | 45 | margin = 0 46 | desc["margin"] = ( 47 | "the width, in pixels, of the areas adjacent to the edges that will be " 48 | "ignored when detecting sources on the reference image. Stars whose center " 49 | "is fewer than 'margin' pixels from any border (horizontal or vertical) of " 50 | "the FITS image are not considered. [default: %default]" 51 | ) 52 | 53 | verbosity = 0 54 | desc["verbosity"] = ( 55 | "increase the amount of information given during the execution. A single " 56 | "-v tracks INFO events, while two or more enable DEBUG messages. These " 57 | "are probably only useful when debugging the module." 58 | ) 59 | 60 | snr_percentile = 25 61 | desc["snr_percentile"] = ( 62 | "the score at the percentile of the signal-to-noise ratio of the " 63 | "astronomical objects that are to be included in the calculation of the " 64 | "FWHM / elongation of a FITS image. Objects whose SNR is below this " 65 | "score are excluded. Note that the percentile is calculated taking into " 66 | "account only the astronomical objects within the image margins (see " 67 | "the '--margin' option) [default: %default]" 68 | ) 69 | 70 | desc["mean"] = ( 71 | "in order to compute the FWHM / elongation of a FITS image, take the " 72 | "arithmetic mean of the astronomical objects (detected by SExtractor) " 73 | "instead of the median. So you say you prefer a non-robust statistic?" 74 | ) 75 | 76 | desc["filter"] = ( 77 | "The supported systems are Johnson, Cousins, Gunn, SDSS, 2MASS, Stromgren " 78 | "and H-alpha, but letters, designating a particular section of the " 79 | "electromagnetic spectrum, may also be used without a system (e.g., 'V'). " 80 | "In addition to the built-in photometric systems, custom filters are " 81 | "supported via the %s configuration file." % setup.CONFIG_FILENAME 82 | ) 83 | -------------------------------------------------------------------------------- /util/queue.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Copyright (c) 2019 Victor Terron. All rights reserved. 4 | # 5 | # This file is part of LEMON. 6 | # 7 | # LEMON is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | 21 | import multiprocessing 22 | import multiprocessing.queues 23 | 24 | 25 | class SharedCounter(object): 26 | """A synchronized shared counter. 27 | 28 | The locking done by multiprocessing.Value ensures that only a single 29 | process or thread may read or write the in-memory ctypes object. However, 30 | in order to do n += 1, Python performs a read followed by a write, so a 31 | second process may read the old value before the new one is written by the 32 | first process. The solution is to use a multiprocessing.Lock to guarantee 33 | the atomicity of the modifications to Value. 34 | 35 | This class comes almost entirely from Eli Bendersky's blog: 36 | http://eli.thegreenplace.net/2012/01/04/shared-counter-with-pythons-multiprocessing/ 37 | 38 | """ 39 | 40 | def __init__(self, n=0): 41 | self.count = multiprocessing.Value("i", n) 42 | 43 | def increment(self, n=1): 44 | """ Increment the counter by n (default = 1) """ 45 | with self.count.get_lock(): 46 | self.count.value += n 47 | 48 | @property 49 | def value(self): 50 | """ Return the value of the counter """ 51 | return self.count.value 52 | 53 | 54 | class Queue(multiprocessing.queues.Queue): 55 | """A portable implementation of multiprocessing.Queue. 56 | 57 | Because of multithreading / multiprocessing semantics, Queue.qsize() may 58 | raise the NotImplementedError exception on Unix platforms like Mac OS X 59 | where sem_getvalue() is not implemented. This subclass addresses this 60 | problem by using a synchronized shared counter (initialized to zero) and 61 | increasing / decreasing its value every time the put() and get() methods 62 | are called, respectively. This not only prevents NotImplementedError from 63 | being raised, but also allows us to implement a reliable version of both 64 | qsize() and empty(). 65 | 66 | """ 67 | 68 | def __init__(self, *args, **kwargs): 69 | super(Queue, self).__init__(*args, **kwargs) 70 | self._size = SharedCounter(0) 71 | 72 | def put(self, *args, **kwargs): 73 | super(Queue, self).put(*args, **kwargs) 74 | self._size.increment(1) 75 | 76 | def get(self, *args, **kwargs): 77 | item = super(Queue, self).get(*args, **kwargs) 78 | self._size.increment(-1) 79 | return item 80 | 81 | def qsize(self): 82 | """ Reliable implementation of multiprocessing.Queue.qsize() """ 83 | return self._size.value 84 | 85 | def empty(self): 86 | """ Reliable implementation of multiprocessing.Queue.empty() """ 87 | return not self.qsize() 88 | 89 | def clear(self): 90 | """ Remove all elements from the Queue. """ 91 | while not self.empty(): 92 | self.get() 93 | -------------------------------------------------------------------------------- /util/test/test_queue.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Copyright (c) 2019 Victor Terron. All rights reserved. 4 | # 5 | # This file is part of LEMON. 6 | # 7 | # LEMON is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | from __future__ import division 21 | 22 | import Queue 23 | import time 24 | from Queue import Empty, Full 25 | 26 | 27 | # LEMON modules 28 | from test import unittest 29 | from util import Queue 30 | 31 | 32 | class QueueTest(unittest.TestCase): 33 | def shortDescription(self): 34 | """Don't use the first line of the test method's docstring.""" 35 | pass 36 | 37 | def test_put_and_get(self): 38 | """Test that this is a FIFO queue.""" 39 | 40 | q = Queue() 41 | q.put(5) 42 | q.put(7) 43 | q.put(9) 44 | 45 | self.assertEqual(5, q.get()) 46 | self.assertEqual(7, q.get()) 47 | self.assertEqual(9, q.get()) 48 | 49 | def test_put_when_queue_is_full(self): 50 | """Test that if Full is raised, qsize() doesn't break.""" 51 | 52 | q = Queue(maxsize=1) 53 | q.put(5) 54 | self.assertEqual(1, q.qsize()) 55 | with self.assertRaises(Full): 56 | q.put(7, block=False) 57 | 58 | # Internal counter doesn't change, see: 59 | # https://github.com/vterron/lemon/pull/103 60 | self.assertEqual(1, q.qsize()) 61 | 62 | # Pause to allow queue to complete operation before terminating 63 | # Otherwise sometimes results in 'IOError: [Errno 32] Broken pipe' 64 | time.sleep(0.1) 65 | 66 | def test_get_when_queue_is_empty(self): 67 | """Test that if Empty is raised, qsize() doesn't break.""" 68 | 69 | q = Queue() 70 | self.assertEqual(0, q.qsize()) 71 | with self.assertRaises(Empty): 72 | q.get(block=False) 73 | 74 | # Internal counter doesn't change, see: 75 | # https://github.com/vterron/lemon/pull/103 76 | self.assertEqual(0, q.qsize()) 77 | time.sleep(0.1) 78 | 79 | def test_qsize(self): 80 | """Test that qsize() gets updated correctly as we put() and get().""" 81 | 82 | q = Queue() 83 | self.assertEqual(0, q.qsize()) 84 | q.put(5) 85 | self.assertEqual(1, q.qsize()) 86 | q.put(7) 87 | self.assertEqual(2, q.qsize()) 88 | q.put(9) 89 | self.assertEqual(3, q.qsize()) 90 | 91 | q.get() 92 | self.assertEqual(2, q.qsize()) 93 | q.get() 94 | self.assertEqual(1, q.qsize()) 95 | q.get() 96 | self.assertEqual(0, q.qsize()) 97 | 98 | def test_empty(self): 99 | """Test empty().""" 100 | 101 | q = Queue() 102 | self.assertTrue(q.empty()) 103 | q.put(1) 104 | self.assertFalse(q.empty()) 105 | time.sleep(0.1) 106 | 107 | def test_clear(self): 108 | """Test that clear() empties the queue.""" 109 | 110 | q = Queue() 111 | q.put(5) 112 | q.put(7) 113 | q.put(9) 114 | 115 | q.clear() 116 | self.assertEqual(0, q.qsize()) 117 | -------------------------------------------------------------------------------- /util/display.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Copyright (c) 2019 Victor Terron. All rights reserved. 4 | # 5 | # This file is part of LEMON. 6 | # 7 | # LEMON is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import functools 21 | import sys 22 | import time 23 | import traceback 24 | 25 | # LEMON modules 26 | import style 27 | 28 | 29 | def show_progress(percentage): 30 | """Print a progress bar strikingly similar to that of the wget command. 31 | 32 | Displays a progress bar with the format used as the Unix wget command: 33 | 51%[===============================> ] 34 | 35 | The whole bar, including the percentage and the surrounding square 36 | brackets, has a length of 79 characters, as recommended by the Style Guide 37 | for Python Code (PEP 8). It should also be noted that the progress bar is 38 | printed in the current line, so a new one should be started before calling 39 | the method if the current line is not to be overwritten. It also does not 40 | include a newline character either. The percentage must be in the range 41 | [0, 100]. 42 | 43 | """ 44 | 45 | if not 0 <= percentage <= 100: 46 | raise ValueError("The value of 'percentage' must be in the range [0,100]") 47 | 48 | length = 79 # the 79 character line recommendation 49 | occupied = 3 + len(style.prefix) # "%" + "[" + "]" + style.prefix 50 | 51 | percent = str(int(percentage)) 52 | available = length - occupied - len(percent) 53 | bar = int((available) * int(percent) / 100.0) 54 | spaces = available - bar 55 | 56 | sys.stdout.write( 57 | "\r" + style.prefix + percent + "%[" + bar * "=" + ">" + spaces * " " + "]" 58 | ) 59 | sys.stdout.flush() 60 | 61 | 62 | def utctime(seconds=None, suffix=True): 63 | """UTC version of time.ctime. 64 | 65 | Convert a time expressed in seconds since the Unix epoch to a 28-character 66 | string, representing Coordinated Universal Time, of the following form: 67 | 'Sun Jun 20 23:21:05 1993 UTC'. Fractions of a second are ignored. The last 68 | part of the string, ' UTC', is omitted (and therefore a 24-character string 69 | returned) if the 'suffix' argument evaluates to False. If 'seconds' is not 70 | provided or None, the current time as returned by time.time() is used. 71 | 72 | """ 73 | 74 | utc_ctime = time.asctime(time.gmtime(seconds)) 75 | if suffix: 76 | utc_ctime += " UTC" 77 | return utc_ctime 78 | 79 | 80 | def print_exception_traceback(func): 81 | """Decorator to print the stack trace of an exception. 82 | 83 | This decorator catches any exception raised by the decorated function, 84 | prints its information to the standard output (traceback.print_exc()) and 85 | re-raises it. In particular, this may be used to decorate functions called 86 | through the multiprocessing module, since exceptions in spawned child 87 | processes do not print stack traces. 88 | 89 | The idea for this decorator comes from Chuan Ji's blog: 90 | http://seasonofcode.com/posts/python-multiprocessing-and-exceptions.html 91 | 92 | """ 93 | 94 | @functools.wraps(func) 95 | def wrapper(*args, **kwargs): 96 | try: 97 | return func(*args, **kwargs) 98 | except Exception: 99 | traceback.print_exc() 100 | print 101 | raise 102 | 103 | return wrapper 104 | -------------------------------------------------------------------------------- /sextractor/sextractor.sex: -------------------------------------------------------------------------------- 1 | # Default configuration file for SExtractor 2.3.5 2 | # EB 2004-12-15 3 | # 4 | 5 | #-------------------------------- Catalog ------------------------------------ 6 | 7 | CATALOG_NAME test.cat # name of the output catalog 8 | CATALOG_TYPE ASCII_HEAD # "NONE","ASCII_HEAD","ASCII","FITS_1.0" 9 | # or "FITS_LDAC" 10 | 11 | PARAMETERS_NAME sextractor.param # name of the file containing catalog contents 12 | 13 | #------------------------------- Extraction ---------------------------------- 14 | 15 | DETECT_TYPE CCD # "CCD" or "PHOTO" 16 | FLAG_IMAGE flag.fits # filename for an input FLAG-image 17 | DETECT_MINAREA 5 # minimum number of pixels above threshold 18 | DETECT_THRESH 1.5 # or , in mag.arcsec-2 19 | ANALYSIS_THRESH 100.0 # or , in mag.arcsec-2 20 | 21 | FILTER Y # apply filter for detection ("Y" or "N")? 22 | FILTER_NAME sextractor.conv # name of the file containing the filter 23 | 24 | DEBLEND_NTHRESH 32 # Number of deblending sub-thresholds 25 | DEBLEND_MINCONT 0.005 # Minimum contrast parameter for deblending 26 | 27 | CLEAN Y # Clean spurious detections? (Y or N)? 28 | CLEAN_PARAM 1.0 # Cleaning efficiency 29 | 30 | MASK_TYPE CORRECT # type of detection MASKing: can be one of 31 | # "NONE", "BLANK" or "CORRECT" 32 | 33 | #------------------------------ Photometry ----------------------------------- 34 | 35 | PHOT_APERTURES 5 # MAG_APER aperture diameter(s) in pixels 36 | PHOT_AUTOPARAMS 2.5, 3.5 # MAG_AUTO parameters: , 37 | PHOT_PETROPARAMS 2.0, 3.5 # MAG_PETRO parameters: , 38 | # 39 | 40 | SATUR_LEVEL 50000.0 # level (in ADUs) at which arises saturation 41 | SATUR_KEY DUMMY123PI # keyword for the saturation level (in ADUs); this will never be 42 | # read from the header, so we need here a 'dummy' value here. 43 | 44 | MAG_ZEROPOINT 25.0 # the zero point of the magnitude scale (IRAF qphot's zmag) 45 | MAG_GAMMA 4.0 # gamma of emulsion (for photographic scans) 46 | GAIN 0.0 # detector gain in e-/ADU 47 | PIXEL_SCALE 1.0 # size of pixel in arcsec (0=use FITS WCS info) 48 | 49 | #------------------------- Star/Galaxy Separation ---------------------------- 50 | 51 | SEEING_FWHM 1.2 # stellar FWHM in arcsec 52 | STARNNW_NAME sextractor.nnw # Neural-Network_Weight table filename 53 | 54 | #------------------------------ Background ----------------------------------- 55 | 56 | BACK_SIZE 64 # Background mesh: or , 57 | BACK_FILTERSIZE 3 # Background filter: or , 58 | 59 | BACKPHOTO_TYPE GLOBAL # can be "GLOBAL" or "LOCAL" 60 | 61 | #------------------------------ Check Image ---------------------------------- 62 | 63 | CHECKIMAGE_TYPE NONE # can be one of "NONE", "BACKGROUND", 64 | # "MINIBACKGROUND", "-BACKGROUND", "OBJECTS", 65 | # "-OBJECTS", "SEGMENTATION", "APERTURES", 66 | # or "FILTERED" 67 | CHECKIMAGE_NAME check.fits # Filename for the check-image 68 | 69 | #--------------------- Memory (change with caution!) ------------------------- 70 | 71 | MEMORY_OBJSTACK 12000 # number of objects in stack 72 | MEMORY_PIXSTACK 1800000 # number of pixels in stack 73 | MEMORY_BUFSIZE 4096 # number of lines in buffer 74 | 75 | #----------------------------- Miscellaneous --------------------------------- 76 | 77 | VERBOSE_TYPE NORMAL # can be "QUIET", "NORMAL" or "FULL" 78 | NTHREADS 1 # Number of simultaneous threads for the SMP 79 | # version of SExtractor; use one thread for 80 | # each of the processes spawned in parallel -------------------------------------------------------------------------------- /lemon: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Copyright (c) 2012 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | # Use import hooks, which are added to sys.meta_path, to enforce 22 | # a minimum version of the modules defined in requirements.txt. 23 | import check_versions 24 | 25 | import atexit 26 | import difflib 27 | import logging 28 | import os.path 29 | import requests 30 | import sys 31 | 32 | 33 | API_QUERY_TIMEOUT = 2 # seconds 34 | LEMON_COMMANDS = [ 35 | "annuli", 36 | "astrometry", 37 | "diffphot", 38 | "export", 39 | "import", 40 | "juicer", 41 | "mosaic", 42 | "offsets", 43 | "photometry", 44 | "seeing", 45 | ] 46 | 47 | 48 | def show_help(name): 49 | """ Help message, listing all commands, that looks like Git's """ 50 | 51 | print "usage: %s [--help] [--version] [--update] COMMAND [ARGS]" % name 52 | print 53 | print "The essential commands are:" 54 | print " astrometry Calibrate the images astrometrically" 55 | print " mosaic Assemble the images into a mosaic" 56 | print " photometry Perform aperture photometry" 57 | print " diffphot Generate light curves" 58 | print " juicer LEMONdB browser and variability analyzer" 59 | print " export Print the light curve of an object" 60 | print 61 | print "The auxiliary, not-always-necessary commands are:" 62 | print " import Group the images of an observing campaign" 63 | print " seeing Discard images with bad seeing or elongated" 64 | print " annuli Find optimal parameters for photometry" 65 | 66 | print 67 | print "See '%s COMMAND' for more information on a specific command." % name 68 | 69 | 70 | if __name__ == "__main__": 71 | 72 | name = os.path.basename(sys.argv[0]) 73 | 74 | if len(sys.argv) == 1 or "--help" in sys.argv: 75 | show_help(name) 76 | sys.exit(0) 77 | 78 | command = sys.argv[1] 79 | args = sys.argv[2:] 80 | 81 | if command not in LEMON_COMMANDS: 82 | msg = "%s: '%s' is not a lemon command. See 'lemon --help'." 83 | print msg % (name, command) 84 | 85 | # Show suggestions, if any, when the command does not exist 86 | matches = difflib.get_close_matches(command, LEMON_COMMANDS) 87 | if matches: 88 | print 89 | print "Did you mean", 90 | print len(matches) == 1 and "this?" or "one of these?" 91 | for match in matches: 92 | print " " * 8 + match 93 | 94 | sys.exit(1) 95 | 96 | elif command == "juicer": 97 | import juicer.main 98 | 99 | kwargs = {} 100 | if args: 101 | kwargs["db_path"] = args[0] 102 | juicer.main.main(**kwargs) 103 | 104 | else: 105 | 106 | # Add the name of the command to the script name so that the brief 107 | # summary of the imported script options includes it (for example, 108 | # "lemon photometry" instead of just "lemon". 109 | 110 | sys.argv[0] = "%s %s" % (name, command) 111 | 112 | # The 'import' statement cannot be used as the name of the module 113 | # is only known at runtime. We need to manually invoke __import__ 114 | # to import the module by name and then run its main() function. 115 | 116 | module = __import__(command) 117 | module.main(args) 118 | -------------------------------------------------------------------------------- /Doc/_themes/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import ( 4 | Keyword, 5 | Name, 6 | Comment, 7 | String, 8 | Error, 9 | Number, 10 | Operator, 11 | Generic, 12 | Whitespace, 13 | Punctuation, 14 | Other, 15 | Literal, 16 | ) 17 | 18 | 19 | class FlaskyStyle(Style): 20 | background_color = "#f8f8f8" 21 | default_style = "" 22 | 23 | styles = { 24 | # No corresponding class for the following: 25 | # Text: "", # class: '' 26 | Whitespace: "underline #f8f8f8", # class: 'w' 27 | Error: "#a40000 border:#ef2929", # class: 'err' 28 | Other: "#000000", # class 'x' 29 | Comment: "italic #8f5902", # class: 'c' 30 | Comment.Preproc: "noitalic", # class: 'cp' 31 | Keyword: "bold #004461", # class: 'k' 32 | Keyword.Constant: "bold #004461", # class: 'kc' 33 | Keyword.Declaration: "bold #004461", # class: 'kd' 34 | Keyword.Namespace: "bold #004461", # class: 'kn' 35 | Keyword.Pseudo: "bold #004461", # class: 'kp' 36 | Keyword.Reserved: "bold #004461", # class: 'kr' 37 | Keyword.Type: "bold #004461", # class: 'kt' 38 | Operator: "#582800", # class: 'o' 39 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 40 | Punctuation: "bold #000000", # class: 'p' 41 | # because special names such as Name.Class, Name.Function, etc. 42 | # are not recognized as such later in the parsing, we choose them 43 | # to look the same as ordinary variables. 44 | Name: "#000000", # class: 'n' 45 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 46 | Name.Builtin: "#004461", # class: 'nb' 47 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 48 | Name.Class: "#000000", # class: 'nc' - to be revised 49 | Name.Constant: "#000000", # class: 'no' - to be revised 50 | Name.Decorator: "#888", # class: 'nd' - to be revised 51 | Name.Entity: "#ce5c00", # class: 'ni' 52 | Name.Exception: "bold #cc0000", # class: 'ne' 53 | Name.Function: "#000000", # class: 'nf' 54 | Name.Property: "#000000", # class: 'py' 55 | Name.Label: "#f57900", # class: 'nl' 56 | Name.Namespace: "#000000", # class: 'nn' - to be revised 57 | Name.Other: "#000000", # class: 'nx' 58 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 59 | Name.Variable: "#000000", # class: 'nv' - to be revised 60 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 61 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 62 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 63 | Number: "#990000", # class: 'm' 64 | Literal: "#000000", # class: 'l' 65 | Literal.Date: "#000000", # class: 'ld' 66 | String: "#4e9a06", # class: 's' 67 | String.Backtick: "#4e9a06", # class: 'sb' 68 | String.Char: "#4e9a06", # class: 'sc' 69 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 70 | String.Double: "#4e9a06", # class: 's2' 71 | String.Escape: "#4e9a06", # class: 'se' 72 | String.Heredoc: "#4e9a06", # class: 'sh' 73 | String.Interpol: "#4e9a06", # class: 'si' 74 | String.Other: "#4e9a06", # class: 'sx' 75 | String.Regex: "#4e9a06", # class: 'sr' 76 | String.Single: "#4e9a06", # class: 's1' 77 | String.Symbol: "#4e9a06", # class: 'ss' 78 | Generic: "#000000", # class: 'g' 79 | Generic.Deleted: "#a40000", # class: 'gd' 80 | Generic.Emph: "italic #000000", # class: 'ge' 81 | Generic.Error: "#ef2929", # class: 'gr' 82 | Generic.Heading: "bold #000080", # class: 'gh' 83 | Generic.Inserted: "#00A000", # class: 'gi' 84 | Generic.Output: "#888", # class: 'go' 85 | Generic.Prompt: "#745334", # class: 'gp' 86 | Generic.Strong: "bold #000000", # class: 'gs' 87 | Generic.Subheading: "bold #800080", # class: 'gu' 88 | Generic.Traceback: "bold #a40000", # class: 'gt' 89 | } 90 | -------------------------------------------------------------------------------- /juicer/gui/snr-threshold-dialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | 8 | 9 | True 10 | 11 | 12 | 5 13 | normal 14 | False 15 | 16 | 17 | True 18 | vertical 19 | 2 20 | 21 | 22 | True 23 | vertical 24 | 25 | 26 | True 27 | Differential magnitudes whose signal-to-noise ratio is below this threshold are not plotted 28 | center 29 | True 30 | word-char 31 | 32 | 33 | False 34 | 5 35 | 0 36 | 37 | 38 | 39 | 40 | True 41 | True 42 | 43 | 44 | True 45 | True 46 | 4 47 | 48 | True 49 | 4 50 | snr-threshold-adjustment 51 | True 52 | if-valid 53 | 54 | 55 | False 56 | False 57 | 5 58 | 0 59 | 60 | 61 | 62 | 63 | 5 64 | 1 65 | 66 | 67 | 68 | 69 | False 70 | 1 71 | 72 | 73 | 74 | 75 | True 76 | center 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | False 86 | end 87 | 0 88 | 89 | 90 | 91 | 92 | 93 | 94 | 100 95 | 9999 96 | 1 97 | 10 98 | 99 | 100 | -------------------------------------------------------------------------------- /export.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | # encoding: UTF-8 3 | 4 | # Author: Victor Terron (c) 2020 5 | # Email: `echo vt2rron1iaa32s | tr 132 @.e` 6 | # License: GNU GPLv3 7 | 8 | from __future__ import division 9 | from __future__ import print_function 10 | from __future__ import absolute_import 11 | from __future__ import unicode_literals 12 | 13 | _DESCRIPTION = """ 14 | Print the light curve of an object stored in a LEMONdB. 15 | 16 | This command takes as input the right ascension and declination of an object, 17 | and finds the one stored in the LEMONdB that's close to these coordinates. It 18 | then prints to standard output the (a) time, (b) differential magnitude and 19 | (c) signal-to-noise ratio of all the points in the light curve of the object 20 | in the specified photometric filter. 21 | """ 22 | 23 | import argparse 24 | import astropy.time 25 | import os.path 26 | import prettytable 27 | import re 28 | import sys 29 | import time 30 | 31 | # LEMON modules 32 | import database 33 | import passband 34 | import util.coords 35 | import util 36 | 37 | parser = argparse.ArgumentParser(description=_DESCRIPTION) 38 | parser.add_argument( 39 | "db_path", 40 | metavar="LEMON_DB", 41 | type=str, 42 | help="the LEMON database with the light curves", 43 | ) 44 | parser.add_argument( 45 | "ra", 46 | metavar="", 47 | type=float, 48 | help="Right adcension of the astronomical object, " "in decimal degrees.", 49 | ) 50 | parser.add_argument( 51 | "dec", 52 | metavar="", 53 | type=float, 54 | help="Declination of the astronomical object, in " "decimal degrees.", 55 | ) 56 | parser.add_argument( 57 | "filter", 58 | metavar="", 59 | type=passband.Passband, 60 | help="The name of the photometric filter.", 61 | ) 62 | parser.add_argument( 63 | "--decimal_places", 64 | dest="places", 65 | type=int, 66 | default=3, 67 | help="Round floating-point numbers to this many decimal places.", 68 | ) 69 | parser.add_argument( 70 | "--output_file", 71 | dest="output", 72 | type=argparse.FileType("w"), 73 | default=sys.stdout, 74 | help="File to which to write the light curve data points", 75 | ) 76 | 77 | 78 | def main(arguments=None): 79 | 80 | if arguments is None: 81 | arguments = sys.argv[1:] 82 | args = parser.parse_args(args=arguments) 83 | 84 | with database.LEMONdB(args.db_path) as db: 85 | print("Input coordinates:") 86 | print("α: {} ({})".format(args.ra, util.coords.ra_str(args.ra))) 87 | print("δ: {} ({})".format(args.dec, util.coords.dec_str(args.dec))) 88 | 89 | star_id, distance = db.star_closest_to_world_coords(args.ra, args.dec) 90 | star = db.get_star(star_id) 91 | 92 | print() 93 | print("Selected star:") 94 | print("ID: {}".format(star_id)) 95 | print("α: {} ({})".format(star.ra, util.coords.ra_str(star.ra))) 96 | print("δ: {} ({})".format(star.dec, util.coords.dec_str(star.dec))) 97 | print("Distance to input coordinates: {} deg".format(distance)) 98 | print() 99 | 100 | if args.output == sys.stdout: 101 | print("Light curve in {!r} photometric filter:".format(args.filter)) 102 | 103 | star_diff = db.get_light_curve(star_id, args.filter) 104 | if star_diff is None: 105 | raise ValueError( 106 | "no light curve for {!r} photometric filter".format(args.filter) 107 | ) 108 | 109 | table = prettytable.PrettyTable() 110 | table.field_names = ["Date (UTC)", "JD", "Δ Mag", "SNR"] 111 | 112 | def format_float(f): 113 | """Returns f as a string rounded to parser.places decimal places.""" 114 | return "{:.{places}f}".format(f, places=args.places) 115 | 116 | for unix_time, magnitude, snr in star_diff: 117 | jd = astropy.time.Time(unix_time, format="unix").jd 118 | table.add_row( 119 | [ 120 | util.utctime(unix_time, suffix=False), 121 | format_float(jd), 122 | format_float(magnitude), 123 | format_float(snr), 124 | ] 125 | ) 126 | 127 | args.output.write(str(table)) 128 | args.output.write("\n") 129 | 130 | if args.output != sys.stdout: 131 | print( 132 | "Wrote light curve in {!r} photometric filter to {!r}.".format( 133 | args.filter, args.output.name 134 | ) 135 | ) 136 | 137 | 138 | if __name__ == "__main__": 139 | sys.exit(main()) 140 | -------------------------------------------------------------------------------- /keywords.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2012 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | # Description of the optparse.OptionGroup 22 | group_description = ( 23 | "These options customize the FITS keywords from which some required " 24 | "information is extracted. The default values are expected to work " 25 | "well with any file that conforms to the FITS standard. If that is " 26 | "not your case, you should make sure to set these options to the " 27 | "correct values, or otherwise face apocalyptic consequences." 28 | ) 29 | 30 | desc = {} # option descriptions (for optparse) 31 | filterk = "FILTER" 32 | desc[ 33 | "filterk" 34 | ] = "keyword for the name of the filter of the observation [default: %default]" 35 | 36 | rak = "RA" 37 | desc["rak"] = ( 38 | "keyword for the right ascension of the astronomical object(s), expressed " 39 | "either as a floating point number in decimal degrees, or as a string in " 40 | "the 'hh:mm:ss[.sss]' format [default: %default]" 41 | ) 42 | 43 | deck = "DEC" 44 | desc["deck"] = ( 45 | "keyword for the declination of the astronomical object(s), expressed " 46 | "either as a floating point number in decimal degrees, or as a string " 47 | "in the 'dd:mm:ss[.sss]' format [default: %default]" 48 | ) 49 | 50 | datek = "DATE-OBS" 51 | desc["datek"] = ( 52 | "keyword for the date of the observation, in the new Y2K compliant " 53 | "date format: 'yyyy-mm-dd' or 'yyyy-mm-ddTHH:MM:SS[.sss] " 54 | "[default: %default]" 55 | ) 56 | 57 | timek = "TIME-OBS" 58 | desc["timek"] = ( 59 | "keyword for the time at which the observation started, in the format " 60 | "HH:MM:SS[.sss]. This keyword is used in conjunction with --datek to " 61 | "determine the starting time of the observation: --datek gives the " 62 | "starting calendar date and this keyword the time within that day. This " 63 | "keyword is not necessary (and thus this option ignored) if the time is " 64 | "included directly as part of the --datek keyword value with the format " 65 | "yyyy-mm-ddTHH:MM:SS[.sss] [default: %default]" 66 | ) 67 | 68 | exptimek = "EXPTIME" 69 | desc["exptimek"] = "keyword for the exposure time [default: %default]" 70 | 71 | airmassk = "AIRMASS" 72 | desc["airmassk"] = "keyword for the airmass [default: %default]" 73 | 74 | gaink = "GAIN" 75 | desc["gaink"] = ( 76 | "keyword for the gain of the CCD, in e-/ADU. Needed in order to " 77 | "accurately calculate the SNR of each measurement [default: %default]" 78 | ) 79 | 80 | uncimgk = None 81 | desc["uncimgk"] = ( 82 | "keyword that stores the path to the uncalibrated image used to check for " 83 | "saturation -- as the overscan, bias and (particularly) flat-fielding steps " 84 | "may take a saturated pixel below the saturation threshold. If (as by " 85 | "default) this option is not set, saturation is checked for on the same " 86 | "image on which we do photometry." 87 | ) 88 | 89 | fwhmk = "LEMON FWHM" 90 | desc["fwhmk"] = ( 91 | "keyword for the Full Width at Half Maximum (FWHM) of the image, which is " 92 | "written to the FITS header by the 'seeing' command [default: %default]" 93 | ) 94 | 95 | objectk = "OBJECT" 96 | desc["objectk"] = "keyword for the name of the object observed [default: %default]" 97 | 98 | typek = "IMAGETYP" 99 | desc["typek"] = ( 100 | "keyword that identifies the type of image, with values such as 'dark', " 101 | "'flat' or 'object', to cite some of the most common [default: %default]" 102 | ) 103 | 104 | # Used by seeing.FITSeeingImage to 'cache' the SExtractor catalog 105 | sex_catalog = "SEX-CAT" 106 | sex_md5sum = "SEX-MD5" 107 | 108 | coaddk = "NCOADDS" 109 | desc["coaddk"] = ( 110 | "keyword for the number of effective coadds. This value is essential to " 111 | "determine the number of counts at which saturation arises in coadded " 112 | "observations. If the keyword is missing, we assume a value of one (that " 113 | "is, that the observation consisted of a single exposure) [default: %default]" 114 | ) 115 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2012 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from __future__ import with_statement 22 | 23 | """ Just a shadow of what the actual setup.py will look like, this simple 24 | script, when run, creates the IRAF login script (login.cl file) and the user 25 | parameters directory (uparm) in the directory where LEMON is installed. This 26 | directory is automatically detected as it is assumed to be the same in which 27 | this file is located, so it does not depend on where it is executed from. 28 | 29 | """ 30 | 31 | import os 32 | import os.path 33 | import errno 34 | import shutil 35 | import subprocess 36 | 37 | MKIRAF_BIN = "mkiraf" 38 | TERM_TYPE = "xgterm" 39 | LOGIN_FILE = "login.cl" 40 | UPARM_DIR = "uparm" 41 | PYRAF_CACHE = "pyraf" 42 | 43 | # The LEMON configuration file 44 | CONFIG_FILENAME = "~/.lemonrc" 45 | CONFIG_PATH = os.path.expanduser(CONFIG_FILENAME) 46 | 47 | 48 | def mkiraf(path): 49 | """Create the IRAF login script and the uparm directory. 50 | 51 | This function implements a high-level wrapper around IRAF's mkiraf, which 52 | initializes the IRAF login script and the user parameters directory in the 53 | path given as input. Any existing login script or uparm directory is 54 | silently overwritten, although mkiraf makes a backup of the former by 55 | appending '.OLD' to its name. In this manner, the original login script 56 | 'login.cl' becomes 'login.cl.OLD'. 57 | 58 | The login script that this function creates chooses 'xgterm' as its 59 | terminal type. It is usually the best choice, being a xterm-like terminal 60 | program written specifically to work with IRAF. Note that, although we are 61 | required to choose a terminal type when mkiraf is run, LEMON never needs 62 | to use it. 63 | 64 | """ 65 | 66 | os.chdir(path) 67 | 68 | # Avoid having to answer the "Initialize uparm? (y|n):" question 69 | if os.path.exists(UPARM_DIR): 70 | shutil.rmtree(UPARM_DIR) 71 | 72 | # mkiraf reads the terminal type ("Enter terminal type:") from the user, 73 | # not accepting it as a command line argument. Thus, we need to use a pipe 74 | # to send the terminal type to mkiraf's stdin. 75 | args = [MKIRAF_BIN] 76 | p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 77 | p.communicate(input=TERM_TYPE) 78 | 79 | if p.returncode: 80 | msg = "execution of %s failed" % MKIRAF_BIN 81 | raise subprocess.CalledProcessError(p.returncode, args) 82 | 83 | with open(LOGIN_FILE, "rt") as fd: 84 | for line in fd: 85 | splitted = line.split() 86 | if len(splitted) == 2: 87 | if splitted[0] == "stty": 88 | if splitted[1] == TERM_TYPE: 89 | break 90 | else: 91 | msg = "terminal type wasn't set correctly" 92 | raise ValueError(msg) 93 | else: 94 | msg = "terminal type not defined in %s" % LOGIN_FILE 95 | raise ValueError(msg) 96 | 97 | 98 | def mkdir_p(path): 99 | """Create a directory, give no error if it already exists. 100 | 101 | This implements the functionality of Unix `mkdir -p`, creating a 102 | directory but without giving any error if it already exists and 103 | making parent directories as needed. 104 | [URL] http://stackoverflow.com/a/600612/184363 105 | 106 | """ 107 | 108 | try: 109 | os.mkdir(path) 110 | except OSError as exc: # Python >2.5 111 | if exc.errno == errno.EEXIST and os.path.isdir(path): 112 | pass 113 | else: 114 | raise 115 | 116 | 117 | if __name__ == "__main__": 118 | 119 | lemon_path = os.path.dirname(os.path.realpath(__file__)) 120 | print "Setting up IRAF's %s in %s ..." % (LOGIN_FILE, lemon_path), 121 | mkiraf(lemon_path) 122 | print "done." 123 | 124 | print "Creating pyraf/ directory for cache...", 125 | pyraf_cache_path = os.path.join(lemon_path, PYRAF_CACHE) 126 | mkdir_p(pyraf_cache_path) 127 | print "done." 128 | -------------------------------------------------------------------------------- /juicer/config.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2012 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify it 9 | # under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import ConfigParser 22 | import os.path 23 | 24 | CONFIG_FILENAME = ".juicerc" 25 | CONFIG_PATH = os.path.expanduser("~/%s" % CONFIG_FILENAME) 26 | 27 | VIEW_SECTION = "view" 28 | VIEW_SEXAGESIMAL = "sexagesimal" 29 | VIEW_DECIMAL = "decimal" 30 | PLOT_AIRMASSES = "airmasses" 31 | PLOT_JULIAN = "julian_dates" 32 | PLOT_MIN_SNR = "snr_threshold" 33 | 34 | DEFAULT_VIEW_SEXAGESIMAL = True 35 | DEFAULT_VIEW_DECIMAL = False 36 | DEFAULT_PLOT_AIRMASSES = True 37 | DEFAULT_PLOT_JULIAN = False 38 | DEFAULT_PLOT_MIN_SNR = 100 39 | 40 | # The color codes can use any of the following formats supported by matplotlib: 41 | # abbreviations ('g'), full names ('green'), hexadecimal strings ('#008000') or 42 | # a string encoding float on the 0-1 range ('0.75') for gray shades. 43 | 44 | COLOR_SECTION = "colors" 45 | DEFAULT_COLORS = dict( 46 | U="violet", 47 | B="blue", 48 | V="green", 49 | R="#ff4246", # light red 50 | I="#e81818", # dark red 51 | Z="cyan", 52 | Y="brown", 53 | J="yellow", 54 | H="pink", 55 | KS="orange", 56 | K="orange", 57 | L="0.75", # light gray 58 | M="0.50", 59 | ) # dark gray 60 | 61 | # The options for how light curves are dumped to plain-text files 62 | CURVEDUMP_SECTION = "curve-export" 63 | DEFAULT_CURVEDUMP_OPTS = dict( 64 | dump_date_text=1, 65 | dump_date_julian=1, 66 | dump_date_seconds=1, 67 | dump_magnitude=1, 68 | dump_snr=1, 69 | dump_max_merr=1, 70 | dump_min_merr=1, 71 | dump_instrumental_magnitude=1, 72 | dump_instrumental_snr=1, 73 | decimal_places=8, 74 | ) 75 | 76 | 77 | class Configuration(ConfigParser.SafeConfigParser): 78 | """Just a quite simple wrapper to automatically have the configuration 79 | file loaded at instantiation and written to disk with the update method""" 80 | 81 | DEFAULT_CONFIG = "\n".join( 82 | [ 83 | "[%s]" % VIEW_SECTION, 84 | "%s = %d" % (VIEW_SEXAGESIMAL, DEFAULT_VIEW_SEXAGESIMAL), 85 | "%s = %d" % (VIEW_DECIMAL, DEFAULT_VIEW_DECIMAL), 86 | "%s = %d" % (PLOT_AIRMASSES, DEFAULT_PLOT_AIRMASSES), 87 | "%s = %d" % (PLOT_JULIAN, DEFAULT_PLOT_JULIAN), 88 | "%s = %d" % (PLOT_MIN_SNR, DEFAULT_PLOT_MIN_SNR), 89 | "", 90 | "[%s]" % COLOR_SECTION, 91 | ] 92 | + ["%s = %s" % (k, v) for k, v in DEFAULT_COLORS.iteritems()] 93 | + ["", "[%s]" % CURVEDUMP_SECTION] 94 | + ["%s = %s" % (k, v) for k, v in DEFAULT_CURVEDUMP_OPTS.iteritems()] 95 | ) 96 | 97 | def __init__(self, path, update=True): 98 | """Parse a configuration file, creating and populating it with 99 | the default options in case 'path' does not exist""" 100 | 101 | ConfigParser.SafeConfigParser.__init__(self) 102 | 103 | if not os.path.exists(path): 104 | with open(path, "wt") as fd: 105 | fd.write(self.DEFAULT_CONFIG) 106 | 107 | self.read([path]) 108 | self.path = path 109 | 110 | def color(self, letter): 111 | """ Return the color code to be used for a photometric filter """ 112 | return self.get(COLOR_SECTION, letter.upper()) 113 | 114 | def update(self): 115 | """ Write to disk the configuration file """ 116 | with open(self.path, "wt") as fd: 117 | self.write(fd) 118 | 119 | # SafeConfigParser is an old-style class (does not support properties) 120 | def get_minimum_snr(self): 121 | """ Return the PLOT_MIN_SNR option in the VIEW_SECTION section """ 122 | return self.getint(VIEW_SECTION, PLOT_MIN_SNR) 123 | 124 | def set_minimum_snr(self, snr): 125 | """ Set the value of the PLOT_MIN_SNR option in the VIEW_SECTION """ 126 | self.set(VIEW_SECTION, PLOT_MIN_SNR, str(int(snr))) 127 | 128 | def dumpint(self, option): 129 | """ Coerce 'option' in the curves export section to an integer """ 130 | return self.getint(CURVEDUMP_SECTION, option) 131 | 132 | def dumpset(self, option, value): 133 | """ Set 'option' to 'value' in the curves export section """ 134 | self.set(CURVEDUMP_SECTION, option, str(value)) 135 | -------------------------------------------------------------------------------- /json_parse.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2012 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import collections 22 | import copy 23 | import json 24 | import operator 25 | 26 | # LEMON modules 27 | import passband 28 | 29 | typename = "CandidateAnnuli" 30 | field_names = "aperture, annulus, dannulus, stdev" 31 | 32 | 33 | class CandidateAnnuli(collections.namedtuple(typename, field_names)): 34 | """Encapsulate the quality of a set of photometric parameters. 35 | 36 | How do we determine how good a set of parameters for aperture photometry 37 | is? In order to compare them, we need to identify the most constant stars 38 | (or, by extension, any other astronomical object) in the field and compute 39 | their light curves. The better the aperture, annulus and dannulus that we 40 | use are, the lower the standard deviation of the resulting curves. 41 | 42 | This class simply encapsulates these four values, mapping the parameters 43 | for aperture photometry (aperture, annulus and dannulus) to the standard 44 | deviation of the light curves of the most constant astronomical objects. 45 | 46 | Fields: 47 | aperture - the aperture radius, in pixels. 48 | annulus - the inner radius of the sky annulus, in pixels. 49 | dannulus - the width of the sky annulus, in pixels. 50 | stdev - the median, arithmetic mean or a similar statistical measure of the 51 | standard deviation of the light curves of the astronomical objects 52 | when photometry is done using these aperture, annulus and dannulus 53 | values. 54 | 55 | """ 56 | 57 | @staticmethod 58 | def dump(annuli, path): 59 | """Save a series of CadidateAnnuli objects to a JSON file. 60 | 61 | Serialize 'annuli' to a JSON file. It must be a dictionary which maps 62 | each photometric filter (a Passband object) to a sequence of the 63 | corresponding CandidateAnnuli objects -- i.e., the different aperture 64 | photometric parameters that were evaluated for that filter. The output 65 | file will be mercilessly overwritten if it already exists. 66 | 67 | """ 68 | 69 | # Being a subclass of tuple, JSON serializes namedtuples as lists. We 70 | # need to convert them to ordered dictionaries (namedtuple._asdict()) 71 | # first, so that the field names are not lost in the serialization. 72 | data = copy.deepcopy(annuli) 73 | for values in data.itervalues(): 74 | for index in xrange(len(values)): 75 | values[index] = values[index]._asdict() 76 | values.sort(key=operator.itemgetter("stdev")) 77 | 78 | # Use strings, not Passband objects, as keys 79 | for pfilter in data.keys(): 80 | data[str(pfilter)] = data.pop(pfilter) 81 | 82 | with open(path, "wt") as fd: 83 | kwargs = dict(indent=2, sort_keys=True) 84 | json.dump(data, fd, **kwargs) 85 | 86 | @classmethod 87 | def load(cls, path): 88 | """Load a series of CandidateAnnuli objects from a JSON file. 89 | 90 | Deserialize a JSON file created with CandidateAnnuli.dump(), returning 91 | a dictionary which maps each photometric filter (a Passband object) to 92 | a list of the corresponding CandidateAnnuli objects. These lists are 93 | sorted in increasing order by the standard deviation ('stdev' attribute 94 | of the namedtuples), so that the one with the lowest standard deviation 95 | (and therefore the optimal for aperture photometry) is returned first. 96 | 97 | """ 98 | 99 | with open(path, "rt") as fd: 100 | data = json.load(fd) 101 | 102 | # Convert the dictionaries back to namedtuples, and then sort them by 103 | # their standard deviation, in increasing order. 104 | for values in data.itervalues(): 105 | for index in xrange(len(values)): 106 | values[index] = cls(**values[index]) 107 | values.sort(key=operator.attrgetter("stdev")) 108 | 109 | # Use Passband objects as keys 110 | for pfilter in data.keys(): 111 | data[passband.Passband(pfilter)] = data.pop(pfilter) 112 | 113 | return data 114 | -------------------------------------------------------------------------------- /util/log.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Copyright (c) 2019 Victor Terron. All rights reserved. 4 | # 5 | # This file is part of LEMON. 6 | # 7 | # LEMON is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import logging 21 | import re 22 | import sys 23 | import warnings 24 | 25 | 26 | def func_catchall(func, *args, **kwargs): 27 | """Return func(*args, **kwargs), or None if an exception is raised. 28 | 29 | Return whatever func() returns when called with the positional arguments 30 | 'args' and keyword arguments 'keywords'. If any exception is raised by 31 | func(), catch it, log it and return None. This is a convenience function 32 | useful when we need to call a function that may raise an exception, 33 | scenario in which we want to use None instead of what would have been 34 | normally returned. 35 | 36 | """ 37 | 38 | try: 39 | return func(*args, **kwargs) 40 | except Exception as e: 41 | exc_type = sys.exc_info()[0] 42 | args = (func.__name__, exc_type.__name__, str(e)) 43 | msg = "%s() raised %s (%s), None returned instead" % args 44 | logging.debug(msg) 45 | return None 46 | 47 | 48 | class StreamToWarningFilter(object): 49 | """A file-like class that matches strings and issues them as warnings. 50 | 51 | This class creates a file-like object that normally writes a string to 52 | 'fd', a file type object. However, those lines that match 'regexp' are 53 | issued as warnings of the 'category' class and logged at INFO level. The 54 | message used for the warning is that matched by the 'msg' named group that 55 | must be present in the regular expression; otherwise, IndexError is raised. 56 | This class may be used (and, in fact, was coded with this purpose in mind), 57 | for example, to capture a message written by a third-party library to the 58 | standard error and issue it as a warning instead. 59 | 60 | For example, consider the scenario where the following object is created: 61 | fd = StreamToWarningFilter(sys.stdout, 'v(?P(\d\.?)+)', UserWarning) 62 | After this, fd.write('v2.19.5') will raise UserWarning (with the message 63 | '2.19.5'), while fd.write('Nobody expects the Spanish inquisition') will 64 | not match the regexp and therefore print the string to sys.stdout. 65 | 66 | """ 67 | 68 | def __init__(self, fd, regexp, category): 69 | self.fd = fd 70 | self.regexp = regexp 71 | self.category = category 72 | 73 | def write(self, str_): 74 | """ Write str_ to the file; issue as a warning if regexp is matched""" 75 | 76 | match = re.match(self.regexp, str_) 77 | if match: 78 | msg = match.group("msg") 79 | logging.info(msg) 80 | warnings.warn(msg, self.category) 81 | else: 82 | self.fd.write(str_) 83 | 84 | def flush(self): 85 | self.fd.flush() 86 | 87 | def close(self): 88 | self.fd.close() 89 | 90 | 91 | class LoggerWriter(object): 92 | """Wrap a logger with a file-like API. 93 | 94 | Sometimes, we need to interface to a third-party API which expects a 95 | file-like object to write to, but we want to direct the API's output to a 96 | logger. This can be done using this class, based on that written by Vinay 97 | Sajip [https://stackoverflow.com/a/9422332/184363]. 98 | 99 | """ 100 | 101 | def __init__(self, level): 102 | """Initialize the LoggerWritter object. 103 | 104 | The argument 'level' must be a valid logging level: 'debug', 'info', 105 | 'warning', 'error' or 'critical', and will correspond to the function 106 | of the logging module that will be used every time the method write() 107 | is called. For example, LoggerWriter('info').write(msg) is equivalent 108 | to logging.info(msg). 109 | 110 | """ 111 | 112 | self.log_func = getattr(logging, level) 113 | 114 | def write(self, msg): 115 | self.log_func(msg) 116 | 117 | def flush(self): 118 | """Do nothing -- required by PyRAF. 119 | 120 | This method does nothing, but it is required in order to be able to 121 | redirect the output of the PyRAF tasks to the logger. Otherwise, the 122 | AttributeError ('LoggerWriter' object has no attribute 'flush') 123 | exception is raised. 124 | 125 | """ 126 | 127 | pass 128 | -------------------------------------------------------------------------------- /test/dss_images.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2013 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from __future__ import division 22 | 23 | """ This module provides access to a series of FITS images that can be used in 24 | the unit tests. In order to keep the size of the repository as small as 25 | possible, these images are not included along with the source code, but instead 26 | downloaded automatically from the STScI Digitized Sky Survey the first time the 27 | module is imported and copied to the ./test_data/fits directory. Any removed 28 | images will be downloaded again the next time the module is imported. In 29 | practical terms, the only thing that we need to know is that the module-level 30 | variable TEST_IMAGES is a set with the paths to several FITS images that were 31 | transparently downloaded from the Digitized Sky Survey. 32 | 33 | """ 34 | 35 | import functools 36 | import re 37 | import os 38 | import os.path 39 | import sys 40 | import urllib 41 | 42 | DATA_DIR = os.path.join(os.path.dirname(__file__), "./test_data") 43 | IMAGES_DIR = os.path.join(DATA_DIR, "fits") 44 | 45 | 46 | def get_dss_image(path, ra, dec): 47 | """Download an image from the STScI Digitized Sky Survey. 48 | 49 | This method uses the DSS CGI script to automatically download a FITS image 50 | from the STScI Digitized Sky Survey, copying it to 'path'. The version of 51 | the survey is POSS2/UKSTU Infrared (second generation, 1 arcsex/pixel). 52 | The rest of the parameters use their default values, which means that the 53 | coordinates are assumed to be J2000 and the dimensions of the FITS image 54 | are 15x15 arcmins. 55 | 56 | DSS Web Access Script Interface Control Document: 57 | http://archive.stsci.edu/dss/script_usage.html 58 | 59 | If any exception is raised by urllib.urlretrieve() (and this includes 60 | KeyboardException, if the user decides to stop the execution), the file 61 | being downloaded is silently removed before the exception is re-raised. 62 | This guarantees that we will not be left with truncated data. 63 | 64 | """ 65 | 66 | base_url = "http://archive.stsci.edu/cgi-bin/dss_search?" 67 | parameters = dict(v="poss2ukstu_ir", ra=ra, dec=dec) 68 | url = base_url + urllib.urlencode(parameters) 69 | 70 | # For example, "Downloading test/test_data/fits/IC_5146.fits: 87 %" 71 | status = functools.partial(("Downloading %s: {0:d} %%" % path).format) 72 | sys.stdout.write(status(0)) 73 | sys.stdout.flush() 74 | 75 | def update_status(count, block_size, total_size): 76 | percent = int(count * block_size / total_size * 100) 77 | sys.stdout.write("\r" + status(percent)) 78 | sys.stdout.flush() 79 | 80 | try: 81 | urllib.urlcleanup() 82 | urllib.urlretrieve(url, filename=path, reporthook=update_status) 83 | except: 84 | try: 85 | os.unlink(path) 86 | except: 87 | pass 88 | raise 89 | finally: 90 | print 91 | 92 | 93 | # Map each object to its right ascension and declination 94 | TEST_OBJECTS = { 95 | "IC 5070": (312.75, 44.37), 96 | "IC 5146": (328.35, 47.267), 97 | "Messier 92": (259.281, 43.136), 98 | "NGC 2264": (100.242, 9.895), 99 | "RMC 136": (98.67, 4.03), 100 | "Serpens": (277.454, 1.247), 101 | "Orion": (83.822, -5.391), 102 | "Trapezium": (83.819, -5.387), 103 | "Trumpler 37": (324.536, 57.447), 104 | "Barnard's Star": (269.452, 4.693), 105 | } 106 | 107 | 108 | def get_image_path(name): 109 | """Determine the local path to which to download the image of an object. 110 | 111 | The base name of the image is that of the astronomical object, but with any 112 | whitespace characters replaced with underscores and the '.fits' extension. 113 | The directory where all the images are downloaded is IMAGES_DIR. 114 | 115 | """ 116 | basename = "%s.fits" % re.sub("\s+", "_", name) 117 | path = os.path.join(IMAGES_DIR, basename) 118 | return os.path.normpath(path) 119 | 120 | 121 | if not os.path.exists(IMAGES_DIR): 122 | os.makedirs(IMAGES_DIR) 123 | 124 | TEST_IMAGES = set() 125 | 126 | for name, (ra, dec) in sorted(TEST_OBJECTS.items()): 127 | path = get_image_path(name) 128 | if not os.path.exists(path): 129 | get_dss_image(path, ra, dec) 130 | TEST_IMAGES.add(path) 131 | -------------------------------------------------------------------------------- /test/test_data/sextractor_noasciihead.cat: -------------------------------------------------------------------------------- 1 | 1 844.359 434.109 100.2910553 +9.4697032 845.428 428.986 0.0016 0.0016 -89.0 3 6.334 8545557 12811.09 5030140 25148.38 8.2460 0.0054 530815.6 8085 0.0006238426 1.562 2 | 2 926.403 493.260 100.3013814 +9.4623269 926.130 492.814 0.0001 0.0001 42.9 7 4.980 1.451282e+08 15277.67 1.379576e+08 10755.87 4.6506 0.0001 1043586 11498 0.0008871913 1.527 3 | 3 868.621 87.703 100.2950454 +9.5130007 868.981 88.247 0.0005 0.0005 84.6 18 1.805 1687773 5936.371 1806055 11282.74 9.3582 0.0068 358059.9 1736 0.0001339506 2.445 4 | 4 824.381 41.622 100.2893902 +9.5186384 824.420 41.680 0.0001 0.0001 -0.2 2 0.949 2445914 2919.919 2354658 1779.543 9.0702 0.0008 503292.4 420 3.240741e-05 1.676 5 | 5 835.545 51.657 100.2908246 +9.5174165 835.552 51.656 0.0001 0.0001 -0.0 2 1.032 4835000 3587.49 4674093 1927.399 8.3258 0.0004 804313.6 634 4.891975e-05 1.835 6 | 6 287.085 29.306 100.2187420 +9.5191253 287.109 29.286 0.0001 0.0001 89.8 0 0.910 1867840 1779.543 1828299 1094.39 9.3449 0.0007 534860.9 156 1.203704e-05 1.067 7 | 7 35.369 24.882 100.1853310 +9.5195156 35.382 24.961 0.0001 0.0001 -0.1 0 0.836 1423676 1527.9 1396500 997.3421 9.6374 0.0008 418107.4 115 8.873457e-06 1.081 8 | 8 806.948 17.857 100.2871619 +9.5215485 808.116 19.477 0.1955 0.1955 -23.3 3 4.374 6874.336 668.2784 22301.68 2584.307 14.1292 0.1258 546.2891 22 1.697531e-06 2.222 9 | 9 813.876 8.434 100.2880866 +9.5227352 810.118 12.496 0.2091 0.2091 0.1 18 4.084 10759.01 854.8647 19686.22 2014.935 14.2646 0.1112 485.2656 36 2.777778e-06 3.162 10 | 10 877.773 13.191 100.2964237 +9.5223040 878.442 12.535 0.1898 0.1898 -77.3 18 3.500 7334.547 697.9941 16434.45 2413.721 14.4606 0.1595 606.8359 24 1.851852e-06 1.842 11 | 11 1171.949 11.431 100.3346245 +9.5232353 1171.984 11.416 0.0018 0.0017 -90.0 0 0.859 114979.5 767.2645 116282.1 878.29 12.3362 0.0082 33955.11 29 2.237654e-06 1.042 12 | 12 1007.948 2.728 100.3134096 +9.5239382 1007.846 3.188 0.0709 0.0643 0.0 27 2.748 23481.08 1017.492 27160.19 1534.529 13.9152 0.0614 1542.508 51 3.935185e-06 1.498 13 | 13 1016.912 6.631 100.3145610 +9.5234716 1016.964 6.723 0.0003 0.0003 89.9 19 0.865 626432.2 1305.827 614346.3 1046.991 10.5290 0.0019 197852.5 84 6.481482e-06 1.071 14 | 14 1023.253 5.818 100.3153875 +9.5235891 1023.261 5.874 0.0007 0.0007 -0.5 19 0.870 305333.6 1094.39 301237.5 987.1127 11.3027 0.0036 91746.89 59 4.552469e-06 1.086 15 | 15 769.909 8.013 100.2823328 +9.5226777 769.966 8.012 0.0008 0.0008 -0.7 0 0.830 222962.2 842.9079 222821.9 866.6564 11.6301 0.0042 85319.17 35 2.700617e-06 1.026 16 | 16 128.857 6.777 100.1976740 +9.5217104 128.933 6.855 0.0007 0.0007 89.5 16 0.874 303882 997.3421 302489.8 923.3594 11.2982 0.0033 105111.1 49 3.780864e-06 1.042 17 | 17 1952.364 7.778 100.4336796 +9.5243017 1952.359 7.854 0.0261 0.0238 -0.6 0 0.792 7049.852 493.5564 6605.125 712.3872 15.4503 0.1171 2235.203 12 9.259259e-07 1.190 18 | 18 1465.682 8.021 100.3723484 +9.5242123 1465.569 8.040 0.0342 0.0314 -0.0 0 0.850 5593.281 450.5532 6215.094 697.9941 15.5164 0.1220 1833.492 10 7.71605e-07 1.128 19 | 19 1447.478 5.934 100.3700380 +9.5244535 1447.446 5.975 0.0158 0.0145 -0.0 16 0.850 13166.91 551.8128 12944.2 753.9198 14.7198 0.0633 3705.555 15 1.157407e-06 1.033 20 | 20 1108.887 7.122 100.3264885 +9.5236333 1108.973 7.068 0.0433 0.0430 -88.3 0 0.771 3314.062 402.9871 3625.68 621.0448 16.1015 0.1860 1461.047 8 6.172839e-07 1.184 21 | 21 184.985 4.916 100.2051261 +9.5219726 184.999 4.929 0.0083 0.0083 90.0 16 0.875 23107.68 652.9137 23901.38 866.6564 14.0539 0.0394 8696.203 21 1.62037e-06 1.017 22 | 22 1691.916 1.760 100.4010942 +9.5252161 1692.030 1.225 0.0002 0.0002 -0.3 24 0.823 633661.2 1085.076 622473.5 740.3345 10.5147 0.0013 300382.2 58 4.475309e-06 1.355 23 | 23 638.753 4.101 100.2651241 +9.5228435 638.806 4.112 0.0303 0.0302 -0.0 16 1.067 7779.977 493.5564 10418.25 976.7761 14.9555 0.1018 3039.453 12 9.259259e-07 1.158 24 | -------------------------------------------------------------------------------- /lemon-completion.sh: -------------------------------------------------------------------------------- 1 | #! bash 2 | # 3 | # Bash completion support for LEMON (commands and --long-options) 4 | # 5 | # Copyright (c) 2013 Victor Terron. All rights reserved. 6 | # Institute of Astrophysics of Andalusia, IAA-CSIC 7 | # Distributed under the GNU General Public License, version 3.0 8 | 9 | # To use these completion routines: 10 | # 1) Copy this file to somewhere (e.g. ~/.lemon-completion.sh). 11 | # 2) Add the following line to your .bashrc/.zshrc: 12 | # source ~/.lemon-completion.sh 13 | 14 | shopt -s extglob 15 | 16 | FITS_EXTS="fit?(s)|FIT?(S)" 17 | JSON_EXTS="json|JSON" 18 | LEMONDB_EXTS="LEMONdB|lemondb" 19 | 20 | # Match the current word against the list given as argument 21 | _match() 22 | { 23 | COMPREPLY=( $(compgen -W "${1}" -- ${cur}) ) 24 | } 25 | 26 | _lemon_import() 27 | { 28 | local opts 29 | opts="--object --pattern --counts --filename --follow --exact 30 | --datek --timek --expk= --objectk --uik" 31 | 32 | if [[ ${cur} != -* ]]; then 33 | _filedir @($FITS_EXTS) 34 | else 35 | _match "${opts}" 36 | fi 37 | } 38 | 39 | _lemon_seeing() 40 | { 41 | local opts 42 | 43 | opts="--filename --maximum --margin --snr-percentile --mean 44 | --sources-percentile --suffix --overwrite --cores --verbose 45 | --fsigma --fwhm_dir --esigma --elong_dir --coaddk --fwhmk" 46 | 47 | if [[ ${cur} == -* ]]; then 48 | _match "${opts}" 49 | else 50 | _filedir @($FITS_EXTS) 51 | fi 52 | } 53 | 54 | _lemon_mosaic() 55 | { 56 | local opts 57 | opts="--overwrite --background-match --no-reprojection --combine 58 | --filter --cores --filterk" 59 | 60 | if [[ ${cur} == -* ]]; then 61 | _match "${opts}" 62 | elif [[ ${prev} == --combine ]]; then 63 | _match "mean median count" 64 | else 65 | _filedir @($FITS_EXTS) 66 | fi 67 | } 68 | 69 | _lemon_astrometry() 70 | { 71 | local opts 72 | opts="--radius --blind --timeout --suffix --cores -o --verbose --rak --deck" 73 | 74 | if [[ ${cur} == -* ]]; then 75 | _match "${opts}" 76 | else 77 | _filedir @($FITS_EXTS) 78 | fi 79 | } 80 | 81 | _lemon_annuli() 82 | { 83 | local opts 84 | opts="--overwrite --margin --gain --cores --verbose --aperture 85 | --annulus --dannulus --min-sky --constant --minimum-constant 86 | --lower --upper --step --sky --width --snr-percentile --mean 87 | --maximum --minimum-images --minimum-stars --pct 88 | --weights-threshold --max-iters --worst-fraction -objectk 89 | --filterk --datek --timek --expk --coaddk --gaink --fwhmk 90 | --airmk --uik" 91 | 92 | if [[ ${cur} == -* ]]; then 93 | _match "${opts}" 94 | else 95 | # Input FITS images / output JSON file 96 | _filedir @($FITS_EXTS|$JSON_EXTS) 97 | fi 98 | } 99 | 100 | _lemon_photometry() 101 | { 102 | local opts 103 | opts="--overwrite --filter --exclude --cbox --maximum --margin 104 | --gain --annuli --cores --verbose --coordinates --epoch --aperture 105 | --annulus --dannulus --min-sky --individual-fwhm --aperture-pix 106 | --annulus-pix --dannulus-pix --snr-percentile --mean --objectk 107 | --filterk --datek --timek --expk --coaddk --gaink --fwhmk --airmk 108 | --uik" 109 | 110 | case $prev in 111 | --annuli) 112 | _filedir @($JSON_EXTS) 113 | return 0 114 | ;; 115 | --coordinates) 116 | _filedir 117 | return 0 118 | ;; 119 | esac 120 | 121 | if [[ ${cur} == -* ]]; then 122 | _match "${opts}" 123 | else 124 | # Input FITS images / output LEMONdB 125 | _filedir @($FITS_EXTS|$LEMONDB_EXTS) 126 | fi 127 | } 128 | 129 | _lemon_diffphot() 130 | { 131 | local opts 132 | opts="--overwrite --cores --verbose --minimum-images --stars 133 | --minimum-stars --pct --weights-threshold --max-iters --worst-fraction" 134 | 135 | if [[ ${cur} == -* ]]; then 136 | _match "${opts}" 137 | else 138 | _filedir @($LEMONDB_EXTS) 139 | fi 140 | } 141 | 142 | _lemon_juicer() 143 | { 144 | _filedir @($LEMONDB_EXTS) 145 | } 146 | 147 | _lemon() 148 | { 149 | local cur prev commands 150 | COMPREPLY=() 151 | cur="${COMP_WORDS[COMP_CWORD]}" 152 | prev="${COMP_WORDS[COMP_CWORD-1]}" 153 | commands="import seeing astrometry mosaic annuli photometry 154 | diffphot juicer" 155 | 156 | # The options that autocomplete depend on the LEMON command being 157 | # executed. For example, the '--exact' option is specific to the 158 | # 'import' command, so it must be available only when that is the 159 | # second word in the current command line (i.e., 'lemon import ...') 160 | 161 | case "${COMP_WORDS[1]}" in 162 | import) 163 | _lemon_import 164 | return 0 165 | ;; 166 | seeing) 167 | _lemon_seeing 168 | return 0 169 | ;; 170 | mosaic) 171 | _lemon_mosaic 172 | return 0 173 | ;; 174 | astrometry) 175 | _lemon_astrometry 176 | return 0 177 | ;; 178 | annuli) 179 | _lemon_annuli 180 | return 0 181 | ;; 182 | photometry) 183 | _lemon_photometry 184 | return 0 185 | ;; 186 | diffphot) 187 | _lemon_diffphot 188 | return 0 189 | ;; 190 | juicer) 191 | _lemon_juicer 192 | return 0 193 | ;; 194 | esac 195 | 196 | _match "${commands}" 197 | 198 | } 199 | 200 | complete -F _lemon lemon 201 | -------------------------------------------------------------------------------- /Misc/CHANGES: -------------------------------------------------------------------------------- 1 | 0.3 (2015-06-08) 2 | ================ 3 | 4 | - Fix NotImplementedError raised by Queue.qsize() on Mac OS X (9ca6b4b) 5 | - Show the stack trace of exceptions raised by map_async() (348e0e5) 6 | - Use unittest2 in Python versions < 2.7 (90d43aa) 7 | - Require SExtractor >= 2.19.5 (8165cbe) 8 | - Add support for user-defined photometric filters (dd278a2) 9 | - Raise an error if the α or δ of an image are out of range (0e28041) 10 | - Add --version option (f2c8971) 11 | - Add --update option (6cb2f79) 12 | - Notify the user when there is a new version available (723e76e) 13 | - Use more widespread default option values (be7671c) 14 | - Use JSON to serialize data — say bye to XML (aeab015) 15 | - Fix outdated version warnings — take dates into account (6dfe004) 16 | - Add support for astronomical objects with known proper motions (4cdc774) 17 | - Differentiate between 'essential' and 'auxiliary' commands (756ebed) 18 | - Add support for sexagesimal coordinates (51277fc) 19 | - Update README for Debian 7 (Wheezy) (ca2b696) 20 | - Remove (lots of) unused code (844a7d8) 21 | - Remove 'periods' command (0748dfc) 22 | - Add support for the Harris photometric system (3117d6a) 23 | 24 | Documentation 25 | ************* 26 | - Add brief introduction (041eb30) 27 | - Add Installation section (edf16f6) 28 | - Add Quickstart (b16ec3d) 29 | - Add "Fork me on GitHub" ribbon (e8ffde5) 30 | - Use 'thumbnail' directive for HTML builds and 'image' for LaTeX builds (f48b5d7) 31 | - Show an example light curve of HAT-P-16b (0e09f7d) 32 | 33 | astrometry 34 | ********** 35 | - Ignore FITS files that cannot be astrometrically solved (ca44b80) 36 | - Add options --rack and --deck (4ad0329 and f879a06) 37 | - Limit the search to the α and δ read from the FITS header (e9d8c7c) 38 | - Add --blind option (0bf7bad) 39 | - Add --timeout option (bddfacd) 40 | - Add multicore support (6ffe2c5) 41 | - Add -o option (2a3eb4f) 42 | 43 | mosaic 44 | ****** 45 | - Add --filter option (76a3a80) 46 | - Reproject the mosaic so that North is up (f73293d) 47 | - Add --no-reprojection option (26c4e07) 48 | - Require all mosaicked images to be astrometrically solved (b16a9bf) 49 | - Ignore the photometric filter of the images unless --filter is given (3c8b08c) 50 | - Add --combine option (2b91673) 51 | - Add workaround for 'background_match' bug in montage.mosaic() (a8ec3c5) 52 | 53 | photometry 54 | ********** 55 | - Avoid bug in IRAF that returns invalid xcenter values (2869943) 56 | - Allow missing FITS keywords in the sources image (afcc3f8) 57 | - Remove warning message if --expk is missing from sources image (d043495) 58 | - Allow to do photometry on the sources FITS image (9edc3ae) 59 | - Allow to use the --filter option multiple times (1585212) 60 | - Add --exclude option (0e0e81d) 61 | - Do not talk about --margin if no astronomical object is ignored (37fc430) 62 | - Make it possible to use Montage mosaics as the sources image (095d6f3) 63 | - Add --cbox option (5d307d3) 64 | - Fix qphot unit tests for 32-bit IRAF (8772356) 65 | 66 | diffphot 67 | ******** 68 | - Remove --output option (fac49a9) 69 | - Fix bug with comparison stars without a standard deviation (cfa00ee) 70 | - Set the default value of --worst-fraction to 0.10 (e36625c) 71 | 72 | juicer 73 | ****** 74 | - Open LEMONdBs from the command line (868c6f0) 75 | - Show celestial coordinates in finding chart (b5cf393) 76 | - Add 'Look up in SIMBAD' button (540eb5c) 77 | - Use world coordinates to mark stars in finding chart (79b8fbf) 78 | - Limit instrumental magnitudes to three decimal places (266780b) 79 | - Add a 'Preferences' dialog to the finding chart (a636d86) 80 | - Fix ValueError if we double click on stars without light curves (785a49d) 81 | - Add an option to use Julian Dates (9a23043) 82 | - Allow to export instrumental magnitudes and SNRs to a file (e6a3f40) 83 | - Plot user-defined filters without a specified color with a random one (3aa1d10) 84 | - Use "Δ magnitude" as the label of the y-axis (fc3fcea) 85 | - Prevent the y-axis label from overlapping with the airmass ticks (3608d07) 86 | - Remove "Select stars by their amplitudes" (257e774) 87 | 88 | seeing 89 | ****** 90 | - Add option --overwrite (3676803) 91 | - Do not modify input FITS files (53406c0) 92 | - Fix error caused by HIERARCH + CONTINUE keywords (f1c4912) 93 | - Avoid unnecessary copy of the temporary FITS files (68ca820) 94 | 95 | 0.2 (2014-03-24) 96 | ================ 97 | 98 | - Remove the 'offsets' command (6b473af) 99 | - Identify astronomical objects by their α and δ (2dff622) 100 | - Use IPAC's Montage to assemble images into a mosaic (abc2438) 101 | - Do astrometry on multiple FITS files at once (027819c) 102 | - Add LEMON icon — by Sofía León (e7b72e9) 103 | - Use a local build of Astrometry.net for calibration (9b8a82a) 104 | - Enforce a minimum version of dependencies (10575ec) 105 | - Add support for Cousins, Gunn, SDSS, 2MASS, Strömgren and H-alpha (8bad300) 106 | - Use Travis for continuous integration (5d23b2f) 107 | - Start writing the documentation using Sphinx (e8e8632) 108 | - Add Bash completion support (31937c5) 109 | - Add the 'lemon' porcelain command (c48ed0e) 110 | - Allow simultaneous observations (d808766) 111 | - Switch from xml.dom.minidom to lxml — much faster (c5a9b34) 112 | - Use .LEMONdB as the database extension (fcfe881) 113 | - Add Juicer, the GUI for data analysis (675953e) 114 | - Add unit tests for several modules (f52f9c8) 115 | 116 | 0.1 (2012-06-04) 117 | ================ 118 | - Initial release 119 | -------------------------------------------------------------------------------- /util/test/test_log.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Copyright (c) 2014 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from __future__ import division 22 | 23 | import StringIO 24 | import operator 25 | import os 26 | import warnings 27 | 28 | 29 | # LEMON modules 30 | from test import unittest 31 | import util 32 | 33 | 34 | class FuncCatchllallTest(unittest.TestCase): 35 | def test_func_catchall(self): 36 | 37 | # Returns func(*args, **kwargs) ... 38 | self.assertEqual(3, util.func_catchall(operator.div, 9, 3)) 39 | self.assertEqual(4, util.func_catchall(int, "4")) 40 | self.assertEqual(-5, util.func_catchall(max, -1, -5, 4, key=abs)) 41 | 42 | # ... unless the function raises an exception. In that case, it is 43 | # catched and None is returned instead. 44 | 45 | def foo_except(): 46 | raise ValueError 47 | 48 | self.assertEqual(None, util.func_catchall(foo_except)) 49 | self.assertEqual(None, util.func_catchall(operator.div, 1, 0)) 50 | 51 | 52 | class StreamToWarningFilterTest(unittest.TestCase): 53 | def test_filter_stream(self): 54 | 55 | # A filter that, if the 'foa', 'fooa' or 'foooa' strings are matched, 56 | # issues UserWarning using the string as the warning message. If not, 57 | # writes the string to the output stream (here, a StringIO) 58 | 59 | output = StringIO.StringIO() 60 | expected_output = "fo{1,3}a" 61 | regexp = "(?P{0})".format(expected_output) 62 | category = UserWarning 63 | args = output, regexp, category 64 | stdout_filter = util.StreamToWarningFilter(*args) 65 | 66 | with warnings.catch_warnings(): 67 | warnings.filterwarnings("error") 68 | 69 | # 'fooa' matches the regexp, so issue the warning 70 | with self.assertRaisesRegexp(category, expected_output): 71 | stdout_filter.write("fooa") 72 | 73 | # 'spam' does not match, so write to the output stream 74 | str_ = "spam" 75 | stdout_filter.write(str_) 76 | self.assertEqual(output.getvalue(), str_) 77 | 78 | # A filter that, if "Warning: Keyword: EXPTIME not found" is matched, 79 | # issues RuntimeWarning with "EXPTIME not matched" as its message. If 80 | # not, writes the string to the output stream (again, a StringIO). 81 | 82 | output = StringIO.StringIO() 83 | expected_output = "EXPTIME not found" 84 | regexp = "Warning: Keyword: (?P{0})".format(expected_output) 85 | category = RuntimeWarning 86 | args = output, regexp, category 87 | stdout_filter = util.StreamToWarningFilter(*args) 88 | 89 | with warnings.catch_warnings(): 90 | warnings.filterwarnings("error") 91 | 92 | with self.assertRaisesRegexp(category, expected_output): 93 | str_ = "Warning: Keyword: EXPTIME not found" 94 | stdout_filter.write(str_) 95 | 96 | str_ = str_.replace("EXPTIME", "OBJECT") 97 | stdout_filter.write(str_) 98 | self.assertEqual(output.getvalue(), str_) 99 | 100 | # The example mentioned in the class docstring 101 | 102 | output = StringIO.StringIO() 103 | expected_output = "2.19.5" 104 | regexp = "v(?P(\d\.?)+)" 105 | category = UserWarning 106 | args = output, regexp, category 107 | stdout_filter = util.StreamToWarningFilter(*args) 108 | 109 | with warnings.catch_warnings(): 110 | warnings.filterwarnings("error") 111 | 112 | with self.assertRaisesRegexp(category, expected_output): 113 | stdout_filter.write("v2.19.5") 114 | 115 | str_ = "Nobody expects the Spanish inquisition" 116 | stdout_filter.write(str_) 117 | self.assertEqual(output.getvalue(), str_) 118 | 119 | # The regular expression does not include the mandatory 'msg' named 120 | # group, so IndexError is raised when the string is matched, as the 121 | # StreamToWarningFilter class tries to refer to a non-existent group. 122 | 123 | output = StringIO.StringIO() 124 | regexp = "spam" 125 | args = output, regexp, UserWarning 126 | stdout_filter = util.StreamToWarningFilter(*args) 127 | 128 | with self.assertRaisesRegexp(IndexError, "no such group"): 129 | stdout_filter.write("spam") 130 | 131 | def test_filter_close(self): 132 | 133 | fd = open(os.devnull, "wt") 134 | regexp = "Keyword (?PEXPTIME) not found" 135 | args = fd, regexp, RuntimeWarning 136 | devnull_filter = util.StreamToWarningFilter(*args) 137 | # Must close the underlying file object 138 | devnull_filter.close() 139 | self.assertTrue(fd.closed) 140 | -------------------------------------------------------------------------------- /Doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\LEMON.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\LEMON.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /Doc/_themes/kr_small/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- flasky theme based on nature theme. 6 | * 7 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'Georgia', serif; 18 | font-size: 17px; 19 | color: #000; 20 | background: white; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.documentwrapper { 26 | float: left; 27 | width: 100%; 28 | } 29 | 30 | div.bodywrapper { 31 | margin: 40px auto 0 auto; 32 | width: 700px; 33 | } 34 | 35 | hr { 36 | border: 1px solid #B1B4B6; 37 | } 38 | 39 | div.body { 40 | background-color: #ffffff; 41 | color: #3E4349; 42 | padding: 0 30px 30px 30px; 43 | } 44 | 45 | img.floatingflask { 46 | padding: 0 0 10px 10px; 47 | float: right; 48 | } 49 | 50 | div.footer { 51 | text-align: right; 52 | color: #888; 53 | padding: 10px; 54 | font-size: 14px; 55 | width: 650px; 56 | margin: 0 auto 40px auto; 57 | } 58 | 59 | div.footer a { 60 | color: #888; 61 | text-decoration: underline; 62 | } 63 | 64 | div.related { 65 | line-height: 32px; 66 | color: #888; 67 | } 68 | 69 | div.related ul { 70 | padding: 0 0 0 10px; 71 | } 72 | 73 | div.related a { 74 | color: #444; 75 | } 76 | 77 | /* -- body styles ----------------------------------------------------------- */ 78 | 79 | a { 80 | color: #004B6B; 81 | text-decoration: underline; 82 | } 83 | 84 | a:hover { 85 | color: #6D4100; 86 | text-decoration: underline; 87 | } 88 | 89 | div.body { 90 | padding-bottom: 40px; /* saved for footer */ 91 | } 92 | 93 | div.body h1, 94 | div.body h2, 95 | div.body h3, 96 | div.body h4, 97 | div.body h5, 98 | div.body h6 { 99 | font-family: 'Garamond', 'Georgia', serif; 100 | font-weight: normal; 101 | margin: 30px 0px 10px 0px; 102 | padding: 0; 103 | } 104 | 105 | {% if theme_index_logo %} 106 | div.indexwrapper h1 { 107 | text-indent: -999999px; 108 | background: url({{ theme_index_logo }}) no-repeat center center; 109 | height: {{ theme_index_logo_height }}; 110 | } 111 | {% endif %} 112 | 113 | div.body h2 { font-size: 180%; } 114 | div.body h3 { font-size: 150%; } 115 | div.body h4 { font-size: 130%; } 116 | div.body h5 { font-size: 100%; } 117 | div.body h6 { font-size: 100%; } 118 | 119 | a.headerlink { 120 | color: white; 121 | padding: 0 4px; 122 | text-decoration: none; 123 | } 124 | 125 | a.headerlink:hover { 126 | color: #444; 127 | background: #eaeaea; 128 | } 129 | 130 | div.body p, div.body dd, div.body li { 131 | line-height: 1.4em; 132 | } 133 | 134 | div.admonition { 135 | background: #fafafa; 136 | margin: 20px -30px; 137 | padding: 10px 30px; 138 | border-top: 1px solid #ccc; 139 | border-bottom: 1px solid #ccc; 140 | } 141 | 142 | div.admonition p.admonition-title { 143 | font-family: 'Garamond', 'Georgia', serif; 144 | font-weight: normal; 145 | font-size: 24px; 146 | margin: 0 0 10px 0; 147 | padding: 0; 148 | line-height: 1; 149 | } 150 | 151 | div.admonition p.last { 152 | margin-bottom: 0; 153 | } 154 | 155 | div.highlight{ 156 | background-color: white; 157 | } 158 | 159 | dt:target, .highlight { 160 | background: #FAF3E8; 161 | } 162 | 163 | div.note { 164 | background-color: #eee; 165 | border: 1px solid #ccc; 166 | } 167 | 168 | div.seealso { 169 | background-color: #ffc; 170 | border: 1px solid #ff6; 171 | } 172 | 173 | div.topic { 174 | background-color: #eee; 175 | } 176 | 177 | div.warning { 178 | background-color: #ffe4e4; 179 | border: 1px solid #f66; 180 | } 181 | 182 | p.admonition-title { 183 | display: inline; 184 | } 185 | 186 | p.admonition-title:after { 187 | content: ":"; 188 | } 189 | 190 | pre, tt { 191 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 192 | font-size: 0.85em; 193 | } 194 | 195 | img.screenshot { 196 | } 197 | 198 | tt.descname, tt.descclassname { 199 | font-size: 0.95em; 200 | } 201 | 202 | tt.descname { 203 | padding-right: 0.08em; 204 | } 205 | 206 | img.screenshot { 207 | -moz-box-shadow: 2px 2px 4px #eee; 208 | -webkit-box-shadow: 2px 2px 4px #eee; 209 | box-shadow: 2px 2px 4px #eee; 210 | } 211 | 212 | table.docutils { 213 | border: 1px solid #888; 214 | -moz-box-shadow: 2px 2px 4px #eee; 215 | -webkit-box-shadow: 2px 2px 4px #eee; 216 | box-shadow: 2px 2px 4px #eee; 217 | } 218 | 219 | table.docutils td, table.docutils th { 220 | border: 1px solid #888; 221 | padding: 0.25em 0.7em; 222 | } 223 | 224 | table.field-list, table.footnote { 225 | border: none; 226 | -moz-box-shadow: none; 227 | -webkit-box-shadow: none; 228 | box-shadow: none; 229 | } 230 | 231 | table.footnote { 232 | margin: 15px 0; 233 | width: 100%; 234 | border: 1px solid #eee; 235 | } 236 | 237 | table.field-list th { 238 | padding: 0 0.8em 0 0; 239 | } 240 | 241 | table.field-list td { 242 | padding: 0; 243 | } 244 | 245 | table.footnote td { 246 | padding: 0.5em; 247 | } 248 | 249 | dl { 250 | margin: 0; 251 | padding: 0; 252 | } 253 | 254 | dl dd { 255 | margin-left: 30px; 256 | } 257 | 258 | pre { 259 | padding: 0; 260 | margin: 15px -30px; 261 | padding: 8px; 262 | line-height: 1.3em; 263 | padding: 7px 30px; 264 | background: #eee; 265 | border-radius: 2px; 266 | -moz-border-radius: 2px; 267 | -webkit-border-radius: 2px; 268 | } 269 | 270 | dl pre { 271 | margin-left: -60px; 272 | padding-left: 60px; 273 | } 274 | 275 | tt { 276 | background-color: #ecf0f3; 277 | color: #222; 278 | /* padding: 1px 2px; */ 279 | } 280 | 281 | tt.xref, a tt { 282 | background-color: #FBFBFB; 283 | } 284 | 285 | a:hover tt { 286 | background: #EEE; 287 | } 288 | -------------------------------------------------------------------------------- /Doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/LEMON.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/LEMON.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/LEMON" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/LEMON" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /check_versions.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2013 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | """ Use Python import hooks to enforce a minimum version of those modules 22 | defined in the requirements.txt file. Another option would have been to check 23 | the versions manually, but that would have forced us to do it multiple times 24 | and across all the code, every time one of the modules were imported. And, as 25 | more code is added in the future, we could forget to test for this. With this 26 | solution we only need to define the hooks once, here, and this guarantee that 27 | the modules will always have the required version, no matter how, when or 28 | where they are imported. 29 | 30 | """ 31 | 32 | import imp 33 | import re 34 | import sys 35 | 36 | # LEMON module 37 | import astromatic 38 | 39 | 40 | def version_to_str(version): 41 | """ From (0, 2, 4) to '0.2.4' for example """ 42 | return ".".join(str(x) for x in version) 43 | 44 | 45 | def str_to_version(version): 46 | """ From '0.2.4' to (0, 2, 4), for example """ 47 | return tuple(int(x) for x in version.split(".")) 48 | 49 | 50 | class RequireModuleVersionHook(object): 51 | """An import hook to enforce minimum versions of Python modules. 52 | 53 | This class implements both the module finder (find_module) and loader 54 | (load_module). It can be installed in sys.meta_path to intercept every 55 | attempt to import a new module, checking whether it has the required 56 | version and raising ImportError otherwise. 57 | 58 | """ 59 | 60 | def __init__(self, fullname, min_version, vfunc): 61 | """Instantiation method for the RequireModuleVersionHook class. 62 | 63 | The 'fullname' parameter is the a fully qualified name of the module 64 | that we want to import, such as 'pyfits' or 'scipy'. 'min_version' is a 65 | tuple of integers specifying the minimum version of the module, such as 66 | (1, 2, 1). Finally, 'vfunc' is a hook function that will be passed the 67 | module object, after it is imported, and that must return its version 68 | as a tuple of integers. 69 | 70 | """ 71 | 72 | self.fullname = fullname 73 | self.min_version = min_version 74 | self.vfunc = vfunc 75 | 76 | def find_module(self, fullname, path=None): 77 | """The module finder. 78 | 79 | Receive the fully qualified name of the module to be imported, along 80 | with, optionally, the path where it is supposed to be found. Return 81 | None if the module cannot be found by this particular finder and self 82 | (since this class also implements the module loader) otherwise. 83 | 84 | """ 85 | 86 | if fullname == self.fullname: 87 | self.path = path 88 | return self 89 | return None 90 | 91 | def load_module(self, fullname): 92 | """The module loader. 93 | 94 | Receive the fully qualified name of the module that we want to import. 95 | Then, import the module, call vfunc() to get its version as a tuple of 96 | integers and compare it to the specified minimum version: if the module 97 | version is equal to or higher than the required one; return the module 98 | object; otherwise, raise ImportError. 99 | 100 | """ 101 | 102 | # If module already imported, return it 103 | if fullname in sys.modules: 104 | return sys.modules[fullname] 105 | 106 | module_info = imp.find_module(fullname, self.path) 107 | module = imp.load_module(fullname, *module_info) 108 | 109 | version = self.vfunc(module) 110 | if not version >= self.min_version: 111 | msg = "%s >= %s is required, found %s" 112 | args = (fullname, version_to_str(self.min_version), version_to_str(version)) 113 | raise ImportError(msg % args) 114 | else: 115 | sys.modules[fullname] = module 116 | return module 117 | 118 | 119 | def get__version__(module): 120 | """ Return module.__version__, as a tuple of integers """ 121 | 122 | version = module.__version__ 123 | # Extract '2.1.1' from '2.1.1-r1785' / '3.2' from '3.2.dev' 124 | regexp = "\d+(\.\d+)+" 125 | match = re.match(regexp, version) 126 | if match is None: 127 | msg = "cannot extract version from '%s' (%s)" % (version, module) 128 | raise Exception(msg) 129 | else: 130 | version = match.group(0) 131 | return str_to_version(version) 132 | 133 | 134 | # For each module whose minimum version has been defined in requirements.txt, 135 | # create an import hook and add it to sys.meta_path, which is searched before 136 | # any implicit default finders or sys.path. 137 | 138 | for module, version in [ 139 | ("numpy", (1, 7, 1)), 140 | ("aplpy", (0, 9, 9)), 141 | ("scipy", (0, 12, 0)), 142 | ("matplotlib", (1, 2, 1)), 143 | ("mock", (1, 0, 1)), 144 | ("pyfits", (3, 1, 2)), 145 | ("pyraf", (2, 1, 1)), 146 | ("uncertainties", (2, 4, 1)), 147 | ]: 148 | hook = RequireModuleVersionHook(module, version, get__version__) 149 | sys.meta_path.append(hook) 150 | 151 | # If the minimum version is not met, raise SExtractorUpgradeRequired as 152 | # soon as possible, instead of waiting until we attempt to run SExtractor. 153 | if astromatic.sextractor_version() < astromatic.SEXTRACTOR_REQUIRED_VERSION: 154 | raise astromatic.SExtractorUpgradeRequired() 155 | -------------------------------------------------------------------------------- /test/test_data/filters/SDSS: -------------------------------------------------------------------------------- 1 | # The first element must be the name of the SDSS photometric filter, 2 | # while the second is the letter that the Passband class must identify 3 | # when the former is parsed. 4 | 5 | "u'", 'U' 6 | "g'", 'G' 7 | "rSDSS", 'R' 8 | "rSloan", 'R' 9 | "iSDSS", 'I' 10 | "Isloan", 'I' 11 | "z SDSS", 'Z' 12 | "Z sloan", 'Z' 13 | 14 | "SDSS U", 'U' 15 | "Sloan_U", 'U' 16 | "sdss g'", 'G' 17 | "sloan-g'", 'G' 18 | "r' (SDSS)", 'R' 19 | "r' (Sloan)", 'R' 20 | "i (sdss)", 'I' 21 | "i_(sloan)", 'I' 22 | "z'SDSS", 'Z' 23 | "z'Sloan", 'Z' 24 | 25 | # Suggested by @cdilga at issue #90 26 | # https://github.com/vterron/lemon/issues/90 27 | 28 | "zprime", 'Z' 29 | "uprime", 'U' 30 | "gprime", 'G' 31 | "rprime", 'R' 32 | "iprime", 'I' 33 | 34 | # ... and, by extension: 35 | 36 | "z prime", 'Z' 37 | "u_prime", 'U' 38 | "G-prime", 'G' 39 | "r_PRIME", 'R' 40 | "iPrime", 'I' 41 | 42 | # These are *real* examples of SDSS filters, kindly provided by César 43 | # Husillos and Pablo Ramírez, who found them among the FITS images of 44 | # several astronomical campaigns. 45 | 46 | "g'_SDSS", 'G' 47 | "i'_SDSS", 'I' 48 | "r'_SDSS", 'R' 49 | "u'_SDSS", 'U' 50 | "Sloanr", 'R' 51 | "Sloang", 'G' 52 | "Sloanu", 'U' 53 | "Sloani", 'I' 54 | 55 | # Extremely convoluted test cases 56 | 57 | "_g'_--", 'G' 58 | "---__g' ", 'G' 59 | "-g'__", 'G' 60 | " __-_G'____", 'G' 61 | "-g'__sdSS-_ -", 'G' 62 | "__g _(--sDsS - )_ ", 'G' 63 | "_gsDsS--", 'G' 64 | "_g'_-SdSS-", 'G' 65 | "____g_SDss--", 'G' 66 | " -__g'SDSs-", 'G' 67 | "_G-_-sdsS_- _ ", 'G' 68 | " GsdSS_--", 'G' 69 | "_-_G-(_- -sDSs_- _)-_ ", 'G' 70 | "_G'sDSS_ __", 'G' 71 | "-_-G'sDSS___ -", 'G' 72 | "-_ -_GSdss_- ", 'G' 73 | "__G'SdsS__-", 'G' 74 | "_- GSdsS_", 'G' 75 | " _G___-(--_-_SDss_ ) ", 'G' 76 | " __G'-_SDSS---", 'G' 77 | "_i'-- -", 'I' 78 | "- _-i'- _", 'I' 79 | "__-_I' --- __", 'I' 80 | "_-_ - I'-_-_", 'I' 81 | "_-_-I'---", 'I' 82 | "--I'-_-", 'I' 83 | "I'__-_", 'I' 84 | " i -(_ sdss- ) _", 'I' 85 | "_i-_sDss_ ", 'I' 86 | "_ iSdss ", 'I' 87 | "i Sdss_-", 'I' 88 | " __i'- SdsS_", 'I' 89 | "__i -_-_SdSS_", 'I' 90 | "_-i-_SDsS ", 'I' 91 | "-_-i'_-__-( --SDSs-__)-", 'I' 92 | "- i'__ -_(_ - SDSS --)--", 'I' 93 | "__- I_--sdss-_", 'I' 94 | "- IsdsS _ _-", 'I' 95 | " _IsDSs---_", 'I' 96 | "____-I sDSS__", 'I' 97 | " I'_- _(_ _SdSs_-)_--_", 'I' 98 | "-I-__ (_-_SdSS _)__ -", 'I' 99 | "__I-(_SDsS_- -)_--", 'I' 100 | "----I_ _(--- SDsS_)-- -", 'I' 101 | "_--r'-_", 'R' 102 | "----r'_ _ -_-", 'R' 103 | "r'-", 'R' 104 | " --- R'- ", 'R' 105 | "_-R'_ ", 'R' 106 | "_r-__-(_-sdss--)_ _", 'R' 107 | "_ r _sdSS___", 'R' 108 | "_rsDss-", 'R' 109 | "-___-r'___-(_----sDsS-__--)_-__", 'R' 110 | "-- _-r_--_sDsS----", 'R' 111 | "--r'-sDSs--", 'R' 112 | "--rsDSs_-_", 'R' 113 | "--r'___SdsS--__", 'R' 114 | "_-rSdSs _", 'R' 115 | "-__-R sdSs_ ", 'R' 116 | "_- R' --(-__-SdSS_)--__", 'R' 117 | "RSDSS", 'R' 118 | " sdssG_-__", 'G' 119 | "-sdSsg--_", 'G' 120 | "__-_sdSs--G'-", 'G' 121 | " ---SdsSg'_--", 'G' 122 | "_- -SDSs G'___", 'G' 123 | "-sdssi_--", 'I' 124 | " sDsSi-", 'I' 125 | "__-sDSs-_ _-I_-", 'I' 126 | "_-_ sDSS_I' _", 'I' 127 | "-Sdss __I_ ", 'I' 128 | "_-SdSsi' -", 'I' 129 | "SdSsI_ ", 'I' 130 | "-SdSS_-I---", 'I' 131 | "-sdss_R-__", 'R' 132 | "_-sdSSr___- ", 'R' 133 | "_-- sDss__-r'__ ", 'R' 134 | "--_sDss_r--_-", 'R' 135 | "-sDss r", 'R' 136 | "-sDssr'_--", 'R' 137 | "__sDSs__R--", 'R' 138 | " Sdssr_-", 'R' 139 | "_- Sdss-- r'__", 'R' 140 | "-SDSs-_r__", 'R' 141 | "_SDSSR_-", 'R' 142 | "sdss-_-u_", 'U' 143 | "-_ sdsSu' ", 'U' 144 | " _--sDss_u_", 'U' 145 | " _ sDsSU'-___", 'U' 146 | "sDSSu- ", 'U' 147 | "_ -_Sdss_u-", 'U' 148 | "_-_- SdsS- u'___", 'U' 149 | "-_SdsS__u", 'U' 150 | "SdSsu", 'U' 151 | "__SDsSu_", 'U' 152 | "_SDsS_ U'- ", 'U' 153 | "sdsSZ_", 'Z' 154 | " --sdSs--z__-- ", 'Z' 155 | "-sdSs_--z__-", 'Z' 156 | "_sdSS__z'- ", 'Z' 157 | "__ -sDsS_-- Z'-", 'Z' 158 | "_-_SdSs_z__-", 'Z' 159 | "_-_-SdSS_ --z _-_", 'Z' 160 | "_SdSSz__- ", 'Z' 161 | " _-SDSsZ_ _", 'Z' 162 | " --__SDSsZ'_", 'Z' 163 | "--SDSs__-Z'--__", 'Z' 164 | "-_-SDSSZ'_ __", 'Z' 165 | " __-u--sdss_", 'U' 166 | "usDSS - ", 'U' 167 | "_--_uSDss-_", 'U' 168 | "____-u_- -(SDSS-_) -__", 'U' 169 | "_uSDSS_", 'U' 170 | "__U'sdss", 'U' 171 | " -__U'_-(-__sdSs-_)__ _", 'U' 172 | "---U'_-_sdSs_ ", 'U' 173 | "-U--sdSS _ -", 'U' 174 | "--_-U'-_ sDSS_ ", 'U' 175 | "--_-USdss-", 'U' 176 | "__ -U'SdsS _- ", 'U' 177 | " -U- _-SDSs__", 'U' 178 | "_-_ u' __ -", 'U' 179 | "_-u'_-___", 'U' 180 | "-- -u'__", 'U' 181 | "-- U'_", 'U' 182 | "--__U'_", 'U' 183 | "U'__ -", 'U' 184 | "_-z'__-(_ sDss__) -__", 'Z' 185 | "_-z -_sDSS_--_-", 'Z' 186 | "--z'SDss-", 'Z' 187 | "_ z'_-(__- SDsS)_ -_", 'Z' 188 | "-z -( SDsS-)_-", 'Z' 189 | "--- z-SDSs_ ", 'Z' 190 | "--zSDSs_ -_", 'Z' 191 | "--Z _-(_sDsS__)-", 'Z' 192 | "_-Z'_(_ - _Sdss__ )-_-_", 'Z' 193 | "- _Z'--(--SdsS-- )__-", 'Z' 194 | "-ZSdSs-", 'Z' 195 | " _Z_---(_SDsS )-", 'Z' 196 | " _-Z'--_-(-_SDSs )-", 'Z' 197 | " z'___", 'Z' 198 | "-_ z' ", 'Z' 199 | "--__z'_", 'Z' 200 | "---_z'___", 'Z' 201 | " --_Z' --- __- ", 'Z' 202 | "_--Z'__-", 'Z' 203 | "-_ Z'_", 'Z' 204 | "--Z'__ ", 'Z' 205 | "-Z'--_ ", 'Z' 206 | "Z'__", 'Z' 207 | "SLOANz -", 'Z' 208 | "-SLOaNz ", 'Z' 209 | " U--_(_sLOAN)__ ", 'U' 210 | "g_-__(-- SloAN)--__", 'G' 211 | "____i--SloAN", 'I' 212 | "_g(_-SloAN)", 'G' 213 | "rSlOan", 'R' 214 | " - sLoANg__-", 'G' 215 | "_--USloAn ", 'U' 216 | "- _SLOANr_ -", 'R' 217 | "___z_(slOan-)__", 'Z' 218 | "-Gsloan", 'G' 219 | "___SLOaNG__-", 'G' 220 | " __u__(SlOan_--)--", 'U' 221 | "_zsLoAn__- ", 'Z' 222 | "_-Rsloan _ -", 'R' 223 | "_-SlOanU--", 'U' 224 | "_-__I_(- --sLOaN_) -_", 'I' 225 | "_____USLoaN _ -", 'U' 226 | " -sLoang_- ", 'G' 227 | "_--rSLoan_- _", 'R' 228 | "--_G _ SlOaN_-_", 'G' 229 | " SlOAN-_G_-__ ", 'G' 230 | " -sLOaNI", 'I' 231 | "__r-_(_SLoAn- )-- __", 'R' 232 | "SLoaNG_", 'G' 233 | "_ -i- _- slOan_-_", 'I' 234 | "-__-zsloan_- ", 'Z' 235 | "_ _sLOanG_-", 'G' 236 | "_-_I----_SlOan -", 'I' 237 | "_-__ sLOanI_", 'I' 238 | "SLoang-", 'G' 239 | "-zslOAn--_", 'Z' 240 | "sLoAnR---_", 'R' 241 | "---SLOAni --", 'I' 242 | "zsloan_-", 'Z' 243 | "--U (sLoan--_)___", 'U' 244 | "sLOAn_G_-", 'G' 245 | "_-SLoAn_-u__", 'U' 246 | " _I-sloaN-", 'I' 247 | "_sLOAN_ R --", 'R' 248 | "-_Z_ SLOAn", 'Z' 249 | "-_--SlOAnI_-", 'I' 250 | "G_(_SLOAN)-", 'G' 251 | "__ SlOAn-_I_ ", 'I' 252 | "z(____slOan___)_", 'Z' 253 | "G-sLOAn ", 'G' 254 | "G--__(---sLOAN--) ", 'G' 255 | "sLoanr_-", 'R' 256 | "isLOan ", 'I' 257 | -------------------------------------------------------------------------------- /util/coords.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Copyright (c) 2019 Victor Terron. All rights reserved. 4 | # 5 | # This file is part of LEMON. 6 | # 7 | # LEMON is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import math 21 | import re 22 | 23 | 24 | def DMS_to_DD(degrees, arcminutes, arcseconds): 25 | """ Degrees, arcminutes, arcseconds to decimal degrees conversion. """ 26 | 27 | decimal = abs(degrees) + float(arcminutes) / 60 + float(arcseconds) / 3600 28 | if degrees < 0: 29 | decimal = -decimal 30 | return decimal 31 | 32 | 33 | def DD_to_DMS(decimal_degrees): 34 | """ Decimal degrees to degrees, arcminutes, arcseconds conversion. """ 35 | 36 | # math.modf(x) returns the fractional and integer parts of x 37 | arcminutes, degrees = math.modf(decimal_degrees) 38 | arcminutes = abs(arcminutes) * 60 # do not propagate the minus sign, if any 39 | arcseconds, arcminutes = math.modf(arcminutes) 40 | arcseconds *= 60 41 | return int(degrees), int(arcminutes), arcseconds 42 | 43 | 44 | def HMS_to_DD(hours, minutes, seconds): 45 | """ Hours, minutes, seconds to decimal degrees conversion. """ 46 | 47 | decimal = abs(hours) * 15 + float(minutes) / 60 * 15 + float(seconds) / 3600 * 15 48 | if hours < 0: 49 | decimal = -decimal 50 | return decimal 51 | 52 | 53 | def DD_to_HMS(decimal_degrees): 54 | """ Decimal degrees to hours, minutes, seconds conversion. """ 55 | 56 | # math.modf(x) returns the fractional and integer parts of x 57 | minutes, hours = math.modf(decimal_degrees / 15.0) 58 | hours = abs(hours) # do not propagate the minus sign, if any 59 | minutes = abs(minutes) * 60 60 | seconds, minutes = math.modf(minutes) 61 | seconds *= 60 62 | return int(hours), int(minutes), seconds 63 | 64 | 65 | def ra_str(decimal_degrees): 66 | """Return the string representation of a right ascension. 67 | Example: 14h 03m 12.64s""" 68 | 69 | ra_hours, ra_min, ra_sec = DD_to_HMS(decimal_degrees) 70 | ra_hours = "%02d" % ra_hours 71 | ra_min = "%02d" % ra_min 72 | ra_sec = "%05.2f" % ra_sec 73 | return "%sh %sm %ss" % (ra_hours, ra_min, ra_sec) 74 | 75 | 76 | def dec_str(decimal_degrees): 77 | """Return the string representation of a declination. 78 | Example: +57d 33m 10.75s""" 79 | 80 | dec_deg, dec_arcmin, dec_arcsec = DD_to_DMS(decimal_degrees) 81 | dec_deg = "%+03d" % dec_deg 82 | dec_arcmin = "%02d" % dec_arcmin 83 | dec_arcsec = "%05.2f" % dec_arcsec 84 | return "%sd %sm %ss" % (dec_deg, dec_arcmin, dec_arcsec) 85 | 86 | 87 | def load_coordinates(path): 88 | """Load a list of celestial coordinates from a text file. 89 | 90 | Parse a text file containing the celestial coordinates of a series of 91 | astronomical objects, one per line, and return a generator that yields (ra, 92 | dec, pm_ra, pm_dec) tuples. The file must have two columns, with the right 93 | ascension and declination (in decimal degrees) and, optionally, two other 94 | columns with the proper motion in right ascension and declination (in 95 | seconds of arc per year) surrounded by brackets. For example: 96 | 97 | 269.456271 4.665281 98 | 269.452075 4.693391 [-0.79858] [10.32812] # Barnard's Star 99 | 269.466450 4.705625 [0.0036] [-.0064] # TYC 425-262-1 100 | 101 | The four-element tuples contain the right ascension, declination, proper 102 | motion in right ascension and proper motion in declination, respectively. 103 | Nones are used in case the proper motion of an astronomical object is not 104 | specified. Empty lines, as well as comments (which start with the hash 105 | character, #, and extend to the end of the physical line) are ignored. 106 | 107 | ValueError is raised (a) if in any line there is a number of coordinates 108 | other than two (right ascension and declination) or the proper motions are 109 | not surrounded by brackets, (b) if any right ascension or declination is 110 | out of range or (c) if the proper motion in right ascension is specified 111 | but not that in declination, or vice versa. 112 | 113 | """ 114 | 115 | with open(path, "rt") as fd: 116 | for line in fd: 117 | 118 | # Ignore comments 119 | line = line.split("#")[0] 120 | 121 | # Ignore empty lines 122 | regexp = "^\s*$" 123 | if re.match(regexp, line): 124 | continue 125 | 126 | kwargs = dict(float="([+-]?\d+(\.\d+)(?:[eE][+\-]?\d+)*)") 127 | regexp = ( 128 | "^\s*" 129 | "(?P{float})" 130 | "\s+" 131 | "(?P{float})" 132 | "(" 133 | "\s+" 134 | "\[\s*(?P{float})\s*\]" 135 | "\s+" 136 | "\[\s*(?P{float})\s*\]" 137 | ")?" 138 | "\s*$" 139 | ) 140 | 141 | match = re.match(regexp.format(**kwargs), line) 142 | 143 | if not match: 144 | msg = ( 145 | "Unable to parse line %r. Astronomical objects must be " 146 | "listed one per line with coordinate values in columns one " 147 | "(right ascension) and two (declination). Proper motions " 148 | "may be optionally specified in columns three (ra) and " 149 | "four (dec), surrounded by brackets -- but, in that case, " 150 | "both of them are required." % line 151 | ) 152 | raise ValueError(msg) 153 | 154 | ra = float(match.group("ra")) 155 | if not 0 <= ra < 360: 156 | msg = "Right ascension '%s' not in range [0, 360[ degrees" 157 | raise ValueError(msg % ra) 158 | 159 | dec = float(match.group("dec")) 160 | if not -90 <= dec <= 90: 161 | msg = "Declination '%s' not in range [-90, 90] degrees" 162 | raise ValueError(msg % dec) 163 | 164 | pm_ra = match.group("pm_ra") 165 | if pm_ra is not None: 166 | pm_ra = float(pm_ra) 167 | 168 | pm_dec = match.group("pm_dec") 169 | if pm_dec is not None: 170 | pm_dec = float(pm_dec) 171 | 172 | yield ra, dec, pm_ra, pm_dec 173 | -------------------------------------------------------------------------------- /customparser.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | 3 | # Copyright (c) 2013 Victor Terron. All rights reserved. 4 | # Institute of Astrophysics of Andalusia, IAA-CSIC 5 | # 6 | # This file is part of LEMON. 7 | # 8 | # LEMON is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import copy 22 | import optparse 23 | import re 24 | import textwrap 25 | 26 | # LEMON modules 27 | import passband 28 | 29 | 30 | class NewlinesFormatter(optparse.IndentedHelpFormatter): 31 | """This quick-and-dirty trick prevents optparse from stripping newlines 32 | (using textwrap) when the description of the module is printed. This should 33 | be acceptable enough until the transition to argparse is made.""" 34 | 35 | def _format_text(self, text): 36 | text_width = self.width - self.current_indent 37 | indent = " " * self.current_indent 38 | # Wrap one paragraph at a time, then concatenate them 39 | formatted_text = "" 40 | for paragraph in text.split("\n\n"): 41 | 42 | formatted_text += textwrap.fill( 43 | paragraph.strip(), 44 | text_width, 45 | initial_indent=indent, 46 | subsequent_indent=indent, 47 | ) 48 | formatted_text += "\n\n" 49 | 50 | return formatted_text.rstrip() 51 | 52 | 53 | def check_passband(option, opt, value): 54 | """Type-checking function for the 'passband' optparse option type. 55 | 56 | This is the type-checking function for 'passband', a custom optparse type 57 | that accepts a string with the name of a photometric filter and returns 58 | it as a passband.Passband object. 'option' is an optpase.Option instance, 59 | 'opt' is the option string (e.g., -f), and 'value' is the string from the 60 | command line that must be checked and converted to a Passband object. 61 | 62 | In case of doubt, please refer to: 63 | http://docs.python.org/2.7/library/optparse.html#adding-new-types 64 | 65 | """ 66 | 67 | try: 68 | return passband.Passband(value) 69 | except ValueError, e: 70 | msg = "option %s: invalid photometric filter: %r (%s)" 71 | raise optparse.OptionValueError(msg % (opt, value, e)) 72 | 73 | 74 | class PassbandOption(optparse.Option): 75 | """Custom optparse option type encapsulating a photometric filter. 76 | 77 | This subclass of optparse's Option class implements 'passband', a custom 78 | option type: it receives a string with the name of a photometric filter, 79 | such as 'Johnson V', and returns it as a passband.Passband object. This 80 | option supports all the photometric systems allowed by the Passband class. 81 | 82 | """ 83 | 84 | # A tuple of type names 85 | TYPES = optparse.Option.TYPES + ("passband",) 86 | # A dictionary mapping type names to type-checking functions 87 | TYPE_CHECKER = copy.copy(optparse.Option.TYPE_CHECKER) 88 | TYPE_CHECKER["passband"] = check_passband 89 | 90 | 91 | def get_parser(description): 92 | """Return the OptionParser object used in the LEMON modules. 93 | 94 | This function instantiates an optparse.OptionParser object and returns it. 95 | Its 'description' argument (a paragraph of text giving a brief overview of 96 | the program) is set to the value of the argument of the same name, while 97 | the NewlinesFormatter class is used for printing help text ('formatter' 98 | argument). This parser adds a custom option type, 'passband', which 99 | receives a string with the name of a photometric filter and converts it to 100 | a passband.Passband object. Neither the default -h nor --help options are 101 | included. 102 | 103 | """ 104 | 105 | kwargs = dict( 106 | description=description, 107 | add_help_option=False, 108 | formatter=NewlinesFormatter(), 109 | option_class=PassbandOption, 110 | ) 111 | parser = optparse.OptionParser(**kwargs) 112 | return parser 113 | 114 | 115 | def clear_metavars(parser): 116 | """Set all the meta-variables of an OptionParser to a whitespace. 117 | 118 | This is a hackish convenience function to set the meta-variables of all the 119 | options of an OptionParser, independently of whether they are contained in 120 | option groups, to the string ' '. This is not an empty string, but a string 121 | consisting of a whitespace: this is necessary because if the meta-variable 122 | evaluates to False the OptionParser converts the destination variable name 123 | to uppercase and uses that instead. 124 | 125 | Using this function on an OptionParser instance clears the meta-variables, 126 | which means that where the help message showed '--filename=FILE' now only 127 | '--filename=' will be displayed, with the equals sign indicating the fact 128 | that the option takes a value. 129 | 130 | """ 131 | 132 | EMPTY_VALUE = " " 133 | for option in parser.option_list: 134 | option.metavar = EMPTY_VALUE 135 | for group in parser.__dict__["option_groups"]: 136 | for option in group.option_list: 137 | option.metavar = EMPTY_VALUE 138 | 139 | 140 | def additional_options_callback(option, opt_str, value, parser): 141 | """opt-parse callback function to parse option strings. 142 | 143 | Use this function to parse a string containing an option with, if any, the 144 | argument that it takes. For example, given the string '--downsample = 2', 145 | '--downsample' would be recognized as the name of the option, and '2' as 146 | the corresponding argument. The name of the option is mapped to the value 147 | in option.dest, which is expected to be a dictionary. If the option does 148 | not take any argument (for example, '-v' or '--verbose'), it is mapped to 149 | None. Raises ValueError if the string can not be successfully parsed. 150 | 151 | option - the Option instance that is calling the callback. 152 | opt_str - the option string seen on the command-line. 153 | value - the argument to this option seen on the command-line. 154 | parser - the OptionParser instance driving the whole thing. 155 | 156 | """ 157 | 158 | regexp = "=?(?P