test/data/vclamp_jpk/README: Document sample versions
[hooke.git] / hooke / driver / mfp1d.py
1 #!/usr/bin/env python
2
3 '''
4 mfp1d.py
5
6 Driver for MFP-1D files.
7
8 Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)
9 This driver is based on the work of R. Naud and A. Seeholzer (see below)
10 to read Igor binary waves. Code used with permission.
11
12 This program is released under the GNU General Public License version 2.
13 '''
14
15 # DEFINITION:
16 # Reads Igor's (Wavemetric) binary wave format, .ibw, files.
17 #
18 # ALGORITHM:
19 # Parsing proper to version 2, 3, or version 5 (see Technical notes TN003.ifn:
20 # http://mirror.optus.net.au/pub/wavemetrics/IgorPro/Technical_Notes/) and data
21 # type 2 or 4 (non complex, single or double precision vector, real values).
22 #
23 # AUTHORS:
24 # Matlab version: R. Naud August 2008 (http://lcn.epfl.ch/~naud/Home.html)
25 # Python port: A. Seeholzer October 2008
26 #
27 # VERSION: 0.1
28 #
29 # COMMENTS:
30 # Only tested for version 2 Igor files for now, testing for 3 and 5 remains to be done.
31 # More header data could be passed back if wished. For significance of ignored bytes see
32 # the technical notes linked above.
33
34 import numpy
35 import os.path
36 import struct
37
38 import lib.driver
39 import lib.curve
40 import lib.plot
41
42 __version__='0.0.0.20100225'
43
44 class mfp1dDriver(lib.driver.Driver):
45
46     def __init__(self, filename):
47         '''
48         This is a driver to import Asylum Research MFP-1D data.
49         Status: experimental
50         '''
51         self.data = []
52         self.note = []
53         self.retract_velocity = None
54         self.spring_constant = None
55         self.filename = filename
56
57         self.filedata = open(filename,'rU')
58         self.lines = list(self.filedata.readlines())
59         self.filedata.close()
60
61     def _load_from_file(self, filename, extract_note=False):
62         data = None
63         f = open(filename, 'rb')
64         ####################### ORDERING
65         # machine format for IEEE floating point with big-endian
66         # byte ordering
67         # MacIgor use the Motorola big-endian 'b'
68         # WinIgor use Intel little-endian 'l'
69         # If the first byte in the file is non-zero, then the file is a WinIgor
70         firstbyte = struct.unpack('b', f.read(1))[0]
71         if firstbyte == 0:
72             format = '>'
73         else:
74             format = '<'
75         #######################  CHECK VERSION
76         f.seek(0)
77         version = struct.unpack(format+'h', f.read(2))[0]
78         #######################  READ DATA AND ACCOMPANYING INFO
79         if version == 2 or version == 3:
80             # pre header
81             wfmSize = struct.unpack(format+'i', f.read(4))[0] # The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.
82             noteSize = struct.unpack(format+'i', f.read(4))[0] # The size of the note text.
83             if version==3:
84                 formulaSize = struct.unpack(format+'i', f.read(4))[0]
85             pictSize = struct.unpack(format+'i', f.read(4))[0] # Reserved. Write zero. Ignore on read.
86             checksum = struct.unpack(format+'H', f.read(2))[0] # Checksum over this header and the wave header.
87             # wave header
88             dtype = struct.unpack(format+'h', f.read(2))[0]
89             if dtype == 2:
90                 dtype = numpy.float32(.0).dtype
91             elif dtype == 4:
92                 dtype = numpy.double(.0).dtype
93             else:
94                 assert False, "Wave is of type '%i', not supported" % dtype
95             dtype = dtype.newbyteorder(format)
96
97             ignore = f.read(4) # 1 uint32
98             bname = self._flatten(struct.unpack(format+'20c', f.read(20)))
99             ignore = f.read(4) # 2 int16
100             ignore = f.read(4) # 1 uint32
101             dUnits = self._flatten(struct.unpack(format+'4c', f.read(4)))
102             xUnits = self._flatten(struct.unpack(format+'4c', f.read(4)))
103             npnts = struct.unpack(format+'i', f.read(4))[0]
104             amod = struct.unpack(format+'h', f.read(2))[0]
105             dx = struct.unpack(format+'d', f.read(8))[0]
106             x0 = struct.unpack(format+'d', f.read(8))[0]
107             ignore = f.read(4) # 2 int16
108             fsValid = struct.unpack(format+'h', f.read(2))[0]
109             topFullScale = struct.unpack(format+'d', f.read(8))[0]
110             botFullScale = struct.unpack(format+'d', f.read(8))[0]
111             ignore = f.read(16) # 16 int8
112             modDate = struct.unpack(format+'I', f.read(4))[0]
113             ignore = f.read(4) # 1 uint32
114             # Numpy algorithm works a lot faster than struct.unpack
115             data = numpy.fromfile(f, dtype, npnts)
116
117         elif version == 5:
118             # pre header
119             checksum = struct.unpack(format+'H', f.read(2))[0] # Checksum over this header and the wave header.
120             wfmSize = struct.unpack(format+'i', f.read(4))[0] # The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.
121             formulaSize = struct.unpack(format+'i', f.read(4))[0]
122             noteSize = struct.unpack(format+'i', f.read(4))[0] # The size of the note text.
123             dataEUnitsSize = struct.unpack(format+'i', f.read(4))[0]
124             dimEUnitsSize = struct.unpack(format+'4i', f.read(16))
125             dimLabelsSize = struct.unpack(format+'4i', f.read(16))
126             sIndicesSize = struct.unpack(format+'i', f.read(4))[0]
127             optionSize1 = struct.unpack(format+'i', f.read(4))[0]
128             optionSize2 = struct.unpack(format+'i', f.read(4))[0]
129
130             # header
131             ignore = f.read(4)
132             CreationDate =  struct.unpack(format+'I',f.read(4))[0]
133             modData =  struct.unpack(format+'I',f.read(4))[0]
134             npnts =  struct.unpack(format+'i',f.read(4))[0]
135             # wave header
136             dtype = struct.unpack(format+'h',f.read(2))[0]
137             if dtype == 2:
138                 dtype = numpy.float32(.0).dtype
139             elif dtype == 4:
140                 dtype = numpy.double(.0).dtype
141             else:
142                 assert False, "Wave is of type '%i', not supported" % dtype
143             dtype = dtype.newbyteorder(format)
144
145             ignore = f.read(2) # 1 int16
146             ignore = f.read(6) # 6 schar, SCHAR = SIGNED CHAR?         ignore = fread(fid,6,'schar'); #
147             ignore = f.read(2) # 1 int16
148             bname = self._flatten(struct.unpack(format+'32c',f.read(32)))
149             ignore = f.read(4) # 1 int32
150             ignore = f.read(4) # 1 int32
151             ndims = struct.unpack(format+'4i',f.read(16)) # Number of of items in a dimension -- 0 means no data.
152             sfA = struct.unpack(format+'4d',f.read(32))
153             sfB = struct.unpack(format+'4d',f.read(32))
154             dUnits = self._flatten(struct.unpack(format+'4c',f.read(4)))
155             xUnits = self._flatten(struct.unpack(format+'16c',f.read(16)))
156             fsValid = struct.unpack(format+'h',f.read(2))
157             whpad3 = struct.unpack(format+'h',f.read(2))
158             ignore = f.read(16) # 2 double
159             ignore = f.read(40) # 10 int32
160             ignore = f.read(64) # 16 int32
161             ignore = f.read(6) # 3 int16
162             ignore = f.read(2) # 2 char
163             ignore = f.read(4) # 1 int32
164             ignore = f.read(4) # 2 int16
165             ignore = f.read(4) # 1 int32
166             ignore = f.read(8) # 2 int32
167
168             data = numpy.fromfile(f, dtype, npnts)
169             note_str = f.read(noteSize)
170             if extract_note:
171                 note_lines = note_str.split('\r')
172                 self.note = {}
173                 for line in note_lines:
174                     if ':' in line:
175                         key, value = line.split(':', 1)
176                         self.note[key] = value
177                 self.retract_velocity = float(self.note['RetractVelocity'])
178                 self.spring_constant = float(self.note['SpringC'])
179         else:
180             assert False, "Fileversion is of type '%i', not supported" % dtype
181             data = []
182
183         f.close()
184         if len(data) > 0:
185             data_list = data.tolist()
186             count = len(data_list) / 2
187             return data_list[:count - 1], data_list[count:]
188         else:
189             return None
190
191     def _flatten(self, tup):
192         out = ''
193         for ch in tup:
194             out += ch
195         return out
196
197     def _read_columns(self):
198         extension = lib.curve.Data()
199         retraction = lib.curve.Data()
200
201         extension.y, retraction.y = self._load_from_file(self.filename, extract_note=True)
202         filename = self.filename.replace('deflection', 'LVDT', 1)
203         extension.x, retraction.x = self._load_from_file(filename, extract_note=False)
204         return [[extension.x, extension.y], [retraction.x, retraction.y]]
205
206     def close_all(self):
207         self.filedata.close()
208
209     def is_me(self):
210         if os.path.isdir(path):
211             return False
212         if len(self.lines) < 34:
213             return False
214
215         name, extension = os.path.splitext(self.filename)
216         #the following only exist in MFP1D files, not MFP-3D
217         #PullDist, PullDistSign, FastSamplingFrequency, SlowSamplingFrequency, FastDecimationFactor
218         #SlowDecimationFactor, IsDualPull, InitRetDist, RelaxDist, SlowTrigger, RelativeTrigger,
219         #EndOfNote
220         if extension == '.ibw' and 'deflection' in name:
221             #check if the corresponding LVDT file exists
222             filename = self.filename.replace('deflection', 'LVDT', 1)
223             if os.path.isfile(filename) and 'EndOfNote' in self.lines:
224                 return True
225             else:
226                 return False
227         else:
228             return False
229
230     def default_plots(self):
231         '''
232         loads the curve data
233         '''
234         defl_ext, defl_ret = self.deflection()
235
236         extension = lib.curve.Curve()
237         retraction = lib.curve.Curve()
238
239         extension.color = 'red'
240         extension.label = 'extension'
241         extension.style = 'plot'
242         extension.title = 'Force curve'
243         extension.units.x = 'm'
244         extension.units.y = 'N'
245         extension.x = self.data[0][0]
246         extension.y = [i * self.spring_constant for i in defl_ext]
247         retraction.color = 'blue'
248         retraction.label = 'retraction'
249         retraction.style = 'plot'
250         retraction.title = 'Force curve'
251         retraction.units.x = 'm'
252         retraction.units.y = 'N'
253         retraction.x = self.data[1][0]
254         retraction.y = [i * self.spring_constant for i in defl_ret]
255
256         plot = lib.plot.Plot()
257         plot.title = os.path.basename(self.filename)
258         plot.curves.append(extension)
259         plot.curves.append(retraction)
260
261         plot.normalize()
262         return plot
263
264     def deflection(self):
265         if not self.data:
266             self.data = self._read_columns()
267         return self.data[0][1], self.data[1][1]