# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""An extensible ASCII table reader and writer.
daophot.py:
Classes to read DAOphot table format
:Copyright: Smithsonian Astrophysical Observatory (2011)
:Author: Tom Aldcroft (aldcroft@head.cfa.harvard.edu)
"""
##
## Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are met:
## * Redistributions of source code must retain the above copyright
## notice, this list of conditions and the following disclaimer.
## * Redistributions in binary form must reproduce the above copyright
## notice, this list of conditions and the following disclaimer in the
## documentation and/or other materials provided with the distribution.
## * Neither the name of the Smithsonian Astrophysical Observatory nor the
## names of its contributors may be used to endorse or promote products
## derived from this software without specific prior written permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
## ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
## WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
## DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
## DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
## (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
## LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
## SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import re
import numpy as np
from . import core
from . import basic
from . import fixedwidth
from ...utils import OrderedDict
[docs]class Daophot(core.BaseReader):
"""Read a DAOphot file.
Example::
#K MERGERAD = INDEF scaleunit %-23.7g
#K IRAF = NOAO/IRAFV2.10EXPORT version %-23s
#K USER = davis name %-23s
#K HOST = tucana computer %-23s
#
#N ID XCENTER YCENTER MAG MERR MSKY NITER \\
#U ## pixels pixels magnitudes magnitudes counts ## \\
#F %-9d %-10.3f %-10.3f %-12.3f %-14.3f %-15.7g %-6d
#
#N SHARPNESS CHI PIER PERROR \\
#U ## ## ## perrors \\
#F %-23.3f %-12.3f %-6d %-13s
#
14 138.538 INDEF 15.461 0.003 34.85955 4 \\
-0.032 0.802 0 No_error
The keywords defined in the #K records are available via the output table
``meta`` attribute::
>>> import os
>>> from astropy.io import ascii
>>> filename = os.path.join(ascii.__path__[0], 'tests/t/daophot.dat')
>>> data = ascii.read(filename)
>>> for name, keyword in data.meta['keywords'].items():
... print name, keyword['value'], keyword['units'], keyword['format']
...
MERGERAD INDEF scaleunit %-23.7g
IRAF NOAO/IRAFV2.10EXPORT version %-23s
USER name %-23s
...
The unit and formats are available in the output table columns::
>>> for colname in data.colnames:
... col = data[colname]
... print colname, col.unit, col.format
...
ID None %-9d
XCENTER pixels %-10.3f
YCENTER pixels %-10.3f
...
Any column values of INDEF are interpreted as a missing value and will be
masked out in the resultant table.
"""
_format_name = 'daophot'
_io_registry_format_aliases = ['daophot']
_io_registry_can_write = False
_description = 'IRAF DAOphot format table'
def __init__(self):
core.BaseReader.__init__(self)
self.header = DaophotHeader()
self.inputter = core.ContinuationLinesInputter()
self.inputter.no_continue = r'\s*#'
self.data.splitter = fixedwidth.FixedWidthSplitter()
self.data.start_line = 0
self.data.comment = r'\s*#'
[docs] def write(self, table=None):
raise NotImplementedError
class DaophotHeader(core.BaseHeader):
"""Read the header from a file produced by the IRAF DAOphot routine."""
def __init__(self):
core.BaseHeader.__init__(self)
self.comment = r'\s*#K'
def update_meta(self, lines, meta):
"""
Extract table-level keywords for DAOphot table. These are indicated by
a leading '#K ' prefix.
"""
table_meta = meta['table']
# Read keywords as a table embedded in the header comments
comment_lines = [line for line in lines if line.startswith('#')]
if len(comment_lines) > 0:
re_header_keyword = re.compile(r'[#]K'
r'\s+ (?P<name> \w+)'
r'\s* = (?P<stuff> .+) $',
re.VERBOSE)
table_meta['keywords'] = OrderedDict()
for line in comment_lines:
m = re_header_keyword.match(line)
if m:
vals = m.group('stuff').strip().rsplit(None, 2)
keyword_dict = {'units': vals[-2],
'format': vals[-1]}
keyword_dict['value'] = (vals[0] if len(vals) > 2 else "")
table_meta['keywords'][m.group('name')] = keyword_dict
def get_cols(self, lines):
"""Initialize the header Column objects from the table ``lines`` for a DAOphot
header. The DAOphot header is specialized so that we just copy the entire BaseHeader
get_cols routine and modify as needed.
:param lines: list of table lines
:returns: list of table Columns
"""
# Parse a series of column defintion lines like below. There may be several
# such blocks in a single file (where continuation characters have already been
# stripped).
# #N ID XCENTER YCENTER MAG MERR MSKY NITER
# #U ## pixels pixels magnitudes magnitudes counts ##
# #F %-9d %-10.3f %-10.3f %-12.3f %-14.3f %-15.7g %-6d
coldef_lines = ['', '', '']
starts = ('#N ', '#U ', '#F ')
col_width = []
col_len_def = re.compile(r'[0-9]+')
re_colformat_def = re.compile(r'#F([^#]+)')
for line in lines:
if not line.startswith('#'):
break # End of header lines
else:
formatmatch = re_colformat_def.search(line)
if formatmatch:
form = formatmatch.group(1).split()
width = ([int(col_len_def.search(s).group()) for s in form])
# original data format might be shorter than 80 characters
# and filled with spaces
width[-1] = 80 - sum(width[:-1])
col_width.extend(width)
for i, start in enumerate(starts):
if line.startswith(start):
line_stripped = line[2:]
coldef_lines[i] = coldef_lines[i] + line_stripped
break
# At this point colddef_lines has three lines corresponding to column
# names, unit, and format. Get the column names by splitting the
# first line on whitespace.
self.names = coldef_lines[0].split()
if not self.names:
raise core.InconsistentTableError('No column names found in DAOphot header')
ends = np.cumsum(col_width)
starts = ends - col_width
# If there wasn't a #U defined (not sure of DAOphot specification), then
# replace the empty line with the right number of ## indicators, which matches
# the DAOphot "no unit" tag.
for i, coldef_line in enumerate(coldef_lines):
if not coldef_line:
coldef_lines[i] = '## ' * len(self.names)
# Read the three lines as a basic table.
reader = core._get_reader(Reader=basic.Basic, comment=None)
reader.header.comment = None
coldefs = reader.read(coldef_lines)
# Create the list of io.ascii column objects
self._set_cols_from_names()
# Set unit and format as needed.
for col in self.cols:
if coldefs[col.name][0] != '##':
col.unit = coldefs[col.name][0]
if coldefs[col.name][1] != '##':
col.format = coldefs[col.name][1]
# Set column start and end positions.
for i, col in enumerate(self.cols):
col.start = starts[i]
col.end = ends[i]
if hasattr(col, 'format'):
if any(x in col.format for x in 'fg'):
col.type = core.FloatType
elif 'd' in col.format:
col.type = core.IntType
elif 's' in col.format:
col.type = core.StrType
# INDEF is the missing value marker
self.data.fill_values.append(('INDEF', '0'))