Update unfold_protein to use the new pyafm.storage._load_afm.
[unfold-protein.git] / unfold_protein / unfolder.py
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):