1 # calibcant - tools for thermally calibrating AFM cantilevers
3 # Copyright (C) 2008-2011 W. Trevor King <wking@drexel.edu>
5 # This file is part of calibcant.
7 # calibcant is free software: you can redistribute it and/or
8 # modify it under the terms of the GNU Lesser General Public
9 # License as published by the Free Software Foundation, either
10 # version 3 of the License, or (at your option) any later version.
12 # calibcant is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Lesser General Public License for more details.
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with calibcant. If not, see
19 # <http://www.gnu.org/licenses/>.
21 """Acquire, save, and load cantilever thermal vibration data.
23 For measuring the cantilever's spring constant.
25 The relevent physical quantity is:
26 Vphoto The photodiode vertical deflection voltage (what we measure)
29 from numpy import zeros as _zeros
30 from FFT_tools import ceil_pow_of_two as _ceil_pow_of_two
31 from pycomedi.constant import TRIG_SRC as _TRIG_SRC
32 from pycomedi.utility import inttrig_insn as _inttrig_insn
33 from pycomedi.utility import Reader as _Reader
35 from . import LOG as _LOG
36 from .vib_analyze import vib_analyze as _vib_analyze
37 from .vib_analyze import vib_save as _vib_save
40 def vib_acquire(piezo, vibration_config):
41 """Record thermal vibration data for `piezo` at its current position.
44 piezo a pypiezo.afm.AFMPiezo instance
45 vibration_config a .config._VibrationConfig instance
47 _LOG.debug('prepare vibration aquisition command')
49 # round up to the nearest power of two, for efficient FFT-ing
50 n_samps = _ceil_pow_of_two(
51 vibration_config['sample-time']*vibration_config['frequency'])
52 time = n_samps / vibration_config['frequency']
53 scan_period_ns = int(1e9 / vibration_config['frequency'])
55 input_channel = piezo.input_channel_by_name('deflection')
56 channel = input_channel.channel
60 dtype = piezo.deflection_dtype()
61 data = _zeros((n_samps, len(channels)), dtype=dtype)
63 cmd = channel.subdevice.get_cmd_generic_timed(
64 len(channels), scan_period_ns)
65 cmd.start_src = _TRIG_SRC.int
67 cmd.stop_src = _TRIG_SRC.count
68 cmd.stop_arg = n_samps
69 cmd.chanlist = channels
70 channel.subdevice.cmd = cmd
72 rc = channel.subdevice.command_test()
74 _LOG.debug('command test %d: %s' % (i,rc))
76 _LOG.info('get %g seconds of vibration data at %g Hz'
77 % (vibration_config['sample-time'],
78 vibration_config['frequency']))
79 channel.subdevice.command()
80 reader = _Reader(channel.subdevice, data)
82 channel.subdevice.device.do_insn(_inttrig_insn(channel.subdevice))
84 data = data.reshape((data.size,))
87 def vib(piezo, vibration_config, filename, group='/'):
88 """Wrapper around vib_acquire(), vib_analyze(), vib_save().
92 >>> from h5config.storage.hdf5 import pprint_HDF5
93 >>> from pycomedi.device import Device
94 >>> from pycomedi.subdevice import StreamingSubdevice
95 >>> from pycomedi.channel import AnalogChannel
96 >>> from pycomedi.constant import AREF, SUBDEVICE_TYPE, UNIT
97 >>> from pypiezo.afm import AFMPiezo
98 >>> from pypiezo.base import InputChannel
99 >>> from pypiezo.config import ChannelConfig
100 >>> from .config import VibrationConfig
102 Setup an `AFMPiezo` instance.
104 >>> fd,filename = tempfile.mkstemp(suffix='.h5', prefix='calibcant-')
107 >>> d = Device('/dev/comedi0')
110 >>> s_in = d.find_subdevice_by_type(SUBDEVICE_TYPE.ai,
111 ... factory=StreamingSubdevice)
113 >>> channel = s_in.channel(0, factory=AnalogChannel, aref=AREF.diff)
114 >>> channel.range = channel.find_range(
115 ... unit=UNIT.volt, min=-10, max=10)
116 >>> channel_config = ChannelConfig()
117 >>> channel_config['name'] = 'deflection'
119 >>> c = InputChannel(config=channel_config, channel=channel)
122 >>> piezo = AFMPiezo(axes=[], inputs=[c])
126 >>> vibration_config = VibrationConfig()
127 >>> vib(piezo, vibration_config, filename, group='/vibration')
128 TODO: replace skipped example data with real-world values
129 >>> pprint_HDF5(filename) # doctest: +ELLIPSIS, +REPORT_UDIFF
133 /vibration/config/deflection
134 <HDF5 dataset "channel": shape (), type "<i4">
136 <HDF5 dataset "conversion-coefficients": shape (2,), type "<f8">
137 [ -1.00000000e+01 3.05180438e-04]
138 <HDF5 dataset "conversion-origin": shape (), type "<f8">
140 <HDF5 dataset "device": shape (), type "|S12">
142 <HDF5 dataset "inverse-conversion-coefficients": shape (2,), type "<f8">
144 <HDF5 dataset "inverse-conversion-origin": shape (), type "<f8">
146 <HDF5 dataset "maxdata": shape (), type "<i8">
148 <HDF5 dataset "name": shape (), type "|S10">
150 <HDF5 dataset "range": shape (), type "<i4">
152 <HDF5 dataset "subdevice": shape (), type "<i4">
154 /vibration/config/vibration
155 <HDF5 dataset "chunk-size": shape (), type "<i4">
157 <HDF5 dataset "frequency": shape (), type "<f8">
159 <HDF5 dataset "maximum-fit-frequency": shape (), type "<f8">
161 <HDF5 dataset "minimum-fit-frequency": shape (), type "<f8">
163 <HDF5 dataset "model": shape (), type "|S12">
165 <HDF5 dataset "overlap": shape (), type "|b1">
167 <HDF5 dataset "sample-time": shape (), type "<i4">
169 <HDF5 dataset "window": shape (), type "|S4">
171 <HDF5 dataset "processed": shape (), type "<f8">
174 <HDF5 dataset "deflection": shape (65536,), type "<u2">
177 Close the Comedi device.
181 Cleanup our temporary config file.
183 >>> os.remove(filename)
185 deflection_input_channel = piezo.input_channel_by_name('deflection')
187 deflection_channel_config = deflection_input_channel.config
189 deflection = vib_acquire(piezo, vibration_config)
190 variance = _vib_analyze(
191 deflection, vibration_config, deflection_channel_config)
193 filename, group, raw_vibration=deflection,
194 vibration_config=vibration_config,
195 deflection_channel_config=deflection_channel_config,
196 processed_vibration=variance)