UP | HOME

Class 22: Python - parsing binary data 2 - SBET IMU data

Table of Contents

Announcements

Google Oceans

I will be at CCOM/JHC through the end of the year.

NSF Sample and Data Policy

National Science Foundation's Division of Ocean Sciences Sample and Data Policy:

http://www.nsf.gov/pubs/2011/nsf11060/nsf11060.pdf

See Also

Public Mercurial (hg) repository    hg vc dvcs

Mercurial is a distributed version control system (DVCS). It is very similar to git. Subversion (svn) is a more traditional version control system.

https://bitbucket.org/schwehr/researchtools

mkdir projects
cd projects
sudo apt-get install mercurial # hg
hg clone https://bitbucket.org/schwehr/researchtools
tree researchtools

If you are inside of CCOM, you can read more about Mercurial on the internal wiki:

http://wiki.ccom.unh.edu/index.php/Mercurial

Dolly the cloned sheep:

http://en.wikipedia.org/wiki/Dolly_(sheep)

Setup

Open emacs

Open a terminal and get going:

# update your mercurial repository of the class notes
cd ~/projects/researchtools
hg pull

mkdir -p ~/class/22
cd ~/class/22

pwd
# Make sure you are in the right location
# ~/class/22

# Rather than downloading the org file from a web browser
# or using wget/curl, you can now get it from the researchtools
# mercurial revision control repository
cp ~/projects/researchtools/class/22-python-binary-files-part-2.org .

curl -O http://vislab-ccom.unh.edu/~schwehr/Classes/2011/esci895-researchtools/examples/21/sample.sbet.bz2
bunzip2 sample.sbet.bz2

md5sum sample.sbet 
196c21f16f07ceae180888b12e9edc56  sample.sbet

Start ipython in the terminal

logstart -o -r log-class-22.py

import struct
import math

sbet_file = open('sample.sbet')
sbet_data = sbet_file.read()

whos

Functions and arguments

See also:

Since functions are a bit tricky, we should go over them again. You start a function with "def" followed by the name of a function, "()" and a ":" to finish the function.

Open the file ~/class/22/sonar.py and put this in it:

def setfrequency():
    print 'Setting frequency'
    # Write code here to set the frequency

Be sure to save the file. That means that the bottom left of your emacs window should have a status of "-U:—" with no "**"

Now in ipython, run it to see what happens:

run sonar

You get nothing, because we have not called the function. So add a function call to your code that uses setfrequency:

# "Define" or create the function
def setfrequency():
    print 'Setting frequency'
    # Write code here to set the frequency

# Use the function
setfrequency()

Run it again and you should see this, but the command number [16] will be different for you:

In [16]: run sonar
Setting frequency

But if we want to set a specific frequency for the sonar, we must be able to pass in the frequency as an "argument" or "parameter" to the function. That is done by putting a name inside the "()". The argument name is only used inside the function. It has no meaning outside of the function.

Change the "def" line and the print right after it to have a parameter called "freq"

# "Define" or create the function
def setfrequency(freq):
    print 'Setting frequency to', freq, 'Hz'
    # Write code here to set the frequency

# Use the function
setfrequency()

Now if we run the code, we get an error!

In [17]: run sonar
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/researchtools/class/test-22/sonar.py in <module>()
      6 
      7 # Use the function

----> 8 setfrequency()
      9 
     10 

TypeError: setfrequency() takes exactly 1 argument (0 given)
WARNING: Failure executing file: <sonar.py>

By calling "setfrequency()", we did not pass in a value for freq. Oops!

For arguments, we can assign a "default" value that will be used if nothing is passed in when calling (aka using) the function. You don't have to do this, but it is often a smart thing to do. Why don't we make the default frequency be 12kHz (12000 cycles per second). Change the def line to look like this:

def setfrequency(freq = 12000):

Now when you run the function, you will see this:

In [18]: run sonar
Setting frequency to 12000

So what do we do now if we want to change it to a different frequency? We need to call setfrequency with a different number. Let's double the frequency to 24kHz. Change the call to look like this:

setfrequency(24000)

Running the sonar.py code in ipython looks like this:

In [19]: run sonar
Setting frequency to 24000

What if we want to have a variable that has the frequency? Try making a variable called my_sonar_freq and set it to 15kHz:

# "Define" or create the function
def setfrequency(freq = 12000):
    print 'Setting frequency to', freq
    # Write code here to set the frequency

my_sonar_freq = 15000

# Use the function
setfrequency( my_sonar_freq )

Now run it:

run sonar
Setting frequency to 15000

You can even have a lookup table using a dictionary.

# "Define" or create the function
def setfrequency(freq = 12000):
    print 'Setting frequency to', freq
    # Write code here to set the frequency

sonar_freq_table = {
    'em122': 12000,
    'knudsen': 3500,
    }

setfrequency( sonar_freq_table['knudsen'] )
In [21]: run sonar
Setting frequency to 3500

Hopefully that gives you a better field for functions! Now we will get back to creating our sbet.py module with functions to handle reading IMU navigation data.

Last time, where were we?

Last time we were editing ~/class/22/sbet.py. Here is a cleaned up version of where we left off. I have removed the extra print statements.

# Decode Applanix POSPac SBET IMU binary files

def decode(data):
    'Decipher a SBET datagram from binary'
    print 'Data length:', len(data)

def main():
    print 'Starting main'
    sbet_file = open('sample.sbet')
    sbet_data = sbet_file.read()

    print 'Read this many bytes:',len(sbet_data)

    decode(sbet_data)

    print 'Finishing main'

Open ~/class/22/sbet.py and put the above code into the file.

Writing a decode method for an sbet data record

Get started on working with your sbet file in ipython

import sbet
sbet.decode()

# remember that after you have done an import, you must use this to
# get updates
reload sbet

Try out the decode function. If you run whos, you will see we have an sbetdata variable in our workspace.

sbet.decode(sbet_data)
# Data length: 22712

We can also call our main function:

sbet.main()
# Starting main
# Read this many bytes: 22712
# Data length: 22712
# Finishing main

It is time improve decode to start pulling apart the SBET data into values that we can use. Add imports for math and struct. Then change decode to unpack the 17 doubles in a SBET report that we discussed in class 21.

The values variable will be a list of 17 values

import math
import struct

def decode(data):
    "Decipher a SBET datagram from binary"
    print "Start decoding datagram"
    values = struct.unpack('17d',data[0:8*17])

    print 'type of values:', type(values)
    print 'contents of values:', values

    time = values[0]

    latitude = values[1]
    lat_deg = math.degrees(latitude)

    longitude = values[2]
    lon_deg = math.degrees(longitude)

    print 'results:', time, lat_deg, lon_deg

We also would like to make this a run-able script from within ipython, so add this to the end of sbet.py:

if __name__=='__main__':
    print 'starting to run script...'
    main()
    print 'script done!'

And add this as the very first line of sbet.py to make it run-able from the bash shell:

#!/usr/bin/env python

To complete making sbet.py work from the bash prompt, you need to set the file as executable with chmod. Remember that "!" tells ipython that we want to run a shell command:

!chmod +x sbet.py

ls -l sbet.py
# -rwxr-xr-x 1 researchtools researchtools 739 2011-11-15 08:00 sbet.py*

# try running it!  There are now two ways

# Using bash from inside of ipython:
!sbet.py
# sh: sbet.py: not found

# Oops!  We need to tell bash where the program is located 
!./sbet.py

# It should print out quite a bit here

# Or you can run it directly from ipython
run sbet

Which will look like this:

In [29]: run sbet
starting to run script...
Starting main
Read this many bytes: 22712
Start decoding datagram
type of values: <type 'tuple'>
contents of values: (334959.0048233234, 1.0549522638507869,
-2.559965741819528, 12.826300557342815, 10.437825046453915,
0.998228318178983, 0.18282804536664027, -0.0026283394812042344,
0.11416603057936824, -0.09985686530029529, -0.40154673926674145,
-0.8249097558096672, -0.3413483211034812, 0.07018300645653144,
0.021320176833628756, 0.029000032024608147, -0.006807197876212325) 
results: 334959.004823 60.4443123064 -146.675232704
Finishing main
script done!

Rather than working with the values list and having to know the number position of each variable, we should create a dictionary like we did last time. Add field_names to the sbet.py file and replace the decode function with this decode

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')

def decode(data):
    "Decipher a SBET datagram from binary"
    values = struct.unpack('17d',data[0:8*17])

    # Create a dictionary for all the values
    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'])

    print 'results:'
    for key in sbet_values:
        print '    ', key, sbet_values[key]

The results of running it should look like this:

In [30]: run sbet
starting to run script...
Starting main
Read this many bytes: 22712
results:
     x_acceleration -0.82490975581
     x_angular_rate 0.0213201768336
     platform_heading -0.0998568653003
     y_angular_rate 0.0290000320246
     pitch 0.114166030579
     altitude 12.8263005573
     z_vel 0.182828045367
     lat_deg 60.4443123064
     longitude -2.55996574182
     roll -0.0026283394812
     y_vel 0.998228318179
     y_acceleration -0.341348321103
     time 334959.004823
     latitude 1.05495226385
     lon_deg -146.675232704
     z_acceleration 0.0701830064565
     z_angular -0.00680719787621
     x_vel 10.4378250465
     wander_angle -0.401546739267
Finishing main
script done!

Rather than printing from inside of the sbet decode function, we should return the dictionary to main.

First add this to the imports at the top of the sbet.py file. pprint stands for "pretty print".

from pprint import pprint

Now replace the print and for loop at the end of decode so that decode looks like this with just a return sbet_values.

def decode(data):
    "Decipher a SBET datagram from binary"
    values = struct.unpack('17d',data[0:8*17])

    # Create a dictionary for all the values
    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  # Send the sbet_values dictionary back to the caller

Now the main function needs to handle the printing of the dictionary.

def main():
    sbet_file = open('sample.sbet')
    sbet_data = sbet_file.read()

    datagram = decode(sbet_data)
    pprint(datagram)  # Note that we are using pretty print, not print

You should see:

In [34]: run sbet.py
starting to run script...
Starting main
Read this many bytes: 22712
{'altitude': 12.826300557342815,
 'lat_deg': 60.444312306421736,
 'latitude': 1.0549522638507869,
 'lon_deg': -146.6752327043359,
 'longitude': -2.559965741819528,
 'pitch': 0.11416603057936824,
 'platform_heading': -0.09985686530029529,
 'roll': -0.0026283394812042344,
 'time': 334959.0048233234,
 'wander_angle': -0.40154673926674145,
 'x_acceleration': -0.8249097558096672,
 'x_angular_rate': 0.021320176833628756,
 'x_vel': 10.437825046453915,
 'y_acceleration': -0.3413483211034812,
 'y_angular_rate': 0.029000032024608147,
 'y_vel': 0.998228318178983,
 'z_acceleration': 0.07018300645653144,
 'z_angular': -0.006807197876212325,
 'z_vel': 0.18282804536664027}
Finishing main
script done!

History

1: ip.magic("logstart -o -r log-class-22.py") 2 : ip.system("ls -F -l") 3 : import struct 4 : import numpy 5 : import math 6 : sbetfile = open('sample.sbet') 7 : sbetdata = sbetfile.read() 8 : ip.magic("history ") 9 : ip.magic("whos ") 10: ip.system("ls -F ") 11: ip.magic("run sonar") 12: ip.magic("run sonar") 13: ip.magic("run sonar") 14: ip.magic("run sonar") 15: ip.magic("run sonar") 16: ip.magic("run sonar") 17: ip.magic("run sonar") 18: ip.magic("run sonar") 19: import sbet 20: del sbet 21: ip.magic("whos ") 22: import sbet 23: sbet.decode() 24: sbet.decode(sbetdata) 25: reload(sbet) 26: sbet.main() 27: 8*17 28: reload(sbet) 29: sbet.main() 30: ip.system("ls -F -l") 31: ip.system("chmod +x sbet.py") 32: ip.system("ls -F -l") 33: ip.system("sbet.py") 34: ip.system("./sbet.py") 35: ip.magic("run sbet.py") 36: ip.magic("run sbet") 37: ip.magic("whos ") 38: from pprint import pprint 39: pprint(sonarfreqtable) 40: ip.magic("run sbet") 41: ip.magic("history ")

ls -l
import struct
import numpy
import math
sbet_file = open('sample.sbet')
sbet_data = sbet_file.read()
history
whos
ls
run sonar
# ... more run sonar commands ...
run sonar
import sbet
del sbet
whos
import sbet
sbet.decode()
sbet.decode(sbet_data)
reload sbet
#[Out]# <module 'sbet' from 'sbet.pyc'>
sbet.main()
8*17
#[Out]# 136
reload sbet
#[Out]# <module 'sbet' from 'sbet.py'>
sbet.main()
ls -l
!chmod +x sbet.py
ls -l
!sbet.py
!./sbet.py
run sbet.py
run sbet
whos
from pprint import pprint
pprint(sonar_freq_table)
run sbet
history

sonar.py:

# "Define" or create the fuction
def setfrequency(freq = 12000, name='unknown'):
    print 'Setting frequency', freq, 'Hz', 'name is', name
    # Write code here to set the frequency

sonar_freq_table = {
    'em122': 12000,
    'knudsen': 3500,
    }

setfrequency( sonar_freq_table['knudsen'], 'R/V Super Slow' )

sbet.py:

#!/usr/bin/env python

# Decode Applanix POSPac SBET IMU binary files

import struct
import math

# This says, import the pprint function from the pprint module
# Yes, this is very confusing
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')

def decode(data):
    'Decipher a SBET datagram from binary'
    print 'Data length:', len(data)
    values = struct.unpack('17d',data[0: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 main():
    print 'Starting main'
    sbet_file = open('sample.sbet')
    sbet_data = sbet_file.read()

    print 'Read this many bytes:', len(sbet_data)

    datagram = decode(sbet_data)
    pprint(datagram)

    print 'Finish main'

if __name__ == '__main__':
    print 'starting to run script...'
    main()
    print 'script done!'

Author: Kurt Schwehr

Date: <2011-11-15 Tue>

HTML generated by org-mode 7.4 in emacs 23