From 87f12e4ce804dfe1d0cfffff684ecac28929112d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 11 May 2012 13:42:47 -0400 Subject: [PATCH] Update unfold_protein to use the new pyafm.storage._load_afm. 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 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 | 30 ++++++++++------ unfold_protein/scan.py | 25 +++++++------- unfold_protein/unfolder.py | 70 ++++++++++++++++++++++++++++++-------- 3 files changed, 86 insertions(+), 39 deletions(-) diff --git a/unfold.py b/unfold.py index ba84404..6a84f81 100755 --- a/unfold.py +++ b/unfold.py @@ -23,9 +23,11 @@ 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) diff --git a/unfold_protein/scan.py b/unfold_protein/scan.py index 426c09e..d0d4183 100644 --- a/unfold_protein/scan.py +++ b/unfold_protein/scan.py @@ -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( diff --git a/unfold_protein/unfolder.py b/unfold_protein/unfolder.py index 874faf6..204177a 100755 --- a/unfold_protein/unfolder.py +++ b/unfold_protein/unfolder.py @@ -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 + # + # >>> 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): -- 2.26.2