9c12a94af2eb6f01a772ddecd85e9f57a9756170
[calibcant.git] / calibcant / bump.py
1 #!/usr/bin/python
2 #
3 # calibcant - tools for thermally calibrating AFM cantilevers
4 #
5 # Copyright (C) 2007,2008, William Trevor King
6 #
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License as
9 # published by the Free Software Foundation; either version 3 of the
10 # License, or (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 # See the GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 # 02111-1307, USA.
21 #
22 # The author may be contacted at <wking@drexel.edu> on the Internet, or
23 # write to Trevor King, Drexel University, Physics Dept., 3141 Chestnut St.,
24 # Philadelphia PA 19104, USA.
25
26 """
27 Aquire, save, and load cantilever calibration bump data.
28 For measuring photodiode sensitivity.
29
30 W. Trevor King Dec. 2007 - Oct. 2008
31
32 The relevent physical quantities are :
33  Vzp_out  Output z-piezo voltage (what we generate)
34  Vzp      Applied z-piezo voltage (after external ZPGAIN)
35  Zp       The z-piezo position
36  Zcant    The cantilever vertical deflection
37  Vphoto   The photodiode vertical deflection voltage (what we measure)
38
39 Which are related by the parameters :
40  zpGain           Vzp_out / Vzp
41  zpSensitivity    Zp / Vzp
42  photoSensitivity Vphoto / Zcant
43
44 Cantilever calibration assumes a pre-calibrated z-piezo (zpSensitivity) and
45 amplifier (zpGain).  In our lab, the z-piezo is calibrated by imaging a
46 calibration sample, which has features with well defined sizes, and the gain
47 is set with a knob on the Nanoscope.
48
49 photoSensitivity is measured by bumping the cantilever against the surface,
50 where Zp = Zcant (see the bump_*() family of functions)
51 The measured slope Vphoto/Vout is converted to photoSensitivity via
52 Vphoto/Vzp_out * Vzp_out/Vzp  * Vzp/Zp   *    Zp/Zcant =    Vphoto/Zcant
53  (measured)      (1/zpGain) (1/zpSensitivity)    (1)  (photoSensitivity)
54
55 We do all these measurements a few times to estimate statistical errors.
56
57 The functions are layed out in the families:
58  bump_*()
59 For each family, * can be any of :
60  aquire       get real-world data
61  save         store real-world data to disk
62  load         get real-world data from disk
63  analyze      interperate the real-world data.
64  plot         show a nice graphic to convince people we're working :p
65  load_analyze_tweaked
66               read a file with a list of paths to previously saved real world data
67               load each file using *_load(), analyze using *_analyze(), and
68               optionally plot using *_plot().
69               Intended for re-processing old data.
70 A family name without any _* extension (e.g. bump()),
71  runs *_aquire(), *_save(), *_analyze(), *_plot().
72 """
73
74 import numpy
75 import time 
76 import data_logger
77 import z_piezo_utils
78 import FFT_tools
79 import linfit
80 from calibcant_bump_analyze import bump_analyze
81
82 LOG_DATA = True  # quietly grab all real-world data and log to LOG_DIR
83 LOG_DIR = '$DEFAULT$/calibrate_cantilever'
84
85 TEXT_VERBOSE = True      # for debugging
86
87
88 # bump family
89
90 def bump_aquire(zpiezo, push_depth, npoints, freq) :
91     """
92     Ramps closer push_depth and returns to the original position.
93     Inputs:
94      zpiezo     an opened zpiezo.zpiezo instance
95      push_depth distance to approach, in nm
96      npoints    number points during the approach and during the retreat
97      freq       rate at which data is aquired
98      log_dir    directory to log data to (see data_logger.py).
99                 None to turn off logging (see also the global LOG_DATA).
100     Returns the aquired ramp data dictionary, with data in DAC/ADC bits.
101     """
102     # generate the bump output
103     start_pos = zpiezo.curPos()
104     pos_dist = zpiezo.pos_nm2out(push_depth) - zpiezo.pos_nm2out(0)
105     close_pos = start_pos + pos_dist
106     appr = linspace(start_pos, close_pos, npoints)
107     retr = linspace(close_pos, start_pos, npoints)
108     out = concatenate((appr, retr))
109     # run the bump, and measure deflection
110     if TEXT_VERBOSE :
111         print "Bump %g nm" % push_depth
112     data = zpiezo.ramp(out, freq)
113     # default saving, so we have a log in-case the operator is lazy ;)
114     if LOG_DATA == True :
115         log = data_logger.data_log(LOG_DIR, noclobber_logsubdir=False,
116                                    log_name="bump_surface")
117         log.write_dict_of_arrays(data)
118     return data
119
120 def bump_save(data, log_dir) :
121     "Save the dictionary data, using data_logger.data_log()"
122     if log_dir != None :
123         log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
124                                    log_name="bump")
125         log.write_dict_of_arrays(data)
126
127 def bump_load(datafile) :
128     "Load the dictionary data, using data_logger.date_load()"
129     dl = data_logger.data_load()
130     data = dl.read_dict_of_arrays(path)
131     return data
132
133 def bump_plot(data, plotVerbose) :
134     "Plot the bump (Vphoto vs Vzp) if plotVerbose or PYLAB_VERBOSE == True"
135     if plotVerbose or PYLAB_VERBOSE :
136         _import_pylab()
137         _pylab.figure(BASE_FIGNUM)
138         _pylab.plot(data["Z piezo output"], data["Deflection input"],
139                     '.', label='bump')
140         _pylab.title("bump surface")
141         _pylab.legend(loc='upper left')
142         _flush_plot()
143
144 def bump(zpiezo, push_depth, npoints=1024, freq=100e3,
145          log_dir=None,
146          plotVerbose=False) :
147     """
148     Wrapper around bump_aquire(), bump_save(), bump_analyze(), bump_plot()
149     """
150     data = bump_aquire(zpiezo, push_depth, npoints, freq)
151     bump_save(data, log_dir)
152     photoSensitivity = bump_analyze(data, zpiezo.gain, zpiezo.sensitivity,
153                                     zpiezo.pos_out2V, zpiezo.def_in2V)
154     bump_plot(data, plotVerbose)
155     return photoSensitivity
156
157 def bump_load_analyze_tweaked(tweak_file, zpGain=_usual_zpGain,
158                               zpSensitivity=_usual_zpSensitivity,
159                               Vzp_out2V=_usual_Vzp_out2V,
160                               Vphoto_in2V=_usual_Vphoto_in2V,
161                               plotVerbose=False) :
162     "Load the output file of tweak_calib_bump.sh, return an array of slopes"
163     photoSensitivity = []
164     for line in file(tweak_file, 'r') :
165         parsed = line.split()
166         path = parsed[0].split('\n')[0]
167         # read the data
168         full_data = bump_load(path)
169         if len(parsed) == 1 :
170             data = full_data # use whole bump
171         else :
172             # use the listed sections
173             zp = []
174             df = []
175             for rng in parsed[1:] :
176                 p = rng.split(':')
177                 starti = int(p[0])
178                 stopi = int(p[1])
179                 zp.extend(full_data['Z piezo output'][starti:stopi])
180                 df.extend(full_data['Deflection input'][starti:stopi])
181             data = {'Z piezo output': array(zp),
182                     'Deflection input':array(df)}
183         pSi = bump_analyze(data, zpGain, zpSensitivity,
184                            Vzp_out2V, Vphoto_in2V, plotVerbose)
185         photoSensitivity.append(pSi)
186         bump_plot(data, plotVervose)
187     return array(photoSensitivity, dtype=numpy.float)
188