-#!/usr/bin/python
-#
# calibcant - tools for thermally calibrating AFM cantilevers
#
-# Copyright (C) 2007,2008, William Trevor King
+# Copyright (C) 2008-2012 W. Trevor King <wking@drexel.edu>
#
-# This program 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.
+# This file is part of calibcant.
#
-# This program 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.
+# 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.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-# 02111-1307, USA.
+# 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.
#
-# The author may be contacted at <wking@drexel.edu> on the Internet, or
-# write to Trevor King, Drexel University, Physics Dept., 3141 Chestnut St.,
-# Philadelphia PA 19104, USA.
+# You should have received a copy of the GNU General Public License along with
+# calibcant. If not, see <http://www.gnu.org/licenses/>.
+
+"""Acquire, save, and load cantilever calibration bump data.
-"""
-Aquire, save, and load cantilever calibration bump data.
For measuring photodiode sensitivity.
-W. Trevor King Dec. 2007 - Oct. 2008
-
-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)
-
-Which are related by the parameters :
- zpGain Vzp_out / Vzp
- zpSensitivity Zp / Vzp
- photoSensitivity Vphoto / Zcant
-
-Cantilever calibration assumes a pre-calibrated z-piezo (zpSensitivity) and
-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 the bump_*() family of functions)
-The measured slope Vphoto/Vout is converted to photoSensitivity via
-Vphoto/Vzp_out * Vzp_out/Vzp * Vzp/Zp * Zp/Zcant = Vphoto/Zcant
- (measured) (1/zpGain) (1/zpSensitivity) (1) (photoSensitivity)
-
-We do all these measurements a few times to estimate statistical errors.
-
-The functions are layed out in the families:
- bump_*()
-For each family, * can be any of :
- aquire 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
- load_analyze_tweaked
- read a file with a list of paths to previously saved real world data
- load each file using *_load(), analyze using *_analyze(), and
- optionally plot using *_plot().
- Intended for re-processing old data.
-A family name without any _* extension (e.g. bump()),
- runs *_aquire(), *_save(), *_analyze(), *_plot().
+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)
+
+Which are related by the parameters:
+ zp_gain Vzp_out / Vzp
+ zp_sensitivity Zp / Vzp
+ photo_sensitivity Vphoto / Zcant
+
+Cantilever calibration assumes a pre-calibrated z-piezo
+(zp_sensitivity) and amplifier (zp_gain). 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 our modified
+NanoScope.
+
+Photo-sensitivity is measured by bumping the cantilever against the
+surface, where `Zp = Zcant`. The measured slope Vphoto/Vout is
+converted to photo-sensitivity via::
+
+ Vphoto/Vzp_out * Vzp_out/Vzp * Vzp/Zp * Zp/Zcant = Vphoto/Zcant
+ (measured) (1/zp_gain) (1/zp_sensitivity) (1) (photo_sensitivity)
+
+We do all these measurements a few times to estimate statistical
+errors.
"""
-import numpy
-import time
-import data_logger
-import z_piezo_utils
-import FFT_tools
-import linfit
-from calibcant_bump_analyze import bump_analyze
+import numpy as _numpy
-LOG_DATA = True # quietly grab all real-world data and log to LOG_DIR
-LOG_DIR = '$DEFAULT$/calibrate_cantilever'
+from pypiezo.base import convert_meters_to_bits as _convert_meters_to_bits
+from pypiezo.base import convert_bits_to_meters as _convert_bits_to_meters
-TEXT_VERBOSE = True # for debugging
+from . import LOG as _LOG
+from .bump_analyze import analyze as _analyze
+from .bump_analyze import save as _save
-# bump family
+def acquire(afm, config):
+ """Ramps `push_depth` closer and returns to the original position.
-def bump_aquire(zpiezo, push_depth, npoints, freq) :
- """
- Ramps closer push_depth and returns to the original position.
Inputs:
- zpiezo an opened zpiezo.zpiezo instance
- push_depth distance to approach, in nm
- npoints number points during the approach and during the retreat
- freq rate at which data is aquired
- log_dir directory to log data to (see data_logger.py).
- None to turn off logging (see also the global LOG_DATA).
- Returns the aquired ramp data dictionary, with data in DAC/ADC bits.
- """
- # generate the bump output
- start_pos = zpiezo.curPos()
- pos_dist = zpiezo.pos_nm2out(push_depth) - zpiezo.pos_nm2out(0)
- close_pos = start_pos + pos_dist
- appr = linspace(start_pos, close_pos, npoints)
- retr = linspace(close_pos, start_pos, npoints)
- out = concatenate((appr, retr))
- # run the bump, and measure deflection
- if TEXT_VERBOSE :
- print "Bump %g nm" % push_depth
- data = zpiezo.ramp(out, freq)
- # default saving, so we have a log in-case the operator is lazy ;)
- if LOG_DATA == True :
- log = data_logger.data_log(LOG_DIR, noclobber_logsubdir=False,
- log_name="bump_surface")
- log.write_dict_of_arrays(data)
- return data
-
-def bump_save(data, log_dir) :
- "Save the dictionary data, using data_logger.data_log()"
- if log_dir != None :
- log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
- log_name="bump")
- log.write_dict_of_arrays(data)
-
-def bump_load(datafile) :
- "Load the dictionary data, using data_logger.date_load()"
- dl = data_logger.data_load()
- data = dl.read_dict_of_arrays(path)
- return data
-
-def bump_plot(data, plotVerbose) :
- "Plot the bump (Vphoto vs Vzp) if plotVerbose or PYLAB_VERBOSE == True"
- if plotVerbose or PYLAB_VERBOSE :
- _import_pylab()
- _pylab.figure(BASE_FIGNUM)
- _pylab.plot(data["Z piezo output"], data["Deflection input"],
- '.', label='bump')
- _pylab.title("bump surface")
- _pylab.legend(loc='upper left')
- _flush_plot()
-
-def bump(zpiezo, push_depth, npoints=1024, freq=100e3,
- log_dir=None,
- plotVerbose=False) :
+ afm a pyafm.AFM instance
+ config a .config._BumpConfig instance
+
+ Returns the acquired ramp data dictionary, with data in DAC/ADC bits.
"""
- Wrapper around bump_aquire(), bump_save(), bump_analyze(), bump_plot()
+ afm.move_just_onto_surface(
+ depth=config['initial-position'], far=config['far-steps'],
+ setpoint=config['setpoint'],
+ min_slope_ratio=config['min-slope-ratio'])
+ #afm.piezo.jump('z', 32000)
+
+ _LOG.info(
+ 'bump the surface to a depth of {} m with a setpoint of {} V'.format(
+ config['push-depth'], config['setpoint']))
+
+ axis = afm.piezo.axis_by_name(afm.config['main-axis'])
+
+ start_pos = afm.piezo.last_output[afm.config['main-axis']]
+ start_pos_m = _convert_bits_to_meters(axis.config, start_pos)
+ close_pos_m = start_pos_m + config['push-depth']
+ close_pos = _convert_meters_to_bits(axis.config, close_pos_m)
+
+ dtype = afm.piezo.channel_dtype(
+ afm.config['main-axis'], direction='output')
+ appr = _numpy.linspace(
+ start_pos, close_pos, config['samples']).astype(dtype)
+ # switch numpy.append to numpy.concatenate with version 2.0+
+ out = _numpy.append(appr, appr[::-1])
+ out = out.reshape((len(out), 1))
+
+ # (samples) / (meters) * (meters/second) = (samples/second)
+ freq = (config['samples'] / config['push-depth']
+ * config['push-speed'])
+
+ data = afm.piezo.ramp(out, freq, output_names=[afm.config['main-axis']],
+ input_names=['deflection'])
+
+ out = out.reshape((len(out),))
+ data = data.reshape((data.size,))
+ return {afm.config['main-axis']: out, 'deflection': data}
+
+def run(afm, config, filename, group='/'):
+ """Wrapper around acquire(), analyze(), save().
+
+ >>> import os
+ >>> import tempfile
+ >>> from h5config.storage.hdf5 import pprint_HDF5
+ >>> from pyafm.storage import load_afm
+ >>> from .config import BumpConfig
+
+ >>> fd,filename = tempfile.mkstemp(suffix='.h5', prefix='calibcant-')
+ >>> os.close(fd)
+
+ >>> devices = []
+ >>> afm = load_afm()
+ >>> afm.load_from_config(devices=devices)
+
+ Test a bump:
+
+ >>> config = BumpConfig()
+ >>> output = run(afm=afm, config=config, filename=filename, group='/')
+ >>> output # doctest: +SKIP
+ 23265462.3047795
+ >>> pprint_HDF5(filename) # doctest: +ELLIPSIS, +REPORT_UDIFF
+ /
+ /config
+ /config/bump
+ <HDF5 dataset "far-steps": shape (), type "<i4">
+ 200
+ <HDF5 dataset "initial-position": shape (), type "<f8">
+ -5e-08
+ <HDF5 dataset "min-slope-ratio": shape (), type "<f8">
+ 10.0
+ <HDF5 dataset "model": shape (), type "|S9">
+ quadratic
+ <HDF5 dataset "push-depth": shape (), type "<f8">
+ 2e-07
+ <HDF5 dataset "push-speed": shape (), type "<f8">
+ 1e-06
+ <HDF5 dataset "samples": shape (), type "<i4">
+ 1024
+ <HDF5 dataset "setpoint": shape (), type "<f8">
+ 2.0
+ /processed
+ <HDF5 dataset "data": shape (), type "<f8">
+ ...
+ <HDF5 dataset "units": shape (), type "|S3">
+ V/m
+ /raw
+ /raw/deflection
+ <HDF5 dataset "data": shape (2048,), type "<u2">
+ [...]
+ <HDF5 dataset "units": shape (), type "|S4">
+ bits
+ /raw/z
+ <HDF5 dataset "data": shape (2048,), type "<u2">
+ [...]
+ <HDF5 dataset "units": shape (), type "|S4">
+ bits
+
+ Close the Comedi devices.
+
+ >>> for device in devices:
+ ... device.close()
+
+ Cleanup our temporary config file.
+
+ >>> os.remove(filename)
"""
- data = bump_aquire(zpiezo, push_depth, npoints, freq)
- bump_save(data, log_dir)
- photoSensitivity = bump_analyze(data, zpiezo.gain, zpiezo.sensitivity,
- zpiezo.pos_out2V, zpiezo.def_in2V)
- bump_plot(data, plotVerbose)
- return photoSensitivity
-
-def bump_load_analyze_tweaked(tweak_file, zpGain=_usual_zpGain,
- zpSensitivity=_usual_zpSensitivity,
- Vzp_out2V=_usual_Vzp_out2V,
- Vphoto_in2V=_usual_Vphoto_in2V,
- plotVerbose=False) :
- "Load the output file of tweak_calib_bump.sh, return an array of slopes"
- photoSensitivity = []
- for line in file(tweak_file, 'r') :
- parsed = line.split()
- path = parsed[0].split('\n')[0]
- # read the data
- full_data = bump_load(path)
- if len(parsed) == 1 :
- data = full_data # use whole bump
- else :
- # use the listed sections
- zp = []
- df = []
- for rng in parsed[1:] :
- p = rng.split(':')
- starti = int(p[0])
- stopi = int(p[1])
- zp.extend(full_data['Z piezo output'][starti:stopi])
- df.extend(full_data['Deflection input'][starti:stopi])
- data = {'Z piezo output': array(zp),
- 'Deflection input':array(df)}
- pSi = bump_analyze(data, zpGain, zpSensitivity,
- Vzp_out2V, Vphoto_in2V, plotVerbose)
- photoSensitivity.append(pSi)
- bump_plot(data, plotVervose)
- return array(photoSensitivity, dtype=numpy.float)
-
+ deflection_channel = afm.piezo.input_channel_by_name('deflection')
+ axis = afm.piezo.axis_by_name(afm.config['main-axis'])
+
+ raw = acquire(afm, config)
+ photo_sensitivity = _analyze(
+ config=config, data=raw, z_axis_config=axis.config,
+ deflection_channel_config=deflection_channel.config)
+ _save(filename=filename, group=group, config=config,
+ raw=raw, processed=photo_sensitivity)
+ return photo_sensitivity