Replace .config rather than reconstructing plugins, drivers, and UIs.
[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 pprint
35
36 import numpy
37
38 from .. import curve as curve
39 from .. import experiment as experiment
40 from . import Driver as Driver
41 from .igorbinarywave import loadibw
42
43
44 __version__='0.0.0.20100604'
45
46
47 class MFP3DDriver (Driver):
48     """Handle Asylum Research's MFP3D data format.
49     """
50     def __init__(self):
51         super(MFP3DDriver, self).__init__(name='mfp3d')
52
53     def is_me(self, path):
54         """Look for identifying fields in the IBW note.
55         """
56         if not path.endswith('.ibw'):
57             return False
58         targets = ['Version:', 'XOPVersion:', 'ForceNote:']
59         found = [False]*len(targets)
60         for line in open(path, 'rU'):
61             for i,ft in enumerate(zip(found, targets)):
62                 f,t = ft
63                 if f == False and line.startswith(t):
64                     found[i] = True
65         if min(found) == True:
66             return True
67         return False
68     
69     def read(self, path, info=None):
70         data,bin_info,wave_info = loadibw(path)
71         approach,retract = self._translate_ibw(data, bin_info, wave_info)
72
73         info = {'filetype':self.name, 'experiment':experiment.VelocityClamp}
74         return ([approach, retract], info)
75      
76     def _translate_ibw(self, data, bin_info, wave_info):
77         if bin_info['version'] != 5:
78             raise NotImplementedError('IBW version %d (< 5) not supported'
79                                       % bin_info['version'])
80             # We need version 5 for multidimensional arrays.
81
82         # Parse the note into a dictionary
83         note = {}
84         for line in bin_info['note'].split('\r'):
85             fields = [x.strip() for x in line.split(':', 1)]
86             key = fields[0]
87             if len(fields) == 2:
88                 value = fields[1]
89             else:
90                 value = None
91             note[key] = value
92         bin_info['note'] = note
93         if note['VerDate'] not in ['80501.041', '80501.0207']:
94             raise Exception(note['VerDate'])
95             raise NotImplementedError(
96                 '%s file version %s not supported (yet!)\n%s'
97                 % (self.name, note['VerDate'], pprint.pformat(note)))
98
99         info = {
100             'raw info':{'bin':bin_info,
101                         'wave':wave_info},
102             'time':wave_info['creationDate'],
103             'spring constant (N/m)':note['SpringConstant'],
104             }
105         # MFP3D's native data dimensions match Hooke's (<point>, <column>) layout.
106         approach = self._scale_block(data[:wave_info['npnts']/2,:], info, 'approach')
107         retract = self._scale_block(data[wave_info['npnts']/2:,:], info, 'retract')
108         return (approach, retract)
109
110     def _scale_block(self, data, info, name):
111         """Convert the block from its native format to a `numpy.float`
112         array in SI units.
113         """
114         shape = 3
115         # raw column indices
116         columns = info['raw info']['bin']['dimLabels'][1]
117         # Depending on your MFP3D version:
118         #   VerDate 80501.0207: ['Raw', 'Defl', 'LVDT', 'Time']
119         #   VerDate 80501.041:  ['Raw', 'Defl', 'LVDT']
120         if 'Time' in columns:
121             n_col = 3
122         else:
123             n_col = 2
124         ret = curve.Data(
125             shape=(data.shape[0], n_col),
126             dtype=numpy.float,
127             info=copy.deepcopy(info)
128             )
129         ret.info['name'] = name
130         ret.info['raw data'] = data # store the raw data
131
132         z_rcol = columns.index('LVDT')
133         d_rcol = columns.index('Defl')
134
135         # scaled column indices
136         ret.info['columns'] = ['z piezo (m)', 'deflection (m)']
137         z_scol = ret.info['columns'].index('z piezo (m)')
138         d_scol = ret.info['columns'].index('deflection (m)')
139
140         # Leading '-' because increasing voltage extends the piezo,
141         # moving the tip towards the surface (positive indentation),
142         # but it makes more sense to me to have it increase away from
143         # the surface (positive separation).
144         ret[:,z_scol] = -data[:,z_rcol].astype(ret.dtype)
145
146         # Leading '-' because deflection voltage increases as the tip
147         # moves away from the surface, but it makes more sense to me
148         # to have it increase as it moves toward the surface (positive
149         # tension on the protein chain).
150         ret[:,d_scol] = -data[:,d_rcol]
151
152         if 'Time' in columns:
153             ret.info['columns'].append('time (s)')
154             t_rcol = columns.index('Time')
155             t_scol = ret.info['columns'].index('time (s)')
156             ret[:,t_scol] = data[:,t_rcol]
157
158         return ret