Fix existing Driver.is_me's crashes if path is a directory.
[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         self.filetype = 'mfp1d'
62         self.experiment = 'smfs'
63
64     def _load_from_file(self, filename, extract_note=False):
65         data = None
66         f = open(filename, 'rb')
67         ####################### ORDERING
68         # machine format for IEEE floating point with big-endian
69         # byte ordering
70         # MacIgor use the Motorola big-endian 'b'
71         # WinIgor use Intel little-endian 'l'
72         # If the first byte in the file is non-zero, then the file is a WinIgor
73         firstbyte = struct.unpack('b', f.read(1))[0]
74         if firstbyte == 0:
75             format = '>'
76         else:
77             format = '<'
78         #######################  CHECK VERSION
79         f.seek(0)
80         version = struct.unpack(format+'h', f.read(2))[0]
81         #######################  READ DATA AND ACCOMPANYING INFO
82         if version == 2 or version == 3:
83             # pre header
84             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.
85             noteSize = struct.unpack(format+'i', f.read(4))[0] # The size of the note text.
86             if version==3:
87                 formulaSize = struct.unpack(format+'i', f.read(4))[0]
88             pictSize = struct.unpack(format+'i', f.read(4))[0] # Reserved. Write zero. Ignore on read.
89             checksum = struct.unpack(format+'H', f.read(2))[0] # Checksum over this header and the wave header.
90             # wave header
91             dtype = struct.unpack(format+'h', f.read(2))[0]
92             if dtype == 2:
93                 dtype = numpy.float32(.0).dtype
94             elif dtype == 4:
95                 dtype = numpy.double(.0).dtype
96             else:
97                 assert False, "Wave is of type '%i', not supported" % dtype
98             dtype = dtype.newbyteorder(format)
99
100             ignore = f.read(4) # 1 uint32
101             bname = self._flatten(struct.unpack(format+'20c', f.read(20)))
102             ignore = f.read(4) # 2 int16
103             ignore = f.read(4) # 1 uint32
104             dUnits = self._flatten(struct.unpack(format+'4c', f.read(4)))
105             xUnits = self._flatten(struct.unpack(format+'4c', f.read(4)))
106             npnts = struct.unpack(format+'i', f.read(4))[0]
107             amod = struct.unpack(format+'h', f.read(2))[0]
108             dx = struct.unpack(format+'d', f.read(8))[0]
109             x0 = struct.unpack(format+'d', f.read(8))[0]
110             ignore = f.read(4) # 2 int16
111             fsValid = struct.unpack(format+'h', f.read(2))[0]
112             topFullScale = struct.unpack(format+'d', f.read(8))[0]
113             botFullScale = struct.unpack(format+'d', f.read(8))[0]
114             ignore = f.read(16) # 16 int8
115             modDate = struct.unpack(format+'I', f.read(4))[0]
116             ignore = f.read(4) # 1 uint32
117             # Numpy algorithm works a lot faster than struct.unpack
118             data = numpy.fromfile(f, dtype, npnts)
119
120         elif version == 5:
121             # pre header
122             checksum = struct.unpack(format+'H', f.read(2))[0] # Checksum over this header and the wave header.
123             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.
124             formulaSize = struct.unpack(format+'i', f.read(4))[0]
125             noteSize = struct.unpack(format+'i', f.read(4))[0] # The size of the note text.
126             dataEUnitsSize = struct.unpack(format+'i', f.read(4))[0]
127             dimEUnitsSize = struct.unpack(format+'4i', f.read(16))
128             dimLabelsSize = struct.unpack(format+'4i', f.read(16))
129             sIndicesSize = struct.unpack(format+'i', f.read(4))[0]
130             optionSize1 = struct.unpack(format+'i', f.read(4))[0]
131             optionSize2 = struct.unpack(format+'i', f.read(4))[0]
132
133             # header
134             ignore = f.read(4)
135             CreationDate =  struct.unpack(format+'I',f.read(4))[0]
136             modData =  struct.unpack(format+'I',f.read(4))[0]
137             npnts =  struct.unpack(format+'i',f.read(4))[0]
138             # wave header
139             dtype = struct.unpack(format+'h',f.read(2))[0]
140             if dtype == 2:
141                 dtype = numpy.float32(.0).dtype
142             elif dtype == 4:
143                 dtype = numpy.double(.0).dtype
144             else:
145                 assert False, "Wave is of type '%i', not supported" % dtype
146             dtype = dtype.newbyteorder(format)
147
148             ignore = f.read(2) # 1 int16
149             ignore = f.read(6) # 6 schar, SCHAR = SIGNED CHAR?         ignore = fread(fid,6,'schar'); #
150             ignore = f.read(2) # 1 int16
151             bname = self._flatten(struct.unpack(format+'32c',f.read(32)))
152             ignore = f.read(4) # 1 int32
153             ignore = f.read(4) # 1 int32
154             ndims = struct.unpack(format+'4i',f.read(16)) # Number of of items in a dimension -- 0 means no data.
155             sfA = struct.unpack(format+'4d',f.read(32))
156             sfB = struct.unpack(format+'4d',f.read(32))
157             dUnits = self._flatten(struct.unpack(format+'4c',f.read(4)))
158             xUnits = self._flatten(struct.unpack(format+'16c',f.read(16)))
159             fsValid = struct.unpack(format+'h',f.read(2))
160             whpad3 = struct.unpack(format+'h',f.read(2))
161             ignore = f.read(16) # 2 double
162             ignore = f.read(40) # 10 int32
163             ignore = f.read(64) # 16 int32
164             ignore = f.read(6) # 3 int16
165             ignore = f.read(2) # 2 char
166             ignore = f.read(4) # 1 int32
167             ignore = f.read(4) # 2 int16
168             ignore = f.read(4) # 1 int32
169             ignore = f.read(8) # 2 int32
170
171             data = numpy.fromfile(f, dtype, npnts)
172             note_str = f.read(noteSize)
173             if extract_note:
174                 note_lines = note_str.split('\r')
175                 self.note = {}
176                 for line in note_lines:
177                     if ':' in line:
178                         key, value = line.split(':', 1)
179                         self.note[key] = value
180                 self.retract_velocity = float(self.note['RetractVelocity'])
181                 self.spring_constant = float(self.note['SpringC'])
182         else:
183             assert False, "Fileversion is of type '%i', not supported" % dtype
184             data = []
185
186         f.close()
187         if len(data) > 0:
188             data_list = data.tolist()
189             count = len(data_list) / 2
190             return data_list[:count - 1], data_list[count:]
191         else:
192             return None
193
194     def _flatten(self, tup):
195         out = ''
196         for ch in tup:
197             out += ch
198         return out
199
200     def _read_columns(self):
201         extension = lib.curve.Data()
202         retraction = lib.curve.Data()
203
204         extension.y, retraction.y = self._load_from_file(self.filename, extract_note=True)
205         filename = self.filename.replace('deflection', 'LVDT', 1)
206         extension.x, retraction.x = self._load_from_file(filename, extract_note=False)
207         return [[extension.x, extension.y], [retraction.x, retraction.y]]
208
209     def close_all(self):
210         self.filedata.close()
211
212     def is_me(self):
213         if os.path.isdir(path):
214             return False
215         if len(self.lines) < 34:
216             return False
217
218         name, extension = os.path.splitext(self.filename)
219         #the following only exist in MFP1D files, not MFP-3D
220         #PullDist, PullDistSign, FastSamplingFrequency, SlowSamplingFrequency, FastDecimationFactor
221         #SlowDecimationFactor, IsDualPull, InitRetDist, RelaxDist, SlowTrigger, RelativeTrigger,
222         #EndOfNote
223         if extension == '.ibw' and 'deflection' in name:
224             #check if the corresponding LVDT file exists
225             filename = self.filename.replace('deflection', 'LVDT', 1)
226             if os.path.isfile(filename) and 'EndOfNote' in self.lines:
227                 return True
228             else:
229                 return False
230         else:
231             return False
232
233     def default_plots(self):
234         '''
235         loads the curve data
236         '''
237         defl_ext, defl_ret = self.deflection()
238
239         extension = lib.curve.Curve()
240         retraction = lib.curve.Curve()
241
242         extension.color = 'red'
243         extension.label = 'extension'
244         extension.style = 'plot'
245         extension.title = 'Force curve'
246         extension.units.x = 'm'
247         extension.units.y = 'N'
248         extension.x = self.data[0][0]
249         extension.y = [i * self.spring_constant for i in defl_ext]
250         retraction.color = 'blue'
251         retraction.label = 'retraction'
252         retraction.style = 'plot'
253         retraction.title = 'Force curve'
254         retraction.units.x = 'm'
255         retraction.units.y = 'N'
256         retraction.x = self.data[1][0]
257         retraction.y = [i * self.spring_constant for i in defl_ret]
258
259         plot = lib.plot.Plot()
260         plot.title = os.path.basename(self.filename)
261         plot.curves.append(extension)
262         plot.curves.append(retraction)
263
264         plot.normalize()
265         return plot
266
267     def deflection(self):
268         if not self.data:
269             self.data = self._read_columns()
270         return self.data[0][1], self.data[1][1]