Skip to content

Commit

Permalink
Merge branch 'py'.
Browse files Browse the repository at this point in the history
- Python support for GsfAttribute record
- Create a gsf_priv.h for testing of private functions.  Disabled static to expose time functions
- Python tests for GsfComment record
  • Loading branch information
schwehr committed Jul 28, 2015
2 parents 023839e + 8597a51 commit 2271020
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 27 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
*.a
.DS_Store
*.dSYM
*.pyc
4 changes: 4 additions & 0 deletions doc/GSF_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -1428,6 +1428,10 @@ measurement. The number of measurements is variable and user-definable, but any
attitude record should contain no more than sixty seconds worth of measurements. Table
4-14 defines the format of an Attitude record.

TODO(schwehr): What are the units on the values in this table? gsf.h has the decoded units, but what is supposed to be over the network or in a file.

TODO(schwehr): On disk, GSF has records of time offest, pitch, roll, heave and heading, but scaled. Not like in table 4-14.

**Table 4-14** Attitude Record Definition.

Field Name | Description | Field Type | Count
Expand Down
66 changes: 59 additions & 7 deletions gsf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,48 @@ def GsfHeader(data):
}


def GsfAttitude(data):
sec = struct.unpack('>I', data[:4])[0]
nsec = struct.unpack('>I', data[4:8])[0]
base_time = datetime.datetime.utcfromtimestamp(sec + 1e-9 * nsec)
num_measurements = struct.unpack('>h', data[8:10])[0]

result = {
'record_type': GSF_ATTITUDE,
'sec': sec,
'nsec': nsec,
'datetime': base_time,
'times': [],
'pitches': [],
'rolls': [],
'heaves': [],
'headings': [],
}

if not num_measurements:
return result

base = 10
# 5 2-byte values.
record_size = 10
for rec_num in range(num_measurements):
start = base + rec_num * record_size
end = base + (rec_num + 1) * record_size

fields = struct.unpack('>4hH', data[start:end])
time_raw, pitch_raw, roll_raw, heave_raw, heading_raw = fields
offset = time_raw / 1000.0
time = base_time + datetime.timedelta(seconds=offset)

result['times'].append(time)
result['pitches'].append(pitch_raw / 100.0)
result['rolls'].append(roll_raw / 100.0)
result['heaves'].append(heave_raw / 100.0)
result['headings'].append(heading_raw / 100.0)

return result


def GsfComment(data):
"""Decode a GSF Comment record from binary data.
Expand Down Expand Up @@ -113,17 +155,26 @@ def GsfHistory(data):

operator_size = struct.unpack('>h', data[base:base+2])[0]
base += 2
operator = data[base:base + operator_size].rstrip('\0')
base += operator_size
if operator_size:
operator = data[base:base + operator_size].rstrip('\0')
base += operator_size
else:
operator = ''

command_size = struct.unpack('>h', data[base:base+2])[0]
base += 2
command = data[base:base + command_size].rstrip('\0')
base += command_size
if command_size:
command = data[base:base + command_size].rstrip('\0')
base += command_size
else:
command = ''

comment_size = struct.unpack('>h', data[base:base+2])[0]
base += 2
comment = data[base:base + comment_size].rstrip('\0')
if comment_size:
comment = data[base:base + comment_size].rstrip('\0')
else:
comment = ''

return {
'record_type': GSF_HISTORY,
Expand Down Expand Up @@ -170,9 +221,9 @@ def __next__(self):
checksum = None
header_data = record_header_text
if have_checksum:
header_data += checksum_text
checksum_text = self.gsf_file.src.read(4)
checksum = struct.unpack('>I', checksum_text)
header_data += checksum_text

data = self.gsf_file.src.read(data_size)

Expand All @@ -187,6 +238,7 @@ def __next__(self):
'data': data
}

# TODO(schwehr): Wrap in try, except and handle malformed records.
if record_type == GSF_HEADER:
record.update(GsfHeader(data))
elif record_type == GSF_SWATH_BATHYMETRY_PING:
Expand All @@ -210,7 +262,7 @@ def __next__(self):
elif record_type == GSF_HV_NAVIGATION_ERROR:
pass
elif record_type == GSF_ATTITUDE:
pass
record.update(GsfAttitude(data))
else:
raise Error("Unknown record_type: %d" % record_type)

Expand Down
4 changes: 3 additions & 1 deletion gsf/dump_hex.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ def DumpHex(filename, include_cpp=True):
header_hex = [Hex2(v) for v in header_data]
data_hex = [Hex2(v) for v in data]
print 'record: ', record_num, type_str
print 'sizes = (%d, %d, %d)' % (record['size_total'], len(header_hex), len(data_hex))
print 'sizes = (%d, %d, %d)' % (record['size_total'],
len(header_hex),
len(data_hex))
print 'header = (', ', '.join(header_hex), ')'
print 'data = (', ', '.join(data_hex), ')'

Expand Down
4 changes: 3 additions & 1 deletion src/gsf/gsf_dec.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "gsf.h"
#include "gsf_dec.h"
#include "gsf_enc.h"
#include "gsf_priv.h"

#if defined(__GNUC__) && __GNUC__ >= 4
# define UNUSED __attribute((__unused__))
Expand Down Expand Up @@ -7540,7 +7541,8 @@ gsfDecodeHVNavigationError(gsfHVNavigationError *hv_nav_error, GSF_FILE_TABLE *f
return (p - sptr);
}

static void LocalAddTimes (struct timespec *base_time, double delta_time, struct timespec *sum_time)
/* static */
void LocalAddTimes (const struct timespec *base_time, double delta_time, struct timespec *sum_time)
{
double fraction = 0.0;
double tmp = 0.0;
Expand Down
4 changes: 3 additions & 1 deletion src/gsf/gsf_enc.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

#include "gsf.h"
#include "gsf_enc.h"
#include "gsf_priv.h"

#if defined(__GNUC__) && __GNUC__ >= 4
# define UNUSED __attribute((__unused__))
Expand Down Expand Up @@ -7888,7 +7889,8 @@ gsfEncodeHVNavigationError(unsigned char *sptr, gsfHVNavigationError *hv_nav_err
return (p - sptr);
}

static void LocalSubtractTimes (struct timespec *base_time, struct timespec *subtrahend, double *difference)
/* static */
void LocalSubtractTimes (const struct timespec *base_time, const struct timespec *subtrahend, double *difference)
{
double fraction = 0.0;
double seconds = 0.0;
Expand Down
45 changes: 45 additions & 0 deletions src/gsf/gsf_priv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/********************************************************************
*
* Description : Internal only functions of GSF.
*
* The functions in this header are only meant for internal use.
* for testing and iternal linking. Do not include this header in
* your code.
*
* Restrictions/Limitations :
*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
*
********************************************************************/

#ifndef __GSF_PRIV_H__
#define __GSF_PRIV_H__

#ifdef __cplusplus
extern "C"
{
#endif

void LocalAddTimes(const struct timespec *base_time,
double delta_time,
struct timespec *sum_time);
void LocalSubtractTimes(const struct timespec *base_time,
const struct timespec *subtrahend,
double *difference);

#ifdef __cplusplus
}
#endif

#endif /* __GSF_PRIV_H__ */
108 changes: 108 additions & 0 deletions test_py/records_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,106 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import datetime
import unittest

import gsf


class AttitudeTest(unittest.TestCase):

def testEmptyAttitude(self):
data = (
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00)
attitude = gsf.GsfAttitude(''.join(chr(v) for v in data))
self.assertEqual(attitude['record_type'], gsf.GSF_ATTITUDE)
self.assertEqual(attitude['sec'], 3)
self.assertEqual(attitude['nsec'], 4)

def testAttitudeLengthOneZeroValues(self):
data = (
0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)

attitude = gsf.GsfAttitude(''.join(chr(v) for v in data))
self.assertEqual(attitude['record_type'], gsf.GSF_ATTITUDE)
self.assertEqual(attitude['sec'], 5)
self.assertEqual(attitude['nsec'], 6)
self.assertEqual(attitude['times'],
[datetime.datetime(1970, 1, 1, 0, 0, 5)])
self.assertEqual(attitude['pitches'], [0.0])
self.assertEqual(attitude['rolls'], [0.0])
self.assertEqual(attitude['heaves'], [0.0])
self.assertEqual(attitude['headings'], [0.0])

def testAttitudeLengthOneNonZero(self):
data = (
0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x01, 0x00,
0x00, 0xFF, 0x88, 0xFE, 0xAC, 0xFD, 0xD0, 0x03, 0x0C)

attitude = gsf.GsfAttitude(''.join(chr(v) for v in data))
self.assertEqual(attitude['record_type'], gsf.GSF_ATTITUDE)
self.assertEqual(attitude['sec'], 9)
self.assertEqual(attitude['nsec'], 10)
self.assertEqual(attitude['times'],
[datetime.datetime(1970, 1, 1, 0, 0, 9)])
self.assertEqual(attitude['pitches'], [-1.2])
self.assertEqual(attitude['rolls'], [-3.4])
self.assertEqual(attitude['heaves'], [-5.6])
self.assertEqual(attitude['headings'], [7.8])

def testAttitudeLengthTwo(self):
data = (
0x55, 0xB6, 0x65, 0x36, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00,
0x00, 0x00, 0x78, 0xD8, 0xE6, 0xB1, 0xCC, 0x46, 0xB4, 0x04, 0x42,
0xFE, 0xAC, 0x27, 0x24, 0xB1, 0xC2, 0x8C, 0x3C, 0x00, 0x00)

attitude = gsf.GsfAttitude(''.join(chr(v) for v in data))
self.assertEqual(attitude['record_type'], gsf.GSF_ATTITUDE)
self.assertEqual(attitude['sec'], 1438016822)
self.assertEqual(attitude['nsec'], 1)
self.assertEqual(attitude['times'],
[datetime.datetime(2015, 7, 27, 17, 7, 2),
datetime.datetime(2015, 7, 27, 17, 7, 3, 90000)]),
self.assertEqual(attitude['pitches'], [1.2, -3.4])
self.assertEqual(attitude['rolls'], [-100.1, 100.2])
self.assertEqual(attitude['heaves'], [-200.2, -200.3])
self.assertEqual(attitude['headings'], [181, 359])


class CommentTest(unittest.TestCase):

def testEmptyComment(self):
data = (
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00)
comment = gsf.GsfComment(''.join(chr(v) for v in data))
self.assertEqual(comment['record_type'], gsf.GSF_COMMENT)
self.assertEqual(comment['sec'], 1)
self.assertEqual(comment['nsec'], 2)
self.assertEqual(comment['comment'], '')

def test100aComment(self):
data = (
0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x64, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61)
comment = gsf.GsfComment(''.join(chr(v) for v in data))
self.assertEqual(comment['record_type'], gsf.GSF_COMMENT)
self.assertEqual(comment['sec'], 8)
self.assertEqual(comment['nsec'], 9)
self.assertEqual(comment['comment'], 'a'*100)


class HistoryTest(unittest.TestCase):

def testAnything(self):
Expand All @@ -38,6 +133,19 @@ def testAnything(self):
self.assertEqual(history['command'], 'command line')
self.assertEqual(history['comment'], 'comment')

def testAllFieldsEmpty(self):
data = (
0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00,
0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00)

history = gsf.GsfHistory(''.join(chr(v) for v in data))
self.assertEqual(history['record_type'], gsf.GSF_HISTORY)
self.assertEqual(history['sec'], 5)
self.assertEqual(history['nsec'], 6)
self.assertEqual(history['name'], '')
self.assertEqual(history['operator'], '')
self.assertEqual(history['command'], '')
self.assertEqual(history['comment'], '')

if __name__ == '__main__':
unittest.main()
6 changes: 6 additions & 0 deletions tests/gsf/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ include ../../Makefile.inc
TESTS := index_test
TESTS += read_gsf_2_9_test
TESTS += read_gsf_3_6_test
TESTS += time_test
TESTS += write_test

all: test
Expand Down Expand Up @@ -100,9 +101,14 @@ read_gsf_2_9_test: read_gsf_2_9_test.o gtest_main.a libgsftest.a
read_gsf_3_6_test: read_gsf_3_6_test.o gtest_main.a libgsftest.a
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@ ../../src/gsf/libgsf.a

# TODO(schwehr): Why does using libgsf.a not work?
time_test: time_test.o gtest_main.a libgsftest.a
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@ ../../src/gsf/gsf_enc.o ../../src/gsf/gsf_dec.o ../../src/gsf/gsf.o ../../src/gsf/gsf_indx.o

write_test: write_test.o gtest_main.a libgsftest.a
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@ ../../src/gsf/libgsf.a

# TODO(schwehr): Move out of testing.
gsf_info: gsf_info.o
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $^ -o $@ ../../src/gsf/libgsf.a

Expand Down
Loading

0 comments on commit 2271020

Please sign in to comment.