"""
Module to read user input from files and create standardized input for orbitize
"""
import deprecation
import numpy as np
import orbitize
from astropy.table import Table
from astropy.io.ascii import read, write
[docs]def read_file(filename):
""" Reads data from any file for use in orbitize
readable by ``astropy.io.ascii.read()``, including csv format.
See the `astropy docs <http://docs.astropy.org/en/stable/io/ascii/index.html#id1>`_.
There are two ways to provide input data to orbitize.
The first way is to provide astrometric measurements, shown with the following example.
Example of an orbitize-readable .csv input file::
epoch,object,raoff,raoff_err,decoff,decoff_err,sep,sep_err,pa,pa_err,rv,rv_err
1234,1,0.010,0.005,0.50,0.05,,,,,,
1235,1,,,,,1.0,0.005,89.0,0.1,,
1236,1,,,,,1.0,0.005,89.3,0.3,,
1237,0,,,,,,,,,10,0.1
Each row must have ``epoch`` (in MJD=JD-2400000.5) and ``object``.
Objects are numbered with integers, where the primary/central object is ``0``.
If you have, for example, one RV measurement of a star and three astrometric
measurements of an orbiting planet, you should put ``0`` in the ``object`` column
for the RV point, and ``1`` in the columns for the astrometric measurements.
Each line must also have at least one of the following sets of valid measurements:
- RA and DEC offsets [mas], or
- sep [mas] and PA [degrees East of NCP], or
- RV measurement [km/s]
.. Note:: Columns with no data can be omitted (e.g. if only separation and PA
are given, the raoff, deoff, and rv columns can be excluded).
If more than one valid set is given (e.g. RV measurement and astrometric measurement
taken at the same epoch), ``read_file()`` will generate a separate output row for
each valid set.
Alternatively, you can also supply a data file with the columns already corresponding to
the orbitize format (see the example in description of what this method returns). This may
be useful if you are wanting to use the output of the `write_orbitize_input` method.
.. Note:: When providing data with columns in the orbitize format, there should be no
empty cells. As in the example below, when quant2 is not applicable, the cell should
contain nan.
Args:
filename (str): Input file name
Returns:
astropy.Table: Table containing orbitize-readable input for given
object. For the example input above::
epoch object quant1 quant1_err quant2 quant2_err quant_type
float64 int float64 float64 float64 float64 str5
------- ------ ------- ---------- ------- ---------- ----------
1234.0 1 0.01 0.005 0.5 0.05 radec
1235.0 1 1.0 0.005 89.0 0.1 seppa
1236.0 1 1.0 0.005 89.3 0.3 seppa
1237.0 0 10.0 0.1 nan nan rv
where ``quant_type`` is one of "radec", "seppa", or "rv".
If ``quant_type`` is "radec" or "seppa", the units of quant are mas and degrees,
if ``quant_type`` is "rv", the units of quant are km/s
Written: Henry Ngo, 2018
"""
# initialize output table
output_table = Table(names=('epoch', 'object', 'quant1', 'quant1_err', 'quant2', 'quant2_err', 'quant_type'),
dtype=(float, int, float, float, float, float, 'S5'))
# read file
try:
input_table = read(filename)
# convert to masked table
if input_table.has_masked_columns:
input_table = Table(input_table, masked=True, copy=False)
except:
raise Exception(
'Unable to read file: {}. \n Please check file path and format.'.format(filename))
num_measurements = len(input_table)
# Decide if input was given in the orbitize style
orbitize_style = 'quant_type' in input_table.columns
# validate input
# if input_table is Masked, then figure out which entries are masked
# otherwise, just check that we have the required columns based on orbitize_style flag
if input_table.masked:
if 'epoch' in input_table.columns:
have_epoch = ~input_table['epoch'].mask
if not have_epoch.all():
raise Exception("Invalid input format: missing some epoch entries")
else:
raise Exception("Input table MUST have epoch!")
if 'object' in input_table.columns:
have_object = ~input_table['object'].mask
if not have_object.all():
raise Exception("Invalid input format: missing some object entries")
else:
raise Exception("Input table MUST have object id!")
if orbitize_style: # proper orbitize style should NEVER have masked entries (nan required)
raise Exception("Input table in orbitize style may NOT have empty cells")
else: # Check for these things when not orbitize style
if 'raoff' in input_table.columns:
have_ra = ~input_table['raoff'].mask
else:
have_ra = np.zeros(num_measurements, dtype=bool) # zeros are False
if 'decoff' in input_table.columns:
have_dec = ~input_table['decoff'].mask
else:
have_dec = np.zeros(num_measurements, dtype=bool) # zeros are False
if 'sep' in input_table.columns:
have_sep = ~input_table['sep'].mask
else:
have_sep = np.zeros(num_measurements, dtype=bool) # zeros are False
if 'pa' in input_table.columns:
have_pa = ~input_table['pa'].mask
else:
have_pa = np.zeros(num_measurements, dtype=bool) # zeros are False
if 'rv' in input_table.columns:
have_rv = ~input_table['rv'].mask
else:
have_rv = np.zeros(num_measurements, dtype=bool) # zeros are False
else: # no masked entries, just check for required columns
if 'epoch' not in input_table.columns:
raise Exception("Input table MUST have epoch!")
if 'object' not in input_table.columns:
raise Exception("Input table MUST have object id!")
if not orbitize_style: # Set these flags only when not already in orbitize style
if 'raoff' in input_table.columns:
have_ra = np.ones(num_measurements, dtype=bool) # ones are False
else:
have_ra = np.zeros(num_measurements, dtype=bool) # zeros are False
if 'decoff' in input_table.columns:
have_dec = np.ones(num_measurements, dtype=bool) # ones are False
else:
have_dec = np.zeros(num_measurements, dtype=bool) # zeros are False
if 'sep' in input_table.columns:
have_sep = np.ones(num_measurements, dtype=bool) # ones are False
else:
have_sep = np.zeros(num_measurements, dtype=bool) # zeros are False
if 'pa' in input_table.columns:
have_pa = np.ones(num_measurements, dtype=bool) # ones are False
else:
have_pa = np.zeros(num_measurements, dtype=bool) # zeros are False
if 'rv' in input_table.columns:
have_rv = np.ones(num_measurements, dtype=bool) # ones are False
else:
have_rv = np.zeros(num_measurements, dtype=bool) # zeros are False
# loop through each row and format table
index = 0
for row in input_table:
# First check if epoch is a number
try:
float_epoch = np.float(row['epoch'])
except:
raise Exception(
'Problem reading epoch in the input file. Epoch should be given in MJD.')
# check epoch format and put in MJD
if row['epoch'] > 2400000.5: # assume this is in JD
print('Converting input epochs from JD to MJD.\n')
MJD = row['epoch'] - 2400000.5
else:
MJD = row['epoch']
# check that "object" is an integer (instead of ABC/bcd)
if not isinstance(row['object'], (int, np.int32, np.int64)):
raise Exception("Invalid object ID. Object IDs must be integers.")
# determine input quantity type (RA/DEC, SEP/PA, or RV)
if orbitize_style:
if row['quant_type'] == 'rv': # special format for rv rows
output_table.add_row([MJD, row['object'], row['quant1'],
row['quant1_err'], None, None, row['quant_type']])
elif row['quant_type'] == 'radec' or row['quant_type'] == 'seppa': # other allowed formats
output_table.add_row([MJD, row['object'], row['quant1'], row['quant1_err'],
row['quant2'], row['quant2_err'], row['quant_type']])
else: # catch wrong formats
raise Exception("Invalid 'quant_type'. Valid values are 'radec', 'seppa' or 'rv'")
else: # When not in orbitize style
if have_ra[index] and have_dec[index]:
output_table.add_row([MJD, row['object'], row['raoff'],
row['raoff_err'], row['decoff'], row['decoff_err'], "radec"])
elif have_sep[index] and have_pa[index]:
output_table.add_row([MJD, row['object'], row['sep'],
row['sep_err'], row['pa'], row['pa_err'], "seppa"])
if have_rv[index]:
output_table.add_row([MJD, row['object'], row['rv'],
row['rv_err'], None, None, "rv"])
index = index+1
return output_table