1 # Copyright (C) 2008-2010 A. Seeholzer
3 # Richard Naud <richard.naud@epfl.ch>
4 # Rolf Schmidt <rschmidt@alcor.concordia.ca>
5 # W. Trevor King <wking@drexel.edu>
7 # This file is part of Hooke.
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.
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.
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/>.
23 """Driver for MFP-3D files.
25 This driver reads Igor binary waves.
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
34 # Reads Igor's (Wavemetric) binary wave format, .ibw, files.
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).
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.
52 from .. import curve as lhc
55 __version__='0.0.0.20100310'
58 class DataChunk(list):
59 #Dummy class to provide ext and ret methods to the data list.
63 return self[0:halflen]
69 class mfp3dDriver(lhc.Driver):
71 #Construction and other special methods
73 def __init__(self,filename):
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
84 #TODO eliminate the need to set chunk numbers
86 self.filepath=filename
91 self.retract_velocity = None
92 self.spring_constant = None
93 self.filename = filename
95 self.filedata = open(filename,'rU')
96 self.lines = list(self.filedata.readlines())
99 self.filetype = 'mfp3d'
100 self.experiment = 'smfs'
103 def _get_data_chunk(self,whichchunk):
106 f = open(self.filename, 'rb')
107 ####################### ORDERING
108 # machine format for IEEE floating point with big-endian
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]
118 ####################### CHECK VERSION
120 version = struct.unpack(format+'h', f.read(2))[0]
121 ####################### READ DATA AND ACCOMPANYING INFO
122 if version == 2 or version == 3:
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.
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.
131 dtype = struct.unpack(format+'h', f.read(2))[0]
133 dtype = numpy.float32(.0).dtype
135 dtype = numpy.double(.0).dtype
137 assert False, "Wave is of type '%i', not supported" % dtype
138 dtype = dtype.newbyteorder(format)
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)
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]
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]
179 dtype = struct.unpack(format+'h',f.read(2))[0]
181 dtype = numpy.float32(.0).dtype
183 dtype = numpy.double(.0).dtype
185 assert False, "Wave is of type '%i', not supported" % dtype
186 dtype = dtype.newbyteorder(format)
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
211 data = numpy.fromfile(f, dtype, npnts)
212 note_str = f.read(noteSize)
213 note_lines = note_str.split('\r')
215 for line in note_lines:
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'])
222 assert False, "Fileversion is of type '%i', not supported" % dtype
227 #we have 3 columns: deflection, LVDT, raw
228 #TODO detect which is each one
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
237 forcechunk=deflection*self.spring_constant
240 if whichchunk==self.forcechunk:
242 if whichchunk==self.distancechunk:
248 #returns force vector
249 Kspring=self.spring_constant
250 return DataChunk([(meter*Kspring) for meter in self._deflection()])
252 def _deflection(self):
253 #for internal use (feeds _force)
254 deflect=self.data_chunks[self.forcechunk]/self.spring_constant
257 def _flatten(self, tup):
264 return DataChunk(self.data_chunks[self.distancechunk])
267 if len(self.lines) < 34:
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]]
283 Explicitly closes all files
285 self.textfile.close()
288 def default_plots(self):
290 creates the default PlotObject
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
304 def deflection(self):
305 #interface for correct plotmanip and others
306 deflectionchunk=DataChunk(self._deflection())
307 return deflectionchunk.ext(),deflectionchunk.ret()