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()
143 return (figure, axes, plot)
145 def _update_plot(figure, axes, 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, figure, axes, plot, output, filename=None,
151 group='/', keypress_test_mode=False):
152 scan_frequency = config['frequency'] * config['samples']
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)
167 figure=figure, axes=axes, plot=plot, cycle=cycle, data=data)
170 def wiggle_for_interference(
171 piezo, config, plot=True, filename=None, group='/',
172 keypress_test_mode=False):
173 """Output a sine wave and measure interference.
175 With a poorly focused or aligned laser, leaked laser light
176 reflecting off the surface may interfere with the light
177 reflected off the cantilever, causing distance-dependent
178 interference with a period roughly half the laser's
179 wavelength. This method wiggles the cantilever near the
180 surface and monitors the magnitude of deflection oscillation,
181 allowing the operator to adjust the laser alignment in order
182 to minimize the interference.
184 Modern commercial AFMs with computer-aligned lasers must do
185 something like this automatically.
187 if _package_config['matplotlib']:
189 _setup_config(piezo=piezo, config=config)
190 _LOG.debug('oscillate for interference wiggle ({})'.format(config))
191 output = _construct_output(piezo=piezo, config=config)
195 filename=filename, group=group, piezo=piezo, config=config,
198 interactive = _matplotlib.is_interactive()
199 figure,axes,plot_ = _setup_plot(
200 piezo=piezo, config=config, output=output)
202 figure = axes = plot_ = None
204 piezo=piezo, config=config, figure=figure, axes=axes, plot=plot_,
205 output=output, filename=filename, group=group,
206 keypress_test_mode=keypress_test_mode)
208 _matplotlib.interactive(interactive)
209 piezo.last_output[config['axis']] = out[-1,0]
210 _LOG.debug('interference wiggle complete')