tdms 0.1.0
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.
- 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
|