calibrate.py should now work.
[calibcant.git] / calibcant / vib_analyze.py
index b8e8c7c3397e50ebab2af581a660c64e14a0efb9..c6bc70fa4c66db285f94163ab921d9d91e9da393 100755 (executable)
@@ -39,9 +39,12 @@ import common # common module for the calibcant package
 import config # config module for the calibcant package
 import data_logger
 import FFT_tools
+from splittable_kwargs import splittableKwargsFunction, \
+    make_splittable_kwargs_function
 
 PLOT_GUESSED_LORENTZIAN=False
 
+@splittableKwargsFunction()
 def vib_analyze_naive(deflection_bits, Vphoto_in2V=config.Vphoto_in2V,
                       zeroVphoto_bits=config.zeroVphoto_bits) :
     """
@@ -51,32 +54,40 @@ def vib_analyze_naive(deflection_bits, Vphoto_in2V=config.Vphoto_in2V,
     
     Vphoto_in2V is a function converting Vphoto values from bits to Volts.
     This function is assumed linear, see bump_analyze().
-    zeroVphoto_bits is the bit value corresponding to Vphoto = zero Volts.
     """
     Vphoto_std_bits = deflection_bits.std()
-    Vphoto_std = Vphoto_in2V(Vphoto_std_bits + zpiezo_zeroV_in)
+    Vphoto_std = Vphoto_in2V(Vphoto_std_bits + zeroVphoto_bits)
     return Vphoto_std**2
-    
-def vib_analyze(deflection_bits, freq, minFreq=500, maxFreq=7000,
+
+#@splittableKwargsFunction((fit_psd, 'freq_axis', 'psd_data'),
+#                          (vib_plot, 'deflection_bits', 'freq_axis', 'power',
+#                           'A', 'B', 'C', 'minFreq', 'maxFreq'))
+# Some of the child functions aren't yet defined, so postpone
+# make-splittable until later in the module.
+def vib_analyze(deflection_bits, freq,
                 chunk_size=4096, overlap=True, window=FFT_tools.window_hann,
-                Vphoto_in2V=config.Vphoto_in2V, plotVerbose=False) :
+                Vphoto_in2V=config.Vphoto_in2V, **kwargs) :
     """
     Calculate the variance in the raw data due to the cantilever's 
     thermal oscillation and convert it to Volts**2.
 
-    Improves on vib_analyze_naive() by first converting Vphoto(t) to 
-    frequency space, and fitting a Lorentzian in the relevant frequency
-    range (see cantilever_calib.pdf for derivation).
+    Improves on vib_analyze_naive() by first converting Vphoto(t) to
+    frequency space, and fitting a Lorentzian in the relevant
+    frequency range (see cantilever_calib.pdf for derivation).
+    However, there may be cases where the fit is thrown off by noise
+    spikes in frequency space.  To protect from errors, the fitted
+    variance is compared to the naive variance (where all noise is
+    included), and the minimum variance is returned.
 
-    The conversion to frequency space generates an average power spectrum
-    by breaking the data into windowed chunks and averaging the power
-    spectrums for the chunks together.
-    See FFT_tools.avg_power_spectrum
-    
     Vphoto_in2V is a function converting Vphoto values from bits to Volts.
     This function is assumed linear, see bump_analyze().
-    zeroVphoto_bits is the bit value corresponding to Vphoto = zero Volts.
     """
+    fit_psd_kwargs,vib_plot_kwargs = vib_analyze._splitargs(vib_analyze,kwargs)
+    if 'minFreq' in fit_psd_kwargs:
+        vib_plot_kwargs['minFreq'] = fit_psd_kwargs['minFreq']
+    if 'maxFreq' in fit_psd_kwargs:
+        vib_plot_kwargs['maxFreq'] = fit_psd_kwargs['maxFreq']
+    
     Vphoto_t = numpy.zeros((len(deflection_bits),),
                            dtype=numpy.float)
     # convert the data from bits to volts
@@ -92,18 +103,26 @@ def vib_analyze(deflection_bits, freq, minFreq=500, maxFreq=7000,
         FFT_tools.unitary_avg_power_spectrum((Vphoto_t - Vphoto_t.mean())*1e6,
                                              freq, chunk_size,overlap, window)
 
-    A,B,C = fit_psd(freq_axis, power, minFreq, maxFreq)
+    A,B,C = fit_psd(freq_axis, power, **kwargs)
 
     if config.TEXT_VERBOSE : 
         print "Fitted f(x) = C / ((A**2-x**2)**2 + (x*B)**2) with"
         print "A = %g, \t B = %g, \t C = %g" % (A, B, C)
 
-    vib_plot(deflection_bits, freq_axis, power, A, B, C,
-             minFreq, maxFreq, plotVerbose=plotVerbose)
+    vib_plot(deflection_bits, freq_axis, power, A, B, C, **kwargs)
 
     # Our A is in uV**2, so convert back to Volts**2
-    return lorentzian_area(A,B,C) * 1e-12
+    fitted_variance = lorentzian_area(A,B,C) * 1e-12
+
+    naive_variance = vib_analyze_naive(deflection_bits,
+                                       Vphoto_in2V=Vphoto_in2V)
+    if config.TEXT_VERBOSE:
+        print "Fitted variance: %g V**2", fitted_variance
+        print "Naive variance : %g V**2", naive_variance
+    
+    return min(fitted_variance, naive_variance)
 
+@splittableKwargsFunction()
 def fit_psd(freq_axis, psd_data, minFreq=500, maxFreq=7000) :
     """
     freq_axis : array of frequencies in Hz
@@ -112,14 +131,10 @@ def fit_psd(freq_axis, psd_data, minFreq=500, maxFreq=7000) :
     Calculate the variance in the raw data due to the cantilever's 
     thermal oscillation and convert it to Volts**2.
 
-    Improves on vib_analyze_naive() by working on frequency space data 
-    and fitting a Lorentzian in the relevant frequency range (see
-    cantilever_calib.pdf for derivation).
-
     The conversion to frequency space generates an average power spectrum
     by breaking the data into windowed chunks and averaging the power
     spectrums for the chunks together.
-    See FFT_tools.unitary_avg_power_spectrum().    
+    See FFT_tools.unitary_avg_power_spectrum().
     """
     # cut out the relevent frequency range
     if config.TEXT_VERBOSE : 
@@ -226,6 +241,8 @@ def fit_psd(freq_axis, psd_data, minFreq=500, maxFreq=7000) :
             else :
                 print "Solution did not converge"
         A,B,C = p
+        A=abs(A) # A and B only show up as squares in f(x)
+        B=abs(B) # so ensure we get positive values
     return (A, B, C)
 
 def lorentzian_area(A, B, C) :
@@ -247,6 +264,7 @@ def gnuplot_check_fit(datafilename, A, B, C) :
     string += "plot '%s', f(x)\n" % datafilename
     return string
 
+@splittableKwargsFunction()
 def vib_save(data, log_dir=None) :
     """Save the dictionary data, using data_logger.data_log()
     data should be a dictionary of arrays with the fields
@@ -255,6 +273,7 @@ def vib_save(data, log_dir=None) :
       'Deflection input'
     """
     if log_dir :
+        freq = data['sample frequency Hz']
         log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
                                    log_name="vib_%gHz" % freq)
         log.write_dict_of_arrays(data)
@@ -270,8 +289,9 @@ def vib_load(datafile=None) :
     data = dl.read_dict_of_arrays(datafile)
     return data
 
+@splittableKwargsFunction()
 def vib_plot(deflection_bits, freq_axis, power, A, B, C,
-             minFreq=None, maxFreq=None, plotVerbose=True) :
+             minFreq=None, maxFreq=None, plotVerbose=False) :
     """
     If plotVerbose or config.PYLAB_VERBOSE == True, plot 3 subfigures:
      Time series (Vphoto vs sample index) (show any major drift events),
@@ -357,15 +377,21 @@ def vib_plot(deflection_bits, freq_axis, power, A, B, C,
         g.getvar("MOUSE_BUTTON")
         del(g)
 
-def vib_load_analyze_tweaked(tweak_file, minFreq=1000, maxFreq=7000,
-                             chunk_size=2048, overlap=True,
-                             window=FFT_tools.window_hann,
-                             Vphoto_in2V=config.Vphoto_in2V,
-                             plotVerbose=False) :
+# Make vib_analyze splittable (was postponed until the child functions
+# were defined).
+make_splittable_kwargs_function(vib_analyze,
+    (fit_psd, 'freq_axis', 'psd_data'),
+    (vib_plot, 'deflection_bits', 'freq_axis', 'power', 'A', 'B', 'C',
+     'minFreq', 'maxFreq'))
+
+@splittableKwargsFunction((vib_analyze, 'deflection_bits', 'freq'))
+def vib_load_analyze_tweaked(tweak_file, **kwargs) :
     """
     Read the vib files listed in tweak_file.
     Return an array of Vphoto variances (due to the cantilever) in Volts**2
     """
+    vib_analyze_kwargs, = \
+        vib_load_analyze_tweaked._splitargs(vib_load_analyze_tweaked, kwargs)
     dl = data_logger.data_load()
     Vphoto_var = []
     for path in file(tweak_file, 'r') :
@@ -378,9 +404,8 @@ def vib_load_analyze_tweaked(tweak_file, minFreq=1000, maxFreq=7000,
         if config.TEXT_VERBOSE :
             print "Analyzing '%s' at %g Hz" % (path, freq)
         # analyze
-        Vphoto_var.append(vib_analyze(deflection_bits, freq, minFreq, maxFreq,
-                                      chunk_size, overlap, window,
-                                      Vphoto_in2V, plotVerbose))
+        Vphoto_var.append(vib_analyze(deflection_bits, freq,
+                                      **vib_analyze_kwargs))
     return numpy.array(Vphoto_var, dtype=numpy.float)
 
 # commandline interface functions
@@ -453,10 +478,6 @@ if __name__ == '__main__' :
         ofile = file(options.ofilename, 'w')
     else :
         ofile = sys.stdout
-    if options.verbose == True :
-        vfile = sys.stderr
-    else :
-        vfile = None
     config.TEXT_VERBOSE = options.verbose
     config.PYLAB_INTERACTIVE = False
     config.PYLAB_VERBOSE = options.pylab