Browse Source

First commit

Ioannis Agtzidis 5 years ago
commit
a02dd6552c
17 changed files with 1128 additions and 0 deletions
  1. 40 0
      AddAttArff.m
  2. 48 0
      AppendArff.m
  3. 24 0
      Arff2Coord.m
  4. 51 0
      Coord2Arff.m
  5. 32 0
      GetAttPositionArff.m
  6. 32 0
      GetIntervalsArff.m
  7. 42 0
      GetIntervalsIndexArff.m
  8. 23 0
      GetMetaExtraPosArff.m
  9. 10 0
      GetMetaExtraValueArff.m
  10. 29 0
      GetNomAttValue.m
  11. 43 0
      IsNomAttribute.m
  12. 12 0
      IsOctave.m
  13. 115 0
      LoadArff.m
  14. 23 0
      PixelsPerDegree.m
  15. 5 0
      README.md
  16. 127 0
      SaveArff.m
  17. 472 0
      glob.m

+ 40 - 0
AddAttArff.m

@@ -0,0 +1,40 @@
+% function AddAttArff:
+%
+% This function adds the data and the name of the new attribute to the initial
+% data as a new column.
+%
+% input:
+%   data            - data of the initial arff file
+%   attributes      - attributes of the initial arff file
+%   attData         - attribute data to append at the data. When nominal attributes 
+%                     are appended the attribute values should corespond to the enumeration
+%                     equivalent
+%   attName         - attribute name
+%   attType         - attribute type (Integer, Numeric or nominal in the form '{val1,val2}')
+%
+% output:
+%   newData         - data after addition of the new column
+%   newAttributes   - attributes containing the addition of the new attribute
+
+function [newData, newAttributes] = AddAttArff(data, attributes, attData, attName, attType)
+    % are data and new attribute smae size
+    assert(size(data,1)==size(attData,1), 'Provided attribute does not have same number of entries as initial data');
+	
+	% check if attribute already exists
+    for i=1:size(attributes,1)
+        if (strcmpi(attributes{i,1}, attName))
+            error(['Attributes "' attName '" already exists in file ' arffFile]);
+        end
+    end
+
+    % merge returned attributes
+    newAttributes = attributes;
+    index = size(attributes,1)+1;
+    newAttributes{index,1} = attName;
+    newAttributes{index,2} = attType;
+
+    % concatenate attribute to the returned data
+    newData = zeros(size(data,1), size(data,2)+1);
+    newData(:,1:end-1) = data(:,:);
+    newData(:,end) = attData(:);
+end

+ 48 - 0
AppendArff.m

@@ -0,0 +1,48 @@
+% function AppendArff:
+%
+% Loads the given ARFF file appends the provided attribute and saves the data
+% the original file
+%
+% input:
+%   arffFile    - file to load the data from
+%   attData     - attribute data
+%   attName     - attribute name
+%   attType     - attribute type (Integer or Numeric accepted for now)
+%   extraId     - extension to add to the name before file extension
+
+function AppendArff(arffFile, attData, attName, attType, extraId)
+    if (nargin<5)
+        extraId = '';
+    end
+    % initially load data
+    [data, metadata, attributes, relation] = LoadArff(arffFile);
+
+    % check if attribute already exists
+    for i=1:size(attributes,1)
+        if (strcmpi(attributes{i,1}, attName))
+            error(['Attributes "' attName '" already exists in file ' arffFile]);
+        end
+    end
+
+    % check for data and attribute values
+    assert(size(data,1)==size(attData,1), 'Attribute data and arff data should be of the same length');
+
+    % set data
+    appData = zeros(size(data,1), size(data,2)+1);
+    appData(:,1:end-1) = data;
+    appData(:,end) = attData;
+
+    % append attribute
+    index = size(attributes,1)+1;
+    attributes{index,1} = attName;
+    attributes{index,2} = attType;
+
+    [dir, name, ext] = fileparts(arffFile);
+    outArffFile = [name extraId ext];
+    if (~isempty(dir))
+        outArffFile = [dir '/' outArffFile];
+    end
+
+    % save to the same file
+    SaveArff(outArffFile, appData, metadata, attributes, relation);
+end    

+ 24 - 0
Arff2Coord.m

@@ -0,0 +1,24 @@
+% Arff2Coord.m
+%
+% This function converts an ARFF file to its .coord representation.
+%
+% input:
+%   arrfFile    - input ARFF file
+%   coordFile   - coord file to sore results
+
+function Arff2Coord(arffFile, coordFile)
+    [data, metadata, attributes, relation] = LoadArff(arffFile);
+
+    timeInd = GetAttPositionArff(attributes, 'time');
+    xInd = GetAttPositionArff(attributes, 'x');
+    yInd = GetAttPositionArff(attributes, 'y');
+    confInd = GetAttPositionArff(attributes, 'confidence');
+
+    fid = fopen(coordFile, 'w');
+    fprintf(fid, 'gaze %d %d\n', metadata.width_px, metadata.height_px);
+    fprintf(fid, 'geometry distance %.2f width %.2f height %.2f\n', metadata.distance_mm/1000, metadata.width_mm/1000, metadata.height_mm/1000);
+    for i=1:size(data,1)
+        fprintf(fid, '%d %.2f %.2f %.2f\n', data(i,timeInd), data(i,xInd), data(i,yInd), data(i, confInd));
+    end
+    fclose(fid);
+end

+ 51 - 0
Coord2Arff.m

@@ -0,0 +1,51 @@
+% function Coord2Arff:
+i%
+% Converts coord files to ARFF files. The created file has the same name
+% with .arff extension.
+%
+% input:
+%   coordFile       - .coord file
+%   storeAtSource   - 1 to store .arff at the same position as .coord. 0 to store at current directory
+
+function Coord2Arff(coordFile, storeAtSource)
+    % check input arguments
+    if (nargin <2)
+        storeAtSource = 0;
+    end
+    % get part of input file
+    [dir, basename, ext] = fileparts(coordFile);
+
+    % load data from coord file
+    [data, pixelX, pixelY, width, height, distance, conf] = LoadCoordAndConf(coordFile);
+
+    arffFile = [basename '.arff'];
+    if (length(dir) > 0)
+        dir = [dir '/'];
+    end
+    if (storeAtSource == 1)
+        arffFile = [dir arffFile];
+    end
+
+    fid = fopen(arffFile, 'w+');
+    % print experiment parameters
+    fprintf(fid, '@RELATION gaze_labels\n\n');
+    fprintf(fid, '%%@METADATA width_px %d\n', pixelX);
+    fprintf(fid, '%%@METADATA height_px %d\n', pixelY);
+    fprintf(fid, '%%@METADATA distance_mm %.2f\n', distance*1000);
+    fprintf(fid, '%%@METADATA width_mm %.2f\n', width*1000);
+    fprintf(fid, '%%@METADATA height_mm %.2f\n\n', height*1000);
+
+    fprintf(fid, '@ATTRIBUTE time INTEGER\n');
+    fprintf(fid, '@ATTRIBUTE x NUMERIC\n');
+    fprintf(fid, '@ATTRIBUTE y NUMERIC\n');
+    fprintf(fid, '@ATTRIBUTE confidence NUMERIC\n\n');
+    fprintf(fid, '@DATA\n');
+
+    for i=1:size(data,1)
+        fprintf(fid, '%d,', data(i,1));
+        fprintf(fid, '%.2f,', data(i,2:3));
+        fprintf(fid, '%.2f\n', conf(i));
+    end
+
+    fclose(fid);
+end

+ 32 - 0
GetAttPositionArff.m

@@ -0,0 +1,32 @@
+% function GetAttPositionArff:
+%
+% Gets a list of attributes returned from LoadArff and an attribute name to
+% search.  If it finds the attribute returns its index otherwise it can raise
+% an error.
+%
+% input:
+%   arffAttributes  - attribute list returned from LoadArff
+%   attribute       - attribute to search
+%   check           - (optional) boolean to check if attribute exists. Default is true
+%
+% output:
+%   attIndex        - index attribute of the attribute in the list if it was found. 
+%                     Returns 0 if it wasn't found
+
+function [attIndex] = GetAttPositionArff(arffAttributes, attribute, check)
+    if (nargin < 3)
+        check = true;
+    end
+    attIndex = 0;
+
+    for i=1:size(arffAttributes,1)
+        if (strcmpi(arffAttributes{i,1}, attribute) == 1)
+            attIndex = i;
+        end
+    end
+
+    % check index
+    if (check)
+        assert(attIndex>0, ['Attribute "' attribute '" not found']);
+    end
+end

+ 32 - 0
GetIntervalsArff.m

@@ -0,0 +1,32 @@
+% function GetIntervalsArff:
+%
+% Uses the data loaded from an ARFF file along with an attribute name and type
+% of eye movement and returns the intervals for the specific eye movement.
+%
+% input:
+%   data            - data loaded from ARFF file with LoadArff
+%   arffAttributes  - attributes returned from LoadArff
+%   attribute       - attribute to consider for interval counting
+%   moveId          - id for eye movement to consider
+%
+% output:
+%   intervals       - nx6 array (start time, start x, start y, end time, end x, end y)
+
+function [intervals] = GetIntervalsArff(data, arffAttributes, attribute, moveId)
+    
+    intervalIndices = GetIntervalsIndexArff(data, arffAttributes, attribute, moveId);
+
+    % initialize data
+    intervals = zeros(size(intervalIndices,1),6);
+
+    % find position of attribute in data
+    timeIndex = GetAttPositionArff(arffAttributes, 'time');
+    xIndex = GetAttPositionArff(arffAttributes, 'x');
+    yIndex = GetAttPositionArff(arffAttributes, 'y');
+
+    for i=1:size(intervals,1)
+        startIndex = intervalIndices(i,1);
+        endIndex = intervalIndices(i,2);
+        intervals(i,:) = [data(startIndex,timeIndex) data(startIndex,xIndex) data(startIndex,yIndex) data(endIndex,timeIndex) data(endIndex,xIndex) data(endIndex,yIndex)];
+    end
+end    

+ 42 - 0
GetIntervalsIndexArff.m

@@ -0,0 +1,42 @@
+% function GetIntervalsIndexArff:
+%
+% Uses the data loaded from an ARFF file along with an attribute name and type
+% of eye movement and returns the intervals (as indices) for the specific eye
+% movement.
+%
+% input:
+%   data            - data loaded from ARFF file with LoadArff
+%   arffAttributes  - attributes returned from LoadArff
+%   attribute       - attribute to consider for interval counting
+%   moveId          - id for eye movement to consider
+%
+% output:
+%   intervals       - nx2 array (start index, end index)
+
+function [intervals] = GetIntervalsIndexArff(data, arffAttributes, attribute, moveId)
+    % initialize data
+    intervals = zeros(0,2);
+
+    % find position of attribute in data
+    dataIndex = GetAttPositionArff(arffAttributes, attribute);
+
+    startIndex = -1;
+    for i=1:size(data,1)
+        if (data(i,dataIndex)==moveId)
+            % first element of interval
+            if (startIndex==-1)
+                startIndex=i;
+            end
+        else
+            % interval finished on previous iteration
+            if (startIndex~=-1)
+                intervals = [intervals; startIndex i-1];
+            end
+            startIndex = -1;
+        end
+    end
+    % add last interval
+    if (startIndex~=-1)
+        intervals = [intervals; startIndex size(data,1)];
+    end
+end    

+ 23 - 0
GetMetaExtraPosArff.m

@@ -0,0 +1,23 @@
+% GetMetaExtraPosArff.m
+%
+% This function finds the position of a metadata stored in the extra metadata.
+% The default metadata are width_px, height_px, height_mm, width_mm, distance_mm
+% are accessed as fields of the structure.
+%
+% input:
+%   arffMeta    - structure holding the metadata
+%   metaName    - name of the metadata to find
+%
+% output:
+%   metaIndex   - index in the metadata field extra which is an nx2 cell array
+
+function metaIndex = GetMetaExtraPosArff(arffMeta, metaName)
+    metaIndex = 0;
+    for i=1:size(arffMeta.extra,1)
+        if(strcmpi(arffMeta.extra{i,1}, metaName) == 1)
+            metaIndex = i;
+        end
+    end
+
+    assert(metaIndex > 0, ['Extra metadata ' metaName ' not found']);
+end

+ 10 - 0
GetMetaExtraValueArff.m

@@ -0,0 +1,10 @@
+% GetMetaExtraValueArff.m
+%
+% This function returns the value of a metadata stored in extra metadta as
+% string.
+
+function value = GetMetaExtraValueArff(arffMeta, metaName)
+    metaInd = GetMetaExtraPosArff(arffMeta, metaName);
+
+    value = arffMeta.extra{metaInd,2};
+end

+ 29 - 0
GetNomAttValue.m

@@ -0,0 +1,29 @@
+% GetNomAttValue.m
+%
+% This function returns the value of a nominal attribute in its correct form without 
+% spaces.
+%
+% input:
+%   attDatatype - the part that describes the attribute after its name
+%
+% output:
+%   attValue    - nominal attribute in its correct form
+
+function [attValue] = GetNomAttValue(attDatatype)
+    openCurl = strfind(attDatatype, '{');
+    closeCurl = strfind(attDatatype, '}');
+
+    if (isempty(openCurl) && isempty(closeCurl))
+        isNom = false;
+        nominalMap = containers.Map;
+        numericMap = containers.Map;
+        return;
+    end
+
+    assert(length(openCurl) == 1, ['Invalid attribute datatype ' attDatatype]);
+    assert(length(closeCurl) == 1, ['Invalid attribute datatype ' attDatatype]);
+    attValue = attDatatype(openCurl:closeCurl);
+
+    % remove spaces from nominal
+    attValue = attValue(~isspace(attValue));
+end

+ 43 - 0
IsNomAttribute.m

@@ -0,0 +1,43 @@
+% IsNomAttribute.m
+%
+% This function checks if an attribute is of nominal type and returns true along
+% with nominal and numeric maps. Otherwise it returns false.
+%
+% input:
+%   attDatatype - the part that describes the attribute after its name
+%
+% output:
+%   isNom       - boolean value denoting if nominal
+%   nominalMap  - mapping of nominal values to doubles as in an C++ enumeration
+%   numericMap  - mapping of doubles to nominal values
+
+function [isNom, nominalMap, numericMap] = IsNomAttribute(attDatatype)
+    openCurl = strfind(attDatatype, '{');
+    closeCurl = strfind(attDatatype, '}');
+
+    if (isempty(openCurl) && isempty(closeCurl))
+        isNom = false;
+        nominalMap = containers.Map;
+        numericMap = containers.Map;
+        return;
+    end
+
+    assert(length(openCurl) == 1, ['Invalid attribute datatype ' attDatatype]);
+    assert(length(closeCurl) == 1, ['Invalid attribute datatype ' attDatatype]);
+    attDatatype = attDatatype(openCurl+1:closeCurl-1);
+
+    % remove spaces from nominal
+    attDatatype = attDatatype(~isspace(attDatatype));
+
+    keys = split(attDatatype, ',');
+    values = 0:length(keys)-1;
+
+    nominalMap = containers.Map(keys, values);
+
+    % convert to simple when we have single key. Otherwise the type is invalid for map creation
+    if (length(keys) == 1)
+        keys = string(keys);
+    end
+    numericMap = containers.Map(values, keys);
+    isNom = true;
+end

+ 12 - 0
IsOctave.m

@@ -0,0 +1,12 @@
+%
+% Return: true if the environment is Octave.
+%
+function retval = IsOctave
+  persistent cacheval;  % speeds up repeated calls
+
+  if isempty (cacheval)
+    cacheval = (exist ("OCTAVE_VERSION", "builtin") > 0);
+  end
+
+  retval = cacheval;
+end

+ 115 - 0
LoadArff.m

@@ -0,0 +1,115 @@
+% LoadArff.m
+%
+% Thi funciton loads data from an ARFF file and returns the data, metadata,
+% attributes, relation and comments. All returned strings are lower case.
+%
+% input:
+%   arffFile    - path to ARFF file to read
+%   
+% output:
+%   data        - data stored in the ARFF file
+%   metadata    - structure holding metadta in the form: metadata.{width_px, height_px, width_mm, height_mm, distance_mm} -1 if not available. Extra metadata are stored in  metadata.extra, which is an nx2 cell array holding name-value pairs
+%   attributes  - nx2 cell array with attribute names and types, where n is the number of attributes
+%   relation    - relation described in ARFF
+%   comments    - nx1 cell array containing one comment line per cell
+
+function [data, metadata, attributes, relation, comments] = LoadArff(arffFile)
+    % initialize data
+    data = [];
+    % initialize metadata
+    metadata.width_px = -1;
+    metadata.height_px = -1;
+    metadata.width_mm = -1; 
+    metadata.height_mm = -1; 
+    metadata.distance_mm = -1; 
+    metadata.extra = {};
+    attributes = {};
+    relation = '';
+    comments = {};
+
+    % nominal attribute handling
+    nomMat = logical([]);
+    nomMaps = {};
+
+    % read header
+    numOfHeaderLines = 1;
+    fid = fopen(arffFile, 'r');
+    fline = fgetl(fid);
+    while (ischar(fline))
+        % split lines into words
+        words = strsplit(fline,' ');
+        % check for relation
+        if (size(words,2)>1 && strcmpi(words{1,1},'@relation')==1)
+            relation = lower(words{1,2});
+        % check for width_px
+        elseif (size(words,2)>2 && strcmpi(words{1,1},'%@metadata')==1 && strcmpi(words{1,2},'width_px')==1)
+            metadata.width_px = str2num(words{1,3});
+        % check for height_px
+        elseif (size(words,2)>2 && strcmpi(words{1,1},'%@metadata')==1 && strcmpi(words{1,2},'height_px')==1)
+            metadata.height_px = str2num(words{1,3});
+        % check for width_mm
+        elseif (size(words,2)>2 && strcmpi(words{1,1},'%@metadata')==1 && strcmpi(words{1,2},'width_mm')==1)
+            metadata.width_mm = str2num(words{1,3});
+        % check for height_mm
+        elseif (size(words,2)>2 && strcmpi(words{1,1},'%@metadata')==1 && strcmpi(words{1,2},'height_mm')==1)
+            metadata.height_mm = str2num(words{1,3});
+        % check for distance_mm
+        elseif (size(words,2)>2 && strcmpi(words{1,1},'%@metadata')==1 && strcmpi(words{1,2},'distance_mm')==1)
+            metadata.distance_mm = str2num(words{1,3});
+        % process the rest of the metadata
+        elseif (size(words,2)>2 && strcmpi(words{1,1},'%@metadata')==1)
+            pos = size(metadata.extra,1)+1;
+            metadata.extra{pos,1} = words{1,2};
+            metadata.extra{pos,2} = words{1,3};
+        % check for attributes
+        elseif (size(words,2)>2 && strcmpi(words{1,1},'@attribute')==1)
+            index = size(attributes,1)+1;
+            attributes{index,1} = lower(words{1,2});
+            attributes{index,2} = words{1,3};
+            [isNom, nominalMap] = IsNomAttribute(fline);
+            nomMat = [nomMat; isNom];
+            if (isNom)
+                nomMaps = [nomMaps; {nominalMap}];
+                attributes{index,2} = GetNomAttValue(fline);
+            else
+                nomMaps = [nomMaps; {[]}];
+            end
+        % check if it is a comment
+        elseif (length(fline>0) && fline(1) == '%')
+            comments{end+1} = fline;
+        % check if data has been reached
+        elseif (size(words,2)>0 && strcmpi(words{1,1},'@data')==1)
+            break;
+        end
+
+        fline = fgetl(fid);
+        numOfHeaderLines = numOfHeaderLines+1;
+    end
+
+    numAtts = size(attributes,1);
+    readFormat = '';
+    for ind=1:numAtts
+        if (nomMat(ind))
+            readFormat = [readFormat '%s '];
+        else
+            readFormat = [readFormat '%f '];
+        end
+    end
+    lines = textscan(fid, readFormat, 'Delimiter', ',');
+
+    nomIndices = find(nomMat);
+    for nomInd=nomIndices'
+        if (isempty(nomInd))
+            break;
+        end
+
+        for ind=1:size(lines{1,nomInd},1)
+            lines{1,nomInd}{ind} = nomMaps{nomInd,1}(lines{1,nomInd}{ind});
+        end
+        lines{1,nomInd} = cell2mat(lines{1,nomInd});
+    end
+
+    data = cell2mat(lines);
+
+    fclose(fid);
+end    

+ 23 - 0
PixelsPerDegree.m

@@ -0,0 +1,23 @@
+% function PixelsPerDegree:
+%
+% This function returns the approximation of pixels per degree for each
+% dimension.  It doesn't take into account the change in distance as we move to
+% the edges of the monitor.
+%
+% input:
+%   width        - width of monitor in meters
+%   height       - height of monitor in meters
+%   distance     - distance of the viewer in meters
+%   widthPixels  - width of moitor in pixels
+%   heightPixels - height of monitor in pixels
+% output:
+%   pxPerDeg     - pixels per degree on X axis(approx.)
+%   pyPerDeg     - pixels per degree on Y axis(approx.)
+
+function [pxPerDeg, pyPerDeg] = PixelsPerDegree(width, height, distance, widthPixels, heightPixels)
+    thetaWtotal = 2*atan(width/(2*distance))*180/pi;
+    thetaHtotal = 2*atan(height/(2*distance))*180/pi;
+
+    pxPerDeg = widthPixels/thetaWtotal;
+    pyPerDeg = heightPixels/thetaHtotal;
+end

+ 5 - 0
README.md

@@ -0,0 +1,5 @@
+
+# Matlab Utilities
+
+This repo contains various Matlab utilities which are used in other projects. 
+The files are licensed under GPLv3 unless stated otherwise.

+ 127 - 0
SaveArff.m

@@ -0,0 +1,127 @@
+% SaveArff.m
+%
+% Function to save ARFF data to file.
+% 
+% input:
+%   arffFile    - name of the file to save data
+%   data        - data to write in arff file
+%   metadata    - metadata struct in
+%   attributes  - nx2 cell array holding the attribute names
+%   relation    - relation described in the file
+%   comments    - (optional) nx1 cell array containing one comment line per cell
+
+function SaveArff(arffFile, data, metadata, attributes, relation, comments)
+    if (nargin < 6)
+        comments = {};
+    end
+    % check input
+    assert(isfield(metadata,'width_px'), 'metadata should contain "width_px" field');
+    assert(isfield(metadata,'height_px'), 'metadata should contain "height_px" field');
+    assert(isfield(metadata,'width_mm'), 'metadata should contain "width_mm" field');
+    assert(isfield(metadata,'height_mm'), 'metadata should contain "height_mm" field');
+    assert(isfield(metadata,'distance_mm'), 'metadata should contain "distance_mm" field');
+    assert(size(relation,2)>0, 'relation should not be empty');
+    assert(size(attributes,1)==size(data,2), 'attribute number should be the same with data');
+
+    % start writing
+    fid = fopen(arffFile, 'w+');
+
+    % write relation
+    fprintf(fid, '@RELATION %s\n\n', relation);
+
+    % write metadata
+    fprintf(fid, '%%@METADATA width_px %d\n', metadata.width_px);
+    fprintf(fid, '%%@METADATA height_px %d\n', metadata.height_px);
+    fprintf(fid, '%%@METADATA width_mm %.2f\n', metadata.width_mm);
+    fprintf(fid, '%%@METADATA height_mm %.2f\n', metadata.height_mm);
+    fprintf(fid, '%%@METADATA distance_mm %.2f\n\n', metadata.distance_mm);
+
+    % write metadata extras. Those are data that vary between experiments
+    for i=1:size(metadata.extra,1)
+        fprintf(fid, '%%@METADATA %s %s\n', metadata.extra{i,1}, metadata.extra{i,2});
+    end
+    % print an empty line
+    fprintf(fid, '\n');
+
+    % write attributes and get their type
+    % 1 = integer
+    % 2 = numeric
+    % -1 = other
+    numAtts = size(attributes,1);
+    attType = -1*ones(numAtts,1);
+    numMaps = cell(numAtts,1);
+    for i=1:numAtts
+        fprintf(fid, '@ATTRIBUTE %s %s\n', attributes{i,1}, attributes{i,2});
+        [isNom, ~, numericMap] = IsNomAttribute(attributes{i,2});
+
+        % get type
+        if (strcmpi(attributes{i,2},'integer')==1) 
+            attType(i) = 1;
+        elseif (strcmpi(attributes{i,2},'numeric')==1)
+            attType(i) = 2;
+        elseif (isNom)
+            attType(i) = 3;
+            numMaps{i,1} = numericMap;
+        end
+    end
+
+    % write comments if they exist
+    if (~isempty(comments))
+        fprintf(fid, '\n');
+        for i=1:length(comments)
+            comment = comments{i};
+            % check if % is the first character
+            if (length(comment)>0 && comment(1)~='%')
+                comment = ['%' comment];
+            end
+
+            fprintf(fid, '%s\n', comment);
+        end
+    end
+
+    % write data keyword
+    fprintf(fid,'\n@DATA\n');
+
+    numEntries = size(data,1);
+    % transpose data in order to allow one line writing because fprintf handles 
+    % matrices column wise when writing in file
+    data = num2cell(data');
+    nomIndices = find(attType==3);
+    for nomInd=nomIndices'
+        if (isempty(nomInd))
+            break;
+        end
+
+        for ind=1:numEntries
+            %data{ind, nomInd} = numMaps{nomInd,1}(data{ind, nomInd});
+            data{nomInd, ind} = numMaps{nomInd,1}(data{nomInd, ind});
+        end
+    end
+
+    writeFormat = '';
+    for ind=1:numAtts
+        if (attType(ind) == 1)
+            writeFormat = [writeFormat '%d'];
+        elseif (attType(ind) == 2)
+            writeFormat = [writeFormat '%.2f'];
+        elseif (attType(ind) == 3)
+            writeFormat = [writeFormat '%s'];
+        else
+            error(['Attribute type "' num2str(attType(ind)) '" is not recognised']);
+        end
+
+        if (ind<numAtts)
+            writeFormat = [writeFormat ','];
+        end
+    end
+    writeFormat = [writeFormat '\n'];
+
+    % One line writing almost halves the writing time
+    fprintf(fid, writeFormat, data{:});
+    %for ind=1:numEntries
+    %    fprintf(fid, writeFormat, data{ind,:});
+    %end
+
+    % close file
+    fclose(fid);
+end    

+ 472 - 0
glob.m

@@ -0,0 +1,472 @@
+%% Expand wildcards for files and directory names
+%
+%   Pattern matching of file and directory names, based on wildcard
+%   characters. This function is similar to wildcard expansion performed by
+%   the Unix shell and Python glob.glob function, but it can handle more
+%   types of wildcards.
+%
+%   [LIST, ISDIR] = glob(FILESPEC)
+%       returns cell array LIST with files or directories that match the
+%       path specified by string FILESPEC. Wildcards may be used for
+%       basenames and for the directory parts. If FILESPEC contains
+%       directory parts, then these will be included in LIST.
+%       ISDIR is a boolean, the same size as LIST that is true for
+%       directories in LIST.
+%
+%       Following wildcards can be used:
+%           *        match zero or more characters
+%           ?        match any single character
+%           [ab12]   match one of the specified characters
+%           [^ab12]  match none of the specified characters
+%           [a-z]    match one character in range of characters
+%           {a,b,c}  matches any one of strings a, b or c
+%
+%           all above wildcards do not match a file separator.
+%
+%           **       match zero or more characters including file separators.
+%                    This can be used to match zero or more directory parts
+%                    and will recursively list matching names.
+%
+%       The differences between GLOB and DIR:
+%           * GLOB supports wildcards for directories.
+%           * GLOB returns the directory part of FILESPEC.
+%           * GLOB returns a cell array of matching names.
+%           * GLOB does not return hidden files and directories that start 
+%             with '.' unless explicitly specified in FILESPEC.
+%           * GLOB does not return '.' and '..' unless explicitly specified 
+%             in FILESPEC.
+%           * GLOB adds a trailing file separator to directory names.
+%           * GLOB does not return the contents of a directory when
+%             a directory is specified. To return contents of a directory,
+%             add a trailing '/*'.
+%           * GLOB returns only directory names when a trailing file
+%             separator is specified.
+%           * On Windows GLOB is not case sensitive, but it returns
+%             matching names exactely in the case as they are defined on
+%             the filesystem. Case of host and sharename of a UNC path and
+%             case of drive letters will be returned as specified in 
+%             FILESPEC.
+%
+%   glob(FILESPEC, '-ignorecase')
+%        Default GLOB is case sensitive on Unix. With option '-ignorecase'
+%        FILESPEC matching is not case sensitive. On Windows, GLOB always
+%        ignores the case. This option can be abbreviated to '-i'.
+%
+% Examples:
+%       glob *.m        list all .m files in current directory.
+%
+%       glob baz/*      list all files and directories in subdirectory 'baz'.
+%
+%       glob b*/*.m     list all .m files in subdirectory names starting
+%                       with 'b'. The list will include the names of the
+%                       matching subdirectories.
+%
+%       glob ?z*.m      list all .m files where the second character
+%                       is 'z'.
+%
+%       glob baz.[ch]   matches baz.c and baz.h
+%
+%       glob test.[^ch] matches test.a but not test.c or test.h
+%
+%       glob demo.[a-c] matches demo.a, demo.b, and demo.c
+%
+%       glob test.{foo,bar,baz} matches test.foo, test.bar, and test.baz
+%
+%       glob .*         list all hidden files in current directory,
+%                       excluding '.' and '..'
+%
+%       glob */         list all subdirectories.
+%
+%       glob **         recursively list all files and directories,
+%                       starting in current directory (current directory
+%                       name, hidden files and hidden directories are 
+%                       excluded).
+%
+%       glob **.m       list all m-files anywhere in directory tree,
+%                       including m-files in current directory. This
+%                       is equivalent with '**/*.m'.
+%
+%       glob foo/**/    recursively list all directories, starting in
+%                       directory 'foo'.
+%
+%       glob **/.svn/   list all .svn directories in directory tree.
+%
+%       glob **/.*/**   recursively list all files in hidden directories
+%                       only.
+%
+%       [r,d]=glob('**')
+%       r(~d)           get all files in directory tree.
+%
+% Known limitation:
+%       When using '**', symbolic linked directories or junctions may cause
+%       an infinite loop.
+%
+% See also dir
+
+%% Last modified
+%   $Date: 2013-02-02 18:41:41 +0100 (Sat, 02 Feb 2013) $
+%   $Author: biggelar $
+%   $Rev: 12966 $
+
+%% History
+%   2013-02-02  biggelar    submitted to Matlab Central
+%   2013-01-11  biggelar    add {} wildcards
+%   2013-01-02  biggelar    Created
+
+%% Copyright (c) 2013, Peter van den Biggelaar
+% All rights reserved.
+% 
+% Redistribution and use in source and binary forms, with or without 
+% modification, are permitted provided that the following conditions are 
+% met:
+% 
+%     * Redistributions of source code must retain the above copyright 
+%       notice, this list of conditions and the following disclaimer.
+%     * Redistributions in binary form must reproduce the above copyright 
+%       notice, this list of conditions and the following disclaimer in 
+%       the documentation and/or other materials provided with the distribution
+%       
+% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
+% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+% POSSIBILITY OF SUCH DAMAGE.
+
+
+% ------------------------------------------------------------------------
+function [LIST, ISDIR] = glob(FILESPEC, ignorecase)
+
+%% check FILESPEC input
+if ischar(FILESPEC)
+    if isempty(FILESPEC)
+        % return when FILESPEC is empty
+        LIST = cell(0);
+        ISDIR = false(0);
+        return
+    elseif size(FILESPEC,1)>1
+        error('glob:invalidInput', 'FILESPEC must be a single string.')
+    end
+else
+    error('glob:invalidInput', 'FILESPEC must be a string.')
+end    
+
+%% check ignorecase option
+if nargin==2
+    if ischar(ignorecase)
+        % ignore case when option is specified; must be at least 2 characters long
+        if strncmp(ignorecase, '-ignorecase', max(numel(ignorecase),2));
+            ignorecase = true;
+        else
+            error('glob:invalidOption', 'Invalid option.') 
+        end    
+    else
+        error('glob:invalidOption', 'Invalid option.')
+    end    
+else
+    % Windows is not case sensitive
+    % Unix is case sensitive
+    ignorecase = ispc;
+end
+
+%% define function handle to regular expression function for the specified case sensitivity
+if ignorecase
+    regexp_fhandle = @regexpi;
+else
+    regexp_fhandle = @regexp;
+end
+
+%% only use forward slashes as file separator to prevent escaping backslashes in regular expressions
+filespec = strrep(FILESPEC, '\', '/');
+
+%% split pathroot part from FILESPEC
+if strncmp(filespec, '//',2)
+    if ispc
+        % FILESPEC specifies a UNC path
+        % It is not allowed to get a directory listing of share names of a 
+        % host with the DIR command.
+        % pathroot will contains e.g. //host/share/
+        pathroot = regexprep(filespec, '(^//+[^/]+/[^/]+/)(.*)', '$1');
+        filespec = regexprep(filespec, '(^//+[^/]+/[^/]+/)(.*)', '$2');
+    else
+        % for Unix, multiple leading file separators are equivalent with a single file separator
+        filespec = regexprep(filespec, '^/*', '/');
+    end
+elseif strncmp(filespec, '/', 1)
+    % FILESPEC specifies a absolute path
+    pathroot = '/';
+    filespec(1) = [];
+elseif ispc && numel(filespec)>=2 && filespec(2)==':'
+    % FILESPEC specifies a absolute path starting with a drive letter
+    % check for a fileseparator after ':'. e.g. 'C:\'
+    if numel(filespec)<3 || filespec(3)~='/'
+        error('glob:invalidInput','Drive letter must be followed by '':\''.')
+    end
+    pathroot = filespec(1:3);
+    filespec(1:3) = [];
+else
+    % FILESPEC specifies a relative path
+    pathroot = './';
+end
+
+%% replace multiple file separators by a single file separator
+filespec = regexprep(filespec, '/+', '/');
+
+%% replace 'a**' with 'a*/**', where 'a' can be any character but not '/'
+filespec = regexprep(filespec, '([^/])(\.\*\.\*)', '$1\*/$2');
+%% replace '**a' with '**/*a', where a can be any character but not '/'
+filespec = regexprep(filespec, '(\.\*\.\*)([^/])', '$1/\*$2');
+
+%% split filespec into chunks at file separator
+chunks = strread(filespec, '%s', 'delimiter', '/'); %#ok<FPARK>
+
+%% add empty chunk at the end when filespec ends with a file separator
+if ~isempty(filespec) && filespec(end)=='/'
+    chunks{end+1} = '';
+end
+
+%% translate chunks to regular expressions
+for i=1:numel(chunks)
+    chunks{i} = glob2regexp(chunks{i});
+end
+
+%% determine file list using LS_REGEXP
+% this function requires that PATHROOT does not to contain any wildcards
+if ~isempty(chunks)
+    list = ls_regexp(regexp_fhandle, pathroot, chunks{1:end});
+else
+    list = {pathroot};
+end
+
+if strcmp(pathroot, './')
+    % remove relative pathroot from result
+    list = regexprep(list, '^\./', '');
+end
+
+if nargout==2
+    % determine directories by checking for '/' at the end
+    I = regexp(list', '/$');
+    ISDIR = ~cellfun('isempty', I);
+end
+
+%% convert to standard file separators for PC
+if ispc
+    list = strrep(list, '/', '\');
+end
+
+%% return output
+if nargout==0
+    if ~isempty(list)
+        % display list
+        disp(char(list))
+    else
+        disp(['''' FILESPEC ''' not found.']);
+    end    
+else
+    LIST = list';
+end
+
+
+% ------------------------------------------------------------------------
+function regexp_str = glob2regexp(glob_str)
+%% translate glob_str to regular expression string
+
+% initialize
+regexp_str  = '';
+in_curlies  = 0;        % is > 0 within curly braces
+
+% handle characters in glob_str one-by-one
+for c = glob_str
+        
+    if any(c=='.()|+^$@%')
+        % escape simple special characters
+        regexp_str = [regexp_str '\' c]; %#ok<AGROW>
+            
+    elseif c=='*'
+        % '*' should not match '/'
+        regexp_str = [regexp_str '[^/]*']; %#ok<AGROW>
+        
+    elseif c=='?'
+        % '?' should not match '/'
+        regexp_str = [regexp_str '[^/]']; %#ok<AGROW>
+        
+    elseif c=='{'
+        regexp_str = [regexp_str '(']; %#ok<AGROW>
+        in_curlies = in_curlies+1;    
+
+    elseif c=='}' && in_curlies
+        regexp_str = [regexp_str ')']; %#ok<AGROW>
+        in_curlies = in_curlies-1;    
+
+    elseif c==',' && in_curlies
+        regexp_str = [regexp_str '|']; %#ok<AGROW>
+            
+    else                    
+        regexp_str = [regexp_str c]; %#ok<AGROW>
+    end
+end
+
+% replace original '**' (that has now become '[^/]*[^/]*') with '.*.*'  
+regexp_str = strrep(regexp_str, '[^/]*[^/]*', '.*.*');
+
+
+
+% ------------------------------------------------------------------------
+function L = ls_regexp(regexp_fhandle, path, varargin)
+% List files that match PATH/r1/r2/r3/... where PATH is a string without
+% any wildcards and r1..rn are regular expresions that contain the parts of
+% a filespec between the file separators.
+% L is a cell array with matching file or directory names.
+% REGEXP_FHANDLE contain a file handle to REGEXP or REGEXPI depending
+% on specified case sensitivity.
+    
+% if first regular expressions contains '**', examine complete file tree
+if nargin>=3 && any(regexp(varargin{1}, '\.\*\.\*'))
+    L = ls_regexp_tree(regexp_fhandle, path, varargin{:});
+    
+else
+    % get contents of path
+    list = dir(path);
+    
+    if nargin>=3
+        if strcmp(varargin{1},'\.') || strcmp(varargin{1},'\.\.')
+            % keep explicitly specified '.' or '..' in first regular expression
+            if ispc && ~any(strcmp({list.name}, '.'))
+                % fix strange windows behaviour: root of a volume has no '.' and '..'
+                list(end+1).name = '.';
+                list(end).isdir = true;
+                list(end+1).name = '..';
+                list(end).isdir = true;                
+            end    
+        else
+            % remove '.' and '..'
+            list(strcmp({list.name},'.')) = [];
+            list(strcmp({list.name},'..')) = [];
+     
+            % remove files starting with '.' specified in first regular expression
+            if ~strncmp(varargin{1},'\.',2)
+                % remove files starting with '.' from list
+                list(strncmp({list.name},'.',1))  = [];
+            end    
+        end
+    end
+    
+    % define shortcuts
+    list_isdir = [list.isdir];
+    list_name = {list.name};
+    
+    L = {};  % initialize
+    if nargin==2    % no regular expressions
+        %% return filename
+        if ~isempty(list_name)
+            % add a trailing slash to directories
+            trailing_fsep = repmat({''}, size(list_name));
+            trailing_fsep(list_isdir) = {'/'};
+            L = strcat(path, list_name, trailing_fsep);
+        end
+
+    elseif nargin==3    % last regular expression
+        %% return list_name matching regular expression
+        I = regexp_fhandle(list_name, ['^' varargin{1} '$']);
+        I = ~cellfun('isempty', I);
+        list_name = list_name(I);
+        list_isdir = list_isdir(I);
+        if ~isempty(list_name)
+            % add a trailing slash to directories
+            trailing_fsep = repmat({''}, size(list_name));
+            trailing_fsep(list_isdir) = {'/'};
+            L = strcat(path, list_name, trailing_fsep);
+        end
+        
+    elseif nargin==4 && isempty(varargin{2})    
+        %% only return directories when last regexp is empty
+        % return list_name matching regular expression and that are directories
+        I = regexp_fhandle(list_name, ['^' varargin{1} '$']);
+        I = ~cellfun('isempty', I);
+        % only return directories
+        list_name = list_name(I);
+        list_isdir = list_isdir(I);
+        if any(list_isdir)
+            % add a trailing file separator
+            L = strcat(path, list_name(list_isdir), '/');
+        end            
+    else
+        %% traverse for list_name matching regular expression
+        I = regexp_fhandle(list_name, ['^' varargin{1} '$']);
+        I = ~cellfun('isempty', I);
+        for name = list_name(I)
+            L = [L   ls_regexp(regexp_fhandle, [path char(name) '/'], varargin{2:end})]; %#ok<AGROW>
+        end
+    end
+end
+
+
+% ------------------------------------------------------------------------
+function L = ls_regexp_tree(regexp_fhandle, path, varargin)
+% use this function when first argument of varargin contains '**'
+
+% build list of complete directory tree
+% if any regexp starts with '\.', keep hidden files and directories
+I = regexp(varargin, '^\\\.');
+I = ~cellfun('isempty', I);
+keep_hidden = any(I);
+list = dir_recur(path, keep_hidden);
+L = {list.name};
+
+% make one regular expression of all individual regexps
+expression = [regexptranslate('escape',path) sprintf('%s/', varargin{1:end-1}) varargin{end}];
+
+% note that /**/ must also match zero directories
+% replace '/**/' with (/**/|/)
+expression = regexprep(expression, '/\.\*\.\*/', '(/\.\*\.\*/|/)');
+
+% return matching names
+if ~isempty(varargin{end})
+    % determing matching names ignoring trailing '/'
+    L_no_trailing_fsep = regexprep(L, '/$', '');
+    I = regexp_fhandle(L_no_trailing_fsep, ['^' expression '$']);
+else
+    % determing matching names including trailing '/'
+    I = regexp_fhandle(L, ['^' expression '$']);
+end
+I = cellfun('isempty', I);
+L(I) = [];
+
+
+
+% ------------------------------------------------------------------------
+function d = dir_recur(startdir,keep_hidden)
+%% determine recursive directory contents
+
+% get directory contents
+d = dir(startdir);
+
+% remove hidden files
+if keep_hidden
+    % only remove '.' and '..'
+    d(strcmp({d.name},'.'))  = [];
+    d(strcmp({d.name},'..')) = [];
+else
+    % remove all hidden files and directories
+    d(strncmp({d.name},'.',1)) = [];
+end
+
+if ~isempty(d)
+    % add trailing fileseparator to directories
+    trailing_fsep = repmat({''}, size(d));
+    trailing_fsep([d.isdir]) = {'/'};
+    
+    % prefix startdir to name and postfix fileseparator for directories
+    dname = strcat(startdir, {d.name}, trailing_fsep');
+    [d(:).name] = deal(dname{:});
+    
+    % recurse into subdirectories
+    for subd = {d([d.isdir]).name}
+        d = [d; dir_recur(char(subd), keep_hidden)]; %#ok<AGROW>
+    end
+end