function data = lvm_import(filename,verbose)
% LVM_IMPORT imports data from a LabView LVM file
% DATA = LVM_IMPORT(FILENAME,VERBOSE) returns the data from a LVM (.lvm)
% ASCII text file created by LabView.
%
% FILENAME The name of the .lvm file, with or without ".lvm" extension
%
% DATA The data found in the LVM file. DATA is a structure with fields
% corresponding to the Segments in the file (see below) and
% LVM file header information
%
% VERBOSE How many messages you want to see. Default is 1 (few messages)
% 0 = silent, 2 = display file information and all messages
%
%
% This function inports the contents of a text-formatted LabView Measurement
% file (LVM, extension ".lvm"). A LVM file can have multiple Segments, so
% that multiple measurements can be combined in a single file. The output
% variable DATA will be a structure with fields named 'Segment1',
% 'Segment2', etc. Each Segment field is a structure with details about
% the data in the Segment and the actual data in the field 'data'. The
% column labels and units are stored as cell arrays that correspond to the
% columns in the array of data.
% The size of the data array depends on the type of x-axis data that is
% stored in the LVM file and the number of channels (N_channels).
% There are three cases:
% 1) No x-data is included in the file ('No')
% The data array will have N_channels columns (one column per channel of data).
% 2) One column of x-data is included in the file ('One')
% The first column of the data array will be the x-values, and the data
% array will have N_channels+1 columns.
% 3) Each channel has its own x-data ('Multi')
% Each channel has two columns, one for x-values, and one for data. These
% are paired in the data array, and the data array will have N_channels*2
% columns. For example, in a Segment with 4 channels, columns 1,3,5,7
% will be the x-values for the data in columns 2,4,6,8.
%
% Note: because MATLAB only works with a "." decimal separator, importing
% large LVM files that use a "," (or other character) will be noticeably
% slower. Use a "." decimal separator to avoid this issue.
%
% The LVM file specification is available at:
% http://zone.ni.com/devzone/cda/tut/p/id/4139
%
%
% Example:
%
% Use the following command to read in the data from a file containing two
% Segments:
%
% >> d=lvm_import('testfile.lvm');
%
% Importing testfile.lvm:
%
% Import complete. 2 Segments found.
%
% >> d
% d =
% x_columns: 'One'
% user: 'hopcroft'
% Description: 'Pressure, Flowrate, Heat, Power, Analog Voltage, Pump on, Temp'
% date: '2008/03/26'
% time: '12:18:02.156616'
% clock: [2008 3 26 12 18 2.156616]
% Segment1: [1x1 struct]
% Segment2: [1x1 struct]
%
% >> d.Segment1
% ans =
% Notes: 'Some notes regarding this data set'
% num_channels: 8
% y_units: {8x1 cell}
% x_units: {8x1 cell}
% X0: [8x1 double]
% Delta_X: [8x1 double]
% column_labels: {9x1 cell}
% data: [211x9 double]
% Comment: 'This data rulz'
%
% >> d.Segment1.column_labels{2}
% ans =
% Thermocouple1
%
% >> plot(d.Segment1.data(:,1),d.Segment1.data(:,2));
% >> xlabel(d.Segment1.column_labels{1});
% >> ylabel(d.Segment1.column_labels{2});
%
%
%
% M.A. Hopcroft
% < mhopeng at gmail.com >
%
% MH Mar2012
% v2.0 fix "string bug" related to comma-separated decimals
% handle multiple Special Headers correctly
% fix help comments
% increment version number to math LabView LVM writer
% MH Sep2011
% v1.3 handles LVM Writer version 2.0 (files with decimal separator)
% Note: if you want to work with older files with a non-"." decimal
% separator character, change the value of "data.Decimal_Separator"
% MH Sep2010
% v1.2 bugfixes for "Special" header in LVM files.
% (Thanks to Jo Blow <bobbyjoe23928@gmail.com> for suggestions)
% MH Apr2010
% v1.1 use case-insensitive comparisons to maintain compatibility with
% NI LVM Writer version 1.00
%
% MH MAY2009
% v1.02 Add filename input
% MH SEP2008
% v1.01 Fix comments, add Cells
% v1.00 Handle all three possibilities for X-columns (No,One,Multi)
% Handle LVM files with no header
% MH AUG2008
% v0.92 extracts Comment for each Segment
% MH APR2008
% v0.9 initial version
%
% display header?
if nargin < 2, verbose = 1; end
%% Open the data file
% open and verify the file
% ask for filename if not provided already
if nargin < 1
filename=input(' Enter the name of the .lvm file: ','s');
end
fid=fopen(filename);
if fid ~= -1, % then file exists
fclose(fid);
else
filename=strcat(filename,'.lvm');
fid=fopen(filename);
if fid ~= -1, % then file exists
fclose(fid);
else
error(['File not found in current directory! (' pwd ')']);
end
end
% open the validated file
fid=fopen(filename);
if verbose >= 1, fprintf(1,'\nlvm_import v2.0\n Importing %s:\n\n',filename); end
if verbose >= 2, fprintf(1,' File Header:\n'); end
% % read the file
% process the file header
% first, is it really a LVM file?
linein=fgetl(fid);
if verbose >= 2, fprintf(1,'%s\n',linein); end
if ~strcmp(sscanf(linein,'%s'),'LabVIEWMeasurement')
try
data.Segment1.data = dlmread(filename,'\t');
if verbose >= 1, fprintf(1,'This file appears to be an LVM file with no header.\n'); end
if verbose >= 1, fprintf(1,'Data was copied, but no other information is available.\n'); end
return
catch fileEx
error('This does not appear to be a text-format LVM file (no header).');
end
end
%% Process file header
% The file header contains several fields with useful information
% default values
data.Decimal_Separator = '.';
% File header contains date, time, etc.
% Also the file delimiter and decimal separator (LVM v2.0)
while 1
% get a line from the file
linein=fgetl(fid);
% handle spurious carriage returns
if isempty(linein), linein=fgetl(fid); end
if verbose >= 2, fprintf(1,'%s\n',linein); end
% what is the tag for this line?
t_in = textscan(linein,'%s');
if isempty(t_in{1})
tag='notag';
else
tag = t_in{1}{1};
end
% exit when we reach the end of the header
if strcmpi(tag,'***End_of_Header***')
break
end
% get the value corresponding to the tag
if ~strcmp(tag,'notag')
v_in = textscan(linein,'%*s %s','delimiter','\t','whitespace','','MultipleDelimsAsOne', 1);
val = v_in{1}{1};
end
switch tag
case 'Date'
data.date = val;
case 'Time'
data.time = val;
case 'Operator'
data.user = val;
case 'Description'
%d_in = textscan(linein,'%*s %s','delimiter','\t','whitespace','');
data.Description = val;
case 'Project'
%d_in = textscan(linein,'%*s %s','delimiter','\t','whitespace','');
data.Project = val;
case 'Separator'
if strcmp(val,'Tab')
separator_char='\t';
elseif strcmp(val,'Comma')
separator_char=',';
end
case 'X_Columns'
data.x_columns = val;
case 'Decimal_Separator'
data.Decimal_Separator = val;
end
end
if isfield(data,'time') && isfield(data,'date')
dt = textscan(data.date,'%d','delimiter','/');
tm = textscan(data.time,'%d','delimiter',':');
if length(tm{1})==3
data.clock=[dt{1}(1) dt{1}(2) dt{1}(3) tm{1}(1) tm{1}(2) tm{1}(3)];
elseif length(tm{1})==2
data.clock=[dt{1}(1) dt{1}(2) dt{1}(3) tm{1}(1) tm{1}(2) 0];
else
data.clock=[dt{1}(1) dt{1}(2) dt{1}(3) 0 0 0];
end
end
%% Process segments
% process data segments in a loop until finished
segnum = 0;
while 1
segnum = segnum +1;
fieldnm = ['Segment' num2str(segnum)];
%% - Segment header
if verbose >= 2, fprintf(1,'\n Segment %d Header:\n\n',segnum); end
while 1
% get a line from the file
linein=fgetl(fid);
% handle spurious carriage returns/blank lines/end of file
while isempty(linein), linein=fgetl(fid); end
if feof(fid), break; end
% ignore "special segments"
if strcmpi(linein,'***Start_Special***')
special_seg = 1;
while special_seg
if verbose >= 2, fprintf(1,'[Special Segment ignored]\n%s\n',linein); end
while 1 % process lines until we find the end of the special segment
% get a line from the file
linein=fgetl(fid);
% handle spurious carriage returns
if isempty(linein), linein=fgetl(fid); end
% test for end of file
if linein==-1, break; end
if verbose >= 2, fprintf(1,'%s\n',linein); end
if strcmpi(linein,'***End_Special***')
if verbose >= 2, fprintf(1,'\n'); end
break
end
end
% get the next line and proceed with file
linein=fgetl(fid);
% handle spurious carriage returns/blank lines/end of file
while isempty(linein), linein=fgetl(fid); end
if feof(fid), break; end
if ~strcmpi(linein,'***Start_Special***')
special_seg = 0;
end
end
end
%if verbose >= 2, fprintf(1,'%s\n',linein); end
% what is the tag for this line?
t_in = textscan(linein,'%s');
if isempty(t_in{1})
tag='notag';
else
tag = t_in{1}{1};
end
% exit when we reach the end of the header
if strcmpi(tag,'***End_of_Header***')
break
end
% if ~strcmp(tag,'notag')
% v_in = textscan(linein,'%*s %s','delimiter','\t','whitespace','');
% val = v_in{1}{1};
% end
switch tag
case 'Notes'
%d_in = textscan(linein,'%*s %s','delimiter','\t','whitespace','');
d_in = linein;
data.(fieldnm).Notes=d_in;
case 'Test_Name'
%d_in = textscan(linein,'%*s %s','delimiter','\t','whitespace','');
d_in = linein;
data.(fieldnm).Test_Name = d_in; %d_in{1}{1};
case 'Channels'
numchan = textscan(linein,'%*s %d');
data.(fieldnm).num_channels = numchan{1};
case 'Y_Unit_Label'
Y_units = textscan(linein,'%s','delimiter',separator_char);
data.(fieldnm).y_units=Y_units{1};
data.(fieldnm).y_units(1)=[];
case 'Y_Dimension'
Y_Dim = textscan(linein,'%s','delimiter',separator_char);
data.(fieldnm).y_type=Y_Dim{1};
data.(fieldnm).y_type(1)=[];
case 'X_Unit_Label'
X_units = textscan(linein,'%s','delimiter',separator_char);
data.(fieldnm).x_units=X_units{1};
data.(fieldnm).x_units(1)=[];
case 'X_Dimension'
X_Dim = textscan(linein,'%s','delimiter',separator_char);
data.(fieldnm).x_type=X_Dim{1};
data.(fieldnm).x_type(1)=[];
case 'X0'
[Xnought, val]=strtok(linein); %#ok<*ASGLU>
if ~strcmp(data.Decimal_Separator,'.')
val = strrep(val,data.Decimal_Separator,'.');
end
data.(fieldnm).X0 = sscanf(val,'%e');
case 'Delta_X' %,
%[Delta_X, cnt, err, ni] = sscanf(linein,'%s',1);
[Delta_X, val]=strtok(linein);
%data.(fieldnm).delta_X = sscanf(linein(ni:end),'%e');
if ~strcmp(data.Decimal_Separator,'.')
val = strrep(val,data.Decimal_Separator,'.');
end
data.(fieldnm).delta_X = sscanf(val,'%e');
end
end % end reading segment header loop
% test for end of file
if linein==-1
if verbose >= 2, fprintf(1,' [End of File; No Segment %d]\n\n',segnum); end
segnum = segnum-1;
break;
end
% after each segment header is the row of column labels
linein=fgetl(fid);
Y_labels = textscan(linein,'%s','delimiter',separator_char);
data.(fieldnm).column_labels=Y_labels{1};
%data.col_labels(1)=[];
if verbose > 1
fprintf(1,' Data Columns:\n');
for i=1:length(data.(fieldnm).column_labels)
fprintf(1,'%s\t',data.(fieldnm).column_labels{i});
end
fprintf(1,'\n');
end
%% - Get data from this segment
if verbose >= 2, fprintf(1,'\n Importing data from Segment %d...',segnum); end
% How many columns have we?
switch data.x_columns
case 'No'
numdatacols = data.(fieldnm).num_channels;
case 'One'
numdatacols = data.(fieldnm).num_channels;
case 'Multi'
numdatacols = data.(fieldnm).num_channels*2 - 1;
end
% handle case of not using periods (aka "dot" or ".") for decimal point separators
% (LVM version 2.0+)
if ~strcmp(data.Decimal_Separator,'.')
if verbose >= 2, fprintf(1,'\n (using decimal separator "%s")\n',data.Decimal_Separator); end
% create a format string for reading data as strings
fs = '%s'; for i=1:numdatacols+1, fs = [fs ' %s']; end %#ok<AGROW>
% read data from file
rawdata = textscan(fid,fs,'delimiter',separator_char);
% save first row comment as The Comment for this segment
data.(fieldnm).Comment = rawdata{size(rawdata,2)}{1};
% MATLAB only understands ".", so convert strings to "."-based numbers
for i=1:length(rawdata)
for j=1:length(rawdata{i})
rawdata{i}(j)=strrep(rawdata{i}(j),data.Decimal_Separator,'.');
end
% convert string inputs to numerical values
% NB: this step will be slow for large data sets
rawdata{i}=str2double(rawdata{i});
end
else
% create a format string for reading data as numbers
fs = '%f'; for i=1:numdatacols, fs = [fs ' %f']; end %#ok<AGROW>
% add one more column for the comment field
fs = [fs ' %s']; %#ok<AGROW>
% read the data from file
rawdata = textscan(fid,fs,'delimiter',separator_char);
% save first row comment as The Comment for this segment
data.(fieldnm).Comment = rawdata{size(rawdata,2)}{1};
end
% consolidate data into a simple array, ignore comments
data.(fieldnm).data=rawdata{1};
for i=2:numdatacols+1
data.(fieldnm).data=[data.(fieldnm).data rawdata{i}];
end
% If we have a "No X data" file, strip the first column (it is NaN)
if strcmp(data.x_columns,'No')
data.(fieldnm).data=data.(fieldnm).data(:,2:end);
end
if verbose >= 2, fprintf(1,' complete.\n'); end
end % end process segment
if verbose >= 1, fprintf(1,' Import complete. File has %s X-Column(s) and %d Data Segments.\n\n',data.x_columns,segnum); end
% close the file
fclose(fid);
return