# calibcant - tools for thermally calibrating AFM cantilevers
#
-# Copyright (C) 2008-2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2008-2012 W. Trevor King <wking@drexel.edu>
#
# 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/>.
"""Surface bump analysis (measures photodiode sensitivity).
-Separate the more general `bump_analyze()` from the other `bump_*()`
+Separate the more general `analyze()` from the other `bump_*()`
functions in calibcant.
The relevant physical quantities are:
`photo_sensitivity` is measured by bumping the cantilever against the
surface, where `Zp = Zcant` (see `calibrate.bump_acquire()`). The
measured slope `Vphoto/Vout` is converted to `photo_sensitivity` with
-`bump_analyze()`.
+`analyze()`.
>>> import os
>>> from pprint import pprint
>>> fd,filename = tempfile.mkstemp(suffix='.h5', prefix='calibcant-')
>>> os.close(fd)
->>> bump_config = BumpConfig()
+>>> config = BumpConfig()
>>> z_channel_config = ChannelConfig()
>>> z_channel_config['name'] = 'z'
>>> z_channel_config['maxdata'] = 200
>>> deflection_channel_config['conversion-coefficients'] = (0,1)
>>> deflection_channel_config['conversion-origin'] = 0
->>> raw_bump = {
+>>> raw = {
... 'z': numpy.arange(100, dtype=numpy.uint16),
... 'deflection': numpy.arange(100, dtype=numpy.uint16),
... }
->>> raw_bump['deflection'][:50] = 50
->>> processed_bump = bump_analyze(
-... raw_bump, bump_config, z_axis_config, deflection_channel_config)
->>> bump_plot(data=raw_bump) # TODO: convert to V and m
->>> bump_save(filename=filename, group='/bump/', raw_bump=raw_bump,
-... bump_config=bump_config, z_axis_config=z_axis_config,
-... deflection_channel_config=deflection_channel_config,
-... processed_bump=processed_bump)
+>>> raw['deflection'][:50] = 50
+>>> processed = analyze(
+... config=config, data=raw, z_axis_config=z_axis_config,
+... deflection_channel_config=deflection_channel_config)
+>>> plot(data=raw) # TODO: convert to V and m
+>>> save(filename=filename, group='/bump/',
+... config=config, raw=raw, processed=processed)
>>> pprint_HDF5(filename) # doctest: +ELLIPSIS, +REPORT_UDIFF
/
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">
1024
<HDF5 dataset "setpoint": shape (), type "<f8">
2.0
- /bump/config/deflection
- /bump/config/deflection/channel
- <HDF5 dataset "channel": shape (), type "<i4">
- 0
- <HDF5 dataset "conversion-coefficients": shape (2,), type "<i4">
- [0 1]
- <HDF5 dataset "conversion-origin": shape (), type "<i4">
- 0
- <HDF5 dataset "device": shape (), type "|S12">
- /dev/comedi0
- <HDF5 dataset "inverse-conversion-coefficients": shape (), type "|S1">
-<BLANKLINE>
- <HDF5 dataset "inverse-conversion-origin": shape (), type "<f8">
- 1.0
- <HDF5 dataset "maxdata": shape (), type "<i4">
- 200
- <HDF5 dataset "name": shape (), type "|S10">
- deflection
- <HDF5 dataset "range": shape (), type "<i4">
- 1
- <HDF5 dataset "subdevice": shape (), type "<i4">
- -1
- /bump/config/z
- /bump/config/z/axis
- /bump/config/z/axis/channel
- <HDF5 dataset "channel": shape (), type "<i4">
- 0
- <HDF5 dataset "conversion-coefficients": shape (2,), type "<i4">
- [0 1]
- <HDF5 dataset "conversion-origin": shape (), type "<i4">
- 0
- <HDF5 dataset "device": shape (), type "|S12">
- /dev/comedi0
- <HDF5 dataset "inverse-conversion-coefficients": shape (), type "|S1">
-<BLANKLINE>
- <HDF5 dataset "inverse-conversion-origin": shape (), type "<f8">
- 1.0
- <HDF5 dataset "maxdata": shape (), type "<i4">
- 200
- <HDF5 dataset "name": shape (), type "|S1">
- z
- <HDF5 dataset "range": shape (), type "<i4">
- 1
- <HDF5 dataset "subdevice": shape (), type "<i4">
- -1
- <HDF5 dataset "gain": shape (), type "<f8">
- 1.0
- <HDF5 dataset "maximum": shape (), type "|S4">
- None
- <HDF5 dataset "minimum": shape (), type "|S4">
- None
- <HDF5 dataset "monitor": shape (), type "|S1">
-<BLANKLINE>
- <HDF5 dataset "sensitivity": shape (), type "<f8">
- 1.0
- <HDF5 dataset "processed": shape (), type "<f8">
- 1.00...
+ /bump/processed
+ <HDF5 dataset "data": shape (), type "<f8">
+ 1.00...
+ <HDF5 dataset "units": shape (), type "|S3">
+ V/m
/bump/raw
- <HDF5 dataset "deflection": shape (100,), type "<u2">
- [50 50 ... 50 51 52 ... 97 98 99]
- <HDF5 dataset "z": shape (100,), type "<u2">
- [ 0 1 2 3 ... 97 98 99]
-
->>> (raw_bump,bump_config,z_axis_config,deflection_channel_config,
-... processed_bump) = bump_load(filename=filename, group='/bump/')
-
->>> pprint(raw_bump) # doctest: +ELLIPSIS
-{'deflection': array([50, 50, ... 51, 52, 53, ..., 97, 98, 99], dtype=uint16),
- 'z': array([ 0, 1, 2, ..., 97, 98, 99], dtype=uint16)}
->>> processed_bump # doctest: +ELLIPSIS
-1.00...
+ /bump/raw/deflection
+ <HDF5 dataset "data": shape (100,), type "<u2">
+ [50 50 ... 50 51 52 ... 97 98 99]
+ <HDF5 dataset "units": shape (), type "|S4">
+ bits
+ /bump/raw/z
+ <HDF5 dataset "data": shape (100,), type "<u2">
+ [ 0 1 2 3 ... 97 98 99]
+ <HDF5 dataset "units": shape (), type "|S4">
+ bits
+
+>>> data = load(filename=filename, group='/bump/')
+
+>>> pprint(data) # doctest: +ELLIPSIS, +REPORT_UDIFF
+{'config': {'bump': <BumpConfig ...>},
+ 'processed': 1.00...,
+ 'raw': {'deflection': array([50, 50, ..., 52, 53, ..., 98, 99], dtype=uint16),
+ 'z': array([ 0, 1, 2, ..., 97, 98, 99], dtype=uint16)}}
>>> os.remove(filename)
"""
-import h5py as _h5py
import numpy as _numpy
from scipy.optimize import leastsq as _leastsq
_matplotlib = None
_matplotlib_import_error = e
-from h5config.storage.hdf5 import HDF5_Storage as _HDF5_Storage
-from h5config.storage.hdf5 import h5_create_group as _h5_create_group
from pypiezo.base import convert_bits_to_volts as _convert_bits_to_volts
from pypiezo.base import convert_bits_to_meters as _convert_bits_to_meters
-from pypiezo.config import ChannelConfig as _ChannelConfig
from pypiezo.config import AxisConfig as _AxisConfig
+from pypiezo.config import InputChannelConfig as _InputChannelConfig
from . import LOG as _LOG
from . import package_config as _package_config
from .config import Linear as _Linear
from .config import Quadratic as _Quadratic
from .config import BumpConfig as _BumpConfig
+from .util import SaveSpec as _SaveSpec
+from .util import save as _save
+from .util import load as _load
-def bump_analyze(data, bump_config, z_axis_config,
- deflection_channel_config, plot=False):
+def analyze(config, data, z_axis_config,
+ deflection_channel_config, plot=False):
"""Return the slope of the bump.
Inputs:
data dictionary of data in DAC/ADC bits
- bump_config `.config._BumpConfig` instance
+ config `.config._BumpConfig` instance
z_axis_config z `pypiezo.config.AxisConfig` instance
deflection_channel_config
- deflection `pypiezo.config.ChannelConfig` instance
+ deflection `pypiezo.config.InputChannelConfig` instance
plot boolean overriding matplotlib config setting.
Returns:
photo_sensitivity (Vphoto/Zcant) in Volts/m
deflection_channel_config, data['deflection'])
high_voltage_rail = _convert_bits_to_volts(
deflection_channel_config, deflection_channel_config['maxdata'])
- if bump_config['model'] == _Linear:
+ if config['model'] == _Linear:
kwargs = {
'param_guesser': limited_linear_param_guess,
'model': limited_linear,
'model': limited_quadratic,
'sensitivity_from_fit_params': limited_quadratic_sensitivity,
}
- photo_sensitivity = bump_fit(
+ photo_sensitivity = fit(
z, deflection, high_voltage_rail=high_voltage_rail, plot=plot,
**kwargs)
return photo_sensitivity
i_high = i
x_contact = float(x[i_low])
x_high = float(x[i_high])
- if x_high == x_contact:
- x.tofile('x-bad.dat', sep='\n')
- y.tofile('y-bad.dat', sep='\n')
+ if x_high == x_contact: # things must be pretty flat
+ x_contact = (x_contact + x[0]) / 2
slope = (y_high - y_contact) / (x_high - x_contact)
return (x_contact, y_contact, slope)
slope = params[2]
return slope
-def bump_fit(z, deflection, high_voltage_rail,
- param_guesser=limited_quadratic_param_guess,
- model=limited_quadratic,
- sensitivity_from_fit_params=limited_quadratic_sensitivity,
- plot=False):
+def fit(z, deflection, high_voltage_rail,
+ param_guesser=limited_quadratic_param_guess,
+ model=limited_quadratic,
+ sensitivity_from_fit_params=limited_quadratic_sensitivity,
+ plot=False):
"""Fit a aurface bump and return the photodiode sensitivitiy.
Parameters:
def residual(p, deflection, z):
return model(z, p, high_voltage_rail=high_voltage_rail) - deflection
param_guess = param_guesser(z, deflection)
- p,cov,info,mesg,ier = _leastsq(
- residual, param_guess, args=(deflection, z), full_output=True,
- maxfev=int(10e3))
+ try:
+ p,cov,info,mesg,ier = _leastsq(
+ residual, param_guess, args=(deflection, z), full_output=True,
+ maxfev=int(10e3))
+ except ValueError:
+ zd = _numpy.ndarray(list(z.shape) + [2], dtype=z.dtype)
+ zd[:,0] = z
+ zd[:,1] = d
+ _numpy.savetxt('/tmp/z-deflection.dat', zd, delimiter='\t')
+ raise
_LOG.debug('fitted params: %s' % p)
_LOG.debug('covariance matrix: %s' % cov)
#_LOG.debug('info: %s' % info)
if plot or _package_config['matplotlib']:
yguess = model(z, param_guess, high_voltage_rail=high_voltage_rail)
yfit = model(z, p, high_voltage_rail=high_voltage_rail)
- bump_plot({'z': z, 'deflection': deflection}, yguess=yguess, yfit=yfit)
+ _plot({'z': z, 'deflection': deflection}, yguess=yguess, yfit=yfit)
return sensitivity_from_fit_params(p)
-def bump_save(filename, group='/', raw_bump=None, bump_config=None,
- z_axis_config=None, deflection_channel_config=None,
- processed_bump=None):
- with _h5py.File(filename, 'a') as f:
- cwg = _h5_create_group(f, group)
- if raw_bump is not None:
- for k in ['z', 'deflection']:
- try:
- del cwg['raw/{}'.format(k)]
- except KeyError:
- pass
- cwg['raw/z'] = raw_bump['z']
- cwg['raw/deflection'] = raw_bump['deflection']
- storage = _HDF5_Storage()
- for config,key in [(bump_config, 'config/bump'),
- (z_axis_config, 'config/z/axis'),
- (deflection_channel_config,
- 'config/deflection/channel')]:
- if config is None:
- continue
- config_cwg = _h5_create_group(cwg, key)
- storage.save(config=config, group=config_cwg)
- if processed_bump is not None:
- try:
- del cwg['processed']
- except KeyError:
- pass
- cwg['processed'] = processed_bump
-
-def bump_load(filename, group='/'):
- assert group.endswith('/')
- raw_bump = processed_bump = None
- configs = []
- with _h5py.File(filename, 'a') as f:
- try:
- raw_bump = {
- 'z': f[group+'raw/z'][...],
- 'deflection': f[group+'raw/deflection'][...],
- }
- except KeyError:
- pass
- for Config,key in [(_BumpConfig, 'config/bump'),
- (_AxisConfig, 'config/z/axis'),
- (_ChannelConfig, 'config/deflection/channel')]:
- config = Config(storage=_HDF5_Storage(
- filename=filename, group=group+key))
- configs.append(config)
- try:
- processed_bump = float(f[group+'processed'][...])
- except KeyError:
- pass
- ret = [raw_bump]
- ret.extend(configs)
- ret.append(processed_bump)
- for config in configs:
- config.load()
- return tuple(ret)
-
-def bump_plot(data, yguess=None, yfit=None):
+def save(filename=None, group='/', config=None, z_axis_config=None,
+ deflection_channel_config=None, raw=None, processed=None):
+ specs = [
+ _SaveSpec(item=config, relpath='config/bump', config=_BumpConfig),
+ _SaveSpec(item=z_axis_config, relpath='config/z', config=_AxisConfig),
+ _SaveSpec(item=deflection_channel_config, relpath='config/deflection',
+ config=_InputChannelConfig),
+ _SaveSpec(item=processed, relpath='processed', units='V/m'),
+ ]
+ if raw is not None:
+ for key in raw.keys():
+ specs.append(_SaveSpec(
+ item=raw[key], relpath='raw/{}'.format(key), array=True,
+ units='bits'))
+ _save(filename=filename, group=group, specs=specs)
+
+def load(filename=None, group='/'):
+ specs = [
+ _SaveSpec(key=('config', 'bump'), relpath='config/bump',
+ config=_BumpConfig),
+ _SaveSpec(key=('config', 'z_axis_config'), relpath='config/z',
+ config=_AxisConfig),
+ _SaveSpec(key=('config', 'deflection_channel_config'),
+ relpath='config/deflection', config=_InputChannelConfig),
+ _SaveSpec(key=('raw', 'z'), relpath='raw/z', array=True, units='bits'),
+ _SaveSpec(key=('raw', 'deflection'), relpath='raw/deflection',
+ array=True, units='bits'),
+ _SaveSpec(key=('processed',), relpath='processed', units='V/m'),
+ ]
+ return _load(filename=filename, group=group, specs=specs)
+
+def plot(data, yguess=None, yfit=None):
"Plot the bump (Vphoto vs Vzp)"
if not _matplotlib:
raise _matplotlib_import_error
#residual_axes.legend(loc='upper right')
residual_axes.set_xlabel('Z piezo (meters)')
residual_axes.set_ylabel('Photodiode (Volts)')
- figure.show()
+ if hasattr(figure, 'show'):
+ figure.show()
+_plot = plot # alternative name for use inside fit()