1 # calibcant - tools for thermally calibrating AFM cantilevers
3 # Copyright (C) 2008-2012 W. Trevor King <wking@drexel.edu>
5 # This file is part of calibcant.
7 # calibcant is free software: you can redistribute it and/or modify it under
8 # the terms of the GNU General Public License as published by the Free Software
9 # Foundation, either version 3 of the License, or (at your option) any later
12 # calibcant is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along with
17 # calibcant. If not, see <http://www.gnu.org/licenses/>.
19 """Acquire, save, and load cantilever calibration bump data.
21 For measuring photodiode sensitivity.
23 The relevent physical quantities are:
24 Vzp_out Output z-piezo voltage (what we generate)
25 Vzp Applied z-piezo voltage (after external ZPGAIN)
26 Zp The z-piezo position
27 Zcant The cantilever vertical deflection
28 Vphoto The photodiode vertical deflection voltage (what we measure)
30 Which are related by the parameters:
32 zp_sensitivity Zp / Vzp
33 photo_sensitivity Vphoto / Zcant
35 Cantilever calibration assumes a pre-calibrated z-piezo
36 (zp_sensitivity) and amplifier (zp_gain). In our lab, the z-piezo is
37 calibrated by imaging a calibration sample, which has features with
38 well defined sizes, and the gain is set with a knob on our modified
41 Photo-sensitivity is measured by bumping the cantilever against the
42 surface, where `Zp = Zcant`. The measured slope Vphoto/Vout is
43 converted to photo-sensitivity via::
45 Vphoto/Vzp_out * Vzp_out/Vzp * Vzp/Zp * Zp/Zcant = Vphoto/Zcant
46 (measured) (1/zp_gain) (1/zp_sensitivity) (1) (photo_sensitivity)
48 We do all these measurements a few times to estimate statistical
52 import numpy as _numpy
54 from pypiezo.base import convert_meters_to_bits as _convert_meters_to_bits
55 from pypiezo.base import convert_bits_to_meters as _convert_bits_to_meters
57 from . import LOG as _LOG
58 from .bump_analyze import analyze as _analyze
59 from .bump_analyze import save as _save
62 def acquire(afm, config):
63 """Ramps `push_depth` closer and returns to the original position.
66 afm a pyafm.AFM instance
67 config a .config._BumpConfig instance
69 Returns the acquired ramp data dictionary, with data in DAC/ADC bits.
71 afm.move_just_onto_surface(
72 depth=config['initial-position'], far=config['far-steps'],
73 setpoint=config['setpoint'],
74 min_slope_ratio=config['min-slope-ratio'])
75 #afm.piezo.jump('z', 32000)
78 'bump the surface to a depth of {} m with a setpoint of {} V'.format(
79 config['push-depth'], config['setpoint']))
81 axis = afm.piezo.axis_by_name(afm.config['main-axis'])
83 start_pos = afm.piezo.last_output[afm.config['main-axis']]
84 start_pos_m = _convert_bits_to_meters(axis.config, start_pos)
85 close_pos_m = start_pos_m + config['push-depth']
86 close_pos = _convert_meters_to_bits(axis.config, close_pos_m)
88 dtype = afm.piezo.channel_dtype(
89 afm.config['main-axis'], direction='output')
90 appr = _numpy.linspace(
91 start_pos, close_pos, config['samples']).astype(dtype)
92 # switch numpy.append to numpy.concatenate with version 2.0+
93 out = _numpy.append(appr, appr[::-1])
94 out = out.reshape((len(out), 1))
96 # (samples) / (meters) * (meters/second) = (samples/second)
97 freq = (config['samples'] / config['push-depth']
98 * config['push-speed'])
100 data = afm.piezo.ramp(out, freq, output_names=[afm.config['main-axis']],
101 input_names=['deflection'])
103 out = out.reshape((len(out),))
104 data = data.reshape((data.size,))
105 return {afm.config['main-axis']: out, 'deflection': data}
107 def run(afm, config, filename, group='/'):
108 """Wrapper around acquire(), analyze(), save().
112 >>> from h5config.storage.hdf5 import pprint_HDF5
113 >>> from pyafm.storage import load_afm
114 >>> from .config import BumpConfig
116 >>> fd,filename = tempfile.mkstemp(suffix='.h5', prefix='calibcant-')
121 >>> afm.load_from_config(devices=devices)
125 >>> config = BumpConfig()
126 >>> output = run(afm=afm, config=config, filename=filename, group='/')
127 >>> output # doctest: +SKIP
129 >>> pprint_HDF5(filename) # doctest: +ELLIPSIS, +REPORT_UDIFF
133 <HDF5 dataset "far-steps": shape (), type "<i4">
135 <HDF5 dataset "initial-position": shape (), type "<f8">
137 <HDF5 dataset "min-slope-ratio": shape (), type "<f8">
139 <HDF5 dataset "model": shape (), type "|S9">
141 <HDF5 dataset "push-depth": shape (), type "<f8">
143 <HDF5 dataset "push-speed": shape (), type "<f8">
145 <HDF5 dataset "samples": shape (), type "<i4">
147 <HDF5 dataset "setpoint": shape (), type "<f8">
150 <HDF5 dataset "data": shape (), type "<f8">
152 <HDF5 dataset "units": shape (), type "|S3">
156 <HDF5 dataset "data": shape (2048,), type "<u2">
158 <HDF5 dataset "units": shape (), type "|S4">
161 <HDF5 dataset "data": shape (2048,), type "<u2">
163 <HDF5 dataset "units": shape (), type "|S4">
166 Close the Comedi devices.
168 >>> for device in devices:
171 Cleanup our temporary config file.
173 >>> os.remove(filename)
175 deflection_channel = afm.piezo.input_channel_by_name('deflection')
176 axis = afm.piezo.axis_by_name(afm.config['main-axis'])
178 raw = acquire(afm, config)
179 photo_sensitivity = _analyze(
180 config=config, data=raw, z_axis_config=axis.config,
181 deflection_channel_config=deflection_channel.config)
182 _save(filename=filename, group=group, config=config,
183 raw=raw, processed=photo_sensitivity)
184 return photo_sensitivity