0c71f079e7510f7ec19a6a810d575c4ed9a2b2dc
[calibcant.git] / calibcant / vib.py
1 # calibcant - tools for thermally calibrating AFM cantilevers
2 #
3 # Copyright (C) 2008-2011 W. Trevor King <wking@drexel.edu>
4 #
5 # This file is part of calibcant.
6 #
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.
11 #
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.
16 #
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/>.
20
21 """Acquire, save, and load cantilever thermal vibration data.
22
23 For measuring the cantilever's spring constant.
24
25 The relevent physical quantity is:
26   Vphoto   The photodiode vertical deflection voltage (what we measure)
27 """
28
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
34
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
38
39
40 def vib_acquire(piezo, vibration_config):
41     """Record thermal vibration data for `piezo` at its current position.
42
43     Inputs:
44       piezo             a pypiezo.afm.AFMPiezo instance
45       vibration_config  a .config._VibrationConfig instance
46     """
47     _LOG.debug('prepare vibration aquisition command')
48
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'])
54
55     input_channel = piezo.input_channel_by_name('deflection')
56     channel = input_channel.channel
57
58     channels = [channel]
59
60     dtype = piezo.deflection_dtype()
61     data = _zeros((n_samps, len(channels)), dtype=dtype)
62
63     cmd = channel.subdevice.get_cmd_generic_timed(
64         len(channels), scan_period_ns)
65     cmd.start_src = _TRIG_SRC.int
66     cmd.start_arg = 0
67     cmd.stop_src = _TRIG_SRC.count
68     cmd.stop_arg = n_samps
69     cmd.chanlist = channels
70     channel.subdevice.cmd = cmd
71     for i in range(3):
72         rc = channel.subdevice.command_test()
73         if rc == None: break
74         _LOG.debug('command test %d: %s' % (i,rc))
75
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)
81     reader.start()
82     channel.subdevice.device.do_insn(_inttrig_insn(channel.subdevice))
83     reader.join()
84     data = data.reshape((data.size,))
85     return data
86
87 def vib(piezo, vibration_config, filename, group='/'):
88     """Wrapper around vib_acquire(), vib_analyze(), vib_save().
89
90     >>> import os
91     >>> import tempfile
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
101
102     Setup an `AFMPiezo` instance.
103
104     >>> fd,filename = tempfile.mkstemp(suffix='.h5', prefix='calibcant-')
105     >>> os.close(fd)
106
107     >>> d = Device('/dev/comedi0')
108     >>> d.open()
109
110     >>> s_in = d.find_subdevice_by_type(SUBDEVICE_TYPE.ai,
111     ...     factory=StreamingSubdevice)
112
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'
118
119     >>> c = InputChannel(config=channel_config, channel=channel)
120     >>> c.setup_config()
121
122     >>> piezo = AFMPiezo(axes=[], inputs=[c])
123
124     Test a vibration:
125
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
130     /
131       /vibration
132         /vibration/config
133           /vibration/config/deflection
134             <HDF5 dataset "channel": shape (), type "<i4">
135               0
136             <HDF5 dataset "conversion-coefficients": shape (2,), type "<f8">
137               [ -1.00000000e+01   3.05180438e-04]
138             <HDF5 dataset "conversion-origin": shape (), type "<f8">
139               0.0
140             <HDF5 dataset "device": shape (), type "|S12">
141               /dev/comedi0
142             <HDF5 dataset "inverse-conversion-coefficients": shape (2,), type "<f8">
143               [    0.    3276.75]
144             <HDF5 dataset "inverse-conversion-origin": shape (), type "<f8">
145               -10.0
146             <HDF5 dataset "maxdata": shape (), type "<i8">
147               65535
148             <HDF5 dataset "name": shape (), type "|S10">
149               deflection
150             <HDF5 dataset "range": shape (), type "<i4">
151               0
152             <HDF5 dataset "subdevice": shape (), type "<i4">
153               0
154           /vibration/config/vibration
155             <HDF5 dataset "chunk-size": shape (), type "<i4">
156               2048
157             <HDF5 dataset "frequency": shape (), type "<f8">
158               50000.0
159             <HDF5 dataset "maximum-fit-frequency": shape (), type "<f8">
160               25000.0
161             <HDF5 dataset "minimum-fit-frequency": shape (), type "<f8">
162               500.0
163             <HDF5 dataset "model": shape (), type "|S12">
164               Breit-Wigner
165             <HDF5 dataset "overlap": shape (), type "|b1">
166               False
167             <HDF5 dataset "sample-time": shape (), type "<i4">
168               1
169             <HDF5 dataset "window": shape (), type "|S4">
170               Hann
171         <HDF5 dataset "processed": shape (), type "<f8">
172           ...
173         /vibration/raw
174           <HDF5 dataset "deflection": shape (65536,), type "<u2">
175             [...]
176
177     Close the Comedi device.
178
179     >>> d.close()
180
181     Cleanup our temporary config file.
182
183     >>> os.remove(filename)
184     """
185     deflection_input_channel = piezo.input_channel_by_name('deflection')
186     
187     deflection_channel_config = deflection_input_channel.config
188
189     deflection = vib_acquire(piezo, vibration_config)
190     variance = _vib_analyze(
191         deflection, vibration_config, deflection_channel_config)
192     _vib_save(
193         filename, group, raw_vibration=deflection,
194         vibration_config=vibration_config,
195         deflection_channel_config=deflection_channel_config,
196         processed_vibration=variance)
197     return variance