Skip to content

Commit 1a675f0

Browse files
authored
Merge pull request #296 from Smithsonian/predict_sexagesimal
Sexagesimal Output
2 parents 9ac5a7c + ee5974d commit 1a675f0

File tree

8 files changed

+486
-3
lines changed

8 files changed

+486
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ coverage.xml
5858

5959
# Django stuff:
6060
*.log
61+
*.err
6162
local_settings.py
6263
db.sqlite3
6364
db.sqlite3-journal

src/layup/predict.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,40 @@ def _get_result_dtypes(primary_id_column_name: str):
5555
)
5656

5757

58+
def _convert_to_sg(data):
59+
"""This function appends two columns of the RA and Dec in sexagesimal to the input array.
60+
61+
Parameters
62+
----------
63+
data : numpy structured array
64+
The data to be processed.
65+
66+
Returns
67+
-------
68+
input array with ra and dec in sexagesimal appended, called ra_str_hms and dec_str_dms respectively.
69+
"""
70+
ra_deg = (data["ra_deg"] / 15) % 24 # Ensuring ra is within 24 hours/360 degrees
71+
ra_h = ra_deg.astype(int)
72+
dec_deg = data["dec_deg"]
73+
dec_d = dec_deg.astype(int)
74+
ra_decimal = (ra_deg % 1) * 60
75+
ra_m = ra_decimal.astype(int)
76+
dec_decimal = (np.abs(dec_deg) % 1) * 60
77+
dec_m = dec_decimal.astype(int)
78+
ra_s = (ra_decimal % 1) * 60 # Take decimal portion again for arcseconds
79+
dec_s = (dec_decimal % 1) * 60
80+
81+
ra = np.empty(len(ra_h), dtype="<U16")
82+
dec = np.empty(len(ra_h), dtype="<U16")
83+
84+
for i in range(len(ra_h)):
85+
86+
ra[i] = f"{ra_h[i]:02} {ra_m[i]:02} {ra_s[i]:05.2f}" # Same format as
87+
dec[i] = f"{dec_d[i]:+03} {dec_m[i]:02} {dec_s[i]:04.1f}" # JPL Horizons
88+
89+
return np.lib.recfunctions.append_fields(data, ["ra_str_hms", "dec_str_dms"], [ra, dec], usemask=False)
90+
91+
5892
def _predict(data, obs_pos_vel, times, cache_dir, primary_id_column_name):
5993
"""This function is called by the parallelization function to call the C++ code.
6094
@@ -244,4 +278,8 @@ def predict_cli(
244278
)
245279

246280
if len(predictions) > 0:
247-
write_csv(predictions, output_file)
281+
if cli_args.sexagesimal:
282+
predictions = _convert_to_sg(predictions)
283+
write_csv(predictions, output_file, move_columns={"ra_str_hms": 3, "dec_str_dms": 4})
284+
else:
285+
write_csv(predictions, output_file)

src/layup/utilities/file_io/file_output.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pandas as pd
66

77

8-
def write_csv(data, filepath):
8+
def write_csv(data, filepath, move_columns=None):
99
"""Write a numpy structured array to a CSV file.
1010
1111
Parameters
@@ -14,8 +14,26 @@ def write_csv(data, filepath):
1414
The data to write to the file.
1515
filepath : str
1616
The path to the file to write.
17+
move_columns : dict, optional
18+
Dict of any column names that need moved, paired with their new position.
1719
"""
1820
df = pd.DataFrame(data)
21+
22+
if move_columns != None:
23+
column_names = list(df.columns.values)
24+
for col in move_columns.keys():
25+
if abs(move_columns[col]) > len(column_names):
26+
raise IndexError(
27+
f"Column position is outside of range. Must be between +-{len(column_names)}"
28+
)
29+
try:
30+
column_names.pop(column_names.index((col)))
31+
column_names.insert(move_columns[col], col)
32+
except:
33+
raise ValueError(f"column {col} not found in df.columns.values.")
34+
35+
df = df.reindex(columns=column_names)
36+
1937
if os.path.exists(filepath):
2038
df.to_csv(filepath, mode="a", header=False, index=False)
2139
else:

src/layup_cmdline/predict.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ def main():
175175
required=False,
176176
)
177177

178+
optional.add_argument(
179+
"-sg",
180+
"--sexagesimal",
181+
action="store_true",
182+
help="Flag to add RA and Dec in sexagesimal format to the output.",
183+
)
184+
178185
args = parser.parse_args()
179186

180187
return execute(args)

tests/data/holman_expected_predict_sg.csv

Lines changed: 338 additions & 0 deletions
Large diffs are not rendered by default.

tests/data/known_sexagesimal.csv

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
provID,ra_deg, dec_deg, ra_str_hms_CHECK, dec_str_dms_CHECK
2+
1,30.35697564, 30.937654376,02 01 25.67,+30 56 15.6
3+
2,352.52968317256614,-4.613618011505283,23 30 07.12,-04 36 49.0
4+
3,-300., 3.5487537,04 00 00.00,+03 32 55.5
5+
4,0.5, 0.5,00 02 00.00,+00 30 00.0
6+
5,0., 0.,00 00 00.00,+00 00 00.0
7+
6,0.005, 0.005,00 00 01.20,+00 00 18.0

tests/layup/test_file_output.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import numpy as np
22
from numpy.testing import assert_equal
3+
from unittest import TestCase
34
import tempfile
45

56
from layup.utilities.file_io.CSVReader import CSVDataReader
@@ -49,6 +50,29 @@ def test_write_csv(tmpdir):
4950
assert_equal(appended_data[2:4], data[3:5])
5051

5152

53+
def test_write_csv_move_columns(tmpdir):
54+
# Read a test CSV file into a numpy structured array.
55+
csv_reader = CSVDataReader(get_test_filepath("CART.csv"))
56+
data = csv_reader.read_rows()
57+
58+
# Get a temp filepath to use in the function
59+
temp_filepath = os.path.join(tmpdir, "test_output.csv")
60+
61+
# Pass a nonexistent column into write_csv to check it returns a ValueError
62+
TestCase().assertRaises(ValueError, write_csv, data, temp_filepath, move_columns={"fake_col": 0})
63+
TestCase().assertRaises(IndexError, write_csv, data, temp_filepath, move_columns={"x": 20})
64+
65+
# Write to temp filepath with swapped columns
66+
write_csv(data, temp_filepath, move_columns={"x": 0, "y": 1, "z": 2})
67+
68+
# Read the data back in and check the columns have been swapped
69+
csv_reader2 = CSVDataReader(temp_filepath)
70+
data2 = csv_reader2.read_rows()
71+
assert_equal(
72+
data2.dtype.names, ["x", "y", "z", "ObjID", "FORMAT", "xdot", "ydot", "zdot", "epochMJD_TDB"]
73+
)
74+
75+
5276
def test_write_empty_hdf5(tmpdir):
5377
# Write an empty numpy structured array to a temporary HDF5 file.
5478
data = np.array([], dtype=[("ObjID", "<U7"), ("FORMAT", "<U4")])

tests/layup/test_predict.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55
from numpy.testing import assert_equal
66

7-
from layup.predict import predict, predict_cli
7+
from layup.predict import predict, predict_cli, _convert_to_sg
88
from layup.utilities.data_utilities_for_tests import get_test_filepath
99
from layup.utilities.file_io.CSVReader import CSVDataReader
1010

@@ -39,6 +39,7 @@ def __init__(self, g=None):
3939
self.n = 1
4040
self.chunk = chunk_size
4141
self.station = "X05"
42+
self.sexagesimal = False
4243

4344
# The naming scheme for the test files indicates its orbit format
4445
test_filename = f"predict_chunk_{input_format}.csv"
@@ -151,3 +152,52 @@ def test_predict_output(tmpdir):
151152
# assert np.allclose(output_data["obs_cov1"], known_data["obs_cov1"])
152153
# assert np.allclose(output_data["obs_cov2"], known_data["obs_cov2"])
153154
# assert np.allclose(output_data["obs_cov3"], known_data["obs_cov3"])
155+
156+
# Testing the output of the sexagesimal conversion separately
157+
158+
result = subprocess.run(
159+
[
160+
"layup",
161+
"predict",
162+
str(input_file),
163+
"-f",
164+
"-o",
165+
str(temp_out_file),
166+
"-s",
167+
start,
168+
"-sg",
169+
]
170+
)
171+
172+
assert result.returncode == 0
173+
174+
result_file = Path(f"{tmpdir}/{temp_out_file}.csv")
175+
assert result_file.exists
176+
177+
# Create a new CSV reader to read in our output file
178+
output_csv_reader = CSVDataReader(str(result_file), "csv", primary_id_column_name="provID")
179+
output_data = output_csv_reader.read_rows()
180+
181+
# Read in the known output
182+
known_output_file = get_test_filepath("holman_expected_predict_sg.csv")
183+
known_output_csv_reader = CSVDataReader(known_output_file, "csv", primary_id_column_name="provID")
184+
known_data = known_output_csv_reader.read_rows()
185+
186+
assert (output_data["ra_str_hms"] == known_data["ra_str_hms"]).all() == True
187+
assert (output_data["dec_str_dms"] == known_data["dec_str_dms"]).all() == True
188+
189+
# Check the columns have been swapped too
190+
assert (known_data.dtype.names == output_data.dtype.names) == True
191+
192+
193+
def test_convert_to_sg(tmpdir):
194+
"""Compare the output given by _convert_to_sg() with an expected output, seeing how it handles edge cases."""
195+
196+
data = CSVDataReader(
197+
get_test_filepath("known_sexagesimal.csv"), "csv", primary_id_column_name="provID"
198+
).read_rows()
199+
200+
data = _convert_to_sg(data)
201+
202+
assert (data["ra_str_hms"] == data["ra_str_hms_CHECK"]).all() == True
203+
assert (data["dec_str_dms"] == data["dec_str_dms_CHECK"]).all() == True

0 commit comments

Comments
 (0)