Class 24: Python - parsing binary data 4 - SBET IMU data
Table of Contents
Please do not
update until the professor asks you to.
Introduction
Goals
- [ ] using argparse to handle the command line
- [ ] saving binary data to matlab format. Read it in octave
- [ ] using numpy to save the file
- [ ] writing a KML
- [ ] creating an SQL database with sqlite3. Viewing it with SQLite Manager
See Also
- Original single document of Python - parsing binary data files (points to the most recent version in bitbucket)
What is GIS?
Now that you have been doing Geographic Information System (GIS) tasks for quite a few classes, let me share with you how I view GIS. This figure is not perfect and is a gross oversimplification, but it is useful. The goal of GIS is "Informed Decision Making", which in NOAA equates to Coastal and Marine Spatial Planning (CMSP), safely navigating ships and many other decision making processes.
Setup
Start emacs
Open a terminal and start ipython
In the past, we did the setup in the bash shell, but we can do everything from ipython.
# update your mercurial repository of the class notes cd ~/projects/researchtools/class bookmark -l bookmark hgclass # Save this for next class bookmark -l ls -l > ~/before.txt !hg pull !hg update ls -l > ~/after.txt !diff --unified ~/before.txt ~/after.txt # You will see nothing if you already updated everything for today's classxs mkdir ~/class/24 cd ~/class/24 bookmark c24 bookmark -l cd hgclass pwd cd c24 pwd logstart -o -r log-class-24.py dhist cd -1 cd -2 alias alias rtupdate (cd ~/projects/researchtools; hg pull; hg update) store rtupdate alias rtupdate # Sadly, I don't know how to save an alias in a simple/clean fashion. # "store" for aliases is broken in ipython 0.11 # http://stackoverflow.com/questions/8183986/saving-ipython-aliases # Fetch the sbet file: !curl -O http://vislab-ccom.unh.edu/~schwehr/Classes/2011/esci895-researchtools/examples/21/sample.sbet.bz2 !curl -O http://vislab-ccom.unh.edu/~schwehr/Classes/2011/esci895-researchtools/examples/24/2010_202_S220_subsampled.sbet.bz2 !curl -O http://vislab-ccom.unh.edu/~schwehr/Classes/2011/esci895-researchtools/examples/24/2011_194_S250A_Stbd_subsampled.sbet.bz2 !bunzip2 sample.sbet.bz2 !bunzip2 2010_202_S220_subsampled.sbet.bz2 !bunzip2 2011_194_S250A_Stbd_subsampled.sbet.bz2 !md5sum sample.sbet 196c21f16f07ceae180888b12e9edc56 sample.sbet !md5sum 2010_202_S220_subsampled.sbet e8f7283deb887e16b05b08581bd7d2bb 2010_202_S220_subsampled.sbet !md5sum 2011_194_S250A_Stbd_subsampled.sbet 72e5bab716485b53ecde9aa1cfc90719 2011_194_S250A_Stbd_subsampled.sbet
To remove a bookmark, do
bookmark -d c24
To get rid of all bookmarks:
bookmark -r
Creating a bash alias for mercurial updating the class
Getting the lastest version of the class notes by hand in bash looks like this:
researchtools@ubuntu:~$ cd ~/projects/researchtools/ researchtools@ubuntu:~/projects/researchtools$ hg pull warning: bitbucket.org certificate with fingerprint 81:2b:08:90:dc:d3:71:ee:e0:7c:b4:75:ce:9b:6c:48:94:56:a1:fe not verified (check hostfingerprints or web.cacerts config setting) pulling from https://bitbucket.org/schwehr/researchtools warning: bitbucket.org certificate with fingerprint 81:2b:08:90:dc:d3:71:ee:e0:7c:b4:75:ce:9b:6c:48:94:56:a1:fe not verified (check hostfingerprints or web.cacerts config setting) searching for changes # -- snip -- warning: bitbucket.org certificate with fingerprint 81:2b:08:90:dc:d3:71:ee:e0:7c:b4:75:ce:9b:6c:48:94:56:a1:fe not verified (check hostfingerprints or web.cacerts config setting) adding changesets adding manifests adding file changes added 4 changesets with 7 changes to 5 files (run 'hg update' to get a working copy) researchtools@ubuntu:~/projects/researchtools$ hg update merging class/23-python-binary-files-part-3.org 4 files updated, 1 files merged, 0 files removed, 0 files unresolved
That's not hard, but we want to make this trivial. Edit your ~/.bashaliases file. Add this line:
# Specific to the research tools class alias rtupdate='(cd ~/projects/researchtools; hg pull; hg update)'
The parentheses "()" around the command create a "scope" in which the
cd
command lives. When bash gets to the right hand ")" is will back
out of the researchtools directory and leave you where you started.
These aliases are not active until you create a new terminal or source the alias file in an existing terminal.
alias rtupdate # try to list the alias # bash: alias: rtupdate: not found source ~/.bash_aliases alias rtupdate # your new alias should show up # To list all aliases: alias
Where were we last time?
#!/usr/bin/env python '''Decode Applanix POSPac SBET IMU binary files''' import struct import math # Use the pprint function from the pprint module from pprint import pprint field_names = ('time', 'latitude', 'longitude', 'altitude', \ 'x_vel', 'y_vel', 'z_vel', \ 'roll', 'pitch', 'platform_heading', 'wander_angle', \ 'x_acceleration', 'y_acceleration', 'z_acceleration', \ 'x_angular_rate', 'y_angular_rate', 'z_angular') datagram_size = 136 # 8*17 bytes per datagram def num_datagrams(data): 'How many packets are in data' assert( len(data) % datagram_size == 0 ) return len(data) / datagram_size def get_offset(datagram_number): 'Calculate the starting offset of a datagram. First datagram is number 0' return datagram_number * datagram_size def decode(data, offset=0): 'Decipher a SBET datagram from binary' values = struct.unpack('17d',data[ offset + 0 : offset + 8*17]) sbet_values = dict(zip (field_names, values)) sbet_values['lat_deg'] = math.degrees(sbet_values['latitude']) sbet_values['lon_deg'] = math.degrees(sbet_values['longitude']) return sbet_values def load_sbet_file(filename): '''This is a GENERATOR that we can loop over with a for''' sbet_file = open(filename) sbet_data = sbet_file.read() for datagram_index in range( num_datagrams(sbet_data) ): offset = get_offset(datagram_index) datagram = decode(sbet_data, offset) datagram['index'] = datagram_index yield datagram def main(): print 'Starting main' sbet_file = open('sample.sbet') sbet_data = sbet_file.read() print 'Number of datagrams:', num_datagrams(sbet_data) print 'Datagram Number, Time, x, y' for datagram_index in range( num_datagrams(sbet_data) ): offset = get_offset(datagram_index) datagram = decode(sbet_data, offset) print datagram_index, datagram['time'], datagram['lon_deg'], datagram['lat_deg'] if __name__ == '__main__': print 'starting to run script...' main() print 'script done!'
Command line arguments commandlineargs
TODO fileinput
If we were not working with binary files, we might try the fileinput module. However, I'll have to leave that for another time.
searching for files with glob glob
If we know something about the names of the files we want to work with, we can use glob. Glob gives us bash command line like file name expansion with "*", "?", and "[0-9]" style expansion. Try it from inside of ipython:
import glob glob.glob('*.py') glob.glob('*.sbet')
Now change your main to handle all of the sbet files in the current directory. This is not a good way to handle files, but it can be useful.
Indent code in main with C-c >, change the loadsbetfile to take a
filename
argument and add the for loop.
Remove the open, read, and print of the number of datagrams lines.
You main function should look like this:
def main(): print 'Starting main' import glob for filename in glob.glob('*.sbet'): print '====',filename,'====' print 'Datagram Number, Time, x, y' for datagram_index, datagram in enumerate(load_sbet_file(filename)): if datagram_index % 10 == 0: print datagram_index, datagram['time'], datagram['lon_deg'], datagram['lat_deg']
Now run it from ipython:
run sbet
Simple route with sys.argv sys
Bet yet, the person using the sbet.py program should be able to specify which sbet files she wants to use. We can do this by using sys.argv. Check out argv from ipython:
import sys print sys.argv # any file names specified will be after the 1st parameter, which # is the name of the program. print sys.argv[1:]
Remove the glob code from your main and replace it to use sys.argv. Your code should look like this:
def main(): print 'Starting main' import sys for filename in sys.argv[1:]: print '====',filename,'====' print 'Datagram Number, Time, x, y' for datagram_index, datagram in enumerate(load_sbet_file(filename)): if datagram_index % 10 == 0: print datagram_index, datagram['time'], datagram['lon_deg'], datagram['lat_deg']
It would be nice if shell expansion of "glob" style worked, but for ipython 0.10.2, we have to list out all the file names
http://stackoverflow.com/questions/8227705/run-from-ipython-with-glob-style-expansion
filenames run sbet.py sample.sbet 2010_202_S220_subsampled.sbet 2011_194_S250A_Stbd_subsampled.sbet
Your results should look like this:
starting to run script... Starting main ==== sample.sbet ==== Datagram Number, Time, x, y 0 334959.004823 -146.675232704 60.4443123064 10 335458.991726 -146.631176844 60.4833850599 20 335958.978642 -146.615374641 60.488061874 30 336458.965552 -146.610408727 60.4884544574 40 336958.952453 -146.602068248 60.4880620038 50 337458.939347 -146.601381986 60.4873117644 60 337958.926239 -146.614299722 60.488558233 70 338458.913138 -146.631671861 60.4823912283 80 338958.900046 -146.6171884 60.4849670034 90 339458.886961 -146.638808522 60.4798499224 100 339958.873884 -146.641052374 60.4777459053 110 340458.860796 -146.640432902 60.4794631211 120 340958.847702 -146.639497417 60.4777739654 130 341458.834603 -146.64539724 60.4721610761 140 341958.821503 -146.63946463 60.4730424798 150 342458.808406 -146.65277304 60.467285398 160 342958.795311 -146.669429917 60.4556509106 ==== 2010_202_S220_subsampled.sbet ==== Datagram Number, Time, x, y 0 309498.001641 -168.105985062 65.5652441157 10 310497.990568 -168.14460618 65.576237069 20 311497.979504 -168.217203577 65.5765027734 30 312497.968453 -168.286847083 65.5766343791 40 313497.957407 -168.360572515 65.5768083537 50 314497.946366 -168.430597117 65.5767847792 60 315497.935336 -168.502290483 65.5768396777 70 316497.924312 -168.562203526 65.5784574745 ==== 2011_194_S250A_Stbd_subsampled.sbet ==== Datagram Number, Time, x, y 0 273498.006144 -75.6779674319 36.9993413966 10 274498.035903 -75.6686272625 37.0166578669 20 275498.065662 -75.6303574293 37.0128677637 30 276498.09543 -75.5838353852 37.0129553714 40 277498.125213 -75.5375106226 37.013026657 50 278498.155027 -75.5016787502 37.0139588907 60 279498.184882 -75.544032377 37.0140028866 70 280498.214787 -75.5869761469 37.0139670547 80 281498.244734 -75.6296578787 37.0137003076 90 282498.274713 -75.6712520852 37.0123495064 100 283498.304712 -75.6345773566 37.0144177023 110 284498.33472 -75.5934162586 37.0146980809 120 285498.364735 -75.552605247 37.0144545719 130 286498.394766 -75.5115917803 37.0146829522 140 287498.424822 -75.5230051401 37.0154931321 150 288498.454905 -75.5635060081 37.0154547408 160 289498.485012 -75.6045727647 37.0156019109 170 290498.515137 -75.645795915 37.0153942394 180 291498.545265 -75.6613422226 37.0160430919 190 292498.575392 -75.6248025467 37.0159742854 200 293498.605513 -75.5837033162 37.0163109221 210 294498.63563 -75.5420885061 37.0157435582 220 295498.665747 -75.5011903989 37.0152076818 230 296498.695871 -75.5359292325 37.0165480198 240 297498.726 -75.576711627 37.0167642931 250 298498.756139 -75.6165338265 37.0169717959 260 299498.786282 -75.6577022044 37.0168299723 270 0.0 0.0 0.0 280 0.0 0.0 0.0 290 0.0 0.0 0.0 300 0.0 0.0 0.0 310 0.0 0.0 0.0 320 0.0 0.0 0.0 330 0.0 0.0 0.0 340 0.0 0.0 0.0 350 0.0 0.0 0.0 360 0.0 0.0 0.0 370 0.0 0.0 0.0 380 0.0 0.0 0.0 390 0.0 0.0 0.0 400 0.0 0.0 0.0 410 0.0 0.0 0.0 420 0.0 0.0 0.0 430 0.0 0.0 0.0 440 0.0 0.0 0.0 450 0.0 0.0 0.0 460 0.0 0.0 0.0 470 0.0 0.0 0.0 480 0.0 0.0 0.0 490 0.0 0.0 0.0 500 0.0 0.0 0.0 510 0.0 0.0 0.0 520 299498.786282 -75.6577022044 37.0168299723 530 299998.801354 -75.6780952432 37.0165026156 script done!
Uh oh! Zeros!
More flexible arguments
Please do NOT write your own command line option parse. Also, please don't use the older "optparse". If you use argparse, other people will be better able to work with your code.
http://www.doughellmann.com/PyMOTW/argparse/
Change your main to look like this:
def main(): print 'Starting main' import sys, argparse parser = argparse.ArgumentParser(description='Parse SBET files') parser.add_argument('filenames', type=str, nargs='+', help='SBET files') args = parser.parse_args() # uses sys.argv for filename in args.filenames: print '====',filename,'====' print 'Datagram Number, Time, x, y' for datagram_index, datagram in enumerate(load_sbet_file(filename)): if datagram_index % 10 == 0: print datagram_index, datagram['time'], datagram['lon_deg'], datagram['lat_deg']
Now try running it from ipython:
run sbet.py --help
run sbet.py sample.sbet 2010_202_S220_subsampled.sbet 2011_194_S250A_Stbd_subsampled.sbet
We do not need any options right now, but this is a much better way to handle command line arguments.
It is time to write out kml files kml
Inside of main, we need to create the kml:
for filename in args.filenames: out = open(filename+'.kml','w') out.write('''<?xml version="1.0" encoding="UTF-8"?> <kml xmlns="http://www.opengis.net/kml/2.2"> <Document> <Placemark> <name>{filename}</name> <LineString> <coordinates> '''.format(filename=filename) ) for datagram_index, datagram in enumerate(load_sbet_file(filename)): if datagram_index % 10 == 0: print datagram_index, datagram['time'], datagram['lon_deg'], datagram['lat_deg'] out.write('{x},{y}\n'.format(x=datagram['lon_deg'], y=datagram['lat_deg'])) out.write('''\t\t\t\t</coordinates> \t\t\t</LineString> \t\t</Placemark> \t</Document> </kml> ''')
Histories
history from ipython
In [92]: history 1 92 1 : _ip.magic("cd class") 2 : _ip.system("ls -F ") 3 : _ip.system("ls -F -l") 4 : _ip.magic("pwd ") 5 : _ip.magic("cd researchtools/class/") 6 : _ip.magic("pwd ") 7 : _ip.system("hg pull") 8 : _ip.system("hg update") 9 : _ip.system("ls -F -l > ~/after.txt") 11: _ip.system("diff --unified ~/before.txt ~/after.txt") 12: _ip.system("mkdir ~/class/24") 14: _ip.magic("cd ~/class/24") 15: _ip.magic("bookmark c24") 16: _ip.magic("bookmark -l") 17: _ip.magic("pwd ") 18: _ip.magic("cd hgclass") 19: _ip.magic("pwd ") 20: _ip.magic("bookmark -l") 21: _ip.magic("cd c24") 22: _ip.magic("cd hgclass") 23: _ip.magic("cd class") 24: _ip.magic("pwd ") 25: _ip.magic("bookmark hgclass") 26: _ip.magic("bookmark -l") 27: _ip.magic("cd c24") 28: _ip.magic("pwd ") 29: _ip.magic("logstart -o -r log-class-24.py") 30: _ip.system("ls -F ") 31: _ip.system("less log-class-24.py") 32: _ip.magic("dhist ") 34: _ip.magic("cd -2") 35: _ip.magic("cd -1") 36: _dh 37: _dh[1] 38: _ip.magic("alias ") 39: _ip.magic("alias rtupdate (cd /home/researchtools/projects/researchtools/; hg pull; hg update)") 40: _ip.system("(cd /home/researchtools/projects/researchtools/; hg pull; hg update) ") 41: _ip.magic("pwd ") 42: _ip.magic("cd c24") 43: _ip.magic("alias ") 45: _ip.magic("store rtupdate") 46: _ip.system("curl -O http://vislab-ccom.unh.edu/~schwehr/Classes/2011/esci895-researchtools/examples/21/sample.sbet.bz2") 47: _ip.system("curl -O http://vislab-ccom.unh.edu/~schwehr/Classes/2011/esci895-researchtools/examples/24/2010_202_S220_subsampled.sbet.bz2") 48: _ip.system("curl -O http://vislab-ccom.unh.edu/~schwehr/Classes/2011/esci895-researchtools/examples/24/2011_194_S250A_Stbd_subsampled.sbet.bz2") 50: _ip.system("ls -F -l") 51: _ip.system("bunzip2 sample.sbet.bz2") 52: _ip.system("bunzip2 2010_202_S220_subsampled.sbet.bz2") 53: _ip.system("bunzip2 2011_194_S250A_Stbd_subsampled.sbet.bz2") 54: _ip.system("ls -F -l") 55: _ip.magic("run sbet.py") 56: import glob 57: glob.glob('*.py') 58: glob.glob('*.sbet') 59: _ip.system("ls -F ") 60: glob.glob('2010*') 62: _ip.magic("run sbet.py") 63: import sys 64: sys.argv 65: print sys.argv 66: print sys.argv[1:] 68: _ip.magic("run sbet.py sample.sbet") 69: _ip.system("ls -F ") 70: _ip.magic("run sbet.py sample.sbet 2010_202_S220_subsampled.sbet") 71: import argparse 72: parser = argparse.ArgumentParser(description='Parse SBET files') 73: args = parser.parse_args() 74: args 75: parser.parse_args(['--help',] ) 76: _ip.magic("run sbet.py --help") 77: _ip.magic("run sbet.py") 80: _ip.magic("run sbet.py sample.sbet") 82: _ip.magic("run sbet.py sample.sbet 2011_194_S250A_Stbd_subsampled.sbet") 86: _ip.magic("pwd ") 87: _ip.magic("run sbet.py sample.sbet 2010_202_S220_subsampled.sbet 2011_194_S250A_Stbd_subsampled.sbet") 88: _ip.system("ls -F -l") 89: _ip.system("less 2011_194_S250A_Stbd_subsampled.sbet.kml") 90: import sys 91: sys.float_info.epsilon
ipython log
cd class ls ls -l pwd #[Out]# '/home/researchtools/projects' cd researchtools/class/ pwd #[Out]# '/home/researchtools/projects/researchtools/class' !hg pull !hg update ls -l > ~/after.txt !diff --unified ~/before.txt ~/after.txt mkdir ~/class/24 cd ~/class/24 bookmark c24 bookmark -l pwd #[Out]# '/home/researchtools/class/24' cd hgclass pwd #[Out]# '/home/researchtools/projects/researchtools' bookmark -l cd c24 cd hgclass cd class pwd #[Out]# '/home/researchtools/projects/researchtools/class' bookmark hgclass bookmark -l cd c24 pwd #[Out]# '/home/researchtools/class/24' ls less log-class-24.py dhist cd -2 cd -1 _dh _dh[1] #[Out]# '/home/researchtools/class/10' alias alias rtupdate (cd /home/researchtools/projects/researchtools/; hg pull; hg update) rtupdate pwd #[Out]# '/home/researchtools/class/10' cd c24 alias store rtupdate !curl -O http://vislab-ccom.unh.edu/~schwehr/Classes/2011/esci895-researchtools/examples/21/sample.sbet.bz2 !curl -O http://vislab-ccom.unh.edu/~schwehr/Classes/2011/esci895-researchtools/examples/24/2010_202_S220_subsampled.sbet.bz2 !curl -O http://vislab-ccom.unh.edu/~schwehr/Classes/2011/esci895-researchtools/examples/24/2011_194_S250A_Stbd_subsampled.sbet.bz2 ls -l !bunzip2 sample.sbet.bz2 !bunzip2 2010_202_S220_subsampled.sbet.bz2 !bunzip2 2011_194_S250A_Stbd_subsampled.sbet.bz2 ls -l run sbet.py import glob glob.glob('*.py') #[Out]# ['log-class-24.py', 'sbet.py'] glob.glob('*.sbet') #[Out]# ['2010_202_S220_subsampled.sbet', 'sample.sbet', '2011_194_S250A_Stbd_subsampled.sbet'] ls glob.glob('2010*') #[Out]# ['2010_202_S220_subsampled.sbet'] run sbet.py import sys sys.argv #[Out]# ['/usr/bin/ipython'] print sys.argv print sys.argv[1:] run sbet.py sample.sbet ls run sbet.py sample.sbet 2010_202_S220_subsampled.sbet import argparse parser = argparse.ArgumentParser(description='Parse SBET files') args = parser.parse_args() args #[Out]# Namespace() parser.parse_args(['--help',] ) run sbet.py --help run sbet.py run sbet.py sample.sbet run sbet.py sample.sbet 2011_194_S250A_Stbd_subsampled.sbet run sbet.py sample.sbet 2011_194_S250A_Stbd_subsampled.sbet pwd #[Out]# '/home/researchtools/class/24' run sbet.py sample.sbet 2010_202_S220_subsampled.sbet 2011_194_S250A_Stbd_subsampled.sbet ls -l less 2011_194_S250A_Stbd_subsampled.sbet.kml import sys sys.float_info.epsilon #[Out]# 2.220446049250313e-16 history 1 92
bash history
2000 ipython 2001 cd projects/researchtools/class/ 2002 ls 2003 mv 24-python-binary-files-part-4.org{,.old} 2004 ls 2005 fg 2006 cd ../.. 2007 rm -rf researchtools/ 2008 hg clone https://bitbucket.org/schwehr/researchtools 2009 ipython 2010 cd ~/class/24 2011 ls -l 2012 chmod +x sbet.py 2013 ls -l *.py 2014 ./sbet.py --help 2015 ./sbet.py sample.sbet 2016 ./sbet.py *.sbet 2017 fg 2018 history
final source
#!/usr/bin/env python '''Decode Applanix POSPac SBET IMU binary files''' import struct import math # Use the pprint function from the pprint module from pprint import pprint field_names = ('time', 'latitude', 'longitude', 'altitude', \ 'x_vel', 'y_vel', 'z_vel', \ 'roll', 'pitch', 'platform_heading', 'wander_angle', \ 'x_acceleration', 'y_acceleration', 'z_acceleration', \ 'x_angular_rate', 'y_angular_rate', 'z_angular') datagram_size = 136 # 8*17 bytes per datagram def num_datagrams(data): 'How many packets are in data' assert( len(data) % datagram_size == 0 ) return len(data) / datagram_size def get_offset(datagram_number): 'Calculate the starting offset of a datagram. First datagram is number 0' return datagram_number * datagram_size def decode(data, offset=0): 'Decipher a SBET datagram from binary' values = struct.unpack('17d',data[ offset + 0 : offset + 8*17]) sbet_values = dict(zip (field_names, values)) sbet_values['lat_deg'] = math.degrees(sbet_values['latitude']) sbet_values['lon_deg'] = math.degrees(sbet_values['longitude']) return sbet_values def load_sbet_file(filename): '''This is a GENERATOR that we can loop over with a for''' sbet_file = open(filename) sbet_data = sbet_file.read() for datagram_index in range( num_datagrams(sbet_data) ): offset = get_offset(datagram_index) datagram = decode(sbet_data, offset) datagram['index'] = datagram_index yield datagram def main(): import glob import sys print 'Starting main' import sys, argparse parser = argparse.ArgumentParser(description='Parse SBET files') parser.add_argument('filenames', type=str, nargs='+', help='SBET files') args = parser.parse_args() # uses sys.argv print 'filenames:', args.filenames for filename in args.filenames: print '====',filename,'====' out = open(filename+'.kml', 'w') out.write('''<?xml version="1.0" encoding="UTF-8"?> <kml xmlns="http://www.opengis.net/kml/2.2"> <Document> <Placemark> <name>{filename}</name> <LineString> <coordinates> '''.format(filename=filename) ) print 'Datagram Number, Time, x, y' for datagram_index, datagram in enumerate(load_sbet_file( filename )): if datagram_index % 20 == 0: print datagram_index, datagram['time'], datagram['lon_deg'], datagram['lat_deg'] out.write('{x},{y}\n'.format(x=datagram['lon_deg'], y=datagram['lat_deg'])) out.write('''\t\t\t\t</coordinates> \t\t\t</LineString> \t\t</Placemark> \t</Document> </kml> ''') if __name__ == '__main__': print 'starting to run script...' main() print 'script done!'
Date: <2011-11-22 Tue>
HTML generated by org-mode 7.4 in emacs 23