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