Oops, replace `n` with `config['samples']` in _run_wiggles().
[pypiezo.git] / pypiezo / wiggle.py
1 # Copyright (C) 2011-2012 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of pypiezo.
4 #
5 # pypiezo is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 3 of the License, or (at your option) any later
8 # version.
9 #
10 # pypiezo is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # pypiezo.  If not, see <http://www.gnu.org/licenses/>.
16
17 """Utilities for wiggling a the piezo near the surface.
18
19 This helps you detect interference between the laser bouncing off the
20 tip and the surface.  The wiggling continues until you stop it, which
21 gives you feedback while you reposition the laser to minimize the
22 interference.  One day we'll all have fancy AFMs with software control
23 over the laser alignment, but until then, we've got this module
24 helping you with your thumbscrews.
25 """
26
27 import time as _time
28
29 import numpy as _numpy
30
31 try:
32     import h5py as _h5py
33     import h5config as _h5config
34     from h5config.storage.hdf5 import HDF5_Storage as _HDF5_Storage
35     from h5config.storage.hdf5 import h5_create_group as _h5_create_group
36 except ImportError, e:
37     _h5py = None
38     _h5py_import_error = e
39
40 try:
41     import matplotlib as _matplotlib
42     import matplotlib.pyplot as _matplotlib_pyplot
43 except (ImportError, RuntimeError), e:
44     _matplotlib = None
45     _matplotlib_import_error = e
46
47 from curses_check_for_keypress import CheckForKeypress as _CheckForKeypress
48
49 from . import LOG as _LOG
50 from . import base as _base
51 from . import package_config as _package_config
52
53
54 def _setup_config(piezo, config):
55     if config['wavelength'] and config['amplitude']:
56         log_string = \
57             'use either laser_wavelength or amplitude, but not both'
58         _LOG.warn(log_string)
59
60     if None in (config['amplitude'], config['offset']):
61         output_axis = piezo.axis_by_name(config['axis'])
62         maxdata = output_axis.axis_channel.get_maxdata()
63         midpoint = int(maxdata/2)
64         if config['offset'] is None:
65             offset = midpoint
66             _LOG.debug(('generated offset for interference wiggle: {}'
67                         ).format(config['offset']))
68         if config['amplitude'] is None:
69             if config['offset'] <= midpoint:
70                 max_amplitude = int(config['offset'])
71             else:
72                 max_amplitude = int(maxdata-config['offset'])
73             offset_meters = _base.convert_bits_to_meters(
74                 output_axis.config, config['offset'])
75             if config['wavelength'] is None:
76                 config['amplitude'] = 0.5*max_amplitude
77             else:
78                 bit_wavelength = _base.convert_meters_to_bits(
79                     output_axis.config,
80                     offset_meters + config['wavelength']
81                     ) - config['offset']
82                 config['amplitude'] = 2*bit_wavelength
83             _LOG.debug(('generated amplitude for interference wiggle: {}'
84                         ).format(config['amplitude']))
85             if config['amplitude'] > max_amplitude:
86                 raise ValueError(
87                     'no room for a two wavelength wiggle ({} > {})'.format(
88                         config['amplitude'], max_amplitude))
89
90 def _construct_output(piezo, config):
91     n = config['samples']  # samples in a single oscillation
92     out = (config['amplitude']
93            * _numpy.sin(_numpy.arange(2*n)*2*_numpy.pi/n)
94            + config['offset'])
95     # 2*n for 2 periods, so you can judge precision
96     out = out.reshape((len(out), 1)).astype(
97         piezo.channel_dtype(config['axis'], direction='output'))
98     return out
99
100 def _setup_datafile(filename, group, piezo, config, output):
101     if not _h5py:
102         raise _h5py_import_error
103     output_axis = afm.piezo.axis_by_name(config['axis'])
104     input_channel = afm.piezo.input_channel_by_name(config['input'])
105     with _h5py.File(filename, 'w') as f:
106         cwg = _h5_create_group(f, group)
107         storage = _HDF5_Storage()
108         for config_,key in [
109             (config, 'config/wiggle'),
110             (output.axis.config,
111              'config/{}/axis'.format(config['axis'])),
112             (input_channel.config,
113              'config/{}/channel'.format(config['input']))]:
114             if config_ is None:
115                 continue
116             config_cwg = _h5_create_group(cwg, key)
117             storage.save(config=config, group=config_cwg)
118         cwg['wiggle/raw/{}'.format(config['axis'])] = output
119
120 def _update_datafile(filename, group, config, cycle, data):
121     timestamp = ('{0}-{1:02d}-{2:02d}T{3:02d}-{4:02d}-{5:02d}'
122                  ).format(*_time.localtime())
123     with _h5py.File(filename, 'a') as f:
124         wiggle_group = _h5_create_group(f, group)
125         cwg = _h5_create_group(
126             wiggle_group, 'wiggle/{}'.format(cycle))
127         cwg['time'] = timestamp
128         cwg['raw/{}'.format(config['input'])] = data
129
130 def _setup_plot(piezo, config, output):
131     if not _matplotlib:
132         raise _matplotlib_import_error
133     _matplotlib.interactive(True)
134     figure = _matplotlib_pyplot.figure()
135     axes = figure.add_subplot(1, 1, 1)
136     axes.hold(False)
137     timestamp = _time.strftime('%H%M%S')
138     axes.set_title('wiggle for interference %s' % timestamp)
139     plot = axes.plot(output, output, 'b.-')
140     figure.show()
141     _matplotlib_pyplot.draw()
142     _matplotlib_pyplot.show()
143     return plot
144
145 def _update_plot(plot, cycle, data):
146     plot[0].set_ydata(data[:,0])
147     axes.set_ylim([data.min(), data.max()])
148     _matplotlib_pyplot.draw()
149
150 def _run_wiggles(piezo, config, plot, output, filename=None, group='/',
151                  keypress_test_mode=False):
152     scan_frequency = config['frequency'] * config['samples']
153     cycle = 0
154     c = _CheckForKeypress(test_mode=keypress_test_mode)
155     while c.input() == None:
156         # input will need processing for multi-segment AFMs...
157         data = piezo.ramp(
158             output, scan_frequency, output_names=[config['axis']],
159             input_names=[config['input']])
160         _LOG.debug('completed a wiggle round')
161         if filename:
162             _update_datafile(
163                 filename=filename, group=group, config=config,
164                 cycle=cycle, data=data)
165         if plot:
166             _update_plot(plot=plot, cycle=cycle, data=data)
167         cycle += 1
168
169 def wiggle_for_interference(
170     piezo, config, plot=True, filename=None, group='/',
171     keypress_test_mode=False):
172     """Output a sine wave and measure interference.
173
174     With a poorly focused or aligned laser, leaked laser light
175     reflecting off the surface may interfere with the light
176     reflected off the cantilever, causing distance-dependent
177     interference with a period roughly half the laser's
178     wavelength.  This method wiggles the cantilever near the
179     surface and monitors the magnitude of deflection oscillation,
180     allowing the operator to adjust the laser alignment in order
181     to minimize the interference.
182
183     Modern commercial AFMs with computer-aligned lasers must do
184     something like this automatically.
185     """
186     if _package_config['matplotlib']:
187         plot = True
188     _setup_config(piezo=piezo, config=config)
189     _LOG.debug('oscillate for interference wiggle ({})'.format(config))
190     output = _construct_output(piezo=piezo, config=config)
191
192     if filename:
193         _setup_datafile(
194             filename=filename, group=group, piezo=piezo, config=config,
195             output=output)
196     if plot:
197         interactive = _matplotlib.is_interactive()
198         plot_ = _setup_plot(piezo=piezo, config=config, output=output)
199     _run_wiggles(
200         piezo=piezo, config=config, plot=plot_, output=output,
201         filename=filename, group=group, keypress_test_mode=keypress_test_mode)
202     if plot:
203         _matplotlib.interactive(interactive)
204     piezo.last_output[config['axis']] = out[-1,0]
205     _LOG.debug('interference wiggle complete')