# Licensed under a 3-clause BSD style license - see LICENSE.rst
# -*- coding: utf-8 -*-
"""This module corresponds to the misc directory in idlutils.
"""
import numpy as np
from . import PydlutilsException
[docs]def decode_mixed(x):
"""Convert bytes in Numpy arrays into strings. Leave other stuff alone.
Parameters
----------
x : object
Input object.
Returns
-------
object
If `x` has a ``decode()`` method, ``x.decode()`` will be returned.
Otherwise `x` will be returned unchanged.
"""
try:
return x.decode()
except:
return x
[docs]def djs_laxisgen(dims, iaxis=0):
"""Returns an integer array where each element of the array is set
equal to its index number along the specified axis.
Parameters
----------
dims : :class:`list`
Dimensions of the array to return.
iaxis : :class:`int`, optional
Index along this dimension.
Returns
-------
:class:`numpy.ndarray`
An array of indexes with ``dtype=int32``.
Raises
------
:exc:`ValueError`
If `iaxis` is greater than or equal to the number of dimensions.
Notes
-----
For two or more dimensions, there is no difference between this routine
and :func:`~pydl.pydlutils.misc.djs_laxisnum`.
Examples
--------
>>> from pydl.pydlutils.misc import djs_laxisgen
>>> print(djs_laxisgen([4,4]))
[[0 0 0 0]
[1 1 1 1]
[2 2 2 2]
[3 3 3 3]]
"""
ndimen = len(dims)
if ndimen == 1:
return np.arange(dims[0], dtype='i4')
return djs_laxisnum(dims, iaxis)
[docs]def djs_laxisnum(dims, iaxis=0):
"""Returns an integer array where each element of the array is set equal
to its index number in the specified axis.
Parameters
----------
dims : :class:`list`
Dimensions of the array to return.
iaxis : :class:`int`, optional
Index along this dimension.
Returns
-------
:class:`numpy.ndarray`
An array of indexes with ``dtype=int32``.
Raises
------
:exc:`ValueError`
If `iaxis` is greater than or equal to the number of dimensions, or
if number of dimensions is greater than three.
Notes
-----
For two or more dimensions, there is no difference between this routine
and :func:`~pydl.pydlutils.misc.djs_laxisgen`.
Examples
--------
>>> from pydl.pydlutils.misc import djs_laxisnum
>>> print(djs_laxisnum([4,4]))
[[0 0 0 0]
[1 1 1 1]
[2 2 2 2]
[3 3 3 3]]
"""
ndimen = len(dims)
result = np.zeros(dims, dtype='i4')
if ndimen == 1:
pass
elif ndimen == 2:
if iaxis == 0:
for k in range(dims[0]):
result[k, :] = k
elif iaxis == 1:
for k in range(dims[1]):
result[:, k] = k
else:
raise ValueError("Bad value for iaxis: {0:d}".format(iaxis))
elif ndimen == 3:
if iaxis == 0:
for k in range(dims[0]):
result[k, :, :] = k
elif iaxis == 1:
for k in range(dims[1]):
result[:, k, :] = k
elif iaxis == 2:
for k in range(dims[2]):
result[:, :, k] = k
else:
raise ValueError("Bad value for iaxis: {0:d}".format(iaxis))
else:
raise ValueError("{0:d} dimensions not supported.".format(ndimen))
return result
[docs]def hogg_iau_name(ra, dec, prefix='SDSS', precision=1):
"""Properly format astronomical source names to the IAU convention.
Parameters
----------
ra : :class:`float` or :class:`numpy.ndarray`
Right ascencion in decimal degrees
dec : :class:`float` or :class:`numpy.ndarray`
Declination in decimal degrees.
prefix : :class:`str`, optional
Add this prefix to the string, defaults to 'SDSS'.
precision : :class:`int`, optional
Display this many digits of precision on seconds, default 1.
Returns
-------
:class:`str` or :class:`list`
The IAU name for the coordinates.
Examples
--------
>>> from pydl.pydlutils.misc import hogg_iau_name
>>> hogg_iau_name(354.120375,-0.544777778)
'SDSS J233628.89-003241.2'
"""
#
# Promote scalar values to arrays.
#
if isinstance(ra, float):
ra = np.array([ra])
if isinstance(dec, float):
dec = np.array([dec])
h = ra/15.0
rah = np.floor(h)
ram = np.floor(60.0*(h-rah))
ras = 60.0*(60.0*(h-rah) - ram)
ras = np.floor(ras*10.0**(precision+1))/10.0**(precision+1)
rasformat = "{{2:0{0:d}.{1:d}f}}".format(precision+4, precision+1)
rah = rah.astype(np.int32)
ram = ram.astype(np.int32)
desgn = np.array(list('+'*len(dec)))
desgn[dec < 0] = '-'
adec = np.absolute(dec)
ded = np.floor(adec)
dem = np.floor(60.0*(adec-ded))
des = 60.0*(60.0*(adec-ded) - dem)
des = np.floor(des*10.0**precision)/10.0**precision
desformat = "{{6:0{0:d}.{1:d}f}}".format(precision+3, precision)
if precision == 0:
desformat = "{6:02d}"
des = des.astype(np.int32)
ded = ded.astype(np.int32)
dem = dem.astype(np.int32)
adformat = "{{0:02d}}{{1:02d}}{ras}{{3:s}}{{4:02d}}{{5:02d}}{des}".format(
ras=rasformat, des=desformat)
adstr = [adformat.format(*x) for x in zip(
rah, ram, ras, desgn, ded, dem, des)]
if prefix == '':
jstr = 'J'
else:
jstr = ' J'
name = ["{0}{1}{2}".format(prefix, jstr, x) for x in adstr]
if len(ra) == 1:
return name[0]
else:
return name
[docs]def hogg_iau_name_main(): # pragma: no cover
from argparse import ArgumentParser
parser = ArgumentParser(description='Properly format astronomical ' +
'source names to the IAU convention.')
parser.add_argument('-P', '--precision', dest='precision', action='store',
metavar='N', default=1, type=int,
help='Digits of precision to add to the declination.')
parser.add_argument('-p', '--prefix', dest='prefix', action='store',
metavar='STR', default='SDSS',
help='Add this prefix to the name.')
parser.add_argument('ra', metavar='RA', type=float,
help='Right Ascension.')
parser.add_argument('dec', metavar='Dec', type=float,
help='Declination.')
options = parser.parse_args()
print(hogg_iau_name(options.ra, options.dec,
prefix=options.prefix, precision=options.precision))
return 0
[docs]def struct_print(array, filename=None, formatcodes=None, alias=None,
fdigit=5, ddigit=7, html=False, no_head=False,
silent=False):
"""Print a NumPy record array (analogous to an IDL structure) in a
nice way.
Parameters
----------
array : :class:`numpy.ndarray`
A record array to print.
filename : :class:`str` or file-like, optional
If supplied, write to this file.
formatcodes : :class:`dict`, optional
If supplied, use explicit format for certain columns.
alias : :class:`dict`, optional
If supplied, use this mapping of record array column names to printed
column names.
fdigit : :class:`int`, optional
Width of 32-bit floating point columns, default 5.
ddigit : :class:`int`, optional
Width of 64-bit floating point columns, default 7.
html : :class:`bool`, optional
If ``True``, print an html table.
no_head : :class:`bool`, optional
If ``True``, *don't* print a header line.
silent : :class:`bool`, optional
If ``True``, do not print the table, just return it.
Returns
-------
:class:`tuple`
A tuple containing a list of the lines in the table. If `html` is
``True``, also returns a list of lines of CSS for formatting the
table.
Examples
--------
>>> import numpy as np
>>> from pydl.pydlutils.misc import struct_print
>>> struct_print(np.array([(1,2.34,'five'),(2,3.456,'seven'),(3,4.5678,'nine')],dtype=[('a','i4'),('bb','f4'),('ccc','S5')]),silent=True)
(['a bb ccc ', '- ----------- -----', '1 2.34 five ', '2 3.456 seven', '3 4.5678 nine '], [])
"""
if html:
headstart = '<tr><th>'
headsep = '</th><th>'
headend = '</th></tr>'
colstart = '<tr><td>'
colsep = '</td><td>'
colend = '</td></tr>'
css = ['<style type="text/css">',
'table {',
' border-collapse: collapse;',
'}',
'th {',
' padding: 2px;',
' text-align: right;',
' border: 1px solid black;',
' font-weight: bold;',
'}',
'td {',
' padding: 2px;',
' text-align: right;',
' border: 1px solid black;',
'}',
'</style>']
else:
headstart = ''
headsep = ' '
headend = ''
colstart = ''
colsep = ' '
colend = ''
css = list()
#
# Alias should be a dictionary that maps structure names to column names
#
if alias is None:
#
# Create a dummy alias dictionary
#
alias = dict(list(zip(array.dtype.names, array.dtype.names)))
else:
#
# Fill in any missing values of the alias dictionary
#
for tag in array.dtype.names:
if tag not in alias:
alias[tag] = tag
#
# Formatcodes allows an override for certain columns.
#
if formatcodes is None:
formatcodes = dict()
#
# This dictionary will hold the number of characters in each column
#
nchar = dict()
#
# Construct format codes for each column
#
for k, tag in enumerate(array.dtype.names):
if tag in formatcodes:
thiscode = formatcodes[tag]
thisn = len(thiscode.format(array[tag][0]))
else:
d = array.dtype.fields[tag][0]
if d.kind == 'i' or d.kind == 'u':
thisn = max(max(len(str(array[tag].min())),
len(str(array[tag].max()))), len(tag))
thiscode = "{{{0:d}:{1:d}d}}".format(k, thisn)
elif d.kind == 'f':
if d.itemsize == 8:
prec = ddigit
else:
prec = fdigit
thisn = prec + 6
if array[tag].min() < 0:
thisn += 1
thiscode = "{{{0:d}:{1:d}.{2:d}g}}".format(k, thisn, prec)
elif d.kind == 'S' or d.kind == 'U':
thisn = max(d.itemsize, len(tag))
thiscode = "{{{0:d}:{1:d}s}}".format(k, thisn)
else:
raise PydlutilsException(
"Unsupported kind: {0}".format(d.kind))
formatcodes[tag] = thiscode
nchar[tag] = thisn
#
# Start building an array of lines
#
lines = list()
#
# Construct header lines
#
if html:
lines.append('<table>')
hdr1 = (headstart + headsep.join([alias[tag]
for tag in array.dtype.names]) + headend)
lines.append(hdr1)
else:
if not no_head:
hdr1 = (headstart + headsep.join([("{{0:{0:d}s}}".format(
nchar[tag])).format(alias[tag])
for tag in array.dtype.names]) + headend)
hdr2 = (headstart + headsep.join(['-' * nchar[tag]
for tag in array.dtype.names]) + headend)
lines.append(hdr1)
lines.append(hdr2)
#
# Create a format string for the data from the individual format codes
#
rowformat = (colstart + colsep.join([formatcodes[tag]
for tag in array.dtype.names]) + colend)
for k in range(array.size):
lines.append(rowformat.format(
*([decode_mixed(l) for l in array[k].tolist()])))
if html:
lines.append('</table>')
f = None # This variable will store a file handle
close_file = False
if filename is not None:
if hasattr(filename, 'write'):
f = filename
else:
f = open(filename, 'w+b')
close_file = True
if f is None:
if not silent: # pragma: no cover
print("\n".join(lines)+"\n")
else:
f.write(("\n".join(lines)+"\n").encode('utf-8'))
if close_file:
f.close()
return (lines, css)