From d40d1abaa829bf5bab923617c5ecd8eced1164b3 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 14 Mar 2012 17:46:27 -0400 Subject: [PATCH] Move wiggle options to WiggleConfig and allow easy saving of wiggle data. --- pypiezo/afm.py | 124 ++++++++++++++++++++++++++++++---------------- pypiezo/config.py | 36 ++++++++++++++ 2 files changed, 118 insertions(+), 42 deletions(-) diff --git a/pypiezo/afm.py b/pypiezo/afm.py index 21d0186..88dd3e8 100644 --- a/pypiezo/afm.py +++ b/pypiezo/afm.py @@ -27,6 +27,15 @@ 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 @@ -134,8 +143,11 @@ class AFMPiezo (_base.Piezo): {'deflection': array([32655, 33968, 35281, 36593], dtype=uint16), 'z': array([32767, 34077, 35387, 36697], dtype=uint16)} - >>> p.wiggle_for_interference('z', offset=p.last_output['z'], - ... laser_wavelength=650e-9, keypress_test_mode=True) + >>> wiggle_config = config.WiggleConfig() + >>> wiggle_config['offset'] = p.last_output['z'] + >>> wiggle_config['wavelength'] = 650e-9 + >>> p.wiggle_for_interference(config=wiggle_config, + ... keypress_test_mode=True) Press any key to continue >>> try: @@ -145,8 +157,10 @@ class AFMPiezo (_base.Piezo): got FlatFit >>> print e # doctest: +SKIP slopes not sufficiently different: 1.0021 and 1.0021 + >>> e.right_slope >>> abs(e.right_slope-1) < 0.1 True + >>> e.left_slope >>> abs(e.left_slope-1) < 0.1 True @@ -281,8 +295,7 @@ class AFMPiezo (_base.Piezo): return data def wiggle_for_interference( - self, axis_name, wiggle_frequency=2, n_samples=1024, amplitude=None, - offset=None, laser_wavelength=None, plot=True, + self, config, plot=True, filename=None, group='/', keypress_test_mode=False): """Output a sine wave and measure interference. @@ -300,55 +313,69 @@ class AFMPiezo (_base.Piezo): """ if _package_config['matplotlib']: plot = True - if laser_wavelength and amplitude: + if config['wavelength'] and config['amplitude']: log_string = \ 'use either laser_wavelength or amplitude, but not both' _LOG.warn(log_string) - if None in (amplitude, offset): - output_axis = self.axis_by_name(axis_name) + 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 offset == None: + if config['offset'] is None: offset = midpoint - log_string = ( - 'generated offset for interference wiggle: %g' % offset) - _LOG.debug(log_string) - if amplitude == None: - if offset <= midpoint: - max_amplitude = int(offset) + _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-offset) + max_amplitude = int(maxdata-config['offset']) offset_meters = _base.convert_bits_to_meters( - output_axis.config, offset) - if laser_wavelength is None: - amplitude = 0.5*max_amplitude + 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 + laser_wavelength - ) - offset - amplitude = 2*bit_wavelength - log_string = ( - 'generated amplitude for interference wiggle: %g' - % amplitude) - _LOG.debug(log_string) - if amplitude > max_amplitude: + 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') - scan_frequency = wiggle_frequency * n_samples - out = (amplitude * _numpy.sin( - _numpy.arange(n_samples) * 4 * _numpy.pi / float(n_samples)) - + offset) - # 4 for 2 periods, so you can judge precision + 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(axis_name, direction='output')) - - _LOG.debug('oscillate for interference wiggle') - log_string = ( - 'amplitude: %d bits, offset: %d bits, scan frequency: %g Hz' - % (amplitude, offset, scan_frequency)) - _LOG.debug(log_string) - + 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 @@ -359,17 +386,30 @@ class AFMPiezo (_base.Piezo): axes.set_title('wiggle for interference %s' % timestamp) plot_p = axes.plot(out, out, 'b.-') figure.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=[axis_name], - input_names=['deflection']) + 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()]) #_flush_plot() - self.last_output[axis_name] = out[-1,0] + cycle += 1 + self.last_output[config['axis']] = out[-1,0] _LOG.debug('interference wiggle complete') get_surface_position = _surface.get_surface_position diff --git a/pypiezo/config.py b/pypiezo/config.py index 93ebb97..14d0ca9 100644 --- a/pypiezo/config.py +++ b/pypiezo/config.py @@ -133,3 +133,39 @@ class PiezoConfig (_config.Config): config_class=InputChannelConfig, default=None), ] + + +class WiggleConfig (_config.Config): + "Configure an interference wiggle" + settings = [ + _config.Setting( + name='axis', + help="Name of the axis to wiggle.", + default='z'), + _config.Setting( + name='input', + help="Name of the channel to watch.", + default='deflection'), + _config.FloatSetting( + name='frequency', + help="Frequency for a full wiggle cycle (hertz).", + default=2), + _config.IntegerSetting( + name='samples', + help='Number of samples in a full wiggle cycle.', + default=1024), + _config.IntegerSetting( + name='offset', + help='Center of the wiggle position (bits)', + default=None), + _config.IntegerSetting( + name='amplitude', + help='Amplitude of the wiggle sinusoid (bits)', + default=None), + _config.IntegerSetting( + name='wavelength', + help=('Instead of giving an explicit amplitude, you can specify ' + 'the laser wavelength in meters. The amplitude will then ' + 'be set to contain about two interference cycles.'), + default=None), + ] -- 2.26.2