Make file-format-version=0.5 support explicit in JPKDriver
[hooke.git] / hooke / driver / igorbinarywave.py
index d0cf2e91bd0dadb3517eb3de58168e12dd4828d7..209bbc70c3d51c99fdfd9fe2215d8310b10287e6 100644 (file)
@@ -1,26 +1,31 @@
 #!/usr/bin/python
 #
-# igorbinarywave provides pure Python interface between IGOR Binary
-# Wave files and Numpy arrays.
-#
 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Lesser General Public License for more details.
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
 # <http://www.gnu.org/licenses/>.
 
+"""igorbinarywave provides pure Python interface between IGOR Binary
+Wave files and Numpy arrays.
+
+This is basically a stand-alone package that we bundle into Hooke for
+convenience.  It is used by the mfp*d drivers, whose data is saved in
+IBW files.
+"""
+
 # Based on WaveMetric's Technical Note 003, "Igor Binary Format"
 #   ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN003.zip
 # From ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN000.txt
@@ -469,11 +474,85 @@ def loadibw(filename):
             ('%d, %d, %d, %s' % (waveDataSize, wave_info['npnts'], t.itemsize, t))
         tail_data = array.array('f', b[-tail:])
         data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail))
+        if version == 5:
+            shape = [n for n in wave_info['nDim'] if n > 0]
+        else:
+            shape = (wave_info['npnts'],)
         data = numpy.ndarray(
-            wave_info['npnts'],
+            shape=shape,
             dtype=t.newbyteorder(byteOrder),
-            buffer=data_b
+            buffer=data_b,
+            order='F',
             )
+
+        if version == 1:
+            pass  # No post-data information
+        elif version == 2:
+            # Post-data info:
+            #   * 16 bytes of padding
+            #   * Optional wave note data
+            pad_b = buffer(f.read(16))  # skip the padding
+            assert max(pad_b) == 0, pad_b
+            bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
+        elif version == 3:
+            # Post-data info:
+            #   * 16 bytes of padding
+            #   * Optional wave note data
+            #   * Optional wave dependency formula
+            """Excerpted from TN003:
+
+            A wave has a dependency formula if it has been bound by a
+            statement such as "wave0 := sin(x)". In this example, the
+            dependency formula is "sin(x)". The formula is stored with
+            no trailing null byte.
+            """
+            pad_b = buffer(f.read(16))  # skip the padding
+            assert max(pad_b) == 0, pad_b
+            bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
+            bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip()
+        elif version == 5:
+            # Post-data info:
+            #   * Optional wave dependency formula
+            #   * Optional wave note data
+            #   * Optional extended data units data
+            #   * Optional extended dimension units data
+            #   * Optional dimension label data
+            #   * String indices used for text waves only
+            """Excerpted from TN003:
+
+            dataUnits - Present in versions 1, 2, 3, 5. The dataUnits
+              field stores the units for the data represented by the
+              wave. It is a C string terminated with a null
+              character. This field supports units of 0 to 3 bytes. In
+              version 1, 2 and 3 files, longer units can not be
+              represented. In version 5 files, longer units can be
+              stored using the optional extended data units section of
+              the file.
+
+            xUnits - Present in versions 1, 2, 3. The xUnits field
+              stores the X units for a wave. It is a C string
+              terminated with a null character.  This field supports
+              units of 0 to 3 bytes. In version 1, 2 and 3 files,
+              longer units can not be represented.
+
+            dimUnits - Present in version 5 only. This field is an
+              array of 4 strings, one for each possible wave
+              dimension. Each string supports units of 0 to 3
+              bytes. Longer units can be stored using the optional
+              extended dimension units section of the file.
+            """
+            bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip()
+            bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
+            bin_info['dataEUnits'] = str(f.read(bin_info['dataEUnitsSize'])).strip()
+            bin_info['dimEUnits'] = [
+                str(f.read(size)).strip() for size in bin_info['dimEUnitsSize']]
+            bin_info['dimLabels'] = []
+            for size in bin_info['dimLabelsSize']:
+                labels = str(f.read(size)).split(chr(0)) # split null-delimited strings
+                bin_info['dimLabels'].append([L for L in labels if len(L) > 0])
+            if wave_info['type'] == 0:  # text wave
+                bin_info['sIndices'] = f.read(bin_info['sIndicesSize'])
+
     finally:
         if not hasattr(filename, 'read'):
             f.close()
@@ -517,7 +596,7 @@ if __name__ == '__main__':
         options.outfile = sys.stdout
 
     data,bin_info,wave_info = loadibw(options.infile)
-    numpy.savetxt(options.outfile, data, fmt='%g', delimiter='\n')
+    numpy.savetxt(options.outfile, data, fmt='%g', delimiter='\t')
     if options.verbose > 0:
         import pprint
         pprint.pprint(bin_info)