1 # calibcant - tools for thermally calibrating AFM cantilevers
3 # Copyright (C) 2011-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 thermal vibration data.
21 For measuring the cantilever's spring constant.
23 The relevent physical quantity is:
24 Vphoto The photodiode vertical deflection voltage (what we measure)
27 from numpy import zeros as _zeros
28 from FFT_tools import ceil_pow_of_two as _ceil_pow_of_two
29 from pycomedi.constant import TRIG_SRC as _TRIG_SRC
30 from pycomedi.utility import inttrig_insn as _inttrig_insn
31 from pycomedi.utility import Reader as _Reader
33 from . import LOG as _LOG
34 from .vibration_analyze import analyze as _analyze
35 from .vibration_analyze import save as _save
38 def acquire(piezo, config):
39 """Record thermal vibration data for `piezo` at its current position.
42 piezo a pypiezo.afm.AFMPiezo instance
43 config a .config.Config instance
45 _LOG.debug('prepare vibration aquisition command')
47 # round up to the nearest power of two, for efficient FFT-ing
48 n_samps = _ceil_pow_of_two(
49 config['sample-time']*config['frequency'])
50 time = n_samps / config['frequency']
51 scan_period_ns = int(1e9 / config['frequency'])
53 input_channel = piezo.input_channel_by_name('deflection')
54 channel = input_channel.channel
58 dtype = piezo.deflection_dtype()
59 data = _zeros((n_samps, len(channels)), dtype=dtype)
61 cmd = channel.subdevice.get_cmd_generic_timed(
62 len(channels), scan_period_ns)
63 cmd.start_src = _TRIG_SRC.int
65 cmd.stop_src = _TRIG_SRC.count
66 cmd.stop_arg = n_samps
67 cmd.chanlist = channels
68 channel.subdevice.cmd = cmd
70 rc = channel.subdevice.command_test()
72 _LOG.debug('command test %d: %s' % (i,rc))
74 _LOG.info('get %g seconds of vibration data at %g Hz'
75 % (config['sample-time'],
77 channel.subdevice.command()
78 reader = _Reader(channel.subdevice, data)
80 channel.subdevice.device.do_insn(_inttrig_insn(channel.subdevice))
82 data = data.reshape((data.size,))
85 def run(piezo, config, filename, group='/'):
86 """Wrapper around acquire(), analyze(), save().
90 >>> from h5config.storage.hdf5 import pprint_HDF5
91 >>> from pyafm.storage import load_afm
92 >>> from .config import VibrationConfig
94 >>> fd,filename = tempfile.mkstemp(suffix='.h5', prefix='calibcant-')
98 >>> afm = load_afm(devices=devices)
99 >>> afm.load_from_config()
103 >>> config = VibrationConfig()
104 >>> output = run(piezo=afm.piezo, config=config, filename=filename,
105 ... group='/vibration')
106 >>> output # doctest: +SKIP
107 4.1589771694838657e-05
108 >>> pprint_HDF5(filename) # doctest: +ELLIPSIS, +REPORT_UDIFF
112 /vibration/config/deflection
113 <HDF5 dataset "analog-reference": shape (), type "|S4">
115 <HDF5 dataset "channel": shape (), type "<i...">
117 <HDF5 dataset "conversion-coefficients": shape (2,), type "<f8">
118 [ -1.00000000e+01 3.05180438e-04]
119 <HDF5 dataset "conversion-origin": shape (), type "<f8">
121 <HDF5 dataset "device": shape (), type "|S12">
123 <HDF5 dataset "inverse-conversion-coefficients": shape (2,), type "<f8">
125 <HDF5 dataset "inverse-conversion-origin": shape (), type "<f8">
127 <HDF5 dataset "maxdata": shape (), type "<i...">
129 <HDF5 dataset "name": shape (), type "|S10">
131 <HDF5 dataset "range": shape (), type "<i...">
133 <HDF5 dataset "subdevice": shape (), type "<i...">
135 /vibration/config/vibration
136 <HDF5 dataset "chunk-size": shape (), type "<i...">
138 <HDF5 dataset "frequency": shape (), type "<f8">
140 <HDF5 dataset "maximum-fit-frequency": shape (), type "<f8">
142 <HDF5 dataset "minimum-fit-frequency": shape (), type "<f8">
144 <HDF5 dataset "model": shape (), type "|S12">
146 <HDF5 dataset "overlap": shape (), type "|b1">
148 <HDF5 dataset "sample-time": shape (), type "<i...">
150 <HDF5 dataset "window": shape (), type "|S4">
153 <HDF5 dataset "data": shape (), type "<f8">
155 <HDF5 dataset "units": shape (), type "|S6">
158 <HDF5 dataset "data": shape (65536,), type "<u2">
160 <HDF5 dataset "units": shape (), type "|S4">
163 Close the Comedi devices.
165 >>> for device in devices:
168 Cleanup our temporary config file.
170 >>> os.remove(filename)
172 deflection_input_channel = piezo.input_channel_by_name('deflection')
174 deflection_channel_config = deflection_input_channel.config
176 deflection = acquire(piezo, config)
178 deflection, config, deflection_channel_config)
180 filename=filename, group=group, raw=deflection, config=config,
181 deflection_channel_config=deflection_channel_config,