Massive rewrite (v 0.6) to base everything on Cython and revamped pypiezo.
[calibcant.git] / calibcant / config.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 """Define some variables to configure the package for a particular lab
22 and workflow."""
23
24 import logging as _logging
25 import os.path as _os_path
26 import sys as _sys
27
28 from FFT_tools import window_hann as _window_hann
29 from pypiezo.config import (
30     _ChoiceSetting, _BooleanSetting, _IntegerSetting, _FloatSetting,
31     _FloatListSetting, _Config, _BackedConfig, _HDF5Config, _YAMLConfig)
32
33 from . import LOG as _LOG
34
35
36 class _BaseConfig (_Config):
37     "Configure `calibcant` module operation"
38     settings = [
39         _ChoiceSetting(
40             name='log-level',
41             help='Module logging level.',
42             default=_logging.WARN,
43             choices=[
44                 ('critical', _logging.CRITICAL),
45                 ('error', _logging.ERROR),
46                 ('warn', _logging.WARN),
47                 ('info', _logging.INFO),
48                 ('debug', _logging.DEBUG),
49                 ]),
50         _BooleanSetting(
51             name='syslog',
52             help='Log to syslog (otherwise log to stderr).',
53             default=False),
54         _BooleanSetting(
55             name='matplotlib',
56             help='Plot piezo motion using `matplotlib`.',
57             default=False),
58         _FloatSetting(
59             name='temperature',
60             help=('Default temperature for thermal calibration in degrees '
61                   'Celsius.'),
62             default=22),
63         ]
64
65 class _TemperatureUnit (object):
66     pass
67 class Celsius (_TemperatureUnit):
68     pass
69 class Kelvin (_TemperatureUnit):
70     pass
71
72 class _TemperatureConfig (_Config):
73     "Configure `calibcant` temperature operation"
74     settings = [
75         _ChoiceSetting(
76             name='units',
77             help='Units of raw temperature measurements.',
78             default=Celsius,
79             choices=[
80                 ('Celsius', Celsius),
81                 ('Kelvin', Kelvin),
82                 ]),
83         _BooleanSetting(
84             name='default',
85             help=('The temperature values are defaults (vs. real '
86                   'measurements).'),
87             default=True),
88         ]
89
90 class _BumpModel (object):
91     pass
92 class Linear (_BumpModel):
93     pass
94 class Quadratic (_BumpModel):
95     pass
96
97 class _BumpConfig (_Config):
98     "Configure `calibcant` bump operation"
99     settings = [
100         _FloatSetting(
101             name='initial-position',
102             help=('Position relative to surface for start of bump in meters.  '
103                   'Should be less than zero to ensure non-contact region '
104                   'before you hit the surface.'),
105             default=-50e-9),
106         _FloatSetting(
107             name='setpoint',
108             help=('Maximum deflection in volts in case of stepper positioning '
109                   'to achieve the initial position.'),
110             default=2.0),
111         _IntegerSetting(
112             name='far-steps',
113             help=('Number of stepper steps to move "far" away from the '
114                   'surface.  For possible stepper adjustments while initially '
115                   'locating the surface.'),
116             default=200),
117         _FloatSetting(
118             name='push-depth',
119             help='Distance to approach in meters.',
120             default=200e-9),
121         _FloatSetting(
122             name='push-speed',
123             help='Approach/retract speed in meters/second.',
124             default=1e-6),
125         _FloatSetting(
126             name='samples',
127             help='Number of samples during approach and during retreat.',
128             default=1024),
129         _ChoiceSetting(
130             name='model',
131             help='Bump deflection model.',
132             default=Quadratic,
133             choices=[
134                 ('linear', Linear),
135                 ('quadratic', Quadratic),
136                 ]),
137         ]
138
139 class _VibrationModel (object):
140     pass
141 class Variance (_VibrationModel):
142     pass
143 class BreitWigner (_VibrationModel):
144     pass
145 class OffsetBreitWigner (_VibrationModel):
146     pass
147
148 class _VibrationConfig (_Config):
149     "Configure `calibcant` vibration operation"
150     settings = [
151         _FloatSetting(
152             name='frequency',
153             help='Sampling frequency in Hz.',
154             default=50e3),
155         _FloatSetting(
156             name='sample-time',
157             help=('Aquisition time in seconds.  This is rounded up as required '
158                   'so the number of samples will be an integer power of two.'),
159             default=1),
160         _ChoiceSetting(
161             name='model',
162             help='Vibration model.',
163             default=BreitWigner,
164             choices=[
165                 ('variance', Variance),
166                 ('Breit-Wigner', BreitWigner),
167                 ('offset Breit-Wigner', OffsetBreitWigner),
168                 ]),
169         _IntegerSetting(
170             name='chunk-size',
171             help='FFT chunk size (for PSD fits).',
172             default=2048),
173         _BooleanSetting(
174             name='overlap',
175             help='Overlap FFT chunks (for PSD fits).'),
176         _ChoiceSetting(
177             name='window',
178             help='FFT chunk window (for PSD fits).',
179             default=_window_hann,
180             choices=[
181                 ('Hann', _window_hann),
182                 ]),
183         _FloatSetting(
184             name='minimum-fit-frequency',
185             help='Lower bound of Lorentzian fitting region.',
186             default=500.),
187         _FloatSetting(
188             name='maximum-fit-frequency',
189             help='Upper bound of Lorentzian fitting region.',
190             default=25e3),
191         ]
192
193
194 class _CalibrationConfig (_Config):
195     "Configure a full `calibcant` calibration run"
196     settings = [
197         _IntegerSetting(
198             name='num-bumps',
199             help='Number of surface bumps.',
200             default=10),
201         _IntegerSetting(
202             name='num-temperatures',
203             help='Number of temperature measurements.',
204             default=10),
205         _IntegerSetting(
206             name='num-vibrations',
207             help='Number of thermal vibration measurements.',
208             default=20),
209         _FloatSetting(
210             name='temperature-sleep',
211             help=('Time between temperature measurements (in seconds) to get '
212                   'independent measurements when reading from slow sensors.'),
213             default=1),
214         _FloatSetting(
215             name='vibration-spacing',
216             help=('Approximate distance from the surface in meters for the '
217                   'vibration measurements.  This should be large enough that '
218                   'surface effects are negligable.'),
219             default=50e-6),
220         ]
221
222
223 # Define HDF5- and YAML-backed subclasses of the basic _Config types.
224 for name,obj in locals().items():
225     if (obj != _Config and
226         type(obj) == type and
227         issubclass(obj, _Config) and
228         not issubclass(obj, _BackedConfig)):
229         for prefix,base in [('HDF5', _HDF5Config), ('YAML', _YAMLConfig)]:
230             _name = '%s%s' % (prefix, name)
231             _bases = (base, obj)
232             _dict = {}
233             _class = type(_name, _bases, _dict)
234             setattr(_sys.modules[__name__], _name, _class)
235
236 del name, obj, prefix, base, _name, _bases, _dict, _class
237
238
239 def find_base_config():
240     "Return the best `_BaseConfig` match after scanning the filesystem"
241     _LOG.info('looking for base_config file')
242     user_basepath = _os_path.join(_os_path.expanduser('~'), '.calibcantrc')
243     system_basepath = _os_path.join('/etc', 'calibcant', 'config')
244     distributed_basepath =  _os_path.join(
245         '/usr', 'share', 'calibcant', 'config')
246     for basepath in [user_basepath, system_basepath, distributed_basepath]:
247         for (extension, config) in [('.h5', HDF5_BaseConfig),
248                                     ('.yaml', YAML_BaseConfig)]:
249             filename = basepath + extension
250             if _os_path.exists(filename):
251                 _LOG.info('base_config file found at %s' % filename)
252                 base_config = config(filename)
253                 base_config.load()
254                 return base_config
255             else:
256                 _LOG.debug('no base_config file at %s' % filename)
257     _LOG.info('new base_config file at %s' % filename)
258     basepath = user_basepath
259     filename = basepath + extension
260     return config(filename)