From 21234a61cdbe04be0d0982c44c7cf7df48cfd56c Mon Sep 17 00:00:00 2001
From: "Mads M. Pedersen" <mmpe@dtu.dk>
Date: Thu, 4 May 2017 11:40:04 +0200
Subject: [PATCH] flex io

---
 wetb/flex/__init__.py                         |   6 +
 wetb/flex/_io.py                              | 159 ++++++++++++++++++
 wetb/flex/tests/__init__.py                   |   0
 wetb/flex/tests/test_files/test1/sensor       |   9 +
 wetb/flex/tests/test_files/test1/test.int     | Bin 0 -> 11352 bytes
 .../tests/test_files/test_sensor_info/sensor  |  69 ++++++++
 wetb/flex/tests/test_flex_io.py               |  27 +++
 wetb/flex/tests/test_sensor.py                |  26 +++
 8 files changed, 296 insertions(+)
 create mode 100644 wetb/flex/__init__.py
 create mode 100644 wetb/flex/_io.py
 create mode 100644 wetb/flex/tests/__init__.py
 create mode 100644 wetb/flex/tests/test_files/test1/sensor
 create mode 100644 wetb/flex/tests/test_files/test1/test.int
 create mode 100644 wetb/flex/tests/test_files/test_sensor_info/sensor
 create mode 100644 wetb/flex/tests/test_flex_io.py
 create mode 100644 wetb/flex/tests/test_sensor.py

diff --git a/wetb/flex/__init__.py b/wetb/flex/__init__.py
new file mode 100644
index 0000000..4d88eaf
--- /dev/null
+++ b/wetb/flex/__init__.py
@@ -0,0 +1,6 @@
+from ._io import load, read_sensor_info
+from wetb import gtsdf
+
+class Dataset(gtsdf.Dataset):
+    def __init__(self, filename):
+        self.time, self.data, self.info = load(filename)
\ No newline at end of file
diff --git a/wetb/flex/_io.py b/wetb/flex/_io.py
new file mode 100644
index 0000000..d3e4e3e
--- /dev/null
+++ b/wetb/flex/_io.py
@@ -0,0 +1,159 @@
+'''
+Created on 11. apr. 2017
+
+@author: mmpe
+'''
+import struct
+import numpy as np
+import os
+
+def load(filename, dtype=np.float):
+    if isinstance(filename, str):
+        fid = open(filename,'rb')
+    elif hasattr(filename, "name"):
+        fid = filename
+        filename = fid.name
+    try:
+        _ = struct.unpack('i', fid.read(4))
+        _ = struct.unpack('i', fid.read(4))
+        title = fid.read(60).strip()
+     
+        _ = struct.unpack('i', fid.read(4))
+        _ = struct.unpack('i', fid.read(4))
+        no_sensors = struct.unpack('i', fid.read(4))[0]
+     
+        sensor_numbers = [struct.unpack('i', fid.read(4))[0] for _ in range(no_sensors)]
+        _ = struct.unpack('i', fid.read(4))
+        _ = struct.unpack('i', fid.read(4))
+        time_start = struct.unpack('f', fid.read(4))[0]
+        time_step = struct.unpack('f', fid.read(4))[0]
+        scale_factors = np.array([struct.unpack('f', fid.read(4))[0] for _ in range(no_sensors)], dtype=np.double)
+        data = np.fromstring(fid.read(), 'int16').astype(dtype)
+    finally: 
+        fid.close()
+ 
+#     if title.isalnum():
+#         self.set_info(title, "", self.filename)
+#     else:
+#         self.set_info(os.path.basename(self.name), "", self.name)
+    try:
+        data = data.reshape(len(data) // no_sensors, no_sensors)
+    except ValueError:
+        raise ValueError("The number of data values (%d) is not divisible by the number of sensors (%d)" % (len(data), no_sensors))
+ 
+    for i in range(data.shape[1]):
+        data[:, i] *= scale_factors[i]
+    no_scans = data.shape[0]
+     
+ 
+    # Load sensor file if exists
+    
+    
+    sensor_filename = os.path.join(os.path.dirname(filename), "sensor")
+    sensor_info = {info[0]:info[1:] for info in read_sensor_info(sensor_filename) }
+
+    # set gain and offset for "Time"
+    gains = []
+    offsets = []
+    names, units, descriptions = [], [], []
+    
+    for sensor_nr in sensor_numbers:
+ 
+        name, unit, description, gain, offset = sensor_info.get(sensor_nr, ["Attribute %d"%sensor_nr, '-','',1,0])
+        if sensor_nr==1 and name=="Time" and unit=="s":
+            data = data[:,1:]
+            continue
+        names.append(name)
+        units.append(unit)
+        descriptions.append(description)
+        gains.append(gain)
+        offsets.append(offset)
+    
+    time = np.arange(time_start, time_start + data.shape[0] * time_step, time_step).reshape((no_scans, 1))
+    #data = np.concatenate((time, data), axis=1)
+    #gains = np.r_[1,gains]
+    #offsets = np.r_[0,offsets]
+    # self[:]*=self.gains
+    # self[:]+=self.offsets
+    info = {"name": title,
+            "description": filename,
+            "attribute_names": names, 
+            "attribute_units": units, 
+            "attribute_descriptions": descriptions}
+    return time, data, info 
+
+
+def read_sensor_info(sensor_file):
+
+    if hasattr(sensor_file, 'readlines'):
+        sensor_info_lines = sensor_file.readlines()[2:]
+    else:
+        with open(sensor_file, encoding="utf-8") as fid:
+            sensor_info_lines = fid.readlines()[2:]
+    sensor_info = []
+    for line in sensor_info_lines:
+        # while "  " in line:
+        #    line = line.replace("  "," ")
+        line = line.strip().split()
+        nr = int(line[0])
+        gain = float(line[1])
+        offset = float(line[2])
+        unit = line[5]
+        name_desc = " ".join(line[6:])
+        name = name_desc[:8].split()[0]
+        description = name_desc[8:]
+#        name = line[6]
+#        description = " ".join(line[7:])
+
+
+        sensor_info.append((nr, name, unit, description, gain, offset))
+    return sensor_info
+
+# def save(dataset, filename):
+#     ds = dataset
+#     # Write int data file
+#     f = open(filename, 'wb')
+#     time_att = ds.basis_attribute()
+#     sensors = [s for s in ds.attributes() if s is not time_att]
+#     
+#     if isinstance(ds, FLEX4Dataset):
+#         data = ds[:]  # (ds[:]-ds.offsets)/ds.gains
+#     else:
+#         data = ds[:]
+#     if time_att.index != -1:  # time_att may be "Index" with index=-1 if "Time" not exists
+#         data = np.delete(data, time_att.index, axis=1)
+#     f.write(struct.pack('ii', 0, 0))  # 2x empty int
+#     title = ("%-60s" % str(ds.name)).encode()
+#     f.write(struct.pack('60s', title))  # title
+#     f.write(struct.pack('ii', 0, 0))  # 2x empty int
+#     ns = len(sensors)
+#     f.write(struct.pack('i', ns))
+#     f.write(struct.pack('i' * ns, *range(1, ns + 1)))  # sensor number
+#     f.write(struct.pack('ii', 0, 0))  # 2x empty int
+#     time = ds.basis_attribute()
+#     f.write(struct.pack('ff', time[0], time[1] - time[0]))  # start time and time step
+# 
+#     scale_factors = np.max(np.abs(data), 0) / 32000
+#     f.write(struct.pack('f' * len(scale_factors), *scale_factors))
+#     # avoid dividing by zero
+#     not0 = np.where(scale_factors != 0)
+#     data[:, not0] /= scale_factors[not0]
+#     #flatten and round
+#     data = np.round(data.flatten()).astype(np.int16)
+#     f.write(struct.pack('h' * len(data), *data.tolist()))
+#     f.close()
+# 
+#     # write sensor file
+#     f = open(os.path.join(os.path.dirname(filename), 'sensor'), 'w')
+#     f.write("Sensor list for %s\n" % filename)
+#     f.write(" No   forst  offset  korr. c  Volt    Unit   Navn    Beskrivelse------------\n")
+#     sensorlineformat = "%3s  %.3f   %.3f      1.00     0.00 %7s %-8s %s\n"
+# 
+#     if isinstance(ds, FLEX4Dataset):
+#         gains = np.r_[ds.gains[1:], np.ones(ds.shape[1] - len(ds.gains))]
+#         offsets = np.r_[ds.offsets[1:], np.zeros(ds.shape[1] - len(ds.offsets))]
+#         sensorlines = [sensorlineformat % ((nr + 1), gain, offset, att.unit[:7], att.name.replace(" ", "_")[:8], att.description[:512]) for nr, att, gain, offset in zip(range(ns), sensors, gains, offsets)]
+#     else:
+#         sensorlines = [sensorlineformat % ((nr + 1), 1, 0, att.unit[:7], att.name.replace(" ", "_")[:8], att.description[:512]) for nr, att in enumerate(sensors)]
+#     f.writelines(sensorlines)
+#     f.close()
\ No newline at end of file
diff --git a/wetb/flex/tests/__init__.py b/wetb/flex/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/wetb/flex/tests/test_files/test1/sensor b/wetb/flex/tests/test_files/test1/sensor
new file mode 100644
index 0000000..88e89f6
--- /dev/null
+++ b/wetb/flex/tests/test_files/test1/sensor
@@ -0,0 +1,9 @@
+Sensor list for C:/mmpe/programming/python/WindEnergyToolbox/wetb/flex/tests/test_files/test1/test.int
+ No   forst  offset  korr. c  Volt    Unit   Navn    Beskrivelse------------
+  1  1.000   0.000      1.00     0.00     m/s WSP_gl._ Free wind speed Vy, gl. coo, of gl. pos    0.75,   0.00, -40.75
+  2  1.000   0.000      1.00     0.00     m/s WSP_gl._ Free wind speed Vy, gl. coo, of gl. pos    1.00,   0.00, -39.00
+  3  1.000   0.000      1.00     0.00     m/s WSP_gl._ Free wind speed Vy, gl. coo, of gl. pos    7.00,   0.00, -33.00
+  4  1.000   0.000      1.00     0.00   rad/s Omega    Rotor speed
+  5  1.000   0.000      1.00     0.00       m Ae_pos_x Aero position x  of blade  1 at radius  20.50, global coo.
+  6  1.000   0.000      1.00     0.00       m Ae_pos_y Aero position y  of blade  1 at radius  20.50, global coo.
+  7  1.000   0.000      1.00     0.00       m Ae_pos_z Aero position z  of blade  1 at radius  20.50, global coo.
diff --git a/wetb/flex/tests/test_files/test1/test.int b/wetb/flex/tests/test_files/test1/test.int
new file mode 100644
index 0000000000000000000000000000000000000000..5df18a5d5b28c9b45ec0b48786925a5fb7d9a306
GIT binary patch
literal 11352
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
J6(Ac%{y#QJk9q(A

literal 0
HcmV?d00001

diff --git a/wetb/flex/tests/test_files/test_sensor_info/sensor b/wetb/flex/tests/test_files/test_sensor_info/sensor
new file mode 100644
index 0000000..593044b
--- /dev/null
+++ b/wetb/flex/tests/test_files/test_sensor_info/sensor
@@ -0,0 +1,69 @@
+  Version ID : HAWC2MB 12.3 (beta -rev_TJ6)
+ No   forst  offset  korr. c  Volt    Unit   Navn    Beskrivelse---------------
+   1    1.0    0.0    0.00      1.0    s      Time    Time
+   2    1.0    0.0    0.00      1.0    deg    bea1 an shaft_rot angle
+   3    1.0    0.0    0.00      1.0    rpm    bea1 an shaft_rot angle speed
+   4    1.0    0.0    0.00      1.0    deg    bea2 an pitch1 angle
+   5    1.0    0.0    0.00      1.0    deg/s  bea2 an pitch1 angle speed
+   6    1.0    0.0    0.00      1.0    deg    bea2 an pitch2 angle
+   7    1.0    0.0    0.00      1.0    deg/s  bea2 an pitch2 angle speed
+   8    1.0    0.0    0.00      1.0    deg    bea2 an pitch3 angle
+   9    1.0    0.0    0.00      1.0    deg/s  bea2 an pitch3 angle speed
+  10    1.0    0.0    0.00      1.0    kNm    Mx coo: MomentMx Mbdy:towertop nodenr:   1 coo: tower  tower top -1: below top mass
+  11    1.0    0.0    0.00      1.0    kNm    My coo: MomentMy Mbdy:towertop nodenr:   1 coo: tower  tower top -1: below top mass
+  12    1.0    0.0    0.00      1.0    kNm    Mz coo: MomentMz Mbdy:towertop nodenr:   1 coo: tower  tower top -1: below top mass
+  13    1.0    0.0    0.00      1.0    kN     Fx coo: Force  Fx Mbdy:towertop nodenr:   1 coo: tower  tower top -1: below top mass
+  14    1.0    0.0    0.00      1.0    kN     Fy coo: Force  Fy Mbdy:towertop nodenr:   1 coo: tower  tower top -1: below top mass
+  15    1.0    0.0    0.00      1.0    kN     Fz coo: Force  Fz Mbdy:towertop nodenr:   1 coo: tower  tower top -1: below top mass
+  16    1.0    0.0    0.00      1.0    kNm    Mx coo: MomentMx Mbdy:tower nodenr:   1 coo: tower  tower base flange
+  17    1.0    0.0    0.00      1.0    kNm    My coo: MomentMy Mbdy:tower nodenr:   1 coo: tower  tower base flange
+  18    1.0    0.0    0.00      1.0    kNm    Mz coo: MomentMz Mbdy:tower nodenr:   1 coo: tower  tower base flange
+  19    1.0    0.0    0.00      1.0    kNm    Mx coo: MomentMx Mbdy:towertop nodenr:   2 coo: towertop  yaw bearing
+  20    1.0    0.0    0.00      1.0    kNm    My coo: MomentMy Mbdy:towertop nodenr:   2 coo: towertop  yaw bearing
+  21    1.0    0.0    0.00      1.0    kNm    Mz coo: MomentMz Mbdy:towertop nodenr:   2 coo: towertop  yaw bearing
+  22    1.0    0.0    0.00      1.0    kN     Fx coo: Force  Fx Mbdy:towertop nodenr:   2 coo: towertop  yaw bering
+  23    1.0    0.0    0.00      1.0    kN     Fy coo: Force  Fy Mbdy:towertop nodenr:   2 coo: towertop  yaw bering
+  24    1.0    0.0    0.00      1.0    kN     Fz coo: Force  Fz Mbdy:towertop nodenr:   2 coo: towertop  yaw bering
+  25    1.0    0.0    0.00      1.0    kNm    Mx coo: MomentMx Mbdy:shaft nodenr:   4 coo: shaft  main bearing
+  26    1.0    0.0    0.00      1.0    kNm    My coo: MomentMy Mbdy:shaft nodenr:   4 coo: shaft  main bearing
+  27    1.0    0.0    0.00      1.0    kNm    Mz coo: MomentMz Mbdy:shaft nodenr:   4 coo: shaft  main bearing
+  28    1.0    0.0    0.00      1.0    kNm    Mx coo: MomentMx Mbdy:blade1 nodenr:   3 coo: blade1  blade 1 root
+  29    1.0    0.0    0.00      1.0    kNm    My coo: MomentMy Mbdy:blade1 nodenr:   3 coo: blade1  blade 1 root
+  30    1.0    0.0    0.00      1.0    kNm    Mz coo: MomentMz Mbdy:blade1 nodenr:   3 coo: blade1  blade 1 root
+  31    1.0    0.0    0.00      1.0    kNm    Mx coo: MomentMx Mbdy:blade1 nodenr:  10 coo: local  blade 1 50% local e coo
+  32    1.0    0.0    0.00      1.0    kNm    My coo: MomentMy Mbdy:blade1 nodenr:  10 coo: local  blade 1 50% local e coo
+  33    1.0    0.0    0.00      1.0    kNm    Mz coo: MomentMz Mbdy:blade1 nodenr:  10 coo: local  blade 1 50% local e coo
+  34    1.0    0.0    0.00      1.0    kN     Fx coo: Force  Fx Mbdy:blade1 nodenr:   1 coo: blade1  blade 1 root
+  35    1.0    0.0    0.00      1.0    kN     Fy coo: Force  Fy Mbdy:blade1 nodenr:   1 coo: blade1  blade 1 root
+  36    1.0    0.0    0.00      1.0    kN     Fz coo: Force  Fz Mbdy:blade1 nodenr:   1 coo: blade1  blade 1 root
+  37    1.0    0.0    0.00      1.0    kNm    Mx coo: MomentMx Mbdy:blade2 nodenr:   1 coo: blade2  blade 2 root
+  38    1.0    0.0    0.00      1.0    kNm    My coo: MomentMy Mbdy:blade2 nodenr:   1 coo: blade2  blade 2 root
+  39    1.0    0.0    0.00      1.0    kNm    Mz coo: MomentMz Mbdy:blade2 nodenr:   1 coo: blade2  blade 2 root
+  40    1.0    0.0    0.00      1.0    kNm    Mx coo: MomentMx Mbdy:blade3 nodenr:   1 coo: blade3  blade 3 root
+  41    1.0    0.0    0.00      1.0    kNm    My coo: MomentMy Mbdy:blade3 nodenr:   1 coo: blade3  blade 3 root
+  42    1.0    0.0    0.00      1.0    kNm    Mz coo: MomentMz Mbdy:blade3 nodenr:   1 coo: blade3  blade 3 root
+  43    1.0    0.0    0.00      1.0    m      State p State pos x  Mbdy:towertop E-nr:   1 Z-rel:1.00 coo: global  tower top flange position
+  44    1.0    0.0    0.00      1.0    m      State p State pos y  Mbdy:towertop E-nr:   1 Z-rel:1.00 coo: global  tower top flange position
+  45    1.0    0.0    0.00      1.0    m      State p State pos z  Mbdy:towertop E-nr:   1 Z-rel:1.00 coo: global  tower top flange position
+  46    1.0    0.0    0.00      1.0    m      State p State pos x  Mbdy:blade1 E-nr:  18 Z-rel:1.00 coo: blade1  blade 1 tip pos
+  47    1.0    0.0    0.00      1.0    m      State p State pos y  Mbdy:blade1 E-nr:  18 Z-rel:1.00 coo: blade1  blade 1 tip pos
+  48    1.0    0.0    0.00      1.0    m      State p State pos z  Mbdy:blade1 E-nr:  18 Z-rel:1.00 coo: blade1  blade 1 tip pos
+  49    1.0    0.0    0.00      1.0    m      State p State pos x  Mbdy:blade2 E-nr:  18 Z-rel:1.00 coo: blade2  blade 2 tip pos
+  50    1.0    0.0    0.00      1.0    m      State p State pos y  Mbdy:blade2 E-nr:  18 Z-rel:1.00 coo: blade2  blade 2 tip pos
+  51    1.0    0.0    0.00      1.0    m      State p State pos z  Mbdy:blade2 E-nr:  18 Z-rel:1.00 coo: blade2  blade 2 tip pos
+  52    1.0    0.0    0.00      1.0    m      State p State pos x  Mbdy:blade3 E-nr:  18 Z-rel:1.00 coo: blade3  blade 3 tip pos
+  53    1.0    0.0    0.00      1.0    m      State p State pos y  Mbdy:blade3 E-nr:  18 Z-rel:1.00 coo: blade3  blade 3 tip pos
+  54    1.0    0.0    0.00      1.0    m      State p State pos z  Mbdy:blade3 E-nr:  18 Z-rel:1.00 coo: blade3  blade 3 tip pos
+  55    1.0    0.0    0.00      1.0    m      State p State pos x  Mbdy:blade1 E-nr:  18 Z-rel:1.00 coo: global  blade 1 tip pos
+  56    1.0    0.0    0.00      1.0    m      State p State pos y  Mbdy:blade1 E-nr:  18 Z-rel:1.00 coo: global  blade 1 tip pos
+  57    1.0    0.0    0.00      1.0    m      State p State pos z  Mbdy:blade1 E-nr:  18 Z-rel:1.00 coo: global  blade 1 tip pos
+  58    1.0    0.0    0.00      1.0    m      State p State pos x  Mbdy:blade1 E-nr:  18 Z-rel:1.00 coo: hub1  blade 1 tip pos hub coo
+  59    1.0    0.0    0.00      1.0    m      State p State pos y  Mbdy:blade1 E-nr:  18 Z-rel:1.00 coo: hub1  blade 1 tip pos hub coo
+  60    1.0    0.0    0.00      1.0    m      State p State pos z  Mbdy:blade1 E-nr:  18 Z-rel:1.00 coo: hub1  blade 1 tip pos hub coo
+  61    1.0    0.0    0.00      1.0    m      State p State pos x  Mbdy:blade2 E-nr:  18 Z-rel:1.00 coo: hub2  blade 2 tip pos hub coo
+  62    1.0    0.0    0.00      1.0    m      State p State pos y  Mbdy:blade2 E-nr:  18 Z-rel:1.00 coo: hub2  blade 2 tip pos hub coo
+  63    1.0    0.0    0.00      1.0    m      State p State pos z  Mbdy:blade2 E-nr:  18 Z-rel:1.00 coo: hub2  blade 2 tip pos hub coo
+  64    1.0    0.0    0.00      1.0    m      State p State pos x  Mbdy:blade3 E-nr:  18 Z-rel:1.00 coo: hub3  blade 3 tip pos hub coo
+  65    1.0    0.0    0.00      1.0    m      State p State pos y  Mbdy:blade3 E-nr:  18 Z-rel:1.00 coo: hub3  blade 3 tip pos hub coo
+  66    1.0    0.0    0.00      1.0    m      State p State pos z  Mbdy:blade3 E-nr:  18 Z-rel:1.00 coo: hub3  blade 3 tip pos hub coo
+  67    1.0    0.0    0.00      1.0    rad/s  omega t State_rot omega tz Mbdy:shaft E-nr:   4 Z-rel:1.00 coo: shaft
diff --git a/wetb/flex/tests/test_flex_io.py b/wetb/flex/tests/test_flex_io.py
new file mode 100644
index 0000000..85aff61
--- /dev/null
+++ b/wetb/flex/tests/test_flex_io.py
@@ -0,0 +1,27 @@
+'''
+Created on 11. apr. 2017
+
+@author: mmpe
+'''
+import os
+import unittest
+from wetb import flex
+
+
+tfp = os.path.join(os.path.dirname(__file__), 'test_files/')  # test file path
+class Test(unittest.TestCase):
+
+
+    def test_load(self):
+        time, data, info = flex.load(tfp+"test1/test.int")
+        self.assertEqual(data.shape, (800,7))
+        self.assertEqual(info['attribute_names'][1], "WSP_gl._")
+        self.assertEqual(info['attribute_units'][1], "m/s")
+        self.assertEqual(info['attribute_descriptions'][1], " Free wind speed Vy, gl. coo, of gl. pos 0.75, 0.00, -40.75")
+
+        self.assertAlmostEqual(data[0, 1], 12.037,3)
+
+
+if __name__ == "__main__":
+    #import sys;sys.argv = ['', 'Test.testName']
+    unittest.main()
\ No newline at end of file
diff --git a/wetb/flex/tests/test_sensor.py b/wetb/flex/tests/test_sensor.py
new file mode 100644
index 0000000..3a88479
--- /dev/null
+++ b/wetb/flex/tests/test_sensor.py
@@ -0,0 +1,26 @@
+'''
+Created on 9. sep. 2016
+
+@author: mmpe
+'''
+import os
+import unittest
+from wetb.flex import read_sensor_info
+
+
+tfp = os.path.join(os.path.dirname(__file__), 'test_files/')  # test file path
+class Test(unittest.TestCase):
+
+
+    def test_sensor_load(self):
+        sensor_info = read_sensor_info(tfp + "test_sensor_info/sensor")
+        nr, name, unit, description, _, _ = sensor_info[17]
+        self.assertEqual(nr, 18)
+        self.assertEqual(name, "Mz coo: ")
+        self.assertEqual(unit, "kNm")
+        self.assertEqual(description, "MomentMz Mbdy:tower nodenr: 1 coo: tower tower base flange")
+
+
+if __name__ == "__main__":
+    #import sys;sys.argv = ['', 'Test.test_sensor_load']
+    unittest.main()
-- 
GitLab