From ce723fbf07053420d62e0735a8295bd97205c8e7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 14 Mar 2012 23:15:45 -0400 Subject: [PATCH] Split AFMPiezo.wiggle_for_interference() into it's own module. And break it down into manageable chunks. --- pypiezo/afm.py | 139 +------------------------------- pypiezo/wiggle.py | 199 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 137 deletions(-) create mode 100644 pypiezo/wiggle.py diff --git a/pypiezo/afm.py b/pypiezo/afm.py index da631b0..490f6bc 100644 --- a/pypiezo/afm.py +++ b/pypiezo/afm.py @@ -27,21 +27,11 @@ except (ImportError, RuntimeError), e: _matplotlib = None _matplotlib_import_error = e -try: - import h5py as _h5py - import h5config as _h5config - from h5config.storage.hdf5 import HDF5_Storage as _HDF5_Storage - from h5config.storage.hdf5 import h5_create_group as _h5_create_group -except ImportError, e: - _h5py = None - _h5py_import_error = e - -from curses_check_for_keypress import CheckForKeypress as _CheckForKeypress - from . import LOG as _LOG from . import base as _base from . import package_config as _package_config from . import surface as _surface +from . import wiggle as _wiggle class AFMPiezo (_base.Piezo): @@ -292,132 +282,7 @@ class AFMPiezo (_base.Piezo): } return data - def wiggle_for_interference( - self, config, plot=True, filename=None, group='/', - keypress_test_mode=False): - """Output a sine wave and measure interference. - - With a poorly focused or aligned laser, leaked laser light - reflecting off the surface may interfere with the light - reflected off the cantilever, causing distance-dependent - interference with a period roughly half the laser's - wavelength. This method wiggles the cantilever near the - surface and monitors the magnitude of deflection oscillation, - allowing the operator to adjust the laser alignment in order - to minimize the interference. - - Modern commercial AFMs with computer-aligned lasers must do - something like this automatically. - """ - if _package_config['matplotlib']: - plot = True - if config['wavelength'] and config['amplitude']: - log_string = \ - 'use either laser_wavelength or amplitude, but not both' - _LOG.warn(log_string) - - if None in (config['amplitude'], config['offset']): - output_axis = self.axis_by_name(config['axis']) - maxdata = output_axis.axis_channel.get_maxdata() - midpoint = int(maxdata/2) - if config['offset'] is None: - offset = midpoint - _LOG.debug(('generated offset for interference wiggle: {}' - ).format(config['offset'])) - if config['amplitude'] is None: - if config['offset'] <= midpoint: - max_amplitude = int(config['offset']) - else: - max_amplitude = int(maxdata-config['offset']) - offset_meters = _base.convert_bits_to_meters( - output_axis.config, config['offset']) - if config['wavelength'] is None: - config['amplitude'] = 0.5*max_amplitude - else: - bit_wavelength = _base.convert_meters_to_bits( - output_axis.config, - offset_meters + config['wavelength'] - ) - config['offset'] - config['amplitude'] = 2*bit_wavelength - _LOG.debug(('generated amplitude for interference wiggle: {}' - ).format(config['amplitude'])) - if config['amplitude'] > max_amplitude: - raise ValueError( - 'no room for a two wavelength wiggle ({} > {})'.format( - config['amplitude'], max_amplitude)) - - n = config['samples'] # samples in a single oscillation - scan_frequency = config['frequency'] * n - out = (config['amplitude'] - * _numpy.sin(_numpy.arange(2*n)*2*_numpy.pi/n) - + config['offset']) - # 2*n for 2 periods, so you can judge precision - out = out.reshape((len(out), 1)).astype( - self.channel_dtype(config['axis'], direction='output')) - - _LOG.debug('oscillate for interference wiggle ({})'.format(config)) - - if filename: - if not _h5py: - raise _h5py_import_error - if not output_axis: # from amplitude/offset setup - output_axis = afm.piezo.axis_by_name(config['axis']) - input_channel = afm.piezo.input_channel_by_name(config['input']) - with _h5py.File(filename, 'w') as f: - cwg = _h5_create_group(f, group) - storage = _HDF5_Storage() - for config_,key in [ - (config, 'config/wiggle'), - (output.axis.config, - 'config/{}/axis'.format(config['axis'])), - (input_channel.config, - 'config/{}/channel'.format(config['input']))]: - if config_ is None: - continue - config_cwg = _h5_create_group(cwg, key) - storage.save(config=config, group=config_cwg) - if plot: - if not _matplotlib: - raise _matplotlib_import_error - interactive = _matplotlib.is_interactive() - _matplotlib.interactive(True) - figure = _matplotlib_pyplot.figure() - axes = figure.add_subplot(1, 1, 1) - axes.hold(False) - timestamp = _time.strftime('%H%M%S') - axes.set_title('wiggle for interference %s' % timestamp) - plot_p = axes.plot(out, out, 'b.-') - figure.show() - _matplotlib_pyplot.draw() - _matplotlib_pyplot.show() - cycle = 0 - c = _CheckForKeypress(test_mode=keypress_test_mode) - while c.input() == None: - # input will need processing for multi-segment AFMs... - data = self.ramp( - out, scan_frequency, output_names=[config['axis']], - input_names=[config['input']]) - _LOG.debug('completed a wiggle round') - if filename: - timestamp = ('{0}-{1:02d}-{2:02d}T{3:02d}-{4:02d}-{5:02d}' - ).format(*_time.localtime()) - with _h5py.File(filename, 'a') as f: - wiggle_group = _h5_create_group(f, group) - cwg = _h5_create_group( - wiggle_group, 'wiggle/{}'.format(cycle)) - cwg['time'] = timestamp - cwg['raw/{}'.format(config['axis'])] = out - cwg['raw/{}'.format(config['input'])] = data - if plot: - plot_p[0].set_ydata(data[:,0]) - axes.set_ylim([data.min(), data.max()]) - _matplotlib_pyplot.draw() - cycle += 1 - if plot: - _matplotlib.interactive(interactive) - self.last_output[config['axis']] = out[-1,0] - _LOG.debug('interference wiggle complete') - + wiggle_for_interference = _wiggle.wiggle_for_interference get_surface_position = _surface.get_surface_position diff --git a/pypiezo/wiggle.py b/pypiezo/wiggle.py new file mode 100644 index 0000000..2f7a758 --- /dev/null +++ b/pypiezo/wiggle.py @@ -0,0 +1,199 @@ +# Copyright (C) 2011-2012 W. Trevor King +# +# This file is part of pypiezo. +# +# pypiezo is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# pypiezo is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# pypiezo. If not, see . + +"""Utilities for wiggling a the piezo near the surface. + +This helps you detect interference between the laser bouncing off the +tip and the surface. The wiggling continues until you stop it, which +gives you feedback while you reposition the laser to minimize the +interference. One day we'll all have fancy AFMs with software control +over the laser alignment, but until then, we've got this module +helping you with your thumbscrews. +""" + +import time as _time + +try: + import h5py as _h5py + import h5config as _h5config + from h5config.storage.hdf5 import HDF5_Storage as _HDF5_Storage + from h5config.storage.hdf5 import h5_create_group as _h5_create_group +except ImportError, e: + _h5py = None + _h5py_import_error = e + +try: + import matplotlib as _matplotlib + import matplotlib.pyplot as _matplotlib_pyplot +except (ImportError, RuntimeError), e: + _matplotlib = None + _matplotlib_import_error = e + +from curses_check_for_keypress import CheckForKeypress as _CheckForKeypress + +from . import LOG as _LOG + + +def _setup_config(peizo, config): + if config['wavelength'] and config['amplitude']: + log_string = \ + 'use either laser_wavelength or amplitude, but not both' + _LOG.warn(log_string) + + if None in (config['amplitude'], config['offset']): + output_axis = piezo.axis_by_name(config['axis']) + maxdata = output_axis.axis_channel.get_maxdata() + midpoint = int(maxdata/2) + if config['offset'] is None: + offset = midpoint + _LOG.debug(('generated offset for interference wiggle: {}' + ).format(config['offset'])) + if config['amplitude'] is None: + if config['offset'] <= midpoint: + max_amplitude = int(config['offset']) + else: + max_amplitude = int(maxdata-config['offset']) + offset_meters = _base.convert_bits_to_meters( + output_axis.config, config['offset']) + if config['wavelength'] is None: + config['amplitude'] = 0.5*max_amplitude + else: + bit_wavelength = _base.convert_meters_to_bits( + output_axis.config, + offset_meters + config['wavelength'] + ) - config['offset'] + config['amplitude'] = 2*bit_wavelength + _LOG.debug(('generated amplitude for interference wiggle: {}' + ).format(config['amplitude'])) + if config['amplitude'] > max_amplitude: + raise ValueError( + 'no room for a two wavelength wiggle ({} > {})'.format( + config['amplitude'], max_amplitude)) + +def _construct_output(piezo, config): + n = config['samples'] # samples in a single oscillation + out = (config['amplitude'] + * _numpy.sin(_numpy.arange(2*n)*2*_numpy.pi/n) + + config['offset']) + # 2*n for 2 periods, so you can judge precision + out = out.reshape((len(out), 1)).astype( + piezo.channel_dtype(config['axis'], direction='output')) + return out + +def _setup_datafile(filename, group, piezo, config, output): + if not _h5py: + raise _h5py_import_error + output_axis = afm.piezo.axis_by_name(config['axis']) + input_channel = afm.piezo.input_channel_by_name(config['input']) + with _h5py.File(filename, 'w') as f: + cwg = _h5_create_group(f, group) + storage = _HDF5_Storage() + for config_,key in [ + (config, 'config/wiggle'), + (output.axis.config, + 'config/{}/axis'.format(config['axis'])), + (input_channel.config, + 'config/{}/channel'.format(config['input']))]: + if config_ is None: + continue + config_cwg = _h5_create_group(cwg, key) + storage.save(config=config, group=config_cwg) + cwg['wiggle/raw/{}'.format(config['axis'])] = output + +def _update_datafile(filename, group, config, cycle, data): + timestamp = ('{0}-{1:02d}-{2:02d}T{3:02d}-{4:02d}-{5:02d}' + ).format(*_time.localtime()) + with _h5py.File(filename, 'a') as f: + wiggle_group = _h5_create_group(f, group) + cwg = _h5_create_group( + wiggle_group, 'wiggle/{}'.format(cycle)) + cwg['time'] = timestamp + cwg['raw/{}'.format(config['input'])] = data + +def _setup_plot(piezo, config): + if not _matplotlib: + raise _matplotlib_import_error + _matplotlib.interactive(True) + figure = _matplotlib_pyplot.figure() + axes = figure.add_subplot(1, 1, 1) + axes.hold(False) + timestamp = _time.strftime('%H%M%S') + axes.set_title('wiggle for interference %s' % timestamp) + plot = axes.plot(out, out, 'b.-') + figure.show() + _matplotlib_pyplot.draw() + _matplotlib_pyplot.show() + return plot + +def _update_plot(plot, cycle, data): + plot[0].set_ydata(data[:,0]) + axes.set_ylim([data.min(), data.max()]) + _matplotlib_pyplot.draw() + +def _run_wiggles(piezo, config, plot, output, filename=None, group='/', + keypress_test_mode=False): + scan_frequency = config['frequency'] * n + cycle = 0 + c = _CheckForKeypress(test_mode=keypress_test_mode) + while c.input() == None: + # input will need processing for multi-segment AFMs... + data = piezo.ramp( + output, scan_frequency, output_names=[config['axis']], + input_names=[config['input']]) + _LOG.debug('completed a wiggle round') + if filename: + _update_datafile( + filename=filename, group=group, config=config, + cycle=cycle, data=data) + if plot: + _update_plot(plot=plot, cycle=cycle, data=data) + cycle += 1 + +def wiggle_for_interference( + piezo, config, plot=True, filename=None, group='/', + keypress_test_mode=False): + """Output a sine wave and measure interference. + + With a poorly focused or aligned laser, leaked laser light + reflecting off the surface may interfere with the light + reflected off the cantilever, causing distance-dependent + interference with a period roughly half the laser's + wavelength. This method wiggles the cantilever near the + surface and monitors the magnitude of deflection oscillation, + allowing the operator to adjust the laser alignment in order + to minimize the interference. + + Modern commercial AFMs with computer-aligned lasers must do + something like this automatically. + """ + if _package_config['matplotlib']: + plot = True + _setup_config(piezo=piezo, config=config) + _LOG.debug('oscillate for interference wiggle ({})'.format(config)) + output = _construct_output(piezo=piezo, config=config) + + if filename: + _setup_datafile( + filename=filename, group=group, piezo=piezo, config=config, + output=output) + if plot: + interactive = _matplotlib.is_interactive() + plot_ = _setup_plot(piezo=piezo, config=config, output=output) + _run_wiggles(piezo=piezo, config=config, plot=plot_) + if plot: + _matplotlib.interactive(interactive) + piezo.last_output[config['axis']] = out[-1,0] + _LOG.debug('interference wiggle complete') -- 2.26.2