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