3 # calibcant - tools for thermally calibrating AFM cantilevers
5 # Copyright (C) 2007,2008, William Trevor King
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.
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.
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
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.
27 Separate the more general bump_analyze() from the other bump_*()
28 functions in calibcant. Also provide a command line interface
29 for analyzing data acquired through other workflows.
31 The relevant physical quantities are :
32 Vzp_out Output z-piezo voltage (what we generate)
33 Vzp Applied z-piezo voltage (after external ZPGAIN)
34 Zp The z-piezo position
35 Zcant The cantilever vertical deflection
36 Vphoto The photodiode vertical deflection voltage (what we measure)
38 Which are related by the parameters :
40 zpSensitivity Zp / Vzp
41 photoSensitivity Vphoto / Zcant
45 import common # common module for the calibcant package
46 import config # config module for the calibcant package
51 def bump_analyze(data, zpGain=config.zpGain,
52 zpSensitivity=config.zpSensitivity,
53 Vzp_out2V=config.Vzp_out2V,
54 Vphoto_in2V=config.Vphoto_in2V,
55 textVerboseFile=None, plotVerbose=False) :
57 Return the slope of the bump ;).
59 data dictionary of data in DAC/ADC bits
60 Vzp_out2V function that converts output DAC bits to Volts
61 Vphoto_in2V function that converts input ADC bits to Volts
62 zpGain zpiezo applied voltage per output Volt
63 zpSensitivity nm zpiezo response per applied Volt
65 photoSensitivity (Vphoto/Zcant) in Volts/nm
66 Checks for strong correlation (r-value) and low randomness chance (p-value)
68 With the current implementation, the data is regressed in DAC/ADC bits
69 and THEN converted, so we're assuming that both conversions are LINEAR.
70 if they aren't, rewrite to convert before the regression.
72 scale_Vzp_bits2V = Vzp_out2V(1) - Vzp_out2V(0)
73 scale_Vphoto_bits2V = Vphoto_in2V(1) - Vphoto_in2V(0)
74 Vphoto2Vzp_out_bit, intercept = \
75 linfit.linregress(x=data["Z piezo output"],
76 y=data["Deflection input"],
77 plotVerbose=plotVerbose)
78 Vphoto2Vzp_out = Vphoto2Vzp_out_bit * scale_Vphoto_bits2V / scale_Vzp_bits2V
80 # 1 / (Vzp/Vzp_out * Zp/Vzp * Zcant/Zp )
81 Vzp_out2Zcant = 1.0/ (zpGain * zpSensitivity) # * 1
82 return Vphoto2Vzp_out * Vzp_out2Zcant
84 def bump_save(data, log_dir) :
85 "Save the dictionary data, using data_logger.data_log()"
87 log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
89 log.write_dict_of_arrays(data)
91 def bump_load(datafile) :
92 "Load the dictionary data, using data_logger.date_load()"
93 dl = data_logger.data_load()
94 data = dl.read_dict_of_arrays(datafile)
97 def bump_plot(data, plotVerbose) :
98 "Plot the bump (Vphoto vs Vzp) if plotVerbose or PYLAB_VERBOSE == True"
99 if plotVerbose or config.PYLAB_VERBOSE :
100 common._import_pylab()
101 common._pylab.figure(config.BASE_FIGNUM)
102 common._pylab.plot(data["Z piezo output"], data["Deflection input"],
104 common._pylab.title("bump surface")
105 common._pylab.legend(loc='upper left')
108 def bump_load_analyze_tweaked(tweak_file, zpGain=config.zpGain,
109 zpSensitivity=config.zpSensitivity,
110 Vzp_out2V=config.Vzp_out2V,
111 Vphoto_in2V=config.Vphoto_in2V,
112 textVerboseFile=None, plotVerbose=False) :
113 "Load the output file of tweak_calib_bump.sh, return an array of slopes"
114 photoSensitivity = []
115 for line in file(tweak_file, 'r') :
116 parsed = line.split()
117 path = parsed[0].split('\n')[0]
118 if textVerboseFile != None :
119 print >> textVerboseFile, "Reading data from %s with ranges %s" % (path, parsed[1:])
121 full_data = bump_load(path)
122 if len(parsed) == 1 :
123 data = full_data # use whole bump
125 # use the listed sections
128 for rng in parsed[1:] :
132 zp.extend(full_data['Z piezo output'][starti:stopi])
133 df.extend(full_data['Deflection input'][starti:stopi])
134 data = {'Z piezo output': numpy.array(zp),
135 'Deflection input': numpy.array(df)}
136 pSi = bump_analyze(data, zpGain, zpSensitivity,
137 Vzp_out2V, Vphoto_in2V, plotVerbose)
138 photoSensitivity.append(pSi)
139 bump_plot(data, plotVerbose)
140 return numpy.array(photoSensitivity, dtype=numpy.float)
142 # commandline interface functions
145 def read_data(ifile):
146 "ifile can be a filename string or open (seekable) file object"
147 if ifile == None : ifile = sys.stdin
148 unlabeled_data=scipy.io.read_array(ifile)
150 data['Z piezo output'] = unlabeled_data[:,0]
151 data['Deflection input'] = unlabeled_data[:,1]
154 def remove_further_than(data, zp_crit) :
156 ndata['Z piezo output'] = []
157 ndata['Deflection input'] = []
158 for zp,df in zip(data['Z piezo output'],data['Deflection input']) :
160 ndata['Z piezo output'].append(zp)
161 ndata['Deflection input'].append(df)
164 if __name__ == '__main__' :
165 # command line interface
166 from optparse import OptionParser
168 usage_string = ('%prog <input-file>\n'
169 '2008, W. Trevor King.\n'
171 'There are two operation modes, one to analyze a single bump file,\n'
172 'and one to analyze tweak files.\n'
174 'Single file mode (the default) :\n'
175 'Scales raw DAC/ADC bit data and fits a straight line.\n'
176 'Returns photodiode sensitivity Vphotodiode/Zcantilever in V/nm.\n'
177 '<input-file> should be whitespace-delimited, 2 column ASCII\n'
178 'without a header line. e.g: "<zp_DAC>\\t<deflection_ADC>\\n"\n'
181 'Runs the same analysis as in single file mode for each bump in\n'
182 'a tweak file. Each line in the tweak file specifies a single bump.\n'
183 'The format of a line is a series of whitespace-separated fields--\n'
184 'a base file path followed by optional point index ranges, e.g.:\n'
185 '20080919/20080919132500_bump_surface 10:651 1413:2047\n'
186 'which only discards all points outside the index ranges [10,651)\n'
187 'and [1413,2047) (indexing starts at 0).\n'
189 parser = OptionParser(usage=usage_string, version='%prog '+common.VERSION)
190 parser.add_option('-C', '--cut-contact', dest='cut',
191 help='bilinear fit to cut out contact region (currently only available in single-file mode)',
192 action='store_true', default=False)
193 parser.add_option('-o', '--output-file', dest='ofilename',
194 help='write output to FILE (default stdout)',
195 type='string', metavar='FILE')
196 parser.add_option('-c', '--comma-out', dest='comma_out', action='store_true',
197 help='Output comma-seperated values (default %default)',
199 parser.add_option('-t', '--tweak-mode', dest='tweakmode', action='store_true',
200 help='Run in tweak-file mode',
202 parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
203 help='Print lots of debugging information',
206 options,args = parser.parse_args()
208 assert len(args) >= 1, "Need an input file"
212 if options.ofilename != None :
213 ofile = file(options.ofilename, 'w')
216 if options.verbose == True :
220 config.TEXT_VERBOSE = options.verbose
221 config.PYLAB_VERBOSE = False
222 config.GNUPLOT_VERBOSE = False
224 if options.tweakmode == False :
225 data = read_data(ifilename)
227 ddict = {'approach':data} # although it may be any combination of approach and retract
229 params = z_piezo_utils.analyzeSurfPosData(ddict, retAllParams=True)
230 a,b,c,d = tuple(params) # model : f(x) = x<c ? a + b*x : (a+b*c) + d*(x-c)
231 print >> sys.stderr, "fit with", params, ". using zp < %d" % c
232 data = remove_further_than(data, c)
233 except z_piezo_utils.poorFit, s :
234 # data not very bilinear, so don't cut anything off.
235 print >> sys.stderr, "use everything"
237 photoSensitivity = bump_analyze(data, textVerboseFile=vfile)
239 print >> ofile, photoSensitivity
240 else : # tweak file mode
241 slopes = bump_load_analyze_tweaked(ifilename, textVerboseFile=vfile)
242 if options.comma_out :
246 common.write_array(ofile, slopes, sep)
248 if options.ofilename != None :