* Vphoto The photodiode vertical deflection voltage (what we measure)
* Fcant The force on the cantilever
* T The temperature of the cantilever and surrounding solution
-* (another thing we measure or guess)
+ (another thing we measure or guess)
* k_b Boltzmann's constant
Which are related by the parameters:
We do all these measurements a few times to estimate statistical
errors.
-
-The functions are layed out in the families::
-
- bump_*(), vib_*(), T_*(), and calib_*()
-
-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()`, `*_analyze()`, and `*_save()`. `*_analyze()` will run
-`*_plot()` if `matplotlib` is set in `calibcant.package_config`.
"""
-from numpy import zeros as _zeros
-from numpy import float as _float
from time import sleep as _sleep
-from . import LOG as _LOG
-
-from .bump import bump as _bump
-from .T import T as _T
-from .vib import vib as _vib
-from .analyze import calib_analyze as _calib_analyze
-from .analyze import calib_save as _calib_save
-
-
-def move_far_from_surface(stepper, distance):
- """Step back approximately `distance` meters.
- """
- steps = int(distance/stepper.step_size)
- _LOG.info('step back %d steps (~%g m)' % (steps, distance))
- stepper.step_relative(-steps)
-
-def calib_acquire(afm, calibration_config, filename=None, group='/'):
- """Acquire data for calibrating a cantilever in one function.
-
- Inputs:
- afm a pyafm.AFM instance
- calibration_config a .config._CalibrationConfig instance
+from numpy import zeros as _zeros
+from numpy import float as _float
- Outputs (all are arrays of recorded data):
- bumps measured (V_photodiode / nm_tip) proportionality constant
- Ts measured temperature (K)
- vibs measured V_photodiode variance (Volts**2) in free solution
+import h5py as _h5py
+from pyafm.afm import AFM as _AFM
+from h5config.storage.hdf5 import HDF5_Storage as _HDF5_Storage
- The temperatures are collected after moving far from the surface
- but before and vibrations are measured to give everything time to
- settle after the big move.
- """
+from . import LOG as _LOG
+from .config import CalibrateConfig as _CalibrateConfig
+from .bump import run as _bump
+from .bump_analyze import load as _bump_load
+from .temperature import run as _temperature
+from .temperature_analyze import load as _temperature_load
+from .vibration import run as _vibration
+from .vibration_analyze import load as _vibration_load
+from .analyze import analyze as _analyze
+from .util import SaveSpec as _SaveSpec
+from .util import save as _save
+from .util import load as _load
+
+
+def load(filename=None, group='/'):
+ config = _CalibrateConfig(storage=_HDF5_Storage(
+ filename=filename, group=group))
+ config.load()
+ return Calibrator(config=config)
+
+def load_all(filename=None, group='/', raw=True):
+ "Load all data from a `Calibration.calibrate()` run."
assert group.endswith('/'), group
+ calibrator = load(
+ filename=filename, group='{}config/'.format(group))
+ data = calibrator.load_results(
+ filename=filename, group='{}calibration/'.format(group))
+ if raw:
+ raw_data = calibrator.load_raw(filename=filename, group=group)
+ else:
+ raw_data = None
+ return (calibrator, data, raw_data)
+
- bumps = _zeros((calibration_config['num-bumps'],), dtype=_float)
- for i in range(calibration_config['num-bumps']):
- _LOG.info('acquire bump %d of %d' % (i, calibration_config['num-bumps']))
- bumps[i] = _bump(afm=afm, bump_config=calibration_config['bump'],
- filename=filename, group='%sbump/%d/' % (group, i))
- _LOG.debug('bumps: %s' % bumps)
-
- move_far_from_surface(
- afm.stepper, distance=calibration_config['vibration-spacing'])
-
- Ts = _zeros((calibration_config['num-temperatures'],), dtype=_float)
- for i in range(calibration_config['num-temperatures']):
- _LOG.info('acquire T %d of %d'
- % (i, calibration_config['num-temperatures']))
- Ts[i] = _T(
- get_T=afm.get_temperature,
- temperature_config=calibration_config['temperature'],
- filename=filename, group='%stemperature/%d/' % (group, i))
- _sleep(calibration_config['temperature-sleep'])
- _LOG.debug('temperatures: %s' % Ts)
-
- # get vibs
- vibs = _zeros((calibration_config['num-vibrations'],), dtype=_float)
- for i in range(calibration_config['num-vibrations']):
- vibs[i] = _vib(
- piezo=afm.piezo, vibration_config=calibration_config['vibration'],
- filename=filename, group='%svibration/%d/' % (group, i))
- _LOG.debug('vibrations: %s' % vibs)
-
- return (bumps, Ts, vibs)
-
-def calib(afm, calibration_config, filename=None, group='/'):
- """Calibrate a cantilever in one function.
-
- Inputs:
- (see `calib_acquire()`)
-
- Outputs:
- k cantilever spring constant (in N/m, or equivalently nN/nm)
- k_s standard deviation in our estimate of k
+class Calibrator (object):
+ """Calibrate a cantilever spring constant using the thermal tune method.
>>> import os
>>> from pprint import pprint
>>> import tempfile
>>> from h5config.storage.hdf5 import pprint_HDF5
- >>> 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 ChannelConfig, AxisConfig
- >>> from stepper import Stepper
- >>> from pyafm.afm import AFM
- >>> from .config import (CalibrationConfig, BumpConfig,
+ >>> from pyafm.storage import load_afm
+ >>> from .config import (CalibrateConfig, BumpConfig,
... TemperatureConfig, VibrationConfig)
- >>> from .analyze import calib_load_all
>>> 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 = AxisConfig()
- >>> axis_config.update(
- ... {'gain':20, 'sensitivity':8e-9, 'minimum':-9})
- >>> axis_channel_config = ChannelConfig()
- >>> axis_channel_config['name'] = 'z'
- >>> axis_config['channel'] = axis_channel_config
- >>> input_channel_config = ChannelConfig()
- >>> input_channel_config['name'] = 'deflection'
-
- >>> a = PiezoAxis(config=axis_config, axis_channel=axis_channel)
- >>> a.setup_config()
-
- >>> c = InputChannel(config=input_channel_config, channel=input_channel)
+ >>> devices = []
+
+ >>> afm = load_afm()
+ >>> afm.load_from_config(devices=devices)
+ >>> if afm.piezo is None:
+ ... raise NotImplementedError('save a better default AFM!')
+ >>> config = CalibrateConfig()
+ >>> config['bump'] = BumpConfig()
+ >>> config['temperature'] = TemperatureConfig()
+ >>> config['vibration'] = VibrationConfig()
+ >>> c = Calibrator(config=config, afm=afm)
>>> c.setup_config()
-
- >>> piezo = AFMPiezo(axes=[a], inputs=[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)
-
- Test calibration:
-
- >>> calibration_config = CalibrationConfig()
- >>> calibration_config['bump'] = BumpConfig()
- >>> calibration_config['temperature'] = TemperatureConfig()
- >>> calibration_config['vibration'] = VibrationConfig()
- >>> calib(afm, calibration_config, filename=filename, group='/')
- TODO: replace skipped example data with real-world values
+ >>> k,k_s,data = c.calibrate(filename=filename)
+ >>> k # doctest: +SKIP
+ 0.058402262154840491
+ >>> k_s # doctest: +SKIP
+ 0.0010609833397949553
+ >>> pprint(data) # doctest: +ELLIPSIS, +REPORT_UDIFF
+ {'bump': array([...]),
+ 'temperature': array([...]),
+ 'vibration': array([...])}
>>> pprint_HDF5(filename) # doctest: +ELLIPSIS, +REPORT_UDIFF
/
/bump
/bump/0/config/bump
<HDF5 dataset "far-steps": shape (), type "<i4">
200
+ <HDF5 dataset "initial-position": shape (), type "<f8">
+ -5e-08
...
- /bump/0/config/deflection
- /bump/0/config/deflection/channel
- <HDF5 dataset "channel": shape (), type "<i4">
- 0
- ...
- /bump/0/config/z
- /bump/0/config/z/axis
- /bump/0/config/z/axis/channel
- <HDF5 dataset "channel": shape (), type "<i4">
- 0
- ...
- <HDF5 dataset "gain": shape (), type "<i4">
- 20
- ...
- <HDF5 dataset "processed": shape (), type "<f8">
- ...
+ /bump/0/processed
+ <HDF5 dataset "data": shape (), type "<f8">
+ ...
+ <HDF5 dataset "units": shape (), type "|S3">
+ V/m
/bump/0/raw
- <HDF5 dataset "deflection": shape (2048,), type "<u2">
- [...]
- <HDF5 dataset "z": shape (2048,), type "<u2">
- [...]
+ /bump/0/raw/deflection
+ <HDF5 dataset "data": shape (2048,), type "<u2">
+ [...]
+ <HDF5 dataset "units": shape (), type "|S4">
+ bits
+ /bump/0/raw/z
+ <HDF5 dataset "data": shape (2048,), type "<u2">
+ [...]
+ <HDF5 dataset "units": shape (), type "|S4">
+ bits
/bump/1
...
- /calibration
- /calibration/config
- /calibration/config/bump
- <HDF5 dataset "far-steps": shape (), type "<i4">
- 200
- ...
- <HDF5 dataset "num-bumps": shape (), type "<i4">
- 10
- ...
- /calibration/processed
- /calibration/processed/spring-constant
+ /config
+ /config/afm
+ <HDF5 dataset "fallback-temperature": shape (), type "<f8">
+ 295.15
+ <HDF5 dataset "far": shape (), type "<f8">
+ 3e-05
+ <HDF5 dataset "main-axis": shape (), type "|S1">
+ z
+ <HDF5 dataset "name": shape (), type "|S5">
+ 1B3D9
+ /config/afm/piezo
+ /config/afm/piezo/axes
+ /config/afm/piezo/axes/0
+ /config/afm/piezo/axes/0/channel
+ <HDF5 dataset "analog-reference": shape (), type "|S6">
+ ground
+ <HDF5 dataset "channel": shape (), type "<i4">
+ 0
+ <HDF5 dataset "conversion-coefficients": shape (2,), type "<f8">
+ [ -1.00000000e+01 3.05180438e-04]
+ <HDF5 dataset "conversion-origin": shape (), type "<f8">
+ 0.0
+ <HDF5 dataset "device": shape (), type "|S12">
+ /dev/comedi0
+ <HDF5 dataset "inverse-conversion-coefficients": shape (2,), type "<f8">
+ [ 0. 3276.75]
+ <HDF5 dataset "inverse-conversion-origin": shape (), type "<f8">
+ -10.0
+ <HDF5 dataset "maxdata": shape (), type "<i8">
+ 65535
+ <HDF5 dataset "name": shape (), type "|S1">
+ z
+ <HDF5 dataset "range": shape (), type "<i4">
+ 0
+ <HDF5 dataset "subdevice": shape (), type "<i4">
+ 1
+ <HDF5 dataset "gain": shape (), type "<f8">
+ 20.0
+ <HDF5 dataset "maximum": shape (), type "<f8">
+ 9.0
+ <HDF5 dataset "minimum": shape (), type "<f8">
+ -9.0
+ <HDF5 dataset "monitor": shape (), type "|S1">
+ <BLANKLINE>
+ <HDF5 dataset "sensitivity": shape (), type "<f8">
+ 8.8e-09
+ /config/afm/piezo/axes/1
+ /config/afm/piezo/axes/1/channel
+ <HDF5 dataset "analog-reference": shape (), type "|S6">
+ ground
+ <HDF5 dataset "channel": shape (), type "<i4">
+ 1
+ <HDF5 dataset "conversion-coefficients": shape (2,), type "<f8">
+ [ -1.00000000e+01 3.05180438e-04]
+ <HDF5 dataset "conversion-origin": shape (), type "<f8">
+ 0.0
+ <HDF5 dataset "device": shape (), type "|S12">
+ /dev/comedi0
+ <HDF5 dataset "inverse-conversion-coefficients": shape (2,), type "<f8">
+ [ 0. 3276.75]
+ <HDF5 dataset "inverse-conversion-origin": shape (), type "<f8">
+ -10.0
+ <HDF5 dataset "maxdata": shape (), type "<i8">
+ 65535
+ <HDF5 dataset "name": shape (), type "|S1">
+ x
+ <HDF5 dataset "range": shape (), type "<i4">
+ 0
+ <HDF5 dataset "subdevice": shape (), type "<i4">
+ 1
+ <HDF5 dataset "gain": shape (), type "<f8">
+ 20.0
+ <HDF5 dataset "maximum": shape (), type "<f8">
+ 8.0
+ <HDF5 dataset "minimum": shape (), type "<f8">
+ -8.0
+ <HDF5 dataset "monitor": shape (), type "|S1">
+ <BLANKLINE>
+ <HDF5 dataset "sensitivity": shape (), type "<f8">
+ 4.16e-09
+ /config/afm/piezo/inputs
+ /config/afm/piezo/inputs/0
+ <HDF5 dataset "analog-reference": shape (), type "|S4">
+ diff
+ <HDF5 dataset "channel": shape (), type "<i4">
+ 0
+ <HDF5 dataset "conversion-coefficients": shape (2,), type "<f8">
+ [ -1.00000000e+01 3.05180438e-04]
+ <HDF5 dataset "conversion-origin": shape (), type "<f8">
+ 0.0
+ <HDF5 dataset "device": shape (), type "|S12">
+ /dev/comedi0
+ <HDF5 dataset "inverse-conversion-coefficients": shape (2,), type "<f8">
+ [ 0. 3276.75]
+ <HDF5 dataset "inverse-conversion-origin": shape (), type "<f8">
+ -10.0
+ <HDF5 dataset "maxdata": shape (), type "<i8">
+ 65535
+ <HDF5 dataset "name": shape (), type "|S10">
+ deflection
+ <HDF5 dataset "range": shape (), type "<i4">
+ 0
+ <HDF5 dataset "subdevice": shape (), type "<i4">
+ 0
+ <HDF5 dataset "name": shape (), type "|S5">
+ 2253E
+ /config/afm/stepper
+ <HDF5 dataset "backlash": shape (), type "<i4">
+ 100
+ <HDF5 dataset "delay": shape (), type "<f8">
+ 0.01
+ <HDF5 dataset "full-step": shape (), type "|b1">
+ True
+ <HDF5 dataset "logic": shape (), type "|b1">
+ True
+ <HDF5 dataset "name": shape (), type "|S9">
+ z-stepper
+ /config/afm/stepper/port
+ <HDF5 dataset "channels": shape (4,), type "<i4">
+ [0 1 2 3]
+ <HDF5 dataset "device": shape (), type "|S12">
+ /dev/comedi0
+ <HDF5 dataset "direction": shape (), type "|S6">
+ output
+ <HDF5 dataset "name": shape (), type "|S12">
+ stepper DB-9
+ <HDF5 dataset "subdevice": shape (), type "<i4">
+ 2
+ <HDF5 dataset "subdevice-type": shape (), type "|S3">
+ dio
+ <HDF5 dataset "step-size": shape (), type "<f8">
+ 1.7e-07
+ /config/afm/temperature
+ <HDF5 dataset "baudrate": shape (), type "<i4">
+ 9600
+ <HDF5 dataset "controller": shape (), type "<i4">
+ 1
+ <HDF5 dataset "device": shape (), type "|S10">
+ /dev/ttyS0
+ <HDF5 dataset "max-current": shape (), type "<f8">
+ 0.0
+ <HDF5 dataset "name": shape (), type "|S14">
+ room (ambient)
+ <HDF5 dataset "units": shape (), type "|S7">
+ Celsius
+ /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
+ <HDF5 dataset "num-bumps": shape (), type "<i4">
+ 10
+ <HDF5 dataset "num-temperatures": shape (), type "<i4">
+ 10
+ <HDF5 dataset "num-vibrations": shape (), type "<i4">
+ 20
+ /config/temperature
+ <HDF5 dataset "sleep": shape (), type "<i4">
+ 1
+ /config/vibration
+ <HDF5 dataset "chunk-size": shape (), type "<i4">
+ 2048
+ <HDF5 dataset "frequency": shape (), type "<f8">
+ 50000.0
+ <HDF5 dataset "maximum-fit-frequency": shape (), type "<f8">
+ 25000.0
+ <HDF5 dataset "minimum-fit-frequency": shape (), type "<f8">
+ 500.0
+ <HDF5 dataset "model": shape (), type "|S12">
+ Breit-Wigner
+ <HDF5 dataset "overlap": shape (), type "|b1">
+ False
+ <HDF5 dataset "sample-time": shape (), type "<i4">
+ 1
+ <HDF5 dataset "window": shape (), type "|S4">
+ Hann
+ <HDF5 dataset "vibration-spacing": shape (), type "<f8">
+ 5e-05
+ /temperature
+ /temperature/0
+ /temperature/0/config
+ /temperature/0/config/temperature
+ <HDF5 dataset "sleep": shape (), type "<i4">
+ 1
+ /temperature/0/processed
<HDF5 dataset "data": shape (), type "<f8">
...
- <HDF5 dataset "standard-deviation": shape (), type "<f8">
+ <HDF5 dataset "units": shape (), type "|S1">
+ K
+ /temperature/0/raw
+ <HDF5 dataset "data": shape (), type "<f8">
...
- <HDF5 dataset "units": shape (), type "|S3">
- N/m
- /calibration/raw
- /calibration/raw/photodiode-sensitivity
- <HDF5 dataset "data": shape (10,), type "<f8">
- [...]
- <HDF5 dataset "units": shape (), type "|S3">
- V/m
- /calibration/raw/temperature
- <HDF5 dataset "data": shape (10,), type "<f8">
- [...]
<HDF5 dataset "units": shape (), type "|S1">
K
- /calibration/raw/thermal-vibration-variance
- <HDF5 dataset "data": shape (20,), type "<f8">
- [...]
- <HDF5 dataset "units": shape (), type "|S3">
- V^2
- /temperature
- /temperature/0
- /temperature/0/config
- <HDF5 dataset "default": shape (), type "|b1">
- False
- <HDF5 dataset "units": shape (), type "|S7">
- Celsius
- <HDF5 dataset "processed": shape (), type "<f8">
- 295.15
- <HDF5 dataset "raw": shape (), type "<i4">
- 22
/temperature/1
...
/vibration
/vibration/0
/vibration/0/config
/vibration/0/config/deflection
- <HDF5 dataset "channel": shape (), type "<i4">
- 0
...
/vibration/0/config/vibration
<HDF5 dataset "chunk-size": shape (), type "<i4">
2048
+ <HDF5 dataset "frequency": shape (), type "<f8">
+ 50000.0
...
- <HDF5 dataset "processed": shape (), type "<f8">
- ...
+ /vibration/0/processed
+ <HDF5 dataset "data": shape (), type "<f8">
+ ...
+ <HDF5 dataset "units": shape (), type "|S6">
+ V^2/Hz
/vibration/0/raw
- <HDF5 dataset "deflection": shape (65536,), type "<u2">
+ <HDF5 dataset "data": shape (65536,), type "<u2">
[...]
- /vibration/1
+ <HDF5 dataset "units": shape (), type "|S4">
+ bits
...
- /vibration/19
- ...
- /vibration/19/raw
- <HDF5 dataset "deflection": shape (65536,), type "<u2">
- [...]
- >>> everything = calib_load_all(filename, '/')
- >>> pprint(everything) # doctest: +ELLIPSIS, +REPORT_UDIFF
- {'bump_details': [{'bump_config': <BumpConfig ...>,
- 'deflection_channel_config': <ChannelConfig ...>,
- 'processed_bump': ...,
- 'raw_bump': {'deflection': array([...], dtype=uint16),
- 'z': array([...], dtype=uint16)},
- 'z_axis_config': <AxisConfig ...>},
- ...],
- 'bumps': array([...]),
- 'calibration_config': <CalibrationConfig ...>,
- 'k': ...,
- 'k_s': ...,
- 'temperature_details': [{'processed_temperature': ...,
- 'raw_temperature': array(22),
- 'temperature_config': <TemperatureConfig ...>},
- ...],
- 'temperatures': array([...]),
- 'vibration_details': [{'deflection_channel_config': <ChannelConfig ...>,
- 'processed_vibration': ...,
- 'raw_vibration': array([...], dtype=uint16),
- 'vibration_config': <VibrationConfig ...>},
- ...],
- 'vibrations': array([...])}
-
- Close the Comedi device.
-
- >>> d.close()
+
+ >>> calibrator,data,raw_data = load_all(filename=filename)
+ >>> calibrator.load_from_config(devices=devices)
+ >>> print(calibrator.config.dump()) # doctest: +ELLIPSIS, +REPORT_UDIFF
+ afm:
+ name: 1B3D9
+ main-axis: z
+ piezo:
+ name: 2253E
+ ...
+ >>> pprint(data) # doctest: +ELLIPSIS, +REPORT_UDIFF
+ {'processed': {'spring_constant': ...
+ 'spring_constant_deviation': ...},
+ 'raw': {'bump': array([...]),
+ 'temperature': array([...]),
+ 'vibration': array([...])}}
+
+ >>> pprint(raw_data) # doctest: +ELLIPSIS, +REPORT_UDIFF
+ {'bump': [{'config': {'bump': <BumpConfig ...>},
+ 'processed': ...,
+ 'raw': {'deflection': array([...], dtype=uint16),
+ 'z': array([...], dtype=uint16)}},
+ {...},
+ ...],
+ 'temperature': [{'config': {'temperature': <TemperatureConfig ...>},
+ 'processed': ...,
+ 'raw': ...},
+ {...},
+ ...],
+ 'vibration': [{'config': {'vibration': <InputChannelConfig ...>},
+ 'processed': ...
+ 'raw': array([...], dtype=uint16)},
+ {...},
+ ...]}
+
+ Close the Comedi devices.
+
+ >>> for device in devices:
+ ... device.close()
Cleanup our temporary config file.
- os.remove(filename)
+ >>> os.remove(filename)
"""
- bumps, Ts, vibs = calib_acquire(
- afm, calibration_config, filename=filename, group=group)
- # TODO: convert vib units?
- k,k_s = _calib_analyze(bumps, Ts, vibs)
- _calib_save(filename, group=group+'calibration/', bumps=bumps,
- temperatures=Ts, vibrations=vibs,
- calibration_config=calibration_config, k=k, k_s=k_s)
- return (k, k_s)
+ def __init__(self, config, afm=None):
+ self.config = config
+ self.afm = afm
+
+ def load_from_config(self, devices):
+ if self.afm is None:
+ self.afm = _AFM(config=self.config['afm'])
+ self.afm.load_from_config(devices=devices)
+
+ def setup_config(self):
+ if self.afm:
+ self.afm.setup_config()
+ self.config['afm'] = self.afm.config
+
+ def calibrate(self, filename=None, group='/'):
+ """Main calibration method.
+
+ Outputs:
+ k cantilever spring constant (in N/m, or equivalently nN/nm)
+ k_s standard deviation in our estimate of k
+ data the data used to determine k
+ """
+ data = self.acquire(filename=filename, group=group)
+ k = k_s = bumps = temperatures = vibrations = None
+ bumps = data.get('bump', None)
+ temperatures = data.get('temperature', None)
+ vibrations = data.get('vibration', None)
+ if None not in [bumps, temperatures, vibrations]:
+ k,k_s = _analyze(
+ bumps=bumps, temperatures=temperatures, vibrations=vibrations)
+ if filename is not None:
+ self.save_results(
+ filename=filename, group='{}calibration/'.format(group),
+ spring_constant=k, spring_constant_deviation=k_s, **data)
+ return (k, k_s, data)
+
+ def acquire(self, filename=None, group='/'):
+ """Acquire data for calibrating a cantilever in one function.
+
+ Outputs a dict of `action` -> `data_array` pairs, for each
+ action (bump, temperature, vibration) that is actually
+ configured. For example, if you wanted to skip the surface
+ approach, bumping, and retraction, you could just set
+ `.config['bump']` to `None`.
+
+ The temperatures are collected after moving far from the
+ surface but before and vibrations are measured to give
+ everything time to settle after the big move.
+
+ Because theres a fair amount of data coming in during a
+ calibration, we save the data as it comes in. So the
+ procedure is bump-0, save-bump-0, bump-1, save-bump-0, etc.
+ To disable the saving, just set `filename` to `None`.
+ """
+ if filename is not None:
+ assert group.endswith('/'), group
+ self.save(filename=filename, group='{}config/'.format(group))
+ data = {}
+ if self.config['bump'] and self.config['num-bumps'] > 0:
+ data['bump'] = _zeros((self.config['num-bumps'],), dtype=_float)
+ for i in range(self.config['num-bumps']):
+ _LOG.info('acquire bump {} of {}'.format(
+ i, self.config['num-bumps']))
+ data['bump'][i] = _bump(
+ afm=self.afm, config=self.config['bump'],
+ filename=filename, group='{}bump/{}/'.format(group, i))
+ _LOG.debug('bumps: {}'.format(data['bump']))
+ self.afm.move_away_from_surface(
+ distance=self.config['vibration-spacing'])
+ if self.config['temperature'] and self.config['num-temperatures'] > 0:
+ data['temperature'] = _zeros(
+ (self.config['num-temperatures'],), dtype=_float)
+ for i in range(self.config['num-temperatures']):
+ _LOG.info('acquire temperature {} of {}'.format(
+ i, self.config['num-temperatures']))
+ data['temperature'][i] = _temperature(
+ get=self.afm.get_temperature,
+ config=self.config['temperature'],
+ filename=filename,
+ group='{}temperature/{}/'.format(group, i))
+ _sleep(self.config['temperature']['sleep'])
+ _LOG.debug('temperatures: {}'.format(data['temperature']))
+ if self.config['vibration'] and self.config['num-vibrations'] > 0:
+ data['vibration'] = _zeros(
+ (self.config['num-vibrations'],), dtype=_float)
+ for i in range(self.config['num-vibrations']):
+ data['vibration'][i] = _vibration(
+ piezo=self.afm.piezo, config=self.config['vibration'],
+ filename=filename,
+ group='{}vibration/{}/'.format(group, i))
+ _LOG.debug('vibrations: {}'.format(data['vibration']))
+ return data
+
+ def save(self, filename=None, group='/'):
+ storage = _HDF5_Storage(filename=filename, group=group)
+ storage.save(config=self.config)
+
+ @staticmethod
+ def save_results(filename=None, group='/', bump=None,
+ temperature=None, vibration=None, spring_constant=None,
+ spring_constant_deviation=None):
+ specs = [
+ _SaveSpec(item=bump, relpath='raw/photodiode-sensitivity',
+ array=True, units='V/m'),
+ _SaveSpec(item=temperature, relpath='raw/temperature',
+ array=True, units='K'),
+ _SaveSpec(item=vibration, relpath='raw/vibration',
+ array=True, units='V^2/Hz'),
+ _SaveSpec(item=spring_constant, relpath='processed/spring-constant',
+ units='N/m', deviation=spring_constant_deviation),
+ ]
+ _save(filename=filename, group=group, specs=specs)
+
+ @staticmethod
+ def load_results(filename, group='/'):
+ """Load results saved with `.save_results()`."""
+ specs = [
+ _SaveSpec(key=('raw', 'bump'),
+ relpath='raw/photodiode-sensitivity',
+ array=True, units='V/m'),
+ _SaveSpec(key=('raw', 'temperature'), relpath='raw/temperature',
+ array=True, units='K'),
+ _SaveSpec(key=('raw', 'vibration'),
+ relpath='raw/vibration',
+ array=True, units='V^2/Hz'),
+ _SaveSpec(key=('processed', 'spring_constant'),
+ relpath='processed/spring-constant',
+ units='N/m', deviation='spring_constant_deviation'),
+ ]
+ return _load(filename=filename, group=group, specs=specs)
+
+ def load_raw(self, filename=None, group='/'):
+ """Load results saved during `.aquire()` by bumps, etc."""
+ data = {}
+ with _h5py.File(filename, 'r') as f:
+ for name,loader in [('bump',_bump_load),
+ ('temperature', _temperature_load),
+ ('vibration', _vibration_load),
+ ]:
+ n = self.config['num-{}s'.format(name)]
+ if n > 0:
+ data[name] = []
+ for i in range(n):
+ try:
+ cwg = f['{}{}/{}/'.format(group, name, i)]
+ except KeyError:
+ pass
+ else:
+ data[name].append(loader(group=cwg))
+ return data