X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=calibcant%2Fbump.py;h=3ba8c7cc8133c1ca0b7424ec6aca3513a3f80fd1;hb=HEAD;hp=8cf78ad34dd321b08a28612c7ed474aad1f31b2e;hpb=1addd0cdb6e3612c326d8bcfdb36f4a6f25e1bc3;p=calibcant.git diff --git a/calibcant/bump.py b/calibcant/bump.py index 8cf78ad..3ba8c7c 100644 --- a/calibcant/bump.py +++ b/calibcant/bump.py @@ -1,22 +1,20 @@ # calibcant - tools for thermally calibrating AFM cantilevers # -# Copyright (C) 2008-2011 W. Trevor King +# Copyright (C) 2008-2013 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 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 -# . +# You should have received a copy of the GNU General Public License along with +# calibcant. If not, see . """Acquire, save, and load cantilever calibration bump data. @@ -41,29 +39,14 @@ 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` (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 @@ -72,160 +55,130 @@ 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 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 + + 200 + + -5e-08 + + 10.0 + + quadratic + + 2e-07 + + 1e-06 + + 1024 + + 2.0 + /processed + + ... + + V/m + /raw + /raw/deflection + + [...] + + bits + /raw/z + + [...] + + 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