jphastings-PLW-Parse 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/plw.rb +151 -0
- metadata +53 -0
data/plw.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# A hacked up class for parsing the PLW data format from PicoTech (for their Oscilloscope data). Its not meant for speed but (constructive :P) criticism and ideas are welcome!
|
2
|
+
class PLW
|
3
|
+
attr_reader :signature,:version,:params,:PLS
|
4
|
+
attr_reader :sample_num,:no_samples,:max_samples,:interval,:trigger_sample,:triggered,:first_sample,:sample_length,:setting_byte,:start_date,:start_time,:min_time,:max_time,:notes,:current_time
|
5
|
+
attr_accessor :current_sample
|
6
|
+
|
7
|
+
# Will take a filename or a file handle (eg. <tt>"filename.plw"</tt> or <tt>open("filename.plw")</tt>)
|
8
|
+
def initialize(filename_or_handle)
|
9
|
+
if filename_or_handle.is_a?(File)
|
10
|
+
@file = filename_or_handle
|
11
|
+
elsif filename_or_handle.is_a?(String)
|
12
|
+
if File.exists? filename_or_handle
|
13
|
+
@file = open(filename_or_handle)
|
14
|
+
else
|
15
|
+
raise "File does not exist: #{filename_or_handle}"
|
16
|
+
end
|
17
|
+
else
|
18
|
+
raise "You need to pass a filename or a File object"
|
19
|
+
end
|
20
|
+
|
21
|
+
parseHeader
|
22
|
+
end
|
23
|
+
|
24
|
+
# Spins the file on to the byte position immediately after the given sample number
|
25
|
+
def current_sample=(sample_num)
|
26
|
+
if sample_num > @no_samples or sample_num < 0
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
# 4 bytes for each paramter, 4 for the time. This bundle sample_num times,
|
30
|
+
# plus the header length is the byte position of the end of that sample
|
31
|
+
position = (@no_params * 4 + 4) * sample_num + @header_length
|
32
|
+
|
33
|
+
@file.seek(position)
|
34
|
+
@current_sample = sample_num
|
35
|
+
end
|
36
|
+
|
37
|
+
# Reconstructs the PLS data file used when measuring this data. Parses into a multi-level hash.
|
38
|
+
def PLS
|
39
|
+
if @pls.nil?
|
40
|
+
wasat = @file.pos
|
41
|
+
self.current_sample = @no_samples
|
42
|
+
@pls = {}
|
43
|
+
section = nil
|
44
|
+
@file.read.split(/\r\n/).each do |line|
|
45
|
+
if line =~ /^\[(.+?)\]$/
|
46
|
+
section = {}
|
47
|
+
@pls[$1] = section
|
48
|
+
elsif line =~ /^(.+)=(.+)$/
|
49
|
+
section[$1] = $2
|
50
|
+
else
|
51
|
+
# Unknown Data format
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@file.seek(wasat)
|
55
|
+
@pls
|
56
|
+
else
|
57
|
+
@pls
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns <tt>n</tt> samples as Hashes, each hash containing:
|
62
|
+
# * :time : The time offset of this sample, in seconds
|
63
|
+
# * :data : The data for each channel as a hash
|
64
|
+
def getSamples(n=1)
|
65
|
+
if (@current_sample + n > @no_samples)
|
66
|
+
n = @no_samples - @current_sample
|
67
|
+
if n < 1
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Hash[*(@current_sample+1).upto(@current_sample += n).collect{ |sample_num| [sample_num,{:time => getuint32*@interval_units,:data => @no_params.times.collect { |param_index| getFloat() } }]}.flatten]
|
73
|
+
end
|
74
|
+
|
75
|
+
# A convenience method that just gives the next sample only
|
76
|
+
def getSample
|
77
|
+
getSamples(1).to_a[0][1]
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def getbytes(numbytes)
|
83
|
+
data = ""
|
84
|
+
numbytes.times do |time|
|
85
|
+
data<<@file.getbyte
|
86
|
+
end
|
87
|
+
data
|
88
|
+
end
|
89
|
+
|
90
|
+
def getFloat
|
91
|
+
getbytes(4).unpack("e4")[0]
|
92
|
+
end
|
93
|
+
|
94
|
+
def getuint16
|
95
|
+
getbytes(2).unpack("v")[0]
|
96
|
+
end
|
97
|
+
|
98
|
+
def getuint32
|
99
|
+
getbytes(4).unpack("V")[0]
|
100
|
+
end
|
101
|
+
|
102
|
+
def parseHeader
|
103
|
+
@header_length = getuint16()
|
104
|
+
@signature = getbytes(40).strip
|
105
|
+
@version = getuint32()
|
106
|
+
@no_params = getuint32()
|
107
|
+
@params = {}
|
108
|
+
@no_params.times do |param_index|
|
109
|
+
@params[param_index] = getuint16
|
110
|
+
end
|
111
|
+
getbytes(2*(250 - @no_params)) # The remaining params aren't used
|
112
|
+
@sample_num = getuint32() # Same as @no_samples unless wraparound occured
|
113
|
+
@no_samples = getuint32() + 415 # Why?! Its not reporting the right length for some reason...
|
114
|
+
@max_samples = getuint32()
|
115
|
+
@interval = getuint32()
|
116
|
+
case getuint16() # next 16 bits are the 'interval unit'
|
117
|
+
when 0 # Femtoseconds
|
118
|
+
@interval_units = 1e-15
|
119
|
+
when 1 # ASSUMED Picosecond
|
120
|
+
@interval_units = 1e-12
|
121
|
+
when 2 # ASSUMED Nanosecond
|
122
|
+
@interval_units = 1e-9
|
123
|
+
when 3 # ASSUMED Millisecond
|
124
|
+
@interval_units = 1e-6
|
125
|
+
when 4 # Milliseconds
|
126
|
+
@interval_units = 1e-3
|
127
|
+
when 5 # Seconds
|
128
|
+
# We're in seconds
|
129
|
+
when 6 # Minutes
|
130
|
+
@interval_units = 60
|
131
|
+
when 7
|
132
|
+
@interval_units = 3600
|
133
|
+
else
|
134
|
+
raise "The units weren't specified correctly in the header file"
|
135
|
+
end
|
136
|
+
@interval *= @interval_units
|
137
|
+
@trigger_sample = getuint32()
|
138
|
+
@triggered = getuint16()
|
139
|
+
@first_sample = getuint32()
|
140
|
+
@sample_length = getuint32()
|
141
|
+
@setting_byte = getuint32()
|
142
|
+
@start_date = getuint32()
|
143
|
+
@start_time = getuint32()
|
144
|
+
@min_time = getuint32()
|
145
|
+
@max_time = getuint32()
|
146
|
+
@notes = getbytes(1000)
|
147
|
+
@current_time = getuint32()
|
148
|
+
getbytes(78) # Spare
|
149
|
+
@current_sample = 0 # We're currently after sample number 0. ie. the first sample is sample '1'
|
150
|
+
end
|
151
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jphastings-PLW-Parse
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- JP Hastings-Spital
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-11 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: contact@projects.kedakai.co.uk
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- plw.rb
|
26
|
+
has_rdoc: true
|
27
|
+
homepage: http://wiki.github.com/jphastings/plw-parse
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
|
31
|
+
require_paths:
|
32
|
+
- .
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: "0"
|
38
|
+
version:
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
requirements: []
|
46
|
+
|
47
|
+
rubyforge_project:
|
48
|
+
rubygems_version: 1.2.0
|
49
|
+
signing_key:
|
50
|
+
specification_version: 2
|
51
|
+
summary: ""
|
52
|
+
test_files: []
|
53
|
+
|