From 9d59696a88b0783167beec25127bad320b7852c9 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 15 Mar 2012 17:56:48 -0400 Subject: [PATCH] Move calibcant.calibrate.move_far_from_surface -> pyafm.afm.AFM.move_away_from_surface. --- calibcant/calibrate.py | 402 ----------------------------------------- pyafm/afm.py | 9 + 2 files changed, 9 insertions(+), 402 deletions(-) delete mode 100644 calibcant/calibrate.py diff --git a/calibcant/calibrate.py b/calibcant/calibrate.py deleted file mode 100644 index e7b0204..0000000 --- a/calibcant/calibrate.py +++ /dev/null @@ -1,402 +0,0 @@ -# calibcant - tools for thermally calibrating AFM cantilevers -# -# Copyright (C) 2008-2012 W. Trevor King -# -# This file is part of calibcant. -# -# calibcant is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# calibcant is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# calibcant. If not, see . - -"""Acquire and analyze cantilever calibration data. - -The relevent physical quantities are: - -* Vzp_out Output z-piezo voltage (what we generate) -* Vzp Applied z-piezo voltage (after external ZPGAIN) -* Zp The z-piezo position -* Zcant The cantilever vertical deflection -* Vphoto The photodiode vertical deflection voltage (what we measure) -* Fcant The force on the cantilever -* T The temperature of the cantilever and surrounding solution -* (another thing we measure or guess) -* k_b Boltzmann's constant - -Which are related by the parameters: - -* zpGain Vzp_out / Vzp -* zpSensitivity Zp / Vzp -* photoSensitivity Vphoto / Zcant -* k_cant Fcant / Zcant - -Cantilever calibration assumes a pre-calibrated z-piezo -(zpSensitivity) and a amplifier (zpGain). In our lab, the z-piezo is -calibrated by imaging a calibration sample, which has features with -well defined sizes, and the gain is set with a knob on the Nanoscope. - -photoSensitivity is measured by bumping the cantilever against the -surface, where Zp = Zcant (see bump_acquire() and the bump_analyze -submodule). - -k_cant is measured by watching the cantilever vibrate in free solution -(see the vib_acquire() and the vib_analyze submodule). The average -energy of the cantilever in the vertical direction is given by the -equipartition theorem. - -.. math:: \frac{1}{2} k_b T = \frac{1}{2} k_cant - -so - -.. math:: k_cant = \frac{k_b T}{Zcant**2} - -but - -.. math:: Zcant = \frac{Vphoto}{photoSensitivity} - -so - -.. math:: k_cant = \frac{k_b T * photoSensitivty^2}{} - -We measured photoSensitivity with the surface bumps. We can either -measure T using an external function (see temperature.py), or just -estimate it (see T_acquire() and the T_analyze submodule). Guessing -room temp ~22 deg C is actually fairly reasonable. Assuming the -actual fluid temperature is within +/- 5 deg, the error in the spring -constant k_cant is within 5/273.15 ~= 2%. A time series of Vphoto -while we're far from the surface and not changing Vzp_out will give us -the average variance . - -We do all these measurements a few times to estimate statistical -errors. - -The functions are layed out in the families:: - - bump_*(), vib_*(), T_*(), and calib_*() - -For each family, * can be any of: - -* acquire get real-world data -* save store real-world data to disk -* load get real-world data from disk -* analyze interperate the real-world data. -* plot show a nice graphic to convince people we're working :p - -A family name without any `_*` extension (e.g. `bump()`), runs -`*_acquire()`, `*_analyze()`, and `*_save()`. `*_analyze()` will run -`*_plot()` if `matplotlib` is set in `calibcant.package_config`. -""" - -from numpy import zeros as _zeros -from numpy import float as _float -from time import sleep as _sleep - -from . import LOG as _LOG - -from .bump import bump as _bump -from .T import T as _T -from .vib import vib as _vib -from .analyze import calib_analyze as _calib_analyze -from .analyze import calib_save as _calib_save - - -def move_far_from_surface(stepper, distance): - """Step back approximately `distance` meters. - """ - steps = int(distance/stepper.step_size) - _LOG.info('step back %d steps (~%g m)' % (steps, distance)) - stepper.step_relative(-steps) - -def calib_acquire(afm, calibration_config, filename=None, group='/'): - """Acquire data for calibrating a cantilever in one function. - - Inputs: - afm a pyafm.AFM instance - calibration_config a .config._CalibrationConfig instance - - Outputs (all are arrays of recorded data): - bumps measured (V_photodiode / nm_tip) proportionality constant - Ts measured temperature (K) - vibs measured V_photodiode variance (Volts**2) in free solution - - The temperatures are collected after moving far from the surface - but before and vibrations are measured to give everything time to - settle after the big move. - """ - assert group.endswith('/'), group - - bumps = _zeros((calibration_config['num-bumps'],), dtype=_float) - for i in range(calibration_config['num-bumps']): - _LOG.info('acquire bump %d of %d' % (i, calibration_config['num-bumps'])) - bumps[i] = _bump(afm=afm, bump_config=calibration_config['bump'], - filename=filename, group='%sbump/%d/' % (group, i)) - _LOG.debug('bumps: %s' % bumps) - - move_far_from_surface( - afm.stepper, distance=calibration_config['vibration-spacing']) - - Ts = _zeros((calibration_config['num-temperatures'],), dtype=_float) - for i in range(calibration_config['num-temperatures']): - _LOG.info('acquire T %d of %d' - % (i, calibration_config['num-temperatures'])) - Ts[i] = _T( - get_T=afm.get_temperature, - temperature_config=calibration_config['temperature'], - filename=filename, group='%stemperature/%d/' % (group, i)) - _sleep(calibration_config['temperature-sleep']) - _LOG.debug('temperatures: %s' % Ts) - - # get vibs - vibs = _zeros((calibration_config['num-vibrations'],), dtype=_float) - for i in range(calibration_config['num-vibrations']): - vibs[i] = _vib( - piezo=afm.piezo, vibration_config=calibration_config['vibration'], - filename=filename, group='%svibration/%d/' % (group, i)) - _LOG.debug('vibrations: %s' % vibs) - - return (bumps, Ts, vibs) - -def calib(afm, calibration_config, filename=None, group='/'): - """Calibrate a cantilever in one function. - - Inputs: - (see `calib_acquire()`) - - Outputs: - k cantilever spring constant (in N/m, or equivalently nN/nm) - k_s standard deviation in our estimate of k - - >>> import os - >>> from pprint import pprint - >>> import tempfile - >>> from h5config.storage.hdf5 import pprint_HDF5 - >>> from pycomedi.device import Device - >>> from pycomedi.subdevice import StreamingSubdevice - >>> from pycomedi.channel import AnalogChannel, DigitalChannel - >>> from pycomedi.constant import AREF, IO_DIRECTION, SUBDEVICE_TYPE, UNIT - >>> from pypiezo.afm import AFMPiezo - >>> from pypiezo.base import PiezoAxis, InputChannel - >>> from pypiezo.config import ChannelConfig, AxisConfig - >>> from stepper import Stepper - >>> from pyafm.afm import AFM - >>> from .config import (CalibrationConfig, BumpConfig, - ... TemperatureConfig, VibrationConfig) - >>> from .analyze import calib_load_all - - >>> fd,filename = tempfile.mkstemp(suffix='.h5', prefix='calibcant-') - >>> os.close(fd) - - >>> d = Device('/dev/comedi0') - >>> d.open() - - Setup an `AFMPiezo` instance. - - >>> s_in = d.find_subdevice_by_type(SUBDEVICE_TYPE.ai, - ... factory=StreamingSubdevice) - >>> s_out = d.find_subdevice_by_type(SUBDEVICE_TYPE.ao, - ... factory=StreamingSubdevice) - - >>> axis_channel = s_out.channel( - ... 0, factory=AnalogChannel, aref=AREF.ground) - >>> input_channel = s_in.channel(0, factory=AnalogChannel, aref=AREF.diff) - >>> for chan in [axis_channel, input_channel]: - ... chan.range = chan.find_range(unit=UNIT.volt, min=-10, max=10) - - We set the minimum voltage for the `z` axis to -9 (a volt above - the minimum possible voltage) to help with testing - `.get_surface_position`. Without this minimum voltage, small - calibration errors could lead to a railed -10 V input for the - first few surface approaching steps, which could lead to an - `EdgeKink` error instead of a `FlatFit` error. - - >>> axis_config = AxisConfig() - >>> axis_config.update( - ... {'gain':20, 'sensitivity':8e-9, 'minimum':-9}) - >>> axis_channel_config = ChannelConfig() - >>> axis_channel_config['name'] = 'z' - >>> axis_config['channel'] = axis_channel_config - >>> input_channel_config = ChannelConfig() - >>> input_channel_config['name'] = 'deflection' - - >>> a = PiezoAxis(config=axis_config, axis_channel=axis_channel) - >>> a.setup_config() - - >>> c = InputChannel(config=input_channel_config, channel=input_channel) - >>> c.setup_config() - - >>> piezo = AFMPiezo(axes=[a], inputs=[c]) - - Setup a `stepper` instance. - - >>> s_d = d.find_subdevice_by_type(SUBDEVICE_TYPE.dio) - >>> d_channels = [s_d.channel(i, factory=DigitalChannel) - ... for i in (0, 1, 2, 3)] - >>> for chan in d_channels: - ... chan.dio_config(IO_DIRECTION.output) - - >>> def write(value): - ... s_d.dio_bitfield(bits=value, write_mask=2**4-1) - - >>> stepper = Stepper(write=write) - - Setup an `AFM` instance. - - >>> afm = AFM(piezo, stepper) - - Test calibration: - - >>> calibration_config = CalibrationConfig() - >>> calibration_config['bump'] = BumpConfig() - >>> calibration_config['temperature'] = TemperatureConfig() - >>> calibration_config['vibration'] = VibrationConfig() - >>> calib(afm, calibration_config, filename=filename, group='/') - TODO: replace skipped example data with real-world values - >>> pprint_HDF5(filename) # doctest: +ELLIPSIS, +REPORT_UDIFF - / - /bump - /bump/0 - /bump/0/config - /bump/0/config/bump - - 200 - ... - /bump/0/config/deflection - /bump/0/config/deflection/channel - - 0 - ... - /bump/0/config/z - /bump/0/config/z/axis - /bump/0/config/z/axis/channel - - 0 - ... - - 20 - ... - - ... - /bump/0/raw - - [...] - - [...] - /bump/1 - ... - /calibration - /calibration/config - /calibration/config/bump - - 200 - ... - - 10 - ... - /calibration/processed - /calibration/processed/spring-constant - - ... - - ... - - N/m - /calibration/raw - /calibration/raw/photodiode-sensitivity - - [...] - - V/m - /calibration/raw/temperature - - [...] - - K - /calibration/raw/thermal-vibration-variance - - [...] - - V^2 - /temperature - /temperature/0 - /temperature/0/config - - False - - Celsius - - 295.15 - - 22 - /temperature/1 - ... - /vibration - /vibration/0 - /vibration/0/config - /vibration/0/config/deflection - - 0 - ... - /vibration/0/config/vibration - - 2048 - ... - - ... - /vibration/0/raw - - [...] - /vibration/1 - ... - /vibration/19 - ... - /vibration/19/raw - - [...] - >>> everything = calib_load_all(filename, '/') - >>> pprint(everything) # doctest: +ELLIPSIS, +REPORT_UDIFF - {'bump_details': [{'bump_config': , - 'deflection_channel_config': , - 'processed_bump': ..., - 'raw_bump': {'deflection': array([...], dtype=uint16), - 'z': array([...], dtype=uint16)}, - 'z_axis_config': }, - ...], - 'bumps': array([...]), - 'calibration_config': , - 'k': ..., - 'k_s': ..., - 'temperature_details': [{'processed_temperature': ..., - 'raw_temperature': array(22), - 'temperature_config': }, - ...], - 'temperatures': array([...]), - 'vibration_details': [{'deflection_channel_config': , - 'processed_vibration': ..., - 'raw_vibration': array([...], dtype=uint16), - 'vibration_config': }, - ...], - 'vibrations': array([...])} - - Close the Comedi device. - - >>> d.close() - - Cleanup our temporary config file. - - os.remove(filename) - """ - bumps, Ts, vibs = calib_acquire( - afm, calibration_config, filename=filename, group=group) - # TODO: convert vib units? - k,k_s = _calib_analyze(bumps, Ts, vibs) - _calib_save(filename, group=group+'calibration/', bumps=bumps, - temperatures=Ts, vibrations=vibs, - calibration_config=calibration_config, k=k, k_s=k_s) - return (k, k_s) diff --git a/pyafm/afm.py b/pyafm/afm.py index 2c228e1..3093a07 100644 --- a/pyafm/afm.py +++ b/pyafm/afm.py @@ -454,3 +454,12 @@ class AFM (object): cd, target_deflection)) self.stepper.single_step(1) # step in cd = self.piezo.read_deflection() + + def move_away_from_surface(stepper, distance=None): + """Step back approximately `distance` meters. + """ + if distance is None: + distance = self.config['far'] + steps = int(distance/self.stepper.step_size) + _LOG.info('step back {} steps (~{} m)' % (steps, distance)) + self.stepper.step_relative(-steps) -- 2.26.2