Merged my unitary FFT wrappers (FFT_tools) as hooke.util.fft.
authorW. Trevor King <wking@drexel.edu>
Mon, 17 May 2010 19:08:29 +0000 (15:08 -0400)
committerW. Trevor King <wking@drexel.edu>
Mon, 17 May 2010 19:08:29 +0000 (15:08 -0400)
Origininally versioned in Git and licensed under BSD, but I wrote
them, so I'm relicensing under the LGPL for Hooke.

The original git repository was:
  http://www.physics.drexel.edu/~wking/code/git/FFT_tools.git/

240 files changed:
.hgignore [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
CHANGELOG [new file with mode: 0644]
COPYING [new file with mode: 0644]
COPYING.LESSER [new file with mode: 0644]
README [new file with mode: 0644]
bin/hooke [new file with mode: 0755]
conf/autopeak.ini [new file with mode: 0644]
conf/convfilt.conf [new file with mode: 0644]
conf/fit.ini [new file with mode: 0644]
conf/flatfilts.ini [new file with mode: 0644]
conf/general.ini [new file with mode: 0644]
conf/generalvclamp.ini [new file with mode: 0644]
conf/hooke configspec.ini [new file with mode: 0644]
conf/hooke.conf [new file with mode: 0644]
conf/hooke.ini [new file with mode: 0644]
conf/macro.conf [new file with mode: 0644]
conf/pca_config.txt [new file with mode: 0644]
conf/pcluster.ini [new file with mode: 0644]
conf/procplots.ini [new file with mode: 0644]
contrib/mfp_igor_scripts/ExportMFP1D.ipf [new file with mode: 0644]
contrib/mfp_igor_scripts/ExportMFP1DMenu.ipf [new file with mode: 0644]
contrib/mfp_igor_scripts/FMjoin.py [new file with mode: 0644]
contrib/mfp_igor_scripts/h5export.py [new file with mode: 0644]
doc/.sconsrc-sphinx [new file with mode: 0644]
doc/SConstruct [new file with mode: 0644]
doc/bugs.txt [new file with mode: 0644]
doc/conf.py [new file with mode: 0644]
doc/config.txt [new file with mode: 0644]
doc/doc.txt [new file with mode: 0644]
doc/generate-hooke-txt.py [new file with mode: 0644]
doc/gui.txt [new file with mode: 0644]
doc/hacking.txt [new file with mode: 0644]
doc/img/hooke.jpg [new file with mode: 0644]
doc/index.txt [new file with mode: 0644]
doc/install.txt [new file with mode: 0644]
doc/testing.txt [new file with mode: 0644]
doc/tutorial.txt [new file with mode: 0644]
hooke/__init__.py [new file with mode: 0644]
hooke/command.py [new file with mode: 0644]
hooke/compat/__init__.py [new file with mode: 0644]
hooke/compat/odict.py [new file with mode: 0644]
hooke/config.py [new file with mode: 0644]
hooke/curve.py [new file with mode: 0644]
hooke/driver/__init__.py [new file with mode: 0644]
hooke/driver/csvdriver.py [new file with mode: 0644]
hooke/driver/hdf5.py [new file with mode: 0644]
hooke/driver/hemingclamp.py [new file with mode: 0644]
hooke/driver/jpk.py [new file with mode: 0644]
hooke/driver/mcs.py [new file with mode: 0644]
hooke/driver/mfp1dexport.py [new file with mode: 0644]
hooke/driver/mfp3d.py [new file with mode: 0644]
hooke/driver/picoforce.py [new file with mode: 0644]
hooke/driver/picoforcealt.py [new file with mode: 0644]
hooke/driver/tutorial.py [new file with mode: 0644]
hooke/engine.py [new file with mode: 0644]
hooke/experiment.py [new file with mode: 0644]
hooke/hooke.py [new file with mode: 0644]
hooke/interaction.py [new file with mode: 0644]
hooke/playlist.py [new file with mode: 0644]
hooke/plugin/__init__.py [new file with mode: 0644]
hooke/plugin/autopeak.py [new file with mode: 0644]
hooke/plugin/config.py [new file with mode: 0644]
hooke/plugin/curve.py [new file with mode: 0644]
hooke/plugin/cut.py [new file with mode: 0644]
hooke/plugin/debug.py [new file with mode: 0644]
hooke/plugin/fit.py [new file with mode: 0644]
hooke/plugin/flatfilts-rolf.py [new file with mode: 0644]
hooke/plugin/flatfilts.py [new file with mode: 0644]
hooke/plugin/generalclamp.py [new file with mode: 0644]
hooke/plugin/generaltccd.py [new file with mode: 0644]
hooke/plugin/generalvclamp.py [new file with mode: 0644]
hooke/plugin/jumpstat.py [new file with mode: 0644]
hooke/plugin/macro.py [new file with mode: 0644]
hooke/plugin/massanalysis.py [new file with mode: 0644]
hooke/plugin/multidistance.py [new file with mode: 0644]
hooke/plugin/multifit.py [new file with mode: 0644]
hooke/plugin/note.py [new file with mode: 0644]
hooke/plugin/pcluster.py [new file with mode: 0644]
hooke/plugin/peakspot.py [new file with mode: 0644]
hooke/plugin/playlist.py [new file with mode: 0644]
hooke/plugin/plotmanip.py [new file with mode: 0644]
hooke/plugin/review.py [new file with mode: 0644]
hooke/plugin/showconvoluted.py [new file with mode: 0644]
hooke/plugin/superimpose.py [new file with mode: 0644]
hooke/plugin/system.py [new file with mode: 0644]
hooke/plugin/tutorial.py [new file with mode: 0644]
hooke/ui/__init__.py [new file with mode: 0644]
hooke/ui/commandline.py [new file with mode: 0644]
hooke/ui/gui/driver.py [new file with mode: 0644]
hooke/ui/gui/export.py [new file with mode: 0644]
hooke/ui/gui/formatter.py [new file with mode: 0644]
hooke/ui/gui/hookeplaylist.py [new file with mode: 0644]
hooke/ui/gui/hookepropertyeditor.py [new file with mode: 0644]
hooke/ui/gui/hookeresults.py [new file with mode: 0644]
hooke/ui/gui/perspectives/Default.txt [new file with mode: 0644]
hooke/ui/gui/plot.py [new file with mode: 0644]
hooke/ui/gui/plotcommands.py [new file with mode: 0644]
hooke/ui/gui/point_request.py [new file with mode: 0644]
hooke/ui/gui/prettyformat.py [new file with mode: 0644]
hooke/ui/gui/results.py [new file with mode: 0644]
hooke/util/__init__.py [new file with mode: 0644]
hooke/util/calculus.py [new file with mode: 0644]
hooke/util/fft.py [moved from FFT_tools.py with 100% similarity]
hooke/util/graph.py [new file with mode: 0644]
hooke/util/pluggable.py [new file with mode: 0644]
setup.py [new file with mode: 0644]
test/__init__.py [new file with mode: 0644]
test/curve_info.py [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-0-0-10.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-0-0-31.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-0-0-43.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-0-2-2.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-0-2-3.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-0-4-28.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-0-9-7.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-1-0-14.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-1-2-2.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-1-4-16.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-1-4-28.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-1-6-20.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-1-8-10.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-1-9-10.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-1-9-12.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-1-9-13.dat [new file with mode: 0644]
test/data/fclamp_hemingway/20080428_a53t-1-9-24.dat [new file with mode: 0644]
test/data/fclamp_hemingway/README [new file with mode: 0644]
test/data/fclamp_hemingway/fclamp_i27-0-0-42.dat [new file with mode: 0644]
test/data/fclamp_hemingway/fclamp_i27-0-1-35.dat [new file with mode: 0644]
test/data/fclamp_hemingway/fclamp_i27-1-5-17.dat [new file with mode: 0644]
test/data/fclamp_hemingway/fclamp_i27-2-1-4.dat [new file with mode: 0644]
test/data/fclamp_hemingway/fclamp_i27-3-4-17.dat [new file with mode: 0644]
test/data/fclamp_hemingway/fclamp_i27-5-1-6.dat [new file with mode: 0644]
test/data/fclamp_hemingway/playlist.hkp [new file with mode: 0644]
test/data/picoforce.000 [new file with mode: 0644]
test/data/test.hkp [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.100 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.101 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.102 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.103 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.104 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.105 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.106 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.107 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.108 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.109 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.110 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.111 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.112 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.113 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.114 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.115 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.116 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.117 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.118 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.119 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.120 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.121 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.122 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.123 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.124 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.125 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.126 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.127 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.128 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.129 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.130 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.131 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.132 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.133 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.134 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.135 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.136 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.137 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.138 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.139 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.140 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.141 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.142 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.143 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.144 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.145 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.146 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.147 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.148 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.149 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.150 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.151 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.152 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.153 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.154 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.155 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.156 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.157 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.158 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.159 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.160 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.161 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.162 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.163 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.164 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.165 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.166 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.167 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.168 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.169 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.170 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.171 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.172 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.173 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.174 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.175 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.176 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.177 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.178 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.179 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.180 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.181 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.182 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.183 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.184 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.185 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.186 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.187 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.188 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.189 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.190 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.191 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.192 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.193 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.194 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.195 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.196 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.197 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.198 [new file with mode: 0644]
test/data/vclamp_picoforce/20071120a_i27_t33.199 [new file with mode: 0644]
test/data/vclamp_picoforce/README [new file with mode: 0644]
test/data/vclamp_picoforce/playlist.hkp [new file with mode: 0644]
test/load_playlist.py [new file with mode: 0644]
update_copyright.py [new file with mode: 0755]

diff --git a/.hgignore b/.hgignore
new file mode 100644 (file)
index 0000000..5437bf0
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,8 @@
+syntax: glob
+
+*.pyc
+.hooke.cfg
+doc/.sconsign.dblite
+doc/build/
+doc/hooke/
+
diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..3deb56d
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,12 @@
+Hooke was written by:
+A. Seeholzer
+Alberto Gomez-Casado
+Allen Chen
+Fabrizio Benedetti
+Francesco Musiani
+Marco Brucale
+Massimo Sandal <devicerandom@gmail.com>
+Pancaldi Paolo
+Richard Naud <richard.naud@epfl.ch>
+Rolf Schmidt <rschmidt@alcor.concordia.ca>
+W. Trevor King <wking@drexel.edu>
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644 (file)
index 0000000..f71b509
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,404 @@
+THIS CHANGELOG FILE CONTANS PRE-SVN CHANGELOGS AND SUMMARIES OF POST-SVN CHANGELOGS.
+FROM 0.8.4 ONWARD, DETAILED CHANGELOGS ARE AVAILABLE ON THE HOOKE SUBVERSION REPOSITORY.
+
+0.8.4
+(2008-x-x)
+    PLUGINS:
+    macro.py:
+        hooke does not crash if it doesn't have permissions to create the folder
+       fixed overwriting of export for curves with the same name and different numerical extension
+       fixed macrodir (out of try-catch)
+       cleaned debug output in execmacro
+    DRIVERS:
+       alternative version of picoforce driver
+    INPUT
+       merged (partially) libinput (so far in hooke_cli)
+    OUTLET     
+       merged liboutlet
+
+
+0.8.3
+(2008-04-16)
+    PLUGINS:
+    generalvclamp.py:
+        fixed autopeak header
+        fixed autopeak slope (now unwanted slope values are discarded)
+
+0.8.2
+(2008-04-10)
+    PLUGINS:
+    flatfilts.py:
+        convfilt does not crash if a file is not a curve
+    generalvclamp.py:
+        autopeak now saves curve data correctly
+        autopeak now generates a dummy note (so that copylog/notelog is aware you measured the curve)
+
+0.8.1
+(2008-04-07)
+    PLUGINS:
+    generalvclamp.py:
+        fixed DeprecationWarning in flatten
+    flatfilts.py
+        convfilt now working
+
+
+0.8.0:
+(2008-04-04)
+    hooke.py:
+        sanity check of CLI plugins to avoid function overloading at startup
+    hooke_cli.py ; libhooke.py:
+        now playlists keep the index (when you reload the playlist, it starts from the
+            last observed curve)
+        updated plot to use _send_plot()
+        hooke.conf accepts lists as arguments for variables in <display>
+        txt, export now have consistent argument order (thanks to A.G.Casado for pointing me that)
+        txt crashes no more if no filename is given (thanks to A.G.Casado for pointing me that)
+    libhookecurve.py:
+        added add_set() , remove_set() methods to make life easier for plugin writers
+    procplots.py:
+        plotmanip_correct() works with new picoforce.py deflection output (see)
+    PLUGINS:
+    fit.py:
+        updated wlc to use _send_plot()
+        wlc noauto now keeps the contact point
+        wlc reclick to click again the contact point
+        temperature now set in hooke.conf
+    generalvclamp.py:
+        implemented slope (thanks to Marco Brucale)
+        implemented autopeak
+    flatfilts.py:
+        convfilt,peaks use flattened curve
+    macro.py:
+        (new) added macro plugin (thanks to Alberto Gomez Casado)
+    DRIVERS:
+    picoforce.py:
+        fixed trigger bug! (thanks to Alberto Gomez Casado)
+        better deflection output (separated extension,retraction)
+
+0.7.5:
+(2008-03-27)
+    hooke_cli.py:
+        removed outdated size command
+    PLUGINS:
+    generalvclamp.py:
+        implemented flatten
+    DRIVERS:
+    added tutorialdriver.py driver
+    csvdriver.py:
+        fixed (forgot close_all() method)
+
+0.7.4:
+(2008-03-19)
+    added csvdriver driver
+    hooke_cli.py:
+        fixed plot manipulators handling (now it's safe to comment a
+              plot manipulator on hooke.conf)
+    PLUGINS:
+    fit.py:
+        fixed possible crash when clicking two times the same point on wlc
+
+0.7.3:
+(2008-01-10)
+    hooke_cli.py:
+        fixed crash on copylog
+    PLUGINS:
+        massanalysis.py:
+            Initial release
+        tutorial.py:
+            Tutorial plugin, initial release
+
+0.7.2.1:
+(2007-11-30)
+    PLUGINS:
+        flatfilt.py:
+            fixed crash on Windows
+
+0.7.2:
+(2007-11-29)
+    hooke.py:
+        new configuration variable hookedir
+    hooke_cli.py:
+        copylog now checks if the destination is a real directory
+        fixed crashes in set
+    PLUGINS:
+        generalvclamp.py:
+            fixed a crash in forcebase when picking two times the same point
+        flatfilt.py:
+            fixed crash due to convfilt.conf impossible to load
+            initial implementation of the blind window for convfilt
+            initial data set maps (NOT FINISHED)
+
+0.7.1:
+(2007-11-26)
+    PLUGINS:
+        flatfilts.py:
+            fixed possible crash in convfilt
+            implemented configuration file convfilt.conf
+            convfilt defaults are now 5 peaks 5 times more the noise absdev
+            implemented convconf
+            implemented setconf
+        libpeakspot.py:
+            fixed:now it really uses noise_absdev
+
+0.7.0:
+(2007-11-15)
+    hooke_cli.py:
+        implemented _send_plot() helper API function
+    PLUGINS:
+        generalvclamp.py:
+            fixed forcebase to work with subtplot
+        flatfilts.py:
+            implemented convfilt!
+            added libpeakspot.py (helping library for convolution filter)
+
+0.6.5:
+(2007-11-06)
+    hooke_cli.py, hooke.py:
+        plateau and contact (unmaintained) deleted and scheduled for re-release in generalvramp
+        implemented _measure_N_points()
+    PLUGINS:
+    generalvclamp.py:
+        implemented forcebase
+    fit.py:
+        wlc now accepts and uses temperature as an argument
+        wlc has been cleaned and uses new APIs
+
+0.6.4:
+(2007-10-23)
+    hooke_cli.py, libhooke.py:
+        implemented support for defining order of plotmanip methods in hooke.conf
+    hooke_cli.py:
+        implemented delta
+        implemented point
+        attempted fix to bug 0033 (notelog crashing Hooke when using Unicode characters)
+    PLUGINS:
+    generalvramp.py:
+        began to move velocity ramp force spectroscopy-specific things in separate plugin
+    procplots.py:
+        added detriggerize; "set detrigger" 0/1 disables/enables it.
+    DRIVERS:
+    picoforce.py:
+        removed detriggerize() from driver
+
+0.6.3:
+(2007-10-02)
+    hooke_cli.py:
+        rewritten txt command, now working
+    DRIVERS:
+    picoforce.py:
+        implemented detriggerize() to bypass the Picoforce trigger bug
+    PLUGINS:
+    superimpose.py:
+        implemented plotavgimpose
+
+0.6.2:
+(2007-09-27)
+    hooke_cli.py:
+        fixed error handling in notelog
+        smarter handling of directory names in genlist
+        unexpected error handling in do_plot()
+    hooke.py:
+        implemented GetDisplayedPlot event and handlers
+    PLUGINS:
+    fit.py:
+        fixed (bug 0029) about replotting of wlc on a subtplot curve
+        multiple fitting displayed (to refine...)
+
+0.6.1:
+(2007-08-06)
+    libhooke.py , hooke.py:
+        initial support for workdir configuration variable
+    libhooke.py:
+        fixed Driver() etc. semantics for gracefully handling unrecognized plots
+    hooke_cli.py:
+        fixed export namehandling
+        fixed plot error handling
+    PLUGINS:
+    flatfilts.py:
+        fixed memory leak
+    generalclamp.py:
+        fixed step command
+
+0.6.0 "Anko":
+(2007-07-25)
+    hooke.py:
+        initial plugin support for the gui
+        wlc fitting now 100% plugin
+        measure_points replaces measure_couple etc. and provides much better extensibility
+    hooke_cli.py:
+       curves are sorted at beginning
+    PLUGINS:
+    procplots.py:
+        fft now allows for user selection of curve segment; select the plot; etc.
+    fit.py:
+        added gui section of plugin, now completely independent
+        fixed bug of wlc output
+    superimpose.py:
+        new plugin for superimposition of curve segments (still in development)
+    generalclamp.py:
+        all clamp commands now in a single plugin
+        implemented step
+
+0.5.4:
+(2007-06-15)
+    procplots.py:
+        fixed fft crash with Numpy 1.0.1
+    hooke.py:
+        fixed crashes if plot.scatter[] was empty
+        fixed management of multiple plots (bug #0025)
+    hooke_cli.py
+        fixed zpiezo error in measurement
+    hemingclamp.py, picoforce.py:
+        implemented close_all() method in drivers to avoid too many open files error
+    flatfilts.py:
+        fixed memory leak
+0.5.3:
+(2007-06-06)
+    wlc.py, hooke.py:
+        fixing and cleaning fit code: now the fit is part of a PlotObject and 100% coded in wlc.py
+        plotting of the wlc.py clicked points also begin to be part of a PlotObject
+        management of 'scatter' style property of plots
+    hooke_cli.py
+        fixed measuring error in defl, zpiezo
+    flatfilts.py:
+        slightly optimized has_features() routine
+    procplots.py:
+        fixed derivplot for every number of vectors
+        fixed possible crash of subtplot if applied on a file with != 2 plots
+        added fft command
+    libhookecurve.py:
+        fixed xaxis, yaxis for non-default plots: now defined from PlotObject
+        PlotObject now defines a styles[] vector
+0.5.2:
+(2007-05-21)
+    versioning a bit cleaned
+    fixed bug in hemingclamp.py preventing filename to appear
+    fixed wxversion problem for 2.8
+    fixed too many open files bug (bug 0024)
+    added index command
+0.5.1:
+(2007-05-09)
+    using wxversion to choose from multiple wx versions
+    fixed old dependencies remaining
+0.5.0 "Ingyo":
+(2007-05-03)
+    general code updating and rewriting due to plugin support/better plot management
+    hooke.py:
+        initial plugin architecture for the command line.
+        initial plugin architecture for file drivers
+        initial plugin architecture for processing plots
+        export can now export both top and bottom plot (not together)
+    hooke_cli.py:
+        wlc fitting moved to fit.py plugin
+        flatfilt moved to flatfilts.py plugin
+        subtplot, derivplot moved to procplots.py plugin
+        double plot temporarily fixed for previous commands
+        export can now export both top and bottom plot (not together)
+
+0.4.1:
+(2007-02-13)
+    hooke_cli.py:
+        double plot now default for clamp experiments
+    libhooke.py:
+        fixed bug that prevented flatfilt to work
+        (maybe) fixed memory leak in flatfilt
+
+0.4.0 "Hanzei":
+(2007-02-08)
+    general code updating and rewriting due to double plot/force clamp supports
+    hooke.py:
+        initial dummy menu sketch
+    hooke.py, hooke_cli.py:
+        first general support in code for double plot:
+        - derivplot now in separate plot
+        - implemented show and close commands
+        - all functions should be double plot-aware
+        - clicking a point is double plot-aware
+    libhooke.py, hooke_cli.py:
+        general code cleanup: vectors_to_plot(), subtract_plot(), find_contact_point() and derivplot routines are now methods of class HookeCurve
+    hooke_cli.py:
+        implemented quit (alias of exit)
+        implemented version
+    libhooke.py, hooke.py, hooke_cli.py:
+        initial support for force clamp experiments:
+        - hemingclamp driver supported
+        - "experiment" flag describes what kind of experiment is a curve
+        - time, zpiezo, defl commands implemented
+    libhemingclamp.py:
+        inital release.
+
+0.3.1:
+    hooke.py:
+        fixed stupid bug in plateau
+        fixed bug in derivplot and subtplot not taking into account xaxes/yaxes variables
+0.3.0:
+    from now on, all changelog is stored in CHANGELOG
+    hooke.py, libhooke.py, hooke_cli.py:
+        fixed plot and flatfilt crash when processing corrupt files
+        flatfilt output now more verbose
+        implemented system (execute an external OS command)
+        implemented copylog (copies annotated curves to a given directory) (todo 0033)
+        initial txt implementation (exports the current curve as a text file) (todo 0023)
+        fixed exit behaviour (bug 0013)
+        xaxes and yaxes variables now control visualization of plot (todo 0018)
+        new (better) contact point algorithm + workaround for the picoforce trigger bug
+0.2.2 :
+    hooke.py, hooke_cli.py, libhooke.py:
+        support for fixed persistent length in WLC
+0.2.1 :
+    hooke.py , libhooke.py:
+        fixed 'wlc noauto' bug (0012) preventing correct contact point to be used
+0.2.0 :
+    hooke_cli.py:
+        implemented getlist (alias of genlist)
+        implemented contact (to plot the contact point)
+        fixed bug 0001 (Hooke crashes when opening a non-pf file)
+        fixed bug 0008  (Hooke crashes when generating a playlist with malformed namefiles/nonexistent files)
+        now the plot is refreshed after a "set" command (todo 0014)
+        wlc fit can use the (new) automatic contact point detection (old behaviour is preserved with "noauto" option)
+    hooke.py:
+        fixed versioning printing
+        complete refactoring of contact point routines
+        wlc fit adapted to use the (new) automatic contact point detection
+        wlc fit code a bit cleaned; parts moved to libhooke.py
+    libhooke.py:
+        new contact point algorithm (new algorithm)
+        wlc fit now uses a fancier domain (from contact point to a bit more than last point); initial chunk preparation section moved from hooke.py
+
+
+OLDER CHANGELOGS:
+
+hooke.py:
+0.1.1   :
+    From now on, all changelog is stored in hooke.py
+    hooke_cli.py:
+        corrected bug 0010 (addtolist bug), alerts when hitting start/end of playlist
+2006_09_15_devel=0.1.0: initial WLC fit support. We hit 0.1 milestone :D
+2006_08_28_devel: refactoring of plot interaction
+2006_06_14_devel: fixed libhooke calls
+2006_06_08_devel: initial automatic contact point finding
+2006_05_30_devel: configuration file support
+
+hooke_cli.py:
+0.1.1 : from now on, all changelog is in hooke.py
+2006_09_15_devel: implemented wlc; 0.1.0 milestone.
+2006_08_28_devel: refactoring of plot interaction
+2006_07_23_devel: implemented note; implemented flatfilt; implemented notelog; exit now warns if playlist/notes
+                  have not been saved.
+2006_07_18_devel: implemented subtplot; bug 0007 ("cd" crashing) fixed
+2006_06_16_devel: moved math helper functions in libhooke.py
+2006_06_14_devel: fixed "jump" output; fixed "exit" (now it works!); fixed off-by-one bug in deflection-correction
+2006_06_08_devel: fixed "loadlist" output;
+2006_05_30_devel: initial configuration file support; added "set" command; initial deflection-correction support; added "ls" command as an alias of "dir"
+2006_05_23_devel: rewriting of playlist-handling code due to major rewrite of hooke_playlist.py
+
+libhooke.py
+0.1.1   : from now on, all changelog is in hooke.py
+2006_09_15_devel : initial WLC support
+2006_09_14_devel : initial support for Hemingway velocity clamp files, minor refactorings
+2006_07_22_devel : implemented math function has_features
+2006_06_16_devel : math functions moved here
+2006_06_08_devel : hooke_playlist.py becomes libhooke.py
+2006_05_30_devel : support for deflection in HookeCurve
+2006_05_29_devel : Initial configuration file support
+2006_05_23_devel : Major rewrite. Fixed bug 0002
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..94a9ed0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/COPYING.LESSER b/COPYING.LESSER
new file mode 100644 (file)
index 0000000..fc8a5de
--- /dev/null
@@ -0,0 +1,165 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions. 
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version. 
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..df17b2e
--- /dev/null
+++ b/README
@@ -0,0 +1,59 @@
+What is it?
+===========
+
+It is a software for the (semiautomatic) analysis and filtering of
+force curves. Force curves are the output of an analytical technique
+called force spectroscopy. A force spectroscopy experiment usually
+requires the analysis of thousands of force curves at a time. As of
+today, there is no standard, free software for the analysis of force
+curves. Hooke aims to solve that.
+
+
+What it does?
+=============
+
+  * View, annotate, measure force curves
+  * Worm-like chain and freely-jointed chain fit of force peaks
+  * Automatic convolution-based filtering of empty curves
+  * Automatic fit and measurement of multiple force peaks
+  * Handles force-clamp force experiments (experimental)
+  * It is extensible by users by mean of plugins and drivers
+
+See the Google Code website for more details
+  http://code.google.com/p/hooke/
+
+
+How do you make it work?
+========================
+
+See the doc/USAGE file distributed with hooke.
+
+
+Is this published in some peer-reviewed journal?
+================================================
+
+Sandal M, Benedetti F, Brucale M, Gomez-Casado A, Samorì B.
+"Hooke: an open software platform for force spectroscopy."
+Bioinformatics, 2009 25(11):1428-1430.
+doi:10.1093/bioinformatics/btp180
+
+Please cite Hooke if you use it.
+
+
+Troubleshooting
+===============
+
+If you have troubles in using it, before throwing it in the trash:
+  1) try look at the TroubleShooting page or in the discussion group
+  2) ask a question in the discussion group!
+http://groups.google.com/group/hookesoftware
+
+
+Disclaimer
+==========
+
+Remember that Hooke is still experimental software! It has been mostly
+done to fit the needs of its authors, and there is no guarantee it
+will do what you need. However, you can write us/help us improve it so
+that it does. We aim to make of Hooke a little, universal tool that
+can help your research.
diff --git a/bin/hooke b/bin/hooke
new file mode 100755 (executable)
index 0000000..7bf1b13
--- /dev/null
+++ b/bin/hooke
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+import hooke.hooke
+
+hooke.hooke.main()
diff --git a/conf/autopeak.ini b/conf/autopeak.ini
new file mode 100644 (file)
index 0000000..7fd1d87
--- /dev/null
@@ -0,0 +1,91 @@
+[autopeak]
+    [[auto_fit_nm]]
+        default = 5
+        minimum = 0
+        type = float
+        value = 5
+
+    [[auto_fit_points]]
+        default = 50
+        minimum = 0
+        type = integer
+        value = 50
+
+    [[auto_left_baseline]]
+        default = 20
+        minimum = 0
+        type = float
+        value = 20
+
+    [[auto_max_p]]
+        default = 10
+        minimum = 0
+        type = float
+        value = 10
+
+    [[auto_min_p]]
+        default = 0.005
+        minimum = 0
+        type = float
+        value = 0.005
+
+    [[auto_right_baseline]]
+        default = 20
+        minimum = 0
+        type = float
+        value = 20
+
+    [[auto_slope_span]]
+        default = 20
+        minimum = 0
+        type = integer
+        value = 20
+
+    [[baseline_clicks]]
+        default = 0
+        maximum = 20
+        minimum = 0
+        type = integer
+        value = 0
+
+    [[noauto]]
+        default = False
+        type = boolean
+        value = False
+
+    [[noflatten]]
+        default = False
+        type = boolean
+        value = False
+
+    [[pl]]
+        default = 5
+        minimum = 0
+        type = float
+        value = 5
+
+    [[rebase]]
+        default = True
+        type = boolean
+        value = False
+
+    [[reclick]]
+        default = False
+        type = boolean
+        value = False
+
+    [[temperature]]
+        default = 293
+        minimum = 0
+        type = float
+        value = 293
+
+    [[usepl]]
+        default = False
+        type = boolean
+        value = False
+
+    [[usepoints]]
+        default = False
+        type = boolean
+        value = False
diff --git a/conf/convfilt.conf b/conf/convfilt.conf
new file mode 100644 (file)
index 0000000..db7134a
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" ?>
+<config>
+
+<!-- Parameters to calculate the noise absolute deviation.
+     positive= cut out most positive (1) or negative (0) values (default=0)
+     maxcut=cut at maximum a maxcut fraction of all points. (default=0.2) 
+     stable=convergency threshold (when cutting more points the ratio doesn't change more than stable, then stop) (default=0.005)
+-->
+<noise_absdev positive="0" maxcut="0.2" stable="0.005"/>
+
+<!-- Parameters of the convfilt.
+     minpeaks=number minimum of peaks we want (default=5)
+     mindeviation=minimum absolute deviation of convolution to define a peak (default=5)
+     seedouble=if two peaks distance less than seedouble points, count them as a single peak (default=10)
+     convolution=the actual convolution vector (DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING)
+     blindwindow=nm after the contact point where we do not count peaks.
+    -->
+<convfilt minpeaks="5" mindeviation="5" seedouble="10" convolution="[6.0,-1.0,-1.0,-1.0,-1.0,-1.0,-1.0]" blindwindow="20"/>
+<!--convfilt minpeaks="5" mindeviation="5" seedouble="10" convolution="[11.0,-1.0,-1.0,-1.0,-1.0,-1.0,-1.0,-1.0,-1.0,-1.0,-1.0,-1.0]" blindwindow="100"/-->
+</config>
diff --git a/conf/fit.ini b/conf/fit.ini
new file mode 100644 (file)
index 0000000..938f895
--- /dev/null
@@ -0,0 +1,3 @@
+[fit]
+flatten = 1
+temperature = 301
diff --git a/conf/flatfilts.ini b/conf/flatfilts.ini
new file mode 100644 (file)
index 0000000..6f2bea9
--- /dev/null
@@ -0,0 +1,84 @@
+[convfilt]
+    [[blindwindow]]
+        default = 20
+        maximum = 10000
+        minimum = 0
+        type = float
+        value = 20
+
+    [[convolution]]
+        default = '[6.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]'
+        type = string
+        #value = '[6.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]'
+        value = '[11.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]'
+
+    [[maxcut]]
+        default = 0.2
+        maximum = 1
+        minimum = 0
+        type = float
+        value = 0.2
+        visible = False
+
+    [[medianfilter]]
+        default = 7
+        maximum = 100
+        minimum = 0
+        type = integer
+        value = 7
+
+    [[mindeviation]]
+        default = 5
+        maximum = 100
+        minimum = 0
+        type = float
+        value = 5
+
+    [[minpeaks]]
+        default = 5
+        maximum = 20
+        minimum = 0
+        type = integer
+        value = 1
+
+    [[positive]]
+        default = False
+        type = boolean
+        value = False
+
+    [[seedouble]]
+        default = 10
+        maximum = 1000
+        minimum = 0
+        type = integer
+        value = 10
+
+    [[stable]]
+        default = 0.005
+        maximum = 1
+        minimum = 0
+        type = float
+        value = 0.005
+
+[flatfilt]
+    #[[median_filter]]
+        #default = 7
+        #maximum = 100
+        #minimum = 0
+        #type = integer
+        #value = 7
+
+    [[min_npks]]
+        default = 4
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 4
+
+    [[min_deviation]]
+        default = 9
+        maximum = 100
+        minimum = 1
+        type = float
+        value = 9
+
diff --git a/conf/general.ini b/conf/general.ini
new file mode 100644 (file)
index 0000000..9d235ab
--- /dev/null
@@ -0,0 +1,22 @@
+[genlist]
+    [[folder]]
+        default = ''
+        type = folder
+        value = 'R:\Programming\Python\Gui\data\dwell'
+
+    [[filemask]]
+        default = '*.*'
+        type = string
+        value = '*.*'
+
+[loadlist]
+    [[filename]]
+        default = 'test.hkp'
+        type = filename
+        value = 'R:\Programming\Python\Gui\untitled.hkp'
+
+[savelist]
+    [[filename]]
+        default = 'untitled.hkp'
+        type = filename
+        value = 'R:\Programming\Python\Gui\untitled.hkp'
diff --git a/conf/generalvclamp.ini b/conf/generalvclamp.ini
new file mode 100644 (file)
index 0000000..b129db2
--- /dev/null
@@ -0,0 +1,19 @@
+[generalvclamp]
+    [[flatten]]
+        default = True
+        type = boolean
+        value = True
+
+    [[max_cycles]]
+        default = 1
+        maximum = 100
+        minimum = 0
+        type = integer
+        value = 1
+
+    [[force_multiplier]]
+        default = 1
+        maximum = 100
+        minimum = 0
+        type = float
+        value = 1
diff --git a/conf/hooke configspec.ini b/conf/hooke configspec.ini
new file mode 100644 (file)
index 0000000..f9d231c
--- /dev/null
@@ -0,0 +1,64 @@
+[drivers]
+csvdriver = boolean(default = False)
+hemingclamp = boolean(default = False)
+jpk = boolean(default = False)
+mcs = boolean(default = False)
+mfp1dexport = boolean(default = True)
+picoforce = boolean(default = True)
+picoforcealt = boolean(default = False)
+tutorialdriver = boolean(default = False)
+
+[folder]
+filterindex = integer(default = 0)
+filters = string(default = 'Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')
+
+[general]
+list = string(default = 'test.hkp')
+workdir = string(default = '')
+
+[main]
+height = integer(default = 500)
+left = integer(default = 50)
+top = integer(default = 50)
+width = integer(default = 700)
+
+[plotmanipulators]
+names(default = list(correct, median, absvalue, flatten, multiplier, clamp, threshold, coincident))
+absvalue = boolean(default = False)
+clamp = boolean(default = True)
+coincident = boolean(default = True)
+correct = boolean(default = True)
+flatten = boolean(default = True)
+median = boolean(default = True)
+multiplier = boolean(default = True)
+threshold = boolean(default = True)
+
+[perspectives]
+active = string(default = Default)
+
+[plugins]
+autopeak = boolean(default = True)
+dummyguiplug = boolean(default = False)
+fit = boolean(default = True)
+flatfilts = boolean(default = True)
+generalclamp = boolean(default = True)
+generaltccd = boolean(default = True)
+generalvclamp = boolean(default = True)
+macro = boolean(default = True)
+massanalysis = boolean(default = True)
+pcluster = boolean(default = True)
+procplots = boolean(default = True)
+superimpose = boolean(default = False)
+tutorial = boolean(default = False)
+viewer = boolean(default = True)
+
+[splashscreen]
+#duration in milliseconds
+duration = integer(default = 1000)
+show = boolean(default = True)
+
+#name = string
+#age = float
+#attributes = string_list
+#likes_cheese = boolean
+#favourite_color = string
diff --git a/conf/hooke.conf b/conf/hooke.conf
new file mode 100644 (file)
index 0000000..3741a2b
--- /dev/null
@@ -0,0 +1,113 @@
+<?xml version="1.0" ?>
+<!-- To comment something, put dashes and ! like here -->
+<config>
+
+
+<!--
+This section defines the Hooke installation.  confpath is hardcoded,
+since it's to find the config file(s) before your read them.
+    -->
+<install>
+    <docpath>
+        ./doc/
+    </docpath>
+</install>
+
+<!-- Internal variables. -->
+<display
+    ext="1"
+    colour_ext="None"
+    ret="1"
+    colour_ret="None"
+    correct="1"
+    colour_correct="None"
+    contact_point="0"
+    medfilt="0"
+    xaxes="0"
+    yaxes="0"
+    flatten="1"
+    fit_function="wlc"
+    temperature="301"
+    auto_fit_points="50"
+    auto_slope_span="20"
+    auto_delta_force="10"
+    auto_fit_nm="5"
+    auto_min_p="0.005"
+    auto_max_p="10"
+    baseline_clicks="0"
+    auto_left_baseline="20"
+    auto_right_baseline="20"
+    force_multiplier="1"
+    fc_showphase="0"
+    fc_showimposed="0"
+    fc_interesting="0"
+    tccd_threshold="0"
+    tccd_coincident="0"
+    centerzero="1"
+/>
+
+<!--
+The following section defines the default playlist to load at startup.
+    -->
+<defaultlist>
+    ./hooke/test/test.hkp
+</defaultlist>
+
+<!--
+This section defines which plugins have to be loaded by Hooke.
+     -->
+<plugins>
+    <fit/>
+    <curvetools/>
+    <procplots/>
+    <flatfilts/>
+    <generalclamp/>
+    <!-- dummyguiplug/ -->
+    <!-- superimpose/ -->
+    <generalvclamp/>
+    <massanalysis/>
+    <viewer/>
+    <!-- tutorial/ -->
+    <macro/>
+    <autopeak/>
+    <pcluster/>
+    <generaltccd/>
+    <multidistance/>
+    <jumpstat/>
+    <review/>
+    <multifit/>
+</plugins>
+
+<!--
+This section defines which drivers have to be loaded by Hooke.
+    -->
+<drivers>
+    <!--picoforce/-->
+    <picoforcealt/>
+    <hemingclamp/>
+    <csvdriver/>
+    <!-- tutorialdriver/ -->
+    <jpk/>
+    <mfp1dexport/>
+    <mcs/>
+    <!-- hdf5/ -->
+    <mfp3d/>
+</drivers>
+
+<!--
+This section defines which plot manipulators have to be loaded by Hooke,
+and -IMPORTANTLY- their order.
+    -->
+<plotmanips>
+    <correct/>
+    <median/>
+    <!-- absvalue/ -->
+    <flatten/>
+    <multiplier/>
+    <clamp/>
+    <threshold/>
+    <coincident/>
+    <centerzero/>
+</plotmanips>
+
+</config>
diff --git a/conf/hooke.ini b/conf/hooke.ini
new file mode 100644 (file)
index 0000000..fb03cb1
--- /dev/null
@@ -0,0 +1,121 @@
+#prefix with '#' to add a comment
+
+#Internal variables
+[display]
+colour_ext = None
+colour_ret = None
+ext = 1
+ret = 1
+correct = 1
+colour_correct = None
+contact_point = 0
+medfilt = 0
+xaxes = 0
+yaxes = 0
+flatten = 1
+temperature = 301
+auto_fit_points = 50
+auto_slope_span = 20
+auto_delta_force = 10
+auto_fit_nm = 5
+auto_min_p = 0.005
+auto_max_p = 10
+baseline_clicks = 0
+auto_left_baseline = 20
+auto_right_baseline = 20
+force_multiplier = 1
+fc_showphase = 0
+fc_showimposed = 0
+fc_interesting = 0
+tccd_threshold = 0
+tccd_coincident = 0
+
+[folders]
+filterindex = 0
+filters = Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')
+
+[general]
+#substitute your work directory
+#workdir = D:\hooke
+workdir = R:\Programming\Python\Gui\
+#the default playlist to load at startup
+list = test.hkp
+#the default perspective to load at startup
+perspective = Default
+#splashscreen
+splash = 1
+splashduration = 1000
+
+#this section defines which plugins have to be loaded by Hooke
+[plugins]
+autopeak = True
+dummyguiplug = False
+fit = True
+flatfilts = True
+generalclamp = False
+generaltccd = False
+generalvclamp = True
+macro = False
+massanalysis = False
+pcluster = False
+procplots = True
+showconvoluted = True
+superimpose = False
+tutorial = False
+viewer = False
+#autopeak = True
+#dummyguiplug = False
+#fit = True
+#flatfilts = True
+#generalclamp = True
+#generaltccd = True
+#generalvclamp = True
+#macro = True
+#massanalysis = True
+#pcluster = True
+#procplots = True
+#superimpose = False
+#tutorial = False
+#viewer = True
+
+#this section defines which drivers have to be loaded by Hooke
+[drivers]
+csvdriver = False
+hemingclamp = False
+jpk = False
+mcs = False
+mfp1dexport = True
+picoforce = True
+picoforcealt = False
+tutorialdriver = False
+
+##this section defines which plot manipulators have to be loaded by Hooke,
+##and -IMPORTANTLY- their order.
+[plotmanipulators]
+absvalue = False
+clamp = False
+coincident = False
+correct = True
+flatten = False
+median = False
+multiplier = False
+names = correct, median, absvalue, flatten, multiplier, clamp, threshold, coincident, showconvoluted
+showconvoluted = False
+threshold = False
+
+#this section defines the window size and position
+[main]
+height = 797
+left = 536
+top = 20
+width = 1092
+
+[splashscreen]
+#duration in milliseconds
+duration = 1000
+show = False
+
+[perspectives]
+active = Default
+
+[folder]
diff --git a/conf/macro.conf b/conf/macro.conf
new file mode 100644 (file)
index 0000000..b197e36
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" ?>
+<config>
+
+<!-- Set the location of your macros directory (for the macro plugin) -->
+<macrodir path="~/.hooke/macros" />
+</config>
diff --git a/conf/pca_config.txt b/conf/pca_config.txt
new file mode 100644 (file)
index 0000000..c3c4d3f
--- /dev/null
@@ -0,0 +1,33 @@
+1,3,6,7,8,9x15,10,11
+0.000000
+1,2,3,4,5,6,7,8,9,10,11
+debug=false
+------------------------------------------
+#1 colonne della prima pca
+#2 limite filtro della densità (x es: "0.000008"; "0" per calcolo automatico.. 3,242311147)
+#3 colonne della secona pca (non usato)
+#4 attiva modalità debug (stampa anche immagini/coordinate di pca/density)
+------------------------------------------
+str(peak_number)+     # non considerato
+str(delta_mean)+      # 0
+str(delta_median)+    # 1 -
+str(force_mean)+      # 2
+str(force_median)+    # 3 -
+str(first_peak_cl)+   # 4 -
+str(last_peak_cl)+    # 5 -
+str(max_force)+       # 6
+str(min_force)+       # 7
+str(max_delta)+       # 8
+str(min_delta)+       # 9
+str(delta_stdev)+     # 10
+str(forces_stdev)+    # 11
+str(peaks_diff)+      # 12
+------------------------------------------
+Lancio di pCluster e pca:
+ - cd 20080917_4s4wt_10mMtris_all
+ - genlist *.*
+ - setconv blindwindow 50
+ - pcluster pl=0.35
+------------------------------------------
+Lancio della sola pca
+ - pca 20080922_gb1x8_tris/pCluster_20090627_1328/coordinate_20080922_gb1x8_tris_blind50.txt
diff --git a/conf/pcluster.ini b/conf/pcluster.ini
new file mode 100644 (file)
index 0000000..91e5357
--- /dev/null
@@ -0,0 +1,10 @@
+[pcluster]
+auto_fit_nm = 5
+auto_fit_points = 50
+auto_left_baseline = 20
+auto_max_p = 10
+auto_min_p = 0.005
+auto_right_baseline = 20
+auto_slope_span = 20
+baseline_clicks = 0
+temperature = 301
diff --git a/conf/procplots.ini b/conf/procplots.ini
new file mode 100644 (file)
index 0000000..02a72ab
--- /dev/null
@@ -0,0 +1,12 @@
+[procplots]
+    [[median]]
+        default = 0
+        maximum = 100
+        minimum = 0
+        type = integer
+        value = 0
+
+    [[correct]]
+        default = True
+        type = boolean
+        value = True
diff --git a/contrib/mfp_igor_scripts/ExportMFP1D.ipf b/contrib/mfp_igor_scripts/ExportMFP1D.ipf
new file mode 100644 (file)
index 0000000..8a3a4ce
--- /dev/null
@@ -0,0 +1,185 @@
+#pragma rtGlobals=1            // Use modern global access method.
+#pragma IgorVersion = 4.0
+#pragma version = 0.4
+
+//
+// ExportMFP1D.ipf - A procedure to export force curves from MFP1D to 'hooke'
+//
+// Copyright (c) 2009 Rolf Schmidt, Montreal
+// rschmidt@alcor.concordia.ca
+// 
+// This procedure is released under the GNU General Public License version 2
+//
+
+// History
+// 2009 07 24: v0.4
+// the wave note is now correctly and fully exported in Igor 4
+// 2009 06 29: v0.3
+// split functionality into ExportMFP1DFolder and ExportMFP1DWaves
+// ExportMFP1DFolder: export individual Igor binary waves file from a folder
+// ExportMFP1DWaves: export all currently open waves to a folder
+// 2009 06 26: v0.2.1
+// added the IgorVersion pragma
+// 2009 06 19: v0.2
+// changed the filename finding algorithm to work with Igor 4 and up
+// Igor 5 users can use the code marked 'the following only works in Igor 5 and up' instead
+// the procedure now catches 'Cancel' on NewPath
+// added version information
+// 2009 05 29: v0.1
+// changed the procedure so that it runs in Igor as well as in MFP (ie Igor with MFP plug-in)
+
+// How to use ExportMFP1D()
+// - save all current waves of interest (ExportMFP1D() kills all open waves before exporting files)
+// - execute ExportMFP1D() from the command line
+// - browse to a folder containing force curves (a 'force curve' consists of two files: one 'deflection' and one 'LVDT' file
+// - ExportMFP1D() will now iterate through all the waves in the folder, extract the header information and create four columns:
+//   1: approach (x) 2: approach (y) 3: retraction (x) 4; retraction (y)
+// - the resulting files are saved in the same folder and the same base name as the original files (ie without 'deflection' and 'LVDT')
+// CAUTION:  existing files will be overwritten!
+// - these files can then be analyzed with 'hooke'
+
+Function ExportMFP1DFolder()
+
+       String sList
+       Variable iCount
+
+       // set the path (used for opening the waves and saving the output files later)
+       NewPath /O /Q /M="Choose the folder that contains the waves" PathExport1D
+
+       KillWaves /A /Z
+       
+       if(V_flag>=0)
+               // get a list of all Igor binary waves in the folder
+               sList=IndexedFile(PathExport1D,-1,".ibw")
+               // load all waves
+               for(iCount=0; iCount<ItemsInList(sList); iCount+=1)
+                       LoadWave /P=PathExport1D /Q StringFromList(iCount, sList)
+               endfor
+               ExportMFP1DWaves()
+       endif
+       // kill the export path
+       KillPath /Z PathExport1D
+End
+
+Function ExportMFP1DWaves()
+
+       String sFileName1
+       String sFileName2
+       String sLine
+       String sList
+       String sNote
+       String sWaveCombined
+       String sWaveDeflection
+       String sWaveLVDT
+       Variable iCount
+       Variable iLine
+       Variable iPoints
+       Variable iRefNum
+       Wave wApproachX
+       Wave wApproachY
+       Wave wRetractionX
+       Wave wRetractionY
+
+       // set the path (used for saving the output files later)
+       NewPath /O /Q /M="Choose the folder to save the waves" PathExport1D
+       
+       // get a list of all LVDT waves (could be deflection as well, they always come in a pair)
+       sList=WaveList("*LVDT*", ";", "")
+       
+       // iterate through all the LVDT waves
+       for(iCount=0; iCount<ItemsInList(sList); iCount+=1)
+               // create the wave names as string
+// the following only works in Igor 5 and up
+//                     sWaveLVDT=ReplaceString(".ibw",StringFromList(iCount, sList), "")
+//                     sWaveDeflection=ReplaceString("LVDT",sWaveLVDT, "deflection")
+//                     sWaveCombined=ReplaceString("LVDT",sWaveLVDT, "_")
+// END: the following only works in Igor 5 and up
+
+// the following works in Igor 4 and up
+               // treat the filename as a key-value list with '.' as a separator
+               // use the first entry (ie 0) as the filename without extension
+               sWaveLVDT=StringFromList(0, StringFromList(iCount, sList), ".")
+               
+               // treat the filename as a key-value list with 'LVDT' as a separator
+               // use the first entry (ie 0) as the first part of the filename
+               sFileName1=StringFromList(0, sWaveLVDT, "LVDT")
+               // getting the second part of the filename is a bit trickier
+               // first, we 'remove' the first part of the filename by treating it as a key
+               // using 'LVDT' as a separator 
+               sFileName2=StringByKey(sFileName1, sWaveLVDT, "LVDT")
+               // unfortunately, StringByKey only removes the first character of the separator
+               // to get the second part of the filename, we use VD as the key and 'T' as the separator
+               sFileName2=StringByKey("VD", sFileName2, "T")
+               // then we create the wave names as follows:
+               sWaveDeflection=sFileName1+"deflection"+sFileName2
+               
+               sWaveCombined=sFileName1+"_"+sFileName2
+               
+// END: the following works in Igor 4 and up
+
+               // create the waves we need
+               Wave wLVDT=$sWaveLVDT
+               Wave wDeflection=$sWaveDeflection
+       
+               // open the output text file, add extension
+               Open /P=PathExport1D iRefNum as sWaveCombined+".txt"
+
+               // create the header
+               fprintf iRefNum, "Wave:"+sWaveCombined+"\r"
+               fprintf iRefNum, "WaveLVDT:"+sWaveLVDT+"\r"
+               fprintf iRefNum, "WaveDeflection:"+sWaveDeflection+"\r"
+
+               // the number of points (use WaveStats to get them) are identical for LVDT and Deflection
+               WaveStats /q wLVDT
+               iPoints=V_npnts/2
+               fprintf iRefNum, "Rows:"+num2str(iPoints)+"\r"
+
+               // add the note to the file
+               // the notes are identical for LVDT and Deflection
+// the following only works in Igor 5 and up
+//             fprintf iRefNum, note(wDeflection)
+// END: the following only works in Igor 5 and up
+               sNote=note(wLVDT)
+               // in order to get the correct number of lines in the note, we have to specify the EOF as \r\n
+               for(iLine=0; iLine<ItemsInList(sNote, "\r\n");iLine+=1)
+                       // print every line to the output file
+                       fprintf iRefNum, StringFromList(iLine, sNote, "\r\n")
+                       // add a CR/LF for every but the last line
+                       if(iLine<ItemsInList(sNote, "\r\n")-1)
+                               fprintf iRefNum, "\r\n"
+                       endif
+               endfor
+
+               // separate the approach from the retraction
+               // by simply taking the first half of the points to be the approach
+               // and the second half to be the retraction
+               // this probably has to be changed for dual pulls
+               Duplicate /O /R=[0, iPoints] wLVDT, wApproachX
+               Duplicate /O /R=[0, iPoints] wDeflection, wApproachY
+               Duplicate /O /R=[iPoints+1] wLVDT, wRetractionX
+               Duplicate /O /R=[iPoints+1] wDeflection, wRetractionY
+
+               // create four columns line by line
+               // 1: approach x 2: approach y 3: retraction x 4: retraction y
+               for(iLine=0; iLine<iPoints; iLine+=1)
+                       sLine=num2str(wApproachX[iLine])+"\t"+num2str(wApproachY[iLine])
+                       sLine=sLine+"\t"+num2str(wRetractionX[iLine])+"\t"+num2str(wRetractionY[iLine])
+                       // add the line to the file
+                       fprintf iRefNum, "\r"+sLine
+               endfor
+
+               // save the text file to disk
+               print "Exporting "+sWaveCombined
+               Close iRefNum
+       endfor
+
+       // print message
+       print "Export completed ("+num2str(ItemsInList(sList))+" files)"
+
+       // kill the temporary waves used
+       // given the names, it is unlikely that this function will interfere with data
+       KillWaves /Z wApproachX
+       KillWaves /Z wApproachY
+       KillWaves /Z wRetractionX
+       KillWaves /Z wRetractionY
+End
diff --git a/contrib/mfp_igor_scripts/ExportMFP1DMenu.ipf b/contrib/mfp_igor_scripts/ExportMFP1DMenu.ipf
new file mode 100644 (file)
index 0000000..5087bb0
--- /dev/null
@@ -0,0 +1,4 @@
+Menu "hooke"
+       "Export folder", ExportMFP1DFolder()
+       "Export waves", ExportMFP1DWaves()
+End
diff --git a/contrib/mfp_igor_scripts/FMjoin.py b/contrib/mfp_igor_scripts/FMjoin.py
new file mode 100644 (file)
index 0000000..de40aee
--- /dev/null
@@ -0,0 +1,71 @@
+# Copyright (C) 2010 Alberto Gomez-Casado
+#                    W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+'''
+FMjoin.py
+Copies all .ibw files contained in a folder and its subfolders into a single folder. Useful for force maps.
+
+Usage: 
+python FMjoin.py origindir destdir
+'''
+
+import os
+import shutil
+import sys
+
+def main(*args):
+       if len(sys.argv) < 2:
+               print 'You must at least specify origin and destination folders.'
+               return 0
+       origin=sys.argv[1]
+       dest=sys.argv[2]
+   
+       if os.path.exists(origin):
+               if os.path.exists(dest):
+                       if os.listdir(dest)!=[]:
+                               print 'Destination folder is not empty! Use another folder.'
+                               return 0
+               else:
+                       print 'Destination folder does not exist, will create it'
+                       os.mkdir(dest)
+       else:
+               print 'You provided a wrong origin folder name, try again.'
+       
+       origin=os.path.abspath(origin)
+       dest=os.path.abspath(dest)
+       
+       for root, dirs, files in os.walk(origin):
+               for filename in files:
+                       if filename.split('.')[1]!="ibw":
+                               continue
+                       filepath=os.path.join(root,filename)
+                       #to avoid overwriting, we collapse unique paths into filenames
+                       rawdest=filepath.split(os.path.commonprefix([origin, filepath]))[1]
+                       rawdest=rawdest.replace('/','') #for linux
+                       rawdest=rawdest.replace('\\','') #for windows
+                       destfile=os.path.join(dest,rawdest)
+                       print 'Copying '+rawdest
+                       shutil.copy(filepath,destfile)
+    
+        return 0
+if __name__ == '__main__':
+    sys.exit(main(*sys.argv))
+
+
diff --git a/contrib/mfp_igor_scripts/h5export.py b/contrib/mfp_igor_scripts/h5export.py
new file mode 100644 (file)
index 0000000..4740be7
--- /dev/null
@@ -0,0 +1,68 @@
+# Copyright (C) 2010 Alberto Gomez-Casado
+#                    W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+import h5py
+import numpy
+import os
+import sys
+
+h5file=os.path.realpath(sys.argv[-1])
+h5dir=os.path.dirname(h5file)
+
+f=h5py.File(h5file)
+
+exportdir=os.path.join(h5dir,'exported')
+try:
+       os.mkdir(exportdir)
+except:
+       print 'mkdir error, maybe the export directory already exists?'
+
+def h5exportfunc(name):
+       Deflname=name
+       if Deflname.endswith('Defl'):      #search for _Defl dataset            
+               LVDTname=str.replace(Deflname,'Defl','LVDT')  #and correspondant LVDT dataset
+               Defldata=f[Deflname][:]   #store the data in local var
+               LVDTdata=f[LVDTname][:]
+               #find useful attr (springc)
+               try:
+                       notes=f[Deflname].attrs['IGORWaveNote']
+                       springmatch=notes.index("SpringConstant: ")+len("SpringConstant: ")
+                       springc=notes[springmatch:].split("\r",1)[0]  #probably extracting the leading numbers can be way more elegant than this
+                       print Deflname  
+               except:
+                       print 'Something bad happened with '+Deflname+', ignoring it'
+                       return None
+                       #returning anything but None halts the visit procedure
+               
+               fp=open(os.path.join(exportdir,name.replace('/',''))+'.txt','w')  
+               #uses the full HDF5 path (slashes out) to avoid potential overwriting             
+               #write attr
+               fp.writelines("IGP-HDF5-Hooke\n")
+               fp.writelines('SpringConstant: '+springc+'\n\n')
+               fp.writelines('App x\tApp y\tRet x\tRet y\n')
+               #write LVDT and Defl data
+               half=Defldata.size/2
+               for i in numpy.arange(0,half):
+                       fp.writelines(str(LVDTdata[i])+'\t'+str(Defldata[i])+'\t'+str(LVDTdata[i+half])+'\t'+str(Defldata[i+half])+'\n')        
+               #close the file
+               fp.close()
+               return None
+
+
+f.visit(h5exportfunc)
diff --git a/doc/.sconsrc-sphinx b/doc/.sconsrc-sphinx
new file mode 100644 (file)
index 0000000..bb1926a
--- /dev/null
@@ -0,0 +1 @@
+genrst = 'python generate-hooke-txt.py'
diff --git a/doc/SConstruct b/doc/SConstruct
new file mode 100644 (file)
index 0000000..b416976
--- /dev/null
@@ -0,0 +1,294 @@
+"""
+This is a generic SCons script for running Sphinx (http://sphinx.pocoo.org).
+
+Type 'scons -h' for help.  This prints the available build targets on your
+system, and the configuration options you can set.
+
+If you set the 'cache' option, the option settings are cached into a file
+called '.sconsrc-sphinx' in the current directory.  When running
+subsequently, this file is reread.  A file with this name is also read from
+your home directory, if it exists, so you can put global settings there.
+
+The script looks into your 'conf.py' file for information about the
+project.  This is used in various places (e.g., to print the introductory
+message, and create package files).
+
+Here's some examples.  To build HTML docs:
+
+   scons html
+
+To create a package containing HTML and PDF docs, remembering the 'install'
+setting:
+
+   scons install=html,pdf cache=True package
+
+To clean up everything:
+
+   scons -c all
+"""
+
+# Script info.
+__author__  = "Glenn Hutchings"
+__email__   = "zondo42@googlemail.com"
+__url__     = "http://bitbucket.org/zondo/sphinx-scons"
+__license__ = "BSD"
+__version__ = "0.4"
+
+import sys, os
+
+# Build targets.
+targets = (
+    ("html",      "make standalone HTML files"),
+    ("dirhtml",   "make HTML files named index.html in directories"),
+    ("pickle",    "make pickle files"),
+    ("json",      "make JSON files"),
+    ("htmlhelp",  "make HTML files and a HTML help project"),
+    ("qthelp",    "make HTML files and a qthelp project"),
+    ("devhelp",   "make HTML files and a GNOME DevHelp project"),
+    ("epub",      "make HTML files and an EPUB file for bookreaders"),
+    ("latex",     "make LaTeX sources"),
+    ("text",      "make text file for each RST file"),
+    ("pdf",       "make PDF file from LaTeX sources"),
+    ("ps",        "make PostScript file from LaTeX sources"),
+    ("dvi",       "make DVI file from LaTeX sources"),
+    ("changes",   "make an overview over all changed/added/deprecated items"),
+    ("linkcheck", "check all external links for integrity"),
+    ("doctest",   "run all doctests embedded in the documentation if enabled"),
+    ("source",    "run a command to generate the reStructuredText source"),
+)
+
+# LaTeX builders.
+latex_builders = {"pdf": "PDF", "ps": "PostScript", "dvi": "DVI"}
+
+# List of target names.
+targetnames = [name for name, desc in targets]
+
+# Configuration cache filename.
+cachefile = ".sconsrc-sphinx"
+
+# User cache file.
+homedir = os.path.expanduser('~')
+usercache = os.path.join(homedir, cachefile)
+
+# Configuration options.
+config = Variables([usercache, cachefile], ARGUMENTS)
+
+config.AddVariables(
+    EnumVariable("default", "default build target", "html", targetnames),
+    PathVariable("config", "sphinx configuration file", "conf.py"),
+    PathVariable("srcdir", "source directory", ".",
+                 PathVariable.PathIsDir),
+    PathVariable("builddir", "build directory", "build",
+                 PathVariable.PathIsDirCreate),
+    PathVariable("doctrees", "place to put doctrees", None,
+                 PathVariable.PathAccept),
+    EnumVariable("paper", "LaTeX paper size", None,
+                 ["a4", "letter"], ignorecase = False),
+    ("tags", "comma-separated list of 'only' tags", None),
+    ("builder", "program to run to build things", "sphinx-build"),
+    ("opts", "extra builder options to use", None),
+    ListVariable("install", "targets to install", ["html"], targetnames),
+    PathVariable("instdir", "installation directory", "/usr/local/doc",
+                 PathVariable.PathAccept),
+    EnumVariable("pkgtype", "package type to build with 'scons package'",
+                 "zip", ["zip", "targz", "tarbz2"], ignorecase = False),
+    BoolVariable("cache", "whether to cache settings in %s" % cachefile, False),
+    BoolVariable("debug", "debugging flag", False),
+    ("genrst", "Command to regenerate reStructuredText source", None),
+)
+
+# Create a new environment, inheriting PATH to find builder program.  Also
+# force LaTeX instead of TeX, since the .tex file won't exist at the right
+# time to check which one to use.
+env = Environment(ENV = {"PATH" : os.environ["PATH"]},
+                  TEX = "latex", PDFTEX = "pdflatex",
+                  tools = ['default', 'packaging'],
+                  variables = config)
+if 'PYTHONPATH' in os.environ:
+    env['ENV']['PYTHONPATH'] = os.environ['PYTHONPATH']
+
+# Get configuration values from environment.
+sphinxconf = env["config"]
+builder = env["builder"]
+default = env["default"]
+
+srcdir = env["srcdir"]
+builddir = env["builddir"]
+doctrees = env.get("doctrees", os.path.join(builddir, "doctrees"))
+
+cache = env["cache"]
+debug = env["debug"]
+
+options = env.get("opts", None)
+paper = env.get("paper", None)
+tags = env.get("tags", None)
+genrst = env.get("genrst", None)
+
+instdir = env["instdir"]
+install = env["install"]
+pkgtype = env["pkgtype"]
+
+# Dump internals if debugging.
+if debug:
+    print "Environment:"
+    print env.Dump()
+
+# Get parameters from Sphinx config file.
+sphinxparams = {}
+execfile(sphinxconf, sphinxparams)
+
+project = sphinxparams["project"]
+release = sphinxparams["release"]
+copyright = sphinxparams["copyright"]
+
+try:
+    texfilename = sphinxparams["latex_documents"][0][1]
+except KeyError:
+    texfilename = None
+
+name2tag = lambda name: name.replace(" ", "-").strip("()")
+project_tag = name2tag(project)
+release_tag = name2tag(release)
+package_tag = project_tag.lower() + "-" + release_tag.lower()
+
+# Build project description string.
+description = "%(project)s, release %(release)s, " \
+               "copyright %(copyright)s" % locals()
+
+Help(description + "\n\n")
+help_format = "   %-10s  %s\n"
+
+# Print banner if required.
+if not any(map(GetOption, ("silent", "clean", "help"))):
+    print
+    print "This is", description
+    print
+
+# Build sphinx command-line options.
+opts = []
+
+if tags:
+    opts.extend(["-t %s" % tag for tag in tags.split(",")])
+
+if paper:
+    opts.append("-D latex_paper_size=%s" % paper)
+
+if options:
+    opts.append(options)
+
+options = " ".join(opts)
+
+# Build Sphinx command template.
+sphinxcmd = """
+%(builder)s -b %(name)s -d %(doctrees)s %(options)s %(srcdir)s %(targetdir)s
+""".strip()
+
+# Set up LaTeX input builder if required.
+if texfilename:
+    latexdir = Dir("latex", builddir)
+    texinput = File(texfilename, latexdir)
+    env.SideEffect(texinput, "latex")
+    env.NoClean(texinput)
+
+# Add build targets.
+Help("Build targets:\n\n")
+
+if genrst != None:
+    source = env.Command('source', [], genrst, chdir = True)
+    env.AlwaysBuild(source)
+    env.Depends(srcdir, source)
+else:
+    source = env.Command(
+        'source', [],
+        '@echo "No reStructuredText generator (genrst) given."')
+
+for name, desc in targets:
+    target = Dir(name, builddir)
+    targetdir = str(target)
+
+    if name == 'source':
+        pass
+    elif name not in latex_builders:
+        # Standard Sphinx target.
+        targets = env.Command(name, sphinxconf,
+                              sphinxcmd % locals(), chdir = True)
+        env.Depends(targets, source)
+        env.AlwaysBuild(name)
+        env.Alias(target, name)
+    elif texinput:
+        # Target built from LaTeX sources.
+        try:
+            buildfunc = getattr(env, latex_builders[name])
+        except AttributeError:
+            continue
+
+        filename = project_tag + "." + name
+        outfile = File(filename, latexdir)
+
+        targets = buildfunc(outfile, texinput)
+        env.Depends(targets, source)
+
+        # Copy built file to separate directory.
+        target = File(filename, target)
+        env.Command(target, outfile, Move(target, outfile), chdir = True)
+
+        env.Alias(name, target)
+    else:
+        continue
+
+    env.Clean(name, [target])
+    env.Clean('all', target)
+
+    if name == default: desc += " (default)"
+    Help(help_format % (name, desc))
+
+Clean('all', doctrees)
+Default(default)
+
+# Add installation targets and collect package sources.
+Help("\nOther targets:\n\n")
+
+Help(help_format % ("install", "install documentation"))
+projectdir = os.path.join(instdir, project_tag)
+sources = []
+
+for name in install:
+    source = Dir(name, builddir)
+    sources.append(source)
+
+    inst = env.Install(projectdir, source)
+    env.Alias('install', inst)
+
+    for node in env.Glob(os.path.join(str(source), '*')):
+        filename = str(node).replace(builddir + os.path.sep, "")
+        dirname = os.path.dirname(filename)
+        dest = os.path.join(projectdir, dirname)
+        inst = env.Install(dest, node)
+        env.Alias('install', inst)
+
+# Add uninstall target.
+env.Command('uninstall', None, Delete(projectdir), chdir = True)
+Help(help_format % ("uninstall", "uninstall documentation"))
+
+## Add package builder.
+#packageroot = "-".join([project_tag, release_tag])
+#archive, package = env.Package(NAME = project_tag, VERSION = release,
+#                               PACKAGEROOT = packageroot,
+#                               PACKAGETYPE = pkgtype,
+#                               source = sources)
+#
+#env.AlwaysBuild(archive)
+#env.AddPostAction(archive, Delete(packageroot))
+#Help(help_format % ("package", "build documentation package"))
+#
+#env.Clean('all', archive)
+
+# Add config settings to help.
+Help("\nConfiguration variables:")
+for line in config.GenerateHelpText(env).split("\n"):
+    Help("\n   " + line)
+
+# Save local configuration if required.
+if cache:
+    config.Update(env)
+    config.Save(cachefile, env)
diff --git a/doc/bugs.txt b/doc/bugs.txt
new file mode 100644 (file)
index 0000000..9279923
--- /dev/null
@@ -0,0 +1,15 @@
+Troubleshooting
+===============
+
+If you have troubles in using Hooke:
+
+1. Search the `issue tracker`_.
+2. Look at the `trouble shooting wiki page`_.
+3. Search the `discussion group`_.
+4. Ask a question in the discussion group.
+5. File a bug on the issue tracker.
+
+.. _issue tracker: http://code.google.com/p/hooke/issues/list
+.. _trouble shooting wiki page:
+   http://code.google.com/p/hooke/wiki/TroubleShooting
+.. _discussion group: http://groups.google.com/group/hookesoftware
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644 (file)
index 0000000..43786fd
--- /dev/null
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+#
+# sphinx-scons documentation build configuration file, created by
+# sphinx-quickstart on Thu Mar 12 10:37:15 2009.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+sys.path.insert(0, os.path.abspath('..'))
+
+import hooke
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary',
+              'sphinx.ext.doctest', 'sphinx.ext.coverage',
+              'numpydoc']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.txt' #'.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Hooke'
+copyright = u'2006-2010, Massimo Sandal et al.'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = hooke.version(2)
+# The full version, including alpha/beta/rc tags.
+release = hooke.version(5)
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+html_title = project + " documentation"
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+#html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'sphinx-sconsdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'hooke.tex', ur'Hooke Documentation',
+   ur'W. Trevor King', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
diff --git a/doc/config.txt b/doc/config.txt
new file mode 100644 (file)
index 0000000..d2b8d14
--- /dev/null
@@ -0,0 +1,45 @@
+*****************
+Configuring Hooke
+*****************
+
+Hooke initializes a number of variables by reading configuration
+files.
+
+Syntax
+------
+
+Hooke uses Python's configparser_ library to read the config files.
+The format is similar to MS Windows' INI (initialization) format::
+
+    [My Section]
+    foo = bar
+    baz = 5
+
+.. _configparser: http://docs.python.org/library/configparser.html
+
+Examples
+--------
+
+Run::
+
+    $ hooke --example-config-file
+
+To print a well commented example config file to stdout.
+
+
+Finding configuration files
+---------------------------
+
+The default search path follows the `Filesystem Hierarchy Standard`_,
+and so will probably need adjustment for non-\*nix systems.  The
+default path list is
+
+* :file:`/usr/share/hooke/hooke.cfg`
+* :file:`/etc/hooke/hooke.cfg`
+* :file:`~/.hooke.cfg`
+* :file:`./.hooke.cfg`
+
+but alternatives can be specified from the command line launching
+Hooke.
+
+.. _Filesystem Hierarchy Standard: http://www.pathname.com/fhs/
diff --git a/doc/doc.txt b/doc/doc.txt
new file mode 100644 (file)
index 0000000..4c8b41f
--- /dev/null
@@ -0,0 +1,30 @@
+****************************
+Producing this documentation
+****************************
+
+This documentation is written in reStructuredText_, and produced using
+Sphinx_ and the numpydoc_ extension.  The documentation source should
+be fairly readable without processing, but to compile the
+documentation, change to the `doc/` directory and run::
+
+    $ scons
+
+For which you'll need to install Sphinx, numpydoc, and SCons_::
+
+    $ easy_install Sphinx
+    $ easy_install numpydoc
+
+.. _Sphinx: http://sphinx.pocoo.org/
+.. _numpydoc: http://pypi.python.org/pypi/numpydoc
+.. _SCons: http://www.scons.org/
+
+See the reStructuredText quick reference and the `NumPy/SciPy
+documentation guide`_ for an introduction to the documentation
+syntax.  See the `Sphinx markup documentation`_ for a discussion
+of Sphinx's reStructuredText extensions.
+
+.. _reStructuredText:
+  http://docutils.sourceforge.net/docs/user/rst/quickref.html
+.. _NumPy/SciPy documentation guide:
+  http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines
+.. _Sphinx markup documentation: http://sphinx.pocoo.org/markup.html
diff --git a/doc/generate-hooke-txt.py b/doc/generate-hooke-txt.py
new file mode 100644 (file)
index 0000000..fb91500
--- /dev/null
@@ -0,0 +1,337 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Auto-generate reStructuredText of the hooke module tree for Sphinx.
+
+This script is adapted from one written for `Bugs Everywhere`_.
+
+.. _Bugs Everywhere: http://bugseverywhere.org/
+"""
+
+import sys
+import os, os.path
+
+
+sys.path.insert(0, os.path.abspath('..'))
+
+
+def title(modname):
+    t = ':mod:`%s`' % modname
+    delim = '*'*len(t)
+    return '\n'.join([delim, t, delim, '', ''])
+
+def automodule(modname):
+    return '\n'.join([
+            '.. automodule:: %s' % modname,
+            '   :members:',
+            '   :undoc-members:',
+            '', ''])
+
+def toctree(children):
+    if len(children) == 0:
+        return ''
+    return '\n'.join([
+            '.. toctree::',
+            '   :maxdepth: 2',
+            '',
+            ] + [
+            '   %s.txt' % c for c in sorted(children)
+            ] + ['', ''])
+
+def make_module_txt(modname, children):
+    filename = os.path.join('hooke', '%s.txt' % modname)
+    if not os.path.exists('hooke'):
+        os.mkdir('hooke')
+    if os.path.exists(filename):
+        return None # don't overwrite potentially hand-written files.
+    f = file(filename, 'w')
+    f.write(title(modname))
+    f.write(automodule(modname))
+    f.write(toctree(children))
+    f.close()
+
+
+
+class Tree(list):
+    """A traversable tree structure.
+
+    Examples
+    --------
+
+    Construct::
+
+               +-b---d-g
+             a-+   +-e
+               +-c-+-f-h-i
+
+    with
+
+    >>> i = Tree();       i.n = "i"
+    >>> h = Tree([i]);    h.n = "h"
+    >>> f = Tree([h]);    f.n = "f"
+    >>> e = Tree();       e.n = "e"
+    >>> c = Tree([f,e]);  c.n = "c"
+    >>> g = Tree();       g.n = "g"
+    >>> d = Tree([g]);    d.n = "d"
+    >>> b = Tree([d]);    b.n = "b"
+    >>> a = Tree();       a.n = "a"
+    >>> a.append(c)
+    >>> a.append(b)
+
+    Get the longest branch length with
+
+    >>> a.branch_len()
+    5
+
+    Sort the tree recursively.  Here we sort longest branch length
+    first.
+
+    >>> a.sort(key=lambda node : -node.branch_len())
+    >>> "".join([node.n for node in a.traverse()])
+    'acfhiebdg'
+
+    And here we sort shortest branch length first.
+
+    >>> a.sort(key=lambda node : node.branch_len())
+    >>> "".join([node.n for node in a.traverse()])
+    'abdgcefhi'
+
+    We can also do breadth-first traverses.
+
+    >>> "".join([node.n for node in a.traverse(depth_first=False)])
+    'abcdefghi'
+
+    Serialize the tree with depth marking branches.
+
+    >>> for depth,node in a.thread():
+    ...     print "%*s" % (2*depth+1, node.n)
+    a
+      b
+        d
+          g
+      c
+        e
+        f
+          h
+            i
+
+    Flattening the thread disables depth increases except at
+    branch splits.
+
+    >>> for depth,node in a.thread(flatten=True):
+    ...     print "%*s" % (2*depth+1, node.n)
+    a
+      b
+      d
+      g
+    c
+      e
+    f
+    h
+    i
+
+    We can also check if a node is contained in a tree.
+
+    >>> a.has_descendant(g)
+    True
+    >>> c.has_descendant(g)
+    False
+    >>> a.has_descendant(a)
+    False
+    >>> a.has_descendant(a, match_self=True)
+    True
+    """
+    def __cmp__(self, other):
+        return cmp(id(self), id(other))
+
+    def __eq__(self, other):
+        return self.__cmp__(other) == 0
+
+    def __ne__(self, other):
+        return self.__cmp__(other) != 0
+
+    def branch_len(self):
+        """Return the largest number of nodes from root to leaf (inclusive).
+
+        For the tree::
+
+               +-b---d-g
+             a-+   +-e
+               +-c-+-f-h-i
+
+        this method returns 5.
+
+        Notes
+        -----
+        Exhaustive search every time == *slow*.
+
+        Use only on small trees, or reimplement by overriding
+        child-addition methods to allow accurate caching.
+        """
+        if len(self) == 0:
+            return 1
+        else:
+            return 1 + max([child.branch_len() for child in self])
+
+    def sort(self, *args, **kwargs):
+        """Sort the tree recursively.
+
+        This method extends :meth:`list.sort` to Trees.
+
+        Notes
+        -----
+        This method can be slow, e.g. on a :meth:`branch_len` sort,
+        since a node at depth `N` from the root has it's
+        :meth:`branch_len` method called `N` times.
+        """
+        list.sort(self, *args, **kwargs)
+        for child in self:
+            child.sort(*args, **kwargs)
+
+    def traverse(self, depth_first=True):
+        """Generate all the nodes in a tree, starting with the root node.
+
+        Parameters
+        ----------
+        depth_first : bool
+          Depth first by default, but you can set `depth_first` to
+          `False` for breadth first ordering.  Siblings are returned
+          in the order they are stored, so you might want to
+          :meth:`sort` your tree first.
+        """
+        if depth_first == True:
+            yield self
+            for child in self:
+                for descendant in child.traverse():
+                    yield descendant
+        else: # breadth first, Wikipedia algorithm
+            # http://en.wikipedia.org/wiki/Breadth-first_search
+            queue = [self]
+            while len(queue) > 0:
+                node = queue.pop(0)
+                yield node
+                queue.extend(node)
+
+    def thread(self, flatten=False):
+        """Generate a (depth, node) tuple for every node in the tree.
+
+        When `flatten` is `False`, the depth of any node is one
+        greater than the depth of its parent.  That way the
+        inheritance is explicit, but you can end up with highly
+        indented threads.
+
+        When `flatten` is `True`, the depth of any node is only
+        greater than the depth of its parent when there is a branch,
+        and the node is not the last child.  This can lead to ancestry
+        ambiguity, but keeps the total indentation down.  For example::
+
+                      +-b                  +-b-c
+                    a-+-c        and     a-+
+                      +-d-e-f              +-d-e-f
+
+        would both produce (after sorting by :meth:`branch_len`)::
+
+            (0, a)
+            (1, b)
+            (1, c)
+            (0, d)
+            (0, e)
+            (0, f)
+
+        """
+        stack = [] # ancestry of the current node
+        if flatten == True:
+            depthDict = {}
+
+        for node in self.traverse(depth_first=True):
+            while len(stack) > 0 \
+                    and id(node) not in [id(c) for c in stack[-1]]:
+                stack.pop(-1)
+            if flatten == False:
+                depth = len(stack)
+            else:
+                if len(stack) == 0:
+                    depth = 0
+                else:
+                    parent = stack[-1]
+                    depth = depthDict[id(parent)]
+                    if len(parent) > 1 and node != parent[-1]:
+                        depth += 1
+                depthDict[id(node)] = depth
+            yield (depth,node)
+            stack.append(node)
+
+    def has_descendant(self, descendant, depth_first=True, match_self=False):
+        """Check if a node is contained in a tree.
+
+        Parameters
+        ----------
+        descendant : Tree
+          The potential descendant.
+        depth_first : bool
+          The search order.  Set this if you feel depth/breadth would
+          be a faster search.
+        match_self : bool
+          Set to `True` for::
+
+              x.has_descendant(x, match_self=True) -> True
+        """
+        if descendant == self:
+            return match_self
+        for d in self.traverse(depth_first):
+            if descendant == d:
+                return True
+        return False
+
+
+def python_tree(root_path='hooke', root_modname='hooke'):
+    tree = Tree()
+    tree.path = root_path
+    tree.parent = None
+    stack = [tree]
+    while len(stack) > 0:
+        f = stack.pop(0)
+        if f.path.endswith('.py'):
+            f.name = os.path.basename(f.path)[:-len('.py')]
+        elif os.path.isdir(f.path) \
+                and os.path.exists(os.path.join(f.path, '__init__.py')):
+            f.name = os.path.basename(f.path)
+            f.is_module = True
+            for child in os.listdir(f.path):
+                if child == '__init__.py':
+                    continue
+                c = Tree()
+                c.path = os.path.join(f.path, child)
+                c.parent = f
+                stack.append(c)
+        else:
+            continue
+        if f.parent == None:
+            f.modname = root_modname
+        else:
+            f.modname = f.parent.modname + '.' + f.name
+            f.parent.append(f)
+    return tree
+
+if __name__ == '__main__':
+    pt = python_tree(root_path='../hooke', root_modname='hooke')
+    for node in pt.traverse():
+        print node.modname
+        make_module_txt(node.modname, [c.modname for c in node])
diff --git a/doc/gui.txt b/doc/gui.txt
new file mode 100644 (file)
index 0000000..25c0f48
--- /dev/null
@@ -0,0 +1,32 @@
+*****************
+The GUI interface
+*****************
+
+by Rolf Schmidt <rschmidt@alcor.concordia.ca>
+
+You can dock/undock all windows except for plot tab (I would like to
+disable docking above the menubar and instead have the windows dock
+beneath the menubar)
+
+You can save perspectives (delete perspectives however does not work yet).
+
+The 'Navigation' menubar works, the 'Main' menubar does not work yet.
+
+Closing plot tabs does not work properly, feedback on specific
+examples and error messages would be much appreciated
+
+In the 'Folders' pane you can double-click playlists (with hkp
+extension) to open them (you cannot double-click curves to open them)
+
+Commands in the 'Commands'-pane that work
+
+* autopeak (only partially, everything that has to do with clicking on the plot does not work)
+* genlist
+* loadlist
+* convfilt
+* flatfilt
+
+When you open or generate a playlist, Hooke seems to hang. This is
+actually not the case, just wait. Hooke loads all the curves into
+memory and applies all the plotmanipulators. In the future, there will
+be a progressbar to indicate the progress.
diff --git a/doc/hacking.txt b/doc/hacking.txt
new file mode 100644 (file)
index 0000000..fb9a7d4
--- /dev/null
@@ -0,0 +1,134 @@
+*******
+Hacking
+*******
+
+.. toctree::
+   :maxdepth: 2
+
+   testing
+
+Dependencies
+============
+
+To clean up the internals, were going to go crazy on the
+object-oriented front, and try to keep the core functionality free of
+any dependencies other than the `Python Standard Library`_ and Numpy_
+/ Scipy_.
+
+.. _Python Standard Library: http://docs.python.org/library/
+.. _Numpy: http://numpy.scipy.org/
+.. _Scipy: http://www.scipy.org/
+
+To make a responsive user interface in parallel with data processing
+and possible GUIs, we'll use Python's multiprocessing_ module.  This
+module is new in Python 2.6, but 2.6 usage is becoming fairly
+widespread.  Certainly more widespread than any alternative queue
+module that I know of.  Since we're leveraging the power of the
+standard library, we use configparser_ for the config files.
+
+.. _multiprocessing: http://docs.python.org/dev/library/multiprocessing.html
+.. _configparser: http://docs.python.org/library/configparser.html
+
+On the testing side, the need to stick to the standard library relaxes
+(developers can install extra packages), so we can use nose_.  See
+the :doc:`testing` section for more information.
+
+.. _nose: http://somethingaboutorange.com/mrl/projects/nose/0.11.3/
+
+
+Architecture
+============
+
+Hooke's main entry point is :class:`~hooke.hooke.Hooke`.
+:class:`~hooke.hooke.Hooke` reads in the configuration files and loads
+Plugins_ and Drivers_.  Then it forks off a
+:class:`~hooke.engine.CommandEngine` instance to execute Commands_,
+and a :class:`~hooke.ui.UserInterface` instance to connect the
+:class:`~hooke.engine.CommandEngine` with the user.  The
+:class:`~hooke.engine.CommandEngine` runs in a subprocess, which
+allows command execution to occur in parallel with
+:class:`~hooke.ui.UserInterface` interaction.  The two processes
+communicate via two :class:`multiprocessing.Queue`\s.
+
+There are a number of special classes availiable to structure queue
+communications.  See :mod:`~hooke.interaction` and
+:class:`~hooke.command.CommandExit` for details.
+
+Plugins
+-------
+
+:class:`~hooke.plugin.Plugin`\s contain bundles of Commands_,
+representing the various operations a user can carry out through the
+Hooke interface.
+
+:class:`~hooke.plugin.Plugin`\s can depend on other
+:class:`~hooke.plugin.Plugin`\s, so you shouldn't need to repeat code.
+One central :class:`~hooke.plugin.Plugin` can provide useful
+functionality to several dependent :class:`~hooke.plugin.Plugin`\s.
+
+There is a :class:`~hooke.plugin.Plugin` subtype
+:class:`~hooke.plugin.Builtin` which is just like a
+:class:`~hooke.plugin.Plugin`, but is considered fundamental enough to
+not be optional.  :class:`~hooke.plugin.Builtin`\s are always loaded.
+
+Commands
+~~~~~~~~
+
+:class:`~hooke.command.Command`\s specify user-actions in an
+interface-agnostic manner.  This makes writing
+:class:`~hooke.ui.UserInterface`\s easier, because you don't need to
+know anything about particular :class:`~hooke.plugin.Plugin`\s or
+:class:`~hooke.command.Command`\s, you just need to be able to explain
+the base classes for you user and then speak the language of
+:mod:`~hooke.interaction` and :class:`~hooke.command.CommandExit` with
+the :class:`~hooke.engine.CommandEngine` process.
+
+Drivers
+-------
+
+:class:`~hooke.driver.Driver`\s are responsible for reading assorted
+data files into Hooke's Data_ structure.
+
+Data
+----
+
+Experiments
+~~~~~~~~~~~
+
+Force spectroscopy experiments come in several major flavors.  Each
+flavor gets its own subclass of :class:`~hooke.experiment.Experiment`
+in :mod:`~hooke.experiment`.  For example, force clamp experiments are
+:class:`~hooke.experiment.ForceClamp`.  This gives Drivers_ a way to
+tag experimental data so Commands_ know what they are working with.
+
+Curves
+~~~~~~
+
+Experiments_ tags need a data-holding container to tag, and
+:class:`~hooke.curve.Curve`\s are that container.  Each
+:class:`~hooke.curve.Curve` can hole several blocks of
+:class:`~hooke.curve.Data` (for example approach and retract curves in
+a :class:`~hooke.experiment.VelocityClamp` experiment would be
+seperate blocks).  :class:`~hooke.curve.Curve`\s also have an
+:attr:`~hooke.curve.Curve.info` attribute for persistently storing
+arbitrary data.
+
+Playlists
+~~~~~~~~~
+
+Normally you'll want to analyze multiple Curves_ in one session.
+:class:`~hooke.playlist.Playlist`\s provide a convenient container for
+Curves_, and the subclass :class:`~hooke.playlist.FilePlaylist` add
+persistant file backing (save, load, etc.).
+
+Utilities
+---------
+
+There are a number of general coding features we need for Hooke that
+are not provided by Python's standard library.  We factor those
+features out into :mod:`~hooke.util`.
+
+There are also a number of features who's standard library support
+changes with different versions of Python.  :mod:`~hooke.compat`
+provides a uniform interface to those tools so that Hooke will work
+with several Python versions.
diff --git a/doc/img/hooke.jpg b/doc/img/hooke.jpg
new file mode 100644 (file)
index 0000000..e7d6d1f
Binary files /dev/null and b/doc/img/hooke.jpg differ
diff --git a/doc/index.txt b/doc/index.txt
new file mode 100644 (file)
index 0000000..b370dc6
--- /dev/null
@@ -0,0 +1,63 @@
+Welcome
+=======
+
+Hooke_ is software for the (semiautomatic) analysis and filtering of
+force curves.  Force curves are the output of an analytical technique
+called `force spectroscopy`_.  Force spectroscopy experiments usually
+require the analysis of thousands of force curves at a time. As of
+today, there is no standard, free software for the analysis of force
+curves. Hooke aims to solve that.
+
+.. _Hooke: http://code.google.com/p/hooke/
+.. _force spectroscopy: http://en.wikipedia.org/wiki/Force_spectroscopy
+
+Features:
+
+* View, annotate, and measure force curves
+* Worm-like chain and freely-jointed chain fits of force peaks
+* Automatic convolution-based filtering of empty curves
+* Automatic fit and measurement of multiple force peaks
+* Handles force-clamp force experiments (experimental)
+* Extensible by users by mean of plugins and drivers 
+
+Contents
+========
+
+.. toctree::
+   :maxdepth: 2
+
+   install
+   tutorial
+   config
+   bugs
+   hacking
+   hooke/hooke
+   doc
+
+Indices and tables
+------------------
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
+Publications
+============
+
+Hooke has been published [#sandal2009]_. Please cite Hooke if you use
+it.  Let us know, and we'll link to your paper!
+
+
+.. [#sandal2009] M. Sandal, F. Benedetti, M. Brucale, A. Gomez-Casado,
+  B. Samorì.
+  "Hooke: an open software platform for force spectroscopy."
+  Bioinformatics, 2009.
+  doi: `10.1093/bioinformatics/btp180 <http://dx.doi.org/10.1093/bioinformatics/btp180>`_
+
+Disclaimer
+==========
+
+Remember that Hooke is still experimental software!  It has been
+mostly done to fit the needs of its authors, and there is no guarantee
+it will do what you need.  However, you can write us/help us improve
+it so that it does.
diff --git a/doc/install.txt b/doc/install.txt
new file mode 100644 (file)
index 0000000..b6ed383
--- /dev/null
@@ -0,0 +1,77 @@
+****************
+Installing Hooke
+****************
+
+Dependencies
+============
+
+Hooke is routinely run successfully on Windows and Linux based
+systems. It is possible to run it happily on Mac OS X too (though
+install can be a bit trickier). Technically, Hooke should run wherever
+the Python_ programming language is installed.
+
+You'll need the following Python modules:
+
+* Numpy_
+* Scipy_
+* Matplotlib_ (for generating plots)
+* wxPython_ (for the GUI)
+
+.. _Numpy: http://numpy.scipy.org/
+.. _Scipy: http://www.scipy.org/
+.. _Python: http://www.python.org/
+.. _Matplotlib: http://matplotlib.sourceforge.net/
+.. _wxPython: http://www.wxpython.org/
+
+Getting the source
+==================
+
+Hooke_ is available as an Subversion_ repository::
+
+    $ svn checkout http://hooke.googlecode.com/svn/trunk/ hooke
+
+There is also a `GUI fork`_ (Rolf Schmidt)::
+
+    $ svn checkout http://hooke.googlecode.com/svn/branches/illysam/ hooke
+
+And a `fork`_ versioned in Mercurial_ (W. Trevor King)::
+
+    $ hg clone http://www.physics.drexel.edu/~wking/code/hg/hooke/ hooke
+
+There are also periodic bundled releases.  See the homepage for each
+fork for details.  For example, get the most recent snapshot of
+Trevor's fork in zip form with::
+
+     $ wget -O hooke.zip http://www.physics.drexel.edu/~wking/code/hg/hgwebdir.cgi/hooke/archive/tip.zip
+     $ unzip hooke.zip
+
+.. _Hooke: http://code.google.com/p/hooke/
+.. _GUI fork: http://code.google.com/p/hooke/wiki/HookeGUI
+.. _fork: http://www.physics.drexel.edu/~wking/code/hg/hgwebdir.cgi/hooke/
+.. _Subversion: http://subversion.tigris.org/
+.. _Mercurial: http://mercurial.selenic.com/
+
+
+Installation
+============
+
+Run::
+
+    $ python setup.py install
+
+to install Hooke.  Run::
+
+    $ python setup.py install --help
+
+to see a list of installation options you may want to configure.
+
+Running Hooke from the source directory
+=======================================
+
+If you like, you can avoid installation by running Hooke directly from
+it's source directory::
+
+     $ wget -o hooke.zip http://www.physics.drexel.edu/~wking/code/git/git.php?p=hooke.git&dl=zip&h=HEAD
+     $ unzip hooke.zip
+     $ cd hooke
+     $ python bin/hooke
diff --git a/doc/testing.txt b/doc/testing.txt
new file mode 100644 (file)
index 0000000..d5e9491
--- /dev/null
@@ -0,0 +1,25 @@
+*******
+Testing
+*******
+
+Hooke's test framework is build using doctest_, unittest_, and nose_.
+``nosetests`` (from the nose_ package) scans through the source tree,
+searching out the various tests and running them.  If you aren't
+familiar with nose_, there is excellent documentation on its home
+page.  We use nose_ because its auto-discovery allows us to avoid
+collecting all of our assorted tests into
+:class:`unittest.TestSuite`\s and running them by hand.
+
+To run the test suite from the Hooke installation directory, just use::
+
+    nosetests --with-doctest --doctest-tests
+
+.. _doctest: http://docs.python.org/library/doctest.html
+.. _unittest: http://docs.python.org/library/unittest.html
+.. _nose: http://somethingaboutorange.com/mrl/projects/nose/0.11.3/
+
+
+Adding tests to modules
+-----------------------
+
+Just go crazy with doctests and unittests; nose_ will find them.
diff --git a/doc/tutorial.txt b/doc/tutorial.txt
new file mode 100644 (file)
index 0000000..6a85383
--- /dev/null
@@ -0,0 +1,281 @@
+********
+Tutorial
+********
+
+`A short video showing Hooke in action`_! (courtesy of Fabrizio
+Benedetti, EPFL, Lausanne)
+
+.. _A short video showing Hooke in action:
+  https://documents.epfl.ch/users/f/fb/fbenedet/www/hooke_short_demostration.ogv
+
+.. toctree::
+   :maxdepth: 2
+
+   gui
+
+Introduction
+============
+
+This tutorial will focus on the command-line interface as the most
+powerful, and leave the :doc:`gui` interface to another document.
+
+.. _command-line: `Command-line interface`_
+
+Installation
+============
+
+See :doc:`install` for details on downloading and installing Hooke.
+
+
+Command-line interface
+======================
+
+Running the hooke shell
+-----------------------
+
+Hooke has a set of commands that depend on the loaded
+:class:`hooke.plugin.Plugin`\s.  To access these commands, you'll need
+to run the Hooke shell.::
+
+    $ hooke
+
+If you are running hooke from the source directory (see
+:doc:`install`), the equivalent command is::
+
+    $ python bin/hooke
+
+You may need to give the full path for Python on Windows systems.
+
+As Hooke launches, you should see something like the following in your
+terminal::
+
+    Hooke version 0.8.0 Seinei
+
+    COPYRIGHT
+    ----
+    hooke>
+
+The final line, ``hooke>``, is the Hooke prompt.  It allows you to
+enter commands to interact with the interpreter.
+
+Help
+----
+
+All commands have help text explaining their purpose and usage.  The
+text is stored in the code itself, and therefore more likely to be up
+to date than this tutorial.  You can get a list of commands and topics
+with::
+
+    hooke> help
+
+Or see specific help on ``TOPIC`` with::
+
+    hooke> help TOPIC
+
+for example::
+
+    hooke> help current
+
+will give help on the ``current`` command.
+
+Creating a playlist
+-------------------
+
+To start analyzing your curves, you first have to build a playlist. The
+playlist is just an index of the force curve files you want to
+analyze. Imagine it as a music playlist (that’s why it is called a
+playlist), but with data files instead of audio files.
+
+Suppose you have 100 PicoForce curve files in your curves directory,
+starting from :file:`mycurve.000` and ending in :file:`mycurve.100`
+and you want to analyze them all.
+
+You then can ``cd`` (change directory) to the directory::
+
+    hooke> cd c:\curves
+
+Type ``pwd`` (print working directory) to check the directory is correct.::
+
+    hooke> pwd
+    c:\curves
+
+You can list the files in the directory using ``ls`` or ``dir``
+(they’re synonyms).::
+
+    hooke> ls
+    [’mycurve.000’, ’mycurve.001’, ...
+    ]
+
+Now you are ready to generate the playlist. The command to use is
+``genlist``.::
+
+    hooke> genlist mycurve.*
+
+You can also generate a playlist containing all what you find in the
+directory by typing:
+
+    hooke> genlist c:\curves
+
+If you want to select what curves to see, based on the filename, you
+can use wildcards.  For example::
+
+    hooke> genlist mycurve.05*
+
+will take only curves from :file:`mycurve.050` to :file:`mycurve.059`.
+
+Note that by using ``genlist`` you just generate the playlist in the
+local session. To save your playlist to a file for future reuse,
+type::
+
+    hooke> savelist mylist
+
+In this example, the list will be saved in the file
+:file:`mylist.hkp`.  Hooke will add the extension ``.hkp`` (Hooke
+playlist) to the playlist if you forget to.  The ``.hkp`` file is an
+XML file you can read and edit with any text editor (i.e. Wordpad), if
+needed.  If you want to load it, simply issue ``loadlist mylist.hkp``
+or ``loadlist mylist``, Hooke will add ``.hkp`` if necessary.
+
+If, generating the playlist, you are including by chance a non-force
+curve file that Hooke cannot open, Hooke will print an error and
+continue on.
+
+Navigating the playlist
+-----------------------
+
+Now you can navigate through your playlist using the commands `next`
+and `previous` or, their aliases `n` and `p`. You don’t need to
+type `n` every time to run along a list of curves.  If you press
+Return to an empty prompt, Hooke will repeat the last command you
+issued explicitly.  You can also navigate through the command history
+by using the up and down arrows.  From the last curve of your
+playlist, `n` will wrap around to the first curve.  Analogously,
+issuing `p` at the first curve will jump to the last.
+
+You can also jump to a given curve::
+
+    hooke> jump c:\curves\mycurve.012
+
+where the path can be either an absolute path, or a path relative to
+the directory holding the playlist file.
+
+Taking notes
+------------
+
+You can take notes about the curves you are looking at.  Just type
+`note` followed by the text you want to append to that curve.  Hooke
+will save the text in your current playlist and in an external log
+file.  The output will look like this::
+
+    Notes taken at Sun Sep 17 20:42:07 2006
+    /home/cyclopia/work/tris/20060620a.041 |             This is a note
+    /home/cyclopia/work/tris/20060620a.207 |             This is another note
+    /home/cyclopia/work/tris/20060620a.286 |             This is a third one
+
+The log file name can be configured (:doc:`config`), but it defaults
+to :file:`hooke.log`.
+
+Usually curves you annotated are useful later.  You can copy the curves
+you annotated to a different directory by using the ``copylog``
+command.
+
+    hooke> copylog c:\nicecurves
+
+will copy all curves you have annotated to the :file:`c:\nicecurves`
+directory.  Make sure that the directory already exists before doing
+that.  TODO: replace with::
+
+    hooke> copylist --log c:\curves\nice.hkp
+
+Exporting curves
+----------------
+
+You can export Hooke curves as images and as text columns. To export
+as images, issue the ``export`` command followed by the filename.
+Supported formats are PNG (raster) and EPS (Encapsulated Postscript,
+vector).  The export format is determined by the filename extension,
+so ``export foo.png`` and ``export foo.eps`` will save PNG and EPS
+files respectively.
+
+To export as text, use the ``txt`` command, followed by the
+filename. The output is a text file containing columns (first two are
+X and Y of extension, second two are X and Y of retraction).
+
+TODO: multiple cycles?  Solution: blank lines for "breaks", add option
+to extract specific sections using Python's slice notation.
+
+
+Interacting with the plot
+-------------------------
+
+(no plots in command line mode...)
+
+Measuring distances and forces
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can easily zoom in the plot by dragging a rectangle on it with the
+left mouse button.  To zoom out, click the right mouse
+button. Sometimes by zooming in and out too much, you can lose the
+picture (this is probably a small bug in Matplotlib).  Just type
+``plot`` at the command line and the curve will be refreshed.
+
+You can measure distances and forces directly in the plot. Just issue
+the command ``distance``.  You will be asked to click two points.
+When you click a point, a blue dot should appear.  When you click the
+second point, the distances (in nanometers and piconewtons) will
+appear on the command line.  You can use ``delta`` if you prefer,
+which gives meaningful values for every kind of graph (not only force
+curves). If you want to know the coordinates of a single point, use
+``point``.
+
+Hooke automatically adjusts the position of the clicked point to the
+nearest point in the graph, so you will be always measuring distances
+and forces between points in the graph.
+
+The commands ``force`` and ``distance`` are present in the
+``generalvclamp`` plugin.
+
+Worm like chain and freely jointed chain fitting
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can measure by hand the parameters relative to a force peak using
+a worm-like chain fitting with the ``fit`` command.  The command by
+default automatically finds the contact point, asks for two points
+delimiting the portion to fit, and performs a two-variable fit, with
+contour length, persistence length, and their relative errors as
+output.  If desired, one can use the ``noauto`` option to manually
+click the contact point, and/or the ``pl=NUMBER`` options to impose a
+specific persistence or Kuhn length (in nanometers). You can choose
+which model to use with ``set fit_function wlc`` or ``set fit_function
+fjc``.  See the help of the ``fit`` command from the Hooke command
+line for details.
+
+Multiple curve fitting and measuring
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can cycle through all your current playlist obtaining WLC fit, FJC
+fit, rupture force and slope (loading rate) information from each
+curve using the ``multifit`` command.  The collected data can be saved
+in a text file for further analysis in your favourite spreadsheet or
+statistical program.  If you want to check your parameters on the
+current curve before fitting all the files in your playlist, use
+``multifit justone``.  See the ``multifit`` help for more options.
+
+Fast curve reviewing and saving
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When automatic routines are not good enough to filter your data, use
+``review`` command to cycle through your playlist presenting ten
+curves in the same graph.  You can then enter the numbers of the
+interesting curves and automatically save a copy of them into another
+directory.
+
+Configuring Hooke
+-----------------
+
+You can set environment variables to influence the behaviour of
+Hooke. The command to use is ``set``.
+
+You can alter permanently the behaviour of Hooke by setting these
+variables in a Hooke configuration file.  See :doc:`config` for
+details.
diff --git a/hooke/__init__.py b/hooke/__init__.py
new file mode 100644 (file)
index 0000000..f0447ad
--- /dev/null
@@ -0,0 +1,93 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""The hooke module does all the legwork for Hooke_.
+
+.. _hooke: http://code.google.com/p/hooke/
+
+To facilitate faster loading, submodules are not imported by default.
+The available submodules are:
+
+* :mod:`hooke.config`
+* :mod:`hooke.compat`
+"""
+
+__version__ = (0, 9, 0, 'devel', None, 'Kenzo')
+"""Version tuple::
+
+    (major, minor, release, type, patch, name)
+
+Where 
+
+  * type: Python uses alpha, beta, candidate, and final.  Whatever
+    so long as the alphabetic sort gets them in the right order.
+  * patch: either manually incremented for each release, the packaging
+    date string, YYYYMMDD, date of last commit, whatever.
+
+See `Greg Noel's post on scons-devel`_ for a good explaination of why this
+versioning scheme is a good idea.
+
+.. _Greg Noel's post on scons-devel
+  http://thread.gmane.org/gmane.comp.programming.tools.scons.devel/8740
+"""
+
+def version(depth=-1, version_tuple=None):
+    """Return a nicely formatted version string.::
+
+        major.minor.release.type[.patch] (name)
+
+    Examples
+    --------
+
+    Since I seem to be unable to override __version__ in a Doctest,
+    we'll pass the version tuple in as an argument.  You can ignore
+    `version_tuple`.
+
+    >>> v = (1, 2, 3, 'devel', '20100501', 'Kenzo')
+
+    If depth -1, a full version string is returned
+
+    >>> version(depth=-1, version_tuple=v)
+    '1.2.3.devel.20100501 (Kenzo)'
+
+    Otherwise, only the first depth fields are used.
+
+    >>> version(depth=3, version_tuple=v)
+    '1.2.3'
+    >>> version(depth=4, version_tuple=v)
+    '1.2.3.devel'
+
+    Here's an example dropping the patch.
+
+    >>> v = (1, 2, 3, 'devel', None, 'Kenzo')
+    >>> version(depth=-1, version_tuple=v)
+    '1.2.3.devel (Kenzo)'
+    """
+    if version_tuple == None:
+        version_tuple = __version__
+    patch_index = 4
+    if version_tuple[patch_index] == None: # No patch field, drop that entry
+        version_tuple = version_tuple[0:patch_index] \
+            + version_tuple[patch_index+1:]
+        if depth >= patch_index:
+            depth -= 1
+    fields = version_tuple[0:depth]
+    string = '.'.join([str(x) for x in fields])
+    if depth == -1 or depth == len(version_tuple):
+        string += ' (%s)' % version_tuple[-1]
+    return string
diff --git a/hooke/command.py b/hooke/command.py
new file mode 100644 (file)
index 0000000..c822cc5
--- /dev/null
@@ -0,0 +1,293 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""The `command` module provides :class:`Command`\s and
+:class:`Argument`\s for defining commands.
+
+It also provides :class:`CommandExit` and subclasses for communicating
+command completion information between
+:class:`hooke.engine.CommandEngine`\s and
+:class:`hooke.ui.UserInterface`\s.
+"""
+
+import Queue as queue
+import sys
+import textwrap
+import traceback
+
+
+class CommandExit (Exception):
+    pass
+
+class Success (CommandExit):
+    pass
+
+class Exit (Success):
+    """The command requests an end to the interpreter session.
+    """
+    pass
+
+class Failure (CommandExit):
+    pass
+
+class UncaughtException (Failure):
+    def __init__(self, exception, traceback_string=None):
+        super(UncaughtException, self).__init__()
+        if traceback_string == None:
+            traceback_string = traceback.format_exc()
+            sys.exc_clear()
+        self.exception = exception
+        self.traceback = traceback_string
+        self.__setstate__(self.__getstate__())
+
+    def __getstate__(self):
+        """Return a picklable representation of the objects state.
+
+        :mod:`pickle`'s doesn't call a :meth:`__init__` when
+        rebuilding a class instance.  To preserve :attr:`args` through
+        a pickle cycle, we use :meth:`__getstate__` and
+        :meth:`__setstate__`.
+
+        See `pickling class instances`_ and `pickling examples`_.
+
+        .. _pickling class instances:
+          http://docs.python.org/library/pickle.html#pickling-and-unpickling-normal-class-instances
+        .. _pickling examples:
+          http://docs.python.org/library/pickle.html#example
+        """
+        return {'exception':self.exception, 'traceback':self.traceback}
+
+    def __setstate__(self, state):
+        """Apply the picklable state from :meth:`__getstate__` to
+        reconstruct the instance.
+        """
+        for key,value in state.items():
+            setattr(self, key, value)
+        self.args = (self.traceback + str(self.exception),)
+
+
+class Command (object):
+    """One-line command description here.
+
+    >>> c = Command(name='test', help='An example Command.')
+    >>> hooke = None
+    >>> status = c.run(hooke, NullQueue(), PrintQueue(),
+    ...                help=True) # doctest: +REPORT_UDIFF
+    ITEM:
+    Command: test
+    <BLANKLINE>
+    Arguments:
+    <BLANKLINE>
+    help BOOL (bool) Print a help message.
+    <BLANKLINE>
+    An example Command.
+    ITEM:
+    <BLANKLINE>
+    """
+    def __init__(self, name, aliases=None, arguments=[], help=''):
+        # TODO: see_also=[other,command,instances,...]
+        self.name = name
+        if aliases == None:
+            aliases = []
+        self.aliases = aliases
+        self.arguments = [
+            Argument(name='help', type='bool', default=False, count=1,
+                     help='Print a help message.'),
+            ] + arguments
+        self._help = help
+
+    def run(self, hooke, inqueue=None, outqueue=None, **kwargs):
+        """`Normalize inputs and handle <Argument help> before punting
+        to :meth:`_run`.
+        """
+        if inqueue == None:
+            inqueue = NullQueue()
+        if outqueue == None:
+            outqueue = NullQueue()
+        try:
+            params = self.handle_arguments(hooke, inqueue, outqueue, kwargs)
+            if params['help'] == True:
+                outqueue.put(self.help())
+                raise(Success())
+            self._run(hooke, inqueue, outqueue, params)
+        except CommandExit, e:
+            if isinstance(e, Failure):
+                outqueue.put(e)
+                return 1
+            # other CommandExit subclasses fall through to the end
+        except Exception, e:
+            x = UncaughtException(e)
+            outqueue.put(x)
+            return 1
+        else:
+            e = Success()
+        outqueue.put(e)
+        return 0
+
+    def _run(self, inqueue, outqueue, params):
+        """This is where the command-specific magic will happen.
+        """
+        pass
+
+    def handle_arguments(self, hooke, inqueue, outqueue, params):
+        """Normalize and validate input parameters (:class:`Argument` values).
+        """
+        for argument in self.arguments:
+            names = [argument.name] + argument.aliases
+            settings = [(name,v) for name,v in params.items() if name in names]
+            num_provided = len(settings)
+            if num_provided == 0:
+                if argument.optional == True or argument.count == 0:
+                    settings = [(argument.name, argument.default)]
+                else:
+                    raise Failure('Required argument %s not set.'
+                                  % argument.name)
+            if num_provided > 1:
+                raise Failure('Multiple settings for %s:\n  %s'
+                    % (argument.name,
+                       '\n  '.join(['%s: %s' % (name,value)
+                                    for name,value in sorted(settings)])))
+            name,value = settings[0]
+            if num_provided == 0:
+                params[argument.name] = value
+            else:
+                if name != argument.name:
+                    params.remove(name)
+                    params[argument.name] = value
+            if argument.callback != None:
+                value = argument.callback(hooke, self, argument, value)
+                params[argument.name] = value
+            argument.validate(value)
+        return params
+
+    def help(self, name_fn=lambda name:name):
+        """Return a help message describing the `Command`.
+
+        `name_fn(internal_name) -> external_name` gives calling
+        :class:`hooke.ui.UserInterface`\s a means of changing the
+        display names if it wants (e.g. to remove spaces from command
+        line tokens).
+        """
+        name_part = 'Command: %s' % name_fn(self.name)
+        if len(self.aliases) > 0:
+            name_part += ' (%s)' % ', '.join(
+                [name_fn(n) for n in self.aliases])
+        parts = [name_part]
+        if len(self.arguments) > 0:
+            argument_part = ['Arguments:', '']
+            for a in self.arguments:
+                argument_part.append(textwrap.fill(
+                        a.help(name_fn),
+                        initial_indent="",
+                        subsequent_indent="    "))
+            argument_part = '\n'.join(argument_part)
+            parts.append(argument_part)
+        parts.append(self._help) # help part
+        return '\n\n'.join(parts)
+
+class Argument (object):
+    """Structured user input for :class:`Command`\s.
+    
+    TODO: ranges for `count`?
+    """
+    def __init__(self, name, aliases=None, type='string', metavar=None,
+                 default=None, optional=True, count=1,
+                 completion_callback=None, callback=None, help=''):
+        self.name = name
+        if aliases == None:
+            aliases = []
+        self.aliases = aliases
+        self.type = type
+        if metavar == None:
+            metavar = type.upper()
+        self.metavar = metavar
+        self.default = default
+        self.optional = optional
+        self.count = count
+        self.completion_callback = completion_callback
+        self.callback = callback
+        self._help = help
+
+    def __str__(self):
+        return '<%s %s>' % (self.__class__.__name__, self.name)
+
+    def __repr__(self):
+        return self.__str__()
+
+    def help(self, name_fn=lambda name:name):
+        """Return a help message describing the `Argument`.
+
+        `name_fn(internal_name) -> external_name` gives calling
+        :class:`hooke.ui.UserInterface`\s a means of changing the
+        display names if it wants (e.g. to remove spaces from command
+        line tokens).
+        """        
+        parts = ['%s ' % name_fn(self.name)]
+        if self.metavar != None:
+            parts.append('%s ' % self.metavar)
+        parts.extend(['(%s) ' % self.type, self._help])
+        return ''.join(parts)
+
+    def validate(self, value):
+        """If `value` is not appropriate, raise `ValueError`.
+        """
+        pass # TODO: validation
+
+    # TODO: type conversion
+
+# TODO: type extensions?
+
+# Useful callbacks
+
+class StoreValue (object):
+    def __init__(self, value):
+        self.value = value
+    def __call__(self, hooke, command, argument, fragment=None):
+        return self.value
+
+class NullQueue (queue.Queue):
+    """The :class:`queue.Queue` equivalent of `/dev/null`.
+
+    This is a bottomless pit.  Items go in, but never come out.
+    """
+    def get(self, block=True, timeout=None):
+        """Raise queue.Empty.
+        
+        There's really no need to override the base Queue.get, but I
+        want to know if someone tries to read from a NullQueue.  With
+        the default implementation they would just block silently
+        forever :(.
+        """
+        raise queue.Empty
+
+    def put(self, item, block=True, timeout=None):
+        """Dump an item into the void.
+
+        Block and timeout are meaningless, because there is always a
+        free slot available in a bottomless pit.
+        """
+        pass
+
+class PrintQueue (NullQueue):
+    """Debugging :class:`NullQueue` that prints items before dropping
+    them.
+    """
+    def put(self, item, block=True, timeout=None):
+        """Print `item` and then dump it into the void.
+        """
+        print 'ITEM:\n%s' % item
diff --git a/hooke/compat/__init__.py b/hooke/compat/__init__.py
new file mode 100644 (file)
index 0000000..ad3d685
--- /dev/null
@@ -0,0 +1,17 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
diff --git a/hooke/compat/odict.py b/hooke/compat/odict.py
new file mode 100644 (file)
index 0000000..d204c6d
--- /dev/null
@@ -0,0 +1,338 @@
+# -*- coding: utf-8 -*-
+
+# An ordered dictionary implementation.  OrderedDict entered the
+# Python Standard Library in Python 2.7 and 3.1.  See `PEP 372`_ for
+# details.  Code_ by Armin Ronacher and the PEP 273 authors.
+# 
+# .. _PEP 372: http://www.python.org/dev/peps/pep-0372/
+# .. _Code: http://dev.pocoo.org/hg/sandbox/raw-file/tip/odict.py
+
+"""
+    odict
+    ~~~~~
+
+    This module is an example implementation of an ordered dict for the
+    collections module.  It's not written for performance (it actually
+    performs pretty bad) but to show how the API works.
+
+
+    Questions and Answers
+    =====================
+
+    Why would anyone need ordered dicts?
+
+        Dicts in python are unordered which means that the order of items when
+        iterating over dicts is undefined.  As a matter of fact it is most of
+        the time useless and differs from implementation to implementation.
+
+        Many developers stumble upon that problem sooner or later when
+        comparing the output of doctests which often does not match the order
+        the developer thought it would.
+
+        Also XML systems such as Genshi have their problems with unordered
+        dicts as the input and output ordering of tag attributes is often
+        mixed up because the ordering is lost when converting the data into
+        a dict.  Switching to lists is often not possible because the
+        complexity of a lookup is too high.
+
+        Another very common case is metaprogramming.  The default namespace
+        of a class in python is a dict.  With Python 3 it becomes possible
+        to replace it with a different object which could be an ordered dict.
+        Django is already doing something similar with a hack that assigns
+        numbers to some descriptors initialized in the class body of a
+        specific subclass to restore the ordering after class creation.
+
+        When porting code from programming languages such as PHP and Ruby
+        where the item-order in a dict is guaranteed it's also a great help
+        to have an equivalent data structure in Python to ease the transition.
+
+    Where are new keys added?
+
+        At the end.  This behavior is consistent with Ruby 1.9 Hashmaps
+        and PHP Arrays.  It also matches what common ordered dict
+        implementations do currently.
+
+    What happens if an existing key is reassigned?
+
+        The key is *not* moved.  This is consitent with existing
+        implementations and can be changed by a subclass very easily::
+
+            class movingodict(odict):
+                def __setitem__(self, key, value):
+                    self.pop(key, None)
+                    odict.__setitem__(self, key, value)
+
+        Moving keys to the end of a ordered dict on reassignment is not
+        very useful for most applications.
+
+    Does it mean the dict keys are sorted by a sort expression?
+
+        That's not the case.  The odict only guarantees that there is an order
+        and that newly inserted keys are inserted at the end of the dict.  If
+        you want to sort it you can do so, but newly added keys are again added
+        at the end of the dict.
+
+    I initializes the odict with a dict literal but the keys are not
+    ordered like they should!
+
+        Dict literals in Python generate dict objects and as such the order of
+        their items is not guaranteed.  Before they are passed to the odict
+        constructor they are already unordered.
+
+    What happens if keys appear multiple times in the list passed to the
+    constructor?
+
+        The same as for the dict.  The latter item overrides the former.  This
+        has the side-effect that the position of the first key is used because
+        the key is actually overwritten:
+
+        >>> odict([('a', 1), ('b', 2), ('a', 3)])
+        odict.odict([('a', 3), ('b', 2)])
+
+        This behavor is consistent with existing implementation in Python
+        and the PHP array and the hashmap in Ruby 1.9.
+
+    This odict doesn't scale!
+
+        Yes it doesn't.  The delitem operation is O(n).  This is file is a
+        mockup of a real odict that could be implemented for collections
+        based on an linked list.
+
+    Why is there no .insert()?
+
+        There are few situations where you really want to insert a key at
+        an specified index.  To now make the API too complex the proposed
+        solution for this situation is creating a list of items, manipulating
+        that and converting it back into an odict:
+
+        >>> d = odict([('a', 42), ('b', 23), ('c', 19)])
+        >>> l = d.items()
+        >>> l.insert(1, ('x', 0))
+        >>> odict(l)
+        odict.odict([('a', 42), ('x', 0), ('b', 23), ('c', 19)])
+
+    :copyright: (c) 2008 by Armin Ronacher and PEP 273 authors.
+    :license: modified BSD license.
+"""
+from itertools import izip, imap
+from copy import deepcopy
+
+missing = object()
+
+
+class odict(dict):
+    """
+    Ordered dict example implementation.
+
+    This is the proposed interface for a an ordered dict as proposed on the
+    Python mailinglist (proposal_).
+
+    It's a dict subclass and provides some list functions.  The implementation
+    of this class is inspired by the implementation of Babel but incorporates
+    some ideas from the `ordereddict`_ and Django's ordered dict.
+
+    The constructor and `update()` both accept iterables of tuples as well as
+    mappings:
+
+    >>> d = odict([('a', 'b'), ('c', 'd')])
+    >>> d.update({'foo': 'bar'})
+    >>> d
+    odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
+    
+    Keep in mind that when updating from dict-literals the order is not
+    preserved as these dicts are unsorted!
+
+    You can copy an odict like a dict by using the constructor, `copy.copy`
+    or the `copy` method and make deep copies with `copy.deepcopy`:
+
+    >>> from copy import copy, deepcopy
+    >>> copy(d)
+    odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
+    >>> d.copy()
+    odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
+    >>> odict(d)
+    odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
+    >>> d['spam'] = []
+    >>> d2 = deepcopy(d)
+    >>> d2['spam'].append('eggs')
+    >>> d
+    odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])])
+    >>> d2
+    odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', ['eggs'])])
+
+    All iteration methods as well as `keys`, `values` and `items` return
+    the values ordered by the the time the key-value pair is inserted:
+
+    >>> d.keys()
+    ['a', 'c', 'foo', 'spam']
+    >>> d.values()
+    ['b', 'd', 'bar', []]
+    >>> d.items()
+    [('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])]
+    >>> list(d.iterkeys())
+    ['a', 'c', 'foo', 'spam']
+    >>> list(d.itervalues())
+    ['b', 'd', 'bar', []]
+    >>> list(d.iteritems())
+    [('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])]
+
+    Index based lookup is supported too by `byindex` which returns the
+    key/value pair for an index:
+
+    >>> d.byindex(2)
+    ('foo', 'bar')
+
+    You can reverse the odict as well:
+
+    >>> d.reverse()
+    >>> d
+    odict.odict([('spam', []), ('foo', 'bar'), ('c', 'd'), ('a', 'b')])
+    
+    And sort it like a list:
+
+    >>> d.sort(key=lambda x: x[0].lower())
+    >>> d
+    odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])])
+
+    .. _proposal: http://thread.gmane.org/gmane.comp.python.devel/95316
+    .. _ordereddict: http://www.xs4all.nl/~anthon/Python/ordereddict/
+    """
+
+    def __init__(self, *args, **kwargs):
+        dict.__init__(self)
+        self._keys = []
+        self.update(*args, **kwargs)
+
+    def __delitem__(self, key):
+        dict.__delitem__(self, key)
+        self._keys.remove(key)
+
+    def __setitem__(self, key, item):
+        if not hasattr(self, '_keys'):
+            self._keys = [] # work around multiprocessing.Queue rebuild
+        if key not in self:
+            self._keys.append(key)
+        dict.__setitem__(self, key, item)
+
+    def __deepcopy__(self, memo=None):
+        if memo is None:
+            memo = {}
+        d = memo.get(id(self), missing)
+        if d is not missing:
+            return d
+        memo[id(self)] = d = self.__class__()
+        dict.__init__(d, deepcopy(self.items(), memo))
+        d._keys = self._keys[:]
+        return d
+
+    def __getstate__(self):
+        return {'items': dict(self), 'keys': self._keys}
+
+    def __setstate__(self, d):
+        self._keys = d['keys']
+        dict.update(d['items'])
+
+    def __reversed__(self):
+        return reversed(self._keys)
+
+    def __eq__(self, other):
+        if isinstance(other, odict):
+            if not dict.__eq__(self, other):
+                return False
+            return self.items() == other.items()
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __cmp__(self, other):
+        if isinstance(other, odict):
+            return cmp(self.items(), other.items())
+        elif isinstance(other, dict):
+            return dict.__cmp__(self, other)
+        return NotImplemented
+
+    @classmethod
+    def fromkeys(cls, iterable, default=None):
+        return cls((key, default) for key in iterable)
+
+    def clear(self):
+        del self._keys[:]
+        dict.clear(self)
+
+    def copy(self):
+        return self.__class__(self)
+
+    def items(self):
+        return zip(self._keys, self.values())
+
+    def iteritems(self):
+        return izip(self._keys, self.itervalues())
+
+    def keys(self):
+        return self._keys[:]
+
+    def iterkeys(self):
+        return iter(self._keys)
+
+    def pop(self, key, default=missing):
+        if default is missing:
+            return dict.pop(self, key)
+        elif key not in self:
+            return default
+        self._keys.remove(key)
+        return dict.pop(self, key, default)
+
+    def popitem(self, key):
+        self._keys.remove(key)
+        return dict.popitem(key)
+
+    def setdefault(self, key, default=None):
+        if key not in self:
+            self._keys.append(key)
+        dict.setdefault(self, key, default)
+
+    def update(self, *args, **kwargs):
+        sources = []
+        if len(args) == 1:
+            if hasattr(args[0], 'iteritems'):
+                sources.append(args[0].iteritems())
+            else:
+                sources.append(iter(args[0]))
+        elif args:
+            raise TypeError('expected at most one positional argument')
+        if kwargs:
+            sources.append(kwargs.iteritems())
+        for iterable in sources:
+            for key, val in iterable:
+                self[key] = val
+
+    def values(self):
+        return map(self.get, self._keys)
+
+    def itervalues(self):
+        return imap(self.get, self._keys)
+
+    def index(self, item):
+        return self._keys.index(item)
+
+    def byindex(self, item):
+        key = self._keys[item]
+        return (key, dict.__getitem__(self, key))
+
+    def reverse(self):
+        self._keys.reverse()
+
+    def sort(self, *args, **kwargs):
+        self._keys.sort(*args, **kwargs)
+
+    def __repr__(self):
+        return 'odict.odict(%r)' % self.items()
+
+    __copy__ = copy
+    __iter__ = iterkeys
+
+
+if __name__ == '__main__':
+    import doctest
+    doctest.testmod()
diff --git a/hooke/config.py b/hooke/config.py
new file mode 100644 (file)
index 0000000..10f4d6f
--- /dev/null
@@ -0,0 +1,288 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Configuration defaults, read/write, and template file creation for
+Hooke.
+"""
+
+import ConfigParser as configparser
+import os.path
+import textwrap
+import unittest
+
+from .compat.odict import odict as OrderedDict
+
+
+DEFAULT_PATHS = [
+    '/usr/share/hooke/hooke.cfg',
+    '/etc/hooke/hooke.cfg',
+    '~/.hooke.cfg',
+    '.hooke.cfg',
+    ]
+"""We start with the system files, and work our way to the more
+specific user files, so the user can override the sysadmin who
+in turn overrides the developer defaults.
+"""
+
+class Setting (object):
+    """An entry (section or option) in HookeConfigParser.
+    """
+    def __init__(self, section, option=None, value=None, help=None, wrap=True):
+        self.section = section
+        self.option = option
+        self.value = value
+        self.help = help
+        self.wrap = wrap
+
+    def __eq__(self, other):
+        for attr in ['__class__', 'section', 'option', 'value', 'help']:
+            value = getattr(self, attr)
+            o_value = getattr(other, attr)
+            if o_value != value:
+                return False
+        return True
+
+    def is_section(self):
+        return self.option == None
+
+    def is_option(self):
+        return not self.is_section()
+
+    def write(self, fp, value=None, comments=True, wrapper=None):
+        if comments == True and self.help != None:
+            text = self.help
+            if self.wrap == True:
+                if wrapper == None:
+                    wrapper = textwrap.TextWrapper(
+                        initial_indent="# ",
+                        subsequent_indent="# ")
+                text = wrapper.fill(text)
+            fp.write(text.rstrip()+'\n')
+        if self.is_section():
+            fp.write("[%s]\n" % self.section)
+        else:
+            fp.write("%s = %s\n" % (self.option,
+                                    str(value).replace('\n', '\n\t')))
+
+DEFAULT_SETTINGS = [
+    Setting('display', help='Control display appearance: colour, ???, etc.'),
+    Setting('display', 'colour_ext', 'None', help=None),
+    Setting('display', 'colour_ret', 'None', help=None),
+    Setting('display', 'ext', '1', help=None),
+    Setting('display', 'ret', '1', help=None),
+
+    Setting('display', 'correct', '1', help=None),
+    Setting('display', 'colout_correct', 'None', help=None),
+    Setting('display', 'contact_point', '0', help=None),
+    Setting('display', 'medfilt', '0', help=None),
+
+    Setting('display', 'xaxes', '0', help=None),
+    Setting('display', 'yaxes', '0', help=None),
+    Setting('display', 'flatten', '1', help=None),
+
+    Setting('conditions', 'temperature', '301', help=None),
+
+    Setting('fitting', 'auto_fit_points', '50', help=None),
+    Setting('fitting', 'auto_slope_span', '20', help=None),
+    Setting('fitting', 'auto_delta_force', '1-', help=None),
+    Setting('fitting', 'auto_fit_nm', '5', help=None),
+    Setting('fitting', 'auto_min_p', '0.005', help=None),
+    Setting('fitting', 'auto_max_p', '10', help=None),
+
+    Setting('?', 'baseline_clicks', '0', help=None),
+    Setting('fitting', 'auto_left_baseline', '20', help=None),
+    Setting('fitting', 'auto_right_baseline', '20', help=None),
+    Setting('fitting', 'force_multiplier', '1', help=None),
+    
+    Setting('display', 'fc_showphase', '0', help=None),
+    Setting('display', 'fc_showimposed', '0', help=None),
+    Setting('display', 'fc_interesting', '0', help=None),
+    Setting('?', 'tccd_threshold', '0', help=None),
+    Setting('?', 'tccd_coincident', '0', help=None),
+    Setting('display', '', '', help=None),
+    Setting('display', '', '', help=None),
+
+    Setting('filesystem', 'filterindex', '0', help=None),
+    Setting('filesystem', 'filters',
+            "Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')",
+            help=None),
+    Setting('filesystem', 'workdir', 'test',
+            help='\n'.join(['# Substitute your work directory',
+                            '#workdir = D:\hooke']),
+            wrap=False),
+    Setting('filesystem', 'playlist', 'test.hkp', help=None),
+    ]
+
+def get_setting(settings, match):
+    """Return the first Setting object matching both match.section and
+    match.option.
+    """
+    for s in settings:
+        if s.section == match.section and s.option == match.option:
+            return s
+    return None
+
+class HookeConfigParser (configparser.SafeConfigParser):
+    """A wrapper around configparser.SafeConfigParser.
+
+    You will probably only need .read and .write.
+
+    Examples
+    --------
+
+    >>> import sys
+    >>> c = HookeConfigParser(paths=DEFAULT_PATHS,
+    ...                       default_settings=DEFAULT_SETTINGS)
+    >>> c.write(sys.stdout) # doctest: +ELLIPSIS
+    # Control display appearance: colour, ???, etc.
+    [display]
+    colour_ext = None
+    colour_ret = None
+    ...
+    """
+    def __init__(self, paths=None, default_settings=None, defaults=None,
+                 dict_type=OrderedDict, indent='# ', **kwargs):
+        # Can't use super() because SafeConfigParser is a classic class
+        #super(HookeConfigParser, self).__init__(defaults, dict_type)
+        configparser.SafeConfigParser.__init__(self, defaults, dict_type)
+        if paths == None:
+            paths = []
+        self._config_paths = paths
+        if default_settings == None:
+            default_settings = []
+        self._default_settings = default_settings
+        for key in ['initial_indent', 'subsequent_indent']:
+            if key not in kwargs:
+                kwargs[key] = indent
+        self._comment_textwrap = textwrap.TextWrapper(**kwargs)
+        self._setup_default_settings()
+
+    def _setup_default_settings(self):
+        for setting in self._default_settings: #reversed(self._default_settings):
+            # reversed cause: http://docs.python.org/library/configparser.html
+            # "When adding sections or items, add them in the reverse order of
+            # how you want them to be displayed in the actual file."
+            if setting.section not in self.sections():
+                self.add_section(setting.section)
+            if setting.option != None:
+                self.set(setting.section, setting.option, setting.value)
+
+    def read(self, filenames=None):
+        """Read and parse a filename or a list of filenames.
+
+        If filenames is None, it defaults to ._config_paths.  If a
+        filename is not in ._config_paths, it gets appended to the
+        list.  We also run os.path.expanduser() on the input filenames
+        internally so you don't have to worry about it.
+
+        Files that cannot be opened are silently ignored; this is
+        designed so that you can specify a list of potential
+        configuration file locations (e.g. current directory, user's
+        home directory, systemwide directory), and all existing
+        configuration files in the list will be read.  A single
+        filename may also be given.
+
+        Return list of successfully read files.
+        """
+        if filenames == None:
+            filenames = [os.path.expanduser(p) for p in self._config_paths]
+        else:
+            if isinstance(filenames, basestring):
+                filenames = [filenames]
+            paths = [os.path.abspath(os.path.expanduser(p))
+                     for p in self._config_paths]
+            for filename in filenames:
+                if os.path.abspath(os.path.expanduser(filename)) not in paths:
+                    self._config_paths.append(filename)
+        # Can't use super() because SafeConfigParser is a classic class
+        #return super(HookeConfigParser, self).read(filenames)
+        return configparser.SafeConfigParser.read(self, filenames)
+
+    def _write_setting(self, fp, section=None, option=None, value=None,
+                       **kwargs):
+        """Print, if known, a nicely wrapped comment form of a
+        setting's .help.
+        """
+        match = get_setting(self._default_settings, Setting(section, option))
+        if match == None:
+            match = Setting(section, option, value, **kwargs)
+        match.write(fp, value=value, wrapper=self._comment_textwrap)
+
+    def write(self, fp=None, comments=True):
+        """Write an .ini-format representation of the configuration state.
+
+        This expands on RawConfigParser.write() by optionally adding
+        comments when they are known (i.e. for ._default_settings).
+        However, comments are not read in during a read, so .read(x)
+        .write(x) may `not preserve comments`_.
+
+        .. _not preserve comments: http://bugs.python.org/issue1410680
+
+        Examples
+        --------
+
+        >>> import sys, StringIO
+        >>> c = HookeConfigParser()
+        >>> instring = '''
+        ... # Some comment
+        ... [section]
+        ... option = value
+        ...
+        ... '''
+        >>> c._read(StringIO.StringIO(instring), 'example.cfg')
+        >>> c.write(sys.stdout)
+        [section]
+        option = value
+        <BLANKLINE>
+        """
+        local_fp = fp == None
+        if local_fp:
+            fp = open(os.path.expanduser(self._config_paths[-1]), 'w')
+        if self._defaults:
+            self._write_setting(fp, configparser.DEFAULTSECT,
+                                help="Miscellaneous options")
+            for (key, value) in self._defaults.items():
+                self._write_setting(fp, configparser.DEFAULTSECT, key, value)
+            fp.write("\n")
+        for section in self._sections:
+            self._write_setting(fp, section)
+            for (key, value) in self._sections[section].items():
+                if key != "__name__":
+                    self._write_setting(fp, section, key, value)
+            fp.write("\n")
+        if local_fp:
+            fp.close()
+
+class TestHookeConfigParser (unittest.TestCase):
+    def test_queue_safe(self):
+        """Ensure :class:`HookeConfigParser` is Queue-safe.
+        """
+        from multiprocessing import Queue
+        q = Queue()
+        a = HookeConfigParser(
+            paths=DEFAULT_PATHS, default_settings=DEFAULT_SETTINGS)
+        q.put(a)
+        b = q.get(a)
+        for attr in ['_dict', '_defaults', '_sections', '_config_paths',
+                     '_default_settings']:
+            a_value = getattr(a, attr)
+            b_value = getattr(b, attr)
+            self.failUnless(
+                a_value == b_value,
+                'HookeConfigParser.%s did not survive Queue: %s != %s'
+                % (attr, a_value, b_value))
diff --git a/hooke/curve.py b/hooke/curve.py
new file mode 100644 (file)
index 0000000..bd58a18
--- /dev/null
@@ -0,0 +1,144 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""The `curve` module provides :class:`Curve` and :class:`Data` for
+storing force curves.
+"""
+
+import os.path
+import numpy
+
+
+class NotRecognized (ValueError):
+    def __init__(self, curve):
+        self.__setstate__(curve)
+
+    def __getstate__(self):
+        return self.curve
+
+    def __setstate__(self, data):
+        if isinstance(data, Curve):
+            msg = 'Not a recognizable curve format: %s' % data.path
+            super(NotRecognized, self).__init__(msg)
+            self.curve = data
+
+class Data (numpy.ndarray):
+    """Stores a single, continuous data set.
+
+    Adds :attr:`info` :class:`dict` to the standard :class:`numpy.ndarray`.
+
+    See :mod:`numpy.doc.subclassing` for the peculiarities of
+    subclassing :class:`numpy.ndarray`.
+
+    Examples
+    --------
+
+    >>> d = Data(shape=(3,2), info={'columns':['distance (m)', 'force (N)']})
+    >>> type(d)
+    <class 'hooke.curve.Data'>
+    >>> for i in range(3): # initialize d
+    ...    for j in range(2):
+    ...        d[i,j] = i*10 + j
+    >>> d
+    Data([[  0.,   1.],
+           [ 10.,  11.],
+           [ 20.,  21.]])
+    >>> d.info
+    {'columns': ['distance (m)', 'force (N)']}
+    >>> row_a = d[:,0]
+    >>> row_a
+    Data([  0.,  10.,  20.])
+    >>> row_a.info
+    {'columns': ['distance (m)', 'force (N)']}
+    """
+    def __new__(subtype, shape, dtype=numpy.float, buffer=None, offset=0,
+                strides=None, order=None, info=None):
+        """Create the ndarray instance of our type, given the usual
+        input arguments.  This will call the standard ndarray
+        constructor, but return an object of our type.
+        """
+        obj = numpy.ndarray.__new__(
+            subtype, shape, dtype, buffer, offset, strides, order)
+        # add the new attribute to the created instance
+        if info == None:
+            info = {}
+        obj.info = info
+        # Finally, we must return the newly created object:
+        return obj
+
+    def __array_finalize__(self, obj):
+        """Set any extra attributes from the original object when
+        creating a new view object."""
+        # reset the attribute from passed original object
+        self.info = getattr(obj, 'info', {})
+        # We do not need to return anything
+
+
+class Curve (object):
+    """A grouped set of :class:`Data` runs from the same file with metadata.
+
+    For an approach/retract force spectroscopy experiment, the group
+    would consist of the approach data and the retract data.  Metadata
+    would be the temperature, cantilever spring constant, etc.
+
+    Two important :attr:`info` settings are `filetype` and
+    `experiment`.  These are two strings that can be used by Hooke
+    commands/plugins to understand what they are looking at.
+
+    * `.info['filetype']` should contain the name of the exact
+      filetype defined by the driver (so that filetype-speciofic
+      commands can know if they're dealing with the correct filetype).
+    * `.info['experiment']` should contain an instance of a
+      :class:`hooke.experiment.Experiment` subclass to identify the
+      experiment type.  For example, various
+      :class:`hooke.driver.Driver`\s can read in force-clamp data, but
+      Hooke commands could like to know if they're looking at force
+      clamp data, regardless of their origin.
+    """
+    def __init__(self, path, info=None):
+        #the data dictionary contains: {name of data: list of data sets [{[x], [y]}]
+        self.path = path
+        self.driver = None
+        self.data = None
+        if info == None:
+            info = {}
+        self.info = info
+        self.name = os.path.basename(path)
+
+    def identify(self, drivers):
+        """Identify the appropriate :class:`hooke.driver.Driver` for
+        the curve file (`.path`).
+        """
+        for driver in drivers:
+            if driver.is_me(self.path):
+                self.driver = driver # remember the working driver
+                return
+        raise NotRecognized(self)
+
+    def load(self):
+        """Use the driver to read the curve into memory.
+        """
+        data,info = self.driver.read(self.path)
+        self.data = data
+        for key,value in info.items():
+            self.info[key] = value
+
+    def unload(self):
+        """Release memory intensive :attr:`.data`.
+        """
+        self.data = None
diff --git a/hooke/driver/__init__.py b/hooke/driver/__init__.py
new file mode 100644 (file)
index 0000000..ddaa0e1
--- /dev/null
@@ -0,0 +1,118 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""The driver module provides :class:`Driver`\s for identifying and
+reading data files.
+
+This allows Hooke to be data-file agnostic.  Drivers for various
+commercial force spectroscopy microscopes are provided, and it's easy
+to write your own to handle your lab's specific format.
+"""
+
+from ..config import Setting
+from ..util.pluggable import IsSubclass, construct_graph
+
+
+DRIVER_MODULES = [
+#    ('csvdriver', True),
+#    ('hdf5', True),
+#    ('hemingclamp', True),
+#    ('jpk', True),
+#    ('mcs', True),
+#    ('mfp1dexport', True),
+#    ('mfp3d', True),
+    ('picoforce', True),
+#    ('picoforcealt', True),
+    ('tutorial', True),
+]
+"""List of driver modules and whether they should be included by
+default.  TODO: autodiscovery
+"""
+
+DRIVER_SETTING_SECTION = 'drivers'
+"""Name of the config section which controls driver selection.
+"""
+
+
+class Driver(object):
+    """Base class for file format drivers.
+    
+    :attr:`name` identifies your driver, and should match the module
+    name.
+    """
+    def __init__(self, name):
+        self.name = name
+        self.setting_section = '%s driver' % self.name
+
+    def dependencies(self):
+        """Return a list of :class:`Driver`\s we require."""
+        return []
+
+    def default_settings(self):
+        """Return a list of :class:`hooke.config.Setting`\s for any
+        configurable driver settings.
+
+        The suggested section setting is::
+
+            Setting(section=self.setting_section, help=self.__doc__)
+        """
+        return []
+
+    def is_me(self, path):
+        """Read the file and return True if the filetype can be
+        managed by the driver.  Otherwise return False.
+        """
+        return False
+
+    def read(self, path):
+        """Read data from `path` and return a
+        ([:class:`hooke.curve.Data`, ...], `info`) tuple.
+
+        The `info` :class:`dict` must contain values for the keys:
+        'filetype' and 'experiment'.  See :class:`hooke.curve.Curve`
+        for details.
+        """
+        raise NotImplementedError
+
+# Construct driver dependency graph and load default drivers.
+
+DRIVER_GRAPH = construct_graph(
+    this_modname=__name__,
+    submodnames=[name for name,include in DRIVER_MODULES],
+    class_selector=IsSubclass(Driver, blacklist=[Driver]))
+"""Topologically sorted list of all possible :class:`Driver`\s.
+"""
+
+def default_settings():
+    settings = [Setting(DRIVER_SETTING_SECTION,
+                        help='Enable/disable default drivers.')]
+    for dnode in DRIVER_GRAPH:
+        driver = dnode.data
+        default_include = [di for mod_name,di in DRIVER_MODULES
+                           if mod_name == driver.name][0]
+        help = driver.__doc__.split('\n', 1)[0]
+        settings.append(Setting(
+                section=DRIVER_SETTING_SECTION,
+                option=driver.name,
+                value=str(default_include),
+                help=help,
+                ))
+    for dnode in DRIVER_GRAPH:
+        driver = dnode.data
+        settings.extend(driver.default_settings())
+    return settings
diff --git a/hooke/driver/csvdriver.py b/hooke/driver/csvdriver.py
new file mode 100644 (file)
index 0000000..227935a
--- /dev/null
@@ -0,0 +1,85 @@
+# Copyright (C) 2008-2010 Massimo Sandal <devicerandom@gmail.com>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Simple driver to read general comma-separated values in Hooke
+
+Columns are read this way:
+
+X1 , Y1 , X2 , Y2 , X3 , Y3 ...
+
+If the number of columns is odd, the last column is ignored.
+"""
+
+from .. import curve as lhc
+from .. import libhooke as lh
+import csv
+
+class csvdriverDriver(lhc.Driver):
+
+        def __init__(self, filename):
+
+            self.filedata = open(filename,'r')
+            self.data = list(self.filedata)
+            self.filedata.close()
+
+            self.filetype = 'generic'
+            self.experiment = ''
+
+            self.filename=filename
+
+        def is_me(self):
+            myfile=file(self.filename)
+            headerline=myfile.readlines()[0]
+            myfile.close()
+
+            #using a custom header makes things much easier...
+            #(looking for raw CSV data is at strong risk of confusion)
+            if headerline[:-1]=='Hooke data':
+                return True
+            else:
+                return False
+
+        def close_all(self):
+            self.filedata.close()
+
+        def default_plots(self):
+            rrows=csv.reader(self.data)
+            rows=list(rrows) #transform the csv.reader iterator in a normal list
+            columns=lh.transposed2(rows[1:])
+
+            main_plot=lhc.PlotObject()
+            main_plot.vectors=[]
+
+            for index in range(0,len(columns),2):
+                main_plot.vectors.append([])
+                temp_x=columns[index]
+                temp_y=columns[index+1]
+
+                #convert to float (the csv gives strings)
+                temp_x=[float(item) for item in temp_x]
+                temp_y=[float(item) for item in temp_y]
+
+                main_plot.vectors[-1].append(temp_x)
+                main_plot.vectors[-1].append(temp_y)
+
+            main_plot.units=['x','y']
+            main_plot.title=self.filename
+            main_plot.destination=0
+
+            return [main_plot]
diff --git a/hooke/driver/hdf5.py b/hooke/driver/hdf5.py
new file mode 100644 (file)
index 0000000..4a05474
--- /dev/null
@@ -0,0 +1,95 @@
+# Copyright (C) 2009-2010 Alberto Gomez-Casado
+#                         Massimo Sandal <devicerandom@gmail.com>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Driver for text-exported HDF5 files from Igor pro
+"""
+
+from .. import curve as lhc
+from .. import libhooke as lh
+
+class hdf5Driver(lhc.Driver):
+    
+    def __init__(self, filename):
+        
+        self.filename=filename
+        self.filedata=open(filename,'rU')
+        self.lines=list(self.filedata.readlines())
+        self.filedata.close()
+        
+        self.filetype='hdf5'
+        self.experiment='smfs'
+        
+    def close_all(self):
+        self.filedata.close()
+        
+    def is_me(self):
+        self.raw_header=self.lines[0]      
+               
+        if 'IGP-HDF5-Hooke' in self.raw_header:
+            return True
+        else:
+            return False
+        
+    def _read_columns(self):
+        
+        self.raw_columns=self.lines[4:]
+        
+        kline=None
+        for line in self.lines:
+            if line[:7]=='SpringC':
+                kline=line
+                break
+        
+        kline=kline.split(':')
+        
+        self.k=float(kline[1])
+        
+        
+        xext=[]
+        xret=[]
+        yext=[]
+        yret=[]
+        for line in self.raw_columns:
+            spline=line.split()
+            xext.append(float(spline[0]))
+            yext.append(float(spline[1]))
+            xret.append(float(spline[2]))
+            yret.append(float(spline[3]))
+            
+        return [[xext,yext],[xret,yret]]
+        
+    def deflection(self):
+        self.data=self._read_columns()
+        return self.data[0][1],self.data[1][1]
+        
+        
+    def default_plots(self):   
+        main_plot=lhc.PlotObject()
+        defl_ext,defl_ret=self.deflection()
+        yextforce=[i*self.k for i in defl_ext]
+        yretforce=[i*self.k for i in defl_ret]
+        main_plot.add_set(self.data[0][0],yextforce)
+        main_plot.add_set(self.data[1][0],yretforce)
+        main_plot.normalize_vectors()
+        main_plot.units=['Z','force']  #FIXME: if there's an header saying something about the time count, should be used
+        main_plot.destination=0
+        main_plot.title=self.filename
+        #main_plot.colors=['red','blue']
+        return [main_plot]
diff --git a/hooke/driver/hemingclamp.py b/hooke/driver/hemingclamp.py
new file mode 100644 (file)
index 0000000..dc9f3cf
--- /dev/null
@@ -0,0 +1,144 @@
+# Copyright (C) 2008-2010 Massimo Sandal <devicerandom@gmail.com>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Library for interpreting Hemingway force spectroscopy files.
+"""
+__version__='2007_02_15_devel'
+
+__changelog__='''
+2007_02_15: fixed time counter with my counter
+2007_02_07: Initial implementation
+'''
+import string
+from .. import curve as lhc
+
+class DataChunk(list):
+    '''Dummy class to provide ext and ret methods to the data list.
+    In this case ext and self can be equal.
+    '''
+
+    def ext(self):
+        return self
+
+    def ret(self):
+        return self
+
+class hemingclampDriver(lhc.Driver):
+
+    def __init__(self, filename):
+
+        self.filedata = open(filename,'r')
+        self.data = self.filedata.readlines()[6:]
+        self.filedata.close()
+
+        self.filetype = 'hemingclamp'
+        self.experiment = 'clamp'
+
+        self.filename=filename
+
+    def __del__(self):
+        self.filedata.close()
+
+    def is_me(self):
+        '''
+        we define our magic heuristic for HemingClamp files
+        '''
+        myfile=file(self.filename)
+        headerlines=myfile.readlines()[0:3]
+        myfile.close()
+        if headerlines[0][0:10]=='#Hemingway' and headerlines[1][0:19]=='#Experiment: FClamp':
+            return True
+        else:
+            return False
+
+    def _getdata_all(self):
+        time = []
+        phase = []
+        zpiezo = []
+        defl = []
+        imposed = []
+        trim_indexes = []
+        trim_counter = 0.0
+
+        for i in self.data:
+            temp = string.split(i)
+            #time.append(float(temp[0])*(1.0e-3)) # This is managed differently now, since each data point = 1ms: see below
+            phase.append(float(temp[1])*(1.0e-7)) # The nonsensical (e-7) multiplier is just there to make phase data nicely plottable along other data
+            zpiezo.append(float(temp[2])*(1.0e-9))
+            defl.append(float(temp[3])*(1.0e-9))
+            imposed.append(float(temp[4])*(1.0e-9))
+
+        for x in range (0,len(phase)):
+            if phase[x] != trim_counter:
+                trim_indexes.append(x)
+                trim_counter = phase[x]
+
+        #we rebuild the time counter assuming 1 point = 1 millisecond
+        c=0.0
+        for z in zpiezo:
+            time.append(c)
+            c+=(1.0e-3)
+
+        return time,phase,zpiezo,defl,imposed,trim_indexes
+
+    def time(self):
+        return DataChunk(self._getdata_all()[0])
+
+    def phase(self):
+        return DataChunk(self._getdata_all()[1])
+
+    def zpiezo(self):
+        return DataChunk(self._getdata_all()[2])
+
+    def deflection(self):
+        return DataChunk(self._getdata_all()[3])
+
+    def imposed(self):
+        return DataChunk(self._getdata_all()[4])
+
+    def trimindexes(self):
+        return DataChunk(self._getdata_all()[5])
+
+    def close_all(self):
+        '''
+        Explicitly closes all files
+        '''
+        self.filedata.close()
+
+    def default_plots(self):
+        main_plot=lhc.PlotObject()
+        defl_plot=lhc.PlotObject()
+
+        time=self.time()
+        phase=self.phase()
+        zpiezo=self.zpiezo()
+        deflection=self.deflection()
+        imposed=self.imposed()
+
+        main_plot.vectors=[[time,zpiezo],[time,phase]]
+        main_plot.units=['seconds','meters']
+        main_plot.destination=0
+        main_plot.title=self.filename
+
+        defl_plot.vectors=[[time,deflection],[time,imposed]]
+        defl_plot.units=['seconds','Newtons']
+        defl_plot.destination=1
+
+        return [main_plot, defl_plot]
+
diff --git a/hooke/driver/jpk.py b/hooke/driver/jpk.py
new file mode 100644 (file)
index 0000000..6f519ef
--- /dev/null
@@ -0,0 +1,156 @@
+# Copyright (C) 2008-2010 Massimo Sandal <devicerandom@gmail.com>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+import string
+from .. import curve as lhc
+
+class DataChunk(list):
+    #Dummy class to provide ext and ret methods to the data list.
+
+    def ext(self):
+        halflen=(len(self)/2)
+        return self[0:halflen]
+
+    def ret(self):
+        halflen=(len(self)/2)
+        return self[halflen:]
+
+class jpkDriver(lhc.Driver):
+
+    def __init__(self, filename):
+        self.filename=filename #self.filename can always be useful, and should be defined
+        self.filedata = open(filename,'r') #We open the file
+        self.filelines=self.filedata.readlines()
+        self.filedata.close()
+        '''These are two strings that can be used by Hooke commands/plugins to understand what they are looking at. They have no other
+        meaning. They have to be somehow defined however - commands often look for those variables.
+
+        self.filetype should contain the name of the exact filetype defined by the driver (so that filetype-specific commands can know
+                      if they're dealing with the correct filetype)
+        self.experiment should contain instead the type of data involved (for example, various drivers can be used for force-clamp experiments,
+                      but hooke commands could like to know if we're looking at force clamp data, regardless of their origin, and not other
+                      kinds of data)
+
+        Of course, all other variables you like can be defined in the class.
+        '''
+        self.filetype = 'jpk'
+        self.experiment = 'smfs'
+
+
+
+    def __del__(self):
+        self.filedata.close()
+
+    def is_me(self):
+        '''
+        we define our magic heuristic for jpk files
+        '''
+        myfile=file(self.filename)
+        headerlines=myfile.readlines()[0:3]
+        myfile.close()
+        if headerlines[0][0:11]=='# xPosition' and headerlines[1][0:11]=='# yPosition':
+            return True
+        else:
+            return False
+
+    def close_all(self):
+        self.filedata.close()
+
+    def _read_data_segment(self):
+        #routine that actually reads the data
+
+        height_ms=[]
+        height_m=[]
+        height=[]
+        v_deflection=[]
+        h_deflection=[]
+
+        self.springconstant=0 #if we don't meet any spring constant, use deflection...
+
+        for line in self.filelines:
+            #we meet the segment defining the order of data columns
+
+            if line[0:9]=='# columns':
+                splitline=line.split()[2:]
+                height_ms_index=splitline.index('smoothedStrainGaugeHeight')
+                height_m_index=splitline.index('strainGaugeHeight')
+                height_index=splitline.index('height')
+                v_deflection_index=splitline.index('vDeflection')
+                #h_deflection=splitline.index('hDeflection')
+
+            if line[0:16]=='# springConstant':
+                self.springconstant=float(line.split()[2])
+
+            if line[0] != '#' and len(line.split())>1:
+                dataline=line.split()
+                height_ms.append(float(dataline[height_ms_index]))
+                height_m.append(float(dataline[height_m_index]))
+                height.append(float(dataline[height_index]))
+                v_deflection.append(float(dataline[v_deflection_index]))
+                #h_deflection.append(float(dataline[h_deflection_index]))
+
+        if self.springconstant != 0:
+            force=[item*self.springconstant for item in v_deflection]
+        else: #we have measured no spring constant :(
+            force=v_deflection
+
+        height_ms=DataChunk([item*-1 for item in height_ms])
+        height_m=DataChunk([item*-1 for item in height_m])
+        height=DataChunk([item*-1 for item in height])
+        deflection=DataChunk(v_deflection)
+        force=DataChunk(force)
+
+        return height_ms,height_m,height,deflection,force
+
+    def deflection(self):
+        height_ms,height_m,height,deflection,force=self._read_data_segment()
+        deflection_ext=deflection.ext()
+        deflection_ret=deflection.ret()
+        deflection_ret.reverse()
+        return deflection_ext,deflection_ret
+
+    def default_plots(self):
+
+        height_ms,height_m,height,deflection,force=self._read_data_segment()
+
+        height_ms_ext=height_ms.ext()
+        height_ms_ret=height_ms.ret()
+        force_ext=force.ext()
+        force_ret=force.ret()
+        #reverse the return data, to make it coherent with hooke standard
+        height_ms_ret.reverse()
+        force_ret.reverse()
+
+        main_plot=lhc.PlotObject()
+        main_plot.add_set(height_ms_ext,force_ext)
+        main_plot.add_set(height_ms_ret,force_ret)
+
+
+
+        if self.springconstant != 0:
+            main_plot.units=['meters','force']
+        else:
+            main_plot.units=['meters','meters']
+
+        main_plot.normalize_vectors()
+
+        main_plot.destination=0
+        main_plot.title=self.filename
+
+        return [main_plot]
diff --git a/hooke/driver/mcs.py b/hooke/driver/mcs.py
new file mode 100644 (file)
index 0000000..a00117f
--- /dev/null
@@ -0,0 +1,93 @@
+# Copyright (C) 2009-2010 Allen Chen
+#                         Massimo Sandal <devicerandom@gmail.com>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Driver for mcs fluorescence files
+"""
+
+from .. import curve as lhc
+from .. import libhooke as lh
+import struct
+
+class mcsDriver(lhc.Driver):
+
+    def __init__(self, filename):
+        '''
+        Open the RED (A) ones; the BLUE (D) mirror ones will be automatically opened
+        '''
+        #obtain name of blue files
+        othername=filename
+        if othername[-8]=='a': #fixme: how to make it general? (maybe should not be in driverspace but in environment...)
+            oth=list(othername)
+            oth[-8]='d'
+            othername=''.join(oth)
+        self.filename=filename
+        self.othername=othername
+
+        #print self.filename, self.othername
+
+        self.filedata=open(filename,'rb')
+        self.reddata=self.filedata.read()
+        self.filedata.close()
+
+        self.filebluedata=open(othername,'rb') #open also the blue ones
+        self.bluedata=self.filebluedata.read()
+        self.filebluedata.close()
+
+        self.filetype = 'mcs'
+        self.experiment = 'smfluo'
+
+    def is_me(self):
+        if self.filename[-3:].lower()=='mcs':
+            return True
+        else:
+            return False
+
+    def close_all(self):
+        self.filedata.close()
+        self.filebluedata.close()
+
+
+    def default_plots(self):
+        red_data=self.read_file(self.reddata)
+        blue_data=self.read_file(self.bluedata)
+        blue_data=[-1*float(item) for item in blue_data] #visualize blue as "mirror" of red
+
+        main_plot=lhc.PlotObject()
+        main_plot.add_set(range(len(red_data)),red_data)
+        main_plot.add_set(range(len(blue_data)),blue_data)
+        main_plot.normalize_vectors()
+        main_plot.units=['time','count']  #FIXME: if there's an header saying something about the time count, should be used
+        main_plot.destination=0
+        main_plot.title=self.filename
+        main_plot.colors=['red','blue']
+
+        return [main_plot]
+
+    def read_file(self, raw_data):
+        real_data=[]
+        intervalsperfile=struct.unpack('h', raw_data[10:12])[0] #read in number of intervals in this file
+                                                                #this data is contained in bit offset 10-12 in mcs file
+        #see http://docs.python.org/library/struct.html#module-struct for additional explanation
+
+        numbytes=len(raw_data) #data is stored in 4-byte chunks, starting with pos 256
+        for j in range(0,intervalsperfile): #read in all intervals in file
+            temp=raw_data[256+j*4:256+j*4+4]    #data starts at byte offset 256
+            real_data.append(struct.unpack('i', temp)[0]) #[0] because it returns a 1-element tuple
+        return real_data
diff --git a/hooke/driver/mfp1dexport.py b/hooke/driver/mfp1dexport.py
new file mode 100644 (file)
index 0000000..4c5363c
--- /dev/null
@@ -0,0 +1,110 @@
+# Copyright (C) 2009-2010 Massimo Sandal <devicerandom@gmail.com>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Driver for text-exported MFP 1D files
+"""
+
+import os
+
+from .. import libhooke as lh
+from .. import curve as lhc
+
+
+__version__='0.0.0.20090923'
+
+
+class mfp1dexportDriver(lhc.Driver):
+
+    def __init__(self, filename):
+        '''
+        This is a driver to import Asylum Research MFP 1D data.
+        Status: experimental
+        '''
+        self.filename = filename
+        self.filedata = open(filename,'rU')
+        self.lines = list(self.filedata.readlines())
+        self.filedata.close()
+
+        self.filetype='mfp1dexport'
+        self.experiment='smfs'
+
+    def close_all(self):
+        self.filedata.close()
+
+    def is_me(self):
+        try:
+            self.raw_header = self.lines[0:38]
+        except:
+            #Not enough lines for a header; not a good file
+            return False
+
+        #FIXME: We want a more reasonable header recognition
+        if self.raw_header[0].startswith('Wave'):
+            return True
+        else:
+            return False
+
+    def _read_columns(self):
+
+        self.raw_columns=self.lines[39:]
+
+        kline=None
+        for line in self.lines:
+            if line[:7]=='SpringC':
+                kline=line
+                break
+
+        kline=kline.split(':')
+
+        #self.k=float(self.raw_header[23][8:])
+        self.k=float(kline[1])
+
+        xext=[]
+        xret=[]
+        yext=[]
+        yret=[]
+        for line in self.raw_columns:
+            spline=line.split()
+            xext.append(float(spline[0]))
+            yext.append(float(spline[1]))
+            xret.append(float(spline[2]))
+            yret.append(float(spline[3]))
+
+        return [[xext,yext],[xret,yret]]
+
+    def deflection(self):
+        self.data = self._read_columns()
+        return self.data[0][1], self.data[1][1]
+
+    def default_plots(self):
+        main_plot = lhc.PlotObject()
+        defl_ext,defl_ret = self.deflection()
+        yextforce = [i*self.k for i in defl_ext]
+        yretforce = [i*self.k for i in defl_ret]
+        main_plot.add_set(self.data[0][0], yextforce)
+        main_plot.add_set(self.data[1][0], yretforce)
+        main_plot.normalize_vectors()
+        #main_plot.units = ['Z','force']  #FIXME: if there's an header saying something about the time count, should be used
+        main_plot.units = ['m','N']
+        main_plot.destination = 0
+        main_plot.filename = self.filename
+        main_plot.title = os.path.basename(self.filename)
+        main_plot.colors = ['red','blue']
+        main_plot.style = ['plot','plot']
+        return [main_plot]
diff --git a/hooke/driver/mfp3d.py b/hooke/driver/mfp3d.py
new file mode 100644 (file)
index 0000000..1c5f209
--- /dev/null
@@ -0,0 +1,307 @@
+# Copyright (C) 2008-2010 A. Seeholzer
+#                         Alberto Gomez-Casado
+#                         Richard Naud <richard.naud@epfl.ch>
+#                         Rolf Schmidt <rschmidt@alcor.concordia.ca>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Driver for MFP-3D files.
+
+This driver reads Igor binary waves.
+
+AUTHORS:
+Matlab version: Richard Naud August 2008 (http://lcn.epfl.ch/~naud/)
+Python port: A. Seeholzer October 2008
+Hooke submission: Rolf Schmidt, Alberto Gomez-Casado 2009
+"""
+
+# DEFINITION:
+# Reads Igor's (Wavemetric) binary wave format, .ibw, files.
+#
+# ALGORITHM:
+# Parsing proper to version 2, 3, or version 5 (see Technical notes TN003.ifn:
+# http://mirror.optus.net.au/pub/wavemetrics/IgorPro/Technical_Notes/) and data
+# type 2 or 4 (non complex, single or double precision vector, real values).
+#
+# VERSION: 0.1
+#
+# COMMENTS:
+# Only tested for version 2 Igor files for now, testing for 3 and 5 remains to be done.
+# More header data could be passed back if wished. For significance of ignored bytes see
+# the technical notes linked above.
+
+import numpy
+import os.path
+import struct
+
+from .. import curve as lhc
+
+
+__version__='0.0.0.20100310'
+
+
+class DataChunk(list):
+    #Dummy class to provide ext and ret methods to the data list.
+    
+    def ext(self):
+        halflen=(len(self)/2)
+        return self[0:halflen]
+        
+    def ret(self):
+        halflen=(len(self)/2)
+        return self[halflen:]
+
+class mfp3dDriver(lhc.Driver):
+
+    #Construction and other special methods
+    
+    def __init__(self,filename):
+        '''
+        constructor method
+        '''
+           
+       self.textfile    =file(filename)
+        self.binfile=file(filename,'rb')
+       #unnecesary, but some other part of the program expects these to be open     
+
+        self.forcechunk=0
+        self.distancechunk=1
+       #TODO eliminate the need to set chunk numbers
+        
+        self.filepath=filename
+        self.debug=True
+        
+       self.data = []
+        self.note = []
+        self.retract_velocity = None
+        self.spring_constant = None
+        self.filename = filename
+
+        self.filedata = open(filename,'rU')
+        self.lines = list(self.filedata.readlines())
+        self.filedata.close()
+
+        self.filetype = 'mfp3d'
+        self.experiment = 'smfs'
+             
+     
+    def _get_data_chunk(self,whichchunk):
+
+       data = None
+        f = open(self.filename, 'rb')
+        ####################### ORDERING
+        # machine format for IEEE floating point with big-endian
+        # byte ordering
+        # MacIgor use the Motorola big-endian 'b'
+        # WinIgor use Intel little-endian 'l'
+        # If the first byte in the file is non-zero, then the file is a WinIgor
+        firstbyte = struct.unpack('b', f.read(1))[0]
+        if firstbyte == 0:
+            format = '>'
+        else:
+            format = '<'
+        #######################  CHECK VERSION
+        f.seek(0)
+        version = struct.unpack(format+'h', f.read(2))[0]
+        #######################  READ DATA AND ACCOMPANYING INFO
+        if version == 2 or version == 3:
+            # pre header
+            wfmSize = struct.unpack(format+'i', f.read(4))[0] # The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.
+            noteSize = struct.unpack(format+'i', f.read(4))[0] # The size of the note text.
+            if version==3:
+                formulaSize = struct.unpack(format+'i', f.read(4))[0]
+            pictSize = struct.unpack(format+'i', f.read(4))[0] # Reserved. Write zero. Ignore on read.
+            checksum = struct.unpack(format+'H', f.read(2))[0] # Checksum over this header and the wave header.
+            # wave header
+            dtype = struct.unpack(format+'h', f.read(2))[0]
+            if dtype == 2:
+                dtype = numpy.float32(.0).dtype
+            elif dtype == 4:
+                dtype = numpy.double(.0).dtype
+            else:
+                assert False, "Wave is of type '%i', not supported" % dtype
+            dtype = dtype.newbyteorder(format)
+
+            ignore = f.read(4) # 1 uint32
+            bname = self._flatten(struct.unpack(format+'20c', f.read(20)))
+            ignore = f.read(4) # 2 int16
+            ignore = f.read(4) # 1 uint32
+            dUnits = self._flatten(struct.unpack(format+'4c', f.read(4)))
+            xUnits = self._flatten(struct.unpack(format+'4c', f.read(4)))
+            npnts = struct.unpack(format+'i', f.read(4))[0]
+            amod = struct.unpack(format+'h', f.read(2))[0]
+            dx = struct.unpack(format+'d', f.read(8))[0]
+            x0 = struct.unpack(format+'d', f.read(8))[0]
+            ignore = f.read(4) # 2 int16
+            fsValid = struct.unpack(format+'h', f.read(2))[0]
+            topFullScale = struct.unpack(format+'d', f.read(8))[0]
+            botFullScale = struct.unpack(format+'d', f.read(8))[0]
+            ignore = f.read(16) # 16 int8
+            modDate = struct.unpack(format+'I', f.read(4))[0]
+            ignore = f.read(4) # 1 uint32
+            # Numpy algorithm works a lot faster than struct.unpack
+            data = numpy.fromfile(f, dtype, npnts)
+
+        elif version == 5:
+            # pre header
+            checksum = struct.unpack(format+'H', f.read(2))[0] # Checksum over this header and the wave header.
+            wfmSize = struct.unpack(format+'i', f.read(4))[0] # The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.
+            formulaSize = struct.unpack(format+'i', f.read(4))[0]
+            noteSize = struct.unpack(format+'i', f.read(4))[0] # The size of the note text.
+            dataEUnitsSize = struct.unpack(format+'i', f.read(4))[0]
+            dimEUnitsSize = struct.unpack(format+'4i', f.read(16))
+            dimLabelsSize = struct.unpack(format+'4i', f.read(16))
+            sIndicesSize = struct.unpack(format+'i', f.read(4))[0]
+            optionSize1 = struct.unpack(format+'i', f.read(4))[0]
+            optionSize2 = struct.unpack(format+'i', f.read(4))[0]
+
+            # header
+            ignore = f.read(4)
+            CreationDate =  struct.unpack(format+'I',f.read(4))[0]
+            modData =  struct.unpack(format+'I',f.read(4))[0]
+            npnts =  struct.unpack(format+'i',f.read(4))[0]
+            # wave header
+            dtype = struct.unpack(format+'h',f.read(2))[0]
+            if dtype == 2:
+                dtype = numpy.float32(.0).dtype
+            elif dtype == 4:
+                dtype = numpy.double(.0).dtype
+            else:
+                assert False, "Wave is of type '%i', not supported" % dtype
+            dtype = dtype.newbyteorder(format)
+
+            ignore = f.read(2) # 1 int16
+            ignore = f.read(6) # 6 schar, SCHAR = SIGNED CHAR?         ignore = fread(fid,6,'schar'); #
+            ignore = f.read(2) # 1 int16
+            bname = self._flatten(struct.unpack(format+'32c',f.read(32)))
+            ignore = f.read(4) # 1 int32
+            ignore = f.read(4) # 1 int32
+            ndims = struct.unpack(format+'4i',f.read(16)) # Number of of items in a dimension -- 0 means no data.
+            sfA = struct.unpack(format+'4d',f.read(32))
+            sfB = struct.unpack(format+'4d',f.read(32))
+            dUnits = self._flatten(struct.unpack(format+'4c',f.read(4)))
+            xUnits = self._flatten(struct.unpack(format+'16c',f.read(16)))
+            fsValid = struct.unpack(format+'h',f.read(2))
+            whpad3 = struct.unpack(format+'h',f.read(2))
+            ignore = f.read(16) # 2 double
+            ignore = f.read(40) # 10 int32
+            ignore = f.read(64) # 16 int32
+            ignore = f.read(6) # 3 int16
+            ignore = f.read(2) # 2 char
+            ignore = f.read(4) # 1 int32
+            ignore = f.read(4) # 2 int16
+            ignore = f.read(4) # 1 int32
+            ignore = f.read(8) # 2 int32
+
+            data = numpy.fromfile(f, dtype, npnts)
+            note_str = f.read(noteSize)
+            note_lines = note_str.split('\r')
+            self.note = {}
+            for line in note_lines:
+               if ':' in line:
+                   key, value = line.split(':', 1)
+                    self.note[key] = value
+            self.retract_velocity = float(self.note['Velocity'])
+            self.spring_constant = float(self.note['SpringConstant'])
+        else:
+            assert False, "Fileversion is of type '%i', not supported" % dtype
+            data = []
+
+        f.close()
+        if len(data) > 0:
+            #we have 3 columns: deflection, LVDT, raw
+            #TODO detect which is each one
+           count = npnts / 3
+            lvdt = data[:count] 
+            deflection = data[count:2 * count] 
+            #every column contains data for extension and retraction
+            #we assume the same number of points for each
+            #we could possibly extract this info from the note
+            count = npnts / 6
+
+           forcechunk=deflection*self.spring_constant
+           distancechunk=lvdt
+       
+           if  whichchunk==self.forcechunk:
+               return forcechunk
+           if whichchunk==self.distancechunk:
+               return distancechunk
+        else:
+            return None                          
+        
+    def _force(self):
+       #returns force vector
+        Kspring=self.spring_constant
+        return DataChunk([(meter*Kspring) for meter in self._deflection()])
+
+    def _deflection(self):
+        #for internal use (feeds _force)
+        deflect=self.data_chunks[self.forcechunk]/self.spring_constant              
+        return deflect
+
+    def _flatten(self, tup):
+        out = ''
+        for ch in tup:
+            out += ch
+        return out            
+    
+    def _Z(self):   
+        return DataChunk(self.data_chunks[self.distancechunk])
+        
+    def is_me(self):
+        if len(self.lines) < 34:
+            return False
+
+        name, extension = os.path.splitext(self.filename)
+        if extension == '.ibw':
+            for line in self.lines:
+                if line.startswith('ForceNote:'):
+                   self.data_chunks=[self._get_data_chunk(num) for num in [0,1,2]]
+                    return True
+            else:
+                return False
+        else:
+            return False
+    
+    def close_all(self):
+        '''
+        Explicitly closes all files
+        '''
+        self.textfile.close()
+        self.binfile.close()
+    
+    def default_plots(self):
+        '''
+        creates the default PlotObject
+        '''
+        force=self._force()
+        zdomain=self._Z()
+        main_plot=lhc.PlotObject()
+        main_plot.vectors=[[zdomain.ext(), force.ext()],[zdomain.ret(), force.ret()]]
+        main_plot.normalize_vectors()
+        main_plot.units=['meters','newton']
+        main_plot.destination=0
+        main_plot.title=self.filepath
+        
+        
+        return [main_plot]
+
+    def deflection(self):
+        #interface for correct plotmanip and others
+        deflectionchunk=DataChunk(self._deflection())
+        return deflectionchunk.ext(),deflectionchunk.ret()
diff --git a/hooke/driver/picoforce.py b/hooke/driver/picoforce.py
new file mode 100644 (file)
index 0000000..c1c9143
--- /dev/null
@@ -0,0 +1,353 @@
+# Copyright (C) 2006-2010 Alberto Gomez-Kasai
+#                         Massimo Sandal <devicerandom@gmail.com>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Library for interpreting Veeco PicoForce force spectroscopy files.
+"""
+
+import pprint
+import re
+import time
+
+import numpy
+
+from .. import curve as curve # this module defines data containers.
+from .. import experiment as experiment # this module defines expt. types
+from ..config import Setting # configurable setting class
+from . import Driver as Driver # this is the Driver base class
+
+
+__version__='0.0.0.20100516'
+
+class PicoForceDriver (Driver):
+    """Handle Veeco Picoforce force spectroscopy files.
+    """
+    def __init__(self):
+        super(PicoForceDriver, self).__init__(name='picoforce')
+
+    def is_me(self, path):
+        f = file(path, 'r')
+        header = f.read(30)
+        f.close()
+
+        return header[2:17] == 'Force file list'
+
+    def read(self, path):
+        info = self._read_header_path(path)
+        self._check_version(info)
+        data = self._read_data_path(path, info)
+        info['filetype'] = self.name
+        info['experiment'] = experiment.VelocityClamp
+        return (data, info)
+
+    def _read_header_path(self, path):
+        """Read curve information from the PicoForce file at `path`.
+
+        See :meth:`._read_header_file`.
+        """
+        return self._read_header_file(file(path, 'rb'))
+
+    def _read_header_file(self, file):
+        r"""Read curve information from a PicoForce file.
+
+        Return a dict of dicts representing the information.  If a
+        field is repeated multiple times, it's value is replaced by a
+        list of the values for each occurence.
+
+        Examples
+        --------
+
+        >>> import pprint
+        >>> import StringIO
+        >>> f = StringIO.StringIO('\r\n'.join([
+        ...             '\*Force file list',
+        ...             '\Version: 0x06120002',
+        ...             '\Date: 04:42:34 PM Tue Sep 11 2007',
+        ...             '\Start context: FOL2',
+        ...             '\Data length: 40960',
+        ...             '\Text: ',
+        ...             '\*Equipment list',
+        ...             '\Description: Extended PicoForce',
+        ...             '\Controller: IIIA',
+        ...             '\*Ciao force image list',
+        ...             '\Data offset: 40960',
+        ...             '\Data length: 8192',
+        ...             '\*Ciao force image list',
+        ...             '\Data offset: 49152',
+        ...             '\Data length: 8192',
+        ...             '\*Ciao force image list',
+        ...             '\Data offset: 57344',
+        ...             '\Data length: 8192',
+        ...             ]))
+        >>> p = PicoForceDriver()
+        >>> d = p._read_header_file(f)
+        >>> pprint.pprint(d, width=60)
+        {'Ciao force image list': [{'Data length': '8192',
+                                    'Data offset': '40960'},
+                                   {'Data length': '8192',
+                                    'Data offset': '49152'},
+                                   {'Data length': '8192',
+                                    'Data offset': '57344'}],
+         'Equipment list': {'Controller': 'IIIA',
+                            'Description': 'Extended PicoForce'},
+         'Force file list': {'Data length': '40960',
+                             'Date': '04:42:34 PM Tue Sep 11 2007',
+                             'Start context': 'FOL2',
+                             'Text:': None,
+                             'Version': '0x06120002'}}
+        """
+        info = {}
+        header_field = None
+        for line in file:
+            line = line.strip()
+            if line.startswith('\*File list end'):
+                break
+            if line.startswith(r'\*'):
+                header_field = line[len(r'\*'):]
+                if header_field in info:
+                    if isinstance(info[header_field], list):
+                        info[header_field].append({}) # >=3rd appearance
+                    else: # Second appearance
+                        info[header_field] = [info[header_field], {}]
+                else: # First appearance
+                    info[header_field] = {}
+            else:
+                assert line.startswith('\\'), line
+                fields = line[len('\\'):].split(': ', 1)
+                key = fields[0]
+                if len(fields) == 1: # fields = [key]
+                    value = None
+                else: # fields = [key, value]
+                    value = fields[1]
+                if isinstance(info[header_field], list): # >=2nd header_field
+                    target_dict = info[header_field][-1]
+                else: # first appearance of header_field
+                    target_dict = info[header_field]
+                if key in target_dict and target_dict[key] != value:
+                    raise NotImplementedError(
+                        'Overwriting %s: %s -> %s'
+                        % (key, target_dict[key], value))
+                target_dict[key] = value
+        return (info)
+
+    def _check_version(self, info):
+        """Ensure the input file is a version we understand.
+
+        Otherwise, raise `ValueError`.
+        """
+        version = info['Force file list'].get('Version', None)
+        if version not in ['0x06120002']:
+            raise ValueError(
+                '%s file version %s not supported (yet!)\n%s'
+                % (self.name, version,
+                   pprint.pformat(info['Force file list'])))
+
+    def _read_data_path(self, path, info):
+        """Read curve data from the PicoForce file at `path`.
+
+        See :meth:`._read_data_file`.
+        """
+        f = file(path, 'rb')
+        data = self._read_data_file(f, info)
+        f.close()
+        return data
+
+    def _read_data_file(self, file, info):
+        file.seek(0)
+        traces = self._extract_traces(buffer(file.read()), info)
+        deflection,z_piezo,deflection_B = traces
+        self._validate_traces(z_piezo, deflection, deflection_B)
+        L = len(deflection)
+        approach = self._extract_block(
+            info, z_piezo, deflection, 0, L/2, 'approach')
+        retract = self._extract_block(
+            info, z_piezo, deflection, L/2, L, 'retract')
+        data = [approach, retract]
+        return data
+
+    def _extract_traces(self, buffer, info):
+        """Extract each of the three vector blocks in a PicoForce file.
+        
+        The blocks are:
+
+        * Deflection input
+        * Z piezo sensor input
+        * Deflection again?
+
+        And their headers are marked with 'Ciao force image list'.
+        """
+        traces = [] 
+        for image in info['Ciao force image list']:
+            offset = int(image['Data offset'])
+            length = int(image['Data length'])
+            sample_size = int(image['Bytes/pixel'])
+            rows = length / sample_size
+            if sample_size != 2:
+                raise NotImplementedError('Size: %s' % sample_size)
+            d = curve.Data(
+                shape=(rows),
+                dtype=numpy.int16,
+                buffer=buffer,
+                offset=offset,
+                info=image,
+                )
+            traces.append(d)
+        return traces
+
+    def _validate_traces(self, z_piezo, deflection, deflection_B):
+        key = 'Spring Constant'
+        spring_constant = z_piezo.info[key]
+        for trace in [deflection, deflection_B]:
+            if trace.info[key] != spring_constant:
+                raise NotImplementedError(
+                    'spring constant missmatch: %s != %s'
+                    % (spring_constant, trace.info[key]))
+        if max(abs(deflection_B[:-1]-deflection[:-1])) != 0:
+            raise NotImplementedError('trace 0 != trace 2')
+        if len(z_piezo) != len(deflection):
+            raise ValueError('Trace length missmatch: %d != %d'
+                             % (len(z_piezo), len(deflection)))
+
+    def _extract_block(self, info, z_piezo, deflection, start, stop, name):
+        block = curve.Data(
+            shape=(stop-start, 2),
+            dtype=numpy.float)
+        block[:,0] = z_piezo[start:stop]
+        block[:,1] = deflection[start:stop]
+        block.info = self._translate_block_info(
+            info, z_piezo.info, deflection.info, name)
+        block = self._scale_block(block)
+        return block
+
+    def _translate_block_info(self, info, z_piezo_info, deflection_info, name):
+        ret = {
+            'name':name,
+            'raw info':info,
+            'raw z piezo info': z_piezo_info,
+            'raw deflection info': deflection_info,
+            'spring constant (N/m)':float(z_piezo_info['Spring Constant'])
+            }
+
+        t = info['Force file list']['Date'] # 04:42:34 PM Tue Sep 11 2007
+        ret['time'] = time.strptime(t, '%I:%M:%S %p %a %b %d %Y')
+
+        type_re = re.compile('S \[(\w*)\] "([\w\s]*)"')
+        match = type_re.match(z_piezo_info['@4:Image Data'])
+        assert match.group(1).lower() == match.group(2).replace(' ','').lower(), \
+            'Name missmatch: "%s", "%s"' % (match.group(1), match.group(2))
+        ret['columns'] = [match.group(2)]
+        match = type_re.match(deflection_info['@4:Image Data'])
+        assert match.group(1).lower() == match.group(2).replace(' ','').lower(), \
+            'Name missmatch: "%s", "%s"' % (match.group(1), match.group(2))
+        ret['columns'].append(match.group(2))
+        assert ret['columns'] == ['Z sensor', 'Deflection'], \
+            'Unexpected columns: %s' % ret['columns']
+        ret['columns'] = ['z piezo (m)', 'deflection (m)']
+
+        volt_re = re.compile(
+            'V \[Sens. (\w*)\] \(([.0-9]*) V/LSB\) ([.0-9]*) V')
+        match = volt_re.match(z_piezo_info['@4:Z scale'])
+        assert match.group(1) == 'ZSensorSens', z_piezo_info['@4:Z scale']
+        ret['z piezo sensitivity (V/bit)'] = float(match.group(2))
+        ret['z piezo range (V)'] = float(match.group(3))
+        ret['z piezo offset (V)'] = 0.0
+        # offset assumed if raw data is signed...
+
+        match = volt_re.match(deflection_info['@4:Z scale'])
+        assert match.group(1) == 'DeflSens', z_piezo_info['@4:Z scale']
+        ret['deflection sensitivity (V/bit)'] = float(match.group(2))
+        ret['deflection range (V)'] = float(match.group(3))
+        ret['deflection offset (V)'] = 0.0
+        # offset assumed if raw data is signed...
+
+        nm_sens_re = re.compile('V ([.0-9]*) nm/V')
+        match = nm_sens_re.match(info['Scanner list']['@Sens. Zsens'])
+        ret['z piezo sensitivity (m/V)'] = float(match.group(1))*1e-9
+
+        match = nm_sens_re.match(info['Ciao scan list']['@Sens. DeflSens'])
+        ret['deflection sensitivity (m/V)'] = float(match.group(1))*1e-9
+
+        match = volt_re.match(info['Ciao force list']['@Z scan start'])
+        ret['z piezo scan (V/bit)'] = float(match.group(2))
+        ret['z piezo scan start (V)'] = float(match.group(3))
+
+        match = volt_re.match(info['Ciao force list']['@Z scan size'])
+        ret['z piezo scan size (V)'] = float(match.group(3))
+
+        const_re = re.compile('C \[([:\w\s]*)\] ([.0-9]*)')
+        match = const_re.match(z_piezo_info['@Z magnify'])
+        assert match.group(1) == '4:Z scale', match.group(1)
+        ret['z piezo magnification'] = match.group(2)
+
+        match = volt_re.match(z_piezo_info['@4:Z scale'])
+        assert match.group(1) == 'ZSensorSens', match.group(1)
+        ret['z piezo scale (V/bit)'] = float(match.group(2))
+        ret['z piezo scale (V)'] = float(match.group(3))
+
+        match = volt_re.match(z_piezo_info['@4:Ramp size'])
+        assert match.group(1) == 'Zsens', match.group(1)
+        ret['z piezo ramp size (V/bit)'] = float(match.group(2))
+        ret['z piezo ramp size (V)'] = float(match.group(3))
+
+        match = volt_re.match(z_piezo_info['@4:Ramp offset'])
+        assert match.group(1) == 'Zsens', match.group(1)
+        ret['z piezo ramp offset (V/bit)'] = float(match.group(2))
+        ret['z piezo ramp offset (V)'] = float(match.group(3))
+        
+        # Unaccounted for:
+        #   Samps*
+        
+        return ret
+
+    def _scale_block(self, data):
+        """Convert the block from its native format to a `numpy.float`
+        array in SI units.
+        """
+        ret = curve.Data(
+            shape=data.shape,
+            dtype=numpy.float,
+            )
+        info = data.info
+        ret.info = info
+        ret.info['raw-data'] = data # store the raw data
+        data.info = {} # break circular reference info <-> data
+
+        z_col = info['columns'].index('z piezo (m)')
+        d_col = info['columns'].index('deflection (m)')
+
+        # Leading '-' because Veeco's z increases towards the surface
+        # (positive indentation), but it makes more sense to me to
+        # have it inzrease away from the surface (positive
+        # separation).
+        ret[:,z_col] = -(
+            (data[:,z_col].astype(ret.dtype)
+             * info['z piezo sensitivity (V/bit)']
+             - info['z piezo offset (V)'])
+            * info['z piezo sensitivity (m/V)']
+            )
+
+        ret[:,d_col] = (
+            (data[:,d_col]
+             * info['deflection sensitivity (V/bit)']
+             - info['deflection offset (V)'])
+            * info['deflection sensitivity (m/V)']
+            )
+
+        return ret
+
diff --git a/hooke/driver/picoforcealt.py b/hooke/driver/picoforcealt.py
new file mode 100644 (file)
index 0000000..b89fc48
--- /dev/null
@@ -0,0 +1,290 @@
+# Copyright (C) 2006-2010 Alberto Gomez-Casado
+#                         Massimo Sandal <devicerandom@gmail.com>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Library for interpreting Picoforce force spectroscopy files.
+
+An alternave implementation of :mod:`hooke.driver.picoforce`.
+"""
+
+import re, struct
+from scipy import arange
+
+from .. import curve as lhc
+
+__version__='0.0.0.20081706'
+
+
+
+class DataChunk(list):
+    #Dummy class to provide ext and ret methods to the data list.
+
+    def ext(self):
+        halflen=(len(self)/2)
+        return self[0:halflen]
+
+    def ret(self):
+        halflen=(len(self)/2)
+        return self[halflen:]
+
+class picoforcealtDriver(lhc.Driver):
+
+    #Construction and other special methods
+
+    def __init__(self,filename):
+        '''
+        constructor method
+        '''
+
+        self.textfile=file(filename)
+        self.binfile=file(filename,'rb')
+
+        #The 0,1,2 data chunks are:
+        #0: D (vs T)
+        #1: Z (vs T)
+        #2: D (vs Z)
+
+        self.forcechunk=0
+        self.distancechunk=1
+       #TODO eliminate the need to set chunk numbers
+
+        self.filepath=filename
+        self.debug=True
+
+        self.filetype='picoforce'
+        self.experiment='smfs'
+
+
+
+    def _get_samples_line(self):
+        '''
+        Gets the samples per line parameters in the file, to understand trigger behaviour.
+        '''
+        self.textfile.seek(0)
+
+        samps_expr=re.compile(".*Samps")
+
+        samps_values=[]
+        for line in self.textfile.readlines():
+            if samps_expr.match(line):
+                try:
+                    samps=int(line.split()[2]) #the third word splitted is the offset (in bytes)
+                    samps_values.append(samps)
+                except:
+                    pass
+
+                #We raise a flag for the fact we meet an offset, otherwise we would take spurious data length arguments.
+
+        return int(samps_values[0])
+
+    def _get_chunk_coordinates(self):
+        '''
+        This method gets the coordinates (offset and length) of a data chunk in our
+        Picoforce file.
+
+        It returns a list containing two tuples:
+        the first element of each tuple is the data_offset, the second is the corresponding
+        data size.
+
+        In near future probably each chunk will get its own data structure, with
+        offset, size, type, etc.
+        '''
+        self.textfile.seek(0)
+
+        offset_expr=re.compile(".*Data offset")
+        length_expr=re.compile(".*Data length")
+
+        data_offsets=[]
+        data_sizes=[]
+        flag_offset=0
+
+        for line in self.textfile.readlines():
+
+            if offset_expr.match(line):
+                offset=int(line.split()[2]) #the third word splitted is the offset (in bytes)
+                data_offsets.append(offset)
+                #We raise a flag for the fact we meet an offset, otherwise we would take spurious data length arguments.
+                flag_offset=1
+
+            #same for the data length
+            if length_expr.match(line) and flag_offset:
+                size=int(line.split()[2])
+                data_sizes.append(size)
+                #Put down the offset flag until the next offset is met.
+                flag_offset=0
+
+        return zip(data_offsets,data_sizes)
+
+    def _get_data_chunk(self,whichchunk):
+        '''
+        reads a data chunk and converts it in 16bit signed int.
+        '''
+        offset,size=self._get_chunk_coordinates()[whichchunk]
+
+
+        self.binfile.seek(offset)
+        raw_chunk=self.binfile.read(size)
+
+        my_chunk=[]
+        for data_position in range(0,len(raw_chunk),2):
+            data_unit_bytes=raw_chunk[data_position:data_position+2]
+            #The unpack function converts 2-bytes in a signed int ('h').
+            #we use output[0] because unpack returns a 1-value tuple, and we want the number only
+            data_unit=struct.unpack('h',data_unit_bytes)[0]
+            my_chunk.append(data_unit)
+
+        return DataChunk(my_chunk)
+
+    def _force(self):
+       #returns force vector
+        Kspring=self.get_spring_constant()
+        return DataChunk([(meter*Kspring) for meter in self._deflection()])
+
+    def _deflection(self):
+        #for internal use (feeds _force)
+        voltrange=1
+        z_scale=self._get_Z_scale()
+        deflsensitivity=self.get_deflection_sensitivity()
+        volts=[((float(lsb))*voltrange*z_scale) for lsb in self.data_chunks[self.forcechunk]]
+        deflect=[volt*deflsensitivity for volt in volts]
+       
+        return deflect
+
+
+    def _Z(self):
+        #returns distance vector (calculated instead than from data chunk)
+        rampsize=self._get_rampsize()
+        sampsline=self._get_samples_line()
+        senszscan=self._get_Z_scan_sens()
+
+        xstep=senszscan*rampsize/sampsline*10**(-9)
+
+        xext=arange(sampsline*xstep,0,-xstep)
+        xret=arange(sampsline*xstep,0,-xstep)
+
+        return DataChunk(xext.tolist()+xret.tolist())
+
+    def _get_Z_scale(self):
+        self.textfile.seek(0)
+        expr=re.compile(".*@4:Z scale")
+
+        for line in self.textfile.readlines():
+            if expr.match(line):
+                zscale=float((line.split()[5]).strip("() []"))
+                break
+        return zscale
+
+    def _get_rampsize(self):
+        self.textfile.seek(0)
+        expr=re.compile(".*@4:Ramp size:")
+
+        for line in self.textfile.readlines():
+            if expr.match(line):
+                zsens=float((line.split()[7]).strip("() []"))
+                break
+        return zsens
+
+    def _get_Z_scan_sens(self):
+        self.textfile.seek(0)
+        expr=re.compile(".*@Sens. Zsens")
+
+        for line in self.textfile.readlines():
+            if expr.match(line):
+                zsens=float((line.split()[3]).strip("() []"))
+                break
+        return zsens
+
+
+
+    def get_deflection_sensitivity(self):
+        '''
+        gets deflection sensitivity
+        '''
+        self.textfile.seek(0)
+
+        def_sensitivity_expr=re.compile(".*@Sens. DeflSens")
+
+        for line in self.textfile.readlines():
+            if def_sensitivity_expr.match(line):
+                def_sensitivity=float(line.split()[3])
+                break
+        #return it in SI units (that is: m/V, not nm/V)
+        return def_sensitivity*(10**(-9))
+
+    def get_spring_constant(self):
+        '''
+        gets spring constant.
+        We actually find *three* spring constant values, one for each data chunk (F/t, Z/t, F/z).
+        They are normally all equal, but we retain all three for future...
+        '''
+        self.textfile.seek(0)
+
+        springconstant_expr=re.compile(".*Spring Constant")
+
+        constants=[]
+
+        for line in self.textfile.readlines():
+            if springconstant_expr.match(line):
+                constants.append(float(line.split()[2]))
+
+        return constants[0]
+
+    def is_me(self):
+        '''
+        self-identification of file type magic
+        '''
+        curve_file=file(self.filepath)
+        header=curve_file.read(30)
+        curve_file.close()
+
+        if header[2:17] == 'Force file list': #header of a picoforce file
+            #here DONT translate chunk
+            self.data_chunks=[self._get_data_chunk(num) for num in [0,1,2]]
+            return True
+        else:
+            return False
+
+    def close_all(self):
+        '''
+        Explicitly closes all files
+        '''
+        self.textfile.close()
+        self.binfile.close()
+
+    def default_plots(self):
+        '''
+        creates the default PlotObject
+        '''
+        force=self._force()
+        zdomain=self._Z()
+        samples=self._get_samples_line()
+        main_plot=lhc.PlotObject()
+        main_plot.vectors=[[zdomain.ext()[0:samples], force.ext()[0:samples]],[zdomain.ret()[0:samples], force.ret()[0:samples]]]
+        main_plot.normalize_vectors()
+        main_plot.units=['meters','newton']
+        main_plot.destination=0
+        main_plot.title=self.filepath
+
+
+        return [main_plot]
+
+    def deflection(self):
+        #interface for correct plotmanip and others
+        deflectionchunk=DataChunk(self._deflection())
+        return deflectionchunk.ext(),deflectionchunk.ret()
diff --git a/hooke/driver/tutorial.py b/hooke/driver/tutorial.py
new file mode 100644 (file)
index 0000000..adcaf2c
--- /dev/null
@@ -0,0 +1,136 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Tutorial driver for Hooke.
+
+This example driver explains driver construction.
+"""
+
+"""
+Here we define a simple file format that is read by this driver. The
+file format is as following::
+
+    TUTORIAL_FILE
+    PLOT1
+    X1
+    n1   <- ?
+    n2
+    ...
+    nN
+    Y1
+    n1
+    n2
+    ...
+    nN
+    X2
+    n1
+    n2
+    ..
+    nN
+    Y2
+    n1
+    n2
+    ..
+    nN
+    PLOT2
+    X1
+    ...
+    Y1
+    ...
+    X2
+    ...
+    Y2
+    ...
+    END
+
+that is, two plots with two datasets each.
+"""
+
+# The following are relative imports.  See PEP 328 for details
+#   http://www.python.org/dev/peps/pep-0328/
+from .. import curve as curve # this module defines data containers.
+from .. import experiment as experiment # this module defines expt. types
+from ..config import Setting # configurable setting class
+from . import Driver as Driver # this is the Driver base class
+
+# The driver must inherit from the parent
+# :class:`hooke.driver.Driver` (which we have imported as `Driver`).
+class TutorialDriver (Driver):
+    """Handle simple text data as an example Driver.
+    """
+    def __init__(self):
+        """YOU MUST OVERRIDE Driver.__init__.
+
+        Here you set a value for `name` to identify your driver.  It
+        should match the module name.
+        """
+        super(TutorialDriver, self).__init__(name='tutorial')
+
+    def default_settings(self):
+        """Return a list of any configurable settings for your driver.
+
+        If your driver does not have any configurable settings, there
+        is no need to override this method.
+        """
+        return [
+            Setting(section=self.setting_section, help=self.__doc__),
+            Setting(section=self.setting_section, option='x units', value='nm',
+                    help='Set the units used for the x data.'),
+            ]
+
+    def is_me(self, path):
+        """YOU MUST OVERRIDE Driver.is_me.
+
+        RETURNS: Boolean (`True` or `False`)
+
+        This method is a heuristic that looks at the file content and
+        decides if the file can be opened by the driver itself.  It
+        returns `True` if the file opened can be interpreted by the
+        current driver, `False` otherwise.  Defining this method allows
+        Hooke to understand what kind of files we're looking at
+        automatically.
+        """
+
+        f = open(path, 'r')
+        header = f.readline() # we only need the first line
+        f.close()
+
+        """Our "magic fingerprint" is the TUTORIAL_FILE header. Of
+        course, depending on the data file, you can have interesting
+        headers, or patterns, etc. that you can use to guess the data
+        format. What matters is successful recognition and the boolean
+        (True/False) return.
+        """
+        if header.startswith('TUTORIAL_FILE'):
+            return True
+        return False
+
+    def read(self, path):
+        f = open(path,'r') # open the file for reading
+        """In this case, we have a data format that is just a list of
+        ASCII values, so we can just divide that in rows, and generate
+        a list with each item being a row.  Of course if your data
+        files are binary, or follow a different approach, do whatever
+        you need. :)
+        """
+        self.data = list(self.filedata)
+        f.close() # remember to close the file
+
+        data = curve.Data()
+        info = {'filetype':'tutorial', 'experiment':experiment.Experiment}
+        return (data, info)
diff --git a/hooke/engine.py b/hooke/engine.py
new file mode 100644 (file)
index 0000000..a973730
--- /dev/null
@@ -0,0 +1,47 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""The `engine` module provides :class:`CommandEngine` for executing
+:class:`hooke.command.Command`\s.
+"""
+
+from .ui import CloseEngine, CommandMessage
+
+class CommandEngine (object):
+    def run(self, hooke, ui_to_command_queue, command_to_ui_queue):
+        """Get a :class:`hooke.ui.QueueMessage` from the incoming
+        `ui_to_command_queue` and act accordingly.
+
+        If the message is a :class:`hooke.ui.CommandMessage` instance,
+        the command run may read subsequent input from
+        `ui_to_command_queue` (if it is an interactive command).  The
+        command may also put assorted data into `command_to_ui_queue`.
+
+        When the command completes, it will put a
+        :class:`hooke.command.CommandExit` instance into
+        `command_to_ui_queue`, at which point the `CommandEngine` will
+        be ready to receive the next :class:`hooke.ui.QueueMessage`.
+        """
+        while True:
+            msg = ui_to_command_queue.get()
+            if isinstance(msg, CloseEngine):
+                command_to_ui_queue.put(hooke)
+                break
+            assert isinstance(msg, CommandMessage), type(msg)
+            msg.command.run(hooke, ui_to_command_queue, command_to_ui_queue,
+                            **msg.arguments)
diff --git a/hooke/experiment.py b/hooke/experiment.py
new file mode 100644 (file)
index 0000000..9f94488
--- /dev/null
@@ -0,0 +1,61 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Define :class:`Experiment` and assorted subclasses.
+
+This allows :class:`hooke.plugin.Plugin`\s to specify the types of
+experiments they can handle.
+"""
+
+class Experiment (object):
+    """Base class for experiment classification.
+    """
+    pass
+
+class ForceClamp (Experiment):
+    """Constant force force spectroscopy [#fernandez2004]_.
+
+    .. [#fernandez2004] J.M. Fernandez, H. Li.
+      "Force-Clamp Spectroscopy Monitors the Folding Trajectory of a
+      Single Protein."
+      Science, 2004.
+      doi: `10.1126/science.1092497 <http://dx.doi.org/10.1126/science.1092497>`_
+    """
+    pass
+
+class VelocityClamp (Experiment):
+    """Constant piezo velocity force spectroscopy [#rief1997]_.
+    
+    .. [#rief1997] M. Rief, M. Gautel, F. Oesterhelt, J.M. Fernandez,
+      H.E. Gaub.
+      "Reversible Unfolding of Individual Titin Immunoglobulin Domains by AFM."
+      Science, 1997.
+      doi: `10.1126/science.276.5315.1109 <http://dx.doi.org/10.1126/science.276.5315.1109>`_
+    """
+    pass
+
+class TwoColorCoincidenceDetection (Experiment):
+    """Two-color fluorescence coincidence spectroscopy [#clarke2007]_.
+
+    .. [#clarke2007] R.W. Clarke, A. Orte, D. Klenerman.
+      "Optimized Threshold Selection for Single-Molecule Two-Color
+      Fluorescence Coincidence Spectroscopy."
+      Anal. Chem., 2007.
+      doi: `10.1021/ac062188w <http://dx.doi.org/10.1021/ac062188w>`_
+    """
+    pass
diff --git a/hooke/hooke.py b/hooke/hooke.py
new file mode 100644 (file)
index 0000000..e8a0050
--- /dev/null
@@ -0,0 +1,159 @@
+# Copyright (C) 2008-2010 Fabrizio Benedetti
+#                         Massimo Sandal <devicerandom@gmail.com>
+#                         Rolf Schmidt <rschmidt@alcor.concordia.ca>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Hooke - A force spectroscopy review & analysis tool.
+"""
+
+import multiprocessing
+import optparse
+import os.path
+import unittest
+import sys
+
+from . import engine as engine
+from . import config as config_mod
+from . import playlist as playlist
+from . import plugin as plugin_mod
+from . import driver as driver_mod
+from . import ui as ui
+
+
+class Hooke (object):
+    def __init__(self, config=None, debug=0):
+        self.debug = debug
+        default_settings = (config_mod.DEFAULT_SETTINGS
+                            + plugin_mod.default_settings()
+                            + driver_mod.default_settings()
+                            + ui.default_settings())
+        if config == None:
+            config = config_mod.HookeConfigParser(
+                paths=config_mod.DEFAULT_PATHS,
+                default_settings=default_settings)
+            config.read()
+        self.config = config
+        self.load_plugins()
+        self.load_drivers()
+        self.load_ui()
+        self.command = engine.CommandEngine()
+
+        self.playlists = playlist.NoteIndexList()
+
+    def load_plugins(self):
+        self.plugins = plugin_mod.load_graph(
+            plugin_mod.PLUGIN_GRAPH, self.config, include_section='plugins')
+        self.commands = []
+        for plugin in self.plugins:
+            self.commands.extend(plugin.commands())
+
+    def load_drivers(self):
+        self.drivers = plugin_mod.load_graph(
+            driver_mod.DRIVER_GRAPH, self.config, include_section='drivers')
+
+    def load_ui(self):
+        self.ui = ui.load_ui(self.config)
+
+    def close(self):
+        self.config.write() # Does not preserve original comments
+
+class HookeRunner (object):
+    def run(self, hooke):
+        """Run Hooke's main execution loop.
+
+        Spawns a :class:`hooke.engine.CommandEngine` subprocess and
+        then runs the UI, rejoining the `CommandEngine` process after
+        the UI exits.
+        """
+        ui_to_command,command_to_ui,command = self._setup_run(hooke)
+        try:
+            self.ui.run(hooke.commands, ui_to_command, command_to_ui)
+        finally:
+            hooke = self._cleanup_run(ui_to_command, command_to_ui, command)
+        return hooke
+
+    def run_lines(self, hooke, lines):
+        """Run the pre-set commands `lines` with the "command line" UI.
+
+        Allows for non-interactive sessions that are otherwise
+        equivalent to :meth:'.run'.
+        """
+        cmdline = ui.load_ui(hooke.config, 'command line')
+        ui_to_command,command_to_ui,command = self._setup_run(hooke)
+        try:
+            cmdline.run_lines(
+                hooke.commands, ui_to_command, command_to_ui, lines)
+        finally:
+            hooke = self._cleanup_run(ui_to_command, command_to_ui, command)
+        return hooke
+
+    def _setup_run(self, hooke):
+        ui_to_command = multiprocessing.Queue()
+        command_to_ui = multiprocessing.Queue()
+        manager = multiprocessing.Manager()        
+        command = multiprocessing.Process(name='command engine',
+            target=hooke.command.run, args=(hooke, ui_to_command, command_to_ui))
+        command.start()
+        return (ui_to_command, command_to_ui, command)
+
+    def _cleanup_run(self, ui_to_command, command_to_ui, command):
+        ui_to_command.put(ui.CloseEngine())
+        hooke = command_to_ui.get()
+        assert isinstance(hooke, Hooke)
+        command.join()
+        return hooke
+
+
+def main():
+    p = optparse.OptionParser()
+    p.add_option(
+        '-s', '--script', dest='script', metavar='FILE',
+        help='Script of command line Hooke commands to run.')
+    p.add_option(
+        '-c', '--command', dest='commands', metavar='COMMAND',
+        action='append', default=[],
+        help='Add a command line Hooke command to run.')
+    options,arguments = p.parse_args()
+    if len(arguments) > 0:
+        print >> sys.stderr, 'Too many arguments to %s: %d > 0' \
+            % (sys.argv[0], len(arguments))
+        print >> sys.stderr, p.help()
+        sys.exit(1)
+
+    hooke = Hooke(debug=__debug__)
+    runner = HookeRunner()
+
+    if options.script != None:
+        f = open(os.path.expanduser(options.script), 'r')
+        options.commands.extend(f.readlines())
+        f.close
+    if len(options.commands) > 0:
+        try:
+            hooke = runner.run_lines(hooke, options.commands)
+        finally:
+            hooke.close()
+        sys.exit(0)
+
+    try:
+        hooke = runner.run(hooke)
+    finally:
+        hooke.close()
+
+if __name__ == '__main__':
+    main()
diff --git a/hooke/interaction.py b/hooke/interaction.py
new file mode 100644 (file)
index 0000000..778a0d6
--- /dev/null
@@ -0,0 +1,140 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""The `interaction` module provides :class:`Request`,
+:class:`Response`, an related classes for handling user interaction
+through the
+:class:`hooke.engine.CommandEngine`/:class:`hooke.ui.UserInterface`
+connection.
+"""
+
+
+class InList (list):
+    """:class:`Request` validator class.
+
+    Examples
+    --------
+
+    >>> i = InList(['abc', 'def', 5, True])
+    >>> i('abc')
+    >>> i(5)
+    >>> i(False)
+    Traceback (most recent call last):
+      ...
+    ValueError: False
+    """
+    def __init__(self, *args, **kwargs):
+        list.__init__(self, *args, **kwargs)
+
+    def __call__(self, value):
+        """Raises ValueError if a given `value` is not in our internal
+        list.
+        """
+        if value not in self:
+            raise ValueError(value)
+
+class Interaction (object):
+    """Mid-command inter-process interaction.
+
+    Stores :attr:`type`, a string representing the interaction type
+    ('boolean', 'string', ...).
+    """
+    def __init__(self, type):
+        self.type = type
+
+class Request (Interaction):
+    """Command engine requests for information from the UI.
+    """
+    def __init__(self, type, response_class,
+                 msg, default=None, validator=None):
+        super(Request, self).__init__(type)
+        self.response_class = response_class
+        self.msg = msg
+        self.default = default
+        self.validator = validator
+
+    def response(self, value):
+        if self.validator != None:
+            self.validator(value)
+        return self.response_class(value)
+
+class Response (Interaction):
+    """UI response to a :class:`Request`.
+    """
+    def __init__(self, type, value):
+        super(Response, self).__init__(type)
+        self.value = value
+
+class BooleanRequest (Request):
+    def __init__(self, msg, default=None):
+        super(BooleanRequest, self).__init__(
+            'boolean', BooleanResponse, msg, default,
+            validator = InList([True, False, default]))
+
+class BooleanResponse (Response):
+    def __init__(self, value):
+        super(BooleanResponse, self).__init__('boolean', value)
+
+class StringRequest (Request):
+    def __init__(self, msg, default=None):
+        super(StringRequest, self).__init__(
+            'string', StringResponse, msg, default)
+
+class StringResponse (Response):
+    def __init__(self, value):
+        super(StringResponse, self).__init__('string', value)
+
+class FloatRequest (Request):
+    def __init__(self, msg, default=None):
+        super(FloatRequest, self).__init__(
+            'float', FloatResponse, msg, default)
+
+class FloatResponse (Response):
+    def __init__(self, value):
+        super(FloatResponse, self).__init__('float', value)
+
+class SelectionRequest (Request):
+    def __init__(self, msg, default=None, options=[]):
+        super(SelectionRequest, self).__init__(
+            'selection', SelectionResponse, msg, default)
+        self.options = options
+
+class SelectionResponse (Response):
+    def __init__(self, value):
+        super(SelectionResponse, self).__init__('selection', value)
+
+class PointRequest (Request):
+    def __init__(self, msg, curve, block=0, default=None):
+        super(PointRequest, self).__init__(
+            'point', PointResponse, msg, default)
+        self.options = options
+
+class PointResponse (Response):
+    def __init__(self, value):
+        super(PointResponse, self).__init__('point', value)
+
+
+class Notification (object):
+    def __init__(self, type):
+        self.type = type
+
+class ReloadUserInterfaceConfig (Notification):
+    def __init__(self, config):
+        super(ReloadUserInterfaceConfig, self).__init__(
+            'reload user interface config')
+        self.config = config
diff --git a/hooke/playlist.py b/hooke/playlist.py
new file mode 100644 (file)
index 0000000..ca38b4e
--- /dev/null
@@ -0,0 +1,284 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""The `playlist` module provides a :class:`Playlist` and its subclass
+:class:`FilePlaylist` for manipulating lists of
+:class:`hooke.curve.Curve`\s.
+"""
+
+import copy
+import hashlib
+import os.path
+import xml.dom.minidom
+
+from . import curve as curve
+
+
+class NoteIndexList (list):
+    """A list that keeps track of a "current" item and additional notes.
+
+    :attr:`index` (i.e. "bookmark") is the index of the currently
+    current curve.  Also keep a :class:`dict` of additional information
+    (:attr:`info`).
+    """
+    def __init__(self, name=None):
+        super(NoteIndexList, self).__init__()
+        self.name = name
+        self.info = {}
+        self._index = 0
+
+    def __str__(self):
+        return '<%s %s>' % (self.__class__.__name__, self.name)
+
+    def current(self):
+        if len(self) == 0:
+            return None
+        return self[self._index]
+
+    def jump(self, index):
+        if len(self) == 0:
+            self._index = 0
+        else:
+            self._index = index % len(self)
+
+    def next(self):
+        self.jump(self._index + 1)
+
+    def previous(self):
+        self.jump(self._index - 1)
+
+    def filter(self, keeper_fn=lambda item:True):
+        c = copy.deepcopy(self)
+        for item in reversed(c):
+            if keeper_fn(item) != True:
+                c.remove(item)
+        try: # attempt to maintain the same current item
+            c._index = c.index(self.current())
+        except ValueError:
+            c._index = 0
+        return c
+
+class Playlist (NoteIndexList):
+    """A :class:`NoteIndexList` of :class:`hooke.curve.Curve`\s.
+
+    Keeps a list of :attr:`drivers` for loading curves.
+    """
+    def __init__(self, drivers, name=None):
+        super(Playlist, self).__init__(name=name)
+        self.drivers = drivers
+        self._loaded = [] # List of loaded curves, see :meth:`._load`.
+        self._max_loaded = 100 # curves to hold in memory simultaneously.
+
+    def append_curve_by_path(self, path, info=None, identify=True):
+        if self.path != None:
+            path = os.path.join(os.path.dirname(self.path), path)
+        path = os.path.normpath(path)
+        c = curve.Curve(path, info=info)
+        if identify == True:
+            c.identify(self.drivers)
+        self.append(c)
+        return c
+
+    def current(self):
+        curve = super(Playlist, self).current()
+        self._load(curve)
+        return curve
+
+    def _load(self, curve):
+        if curve != None and curve not in self._loaded:
+            if curve not in self:
+                self.append(curve)
+            if curve.driver == None:
+                c.identify(self.drivers)
+            if curve.data == None:
+                curve.load()
+            self._loaded.append(curve)
+            if len(self._loaded) > self._max_loaded:
+                oldest = self._loaded.pop(0)
+                oldest.unload()
+
+class FilePlaylist (Playlist):
+    version = '0.1'
+
+    def __init__(self, drivers, name=None, path=None):
+        super(FilePlaylist, self).__init__(drivers, name)
+        self.set_path(path)
+        self._digest = None
+
+    def set_path(self, path):
+        if path != None:
+            if not path.endswith('.hkp'):
+                path += '.hkp'
+            self.path = path
+            if self.name == None:
+                self.name = os.path.basename(path)
+
+    def is_saved(self):
+        return self.digest() == self._digest
+
+    def digest(self):
+        r"""Compute the sha1 digest of the flattened playlist
+        representation.
+
+        Examples
+        --------
+
+        >>> root_path = os.path.sep + 'path'
+        >>> p = FilePlaylist(drivers=[],
+        ...                  path=os.path.join(root_path, 'to','playlist'))
+        >>> p.info['note'] = 'An example playlist'
+        >>> c = curve.Curve(os.path.join(root_path, 'to', 'curve', 'one'))
+        >>> c.info['note'] = 'The first curve'
+        >>> p.append(c)
+        >>> c = curve.Curve(os.path.join(root_path, 'to', 'curve', 'two'))
+        >>> c.info['note'] = 'The second curve'
+        >>> p.append(c)
+        >>> p.digest()
+        "\xa1\x99\x8a\x99\xed\xad\x13'\xa7w\x12\x00\x07Z\xb3\xd0zN\xa2\xe1"
+        """
+        string = self.flatten()
+        return hashlib.sha1(string).digest()
+
+    def flatten(self, absolute_paths=False):
+        """Create a string representation of the playlist.
+
+        A playlist is an XML document with the following syntax::
+
+            <?xml version="1.0" encoding="utf-8"?>
+            <playlist attribute="value">
+              <curve path="/my/file/path/"/ attribute="value" ...>
+              <curve path="...">
+            </playlist>
+
+        Relative paths are interpreted relative to the location of the
+        playlist file.
+        
+        Examples
+        --------
+
+        >>> root_path = os.path.sep + 'path'
+        >>> p = FilePlaylist(drivers=[],
+        ...                  path=os.path.join(root_path, 'to','playlist'))
+        >>> p.info['note'] = 'An example playlist'
+        >>> c = curve.Curve(os.path.join(root_path, 'to', 'curve', 'one'))
+        >>> c.info['note'] = 'The first curve'
+        >>> p.append(c)
+        >>> c = curve.Curve(os.path.join(root_path, 'to', 'curve', 'two'))
+        >>> c.info['note'] = 'The second curve'
+        >>> p.append(c)
+        >>> print p.flatten() # doctest: +NORMALIZE_WHITESPACE +REPORT_UDIFF
+        <?xml version="1.0" encoding="utf-8"?>
+        <playlist index="0" note="An example playlist" version="0.1">
+            <curve note="The first curve" path="../curve/one"/>
+            <curve note="The second curve" path="../curve/two"/>
+        </playlist>
+        <BLANKLINE>
+        >>> print p.flatten(absolute_paths=True) # doctest: +NORMALIZE_WHITESPACE +REPORT_UDIFF
+        <?xml version="1.0" encoding="utf-8"?>
+        <playlist index="0" note="An example playlist" version="0.1">
+            <curve note="The first curve" path="/path/to/curve/one"/>
+            <curve note="The second curve" path="/path/to/curve/two"/>
+        </playlist>
+        <BLANKLINE>
+        """
+        implementation = xml.dom.minidom.getDOMImplementation()
+        # create the document DOM object and the root element
+        doc = implementation.createDocument(None, 'playlist', None)
+        root = doc.documentElement
+        root.setAttribute('version', self.version) # store playlist version
+        root.setAttribute('index', str(self._index))
+        for key,value in self.info.items(): # save info variables
+            root.setAttribute(key, str(value))
+        for curve in self: # save curves and their attributes
+            curve_element = doc.createElement('curve')
+            root.appendChild(curve_element)
+            path = os.path.abspath(os.path.expanduser(curve.path))
+            if absolute_paths == False:
+                path = os.path.relpath(
+                    path,
+                    os.path.abspath(os.path.expanduser(self.path)))
+            curve_element.setAttribute('path', path)
+            for key,value in curve.info.items():
+                curve_element.setAttribute(key, str(value))
+        string = doc.toprettyxml(encoding='utf-8')
+        root.unlink() # break circular references for garbage collection
+        return string
+
+    def _from_xml_doc(self, doc, identify=True):
+        """Load a playlist from an :class:`xml.dom.minidom.Document`
+        instance.
+        """
+        root = doc.documentElement
+        for attribute,value in root.attributes.items():
+            if attribute == 'version':
+                assert value == self.version, \
+                    'Cannot read v%s playlist with a v%s reader' \
+                    % (value, self.version)
+            elif attribute == 'index':
+                self._index = int(value)
+            else:
+                self.info[attribute] = value
+        for curve_element in doc.getElementsByTagName('curve'):
+            path = curve_element.getAttribute('path')
+            info = dict(curve_element.attributes.items())
+            info.pop('path')
+            self.append_curve_by_path(path, info, identify=identify)
+        self.jump(self._index) # ensure valid index
+
+    def from_string(self, string, identify=True):
+        """Load a playlist from a string.
+
+        Examples
+        --------
+
+        >>> string = '''<?xml version="1.0" encoding="utf-8"?>
+        ... <playlist index="1" note="An example playlist" version="0.1">
+        ...     <curve note="The first curve" path="../curve/one"/>
+        ...     <curve note="The second curve" path="../curve/two"/>
+        ... </playlist>
+        ... '''
+        >>> p = FilePlaylist(drivers=[],
+        ...                  path=os.path.join('path', 'to', 'my', 'playlist'))
+        >>> p.from_string(string, identify=False)
+        >>> p._index
+        1
+        >>> p.info
+        {u'note': u'An example playlist'}
+        >>> for curve in p:
+        ...     print curve.path
+        path/to/curve/one
+        path/to/curve/two
+        """
+        doc = xml.dom.minidom.parseString(string)
+        self._from_xml_doc(doc, identify=identify)
+
+    def load(self, path=None, identify=True):
+        """Load a playlist from a file.
+        """
+        self.set_path(path)
+        doc = xml.dom.minidom.parse(self.path)
+        self._from_xml_doc(doc, identify=identify)
+        self._digest = self.digest()
+
+    def save(self, path=None):
+        """Saves the playlist in a XML file.
+        """
+        self.set_path(path)
+        f = file(self.path, 'w')
+        f.write(self.flatten())
+        f.close()
diff --git a/hooke/plugin/__init__.py b/hooke/plugin/__init__.py
new file mode 100644 (file)
index 0000000..89a23b2
--- /dev/null
@@ -0,0 +1,159 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""The `plugin` module provides optional submodules that add new Hooke
+commands.
+
+All of the science happens in here.
+"""
+
+import ConfigParser as configparser
+
+from ..config import Setting
+from ..util.pluggable import IsSubclass, construct_graph
+
+
+PLUGIN_MODULES = [
+#    ('autopeak', True),
+#    ('curvetools', True),
+    ('cut', True),
+#    ('fit', True),
+#    ('flatfilts-rolf', True),
+#    ('flatfilts', True),
+#    ('generalclamp', True),
+#    ('generaltccd', True),
+#    ('generalvclamp', True),
+#    ('jumpstat', True),
+#    ('macro', True),
+#    ('massanalysis', True),
+#    ('multidistance', True),
+#    ('multifit', True),
+#    ('pcluster', True),
+#    ('peakspot', True),
+#    ('procplots', True),
+#    ('review', True),
+#    ('showconvoluted', True),
+#    ('superimpose', True),
+#    ('tutorial', True),
+    ]
+"""List of plugin modules and whether they should be included by
+default.  TODO: autodiscovery
+"""
+
+BUILTIN_MODULES = [
+    'config',
+    'curve',
+    'debug',
+    'note',
+    'playlist',
+    'system',
+    ]
+"""List of builtin modules.  TODO: autodiscovery
+"""
+
+PLUGIN_SETTING_SECTION = 'plugins'
+"""Name of the config section which controls plugin selection.
+"""
+
+
+# Plugins and settings
+
+class Plugin (object):
+    """A pluggable collection of Hooke commands.
+
+    Fulfills the same role for Hooke that a software package does for
+    an operating system.
+    """
+    def __init__(self, name):
+        self.name = name
+        self.setting_section = '%s plugin' % self.name
+        self.config = {}
+
+    def dependencies(self):
+        """Return a list of :class:`Plugin`\s we require."""
+        return []
+
+    def default_settings(self):
+        """Return a list of :class:`hooke.config.Setting`\s for any
+        configurable plugin settings.
+
+        The suggested section setting is::
+
+            Setting(section=self.setting_section, help=self.__doc__)
+        """
+        return []
+
+    def commands(self):
+        """Return a list of :class:`hooke.command.Command`\s provided.
+        """
+        return []
+
+class Builtin (Plugin):
+    """A required collection of Hooke commands.
+
+    These "core" plugins provide essential administrative commands
+    (playlist handling, etc.).
+    """
+    pass
+
+# Construct plugin dependency graph and load plugin instances.
+
+PLUGIN_GRAPH = construct_graph(
+    this_modname=__name__,
+    submodnames=[name for name,include in PLUGIN_MODULES] + BUILTIN_MODULES,
+    class_selector=IsSubclass(Plugin, blacklist=[Plugin, Builtin]))
+"""Topologically sorted list of all possible :class:`Plugin`\s and
+:class:`Builtin`\s.
+"""
+
+def default_settings():
+    settings = [Setting(PLUGIN_SETTING_SECTION,
+                        help='Enable/disable default plugins.')]
+    for pnode in PLUGIN_GRAPH:
+        if pnode.data.name in BUILTIN_MODULES:
+            continue # builtin inclusion is not optional
+        plugin = pnode.data
+        default_include = [di for mod_name,di in PLUGIN_MODULES
+                           if mod_name == plugin.name][0]
+        help = 'Commands: ' + ', '.join([c.name for c in plugin.commands()])
+        settings.append(Setting(
+                section=PLUGIN_SETTING_SECTION,
+                option=plugin.name,
+                value=str(default_include),
+                help=help,
+                ))
+    for pnode in PLUGIN_GRAPH:
+        plugin = pnode.data
+        settings.extend(plugin.default_settings())
+    return settings
+
+def load_graph(graph, config, include_section):
+    items = []
+    for node in graph:
+        item = node.data
+        try:
+            include = config.getboolean(include_section, item.name)
+        except configparser.NoOptionError:
+            include = True # non-optional include (e.g. a Builtin)
+        if include == True:
+            try:
+                item.config = dict(config.items(item.setting_section))
+            except configparser.NoSectionError:
+                pass
+            items.append(item)
+    return items
diff --git a/hooke/plugin/autopeak.py b/hooke/plugin/autopeak.py
new file mode 100644 (file)
index 0000000..6294e5f
--- /dev/null
@@ -0,0 +1,359 @@
+# Copyright (C) 2008-2010 Alberto Gomez-Casado
+#                         Fabrizio Benedetti
+#                         Marco Brucale
+#                         Massimo Sandal <devicerandom@gmail.com>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""The autopeak module provides :class:`Autopeak`, a
+:class:`hooke.plugin.Plugin` for automatically extracting force peaks
+(unfolding events) from force curves.
+"""
+
+from hooke.libhooke import WX_GOOD
+
+import wxversion
+wxversion.select(WX_GOOD)
+from wx import PostEvent
+import numpy as np
+import scipy as sp
+import copy
+import os.path
+import time
+
+import warnings
+warnings.simplefilter('ignore',np.RankWarning)
+
+#from .. import ui.gui.results as results
+
+
+class autopeakCommands(object):
+    '''
+    TODO: autopeak docstring.
+    '''
+    def do_autopeak(self, args):
+        '''
+        AUTOPEAK
+        (autopeak.py)
+        Automatically performs a number of analyses on the peaks of the given curve.
+        Currently it automatically:
+        - fits peaks with WLC or FJC function (depending on how the fit_function variable is set)
+        - measures peak maximum forces with a baseline
+        - measures slope in proximity of peak maximum
+        Requires flatten plotmanipulator , fit.py plugin , flatfilts.py plugin with convfilt
+
+        Syntax:
+        autopeak [rebase] [pl=value] [t=value] [noauto] [reclick]
+
+        rebase : Re-asks baseline interval
+
+        pl=[value] : Use a fixed persistent length for the fit. If pl is not given,
+                     the fit will be a 2-variable
+                     fit. DO NOT put spaces between 'pl', '=' and the value.
+                     The value must be in meters.
+                     Scientific notation like 0.35e-9 is fine.
+
+        t=[value] : Use a user-defined temperature. The value must be in
+                    kelvins; by default it is 293 K.
+                    DO NOT put spaces between 't', '=' and the value.
+
+        noauto : allows for clicking the contact point by
+                 hand (otherwise it is automatically estimated) the first time.
+                 If subsequent measurements are made, the same contact point
+                 clicked the first time is used
+
+        reclick : redefines by hand the contact point, if noauto has been used before
+                  but the user is unsatisfied of the previously choosen contact point.
+
+        usepoints : fit interval by number of point