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