Split AFMPiezo.wiggle_for_interference() into it's own module.
authorW. Trevor King <wking@drexel.edu>
Thu, 15 Mar 2012 03:15:45 +0000 (23:15 -0400)
committerW. Trevor King <wking@drexel.edu>
Thu, 15 Mar 2012 03:15:45 +0000 (23:15 -0400)
And break it down into manageable chunks.

pypiezo/afm.py
pypiezo/wiggle.py [new file with mode: 0644]

index da631b0f827fc1497fd61c682e2cfe56d566849a..490f6bc6568c70849d4da9e3abdf296030da0f81 100644 (file)
@@ -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 (file)
index 0000000..2f7a758
--- /dev/null
@@ -0,0 +1,199 @@
+# Copyright (C) 2011-2012 W. Trevor King <wking@drexel.edu>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+"""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')