ruby_tdms 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +7 -0
  4. data/CODE_OF_CONDUCT.md +13 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +31 -0
  7. data/LICENSE.txt +26 -0
  8. data/README.md +28 -0
  9. data/Rakefile +10 -0
  10. data/demo.rb +14 -0
  11. data/doc/data_types.txt +23 -0
  12. data/doc/example_disasm.txt +47 -0
  13. data/doc/tdms_format.txt +101 -0
  14. data/doc/usage.txt +48 -0
  15. data/lib/ruby_tdms.rb +2 -0
  16. data/lib/ruby_tdms/aggregate_channel.rb +53 -0
  17. data/lib/ruby_tdms/aggregate_channel_enumerator.rb +50 -0
  18. data/lib/ruby_tdms/channel_enumerator.rb +33 -0
  19. data/lib/ruby_tdms/data_types.rb +22 -0
  20. data/lib/ruby_tdms/data_types/base.rb +19 -0
  21. data/lib/ruby_tdms/data_types/boolean.rb +15 -0
  22. data/lib/ruby_tdms/data_types/double.rb +19 -0
  23. data/lib/ruby_tdms/data_types/double_with_unit.rb +19 -0
  24. data/lib/ruby_tdms/data_types/int16.rb +19 -0
  25. data/lib/ruby_tdms/data_types/int32.rb +19 -0
  26. data/lib/ruby_tdms/data_types/int64.rb +19 -0
  27. data/lib/ruby_tdms/data_types/int8.rb +15 -0
  28. data/lib/ruby_tdms/data_types/single.rb +19 -0
  29. data/lib/ruby_tdms/data_types/single_with_unit.rb +19 -0
  30. data/lib/ruby_tdms/data_types/string.rb +15 -0
  31. data/lib/ruby_tdms/data_types/timestamp.rb +15 -0
  32. data/lib/ruby_tdms/data_types/u_int16.rb +19 -0
  33. data/lib/ruby_tdms/data_types/u_int32.rb +19 -0
  34. data/lib/ruby_tdms/data_types/u_int64.rb +19 -0
  35. data/lib/ruby_tdms/data_types/u_int8.rb +15 -0
  36. data/lib/ruby_tdms/document.rb +105 -0
  37. data/lib/ruby_tdms/file.rb +13 -0
  38. data/lib/ruby_tdms/object_parser.rb +47 -0
  39. data/lib/ruby_tdms/objects/base.rb +47 -0
  40. data/lib/ruby_tdms/objects/channel.rb +105 -0
  41. data/lib/ruby_tdms/objects/file.rb +11 -0
  42. data/lib/ruby_tdms/objects/group.rb +22 -0
  43. data/lib/ruby_tdms/path.rb +97 -0
  44. data/lib/ruby_tdms/property.rb +16 -0
  45. data/lib/ruby_tdms/segment.rb +107 -0
  46. data/lib/ruby_tdms/streaming.rb +124 -0
  47. data/lib/ruby_tdms/string_channel_enumerator.rb +49 -0
  48. data/lib/ruby_tdms/version.rb +3 -0
  49. data/ruby_tdms.gemspec +38 -0
  50. metadata +185 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a8c2010fd87e23f9d15a5dd3a2698eaa8fd6a9e1
4
+ data.tar.gz: a215da2e96b0594c1de8e49c68cd10ef65c8489d
5
+ SHA512:
6
+ metadata.gz: 5d3e0a5d0cc9036cacfbd7300134dc134b4ae4fa04b3f89192bb0dd42b6e47aeea3cf698edf451bf9b7e6e87dc4ea0cee501d136225e4d972a340d28e903e361
7
+ data.tar.gz: 00fd1b34967d4ef65ce032ce3c5c03d97ad05f5cfe498bb0d01d792d6494bb9f44bdcbf76f4fccac423dd6ffbeca889b8aab4c8a60e4bf74b51a27cab88c1219
@@ -0,0 +1 @@
1
+ /.idea/
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ sudo: false
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1
7
+ - 2.2.2
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tdms.gemspec
4
+ gemspec
@@ -0,0 +1,31 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ruby_tdms (2.0.0)
5
+ coalesce
6
+ require_all
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ coalesce (0.0.1)
12
+ docile (1.1.5)
13
+ minitest (5.5.1)
14
+ multi_json (1.11.0)
15
+ rake (10.3.2)
16
+ require_all (1.4.0)
17
+ simplecov (0.9.2)
18
+ docile (~> 1.1.0)
19
+ multi_json (~> 1.0)
20
+ simplecov-html (~> 0.9.0)
21
+ simplecov-html (0.9.0)
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ bundler (>= 1.7)
28
+ minitest
29
+ rake (~> 10.0)
30
+ ruby_tdms!
31
+ simplecov
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2012, Mike Naberezny.
2
+ Copyright (c) 2015-2017, Aaron Ten Clay <aarontc@aarontc.com>.
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+ * Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+ * Neither the name of Maintainable Software, LLC. nor the names of its
14
+ contributors may be used to endorse or promote products derived from
15
+ this software without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,28 @@
1
+ # RubyTDMS
2
+
3
+ (Note: The RubyTDMS gem was formerly known as simply "TDMS". The API did not change from TDMS 1.0.0
4
+ to RubyTDMS 2.0.0, but the version was bumped due to the breaking name change.)
5
+
6
+ TDMS is a binary file format for measurement data. It was created
7
+ by National Instruments.
8
+
9
+ - [NI TDMS File Format](http://zone.ni.com/devzone/cda/tut/p/id/3727)
10
+ - [TDMS File Format Internal Structure](http://zone.ni.com/devzone/cda/tut/p/id/5696)
11
+
12
+ National Instruments software such as LabVIEW, DIAdem, and Measurement
13
+ Studio support reading and writing TDMS files. NI also provides a DLL
14
+ written in C for using TDMS files on Windows.
15
+
16
+ RubyTDMS provides a convenient way to work with
17
+ TDMS files on Unix-like platforms.
18
+
19
+ ## Current State
20
+
21
+ This gem is nearly feature-complete for for reading the TDMS files according to the
22
+ 2015 specification from National Instruments.
23
+
24
+ - Writing TDMS files is not yet supported.
25
+
26
+ ## Contributors
27
+
28
+ This gem is heavily based on the [TDMS library](http://github.com/mnaberez/tdms) created by Mike Naberezny.
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/demo.rb ADDED
@@ -0,0 +1,14 @@
1
+ require_relative 'lib/ruby_tdms'
2
+
3
+ filename = File.dirname(__FILE__) + '/test/fixtures/example.tdms'
4
+ doc = RubyTDMS::File.parse(filename)
5
+
6
+ ch1 = doc.channels.find { |c| c.name == 'StatisticsText' }
7
+ ch2 = doc.channels.find { |c| c.name == 'Res_Noise_1' }
8
+
9
+ last = [ch1.values.size, ch2.values.size].min - 1
10
+
11
+ puts "#{ch1.name},#{ch2.name}"
12
+ 0.upto(last) do |i|
13
+ puts "#{ch1.values[i]},#{ch2.values[i]}"
14
+ end
@@ -0,0 +1,23 @@
1
+ Data Types
2
+
3
+ Identifier Name Length (bytes) Ruby
4
+
5
+ 0x00000000 tdsTypeVoid 1 Nil
6
+ 0x00000001 tdsTypeI8 1 Integer
7
+ 0x00000002 tdsTypeI16 2 Integer
8
+ 0x00000003 tdsTypeI32 4 Integer
9
+ 0x00000004 tdsTypeI64 8 Integer
10
+ 0x00000005 tdsTypeU8 1 Integer
11
+ 0x00000006 tdsTypeU16 2 Integer
12
+ 0x00000007 tdsTypeU32 4 Integer
13
+ 0x00000008 tdsTypeU64 8 Integer
14
+ 0x00000009 tdsTypeSingleFloat 4 Float
15
+ 0x0000000A tdsTypeDoubleFloat 8 Float
16
+ 0x0000000B tdsTypeExtendedFloat 10 ?
17
+ 0x00000019 tdsTypeSingleFloatWithUnit 4 Float
18
+ 0x0000001A tdsTypeDoubleFloatWithUnit 8 Float
19
+ 0x0000001B tdsTypeExtendedFloatWithUnit 10 ?
20
+ 0x00000020 tdsTypeString 4 len + n chars String
21
+ 0x00000021 tdsTypeBoolean 1 True, False
22
+ 0x00000044 tdsTypeTimeStamp 16 DateTime
23
+ 0xFFFFFFFF tdsTypeDAQmxRawData ? ?
@@ -0,0 +1,47 @@
1
+ EXAMPLE.tdms
2
+ Contains 4 segments
3
+
4
+
5
+ 000000 54 44 53 6d "TDSm" id tag
6
+ 000004 0E 00 00 00 0e is ToC flag, other 3 bytes unused
7
+ 000008 68 12 00 00 6812 is little endian for 0x1268 or 4713 (TDMS standard version). Other two bytes unused.
8
+ 00000C E3 88 20 00
9
+ 000010 00 00 00 00 => 64-bit unsigned little endian 0x00000000002088E3 is the next segment offset
10
+ 000014 E3 08 00 00
11
+ 000018 00 00 00 00 => 64-bit unsigned little endian 0x00000000000008E3 is the raw data offset
12
+ ( End of Lead-In )
13
+ ( 00001C is the offset of the next byte after the lead-in )
14
+ ( Next segment offset = 00001C + 2088E3 = 2088FF )
15
+ ( Raw data offset = 00001C + 0008E3 = 0008FF )
16
+
17
+ ( Start of Metadata )
18
+ 00001C 08 00 00 00 => 32-bit unsigned little endian 0x00000008 is the number of
19
+ new/changed objects in this segment (8 objects)
20
+ 000020 11 00 00 00 => 32-bit unsigned little endian 0x00000011 is the length of
21
+ the object's path string (11 bytes)
22
+ (Begin Path String)
23
+ 000024 2F 27 45 58 /'EX
24
+ 000028 41 4D 50 4C AMPL
25
+ 00002C 45 27 2F 27 E'/'
26
+ 000030 54 69 6D 65 Time
27
+ 000034 27 '
28
+ (Begin Length of Index)
29
+ 000034 14 00 00
30
+ 000038 0A 00 00 => 32-bit unsigned little endian 0x00000014 is the
31
+ raw data index
32
+ (Begin Data Type)
33
+ 000038 0A
34
+ 00003C 00 00 00 => 32-bit unsigned little endian 0x0000000A is the
35
+ data type (tdsTypeDoubleFloat)
36
+
37
+ (Begin Array Dimension)
38
+ 00003C 01
39
+ 000040 00 00 00 => 32-bit unsigned little endian 0x00000001 is the
40
+ array dimension (always 1)
41
+ (Begin Number of Values)
42
+ 000040 00
43
+ 000044 04 00 00 00
44
+ 000048 04 00 00 => 64-bit unsigned little endian 0x0000000000000400 is the
45
+ number of values (1024 values)
46
+ (Begin Total Size in Bytes)
47
+
@@ -0,0 +1,101 @@
1
+ http://zone.ni.com/devzone/cda/tut/p/id/5696
2
+
3
+ Object Hierarchy
4
+
5
+ Hierarchy Path
6
+
7
+ example_events.tdms (File) /
8
+ |
9
+ +-- Measured Data (Group) /Measured Data
10
+ | |
11
+ | +-- Amplitude Sweep (Channel) /Measured Data/Amplitude Sweep
12
+ | +-- Phase Sweep (Channel) /Measured Data/Phase Sweep
13
+ |
14
+ +-- Dr. T's Events (Group) /Dr. T's Events
15
+ |
16
+ +-- Time (Channel) /Dr. T's Events/Time
17
+ +-- Description (Channel) /Dr. T's Events/Description
18
+
19
+ There are exactly 3 levels of objects in TDMS:
20
+ - File (every TDMS file must have one)
21
+ - Group (has many Channels, may have none)
22
+ - Channel (belongs to a Group)
23
+
24
+ Every object is identified by a string path. The only thing that
25
+ identifies an object as being a File, Group, or Channel is the
26
+ number of Segments in the string path:
27
+
28
+ Object Type Example Path Number of Path Segments
29
+ File / 0
30
+ Group /Group 1
31
+ Channel /Group/Channel 2
32
+
33
+
34
+ File Structure
35
+
36
+ File is divided into Segments.
37
+ Each segment contains one or more Objects
38
+ Every Object has a string Path
39
+
40
+
41
+ Segment
42
+
43
+ +-----------+------------+------------+----------------------------
44
+ | Lead-in | Metadata | Raw Data | Lead-in (Next Segment) ...
45
+ +-----------+------------+------------+----------------------------
46
+
47
+ Lead-in
48
+ +---------+--------+---------+--------------------+--------------------+---
49
+ | 4: TDSm | 4: ToC | 4: Vers | 8: Next Seg Offset | 8: Raw Data Offset | Metadata ...
50
+ +---------+--------+---------+--------------------+--------------------+---
51
+ 00 04 08 0C 14 1C
52
+
53
+
54
+ 00-03 4 bytes: TDMS identifier (always "TDSm")
55
+ 04-07 4 bytes: Table of contents (only first byte used)
56
+ Flags to indicate what is in the segment
57
+ 08-0B 4 bytes: TDMS version number (always 4713)
58
+ 0C-13 8 bytes: Offset of the next segment (little endian). Take the
59
+ absolute offset of the next byte after the lead-in and
60
+ add it to this number to find the next segment.
61
+ 14-1B 8 bytes: Offset of the raw data in this segment (little endian).
62
+ Also calculate it from next byte after the lead-in.
63
+
64
+
65
+ Metadata
66
+ +----------+-------------+---------+-------------------+--------------+-
67
+ | 4: Count | 4: Path Len | n: Path | 4: Raw data index | 4: Num Props |
68
+ +----------+-------------+---------+-------------------+--------------+-
69
+ 00 04 08
70
+
71
+ 4 bytes: Number of new/changed objects in this segment
72
+
73
+ For each object in the segment:
74
+ 4 bytes: Length of object's string path
75
+ n bytes: Object's string path
76
+
77
+ Index block or markers:
78
+ If no raw data in the segment:
79
+ 4 bytes: FF FF FF FF
80
+ Else if raw data index block exactly matches last segment:
81
+ 4 bytes: 00 00 00 00
82
+ Else:
83
+ 4 bytes: Length of raw data index block + 4
84
+ 4 bytes: Data type of the raw data in this object
85
+ 4 bytes: Dimension of raw data array (only first byte used, always 1)
86
+ 8 bytes: Number of values
87
+ 8 bytes: Total size in bytes (?) -- may not be here
88
+
89
+ Properties Block:
90
+
91
+ 4 bytes: Number of properties of this object
92
+
93
+ For each property:
94
+ 4 bytes: Length of property name
95
+ n bytes: Property name
96
+ 4 bytes: Data type of property value (only first byte used)
97
+ n bytes: Property value
98
+ String is 4 bytes for length, then bytes of string
99
+ Others are fixed number of bytes for value based on type
100
+
101
+ Raw Data
@@ -0,0 +1,48 @@
1
+ READING
2
+ =======
3
+
4
+ # display properties of a channel
5
+
6
+ group = segment.groups.find {|grp| grp.path == "/EXAMPLE" }
7
+ speed = group.channels.find {|ch| ch.path == "/EXAMPLE/Time" }
8
+ speed.properties.each_pair do |k,v|
9
+ puts k,v
10
+ end
11
+
12
+ # loop through a channel
13
+
14
+ group = segment.groups.find {|grp| grp.path == "/EXAMPLE" }
15
+ speed = group.channels.find {|ch| ch.path == "/EXAMPLE/Time" }
16
+ speed.values.each do |v|
17
+ puts v #=> float
18
+ end
19
+
20
+ # spreadsheet of two channels
21
+
22
+ group = segment.groups.find {|grp| grp.path == "/EXAMPLE" }
23
+
24
+ time = group.channels.find { |ch| ch.path == "/EXAMPLE/Time" }
25
+ speed = group.channels.find { |ch| ch.path == "/EXAMPLE/Speed" }
26
+
27
+ max = [time.values.size, speed.values.size].max - 1
28
+ 0.upto(max) do |i|
29
+ puts "%f,%f" % (time.values[i], speed.values[i])
30
+ end
31
+
32
+
33
+ WRITING
34
+ =======
35
+
36
+ (Note: writing is not yet supported)
37
+
38
+ tdms = RubyTDMS::File.new("some filename")
39
+ seg = tdms.segment.build
40
+
41
+ group = seg.groups.build("foo")
42
+
43
+ chan = group.channels.build("bar")
44
+ chan.properties["Flux Capacitor"] = "On"
45
+ channel.values << 1.02
46
+ channel.values << 1.02
47
+
48
+ seg.save
@@ -0,0 +1,2 @@
1
+ require 'require_all'
2
+ require_rel 'ruby_tdms'
@@ -0,0 +1,53 @@
1
+ require_relative 'aggregate_channel_enumerator'
2
+
3
+ module RubyTDMS
4
+ class AggregateChannel
5
+ def initialize(channels = [])
6
+ @channels = channels
7
+ end
8
+
9
+
10
+ def inspect
11
+ "#<#{self.class.name}:#{self.object_id} path=#{path.inspect}, #{@channels.length} channel(s)>"
12
+ end
13
+
14
+
15
+ def path
16
+ @channels[0].path
17
+ end
18
+
19
+
20
+ def name
21
+ @channels[0].name
22
+ end
23
+
24
+
25
+ def data_type
26
+ @channels[0].data_type
27
+ end
28
+
29
+
30
+ def data_type_id
31
+ @channels[0].data_type_id
32
+ end
33
+
34
+
35
+ def values
36
+ @values ||= AggregateChannelEnumerator.new @channels
37
+ end
38
+
39
+
40
+ def as_json
41
+ result = @channels[0].as_json
42
+ # Iterate over all channel objects and update properties
43
+ result[:properties] = @channels.reduce({}) do |properties, channel|
44
+ channel.properties.each do |property|
45
+ properties[property.name.to_sym] = property.value
46
+ end
47
+ properties
48
+ end
49
+ result[:values] = values.to_a
50
+ result
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,50 @@
1
+ module RubyTDMS
2
+ class AggregateChannelEnumerator
3
+ include Enumerable
4
+
5
+
6
+ def initialize(channels)
7
+ @channels = channels
8
+ @offsets = []
9
+
10
+ size = 0
11
+ @channels.inject(0) do |size, channel|
12
+ @offsets << size
13
+ size += channel.values.size
14
+ end
15
+ end
16
+
17
+
18
+ def size
19
+ @size ||= @channels.inject(0) { |sum, chan| sum += chan.values.size }
20
+ end
21
+
22
+
23
+ def each
24
+ @channels.each do |channel|
25
+ channel.values.each { |value| yield value }
26
+ end
27
+ end
28
+
29
+
30
+ def [](i)
31
+ if (i < 0) || (i >= size)
32
+ raise RangeError, 'Channel %s has a range of 0 to %d, got invalid index: %d' % [@channels[0].path, size - 1, i]
33
+ end
34
+
35
+ channel, offset = nil, nil
36
+ j = @offsets.size - 1
37
+ @offsets.reverse_each do |o|
38
+ if i >= o
39
+ channel = @channels[j]
40
+ offset = @offsets[j]
41
+ break
42
+ else
43
+ j -= 1
44
+ end
45
+ end
46
+
47
+ channel.values[i - offset]
48
+ end
49
+ end
50
+ end