Skip to content

Commit 8abd7ab

Browse files
authored
Merge pull request #12 from lanl/develop
Modernize the code base
2 parents ad6af44 + 831f583 commit 8abd7ab

14 files changed

Lines changed: 1258 additions & 825 deletions

.vscode/launch.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Python: Current File",
9+
"type": "python",
10+
"request": "launch",
11+
"program": "${file}",
12+
"console": "integratedTerminal",
13+
"justMyCode": false
14+
}
15+
]
16+
}

CHANGES.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Changes for swiftbat_python
2+
3+
## v 0.1.4 2023-08-05
4+
5+
- Started CHANGES.md file
6+
- Replace `pyephem` with `skyfield`
7+
- pyephem is no longer supported
8+
- Pointing history replaced screen-scraping with `swifttool` database access
9+
- Appeared as DOS on the scraped website when I ran too often
10+
- General decrufting
11+
- reformatted with black
12+
- Code still suffers from being the first Python I ever wrote, on python2.6
13+
- Some (but not much) type hinting)
14+
- Requirements updated:
15+
- Python >= 3.9
16+
- astropy >= 5
17+
- skyfield >= 1.4

setup.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from setuptools import setup
22

3-
copyright="""
3+
copyright = """
44
Copyright (c) 2018, Triad National Security, LLC. All rights reserved.
55
66
This program was produced under U.S. Government contract
@@ -28,27 +28,38 @@
2828
"""
2929

3030
try:
31-
with open("README.md", 'r') as f:
31+
with open("README.md", "r") as f:
3232
long_description = f.read()
3333
except FileNotFoundError:
34-
long_description = ''
34+
long_description = ""
3535

3636
setup(
37-
name='swiftbat',
38-
version='0.1.2a3',
39-
packages=['swiftbat'],
40-
package_data={'':['catalog', 'recent_bcttb.fits.gz']},
41-
url='https://github.com/lanl/swiftbat_python/',
42-
license='BSD-3-Clause',
43-
author='David M. Palmer',
44-
author_email='palmer@lanl.gov',
45-
description='Routines for dealing with data from BAT on the Neil Gehrels Swift Observatory',
37+
name="swiftbat",
38+
version="0.1.4",
39+
packages=["swiftbat"],
40+
package_data={"": ["catalog", "recent_bcttb.fits.gz"]},
41+
url="https://github.com/lanl/swiftbat_python/",
42+
license="BSD-3-Clause",
43+
author="David M. Palmer",
44+
author_email="palmer@lanl.gov",
45+
description="Routines for dealing with data from BAT on the Neil Gehrels Swift Observatory",
4646
long_description=long_description,
47-
long_description_content_type='text/markdown',
48-
entry_points={'console_scripts': ['swinfo=swiftbat.swinfo:main']},
49-
install_requires = ['beautifulsoup4', 'pyephem', 'astropy', 'astroquery', 'numpy'],
50-
classifiers=['Development Status :: 3 - Alpha', 'Intended Audience :: Science/Research', 'Programming Language :: Python',
51-
'Programming Language :: Python :: 3',
52-
'Topic :: Scientific/Engineering :: Astronomy', ],
53-
python_requires='>=3.6',
47+
long_description_content_type="text/markdown",
48+
entry_points={"console_scripts": ["swinfo=swiftbat.swinfo:swinfo_main"]},
49+
install_requires=[
50+
"astropy>=5",
51+
"astroquery",
52+
"numpy",
53+
"python-dateutil",
54+
"skyfield>=1.4",
55+
"swifttools",
56+
],
57+
classifiers=[
58+
"Development Status :: 4 - Beta",
59+
"Intended Audience :: Science/Research",
60+
"Programming Language :: Python",
61+
"Programming Language :: Python :: 3",
62+
"Topic :: Scientific/Engineering :: Astronomy",
63+
],
64+
python_requires=">=3.9",
5465
)

swiftbat/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
1818
"""
1919

20-
from __future__ import print_function, division, absolute_import
21-
20+
from .sfmisc import sftime, sfts, loadsfephem
2221
from .clockinfo import utcf
2322
from .swutil import *
2423
from .swinfo import *
2524
from .batcatalog import BATCatalog
25+
from . import generaldir

swiftbat/batcatalog.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
from astropy.io import fits
2626
import numpy as np
2727
from pathlib import Path
28-
from swiftbat import simbadnames, simbadlocation
28+
from swiftbat import simbadlocation
2929
import re
3030

3131

32-
class BATCatalog():
33-
filebasename = 'recent_bcttb.fits.gz'
32+
class BATCatalog:
33+
filebasename = "recent_bcttb.fits.gz"
3434
thisdir = Path(__file__).parent
3535

3636
def __init__(self, catalogfilename=None):
@@ -86,34 +86,36 @@ def getall(self, item):
8686

8787
def simbadmatch(self, item, tolerance=0.2):
8888
"""
89-
What rows match the catalogued
90-
:param item:
91-
:param tolerance:
92-
:return:
89+
What rows match the catalogued
90+
:param item:
91+
:param tolerance:
92+
:return:
9393
"""
9494
ra, dec = simbadlocation(item)
9595
return self.positionmatch(ra, dec, tolerance)
9696

9797
def positionmatch(self, radeg, decdeg, tolerance=0.2):
9898
rascale = np.cos(np.deg2rad(decdeg))
99-
tol2 = tolerance ** 2
100-
raoff = (((self.cattable['RA_OBJ'] - radeg) + 180) % 360 - 180) # Handle the 360-0 wrap
101-
dist2 = (raoff * rascale) ** 2 + (self.cattable['DEC_OBJ'] - decdeg) ** 2
99+
tol2 = tolerance**2
100+
raoff = (
101+
(self.cattable["RA_OBJ"] - radeg) + 180
102+
) % 360 - 180 # Handle the 360-0 wrap
103+
dist2 = (raoff * rascale) ** 2 + (self.cattable["DEC_OBJ"] - decdeg) ** 2
102104
return self.cattable[dist2 < tol2]
103105

104106
def allnames(self):
105-
return set([row['NAME'] for row in self.cattable if row['NAME']])
107+
return set([row["NAME"] for row in self.cattable if row["NAME"]])
106108

107109
def makeindices(self):
108110
self.bycatnum = {}
109111
self.byname = {}
110112
self.bysimplename = {}
111113
for row in self.cattable:
112-
if not row['CATNUM']:
114+
if not row["CATNUM"]:
113115
continue
114-
self.bycatnum.setdefault(row['CATNUM'], []).append(row)
115-
self.byname.setdefault(row['NAME'], []).append(row)
116-
self.bysimplename.setdefault(self.simplename(row['NAME']), []).append(row)
116+
self.bycatnum.setdefault(row["CATNUM"], []).append(row)
117+
self.byname.setdefault(row["NAME"], []).append(row)
118+
self.bysimplename.setdefault(self.simplename(row["NAME"]), []).append(row)
117119

118120
@lru_cache(maxsize=0)
119121
def _cattable(self, catalogfilename=None):
@@ -132,4 +134,4 @@ def simplename(self, name):
132134
:param name:
133135
:return:
134136
"""
135-
return re.sub('[^a-z0-9]', '', name.lower())
137+
return re.sub("[^a-z0-9]", "", name.lower())

swiftbat/clockinfo.py

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
from __future__ import division, print_function
2-
31
import os
2+
import sys
43
import glob
54
import datetime
6-
import re
75
from pathlib import Path
8-
import ftplib
6+
from .generaldir import httpDir
7+
from astropy.io import fits
8+
99

1010
"""
1111
From the FITS file:
@@ -50,36 +50,38 @@
5050

5151

5252
class clockErrData:
53-
# URL is never used on David Palmer's machine (updates handled by ...swift-trend/getit)
54-
# clockurl = "https://heasarc.gsfc.nasa.gov/FTP/swift/calib_data/sc/bcf/clock/"
55-
clockurl = "ftps://heasarc.gsfc.nasa.gov/caldb/data/swift/mis/bcf/clock/"
56-
clockhost = 'heasarc.gsfc.nasa.gov'
57-
clockhostdir = '/caldb/data/swift/mis/bcf/clock/'
58-
clockfile_regex = 'swclockcor20041120v\d*.fits'
53+
clockurl = "https://heasarc.gsfc.nasa.gov/FTP/swift/calib_data/sc/bcf/clock/"
54+
clockhost = "heasarc.gsfc.nasa.gov"
55+
clockhostdir = "/caldb/data/swift/mis/bcf/clock/"
56+
clockfile_regex = "swclockcor20041120v\d*.fits"
57+
clockfilepattern = "swclockcor20041120v*.fits"
5958
# FIXME this should be derived from the dotswift params
60-
clocklocalsearchpath = ['/opt/data/Swift/swift-trend/clock',
61-
os.path.expanduser('~/.swift/swiftclock'),
62-
'/tmp/swiftclock']
63-
clockfilepattern = 'swclockcor20041120v*.fits'
59+
clocklocalsearchpath = [
60+
"/opt/data/Swift/swift-trend/clock",
61+
os.path.expanduser("~/.swift/swiftclock"),
62+
"/tmp/swiftclock",
63+
]
6464

6565
def __init__(self):
6666
try:
6767
self._clockfile = self.clockfile()
68-
from astropy.io import fits
68+
6969
f = fits.open(self._clockfile)
7070
# copies are needed to prevent pyfits from holding open a file handle for each item
71-
self._tstart = f[1].data.field('TSTART').copy()
72-
self._tstop = f[1].data.field('TSTOP').copy()
73-
self._toffset = f[1].data.field('TOFFSET').copy()
74-
self._c0 = f[1].data.field('C0').copy()
75-
self._c1 = f[1].data.field('C1').copy()
76-
self._c2 = f[1].data.field('C2').copy()
71+
self._tstart = f[1].data.field("TSTART").copy()
72+
self._tstop = f[1].data.field("TSTOP").copy()
73+
self._toffset = f[1].data.field("TOFFSET").copy()
74+
self._c0 = f[1].data.field("C0").copy()
75+
self._c1 = f[1].data.field("C1").copy()
76+
self._c2 = f[1].data.field("C2").copy()
7777
f.close()
7878
except:
7979
self._clockfile = ""
8080
raise RuntimeError("No clock UTCF file")
8181

82-
def utcf(self, t, trow=None): # Returns value in seconds to be added to MET to give correct UTCF
82+
def utcf(
83+
self, t, trow=None
84+
): # Returns value in seconds to be added to MET to give correct UTCF
8385
if not self._clockfile:
8486
return 0.0, "No UTCF file"
8587
if trow is None:
@@ -93,9 +95,13 @@ def utcf(self, t, trow=None): # Returns value in seconds to be added to MET to
9395
else:
9496
row = row[-1]
9597
if self._tstop[row] + caveat_time < t:
96-
caveats += "Time %.1f days after clock correction interval\n" % ((t - self._tstop[row]) / 86400)
98+
caveats += "Time %.1f days after clock correction interval\n" % (
99+
(t - self._tstop[row]) / 86400
100+
)
97101
ddays = (t - self._tstart[row]) / 86400.0
98-
tcorr = self._toffset[row] + 1e-6 * (self._c0[row] + ddays * (self._c1[row] + ddays * self._c2[row]))
102+
tcorr = self._toffset[row] + 1e-6 * (
103+
self._c0[row] + ddays * (self._c1[row] + ddays * self._c2[row])
104+
)
99105
return -tcorr, caveats
100106

101107
def updateclockfiles(self, clockdir, ifolderthan_days=30, test_days=1):
@@ -108,39 +114,44 @@ def updateclockfiles(self, clockdir, ifolderthan_days=30, test_days=1):
108114
"""
109115
testfile = os.path.join(clockdir, "clocktest")
110116
try:
111-
clockfile = sorted(list(glob.glob(os.path.join(clockdir, self.clockfilepattern))))[-1]
112-
age = datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(os.path.getmtime(clockfile))
117+
clockfile = sorted(
118+
list(glob.glob(os.path.join(clockdir, self.clockfilepattern)))
119+
)[-1]
120+
age = datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(
121+
os.path.getmtime(clockfile)
122+
)
113123
if age.total_seconds() < (86400 * ifolderthan_days):
114124
return
115125
# Check no more than once a day
116-
testage = datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(os.path.getmtime(testfile))
126+
testage = datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(
127+
os.path.getmtime(testfile)
128+
)
117129
if testage.total_seconds() < (86400 * test_days):
118130
return
119131
except:
120132
pass
121-
# Requires wget. If this is a problem, use ftplib.FTP
122-
# os.system(
123-
# "wget -q --directory-prefix=%s --no-host --no-clobber --cut-dirs=6 -r %s"
124-
# % (clockdir, self.clockurl) )
125133
try:
126-
ftps = ftplib.FTP_TLS(self.clockhost)
127-
ftps.login() # anonymous
128-
ftps.prot_p() # for ftps
129-
ftps.cwd(self.clockhostdir)
130-
clockreg = re.compile(self.clockfile_regex)
131-
ftplatest = sorted([f for f in ftps.nlst() if clockreg.match(f)])[-1]
132-
locallatest = Path(clockdir).joinpath(ftplatest)
133-
if not locallatest.exists():
134-
with open(locallatest, "wb") as newfile:
135-
ftps.retrbinary(f'RETR {ftplatest}', newfile.write)
136-
open(testfile,'w').write(' ') # touch
134+
clockremotedir = httpDir(self.clockurl)
135+
clockremote = sorted(clockremotedir.getMatches("", self.clockfile_regex))[
136+
-1
137+
]
138+
locallatest = Path(clockdir).joinpath(Path(clockremote).name)
139+
clockremotedir.copyToFile(clockremote, locallatest)
140+
open(testfile, "w").write(" ") # touch
137141
except Exception as e:
138142
print(e, file=sys.stdout)
139143

140-
141144
def clockfile(self):
142145
for clockdir in self.clocklocalsearchpath:
146+
# Directory must exist and have readable clock files in it.
143147
if os.path.exists(clockdir):
148+
try:
149+
clockfile = sorted(
150+
list(glob.glob(os.path.join(clockdir, self.clockfilepattern)))
151+
)[-1]
152+
clockdata = fits.getdata(clockfile)
153+
except:
154+
continue
144155
break
145156
else:
146157
for clockdir in self.clocklocalsearchpath:
@@ -154,11 +165,15 @@ def clockfile(self):
154165
pass
155166
else:
156167
try:
157-
os.makedirs(clockdir) # Force directory to exist in the temp directory
168+
os.makedirs(
169+
clockdir
170+
) # Force directory to exist in the temp directory
158171
except FileExistsError:
159172
pass
160173
self.updateclockfiles(clockdir)
161-
clockfile = sorted(list(glob.glob(os.path.join(clockdir, self.clockfilepattern))))[-1]
174+
clockfile = sorted(
175+
list(glob.glob(os.path.join(clockdir, self.clockfilepattern)))
176+
)[-1]
162177
return clockfile
163178

164179

@@ -175,9 +190,7 @@ def utcf(met, printCaveats=True, returnCaveats=False):
175190
theClockData = clockErrData()
176191
try:
177192
uc = [theClockData.utcf(t_) for t_ in met]
178-
# http://muffinresearch.co.uk/archives/2007/10/16/python-transposing-lists-with-map-and-zip/
179-
# Not valid after Python 2.7
180-
u, c = map(None, *uc)
193+
u, c = zip(*uc)
181194
if printCaveats and any(c):
182195
print("\n".join(["**** " + c_ for c_ in c if c_]))
183196
except TypeError:

0 commit comments

Comments
 (0)