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 modify it
10 # under the terms of the GNU Lesser General Public License as
11 # published by the Free Software Foundation, either version 3 of the
12 # License, or (at your option) any later version.
14 # Hooke is distributed in the hope that it will be useful, but WITHOUT
15 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
17 # 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
39 from .. import curve as curve
40 from .. import experiment as experiment
41 from . import Driver as Driver
42 from .igorbinarywave import loadibw
45 __version__='0.0.0.20100604'
48 class MFP3DDriver (Driver):
49 """Handle Asylum Research's MFP3D data format.
52 super(MFP3DDriver, self).__init__(name='mfp3d')
54 def is_me(self, path):
55 """Look for identifying fields in the IBW note.
57 if os.path.isdir(path):
59 if not path.endswith('.ibw'):
61 targets = ['Version:', 'XOPVersion:', 'ForceNote:']
62 found = [False]*len(targets)
63 for line in open(path, 'rU'):
64 for i,ft in enumerate(zip(found, targets)):
66 if f == False and line.startswith(t):
68 if min(found) == True:
72 def read(self, path, info=None):
73 data,bin_info,wave_info = loadibw(path)
74 approach,retract,info = self._translate_ibw(data, bin_info, wave_info)
75 info['filetype'] = self.name
76 info['experiment'] = experiment.VelocityClamp
77 return ([approach, retract], info)
79 def _translate_ibw(self, data, bin_info, wave_info):
80 if bin_info['version'] != 5:
81 raise NotImplementedError('IBW version %d (< 5) not supported'
82 % bin_info['version'])
83 # We need version 5 for multidimensional arrays.
85 # Parse the note into a dictionary
87 for line in bin_info['note'].split('\r'):
88 fields = [x.strip() for x in line.split(':', 1)]
95 bin_info['note'] = note
96 if note['VerDate'] not in ['80501.041', '80501.0207']:
97 raise Exception(note['VerDate'])
98 raise NotImplementedError(
99 '%s file version %s not supported (yet!)\n%s'
100 % (self.name, note['VerDate'], pprint.pformat(note)))
103 'raw info':{'bin':bin_info,
105 'time':wave_info['creationDate'],
106 'spring constant (N/m)':float(note['SpringConstant']),
107 'temperature (K)':self._temperature(note),
109 # MFP3D's native data dimensions match Hooke's (<point>, <column>) layout.
110 approach = self._scale_block(data[:wave_info['npnts']/2,:], info, 'approach')
111 retract = self._scale_block(data[wave_info['npnts']/2:,:], info, 'retract')
112 return (approach, retract, info)
114 def _scale_block(self, data, info, name):
115 """Convert the block from its native format to a `numpy.float`
120 columns = info['raw info']['bin']['dimLabels'][1]
121 # Depending on your MFP3D version:
122 # VerDate 80501.0207: ['Raw', 'Defl', 'LVDT', 'Time']
123 # VerDate 80501.041: ['Raw', 'Defl', 'LVDT']
124 if 'Time' in columns:
129 shape=(data.shape[0], n_col),
131 info=copy.deepcopy(info)
133 ret.info['name'] = name
134 ret.info['raw data'] = data # store the raw data
136 z_rcol = columns.index('LVDT')
137 d_rcol = columns.index('Defl')
139 # scaled column indices
140 ret.info['columns'] = ['z piezo (m)', 'deflection (m)']
141 z_scol = ret.info['columns'].index('z piezo (m)')
142 d_scol = ret.info['columns'].index('deflection (m)')
144 # Leading '-' because increasing voltage extends the piezo,
145 # moving the tip towards the surface (positive indentation),
146 # but it makes more sense to me to have it increase away from
147 # the surface (positive separation).
148 ret[:,z_scol] = -data[:,z_rcol].astype(ret.dtype)
150 # Leading '-' because deflection voltage increases as the tip
151 # moves away from the surface, but it makes more sense to me
152 # to have it increase as it moves toward the surface (positive
153 # tension on the protein chain).
154 ret[:,d_scol] = -data[:,d_rcol]
156 if 'Time' in columns:
157 ret.info['columns'].append('time (s)')
158 t_rcol = columns.index('Time')
159 t_scol = ret.info['columns'].index('time (s)')
160 ret[:,t_scol] = data[:,t_rcol]
164 def _temperature(self, note):
165 # I'm not sure which field we should be using here. Options are:
171 # I imagine the 'Start*Temp' fields were measured at
172 # 'StartTempSeconds' at the beginning of a series of curves,
173 # while our particular curve was initiated at 'Seconds'.
174 # python -c "from hooke.hooke import Hooke;
176 # h.run_command('load playlist',
177 # {'input':'test/data/vclamp_mfp3d/playlist'});
178 # x = [(int(c.info['raw info']['bin']['note']['Seconds'])
179 # - int(c.info['raw info']['bin']['note']['StartTempSeconds']))
180 # for c in h.playlists.current().items()];
181 # print 'average', float(sum(x))/len(x);
182 # print 'range', min(x), max(x);
184 # For the Line*Point*.ibw series, the difference increases slowly
185 # 46, 46, 47, 47, 48, 49, 49, 50, 50, 51, 51, 52, 52, 53, 53, 54,...
186 # However, for the Image*.ibw series, the difference increase
188 # 21, 38, 145, 150, 171, 181
189 # This makes the 'Start*Temp' fields less and less relevant as
190 # the experiment continues. Still, I suppose it's better than
193 # The 'Thermal' fields seem to be related to cantilever calibration.
194 celsius = unicode(note['StartHeadTemp'], 'latin-1')
195 if celsius.endswith(u' \u00b0C'):
196 number = celsius.split(None, 1)[0]
197 return float(number) + 273.15 # Convert to Kelvin.
199 raise NotImplementedError(
200 'unkown temperature format: %s' % repr(celsius))