Code covered by the BSD License  

Highlights from
Convert TDMS (v2)

from Convert TDMS (v2) by Grant Lohsen
Import a TDMS v2 file as a matlab object (ob).

[data, index]=convertTDMS(filename);
function [data, index]=convertTDMS(filename);

%convertTDMS - function to convert Labview TDMS data files into .mat files.
%   If called with no input, user selects from file open dialog box.  A
%   .mat file with the same filename as the TDMS file is automatically
%   written to the same directory (warning - will over write .mat file of
%   the same name.
%
%   TDMS format is based on information provided by National Instruments
%   at:    http://zone.ni.com/devzone/cda/tut/p/id/5696
%
% convertTDMS(filename)
%
%       Inputs:
%               filename - filename to be converted.  If not supplied, user
%                 is provided dialog box to open file.  Can be a cell array
%                 of files for bulk conversion.
%
%       Outputs:
%               ob - structure with all of the data objects
%               index - index of the information in ob
%

%---------------------------------------------
%Brad Humphreys - v1.0 2008-04-23
%ZIN Technologies
%---------------------------------------------
%---------------------------------------------
%Brad Humphreys - v1.1 2008-07-3
%ZIN Technologies
%-Added abilty for timestamp to be a raw data type, not just meta data.
%-Addressed an issue with having a default nsmaples entry for new objects.
%-Added Error trap if file name not found.
%-Corrected significant problem where it was assumed that once an object
%    existsed, it would in in every subsequent segement.  This is not true.
%---------------------------------------------

%---------------------------------------------
%Grant Lohsen - v1.2 2009-11-15
%Georgia Tech Research Institute
%-Converts TDMS v2 files
%Folks, it's not pretty but I don't have time to make it pretty. Enjoy.
%---------------------------------------------


%---------------------------------------------
%Jeff Sitterle - v1.3 2010-01-10
%Georgia Tech Research Institute
%Modified to return all information stored in the TDMS file to inlcude
%name, start time, start time offset, samples per read, total samples, unit
%description, and unit string.  Also provides event time and event
%description in text form
%Vast speed improvement as save was the previous longest task
%---------------------------------------------

%---------------------------------------------
%Grant Lohsen - v1.4 2009-04-15
%Georgia Tech Research Institute
%Reads file header info and stores in the Root Structure.
%---------------------------------------------


dispPerComp = 0;   %set to 1 to display percentage complete


conver='1.4';    %conversion version number

%startingdir=cd; %Get the starting directory

if nargin==0                %If file is not provided,prompt user
    [filename,pathname,filterindex] = uigetfile({'*.*',  'All Files (*.*)'},'Choose File');
    filename=fullfile(pathname,filename);
end

%Create filelist
if iscell(filename)         %If it is a group of files
    numfiles=max(size(filename));
    infilename=filename;
else
    numfiles=1;             %If only one file name is provided
    infilename{1}=filename;
end



for fnum=1:numfiles
    disp([datestr(now,13) ' Begining conversion of:  '  infilename{fnum}])
    
    bytesInfile = dir(infilename{fnum});    %Get size of file.  Needed for later estimation of variable size.
    
    if isempty(bytesInfile)
        error(['Error: ' infilename{fnum} ' not Found']);
    end
    filesize = bytesInfile.bytes;
    
    %Initialize variables for each file conversion
    index.names=[];
    ob=[];
    %firstRawData=1;
    segCnt=0;
    
    fid=fopen(infilename{fnum});
    rawDataInThisSeg = [];
    fseek(fid, 0, 1);
    eoff = ftell(fid);
    fseek(fid, 0, -1);
    perComp = 0;
    while (ftell(fid) ~= eoff)
 %   while ~feof(fid) %does not work
        if dispPerComp == 1
            if (100*ftell(fid)/eoff) > (perComp + 10) 
                disp(sprintf('Percent Complete: %2.1f%%', (100*ftell(fid)/eoff)))
                perComp = (100*ftell(fid)/eoff);
            end
        end
        Ttag=fread(fid,1,'uint8');
        Dtag=fread(fid,1,'uint8');
        Stag=fread(fid,1,'uint8');
        mtag=fread(fid,1,'uint8');
        if Ttag==84 & Dtag==68 & Stag==83 & mtag==109
            segCnt=segCnt+1;
            if ~isempty(rawDataInThisSeg)
            rawDataInThisSeg(1:length(rawDataInThisSeg))=0;      % Reset the raw data indicator
            end
            
            %ToC Field
            Toc=fread(fid,1,'uint32');
            kTocMetaData=bitget(Toc,2);
            kTocNewObject=bitget(Toc,3);
            kTocRawData=bitget(Toc,4);
            
            %Segment
            vernum=fread(fid,1,'uint32');
            if vernum ~= 4713 
                    sprintf('Warning: This code is designed for version 4713 of the labview TDMS format.\nYou are attempting to import a file from version %s . Aborting Now', num2str(vernum))
                return
            end
            
            segLength=fread(fid,1,'uint64');
            
            metaRawLength=fread(fid,1,'uint64');
            
            %% Process Meta Data
            if kTocMetaData                                     %If there is meta data in this segment
                numNewObjInSeg=fread(fid,1,'uint32');
                
                for q=1:numNewObjInSeg
                    
                    obLength=fread(fid,1,'uint32');             %Get the length of the objects name
                    obname=[fread(fid,obLength,'uint8=>char')]';%Get the objects name
                    
                    %Fix Object Name
                    if strcmp(obname,'/')
                        obname='Root';
                    else
                        obname=fixcharformatlab(obname);
                    end
                    
                    if ~isfield(ob,obname)                         %If the object does not already exist, create it
                        index.names{end+1}=obname;
                        ob.(obname)=[];
                        obnum=max(size(index.names));               %Get the object number
                        newob(obnum)=1;                             %Brand new object
                    else                                            %if it does exist, get it's index and object number
                        newob(obnum)=0;
                        obnum=find(strcmp(index.names,obname)==1,1,'last');
                    end
                    
                    rawdataindex=fread(fid,1,'uint32');             %Get the raw data Index
                    if rawdataindex==0                              %No raw data assigned to this object in this segment
                        index.entry(obnum)=0;
                        index.dataType(obnum)=1;
                        index.arrayDim(obnum)=0;
                        index.nValues(obnum)=0;
                        index.byteSize(obnum)=0;
                        rawDataInThisSeg(obnum)=0;
                    elseif rawdataindex+1==2^32                     %Objects raw data index matches previous index - no changes
                        if ~isfield(index,'entry')                   %The root object will always have a FFFFFFFF entry
                            rawDataInThisSeg=0;
                        else
                            if max(size(index.entry))<obnum         %In case an object besides the root is added that has no dat but
                                rawDataInThisSeg=0;                    %reports using previous
                            end
                        end
                    else                                            %Get new object information
                        index.entry(obnum)=rawdataindex;
                        index.dataType(obnum)=fread(fid,1,'uint32');
                        index.arrayDim(obnum)=fread(fid,1,'uint32');
%                         disp('nvals');
%                         tango = ftell(fid)
%                         if tango == 1633574
%                         disp('halto')
%                         end
                        
                        index.nValues(obnum)=fread(fid,1,'uint64');
                        if index.dataType(obnum)==32                %If the datatype is a string
                            index.byteSize(obnum)=fread(fid,1,'uint64');
                        else
                            index.byteSize(obnum)=0;
                        end
                        rawDataInThisSeg(obnum)=1;
                    end
                    
                    
                    numProps=fread(fid,1,'uint32');
                    for p=1:numProps
                        propNameLength=fread(fid,1,'uint32');
                        propsName=[fread(fid,propNameLength,'uint8=>char')]';
                        propsName=fixcharformatlab(propsName);
                        propsDataType=fread(fid,1,'uint32');
                        propExists=isfield(ob.(obname),propsName);
                        dataExists=isfield(ob.(obname),'data');
                        
                        if dataExists                                               %Get number of data samples for the object in this segment
                            %nsamps=max(size(ob.(obname).data));
                            
                            
                            nsamps=ob.(cname).nsamples+1;
                            
                        else
                            nsamps=0;
                            estNumSeg=floor(filesize/(20+segLength)*1.2);            %Estimate # of Segements.  20 is the number of bytes prior to the segLength Read
                        end
                        
                        if propsDataType==32                                         %If the data type is a string
                            propsValueLength=fread(fid,1,'uint32');
                            propsValue=fread(fid,propsValueLength,'uint8=>char');
                            if propExists
                                cnt=ob.(obname).(propsName).cnt+1;
                                ob.(obname).(propsName).cnt=cnt;
                                ob.(obname).(propsName).value{cnt}=propsValue;
                                ob.(obname).(propsName).samples(cnt)=nsamps;
                            else
                                if strcmp(obname, 'Root')
                                    %header data
                                    %ob.(obname).(propsName).cnt=1;
                                    
                                    ob.(obname).(propsName) = propsValue'; %fixes the annoying habit of the char array being vertical 
                                    %ob.(obname).(propsName).samples(1)=nsamps;

                                else
                                    %group.channel data
                                    ob.(obname).(propsName).cnt=1;
                                    ob.(obname).(propsName).value{estNumSeg}=NaN;
                                    ob.(obname).(propsName).samples(estNumSeg)=0;
                                    ob.(obname).(propsName).value{1}=propsValue;
                                    ob.(obname).(propsName).samples(1)=nsamps;
                                end
                            end
                        else                                                        %Numeric Data type
                            if propsDataType==68                                     %If the data type is a timestamp
                                tsec=fread(fid,1,'uint64')/2^64+fread(fid,1,'uint64');   %time since Jan-1-1904 in seconds
                                propsValue=tsec/86400+695422-5/24;                   %/864000 convert to days; +695422 days from Jan-0-0000 to Jan-1-1904
                            else
                                matType=LV2MatlabDataType(propsDataType);
                                propsValue=fread(fid,1,matType);
                            end
                            if propExists
                                cnt=ob.(obname).(propsName).cnt+1;
                                ob.(obname).(propsName).cnt=cnt;
                                ob.(obname).(propsName).value(cnt)=propsValue;
                                ob.(obname).(propsName).samples(cnt)=nsamps;
                            else
                                ob.(obname).(propsName).cnt=1;
                                ob.(obname).(propsName).value(estNumSeg)=NaN;
                                ob.(obname).(propsName).samples(estNumSeg)=0;
                                ob.(obname).(propsName).value(1)=propsValue;
                                ob.(obname).(propsName).samples(1)=nsamps;
                            end
                        end
                        
                    end
                    
                end
            end
            
            %% Process Raw Data
            
            
            
            
        else
            if ~(isempty(Ttag) & isempty(Dtag) & isempty(Stag) & isempty(mtag))  %On last read, all will be empty
                %error('Unexpected bit stream in file: %s  at segment %d (dec: %d %d %d %d)',infilename{fnum}, segCnt+1, Ttag, Dtag, Stag, mtag);
                fseek(fid, -4, 0);
                %disp('set');
                %ftell(fid)
            end
        end
        
        
        
        for r=1:max(size(index.names))                                      %Loop through the index
            
            if newob(r) & rawDataInThisSeg(r)                               %If brand new object and new raw data, preallocate data arrays
                %                     firstRawData=0;
                newob(r)=0;
                estNumSeg=filesize/(20+segLength);                              %Estimate # of Segements.  20 is the number of bytes prior to the segLength Read
                
                %for b=1:max(size(index.names))
                cname=cell2mat(index.names(r));
                nEstSamples=floor(1.2*estNumSeg*index.nValues(r));
                if index.dataType(r)~=32 | index.dataType(r)~=68             %If the data is numeric type
                    ob.(cname).data(1:(nEstSamples),1)=NaN;
                else                                                         %If the data is string type
                    ob.(cname).data{nEstSamples}=NaN;
                end
                ob.(cname).nsamples=0;
                %end
            end
            if rawDataInThisSeg(r)
                nvals=index.nValues(r);
                if index.dataType(r)~=68                                    %If not a timestamp data type
                    [data cnt]=fread(fid,nvals,LV2MatlabDataType(index.dataType(r)));
                else                                                        %If a timestamp
                    clear data
                    for dcnt=1:nvals
                        tsec=fread(fid,1,'uint64')/2^64+fread(fid,1,'uint64');   %time since Jan-1-1904 in seconds
                        data(1,dcnt)=tsec/86400+695422-5/24;
                    end
                    cnt=dcnt;
                end
                cname=cell2mat(index.names(r));
                if isfield(ob.(cname),'nsamples')
                    ssamples=ob.(cname).nsamples;
                else
                    ssamples=0;
                end
                
                %                     ssamples=ob.(cname).nsamples;
                if index.dataType(r)~=32                                    %If the data is numeric type
                        ob.(cname).data(ssamples+1:ssamples+cnt,1)=data;    
                else                                                        %If the data is string type
                    
                    ob.(cname).data(ssamples+1:ssamples+cnt,1)=data;
                    
                end
                ob.(cname).nsamples=ssamples+cnt;
            end
        end
        %rawDataInThisSeg(1:end)=0;      % Reset the raw data indicator
    end
end


fclose(fid);

%% Clean up preallocated arrays   (preallocation required for speed)
for y=1:max(size(index.names))
    cname=cell2mat(index.names(y));
    if isfield(ob.(cname),'nsamples')
        nsamples=ob.(cname).nsamples;
        if nsamples>0       %Remove any excess from preallocation of data
            if index.dataType(y)~=32
                ob.(cname).data=ob.(cname).data(1:nsamples,1);                  %If the data is numeric type
            else
                ob.(cname).data=ob.(cname).data(1:nsamples,1);                  %If the data is string type
            end
            
            proplist=fieldnames(ob.(cname));    %Remove any excess from preallocation of properties
            for isaac=1:size(proplist,1)
                if isfield(ob.(cname).(proplist{isaac}),'cnt')
                    cnt=ob.(cname).(proplist{isaac}).cnt;
                    ob.(cname).(proplist{isaac}).value=ob.(cname).(proplist{isaac}).value(1:cnt);
                    ob.(cname).(proplist{isaac}).samples=ob.(cname).(proplist{isaac}).samples(1:cnt);
                    ob.(cname).(proplist{isaac})=rmfield(ob.(cname).(proplist{isaac}),'cnt');
                end
            end
            
        end
    end
end



ob.index=index;
ob.conver=conver;
data = postProcess(ob);
% cd(startingdir);
end

function DataStructure = postProcess(ob)

%Modified to return all information stored in the TDMS file to inlcude
%name, start time, start time offset, samples per read, total samples, unit
%description, and unit string.  Also provides event time and event
%description in text form

%Jeff Sitterle
%January 10, 2010

DataStructure.Root = [];
DataStructure.MeasuredData.Name = [];
DataStructure.MeasuredData.Data = [];
DataStructure.Events.Name = [];
DataStructure.Events.Data = [];
%DataStructure.RawOutput = ob;
varNameMask = '';
cntData = 1;
cntEvent = 1;

for i = 1:length(ob.index.names)
    cname=cell2mat(ob.index.names(i));
    if strcmp(cname, 'Root')
        DataStructure.Root = ob.(cname);
        
    end
    if isfield(ob.(cname),'data')
        if strcmp(varNameMask, 'Events')
            DataStructure.Events(cntEvent).Name = char(regexprep(ob.index.names(i), varNameMask, '', 'ignorecase'));
            
            if strcmp(DataStructure.Events(cntEvent).Name, 'Description')
                event_string = char(ob.(cname).data');
                seperator = event_string(1:4);
                locations = findstr(seperator, event_string);
                num_events = max(size(locations));
                for j = 1:num_events
                    if j < num_events
                        DataStructure.Events(cntEvent).Data(j,:) = cellstr(event_string(locations(j)+4:locations(j+1)-1));
                    else
                        DataStructure.Events(cntEvent).Data(j,:) = cellstr(event_string(locations(j)+4:max(size(event_string))));
                    end
                end
            else
                DataStructure.Events(cntEvent).Data = ob.(cname).data;
            end
            cntEvent = cntEvent + 1;
        
        else
            
            DataStructure.MeasuredData(cntData).Name = char(regexprep(ob.index.names(i), varNameMask, '', 'ignorecase'));
            DataStructure.MeasuredData(cntData).Start_Time = ob.(cname).wf_start_time.value;
            DataStructure.MeasuredData(cntData).Start_Time_Offset = ob.(cname).wf_start_offset.value;
            DataStructure.MeasuredData(cntData).Sample_Rate = ob.(cname).wf_increment.value;
            DataStructure.MeasuredData(cntData).Samples_Per_Read = ob.(cname).wf_samples.value;
            DataStructure.MeasuredData(cntData).Total_Samples = ob.(cname).nsamples;
            DataStructure.MeasuredData(cntData).Units_Decription = char(ob.(cname).NI_UnitDescription.value)';
            DataStructure.MeasuredData(cntData).Unit_String = char(ob.(cname).unit_string.value)';
            DataStructure.MeasuredData(cntData).Data = ob.(cname).data;
            cntData = cntData + 1;
        
        end
        
        
    else
        varNameMask = ob.index.names(i);
        varNameMask = varNameMask{:};
    end
    
end


end


function  fixedtext=fixcharformatlab(textin)
%Private Function to remove all text that is not MATLAB variable name
%compatible
textin=strrep(textin,'''','');
textin=strrep(textin,'\','');
textin=strrep(textin,'/Untitled/','');
textin=strrep(textin,'/','.');
textin=strrep(textin,'-','');
textin=strrep(textin,'?','');
textin=strrep(textin,' ','_');
textin=strrep(textin,'.','');
textin=strrep(textin,'[','_');
textin=strrep(textin,']','');
textin=strrep(textin,'%','');
textin=strrep(textin,'#','');
textin=strrep(textin,'(','');
textin=strrep(textin,')','');
textin=strrep(textin,':','');
fixedtext=textin;

end

function matType=LV2MatlabDataType(LVType)
%Cross Refernce Labview TDMS Data type to MATLAB

switch LVType
    case 1   %tdsTypeVoid
        matType='';
    case 2   %tdsTypeI8
        matType='int8';
    case 3   %tdsTypeI16
        matType='int32';
    case 4   %tdsTypeI32
        matType='int32';
    case 5   %tdsTypeI64
        matType='int64';
    case 6   %tdsTypeU8
        matType='uint8';
    case 7   %tdsTypeU16
        matType='uint16';
    case 8   %tdsTypeU32
        matType='uint32';
    case 9   %tdsTypeU64
        matType='uint64';
    case 10  %tdsTypeSingleFloat
        matType='float64';
    case 11  %tdsTypeDoubleFloat
        matType='float64';
    case 12  %tdsTypeExtendedFloat
        matType='';
    case 32  %tdsTypeString
        matType='char';
    case 33  %tdsTypeBoolean
        matType='bit1';
    case 68  %tdsTypeTimeStamp
        matType='bit224';
    otherwise
        matType='';
end

end

Contact us at files@mathworks.com