1 # Copyright (C) 2011 W. Trevor King <wking@drexel.edu>
3 # This file is part of unfold_protein.
5 # Unfold_protein is free software: you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation, either
8 # version 3 of the License, or (at your option) any later version.
10 # Unfold_protein is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with unfold_protein. If not, see
17 # <http://www.gnu.org/licenses/>.
19 """Define classes for carrying out an unfolding cycle with an AFM."""
21 import email.utils as _email_utils
22 import os.path as _os_path
26 import pypiezo.base as _pypiezo_base
27 from h5config.storage.hdf5 import HDF5_Storage as _HDF5_Storage
28 from h5config.storage.hdf5 import h5_create_group as _h5_create_group
30 from . import LOG as _LOG
31 from . import package_config as _package_config
34 import numpy as _numpy
35 from matplotlib import pyplot as _pyplot
36 FIGURE = _pyplot.figure()
37 except (ImportError, RuntimeError), _matplotlib_import_error:
39 # from pylab import figure, plot, title, legend, hold, subplot, draw
42 class ExceptionTooClose (Exception):
44 The piezo is too close to the surface.
48 class ExceptionTooFar (Exception):
50 The piezo is too far from the surface.
55 class Unfolder (object):
56 def __init__(self, config, afm):
62 """Approach-bind-unfold-save[-plot] cycle.
65 ret['timestamp'] = _email_utils.formatdate(localtime=True)
66 ret['temperature'] = self.afm.get_temperature()
67 ret['approach'] = self._approach()
69 ret['unfold'] = self._unfold()
71 if _package_config['matplotlib']:
76 """Approach the surface using the piezo
78 Steps in until a given setpoint is reached.
80 config = self.config['approach']
81 deflection = self.read_deflection()
82 setpoint = deflection + config['relative setpoint']
83 _LOG.info('approach with setpoint = {}'.format(setpoint))
84 axis_config = self.afm.piezo.config.select_config(
85 'axes', self.afm.axis_name,
86 get_attribute=_pypiezo_base.get_axis_name
88 def_config = self.afm.piezo.config.select_config(
89 'inputs', 'deflection')
90 start_pos = self.afm.piezo.last_output[self.afm.axis_name]
92 # calculate parameters for move_to_pos_or_def from config
93 setpoint_bits = _pypiezo_base.convert_volts_to_bits(
95 mid_pos_bits = _pypiezo_base.convert_meters_to_bits(
97 step_pos_bits = _pypiezo_base.convert_meters_to_bits(
98 axis_config, config['step'])
99 step_bits = step_pos_bits - mid_pos_bits
100 frequency = config['velocity'] / config['step']
103 data = self.afm.piezo.move_to_pos_or_def(
104 axis_name=self.afm.axis_name, deflection=setpoint_bits,
105 step=step_bits, frequency=frequency, return_data=True)
106 data['setpoint'] = setpoint
108 if data['deflection'][-1] < setpoint_bits:
109 _LOG.info('unfolding too far from the surface')
110 self.afm.piezo.jump(self.afm.axis_name, start_pos)
111 #if PYLAB_INTERACTIVE_VERBOSE == True:
112 # figure(BASE_FIG_NUM+1)
114 # plot_dict(data, 'Approach')
116 # title('Unfolding too far')
117 _LOG.debug('raising ExceptionTooFar')
118 raise ExceptionTooFar
122 """Wait on the surface while the protein binds."""
123 time = self.config['bind time']
124 _LOG.info('binding for {:.3f} seconds'.format(time))
128 """Pull the bound protein, forcing unfolding events."""
129 config = self.config['unfold']
130 velocity = config['velocity']
131 _LOG.info('unfold at {:g} m/s'.format(velocity))
132 axis = self.afm.piezo.axis_by_name(self.afm.axis_name)
133 d = self.afm.piezo.channel_by_name('deflection')
134 start_pos = self.afm.piezo.last_output[self.afm.axis_name]
135 start_pos_m = _pypiezo_base.convert_bits_to_meters(axis, start_pos)
136 final_pos_m = bind_pos_m - config['distance']
137 final_pos = _pypiezo_base.convert_meters_to_bits(axis, final_pos_m)
138 dtype = afm.piezo.channel_dtype(self.afm.axis_name, direction='output')
140 config['distance'] / config['velocity'] * config['frequency']) + 1
141 # (m) * (s/m) * (samples/s)
142 out = _numpy.linspace(
143 start_pos, final_pos, num_steps).astype(dtype=dtype)
145 'unfolding from {:d} to {:d} in {:d} steps at {:g} Hz'.format(
146 start_pos, final_pos, num_steps, config['frequency']))
147 data = afm.piezo.ramp(
148 data=out, frequency=config['frequency'],
149 output_names=[self.afm.axis_name], input_names=['deflection'])
150 return {self.afm.axis_name:out, 'deflection':data}
152 def _save(self, temperature, approach, unfold, timestamp):
153 config = self.config['save']
154 time_tuple = _email_utils.parsedate(timestamp)
155 filename = _os_path.join(
156 config['base directory'],
157 '{0}-{1:02d}-{2:02d}_{3:02d}-{4:02d}-{5:02d}.h5'.format(
159 _LOG.info('saving {}'.format(filename))
160 with _h5py.File(filename, 'a') as f:
161 storage = _HDF5_Storage()
162 config_cwg = _h5_create_group(f, 'config')
163 storage.save(config=self.config, group=config_cwg)
164 f['timestamp'] = timestamp
165 f['temperature'] = temperature
166 for k,v in approach.items():
167 f['approach/{}'.format(k)] = v
168 for k,v in unfold.items():
169 f['unfold/{}'.format(k)] = v
171 def _plot(self, temperature, approach, unfold, timestamp):
172 "Plot the unfolding cycle"
174 raise _matplotlib_import_error
177 #if PYLAB_INTERACTIVE_VERBOSE == True:
178 # figure(BASE_FIG_NUM)
180 # plot_dict(approach_data, 'Approach')
182 # plot_dict(out['data'], 'Unfold')
187 def zero_piezo(self):
188 _LOG.info('zero piezo')
189 x_mid_pos = _pypiezo_base.convert_volts_to_bits(
190 self.afm.piezo.config.select_config(
191 'axes', 'x', get_attribute=_pypiezo_base.get_axis_name
194 z_mid_pos = _pypiezo_base.convert_volts_to_bits(
195 self.afm.piezo.config.select_config(
196 'axes', 'z', get_attribute=_pypiezo_base.get_axis_name
199 self.afm.piezo.jump('z', z_mid_pos)
200 self.afm.piezo.jump('x', x_mid_pos)
202 def read_deflection(self):
203 bits = self.afm.piezo.read_deflection()
204 return _pypiezo_base.convert_bits_to_volts(
205 self.afm.piezo.config.select_config('inputs', 'deflection'), bits)