tdms 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +10 -0
- data/LICENSE.txt +25 -0
- data/README.md +34 -0
- data/Rakefile +15 -0
- data/demo.rb +16 -0
- data/doc/data_types.txt +23 -0
- data/doc/example_disasm.txt +47 -0
- data/doc/tdms_format.txt +101 -0
- data/doc/usage.txt +46 -0
- data/lib/tdms.rb +8 -0
- data/lib/tdms/aggregate.rb +71 -0
- data/lib/tdms/channel.rb +100 -0
- data/lib/tdms/datatypes.rb +173 -0
- data/lib/tdms/document.rb +114 -0
- data/lib/tdms/path.rb +58 -0
- data/lib/tdms/property.rb +17 -0
- data/lib/tdms/segment.rb +12 -0
- data/lib/tdms/streaming.rb +82 -0
- data/test/build_fixtures/README.txt +25 -0
- data/test/build_fixtures/type_01_int8_one_segment.vi +0 -0
- data/test/build_fixtures/type_01_int8_three_segments.vi +0 -0
- data/test/build_fixtures/type_01_int8_two_channels_one_segment.vi +0 -0
- data/test/build_fixtures/type_02_int16_one_segment.vi +0 -0
- data/test/build_fixtures/type_02_int16_three_segments.vi +0 -0
- data/test/build_fixtures/type_02_int16_two_channels_one_segment.vi +0 -0
- data/test/build_fixtures/type_03_int32_one_segment.vi +0 -0
- data/test/build_fixtures/type_03_int32_three_segments.vi +0 -0
- data/test/build_fixtures/type_03_int32_two_channels_one_segment.vi +0 -0
- data/test/build_fixtures/type_04_int64_one_segment.vi +0 -0
- data/test/build_fixtures/type_04_int64_three_segments.vi +0 -0
- data/test/build_fixtures/type_04_int64_two_channels_one_segment.vi +0 -0
- data/test/build_fixtures/type_05_uint8_one_segment.vi +0 -0
- data/test/build_fixtures/type_05_uint8_three_segments.vi +0 -0
- data/test/build_fixtures/type_05_uint8_two_channels_one_segment.vi +0 -0
- data/test/build_fixtures/type_06_uint16_one_segment.vi +0 -0
- data/test/build_fixtures/type_06_uint16_three_segments.vi +0 -0
- data/test/build_fixtures/type_06_uint16_two_channels_one_segment.vi +0 -0
- data/test/build_fixtures/type_07_uint32_one_segment.vi +0 -0
- data/test/build_fixtures/type_07_uint32_three_segments.vi +0 -0
- data/test/build_fixtures/type_07_uint32_two_channels_one_segment.vi +0 -0
- data/test/build_fixtures/type_08_uint64_one_segment.vi +0 -0
- data/test/build_fixtures/type_08_uint64_three_segments.vi +0 -0
- data/test/build_fixtures/type_08_uint64_two_channels_one_segment.vi +0 -0
- data/test/build_fixtures/type_09_single_one_segment.vi +0 -0
- data/test/build_fixtures/type_09_single_three_segments.vi +0 -0
- data/test/build_fixtures/type_09_single_two_channels_one_segment.vi +0 -0
- data/test/build_fixtures/type_0a_double_one_segment.vi +0 -0
- data/test/build_fixtures/type_0a_double_three_segments.vi +0 -0
- data/test/build_fixtures/type_0a_double_two_channels_one_segment.vi +0 -0
- data/test/build_fixtures/type_20_string_one_segment.vi +0 -0
- data/test/build_fixtures/type_20_string_three_segments.vi +0 -0
- data/test/build_fixtures/type_20_string_two_channels_one_segment.vi +0 -0
- data/test/build_fixtures/type_21_boolean_one_segment.vi +0 -0
- data/test/build_fixtures/type_21_boolean_three_segments.vi +0 -0
- data/test/build_fixtures/type_21_boolean_two_channels_one_segment.vi +0 -0
- data/test/build_fixtures/type_44_datetime_one_segment.vi +0 -0
- data/test/build_fixtures/type_44_timestamp_three_segments.vi +0 -0
- data/test/build_fixtures/type_44_timestamp_two_channels_one_segment.vi +0 -0
- data/test/fixtures/example.tdms +0 -0
- data/test/fixtures/type_01_int8_one_segment.tdms +0 -0
- data/test/fixtures/type_01_int8_three_segments.tdms +0 -0
- data/test/fixtures/type_01_int8_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_02_int16_one_segment.tdms +0 -0
- data/test/fixtures/type_02_int16_three_segments.tdms +0 -0
- data/test/fixtures/type_02_int16_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_03_int32_one_segment.tdms +0 -0
- data/test/fixtures/type_03_int32_three_segments.tdms +0 -0
- data/test/fixtures/type_03_int32_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_04_int64_one_segment.tdms +0 -0
- data/test/fixtures/type_04_int64_three_segments.tdms +0 -0
- data/test/fixtures/type_04_int64_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_05_uint8_one_segment.tdms +0 -0
- data/test/fixtures/type_05_uint8_three_segments.tdms +0 -0
- data/test/fixtures/type_05_uint8_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_06_uint16_one_segment.tdms +0 -0
- data/test/fixtures/type_06_uint16_three_segments.tdms +0 -0
- data/test/fixtures/type_06_uint16_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_07_uint32_one_segment.tdms +0 -0
- data/test/fixtures/type_07_uint32_three_segments.tdms +0 -0
- data/test/fixtures/type_07_uint32_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_08_uint64_one_segment.tdms +0 -0
- data/test/fixtures/type_08_uint64_three_segments.tdms +0 -0
- data/test/fixtures/type_08_uint64_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_09_single_one_segment.tdms +0 -0
- data/test/fixtures/type_09_single_three_segments.tdms +0 -0
- data/test/fixtures/type_09_single_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_0a_double_one_segment.tdms +0 -0
- data/test/fixtures/type_0a_double_three_segments.tdms +0 -0
- data/test/fixtures/type_0a_double_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_19_single_with_unit_one_segment.tdms +0 -0
- data/test/fixtures/type_19_single_with_unit_three_segments.tdms +0 -0
- data/test/fixtures/type_19_single_with_unit_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_1a_double_with_unit_one_segment.tdms +0 -0
- data/test/fixtures/type_1a_double_with_unit_three_segments.tdms +0 -0
- data/test/fixtures/type_1a_double_with_unit_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_20_double_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_20_string_one_segment.tdms +0 -0
- data/test/fixtures/type_20_string_three_segments.tdms +0 -0
- data/test/fixtures/type_20_string_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_20_string_two_segments.tdms +0 -0
- data/test/fixtures/type_21_boolean_one_segment.tdms +0 -0
- data/test/fixtures/type_21_boolean_three_segments.tdms +0 -0
- data/test/fixtures/type_21_boolean_two_channels_one_segment.tdms +0 -0
- data/test/fixtures/type_44_timestamp_one_segment.tdms +0 -0
- data/test/fixtures/type_44_timestamp_three_segments.tdms +0 -0
- data/test/fixtures/type_44_timestamp_two_channels_one_segment.tdms +0 -0
- data/test/read_type_01_int8_test.rb +58 -0
- data/test/read_type_02_int16_test.rb +58 -0
- data/test/read_type_03_int32_test.rb +58 -0
- data/test/read_type_04_int64_test.rb +64 -0
- data/test/read_type_05_uint8_test.rb +56 -0
- data/test/read_type_06_uint16_test.rb +58 -0
- data/test/read_type_07_uint32_test.rb +58 -0
- data/test/read_type_08_uint64_test.rb +58 -0
- data/test/read_type_09_single_test.rb +58 -0
- data/test/read_type_0a_double_test.rb +58 -0
- data/test/read_type_19_single_with_unit_test.rb +58 -0
- data/test/read_type_1a_double_with_unit_test.rb +58 -0
- data/test/read_type_20_string_test.rb +60 -0
- data/test/read_type_21_boolean_test.rb +56 -0
- data/test/read_type_44_timestamp_test.rb +60 -0
- data/test/test_helper.rb +9 -0
- metadata +284 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1cdde94353fa626b85a4fa61044c5562baeca357
|
4
|
+
data.tar.gz: 6cd526130eecd98461297ce47a8a51ec11671801
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8c3ed68005a5ec8c0c97f074d1096b15035cbe3174ebf6f212a0cc6225ba45bac582b3ee10a365c529bfa4ba9fa354c2b2365e84c6d9d32fa3777515ecb48cdc
|
7
|
+
data.tar.gz: 4443ac28b1267c32ac333c606f7c53bf11439d400076dec64ecf859163629a9c7139b4072fee5ce250643236092c509e80fc560ce1c5ce688a622d80f0a16053
|
data/.travis.yml
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (c) 2012, Mike Naberezny.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright
|
10
|
+
notice, this list of conditions and the following disclaimer in the
|
11
|
+
documentation and/or other materials provided with the distribution.
|
12
|
+
* Neither the name of Maintainable Software, LLC. nor the names of its
|
13
|
+
contributors may be used to endorse or promote products derived from
|
14
|
+
this software without specific prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# TDMS for Ruby
|
2
|
+
|
3
|
+
TDMS is a binary file format for measurement data. It was created
|
4
|
+
by National Instruments.
|
5
|
+
|
6
|
+
- [NI TDMS File Format](http://zone.ni.com/devzone/cda/tut/p/id/3727)
|
7
|
+
- [TDMS File Format Internal Structure](http://zone.ni.com/devzone/cda/tut/p/id/5696)
|
8
|
+
|
9
|
+
National Instruments software such as LabVIEW, DIAdem, and Measurement
|
10
|
+
Studio support reading and writing TDMS files. NI also provides a DLL
|
11
|
+
written in C for using TDMS files on Windows.
|
12
|
+
|
13
|
+
TDMS for Ruby was written to provide a convenient way to work with
|
14
|
+
TDMS files on Unix-like platforms.
|
15
|
+
|
16
|
+
## Current State
|
17
|
+
|
18
|
+
This library is very early in development but is complete enough to
|
19
|
+
read the example TDMS file that comes with NI DIAdem.
|
20
|
+
|
21
|
+
- Segments with interleaved measurements are not yet supported.
|
22
|
+
- Segments with big endian data are not yet supported.
|
23
|
+
- Writing TDMS files is not yet supported.
|
24
|
+
|
25
|
+
## Fork
|
26
|
+
|
27
|
+
Aaron Ten Clay has a [fork](https://github.com/aarontc/ruby_tdms) of this
|
28
|
+
library that claims to support the segment types above, along with reading
|
29
|
+
from streams instead of files, and other features.
|
30
|
+
|
31
|
+
## Contributors
|
32
|
+
|
33
|
+
[Mike Naberezny](http://github.com/mnaberez) is the author of TDMS for
|
34
|
+
Ruby. Development is sponsored by [Maintainable](http://maintainable.com).
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
here = File.expand_path('..', __FILE__)
|
2
|
+
$LOAD_PATH.unshift File.join(here, 'lib')
|
3
|
+
$LOAD_PATH.unshift File.join(here, 'test')
|
4
|
+
|
5
|
+
desc "Run tests"
|
6
|
+
task :test do
|
7
|
+
require 'tdms'
|
8
|
+
require 'test_helper'
|
9
|
+
|
10
|
+
Dir.chdir(File.join(here, 'test')) do
|
11
|
+
Dir['**/*_test.rb'].each { |file| require file }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
task :default => [:test]
|
data/demo.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib')
|
2
|
+
|
3
|
+
require 'tdms'
|
4
|
+
|
5
|
+
filename = File.dirname(__FILE__) + "/test/fixtures/example.tdms"
|
6
|
+
doc = Tdms::File.parse(filename)
|
7
|
+
|
8
|
+
ch1 = doc.channels.find {|c| c.name == "StatisticsText"}
|
9
|
+
ch2 = doc.channels.find {|c| c.name == "Res_Noise_1"}
|
10
|
+
|
11
|
+
last = [ch1.values.size, ch2.values.size].min - 1
|
12
|
+
|
13
|
+
puts "#{ch1.name},#{ch2.name}"
|
14
|
+
0.upto(last) do |i|
|
15
|
+
puts "#{ch1.values[i]},#{ch2.values[i]}"
|
16
|
+
end
|
data/doc/data_types.txt
ADDED
@@ -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
|
+
|
data/doc/tdms_format.txt
ADDED
@@ -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
|
data/doc/usage.txt
ADDED
@@ -0,0 +1,46 @@
|
|
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
|
+
tdms = Tdms::File.new("some filename")
|
37
|
+
seg = tdms.segment.build
|
38
|
+
|
39
|
+
group = seg.groups.build("foo")
|
40
|
+
|
41
|
+
chan = group.channels.build("bar")
|
42
|
+
chan.properties["Flux Capacitor"] = "On"
|
43
|
+
channel.values << 1.02
|
44
|
+
channel.values << 1.02
|
45
|
+
|
46
|
+
seg.save
|
data/lib/tdms.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
module Tdms
|
2
|
+
|
3
|
+
class AggregateChannel
|
4
|
+
def initialize(channels=[])
|
5
|
+
@channels = channels
|
6
|
+
end
|
7
|
+
|
8
|
+
def path
|
9
|
+
@channels[0].path
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
@channels[0].name
|
14
|
+
end
|
15
|
+
|
16
|
+
def data_type
|
17
|
+
@channels[0].data_type
|
18
|
+
end
|
19
|
+
|
20
|
+
def values
|
21
|
+
@values ||= AggregateChannelEnumerator.new(@channels)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class AggregateChannelEnumerator
|
26
|
+
include Enumerable
|
27
|
+
|
28
|
+
def initialize(channels)
|
29
|
+
@channels = channels
|
30
|
+
@offsets = []
|
31
|
+
|
32
|
+
size = 0
|
33
|
+
@channels.inject(0) do |size, channel|
|
34
|
+
@offsets << size
|
35
|
+
size += channel.values.size
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def size
|
40
|
+
@size ||= @channels.inject(0) { |sum, chan| sum += chan.values.size }
|
41
|
+
end
|
42
|
+
|
43
|
+
def each
|
44
|
+
@channels.each do |channel|
|
45
|
+
channel.values.each { |value| yield value }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def [](i)
|
50
|
+
if (i < 0) || (i >= size)
|
51
|
+
raise RangeError, "Channel %s has a range of 0 to %d, got invalid index: %d" %
|
52
|
+
[@channels[0].path, size - 1, i]
|
53
|
+
end
|
54
|
+
|
55
|
+
channel, offset = nil, nil
|
56
|
+
j = @offsets.size - 1
|
57
|
+
@offsets.reverse_each do |o|
|
58
|
+
if i >= o
|
59
|
+
channel = @channels[j]
|
60
|
+
offset = @offsets[j]
|
61
|
+
break
|
62
|
+
else
|
63
|
+
j -= 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
channel.values[i - offset]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/lib/tdms/channel.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
module Tdms
|
2
|
+
|
3
|
+
class Channel < Object
|
4
|
+
attr_accessor :file, :path, :data_type_id, :dimension, :num_values,
|
5
|
+
:raw_data_pos
|
6
|
+
|
7
|
+
def name
|
8
|
+
path.channel
|
9
|
+
end
|
10
|
+
|
11
|
+
def values
|
12
|
+
@values ||= begin
|
13
|
+
klass = if data_type::LengthInBytes.nil?
|
14
|
+
StringChannelEnumerator
|
15
|
+
else
|
16
|
+
ChannelEnumerator
|
17
|
+
end
|
18
|
+
|
19
|
+
klass.new(self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def data_type
|
24
|
+
@data_type ||= DataType.find_by_id(data_type_id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class ChannelEnumerator
|
29
|
+
include Enumerable
|
30
|
+
|
31
|
+
def initialize(channel)
|
32
|
+
@channel = channel
|
33
|
+
end
|
34
|
+
|
35
|
+
def size
|
36
|
+
@size ||= @channel.num_values
|
37
|
+
end
|
38
|
+
|
39
|
+
def each
|
40
|
+
0.upto(size - 1) { |i| yield self[i] }
|
41
|
+
end
|
42
|
+
|
43
|
+
def [](i)
|
44
|
+
if (i < 0) || (i >= size)
|
45
|
+
raise RangeError, "Channel %s has a range of 0 to %d, got invalid index: %d" %
|
46
|
+
[@channel.path, size - 1, i]
|
47
|
+
end
|
48
|
+
|
49
|
+
@channel.file.seek @channel.raw_data_pos + (i * @channel.data_type::LengthInBytes)
|
50
|
+
@channel.data_type.read_from_stream(@channel.file).value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class StringChannelEnumerator
|
55
|
+
include Enumerable
|
56
|
+
|
57
|
+
def initialize(channel)
|
58
|
+
@channel = channel
|
59
|
+
|
60
|
+
@index_pos = @channel.raw_data_pos
|
61
|
+
@data_pos = @index_pos + (4 * @channel.num_values)
|
62
|
+
end
|
63
|
+
|
64
|
+
def size
|
65
|
+
@size ||= @channel.num_values
|
66
|
+
end
|
67
|
+
|
68
|
+
def each
|
69
|
+
data_pos = @data_pos
|
70
|
+
|
71
|
+
0.upto(size - 1) do |i|
|
72
|
+
index_pos = @index_pos + (4 * i)
|
73
|
+
|
74
|
+
@channel.file.seek index_pos
|
75
|
+
next_data_pos = @data_pos + @channel.file.read_u32
|
76
|
+
|
77
|
+
length = next_data_pos - data_pos
|
78
|
+
|
79
|
+
@channel.file.seek data_pos
|
80
|
+
yield @channel.file.read(length)
|
81
|
+
|
82
|
+
data_pos = next_data_pos
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def [](i)
|
87
|
+
if (i < 0) || (i >= size)
|
88
|
+
raise RangeError, "Channel %s has a range of 0 to %d, got invalid index: %d" %
|
89
|
+
[@channel.path, size - 1, i]
|
90
|
+
end
|
91
|
+
|
92
|
+
inject(0) do |j, value|
|
93
|
+
return value if j == i
|
94
|
+
j += 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|