Merged my unitary FFT wrappers (FFT_tools) as hooke.util.fft.
[hooke.git] / hooke / driver / mfp3d.py
1 # Copyright (C) 2008-2010 A. Seeholzer
2 #                         Alberto Gomez-Casado
3 #                         Richard Naud <richard.naud@epfl.ch>
4 #                         Rolf Schmidt <rschmidt@alcor.concordia.ca>
5 #                         W. Trevor King <wking@drexel.edu>
6 #
7 # This file is part of Hooke.
8 #
9 # Hooke is free software: you can redistribute it and/or
10 # modify it under the terms of the GNU Lesser General Public
11 # License as published by the Free Software Foundation, either
12 # version 3 of the License, or (at your option) any later version.
13 #
14 # Hooke is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU Lesser General Public License for more details.
18 #
19 # You should have received a copy of the GNU Lesser General Public
20 # License along with Hooke.  If not, see
21 # <http://www.gnu.org/licenses/>.
22
23 """Driver for MFP-3D files.
24
25 This driver reads Igor binary waves.
26
27 AUTHORS:
28 Matlab version: Richard Naud August 2008 (http://lcn.epfl.ch/~naud/)
29 Python port: A. Seeholzer October 2008
30 Hooke submission: Rolf Schmidt, Alberto Gomez-Casado 2009
31 """
32
33 # DEFINITION:
34 # Reads Igor's (Wavemetric) binary wave format, .ibw, files.
35 #
36 # ALGORITHM:
37 # Parsing proper to version 2, 3, or version 5 (see Technical notes TN003.ifn:
38 # http://mirror.optus.net.au/pub/wavemetrics/IgorPro/Technical_Notes/) and data
39 # type 2 or 4 (non complex, single or double precision vector, real values).
40 #
41 # VERSION: 0.1
42 #
43 # COMMENTS:
44 # Only tested for version 2 Igor files for now, testing for 3 and 5 remains to be done.
45 # More header data could be passed back if wished. For significance of ignored bytes see
46 # the technical notes linked above.
47
48 import numpy
49 import os.path
50 import struct
51
52 from .. import curve as lhc
53
54
55 __version__='0.0.0.20100310'
56
57
58 class DataChunk(list):
59     #Dummy class to provide ext and ret methods to the data list.
60     
61     def ext(self):
62         halflen=(len(self)/2)
63         return self[0:halflen]
64         
65     def ret(self):
66         halflen=(len(self)/2)
67         return self[halflen:]
68
69 class mfp3dDriver(lhc.Driver):
70
71     #Construction and other special methods
72     
73     def __init__(self,filename):
74         '''
75         constructor method
76         '''
77            
78         self.textfile    =file(filename)
79         self.binfile=file(filename,'rb')
80         #unnecesary, but some other part of the program expects these to be open     
81
82         self.forcechunk=0
83         self.distancechunk=1
84         #TODO eliminate the need to set chunk numbers
85         
86         self.filepath=filename
87         self.debug=True
88         
89         self.data = []
90         self.note = []
91         self.retract_velocity = None
92         self.spring_constant = None
93         self.filename = filename
94
95         self.filedata = open(filename,'rU')
96         self.lines = list(self.filedata.readlines())
97         self.filedata.close()
98
99         self.filetype = 'mfp3d'
100         self.experiment = 'smfs'
101              
102      
103     def _get_data_chunk(self,whichchunk):
104
105         data = None
106         f = open(self.filename, 'rb')
107         ####################### ORDERING
108         # machine format for IEEE floating point with big-endian
109         # byte ordering
110         # MacIgor use the Motorola big-endian 'b'
111         # WinIgor use Intel little-endian 'l'
112         # If the first byte in the file is non-zero, then the file is a WinIgor
113         firstbyte = struct.unpack('b', f.read(1))[0]
114         if firstbyte == 0:
115             format = '>'
116         else:
117             format = '<'
118         #######################  CHECK VERSION
119         f.seek(0)
120         version = struct.unpack(format+'h', f.read(2))[0]
121         #######################  READ DATA AND ACCOMPANYING INFO
122         if version == 2 or version == 3:
123             # pre header
124             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.
125             noteSize = struct.unpack(format+'i', f.read(4))[0] # The size of the note text.
126             if version==3:
127                 formulaSize = struct.unpack(format+'i', f.read(4))[0]
128             pictSize = struct.unpack(format+'i', f.read(4))[0] # Reserved. Write zero. Ignore on read.
129             checksum = struct.unpack(format+'H', f.read(2))[0] # Checksum over this header and the wave header.
130             # wave header
131             dtype = struct.unpack(format+'h', f.read(2))[0]
132             if dtype == 2:
133                 dtype = numpy.float32(.0).dtype
134             elif dtype == 4:
135                 dtype = numpy.double(.0).dtype
136             else:
137                 assert False, "Wave is of type '%i', not supported" % dtype
138             dtype = dtype.newbyteorder(format)
139
140             ignore = f.read(4) # 1 uint32
141             bname = self._flatten(struct.unpack(format+'20c', f.read(20)))
142             ignore = f.read(4) # 2 int16
143             ignore = f.read(4) # 1 uint32
144             dUnits = self._flatten(struct.unpack(format+'4c', f.read(4)))
145             xUnits = self._flatten(struct.unpack(format+'4c', f.read(4)))
146             npnts = struct.unpack(format+'i', f.read(4))[0]
147             amod = struct.unpack(format+'h', f.read(2))[0]
148             dx = struct.unpack(format+'d', f.read(8))[0]
149             x0 = struct.unpack(format+'d', f.read(8))[0]
150             ignore = f.read(4) # 2 int16
151             fsValid = struct.unpack(format+'h', f.read(2))[0]
152             topFullScale = struct.unpack(format+'d', f.read(8))[0]
153             botFullScale = struct.unpack(format+'d', f.read(8))[0]
154             ignore = f.read(16) # 16 int8
155             modDate = struct.unpack(format+'I', f.read(4))[0]
156             ignore = f.read(4) # 1 uint32
157             # Numpy algorithm works a lot faster than struct.unpack
158             data = numpy.fromfile(f, dtype, npnts)
159
160         elif version == 5:
161             # pre header
162             checksum = struct.unpack(format+'H', f.read(2))[0] # Checksum over this header and the wave header.
163             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.
164             formulaSize = struct.unpack(format+'i', f.read(4))[0]
165             noteSize = struct.unpack(format+'i', f.read(4))[0] # The size of the note text.
166             dataEUnitsSize = struct.unpack(format+'i', f.read(4))[0]
167             dimEUnitsSize = struct.unpack(format+'4i', f.read(16))
168             dimLabelsSize = struct.unpack(format+'4i', f.read(16))
169             sIndicesSize = struct.unpack(format+'i', f.read(4))[0]
170             optionSize1 = struct.unpack(format+'i', f.read(4))[0]
171             optionSize2 = struct.unpack(format+'i', f.read(4))[0]
172
173             # header
174             ignore = f.read(4)
175             CreationDate =  struct.unpack(format+'I',f.read(4))[0]
176             modData =  struct.unpack(format+'I',f.read(4))[0]
177             npnts =  struct.unpack(format+'i',f.read(4))[0]
178             # wave header
179             dtype = struct.unpack(format+'h',f.read(2))[0]
180             if dtype == 2:
181                 dtype = numpy.float32(.0).dtype
182             elif dtype == 4:
183                 dtype = numpy.double(.0).dtype
184             else:
185                 assert False, "Wave is of type '%i', not supported" % dtype
186             dtype = dtype.newbyteorder(format)
187
188             ignore = f.read(2) # 1 int16
189             ignore = f.read(6) # 6 schar, SCHAR = SIGNED CHAR?         ignore = fread(fid,6,'schar'); #
190             ignore = f.read(2) # 1 int16
191             bname = self._flatten(struct.unpack(format+'32c',f.read(32)))
192             ignore = f.read(4) # 1 int32
193             ignore = f.read(4) # 1 int32
194             ndims = struct.unpack(format+'4i',f.read(16)) # Number of of items in a dimension -- 0 means no data.
195             sfA = struct.unpack(format+'4d',f.read(32))
196             sfB = struct.unpack(format+'4d',f.read(32))
197             dUnits = self._flatten(struct.unpack(format+'4c',f.read(4)))
198             xUnits = self._flatten(struct.unpack(format+'16c',f.read(16)))
199             fsValid = struct.unpack(format+'h',f.read(2))
200             whpad3 = struct.unpack(format+'h',f.read(2))
201             ignore = f.read(16) # 2 double
202             ignore = f.read(40) # 10 int32
203             ignore = f.read(64) # 16 int32
204             ignore = f.read(6) # 3 int16
205             ignore = f.read(2) # 2 char
206             ignore = f.read(4) # 1 int32
207             ignore = f.read(4) # 2 int16
208             ignore = f.read(4) # 1 int32
209             ignore = f.read(8) # 2 int32
210
211             data = numpy.fromfile(f, dtype, npnts)
212             note_str = f.read(noteSize)
213             note_lines = note_str.split('\r')
214             self.note = {}
215             for line in note_lines:
216                 if ':' in line:
217                     key, value = line.split(':', 1)
218                     self.note[key] = value
219             self.retract_velocity = float(self.note['Velocity'])
220             self.spring_constant = float(self.note['SpringConstant'])
221         else:
222             assert False, "Fileversion is of type '%i', not supported" % dtype
223             data = []
224
225         f.close()
226         if len(data) > 0:
227             #we have 3 columns: deflection, LVDT, raw
228             #TODO detect which is each one
229             count = npnts / 3
230             lvdt = data[:count] 
231             deflection = data[count:2 * count] 
232             #every column contains data for extension and retraction
233             #we assume the same number of points for each
234             #we could possibly extract this info from the note
235             count = npnts / 6
236
237             forcechunk=deflection*self.spring_constant
238             distancechunk=lvdt
239         
240             if  whichchunk==self.forcechunk:
241                 return forcechunk
242             if whichchunk==self.distancechunk:
243                 return distancechunk
244         else:
245             return None                          
246         
247     def _force(self):
248         #returns force vector
249         Kspring=self.spring_constant
250         return DataChunk([(meter*Kspring) for meter in self._deflection()])
251
252     def _deflection(self):
253         #for internal use (feeds _force)
254         deflect=self.data_chunks[self.forcechunk]/self.spring_constant               
255         return deflect
256
257     def _flatten(self, tup):
258         out = ''
259         for ch in tup:
260             out += ch
261         return out            
262     
263     def _Z(self):   
264         return DataChunk(self.data_chunks[self.distancechunk])
265         
266     def is_me(self):
267         if len(self.lines) < 34:
268             return False
269
270         name, extension = os.path.splitext(self.filename)
271         if extension == '.ibw':
272             for line in self.lines:
273                 if line.startswith('ForceNote:'):
274                     self.data_chunks=[self._get_data_chunk(num) for num in [0,1,2]]
275                     return True
276             else:
277                 return False
278         else:
279             return False
280     
281     def close_all(self):
282         '''
283         Explicitly closes all files
284         '''
285         self.textfile.close()
286         self.binfile.close()
287     
288     def default_plots(self):
289         '''
290         creates the default PlotObject
291         '''
292         force=self._force()
293         zdomain=self._Z()
294         main_plot=lhc.PlotObject()
295         main_plot.vectors=[[zdomain.ext(), force.ext()],[zdomain.ret(), force.ret()]]
296         main_plot.normalize_vectors()
297         main_plot.units=['meters','newton']
298         main_plot.destination=0
299         main_plot.title=self.filepath
300         
301         
302         return [main_plot]
303
304     def deflection(self):
305         #interface for correct plotmanip and others
306         deflectionchunk=DataChunk(self._deflection())
307         return deflectionchunk.ext(),deflectionchunk.ret()