-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrdvdif.py
184 lines (153 loc) · 6.01 KB
/
rdvdif.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import struct, os, collections, datetime
def mapfold(fn, lst, init):
# fn(acc, res) => (acc, res)
# init = initial accumulator
def reductor( (acc, results), element):
(newacc, res) = fn(acc, element)
return (newacc, results+[res])
return reduce(reductor, lst, (init, []))
class vdif_frame(object):
def __init__(self, vdif_file, rdData=True):
bytes = vdif_file.read(16)
l = struct.unpack("<4I", bytes)
self.hdr = l[0:4]
# skip 16 bytes if not legacy
if not self.legacy():
vdif_file.seek(16, os.SEEK_CUR)
# read data array
daSz = self.dataArraySize()
#self.data = struct.unpack("<{0}B".format(daSz), vdif_file.read(daSz) )
if rdData:
self.data = struct.unpack("<{0}I".format(daSz/4), vdif_file.read(daSz) )
else:
vdif_file.seek(daSz, os.SEEK_CUR)
self.data = None
def time(self, year=None):
# vdif time stamp is 'seconds since the epoch'
return self.hdr[0] & 0x3fffffff
def legacy(self):
# bit 30 in word 0 of the header
return bool(self.hdr[0] & 0x40000000)
def complex(self):
# complex flex is bit 31 in word 3
return bool(self.hdr[3] & 0x80000000)
def framenr(self):
# bits 0:23 in word 1 of the header
return self.hdr[1] & 0x00ffffff
def VDIFEpoch(self):
# epoch is 6 bits in word 1 of the header, after
# (or before) the 24 bit frame number within seconds
return (self.hdr[1] >> 24) & 0x3f
def frameSize(self):
# data array size is encoded in the header as
# 'number of 8-byte words' in bits 0:23 of word 2
return ((self.hdr[2] & 0x00ffffff) * 8)
def dataArraySize(self):
return self.frameSize() - (16 if self.legacy() else 32)
def nChannels(self):
# 2log(nchan) is stored in 5 bits; bits 24:28 in word 2
return 2**((self.hdr[2]>>24) & 0x1f)
def bitsPerSample(self):
# "bits per sample - 1" is recorded in bits 26:30 (5 bits) of word 3
# Fix broken "bps-1" value in some (old) VDIF files produced by an
# erroneous version of jive5ab ... [if only we know who the author of
# that PoS was!!!]
bps = ((self.hdr[3]>>26) & 0x1f) + 1
return (bps-1 if (bps % 2) else bps)
def threadID(self):
# 10 bits in word 3; 16:25
return ((self.hdr[3]>>16) & 0x3ff)
def stationID(self):
# 16 bits that are either two characters or one 16-bit number
# station ID is lower 16 bits in word 3
# return as string?
return "{0}{1} [0x{2:X}]".format( chr(self.hdr[3]&0xff00), chr(self.hdr[3]&0xff), int(self.hdr[3]&0xffff) )
def timeVex(self):
# epoch counts number of half years since 2000
(ny, nhalf) = divmod(self.VDIFEpoch(), 2)
(y, m, d) = (2000 + ny, 6*nhalf, 1)
(y, m, d, h, m, s, dow, doy, tz) \
= (datetime.datetime(y, m, d) + datetime.timedelta( float(self.time())/86400.0 )).timetuple() if \
(y>0 and m>0 and d>0) else [0]*9
return "{0:04d}y{1:03d}d{2:02d}h{3:02d}m{4:02d}s".format( y, doy, h, m, s )
def deChannelize(self):
rv = collections.defaultdict(list)
numChannel = self.nChannels()
chOffset = 0
nSampByte = 8 / self.bitsPerSample()
LUT = luts[ self.bitsPerSample() ]
if self.data is None:
return rv
# decode in chunks of numChannel
# i.e. decode this thread into numChannel channels,
# but there may be other threadIDs too so we assume
# same #-channels-per-thread for all threads in this
# VDIF stream
curWord = 0
curChannel = 0
chanOffset = self.threadID()*numChannel
while curWord<len(self.data):
woord = self.data[curWord]
for curByte in range(4):
decodedChannels = LUT[ woord & 0xff ]
# Loop over the samples in this byte
for i in range(nSampByte):
rv[ chanOffset + curChannel ].append( decodedChannels[i] )
curChannel = (curChannel+1) % numChannel
woord >>= 8
curWord = curWord+1
print "Decoded frame:"
for (k, v) in rv.iteritems():
print " Ch[{0}]: {1} samples".format(k, len(v))
return rv
def __str__(self):
return self.timeVex()+"/{0:05d}".format(self.framenr())
#return str(self.time()) + "/" + ("%05d" % self.framenr())
def frames(filenm, n, rddata):
if n is None:
n = -1
with open(filenm) as f:
cnt = 0
while (n<0 or cnt<n):
vf = vdif_frame( f, rddata )
yield vf
#f.seek(vf.byteSize()-16, os.SEEK_CUR)
cnt += 1
# decode a VDIF frame into a dict of sample streams -
# one stream per channel
def decode_file(fn, n=None):
for frm in frames(fn, n, True):
yield frm.deChannelize()
# LUTs taken from Walter Brisken's mark5access library
# 1 bit lookup table
def mk_lut_1bit():
lut = []
for i in range(256):
lut.append( [0]*8 )
for ch in range(8):
lut[-1][ch] = 1.0 if (i & 0x1) else -1.0
i >>= 1
return lut
HiMag = 3.3359
# 2-bit levels: 00 = lowest, 01 = -sigma, 10 = +sigma, 11 = highest
twobitLevels = { 0:-HiMag, 1:-1.0, 2:1.0, 3:HiMag }
def mk_lut_2bit():
lut = []
for i in range(256):
lut.append( [0]*4 )
for ch in range(4):
lut[-1][ch] = twobitLevels[ i & 0x3 ]
i >>= 2
return lut
luts = { 1: mk_lut_1bit(), 2:mk_lut_2bit() }
############################
##
## Print frame information
##
############################
def rdfile(fn, n=None):
for frm in frames(fn, n, False):
print "{0} THRD:{1} NCH:{2}@{3}bps{4}".format(str(frm), frm.threadID(), frm.nChannels(), frm.bitsPerSample(), " (Legacy)" if frm.legacy() else "")
if __name__ == "__main__":
import sys
map(rdfile, sys.argv[1:])