From 5160308225d7db35b3e8c319860e076b6e11de22 Mon Sep 17 00:00:00 2001 From: devicerandom Date: Mon, 1 Feb 2010 16:09:54 +0000 Subject: [PATCH] Added illysam branch W. Trevor King (2010-6-3): This branch originally had a separate root from the default branch, despite being a fork. The history of the forking has been lost, so I'm grafting it on at a likely place. The graft should help with merging future changes, but don't consider it to be the true history of this branch. --- CHANGELOG | 404 ----- LICENSE | 165 -- autopeak.py | 369 ---- config/__init__.py | 1 + config/config.py | 32 + config/hooke configspec.ini | 42 + config/hooke.ini | 54 + convfilt.conf | 20 - csvdriver.py | 75 - default.000 => data/default.000 | 0 drivers/__init__.py | 1 + drivers/csvdriver.py | 85 + hemingclamp.py => drivers/hemingclamp.py | 171 +- jpk.py => drivers/jpk.py | 157 +- mcs.py => drivers/mcs.py | 95 +- drivers/mfp1dexport.py | 127 ++ picoforce.py => drivers/picoforce.py | 394 +++-- fit.py | 455 ----- flatfilts.py | 427 ----- generalclamp.py | 251 --- generaltccd.py | 64 - generalvclamp.py | 321 ---- hooke.conf | 71 - hooke.py | 1979 ++++++++++++++-------- hooke_cli.py | 990 ----------- lib/__init__.py | 1 + lib/curve.py | 39 + lib/driver.py | 45 + lib/file.py | 40 + lib/libhooke.py | 108 ++ libpeakspot.py => lib/peakspot.py | 56 +- lib/playlist.py | 174 ++ lib/plot.py | 40 + lib/plotmanipulator.py | 25 + lib/plugin.py | 18 + lib/prettyformat.py | 142 ++ lib/results.py | 199 +++ libhooke.py | 346 ---- libhookecurve.py | 142 -- libinput.py | 155 -- liboutlet.py | 61 - libviewer.py | 53 - macro.py | 233 --- massanalysis.py | 143 -- mfp1dexport.py | 88 - mfp_igor_scripts/ExportMFP1D.ipf | 185 -- mfp_igor_scripts/ExportMFP1DMenu.ipf | 4 - panels/__init__.py | 1 + panels/commands.py | 90 + panels/perspectives.py | 73 + panels/playlist.py | 85 + panels/plot.py | 153 ++ panels/propertyeditor.py | 515 ++++++ panels/results.py | 83 + pca_config.txt | 33 - pcluster.py | 735 -------- picoforcealt.py | 278 --- playlists/test.hkp | 5 + plugins/__init__.py | 1 + plugins/autopeak.ini | 119 ++ plugins/autopeak.py | 358 ++++ plugins/core.ini | 52 + plugins/export.ini | 60 + plugins/export.py | 211 +++ plugins/fit.ini | 35 + plugins/fit.py | 836 +++++++++ plugins/flatfilts.ini | 96 ++ plugins/flatfilts.py | 310 ++++ plugins/generalvclamp.ini | 67 + plugins/generalvclamp.py | 326 ++++ plugins/pcluster.ini | 10 + plugins/playlist.ini | 22 + plugins/playlist.py | 125 ++ plugins/plot.ini | 1 + plugins/plot.py | 23 + plugins/procplots.ini | 90 + plugins/procplots.py | 257 +++ plugins/results.ini | 6 + plugins/results.py | 29 + procplots.py | 251 --- hooke.jpg => resources/hooke.jpg | Bin resources/microscope.ico | Bin 0 -> 13270 bytes superimpose.py | 98 -- test.hkp | 5 - tutorial.py | 538 ------ tutorialdriver.py | 208 --- viewer.py | 39 - 87 files changed, 6947 insertions(+), 8324 deletions(-) delete mode 100755 CHANGELOG delete mode 100644 LICENSE delete mode 100644 autopeak.py create mode 100644 config/__init__.py create mode 100644 config/config.py create mode 100644 config/hooke configspec.ini create mode 100644 config/hooke.ini delete mode 100644 convfilt.conf delete mode 100644 csvdriver.py rename default.000 => data/default.000 (100%) mode change 100755 => 100644 create mode 100644 drivers/__init__.py create mode 100644 drivers/csvdriver.py rename hemingclamp.py => drivers/hemingclamp.py (56%) rename jpk.py => drivers/jpk.py (73%) rename mcs.py => drivers/mcs.py (54%) create mode 100644 drivers/mfp1dexport.py rename picoforce.py => drivers/picoforce.py (80%) delete mode 100755 fit.py delete mode 100755 flatfilts.py delete mode 100644 generalclamp.py delete mode 100644 generaltccd.py delete mode 100644 generalvclamp.py delete mode 100755 hooke.conf mode change 100755 => 100644 hooke.py delete mode 100755 hooke_cli.py create mode 100644 lib/__init__.py create mode 100644 lib/curve.py create mode 100644 lib/driver.py create mode 100644 lib/file.py create mode 100644 lib/libhooke.py rename libpeakspot.py => lib/peakspot.py (87%) create mode 100644 lib/playlist.py create mode 100644 lib/plot.py create mode 100644 lib/plotmanipulator.py create mode 100644 lib/plugin.py create mode 100644 lib/prettyformat.py create mode 100644 lib/results.py delete mode 100755 libhooke.py delete mode 100755 libhookecurve.py delete mode 100644 libinput.py delete mode 100644 liboutlet.py delete mode 100644 libviewer.py delete mode 100644 macro.py delete mode 100644 massanalysis.py delete mode 100644 mfp1dexport.py delete mode 100644 mfp_igor_scripts/ExportMFP1D.ipf delete mode 100644 mfp_igor_scripts/ExportMFP1DMenu.ipf create mode 100644 panels/__init__.py create mode 100644 panels/commands.py create mode 100644 panels/perspectives.py create mode 100644 panels/playlist.py create mode 100644 panels/plot.py create mode 100644 panels/propertyeditor.py create mode 100644 panels/results.py delete mode 100644 pca_config.txt delete mode 100644 pcluster.py delete mode 100644 picoforcealt.py create mode 100644 playlists/test.hkp create mode 100644 plugins/__init__.py create mode 100644 plugins/autopeak.ini create mode 100644 plugins/autopeak.py create mode 100644 plugins/core.ini create mode 100644 plugins/export.ini create mode 100644 plugins/export.py create mode 100644 plugins/fit.ini create mode 100644 plugins/fit.py create mode 100644 plugins/flatfilts.ini create mode 100644 plugins/flatfilts.py create mode 100644 plugins/generalvclamp.ini create mode 100644 plugins/generalvclamp.py create mode 100644 plugins/pcluster.ini create mode 100644 plugins/playlist.ini create mode 100644 plugins/playlist.py create mode 100644 plugins/plot.ini create mode 100644 plugins/plot.py create mode 100644 plugins/procplots.ini create mode 100644 plugins/procplots.py create mode 100644 plugins/results.ini create mode 100644 plugins/results.py delete mode 100755 procplots.py rename hooke.jpg => resources/hooke.jpg (100%) mode change 100755 => 100644 create mode 100644 resources/microscope.ico delete mode 100644 superimpose.py delete mode 100644 test.hkp delete mode 100755 tutorial.py delete mode 100644 tutorialdriver.py delete mode 100644 viewer.py diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100755 index 318ab34..0000000 --- a/CHANGELOG +++ /dev/null @@ -1,404 +0,0 @@ -THIS CHANGELOG FILE CONTANS PRE-SVN CHANGELOGS AND SUMMARIES OF POST-SVN CHANGELOGS. -FROM 0.8.4 ONWARD, DETAILED CHANGELOGS ARE AVAILABLE ON THE HOOKE SUBVERSION REPOSITORY. - -0.8.4 -(2008-x-x) - PLUGINS: - macro.py: - hooke does not crash if it doesn't have permissions to create the folder - fixed overwriting of export for curves with the same name and different numerical extension - fixed macrodir (out of try-catch) - cleaned debug output in execmacro - DRIVERS: - alternative version of picoforce driver - INPUT - merged (partially) libinput (so far in hooke_cli) - OUTLET - merged liboutlet - - -0.8.3 -(2008-04-16) - PLUGINS: - generalvclamp.py: - fixed autopeak header - fixed autopeak slope (now unwanted slope values are discarded) - -0.8.2 -(2008-04-10) - PLUGINS: - flatfilts.py: - convfilt does not crash if a file is not a curve - generalvclamp.py: - autopeak now saves curve data correctly - autopeak now generates a dummy note (so that copylog/notelog is aware you measured the curve) - -0.8.1 -(2008-04-07) - PLUGINS: - generalvclamp.py: - fixed DeprecationWarning in flatten - flatfilts.py - convfilt now working - - -0.8.0: -(2008-04-04) - hooke.py: - sanity check of CLI plugins to avoid function overloading at startup - hooke_cli.py ; libhooke.py: - now playlists keep the index (when you reload the playlist, it starts from the - last observed curve) - updated plot to use _send_plot() - hooke.conf accepts lists as arguments for variables in - txt, export now have consistent argument order (thanks to A.G.Casado for pointing me that) - txt crashes no more if no filename is given (thanks to A.G.Casado for pointing me that) - libhookecurve.py: - added add_set() , remove_set() methods to make life easier for plugin writers - procplots.py: - plotmanip_correct() works with new picoforce.py deflection output (see) - PLUGINS: - fit.py: - updated wlc to use _send_plot() - wlc noauto now keeps the contact point - wlc reclick to click again the contact point - temperature now set in hooke.conf - generalvclamp.py: - implemented slope (thanks to Marco Brucale) - implemented autopeak - flatfilts.py: - convfilt,peaks use flattened curve - macro.py: - (new) added macro plugin (thanks to Alberto Gomez Casado) - DRIVERS: - picoforce.py: - fixed trigger bug! (thanks to Alberto Gomez Casado) - better deflection output (separated extension,retraction) - -0.7.5: -(2008-03-27) - hooke_cli.py: - removed outdated size command - PLUGINS: - generalvclamp.py: - implemented flatten - DRIVERS: - added tutorialdriver.py driver - csvdriver.py: - fixed (forgot close_all() method) - -0.7.4: -(2008-03-19) - added csvdriver driver - hooke_cli.py: - fixed plot manipulators handling (now it's safe to comment a - plot manipulator on hooke.conf) - PLUGINS: - fit.py: - fixed possible crash when clicking two times the same point on wlc - -0.7.3: -(2008-01-10) - hooke_cli.py: - fixed crash on copylog - PLUGINS: - massanalysis.py: - Initial release - tutorial.py: - Tutorial plugin, initial release - -0.7.2.1: -(2007-11-30) - PLUGINS: - flatfilt.py: - fixed crash on Windows - -0.7.2: -(2007-11-29) - hooke.py: - new configuration variable hookedir - hooke_cli.py: - copylog now checks if the destination is a real directory - fixed crashes in set - PLUGINS: - generalvclamp.py: - fixed a crash in forcebase when picking two times the same point - flatfilt.py: - fixed crash due to convfilt.conf impossible to load - initial implementation of the blind window for convfilt - initial data set maps (NOT FINISHED) - -0.7.1: -(2007-11-26) - PLUGINS: - flatfilts.py: - fixed possible crash in convfilt - implemented configuration file convfilt.conf - convfilt defaults are now 5 peaks 5 times more the noise absdev - implemented convconf - implemented setconf - libpeakspot.py: - fixed:now it really uses noise_absdev - -0.7.0: -(2007-11-15) - hooke_cli.py: - implemented _send_plot() helper API function - PLUGINS: - generalvclamp.py: - fixed forcebase to work with subtplot - flatfilts.py: - implemented convfilt! - added libpeakspot.py (helping library for convolution filter) - -0.6.5: -(2007-11-06) - hooke_cli.py, hooke.py: - plateau and contact (unmaintained) deleted and scheduled for re-release in generalvramp - implemented _measure_N_points() - PLUGINS: - generalvclamp.py: - implemented forcebase - fit.py: - wlc now accepts and uses temperature as an argument - wlc has been cleaned and uses new APIs - -0.6.4: -(2007-10-23) - hooke_cli.py, libhooke.py: - implemented support for defining order of plotmanip methods in hooke.conf - hooke_cli.py: - implemented delta - implemented point - attempted fix to bug 0033 (notelog crashing Hooke when using Unicode characters) - PLUGINS: - generalvramp.py: - began to move velocity ramp force spectroscopy-specific things in separate plugin - procplots.py: - added detriggerize; "set detrigger" 0/1 disables/enables it. - DRIVERS: - picoforce.py: - removed detriggerize() from driver - -0.6.3: -(2007-10-02) - hooke_cli.py: - rewritten txt command, now working - DRIVERS: - picoforce.py: - implemented detriggerize() to bypass the Picoforce trigger bug - PLUGINS: - superimpose.py: - implemented plotavgimpose - -0.6.2: -(2007-09-27) - hooke_cli.py: - fixed error handling in notelog - smarter handling of directory names in genlist - unexpected error handling in do_plot() - hooke.py: - implemented GetDisplayedPlot event and handlers - PLUGINS: - fit.py: - fixed (bug 0029) about replotting of wlc on a subtplot curve - multiple fitting displayed (to refine...) - -0.6.1: -(2007-08-06) - libhooke.py , hooke.py: - initial support for workdir configuration variable - libhooke.py: - fixed Driver() etc. semantics for gracefully handling unrecognized plots - hooke_cli.py: - fixed export namehandling - fixed plot error handling - PLUGINS: - flatfilts.py: - fixed memory leak - generalclamp.py: - fixed step command - -0.6.0 "Anko": -(2007-07-25) - hooke.py: - initial plugin support for the gui - wlc fitting now 100% plugin - measure_points replaces measure_couple etc. and provides much better extensibility - hooke_cli.py: - curves are sorted at beginning - PLUGINS: - procplots.py: - fft now allows for user selection of curve segment; select the plot; etc. - fit.py: - added gui section of plugin, now completely independent - fixed bug of wlc output - superimpose.py: - new plugin for superimposition of curve segments (still in development) - generalclamp.py: - all clamp commands now in a single plugin - implemented step - -0.5.4: -(2007-06-15) - procplots.py: - fixed fft crash with Numpy 1.0.1 - hooke.py: - fixed crashes if plot.scatter[] was empty - fixed management of multiple plots (bug #0025) - hooke_cli.py - fixed zpiezo error in measurement - hemingclamp.py, picoforce.py: - implemented close_all() method in drivers to avoid too many open files error - flatfilts.py: - fixed memory leak -0.5.3: -(2007-06-06) - wlc.py, hooke.py: - fixing and cleaning fit code: now the fit is part of a PlotObject and 100% coded in wlc.py - plotting of the wlc.py clicked points also begin to be part of a PlotObject - management of 'scatter' style property of plots - hooke_cli.py - fixed measuring error in defl, zpiezo - flatfilts.py: - slightly optimized has_features() routine - procplots.py: - fixed derivplot for every number of vectors - fixed possible crash of subtplot if applied on a file with != 2 plots - added fft command - libhookecurve.py: - fixed xaxis, yaxis for non-default plots: now defined from PlotObject - PlotObject now defines a styles[] vector -0.5.2: -(2007-05-21) - versioning a bit cleaned - fixed bug in hemingclamp.py preventing filename to appear - fixed wxversion problem for 2.8 - fixed too many open files bug (bug 0024) - added index command -0.5.1: -(2007-05-09) - using wxversion to choose from multiple wx versions - fixed old dependencies remaining -0.5.0 "Ingyo": -(2007-05-03) - general code updating and rewriting due to plugin support/better plot management - hooke.py: - initial plugin architecture for the command line. - initial plugin architecture for file drivers - initial plugin architecture for processing plots - export can now export both top and bottom plot (not together) - hooke_cli.py: - wlc fitting moved to fit.py plugin - flatfilt moved to flatfilts.py plugin - subtplot, derivplot moved to procplots.py plugin - double plot temporarily fixed for previous commands - export can now export both top and bottom plot (not together) - -0.4.1: -(2007-02-13) - hooke_cli.py: - double plot now default for clamp experiments - libhooke.py: - fixed bug that prevented flatfilt to work - (maybe) fixed memory leak in flatfilt - -0.4.0 "Hanzei": -(2007-02-08) - general code updating and rewriting due to double plot/force clamp supports - hooke.py: - initial dummy menu sketch - hooke.py, hooke_cli.py: - first general support in code for double plot: - - derivplot now in separate plot - - implemented show and close commands - - all functions should be double plot-aware - - clicking a point is double plot-aware - libhooke.py, hooke_cli.py: - general code cleanup: vectors_to_plot(), subtract_plot(), find_contact_point() and derivplot routines are now methods of class HookeCurve - hooke_cli.py: - implemented quit (alias of exit) - implemented version - libhooke.py, hooke.py, hooke_cli.py: - initial support for force clamp experiments: - - hemingclamp driver supported - - "experiment" flag describes what kind of experiment is a curve - - time, zpiezo, defl commands implemented - libhemingclamp.py: - inital release. - -0.3.1: - hooke.py: - fixed stupid bug in plateau - fixed bug in derivplot and subtplot not taking into account xaxes/yaxes variables -0.3.0: - from now on, all changelog is stored in CHANGELOG - hooke.py, libhooke.py, hooke_cli.py: - fixed plot and flatfilt crash when processing corrupt files - flatfilt output now more verbose - implemented system (execute an external OS command) - implemented copylog (copies annotated curves to a given directory) (todo 0033) - initial txt implementation (exports the current curve as a text file) (todo 0023) - fixed exit behaviour (bug 0013) - xaxes and yaxes variables now control visualization of plot (todo 0018) - new (better) contact point algorithm + workaround for the picoforce trigger bug -0.2.2 : - hooke.py, hooke_cli.py, libhooke.py: - support for fixed persistent length in WLC -0.2.1 : - hooke.py , libhooke.py: - fixed 'wlc noauto' bug (0012) preventing correct contact point to be used -0.2.0 : - hooke_cli.py: - implemented getlist (alias of genlist) - implemented contact (to plot the contact point) - fixed bug 0001 (Hooke crashes when opening a non-pf file) - fixed bug 0008 (Hooke crashes when generating a playlist with malformed namefiles/nonexistent files) - now the plot is refreshed after a "set" command (todo 0014) - wlc fit can use the (new) automatic contact point detection (old behaviour is preserved with "noauto" option) - hooke.py: - fixed versioning printing - complete refactoring of contact point routines - wlc fit adapted to use the (new) automatic contact point detection - wlc fit code a bit cleaned; parts moved to libhooke.py - libhooke.py: - new contact point algorithm (new algorithm) - wlc fit now uses a fancier domain (from contact point to a bit more than last point); initial chunk preparation section moved from hooke.py - - -OLDER CHANGELOGS: - -hooke.py: -0.1.1 : - From now on, all changelog is stored in hooke.py - hooke_cli.py: - corrected bug 0010 (addtolist bug), alerts when hitting start/end of playlist -2006_09_15_devel=0.1.0: initial WLC fit support. We hit 0.1 milestone :D -2006_08_28_devel: refactoring of plot interaction -2006_06_14_devel: fixed libhooke calls -2006_06_08_devel: initial automatic contact point finding -2006_05_30_devel: configuration file support - -hooke_cli.py: -0.1.1 : from now on, all changelog is in hooke.py -2006_09_15_devel: implemented wlc; 0.1.0 milestone. -2006_08_28_devel: refactoring of plot interaction -2006_07_23_devel: implemented note; implemented flatfilt; implemented notelog; exit now warns if playlist/notes - have not been saved. -2006_07_18_devel: implemented subtplot; bug 0007 ("cd" crashing) fixed -2006_06_16_devel: moved math helper functions in libhooke.py -2006_06_14_devel: fixed "jump" output; fixed "exit" (now it works!); fixed off-by-one bug in deflection-correction -2006_06_08_devel: fixed "loadlist" output; -2006_05_30_devel: initial configuration file support; added "set" command; initial deflection-correction support; added "ls" command as an alias of "dir" -2006_05_23_devel: rewriting of playlist-handling code due to major rewrite of hooke_playlist.py - -libhooke.py -0.1.1 : from now on, all changelog is in hooke.py -2006_09_15_devel : initial WLC support -2006_09_14_devel : initial support for Hemingway velocity clamp files, minor refactorings -2006_07_22_devel : implemented math function has_features -2006_06_16_devel : math functions moved here -2006_06_08_devel : hooke_playlist.py becomes libhooke.py -2006_05_30_devel : support for deflection in HookeCurve -2006_05_29_devel : Initial configuration file support -2006_05_23_devel : Major rewrite. Fixed bug 0002 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index fc8a5de..0000000 --- a/LICENSE +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/autopeak.py b/autopeak.py deleted file mode 100644 index ffacd82..0000000 --- a/autopeak.py +++ /dev/null @@ -1,369 +0,0 @@ -#!/usr/bin/env python - -from libhooke import WX_GOOD, ClickedPoint -import wxversion -wxversion.select(WX_GOOD) -from wx import PostEvent -import numpy as np -import scipy as sp -import copy -import os.path -import time - -import warnings -warnings.simplefilter('ignore',np.RankWarning) - - -class autopeakCommands: - - def do_autopeak(self,args): - ''' - AUTOPEAK - (autopeak.py) - Automatically performs a number of analyses on the peaks of the given curve. - Currently it automatically: - - fits peaks with WLC function - - measures peak maximum forces with a baseline - - measures slope in proximity of peak maximum - Requires flatten plotmanipulator , fit.py plugin , flatfilts.py plugin with convfilt - - Syntax: - autopeak [rebase] [pl=value] [t=value] [noauto] [reclick] - - rebase : Re-asks baseline interval - - pl=[value] : Use a fixed persistent length for the fit. If pl is not given, - the fit will be a 2-variable - fit. DO NOT put spaces between 'pl', '=' and the value. - The value must be in meters. - Scientific notation like 0.35e-9 is fine. - - t=[value] : Use a user-defined temperature. The value must be in - kelvins; by default it is 293 K. - DO NOT put spaces between 't', '=' and the value. - - noauto : allows for clicking the contact point by - hand (otherwise it is automatically estimated) the first time. - If subsequent measurements are made, the same contact point - clicked the first time is used - - reclick : redefines by hand the contact point, if noauto has been used before - but the user is unsatisfied of the previously choosen contact point. - - usepoints : fit interval by number of points instead than by nanometers - - noflatten : does not use the "flatten" plot manipulator - - When you first issue the command, it will ask for the filename. If you are giving the filename - of an existing file, autopeak will resume it and append measurements to it. If you are giving - a new filename, it will create the file and append to it until you close Hooke. - - - Useful variables (to set with SET command): - --- - temperature= temperature of the system for wlc fit (in K) - - auto_slope_span = number of points on which measure the slope, for slope - - auto_fit_nm = number of nm to fit before the peak maximum, for WLC (if usepoints false) - auto_fit_points = number of points to fit before the peak maximum, for WLC (if usepoints true) - - baseline_clicks = -1: no baseline, f=0 at the contact point (whether hand-picked or automatically found) - 0: automatic baseline - 1: decide baseline with a single click and length defined in auto_left_baseline - 2: let user click points of baseline - auto_left_baseline = length in nm to use as baseline from the right point (if baseline_clicks=0 , 1) - auto_right_baseline = distance in nm of peak-most baseline point from last peak (if baseline_clicks = 0) - ''' - - #MACROS. - #FIXME: to move outside function - def fit_interval_nm(start_index,plot,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. - ''' - whatset=1 #FIXME: should be decidable - x_vect=plot.vectors[1][0] - - c=0 - i=start_index - start=x_vect[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 pickup_contact_point(): - '''macro to pick up the contact point by clicking''' - contact_point=self._measure_N_points(N=1, whatset=1)[0] - contact_point_index=contact_point.index - self.wlccontact_point=contact_point - self.wlccontact_index=contact_point.index - self.wlccurrent=self.current.path - return contact_point, contact_point_index - - def find_current_peaks(noflatten): - #Find peaks. - defplot=self.current.curve.default_plots()[0] - if not noflatten: - flatten=self._find_plotmanip('flatten') #Extract flatten plotmanip - defplot=flatten(defplot, self.current, customvalue=1) #Flatten curve before feeding it to has_peaks - peak_location,peak_size=self.has_peaks(defplot, self.convfilt_config['mindeviation']) - return peak_location, peak_size - - #default fit etc. variables - pl_value=None - T=self.config['temperature'] - - slope_span=int(self.config['auto_slope_span']) - delta_force=10 - rebase=False #if true=we select rebase - noflatten=False #if true=we avoid flattening - - #initialize output data vectors - c_lengths=[] - p_lengths=[] - sigma_c_lengths=[] - sigma_p_lengths=[] - forces=[] - slopes=[] - - #pick up plot - displayed_plot=self._get_displayed_plot(0) - - #COMMAND LINE PARSING - #--Using points instead of nm interval - if 'usepoints' in args.split(): - fit_points=int(self.config['auto_fit_points']) - usepoints=True - else: - fit_points=None - usepoints=False - #--Recalculate baseline - if 'rebase' in args or (self.basecurrent != self.current.path): - rebase=True - - if 'noflatten' in args: - noflatten=True - - #--Custom persistent length / custom temperature - for arg in args.split(): - #look for a persistent length argument. - if 'pl=' in arg: - pl_expression=arg.split('=') - pl_value=float(pl_expression[1]) #actual value - #look for a T argument. FIXME: spaces are not allowed between 'pl' and value - if ('t=' in arg[0:2]) or ('T=' in arg[0:2]): - t_expression=arg.split('=') - T=float(t_expression[1]) - #--Contact point arguments - if 'reclick' in args.split(): - print 'Click contact point' - contact_point, contact_point_index = pickup_contact_point() - elif 'noauto' in args.split(): - if self.wlccontact_index==None or self.wlccurrent != self.current.path: - print 'Click contact point' - contact_point , contact_point_index = pickup_contact_point() - else: - contact_point=self.wlccontact_point - contact_point_index=self.wlccontact_index - else: - #Automatically find contact point - cindex=self.find_contact_point() - contact_point=self._clickize(displayed_plot.vectors[1][0], displayed_plot.vectors[1][1], cindex) - #--END COMMAND LINE PARSING-- - - - peak_location, peak_size = find_current_peaks(noflatten) - - if len(peak_location) == 0: - print 'No peaks to fit.' - return - - fitplot=copy.deepcopy(displayed_plot) - - #Pick up force baseline - if rebase: - clicks=self.config['baseline_clicks'] - if clicks==0: - self.basepoints=[] - base_index_0=peak_location[-1]+fit_interval_nm(peak_location[-1], displayed_plot, self.config['auto_right_baseline'],False) - self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_0)) - base_index_1=self.basepoints[0].index+fit_interval_nm(self.basepoints[0].index, displayed_plot, self.config['auto_left_baseline'],False) - self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_1)) - elif clicks>0: - print 'Select baseline' - if clicks==1: - self.basepoints=self._measure_N_points(N=1, whatset=whatset) - base_index_1=self.basepoints[0].index+fit_interval_nm(self.basepoints[0].index, displayed_plot, self.config['auto_left_baseline'], False) - self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_1)) - else: - self.basepoints=self._measure_N_points(N=2, whatset=whatset) - - self.basecurrent=self.current.path - - boundaries=[self.basepoints[0].index, self.basepoints[1].index] - boundaries.sort() - to_average=displayed_plot.vectors[1][1][boundaries[0]:boundaries[1]] #y points to average - avg=np.mean(to_average) - - clicks=self.config['baseline_clicks'] - if clicks==-1: - try: - avg=displayed_plot.vectors[1][1][contact_point_index] - except: - avg=displayed_plot.vectors[1][1][cindex] - - for peak in peak_location: - #WLC FITTING - #define fit interval - if not usepoints: - fit_points=fit_interval_nm(peak, displayed_plot, self.config['auto_fit_nm'], True) - peak_point=self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],peak) - other_fit_point=self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],peak-fit_points) - - #points for the fit - points=[contact_point, peak_point, other_fit_point] - - if abs(peak_point.index-other_fit_point.index) < 2: - continue - - params, yfit, xfit, fit_errors = self.wlc_fit(points, displayed_plot.vectors[1][0], displayed_plot.vectors[1][1], pl_value, T, return_errors=True) - - - #Measure forces - delta_to_measure=displayed_plot.vectors[1][1][peak-delta_force:peak+delta_force] - y=min(delta_to_measure) - #save force values (pN) - #Measure slopes - slope=self.linefit_between(peak-slope_span,peak)[0] - - - #check fitted data and, if right, add peak to the measurement - #FIXME: code duplication - if len(params)==1: #if we did choose 1-value fit - p_lengths.append(pl_value) - c_lengths.append(params[0]*(1.0e+9)) - sigma_p_lengths.append(0) - sigma_c_lengths.append(fit_errors[0]*(1.0e+9)) - forces.append(abs(y-avg)*(1.0e+12)) - slopes.append(slope) - #Add WLC fit lines to plot - fitplot.add_set(xfit,yfit) - if len(fitplot.styles)==0: - fitplot.styles=[] - fitplot.colors=[] - else: - fitplot.styles.append(None) - fitplot.colors.append(None) - else: #2-value fit - p_leng=params[1]*(1.0e+9) - #check if persistent length makes sense. otherwise, discard peak. - if p_leng>self.config['auto_min_p'] and p_leng - - - - - - - - - diff --git a/csvdriver.py b/csvdriver.py deleted file mode 100644 index 4222255..0000000 --- a/csvdriver.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python - -''' -csvdriver.py - -Simple driver to read general comma-separated values in Hooke - -Columns are read this way: - -X1 , Y1 , X2 , Y2 , X3 , Y3 ... - -If the number of columns is odd, the last column is ignored. - -(c)Massimo Sandal, 2008 -''' - -import libhookecurve as lhc -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] - - \ No newline at end of file diff --git a/default.000 b/data/default.000 old mode 100755 new mode 100644 similarity index 100% rename from default.000 rename to data/default.000 diff --git a/drivers/__init__.py b/drivers/__init__.py new file mode 100644 index 0000000..803f29c --- /dev/null +++ b/drivers/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python diff --git a/drivers/csvdriver.py b/drivers/csvdriver.py new file mode 100644 index 0000000..0d385b4 --- /dev/null +++ b/drivers/csvdriver.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +''' +csvdriver.py + +Simple driver to read general comma-separated values in Hooke + +Columns are read this way: + +X1 , Y1 , X2 , Y2 , X3 , Y3 ... + +If the number of columns is odd, the last column is ignored. + +Copyright 2008 by Massimo Sandal +with modifications 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 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 + diff --git a/hemingclamp.py b/drivers/hemingclamp.py similarity index 56% rename from hemingclamp.py rename to drivers/hemingclamp.py index e2f2e1c..7b4af45 100755 --- a/hemingclamp.py +++ b/drivers/hemingclamp.py @@ -1,62 +1,62 @@ #!/usr/bin/env python ''' -libhemingclamp.py +hemingclamp.py Library for interpreting Hemingway force spectroscopy files. -Copyright (C) 2008 Massimo Sandal, Marco Brucale (University of Bologna, Italy) +Copyright 2008 by Massimo Sandal, Marco Brucale (University of Bologna, Italy) +with modifications by Dr. Rolf Schmidt (Concordia University, Canada) This program is released under the GNU General Public License version 2. ''' + __version__='2007_02_15_devel' __changelog__=''' +2010_01_22: initial release for Hooke GUI 2007_02_15: fixed time counter with my counter -2007_02_07: Initial implementation +2007_02_07: initial implementation ''' + +import copy +import os.path import string -import libhookecurve as lhc + +import lib.curve +import lib.driver +import lib.plot class DataChunk(list): - '''Dummy class to provide ext and ret methods to the data list. + #TODO: something similar is also used in jpk.py + #potential for OOP/inheritance? + ''' + Dummy class to provide ext and ret methods to the data list. In this case ext and self can be equal. ''' - + def ext(self): return self - + def ret(self): return self -class hemingclampDriver(lhc.Driver): - +class hemingclampDriver(lib.driver.Driver): + def __init__(self, filename): - + self.filedata = open(filename,'r') self.data = self.filedata.readlines()[6:] self.filedata.close() - + self.filetype = 'hemingclamp' self.experiment = 'clamp' - + self.filename=filename - + def __del__(self): - self.filedata.close() - - def is_me(self): - ''' - we define our magic heuristic for HemingClamp files - ''' - myfile=file(self.filename) - headerlines=myfile.readlines()[0:3] - myfile.close() - if headerlines[0][0:10]=='#Hemingway' and headerlines[1][0:19]=='#Experiment: FClamp': - return True - else: - return False - + self.filedata.close() + def _getdata_all(self): time = [] phase = [] @@ -65,7 +65,7 @@ class hemingclampDriver(lhc.Driver): imposed = [] trim_indexes = [] trim_counter = 0.0 - + for i in self.data: temp = string.split(i) #time.append(float(temp[0])*(1.0e-3)) # This is managed differently now, since each data point = 1ms: see below @@ -78,57 +78,98 @@ class hemingclampDriver(lhc.Driver): if phase[x] != trim_counter: trim_indexes.append(x) trim_counter = phase[x] - + #we rebuild the time counter assuming 1 point = 1 millisecond c=0.0 for z in zpiezo: time.append(c) - c+=(1.0e-3) - - return time,phase,zpiezo,defl,imposed,trim_indexes - - def time(self): - return DataChunk(self._getdata_all()[0]) + c+=(1.0e-3) - def phase(self): - return DataChunk(self._getdata_all()[1]) - - def zpiezo(self): - return DataChunk(self._getdata_all()[2]) - - def deflection(self): - return DataChunk(self._getdata_all()[3]) - - def imposed(self): - return DataChunk(self._getdata_all()[4]) + return time,phase,zpiezo,defl,imposed,trim_indexes - def trimindexes(self): - return DataChunk(self._getdata_all()[5]) - def close_all(self): ''' Explicitly closes all files ''' self.filedata.close() - + def default_plots(self): - main_plot=lhc.PlotObject() - defl_plot=lhc.PlotObject() - time=self.time() phase=self.phase() zpiezo=self.zpiezo() deflection=self.deflection() imposed=self.imposed() - - main_plot.vectors=[[time,zpiezo],[time,phase]] - main_plot.units=['seconds','meters'] - main_plot.destination=0 - main_plot.title=self.filename - - defl_plot.vectors=[[time,deflection],[time,imposed]] - defl_plot.units=['seconds','Newtons'] - defl_plot.destination=1 - - return [main_plot, defl_plot] - \ No newline at end of file + + #return [main_plot, defl_plot] + main_extension = lib.curve.Curve() + main_retraction = lib.curve.Curve() + + #TODO: check 'title' below + main_extension.color = 'red' + main_extension.label = 'extension' + main_extension.style = 'plot' + main_extension.title = 'Force curve' + main_extension.units.x = 's' + main_extension.units.y = 'm' + main_extension.x = time + main_extension.y = zpiezo + main_retraction.color = 'blue' + main_retraction.label = 'retraction' + main_retraction.style = 'plot' + main_retraction.title = 'Force curve' + main_retraction.units.x = 's' + #TODO: what is the real unit for y? + main_retraction.units.y = 'degree' + main_retraction.x = time + main_retraction.y = phase + + deflection_extension = copy.deepcopy(main_extension) + deflection_retraction = copy.deepcopy(main_retraction) + #TODO: check 'title' below + deflection_extension.destination.row = 2 + deflection_extension.units.y = 'N' + deflection_extension.y = deflection + #TODO: what is the real unit for y? + deflection_retraction.destination.row = 2 + deflection_retraction.units.y = 'N' + deflection_retraction.y = imposed + + plot = lib.plot.Plot() + plot.title = os.path.basename(self.filename) + plot.curves.append(main_extension) + plot.curves.append(main_retraction) + plot.curves.append(deflection_extension) + plot.curves.append(deflection_retraction) + + plot.normalize() + return plot + + def deflection(self): + return DataChunk(self._getdata_all()[3]) + + def imposed(self): + return DataChunk(self._getdata_all()[4]) + + def is_me(self): + ''' + we define our magic heuristic for HemingClamp files + ''' + myfile=file(self.filename) + headerlines=myfile.readlines()[0:3] + myfile.close() + if headerlines[0][0:10]=='#Hemingway' and headerlines[1][0:19]=='#Experiment: FClamp': + return True + else: + return False + + def phase(self): + return DataChunk(self._getdata_all()[1]) + + def time(self): + return DataChunk(self._getdata_all()[0]) + + def trimindexes(self): + return DataChunk(self._getdata_all()[5]) + + def zpiezo(self): + return DataChunk(self._getdata_all()[2]) diff --git a/jpk.py b/drivers/jpk.py similarity index 73% rename from jpk.py rename to drivers/jpk.py index 4f64331..90aa5c8 100644 --- a/jpk.py +++ b/drivers/jpk.py @@ -1,74 +1,74 @@ #!/usr/bin/env python -import string -import libhookecurve as lhc +''' +jpk.py + +Driver for jpk files. + +Copyright ??? by Massimo Sandal? +with modifications by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' + +import os.path + +import lib.curve +import lib.driver +import lib.plot class DataChunk(list): #Dummy class to provide ext and ret methods to the data list. - + def ext(self): halflen=(len(self)/2) return self[0:halflen] - + def ret(self): halflen=(len(self)/2) return self[halflen:] -class jpkDriver(lhc.Driver): +class jpkDriver(lib.driver.Driver): def __init__(self, filename): self.filename=filename #self.filename can always be useful, and should be defined self.filedata = open(filename,'r') #We open the file self.filelines=self.filedata.readlines() - self.filedata.close() - '''These are two strings that can be used by Hooke commands/plugins to understand what they are looking at. They have no other + self.filedata.close() + ''' + These are two strings that can be used by Hooke commands/plugins to understand what they are looking at. They have no other meaning. They have to be somehow defined however - commands often look for those variables. - + self.filetype should contain the name of the exact filetype defined by the driver (so that filetype-specific commands can know if they're dealing with the correct filetype) self.experiment should contain instead the type of data involved (for example, various drivers can be used for force-clamp experiments, - but hooke commands could like to know if we're looking at force clamp data, regardless of their origin, and not other + but hooke commands could like to know if we're looking at force clamp data, regardless of their origin, and not other kinds of data) - + Of course, all other variables you like can be defined in the class. ''' self.filetype = 'jpk' self.experiment = 'smfs' - - + + def __del__(self): - self.filedata.close() - - def is_me(self): - ''' - we define our magic heuristic for jpk files - ''' - myfile=file(self.filename) - headerlines=myfile.readlines()[0:3] - myfile.close() - if headerlines[0][0:11]=='# xPosition' and headerlines[1][0:11]=='# yPosition': - return True - else: - return False - - def close_all(self): self.filedata.close() - + def _read_data_segment(self): #routine that actually reads the data - + height_ms=[] height_m=[] height=[] v_deflection=[] h_deflection=[] - + self.springconstant=0 #if we don't meet any spring constant, use deflection... - + for line in self.filelines: #we meet the segment defining the order of data columns - + if line[0:9]=='# columns': splitline=line.split()[2:] height_ms_index=splitline.index('smoothedStrainGaugeHeight') @@ -76,10 +76,10 @@ class jpkDriver(lhc.Driver): height_index=splitline.index('height') v_deflection_index=splitline.index('vDeflection') #h_deflection=splitline.index('hDeflection') - + if line[0:16]=='# springConstant': self.springconstant=float(line.split()[2]) - + if line[0] != '#' and len(line.split())>1: dataline=line.split() height_ms.append(float(dataline[height_ms_index])) @@ -87,31 +87,27 @@ class jpkDriver(lhc.Driver): height.append(float(dataline[height_index])) v_deflection.append(float(dataline[v_deflection_index])) #h_deflection.append(float(dataline[h_deflection_index])) - + if self.springconstant != 0: force=[item*self.springconstant for item in v_deflection] else: #we have measured no spring constant :( force=v_deflection - + height_ms=DataChunk([item*-1 for item in height_ms]) height_m=DataChunk([item*-1 for item in height_m]) height=DataChunk([item*-1 for item in height]) deflection=DataChunk(v_deflection) force=DataChunk(force) - + return height_ms,height_m,height,deflection,force - - def deflection(self): - height_ms,height_m,height,deflection,force=self._read_data_segment() - deflection_ext=deflection.ext() - deflection_ret=deflection.ret() - deflection_ret.reverse() - return deflection_ext,deflection_ret - + + def close_all(self): + self.filedata.close() + def default_plots(self): - + height_ms,height_m,height,deflection,force=self._read_data_segment() - + height_ms_ext=height_ms.ext() height_ms_ret=height_ms.ret() force_ext=force.ext() @@ -119,21 +115,56 @@ class jpkDriver(lhc.Driver): #reverse the return data, to make it coherent with hooke standard height_ms_ret.reverse() force_ret.reverse() - - main_plot=lhc.PlotObject() - main_plot.add_set(height_ms_ext,force_ext) - main_plot.add_set(height_ms_ret,force_ret) - - - + if self.springconstant != 0: - main_plot.units=['meters','force'] + #TODO: force is not really a unit + y_unit = 'force' else: - main_plot.units=['meters','meters'] - - main_plot.normalize_vectors() - - main_plot.destination=0 - main_plot.title=self.filename - - return [main_plot] \ No newline at end of file + y_unit = 'm' + + 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 = y_unit + extension.x = height_ms_ext + extension.y = force_ext + retraction.color = 'blue' + retraction.label = 'retraction' + retraction.style = 'plot' + retraction.title = 'Force curve' + retraction.units.x = 'm' + retraction.units.y = y_unit + retraction.x = height_ms_ret + retraction.y = force_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): + height_ms,height_m,height,deflection,force=self._read_data_segment() + deflection_ext=deflection.ext() + deflection_ret=deflection.ret() + deflection_ret.reverse() + return deflection_ext,deflection_ret + + def is_me(self): + ''' + we define our magic heuristic for jpk files + ''' + myfile=file(self.filename) + headerlines=myfile.readlines()[0:3] + myfile.close() + if headerlines[0][0:11]=='# xPosition' and headerlines[1][0:11]=='# yPosition': + return True + else: + return False diff --git a/mcs.py b/drivers/mcs.py similarity index 54% rename from mcs.py rename to drivers/mcs.py index 711e8c3..df12df6 100644 --- a/mcs.py +++ b/drivers/mcs.py @@ -3,17 +3,23 @@ ''' mcs.py -driver for mcs fluorescence files +Driver for mcs fluorescence files. -Massimo Sandal, Allen Chen (c) 2009 +Copyright 2009 by Massimo Sandal, Allen Chen +with modifications by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. ''' -import libhookecurve as lhc -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): ''' Open the RED (A) ones; the BLUE (D) mirror ones will be automatically opened @@ -25,54 +31,75 @@ class mcsDriver(lhc.Driver): oth[-8]='d' othername=''.join(oth) self.filename=filename - self.othername=othername - + self.othername=othername + #print self.filename, self.othername - + self.filedata=open(filename,'rb') self.reddata=self.filedata.read() self.filedata.close() - + self.filebluedata=open(othername,'rb') #open also the blue ones self.bluedata=self.filebluedata.read() self.filebluedata.close() - + self.filetype = 'mcs' self.experiment = 'smfluo' - - def is_me(self): - if self.filename[-3:].lower()=='mcs': - return True - else: - return False - + def close_all(self): self.filedata.close() self.filebluedata.close() - - + def default_plots(self): + #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'] - - return [main_plot] - - def read_file(self, raw_data): + + 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 + + def is_me(self): + if self.filename[-3:].lower()=='mcs': + return True + else: + return False + + def read_file(self, raw_data): real_data=[] intervalsperfile=struct.unpack('h', raw_data[10:12])[0] #read in number of intervals in this file #this data is contained in bit offset 10-12 in mcs file #see http://docs.python.org/library/struct.html#module-struct for additional explanation - + numbytes=len(raw_data) #data is stored in 4-byte chunks, starting with pos 256 for j in range(0,intervalsperfile): #read in all intervals in file temp=raw_data[256+j*4:256+j*4+4] #data starts at byte offset 256 diff --git a/drivers/mfp1dexport.py b/drivers/mfp1dexport.py new file mode 100644 index 0000000..0214866 --- /dev/null +++ b/drivers/mfp1dexport.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python + +''' +mfp1dexport.py + +Driver for text-exported MFP 1D files. + +Copyright 2009 by Massimo Sandal +with modifications by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' + +import os.path + +import lib.driver +import lib.curve +import lib.plot + +__version__='0.0.0.20090923' + +class mfp1dexportDriver(lib.driver.Driver): + + def __init__(self, filename): + ''' + This is a driver to import Asylum Research MFP 1D data. + Status: experimental + ''' + self.filename = filename + self.filedata = open(filename,'rU') + self.lines = list(self.filedata.readlines()) + self.filedata.close() + + self.filetype = 'mfp1dexport' + self.experiment = 'smfs' + + def _read_columns(self): + + self.raw_columns=self.lines[39:] + + kline=None + for line in self.lines: + if line[:7]=='SpringC': + kline=line + break + + kline=kline.split(':') + + #self.k=float(self.raw_header[23][8:]) + self.k=float(kline[1]) + + #find retract velocity to calculate loading rate + retract_velocity = None + for line in self.lines: + if line.startswith('RetractVelocity:'): + retract_velocity = line.split(':') + self.retract_velocity = float(retract_velocity[1]) + break + + xext=[] + xret=[] + yext=[] + yret=[] + for line in self.raw_columns: + spline=line.split() + xext.append(float(spline[0])) + yext.append(float(spline[1])) + xret.append(float(spline[2])) + yret.append(float(spline[3])) + + return [[xext,yext],[xret,yret]] + + def close_all(self): + self.filedata.close() + + def is_me(self): + try: + self.raw_header = self.lines[0:38] + except: + #Not enough lines for a header; not a good file + return False + + #FIXME: We want a more reasonable header recognition + if self.raw_header[0].startswith('Wave'): + return True + else: + return False + + def default_plots(self): + ''' + loads the curve data + ''' + defl_ext, defl_ret = self.deflection() + yextforce = [i * self.k for i in defl_ext] + yretforce = [i * self.k for i in defl_ret] + + 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 = yextforce + 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 = yretforce + + 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): + self.data = self._read_columns() + return self.data[0][1], self.data[1][1] diff --git a/picoforce.py b/drivers/picoforce.py similarity index 80% rename from picoforce.py rename to drivers/picoforce.py index d331767..5fe17ba 100755 --- a/picoforce.py +++ b/drivers/picoforce.py @@ -1,97 +1,104 @@ #!/usr/bin/env python ''' -libpicoforce.py +picoforce.py Library for interpreting Picoforce force spectroscopy files. -Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy). +Copyright 2006 by Massimo Sandal (University of Bologna, Italy) +with modifications by Dr. Rolf Schmidt (Concordia University, Canada) This program is released under the GNU General Public License version 2. ''' -import re, struct +import os +import re +import struct from scipy import arange -import libhookecurve as lhc +import lib.libhooke as lh +import lib.curve +import lib.driver +import lib.plot -__version__='0.0.0.20080404' +__version__='0.0.0.20090923' class DataChunk(list): #Dummy class to provide ext and ret methods to the data list. - + def ext(self): halflen=(len(self)/2) return self[0:halflen] - + def ret(self): halflen=(len(self)/2) return self[halflen:] -class picoforceDriver(lhc.Driver): +class picoforceDriver(lib.driver.Driver): #Construction and other special methods - - def __init__(self,filename): + + def __init__(self, filename): ''' constructor method ''' - - self.textfile=file(filename) - self.binfile=file(filename,'rb') - + + filename = lh.get_file_path(filename) + self.filename = filename + #The 0,1,2 data chunks are: #0: D (vs T) #1: Z (vs T) #2: D (vs Z) - - - self.filepath=filename - self.debug=False - - self.filetype='picoforce' - self.experiment='smfs' - - + + self.retract_velocity = None + + self.debug = False + + self.filetype = 'picoforce' + self.experiment = 'smfs' + #Hidden methods. These are meant to be used only by API functions. If needed, however, #they can be called just like API methods. - + def _get_samples_line(self): ''' Gets the samples per line parameters in the file, to understand trigger behaviour. ''' - self.textfile.seek(0) - + textfile = file(self.filename) + samps_expr=re.compile(".*Samps") - + samps_values=[] - for line in self.textfile.readlines(): + for line in textfile.readlines(): if samps_expr.match(line): try: samps=int(line.split()[2]) #the third word splitted is the offset (in bytes) samps_values.append(samps) except: pass - + #We raise a flag for the fact we meet an offset, otherwise we would take spurious data length arguments. - + + textfile.close() + return int(samps_values[0]) - + def _get_chunk_coordinates(self): ''' This method gets the coordinates (offset and length) of a data chunk in our Picoforce file. - - It returns a list containing two tuples: - the first element of each tuple is the data_offset, the second is the corresponding + + It returns a list containing two tuples: + the first element of each tuple is the data_offset, the second is the corresponding data size. - - In near future probably each chunk will get its own data structure, with + + In near future probably each chunk will get its own data structure, with offset, size, type, etc. ''' - self.textfile.seek(0) - + textfile = file(self.filename) + offset_expr=re.compile(".*Data offset") length_expr=re.compile(".*Data length") @@ -99,48 +106,53 @@ class picoforceDriver(lhc.Driver): data_sizes=[] flag_offset=0 - for line in self.textfile.readlines(): + for line in textfile.readlines(): if offset_expr.match(line): offset=int(line.split()[2]) #the third word splitted is the offset (in bytes) data_offsets.append(offset) #We raise a flag for the fact we meet an offset, otherwise we would take spurious data length arguments. - flag_offset=1 - + flag_offset=1 + #same for the data length - if length_expr.match(line) and flag_offset: + if length_expr.match(line) and flag_offset: size=int(line.split()[2]) data_sizes.append(size) #Put down the offset flag until the next offset is met. flag_offset=0 + textfile.close() + return zip(data_offsets,data_sizes) - - def _get_data_chunk(self,whichchunk): + + def _get_data_chunk(self, whichchunk): ''' reads a data chunk and converts it in 16bit signed int. ''' + binfile = file(self.filename,'rb') + offset,size=self._get_chunk_coordinates()[whichchunk] - - - self.binfile.seek(offset) - raw_chunk=self.binfile.read(size) - + + binfile.seek(offset) + raw_chunk = binfile.read(size) + my_chunk=[] for data_position in range(0,len(raw_chunk),2): data_unit_bytes=raw_chunk[data_position:data_position+2] #The unpack function converts 2-bytes in a signed int ('h'). #we use output[0] because unpack returns a 1-value tuple, and we want the number only data_unit=struct.unpack('h',data_unit_bytes)[0] - my_chunk.append(data_unit) - + my_chunk.append(data_unit) + + binfile.close() + return DataChunk(my_chunk) - + def _get_Zscan_info(self,index): ''' gets the Z scan informations needed to interpret the data chunk. These info come from the general section, BEFORE individual chunk headers. - + By itself, the function will parse for three parameters. (index) that tells the function what to return when called by exposed API methods. @@ -148,21 +160,21 @@ class picoforceDriver(lhc.Driver): index=1 : returns Zscan_V_start index=2 : returns Zscan_V_size ''' - self.textfile.seek(0) - + textfile = file(self.filename) + ciaoforcelist_expr=re.compile(".*Ciao force") zscanstart_expr=re.compile(".*@Z scan start") zscansize_expr=re.compile(".*@Z scan size") - + ciaoforce_flag=0 - theline=0 - for line in self.textfile.readlines(): +# theline=0 + for line in textfile.readlines(): if ciaoforcelist_expr.match(line): ciaoforce_flag=1 #raise a flag: zscanstart and zscansize params to read are later - + if ciaoforce_flag and zscanstart_expr.match(line): raw_Zscanstart_line=line.split() - + if ciaoforce_flag and zscansize_expr.match(line): raw_Zscansize_line=line.split() @@ -171,36 +183,37 @@ class picoforceDriver(lhc.Driver): for itemscanstart,itemscansize in zip(raw_Zscanstart_line,raw_Zscansize_line): Zscanstart_line.append(itemscanstart.strip('[]()')) Zscansize_line.append(itemscansize.strip('[]()')) - + Zscan_V_LSB=float(Zscanstart_line[6]) Zscan_V_start=float(Zscanstart_line[8]) Zscan_V_size=float(Zscansize_line[8]) - + + textfile.close() + return (Zscan_V_LSB,Zscan_V_start,Zscan_V_size)[index] - + def _get_Z_magnify_scale(self,whichchunk): ''' gets Z scale and Z magnify Here we get Z scale/magnify from the 'whichchunk' only. whichchunk=1,2,3 - TODO: make it coherent with data_chunks syntaxis (0,1,2) - + TODO: make it coherent with data_chunks syntax (0,1,2) + In future, should we divide the *file* itself into chunk descriptions and gain true chunk data structures? ''' - self.textfile.seek(0) - + textfile = file(self.filename) + z_scale_expr=re.compile(".*@4:Z scale") z_magnify_expr=re.compile(".*@Z magnify") - + ramp_size_expr=re.compile(".*@4:Ramp size") ramp_offset_expr=re.compile(".*@4:Ramp offset") - + occurrences=0 found_right=0 - - - for line in self.textfile.readlines(): + + for line in textfile.readlines(): if z_magnify_expr.match(line): occurrences+=1 if occurrences==whichchunk: @@ -208,177 +221,189 @@ class picoforceDriver(lhc.Driver): raw_z_magnify_expression=line.split() else: found_right=0 - + if found_right and z_scale_expr.match(line): raw_z_scale_expression=line.split() if found_right and ramp_size_expr.match(line): raw_ramp_size_expression=line.split() if found_right and ramp_offset_expr.match(line): raw_ramp_offset_expression=line.split() - + + textfile.close() + return float(raw_z_magnify_expression[5]),float(raw_z_scale_expression[7]), float(raw_ramp_size_expression[7]), float(raw_ramp_offset_expression[7]), float(raw_z_scale_expression[5][1:]) - - + + #Exposed APIs. - #These are the methods that are meant to be called from external apps. - + #These are the methods that are meant to be called from external apps. + def LSB_to_volt(self,chunknum,voltrange=20): ''' Converts the LSB data of a given chunk (chunknum=0,1,2) in volts. First step to get the deflection and the force. - + SYNTAXIS: item.LSB_to_volt(chunknum, [voltrange]) - + The voltrange is by default set to 20 V. ''' return DataChunk([((float(lsb)/65535)*voltrange) for lsb in self.data_chunks[chunknum]]) - + def LSB_to_deflection(self,chunknum,deflsensitivity=None,voltrange=20): ''' Converts the LSB data in deflection (meters). - + SYNTAXIS: item.LSB_to_deflection(chunknum, [deflection sensitivity], [voltrange]) - + chunknum is the chunk you want to parse (0,1,2) - - The deflection sensitivity by default is the one parsed from the file. + + The deflection sensitivity by default is the one parsed from the file. The voltrange is by default set to 20 V. ''' if deflsensitivity is None: deflsensitivity=self.get_deflection_sensitivity() - - lsbvolt=self.LSB_to_volt(chunknum) + + lsbvolt=self.LSB_to_volt(chunknum) return DataChunk([volt*deflsensitivity for volt in lsbvolt]) - + def deflection(self): ''' Get the actual force curve deflection. ''' deflchunk= self.LSB_to_deflection(2) return deflchunk.ext(),deflchunk.ret() - + def LSB_to_force(self,chunknum=2,Kspring=None,voltrange=20): ''' Converts the LSB data (of deflection) in force (newtons). - + SYNTAXIS: item.LSB_to_force([chunknum], [spring constant], [voltrange]) - + chunknum is the chunk you want to parse (0,1,2). The chunk used is by default 2. The spring constant by default is the one parsed from the file. The voltrange is by default set to 20 V. ''' if Kspring is None: Kspring=self.get_spring_constant() - - lsbdefl=self.LSB_to_deflection(chunknum) + + lsbdefl=self.LSB_to_deflection(chunknum) return DataChunk([(meter*Kspring) for meter in lsbdefl]) - + def get_Zscan_V_start(self): return self._get_Zscan_info(1) - + def get_Zscan_V_size(self): return self._get_Zscan_info(2) - + def get_Z_scan_sensitivity(self): ''' gets Z sensitivity ''' - self.textfile.seek(0) - + textfile = file(self.filename) + z_sensitivity_expr=re.compile(".*@Sens. Zsens") - - for line in self.textfile.readlines(): + + for line in textfile.readlines(): if z_sensitivity_expr.match(line): z_sensitivity=float(line.split()[3]) + + textfile.close() + #return it in SI units (that is: m/V, not nm/V) return z_sensitivity*(10**(-9)) - + def get_Z_magnify(self,whichchunk): ''' Gets the Z magnify factor. Normally it is 1, unknown exact use as of 2006-01-13 ''' return self._get_Z_magnify_scale(whichchunk)[0] - + def get_Z_scale(self,whichchunk): ''' Gets the Z scale. ''' return self._get_Z_magnify_scale(whichchunk)[1] - + def get_ramp_size(self,whichchunk): ''' Gets the -user defined- ramp size ''' return self._get_Z_magnify_scale(whichchunk)[2] - + def get_ramp_offset(self,whichchunk): ''' Gets the ramp offset ''' return self._get_Z_magnify_scale(whichchunk)[3] - + def get_Z_scale_LSB(self,whichchunk): ''' Gets the LSB-to-volt conversion factor of the Z data. (so called hard-scale in the Nanoscope documentation) - + ''' return self._get_Z_magnify_scale(whichchunk)[4] - + def get_deflection_sensitivity(self): ''' gets deflection sensitivity - ''' - self.textfile.seek(0) - + ''' + textfile = file(self.filename) + def_sensitivity_expr=re.compile(".*@Sens. DeflSens") - - for line in self.textfile.readlines(): + + for line in textfile.readlines(): if def_sensitivity_expr.match(line): def_sensitivity=float(line.split()[3]) break + + textfile.close() + #return it in SI units (that is: m/V, not nm/V) return def_sensitivity*(10**(-9)) - + def get_spring_constant(self): ''' gets spring constant. We actually find *three* spring constant values, one for each data chunk (F/t, Z/t, F/z). They are normally all equal, but we retain all three for future... ''' - self.textfile.seek(0) - + textfile = file(self.filename) + springconstant_expr=re.compile(".*Spring Constant") - + constants=[] - - for line in self.textfile.readlines(): + + for line in textfile.readlines(): if springconstant_expr.match(line): constants.append(float(line.split()[2])) - + + textfile.close() + return constants[0] - + def get_Zsensorsens(self): ''' gets Zsensorsens for Z data. - + This is the sensitivity needed to convert the LSB data in nanometers for the Z-vs-T data chunk. - ''' - self.textfile.seek(0) - + ''' + textfile = file(self.filename) + zsensorsens_expr=re.compile(".*Sens. ZSensorSens") - - for line in self.textfile.readlines(): + + for line in textfile.readlines(): if zsensorsens_expr.match(line): zsensorsens_raw_expression=line.split() #we must take only first occurrence, so we exit from the cycle immediately break - + + textfile.close() + return (float(zsensorsens_raw_expression[3]))*(10**(-9)) - + def Z_data(self): ''' returns converted ext and ret Z curves. @@ -390,18 +415,18 @@ class picoforceDriver(lhc.Driver): #rampsize_zt=self.get_ramp_size(2) #rampoffset_zt=self.get_ramp_offset(2) zsensorsens=self.get_Zsensorsens() - + ''' The magic formula that converts the Z data is: - + meters = LSB * V_lsb_conversion_factor * ZSensorSens ''' - + #z_curves=[item*Zlsb_zt*zsensorsens for item in self.data_chunks[1].pair['ext']],[item*Zlsb_zt*zsensorsens for item in self.data_chunks[1].pair['ret']] - z_curves=[item*Zlsb_zt*zsensorsens for item in self.data_chunks[1].ext()],[item*Zlsb_zt*zsensorsens for item in self.data_chunks[1].ret()] + z_curves=[item*Zlsb_zt*zsensorsens for item in self.data_chunks[1].ext()],[item*Zlsb_zt*zsensorsens for item in self.data_chunks[1].ret()] z_curves=[DataChunk(item) for item in z_curves] return z_curves - + def Z_extremes(self): ''' returns the extremes of the Z values @@ -410,9 +435,9 @@ class picoforceDriver(lhc.Driver): z_extremes={} z_extremes['ext']=zcurves[0][0],zcurves[0][-1] z_extremes['ret']=zcurves[1][0],zcurves[1][-1] - + return z_extremes - + def Z_step(self): ''' returns the calculated step between the Z values @@ -421,10 +446,10 @@ class picoforceDriver(lhc.Driver): zpoints={} z_extremes=self.Z_extremes() - + zrange['ext']=abs(z_extremes['ext'][0]-z_extremes['ext'][1]) zrange['ret']=abs(z_extremes['ret'][0]-z_extremes['ret'][1]) - + #We must take 1 from the calculated zpoints, or when I use the arange function gives me a point more #with the step. That is, if I have 1000 points, and I use arange(start,stop,step), I have 1001 points... #For cleanness, solution should really be when using arange, but oh well... @@ -432,17 +457,17 @@ class picoforceDriver(lhc.Driver): zpoints['ret']=len(self.Z_data()[1])-1 #this syntax must become coherent!! return (zrange['ext']/zpoints['ext']),(zrange['ret']/zpoints['ret']) - + def Z_domains(self): ''' returns the Z domains on which to plot the force data. - + The Z domains are returned as a single long DataChunk() extended list. The extension and retraction part - can be extracted using ext() and ret() methods. - ''' + can be extracted using ext() and ret() methods. + ''' x1step=self.Z_step()[0] - x2step=self.Z_step()[1] - + x2step=self.Z_step()[1] + try: xext=arange(self.Z_extremes()['ext'][0],self.Z_extremes()['ext'][1],-x1step) xret=arange(self.Z_extremes()['ret'][0],self.Z_extremes()['ret'][1],-x2step) @@ -450,7 +475,7 @@ class picoforceDriver(lhc.Driver): xext=arange(0,1) xret=arange(0,1) print 'picoforce.py: Warning. xext, xret domains cannot be extracted.' - + if not (len(xext)==len(xret)): if self.debug: #print warning @@ -460,36 +485,36 @@ class picoforceDriver(lhc.Driver): print "You cannot trust the resulting curve." print "Until a solution is found, I substitute the ext domain with the ret domain. Sorry." xext=xret - + return DataChunk(xext.tolist()+xret.tolist()) - + def Z_scan_size(self): return self.get_Zscan_V_size()*self.get_Z_scan_sensitivity() - + def Z_start(self): return self.get_Zscan_V_start()*self.get_Z_scan_sensitivity() - + def ramp_size(self,whichchunk): ''' to be implemented if needed ''' raise "Not implemented yet." - - + + def ramp_offset(self,whichchunk): ''' to be implemented if needed ''' raise "Not implemented yet." - + def detriggerize(self, forcext): ''' Cuts away the trigger-induced s**t on the extension curve. DEPRECATED cutindex=2 startvalue=forcext[0] - - for index in range(len(forcext)-1,2,-2): + + for index in range(len(forcext)-1,2,-2): if forcext[index]>startvalue: cutindex=index else: @@ -498,48 +523,61 @@ class picoforceDriver(lhc.Driver): return cutindex ''' return 0 - + def is_me(self): ''' self-identification of file type magic ''' - curve_file=file(self.filepath) + curve_file=file(self.filename) header=curve_file.read(30) curve_file.close() - + if header[2:17] == 'Force file list': #header of a picoforce file self.data_chunks=[self._get_data_chunk(num) for num in [0,1,2]] return True else: return False - + def close_all(self): ''' Explicitly closes all files ''' - self.textfile.close() - self.binfile.close() - + pass + def default_plots(self): ''' - creates the default PlotObject + loads the curve data ''' - - - force=self.LSB_to_force() - zdomain=self.Z_domains() - - samples=self._get_samples_line() + force = self.LSB_to_force() + zdomain = self.Z_domains() + + samples = self._get_samples_line() #cutindex=0 #cutindex=self.detriggerize(force.ext()) - - main_plot=lhc.PlotObject() - - main_plot.vectors=[[zdomain.ext()[0:samples], force.ext()[0:samples]],[zdomain.ret()[0:samples], force.ret()[0:samples]]] - main_plot.normalize_vectors() - main_plot.units=['meters','newton'] - main_plot.destination=0 - main_plot.title=self.filepath - - - return [main_plot] + 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 = zdomain.ext()[0:samples] + extension.y = force.ext()[0:samples] + retraction.color = 'blue' + retraction.label = 'retraction' + retraction.style = 'plot' + retraction.title = 'Force curve' + retraction.units.x = 'm' + retraction.units.y = 'N' + retraction.x = zdomain.ret()[0:samples] + retraction.y = force.ret()[0:samples] + + plot = lib.plot.Plot() + plot.title = os.path.basename(self.filename) + plot.curves.append(extension) + plot.curves.append(retraction) + + plot.normalize() + return plot diff --git a/fit.py b/fit.py deleted file mode 100755 index 10d0331..0000000 --- a/fit.py +++ /dev/null @@ -1,455 +0,0 @@ -#!/usr/bin/env python - -''' -FIT - -Force spectroscopy curves basic fitting plugin. -Licensed under the GNU GPL version 2 - -Non-standard Dependencies: -procplots.py (plot processing plugin) -''' -from libhooke import WX_GOOD, ClickedPoint -import wxversion -wxversion.select(WX_GOOD) -#from wx import PostEvent -#from wx.lib.newevent import NewEvent -import scipy -import scipy.odr -import numpy as np -import copy -import Queue - -global measure_wlc -global EVT_MEASURE_WLC - -#measure_wlc, EVT_MEASURE_WLC = NewEvent() - -global events_from_fit -events_from_fit=Queue.Queue() #GUI ---> CLI COMMUNICATION - - -class fitCommands: - - def _plug_init(self): - self.wlccurrent=None - self.wlccontact_point=None - self.wlccontact_index=None - - def wlc_fit(self,clicked_points,xvector,yvector, pl_value, T=293, return_errors=False): - ''' - Worm-like chain model fitting. - The function is the simple polynomial worm-like chain as proposed by C.Bustamante, J.F.Marko, E.D.Siggia - and S.Smith (Science. 1994 Sep 9;265(5178):1599-600.) - ''' - - '''clicked_points[0] = contact point (calculated or hand-clicked) - clicked_points[1] and [2] are edges of chunk''' - - #STEP 1: Prepare the vectors to apply the fit. - - if pl_value is not None: - pl_value=pl_value/(10**9) - - #indexes of the selected chunk - first_index=min(clicked_points[1].index, clicked_points[2].index) - last_index=max(clicked_points[1].index, clicked_points[2].index) - - #getting the chunk and reverting it - xchunk,ychunk=xvector[first_index:last_index],yvector[first_index:last_index] - xchunk.reverse() - ychunk.reverse() - #put contact point at zero and flip around the contact point (the fit wants a positive growth for extension and force) - xchunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xchunk] - ychunk_corr_up=[-(y-clicked_points[0].graph_coords[1]) for y in ychunk] - - #make them arrays - xchunk_corr_up=scipy.array(xchunk_corr_up) - ychunk_corr_up=scipy.array(ychunk_corr_up) - - - #STEP 2: actually do the fit - - #Find furthest point of chunk and add it a bit; the fit must converge - #from an excess! - xchunk_high=max(xchunk_corr_up) - xchunk_high+=(xchunk_high/10) - - #Here are the linearized start parameters for the WLC. - #[lambd=1/Lo , pii=1/P] - - p0=[(1/xchunk_high),(1/(3.5e-10))] - p0_plfix=[(1/xchunk_high)] - ''' - ODR STUFF - fixme: remove these comments after testing - ''' - - - def f_wlc(params,x,T=T): - ''' - wlc function for ODR fitting - ''' - lambd,pii=params - Kb=(1.38065e-23) - therm=Kb*T - y=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd)) - return y - - def f_wlc_plfix(params,x,pl_value=pl_value,T=T): - ''' - wlc function for ODR fitting - ''' - lambd=params - pii=1/pl_value - Kb=(1.38065e-23) - therm=Kb*T - y=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd)) - return y - - #make the ODR fit - realdata=scipy.odr.RealData(xchunk_corr_up,ychunk_corr_up) - if pl_value: - model=scipy.odr.Model(f_wlc_plfix) - o = scipy.odr.ODR(realdata, model, p0_plfix) - else: - model=scipy.odr.Model(f_wlc) - o = scipy.odr.ODR(realdata, model, p0) - - o.set_job(fit_type=2) - out=o.run() - fit_out=[(1/i) for i in out.beta] - - #Calculate fit errors from output standard deviations. - #We must propagate the error because we fit the *inverse* parameters! - #The error = (error of the inverse)*(value**2) - fit_errors=[] - for sd,value in zip(out.sd_beta, fit_out): - err_real=sd*(value**2) - fit_errors.append(err_real) - - def wlc_eval(x,params,pl_value,T): - ''' - Evaluates the WLC function - ''' - if not pl_value: - lambd, pii = params - else: - lambd = params - - if pl_value: - pii=1/pl_value - - Kb=(1.38065e-23) #boltzmann constant - therm=Kb*T #so we have thermal energy - - return ( (therm*pii/4.0) * (((1-(x*lambd))**-2.0) - 1 + (4.0*x*lambd)) ) - - #STEP 3: plotting the fit - - #obtain domain to plot the fit - from contact point to last_index plus 20 points - thule_index=last_index+10 - if thule_index > len(xvector): #for rare cases in which we fit something at the END of whole curve. - thule_index = len(xvector) - #reverse etc. the domain - xfit_chunk=xvector[clicked_points[0].index:thule_index] - xfit_chunk.reverse() - xfit_chunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xfit_chunk] - xfit_chunk_corr_up=scipy.array(xfit_chunk_corr_up) - - #the fitted curve: reflip, re-uncorrect - yfit=wlc_eval(xfit_chunk_corr_up, out.beta, pl_value,T) - yfit_down=[-y for y in yfit] - yfit_corr_down=[y+clicked_points[0].graph_coords[1] for y in yfit_down] - - if return_errors: - return fit_out, yfit_corr_down, xfit_chunk, fit_errors - else: - return fit_out, yfit_corr_down, xfit_chunk, None - - - def do_wlc(self,args): - ''' - WLC - (fit plugin) - Fits a worm-like chain entropic rise to a given chunk of the curve. - - First you have to click a contact point. - Then you have to click the two edges of the data you want to fit. - The function is the simple polynomial worm-like chain as proposed by - C.Bustamante, J.F.Marko, E.D.Siggia and S.Smith (Science. 1994 - Sep 9;265(5178):1599-600.) - - Arguments: - pl=[value] : Use a fixed persistent length for the fit. If pl is not given, - the fit will be a 2-variable - fit. DO NOT put spaces between 'pl', '=' and the value. - The value must be in nanometers. - - t=[value] : Use a user-defined temperature. The value must be in - kelvins; by default it is 293 K. - DO NOT put spaces between 't', '=' and the value. - - noauto : allows for clicking the contact point by - hand (otherwise it is automatically estimated) the first time. - If subsequent measurements are made, the same contact point - clicked is used - - reclick : redefines by hand the contact point, if noauto has been used before - but the user is unsatisfied of the previously choosen contact point. - --------- - Syntax: wlc [pl=(value)] [t=value] [noauto] - ''' - pl_value=None - T=self.config['temperature'] - for arg in args.split(): - #look for a persistent length argument. - if 'pl=' in arg: - pl_expression=arg.split('=') - pl_value=float(pl_expression[1]) #actual value - #look for a T argument. FIXME: spaces are not allowed between 'pl' and value - if ('t=' in arg[0:2]) or ('T=' in arg[0:2]): - t_expression=arg.split('=') - T=float(t_expression[1]) - - #use the currently displayed plot for the fit - displayed_plot=self._get_displayed_plot() - - #handle contact point arguments correctly - if 'reclick' in args.split(): - print 'Click contact point' - contact_point=self._measure_N_points(N=1, whatset=1)[0] - contact_point_index=contact_point.index - self.wlccontact_point=contact_point - self.wlccontact_index=contact_point.index - self.wlccurrent=self.current.path - elif 'noauto' in args.split(): - if self.wlccontact_index==None or self.wlccurrent != self.current.path: - print 'Click contact point' - contact_point=self._measure_N_points(N=1, whatset=1)[0] - contact_point_index=contact_point.index - self.wlccontact_point=contact_point - self.wlccontact_index=contact_point.index - self.wlccurrent=self.current.path - else: - contact_point=self.wlccontact_point - contact_point_index=self.wlccontact_index - else: - cindex=self.find_contact_point() - contact_point=ClickedPoint() - contact_point.absolute_coords=displayed_plot.vectors[1][0][cindex], displayed_plot.vectors[1][1][cindex] - contact_point.find_graph_coords(displayed_plot.vectors[1][0], displayed_plot.vectors[1][1]) - contact_point.is_marker=True - - print 'Click edges of chunk' - points=self._measure_N_points(N=2, whatset=1) - points=[contact_point]+points - try: - params, yfit, xfit, fit_errors = self.wlc_fit(points, displayed_plot.vectors[1][0], displayed_plot.vectors[1][1],pl_value,T, return_errors=True ) - except: - print 'Fit not possible. Probably wrong interval -did you click two *different* points?' - return - - print 'Contour length: ',params[0]*(1.0e+9),' nm' - to_dump='contour '+self.current.path+' '+str(params[0]*(1.0e+9))+' nm' - self.outlet.push(to_dump) - if len(params)==2: #if we did choose 2-value fit - print 'Persistent length: ',params[1]*(1.0e+9),' nm' - to_dump='persistent '+self.current.path+' '+str(params[1]*(1.0e+9))+' nm' - self.outlet.push(to_dump) - - if fit_errors: - fit_nm=[i*(10**9) for i in fit_errors] - print 'Standard deviation (contour length)', fit_nm[0] - if len(fit_nm)>1: - print 'Standard deviation (persistent length)', fit_nm[1] - - - #add the clicked points in the final PlotObject - clickvector_x, clickvector_y=[], [] - for item in points: - clickvector_x.append(item.graph_coords[0]) - clickvector_y.append(item.graph_coords[1]) - - #create a custom PlotObject to gracefully plot the fit along the curves - - fitplot=copy.deepcopy(displayed_plot) - fitplot.add_set(xfit,yfit) - fitplot.add_set(clickvector_x,clickvector_y) - - #FIXME: this colour/styles stuff must be solved at the root! - if fitplot.styles==[]: - fitplot.styles=[None,None,None,'scatter'] - else: - fitplot.styles+=[None,'scatter'] - - if fitplot.colors==[]: - fitplot.colors=[None,None,None,None] - else: - fitplot.colors+=[None,None] - - self._send_plot([fitplot]) - - def find_contact_point(self,plot=False): - ''' - Finds the contact point on the curve. - - The current algorithm (thanks to Francesco Musiani, francesco.musiani@unibo.it and Massimo Sandal) is: - - take care of the PicoForce trigger bug - exclude retraction portions with too high standard deviation - - fit the second half of the retraction curve to a line - - if the fit is not almost horizontal, take a smaller chunk and repeat - - otherwise, we have something horizontal - - so take the average of horizontal points and use it as a baseline - - Then, start from the rise of the retraction curve and look at the first point below the - baseline. - - FIXME: should be moved, probably to generalvclamp.py - ''' - - if not plot: - plot=self.plots[0] - - outplot=self.subtract_curves(1) - xret=outplot.vectors[1][0] - ydiff=outplot.vectors[1][1] - - xext=plot.vectors[0][0] - yext=plot.vectors[0][1] - xret2=plot.vectors[1][0] - yret=plot.vectors[1][1] - - #taking care of the picoforce trigger bug: we exclude portions of the curve that have too much - #standard deviation. yes, a lot of magic is here. - monster=True - monlength=len(xret)-int(len(xret)/20) - finalength=len(xret) - while monster: - monchunk=scipy.array(ydiff[monlength:finalength]) - if abs(scipy.stats.std(monchunk)) < 2e-10: - monster=False - else: #move away from the monster - monlength-=int(len(xret)/50) - finalength-=int(len(xret)/50) - - - #take half of the thing - endlength=int(len(xret)/2) - - ok=False - - while not ok: - xchunk=yext[endlength:monlength] - ychunk=yext[endlength:monlength] - regr=scipy.stats.linregress(xchunk,ychunk)[0:2] - #we stop if we found an almost-horizontal fit or if we're going too short... - #FIXME: 0.1 and 6 here are "magic numbers" (although reasonable) - if (abs(regr[1]) > 0.1) and ( endlength < len(xret)-int(len(xret)/6) ) : - endlength+=10 - else: - ok=True - - - ymean=scipy.mean(ychunk) #baseline - - index=0 - point = ymean+1 - - #find the first point below the calculated baseline - while point > ymean: - try: - point=yret[index] - index+=1 - except IndexError: - #The algorithm didn't find anything below the baseline! It should NEVER happen - index=0 - return index - - return index - - - - def find_contact_point2(self, debug=False): - ''' - TO BE DEVELOPED IN THE FUTURE - Finds the contact point on the curve. - - FIXME: should be moved, probably to generalvclamp.py - ''' - - #raw_plot=self.current.curve.default_plots()[0] - raw_plot=self.plots[0] - '''xext=self.plots[0].vectors[0][0] - yext=self.plots[0].vectors[0][1] - xret2=self.plots[0].vectors[1][0] - yret=self.plots[0].vectors[1][1] - ''' - xext=raw_plot.vectors[0][0] - yext=raw_plot.vectors[0][1] - xret2=raw_plot.vectors[1][0] - yret=raw_plot.vectors[1][1] - - first_point=[xext[0], yext[0]] - last_point=[xext[-1], yext[-1]] - - #regr=scipy.polyfit(first_point, last_point,1)[0:2] - diffx=abs(first_point[0]-last_point[0]) - diffy=abs(first_point[1]-last_point[1]) - - #using polyfit results in numerical errors. good old algebra. - a=diffy/diffx - b=first_point[1]-(a*first_point[0]) - baseline=scipy.polyval((a,b), xext) - - ysub=[item-basitem for item,basitem in zip(yext,baseline)] - - contact=ysub.index(min(ysub)) - - return xext,ysub,contact - - #now, exploit a ClickedPoint instance to calculate index... - dummy=ClickedPoint() - dummy.absolute_coords=(x_intercept,y_intercept) - dummy.find_graph_coords(xret2,yret) - - if debug: - return dummy.index, regr, regr_contact - else: - return dummy.index - - - - def x_do_contact(self,args): - ''' - DEBUG COMMAND to be activated in the future - ''' - xext,ysub,contact=self.find_contact_point2(debug=True) - - contact_plot=self.plots[0] - contact_plot.add_set(xext,ysub) - contact_plot.add_set([xext[contact]],[self.plots[0].vectors[0][1][contact]]) - #contact_plot.add_set([first_point[0]],[first_point[1]]) - #contact_plot.add_set([last_point[0]],[last_point[1]]) - contact_plot.styles=[None,None,None,'scatter'] - self._send_plot([contact_plot]) - return - - - index,regr,regr_contact=self.find_contact_point2(debug=True) - print regr - print regr_contact - raw_plot=self.current.curve.default_plots()[0] - xret=raw_plot.vectors[0][0] - #nc_line=[(item*regr[0])+regr[1] for item in x_nc] - nc_line=scipy.polyval(regr,xret) - c_line=scipy.polyval(regr_contact,xret) - - - contact_plot=self.current.curve.default_plots()[0] - contact_plot.add_set(xret, nc_line) - contact_plot.add_set(xret, c_line) - contact_plot.styles=[None,None,None,None] - #contact_plot.styles.append(None) - contact_plot.destination=1 - self._send_plot([contact_plot]) - \ No newline at end of file diff --git a/flatfilts.py b/flatfilts.py deleted file mode 100755 index b287527..0000000 --- a/flatfilts.py +++ /dev/null @@ -1,427 +0,0 @@ -#!/usr/bin/env python - -''' -FLATFILTS - -Force spectroscopy curves filtering of flat curves -Licensed under the GNU LGPL version 2 - -Other plugin dependencies: -procplots.py (plot processing plugin) -''' -from libhooke import WX_GOOD -import wxversion -wxversion.select(WX_GOOD) - -import xml.dom.minidom - -import wx -import scipy -import numpy -from numpy import diff - -#import pickle - -import libpeakspot as lps -import libhookecurve as lhc - - -class flatfiltsCommands: - - def _plug_init(self): - #configurate convfilt variables - convfilt_configurator=ConvfiltConfig() - - #different OSes have different path conventions - if self.config['hookedir'][0]=='/': - slash='/' #a Unix or Unix-like system - else: - slash='\\' #it's a drive letter, we assume it's Windows - - self.convfilt_config=convfilt_configurator.load_config(self.config['hookedir']+slash+'convfilt.conf') - - def do_flatfilt(self,args): - ''' - FLATFILT - (flatfilts.py) - Filters out flat (featureless) curves of the current playlist, - creating a playlist containing only the curves 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. - ''' - median_filter=7 - min_npks=4 - min_deviation=9 - - args=args.split(' ') - if len(args) == 2: - min_npks=int(args[0]) - min_deviation=int(args[1]) - else: - pass - - print 'Processing playlist...' - notflat_list=[] - - c=0 - for item in self.current_list: - c+=1 - - try: - notflat=self.has_features(item, median_filter, min_npks, min_deviation) - print 'Curve',item.path, 'is',c,'of',len(self.current_list),': features are ',notflat - except: - notflat=False - print 'Curve',item.path, 'is',c,'of',len(self.current_list),': cannot be filtered. Probably unable to retrieve force data from corrupt file.' - - if notflat: - item.features=notflat - item.curve=None #empty the item object, to further avoid memory leak - notflat_list.append(item) - - if len(notflat_list)==0: - print 'Found nothing interesting. Check your playlist, could be a bug or criteria could be too much stringent' - return - else: - print 'Found ',len(notflat_list),' potentially interesting curves' - print 'Regenerating playlist...' - self.pointer=0 - self.current_list=notflat_list - self.current=self.current_list[self.pointer] - self.do_plot(0) - - def has_features(self,item,median_filter,min_npks,min_deviation): - ''' - 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. - ''' - retvalue=False - - item.identify(self.drivers) - #we assume the first is the plot with the force curve - #do the median to better resolve features from noise - flat_plot=self.plotmanip_median(item.curve.default_plots()[0], item, customvalue=median_filter) - flat_vects=flat_plot.vectors - item.curve.close_all() - #needed to avoid *big* memory leaks! - del item.curve - del item - - #absolute value of derivate - yretdiff=diff(flat_vects[1][1]) - yretdiff=[abs(value) for value in yretdiff] - #average of derivate values - diffmean=numpy.mean(yretdiff) - yretdiff.sort() - yretdiff.reverse() - c_pks=0 - for value in yretdiff: - if value/diffmean > min_deviation: - c_pks+=1 - else: - break - - if c_pks>=min_npks: - retvalue = c_pks - - del flat_plot, flat_vects, yretdiff - - return retvalue - - ################################################################ - #-----CONVFILT------------------------------------------------- - #-----Convolution-based peak recognition and filtering. - #Requires the libpeakspot.py library - - def has_peaks(self, plot, abs_devs=None): - ''' - Finds peak position in a force curve. - FIXME: should be moved in libpeakspot.py - ''' - if abs_devs==None: - abs_devs=self.convfilt_config['mindeviation'] - - - xret=plot.vectors[1][0] - yret=plot.vectors[1][1] - #Calculate convolution. - convoluted=lps.conv_dx(yret, self.convfilt_config['convolution']) - - #surely cut everything before the contact point - cut_index=self.find_contact_point(plot) - #cut even more, before the blind window - start_x=xret[cut_index] - blind_index=0 - for value in xret[cut_index:]: - if abs((value) - (start_x)) > self.convfilt_config['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:], self.convfilt_config['positive'], self.convfilt_config['maxcut'], self.convfilt_config['stable']) - above=lps.abovenoise(convoluted,noise_level,cut_index,abs_devs) - peak_location,peak_size=lps.find_peaks(above,seedouble=self.convfilt_config['seedouble']) - - #take the maximum - for i in range(len(peak_location)): - peak=peak_location[i] - maxpk=min(yret[peak-10:peak+10]) - index_maxpk=yret[peak-10:peak+10].index(maxpk)+(peak-10) - peak_location[i]=index_maxpk - - return peak_location,peak_size - - - def exec_has_peaks(self,item,abs_devs): - ''' - encapsulates has_peaks for the purpose of correctly treating the curve objects in the convfilt loop, - to avoid memory leaks - ''' - item.identify(self.drivers) - #we assume the first is the plot with the force curve - plot=item.curve.default_plots()[0] - - if 'flatten' in self.config['plotmanips']: - #If flatten is present, use it for better recognition of peaks... - flatten=self._find_plotmanip('flatten') #extract flatten plot manipulator - plot=flatten(plot, item, customvalue=1) - - peak_location,peak_size=self.has_peaks(plot,abs_devs) - #close all open files - item.curve.close_all() - #needed to avoid *big* memory leaks! - del item.curve - del item - return peak_location, peak_size - - #------------------------ - #------commands---------- - #------------------------ - def do_peaks(self,args): - ''' - PEAKS - (flatfilts.py) - Test command for convolution filter / test. - ---- - Syntax: peaks [deviations] - absolute deviation = number of times the convolution signal is above the noise absolute deviation. - Default is 5. - ''' - if len(args)==0: - args=self.convfilt_config['mindeviation'] - - try: - abs_devs=float(args) - except: - pass - - defplots=self.current.curve.default_plots()[0] #we need the raw, uncorrected plots - - if 'flatten' in self.config['plotmanips']: - flatten=self._find_plotmanip('flatten') #extract flatten plot manipulator - defplots=flatten(defplots, self.current) - else: - print 'You have the flatten plot manipulator not loaded. Enabling it could give you better results.' - - peak_location,peak_size=self.has_peaks(defplots,abs_devs) - print 'Found '+str(len(peak_location))+' peaks.' - to_dump='peaks '+self.current.path+' '+str(len(peak_location)) - self.outlet.push(to_dump) - #print peak_location - - #if no peaks, we have nothing to plot. exit. - if len(peak_location)==0: - return - - #otherwise, we plot the peak locations. - xplotted_ret=self.plots[0].vectors[1][0] - yplotted_ret=self.plots[0].vectors[1][1] - xgood=[xplotted_ret[index] for index in peak_location] - ygood=[yplotted_ret[index] for index in peak_location] - - recplot=self._get_displayed_plot() - recplot.vectors.append([xgood,ygood]) - if recplot.styles==[]: - recplot.styles=[None,None,'scatter'] - recplot.colors=[None,None,None] - else: - recplot.styles+=['scatter'] - recplot.colors+=[None] - - self._send_plot([recplot]) - - def do_convfilt(self,args): - ''' - CONVFILT - (flatfilts.py) - Filters out flat (featureless) curves of the current playlist, - creating a playlist containing only the curves with potential - features. - ------------ - Syntax: - convfilt [min_npks min_deviation] - - min_npks = minmum number of peaks - (to set the default, see convfilt.conf file; CONVCONF and SETCONF commands) - - min_deviation = minimum signal/noise ratio *in the convolution* - (to set the default, see convfilt.conf file; CONVCONF and SETCONF commands) - - If called without arguments, it uses default values. - ''' - - min_npks=self.convfilt_config['minpeaks'] - min_deviation=self.convfilt_config['mindeviation'] - - args=args.split(' ') - if len(args) == 2: - min_npks=int(args[0]) - min_deviation=int(args[1]) - else: - pass - - print 'Processing playlist...' - print '(Please wait)' - notflat_list=[] - - c=0 - for item in self.current_list: - c+=1 - - try: - peak_location,peak_size=self.exec_has_peaks(item,min_deviation) - if len(peak_location)>=min_npks: - isok='+' - else: - isok='' - print 'Curve',item.path, 'is',c,'of',len(self.current_list),': found '+str(len(peak_location))+' peaks.'+isok - except: - peak_location,peak_size=[],[] - print 'Curve',item.path, 'is',c,'of',len(self.current_list),': cannot be filtered. Probably unable to retrieve force data from corrupt file.' - - if len(peak_location)>=min_npks: - item.peak_location=peak_location - item.peak_size=peak_size - item.curve=None #empty the item object, to further avoid memory leak - notflat_list.append(item) - - #Warn that no flattening had been done. - if not ('flatten' in self.config['plotmanips']): - print 'Flatten manipulator was not found. Processing was done without flattening.' - print 'Try to enable it in your configuration file for better results.' - - if len(notflat_list)==0: - print 'Found nothing interesting. Check your playlist, could be a bug or criteria could be too much stringent' - return - else: - print 'Found ',len(notflat_list),' potentially interesting curves' - print 'Regenerating playlist...' - self.pointer=0 - self.current_list=notflat_list - self.current=self.current_list[self.pointer] - self.do_plot(0) - - - def do_setconv(self,args): - ''' - SETCONV - (flatfilts.py) - Sets the convfilt configuration variables - ------ - Syntax: setconv variable value - ''' - args=args.split() - #FIXME: a general "set dictionary" function has to be built - if len(args)==0: - print self.convfilt_config - else: - if not (args[0] in self.convfilt_config.keys()): - print 'This is not an internal convfilt variable!' - print 'Run "setconv" without arguments to see a list of defined variables.' - return - - if len(args)==1: - print self.convfilt_config[args[0]] - elif len(args)>1: - try: - self.convfilt_config[args[0]]=eval(args[1]) - except NameError: #we have a string argument - self.convfilt_config[args[0]]=args[1] - - -######################### -#HANDLING OF CONFIGURATION FILE -class ConvfiltConfig: - ''' - Handling of convfilt configuration file - - Mostly based on the simple-yet-useful examples of the Python Library Reference - about xml.dom.minidom - - FIXME: starting to look a mess, should require refactoring - ''' - - def __init__(self): - self.config={} - - - def load_config(self, filename): - myconfig=file(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...) - #yes, I'm an XML n00b - the_file=myconfig.read() - the_file_lines=the_file.split('\n') - the_file=''.join(the_file_lines) - - self.config_tree=xml.dom.minidom.parseString(the_file) - - def getText(nodelist): - #take the text from a nodelist - #from Python Library Reference 13.7.2 - rc = '' - for node in nodelist: - if node.nodeType == node.TEXT_NODE: - rc += node.data - return rc - - def handleConfig(config): - noiseabsdev_elements=config.getElementsByTagName("noise_absdev") - convfilt_elements=config.getElementsByTagName("convfilt") - handleAbsdev(noiseabsdev_elements) - handleConvfilt(convfilt_elements) - - def handleAbsdev(noiseabsdev_elements): - for element in noiseabsdev_elements: - for attribute in element.attributes.keys(): - self.config[attribute]=element.getAttribute(attribute) - - def handleConvfilt(convfilt_elements): - for element in convfilt_elements: - for attribute in element.attributes.keys(): - self.config[attribute]=element.getAttribute(attribute) - - handleConfig(self.config_tree) - #making items in the dictionary machine-readable - for item in self.config.keys(): - try: - self.config[item]=eval(self.config[item]) - except NameError: #if it's an unreadable string, keep it as a string - pass - - return self.config \ No newline at end of file diff --git a/generalclamp.py b/generalclamp.py deleted file mode 100644 index 4ac30e7..0000000 --- a/generalclamp.py +++ /dev/null @@ -1,251 +0,0 @@ -#!/usr/bin/env python - -''' -GENERALCLAMP.py - -Plugin regarding general force clamp measurements -''' -from libhooke import WX_GOOD, ClickedPoint -import wxversion -import libhookecurve as lhc -wxversion.select(WX_GOOD) -from wx import PostEvent - -class generalclampCommands: - - def plotmanip_clamp(self, plot, current, customvalue=False): - ''' - Handles some viewing options for the "force clamp" data format, depending on the state of these configuration variables: - (1) If self.config['fc_showphase'] != 0, the 'phase' data column (i.e. the 2nd) is shown in the 0th graph (else it isn't) - (2) If self.config['fc_showimposed'] != 0, the 'imposed deflection' data column (i.e. the 5th) is shown in the 1st graph (else it isn't) - (3) If self.config['fc_interesting'] == 0, the entire curve is shown in the graphs; if it has a non-zero value N, only phase N is shown. - - NOTE - my implementation of point(3) feels quite awkward - someone smarter than me plz polish that! - - ''' - - #not a fclamp curve... - if current.curve.experiment != 'clamp': - return plot - - if self.config['fc_interesting'] != 0 and plot.destination==0: - lower = int((self.config['fc_interesting'])-1) - upper = int((self.config['fc_interesting'])+1) - trim = current.curve.trimindexes()[lower:upper] - newtime = [] - newzpiezo = [] - newphase = [] - for x in range(trim[0],trim[1]): - newtime.append(self.plots[0].vectors[0][0][x]) - newzpiezo.append(self.plots[0].vectors[0][1][x]) - newphase.append(self.plots[0].vectors[1][1][x]) - self.plots[0].vectors[0][0] = newtime - self.plots[0].vectors[0][1] = newzpiezo - self.plots[0].vectors[1][0] = newtime - self.plots[0].vectors[1][1] = newphase - - if self.config['fc_interesting'] != 0 and plot.destination==1: - lower = int((self.config['fc_interesting'])-1) - upper = int((self.config['fc_interesting'])+1) - trim = current.curve.trimindexes()[lower:upper] - newtime = [] - newdefl = [] - newimposed = [] - for x in range(trim[0],trim[1]): - newtime.append(self.plots[1].vectors[0][0][x]) - newdefl.append(self.plots[1].vectors[0][1][x]) - newimposed.append(self.plots[1].vectors[1][1][x]) - self.plots[1].vectors[0][0] = newtime - self.plots[1].vectors[0][1] = newdefl - self.plots[1].vectors[1][0] = newtime - self.plots[1].vectors[1][1] = newimposed - - if self.config['fc_showphase'] == 0 and plot.destination==0: - self.plots[0].remove_set(1) - - if self.config['fc_showimposed'] == 0 and plot.destination==1: - self.plots[1].remove_set(1) - - return plot - - def do_time(self,args): - ''' - Measures the time difference (in seconds) between two points - Implemented only for force clamp - ---- - Syntax: time - ''' - if self.current.curve.experiment == 'clamp': - time=self._delta(set=0)[0] - print str(time*1000)+' ms' - else: - print 'This command makes no sense for a non-force clamp experiment.' - - def do_zpiezo(self,args): - ''' - Measures the zpiezo difference (in nm) between two points - Implemented only for force clamp - ---- - Syntax: zpiezo - ''' - if self.current.curve.experiment == 'clamp': - zpiezo=self._delta(set=0)[2] - print str(zpiezo*(10**9))+' nm' - else: - print 'This command makes no sense for a non-force clamp experiment.' - - def do_defl(self,args): - ''' - Measures the deflection difference (in nm) between two points - Implemented only for force clamp - NOTE: It makes sense only on the time VS defl plot; it is still not masked for the other plot... - ----- - Syntax: defl - ''' - if self.current.curve.experiment == 'clamp': - print "Warning - don't use on the zpiezo plot!" - defl=self._delta(set=1)[2] - print str(defl*(10**12))+' pN' - else: - print 'This command makes no sense for a non-force clamp experiment.' - - def do_step(self,args): - ''' - Measures the length and time duration of a time-Z step - ----- - Syntax: step - ''' - if self.current.curve.experiment == 'clamp': - print 'Click three points in this fashion:' - print ' (0)-------(1)' - print ' |' - print ' |' - print ' (2)----------' - points=self._measure_N_points(N=3,whatset=0) - dz=abs(points[2].graph_coords[1]-points[1].graph_coords[1])*(10e+8) - dt=abs(points[1].graph_coords[0]-points[0].graph_coords[0]) - print 'dZ: ',dz,' nm' - print 'dT: ',dt,' s' - - else: - print 'This command makes no sense for a non-force clamp experiment.' - - def do_fcfilt(self,args): - ''' - Filters out featureless force clamp curves of the current playlist. - It's very similar to 'flatfilt' for velocity clamp curves. - Creates a new playlist only containing non-empty curves. - - WARNING - Only works if you set an appropriate fc_interesting config variable! - WARNING - arguments are NOT optional at the moment! - - Syntax: fcfilt maxretraction(nm) mindeviation (pN) - - Suggested values for an (i27)8 experiment with our setup are 200nm and 10-15 pN - ''' - - if self.config['fc_interesting'] == 0: - print 'You must specify the phase of interest (using set fc_interesing X) prior to running fcfilt!' - return - - maxretraction=0 - threshold=0 - args=args.split(' ') - if len(args)==2: - maxretraction=int(args[0]) - threshold=int(args[1]) - else: - print 'Arguments are not optional for fcfilt. You should pass two numbers:' - print '(1) the maximum plausible piezo retraction in NANOMETERS (e.g. the length of the protein)' - print "(2) the threshold, in PICONEWTONS. If signal deviates from imposed more than this, it's an event" - return - - - print 'Processing playlist... go get yourself a cup of coffee.' - notflat_list=[] - - c=0 - - for item in self.current_list: - c+=1 - try: - notflat=self.has_stuff(item,maxretraction,threshold) - print 'Curve',item.path,'is',c,'of',len(self.current_list),'--->Has Stuff =',notflat - except: - notflat=False - print 'Curve',item.path,'is',c,'of',len(self.current_list),'--->could not be processed' - if notflat: - item.features=notflat - item.curve=None - notflat_list.append(item) - - if len(notflat_list)==0: - print 'Nothing interesting here. Reconsider either your filtering criteria or your experimental data' - return - else: - print 'Found',len(notflat_list),'potentially interesting curves.' - print 'Regenerating Playlist...' - self.pointer=0 - self.current_list=notflat_list - self.current=self.current_list[self.pointer] - self.do_plot(0) - - def has_stuff(self,item,maxretraction,threshold): - ''' - Decides whether a curve has some features in the interesting phase. - Algorithm: - - clip the interesting phase portion of the curve. - - discard the first 20 milliseconds (this is due to a quirk of our hardware). - - look at the zpiezo plot and note down when (if) retratcs more than [maxretraction] nm away from the first point. - - clip off any data after this point, with an excess of 100 points (again, an hardware quirk) - - if the remainder is less than 100 points, ditch the curve. - - now look at the deflection plot and check if there are points more than [threshold] pN over the 'flat zone'. - - if you find such points, bingo! - ''' - - item.identify(self.drivers) - - lower = int((self.config['fc_interesting'])-1) - upper = int((self.config['fc_interesting'])+1) - trim_idxs = item.curve.trimindexes()[lower:upper] - lo=trim_idxs[0]+20 #clipping the first 20 points off... - hi=trim_idxs[1] - trimmed_zpiezo=item.curve.default_plots()[0].vectors[0][1][lo:hi] - trimmed_defl=item.curve.default_plots()[1].vectors[0][1][lo:hi] - trimmed_imposed=item.curve.default_plots()[1].vectors[1][1][lo:hi] - imposed=trimmed_imposed[21] #just to match the 20-pts clipping... - - item.curve.close_all() - del item.curve - del item - - starting_z=trimmed_zpiezo[0] - plausible=starting_z-(maxretraction*1e-9) - det_trim=0 - while trimmed_zpiezo[det_trim]>plausible: - det_trim+=1 - if det_trim >= len(trimmed_zpiezo): #breaking cycles makes me shiver... - det_trim=len(trimmed_zpiezo) #but I cannot think of anything better now. - break - further_trim=det_trim-100 - if further_trim<100: - return False - trimmed_defl=trimmed_defl[:further_trim] - - trimmed_defl.sort() - ninetypercent=int(0.9*len(trimmed_defl)) - j=0 - sum=0 - for j in trimmed_defl[:ninetypercent]: - sum+=j - avg=float(sum/ninetypercent) - sweetspot=float(avg+(threshold*1e-12)) - if trimmed_defl[-1]>sweetspot: - flag=True - else: - flag=False - - del trimmed_defl,trimmed_zpiezo,trimmed_imposed - - return flag - \ No newline at end of file diff --git a/generaltccd.py b/generaltccd.py deleted file mode 100644 index 70ad513..0000000 --- a/generaltccd.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python - -''' -generaltccd.py - -General utilities for TCCD stuff -''' - -class generaltccdCommands: - - def plotmanip_threshold(self, plot, current, customvalue=False): - ''' - Cuts from the plot everything below the threshold. - Set the threshold with "set tccd_threshold" - ''' - - if current.curve.experiment != 'smfluo': - return plot - - if not self.config['tccd_threshold'] and (not customvalue): - return plot - - if customvalue: - thresh=customvalue - else: - thresh=self.config['tccd_threshold'] - - for set in plot.vectors: - newy=[] - for value in set[1]: - if abs(value) < thresh: - newy.append(0) - else: - newy.append(value) - - set[1]=newy - - return plot - - - def plotmanip_coincident(self,plot,current, customvalue=False): - ''' - Shows only coincident events - ''' - if current.curve.experiment != 'smfluo': - return plot - - if not self.config['tccd_coincident'] and (not customvalue): - return plot - - newred=[] - newblue=[] - for index in range(len(plot.vectors[0][1])): - if abs(plot.vectors[0][1][index])>self.config['tccd_threshold'] and abs(plot.vectors[1][1][index])>self.config['tccd_threshold']: - newred.append(plot.vectors[0][1][index]) - newblue.append(plot.vectors[1][1][index]) - else: - newred.append(0) - newblue.append(0) - - plot.vectors[0][1]=newred - plot.vectors[1][1]=newblue - - return plot \ No newline at end of file diff --git a/generalvclamp.py b/generalvclamp.py deleted file mode 100644 index 1c71de0..0000000 --- a/generalvclamp.py +++ /dev/null @@ -1,321 +0,0 @@ -#!/usr/bin/env python - -''' -generalvclamp.py - -Plugin regarding general velocity clamp measurements -''' - -from libhooke import WX_GOOD, ClickedPoint -import wxversion -wxversion.select(WX_GOOD) -from wx import PostEvent -import numpy as np -import scipy as sp -import copy -import os.path -import time - -import warnings -warnings.simplefilter('ignore',np.RankWarning) - - -class generalvclampCommands: - - def _plug_init(self): - self.basecurrent=None - self.basepoints=None - self.autofile='' - - def do_distance(self,args): - ''' - 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 - ''' - if self.current.curve.experiment == 'clamp': - print 'You wanted to use zpiezo perhaps?' - return - else: - dx,unitx,dy,unity=self._delta(set=1) - print str(dx*(10**9))+' nm' - to_dump='distance '+self.current.path+' '+str(dx*(10**9))+' nm' - self.outlet.push(to_dump) - - - def do_force(self,args): - ''' - FORCE - (generalvclamp.py) - Measure the force difference (in pN) between two points - --------------- - Syntax: force - ''' - if self.current.curve.experiment == 'clamp': - print 'This command makes no sense for a force clamp experiment.' - return - dx,unitx,dy,unity=self._delta(set=1) - print str(dy*(10**12))+' pN' - to_dump='force '+self.current.path+' '+str(dy*(10**12))+' pN' - self.outlet.push(to_dump) - - - def do_forcebase(self,args): - ''' - FORCEBASE - (generalvclamp.py) - Measures the difference in force (in pN) between a point and a baseline - took 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 - ''' - rebase=False #if true=we select rebase - maxpoint=False #if true=we measure the maximum peak - - plot=self._get_displayed_plot() - whatset=1 #fixme: for all sets - if 'rebase' in args or (self.basecurrent != self.current.path): - rebase=True - if 'max' in args: - maxpoint=True - - if rebase: - print 'Select baseline' - self.basepoints=self._measure_N_points(N=2, whatset=whatset) - self.basecurrent=self.current.path - - if maxpoint: - print 'Select two points' - points=self._measure_N_points(N=2, whatset=whatset) - boundpoints=[points[0].index, points[1].index] - boundpoints.sort() - try: - y=min(plot.vectors[whatset][1][boundpoints[0]:boundpoints[1]]) - except ValueError: - print 'Chosen interval not valid. Try picking it again. Did you pick the same point as begin and end of interval?' - else: - print 'Select point to measure' - points=self._measure_N_points(N=1, whatset=whatset) - #whatplot=points[0].dest - y=points[0].graph_coords[1] - - #fixme: code duplication - boundaries=[self.basepoints[0].index, self.basepoints[1].index] - boundaries.sort() - to_average=plot.vectors[whatset][1][boundaries[0]:boundaries[1]] #y points to average - - avg=np.mean(to_average) - forcebase=abs(y-avg) - print str(forcebase*(10**12))+' pN' - to_dump='forcebase '+self.current.path+' '+str(forcebase*(10**12))+' pN' - self.outlet.push(to_dump) - - def plotmanip_multiplier(self, plot, current): - ''' - 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.curve.experiment != 'smfs': - return plot - - #only one set is present... - if len(self.plots[0].vectors) != 2: - return plot - - #multiplier is 1... - if (self.config['force_multiplier']==1): - return plot - - for i in range(len(plot.vectors[0][1])): - plot.vectors[0][1][i]=plot.vectors[0][1][i]*self.config['force_multiplier'] - - for i in range(len(plot.vectors[1][1])): - plot.vectors[1][1][i]=plot.vectors[1][1][i]*self.config['force_multiplier'] - - return plot - - - def plotmanip_flatten(self, plot, current, customvalue=False): - ''' - 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=False) - ''' - - #not a smfs curve... - if current.curve.experiment != 'smfs': - return plot - - #only one set is present... - if len(self.plots[0].vectors) != 2: - return plot - - #config is not flatten, and customvalue flag is false too - if (not self.config['flatten']) and (not customvalue): - return plot - - max_exponent=12 - delta_contact=0 - - if customvalue: - max_cycles=customvalue - else: - max_cycles=self.config['flatten'] #Using > 1 usually doesn't help and can give artefacts. However, it could be useful too. - - contact_index=self.find_contact_point() - - 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.vectors[0][0][contact_index+delta_contact:] - y_ext=plot.vectors[0][1][contact_index+delta_contact:] - x_ret=plot.vectors[1][0][contact_index+delta_contact:] - y_ret=plot.vectors[1][1][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 plot - - best_exponent=errn.index(min(errn)) - - #extension - ycorr_ext=y_ext-yrn[best_exponent]+y_ext[0] #noncontact part - yjoin_ext=np.array(plot.vectors[0][1][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.vectors[1][1][0:contact_index+delta_contact]) #contact part - - ycorr_ext=np.concatenate((yjoin_ext, ycorr_ext)) - ycorr_ret=np.concatenate((yjoin_ret, ycorr_ret)) - - plot.vectors[0][1]=list(ycorr_ext) - plot.vectors[1][1]=list(ycorr_ret) - - return plot - - #---SLOPE--- - def do_slope(self,args): - ''' - 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. - (c) Marco Brucale, Massimo Sandal 2008 - ''' - - # Reads the facultative width argument - try: - fitspan=int(args) - except: - fitspan=0 - - # Decides between the two forms of user input, as per (args) - if fitspan == 0: - # Gets the Xs of two clicked points as indexes on the current curve vector - print 'Click twice to delimit chunk' - clickedpoints=[] - points=self._measure_N_points(N=2,whatset=1) - clickedpoints=[points[0].index,points[1].index] - clickedpoints.sort() - else: - print 'Click once on the leftmost point of the chunk (i.e.usually the peak)' - clickedpoints=[] - points=self._measure_N_points(N=1,whatset=1) - clickedpoints=[points[0].index-fitspan,points[0].index] - - # Calls the function linefit_between - parameters=[0,0,[],[]] - try: - parameters=self.linefit_between(clickedpoints[0],clickedpoints[1]) - except: - print 'Cannot fit. Did you click twice the same point?' - return - - # Outputs the relevant slope parameter - print 'Slope:' - print str(parameters[0]) - to_dump='slope '+self.current.path+' '+str(parameters[0]) - self.outlet.push(to_dump) - - # 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]) - - lineplot=self._get_displayed_plot(0) #get topmost displayed plot - - lineplot.add_set(xtoplot,ytoplot) - lineplot.add_set(clickvector_x, clickvector_y) - - - if lineplot.styles==[]: - lineplot.styles=[None,None,None,'scatter'] - else: - lineplot.styles+=[None,'scatter'] - if lineplot.colors==[]: - lineplot.styles=[None,None,None,None] - else: - lineplot.colors+=[None,None] - - - self._send_plot([lineplot]) - - def linefit_between(self,index1,index2,whatset=1): - ''' - Creates two vectors (xtofit,ytofit) slicing out from the - current return trace a portion delimited by the two indexes - 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. - (c) Marco Brucale, Massimo Sandal 2008 - ''' - # Translates the indexes into two vectors containing the x,y data to fit - xtofit=self.plots[0].vectors[whatset][0][index1:index2] - ytofit=self.plots[0].vectors[whatset][1][index1:index2] - - # Does the actual linear fitting (simple least squares with numpy.polyfit) - linefit=[] - linefit=np.polyfit(xtofit,ytofit,1) - - return (linefit[0],linefit[1],xtofit,ytofit) - - - diff --git a/hooke.conf b/hooke.conf deleted file mode 100755 index 8c72cb6..0000000 --- a/hooke.conf +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - insert directory - - - - - test.hkp - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/hooke.py b/hooke.py old mode 100755 new mode 100644 index 21cd509..6a28f12 --- a/hooke.py +++ b/hooke.py @@ -3,812 +3,1287 @@ ''' HOOKE - A force spectroscopy review & analysis tool -(C) 2008 Massimo Sandal - -Copyright (C) 2008 Massimo Sandal (University of Bologna, Italy). +Copyright 2008 by Massimo Sandal (University of Bologna, Italy). +Copyright 2010 by Rolf Schmidt (Concordia University, Canada). This program is released under the GNU General Public License version 2. ''' -from libhooke import HOOKE_VERSION -from libhooke import WX_GOOD - -import os - import wxversion -wxversion.select(WX_GOOD) -import wx -import wxmpl -from wx.lib.newevent import NewEvent - -import matplotlib.numerix as nx -import scipy as sp - -from threading import * -import Queue - -from hooke_cli import HookeCli -from libhooke import * -import libhookecurve as lhc - -#import file versions, just to know with what we're working... -from hooke_cli import __version__ as hookecli_version +import lib.libhooke as lh +wxversion.select(lh.WX_GOOD) + +from configobj import ConfigObj +import copy +import os.path +import platform +import time +#import wx +import wx.html +import wx.lib.agw.aui as aui +import wx.lib.evtmgr as evtmgr +import wx.propgrid as wxpg + +from matplotlib import __version__ as mpl_version +from numpy import __version__ as numpy_version +from scipy import __version__ as scipy_version +from sys import version as python_version +from wx import __version__ as wx_version + +try: + from agw import cubecolourdialog as CCD +except ImportError: # if it's not there locally, try the wxPython lib. + import wx.lib.agw.cubecolourdialog as CCD + +#set the Hooke directory +lh.hookeDir = os.path.abspath(os.path.dirname(__file__)) +from config.config import config +import drivers +import lib.playlist +import lib.plotmanipulator +import panels.commands +import panels.perspectives +import panels.playlist +import panels.plot +import panels.propertyeditor +import panels.results +import plugins global __version__ -global events_from_gui -global config -global CLI_PLUGINS -global GUI_PLUGINS -global LOADED_PLUGINS -global PLOTMANIP_PLUGINS -global FILE_DRIVERS - -__version__=HOOKE_VERSION[0] -__release_name__=HOOKE_VERSION[1] - -events_from_gui=Queue.Queue() #GUI ---> CLI COMMUNICATION - -print 'Starting Hooke.' -#CONFIGURATION FILE PARSING -config_obj=HookeConfig() -config=config_obj.load_config('hooke.conf') - -#IMPORTING PLUGINS - -CLI_PLUGINS=[] -GUI_PLUGINS=[] -PLOTMANIP_PLUGINS=[] -LOADED_PLUGINS=[] - -plugin_commands_namespaces=[] -plugin_gui_namespaces=[] -for plugin_name in config['plugins']: - try: - plugin=__import__(plugin_name) - try: - eval('CLI_PLUGINS.append(plugin.'+plugin_name+'Commands)') #take Command plugin classes - plugin_commands_namespaces.append(dir(eval('plugin.'+plugin_name+'Commands'))) - except: - pass - try: - eval('GUI_PLUGINS.append(plugin.'+plugin_name+'Gui)') #take Gui plugin classes - plugin_gui_namespaces.append(dir(eval('plugin.'+plugin_name+'Gui'))) - except: - pass - except ImportError: - print 'Cannot find plugin ',plugin_name - else: - LOADED_PLUGINS.append(plugin_name) - print 'Imported plugin ',plugin_name - -#eliminate names common to all namespaces -for i in range(len(plugin_commands_namespaces)): - plugin_commands_namespaces[i]=[item for item in plugin_commands_namespaces[i] if (item != '__doc__' and item != '__module__' and item != '_plug_init')] -#check for conflicts in namespaces between plugins -#FIXME: only in commands now, because I don't have Gui plugins to check -#FIXME: how to check for plugin-defined variables (self.stuff) ?? -plugin_commands_names=[] -whatplugin_defines=[] -plugin_gui_names=[] -for namespace,plugin_name in zip(plugin_commands_namespaces, config['plugins']): - for item in namespace: - if item in plugin_commands_names: - i=plugin_commands_names.index(item) #we exploit the fact index gives the *first* occurrence of a name... - print 'Error. Plugin ',plugin_name,' defines a function already defined by ',whatplugin_defines[i],'!' - print 'This should not happen. Please disable one or both plugins and contact the plugin authors to solve the conflict.' - print 'Hooke cannot continue.' - exit() - else: - plugin_commands_names.append(item) - whatplugin_defines.append(plugin_name) - - -config['loaded_plugins']=LOADED_PLUGINS #FIXME: kludge -this should be global but not in config! -#IMPORTING DRIVERS -#FIXME: code duplication -FILE_DRIVERS=[] -LOADED_DRIVERS=[] -for driver_name in config['drivers']: - try: - driver=__import__(driver_name) - try: - eval('FILE_DRIVERS.append(driver.'+driver_name+'Driver)') - except: - pass - except ImportError: - print 'Cannot find driver ',driver_name - else: - LOADED_DRIVERS.append(driver_name) - print 'Imported driver ',driver_name -config['loaded_drivers']=LOADED_DRIVERS - -#LIST OF CUSTOM WX EVENTS FOR CLI ---> GUI COMMUNICATION -#FIXME: do they need to be here? -list_of_events={} - -plot_graph, EVT_PLOT = NewEvent() -list_of_events['plot_graph']=plot_graph - -plot_contact, EVT_PLOT_CONTACT = NewEvent() -list_of_events['plot_contact']=plot_contact - -measure_points, EVT_MEASURE_POINTS = NewEvent() -list_of_events['measure_points']=measure_points - -export_image, EVT_EXPORT_IMAGE = NewEvent() -list_of_events['export_image']=export_image - -close_plot, EVT_CLOSE_PLOT = NewEvent() -list_of_events['close_plot'] = close_plot - -show_plots, EVT_SHOW_PLOTS = NewEvent() -list_of_events['show_plots'] = show_plots - -get_displayed_plot, EVT_GET_DISPLAYED_PLOT = NewEvent() -list_of_events['get_displayed_plot'] = get_displayed_plot -#------------ - -class CliThread(Thread): - - def __init__(self,frame,list_of_events): - Thread.__init__(self) - - #here we have to put temporary references to pass to the cli object. - self.frame=frame - self.list_of_events=list_of_events - - self.debug=0 #to be used in the future - - def run(self): - print '\n\nThis is Hooke, version',__version__ , __release_name__ - print - print '(c) Massimo Sandal & others, 2006-2008. Released under the GNU Lesser General Public License Version 3' - print 'Hooke is Free software.' - print '----' - print '' +global __codename__ +global __releasedate__ +__version__ = lh.HOOKE_VERSION[0] +__codename__ = lh.HOOKE_VERSION[1] +__releasedate__ = lh.HOOKE_VERSION[2] +__release_name__ = lh.HOOKE_VERSION[1] + +#TODO: add general preferences to Hooke +#this might be useful +#ID_Config = wx.NewId() +ID_About = wx.NewId() +ID_Next = wx.NewId() +ID_Previous = wx.NewId() + +ID_ViewAssistant = wx.NewId() +ID_ViewCommands = wx.NewId() +ID_ViewFolders = wx.NewId() +ID_ViewOutput = wx.NewId() +ID_ViewPlaylists = wx.NewId() +ID_ViewProperties = wx.NewId() +ID_ViewResults = wx.NewId() + +ID_DeletePerspective = wx.NewId() +ID_SavePerspective = wx.NewId() + +ID_FirstPerspective = ID_SavePerspective + 1000 +#I hope we'll never have more than 1000 perspectives +ID_FirstPlot = ID_SavePerspective + 2000 + +class Hooke(wx.App): + + def OnInit(self): + self.SetAppName('Hooke') + self.SetVendorName('') + + windowPosition = (config['main']['left'], config['main']['top']) + windowSize = (config['main']['width'], config['main']['height']) + + #setup the splashscreen + if config['splashscreen']['show']: + filename = lh.get_file_path('hooke.jpg', ['resources']) + if os.path.isfile(filename): + bitmap = wx.Image(filename).ConvertToBitmap() + splashStyle = wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT + splashDuration = config['splashscreen']['duration'] + wx.SplashScreen(bitmap, splashStyle, splashDuration, None, -1) + wx.Yield() + ''' + we need for the splash screen to disappear + for whatever reason splashDuration and sleep do not correspond to each other + at least not on Windows + maybe it's because duration is in milliseconds and sleep in seconds + thus we need to increase the sleep time a bit + a factor of 1.2 seems to work quite well + ''' + sleepFactor = 1.2 + time.sleep(sleepFactor * splashDuration / 1000) + + plugin_objects = [] + for plugin in config['plugins']: + if config['plugins'][plugin]: + filename = ''.join([plugin, '.py']) + path = lh.get_file_path(filename, ['plugins']) + if os.path.isfile(path): + #get the corresponding filename and path + plugin_name = ''.join(['plugins.', plugin]) + #import the module + __import__(plugin_name) + #get the file that contains the plugin + class_file = getattr(plugins, plugin) + #get the class that contains the commands + class_object = getattr(class_file, plugin + 'Commands') + plugin_objects.append(class_object) def make_command_class(*bases): - #FIXME: perhaps redundant - return type(HookeCli)("HookeCliPlugged", bases + (HookeCli,), {}) - cli = make_command_class(*CLI_PLUGINS)(self.frame,self.list_of_events,events_from_gui,config,FILE_DRIVERS) - cli.cmdloop() - -''' -GUI CODE - -FIXME: put it in a separate module in the future? -''' -class MainMenuBar(wx.MenuBar): - ''' - Creates the menu bar - ''' - def __init__(self): - wx.MenuBar.__init__(self) - '''the menu description. the key of the menu is XX&Menu, where XX is a number telling - the order of the menus on the menubar. - &Menu is the Menu text - the corresponding argument is ('&Item', 'itemname'), where &Item is the item text and itemname - the inner reference to use in the self.menu_items dictionary. - - See create_menus() to see how it works - - Note: the mechanism on page 124 of "wxPython in Action" is less awkward, maybe, but I want - binding to be performed later. Perhaps I'm wrong :) - ''' - - self.menu_desc={'00&File':[('&Open playlist','openplaymenu'),('&Exit','exitmenu')], - '01&Edit':[('&Export text...','exporttextmenu'),('&Export image...','exportimagemenu')], - '02&Help':[('&About Hooke','aboutmenu')]} - self.create_menus() - - def create_menus(self): + #create metaclass with plugins and plotmanipulators + return type(HookeFrame)("HookeFramePlugged", bases + (HookeFrame,), {}) + frame = make_command_class(*plugin_objects)(parent=None, id=wx.ID_ANY, title='Hooke', pos=windowPosition, size=windowSize) + frame.Show(True) + self.SetTopWindow(frame) + + return True + + def OnExit(self): + return True + + +class HookeFrame(wx.Frame): + + def __init__(self, parent, id=-1, title='', pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE|wx.SUNKEN_BORDER|wx.CLIP_CHILDREN): + #call parent constructor + wx.Frame.__init__(self, parent, id, title, pos, size, style) + self.config = config + self.CreateApplicationIcon() + #self.configs contains: {the name of the Commands file: corresponding ConfigObj} + self.configs = {} + #self.displayed_plot holds the currently displayed plot + self.displayed_plot = None + #self.playlists contains: {the name of the playlist: [playlist, tabIndex, plotID]} + self.playlists = {} + #list of all plotmanipulators + self.plotmanipulators = [] + #self.plugins contains: {the name of the plugin: [caption, function]} + self.plugins = {} + + #tell FrameManager to manage this frame + self._mgr = aui.AuiManager() + self._mgr.SetManagedWindow(self) + #set the gradient style + self._mgr.GetArtProvider().SetMetric(aui.AUI_DOCKART_GRADIENT_TYPE, aui.AUI_GRADIENT_NONE) + #set transparent drag + self._mgr.SetFlags(self._mgr.GetFlags() ^ aui.AUI_MGR_TRANSPARENT_DRAG) + + # set up default notebook style + self._notebook_style = aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER + self._notebook_theme = 0 + + #holds the perspectives: {name, perspective_str} + self._perspectives = {} + + # min size for the frame itself isn't completely done. + # see the end up FrameManager::Update() for the test + # code. For now, just hard code a frame minimum size + self.SetMinSize(wx.Size(500, 500)) + #create panels here + self.panelAssistant = self.CreatePanelAssistant() + self.panelCommands = self.CreatePanelCommands() + self.panelFolders = self.CreatePanelFolders() + self.panelPlaylists = self.CreatePanelPlaylists() + self.panelProperties = self.CreatePanelProperties() + self.panelOutput = self.CreatePanelOutput() + self.panelResults = self.CreatePanelResults() + self.plotNotebook = self.CreateNotebook() + #self.textCtrlCommandLine=self.CreateCommandLine() + + # add panes + self._mgr.AddPane(self.panelFolders, aui.AuiPaneInfo().Name('Folders').Caption('Folders').Left().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.panelPlaylists, aui.AuiPaneInfo().Name('Playlists').Caption('Playlists').Left().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.plotNotebook, aui.AuiPaneInfo().Name('Plots').CenterPane().PaneBorder(False)) + self._mgr.AddPane(self.panelCommands, aui.AuiPaneInfo().Name('Commands').Caption('Settings and commands').Right().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.panelProperties, aui.AuiPaneInfo().Name('Properties').Caption('Properties').Right().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.panelAssistant, aui.AuiPaneInfo().Name('Assistant').Caption('Assistant').Right().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.panelOutput, aui.AuiPaneInfo().Name('Output').Caption('Output').Bottom().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.panelResults, aui.AuiPaneInfo().Name('Results').Caption('Results').Bottom().CloseButton(True).MaximizeButton(False)) + #self._mgr.AddPane(self.textCtrlCommandLine, aui.AuiPaneInfo().Name('CommandLine').CaptionVisible(False).Fixed().Bottom().Layer(2).CloseButton(False).MaximizeButton(False)) + #self._mgr.AddPane(panelBottom, aui.AuiPaneInfo().Name("panelCommandLine").Bottom().Position(1).CloseButton(False).MaximizeButton(False)) + + # add the toolbars to the manager + #self.toolbar=self.CreateToolBar() + self.toolbarNavigation=self.CreateToolBarNavigation() + #self._mgr.AddPane(self.toolbar, aui.AuiPaneInfo().Name('toolbar').Caption('Toolbar').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False)) + self._mgr.AddPane(self.toolbarNavigation, aui.AuiPaneInfo().Name('toolbarNavigation').Caption('Navigation').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False)) + # "commit" all changes made to FrameManager + self._mgr.Update() + #create the menubar after the panes so that the default perspective + #is created with all panes open + self.CreateMenuBar() + self.statusbar = self.CreateStatusbar() + self._BindEvents() + + name = self.config['perspectives']['active'] + menu_item = self.GetPerspectiveMenuItem(name) + if menu_item is not None: + self.OnRestorePerspective(menu_item) + #TODO: config setting to remember playlists from last session + self.playlists = self.panelPlaylists.Playlists + #define the list of active drivers + self.drivers = [] + for driver in self.config['drivers']: + if self.config['drivers'][driver]: + #get the corresponding filename and path + filename = ''.join([driver, '.py']) + path = lh.get_file_path(filename, ['drivers']) + #the driver is active for driver[1] == 1 + if os.path.isfile(path): + #driver files are located in the 'drivers' subfolder + driver_name = ''.join(['drivers.', driver]) + __import__(driver_name) + class_file = getattr(drivers, driver) + for command in dir(class_file): + if command.endswith('Driver'): + self.drivers.append(getattr(class_file, command)) + #import all active plugins and plotmanips + #add 'core.ini' to self.configs (this is not a plugin and thus must be imported separately) + ini_path = lh.get_file_path('core.ini', ['plugins']) + plugin_config = ConfigObj(ini_path) + #self.config.merge(plugin_config) + self.configs['core'] = plugin_config + #make sure we execute _plug_init() for every command line plugin we import + for plugin in self.config['plugins']: + if self.config['plugins'][plugin]: + filename = ''.join([plugin, '.py']) + path = lh.get_file_path(filename, ['plugins']) + if os.path.isfile(path): + #get the corresponding filename and path + plugin_name = ''.join(['plugins.', plugin]) + try: + #import the module + module = __import__(plugin_name) + #prepare the ini file for inclusion + ini_path = path.replace('.py', '.ini') + #include ini file + plugin_config = ConfigObj(ini_path) + #self.config.merge(plugin_config) + self.configs[plugin] = plugin_config + #add to plugins + commands = eval('dir(module.' + plugin+ '.' + plugin + 'Commands)') + #keep only commands (ie names that start with 'do_') + #TODO: check for existing commands and warn the user! + commands = [command for command in commands if command.startswith('do_')] + if commands: + self.plugins[plugin] = commands + try: + #initialize the plugin + eval('module.' + plugin+ '.' + plugin + 'Commands._plug_init(self)') + except AttributeError: + pass + except ImportError: + pass + #initialize the commands tree + commands = dir(HookeFrame) + commands = [command for command in commands if command.startswith('do_')] + if commands: + self.plugins['core'] = commands + self.panelCommands.Initialize(self.plugins) + for command in dir(self): + if command.startswith('plotmanip_'): + self.plotmanipulators.append(lib.plotmanipulator.Plotmanipulator(method=getattr(self, command), command=command)) + + #load default list, if possible + self.do_loadlist(self.config['core']['list']) + #self.do_loadlist() + + def _BindEvents(self): + #TODO: figure out if we can use the eventManager for menu ranges + #and events of 'self' without raising an assertion fail error + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_CLOSE, self.OnClose) + # Show How To Use The Closing Panes Event + self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose) + self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnNotebookPageClose) + #menu + evtmgr.eventManager.Register(self.OnAbout, wx.EVT_MENU, win=self, id=wx.ID_ABOUT) + evtmgr.eventManager.Register(self.OnClose, wx.EVT_MENU, win=self, id=wx.ID_EXIT) + #view + self.Bind(wx.EVT_MENU_RANGE, self.OnView, id=ID_ViewAssistant, id2=ID_ViewResults) + #perspectives + self.Bind(wx.EVT_MENU, self.OnDeletePerspective, id=ID_DeletePerspective) + self.Bind(wx.EVT_MENU, self.OnSavePerspective, id=ID_SavePerspective) + self.Bind(wx.EVT_MENU_RANGE, self.OnRestorePerspective, id=ID_FirstPerspective, id2=ID_FirstPerspective+1000) + #toolbar + evtmgr.eventManager.Register(self.OnNext, wx.EVT_TOOL, win=self, id=ID_Next) + evtmgr.eventManager.Register(self.OnPrevious, wx.EVT_TOOL, win=self, id=ID_Previous) + #self.Bind(.EVT_AUITOOLBAR_TOOL_DROPDOWN, self.OnDropDownToolbarItem, id=ID_DropDownToolbarItem) + #dir control + treeCtrl = self.panelFolders.GetTreeCtrl() + #tree.Bind(wx.EVT_LEFT_UP, self.OnDirCtrl1LeftUp) + #tree.Bind(wx.EVT_LEFT_DOWN, self.OnGenericDirCtrl1LeftDown) + treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self.OnDirCtrlLeftDclick) + #playlist tree + self.panelPlaylists.PlaylistsTree.Bind(wx.EVT_LEFT_DOWN, self.OnPlaylistsLeftDown) + self.panelPlaylists.PlaylistsTree.Bind(wx.EVT_LEFT_DCLICK, self.OnPlaylistsLeftDclick) + #commands tree + evtmgr.eventManager.Register(self.OnExecute, wx.EVT_BUTTON, self.panelCommands.ExecuteButton) + evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self.panelCommands.CommandsTree) + evtmgr.eventManager.Register(self.OnTreeCtrlItemActivated, wx.EVT_TREE_ITEM_ACTIVATED, self.panelCommands.CommandsTree) + #property editor + self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged) + #results panel + self.panelResults.results_list.OnCheckItem = self.OnResultsCheck + + def _GetActiveFileIndex(self): + lib.playlist.Playlist = self.GetActivePlaylist() + #get the selected item from the tree + selected_item = self.panelPlaylists.PlaylistsTree.GetSelection() + #test if a playlist or a curve was double-clicked + if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): + return -1 + else: + count = 0 + selected_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item) + while selected_item.IsOk(): + count += 1 + selected_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item) + return count + + def _GetPlaylistTab(self, name): + for index, page in enumerate(self.plotNotebook._tabs._pages): + if page.caption == name: + return index + return -1 + + def _GetUniquePlaylistName(self, name): + playlist_name = name + count = 1 + while playlist_name in self.playlists: + playlist_name = ''.join([name, str(count)]) + count += 1 + return playlist_name + + def _RestorePerspective(self, name): + self._mgr.LoadPerspective(self._perspectives[name]) + self.config['perspectives']['active'] = name + self._mgr.Update() + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if not pane.name.startswith('toolbar'): + if pane.name == 'Assistant': + self.MenuBar.FindItemById(ID_ViewAssistant).Check(pane.window.IsShown()) + if pane.name == 'Folders': + self.MenuBar.FindItemById(ID_ViewFolders).Check(pane.window.IsShown()) + if pane.name == 'Playlists': + self.MenuBar.FindItemById(ID_ViewPlaylists).Check(pane.window.IsShown()) + if pane.name == 'Commands': + self.MenuBar.FindItemById(ID_ViewCommands).Check(pane.window.IsShown()) + if pane.name == 'Properties': + self.MenuBar.FindItemById(ID_ViewProperties).Check(pane.window.IsShown()) + if pane.name == 'Output': + self.MenuBar.FindItemById(ID_ViewOutput).Check(pane.window.IsShown()) + if pane.name == 'Results': + self.MenuBar.FindItemById(ID_ViewResults).Check(pane.window.IsShown()) + + def _SavePerspectiveToFile(self, name, perspective): + filename = ''.join([name, '.txt']) + filename = lh.get_file_path(filename, ['perspectives']) + perspectivesFile = open(filename, 'w') + perspectivesFile.write(perspective) + perspectivesFile.close() + + def _UnbindEvents(self): + #menu + evtmgr.eventManager.DeregisterListener(self.OnAbout) + evtmgr.eventManager.DeregisterListener(self.OnClose) + #toolbar + evtmgr.eventManager.DeregisterListener(self.OnNext) + evtmgr.eventManager.DeregisterListener(self.OnPrevious) + #commands tree + evtmgr.eventManager.DeregisterListener(self.OnExecute) + evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged) + + def AddPlaylist(self, playlist=None, name='Untitled'): + if playlist and playlist.count > 0: + playlist.name = self._GetUniquePlaylistName(name) + playlist.reset() + self.AddToPlaylists(playlist) + + def AddPlaylistFromFiles(self, files=[], name='Untitled'): + if files: + playlist = lib.playlist.Playlist(self, self.drivers) + for item in files: + playlist.add_curve(item) + if playlist.count > 0: + playlist.name = self._GetUniquePlaylistName(name) + playlist.reset() + self.AddTayliss(playlist) + + def AddToPlaylists(self, playlist): + if playlist.count > 0: + #setup the playlist in the Playlist tree + tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem() + playlist_root = self.panelPlaylists.PlaylistsTree.AppendItem(tree_root, playlist.name, 0) + #add all files to the Playlist tree +# files = {} + for index, file_to_add in enumerate(playlist.files): + #TODO: optionally remove the extension from the name of the curve + #item_text, extension = os.path.splitext(curve.name) + #curve_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, item_text, 1) + file_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, file_to_add.name, 1) + if index == playlist.index: + self.panelPlaylists.PlaylistsTree.SelectItem(file_ID) + playlist.reset() + #create the plot tab and add playlist to the dictionary + plotPanel = panels.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists)) + notebook_tab = self.plotNotebook.AddPage(plotPanel, playlist.name, True) + #tab_index = self.plotNotebook.GetSelection() + playlist.figure = plotPanel.get_figure() + self.playlists[playlist.name] = playlist + #self.playlists[playlist.name] = [playlist, figure] + self.panelPlaylists.PlaylistsTree.Expand(playlist_root) + self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdatePlot() + + def AppendToOutput(self, text): + self.panelOutput.AppendText(''.join([text, '\n'])) + + def AppliesPlotmanipulator(self, name): ''' - Smartish routine to create the menu from the self.menu_desc dictionary - Hope it's a workable solution for the future. + returns True if the plotmanipulator 'name' is applied, False otherwise + name does not contain 'plotmanip_', just the name of the plotmanipulator (e.g. 'flatten') ''' - self.menus=[] #the menu objects to append to the menubar - self.menu_items={} #the single menu items dictionary, to bind to events - - names=self.menu_desc.keys() #we gotta sort, because iterating keys goes in odd order - names.sort() - - for name in names: - self.menus.append(wx.Menu()) - for menu_item in self.menu_desc[name]: - self.menu_items[menu_item[1]]=self.menus[-1].Append(-1, menu_item[0]) - - for menu,name in zip(self.menus,names): - self.Append(menu,name[2:]) - -class MainPanel(wx.Panel): - def __init__(self,parent,id): - - wx.Panel.__init__(self,parent,id) - self.splitter = wx.SplitterWindow(self) - -ID_FRAME=100 -class MainWindow(wx.Frame): - '''we make a frame inheriting wx.Frame and setting up things on the init''' - def __init__(self,parent,id,title): - - #----------------------------- - #WX WIDGETS INITIALIZATION - - wx.Frame.__init__(self,parent,ID_FRAME,title,size=(800,600),style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE) - - self.mainpanel=MainPanel(self,-1) - self.cpanels=[] - - self.cpanels.append(wx.Panel(self.mainpanel.splitter,-1)) - self.cpanels.append(wx.Panel(self.mainpanel.splitter,-1)) - - self.statusbar=wx.StatusBar(self,-1) - self.SetStatusBar(self.statusbar) - - self.mainmenubar=MainMenuBar() - self.SetMenuBar(self.mainmenubar) - - self.controls=[] - self.figures=[] - self.axes=[] - - #This is our matplotlib plot - self.controls.append(wxmpl.PlotPanel(self.cpanels[0],-1)) - self.controls.append(wxmpl.PlotPanel(self.cpanels[1],-1)) - #These are our figure and axes, so to have easy references - #Also, we initialize - self.figures=[control.get_figure() for control in self.controls] - self.axes=[figure.gca() for figure in self.figures] + return self.GetBoolFromConfig('core', 'plotmanipulators', name) + + def CreateApplicationIcon(self): + iconFile = 'resources' + os.sep + 'microscope.ico' + icon = wx.Icon(iconFile, wx.BITMAP_TYPE_ICO) + self.SetIcon(icon) + + def CreateCommandLine(self): + return wx.TextCtrl(self, -1, '', style=wx.NO_BORDER|wx.EXPAND) + + def CreatePanelAssistant(self): + panel = wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE) + panel.SetEditable(False) + return panel + + def CreatePanelCommands(self): + return panels.commands.Commands(self) + + def CreatePanelFolders(self): + #set file filters + filters = self.config['folders']['filters'] + index = self.config['folders'].as_int('filterindex') + #set initial directory + folder = self.config['core']['workdir'] + return wx.GenericDirCtrl(self, -1, dir=folder, size=(200, 250), style=wx.DIRCTRL_SHOW_FILTERS, filter=filters, defaultFilter=index) + + def CreatePanelOutput(self): + return wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE) + + def CreatePanelPlaylists(self): + return panels.playlist.Playlists(self) + + def CreatePanelProperties(self): + return panels.propertyeditor.PropertyEditor(self) + + def CreatePanelResults(self): + return panels.results.Results(self) + + def CreatePanelWelcome(self): + #TODO: move into panels.welcome + ctrl = wx.html.HtmlWindow(self, -1, wx.DefaultPosition, wx.Size(400, 300)) + introStr = '

Welcome to Hooke

' + \ + '

Features

' + \ + '
    ' + \ + '
  • View, annotate, measure force files
  • ' + \ + '
  • Worm-like chain fit of force peaks
  • ' + \ + '
  • Automatic convolution-based filtering of empty files
  • ' + \ + '
  • Automatic fit and measurement of multiple force peaks
  • ' + \ + '
  • Handles force-clamp force experiments (experimental)
  • ' + \ + '
  • It is extensible by users by means of plugins and drivers
  • ' + \ + '
' + \ + '

See the DocumentationIndex for more information

' + ctrl.SetPage(introStr) + return ctrl + + def CreateMenuBar(self): + menu_bar = wx.MenuBar() + self.SetMenuBar(menu_bar) + #file + file_menu = wx.Menu() + file_menu.Append(wx.ID_EXIT, 'Exit\tCtrl-Q') +# edit_menu.AppendSeparator(); +# edit_menu.Append(ID_Config, 'Preferences') + #view + view_menu = wx.Menu() + view_menu.AppendCheckItem(ID_ViewFolders, 'Folders\tF5') + view_menu.AppendCheckItem(ID_ViewPlaylists, 'Playlists\tF6') + view_menu.AppendCheckItem(ID_ViewCommands, 'Commands\tF7') + view_menu.AppendCheckItem(ID_ViewProperties, 'Properties\tF8') + view_menu.AppendCheckItem(ID_ViewAssistant, 'Assistant\tF9') + view_menu.AppendCheckItem(ID_ViewResults, 'Results\tF10') + view_menu.AppendCheckItem(ID_ViewOutput, 'Output\tF11') + #perspectives +# perspectives_menu = self.CreatePerspectivesMenu() + perspectives_menu = wx.Menu() + + #help + help_menu = wx.Menu() + help_menu.Append(wx.ID_ABOUT, 'About Hooke') + #put it all together + menu_bar.Append(file_menu, 'File') +# menu_bar.Append(edit_menu, 'Edit') + menu_bar.Append(view_menu, 'View') + menu_bar.Append(perspectives_menu, "Perspectives") + self.UpdatePerspectivesMenu() + menu_bar.Append(help_menu, 'Help') + + def CreateNotebook(self): + # create the notebook off-window to avoid flicker + client_size = self.GetClientSize() + ctrl = aui.AuiNotebook(self, -1, wx.Point(client_size.x, client_size.y), wx.Size(430, 200), self._notebook_style) + arts = [aui.AuiDefaultTabArt, aui.AuiSimpleTabArt, aui.VC71TabArt, aui.FF2TabArt, aui.VC8TabArt, aui.ChromeTabArt] + art = arts[self._notebook_theme]() + ctrl.SetArtProvider(art) + #uncomment if we find a nice icon + #page_bmp = wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16)) + ctrl.AddPage(self.CreatePanelWelcome(), "Welcome", False) + return ctrl + + def CreateStatusbar(self): + statusbar = self.CreateStatusBar(2, wx.ST_SIZEGRIP) + statusbar.SetStatusWidths([-2, -3]) + statusbar.SetStatusText('Ready', 0) + welcomeString=u'Welcome to Hooke (version '+__version__+', '+__release_name__+')!' + statusbar.SetStatusText(welcomeString, 1) + return statusbar + + def CreateToolBarNavigation(self): + toolbar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.TB_FLAT | wx.TB_NODIVIDER) + toolbar.SetToolBitmapSize(wx.Size(16,16)) + toolbar_bmpBack = wx.ArtProvider_GetBitmap(wx.ART_GO_BACK, wx.ART_OTHER, wx.Size(16, 16)) + toolbar_bmpForward = wx.ArtProvider_GetBitmap(wx.ART_GO_FORWARD, wx.ART_OTHER, wx.Size(16, 16)) + toolbar.AddLabelTool(ID_Previous, 'Previous', toolbar_bmpBack, shortHelp='Previous curve') + toolbar.AddLabelTool(ID_Next, 'Next', toolbar_bmpForward, shortHelp='Next curve') + toolbar.Realize() + return toolbar + + def DeleteFromPlaylists(self, name): + if name in self.playlists: + del self.playlists[name] + tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem() + item, cookie = self.panelPlaylists.PlaylistsTree.GetFirstChild(tree_root) + while item.IsOk(): + playlist_name = self.panelPlaylists.PlaylistsTree.GetItemText(item) + if playlist_name == name: + try: + self.panelPlaylists.PlaylistsTree.Delete(item) + except: + pass + item = self.panelPlaylists.PlaylistsTree.GetNextSibling(item) + + def GetActiveFigure(self): + playlist_name = self.GetActivePlaylistName() + figure = self.playlists[playlist_name].figure + if figure is not None: + return figure + return None + + def GetActiveFile(self): + playlist = self.GetActivePlaylist() + if playlist is not None: + return playlist.get_active_file() + return None + + def GetActivePlaylist(self): + playlist_name = self.GetActivePlaylistName() + if playlist_name in self.playlists: + return self.playlists[playlist_name] + return None + + def GetActivePlaylistName(self): + #get the selected item from the tree + selected_item = self.panelPlaylists.PlaylistsTree.GetSelection() + #test if a playlist or a curve was double-clicked + if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): + playlist_item = selected_item + else: + #get the name of the playlist + playlist_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item) + #now we have a playlist + return self.panelPlaylists.PlaylistsTree.GetItemText(playlist_item) + + def GetActivePlot(self): + playlist = self.GetActivePlaylist() + if playlist is not None: + return playlist.get_active_file().plot + return None + + def GetDisplayedPlot(self): + plot = copy.deepcopy(self.displayed_plot) + plot.curves = [] + plot.curves = copy.deepcopy(plot.curves) + return plot - self.cpanels[1].Hide() - self.mainpanel.splitter.Initialize(self.cpanels[0]) + def GetDisplayedPlotCorrected(self): + plot = copy.deepcopy(self.displayed_plot) + plot.curves = [] + plot.curves = copy.deepcopy(plot.corrected_curves) + return plot - self.sizer_dance() #place/size the widgets + def GetDisplayedPlotRaw(self): + plot = copy.deepcopy(self.displayed_plot) + plot.curves = [] + plot.curves = copy.deepcopy(plot.raw_curves) + return plot - self.controls[0].SetSize(self.cpanels[0].GetSize()) - self.controls[1].SetSize(self.cpanels[1].GetSize()) + def GetDockArt(self): + return self._mgr.GetArtProvider() + + def GetBoolFromConfig(self, *args): + if len(args) == 2: + plugin = args[0] + section = args[0] + key = args[1] + elif len(args) == 3: + plugin = args[0] + section = args[1] + key = args[2] + if self.configs.has_key(plugin): + config = self.configs[plugin] + return config[section][key].as_bool('value') + return None + + def GetColorFromConfig(self, *args): + if len(args) == 2: + plugin = args[0] + section = args[0] + key = args[1] + elif len(args) == 3: + plugin = args[0] + section = args[1] + key = args[2] + if self.configs.has_key(plugin): + config = self.configs[plugin] + color_tuple = eval(config[section][key]['value']) + color = [value / 255.0 for value in color_tuple] + return color + return None + + def GetFloatFromConfig(self, *args): + if len(args) == 2: + plugin = args[0] + section = args[0] + key = args[1] + elif len(args) == 3: + plugin = args[0] + section = args[1] + key = args[2] + if self.configs.has_key(plugin): + config = self.configs[plugin] + return config[section][key].as_float('value') + return None + + def GetIntFromConfig(self, *args): + if len(args) == 2: + plugin = args[0] + section = args[0] + key = args[1] + elif len(args) == 3: + plugin = args[0] + section = args[1] + key = args[2] + if self.configs.has_key(plugin): + config = self.configs[plugin] + return config[section][key].as_int('value') + return None + + def GetStringFromConfig(self, *args): + if len(args) == 2: + plugin = args[0] + section = args[0] + key = args[1] + elif len(args) == 3: + plugin = args[0] + section = args[1] + key = args[2] + if self.configs.has_key(plugin): + config = self.configs[plugin] + return config[section][key]['value'] + return None + + def GetPerspectiveMenuItem(self, name): + if self._perspectives.has_key(name): + perspectives_list = [key for key, value in self._perspectives.iteritems()] + perspectives_list.sort() + index = perspectives_list.index(name) + perspective_Id = ID_FirstPerspective + index + menu_item = self.MenuBar.FindItemById(perspective_Id) + return menu_item + else: + return None - #resize the frame to properly draw on Windows - frameSize=self.GetSize() - frameSize.DecBy(1, 1) - self.SetSize(frameSize) + def HasPlotmanipulator(self, name): ''' - #if you need the exact same size as before DecBy, uncomment this block - frameSize.IncBy(1, 1) - self.SetSize(frameSize) + returns True if the plotmanipulator 'name' is loaded, False otherwise ''' - - #------------------------------------------- - #NON-WX WIDGETS INITIALIZATION - - #Flags. - self.click_plot=0 - - #FIXME: These could become a single flag with different (string?) values - #self.on_measure_distance=False - #self.on_measure_force=False - - self.plot_fit=False - - #Number of points to be clicked - self.num_of_points = 2 - - #Data. + for plotmanipulator in self.plotmanipulators: + if plotmanipulator.command == name: + return True + return False + + def OnAbout(self, event): + message = 'Hooke\n\n'+\ + 'A free, open source data analysis platform\n\n'+\ + 'Copyright 2006-2008 by Massimo Sandal\n'+\ + 'Copyright 2010 by Dr. Rolf Schmidt\n\n'+\ + 'Hooke is released under the GNU General Public License version 2.' + dialog = wx.MessageDialog(self, message, 'About Hooke', wx.OK | wx.ICON_INFORMATION) + dialog.ShowModal() + dialog.Destroy() + + def OnClose(self, event): + #apply changes + self.config['main']['height'] = str(self.GetSize().GetHeight()) + self.config['main']['left'] = str(self.GetPosition()[0]) + self.config['main']['top'] = str(self.GetPosition()[1]) + self.config['main']['width'] = str(self.GetSize().GetWidth()) + #save the configuration file to 'config/hooke.ini' + self.config.write() + #save all plugin config files + for config in self.configs: + plugin_config = self.configs[config] + plugin_config.write() + self._UnbindEvents() + self._mgr.UnInit() + del self._mgr + self.Destroy() + + def OnDeletePerspective(self, event): + dialog = panels.perspectives.Perspectives(self, -1, 'Delete perspective(s)') + dialog.CenterOnScreen() + dialog.ShowModal() + dialog.Destroy() + self.UpdatePerspectivesMenu() + #unfortunately, there is a bug in wxWidgets (Ticket #3258) that + #makes the radio item indicator in the menu disappear + #the code should be fine once this issue is fixed + + def OnDirCtrlLeftDclick(self, event): + file_path = self.panelFolders.GetPath() + if os.path.isfile(file_path): + if file_path.endswith('.hkp'): + self.do_loadlist(file_path) + event.Skip() + + def OnEraseBackground(self, event): + event.Skip() + + def OnExecute(self, event): + item = self.panelCommands.CommandsTree.GetSelection() + if item.IsOk(): + if not self.panelCommands.CommandsTree.ItemHasChildren(item): + item_text = self.panelCommands.CommandsTree.GetItemText(item) + command = ''.join(['self.do_', item_text, '()']) + #self.AppendToOutput(command + '\n') + exec(command) + + def OnExit(self, event): + self.Close() + + def OnNext(self, event): ''' - self.current_x_ext=[[],[]] - self.current_y_ext=[[],[]] - self.current_x_ret=[[],[]] - self.current_y_ret=[[],[]] - - - self.current_x_unit=[None,None] - self.current_y_unit=[None,None] - ''' - - #Initialize xaxes, yaxes - #FIXME: should come from config - self.current_xaxes=0 - self.current_yaxes=0 - - #Other - - - self.index_buffer=[] - - self.clicked_points=[] - - self.measure_set=None - - self.events_from_gui = events_from_gui - - ''' - This dictionary keeps all the flags and the relative functon names that - have to be called when a point is clicked. - That is: - - if point is clicked AND foo_flag=True - - foo() - - Conversely, foo_flag is True if a corresponding event is launched by the CLI. - - self.ClickedPoints() takes care of handling this - ''' - - self.click_flags_functions={'measure_points':[False, 'MeasurePoints']} - - #Binding of custom events from CLI --> GUI functions! - #FIXME: Should use the self.Bind() syntax - EVT_PLOT(self, self.PlotCurve) - EVT_PLOT_CONTACT(self, self.PlotContact) - EVT_GET_DISPLAYED_PLOT(self, self.OnGetDisplayedPlot) - EVT_MEASURE_POINTS(self, self.OnMeasurePoints) - EVT_EXPORT_IMAGE(self,self.ExportImage) - EVT_CLOSE_PLOT(self, self.OnClosePlot) - EVT_SHOW_PLOTS(self, self.OnShowPlots) - - #This event and control decide what happens when I click on the plot 0. - wxmpl.EVT_POINT(self, self.controls[0].GetId(), self.ClickPoint0) - wxmpl.EVT_POINT(self, self.controls[1].GetId(), self.ClickPoint1) - - #RUN PLUGIN-SPECIFIC INITIALIZATION - #make sure we execute _plug_init() for every command line plugin we import - for plugin_name in config['plugins']: - try: - plugin=__import__(plugin_name) - try: - eval('plugin.'+plugin_name+'Gui._plug_init(self)') - pass - except AttributeError: - pass - except ImportError: - pass - - - - #WX-SPECIFIC FUNCTIONS - def sizer_dance(self): + NEXT + Go to the next curve in the playlist. + If we are at the last curve, we come back to the first. + ----- + Syntax: next, n ''' - adjust size and placement of wxpython widgets. - ''' - self.splittersizer = wx.BoxSizer(wx.VERTICAL) - self.splittersizer.Add(self.mainpanel.splitter, 1, wx.EXPAND) - - self.plot1sizer = wx.BoxSizer() - self.plot1sizer.Add(self.controls[0], 1, wx.EXPAND) - - self.plot2sizer = wx.BoxSizer() - self.plot2sizer.Add(self.controls[1], 1, wx.EXPAND) - - self.panelsizer=wx.BoxSizer() - self.panelsizer.Add(self.mainpanel, -1, wx.EXPAND) - - self.cpanels[0].SetSizer(self.plot1sizer) - self.cpanels[1].SetSizer(self.plot2sizer) - - self.mainpanel.SetSizer(self.splittersizer) - self.SetSizer(self.panelsizer) - - def binding_dance(self): - self.Bind(wx.EVT_MENU, self.OnOpenPlayMenu, self.menubar.menu_items['openplaymenu']) - self.Bind(wx.EVT_MENU, self.OnExitMenu, self.menubar.menu_items['exitmenu']) - self.Bind(wx.EVT_MENU, self.OnExportText, self.menubar.menu_items['exporttextmenu']) - self.Bind(wx.EVT_MENU, self.OnExportImage, self.menubar.menu_items['exportimagemenu']) - self.Bind(wx.EVT_MENU, self.OnAboutMenu, self.menubar.menu_items['aboutmenu']) - - # DOUBLE PLOT MANAGEMENT - #---------------------- - def show_both(self): + selected_item = self.panelPlaylists.PlaylistsTree.GetSelection() + if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): + #GetFirstChild returns a tuple + #we only need the first element + next_item = self.panelPlaylists.PlaylistsTree.GetFirstChild(selected_item)[0] + else: + next_item = self.panelPlaylists.PlaylistsTree.GetNextSibling(selected_item) + if not next_item.IsOk(): + parent_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item) + #GetFirstChild returns a tuple + #we only need the first element + next_item = self.panelPlaylists.PlaylistsTree.GetFirstChild(parent_item)[0] + self.panelPlaylists.PlaylistsTree.SelectItem(next_item, True) + if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): + playlist = self.GetActivePlaylist() + if playlist.count > 1: + playlist.next() + self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdatePlot() + + def OnNotebookPageClose(self, event): + ctrl = event.GetEventObject() + playlist_name = ctrl.GetPageText(ctrl._curpage) + self.DeleteFromPlaylists(playlist_name) + + def OnPaneClose(self, event): + event.Skip() + + def OnPlaylistsLeftDclick(self, event): + if self.panelPlaylists.PlaylistsTree.Count > 0: + playlist_name = self.GetActivePlaylistName() + #if that playlist already exists + #we check if it is the active playlist (ie selected in panelPlaylists) + #and switch to it if necessary + if playlist_name in self.playlists: + index = self.plotNotebook.GetSelection() + current_playlist = self.plotNotebook.GetPageText(index) + if current_playlist != playlist_name: + index = self._GetPlaylistTab(playlist_name) + self.plotNotebook.SetSelection(index) + #if a curve was double-clicked + item = self.panelPlaylists.PlaylistsTree.GetSelection() + if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item): + index = self._GetActiveFileIndex() + else: + index = 0 + if index >= 0: + playlist = self.GetActivePlaylist() + playlist.index = index + self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdatePlot() + #if you uncomment the following line, the tree will collapse/expand as well + #event.Skip() + + def OnPlaylistsLeftDown(self, event): + hit_item, hit_flags = self.panelPlaylists.PlaylistsTree.HitTest(event.GetPosition()) + if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0: + self.panelPlaylists.PlaylistsTree.SelectItem(hit_item) + playlist_name = self.GetActivePlaylistName() + playlist = self.GetActivePlaylist() + #if a curve was clicked + item = self.panelPlaylists.PlaylistsTree.GetSelection() + if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item): + index = self._GetActiveFileIndex() + if index >= 0: + playlist.index = index + self.playlists[playlist_name] = playlist + event.Skip() + + def OnPrevious(self, event): ''' - Shows both plots. - ''' - self.mainpanel.splitter.SplitHorizontally(self.cpanels[0],self.cpanels[1]) - self.mainpanel.splitter.SetSashGravity(0.5) - self.mainpanel.splitter.SetSashPosition(300) #FIXME: we should get it and restore it - self.mainpanel.splitter.UpdateSize() - - def close_plot(self,plot): + PREVIOUS + Go to the previous curve in the playlist. + If we are at the first curve, we jump to the last. + ------- + Syntax: previous, p ''' - Closes one plot - only if it's open - ''' - if not self.cpanels[plot].IsShown(): - return - if plot != 0: - self.current_plot_dest = 0 + #playlist = self.playlists[self.GetActivePlaylistName()][0] + #select the previous curve and tell the user if we wrapped around + #self.AppendToOutput(playlist.previous()) + selected_item = self.panelPlaylists.PlaylistsTree.GetSelection() + if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): + previous_item = self.panelPlaylists.PlaylistsTree.GetLastChild(selected_item) else: - self.current_plot_dest = 1 - self.cpanels[plot].Hide() - self.mainpanel.splitter.Unsplit(self.cpanels[plot]) - self.mainpanel.splitter.UpdateSize() - + previous_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item) + if not previous_item.IsOk(): + parent_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item) + previous_item = self.panelPlaylists.PlaylistsTree.GetLastChild(parent_item) + self.panelPlaylists.PlaylistsTree.SelectItem(previous_item, True) + playlist = self.GetActivePlaylist() + if playlist.count > 1: + playlist.previous() + self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdatePlot() + + def OnPropGridChanged (self, event): + prop = event.GetProperty() + if prop: + item_section = self.panelProperties.SelectedTreeItem + item_plugin = self.panelCommands.CommandsTree.GetItemParent(item_section) + plugin = self.panelCommands.CommandsTree.GetItemText(item_plugin) + config = self.configs[plugin] + property_section = self.panelCommands.CommandsTree.GetItemText(item_section) + property_key = prop.GetName() + property_value = prop.GetDisplayedString() + + config[property_section][property_key]['value'] = property_value + + def OnRestorePerspective(self, event): + name = self.MenuBar.FindItemById(event.GetId()).GetLabel() + self._RestorePerspective(name) +# self._mgr.LoadPerspective(self._perspectives[name]) +# self.config['perspectives']['active'] = name +# self._mgr.Update() +# all_panes = self._mgr.GetAllPanes() +# for pane in all_panes: +# if not pane.name.startswith('toolbar'): +# if pane.name == 'Assistant': +# self.MenuBar.FindItemById(ID_ViewAssistant).Check(pane.window.IsShown()) +# if pane.name == 'Folders': +# self.MenuBar.FindItemById(ID_ViewFolders).Check(pane.window.IsShown()) +# if pane.name == 'Playlists': +# self.MenuBar.FindItemById(ID_ViewPlaylists).Check(pane.window.IsShown()) +# if pane.name == 'Commands': +# self.MenuBar.FindItemById(ID_ViewCommands).Check(pane.window.IsShown()) +# if pane.name == 'Properties': +# self.MenuBar.FindItemById(ID_ViewProperties).Check(pane.window.IsShown()) +# if pane.name == 'Output': +# self.MenuBar.FindItemById(ID_ViewOutput).Check(pane.window.IsShown()) +# if pane.name == 'Results': +# self.MenuBar.FindItemById(ID_ViewResults).Check(pane.window.IsShown()) + + def OnResultsCheck(self, index, flag): + #TODO: fix for multiple results + results = self.GetActivePlot().results + fit_function_str = self.GetStringFromConfig('results', 'show_results', 'fit_function') + results[fit_function_str].results[index].visible = flag + self.UpdatePlot() + + def OnSavePerspective(self, event): + + def nameExists(name): + menu_position = self.MenuBar.FindMenu('Perspectives') + menu = self.MenuBar.GetMenu(menu_position) + for item in menu.GetMenuItems(): + if item.GetText() == name: + return True + return False + + done = False + while not done: + dialog = wx.TextEntryDialog(self, 'Enter a name for the new perspective:', 'Save perspective') + dialog.SetValue('New perspective') + if dialog.ShowModal() != wx.ID_OK: + return + else: + name = dialog.GetValue() - def OnClosePlot(self,event): - self.close_plot(event.to_close) + if nameExists(name): + dialogConfirm = wx.MessageDialog(self, 'A file with this name already exists.\n\nDo you want to replace it?', 'Confirm', wx.YES_NO|wx.ICON_QUESTION|wx.CENTER) + if dialogConfirm.ShowModal() == wx.ID_YES: + done = True + else: + done = True + + perspective = self._mgr.SavePerspective() + self._SavePerspectiveToFile(name, perspective) + self.config['perspectives']['active'] = name + self.UpdatePerspectivesMenu() +# if nameExists(name): +# #check the corresponding menu item +# menu_item = self.GetPerspectiveMenuItem(name) +# #replace the perspectiveStr in _pespectives +# self._perspectives[name] = perspective +# else: +# #because we deal with radio items, we need to do some extra work +# #delete all menu items from the perspectives menu +# for item in self._perspectives_menu.GetMenuItems(): +# self._perspectives_menu.DeleteItem(item) +# #recreate the perspectives menu +# self._perspectives_menu.Append(ID_SavePerspective, 'Save Perspective') +# self._perspectives_menu.Append(ID_DeletePerspective, 'Delete Perspective') +# self._perspectives_menu.AppendSeparator() +# #convert the perspectives dictionary into a list +# # the list contains: +# #[0]: name of the perspective +# #[1]: perspective +# perspectives_list = [key for key, value in self._perspectives.iteritems()] +# perspectives_list.append(name) +# perspectives_list.sort() +# #add all previous perspectives +# for index, item in enumerate(perspectives_list): +# menu_item = self._perspectives_menu.AppendRadioItem(ID_FirstPerspective + index, item) +# if item == name: +# menu_item.Check() +# #add the new perspective to _perspectives +# self._perspectives[name] = perspective + + def OnSize(self, event): + event.Skip() + + def OnTreeCtrlCommandsSelectionChanged(self, event): + selected_item = event.GetItem() + if selected_item is not None: + plugin = '' + section = '' + #deregister/register the listener to avoid infinite loop + evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged) + self.panelCommands.CommandsTree.SelectItem(selected_item) + evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self.panelCommands.CommandsTree) + self.panelProperties.SelectedTreeItem = selected_item + #if a command was clicked + properties = [] + if not self.panelCommands.CommandsTree.ItemHasChildren(selected_item): + item_plugin = self.panelCommands.CommandsTree.GetItemParent(selected_item) + plugin = self.panelCommands.CommandsTree.GetItemText(item_plugin) + if self.configs.has_key(plugin): + #config = self.panelCommands.CommandsTree.GetPyData(item_plugin) + config = self.configs[plugin] + section = self.panelCommands.CommandsTree.GetItemText(selected_item) + #display docstring in help window + doc_string = eval('self.do_' + section + '.__doc__') + if section in config: + for option in config[section]: + properties.append([option, config[section][option]]) + else: + plugin = self.panelCommands.CommandsTree.GetItemText(selected_item) + if plugin != 'core': + doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__') + else: + doc_string = 'The module "core" contains Hooke core functionality' + if doc_string is not None: + self.panelAssistant.ChangeValue(doc_string) + else: + self.panelAssistant.ChangeValue('') + panels.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties) + #save the currently selected command/plugin to the config file + self.config['command']['command'] = section + self.config['command']['plugin'] = plugin + + def OnTreeCtrlItemActivated(self, event): + self.OnExecute(event) + + def OnView(self, event): + menu_id = event.GetId() + menu_item = self.MenuBar.FindItemById(menu_id) + menu_label = menu_item.GetLabel() + + pane = self._mgr.GetPane(menu_label) + pane.Show(not pane.IsShown()) + #if we don't do the following, the Folders pane does not resize properly on hide/show + if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked(): + #folders_size = pane.GetSize() + self.panelFolders.Fit() + self._mgr.Update() + + def _measure_N_points(self, N, message='', whatset=lh.RETRACTION): + ''' + General helper function for N-points measurements + By default, measurements are done on the retraction + ''' + if message != '': + dialog = wx.MessageDialog(None, message, 'Info', wx.OK) + dialog.ShowModal() + + figure = self.GetActiveFigure() + + xvector = self.displayed_plot.curves[whatset].x + yvector = self.displayed_plot.curves[whatset].y + + clicked_points = figure.ginput(N, timeout=-1, show_clicks=True) + + points = [] + for clicked_point in clicked_points: + point = lh.ClickedPoint() + point.absolute_coords = clicked_point[0], clicked_point[1] + point.dest = 0 + #TODO: make this optional? + #so far, the clicked point is taken, not the corresponding data point + point.find_graph_coords(xvector, yvector) + point.is_line_edge = True + point.is_marker = True + points.append(point) + return points + + def _clickize(self, xvector, yvector, index): + ''' + returns a ClickedPoint() object from an index and vectors of x, y coordinates + ''' + point = lh.ClickedPoint() + point.index = index + point.absolute_coords = xvector[index], yvector[index] + point.find_graph_coords(xvector, yvector) + return point - def OnShowPlots(self,event): - self.show_both() + def _delta(self, color='black', message='Click 2 points', show=True, whatset=1): + ''' + calculates the difference between two clicked points + ''' + clicked_points = self._measure_N_points(N=2, message=message, whatset=whatset) + dx = abs(clicked_points[0].graph_coords[0] - clicked_points[1].graph_coords[0]) + dy = abs(clicked_points[0].graph_coords[1] - clicked_points[1].graph_coords[1]) + plot = self.GetDisplayedPlotCorrected() - #FILE MENU FUNCTIONS - #-------------------- - def OnOpenPlayMenu(self, event): - pass + curve = plot.curves[whatset] + unitx = curve.units.x + unity = curve.units.y - def OnExitMenu(self,event): - pass + #TODO: move this to clicked_points? + if show: + for point in clicked_points: + points = copy.deepcopy(curve) + points.x = point.graph_coords[0] + points.y = point.graph_coords[1] - def OnExportText(self,event): - pass + points.color = color + points.size = 20 + points.style = 'scatter' + plot.curves.append(points) - def OnExportImage(self,event): - pass + self.UpdatePlot(plot) - def OnAboutMenu(self,event): - pass + return dx, unitx, dy, unity - #PLOT INTERACTION - #---------------- - def PlotCurve(self,event): + def do_plotmanipulators(self): ''' - plots the current ext,ret curve. - ''' - dest=0 - - #FIXME: BAD kludge following. There should be a well made plot queue mechanism, with replacements etc. - #--- - #If we have only one plot in the event, we already have one in self.plots and this is a secondary plot, - #do not erase self.plots but append the new plot to it. - if len(event.plots) == 1 and event.plots[0].destination != 0 and len(self.plots) == 1: - self.plots.append(event.plots[0]) - #if we already have two plots and a new secondary plot comes, we substitute the previous - if len(event.plots) == 1 and event.plots[0].destination != 0 and len(self.plots) > 1: - self.plots[1] = event.plots[0] - else: - self.plots = event.plots - - #FIXME. Should be in PlotObject, somehow - c=0 - for plot in self.plots: - if self.plots[c].styles==[]: - self.plots[c].styles=[None for item in plot.vectors] - if self.plots[c].colors==[]: - self.plots[c].colors=[None for item in plot.vectors] - - for plot in self.plots: - ''' - MAIN LOOP FOR ALL PLOTS (now only 2 are allowed but...) - ''' - if 'destination' in dir(plot): - dest=plot.destination - - #if the requested panel is not shown, show it - if not ( self.cpanels[dest].IsShown() ): - self.show_both() - - self.axes[dest].hold(False) - self.current_vectors=plot.vectors - self.current_title=plot.title - self.current_plot_dest=dest #let's try this way to take into account the destination plot... - - c=0 - - if len(plot.colors)==0: - plot.colors=[None] * len(plot.vectors) - if len(plot.styles)==0: - plot.styles=[None] * len(plot.vectors) - - for vectors_to_plot in self.current_vectors: - if plot.styles[c]=='scatter': - if plot.colors[c]==None: - self.axes[dest].scatter(vectors_to_plot[0], vectors_to_plot[1]) - else: - self.axes[dest].scatter(vectors_to_plot[0], vectors_to_plot[1],color=plot.colors[c]) - else: - if plot.colors[c]==None: - self.axes[dest].plot(vectors_to_plot[0], vectors_to_plot[1]) - else: - self.axes[dest].plot(vectors_to_plot[0], vectors_to_plot[1], color=plot.colors[c]) - self.axes[dest].hold(True) - c+=1 - - ''' - for vectors_to_plot in self.current_vectors: - if len(vectors_to_plot)==2: #3d plots are to come... - if len(plot.styles) > 0 and plot.styles[c] == 'scatter': - self.axes[dest].scatter(vectors_to_plot[0],vectors_to_plot[1]) - elif len(plot.styles) > 0 and plot.styles[c] == 'scatter_red': - self.axes[dest].scatter(vectors_to_plot[0],vectors_to_plot[1],color='red') - else: - self.axes[dest].plot(vectors_to_plot[0],vectors_to_plot[1]) - - self.axes[dest].hold(True) - c+=1 - else: - pass - ''' - #FIXME: tackles only 2d plots - self.axes[dest].set_xlabel(plot.units[0]) - self.axes[dest].set_ylabel(plot.units[1]) - - #FIXME: set smaller fonts - self.axes[dest].set_title(plot.title) - - if plot.xaxes: - #swap X axis - xlim=self.axes[dest].get_xlim() - self.axes[dest].set_xlim((xlim[1],xlim[0])) - if plot.yaxes: - #swap Y axis - ylim=self.axes[dest].get_ylim() - self.axes[dest].set_ylim((ylim[1],ylim[0])) - - self.controls[dest].draw() - + Please select the plotmanipulators you would like to use + and define the order in which they will be applied to the data. - def PlotContact(self,event): + Click 'Execute' to apply your changes. ''' - plots the contact point - DEPRECATED! - ''' - self.axes[0].hold(True) - self.current_contact_index=event.contact_index + self.UpdatePlot() - #now we fake a clicked point - self.clicked_points.append(ClickedPoint()) - self.clicked_points[-1].absolute_coords=self.current_x_ret[dest][self.current_contact_index], self.current_y_ret[dest][self.current_contact_index] - self.clicked_points[-1].is_marker=True - - self._replot() - self.clicked_points=[] + def do_test(self): + self.AppendToOutput(self.config['perspectives']['active']) + pass - def OnMeasurePoints(self,event): + def do_version(self): ''' - trigger flags to measure N points - ''' - self.click_flags_functions['measure_points'][0]=True - if 'num_of_points' in dir(event): - self.num_of_points=event.num_of_points - if 'set' in dir(event): - self.measure_set=event.set - - def ClickPoint0(self,event): - self.current_plot_dest=0 - self.ClickPoint(event) - def ClickPoint1(self,event): - self.current_plot_dest=1 - self.ClickPoint(event) - - def ClickPoint(self,event): + VERSION + ------ + Prints the current version and codename, plus library version. Useful for debugging. ''' - this function decides what to do when we receive a left click on the axes. - We trigger other functions: - - the action chosen by the CLI sends an event - - the event raises a flag : self.click_flags_functions['foo'][0] - - the raised flag wants the function in self.click_flags_functions[1] to be called after a click - ''' - for key, value in self.click_flags_functions.items(): - if value[0]: - eval('self.'+value[1]+'(event)') - - - - def MeasurePoints(self,event,current_set=1): - dest=self.current_plot_dest - try: - current_set=self.measure_set - except AttributeError: - pass - - #find the current plot matching the clicked destination - plot=self._plot_of_dest() - if len(plot.vectors)-1 < current_set: #what happens if current_set is 1 and we have only 1 vector? - current_set=current_set-len(plot.vectors) - - xvector=plot.vectors[current_set][0] - yvector=plot.vectors[current_set][1] - - self.clicked_points.append(ClickedPoint()) - self.clicked_points[-1].absolute_coords=event.xdata, event.ydata - self.clicked_points[-1].find_graph_coords(xvector,yvector) - self.clicked_points[-1].is_marker=True - self.clicked_points[-1].is_line_edge=True - self.clicked_points[-1].dest=dest - - self._replot() - - if len(self.clicked_points)==self.num_of_points: - self.events_from_gui.put(self.clicked_points) - #restore to default state: - self.clicked_points=[] - self.click_flags_functions['measure_points'][0]=False - - - def OnGetDisplayedPlot(self,event): - if 'dest' in dir(event): - self.GetDisplayedPlot(event.dest) + self.AppendToOutput('Hooke ' + __version__ + ' (' + __codename__ + ')') + self.AppendToOutput('Released on: ' + __releasedate__) + self.AppendToOutput('---') + self.AppendToOutput('Python version: ' + python_version) + self.AppendToOutput('WxPython version: ' + wx_version) + self.AppendToOutput('Matplotlib version: ' + mpl_version) + self.AppendToOutput('SciPy version: ' + scipy_version) + self.AppendToOutput('NumPy version: ' + numpy_version) + self.AppendToOutput('---') + self.AppendToOutput('Platform: ' + str(platform.uname())) + #TODO: adapt to 'new' config + #self.AppendToOutput('---') + #self.AppendToOutput('Loaded plugins:', self.config['loaded_plugins']) + + def UpdatePerspectivesMenu(self): + #add perspectives to menubar and _perspectives + perspectivesDirectory = os.path.join(lh.hookeDir, 'perspectives') + self._perspectives = {} + if os.path.isdir(perspectivesDirectory): + perspectiveFileNames = os.listdir(perspectivesDirectory) + for perspectiveFilename in perspectiveFileNames: + filename = lh.get_file_path(perspectiveFilename, ['perspectives']) + if os.path.isfile(filename): + perspectiveFile = open(filename, 'rU') + perspective = perspectiveFile.readline() + perspectiveFile.close() + if perspective != '': + name, extension = os.path.splitext(perspectiveFilename) + if extension == '.txt': + self._perspectives[name] = perspective + + #in case there are no perspectives + if not self._perspectives: + perspective = self._mgr.SavePerspective() + self._perspectives['Default'] = perspective + self._SavePerspectiveToFile('Default', perspective) + + selected_perspective = self.config['perspectives']['active'] + if not self._perspectives.has_key(selected_perspective): + self.config['perspectives']['active'] = 'Default' + selected_perspective = 'Default' + + perspectives_list = [key for key, value in self._perspectives.iteritems()] + perspectives_list.sort() + + #get the Perspectives menu + menu_position = self.MenuBar.FindMenu('Perspectives') + menu = self.MenuBar.GetMenu(menu_position) + #delete all menu items + for item in menu.GetMenuItems(): + menu.DeleteItem(item) + #rebuild the menu by adding the standard menu items + menu.Append(ID_SavePerspective, 'Save Perspective') + menu.Append(ID_DeletePerspective, 'Delete Perspective') + menu.AppendSeparator() + #add all previous perspectives + for index, label in enumerate(perspectives_list): + menu_item = menu.AppendRadioItem(ID_FirstPerspective + index, label) + if label == selected_perspective: + self._RestorePerspective(label) + menu_item.Check(True) + + def UpdatePlaylistsTreeSelection(self): + playlist = self.GetActivePlaylist() + if playlist is not None: + if playlist.index >= 0: + self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdatePlot() + + def UpdatePlot(self, plot=None): + + def add_to_plot(curve): + if curve.visible and curve.x and curve.y: + destination = (curve.destination.column - 1) * number_of_rows + curve.destination.row - 1 + axes_list[destination].set_title(curve.title) + axes_list[destination].set_xlabel(curve.units.x) + axes_list[destination].set_ylabel(curve.units.y) + if curve.style == 'plot': + axes_list[destination].plot(curve.x, curve.y, color=curve.color, label=curve.label, zorder=1) + if curve.style == 'scatter': + axes_list[destination].scatter(curve.x, curve.y, color=curve.color, label=curve.label, s=curve.size, zorder=2) + + if plot is None: + active_file = self.GetActiveFile() + if not active_file.driver: + active_file.identify(self.drivers) + self.displayed_plot = copy.deepcopy(active_file.plot) + #add raw curves to plot + self.displayed_plot.raw_curves = copy.deepcopy(self.displayed_plot.curves) + #apply all active plotmanipulators and add the 'manipulated' data + for plotmanipulator in self.plotmanipulators: + if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name): + self.displayed_plot = plotmanipulator.method(self.displayed_plot, active_file) + #add corrected curves to plot + self.displayed_plot.corrected_curves = copy.deepcopy(self.displayed_plot.curves) else: - self.GetDisplayedPlot(self.current_plot_dest) + active_file = None + self.displayed_plot = copy.deepcopy(plot) - def GetDisplayedPlot(self,dest): - ''' - returns to the CLI the currently displayed plot for the given destination - ''' - displayed_plot=self._plot_of_dest(dest) - events_from_gui.put(displayed_plot) + figure = self.GetActiveFigure() - def ExportImage(self,event): - ''' - exports an image as a file. - Current supported file formats: png, eps - (matplotlib docs say that jpeg should be supported too, but with .jpg it doesn't work for me!) - ''' - #dest=self.current_plot_dest - dest=event.dest - filename=event.name - self.figures[dest].savefig(filename) - - ''' - def _find_nearest_point(self, mypoint, dataset=1): - - #Given a clicked point on the plot, finds the nearest point in the dataset (in X) that - #corresponds to the clicked point. - - dest=self.current_plot_dest - - xvector=plot.vectors[dataset][0] - yvector=plot.vectors[dataset][1] - - #Ye Olde sorting algorithm... - #FIXME: is there a better solution? - index=0 - best_index=0 - best_diff=10^9 #hope we never go over this magic number :( - for point in xvector: - diff=abs(point-mypoint) - if diff 0 and plot.styles[c]=='scatter': - self.axes[dest].scatter(plotset[0], plotset[1],color=plot.colors[c]) - elif len(plot.styles) > 0 and plot.styles[c] == 'scatter_red': - self.axes[dest].scatter(plotset[0],plotset[1],color='red') - else: - self.axes[dest].plot(plotset[0], plotset[1]) - ''' - c+=1 - #plot points we have clicked - for item in self.clicked_points: - if item.is_marker: - if item.graph_coords==(None,None): #if we have no graph coords, we display absolute coords - self.axes[dest].scatter([item.absolute_coords[0]],[item.absolute_coords[1]]) - else: - self.axes[dest].scatter([item.graph_coords[0]],[item.graph_coords[1]]) - - if self.plot_fit: - print 'DEBUGGING WARNING: use of self.plot_fit is deprecated!' - self.axes[dest].plot(self.plot_fit[0],self.plot_fit[1]) + axes_list =[] - self.axes[dest].hold(True) - #set old axes again - self.axes[dest].set_xlim(xlim) - self.axes[dest].set_ylim(ylim) - #set title and names again... - self.axes[dest].set_title(self.current_title) - self.axes[dest].set_xlabel(plot.units[0]) - self.axes[dest].set_ylabel(plot.units[1]) - #and redraw! - self.controls[dest].draw() + number_of_columns = max([curve.destination.column for curve in self.displayed_plot.curves]) + number_of_rows = max([curve.destination.row for curve in self.displayed_plot.curves]) + for index in range(number_of_rows * number_of_columns): + axes_list.append(figure.add_subplot(number_of_rows, number_of_columns, index + 1)) -class MySplashScreen(wx.SplashScreen): - """ - Create a splash screen widget. - That's just a fancy addition... every serious application has a splash screen! - """ - def __init__(self, frame): - # This is a recipe to a the screen. - # Modify the following variables as necessary. - #aBitmap = wx.Image(name = "wxPyWiki.jpg").ConvertToBitmap() - aBitmap=wx.Image(name='hooke.jpg').ConvertToBitmap() - splashStyle = wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT - splashDuration = 2000 # milliseconds - splashCallback = None - # Call the constructor with the above arguments in exactly the - # following order. - wx.SplashScreen.__init__(self, aBitmap, splashStyle, - splashDuration, None, -1) - wx.EVT_CLOSE(self, self.OnExit) - self.frame=frame - wx.Yield() + for curve in self.displayed_plot.curves: + add_to_plot(curve) - def OnExit(self, evt): - self.Hide() + #make sure the titles of 'subplots' do not overlap with the axis labels of the 'main plot' + figure.subplots_adjust(hspace=0.3) - self.frame.Show() - # The program will freeze without this line. - evt.Skip() # Make sure the default handler runs too... - - -#------------------------------------------------------------------------------ - -def main(): - - #save the directory where Hooke is located - config['hookedir']=os.getcwd() - - #now change to the working directory. - try: - os.chdir(config['workdir']) - except OSError: - print "Warning: Invalid work directory." + #TODO: add multiple results support to fit in curve.results: + #get the fit_function results to display + fit_function_str = self.GetStringFromConfig('results', 'show_results', 'fit_function') + self.panelResults.ClearResults() + plot = self.GetActivePlot() + if plot is not None: + if plot.results.has_key(fit_function_str): + for curve in plot.results[fit_function_str].results: + add_to_plot(curve) + self.panelResults.DisplayResults(plot.results[fit_function_str]) + else: + self.panelResults.ClearResults() - app=wx.PySimpleApp() + figure.canvas.draw() - def make_gui_class(*bases): - return type(MainWindow)("MainWindowPlugged", bases + (MainWindow,), {}) + for axes in axes_list: + #TODO: add legend as global option or per graph option + #axes.legend() + axes.figure.canvas.draw() - main_frame = make_gui_class(*GUI_PLUGINS)(None, -1, ('Hooke '+__version__)) - #FIXME. The frame.Show() is called by the splashscreen here! Ugly as hell. +if __name__ == '__main__': - mysplash=MySplashScreen(main_frame) - mysplash.Show() + ## now, silence a deprecation warning for py2.3 + import warnings + warnings.filterwarnings("ignore", "integer", DeprecationWarning, "wxPython.gdi") - my_cmdline=CliThread(main_frame, list_of_events) - my_cmdline.start() + redirect = True + if __debug__: + redirect=False + app = Hooke(redirect=redirect) app.MainLoop() -main() + diff --git a/hooke_cli.py b/hooke_cli.py deleted file mode 100755 index 4746ca5..0000000 --- a/hooke_cli.py +++ /dev/null @@ -1,990 +0,0 @@ -#!/usr/bin/env python - -''' -hooke_cli.py - -Command line module of Hooke. - -Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy). - -This program is released under the GNU General Public License version 2. -''' - - -from libhooke import * #FIXME -import libhookecurve as lhc - -import libinput as linp -import liboutlet as lout - -from libhooke import WX_GOOD -from libhooke import HOOKE_VERSION - -import wxversion -wxversion.select(WX_GOOD) -import wx - -from wx.lib.newevent import NewEvent -from matplotlib.numerix import * #FIXME - -import xml.dom.minidom -import sys, os, os.path, glob, shutil -import Queue -import cmd -import time - -global __version__ -global __codename__ -global __releasedate__ -__version__ = HOOKE_VERSION[0] -__codename__ = HOOKE_VERSION[1] -__releasedate__ = HOOKE_VERSION[2] - -from matplotlib import __version__ as mpl_version -from wx import __version__ as wx_version -from wxmpl import __version__ as wxmpl_version -from scipy import __version__ as scipy_version -from numpy import __version__ as numpy_version -from sys import version as python_version -import platform - - -class HookeCli(cmd.Cmd): - - def __init__(self,frame,list_of_events,events_from_gui,config,drivers): - cmd.Cmd.__init__(self) - - self.prompt = 'hooke: ' - - - self.current_list=[] #the playlist we're using - - self.current=None #the current curve under analysis. - self.plots=None - ''' - The actual hierarchy of the "current curve" is a bit complex: - - self.current = the lhc.HookeCurve container object of the current curve - self.current.curve = the current "real" curve object as defined in the filetype driver class - self.current.curve.default_plots() = the default plots of the filetype driver. - - The plot objects obtained by mean of self.current.curve.default_plots() - then undergoes modifications by the plotmanip - modifier functions. The modified plot is saved in self.plots and used if needed by other functions. - ''' - - - self.pointer=0 #a pointer to navigate the current list - - #Things that come from outside - self.frame=frame #the wx frame we refer to - self.list_of_events=list_of_events #a list of wx events we use to interact with the GUI - self.events_from_gui=events_from_gui #the Queue object we use to have messages from the GUI - self.config=config #the configuration dictionary - self.drivers=drivers #the file format drivers - - #get plot manipulation functions - plotmanip_functions=[] - for object_name in dir(self): - if object_name[0:9]=='plotmanip': - plotmanip_functions.append(getattr(self,object_name)) - #put plotmanips in order - self.plotmanip=[None for item in self.config['plotmanips']] - for item in plotmanip_functions: - namefunction=item.__name__[10:] - if namefunction in self.config['plotmanips']: - nameindex=self.config['plotmanips'].index(namefunction) #index of function in plotmanips config - self.plotmanip[nameindex] = item - else: - pass - - - self.playlist_saved=0 #Did we save the playlist? - self.playlist_name='' #Name of playlist - self.notes_saved=1 #Did we save the notes? - self.notes_filename=None #Name of notes - - #create outlet - self.outlet=lout.Outlet() - - #Data that must be saved in the playlist, related to the whole playlist (not individual curves) - self.playlist_generics={} - - #make sure we execute _plug_init() for every command line plugin we import - for plugin_name in self.config['plugins']: - try: - plugin=__import__(plugin_name) - try: - eval('plugin.'+plugin_name+'Commands._plug_init(self)') - except AttributeError: - pass - except ImportError: - pass - - #load default list, if possible - self.do_loadlist(self.config['defaultlist']) - -#HELPER FUNCTIONS -#Everything sending an event should be here - def _measure_N_points(self, N, whatset=1): - ''' - general helper function for N-points measures - ''' - wx.PostEvent(self.frame,self.list_of_events['measure_points'](num_of_points=N, set=whatset)) - while 1: - try: - points=self.frame.events_from_gui.get() - break - except Empty: - pass - return points - - def _get_displayed_plot(self,dest=0): - ''' - returns the currently displayed plot. - ''' - wx.PostEvent(self.frame, self.list_of_events['get_displayed_plot'](dest=dest)) - while 1: - try: - displayed_plot=self.events_from_gui.get() - except Empty: - pass - if displayed_plot: - break - return displayed_plot - - def _send_plot(self,plots): - ''' - sends a plot to the GUI - ''' - wx.PostEvent(self.frame, self.list_of_events['plot_graph'](plots=plots)) - return - - def _find_plotmanip(self, name): - ''' - returns a plot manipulator function from its name - ''' - return self.plotmanip[self.config['plotmanips'].index(name)] - - def _clickize(self, xvector, yvector, index): - ''' - returns a ClickedPoint() object from an index and vectors of x, y coordinates - ''' - point=ClickedPoint() - point.index=index - point.absolute_coords=xvector[index],yvector[index] - point.find_graph_coords(xvector,yvector) - return point - -#HERE COMMANDS BEGIN - - def help_set(self): - print ''' -SET -Sets a local configuration variable -------------- -Syntax: set [variable] [value] - ''' - def do_set(self,args): - #FIXME: some variables in self.config should be hidden or intelligently configurated... - args=args.split() - if len(args)==0: - print 'You must specify a variable and a value' - print 'Available variables:' - print self.config.keys() - return - if args[0] not in self.config.keys(): - print 'This is not an internal Hooke variable!' - return - if len(args)==1: - #FIXME:we should reload the config file and reset the config value - print self.config[args[0]] - return - key=args[0] - try: #try to have a numeric value - value=float(args[1]) - except ValueError: #if it cannot be converted to float, it's None, or a string... - if value.lower()=='none': - value=None - else: - value=args[1] - - self.config[key]=value - self.do_plot(0) - -#PLAYLIST MANAGEMENT AND NAVIGATION -#------------------------------------ - - def help_loadlist(self): - print ''' -LOADLIST -Loads a file playlist ------------ -Syntax: loadlist [playlist file] - ''' - def do_loadlist(self, args): - #checking for args: if nothing is given as input, we warn and exit. - while len(args)==0: - args=linp.safeinput('File to load?') - - arglist=args.split() - play_to_load=arglist[0] - - #We assume a Hooke playlist has the extension .hkp - if play_to_load[-4:] != '.hkp': - play_to_load+='.hkp' - - try: - playxml=PlaylistXML() - self.current_list, self.playlist_generics=playxml.load(play_to_load) - self.current_playxml=playxml - except IOError: - print 'File not found.' - return - - print 'Loaded %s curves' %len(self.current_list) - - if 'pointer' in self.playlist_generics.keys(): - self.pointer=int(self.playlist_generics['pointer']) - else: - #if no pointer is found, set the current curve as the first curve of the loaded playlist - self.pointer=0 - print 'Starting at curve ',self.pointer - - self.current=self.current_list[self.pointer] - - #resets saved/notes saved state - self.playlist_saved=0 - self.playlist_name='' - self.notes_saved=0 - - self.do_plot(0) - - - def help_genlist(self): - print ''' -GENLIST -Generates a file playlist. -Note it doesn't *save* it: see savelist for this. - -If [input files] is a directory, it will use all files in the directory for playlist. -So: -genlist dir -genlist dir/ -genlist dir/*.* - -are all equivalent syntax. ------------- -Syntax: genlist [input files] - -''' - def do_genlist(self,args): - #args list is: input path, output name - if len(args)==0: - args=linp.safeinput('Input files?') - - arglist=args.split() - list_path=arglist[0] - - #if it's a directory, is like /directory/*.* - #FIXME: probably a bit kludgy. - if os.path.isdir(list_path): - if platform.system == 'Windows': - SLASH="\\" - else: - SLASH="/" - if list_path[-1] == SLASH: - list_path=list_path+'*.*' - else: - list_path=list_path+SLASH+'*.*' - - #expanding correctly the input list with the glob module :) - list_files=glob.glob(list_path) - list_files.sort() - - self.current_list=[] - for item in list_files: - try: - if os.path.isfile(item): - self.current_list.append(lhc.HookeCurve(os.path.abspath(item))) - except: - pass - - self.pointer=0 - if len(self.current_list)>0: - self.current=self.current_list[self.pointer] - else: - print 'Empty list!' - return - - #resets saved/notes saved state - self.playlist_saved=0 - self.playlist_name='' - self.notes_saved=0 - - self.do_plot(0) - - - def do_savelist(self,args): - ''' - SAVELIST - Saves the current file playlist on disk. - ------------ - Syntax: savelist [filename] - ''' - while len(args)==0: - args=linp.safeinput('Output file?',['savedlist.txt']) - - output_filename=args - - self.playlist_generics['pointer']=self.pointer - - #autocomplete filename if not specified - if output_filename[-4:] != '.hkp': - output_filename+='.hkp' - - playxml=PlaylistXML() - playxml.export(self.current_list, self.playlist_generics) - playxml.save(output_filename) - - #remembers we have saved playlist - self.playlist_saved=1 - - def help_addtolist(self): - print ''' -ADDTOLIST -Adds a file to the current playlist --------------- -Syntax: addtolist [filename] -''' - def do_addtolist(self,args): - #args list is: input path - if len(args)==0: - print 'You must give the input filename you want to add' - self.help_addtolist() - return - - filenames=glob.glob(args) - - for filename in filenames: - self.current_list.append(lhc.HookeCurve(os.path.abspath(filename))) - #we need to save playlist - self.playlist_saved=0 - - def help_printlist(self): - print ''' -PRINTLIST -Prints the list of curves in the current playlist -------------- -Syntax: printlist -''' - def do_printlist(self,args): - for item in self.current_list: - print item.path - - - def help_jump(self): - print ''' -JUMP -Jumps to a given curve. ------- -Syntax: jump {$curve} - -If the curve is not in the current playlist, it politely asks if we want to add it. - ''' - def do_jump(self,filename): - ''' - jumps to the curve with the given filename. - if the filename is not in the playlist, it asks if we must add it or not. - ''' - - if filename=='': - filename=linp.safeinput('Jump to?') - - filepath=os.path.abspath(filename) - print filepath - - c=0 - item_not_found=1 - while item_not_found: - try: - - if self.current_list[c].path == filepath: - self.pointer=c - self.current=self.current_list[self.pointer] - item_not_found=0 - self.do_plot(0) - else: - c+=1 - except IndexError: - #We've found the end of the list. - answer=linp.safeinput('Curve not found in playlist. Add it to list?',['y']) - if answer.lower()[0]=='y': - try: - self.do_addtolist(filepath) - except: - print 'Curve file not found.' - return - self.current=self.current_list[-1] - self.pointer=(len(current_list)-1) - self.do_plot(0) - - item_not_found=0 - - - def do_index(self,args): - ''' - INDEX - Prints the index of the current curve in the list - ----- - Syntax: index - ''' - print self.pointer+1, 'of', len(self.current_list) - - - def help_next(self): - print ''' -NEXT -Go the next curve in the playlist. -If we are at the last curve, we come back to the first. ------ -Syntax: next, n - ''' - def do_next(self,args): - try: - self.current.curve.close_all() - except: - print 'No curve file loaded, currently!' - print 'This should not happen, report to http://code.google.com/p/hooke' - return - - if self.pointer == (len(self.current_list)-1): - self.pointer=0 - print 'Playlist finished; back to first curve.' - else: - self.pointer+=1 - - self.current=self.current_list[self.pointer] - self.do_plot(0) - - - def help_n(self): - self.help_next() - def do_n(self,args): - self.do_next(args) - - def help_previous(self,args): - print ''' -PREVIOUS -Go to the previous curve in the playlist. -If we are at the first curve, we jump to the last. -------- -Syntax: previous, p - ''' - def do_previous(self,args): - try: - self.current.curve.close_all() - except: - print 'No curve file loaded, currently!' - print 'This should not happen, report to http://code.google.com/p/hooke' - return - if self.pointer == 0: - self.pointer=(len(self.current_list)-1) - print 'Start of playlist; jump to last curve.' - else: - self.pointer-=1 - - self.current=self.current_list[self.pointer] - self.do_plot(args) - - - def help_p(self): - self.help_previous() - def do_p(self,args): - self.do_previous(args) - - -#PLOT INTERACTION COMMANDS -#------------------------------- - def help_plot(self): - print ''' -PLOT -Plots the current force curve -------- -Syntax: plot - ''' - def do_plot(self,args): - - self.current.identify(self.drivers) - self.plots=self.current.curve.default_plots() - try: - self.plots=self.current.curve.default_plots() - except Exception, e: - print 'Unexpected error occurred in do_plot().' - print e - return - - #apply the plotmanip functions eventually present - nplots=len(self.plots) - c=0 - while c 1: - dest=int(args[1]) - - export_image=self.list_of_events['export_image'] - wx.PostEvent(self.frame, export_image(name=name, dest=dest)) - - - def help_txt(self): - print ''' -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} - ''' - def do_txt(self,args): - - 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) - - whichplot=0 - args=args.split() - if len(args)==0: - filename=linp.safeinput('Filename?',[self.current.path+'.txt']) - else: - filename=linp.checkalphainput(args[0],self.current.path+'.txt',[]) - try: - whichplot=int(args[1]) - except: - pass - - columns=[] - for dataset in self.plots[whichplot].vectors: - for i in range(0,len(dataset)): - columns.append([]) - for value in dataset[i]: - columns[-1].append(str(value)) - - rows=transposed2(columns, 'nan') - rows=[' , '.join(item) for item in rows] - text='\n'.join(rows) - - txtfile=open(filename,'w+') - #Save units of measure in header - txtfile.write('X:'+self.plots[whichplot].units[0]+'\n') - txtfile.write('Y:'+self.plots[whichplot].units[1]+'\n') - txtfile.write(text) - txtfile.close() - - - #LOGGING, REPORTING, NOTETAKING - - - def do_note_old(self,args): - ''' - NOTE_OLD - **deprecated**: Use note instead. Will be removed in 0.9 - - Writes or displays a note about the current curve. - If [anything] is empty, it displays the note, otherwise it adds a note. - The note is then saved in the playlist if you issue a savelist command - --------------- - Syntax: note_old [anything] - - ''' - if args=='': - print self.current_list[self.pointer].notes - else: - #bypass UnicodeDecodeError troubles - try: - args=args.decode('ascii') - except: - args=args.decode('ascii','ignore') - if len(args)==0: - args='?' - - self.current_list[self.pointer].notes=args - self.notes_saved=0 - - - def do_note(self,args): - ''' - NOTE - - Writes or displays a note about the current curve. - If [anything] is empty, it displays the note, otherwise it adds a note. - The note is then saved in the playlist if you issue a savelist command. - --------------- - Syntax: note_old [anything] - - ''' - if args=='': - print self.current_list[self.pointer].notes - else: - if self.notes_filename == None: - self.notes_filename=raw_input('Notebook filename? ') - title_line='Notes taken at '+time.asctime()+'\n' - f=open(self.notes_filename,'w') - f.write(title_line) - f.close() - - #bypass UnicodeDecodeError troubles - try: - args=args.decode('ascii') - except: - args=args.decode('ascii','ignore') - if len(args)==0: - args='?' - self.current_list[self.pointer].notes=args - - f=open(self.notes_filename,'a+') - note_string=(self.current.path+' | '+self.current.notes+'\n') - f.write(note_string) - f.close() - - def help_notelog(self): - print ''' -NOTELOG -Writes a log of the notes taken during the session for the current -playlist --------------- -Syntax notelog [filename] -''' - def do_notelog(self,args): - - if len(args)==0: - args=linp.safeinput('Notelog filename?',['notelog.txt']) - - note_lines='Notes taken at '+time.asctime()+'\n' - for item in self.current_list: - if len(item.notes)>0: - #FIXME: log should be justified - #FIXME: file path should be truncated... - note_string=(item.path+' | '+item.notes+'\n') - note_lines+=note_string - - try: - f=open(args,'a+') - f.write(note_lines) - f.close() - except IOError, (ErrorNumber, ErrorMessage): - print 'Error: notes cannot be saved. Catched exception:' - print ErrorMessage - - self.notes_saved=1 - - def help_copylog(self): - print ''' -COPYLOG -Moves the annotated curves to another directory ------------ -Syntax copylog [directory] - ''' - def do_copylog(self,args): - - if len(args)==0: - args=linp.safeinput('Destination directory?') #TODO default - - mydir=os.path.abspath(args) - if not os.path.isdir(mydir): - print 'Destination is not a directory.' - return - - for item in self.current_list: - if len(item.notes)>0: - try: - shutil.copy(item.path, mydir) - except (OSError, IOError): - print 'Cannot copy file. '+item.path+' Perhaps you gave me a wrong directory?' - -#OUTLET management -#----------------- - def do_outlet_show(self,args): - '''OUTLET_SHOW - --------- - Shows current content of outlet with index for reference - ''' - self.outlet.printbuf() - - def do_outlet_undo(self, args): - '''OUTLET_UNDO - --------- - Eliminates last entry in outlet - ''' - print 'Erasing last entry' - self.outlet.pop() - - def do_outlet_delete(self, args): - '''OUTLET_DELETE - Eliminates a particular entry from outlet - Syntax: outlet_delete n - ''' - if len(args)==0: - print 'Index needed!, use outlet_show to know it' - else: - self.outlet.delete(args) - -#OS INTERACTION COMMANDS -#----------------- - def help_dir(self): - print ''' -DIR, LS -Lists the files in the directory ---------- -Syntax: dir [path] - ls [path] - ''' - def do_dir(self,args): - - if len(args)==0: - args='*' - print glob.glob(args) - - def help_ls(self): - self.help_dir(self) - def do_ls(self,args): - self.do_dir(args) - - def help_pwd(self): - print ''' -PWD -Gives the current working directory. ------------- -Syntax: pwd - ''' - def do_pwd(self,args): - print os.getcwd() - - def help_cd(self): - print ''' -CD -Changes the current working directory ------ -Syntax: cd - ''' - def do_cd(self,args): - mypath=os.path.abspath(args) - try: - os.chdir(mypath) - except OSError: - print 'I cannot access that directory.' - - - def help_system(self): - print ''' -SYSTEM -Executes a system command line and reports the output ------ -Syntax system [command line] - ''' - pass - def do_system(self,args): - waste=os.system(args) - - def do_debug(self,args): - ''' - this is a dummy command where I put debugging things - ''' - print self.config['plotmanips'] - pass - - def help_current(self): - print ''' -CURRENT -Prints the current curve path. ------- -Syntax: current - ''' - def do_current(self,args): - print self.current.path - - def do_info(self,args): - ''' - INFO - ---- - Returns informations about the current curve. - ''' - print 'Path: ',self.current.path - print 'Experiment: ',self.current.curve.experiment - print 'Filetype: ',self.current.curve.filetype - for plot in self.current.curve.default_plots(): - for set in plot.vectors: - lengths=[len(item) for item in set] - print 'Data set size: ',lengths - - def do_version(self,args): - ''' - VERSION - ------ - Prints the current version and codename, plus library version. Useful for debugging. - ''' - print 'Hooke '+__version__+' ('+__codename__+')' - print 'Released on: '+__releasedate__ - print '---' - print 'Python version: '+python_version - print 'WxPython version: '+wx_version - print 'wxMPL version: '+wxmpl_version - print 'Matplotlib version: '+mpl_version - print 'SciPy version: '+scipy_version - print 'NumPy version: '+numpy_version - print '---' - print 'Platform: '+str(platform.uname()) - print '---' - print 'Loaded plugins:',self.config['loaded_plugins'] - - def help_exit(self): - print ''' -EXIT, QUIT -Exits the program cleanly. ------- -Syntax: exit -Syntax: quit -''' - def do_exit(self,args): - we_exit='N' - - if (not self.playlist_saved) or (not self.notes_saved): - we_exit=linp.safeinput('You did not save your playlist and/or notes. Exit?',['n']) - else: - we_exit=linp.safeinput('Exit?',['y']) - - if we_exit[0].upper()=='Y': - wx.CallAfter(self.frame.Close) - sys.exit(0) - else: - return - - def help_quit(self): - self.help_exit() - def do_quit(self,args): - self.do_exit(args) - - - - - -if __name__ == '__main__': - mycli=HookeCli(0) - mycli.cmdloop() diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..803f29c --- /dev/null +++ b/lib/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python diff --git a/lib/curve.py b/lib/curve.py new file mode 100644 index 0000000..176d7bf --- /dev/null +++ b/lib/curve.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +''' +curve.py + +Curve and related classes for Hooke. + +Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' + +class Curve(object): + + def __init__(self): + self.color = 'blue' + self.destination = Destination() + self.label = '' + self.size = 0.5 + self.style = 'plot' + self.title = '' + self.units = Units() + self.visible = True + self.x = [] + self.y = [] + + +class Destination(object): + + def __init__(self): + self.column = 1 + self.row = 1 + + +class Units(object): + + def __init__(self): + self.x = '' + self.y = '' diff --git a/lib/driver.py b/lib/driver.py new file mode 100644 index 0000000..b5b4809 --- /dev/null +++ b/lib/driver.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +''' +driver.py + +Base class for file format drivers. + +Copyright 2006 by Massimo Sandal (University of Bologna, Italy). + +This program is released under the GNU General Public License version 2. +''' + +import lib.plot + +class Driver(object): + ''' + Base class for file format drivers. + + To be overridden + ''' + def __init__(self): + self.experiment = '' + self.filetype = '' + + 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 + + def close_all(self): + ''' + This method must close all the open files of the driver, explicitly. + ''' + return None + + def default_plots(self): + plot = lib.plot.Plot() + plot.curves.append([0]) + plot.curves.append([0]) + + return [plot] + + + diff --git a/lib/file.py b/lib/file.py new file mode 100644 index 0000000..c7ff7d3 --- /dev/null +++ b/lib/file.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +''' +file.py + +File class for Hooke. + +Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' + +import os.path +import lib.plot + +class File(object): + + def __init__(self, filename=None, drivers=None): + self.driver = None + self.notes = '' + self.plot = lib.plot.Plot() + if filename is None: + self.filename = None + self.name = None + self.path = None + else: + self.filename = filename + self.path, self.name = os.path.split(filename) + + def identify(self, drivers): + ''' + identifies a curve and returns the corresponding object + ''' + for driver in drivers: + current_driver = driver(self.filename) + if current_driver.is_me(): + #bring on all the driver, with its load of methods etc. + #so we can access the whole of it. + self.plot = current_driver.default_plots() + self.driver = current_driver diff --git a/lib/libhooke.py b/lib/libhooke.py new file mode 100644 index 0000000..f69f2d2 --- /dev/null +++ b/lib/libhooke.py @@ -0,0 +1,108 @@ +#!/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 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 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 coth(z): + ''' + hyperbolic cotangent + ''' + return (numpy.exp(2 * z) + 1) / (numpy.exp(2 * z) - 1) + +class ClickedPoint(object): + ''' + this class defines what a clicked point on the curve plot is + ''' + def __init__(self): + + self.is_marker = None #boolean ; decides if it is a marker + self.is_line_edge = None #boolean ; decides if it is the edge of a line (unused) + self.absolute_coords = (None, None) #(float,float) ; the absolute coordinates of the clicked point on the graph + self.graph_coords = (None, None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point + self.index = None #integer ; the index of the clicked point with respect to the vector selected + self.dest = None #0 or 1 ; 0=top plot 1=bottom plot + + def find_graph_coords(self, xvector, yvector): + ''' + Given a clicked point on the plot, finds the nearest point in the dataset (in X) that + corresponds to the clicked point. + ''' + dists = [] + for index in scipy.arange(1, len(xvector), 1): + dists.append(((self.absolute_coords[0] - xvector[index]) ** 2)+((self.absolute_coords[1] - yvector[index]) ** 2)) + + self.index=dists.index(min(dists)) + self.graph_coords=(xvector[self.index], yvector[self.index]) +#----------------------------------------- +#CSV-HELPING FUNCTIONS + +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) + +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. diff --git a/libpeakspot.py b/lib/peakspot.py similarity index 87% rename from libpeakspot.py rename to lib/peakspot.py index 398fe84..1a715ac 100644 --- a/libpeakspot.py +++ b/lib/peakspot.py @@ -1,11 +1,16 @@ #!/usr/bin/env python ''' -a library of helping functions for spotting force spectroscopy peaks +peakspot.py -(c)Fabrizio Benedetti and Massimo Sandal , 2007 +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. ''' -import numpy as np + +from numpy import mean def conv_dx(data,vect): ''' @@ -14,43 +19,44 @@ def conv_dx(data,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] + for k in range(window): + temparr[j]+=data[j+k]*vect[k] + + return temparr - return temparr - def absdev(arr): ''' Calculates the absolute deviation of a vector ''' med=0.0 absD=0.0 - - med=np.mean(arr) + + 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... @@ -58,18 +64,18 @@ def noise_absdev(data,positive=False,maxcut=0.2,stable=0.005): 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 self.count - 1: + self.index = 0 + + def filter_curves(self, curves_to_keep=[]): + playlist = Playlist() + for curve_index in curves_to_keep: + playlist.files.append(self.files[curve_index]) + playlist.count = len(playlist.files) + playlist.index = 0 + return playlist + + def get_active_file(self): + return self.files[self.index] + + #def get_active_plot(self): + ##TODO: is this the only active (or default?) plot? + #return self.files[self.index].plots[0] + + def get_status_string(self): + if self.count > 0: + activeFileStr = ''.join([' (%s/%s)' %(self.index + 1, self.count)]) + return ''.join([self.name, activeFileStr]) + else: + return ''.join(['The file ', self.name, ' does not contain any valid force curve data.\n']) + + def load(self, filename): + ''' + loads a playlist file + ''' + self.filename = filename + self.path, self.name = os.path.split(filename) + playlist_file = lib.libhooke.delete_empty_lines_from_xmlfile(filename) + self.xml = xml.dom.minidom.parseString(playlist_file) + #TODO: rename 'element' to 'curve' or something in hkp file + #TODO: rename 'path' to 'filename' + #TODO: switch from attributes to nodes, it's cleaner XML in my eyes + + element_list = self.xml.getElementsByTagName('element') + #populate playlist with files + for index, element in enumerate(element_list): + #rebuild a data structure from the xml attributes + #the next two lines are here for backwards compatibility, newer playlist files use 'filename' instead of 'path' + if element.hasAttribute('path'): + filename = lib.libhooke.get_file_path(element.getAttribute('path')) + if element.hasAttribute('filename'): + filename = lib.libhooke.get_file_path(element.getAttribute('filename')) + if os.path.isfile(filename): + data_file = lib.file.File(filename) + self.files.append(data_file) + self.count = len(self.files) + if self.count > 0: + #populate generics + genericsDict = {} + generics_list = self.xml.getElementsByTagName('generics') + if generics_list: + for attribute in generics_list[0].attributes.keys(): + genericsDict[attribute] = generics_list[0].getAttribute(attribute) + if genericsDict.has_key('pointer'): + index = int(genericsDict['pointer']) + if index >= 0 and index < self.count: + self.index = index + else: + index = 0 + self._saved = True + + def next(self): + self.index += 1 + if self.index > self.count - 1: + self.index = 0 + + def previous(self): + self.index -= 1 + if self.index < 0: + self.index = self.count - 1 + + def reset(self): + if self.count > 0: + self.index = 0 + else: + self.index = -1 + + def save(self, filename): + ''' + Saves a playlist from a list of files. + A playlist is an XML document with the following syntax: + + + + + ''' + try: + output_file = file(filename, 'w') + except IOError: + self.AppendToOutput('Cannot save playlist. Wrong path or filename') + return + #create the output playlist, a simple XML document + implementation = xml.dom.minidom.getDOMImplementation() + #create the document DOM object and the root element + self.xml = implementation.createDocument(None, 'playlist', None) + root = self.xml.documentElement + + #save generics variables + playlist_generics = self.xml.createElement('generics') + root.appendChild(playlist_generics) + self.generics_dict['pointer'] = self.index + for key in self.generics_dict.keys(): + #for key in generics.keys(): + self.xml.createAttribute(key) + playlist_generics.setAttribute(key, str(self.generics_dict[key])) + + #save files and their attributes + for item in self.files: + #playlist_element=newdoc.createElement("file") + playlist_element = self.xml.createElement('element') + root.appendChild(playlist_element) + for key in item.__dict__: + if not (key in self.hidden_attributes): + self.xml.createAttribute(key) + playlist_element.setAttribute(key, str(item.__dict__[key])) + self._saved = False + + self.xml.writexml(output_file, indent='\n') + output_file.close() + self._saved = True diff --git a/lib/plot.py b/lib/plot.py new file mode 100644 index 0000000..cfad721 --- /dev/null +++ b/lib/plot.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +''' +plot.py + +Plot class for Hooke. + +Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' + +class Invert(object): + + def __init__(self): + self.x = False + self.y = False + + +class Plot(object): + + def __init__(self): + self.corrected_curves = [] + self.curves = [] + self.invert = Invert() + self.raw_curves = [] + self.results = {} + self.title = '' + + def normalize(self): + ''' + Trims the vector lengths as to be equal in a plot. + ''' + lengths = [] + for curve in self.curves: + lengths.append(len(curve.x)) + lengths.append(len(curve.y)) + if min(lengths) != max(lengths): + curve.x = curve.x[0:min(lengths)] + curve.y = curve.y[0:min(lengths)] diff --git a/lib/plotmanipulator.py b/lib/plotmanipulator.py new file mode 100644 index 0000000..2d820d3 --- /dev/null +++ b/lib/plotmanipulator.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +''' +plotmanipulator.py + +Plotmanipulator class for Hooke. + +Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' + +from string import replace + +class Plotmanipulator(object): + def __init__(self, command=None, method=None): + #the command (e.g. plotmanip_correct) + self.command = command + #the method associated with the command + self.method = method + #the suffix of the command (e.g. correct) to retrieve + #status (active or not from config) + self.name = command.replace('plotmanip_', '') + + diff --git a/lib/plugin.py b/lib/plugin.py new file mode 100644 index 0000000..a4641a6 --- /dev/null +++ b/lib/plugin.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +''' +plugin.py + +ConfigObj plugin class for Hooke. + +Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' + +class Plugin(object): + + def __init__(self): + self.key = '' + self.name = '' + self.prefix = '' diff --git a/lib/prettyformat.py b/lib/prettyformat.py new file mode 100644 index 0000000..b346115 --- /dev/null +++ b/lib/prettyformat.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python + +''' +prettyformat.py + +Simple Python function to format values with nice prefixes +Version 1.0.1 + +History +2009 07 16: added negative number support + added decimal-formatted output + +Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' + +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' +''' \ No newline at end of file diff --git a/lib/results.py b/lib/results.py new file mode 100644 index 0000000..7535ac9 --- /dev/null +++ b/lib/results.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python + +''' +results.py + +Result and Results classes for Hooke. + +Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' + +import prettyformat +import lib.curve + +DEFAULT_COLOR = 'green' +DEFAULT_DECIMAL = 2 +DEFAULT_STYLE = 'scatter' + +class Result(lib.curve.Curve): + def __init__(self): + lib.curve.Curve.__init__(self) + self.color = DEFAULT_COLOR + self.result = {} + self.style = DEFAULT_STYLE + +class Results(object): + 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 += 'Slope ['+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 len(self.results) > 0 + + def get_header_as_list(self): + header_list = [] + if self.has_results(): + if not self.has_multipliers: + self.set_multipliers() + for column in self.columns: + unit_str = ''.join([prettyformat.get_prefix(self.multipliers[column]), self.units[column]]) + header_str = ''.join([column, ' [', unit_str, ']']) + header_list.append(header_str) + return header_list + + def get_header_as_str(self, separator=None): + if separator is None: + separator = self.separator + return separator.join(map(str, self.get_header_as_list())) + + def get_result_as_list(self, index=0): + if index >= 0 and index < len(self.results): + result_list = [] + if self.has_results(): + if not self.has_multipliers: + self.set_multipliers() + for column in self.columns: + result_str = prettyformat.pretty_format(self.results[index].result[column], '', self.decimals[column], self.multipliers[column], True) + result_list.append(result_str) + return result_list + else: + return None + + def get_result_as_string(self, index=0): + results_list = self.get_result_as_list(index) + if results_list is not None: + return self.separator.join(map(str, results_list)) + else: + return '' + + def set_decimal(self, column, decimal=DEFAULT_DECIMAL): + if self.decimals.has_key(column): + self.decimals[column] = 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 ResultsFJC(Results): + def __init__(self): + Results.__init__(self) + self.columns = ['Contour length', 'sigma contour length', 'Kuhn length', 'sigma Kuhn length', 'Rupture force', 'Slope', 'Loading rate'] + self.units['Contour length'] = 'm' + self.units['sigma contour length'] = 'm' + self.units['Kuhn length'] = 'm' + self.units['sigma Kuhn length'] = 'm' + self.units['Rupture force'] = 'N' + self.units['Slope'] = 'N/m' + self.units['Loading rate'] = 'N/s' + 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 Kuhn length': + self.multipliers[column] = self.multipliers['Kuhn length'] + else: + self.multipliers[column] = prettyformat.get_multiplier(result[column]) + 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', 'Slope', '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['Slope'] = 'N/m' + self.units['Loading rate'] = 'N/s' + 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 diff --git a/libhooke.py b/libhooke.py deleted file mode 100755 index ed5c088..0000000 --- a/libhooke.py +++ /dev/null @@ -1,346 +0,0 @@ -#!/usr/bin/env python - -''' -libhooke.py - -General library of internal objects and utilities for Hooke. - -Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy). -With algorithms contributed by Francesco Musiani (University of Bologna, Italy) - -This program is released under the GNU General Public License version 2. -''' - - - -import libhookecurve as lhc - -import scipy -import scipy.signal -import scipy.optimize -import scipy.stats -import numpy -import xml.dom.minidom -import os -import string -import csv - -HOOKE_VERSION=['0.8.3_devel', 'Seinei', '2008-04-16'] -WX_GOOD=['2.6','2.8'] - -class PlaylistXML: - ''' - This module allows for import/export of an XML playlist into/out of a list of HookeCurve objects - ''' - - def __init__(self): - - self.playlist=None #the DOM object representing the playlist data structure - self.playpath=None #the path of the playlist XML file - self.plaything=None - self.hidden_attributes=['curve'] #This list contains hidden attributes that we don't want to go into the playlist. - - def export(self, list_of_hooke_curves, generics): - ''' - Creates an initial playlist from a list of files. - A playlist is an XML document with the following syntaxis: - - - - - ''' - - #create the output playlist, a simple XML document - impl=xml.dom.minidom.getDOMImplementation() - #create the document DOM object and the root element - newdoc=impl.createDocument(None, "playlist",None) - top_element=newdoc.documentElement - - #save generics variables - playlist_generics=newdoc.createElement("generics") - top_element.appendChild(playlist_generics) - for key in generics.keys(): - newdoc.createAttribute(key) - playlist_generics.setAttribute(key,str(generics[key])) - - #save curves and their attributes - for item in list_of_hooke_curves: - #playlist_element=newdoc.createElement("curve") - playlist_element=newdoc.createElement("element") - top_element.appendChild(playlist_element) - for key in item.__dict__: - if not (key in self.hidden_attributes): - newdoc.createAttribute(key) - playlist_element.setAttribute(key,str(item.__dict__[key])) - - self.playlist=newdoc - - def load(self,filename): - ''' - loads a playlist file - ''' - myplay=file(filename) - self.playpath=filename - - #the following 3 lines are needed to strip newlines. otherwise, since newlines - #are XML elements too (why?), the parser would read them (and re-save them, multiplying - #newlines...) - #yes, I'm an XML n00b - the_file=myplay.read() - the_file_lines=the_file.split('\n') - the_file=''.join(the_file_lines) - - self.playlist=xml.dom.minidom.parseString(the_file) - - #inner parsing functions - def handlePlaylist(playlist): - list_of_files=playlist.getElementsByTagName("element") - generics=playlist.getElementsByTagName("generics") - return handleFiles(list_of_files), handleGenerics(generics) - - def handleGenerics(generics): - generics_dict={} - if len(generics)==0: - return generics_dict - - for attribute in generics[0].attributes.keys(): - generics_dict[attribute]=generics[0].getAttribute(attribute) - return generics_dict - - def handleFiles(list_of_files): - new_playlist=[] - for myfile in list_of_files: - #rebuild a data structure from the xml attributes - the_curve=lhc.HookeCurve(myfile.getAttribute('path')) - for attribute in myfile.attributes.keys(): #extract attributes for the single curve - the_curve.__dict__[attribute]=myfile.getAttribute(attribute) - new_playlist.append(the_curve) - - return new_playlist #this is the true thing returned at the end of this function...(FIXME: clarity) - - return handlePlaylist(self.playlist) - - - def save(self,output_filename): - ''' - saves the playlist in a XML file. - ''' - try: - outfile=file(output_filename,'w') - except IOError: - print 'libhooke.py : Cannot save playlist. Wrong path or filename' - return - - self.playlist.writexml(outfile,indent='\n') - outfile.close() - - -class HookeConfig: - ''' - Handling of Hooke configuration file - - Mostly based on the simple-yet-useful examples of the Python Library Reference - about xml.dom.minidom - - FIXME: starting to look a mess, should require refactoring - ''' - - def __init__(self): - self.config={} - self.config['plugins']=[] - self.config['drivers']=[] - self.config['plotmanips']=[] - - def load_config(self, filename): - myconfig=file(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...) - #yes, I'm an XML n00b - the_file=myconfig.read() - the_file_lines=the_file.split('\n') - the_file=''.join(the_file_lines) - - self.config_tree=xml.dom.minidom.parseString(the_file) - - def getText(nodelist): - #take the text from a nodelist - #from Python Library Reference 13.7.2 - rc = '' - for node in nodelist: - if node.nodeType == node.TEXT_NODE: - rc += node.data - return rc - - def handleConfig(config): - display_elements=config.getElementsByTagName("display") - plugins_elements=config.getElementsByTagName("plugins") - drivers_elements=config.getElementsByTagName("drivers") - workdir_elements=config.getElementsByTagName("workdir") - defaultlist_elements=config.getElementsByTagName("defaultlist") - plotmanip_elements=config.getElementsByTagName("plotmanips") - handleDisplay(display_elements) - handlePlugins(plugins_elements) - handleDrivers(drivers_elements) - handleWorkdir(workdir_elements) - handleDefaultlist(defaultlist_elements) - handlePlotmanip(plotmanip_elements) - - def handleDisplay(display_elements): - for element in display_elements: - for attribute in element.attributes.keys(): - self.config[attribute]=element.getAttribute(attribute) - - def handlePlugins(plugins): - for plugin in plugins[0].childNodes: - try: - self.config['plugins'].append(str(plugin.tagName)) - except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it... - pass - #FIXME: code duplication - def handleDrivers(drivers): - for driver in drivers[0].childNodes: - try: - self.config['drivers'].append(str(driver.tagName)) - except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it... - pass - - def handlePlotmanip(plotmanips): - for plotmanip in plotmanips[0].childNodes: - try: - self.config['plotmanips'].append(str(plotmanip.tagName)) - except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it... - pass - - def handleWorkdir(workdir): - ''' - default working directory - ''' - wdir=getText(workdir[0].childNodes) - self.config['workdir']=wdir.strip() - - def handleDefaultlist(defaultlist): - ''' - default playlist - ''' - dflist=getText(defaultlist[0].childNodes) - self.config['defaultlist']=dflist.strip() - - handleConfig(self.config_tree) - #making items in the dictionary more machine-readable - for item in self.config.keys(): - try: - self.config[item]=float(self.config[item]) - except TypeError: #we are dealing with a list, probably. keep it this way. - try: - self.config[item]=eval(self.config[item]) - except: #not a list, not a tuple, probably a string? - pass - except ValueError: #if we can't get it to a number, it must be None or a string - if string.lower(self.config[item])=='none': - self.config[item]=None - else: - pass - - return self.config - - - def save_config(self, config_filename): - print 'Not Implemented.' - pass - - -class ClickedPoint: - ''' - this class defines what a clicked point on the curve plot is - ''' - def __init__(self): - - self.is_marker=None #boolean ; decides if it is a marker - self.is_line_edge=None #boolean ; decides if it is the edge of a line (unused) - self.absolute_coords=(None,None) #(float,float) ; the absolute coordinates of the clicked point on the graph - self.graph_coords=(None,None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point - self.index=None #integer ; the index of the clicked point with respect to the vector selected - self.dest=None #0 or 1 ; 0=top plot 1=bottom plot - - - def find_graph_coords_old(self, xvector, yvector): - ''' - Given a clicked point on the plot, finds the nearest point in the dataset (in X) that - corresponds to the clicked point. - OLD & DEPRECATED - to be removed - ''' - - #FIXME: a general algorithm using min() is needed! - best_index=0 - best_dist=10**9 #should be more than enough given the scale - - for index in scipy.arange(1,len(xvector),1): - dist=((self.absolute_coords[0]-xvector[index])**2)+(100*((self.absolute_coords[1]-yvector[index])))**2 - #TODO, generalize? y coordinate is multiplied by 100 due to scale differences in the plot - if dist0: - #if valid values are string we use alphainput, if it is only one we take as default - if type(valid[0]) is StringType: - if len(valid)==1: - return alphainput(message, valid[0], 0,[]) - else: - return alphainput(message,'', 1,valid) - - #if valid values are numbers we use numinput - if type(valid[0]) is IntType: - if len(valid)==1: - return numinput(message,valid[0],1,[]) - else: - return numinput(message,'',1,valid) - - - -def alphainput (message, default, repeat, valid): - ''' - message: prompt for the user - default: return value if user input was not correct (and repeat=0) - repeat: keeps asking user till it gets a valid input - valid: list of allowed answers, empty list for "anything" - ''' - if default and not repeat: - print 'Press [enter] for default: ('+str(default)+')' - reply=raw_input(message) - if len(valid)>0: - if reply in valid: - return reply - else: - if repeat==1: - while reply not in valid: - reply=raw_input('You should enter any of these: '+ str(valid) +'\n'+ message) - return reply - else: - return default - else: - if len(reply)>0: - return reply - else: - if not repeat: - return default - else: - while len(reply)==0: - print 'Try again' - reply=raw_input(message) - return reply - - - -def checkalphainput (test, default, valid): - #useful when input was taken form command args - if len(valid)>0: - if test in valid: - return test - else: - return default - else: - #TODO: raise exception? - if len(test)>0: - return test - else: - return default - - -def numinput(message, default, repeat, limits): - ''' - message: prompt for the user - default: return value if user input was not correct (and repeat=0) - repeat: keeps asking user till it gets a valid input - limits: pair of values, input is checked to be between them, empty list for "any number" - ''' - if default and not repeat: - print 'Press [enter] for default: '+str(default) - - reply=raw_input(message) - - try: - intreply=int(reply) - except: - intreply=None - - if len(limits)==2: - high=int(limits.pop()) - low=int(limits.pop()) - if intreply>=low and intreply <= high: - return intreply - else: - if repeat==1: - while intreplyhigh : - reply=raw_input('You should enter values between: '+ str(low)+' and '+str(high) +'\n'+ message) - try: - intreply=int(reply) - except: - intreply=None - return intreply - else: - return default - else: - if intreply!=None: - return intreply - else: - if not repeat: - return default - else: - while intreply==None: - print 'Try again' - reply=raw_input(message) - try: - intreply=int(reply) - except: - intreply=None - return intreply - -def checknuminput(test,default,limits): - #useful when input was taken from command args - if len(limits)==2: - high=int(limits.pop()) - low=int(limits.pop()) - if test>=low and test <= high: - return int(test) - else: - return default - else: - if len(test)>0: - return int(test) - else: - return default - diff --git a/liboutlet.py b/liboutlet.py deleted file mode 100644 index 06e0940..0000000 --- a/liboutlet.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python - -''' -Basic outlet object - -Copyright (C) 2008 Alberto Gomez-Casado (University of Twente). - -This program is released under the GNU General Public License version 2. -''' - - -import re - - -class Outlet(object): - - def __init__(self): - self.buffer=[] - #relations is still unused - self.relations=[] - - def push(self, args): - #queue new entry - self.buffer.append(args) - - def pop(self): - #delete last entry - return self.buffer.pop(); - - def printbuf(self): - j=1; - for i in self.buffer: - print j, i - j=j+1 - - def delete(self, number): - #delete entry matching given index - if len(self.buffer)>int(number)-1 and int(number)>0: - self.buffer.pop(int(number)-1) - - def empty(self): - self.buffer=[] - - def read_last(self): - return self.buffer[len(self.buffer)-1] - - def read_first(self): - return self.buffer[0] - - def read_type(self,dtype): - #returns entries matching a given type (force, distance, point...) - aux=[] - index=0 - if dtype=='all': - return self.buffer - for i in self.buffer: - if re.match(dtype+'*',i): - aux.append(i) - return aux - - diff --git a/libviewer.py b/libviewer.py deleted file mode 100644 index 99ff002..0000000 --- a/libviewer.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python - -''' -Basic Viewer and ascii saver example - -Copyright (C) 2008 Alberto Gomez-Casado (University of Twente). - -This program is released under the GNU General Public License version 2. -''' - - -import liboutlet as lout -import libinput as linput - -class Viewer(object): - source=[] - data=[] - dtype='all' - action=[] #alias to call the actual viewer function, makes it general - - - def setdtype(self, dt): - #determines wich type of data will be retrieved from outlet - self.dtype=dt - - def show(self): - #TODO should print only data matching 'type' - self.source.printbuf() - - def getdata(self): - #retrieves data from outlet - self.data=self.source.read_type(self.dtype) - - - -class Ascii(Viewer): -#example viewer, it just retrieves data and writes it to a text file -#probably this should be in viewer.py? - - def __init__(self,outref): - self.source=outref - #tells the viewer which outlet has the data (so far only one in hooke) - self.action=self.dump - #this allows to call dump (or any other function, depending on the viewer) from the CLI using 'vwaction' - - def dump(self): - #retrieves and saves data - self.getdata() - destination=linput.safeinput('Enter filename:',['results.txt']) - destfile=open(destination,'w+') - destfile.write('\n'.join(self.data)) - destfile.close() - diff --git a/macro.py b/macro.py deleted file mode 100644 index bdeafab..0000000 --- a/macro.py +++ /dev/null @@ -1,233 +0,0 @@ -#!/usr/bin/env python - -''' -COMMAND MACRO PLUGIN FOR HOOKE - -Records, saves and executes batches of commands -(c)Alberto Gomez-Casado 2008 -''' - -import libhookecurve as lhc -import libinput as linput -import os.path -import string - -class macroCommands: - - currentmacro=[] - pause=0 - auxprompt=[] - macrodir=[] - - - def _plug_init(self): - self.currentmacro=[] - self.auxprompt=self.prompt - self.macrodir=self.config['workdir'] - if not os.path.exists(os.path.join(self.macrodir,'macros')): - try: - os.mkdir('macros') - except: - print 'Warning: cannot create macros folder.' - print 'Probably you do not have permissions in your Hooke folder, use macro at your own risk.' - self.macrodir=os.path.join(self.macrodir,'macros') - - def collect(self): - - print 'Enter STOP / PAUSE to go back to normal mode\nUNDO to remove last command' - line=[] - while not(line=='STOP' or line=='PAUSE'): - line=raw_input('hooke (macroREC): ') - if line=='PAUSE': - self.pause=1 - self.prompt='hooke (macroPAUSE): ' - break - if line=='STOP': - self.prompt=self.auxprompt - self.do_recordmacro('stop') - break - if line=='UNDO': - self.currentmacro.pop() - continue - param=line.split() - - #FIXME check if accessing param[2] when it doesnt exist breaks something - if param[0] =='export': - exportline=param[0]+' __curve__ ' - if len(param)==3: - exportline=exportline+param[2] - self.currentmacro.append(exportline) - self.onecmd(line) - continue - - if param[0] =='txt': - exportline=param[0] - if len(param)==3: - exportline=exportline+' '+param[2] - exportline=exportline+'__curve__' - self.currentmacro.append(exportline) - self.onecmd(line) - continue - - self.onecmd(line) - - self.currentmacro.append(line) - - - def do_recordmacro(self, args): - '''RECORDMACRO - Stores input commands to create script files - ------- - Syntax: recordmacro [start / stop] - If a macro is currently paused start resumes recording - ''' - - - if len(args)==0: - args='start' - - if args=='stop': - self.pause=0 - self.prompt=self.auxprompt - if len(self.currentmacro) != 0: - answer=linput.safeinput('Do you want to save this macro? ',['y']) - if answer[0].lower() == 'y': - self.do_savemacro('') - else: - print 'Macro discarded' - self.currentmacro=[] - else: - print 'Macro was empty' - - if args=='start': - - if self.pause==1: - self.pause=0 - self.collect() - else: - if len(self.currentmacro) != 0: - answer=linput.safeinput('Another macro is already beign recorded\nDo you want to save it?',['y']) - if answer[0].lower() == 'y': - self.do_savemacro('') - else: - print 'Old macro discarded, you can start recording the new one' - - self.currentmacro=[] - self.collect() - - - def do_savemacro(self, macroname): - - '''SAVEMACRO - Saves previously recorded macro into a script file for future use - ------- - Syntax: savemacro [macroname] - If no macroname is supplied one will be interactively asked - ''' - - saved_ok=0 - if self.currentmacro==None: - print 'No macro is being recorded!' - return 0 - if len(macroname)==0: - macroname=linput.safeinput('Enter new macro name: ') - if len(macroname) == 0: - print 'Invalid name' - - macroname=os.path.join(self.macrodir,macroname+'.hkm') - if os.path.exists(macroname): - overwrite=linput.safeinput('That name is in use, overwrite?',['n']) - if overwrite[0].lower()!='y': - print 'Cancelled save' - return 0 - txtfile=open(macroname,'w+') - self.currentmacro='\n'.join(self.currentmacro) - txtfile.write(self.currentmacro) - txtfile.close() - print 'Saved on '+macroname - self.currentmacro=[] - - def do_execmacro (self, args): - - '''EXECMACRO - Loads a macro and executes it over current curve / playlist - ----- - Syntax: execmacro macroname [playlist] [v] - - macroname.hkm should be present at [hooke]/macros directory - By default the macro will be executed over current curve - passing 'playlist' word as second argument executes macroname - over all curves - By default curve(s) will be processed silently, passing 'v' - as second/third argument will print each command that is - executed - - Note that macros applied to playlists should end by export - commands so the processed curves are not lost - ''' - verbose=0 - cycle=0 - curve=None - - if len(self.currentmacro) != 0: - print 'Warning!: you are calling a macro while recording other' - if len(args) == 0: - print 'You must provide a macro name' - return 0 - args=args.split() - - #print 'args ' + ' '.join(args) - - if len(args)>1: - if args[1] == 'playlist': - cycle=1 - print 'Remember! macros applied over playlists should include export orders' - if len(args)>2 and args[2] == 'v': - verbose=1 - else: - if args[1] == 'v': - verbose=1 - #print cycle - #print verbose - - macropath=os.path.join(self.macrodir,args[0]+'.hkm') - if not os.path.exists(macropath): - print 'Could not find a macro named '+macropath - return 0 - txtfile=open(macropath) - if cycle ==1: - #print self.current_list - for item in self.current_list: - self.current=item - self.do_plot(0) - - for command in txtfile: - - if verbose==1: - print 'Executing command '+command - testcmd=command.split() - w=0 - for word in testcmd: - if word=='__curve__': - testcmd[w]=os.path.splitext(os.path.basename(item.path))[0] - w=w+1 - self.onecmd(' '.join(testcmd)) - self.current.curve.close_all() - txtfile.seek(0) - else: - for command in txtfile: - testcmd=command.split() - w=0 - for word in testcmd: - if word=='__curve__': - w=w+1 - testcmd[w]=os.path.splitext(os.path.basename(self.current.path))[0]+'-'+string.lstrip(os.path.splitext(os.path.basename(self.current.path))[1],'.') - if verbose==1: - print 'Executing command '+' '.join(testcmd) - self.onecmd(' '.join(testcmd)) - - - - - - diff --git a/massanalysis.py b/massanalysis.py deleted file mode 100644 index e3d3830..0000000 --- a/massanalysis.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env python - -''' -massanalysis.py - -Global analysis of force curves with various parameters - -Requires: -libpeakspot.py -flatfilts.py -''' - - -import libpeakspot as lps -import libhookecurve as lhc -import libhooke as lh -import numpy as np - -import csv - -class massanalysisCommands: - - def _plug_init(self): - self.mass_variables={} - self.interesting_variables=['curve','firstpeak_distance','lastpeak_distance','Npeaks','median_distance','mean_distance'] - self._clean_data() - - def _clean_data(self): - for variable in self.interesting_variables: - self.mass_variables[variable]=[] - - def peak_position_from_contact(self, item, locations): - ''' - calculates X distance of a peak from the contact point - ''' - item.identify(self.drivers) - - real_positions=[] - cut_index=self.find_contact_point() - - #we assume the first is the plot with the force curve - plot=item.curve.default_plots()[0] - xret=plot.vectors[1][0] - - start_x=xret[cut_index] - - real_positions=[abs((xret[index])-(start_x)) for index in locations] - #close all open files - item.curve.close_all() - #needed to avoid *big* memory leaks! - del item.curve - del item - return real_positions - - def do_maplist(self,args): - ''' - MAPLIST - (flatfilts.py) - ---- - pass - ''' - self._clean_data() #if we recall it, clean previous data! - min_deviation=self.convfilt_config['mindeviation'] - - - c=0 - for item in self.current_list: - try: - peak_location,peak_size=self.exec_has_peaks(item, min_deviation) - real_positions=self.peak_position_from_contact(item, peak_location) - - self.mass_variables['Npeaks'].append(len(peak_location)) - - if len(peak_location) > 1: - self.mass_variables['firstpeak_distance'].append(min(real_positions)) - self.mass_variables['lastpeak_distance'].append(max(real_positions)) - - distancepeaks=[] - for index in range(len(real_positions)-1): - distancepeaks.append(real_positions[index+1]-real_positions[index]) - else: - self.mass_variables['firstpeak_distance'].append(0) - self.mass_variables['lastpeak_distance'].append(0) - - if len(peak_location) > 2: - self.mass_variables['median_distance'].append(np.median(distancepeaks)) - self.mass_variables['mean_distance'].append(np.mean(distancepeaks)) - else: - self.mass_variables['median_distance'].append(0) - self.mass_variables['mean_distance'].append(0) - - print 'curve',c - except SyntaxError: - print 'curve',c,'not mapped' - pass - - c+=1 - - def do_plotmap(self,args): - ''' - ''' - args=args.split() - if len(args)>1: - x=self.mass_variables[args[0]] - y=self.mass_variables[args[1]] - else: - print 'Give me two arguments between those:' - print self.interesting_variables - return - - scattermap=lhc.PlotObject() - scattermap.vectors=[[]] - scattermap.vectors[0].append(x) - scattermap.vectors[0].append(y) - - scattermap.units=[args[0],args[1]] - scattermap.styles=['scatter'] - scattermap.destination=1 - - self._send_plot([scattermap]) - - def do_savemaps(self,args): - ''' - args=filename - ''' - - ''' - def csv_write_cols(data, f): - - #from Bruno Desthuillers on comp.lang.python - - writer = csv.writer(f) - keys = data.keys() - writer.writerow(dict(zip(keys,keys))) - for row in zip(*data.values()): - writer.writerow(dict(zip(keys, row))) - ''' - - f=open(args,'wb') - lh.csv_write_dictionary(f,self.mass_variables) - f.close() - - \ No newline at end of file diff --git a/mfp1dexport.py b/mfp1dexport.py deleted file mode 100644 index 57e75d9..0000000 --- a/mfp1dexport.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python - -''' -mfp1dexport.py - -Driver for text-exported MFP 1D files - -Massimo Sandal (c) 2009 -''' - -import libhookecurve as lhc -import libhooke as lh - -class mfp1dexportDriver(lhc.Driver): - - def __init__(self, filename): - - self.filename=filename - self.filedata=open(filename,'rU') - self.lines=list(self.filedata.readlines()) - self.filedata.close() - - self.filetype='mfp1dexport' - self.experiment='smfs' - - def close_all(self): - self.filedata.close() - - def is_me(self): - try: - self.raw_header=self.lines[0:38] - except: - #Not enough lines for a header; not a good file - return False - - #FIXME: We want a more reasonable header recognition - if self.raw_header[0][0:4]=='Wave': - return True - else: - return False - - def _read_columns(self): - - self.raw_columns=self.lines[39:] - - kline=None - for line in self.lines: - if line[:7]=='SpringC': - kline=line - break - - kline=kline.split(':') - - #self.k=float(self.raw_header[23][8:]) - self.k=float(kline[1]) - - - xext=[] - xret=[] - yext=[] - yret=[] - for line in self.raw_columns: - spline=line.split() - xext.append(float(spline[0])) - yext.append(float(spline[1])) - xret.append(float(spline[2])) - yret.append(float(spline[3])) - - return [[xext,yext],[xret,yret]] - - def deflection(self): - self.data=self._read_columns() - return self.data[0][1],self.data[1][1] - - - def default_plots(self): - main_plot=lhc.PlotObject() - defl_ext,defl_ret=self.deflection() - yextforce=[i*self.k for i in defl_ext] - yretforce=[i*self.k for i in defl_ret] - main_plot.add_set(self.data[0][0],yextforce) - main_plot.add_set(self.data[1][0],yretforce) - main_plot.normalize_vectors() - main_plot.units=['Z','force'] #FIXME: if there's an header saying something about the time count, should be used - main_plot.destination=0 - main_plot.title=self.filename - #main_plot.colors=['red','blue'] - return [main_plot] diff --git a/mfp_igor_scripts/ExportMFP1D.ipf b/mfp_igor_scripts/ExportMFP1D.ipf deleted file mode 100644 index d1234c3..0000000 --- a/mfp_igor_scripts/ExportMFP1D.ipf +++ /dev/null @@ -1,185 +0,0 @@ -#pragma rtGlobals=1 // Use modern global access method. -#pragma IgorVersion = 4.0 -#pragma version = 0.4 - -// -// ExportMFP1D.ipf - A procedure to export force curves from MFP1D to 'hooke' -// -// Copyright (c) 2009 Rolf Schmidt, Montreal -// rschmidt@alcor.concordia.ca -// -// This procedure is released under the GNU General Public License version 2 -// - -// History -// 2009 07 24: v0.4 -// the wave note is now correctly and fully exported in Igor 4 -// 2009 06 29: v0.3 -// split functionality into ExportMFP1DFolder and ExportMFP1DWaves -// ExportMFP1DFolder: export individual Igor binary waves file from a folder -// ExportMFP1DWaves: export all currently open waves to a folder -// 2009 06 26: v0.2.1 -// added the IgorVersion pragma -// 2009 06 19: v0.2 -// changed the filename finding algorithm to work with Igor 4 and up -// Igor 5 users can use the code marked 'the following only works in Igor 5 and up' instead -// the procedure now catches 'Cancel' on NewPath -// added version information -// 2009 05 29: v0.1 -// changed the procedure so that it runs in Igor as well as in MFP (ie Igor with MFP plug-in) - -// How to use ExportMFP1D() -// - save all current waves of interest (ExportMFP1D() kills all open waves before exporting files) -// - execute ExportMFP1D() from the command line -// - browse to a folder containing force curves (a 'force curve' consists of two files: one 'deflection' and one 'LVDT' file -// - ExportMFP1D() will now iterate through all the waves in the folder, extract the header information and create four columns: -// 1: approach (x) 2: approach (y) 3: retraction (x) 4; retraction (y) -// - the resulting files are saved in the same folder and the same base name as the original files (ie without 'deflection' and 'LVDT') -// CAUTION: existing files will be overwritten! -// - these files can then be analyzed with 'hooke' - -Function ExportMFP1DFolder() - - String sList - Variable iCount - - // set the path (used for opening the waves and saving the output files later) - NewPath /O /Q /M="Choose the folder that contains the waves" PathExport1D - - KillWaves /A /Z - - if(V_flag>=0) - // get a list of all Igor binary waves in the folder - sList=IndexedFile(PathExport1D,-1,".ibw") - // load all waves - for(iCount=0; iCount 0: + + # We must check this since an 'empty' text event + # may be triggered when creating the property. + PG_FL_IN_SELECT_PROPERTY = 0x00100000 + if not (propgrid.GetInternalFlags() & PG_FL_IN_SELECT_PROPERTY): + event.Skip(); + event.SetId(propgrid.GetId()); + + propgrid.EditorsValueWasModified(); + + return False + + + def CopyValueFromControl(self, property, ctrl): + tc = ctrl.tc + res = property.SetValueFromString(tc.GetValue(),0) + # Changing unspecified always causes event (returning + # true here should be enough to trigger it). + if not res and property.IsFlagSet(wxpg.PG_PROP_AUTO_UNSPECIFIED): + res = True + + return res + + def SetValueToUnspecified(self, ctrl): + ctrl.tc.Remove(0,len(ctrl.tc.GetValue())); + + def SetControlStringValue(self, ctrl, txt): + ctrl.SetValue(txt) + + def OnFocus(self, property, ctrl): + ctrl.tc.SetSelection(-1,-1) + ctrl.tc.SetFocus() + + +class PropertyEditor(wx.Panel): + + def __init__(self, parent): + # Use the WANTS_CHARS style so the panel doesn't eat the Return key. + wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS, size=(160, 200)) + + sizer = wx.BoxSizer(wx.VERTICAL) + + self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT) + + # Show help as tooltips + self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS) + + #pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChange) + #pg.Bind(wxpg.EVT_PG_SELECTED, self.OnPropGridSelect) + #self.pg.Bind(wxpg.EVT_PG_RIGHT_CLICK, self.OnPropGridRightClick) + + # Needed by custom image editor + wx.InitAllImageHandlers() + + # + # Let's create a simple custom editor + # + # NOTE: Editor must be registered *before* adding a property that uses it. + self.pg.RegisterEditor(LargeImageEditor) + + ''' + # + # Add properties + # + + pg.Append( wxpg.PropertyCategory("1 - Basic Properties") ) + pg.Append( wxpg.StringProperty("String",value="Some Text") ) + pg.Append( wxpg.IntProperty("Int",value=100) ) + pg.Append( wxpg.FloatProperty("Float",value=100.0) ) + pg.Append( wxpg.BoolProperty("Bool",value=True) ) + pg.Append( wxpg.BoolProperty("Bool_with_Checkbox",value=True) ) + pg.SetPropertyAttribute("Bool_with_Checkbox", "UseCheckbox", True) + + pg.Append( wxpg.PropertyCategory("2 - More Properties") ) + pg.Append( wxpg.LongStringProperty("LongString",value="This is a\\nmulti-line string\\nwith\\ttabs\\nmixed\\tin.") ) + pg.Append( wxpg.DirProperty("Dir",value="C:\\Windows") ) + pg.Append( wxpg.FileProperty("File",value="C:\\Windows\\system.ini") ) + pg.Append( wxpg.ArrayStringProperty("ArrayString",value=['A','B','C']) ) + + pg.Append( wxpg.EnumProperty("Enum","Enum", + ['wxPython Rules','wxPython Rocks','wxPython Is The Best'], + [10,11,12],0) ) + pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") ) + + pg.Append( wxpg.PropertyCategory("3 - Advanced Properties") ) + pg.Append( wxpg.DateProperty("Date",value=wx.DateTime_Now()) ) + pg.Append( wxpg.FontProperty("Font",value=self.GetFont()) ) + pg.Append( wxpg.ColourProperty("Colour",value=self.GetBackgroundColour()) ) + pg.Append( wxpg.SystemColourProperty("SystemColour") ) + pg.Append( wxpg.ImageFileProperty("ImageFile") ) + pg.Append( wxpg.MultiChoiceProperty("MultiChoice",choices=['wxWidgets','QT','GTK+']) ) + + pg.Append( wxpg.PropertyCategory("4 - Additional Properties") ) + pg.Append( wxpg.PointProperty("Point",value=self.GetPosition()) ) + pg.Append( wxpg.SizeProperty("Size",value=self.GetSize()) ) + pg.Append( wxpg.FontDataProperty("FontData") ) + pg.Append( wxpg.IntProperty("IntWithSpin",value=256) ) + pg.SetPropertyEditor("IntWithSpin","SpinCtrl") + pg.Append( wxpg.DirsProperty("Dirs",value=['C:/Lib','C:/Bin']) ) + pg.SetPropertyHelpString( "String", "String Property help string!" ) + pg.SetPropertyHelpString( "Dirs", "Dirs Property help string!" ) + + pg.SetPropertyAttribute( "File", wxpg.PG_FILE_SHOW_FULL_PATH, 0 ) + pg.SetPropertyAttribute( "File", wxpg.PG_FILE_INITIAL_PATH, "C:\\Program Files\\Internet Explorer" ) + pg.SetPropertyAttribute( "Date", wxpg.PG_DATE_PICKER_STYLE, wx.DP_DROPDOWN|wx.DP_SHOWCENTURY ) + + pg.Append( wxpg.PropertyCategory("5 - Custom Properties") ) + pg.Append( IntProperty2("IntProperty2", value=1024) ) + + pg.Append( ShapeProperty("ShapeProperty", value=0) ) + pg.Append( PyObjectProperty("PyObjectProperty") ) + + pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") ) + pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor") + + + pg.SetPropertyClientData( "Point", 1234 ) + if pg.GetPropertyClientData( "Point" ) != 1234: + raise ValueError("Set/GetPropertyClientData() failed") + + # Test setting unicode string + pg.GetPropertyByName("String").SetValue(u"Some Unicode Text") + + # + # Test some code that *should* fail (but not crash) + #try: + #a_ = pg.GetPropertyValue( "NotARealProperty" ) + #pg.EnableProperty( "NotAtAllRealProperty", False ) + #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" ) + #except: + #pass + #raise + + ''' + sizer.Add(self.pg, 1, wx.EXPAND) + self.SetSizer(sizer) + sizer.SetSizeHints(self) + + self.SelectedTreeItem = None + + def GetPropertyValues(self): + return self.pg.GetPropertyValues() + + def Initialize(self, properties): + pg = self.pg + pg.Clear() + + if properties: + for element in properties: + if element[1]['type'] == 'arraystring': + elements = element[1]['elements'] + if 'value' in element[1]: + property_value = element[1]['value'] + else: + property_value = element[1]['default'] + #retrieve individual strings + property_value = split(property_value, ' ') + #remove " delimiters + values = [value.strip('"') for value in property_value] + pg.Append(wxpg.ArrayStringProperty(element[0], value=values)) + + if element[1]['type'] == 'boolean': + if 'value' in element[1]: + property_value = element[1].as_bool('value') + else: + property_value = element[1].as_bool('default') + property_control = wxpg.BoolProperty(element[0], value=property_value) + pg.Append(property_control) + pg.SetPropertyAttribute(element[0], 'UseCheckbox', True) + + #if element[0] == 'category': + #pg.Append(wxpg.PropertyCategory(element[1])) + + if element[1]['type'] == 'color': + if 'value' in element[1]: + property_value = element[1]['value'] + else: + property_value = element[1]['default'] + property_value = eval(property_value) + pg.Append(wxpg.ColourProperty(element[0], value=property_value)) + + if element[1]['type'] == 'enum': + elements = element[1]['elements'] + if 'value' in element[1]: + property_value = element[1]['value'] + else: + property_value = element[1]['default'] + pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value))) + + if element[1]['type'] == 'filename': + if 'value' in element[1]: + property_value = element[1]['value'] + else: + property_value = element[1]['default'] + pg.Append(wxpg.FileProperty(element[0], value=property_value)) + + if element[1]['type'] == 'float': + if 'value' in element[1]: + property_value = element[1].as_float('value') + else: + property_value = element[1].as_float('default') + property_control = wxpg.FloatProperty(element[0], value=property_value) + pg.Append(property_control) + + if element[1]['type'] == 'folder': + if 'value' in element[1]: + property_value = element[1]['value'] + else: + property_value = element[1]['default'] + pg.Append(wxpg.DirProperty(element[0], value=property_value)) + + if element[1]['type'] == 'integer': + if 'value' in element[1]: + property_value = element[1].as_int('value') + else: + property_value = element[1].as_int('default') + property_control = wxpg.IntProperty(element[0], value=property_value) + if 'maximum' in element[1]: + property_control.SetAttribute('Max', element[1].as_int('maximum')) + if 'minimum' in element[1]: + property_control.SetAttribute('Min', element[1].as_int('minimum')) + property_control.SetAttribute('Wrap', True) + pg.Append(property_control) + pg.SetPropertyEditor(element[0], 'SpinCtrl') + + if element[1]['type'] == 'string': + if 'value' in element[1]: + property_value = element[1]['value'] + else: + property_value = element[1]['default'] + pg.Append(wxpg.StringProperty(element[0], value=property_value)) + + pg.Refresh() + + def OnReserved(self, event): + pass diff --git a/panels/results.py b/panels/results.py new file mode 100644 index 0000000..4b10f83 --- /dev/null +++ b/panels/results.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +''' +results.py + +Fitting results panel for Hooke. + +Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' + +import sys +import wx +from wx.lib.mixins.listctrl import CheckListCtrlMixin + +class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin): + def __init__(self, parent): + wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT) + CheckListCtrlMixin.__init__(self) + self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated) + + def OnItemActivated(self, evt): + self.ToggleItem(evt.m_itemIndex) + + +class Results(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent, -1) + self.results_list = CheckListCtrl(self) + sizer = wx.BoxSizer() + sizer.Add(self.results_list, 1, wx.EXPAND) + self.SetSizer(sizer) + self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.results_list) + self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self.results_list) + + def _GetWidthInPixels(self, text): + #TODO: + #Returns the width of a string in pixels + #Unfortunately, it does not work terribly well (although it should). + #Thus, we have to add a bit afterwards. + #Annoys the heck out of me (me being Rolf). + font = self.results_list.GetFont() + dc = wx.WindowDC(self.results_list) + dc.SetFont(font) + width, height = dc.GetTextExtent(text) + return width + + def ClearResults(self): + self.results_list.ClearAll() + + def DisplayResults(self, results): + self.ClearResults() + header = results.get_header_as_list() + self.results_list.InsertColumn(0, 'Show') + for index, column in enumerate(header): + self.results_list.InsertColumn(index + 1, column, wx.LIST_FORMAT_RIGHT) + + for result in results.results: + done = False + for index, column in enumerate(results.columns): + value_str = results.get_pretty_value(column, result.result[column]) + if not done: + index_col = self.results_list.InsertStringItem(sys.maxint, '') + done = True + column_width = len(self.results_list.GetColumn(index + 1).GetText()) + value_str = value_str.center(column_width) + self.results_list.SetStringItem(index_col, index + 1, value_str) + + for index, result in enumerate(results.results): + if result.visible: + #if we use 'CheckItem' then 'UpdatePlot' is called (ie repeated updates) + self.results_list.SetItemImage(index, 1) + for index in range(self.results_list.GetColumnCount()): + column_text = self.results_list.GetColumn(index).GetText() + column_width = self._GetWidthInPixels(column_text) + self.results_list.SetColumnWidth(index, column_width + 15) + + def OnItemSelected(self, evt): + pass + + def OnItemDeselected(self, evt): + pass diff --git a/pca_config.txt b/pca_config.txt deleted file mode 100644 index 8eb72a9..0000000 --- a/pca_config.txt +++ /dev/null @@ -1,33 +0,0 @@ -1,3,6,7,8,9x15,10,11 -0.000000 -1,2,3,4,5,6,7,8,9,10,11 -debug=false ------------------------------------------- -#1 colonne della prima pca -#2 limite filtro della densità (x es: "0.000008"; "0" per calcolo automatico.. 3,242311147) -#3 colonne della secona pca (non usato) -#4 attiva modalità debug (stampa anche immagini/coordinate di pca/density) ------------------------------------------- -str(peak_number)+ # non considerato -str(delta_mean)+ # 0 -str(delta_median)+ # 1 - -str(force_mean)+ # 2 -str(force_median)+ # 3 - -str(first_peak_cl)+ # 4 - -str(last_peak_cl)+ # 5 - -str(max_force)+ # 6 -str(min_force)+ # 7 -str(max_delta)+ # 8 -str(min_delta)+ # 9 -str(delta_stdev)+ # 10 -str(forces_stdev)+ # 11 -str(peaks_diff)+ # 12 ------------------------------------------- -Lancio di pCluster e pca: - - cd 20080917_4s4wt_10mMtris_all - - genlist *.* - - setconv blindwindow 50 - - pcluster pl=0.35 ------------------------------------------- -Lancio della sola pca - - pca 20080922_gb1x8_tris/pCluster_20090627_1328/coordinate_20080922_gb1x8_tris_blind50.txt \ No newline at end of file diff --git a/pcluster.py b/pcluster.py deleted file mode 100644 index 5084b83..0000000 --- a/pcluster.py +++ /dev/null @@ -1,735 +0,0 @@ -#!/usr/bin/env python - -from mdp import pca -from libhooke import WX_GOOD, ClickedPoint -import wxversion -wxversion.select(WX_GOOD) -from wx import PostEvent -import numpy as np -import scipy as sp -import copy -import os.path -import time -import libhookecurve as lhc -import pylab as pyl - -import warnings -warnings.simplefilter('ignore',np.RankWarning) - - -class pclusterCommands: - - def _plug_init(self): - self.clustplot1=None - self.clustplot2=None - - def do_pcluster(self,args): - ''' - pCLUSTER - (pcluster.py) - Automatically measures peaks and extracts informations for further clustering - (c)Paolo Pancaldi, Massimo Sandal 2009 - ''' - if self.config['hookedir'][0]=='/': - slash='/' #a Unix or Unix-like system - else: - slash='\\' - blindw = str(self.convfilt_config['blindwindow']) - pclus_dir = "pCluster_blind"+blindw+"_"+time.strftime("%Y%m%d_%H%M") - self.my_work_dir = os.getcwd()+slash+pclus_dir+slash - self.my_curr_dir = os.path.basename(os.getcwd()) - os.mkdir(self.my_work_dir) - - #--Custom persistent length - pl_value=None - for arg in args.split(): - #look for a persistent length argument. - if 'pl=' in arg: - pl_expression=arg.split('=') - pl_value=float(pl_expression[1]) #actual value - else: - pl_value=None - - #configuration variables - min_npks = self.convfilt_config['minpeaks'] - min_deviation = self.convfilt_config['mindeviation'] - - pclust_filename = "automeasure_"+self.my_curr_dir+"_blind"+blindw+".txt" #raw_input('Automeasure filename? ') - realclust_filename = "coordinate_"+self.my_curr_dir+"_blind"+blindw+".txt" #raw_input('Coordinates filename? ') - peackforce_filename = "peakforce_"+self.my_curr_dir+"_blind"+blindw+".txt" #raw_input('Peacks and Forces filename? ') - - f=open(self.my_work_dir+pclust_filename,'w+') - f.write('Analysis started '+time.asctime()+'\n') - f.write('----------------------------------------\n') - f.write('; Contour length (nm) ; Persistence length (nm) ; Max.Force (pN) ; Slope (N/m) ; Sigma contour (nm) ; Sigma persistence (nm)\n') - f.close() - - f=open(self.my_work_dir+realclust_filename,'w+') - f.write('Analysis started '+time.asctime()+'\n') - f.write('----------------------------------------\n') - f.write('; Peak number ; Mean delta (nm) ; Median delta (nm) ; Mean force (pN) ; Median force (pN) ; First peak length (nm) ; Last peak length (nm) ; Max force (pN) ; Min force (pN) ; Max delta (nm) ; Min delta (nm) ; Peaks Diff\n') - f.close() - - f=open(self.my_work_dir+peackforce_filename,'w+') - f.write('Analysis started '+time.asctime()+'\n') - f.write('----------------------------------------\n') - f.write('; Peak number ; 1 peak Length (nm) ; 1 peak Force (pN) ; 2 peak Length (nm) ; 2 peak Force (pN) ; 3 peak Length (nm) ; 3 peak Force (pN) ; 4 peak Length (nm) ; 4 peak Force (pN) ; 5 peak Length (nm) ; 5 peak Force (pN) ; 6 peak Length (nm) ; 6 peak Force (pN) ; 7 peak Length (nm) ; 7 peak Force (pN) ; 8 peak Length (nm) ; 8 peak Force (pN)\n') - f.close() - - # ------ FUNCTION ------ - def fit_interval_nm(start_index,plot,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. - ''' - whatset=1 #FIXME: should be decidable - x_vect=plot.vectors[1][0] - - c=0 - i=start_index - start=x_vect[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 plot_informations(itplot,pl_value): - ''' - OUR VARIABLES - contact_point.absolute_coords (2.4584142802103689e-007, -6.9647135616234017e-009) - peak_point.absolute_coords (3.6047748250571423e-008, -7.7142802788854212e-009) - other_fit_point.absolute_coords (4.1666139243838867e-008, -7.3759393477579707e-009) - peak_location [510, 610, 703, 810, 915, 1103] - peak_size [-1.2729111505202212e-009, -9.1632775347399312e-010, -8.1707438353929907e-010, -8.0335812578148904e-010, -8.7483955226387558e-010, -3.6269619757067322e-009] - params [2.2433999931959462e-007, 3.3230248825175678e-010] - fit_errors [6.5817195369767644e-010, 2.4415923138871498e-011] - ''' - fit_points=int(self.config['auto_fit_points']) # number of points to fit before the peak maximum <50> - - T=self.config['temperature'] #temperature of the system in kelvins. By default it is 293 K. <301.0> - cindex=self.find_contact_point(itplot[0]) #Automatically find contact point <158, libhooke.ClickedPoint> - contact_point=self._clickize(itplot[0].vectors[1][0], itplot[0].vectors[1][1], cindex) - self.basepoints=[] - base_index_0=peak_location[-1]+fit_interval_nm(peak_location[-1], itplot[0], self.config['auto_right_baseline'],False) - self.basepoints.append(self._clickize(itplot[0].vectors[1][0],itplot[0].vectors[1][1],base_index_0)) - base_index_1=self.basepoints[0].index+fit_interval_nm(self.basepoints[0].index, itplot[0], self.config['auto_left_baseline'],False) - self.basepoints.append(self._clickize(itplot[0].vectors[1][0],itplot[0].vectors[1][1],base_index_1)) - self.basecurrent=self.current.path - boundaries=[self.basepoints[0].index, self.basepoints[1].index] - boundaries.sort() - to_average=itplot[0].vectors[1][1][boundaries[0]:boundaries[1]] #y points to average - avg=np.mean(to_average) - return fit_points, contact_point, pl_value, T, cindex, avg - - def features_peaks(itplot, peak, fit_points, contact_point, pl_value, T, cindex, avg): - ''' - calculate informations for each peak and add they in - c_lengths, p_lengths, sigma_c_lengths, sigma_p_lengths, forces, slopes - ''' - c_leng=None - p_leng=None - sigma_c_leng=None - sigma_p_leng=None - force=None - slope=None - - delta_force=10 - slope_span=int(self.config['auto_slope_span']) - - peak_point=self._clickize(itplot[0].vectors[1][0],itplot[0].vectors[1][1],peak) - other_fit_point=self._clickize(itplot[0].vectors[1][0],itplot[0].vectors[1][1],peak-fit_points) - - points=[contact_point, peak_point, other_fit_point] - - params, yfit, xfit, fit_errors = self.wlc_fit(points, itplot[0].vectors[1][0], itplot[0].vectors[1][1], pl_value, T, return_errors=True) - - #Measure forces - delta_to_measure=itplot[0].vectors[1][1][peak-delta_force:peak+delta_force] - y=min(delta_to_measure) - #Measure slopes - slope=self.linefit_between(peak-slope_span,peak)[0] - #check fitted data and, if right, add peak to the measurement - if len(params)==1: #if we did choose 1-value fit - p_leng=pl_value - c_leng=params[0]*(1.0e+9) - sigma_p_leng=0 - sigma_c_leng=fit_errors[0]*(1.0e+9) - force = abs(y-avg)*(1.0e+12) - else: #2-value fit - p_leng=params[1]*(1.0e+9) - #check if persistent length makes sense. otherwise, discard peak. - if p_leng>self.config['auto_min_p'] and p_leng 1: - deltas=[] - for i in range(len(c_lengths)-1): - deltas.append(c_lengths[i+1]-c_lengths[i]) - - delta_mean=np.mean(deltas) - delta_median=np.median(deltas) - - force_mean=np.mean(forces) - force_median=np.median(forces) - - first_peak_cl=c_lengths[0] - last_peak_cl=c_lengths[-1] - - max_force=max(forces[:-1]) - min_force=min(forces) - - max_delta=max(deltas) - min_delta=min(deltas) - - delta_stdev=np.std(deltas) - forces_stdev=np.std(forces[:-1]) - - peaks_diff=(last_peak_cl-first_peak_cl)/peak_number - - print 'Coordinates' - print 'Peaks',peak_number - print 'Mean delta',delta_mean - print 'Median delta',delta_median - print 'Mean force',force_mean - print 'Median force',force_median - print 'First peak',first_peak_cl - print 'Last peak',last_peak_cl - print 'Max force',max_force - print 'Min force',min_force - print 'Max delta',max_delta - print 'Min delta',min_delta - print 'Delta stdev',delta_stdev - print 'Forces stdev',forces_stdev - print 'Peaks difference',peaks_diff - - ''' - write clustering coordinates - ''' - f=open(self.my_work_dir+realclust_filename,'a+') - f.write(item.path+'\n') - f.write(' ; '+str(peak_number)+ # non considerato - ' ; '+str(delta_mean)+ # 0 - ' ; '+str(delta_median)+ # 1 - - ' ; '+str(force_mean)+ # 2 - ' ; '+str(force_median)+ # 3 - - ' ; '+str(first_peak_cl)+ # 4 - - ' ; '+str(last_peak_cl)+ # 5 - - ' ; '+str(max_force)+ # 6 - ' ; '+str(min_force)+ # 7 - ' ; '+str(max_delta)+ # 8 - ' ; '+str(min_delta)+ # 9 - ' ; '+str(delta_stdev)+ # 10 - ' ; '+str(forces_stdev)+ # 11 - ' ; '+str(peaks_diff)+ # 12 - '\n') - f.close() - - # start PCA - self.do_pca(pclus_dir+"/"+realclust_filename) - - - def do_pca(self,args): - ''' - PCA -> "pca gaeta_coor_blind50.txt 1,3,6" - Automatically measures pca from coordinates filename and shows two interactives plots - With the second argument (arbitrary) you can select the columns and the multiplier factor - to use for the pca (for es "1,3*50,6,8x10,9"). Dont use spaces. "*" or "x" are the same thing. - Without second argument it reads pca_config.txt file - (c)Paolo Pancaldi, Massimo Sandal 2009 - ''' - - # reads the columns of pca - if self.config['hookedir'][0]=='/': - slash='/' #a Unix or Unix-like system - else: - slash='\\' - self.my_hooke_dir = self.config['hookedir']+slash - #self.my_work_dir = os.getcwd()+slash+"pCluster_"+time.strftime("%Y%m%d_%H%M")+slash - #self.my_curr_dir = os.path.basename(os.getcwd()) - conf=open(self.my_hooke_dir+"pca_config.txt") - config = conf.readlines() - conf.close() - - self.plot_myCoord = [] # tiene le coordinate prese direttamente dal file creato con pCluster - self.plot_origCoord = [] # tiene le coordinate solo delle colonne scelte e moltiplicate per i valori scelti - self.plot_pcaCoord = [] # tiene le due colonne della pca - self.plot_pcaCoordTr = [] # tiene le due colonne della pca trasposta - self.plot_FiltOrigCoord = [] # tiene le coordinate solo dei punti filtrati per densita - self.plot_FiltPaths = [] # tiene i paths dei plot solo dei punti filtrati per densita - self.plot_paths = [] # tiene i paths di tutti i plots - self.plot_NewPcaCoord = [] # tiene le due colonne della pca filtrate per densita - self.plot_NewPcaCoordTr=[] # tiene le due colonne della pca trasposta filtrate per densita - plot_path_temp = "" - - # prende in inpunt un arg (nome del file) - # e il secondo le colonne su cui lavorare (e' arbitrario, riceve x es "1,2,3") - arg = args.split(" ") - if arg[0]==args: - file_name=args - else: - file_name=arg[0] - config[0] = arg[1] - - # creo l'array "plot_myCoord" con tutte le coordinate dei plots - # e l'array plot_paths con tutti i percorsi dei plots - nPlotTot = -3 #tolgo le prime 3 righe iniziali del file - f=open(file_name) - rows = f.readlines() - for row in rows: - if row[0]!=" " and row[0]!="": - nPlotTot = nPlotTot+1 - plot_path_temp = row - if row[0]==" " and row.find('nan')==-1 and row.find("-1.#IND")==-1: - row = row[row.index(";",2)+2:].split(" ; ") # non considero la prima colonna col #picchi - row = [float(i) for i in row] - - #0:Mean delta, 1:Median delta, 2:Mean force, 3:Median force, 4:First peak length, 5:Last peak length - #6:Max delta 7:Min delta 8:Max force 9:Min force 10:Std delta 11:Std force - if (row[0]<500 and row[1]<500 and row[2]<500 and row[3]<500 and row[4]<500 and row[5]<500 and row[6]<500 and row[7]<500 and row[8]<500 and row[9]<500 and row[10]<500 and row[11]<500): - if (row[0]>0 and row[1]>0 and row[2]>0 and row[3]>0 and row[4]>0 and row[5]>0 and row[6]>0 and row[7]>0 and row[8]>0 and row[9]>0 and row[10]>0 and row[11]>0): - #row = row[0], row[2], row[3]*3, row[6], row[7]*56, row[8] - self.plot_myCoord.append(row) - self.plot_paths.append(plot_path_temp) - f.close() - - # creo l'array con alcune colonne e pure moltiplicate - for row in self.plot_myCoord: - res=[] - for cols in config[0].split(","): - if cols.find("*")!=-1: - col = int(cols.split("*")[0]) - molt = int(cols.split("*")[1]) - elif cols.find("x")!=-1: - col = int(cols.split("x")[0]) - molt = int(cols.split("x")[1]) - else: - col = int(cols) - molt = 1 - res.append(row[col]*molt) - self.plot_origCoord.append(res) - - # array convert, calculate PCA, transpose - self.plot_origCoord = np.array(self.plot_origCoord,dtype='float') - #print self.plot_origCoord.shape - self.plot_pcaCoord = pca(self.plot_origCoord, output_dim=2) #other way -> y = mdp.nodes.PCANode(output_dim=2)(array) - self.plot_pcaCoordTr = np.transpose(self.plot_pcaCoord) - pca_X=np.array(self.plot_pcaCoordTr[0],dtype='float') - pca_Y=np.array(self.plot_pcaCoordTr[1],dtype='float') - - ''' - # Start section of testing with good plots # 4 TESTING! - Xsyn_1=[] - Ysyn_1=[] - Xgb1_1=[] - Ygb1_1=[] - Xbad_1=[] - Ybad_1=[] - goodnamefile=open(file_name.replace("coordinate", "good"),'r') - goodnames=goodnamefile.readlines() - nPlotGood = len(goodnames)-2 #tolgo prima e ultima riga - goodnames=[i.split()[0] for i in goodnames[1:]] - - for index in range(len(self.plot_paths)): - if self.plot_paths[index][:-1] in goodnames: - Xsyn_1.append(pca_X[index]) - Ysyn_1.append(pca_Y[index]) - else: - Xbad_1.append(pca_X[index]) - Ybad_1.append(pca_Y[index]) - # Stop section of testing with good plots # 4 TESTING! - ''' - - # print first plot - clustplot1=lhc.PlotObject() - clustplot1.add_set(pca_X,pca_Y) - #clustplot1.add_set(Xbad_1,Ybad_1) # 4 TESTING! - #clustplot1.add_set(Xsyn_1,Ysyn_1) # 4 TESTING! - clustplot1.normalize_vectors() - clustplot1.styles=['scatter', 'scatter','scatter'] - clustplot1.colors=[None,'red','green'] - clustplot1.destination=0 - self._send_plot([clustplot1]) - self.clustplot1=clustplot1 - - # density and filer estimation - kernel = sp.stats.kde.gaussian_kde(sp.c_[pca_X,pca_Y].T) - tallest = 0 - for i in range(len(pca_X)): - kern_value = kernel.evaluate([pca_X[i],pca_Y[i]]) - if tallest < kern_value: - tallest = float(kern_value) - if float(config[1]) == 0: - my_filter = float(tallest / 3.242311147) - else: - my_filter = float(config[1]) - ''' - # section useful only for graphic printing - xmin = pca_X.min() - xmax = pca_X.max() - ymin = pca_Y.min() - ymax = pca_Y.max() - mX, mY = sp.mgrid[xmin:xmax:100j, ymin:ymax:100j] - Z = sp.rot90(sp.fliplr(sp.reshape(kernel(sp.c_[mX.ravel(), mY.ravel()].T).T, mX.T.shape))) - axis_X = np.linspace(xmin,xmax,num=100) - axis_Y = np.linspace(ymin,ymax,num=100) - ''' - - # density filtering: - # tramite "kernel.evaluate" trovo lo score (altezza) di ogni coordinata e decido se mantenerla o no - filtered_pca_X = [] - filtered_pca_Y = [] - filtered_PcaCoordTr = [] - filtered_PcaCoord = [] - for i in range(len(pca_X)): - kern_value = kernel.evaluate([pca_X[i],pca_Y[i]]) - if kern_value > my_filter: - filtered_pca_X.append(pca_X[i]) - filtered_pca_Y.append(pca_Y[i]) - filtered_PcaCoordTr.append(filtered_pca_X) - filtered_PcaCoordTr.append(filtered_pca_Y) - filtered_PcaCoord = np.transpose(filtered_PcaCoordTr) - - # creo i due array "plot_FiltOrigCoord" e "plot_FiltPaths" contenenti solo i dati filtrati con alta densita - for index in range(len(self.plot_pcaCoord)): - if self.plot_pcaCoord[index] in filtered_PcaCoord: - self.plot_FiltOrigCoord.append(self.plot_myCoord[index]) - self.plot_FiltPaths.append(self.plot_paths[index]) - - ''' - # START PCA#2: USELESS!!! - - # creo l array con alcune colonne e pure moltiplicate - temp_coord = [] - for row in self.plot_FiltOrigCoord: - res=[] - for cols in config[2].split(","): - if cols.find("*")!=-1: - col = int(cols.split("*")[0]) - molt = int(cols.split("*")[1]) - elif cols.find("x")!=-1: - col = int(cols.split("x")[0]) - molt = int(cols.split("x")[1]) - else: - col = int(cols) - molt = 1 - res.append(row[col]*molt) - temp_coord.append(res) - self.plot_FiltOrigCoord = temp_coord - - # ricalcolo la PCA: array convert, calculate PCA, transpose - self.plot_FiltOrigCoord = np.array(self.plot_FiltOrigCoord,dtype='float') - #print self.plot_FiltOrigCoord.shape - self.plot_NewPcaCoord = pca(self.plot_FiltOrigCoord, output_dim=2) #other way -> y = mdp.nodes.PCANode(output_dim=2)(array) - self.plot_NewPcaCoordTr = np.transpose(self.plot_NewPcaCoord) - pca_X2=np.array(self.plot_NewPcaCoordTr[0],dtype='float') - pca_Y2=np.array(self.plot_NewPcaCoordTr[1],dtype='float') - - # Start section of testing with good plots # 4 TESTING! - Xsyn_2=[] - Ysyn_2=[] - Xbad_2=[] - Ybad_2=[] - for index in range(len(self.plot_FiltPaths)): - if self.plot_FiltPaths[index][:-1] in goodnames: - Xsyn_2.append(pca_X2[index]) - Ysyn_2.append(pca_Y2[index]) - else: - Xbad_2.append(pca_X2[index]) - Ybad_2.append(pca_Y2[index]) - - # print second plot - clustplot2=lhc.PlotObject() - #clustplot2.add_set(pca_X2,pca_Y2) - clustplot2.add_set(Xbad_2,Ybad_2) # 4 TESTING! - clustplot2.add_set(Xsyn_2,Ysyn_2) # 4 TESTING! - clustplot2.normalize_vectors() - clustplot2.styles=['scatter', 'scatter','scatter'] - clustplot2.colors=[None,'red','green'] - clustplot2.destination=1 - self._send_plot([clustplot2]) - self.clustplot2=clustplot2 - ''' - - # PRINT density plot - clustplot2=lhc.PlotObject() - clustplot2.add_set(filtered_pca_X,filtered_pca_Y) - clustplot2.normalize_vectors() - clustplot2.styles=['scatter', 'scatter','scatter'] - clustplot2.colors=[None,'red','green'] - clustplot2.destination=1 - self._send_plot([clustplot2]) - self.clustplot2=clustplot2 - - # printing results - config_pca1 = config[0].replace("*", "x").rstrip("\n") - config_pca2 = config[2].replace("*", "x").rstrip("\n") - print "" - print "- START: "+file_name - print "Curve totali: ", nPlotTot - #print "Curve totali good: ", nPlotGood # 4 TESTING! - print "- FILTRO 1: 0-500 e NaN" - print "Curve totali rimaste: ", len(self.plot_origCoord) - #print 'Curve good rimaste: ', len(Xsyn_1) # 4 TESTING! - print "- FILTRO 2: PCA:"+config_pca1+" e DENSITA:"+str(my_filter) - print "Curve totali rimaste: ", len(self.plot_FiltOrigCoord) - #print 'Curve good rimaste: ', len(Xsyn_2) # 4 TESTING! - print "Piu alta: ", tallest - #print "- FILTRO 3: 2'PCA:"+config_pca2 - print "" - - # -- exporting coordinates and plot of PCA in debug mode! -- - if config[3].find("true")!=-1: - #1' PCA: save plot and build coordinate s file - self.do_export(file_name.replace("coordinate_", "debug_pca1graph_").replace('.txt','_'+config_pca1) + " 0") - f = open(file_name.replace("coordinate_", "debug_pca1coor_").replace('.txt','_'+config_pca1+'.txt'),'w') - for i in range(len(pca_X)): - f.write (str(i) + "\t" + str(pca_X[i]) + "\t" + str(pca_Y[i]) + "\n") - f.close() - #2' PCA: save plot and build coordinate s file - #self.do_export(file_name.replace("coordinate_", "debug_pca2graph_").replace('.txt','_'+config_pca2) + " 1") - #f = open(file_name.replace("coordinate_", "debug_pca2coor_").replace('.txt','_'+config_pca2+'.txt'),'w') - #for i in range(len(pca_X2)): - # f.write (str(i) + "\t" + str(pca_X2[i]) + "\t" + str(pca_Y2[i]) + "\n") - #f.close() - #DENSITY: save plot - self.do_export(file_name.replace("coordinate_", "debug_densitygraph_").replace('.txt','_'+config_pca1+'_'+str(my_filter).replace(".",",")) + " 1") - f = open(file_name.replace("coordinate_", "debug_densitycoor_").replace('.txt','_'+config_pca1+'_'+str(my_filter).replace(".",",")+'.txt'),'w') - for i in range(len(filtered_pca_X)): - f.write (str(i) + "\t" + str(filtered_pca_X[i]) + "\t" + str(filtered_pca_Y[i]) + "\n") - f.close() - #ALL GOOD COORDINATES (without NaN and 0 "multipca gaeta_coor_blind50.txt 3" - Automatically multiply the column suggest in second argument for value between 1-100 (step of 2), - measures pca from coordinates filename and save the png plots. - (c)Paolo Pancaldi, Massimo Sandal 2009 - ''' - # reads the columns of pca - conf=open("pca_config.txt") - config = conf.readlines() # config[0] = "1,2,3" - conf.close() - # cycling pca - arg = args.split(" ") - file_name=arg[0] - column=str(arg[1]) - for i in range(1, 51, 1): - self.do_pca(file_name + " " + config[0].replace(column,column+"*"+str(i),1)) - - def do_doublepca(self,args): - ''' - DOUBLEPCA -> "doublepca gaeta_coor_blind50.txt" - Automatically it launches the pca command for all combinations with two column - (c)Paolo Pancaldi, Massimo Sandal 2009 - ''' - # cycling pca - arg = args.split(" ") - file_name=arg[0] - for i in range(1, 13): - for j in range(1, 13): - if i!=j: - self.do_pca(file_name + " " + str(i) + "," + str(j)) - - def do_triplepca(self,args): - ''' - TRIPLEPCA -> "triplepca gaeta_coor_blind50.txt" - Automatically it launches the pca command for all combinations with three column - (c)Paolo Pancaldi, Massimo Sandal 2009 - ''' - # cycling pca - arg = args.split(" ") - file_name=arg[0] - for i in range(1, 13): - for j in range(1, 13): - for k in range(1, 13): - if i!=j and i!=k and j!=k: - self.do_pca(file_name + " " + str(i) + "," + str(j) + "," + str(k)) - - def do_pclick(self,args): - ''' - It returns id, coordinates and file name of a clicked dot on a PCA graphic - ''' - - self._send_plot([self.clustplot1]) #quick workaround for BAD problems in the GUI - print 'Click point' - point = self._measure_N_points(N=1, whatset=0) - indice = point[0].index - plot_file = self.plot_paths[indice] - dot_coord = self.plot_pcaCoord[indice] - print "file: " + str(plot_file).rstrip() - print "id: " + str(indice) - print "coord: " + str(dot_coord) - self.do_genlist(str(plot_file)) - #self.do_jump(str(plot_file)) - - # indea iniziata e messa da parte... - def do_peakforce(self, args): - ''' - peackforce -> "peackforce peackforce_file.txt" - Automatically measures peack and force plots - (c)Paolo Pancaldi, Massimo Sandal 2009 - ''' - - # prende in inpunt un arg (nome del file) - file_name=args - f=open(file_name) - - # scrivo un file temp - g = open('_prove.txt','w') - - plot_file = ''; - rows = f.readlines() - for row in rows: - if row[0]=="/": - plot_file = row - if row[0]==" " and row.find('nan')==-1 and row.find("-1.#IND")==-1: - # FILTRO SUI 7 PICCHI - num_pic = int(row.split(" ; ")[1]) - if num_pic==7: - width_force = row.split(" ; ") - w1 = float(width_force[2]); f1 = float(width_force[3]); - w2 = float(width_force[4]); f2 = float(width_force[5]); - w3 = float(width_force[6]); f3 = float(width_force[7]); - w4 = float(width_force[8]); f4 = float(width_force[9]); - w5 = float(width_force[10]); f5 = float(width_force[11]); - w6 = float(width_force[12]); f6 = float(width_force[13]); - w7 = float(width_force[14]); f7 = float(width_force[15]); - if w1>0 and w1<1000 and w2>0 and w2<1000 and w3>0 and w3<1000 and w4>0 and w4<1000 and w5>0 and w5<1000 and w6>0 and w6<1000 and w7>0 and w7<1000: - score_76 = abs(32 - (w7 - w6)) - score_65 = abs(32 - (w6 - w5)) - score_54 = abs(32 - (w5 - w4)) - score_43 = abs(32 - (w4 - w3)) - score_32 = abs(32 - (w3 - w2)) - score_21 = abs(32 - (w2 - w1)) - writeme = str(score_76) + " --- " + str(row) - g.write(writeme) - g.close() - f.close() - - \ No newline at end of file diff --git a/picoforcealt.py b/picoforcealt.py deleted file mode 100644 index 078430b..0000000 --- a/picoforcealt.py +++ /dev/null @@ -1,278 +0,0 @@ -#!/usr/bin/env python - -''' -libpicoforce.py - -Library for interpreting Picoforce force spectroscopy files. Alternate version - -Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy). -Copyright (C) 2008 Alberto Gomez-Casado (University of Twente, Netherlands). - -This program is released under the GNU General Public License version 2. -''' - -import re, struct -from scipy import arange - -import libhookecurve as lhc - -__version__='0.0.0.20081706' - - - -class DataChunk(list): - #Dummy class to provide ext and ret methods to the data list. - - def ext(self): - halflen=(len(self)/2) - return self[0:halflen] - - def ret(self): - halflen=(len(self)/2) - return self[halflen:] - -class picoforcealtDriver(lhc.Driver): - - #Construction and other special methods - - def __init__(self,filename): - ''' - constructor method - ''' - - self.textfile=file(filename) - self.binfile=file(filename,'rb') - - #The 0,1,2 data chunks are: - #0: D (vs T) - #1: Z (vs T) - #2: D (vs Z) - - self.forcechunk=0 - self.distancechunk=1 - #TODO eliminate the need to set chunk numbers - - self.filepath=filename - self.debug=True - - self.filetype='picoforce' - self.experiment='smfs' - - - - def _get_samples_line(self): - ''' - Gets the samples per line parameters in the file, to understand trigger behaviour. - ''' - self.textfile.seek(0) - - samps_expr=re.compile(".*Samps") - - samps_values=[] - for line in self.textfile.readlines(): - if samps_expr.match(line): - try: - samps=int(line.split()[2]) #the third word splitted is the offset (in bytes) - samps_values.append(samps) - except: - pass - - #We raise a flag for the fact we meet an offset, otherwise we would take spurious data length arguments. - - return int(samps_values[0]) - - def _get_chunk_coordinates(self): - ''' - This method gets the coordinates (offset and length) of a data chunk in our - Picoforce file. - - It returns a list containing two tuples: - the first element of each tuple is the data_offset, the second is the corresponding - data size. - - In near future probably each chunk will get its own data structure, with - offset, size, type, etc. - ''' - self.textfile.seek(0) - - offset_expr=re.compile(".*Data offset") - length_expr=re.compile(".*Data length") - - data_offsets=[] - data_sizes=[] - flag_offset=0 - - for line in self.textfile.readlines(): - - if offset_expr.match(line): - offset=int(line.split()[2]) #the third word splitted is the offset (in bytes) - data_offsets.append(offset) - #We raise a flag for the fact we meet an offset, otherwise we would take spurious data length arguments. - flag_offset=1 - - #same for the data length - if length_expr.match(line) and flag_offset: - size=int(line.split()[2]) - data_sizes.append(size) - #Put down the offset flag until the next offset is met. - flag_offset=0 - - return zip(data_offsets,data_sizes) - - def _get_data_chunk(self,whichchunk): - ''' - reads a data chunk and converts it in 16bit signed int. - ''' - offset,size=self._get_chunk_coordinates()[whichchunk] - - - self.binfile.seek(offset) - raw_chunk=self.binfile.read(size) - - my_chunk=[] - for data_position in range(0,len(raw_chunk),2): - data_unit_bytes=raw_chunk[data_position:data_position+2] - #The unpack function converts 2-bytes in a signed int ('h'). - #we use output[0] because unpack returns a 1-value tuple, and we want the number only - data_unit=struct.unpack('h',data_unit_bytes)[0] - my_chunk.append(data_unit) - - return DataChunk(my_chunk) - - def _force(self): - #returns force vector - Kspring=self.get_spring_constant() - return DataChunk([(meter*Kspring) for meter in self._deflection()]) - - def _deflection(self): - #for internal use (feeds _force) - voltrange=1 - z_scale=self._get_Z_scale() - deflsensitivity=self.get_deflection_sensitivity() - volts=[((float(lsb))*voltrange*z_scale) for lsb in self.data_chunks[self.forcechunk]] - deflect=[volt*deflsensitivity for volt in volts] - - return deflect - - - def _Z(self): - #returns distance vector (calculated instead than from data chunk) - rampsize=self._get_rampsize() - sampsline=self._get_samples_line() - senszscan=self._get_Z_scan_sens() - - xstep=senszscan*rampsize/sampsline*10**(-9) - - xext=arange(sampsline*xstep,0,-xstep) - xret=arange(sampsline*xstep,0,-xstep) - - return DataChunk(xext.tolist()+xret.tolist()) - - def _get_Z_scale(self): - self.textfile.seek(0) - expr=re.compile(".*@4:Z scale") - - for line in self.textfile.readlines(): - if expr.match(line): - zscale=float((line.split()[5]).strip("() []")) - break - return zscale - - def _get_rampsize(self): - self.textfile.seek(0) - expr=re.compile(".*@4:Ramp size:") - - for line in self.textfile.readlines(): - if expr.match(line): - zsens=float((line.split()[7]).strip("() []")) - break - return zsens - - def _get_Z_scan_sens(self): - self.textfile.seek(0) - expr=re.compile(".*@Sens. Zsens") - - for line in self.textfile.readlines(): - if expr.match(line): - zsens=float((line.split()[3]).strip("() []")) - break - return zsens - - - - def get_deflection_sensitivity(self): - ''' - gets deflection sensitivity - ''' - self.textfile.seek(0) - - def_sensitivity_expr=re.compile(".*@Sens. DeflSens") - - for line in self.textfile.readlines(): - if def_sensitivity_expr.match(line): - def_sensitivity=float(line.split()[3]) - break - #return it in SI units (that is: m/V, not nm/V) - return def_sensitivity*(10**(-9)) - - def get_spring_constant(self): - ''' - gets spring constant. - We actually find *three* spring constant values, one for each data chunk (F/t, Z/t, F/z). - They are normally all equal, but we retain all three for future... - ''' - self.textfile.seek(0) - - springconstant_expr=re.compile(".*Spring Constant") - - constants=[] - - for line in self.textfile.readlines(): - if springconstant_expr.match(line): - constants.append(float(line.split()[2])) - - return constants[0] - - def is_me(self): - ''' - self-identification of file type magic - ''' - curve_file=file(self.filepath) - header=curve_file.read(30) - curve_file.close() - - if header[2:17] == 'Force file list': #header of a picoforce file - #here DONT translate chunk - self.data_chunks=[self._get_data_chunk(num) for num in [0,1,2]] - return True - else: - return False - - def close_all(self): - ''' - Explicitly closes all files - ''' - self.textfile.close() - self.binfile.close() - - def default_plots(self): - ''' - creates the default PlotObject - ''' - force=self._force() - zdomain=self._Z() - samples=self._get_samples_line() - main_plot=lhc.PlotObject() - main_plot.vectors=[[zdomain.ext()[0:samples], force.ext()[0:samples]],[zdomain.ret()[0:samples], force.ret()[0:samples]]] - main_plot.normalize_vectors() - main_plot.units=['meters','newton'] - main_plot.destination=0 - main_plot.title=self.filepath - - - return [main_plot] - - def deflection(self): - #interface for correct plotmanip and others - deflectionchunk=DataChunk(self._deflection()) - return deflectionchunk.ext(),deflectionchunk.ret() diff --git a/playlists/test.hkp b/playlists/test.hkp new file mode 100644 index 0000000..22c7799 --- /dev/null +++ b/playlists/test.hkp @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/plugins/__init__.py b/plugins/__init__.py new file mode 100644 index 0000000..803f29c --- /dev/null +++ b/plugins/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python diff --git a/plugins/autopeak.ini b/plugins/autopeak.ini new file mode 100644 index 0000000..d2c5b23 --- /dev/null +++ b/plugins/autopeak.ini @@ -0,0 +1,119 @@ +[autopeak] + [[auto_fit_nm]] + default = 5 + minimum = 0 + type = float + value = 5 + + [[auto_fit_points]] + default = 50 + minimum = 0 + type = integer + value = 50 + + [[auto_left_baseline]] + default = 20 + minimum = 0 + type = float + value = 20 + + [[auto_max_p]] + default = 10 + minimum = 0 + type = float + value = 10 + + [[auto_min_p]] + default = 0.005 + minimum = 0 + type = float + value = 0.005 + + [[auto_right_baseline]] + default = 20 + minimum = 0 + type = float + value = 20 + + [[auto_slope_span]] + default = 20 + minimum = 0 + type = integer + value = 20 + + [[baseline_clicks]] + default = automatic + elements = contact point, automatic, 1 point, 2 points + type = enum + value = contact point + + [[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 + + [[peak_color]] + default = black + type = color + value = "(255,128,0)" + + [[peak_show]] + default = False + type = boolean + value = False + + [[peak_size]] + default = 20 + maximum = 10000 + minimum = 1 + type = integer + value = 50 + + [[persistence_length]] + default = 0.35e-9 + minimum = 0 + type = float + value = 0.175 + + [[rebase]] + 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/plugins/autopeak.py b/plugins/autopeak.py new file mode 100644 index 0000000..6e6d5a2 --- /dev/null +++ b/plugins/autopeak.py @@ -0,0 +1,358 @@ +#!/usr/bin/env python + +''' +autopeak.py + +Automatic peak detection and analysis. + +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 mean, RankWarning + +import warnings +warnings.simplefilter('ignore', RankWarning) + +#import config +import lib.plugin +import lib.results + +class autopeakCommands: + ''' + Autopeak carries out force curve fitting with a chosen model: + - WLC + - FJC + - FJC-PEG + ''' + + def do_autopeak(self, plot=None): + ''' + AUTOPEAK + (autopeak.py) + Automatically performs a number of analyses on the peaks of the given curve. + Currently it automatically: + - fits peaks with WLC or FJC function (depending on how the fit_function variable is set) + - measures peak maximum forces with a baseline + - measures slope in proximity of peak maximum + Requires flatten plotmanipulator , fit.py plugin , flatfilts.py plugin with convfilt + + Syntax: + autopeak [rebase] [persistence_length=value] [t=value] [noauto] [reclick] + + rebase : Re-asks baseline interval + + persistence_length=[value] : Use a fixed persistent length for the fit. If persistence_length is not given, + the fit will be a 2-variable + fit. DO NOT put spaces between 'persistence_length', '=' and the value. + The value must be in nanometers. + Scientific notation like 0.35 is fine. + + t=[value] : Use a user-defined temperature. The value must be in + kelvins; by default it is 293 K. + DO NOT put spaces between 't', '=' and the value. + + noauto : allows for clicking the contact point by + hand (otherwise it is automatically estimated) the first time. + If subsequent measurements are made, the same contact point + clicked the first time is used + + reclick : redefines by hand the contact point, if noauto has been used before + but the user is unsatisfied of the previously choosen contact point. + + usepoints : fit interval by number of points instead than by nanometers + + noflatten : does not use the "flatten" plot manipulator + + When you first issue the command, it will ask for the filename. If you are giving the filename + of an existing file, autopeak will resume it and append measurements to it. If you are giving + a new filename, it will create the file and append to it until you close Hooke. + + + Useful variables (to set with SET command): + --- + fit_function = type of function to use for elasticity. If "wlc" worm-like chain is used, if "fjc" freely jointed + chain is used + + temperature= temperature of the system for wlc/fjc fit (in K) + + auto_slope_span = number of points on which measure the slope, for slope + + auto_fit_nm = number of nm to fit before the peak maximum, for WLC/FJC (if usepoints false) + auto_fit_points = number of points to fit before the peak maximum, for WLC/FJC (if usepoints true) + + baseline_clicks = contact point: no baseline, f=0 at the contact point (whether hand-picked or automatically found) + automatic: automatic baseline + 1 point: decide baseline with a single click and length defined in auto_left_baseline + 2 points: let user click points of baseline + auto_left_baseline = length in nm to use as baseline from the right point (if baseline_clicks = automatic , 1 point) + auto_right_baseline = distance in nm of peak-most baseline point from last peak (if baseline_clicks = automatic) + + auto_min_p ; auto_max_p = Minimum and maximum persistence length (if using WLC) or Kuhn length (if using FJC) + outside of which the peak is automatically discarded (in nm) + ''' + + #default fit etc. variables + auto_fit_nm = self.GetFloatFromConfig('autopeak', 'auto_fit_nm') + auto_left_baseline = self.GetFloatFromConfig('autopeak', 'auto_left_baseline') + auto_max_p = self.GetFloatFromConfig('autopeak', 'auto_max_p') + auto_min_p = self.GetFloatFromConfig('autopeak', 'auto_min_p') + auto_right_baseline = self.GetFloatFromConfig('autopeak', 'auto_right_baseline') + baseline_clicks = self.GetStringFromConfig('autopeak', 'baseline_clicks') + fit_function = self.GetStringFromConfig('autopeak', 'fit_function') + fit_points = self.GetIntFromConfig('autopeak', 'auto_fit_points') + noauto = self.GetBoolFromConfig('autopeak', 'noauto') + #noflatten: if true we do not flatten the curve + noflatten = self.GetBoolFromConfig('autopeak', 'noflatten') + peak_show = self.GetBoolFromConfig('autopeak', 'peak_show') + persistence_length = self.GetFloatFromConfig('autopeak', 'persistence_length') + #rebase: redefine the baseline + rebase = self.GetBoolFromConfig('autopeak', 'rebase') + reclick = self.GetBoolFromConfig('autopeak', 'reclick') + slope_span = self.GetIntFromConfig('autopeak', 'auto_slope_span') + T = self.GetFloatFromConfig('autopeak', 'temperature') + usepl = self.GetBoolFromConfig('autopeak', 'usepl') + if not usepl: + pl_value = None + else: + pl_value = persistence_length / 10**9 + usepoints = self.GetBoolFromConfig('autopeak', 'usepoints') + whatset_str = self.GetStringFromConfig('autopeak', 'whatset') + if whatset_str == 'extension': + whatset = lh.EXTENSION + if whatset_str == 'retraction': + whatset = lh.RETRACTION + + #TODO: should this be variable? + delta_force = 10 + + #setup header column labels for results + if fit_function == 'wlc': + fit_results = lib.results.ResultsWLC() + segment_str = 'Persistence length' + sigma_segment_str = 'sigma persistence length' + elif fit_function == 'fjc' or fit_function == 'fjcPEG': + fit_results = lib.results.ResultsFJC() + segment_str = 'Kuhn length' + sigma_segment_str = 'sigma Kuhn length' + else: + self.AppendToOutput('Unknown fit function, Please set fit_function as wlc, fjc or fjcPEG') + return + + #initialize output data vectors + c_lengths = [] + p_lengths = [] + sigma_c_lengths = [] + sigma_p_lengths = [] + forces = [] + slopes = [] + + #pick up plot + if plot is None: + plot = copy.deepcopy(self.GetActivePlot()) + filename = self.GetActiveFile().name + + #apply all active plotmanipulators and add the 'manipulated' data + for plotmanipulator in self.plotmanipulators: + if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name): + if plotmanipulator.name == 'flatten': + if not noflatten: + plot = plotmanipulator.method(plot, self.GetActiveFile()) + else: + plot = plotmanipulator.method(plot, self.GetActiveFile()) + + #--Using points instead of nm interval + if not usepoints: + fit_points = None + + #--Contact point arguments + if reclick: + contact_point, contact_point_index = self.pickup_contact_point(filename=filename) + elif noauto: + if self.wlccontact_index is None or self.wlccurrent != filename: + contact_point, contact_point_index = self.pickup_contact_point(filename=filename) + else: + contact_point = self.wlccontact_point + contact_point_index = self.wlccontact_index + else: + #Automatically find contact point + cindex = self.find_contact_point(plot) + contact_point = self._clickize(plot.curves[lh.RETRACTION].x, plot.curves[lh.EXTENSION].y, cindex) + + #peak_size comes from convolution curve + peak_location, peak_size = self.find_current_peaks(plot=plot, noflatten=noflatten) + + if len(peak_location) == 0: + self.AppendToOutput('No peaks to fit.') + return + + #Pick up force baseline + if baseline_clicks == 'contact point': + try: + avg = plot.curves[lh.RETRACTION].y[contact_point_index] + except: + avg = plot.curves[lh.RETRACTION].y[cindex] + + if rebase or (self.basecurrent != filename) or self.basepoints is None: + if baseline_clicks == 'automatic': + self.basepoints = [] + base_index_0 = peak_location[-1] + self.fit_interval_nm(peak_location[-1], plot.curves[lh.RETRACTION].x, auto_right_baseline, False) + self.basepoints.append(self._clickize(plot.curves[lh.RETRACTION].x, plot.curves[lh.RETRACTION].y, base_index_0)) + base_index_1 = self.basepoints[0].index + self.fit_interval_nm(self.basepoints[0].index, plot.curves[lh.RETRACTION].x, auto_left_baseline, False) + self.basepoints.append(self._clickize(plot.curves[lh.RETRACTION].x, plot.curves[lh.RETRACTION].y, base_index_1)) + if baseline_clicks == '1 point': + self.basepoints=self._measure_N_points(N=1, message='Click on 1 point to select the baseline.', whatset=whatset) + base_index_1 = self.basepoints[0].index + self.fit_interval_nm(self.basepoints[0].index, plot.curves[lh.RETRACTION].x, auto_left_baseline, False) + self.basepoints.append(self._clickize(plot.curves[lh.RETRACTION].x, plot.curves[lh.RETRACTION].y, base_index_1)) + if baseline_clicks == '2 points': + self.basepoints=self._measure_N_points(N=2, message='Click on 2 points to select the baseline.', whatset=whatset) + if baseline_clicks != 'contact point': + boundaries=[self.basepoints[0].index, self.basepoints[1].index] + boundaries.sort() + to_average = plot.curves[lh.RETRACTION].y[boundaries[0]:boundaries[1]] #y points to average + avg = mean(to_average) + self.basecurrent = filename + + x_values = plot.curves[lh.RETRACTION].x + y_values = plot.curves[lh.RETRACTION].y + for index, peak in enumerate(peak_location): + #WLC FITTING + #define fit interval + if not usepoints: + fit_points = self.fit_interval_nm(peak, plot.curves[lh.RETRACTION].x, auto_fit_nm, True) + peak_point = self._clickize(x_values, y_values, peak) + other_fit_point=self._clickize(x_values, y_values, peak - fit_points) + + #points for the fit + points = [contact_point, peak_point, other_fit_point] + + if abs(peak_point.index - other_fit_point.index) < 2: + continue + + if fit_function == 'wlc': + params, yfit, xfit, fit_errors = self.wlc_fit(points, x_values, y_values, pl_value, T, return_errors=True) + elif fit_function == 'fjc': + params, yfit, xfit, fit_errors = self.fjc_fit(points, x_values, y_values, pl_value, T, return_errors=True) + elif fit_function == 'fjcPEG': + params, yfit, xfit, fit_errors = self.fjcPEG_fit(points, x_values, y_values, pl_value, T, return_errors=True) + + #Measure forces + delta_to_measure = y_values[peak - delta_force:peak + delta_force] + y = min(delta_to_measure) + #save force values (pN) + #Measure slopes + slope = self.linefit_between(peak - slope_span, peak, whatset=lh.RETRACTION)[0] + + #check fitted data and, if right, add peak to the measurement + fit_result = lib.results.Result() + + fit_result.result['Contour length'] = params[0] + fit_result.result['sigma contour length'] = fit_errors[0] + fit_result.result['Rupture force'] = abs(y - avg) + fit_result.result['Slope'] = slope + active_file = self.GetActiveFile() + if active_file.driver.retract_velocity: + fit_result.result['Loading rate'] = slope * active_file.driver.retract_velocity + else: + fit_result.result['Loading rate'] = -1 + if len(params) == 1: #if we did choose 1-value fit + fit_result.result[segment_str] = pl_value + fit_result.result[sigma_segment_str] = 0 + + p_lengths.append(pl_value) + c_lengths.append(params[0]*(1.0e+9)) + sigma_p_lengths.append(0) + sigma_c_lengths.append(fit_errors[0]*(1.0e+9)) + forces.append(abs(y-avg)*(1.0e+12)) + slopes.append(slope) + else: #2-value fit + p_leng = params[1] * (1.0e+9) + #check if persistence length makes sense, otherwise discard peak. + if p_leng > auto_min_p and p_leng < auto_max_p: + fit_result.result[segment_str] = params[1] + fit_result.result[sigma_segment_str] = fit_errors[1] + + p_lengths.append(p_leng) + c_lengths.append(params[0]*(1.0e+9)) + sigma_c_lengths.append(fit_errors[0]*(1.0e+9)) + sigma_p_lengths.append(fit_errors[1]*(1.0e+9)) + forces.append(abs(y-avg)*(1.0e+12)) + slopes.append(slope) + else: + fit_result.result = {} + + if len(fit_result.result) > 0: + fit_result.label = fit_function + '_' + str(index) + fit_result.title = plot.curves[lh.RETRACTION].title + fit_result.units.x = plot.curves[lh.RETRACTION].units.x + fit_result.units.y = plot.curves[lh.RETRACTION].units.y + fit_result.visible = True + fit_result.x = xfit + fit_result.y = yfit + fit_results.results.append(fit_result) + + if fit_results.results: + fit_results.set_multipliers(0) + plot = self.GetActivePlot() + plot.results[fit_function] = fit_results + if peak_show: + plugin = lib.plugin.Plugin() + plugin.name = 'autopeak' + plugin.section = 'autopeak' + plugin.prefix = 'peak_' + self.do_peaks(plugin=plugin, peak_location=peak_location, peak_size=peak_size) + else: + self.UpdatePlot() + + else: + self.AppendToOutput('No peaks found.') + + #TODO: + #self.do_note('autopeak') + + def find_current_peaks(self, plot=None, noflatten=True): + if not noflatten: + plot_temp = self.plotmanip_flatten(plot, self.GetActiveFile(), customvalue=1) + peak_location, peak_size = self.has_peaks(plot_temp) + return peak_location, peak_size + + def fit_interval_nm(self, 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 pickup_contact_point(self, filename=''): + ''' + macro to pick up the contact point by clicking + ''' + contact_point = self._measure_N_points(N=1, message='Please click on the contact point.')[0] + contact_point_index = contact_point.index + self.wlccontact_point = contact_point + self.wlccontact_index = contact_point.index + self.wlccurrent = filename + return contact_point, contact_point_index + + diff --git a/plugins/core.ini b/plugins/core.ini new file mode 100644 index 0000000..ac026ec --- /dev/null +++ b/plugins/core.ini @@ -0,0 +1,52 @@ +[plotmanipulators] + [[order]] + default = '"correct", "median", "absvalue", "flatten", "multiplier", "clamp", "threshold", "coincident", "showconvoluted"' + elements = correct, median, absvalue, flatten, multiplier, clamp, threshold, coincident, showconvoluted + #elements = correct, median, absvalue, flatten, multiplier, clamp, threshold, coincident, showconvoluted + type = arraystring + value = '"correct" "median" "absvalue" "flatten" "multiplier" "clamp" "threshold" "coincident" "showconvoluted"' + + [[absvalue]] + default = False + type = boolean + value = False + + [[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 diff --git a/plugins/export.ini b/plugins/export.ini new file mode 100644 index 0000000..da8db68 --- /dev/null +++ b/plugins/export.ini @@ -0,0 +1,60 @@ +[fits] + [[ext]] + default = txt + type = string + value = csv + + [[folder]] + default = "" + type = folder + value = R:\Programming\Python\Gui\data\export + + [[prefix]] + default = "" + type = string + value = "_fit " + + [[separator]] + default = "," + type = string + value = ", " + +[force_curve] + [[ext]] + default = txt + type = string + value = txt + + [[folder]] + default = "" + type = folder + value = R:\Programming\Python\Gui\data\export + + [[prefix]] + default = "" + type = string + value = _ + + [[separator]] + default = "," + type = string + value = ", " + +[overlay] + [[prefix]] + default = origin + type = string + value = _ + +[results] + [[filename]] + default = fit export.txt + type = filename + value = R:\Programming\Python\Gui\data\export\fit export.txt + + [[separator]] + default = "," + type = string + value = ", " + + diff --git a/plugins/export.py b/plugins/export.py new file mode 100644 index 0000000..a111c0d --- /dev/null +++ b/plugins/export.py @@ -0,0 +1,211 @@ +#!/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):#, ext='', folder='', prefix='', separator=''): + ''' + Exports all approach and retraction files in a playlist and + all fitting results (if available) in a columnar ASCII format. + Please make sure that the number of points in the fit is smaller + or equal to the number of points in the approach/retraction. + For the time being, exports only one set of results (e.g. WLC + or FJC, not both). + ''' + + 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() + + #TODO: fix for multiple results + #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() + plot = self.GetActivePlot() + extension = plot.curves[lh.EXTENSION] + retraction = plot.curves[lh.RETRACTION] + + output = [] + header_str = '' + for index, curve in enumerate(plot.curves): + 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) + #TODO: add units + 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_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, filename='', separator=''): + ''' + EXPORTFITS + Exports all visible fit results in a playlist into a delimited text file + ''' + 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]) + 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/plugins/fit.ini b/plugins/fit.ini new file mode 100644 index 0000000..34df5c1 --- /dev/null +++ b/plugins/fit.ini @@ -0,0 +1,35 @@ +[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/plugins/fit.py b/plugins/fit.py new file mode 100644 index 0000000..87caa4c --- /dev/null +++ b/plugins/fit.py @@ -0,0 +1,836 @@ +#!/usr/bin/env python + +''' +fit.py + +Force spectroscopy curves basic fitting plugin. + +Plugin dependencies: +procplots.py (plot processing plugin) + +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 math import exp +import numpy as np +import scipy.stats +import scipy.odr + +from lib.libhooke import coth + +class fitCommands(object): + + def _plug_init(self): + self.wlccurrent=None + self.wlccontact_point=None + self.wlccontact_index=None + + def wlc_fit(self, clicked_points, xvector, yvector, pl_value, T=293, return_errors=False): + ''' + Worm-like chain model fitting. + The function is the simple polynomial worm-like chain as proposed by C.Bustamante, J.F.Marko, E.D.Siggia + and S.Smith (Science. 1994 Sep 9;265(5178):1599-600.) + ''' + + ''' + clicked_points[0] = contact point (calculated or hand-clicked) + clicked_points[1] and [2] are edges of chunk + ''' + + #STEP 1: Prepare the vectors to apply the fit. + + if pl_value is not None: + pl_value=pl_value/(10**9) + + #indexes of the selected chunk + first_index=min(clicked_points[1].index, clicked_points[2].index) + last_index=max(clicked_points[1].index, clicked_points[2].index) + + #getting the chunk and reverting it + xchunk,ychunk=xvector[first_index:last_index],yvector[first_index:last_index] + xchunk.reverse() + ychunk.reverse() + #put contact point at zero and flip around the contact point (the fit wants a positive growth for extension and force) + xchunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xchunk] + ychunk_corr_up=[-(y-clicked_points[0].graph_coords[1]) for y in ychunk] + + #make them arrays + xchunk_corr_up=scipy.array(xchunk_corr_up) + ychunk_corr_up=scipy.array(ychunk_corr_up) + + #STEP 2: actually do the fit + + #Find furthest point of chunk and add it a bit; the fit must converge + #from an excess! + xchunk_high=max(xchunk_corr_up) + xchunk_high+=(xchunk_high/10) + + #Here are the linearized start parameters for the WLC. + #[lambd=1/Lo , pii=1/P] + + p0=[(1/xchunk_high),(1/(3.5e-10))] + p0_plfix=[(1/xchunk_high)] + ''' + ODR STUFF + fixme: remove these comments after testing + ''' + + def f_wlc(params,x,T=T): + ''' + wlc function for ODR fitting + ''' + lambd,pii=params + Kb=(1.38065e-23) + therm=Kb*T + y=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd)) + return y + + def f_wlc_plfix(params,x,pl_value=pl_value,T=T): + ''' + wlc function for ODR fitting + ''' + lambd=params + pii=1/pl_value + Kb=(1.38065e-23) + therm=Kb*T + y=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd)) + return y + + #make the ODR fit + realdata=scipy.odr.RealData(xchunk_corr_up,ychunk_corr_up) + + if pl_value: + model=scipy.odr.Model(f_wlc_plfix) + o = scipy.odr.ODR(realdata, model, p0_plfix) + else: + model=scipy.odr.Model(f_wlc) + o = scipy.odr.ODR(realdata, model, p0) + + o.set_job(fit_type=2) + out=o.run() + fit_out=[(1/i) for i in out.beta] + + #Calculate fit errors from output standard deviations. + #We must propagate the error because we fit the *inverse* parameters! + #The error = (error of the inverse)*(value**2) + fit_errors=[] + for sd,value in zip(out.sd_beta, fit_out): + err_real=sd*(value**2) + fit_errors.append(err_real) + + def wlc_eval(x, params, pl_value, T): + ''' + Evaluates the WLC function + ''' + if not pl_value: + lambd, pii = params + else: + lambd = params + + if pl_value: + pii=1/pl_value + + Kb=(1.38065e-23) #Boltzmann constant + therm=Kb*T #so we have thermal energy + + return ((therm*pii / 4.0) * (((1 - (x*lambd))**-2.0) - 1 + (4.0*x*lambd)) ) + + #STEP 3: plotting the fit + + #obtain domain to plot the fit - from contact point to last_index plus 20 points + thule_index=last_index + 1 + if thule_index > len(xvector): #for rare cases in which we fit something at the END of whole curve. + thule_index = len(xvector) + #reverse etc. the domain + xfit_chunk=xvector[clicked_points[0].index:thule_index] + xfit_chunk.reverse() + xfit_chunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xfit_chunk] + xfit_chunk_corr_up=scipy.array(xfit_chunk_corr_up) + + #the fitted curve: reflip, re-uncorrect + #x_max = xfit_chunk_corr_up[-1] + #x_min = xfit_chunk_corr_up[0] + #points_in_fit = 50 + #increment = (x_max - x_min) / (points_in_fit - 1) + #x_help = [x_min + x * increment for x in range(points_in_fit)] + #xfit_chunk = [x_min + x * increment for x in range(points_in_fit)] + #xfit_chunk.reverse() + #x_help = scipy.array(x_help) + + #yfit = wlc_eval(x_help, out.beta, pl_value, T) + + yfit=wlc_eval(xfit_chunk_corr_up, out.beta, pl_value,T) + yfit_down=[-y for y in yfit] + yfit_corr_down=[y+clicked_points[0].graph_coords[1] for y in yfit_down] + + plot = self.GetActivePlot() + + if return_errors: + return fit_out, yfit_corr_down, xfit_chunk, fit_errors + else: + return fit_out, yfit_corr_down, xfit_chunk, None + + def fjc_fit(self, clicked_points, xvector, yvector, pl_value, T=293, return_errors=False): + ''' + Freely-jointed chain function + ref: C.Ray and B.B. Akhremitchev; http://www.chem.duke.edu/~boris/research/force_spectroscopy/fit_efjc.pdf + ''' + + ''' + clicked_points[0] = contact point (calculated or hand-clicked) + clicked_points[1] and [2] are edges of chunk + ''' + + #STEP 1: Prepare the vectors to apply the fit. + if pl_value is not None: + pl_value=pl_value/(10**9) + + #indexes of the selected chunk + first_index=min(clicked_points[1].index, clicked_points[2].index) + last_index=max(clicked_points[1].index, clicked_points[2].index) + + #getting the chunk and reverting it + xchunk,ychunk=xvector[first_index:last_index],yvector[first_index:last_index] + xchunk.reverse() + ychunk.reverse() + #put contact point at zero and flip around the contact point (the fit wants a positive growth for extension and force) + xchunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xchunk] + ychunk_corr_up=[-(y-clicked_points[0].graph_coords[1]) for y in ychunk] + + #make them arrays + xchunk_corr_up=scipy.array(xchunk_corr_up) + ychunk_corr_up=scipy.array(ychunk_corr_up) + + + #STEP 2: actually do the fit + + #Find furthest point of chunk and add it a bit; the fit must converge + #from an excess! + xchunk_high=max(xchunk_corr_up) + xchunk_high+=(xchunk_high/10) + + #Here are the linearized start parameters for the WLC. + #[lambd=1/Lo , pii=1/P] + + p0=[(1/xchunk_high),(1/(3.5e-10))] + p0_plfix=[(1/xchunk_high)] + ''' + ODR STUFF + fixme: remove these comments after testing + ''' + def x_fjc(params,f,T=T): + ''' + fjc function for ODR fitting + ''' + lambd,pii=params + Kb=(1.38065e-23) + therm=Kb*T + + #x=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd)) + x=(1/lambd)*(coth(f*(1/pii)/therm) - (therm*pii)/f) + return x + + def x_fjc_plfix(params,f,pl_value=pl_value,T=T): + ''' + fjc function for ODR fitting + ''' + lambd=params + pii=1/pl_value + Kb=(1.38065e-23) + therm=Kb*T + #y=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd)) + x=(1/lambd)*(coth(f*(1/pii)/therm) - (therm*pii)/f) + return x + + #make the ODR fit + realdata=scipy.odr.RealData(ychunk_corr_up,xchunk_corr_up) + if pl_value: + model=scipy.odr.Model(x_fjc_plfix) + o = scipy.odr.ODR(realdata, model, p0_plfix) + else: + model=scipy.odr.Model(x_fjc) + o = scipy.odr.ODR(realdata, model, p0) + + o.set_job(fit_type=2) + out=o.run() + fit_out=[(1/i) for i in out.beta] + + #Calculate fit errors from output standard deviations. + #We must propagate the error because we fit the *inverse* parameters! + #The error = (error of the inverse)*(value**2) + fit_errors=[] + for sd,value in zip(out.sd_beta, fit_out): + err_real=sd*(value**2) + fit_errors.append(err_real) + + def fjc_eval(y, params, pl_value, T): + ''' + Evaluates the FJC function + ''' + if not pl_value: + lambd, pii = params + else: + lambd = params + + if pl_value: + pii = 1/pl_value + + Kb = (1.38065e-23) #Boltzmann constant + therm = Kb * T #so we have thermal energy + #return ( (therm*pii/4.0) * (((1-(x*lambd))**-2.0) - 1 + (4.0*x*lambd)) ) + return (1 / lambd) * (coth(y * (1 / pii) / therm) - (therm * pii) / y) + + + #STEP 3: plotting the fit + + #obtain domain to plot the fit - from contact point to last_index plus 20 points + thule_index=last_index+10 + if thule_index > len(xvector): #for rare cases in which we fit something at the END of whole curve. + thule_index = len(xvector) + #reverse etc. the domain + ychunk=yvector[clicked_points[0].index:thule_index] + + if len(ychunk)>0: + y_evalchunk=np.linspace(min(ychunk),max(ychunk),100) + else: + #Empty y-chunk. It happens whenever we set the contact point after a recognized peak, + #or other buggy situations. Kludge to live with it now... + ychunk=yvector[:thule_index] + y_evalchunk=np.linspace(min(ychunk),max(ychunk),100) + + yfit_down=[-y for y in y_evalchunk] + yfit_corr_down=[y+clicked_points[0].graph_coords[1] for y in yfit_down] + yfit_corr_down=scipy.array(yfit_corr_down) + + #the fitted curve: reflip, re-uncorrect + xfit=fjc_eval(yfit_corr_down, out.beta, pl_value,T) + xfit=list(xfit) + xfit.reverse() + xfit_chunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xfit] + + #xfit_chunk_corr_up=scipy.array(xfit_chunk_corr_up) + #deltay=yfit_down[0]-yvector[clicked_points[0].index] + + #This is a terrible, terrible kludge to find the point where it should normalize (and from where it should plot) + xxxdists=[] + for index in scipy.arange(1,len(xfit_chunk_corr_up),1): + xxxdists.append((clicked_points[0].graph_coords[0]-xfit_chunk_corr_up[index])**2) + normalize_index=xxxdists.index(min(xxxdists)) + #End of kludge + + deltay=yfit_down[normalize_index]-clicked_points[0].graph_coords[1] + yfit_corr_down=[y-deltay for y in yfit_down] + + if return_errors: + #return fit_out, yfit_corr_down, xfit_chunk_corr_up, fit_errors + return fit_out, yfit_corr_down[normalize_index+1:], xfit_chunk_corr_up[normalize_index+1:], fit_errors + else: + #return fit_out, yfit_corr_down, xfit_chunk_corr_up, None + return fit_out, yfit_corr_down[normalize_index+1:], xfit_chunk_corr_up[normalize_index+1:], None + + def fjcPEG_fit(self, clicked_points, xvector, yvector, pl_value, T=293, return_errors=False): + ''' + Freely-jointed chain function for PEG-containing molecules + ref: C.Ray and B.B. Akhremitchev; http://www.chem.duke.edu/~boris/research/force_spectroscopy/fit_efjc.pdf + ''' + + ''' + clicked_points[0] = contact point (calculated or hand-clicked) + clicked_points[1] and [2] are edges of chunk + ''' + + #STEP 1: Prepare the vectors to apply the fit. + if pl_value is not None: + pl_value=pl_value/(10**9) + + #indexes of the selected chunk + first_index=min(clicked_points[1].index, clicked_points[2].index) + last_index=max(clicked_points[1].index, clicked_points[2].index) + + #getting the chunk and reverting it + xchunk,ychunk=xvector[first_index:last_index],yvector[first_index:last_index] + xchunk.reverse() + ychunk.reverse() + #put contact point at zero and flip around the contact point (the fit wants a positive growth for extension and force) + xchunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xchunk] + ychunk_corr_up=[-(y-clicked_points[0].graph_coords[1]) for y in ychunk] + + #make them arrays + xchunk_corr_up=scipy.array(xchunk_corr_up) + ychunk_corr_up=scipy.array(ychunk_corr_up) + + #STEP 2: actually do the fit + + #Find furthest point of chunk and add it a bit; the fit must converge + #from an excess! + xchunk_high=max(xchunk_corr_up) + xchunk_high+=(xchunk_high/10) + + #Here are the linearized start parameters for the WLC. + #[lambd=1/Lo , pii=1/P] + + L_helical = self.GetFloatFromConfig('fit', 'fjcPEG', 'L_helical') #in nm + L_planar = self.GetFloatFromConfig('fit', 'fjcPEG', 'L_planar') #in nm + delta_G = self.GetFloatFromConfig('fit', 'fjcPEG', 'delta_G') #in Kb*T + + p0=[(1/xchunk_high),(1/(3.5e-10))] + p0_plfix=[(1/xchunk_high)] + ''' + ODR STUFF + fixme: remove these comments after testing + ''' + def x_fjcPEG(params,f,T=T): + ''' + fjcPEG function for ODR fitting + ''' + lambd,pii=params + Kb=(1.38065e-23) + therm=Kb*T + + #x=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd)) + + x=(1/lambd)*(1 / (exp(delta_G) + 1) + (L_helical/L_planar) * (1 / (exp(-delta_G) + 1))) * (coth(f*(1/pii)/therm) - (therm*pii)/f) + return x + + def x_fjcPEG_plfix(params,f,pl_value=pl_value,T=T): + ''' + fjcPEG function for ODR fitting + ''' + lambd=params + pii=1/pl_value + Kb=(1.38065e-23) + therm=Kb*T + #y=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd)) + x=(1/lambd)*(1 / (exp(delta_G) + 1) + (L_helical/L_planar) * (1 / (exp(-delta_G) + 1))) * (coth(f*(1/pii)/therm) - (therm*pii)/f) + return x + + #make the ODR fit + realdata=scipy.odr.RealData(ychunk_corr_up,xchunk_corr_up) + if pl_value: + model=scipy.odr.Model(x_fjcPEG_plfix) + o = scipy.odr.ODR(realdata, model, p0_plfix) + else: + model=scipy.odr.Model(x_fjcPEG) + o = scipy.odr.ODR(realdata, model, p0) + + o.set_job(fit_type=2) + out=o.run() + fit_out=[(1/i) for i in out.beta] + + #Calculate fit errors from output standard deviations. + #We must propagate the error because we fit the *inverse* parameters! + #The error = (error of the inverse)*(value**2) + fit_errors=[] + for sd,value in zip(out.sd_beta, fit_out): + err_real=sd*(value**2) + fit_errors.append(err_real) + + def fjcPEG_eval(y, params, pl_value, T): + ''' + Evaluates the fjcPEG function + ''' + if not pl_value: + lambd, pii = params + else: + lambd = params + + if pl_value: + pii = 1/pl_value + + Kb = (1.38065e-23) #Boltzmann constant + therm = Kb * T #so we have thermal energy + #return ( (therm*pii/4.0) * (((1-(x*lambd))**-2.0) - 1 + (4.0*x*lambd)) ) + return (1/lambd)*(1 / (exp(delta_G) + 1) + (L_helical/L_planar) * (1 / (exp(-delta_G) + 1))) * (coth(y*(1/pii)/therm) - (therm*pii)/y) + + #STEP 3: plotting the fit + + #obtain domain to plot the fit - from contact point to last_index plus 20 points + thule_index=last_index+10 + if thule_index > len(xvector): #for rare cases in which we fit something at the END of whole curve. + thule_index = len(xvector) + #reverse etc. the domain + ychunk=yvector[clicked_points[0].index:thule_index] + + if len(ychunk)>0: + y_evalchunk=np.linspace(min(ychunk),max(ychunk),100) + else: + #Empty y-chunk. It happens whenever we set the contact point after a recognized peak, + #or other buggy situations. Kludge to live with it now... + ychunk=yvector[:thule_index] + y_evalchunk=np.linspace(min(ychunk),max(ychunk),100) + + yfit_down=[-y for y in y_evalchunk] + yfit_corr_down=[y+clicked_points[0].graph_coords[1] for y in yfit_down] + yfit_corr_down=scipy.array(yfit_corr_down) + + #the fitted curve: reflip, re-uncorrect + xfit=fjcPEG_eval(yfit_corr_down, out.beta, pl_value,T) + xfit=list(xfit) + xfit.reverse() + xfit_chunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xfit] + + #xfit_chunk_corr_up=scipy.array(xfit_chunk_corr_up) + #deltay=yfit_down[0]-yvector[clicked_points[0].index] + + #This is a terrible, terrible kludge to find the point where it should normalize (and from where it should plot) + xxxdists=[] + for index in scipy.arange(1,len(xfit_chunk_corr_up),1): + xxxdists.append((clicked_points[0].graph_coords[0]-xfit_chunk_corr_up[index])**2) + normalize_index=xxxdists.index(min(xxxdists)) + #End of kludge + + deltay=yfit_down[normalize_index]-clicked_points[0].graph_coords[1] + yfit_corr_down=[y-deltay for y in yfit_down] + + if return_errors: + #return fit_out, yfit_corr_down, xfit_chunk_corr_up, fit_errors + return fit_out, yfit_corr_down[normalize_index+1:], xfit_chunk_corr_up[normalize_index+1:], fit_errors + else: + #return fit_out, yfit_corr_down, xfit_chunk_corr_up, None + return fit_out, yfit_corr_down[normalize_index+1:], xfit_chunk_corr_up[normalize_index+1:], None + + def do_wlc(self,args): + ''' + WLC + (fit.py plugin) + + See the fit command + ''' + self.fit(args) + + def do_fjc(self,args): + ''' + FJC + (fit.py plugin) + + See the fit command + ''' + self.fit(args) + + def do_fjcPEG(self,args): + ''' + fjcPEG + (fit.py plugin) + + default values for PEG + ---------------------- + delta_G: 3 Kb*T + L_helical: 0.28 nm + L_planar: 0.358 nm + + See the fit command for more information + ''' + self.fit(args) + + def fit(self,args): + ''' + FIT + (fit.py plugin) + Fits an entropic elasticity function to a given chunk of the curve. + + First you have to click a contact point. + Then you have to click the two edges of the data you want to fit. + + The fit function depends on the fit_function variable. You can set it with the command + "set fit_function wlc" or "set fit_function fjc" depending on the function you prefer. + + For WLC, the function is the simple polynomial worm-like chain as proposed by + C.Bustamante, J.F.Marko, E.D.Siggia and S.Smith (Science. 1994 + Sep 9;265(5178):1599-600.) + + For FJC, ref: + C.Ray and B.B. Akhremitchev; http://www.chem.duke.edu/~boris/research/force_spectroscopy/fit_efjc.pdf + + Arguments: + pl=[value] : Use a fixed persistent length (WLC) or Kuhn length (FJC) for the fit. If pl is not given, + the fit will be a 2-variable + fit. DO NOT put spaces between 'pl', '=' and the value. + The value must be in nanometers. + + t=[value] : Use a user-defined temperature. The value must be in + kelvins; by default it is 293 K. + DO NOT put spaces between 't', '=' and the value. + + noauto : allows for clicking the contact point by + hand (otherwise it is automatically estimated) the first time. + If subsequent measurements are made, the same contact point + clicked is used + + reclick : redefines by hand the contact point, if noauto has been used before + but the user is unsatisfied of the previously choosen contact point. + --------- + Syntax: fit [pl=(value)] [t=value] [noauto] + ''' + pl_value=None + T = self.config['fit'].as_float['temperature'] + for arg in args.split(): + #look for a persistent length argument. + if 'pl=' in arg: + pl_expression=arg.split('=') + pl_value=float(pl_expression[1]) #actual value + #look for a T argument. FIXME: spaces are not allowed between 'pl' and value + if ('t=' in arg[0:2]) or ('T=' in arg[0:2]): + t_expression=arg.split('=') + T=float(t_expression[1]) + + #use the currently displayed plot for the fit + displayed_plot=self._get_displayed_plot() + + #handle contact point arguments correctly + if 'reclick' in args.split(): + print 'Click contact point' + contact_point=self._measure_N_points(N=1, whatset=1)[0] + contact_point_index=contact_point.index + self.wlccontact_point=contact_point + self.wlccontact_index=contact_point.index + self.wlccurrent=self.current.path + elif 'noauto' in args.split(): + if self.wlccontact_index is None or self.wlccurrent != self.current.path: + print 'Click contact point' + contact_point=self._measure_N_points(N=1, whatset=1)[0] + contact_point_index=contact_point.index + self.wlccontact_point=contact_point + self.wlccontact_index=contact_point.index + self.wlccurrent=self.current.path + else: + contact_point=self.wlccontact_point + contact_point_index=self.wlccontact_index + else: + cindex=self.find_contact_point() + contact_point=lh.ClickedPoint() + contact_point.absolute_coords=displayed_plot.vectors[1][0][cindex], displayed_plot.vectors[1][1][cindex] + contact_point.find_graph_coords(displayed_plot.vectors[1][0], displayed_plot.vectors[1][1]) + contact_point.is_marker=True + + print 'Click edges of chunk' + points=self._measure_N_points(N=2, whatset=1) + points=[contact_point]+points + try: + if self.config['fit_function']=='wlc': + params, yfit, xfit, fit_errors = self.wlc_fit(points, displayed_plot.vectors[1][0], displayed_plot.vectors[1][1],pl_value,T, return_errors=True ) + name_of_charlength='Persistent length' + elif self.config['fit_function']=='fjc': + params, yfit, xfit, fit_errors = self.fjc_fit(points, displayed_plot.vectors[1][0], displayed_plot.vectors[1][1],pl_value,T, return_errors=True ) + name_of_charlength='Kuhn length' + elif self.config['fit_function']=='fjcPEG': + params, yfit, xfit, fit_errors = self.fjcPEG_fit(points, displayed_plot.vectors[1][0], displayed_plot.vectors[1][1],pl_value,T, return_errors=True ) + name_of_charlength='Kuhn length' + else: + print 'No recognized fit function defined!' + print 'Set your fit function to wlc or fjc.' + return + + except: + print 'Fit not possible. Probably wrong interval -did you click two *different* points?' + return + + #FIXME: print "Kuhn length" for FJC + print 'Fit function:',self.config['fit_function'] + print 'Contour length: ',params[0]*(1.0e+9),' nm' + to_dump='contour '+self.current.path+' '+str(params[0]*(1.0e+9))+' nm' + self.outlet.push(to_dump) + if len(params)==2: #if we did choose 2-value fit + print name_of_charlength+': ',params[1]*(1.0e+9),' nm' + to_dump='persistent '+self.current.path+' '+str(params[1]*(1.0e+9))+' nm' + self.outlet.push(to_dump) + + if fit_errors: + fit_nm=[i*(10**9) for i in fit_errors] + print 'Standard deviation (contour length)', fit_nm[0] + if len(fit_nm)>1: + print 'Standard deviation ('+name_of_charlength+')', fit_nm[1] + + #add the clicked points in the final PlotObject + clickvector_x, clickvector_y=[], [] + for item in points: + clickvector_x.append(item.graph_coords[0]) + clickvector_y.append(item.graph_coords[1]) + + #create a custom PlotObject to gracefully plot the fit along the curves + + fitplot=copy.deepcopy(displayed_plot) + fitplot.add_set(xfit,yfit) + fitplot.add_set(clickvector_x,clickvector_y) + + #FIXME: this colour/styles stuff must be solved at the root! + if fitplot.styles==[]: + fitplot.styles=[None,None,None,'scatter'] + else: + fitplot.styles+=[None,'scatter'] + + if fitplot.colors==[]: + fitplot.colors=[None,None,None,None] + else: + fitplot.colors+=[None,None] + + self._send_plot([fitplot]) + + #TODO: remove 'plot' as parameter + def find_contact_point(self, plot=None): + ''' + Finds the contact point on the curve. + + The current algorithm (thanks to Francesco Musiani, francesco.musiani@unibo.it and Massimo Sandal) is: + - take care of the PicoForce trigger bug - exclude retraction portions with too high standard deviation + - fit the second half of the retraction curve to a line + - if the fit is not almost horizontal, take a smaller chunk and repeat + - otherwise, we have something horizontal + - so take the average of horizontal points and use it as a baseline + + Then, start from the rise of the retraction curve and look at the first point below the + baseline. + + FIXME: should be moved, probably to generalvclamp.py + ''' + + #TODO: pickup current curve? + if plot is None: + return + + extension = copy.deepcopy(plot.curves[lh.EXTENSION]) + retraction = copy.deepcopy(plot.curves[lh.RETRACTION]) + yext = extension.y + yret = retraction.y + extension, retraction = self.subtract_curves(extension, retraction) + xret = retraction.x + ydiff = retraction.y + #taking care of the picoforce trigger bug: we exclude portions of the curve that have too much + #standard deviation. yes, a lot of magic is here. + monster=True + monlength=len(xret)-int(len(xret)/20) + finalength=len(xret) + while monster: + monchunk=scipy.array(ydiff[monlength:finalength]) + #TODO: there is a difference between scipy.stats.std and numpy.std: why? + #if abs(scipy.stats.std(monchunk)) < 2e-10: + if abs(np.std(monchunk)) < 2e-10: + monster=False + else: #move away from the monster + monlength-=int(len(xret)/50) + finalength-=int(len(xret)/50) + #take half of the thing + endlength=int(len(xret)/2) + ok=False + while not ok: + xchunk=yext[endlength:monlength] + ychunk=yext[endlength:monlength] + regr=scipy.stats.linregress(xchunk,ychunk)[0:2] + #we stop if we found an almost-horizontal fit or if we're going too short... + #FIXME: 0.1 and 6 here are "magic numbers" (although reasonable) + if (abs(regr[1]) > 0.1) and ( endlength < len(xret)-int(len(xret)/6) ) : + endlength+=10 + else: + ok=True + #ymean=scipy.mean(ychunk) #baseline + ymean = np.mean(ychunk) #baseline + + #TODO: check if this works rather than the thing below + #for index, point in enumerate(yret): + #if point < ymean: + #result = index + #result = -1 + + #find the first point below the calculated baseline + index=0 + point = ymean+1 + while point > ymean: + try: + point=yret[index] + index+=1 + except IndexError: + #The algorithm didn't find anything below the baseline! It should NEVER happen + index=0 + return index + + return index + + def find_contact_point2(self, debug=False): + ''' + TO BE DEVELOPED IN THE FUTURE + Finds the contact point on the curve. + + FIXME: should be moved, probably to generalvclamp.py + ''' + + #raw_plot=self.current.curve.default_plots()[0] + raw_plot=self.plots[0] + '''xext=self.plots[0].vectors[0][0] + yext=self.plots[0].vectors[0][1] + xret2=self.plots[0].vectors[1][0] + yret=self.plots[0].vectors[1][1] + ''' + xext=raw_plot.vectors[0][0] + yext=raw_plot.vectors[0][1] + xret2=raw_plot.vectors[1][0] + yret=raw_plot.vectors[1][1] + + first_point=[xext[0], yext[0]] + last_point=[xext[-1], yext[-1]] + + #regr=scipy.polyfit(first_point, last_point,1)[0:2] + diffx=abs(first_point[0]-last_point[0]) + diffy=abs(first_point[1]-last_point[1]) + + #using polyfit results in numerical errors. good old algebra. + a=diffy/diffx + b=first_point[1]-(a*first_point[0]) + baseline=scipy.polyval((a,b), xext) + + ysub=[item-basitem for item,basitem in zip(yext,baseline)] + + contact=ysub.index(min(ysub)) + + return xext,ysub,contact + + #now, exploit a ClickedPoint instance to calculate index... + dummy=lh.ClickedPoint() + dummy.absolute_coords=(x_intercept,y_intercept) + dummy.find_graph_coords(xret2,yret) + + if debug: + return dummy.index, regr, regr_contact + else: + return dummy.index + + + + #def x_do_contact(self,args): + #''' + #DEBUG COMMAND to be activated in the future + #''' + #xext,ysub,contact=self.find_contact_point2(debug=True) + + #contact_plot=self.plots[0] + #contact_plot.add_set(xext,ysub) + #contact_plot.add_set([xext[contact]],[self.plots[0].vectors[0][1][contact]]) + ##contact_plot.add_set([first_point[0]],[first_point[1]]) + ##contact_plot.add_set([last_point[0]],[last_point[1]]) + #contact_plot.styles=[None,None,None,'scatter'] + #self._send_plot([contact_plot]) + #return + + #index,regr,regr_contact=self.find_contact_point2(debug=True) + #print regr + #print regr_contact + #raw_plot=self.current.curve.default_plots()[0] + #xret=raw_plot.vectors[0][0] + ##nc_line=[(item*regr[0])+regr[1] for item in x_nc] + #nc_line=scipy.polyval(regr,xret) + #c_line=scipy.polyval(regr_contact,xret) + + + #contact_plot=self.current.curve.default_plots()[0] + #contact_plot.add_set(xret, nc_line) + #contact_plot.add_set(xret, c_line) + #contact_plot.styles=[None,None,None,None] + ##contact_plot.styles.append(None) + #contact_plot.destination=1 + #self._send_plot([contact_plot]) diff --git a/plugins/flatfilts.ini b/plugins/flatfilts.ini new file mode 100644 index 0000000..3a6f604 --- /dev/null +++ b/plugins/flatfilts.ini @@ -0,0 +1,96 @@ +[convfilt] + [[blindwindow]] + default = 20 + maximum = 10000 + minimum = 0 + type = float + value = 20 + + [[convolution]] + default = "[6.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]" + type = string + #value = '[6.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]' + value = "[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 + visible = False + + [[medianfilter]] + default = 7 + maximum = 100 + minimum = 0 + type = integer + value = 2 + + [[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] + #[[median_filter]] + #default = 7 + #maximum = 100 + #minimum = 0 + #type = integer + #value = 7 + + [[min_npks]] + default = 4 + maximum = 10000 + minimum = 1 + type = integer + value = 7 + + [[min_deviation]] + default = 9 + maximum = 100 + minimum = 1 + type = float + value = 10 + +[peaks] + [[color]] + default = black + type = color + value = "(255,0,128)" + + [[size]] + default = 20 + maximum = 10000 + minimum = 1 + type = integer + value = 50 diff --git a/plugins/flatfilts.py b/plugins/flatfilts.py new file mode 100644 index 0000000..9ce8a93 --- /dev/null +++ b/plugins/flatfilts.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python + +''' +flatfilts.py + +Force spectroscopy files filtering of flat files. + +Plugin dependencies: +procplots.py (plot processing plugin) + +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 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): + ''' + Finds peak position in a force curve. + FIXME: should be moved to peakspot.py + ''' + + 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') + + if plot is None: + plot = self.GetActivePlot() + + xret = plot.curves[lh.RETRACTION].x + yret = plot.curves[lh.RETRACTION].y + #Calculate convolution + convoluted = lps.conv_dx(yret, convolution) + + #surely cut everything before the contact point + cut_index = self.find_contact_point(plot) + #cut even more, before the blind window + start_x = xret[cut_index] + blind_index = 0 + for value in xret[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(yret[peak - 10:peak + 10]) + index_maxpk = yret[peak - 10:peak + 10].index(maxpk) + (peak - 10) + peak_location[i] = index_maxpk + + return peak_location, peak_size + + def exec_has_peaks(self, item): + ''' + encapsulates has_peaks for the purpose of correctly treating the curve objects in the convfilt loop, + to avoid memory leaks + ''' + #TODO: things have changed, check for the memory leak again + + if self.HasPlotmanipulator('plotmanip_flatten'): + #If flatten is present, use it for better recognition of peaks... + flat_plot = self.plotmanip_flatten(item.plot, item, customvalue=1) + + peak_location, peak_size = self.has_peaks(flat_plot) + + return peak_location, peak_size + + def do_peaks(self, plugin=None, peak_location=None, peak_size=None): + ''' + PEAKS + (flatfilts.py) + Test command for convolution filter / test. + ---- + 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: + xplotted_ret = plot.curves[lh.RETRACTION].x + yplotted_ret = plot.curves[lh.RETRACTION].y + xgood = [xplotted_ret[index] for index in peak_location] + ygood = [yplotted_ret[index] for index in peak_location] + + peaks = lib.curve.Curve() + peaks.color = color + peaks.size = size + peaks.style = 'scatter' + peaks.x = xgood + peaks.y = ygood + + plot.curves.append(peaks) + + self.UpdatePlot(plot) + + def do_convfilt(self): + ''' + CONVFILT + (flatfilts.py) + Filters out flat (featureless) files of the current playlist, + creating a playlist containing only the files with potential + features. + ------------ + Syntax: + convfilt [min_npks min_deviation] + + min_npks = minmum number of peaks + (to set the default, see convfilt.conf file; CONVCONF and SETCONF commands) + + min_deviation = minimum signal/noise ratio *in the convolution* + (to set the default, see convfilt.conf file; CONVCONF and SETCONF commands) + + If called without arguments, it uses default values. + ''' + + self.AppendToOutput('Processing playlist...') + self.AppendToOutput('(Please wait)') + 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) + peak_location, peak_size = self.exec_has_peaks(copy.deepcopy(current_file)) + 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.') + self.AppendToOutput('Try to enable it in the configuration file 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/plugins/generalvclamp.ini b/plugins/generalvclamp.ini new file mode 100644 index 0000000..3d930e9 --- /dev/null +++ b/plugins/generalvclamp.ini @@ -0,0 +1,67 @@ +[distance] + [[show_points]] + default = True + type = boolean + value = True + + [[whatset]] + default = retraction + elements = extension, retraction + type = enum + value = retraction + +[force] + [[whatset]] + default = retraction + elements = extension, retraction + type = enum + value = retraction + +[forcebase] + [[max]] + default = False + type = boolean + value = False + + [[rebase]] + default = False + type = boolean + value = False + + [[whatset]] + default = retraction + elements = extension, retraction + type = enum + value = retraction + + [[baseline]] + type = none + value = 1 + +[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 = 2 + +[slope] + [[fitspan]] + default = 0 + maximum = 10000 + minimum = 0 + type = integer + value = 60 diff --git a/plugins/generalvclamp.py b/plugins/generalvclamp.py new file mode 100644 index 0000000..9f97136 --- /dev/null +++ b/plugins/generalvclamp.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python + +''' +generalvclamp.py + +Plugin regarding general velocity clamp measurements + +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 numpy as np +import scipy as sp + +import warnings +warnings.simplefilter('ignore', np.RankWarning) + +import lib.curve + +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 + ''' + show_points = self.GetBoolFromConfig('generalvclamp', 'distance', 'show_points') + 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() + plot = self.GetActivePlot() + if active_file.driver.experiment == 'clamp': + self.AppendToOutput('You wanted to use zpiezo perhaps?') + return + dx, unitx, dy, unity = self._delta(message='Click 2 points to measure the distance.', show=show_points, whatset=whatset) + #TODO: pretty format + self.AppendToOutput(str(dx * (10 ** 9)) + ' nm') + + def do_force(self): + ''' + FORCE + (generalvclamp.py) + Measure the force difference (in pN) between two points + --------------- + Syntax: force + ''' + 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() + plot = self.GetActivePlot() + if active_file.driver.experiment == 'clamp': + self.AppendToOutput('This command makes no sense for a force clamp experiment.') + return + dx, unitx, dy, unity = self._delta(whatset=whatset, message='Click 2 points to measure the force.') + #TODO: pretty format + self.AppendToOutput(str(dy * (10 ** 12)) + ' pN') + + 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 + ''' + maxpoint = self.GetBoolFromConfig('generalvclamp', 'forcebase', 'max') + 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 + + if maxpoint: + boundpoints = [] + points = self._measure_N_points(N=2, message='Click 2 points to select the range for maximum detection.', whatset=whatset) + boundpoints = [points[0].index, points[1].index] + boundpoints.sort() + try: + y = min(plot.curves[whatset].y[boundpoints[0]:boundpoints[1]]) + 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?') + else: + points = self._measure_N_points(N=1, message='Click on the point to measure.', whatset=whatset) + y = points[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) + #TODO: pretty format + self.AppendToOutput(str(forcebase * (10 ** 12)) + ' pN') + + 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 + ''' + + fitspan = self.GetIntFromConfig('generalvclamp', 'slope', 'fitspan') + # Decides between the two forms of user input + #TODO: add an option 'mode' with options 'chunk' and 'point' + #TODO: add 'whatset' as option, too + if fitspan == 0: + # Gets the Xs of two clicked points as indexes on the curve curve vector + clickedpoints = [] + points = self._measure_N_points(N=2, message='Click 2 points to select the chunk.', whatset=1) + clickedpoints = [points[0].index, points[1].index] + clickedpoints.sort() + else: + clickedpoints = [] + points = self._measure_N_points(N=1, message='Click on the leftmost point of the chunk (i.e.usually the peak).', whatset=1) + clickedpoints = [points[0].index - fitspan, points[0].index] + + # Calls the function linefit_between + parameters = [0, 0, [], []] + try: + parameters = self.linefit_between(clickedpoints[0], clickedpoints[1]) + except: + self.AppendToOutput('Cannot fit. Did you click the same point twice?') + return + + # Outputs the relevant slope parameter + #TODO: pretty format with units + self.AppendToOutput(''.join(['Slope: ', str(parameters[0])])) + + #TODO: add option to keep previous slope + 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]) + + #add the slope to the plot + slope = lib.curve.Curve() + slope.color = 'red' + slope.label = 'slope' + slope.style = 'plot' + slope.x = xtoplot + slope.y = ytoplot + + #add the clicked points to the plot + points = lib.curve.Curve() + points.color = 'black' + points.label = 'points' + points.size = 10 + points.style = 'scatter' + points.x = clickvector_x + points.y = clickvector_y + + plot.curves.append(slope) + plot.curves.append(points) + + self.UpdatePlot(plot) + + def linefit_between(self, index1, index2, whatset=1): + ''' + 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/plugins/pcluster.ini b/plugins/pcluster.ini new file mode 100644 index 0000000..14eeefc --- /dev/null +++ b/plugins/pcluster.ini @@ -0,0 +1,10 @@ +[pcluster] +auto_fit_nm = 5 +auto_fit_points = 50 +auto_left_baseline = 20 +auto_max_p = 10 +auto_min_p = 0.005 +auto_right_baseline = 20 +auto_slope_span = 20 +baseline_clicks = 0 +temperature = 301 diff --git a/plugins/playlist.ini b/plugins/playlist.ini new file mode 100644 index 0000000..10de454 --- /dev/null +++ b/plugins/playlist.ini @@ -0,0 +1,22 @@ +[genlist] + [[folder]] + default = "" + type = folder + value = R:\Programming\Python\Gui\data\Ecoil good data set 1 week35_surface251_16.5pNnm + + [[filemask]] + default = *.* + type = string + value = *.* + +[loadlist] + [[filename]] + default = test.hkp + type = filename + value = R:\Programming\Python\Gui\untitled2.hkp + +[savelist] + [[filename]] + default = untitled.hkp + type = filename + value = R:\Programming\Python\Gui\untitled2.hkp diff --git a/plugins/playlist.py b/plugins/playlist.py new file mode 100644 index 0000000..41db26e --- /dev/null +++ b/plugins/playlist.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python + +''' +playlist.py + +Playlist 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 glob +import os.path +import wx + +from lib.playlist import Playlist + +class playlistCommands(object): + ''' + Playlist commands to generate, save and load lists of force curves + ''' + + def _plug_init(self): + pass + + def do_genlist(self, filemask='', folder=''): + ''' + GENLIST + Generates a file playlist. + Note it doesn't *save* it: see savelist for this. + + If [input files] is a directory, it will use all files in the directory for playlist. + So: + genlist dir + genlist dir/ + genlist dir/*.* + + are all equivalent syntax. + ------------ + Syntax: genlist [input files] + ''' + if filemask == '': + filemask = self.GetStringFromConfig('playlist', 'genlist', 'filemask') + if folder == '': + folder = self.GetStringFromConfig('playlist', 'genlist', 'folder') + if os.path.isdir(folder): + path = os.path.join(folder, filemask) + #expanding correctly the input list with the glob module :) + files = glob.glob(path) + files.sort() + playlist = Playlist() + for file_to_add in files: + playlist.add_file(file_to_add) + if playlist.count > 0: + playlist.name = self._GetUniquePlaylistName(os.path.basename(folder)) + playlist.reset() + self.AddToPlaylists(playlist) + self.AppendToOutput('Playlist generated.') + self.AppendToOutput(playlist.get_status_string()) + else: + self.AppendToOutput(''.join(['Cannot find folder ', folder])) + + def do_loadlist(self, filename=''): + ''' + LOADLIST + Loads a file playlist + ----------- + Syntax: loadlist [playlist file] + ''' + #TODO: check for duplicate playlists, ask the user for a unique name + #if self.playlist_name in self.playlists: + #activate playlist + + if filename == '': + filename = self.GetStringFromConfig('playlist', 'loadlist', 'filename') + + #add hkp extension if necessary + if not filename.endswith('.hkp'): + filename = ''.join([filename, '.hkp']) + #prefix with 'hookeDir' if just a filename or a relative path + filename = lh.get_file_path(filename) + if os.path.isfile(filename): + #load playlist + playlist_new = Playlist(filename) + if playlist_new.count > 0: + self.AppendToOutput('Playlist loaded.') + self.AddToPlaylists(playlist_new) + self.AppendToOutput(playlist_new.get_status_string()) + else: + message = ''.join(['The file ', filename, ' does not contain any valid curve information.']) + dialog = wx.MessageDialog(None, message, 'Info', wx.OK) + dialog.ShowModal() + else: + message = ''.join(['File ', filename, ' not found.']) + dialog = wx.MessageDialog(None, message, 'Info', wx.OK) + dialog.ShowModal() + + def do_savelist(self, filename=''): + ''' + SAVELIST + Saves the current file playlist on disk. + ------------ + Syntax: savelist [filename] + ''' + + if filename == '': + filename = self.GetStringFromConfig('playlist', 'savelist', 'filename') + + #autocomplete filename if not specified + if not filename.endswith('.hkp'): + filename = filename.join(['.hkp']) + + playlist = self.GetActivePlaylist() + try: + playlist.save(filename) + self.AppendToOutput('Playlist saved.') + except: + self.AppendToOutput('Playlist could not be saved.') + + diff --git a/plugins/plot.ini b/plugins/plot.ini new file mode 100644 index 0000000..d3f5a12 --- /dev/null +++ b/plugins/plot.ini @@ -0,0 +1 @@ + diff --git a/plugins/plot.py b/plugins/plot.py new file mode 100644 index 0000000..beab346 --- /dev/null +++ b/plugins/plot.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +''' +plot.py + +Plot 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 plotCommands(object): + ''' + Plot commands to replot the original data with fits (if applicable) + but without secondary plots (unless they are part of the original data) + ''' + + def _plug_init(self): + pass + + def do_replot(self): + self.UpdatePlot() diff --git a/plugins/procplots.ini b/plugins/procplots.ini new file mode 100644 index 0000000..842abb9 --- /dev/null +++ b/plugins/procplots.ini @@ -0,0 +1,90 @@ +[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 = "[11.0,-1.0,-1.0,-1.0,-1.0,-1.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 = both + +[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 = both + +[procplots] + [[median]] + default = 0 + maximum = 100 + minimum = 0 + type = integer + value = 7 + + [[correct]] + default = True + type = boolean + value = True diff --git a/plugins/procplots.py b/plugins/procplots.py new file mode 100644 index 0000000..7324b77 --- /dev/null +++ b/plugins/procplots.py @@ -0,0 +1,257 @@ +#!/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 +from scipy.signal import medfilt + +from lib.peakspot import conv_dx + +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): + ''' + DERIVPLOT + (procplots.py plugin) + Plots the discrete differentiation of the currently displayed force curve retraction + --------- + Syntax: derivplot + ''' + column = self.GetIntFromConfig('procplots', 'derivplot', 'column') + row = self.GetIntFromConfig('procplots', 'derivplot', 'row') + 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_subtplot(self): + ''' + SUBTPLOT + (procplots.py plugin) + Plots the difference between retraction and extension of the currently displayed curve + ------- + Syntax: subtplot + ''' + #TODO: what is sub_filter supposed to do? + + #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 + +#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=1) + 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.destination.column = column + fft_curve.destination.row = row + fft_curve.title = 'FFT' + fft_curve.units.x = 'frequency' + fft_curve.units.y = 'power' + plot.curves.append(fft_curve) + + self.UpdatePlot(plot) diff --git a/plugins/results.ini b/plugins/results.ini new file mode 100644 index 0000000..ef4cea2 --- /dev/null +++ b/plugins/results.ini @@ -0,0 +1,6 @@ +[show_results] + [[fit_function]] + default = wlc + elements = wlc, fjc, fjcPEG + type = enum + value = wlc diff --git a/plugins/results.py b/plugins/results.py new file mode 100644 index 0000000..8476882 --- /dev/null +++ b/plugins/results.py @@ -0,0 +1,29 @@ +#!/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): + plot = self.GetActivePlot() + if plot is not None: + plot.results.clear() + self.UpdatePlot() + + + def do_show_results(self): + self.UpdatePlot() diff --git a/procplots.py b/procplots.py deleted file mode 100755 index f1cc0a3..0000000 --- a/procplots.py +++ /dev/null @@ -1,251 +0,0 @@ -#!/usr/bin/env python - -''' -PROCPLOTS -Processed plots plugin for force curves. - -Licensed under the GNU GPL version 2 -''' -from libhooke import WX_GOOD -import wxversion -wxversion.select(WX_GOOD) - -import wx -import libhookecurve as lhc -import numpy as np -import scipy as sp -import scipy.signal -import copy - -class procplotsCommands: - - def _plug_init(self): - pass - - def do_derivplot(self,args): - ''' - DERIVPLOT - (procplots.py plugin) - Plots the derivate (actually, the discrete differentiation) of the current force curve - --------- - Syntax: derivplot - ''' - dplot=self.derivplot_curves() - plot_graph=self.list_of_events['plot_graph'] - wx.PostEvent(self.frame,plot_graph(plots=[dplot])) - - def derivplot_curves(self): - ''' - do_derivplot helper function - - create derivate plot curves for force curves. - ''' - dplot=lhc.PlotObject() - dplot.vectors=[] - - for vector in self.plots[0].vectors: - dplot.vectors.append([]) - dplot.vectors[-1].append(vector[0][:-1]) - dplot.vectors[-1].append(np.diff(vector[1])) - - dplot.destination=1 - dplot.units=self.plots[0].units - - return dplot - - def do_subtplot(self,args): - ''' - SUBTPLOT - (procplots.py plugin) - Plots the difference between ret and ext current curve - ------- - Syntax: subtplot - ''' - #FIXME: sub_filter and sub_order must be args - - if len(self.plots[0].vectors) != 2: - print 'This command only works on a curve with two different plots.' - pass - - outplot=self.subtract_curves(sub_order=1) - - plot_graph=self.list_of_events['plot_graph'] - wx.PostEvent(self.frame,plot_graph(plots=[outplot])) - - def subtract_curves(self, sub_order): - ''' - subtracts the extension from the retraction - ''' - xext=self.plots[0].vectors[0][0] - yext=self.plots[0].vectors[0][1] - xret=self.plots[0].vectors[1][0] - yret=self.plots[0].vectors[1][1] - - #we want the same number of points - maxpoints_tot=min(len(xext),len(xret)) - xext=xext[0:maxpoints_tot] - yext=yext[0:maxpoints_tot] - xret=xret[0:maxpoints_tot] - yret=yret[0:maxpoints_tot] - - if sub_order: - ydiff=[yretval-yextval for yretval,yextval in zip(yret,yext)] - else: #reverse subtraction (not sure it's useful, but...) - ydiff=[yextval-yretval for yextval,yretval in zip(yext,yret)] - - outplot=copy.deepcopy(self.plots[0]) - outplot.vectors[0][0], outplot.vectors[1][0] = xext,xret #FIXME: if I use xret, it is not correct! - outplot.vectors[1][1]=ydiff - outplot.vectors[0][1]=[0 for item in outplot.vectors[1][0]] - - return outplot - - -#-----PLOT MANIPULATORS - def plotmanip_median(self, plot, current, customvalue=None): - ''' - does the median of the y values of a plot - ''' - if customvalue: - median_filter=customvalue - else: - median_filter=self.config['medfilt'] - - if median_filter==0: - return plot - - if float(median_filter)/2 == int(median_filter)/2: - median_filter+=1 - - nplots=len(plot.vectors) - c=0 - while cstartvalue: - cutindex=index - else: - break - - plot.vectors[0][0]=plot.vectors[0][0][:cutindex] - plot.vectors[0][1]=plot.vectors[0][1][:cutindex] - - return plot - ''' - - - -#FFT--------------------------- - def fft_plot(self, vector): - ''' - calculates the fast Fourier transform for the selected vector in the plot - ''' - fftplot=lhc.PlotObject() - fftplot.vectors=[[]] - - fftlen=len(vector)/2 #need just 1/2 of length - fftplot.vectors[-1].append(np.arange(1,fftlen).tolist()) - - try: - fftplot.vectors[-1].append(abs(np.fft(vector)[1:fftlen]).tolist()) - except TypeError: #we take care of newer NumPy (1.0.x) - fftplot.vectors[-1].append(abs(np.fft.fft(vector)[1:fftlen]).tolist()) - - - fftplot.destination=1 - - - return fftplot - - - def do_fft(self,args): - ''' - 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) - ''' - - #args parsing - #whatplot = plot to fft - #whatset = set to fft in the plot - select=('select' in args) - if 'top' in args: - whatplot=0 - elif 'bottom' in args: - whatplot=1 - else: - whatplot=0 - whatset=0 - for arg in args: - try: - whatset=int(arg) - except ValueError: - pass - - if select: - points=self._measure_N_points(N=2, whatset=whatset) - boundaries=[points[0].index, points[1].index] - boundaries.sort() - y_to_fft=self.plots[whatplot].vectors[whatset][1][boundaries[0]:boundaries[1]] #y - else: - y_to_fft=self.plots[whatplot].vectors[whatset][1] #y - - fftplot=self.fft_plot(y_to_fft) - fftplot.units=['frequency', 'power'] - plot_graph=self.list_of_events['plot_graph'] - wx.PostEvent(self.frame,plot_graph(plots=[fftplot])) - \ No newline at end of file diff --git a/hooke.jpg b/resources/hooke.jpg old mode 100755 new mode 100644 similarity index 100% rename from hooke.jpg rename to resources/hooke.jpg diff --git a/resources/microscope.ico b/resources/microscope.ico new file mode 100644 index 0000000000000000000000000000000000000000..6841f382d049b126fc31d6b8a0db24f951a221fd GIT binary patch literal 13270 zcmeHN37AyXl|CgZ8k68mCNoYZNp~FM5MzQdj&a-|CgYf(jpNrcE@*=xKte=7wg5s4 zh=^=L2N39mtw98u9h$8gXqE<~*;j=Y0a=>r>guZM>gwvg^Z&Qrt$tPgs%iZ)`7+;^ z_kFkCd-tAu&fVTQ|2d~bTymqNrV8#i$-kB3l#GNZ^ZFN5#5 z`d*gXdY9vlg8R2|PaWWq`!YT9OqN%MF80a7T|wD=G$cDd4#{qmy{AKxw>2m~9)|Iv z+=X#HjPbn!eq-18WZAx;>^>cqeW06zarT(wlgF?QujF_!FQ5D#?;WRlLBA89M`Y^epmfcvk~3E#^50pN z^1*Jb#pzl(aJE*CU5Lo(%Mp2XewB<|5s=Tlby6L!m#bBE^5__^xFPE!$T)JoR!&`t z$l0rP7*maS0(J7MiIviRQl(S|>txVkzw9SJ$h%NcCsQ^BC1+c;gd_Fx2>7hPdhEft z4t`cECoe|i)AEQMy-+Kq=o74|m#3#xO7W?%9LD%RhMdnlb@JA#fOMQzDItuf-y*+k z!dSM0=E(Voe2g)l1I<)-P_iLs@!pV3+7Oi4ShtsF`sDfdeKKrWKsI9i+P&kI9}MA{ zsKmJpND1aXa%DjJec+e+dL?tVha_!srPN}qJF%ue8&x6G-PMv?%>Ap85m*;*uwDWf zXH9LrMCugIaZodRhmT|*J$C%W$x|PHGA_P-SIO>oQ6TeiF-+Sj3+pT-_6qwT;rLy|GoYARsmsj+Qt+$QE3okz3=_Rx$wi=RWJO0me z&)XZ7x0O$lwJR;IIB$DD4X;miDvPh%_OC}(MDRPa=(D7bm^)CZ2@+5D{pe)Ly zY~mC^AGrs`;FP@U@M5k+oB~@*oO0(N4{(Q91~2tPCr9n@?XdU6F~lVgj`C>xJa&yA z_Q(e;;?;QIe$;o%10Dh1n6o1&V^;aKj->4;PMA?dyb*#P55Yc%f0roMFICtM-Rv7wq&t;D`NXVT{oaJM7We>bdDrjPwHT zQ#bozmqM}+G&{h15-<_5MLx#17j?%kgn?DUz+rya!>}AY8`cIMWZg5!l~)DfdNy{kT6ttgjF28O3Hfz-bpTW?~3p z6ynFe3;nVP_HGd{;vn!M2Au`ElNZC<9=68bqhCB7m)p?#)(--&%7c6kxw_xS5% z3HVRMx)LuDkBkRS_}+l48hc%_u)q+^eFe@6u@$i+aZzgjt8yH2yuebl<-eO-rSTT= zm_JmHHLH}?z%Y}+lQ@g`hZvAJq&si|G0p*ul^BHB=gIdfH4d2ytUyfCW_SfKRYZP* z^&kc%b|e-ew)rWrVlj9Ti=6^CBYq<;BF-$qo)eQ@tgHjZ3`+?3hxlYI){D55Sd$oL zD6q#{z$Y`eRBJp$Tzd9uMEV1_5r@44{F?<#NGub@owYJ}iC<$=;w<7%;$PxBVpHNH za}`>bd5_*rE$x=Z1>@?}>ie8;n^ z;%vTmi>}wa^wWjUFJ3CRyyZ%?n)T7A1!bR}9r@X&^Jfp44*Gs_CP2XHO(V{@3aonG zj+7jocCO&q@vH(E=o8Tf{d-`+)RKdEI)sM9w))cj*2`{mu+bcO-$K08DU1!gkYpb^!X3U(G zoijOFPjX!PrS?x#r%iufKSpbKlGD-DmqW#^*Pb0bMq3T|nyu>JaLo<)tCm zAFtLK^a=QV(bu4^x&(bhpNoF!eEP1?tHvj!uAnbVyK(_KhRiSrs2l%w{cQ#Qzpp?CUTON$kN9!j_Rl~5M!SEzIW0}s-1yyF@A}Tqzy9P+Ad1rd zK)&|S-A~-dPP*^?|9}@CxH87E&nn0s!1MnS&g9-k+PQJDry0r0xkfH z;0t#unNZ7_uHw!`En1|akFYQGY${4S6qkMi4HfT~yr5UwpbSTGqo^c>ZKOeD_0MyG zR|ZNcN-~+r>e(mb%Z_63l0XXLM{d{~h4_y`Tu1?HE7%c*_>w}*Od)2e5JysoqbbCR z6ykLXwkB~rZj1vv()PN`({l!%)$^rX#PSsEuY&zm zz}HUlOvCYhx8tEtvP4T7q+`mAMi?6$GvED9#H2_)(3b;Ax^Hi z=A1WW!w*uxAqsI>1Og2%T)B7`K43N>}_sz487_fp5st}vjYmV5xg1@7H{S;#33UPXc*tSA^ zSi!eahz~o-wAYnVK4iCXnmc0O3O=en|NPE?BlS7w8RvJ-a|$0(A#SbL2Y#(W+*%=S ztq`|Xz;OWlEgflX z@+jMFdymmm*YV854^@cs$Lf)1Q7lem#0b zN)B-_zL=a zqy0h7CT<-6SMoVt+BM4OxkfHYA(o=grhP8()AYQN+xoN4A9+HBJXRz5{N52a(`x`< zPwPSCwzQ4po!^Xod|&q2tUq<0S%1W{6!L8f`8I`on?n3Y>t*EK^xSzz{;fIk-OU*n zb&ffI@b$FLLq1R;AE@9bYdwsdpyE1^AMf0UYw)J*;n$$iJdmr@c9wbfZ!9g}kb^d5Ga!XRuaL-ypY&Z=*)90rIN~`Bg2i zne*2;6|$PNKV~e%9^3=kJ^1_zd0>SYtG*+yppXkzh&?L!@d~+Mg}if9bJzCAvOyU} zMpJj>qZQ(Pifh1gh`6e@yR?1CQ8y*asVxqRL9V+g*=$38Tp{PK&jGO(afcZL^5sr4 zlGVlG4{HrlAfw`1W1wB4F62J&ESfPOrkYGvVm)Ov!e7=HI$TRW_msHPU_Y({_u?9R zZ6AXp{-k_fke|`i7?7t{_@4vtP)M%>;$r$d5Gyy;&uWX?^Qgqezo6whG`Sb}4~9a1 zK%WPmNn+AuxWMAou}`zs+evO>9eA}t8IHpFXgRct^a~Jk)V?Uj!8X!py_U$s z>HV)OubHZc*ct!T`XU$R+W{pPVZMea;d z*HO>Y=cA8DTZ$Myrf&p7d?PfdM}e1gW-wCTv*!Jn#A$ld8Wdz#$ArGqK4fMNtPU-L3X}BgFx4Dr2h- zY@=(`A9-5CUf-LdP1Z(V5_uhkT(9=q?R1GglfA9E&&V%pOiZ7M=bvk3`f~jAgZZtn znEEerM_yaw9O7M`dE~Yga@z{IZCy|P=4wKQ;~sDvj3v>pMNSPdl!!j}jO!ui9@}3t zUdl1gwmW&}+TP=6kk8j^K&(itik!ZVlW4>&TXmq@l zu_fwdt`lW&9>%{-)(`bD{btFS=tPVEW@tUcSSe+29$YK@N5gTx7LPdnO#Zykmg^*g z@nPC7&Vy^k{h-Zpq9ualoh@LypynzvH+QB|%18lrt!r2mbd0 zd*YA9I*D(oM@_p$8@JwxJe}Ur&**P0i}sT?lW|4Fw^^4<8mGRd?k}5{Pc>p2^765o z_y>$T1I3M^5(?`w;;9?nGM5KZfR{^|*C6&Gh9!okFCUF-$6{=y;FHqOQ=h*S*s==h z59Ik3a_~C;!Lv<0#K^=0Kq1GU$b)^|raxm8j4Kn@Fs8!X67xOC>1$j-pUTi$_gG)P z^LwK{!+!(fd@tI0b0zdo= (value+step) and xvalue <= (value-step): - to_avg.append(yvalue) - y_avg.append(mean(to_avg)) - - print 'len x len y' - print len(x_avg), len(y_avg) - print y_avg - - avg_object=lhc.PlotObject() - avg_object.vectors=[[x_avg, y_avg]] - avg_object.normalize_vectors() - avg_object.units=self.plots[0].units - avg_object.title="Average curve" - avg_object.destination=1 - - plot_graph=self.list_of_events['plot_graph'] - PostEvent(self.frame,plot_graph(plots=[avg_object])) \ No newline at end of file diff --git a/test.hkp b/test.hkp deleted file mode 100644 index da648f4..0000000 --- a/test.hkp +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/tutorial.py b/tutorial.py deleted file mode 100755 index f9f2f33..0000000 --- a/tutorial.py +++ /dev/null @@ -1,538 +0,0 @@ -#!/usr/bin/env python - -''' -TUTORIAL PLUGIN FOR HOOKE - -This plugin contains example commands to teach how to write an Hooke plugin, including description of main Hooke -internals. -(c)Massimo Sandal 2007 -''' - -import libhookecurve as lhc - -import numpy as np - -''' -SYNTAX OF DATA TYPE DECLARATION: - type = type of object - [ type ] = list containing objects of type - {typekey:typearg} = dictionary with keys of type typekey and args of type typearg - ( type ) = tuple containing objects of type -''' - - -class tutorialCommands: - ''' - Here we define the class containing all the Hooke commands we want to define - in the plugin. - - Notice the class name!! - The syntax is filenameCommands. That is, if your plugin is pluggy.py, your class - name is pluggyCommands. - - Otherwise, the class will be ignored by Hooke. - ''' - - def _plug_init(self): - ''' - This is the plugin initialization. - When Hooke starts and the plugin is loaded, this function is executed. - If there is something you need to do when Hooke starts, code it in this function. - ''' - print 'I am the Tutorial plugin initialization!' - - #Here we initialize a local configuration variable; see plotmanip_absvalue() for explanation. - self.config['tutorial_absvalue']=0 - pass - - def do_nothing(self,args): - ''' - This is a boring but working example of an actual Hooke command. - A Hooke command is a function of the xxxxCommands class, which is ALWAYS defined - this way: - - def do_nameofcommand(self,args) - - *do_ is needed to make Hooke understand this function is a command - *nameofcommand is how the command will be called in the Hooke command line. - *self is, well, self - *args is ALWAYS needed (otherwise Hooke will crash executing the command). We will see - later what args is. - - Note that if you now start Hooke with this plugin activated and you type in the Hooke command - line "help nothing" you will see this very text as output. So the help of a command is a - string comment below the function definition, like this one. - - Commands usually return None. - ''' - print 'I am a Hooke command. I do nothing.' - - def do_printargs(self,args): - ''' - This command prints the args you give to it. - args is always a string, that contains everything you write after the command. - So if you issue "mycommand blah blah 12345" args is "blah blah 12345". - - Again, args is needed in the definition even if your command does not use it. - ''' - print 'You gave me those args: '+args - - def help_tutorial(self): - ''' - This is a help function. - If you want a help function for something that is not a command, you can write a help - function like this. Calling "help tutorial" will execute this function. - ''' - print 'You called help_tutorial()' - - def do_environment(self,args): - ''' - This plugin contains a panoramic of the Hooke command line environment variables, - and prints their current value. - ''' - - '''self.current_list - TYPE: [ libhookecurve.HookeCurve ], len=variable - contains the actual playlist of Hooke curve objects. - Each HookeCurve object represents a reference to a data file. - We will see later in detail how do they work. - ''' - print 'current_list length:',len(self.current_list) - print 'current_list 0th:',self.current_list[0] - - '''self.pointer - TYPE: int - contains the index of - the current curve in the playlist - ''' - print 'pointer: ',self.pointer - - '''self.current - TYPE: libhookecurve.HookeCurve - contains the current curve displayed. - We will see later how it works. - ''' - print 'current:',self.current - - '''self.plots - TYPE: [ libhookecurve.PlotObject ], len=1,2 - contains the current default plots. - Each PlotObject contains all info needed to display - the plot: apart from the data vectors, the title, destination - etc. - Usually self.plots[0] is the default topmost plot, self.plots[1] is the - accessory bottom plot. - ''' - print 'plots:',self.plots - - '''self.config - TYPE: { string:anything } - contains the current Hooke configuration variables, in form of a dictionary. - ''' - print 'config:',self.config - - '''self.plotmanip - TYPE: [ function ] - Contains the ordered plot manipulation functions. - These functions are called to modify the default plot by default before it is plotted. - self.plots contains the plot passed through the plot manipulators. - We will see it better later. - *YOU SHOULD NEVER MODIFY THAT* - ''' - print 'plotmanip: ',self.plotmanip - - '''self.drivers - TYPE: [ class ] - Contains the plot reading drivers. - *YOU SHOULD NEVER MODIFY THAT* - ''' - print 'drivers: ',self.drivers - - '''self.frame - TYPE: wx.Frame - Contains the wx Frame of the GUI. - ***NEVER, EVER TOUCH THAT.*** - ''' - print 'frame: ',self.frame - - '''self.list_of_events - TYPE: { string:wx.Event } - Contains the wx.Events to communicate with the GUI. - Usually not necessary to use it, unless you want - to create a GUI plugin. - ''' - print 'list of events:',self.list_of_events - - '''self.events_from_gui - TYPE: Queue.Queue - Contains the Queue where data from the GUI is put. - Usually not necessary to use it, unless you want - to create a GUI plugin. - ''' - print 'events from gui:',self.events_from_gui - - '''self.playlist_saved - TYPE: Int (0/1) ; Boolean - Flag that tells if the playlist has been saved or not. - ''' - print 'playlist saved:',self.playlist_saved - - '''self.playlist_name - TYPE: string - Name of current playlist - ''' - print 'playlist name:',self.playlist_name - - '''self.notes_saved - TYPE: Int (0/1) ; Boolean - Flag that tells if the playlist has been saved or not. - ''' - print 'notes saved:',self.notes_saved - - - def do_myfirstplot(self,args): - ''' - In this function, we see how to create a PlotObject and send it to the screen. - ***Read the code of PlotObject in libhookecurve.py before!***. - ''' - - #We generate some interesting data to plot for this example. - xdata1=np.arange(-5,5,0.1) - xdata2=np.arange(-5,5,0.1) - ydata1=[item**2 for item in xdata1] - ydata2=[item**3 for item in xdata2] - - #Create the object. - #The PlotObject class lives in the libhookecurve library. - myplot=lhc.PlotObject() - ''' - The *data* of the plot live in the .vectors list. - - plot.vectors is a multidimensional array: - plot.vectors[0]=set1 - plot.vectors[1]=set2 - plot.vectors[2]=sett3 - 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] - ''' - #Pour 0-th dataset into myplot: - myplot.add_set(xdata1,ydata1) - - #Pour 1-st dataset into myplot: - myplot.add_set(xdata2,ydata2) - - #Add units to x and y axes - #units=[string, string] - myplot.units=['x axis','y axis'] - - #Where do we want the plot? 0=top, 1=bottom - myplot.destination=1 - - '''Send it to the GUI. - Note that you *have* to encapsulate it into a list, so you - have to send [myplot], not simply myplot. - - You can also send more two plots at once - self.send_plot([plot1,plot2]) - ''' - self._send_plot([myplot]) - - - def do_myfirstscatter(self,args): - ''' - How to draw a scatter plot. - ''' - #We generate some interesting data to plot for this example. - xdata1=np.arange(-5,5,1) - xdata2=np.arange(-5,5,1) - ydata1=[item**2 for item in xdata1] - ydata2=[item**3 for item in xdata2] - - myplot=lhc.PlotObject() - myplot.add_set(xdata1,ydata1) - myplot.add_set(xdata2,ydata2) - - - #Add units to x and y axes - myplot.units=['x axis','y axis'] - - #Where do we want the plot? 0=top, 1=bottom - myplot.destination=1 - - '''None=standard line plot - 'scatter'=scatter plot - By default, the styles attribute is an empty list. If you - want to define a scatter plot, you must define all other - plots as None or 'scatter', depending on what you want. - - Here we define the second set to be plotted as scatter, - and the first to be plotted as line. - - Here we define also the colors to be the default Matplotlib colors - ''' - myplot.styles=[None,'scatter'] - myplot.colors=[None,None] - self._send_plot([myplot]) - - - def do_clickaround(self,args): - ''' - Here we click two points on the curve and take some parameters from the points - we have clicked. - ''' - - ''' - points = self._measure_N_points(N=Int, whatset=Int) - *N = number of points to measure(1...n) - *whatset = data set to measure (0,1...n) - *points = a list of ClickedPoint objects, one for each point requested - ''' - points=self._measure_N_points(N=2,whatset=1) - print 'You clicked the following points.' - - ''' - These are the absolute coordinates of the - point clicked. - [float, float] = x,y - ''' - print 'Absolute coordinates:' - print points[0].absolute_coords - print points[1].absolute_coords - print - - ''' - These are the coordinates of the points - clicked, remapped on the graph. - Hooke looks at the graph point which X - coordinate is next to the X coordinate of - the point measured, and uses that point - as the actual clicked point. - [float, float] = x,y - ''' - print 'Coordinates on the graph:' - print points[0].graph_coords - print points[1].graph_coords - print - - ''' - These are the indexes of the clicked points - on the dataset vector. - ''' - print 'Index of points on the graph:' - print points[0].index - print points[1].index - - - def help_thedifferentplots(self): - ''' - The *three* different default plots you should be familiar with - in Hooke. - - Each plot contains of course the respective data in their - vectors attribute, so here you learn also which data access for - each situation. - ''' - print ''' - 1. THE RAW, CURRENT PLOTS - - self.current - --- - Contains the current libhookecurve.HookeCurve container object. - A HookeCurve object defines only two default attributes: - - * self.current.path = string - The path of the current displayed curve - - * self.current.curve = libhookecurve.Driver - The curve object. This is not only generated by the driver, - this IS a driver instance in itself. - This means that in self.current.curve you can access the - specific driver APIs, if you know them. - - And defines only one method: - * self.current.identify() - Fills in the self.current.curve object. - See in the cycling tutorial. - - ***** - The REAL curve data actually lives in: - --- - * self.current.curve.default_plots() = [ libhooke.PlotObject ] - Contains the raw PlotObject-s, as "spitted out" by the driver, without any - intervention. - This is as close to the raw data as Hooke gets. - - One or two plots can be spit out; they are always enclosed in a list. - ***** - - Methods of self.current.curve are: - --- - - * self.current.curve.is_me() - (Used by identify() only.) - - * self.current.curve.close_all() - Closes all driver open files; see the cycling tutorial. - ''' - - print ''' - 2. THE PROCESSED, DEFAULT PLOT - - The plot that is spitted out by the driver is *not* the usual default plot - that is displayed by calling "plot" at the Hooke prompt. - - This is because the raw, driver-generated plot is usually *processed* by so called - *plot processing* functions. We will see in the tutorial how to define - them. - - For example, in force spectroscopy force curves, raw data are automatically corrected - for deflection. Other data can be, say, filtered by default. - - The default plots are accessible in - self.plots = [ libhooke.PlotObject ] - - self.plots[0] is usually the topmost plot - self.plots[1] is usually the bottom plot (if present) - ''' - - print ''' - 3. THE PLOT DISPLAYED RIGHT NOW. - - Sometimes the plots you are displaying *right now* is different from the previous - two. You may have a fit trace, you may have issued some command that spits out - a custom plot and you want to rework that, whatever. - - You can obtain in any moment the plot currently displayed by Hooke by issuing - - PlotObject = self._get_displayed_plot(dest) - * dest = Int (0/1) - dest=0 : top plot - dest=1 : bottom plot - ''' - - - def do_cycling(self,args): - ''' - Here we cycle through our playlist and print some info on the curves we find. - Cycling through the playlist needs a bit of care to avoid memory leaks and dangling - open files... - - Look at the source code for more information. - ''' - - def things_when_cycling(item): - ''' - We encapsulate here everything has to open the actual curve file. - By doing it all here, we avoid to do acrobacies when deleting objects etc. - in the main loop: we do the dirty stuff here. - ''' - - ''' - identify() - - This method looks for the correct driver in self.drivers to use; - and puts the curve content in the .curve attribute. - Basically, until identify() is called, the HookeCurve object - is just an empty shell. When identify() is called (usually by - the Hooke plot routine), the HookeCurve object is "filled" with - the actual curve. - ''' - - item.identify(self.drivers) - - ''' - After the identify(), item.curve contains the curve, and item.curve.default_plots() behaves exactly like - self.current.curve.default_plots() -but for the given item. - ''' - itplot=item.curve.default_plots() - - print 'length of X1 vector:',len(itplot[0].vectors[0][0]) #just to show something - - ''' - The following three lines are a magic spell you HAVE to do - before closing the function. - (Otherwise you will be plagued by unpredicatable, system-dependent bugs.) - ''' - item.curve.close_all() #Avoid open files dangling - del item.curve #Avoid memory leaks - del item #Just be paranoid. Don't ask. - - return - - - c=0 - for item in self.current_list: - print 'Looking at curve ',c,'of',len(self.current_list) - things_when_cycling(item) - c+=1 - - return - - - - def plotmanip_absvalue(self, plot, current, customvalue=None): - ''' - This function defines a PLOT MANIPULATOR. - A plot manipulator is a function that takes a plot in input, does something to the plot - and returns the modified plot in output. - The function, once plugged, gets automatically called everytime self.plots is updated - - For example, in force spectroscopy force curves, raw data are automatically corrected - for deflection. Other data can be, say, filtered by default. - - To create and activate a plot manipulator you have to: - * Write a function (like this) which name starts with "plotmanip_" (just like commands - start with "do_") - * The function must support four arguments: - self : (as usual) - plot : a plot object - current : (usually not used, deprecated) - customvalue=None : a variable containing custom value(s) you need for your plot manipulators. - * The function must return a plot object. - * Add an entry in hooke.conf: if your function is "plotmanip_something" you will have - to add in the plotmanips section: example - - - - - - - - - Important: Plot manipulators are *in pipe*: each plot manipulator output becomes the input of the next one. - The order in hooke.conf *is the order* in which plot manipulators are connected, so in the example above - we have: - self.current.curve.default_plots() --> detriggerize --> correct --> median --> something --> self.plots - ''' - - ''' - Here we see what is in a configuration variable to enable/disable the plot manipulator as user will using - the Hooke "set" command. - Typing "set tutorial_absvalue 0" disables the plot manipulator; typing "set tutorial_absvalue 1" will enable it. - ''' - if not self.config['tutorial_absvalue']: - return plot - - #We do something to the plot, for demonstration's sake - #If we needed variables, we would have used customvalue. - plot.vectors[0][1]=[abs(i) for i in plot.vectors[0][1]] - plot.vectors[1][1]=[abs(i) for i in plot.vectors[1][1]] - - #Return the plot object. - return plot - - -#TODO IN TUTORIAL: -#how to add lines to an existing plot!! -#peaks -#configuration files -#gui plugins diff --git a/tutorialdriver.py b/tutorialdriver.py deleted file mode 100644 index b16512a..0000000 --- a/tutorialdriver.py +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env python - -''' -tutorialdriver.py - -TUTORIAL DRIVER FOR HOOKE - -Example driver to teach how to write a driver for data types. -(c)Massimo Sandal 2008 -''' - -''' -Here we define a (fake) file format that is read by this driver. The file format is as following: - -TUTORIAL_FILE -PLOT1 -X1 -n1 -n2 -... -nN -Y1 -n1 -n2 -... -nN -X2 -n1 -n2 -.. -nN -Y2 -n1 -n2 -.. -nN -PLOT2 -X1 -... -Y1 -... -X2 -... -Y2 -... -END -that is, two plots with two datasets each. -''' - -import libhookecurve as lhc #We need to import this library to define some essential data types - -class tutorialdriverDriver(lhc.Driver): - ''' - This is a *generic* driver, not a specific force spectroscopy driver. - See the written documentation to see what a force spectroscopy driver must be defined to take into account Hooke facilities. - - Our driver is a class with the name convention nameofthedriverDriver, where "nameofthedriver" is the filename. - The driver must inherit from the parent class lhc.Driver, so the syntax is - class nameofthedriverDriver(lhc.Driver) - ''' - def __init__(self, filename): - ''' - THIS METHOD MUST BE DEFINED. - The __init__ method MUST call the filename, so that it can open the file. - ''' - self.filename=filename #self.filename can always be useful, and should be defined - self.filedata = open(filename,'r') #We open the file - ''' - In this case, we have a data format that is just a list of ASCII values, so we can just divide that in rows, and generate a list - with each item being a row. - Of course if your data files are binary, or follow a different approach, do whatever you need. :) - ''' - self.data = list(self.filedata) - self.filedata.close() #remember to close the file - - '''These are two strings that can be used by Hooke commands/plugins to understand what they are looking at. They have no other - meaning. They have to be somehow defined however - commands often look for those variables. - - self.filetype should contain the name of the exact filetype defined by the driver (so that filetype-specific commands can know - if they're dealing with the correct filetype) - self.experiment should contain instead the type of data involved (for example, various drivers can be used for force-clamp experiments, - but hooke commands could like to know if we're looking at force clamp data, regardless of their origin, and not other - kinds of data) - - Of course, all other variables you like can be defined in the class. - ''' - self.filetype = 'tutorial' - self.experiment = 'generic' - - def is_me(self): - ''' - THIS METHOD MUST BE DEFINED. - RETURNS: Boolean (True or False) - This method must be an heuristic that looks at the file content and decides if the file can be opened by the driver itself. - It returns True if the file opened can be interpreted by the current driver, False otherwise. - Defining this method allows Hooke to understand what kind of files we're looking at automatically. - - We have to re-open/re-close the file here. - ''' - - myfile=open(self.filename, 'r') - headerline=myfile.readlines()[0] #we take the first line - myfile.close() - - ''' - Here, our "magic fingerprint" is the TUTORIAL_FILE header. Of course, depending on the data file, you can have interesting - headers, or patterns, etc. that you can use to guess the data format. What matters is successful recognizing, and returning - a boolean (True/False). - ''' - if headerline[:-1]=='TUTORIAL_FILE': #[:-1], otherwise the return character is included in the line - return True - else: - return False - - 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). - - 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 - - return vectors - - def close_all(self): - ''' - THIS METHOD MUST BE DEFINED. - This method is a precaution method that is invoked when cycling to avoid eventually dangling open files. - In this method, all file objects defined in the driver must be closed. - ''' - self.filename.close() - - - 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 libhookecurve.py source code to see how PlotObjects are defined and work in detail. - ''' - gen_vectors=self._generate_vectors() - - #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=[] - - ''' - 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. - - 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]) - - other_plot.add_set(gen_vectors['PLOT2'][0],gen_vectors['PLOT2'][1]) - other_plot.add_set(gen_vectors['PLOT2'][2],gen_vectors['PLOT2'][3]) - - ''' - 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. - - for each plot. - Again, see libhookecurve.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] \ No newline at end of file diff --git a/viewer.py b/viewer.py deleted file mode 100644 index 42f4b6d..0000000 --- a/viewer.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python - -''' -Viewer test case - -Copyright (C) 2008 Alberto Gomez-Casado (University of Twente). - -This program is released under the GNU General Public License version 2. -''' - - -import libviewer as lview -import libinput as linput - -class viewerCommands: - - def _plug_init(self): - self.viewerlist=[] - #we keep a list of different viewers so it's possible to retrieve different data - #or process the same data differently - - - def do_vwnew(self,args): - #creates a new viewer - self.viewerlist.append(lview.Ascii(self.outlet)) - dt=linput.safeinput('What type of data will this viewer handle? (force/distance/all)',['force', 'distance', 'all']) - #TODO update types, make a list somewhere? - print dt - self.viewerlist[-1].setdtype(dt) - - - def do_vwaction(self,args): - ''' - triggers default action of viewer number n (default 0) - ''' - - if len(args)==0: - args=0 - self.viewerlist[int(args)].action() \ No newline at end of file -- 2.26.2