From 749d78df80a5146b0b19472ca9d0183c9052a286 Mon Sep 17 00:00:00 2001 From: Theo Waltwood Date: Wed, 17 Apr 2024 17:41:50 +1000 Subject: [PATCH 1/3] Modified parser to recognise a comma delimited lvm format --- data/pickle_only.lvm.pkl | Bin 939 -> 1677 bytes lvm_read.py | 208 +++++++++++++++++++++++++++------------ setup.py | 2 +- tests/test_all.py | 46 ++++++--- 4 files changed, 176 insertions(+), 80 deletions(-) diff --git a/data/pickle_only.lvm.pkl b/data/pickle_only.lvm.pkl index 5aff4915185e5ff7b043d687bf7dc085a4a1bc1f..47db324b2e66c6fe24ffc98b27cc3d131670e7b3 100644 GIT binary patch literal 1677 zcmbtUO>7%Q6i%HqPU3*n0;g8RDrzy*B)jW4_Li#C)^VYAVxpvRHlVVsopEN-?%J$( zfl5SeDI`>yNNt%SE^q)OgaC0xiV#vcaNy7bNQfI0XgL6i*aNLNz?=0>5+g+%n3d+| zn>X{``@T1OZu`}jd%ME6$WnB`dfwIvHs-O*`1+o4qgpvpoJ~$(qvgBU#*W8m7o&I2 zx~7L+y@K7A*>D)$rT{nFYTYkO~W<3h6^VBjP4l2HPbdMeKVehqZ7X6nfl{! zf$1!?810_J(D!S4p<(&91M^t90l+EK#`>g->x}jtt+rs(!!UiKi6bwulhcg$j2Rpr zP$W4sEGfgX3K&1i=x#|pn3WG^)U>STq^vB-%806tD!HthSD8-}^M+?^=Af@Y49CG% zi=AdE(o1?s52o>H!){s-B49KoOAsD(e5dW2!yVo?FG9008g3Q^G$N&QN>0wJ(&&h! zWb-*b%dE${#Wufca893bOiu@`uoVUrFXI=y1}AO1J=}U)2?I-L`WP_agnDtu5h3k? zUlh}%@c4aFTKZQj9)J|L!1>Ym7`A{mEj*vdq#<@CD-|EfGTN~UZH1&fh`6R#49f>$ z?k&DpGd;tDa!RI7x#q$Gb{}GN=M-)=As4(Ej29YqSc}-F9r`Ttyvha$O@=}#47kDZ z?dF-GTEoRdFvYG}gQ)bn+pw#Cy$+@RI!nDV*?t94fBS5E^8ZKBAoLI8ymzLF!5NL! z^X%el%WR6JO5JqZAyzJzPyP)p>`Gl8tCj{{g%?v`9vHzhNJd4-%08q>j*>?=N|5Y{ zijM4!G$s3K?_81>1tSmBgqGY89%$h*6D5)?sWCE2j*;R91A|u%fBX*m4NWYUudm8K zpkLnmbLErIeniXku@7E(|5xNJ?OMBI+(5hjxITE}!uRMjJ@4=Sd=1@YU47=c{CDW` zOZzVET)l;^Xj8u*{_g7KxWBr@ zAeIKm^=*?>v>fbH~X!ZD4 z=l47d_BStmbL?V(^6%dK@TXdU-a5Dc+R{R>RY-KbuG;QX9X0Xa3r0@R*o?fPEtvpi zOFi)QMVhu*@Zmqf|1*s9zSeQ^?X!Y^k+i5pp@$$r;6fikiok_lf+T?p{RC+O7kUa3 h1upazqzYWUCqwdz%q3KGXogZPSKc{FE!CpRfwgBGdQ&%u_&=5zi3Jib4X&+#I`OSZw8=> z9uDV>#Js%JoZ>0oOg-$uiMa(isl`)br)YR{d2_kf|A3e^Ie<}>xrbrO&UT$elNoHbEQDWtk9-jE3)a3lU;*z4$loG zVb4o}sF^&)o1t}zGh@<}_9;O?Z!iKq!qhe;!?1@nrKGYT6{xF+Da`_?%A0X=4YO>0 zr}F}yNp@=&*}t{t-tqkBWw+1vzU_YJcN;#m&ov86niu-n{>75-z3cZrws-hFQOPLh zvweGy`^uK(Z|r>ze-JcG{cNAtCjH@O;v;+Kh+fq-y`Sx+Kb_}!)cnAH#-Zl4>$RWl zV|yA)W%hrt-|cp~Q7-GVJ#Ws`ghGp(_PNg{T(NfQ{A@q5Msyo=QgMvYD@oYKc!?!QV%aQsuc1Pb0Kjwxr@n)H#jvtH#M&W7=etXdH`~? B$p8QV diff --git a/lvm_read.py b/lvm_read.py index 90c05bc..831a76f 100644 --- a/lvm_read.py +++ b/lvm_read.py @@ -7,7 +7,12 @@ import pickle import numpy as np -__version__ = '1.21' +__version__ = '1.30' + + +class LVMFormatError(Exception): + pass + def _lvm_pickle(filename): """ Reads pickle file (for local use) @@ -54,6 +59,69 @@ def _read_lvm_base(filename): return lvm_data +def get_separator(lines): + """ Search the LVM header for the separator header + + :param lines: lines of lvm file + :return separator: separator for this lvm + """ + # Search for separator header + for line in lines: + # Strip new line + line = line.replace('\r', '').replace('\n', '') + if line == 'Separator\tTab': + lines.seek(0) + return '\t' + elif line == 'Separator,Comma': + lines.seek(0) + return ',' + + raise LVMFormatError("Unable to find Separator header") + + +def read_header(lines): + """ Read the LVM header and return relevant information + + :param lines: lines of lvm file + :return lvm_header, data_header: information on lvm data + """ + + separator = get_separator(lines) + + lvm_header = dict() + data_header = dict() + + # First header is the LVM header + header = lvm_header + + for line in lines: + # Strip new line + line = line.replace('\r', '').replace('\n', '') + # Reached end of lvm header -> switch to data header + if header is lvm_header and line.startswith('***End_of_Header***'): + header = data_header + continue + # Reached end of data header -> return both headers + elif line.startswith('***End_of_Header***'): + return lvm_header, data_header + + # Skip blank lines + if line.startswith(separator): + continue + + key, *data = line.split(separator) + + if key == 'Separator': + header[key] = {'Comma': ',', 'Tab': '\t'}[data[0]] + else: + if len(data) == 1: + data = data[0] + header[key] = data + + # Should return from inside for loop + raise LVMFormatError("Failed to parse header") + + def read_lines(lines): """ Read lines of strings. @@ -61,71 +129,86 @@ def read_lines(lines): :return lvm_data: lvm dict """ lvm_data = dict() - lvm_data['Decimal_Separator'] = '.' - data_channels_comment_reading = False - data_reading = False - segment = None - seg_data = [] - first_column = 0 - nr_of_columns = 0 - segment_nr = 0 + + # Read header data + lvm_header, data_header = read_header(lines) + lvm_data['lvm_header'] = lvm_header + lvm_data['data_header'] = data_header + + # Check if Decimal Separator header exists + if 'Decimal_Separator' not in lvm_header: + lvm_header['Decimal_Separator'] = '.' + def to_float(a): try: - return float(a.replace(lvm_data['Decimal_Separator'], '.')) + return float(a.replace(lvm_header['Decimal_Separator'], '.')) except: return np.nan + + # First line after headers should be column names + # Will begin with 'X_Value' + columnNames = next(lines).replace('\r', '').replace('\n', '') + if not columnNames.startswith('X_Value'): + raise LVMFormatError("Failed to read column names") + + data_header['Columns'] = columnNames.split(lvm_header['Separator']) + + # Create the channels from the data header + X_channel = None + + lvm_data['Channels'] = [] + channel_no = 0 + + for i in range(len(data_header['Columns'])): + if data_header['Columns'][i] == 'X_Value': + channel = { + 'Name': data_header['X_Dimension'][channel_no], + 'Data': [], + 'X Channel': None + } + # Set this channel as the X channel for the next channels + X_channel = channel + elif data_header['Columns'][i] == 'Comment': + channel = { + 'Name': 'Comment', + 'Data': [], + } + else: + channel = { + 'Name': data_header['Columns'][i], + 'Samples': data_header['Samples'][channel_no], + 'Date': data_header['Date'][channel_no], + 'Time': data_header['Time'][channel_no], + 'Y Unit': (data_header['Y_Unit_Label'][channel_no] + if 'Y_Unit_Label' in data_header else None), + 'X Dimension': data_header['X_Dimension'][channel_no], + 'X0': data_header['X0'][channel_no], + 'Delta X': data_header['Delta_X'][channel_no], + 'Data': [], + 'X Channel': X_channel + } + channel_no += 1 + + lvm_data['Channels'].append(channel) + + # Read data into channels for line in lines: - line = line.replace('\r', '') - line_sp = line.replace('\n', '').split('\t') - if line_sp[0] in ['***End_of_Header***', 'LabVIEW Measurement']: - continue - elif line in ['\n', '\t\n']: - # segment finished, new segment follows - segment = dict() - lvm_data[segment_nr] = segment - data_reading = False - segment_nr += 1 - continue - elif data_reading: # this was moved up, to speed up the reading - seg_data.append([to_float(a) for a in - line_sp[first_column:(nr_of_columns + 1)]]) - elif segment == None: - if len(line_sp) == 2: - key, value = line_sp - lvm_data[key] = value - elif segment != None: - if line_sp[0] == 'Channels': - key, value = line_sp[:2] - nr_of_columns = len(line_sp) - 1 - segment[key] = eval(value) - if nr_of_columns < segment['Channels']: - nr_of_columns = segment['Channels'] - data_channels_comment_reading = True - elif line_sp[0] == 'X_Value': - seg_data = [] - segment['data'] = seg_data - if lvm_data['X_Columns'] == 'No': - first_column = 1 - segment['Channel names'] = line_sp[first_column:(nr_of_columns + 1)] - data_channels_comment_reading = False - data_reading = True - elif data_channels_comment_reading: - key, values = line_sp[0], line_sp[1:(nr_of_columns + 1)] - if key in ['Delta_X', 'X0', 'Samples']: - segment[key] = [eval(val.replace(lvm_data['Decimal_Separator'], '.')) if val else np.nan for val in - values] - else: - segment[key] = values - elif len(line_sp) == 2: - key, value = line_sp - segment[key] = value - - if not lvm_data[segment_nr - 1]: - del lvm_data[segment_nr - 1] - segment_nr -= 1 - lvm_data['Segments'] = segment_nr - for s in range(segment_nr): - lvm_data[s]['data'] = np.asarray(lvm_data[s]['data']) + line = line.replace('\r', '').replace('\n', '') + line_sp = line.split(lvm_header['Separator']) + for i in range(len(lvm_data['Channels'])): + ch = lvm_data['Channels'][i] + dp = line_sp[i] if len(line_sp) > i else '' # fill in blank values + if ch['Name'] == 'Comment': + ch['Data'].append(dp if dp else '') + else: + ch['Data'].append(to_float(dp)) + + for ch in lvm_data['Channels']: + ch['Data'] = np.asarray(ch['Data']) + + lvm_data['data'] = np.column_stack( + [ch['Data'] for ch in lvm_data['Channels'] if ch['Name'] != 'Comment']) + return lvm_data @@ -185,11 +268,10 @@ def read(filename, read_from_pickle=True, dump_file=True): if __name__ == '__main__': import matplotlib.pyplot as plt - da = read('data/with_comments.lvm',read_from_pickle=False) + da = read('data/with_comments.lvm', read_from_pickle=False) #da = read('data\with_empty_fields.lvm',read_from_pickle=False) print(da.keys()) print('Number of segments:', da['Segments']) plt.plot(da[0]['data']) plt.show() - diff --git a/setup.py b/setup.py index b1e9bb8..3bf57bc 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ #from distutils.core import setup, Extension from setuptools import setup, Extension setup(name='lvm_read', - version='1.21', + version='1.30', author='Janko Slavič et al.', author_email='janko.slavic@fs.uni-lj.si', url='https://github.com/ladisk/lvm_read', diff --git a/tests/test_all.py b/tests/test_all.py index a0d5f9d..84e8042 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -3,42 +3,55 @@ """ import numpy as np -import sys, os +import sys +import os import time myPath = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, myPath + '/../') from lvm_read import read + def test_short_lvm(): data = read('./data/pickle_only.lvm') - np.testing.assert_equal(data[0]['data'][0,0],0.914018) + np.testing.assert_equal(data['data'][0, 1], 0.914018) data = read('./data/short.lvm', read_from_pickle=False) - np.testing.assert_equal(data[0]['data'][0,0],0.914018) + np.testing.assert_equal(data['data'][0, 1], 0.914018) data = read('./data/short.lvm', read_from_pickle=True) - np.testing.assert_equal(data[0]['data'][0, 0], 0.914018) + np.testing.assert_equal(data['data'][0, 1], 0.914018) + + data = read('./data/short_new_line_end.lvm', + read_from_pickle=True, dump_file=False) + np.testing.assert_equal(data['data'][0, 1], 0.914018) - data = read('./data/short_new_line_end.lvm', read_from_pickle=True, dump_file=False) - np.testing.assert_equal(data[0]['data'][0, 0], 0.914018) def test_with_empty_fields_lvm(): - data = read('./data/with_empty_fields.lvm', read_from_pickle=False, dump_file=False) - np.testing.assert_equal(data[0]['data'][0,7],-0.011923) + data = read('./data/with_empty_fields.lvm', + read_from_pickle=False, dump_file=False) + np.testing.assert_equal(data['data'][0, 7], -0.011923) + def test_with_multi_time_column_lvm(): - data = read('./data/multi_time_column.lvm', read_from_pickle=False, dump_file=False) - np.testing.assert_allclose(data[0]['data'][0],\ - np.array([0.000000,-0.035229,0.000000,0.532608])) + data = read('./data/multi_time_column.lvm', + read_from_pickle=False, dump_file=False) + np.testing.assert_allclose(data['data'][0], + np.array([0.000000, -0.035229, + 0.000000, 0.532608])) + def test_no_decimal_separator(): - data = read('./data/no_decimal_separator.lvm', read_from_pickle=False, dump_file=False) - np.testing.assert_equal(data[0]['data'][0,1],-0.008807) + data = read('./data/no_decimal_separator.lvm', + read_from_pickle=False, dump_file=False) + np.testing.assert_equal(data['data'][0, 1], -0.008807) + def test_several_comments(): - data = read('./data/with_comments.lvm', read_from_pickle=False, dump_file=False) - np.testing.assert_equal(data[0]['data'][0,1],1.833787) + data = read('./data/with_comments.lvm', + read_from_pickle=False, dump_file=False) + np.testing.assert_equal(data['data'][0, 1], 1.833787) + def timing_on_long_short_lvm(): N = 5 @@ -48,9 +61,10 @@ def timing_on_long_short_lvm(): toc = time.time() print(f'Average time: {(toc-tic)/N:3.1f}s') + if __name__ == '__mains__': np.testing.run_module_suite() if __name__ == '__main__': test_several_comments() - #timing_on_long_short_lvm() \ No newline at end of file + # timing_on_long_short_lvm() From d53e12f7fc8da6ce367ae3cdf817e6d35710dc04 Mon Sep 17 00:00:00 2001 From: Theo Waltwood Date: Mon, 6 May 2024 17:05:09 +1000 Subject: [PATCH 2/3] Updated parser to a specification driven approach --- Showcase lvm_read.ipynb | 256 ++++++++++++------- data/pickle_only.lvm.pkl | Bin 1677 -> 1265 bytes data/with_empty_fields.lvm | 2 +- lvm_format.py | 253 +++++++++++++++++++ lvm_read.py | 486 ++++++++++++++++++++++++++++--------- setup.py | 2 +- tests/test_all.py | 21 +- 7 files changed, 808 insertions(+), 212 deletions(-) create mode 100644 lvm_format.py diff --git a/Showcase lvm_read.ipynb b/Showcase lvm_read.ipynb index be127c1..2cdb4c9 100644 --- a/Showcase lvm_read.ipynb +++ b/Showcase lvm_read.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -10,7 +10,8 @@ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", - "import urllib" + "import urllib\n", + "import datetime" ] }, { @@ -39,7 +40,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -52,12 +53,12 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "#%%timeit -n1\n", - "lvm = lvm_read.read('.\\\\data\\\\'+filename, read_from_pickle=False)" + "lvm = lvm_read.read('./data/'+filename, read_from_pickle=False)" ] }, { @@ -70,22 +71,22 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "dict_keys(['Decimal_Separator', 'Writer_Version', 'Reader_Version', 'Separator', 'Multi_Headings', 'X_Columns', 'Time_Pref', 'Operator', 'Date', 'Time', 0, 'Segments'])" + "dict_keys(['Description', 'Multi_Headings', 'Operator', 'Project', 'Reader_Version', 'Separator', 'Decimal_Separator', 'Time_Pref', 'X_Columns', 'LabVIEW Measurement', 'Writer_Version', 'Date', 'Time'])" ] }, - "execution_count": 24, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "lvm.keys()" + "lvm['file_header'].keys()" ] }, { @@ -93,27 +94,27 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "E.g.: number of segments in the lvm file:" + "E.g.: time of measurement:" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "1" + "'Tue Feb 19 09:51:39 2013'" ] }, - "execution_count": 25, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "lvm['Segments']" + "datetime.datetime.combine(lvm['file_header']['Date'], lvm['file_header']['Time']).ctime()" ] }, { @@ -134,129 +135,220 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'Channels': 2,\n", - " 'Samples': [10, 10, nan],\n", - " 'Date': ['2013/02/19', '2013/02/19', ''],\n", - " 'Time': ['09:51:40,7271890640258789063', '09:51:40,7271890640258789063', ''],\n", - " 'Y_Unit_Label': ['Newtons', 'm/s^2', ''],\n", - " 'X_Dimension': ['Time', 'Time', ''],\n", - " 'X0': [0.0, 0.0, nan],\n", - " 'Delta_X': [3.90625e-05, 3.90625e-05, nan],\n", - " 'data': array([[0.914018, 1.204792],\n", - " [0.537321, 1.208403],\n", - " [0.616905, 1.213915],\n", - " [0.895449, 1.212205],\n", - " [0.57446 , 1.222088],\n", - " [0.516099, 1.218223],\n", - " [1.046658, 1.213408],\n", - " [0.39407 , 1.221011],\n", - " [0.741586, 1.211888],\n", - " [0.680572, 1.212775]]),\n", - " 'Channel names': ['Excitation (Trigger)', 'Response (Trigger)', 'Comment']}" + "{'Header': {'Notes': '',\n", + " 'Test_Name': '',\n", + " 'Test_Numbers': '',\n", + " 'Test_Series': '',\n", + " 'UUT_M/N': '',\n", + " 'UUT_Name': '',\n", + " 'UUT_S/N': '',\n", + " 'X_Dimension': ['Time', 'Time'],\n", + " 'X_Unit_Label': 'Default SI Unit',\n", + " 'Y_Dimension': 'Electric Potential',\n", + " 'Y_Unit_Label': ['Newtons', 'm/s^2'],\n", + " 'Channels': 2,\n", + " 'Samples': [10, 10],\n", + " 'Date': [datetime.date(2013, 2, 19), datetime.date(2013, 2, 19)],\n", + " 'Time': [datetime.time(9, 51, 40, 727189), datetime.time(9, 51, 40, 727189)],\n", + " 'X0': [0, 0],\n", + " 'Delta_X': [3.90625e-05, 3.90625e-05],\n", + " 'Columns': ['X_Value',\n", + " 'Excitation (Trigger)',\n", + " 'Response (Trigger)',\n", + " 'Comment'],\n", + " 'Y_Labels': ['Excitation (Trigger)', 'Response (Trigger)', 'Comment']},\n", + " 'Data': [[[0.0,\n", + " 3.90625e-05,\n", + " 7.8125e-05,\n", + " 0.0001171875,\n", + " 0.00015625,\n", + " 0.0001953125,\n", + " 0.000234375,\n", + " 0.0002734375,\n", + " 0.0003125,\n", + " 0.0003515625],\n", + " [0.914018,\n", + " 0.537321,\n", + " 0.616905,\n", + " 0.895449,\n", + " 0.57446,\n", + " 0.516099,\n", + " 1.046658,\n", + " 0.39407,\n", + " 0.741586,\n", + " 0.680572]],\n", + " [[0.0,\n", + " 3.90625e-05,\n", + " 7.8125e-05,\n", + " 0.0001171875,\n", + " 0.00015625,\n", + " 0.0001953125,\n", + " 0.000234375,\n", + " 0.0002734375,\n", + " 0.0003125,\n", + " 0.0003515625],\n", + " [1.204792,\n", + " 1.208403,\n", + " 1.213915,\n", + " 1.212205,\n", + " 1.222088,\n", + " 1.218223,\n", + " 1.213408,\n", + " 1.221011,\n", + " 1.211888,\n", + " 1.212775]]],\n", + " 'Comments': ['', '', '', '', '', '', '', '', '', '']}" ] }, - "execution_count": 26, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "lvm[0]" + "lvm['segments'][0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Each segment has its own header, data, and comments section" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The headers of the segments look like:" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'Channels': 2,\n", - " 'Samples': [10, 10, nan],\n", - " 'Date': ['2013/02/19', '2013/02/19', ''],\n", - " 'Time': ['09:51:40,7271890640258789063', '09:51:40,7271890640258789063', ''],\n", - " 'Y_Unit_Label': ['Newtons', 'm/s^2', ''],\n", - " 'X_Dimension': ['Time', 'Time', ''],\n", - " 'X0': [0.0, 0.0, nan],\n", - " 'Delta_X': [3.90625e-05, 3.90625e-05, nan],\n", - " 'data': array([[0.914018, 1.204792],\n", - " [0.537321, 1.208403],\n", - " [0.616905, 1.213915],\n", - " [0.895449, 1.212205],\n", - " [0.57446 , 1.222088],\n", - " [0.516099, 1.218223],\n", - " [1.046658, 1.213408],\n", - " [0.39407 , 1.221011],\n", - " [0.741586, 1.211888],\n", - " [0.680572, 1.212775]]),\n", - " 'Channel names': ['Excitation (Trigger)', 'Response (Trigger)', 'Comment']}" + "{'Notes': '',\n", + " 'Test_Name': '',\n", + " 'Test_Numbers': '',\n", + " 'Test_Series': '',\n", + " 'UUT_M/N': '',\n", + " 'UUT_Name': '',\n", + " 'UUT_S/N': '',\n", + " 'X_Dimension': ['Time', 'Time'],\n", + " 'X_Unit_Label': 'Default SI Unit',\n", + " 'Y_Dimension': 'Electric Potential',\n", + " 'Y_Unit_Label': ['Newtons', 'm/s^2'],\n", + " 'Channels': 2,\n", + " 'Samples': [10, 10],\n", + " 'Date': [datetime.date(2013, 2, 19), datetime.date(2013, 2, 19)],\n", + " 'Time': [datetime.time(9, 51, 40, 727189), datetime.time(9, 51, 40, 727189)],\n", + " 'X0': [0, 0],\n", + " 'Delta_X': [3.90625e-05, 3.90625e-05],\n", + " 'Columns': ['X_Value',\n", + " 'Excitation (Trigger)',\n", + " 'Response (Trigger)',\n", + " 'Comment'],\n", + " 'Y_Labels': ['Excitation (Trigger)', 'Response (Trigger)', 'Comment']}" ] }, - "execution_count": 27, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "lvm[0]" + "lvm['segments'][0]['header']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Each Y Column comes paired with its X column e.g.," ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "array([[0.914018, 1.204792],\n", - " [0.537321, 1.208403],\n", - " [0.616905, 1.213915],\n", - " [0.895449, 1.212205],\n", - " [0.57446 , 1.222088],\n", - " [0.516099, 1.218223],\n", - " [1.046658, 1.213408],\n", - " [0.39407 , 1.221011],\n", - " [0.741586, 1.211888],\n", - " [0.680572, 1.212775]])" + "[[0.0,\n", + " 3.90625e-05,\n", + " 7.8125e-05,\n", + " 0.0001171875,\n", + " 0.00015625,\n", + " 0.0001953125,\n", + " 0.000234375,\n", + " 0.0002734375,\n", + " 0.0003125,\n", + " 0.0003515625],\n", + " [1.204792,\n", + " 1.208403,\n", + " 1.213915,\n", + " 1.212205,\n", + " 1.222088,\n", + " 1.218223,\n", + " 1.213408,\n", + " 1.221011,\n", + " 1.211888,\n", + " 1.212775]]" ] }, - "execution_count": 28, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "lvm[0]['data']" + "lvm['segments'][0]['data'][1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plots can be generated via:" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 42, "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ - "
" + "
" ] }, - "metadata": { - "needs_background": "light" - }, + "metadata": {}, "output_type": "display_data" } ], "source": [ - "plt.plot(lvm[0]['data']);" + "for seg in lvm['segments']:\n", + " labels = [f\"{l} ({u})\" for l,u in zip(seg['Header']['Y_Labels'], seg['Header']['Y_Unit_Label'])]\n", + " for ch, l in zip(seg['data'], labels):\n", + " plt.plot(*ch, label=l)\n", + "\n", + " plt.legend()\n", + " plt.xlabel(seg['Header']['X_Dimension'][0])\n", + " plt.ylabel(seg['Header']['Y_Dimension'])\n", + " plt.show()" ] }, { @@ -270,9 +362,9 @@ "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python 3", + "display_name": "lvm_read", "language": "python", - "name": "python3" + "name": "lvm_read" }, "language_info": { "codemirror_mode": { @@ -284,7 +376,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.6" + "version": "3.12.3" }, "latex_envs": { "bibliofile": "biblio.bib", @@ -309,5 +401,5 @@ } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/data/pickle_only.lvm.pkl b/data/pickle_only.lvm.pkl index 47db324b2e66c6fe24ffc98b27cc3d131670e7b3..5a78b9496a17c2c68af57c7367f306dcf8894fe5 100644 GIT binary patch literal 1265 zcmcIiOK1~O6peLik|tJKD+<*T#7_lLtD2QN7gF4K-%L~DXY0(soOkYh_jB&;Egc${ zeC~6c*RoJr)j-JtJpd+mi6~epQ_b6I&fv_)S$VUg*{ZSwT~s4u@v|(jCl4mIb0%jc zyW*VrTg+S!q->s*Sb3WpO6dU85)sH2FPD!67>$tP&X{h8q0AucQ@_s*x9%@(hSscOwhsbD%fkPHkkjYK#Eck zDR2OPHYVXr5|Cfu4U+$5$y|Plw;?C;^Xxn(xjtx|6yn6L!|F5GktanBG+ zDp9P>aE0x{;d!M_{eXp&O1q)j3JL=nXLF*ErZ~Gud}l=PoP|1z0Dr+hO=IM;b z?EE5Oe>(*W&- z9YH(uCzTGWIXLI4#=|MqredcfwFy(rWWZd(S!Ekod29`eNerR8yM%pqdRn@@W#icd zIPS_&k>2~r)O!1-iy(4Up{$yb)=#HuMu@zmz^u3=p36jDRWQ{FkJisbUSIIpShhJo z{Jad@7Qe{X?$i&4iTt{5^hxYYGm+okzt{ic`bQ#vzH#ycpGy zlW)~To_Mg(>0@0)ezED`{vmBPkw1)9o?m)wfXMG3)qibE4uS|TGW_TIzi)p!`C%kP z7%Q6i%HqPU3*n0;g8RDrzy*B)jW4_Li#C)^VYAVxpvRHlVVsopEN-?%J$( zfl5SeDI`>yNNt%SE^q)OgaC0xiV#vcaNy7bNQfI0XgL6i*aNLNz?=0>5+g+%n3d+| zn>X{``@T1OZu`}jd%ME6$WnB`dfwIvHs-O*`1+o4qgpvpoJ~$(qvgBU#*W8m7o&I2 zx~7L+y@K7A*>D)$rT{nFYTYkO~W<3h6^VBjP4l2HPbdMeKVehqZ7X6nfl{! zf$1!?810_J(D!S4p<(&91M^t90l+EK#`>g->x}jtt+rs(!!UiKi6bwulhcg$j2Rpr zP$W4sEGfgX3K&1i=x#|pn3WG^)U>STq^vB-%806tD!HthSD8-}^M+?^=Af@Y49CG% zi=AdE(o1?s52o>H!){s-B49KoOAsD(e5dW2!yVo?FG9008g3Q^G$N&QN>0wJ(&&h! zWb-*b%dE${#Wufca893bOiu@`uoVUrFXI=y1}AO1J=}U)2?I-L`WP_agnDtu5h3k? zUlh}%@c4aFTKZQj9)J|L!1>Ym7`A{mEj*vdq#<@CD-|EfGTN~UZH1&fh`6R#49f>$ z?k&DpGd;tDa!RI7x#q$Gb{}GN=M-)=As4(Ej29YqSc}-F9r`Ttyvha$O@=}#47kDZ z?dF-GTEoRdFvYG}gQ)bn+pw#Cy$+@RI!nDV*?t94fBS5E^8ZKBAoLI8ymzLF!5NL! z^X%el%WR6JO5JqZAyzJzPyP)p>`Gl8tCj{{g%?v`9vHzhNJd4-%08q>j*>?=N|5Y{ zijM4!G$s3K?_81>1tSmBgqGY89%$h*6D5)?sWCE2j*;R91A|u%fBX*m4NWYUudm8K zpkLnmbLErIeniXku@7E(|5xNJ?OMBI+(5hjxITE}!uRMjJ@4=Sd=1@YU47=c{CDW` zOZzVET)l;^Xj8u*{_g7KxWBr@ zAeIKm^=*?>v>fbH~X!ZD4 z=l47d_BStmbL?V(^6%dK@TXdU-a5Dc+R{R>RY-KbuG;QX9X0Xa3r0@R*o?fPEtvpi zOFi)QMVhu*@Zmqf|1*s9zSeQ^?X!Y^k+i5pp@$$r;6fikiok_lf+T?p{RC+O7kUa3 h1upazqzYW 6: + msec = msec[:6] + return datetime.strptime(f"{t}.{msec}", f"{lvm.TIME_FORMAT}.%f").time() + else: + return datetime.strptime(value, lvm.TIME_FORMAT).time() + # Bool: bool should contain either "Yes" or "No" which should map to + # True or False + elif vtype == 'bool': + if value == "Yes": + return True + elif value == "No": + return False + else: + raise None + # None: Header has no value + elif vtype == None: + return '' + +def read_segment_data(lines, file_header, seg_header, x0=None): + """ Reads the data portion of the segment. The returned list will contain + an array for each X column and its related Y columns. E.g., + [[[x1,x2,...], [y1,y2,...]], [[x1,x2,...], [y1,y2,...]], comments] + The X values will be infered from the seg_header when X_Columns is "No" + + :param lines: lines of file + :param file_header: header for file + :param seg_header: header for segment + :param x0: list of starting X values. Used for same-header segments + :return seg_data, comments: list of data arrays, list of comments + """ + + samples = seg_header['Samples'] + x_columns = file_header['X_Columns'] + decsep = file_header['Decimal_Separator'] + + # Create number of empty lists equal to channels + # Each of the channels should contain an x and y list + seg_data = [[[], []] for _ in range(seg_header['Channels'])] + comments = [] + + # Fill in x0 from segment header + if x0 is None: + x0 = seg_header['X0'] + + # Check if a new segment exists + line = next(lines, None) + if line is None: + return None, None + + sample = 0 + max_samples = max(samples) + + # Read data into channels + while sample < max_samples \ + and line is not None \ + and line not in ['\n', '\r\n', '']: + + line = line.replace('\r', '').replace('\n', '') + values = line.split(file_header['Separator']) + + # One X Column means all x_values are the same + if x_columns == "One": + x_value = _lvm_float(values[0], decsep) + + # Multi will have X column for every Y Column + if x_columns == "Multi": + columns = seg_header['Channels'] * 2 + else: # One or No will have a X Column at the front + columns = seg_header['Channels'] + 1 + + for i in range(seg_header['Channels']): + ch = seg_data[i] + if x_columns == "One": + y_value = _lvm_float(values[i + 1], decsep) + elif x_columns == "Multi": + x_value = _lvm_float(values[i * 2], decsep) + y_value = _lvm_float(values[i * 2 + 1], decsep) + elif x_columns == "No": + x_value = x0[i] + seg_header['Delta_X'][i] *\ + sample + y_value = _lvm_float(values[i + 1], decsep) + + # Check if data set has ended + if y_value == None: + continue + + ch[0].append(x_value) + ch[1].append(y_value) + + # Add comments + comments.append(values[columns] if len(values) > columns else '') + + # Get next line + line = next(lines, None) + sample += 1 + + if line is None and sample != max_samples: + raise LVMFormatError("EOF before finished segment") + + for ch in seg_data: + ch = [np.asarray(ch[0]),np.asarray(ch[1])] + + return seg_data, comments + +def read_segment_header(lines, file_header): + """ Reads the segment header + + :param lines: lines of lvm file + :param file_header: header for file + :return seg_header: header for segment. None if no new segment to be read + """ + # Setup headers with default values + seg_header = {h: lvm.SEGMENT_HEADERS[h]['default'] + for h in lvm.SEGMENT_HEADERS if not lvm.SEGMENT_HEADERS[h]['required']} + + seg_started = False + for line in lines: # Strip new line line = line.replace('\r', '').replace('\n', '') - if line == 'Separator\tTab': - lines.seek(0) - return '\t' - elif line == 'Separator,Comma': - lines.seek(0) - return ',' - raise LVMFormatError("Unable to find Separator header") + # Reached end of segment header -> return header + if line.startswith(lvm.END_OF_HEADER): + validate_header(seg_header, lvm.SEGMENT_HEADERS) + # First line after headers should be column names + # Will begin with 'X_Value' + columnNames = next(lines).replace('\r', '').replace('\n', '') + if not columnNames.startswith('X_Value'): + raise LVMFormatError("Failed to read column names") + seg_header['Columns'] = columnNames.split(file_header['Separator']) + seg_header['Y_Labels'] = [c for c in seg_header['Columns'] + if c not in ['X_Value', 'Comment']] + return seg_header -def read_header(lines): - """ Read the LVM header and return relevant information + # Skip blank lines + if line.startswith(file_header['Separator']) or line == '': + continue + # Enter Special block + elif line.startswith(lvm.SPECIAL_BLOCK_START): + skip_special_block(lines) + # will return upon reaching line containing SPECIAL_BLOCK_END + continue + + key, *values = line.split(file_header['Separator']) + if key not in lvm.SEGMENT_HEADERS: + raise LVMFormatError(f"Invalid File Header: {key}") + + vtype = lvm.SEGMENT_HEADERS[key]['type'] + + data = [] + + for v in values: + if v != '': + data.append(parse_value(v, vtype, file_header['Separator'], file_header['Decimal_Separator'])) + + if not data: + raise LVMFormatError(f"Error parsing value at:\n{line}") + elif len(data) == 1: + seg_header[key] = data[0] + elif 'Channels' in seg_header and len(data) == seg_header['Channels']: + seg_header[key] = data + else: + raise LVMFormatError( + "Mismatch between number of Channels (" + f"{seg_header['Channels'] if 'Channels' in seg_header else 'Not Found'}" + f") and header {key} data {data} ({len(data)})" + ) + + # If the segment wasn't started (i.e., reached EOF before new data) + # this is expected as this function will be run after the last segment + # is read. Return None to signify EOF + if not seg_started: + return None + # Otherwise if we started reading segment header data but never reached + # END_OF_HEADER a format error has occured. Raise accordingly + raise LVMFormatError("Failed to parse segment header") + +def read_segment(lines, file_header, seg_header=None, x0=None): + """ Reads a data segment :param lines: lines of lvm file - :return lvm_header, data_header: information on lvm data + :param file_header: header for file + :param seg_header: used for passing previous seg_header + :return lvm_segment: Segment of parsed data """ - separator = get_separator(lines) + # Parse Segment Header + if seg_header is None or file_header['Multi_Headings']: + seg_header = read_segment_header(lines, file_header) - lvm_header = dict() - data_header = dict() + # if segment header is still None this means EOF and no more segments + # so return None + if seg_header is None: + return None - # First header is the LVM header - header = lvm_header + data, comments = read_segment_data(lines, file_header, seg_header, x0) + if data is None: + return None + return { + 'header': seg_header, + 'data': data, + 'comments': comments + } + +def read_file_header(lines): + """ Reads the LVM file header and return relevant information + + :param lines: lines of lvm file + :return file_header: information on lvm file + """ + + # Scan the file for the Separator header + separator, buffer = get_separator(lines) + + # Setup headers with default values + file_header = {h: lvm.FILE_HEADERS[h]['default'] + for h in lvm.FILE_HEADERS if not lvm.FILE_HEADERS[h]['required']} + + # Create a generator to go through buffer first and then consume lines + def next_line(lines, buf): + for L in buffer: + yield L + for L in lines: + yield L + + lines = next_line(lines, buffer) + + # Check for magic identifier + identifier = next(lines) + if not identifier.startswith(lvm.FILE_MAGIC_IDENTIFIER): + raise LVMFormatError("Did not find magic identifier" + f" {lvm.FILE_MAGIC_IDENTIFIER} at start of file") for line in lines: # Strip new line line = line.replace('\r', '').replace('\n', '') - # Reached end of lvm header -> switch to data header - if header is lvm_header and line.startswith('***End_of_Header***'): - header = data_header - continue - # Reached end of data header -> return both headers - elif line.startswith('***End_of_Header***'): - return lvm_header, data_header + + # Reached end of file header -> return header + if line.startswith(lvm.END_OF_HEADER): + validate_header(file_header, lvm.FILE_HEADERS) + # Replace Separator header with character instead of word + file_header['Separator'] = separator + return file_header # Skip blank lines - if line.startswith(separator): + elif line.startswith(separator): continue - key, *data = line.split(separator) + # Enter Special block + elif line.startswith(lvm.SPECIAL_BLOCK_START): + skip_special_block(lines) + # will return upon reaching line containing SPECIAL_BLOCK_END + continue - if key == 'Separator': - header[key] = {'Comma': ',', 'Tab': '\t'}[data[0]] - else: - if len(data) == 1: - data = data[0] - header[key] = data + line_sp = line.split(separator) + if len(line_sp) != 2: + raise LVMFormatError(f"Error parsing key,value pair from '{line_sp}'") + key, value = line_sp + if key not in lvm.FILE_HEADERS: + raise LVMFormatError(f"Invalid File Header: {key}") - # Should return from inside for loop - raise LVMFormatError("Failed to parse header") + vtype = lvm.FILE_HEADERS[key]['type'] + + data = parse_value(value, vtype, separator, file_header['Decimal_Separator']) + + if data is None: + raise LVMFormatError(f"Error parsing value at:\n{line}") + file_header[key] = data + + # Should return from inside for loop + raise LVMFormatError("Failed to parse file header") def read_lines(lines): """ Read lines of strings. @@ -131,87 +438,22 @@ def read_lines(lines): lvm_data = dict() # Read header data - lvm_header, data_header = read_header(lines) - lvm_data['lvm_header'] = lvm_header - lvm_data['data_header'] = data_header + file_header = read_file_header(lines) + lvm_data['file_header'] = file_header - # Check if Decimal Separator header exists - if 'Decimal_Separator' not in lvm_header: - lvm_header['Decimal_Separator'] = '.' + lvm_data['segments'] = [] - def to_float(a): - try: - return float(a.replace(lvm_header['Decimal_Separator'], '.')) - except: - return np.nan - - # First line after headers should be column names - # Will begin with 'X_Value' - columnNames = next(lines).replace('\r', '').replace('\n', '') - if not columnNames.startswith('X_Value'): - raise LVMFormatError("Failed to read column names") - - data_header['Columns'] = columnNames.split(lvm_header['Separator']) - - # Create the channels from the data header - X_channel = None - - lvm_data['Channels'] = [] - channel_no = 0 - - for i in range(len(data_header['Columns'])): - if data_header['Columns'][i] == 'X_Value': - channel = { - 'Name': data_header['X_Dimension'][channel_no], - 'Data': [], - 'X Channel': None - } - # Set this channel as the X channel for the next channels - X_channel = channel - elif data_header['Columns'][i] == 'Comment': - channel = { - 'Name': 'Comment', - 'Data': [], - } - else: - channel = { - 'Name': data_header['Columns'][i], - 'Samples': data_header['Samples'][channel_no], - 'Date': data_header['Date'][channel_no], - 'Time': data_header['Time'][channel_no], - 'Y Unit': (data_header['Y_Unit_Label'][channel_no] - if 'Y_Unit_Label' in data_header else None), - 'X Dimension': data_header['X_Dimension'][channel_no], - 'X0': data_header['X0'][channel_no], - 'Delta X': data_header['Delta_X'][channel_no], - 'Data': [], - 'X Channel': X_channel - } - channel_no += 1 - - lvm_data['Channels'].append(channel) + segment = read_segment(lines, file_header) - # Read data into channels - for line in lines: - line = line.replace('\r', '').replace('\n', '') - line_sp = line.split(lvm_header['Separator']) - for i in range(len(lvm_data['Channels'])): - ch = lvm_data['Channels'][i] - dp = line_sp[i] if len(line_sp) > i else '' # fill in blank values - if ch['Name'] == 'Comment': - ch['Data'].append(dp if dp else '') - else: - ch['Data'].append(to_float(dp)) - - for ch in lvm_data['Channels']: - ch['Data'] = np.asarray(ch['Data']) - - lvm_data['data'] = np.column_stack( - [ch['Data'] for ch in lvm_data['Channels'] if ch['Name'] != 'Comment']) + while segment != None: + lvm_data['segments'].append(segment) + x_final = [ch[0][-1] if ch[0] else x0 + for ch, x0 in zip(segment['data'], + segment['header']['X0'])] + segment = read_segment(lines, file_header, segment['header'], x_final) return lvm_data - def read_str(str): """ Parse the string as the content of lvm file. @@ -230,7 +472,7 @@ def read_str(str): >>> lvm.keys() #explore the dictionary dict_keys(['', 'Date', 'X_Columns', 'Time_Pref', 'Time', 'Writer_Version',... """ - return read_lines(str.splitlines(keepends=True)) + return read_lines(iter(str.splitlines(keepends=True))) def read(filename, read_from_pickle=True, dump_file=True): @@ -253,7 +495,7 @@ def read(filename, read_from_pickle=True, dump_file=True): f.write(sample_file) >>> lvm = lvm_read.read('short.lvm') #read the file >>> lvm.keys() #explore the dictionary - dict_keys(['', 'Date', 'X_Columns', 'Time_Pref', 'Time', 'Writer_Version',... + dict_keys(['file_header','segments']) """ lvm_data = _lvm_pickle(filename) if read_from_pickle and lvm_data: @@ -271,7 +513,15 @@ def read(filename, read_from_pickle=True, dump_file=True): da = read('data/with_comments.lvm', read_from_pickle=False) #da = read('data\with_empty_fields.lvm',read_from_pickle=False) print(da.keys()) - print('Number of segments:', da['Segments']) - - plt.plot(da[0]['data']) - plt.show() + print('Number of segments:', len(da['segments'])) + + for seg in da['Segments']: + labels = [f"{l} ({u})" for l,u in zip(seg['header']['Y_Labels'], + seg['header']['Y_Unit_Label'])] + for ch, l in zip(seg['Data'], labels): + plt.plot(*ch, label=l) + + plt.legend() + plt.xlabel(seg['header']['X_Dimension'][0]) + plt.ylabel(seg['header']['Y_Dimension']) + plt.show() diff --git a/setup.py b/setup.py index 3bf57bc..c233672 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ #from distutils.core import setup, Extension from setuptools import setup, Extension setup(name='lvm_read', - version='1.30', + version='1.40', author='Janko Slavič et al.', author_email='janko.slavic@fs.uni-lj.si', url='https://github.com/ladisk/lvm_read', diff --git a/tests/test_all.py b/tests/test_all.py index 84e8042..9a63e8f 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -14,43 +14,44 @@ def test_short_lvm(): data = read('./data/pickle_only.lvm') - np.testing.assert_equal(data['data'][0, 1], 0.914018) + np.testing.assert_equal(data['segments'][0]['data'][0][1][0], 0.914018) data = read('./data/short.lvm', read_from_pickle=False) - np.testing.assert_equal(data['data'][0, 1], 0.914018) + np.testing.assert_equal(data['segments'][0]['data'][0][1][0], 0.914018) data = read('./data/short.lvm', read_from_pickle=True) - np.testing.assert_equal(data['data'][0, 1], 0.914018) + np.testing.assert_equal(data['segments'][0]['data'][0][1][0], 0.914018) data = read('./data/short_new_line_end.lvm', read_from_pickle=True, dump_file=False) - np.testing.assert_equal(data['data'][0, 1], 0.914018) + np.testing.assert_equal(data['segments'][0]['data'][0][1][0], 0.914018) def test_with_empty_fields_lvm(): data = read('./data/with_empty_fields.lvm', read_from_pickle=False, dump_file=False) - np.testing.assert_equal(data['data'][0, 7], -0.011923) + np.testing.assert_equal(data['segments'][0]['data'][6][1][0], -0.011923) def test_with_multi_time_column_lvm(): data = read('./data/multi_time_column.lvm', read_from_pickle=False, dump_file=False) - np.testing.assert_allclose(data['data'][0], - np.array([0.000000, -0.035229, - 0.000000, 0.532608])) + np.testing.assert_equal(data['segments'][0]['data'][0][0][0], 0.000000) + np.testing.assert_equal(data['segments'][0]['data'][0][1][0], -0.035229) + np.testing.assert_equal(data['segments'][0]['data'][1][0][0], 0.000000) + np.testing.assert_equal(data['segments'][0]['data'][1][1][0], 0.532608) def test_no_decimal_separator(): data = read('./data/no_decimal_separator.lvm', read_from_pickle=False, dump_file=False) - np.testing.assert_equal(data['data'][0, 1], -0.008807) + np.testing.assert_equal(data['segments'][0]['data'][0][1][0], -0.008807) def test_several_comments(): data = read('./data/with_comments.lvm', read_from_pickle=False, dump_file=False) - np.testing.assert_equal(data['data'][0, 1], 1.833787) + np.testing.assert_equal(data['segments'][0]['data'][0][1][0], 1.833787) def timing_on_long_short_lvm(): From a5ea7b7670438e2e536ca259a50fa629da8efba7 Mon Sep 17 00:00:00 2001 From: Theo Waltwood Date: Mon, 6 May 2024 17:10:21 +1000 Subject: [PATCH 3/3] Fixed jupyter notebook having incorrect header name --- Showcase lvm_read.ipynb | 46 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Showcase lvm_read.ipynb b/Showcase lvm_read.ipynb index 2cdb4c9..3333a00 100644 --- a/Showcase lvm_read.ipynb +++ b/Showcase lvm_read.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 14, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -40,7 +40,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -53,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -71,16 +71,16 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "dict_keys(['Description', 'Multi_Headings', 'Operator', 'Project', 'Reader_Version', 'Separator', 'Decimal_Separator', 'Time_Pref', 'X_Columns', 'LabVIEW Measurement', 'Writer_Version', 'Date', 'Time'])" + "dict_keys(['Description', 'Multi_Headings', 'Operator', 'Project', 'Reader_Version', 'Separator', 'Decimal_Separator', 'Time_Pref', 'X_Columns', 'Writer_Version', 'Date', 'Time'])" ] }, - "execution_count": 13, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -99,7 +99,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -108,7 +108,7 @@ "'Tue Feb 19 09:51:39 2013'" ] }, - "execution_count": 19, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -135,13 +135,13 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'Header': {'Notes': '',\n", + "{'header': {'Notes': '',\n", " 'Test_Name': '',\n", " 'Test_Numbers': '',\n", " 'Test_Series': '',\n", @@ -162,8 +162,8 @@ " 'Excitation (Trigger)',\n", " 'Response (Trigger)',\n", " 'Comment'],\n", - " 'Y_Labels': ['Excitation (Trigger)', 'Response (Trigger)', 'Comment']},\n", - " 'Data': [[[0.0,\n", + " 'Y_Labels': ['Excitation (Trigger)', 'Response (Trigger)']},\n", + " 'data': [[[0.0,\n", " 3.90625e-05,\n", " 7.8125e-05,\n", " 0.0001171875,\n", @@ -203,10 +203,10 @@ " 1.221011,\n", " 1.211888,\n", " 1.212775]]],\n", - " 'Comments': ['', '', '', '', '', '', '', '', '', '']}" + " 'comments': ['', '', '', '', '', '', '', '', '', '']}" ] }, - "execution_count": 20, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -231,7 +231,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -258,10 +258,10 @@ " 'Excitation (Trigger)',\n", " 'Response (Trigger)',\n", " 'Comment'],\n", - " 'Y_Labels': ['Excitation (Trigger)', 'Response (Trigger)', 'Comment']}" + " 'Y_Labels': ['Excitation (Trigger)', 'Response (Trigger)']}" ] }, - "execution_count": 24, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -279,7 +279,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -307,7 +307,7 @@ " 1.212775]]" ] }, - "execution_count": 37, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -325,7 +325,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -341,13 +341,13 @@ ], "source": [ "for seg in lvm['segments']:\n", - " labels = [f\"{l} ({u})\" for l,u in zip(seg['Header']['Y_Labels'], seg['Header']['Y_Unit_Label'])]\n", + " labels = [f\"{l} ({u})\" for l,u in zip(seg['header']['Y_Labels'], seg['header']['Y_Unit_Label'])]\n", " for ch, l in zip(seg['data'], labels):\n", " plt.plot(*ch, label=l)\n", "\n", " plt.legend()\n", - " plt.xlabel(seg['Header']['X_Dimension'][0])\n", - " plt.ylabel(seg['Header']['Y_Dimension'])\n", + " plt.xlabel(seg['header']['X_Dimension'][0])\n", + " plt.ylabel(seg['header']['Y_Dimension'])\n", " plt.show()" ] },