Skip to content

Commit

Permalink
Enchancement to pull_isotime for parsing TDB timescale ISOTimes.
Browse files Browse the repository at this point in the history
  • Loading branch information
timstaley committed Nov 9, 2016
1 parent 5b03f1e commit f30d82d
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 41 deletions.
30 changes: 30 additions & 0 deletions CHANGES.rst
@@ -0,0 +1,30 @@
Change history
===============


0.9.8 - 2016/11/09
-------------------
Enhancement to ``pull_isotime`` convenience function: Add support for
conversion of event-timestamps to UTC from the TDB (Barycentric Dynamical
Time) format. This is now in use 'in the wild' for the GAIA VOEvent
stream.
(Support for remaining VOEvent timescales 'GPS' and 'TT' has been
considered but needs a motivating use-case, see
https://github.com/timstaley/voevent-parse/issues/5 )

0.9.7 - 2016/10/31
------------------
Identical to 0.96, fixes a packaging issue most likely due to temporary
files / dev installation confusing the packager.

0.9.6 - 2016/10/31
------------------
Minor bugfix to ``pull_params``: Don't bork on missing Param name.
However, note that if multiple Params are present without a `name`
attribute then this convenience routine doesn't really make sense - see
warning in docs.

0.9.5 - 2016/05/03
------------------
Switch to versioneer for version numbering / release tagging.
CF https://github.com/warner/python-versioneer
2 changes: 2 additions & 0 deletions documentation/source/changelog.rst
@@ -0,0 +1,2 @@

.. include:: ../../CHANGES.rst
1 change: 1 addition & 0 deletions documentation/source/index.rst
Expand Up @@ -24,6 +24,7 @@ Contents
intro
examples
reference
changelog


Indices and tables
Expand Down
18 changes: 12 additions & 6 deletions setup.py
Expand Up @@ -3,20 +3,26 @@
from setuptools import setup
import versioneer

install_requires = [
"astropy>=1.2",
"lxml>=2.3, <4.0",
'iso8601',
'pytz',
'six',
]

setup(
name="voevent-parse",
version=versioneer.get_version(),
cmdclass=versioneer.get_cmdclass(),
packages=['voeventparse', 'voeventparse.tests', 'voeventparse.tests.resources'],
package_data={'voeventparse':['tests/resources/*.xml']},
packages=['voeventparse', 'voeventparse.tests',
'voeventparse.tests.resources'],
package_data={'voeventparse': ['tests/resources/*.xml']},
description="Convenience routines for parsing and manipulation of "
"VOEvent XML packets.",
author="Tim Staley",
author_email="timstaley337@gmail.com",
url="https://github.com/timstaley/voevent-parse",
install_requires=["lxml>=2.3, <4.0",
'six',
'iso8601',
],
install_requires=install_requires,
test_suite='voeventparse.tests'
)
2 changes: 1 addition & 1 deletion tox.ini
Expand Up @@ -4,7 +4,7 @@
# and then run "tox" from this directory.

[tox]
envlist = py27, py34, docs
envlist = py27, py34, py35, docs

[testenv]
commands = nosetests
Expand Down
53 changes: 37 additions & 16 deletions voeventparse/convenience.py
@@ -1,13 +1,14 @@
"""Convenience routines for common actions on VOEvent objects"""

from __future__ import absolute_import
import iso8601
import lxml

from collections import OrderedDict
from voeventparse.misc import (Param, Group, Reference, Inference, Position2D,
Citation)
from copy import deepcopy

import astropy.time
import lxml
import pytz
from voeventparse.misc import (Position2D)


def pull_astro_coords(voevent, index=0):
Expand All @@ -32,7 +33,6 @@ def pull_astro_coords(voevent, index=0):
ac_sys = voevent.WhereWhen.ObsDataLocation.ObservationLocation.AstroCoordSystem
sys = ac_sys.attrib['id']


if hasattr(ac.Position2D, "Name1"):
assert ac.Position2D.Name1 == 'RA' and ac.Position2D.Name2 == 'Dec'
posn = Position2D(ra=float(ac.Position2D.Value2.C1),
Expand All @@ -43,8 +43,13 @@ def pull_astro_coords(voevent, index=0):
return posn


# def pull_astropy_time(voevent, index=0):

def pull_isotime(voevent, index=0):
"""Extracts the event time from a given `WhereWhen.ObsDataLocation`.
"""
Extracts the event time from a given `WhereWhen.ObsDataLocation`
Returns a datetime (timezone-aware, UTC).
Accesses a `WhereWhere.ObsDataLocation.ObservationLocation`
element and returns the AstroCoords.Time.TimeInstant.ISOTime element,
Expand All @@ -55,11 +60,13 @@ def pull_isotime(voevent, index=0):
moving over time. Most packets will have only one, however, so the
default is to access the first.
.. warning::
This function now implements conversion from the
TDB (Barycentric Dynamical Time) time scale in ISOTime format,
since this is the format used by GAIA VOEvents.
(See also http://docs.astropy.org/en/stable/time/#time-scale )
This function currently only works with UTC time-system coords.
Future updates may implement conversion from other systems (TT, GPS)
using astropy functionality.
Other timescales (i.e. TT, GPS) will presumably be formatted as a
TimeOffset, parsing this format is not yet implemented.
Args:
voevent (:class:`voeventparse.voevent.Voevent`): Root node of the VOevent
Expand All @@ -77,12 +84,28 @@ def pull_isotime(voevent, index=0):
od = voevent.WhereWhen.ObsDataLocation[index]
ol = od.ObservationLocation
coord_sys = ol.AstroCoords.attrib['coord_system_id']
if coord_sys.split('-')[0] != 'UTC':
timesys_identifier = coord_sys.split('-')[0]

if timesys_identifier == 'UTC':
isotime_str = str(ol.AstroCoords.Time.TimeInstant.ISOTime)
time = astropy.time.Time(isotime_str, format='isot', scale='utc')
elif (timesys_identifier == 'TDB'):
isotime_str = str(ol.AstroCoords.Time.TimeInstant.ISOTime)
time = astropy.time.Time(isotime_str, format='isot', scale='tdb')
elif (timesys_identifier == 'TT' or timesys_identifier =='GPS'):
raise NotImplementedError(
'Loading from time-systems other than UTC not yet implemented'
"Conversion from time-system '{}' to UTC not yet implemented"
)
else:
raise ValueError(
'Unrecognised time-system: {} (badly formatted VOEvent?)'.format(
timesys_identifier
)
)
isotime_str = str(ol.AstroCoords.Time.TimeInstant.ISOTime)
return iso8601.parse_date(isotime_str)

timezone_naive_UTC_datetime = time.utc.to_datetime()
return timezone_naive_UTC_datetime.replace(tzinfo=pytz.UTC)

except AttributeError:
return None

Expand Down Expand Up @@ -153,5 +176,3 @@ def prettystr(subtree):
lxml.objectify.deannotate(subtree)
lxml.etree.cleanup_namespaces(subtree)
return lxml.etree.tostring(subtree, pretty_print=True)


1 change: 0 additions & 1 deletion voeventparse/misc.py
Expand Up @@ -24,7 +24,6 @@ class Position2D(namedtuple('Position2D', 'ra dec err units system')):
"""
pass # Just wrapping a namedtuple so we can assign a docstring.


_datatypes_autoconversion = {
bool: ('string', lambda b: str(b)),
int: ('int', lambda i: str(i)),
Expand Down
57 changes: 40 additions & 17 deletions voeventparse/tests/test_convenience_routines.py
Expand Up @@ -4,6 +4,7 @@

import voeventparse as vp
from voeventparse.tests.resources import datapaths
import iso8601


class TestConvenienceRoutines(TestCase):
Expand All @@ -13,63 +14,85 @@ def setUp(self):
with open(datapaths.moa_lensing_event_path, 'rb') as f:
self.moa_packet = vp.load(f)
with open(datapaths.gaia_alert_16aac_direct, 'rb') as f:
self.noname_param_packet = vp.load(f)
self.gaia_noname_param_packet = vp.load(f)
self.blank = vp.Voevent(stream='voevent.soton.ac.uk/TEST',
stream_id='100',
role='test')
stream_id='100',
role='test')

def test_pull_astro_coords(self):
known_swift_grb_posn = vp.Position2D(ra=74.741200, dec=-9.313700,
err=0.05,
units='deg',
system='UTC-FK5-GEO')
err=0.05,
units='deg',
system='UTC-FK5-GEO')
p = vp.pull_astro_coords(self.swift_grb_v2_packet)
self.assertEqual(p, known_swift_grb_posn)
self.assertIsInstance(p.ra, float)

def test_pull_params(self):
swift_params = vp.pull_params(self.swift_grb_v2_packet)

#General example, check basic functionality
# General example, check basic functionality
self.assertEqual(swift_params[None]['Packet_Type']['value'], '61')
self.assertEqual(swift_params['Misc_Flags']['Values_Out_of_Range']['value'],
'false')
self.assertEqual(
swift_params['Misc_Flags']['Values_Out_of_Range']['value'],
'false')

#Check ordering is preserved
# Check ordering is preserved
self.assertEqual(list(swift_params[None].keys())[:3],
['Packet_Type', 'Pkt_Ser_Num', 'TrigID'])

#Test empty What section
# Test empty What section
params = vp.pull_params(self.blank)
self.assertEqual(params, {})

#Test known (generated) example
# Test known (generated) example
single_par = copy(self.blank)
single_par.What.append(vp.Param(name="test_param", value=123))
params = vp.pull_params(single_par)
self.assertEqual(len(params), 1)
self.assertEqual(list(params[None].keys()), ['test_param'])

#Test case where a What Group is present, but empty:
# Test case where a What Group is present, but empty:
params = vp.pull_params(self.moa_packet)
self.assertEqual(params['Misc_Flags'], {})

#Test case where a Param is present with no name:
params = vp.pull_params(self.noname_param_packet)

# Test case where a Param is present with no name:
params = vp.pull_params(self.gaia_noname_param_packet)

def test_pull_isotime(self):
isotime = vp.pull_isotime(self.swift_grb_v2_packet)
# check it works, and returns timezone aware datetime:
self.assertIsInstance(isotime, datetime.datetime)
self.assertTrue(isotime.tzinfo is not None)
self.assertEqual(isotime.utcoffset(), datetime.timedelta(0))

def test_pull_isotime_blank(self):
# Running on an empty VOEvent should return None, not an exception:
null_result = vp.pull_isotime(self.blank)
self.assertIsNone(null_result)

def test_pull_isotime_from_TDB(self):
converted_isotime = vp.pull_isotime(self.gaia_noname_param_packet)
# check it works, and returns timezone aware datetime:
self.assertIsInstance(converted_isotime, datetime.datetime)
self.assertTrue(converted_isotime.tzinfo is not None)
self.assertEqual(converted_isotime.utcoffset(), datetime.timedelta(0))

od = self.gaia_noname_param_packet.WhereWhen.ObsDataLocation[0]
ol = od.ObservationLocation
coord_sys = ol.AstroCoords.attrib['coord_system_id']
self.assertEqual(coord_sys, 'TDB-ICRS-BARY')

raw_iso_string = str(ol.AstroCoords.Time.TimeInstant.ISOTime)
misinterpreted_as_utc = iso8601.parse_date(raw_iso_string)
self.assertNotEqual(converted_isotime, misinterpreted_as_utc)


class TestPrettyStr(TestCase):
def setUp(self):
with open(datapaths.swift_bat_grb_pos_v2, 'rb') as f:
self.swift_grb_v2_packet = vp.load(f)

def test_for_packet_mangling(self):
"""
Check that applying prettystr to a packet does not change it.
Expand All @@ -79,4 +102,4 @@ def test_for_packet_mangling(self):
vp.prettystr(self.swift_grb_v2_packet)
self.assertTrue(vp.valid_as_v2_0(self.swift_grb_v2_packet))
after = vp.dumps(self.swift_grb_v2_packet)
self.assertEqual(before,after)
self.assertEqual(before, after)

0 comments on commit f30d82d

Please sign in to comment.