Tutorial 3 - Spatial filtering and Astropy coords

By now, you should have some idea of how to explore the various voeventdb.remote endpoints and apply filters.

** This notebook expands on the ‘cone-search’ filter, including how to use it with astropy.coordinates classes. **

In [ ]:
from __future__ import print_function
import logging
logging.basicConfig(level=logging.DEBUG)
In [ ]:
import voeventdb.remote as vr
import voeventdb.remote.apiv1 as apiv1
from voeventdb.remote.apiv1 import FilterKeys
from voeventdb.remote.helpers import Synopsis
from astropy.coordinates import Angle, SkyCoord

In the last tutorial, we inspected a Swift XRT event which gave us a position estimate and and error-circle. Let’s grab those details again:

In [ ]:
xrt_synopsis = Synopsis(apiv1.packet_synopsis('ivo://nasa.gsfc.gcn/SWIFT#XRT_Pos_666352-553'))
In [ ]:
sky_event = xrt_synopsis.sky_events[0]
print(sky_event)
print("Error-circle radius in degrees, arcminutes and arcseconds:", sky_event.position_error)

Filtering using a cone-search (AKA spatial queries)

Next, let’s see if there are any other recorded events with associated positions nearby. To do so, we’ll need to define a ‘cone’, a sky-position and circle around it to search in. For setting up a voeventdb.remote cone-filter, we can use a tuple of type

(astropy.coordinates.SkyCoord, astropy.coordinates.Angle)

A natural candidate is the position and position-error from the XRT event; like so:

In [ ]:
cone = (sky_event.position, sky_event.position_error)
cone

However, the XRT position has a really tight error-circle, about 5 arcseconds. Note that the cone-search will only return VOEvents with a best-estimate position within the cone - it does not take into account overlapping error-circles (at least for version 1!). This means that we could have a related event with a large error-circle, but which has a best-estimate position just outside the tiny XRT error-circle, and it wouldn’t be returned - so we have to use some judgement here. We’ll set the cone angular radius to half a degree, instead:

In [ ]:
cone = (sky_event.position, Angle(0.5, unit='deg'))
cone

OK, let’s see how that works:

In [ ]:
cone_filters = {
    FilterKeys.role: 'observation',
    FilterKeys.cone: cone
    }
In [ ]:
apiv1.map_stream_count(cone_filters)

A reasonable number. Let’s take a look:

In [ ]:
sorted(apiv1.list_ivorn(cone_filters))

Investigating ‘background’ event rates

So, we have a bunch of Swift packets related to the same event (ID 666352), some ‘BAT_SubSubThresh’ events, and a ‘Known_Pos’ event. It’s worth noting that ‘SubSubThresh’ events are extremely common and show up all over; in fact they currently make up about half the packets in the database:

In [ ]:
all_events_count = apiv1.count(filters={FilterKeys.role:'observation'})
ss_thresh_count = apiv1.count(filters={
        FilterKeys.role:'observation',
        FilterKeys.ivorn_contains:'BAT_SubSubThresh',
        })
print("Of {} observation packets in the database, {} are BAT_SubSubThresh packets".format(
        all_events_count, ss_thresh_count))

So it’s perhaps not surprising that we’d encounter a few of them co-incidentally lying in the search-cone. We can define a search-cone of the same radius at arbitrary co-ordinates and see what we get back, for comparison:

In [ ]:
cone2 = (SkyCoord(ra=0, dec=35., unit='deg'), Angle(0.5, unit='deg'))
cone2
In [ ]:
cone2_filters = {
    FilterKeys.role: 'observation',
    FilterKeys.cone: cone2
    }
In [ ]:
apiv1.map_stream_count(filters=cone2_filters)
In [ ]:
apiv1.list_ivorn(filters=cone2_filters)

Result: looks like we get a similar number of these ‘SubSubThresh’ events wherever we point our search-cone!

Restricting the time-period to a 60-day window

Fortunately, it’s easy to narrow things down a bit further, if we expect related events to occur within a reasonably short time-interval. Let’s modify our original filter-set a bit more:

In [ ]:
from datetime import timedelta
cone_filters[FilterKeys.authored_since] = sky_event.timestamp - timedelta(days=30)
cone_filters[FilterKeys.authored_until] = sky_event.timestamp + timedelta(days=30)
cone_filters
In [ ]:
sorted(apiv1.list_ivorn(cone_filters))

As a result of restricting our search to a window about the XRT-position timestamp, we’ve cut out all of those ‘SubSubThresh’ events that probably weren’t related.

A note of warning

What hasn’t been mentioned so far is that a query with a cone-search filter will only return VOEvents which contain a position in the `WhereWhen <http://voevent.readthedocs.org/en/latest/reading.html#overall-structure>`__ section of the packet. This sounds obvious, but on the other hand it’s worth being reminded that some related and interesting VOEvents may not contain any position information.

Most packets relating to follow-up or simultaneous observations of an event will contain position data, but we can imagine circumstances where this wouldn’t apply, e.g. an all-sky high-energy gamma-ray detector without good localization. Alternatively a VOEvent author may simply decide not to repeat co-ordinates that have been given in an earlier packet (perhaps when giving refined estimates about flux / lightcurve / spectral index, etc), but instead rely on the citation mechanism - see the next notebook!

Coming next …

In the search above for packets near to a Swift XRT position, many of the packets were clearly designated as relating to the same event - they all had the same ID number. Is that relation encoded into the packet-data? Can we just select the VOEvents which are already marked as being related to a particular packet?

The answer, of course, is yes - we make use of citation data.