5e5c58f6ba6c6b6f5f830667c66cba89eba2dce7
[hooke.git] / hooke / driver / mfp3d.py
1 # Copyright (C) 2008-2010 A. Seeholzer
2 #                         Alberto Gomez-Casado
3 #                         Richard Naud <richard.naud@epfl.ch>
4 #                         Rolf Schmidt <rschmidt@alcor.concordia.ca>
5 #                         W. Trevor King <wking@drexel.edu>
6 #
7 # This file is part of Hooke.
8 #
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.
13 #
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.
18 #
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/>.
22
23 """Driver for MFP-3D files.
24
25 This driver reads IGOR binary waves.
26
27 AUTHORS:
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
31 """
32
33 import copy
34 import os.path
35 import pprint
36
37 import numpy
38
39 from .. import curve as curve
40 from .. import experiment as experiment
41 from . import Driver as Driver
42 from .igorbinarywave import loadibw
43
44
45 __version__='0.0.0.20100604'
46
47
48 class MFP3DDriver (Driver):
49     """Handle Asylum Research's MFP3D data format.
50     """
51     def __init__(self):
52         super(MFP3DDriver, self).__init__(name='mfp3d')
53
54     def is_me(self, path):
55         """Look for identifying fields in the IBW note.
56         """
57         if os.path.isdir(path):
58             return False
59         if not path.endswith('.ibw'):
60             return False
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)):
65                 f,t = ft
66                 if f == False and line.startswith(t):
67                     found[i] = True
68         if min(found) == True:
69             return True
70         return False
71     
72     def read(self, path, info=None):
73         data,bin_info,wave_info = loadibw(path)
74         approach,retract = self._translate_ibw(data, bin_info, wave_info)
75
76         info = {'filetype':self.name, 'experiment':experiment.VelocityClamp}
77         return ([approach, retract], info)
78      
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.
84
85         # Parse the note into a dictionary
86         note = {}
87         for line in bin_info['note'].split('\r'):
88             fields = [x.strip() for x in line.split(':', 1)]
89             key = fields[0]
90             if len(fields) == 2:
91                 value = fields[1]
92             else:
93                 value = None
94             note[key] = value
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)))
101
102         info = {
103             'raw info':{'bin':bin_info,
104                         'wave':wave_info},
105             'time':wave_info['creationDate'],
106             'spring constant (N/m)':note['SpringConstant'],
107             }
108         # MFP3D's native data dimensions match Hooke's (<point>, <column>) layout.
109         approach = self._scale_block(data[:wave_info['npnts']/2,:], info, 'approach')
110         retract = self._scale_block(data[wave_info['npnts']/2:,:], info, 'retract')
111         return (approach, retract)
112
113     def _scale_block(self, data, info, name):
114         """Convert the block from its native format to a `numpy.float`
115         array in SI units.
116         """
117         shape = 3
118         # raw column indices
119         columns = info['raw info']['bin']['dimLabels'][1]
120         # Depending on your MFP3D version:
121         #   VerDate 80501.0207: ['Raw', 'Defl', 'LVDT', 'Time']
122         #   VerDate 80501.041:  ['Raw', 'Defl', 'LVDT']
123         if 'Time' in columns:
124             n_col = 3
125         else:
126             n_col = 2
127         ret = curve.Data(
128             shape=(data.shape[0], n_col),
129             dtype=numpy.float,
130             info=copy.deepcopy(info)
131             )
132         ret.info['name'] = name
133         ret.info['raw data'] = data # store the raw data
134
135         z_rcol = columns.index('LVDT')
136         d_rcol = columns.index('Defl')
137
138         # scaled column indices
139         ret.info['columns'] = ['z piezo (m)', 'deflection (m)']
140         z_scol = ret.info['columns'].index('z piezo (m)')
141         d_scol = ret.info['columns'].index('deflection (m)')
142
143         # Leading '-' because increasing voltage extends the piezo,
144         # moving the tip towards the surface (positive indentation),
145         # but it makes more sense to me to have it increase away from
146         # the surface (positive separation).
147         ret[:,z_scol] = -data[:,z_rcol].astype(ret.dtype)
148
149         # Leading '-' because deflection voltage increases as the tip
150         # moves away from the surface, but it makes more sense to me
151         # to have it increase as it moves toward the surface (positive
152         # tension on the protein chain).
153         ret[:,d_scol] = -data[:,d_rcol]
154
155         if 'Time' in columns:
156             ret.info['columns'].append('time (s)')
157             t_rcol = columns.index('Time')
158             t_scol = ret.info['columns'].index('time (s)')
159             ret[:,t_scol] = data[:,t_rcol]
160
161         return ret