jphastings-PLW-Parse 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|