calibrate.py should now work.
authorW. Trevor King <wking@drexel.edu>
Sun, 21 Dec 2008 05:01:11 +0000 (00:01 -0500)
committerW. Trevor King <wking@drexel.edu>
Sun, 21 Dec 2008 05:01:11 +0000 (00:01 -0500)
A bunch of changes in one commit, sorry.

Moved to fledgling splittable_kwargs system to make default argument
maintenance easier.  I expect the splittable_kwargs system still has
some growing to do, but it's already better than the old system.

Merged BE database from the calibcant subdir into the main BE database.
It was a mistake to create the database there in the first place.

37 files changed:
.be/bugs/1456fd10-2036-46de-b18e-ddf8cab14ae2/comments/b7152be2-60ea-439f-be64-9ba45ce7e4fa/body [moved from calibcant/.be/bugs/1456fd10-2036-46de-b18e-ddf8cab14ae2/comments/b7152be2-60ea-439f-be64-9ba45ce7e4fa/body with 100% similarity]
.be/bugs/1456fd10-2036-46de-b18e-ddf8cab14ae2/comments/b7152be2-60ea-439f-be64-9ba45ce7e4fa/values [moved from calibcant/.be/bugs/1456fd10-2036-46de-b18e-ddf8cab14ae2/comments/b7152be2-60ea-439f-be64-9ba45ce7e4fa/values with 100% similarity]
.be/bugs/1456fd10-2036-46de-b18e-ddf8cab14ae2/values [moved from calibcant/.be/bugs/1456fd10-2036-46de-b18e-ddf8cab14ae2/values with 100% similarity]
.be/bugs/3059911a-963c-472e-a9d3-bf0a290de1a2/comments/1d27eecf-4327-4342-bd82-41a859d33a37/body [moved from calibcant/.be/bugs/3059911a-963c-472e-a9d3-bf0a290de1a2/comments/1d27eecf-4327-4342-bd82-41a859d33a37/body with 100% similarity]
.be/bugs/3059911a-963c-472e-a9d3-bf0a290de1a2/comments/1d27eecf-4327-4342-bd82-41a859d33a37/values [moved from calibcant/.be/bugs/3059911a-963c-472e-a9d3-bf0a290de1a2/comments/1d27eecf-4327-4342-bd82-41a859d33a37/values with 100% similarity]
.be/bugs/3059911a-963c-472e-a9d3-bf0a290de1a2/comments/82052baf-730c-4b42-b2a0-783de0201c2f/body [moved from calibcant/.be/bugs/3059911a-963c-472e-a9d3-bf0a290de1a2/comments/82052baf-730c-4b42-b2a0-783de0201c2f/body with 100% similarity]
.be/bugs/3059911a-963c-472e-a9d3-bf0a290de1a2/comments/82052baf-730c-4b42-b2a0-783de0201c2f/values [moved from calibcant/.be/bugs/3059911a-963c-472e-a9d3-bf0a290de1a2/comments/82052baf-730c-4b42-b2a0-783de0201c2f/values with 100% similarity]
.be/bugs/3059911a-963c-472e-a9d3-bf0a290de1a2/values [moved from calibcant/.be/bugs/3059911a-963c-472e-a9d3-bf0a290de1a2/values with 100% similarity]
.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/285836e5-205d-441c-8b9d-65adea9d486f/body [new file with mode: 0644]
.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/285836e5-205d-441c-8b9d-65adea9d486f/values [new file with mode: 0644]
.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/651ebc04-cc59-4996-b4f3-33bfd9c88c23/body [new file with mode: 0644]
.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/651ebc04-cc59-4996-b4f3-33bfd9c88c23/values [new file with mode: 0644]
.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/96566a23-d674-44cb-8839-dfd962c3ad99/body [new file with mode: 0644]
.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/96566a23-d674-44cb-8839-dfd962c3ad99/values [new file with mode: 0644]
.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/daf08023-7758-482e-a481-800872e84312/body [new file with mode: 0644]
.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/daf08023-7758-482e-a481-800872e84312/values [new file with mode: 0644]
.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/values [new file with mode: 0644]
.be/bugs/4d7fd386-2bf6-4c31-ae68-cdfa364ce2ac/comments/68e5c534-39b4-46ce-85bc-766155f03d6f/body [new file with mode: 0644]
.be/bugs/4d7fd386-2bf6-4c31-ae68-cdfa364ce2ac/comments/68e5c534-39b4-46ce-85bc-766155f03d6f/values [new file with mode: 0644]
.be/bugs/4d7fd386-2bf6-4c31-ae68-cdfa364ce2ac/values [new file with mode: 0644]
.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/comments/7bb7514d-f094-461c-8b96-04590415282c/body [moved from calibcant/.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/comments/7bb7514d-f094-461c-8b96-04590415282c/body with 100% similarity]
.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/comments/7bb7514d-f094-461c-8b96-04590415282c/values [moved from calibcant/.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/comments/7bb7514d-f094-461c-8b96-04590415282c/values with 100% similarity]
.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/comments/d239edab-0769-496e-8c49-fdd25677afb7/body [moved from calibcant/.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/comments/d239edab-0769-496e-8c49-fdd25677afb7/body with 100% similarity]
.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/comments/d239edab-0769-496e-8c49-fdd25677afb7/values [moved from calibcant/.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/comments/d239edab-0769-496e-8c49-fdd25677afb7/values with 100% similarity]
.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/comments/d6896169-8e43-4e31-a2c6-53a580ba5ec6/body [moved from calibcant/.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/comments/d6896169-8e43-4e31-a2c6-53a580ba5ec6/body with 100% similarity]
.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/comments/d6896169-8e43-4e31-a2c6-53a580ba5ec6/values [moved from calibcant/.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/comments/d6896169-8e43-4e31-a2c6-53a580ba5ec6/values with 100% similarity]
.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/values [moved from calibcant/.be/bugs/f3ba76cd-eddd-4520-bb2a-17ca371ac6b6/values with 100% similarity]
.be/settings
calibcant/.be/settings [deleted file]
calibcant/.be/version [deleted file]
calibcant/T_analyze.py
calibcant/analyze.py
calibcant/bump_analyze.py
calibcant/calibrate.py [new file with mode: 0644]
calibcant/calibrate_cantilever.py [deleted file]
calibcant/config.py
calibcant/vib_analyze.py

diff --git a/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/285836e5-205d-441c-8b9d-65adea9d486f/body b/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/285836e5-205d-441c-8b9d-65adea9d486f/body
new file mode 100644 (file)
index 0000000..883bebe
--- /dev/null
@@ -0,0 +1,30 @@
+z_piezo_calib output confirmed with
+
+$ cd /home/wking/research/drexel/gyang/data/z_piezo_calib
+$ ipython --pylab
+In [1]: import z_piezo_calib
+In [2]: z_piezo_calib.analyze_calibration_data_from_tweaks(bump_tweaks="20081215_tweak.bump", vib_tweaks="20081215_tweak.vib", T_tweaks="20081215_tweak.temp", plotVerbose=False)
+a**2    : 0.000190122 +/- 1.86869e-06 (0.00982893)
+T       : 295.15 +/- 5.68434e-14 (1.92592e-16)
+1/Vp**2 : 61416.8 +/- 17792.9 (0.289708)
+Out[2]: 
+(0.0475823000538,
+ 0.0137928827301,
+ 0.00982893194938,
+ 1.92591627514e-16,
+ 0.289707547407)
+
+In [3]: z_piezo_calib.analyze_calibration_data_from_tweaks(bump_tweaks="20081215_tweak_second.bump", vib_tweaks="20081215_tweak_second.vib", T_tweaks="20081215_tweak_second.temp", plotVerbose=False)
+a**2    : 0.000217743 +/- 1.08297e-06 (0.00497362)
+T       : 295.15 +/- 5.68434e-14 (1.92592e-16)
+1/Vp**2 : 38507.9 +/- 13142.7 (0.341299)
+Out[3]: 
+(0.034168120631,
+ 0.0116627940459,
+ 0.00497362408195,
+ 1.92591627514e-16,
+ 0.341299306648)
+
+However, plotVerbose is broken (missing figure definition), so I'm
+gonna go fix that now.
+
diff --git a/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/285836e5-205d-441c-8b9d-65adea9d486f/values b/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/285836e5-205d-441c-8b9d-65adea9d486f/values
new file mode 100644 (file)
index 0000000..accc717
--- /dev/null
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Fri, 19 Dec 2008 06:17:07 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: daf08023-7758-482e-a481-800872e84312
+
diff --git a/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/651ebc04-cc59-4996-b4f3-33bfd9c88c23/body b/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/651ebc04-cc59-4996-b4f3-33bfd9c88c23/body
new file mode 100644 (file)
index 0000000..5e95cdc
--- /dev/null
@@ -0,0 +1,46 @@
+I just reran Marissa's calibration.  My original output (from the old
+gnuplot-fitted ~/.python/calibrate_cantilever.py) had produced
+
+  $ cat 20081215/20081215101516_analysis_text
+  k (N/m) : 0.0471212 +/- 0.0132663
+  a**2 (V/nm)**2 : 0.000191658 +/- 1.36722e-06
+  T (K) : 295.15 +/- 5.68434e-14
+  1/Vp**2 (1/V)**2 : 60334.2 +/- 16980.8
+  $ cat 20081215/20081215124856_analysis_text
+  k (N/m) : 0.0346675 +/- 0.0118672
+  a**2 (V/nm)**2 : 0.000217743 +/- 1.08297e-06
+  T (K) : 295.15 +/- 5.68434e-14
+  1/Vp**2 (1/V)**2 : 39070.7 +/- 13373.1
+
+Using all the defaults (and only the second take for the first
+calibration).  However, once I'd set up the tweakfiles and ran
+20081215_calibrate.sh, I got
+
+  $ cat 20081215/20081215101516_analysis_text
+  Variable (units)              : mean +/- std. dev. (relative error)
+  Cantilever k (N/m)            : 0.0656644 +/- 0.00107743 (0.016408)
+  photoSensitivity**2 (V/nm)**2 : 0.000190122 +/- 1.86869e-06 (0.00982893)
+  T (K)                         : 295.15 +/- 5.68434e-14 (1.92592e-16)
+  1/Vphoto**2 (1/V)**2          : 84756.3 +/- 1113.56 (0.0131383)
+  $ cat 20081215/20081215124856_analysis_text
+  Variable (units)              : mean +/- std. dev. (relative error)
+  Cantilever k (N/m)            : 0.0494809 +/- 0.000855265 (0.0172848)
+  photoSensitivity**2 (V/nm)**2 : 0.000217743 +/- 1.08297e-06 (0.00497362)
+  T (K)                         : 295.15 +/- 5.68434e-14 (1.92592e-16)
+  1/Vphoto**2 (1/V)**2          : 55765.6 +/- 923.129 (0.0165537)
+
+Comparing the two, we see:
+  101516:
+    k 0.0471212 -> 0.0656644 (with a 92% drop in error)
+    photoSensitivity 0.000191658 (little change)
+    T 295.15 (no change)
+    1/Vphoto**2 60334.2 -> 84756.3 (with a 94% drop in error)
+  124856:
+    k 0.0346675 -> 0.0494809 (with a 93% drop in error)
+    photoSensitivity 0.000217743 (no change)
+    T 295.15 (no change)
+    1/Vphoto**2 39070.7 -> 55765.6 (with a 93% drop in error)
+
+I don't have hard evidence yet, but I am confident this discrepancy is
+due to poor fitting with the older calibration file.
+
diff --git a/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/651ebc04-cc59-4996-b4f3-33bfd9c88c23/values b/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/651ebc04-cc59-4996-b4f3-33bfd9c88c23/values
new file mode 100644 (file)
index 0000000..2b97586
--- /dev/null
@@ -0,0 +1,8 @@
+Content-type: text/plain
+
+
+Date: Fri, 19 Dec 2008 05:25:39 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
diff --git a/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/96566a23-d674-44cb-8839-dfd962c3ad99/body b/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/96566a23-d674-44cb-8839-dfd962c3ad99/body
new file mode 100644 (file)
index 0000000..da07afb
--- /dev/null
@@ -0,0 +1,2 @@
+Yikes, z_piezo_calib uses the actual variance (no fitting)!  No wonder
+it was so ugly.
diff --git a/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/96566a23-d674-44cb-8839-dfd962c3ad99/values b/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/96566a23-d674-44cb-8839-dfd962c3ad99/values
new file mode 100644 (file)
index 0000000..2b48f42
--- /dev/null
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Fri, 19 Dec 2008 06:22:45 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: 651ebc04-cc59-4996-b4f3-33bfd9c88c23
+
diff --git a/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/daf08023-7758-482e-a481-800872e84312/body b/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/daf08023-7758-482e-a481-800872e84312/body
new file mode 100644 (file)
index 0000000..9cced00
--- /dev/null
@@ -0,0 +1,13 @@
+Oops, I was running ~/.python/z_piezo_calib.calibrate_cantilever()
+during the experiment, not ~/.python/calibrate_cantilever.something().
+I was wondering why it was so difficult to get
+~./python/calibrate_cantilever.py working.  I ended up getting
+
+  Cantilever k        : 0.589927 +/- 0.790152 (1.33941)
+  photoSensitivity**2 : 0.000190122 +/- 1.86869e-06 (0.00982893)
+  T                   : 295.15 +/- 0 (0)
+  1/Vphoto**2         : 761447 +/- 1.01986e+06 (1.33937)
+
+out of calibrate_cantilever.py for the first cantilever, but that is
+pretty rediculous...  Maybe I'll come back and figure out what's going on
+after I check out the z_piezo_calib version...
diff --git a/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/daf08023-7758-482e-a481-800872e84312/values b/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/comments/daf08023-7758-482e-a481-800872e84312/values
new file mode 100644 (file)
index 0000000..1a459ea
--- /dev/null
@@ -0,0 +1,11 @@
+Content-type: text/plain
+
+
+Date: Fri, 19 Dec 2008 06:09:39 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
+
+In-reply-to: 651ebc04-cc59-4996-b4f3-33bfd9c88c23
+
diff --git a/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/values b/.be/bugs/443dd869-a3f5-456c-82af-388f0923bc27/values
new file mode 100644 (file)
index 0000000..58d8051
--- /dev/null
@@ -0,0 +1,17 @@
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: W. Trevor King <wking@drexel.edu>
+
+
+severity: minor
+
+
+status: fixed
+
+
+summary: Suprising difference between old calibration and new calibration
+
+
+time: Fri, 19 Dec 2008 05:08:51 +0000
+
diff --git a/.be/bugs/4d7fd386-2bf6-4c31-ae68-cdfa364ce2ac/comments/68e5c534-39b4-46ce-85bc-766155f03d6f/body b/.be/bugs/4d7fd386-2bf6-4c31-ae68-cdfa364ce2ac/comments/68e5c534-39b4-46ce-85bc-766155f03d6f/body
new file mode 100644 (file)
index 0000000..278309b
--- /dev/null
@@ -0,0 +1,2 @@
+Some files use config.TEXT_VERBOSE boolean, others use textVerboseFile option.
+Pick one method and go with it.
diff --git a/.be/bugs/4d7fd386-2bf6-4c31-ae68-cdfa364ce2ac/comments/68e5c534-39b4-46ce-85bc-766155f03d6f/values b/.be/bugs/4d7fd386-2bf6-4c31-ae68-cdfa364ce2ac/comments/68e5c534-39b4-46ce-85bc-766155f03d6f/values
new file mode 100644 (file)
index 0000000..5d3e5f1
--- /dev/null
@@ -0,0 +1,8 @@
+Content-type: text/plain
+
+
+Date: Sun, 21 Dec 2008 03:07:58 +0000
+
+
+From: W. Trevor King <wking@drexel.edu>
+
diff --git a/.be/bugs/4d7fd386-2bf6-4c31-ae68-cdfa364ce2ac/values b/.be/bugs/4d7fd386-2bf6-4c31-ae68-cdfa364ce2ac/values
new file mode 100644 (file)
index 0000000..dcb31dc
--- /dev/null
@@ -0,0 +1,17 @@
+creator: W. Trevor King <wking@drexel.edu>
+
+
+reporter: W. Trevor King <wking@drexel.edu>
+
+
+severity: minor
+
+
+status: open
+
+
+summary: unify TEXT_VERBOSE
+
+
+time: Sun, 21 Dec 2008 03:07:08 +0000
+
index 3fe7e729725735b4275543420a46f682bc403e2e..739d28cd3c9a1ae91c64eab0604893bb46d161fc 100644 (file)
@@ -1,7 +1,2 @@
-
-
-
-rcs_name=git
-
-
+rcs_name: git
 
diff --git a/calibcant/.be/settings b/calibcant/.be/settings
deleted file mode 100644 (file)
index 3fe7e72..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-rcs_name=git
-
-
-
diff --git a/calibcant/.be/version b/calibcant/.be/version
deleted file mode 100644 (file)
index 990837e..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Bugs Everywhere Tree 1 0
index 7d46993ed9a9eca9e48f0934ca7125a4257ee104..d52475d86c7d8cfef67823c5fa6258492c5a8c11 100755 (executable)
@@ -37,11 +37,18 @@ import common # common module for the calibcant package
 import config # config module for the calibcant package
 import data_logger
 import linfit
+from splittable_kwargs import splittableKwargsFunction, \
+    make_splittable_kwargs_function
 
 def C_to_K(celsius) :
     "Convert Celsius -> Kelvin."
     return celsius + 273.15
 
+def K_to_K(kelvin) :
+    "Convert Kelvin -> Kelvin."
+    return kelvin
+
+@splittableKwargsFunction()
 def T_analyze(T, convert_to_K=C_to_K) :
     """
     Not much to do here, just convert to Kelvin.
@@ -54,6 +61,7 @@ def T_analyze(T, convert_to_K=C_to_K) :
         T = [convert_to_K(T)]
     return T
 
+@splittableKwargsFunction()
 def T_save(T, log_dir=None) :
     """
     Save either a single T (if you are crazy :p),
@@ -81,15 +89,20 @@ def T_load(datafile=None) :
         dl = data_logger.data_load()
         return dl.read_binary(datafile)
 
-def T_plot(T, plotVerbose) :
+@splittableKwargsFunction()
+def T_plot(T, plotVerbose=False) :
     """
     Umm, just print the temperature?
     """
     if plotVerbose or PYLAB_VERBOSE or TEXT_VERBOSE :
         print "Temperature ", T
 
-def T_load_analyze_tweaked(tweak_file, convert_to_K=C_to_K, textVerboseFile=None) :
+@splittableKwargsFunction((T_analyze, 'T', 'convert_to_K'),
+                          (T_plot, 'T'))
+def T_load_analyze_tweaked(tweak_file, convert_to_K=C_to_K, textVerboseFile=None, **kwargs) :
     "Load all the T array files from a tweak file and return a single array"
+    T_analyze_kwargs,T_plot_kwargs = \
+        T_load_analyze_tweaked._splitargs(T_load_analyze_tweaked, kwargs)
     Ts = []
     for line in file(tweak_file, 'r') :
         parsed = line.split()
@@ -99,6 +112,7 @@ def T_load_analyze_tweaked(tweak_file, convert_to_K=C_to_K, textVerboseFile=None
         # read the data
         data = T_load(path)
         Ts.extend(data)
+    T_analyze(Ts, convert_to_K=K_to_K)
     return numpy.array(Ts, dtype=numpy.float)
 
 # commandline interface functions
index 797a78a5a91e4e2785cc37b31573ebe7526853b4..2e535efac835df4974fcdcee406cfd4a7c414e1f 100755 (executable)
@@ -47,13 +47,18 @@ Which are related by the parameters :
 """
 
 import numpy
+from splittable_kwargs import splittableKwargsFunction, \
+    make_splittable_kwargs_function
 import common # common module for the calibcant package
 import config # config module for the calibcant package
 import T_analyze # T_analyze module for the calibcant package
 
 kb = 1.3806504e-23 # Boltzmann's constant in J/K
 
-def calib_analyze(bumps, Ts, vibs) :
+#@splittableKwargsFunction((calib_plot, 'bumps', 'Ts', 'vibs'))
+# Some of the child functions aren't yet defined, so postpone
+# make-splittable until later in the module.
+def calib_analyze(bumps, Ts, vibs, **kwargs) :
     """
     Analyze data from get_calibration_data()
     return (k, k_s, !!!a2_r, T_r, one_o_Vp2_r)
@@ -78,6 +83,7 @@ def calib_analyze(bumps, Ts, vibs) :
     Remember that you need enough samples to have a valid error estimate in
     the first place, and that none of this addresses any systematic errors.
     """
+    calib_plot_kwargs, = calib_analyze._splitargs(calib_analyze, kwargs)
     photoSensitivity2 = bumps**2
     one_o_Vphoto2 = 1/vibs
 
@@ -105,10 +111,13 @@ def calib_analyze(bumps, Ts, vibs) :
 
     k_s = k*(photoSensitivity2_r**2 + T_r**2 + one_o_Vphoto2_r**2)**0.5
 
+    calib_plot(bumps, Ts, vibs, **calib_plot_kwargs)
+    
     return (k, k_s,
             photoSensitivity2_m, photoSensitivity2_s,
             T_m, T_s, one_o_Vphoto2_m, one_o_Vphoto2_s)
 
+@splittableKwargsFunction()
 def string_errors(k_m, k_s,
                   photoSensitivity2_m, photoSensitivity2_s,
                   T_m, T_s,
@@ -128,19 +137,66 @@ def string_errors(k_m, k_s,
               % (one_o_Vphoto2_m, one_o_Vphoto2_s, one_o_Vphoto2_r)
     return string
 
-def calib_plot(bumps, Ts, vibs) :
-    from pylab import figure, subplot, plot, title, show
-    figure()
-    subplot(311)
-    plot(bumps, 'g.-')
-    title('Photodiode sensitivity (V/nm)')
-    subplot(312)
-    plot(Ts, 'r.-')
-    title('Temperature (K)')
-    subplot(313)
-    plot(vibs, 'b.-')
-    title('Thermal deflection variance (Volts**2)')
-    show()
+@splittableKwargsFunction()
+def calib_save(bumps, Ts, vibs, log_dir=None) :
+    """
+    Save a dictonary with the bump, T, and vib data.
+    """
+    if log_dir != None :
+        data = {'bump':bumps, 'T':Ts, 'vib':vibs}
+        log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
+                                   log_name="calib")
+        log.write_dict_of_arrays(data)
+
+def calib_load(datafile) :
+    "Load the dictionary data, using data_logger.date_load()"
+    dl = data_logger.data_load()
+    data = dl.read_dict_of_arrays(path)
+    return (data['bump'], data['T'], data['vib'])
+
+def calib_save_analysis(k, k_s,
+                        photoSensitivity2_m, photoSensitivity2_s,
+                        T_m, T_s, one_o_Vphoto2_m, one_o_Vphoto2_s,
+                        log_dir=None) :
+    if log_dir != None :
+        log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
+                                   log_name="calib_analysis_text")
+        log.write_binary((
+            "k (N/m) : %g +/- %g\n" % (k, k_s)
+            + "photoSensitivity**2 (V/nm)**2 : %g +/- %g\n" % \
+                (photoSensitivity2_m, photoSensitivity2_s)
+            + "T (K) : %g +/- %g\n" % (T_m, T_s)
+            + "1/Vp**2 (1/V)**2 : %g +/- %g\n" % \
+                (one_o_Vphoto2_m, one_o_Vphoto2_s)
+                         ))
+
+@splittableKwargsFunction()
+def calib_plot(bumps, Ts, vibs, plotVerbose=False) :
+    if plotVerbose or config.PYLAB_VERBOSE :
+        common.import_pylab()
+        common._pylab.figure(config.BASE_FIGNUM+4)
+        common._pylab.subplot(311)
+        common._pylab.plot(bumps, 'g.')
+        common._pylab.title('Photodiode sensitivity (V/nm)')
+        common._pylab.subplot(312)
+        common._pylab.plot(Ts, 'r.')
+        common._pylab.title('Temperature (K)')
+        common._pylab.subplot(313)
+        common._pylab.plot(vibs, 'b.')
+        common._pylab.title('Thermal deflection variance (Volts**2)')
+        common._flush_plot()
+
+make_splittable_kwargs_function(calib_analyze,
+                                (calib_plot, 'bumps', 'Ts', 'vibs'))
+
+@splittableKwargsFunction((calib_analyze, 'bumps', 'Ts', 'vibs'))
+def calib_load_analyze_tweaks(bump_tweaks, vib_tweaks, T_tweaks=None) :
+    raise NotImplementedError
+    a = read_tweaked_bumps(bump_tweaks)
+    vib = V_photo_variance_from_file(vib_tweaks)
+    if T_tweaks == None:
+        pass
+    return analyze_calibration_data(a, T, vib, log_dir=log_dir)
 
 # commandline interface functions
 import scipy.io, sys
index 3d3a891b35b6676dc8f3eb6d2780a43afe086e39..11a8f51e719f7f8b96220bec7fca47b4357ddb06 100755 (executable)
@@ -39,6 +39,10 @@ Which are related by the parameters :
  zpGain           Vzp_out / Vzp
  zpSensitivity    Zp / Vzp
  photoSensitivity Vphoto / Zcant
+
+photoSensitivity is measured by bumping the cantilever against the
+surface, where Zp = Zcant (see calibrate.bump_aquire()).  The measured
+slope Vphoto/Vout is converted to photoSensitivity with bump_analyze().
 """
 
 import numpy
@@ -47,12 +51,17 @@ import config # config module for the calibcant package
 import data_logger
 import z_piezo_utils
 import linfit
+from splittable_kwargs import splittableKwargsFunction, \
+    make_splittable_kwargs_function
 
+#@splittableKwargsFunction((bump_plot, 'data'))
+# Some of the child functions aren't yet defined, so postpone
+# make-splittable until later in the module.
 def bump_analyze(data, zpGain=config.zpGain,
                  zpSensitivity=config.zpSensitivity,
                  Vzp_out2V=config.Vzp_out2V,
                  Vphoto_in2V=config.Vphoto_in2V,
-                 textVerboseFile=None, plotVerbose=False) :
+                 **kwargs) :
     """
     Return the slope of the bump ;).
     Inputs:
@@ -69,19 +78,22 @@ def bump_analyze(data, zpGain=config.zpGain,
     and THEN converted, so we're assuming that both conversions are LINEAR.
     if they aren't, rewrite to convert before the regression.
     """
+    bump_plot_kwargs, = bump_analyze._splitargs(bump_analyze, kwargs)
     scale_Vzp_bits2V = Vzp_out2V(1) - Vzp_out2V(0)
     scale_Vphoto_bits2V = Vphoto_in2V(1) - Vphoto_in2V(0)
     Vphoto2Vzp_out_bit, intercept = \
         linfit.linregress(x=data["Z piezo output"],
                           y=data["Deflection input"],
-                          plotVerbose=plotVerbose)
-    Vphoto2Vzp_out = Vphoto2Vzp_out_bit * scale_Vphoto_bits2V / scale_Vzp_bits2V
+                          plotVerbose=False)
+    Vphoto2Vzp_out = Vphoto2Vzp_out_bit *scale_Vphoto_bits2V / scale_Vzp_bits2V
 
     #               1 / (Vzp/Vzp_out  *     Zp/Vzp       * Zcant/Zp )
     Vzp_out2Zcant = 1.0/ (zpGain      * zpSensitivity) # * 1
+    bump_plot(data, **bump_plot_kwargs)
     return Vphoto2Vzp_out * Vzp_out2Zcant
 
-def bump_save(data, log_dir) :
+@splittableKwargsFunction()
+def bump_save(data, log_dir=None) :
     "Save the dictionary data, using data_logger.data_log()"
     if log_dir != None :
         log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
@@ -94,7 +106,8 @@ def bump_load(datafile) :
     data = dl.read_dict_of_arrays(datafile)
     return data
 
-def bump_plot(data, plotVerbose) :
+@splittableKwargsFunction()
+def bump_plot(data, plotVerbose=False) :
     "Plot the bump (Vphoto vs Vzp) if plotVerbose or PYLAB_VERBOSE == True"
     if plotVerbose or config.PYLAB_VERBOSE :
         common._import_pylab()
@@ -105,18 +118,19 @@ def bump_plot(data, plotVerbose) :
         common._pylab.legend(loc='upper left')
         common._flush_plot()
 
-def bump_load_analyze_tweaked(tweak_file, zpGain=config.zpGain,
-                              zpSensitivity=config.zpSensitivity,
-                              Vzp_out2V=config.Vzp_out2V,
-                              Vphoto_in2V=config.Vphoto_in2V,
-                              textVerboseFile=None, plotVerbose=False) :
+make_splittable_kwargs_function(bump_analyze, (bump_plot, 'data'))
+
+@splittableKwargsFunction((bump_analyze, 'data'))
+def bump_load_analyze_tweaked(tweak_file, **kwargs):
     "Load the output file of tweak_calib_bump.sh, return an array of slopes"
+    bump_analyze_kwargs, = \
+        bump_load_analyze_tweaked._splitargs(bump_load_analyze_tweaked, kwargs)
     photoSensitivity = []
     for line in file(tweak_file, 'r') :
         parsed = line.split()
         path = parsed[0].split('\n')[0]
-        if textVerboseFile != None :
-            print >> textVerboseFile, "Reading data from %s with ranges %s" % (path, parsed[1:])
+        if config.TEXT_VERBOSE :
+            print "Reading data from %s with ranges %s" % (path, parsed[1:])
         # read the data
         full_data = bump_load(path)
         if len(parsed) == 1 :
@@ -133,10 +147,8 @@ def bump_load_analyze_tweaked(tweak_file, zpGain=config.zpGain,
                 df.extend(full_data['Deflection input'][starti:stopi])
             data = {'Z piezo output': numpy.array(zp),
                     'Deflection input': numpy.array(df)}
-        pSi = bump_analyze(data, zpGain, zpSensitivity,
-                           Vzp_out2V, Vphoto_in2V, plotVerbose)
+        pSi = bump_analyze(data, **bump_analyze_kwargs)
         photoSensitivity.append(pSi)
-        bump_plot(data, plotVerbose)
     return numpy.array(photoSensitivity, dtype=numpy.float)
 
 # commandline interface functions
@@ -216,10 +228,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
@@ -238,11 +246,11 @@ if __name__ == '__main__' :
                 # data not very bilinear, so don't cut anything off.
                 print >> sys.stderr, "use everything"
                 
-        photoSensitivity = bump_analyze(data, textVerboseFile=vfile)
+        photoSensitivity = bump_analyze(data)
         
         print >> ofile, photoSensitivity
     else : # tweak file mode
-        slopes = bump_load_analyze_tweaked(ifilename, textVerboseFile=vfile)
+        slopes = bump_load_analyze_tweaked(ifilename)
         if options.comma_out :
             sep = ','
         else :
diff --git a/calibcant/calibrate.py b/calibcant/calibrate.py
new file mode 100644 (file)
index 0000000..64ac942
--- /dev/null
@@ -0,0 +1,393 @@
+#!/usr/bin/python
+
+"""
+Aquire and analyze cantilever calibration data.
+
+W. Trevor King Dec. 2007-Jan. 2008
+
+GPL BOILERPLATE
+
+
+The relevent physical quantities are :
+ Vzp_out  Output z-piezo voltage (what we generate)
+ Vzp      Applied z-piezo voltage (after external ZPGAIN)
+ Zp       The z-piezo position
+ Zcant    The cantilever vertical deflection
+ Vphoto   The photodiode vertical deflection voltage (what we measure)
+ Fcant    The force on the cantilever
+ T        The temperature of the cantilever and surrounding solution
+          (another thing we measure or guess)
+ k_b      Boltzmann's constant
+
+Which are related by the parameters :
+ zpGain           Vzp_out / Vzp
+ zpSensitivity    Zp / Vzp
+ photoSensitivity Vphoto / Zcant
+ k_cant           Fcant / Zcant
+
+Cantilever calibration assumes a pre-calibrated z-piezo
+(zpSensitivity) and a amplifier (zpGain).  In our lab, the z-piezo is
+calibrated by imaging a calibration sample, which has features with
+well defined sizes, and the gain is set with a knob on the Nanoscope.
+
+photoSensitivity is measured by bumping the cantilever against the
+surface, where Zp = Zcant (see bump_aquire() and the bump_analyze
+submodule).
+
+k_cant is measured by watching the cantilever vibrate in free solution
+(see the vib_aquire() and the vib_analyze submodule).  The average
+energy of the cantilever in the vertical direction is given by the
+equipartition theorem.
+    1/2 k_b T   =   1/2 k_cant <Zcant**2>
+ so     k_cant  = k_b T / Zcant**2
+ but    Zcant   = Vphoto / photoSensitivity
+ so     k_cant  = k_b T * photoSensitivty**2 / <Vphoto**2>
+
+We measured photoSensitivity with the surface bumps.  We can either
+measure T using an external function (see temperature.py), or just
+estimate it (see T_aquire() and the T_analyze submodule).  Guessing
+room temp ~22 deg C is actually fairly reasonable.  Assuming the
+actual fluid temperature is within +/- 5 deg, the error in the spring
+constant k_cant is within 5/273.15 ~= 2%.  A time series of Vphoto
+while we're far from the surface and not changing Vzp_out will give us
+the average variance <Vphoto**2>.
+
+We do all these measurements a few times to estimate statistical
+errors.
+
+The functions are layed out in the families:
+ bump_*(), vib_*(), T_*(), and calib_*()
+where calib_{save|load|analyze}() deal with derived data, not
+real-world data.
+
+For each family, * can be any of :
+ aquire       get real-world data
+ save         store real-world data to disk
+ load         get real-world data from disk
+ analyze      interperate the real-world data.
+ plot         show a nice graphic to convince people we're working :p
+ load_analyze_tweaked
+              read a file with a list of paths to previously saved
+              real world data load each file using *_load(), analyze
+              using *_analyze(), and optionally plot using *_plot().
+              Intended for re-processing old data.
+A family name without any _* extension (e.g. bump()), runs *_aquire(),
+ *_save(), *_analyze(), *_plot().
+
+We also define the two positioning functions:
+ move_just_onto_surface() and move_far_from_surface()
+which make automating the calibration procedure more straightforward.
+"""
+
+import numpy
+import time 
+import z_piezo_utils
+from splittable_kwargs import splittableKwargsFunction, \
+    make_splittable_kwargs_function
+
+import common
+import config
+import bump_analyze
+import T_analyze
+import vib_analyze
+import analyze
+
+# bump family
+
+@splittableKwargsFunction()
+def bump_aquire(zpiezo, push_depth=200, npoints=1024, freq=100e3) :
+    """
+    Ramps closer push_depth and returns to the original position.
+    Inputs:
+     zpiezo     an opened zpiezo.zpiezo instance
+     push_depth distance to approach, in nm
+     npoints    number points during the approach and during the retreat
+     freq       rate at which data is aquired
+    Returns the aquired ramp data dictionary, with data in DAC/ADC bits.
+    """
+    # generate the bump output
+    start_pos = zpiezo.curPos()
+    pos_dist = zpiezo.pos_nm2out(push_depth) - zpiezo.pos_nm2out(0)
+    close_pos = start_pos + pos_dist
+    appr = linspace(start_pos, close_pos, npoints)
+    retr = linspace(close_pos, start_pos, npoints)
+    out = concatenate((appr, retr))
+    # run the bump, and measure deflection
+    if config.TEXT_VERBOSE :
+        print "Bump %g nm" % push_depth
+    data = zpiezo.ramp(out, freq)
+    return data
+
+@splittableKwargsFunction(bump_aquire,
+                          (bump_analyze.bump_save, 'data'),
+                          (bump_analyze.bump_analyze, 'data'))
+def bump(**kwargs):
+    """
+    Wrapper around bump_aquire(), bump_save(), bump_analyze()
+    """
+    bump_aquire_kwargs,bump_save_kwargs,bump_analyze_kwargs = \
+        bump._splitargs(bump, kwargs)
+    data = bump_aquire(**bump_aquire_kwargs)
+    bump_analyze.bump_save(data, **bump_save_kwargs)
+    photoSensitivity = bump_analyze.bump_analyze(data, **bump_analyze_kwargs)
+    return photoSensitivity
+
+# T family.
+# Fairly stubby, since a one shot Temp measurement is a common thing.
+# We just wrap that to provide a consistent interface.
+
+@splittableKwargsFunction()
+def T_aquire(get_T=None) :
+    """
+    Measure the current temperature of the sample, 
+    or, if get_T == None, fake it by returning config.DEFAULT_TEMP
+    """
+    if get_T == None :
+        if config.TEXT_VERBOSE :
+            print "Fake temperature %g" % config.DEFAULT_TEMP
+        return config.DEFAULT_TEMP
+    else :
+        if config.TEXT_VERBOSE :
+            print "Measure temperature"
+        return get_T()
+
+@splittableKwargsFunction(T_aquire,
+                          (T_analyze.T_save, 'T'),
+                          (T_analyze.T_analyze, 'T'))
+def T(**kwargs):
+    """
+    Wrapper around T_aquire(), T_save(), T_analyze(), T_plot()
+    """
+    T_aquire_kwargs,T_save_kwargs,T_analyze_kwargs = \
+        T._splitargs(T, kwargs)
+    T_raw = T_aquire(**T_aquire_kwargs)
+    T_analyze.T_save(T_raw, **T_save_kwargs)
+    T_ret = T_analyze.T_analyze(T_raw, **T_analyze_kwargs)
+    return T_ret
+
+# vib family
+
+@splittableKwargsFunction()
+def vib_aquire(zpiezo, time=1, freq=50e3) :
+    """
+    Record data for TIME seconds at FREQ Hz from ZPIEZO at it's current position.
+    """
+    # round up to the nearest power of two, for efficient FFT-ing
+    nsamps = ceil_pow_of_two(time*freq)
+    time = nsamps / freq
+    # take some data, keeping the position voltage at it's current value
+    out = ones((nsamps,), dtype=uint16) * zpiezo.curPos()
+    if config.TEXT_VERBOSE :
+        print "get %g seconds of data" % time
+    data = zpiezo.ramp(out, freq)
+    data['sample frequency Hz'] = freq
+    return data
+
+@splittableKwargsFunction(vib_aquire,
+                          (vib_analyze.vib_save, 'data'),
+                          (vib_analyze.vib_analyze, 'deflection_bits', 'freq'))
+def vib(**kwargs) :
+    """
+    Wrapper around vib_aquire(), vib_save(), vib_analyze()
+    """
+    vib_aquire_kwargs,vib_save_kwargs,vib_analyze_kwargs = \
+        vib._splitargs(vib, kwargs)
+    data = vib_aquire(freq=freq, **vib_aquire_kwargs)
+    vib_analyze.vib_save(data, **vib_save_kwargs)
+    freq = data['sample frequency Hz']
+    Vphoto_var = vib_analyze.vib_analyze(deflection_bits=data, freq=freq,
+                                         **vib_analyze_kwargs)
+    return Vphoto_var
+
+# A few positioning functions, so we can run bump_aquire() and vib_aquire()
+# with proper spacing relative to the surface.
+
+@splittableKwargsFunction()
+def move_just_onto_surface(stepper, zpiezo, Depth_nm=100, setpoint=2) :
+    """
+    Uses z_piezo_utils.getSurfPos() to pinpoint the position of the surface.
+    Adjusts the stepper position as required to get within stepper_tol nm
+    of the surface.
+    Then set Vzp to place the cantilever Depth_nm onto the surface.
+
+    If getSurfPos() fails to find the surface, backs off (for safety)
+    and steps in (without moving the zpiezo) until Vphoto > setpoint.
+    """
+    stepper_tol = 250 # nm, generous estimate of the fullstep stepsize
+
+    if config.TEXT_VERBOSE :
+        print "moving just onto surface"
+    # Zero the piezo
+    if config.TEXT_VERBOSE :
+        print "zero the z piezo output"
+    zpiezo.jumpToPos(zpiezo.pos_nm2out(0))
+    # See if we're near the surface already
+    if config.TEXT_VERBOSE :
+        print "See if we're starting near the surface"
+    try :
+        dist = zpiezo.pos_out2nm( \
+            z_piezo_utils.getSurfPos(zpiezo, zpiezo.def_V2in(setpoint))
+                                )
+    except (z_piezo_utils.tooClose, z_piezo_utils.poorFit), string :
+        if config.TEXT_VERBOSE :
+            print "distance failed with: ", string
+            print "Back off 200 half steps"
+        # Back away 200 steps
+        stepper.step_rel(-400)
+        stepper.step_rel(200)
+        sp = zpiezo.def_V2in(setpoint) # sp = setpoint in bits
+        zpiezo.updateInputs()
+        cd = zpiezo.curDef()           # cd = current deflection in bits
+        if config.TEXT_VERBOSE :
+            print "Single stepping approach"
+        while cd < sp :
+            if config.TEXT_VERBOSE :
+                print "deflection %g < setpoint %g.  step closer" % (cd, sp)
+            stepper.step_rel(2) # Full step in
+            zpiezo.updateInputs()
+            cd = zpiezo.curDef()
+        # Back off two steps (protecting against backlash)
+        if config.TEXT_VERBOSE :
+            print "Step back 4 half steps to get off the setpoint"
+        stepper.step_rel(-200)
+        stepper.step_rel(196)
+        # get the distance to the surface
+        zpiezo.updateInputs()
+        if config.TEXT_VERBOSE :
+            print "get surf pos, with setpoint %g (%d)" % (setpoint, zpiezo.def_V2in(setpoint))
+        for i in range(20) : # HACK, keep stepping back until we get a distance
+            try :
+                dist = zpiezo.pos_out2nm(getSurfPos(zpiezo, zpiezo.def_V2in(setpoint)))
+            except (tooClose, poorFit), string :
+                stepper.step_rel(-200)
+                stepper.step_rel(198)
+                continue
+            break
+        if i >= 19 :
+            print "tried %d times, still too close! bailing" % i
+            print "probably an invalid setpoint."
+            raise Exception, "weirdness"
+    if config.TEXT_VERBOSE :
+        print 'distance to surface ', dist, ' nm'
+    # fine tune the stepper position
+    while dist < -stepper_tol : # step back if we need to
+        stepper.step_rel(-200)
+        stepper.step_rel(198)
+        dist = zpiezo.pos_out2nm(getSurfPos(zpiezo, zpiezo.def_V2in(setpoint)))
+        if config.TEXT_VERBOSE :
+            print 'distance to surface ', dist, ' nm, step back'
+    while dist > stepper_tol : # and step forward if we need to
+        stepper.step_rel(2)
+        dist = zpiezo.pos_out2nm(getSurfPos(zpiezo, zpiezo.def_V2in(setpoint)))
+        if config.TEXT_VERBOSE :
+            print 'distance to surface ', dist, ' nm, step closer'
+    # now adjust the zpiezo to place us just onto the surface
+    target = dist + Depth_nm
+    zpiezo.jumpToPos(zpiezo.pos_nm2out(target))
+    # and we're there :)
+    if config.TEXT_VERBOSE :
+        print "We're %g nm into the surface" % Depth_nm
+
+@splittableKwargsFunction()
+def move_far_from_surface(stepper, um_back=50) :
+    """
+    Step back a specified number of microns.
+    (uses very rough estimate of step distance at the moment)
+    """
+    step_nm = 100
+    steps = int(um_back*1000/step_nm)
+    print "step back %d steps" % steps
+    stepper.step_rel(-steps)
+
+
+# and finally, the calib family
+
+@splittableKwargsFunction((move_just_onto_surface, 'stepper', 'zpiezo'),
+                          (bump, 'zpiezo', 'freq', 'log_dir', 'Vphoto_in2V'),
+                          (move_far_from_surface, 'stepper'),
+                          (T, 'log_dir'),
+                          (vib, 'zpiezo', 'log_dir', 'Vphoto_in2V'),
+                          (analyze.calib_save, 'bumps','Ts','vibs','log_dir'))
+def calib_aquire(stepper, zpiezo, num_bumps=10, num_Ts=10, num_vibs=20,
+                 bump_freq=100e3,
+                 log_dir=config.LOG_DATA, Vphoto_in2V=config.Vphoto_in2V,
+                 **kwargs):
+    """
+    Aquire data for calibrating a cantilever in one function.
+    return (bump, T, vib), each of which is an array.
+    Inputs :
+     stepper       a stepper.stepper_obj for coarse Z positioning
+     zpiezo        a z_piezo.z_piezo for fine positioning and deflection readin
+     setpoint      maximum allowed deflection (in Volts) during approaches
+     num_bumps     number of 'a's (see Outputs)
+     push_depth_nm depth of each push when generating a
+     num_temps     number of 'T's (see Outputs)
+     num_vibs      number of 'vib's (see Outputs)
+     log_dir       directory to log data to.  Default 'None' disables logging.
+    Outputs (all are arrays of recorded data) :
+     bumps measured (V_photodiode / nm_tip) proportionality constant
+     Ts    measured temperature (K)
+     vibs  measured V_photodiode variance in free solution
+    """
+    move_just_onto_surface_kwargs,bump_kwargs,move_far_from_surface_kwargs, \
+        T_kwargs,vib_kwargs,calib_save_kwargs = \
+        calib_aquire._splitargs(calib_aquire, kwargs)
+    # get bumps
+    move_just_onto_surface(stepper, zpiezo, **move_just_onto_surface_kwargs)
+    bumps=zeros((num_bumps,))
+    for i in range(num_bumps) :
+        bumps[i] = bump(zpiezo, freq=bump_freq, log_dir=log_dir,
+                        Vphot_in2V=Vphoto_in2V, **bump_kwargs)
+    if config.TEXT_VERBOSE :
+        print bumps
+
+    move_far_from_surface(stepper, **move_far_from_surface_kwargs)
+
+    # get Ts
+    Ts=zeros((num_Ts,), dtype=float)
+    for i in range(num_Ts) :
+        Ts[i] = T(**T_kwargs)
+        time.sleep(1) # wait a bit to get an independent temperature measure
+    print Ts
+
+    # get vibs
+    vibs=zeros((num_vibs,))
+    for i in range(num_vibs) :
+        vibs[i] = vib(zpiezo, log_dir=log_dir, Vphoto_in2V=Vphoto_in2V,
+                      **vib_kwargs)
+    print vibs
+    
+    analyze.calib_save(bumps, Ts, vibs, log_dir, **calib_save_kwargs)
+    
+    return (bumps, Ts, vibs)
+
+
+@splittableKwargsFunction( \
+    (calib_aquire, 'log_dir'),
+    (analyze.calib_analyze, 'bumps','Ts','vibs'))
+def calib(log_dir=None, **kwargs) :
+    """
+    Calibrate a cantilever in one function.
+    The I-don't-care-about-the-details black box version :p.
+    return (k, k_s)
+    Inputs:
+     (see calib_aquire()) 
+    Outputs :
+     k    cantilever spring constant (in N/m, or equivalently nN/nm)
+     k_s  standard deviation in our estimate of k
+    Notes :
+     See get_calibration_data() for the data aquisition code
+     See analyze_calibration_data() for the analysis code
+    """
+    calib_aquire_kwargs,calib_analyze_kwargs = \
+        calib._splitargs(calib, kwargs)
+    a, T, vib = calib_aquire(**calib_aquire_kwargs)
+    k,k_s,ps2_m, ps2_s,T_m,T_s,one_o_Vp2_m,one_o_Vp2_s = \
+        analyze.calib_analyze(a, T, vib, log_dir=log_dir,
+                              **calib_analyze_kwargs)
+    analyze.calib_save_analysis(k, k_s, ps2_m, ps2_s, T_m, T_s,
+                                one_o_Vp2_m, one_o_Vp2_s, log_dir)
+    return (k, k_s)
+
+    
+
diff --git a/calibcant/calibrate_cantilever.py b/calibcant/calibrate_cantilever.py
deleted file mode 100644 (file)
index 6721605..0000000
+++ /dev/null
@@ -1,911 +0,0 @@
-#!/usr/bin/python
-
-"""
-Aquire and analyze cantilever calibration data.
-
-W. Trevor King Dec. 2007-Jan. 2008
-
-GPL BOILERPLATE
-
-
-The relevent physical quantities are :
- Vzp_out  Output z-piezo voltage (what we generate)
- Vzp      Applied z-piezo voltage (after external ZPGAIN)
- Zp       The z-piezo position
- Zcant    The cantilever vertical deflection
- Vphoto   The photodiode vertical deflection voltage (what we measure)
- Fcant    The force on the cantilever
- T        The temperature of the cantilever and surrounding solution
-          (another thing we measure)
- k_b      Boltzmann's constant
-
-Which are related by the parameters :
- zpGain           Vzp_out / Vzp
- zpSensitivity    Zp / Vzp
- photoSensitivity Vphoto / Zcant
- k_cant           Fcant / Zcant
-
-Cantilever calibration assumes a pre-calibrated z-piezo (zpSensitivity) and
-a amplifier (zpGain).
-In our lab, the z-piezo is calibrated by imaging a calibration sample,
-which has features with well defined sizes, and the gain is set with a knob
-on the Nanoscope.
-
-photoSensitivity is measured by bumping the cantilever against the surface,
-where Zp = Zcant (see the bump_*() family of functions)
-The measured slope Vphoto/Vout is converted to photoSensitivity via
-Vphoto/Vzp_out * Vzp_out/Vzp  * Vzp/Zp   *    Zp/Zcant =    Vphoto/Zcant
- (measured)      (1/zpGain) (1/zpSensitivity)    (1)  (photoSensitivity)
-
-k_cant is measured by watching the cantilever vibrate in free solution
-(see the vib_*() family of functions)
-The average energy of the cantilever in the vertical direction is given
-by the equipartition theorem.
-    1/2 k_b T   =   1/2 k_cant Zcant**2
- so     k_cant  = k_b T / Zcant**2
- but    Zcant   = Vphoto / photoSensitivity
- so     k_cant  = k_b T * photoSensitivty**2 / Vphoto**2
-We measured photoSensitivity above.
-We can either measure T using an external function (see temperature.py),
-or just estimate it (see the T_*() family of functions).
- (room temp ~22 deg C, accurate to +/- 5 deg, so 5/273.15 ~= 2%)
-Finally, a time series of Vphoto while we're far from the surface and not
-changing Vzp_out will give us Vphoto.
-
-We do all these measurements a few times to estimate statistical errors.
-
-The functions are layed out in the families:
- bump_*(), vib_*(), T_*(), and calib_*()
-where calib_{save|load|analyze}() deal with derived data, not real-world data.
-For each family, * can be any of :
- aquire       get real-world data
- save         store real-world data to disk
- load         get real-world data from disk
- analyze      interperate the real-world data.
- plot         show a nice graphic to convince people we're working :p
- load_analyze_tweaked
-              read a file with a list of paths to previously saved real world data
-              load each file using *_load(), analyze using *_analyze(), and
-              optionally plot using *_plot().
-              Intended for re-processing old data.
-A family name without any _* extension (e.g. bump()),
- runs *_aquire(), *_save(), *_analyze(), *_plot().
-
-Also defines the two positioning functions:
- move_just_onto_surface() and move_far_from_surface()
-"""
-
-import numpy
-import time 
-import data_logger
-import z_piezo_utils
-import FFT_tools
-import linfit
-import GnuplotBiDir # used for fitting lorentzian
-
-kb = 1.3806504e-23 # Boltzmann's constant in J/K
-DEFAULT_TEMP = 22  # assume 22 deg C
-
-LOG_DATA = True  # quietly grab all real-world data and log to LOG_DIR
-LOG_DIR = '/home/wking/rsrch/data/calibrate_cantilever'
-
-GNUFIT_DATA_BASE='./calibrate_cantilever_fitdata'
-
-TEXT_VERBOSE = True      # for debugging
-GNUPLOT_VERBOSE = True     # turn on fit check plotting
-PYLAB_VERBOSE = True     # turn on plotting
-PYLAB_INTERACTIVE = True # select between draw() and show() for flushing plots
-BASE_FIGNUM = 20 # to avoid writing to already existing figures
-
-# handle extra verbose input modules, only imported if we need them
-_flush_plot = None
-_final_flush_plot = None
-_pylab = None
-def _import_pylab() :
-    "Import pylab plotting functions for when we need to plot"
-    global _pylab
-    global _flush_plot
-    global _final_flush_plot
-    if _pylab == None :
-        import pylab as _pylab
-    if _flush_plot == None :
-        if PYLAB_INTERACTIVE :
-            _flush_plot = _pylab.draw
-        else :
-            def _flush_plot () : pass
-    if _final_flush_plot == None :
-        if PYLAB_INTERACTIVE :
-            _final_flush_plot = _pylab.draw
-        else :
-            _final_flush_plot = _pylab.show
-
-
-# HACK
-# make sure you make a system note (add_system_note) if you change these
-# in case you don't have access to a z_piezo.z_piezo for conversion functions
-_usual_zpGain=20
-_usual_zpSensitivity=7.41
-_usual_zeroVphoto_bits = 2**15
-_usual_zeroVzp_bits = 2**15
-def _usual_Vphoto_in2V(inp) :
-    return (float(inp)-float(_usual_zeroVphoto_bits))*(10.0/2**15)
-def _usual_Vzp_out2V(inp) :
-    return (float(inp)-float(_usual_zeroVzp_bits))*(10.0/2**15)
-
-def C_to_K(celsius) :
-    "Convert Celsius -> Kelvin."
-    return celsius + 273.15
-
-# bump family
-
-def bump_aquire(zpiezo, push_depth, npoints, freq) :
-    """
-    Ramps closer push_depth and returns to the original position.
-    Inputs:
-     zpiezo     an opened zpiezo.zpiezo instance
-     push_depth distance to approach, in nm
-     npoints    number points during the approach and during the retreat
-     freq       rate at which data is aquired
-     log_dir    directory to log data to (see data_logger.py).
-                None to turn off logging (see also the global LOG_DATA).
-    Returns the aquired ramp data dictionary, with data in DAC/ADC bits.
-    """
-    # generate the bump output
-    start_pos = zpiezo.curPos()
-    pos_dist = zpiezo.pos_nm2out(push_depth) - zpiezo.pos_nm2out(0)
-    close_pos = start_pos + pos_dist
-    appr = linspace(start_pos, close_pos, npoints)
-    retr = linspace(close_pos, start_pos, npoints)
-    out = concatenate((appr, retr))
-    # run the bump, and measure deflection
-    if TEXT_VERBOSE :
-        print "Bump %g nm" % push_depth
-    data = zpiezo.ramp(out, freq)
-    # default saving, so we have a log in-case the operator is lazy ;)
-    if LOG_DATA == True :
-        log = data_logger.data_log(LOG_DIR, noclobber_logsubdir=False,
-                                   log_name="bump_surface")
-        log.write_dict_of_arrays(data)
-    return data
-
-def bump_save(data, log_dir) :
-    "Save the dictionary data, using data_logger.data_log()"
-    if log_dir != None :
-        log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
-                                   log_name="bump")
-        log.write_dict_of_arrays(data)
-
-def bump_load(datafile) :
-    "Load the dictionary data, using data_logger.date_load()"
-    dl = data_logger.data_load()
-    data = dl.read_dict_of_arrays(path)
-    return data
-
-def bump_analyze(data, zpGain=_usual_zpGain,
-                 zpSensitivity=_usual_zpSensitivity,
-                 Vzp_out2V=_usual_Vzp_out2V,
-                 Vphoto_in2V=_usual_Vphoto_in2V,
-                 plotVerbose=False) :
-    """
-    Return the slope of the bump ;).
-    Inputs:
-      data       dictinary of data in DAC/ADC bits
-      pos_out2V  function that converts output DAC bits to Volts
-      def_in2V   funtion that converts input ADC bits to Volts
-      zpGain     zpiezo applied voltage per output Volt
-      zpSensitivity  nm zpiezo response per applied Volt
-    Returns:
-     photoSensitivity (Vphoto/Zcant) in Volts/nm
-    Checks for strong correlation (r-value) and low randomness chance (p-value)
-    
-    With the current implementation, the data is regressed in DAC/ADC bits
-    and THEN converted, so we're assuming that both conversions are LINEAR.
-    if they aren't, rewrite to convert before the regression.
-    """
-    scale_Vzp_bits2V = Vzp_out2V(1) - Vzp_out2V(0)
-    scale_Vphoto_bits2V = Vzp_in2V(1) - Vphoto_in2V(0)
-    Vphoto2Vzp_out_bit, intercept = \
-        linfit.linregress(x=data["Z piezo output"],
-                          y=data["Deflection input"],
-                          plotVerbose=plotVerbose)
-    Vphoto2Vzp_out = Vphoto2Vzp_out * scale_Vphoto_bits2V / scale_Vzp_bits2V
-
-    #               1 / (Vzp/Vzp_out  *       Zp/Vzp           * Zcant/Zp )
-    Vzp_out2Zcant = 1.0/ (zpiezo_gain  * zpiezo_sensitivity) # * 1
-    return Vphoto2Vzp_out * Vzp_out2Zcant
-
-def bump_plot(data, plotVerbose) :
-    "Plot the bump (Vphoto vs Vzp) if plotVerbose or PYLAB_VERBOSE == True"
-    if plotVerbose or PYLAB_VERBOSE :
-        _import_pylab()
-        _pylab.figure(BASE_FIGNUM)
-        _pylab.plot(data["Z piezo output"], data["Deflection input"],
-                    '.', label='bump')
-        _pylab.title("bump surface")
-        _pylab.legend(loc='upper left')
-        _flush_plot()
-
-def bump(zpiezo, push_depth, npoints=1024, freq=100e3,
-         log_dir=None,
-         plotVerbose=False) :
-    """
-    Wrapper around bump_aquire(), bump_save(), bump_analyze(), bump_plot()
-    """
-    data = bump_aquire(zpiezo, push_depth, npoints, freq)
-    bump_save(data, log_dir)
-    photoSensitivity = bump_analyze(data, zpiezo.gain, zpiezo.sensitivity,
-                                    zpiezo.pos_out2V, zpiezo.def_in2V)
-    bump_plot(data, plotVerbose)
-    return photoSensitivity
-
-def bump_load_analyze_tweaked(tweak_file, zpGain=_usual_zpGain,
-                              zpSensitivity=_usual_zpSensitivity,
-                              Vzp_out2V=_usual_Vzp_out2V,
-                              Vphoto_in2V=_usual_Vphoto_in2V,
-                              plotVerbose=False) :
-    "Load the output file of tweak_calib_bump.sh, return an array of slopes"
-    photoSensitivity = []
-    for line in file(tweak_file, 'r') :
-        parsed = line.split()
-        path = parsed[0].split('\n')[0]
-        # read the data
-        full_data = bump_load(path)
-        if len(parsed) == 1 :
-            data = full_data # use whole bump
-        else :
-            # use the listed sections
-            zp = []
-            df = []
-            for rng in parsed[1:] :
-                p = rng.split(':')
-                starti = int(p[0])
-                stopi = int(p[1])
-                zp.extend(full_data['Z piezo output'][starti:stopi])
-                df.extend(full_data['Deflection input'][starti:stopi])
-            data = {'Z piezo output': array(zp),
-                    'Deflection input':array(df)}
-        pSi = bump_analyze(data, zpGain, zpSensitivity,
-                           Vzp_out2V, Vphoto_in2V, plotVerbose)
-        photoSensitivity.append(pSi)
-        bump_plot(data, plotVervose)
-    return array(photoSensitivity, dtype=numpy.float)
-
-# T family.
-# Fairly stubby, since a one shot Temp measurement is a common thing.
-# We just wrap that to provide a consistent interface.
-
-def T_aquire(get_T=None) :
-    """
-    Measure the current temperature of the sample, 
-    or, if get_T == None, fake it by returning DEFAULT_TEMP
-    """
-    if get_T == None :
-        if TEXT_VERBOSE :
-            print "Fake temperature %g" % DEFAULT_TEMP
-        return DEFAULT_TEMP
-    else :
-        if TEXT_VERBOSE :
-            print "Measure temperature"
-        return get_T()
-
-def T_save(T, log_dir=None) :
-    """
-    Save either a single T (if you are crazy :p),
-    or an array of them (more reasonable)
-    """
-    T = numpy.array(T, dtype=numpy.float)
-    if log_dir != None :
-        log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
-                                   log_name="T_float")
-        log.write_binary(T.tostring())
-    if LOG_DATA != None :
-        log = data_logger.data_log(LOG_DIR, noclobber_logsubdir=False,
-                                   log_name="T_float")
-        log.write_binary(T.tostring())
-        
-def T_load(datafile=None) :
-    """
-    Load the saved T array (possibly of length 1), and return it.
-    If datafile == None, return an array of one DEFAULT_TEMP instead.
-    """
-    if datafile == None :
-        return numpy.array([DEFAULT_TEMP], dtype=numpy.float)
-    else :
-        return fromfile(file=cleanpath, dtype=numpy.float)
-
-def T_analyze(T, convert_to_K=C_to_K) :
-    """
-    Not much to do here, just convert to Kelvin.
-    Uses C_to_K (defined above) by default.
-    """
-    try : # if T is an array, convert element by element
-        for i in range(len(T)) :
-            T[i] = convert_to_K(T[i])
-    except TypeError :
-        T = convert_to_K(T)
-    return T
-
-def T_plot(T, plotVerbose) :
-    """
-    Umm, just print the temperature?
-    """
-    if plotVerbose or PYLAB_VERBOSE or TEXT_VERBOSE :
-        print "Temperature ", T
-
-def T(get_T=None, convert_to_K=C_to_K, log_dir=None, plotVerbose=None) :
-    """
-    Wrapper around T_aquire(), T_save(), T_analyze(), T_plot()
-    """
-    T_raw = T_aquire(get_T)
-    T_save(T_raw, log_dir)
-    T_ret = T_analyze(T_raw, convert_to_K)
-    T_plot(T_raw, plotVerbose)
-    return T_ret
-
-def T_load_analyze_tweaked(tweak_file=None, convert_to_K=C_to_K,
-                           plotVerbose=False) :
-    "Read the T files listed in tweak_file, return an array of Ts in Kelvin"
-    if tweak_file != None :
-        Tlist=[]
-        for filepath in file(datafile, 'r') :
-            cleanpath = filepath.split('\n')[0]
-            Tlist.extend(T_load(cleanpath))
-        Tlist = numpy.array(Tlist, dtype=numpy.float)
-        T_raw = array(Tlist, dtype=numpy.float)
-        for Ti in T_raw :
-            T_plot(T, plotVerbose)
-    else :
-        T_raw = T_load(None)
-    T = T_analyze(T_raw, convert_to_K)
-    return T
-
-# vib family
-
-def vib_aquire(zpiezo, time, freq) :
-    """
-    Record data for TIME seconds at FREQ Hz from ZPIEZO at it's current position.
-    """
-    # round up to the nearest power of two, for efficient FFT-ing
-    nsamps = ceil_pow_of_two(time*freq)
-    time = nsamps / freq
-    # take some data, keeping the position voltage at it's current value
-    out = ones((nsamps,), dtype=uint16) * zpiezo.curPos()
-    if TEXT_VERBOSE :
-        print "get %g seconds of data" % time
-    data = zpiezo.ramp(out, freq)
-    if LOG_DATA :
-        log = data_logger.data_log(LOG_DIR, noclobber_logsubdir=False,
-                                   log_name="vib_%gHz" % freq)
-        log.write_dict_of_arrays(data)
-    return data
-
-def vib_save(data, log_dir=None) :
-    "Save the dictionary data, using data_logger.data_log()"
-    if log_dir :
-        log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
-                                   log_name="vib_%gHz" % freq)
-        log.write_dict_of_arrays(data)
-
-def vib_load(datafile=None) :
-    "Load the dictionary data, using data_logger.date_load()"
-    dl = data_logger.data_load()
-    data = dl.read_dict_of_arrays(path)
-    return data
-
-def vib_plot(data, freq,
-             chunk_size=2048, overlap=True, window=FFT_tools.window_hann,
-             plotVerbose=True) :
-    """
-    If plotVerbose or PYLAB_VERBOSE == True, plot 3 subfigures:
-     Time series (Vphoto vs sample index) (show any major drift events),
-     A histogram of Vphoto, with a gaussian fit (demonstrate randomness), and
-     FFTed Vphoto data (Vphoto vs Frequency) (show major noise components).
-    """
-    if plotVerbose or PYLAB_VERBOSE :
-        _import_pylab()
-        _pylab.hold(False)
-        _pylab.figure(BASE_FIGNUM+2)
-
-        # plot time series
-        _pylab.subplot(311)
-        _pylab.plot(data["Deflection input"], 'r.')
-        _pylab.title("free oscillation")
-
-        # plot histogram distribution and gaussian fit
-        _pylab.subplot(312)
-        n, bins, patches = \
-            _pylab.hist(data["Deflection input"], bins=30,
-                        normed=1, align='center')
-        gaus = numpy.zeros((len(bins),))
-        mean = data["Deflection input"].mean()
-        std = data["Deflection input"].std()
-        pi = numpy.pi
-        exp = numpy.exp
-        for i in range(len(bins)) :
-            gaus[i] = (2*pi)**(-.5)/std * exp(-0.5*((bins[i]-mean)/std)**2)
-        _pylab.hold(True)
-        _pylab.plot(bins, gaus, 'r-');
-        _pylab.hold(False)
-
-        # plot FFTed data
-        _pylab.subplot(313)
-        AC_data = data["Deflection input"] - mean
-        (freq_axis, power) = \
-            FFT_tools.unitary_avg_power_spectrum(AC_data, freq, chunk_size,
-                                                 overlap, window)
-        _pylab.semilogy(freq_axis, power, 'r.-')
-        _pylab.hold(True)
-        filtered_power = FFT_tools.windowed_filter(power,
-                            FFT_tools.windowed_median_point, s=5)
-        _pylab.semilogy(freq_axis, filtered_power, 'b.-')
-        _flush_plot()
-
-def vib_analyze_naive(data, Vphoto_in2V=_usual_Vphoto_in2V,
-                      zeroVphoto_bits=_usual_zeroVphoto_bits,
-                      plotVerbose=False) :
-    """
-    Calculate the variance of the raw data, and convert it to Volts**2.
-    This method is simple and easy to understand, but it highly succeptible
-    to noise, drift, etc.
-    
-    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 = data["Deflection input"].std()
-    Vphoto_std = Vphoto_in2V(Vphoto_std_bits + zpiezo_zeroV_in)
-    return Vphoto_std**2
-    
-def vib_analyze(data, freq, minFreq=500, maxFreq=7000,
-                chunk_size=4096, overlap=True, window=FFT_tools.window_hann,
-                median_filter_width=5,
-                Vphoto_in2V=_usual_Vphoto_in2V, plotVerbose=False) :
-    """
-    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).
-
-    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.
-    """
-    Vphoto_t = numpy.zeros((len(data["Deflection input"]),),
-                           dtype=numpy.float)
-    # convert the data from bits to volts
-    if TEXT_VERBOSE : 
-        print "Converting %d bit values to voltages" % len(Vphoto_t)
-    for i in range(len(data["Deflection input"])) :
-        Vphoto_t[i] = Vphoto_in2V(data["Deflection input"][i])
-
-    # Compute the average power spectral density per unit time (in uV**2/Hz)
-    if TEXT_VERBOSE : 
-        print "Compute the averaged power spectral density in uV**2/Hz"
-    (freq_axis, power) = \
-        FFT_tools.unitary_avg_power_spectrum((Vphoto_t - Vphoto_t.mean())*1e6,
-                                             freq, chunk_size,overlap, window)
-
-    # cut out the relevent frequency range
-    if TEXT_VERBOSE : 
-        print "Cut the frequency range %g to %g Hz" % (minFreq, maxFreq)
-    imin = 0
-    while freq_axis[imin] < minFreq : imin += 1
-    imax = imin
-    while freq_axis[imax] < maxFreq : imax += 1
-    assert imax >= imin + 10 , \
-        "Less than 10 points in freq range (%g,%g)" % (minFreq, maxFreq)
-
-    # We don't need to filter, because taking data is so cheap...
-    # median filter the relevent range
-    #filtered_power = FFT_tools.windowed_filter(power[imin:imax],
-    #                     FFT_tools.windowed_median_point,
-    #                     s=median_filter_width)
-    filtered_power = power[imin:imax]
-
-    # check
-    if (plotVerbose or PYLAB_VERBOSE) and False :
-        if TEXT_VERBOSE : 
-            print "Plot the FFT data for checking"
-        vib_plot(data, freq, chunk_size, overlap, window, plotVerbose)
-        _import_pylab()
-        _pylab.figure(5)
-        #_pylab.semilogy(freq_axis, power, 'r.-')
-        #print "len ", len(freq_axis), len(power), freq_axis.min(), filtered_power.min()
-        _pylab.semilogy(freq_axis, power, 'r.-',
-                        freq_axis[imin:imax], power[imin:imax], 'b.-',
-                        freq_axis[imin:imax], filtered_power, 'g.-')
-        _flush_plot()
-    
-    # save about-to-be-fitted data to a temporary file
-    if TEXT_VERBOSE : 
-        print "Save data in temp file for fitting"
-    datafilename = "%s_%d" % (GNUFIT_DATA_BASE, time.time())
-    fd = file(datafilename, 'w')
-    for i in range(imin, imax) :
-        fd.write("%g\t%g\n" % (freq_axis[i], filtered_power[i-imin]))
-    fd.close()
-
-    # generate guesses for Lorentzian parameters A,B,C
-    if TEXT_VERBOSE : 
-        print "Fit lorentzian"
-    max_fp_index = numpy.argmin(filtered_power)
-    max_fp = filtered_power[max_fp_index]
-    half_max_index = 0
-    while filtered_power[half_max_index] < max_fp/2 :
-        half_max_index += 1
-    # Lorentzian L(x) = c / ((a**2-x**2)**2 + (b*x)**2)
-    # peak centered at a, so
-    A_guess = freq_axis[max_fp_index+imin]
-    # Half width w on lower side when L(a-w) = L(a)/2
-    #  (a**2 - (a-w)**2)**2 + (b*(a-w))**2 = 2*(b*a)**2
-    # Let W=(a-w)**2, A=a**2, and B=b**2
-    #  (A - W)**2 + BW = 2*AB
-    #  W**2 - 2AW + A**2 + BW = 2AB
-    #  W**2 + (B-2A)W + (A**2-2AB) = 0
-    #  W = (2A-B)/2 * [1 +/- sqrt(1 - 4(A**2-2AB)/(B-2A)**2]
-    #    = (2A-B)/2 * [1 +/- sqrt(1 - 4A(A-2B)/(B-2A)**2]
-    #  (a-w)**2 = (2A-B)/2 * [1 +/- sqrt(1 - 4A(A-2B)/(B-2A)**2]
-    #  so w is a disaster ;)
-    # For some values of A and B (non-underdamped), W is imaginary or negative.
-    B_guess = A_guess/2
-    # for underdamped, max value at x ~= a, where f(a) = c/(ab)**2, so
-    C_guess = max_fp * (A_guess*B_guess)**2
-
-    # fit Lorentzian using Gnuplot's 'fit' command
-    g = GnuplotBiDir.Gnuplot()
-    # The Lorentzian is the predicted one-sided power spectral density
-    # For an oscillator whose position x obeys
-    # m x'' + gamma x' + kx = F_thermal(t)
-    #  A is the ?driving noise?
-    #  B is gamma, the damping term
-    #  C is omega_0, the resonant frequency
-    # Fitting the PSD of V = photoSensitivity*x just rescales the parameters
-    g("f(x) = C / ((A**2-x**2)**2 + (x*B)**2)")
-    ## The mass m is given by m = k_B T / (pi A)
-    ## The spring constant k is given by k = m (omega_0)**2
-    ## The quality factor Q is given by Q = omega_0 m / gamma
-    g("A=%g; B=%g; C=%g" % (A_guess, B_guess, C_guess))
-    g("fit f(x) '%s' via A,B,C" % datafilename)
-    A=float(g.getvar('A'))
-    B=float(g.getvar('B'))
-    C=float(g.getvar('C'))
-    if 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)
-    if plotVerbose or GNUPLOT_VERBOSE :
-        if 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)
-        # write all the ft data now
-        fd = file(datafilename, 'w')
-        for i in range(len(freq_axis)) :
-            fd.write("%g\t%g\n" % (freq_axis[i], power[i]))
-        fd.write("\n") # blank line to separate fit data.
-        # write the fit data
-        for i in range(imin, imax) :
-            x = freq_axis[i]
-            fd.write("%g\t%g\n" % (freq_axis[i],
-                                   C / ((A**2-x**2)**2 + (x*B)**2) ) )
-        fd.close()
-        print Vphoto_t.var(), A, B, C, ((numpy.pi * C)/(2 * B * A**2) * 1e-12)
-        g("set terminal epslatex color solid")
-        g("set output 'calibration.tex")
-        g("set size 2,2") # multipliers on default 5"x3.5"
-        #g("set title 'Thermal calibration'")
-        g("set logscale y")
-        g("set xlabel 'Frequency (Hz)'")
-        g("set ylabel 'Power ($\mu$V$^2$/Hz)'")
-        g("set xrange [0:20000]")
-        g("g(x) = x < %g || x > %g ? 1/0 : f(x)" % (minFreq, maxFreq)) # only plot on fitted region
-        g("plot '%s' title 'Vibration data', g(x) title 'Lorentzian fit'" % datafilename)
-
-        g("set mouse")
-        g("pause mouse 'click with mouse'")
-        g.getvar("MOUSE_BUTTON")
-        del(g)
-
-    # Integrating the the power spectral density per unit time (power) over the
-    # frequency axis [0, fN] returns the total signal power per unit time
-    #  int_0^fN power(f)df = 1/T int_0^T |x(t)**2| dt
-    #                      = <V(t)**2>, the variance for our AC signal.
-    # The variance from our fitted Lorentzian is the area under the Lorentzian
-    #  <V(t)**2> = (pi*C) / (2*B*A**2)
-    # Our A is in uV**2, so convert back to Volts
-    return (numpy.pi * C) / (2 * B * A**2) * 1e-12
-
-def vib(zpiezo, time=1, freq=50e3, log_dir=None, plotVerbose=False) :
-    """
-    Wrapper around vib_aquire(), vib_save(), vib_analyze(), vib_plot()
-    """
-    data = vib_aquire(zpiezo, time, freq)
-    vib_save(data, log_dir)
-    Vphoto_var = vib_analyze(data, freq)
-    vib_plot(data, plotVerbose)
-    return Vphoto_var
-
-def vib_load_analyze_tweaked(tweak_file, minFreq=1000, maxFreq=7000,
-                             chunk_size=2048, overlap=True,
-                             window=FFT_tools.window_hann,
-                             median_filter_width=5,
-                             Vphoto_in2V=_usual_Vphoto_in2V,
-                             plotVerbose=False) :
-    """
-    Read the vib files listed in tweak_file.
-    Return an array of Vphoto variances (due to the cantilever) in Volts**2
-    """
-    dl = data_logger.data_load()
-    Vphoto_var = []
-    for path in file(tweak_file, 'r') :
-        if path[-1] == '\n':
-            path = path.split('\n')[0]
-        # read the data
-        data = dl.read_dict_of_arrays(path)
-        freq = float(path.split('_')[-1].split('Hz')[0])
-        if TEXT_VERBOSE and False :
-            print "Analyzing '%s' at %g Hz" % (path, freq)
-        # analyze
-        Vphoto_var.append(vib_analyze(data, freq, minFreq, maxFreq,
-                                      chunk_size, overlap, window,
-                                      median_filter_width,
-                                      Vphoto_in2V, plotVerbose))
-    return numpy.array(Vphoto_var, dtype=numpy.float)
-
-# A few positioning functions, so we can run bump_aquire() and vib_aquire()
-# with proper spacing relative to the surface.
-
-def move_just_onto_surface(stepper, zpiezo, Depth_nm=100, setpoint=2) :
-    """
-    Uses z_piezo_utils.getSurfPos() to pinpoint the position of the surface.
-    Adjusts the stepper position as required to get within stepper_tol nm
-    of the surface.
-    Then set Vzp to place the cantilever Depth_nm onto the surface.
-
-    If getSurfPos() fails to find the surface, backs off (for safety)
-    and steps in (without moving the zpiezo) until Vphoto > setpoint.
-    """
-    stepper_tol = 250 # nm, generous estimate of the fullstep stepsize
-
-    if TEXT_VERBOSE :
-        print "moving just onto surface"
-    # Zero the piezo
-    if TEXT_VERBOSE :
-        print "zero the z piezo output"
-    zpiezo.jumpToPos(zpiezo.pos_nm2out(0))
-    # See if we're near the surface already
-    if TEXT_VERBOSE :
-        print "See if we're starting near the surface"
-    try :
-        dist = zpiezo.pos_out2nm( \
-            z_piezo_utils.getSurfPos(zpiezo, zpiezo.def_V2in(setpoint))
-                                )
-    except (z_piezo_utils.tooClose, z_piezo_utils.poorFit), string :
-        if TEXT_VERBOSE :
-            print "distance failed with: ", string
-            print "Back off 200 half steps"
-        # Back away 200 steps
-        stepper.step_rel(-400)
-        stepper.step_rel(200)
-        sp = zpiezo.def_V2in(setpoint) # sp = setpoint in bits
-        zpiezo.updateInputs()
-        cd = zpiezo.curDef()           # cd = current deflection in bits
-        if TEXT_VERBOSE :
-            print "Single stepping approach"
-        while cd < sp :
-            if TEXT_VERBOSE :
-                print "deflection %g < setpoint %g.  step closer" % (cd, sp)
-            stepper.step_rel(2) # Full step in
-            zpiezo.updateInputs()
-            cd = zpiezo.curDef()
-        # Back off two steps (protecting against backlash)
-        if TEXT_VERBOSE :
-            print "Step back 4 half steps to get off the setpoint"
-        stepper.step_rel(-200)
-        stepper.step_rel(196)
-        # get the distance to the surface
-        zpiezo.updateInputs()
-        if TEXT_VERBOSE :
-            print "get surf pos, with setpoint %g (%d)" % (setpoint, zpiezo.def_V2in(setpoint))
-        for i in range(20) : # HACK, keep stepping back until we get a distance
-            try :
-                dist = zpiezo.pos_out2nm(getSurfPos(zpiezo, zpiezo.def_V2in(setpoint)))
-            except (tooClose, poorFit), string :
-                stepper.step_rel(-200)
-                stepper.step_rel(198)
-                continue
-            break
-        if i >= 19 :
-            print "tried %d times, still too close! bailing" % i
-            print "probably an invalid setpoint."
-            raise Exception, "weirdness"
-    if TEXT_VERBOSE :
-        print 'distance to surface ', dist, ' nm'
-    # fine tune the stepper position
-    while dist < -stepper_tol : # step back if we need to
-        stepper.step_rel(-200)
-        stepper.step_rel(198)
-        dist = zpiezo.pos_out2nm(getSurfPos(zpiezo, zpiezo.def_V2in(setpoint)))
-        if TEXT_VERBOSE :
-            print 'distance to surface ', dist, ' nm, step back'
-    while dist > stepper_tol : # and step forward if we need to
-        stepper.step_rel(2)
-        dist = zpiezo.pos_out2nm(getSurfPos(zpiezo, zpiezo.def_V2in(setpoint)))
-        if TEXT_VERBOSE :
-            print 'distance to surface ', dist, ' nm, step closer'
-    # now adjust the zpiezo to place us just onto the surface
-    target = dist + Depth_nm
-    zpiezo.jumpToPos(zpiezo.pos_nm2out(target))
-    # and we're there :)
-    if TEXT_VERBOSE :
-        print "We're %g nm into the surface" % Depth_nm
-
-def move_far_from_surface(stepper, um_back=50) :
-    """
-    Step back a specified number of microns.
-    (uses very rough estimate of step distance at the moment)
-    """
-    step_nm = 100
-    steps = int(um_back*1000/step_nm)
-    print "step back %d steps" % steps
-    stepper.step_rel(-steps)
-
-
-# and finally, the calib family
-
-def calib_aquire(stepper, zpiezo, get_T=None,
-                 approach_setpoint=2,
-                 bump_start_depth=100, bump_push_depth=200,
-                 bump_npoints=1024, bump_freq=100e3,
-                 T_convert_to_K=C_to_K,
-                 vib_time=1, vib_freq=50e3,
-                 num_bumps=10, num_Ts=10, num_vibs=20,
-                 log_dir=None, plotVerbose=False) :
-    """
-    Aquire data for calibrating a cantilever in one function.
-    return (bump, T, vib), each of which is an array.
-    Inputs :
-     stepper       a stepper.stepper_obj for coarse Z positioning
-     zpiezo        a z_piezo.z_piezo for fine positioning and deflection readin
-     setpoint      maximum allowed deflection (in Volts) during approaches
-     num_bumps     number of 'a's (see Outputs)
-     push_depth_nm depth of each push when generating a
-     num_temps     number of 'T's (see Outputs)
-     num_vibs      number of 'vib's (see Outputs)
-     log_dir       directory to log data to.  Default 'None' disables logging.
-    Outputs (all are arrays of recorded data) :
-     bumps measured (V_photodiode / nm_tip) proportionality constant
-     Ts    measured temperature (K)
-     vibs  measured V_photodiode variance in free solution
-    """
-    # get bumps
-    move_just_onto_surface(stepper, zpiezo,
-                           bump_start_depth, approach_setpoint)
-    bumps=zeros((num_bumps,))
-    for i in range(num_bumps) :
-        bumps[i] = bump(zpiezo, bump_push_depth, bump_npoints, bump_freq,
-                        log_dir, plotVerbose)
-    if TEXT_VERBOSE :
-        print bumps
-
-    # get Ts
-    if get_T == None :
-        get_T = lambda : DEFAULT_TEMP
-        assert T_convert_to_K == C_to_K, "You didn't define get_T()!"
-    Ts=zeros((num_Ts,), dtype=float)
-    for i in range(num_Ts) :
-        Ts[i] = T(get_T, T_convert_to_K, log_dir, plotVerbose)
-        time.sleep(1) # wait a bit to get an independent temperature measure
-    print Ts
-
-    # get vibs
-    move_far_from_surface(stepper)
-    vibs=zeros((num_vibs,))
-    for i in range(num_vibs) :
-        vibs[i] = vib(zpiezo, vib_time, vib_freq, log_dir=log_dir)
-    print vibs
-
-    if LOG_DATA != None :
-        data = {'bump':bumps, 'T':Ts, 'vib':vibs}
-        log = data_logger.data_log(LOG_DIR, noclobber_logsubdir=False,
-                                   log_name="calib")
-        log.write_dict_of_arrays(data)
-
-    return (bumps, Ts, vibs)
-
-def calib_save(bumps, Ts, vibs, log_dir) :
-    """
-    Save a dictonary with the bump, T, and vib data.
-    """
-    if log_dir != None :
-        data = {'bump':bumps, 'T':Ts, 'vib':vibs}
-        log = data_logger.data_log(log_dir, noclobber_logsubdir=False,
-                                   log_name="calib")
-        log.write_dict_of_arrays(data)
-
-def calib_load(datafile) :
-    "Load the dictionary data, using data_logger.date_load()"
-    dl = data_logger.data_load()
-    data = dl.read_dict_of_arrays(path)
-    return (data['bump'], data['T'], data['vib'])
-
-def calib_save_analysis(k, k_s,
-                        photoSensitivity2_m, photoSensitivity2_s,
-                        T_m, T_s, one_o_Vphoto2_m, one_o_Vphoto2_s,
-                        log_dir=None) :
-    if log_dir != None :
-        log = data_logger.data_log(LOG_DIR, noclobber_logsubdir=False,
-                                   log_name="calib_analysis_text")
-        log.write_binary((
-            "k (N/m) : %g +/- %g\n" % (k, k_s)
-            + "photoSensitivity**2 (V/nm)**2 : %g +/- %g\n" % \
-                (photoSensitivity2_m, photoSensitivity2_s)
-            + "T (K) : %g +/- %g\n" % (T_m, T_s)
-            + "1/Vp**2 (1/V)**2 : %g +/- %g\n" % \
-                (one_o_Vphoto2_m, one_o_Vphoto2_s)
-                         ))
-
-def calib_plot(bumps, Ts, vibs, plotVerbose) :
-    if plotVerbose or PYLAB_VERBOSE :
-        _import_pylab()
-        _pylab.figure(BASE_FIGNUM+4)
-        _pylab.subplot(311)
-        _pylab.plot(bumps, 'g.')
-        _pylab.title('Photodiode sensitivity (V/nm)')
-        _pylab.subplot(312)
-        _pylab.plot(Ts, 'r.')
-        _pylab.title('Temperature (K)')
-        _pylab.subplot(313)
-        _pylab.plot(vibs, 'b.')
-        _pylab.title('Thermal deflection variance (Volts**2)')
-        _flush_plot()
-
-def calib(stepper, zpiezo, tempController=None,
-          setpoint=2.0,
-          num_bumps=10, push_depth_nm=300,
-          num_temps=10,
-          num_vibs=10,
-          log_dir=None) :
-    """
-    Calibrate a cantilever in one function.
-    The I-don't-care-about-the-details black box version :p.
-    return (k, k_s)
-    Inputs:
-     (see calib_aquire()) 
-    Outputs :
-     k    cantilever spring constant (in N/m, or equivalently nN/nm)
-     k_s  standard deviation in our estimate of k
-    Notes :
-     See get_calibration_data() for the data aquisition code
-     See analyze_calibration_data() for the analysis code
-    """
-    a, T, vib = calib_aquire(stepper, zpiezo, tempController,
-                             setpoint,
-                             num_bumps=num_bumps,
-                             push_depth_nm=push_depth_nm,
-                             num_temps=num_temps,
-                             num_vibs=num_vibs,
-                             log_dir=log_dir)
-    calib_save(a, T, vib, log_dir)
-    k,k_s,ps2_m, ps2_s,T_m,T_s,one_o_Vp2_m,one_o_Vp2_s = \
-        calib_analyze(a, T, vib, log_dir=log_dir)
-    calib_save_analysis(k,k_s,ps2_m, ps2_s,T_m,T_s,one_o_Vp2_m,one_o_Vp2_s,
-                        log_dir)
-    calib_plot(a, T, vib, plotVerbose)
-    return (k, k_s)
-
-def calib_load_analyze_tweaks(bump_tweaks, vib_tweaks,
-                              T_tweaks=None,
-                              log_dir=None,
-                              plotVerbose=True) :
-    a = read_tweaked_bumps(bump_tweaks)
-    vib = V_photo_variance_from_file(vib_tweaks)
-    return analyze_calibration_data(a, T, vib, log_dir=log_dir)
-    
-
index 14af67b5fa340b188b06e32fc4c683e90ca66047..8c0369683b639c63173328645f7b1ab83101a9b3 100644 (file)
@@ -13,6 +13,12 @@ PYLAB_VERBOSE = True     # turn on plotting
 PYLAB_INTERACTIVE = True # select between draw() and show() for flushing plots
 BASE_FIGNUM = 20 # to avoid writing to already existing figures
 
+
+# HACK
+# make sure you make a system note (add_system_note) if you change
+# these in case you don't have access to a z_piezo for conversion
+# functions
+
 # zpGain      zpiezo applied voltage per output Volt
 zpGain = z_piezo.DEFAULT_GAIN
 # zpSensitivity  nm zpiezo response per applied Volt
index 72c2140fecf6c9183408065506082e402c6b943b..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) :
     """
@@ -55,10 +58,15 @@ def vib_analyze_naive(deflection_bits, Vphoto_in2V=config.Vphoto_in2V,
     Vphoto_std_bits = deflection_bits.std()
     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.
@@ -74,6 +82,12 @@ def vib_analyze(deflection_bits, freq, minFreq=500, maxFreq=7000,
     Vphoto_in2V is a function converting Vphoto values from bits to Volts.
     This function is assumed linear, see bump_analyze().
     """
+    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
@@ -89,14 +103,13 @@ 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
     fitted_variance = lorentzian_area(A,B,C) * 1e-12
@@ -109,6 +122,7 @@ def vib_analyze(deflection_bits, freq, minFreq=500, maxFreq=7000,
     
     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
@@ -250,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
@@ -258,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)
@@ -273,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),
@@ -360,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') :
@@ -381,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
@@ -456,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