Move wiggle options to WiggleConfig and allow easy saving of wiggle data.
authorW. Trevor King <wking@drexel.edu>
Wed, 14 Mar 2012 21:46:27 +0000 (17:46 -0400)
committerW. Trevor King <wking@drexel.edu>
Wed, 14 Mar 2012 21:46:27 +0000 (17:46 -0400)
pypiezo/afm.py
pypiezo/config.py

index 21d0186ea4cb4a4ede78c1dce7eed8b9d72812ca..88dd3e85bc91001d21bcef69ccf1a5db8b6c0a52 100644 (file)
@@ -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
index 93ebb97bb6c912225b227519d83e6b5c8026c1ac..14d0ca9e1c316d979a4e7f0527c4bf8dec424048 100644 (file)
@@ -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),
+        ]