Update unfold_protein to use the new pyafm.storage._load_afm.
authorW. Trevor King <wking@tremily.us>
Fri, 11 May 2012 17:42:47 +0000 (13:42 -0400)
committerW. Trevor King <wking@tremily.us>
Fri, 11 May 2012 18:14:52 +0000 (14:14 -0400)
This is a messy commit to get my Git repo in sync with the version I'm
currently using in the lab, but most of the changes have to do with
the nested-config overhaul starting with pypiezo's:

  commit de497a3734372f5fa7c92d6ff7bdb6b2e327c345
  Author: W. Trevor King <wking@drexel.edu>
  Date:   Thu Mar 15 10:13:20 2012 -0400

    Optional config-based-setup for PiezoAxis, OutputChannel, and InputChannel.

    See the module docstring for details on why this is useful.

unfold.py
unfold_protein/scan.py
unfold_protein/unfolder.py

index ba8440495517d7734dc2e26c15fc76bb4ad46e24..6a84f81bb60f54d2f8136dbcb54ba92d8bf6c119 100755 (executable)
--- a/unfold.py
+++ b/unfold.py
 import os
 import os.path
 
-from calibcant.config import Kelvin as _Kelvin
-from unfold_protein import __version__ as version
-from unfold_protein.afm import get_afm
+import numpy as _numpy
+
+from pyafm.storage import load_afm as _load_afm
+from pyafm.config import Kelvin as _Kelvin
+from unfold_protein import __version__
 from unfold_protein.unfolder import Unfolder
 from unfold_protein.scan import UnfoldScanner
 import unfold_protein.config as _config
@@ -35,7 +37,7 @@ if __name__ == '__main__':
     from argparse import ArgumentParser
 
     parser = ArgumentParser(
-        description='Play a pure tone', version=version)
+        description=__doc__, version=__version__)
     parser.add_argument(
         '-s', '--song',
         help='Path to a song to play when the experiment is complete')
@@ -43,23 +45,29 @@ if __name__ == '__main__':
     args = parser.parse_args()
 
     unfold_config = _config.UnfoldCycleConfig()
-    unfold_config['temperature'] = _config.TemperatureConfig()
-    unfold_config['temperature']['units'] = _Kelvin
     unfold_config['approach'] = _config.ApproachConfig()
     unfold_config['unfold'] = _config.UnfoldConfig()
+    unfold_config['unfold']['distance'] = 900e-9
     unfold_config['save'] = _config.SaveConfig()
     scan_config = _config.ScanConfig()
     scan_config['velocity'] = _config.VelocityScanConfig()
+    #scan_config['velocity']['unfolding velocities'] = _numpy.array([1e-6])
+    scan_config['velocity']['num loops'] = 500
     scan_config['position'] = _config.PositionScanConfig()
 
-    afm,comedi_device = get_afm(with_temperature=False)
-    unfolder = Unfolder(config=unfold_config, afm=afm)
-    scanner = UnfoldScanner(config=scan_config, unfolder=unfolder)
+    devices = []
     try:
+        afm = _load_afm()
+        afm.load_from_config(devices=devices)
+        afm.piezo.zero()
+        unfolder = Unfolder(config=unfold_config, afm=afm)
+        scanner = UnfoldScanner(config=scan_config, unfolder=unfolder)
         scanner.run()
     finally:
-        scanner.move_far_from_surface()
-        comedi_device.close()
+        afm.move_away_from_surface()
+        afm.piezo.zero()
+        for device in devices:
+            device.close()
         if args.song:
             song = os.path.abspath(os.path.expanduser(args.song))
             os.system("aplay '%s'" % song)
index 426c09e2a1a110d7c23661db26ab90e608d8ecf3..d0d4183bb055ae032694bdcb24ae395edfe78dc3 100644 (file)
@@ -20,7 +20,6 @@
 
 import signal as _signal
 
-from calibcant.calibrate import move_far_from_surface as _move_far_from_surface
 import pypiezo.base as _pypiezo_base
 
 from . import LOG as _LOG
@@ -46,7 +45,7 @@ class UnfoldScanner (object):
                 except _ExceptionTooFar:
                     self.stepper_approach()
                 except _ExceptionTooClose:
-                    self.move_far_from_surface()
+                    self.afm.move_far_from_surface()
                     self.stepper_approach()
                 else:
                     self.position_scan_step()
@@ -54,13 +53,6 @@ class UnfoldScanner (object):
     def _handle_stop_signal(self, signal, frame):
         self._stop = True
 
-    def move_far_from_surface(self):
-        _LOG.info('retract with the stepper motor by {} m'.format(
-                self.unfolder.config['approach']['far']))
-        _move_far_from_surface(
-            stepper=self.unfolder.afm.stepper,
-            distance=self.unfolder.config['approach']['far'])
-
     def stepper_approach(self):
         config = self.unfolder.config['approach']
         deflection = self.unfolder.read_deflection()
@@ -75,18 +67,25 @@ class UnfoldScanner (object):
         axis_name = 'x'
         config = self.config['position'] 
         axis_config = self.unfolder.afm.piezo.config.select_config(
-                'axes', self.unfolder.afm.axis_name,
+                'axes', self.unfolder.afm.config['main-axis'],
                 get_attribute=_pypiezo_base.get_axis_name
                 )
         pos = self.unfolder.afm.piezo.last_output[axis_name]
         pos_m = _pypiezo_base.convert_bits_to_meters(axis_config, pos)
-        next_pos_m = pos_m + self._state['x direction']*config['x step']
+        # HACK
+        try:
+            step = float(open('/home/wking/x-step', 'r').read())
+            _LOG.info('read step from file: {}'.format(step))
+        except Exception, e:
+            _LOG.warn('could not read step from file: {}'.format(e))
+            step = config['x step']
+        next_pos_m = pos_m + self._state['x direction']*step
         if next_pos_m > config['x max']:
             self._state['x direction'] = -1
-            next_pos_m = pos_m + self._state['x direction']*config['x step']
+            next_pos_m = pos_m + self._state['x direction']*step
         elif next_pos_m < config['x min']:
             self._state['x direction'] = 1
-            next_pos_m = pos_m + self._state['x direction']*config['x step']
+            next_pos_m = pos_m + self._state['x direction']*step
         next_pos = _pypiezo_base.convert_meters_to_bits(
             axis_config, next_pos_m)
         _LOG.info('move {} from {:g} to {:g} bits'.format(
index 874faf614729e68ec83c0a37dad971173b2caf25..204177a96d30086f2ecbadfcf17d37ab203c695a 100755 (executable)
@@ -18,6 +18,8 @@
 
 """Define classes for carrying out an unfolding cycle with an AFM."""
 
+from __future__ import division
+
 import email.utils as _email_utils
 import os.path as _os_path
 import time as _time
@@ -33,6 +35,7 @@ from . import package_config as _package_config
 try:
     import numpy as _numpy
     from matplotlib import pyplot as _pyplot
+    _pyplot.ion()
     FIGURE = _pyplot.figure()
 except (ImportError, RuntimeError), _matplotlib_import_error:
     _pyplot = None
@@ -82,12 +85,12 @@ class Unfolder (object):
         setpoint = deflection + config['relative setpoint']
         _LOG.info('approach with setpoint = {}'.format(setpoint))
         axis_config = self.afm.piezo.config.select_config(
-                'axes', self.afm.axis_name,
+                'axes', self.afm.config['main-axis'],
                 get_attribute=_pypiezo_base.get_axis_name
                 )
         def_config = self.afm.piezo.config.select_config(
             'inputs', 'deflection')
-        start_pos = self.afm.piezo.last_output[self.afm.axis_name]
+        start_pos = self.afm.piezo.last_output[self.afm.config['main-axis']]
 
         # calculate parameters for move_to_pos_or_def from config
         setpoint_bits = _pypiezo_base.convert_volts_to_bits(
@@ -101,7 +104,7 @@ class Unfolder (object):
 
         # run the approach
         data = self.afm.piezo.move_to_pos_or_def(
-            axis_name=self.afm.axis_name, deflection=setpoint_bits,
+            axis_name=self.afm.config['main-axis'], deflection=setpoint_bits,
             step=step_bits, frequency=frequency, return_data=True)
         data['setpoint'] = setpoint
         # check the output
@@ -109,7 +112,7 @@ class Unfolder (object):
             _LOG.info(('unfolding too far from the surface '
                        '(def {} < target {})').format(
                         data['deflection'].max(), setpoint_bits))
-            self.afm.piezo.jump(self.afm.axis_name, start_pos)
+            self.afm.piezo.jump(self.afm.config['main-axis'], start_pos)
             if _package_config['matplotlib']:
                 print data
                 FIGURE.clear()
@@ -132,15 +135,15 @@ class Unfolder (object):
         config = self.config['unfold']
         velocity = config['velocity']
         _LOG.info('unfold at {:g} m/s'.format(velocity))
-        axis = self.afm.piezo.axis_by_name(self.afm.axis_name)
+        axis = self.afm.piezo.axis_by_name(self.afm.config['main-axis'])
         axis_config = self.afm.piezo.config.select_config(
-                'axes', self.afm.axis_name,
+                'axes', self.afm.config['main-axis'],
                 get_attribute=_pypiezo_base.get_axis_name
                 )
         d = self.afm.piezo.channel_by_name('deflection')
         def_config = self.afm.piezo.config.select_config(
             'inputs', 'deflection')
-        start_pos = self.afm.piezo.last_output[self.afm.axis_name]
+        start_pos = self.afm.piezo.last_output[self.afm.config['main-axis']]
 
         start_pos_m = _pypiezo_base.convert_bits_to_meters(
             axis_config, start_pos)
@@ -148,21 +151,55 @@ class Unfolder (object):
         final_pos = _pypiezo_base.convert_meters_to_bits(
             axis_config, final_pos_m)
         dtype = self.afm.piezo.channel_dtype(
-            self.afm.axis_name, direction='output')
+            self.afm.config['main-axis'], direction='output')
+        frequency = config['frequency']
         num_steps = int(
-            config['distance'] / config['velocity'] * config['frequency']) + 1
+            config['distance'] / config['velocity'] * frequency) + 1
         #   (m)                * (s/m)              * (samples/s)
+        max_samples = self._get_max_samples()
+        if num_steps > max_samples:
+            num_steps = max_samples
+            frequency = (num_steps - 1)*config['velocity']/config['distance']
+            _LOG.info(('limit frequency to {} Hz (from {} Hz) to fit in DAQ '
+                       'card buffer').format(frequency, config['frequency']))
+
         out = _numpy.linspace(
             start_pos, final_pos, num_steps).astype(dtype)
         # TODO: check size of output buffer.
         out = out.reshape((len(out), 1))
         _LOG.debug(
             'unfolding from {} to {} in {} steps at {} Hz'.format(
-                start_pos, final_pos, num_steps, config['frequency']))
+                start_pos, final_pos, num_steps, frequency))
         data = self.afm.piezo.ramp(
-            data=out, frequency=config['frequency'],
-            output_names=[self.afm.axis_name], input_names=['deflection'])
-        return {self.afm.axis_name:out, 'deflection':data}
+            data=out, frequency=frequency, output_names=[self.afm.config['main-axis']],
+            input_names=['deflection'])
+        return {
+            'frequency': frequency, self.afm.config['main-axis']:out, 'deflection':data}
+
+    def _get_max_samples(self):
+        """Return the maximum number of samples that will fit on the card.
+
+        `pycomedi.utility.Writer` seems to have trouble when the the
+        output buffer is bigger than the card's onboard memory, so
+        we reduce the frequency if neccessary to fit the scan in
+        memory.
+        """
+        axis = self.afm.piezo.axis_by_name(self.afm.config['main-axis'])
+        buffer_size = axis.axis_channel.subdevice.get_buffer_size()
+        dtype = self.afm.piezo.channel_dtype(
+            self.afm.config['main-axis'], direction='output')
+        # `channel_dtype` returns `numpy.uint16`, `numpy.uint32`,
+        # etc., which are "generic types".  We use `numpy.dtype` to
+        # construct a `dtype` object:
+        #   >>> import numpy
+        #   >>> numpy.uint16
+        #   <type 'numpy.uint16'>
+        #   >>> numpy.dtype(numpy.uint16)
+        #   dtype('uint16')
+        dt = _numpy.dtype(dtype)
+        sample_size = dt.itemsize
+        max_output_samples = buffer_size // sample_size
+        return max_output_samples
 
     def _save(self, temperature, approach, unfold, timestamp):
         config = self.config['save']
@@ -176,8 +213,11 @@ class Unfolder (object):
             storage = _HDF5_Storage()
             config_cwg = _h5_create_group(f, 'config')
             storage.save(config=self.config, group=config_cwg)
+            afm_piezo_cwg = _h5_create_group(config_cwg, 'afm/piezo')
+            storage.save(config=self.afm.piezo.config, group=afm_piezo_cwg)
             f['timestamp'] = timestamp
-            f['temperature'] = temperature
+            if temperature is not None:
+                f['temperature'] = temperature
             for k,v in approach.items():
                 f['approach/{}'.format(k)] = v
             for k,v in unfold.items():
@@ -192,9 +232,9 @@ class Unfolder (object):
         axes.hold(True)
         axes.plot(approach['z'], approach['deflection'], label='Approach')
         axes.plot(unfold['z'], unfold['deflection'], label='Unfold')
-        axes.set_title('Unfolding too far')
         axes.legend(loc='best')
         axes.set_title('Unfolding')
+        _pyplot.draw()
         _pyplot.show()
 
     def zero_piezo(self):