Get calibcant working with the new load_from_config-based pyafm.
[calibcant.git] / calibcant / vibration.py
1 # calibcant - tools for thermally calibrating AFM cantilevers
2 #
3 # Copyright (C) 2011-2012 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 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
10 # version.
11 #
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.
15 #
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/>.
18
19 """Acquire, save, and load cantilever thermal vibration data.
20
21 For measuring the cantilever's spring constant.
22
23 The relevent physical quantity is:
24   Vphoto   The photodiode vertical deflection voltage (what we measure)
25 """
26
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
32
33 from . import LOG as _LOG
34 from .vibration_analyze import analyze as _analyze
35 from .vibration_analyze import save as _save
36
37
38 def acquire(piezo, config):
39     """Record thermal vibration data for `piezo` at its current position.
40
41     Inputs:
42       piezo             a pypiezo.afm.AFMPiezo instance
43       config  a .config.Config instance
44     """
45     _LOG.debug('prepare vibration aquisition command')
46
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'])
52
53     input_channel = piezo.input_channel_by_name('deflection')
54     channel = input_channel.channel
55
56     channels = [channel]
57
58     dtype = piezo.deflection_dtype()
59     data = _zeros((n_samps, len(channels)), dtype=dtype)
60
61     cmd = channel.subdevice.get_cmd_generic_timed(
62         len(channels), scan_period_ns)
63     cmd.start_src = _TRIG_SRC.int
64     cmd.start_arg = 0
65     cmd.stop_src = _TRIG_SRC.count
66     cmd.stop_arg = n_samps
67     cmd.chanlist = channels
68     channel.subdevice.cmd = cmd
69     for i in range(3):
70         rc = channel.subdevice.command_test()
71         if rc == None: break
72         _LOG.debug('command test %d: %s' % (i,rc))
73
74     _LOG.info('get %g seconds of vibration data at %g Hz'
75               % (config['sample-time'],
76                  config['frequency']))
77     channel.subdevice.command()
78     reader = _Reader(channel.subdevice, data)
79     reader.start()
80     channel.subdevice.device.do_insn(_inttrig_insn(channel.subdevice))
81     reader.join()
82     data = data.reshape((data.size,))
83     return data
84
85 def run(piezo, config, filename, group='/'):
86     """Wrapper around acquire(), analyze(), save().
87
88     >>> import os
89     >>> import tempfile
90     >>> from h5config.storage.hdf5 import pprint_HDF5
91     >>> from pyafm.storage import load_afm
92     >>> from .config import VibrationConfig
93
94     >>> fd,filename = tempfile.mkstemp(suffix='.h5', prefix='calibcant-')
95     >>> os.close(fd)
96
97     >>> devices = []
98     >>> afm = load_afm(devices=devices)
99     >>> afm.load_from_config()
100
101     Test a vibration:
102
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
109     /
110       /vibration
111         /vibration/config
112           /vibration/config/deflection
113             <HDF5 dataset "analog-reference": shape (), type "|S4">
114               diff
115             <HDF5 dataset "channel": shape (), type "<i...">
116               0
117             <HDF5 dataset "conversion-coefficients": shape (2,), type "<f8">
118               [ -1.00000000e+01   3.05180438e-04]
119             <HDF5 dataset "conversion-origin": shape (), type "<f8">
120               0.0
121             <HDF5 dataset "device": shape (), type "|S12">
122               /dev/comedi0
123             <HDF5 dataset "inverse-conversion-coefficients": shape (2,), type "<f8">
124               [    0.    3276.75]
125             <HDF5 dataset "inverse-conversion-origin": shape (), type "<f8">
126               -10.0
127             <HDF5 dataset "maxdata": shape (), type "<i...">
128               65535
129             <HDF5 dataset "name": shape (), type "|S10">
130               deflection
131             <HDF5 dataset "range": shape (), type "<i...">
132               0
133             <HDF5 dataset "subdevice": shape (), type "<i...">
134               0
135           /vibration/config/vibration
136             <HDF5 dataset "chunk-size": shape (), type "<i...">
137               2048
138             <HDF5 dataset "frequency": shape (), type "<f8">
139               50000.0
140             <HDF5 dataset "maximum-fit-frequency": shape (), type "<f8">
141               25000.0
142             <HDF5 dataset "minimum-fit-frequency": shape (), type "<f8">
143               500.0
144             <HDF5 dataset "model": shape (), type "|S12">
145               Breit-Wigner
146             <HDF5 dataset "overlap": shape (), type "|b1">
147               False
148             <HDF5 dataset "sample-time": shape (), type "<i...">
149               1
150             <HDF5 dataset "window": shape (), type "|S4">
151               Hann
152         /vibration/processed
153           <HDF5 dataset "data": shape (), type "<f8">
154             ...
155           <HDF5 dataset "units": shape (), type "|S6">
156             V^2/Hz
157         /vibration/raw
158           <HDF5 dataset "data": shape (65536,), type "<u2">
159             [...]
160           <HDF5 dataset "units": shape (), type "|S4">
161             bits
162
163     Close the Comedi devices.
164
165     >>> for device in devices:
166     ...     device.close()
167
168     Cleanup our temporary config file.
169
170     >>> os.remove(filename)
171     """
172     deflection_input_channel = piezo.input_channel_by_name('deflection')
173     
174     deflection_channel_config = deflection_input_channel.config
175
176     deflection = acquire(piezo, config)
177     variance = _analyze(
178         deflection, config, deflection_channel_config)
179     _save(
180         filename=filename, group=group, raw=deflection, config=config,
181         deflection_channel_config=deflection_channel_config,
182         processed=variance)
183     return variance