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.
Files changed (2) hide show
  1. data/plw.rb +151 -0
  2. 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
+