1 # Copyright (C) 2012 W. Trevor King <wking@tremily.us>
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 if hasattr(figure, 'show'):
143 if not _matplotlib.is_interactive():
144 _matplotlib_pyplot.show()
145 return (figure, axes, plot)
147 def _update_plot(figure, axes, plot, cycle, data):
148 plot[0].set_ydata(data[:,0])
149 axes.set_ylim([data.min(), data.max()])
150 _matplotlib_pyplot.draw()
152 def _run_wiggles(piezo, config, figure, axes, plot, output, filename=None,
153 group='/', keypress_test_mode=False):
154 scan_frequency = config['frequency'] * config['samples']
156 c = _CheckForKeypress(test_mode=keypress_test_mode)
157 while c.input() == None:
158 # input will need processing for multi-segment AFMs...
160 output, scan_frequency, output_names=[config['axis']],
161 input_names=[config['input']])
162 _LOG.debug('completed a wiggle round')
165 filename=filename, group=group, config=config,
166 cycle=cycle, data=data)
169 figure=figure, axes=axes, plot=plot, cycle=cycle, data=data)
172 def wiggle_for_interference(
173 piezo, config, plot=True, filename=None, group='/',
174 keypress_test_mode=False):
175 """Output a sine wave and measure interference.
177 With a poorly focused or aligned laser, leaked laser light
178 reflecting off the surface may interfere with the light
179 reflected off the cantilever, causing distance-dependent
180 interference with a period roughly half the laser's
181 wavelength. This method wiggles the cantilever near the
182 surface and monitors the magnitude of deflection oscillation,
183 allowing the operator to adjust the laser alignment in order
184 to minimize the interference.
186 Modern commercial AFMs with computer-aligned lasers must do
187 something like this automatically.
189 if _package_config['matplotlib']:
191 _setup_config(piezo=piezo, config=config)
192 _LOG.debug('oscillate for interference wiggle ({})'.format(config))
193 output = _construct_output(piezo=piezo, config=config)
197 filename=filename, group=group, piezo=piezo, config=config,
200 interactive = _matplotlib.is_interactive()
201 figure,axes,plot_ = _setup_plot(
202 piezo=piezo, config=config, output=output)
204 figure = axes = plot_ = None
206 piezo=piezo, config=config, figure=figure, axes=axes, plot=plot_,
207 output=output, filename=filename, group=group,
208 keypress_test_mode=keypress_test_mode)
210 _matplotlib.interactive(interactive)
211 piezo.last_output[config['axis']] = output[-1,0]
212 _LOG.debug('interference wiggle complete')