barycenter
Barycentric correction of X-ray event files, in pure Python.
A photon's arrival time at the spacecraft is not a useful clock. The spacecraft moves around the Earth, and the Earth around the Sun, so recorded arrival times wander by up to about 500 seconds over a year. Barycentring removes that: it reports each photon as it would have arrived at the solar system's centre of mass, which is very nearly inertial, so times can be compared between missions and across decades.
That is the job of HEASOFT barycorr, XMM-SAS barycen and CIAO axbary. barycenter
does it with nothing installed but Python, agrees with each of those tools to better than
100 nanoseconds, and is not restricted to the ephemerides they happen to ship.
Install
pip install git+https://github.com/matteobachetti/barycenter.git
The optional speed extra installs numba, which makes the
spacecraft-clock interpolation about six times faster. It changes no answer — the
compiled and pure-numpy kernels are bit-identical — and only pays for its compilation
time on very large files:
pip install "barycenter[speed] @ git+https://github.com/matteobachetti/barycenter.git"
Use it from the command line
You need the event file and the mission's orbit file, and a position on the sky:
barycenter nu30702012003A06_cl.evt nu30702012003A.attorb \
--ra 148.9584 --dec 69.6794 --ephem DE440
The position can come from a pulsar parameter file instead, which saves copying coordinates by hand and also picks up the ephemeris the model was fitted with:
barycenter ni1013010101_0mpu7_cl.evt ni1013010101.orb --parfile Crab.par
The default engine takes the static RAJ/DECJ out of the model. If the source's proper
motion or parallax matters, add --engine pint, which hands the whole timing model over
and so applies them.
Several orbit files can be given at once, for an observation split across them, and
--clockfile names a spacecraft clock file (NuSTAR's is fetched from the CALDB
automatically if you do not). barycenter --help lists the rest; the options worth
knowing about are --ephem, --engine and --dt.
Use it from Python
The whole command line is one function:
from barycenter import apply_barycenter_correction
outfile = apply_barycenter_correction(
"nu30702012003A06_cl.evt",
"nu30702012003A.attorb",
outfile="bary.evt",
ra=148.9584,
dec=69.6794,
ephem="DE440",
)
If you only want the correction itself — to apply to times you already hold, or to plot — ask for the function of mission elapsed time and call it:
from barycenter import get_barycentric_correction, read_orbit
orbit = "nu30702012003A.attorb"
correction = get_barycentric_correction(orbit, ra=148.9584, dec=69.6794, ephem="DE440")
met = read_orbit(orbit)["MET"].value # the times the orbit file covers
barycentred = met + correction(met) # correction is in seconds, about +309 here
The returned object is a cubic spline over the orbit file's own time span, so ask it only for times inside that span: outside it the spline extrapolates, quietly and badly. The command line checks this for you and warns.
Missions
| Mission | Orbit file | Clock correction | Agreement with the mission's own tool |
|---|---|---|---|
| NuSTAR | nu<obsid>A.attorb |
nuCclock*.fits, fetched automatically |
+22 ns vs barycorr |
| RXTE | orbit/FPorbit_* |
tdc.dat, bundled |
+25 ns vs barycorr |
| Swift | auxil/sw<obsid>sao.fits |
swclockcor*.fits (the UTCF) |
+37 ns vs barycorr |
| XMM-Newton | PPS *ORBTSR*.FTZ |
none needed | −42 ns vs SAS barycen |
| Chandra | primary/orbitf*_eph1.fits |
none needed | +47 ns vs CIAO axbary |
| NICER | ni<obsid>.orb |
none needed | +53 ns vs barycorr, one-off check |
| IXPE, Fermi, SVOM | see the docs | none implemented | orbit file read, never checked against a tool |
The first five rows have a reference file committed to the repository, so continuous
integration re-checks them on every change. The NICER figure comes from a single
comparison against a barycorr run on a Crab observation too large to commit, and the
last three missions have never been compared against an official tool at all — their
orbit files are read correctly, which is a different claim.
XMM-Newton and Chandra are worth singling out: barycorr cannot do either mission, and
axbary can only reach DE200 and DE405, so this is the only way to barycentre them
against a modern ephemeris. On the Chandra test data DE405 is 79 µs away from DE440.
Per-mission instructions are in docs/missions.md, and adding a new mission is about five lines plus a reference file to check it against.
Accuracy
The target is 100 nanoseconds against each mission's own tool, and it is met on all five missions for which an official reference could be produced. Most of the residual scatter in the table above is not ours: a reference file stores times as 64-bit floats, which near a mission elapsed time of 5e8 seconds cannot express a difference finer than about 119 ns, so the tests add that floor to their tolerance.
Two engines compute the correction. The default, --engine native, uses astropy, ERFA
and a JPL ephemeris directly, keeping every term as a small quantity so that it works
identically on any platform. --engine pint routes the same job through
PINT as an independent second opinion; the two agree
to sub-nanosecond once the differing Shapiro-delay convention is accounted for. On Apple
Silicon and Windows the PINT path is quantised at about 1.1 µs, because those platforms
have no extended-precision float; the native engine is unaffected.
Documentation
- Per-mission how-to — where each mission's files come from, and the exact command
- Adding a mission
- Technical details — how it works, and every measurement quoted above
- Known issues — an honest list of what is wrong or missing
Contributing
Bug reports and patches are welcome; see CONTRIBUTING.md and the Code of Conduct.
Copyright
- Copyright © 2025 Matteo Bachetti.
- Free software distributed under the MIT License.
Metadata
Release files for barycenter 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| barycenter-1.0.0.tar.gz | 85.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| barycenter-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 173.2 kB
Release files / barycenter-1.0.0.tar.gz
| Download URL | barycenter-1.0.0.tar.gz |
|---|---|
| Size | 85.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e898470bf9d4000e287ae4f25153f0d06700d04b224617456cd976c294a44631
|
|
BLAKE2b-256 checksum How to use checksums |
bdace0b34d7ac80dc672e4647cca211723566fb649fe405a2cb82c742cfe5e03
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / barycenter-1.0.0-py3-none-any.whl
| Download URL | barycenter-1.0.0-py3-none-any.whl |
|---|---|
| Size | 87.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f23c65894339d186ca7ae88c2ecc46d4851ee39c8d765e5d4d6b07edec78b072
|
|
BLAKE2b-256 checksum How to use checksums |
b1e8c62a19c41ca255c6ae9371488bb905aa575680206b8eaa28621a64c6f9a5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency log