Merged Rolf Schmidt's illysam branch
authorW. Trevor King <wking@drexel.edu>
Fri, 18 Jun 2010 11:53:55 +0000 (07:53 -0400)
committerW. Trevor King <wking@drexel.edu>
Fri, 18 Jun 2010 11:53:55 +0000 (07:53 -0400)
49 files changed:
conf/autopeak.ini
conf/core.ini [new file with mode: 0644]
conf/export.ini [new file with mode: 0644]
conf/fit.ini
conf/flatfilts.ini [new file with mode: 0644]
conf/generalvclamp.ini
conf/hooke configspec.ini
conf/hooke.ini
conf/multidistance.ini [new file with mode: 0644]
conf/playlist.ini [new file with mode: 0644]
conf/plot.ini [new file with mode: 0644]
conf/procplots.ini
conf/results.ini [new file with mode: 0644]
doc/gui.txt
doc/img/gui_perspective.jpg [new file with mode: 0644]
doc/img/gui_screenshot.jpg [new file with mode: 0644]
doc/img/microscope.ico [new file with mode: 0644]
hooke/driver/csvdriver.py
hooke/driver/mcs.py
hooke/driver/mfp1d.py [new file with mode: 0644]
hooke/plugin/autopeak.py
hooke/plugin/export.py [new file with mode: 0644]
hooke/plugin/flatfilts.py [new file with mode: 0644]
hooke/plugin/generalvclamp.py [new file with mode: 0644]
hooke/plugin/plot.py [new file with mode: 0644]
hooke/plugin/procplots.py [new file with mode: 0644]
hooke/plugin/results.py [new file with mode: 0644]
hooke/ui/gui/__init__.py [new file with mode: 0644]
hooke/ui/gui/clickedpoint.py [new file with mode: 0644]
hooke/ui/gui/curve.py [new file with mode: 0644]
hooke/ui/gui/delta.py [new file with mode: 0644]
hooke/ui/gui/driver.py
hooke/ui/gui/file.py [new file with mode: 0644]
hooke/ui/gui/libhooke.py [new file with mode: 0644]
hooke/ui/gui/panels/__init__.py [new file with mode: 0644]
hooke/ui/gui/panels/commands.py [new file with mode: 0644]
hooke/ui/gui/panels/note.py [new file with mode: 0644]
hooke/ui/gui/panels/perspectives.py [new file with mode: 0644]
hooke/ui/gui/panels/playlist.py [new file with mode: 0644]
hooke/ui/gui/panels/plot.py [new file with mode: 0644]
hooke/ui/gui/panels/propertyeditor.py [new file with mode: 0644]
hooke/ui/gui/panels/results.py [new file with mode: 0644]
hooke/ui/gui/peakspot.py [new file with mode: 0644]
hooke/ui/gui/playlist.py [new file with mode: 0644]
hooke/ui/gui/plot.py
hooke/ui/gui/plotmanipulator.py [new file with mode: 0644]
hooke/ui/gui/plugin.py [new file with mode: 0644]
hooke/ui/gui/prettyformat.py
hooke/ui/gui/results.py

index 7fd1d877fc3fb0ea091d6aaf191f85a77c239e4b..ffc42f1023c3de7514cf7f1afc780e4a9bd0f482 100644 (file)
         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
+        default = automatic
+        elements = contact point, automatic, 1 point, 2 points
+        type = enum
+        value = automatic
+    
+    [[color]]
+        default = black
+        type = color
+        value = "(0,0,0)"
+    
+    [[delta_force]]
+        default = 10
         minimum = 0
         type = integer
-        value = 0
-
+        value = 10
+    
+    [[fit_function]]
+        default = wlc
+        elements = wlc, fjc, fjcPEG
+        type = enum
+        value = wlc
+    
     [[noauto]]
         default = False
         type = boolean
         value = False
-
+    
     [[noflatten]]
         default = False
         type = boolean
         value = False
-
-    [[pl]]
-        default = 5
+    
+    [[persistence_length]]
+        default = 0.35
         minimum = 0
         type = float
-        value = 5
-
+        value = 0.35
+    
+    [[plot_linewidth]]
+        default = 1
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 1
+    
+    [[plot_size]]
+        default = 20
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 20
+    
+    [[plot_style]]
+        default = scatter
+        elements = plot, scatter
+        type = enum
+        value = scatter
+    
     [[rebase]]
-        default = True
+        default = False
         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
+    
+    [[whatset]]
+        default = retraction
+        elements = extension, retraction
+        type = enum
+        value = retraction
diff --git a/conf/core.ini b/conf/core.ini
new file mode 100644 (file)
index 0000000..b5af917
--- /dev/null
@@ -0,0 +1,119 @@
+[plotmanipulators]
+    [[order]]
+        default = '"correct", "median", "absvalue", "flatten", "multiplier", "clamp", "threshold", "coincident", "showconvoluted", "centerzero"'
+        elements = correct, median, absvalue, flatten, multiplier, clamp, threshold, coincident, showconvoluted, centerzero
+        type = arraystring
+        value = '"correct" "median" "absvalue" "flatten" "multiplier" "clamp" "threshold" "coincident" "showconvoluted" "centerzero"'
+    
+    [[absvalue]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[centerzero]]
+        default = False
+        type = boolean
+        value = True
+    
+    [[clamp]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[coincident]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[correct]]
+        default = False
+        type = boolean
+        value = True
+    
+    [[flatten]]
+        default = False
+        type = boolean
+        value = True
+    
+    [[median]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[multiplier]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[showconvoluted]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[threshold]]
+        default = False
+        type = boolean
+        value = False
+
+[preferences]
+    [[hide_curve_extension]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[legend]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[playlist]]
+        default = playlists\test.hkp
+        type = filename
+        value = playlists\test.hkp
+    
+    [[use_zero]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[workdir]]
+        default = ""
+        type = folder
+        value = 
+    
+    [[x_decimals]]
+        default = 2
+        maximum = 10
+        minimum = 0
+        type = integer
+        value = 2
+    
+    [[x_prefix]]
+        default = n
+        elements = n, p
+        type = enum
+        value = n
+    
+    [[y_decimals]]
+        default = 2
+        maximum = 10
+        minimum = 0
+        type = integer
+        value = 2
+    
+    [[y_prefix]]
+        default = n
+        elements = n, p
+        type = enum
+        value = n
+
+[copylog]
+    [[destination]]
+        default = ""
+        type = folder
+        value = 
+    
+    [[use_LVDT_folder]]
+        default = False
+        type = boolean
+        value = False
diff --git a/conf/export.ini b/conf/export.ini
new file mode 100644 (file)
index 0000000..de65fd5
--- /dev/null
@@ -0,0 +1,84 @@
+[fits]
+    [[ext]]
+        default = txt
+        type = string
+        value = txt
+    
+    [[folder]]
+        default = ""
+        type = folder
+        value = 
+    
+    [[prefix]]
+        default = ""
+        type = string
+        value = 
+    
+    [[separator]]
+        default = ","
+        type = string
+        value = ","
+
+[force_curve]
+    [[ext]]
+        default = txt
+        type = string
+        value = txt
+    
+    [[folder]]
+        default = ""
+        type = folder
+        value = 
+    
+    [[prefix]]
+        default = ""
+        type = string
+        value = 
+    
+    [[separator]]
+        default = ","
+        type = string
+        value = ","
+
+[overlay]
+    [[prefix]]
+        default = overlay_
+        type = string
+        value = overlay_
+
+[notes]
+    [[filename]]
+        default = notes
+        type = string
+        value = notes
+    
+    [[folder]]
+        default = ""
+        type = folder
+        value = 
+    
+    [[prefix]]
+        default = notes_
+        type = string
+        value = notes_
+    
+    [[use_playlist_filename]]
+        default = False
+        type = boolean
+        value = False
+
+[results]
+    [[append]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[filename]]
+        default = results.txt
+        type = filename
+        value = results.txt
+    
+    [[separator]]
+        default = ","
+        type = string
+        value = ","
index 938f895e732ea9cf480e7193beebd1369df3b450..ecc0b99e52fff1b169898a2a81dfe44b0696b6ae 100644 (file)
@@ -1,3 +1,38 @@
-[fit]
-flatten = 1
-temperature = 301
+[fjc]
+    [[temperature]]
+        default = 293
+        minimum = 0
+        type = float
+        value = 293
+
+[fjcPEG]
+    [[delta_G]]
+        default = 3
+        minimum = 0
+        type = float
+        value = 3
+
+    [[L_helical]]
+        default = 0.28
+        minimum = 0
+        type = float
+        value = 0.28
+
+    [[L_planar]]
+        default = 0.358
+        minimum = 0
+        type = float
+        value = 0.358
+
+    [[temperature]]
+        default = 293
+        minimum = 0
+        type = float
+        value = 293
+
+[wlc]
+    [[temperature]]
+        default = 293
+        minimum = 0
+        type = float
+        value = 293
diff --git a/conf/flatfilts.ini b/conf/flatfilts.ini
new file mode 100644 (file)
index 0000000..7780ae9
--- /dev/null
@@ -0,0 +1,93 @@
+[convfilt]
+    [[apply_plotmanipulators]]
+        default = all
+        elements = all, flatten, none
+        type = enum
+        value = all
+    
+    [[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]"
+    
+    [[maxcut]]
+        default = 0.2
+        maximum = 1
+        minimum = 0
+        type = float
+        value = 0.2
+    
+    [[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 = 5
+    
+    [[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]
+    [[min_npks]]
+        default = 4
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 4
+    
+    [[min_deviation]]
+        default = 9
+        maximum = 100
+        minimum = 1
+        type = float
+        value = 9
+
+[peaks]
+    [[color]]
+        default = black
+        type = color
+        value = "(0,0,0)"
+    
+    [[size]]
+        default = 20
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 20
index b129db2d66a5aa3a02f94a0ded380776b2a3f264..efe43efa291f76c6ec773eba3ac566d54a270e1a 100644 (file)
+[distance]
+    [[color]]
+        default = black
+        type = color
+        value = "(0,0,0)"
+    
+    [[decimals]]
+        default = 2
+        maximum = 10
+        minimum = 0
+        type = integer
+        value = 2
+    
+    [[prefix]]
+        default = n
+        elements = n, p
+        type = enum
+        value = n
+    
+    [[show]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[show_in_legend]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[size]]
+        default = 20
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 20
+    
+    [[whatset]]
+        default = retraction
+        elements = extension, retraction
+        type = enum
+        value = retraction
+
+[force]
+    [[color]]
+        default = black
+        type = color
+        value = "(0,0,0)"
+    
+    [[decimals]]
+        default = 2
+        maximum = 10
+        minimum = 0
+        type = integer
+        value = 2
+    
+    [[prefix]]
+        default = p
+        elements = n, p
+        type = enum
+        value = p
+    
+    [[show]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[show_in_legend]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[size]]
+        default = 20
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 20
+    
+    [[whatset]]
+        default = retraction
+        elements = extension, retraction
+        type = enum
+        value = retraction
+
+[forcebase]
+    [[baseline_color]]
+        default = black
+        type = color
+        value = "(0,0,0)"
+    
+    [[baseline_show]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[baseline_show_in_legend]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[baseline_size]]
+        default = 20
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 20
+    
+    [[decimals]]
+        default = 2
+        maximum = 10
+        minimum = 0
+        type = integer
+        value = 2
+    
+    [[max]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[maximum_color]]
+        default = black
+        type = color
+        value = "(0,0,0)"
+    
+    [[maximum_show]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[maximum_show_in_legend]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[maximum_size]]
+        default = 20
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 20
+    
+    [[maximumrange_color]]
+        default = black
+        type = color
+        value = "(0,0,0)"
+    
+    [[maximumrange_show]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[maximumrange_show_in_legend]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[maximumrange_size]]
+        default = 20
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 20
+    
+    [[prefix]]
+        default = p
+        elements = n, p
+        type = enum
+        value = p
+    
+    [[rebase]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[whatset]]
+        default = retraction
+        elements = extension, retraction
+        type = enum
+        value = retraction
+
 [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
+
+[slope]
+    [[decimals]]
+        default = 5
+        maximum = 10
+        minimum = 0
+        type = integer
+        value = 5
+    
+    [[fitspan]]
+        default = 0
+        maximum = 10000
+        minimum = 0
+        type = integer
+        value = 0
+    
+    [[point_color]]
+        default = black
+        type = color
+        value = "(0,0,0)"
+    
+    [[point_show]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[point_show_in_legend]]
+        default = True
+        type = boolean
+        value = False
+    
+    [[point_size]]
+        default = 20
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 20
+    
+    [[slope_color]]
+        default = black
+        type = color
+        value = "(0,0,0)"
+    
+    [[slope_linewidth]]
+        default = 1
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 1
+    
+    [[slope_show]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[slope_show_in_legend]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[whatset]]
+        default = retraction
+        elements = extension, retraction
+        type = enum
+        value = retraction
index f9d231c4662ebcb5d3137bff7a87e515559e468b..c5691de253b8e6789d6d08dcff03fbcfb7283f78 100644 (file)
@@ -1,64 +1,40 @@
-[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
+[drivers]\r
+csvdriver = boolean(default = False)\r
+hemingclamp = boolean(default = False)\r
+jpk = boolean(default = False)\r
+mcs = boolean(default = False)\r
+mfp1d = boolean(default = True)\r
+mfp1dexport = boolean(default = True)\r
+mfp3d = boolean(default = True)\r
+picoforce = boolean(default = True)\r
+picoforcealt = boolean(default = False)\r
+tutorialdriver = boolean(default = False)\r
+\r
+[folders]\r
+filterindex = integer(default = 0)\r
+filters = string(default = 'Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')\r
+\r
+[main]\r
+height = integer(default = 500)\r
+left = integer(default = 50)\r
+top = integer(default = 50)\r
+width = integer(default = 700)\r
+\r
+[perspectives]\r
+active = string(default = Default)\r
+\r
+[plugins]\r
+autopeak = boolean(default = True)\r
+export = boolean(default = True)\r
+fit = boolean(default = True)\r
+flatfilts = boolean(default = True)\r
+generalvclamp = boolean(default = True)\r
+playlist = boolean(default = True)\r
+plot = boolean(default = True)\r
+procplots = boolean(default = True)\r
+results = boolean(default = True)\r
+\r
+[splashscreen]\r
+#duration in milliseconds\r
+duration = integer(default = 1000)\r
+show = boolean(default = True)\r
index fb03cb173d098d59a23b593d6a1c664e47706613..fe6a3f171310bdcff9e3585ed1281fcdaa1850b7 100644 (file)
-#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]
+#prefix with '#' to add a comment\r
+\r
+[command]\r
+command = autopeak\r
+plugin = autopeak\r
+\r
+#this section defines which drivers have to be loaded by Hooke\r
+[drivers]\r
+csvdriver = False\r
+hemingclamp = False\r
+jpk = False\r
+mcs = False\r
+mfp1d = True\r
+mfp3d = True\r
+mfp1dexport = True\r
+picoforce = True\r
+picoforcealt = False\r
+tutorialdriver = False\r
+\r
+[folders]\r
+filterindex = 0\r
+filters = Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')\r
+\r
+#this section defines the window size and position\r
+[main]\r
+height = 500\r
+left = 20\r
+top = 20\r
+width = 600\r
+\r
+[perspectives]\r
+active = Default\r
+default = Default\r
+\r
+#this section defines which plugins have to be loaded by Hooke\r
+[plugins]\r
+autopeak = True\r
+export = True\r
+fit = True\r
+flatfilts = True\r
+generalvclamp = True\r
+multidistance = True\r
+playlist = True\r
+plot = True\r
+procplots = True\r
+results = True\r
+\r
+[splashscreen]\r
+#duration in milliseconds\r
+duration = 1000\r
+show = True\r
diff --git a/conf/multidistance.ini b/conf/multidistance.ini
new file mode 100644 (file)
index 0000000..5c595a0
--- /dev/null
@@ -0,0 +1,82 @@
+[multidistance]
+    [[apply_plotmanipulators]]
+        default = all
+        elements = all, flatten, none
+        type = enum
+        value = all
+    
+    [[blindwindow]]
+        default = 20
+        maximum = 10000
+        minimum = 0
+        type = float
+        value = 20
+    
+    [[color]]
+        default = black
+        type = color
+        value = "(0,0,0)"
+    
+    [[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]"
+    
+    [[maxcut]]
+        default = 0.2
+        maximum = 1
+        minimum = 0
+        type = float
+        value = 0.2
+    
+    [[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 = 5
+    
+    [[positive]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[seedouble]]
+        default = 10
+        maximum = 1000
+        minimum = 0
+        type = integer
+        value = 10
+    
+    [[size]]
+        default = 20
+        maximum = 10000
+        minimum = 1
+        type = integer
+        value = 20
+    
+    [[stable]]
+        default = 0.005
+        maximum = 1
+        minimum = 0
+        type = float
+        value = 0.005
+    
+    [[use_convfilt]]
+        default = False
+        type = boolean
+        value = False
diff --git a/conf/playlist.ini b/conf/playlist.ini
new file mode 100644 (file)
index 0000000..c214918
--- /dev/null
@@ -0,0 +1,27 @@
+[genlist]
+    [[detect_mfp1d]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[folder]]
+        default = ""
+        type = folder
+        value = 
+    
+    [[filemask]]
+        default = *.*
+        type = string
+        value = *.*
+
+[loadlist]
+    [[filename]]
+        default = test.hkp
+        type = filename
+        value = test.hkp
+
+[savelist]
+    [[filename]]
+        default = untitled.hkp
+        type = filename
+        value = untitled.hkp
diff --git a/conf/plot.ini b/conf/plot.ini
new file mode 100644 (file)
index 0000000..267255d
--- /dev/null
@@ -0,0 +1,32 @@
+[preferences]
+    [[legend]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[x_decimals]]
+        default = 2
+        maximum = 10
+        minimum = 0
+        type = integer
+        value = 2
+    
+    [[x_prefix]]
+        default = n
+        elements = n, p
+        type = enum
+        value = n
+    
+    [[y_decimals]]
+        default = 2
+        maximum = 10
+        minimum = 0
+        type = integer
+        value = 2
+    
+    [[y_prefix]]
+        default = n
+        elements = n, p
+        type = enum
+        value = n
+    
index 02a72ab13d64a37aa021da2ab7602d2b36a6ddfa..eb9f3f245004d607328191992d382e2f7cb73dca 100644 (file)
@@ -1,12 +1,95 @@
+[convplot]
+    [[column]]
+        default = 1
+        maximum = 20
+        minimum = 1
+        type = integer
+        value = 1
+    
+    [[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]"
+    
+    [[row]]
+        default = 2
+        maximum = 20
+        minimum = 1
+        type = integer
+        value = 2
+    
+    [[whatset]]
+        default = retraction
+        elements = extension, retraction, both
+        type = enum
+        value = retraction
+
+[derivplot]
+    [[column]]
+        default = 1
+        maximum = 20
+        minimum = 1
+        type = integer
+        value = 1
+    
+    [[row]]
+        default = 2
+        maximum = 20
+        minimum = 1
+        type = integer
+        value = 2
+    
+    [[select]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[whatset]]
+        default = retraction
+        elements = extension, retraction, both
+        type = enum
+        value = retraction
+
+[fft]
+    [[column]]
+        default = 1
+        maximum = 20
+        minimum = 1
+        type = integer
+        value = 1
+    
+    [[row]]
+        default = 2
+        maximum = 20
+        minimum = 1
+        type = integer
+        value = 2
+    
+    [[select]]
+        default = False
+        type = boolean
+        value = False
+    
+    [[whatset]]
+        default = retraction
+        elements = extension, retraction, both
+        type = enum
+        value = retraction
+
 [procplots]
+    [[centerzero]]
+        default = True
+        type = boolean
+        value = True
+    
+    [[correct]]
+        default = True
+        type = boolean
+        value = True
+    
     [[median]]
         default = 0
         maximum = 100
         minimum = 0
         type = integer
         value = 0
-
-    [[correct]]
-        default = True
-        type = boolean
-        value = True
diff --git a/conf/results.ini b/conf/results.ini
new file mode 100644 (file)
index 0000000..0d7de52
--- /dev/null
@@ -0,0 +1,6 @@
+[show_results]
+    [[result_type]]
+        default = wlc
+        elements = wlc, fjc, fjcPEG, multidistance
+        type = enum
+        value = wlc
index 25c0f48d8883f198c9a8dd01f10e27218285155d..697076fccfa537f930a83ca3b85ab48aa7a3f24a 100644 (file)
@@ -4,29 +4,161 @@ 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)
+Enable by selecting `gui` in your :file:`hooke.cfg`.::
 
-You can save perspectives (delete perspectives however does not work yet).
+    [user interfaces]
+    command line = False
+    gui = True
 
-The 'Navigation' menubar works, the 'Main' menubar does not work yet.
+Interface
+=========
 
-Closing plot tabs does not work properly, feedback on specific
-examples and error messages would be much appreciated
+Starting Hooke's GUI for the first time, you will see the central plot
+area with the current plot surrounded by the following windows (the `F*`
+key toggles the visibility of the panel):
 
-In the 'Folders' pane you can double-click playlists (with hkp
-extension) to open them (you cannot double-click curves to open them)
+  # Folders (`F5`)
+  # Playlists (`F6`)
+  # Commands (or Settings and commands) (`F7`)
+  # Properties (`F8`)
+  # Assistant (`F9`)
+  # Results (`F10`)
+  # Output (`F11`)
+  # Note (`F12`)
 
-Commands in the 'Commands'-pane that work
+.. img:: img/gui_screenshot.jpg
 
-* autopeak (only partially, everything that has to do with clicking on the plot does not work)
-* genlist
-* loadlist
-* convfilt
-* flatfilt
+Initially, the window will be rather small in order to work with small
+screen resolutions. Adjust the size and position to your liking.
 
-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.
+Above the windows you see the navigation toolbar to switch from one
+curve to another (next/previous).
+
+Plot area
+=========
+
+The plot area can be customised by setting `preferences` in the core plugin.
+
+* hide_curve_extension: hides the curve extension in the title of the
+  plot (and from the playlist in the playlists panel)
+* legend: show or hide the legend
+* use_zero: display `0` instead of e.g. `0.00` on the axes
+* decimals (x_decimals, y_decimals): set the decimal places for the x
+  and y axes
+* prefixes(x_prefix, y_prefix): set the prefix for the x and y axes
+
+These are global settings. Individual plots can be customised with the
+same options (except hide_curve_extension) by setting `preferences` in
+the plot plugin.
+
+Folders
+=======
+Here you can navigate your file system and double click on a saved
+playlist to open it. You can change the initial folder by modifying
+`workdir` in the `preferences` (core plugin).
+
+Playlists
+=========
+You can manage several playlists in this window. As the GUI is rather
+flexible, it is possible to display the curves from different
+playlists side by side to compare them (relatively handy when
+comparing different fit parameters). You can double-click a file in
+the playlist to display it in the plot area. Deleting entire playlists
+or single files can be accomplished by right-clicking and selecting
+`Delete`.
+
+Commands (or Settings and commands)
+===================================
+
+All available commands are listed under their corresponding plugin. In
+order to see a plugin and its commands, you have to edit
+:file:`hooke.conf` and enable th plugin in the plugins section.
+Selecting a plugin or command will display the associated help in the
+`Assistant`_ window. You can edit the properties of the selected
+command in the `Properties`_ window and click `Execute` to run the
+selected command. If you do not need to modify any properties, you can
+also double-click a command to run it.
+
+Properties
+==========
+The properties for the command selected in the `Commands`_ window are
+displayed here. Edit the properties to your satisfaction (some need to
+be confirmed by hitting enter, this seems to be a problem in
+wxPropertyGrid) and click the `Execute` button to run the selected
+command. Floating point values are limited to a certain number of
+decimals (limitation of wxPropertyGrid?) so be careful when using
+floating point values.
+
+Assistant
+=========
+Selecting a plugin or command in the `Commands`_ window will display the
+associated help here. The help for the plugin should give a general
+description of the plugin. The help for a command should describe the
+properties available for this command and suggest reasonable default
+values if possible. Feel free to point out missing help content.
+
+Results
+=======
+The results from the `autopeak` or `multidistance` commands are
+displayed here. Initially, all results are checked (i.e. visible). If
+you want to hide a result, simply uncheck it. Hidden curves will not
+be exported either. You can only display one type of fit result (WLC,
+FJC, etc.) at a time and you can switch between result types (results
+plugin - show_results).
+
+Output
+======
+The Output window serves as a log where pertinent information is
+displayed. If something does not work the way you expect it, have a
+look here to see if there is more information available.
+
+Note
+====
+A note can be added to every curve: enter your note and click
+`Update note`. With the copylog command (core plugin) you can copy all
+the files with a note to a different folder.
+
+General remarks
+===============
+Ignore the text on the `Welcome` tab. This tab is more like a proof of
+principle and will contain a short how-to in the future (once the
+howto is written).
+
+Hooke's GUI will remember the size and position of the main window
+(stored in ~/.hooke-gui.cfg).  You can arrange the panels any which
+way you like and save this arrangement as a perspective.
+
+.. img:: img/gui_perspective.jpg
+
+Hooke will always start with the last used perspective and you can
+switch from one perspective to another by selecting a perspective from
+the perspectives menu. After deleting a perspective, the radio
+indicator in the perspectives menu disappears (known bug in
+wxPython). This is only a visual problem and does not affect anything
+else.
+
+In order to pan the plot, zoom in and out and export the plot of your
+force curves, use the plot toolbar under the plot. A more detailed
+description is available on the
+[http://matplotlib.sourceforge.net/users/navigation_toolbar.html
+matplotlib website].
+
+== Some plugins and commands ==
+  * replot (plot): replots the current force curve from scratch eliminating any secondary plots
+  * fjc/fjcPEG/wlc (fit): do not use any of these commands directly, they are not implemented properly yet. However, the properties you set for these commands are used for the autopeak command
+  * plotmanipulators (core): select the plotmanipulators you want to use and arrange them in the proper order
+  * test (test): use this for testing purposes. You find do_test in hooke.py
+  * clear_results (results): deletes all fitting results from the curve
+  * show_results (results): select which fitting results should be displayed on the plot
+  * overlay (export): exports all retraction curves in a playlist on the same scale. This is achieved by determining the maximum x window and adding x(max) and x(min) to all curves that are shorter than the maximum x window. Make sure to filter your playlist before running this command!
+
+== Basic analysis and autopeak ==
+Please follow the steps for basic analysis described [BasicAnalysis here]. Instead of typing in the command at the command-line, select it in the Commands window, set your properties in the Properties window and click on 'Execute'.
+
+The [Brief_Autopeak_HowTo autopeak] tutorial is also applicable. In Hooke(GUI) you need to setup the type of fit you want to use: in the Properties of the autopeak command (autopeak plugin) select wlc, fjc or fjcPEG from the dropdown list for the fit_function.
+
+If you run different fits (e.g. WLC and FJC) you can switch the display of the results with the show_results command (results plugin).
+
+== Brief plugin/Properties tutorial ==
+Have a look at the files in the _plugins_ folder. The python files contain the plotmanipulators (i.e. plotmanip_NAME), commands (i.e. do_COMMAND) and auxilliary methods. The ini files contain the information for the Properties window. You can already use a fair number of datatypes (e.g. integer, float, boolean, list, color, etc.) and more can be added. Be careful when using floats as there is a limit to the number of decimals (see above). The plotmanipulators and commands should read the properties directly from the ini file instead of having them passed to them as arguments. For the time being, accessor methods are located in hooke.py (e.g. GetBoolFromConfig()).
+A more detailed description will be made available.
diff --git a/doc/img/gui_perspective.jpg b/doc/img/gui_perspective.jpg
new file mode 100644 (file)
index 0000000..40889e5
Binary files /dev/null and b/doc/img/gui_perspective.jpg differ
diff --git a/doc/img/gui_screenshot.jpg b/doc/img/gui_screenshot.jpg
new file mode 100644 (file)
index 0000000..004f89b
Binary files /dev/null and b/doc/img/gui_screenshot.jpg differ
diff --git a/doc/img/microscope.ico b/doc/img/microscope.ico
new file mode 100644 (file)
index 0000000..6841f38
Binary files /dev/null and b/doc/img/microscope.ico differ
index 227935a7dc3ef48fd47b603923849511b9b57318..89ff5ff52022430b104476196770f5f648ad023b 100644 (file)
@@ -1,21 +1,4 @@
-# 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/>.
+# Copyright
 
 """Simple driver to read general comma-separated values in Hooke
 
@@ -26,60 +9,68 @@ 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]
+import os.path
+
+import lib.curve
+import lib.driver
+import lib.libhooke
+import lib.plot
+
+class csvdriverDriver(lib.driver.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 close_all(self):
+        self.filedata.close()
+
+    def default_plots(self):
+        rrows=csv.reader(self.data)
+        rows=list(rrows) #transform the csv.reader iterator into a normal list
+        columns=lib.libhooke.transposed2(rows[1:])
+
+        for index in range(0, len(columns), 2):
+            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]
+
+            curve = lib.curve.Curve()
+
+            curve.destination.row = index + 1
+            curve.label = 'curve ' + str(index)
+            curve.style = 'plot'
+            curve.units.x = 'x'
+            curve.units.y = 'y'
+            curve.x = temp_x
+            curve.y = temp_y
+
+            plot = lib.plot.Plot()
+            plot.title = os.path.basename(self.filename)
+            plot.curves.append(curve)
+
+        #TODO: is normalization helpful or detrimental here?
+        #plot.normalize()
+        return plot
+
+    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
index a00117ff4e59dbc3633e91fb9b591da0c2f211cc..f8208c04aea545dbfe482eeec35ff48e68feada7 100644 (file)
@@ -1,31 +1,16 @@
-# 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
+# Copyright
+
+"""Driver for mcs fluorescence files.
 """
 
-from .. import curve as lhc
-from .. import libhooke as lh
+import os.path
+
+import lib.curve
+import lib.driver
+import lib.plot
 import struct
 
-class mcsDriver(lhc.Driver):
+class mcsDriver(lib.driver.Driver):
 
     def __init__(self, filename):
         '''
@@ -53,32 +38,53 @@ class mcsDriver(lhc.Driver):
         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):
+        #TODO: rename blue and red data to something more appropriate if possible
         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']
+        extension = lib.curve.Curve()
+        retraction = lib.curve.Curve()
+
+        extension.color = 'red'
+        extension.label = 'extension'
+        extension.style = 'plot'
+        extension.title = 'Force curve'
+        #FIXME: if there's an header saying something about the time count, should be used
+        #TODO: time is not really a unit
+        extension.units.x = 'time'
+        extension.units.y = 'count'
+        extension.x = range(len(red_data))
+        extension.y = red_data
+        retraction.color = 'blue'
+        retraction.label = 'retraction'
+        retraction.style = 'plot'
+        retraction.title = 'Force curve'
+        #FIXME: if there's an header saying something about the time count, should be used
+        #TODO: time is not really a unit
+        retraction.units.x = 'time'
+        retraction.units.y = 'count'
+        retraction.x = range(len(blue_data))
+        retraction.y = blue_data
+
+        plot = lib.plot.Plot()
+        plot.title = os.path.basename(self.filename)
+        plot.curves.append(extension)
+        plot.curves.append(retraction)
+
+        plot.normalize()
+        return plot
 
-        return [main_plot]
+    def is_me(self):
+        if self.filename[-3:].lower()=='mcs':
+            return True
+        else:
+            return False
 
     def read_file(self, raw_data):
         real_data=[]
diff --git a/hooke/driver/mfp1d.py b/hooke/driver/mfp1d.py
new file mode 100644 (file)
index 0000000..7f9f2f8
--- /dev/null
@@ -0,0 +1,268 @@
+#!/usr/bin/env python
+
+'''
+mfp1d.py
+
+Driver for MFP-1D files.
+
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)
+This driver is based on the work of R. Naud and A. Seeholzer (see below)
+to read Igor binary waves. Code used with permission.
+
+This program is released under the GNU General Public License version 2.
+'''
+
+# 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).
+#
+# AUTHORS:
+# Matlab version: R. Naud August 2008 (http://lcn.epfl.ch/~naud/Home.html)
+# Python port: A. Seeholzer October 2008
+#
+# 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
+
+import lib.driver
+import lib.curve
+import lib.plot
+
+__version__='0.0.0.20100225'
+
+class mfp1dDriver(lib.driver.Driver):
+
+    def __init__(self, filename):
+        '''
+        This is a driver to import Asylum Research MFP-1D data.
+        Status: experimental
+        '''
+        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 = 'mfp1d'
+        self.experiment = 'smfs'
+
+    def _load_from_file(self, filename, extract_note=False):
+        data = None
+        f = open(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)
+            if extract_note:
+                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['RetractVelocity'])
+                self.spring_constant = float(self.note['SpringC'])
+        else:
+            assert False, "Fileversion is of type '%i', not supported" % dtype
+            data = []
+
+        f.close()
+        if len(data) > 0:
+            data_list = data.tolist()
+            count = len(data_list) / 2
+            return data_list[:count - 1], data_list[count:]
+        else:
+            return None
+
+    def _flatten(self, tup):
+        out = ''
+        for ch in tup:
+            out += ch
+        return out
+
+    def _read_columns(self):
+        extension = lib.curve.Data()
+        retraction = lib.curve.Data()
+
+        extension.y, retraction.y = self._load_from_file(self.filename, extract_note=True)
+        filename = self.filename.replace('deflection', 'LVDT', 1)
+        extension.x, retraction.x = self._load_from_file(filename, extract_note=False)
+        return [[extension.x, extension.y], [retraction.x, retraction.y]]
+
+    def close_all(self):
+        self.filedata.close()
+
+    def is_me(self):
+        if len(self.lines) < 34:
+            return False
+
+        name, extension = os.path.splitext(self.filename)
+        #the following only exist in MFP1D files, not MFP-3D
+        #PullDist, PullDistSign, FastSamplingFrequency, SlowSamplingFrequency, FastDecimationFactor
+        #SlowDecimationFactor, IsDualPull, InitRetDist, RelaxDist, SlowTrigger, RelativeTrigger,
+        #EndOfNote
+        if extension == '.ibw' and 'deflection' in name:
+            #check if the corresponding LVDT file exists
+            filename = self.filename.replace('deflection', 'LVDT', 1)
+            if os.path.isfile(filename) and 'EndOfNote' in self.lines:
+                return True
+            else:
+                return False
+        else:
+            return False
+
+    def default_plots(self):
+        '''
+        loads the curve data
+        '''
+        defl_ext, defl_ret = self.deflection()
+
+        extension = lib.curve.Curve()
+        retraction = lib.curve.Curve()
+
+        extension.color = 'red'
+        extension.label = 'extension'
+        extension.style = 'plot'
+        extension.title = 'Force curve'
+        extension.units.x = 'm'
+        extension.units.y = 'N'
+        extension.x = self.data[0][0]
+        extension.y = [i * self.spring_constant for i in defl_ext]
+        retraction.color = 'blue'
+        retraction.label = 'retraction'
+        retraction.style = 'plot'
+        retraction.title = 'Force curve'
+        retraction.units.x = 'm'
+        retraction.units.y = 'N'
+        retraction.x = self.data[1][0]
+        retraction.y = [i * self.spring_constant for i in defl_ret]
+
+        plot = lib.plot.Plot()
+        plot.title = os.path.basename(self.filename)
+        plot.curves.append(extension)
+        plot.curves.append(retraction)
+
+        plot.normalize()
+        return plot
+
+    def deflection(self):
+        if not self.data:
+            self.data = self._read_columns()
+        return self.data[0][1], self.data[1][1]
index 6294e5f5e460419e9308d0bedb2682bb176c99e3..ac76228a63b55167f18cad4a12fa1eb37a44b4fb 100644 (file)
@@ -44,7 +44,10 @@ warnings.simplefilter('ignore',np.RankWarning)
 
 class autopeakCommands(object):
     '''
-    TODO: autopeak docstring.
+    Autopeak carries out force curve fitting with a chosen model:
+        - WLC
+        - FJC
+        - FJC-PEG
     '''
     def do_autopeak(self, args):
         '''
diff --git a/hooke/plugin/export.py b/hooke/plugin/export.py
new file mode 100644 (file)
index 0000000..3623caa
--- /dev/null
@@ -0,0 +1,243 @@
+#!/usr/bin/env python
+
+'''
+export.py
+
+Export commands for Hooke.
+
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+import lib.libhooke as lh
+import wxversion
+wxversion.select(lh.WX_GOOD)
+
+import copy
+import os.path
+import time
+import wx
+
+class exportCommands(object):
+    '''
+    Export force curves, fits and results in different formats
+    '''
+
+    def _plug_init(self):
+        pass
+
+    def do_fits(self):
+        '''
+        Exports all fitting results (if available) in a columnar ASCII format.
+        '''
+        ext = self.GetStringFromConfig('export', 'fits', 'ext')
+        folder = self.GetStringFromConfig('export', 'fits', 'folder')
+        prefix = self.GetStringFromConfig('export', 'fits', 'prefix')
+        separator = self.GetStringFromConfig('export', 'fits', 'separator')
+        #TODO: add list for Tab, Space, Comma, Other
+        #add string for Other
+
+        active_file = self.GetActiveFile()
+        plot = self.GetDisplayedPlot()
+
+        #add empty columns before adding new results if necessary
+        if plot is not None:
+            for results_str, results in plot.results.items():
+                for curve in results.results:
+                    output = []
+                    header_str = ''
+                    if curve.visible:
+                        header_str += curve.label + '_x (' + curve.units.x + ')' + separator + curve.label + '_y (' + curve.units.y + ')'
+                        output.append(header_str)
+                        for index, row in enumerate(curve.x):
+                            output.append(separator.join([str(curve.x[index]), str(curve.y[index])]))
+                    if output:
+                        #TODO: add option to replace or add the new file extension
+                        #add option to rename file from default.000 to default_000
+                        filename = os.path.basename(active_file.filename)
+                        filename = ''.join([prefix, filename, '_', curve.label, '.', ext])
+                        filename = os.path.join(folder, filename)
+                        output_file = open(filename, 'w')
+                        output_file.write('\n'.join(output))
+                        output_file.close
+
+    def do_force_curve(self):
+        '''
+        TXT
+        Saves the current curve as a text file
+        Columns are, in order:
+        X1 , Y1 , X2 , Y2 , X3 , Y3 ...
+
+        -------------
+        Syntax: txt [filename] {plot to export}
+        '''
+
+        ext = self.GetStringFromConfig('export', 'force_curve', 'ext')
+        folder = self.GetStringFromConfig('export', 'force_curve', 'folder')
+        prefix = self.GetStringFromConfig('export', 'force_curve', 'prefix')
+        separator = self.GetStringFromConfig('export', 'force_curve', 'separator')
+        #TODO: add list for Tab, Space, Comma, Other
+        #add string for Other
+
+        active_file = self.GetActiveFile()
+        #create the header from the raw plot (i.e. only the force curve)
+        plot = self.GetDisplayedPlotRaw()
+
+        output = []
+        header_str = ''
+        for index, curve in enumerate(plot.curves):
+            #TODO: only add labels for original curves (i.e. excluding anything added after the fact)
+            header_str += curve.label + '_x (' + curve.units.x + ')' + separator + curve.label + '_y (' + curve.units.y + ')'
+            if index < len(plot.curves) - 1:
+                header_str += separator
+        output.append(header_str)
+
+        #export the displayed plot
+        plot = self.GetDisplayedPlot()
+        extension = plot.curves[lh.EXTENSION]
+        retraction = plot.curves[lh.RETRACTION]
+        for index, row in enumerate(extension.x):
+            output.append(separator.join([str(extension.x[index]), str(extension.y[index]), str(retraction.x[index]), str(retraction.y[index])]))
+
+        if output:
+            #TODO: add option to replace or add the new file extension
+            #add option to rename file from default.000 to default_000
+            filename = os.path.basename(active_file.filename)
+            filename = ''.join([prefix, filename, '.', ext])
+            filename = os.path.join(folder, filename)
+            output_file = open(filename, 'w')
+            output_file.write('\n'.join(output))
+            output_file.close
+
+    def do_notes(self):
+        '''
+        Exports the note for all the files in a playlist.
+        '''
+        filename = self.GetStringFromConfig('export', 'notes', 'filename')
+        folder = self.GetStringFromConfig('export', 'notes', 'folder')
+        prefix = self.GetStringFromConfig('export', 'notes', 'prefix')
+        use_playlist_filename = self.GetBoolFromConfig('export', 'notes', 'use_playlist_filename')
+        
+        playlist = self.GetActivePlaylist()
+        output_str = ''
+        for current_file in playlist.files:
+            output_str = ''.join([output_str, current_file.filename, '  |  ', current_file.note, '\n'])
+        if output_str:
+            output_str = ''.join(['Notes taken at ', time.asctime(), '\n', playlist.filename, '\n', output_str])
+            if use_playlist_filename:
+                path, filename = os.path.split(playlist.filename)
+                filename = lh.remove_extension(filename)
+            filename = ''.join([prefix, filename, '.txt'])
+            filename = os.path.join(folder, filename)
+            output_file = open(filename, 'w')
+            output_file.write(output_str)
+            output_file.close
+        else:
+            dialog = wx.MessageDialog(None, 'No notes found, file not saved.', 'Info', wx.OK)
+            dialog.ShowModal()
+
+    def do_overlay(self):
+        '''
+        Exports all retraction files in a playlist with the same scale.
+        The files can then be overlaid in a graphing program to see which
+        ones have the same shape.
+        Use this export command only on filtered lists as it takes a long time
+        to complete even with a small number of curves.
+        '''
+        playlist = self.GetActivePlaylist()
+
+        filename_prefix = self.GetStringFromConfig('export', 'overlay', 'prefix')
+
+        differences_x = []
+        differences_y = []
+        number_of_curves = playlist.count
+        message_str = ''.join([str(number_of_curves), ' files to load.\n\n'])
+        progress_dialog = wx.ProgressDialog('Loading', message_str, maximum=number_of_curves, parent=self, style=wx.PD_APP_MODAL|wx.PD_SMOOTH|wx.PD_AUTO_HIDE)
+        for index, current_file in enumerate(playlist.files):
+            current_file.identify(self.drivers)
+            plot = current_file.plot
+
+            plot.raw_curves = copy.deepcopy(plot.curves)
+            #apply all active plotmanipulators and add the 'manipulated' data
+            for plotmanipulator in self.plotmanipulators:
+                if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name):
+                    plot = plotmanipulator.method(plot, current_file)
+            #add corrected curves to plot
+            plot.corrected_curves = copy.deepcopy(plot.curves)
+
+            curve = current_file.plot.corrected_curves[lh.RETRACTION]
+            differences_x.append(curve.x[0] - curve.x[-1])
+            differences_y.append(curve.x[0] - curve.y[-1])
+            progress_dialog.Update(index, ''.join([message_str, 'Loading ', str(index + 1), '/', str(number_of_curves)]))
+        progress_dialog.Destroy()
+
+        max_x = max(differences_x)
+        max_y = max(differences_y)
+        message_str = ''.join([str(number_of_curves), ' files to export.\n\n'])
+        for index, current_file in enumerate(playlist.files):
+            curve = current_file.plot.corrected_curves[lh.RETRACTION]
+            first_x = curve.x[0]
+            first_y = curve.y[0]
+            new_x = [x - first_x for x in curve.x]
+            new_y = [y - first_y for y in curve.y]
+            new_x.append(-max_x)
+            new_y.append(-max_y)
+            output_str = ''
+            for row_index, row in enumerate(new_x):
+                output_str += ''.join([str(new_x[row_index]), ', ', str(new_y[row_index]), '\n'])
+
+            if output_str:
+                filename = ''.join([filename_prefix, current_file.name])
+                filename = current_file.filename.replace(current_file.name, filename)
+                output_file = open(filename, 'w')
+                output_file.write(output_str)
+                output_file.close
+        progress_dialog.Destroy()
+
+    def do_results(self, append=None, filename='', separator=''):
+        '''
+        Exports all visible fit results in a playlist into a delimited text file
+        append: set append to True if you want to append to an existing results file
+        filename: the filename and path of the results file
+        separator: the separator between columns
+        '''
+        if not append:
+            append = self.GetStringFromConfig('export', 'results', 'append')
+        if filename == '':
+            filename = self.GetStringFromConfig('export', 'results', 'filename')
+        if separator == '':
+            separator = self.GetStringFromConfig('export', 'results', 'separator')
+
+        playlist = self.GetActivePlaylist()
+        output_str = ''
+        header_str = ''
+        for current_file in playlist.files:
+            if len(current_file.plot.results) > 0:
+                for key in current_file.plot.results.keys():
+                    #if there are different types of fit results in the playlist, the header might have to change
+                    #here, we generate a temporary header and compare it to the current header
+                    #if they are different, the tempeorary header is used
+                    #we get the header from the fit and add the 'filename' column
+                    temporary_header_str = ''.join([current_file.plot.results[key].get_header_as_str(), separator, 'Filename'])
+                    if temporary_header_str != header_str:
+                        header_str = ''.join([current_file.plot.results[key].get_header_as_str(), separator, 'Filename'])
+                        output_str = ''.join([output_str, header_str, '\n'])
+                    for index, result in enumerate(current_file.plot.results[key].results):
+                        if result.visible:
+                            #similar to above, we get the result from the fit and add the filename
+                            line_str = current_file.plot.results[key].get_result_as_string(index)
+                            line_str = ''.join([line_str, separator, current_file.filename])
+                            output_str = ''.join([output_str, line_str, '\n'])
+        if output_str:
+            output_str = ''.join(['Analysis started ', time.asctime(), '\n', output_str])
+
+            if append and os.path.isfile(filename):
+                output_file = open(filename,'a')
+            else:
+                output_file = open(filename, 'w')
+                output_file.write(output_str)
+                output_file.close
+        else:
+            dialog = wx.MessageDialog(None, 'No results found, file not saved.', 'Info', wx.OK)
+            dialog.ShowModal()
diff --git a/hooke/plugin/flatfilts.py b/hooke/plugin/flatfilts.py
new file mode 100644 (file)
index 0000000..351dada
--- /dev/null
@@ -0,0 +1,302 @@
+#!/usr/bin/env python
+
+'''
+flatfilts.py
+
+Force spectroscopy files filtering of flat files.
+
+Plugin dependencies:
+procplots.py (plot processing plugin)
+
+Copyright 2008 Massimo Sandal, Fabrizio Benedetti
+with modifications by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+import lib.libhooke as lh
+import wxversion
+wxversion.select(lh.WX_GOOD)
+
+import copy
+from numpy import diff, mean
+
+import lib.peakspot as lps
+import lib.curve
+
+class flatfiltsCommands:
+
+    def do_flatfilt(self):
+        '''
+        FLATFILT
+        (flatfilts.py)
+        Filters out flat (featureless) files of the current playlist,
+        creating a playlist containing only the files with potential
+        features.
+        ------------
+        Syntax:
+        flatfilt [min_npks min_deviation]
+
+        min_npks = minmum number of points over the deviation
+        (default=4)
+
+        min_deviation = minimum signal/noise ratio
+        (default=9)
+
+        If called without arguments, it uses default values, that
+        should work most of the times.
+        '''
+
+        self.AppendToOutput('Processing playlist...')
+        self.AppendToOutput('(Please wait)')
+        features = []
+        playlist = self.GetActivePlaylist()
+        files = playlist.files
+        file_index = 0
+        for current_file in files:
+            file_index += 1
+            try:
+                current_file.identify(self.drivers)
+                notflat = self.has_features(copy.deepcopy(current_file))
+                feature_string = ''
+                if notflat != 1:
+                    if notflat > 0:
+                        feature_string = str(notflat) + ' features'
+                    else:
+                        feature_string = 'no features'
+                else:
+                    feature_string = '1 feature'
+                output_string = ''.join(['Curve ', current_file.name, '(', str(file_index), '/', str(len(files)), '): ', feature_string])
+            except:
+                notflat = False
+                output_string = ''.join(['Curve ', current_file.name, '(', str(file_index), '/', str(len(files)), '): cannot be filtered. Probably unable to retrieve force data from corrupt file.'])
+            self.AppendToOutput(output_string)
+            if notflat:
+                current_file.features = notflat
+                features.append(file_index - 1)
+        if not features:
+            self.AppendToOutput('Found nothing interesting. Check the playlist, could be a bug or criteria could be too stringent.')
+        else:
+            if len(features) < playlist.count:
+                self.AppendToOutput(''.join(['Found ', str(len(features)), ' potentially interesting files.']))
+                self.AppendToOutput('Regenerating playlist...')
+                playlist_filtered = playlist.filter_curves(features)
+                self.AddPlaylist(playlist_filtered, name='flatfilt')
+            else:
+                self.AppendToOutput('No files filtered. Try different filtering criteria.')
+
+    def has_features(self, current_file):
+        '''
+        decides if a curve is flat enough to be rejected from analysis: it sees if there
+        are at least min_npks points that are higher than min_deviation times the absolute value
+        of noise.
+
+        Algorithm original idea by Francesco Musiani, with my tweaks and corrections.
+        '''
+        #TODO: shoudl medianfilter be variable?
+        medianfilter = 7
+        #medianfilter = self.GetIntFromConfig('flatfilts', 'flatfilt', 'median_filter')
+        mindeviation = self.GetIntFromConfig('flatfilts', 'flatfilt', 'min_deviation')
+        minpeaks = self.GetIntFromConfig('flatfilts', 'flatfilt', 'min_npks')
+
+        retvalue = 0
+
+        #we assume the first is the plot with the force curve
+        #do the median to better resolve features from noise
+        flat_curve = self.plotmanip_median(current_file.plot, current_file, customvalue=medianfilter)
+
+        #absolute value of derivate
+        yretdiff = diff(flat_curve.curves[lh.RETRACTION].y)
+        yretdiff = [abs(value) for value in yretdiff]
+        #average of derivate values
+        diffmean = mean(yretdiff)
+        yretdiff.sort()
+        yretdiff.reverse()
+        c_pks = 0
+        for value in yretdiff:
+            if value / diffmean > mindeviation:
+                c_pks += 1
+            else:
+                break
+
+        if c_pks >= minpeaks:
+            retvalue = c_pks
+
+        return retvalue
+
+    ################################################################
+    #-----CONVFILT-------------------------------------------------
+    #-----Convolution-based peak recognition and filtering.
+    #Requires the peakspot.py library
+
+    def has_peaks(self, plot=None, plugin=None):
+        '''
+        Finds peak position in a force curve.
+        '''
+
+        if plugin is None:
+            blindwindow = self.GetFloatFromConfig('flatfilts', 'convfilt', 'blindwindow')
+            #need to convert the string that contains the list into a list
+            convolution = eval(self.GetStringFromConfig('flatfilts', 'convfilt', 'convolution'))
+            maxcut = self.GetFloatFromConfig('flatfilts', 'convfilt', 'maxcut')
+            mindeviation = self.GetFloatFromConfig('flatfilts', 'convfilt', 'mindeviation')
+            positive = self.GetBoolFromConfig('flatfilts', 'convfilt', 'positive')
+            seedouble = self.GetIntFromConfig('flatfilts', 'convfilt', 'seedouble')
+            stable = self.GetFloatFromConfig('flatfilts', 'convfilt', 'stable')
+        else:
+            blindwindow = self.GetFloatFromConfig(plugin.name, plugin.section, plugin.prefix + 'blindwindow')
+            #need to convert the string that contains the list into a list
+            convolution = eval(self.GetStringFromConfig(plugin.name, plugin.section, plugin.prefix + 'convolution'))
+            maxcut = self.GetFloatFromConfig(plugin.name, plugin.section, plugin.prefix + 'maxcut')
+            mindeviation = self.GetFloatFromConfig(plugin.name, plugin.section, plugin.prefix + 'mindeviation')
+            positive = self.GetBoolFromConfig(plugin.name, plugin.section, plugin.prefix + 'positive')
+            seedouble = self.GetIntFromConfig(plugin.name, plugin.section, plugin.prefix + 'seedouble')
+            stable = self.GetFloatFromConfig(plugin.name, plugin.section, plugin.prefix + 'stable')
+
+        if plot is None:
+            plot = self.GetDisplayedPlotCorrected()
+
+        retraction = plot.curves[lh.RETRACTION]
+        #Calculate convolution
+        convoluted = lps.conv_dx(retraction.y, convolution)
+
+        #surely cut everything before the contact point
+        cut_index = self.find_contact_point(plot)
+        #cut even more, before the blind window
+        start_x = retraction.x[cut_index]
+        blind_index = 0
+        for value in retraction.x[cut_index:]:
+            if abs((value) - (start_x)) > blindwindow * (10 ** -9):
+                break
+            blind_index += 1
+        cut_index += blind_index
+        #do the dirty convolution-peak finding stuff
+        noise_level = lps.noise_absdev(convoluted[cut_index:], positive, maxcut, stable)
+        above = lps.abovenoise(convoluted, noise_level, cut_index, mindeviation)
+        peak_location, peak_size = lps.find_peaks(above, seedouble=seedouble)
+        #take the maximum
+        for i in range(len(peak_location)):
+            peak = peak_location[i]
+            maxpk = min(retraction.y[peak - 10:peak + 10])
+            index_maxpk = retraction.y[peak - 10:peak + 10].index(maxpk) + (peak - 10)
+            peak_location[i] = index_maxpk
+
+        return peak_location, peak_size
+
+    def do_peaks(self, plugin=None, peak_location=None, peak_size=None):
+        '''
+        Test command for convolution filter.
+        ----
+        Syntax: peaks [deviations]
+        absolute deviation = number of times the convolution signal is above the noise absolute deviation.
+        Default is 5.
+        '''
+
+        if plugin is None:
+            color = self.GetColorFromConfig('flatfilts', 'peaks', 'color')
+            size = self.GetIntFromConfig('flatfilts', 'peaks', 'size')
+        else:
+            color = self.GetColorFromConfig(plugin.name, plugin.section, plugin.prefix + 'color')
+            size = self.GetIntFromConfig(plugin.name, plugin.section, plugin.prefix + 'size')
+
+        plot = self.GetDisplayedPlotCorrected()
+
+        if peak_location is None and peak_size is None:
+            if not self.AppliesPlotmanipulator('flatten'):
+                self.AppendToOutput('The flatten plot manipulator is not loaded. Enabling it could give better results.')
+
+            peak_location, peak_size = self.has_peaks(plot)
+            if len(peak_location) != 1:
+                peak_str = ' peaks.'
+            else:
+                peak_str = ' peak.'
+            self.AppendToOutput('Found ' + str(len(peak_location)) + peak_str)
+
+        if peak_location:
+            retraction = plot.curves[lh.RETRACTION]
+
+            peaks = lib.curve.Curve()
+            peaks.color = color
+            peaks.size = size
+            peaks.style = 'scatter'
+            peaks.title = 'Peaks'
+            peaks.x = [retraction.x[index] for index in peak_location]
+            peaks.y = [retraction.y[index] for index in peak_location]
+
+            plot.curves.append(peaks)
+
+            self.UpdatePlot(plot)
+
+    def do_convfilt(self):
+        '''
+        Filters out flat (featureless) files of the current playlist,
+        creating a playlist containing only the files with potential
+        features.
+        ------------
+        min_npks: minmum number of peaks
+        min_deviation: minimum signal/noise ratio *in the convolution*
+        '''
+
+        self.AppendToOutput('Processing playlist...')
+        self.AppendToOutput('(Please wait)')
+        apply_plotmanipulators = self.GetStringFromConfig('flatfilts', 'convfilt', 'apply_plotmanipulators')
+        minpeaks = self.GetIntFromConfig('flatfilts', 'convfilt', 'minpeaks')
+        features = []
+        playlist = self.GetActivePlaylist()
+
+        files = self.GetActivePlaylist().files
+        file_index = 0
+        for current_file in files:
+            number_of_peaks = 0
+            file_index += 1
+            try:
+                current_file.identify(self.drivers)
+                if apply_plotmanipulators == 'all':
+                    plot = self.ApplyPlotmanipulators(current_file.plot, current_file)
+                if apply_plotmanipulators == 'flatten':
+                    plotmanipulator = self.GetPlotmanipulator('flatten')
+                    plot = plotmanipulator.method(current_file.plot, current_file)
+                if apply_plotmanipulators == 'none':
+                    plot = copy.deepcopy(current_file.plot)
+
+                peak_location, peak_size = self.has_peaks(plot)
+                number_of_peaks = len(peak_location)
+                if number_of_peaks != 1:
+                    if number_of_peaks > 0:
+                        feature_string = str(number_of_peaks) + ' features'
+                    else:
+                        feature_string = 'no features'
+                else:
+                    feature_string = '1 feature'
+                if number_of_peaks >= minpeaks:
+                    feature_string += '+'
+                output_string = ''.join(['Curve ', current_file.name, '(', str(file_index), '/', str(len(files)), '): ', feature_string])
+            except:
+                peak_location = []
+                peak_size = []
+                output_string = ''.join(['Curve ', current_file.name, '(', str(file_index), '/', str(len(files)), '): cannot be filtered. Probably unable to retrieve force data from corrupt file.'])
+            self.AppendToOutput(output_string)
+            if number_of_peaks >= minpeaks:
+                current_file.peak_location = peak_location
+                current_file.peak_size = peak_size
+                features.append(file_index - 1)
+
+        #Warn that no flattening had been done.
+        if not self.HasPlotmanipulator('plotmanip_flatten'):
+            self.AppendToOutput('Flatten manipulator was not found. Processing was done without flattening.')
+        else:
+            if not self.AppliesPlotmanipulator('flatten'):
+                self.AppendToOutput('Flatten manipulator was not applied.')
+                self.AppendToOutput('Try to enable the flatten plotmanipulator for better results.')
+
+        if not features:
+            self.AppendToOutput('Found nothing interesting. Check the playlist, could be a bug or criteria could be too stringent.')
+        else:
+            if len(features) < playlist.count:
+                self.AppendToOutput(''.join(['Found ', str(len(features)), ' potentially interesting files.']))
+                self.AppendToOutput('Regenerating playlist...')
+                playlist_filtered = playlist.filter_curves(features)
+                self.AddPlaylist(playlist_filtered, name='convfilt')
+            else:
+                self.AppendToOutput('No files filtered. Try different filtering criteria.')
+
diff --git a/hooke/plugin/generalvclamp.py b/hooke/plugin/generalvclamp.py
new file mode 100644 (file)
index 0000000..ccfb5cc
--- /dev/null
@@ -0,0 +1,487 @@
+#!/usr/bin/env python
+
+'''
+generalvclamp.py
+
+Plugin regarding general velocity clamp measurements
+
+Copyright 2008 by Massimo Sandal, Fabrizio Benedetti, Marco Brucale, Bruno Samori (University of Bologna, Italy),
+and Alberto Gomez-Casado (University of Twente)
+with modifications by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+import lib.libhooke as lh
+import wxversion
+wxversion.select(lh.WX_GOOD)
+
+import numpy as np
+import scipy as sp
+
+import warnings
+warnings.simplefilter('ignore', np.RankWarning)
+
+import lib.curve
+import lib.prettyformat
+
+class generalvclampCommands:
+
+    def _plug_init(self):
+        self.basecurrent = ''
+        self.basepoints = []
+        #TODO: what is self.autofile for?
+        #self.autofile = ''
+
+    def do_distance(self):
+        '''
+        DISTANCE
+        (generalvclamp.py)
+        Measure the distance (in nm) between two points.
+        For a standard experiment this is the delta X distance.
+        For a force clamp experiment this is the delta Y distance (actually becomes
+        an alias of zpiezo)
+        -----------------
+        Syntax: distance
+        '''
+        color = self.GetColorFromConfig('generalvclamp', 'distance', 'color')
+        decimals = self.GetIntFromConfig('generalvclamp', 'distance', 'decimals')
+        prefix = self.GetStringFromConfig('generalvclamp', 'distance', 'prefix')
+        multiplier = 10 ** lib.prettyformat.get_exponent(prefix)
+        show =  self.GetBoolFromConfig('generalvclamp', 'distance', 'show')
+        show_in_legend = self.GetBoolFromConfig('generalvclamp', 'distance', 'show_in_legend')
+        size = self.GetIntFromConfig('generalvclamp', 'distance', 'size')
+        whatset_str = self.GetStringFromConfig('generalvclamp', 'distance', 'whatset')
+        whatset = 'retraction'
+        if whatset_str == 'extension':
+            whatset = lh.EXTENSION
+        if whatset_str == 'retraction':
+            whatset = lh.RETRACTION
+
+        active_file = self.GetActiveFile()
+        if active_file.driver.experiment == 'clamp':
+            self.AppendToOutput('You wanted to use zpiezo perhaps?')
+            return
+        plugin = lib.plugin.Plugin()
+        plugin.name = 'generalvclamp'
+        plugin.section = 'distance'
+        delta = self._delta(message='Click 2 points to measure the distance.', whatset=whatset)
+
+        plot = self.GetDisplayedPlotCorrected()
+        if show:
+            #add the points to the plot
+            points = lib.curve.Curve()
+            points.color = color
+            if show_in_legend:
+                points.label = 'distance'
+            else:
+                points.label = '_nolegend_'
+            points.size = size
+            points.style = 'scatter'
+            points.units.x = delta.units.x
+            points.units.y = delta.units.y
+            points.x = [delta.point1.x, delta.point2.x]
+            points.y = [delta.point1.y, delta.point2.y]
+            plot.curves.append(points)
+
+        self.UpdatePlot(plot)
+
+        output_str = lib.prettyformat.pretty_format(abs(delta.get_delta_x()), delta.units.x, decimals, multiplier)
+        self.AppendToOutput(''.join(['Distance: ', output_str]))
+
+    def do_force(self):
+        '''
+        FORCE
+        (generalvclamp.py)
+        Measure the force difference (in pN) between two points
+        ---------------
+        Syntax: force
+        '''
+        color = self.GetColorFromConfig('generalvclamp', 'force', 'color')
+        decimals = self.GetIntFromConfig('generalvclamp', 'force', 'decimals')
+        prefix = self.GetStringFromConfig('generalvclamp', 'force', 'prefix')
+        multiplier = 10 ** lib.prettyformat.get_exponent(prefix)
+        show = self.GetBoolFromConfig('generalvclamp', 'force', 'show')
+        show_in_legend = self.GetBoolFromConfig('generalvclamp', 'force', 'show_in_legend')
+        size = self.GetIntFromConfig('generalvclamp', 'force', 'size')
+        whatset_str = self.GetStringFromConfig('generalvclamp', 'force', 'whatset')
+        whatset = 'retraction'
+        if whatset_str == 'extension':
+            whatset = lh.EXTENSION
+        if whatset_str == 'retraction':
+            whatset = lh.RETRACTION
+
+        active_file = self.GetActiveFile()
+        if active_file.driver.experiment == 'clamp':
+            self.AppendToOutput('This command makes no sense for a force clamp experiment.')
+            return
+        plugin = lib.plugin.Plugin()
+        plugin.name = 'generalvclamp'
+        plugin.section = 'force'
+        delta = self._delta(message='Click 2 points to measure the force.', whatset=whatset)
+
+        plot = self.GetDisplayedPlotCorrected()
+        if show:
+            #add the points to the plot
+            points = lib.curve.Curve()
+            points.color = color
+            if show_in_legend:
+                points.label = 'force'
+            else:
+                points.label = '_nolegend_'
+            points.size = size
+            points.style = 'scatter'
+            points.units.x = delta.units.x
+            points.units.y = delta.units.y
+            points.x = [delta.point1.x, delta.point2.x]
+            points.y = [delta.point1.y, delta.point2.y]
+            plot.curves.append(points)
+
+        self.UpdatePlot(plot)
+
+        output_str = lib.prettyformat.pretty_format(abs(delta.get_delta_y()), delta.units.y, decimals, multiplier)
+        self.AppendToOutput(''.join(['Force: ', output_str]))
+
+    def do_forcebase(self):
+        '''
+        FORCEBASE
+        (generalvclamp.py)
+        Measures the difference in force (in pN) between a point and a baseline
+        taken as the average between two points.
+
+        The baseline is fixed once for a given curve and different force measurements,
+        unless the user wants it to be recalculated
+        ------------
+        Syntax: forcebase [rebase]
+                rebase: Forces forcebase to ask again the baseline
+                max: Instead of asking for a point to measure, asks for two points and use
+                     the maximum peak in between
+        '''
+        baseline_color =  self.GetColorFromConfig('generalvclamp', 'forcebase', 'baseline_color')
+        baseline_show = self.GetBoolFromConfig('generalvclamp', 'forcebase', 'baseline_show')
+        baseline_show_in_legend = self.GetBoolFromConfig('generalvclamp', 'forcebase', 'baseline_show_in_legend')
+        baseline_size = self.GetIntFromConfig('generalvclamp', 'forcebase', 'baseline_size')
+        decimals = self.GetIntFromConfig('generalvclamp', 'forcebase', 'decimals')
+        maximum_color =  self.GetColorFromConfig('generalvclamp', 'forcebase', 'maximum_color')
+        maximum_show = self.GetBoolFromConfig('generalvclamp', 'forcebase', 'maximum_show')
+        maximum_show_in_legend = self.GetBoolFromConfig('generalvclamp', 'forcebase', 'maximum_show_in_legend')
+        maximum_size = self.GetIntFromConfig('generalvclamp', 'forcebase', 'maximum_size')
+        maximumrange_color =  self.GetColorFromConfig('generalvclamp', 'forcebase', 'maximumrange_color')
+        maximumrange_show = self.GetBoolFromConfig('generalvclamp', 'forcebase', 'maximumrange_show')
+        maximumrange_show_in_legend = self.GetBoolFromConfig('generalvclamp', 'forcebase', 'maximumrange_show_in_legend')
+        maximumrange_size = self.GetIntFromConfig('generalvclamp', 'forcebase', 'maximumrange_size')
+        maxpoint = self.GetBoolFromConfig('generalvclamp', 'forcebase', 'max')
+        prefix = self.GetStringFromConfig('generalvclamp', 'forcebase', 'prefix')
+        multiplier = 10 ** lib.prettyformat.get_exponent(prefix)
+        rebase = self.GetBoolFromConfig('generalvclamp', 'forcebase', 'rebase')
+        whatset_str = self.GetStringFromConfig('generalvclamp', 'forcebase', 'whatset')
+        whatset = 'retraction'
+        if whatset_str == 'extension':
+            whatset = lh.EXTENSION
+        if whatset_str == 'retraction':
+            whatset = lh.RETRACTION
+
+        plot = self.GetDisplayedPlotCorrected()
+
+        filename = self.GetActiveFile().name
+        if rebase or (self.basecurrent != filename):
+            self.basepoints = self._measure_N_points(N=2, message='Click on 2 points to select the baseline.', whatset=whatset)
+            self.basecurrent = filename
+
+        #TODO: maxpoint does not seem to be picking up the 'real' maximum (at least not with test.hkp/default.000)
+        maximumrange_points = []
+        maximum_point = []
+        if maxpoint:
+            maximumrange_points = self._measure_N_points(N=2, message='Click 2 points to select the range for maximum detection.', whatset=whatset)
+            boundpoints = [maximumrange_points[0].index, maximumrange_points[1].index]
+            boundpoints.sort()
+            try:
+                vector_x = plot.curves[whatset].x[boundpoints[0]:boundpoints[1]]
+                vector_y = plot.curves[whatset].y[boundpoints[0]:boundpoints[1]]
+                y = min(vector_y)
+                index = vector_y.index(y)
+                maximum_point = [self._clickize(vector_x, vector_y, index)]
+            except ValueError:
+                self.AppendToOutput('Chosen interval not valid. Try picking it again. Did you pick the same point as begin and end of the interval?')
+                return
+        else:
+            maximum_point = self._measure_N_points(N=1, message='Click on the point to measure.', whatset=whatset)
+            y = maximum_point[0].graph_coords[1]
+
+        boundaries = [self.basepoints[0].index, self.basepoints[1].index]
+        boundaries.sort()
+        to_average = plot.curves[whatset].y[boundaries[0]:boundaries[1]] #y points to average
+
+        avg = np.mean(to_average)
+        forcebase = abs(y - avg)
+
+        curve = plot.curves[whatset]
+        if self.basepoints and baseline_show:
+            #add the baseline points to the plot
+            baseline = lib.curve.Curve()
+            baseline.color = baseline_color
+            if baseline_show_in_legend:
+                baseline.label = 'baseline'
+            else:
+                baseline.label = '_nolegend_'
+            baseline.size = baseline_size
+            baseline.style = 'scatter'
+            baseline.units.x = curve.units.x
+            baseline.units.y = curve.units.y
+            for point in self.basepoints:
+                baseline.x += [point.graph_coords[0]]
+                baseline.y += [point.graph_coords[1]]
+            plot.curves.append(baseline)
+
+        if maximumrange_points and maximumrange_show:
+            #add the range points to the plot
+            maximumrange = lib.curve.Curve()
+            maximumrange.color = maximumrange_color
+            if maximumrange_show_in_legend:
+                maximumrange.label = 'maximumrange'
+            else:
+                maximumrange.label = '_nolegend_'
+            maximumrange.size = maximumrange_size
+            maximumrange.style = 'scatter'
+            maximumrange.units.x = curve.units.x
+            maximumrange.units.y = curve.units.y
+            for point in maximumrange_points:
+                maximumrange.x += [point.graph_coords[0]]
+                maximumrange.y += [point.graph_coords[1]]
+            plot.curves.append(maximumrange)
+
+        if maximum_show:
+            #add the maximum to the plot
+            maximum = lib.curve.Curve()
+            maximum.color = maximum_color
+            if maximum_show_in_legend:
+                maximum.label = 'maximum'
+            else:
+                maximum.label = '_nolegend_'
+            maximum.size = maximum_size
+            maximum.style = 'scatter'
+            maximum.units.x = curve.units.x
+            maximum.units.y = curve.units.y
+            maximum.x = [maximum_point[0].graph_coords[0]]
+            maximum.y = [maximum_point[0].graph_coords[1]]
+            plot.curves.append(maximum)
+
+        self.UpdatePlot(plot)
+
+        unit_str = plot.curves[whatset].units.y
+        output_str = lib.prettyformat.pretty_format(forcebase, unit_str, decimals, multiplier)
+        self.AppendToOutput(''.join(['Force: ', output_str]))
+
+    def plotmanip_multiplier(self, plot, current, customvalue=False):
+        '''
+        Multiplies all the Y values of an SMFS curve by a value stored in the 'force_multiplier'
+        configuration variable. Useful for calibrations and other stuff.
+        '''
+
+        #not a smfs curve...
+        if current.driver.experiment != 'smfs':
+            return plot
+
+        force_multiplier = self.GetFloatFromConfig('generalvclamp', 'force_multiplier')
+        if force_multiplier == 1:
+            return plot
+
+        plot.curves[lh.EXTENSION].y = [element * force_multiplier for element in plot.curves[lh.EXTENSION].y]
+        plot.curves[lh.RETRACTION].y = [element * force_multiplier for element in plot.curves[lh.RETRACTION].y]
+
+        return plot
+
+    def plotmanip_flatten(self, plot, current, customvalue=0):
+        '''
+        Subtracts a polynomial fit to the non-contact part of the curve, as to flatten it.
+        the best polynomial fit is chosen among polynomials of degree 1 to n, where n is
+        given by the configuration file or by the customvalue.
+
+        customvalue = int (>0) --> starts the function even if config says no (default=0)
+        '''
+
+        #not a smfs curve...
+        if current.driver.experiment != 'smfs':
+            return current
+
+        #config is not flatten, and customvalue flag is false too
+        #if (not self.config['generalvclamp']['flatten'].as_bool('value')) and (customvalue == 0):
+        ##TODO: do we need this?
+        #if (not self.GetBoolFromConfig('generalvclamp', 'flatten')) and (customvalue == 0):
+            #return plot
+
+        max_exponent = 12
+        delta_contact = 0
+
+        if customvalue > 0:
+            max_cycles = customvalue
+        else:
+            #Using > 1 usually doesn't help and can give artefacts. However, it could be useful too.
+            max_cycles = self.GetIntFromConfig('generalvclamp', 'max_cycles')
+
+        contact_index = self.find_contact_point(plot)
+
+        valn = [[] for item in range(max_exponent)]
+        yrn = [0.0 for item in range(max_exponent)]
+        errn = [0.0 for item in range(max_exponent)]
+
+        for i in range(int(max_cycles)):
+            x_ext = plot.curves[lh.EXTENSION].x[contact_index + delta_contact:]
+            y_ext = plot.curves[lh.EXTENSION].y[contact_index + delta_contact:]
+            x_ret = plot.curves[lh.RETRACTION].x[contact_index + delta_contact:]
+            y_ret = plot.curves[lh.RETRACTION].y[contact_index + delta_contact:]
+            for exponent in range(max_exponent):
+                try:
+                    valn[exponent] = sp.polyfit(x_ext, y_ext, exponent)
+                    yrn[exponent] = sp.polyval(valn[exponent], x_ret)
+                    errn[exponent] = sp.sqrt(sum((yrn[exponent] - y_ext) ** 2) / float(len(y_ext)))
+                except Exception, e:
+                    print 'Cannot flatten!'
+                    print e
+                    return current
+
+            best_exponent = errn.index(min(errn))
+
+            #extension
+            ycorr_ext = y_ext - yrn[best_exponent] + y_ext[0] #noncontact part
+            yjoin_ext = np.array(plot.curves[lh.EXTENSION].y[0:contact_index + delta_contact]) #contact part
+            #retraction
+            ycorr_ret = y_ret - yrn[best_exponent] + y_ext[0] #noncontact part
+            yjoin_ret = np.array(plot.curves[lh.RETRACTION].y[0:contact_index + delta_contact]) #contact part
+
+            ycorr_ext = np.concatenate((yjoin_ext, ycorr_ext))
+            ycorr_ret = np.concatenate((yjoin_ret, ycorr_ret))
+
+            plot.curves[lh.EXTENSION].y = list(ycorr_ext)
+            plot.curves[lh.RETRACTION].y = list(ycorr_ret)
+
+        return plot
+
+    #---SLOPE---
+    def do_slope(self):
+        '''
+        SLOPE
+        (generalvclamp.py)
+        Measures the slope of a delimited chunk on the return trace.
+        The chunk can be delimited either by two manual clicks, or have
+        a fixed width, given as an argument.
+        ---------------
+        Syntax: slope [width]
+                The facultative [width] parameter specifies how many
+                points will be considered for the fit. If [width] is
+                specified, only one click will be required.
+        Copyright 2008 by Marco Brucale, Massimo Sandal
+        '''
+
+        decimals = self.GetIntFromConfig('generalvclamp', 'slope', 'decimals')
+        fitspan = self.GetIntFromConfig('generalvclamp', 'slope', 'fitspan')
+        point_color = self.GetColorFromConfig('generalvclamp', 'slope', 'point_color')
+        point_show = self.GetBoolFromConfig('generalvclamp', 'slope', 'point_show')
+        point_show_in_legend = self.GetBoolFromConfig('generalvclamp', 'slope', 'point_show_in_legend')
+        point_size = self.GetIntFromConfig('generalvclamp', 'slope', 'point_size')
+        slope_color = self.GetColorFromConfig('generalvclamp', 'slope', 'slope_color')
+        slope_linewidth = self.GetIntFromConfig('generalvclamp', 'slope', 'slope_linewidth')
+        slope_show = self.GetBoolFromConfig('generalvclamp', 'slope', 'slope_show')
+        slope_show_in_legend = self.GetBoolFromConfig('generalvclamp', 'slope', 'slope_show_in_legend')
+        whatset_str = self.GetStringFromConfig('generalvclamp', 'slope', 'whatset')
+        whatset = 'retraction'
+        if whatset_str == 'extension':
+            whatset = lh.EXTENSION
+        if whatset_str == 'retraction':
+            whatset = lh.RETRACTION
+
+        # Decides between the two forms of user input
+        #TODO: add an option 'mode' with options 'chunk' and 'point'
+        if fitspan == 0:
+            # Gets the Xs of two clicked points as indexes on the curve curve vector
+            clicked_points = []
+            points = self._measure_N_points(N=2, message='Click 2 points to select the chunk.', whatset=whatset)
+            clicked_points = [points[0].index, points[1].index]
+            clicked_points.sort()
+        else:
+            clicked_points = []
+            points = self._measure_N_points(N=1, message='Click on the leftmost point of the chunk (i.e.usually the peak).', whatset=whatset)
+            clicked_points = [points[0].index - fitspan, points[0].index]
+
+        # Calls the function linefit_between
+        parameters = [0, 0, [], []]
+        try:
+            parameters = self.linefit_between(clicked_points[0], clicked_points[1], whatset=whatset)
+        except:
+            self.AppendToOutput('Cannot fit. Did you click the same point twice?')
+            return
+
+        plot = self.GetDisplayedPlotCorrected()
+        # Makes a vector with the fitted parameters and sends it to the GUI
+        xtoplot=parameters[2]
+        ytoplot=[]
+        x = 0
+        for x in xtoplot:
+            ytoplot.append((x * parameters[0]) + parameters[1])
+
+        clickvector_x = []
+        clickvector_y = []
+        for item in points:
+            clickvector_x.append(item.graph_coords[0])
+            clickvector_y.append(item.graph_coords[1])
+
+        if point_show:
+            #add the clicked point to the plot
+            point = lib.curve.Curve()
+            point.color = point_color
+            if point_show_in_legend:
+                point.label = 'clicked point'
+            else:
+                point.label = '_nolegend_'
+            point.size = point_size
+            point.style = 'scatter'
+            point.x = clickvector_x
+            point.y = clickvector_y
+            plot.curves.append(point)
+
+        if slope_show:
+            #add the slope to the plot
+            slope = lib.curve.Curve()
+            slope.color = slope_color
+            if slope_show_in_legend:
+                slope.label = 'slope'
+            else:
+                slope.label = '_nolegend_'
+            slope.linewidth = slope_linewidth
+            slope.style = 'plot'
+            slope.units.x = plot.curves[whatset].units.x
+            slope.units.y = plot.curves[whatset].units.y
+            slope.x = xtoplot
+            slope.y = ytoplot
+            plot.curves.append(slope)
+
+        self.UpdatePlot(plot)
+
+        # Outputs the relevant slope parameter
+        unit_str = plot.curves[whatset].units.x + '/' + plot.curves[whatset].units.y
+        output_str = lib.prettyformat.pretty_format(parameters[0], unit_str, decimals, 1)
+        self.AppendToOutput(''.join(['Slope: ', output_str]))
+
+    def linefit_between(self, index1, index2, whatset=lh.RETRACTION):
+        '''
+        Creates two vectors (xtofit, ytofit) slicing out from the
+        curve return trace a portion delimited by the two indeces
+        given as arguments.
+        Then does a least squares linear fit on that slice.
+        Finally returns [0]=the slope, [1]=the intercept of the
+        fitted 1st grade polynomial, and [2,3]=the actual (x,y) vectors
+        used for the fit.
+        Copyright 2008 by Marco Brucale, Massimo Sandal
+        '''
+        # Translates the indeces into two vectors containing the x, y data to fit
+        plot = self.displayed_plot
+        xtofit = plot.corrected_curves[whatset].x[index1:index2]
+        ytofit = plot.corrected_curves[whatset].y[index1:index2]
+
+        # Does the actual linear fitting (simple least squares with numpy.polyfit)
+        linefit = np.polyfit(xtofit, ytofit, 1)
+
+        return (linefit[0], linefit[1], xtofit, ytofit)
+
+
+
diff --git a/hooke/plugin/plot.py b/hooke/plugin/plot.py
new file mode 100644 (file)
index 0000000..26cd8a2
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+
+'''
+plot.py
+
+Global settings for plots
+
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+class plotCommands:
+
+    def do_preferences(self):
+        active_file = self.GetActiveFile()
+        for curve in active_file.plot.curves:
+            curve.decimals.x = self.GetIntFromConfig('plot', 'preferences', 'x_decimals')
+            curve.decimals.y = self.GetIntFromConfig('plot', 'preferences', 'y_decimals')
+            curve.legend = self.GetBoolFromConfig('plot', 'preferences', 'legend')
+            curve.prefix.x = self.GetStringFromConfig('plot', 'preferences', 'x_prefix')
+            curve.prefix.y = self.GetStringFromConfig('plot', 'preferences', 'y_prefix')
+
+        self.UpdatePlot();
diff --git a/hooke/plugin/procplots.py b/hooke/plugin/procplots.py
new file mode 100644 (file)
index 0000000..f56010a
--- /dev/null
@@ -0,0 +1,288 @@
+#!/usr/bin/env python
+
+'''
+procplots.py
+
+Process plots plugin for force curves.
+
+Copyright ???? by ?
+with modifications by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+import lib.libhooke as lh
+import wxversion
+wxversion.select(lh.WX_GOOD)
+
+import copy
+from numpy import arange, diff, fft, median
+from scipy.signal import medfilt
+
+from lib.peakspot import conv_dx
+import lib.prettyformat
+
+class procplotsCommands:
+
+    def _plug_init(self):
+        pass
+
+    def do_convplot(self):
+        '''
+        CONVPLOT
+        (procplots.py)
+        Plots the convolution data of the currently displayed force curve retraction.
+        ------------
+        Syntax:
+        convplot
+        '''
+
+        #need to convert the string that contains the list into a list
+        column = self.GetIntFromConfig('procplots', 'convplot', 'column')
+        convolution = eval(self.GetStringFromConfig('procplots', 'convplot', 'convolution'))
+        row = self.GetIntFromConfig('procplots', 'convplot', 'row')
+        whatset_str = self.GetStringFromConfig('procplots', 'convplot', 'whatset')
+        whatset = []
+        if whatset_str == 'extension':
+            whatset = [lh.EXTENSION]
+        if whatset_str == 'retraction':
+            whatset = [lh.RETRACTION]
+        if whatset_str == 'both':
+            whatset = [lh.EXTENSION, lh.RETRACTION]
+
+        #TODO: add option to keep previous derivplot
+        plot = self.GetDisplayedPlotCorrected()
+
+        for index in whatset:
+            conv_curve = copy.deepcopy(plot.curves[index])
+            #Calculate convolution
+            conv_curve.y = conv_dx(plot.curves[index].y, convolution)
+
+            conv_curve.destination.column = column
+            conv_curve.destination.row = row
+            conv_curve.title = 'Convolution'
+            plot.curves.append(conv_curve)
+
+        #warn if no flattening has been done.
+        if not self.AppliesPlotmanipulator('flatten'):
+            self.AppendToOutput('Flatten manipulator was not applied. Processing was done without flattening.')
+            self.AppendToOutput('Enable the flatten plotmanipulator for better results.')
+
+        self.UpdatePlot(plot)
+
+    def do_derivplot(self):
+        '''
+        Plots the discrete differentiation of the currently displayed force curve.
+        '''
+        column = self.GetIntFromConfig('procplots', 'derivplot', 'column')
+        row = self.GetIntFromConfig('procplots', 'derivplot', 'row')
+        #TODO: what os select good for?
+        select = self.GetBoolFromConfig('procplots', 'derivplot', 'select')
+        whatset_str = self.GetStringFromConfig('procplots', 'derivplot', 'whatset')
+        whatset = []
+        if whatset_str == 'extension':
+            whatset = [lh.EXTENSION]
+        if whatset_str == 'retraction':
+            whatset = [lh.RETRACTION]
+        if whatset_str == 'both':
+            whatset = [lh.EXTENSION, lh.RETRACTION]
+
+        #TODO: add option to keep previous derivplot
+        plot = self.GetDisplayedPlotCorrected()
+
+        for index in whatset:
+            deriv_curve = copy.deepcopy(plot.curves[index])
+            deriv_curve.x = deriv_curve.x[:-1]
+            deriv_curve.y = diff(deriv_curve.y).tolist()
+
+            deriv_curve.destination.column = column
+            deriv_curve.destination.row = row
+            deriv_curve.title = 'Discrete differentiation'
+            deriv_curve.units.y += ' ' + deriv_curve.units.x + '-1'
+            plot.curves.append(deriv_curve)
+
+        self.UpdatePlot(plot)
+
+    def do_replot(self):
+        '''
+        Replots the current force curve from scratch eliminating any secondary plots.
+        '''
+        self.UpdatePlot()
+
+    def do_subtplot(self):
+        '''
+        SUBTPLOT
+        (procplots.py plugin)
+        Plots the difference between retraction and extension of the currently displayed curve
+        -------
+        Syntax: subtplot
+        '''
+
+        #TODO: add option to keep previous subtplot
+        plot = self.GetDisplayedPlotCorrected()
+
+        extension = plot.curves[lh.EXTENSION]
+        retraction = plot.curves[lh.RETRACTION]
+
+        extension, retraction = self.subtract_curves(extension, retraction)
+
+        self.UpdatePlot(plot)
+
+    def subtract_curves(self, minuend, subtrahend):
+        '''
+        calculates: difference = minuend - subtrahend
+        (usually:              extension - retraction
+        '''
+
+        #we want the same number of points for minuend and subtrahend
+        #TODO: is this not already done when normalizing in the driver?
+        maxpoints_tot = min(len(minuend.x), len(subtrahend.x))
+        minuend.x = minuend.x[0:maxpoints_tot]
+        minuend.y = minuend.y[0:maxpoints_tot]
+        subtrahend.x = subtrahend.x[0:maxpoints_tot]
+        subtrahend.y = subtrahend.y[0:maxpoints_tot]
+
+        subtrahend.y = [y_subtrahend - y_minuend for y_subtrahend, y_minuend in zip(subtrahend.y, minuend.y)]
+        minuend.y = [0] * len(minuend.x)
+
+        return minuend, subtrahend
+
+#-----PLOT MANIPULATORS
+    def plotmanip_median(self, plot, current, customvalue=False):
+        '''
+        does the median of the y values of a plot
+        '''
+        median_filter = self.GetIntFromConfig('procplots', 'median')
+        if median_filter == 0:
+            return plot
+
+        if float(median_filter) / 2 == int(median_filter) / 2:
+            median_filter += 1
+
+        for curve in plot.curves:
+            curve.y = medfilt(curve.y, median_filter).tolist()
+
+        return plot
+
+    def plotmanip_correct(self, plot, current, customvalue=False):
+        '''
+        does the correction for the deflection for a force spectroscopy curve.
+        Assumes that:
+        - the current plot has a deflection() method that returns a vector of values
+        - the deflection() vector is as long as the X of extension + the X of retraction
+        - plot.vectors[0][0] is the X of extension curve
+        - plot.vectors[1][0] is the X of retraction curve
+
+        FIXME: both this method and the picoforce driver have to be updated, deflection() must return
+        a more sensible data structure!
+        '''
+        #use only for force spectroscopy experiments!
+        if current.driver.experiment != 'smfs':
+            return plot
+
+        if not customvalue:
+            customvalue = self.GetBoolFromConfig('procplots', 'correct')
+        if not customvalue:
+            return plot
+
+        defl_ext, defl_ret = current.driver.deflection()
+
+        plot.curves[lh.EXTENSION].x = [(zpoint - deflpoint) for zpoint,deflpoint in zip(plot.curves[lh.EXTENSION].x, defl_ext)]
+        plot.curves[lh.RETRACTION].x = [(zpoint - deflpoint) for zpoint,deflpoint in zip(plot.curves[lh.RETRACTION].x, defl_ret)]
+
+        return plot
+
+    def plotmanip_centerzero(self, plot, current, customvalue=False):
+        '''
+        Centers the force curve so the median (the free level) corresponds to 0 N
+        '''
+        #use only for force spectroscopy experiments!
+        if current.driver.experiment != 'smfs':
+            return plot
+
+        if not customvalue:
+            customvalue = self.GetBoolFromConfig('procplots', 'centerzero')
+        if not customvalue:
+            return plot
+
+        levelapp = float(median(plot.curves[lh.EXTENSION].y))
+        levelret = float(median(plot.curves[lh.RETRACTION].y))
+
+        level = (levelapp + levelret)/2
+
+        plot.curves[lh.EXTENSION].y = [i-level for i in plot.curves[lh.EXTENSION].y]
+        plot.curves[lh.RETRACTION].y = [i-level for i in plot.curves[lh.RETRACTION].y]
+
+        return plot
+
+#FFT---------------------------
+    def fft_plot(self, curve, boundaries=[0, -1]):
+        '''
+        calculates the fast Fourier transform for the selected vector in the plot
+        '''
+
+        fftlen = len(curve.y[boundaries[0]:boundaries[1]]) / 2 #need just 1/2 of length
+        curve.x = arange(1, fftlen).tolist()
+
+        try:
+            curve.y = abs(fft(curve.y[boundaries[0]:boundaries[1]])[1:fftlen]).tolist()
+        except TypeError: #we take care of newer NumPy (1.0.x)
+            curve.y = abs(fft.fft(curve.y[boundaries[0]:boundaries[1]])[1:fftlen]).tolist()
+
+        return curve
+
+    def do_fft(self):
+        '''
+        FFT
+        (procplots.py plugin)
+        Plots the fast Fourier transform of the selected plot
+        ---
+        Syntax: fft [top,bottom] [select] [0,1...]
+
+        By default, fft performs the Fourier transform on all the 0-th data set on the
+        top plot.
+
+        [top, bottom]: which plot is the data set to fft (default: top)
+        [select]: you pick up two points on the plot and fft only the segment between
+        [0,1,...]: which data set on the selected plot you want to fft (default: 0)
+        '''
+
+        column = self.GetIntFromConfig('procplots', 'fft', 'column')
+        row = self.GetIntFromConfig('procplots', 'fft', 'row')
+        select = self.GetBoolFromConfig('procplots', 'fft', 'select')
+        whatset_str = self.GetStringFromConfig('procplots', 'fft', 'whatset')
+        whatset = []
+        if whatset_str == 'extension':
+            whatset = [lh.EXTENSION]
+        if whatset_str == 'retraction':
+            whatset = [lh.RETRACTION]
+        if whatset_str == 'both':
+            whatset = [lh.EXTENSION, lh.RETRACTION]
+
+        if select:
+            points = self._measure_N_points(N=2, message='Please select a region by clicking on the start and the end point.', whatset=lh.RETRACTION)
+            boundaries = [points[0].index, points[1].index]
+            boundaries.sort()
+        else:
+            boundaries = [0, -1]
+
+        #TODO: add option to keep previous FFT
+        plot = self.GetDisplayedPlotCorrected()
+
+        for index in whatset:
+            fft_curve = self.fft_plot(copy.deepcopy(plot.curves[index]), boundaries)
+
+            fft_curve.decimals.x = 3
+            fft_curve.decimals.y = 0
+            fft_curve.destination.column = column
+            fft_curve.destination.row = row
+            fft_curve.label = plot.curves[index].label
+            fft_curve.legend = True
+            fft_curve.prefix.x = lib.prettyformat.get_prefix(max(fft_curve.x))
+            fft_curve.prefix.y = lib.prettyformat.get_prefix(max(fft_curve.y))
+            fft_curve.title = 'FFT'
+            fft_curve.units.x = 'Hz'
+            fft_curve.units.y = 'power'
+            plot.curves.append(fft_curve)
+
+        self.UpdatePlot(plot)
diff --git a/hooke/plugin/results.py b/hooke/plugin/results.py
new file mode 100644 (file)
index 0000000..e7b67ce
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+'''
+results.py
+
+Results commands for Hooke.
+
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+class resultsCommands(object):
+    '''
+    Results commands to show a certain type of results and to clear results
+    '''
+
+    def _plug_init(self):
+        pass
+
+    def do_clear_results(self):
+        '''
+        Deletes all fitting results from the curve.
+        '''
+        plot = self.GetActivePlot()
+        if plot is not None:
+            plot.results.clear()
+        self.UpdatePlot()
+
+
+    def do_show_results(self):
+        '''
+        Select which type of result should be displayed on the plot.
+        '''
+        self.results_str = self.GetStringFromConfig('results', 'show_results', 'result_type')
+        self.UpdatePlot()
diff --git a/hooke/ui/gui/__init__.py b/hooke/ui/gui/__init__.py
new file mode 100644 (file)
index 0000000..803f29c
--- /dev/null
@@ -0,0 +1 @@
+#!/usr/bin/env python\r
diff --git a/hooke/ui/gui/clickedpoint.py b/hooke/ui/gui/clickedpoint.py
new file mode 100644 (file)
index 0000000..377ff2a
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+clickedpoint.py\r
+\r
+ClickedPoint class for Hooke.\r
+\r
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+from scipy import arange\r
+\r
+class ClickedPoint(object):\r
+    '''\r
+    This class defines what a clicked point on the curve plot is.\r
+    '''\r
+    def __init__(self):\r
+\r
+        self.is_marker = None #boolean ; decides if it is a marker\r
+        self.is_line_edge = None #boolean ; decides if it is the edge of a line (unused)\r
+        self.absolute_coords = (None, None) #(float,float) ; the absolute coordinates of the clicked point on the graph\r
+        self.graph_coords = (None, None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point\r
+        self.index = None #integer ; the index of the clicked point with respect to the vector selected\r
+        self.dest = None #0 or 1 ; 0=top plot 1=bottom plot\r
+\r
+    def find_graph_coords(self, xvector, yvector):\r
+        '''\r
+        Given a clicked point on the plot, finds the nearest point in the dataset (in X) that\r
+        corresponds to the clicked point.\r
+        '''\r
+        dists = []\r
+        for index in arange(1, len(xvector), 1):\r
+            dists.append(((self.absolute_coords[0] - xvector[index]) ** 2)+((self.absolute_coords[1] - yvector[index]) ** 2))\r
+\r
+        self.index=dists.index(min(dists))\r
+        self.graph_coords=(xvector[self.index], yvector[self.index])\r
diff --git a/hooke/ui/gui/curve.py b/hooke/ui/gui/curve.py
new file mode 100644 (file)
index 0000000..1a8c688
--- /dev/null
@@ -0,0 +1,86 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+curve.py\r
+\r
+Curve and related classes for Hooke.\r
+\r
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+from  matplotlib.ticker import Formatter\r
+import lib.prettyformat\r
+\r
+class Curve(object):\r
+\r
+    def __init__(self):\r
+        self.color = 'blue'\r
+        self.decimals = Decimals()\r
+        self.destination = Destination()\r
+        self.label = ''\r
+        self.legend = False\r
+        self.linewidth = 1\r
+        self.prefix = Prefix()\r
+        self.size = 0.5\r
+        self.style = 'plot'\r
+        self.title = ''\r
+        self.units = Units()\r
+        self.visible = True\r
+        self.x = []\r
+        self.y = []\r
+\r
+\r
+class Data(object):\r
+\r
+    def __init__(self):\r
+        self.x = []\r
+        self.y = []\r
+\r
+\r
+class Decimals(object):\r
+\r
+    def __init__(self):\r
+        self.x = 2\r
+        self.y = 2\r
+\r
+\r
+class Destination(object):\r
+\r
+    def __init__(self):\r
+        self.column = 1\r
+        self.row = 1\r
+\r
+\r
+class Prefix(object):\r
+\r
+    def __init__(self):\r
+        self.x = 'n'\r
+        self.y = 'p'\r
+\r
+\r
+class PrefixFormatter(Formatter):\r
+    '''\r
+    Formatter (matplotlib) class that uses power prefixes.\r
+    '''\r
+    def __init__(self, decimals=2, prefix='n', use_zero=True):\r
+        self.decimals = decimals\r
+        self.prefix = prefix\r
+        self.use_zero = use_zero\r
+\r
+    def __call__(self, x, pos=None):\r
+        'Return the format for tick val *x* at position *pos*'\r
+        if self.use_zero:\r
+            if x == 0:\r
+                return '0'\r
+        multiplier = lib.prettyformat.get_exponent(self.prefix)\r
+        decimals_str = '%.' + str(self.decimals) + 'f'\r
+        return decimals_str % (x / (10 ** multiplier))\r
+\r
+\r
+class Units(object):\r
+\r
+    def __init__(self):\r
+        self.x = ''\r
+        self.y = ''\r
diff --git a/hooke/ui/gui/delta.py b/hooke/ui/gui/delta.py
new file mode 100644 (file)
index 0000000..7996daa
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+
+'''
+delta.py
+
+Delta class for Hooke to describe differences between 2 points.
+
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+from lib.curve import Units
+
+class Point(object):
+
+    def __init__(self):
+        self.x = 0
+        self.y = 0
+
+class Delta(object):
+
+    def __init__(self):
+        self.point1 = Point()
+        self.point2 = Point()
+        self.units = Units()\r
+\r
+    def get_delta_x(self):\r
+        return self.point1.x - self.point2.x\r
+\r
+    def get_delta_y(self):\r
+        return self.point1.y - self.point2.y\r
+\r
+
index 7962d6aece34246622585208b9bb1ed2cd9e273d..b5b4809dd177a0ea52135d00abac8d161b0e7d6c 100644 (file)
-# 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/>.
+#!/usr/bin/env python
 
+'''
+driver.py
 
-    def _generate_vectors(self):
-        """
-        Here we parse the data and generate the raw vectors. This
-        method has only to do with the peculiar file format here, so
-        it's of no big interest (I just wrote it to present a
-        functional driver).
+Base class for file format drivers.
 
-        Only thing to remember, it can be nice to call methods that
-        are used only "internally" by the driver (or by plugins) with
-        a "_" prefix, so to have a visual remark. But it's just an
-        advice.
-        """
-        vectors={'PLOT1':[[],[],[],[]] , 'PLOT2':[[],[],[],[]]}
-        positions={'X1':0,'Y1':1,'X2':2,'Y2':3}
-        whatplot=''
-        pos=0
-        for item in self.data:
-            try:
-                num=float(item)
-                vectors[whatplot][pos].append(num)
-            except ValueError:
-                if item[:-1]=='PLOT1':
-                    whatplot=item[:-1]
-                elif item[:-1]=='PLOT2':
-                    whatplot=item[:-1]
-                elif item[0]=='X' or item[0]=='Y':
-                    pos=positions[item[:-1]]
-                else:
-                    pass
+Copyright 2006 by Massimo Sandal (University of Bologna, Italy).
 
-        return vectors
+This program is released under the GNU General Public License version 2.
+'''
 
-    def default_plots(self):
-        """
-        THIS METHOD MUST BE DEFINED.
-        RETURNS: [ lhc.PlotObject ] or [ lhc.PlotObject, lhc.PlotObject]
-
-        This is the method that returns the plots to Hooke.
-        It must return a list with *one* or *two* PlotObjects.
-
-        See the curve.py source code to see how PlotObjects are defined and work in detail.
-        """
-        gen_vectors=self._generate_vectors()
+import lib.plot
 
-        #Here we create the main plot PlotObject and initialize its vectors.
-        main_plot=lhc.PlotObject()
-        main_plot.vectors=[]
-        #The same for the other plot.
-        other_plot=lhc.PlotObject()
-        other_plot.vectors=[]
+class Driver(object):
+    '''
+    Base class for file format drivers.
 
-        """
-        Now we fill the plot vectors with our data.
-                                                           set 1                           set 2
-        The "correct" shape of the vector is [ [[x1,x2,x3...],[y1,y2,y3...]] , [[x1,x2,x3...],[y1,y2,y3...]] ], so we have to put stuff in this way into it.
+    To be overridden
+    '''
+    def __init__(self):
+        self.experiment = ''
+        self.filetype = ''
 
-        The add_set() method takes care of this , just use plot.add_set(x,y).
-        """
-        main_plot.add_set(gen_vectors['PLOT1'][0],gen_vectors['PLOT1'][1])
-        main_plot.add_set(gen_vectors['PLOT1'][2],gen_vectors['PLOT1'][3])
+    def is_me(self):
+        '''
+        This method must read the file and return True if the filetype can be managed by the driver, False if not.
+        '''
+        return False
 
-        other_plot.add_set(gen_vectors['PLOT2'][0],gen_vectors['PLOT2'][1])
-        other_plot.add_set(gen_vectors['PLOT2'][2],gen_vectors['PLOT2'][3])
+    def close_all(self):
+        '''
+        This method must close all the open files of the driver, explicitly.
+        '''
+        return None
 
-        """
-        normalize_vectors() trims the vectors, so that if two x/y couples are of different lengths, the latest
-        points are trimmed (otherwise we have a python error). Always a good idea to run it, to avoid crashes.
-        """
-        main_plot.normalize_vectors()
-        other_plot.normalize_vectors()
-
-        """
-        Here we define:
-        - units: [string, string], define the measure units of X and Y axes
-        - destination: 0/1 , defines where to plot the plot (0=top, 1=bottom), default=0
-        - title: string , the plot title.
+    def default_plots(self):
+        plot = lib.plot.Plot()
+        plot.curves.append([0])
+        plot.curves.append([0])
+        
+        return [plot]
 
-        for each plot.
-        Again, see curve.py comments for details.
-        """
-        main_plot.units=['unit of x','unit of y']
-        main_plot.destination=0
-        main_plot.title=self.filename+' main'
 
-        other_plot.units=['unit of x','unit of y']
-        other_plot.destination=1
-        other_plot.title=self.filename+' other'
 
-        return [main_plot, other_plot]
diff --git a/hooke/ui/gui/file.py b/hooke/ui/gui/file.py
new file mode 100644 (file)
index 0000000..6af41db
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+file.py\r
+\r
+File class for Hooke.\r
+\r
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+import os.path\r
+import lib.plot\r
+\r
+class File(object):\r
+\r
+    def __init__(self, filename=None, drivers=None):\r
+        self.driver = None\r
+        self.note = ''\r
+        self.plot = lib.plot.Plot()\r
+        if filename is None:\r
+            self.filename = None\r
+            self.name = None\r
+            self.path = None\r
+        else:\r
+            self.filename = filename\r
+            self.path, self.name = os.path.split(filename)\r
+\r
+    def identify(self, drivers):\r
+        '''\r
+        identifies a curve and returns the corresponding object\r
+        '''\r
+        for driver in drivers:\r
+            current_driver = driver(self.filename)\r
+            if current_driver.is_me():\r
+                #bring on all the driver, with its load of methods etc.\r
+                #so we can access the whole of it.\r
+                self.plot = current_driver.default_plots()\r
+                self.driver = current_driver\r
diff --git a/hooke/ui/gui/libhooke.py b/hooke/ui/gui/libhooke.py
new file mode 100644 (file)
index 0000000..3e48f92
--- /dev/null
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+
+'''
+libhooke.py
+
+General library of internal objects and utilities for Hooke.
+
+Copyright 2006 by Massimo Sandal (University of Bologna, Italy).
+With algorithms contributed by Francesco Musiani (University of Bologna, Italy)
+And additions contributed by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+import csv
+import os.path
+import numpy
+import scipy
+
+HOOKE_VERSION=['0.9.0_devel', 'Kenzo', '2010-01-31']
+WX_GOOD=['2.6','2.8']
+hookeDir=''
+
+#constants for 'special' curves
+#this can make it easier to understand what curve we are working on
+EXTENSION = 0
+RETRACTION = 1
+
+def coth(z):
+    '''
+    Hyperbolic cotangent.
+    '''
+    return (numpy.exp(2 * z) + 1) / (numpy.exp(2 * z) - 1)
+
+def delete_empty_lines_from_xmlfile(filename):
+    #the following 3 lines are needed to strip newlines.
+    #Otherwise, since newlines are XML elements too, the parser would read them
+    #(and re-save them, multiplying newlines...)
+    aFile=file(filename).read()
+    aFile=aFile.split('\n')
+    aFile=''.join(aFile)
+    return aFile
+
+def fit_interval_nm(start_index, x_vect, nm, backwards):
+    '''
+    Calculates the number of points to fit, given a fit interval in nm
+    start_index: index of point
+    plot: plot to use
+    backwards: if true, finds a point backwards.
+    '''
+    c = 0
+    i = start_index
+    maxlen=len(x_vect)
+    while abs(x_vect[i] - x_vect[start_index]) * (10**9) < nm:
+        if i == 0 or i == maxlen-1: #we reached boundaries of vector!
+            return c
+        if backwards:
+            i -= 1
+        else:
+            i += 1
+        c += 1
+    return c
+
+def get_file_path(filename, folders = []):
+    if os.path.dirname(filename) == '' or os.path.isabs(filename) == False:
+        path = ''
+        for folder in folders:
+            path = os.path.join(path, folder)
+        filename = os.path.join(hookeDir, path, filename)
+    return filename
+
+def remove_extension(filename):
+    '''
+    Removes the extension from a filename.
+    '''
+    name, extension = os.path.splitext(filename)
+    return name
+
+#CSV-HELPING FUNCTIONS
+def csv_write_dictionary(f, data, sorting='COLUMNS'):
+    '''
+    Writes a CSV file from a dictionary, with keys as first column or row
+    Keys are in "random" order.
+
+    Keys should be strings
+    Values should be lists or other iterables
+    '''
+    keys=data.keys()
+    values=data.values()
+    t_values=transposed2(values)
+    writer=csv.writer(f)
+
+    if sorting=='COLUMNS':
+        writer.writerow(keys)
+        for item in t_values:
+            writer.writerow(item)
+
+    if sorting=='ROWS':
+        print 'Not implemented!' #FIXME: implement it.
+
+def transposed2(lists, defval=0):
+    '''
+    transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
+    elements
+    (by Zoran Isailovski on the Python Cookbook online)
+    '''
+    if not lists: return []
+    return map(lambda *row: [elem or defval for elem in row], *lists)
+
diff --git a/hooke/ui/gui/panels/__init__.py b/hooke/ui/gui/panels/__init__.py
new file mode 100644 (file)
index 0000000..803f29c
--- /dev/null
@@ -0,0 +1 @@
+#!/usr/bin/env python\r
diff --git a/hooke/ui/gui/panels/commands.py b/hooke/ui/gui/panels/commands.py
new file mode 100644 (file)
index 0000000..52db8a4
--- /dev/null
@@ -0,0 +1,93 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+commands.py\r
+\r
+Commands and settings panel for Hooke.\r
+\r
+Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+from configobj import ConfigObj\r
+import os.path\r
+import wx\r
+\r
+import lib.libhooke as lh\r
+\r
+class Commands(wx.Panel):\r
+\r
+    def __init__(self, parent):\r
+        # Use the WANTS_CHARS style so the panel doesn't eat the Return key.\r
+        wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS|wx.NO_BORDER, size=(160, 200))\r
+\r
+        self.CommandsTree = wx.TreeCtrl(self, -1, wx.Point(0, 0), wx.Size(160, 250), wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.TR_HIDE_ROOT)\r
+        imglist = wx.ImageList(16, 16, True, 2)\r
+        imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))\r
+        imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_EXECUTABLE_FILE, wx.ART_OTHER, wx.Size(16, 16)))\r
+        self.CommandsTree.AssignImageList(imglist)\r
+        self.CommandsTree.AddRoot('Commands and Settings', 0)\r
+\r
+        self.ExecuteButton = wx.Button(self, -1, 'Execute')\r
+\r
+        sizer = wx.BoxSizer(wx.VERTICAL)\r
+        sizer.Add(self.CommandsTree, 1, wx.EXPAND)\r
+        sizer.Add(self.ExecuteButton, 0, wx.EXPAND)\r
+\r
+        self.SetSizer(sizer)\r
+        sizer.Fit(self)\r
+\r
+    def Initialize(self, plugins):\r
+        selected = None\r
+        tree_root = self.CommandsTree.GetRootItem()\r
+        path = lh.get_file_path('hooke.ini', ['config'])\r
+        config = ConfigObj()\r
+        if os.path.isfile(path):\r
+            config.filename = path\r
+            config.reload()\r
+            #get the selected command/plugin from the config file\r
+            command_str = config['command']['command']\r
+            module_str = config['command']['plugin']\r
+\r
+            #sort the plugins into alphabetical order\r
+            plugins_list = [key for key, value in plugins.iteritems()]\r
+            plugins_list.sort()\r
+            for plugin in plugins_list:\r
+                filename = ''.join([plugin, '.ini'])\r
+                path = lh.get_file_path(filename, ['plugins'])\r
+                config = ConfigObj()\r
+                if os.path.isfile(path):\r
+                    config.filename = path\r
+                    config.reload()\r
+                    #append the ini file to the plugin\r
+                    plugin_root = self.CommandsTree.AppendItem(tree_root, plugin, 0, data=wx.TreeItemData(config))\r
+                else:\r
+                    plugin_root = self.CommandsTree.AppendItem(tree_root, plugin, 0)\r
+                #select the plugin according to the config file\r
+                if plugin == module_str:\r
+                    selected = plugin_root\r
+\r
+                #add all commands to the tree\r
+                for command in plugins[plugin]:\r
+                    command_label = command.replace('do_', '')\r
+                    #do not add the ini file to the command (we'll access the ini file of the plugin (ie parent) instead, see above)\r
+                    item = self.CommandsTree.AppendItem(plugin_root, command_label, 1)\r
+                    #select the command according to the config file\r
+                    if plugin == module_str and command_label == command_str:\r
+                        selected = item\r
+                        #e = wx.MouseEvent()\r
+                        #e.SetEventType(wx.EVT_LEFT_DOWN.typeId)\r
+                        #e.SetEventObject(self.CommandsTree)\r
+\r
+                        ##e.SetSelection(page)\r
+                        #self.Parent.OnTreeCtrlCommandsLeftDown(e)\r
+                        #wx.PostEvent(self, e)\r
+\r
+                        #self.CommandsTree.SelectItem(item, True)\r
+\r
+                self.CommandsTree.Expand(plugin_root)\r
+            #make sure the selected command/plugin is visible in the tree\r
+            if selected is not None:\r
+                self.CommandsTree.SelectItem(selected, True)\r
+                self.CommandsTree.EnsureVisible(selected)\r
diff --git a/hooke/ui/gui/panels/note.py b/hooke/ui/gui/panels/note.py
new file mode 100644 (file)
index 0000000..2257d88
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+note.py\r
+\r
+Note panel for Hooke.\r
+\r
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+import wx\r
+\r
+class Note(wx.Panel):\r
+\r
+    def __init__(self, parent):\r
+        wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS|wx.NO_BORDER, size=(160, 200))\r
+\r
+        self.Editor = wx.TextCtrl(self, style=wx.TE_MULTILINE)\r
+\r
+        self.UpdateButton = wx.Button(self, -1, 'Update note')\r
+\r
+        sizer = wx.BoxSizer(wx.VERTICAL)\r
+        sizer.Add(self.Editor, 1, wx.EXPAND)\r
+        sizer.Add(self.UpdateButton, 0, wx.EXPAND)\r
+\r
+        self.SetSizer(sizer)\r
+        self.SetAutoLayout(True)\r
diff --git a/hooke/ui/gui/panels/perspectives.py b/hooke/ui/gui/panels/perspectives.py
new file mode 100644 (file)
index 0000000..75eab72
--- /dev/null
@@ -0,0 +1,73 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+perspectives.py\r
+\r
+Perspectives panel for deletion.\r
+\r
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+from os import remove\r
+import wx\r
+\r
+import lib.libhooke as lh\r
+\r
+class Perspectives(wx.Dialog):\r
+\r
+    def __init__(self, parent, ID, title):\r
+        wx.Dialog.__init__(self, parent, ID, title, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)\r
+\r
+        # contents\r
+        sizer_vertical = wx.BoxSizer(wx.VERTICAL)\r
+\r
+        message_str = "\nPlease check the perspectives\n\nyou want to delete and click 'Delete'.\n"\r
+        text = wx.StaticText(self, -1, message_str, wx.DefaultPosition, style=wx.ALIGN_CENTRE)\r
+        sizer_vertical.Add(text, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)\r
+\r
+        perspectives_list = [item[0] for item in self.Parent._perspectives.items() if item[0] != 'Default']\r
+        perspectives_list.sort()\r
+        listbox = wx.CheckListBox(self, -1, wx.DefaultPosition, wx.Size(175, 200), perspectives_list)\r
+        self.Bind(wx.EVT_CHECKLISTBOX, self.EvtCheckListBox, listbox)\r
+        listbox.SetSelection(0)\r
+        sizer_vertical.Add(listbox, 1, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)\r
+        self.listbox = listbox\r
+\r
+        horizontal_line = wx.StaticLine(self, -1, size=(20,-1), style=wx.LI_HORIZONTAL)\r
+        sizer_vertical.Add(horizontal_line, 0, wx.GROW, 5)\r
+\r
+        sizer_buttons = wx.BoxSizer(wx.HORIZONTAL)\r
+\r
+        button_delete = wx.Button(self, wx.ID_DELETE)\r
+        self.Bind(wx.EVT_BUTTON, self.OnButtonDelete, button_delete)\r
+        button_delete.SetDefault()\r
+        sizer_buttons.Add(button_delete, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)\r
+\r
+        button_close = wx.Button(self, wx.ID_CLOSE)\r
+        self.Bind(wx.EVT_BUTTON, self.OnButtonClose, button_close)\r
+        sizer_buttons.Add(button_close, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)\r
+\r
+        sizer_vertical.Add(sizer_buttons, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5)\r
+\r
+        self.SetSizer(sizer_vertical)\r
+        sizer_vertical.Fit(self)\r
+\r
+    def EvtCheckListBox(self, event):\r
+        index = event.GetSelection()\r
+        self.listbox.SetSelection(index)    # so that (un)checking also selects (moves the highlight)\r
+\r
+    def OnButtonClose(self, event):\r
+        self.EndModal(wx.ID_CLOSE)\r
+\r
+    def OnButtonDelete(self, event):\r
+        items = self.listbox.GetItems()\r
+        selected_perspective = self.Parent.config['perspectives']['active']\r
+        for index in reversed(self.listbox.GetChecked()):\r
+            self.listbox.Delete(index)\r
+            if items[index] == selected_perspective:\r
+                self.Parent.config['perspectives']['active'] = 'Default'\r
+\r
+            filename = lh.get_file_path(items[index] + '.txt', ['perspectives'])\r
+            remove(filename)\r
diff --git a/hooke/ui/gui/panels/playlist.py b/hooke/ui/gui/panels/playlist.py
new file mode 100644 (file)
index 0000000..3868a4f
--- /dev/null
@@ -0,0 +1,85 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+playlist.py\r
+\r
+Playlist panel for Hooke.\r
+\r
+Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+import wx\r
+\r
+class Playlists(wx.Panel):\r
+\r
+    def __init__(self, parent):\r
+        # Use the WANTS_CHARS style so the panel doesn't eat the Return key.\r
+        wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS|wx.NO_BORDER, size=(160, 200))\r
+\r
+        self.PlaylistsTree = wx.TreeCtrl(self, -1, wx.Point(0, 0), wx.Size(160, 250), wx.TR_DEFAULT_STYLE | wx.NO_BORDER | wx.TR_HIDE_ROOT)\r
+        imglist = wx.ImageList(16, 16, True, 2)\r
+        imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))\r
+        imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16)))\r
+        self.PlaylistsTree.AssignImageList(imglist)\r
+        self.PlaylistsTree.AddRoot('Playlists', 0)\r
+        self.PlaylistsTree.Bind(wx.EVT_RIGHT_DOWN , self.OnContextMenu)\r
+\r
+        self.Playlists = {}\r
+\r
+        sizer = wx.BoxSizer(wx.VERTICAL)\r
+        sizer.Add(self.PlaylistsTree, 1, wx.EXPAND)\r
+        self.SetSizer(sizer)\r
+        sizer.Fit(self)\r
+\r
+    def OnContextMenu(self, event):\r
+        hit_item, hit_flags = self.PlaylistsTree.HitTest(event.GetPosition())\r
+        if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0:\r
+            self.PlaylistsTree.SelectItem(hit_item)\r
+            # only do this part the first time so the events are only bound once\r
+            # Yet another alternate way to do IDs. Some prefer them up top to\r
+            # avoid clutter, some prefer them close to the object of interest\r
+            # for clarity.\r
+            if not hasattr(self, 'ID_popupAdd'):\r
+                #self.ID_popupAdd = wx.NewId()\r
+                self.ID_popupDelete = wx.NewId()\r
+                #self.Bind(wx.EVT_MENU, self.OnPopupAdd, id=self.ID_popupAdd)\r
+                self.Bind(wx.EVT_MENU, self.OnPopupDelete, id=self.ID_popupDelete)\r
+            # make a menu\r
+            menu = wx.Menu()\r
+            #items = [['Add', self.ID_popupAdd] , ['Delete', self.ID_popupDelete]]\r
+            items = [['Delete', self.ID_popupDelete]]\r
+            for item in items:\r
+                menu.Append(item[1], item[0])\r
+            # Popup the menu.  If an item is selected then its handler\r
+            # will be called before PopupMenu returns.\r
+            self.PopupMenu(menu)\r
+            menu.Destroy()\r
+\r
+    def OnPopupAdd(self, event):\r
+        pass\r
+\r
+    def OnPopupDelete(self, event):\r
+        item = self.PlaylistsTree.GetSelection()\r
+        playlist = self.Parent.GetActivePlaylist()\r
+        if self.PlaylistsTree.ItemHasChildren(item):\r
+            playlist_name = self.PlaylistsTree.GetItemText(item)\r
+            notebook = self.Parent.plotNotebook\r
+            index = self.Parent._GetPlaylistTab(playlist_name)\r
+            notebook.SetSelection(index)\r
+            notebook.DeletePage(notebook.GetSelection())\r
+            self.Parent.DeleteFromPlaylists(playlist_name)\r
+        else:\r
+            if playlist is not None:\r
+                if playlist.count == 1:\r
+                    notebook = self.Parent.plotNotebook\r
+                    index = self.Parent._GetPlaylistTab(playlist.name)\r
+                    notebook.SetSelection(index)\r
+                    notebook.DeletePage(notebook.GetSelection())\r
+                    self.Parent.DeleteFromPlaylists(playlist.name)\r
+                else:\r
+                    file_name = self.PlaylistsTree.GetItemText(item)\r
+                    playlist.delete_file(file_name)\r
+                    self.PlaylistsTree.Delete(item)\r
+                    self.Parent.UpdatePlaylistsTreeSelection()\r
diff --git a/hooke/ui/gui/panels/plot.py b/hooke/ui/gui/panels/plot.py
new file mode 100644 (file)
index 0000000..7a1515e
--- /dev/null
@@ -0,0 +1,153 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+plot.py\r
+\r
+Plot panel for Hooke.\r
+\r
+Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas\r
+\r
+from matplotlib.backends.backend_wx import NavigationToolbar2Wx\r
+\r
+from matplotlib.figure import Figure\r
+\r
+import wx\r
+\r
+#there are many comments in here from the demo app\r
+#they should come in handy to expand the functionality in the future\r
+\r
+class HookeCustomToolbar(NavigationToolbar2Wx):\r
+\r
+    def __init__(self, plotCanvas):\r
+        NavigationToolbar2Wx.__init__(self, plotCanvas)\r
+        # add new toolbar buttons\r
+        #glyph_file = 'resources' + os.sep + 'pipette.png'\r
+        #glyph = wx.Image(glyph_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()\r
+\r
+        #self.AddCheckTool(ON_CUSTOM_PICK, glyph, shortHelp='Select a data point', longHelp='Select a data point')\r
+        #wx.EVT_TOOL(self, ON_CUSTOM_PICK, self.OnSelectPoint)\r
+\r
+        # remove the unwanted button\r
+#        POSITION_OF_CONFIGURE_SUBPLOTS_BTN = 6\r
+#        self.DeleteToolByPos(POSITION_OF_CONFIGURE_SUBPLOTS_BTN)\r
+\r
+    #def OnSelectPoint(self, event):\r
+        #self.Parent.Parent.Parent.pick_active = True\r
+\r
+\r
+#class LineBuilder:\r
+    #def __init__(self, line):\r
+        #self.line = line\r
+        #self.xs = list(line.get_xdata())\r
+        #self.ys = list(line.get_ydata())\r
+        #self.cid = line.figure.canvas.mpl_connect('button_press_event', self)\r
+\r
+    #def __call__(self, event):\r
+        #print 'click', event\r
+        #if event.inaxes != self.line.axes:\r
+            #return\r
+        #self.xs.append(event.xdata)\r
+        #self.ys.append(event.ydata)\r
+        #self.line.set_data(self.xs, self.ys)\r
+        #self.line.figure.canvas.draw()\r
+\r
+\r
+class PlotPanel(wx.Panel):\r
+\r
+    def __init__(self, parent, ID):\r
+        wx.Panel.__init__(self, parent, ID, style=wx.WANTS_CHARS|wx.NO_BORDER, size=(160, 200))\r
+\r
+        self.figure = Figure()\r
+        self.canvas = FigureCanvas(self, -1, self.figure)\r
+        self.SetColor(wx.NamedColor('WHITE'))\r
+\r
+        self.sizer = wx.BoxSizer(wx.VERTICAL)\r
+        self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)\r
+        self.SetSizer(self.sizer)\r
+        self.Fit()\r
+\r
+        self.display_coordinates = False\r
+\r
+        self.figure.canvas.mpl_connect('button_press_event', self.OnClick)\r
+        self.figure.canvas.mpl_connect('axes_enter_event', self.OnEnterAxes)\r
+        self.figure.canvas.mpl_connect('axes_leave_event', self.OnLeaveAxes)\r
+        self.figure.canvas.mpl_connect('motion_notify_event', self.OnMouseMove)\r
+        self.add_toolbar()  # comment this out for no toolbar\r
+\r
+    def add_toolbar(self):\r
+        self.toolbar = HookeCustomToolbar(self.canvas)\r
+        self.toolbar.Realize()\r
+        if wx.Platform == '__WXMAC__':\r
+            # Mac platform (OSX 10.3, MacPython) does not seem to cope with\r
+            # having a toolbar in a sizer. This work-around gets the buttons\r
+            # back, but at the expense of having the toolbar at the top\r
+            self.SetToolBar(self.toolbar)\r
+        else:\r
+            # On Windows platform, default window size is incorrect, so set\r
+            # toolbar width to figure width.\r
+            tw, th = self.toolbar.GetSizeTuple()\r
+            fw, fh = self.canvas.GetSizeTuple()\r
+            # By adding toolbar in sizer, we are able to put it at the bottom\r
+            # of the frame - so appearance is closer to GTK version.\r
+            # As noted above, doesn't work for Mac.\r
+            self.toolbar.SetSize(wx.Size(fw, th))\r
+            self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)\r
+        # update the axes menu on the toolbar\r
+        self.toolbar.update()\r
+\r
+    def get_figure(self):\r
+        return self.figure\r
+\r
+    def SetColor(self, rgbtuple):\r
+        '''\r
+        Set figure and canvas colours to be the same\r
+        '''\r
+        if not rgbtuple:\r
+            rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()\r
+        col = [c / 255.0 for c in rgbtuple]\r
+        self.figure.set_facecolor(col)\r
+        self.figure.set_edgecolor(col)\r
+        self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple))\r
+\r
+    def SetStatusText(self, text, field=1):\r
+        self.Parent.Parent.statusbar.SetStatusText(text, field)\r
+\r
+    def OnClick(self, event):\r
+        #self.SetStatusText(str(event.xdata))\r
+        #print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(event.button, event.x, event.y, event.xdata, event.ydata)\r
+        pass\r
+\r
+    def OnEnterAxes(self, event):\r
+        self.display_coordinates = True\r
+\r
+    def OnLeaveAxes(self, event):\r
+        self.display_coordinates = False\r
+        self.SetStatusText('')\r
+\r
+    def OnMouseMove(self, event):\r
+        if event.guiEvent.m_shiftDown:\r
+            self.toolbar.set_cursor(2)\r
+            #print 'hand: ' + str(wx.CURSOR_HAND)\r
+            #print 'cross: ' + str(wx.CURSOR_CROSS)\r
+            #print 'ibeam: ' + str(wx.CURSOR_IBEAM)\r
+            #print 'wait: ' + str(wx.CURSOR_WAIT)\r
+            #print 'hourglass: ' + str(wx.HOURGLASS_CURSOR)\r
+        else:\r
+            self.toolbar.set_cursor(1)\r
+\r
+            #axes = self.figure.axes[0]\r
+            #line, = axes.plot([event.x - 20 , event.x + 20], [event.y - 20, event.y + 20])\r
+\r
+            #line.figure.canvas.draw()\r
+        if self.display_coordinates:\r
+            coordinateString = ''.join(['x: ', str(event.xdata), ' y: ', str(event.ydata)])\r
+            #TODO: pretty format\r
+            self.SetStatusText(coordinateString)\r
+\r
+    def OnPaint(self, event):\r
+        self.canvas.draw()\r
diff --git a/hooke/ui/gui/panels/propertyeditor.py b/hooke/ui/gui/panels/propertyeditor.py
new file mode 100644 (file)
index 0000000..40eecaf
--- /dev/null
@@ -0,0 +1,515 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+propertyeditor.py\r
+\r
+Property editor panel for Hooke.\r
+\r
+Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+import sys\r
+import os.path\r
+\r
+import wx\r
+import wx.propgrid as wxpg\r
+#import wx.stc\r
+from string import split\r
+\r
+#there are many comments and code fragments in here from the demo app\r
+#they should come in handy to expand the functionality in the future\r
+\r
+class Display:\r
+    property_descriptor = []\r
+    def __init__(self):\r
+        pass\r
+\r
+\r
+class ValueObject:\r
+    def __init__(self):\r
+        pass\r
+\r
+\r
+class IntProperty2(wxpg.PyProperty):\r
+    """\\r
+    This is a simple re-implementation of wxIntProperty.\r
+    """\r
+    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=0):\r
+        wxpg.PyProperty.__init__(self, label, name)\r
+        self.SetValue(value)\r
+\r
+    def GetClassName(self):\r
+        """\\r
+        This is not 100% necessary and in future is probably going to be\r
+        automated to return class name.\r
+        """\r
+        return "IntProperty2"\r
+\r
+    def GetEditor(self):\r
+        return "TextCtrl"\r
+\r
+    def GetValueAsString(self, flags):\r
+        return str(self.GetValue())\r
+\r
+    def PyStringToValue(self, s, flags):\r
+        try:\r
+            v = int(s)\r
+            if self.GetValue() != v:\r
+                return v\r
+        except TypeError:\r
+            if flags & wxpg.PG_REPORT_ERROR:\r
+                wx.MessageBox("Cannot convert '%s' into a number."%s, "Error")\r
+        return False\r
+\r
+    def PyIntToValue(self, v, flags):\r
+        if (self.GetValue() != v):\r
+            return v\r
+\r
+\r
+class PyFilesProperty(wxpg.PyArrayStringProperty):\r
+    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=[]):\r
+        wxpg.PyArrayStringProperty.__init__(self, label, name, value)\r
+        self.SetValue(value)\r
+\r
+    def OnSetValue(self, v):\r
+        self.value = v\r
+        self.display = ', '.join(self.value)\r
+\r
+    def GetValueAsString(self, argFlags):\r
+        return self.display\r
+\r
+    def PyStringToValue(self, s, flags):\r
+        return [a.strip() for a in s.split(',')]\r
+\r
+    def OnEvent(self, propgrid, ctrl, event):\r
+        if event.GetEventType() == wx.wxEVT_COMMAND_BUTTON_CLICKED:\r
+            # Show dialog to select a string, call DoSetValue and\r
+            # return True, if value changed.\r
+            return True\r
+\r
+        return False\r
+\r
+\r
+class PyObjectPropertyValue:\r
+    """\\r
+    Value type of our sample PyObjectProperty. We keep a simple dash-delimited\r
+    list of string given as argument to constructor.\r
+    """\r
+    def __init__(self, s=None):\r
+        try:\r
+            self.ls = [a.strip() for a in s.split('-')]\r
+        except:\r
+            self.ls = []\r
+\r
+    def __repr__(self):\r
+        return ' - '.join(self.ls)\r
+\r
+\r
+class PyObjectProperty(wxpg.PyProperty):\r
+    """\\r
+    Another simple example. This time our value is a PyObject (NOTE: we can't\r
+    return an arbitrary python object in DoGetValue. It cannot be a simple\r
+    type such as int, bool, double, or string, nor an array or wxObject based.\r
+    Dictionary, None, or any user-specified Python object is allowed).\r
+    """\r
+    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=None):\r
+        wxpg.PyProperty.__init__(self, label, name)\r
+        self.SetValue(value)\r
+\r
+    def GetClassName(self):\r
+        return self.__class__.__name__\r
+\r
+    def GetEditor(self):\r
+        return "TextCtrl"\r
+\r
+    def GetValueAsString(self, flags):\r
+        return repr(self.GetValue())\r
+\r
+    def PyStringToValue(self, s, flags):\r
+        return PyObjectPropertyValue(s)\r
+\r
+\r
+class ShapeProperty(wxpg.PyEnumProperty):\r
+    """\\r
+    Demonstrates use of OnCustomPaint method.\r
+    """\r
+    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=-1):\r
+        wxpg.PyEnumProperty.__init__(self, label, name, ['Line','Circle','Rectangle'], [0,1,2], value)\r
+\r
+    def OnMeasureImage(self, index):\r
+        return wxpg.DEFAULT_IMAGE_SIZE\r
+\r
+    def OnCustomPaint(self, dc, rect, paint_data):\r
+        """\\r
+        paint_data.m_choiceItem is -1 if we are painting the control,\r
+        in which case we need to get the drawn item using DoGetValue.\r
+        """\r
+        item = paint_data.m_choiceItem\r
+        if item == -1:\r
+            item = self.DoGetValue()\r
+\r
+        dc.SetPen(wx.Pen(wx.BLACK))\r
+        dc.SetBrush(wx.Brush(wx.BLACK))\r
+\r
+        if item == 0:\r
+            dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height)\r
+        elif item == 1:\r
+            half_width = rect.width / 2\r
+            dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3)\r
+        elif item == 2:\r
+            dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)\r
+\r
+\r
+class LargeImagePickerCtrl(wx.Window):\r
+    """\\r
+    Control created and used by LargeImageEditor.\r
+    """\r
+    def __init__(self):\r
+        pre = wx.PreWindow()\r
+        self.PostCreate(pre)\r
+\r
+    def Create(self, parent, id_, pos, size, style = 0):\r
+        wx.Window.Create(self, parent, id_, pos, size, style | wx.BORDER_SIMPLE)\r
+        img_spc = size[1]\r
+        self.tc = wx.TextCtrl(self, -1, "", (img_spc,0), (2048,size[1]), wx.BORDER_NONE)\r
+        self.SetBackgroundColour(wx.WHITE)\r
+        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)\r
+        self.property = None\r
+        self.bmp = None\r
+        self.Bind(wx.EVT_PAINT, self.OnPaint)\r
+\r
+    def OnPaint(self, event):\r
+        dc = wx.BufferedPaintDC(self)\r
+\r
+        whiteBrush = wx.Brush(wx.WHITE)\r
+        dc.SetBackground(whiteBrush)\r
+        dc.Clear()\r
+\r
+        bmp = self.bmp\r
+        if bmp:\r
+            dc.DrawBitmap(bmp, 2, 2)\r
+        else:\r
+            dc.SetPen(wx.Pen(wx.BLACK))\r
+            dc.SetBrush(whiteBrush)\r
+            dc.DrawRectangle(2, 2, 64, 64)\r
+\r
+    def RefreshThumbnail(self):\r
+        """\\r
+        We use here very simple image scaling code.\r
+        """\r
+        if not self.property:\r
+            self.bmp = None\r
+            return\r
+\r
+        path = self.property.DoGetValue()\r
+\r
+        if not os.path.isfile(path):\r
+            self.bmp = None\r
+            return\r
+\r
+        image = wx.Image(path)\r
+        image.Rescale(64, 64)\r
+        self.bmp = wx.BitmapFromImage(image)\r
+\r
+    def SetProperty(self, property):\r
+        self.property = property\r
+        self.tc.SetValue(property.GetDisplayedString())\r
+        self.RefreshThumbnail()\r
+\r
+    def SetValue(self, s):\r
+        self.RefreshThumbnail()\r
+        self.tc.SetValue(s)\r
+\r
+    def GetLastPosition(self):\r
+        return self.tc.GetLastPosition()\r
+\r
+\r
+class LargeImageEditor(wxpg.PyEditor):\r
+    """\\r
+    Double-height text-editor with image in front.\r
+    """\r
+    def __init__(self):\r
+        wxpg.PyEditor.__init__(self)\r
+\r
+    def CreateControls(self, propgrid, property, pos, sz):\r
+        try:\r
+            h = 64 + 6\r
+            x = propgrid.GetSplitterPosition()\r
+            x2 = propgrid.GetClientSize().x\r
+            bw = propgrid.GetRowHeight()\r
+            lipc = LargeImagePickerCtrl()\r
+            if sys.platform == 'win32':\r
+                lipc.Hide()\r
+            lipc.Create(propgrid, wxpg.PG_SUBID1, (x,pos[1]), (x2-x-bw,h))\r
+            lipc.SetProperty(property)\r
+            # Hmmm.. how to have two-stage creation without subclassing?\r
+            #btn = wx.PreButton()\r
+            #pre = wx.PreWindow()\r
+            #self.PostCreate(pre)\r
+            #if sys.platform == 'win32':\r
+            #    btn.Hide()\r
+            #btn.Create(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
+            btn = wx.Button(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
+            return (lipc, btn)\r
+        except:\r
+            import traceback\r
+            print traceback.print_exc()\r
+\r
+    def UpdateControl(self, property, ctrl):\r
+        ctrl.SetValue(property.GetDisplayedString())\r
+\r
+    def DrawValue(self, dc, property, rect):\r
+        if not (property.GetFlags() & wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
+            dc.DrawText( property.GetDisplayedString(), rect.x+5, rect.y );\r
+\r
+    def OnEvent(self, propgrid, ctrl, event):\r
+        if not ctrl:\r
+            return False\r
+\r
+        evtType = event.GetEventType()\r
+\r
+        if evtType == wx.wxEVT_COMMAND_TEXT_ENTER:\r
+            if propgrid.IsEditorsValueModified():\r
+                return True\r
+\r
+        elif evtType == wx.wxEVT_COMMAND_TEXT_UPDATED:\r
+            if not property.HasFlag(wxpg.PG_PROP_AUTO_UNSPECIFIED) or not ctrl or \\r
+               ctrl.GetLastPosition() > 0:\r
+\r
+                # We must check this since an 'empty' text event\r
+                # may be triggered when creating the property.\r
+                PG_FL_IN_SELECT_PROPERTY = 0x00100000\r
+                if not (propgrid.GetInternalFlags() & PG_FL_IN_SELECT_PROPERTY):\r
+                    event.Skip();\r
+                    event.SetId(propgrid.GetId());\r
+\r
+                propgrid.EditorsValueWasModified();\r
+\r
+        return False\r
+\r
+\r
+    def CopyValueFromControl(self, property, ctrl):\r
+        tc = ctrl.tc\r
+        res = property.SetValueFromString(tc.GetValue(),0)\r
+        # Changing unspecified always causes event (returning\r
+        # true here should be enough to trigger it).\r
+        if not res and property.IsFlagSet(wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
+            res = True\r
+\r
+        return res\r
+\r
+    def SetValueToUnspecified(self, ctrl):\r
+        ctrl.tc.Remove(0,len(ctrl.tc.GetValue()));\r
+\r
+    def SetControlStringValue(self, ctrl, txt):\r
+        ctrl.SetValue(txt)\r
+\r
+    def OnFocus(self, property, ctrl):\r
+        ctrl.tc.SetSelection(-1,-1)\r
+        ctrl.tc.SetFocus()\r
+\r
+\r
+class PropertyEditor(wx.Panel):\r
+\r
+    def __init__(self, parent):\r
+        # Use the WANTS_CHARS style so the panel doesn't eat the Return key.\r
+        wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS, size=(160, 200))\r
+\r
+        sizer = wx.BoxSizer(wx.VERTICAL)\r
+\r
+        self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT)\r
+\r
+        # Show help as tooltips\r
+        self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)\r
+\r
+        #pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChange)\r
+        #pg.Bind(wxpg.EVT_PG_SELECTED, self.OnPropGridSelect)\r
+        #self.pg.Bind(wxpg.EVT_PG_RIGHT_CLICK, self.OnPropGridRightClick)\r
+\r
+        # Needed by custom image editor\r
+        wx.InitAllImageHandlers()\r
+\r
+        #\r
+        # Let's create a simple custom editor\r
+        #\r
+        # NOTE: Editor must be registered *before* adding a property that uses it.\r
+        self.pg.RegisterEditor(LargeImageEditor)\r
+\r
+        '''\r
+        #\r
+        # Add properties\r
+        #\r
+\r
+        pg.Append( wxpg.PropertyCategory("1 - Basic Properties") )\r
+        pg.Append( wxpg.StringProperty("String",value="Some Text") )\r
+        pg.Append( wxpg.IntProperty("Int",value=100) )\r
+        pg.Append( wxpg.FloatProperty("Float",value=100.0) )\r
+        pg.Append( wxpg.BoolProperty("Bool",value=True) )\r
+        pg.Append( wxpg.BoolProperty("Bool_with_Checkbox",value=True) )\r
+        pg.SetPropertyAttribute("Bool_with_Checkbox", "UseCheckbox", True)\r
+\r
+        pg.Append( wxpg.PropertyCategory("2 - More Properties") )\r
+        pg.Append( wxpg.LongStringProperty("LongString",value="This is a\\nmulti-line string\\nwith\\ttabs\\nmixed\\tin.") )\r
+        pg.Append( wxpg.DirProperty("Dir",value="C:\\Windows") )\r
+        pg.Append( wxpg.FileProperty("File",value="C:\\Windows\\system.ini") )\r
+        pg.Append( wxpg.ArrayStringProperty("ArrayString",value=['A','B','C']) )\r
+\r
+        pg.Append( wxpg.EnumProperty("Enum","Enum",\r
+                                     ['wxPython Rules','wxPython Rocks','wxPython Is The Best'],\r
+                                     [10,11,12],0) )\r
+        pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") )\r
+\r
+        pg.Append( wxpg.PropertyCategory("3 - Advanced Properties") )\r
+        pg.Append( wxpg.DateProperty("Date",value=wx.DateTime_Now()) )\r
+        pg.Append( wxpg.FontProperty("Font",value=self.GetFont()) )\r
+        pg.Append( wxpg.ColourProperty("Colour",value=self.GetBackgroundColour()) )\r
+        pg.Append( wxpg.SystemColourProperty("SystemColour") )\r
+        pg.Append( wxpg.ImageFileProperty("ImageFile") )\r
+        pg.Append( wxpg.MultiChoiceProperty("MultiChoice",choices=['wxWidgets','QT','GTK+']) )\r
+\r
+        pg.Append( wxpg.PropertyCategory("4 - Additional Properties") )\r
+        pg.Append( wxpg.PointProperty("Point",value=self.GetPosition()) )\r
+        pg.Append( wxpg.SizeProperty("Size",value=self.GetSize()) )\r
+        pg.Append( wxpg.FontDataProperty("FontData") )\r
+        pg.Append( wxpg.IntProperty("IntWithSpin",value=256) )\r
+        pg.SetPropertyEditor("IntWithSpin","SpinCtrl")\r
+        pg.Append( wxpg.DirsProperty("Dirs",value=['C:/Lib','C:/Bin']) )\r
+        pg.SetPropertyHelpString( "String", "String Property help string!" )\r
+        pg.SetPropertyHelpString( "Dirs", "Dirs Property help string!" )\r
+\r
+        pg.SetPropertyAttribute( "File", wxpg.PG_FILE_SHOW_FULL_PATH, 0 )\r
+        pg.SetPropertyAttribute( "File", wxpg.PG_FILE_INITIAL_PATH, "C:\\Program Files\\Internet Explorer" )\r
+        pg.SetPropertyAttribute( "Date", wxpg.PG_DATE_PICKER_STYLE, wx.DP_DROPDOWN|wx.DP_SHOWCENTURY )\r
+\r
+        pg.Append( wxpg.PropertyCategory("5 - Custom Properties") )\r
+        pg.Append( IntProperty2("IntProperty2", value=1024) )\r
+\r
+        pg.Append( ShapeProperty("ShapeProperty", value=0) )\r
+        pg.Append( PyObjectProperty("PyObjectProperty") )\r
+\r
+        pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") )\r
+        pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor")\r
+\r
+\r
+        pg.SetPropertyClientData( "Point", 1234 )\r
+        if pg.GetPropertyClientData( "Point" ) != 1234:\r
+            raise ValueError("Set/GetPropertyClientData() failed")\r
+\r
+        # Test setting unicode string\r
+        pg.GetPropertyByName("String").SetValue(u"Some Unicode Text")\r
+\r
+        #\r
+        # Test some code that *should* fail (but not crash)\r
+        #try:\r
+            #a_ = pg.GetPropertyValue( "NotARealProperty" )\r
+            #pg.EnableProperty( "NotAtAllRealProperty", False )\r
+            #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" )\r
+        #except:\r
+            #pass\r
+            #raise\r
+\r
+        '''\r
+        sizer.Add(self.pg, 1, wx.EXPAND)\r
+        self.SetSizer(sizer)\r
+        sizer.SetSizeHints(self)\r
+\r
+        self.SelectedTreeItem = None\r
+\r
+    def GetPropertyValues(self):\r
+        return self.pg.GetPropertyValues()\r
+\r
+    def Initialize(self, properties):\r
+        pg = self.pg\r
+        pg.Clear()\r
+\r
+        if properties:\r
+            for element in properties:\r
+                if element[1]['type'] == 'arraystring':\r
+                    elements = element[1]['elements']\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1]['value']\r
+                    else:\r
+                        property_value = element[1]['default']\r
+                    #retrieve individual strings\r
+                    property_value = split(property_value, ' ')\r
+                    #remove " delimiters\r
+                    values = [value.strip('"') for value in property_value]\r
+                    pg.Append(wxpg.ArrayStringProperty(element[0], value=values))\r
+\r
+                if element[1]['type'] == 'boolean':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1].as_bool('value')\r
+                    else:\r
+                        property_value = element[1].as_bool('default')\r
+                    property_control = wxpg.BoolProperty(element[0], value=property_value)\r
+                    pg.Append(property_control)\r
+                    pg.SetPropertyAttribute(element[0], 'UseCheckbox', True)\r
+\r
+                #if element[0] == 'category':\r
+                    #pg.Append(wxpg.PropertyCategory(element[1]))\r
+\r
+                if element[1]['type'] == 'color':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1]['value']\r
+                    else:\r
+                        property_value = element[1]['default']\r
+                    property_value = eval(property_value)\r
+                    pg.Append(wxpg.ColourProperty(element[0], value=property_value))\r
+\r
+                if element[1]['type'] == 'enum':\r
+                    elements = element[1]['elements']\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1]['value']\r
+                    else:\r
+                        property_value = element[1]['default']\r
+                    pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value)))\r
+\r
+                if element[1]['type'] == 'filename':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1]['value']\r
+                    else:\r
+                        property_value = element[1]['default']\r
+                    pg.Append(wxpg.FileProperty(element[0], value=property_value))\r
+\r
+                if element[1]['type'] == 'float':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1].as_float('value')\r
+                    else:\r
+                        property_value = element[1].as_float('default')\r
+                    property_control = wxpg.FloatProperty(element[0], value=property_value)\r
+                    pg.Append(property_control)\r
+\r
+                if element[1]['type'] == 'folder':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1]['value']\r
+                    else:\r
+                        property_value = element[1]['default']\r
+                    pg.Append(wxpg.DirProperty(element[0], value=property_value))\r
+\r
+                if element[1]['type'] == 'integer':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1].as_int('value')\r
+                    else:\r
+                        property_value = element[1].as_int('default')\r
+                    property_control = wxpg.IntProperty(element[0], value=property_value)\r
+                    if 'maximum' in element[1]:\r
+                        property_control.SetAttribute('Max', element[1].as_int('maximum'))\r
+                    if 'minimum' in element[1]:\r
+                        property_control.SetAttribute('Min', element[1].as_int('minimum'))\r
+                    property_control.SetAttribute('Wrap', True)\r
+                    pg.Append(property_control)\r
+                    pg.SetPropertyEditor(element[0], 'SpinCtrl')\r
+\r
+                if element[1]['type'] == 'string':\r
+                    if 'value' in element[1]:\r
+                        property_value = element[1]['value']\r
+                    else:\r
+                        property_value = element[1]['default']\r
+                    pg.Append(wxpg.StringProperty(element[0], value=property_value))\r
+\r
+        pg.Refresh()\r
+\r
+    def OnReserved(self, event):\r
+        pass\r
diff --git a/hooke/ui/gui/panels/results.py b/hooke/ui/gui/panels/results.py
new file mode 100644 (file)
index 0000000..c53de11
--- /dev/null
@@ -0,0 +1,83 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+results.py\r
+\r
+Fitting results panel for Hooke.\r
+\r
+Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+import sys\r
+import wx\r
+from wx.lib.mixins.listctrl import CheckListCtrlMixin\r
+\r
+class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin):\r
+    def __init__(self, parent):\r
+        wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT)\r
+        CheckListCtrlMixin.__init__(self)\r
+        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)\r
+\r
+    def OnItemActivated(self, evt):\r
+        self.ToggleItem(evt.m_itemIndex)\r
+\r
+\r
+class Results(wx.Panel):\r
+    def __init__(self, parent):\r
+        wx.Panel.__init__(self, parent, -1)\r
+        self.results_list = CheckListCtrl(self)\r
+        sizer = wx.BoxSizer()\r
+        sizer.Add(self.results_list, 1, wx.EXPAND)\r
+        self.SetSizer(sizer)\r
+        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.results_list)\r
+        self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self.results_list)\r
+\r
+    def _GetWidthInPixels(self, text):\r
+        #TODO:\r
+        #Returns the width of a string in pixels\r
+        #Unfortunately, it does not work terribly well (although it should).\r
+        #Thus, we have to add a bit afterwards.\r
+        #Annoys the heck out of me (illysam).\r
+        font = self.results_list.GetFont()\r
+        dc = wx.WindowDC(self.results_list)\r
+        dc.SetFont(font)\r
+        width, height = dc.GetTextExtent(text)\r
+        return width\r
+\r
+    def ClearResults(self):\r
+        self.results_list.ClearAll()\r
+\r
+    def DisplayResults(self, results):\r
+        self.ClearResults()\r
+        header = results.get_header_as_list()\r
+        self.results_list.InsertColumn(0, 'Show')\r
+        for index, column in enumerate(header):\r
+            self.results_list.InsertColumn(index + 1, column, wx.LIST_FORMAT_RIGHT)\r
+\r
+        for result in results.results:\r
+            done = False\r
+            for index, column in enumerate(results.columns):\r
+                value_str = results.get_pretty_value(column, result.result[column])\r
+                if not done:\r
+                    index_col = self.results_list.InsertStringItem(sys.maxint, '')\r
+                    done = True\r
+                column_width = len(self.results_list.GetColumn(index + 1).GetText())\r
+                value_str = value_str.center(column_width)\r
+                self.results_list.SetStringItem(index_col, index + 1, value_str)\r
+\r
+        for index, result in enumerate(results.results):\r
+            if result.visible:\r
+                #if we use 'CheckItem' then 'UpdatePlot' is called (ie repeated updates)\r
+                self.results_list.SetItemImage(index, 1)\r
+        for index in range(self.results_list.GetColumnCount()):\r
+            column_text = self.results_list.GetColumn(index).GetText()\r
+            column_width = self._GetWidthInPixels(column_text)\r
+            self.results_list.SetColumnWidth(index, column_width + 15)\r
+\r
+    def OnItemSelected(self, evt):\r
+        pass\r
+\r
+    def OnItemDeselected(self, evt):\r
+        pass\r
diff --git a/hooke/ui/gui/peakspot.py b/hooke/ui/gui/peakspot.py
new file mode 100644 (file)
index 0000000..1a715ac
--- /dev/null
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+
+'''
+peakspot.py
+
+A library of helping functions for spotting force spectroscopy peaks.
+
+Copyright 2007 by Fabrizio Benedetti and Massimo Sandal
+
+This program is released under the GNU General Public License version 2.
+'''
+
+from numpy import mean
+
+def conv_dx(data,vect):
+    '''
+    Returns the right-centered convolution of data with vector vect
+    '''
+    dim=len(data)
+    window=len(vect)
+    temparr=[0.0]*dim
+
+    end=dim-window
+
+    for j in range(end):
+        for k in range(window):
+            temparr[j]+=data[j+k]*vect[k]
+
+    return temparr
+
+def absdev(arr):
+    '''
+    Calculates the absolute deviation of a vector
+    '''
+    med=0.0
+    absD=0.0
+
+    med=mean(arr)
+    for j in arr:
+        absD+=abs(j-med)
+    return absD/len(arr)
+
+def noise_absdev(data,positive=False,maxcut=0.2,stable=0.005):
+    '''
+    Returns the standard deviation of the noise.
+    The logic is: we cut the most negative (or positive) data points until the absolute deviation
+    becomes stable (it doesn't vary more than 0.005) or we have cut more than maxcut*len(data) points.
+    Then calculate the absolute deviation.
+
+    If positive=True we cut the most positive data points, False=we cut the negative ones.
+    '''
+    #TOD: check if this is necessary
+    out=[item for item in data] #we copy just to be sure...
+    out.sort()
+    if positive:
+        out.reverse()
+
+    temp_absdev=absdev(out)
+
+    for index in range(len(out)):
+        cutindex=(index+1)*5
+        cut_absdev=absdev(out[cutindex:]) #we jump five points after five points...
+        if 1-(cut_absdev/temp_absdev) < stable and cutindex<(maxcut*len(out)):
+            temp_absdev=cut_absdev
+        else:
+            break
+
+    return cut_absdev
+
+def abovenoise(convoluted,noise_level,cut_index=0,abs_devs=4):
+    '''
+    Generates a vector which is 0 where the vector is less than abs_devs*noise_level ; 1 if not (spike).
+    '''
+    #calculate absolute noise deviation
+    #noise_level=noise_absdev(convoluted[cut_index:])
+
+    above=[]
+
+    for index in range(len(convoluted)):
+        if index<cut_index:
+            above.append(0)
+        else:
+            #FIXME: should calculate the *average* (here we assume that convolution mean is 0, which is FALSE!)
+            if convoluted[index] < -1*noise_level*abs_devs:
+                above.append(convoluted[index])
+            else:
+                above.append(0)
+    return above
+
+def find_peaks(above, seedouble=10):
+    '''
+    Finds individual peak location.
+    abovenoise() finds all points above a given threshold in the convolution. This point is often not unique
+    but there is a "cluster" of points above a threshold.
+    Here we obtain only the coordinates of the largest *convolution* spike for each cluster.
+
+    above=vector obtained from abovenoise()
+    seedouble=value at which we want to "delete" double peaks. That is, if two peaks have a distance
+    < than $seedouble points , only the first is kept.
+    '''
+    nonzero=[]
+    peaks_location=[]
+    peaks_size=[]
+
+    for index in range(len(above)):
+        if above[index] != 0:
+            nonzero.append(index)
+        else:
+            if len(nonzero) != 0:
+                if len(nonzero)==1:
+                    nonzero.append(nonzero[0]+1)
+                peaks_size.append(min(above[nonzero[0]:nonzero[-1]]))
+                peaks_location.append(above[nonzero[0]:nonzero[-1]].index(peaks_size[-1])+nonzero[0])
+                nonzero=[]
+            else:
+                pass
+
+    #recursively eliminate double peaks
+    #(maybe not the smartest of cycles, but i'm asleep...)
+    temp_location=None
+    while temp_location != []:
+        temp_location=[]
+        if len(peaks_location)>1:
+            for index in range(len(peaks_location)-1):
+                if peaks_location[index+1]-peaks_location[index] < seedouble:
+                    temp_location=peaks_location[:index]+peaks_location[index+1:]
+        if temp_location != []:
+            peaks_location=temp_location
+
+    return peaks_location,peaks_size
\ No newline at end of file
diff --git a/hooke/ui/gui/playlist.py b/hooke/ui/gui/playlist.py
new file mode 100644 (file)
index 0000000..90a730f
--- /dev/null
@@ -0,0 +1,182 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+playlist.py\r
+\r
+Playlist class for Hooke.\r
+\r
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+import os.path\r
+import xml.dom.minidom\r
+\r
+import lib.libhooke\r
+import lib.file\r
+\r
+class Playlist(object):\r
+\r
+    def __init__(self, filename=None):\r
+        self._saved = False\r
+        self.count = 0\r
+        self.figure = None\r
+        self.files = []\r
+        self.generics_dict = {}\r
+        self.hidden_attributes = ['curve', 'data', 'driver', 'fits', 'name', 'plot', 'plots']\r
+        self.index = -1\r
+        self.name = None\r
+        self.path = None\r
+        self.plot_panel = None\r
+        self.plot_tab = None\r
+        if filename is None:\r
+            self.filename = None\r
+        else:\r
+            self.load(filename)\r
+\r
+    def add_file(self, filename):\r
+        if os.path.isfile(filename):\r
+            file_to_add = lib.file.File(filename)\r
+            self.files.append(file_to_add)\r
+            self._saved = False\r
+        self.count = len(self.files)\r
+\r
+    def delete_file(self, name):\r
+        for index, item in enumerate(self.files):\r
+            if item.name == name:\r
+                del self.files[index]\r
+                self.index = index\r
+        self.count = len(self.files)\r
+        if self.index > self.count - 1:\r
+            self.index = 0\r
+\r
+    def filter_curves(self, curves_to_keep=[]):\r
+        playlist = Playlist()\r
+        for curve_index in curves_to_keep:\r
+            playlist.files.append(self.files[curve_index])\r
+        playlist.count = len(playlist.files)\r
+        playlist.index = 0\r
+        return playlist\r
+\r
+    def get_active_file(self):\r
+        return self.files[self.index]\r
+\r
+    #def get_active_plot(self):\r
+        ##TODO: is this the only active (or default?) plot?\r
+        #return self.files[self.index].plots[0]\r
+\r
+    def get_status_string(self):\r
+        if self.count > 0:\r
+            activeFileStr = ''.join([' (%s/%s)' %(self.index + 1, self.count)])\r
+            return ''.join([self.name, activeFileStr])\r
+        else:\r
+            return ''.join(['The file ', self.name, ' does not contain any valid force curve data.\n'])\r
+\r
+    def load(self, filename):\r
+        '''\r
+        Loads a playlist file\r
+        '''\r
+        self.filename = filename\r
+        self.path, self.name = os.path.split(filename)\r
+        playlist_file = lib.libhooke.delete_empty_lines_from_xmlfile(filename)\r
+        self.xml = xml.dom.minidom.parseString(playlist_file)\r
+        #TODO: rename 'element' to 'curve' or something in hkp file\r
+        #TODO: rename 'path' to 'filename'\r
+        #TODO: switch from attributes to nodes, it's cleaner XML in my eyes\r
+\r
+        element_list = self.xml.getElementsByTagName('element')\r
+        #populate playlist with files\r
+        for index, element in enumerate(element_list):\r
+            #rebuild a data structure from the xml attributes\r
+            #the next two lines are here for backwards compatibility, newer playlist files use 'filename' instead of 'path'\r
+            if element.hasAttribute('path'):\r
+                #path, name = os.path.split(element.getAttribute('path'))\r
+                #path = path.split(os.sep)\r
+                #filename = lib.libhooke.get_file_path(name, path)\r
+                filename = element.getAttribute('path')\r
+            if element.hasAttribute('filename'):\r
+                #path, name = os.path.split(element.getAttribute('filename'))\r
+                #path = path.split(os.sep)\r
+                #filename = lib.libhooke.get_file_path(name, path)\r
+                filename = element.getAttribute('filename')\r
+            if os.path.isfile(filename):\r
+                data_file = lib.file.File(filename)\r
+                if element.hasAttribute('note'):\r
+                    data_file.note = element.getAttribute('note')\r
+                self.files.append(data_file)\r
+        self.count = len(self.files)\r
+        if self.count > 0:\r
+            #populate generics\r
+            genericsDict = {}\r
+            generics_list = self.xml.getElementsByTagName('generics')\r
+            if generics_list:\r
+                for attribute in generics_list[0].attributes.keys():\r
+                    genericsDict[attribute] = generics_list[0].getAttribute(attribute)\r
+            if genericsDict.has_key('pointer'):\r
+                index = int(genericsDict['pointer'])\r
+                if index >= 0 and index < self.count:\r
+                    self.index = index\r
+                else:\r
+                    index = 0\r
+            self._saved = True\r
+\r
+    def next(self):\r
+        self.index += 1\r
+        if self.index > self.count - 1:\r
+            self.index = 0\r
+\r
+    def previous(self):\r
+        self.index -= 1\r
+        if self.index < 0:\r
+            self.index = self.count - 1\r
+\r
+    def reset(self):\r
+        if self.count > 0:\r
+            self.index = 0\r
+        else:\r
+            self.index = -1\r
+\r
+    def save(self, filename):\r
+        '''\r
+        Saves a playlist from a list of files.\r
+        A playlist is an XML document with the following syntax:\r
+        <playlist>\r
+        <element path="/my/file/path/"/ attribute="attribute">\r
+        <element path="...">\r
+        </playlist>\r
+        '''\r
+        try:\r
+            output_file = file(filename, 'w')\r
+        except IOError:\r
+            self.AppendToOutput('Cannot save playlist. Wrong path or filename')\r
+            return\r
+        #create the output playlist, a simple XML document\r
+        implementation = xml.dom.minidom.getDOMImplementation()\r
+        #create the document DOM object and the root element\r
+        self.xml = implementation.createDocument(None, 'playlist', None)\r
+        root = self.xml.documentElement\r
+\r
+        #save generics variables\r
+        playlist_generics = self.xml.createElement('generics')\r
+        root.appendChild(playlist_generics)\r
+        self.generics_dict['pointer'] = self.index\r
+        for key in self.generics_dict.keys():\r
+        #for key in generics.keys():\r
+            self.xml.createAttribute(key)\r
+            playlist_generics.setAttribute(key, str(self.generics_dict[key]))\r
+\r
+        #save files and their attributes\r
+        for item in self.files:\r
+            #playlist_element=newdoc.createElement("file")\r
+            playlist_element = self.xml.createElement('element')\r
+            root.appendChild(playlist_element)\r
+            for key in item.__dict__:\r
+                if not (key in self.hidden_attributes):\r
+                    self.xml.createAttribute(key)\r
+                    playlist_element.setAttribute(key, str(item.__dict__[key]))\r
+        self._saved = False\r
+\r
+        self.xml.writexml(output_file, indent='\n')\r
+        output_file.close()\r
+        self._saved = True\r
index 3aedbc54c0918aefa9fd8437388c63d9577309a2..cfad7211e79e543f170dbe79640f7568956d1d10 100644 (file)
-# 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/>.
-
-class PlotObject(object):
-
-    def __init__(self):
-
-        '''
-        the plot destination
-        0=top
-        1=bottom
-        '''
-        self.destination=0
-
-        '''
-        self.vectors is a multidimensional array:
-        self.vectors[0]=plot1
-        self.vectors[1]=plot2
-        self.vectors[2]=plot3
-        etc.
-
-        2 curves in a x,y plot are:
-        [[[x1],[y1]],[[x2],[y2]]]
-        for example:
-            x1          y1              x2         y2
-        [[[1,2,3,4],[10,20,30,40]],[[3,6,9,12],[30,60,90,120]]]
-        x1 = self.vectors[0][0]
-        y1 = self.vectors[0][1]
-        x2 = self.vectors[1][0]
-        y2 = self.vectors[1][1]
-        '''
-        self.vectors=[]
-
-        '''
-        self.units is simpler. for each plot with N axes (x,y,z...) only N labels
-        can be made, regardless of the number of superimposed plots
-        so units for the double plot above is: [unitx, unity]
-
-        units are strings
-        '''
-        self.units=['', '']
-
-        '''
-        xaxes and yaxes directions. 0, 0 means the common +X=right, +Y=top directions
-        '''
-        self.xaxes = 0
-        self.yaxes = 0
-
-        self.filename = ''
-        self.title = '' #title
-
-        '''
-        styles: defines what is the style of the current plots. If undefined or None, it is line plot.
-        If an element of the list is 'scatter', the corresponding dataset
-        is drawn with scattered points and not a continuous line.
-        '''
-        self.styles = []
-
-        '''
-        colors: define what is the colour of the current plots
-        '''
-        self.colors = []
-
-    def add_set(self, x, y):
-        '''
-        Adds an x, y data set to the vectors.
-        '''
-        self.vectors.append([])
-        self.vectors[-1].append(x)
-        self.vectors[-1].append(y)
-
-    def remove_set(self, whichset):
-        '''
-        Removes a set
-        '''
-        #TODO: do we need 'waste' here?
-        waste = self.vectors.pop(whichset)
-
-    def normalize_vectors(self):
-        '''
-        Trims the vector lengths as to be equal in a plot.
-        '''
-        for index in range(0,len(self.vectors)):
-            vectors_to_plot=self.vectors[index]
-            lengths=[len(vector) for vector in vectors_to_plot]
-            if min(lengths) != max(lengths):
-                for indexplot in range(0,len(vectors_to_plot)):
-                    self.vectors[index][indexplot] = self.vectors[index][indexplot][0:min(lengths)]
-
+#!/usr/bin/env python\r
+\r
+'''\r
+plot.py\r
+\r
+Plot class for Hooke.\r
+\r
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+class Invert(object):\r
+\r
+    def __init__(self):\r
+        self.x = False\r
+        self.y = False\r
+\r
+\r
+class Plot(object):\r
+\r
+    def __init__(self):\r
+        self.corrected_curves = []\r
+        self.curves = []\r
+        self.invert = Invert()\r
+        self.raw_curves = []\r
+        self.results = {}\r
+        self.title = ''\r
+\r
+    def normalize(self):\r
+        '''\r
+        Trims the vector lengths as to be equal in a plot.\r
+        '''\r
+        lengths = []\r
+        for curve in self.curves:\r
+            lengths.append(len(curve.x))\r
+            lengths.append(len(curve.y))\r
+            if min(lengths) != max(lengths):\r
+                curve.x = curve.x[0:min(lengths)]\r
+                curve.y = curve.y[0:min(lengths)]\r
diff --git a/hooke/ui/gui/plotmanipulator.py b/hooke/ui/gui/plotmanipulator.py
new file mode 100644 (file)
index 0000000..2d820d3
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+plotmanipulator.py\r
+\r
+Plotmanipulator class for Hooke.\r
+\r
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+from string import replace\r
+\r
+class Plotmanipulator(object):\r
+    def __init__(self, command=None, method=None):\r
+        #the command (e.g. plotmanip_correct)\r
+        self.command = command\r
+        #the method associated with the command\r
+        self.method = method\r
+        #the suffix of the command (e.g. correct) to retrieve\r
+        #status (active or not from config)\r
+        self.name = command.replace('plotmanip_', '')\r
+\r
+\r
diff --git a/hooke/ui/gui/plugin.py b/hooke/ui/gui/plugin.py
new file mode 100644 (file)
index 0000000..24364cb
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/env python\r
+\r
+'''\r
+plugin.py\r
+\r
+ConfigObj plugin class for Hooke.\r
+\r
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+class Plugin(object):\r
+    '''\r
+    Plugin is a class that is used to facilitate accessing\r
+    configuration parameters in a ConfigObj from different plugins.\r
+    '''\r
+\r
+    def __init__(self):\r
+        self.name = ''\r
+        #if the calling plugin uses a prefix, this can be added to the name\r
+        #e.g. autopeak.ini: [[peak_color]]\r
+        #flatfilts.ini [[color]]\r
+        #are both used to set the color of the peak plot (scatter plot)\r
+        #in order to access 'peak_color' rather than 'color', the prefix needs to\r
+        #be set to 'peak_'\r
+        #if the names are identical, set prefix to ''\r
+        self.prefix = ''\r
+        self.section = ''\r
index 0303699ebd5cf957a1b3753e65a6272569364d81..0c0a662bdf1d87d7fbde8fa52730469d95d10bf2 100644 (file)
-# Copyright (C) 2009-2010 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/>.
-
-"""Format values with nice prefixes.
-History:
-
-* 2009 07 16:
-
-  * added negative number support
-  * added decimal-formatted output
-"""
-__version__ = "1.0.1"
-
-import math
-from numpy import isnan
-
-def pretty_format(fValue, sUnit='', iDecimals=-1, iMultiplier=1, bLeadingSpaces=False):
-    if fValue != 0:
-        iLeadingSpaces = 0
-        if bLeadingSpaces:
-            iLeadingSpaces = 5
-        if iMultiplier == 1:
-            iMultiplier=get_multiplier(fValue)
-        sUnitString = ''
-        if sUnit != '':
-            sUnitString = ' ' + get_prefix(iMultiplier) + sUnit
-        if iDecimals >= 0:
-            formatString = '% ' + repr(iLeadingSpaces + iDecimals) + '.' + repr(iDecimals) + 'f'
-            return formatString % (fValue / iMultiplier) + sUnitString
-        else:
-            return str(fValue / iMultiplier) + sUnitString
-    else:
-        return '0'
-    return str(fValue / iMultiplier) + ' ' + get_prefix(fValue / iMultiplier) + sUnit
-
-def get_multiplier(fValue):
-    return pow(10, get_power(fValue))
-
-def get_power(fValue):
-    if fValue != 0 and not isnan(fValue):
-        #get the log10 from fValue (make sure the value is not negative)
-        dHelp = math.floor(math.log10(math.fabs(fValue)))
-        #reduce the log10 to a multiple of 3 and return it
-        return dHelp-(dHelp % 3)
-    else:
-        return 0
-
-def get_prefix(fValue):
-    #set up a dictionary to find the prefix
-    prefix = {
-        24: lambda: 'Y',
-        21: lambda: 'Z',
-        18: lambda: 'E',
-        15: lambda: 'P',
-        12: lambda: 'T',
-        9: lambda: 'G',
-        6: lambda: 'M',
-        3: lambda: 'k',
-        0: lambda: '',
-        -3: lambda: 'm',
-        -6: lambda: u'\u00B5',
-        -9: lambda: 'n',
-        -12: lambda: 'p',
-        -15: lambda: 'f',
-        -18: lambda: 'a',
-        -21: lambda: 'z',
-        -24: lambda: 'y',
-    }
-    if fValue != 0 and not isnan(fValue):
-        #get the log10 from fValue
-        dHelp = math.floor(math.log10(math.fabs(fValue)))
-    else:
-        dHelp = 0
-    #reduce the log10 to a multiple of 3 and create the return string
-    return prefix.get(dHelp - (dHelp % 3))()
-
-'''
-dTestValue=-2.4115665714484597e-008
-print 'Value: '+str(dTestValue)+')'
-print 'pretty_format example (value, unit)'
-print pretty_format(dTestValue, 'N')
-print'-----------------------'
-print 'pretty_format example (value, unit, decimals)'
-print pretty_format(dTestValue, 'N', 3)
-print'-----------------------'
-print 'pretty_format example (value, unit, decimals, multiplier)'
-print pretty_format(dTestValue, 'N', 5, 0.000001)
-print'-----------------------'
-print 'pretty_format example (value, unit, decimals, multiplier, leading spaces)'
-print pretty_format(0.0166276297705, 'N', 3, 0.001, True)
-print pretty_format(0.00750520813323, 'N', 3, 0.001, True)
-print pretty_format(0.0136453282825, 'N', 3, 0.001, True)
-'''
-'''
-#example use autoFormatValue
-dTestValue=0.00000000567
-print 'autoFormatValue example ('+str(dTestValue)+')'
-print autoFormatValue(dTestValue, 'N')
-#outputs 5.67 nN
-'''
-'''
-#example use of decimalFormatValue(fValue, iDecimals, sUnit):
-dTestValue=-2.4115665714484597e-008
-iDecimals=3
-print 'decimalFormatValue example ('+str(dTestValue)+')'
-print decimalFormatValue(dTestValue, iDecimals, 'N')
-#outputs -24.116 nN
-#change iDecimals to see the effect
-'''
-'''
-#example use formatValue
-dTestValue=0.000000000567
-print 'formatValue example ('+str(dTestValue)+')'
-#find the (common) multiplier
-iMultiplier=get_multiplier(dTestValue)
-#use the multiplier and a unit to format the value
-print formatValue(dTestValue, iMultiplier, 'N')
-#outputs 567.0 pN
-'''
-'''
-#to output a scale:
-#choose any value on the axis and find the multiplier and prefix for it
-#use those to format the rest of the scale
-#as values can span several orders of magnitude, you have to decide what units to use
-
-#tuple of values:
-scaleValues=0.000000000985, 0.000000001000, 0.000000001015
-#use this element (change to 1 or 2 to see the effect on the scale and label)
-iIndex=0
-#get the multiplier from the value at iIndex
-iMultiplier=get_multiplier(scaleValues[iIndex])
-print '\nScale example'
-iDecimals=3
-#print the scale
-for aValue in scaleValues: print decimalFormat(aValue/iMultiplier, iDecimals),
-#print the scale label using the value at iIndex
-print '\n'+get_prefix(scaleValues[iIndex])+'N'
-'''
+# Copyright\r
+\r
+"""Simple Python function to format values with nice prefixes.\r
+"""\r
+\r
+import math\r
+from numpy import isnan\r
+\r
+\r
+def pretty_format(value, unit='', decimals=-1, multiplier=0, leading_spaces=False):\r
+    if value == 0:\r
+        return '0'\r
+    if isnan(value):\r
+        return 'NaN'\r
+\r
+    output_str = ''\r
+    leading_spaces_int = 0\r
+    if leading_spaces:\r
+        leading_spaces_int = 5\r
+    #automatic setting of multiplier\r
+    if multiplier == 0:\r
+        multiplier=get_multiplier(value)\r
+    unit_str = ''\r
+    if unit:\r
+        unit_str = ' ' + get_prefix(multiplier) + unit\r
+    if decimals >= 0:\r
+        format_str = '% ' + repr(leading_spaces_int + decimals) + '.' + repr(decimals) + 'f'\r
+        output_str = format_str % (value / multiplier) + unit_str\r
+    else:\r
+        output_str = str(value / multiplier) + unit_str\r
+\r
+    if decimals < 0:\r
+        output_str = str(value / multiplier) + ' ' + get_prefix(value / multiplier) + unit\r
+\r
+    if leading_spaces_int == 0:\r
+        output_str = output_str.lstrip()\r
+\r
+    return output_str\r
+\r
+def get_multiplier(value):\r
+    return pow(10, get_power(value))\r
+\r
+def get_power(value):\r
+    if value != 0 and not isnan(value):\r
+        #get the log10 from value (make sure the value is not negative)\r
+        value_temp = math.floor(math.log10(math.fabs(value)))\r
+        #reduce the log10 to a multiple of 3 and return it\r
+        return value_temp - (value_temp % 3)\r
+    else:\r
+        return 0\r
+\r
+def get_exponent(prefix):\r
+    #set up a dictionary to find the exponent\r
+    exponent = {\r
+        'Y': 24,\r
+        'Z': 21,\r
+        'E': 18,\r
+        'P': 15,\r
+        'T': 12,\r
+        'G': 9,\r
+        'M': 6,\r
+        'k': 3,\r
+        '': 0,\r
+        'm': -3,\r
+        u'\u00B5': -6,\r
+        'n': -9,\r
+        'p': -12,\r
+        'f': -15,\r
+        'a': -18,\r
+        'z': -21,\r
+        'y': -24,\r
+    }\r
+    if exponent.has_key(prefix):\r
+        return exponent[prefix]\r
+    else:\r
+        return 1\r
+\r
+def get_prefix(value):\r
+    #set up a dictionary to find the prefix\r
+    prefix = {\r
+        24: lambda: 'Y',\r
+        21: lambda: 'Z',\r
+        18: lambda: 'E',\r
+        15: lambda: 'P',\r
+        12: lambda: 'T',\r
+        9: lambda: 'G',\r
+        6: lambda: 'M',\r
+        3: lambda: 'k',\r
+        0: lambda: '',\r
+        -3: lambda: 'm',\r
+        -6: lambda: u'\u00B5',\r
+        -9: lambda: 'n',\r
+        -12: lambda: 'p',\r
+        -15: lambda: 'f',\r
+        -18: lambda: 'a',\r
+        -21: lambda: 'z',\r
+        -24: lambda: 'y',\r
+    }\r
+    if value != 0 and not isnan(value):\r
+        #get the log10 from value\r
+        value_temp = math.floor(math.log10(math.fabs(value)))\r
+    else:\r
+        value_temp = 0\r
+    #reduce the log10 to a multiple of 3 and create the return string\r
+    return prefix.get(value_temp - (value_temp % 3))()\r
+\r
+'''\r
+test_value=-2.4115665714484597e-008\r
+print 'Value: '+str(test_value)+')'\r
+print 'pretty_format example (value, unit)'\r
+print pretty_format(test_value, 'N')\r
+print'-----------------------'\r
+print 'pretty_format example (value, unit, decimals)'\r
+print pretty_format(test_value, 'N', 3)\r
+print'-----------------------'\r
+print 'pretty_format example (value, unit, decimals, multiplier)'\r
+print pretty_format(test_value, 'N', 5, 0.000001)\r
+print'-----------------------'\r
+print 'pretty_format example (value, unit, decimals, multiplier, leading spaces)'\r
+print pretty_format(0.0166276297705, 'N', 3, 0.001, True)\r
+print pretty_format(0.00750520813323, 'N', 3, 0.001, True)\r
+print pretty_format(0.0136453282825, 'N', 3, 0.001, True)\r
+'''\r
+'''\r
+#to output a scale:\r
+#choose any value on the axis and find the multiplier and prefix for it\r
+#use those to format the rest of the scale\r
+#as values can span several orders of magnitude, you have to decide what units to use\r
+\r
+#tuple of values:\r
+scale_values=0.000000000985, 0.000000001000, 0.000000001015\r
+#use this element (change to 1 or 2 to see the effect on the scale and label)\r
+index=0\r
+#get the multiplier from the value at index\r
+multiplier=get_multiplier(scale_values[index])\r
+print '\nScale example'\r
+decimals=3\r
+#print the scale\r
+for aValue in scale_values: print decimalFormat(aValue/multiplier, decimals),\r
+#print the scale label using the value at index\r
+print '\n'+get_prefix(scale_values[index])+'N'\r
+'''\r
index 77b9ca7ea69367cd26ba769c478c676472792d85..ca458bd474c03747df079e11b1ad6791b3286d69 100644 (file)
-# 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/>.
-
-import prettyformat
-
-DEFAULT_COLOR = 'orange'
-DEFAULT_DECIMAL = 2
-DEFAULT_STYLE = 'plot'
-
-class Result:
-    def __init__(self):
-        self.color = DEFAULT_COLOR
-        self.result = {}
-        self.style = DEFAULT_STYLE
-        self.visible = True
-        self.x = []
-        self.y = []
-
-class Results:
-    def __init__(self):
-        self.columns = []
-        self.decimals = {}
-        self.has_multipliers = False
-        self.multipliers = {}
-        self.results = []
-        self.separator='\t'
-        self.units = {}
-
-    def get_pretty_value(self, column, value):
-        if self.has_multipliers and self.has_results():
-            multiplier = self.multipliers[column]
-            decimals = self.decimals[column]
-            return prettyformat.pretty_format(value, '', decimals, multiplier, True)
-        return str(value)
-
-    #def get_fit_result(self):
-        #if not(self.has_multipliers):
-            #self.set_multipliers()
-
-        #sResult = 'Contour length ['+prettyformat.get_prefix(self.multiplierContourLength) + 'm]' + self.separator
-        #sResult += prettyformat.pretty_format(self.contourLength[0], '', self.decimals, self.multiplierContourLength, True) + '\n'
-        #sResult += 'Persistence length ['+prettyformat.get_prefix(self.multiplierPersistenceLength) + 'm]' + self.separator
-        #sResult += prettyformat.pretty_format(self.persistenceLength[0], '', self.decimals, self.multiplierPersistenceLength, True) + '\n'
-        #sResult += 'Rupture force ['+prettyformat.get_prefix(self.multiplierRuptureForce) + 'N]' + self.separator
-        #sResult += prettyformat.pretty_format(self.ruptureForces[0], '', self.decimals, self.multiplierRuptureForce, True) + '\n'
-        #sResult += 'Loading rate ['+prettyformat.get_prefix(self.multiplierSlope) + 'N/m]' + self.separator
-        #sResult += prettyformat.pretty_format(self.slopes[0], '', self.decimals, self.multiplierSlope, True)+'\n'
-        #sResult += 'Sigma contour ['+prettyformat.get_prefix(self.multiplierContourLength) + 'm]' + self.separator
-        #sResult += prettyformat.pretty_format(self.contourLengthSigma[0], '', self.decimals, self.multiplierContourLength, True) + '\n'
-        #sResult += 'Sigma persistence ['+prettyformat.get_prefix(self.multiplierPersistenceLength) + 'm]' + self.separator
-        #sResult += prettyformat.pretty_format(self.persistenceLengthSigma[0], '', self.decimals, self.multiplierPersistenceLength, True)
-
-        #return sResult
-
-    #def get_fit_results(self, index):
-        #if index >= 0 and index < len(self.contourLength):
-            #if not(self.has_multipliers):
-                #self.set_multipliers()
-            #sLine = prettyformat.pretty_format(self.contourLength[index], '', self.decimals, self.multiplierContourLength, True) + self.separator
-            #sLine += prettyformat.pretty_format(self.persistenceLength[index], '', self.decimals, self.multiplierPersistenceLength, True) + self.separator
-            #sLine += prettyformat.pretty_format(self.ruptureForces[index], '', self.decimals, self.multiplierRuptureForce, True) + self.separator
-            #sLine += prettyformat.pretty_format(self.slopes[index], '', self.decimals, self.multiplierSlope, True) + self.separator
-            #sLine += prettyformat.pretty_format(self.contourLengthSigma[index], '', self.decimals, self.multiplierContourLength, True) + self.separator
-            #sLine += prettyformat.pretty_format(self.persistenceLengthSigma[index], '', self.decimals, self.multiplierPersistenceLength, True)
-
-            #return sLine
-        #else:
-            #return ''
-
-    def has_results(self):
-        return self.results
-
-    def header_as_list(self):
-        header = []
-        if self.has_results():
-            if not self.has_multipliers:
-                self.set_multipliers()
-            for column in self.columns:
-                #result will contain the results dictionary for 'column'
-                #result = self.results[0][0][column]
-                #result[1] contains the unit
-                unit_str = ''.join([prettyformat.get_prefix(self.multipliers[column]), self.units[column]])
-                header_str = ''.join([column, ' [', unit_str, ']'])
-                header.append(header_str)
-        return header
-
-    #def header_as_str(self):
-        #if self.has_results():
-            #if not self.has_multipliers:
-                #self.set_multipliers()
-            #header_str = ''
-            #for column in self.columns:
-                ##result will contain the results dictionary for 'column'
-                #result = self.results[0][0][column]
-                ##result[1] contains the unit
-                #unit_str = ''.join([prettyformat.get_prefix(self.multipliers[column]), result[1]])
-                #header_str = ''.join([header_str, result_str, ' [', unit_str, ']', self.separator])
-            #return header_str
-        #else:
-            #return None
-
-    def set_decimal(self, column, decimal=DEFAULT_DECIMAL):
-        if self.decimals.has_key(name):
-            self.decimals[name] = decimal
-
-    def set_decimals(self, decimals=DEFAULT_DECIMAL):
-        if decimals < 0:
-            #set default value if necessary
-            decimals = DEFAULT_DECIMAL
-        for column in self.columns:
-            self.decimals[column] = decimals
-
-    def set_multipliers(self, index=0):
-        if self.has_results():
-            if index >= 0 and index < len(self.results):
-                for column in self.columns:
-                    #result will contain the results dictionary at 'index'
-                    result = self.results[index][0]
-                    #in position 0 of the result we find the value
-                    self.multipliers[column] = prettyformat.get_multiplier(result[column][0])
-                self.has_multipliers = True
-        else:
-            self.has_multipliers = False
-
-
-class ResultsWLC(Results):
-    def __init__(self):
-        Results.__init__(self)
-        self.columns = ['Contour length', 'sigma contour length', 'Persistence length', 'sigma persistence length', 'Rupture force', 'Loading rate']
-        self.units['Contour length'] = 'm'
-        self.units['sigma contour length'] = 'm'
-        self.units['Persistence length'] = 'm'
-        self.units['sigma persistence length'] = 'm'
-        self.units['Rupture force'] = 'N'
-        self.units['Loading rate'] = 'N/m'
-        self.set_decimals(2)
-
-    def set_multipliers(self, index=0):
-        if self.has_results():
-            if index >= 0 and index < len(self.results):
-                for column in self.columns:
-                    #result will contain the results dictionary at 'index'
-                    result = self.results[index].result
-                    #in position 0 of the result we find the value
-                    if column == 'sigma contour length':
-                        self.multipliers[column] = self.multipliers['Contour length']
-                    elif column == 'sigma persistence length':
-                        self.multipliers[column] = self.multipliers['Persistence length']
-                    else:
-                        self.multipliers[column] = prettyformat.get_multiplier(result[column])
-                self.has_multipliers = True
-        else:
-            self.has_multipliers = False
+#!/usr/bin/env python\r
+\r
+'''\r
+results.py\r
+\r
+Result and Results classes for Hooke.\r
+\r
+Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada)\r
+\r
+This program is released under the GNU General Public License version 2.\r
+'''\r
+\r
+from numpy import nan\r
+\r
+import prettyformat\r
+import lib.curve\r
+\r
+DEFAULT_COLOR = 'green'\r
+DEFAULT_DECIMAL = 2\r
+DEFAULT_STYLE = 'scatter'\r
+\r
+class Result(lib.curve.Curve):\r
+    def __init__(self):\r
+        lib.curve.Curve.__init__(self)\r
+        self.color = DEFAULT_COLOR\r
+        self.result = {}\r
+        self.style = DEFAULT_STYLE\r
+\r
+class Results(object):\r
+    def __init__(self):\r
+        self.columns = []\r
+        self.decimals = {}\r
+        self.has_multipliers = False\r
+        self.multipliers = {}\r
+        self.results = []\r
+        self.separator='\t'\r
+        self.units = {}\r
+\r
+    def get_pretty_value(self, column, value):\r
+        if self.has_multipliers and self.has_results():\r
+            multiplier = self.multipliers[column]\r
+            decimals = self.decimals[column]\r
+            return prettyformat.pretty_format(value, '', decimals, multiplier, True)\r
+        return str(value)\r
+\r
+    def has_results(self):\r
+        return len(self.results) > 0\r
+\r
+    def get_header_as_list(self):\r
+        header_list = []\r
+        if self.has_results():\r
+            if not self.has_multipliers:\r
+                self.set_multipliers()\r
+            for column in self.columns:\r
+                unit_str = ''.join([prettyformat.get_prefix(self.multipliers[column]), self.units[column]])\r
+                header_str = ''.join([column, ' [', unit_str, ']'])\r
+                header_list.append(header_str)\r
+        return header_list\r
+\r
+    def get_header_as_str(self, separator=None):\r
+        if separator is None:\r
+            separator = self.separator\r
+        return separator.join(map(str, self.get_header_as_list()))\r
+\r
+    def get_result_as_list(self, index=0):\r
+        if index >= 0 and index < len(self.results):\r
+            result_list = []\r
+            if self.has_results():\r
+                if not self.has_multipliers:\r
+                    self.set_multipliers()\r
+                for column in self.columns:\r
+                    result_str = prettyformat.pretty_format(self.results[index].result[column], '', self.decimals[column], self.multipliers[column], True)\r
+                    result_list.append(result_str)\r
+            return result_list\r
+        else:\r
+            return None\r
+\r
+    def get_result_as_string(self, index=0):\r
+        results_list = self.get_result_as_list(index)\r
+        if results_list is not None:\r
+            return self.separator.join(map(str, results_list))\r
+        else:\r
+            return ''\r
+\r
+    def set_decimal(self, column, decimal=DEFAULT_DECIMAL):\r
+        if self.decimals.has_key(column):\r
+            self.decimals[column] = decimal\r
+\r
+    def set_decimals(self, decimals=DEFAULT_DECIMAL):\r
+        if decimals < 0:\r
+            #set default value if necessary\r
+            decimals = DEFAULT_DECIMAL\r
+        for column in self.columns:\r
+            self.decimals[column] = decimals\r
+\r
+    def set_multipliers(self, index=0):\r
+        if self.has_results():\r
+            for column in self.columns:\r
+                #result will contain the results dictionary at 'index'\r
+                result = self.results[index].result\r
+                #in position 0 of the result we find the value\r
+                self.multipliers[column] = prettyformat.get_multiplier(result[column])\r
+            self.has_multipliers = True\r
+        else:\r
+            self.has_multipliers = False\r
+\r
+    def update(self):\r
+        pass\r
+\r
+\r
+class ResultsFJC(Results):\r
+    def __init__(self):\r
+        Results.__init__(self)\r
+        self.columns = ['Contour length', 'sigma contour length', 'Kuhn length', 'sigma Kuhn length', 'Rupture force', 'Slope', 'Loading rate']\r
+        self.units['Contour length'] = 'm'\r
+        self.units['sigma contour length'] = 'm'\r
+        self.units['Kuhn length'] = 'm'\r
+        self.units['sigma Kuhn length'] = 'm'\r
+        self.units['Rupture force'] = 'N'\r
+        self.units['Slope'] = 'N/m'\r
+        self.units['Loading rate'] = 'N/s'\r
+        self.set_decimals(2)\r
+\r
+    def set_multipliers(self, index=0):\r
+        if self.has_results():\r
+            for column in self.columns:\r
+                #result will contain the results dictionary at 'index'\r
+                result = self.results[index].result\r
+                #in position 0 of the result we find the value\r
+                if column == 'sigma contour length':\r
+                    self.multipliers[column] = self.multipliers['Contour length']\r
+                elif column == 'sigma Kuhn length':\r
+                    self.multipliers[column] = self.multipliers['Kuhn length']\r
+                else:\r
+                    self.multipliers[column] = prettyformat.get_multiplier(result[column])\r
+            self.has_multipliers = True\r
+        else:\r
+            self.has_multipliers = False\r
+\r
+class ResultsMultiDistance(Results):\r
+    def __init__(self):\r
+        Results.__init__(self)\r
+        self.columns = ['Distance']\r
+        self.units['Distance'] = 'm'\r
+        self.set_decimals(2)\r
+\r
+    def update(self):\r
+        if (self.results) > 0:\r
+            for result in self.results:\r
+                if result.visible:\r
+                    reference_peak = result.x\r
+                    break\r
+\r
+            for result in self.results:\r
+                if result.visible:\r
+                    result.result['Distance'] = reference_peak - result.x\r
+                    reference_peak = result.x\r
+                else:\r
+                    result.result['Distance'] = nan\r
+\r
+\r
+class ResultsWLC(Results):\r
+    def __init__(self):\r
+        Results.__init__(self)\r
+        self.columns = ['Contour length', 'sigma contour length', 'Persistence length', 'sigma persistence length', 'Rupture force', 'Slope', 'Loading rate']\r
+        self.units['Contour length'] = 'm'\r
+        self.units['sigma contour length'] = 'm'\r
+        self.units['Persistence length'] = 'm'\r
+        self.units['sigma persistence length'] = 'm'\r
+        self.units['Rupture force'] = 'N'\r
+        self.units['Slope'] = 'N/m'\r
+        self.units['Loading rate'] = 'N/s'\r
+        self.set_decimals(2)\r
+\r
+    def set_multipliers(self, index=0):\r
+        if self.has_results():\r
+            for column in self.columns:\r
+                #result will contain the results dictionary at 'index'\r
+                result = self.results[index].result\r
+                #in position 0 of the result we find the value\r
+                if column == 'sigma contour length':\r
+                    self.multipliers[column] = self.multipliers['Contour length']\r
+                elif column == 'sigma persistence length':\r
+                    self.multipliers[column] = self.multipliers['Persistence length']\r
+                else:\r
+                    self.multipliers[column] = prettyformat.get_multiplier(result[column])\r
+            self.has_multipliers = True\r
+        else:\r
+            self.has_multipliers = False\r