# calibcant - tools for thermally calibrating AFM cantilevers
#
-# Copyright (C) 2008-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2008-2013 W. Trevor King <wking@tremily.us>
#
# This file is part of calibcant.
#
-# calibcant is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser 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 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 Lesser General Public License for more details.
+# 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 Lesser General Public
-# License along with calibcant. If not, see
-# <http://www.gnu.org/licenses/>.
+# 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.
NanoScope.
Photo-sensitivity 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 photo-sensitivity via
+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.
-
-The functions are layed out in the families:
- bump_*()
-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()`,
-`*_save()`, `*_analyze()`.
-
-If `package_config['matplotlib']` is `True`, `*_analyze()` will call
-`*_plot()` internally.
"""
import numpy as _numpy
from pypiezo.base import convert_bits_to_meters as _convert_bits_to_meters
from . import LOG as _LOG
-from .bump_analyze import bump_analyze as _bump_analyze
-from .bump_analyze import bump_save as _bump_save
+from .bump_analyze import analyze as _analyze
+from .bump_analyze import save as _save
-def bump_acquire(afm, bump_config):
+def acquire(afm, config):
"""Ramps `push_depth` closer and returns to the original position.
Inputs:
- afm a pyafm.AFM instance
- bump_config a .config._BumpConfig instance
+ afm a pyafm.AFM instance
+ config a .config._BumpConfig instance
Returns the acquired ramp data dictionary, with data in DAC/ADC bits.
"""
afm.move_just_onto_surface(
- depth=bump_config['initial-position'], far=bump_config['far-steps'])
+ 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 %g m'
- % bump_config['push-depth'])
+ _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.axis_name)
+ axis = afm.piezo.axis_by_name(afm.config['main-axis'])
- start_pos = afm.piezo.last_output[afm.axis_name]
- start_pos_m = _convert_bits_to_meters(
- axis.axis_channel_config, axis.axis_config, start_pos)
- close_pos_m = start_pos_m + bump_config['push-depth']
- close_pos = _convert_meters_to_bits(
- axis.axis_channel_config, axis.axis_config, close_pos_m)
+ 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.axis_name, direction='output')
+ dtype = afm.piezo.channel_dtype(
+ afm.config['main-axis'], direction='output')
appr = _numpy.linspace(
- start_pos, close_pos, bump_config['samples']).astype(dtype)
+ 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 = (bump_config['samples'] / bump_config['push-depth']
- * bump_config['push-speed'])
+ freq = (config['samples'] / config['push-depth']
+ * config['push-speed'])
- data = afm.piezo.ramp(out, freq, output_names=[afm.axis_name],
+ 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.axis_name: out, 'deflection': data}
+ return {afm.config['main-axis']: out, 'deflection': data}
-def bump(afm, bump_config, filename, group='/'):
- """Wrapper around bump_acquire(), bump_analyze(), bump_save().
+def run(afm, config, filename, group='/'):
+ """Wrapper around acquire(), analyze(), save().
>>> import os
>>> import tempfile
- >>> 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 (HDF5_ChannelConfig, HDF5_AxisConfig,
- ... pprint_HDF5)
- >>> from stepper import Stepper
- >>> from pyafm import AFM
- >>> from .config import HDF5_BumpConfig
+ >>> 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)
- >>> 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 = HDF5_AxisConfig(filename, '/bump/config/z/axis')
- >>> axis_config.update(
- ... {'gain':20, 'sensitivity':8e-9, 'minimum':-9})
- >>> axis_channel_config = HDF5_ChannelConfig(
- ... filename, '/bump/config/z/channel')
- >>> input_channel_config = HDF5_ChannelConfig(
- ... filename, '/bump/config/deflection/channel')
-
- >>> a = PiezoAxis(axis_config=axis_config,
- ... axis_channel_config=axis_channel_config,
- ... axis_channel=axis_channel, name='z')
- >>> a.setup_config()
-
- >>> c = InputChannel(
- ... channel_config=input_channel_config, channel=input_channel,
- ... name='deflection')
- >>> c.setup_config()
-
- >>> piezo = AFMPiezo(axes=[a], input_channels=[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)
+ >>> devices = []
+ >>> afm = load_afm()
+ >>> afm.load_from_config(devices=devices)
Test a bump:
- >>> bump_config = HDF5_BumpConfig(
- ... filename=filename, group='/bump/config/bump')
- >>> bump(afm, bump_config, filename, group='/bump')
- TODO: replace skipped example data with real-world values
+ >>> config = BumpConfig()
+ >>> output = run(afm=afm, config=config, filename=filename, group='/')
+ >>> output # doctest: +SKIP
+ 23265462.3047795
>>> pprint_HDF5(filename) # doctest: +ELLIPSIS, +REPORT_UDIFF
-
- Close the Comedi device.
-
- >>> d.close()
+ /
+ /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)
"""
deflection_channel = afm.piezo.input_channel_by_name('deflection')
- axis = afm.piezo.axis_by_name(afm.axis_name)
-
- data = bump_acquire(afm, bump_config)
- photo_sensitivity = _bump_analyze(
- data, bump_config, z_channel_config=axis.axis_channel_config,
- z_axis_config=axis.axis_config,
- deflection_channel_config=deflection_channel.channel_config)
- _bump_save(
- filename, group, data, bump_config,
- z_channel_config=axis.axis_channel_config,
- z_axis_config=axis.axis_config,
- deflection_channel_config=deflection_channel.channel_config,
- processed_bump=photo_sensitivity)
+ 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