# Licensed under a 3-clause BSD style license - see PYFITS.rst import copy import operator import re import sys import warnings import weakref import numbers from functools import reduce from collections import OrderedDict from contextlib import suppress import numpy as np from numpy import char as chararray from .card import Card, CARD_LENGTH from .util import (pairwise, _is_int, _convert_array, encode_ascii, cmp, NotifierMixin) from .verify import VerifyError, VerifyWarning from astropy.utils import lazyproperty, isiterable, indent from astropy.utils.exceptions import AstropyUserWarning __all__ = ['Column', 'ColDefs', 'Delayed'] # mapping from TFORM data type to numpy data type (code) # L: Logical (Boolean) # B: Unsigned Byte # I: 16-bit Integer # J: 32-bit Integer # K: 64-bit Integer # E: Single-precision Floating Point # D: Double-precision Floating Point # C: Single-precision Complex # M: Double-precision Complex # A: Character FITS2NUMPY = {'L': 'i1', 'B': 'u1', 'I': 'i2', 'J': 'i4', 'K': 'i8', 'E': 'f4', 'D': 'f8', 'C': 'c8', 'M': 'c16', 'A': 'a'} # the inverse dictionary of the above NUMPY2FITS = {val: key for key, val in FITS2NUMPY.items()} # Normally booleans are represented as ints in Astropy, but if passed in a numpy # boolean array, that should be supported NUMPY2FITS['b1'] = 'L' # Add unsigned types, which will be stored as signed ints with a TZERO card. NUMPY2FITS['u2'] = 'I' NUMPY2FITS['u4'] = 'J' NUMPY2FITS['u8'] = 'K' # Add half precision floating point numbers which will be up-converted to # single precision. NUMPY2FITS['f2'] = 'E' # This is the order in which values are converted to FITS types # Note that only double precision floating point/complex are supported FORMATORDER = ['L', 'B', 'I', 'J', 'K', 'D', 'M', 'A'] # Convert single precision floating point/complex to double precision. FITSUPCONVERTERS = {'E': 'D', 'C': 'M'} # mapping from ASCII table TFORM data type to numpy data type # A: Character # I: Integer (32-bit) # J: Integer (64-bit; non-standard) # F: Float (64-bit; fixed decimal notation) # E: Float (64-bit; exponential notation) # D: Float (64-bit; exponential notation, always 64-bit by convention) ASCII2NUMPY = {'A': 'a', 'I': 'i4', 'J': 'i8', 'F': 'f8', 'E': 'f8', 'D': 'f8'} # Maps FITS ASCII column format codes to the appropriate Python string # formatting codes for that type. ASCII2STR = {'A': '', 'I': 'd', 'J': 'd', 'F': 'f', 'E': 'E', 'D': 'E'} # For each ASCII table format code, provides a default width (and decimal # precision) for when one isn't given explicitly in the column format ASCII_DEFAULT_WIDTHS = {'A': (1, 0), 'I': (10, 0), 'J': (15, 0), 'E': (15, 7), 'F': (16, 7), 'D': (25, 17)} # TDISPn for both ASCII and Binary tables TDISP_RE_DICT = {} TDISP_RE_DICT['F'] = re.compile(r'(?:(?P[F])(?:(?P[0-9]+)\.{1}' r'(?P[0-9])+)+)|') TDISP_RE_DICT['A'] = TDISP_RE_DICT['L'] = \ re.compile(r'(?:(?P[AL])(?P[0-9]+)+)|') TDISP_RE_DICT['I'] = TDISP_RE_DICT['B'] = \ TDISP_RE_DICT['O'] = TDISP_RE_DICT['Z'] = \ re.compile(r'(?:(?P[IBOZ])(?:(?P[0-9]+)' r'(?:\.{0,1}(?P[0-9]+))?))|') TDISP_RE_DICT['E'] = TDISP_RE_DICT['G'] = \ TDISP_RE_DICT['D'] = \ re.compile(r'(?:(?P[EGD])(?:(?P[0-9]+)\.' r'(?P[0-9]+))+)' r'(?:E{0,1}(?P[0-9]+)?)|') TDISP_RE_DICT['EN'] = TDISP_RE_DICT['ES'] = \ re.compile(r'(?:(?PE[NS])(?:(?P[0-9]+)\.{1}' r'(?P[0-9])+)+)') # mapping from TDISP format to python format # A: Character # L: Logical (Boolean) # I: 16-bit Integer # Can't predefine zero padding and space padding before hand without # knowing the value being formatted, so grabbing precision and using that # to zero pad, ignoring width. Same with B, O, and Z # B: Binary Integer # O: Octal Integer # Z: Hexadecimal Integer # F: Float (64-bit; fixed decimal notation) # EN: Float (engineering fortran format, exponential multiple of thee # ES: Float (scientific, same as EN but non-zero leading digit # E: Float, exponential notation # Can't get exponential restriction to work without knowing value # before hand, so just using width and precision, same with D, G, EN, and # ES formats # D: Double-precision Floating Point with exponential # (E but for double precision) # G: Double-precision Floating Point, may or may not show exponent TDISP_FMT_DICT = { 'I': '{{:{width}d}}', 'B': '{{:{width}b}}', 'O': '{{:{width}o}}', 'Z': '{{:{width}x}}', 'F': '{{:{width}.{precision}f}}', 'G': '{{:{width}.{precision}g}}' } TDISP_FMT_DICT['A'] = TDISP_FMT_DICT['L'] = '{{:>{width}}}' TDISP_FMT_DICT['E'] = TDISP_FMT_DICT['D'] = \ TDISP_FMT_DICT['EN'] = TDISP_FMT_DICT['ES'] = '{{:{width}.{precision}e}}' # tuple of column/field definition common names and keyword names, make # sure to preserve the one-to-one correspondence when updating the list(s). # Use lists, instead of dictionaries so the names can be displayed in a # preferred order. KEYWORD_NAMES = ('TTYPE', 'TFORM', 'TUNIT', 'TNULL', 'TSCAL', 'TZERO', 'TDISP', 'TBCOL', 'TDIM', 'TCTYP', 'TCUNI', 'TCRPX', 'TCRVL', 'TCDLT', 'TRPOS') KEYWORD_ATTRIBUTES = ('name', 'format', 'unit', 'null', 'bscale', 'bzero', 'disp', 'start', 'dim', 'coord_type', 'coord_unit', 'coord_ref_point', 'coord_ref_value', 'coord_inc', 'time_ref_pos') """This is a list of the attributes that can be set on `Column` objects.""" KEYWORD_TO_ATTRIBUTE = OrderedDict(zip(KEYWORD_NAMES, KEYWORD_ATTRIBUTES)) ATTRIBUTE_TO_KEYWORD = OrderedDict(zip(KEYWORD_ATTRIBUTES, KEYWORD_NAMES)) # TODO: Define a list of default comments to associate with each table keyword # TFORMn regular expression TFORMAT_RE = re.compile(r'(?P^[0-9]*)(?P[LXBIJKAEDCMPQ])' r'(?P