aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README68
-rw-r--r--__init__.py1
-rwxr-xr-xconfig.py2
-rwxr-xr-xdraw.py57
-rwxr-xr-xfetch.py115
-rw-r--r--lecroy.py23
-rw-r--r--setup.py1
-rwxr-xr-xsock.py103
-rwxr-xr-xview.py2
9 files changed, 302 insertions, 70 deletions
diff --git a/README b/README
index a6f371b..6dbf283 100644
--- a/README
+++ b/README
@@ -1,14 +1,35 @@
-LeCrunch version 1.0
+LeCrunch version 1.1
====================
Installation
------------
-You must have h5py, a general-purpose Python interface to the Hierarchical
-Data Format Library version 5 installed. The source can be downloaded from
-<code.google.com/p/h5py>, or on an ubuntu distribution:
+LeCrunch requires a python version >= 2.6 and the following python packages:
-~$ sudo apt-get install python-h5py
+ numpy
+ The fundamental package needed for scientific computing with python.
+ On an ubuntu distribution:
+
+ ~$ sudo apt-get install python-numpy
+
+ h5py
+ A general-purpose Python interface to the Hierarchical Data Format
+ Library. The source can be downloaded from <code.google.com/p/h5py>,
+ or on an ubuntu distribution:
+
+ ~$ sudo apt-get install python-h5py
+
+ matplotlib
+ A python 2D plotting library which produces publication quality figures
+ in a variety of hardcopy formats and interactive environments across
+ platforms; on an ubuntu distribution:
+
+ ~$ sudo apt-get install python-matplotlib
+
+After downloading LeCrunch, you should first edit the file "config.py" and
+set the variable "scope_ip" to the ip address of your oscilloscope. If you're
+unsure of the ip address of your oscilloscope, just open up a web browser
+on the oscilloscope and go to www.whatsmyip.org.
Then just source the env.sh file in this directory, and you're good to go!
@@ -28,6 +49,43 @@ The syntax to fetch waveforms:
where <filename> is the name of the file you would like LeCrunch to store the
extracted waveforms; waveforms are stored in the hdf5 file format.
+Once you have fetched some waveforms you can plot an overlay with draw.py:
+
+~$ draw.py <filename>
+
+You can load and save the scope's configuration with config.py. Suppose you
+took some data a month ago and saved it to the file "run001.hdf5". Then, to
+restore the scope configuration as it was when you took that data, just run:
+
+~$ config.py load run001.hdf5
+
+In order to send commands directly to the oscilloscope you can run the file
+"sock.py" as a script:
+
+~$ sock.py "display?"
+'DISP ON\n'
+
+~$ sock.py "sequence?"
+'SEQ OFF,1000,25E+3 SAMPLE\n'
+
+~$ sock.py "seq on, 100"
+
+For a list of commands see the LeCroy Remote Control Manual. If you don't have
+a copy, just google it.
+
+How to Transfer Waveforms at High Speed
+---------------------------------------
+
+Turn on sequence mode!
+
+~$ sock.py 'seq on, 1000'
+
+Be aware that as you turn up the number of segments the scope will take
+longer to process a single acquisition. The default timeout value for the
+socket connection in sock.py is set to 2 seconds; therefore, if an acquisition
+takes longer than 2 seconds, the socket object will raise a timeout exception
+when you request a waveform.
+
Documentation
-------------
diff --git a/__init__.py b/__init__.py
deleted file mode 100644
index 80ccd7c..0000000
--- a/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from fetch import fetch
diff --git a/config.py b/config.py
index b55c5f5..2af80da 100755
--- a/config.py
+++ b/config.py
@@ -40,7 +40,7 @@ Examples:
"""
commands = ['TIME_DIV', 'COMM_FORMAT', 'COMM_HEADER', 'COMM_ORDER'] + \
- ['TRIG_DELAY', 'TRIG_SELECT', 'TRIG_MODE', 'TRIG_PATTERN'] + \
+ ['TRIG_DELAY', 'TRIG_SELECT', 'TRIG_MODE', 'TRIG_PATTERN', 'SEQUENCE'] + \
['C%i:COUPLING' % i for i in range(1,5)] + \
['C%i:VOLT_DIV' % i for i in range(1,5)] + \
['C%i:OFFSET' % i for i in range(1,5)] + \
diff --git a/draw.py b/draw.py
new file mode 100755
index 0000000..70efda7
--- /dev/null
+++ b/draw.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+import h5py
+import numpy as np
+import matplotlib.pyplot as plt
+from itertools import cycle, islice
+
+def roundrobin(*iterables):
+ pending = len(iterables)
+ nexts = cycle(iter(it).next for it in iterables)
+
+ while pending:
+ try:
+ for next in nexts:
+ yield next()
+ except StopIteration:
+ pending -= 1
+ nexts = cycle(islice(nexts, pending))
+
+def draw(filename, nevents):
+ f = h5py.File(filename)
+
+ fig = plt.figure()
+ for i, dataset in enumerate((f[name] for name in f)):
+ plt.subplot(len(f), 1, i+1)
+ nevents = min(options.nevents, len(dataset))
+ nsamples = dataset.attrs['wave_array_count']//dataset.attrs['nom_subarray_count']
+ dx = dataset.attrs['horiz_interval']
+ dy = dataset.attrs['vertical_gain']
+ xoffset = dataset.attrs['horiz_offset']
+ yoffset = dataset.attrs['vertical_offset']
+
+ x = np.tile(np.linspace(xoffset, xoffset+dx*nsamples, nsamples)*1e9, (nevents,1))
+ y = dataset[:nevents]*dy - yoffset
+
+ plt.plot(*roundrobin(x, y), color='black')
+ plt.xlabel('Time (ns)')
+ plt.ylabel('Voltage (V)')
+ plt.title(dataset.name)
+
+ return fig
+
+if __name__ == '__main__':
+ import sys
+ import optparse
+
+ parser = optparse.OptionParser('%prog file [...]')
+ parser.add_option('-n', type='int', dest='nevents', default=100)
+ options, args = parser.parse_args()
+
+ if len(args) < 1:
+ sys.exit(parser.format_help())
+
+ figures = []
+ for filename in args:
+ figures.append(draw(filename, options.nevents))
+
+ plt.show()
diff --git a/fetch.py b/fetch.py
index 0db4984..eaeca88 100755
--- a/fetch.py
+++ b/fetch.py
@@ -21,21 +21,21 @@ import time
import socket
import struct
import h5py
+import setup
from lecroy import LeCroyScope
from config import get_settings
-def fetch(filename, events):
+def fetch(filename, nevents):
"""
Fetch and save waveform traces from the oscilloscope.
Args:
- filename: str
Filename to store traces in (in hdf5 format).
- - events: int
+ - nevents: int
Number of triggered events to save in `filename`.
"""
- scope = LeCroyScope('scope01.hep.upenn.edu')
- scope.connect()
+ scope = LeCroyScope(setup.scope_ip, timeout=20.0)
# turn off the display
scope.send('display off')
@@ -52,33 +52,38 @@ def fetch(filename, events):
# get wave descriptors for each channel
# important to do this before queue is primed!
+ # need to trigger in order to get correct wave_array_count
+ scope.trigger()
+
+ time.sleep(5.0)
+
wavedesc = {}
for channel in channels:
wavedesc[channel] = scope.getwavedesc(channel)
- # prime the output queue
- for i in range(10):
- scope.trigger()
- for channel in channels:
- scope.send('c%i:wf? dat1' % channel)
-
# open up the output file
f = h5py.File(filename, 'w')
+
# set scope configuration
for command, setting in settings.items():
f.attrs[command] = setting
- dataset = {}
+
+ if 'ON' in f.attrs['SEQUENCE']:
+ sequence_count = int(f.attrs['SEQUENCE'].split(',')[1])
+
+ if sequence_count < 1:
+ raise Exception('sequence count must be a positive number.')
+ else:
+ sequence_count = 1
+
for channel in channels:
- samples = wavedesc[channel]['wave_array_count']
- chunks = (max(1,min(100, events//100)), samples)
- dataset[channel] = f.create_dataset('channel%i' % channel,
- (events, samples),
- dtype=wavedesc[channel]['dtype'],
- chunks=chunks,
- compression='gzip')
+ nsamples = wavedesc[channel]['wave_array_count']//sequence_count
+
+ f.create_dataset('channel%i' % channel, (nevents, nsamples), dtype=wavedesc[channel]['dtype'], chunks=(max(1,min(100, nevents//100)), nsamples), compression='gzip')
+
for key, value in wavedesc[channel].items():
try:
- dataset[channel].attrs[key] = value
+ f['channel%i' % channel].attrs[key] = value
except ValueError:
pass
@@ -86,53 +91,68 @@ def fetch(filename, events):
time0 = time.time()
try:
- for i in range(events):
- print '\rSaving event: %i' % (i+1),
+ i = 0
+ while True:
+ print '\rsaving event: %i' % i,
sys.stdout.flush()
try:
scope.trigger()
for channel in channels:
- dataset[channel][i] = scope.getwaveform(channel,
- wavedesc[channel])
- except (socket.error, struct.error, RuntimeError, OverflowError):
- print "\nFailed to receive waveform %i" % (i+1)
- # clear the output queue
+ wave_array = scope.getwaveform(channel, wavedesc[channel])
+
+ if sequence_count > 1:
+ try:
+ f['channel%i' % channel][i:i+sequence_count] = \
+ wave_array.reshape(sequence_count, wave_array.size//sequence_count)
+ except ValueError:
+ f['channel%i' % channel][i:i+sequence_count] = \
+ wave_array.reshape(sequence_count, wave_array.size//sequence_count)[:len(f['channel%i' % channel])-i]
+ else:
+ f['channel%i' % channel][i] = wave_array
+
+ except (socket.error, struct.error) as e:
+ print '\n' + str(e)
scope.clear()
- # reprime the queue
- for i in range(10):
- scope.trigger()
- for channel in channels:
- scope.send('c%i:wf? dat1' % channel)
continue
+
+ i += sequence_count
+
+ if i >= nevents:
+ print '\rsaving event: %i' % i,
+ break
+
print
+
except KeyboardInterrupt:
- print
- print 'Resizing datasets...'
+ print '\nresizing datasets...'
+
for channel in channels:
- dataset[channel].resize((i, wavedesc[channel]['wave_array_count']))
- raise KeyboardInterrupt
+ f['channel%i' % channel].resize((i, wavedesc[channel]['wave_array_count']//sequence_count))
+
+ raise
+
finally:
f.close()
scope.clear()
scope.send('display on')
scope.check_last_command()
- scope.close()
elapsed = time.time() - time0
- print 'Completed %i events in %.3f seconds.' % (i+1, elapsed)
- print 'Averaged %.5f seconds per acquisition.' % (elapsed/(i+1))
- print "Wrote to file '%s'." % filename
+ if i > 0:
+ print 'Completed %i events in %.3f seconds.' % (i, elapsed)
+ print 'Averaged %.5f seconds per acquisition.' % (elapsed/i)
+ print "Wrote to file '%s'." % filename
if __name__ == '__main__':
import optparse
usage = "usage: %prog <filename/prefix> [-n] [-r]"
parser = optparse.OptionParser(usage, version="%prog 0.1.0")
- parser.add_option("-n", type="int", dest="events",
+ parser.add_option("-n", type="int", dest="nevents",
help="number of events to store per run", default=1000)
- parser.add_option("-r", type="int", dest="runs",
+ parser.add_option("-r", type="int", dest="nruns",
help="number of runs", default=1)
parser.add_option("--time", action="store_true", dest="time",
help="append time string to filename", default=False)
@@ -141,16 +161,19 @@ if __name__ == '__main__':
if len(args) < 1:
sys.exit(parser.format_help())
- if options.events < 1 or options.runs < 1:
+ if options.nevents < 1 or options.nruns < 1:
sys.exit("Please specify a number >= 1 for number of events/runs")
- if options.runs == 1 and not options.time:
- fetch(args[0], options.events)
+ if options.nruns == 1 and not options.time:
+ try:
+ fetch(args[0], options.nevents)
+ except KeyboardInterrupt:
+ pass
else:
import time
import string
- for i in range(options.runs):
+ for i in range(options.nruns):
timestr = string.replace(time.asctime(time.localtime()), ' ', '-')
filename = args[0] + '_' + timestr + '.hdf5'
print '-' * 65
@@ -158,6 +181,6 @@ if __name__ == '__main__':
print '-' * 65
try:
- fetch(filename, options.events)
+ fetch(filename, options.nevents)
except KeyboardInterrupt:
break
diff --git a/lecroy.py b/lecroy.py
index 31c282e..22e4f13 100644
--- a/lecroy.py
+++ b/lecroy.py
@@ -20,7 +20,7 @@ import array
import StringIO
import struct
import numpy as np
-import scope
+import sock
# data types in lecroy binary blocks, where:
# length -- byte length of type
@@ -123,21 +123,12 @@ wavedesc_template = ( ('descriptor_name' , 0 , String),
('acq_vert_offset' , 340 , Float),
('wave_source' , 344 , Enum) )
-class LeCroyScope(scope.Scope):
+class LeCroyScope(sock.Socket):
"""
A class for triggering and fetching waveforms from the oscilloscope.
-
- .. note::
-
- You must call connect() on the object before fetching waveforms.
"""
- def __init__(self, host, port=1861, timeout=2.0):
- super(LeCroyScope, self).__init__(host, port, timeout)
-
- def connect(self):
- """Connect to the oscilloscope."""
- super(LeCroyScope, self).connect()
- # initialize some parameters
+ def __init__(self, *args, **kwargs):
+ super(LeCroyScope, self).__init__(*args, **kwargs)
self.send('comm_header short')
self.check_last_command()
self.send('comm_format DEF9,BYTE,BIN')
@@ -160,7 +151,7 @@ class LeCroyScope(scope.Scope):
if channel not in range(1, 5):
raise Exception('channel must be in %s.' % str(range(1, 5)))
- self.send('c%s:wf? all' % str(channel))
+ self.send('c%s:wf? desc' % str(channel))
msg = self.recv()
if not int(msg[1]) == channel:
@@ -207,8 +198,8 @@ class LeCroyScope(scope.Scope):
def getwaveform(self, channel, wavedesc):
"""
- Request, process, and return the x and y arrays for channel number
- *channel* from the oscilloscope.
+ Request, process, and return the voltage array for channel number
+ `channel` from the oscilloscope as a numpy array.
"""
if channel not in range(1, 5):
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..99572cd
--- /dev/null
+++ b/setup.py
@@ -0,0 +1 @@
+scope_ip = '128.91.41.179'
diff --git a/sock.py b/sock.py
new file mode 100755
index 0000000..8c6da47
--- /dev/null
+++ b/sock.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+
+import struct
+import socket
+import os
+
+headerformat = '>BBBBL'
+
+errors = { 1 : 'unrecognized command/query header',
+ 2 : 'illegal header path',
+ 3 : 'illegal number',
+ 4 : 'illegal number suffix',
+ 5 : 'unrecognized keyword',
+ 6 : 'string error',
+ 7 : 'GET embedded in another message',
+ 10 : 'arbitrary data block expected',
+ 11 : 'non-digit character in byte count field of arbitrary data '
+ 'block',
+ 12 : 'EOI detected during definite length data block transfer',
+ 13 : 'extra bytes detected during definite length data block '
+ 'transfer' }
+
+class Socket(object):
+ def __init__(self, host, port=1861, timeout=5.0):
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.connect((host, port))
+ self.sock.settimeout(timeout)
+
+ def clear(self, timeout=0.5):
+ """
+ Clear any bytes in the oscilloscope's output queue by receiving
+ packets until the connection blocks for more than `timeout` seconds.
+ """
+ t = self.sock.gettimeout()
+ self.sock.settimeout(timeout)
+ try:
+ while True:
+ self.sock.recv(4096)
+ except socket.timeout:
+ pass
+ self.sock.settimeout(t)
+
+ def send(self, msg):
+ """Format and send the string `msg`."""
+ if not msg.endswith('\n'):
+ msg += '\n'
+ header = struct.pack(headerformat, 129, 1, 1, 0, len(msg))
+ self.sock.sendall(header + msg)
+
+ def check_last_command(self):
+ """
+ Check that the last command sent was received okay; if not, raise
+ an exception with details about the error.
+ """
+ self.send('cmr?')
+ err = int(self.recv().split(' ')[-1].rstrip('\n'))
+
+ if err in errors:
+ self.sock.close()
+ raise Exception(errors[err])
+
+ def recv(self):
+ """Return a message from the scope."""
+
+ reply = ''
+ while True:
+ header = ''
+
+ while len(header) < 8:
+ header += self.sock.recv(8 - len(header))
+
+ operation, headerver, seqnum, spare, totalbytes = \
+ struct.unpack(headerformat, header)
+
+ buffer = ''
+
+ while len(buffer) < totalbytes:
+ buffer += self.sock.recv(totalbytes - len(buffer))
+
+ reply += buffer
+
+ if operation % 2:
+ break
+
+ return reply
+
+ def __del__(self):
+ self.sock.close()
+
+if __name__ == '__main__':
+ import sys
+ import setup
+
+ sock = Socket(setup.scope_ip)
+ sock.clear()
+
+ for msg in sys.argv[1:]:
+ sock.send(msg)
+
+ if '?' in msg:
+ print repr(sock.recv())
+
+ sock.check_last_command()
diff --git a/view.py b/view.py
index 7c465e2..c845bfe 100755
--- a/view.py
+++ b/view.py
@@ -31,7 +31,7 @@ mg = {}
for dataset in [f[name] for name in f]:
mg[dataset.name] = TMultiGraph()
- samples = dataset.attrs['wave_array_count']
+ samples = dataset.attrs['wave_array_count']//dataset.attrs['nom_subarray_count']
dx = dataset.attrs['horiz_interval']
dy = dataset.attrs['vertical_gain']
xoffset = dataset.attrs['horiz_offset']