1 # Copyright (C) 2011-2012 W. Trevor King <wking@drexel.edu>
3 # This file is part of pypiezo.
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
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.
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/>.
17 """Utilities for wiggling a the piezo near the surface.
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.
29 import numpy as _numpy
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:
38 _h5py_import_error = e
41 import matplotlib as _matplotlib
42 import matplotlib.pyplot as _matplotlib_pyplot
43 except (ImportError, RuntimeError), e:
45 _matplotlib_import_error = e
47 from curses_check_for_keypress import CheckForKeypress as _CheckForKeypress
49 from . import LOG as _LOG
50 from . import base as _base
51 from . import package_config as _package_config
54 def _setup_config(piezo, config):
55 if config['wavelength'] and config['amplitude']:
57 'use either laser_wavelength or amplitude, but not both'
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:
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'])
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
78 bit_wavelength = _base.convert_meters_to_bits(
80 offset_meters + config['wavelength']
82 config['amplitude'] = 2*bit_wavelength
83 _LOG.debug(('generated amplitude for interference wiggle: {}'
84 ).format(config['amplitude']))
85 if config['amplitude'] > max_amplitude:
87 'no room for a two wavelength wiggle ({} > {})'.format(
88 config['amplitude'], max_amplitude))
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)
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'))
100 def _setup_datafile(filename, group, piezo, config, output):
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()
109 (config, 'config/wiggle'),
111 'config/{}/axis'.format(config['axis'])),
112 (input_channel.config,
113 'config/{}/channel'.format(config['input']))]:
116 config_cwg = _h5_create_group(cwg, key)
117 storage.save(config=config, group=config_cwg)
118 cwg['wiggle/raw/{}'.format(config['axis'])] = output
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
130 def _setup_plot(piezo, config, output):
132 raise _matplotlib_import_error
133 _matplotlib.interactive(True)
134 figure = _matplotlib_pyplot.figure()
135 axes = figure.add_subplot(1, 1, 1)
137 timestamp = _time.strftime('%H%M%S')
138 axes.set_title('wiggle for interference %s' % timestamp)
139 plot = axes.plot(output, output, 'b.-')
141 _matplotlib_pyplot.draw()
142 _matplotlib_pyplot.show()
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()
150 def _run_wiggles(piezo, config, plot, output, filename=None, group='/',
151 keypress_test_mode=False):
152 scan_frequency = config['frequency'] * n
154 c = _CheckForKeypress(test_mode=keypress_test_mode)
155 while c.input() == None:
156 # input will need processing for multi-segment AFMs...
158 output, scan_frequency, output_names=[config['axis']],
159 input_names=[config['input']])
160 _LOG.debug('completed a wiggle round')
163 filename=filename, group=group, config=config,
164 cycle=cycle, data=data)
166 _update_plot(plot=plot, cycle=cycle, data=data)
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.
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.
183 Modern commercial AFMs with computer-aligned lasers must do
184 something like this automatically.
186 if _package_config['matplotlib']:
188 _setup_config(piezo=piezo, config=config)
189 _LOG.debug('oscillate for interference wiggle ({})'.format(config))
190 output = _construct_output(piezo=piezo, config=config)
194 filename=filename, group=group, piezo=piezo, config=config,
197 interactive = _matplotlib.is_interactive()
198 plot_ = _setup_plot(piezo=piezo, config=config, output=output)
200 piezo=piezo, config=config, plot=plot_, output=output,
201 filename=filename, group=group, keypress_test_mode=keypress_test_mode)
203 _matplotlib.interactive(interactive)
204 piezo.last_output[config['axis']] = out[-1,0]
205 _LOG.debug('interference wiggle complete')